JS重写内置 call 方法

看一下.call都做了些什么

let obj = {
  age: 18
}
function fn(...params) {
  console.log(this.age, ...params);// 18 "参数1" "参数2" "参数n"
  return "返回值"
}
console.log(fn.call(obj, "参数1", "参数2", "参数n"))// "返回值"
  1. this指向了.call()的第一个参数。2. 从第二个参数到最后一个参数,会对 fn 进行传参。3. 执行函数 fn

模拟 call 方法,写一个 myCall 方法

let obj = {
  age: 18
}
function fn(...params) {
  console.log(this.age, ...params)
}
Function.prototype.myCall = function myCall(context, ...params) {
  // 此时的 myCall 还仅仅是个函数,不能做任何事
}
// 将来要和原来的 call 方法一样使用。
fn.myCall(obj, "参数1", "参数2", "参数n")

第一步改变this指向

如果将 fn 变成 obj 的一个属性,并调用obj.fn(),此时 fn 中的 this 自然指向 obj。

let obj = {
  age: 18
}
function fn(...params) {
  console.log(this.age, ...params)
}
obj.fn = fn
obj.fn("参数1", "参数2", "参数n") // 18 "参数1" "参数2" "参数n"

所以,myCall 中可以这样写:

let obj = {
  age: 18
}
function fn(...params) {
  console.log(this.age, ...params)
}
Function.prototype.myCall = function myCall(context, ...params) {
  let fn = Symbol();
  context[fn] = this; // 这里的 this 是.前面的主,即 fn,我们将 fn 当做一个属性赋值给 context
  context[fn](); // 执行 context.fn(),之后 fn 中的 this,自然指向.前面的主,即 context
}
fn.myCall(obj, "参数1", "参数2", "参数n") // 18

可以看到,这一步,我们实现了函数 fn 中的 this 指向变为 obj 的功能,并执行了 fn 函数

第二步传递参数给 fn

这一步就简单了,我们可以用 arguments 从下标1开始截取需要传递的参数,也可以和上面的例子一样,用...params来获取参数,进行传递

let obj = {
  age: 18
}
function fn(...params) {
  console.log(this.age, ...params)
}
Function.prototype.myCall = function myCall(context, ...params) {
  let fn = Symbol();
  context[fn] = this; 
  context[fn](...params); // 传参
}
fn.myCall(obj, "参数1", "参数2", "参数n") // 18 "参数1" "参数2" "参数n"

看到这一步,基本的 myCall 方法已经完成80%,还有两个小点要注意:

  1. this 参数可以传 null,当为 null 的时候,视为指向 window
Function.prototype.call = function call(context, ...params) {
  // 做判断,context 应该为 this 需要指向的对象或者函数
  if (context == null) context = window;
  let type = typeof context;
  if (type !== "object" && type !== "function") {
    // 把传递的原始值类型变为对应的对象类型值
    context = Object(context);
  }
  let fn = Symbol();
  context[fn] = this;
  context[fn](...params);
  delete context[fn]; // 用完记得把新加的属性删除掉,不然会越来越。
};
  1. 函数 fn 是可以有返回值的!
Function.prototype.call = function call(context, ...params) {
  if (context == null) context = window;
  let type = typeof context;
  if (type !== "object" && type !== "function") {
    context = Object(context);
  }
  let fn = Symbol();
  context[fn] = this;
  let result = context[fn](...params); // 函数执行之后,获取到返回值。
  delete context[fn];
  return result // 将获取到的返回值,返回出去。
};

一个 模拟 call 方法的 myCall 方法完成。

原文地址:https://www.cnblogs.com/MrZhujl/p/14732214.html