模擬js中的call、apply和bind的實現

晴天~ ☀發表於2019-03-06

一、call和apply的特點

  • 可以改變我們當前函式的this指向
  • 還會讓當前函式執行

var person = {
    value : 1
}
function bar() {
    console.log(this.value)
}
// 如果不對this進行繫結執行bar() 會返回undefined,this指向window
bar.call(person) // 1複製程式碼

試想一下當呼叫call的時候也就類似於

var person = {    value: 1,
    bar: function() {
        console.log(this.value)
    }
}
person.bar() // 1複製程式碼

這樣就把 this 指向到了 person上,但是這樣給 person 物件加了一個屬性,不太合適,不過不要緊,執行完刪除這個屬性就可以實現了。

也就是說步驟其實是這樣的

  1. 將函式設為物件的屬性
  2. 執行這個函式
  3. 刪除這個函式

1、call模擬實現

Function.prototype.call = function(context){
    context = context ? Object(context) : window;//不傳遞context預設為window
    context.fn = this;//this也就是呼叫call的函式
    let args = [...arguments].slice(1);
    let r = context.fn(...args);
    delete context.fn;
    return r;
}複製程式碼

2、apply模擬實現

apply的方法和 call 方法的實現類似,只不過是如果有引數,以陣列形式進行傳。

Function.prototype.apply= function(context,args){
    context = context ? Object(context) : window;//不傳遞context預設為window
    context.fn = this;
    if(!args){
        return context.fn();
    }
    let r = context.fn(...args);
    delete context.fn;
    return r;
}複製程式碼

二、bind的特點

  • bind方法可以繫結this指向  繫結引數
  • bind方法返回一個繫結後的函式(高階函式)
  • 呼叫繫結後的方法,會讓原方法執行
  • 如果繫結的函式被new了,當前函式的this就是當前的例項
  • new出來的結果,可以找到原有類的原型

1、bind方法模擬實現第一步

用法:

let obj = {
    name:'gjf'
}
function fn(){
    console.log(this.name)
}
let bindFn = fn.bind(obj); //返因一個繫結後的方法
findFn() //用繫結後的方法,讓原方法執行複製程式碼

實現:

Function.prototype.bind = function(context){
    let that = this;
    return  function(){
        return that.apply(context);    
    }
}複製程式碼

這樣實現了最簡單的改變this指向的bind,但是這樣還遠遠不夠,因為bind還可以繫結引數;

2、bind方法模擬實現第二步

方法傳參可以分兩批傳,一批可以先在bind方法裡面先繫結好,另一批在呼叫的時候傳參,例如以下示例;

用法:

let obj = {
    name:'gjf'
}
function fn(name,age){
    console.log(this.name+'養了一隻'+name+age+'歲了')
}
let bindFn = fn.bind(obj,'貓'); //返因一個繫結後的方法
findFn(8) //用繫結後的方法,讓原方法執行複製程式碼

實現:

Function.prototype.bind = function(context){
    let that = this;
    let bindArgs = Array.prototype.slice.call(argument,1)//['貓']
    return  function(){
        let args = Array.prototype.slice.call(argument);
        return that.apply(context,bindArgs.concat(args));    
    }
}複製程式碼

3、bind方法模擬實現第三步

呼叫bind返回的函式除了可以直接呼叫,還可以把函式當成一個類來呼叫;原函式上繫結了屬性,new出來的例項上能否訪問。

用法:

fn.prototype.flag = '哺乳類'; //原函式上繫結了屬性
let findFn = fn.bind(obj,'貓');
let instance = new findFn(8);//如果繫結的函式被new了,當前函式的this就是當前的例項
console.log(instance.flag) //undefined複製程式碼

實現:

Function.prototype.bind = function(context){
    let that = this;
    let bindArgs = Array.prototype.slice.call(argument,1)//['貓']
    function Fn(){} //Object.create的原理
    function fBound(){
        let args = Array.prototype.slice.call(argument);
        return that.apply(this instanceof fBound ? this:context,bindArgs.concat(args));    
    }
    Fn.prototype = this.prototype;
    fBound.prototype = new Fn();
    return fBound;
}複製程式碼

這裡,我們js裡的三種改變this指向的方法就實現啦。。。。。


相關文章