Vượt qua chính xác bối cảnh này trong bối cảnh này để gọi lại setTimeout?


249

Làm thế nào để tôi vượt qua bối cảnh setTimeout? Tôi muốn gọi this.tip.destroy()nếu this.options.destroyOnHidesau 1000 ms. Làm thế nào tôi có thể làm điều đó?

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

Khi tôi thử ở trên, thisđề cập đến cửa sổ.


4
Cờ trùng lặp có thực sự hợp lệ? Câu hỏi này đã thực sự được hỏi trước đó.
Giấc mơ Sui

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

Câu trả lời:


352

EDIT: Tóm lại, trở lại năm 2010 khi câu hỏi này được hỏi, cách phổ biến nhất để giải quyết vấn đề này là lưu tham chiếu đến bối cảnh nơi thực hiện lệnh setTimeoutgọi hàm, bởi vì setTimeoutthực thi hàm với việc thistrỏ đến đối tượng toàn cục:

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

Trong thông số ES5, vừa được phát hành một năm trước thời điểm đó, nó đã giới thiệu bindphương pháp này, điều này không được đề xuất trong câu trả lời ban đầu vì nó chưa được hỗ trợ rộng rãi và bạn cần sử dụng polyfill để sử dụng nhưng bây giờ nó ở khắp mọi nơi:

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

Các bindchức năng tạo ra một chức năng mới với các thisgiá trị điền sẵn.

Bây giờ trong JS hiện đại, đây chính xác là các hàm mũi tên vấn đề giải quyết trong ES6 :

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

Các hàm mũi tên không có thisgiá trị riêng, khi bạn truy cập vào nó, bạn đang truy cập thisgiá trị của phạm vi từ vựng kèm theo.

HTML5 cũng đã chuẩn hóa bộ định thời vào năm 2011 và bạn có thể chuyển các đối số ngay bây giờ cho hàm gọi lại:

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

Xem thêm:


3
Nó hoạt động. Tôi đã thử nghiệm khái niệm này với tập lệnh jsbin
John K

1
Mã này liên quan đến việc tạo một biến không cần thiết (có phạm vi toàn hàm); nếu bạn chuyển đúng vào thishàm, bạn đã giải quyết vấn đề này cho trường hợp này, cho map (), forEach (), v.v., v.v., sử dụng ít mã hơn, ít chu kỳ CPU hơn và ít bộ nhớ hơn. *** Xem: Câu trả lời của Misha Reyzlin.
Hold OfferHunger

222

Có các phím tắt được tạo sẵn (đường cú pháp) để trình bao bọc hàm @CMS trả lời. (Dưới đây giả sử rằng bối cảnh bạn muốn là this.tip.)


ECMAScript 5 ( trình duyệt hiện tại , Node.js) và Prototype.js

Nếu bạn nhắm mục tiêu trình duyệt tương thích với ECMA-262, phiên bản thứ 5 (ECMAScript 5) hoặc Node.js , bạn có thể sử dụng Function.prototype.bind. Bạn có thể tùy ý chuyển bất kỳ đối số chức năng để tạo các chức năng một phần .

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

Một lần nữa, trong trường hợp của bạn, hãy thử điều này:

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

Chức năng tương tự cũng đã được triển khai trong Prototype (bất kỳ thư viện nào khác?).

Function.prototype.bindcó thể được thực hiện như thế này nếu bạn muốn tương thích ngược tùy chỉnh (nhưng vui lòng quan sát các ghi chú).


ECMAScript 2015 ( một số trình duyệt , Node.js 5.0.0+)

Để phát triển tiên tiến (2015), bạn có thể sử dụng các hàm mũi tên béo , là một phần của đặc tả ECMAScript 2015 (Harmony / ES6 / ES2015) ( ví dụ ).

Một biểu thức hàm mũi tên (còn được gọi là hàm mũi tên béo ) có cú pháp ngắn hơn so với biểu thức hàm và liên kết từ vựng với thisgiá trị [...].

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

Trong trường hợp của bạn, hãy thử điều này:

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

jQuery

Nếu bạn đã sử dụng jQuery 1.4+, có một hàm được tạo sẵn để đặt rõ ràng thisbối cảnh của một hàm.

jQuery.proxy () : Thực hiện một hàm và trả về một hàm mới sẽ luôn có một bối cảnh cụ thể.

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

Trong trường hợp của bạn, hãy thử điều này:

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

Underscore.js , lodash

Nó có sẵn trong Underscore.js, cũng như lodash, như _.bind(...)1 , 2

liên kết Bind một hàm với một đối tượng, nghĩa là bất cứ khi nào hàm được gọi, giá trị củathissẽ là đối tượng. Tùy chọn, liên kết các đối số với hàm để điền trước chúng, còn được gọi là ứng dụng một phần.

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

Trong trường hợp của bạn, hãy thử điều này:

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


Tại sao không mặc định func.bind(context...)? Tôi có bỏ lỡ điều gì không?
aTei

Có phải nó hoạt động liên tục tạo ra một chức năng mới (liên kết nào) mỗi khi bạn gọi nó? Tôi có thời gian chờ tìm kiếm đặt lại sau mỗi lần nhấn phím và có vẻ như tôi nên lưu trữ phương thức 'bị ràng buộc' này ở đâu đó để sử dụng lại.
Triynko

@Triynko: Tôi sẽ không xem ràng buộc một chức năng là một hoạt động đắt tiền, nhưng nếu bạn gọi cùng một chức năng ràng buộc nhiều lần, bạn cũng có thể giữ một tham chiếu: var boundFn = fn.bind(this); boundFn(); boundFn();ví dụ.
Joel Purra

30

Trong các trình duyệt khác ngoài Internet Explorer, bạn có thể truyền các tham số cho hàm sau khi trì hoãn:

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

Vì vậy, bạn có thể làm điều này:

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

Điều này tốt hơn về mặt hiệu suất so với tra cứu phạm vi (lưu thisvào bộ đệm bên ngoài biểu thức hết thời gian / khoảng thời gian), sau đó tạo một bao đóng (bằng cách sử dụng $.proxyhoặc Function.prototype.bind).

Mã để làm cho nó hoạt động trong IE từ 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
Khi tạo một lớp bằng cách sử dụng chuỗi nguyên mẫu và các phương thức của bạn là các phương thức nguyên mẫu ... 'liên kết' là điều duy nhất sẽ thay đổi 'cái này' trong phương thức. Bằng cách chuyển một tham số cho hàm gọi lại, bạn không thay đổi 'cái này' trong hàm, do đó, một hàm nguyên mẫu như vậy không thể được viết bằng 'this' trong bất kỳ phương thức nguyên mẫu nào khác có thể. Điều đó dẫn đến sự không nhất quán. Ràng buộc là thứ gần nhất với những gì chúng ta thực sự muốn và việc đóng có thể được lưu trong bộ đệm 'này' để có hiệu suất tra cứu cao hơn và không phải tạo nhiều lần.
Triynko

3

LƯU Ý: Điều này sẽ không hoạt động trong 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

Nếu bạn đang sử dụng underscore, bạn có thể sử dụng bind.

Ví dụ

if (this.options.destroyOnHide) {
     setTimeout(_.bind(this.tip.destroy, this), 1000);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.