题记:JavaSrcipt中有很多令人困惑的地方,但正是这些困惑又让JavaScript显得那么美好又与众不同。比方说是闭包,原型链,继承等,而这些又与this息息相关,今天,我们就来令人费解的this进制,别再让this再来困扰你的美好2020年。
1:this是什么?
this是JavaScript中定义的一个关键字,是指向调用该this的对象的。this本身也是一个对象,可以在函数执行时保存该函数里相关的值;其存在的目的是利用this指向的不同可在不同的对象环境中执行这个函数,达到复用的效果。换句话说,this的指向并不是在函数定义时确定的,而是在调用的时候确定的,即函数的调用方式决定了this的指向。
2:this是什么时候定义的?
理解this指向的前提是首先要理解this是如何定义的
首先我们来看一个简单的例子:
该函数正常执行时,在非严格模式下其AO为:
this是在函数执行时浏览器自动为函数定义的一个对象,在默认情况下指代的是window,此时this将包括函数中的全部的全局变量。即在其正常执行时,可以默认的认为this就是window,不需要返回就会起作用。(在正常执行时c也认为是this的一部分)。故可认为其AO就是:
在严格模式下this指向的是null。
而当实例化时 :
此时AO中的this对象会与window对象区分,并且将this对象隐性的返回出去给外界的test。即此时会产生的行为是:
第一步,产生一个空指针this
第二步:将构造函数中的this.color 等放在this对象中
第三步:当没有返回引用值时默认返回this,被test接收并保存到GO。
故new的作用就是将这个构造函数隐性的构造一个this对象并返回this给外界,从而产生一个对象。此时其AO为
此时C没有在this中的原因是此时this已经不指向window了。
即:当一个函数在对象中执行时,就会指向该对象,如果没有的话就会指向window。如下图所示,this指向的是p1而不是window。
3:this进制的指向
3.1:当实例化对象时
此时this如上述所诉,将指向被实例化出来的对象。此时因为是返回了一个对象,故可以认为实例化对象时形成了一个闭包。故实例化具有对象的一切优势,但是此时特别要注意的是如果构造函数内部返回了一个引用值(函数,数组,对象)的话那么就会导致返回的对象覆盖掉this,使得其无法起作用。而如果返回的是原始值的时候不会覆盖掉this。如下所示
3.2:当函数默认情况下执行时
此时是正常的执行函数,如上述所示,此时this将指向window。故此时this里面的数都是全局变量,可以在任何位置被调用。
3.3:当被call,apply和bind调用时
这三者都是改变函数this指向的,即可以改变上诉两个默认情况下的this指向,而使得其可以指向任何想指向的对象。如下图所示
1:call,apply和bind的定义
如上所述,三者都是可以在本次执行时改变this的指向,同时不影响构造函数中this的指向,构造函数还可以继续正常实例化对象。但是这三者也都略有不用:
call:立即执行,传参的话是需要一个一个传;
apply:立即执行,传参的话是以一个数组的形式统一全部传进去;
bind:不立即执行,而是将构造函数直接复制成一个新的函数,并且改变新的函数的this的指向。即上图中的p3。
2:call,apply和bind的用途
在用面向对象的方式写插件时,很容易就需要考虑this的指向并去改变this指向而使得达到我们的想要的结果;如下图
故此时需要解决的问题及要求有:
1:改变this的指向;
2:在绑定点击事件时不能执行函数
3:因为为了防止内存泄漏,后面需要解除事件处理函数,故此时需要用外部函数而不是匿名函数(要不然到了解除绑定事件是匿名函数又重新定义,不存在同一内存地址中,导致和增加事件监听时的函数不是同一个)。
为了解决上面的要求:就需要使用到bind了,这也是bind的优势
3:call,apply和bind的用途
为什么事件监听函数的this是指向绑定该事件函数的元素的呢?为什么setTimeout函数的this是直接指向window的呢?为什么forEach filter reduce等函数this是指向window而不是调用该方法的元素呢?
在前面所说,函数如果没有被实例化成对象或者在对象中就默认this就是指向window的,但是为什么这些事件监听函数等就会和这个相悖呢?这个就需要去考虑其内部函数的执行了,即里面肯定用到了call,apply或者bind中的方法去改变了this指向,下面就具体用例子来描述:
因为这个function不是在datas对象中执行的,forEach只是调用了datas的数据,然后循环执行datas长度的次数,function只是传入的一个函数,相当于传入的一个变量,循环执行了这个函数,故这个函数还是在window下正常执行,此时this仍然是指向window。如果还是不明白,那重写一次该函数功能就能明白了;
重写的函数如下:
此时说明在这个函数内部是有是有apply的,故会改变this的指向,这也就可以解释上面的问题了,即看函数到底是在哪里执行的,事件绑定函数是需要在该事件触发时执行,故这个事件函数也是在该函数内部中执行的,而setTimeout和setInterval等函数是window上的方法,是在延时到底后执行,是在这个函数内部去执行,此时内部的函数肯定调用了apply等使得this发生改变
3.4:在setTimeout setInterval和事件处理函数中执行时
如上面所述setTimeout和setInterval等函数是window上的方法,此时的this是指向window的,而事件处理函数中this则指向调用该事件处理的元素的,两者好像没有什么关联。为什么将这两个不相关的函数放一起比较呢?。这是因为setInterval函数经常会在DOM事件处理函数中执行,如果不注意,很容易弄混乱。如下图(因举例子,没有写点击事件的兼容性):
本意是想点击button按钮时,首先其值先变成”加载中....”,然后过了500ms后其值变成“获取更多数据”;但是因为this的指向问题,此时setTimeout函数里的this是指向window,而不是指向button的,此时会导致与实际的结果与想要的结果不符,故此时需要将button的this保存后放在setTimeout中执行:
4:结束语
一句话总结this指向:当没有对象和调用时,就是指向window;而当实例化对象或本身就是在对象中时,就会指向该实例化对象,而当被调用时,就指向调用他的对象。