「前端厚说」手写实现 call 与apply

984 阅读2分钟
  • 以下的所有原文收录在仓库: github.com/yayxs/top-f…
  • 首次发布时间:2020年02月26日
  • 更新时间:2020年11月25号
  • 建议阅读地址: ……written/call.html
  • 建议阅读时长:15min
  • 建议阅读对象:初中级前端工程师、Js爱好者

前言

函数可以被传递、用作对象等让我们先看一段代码,函数虽然将调用传递给原始的方法,但是上下文this 却会存在不见的地方,这也就引出我们的call()方法,call 是什么黑魔法,不过是一个内置函数方法,使用的方式是 add.call(),并且add函数自动执行输出结果 3

// 参数context 指定的this值
// arg1 参数一
// arg2 参数二
func.call(context, arg1, arg2, ...)
function add(a, b) {
  console.log("add函数中this", this);
  console.log(a + b);
}
add(1, 2); // this指向window
add.call({ name: "yayxs" }, 1, 2); // this指向传入的 {name:'yayxs'}

实现第一版 call

这时候我们把传入的对象抽离出来

let o = {
  name: "yayxs",
};

function sayName() {
  console.log(this.name);
}

sayName.call(o); // yayxs

Function.prototype.myCall = function(ctx) {
    console.log(this) // 其中this 就是sayName 这个函数
    console.log(ctx) //  {name: "yayxs"}
    ctx.tempFunc = this
    ctx.tempFunc()
    delete ctx.tempFunc
};

sayName.myCall(o,'参数一','参数二') // 理论上输出 yayxs

实现第二版 call

我们上述的myCall 传入的参数一和参数二并没有参与感,再完善一下

Function.prototype.myCall = function(ctx) {
    console.log(this) // 其中this 就是sayName 这个函数
    console.log(ctx) //  {name: "yayxs"}
    console.log('arguments',arguments)
    let tempArgs = [];// 用来存放参数
    for(let i=1,len=arguments.length;i<len;i++){
        console.log(arguments[i]) // 第一遍循环体 输出参数一 第二遍循环体 参数二
        tempArgs.push('arguments[' + i + ']');
    }
    console.log(tempArgs);
    ctx.tempFunc = this
    // ctx.tempFunc()
    let evalScript = 'ctx.tempFunc(' + tempArgs +')'
    eval(evalScript);
    delete ctx.tempFunc
};

手动实现 apply


// ---------------- 实现myApply
Function.prototype.myApply = function(ctx,arr){
  ctx.tempFunc = this
  let result
  if(!arr){
    result = ctx.tempFunc() // 直接执行
  }else{
    let args = []
    for (let i = 0, len = arr.length; i < len; i++) {
      args.push('arr[' + i + ']');
  }
    result = eval('ctx.tempFunc(' + args + ')')
  }
  delete ctx.tempFunc
  return result 
}

总结

总体来说,call apply 函数的作用都是用来改变this的指向。目前的 js 还存在回调函数这一现象,尤其在框架中一些异步回调也是十分的常见,难免this会迷失方向既有不同也有相似,倘若自己手写代码实现callapply

  1. 获取被绑定的函数
  2. 被绑定的函数追加到劫持替换的对象
  3. 被绑定的函数追加到劫持替换的对象
方法名作用是否自动执行参数列表
call改变 this 指向自动执行函数一般列表
apply改变 this 指向自动执行函数数组形式