2008-10-08 20 views
58

Tôi đang gặp một số sự cố với JavaScript cũ thuần túy (không có khung công tác) khi tham chiếu đối tượng của tôi trong chức năng gọi lại.Phạm vi gọi lại JavaScript

function foo(id) { 
    this.dom = document.getElementById(id); 
    this.bar = 5; 
    var self = this; 
    this.dom.addEventListener("click", self.onclick, false); 
} 

foo.prototype = { 
    onclick : function() { 
     this.bar = 7; 
    } 
}; 

Bây giờ khi tôi tạo ra một đối tượng mới (sau khi DOM đã được nạp, với một bài kiểm tra khoảng #)

var x = new foo('test'); 

Các 'này' bên trong điểm chức năng onclick để kiểm tra khoảng # và không đối tượng foo.

Làm cách nào để có tham chiếu đến đối tượng foo của tôi bên trong chức năng onclick?

Trả lời

81

(chiết xuất một số giải thích rằng được giấu trong bình luận trong câu trả lời khác)

Vấn đề nằm ở dòng sau:

this.dom.addEventListener("click", self.onclick, false); 

Ở đây, bạn vượt qua một đối tượng chức năng được sử dụng như gọi lại . Khi sự kiện kích hoạt, chức năng được gọi nhưng bây giờ nó không có liên kết với bất kỳ đối tượng (điều này).

Vấn đề có thể được giải quyết bằng cách gói các chức năng (với tham chiếu đối tượng của nó) trong một kết thúc như sau:

this.dom.addEventListener(
    "click", 
    function(event) {self.onclick(event)}, 
    false); 

Kể từ khi tự biến được gán này khi đóng cửa đã được tạo ra, chức năng đóng cửa sẽ nhớ giá trị của biến tự khi nó được gọi sau đó.

Một cách khác để giải quyết việc này là để thực hiện một chức năng hữu ích (và tránh sử dụng các biến cho ràng buộc này):

function bind(scope, fn) { 
    return function() { 
     fn.apply(scope, arguments); 
    }; 
} 

Mã được cập nhật sau đó sẽ như thế nào:

this.dom.addEventListener("click", bind(this, this.onclick), false); 

Function.prototype.bind là một phần của ECMAScript 5 và cung cấp chức năng tương tự. Vì vậy, bạn có thể làm:

this.dom.addEventListener("click", this.onclick.bind(this), false); 

Đối với trình duyệt mà không hỗ trợ ES5 nào, MDN provides the following shim:

if (!Function.prototype.bind) { 
    Function.prototype.bind = function (oThis) { 
    if (typeof this !== "function") { 
     // closest thing possible to the ECMAScript 5 internal IsCallable function 
     throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); 
    } 

    var aArgs = Array.prototype.slice.call(arguments, 1), 
     fToBind = this, 
     fNOP = function() {}, 
     fBound = function() { 
      return fToBind.apply(this instanceof fNOP 
           ? this 
           : oThis || window, 
           aArgs.concat(Array.prototype.slice.call(arguments))); 
     }; 

    fNOP.prototype = this.prototype; 
    fBound.prototype = new fNOP(); 

    return fBound; 
    }; 
} 
+1

Chức năng tiện ích createCallback rất tiện dụng! cảm ơn. –

+0

closure() có lẽ là tên tốt hơn. – hishadow

+2

Hoặc liên kết() hoặc bindMethod(). – outis

15
this.dom.addEventListener("click", function(event) { 
    self.onclick(event) 
}, false); 
+1

Cảm ơn, nhưng bạn có thể giải thích lý do tại sao tôi cần phải tạo ra một chức năng ẩn danh đó? Tại sao điều đó thay đổi ràng buộc? –

+0

Bất cứ khi nào một sự kiện được kích hoạt, 'this' đề cập đến đối tượng đã gọi sự kiện. – Tom

+2

Khi bạn sử dụng biểu mẫu trong câu hỏi của bạn, self.onclick chỉ là một chức năng ẩn danh, khi được xử lý, hoạt động giống như bạn đã đính kèm nó trực tiếp vào khoảng. Khi bạn quấn nó trong một đóng cửa, self.onclick (sự kiện) thực sự làm những gì bạn muốn, ví dụ: sử dụng 'self' từ ngữ cảnh xác định việc đóng cửa. –

-4

Đây là một trong những điểm khó hiểu nhất của JS: biến 'this' có nghĩa là đối tượng địa phương nhất ... nhưng chức năng cũng là đối tượng, vì vậy 'this' chỉ ở đó. Có những điểm tinh tế khác, nhưng tôi không nhớ hết.

Tôi thường tránh sử dụng 'this', chỉ cần xác định biến 'me' cục bộ và sử dụng thay vào đó.

+2

Câu lệnh của bạn hoàn toàn không chính xác. Cả hai phương thức apply() và call() của hàm đều cho phép bạn thực hiện một hàm với 'this' đề cập đến một biến mà bạn truyền vào trong cuộc gọi. – Kenaniah

+1

@Kenaniah: .... do đó thêm vào sự nhầm lẫn, ý nghĩa chính xác của mã phụ thuộc vào cách nó được gọi là! – Javier

+0

Các chức năng rất linh hoạt trong JavaScript và không sử dụng 'điều này 'hầu như không phải là giải pháp cho vấn đề. 'this' là tính năng phạm vi động duy nhất trong JS và nó bổ sung thêm rất nhiều sức mạnh cho ngôn ngữ. Và có, thực tế là các hàm là các đối tượng hạng nhất trong JS ngụ ý tính chất động của 'con trỏ ngữ cảnh thực hiện'. Đó là một tính năng tuyệt vời khi được sử dụng một cách khôn ngoan. –

2

Lời giải thích là self.onclick không có nghĩa là những gì bạn nghĩ rằng nó có nghĩa là trong JavaScript. Nó thực sự có nghĩa là hàm onclick trong nguyên mẫu của đối tượng self (không có bất kỳ cách nào tham khảo chính bản thân số self).

JavaScript chỉ có chức năng và không có đại biểu như C#, vì vậy không thể chuyển phương thức VÀ đối tượng cần được áp dụng làm gọi lại.

Cách duy nhất để gọi phương thức trong gọi lại là gọi chính nó bên trong chức năng gọi lại. Bởi vì chức năng Javascript là đóng cửa, họ có thể truy cập các biến khai báo trong phạm vi chúng được tạo ra trong.

var obj = ...; 
function callback(){ return obj.method() }; 
something.bind(callback); 
+0

Tôi gọi hàm nguyên mẫu 'onclick' để dễ dàng liên kết nó. Tôi biết nó sẽ không được gọi tự động bởi sự kiện DOM onclick thực tế, đó là lý do tại sao tôi đã cố gắng để làm người nghe sự kiện để ràng buộc đối tượng của tôi onclick với chức năng onclick của DOM. –

+0

Đó không phải là quan điểm của tôi, tôi hiểu chức năng onclick của bạn. Quan điểm của tôi là không có sự khác biệt về JavaScript giữa self.onclick và foo.prototype.onclick. Không có cách nào trong JavaScript để nói "phương pháp này bị ràng buộc với đối tượng này". –

+0

Cách duy nhất là sử dụng đóng cửa. – dolmen

1

tôi đã viết plugin này ...

tôi nghĩ rằng nó sẽ hữu ích

jquery.callback

2

Giải thích tốt về vấn đề (tôi gặp sự cố khi hiểu các giải pháp được mô tả cho đến thời điểm này) là available here.

5

Đối với jQuery người dùng tìm kiếm một giải pháp cho vấn đề này, bạn nên sử dụng jQuery.proxy

+0

Đây là một giải pháp tốt đẹp vì nó được xây dựng ngay trong jQuery. – Chetan