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是函数的局部作用于。