博客 / 詳情

返回

熱點面試題:JS 中 call, apply, bind 概念、用法、區別及實現?

前言

極度投入,深度沉浸,邊界清晰

前端小菜雞一枚,分享的文章純屬個人見解,若有不正確或可待討論點可隨意評論,與各位同學一起學習~

歡迎關注 『前端進階圈』 公眾號 ,一起探索學習前端技術......

公眾號回覆 加羣掃碼, 即可加入前端交流學習羣,長期交流學習......

公眾號回覆 加好友,即可添加為好友

熱點面試題:JS 中 call, apply, bind 概念、用法、區別及實現?

概念:

  • function.call(thisArg, arg1, arg2, ...)
  • function.apply(thisArg, [arg1, arg2, ...])
  • function.bind(thisArg, arg1, arg2, ...)
  • 三者都是改變 this 指向,通過一個參數或多個參數來調用一個函數的。

用法:

let obj = {
    name: "哈哈",
    sayName: function () {
        console.log("sayName", this.name);
        return this.name;
    },
    eat: function (food1, food2) {
        console.log("eat", food1, food2);
    },
};

let obj2 = {
    name: "是的",
};

obj.sayName.call(obj2); // sayName 是的
obj.eat.call(obj2, "魚", "肉"); // eat 魚 肉

obj.eat.apply(obj2, ["魚", "肉"]); // e at 魚 肉

obj.eat.bind(obj2, "魚", "肉"); // 不會調用,需要一個結果來接收
let res = obj.eat.bind(obj2, "魚", "肉");
res(); // eat 魚 肉

區別:

  • call 與 bind 的區別?

    • call 會直接調用,而 bind 會創建一個新的函數作為一個返回值進行調用, 而其餘參數將作為新函數的參數,供調用時使用
  • call 與 apply 的區別?

    • 主要區別在第二個參數中,call 接受的是一個參數列表,也就是一個個參數,而 apply 接受的是一個包含多個參數的數組

實現:

  • function.call(thisArg, arg1, arg2, ...)

    Function.prototype.myCall = function (context, ...args) {
    // 條件判斷,判斷當前調用的對象是否為函數,
    if (Object.prototype.toString.call(this).slice(8, -1) != "Function")
        throw new Error("type error");
    // 判斷傳入上下文對象是否存在,如果不存在,則設置為 window
    if (!context || context === null) context = window;
    // 創建唯一的 key 值,作為構建的 context 內部方法名
    let fn = Symbol();
    // 將 this 指向調用的 call 函數
    context[fn] = this;
    // 執行函數並返回結果 === 把自身作為傳入的 context 的方法進行調用
    return context[fn](...args);
    };
    let obj = {
    name: "哈哈",
    sayName: function () {
        console.log("sayName", this.name);
        return this.name;
    },
    eat: function (food1, food2) {
        console.log("eat", food1, food2);
    },
    };
    let obj2 = {
    name: "是的",
    };
    obj.sayName.myCall(obj2);
  • function.apply(thisArg, [arg1, arg2, ...])

    Function.prototype.MyApply = function (context, args) {
    // 條件判斷,判斷當前調用的對象是否為函數,
    if (Object.prototype.toString.call(this).slice(8, -1) != "Function")
        throw new Error("type error");
    // 判斷傳入上下文對象是否存在,如果不存在,則設置為 window
    if (!context || context === null) context = window;
    // 創建唯一的 key 值,作為構建的 context 內部方法名
    let fn = Symbol();
    // 將 this 指向調用的 call 函數
    context[fn] = this;
    // 執行函數並返回結果 === 把自身作為傳入的 context 的方法進行調用
    return context[fn](...args);
    };
    let obj = {
    name: "哈哈",
    sayName: function () {
        console.log("sayName", this.name);
        return this.name;
    },
    eat: function (food1, food2) {
        console.log("eat", food1, food2);
    },
    };
    let obj2 = {
    name: "是的",
    };
    obj.sayName.MyApply(obj2, []);
  • function.bind(thisArg, arg1, arg2, ...)

    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支持參數合併
        } else {
            // 如果只是作為普通函數調用  那就很簡單了 直接改變this指向為傳入的context
            context[fn](...[...args, ...innerArgs]);
        }
    };
    // 如果綁定的是構造函數 那麼需要繼承構造函數原型屬性和方法
    // 實現繼承的方式: 使用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: "我是obj傳進來的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
    // 再測試作為普通函數調用
    // let bindFun = normalFun.myBind(obj, '我是參數傳進來的name')
    //  bindFun('我是參數傳進來的age')

文章特殊字符描述:

  1. 問題標註 Q:(question)
  2. 答案標註 R:(result)
  3. 注意事項標準:A:(attention matters)
  4. 詳情描述標註:D:(detail info)
  5. 總結標註:S:(summary)
  6. 分析標註:Ana:(analysis)
  7. 提示標註:T:(tips)

往期回顧:

  • 熱點面試題:Virtual DOM 相關問題?
  • 熱點面試題:什麼是粘包/半包問題,該如何解決?
  • 熱點面試題:console.log()同異步問題?
  • 熱點面試題:進程系列問題?
  • 熱點面試題:Node.js 中的垃圾回收機制?
  • 熱點面試題:簡述 http3.0~http1.0 分別有什麼改進?
  • JavaScript中的AMD和CMD規範
  • Vue數據監聽Object.definedProperty()方法的實現

最後:

  • 歡迎關注 『前端進階圈』 公眾號 ,一起探索學習前端技術......
  • 公眾號回覆 加羣掃碼, 即可加入前端交流學習羣,長期交流學習......
  • 公眾號回覆 加好友,即可添加為好友
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.