ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
在理解this绑定之前,先要理解调用位置。 我们可以通过浏览器的调试工具来查看调用栈。 在第一行代码钱插入一条debugger;语句,运行代码,可以看到当前位置函数的调用列表(call stack),找到栈中的第二个元素,就是真正的调用位置。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603eede32f.jpg "") 声明:下面很多内容使用的是《You Don’t Kown JavaScript》中的思想。 this的绑定对象———先找到函数的执行过程中调用位置,然后判断应用了下面四条绑定规则的哪一条。 **this的四种绑定规则:** 1、默认绑定 2、隐式绑定 3、显示绑定 4、new绑定 ### 默认绑定 首先从默认绑定开始,在无法应用其他规则时的默认规则,通常是独立函数调用。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603ef013d0.jpg "") 在调用add()时,应用了默认绑定,this指向全局对象(非严格模式下),严格模式下,this指向undefined。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603ef15741.jpg "") ### 隐式绑定 调用位置是否有上下文对象/被某个对象“拥有”或“包含”(但不是真正的拥有) ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603ef26531.jpg "") add函数声明在外部,严格来说并不属于instance,但是在调用add时,调用位置会使用instance的上下文来引用函数,隐式绑定会把函数调用中的this(即此例add函数中的this)绑定到这个上下文对象(即此例中的instance) 需要注意的是:对象属性链中只有最后一层会影响到调用位置。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603ef34f44.jpg "") 隐式绑定有一个问题,就是隐式丢失(在第一篇中提到过的绑定丢失的问题) ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603ef47374.jpg "") 绑定丢失的另一种情况发生在回调函数中。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603ef56fc6.jpg "") 我的理解是:fun(instance.add),做了一个隐式的赋值操作,var fn = instance.add,然后运行fn();这样就跟上面的例子一样了。 本来看书上说做了一个隐式赋值,自己并未理解,但是想努力地说清楚这个问题,于是自己思考,反而与书中不谋而合,本来没明白他说得隐式赋值操作是什么。 到这个地方,就不得不提起setTimeout了。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603edad1d6.jpg "") setTimeout(person1.grow, 100); //undefined 实话说,我原本是一个似理解又不是很理解的一个状态,现在算是豁然开朗了。 setTimeout(fn,delay){ fn(); } 与上面的例子其实是一样的。 除了上面的两种情况以外,还有一种情况this的行为会出乎我们意料:调用回调函数的函数可能会修改this,在一些流行的JS库中事件处理器常会把毁掉函数的this强制绑定到触发事件的DOM元素上。(关于这个问题,暂未研究,因此也不予举例说明) ### 显式绑定 显式绑定就是通过call()或者apply()函数指定this的绑定对象。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603ef7e55f.jpg "") 不过显式绑定还是未能解决绑定丢失的问题,如下面的代码,结果依旧为50。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603ef8f9ae.jpg "") 因此我们引入“硬绑定”这个概念。注意下面例子中划红色横线的部分,根据书中说硬绑定之后,无法再修改它的this值,应该是我红色写得部分,即(fn.call(instance))这种写法,无论fun.call()第一个参数传什么,都不能再改变它的this值,相反,我例子中的写法,是需要和外围配合的,即需要传什么this值,就去指定它。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603efa0006.jpg "") **硬绑定**是能够解决绑定丢失的问题的,如下: ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603efb05f7.jpg "") ES5中提供了内置的方法进行硬绑定,在第一篇this词法的博文中也提及过,即bind. Function.prototype.bind,用法如下: ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603efd2b8f.jpg "") 上面的两句代码可以用那一句红色的代码代替,需要注意的是: bind是function对象的方法,千万不能写成add().bind(instance);因为这个add()函数中返回值是undefined,而undefined显然是没有bind方法的。 如果是下面这样,那显然又是不一样的。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603efe3c34.jpg "") 这儿使用的是add().bind,原因也很简单,因为add()返回的是一个函数。 **API调用的“上下文”** ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603eff2efc.jpg "") 顺便了解一下forEach的用法。forEach是一个JavaScript扩展到ECMA-262标准;因此它可能不存在在标准的其他实现。为了使它工作,可以增加下面的代码: ~~~ if(!Array.prototype.forEach) { Array.prototype.forEach = function(fun) { varlen = this.length; if(typeoffun != "function"){ thrownewTypeError(); } varself= arguments[1]; for(vari = 0; i < len; i++) { if(i inthis) fun.call(self, this[i], i, this); } }; } ~~~ 对每个函数成员执行callback函数,并且可以指定this的值。 ### new绑定 javaScript和C++不一样,并没有类,在javaScript中,构造函数只是使用new操作符时被调用的函数,这些函数和普通的函数并没有什么不同,它不属于某个类,也不可能实例化出一个类。任何一个函数都可以使用new来调用,因此其实并不存在构造函数,而只有对于函数的“构造调用”。 使用new来调用函数,会自动执行下面的操作: 1、创建一个新对象 2、这个新对象会被执行[[原型]]连接 3、这个新对象会绑定到函数调用的this 4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f00fdb0.jpg "") instance绑定到Add的this。 优先级 了解了函数调用中this绑定的四条规则之后,需要做的就是找到函数的调用位置并判断应当应用哪条规则。当某个调用位置可以应用多条规则时,就需要了解这些绑定规则的优先级。 关于优先级的问题,不再细说,只给出结果,只说下显式绑定和new绑定优先级。 默认绑定 < 隐式绑定 < 显式绑定 < new绑定 new和call/apply无法一起使用,因此无法通过测试new add.call(obj)的方式来直接进行测试,但是可以使用硬绑定来测试他们的优先级 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f02112b.jpg "") 当硬绑定函数被new调用时,会使用新创建的this替换硬绑定的this。当然再次使用硬绑定,我们还是可以将this绑定在其它的对象上的。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f081656.jpg "") 总结一下,判断this的步骤: 1.函数是否在new中调用,即是否是new绑定,如果是new绑定,那么this绑定的是新创建的对象。 2.函数是否是显示绑定(call,apply,bind),如果是,那么this绑定的是指定的对象 3.函数是否在某个上下文对象中调用,即隐式绑定,较多的是在某个对象中调用,如果是的话,this绑定的是那个上下文对象。 4.如果以上规则都不能应用,那么就是默认绑定,在严格模式下,this被绑定到undefined,在非严格模式下,绑定到全局对象。 最后,我们来看下 ### 绑定例外 : **1、被忽略的this** 如果我们将null或者是undefined作为this的绑定对象传入call、apply或者是bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f099d06.jpg "") 实际应用的是默认绑定规则,add中的this被绑定到了全局对象(非严格模式) 在使用apply()来“展开”一个数组,并当做一个参数传入一个函数,或使用bind()对参数进行柯里化(即预先设置好一些参数),bind和apply都需要传入一个参数作为this的绑定对象,即使函数不关心this的值,仍然需要传入一个占位值,这是null或者是undefined可能就是一个不错的选择。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f0ab07b.jpg "") 上面说可能是一个不错的选择,那么也就是说这其实并不是一个比较好的做法,因为如果某个函数确实使用了this,那默认绑定规则会把this绑定到全局对象,这就可能会产生难以预料的问题。 一个更安全的做法是传入一个特殊的对象,把this绑定到这个对象则不会对程序产生副作用,即使某个函数中使用了this,也不会影响到全局对象。 首先创建一个空对象:Φ(数学中的空集符号,既能表达意思,而且不易重名) ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f0c17b7.jpg "") **2、间接引用** 可能会无意间创建一个函数的“间接引用”,在这种情况下,调用这个函数会引用默认绑定规则。间接引用最容易在赋值时发生。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f0d5f2a.jpg "") 这种情况下调用位置是add(),而不是example.add()或者是instance.add(),应用默认绑定。 想加个debugger看看,然后引发出了一个新问题,如下:期望得到解答。 ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f0e4dd2.jpg "") **3、软绑定** 硬绑定可以将this的值强制绑定到指定的对象,防止函数调用应用默认绑定规则,但是,硬绑定同样会带来一个灵活性的问题,用硬绑定之后就无法使用隐式绑定或者显式绑定去修改this的值,只能通过new或者重写硬绑定。 如果给默认绑定指定一个全局对象和undefined/null以外的值,那就可以实现和硬绑定相同的效果,同时可以保留隐式绑定或者显式绑定修改this的能力。 softBind()的原理和bind()类似,会对指定的函数进行封装,首先检查调用时的this,如果this绑定到全局对象或者是undefined,那么就把指定的默认对象的obj绑定到this,否则不会修改this. ~~~ if(!Function.prototype.softBind){ Function.prototype.softBind = function(obj){ var fn = this; var curried = [].slice.call( arguments,1); var bound = function(){ return fn.apply( (!this || this === (window || global)) ? obj : this, curried.concat.apply( curried, arguments ) ); }; bound.prototype = Object.create( fn.prototype ); return bound; }; } ~~~ ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f103f1c.jpg "") 最后简单说下ES6中的箭头函数,其实在第一篇关于this词法的博文中已经提及过了,再巩固一下吧。 前面所说的四种规则,只适用于普通的函数,对于ES6中的箭头函数是不起作用的。箭头函数不适用以上的规则,它根据外层的作用域来决定this. ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f12fb0b.jpg "") ![这里写图片描述](https://box.kancloud.cn/2016-04-07_570603f13f045.jpg "") 对比这两个,应该能明显得看出问题吧,箭头函数的绑定是无法修改的,它被绑定到instance之后,即使我们通过call再去指定this的值,它也不会搭理我们,即使适用等级最高的new也是不行的,这里,适用new操作符,最终它只会华丽丽得返回给你一个undefined。 箭头函数事实上和增加一句self = this效果基本一致。无论是self = this或者是箭头函数,都想用替代this机制。 建议: 在编写代码时,应当: 1、只采用词法作用域并完全抛弃this风格的代码; 2、完全采用this风格,在必要的时候使用bind(),尽量避免使用self=this和箭头函数(出自Kyle Simpson) 到此,this词法就暂且告一段落,后续只会在有什么顿悟的时候再补充了。 本来已经看得有点崩溃,但是为了对读到此文的读者负责,还是看完了所有的关于这部分的内容,此前已经看了两遍,希望本文对您理解JS的this词法能有一点帮助。