call,apply,bind手写会了吗?

79 阅读4分钟

call,apply,bind 是面试比较常见的一个考点,原来看一个 up 主封装自己的 jquery 的时候就用到了 call 方法来改变 this 的指向。除此以外 babel 在 class 的语法转换也是用到了 call 方法,本文将实现手写 call,apply 和 bind。

浅显介绍

在 js 里面实现改变 this 指向几乎只能依靠 call,apply 和 bind 和三种方式,所以一旦涉及到改变 this 指向的问题就想到 call,虽然是手写但还是浅谈一下这三者的区别,call 和 apply 都是传递参数后立即执行,区别在于传递参数的方式 call 以参数列表的形式依次传递,而 apply 则是以数组的形式,bind 返回一个永久改变了 this 指向的函数。函数自身的并不影响。

const obj = { name: "张三" };
function foo(name) {
  this.name = name;
  console.log(this);
}
foo.call(obj, "李四"); //打印张三
foo.apply(obj, ["李四"]); //同上
const f = foo.bind(obj, "李四");
f(); //同上
言归正传
不管是call还是apply,如果想实现this指向某个对象,只需要把方法挂载到该对象身上执行一遍,那方法的this不就顺理成章地改变指向了?(全文最关键) 因为对象身上的方法通过对象调用,this永远是指向该对象的,如果把这句话想清楚了,那么接下来的代码都是在围绕者怎么把方法挂载到对象上去执行,其它的不过是点缀。我想针对于代码稍微看看注释就理解了。
实现 call

(均为 node 环境执行,浏览器环境全局对象为 window)

Function.prototype.myCall = function (obj, ...args) {
  //对obj做一个包装(装箱),都装箱为对象类型
  obj = obj == undefined || obj == null ? window : obj;
  const foo = this;
  //   利用属性描述符将方法挂载到对象身上
  Object.defineProperty(obj, "foo", {
    //也可以直接obj.foo = foo;但是删除在执行之后,在node环境会打印出foo,
    value: this,
    writable: false,
    enumerable: false,
    configurable: true,
  });
  obj.foo(); //方法挂载到目标对象上执行一遍
  delete obj.foo; //用完之后删除即可
};

function test01() {
  console.log(this);
}

const obj = {
  name: "zxy",
  age: 18,
};

test01.myCall(obj);
/* 打印(obj): {name:'zxy',age:'18'} , */
实现 apply
Function.prototype.myapply = function (obj, args = []) {
  obj = obj == undefined || obj == null ? globalThis : Object(obj);
  let foo = this;
  //   利用属性描述符将方法挂载到对象身上
  Object.defineProperty(obj, "foo", {
    //也可以直接obj.foo = foo;但是删除在执行之后,在node环境会打印出foo,
    value: this,
    writable: false,
    enumerable: false,
    configurable: true,
  });
  obj.foo(...args);
  delete obj.foo; //用完之后删除
};

function foo(name, age) {
  this.name = name;
  this.age = age;
  console.log(this); //打印{ name: 'zs', age: 20 }
}

let obj = {
  name: "zxy",
  age: 18,
};

foo.myapply(obj, ["zs", 20]);
实现 bind
Function.prototype.mybind = function (obj, ...args) {
  obj = obj == undefined || obj == null ? globalThis : Object(obj);
  //   利用属性描述符将方法挂载到对象身上
  Object.defineProperty(obj, "foo", {
    //也可以直接obj.foo = foo;但是删除在执行之后,在node环境会打印出foo,
    value: this,
    writable: false,
    enumerable: false,
    configurable: true,
  });
  return function () {
    obj.foo(...args);
  }; //返回一个改变了this指向的新函数
};

const obj = {
  name: "zxy",
};

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

const foo2 = foo.mybind(obj, "张三");
foo2(); //打印{ name: '张三' }
应用场景

call 等方式主要是改变 this 指向,这样说属实晦涩难懂,或者说有点神经病其它语言都没这种需求, 换句话说可以充分利用起来函数的 this,可以让两个毫不相干的函数和对象产生关系,比如 (只是方便理解,不是说 vue 是这么实现)

const d = {
  data: {
    name: "zxy",
  },
  method: {
    getname() {
      console.log(this.name);
    },
  },
};
d.method.getname(); //this指向method,打印undefined
d.method.getname.apply(d.data); //打印'zxy'

虽然不太恰当,但是成功把 data 和 getname 之间绑定到一起,这样访问起来只需要在 method 中书写 this 就可以直接访问到 data 的属性,再加工屏蔽一下调用细节,method 用起来就很舒服。 除此以外还有一些技巧性地用法,

  1. 比如准确的判断类型
Object.prototype.toString.call(obj);
  1. 查找一段数的最大或最小值(利用了 apply 的传参特性)s
Math.max.apply(null, [1, 2, 3, 4]);

除此以外自己模拟实现 new 和 class 继承都可以使用 call 来建立函数和对象间的关系。文章还正在写。

总结 所有文章都是出自于我个人在 js 使用过程中的一些实战总结,如果有理解不对的地方欢迎大家指正。我是小鱼干,一个妄想做好每一件事的大学生。