關於JavaScript中this的軟綁定

首先,什麼是軟綁定?

所謂軟綁定,是和硬綁定相對應的一個詞,在詳細解釋軟綁定以前,咱們先來看看硬綁定。在JavaScript中,this的綁定是動態的,在函數被調用的時候綁定,它指向什麼徹底取決於函數在哪裏調用,狀況比較複雜,光是綁定規則就有默認綁定、隱式綁定、顯式綁定、new綁定等,而硬綁定是顯式綁定中的一種,一般狀況下是經過調用函數的  apply() 、  call() 或者ES5裏提供的  bind() 方法來實現硬綁定的。

硬綁定有什麼問題,爲何須要軟綁定

上述三個方法好是好,能夠按照本身的想法將函數的this強制綁定到指定的對象上(除了使用new綁定能夠改變硬綁定外),可是硬綁定存在一個問題,就是會下降函數的靈活性,而且在硬綁定以後沒法再使用隱式綁定或者顯式綁定來修改this的指向。java

在這種狀況下,被稱爲軟綁定的實現就出現了,也就是說,經過軟綁定,咱們但願this在默認狀況下再也不指向全局對象(非嚴格模式)或  undefined (嚴格模式),而是指向二者以外的一個對象(這點和硬綁定的效果相同),可是同時又保留了隱式綁定和顯式綁定在以後能夠修改this指向的能力。

軟綁定的具體實現

在這裏,我用的是《你不知道的JavaScript 上》中的軟綁定的代碼實現:閉包

 1 if(!Function.prototype.softBind){
 2     Function.prototype.softBind=function(obj){
 3         var fn=this;
 4         var args=Array.prototype.slice.call(arguments,1);
 5         var bound=function(){
 6             return fn.apply(
 7                 (!this||this===(window||global))?obj:this,
 8                 args.concat.apply(args,arguments)
 9             );
10         };
11         bound.prototype=Object.create(fn.prototype);
12         return bound;
13     };
14 }

咱們先來看一下效果,以後再討論它的實現。app

 1 function foo(){
 2     console.log("name: "+this.name);
 3 }
 4 
 5 var obj1={name:"obj1"},
 6     obj2={name:"obj2"},
 7     obj3={name:"obj3"};
 8 
 9 var fooOBJ=foo.softBind(obj1);
10 fooOBJ();//"name: obj1" 在這裏軟綁定生效了,成功修改了this的指向,將this綁定到了obj1上
11 
12 obj2.foo=foo.softBind(obj1);
13 obj2.foo();//"name: obj2" 在這裏軟綁定的this指向成功被隱式綁定修改了,綁定到了obj2上
14 
15 fooOBJ.call(obj3);//"name: obj3" 在這裏軟綁定的this指向成功被硬綁定修改了,綁定到了obj3上
16 
17 setTimeout(obj2.foo,1000);//"name: obj1"
18 /*回調函數至關於一個隱式的傳參,若是沒有軟綁定的話,這裏將會應用默認綁定將this綁定到全局環
19 境上,但有軟綁定,這裏this仍是指向obj1*/

能夠看到軟綁定生效了。下面咱們來具體看一下 softBind() 的實現。函數

 

在第一行,先經過判斷,若是函數的原型上沒有  softBind() 這個方法,則添加它,而後經過  Array.prototype.slice.call(arguments,1) 獲取傳入的外部參數,這裏這樣作其實爲了函數柯里化,也就是說,容許在軟綁定的時候,事先設置好一些參數,在調用函數的時候再傳入另外一些參數(關於函數柯里化你們能夠去網上搜一下詳細的講解)最後返回一個  bound 函數造成一個閉包,這時候,在函數調用  softBind() 以後,獲得的就是  bound 函數,例如上面的  var fooOBJ=foo.softBind(obj1) 。
 
在  bound 函數中,首先會判斷調用軟綁定以後的函數(如fooOBJ)的調用位置,或者說它的this的指向,若是  !this (this指向undefined)或者  this===(window||global) (this指向全局對象),那麼就將函數的this綁定到傳入  softBind 中的參數obj上。若是此時this不指向undefind或者全局對象,那麼就將this綁定到如今正在指向的函數(即隱式綁定或顯式綁定)。  fn.apply 的第二個參數則是運行  foo 所須要的參數,由上面的args(外部參數)和內部的arguments(內部參數)鏈接成,也就是上面說的柯里化。
 
其實在第一遍看這個函數時,也有點迷,有一些疑問,好比  var fn=this 這句,在  foo 經過  foo.softBind() 調用  softBind 的時候,fn到底指向誰呢?是指向foo仍是指向softBind?咱們能夠寫個demo測試,而後能夠很清晰地看出fn指向什麼:
 1 var a=2;
 2 function foo(){
 3 }
 4 foo.a=3;
 5 Function.prototype.softBind=function(){
 6     var fn=this;
 7     return function(){
 8         console.log(fn.a);
 9     }
10 };
11 Function.prototype.a=4;
12 Function.prototype.softBind.a=5;
13 
14 foo.softBind()();//3
15 Function.prototype.softBind()();//4
能夠看出,fn(或者說this)的指向仍是遵循this的綁定規則的,  softBind 函數定義在Function的原型  Function.prototype 中,可是JavaScript中函數永遠不會「屬於」某個對象(不像其餘語言如java中類裏面定義的方法那樣),只是對象內部引用了這個函數,因此在經過下面兩種方式調用時,fn(或者說this)分別隱式綁定到了  foo 和  Function.prototype ,因此分別輸出3和4。後面的  fn.apply() 也就至關於  foo.apply() 。
 
注:這篇文章我首載於掘金,因爲一些我的緣由如今才發到博客園,原文可見於 http://www.javashuo.com/article/p-ahgsjgas-ez.html
相關文章
相關標籤/搜索