JS中的this
louzhedong opened this issue · comments
JS中的this
this关键字是JavaScript中最复杂的机制之一
它提供了一种优雅的方式来隐式传递一个对象引用
即使是非常有经验的前端程序员,也可能说不清它到底指向什么
本文的概念都基于非严格模式,严格模式中某些场景会有些许出入
this是什么
首先来理解一下this的概念:
this在函数运行时绑定,而不是在编写时,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式
再深入一点:
当一个函数被调用时,会创建一个执行上下文,执行上下文包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this也是这个执行上下文的一个属性,会在函数执行的过程中用到
调用位置
要理解this的绑定过程,首先要理解调用位置:调用位置就是函数在代码中被调用的位置(而不是被声明的位置)
this绑定规则
找到函数的调用位置之后,我们就可以去探索this的指向了
this的指向可以通过以下四条规则去判断,分别为
- 默认绑定
- 隐式绑定
- 显示绑定
- new 绑定
1.默认绑定
默认绑定发生在独立调用函数时。当其他规则无法应用时,默认应用此规则
查看下列示例:
function hello(){
console.log('hello,' + this.name);
}
var name = 'Mike';
hello(); // hello,Mike
在这个示例中,hello函数是一个独立执行的函数,没有任何对象去调用它或者被它绑定。此时函数的this指向全局对象,所以能够找到全局对象中的name变量
2.隐式绑定
隐式绑定需要考虑调用位置是否有上下文对象,即调用函数是否被某个对象拥有或者包含
查看下列示例:
function hello(){
console.log('hello,' + this.name);
}
var person = {
name: 'Mike',
hello: hello
}
// 定义全局中的name
var name = 'John'
person.hello(); // hello,Mike
这里的调用位置会使用person上下文来引用函数,因此看起来this被绑定到了person对象上
隐式丢失
初学者经常会遇到的一个问题就是隐式绑定丢失绑定对象,即应用了默认绑定,从而把this绑定到全局对象上
查看下列示例:
function hello(){
console.log('hello,' + this.name);
}
var person = {
name: 'Mike',
hello: hello
}
// 定义全局中的name
var name = 'John'
// 函数别名
var myHello = person.hello;
myHello(); // hello,John
发现此时this没有指向person,而是指向了全局对象
那为什么会发生这种情况呢?
在执行var myHello = person.hello;
这行代码时,myHello实际上是hello函数的一个引用,即他们指向同一个内存堆中的函数。调用myhello就相当于直接单独调用了hello函数,因此会使用前面所讲的默认引用规则
另一种可能会迷惑的地方是将函数当做参数进行传递
查看下列示例:
function hello(){
console.log('hello,' + this.name);
}
var person = {
name: 'Mike',
hello: hello
}
// 定义全局中的name
var name = 'John'
function triggerHello(fn) {
// fn 为hello函数的引用
fn();
}
triggerHello(person.hello); // hello,John
出现这个情况的原因其实和上个例子一个,函数在调用时应用了默认绑定的规则
3.显示绑定
在JS中,我们可以通过apply和call来将函数绑定到某个对象中,此时函数中的this就会指向这个对象
查看下列示例:
function hello(){
console.log('hello,' + this.name);
}
var person = {
name: 'Mike'
}
hello.call(person); // hello,Mike
通过call函数,我们在调用hello函数时强制把它的this绑定到person上
另一种显示绑定的方式是通过bind,bind函数会返回一个新函数,并将指定给bind的参数设置为this的上下文并调用原始函数
查看下列示例:
function hello(){
console.log('hello,' + this.name);
}
var person = {
name: 'Mike'
}
var bindHello = hello.bind(person);
bindHello(); // hello,Mike
4.new 绑定
使用new来调用函数时,会进行this的绑定,具体在使用new时发生了什么可以查看 new原理及实现 这篇文章
查看下列示例:
function hello(name) {
this.name = name;
}
var newHello = new hello("Mike");
console.log(newHello.name); // Mike
规则的优先级
现在我们有了四种绑定规则,那么它们的优先级是怎么样的呢?
这里直接给出结论
- 函数是否通过new绑定,如果是的话this绑定的就是新创建的对象
var newHello = new hello()
- 函数是否通过call,apply,bind绑定,如果是的话this绑定的是指定的对象
var newHello = hello.call(person)
- 函数是否在某个上下文对象中调用(隐式绑定),如果是的话this绑定的是那个上下文对象
person.hello()
- 如果都不上,使用默认绑定,即在非严格模式下绑定到全局对象
hello()
箭头函数
上述四种规则应用于大部分场景,但箭头函数不包含其中。箭头函数根据外层作用域来决定this
小结
this确实是JavaScript这门语言中比较难理解但又非常重要的一个点,单单一篇文章可能也无法完全帮助我们理清楚this的问题。但对于大部分的问题,只要我们用这四种规则去匹配它们,基本上就能看透问题的本质。