对this的误解
学习this
之前,应该知道两句话。
首先要先消除对this
的误解,明白第一句话:this
既不指向函数自身也不指向函数的词法作用域。这句话很重要,现在看一下证明这句话的例子。
既不指向函数自身
看下面的代码:
1 | function foo(num) { |
在foo.count = 0
执行的时候,确实是向函数foo
添加了一个属性count
,但是this.count
中的this
并不是指向foo
函数本身。
也不指向函数的词法作用域
看下面代码:
1 | function foo() { |
是不是觉得应该输出2?但是this
并不指向foo
的词法作用域,实际上this
在任何情况都不指向函数的词法作用域,使用this
不可能在词法作用域查询到什么。
this指向什么
第二句重要的话:this实际上是在函数被调用时发生的绑定,它指向什么全完取决于函数被调用的位置。
它指向什么全完取决于函数被调用的位置。
首先就是要找到函数被调用的位置,找位置要分析调用栈(执行当前函数的位置),我们要找的调用位置就是当前执行函数的前一个调用的位置。说着绕看代码就很明了了:
1 | function baz(){ |
看完这个应该就会明白了,this指向的位置,就是在调用的位置。如果this
在foo
函数中,foo
函数的调用位置是bar
,那foo
函数中的this
就指向bar
。
this实际上是在函数被调用时发生的绑定
找到了指向的位置,就要找this绑定在哪个对象上。this
绑定会有四条规则,每一条的优先级是不同的。
四条规则分别是:默认绑定、隐式绑定、显式绑定、new绑定。
优先级是这样的:
- 是否在new中调用?如果是,this就绑定的是新创建的对象。
- 是否通过call、apply、bind调用(显式绑定)?如果是,this绑定的就是指定的对象。
- 是否在某个上下文对象中调用(隐式绑定)?如果是,this绑定的是那个上下文对象。
- 都不是的话,就是默认绑定。在严格模式下绑定到
undefined
,否则就是全局对象上。
下面介绍四中绑定:
默认绑定
看下面这个代码:
1 | function foo() { |
我们应该已经知道了,这个里的this
会指向全局作用域。但原因是什么?是因为foo()
是直接使用,没有在别的函数或对象内部被调用,所以就是默认绑定,默认绑定的this
就会指向全局作用域。
但是如果在严格模式中,全局对象无法使用默认绑定,this
就会绑定到undefined
:
1 | function foo() { |
隐式绑定
如果this
所在的函数被某个对象拥有或者包含,函数在运行时就会有这个对象的上下文,隐式绑定规则就会把函数中的this
(隐式)绑定到这个上下文对象上。如果是一个链式调用呢,就只会关心最后一次调用时的上下文。
看下面代码:
1 | function foo() { |
像这样的链式调用呢,this
最后在obj2
中被引用,所以this.a
其实就是obj2.a
,就是42。
隐式丢失
1 | function foo() { |
因为bar()
是一个不带任何修饰的函数调用,所以应用了默认绑定。
还有一个更常见的问题,在传入回调函数的时候:
1 | function foo() { |
参数传递其实就是一种隐式赋值,所以我们传入的函数时也会被隐式赋值,隐式赋值会在全局作用域创建一个变量,所以结果就和上个例子一样。
语言的一些内置函数的本质其实也是传递参数,所以也会隐式赋值,就是出现绑定的丢失。
比如setTimeout
函数:
1 | function foo() { |
JavaScript内部setTimeout
的实现,会传递参数:
1 | //类似实现 |
显式绑定
JavaScript提供了call()
和apply()
方法,可以直接指定this的绑定对象,称之为显式绑定。就像下面这样:
1 | function foo() { |
这样通过foo.call()
,可以把this
绑定到obj
上面。
如果在call()
中传入一个基本类型来当做this
的绑定对象,那基本类型会调用基本包装类,把它变成对象的形式。
但显示绑定还是不能解决绑定丢失的问题,显式绑定的一个变种可以解决这个问题。办法就是在显式绑定的外面再加一个包裹函数,负责接收参数并返回值,这种方法叫硬绑定。
下面是例子:
1 | function foo(something) { |
ES5提供了一个内置的硬绑定的方法:bind()
,用法如下:
1 | function foo(something) { |
new绑定
使用new来构造函数调用,会发生new绑定。会自动执行下面的操作:
- 创建一个全新的对象。
- 这个新对象会被执行[[Prototype]]连接。
- 这个新对象会绑定到函数调用的this。
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
参考代码:
1 | function foo(a) { |
this
的内容大概就这么多,关于这本书对象和原型这部分,书上讲了很多理论知识,并且知识点很多很杂,我的理解也不是很深刻,总结无非是把书上东西搬上来而已,不如换一个方式。慕课网上Bosn老师有一系列课程对对象原型有了很好的解释,不妨去看一下。链接在这里