this有什么问题?
JavaScript有三大坑:闭包,this和异步。在十五分钟搞定JavaScript闭包中我们简单看了看闭包,今天就来说一说这个this。那么this有什么问题呢?JavaScript中到处都是惊喜,先来看一个:
1 | const obj = { |
执行这段代码的结果是啥?这个没有问题,运行结果就是输出两行内容:excuting function foo...
和obj's message
。如果想让它执行结束过一秒再执行一次的话,大家可能会认为代码应该是这个样子的:
1 | const obj = { |
猜一猜结果?
然而在浏览器和nodejs中执行结果都是一样的:excuting function foo...
obj's message
一秒后:excuting function foo...
undefined
立即执行的没有问题,过一秒再执行的就成了undefined
了?
如果我们在代码中再加一行,然后在浏览器中运行的话就更奇怪了:
1 | var msg = 'Global Message!'; |
结果是:excuting function foo...
obj's message
一秒后:excuting function foo...
Global Message!
不知道你是什么反应,我的表情如下:
我们就来看看这个this为什么这么古怪,它究竟是什么。
this是什么?
this代表的是执行函数时自动生成的一个内部对象,只存在于函数体中。函数在调用的时候,这个函数就会有一个执行环境,这个执行环境描述了包括函数在哪里被调用,调用的方法,变量对象(作用域链)以及this的值等等信息。也就是说,这个this的值是在函数调用时才有的,也只有在函数调用时才存在,跟声明函数没有什么关系。
我们通过不同的函数调用情况来看this的值。
普通的函数调用
1 | function sendMessage() { |
这个结果在浏览器中就是Window
对象,nodejs中的是global
对象,在非严格模式下是这样的,严格模式下返回的都是undefined
。
为了简单起见,我们只分析非严格模式下this的行为举止。还有我实在不喜欢var,所以的话用var声明的情况也就不考虑了,这个var是一个设计失误,我们没有必要去花很多心思去理解一个错误的东西。
通对象的方法去调用函数
1 | function sendMessage() { |
这段代码的返回值是obj's message
,因为这个函数被调用时的执行环境就是obj
对象,所以函数调用的this会绑定到这个对象上。
假如我们想给obj
的sendMessage
方法再起一个名字叫anotherFuncName
,然后通过anotherFuncName
来调用这个方法话,可能会这样写代码:
1 | function sendMessage() { |
这样行不行呢?不行,返回的结果是undefined
。为什么呢?原因在于函数的名字它真的就只是个名字,就像一个盒子上面贴的标签一样。复制函数就像又给函数贴了一个标签一样,实际上anotherFuncName
引用的依然是sendMessage
函数本身。在调用的时候就相当于普通的函数调用,并没有额外的信息,所以this绑定的Window
对象,而Window
对象的属性不能这么访问(ES2015之前的非严格模式下在全局作用域下使用var申明变量会自动添加到全局对象上,作为其属性,这个可以说是一种错误或者巨大的缺陷,这里就不做错误的示范了)。
这里的例子想要说明什么?把函数赋值给或当做参数传递的时候也会意外的发生同样的事情,只是会给函数再加一个名称,并不会真正的把函数执行的相关信息也一同复制过来。所以在调用的时候就如同普通的函数调用一样,而函数中的如果有this的话,自然就绑定的是undefined
。这也就是我们文章开头看到的延时一秒执行出问题的原因。问题怎么解决呢?
call、apply和bind
call和apply的作用就是将函数在调用时的this绑定传入的第一个参数上。所以上面的问题可以这样来解决:
1 | function sendMessage() { |
这样一来,通过anotherFuncName来调用函数,会在执行的时候将sendMessage函数的this绑定到obj对象上了。
bind是干什么的呢?bind可以将函数的this永久地绑定到传入的第一个参数上。上面的代码可以改写成:
1 | function sendMessage() { |
bind()接受的参数就是想要将函数的this绑定的对象,返回原函数。
new调用
对任何函数都可以使用new操作符来new一下,这个过程发生了什么事?
- 新建一个对象
- 将新对象的[[prototype]]属性连接到new后面的函数的原型对象上
- new后面的函数调用时的this绑定到新对象上
- 没有返回其他对象的话,会返回新建的对象
1 | function say(msg) { |
在执行new时,会将this绑定到新建的obj上,所以obj会获得一个msg属性。