function elementFromChar(legend, ch) {
if (ch == " ")
return null;
var element = new legend[ch]();
element.originChar = ch;
return element;
}
function World(map, legend) {
var grid = new Grid(map[0].length, map.length);
this.grid = grid;
this.legend = legend;
map.forEach(function(line, y) {
for (var x = 0; x < line.length; x++)
grid.set(new Vector(x, y),
elementFromChar(legend, line[x]));
});
}
В elementFromChar
мы сначала создаём экземпляр нужного типа, находя конструктор символа и применяя к нему new
. Потом добавляем свойство originChar
, чтобы было просто выяснить, из какого символа элемент был создан изначально.
Нам понадобится это свойство originChar
при изготовлении мирового метода toString
. Метод строит карту в виде строки из текущего состояния мира, проходя двумерным циклом по клеткам сетки.
function charFromElement(element) {
if (element == null)
return " ";
else
return element.originChar;
}
World.prototype.toString = function() {
var output = "";
for (var y = 0; y < this.grid.height; y++) {
for (var x = 0; x < this.grid.width; x++) {
var element = this.grid.get(new Vector(x, y));
output += charFromElement(element);
}
output += "\n";
}
return output;
};
Стена wall
– простой объект. Используется для занятия места и не имеет метода act
.
function Wall() {}
Проверяя объект World
, создав экземпляр с использованием плана, заданного в начале главы, и затем вызвав его метод toString
, мы получим очень похожую на этот план строку.
var world = new World(plan, {"#": Wall, "o": BouncingCritter});
console.log(world.toString());
// → ############################
// # # # o ##
// # #
// # ##### #
// ## # # ## #
// ### ## # #
// # ### # #
// # #### #
// # ## o #
// # o # o ### #
// # # #
// ############################
this и его область видимости
В конструкторе World
есть вызов forEach
. Хочу отметить, что внутри функции, передаваемой в forEach
, мы уже не находимся непосредственно в области видимости конструктора. Каждый вызов функции получает своё пространство имён, поэтому this
внутри неё уже не ссылается на создаваемый объект, на который ссылается this
снаружи функции. И вообще, если функция вызывается не как метод, this
будет относиться к глобальному объекту.
Значит, мы не можем писать this.grid
для доступа к сетке изнутри цикла. Вместо этого внешняя функция создаёт локальную переменную grid
, через которую внутренняя функция получает доступ к сетке.
Это промах в дизайне JavaScript. К счастью, в следующей версии есть решение этой проблемы. А пока есть пути обхода. Обычно пишут var self = this
и после этого работают с переменной self
.
Другое решение – использовать метод bind
, который позволяет привязаться к конкретному объекту this
.
var test = {
prop: 10,
addPropTo: function(array) {
return array.map(function(elt) {
return this.prop + elt;
}.bind(this));
}
};
console.log(test.addPropTo([5]));
// → [15]
Функция, передаваемая в map
– результат привязки вызова, и посему её this
привязан к первому аргументу, переданному в bind
, то есть переменной this
внешней функции (в которой содержится объект test
).
Большинство стандартных методов высшего порядка у массивов, таких как forEach
и map
, принимают необязательный второй аргумент, который тоже можно использовать для передачи this
при вызовах итерационной функции. Вы могли бы написать предыдущий пример чуть проще:
var test = {
prop: 10,
addPropTo: function(array) {
return array.map(function(elt) {
return this.prop + elt;
}, this); // ← без bind
}
};
console.log(test.addPropTo([5]));
// → [15]
Это работает только с теми функциями высшего порядка, у которых есть такой контекстный параметр. Если нет – приходится использовать другие упомянутые подходы.