bind
bind() 方法會創建一個新函數。當這個新函數被調用時,bind() 的第一個參數將作爲它運行時的 this,之後的一序列參數將會在傳遞的實參前傳入作爲它的參數。
由此我們可以首先得出 bind 函數的兩個特點:
- 返回一個函數
- 可以傳入參數
例1
var foo = { value: 1};
function bar() {
console.log(this.value);
}
var bindFoo = bar.bind(foo);
bindFoo(); // 1
讓我們看看發生了什麼?
(1) bind返回了一個函數
(2) 傳入的第一個參數改變了bar的指針
模擬bind第一版:
Function.prototype.myBind = function (context) {
var self = this;
return function () {
self.apply(context);
}
}
例2
var foo = {value: 1};
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo, 'bty', '18');
bindFoo(); // 1 bty 18
var bindFoo = bar.bind(foo, 'bty');
bindFoo('18'); // 1 bty 18
var bindFoo = bar.bind(foo);
bindFoo('bty', '18'); // 1 bty 18
讓我們看看發生了什麼?
name和age兩個參數,
(1)可以都在bind的時候傳入
(2)也可以在 bind 的時候,只傳一個 name,在執行返回的函數的時候,再傳另一個參數 age
(3)還可以在返回函數的時候都傳入
這裏我們用 arguments 進行處理,
模擬bind第二版:
Function.prototype.myBind = function (context) {
var self = this;
// 獲取myBind從第二個參數到最後一個參數
var args = Array.prototype.slice.call(arguments, 1);
return function() {
// 這時的arguments是指bind返回的函數傳入的參數
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(context, args.concat(bindArgs));
}
}
例3
bind 另一個特點:
一個綁定函數也能使用new操作符創建對象:這種行爲就像把原函數當成構造器。提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。
var value = 2;
var foo = {value: 1};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'bty';
var bindFoo = bar.bind(foo, 'dadd');
var obj = new bindFoo('18'); // undefined dadd 18
console.log(obj.habit); // shopping
console.log(obj.friend); //bty
讓我們看看發生了什麼?
全局和 foo 中都聲明瞭 value 值,但最後返回了 undefind,說明綁定的 this 失效了。
如果大家瞭解 new 的模擬實現,就會知道這個時候的 this 已經指向了 obj。
模擬bind第三版:
Function.prototype.myBind = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fbound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
// 當作爲構造函數時,this 指向實例,self 指向綁定函數,因爲下面一句 `fbound.prototype = this.prototype;`,已經修改了 fbound.prototype 爲 綁定函數的 prototype,此時結果爲 true,當結果爲 true 的時候,this 指向實例。
// 當作爲普通函數時,this 指向 window,self 指向綁定函數,此時結果爲 false,當結果爲 false 的時候,this 指向綁定的 context。
self.apply(this instanceof self ? this : context, args.concat(bindArgs));
}
fbound.prototype = this.prototype;
return fbound;
}
上面代碼的問題:
(1)fbound.prototype = this.prototype:我們直接修改 fbound.prototype 時,也會直接修改函數的 prototype。這個時候,我們可以通過一個空函數來進行中轉
(2)如果調用bind不是函數怎麼辦?
模擬bind第四版:
Function.prototype.myBind = function (context) {
if (typeof this !== "function") { //(2)
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fbound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(this instanceof self ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fbound.prototype = new fNOP(); //(1)
return fbound;
}