Марейн Хавербеке - Выразительный JavaScript
- Название:Выразительный JavaScript
- Автор:
- Жанр:
- Издательство:неизвестно
- Год:неизвестен
- ISBN:978-1593275846
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Марейн Хавербеке - Выразительный JavaScript краткое содержание
В процессе чтения вы познакомитесь с основами программирования и, в частности, языка JavaScript, а также выполните несколько небольших проектов. Один из самых интересных проектов — создание своего языка программирования.
Выразительный JavaScript - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Конструкция || "s"
в методе act
нужна, чтобы this.direction
не получил null
, в случае если существо забилось в угол без свободного пространства вокруг – например, окружено другими существами.
Мировой объект
Теперь можно приступать к мировому объекту World
. Конструктор принимает план (массив строк, представляющих сетку мира) и объект legend
. Это объект, сообщающий, что означает каждый из символов карты. В нём есть конструктор для каждого символа – кроме пробела, который ссылается на null
(представляющий пустое пространство).
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]
Это работает только с теми функциями высшего порядка, у которых есть такой контекстный параметр. Если нет – приходится использовать другие упомянутые подходы.
В нашей собственной функции высшего порядка мы можем включить поддержку контекстного параметра, используя метод call
для вызова функции, переданной в качестве аргумента. К примеру, вот вам метод forEach
для нашего типа Grid
, вызывающий заданную функцию для каждого элемента сетки, который не равен null
или undefined
:
Grid.prototype.forEach = function(f, context) {
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var value = this.space[x + y * this.width];
if (value != null)
f.call(context, value, new Vector(x, y));
}
}
};
Оживляем мир
Следующий шаг – создание метода turn
(ход) для мирового объекта, дающего существам возможность действовать. Он будет обходить сетку методом forEach
, и искать объекты, у которых есть метод act
. Найдя объект, turn
вызывает этот метод, получая объект action
и производит это действие, если оно допустимо. Пока мы понимаем только действие “move”
.
Есть одна возможная проблема. Догадаетесь, какая? Если мы позволим существам двигаться по мере того, как мы их перебираем, они могут перейти на клетку, которую мы ещё не обработали, и тогда мы позволим им сдвинуться ещё раз, когда очередь дойдёт до этой клетки. Таким образом, нам надо хранить массив существ, которые уже сделали свой шаг, и игнорировать их при повторном проходе.
World.prototype.turn = function() {
var acted = [];
this.grid.forEach(function(critter, vector) {
if (critter.act && acted.indexOf(critter) == -1) {
acted.push(critter);
this.letAct(critter, vector);
}
}, this);
};
Второй параметр метода forEach
используется для доступа к правильной переменной this
во внутренней функции. Метод letAct
содержит логику, которая позволяет существам двигаться.
World.prototype.letAct = function(critter, vector) {
var action = critter.act(new View(this, vector));
if (action && action.type == "move") {
var dest = this.checkDestination(action, vector);
if (dest && this.grid.get(dest) == null) {
this.grid.set(vector, null);
this.grid.set(dest, critter);
}
}
};
World.prototype.checkDestination = function(action, vector) {
if (directions.hasOwnProperty(action.direction)) {
Интервал:
Закладка: