Передати правильний "цей" контекст для зворотного дзвінка setTimeout?


250

Як передати контекст setTimeout? Я хочу зателефонувати, this.tip.destroy()якщо this.options.destroyOnHideчерез 1000 мс. Як я можу це зробити?

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

Коли я пробую вище, thisзвертається до вікна.


4
Чи дійсно повторюваний прапор дійсний? Це питання насправді було задано раніше.
Сон Суй

1
if (this.options.destroyOnHide) {setTimeout (function () {this.tip.destroy ()} .bind (this), 1000); }
Зібрі

Відповіді:


352

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

Дивитися також:


3
Це працює. Я перевірив концепцію сценарієм jsbin
Джон К

1
Цей код включає створення непотрібної змінної (яка має область функціонування); якщо ви правильно передали thisцю функцію, ви вирішили б цю проблему для цього випадку для карти (), forEach () тощо) тощо, використовуючи менше коду, менше циклів процесора та менше пам'яті. *** Див: відповідь Міші Рейзліна.
HoldOffHunger

222

Існують готові ярлики (синтаксичний цукор) до обгортки функції @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);
}

Така ж функціональність була реалізована і в прототипі (будь-які інші бібліотеки?).

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

Підкреслити.js , lodash

Доступний на Underscore.js, а також lodash, як _.bind(...)1 , 2

прив'язує Прив'язує функцію до об'єкта, тобто, щоразу, коли функція викликається, значеннямthisбуде об'єкт. За бажанням, прив’яжіть аргументи до функції, щоб попередньо їх заповнити, також відомий як часткове застосування.

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

У вашому випадку спробуйте це:

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


Чому не за замовчуванням func.bind(context...)? Я щось сумую?
aTei

Чи є виконавцем постійно продовжувати створювати нову функцію (яка пов'язує) щоразу, коли ви це називаєте? У мене є тайм-аут пошуку, який скидається після кожного натискання клавіші, і мені здається, що я повинен кешувати цей "прив'язаний" метод десь для повторного використання.
Трайко

@Triynko: Я не бачив би прив'язку функції як дорогу операцію, але якщо ви будете викликати одну і ту ж зв'язану функцію кілька разів, ви також можете зберегти посилання: var boundFn = fn.bind(this); boundFn(); boundFn();наприклад.
Джоел Пурра

30

У браузерах, окрім 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);
    }
});
@*/

1
При створенні класу за допомогою ланцюга прототипу, а ваші методи є прототипами методів ... "bind" - це єдине, що змінить те, що "це" в методі. Передаючи параметр зворотному виклику, ви не змінюєте те, що "це" у функції, тому таку функцію прототипу не можна було записати, використовуючи "this" всередині неї, як будь-який інший метод прототипу. Це призводить до неузгодженості. Bind - це найближче до того, що ми насправді хочемо, і закриття може бути кешоване в "це" для більш високої продуктивності пошуку і не потрібно створювати його не один раз.
Трайнко

3

ПРИМІТКА. Це не працює в 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);

2

Якщо ви використовуєте underscore, ви можете використовувати bind.

Напр

if (this.options.destroyOnHide) {
     setTimeout(_.bind(this.tip.destroy, this), 1000);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.