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

前言

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

前置概念(关于this指向)

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

javascript 复制代码
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 的函数实现

typescript 复制代码
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 的函数实现

typescript 复制代码
    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方法

javascript 复制代码
Function.prototype.myBind = function (thisArg, ...args) {
  return (...argument) => this.call(thisArg, ...args, ...argument)
}

结尾

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

相关推荐
GISer_Jing40 分钟前
Next.js数据获取演进史
java·开发语言·javascript
wangpq2 小时前
使用rerender-spa-plugin在构建时预渲染静态HTML文件优化SEO
前端·javascript·vue.js
前端开发爱好者2 小时前
弃用 uni-app!Vue3 的原生 App 开发框架来了!
前端·javascript·vue.js
聪明的笨猪猪2 小时前
Java Redis “Sentinel(哨兵)与集群”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
PairsNightRain2 小时前
React.lazy 和 suspense 如何使用?
前端·javascript·react.js
渣哥2 小时前
三级缓存揭秘:Spring 如何优雅地处理循环依赖问题
javascript·后端·面试
渣哥3 小时前
为什么几乎所有 Java 项目都离不开 IoC?Spring 控制反转的优势惊人!
javascript·后端·面试
前端赵哈哈3 小时前
Vue I18n 完整安装与使用指南
前端·vue.js·面试
深蓝电商API3 小时前
网页结构解析入门:HTML、CSS、JS 与爬虫的关系
javascript·css·html
code_Bo3 小时前
前端使用snapdom报错问题
前端·javascript·vue.js