大概两周没更新分享了,前一周忙工作,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
来看看结果:
结果正确,成功完成第一个手撕,舒服~
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,今天的分享就到此结束啦,以后我会多分享这种源码实现的知识出来,很有趣,也能很好的提升代码设计能力与分析能力。如文章中的方法实现有处理不妥的地方,望指点提醒,多谢啦~