javascript中手写call,apply,bind函数


call/apply/bind 都是JS的中改变函数里this的方法,面试中经常会考到手写,尤其是 bind函数

call

步骤:

  1. 首先明确call函数是定义在Function.prototype上的,为所有Function类型的对象所共享。现自定义Function.prototype.myCall
    传入的参数:
    (1) context表示谁来调用这个原函数,
    (2) ...args用三点运算符直接将后面传入的参数解析成数组;
  2. 确定由谁来调用函数,命名为new_this;若没有传入要绑定的对象, 默认绑定window对象;
  3. 把方法作为对象的属性绑定给new_this,但要注意,也许原有属性就有func这个属性,为了避免冲突,这里用了symbol
  4. 执行当前函数,并获取返回值;
  5. 删除我们绑定的的Symbol(func)属性,以免污染new_this的属性;
  6. 返回第3步得到的返回值;
Function.prototype.myCall = function (context, ...args) {
    // if(typeof this !== 'function'){ //不需要判断类型,因为myCall定义在Function.prototype上
    //     throw new TypeError(`${this} is not a function!`)
    // }
    // 1. 确定要绑定的对象,即最终谁来调用函数,命名为new_this;若没有传入要绑定的对象, 默认绑定window对象
    const new_this = context || window
    // 2. 把方法作为对象的属性绑定给new_this,但要注意,也许原有属性就有func,为了避免冲突,这里用symbol
    const func = Symbol('func')
    //由于这里func是Symbol变量不再是字符串,所以不能再用new_this.func而是要用中括号获取属性
    new_this[func] = this
    // 3. 执行当前函数,并获取返回值
    const res = new_this[func](...args)
    // 4. 删除我们绑定的的Symbol(func)属性,以免污染new_this的属性
    delete new_this[func]
	// 5. 返回第3步得到的返回值
    return res
}

测试:

function func1(a, b, c) {
    console.log(this)
    return a + b + c
}
const res = func1.myCall({ x: 10 },1,2,3)
console.log(res);

效果如下:
注意,在绑定的函数func1里执行console.log(this)语句,发现打印出来的内容带上了我们临时绑定的Symbol(func)方法,但是用JS原生的call函数就没有这个问题,这是因为添加的Symbol(func)方法是可枚举的属性

在这里插入图片描述
通过defineProperty可以避免这个问题

  Object.defineProperty(new_this,func,{value:this,enumerable:false}) // 改用defineProperty,把enumerable设为false更干净
  // new_this[func] = this

apply

步骤:
同call,不同的是apply只接收两个参数:要调用的函数和参数数组,因此函数形参略有不同,...args改成args,并且加一个类型判断,判断args是否是Array实例。

  1. 首先明确apply函数是定义在Function.prototype上的,为所有Function类型的对象所共享。现自定义Function.prototype.myApply
    传入的参数:
    (1) context表示谁来调用这个原函数,
    (2) args表示参数数组;
  2. 判断args的类型,如果不是Array的实例,抛出一个TypeError
  3. 确定由谁来调用函数,命名为new_this;若没有传入要绑定的对象, 默认绑定window对象;
  4. 把方法作为对象的属性绑定给new_this,但要注意,也许原有属性就有func这个属性,为了避免冲突,这里用了symbol
  5. 执行当前函数,并获取返回值;
  6. 删除我们绑定的的Symbol(func)属性,以免污染new_this的属性;
  7. 返回第3步得到的返回值;
Function.prototype.myApply = function (context, args) {
	// 1. 判断args的类型,如果不是Array的实例,抛出一个TypeError;
    if(!(args instanceof Array)){
        throw new TypeError(`args is not an array!`)
    }
    // 2. 确定要绑定的对象,即最终谁来调用函数,命名为new_this;若没有传入要绑定的对象, 默认绑定window对象
    const new_this = context || window
    // 3. 把方法作为对象的属性绑定给new_this,但要注意,也许原有属性就有func,为了避免冲突,这里用symbol
    const func = Symbol('func')
    //由于这里func是Symbol变量不再是字符串,所以不能再用new_this.func而是要用中括号获取属性
    new_this[func] = this
    // 4. 执行当前函数,并获取返回值
    const res = new_this[func](...args)
    // 5. 删除我们绑定的的Symbol(func)属性,以免污染new_this的属性
    delete new_this[func]
	// 6. 返回第3步得到的返回值
    return res
}

测试:

function func1(a, b, c) {
    console.log(this)
    return a + b + c
}
const res = func1.myApply({ x: 10 },[1,2,3])
console.log(res);

结果:
在这里插入图片描述
和上面是也一样的问题。

bind(使用apply实现)

步骤:

  1. 首先明确bind函数是定义在Function.prototype上的,为所有Function类型的对象所共享。现自定义Function.prototype.myBind
    传入的参数:
    (1) context表示谁来调用这个原函数,
    (2) ...args用三点运算符直接将后面传入的参数解析成数组;
  2. 确定要绑定的对象,即最终谁来调用函数,命名为new_this;若没有传入要绑定的对象, 默认绑定window对象;
  3. (非必要)把原函数(即this)用一个fn变量保存一下,这样更能看出它表示一个函数 ;
  4. 返回一个新函数,新函数内部通过apply方法将原函数的this指向new_this
Function.prototype.my_bind = function(context,...args){
   	// 1. 确定要绑定的对象,即最终谁来调用函数,命名为new_this;若没有传入要绑定的对象, 默认绑定window对象
    const new_this = context || window
    // 2. 把原函数(即this)用一个fn变量保存一下,这样更能看出它表示一个函数 
    const fn = this
    // 3. 返回一个新函数
    return function(){
    	//新函数内部通过apply方法将原函数的this指向new_this
        return fn.apply(new_this,args)
    }
}

测试

function func1(a,b,c){
    console.log(this)
    return a+b+c
}
// 用一个func2来接收func1.my_bind()传出来的新函数
const func2 = func1.my_bind({x:10},1,2,3)
// 输出func2()调用的返回值
console.log(func2());

结果:
在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐