如果對call,apply,bind的應用和區別還不瞭解,可以去看我之前的文章瞭解下。 讓你弄懂 call、apply、bind的應用和區別
如果出現錯誤,請在評論中指出,我也好自己糾正自己的錯誤
author: thomaszhou
bind實現
一般我們會直接使用bind函式,但是這次我們通過原生js來嘗試實現這個函式
bind() 方法會建立一個新函式。當這個新函式被呼叫時,bind() 的第一個引數將作為它執行時的 this,之後的一序列引數將會在傳遞的實參前傳入作為它的引數
由此我們可以首先得出 bind 函式的三個特點:
(1)返回一個函式
:我們可以使用 call 或者 apply 實現(2)可以傳入引數
:我們用 arguments 進行處理
var foo = { value: 1 };
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy'); // (1)bindFoo是一個函式
bindFoo('18'); // (2)此處可以再次傳入引數
// 1
// daisy
// 18
複製程式碼
先實現前兩個特點:實現程式碼(version 1.0):
Function.prototype.bind2 = function (context) {
var self = this;
// 獲取bind2函式從第二個引數到最後一個引數
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 這個時候的arguments是指bind返回的函式bindFoo呼叫時傳入的引數
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(context, args.concat(bindArgs));
}
}
複製程式碼
(3)一個繫結函式也能使用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 = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18'); // this 已經指向了 obj
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
複製程式碼
注意:儘管在全域性和 foo 中都宣告瞭 value 值,最後依然返回了 undefind,說明繫結的 this 失效了
先實現第三個特點:實現程式碼(version 2.0):
Function.prototype.bind2 = function (context) {
let self = this;
// self --> ƒ bar(){}
let args = Array.prototype.slice.call(arguments, 1);
let fbound = function () {
let bindArgs = Array.prototype.slice.call(arguments);
// (1) 當作為建構函式時,this --> 例項(fbound建立的的例項),self --> 繫結函式bar,結果為true,那麼self指向例項
// (2) 當作為普通函式時,this -->window,self -->繫結函式,此時結果為false,那麼 self指向繫結的 context。
self.apply(this instanceof self ? this : context, args.concat(bindArgs));
};
// 為了要讓 this instanceof self為true,就要使建立的例項繼承繫結函式bar,
// 唯一辦法就是讓建立例項的函式fbound的prototype指向bar函式的prototype
fbound.prototype = this.prototype;
return fbound;
};
複製程式碼
優化:實現程式碼(version 3.0):
version 2.0 直接將 fbound.prototype = this.prototype
,我們直接修改fbound.prototype 的時候,也會直接修改函式bar的 prototype。這個時候,我們可以通過一個空函式來進行中轉,然後利用組合繼承的方式來實現
Function.prototype.bind2 = function (context) {
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 的原型方法指向 this 即函式bar的原型方法,繼承 this 的屬性
fNOP.prototype = this.prototype;
// 再將 fbound 即要返回的新函式的原型方法指向 fNOP 的例項化物件
// 這樣,既能讓 fBound 繼承 this 的屬性,在修改其原型鏈時,又不會影響到 this 的原型鏈
fbound.prototype = new fNOP();
return fbound;
}
複製程式碼
在上面的程式碼中,我們引入了一個新的函式 fun,用於繼承原函式的原型,並通過 new 操作符例項化出它的例項物件,供 fBound 的原型繼承,至此,我們既讓新函式繼承了原函式的所有屬性與方法,又保證了不會因為其對原型鏈的操作影響到原函式
-------------優化三點-------------------------
(相容性)當 Function 的原型鏈上沒有 bind 函式時,才加上此函式
Function.prototype.bind = Function.prototype.bind || function () {
……
};
複製程式碼
只有函式才能呼叫 bind 函式,其他的物件不行。即判斷 this 是否為函式。
if (typeof this !== 'function') {
throw new TypeError("NOT_A_FUNCTION -- this is not callable");
}
複製程式碼
對於引數傳遞的程式碼,可以用ES6的擴充運算子來替換
最終版本!!!
Function.prototype.bind = Function.prototype.bind || function (context, ...formerArgs) {
let self = this;
if (typeof this !== 'function') {
throw new TypeError("NOT_A_FUNCTION -- this is not callable");
}
let fNOP = function () {};
let fbound = function (...laterArgs) {
self.apply(this instanceof self ? this : context, formerArgs.concat(laterArgs));
// es6 : formerArgs.concat(laterArgs)
};
fNOP.prototype = this.prototype;
fbound.prototype = new fNOP();
return fbound;
};
複製程式碼