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

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

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

Когда я пытаюсь сделать это, this относится к окну.


person JamesBrownIsDead    schedule 25.01.2010    source источник
comment
Флаг дублирования действительно действителен? Этот вопрос фактически задавался ранее.   -  person Sui Dream    schedule 30.10.2017
comment
если (this.options.destroyOnHide) {setTimeout (функция () {this.tip.destroy ()} .bind (это), 1000); }   -  person Zibri    schedule 04.03.2018


Ответы (6)


РЕДАКТИРОВАТЬ: Таким образом, еще в 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);
}

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

person Christian C. Salvadó    schedule 25.01.2010
comment
Оно работает. Я протестировал эту концепцию с помощью сценария jsbin: jsbin.com/etise/7/edit - person John K; 25.01.2010
comment
В этом коде создается ненужная переменная (имеющая функциональную область видимости); если вы правильно передали this функции, вы бы решили эту проблему для этого случая, для map (), forEach () и т. д. и т. д., используя меньше кода, меньше циклов процессора и меньше памяти. *** См. Ответ Миши Рейзлина. - person HoldOffHunger; 20.10.2017

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


ECMAScript 2015 (все распространенные браузеры и смартфоны, Node.js 5.0.0+)

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

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

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

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

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

ECMAScript 5 (старые браузеры и смартфоны, Node.js) и Prototype.js

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

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

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

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

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

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


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);
}

bind jquery underscore.js ecmascript-5 prototypejs node.js

person Joel Purra    schedule 10.01.2012
comment
Почему не по умолчанию func.bind(context...)? Я что-то упускаю? - person aTei; 12.07.2015
comment
Эффективно ли постоянно создавать новую функцию (что делает bind) каждый раз, когда вы вызываете это? У меня есть тайм-аут поиска, который сбрасывается после каждого нажатия клавиши, и мне кажется, что я должен где-то кэшировать этот «связанный» метод для повторного использования. - person Triynko; 02.12.2015
comment
@Triynko: Я бы не стал рассматривать привязку функции как дорогостоящую операцию, но если вы вызываете одну и ту же связанную функцию несколько раз, вы можете сохранить ссылку: например, var boundFn = fn.bind(this); boundFn(); boundFn();. - person Joel Purra; 05.12.2015

В браузерах, отличных от 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`'s 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);
    }
});
@*/
person Misha Reyzlin    schedule 15.02.2012
comment
При создании класса с использованием цепочки прототипов, а ваши методы являются методами прототипа ... 'bind' - единственное, что изменит значение 'this' внутри метода. Передавая параметр обратному вызову, вы не изменяете, что такое this в функции, поэтому такую ​​функцию-прототип нельзя было написать с использованием this внутри нее, как это мог бы сделать любой другой метод-прототип. Это ведет к непоследовательности. Связывание - это наиболее близкое к тому, что мы действительно хотим, и закрытие можно кэшировать в this, чтобы повысить производительность поиска и не создавать его более одного раза. - person Triynko; 02.12.2015

ПРИМЕЧАНИЕ: это не будет работать в 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);
person humkins    schedule 26.05.2014

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

E.g.

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

Если вы используете TypeScript, вы можете передать функцию как параметр, например:

setTimeout(this.tip.destroy, 1000);

И контекст this будет назначен, как если бы вы инкапсулировали вызов в стрелочную функцию в JavaScript.

person Paul Razvan Berg    schedule 03.07.2021