“Programmers are in a race with the Universe to create bigger and better idiot-proof programs, while the Universe is trying to create bigger and better idiots. So far the Universe is winning.” —Rich Cook
官方描述
首先我们先来看看MDN上对于bind函数的定义,主要有三个特点:
- bind函数会创建一个新函数(称为绑定函数),新函数与原函数具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)
- 当生成的新函数被调用执行时,其this值始终指向bind函数的第一个参数且无法改变
- bin函数可以接受预设的参数,该参数最终提供给原函数
- 新函数也能使用new操作符创建对象,这种行为就像把原函数当成构造器。提供的
this
值被忽略,同时调用时的参数被提供给模拟函数
用例说明
众所周知,JS中函数(ES6箭头函数除外)的this指向是在函数执行时动态绑定的,函数定义和实际运行时的所处的环境不一样,往往导致未知的bug。 尤其是在React组件中,经常会由于this指向问题导致无法调用到函数,所以通常我们会在constructor函数中使用bind函数来绑定this(当然一般我们都会使用箭头函数来避免此类情况)。
1 | var name = "paopaolee" |
动手实现
第一版
目标:实现描述中的特征1、2、3
分析:返回的结果是一个函数;新函数调用时this指向第一个参数, 可以通过call/apply函数解决,call/apply函数的区别在于参数传递,前者需逐一列出,后者可以传递数组;bind函数可接受预设参数最终提供原函数,由于参数不确定,所以选用apply实现。
实现:
1 | Function.prototype.paopaoleeBind = function (Othat) { |
第二版
目标:第一版中初步实现了前三个特征,当然还有诸多问题,例如如果新函数执行时需要有返回值、第一版中实现了绑定时传参,那新函数调用时传参数呢?以及bind函数调用不正确等等。
优化:
1 | Function.prototype.paopaoleeBind = function (Othat) { |
第三版
目标: 解决bind函数的第三个特征:
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的
this
值被忽略,同时调用时的参数被提供给模拟函数。
也就是说调用bind返回的新函数作为构造函数执行时,bind时指定的对象(参数Othat)会失效,但是传入的参数依然有效。
举个🌰:
1 | function female(name, age){ |
尽管我们执行了 female.bind(obj, 'paopaoliu')
,但最终 this instanceof female
为 true
。
分析: 要判断新函数调用时是否为new构造函数调用,可以通过this是否为新函数的实例,所以我们把返回的匿名函数换成具名函数,以便判断。
实现
1 | Function.prototype.paopaoleeBind = function (Othat) { |
用上面的🌰测试下:
1 | function female(name, age){ |
显然,此时 this
指向的是 boundFunc
的实例。female
的原型链被破坏了;
第四版
目标:重塑原型链
实现:
1 | Function.prototype.paopaoleeBind = function (Othat) { |
上述代码,重塑原型链的做法是直接将boundFunc.prototype = this.prototype,相当于直接用原函数的prototype覆盖掉了boundFunc函数原来的prototype,这样如果我们更改boundFunc.prototype中的属性是也会影响原函数的prototype上的属性。
最终版
1 | Function.prototype.paopaoleeBind = function (Othat) { |