this是JavaScript中的一个关键字,其会在执行上下文中绑定一个对象,但是在不同执行条件下会绑定不同的对象,那么this在不同执行条件下的绑定规则是如何的呢?
一、为什么要使用this
与Java中的this不同(this通常用在类的方法中),JavaScript中的this更加灵活,在不同的位置会代表不同的含义,但是为什么要使用this呢,又带来了怎样的意义?
- 如果没有this
var person = {
name: 'zs',
eat: function() {
console.log(person.name + 'eat');
},
run: function() {
console.log(person.name + 'run')
}
}
person.eat()
这种方式情况下,如果修改了变量person的变量名,那么所有方法中的person都得跟着修改
- 如果有this
var person = {
name: 'zs',
eat: function() {
console.log(this.name + 'eat');
},
run: function() {
console.log(this.name + 'run')
}
}
person.eat()
这种方式情况下,即使修改了变量person的变量名也不会有影响
实际上,当通过person去调取其中的方法时,this的指向就是person对象。所以可以通过this来优化。
二、两个场景下的this指向
2-1、全局作用域下的this指向
- 浏览器中:this指向window(GO)
- Node中:this指向空对象{}
2-2、函数中的this指向
函数在执行时会创建上下文对象,上下文中包含了VO、scopeChain(作用域链:由当前上下文的VO及父级作用域的AO组成)、this
- this是动态绑定的,在函数执行时才回去绑定,而不是编译时确定的,所以不同的调用方式,this的指向不同:
function foo() {
console.log(this); // window
}
foo();
var obj = {
name: 'zs',
foo: foo
}
obj.foo(); // obj: {name: 'zs', foo: function}
foo.apply("abc"); // String("abc")
可见:
- this的绑定与定义位置无关
- this的绑定与调用方式及调用位置有关
- this是在运行时被绑定的
三、this的绑定规则
this的绑定规则有4种:
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
3-1、默认绑定
当函数被独立调用时会采用默认绑定规则(可理解为:函数没有被绑定到某个对象上进行调用)
3-1-1、案例1
function foo() {
console.log(this); // window
}
foo();
foo被独立调用,采用默认绑定规则,this此时指向window
3-1-2、案例2
function foo1() {
console.log(this); // window
}
function foo2() {
console.log(this);// window
foo1();
}
function foo3(){
console.log(this); // window
foo2();
}
foo3();
所有函数均是被独立调用,采用默认绑定规则,this均指向window
3-1-3、案例3
var obj = {
name: 'zs',
foo: function() {
console.log(this);// window
}
}
var bar = obj.foo;
bar();
依旧属于独立函数调用,即使bar = obj.foo,但执行bar时是独立调用的。
3-1-4、案例4
function foo(fun) {
fun();
}
function bar() {
console.log(this);// window
}
foo(bar);
bar最终执行也是被独立调用的,所以this也是指向window
3-1-5、案例5
function foo(fun) {
fun();
}
var obj = {
name: 'zs',
foo: function() {
console.log(this);// window
}
}
foo(obj.foo);
注意,虽然为foo传递的为obj.foo,但传递过去后,函数仍然是被独立调用的,所以this还是指向window
3-2、隐式绑定
函数的调用是某个对象发起的,这种方式this采用的绑定规则为隐式绑定
3-2-1、案例1
function foo() {
console.log(this);
}
var obj = {
name: 'zs',
foo: function() {
console.log(this);// obj: { name: 'zs', foo: [Function: foo] }
}
}
obj.foo();
foo的调用是obj对象发起的,this指向该对象(this会隐式绑定到obj对象上)
3-2-2、案例2
var obj1 = {
name: 'zs',
foo: function() {
console.log(this); // obj2: { name: 'lisi', bar: [Function: foo] }
}
}
var obj2 = {
name: 'lisi',
bar: obj1.foo
}
obj2.bar();
虽然obj2的bar是obj1的foo(bar指向foo函数),但是bar函数的调用是obj2发起的,所以this会被隐式绑定为obj2对象
3-2-3、案例3
var obj = {
name: 'zs',
foo: function() {
console.log(this);// window
}
}
var bar = obj.foo;
bar();
注意:要看最后是以什么方式调用的,虽然bar赋值为obj对象的foo,但是bar函数是被独立调用的。这种情况会造成隐式绑定丢失,会采用默认绑定规则。
3-3、显示绑定
隐式绑定的前提是,对象内部必须有一个属性且属性的值为函数的引用,通过这个引用才能间接地将this绑定到这个对象上。
那如果不希望在对象内部包含这个属性值为函数的引用的属性,又希望this指向这个对象,那怎么做呢?
JavaScript中每个函数都有call和apply方法,这两个方法的参数可以传递为需要绑定的对象,这样就会将this绑定到这个对象上。(显示绑定即:明确地去绑定this指向的对象)
3-3-1、案例1
function foo() {
console.log(this);
}
foo.call({name: 'zs'}); // {name: 'zs'}
foo.call(123); // Number对象
3-3-2、案例2
function foo() {
console.log(this);
}
var fn = foo.bind({name: 'zs'});
fn(); // {name: 'zs'}
3-4、new绑定
在使用new关键字时,会执行如下操作:
- 创建一个新对象
- 这个新对象会被执行Prototype连接
- 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤上完成)
- 如果函数没有返回其他对象,则返回这个新对象
3-4-1、案例
function Person(name) {
this.name = name;
console.log(this); // Person {name: 'zs'}
}
const p = new Person('zs');
四、内置函数的this绑定
4-1、setTimeout
setTimeout对传入的函数在内部相当于独立调用的,所以this通常是window
setTimeout(function() {
console.log(this); // window
})
4-2、forEach、map...
数组的forEach、map等方法对于传递进去的函数默认在内部也是进行独立调用的,所以this默认指向window
var arr = [1,2,3];
arr.forEach(function(i){
console.log(this); // window
})
- 可通过传递参数的方式改变this指向:
var arr = [1,2,3];
var obj = {name: 'zs'};
arr.forEach(function(i) {
console.log(this); // { name: 'zs' }
}, obj)
4-3、事件绑定相关
4-3-1、传统事件绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.div{
width: 100px;
height: 100px;
background-color: salmon;
}
</style>
</head>
<body>
<div class="div"></div>
</body>
<script>
var div = document.querySelector('.div');
div.onclick = function() {
console.log(this); // <div class="div"></div>
}
</script>
</html>
- 相当于给div添加了个onclick属性,底层直接通过div.onclick()的方式去调用,进行了隐式绑定
4-3-2、addEventListener
div.addEventListener('click', function() {
console.log(this); // <div class="div"></div>
})
- 内部进行了类似于fn.apply(div)的显式绑定
五、this绑定规则的优先级
5-1、默认绑定规则优先级最低
5-2、显示绑定优先级高于隐式绑定优先级
5-2-1、案例1
var obj = {
name: 'zs',
foo: function() {
console.log(this);
}
}
obj.foo(); // { name: 'zs', foo: [Function: foo] }
obj.foo.call('123'); // [String: '123']
var bar = obj.foo.bind('abc');
bar(); // [String: 'abc']
5-2-2、案例2
function foo() {
console.log(this);
}
var obj = {
name: 'zs',
foo: foo.bind('aaa')
}
obj.foo(); // [String: 'aaa']
5-3、new绑定优先级高于隐式绑定
5-3-1、案例
var obj = {
name: 'zs',
foo: function() {
console.log(this);
}
}
new obj.foo(); // foo {}
5-4、new绑定优先级高于显式绑定
new不能和apply、call一起使用,因为apply和call是主动调用函数
5-4-1、案例
function foo() {
console.log(this);
}
var bar = foo.bind('aaa');
new bar(); // foo {}
\
总结: new 绑定 > 显示绑定(bind)> 隐式绑定 > 默认绑定
六、this绑定的特殊场景
6-1、忽略显示绑定
当显示绑定传入null或undefined时,会自动将this绑定为全局对象
function foo() {
console.log(this);
}
var obj = {
name: 'zs'
}
foo.apply(obj); // {name: 'zs'}
foo.apply(null); // window
foo.apply(undefined); // window
var bar = foo.bind(null);
bar(); // window
var bza = foo.bind(undefined);
bza(); // window
6-2、间接函数引用导致使用默认绑定规则
var foo1 = {
name: 'foo1',
bar: function() {
console.log(this);
}
};
var foo2 = {
name: 'foo2'
};
(foo2.baz = foo1.bar)(); // window
6-3、箭头函数中的this
- 箭头函数中不绑定this和arguments
- 所以箭头函数不使用this的四种规则,而是根据外层作用域决定this
var foo = () => {
console.log(this);
}
var obj = {
foo: foo
}
obj.foo(); // window
foo.call('aaa'); // window
七、相关面试题
7-1、题目1
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss(); // window
person.sayName(); // person
(person.sayName)(); // person
(b = person.sayName)(); // window
}
sayName();
- sss()属于独立函数调用,this为window
- 隐式绑定,this指向person
- 隐式绑定,this指向person
- 间接函数引用,导致this隐式绑定失效,采用默认绑定规则,所以输出window
7-2、题目2
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
person1.foo1(); // person1
person1.foo1.call(person2); // person2
person1.foo2(); // window
person1.foo2.call(person2); // window
person1.foo3()(); // window
person1.foo3.call(person2)(); // window
person1.foo3().call(person2); // person2
person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1
- 隐式绑定,输出person1
- 显示绑定 > 隐式绑定 所以输出person2
- 箭头函数不绑定this,其this为上层作用域(全局作用域)的this,所以是window
- 箭头函数不绑定this,显示绑定也无效,其this为上层作用域(全局作用域)的this,所以是window
- person1的foo3返回一个函数,再次调用函数时相当于独立函数调用,所以是window
- 虽然最先进行了显示绑定,但绑定的是foo3,真正执行的还是返回的函数,依旧是独立调用的,所以是window
- 这次绑定的是返回的函数,所以this指向person2
- 执行的是foo4返回的箭头函数,不绑定this,无论怎样执行,其this为上层作用域(foo4)的this,即:person1
- 也是执行的foo4箭头函数,但此时其上层作用域foo4被显式绑定了person2对象,所以this指向person2
- 要显示的给箭头函数绑定person2,但箭头函数不绑定this,还是看其上层作用域的this,所以是person1
7-3、题目3
var name = 'window'
function Person (name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1() // person1
person1.foo1.call(person2) // person2
person1.foo2() // person1
person1.foo2.call(person2) // person1
person1.foo3()() // window
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2
person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
- 隐式绑定,this指向person1
- 显示绑定 > 隐式绑定,this指向person2
- 箭头函数不绑定this,this是其上层作用域的this,this指向person1
- 箭头函数不绑定this,this是其上层作用域的this,this指向person1
- 相当于独立调用返回的函数,采用默认绑定规则,this指向window
- 虽然进行了显示绑定,但绑定的为foo3函数,最终执行的依旧是返回的函数,是独立调用的,所以是window
- 为返回的函数显示绑定了person2,所以this指向person2
- 执行的为箭头函数,不绑定this,this为上层作用域的this,person1
- 上层作用域this显示绑定为person2,所以为person2
- 为箭头函数显示绑定person2无效,this依旧是其上层作用域的this,person1
7-4、题目4
var name = 'window'
function Person (name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2
person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj
- 最终执行的返回的函数是被独立调用的,this指向window
- 返回的函数的上层作用域被显示绑定为了person2但函数依旧是被独立调用的,所以this还是指向window
- 执行的函数被显示绑定为了person2,this指向person2
- 最终执行的是箭头函数,不绑定this,this为其上层作用域(foo2)的this,foo2被隐式绑定为了obj,所以this指向obj
- 最终执行的是箭头函数,不绑定this,this为其上层作用域(foo2)的this,foo2被显示绑定了person2,所以this指向person2(虽然foo2也被隐式绑定了obj,但显示绑定 > 隐式绑定)
- 最终执行的是箭头函数,不绑定this,this为其上层作用域(foo2)的this,foo2被隐式绑定为了obj,所以this指向obj