標籤:
函數綁定(Function binding)很有可能是你在開始使用JavaScript時最少關注的一點,但是當你意識到你需要一個解決方案來解決如何在另一個函數中保持this內容相關的時候,你真正需要的其實就是 Function.prototype.bind(),只是你有可能仍然沒有意識到這點。
第一次遇到這個問題的時候,你可能傾向於將this設定到一個變數上,這樣你可以在改變了上下文之後繼續引用到它。很多人選擇使用 self, _this 或者 context 作為變數名稱(也有人使用 that)。這些方式都是有用的,當然也沒有什麼問題。但是其實有更好、更專用的方式。
我們真正需要解決的問題是什嗎?
在下面的例子代碼中,我們可以名正言順地將上下文緩衝到一個變數中:
var myObj = { specialFunction: function () { }, anotherSpecialFunction: function () { }, getAsyncData: function (cb) { cb(); }, render: function () { var that = this; this.getAsyncData(function () { that.specialFunction(); that.anotherSpecialFunction(); }); }}; myObj.render();
如果我們簡單地使用 this.specialFunction() 來調用方法的話,會收到下面的錯誤:
Uncaught TypeError: Object [object global] has no method ‘specialFunction‘
我們需要為回呼函數的執行保持對 myObj 物件內容的引用。 調用 that.specialFunction()讓我們能夠維持範圍上下文並且正確執行我們的函數。 然而使用 Function.prototype.bind() 可以有更加簡潔乾淨的方式:
render: function () { this.getAsyncData(function () { this.specialFunction(); this.anotherSpecialFunction(); }.bind(this)); }我們剛才做了什嗎?
.bind()建立了一個函數,當這個函數在被調用的時候,它的 this 關鍵詞會被設定成被傳入的值(這裡指調用bind()時傳入的參數)。因此,我們傳入想要的上下文,this(其實就是 myObj),到.bind()函數中。然後,當回呼函數被執行的時候, this 便指向 myObj 對象。
如果有興趣想知道 Function.prototype.bind() 內部長什麼樣以及是如何工作的,這裡有個非常簡單的例子:
Function.prototype.bind = function (scope) { var fn = this; return function () { return fn.apply(scope); };}
還有一個非常簡單的用例:
var foo = { x: 3} var bar = function(){ console.log(this.x);} bar(); // undefined var boundFunc = bar.bind(foo); boundFunc(); // 3
我們建立了一個新的函數,當它被執行的時候,它的 this 會被設定成 foo —— 而不是像我們調用 bar() 時的全域範圍。
瀏覽器支援
| Browser |
Version support |
| Chrome |
7 |
| Firefox (Gecko) |
4.0 (2) |
| Internet Explorer |
9 |
| Opera |
11.60 |
| Safari |
5.1.4 |
正如你看到的,很不幸,Function.prototype.bind 在IE8及以下的版本中不被支援,所以如果你沒有一個備用方案的話,可能在運行時會出現問題。
幸運的是,Mozilla Developer Network(很棒的資產庫),為沒有自身實現 .bind() 方法的瀏覽器提供了一個絕對可靠的替代方案:
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 && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; };}適用的模式
在學習技術點的時候,我發現有用的不僅僅在於徹底學習和理解概念,更在於看看在手頭的工作中有沒有適用它的地方,或者比較接近它的的東西。我希望,下面的某些例子能夠適用於你的代碼或者解決你正在面對的問題。
CLICK HANDLERS(點擊處理函數)
一個用途是記錄點擊事件(或者在點擊之後執行一個操作),這可能需要我們在一個對象中存入一些資訊,比如:
var logger = { x: 0, updateCount: function(){ this.x++; console.log(this.x); }}
我們可能會以下面的方式來指定點擊處理函數,隨後調用 logger 對象中的 updateCount() 方法。
document.querySelector(‘button‘).addEventListener(‘click‘, function(){ logger.updateCount();});
但是我們必須要建立一個多餘的匿名函數,來確保 updateCount()函數中的 this 關鍵字有正確的值。
我們可以使用如下更乾淨的方式:
document.querySelector(‘button‘).addEventListener(‘click‘, logger.updateCount.bind(logger));
我們巧妙地使用了方便的 .bind() 函數來建立一個新的函數,而將它的範圍綁定為 logger 對象。
SETTIMEOUT
如果你使用過模板引擎(比如Handlebars)或者尤其使用過某些MV*架構(從我的經驗我只能談論Backbone.js),那麼你也許知道下面討論的關於在渲染模板之後立即訪問新的DOM節點時會遇到的問題。
假設我們想要執行個體化一個jQuery外掛程式:
var myView = { template: ‘/* 一個包含 <select /> 的模板字串*/‘, $el: $(‘#content‘), afterRender: function () { this.$el.find(‘select‘).myPlugin(); }, render: function () { this.$el.html(this.template()); this.afterRender(); }} myView.render();
你或許發現它能正常工作——但並不是每次都行,因為裡面存在著問題。這是一個競爭的問題:只有先到達的才能獲勝。有時候是渲染先到,而有時候是外掛程式的執行個體化先到。【譯者註:如果渲染過程還沒有完成(DOM Node還沒有被添加到DOM樹上),那麼find(‘select’)將無法找到相應的節點來執行執行個體化。】
現在,或許並不被很多人知曉,我們可以使用基於 setTimeout() 的 slight hack來解決問題。
我們稍微改寫一下我們的代碼,就在DOM節點載入後再安全的執行個體化我們的jQuery外掛程式:
afterRender: function () { this.$el.find(‘select‘).myPlugin(); }, render: function () { this.$el.html(this.template()); setTimeout(this.afterRender, 0); }
然而,我們獲得的是 函數 .afterRender() 不能找到 的錯誤資訊。
我們接下來要做的,就是將.bind()使用到我們的代碼中:
// afterRender: function () { this.$el.find(‘select‘).myPlugin(); }, render: function () { this.$el.html(this.template()); setTimeout(this.afterRender.bind(this), 0); } //
原文出自:http://blog.jobbole.com/58032/
參考資料:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
深入理解javascript中的Function.prototye.bind