1. 程式人生 > 其它 >手寫JS面試題 --- call apply bind 實現

手寫JS面試題 --- call apply bind 實現

手寫JS面試題 --- call apply bind 實現

題目描述:手寫 call apply bind 實現

實現程式碼如下:

    Function.prototype.myCall = function (context, ...args) {
        if (!context || context === null) {
            context = window;
        }

        // 建立唯一的 key 值 作為我們構造的 context 內部方法名
        let fn = Symbol();
        context[fn] = this; // this 指向呼叫 call 的函式
        // 執行函式並返回結果 相當於把自身作為傳入的 context 的方法進行呼叫了!
        return context[fn](...args);
    };

    // apply 原理一致 只是第二個傳入的引數是一個數組!
    Function.prototype.myApply = function (context, args) {
        if (!context || context === null) {
            context = window;
        }

        // 建立唯一的 key 值 作為我們構造的 context 內部方法名
        let fn = Symbol();
        context[fn] = this;
        // 執行函式並返回結果!
        return context[fn](...args);
    };

    // bind 實現要複雜一點 因為他考慮的情景比較多 還要涉及到引數合併(類似函式柯里化)

    Function.prototype.myBind = function (context, ...args) {
        if (!context || context === null) {
            context = window;
        }

        // 建立唯一的 key 值 作為我們構造的 context 內部方法名!
        let fn = Symbol();
        context[fn] = this;
        let _this = this;
        // bind 情況要複雜一點
        const result = function (...innerArgs) {
            // 第一種情況 :若是將 bind 繫結之後的函式當作建構函式,通過 new 操作符使用,則不繫結傳入的 this,而是將 this 指向例項化出來的物件
            // 此時由於new操作符作用  this指向result例項物件  而result又繼承自傳入的_this 根據原型鏈知識可得出以下結論
            // this.__proto__ === result.prototype // this instanceof result => true
            // this.__proto__.__proto__ = result.prototype.__proto__ === _this.prototype; // this instanceof _this => true
            if (this instanceof _this === true) {
                // 此時 this 指向 result 的例項 這時候不需要改變 this 指向
                this[fn] = _this;
                this[fn](...[...args, ...innerArgs]); // 這裡使用 es6 的方法讓 bind 支援引數合併
                delete this[fn];
            } else {
                // 如果只是作為普通的函式使用 那就很簡單了 直接改變 this 指向為傳入的 context
                context[fn](...[...args, ...innerArgs]);
                delete context[fn];
            }
        };
        // 如果繫結的是建構函式 那麼需要繼承建構函式原型的屬性和方法
        // 實現繼承的方式:使用 Object.create
        result.prototype = Object.create(this.prototype);
        return result;
    }

    // 用法如下:
    function Person(name, age) {
        console.log(name); //'我是引數傳進來的name'
        console.log(age); //'我是引數傳進來的age'
        console.log(this); //建構函式this指向例項物件
    }

    // 建構函式原型的方法
    Person.prototype.say = function() {
        console.log(123);
    }

    let obj = {
        objName: '我是 obj 傳進來的 name',
        objAge: '我是 objAge 傳進來的 age'
    }

    // 普通函式
    function normalFun(name, age) {
        console.log(name);   //'我是引數傳進來的name'
        console.log(age);   //'我是引數傳進來的age'
        console.log(this); //普通函式this指向繫結bind的第一個引數 也就是例子中的obj
        console.log(this.objName); //'我是obj傳進來的name'
        console.log(this.objAge); //'我是obj傳進來的age'

    }

    // 先測試作為建構函式的呼叫
    // let bindFun = Person.myBind(obj, '我是引數傳進來的name')
    // let a = new bindFun('我是引數傳進來的 age')
    // a.say(); // 123

    console.log('------------------------')

    // 在測試作為普通函式的呼叫!
    let bindFun = normalFun.myBind(obj, '我是引數傳進來的name')
    bindFun('我是引數傳進來的 age')