# 手写 call 原理

# 手写 call/apply/bind

  • call/apply/bind 可以改变函数中 this 的指向
  • 第一个参数是改变 this 指向(非严格模式下,传递 null/undefined 指向也是 window)
  • call 参数是依次传递,apply 是以数组的方式传递
Function.prototype.mycall = function (context, ...rest) {
  context.fun = this;
  const r = context.fun(...rest);
  delete context.fun;
  return r;
};

function fn(a, b, c) {
  return a + b + c;
}
let result = fn.mycall({ name: "abc" }, 2, 4, 9);
console.log(result);
1
2
3
4
5
6
7
8
9
10
11
12
!(function (proto) {
  function getContext(context) {
    context = context || window;
    var type = typeof context;
    if (["number", "string", "boolean", "null"].includes(type)) {
      context = new context.constructor(context);
    }
    return context;
  }
  function call(context, ...args) {
    context = getContext(context);
    const fn = Symbol("fn"); // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
    context[fn] = this;
    let result = context[fn](...args);
    delete context.fn;
    return result;
  }
  function apply(context, args) {
    context = getContext(context);
    const fn = Symbol("fn"); // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
    context[fn] = this; // this指向调用call的对象,即我们要改变this指向的函数
    let result = context[fn](...args); // 执行函数
    delete context[fn]; // 删除我们声明的fn属性
    return result; // 返回函数执行结果
  }

  function bind(context, ...bindArgs) {
    return (...args) => this.call(context, ...bindArgs, ...args);
  }
  proto.call = call;
  proto.apply = apply;
  proto.bind = bind;
})(Function.prototype);

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

// 测试
const obj = {
  name: "写代码像蔡徐抻",
};
// 测试
const obj1 = {
  name: "打球像蔡徐抻",
};
obj.foo = foo; // 变更foo的调用者
// obj.foo()       // '写代码像蔡徐抻'
//测试
foo.call(obj1); // 输出'打球像蔡徐抻'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Last Updated: 3/6/2022, 8:19:57 PM
강남역 4번 출구
Plastic / Fallin` Dild