标签(空格分隔): 博客 JS
缘由
三面和面试官激情肉搏后发现自己的JS基础实在太差了。接下来的日子决定每天一篇博客,已加强自己对JS的认知,顺便拾起自己糟糕的文笔。希望对看这篇文章的人有所启发
First
TDD 测试驱动开发
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
b
本系列采用mocha进行测试,包含详尽的测试用例
对不懂前端的人来说似乎很复杂,直接来看测试代码
const sinon = require("sinon");const sinonChai = require("sinon-chai");chai.use(sinonChai);const assert = chai.assert;describe('手写bind',()=>{Function.prototype.bind2 = newBindit('这是一个函数',()=>{assert.isFunction(newBind)}),it('this绑定成功',()=>{const animal={name:'猴子'}const demo=function () {return this}const test=demo.bind2(animal)assert(test()===animal)}),it('this,p1,p2绑定成功',()=>{const animal={name:'猴子'}const demo=function (p1,p2) {return [this,p1,p2]}const test=demo.bind2(animal,'test1','test2')assert(test()[0]===animal)assert(test()[1]==='test1')assert(test()[2]==='test2')}),})
接下来开始写代码
第一版
首先newBind肯定要返回一个函数,要将给定的this与参数绑定到新函数上
于是就有了第一版
const newBind = function (asThis, ...arg) {let fn = thisreturn function () {return fn.call(asThis,...arg)}}
利用了es6的…操作符直接获取到参数 并且利用call将asThis绑定到fn
第二版
接着继续进行改造,这时有一个bug,第一版的是不支持new的!
写个测试代码来看看
it('支持new', () => {Function.prototype.bind2 = newBind;const fn = function (p1, p2) {this.p1 = p1;this.p2 = p2;};const fn2 = fn.bind(undefined, "x", "y");const object = new fn2();assert(object.p1 === "x", "x");assert(object.p2 === "y", "y");})
我们先换成bind来试试,fn上bind了undifine即绑定默认this。将x,y绑定到默认this上面,
接着new fn2 可以看到object的p1,p2都与x,y一一对应。但是我们换成我们写的bind2
测试,就发生以下错误

我们把object打出来,却发现是空对象!!!
为什么是空对象呢? 这就要提到new一个对象的时候到底发生了,详情请见不会吧不会吧之二—-你怎么还不理解new操作?
简而言之,当我们调用bind2的时候返回一个函数。当我们对这个函数进行new操作的时候,一般会发生以:
- 首先产生一个临时空对象,将空对象的 proto 绑定到要 new 的 对象的原型上,然后返回这个空对象。
问题就出在我们在定义这个函数的时候已经指定了返回值!!!
return function result(){return fn.call(asThis,...arg)}
我们在这里调用了的另外一个This!! 问题是我们tm根本没有给this,我们调用的是undifined!!这就导致了p1,p2绑定到了window对象!
所以我们接下来就要判断到底操作者是用了new还是直接调用
那如何才能判断呢? 来看上文,new一个对象的时候发生了什么,我们把一个临时对象的 proto绑定到了要new 的对象的原型上。即 如果发生了 this.proto ===result.prototype就表示用了new,我们就需要在call的时候直接调用this而非调用asThis
看代码
return function result() {return fn.call(this.__proto__ === result.prototype ? this : asThis, ...arg)
接下来运行测试用例,很明显通过
还有Bug
如果我们在fn的原型上加了东西呢?
it('支持原型链', () => {Function.prototype.bind2 = newBindconst fn = function (p1, p2) {this.p1 = p1;this.p2 = p2;};fn.prototype.sayHi=function () {}const fn2 = fn.bind2(undefined, "x", "y");const object = new fn2();assert(object.p1 === "x", "x");assert(object.p2 === "y", "y");assert.isFunction(object.sayHi)})
重点看最后一行,new一个对象的时候会继承该对象的原型,即object的原型链上是有sayHi的。那么它肯定是一个函数,运行一下发现,很明显报错。
继续改代码
const newBind = function (asThis, ...arg) {let fn = thisfunction resultFn() {return fn.call(this.__proto__ === resultFn.prototype ? this : asThis, ...arg)}resultFn.prototype = this.prototypereturn resultFn
我们其实只需要把resultFn的prototype绑定到最初this的prototype上就行了。再测试一下,顺利通过。
继续
还是有Bug!!!
我们这里的bind使用了es6的语法,所以要poyfill
const newBind2 = function (asThis) {var args = slice.call(arguments, 1)var fn = thisfunction resultFn() {var args2 = slice.call(arguments, 1)return fn.call(resultFn.prototype.isPrototypeOf(this) ? this : asThis, args.concat(args2))}resultFn.prototype = fn.prototypereturn resultFn}
至于为什么就不解释了,自己看
