В первой части статьи рассматривались структуры execution context, lexical environment и объекты Function. Вторая часть посвящена использованию this.
Как работает this
В execution context кроме VariableEnvironment есть поле ThisBinding. При поиске обычных переменных используется VariableEnvironment, при обращении через this – ThisBinding. ThisBinding устанавливается в execution context в зависимости от способа вызова функции. Если функция вызвана через точку o.f(), то ThisBinding будет указывать на объект o. Во всех остальных случаях ThisBinding будет ссылаться на global object. При этом функция может быть одна и та же.
Например, создадим функцию print и объект o, а затем добавим ссылку на функцию в объект. Получается, что функцию можно вызывать как print()
, так и как o.print()
. ThisBinding при этом будет различаться. Если скопировать ссылку на o.print в новую переменную print2, то при вызове print2 ThisBinding будет указывать на глобальный объект, несмотря на то, что ссылка была взята из объекта.
Еще раз, если вызов "через точку", то ThisBinding указывает на то, что до точки. В противном случае, ThisBinding ссылается на global object.
Подробнее про то, как устанавливается this при вызове функции, можно прочитать в следующих разделах стандарта:
Что происходит при вызове setTimeout
При использовании callback’ов передается ссылка на функцию, но не на объект. Поэтому при вызове callback’а this будет указывать на global object.
В случае с setTimout первым аргументом передается ссылка на функцию func. После указанной задержки setTimeout её вызывает. Вызов производится без точки, просто как func()
, поэтому ThisBinding указывает на global object.
Стандартный способ решить эту задачу – сохранить ссылку на объект в environment вспомогательной функции. Имея ссылку на объект можно воспользоваться функцией Function.prototype.call. При вызове функции с помощью call можно явно указать ссылку, которая будет записана в ThisBinding контекста.
Так при вызове print.call(o)
будет создан контекст, в ThisBinding которого будет записана ссылка на o.
Такая функция как правило называется bind и может быть реализована следующим образом.
...
// bind возвращает функцию, которая вызывает функцию f
function bind(thisArg, f) {
return function wrapper() {
f.call(thisArg);
}
}
// пользоваться bind надо так
setTimeout(bind(o, o.print), 1);
При вызове функции bind создается environment и объект Function с именем wrapper. В thisArg и f сохраняются ссылки на объект и функцию print. В scope нового объекта Function кладется ссылка на environment. При выходе из bind контекст уничтожается, но environment остается, так как на него ссылается wrapper.
При вызове setTimeout через delay миллисекунд вызовется функция wrapper. При создании environment в поле outer записывается значение scope вызываемой функции. В данном случае outer будет ссылаться на environment функции bind. При вызове f.call(thisArg)
из wrapper обе переменные будут найдены в этом environment. Затем при вызове call будет создан контекст для print, где ThisBinding будет указывать на o.
В JavaScript есть стандартный метод Function.prototype.bind, который позволяет запомнить не только this, но и аргументы функции.
// вместо
setTimeout(bind(o, o.print), 1);
// правильнее использовать стандартный bind
setTimeout(o.print.bind(o), 1);
В следующей части поговорим о прототипном наследовании.
Автор: alvin777