Пропустить правильный контекст "this" для вызова обратного вызова setTimeout?

Как передать контекст в setTimeout? Я хочу называть this.tip.destroy(), если this.options.destroyOnHide после 1000 мс. Как я могу это сделать?

if (this.options.destroyOnHide) {
     setTimeout(function() { this.tip.destroy() }, 1000);
} 

Когда я попробую выше, this относится к окну.

Ответ 1

EDIT: В заключение, еще в 2010 году, когда этот вопрос был задан самый распространенный способ решения этой проблемы в том, чтобы сохранить ссылку на контекст, где setTimeout производится вызов функции, поскольку setTimeout выполняет функцию с this, указывая на глобальный объект:

var that = this;
if (this.options.destroyOnHide) {
     setTimeout(function(){ that.tip.destroy() }, 1000);
} 

В спецификации ES5, выпущенной за год до этого, в нем был представлен метод bind, он не был предложен в первоначальном ответе, потому что он еще не получил широкой поддержки, и вам потребовались полифилы, чтобы использовать его, но теперь он везде:

if (this.options.destroyOnHide) {
     setTimeout(function(){ this.tip.destroy() }.bind(this), 1000);
}

Функция bind создает новую функцию с предварительно заполненным значением this.

Теперь в современном JS именно эту проблему решают функции стрелок в ES6:

if (this.options.destroyOnHide) {
     setTimeout(() => { this.tip.destroy() }, 1000);
}

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

HTML5 также стандартизировал таймеры еще в 2011 году, и теперь вы можете передавать аргументы в функцию обратного вызова:

if (this.options.destroyOnHide) {
     setTimeout(function(that){ that.tip.destroy() }, 1000, this);
}

Смотрите также:

Ответ 2

Есть готовые ярлыки (синтаксический сахар) к функции-обертки @CMS, на которую отвечает. (Ниже предполагается, что контекст, который вы хотите, this.tip.)


ECMAScript 5 (текущие браузеры, Node.js) и Prototype.js

Если вы настроили браузер совместимый с ECMA-262, 5-е издание (ECMAScript 5) или Node.js, вы можете использовать Function.prototype.bind. Вы можете опционально передать любые аргументы функции для создания частичных функций.

fun.bind(thisArg[, arg1[, arg2[, ...]]])

Опять же, в вашем случае, попробуйте следующее:

if (this.options.destroyOnHide) {
    setTimeout(this.tip.destroy.bind(this.tip), 1000);
}

Та же функциональность также была реализована в Prototype (любые другие библиотеки?).

Function.prototype.bind может быть реализовано следующим образом, если вам нужна пользовательская обратная совместимость (но, пожалуйста, обратите внимание на примечания).


ECMAScript 2015 (несколько браузеров, Node.js 5.0.0 +)

Для передовых разработок (2015) вы можете использовать функции толстых стрелок, которые часть спецификации ECMAScript 2015 (Harmony/ES6/ES2015) (примеры).

выражение функции стрелки (также известное как функция стрелки жира) имеет более короткий синтаксис по сравнению с функциональными выражениями и лексически связывает значение this [...].

(param1, param2, ...rest) => { statements }

В вашем случае попробуйте следующее:

if (this.options.destroyOnHide) {
    setTimeout(() => { this.tip.destroy(); }, 1000);
}

jQuery

Если вы уже используете jQuery 1.4+, есть готовая функция для явной настройки контекста this для функции.

jQuery.proxy(): принимает функцию и возвращает новую, которая всегда будет иметь определенный контекст.

$.proxy(function, context[, additionalArguments])

В вашем случае попробуйте следующее:

if (this.options.destroyOnHide) {
    setTimeout($.proxy(this.tip.destroy, this.tip), 1000);
}

Underscore.js, lodash

Он доступен в Underscore.js, а также lodash, как _.bind(...) 1, 2

bind Связывает функцию с объектом, что означает, что всякий раз, когда вызывается функция, значение this будет объектом. Необязательно, свяжите аргументы с функцией, чтобы предварительно заполнить их, также известную как частичное приложение.

_.bind(function, object, [*arguments])

В вашем случае попробуйте следующее:

if (this.options.destroyOnHide) {
    setTimeout(_.bind(this.tip.destroy, this.tip), 1000);
}

Ответ 3

В браузерах, отличных от Internet Explorer, вы можете передавать параметры функции вместе после задержки:

var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);

Итак, вы можете сделать это:

var timeoutID = window.setTimeout(function (self) {
  console.log(self); 
}, 500, this);

Это лучше с точки зрения производительности, чем просмотр области (кэширование this в переменную за пределами выражения тайм-аута/интервала), а затем создание закрытия (с помощью $.proxy или Function.prototype.bind).

Код, чтобы заставить его работать в IE с Webreflection:

/*@cc_on
(function (modifierFn) {
  // you have to invoke it as `window` property so, `window.setTimeout`
  window.setTimeout = modifierFn(window.setTimeout);
  window.setInterval = modifierFn(window.setInterval);
})(function (originalTimerFn) {
    return function (callback, timeout){
      var args = [].slice.call(arguments, 2);
      return originalTimerFn(function () { 
        callback.apply(this, args) 
      }, timeout);
    }
});
@*/

Ответ 4

ПРИМЕЧАНИЕ. Это не будет работать в IE

var ob = {
    p: "ob.p"
}

var p = "window.p";

setTimeout(function(){
    console.log(this.p); // will print "window.p"
},1000); 

setTimeout(function(){
    console.log(this.p); // will print "ob.p"
}.bind(ob),1000);

Ответ 5

Если вы используете underscore, вы можете использовать bind.

например.

if (this.options.destroyOnHide) {
     setTimeout(_.bind(this.tip.destroy, this), 1000);
}