原生JavaScript实现callapply和bind

Posted 饮尽杯中月

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原生JavaScript实现callapply和bind相关的知识,希望对你有一定的参考价值。

简洁

方法一:


如果用call进行绑定,其实相当于在egg对象里面增加一个person函数,再用egg.person()调用就好了

简单来说就是在egg这个位置开始进行调用,然后执行person函数,如果发现了this就会绑定到egg,因为egg直接执行了person,egg是真正的调用位置,因此this指向了egg

此时的this指向window,person函数进行调用,然后执行newCall函数,发现了this,因此需要进行绑定,person才是真正调用位置,所以this指向person函数

实现call

相当于给对象obj添加了person函数,不过要记得删除该方法,因为不能改写了对象

call的第一个参数是this指向,后面的参数就是函数本身的参数,但是函数本身的参数是没有this的,但是call的第一个参数是this,所以我们需要将所有的参数另外保存起来,并且不保留第一个this参数,使得参数回归正常的序号,获取函数参数使用arguments对象。我们在设置newCall的时候,内部其实执行的是不带参数的函数,也就是说没有使用任何一个参数,所以需要将该arguments分开来显示



但是此时egg改为null会出错,对象不存在时候就指向window
因为可能函数person里面有返回一个对象的问题

实现apply

第一个参数this,第二个参数为数组,所以要么有第二个参数,要么没有

实现bind

  1. bind会返回一个函数
  2. 因为函数中有返回的函数,所以在执行中容易造成this丢失,所以提前保存this,返回的函数里面就是执行和call和apply的功能了(保存当前调用的函数)
  3. 考虑后面的参数问题,我们需要切割,但是要注意arguments其实是对象,不是数组,因此不能直接使用数组的切割方法,需要利用call方法把slice切割方法赋给arguments对象,这个时候就可以切割第一个数组元素,并且赋值给arr,接下来就要将数组放入return的函数里面,但是call接收数组参数很麻烦,所以改为apply
  4. 实际bind具有柯里化特性,现在为返回函数中传入一个参数,结果显示undefind,因为没有向return的函数进行参数的传入
  5. newBind里面的参数有两个不同的arguments
  6. 将两个arguments对象的数组进行合并,所以将arguments对象转为数组

  1. 使用new,发现打印为undefind,因为这个实例和现在的原型对象没有关系
  2. 将返回函数设置为具名函数,因为有了名字的函数,才能进行关系的确认,
    如果使用new来调用函数,也就是发生了构造函数,那么实例(新对象)会绑定到函数调用的this

new的过程

方法二:

实现call

  1. 将addAge函数添加到obj对象中,设为其属性(方法)
  2. 然后在作用域是obj的情况下,执行addAge方法
  3. 最后删除obj里面的addAge函数,这样就不会影响obj对象
  4. this指向addAge函数,原因:Function是所有function函数的构造函数,然后构造器里面的this永远指向其构造的实例,而addAge函数就是Function构造的实例

  5. newCall中实现参数传入和返回值(像原来的addAge一样可以返回值)这两个特性
  6. 将args转换为字符串,这样eval()中就传入的是全字符串格式
  7. 但是此时第一个参数改为null会出错,对象不存在时候就指向window

实现apply

实现bind

原理:将addStuff函数添加到obj对象的作用域下,当执行该addStuf函数时,其中的this将指向obj,且将26传入age中,所以age就变为了私有参数的元素,在后面使用addStuffToObj的过程中,无法更改age的输入值,只能传入birth

  1. 忽略第一个参数,从第二个参数开始获得
  2. 生成新的函数binded,在binded中运行func(即是addStuff),并且是在指定的作用域obj下运行,然后传入参数,该参数包括age和birth,所以要连接在一起

  3. 使用new时,bind的第一参数不再是传入obj作用域了,因为构造函数的作用域会在new作用下,被赋值到新生成的实例上,所以可以不传值,或者(null,参数)

  4. 为实现原生的new效果,所以在binded中加入判断语句,如果在调用binded中,使用new来调用的,此处的this是指向,new生成的实例对象stuff,而运行传入的func(即是addStuff)就需要在stuff的作用域下运行,所以age和birth赋值到stuff对象的属性里面
  5. 而其他情况,像这样,bind2生成的不是构造函数,而是普通函数

  6. 如果处在原型链上,生成的新的构造函数newAddStuff应该继承旧的addStuff构造函数的原型,但是截至目前还未完成该操作 。此处的this指向addStuff函数,是的addStuff的原型赋值到了新的构造函数newAddStuff(binded.prototype)的原型,所以newAddStuff上的原型链继承了addStuff
  7. 更改newAddStuff的prototype时将会更改addStuff的prototype,这是不正确的,解决方案,建立一个空函数,使得空函数的prototype等于addStuff的prototype,让新生成的binded函数的原型成为空函数的实例,则在更改新生成的实例binded的prototype的过程中,只会更改新的实例
    内部功能,而不会触及到addStuff的原型链了

以上是关于原生JavaScript实现callapply和bind的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript实现callapply和bind

#yyds干货盘点# JavaScript之手撕callapply

源码来袭:callapply手写实现与应用

使用callapply和bind解决js中烦人的this,事件绑定时的this和传参问题

JavaScript中this这号人物和callapply的详解

JavaScript 函数 CallApply