js bind 传参、_手撕源码之apply()、call()、bind()

大概两周没更新分享了,前一周忙工作,51节忙着浪,终于轮到分享的时候了,心里倍儿爽~

ok,今天给大家带来JS中的apply()、call()以及bind()方法的源码实现。这三兄弟大家应该不陌生,平时工作中也会经常使用到,既然能用,想必原理方面的理解也大致了解,如果让你自己去实现这三个方法呢?在阅读本篇手撕源码之前,大家可以先自己去尝试一下如何实现,然后再来和我分享的实现方法对比一下,最好能取长补短。

正片开始啦~

1.call()

首先来撕call()方法,因为这玩意儿简单。我们先捋一捋call()方法的原理,call()方法用来扩充调用它的函数的执行作用域,简而言之就是改变函数的this指向,它接收多个参数,第一个参数为执行作用域,第二个及以后的参数是传递给函数的参数。ok,现在一步一步来实现这个方法。

先把我们的call方法定义到函数原型对象上,这里命名为_call,以便所有的函数均可使用:

// 全局添加一个_call方法

它接收一个参数obj,这个obj就是函数的执行作用域。这个obj参数是一个Object类型,如果_call()方法没有传递任何参数,则默认执行作用域为window对象,代码实现如下:

// 全局添加一个_call方法

重要的步骤来了,要让函数的执行作用域为参数obj,那么这个函数必须是obj的一个属性或方法,因此这里为_obj添加一个fn方法,并把这个函数赋给这个方法:

// 全局添加一个_call方法

接下来该获取其他函数参数了,因为这里正在实现call方法,我们只好去遍历arguments对象,把这些参数转为真正的数组,并拆分为参数传给函数并执行:

// var argArr = [...arguments].slice(1)

当然除了遍历arguments,我们可以采用ES6的数组方法,比如扩展运算符和Array.from()方法。

最后,要把obj对象中的fn方法移除,_call()方法的完整代码如下:

// 全局添加一个_call方法

做个测试:

function 

来看看结果:

e60f5f22ff2221eb5569e35688852a18.png

结果正确,成功完成第一个手撕,舒服~

2.apply()

接下来继续撕碎apply()方法,有了上面_call()的实现,那么_apply()就很好编码了。要知道apply()与call()方法的区别在于第二位参数,apply()方法第二位参数也是传递给函数的参数,但是它是一个数组类型的,下面就不需要那么多废话了,我直接贴上完整代码:

Function

每一步的注释已经敲得比较详细了,非常容易理解,我就不多说了,看看效果:

function 

结果达到预期,没问题。

3.bind()

今天最难的点来了——实现bind()方法。先简单说说bind()方法原理,bind()依然用来改变函数this指向,但是它不会像call()与apply()方法那样立即执行这个函数,而是返回一个新的函数给外部,外部用一个变量去接收这个新函数并执行。需要注意的是,bind()方法返回的那个函数不仅仅可以作为普通函数调用,还可以作为一个构造函数被调用,下面一步一步来实现bind()方法。

首先判断执行_bind()方法的是不是一个函数,如果不是则抛出错误:

Function

然后同call()与apply()一样,截取第一个参数,也就是函数执行作用域,这里建立在存在call()方法的条件下,我们可以直接使用Array.slice.call()将arguments对象转为真正的数组并截取从第二项开始的参数:

// 截取传给函数的参数

然后将执行bind()方法的这个函数保存在一个变量中,在下面改变函数执行作用域以及原型继承的时候会使用到:

var 

再创建一个新的函数变量,用来改变函数执行作用域:

var 

特别注意var newArgs = Array.prototype.slice.call(arguments)这段代码,不要跟var args = Array.prototype.slice.call(arguments,1)这段代码搞混淆了,newArgs是返回的新函数的参数,而args是_bind()方法接收并传递给调用它的函数的参数。

再来看看var _obj = this.constructor === _fn ?this: obj这段代码,什么意思呢,上面说到bind()方法返回的新函数可以普通调用也可以构造函数方式调用,当为构造函数时,this是指向实例的,因此才会做这样的处理。

args.concat(newArgs)是什么意思呢,bind()方法的参数具有一个特性,就是函数柯里化,简单来说就是保留一个参数的位置,再第二次传参的时候自动把参数存入到这个位置中,而这段代码正是用来实现函数柯里化的。

既然bind()返回的函数可以作为构造函数,那么它得继承调用它的那个函数的原型对象以及属性,这里创建一个媒介函数,用来实现寄生组合式继承:

var 

最后把这个bindFn方法作为一个新的函数返回到外部,下面直接附上完整代码:

Function

我们来测试一下:

var 

这里是普通调用新函数,稍作修改,来体现一下函数柯里化:

var 

结果z为4,总合为7,成功实现。最后来测一下构造函数调用:

var 

结果完全没问题,为啥this.name是undefined呢,永远记住,构造函数中,this指向的是实例对象,而这个实例中没有name属性,因此为undefined。

OK,今天的分享就到此结束啦,以后我会多分享这种源码实现的知识出来,很有趣,也能很好的提升代码设计能力与分析能力。如文章中的方法实现有处理不妥的地方,望指点提醒,多谢啦~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值