一、bind()函數的兩個特性:
1、bind和curring,函數科里化
function add(a, b, c) { var i = a+b+c; console.log(i); return i; } var func = add.bind(undefined, 100);//給add()傳了第一個參數a
func(1, 2);//103,繼續傳入b和c
var func2 = func.bind(undefined, 200);//給func2傳入第一個參數,也就是b,此前func已有參數a=100
func2(10);//310,繼續傳入c,100+200+10
可以利用此種特性方便代碼重用,如下,可以不同的頁面中只需要配置某幾項,前面幾項固定的配置可以選擇用bind函數先綁定好,講一個復雜的函數拆分成簡單的子函數。
2、bind和new
function foo() { this.b = 100; console.log(this.a); return this.a; } var func = foo.bind({a:1}); func();//1
new func();//undefined {b:100},可以看到此時上面的bind並不起作用
函數中的return除非返回的是個對象,否則通過new返回的是個this,指向一個空對象,空對象原型指向foo.prototype,空對象的b屬性是100。也就是說通過new的方式創建一個對象,bind()函數在this層面上並不起作用,但是需要注意在參數層面上仍起作用,如下:
function foo(c) { this.b = 100; console.log(this.a); console.log(c); return this.a; } var func = foo.bind({a:1},20); new func();//undefined 20,通過new創建對象func,bind綁定的c依舊起作用
二、bind實現
了解完以上兩個特性,再來看看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 || window, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }
第3行:傳入oThis就是foo.bind({a:1})中傳入的對象{a:1};
第4行:判斷調用此bind方法的對象是不是一個函數function,若不是則報錯;
第10行:
(1)因為函數自帶的arguments屬性並不是一個數組,只是一個類數組,不具有slice這些方法,所以用call方法給slice()指定this為arguments,讓arguments也可以實現slice()方法。
(2)后面傳入參數1,是slice(start, end)中的一個參數start,表示從arguments的小標為1,即第二個參數開始切割。 這里是將bind函數的參數數組取出來,第一個參數不要(就是不要oThis)也就是要被綁定方法的那個對象
(3)arguments參數只有在函數調用執行的時候才存在,也就是當var func = foo.bind({a:1});的時候,調用了bind,此時aArgs是一個空數組。如果是var func = foo.bind({a:1}, 2),那么aArgs = [2];
第12行,13行,20行,21行:創建了一個空對象FNOP,並將這個空對象的原型指向foo的原型;
然后又將func/fBound的原型指向一個新的FNOP實例,這個步驟完成了給func/fBound拷貝一個FNOP的prototype即this/foo的prototype。
其實這幾句就相當於fBound.prototype = Object.create(this.prototype);
第14行:
(1)給 fBound/func return一個fToBind/foo對象;
(2)這里的this指的是調用func()時的執行環境;直接調用func()的時候,this指向的是全局對象,那么結果是oThis/{a:1},這樣就可以讓這個fToBind的this指向這個傳進來的對象oThis;
(3)bind()同時也會傳參數:aArgs.concat(Array.prototype.slice.call(arguments))
,
【注意】這里的arguments是調用此函數時的arguments,也就是func()的執行環境,和上面的arguments(bind的執行環境)不一樣,在此例中,此時的arguments是空數組,因為並沒有給func()傳參數。這段contact的意思就是把bind()中傳的參數和func()中傳的參數連起來,來實現上面提到的bind的科里性。
(4)如果通過new func()來調用,this會指向一個空對象,這個空對象的原型會指向構造器的prototype的屬性,也就是func/fBound的prototype屬性。此時this instanceof fNOP
為true,那么返回的是this就是當前正常的this;相當於忽略掉bind的this的影響,實現了上述的bind特性二:bind和new。
那么想想為什么要給func/fBound拷貝一個FNOP的prototype即this/foo的prototype?沒有實現這個會怎樣?
我們知道bind()函數其實是實現了this的指定和參數的傳遞; 實際中的new func()其實相當於創建了func()一個新實例,使用的構造函數是func,它只為新對象定義了【默認的】屬性和方法。也就是Object.create(this.prototype)的作用,如果不把foo的prototype拷貝個func,那么這里的new func()就沒法得到foo默認的屬性。 如圖我把那兩行注釋掉后,za沒辦法獲取到this.b的值