JS系列-趣談bind

bind簡介

咱們先來看看MDN上給出的簡介markdown

bind() 方法建立一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定爲 bind() 的第一個參數,而其他參數將做爲新函數的參數,供調用時使用。app

是否是看不太懂,不要緊我翻譯一下,說人話就是 bind 函數執行完會返回一個新的函數,後續咱們都稱爲綁定函數,執行這個綁定函數時, this 的指向就會變成 bind 函數的第一個參數,其餘參數會做爲返回的綁定函數的參數,在執行綁定函數的時候再傳入到這個函數中。 這下明白了吧,什麼?你沒聽明白,管你聽沒聽懂,看就完事。函數

舉個例子:oop

const module = {
    x42,
    getXfunction (y = 0{
        return this.x + y;
    }
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // NaN 該函數在全局做用域中被調用,此時this指向的是window, 

const boundGetX = unboundGetX.bind(module); // 這個時候咱們將新的函數的this指向經過bind改爲了module
console.log(boundGetX()); // 42
console.log(boundGetX(3)); // 45
複製代碼

bind的實現

經過上面的例子咱們已經明白了 bind 的功能了,能夠咱們動手實現 bind 函數了。post

初步模擬實現

在寫代碼前咱們須要瞭解下 bind 有哪些特性,而後咱們能夠根據它的特性來進行實現ui

  • bind 是掛載在 function 的原型上的,因此 function 能直接調用
  • bind 會返回一個新的函數
  • bind 傳遞的第一個參數會綁定爲新函數的 this 的指向
  • bind 的其餘參數會做爲綁定函數的參數
Function.prototype.selfBind = function (context, ...bindArgs{
    const self = this;
    return function (...args{
        return self.apply(context, bindArgs.concat(args)); // 利用apply修改指向傳入的第一個參數,同時參數拼接給新的函數
    }
}
複製代碼

代碼寫完了,咱們來執行上面的例子來看下是否知足要求this

const module = {
    x42,
    getXfunction (y = 0{
        return this.x + y;
    }
};

Function.prototype.selfBind = function (context, ...bindArgs{
    const self = this;
    return function (...args{
        return self.apply(context, bindArgs.concat(args));
    }
}

const unboundGetX = module.getX;
const boundGetX = unboundGetX.selfBind(module); // 使用本身自定義的bind修改this指向
console.log(boundGetX()); // 42,輸出的值和原生bind同樣
console.log(boundGetX(3)); // 45,輸出的值和原生bind同樣
複製代碼

new構造函數處理

這樣咱們就實現...實現好了嗎?spa

咱們來看看MDN的原話吧。prototype

綁定函數自動適應於使用 new 操做符去構造一個由目標函數建立的新實例。當一個綁定函數是用來構建一個值的,原來提供的 this 就會被忽略。不過提供的參數列表仍然會插入到構造函數調用時的參數列表以前。翻譯

什麼意思呢?就是咱們能夠把 bind 返回的函數當作構造函數去用 new 操做符建立實例的時候,bind 傳入的 this 指向會失效,可是傳入的參數仍是有效的。

咱們先來看看原生的 bind 效果吧

const module = {
    x42,
    getXfunction (y = 0{
        console.log('this.x: 'this.x);
        console.log('y :', y);
        this.property = '我是getX自帶的屬性';
        return this.x + y;
    }
};
module.getX.prototype.prototypeProperty = '我是getX原型上的屬性'// 給getX原型掛載數據

const unboundGetX = module.getX;
const boundGetX = unboundGetX.bind(module); // 注意這裏是用的原生的bind
const instance = new boundGetX(3);
// this.x:  undefined
// y: 3
console.log(instance.property); 
// 我是getX自帶的屬性
console.log(instance.prototypeProperty); 
// 我是getX原型上的屬性
複製代碼

這裏咱們能夠看到 this 指向的不是傳入的 module 並且 getX ,這實際上是 new 操做符帶來的影響,下週我會繼續出一篇關於 new 操做符的內幕,有興趣的小夥伴能夠點個關注。我知道大家這時候都是

那麼咱們如今來處理下被 new 的狀況

Function.prototype.selfBind = function (context, ...bindArgs{
    const self = this;
    let fBound = function (...args{
        // 若是綁定函數做爲構造函數使用,經過判斷this是否繼承原函數,this指向當前實例,self指向須要綁定的函數
        return self.apply(this instanceof self ? this : context, bindArgs.concat(args));
    }
    // 修改綁定函數的prototype爲要綁定的函數的prototype,實例就能繼承函數的原型,這樣上面才能用instanceof判斷this是否繼承self
    function Fn({};
    Fn.prototype = this.prototype;
    fBound.prototype = new Fn();
    return fBound;
}
複製代碼

結果驗證

const module = {
    x42,
    getXfunction (y = 0{
        console.log('this.x: 'this.x);
        console.log('y :', y);
        this.property = '我是getX自帶的屬性';
        return this.x + y;
    }
};
module.getX.prototype.prototypeProperty = '我是getX原型上的屬性'// 給getX原型掛載數據

const unboundGetX = module.getX;
const boundGetX = unboundGetX.selfBind(module); // 注意這裏是用的原生的bind
const instance = new boundGetX(3);
// this.x:  undefined
// y: 3
console.log(instance.property); 
// 我是getX自帶的屬性
console.log(instance.prototypeProperty); 
// 我是getX原型上的屬性
複製代碼

和原生的 bind 返回同樣,這樣咱們就完整的實現了一個原生的 bind 方法???no no no,這還不夠嚴謹,咱們還須要對調用 bind 的對象進行一個類型校驗

添加類型校驗

爲了不一些特殊狀況的發生,好比:

let obj = {};
obj.__proto__ = Function.prototype;
obj.selfBind(module);
複製代碼

咱們就須要對調用者進行一個類型校驗了,判斷 this 類型是不是 function 便可

if (typeof this !== 'function') {
    throw new TypeError('Function.prototype.selfBind - what is trying to be bound is not callable');
}
複製代碼

完整代碼

Function.prototype.selfBind = function (context, ...bindArgs{
    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');
    }

    const self = this;
    let fBound = function (...args{
        // 若是綁定函數做爲構造函數使用,經過判斷this是否繼承原函數,this指向當前實例,self指向須要綁定的函數
        return self.apply(this instanceof self ? this : context, bindArgs.concat(args));
    }
    // 修改綁定函數的prototype爲要綁定的函數的prototype,實例就能繼承函數的原型,這樣上面才能用instanceof判斷this是否繼承self
    function Fn({};
    Fn.prototype = this.prototype;
    fBound.prototype = new Fn();
    return fBound;
}
複製代碼

很是感謝各位能閱讀到這裏,以爲有幫助的話不妨點個贊,你的支持是對我對最大的鼓勵。

新一篇的 new 已經更新了,歡迎各位看官捧場!!

趣談new

相關文章
相關標籤/搜索