call,apply,bind的模擬實現

不少人都知道這三種方法怎用,可是都不知道他們的原理javascript

call

  • 定義
call() 方法調用一個函數,其具備一個指定的this值和分別地提供的參數(參數的列表)。
複製代碼
let name='憨蛋';

let person={
    name:'romin'
}

function say(){
    console.log('my name is'+this.name)
}

say.call(person);
// my name is romin
複製代碼

call 改變了say函數的this指向,同時say函數執行。java

那麼咱們就來實現它數組

  • 一、改變this指向,直接把 say 函數放進 person 對象中便可。
  • 二、函數執行,調用 person 中的 say 函數
let person={
    name:'romin',
    say:function(){
        console.log(this.name)
    }
}
person.say();
複製代碼

因爲上面的方法改變了 person 對象的結構,須要須要多餘的 say 函數刪除掉,要執行 delete person.say 的操做。bash

這時候,咱們已經明確了該作的事情:app

一、把待執行的函數(call前面的函數)放入指定的對象(call的第一個參數)中;函數

二、執行該函數;ui

三、刪除對象中添加的函數;this

Function.prototype.call = function(context){
    // 第一步
    //這裏的this就是代調用的函數
    context.fn = this;
    // 第二步
    context.fn();
    // 第三步
    delete context.fn;
}
複製代碼

call 中的第一個參數也多是null,也多是字符串,咱們須要對 context 進行處理,不然 字符串上無法掛載 函數 fnspa

// 對context 作處理
context = context?Object(context):window;
複製代碼

以上面的爲例,call 中可能會有多個參數,除了context,剩餘的參數都要交給 say 來處理的。prototype

let name='憨蛋';

let person={
    name:'romin'
}

function say(animal,age){
    console.log(animal)
    console.log(age)
    console.log('my name is '+this.name+',我屬'+animal);
}

say.call(person,'🐯',15);
// my name is romin,我屬🐯
複製代碼

call裏的參數該怎麼交給 say ,而且讓它執行呢?使用 arguments 的話,它是類數組,和 call 的要求不一致,call 要求 一個個傳參的。 拼出一個參數字符串來。

let args = [];
for(let i=1;i<arguments.length;i++){
    args.push('arguments['+i+']')
}

// ['arguments[1]','arguments[2]']

// args 和 字符串拼接,調用了它的 toString() ,至關於 context.fn(arguments[1],arguments[1])
eval('context.fn('+ args +')')
複製代碼

完整的實現:

Function.prototype.call = function(context){
    // 對context 作處理
    context = context?Object(context):window;
    // 第一步,掛載函數
    //這裏的this就是代調用的函數
    context.fn = this;
    
    // 第二步,準備參數,而後讓函數執行
    let args = [];
    for(let i=1;i<arguments.length;i++){
        args.push('arguments['+i+']')
    }
    let result = eval('context.fn('+ args +')')
    // 第三步
    delete context.fn;
    // 注意可能有返回值
    return result;
}

複製代碼

apply

  • 定義
apply() 方法調用一個具備給定this值的函數,以及做爲一個數組(或相似數組對象)提供的參數。
複製代碼

applycall 相似,只是傳參的不一樣而已,能夠直接拿上面的代碼進行改造

Function.prototype.apply = function(context,arr){
    // 對context 作處理
    context = context?Object(context):window;
    // 第一步,掛載函數
    //這裏的this就是代調用的函數
    context.fn = this;
    let result;
    if(!arr){
        result = context.fn();
    }else{
        let args = [];
        for(let i=0;i<arr.length;i++){
            args.push('arr['+i+']')
        }
       result = eval('context.fn('+ args +')') 
    }
    // 第三步
    delete context.fn;
    // 注意可能有返回值
    return result;
}
複製代碼

bind

  • 定義:
`bind()`方法建立一個新的函數,在調用時設置`this`關鍵字爲提供的值。並在調用新函數時,將給定參數列表做爲原函數的參數序列的前若干項。
複製代碼

特色:改變 this 指向,同時返回一個函數(高階函數),能夠傳入參數;

let name='憨蛋';

let person={
    name:'romin'
}

function say(animal,age){
    console.log(animal)
    console.log(age)
    console.log('my name is '+this.name+',我屬'+animal);
}

let rominsay = say.bind(person,'🐯');
rominsay(15);
複製代碼

它的模擬實現,若是不帶參數列表:

Function.prototype.bind = function(context){
    let that = this;
    return function(){
        // 一樣要注意可能有返回值
        return that.apply(context);
    }
}
複製代碼

第二版,若是有參數:

Function.prototype.bind = function(context){
    let that = this;
    // 取到參數列表
    let bindArgs = Array.prototype.slice.call(arguments,1);
    return function(){
        // 取到調用時候的參數列表
        let args = Array.prototype.slice.call(arguments);
        // 一樣要注意可能有返回值
        return that.apply(context,bindArgs.concat(args));
    }
}
複製代碼

接下來請看,下面的例子使用原生的 bind 方法:

let person={
    name:'romin',
    gender:'男',
}

function Say(name,age){
    this.habit = '上班划水';
    console.log(this.gender);
    console.log(name);
    console.log(age);
}

Say.prototype.friend = '憨蛋';

let RominSay = Say.bind(person,'romin');

let say = new RominSay('15');

console.log(say.friend) // 憨蛋
console.log(say.gender);// undefined
console.log(say.habit)// 上班划水

複製代碼

若是使用本身實現的方法,那麼結果是

console.log(say.friend) // undefined
console.log(say.gender);// undefined
console.log(say.habit)// undefined
複製代碼

那麼就會有兩個疑惑:

  • 一、原生的方法中gender 失效了
  • 二、我本身寫的friend 屬性沒有繼承成功;
  • 三、我本身寫的habit 也沒有取到;

可是請注意下面的一個問題:

若是被綁定的函數被new ,那麼 返回的函數中的this 是當前函數的實例
複製代碼

套用上面的話,RominSaynew 出了一個 say, 那麼 this 就是 當前 RominSay 的實例say(而不該該是 person 了),那麼就能經過原型鏈找到 friend 屬性,

對上面的實現方法進行改造

Function.prototype.bind = function(context){
    let that = this;
    // 取到參數列表
    let bindArgs = Array.prototype.slice.call(arguments,1);
    function newFun(){
        // 取到調用時候的參數列表
        let args = Array.prototype.slice.call(arguments);
        //當newFun做爲構造函數時,this 指向實例,若是不是,this還指向 context
        return that.apply(this instanceof newFun ?this:context,bindArgs.concat(args));
    }
    // 修改返回函數的 prototype 爲綁定函數的 prototype,實例就能夠繼承綁定函數的原型中的值
    newFun.prototype = this.prototype;
    return newFun;
}

複製代碼
相關文章
相關標籤/搜索