Làm thế nào để truy cập chính xác `this` trong một cuộc gọi lại?


1425

Tôi có một hàm tạo để đăng ký một trình xử lý sự kiện:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', function () {
        alert(this.data);
    });
}

// Mock transport object
var transport = {
    on: function(event, callback) {
        setTimeout(callback, 1000);
    }
};

// called as
var obj = new MyConstructor('foo', transport);

Tuy nhiên, tôi không thể truy cập thuộc datatính của đối tượng được tạo bên trong cuộc gọi lại. Có vẻ như thiskhông đề cập đến đối tượng đã được tạo mà là một đối tượng khác.

Tôi cũng đã thử sử dụng một phương thức đối tượng thay vì một hàm ẩn danh:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', this.alert);
}

MyConstructor.prototype.alert = function() {
    alert(this.name);
};

nhưng nó thể hiện những vấn đề tương tự

Làm thế nào tôi có thể truy cập đúng đối tượng?


95
Thỉnh thoảng tôi chán ngấy với một loại câu hỏi nhất định, đến nỗi tôi quyết định viết một câu trả lời kinh điển. Mặc dù những câu hỏi này đã được trả lời như một triệu lần, nhưng không phải lúc nào bạn cũng có thể tìm thấy một câu hỏi hay câu trả lời không bị ô nhiễm bởi những thông tin không liên quan. Đây là một trong những khoảnh khắc và một trong những câu hỏi đó (và tôi thấy chán). Nếu bạn nghĩ rằng thực sự có một câu hỏi / câu trả lời chính tắc tốt cho loại câu hỏi này, hãy cho tôi biết và tôi sẽ xóa câu hỏi này. Đề xuất cải tiến được chào đón!
Felix Kling



Câu trả lời:


1791

Những gì bạn nên biết về this

this(còn gọi là "bối cảnh") là một từ khóa đặc biệt bên trong mỗi hàm và giá trị của nó chỉ phụ thuộc vào cách hàm được gọi, chứ không phải cách thức / thời gian / nơi nó được xác định. Nó không bị ảnh hưởng bởi phạm vi từ vựng như các biến khác (ngoại trừ các hàm mũi tên, xem bên dưới). Dưới đây là một số ví dụ:

function foo() {
    console.log(this);
}

// normal function call
foo(); // `this` will refer to `window`

// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`

// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`

Để tìm hiểu thêm về this, hãy xem tài liệu MDN .


Làm thế nào để tham khảo chính xác this

Đừng dùng this

Bạn thực sự không muốn truy cập thiscụ thể, nhưng đối tượng mà nó đề cập đến . Đó là lý do tại sao một giải pháp dễ dàng là chỉ cần tạo một biến mới cũng đề cập đến đối tượng đó. Biến có thể có bất kỳ tên nào, nhưng tên chung là selfthat.

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function() {
        alert(self.data);
    });
}

selflà một biến bình thường, nó tuân theo các quy tắc phạm vi từ vựng và có thể truy cập được trong cuộc gọi lại. Điều này cũng có lợi thế là bạn có thể truy cập vào thisgiá trị của chính cuộc gọi lại.

Hoàn toàn thiết lập thiscuộc gọi lại - phần 1

Có vẻ như bạn không có quyền kiểm soát giá trị thisvì giá trị của nó được đặt tự động, nhưng thực tế không phải vậy.

Mỗi hàm có phương thức .bind [docs] , trả về một hàm mới có thisràng buộc với một giá trị. Hàm này có chính xác hành vi giống như hành vi bạn đã gọi .bind, chỉ có điều đó thisđược đặt bởi bạn. Bất kể chức năng đó được gọi như thế nào hoặc khi nào, thissẽ luôn đề cập đến giá trị được truyền.

function MyConstructor(data, transport) {
    this.data = data;
    var boundFunction = (function() { // parenthesis are not necessary
        alert(this.data);             // but might improve readability
    }).bind(this); // <- here we are calling `.bind()` 
    transport.on('data', boundFunction);
}

Trong trường hợp này, chúng ta đang ràng buộc gọi lại của thisgiá trị của MyConstructor's this.

Lưu ý: Khi liên kết ngữ cảnh cho jQuery, thay vào đó hãy sử dụng jQuery.proxy [docs] . Lý do để làm điều này là vì vậy bạn không cần lưu trữ tham chiếu đến hàm khi hủy liên kết một cuộc gọi lại sự kiện. jQuery xử lý nội bộ đó.

ECMAScript 6: Sử dụng các chức năng mũi tên

ECMAScript 6 giới thiệu các hàm mũi tên , có thể được coi là các hàm lambda. Họ không có thisràng buộc riêng . Thay vào đó, thisđược tìm kiếm trong phạm vi giống như một biến bình thường. Điều đó có nghĩa là bạn không phải gọi .bind. Đó không phải là hành vi đặc biệt duy nhất họ có, vui lòng tham khảo tài liệu MDN để biết thêm thông tin.

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', () => alert(this.data));
}

Đặt thiscuộc gọi lại - phần 2

Một số chức năng / phương thức chấp nhận cuộc gọi lại cũng chấp nhận giá trị mà cuộc gọi lại thisnên tham khảo. Điều này về cơ bản giống như tự ràng buộc nó, nhưng chức năng / phương thức thực hiện nó cho bạn. Array#map [docs] là một phương pháp như vậy. Chữ ký của nó là:

array.map(callback[, thisArg])

Đối số đầu tiên là cuộc gọi lại và đối số thứ hai là giá trị thisnên tham khảo. Đây là một ví dụ giả định:

var arr = [1, 2, 3];
var obj = {multiplier: 42};

var new_arr = arr.map(function(v) {
    return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument

Lưu ý: Việc bạn có thể vượt qua một giá trị hay không thisthường được đề cập trong tài liệu về chức năng / phương thức đó. Ví dụ: phương thức [docs] của jQuery$.ajax mô tả một tùy chọn được gọi là context:

Đối tượng này sẽ được tạo thành bối cảnh của tất cả các cuộc gọi lại liên quan đến Ajax.


Vấn đề thường gặp: Sử dụng các phương thức đối tượng làm hàm gọi lại / xử lý sự kiện

Một biểu hiện phổ biến khác của vấn đề này là khi một phương thức đối tượng được sử dụng làm trình xử lý gọi lại / sự kiện. Các hàm là các công dân hạng nhất trong JavaScript và thuật ngữ "phương thức" chỉ là một thuật ngữ thông tục cho một hàm là giá trị của một thuộc tính đối tượng. Nhưng chức năng đó không có một liên kết cụ thể đến đối tượng "chứa" của nó.

Hãy xem xét ví dụ sau:

function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = function() {
    console.log(this.data);
};

Hàm this.methodđược gán là trình xử lý sự kiện nhấp, nhưng nếu document.bodyđược bấm, giá trị được ghi sẽ là undefined, bởi vì bên trong trình xử lý sự kiện, thisđề cập đến document.body, chứ không phải là thể hiện của Foo.
Như đã đề cập ở phần đầu, những gì thisđề cập đến phụ thuộc vào cách gọi hàm , chứ không phải cách xác định .
Nếu mã giống như sau, có thể rõ ràng hơn là hàm không có tham chiếu ngầm đến đối tượng:

function method() {
    console.log(this.data);
}


function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = method;

Giải pháp tương tự như đã đề cập ở trên: Nếu có sẵn, hãy sử dụng .bindđể liên kết rõ ràng thisvới một giá trị cụ thể

document.body.onclick = this.method.bind(this);

hoặc gọi một cách rõ ràng hàm là "phương thức" của đối tượng, bằng cách sử dụng hàm ẩn danh làm trình xử lý gọi lại / sự kiện và gán đối tượng ( this) cho một biến khác:

var self = this;
document.body.onclick = function() {
    self.method();
};

hoặc sử dụng chức năng mũi tên:

document.body.onclick = () => this.method();

39
Felix, tôi đã đọc câu trả lời này trước đây nhưng chưa bao giờ trả lời. Tôi phát triển quan tâm rằng mọi người sử dụng selfthattham khảo this. Tôi cảm thấy như vậy vì thismột biến quá tải được sử dụng trong các bối cảnh khác nhau; trong khi đó selfthường tương ứng với thể hiện cục bộ và thatthường đề cập đến một đối tượng khác. Tôi biết bạn đã không đặt quy tắc này, vì tôi đã thấy nó xuất hiện ở một số nơi khác, nhưng đó cũng là lý do tại sao tôi bắt đầu sử dụng _this, nhưng không chắc người khác cảm thấy thế nào, ngoại trừ việc thực hành không thống nhất điều đó đã có kết quả
vol7ron

3
@FelixKling có an toàn không khi cho rằng sử dụng chức năng nguyên mẫu bên trong này sẽ luôn có hành vi mong đợi bất kể chúng được gọi như thế nào (thông thường)? Khi sử dụng các hàm gọi lại bên trong các hàm nguyên mẫu, có sự thay thế nào cho bind (), self hay không?
andig

5
@FelixKling Đôi khi có thể dựa vào Function.prototype.call ()Function.prototype.apply (). Riêng với apply ()tôi đã nhận được rất nhiều dặm. Tôi ít có khuynh hướng sử dụng bind ()có lẽ chỉ theo thói quen mặc dù tôi biết (nhưng không chắc chắn) rằng có thể có những lợi thế nhỏ trên đầu để sử dụng ràng buộc so với các tùy chọn khác.
Nolo

5
Câu trả lời tuyệt vời nhưng xem xét thêm một giải pháp tùy chọn bổ sung mà chỉ để không sử dụng các lớp, mới, hoặc điều này cả.
Aluan Haddad

4
chức năng mũi tên lại "Thay vào đó, điều này được tìm kiếm trong phạm vi giống như một biến thông thường." hoàn toàn thực hiện nhấp chuột này cho tôi, cảm ơn bạn! () => this.clicked();)
chữ và số0101

211

Dưới đây là một số cách để truy cập bối cảnh cha mẹ trong bối cảnh con -

  1. Bạn có thể sử dụng bind()chức năng.
  2. Lưu trữ tham chiếu đến bối cảnh / cái này bên trong một biến khác (xem ví dụ bên dưới).
  3. Sử dụng các chức năng Mũi tên ES6 .
  4. Thay đổi mã / thiết kế chức năng / kiến ​​trúc - đối với điều này, bạn nên có lệnh trên các mẫu thiết kế trong javascript.

1. Sử dụng bind()chức năng

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', ( function () {
        alert(this.data);
    }).bind(this) );
}
// Mock transport object
var transport = {
    on: function(event, callback) {
        setTimeout(callback, 1000);
    }
};
// called as
var obj = new MyConstructor('foo', transport);

Nếu bạn đang sử dụng underscore.js- http://underscorejs.org/#bind

transport.on('data', _.bind(function () {
    alert(this.data);
}, this));

2 Lưu trữ tham chiếu đến bối cảnh / cái này bên trong một biến khác

function MyConstructor(data, transport) {
  var self = this;
  this.data = data;
  transport.on('data', function() {
    alert(self.data);
  });
}

Chức năng 3 mũi tên

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}

1
Tùy chọn bind () thật đáng kinh ngạc khi nó chỉ vượt qua con trỏ của Đối tượng này để trở thành đối tượng này trên đối tượng khác (: Cảm ơn!
Stav Bodik

Các bind () hoạt động như một nét duyên dáng. Cảm ơn bạn rất nhiều +1 từ tôi :)
Anjana Silva

56

Đó là tất cả trong cú pháp "ma thuật" của việc gọi một phương thức:

object.property();

Khi bạn nhận được thuộc tính từ đối tượng và gọi nó trong một lần, đối tượng sẽ là bối cảnh cho phương thức. Nếu bạn gọi cùng một phương thức, nhưng trong các bước riêng biệt, bối cảnh là phạm vi toàn cầu (cửa sổ) thay vào đó:

var f = object.property;
f();

Khi bạn nhận được tham chiếu của một phương thức, nó không còn được gắn vào đối tượng nữa, nó chỉ là một tham chiếu đến một hàm đơn giản. Điều tương tự cũng xảy ra khi bạn lấy tham chiếu để sử dụng làm cuộc gọi lại:

this.saveNextLevelData(this.setAll);

Đó là nơi bạn sẽ liên kết bối cảnh với chức năng:

this.saveNextLevelData(this.setAll.bind(this));

Nếu bạn đang sử dụng jQuery, bạn nên sử dụng $.proxyphương thức thay thế, vì bindkhông được hỗ trợ trong tất cả các trình duyệt:

this.saveNextLevelData($.proxy(this.setAll, this));

33

Những rắc rối với "bối cảnh"

Thuật ngữ "bối cảnh" đôi khi được sử dụng để chỉ đối tượng được tham chiếu bởi điều này . Việc sử dụng nó là không phù hợp vì nó không phù hợp hoặc ngữ nghĩa hoặc về mặt kỹ thuật với của ECMAScript này .

"Bối cảnh" có nghĩa là các tình huống xung quanh một cái gì đó thêm ý nghĩa, hoặc một số thông tin trước và sau mang lại ý nghĩa bổ sung. Thuật ngữ "bối cảnh" được sử dụng trong ECMAScript để chỉ bối cảnh thực thi , đó là tất cả các tham số, phạm vi và điều này trong phạm vi của một số mã thực thi.

Điều này được thể hiện trong ECMA-262 phần 10.4.2 :

Đặt ThisBinding thành cùng giá trị với ThisBinding của bối cảnh thực hiện cuộc gọi

trong đó chỉ rõ rằng đây là một phần của bối cảnh thực thi.

Một bối cảnh thực thi cung cấp thông tin xung quanh bổ sung ý nghĩa cho mã đang được thực thi. Nó bao gồm nhiều thông tin hơn chỉ là thisBinding .

Vì vậy, giá trị của đây không phải là "bối cảnh", nó chỉ là một phần của bối cảnh thực thi. Về cơ bản, đây là một biến cục bộ có thể được đặt bởi lệnh gọi tới bất kỳ đối tượng nào và trong chế độ nghiêm ngặt, với bất kỳ giá trị nào.


Không thể đồng ý với câu trả lời này. Sự tồn tại của thuật ngữ "bối cảnh thực thi" không vượt quá các cách sử dụng "bối cảnh" khác ngoài việc sử dụng nhiều hơn các cách sử dụng "thực thi" khác. Có thể có một thuật ngữ tốt hơn để mô tả thisnhưng không có thuật ngữ nào được cung cấp ở đây, và có lẽ đã quá muộn để đóng cánh cửa vào "bối cảnh".
Roamer-1888

@ Roamer-1888, cảm ơn bạn đã chỉnh sửa. Bạn nói đúng, nhưng lập luận của tôi không dựa vào sự tồn tại của "bối cảnh thực thi" loại trừ "bối cảnh" cho một số mục đích khác. Thay vào đó, nó dựa trên "bối cảnh" không phù hợp từ cả góc độ kỹ thuật và ngữ nghĩa. Tôi cũng nghĩ rằng việc sử dụng "bối cảnh" thay vì "điều này" sẽ không còn nữa. Tôi không thấy bất kỳ lý do nào để tìm một thuật ngữ thay thế cho điều này hoặc Liên kết này , nó chỉ làm khó hiểu và có nghĩa là tại một số điểm bạn phải giải thích rằng "bối cảnh" thực sự là thế này , và dù sao đó cũng không phải là "bối cảnh". :-)
RobG

Tôi không nghĩ bạn có thể nói rằng đây không phải là "bối cảnh" theo bất kỳ cách nào, khi bạn đã thừa nhận rằng đó là một phần của bối cảnh thực thi, trong đó "thực thi" chỉ là tính từ.
Roamer-1888

@ Roamer-1888, tôi sẽ không tiếp tục cuộc trò chuyện này vào thời điểm này. Vâng, đâymột phần của bối cảnh thực hiện. Nói đó là những bối cảnh giống như nói một cầu thủ của một đội bóng là đội.
RobG

RobG, đáng tiếc bạn không muốn tiếp tục. Đó là một cuộc tranh luận thú vị. Cảm ơn bạn đã cho tôi thời gian của bạn.
Roamer-1888

31

Bạn nên biết về từ khóa "này".

Theo quan điểm của tôi, bạn có thể thực hiện "điều này" theo ba cách (chức năng Tự / Mũi tên / Phương pháp ràng buộc)

Từ khóa này của một hàm hoạt động hơi khác so với JavaScript so với các ngôn ngữ khác.

Nó cũng có một số khác biệt giữa chế độ nghiêm ngặt và chế độ không nghiêm ngặt.

Trong hầu hết các trường hợp, giá trị của điều này được xác định bằng cách gọi hàm.

Nó không thể được thiết lập bằng cách gán trong khi thực hiện và nó có thể khác nhau mỗi khi hàm được gọi.

ES5 đã giới thiệu phương thức bind () để đặt giá trị của hàm này bất kể nó được gọi như thế nào,

và ES2015 đã giới thiệu các hàm mũi tên không cung cấp ràng buộc riêng của chúng (nó giữ lại giá trị này của bối cảnh từ vựng kèm theo).

Phương pháp 1: Tự - Tự đang được sử dụng để duy trì tham chiếu đến bản gốc này ngay cả khi bối cảnh đang thay đổi. Đây là một kỹ thuật thường được sử dụng trong các trình xử lý sự kiện (đặc biệt là trong các bao đóng).

Tham khảo : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function () {
        alert(self.data);
    });
}

Phương thức 2 : Hàm mũi tên - Biểu thức hàm mũi tên là một thay thế nhỏ gọn về mặt cú pháp cho biểu thức hàm thông thường,

mặc dù không có các ràng buộc riêng của nó với các từ khóa này, các đối số, siêu hoặc new.target.

Các biểu thức hàm mũi tên không phù hợp làm phương thức và chúng không thể được sử dụng làm hàm tạo.

Tham khảo : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Fifts/Arrow_fifts

  function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data',()=> {
        alert(this.data);
    });
}

Phương thức 3 : Bind- Phương thức bind () tạo ra một hàm mới,

khi được gọi, từ khóa này được đặt thành giá trị được cung cấp,

với một chuỗi các đối số đã cho trước bất kỳ được cung cấp khi hàm mới được gọi.

Tham khảo: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

  function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data',(function() {
        alert(this.data);
    }).bind(this);

25

Trước tiên, bạn cần có một sự hiểu biết rõ ràng scopevà hành vi của thistừ khóa trong bối cảnh scope.

this& scope:


there are two types of scope in javascript. They are :

   1) Global Scope

   2) Function Scope

trong ngắn hạn, phạm vi toàn cục đề cập đến đối tượng cửa sổ. Các biến được khai báo trong phạm vi toàn cục có thể truy cập được từ mọi nơi. Phạm vi chức năng mặt khác nằm bên trong hàm. Có thể truy cập bên trong một hàm thông thường từ thế giới bên ngoài. thistừ khóa trong phạm vi toàn cầu đề cập đến đối tượng cửa sổ. thisHàm bên trong cũng đề cập đến đối tượng cửa sổ. Vì vậy, thissẽ luôn luôn đề cập đến cửa sổ cho đến khi chúng ta tìm ra cách thao tác thisđể chỉ ra bối cảnh lựa chọn của chính chúng ta.

--------------------------------------------------------------------------------
-                                                                              -
-   Global Scope                                                               -
-   ( globally "this" refers to window object)                                 -     
-                                                                              -
-         function outer_function(callback){                                   -
-                                                                              -
-               // outer function scope                                        -
-               // inside outer function"this" keyword refers to window object -                                                                              -
-              callback() // "this" inside callback also refers window object  -

-         }                                                                    -
-                                                                              -
-         function callback_function(){                                        -
-                                                                              -
-                //  function to be passed as callback                         -
-                                                                              -
-                // here "THIS" refers to window object also                   -
-                                                                              -
-         }                                                                    -
-                                                                              -
-         outer_function(callback_function)                                    -
-         // invoke with callback                                              -
--------------------------------------------------------------------------------

Các cách khác nhau để thao tác thisbên trong các hàm gọi lại:

Ở đây tôi có một hàm tạo được gọi là Person. Nó có một tính chất gọi namevà bốn phương pháp gọi là sayNameVersion1, sayNameVersion2, sayNameVersion3, sayNameVersion4. Tất cả bốn người trong số họ có một nhiệm vụ cụ thể. Chấp nhận một cuộc gọi lại và gọi nó. Cuộc gọi lại có một nhiệm vụ cụ thể là ghi nhật ký thuộc tính tên của một thể hiện của hàm xây dựng Person.

function Person(name){

    this.name = name

    this.sayNameVersion1 = function(callback){
        callback.bind(this)()
    }
    this.sayNameVersion2 = function(callback){
        callback()
    }

    this.sayNameVersion3 = function(callback){
        callback.call(this)
    }

    this.sayNameVersion4 = function(callback){
        callback.apply(this)
    }

}

function niceCallback(){

    // function to be used as callback

    var parentObject = this

    console.log(parentObject)

}

Bây giờ, hãy tạo một cá thể từ hàm tạo người và gọi các phiên bản khác nhau của phương thức sayNameVersionX(X đề cập đến 1,2,3,4) niceCallbackđể xem có bao nhiêu cách chúng ta có thể thao tác thisgọi lại bên trong để tham chiếu đến personthể hiện.

var p1 = new Person('zami') // create an instance of Person constructor

trói buộc :

Những gì liên kết làm là tạo ra một chức năng mới với thistừ khóa được đặt thành giá trị được cung cấp.

sayNameVersion1sayNameVersion2sử dụng liên kết để thao tác thischức năng gọi lại.

this.sayNameVersion1 = function(callback){
    callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
    callback()
}

đầu tiên liên kết thisvới gọi lại bên trong chính phương thức. Và cho lần gọi lại thứ hai được truyền với đối tượng được liên kết với nó.

p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method

p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback

gọi :

Các first argumentcủa callphương pháp được sử dụng như thisbên trong hàm đó được gọi với callgắn liền với nó.

sayNameVersion3sử dụng callđể thao tác thisđể tham chiếu đến đối tượng người mà chúng ta đã tạo, thay vì đối tượng cửa sổ.

this.sayNameVersion3 = function(callback){
    callback.call(this)
}

và nó được gọi như sau:

p1.sayNameVersion3(niceCallback)

ứng dụng :

Tương tự như call, đối số đầu tiên applyđề cập đến đối tượng sẽ được chỉ định bởi thistừ khóa.

sayNameVersion4dùng applyđể thao túng thisđể chỉ người

this.sayNameVersion4 = function(callback){
    callback.apply(this)
}

và nó được gọi như sau. Đồng nghĩa với việc gọi lại được thông qua,

p1.sayNameVersion4(niceCallback)

1
bất kỳ lời phê bình mang tính xây dựng nào liên quan đến câu trả lời sẽ được đánh giá cao!
AL-zami

1
Từ khóa này trong phạm vi toàn cầu không nhất thiết phải đề cập đến đối tượng cửa sổ . Điều đó chỉ đúng trong một trình duyệt.
Randall Flagg

1
@RandallFlagg tôi đã viết câu trả lời này từ góc nhìn của trình duyệt. Hãy tự do hít vào câu trả lời này nếu cần thiết :)
AL-zami

19

Chúng ta không thể liên kết điều này với setTimeout(), vì nó luôn luôn thực thi với đối tượng toàn cầu (Window) , nếu bạn muốn truy cập thisngữ cảnh trong hàm gọi lại thì bằng cách sử dụng bind()chức năng gọi lại, chúng ta có thể đạt được như sau:

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);

9
Làm thế nào là khác nhau hơn bất kỳ câu trả lời hiện có?
Felix Kling

13

Câu hỏi xoay quanh cách thishoạt động của từ khóa trong javascript. thiscư xử khác nhau như dưới đây,

  1. Giá trị của thisthường được xác định bởi bối cảnh thực thi chức năng.
  2. Trong phạm vi toàn cầu, thisđề cập đến đối tượng toàn cầu ( windowđối tượng).
  3. Nếu chế độ nghiêm ngặt được kích hoạt cho bất kỳ chức năng nào thì giá trị của thissẽ undefinednhư trong chế độ nghiêm ngặt, đối tượng toàn cục đề cập đến undefinedvị trí của windowđối tượng.
  4. Đối tượng đứng trước dấu chấm là từ khóa này sẽ bị ràng buộc.
  5. Chúng ta có thể thiết lập giá trị của việc này một cách rõ ràng với call(), bind()apply()
  6. Khi newtừ khóa được sử dụng (một hàm tạo), điều này bị ràng buộc với đối tượng mới được tạo.
  7. Hàm mũi tên không ràng buộc this - thay vào đó, thisbị ràng buộc theo từ vựng (nghĩa là dựa trên bối cảnh ban đầu)

Như hầu hết các câu trả lời gợi ý, chúng ta có thể sử dụng chức năng Mũi tên hoặc bind()Phương thức hoặc Tự var. Tôi sẽ trích dẫn một điểm về lambdas (chức năng Mũi tên) từ Hướng dẫn về Phong cách JavaScript của Google

Thích sử dụng các hàm mũi tên hơn f.bind (cái này) và đặc biệt là trên goog.bind (f, cái này). Tránh viết const self = cái này. Các hàm mũi tên đặc biệt hữu ích cho các cuộc gọi lại, đôi khi vượt qua các đối số bổ sung không mong muốn.

Google rõ ràng khuyên bạn nên sử dụng lambdas chứ không phải ràng buộc hoặc const self = this

Vì vậy, giải pháp tốt nhất là sử dụng lambdas như dưới đây,

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}

Người giới thiệu:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. mũi tên-chức năng-vs-ràng buộc

Câu hỏi này đặc biệt về việc sử dụng các hàm / phương thức làm cuộc gọi lại. Câu trả lời của bạn có thể phù hợp hơn với stackoverflow.com/q/3127429/218196 .
Felix Kling

@FelixKling Có câu hỏi là về việc sử dụng các hàm / phương thức làm cuộc gọi lại trong vấn đề chính đó là do xử lý thistừ khóa đó là lý do tại sao tôi chia câu trả lời của mình thành hai phần, một về thisvà thứ hai về cách sử dụng hàm / phương thức làm cuộc gọi lại. Hãy chỉnh sửa câu trả lời.
Code_Mode

Tôi thấy điểm thứ tư của bạn mơ hồ. Hãy xem xét ví dụ về vấn đề khi sử dụng các phương thức với đối tượng này là Callbacks , trong đó đối tượng bên phải đang đứng trước dấu chấm, nhưng bối cảnh không phải là đối tượng đó.
bleistift2

7

Hiện tại có một cách tiếp cận khác có thể nếu các lớp được sử dụng trong mã.

Với sự hỗ trợ của các trường lớp , có thể thực hiện theo cách tiếp theo:

class someView {
    onSomeInputKeyUp = (event) => {
        console.log(this); // this refers to correct value
    // ....
    someInitMethod() {
        //...
        someInput.addEventListener('input', this.onSomeInputKeyUp)

Để chắc chắn dưới mui xe, tất cả chức năng mũi tên tốt cũ liên kết bối cảnh nhưng ở dạng này có vẻ rõ ràng hơn nhiều ràng buộc rõ ràng.

Vì Đề xuất Giai đoạn 3, bạn sẽ cần babel và plugin babel thích hợp để xử lý nó như bây giờ (08/2018).


2
Đây chính xác là cách tôi đã làm cho nó hoạt động trong Typecript: public methodName = (params) => { body }bên trong một lớp.
yeyeyerman

5

Một cách tiếp cận khác, là cách tiêu chuẩn kể từ khi DOM2 liên kết thistrong trình lắng nghe sự kiện, cho phép bạn luôn loại bỏ trình nghe (trong số các lợi ích khác), là handleEvent(evt)phương thức khỏi EventListenergiao diện:

var obj = {
  handleEvent(e) {
    // always true
    console.log(this === obj);
  }
};

document.body.addEventListener('click', obj);

Thông tin chi tiết về việc sử dụng handleEventcó thể được tìm thấy ở đây: https://medium.com/@WebReflection/dom-handleevent-a-cross-pl platform-st Chuẩn-since-year-2000-5bf17287fd38


0

this trong JS:

Giá trị của thistrong JS được xác định 100% bằng cách gọi hàm và không xác định hàm như thế nào. Chúng ta có thể tương đối dễ dàng tìm thấy những giá trị của thiscác 'trái của dot quy tắc' :

  1. Khi hàm được tạo bằng từ khóa hàm, giá trị của thislà đối tượng bên trái của dấu chấm của hàm được gọi
  2. Nếu không có đối tượng còn lại của dấu chấm thì giá trị thisbên trong hàm thường là đối tượng toàn cục ( globaltrong nút, windowtrong trình duyệt). Tôi không khuyên bạn nên sử dụng thistừ khóa ở đây vì nó ít rõ ràng hơn so với sử dụng một cái gì đó như window!
  3. Tồn tại một số cấu trúc nhất định như các hàm mũi tên và các hàm được tạo bằng Function.prototype.bind()hàm có thể sửa giá trị của this. Đây là những ngoại lệ của quy tắc nhưng thực sự hữu ích để sửa giá trị của this.

Ví dụ trong nodeJS

module.exports.data = 'module data';
// This outside a function in node refers to module.exports object
console.log(this);

const obj1 = {
    data: "obj1 data",
    met1: function () {
        console.log(this.data);
    },
    met2: () => {
        console.log(this.data);
    },
};

const obj2 = {
    data: "obj2 data",
    test1: function () {
        console.log(this.data);
    },
    test2: function () {
        console.log(this.data);
    }.bind(obj1),
    test3: obj1.met1,
    test4: obj1.met2,
};

obj2.test1();
obj2.test2();
obj2.test3();
obj2.test4();
obj1.met1.call(obj2);

Đầu ra:

nhập mô tả hình ảnh ở đây

Hãy để tôi hướng dẫn bạn qua các kết quả đầu ra 1 (bỏ qua nhật ký đầu tiên bắt đầu từ lần thứ hai):

  1. thisobj2do bên trái của quy tắc dấu chấm, chúng ta có thể thấy cách test1gọi obj2.test1();. obj2là bên trái của dấu chấm và do đó thisgiá trị.
  2. Mặc dù obj2bên trái của dấu chấm, test2bị ràng buộc obj1thông qua bind()phương thức. Vì vậy, thisgiá trị là obj1.
  3. obj2là bên trái của dấu chấm từ hàm được gọi là : obj2.test3(). Do đó obj2sẽ là giá trị của this.
  4. Trong trường hợp này: obj2.test4() obj2là bên trái của dấu chấm. Tuy nhiên chức năng mũi tên không có thisràng buộc riêng của họ . Do đó, nó sẽ liên kết với thisgiá trị của phạm vi bên ngoài là module.exportsđối tượng được ghi vào đầu.
  5. Chúng ta cũng có thể chỉ định giá trị của thisbằng cách sử dụng callhàm. Ở đây chúng ta có thể chuyển vào thisgiá trị mong muốn dưới dạng đối số, obj2trong trường hợp này.
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.