likes
comments
collection
share

彻底弄懂call、apply、bind, 以及把他们手写一遍

作者站长头像
站长
· 阅读数 17

前言

前端在面试的时候其实挺容易遇到个call,apply,bind之类的面试题,其实理解实现原理之后实现起来非常的简单, 当然其中也有一些需要注意的点, 避坑之后你会发现仅仅几行代码即可实现手写,跟着我的思路我们一步步去试着实现

前置概念(关于this指向)

首先我们需要知道,这三个函数都是用于改变this指向的,那么,什么是this的指向呢, 简单出几个题, 大家可以试着思考一下, 下面的代码中, 在浏览器环境分别输出什么,

function fn() {
    console.log(this)
}
var obj = {
    fn: function () {
        console.log(this)
    },
    fn1: function () {
        function b() {
            console.log(this)
        }
        b()
    }
}

fn() // ?
obj.fn() // ?
obj.fn1() // ?

OK, 答案依次是 window对象, obj对象, window对象, 简单来说,函数中的this 谁调用就指向谁,如果没人调用就指向window(严格模式下指向undefined),而对于箭头函数而言,它们是没有this的, 他们的this指向就是父作用域块的this指向

明确思路

关于 call , apply , bind 的调用方式以及作用, 点击可跳转 mdn 查看对应的详细说明。

callapplybind 都是 Function 原型上的方法,用于修改 this 的指向,他们的区别在于: call, apply是调用时指定this, 会在调用时执行该函数, 而bind是创建时指定(箭头函数也是创建时指定), 它并不会立即调用函数,而是会返回一个新的函数,this指向传入的对象

callapply的不同在于传参方式,call参数第一个为 thisArg, 表示调用函数要使用的this值, 后面可以跟上函数的传参,是一个args, apply 接受两个参数, 第一个也是 thisArg, 第二个是一个数组,里面是调用函数时的传参.

myCall 的函数实现

Function.prototype.myCall = function (thisArg, ...argument) {
     const type = typeof thisArg
     switch (type) {
       case 'undefined':
         thisArg = window
         break;
       case 'null':
         thisArg = window
         break;
       case 'number':
         thisArg = new Number(thisArg)
         break;
       case 'string':
         thisArg = new String(thisArg)
         break;
       case 'boolean':
         thisArg = new Boolean(thisArg)
         break
     }
     const key = Symbol('myCall key')
     thisArg[key] = this
     const result = thisArg[key](...argument)
     delete thisArg[key]
     return result
}

myCall 的实现讲解

  1. 我们需要在Function原型链上添加一个方法供我们调用,我们命名了一个myCall函数
  2. 调用方式大概是这样: xxx.myCall(thisArg, arg1, arg2...) , 所以我们用 ...argument把参数拿到
  3. 注意的点来了,首先是 thisArg 的传值, 我们对比原生 call 方法发现,如果我没传值或者传了 undefined 或者 null, 这里会把 this 指向 window,这是call 内部做的处理,其次, 如果传值为基本类型,那么call会自动给我们写一个包装器, 那么我们如果要和原生做的一样,需要处理一下 thisArg 的类型
  4. 我们利用了函数内部this指向调用者这个特性,拿到了需要改变this指向的函数, 即 this, 同时需要把this这个函数中的this指向thisArg, 我们还是利用函数内部this指向调用者这个特性,给thisArg绑定一个属性为函数this,然后去调用这个函数, 调用完成之后再删除这个属性,这时就可以实现this这个函数中的this指向thisArg, 重点在于传入的thisArg添加属性这一步, 可能会造成覆盖掉thisArg原本有的属性,所以我们使用了Symbol来实现属性key的唯一性,避免了覆盖
  5. 最后就是返回值了, 原生call函数的返回值为调用函数后的返回值,我们需要保存一下返回值,在调用结束删除掉属性之后再将值返回

以上就是一个call函数的手写了, 理解了这个,那么手写一个apply就没什么压力了, 仅仅只是参数的差别,可以自行手写实现一下,这里我就不做讲解了, 贴一个代码大家参考一下

myApply 的函数实现

    Function.prototype.myApply = function (thisArg, args) {
      // 同 call, 只是接受的参数第二个为数组,传参方式不同罢了
      // 1. 如果传值为 null|undefined , 则赋值为 window, 如果为 基本类型,则自动包装类型
      const type = typeof thisArg
      switch (type) {
        case 'undefined':
          thisArg = window
          break;
        case 'null':
          thisArg = window
          break;
        case 'number':
          thisArg = new Number(thisArg)
          break;
        case 'string':
          thisArg = new String(thisArg)
          break;
        case 'boolean':
          thisArg = new Boolean(thisArg)
          break
      }

      const key = Symbol('myApply key')
      thisArg[key] = this
      const result = thisArg[key](...args)
      delete thisArg[key]
      return result
    }

myBind 的函数实现

不同于 applycallbind 是需要返回一个函数,我们借用箭头函数创建时 this 指向外部块this这一特性简单实现一下, 我们只需要返回一个箭头函数, 函数内部调用原生的 call 方法,这里需要注意的是传参, 由于 bind 函数是可以传参的, 而bind 返回的函数也可以传参, 所以我们需要处理这两个地方的参数,注意先后顺序,先bind的参数,再是返回函数的参数, 这里借助了原生的 call 方法,这里可以自己简单扩展一下,自己试一试在不使用箭头函数以及原生call去写一下这个bind方法

Function.prototype.myBind = function (thisArg, ...args) {
  return (...argument) => this.call(thisArg, ...args, ...argument)
}

结尾

好了,以上就是本文的全部内容了,最后说一句,其实很多知识理解原理之后就会变得很简单,只需要明确思路, 然后动手去实行即可。