手写call,apply,bind原来这么简单

227 阅读4分钟

前言

不知道大家有没有这种感觉,就是现在面试动不动就是手写这个手写哪个的,可惜没办法,现在的环境就是这么卷,其实表面上看起来比较难,但实际上其实并没有大家想象的那么复杂。首先咱们需要先了解this的执行,在绝大多数情况下呢,函数的调用方式决定了this的值,且this在不能再执行期间被赋值。首先咱们先学习一下如何来确认this的值

如何确认this

在非严格模式下呢,this总是指向一个对象,而在严格模式下可以是任意的值,咱们再日常开发下普遍用的比较多的都是非严格模式,那么如何开启严格模式呢,只需要再函数或者脚本的顶端写上use strict即可为脚本或函数开启严格模式,那么在严格模式和非严格模式下this指向有什么不同呢。可以以下例子:

//全局执行环境
<script>
 `use strict`;
 console.log(this);//Window
</script>
<script>
 console.log(this);//Window
</script>
 // 直接调用 严格模式
 function init () {
   "use strict"
   console.log(this);
 }
 init(); // undefined
 // 直接调用 非严格模式
 function fn () {
   console.log(this);
 }
 fn() // Window 
   //  通过对象调用  严格模式
 const obj = {
   name: 'onlooker',
   fn () {
     "use strict"
     console.log(this);
   }
 }
 obj.fn() //{name: 'onlooker', fn: ƒ}
 // 通过对象调用  非严格模式
 const objtwo = {
   name: 'bin',
   fn () {
     console.log(this);
   }
 }
 objtwo.fn() //{name: 'bin', fn: ƒ}

通过上面代码可以了解到,在全局执行环境中,严格模式和非严格模式是一样的都是指向全局对象,在函数内部直接调用的话严格模式下为undefined,对象方法调用this为调用者。当然也可以自己指定this。就涉及到咱们接下来要手写的,call,apply,bind三个方法

call

下面就实现一下call一样的方法Mycall,需求就是所有函数都可以调用,且传入要改变的this指向,且返回执行好的结果(call为调用时改变this的指向)

  Function.prototype.Mycall = function (obj, ...number) {
   const key = Symbol('mycall');
   obj[key] = this
   const res = obj[key](...number)
   delete obj[key]
   return res
 }
 function fn (number) {
   console.log(number);
   console.log(this);
   return (this.APrice + this.BPrice + this.CPrice) * number
 }
 const food = {
   A: '土豆',
   APrice: 3,
   B: '西红柿',
   BPrice: 5,
   C: '茄子',
   CPrice: 7
 }
 const Price = fn.Mycall(food, 2)
 console.log(Price);

首先我们在Function原型上写上一个Mycall方法,这样所有的函数就都可以进行调用了。改变this是怎么做的呢,上面如何确认this的值咱们提到了,不论是在严格模式下还是非严格模式下,对象的方式调用this为调用者,那么在Mycall内的this就为fn。然后接下来就可以往obj内添入fn,那么此时的 obj调用fn函数那么fn内部的this就指为了obj

apply

上面看了call的方法,那么应该也就想到怎么样来写apply方法了,因为他们两个除了传参上不一样以外其他地方基本上都是一样的

  Function.prototype.Myapply = function (obj, array) {
   const key = Symbol('myapply');
   obj[key] = this
   const res = obj[key](...array)
   delete obj[key]
   return res
 }
 function fn (numA, numB) {
   console.log(numA, numB);
   console.log(this);
   return (this.APrice + this.BPrice + this.CPrice) + numA + numB
 }
 const food = {
   A: '土豆',
   APrice: 3,
   B: '西红柿',
   BPrice: 5,
   C: '茄子',
   CPrice: 7
 }
 const Price = fn.Myapply(food, [2, 9])
 console.log(Price);

除了接收参数的地方发生了改变其他地方基本上都是一样的,因为apply是传入一个数组,我们只需要接收到这个数组,展开传给函数就可以了

bind

bind呢和上面的callapply稍微有点不一样,bind呢是在创建时改变this指向的且参数可以分为两次传递,下面看一下bind函数是如何实现的呢

  Function.prototype.Myapply = function (obj, ...arr) {
   return (...arrTWO) => {
     return this.call(obj, ...arr, ...arrTWO);
   }
 }
 function fn (numA, numB, numC) {
   console.log(numA, numB);
   console.log(this);
   return numA * this.APrice + numB * this.BPrice + numC * this.CPrice
 }
 const food = {
   A: '土豆',
   APrice: 3,
   B: '西红柿',
   BPrice: 5,
   C: '茄子',
   CPrice: 7
 }
 const Thfn = fn.bind(food, 1, 2)
 console.log(Thfn(3));

bind呢这里用到了call来做辅助,首先咱们用的是返回一个箭头函数。箭头函数找不到this就会去他的上层找,就找到了这个函数,此时用call来改变this,箭头函数接收第二波参数,再返回执行后的值。

结尾

call,apply,bind实现起来呢是比较简单的,主要就是需要理解当前this的值是什么。主要知道当前情况下的this为什么,然后把他放入到想要的作用域下就可以改变他的this的值