Почему для сохранения этого требуется анонимная функция с помощью setTimeout

Я использовал setTimeout много раз, передавая функцию в качестве ссылки, например.

setTimeout(someFunction, 3000);

В некоторых случаях, чтобы сохранить значение this, мне приходилось заранее присваивать его переменной, но я не понимаю, почему следующее не работает:

var logger = {
    log: function() { 
        var that = this;
        console.log(that.msg); 
        setTimeout(that.log, 3000); 
    },
    msg: "test"
};

logger.log();

Однако использование анонимной функции работает:

var logger = {
    log: function() { 
        var that = this;
        console.log(that.msg); 
        setTimeout(function() { that.log() }, 3000); 
    },
    msg: "test"
};

person Ben Foster    schedule 29.01.2014    source источник
comment
Эта проблема: Код, выполняемый setTimeout(), запускается в отдельном контексте выполнения от функции, из которой он был вызван. Как следствие, ключевое слово this для вызываемой функции будет установлено на оконный (или глобальный) объект; оно не будет таким же, как значение this для функции, вызвавшей setTimeout.   -  person Andreas    schedule 29.01.2014
comment
Не уверен, что это точный обман, но похоже: stackoverflow.com/questions/1101668/   -  person Qantas 94 Heavy    schedule 29.01.2014


Ответы (2)


Это не работает, так как setTimeout вызывает функцию со значением this в качестве глобального объекта, а не родительского объекта. Вы передаете значение в функцию setTimeout -- она ​​не знает, как к ней был получен доступ, и поэтому не может вызвать ее с правильным значением this (в отличие от обычных переменных, значение this определяется только при вызове функции, если только this не было привязано к определенному значению с помощью Function.prototype.bind).

Изменив это на анонимную функцию, вы используете замыкание для доступа к значению that, даже когда оно вызывается как значение (область действия переменной функции устанавливается при ее определении, а не при ее запуске).

Это так же, как если бы вы сделали что-то вроде этого:

var a = { b: function () { return this.foo; }, foo: 'proper' };
function test(arg) {
    return arg();
}
var foo = 'random';
console.log(a.b()); // proper
console.log(test(a.b)); // random

Также есть связанный с этим вопрос об использовании this с setTimeout: обратный вызов setTimeout?

person Qantas 94 Heavy    schedule 29.01.2014

Потому что в первом случае вы ссылаетесь только на функцию log, которая находится внутри объекта that, но ее связь с that теряется. Думайте об этом как о том, что setTimeout напрямую вызывает метод log по сохраненному адресу памяти с глобальным контекстом.

Однако во втором примере вы приходите из глобального контекста, но сначала ищется that, а затем log, который затем вызывается с контекстом that.

Представьте, что setTimeout имеет следующую структуру:

var setTimeout = function (func, time) {
   someWaitMechanism(time, function () { //this is called after the timeout
       func.call(null); //calls func with global scope
   });
}
person Matyas    schedule 29.01.2014
comment
func.call(null) и func() ничем не отличаются, верно? Возможно, вы захотите прояснить это ;) - person Qantas 94 Heavy; 29.01.2014
comment
Просто хотел подчеркнуть, что thisArg установлен на null, поэтому глобальный контекст, но вы правы :) - person Matyas; 29.01.2014
comment
На самом деле, это может быть не так очевидно :) - person Qantas 94 Heavy; 29.01.2014