若是對call,apply,bind的應用和區別還不瞭解,能夠去看我以前的文章瞭解下。 讓你弄懂 call、apply、bind的應用和區別es6
若是出現錯誤,請在評論中指出,我也好本身糾正本身的錯誤bash
author: thomaszhouapp
bind實現
通常咱們會直接使用bind函數,可是此次咱們經過原生js來嘗試實現這個函數函數
bind() 方法會建立一個新函數。當這個新函數被調用時,bind() 的第一個參數將做爲它運行時的 this,以後的一序列參數將會在傳遞的實參前傳入做爲它的參數post
由此咱們能夠首先得出 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 失效了ui
先實現第三個特色:實現代碼(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。這個時候,咱們能夠經過一個空函數來進行中轉,而後利用組合繼承的方式來實現this
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 的原型繼承,至此,咱們既讓新函數繼承了原函數的全部屬性與方法,又保證了不會由於其對原型鏈的操做影響到原函數spa
-------------優化三點-------------------------
(兼容性)當 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;
};
複製代碼