手写JavaScript apply,call,bind

47 阅读2分钟

手写JavaScript apply,call,bind

写在前面 调用apply,call,bind方法的函数,都不能作为构造函数

let aTestObj = {
    a:1,
    b:2,
    c:3,
    name:'aTestObj'
}

let countFn = function(d,e,f){
    console.log(this.a+this.b+this.c+d+e+f);
}

function consTest(q,w,e){
    this.a=q;
    this.b=w;
    this.c=e;
}

console.log(new consTest.bind(aTestObj)(78,9));
console.log(new consTest.call(aTestObj,7,8,9));
console.log(new consTest.apply(aTestObj,[7,8,9]));
//TypeError: consTest.bind(call/apply) is not a constructor

手写call: 坑点:①call后的第一个参数context不一定是Object形式(可能是基本类型如Number,String,Boolean等),所以一定要用Object包装context ②要让context调用函数,所以这个函数应成为context的属性,同时属性名绝对不能产生覆盖,故选择Symbol类型

//手写call
Function.prototype.myCall = function(context,...args){
    //由于myCall一定是经过某个function调用的,所以myCall中的this指向调用它的那个function
    if(!(this instanceof Function)) return false;
    //用对象包装context,确保context包含对象方法
    if(typeof context !== 'object'){
        context = Object(context); //如果context是一个Number类型,相当于new Number(context)
    }
    //在这个对象上创建一个方法,内容是这个调用myCall的function,这样就能将function的上下文绑定在context对象上
    //但是这个方法绝对不能跟context上的已有方法重名,避免覆盖,因此采用Symbol属性
    let symbolProFunc = Symbol();
    context[symbolProFunc] = this;
    //此时通过context调用function,function中的this会指向context
    let res = context[symbolProFunc](...args);
    //删去这个属性,避免冗余
    delete context[symbolProFunc];
    return res;
}

手写apply:

//手写apply(只需处理数组形式的args)
Function.prototype.myApply = function(context,args){
    if(!(this instanceof Function)) return false;
    if(typeof context !== 'object'){
        context = Object(context);
    }
    let symbolProFunc = Symbol();
    context[symbolProFunc] = this;
    let res = context[symbolProFunc](...args);
    delete context[symbolProFunc];
    return res;
}

可见apply和call本质上是完全一样的。

在手写bind之前,需要再次强调bind函数的几个特点(坑点): ①bind的参数列表可以分两次传入 ②new的优先级高于bind(在绑定this优先级方面),当遇到bind返回的函数作为构造函数的情况时,自动忽略经由bind的第一个参数context(以及其绑定的this)

手写bind:

//手写bind
Function.prototype.myBind = function(context,...someArgs){
    //this指向调用myBind的function
    let fn = this;
    return function bindReturn(...restArgs){
        if(this instanceof bindReturn){
            //bindReturn通过new调用的情况
            return new fn(...someArgs,...restArgs); // new后面的构造函数是fn,即调用bind的函数,bind方法的上下文this
        }else{
            return fn.myCall(context,...someArgs,...restArgs);
        }
    }
}

同时,在bindReturn作构造函数的情况下,new产生对象的原型为调用bind的函数,和bindReturn无关