2011-09-02 9 views
22

Tôi có câu hỏi khá thú vị về EcmaScript-5 Function.prototype.bind implementation. Thông thường khi bạn sử dụng ràng buộc, bạn làm điều đó theo cách này:Function.prototype.bind

var myFunction = function() { 
    alert(this); 
}.bind(123); 

// will alert 123 
myFunction(); 

Được rồi vì vậy đó là mát mẻ, nhưng những gì là giả sử xảy ra khi chúng ta làm điều này?

// rebind binded function 
myFunction = myFunction.bind('foobar'); 
// will alert... 123! 
myFunction(); 

Tôi hiểu rằng đó là hành vi hoàn toàn hợp lý trong điều kiện như thế nào Function.prototype.bind được thực hiện (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind). Nhưng trong điều kiện thực tế, đó là hành vi hoàn toàn vô dụng phải không? Câu hỏi đặt ra là: nó là lỗi hay tính năng? Nếu đó là lỗi, tại sao nó không được đề cập đến? Nếu đó là một tính năng, tại sao Google Chrome với bản cài đặt "liên kết" gốc hoạt động hoàn toàn theo cùng một cách?

Để làm cho nó rõ ràng hơn, những gì theo ý kiến ​​của tôi sẽ có ý nghĩa hơn, đây là đoạn mã mà thực hiện Function.prototype.bind một chút khác nhau:

if (!Function.prototype.bind) { 
    Function.prototype.bind = function() { 
     var funcObj = this; 
     var original = funcObj; 
     var extraArgs = Array.prototype.slice.call(arguments); 
     var thisObj = extraArgs.shift(); 
     var func = function() { 
      var thatObj = thisObj; 
      return original.apply(thatObj, extraArgs.concat(
       Array.prototype.slice.call(
        arguments, extraArgs.length 
       ) 
      )); 
     }; 
     func.bind = function() { 
      var args = Array.prototype.slice.call(arguments); 
      return Function.prototype.bind.apply(funcObj, args); 
     } 
     return func; 
    }; 
} 

Bây giờ thử điều này:

// rebind binded function 
myFunction = myFunction.bind('foobar'); 
// will alert... "foobar" 
myFunction(); 

Theo tôi, thay thế "này" có ý nghĩa hơn ...

Vì vậy, những gì bạn nghĩ về nó?

+2

Đó là một tính năng. Nếu bạn có thể ghi đè lên nó, nó sẽ không thực sự "bị ràng buộc", phải không? Các chi tiết có thể được tìm thấy trong đặc điểm kỹ thuật: http://ecma262-5.com/ELS5_HTML.htm#Section_15.3.4.5 –

+1

Chắc chắn nhưng nếu có, thì cách xác định xem hàm có bị ràng buộc với một cái gì đó? hoặc tại sao nó không ném bất kỳ ngoại lệ khi cố gắng để rebind nó? Vấn đề là nó làm cho nó vô cùng khó khăn để gỡ lỗi, nếu bạn không biết nếu chức năng đã bị ràng buộc hoặc bạn đối phó với bản sao đầu tiên của nó. Điều đó đã xảy ra với tôi ngày hôm qua, và tôi đã dành gần một ngày để tìm ra nguồn gốc của vấn đề ... – Ruslan

+1

Dù sao thì, có vẻ như không ai trả lời câu hỏi này, vì vậy nếu có ai đó sẽ có cùng một vấn đề, tôi đã tạo một workaroud mà bạn có thể tìm thấy ở đây: http://www.angrycoding.com/2011/09/to-bind-or-not-to-bind-that-is-in.html – Ruslan

Trả lời

14

Tiền lệ cho Function.prototype.bind là việc triển khai ý tưởng trong các khuôn khổ JS khác nhau. Theo hiểu biết tốt nhất của tôi, không ai trong số họ cho phép this ràng buộc được thay đổi bằng cách ràng buộc tiếp theo. Bạn cũng có thể hỏi tại sao không ai trong số họ cho phép thay đổi ràng buộc này, như để hỏi tại sao ES5 không cho phép nó.

Bạn không phải là người duy nhất tôi đã nghe ai nghĩ đến điều kỳ lạ này. Chris Leary, người làm việc trên công cụ JS của Mozilla (như tôi), nghĩ rằng nó hơi kỳ quặc, gây ra vấn đề trên Twitter một vài tháng trước. Và trong một hình thức hơi khác, tôi nhớ một trong những tin tặc của Mozilla Labs đặt câu hỏi nếu có cách nào đó để "unbind" một hàm, để trích xuất hàm mục tiêu từ nó. (Nếu bạn có thể làm điều đó, bạn có thể ràng buộc nó với một khác nhau this, ít nhất là nếu bạn cũng có thể trích xuất danh sách đối số ràng buộc để cũng vượt qua nó cùng.)

Tôi không nhớ vấn đề đang được thảo luận khi bind đã được chỉ định. Tuy nhiên, tôi đã không chú ý đặc biệt đến danh sách gửi thư thảo luận tại thời điểm công cụ này được băm ra. Điều đó nói rằng, tôi không tin ES5 đang tìm kiếm để đổi mới trong khu vực nhiều, chỉ cần "mở một con đường đồng hồ", để mượn một cụm từ.

Bạn có thể có thể đề xuất một số phương pháp nội suy để giải quyết những mối quan ngại này để thảo luận, nếu bạn đã viết một đề xuất đầy đủ chi tiết. Mặt khác, ràng buộc là một hình thức của cơ chế ẩn thông tin, mà sẽ cắt giảm chống lại việc áp dụng nó. Nó có thể là một giá trị cố gắng để đề xuất một cái gì đó, nếu bạn có thời gian. Tôi đoán là mối quan tâm ẩn thông tin sẽ chặn một đề xuất không được chấp nhận. Nhưng đó chỉ là phỏng đoán có thể là sai. Chỉ có một cách để tìm ra ...

+0

Cảm ơn bạn rất nhiều vì câu trả lời, nó rất hữu ích. Bạn có thể mô tả quy trình đưa ra đề xuất như vậy không? Tôi nghĩ điều này sẽ hữu ích không chỉ cho trường hợp này. Cảm ơn bạn trước. – Ruslan

+0

Tôi không tham gia đủ vào công việc tiêu chuẩn hóa thực tế để biết nhiều về quy trình (tôi không tin rằng có một quy trình chính thức), nhưng gửi thư đến [thảo luận] (https://mail.mozilla.org/listinfo/es-thảo luận) sẽ nhận được quả bóng lăn. Bạn cũng có thể thử hỏi trong kênh #jslang trên irc.mozilla.org; đó là hangout thực sự duy nhất tôi biết về các tiêu chuẩn ngôn ngữ JS tham gia vào việc chỉ định nội dung, vì vậy ai đó ở đó cũng có thể biết cách nhận đề xuất đầy đủ. –

2

Khi bạn liên kết một hàm, bạn yêu cầu một hàm mới bỏ qua đối số giả của nó và gọi hàm gốc với giá trị cố định cho điều này.

Ràng buộc chức năng này một lần khác có chính xác cùng một hành vi. Nếu ràng buộc bằng cách nào đó sẽ vá một cái mới vào nó, nó sẽ phải có trường hợp đặc biệt cho các hàm đã bị ràng buộc.Nói cách khác, bind hoạt động chính xác như nhau trên các hàm "bình thường" khi nó hoạt động trên các hàm trả về bởi ràng buộc, và trong trường hợp không có các yếu tố quan trọng, đó là kỹ thuật tốt để giữ sự phức tạp ngữ nghĩa của hàm thấp - dễ dàng hơn để nhớ ràng buộc nào thực hiện cho một hàm nếu nó xử lý tất cả các hàm đầu vào theo cùng một cách, trái ngược với việc xử lý một số hàm đầu vào đặc biệt.

Tôi nghĩ sự nhầm lẫn của bạn là bạn xem ràng buộc như sửa đổi một hàm hiện có, và do đó, nếu bạn sửa đổi lại, bạn mong đợi hàm ban đầu sẽ được sửa đổi lại. Tuy nhiên, ràng buộc không sửa đổi bất cứ điều gì, nó tạo ra một chức năng mới với hành vi cụ thể. Chức năng mới là một chức năng ở bên phải của chính nó, nó không phải là một phiên bản ma thuật vá của chức năng ban đầu.

Như vậy, không có gì bí ẩn về lý do tại sao nó được chuẩn hóa hoặc phát minh theo cách nó: bind trả về một hàm cung cấp một đối số mới và lập trước, và hoạt động giống nhau trên tất cả các hàm. Ngữ nghĩa đơn giản nhất có thể.

Chỉ có cách này là nó thực sự an toàn để sử dụng - nếu ràng buộc sẽ tạo ra sự khác biệt giữa các hàm đã bị ràng buộc và các hàm bình thường, người ta phải kiểm tra trước khi ràng buộc. Một ví dụ điển hình là jQuery.each, nó sẽ vượt qua điều này. Nếu liên kết với các hàm ràng buộc đặc biệt, sẽ không có cách nào an toàn để chuyển một hàm ràng buộc vào jQuery.each, vì điều này sẽ bị ghi đè trên mỗi cuộc gọi. Để khắc phục điều đó, jQuery.each sẽ phải đặc biệt hóa các hàm ràng buộc bằng cách nào đó.

+0

Người ta có thể lập luận rằng ràng buộc không nên chạm vào điều này chút nào, vì điều này sẽ phù hợp với hành vi JS khác. Tuy nhiên, tôi muốn nói rằng ứng dụng phổ biến nhất cho ràng buộc là chuyển đổi một lời gọi phương thức thành một cuộc gọi hàm (javascript không có đối tượng gọi phương thức tích hợp có thể được chuyển qua như gọi lại - ràng buộc lấp đầy lỗ này) –

+0

, bạn nên tự hỏi tại sao bạn mong đợi liên kết với bản vá trong một giá trị mới này, nhưng không được vá trong các đối số mới. Điều đó khá không nhất quán. –

+0

Tôi hoàn toàn không đồng ý! Cách bind() hoạt động hoàn toàn phản trực giác. Vấn đề là, bạn phải biết rằng một hàm đã bị ràng buộc. Bạn không bao giờ có thể chắc chắn những gì bạn sẽ nhận được từ một hàm khi gọi .bind() trên đó. Đôi khi bạn nhận được hành vi mong đợi.Đôi khi bạn không. Nó chỉ là như thế này: một hàm ràng buộc có ngữ nghĩa đặc biệt và KHÔNG chỉ là một hàm. Theo tôi, một hàm ràng buộc chỉ là một hàm mà bạn có thể áp dụng tất cả các thứ từ Function.prototype. Nếu không, hãy cung cấp cho nó một nguyên mẫu khác, ví dụ: BoundFunction và loại bỏ .bind() khỏi nó. – NicBright