手撕call、bind、apply

返回值:

call/apply:fun执行的结果 bind:返回fun的拷贝,并拥有指定的this值和初始参数

参数

thisArg(可选):

  • 若thisArg存在,funthis指向thisArg对象
  • 非严格模式下:若thisArg指定为null,undefined,fun中的this指向window对象.
  • 严格模式下:funthis为指定的null或undefined
  • 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean
 call()apply()bind()
相同点改变函数this指向改变函数this指向改变函数this指向
是否调用函数
传递参数逗号,隔开数组形式[]逗号,隔开
应用场景继承与数组有关不想调用函数

手撕call

注意:

值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean

思路:

  1. 根据call的规则设置上下文对象,也就是this的指向(指向window或自动包装对象)。
  2. 通过添加obj的临时属性,将函数的this指向隐式绑定到obj上
  3. 通过隐式绑定执行函数并传递参数。
  4. 删除临时属性,返回函数执行结果
function handle(...para) {
    console.log( this, ...para) // do some thing
}//等同于 var handle = new Function('...para', `console.log...`)
obj=new Object();
obj.name="chun";


//handle实例的__proto__指向构造函数Function的prototype,在这上面添加myCall
Function.prototype.myCall = function (obj, ...args) {
    // 判断上下文
    if (obj === null || obj === undefined) {
       // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
        obj = window 
    } else {
        obj = Object(obj) // 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean
    }
//node环境下绑定的是global
    // 将函数设置为对象的属性
    obj.fn = this;//this指向handle这个函数
    // 执行这个函数,并拿到返回值
    const res = obj.fn(...args);
    // 删除这个函数属性
    delete obj.fn;
    // 返回值
    return res;
};
handle.myCall(obj,1,2,3,4);// { name: 'chun', fn: [Function: handle] } 1 2 3 4

使用Symbol临时储存函数,直接在对象上绑定有跟上下文对象的原属性冲突的风险。

const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数

context[specialPrototype] = this; // 函数的this指向隐式绑定到context上

Function.prototype.myCall = function (context, ...args) {
    context = context || window//为null或者undefined的话则绑定到window上
    // 创建唯一的属性防止污染
    const key = Symbol()
    // this 就是绑定的那个函数
    context[key] = this
    const result = context[key](...args)
    delete context[key]
    return result
}

手撕apply

apply第二个参数是以数组形式传递的,所以基本步骤与call一致,不同的是函数执行的时候需要进行判断是否传入了第二个参数。如果有,将其传入并执行;若没有,直接执行。

function handle(...para) {
    console.log( this, ...para) // do some thing
}//等同于 var handle = new Function('...para', `console.log...`)
obj=new Object();
obj.name="chun";


//handle实例的__proto__指向构造函数Function的prototype,在这上面添加myCall
Function.prototype.myApply = function (obj, arg) {
    // 判断上下文
    if (obj === null || obj === undefined) {
       // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
        obj = window 
    } else {
        obj = Object(obj) // 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean
    }
    //node环境下绑定的是global
    // 将函数设置为对象的属性
    obj.fn = this;//this指向handle这个函数
    let res;
    if(arg){//判断是否传入数组参数
        res =obj.fn(...arg);
    }
    else{
        res =obj.fn();
    }
    // 执行这个函数,并拿到返回值
    // 删除这个函数属性
    delete obj.fn;
    // 返回值
    return res;
};
handle.myApply(obj,[4,2,3]);// { name: 'chun', fn: [Function: handle] } 4 2 3
handle.myApply(obj) //{ name: 'chun', fn: [Function: handle] }
Function.prototype.myApply = function (context, args) {
    if(!Array.isArray(args)) {
       new Error('参数必须是数组')
    }
    context = context || window
    // 创建唯一的属性防止污染
    const key = Symbol()
    // this 就是绑定的那个函数
    context[key] = this
    const result = context[key](...args)
    delete context[key]
    return result
}

手撕bind

步骤:

1.拷贝源函数:

  • 通过变量thisfn储存源函数

2.定义拷贝的函数fToBind

  • this指向判断:通过instanceof判断this是否是fToBind函数的实例 (即是否通过new调用),来决定绑定的realobj (new会覆盖传入的objThis)
  • 返回 源函数thisfn.call(realobj,...fistparam,...secondparam)的调用,返回源函数的执行结果

3.

  • 使用Object.create复制源函数的prototype给fToBind (实现深拷贝,存储不同的内存)
  • 最后返回函数fToBind

bind返回的函数有两种调用方式,通过new调用或者直接调用

细读以下代码(可延申至原型链、赋值与深浅拷贝的区别、new的实现、this隐式绑定)

function handle(...para) {
    name:'handlename'
    this.name='newfuncname' 
    console.log( this.name,this.objname,...para) 
}//等同于 var handle = new Function('...para', `console.log...`)

obj=new Object();
obj.objname="chun";


//handle实例的__proto__指向构造函数Function的prototype,在这上面添加myBall
Function.prototype.myBind = function (objThis, ...params) {
    const thisFn = this; // 存储源函数以及上方的params(函数参数)
    // 对返回的函数 secondParams 二次传参
    let fToBind = function (...secondParams) {

// 判断this是否是fToBind的实例,也就是判断返回的fToBind函数是否通过new调用,或是直接调用
        const isNew = this instanceof fToBind 
// new调用就绑定到this上,否则就绑定到传入的objThis上
        const realobj = isNew ? this : Object(objThis) 
// 用call调用源函数绑定this的指向并传递参数,返回执行结果
        return thisFn.call(realobj, ...params, ...secondParams); 
    };
    if (thisFn.prototype) {
        // 复制源函数的prototype给fToBind 一些情况下函数没有prototype,比如箭头函数
        fToBind.prototype = Object.create(thisFn.prototype);
    }
    return fToBind; // 返回拷贝的函数
};


let Handle = handle.myBind(obj,1,2,3);
//bind返回函数,后面根据new或直接调用判断this的指向

let c = new Handle('a','b');
//用new调用bind返回的函数,丢失obj,this指向实例对象 输出: newfuncname undefined 1 2 3 a b

let cc= new handle('c','d')
//直接new调用handle函数   输出: newfuncname undefined c d

Handle();//直接调用bind返回的函数,this指向obj,  输出:newfuncname chun 1 2 3

手写尝试:

Function.prototype.myBind = function (objThis, ...params) {
    let thisfn=this;//保存源函数
    let fToBind=function (...sencondparams) {//定义ftb
        //判断this指向,此处this是调用fToBind的对象,如果是new则指向this,否则为传入的objThis
        let realobj = this instanceof fToBind ? this : Object(objThis);
      //通过call调用并返回
        return thisfn.call(realobj, ...params,...sencondparams); 
    }
    if(this.prototype){//深拷贝原型对象
        fToBind.prototype=Object.create(this.prototype);
    }
    return fToBind;
};

阅读剩余
THE END