JS语言理解07 this的理解
JavaScript里this
非常重要,又很让我迷惑,需要反复的学习、体会。
什么是this
this
是在运行的时候绑定的,并不是在编写的时候绑定,它的上下文取决于函数调用时候的各种条件。所以this
的绑定和函数声明的位置没有关系,只与函数的调用方式有关系。this
是上下文的一个属性。
上下文: 当一个函数调用的时候,会创建一个活动记录(上下文),这个记录会包含函数在哪里被调用,调用的方法,传入参数的信息。this就是这个记录(上下文)的一个属性
在全局函数中,this
等于window
,而当函数被视为某个对象的方法调用时,this
等于那个对象。
this
实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用
也就是说,只有函数才有this
属性,其他的对象并没有这个属性。
this
的绑定规则
上面提到了,this
是在函数被调用时发生的绑定,它指向什么完全取决于在哪里调用。所以说,我们的第一步就是判断函数的调用位置。
分析出了函数的调用位置之后,接下来就是要判断它是应用于哪种绑定规则,this
有4种绑定规则:
- 默认绑定
- 隐式绑定
- 显示绑定
new
绑定
四种绑定规则的优先级从上到下依次递增,默认绑定的优先级最低,new
绑定的优先级最高,而bind
方法就是显示绑定的一种
(1) 默认绑定
独立函数调用时,this
默认指向全局
1 | var a = 2; |
这个例子中,函数的调用位置是全局环境,所以其指向全局即等于window
(注意这是在非严格模式下),如果在严格模式下会输出undefined
(2)隐式绑定
调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。
1 | function foo() { |
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this
绑定到这个上下文对象中。当然,也有一些隐式绑定的函数丢失绑定对象的问题,例如:
1 | var a = '全局中的a'; |
s
只是obj.foo
的一个引用,而在当s
调用的时候已经没有了上下文对象,因此将会默认绑定,从而输出全局中的a
(3)显示绑定
用call
/apply
/bind
方法,我们可以在调用时强制把函数的this
绑定到某个对象上。
(4)new
绑定
使用new
关键字调用一个构造函数时,也会发生this
的绑定,具体过程:
- 创建一个新对象
- 这个对象会被执行
__proto__
连接 - 这个新对象会绑定到函数调用的
this
. - 如果函数没有返回其他对象,那么
new
表达式中的函数会自动返回这个新对象。
1 | function foo(a) { |
一种对this
的理解方式
来理解一道题:
1 | var obj = { |
为什么是这样的结果呢?
先看 bar()
,相当于是这样的调用:
1 | bar.call(undefined) |
在非严格模式下,如果传入的 context 是 context
或者 undefined
,那么就默认是 window
而对象的调用 obj.foo()
,相当于是这样的调用:
1 | obj.foo.call(obj) |
结果自然就是 obj
所以可以得出这样的结论:
this
就是call
一个函数时,传入的第一个参数- 如果函数调用形式不是
call
形式,请转换为call
形式再来看this
指向谁
call
/apply
和函数执行的本质
当我们执行一个函数,一下集中调用方式等价:
1 |
|
在严格模式下, fn
里的this
就是call
的第一个参数,也就是undefined
。
在非严格模式下, call
传递的第一个参数如果是undefined
或者null
,那this
会自动替换为Window
对象
1 | var obj = { |
练习
以上就是原理,我们根据原理来发散做几道测试题
1 | var name = '饥人谷'; |
第二道:(这道题有点意思)
1 | var arr = [] |
bind
bind
的作用和call
与apply
类似,区别在于使用上。bind
的执行的结果返回的是绑定了一个对象的新函数
先看一个使用例子:
1 | var obj = { name: '饥人谷' }; |
bind()
方法创建了一个新的函数,在bind()
被调用时,这个新函数的this
会被设定为bind
方法传入的第一个参数,其余的参数将作为新函数的参数供调用时使用。
从上面的定义来看,bind
函数的功能包括:
- 改变原函数的
this
指向,绑定this
- 返回原函数的拷贝,并预传参数
要注意的,当使用new
调用bind
返回的参数时,bind
绑定this
失效。这是由于上面提到过的this
绑定的优先级,new
的优先级高于bind
箭头函数
箭头函数没有自己的this
,它的this
是继承而来,默认指向在定义它时,它所处的对象(宿主对象),而不是执行时的对象,
1 | let app = { |
粗略一看,fn1
、fn2
、fn3
貌似都一样,实际上fn1
和fn2
完全等价,但fn3
是有区别的
以上代码等同于
1 | app.fn2.call(app) |
再看一道题目:
1 | const app = { |
再看一道题目:
1 | const app = { |
易犯错误
(1)this
对象指向自身
1 | function foo(num) { |
this
并不是指向foo
自身的,因此当输出foo.count
值的时候,其依然是0
。此时foo()
作为一个全局函数被调用,this
指向的window
这种错误是对this
根本没有理解,this
代表的是函数调用时的上下文环境,而不是从字面意义的理解,表示函数自己
(2)this
指向自身的作用域
1 | var a = 1; |
仍然是比较初级的错误,foo
里面定义的var a
是函数的局部作用于。