一、call 方法的模拟

在实现 call 方法之前,我们先来看一个 call 的调用示范:

  1. var me = {
  2. name: 'xiaoming'
  3. }
  4. function showName() {
  5. console.log(this.name)
  6. }
  7. showName.call(me) // xiaoming

结合 call 表现出的特性,我们首先至少能想到以下两点:
1、call 是可以被所有的函数继承的,所以 call 方法应该被定义在 Function.prototype 上
2、call 方法做了两件事:

  • 改变 this 的指向,将 this 绑到第一个入参指定的的对象上去;
  • 根据输入的参数,执行函数。

结合这两点,我们一步一步来实现 call 方法。首先,改变 this 的指向:
showName 在 call 方法调用后,表现得就像是 me 这个对象的一个方法一样。
所以我们最直接的一个联想是,如果能把 showName 直接塞进 me 对象里就好了,像这样:

  1. var me = {
  2. name: 'xiaoming',
  3. showName: function() {
  4. console.log(this.name)
  5. }
  6. }
  7. me.showName()

用户在传入 me 这个对象的时候, 想做的仅仅是让 call 把 showName 里的 this 给改掉,而不想给 me 对象新增一个 showName 方法。来写第一版代码:

  1. Function.prototype.myCall = function(context) {
  2. // step1: 把函数挂到目标对象上(这里的 this 就是我们要改造的的那个函数)
  3. context.func = this
  4. // step2: 执行函数
  5. context.func()
  6. // step3: 删除 step1 中挂到目标对象上的函数,把目标对象”完璧归赵”
  7. delete context.func
  8. }

call方法允许传入参数:

  1. Function.prototype.myCall = function(context, ...args) {
  2. // step1: 把函数挂到目标对象上(这里的 this 就是我们要改造的的那个函数)
  3. context.func = this
  4. // step2: 执行函数,利用扩展运算符将数组展开
  5. context.func(...args)
  6. // step3: 删除 step1 中挂到目标对象上的函数,把目标对象”完璧归赵”
  7. delete context.func
  8. }

this 参数可以传 null,当为 null 的时候,视为指向 window:

  1. Function.prototype.myCall = function (context,...args) {
  2. var context = context || window;
  3. context.fn = this;
  4. context.func(...args)
  5. delete context.fn
  6. }

二、apply 方法的模拟

apply 的实现跟 call 类似,只是传参不同:

  1. Function.prototype.myApply = function (context,args) {
  2. var context = context || window;
  3. context.fn = this;
  4. context.func(...args)
  5. delete context.fn
  6. }

三、bind 方法的模拟

bind函数的两个特点:

  1. 返回一个函数
  2. 可以传入参数
    1. Function.prototype.myBind = function (context,...outArgs) {
    2. return (...args) => this.call(context,...outArgs,...args)
    3. }