五、this详细介绍
5.1、this到底是什么?
- 当一个函数被调用时,会创建一个执行上下文(具体概念参见我之前的文章:《JS面试(三)详细介绍闭包概念》)。这个执行上下文会记录包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性。
- this在函数被调用时发生绑定,它指向什么完全取决于函数在哪里被调用,
5.2、this的绑定规则(一)、默认绑定
- 在非严格模式下,函数被独立调用时,this指向全局对象(注:在严格模式下,会绑定到undefined)
例1:
//非严格模式
var a = "window_a"
function foo(){
console.log(this.a)
}
foo()//"window_a"
//严格模式
var a = "window_a"
function foo(){
"use strict"
console.log(this.a)
}
foo()//undefined
- 再来举另外一个例子:
例2:
var a = 'window'
function foo(){
let a = 'inner'
function innerfoo(){
console.log(this.a)
}
innerfoo()
}
foo()//window
-
- 在这里例子中,我们先调用foo函数,接在在它的执行上下文中调用了innerfoo函数,这时候innerfoo函数没有被任何对象引用,所以根据默认绑定,this被绑定到全局作用域对象中
5.3、this的绑定规则(二)、隐式绑定
- 当有上下文对象引用该函数时,隐式绑定会把函数调用中的this绑定到这个上下文对象中
例3:
function foo(){
let a = 3
console.log(this.a)
}
let obj = {
a:2,
foo:foo
}
obj.foo()//2
- 注意:在对象的引用链中只有最后一层会影响函数的调用位置
例4:
let obj2 = {
a : 3,
foo:foo
}
let obj1 = {
a : 2,
obj2:obj2
}
function foo(){
console.log(this.a)
}
obj1.obj2.foo()//3
- 下面讲一个比较坑的例子:
例5:
1:let obj = {
2: a :'inner',
3: foo:foo
4:}
5:
6:var a = 'window'
7:
8:function foo(){
9: chosole.log(this.a)
10:}
11:
12:let bar = obj.foo
13:bar()//window
-
- 在这个例子中,在第12行中,我们只是将obj.foo定义的函数赋值给了bar,并没有调用。然后在第13行中,调用了bar函数
-
- 调用bar函数的位置是在全局作用域中调用了,此时this是默认绑定,这时候它就被绑定到了全局对象中的a属性中
- 我来再讲另一个比较坑的例子:
例6:
1:let obj = {
2: a:'inner',
3: foo:foo
4:}
5:var a = 'window'
6:
7:function foo(){
8: console.log(this.a)
9:}
10:
11:function dofoo(foo){
12: foo()
13:}
14:
15:dofoo(obj.foo)//"window""
-
- 此时需要注意的一点便是,函数的参数传递是按值传递的,是一种值的赋值
-
- 所以,在第11到13行中,相当于:
function dofoo(foo){
let bar = foo
bar()
}
-
- 所以在第15行中,相当于,在全局作用域中,执行了foo定义的函数。与例5相同
8.4、this的绑定规则(三)、显示绑定
- 在上面的隐式绑定中,this的绑定对象是根据函数调用的位置来决定的,这是一种间接的方式。如果我们想把this强制绑定到我们想绑定的对象上,我们可以使用apply()、call()、bind()方法,来实现(即显示绑定)
- 我来讲一下apply()、call()、bind()三类方法的区别:
- apply()方法调用一个函数,并为其指定一个this值。它接收两个参数,第一个参数为指定的this值,它可以是我们指定的对象,也可以是null或undefined(这时候this指向全局对象)
- 第二个参数是一个数组对象,其中的数组元素将作为单独的参数传给调用的函数
例7:
function foo(b){
console.log(this.a,b)
return this.a + b
}
let obj = {
a:2
}
var a = 10
let bar = function(){
return foo.apply(obj,arguments)
}
let res = bar(3)//2 3
console.log(res)//5
- call()与apply()基本相同,第一个参数都是指定的this值,但是它的第二个参数不是数组,它的第二个参数以及后面的参数,接收到的是若干个单独的参数,然后将这些参数传递给调用的函数
例8:
function foo(b){
console.log(this.a,b)
return this.a + b
}
let obj = {
a:2
}
var a = 10
let bar = foo.call(obj,3)//2 3
console.log(bar)//5
- bind()相当于一个语法糖,它创建一个新的函数,当被调用时,将this关键字设置为提供的值,在调用新函数时,在任何之前提供提供一个给定的参数序列。这个语法糖相当于:
function bind(func,obj){
return function(){
return func.apply(obj,arguments)
}
}
例9:
function foo(b){
console.log(this.a,b)
return this.a + b
}
let obj = {
a:2
}
var a = 10
let bar = foo.bind(obj,3)
let baz = bar()//2 3
console.log(baz)//5
8.5、this的绑定规则(四)、new绑定
- 首先需要说明:使用new来调用函数时,JS引擎会自动执行下面的操作:
-
- 创建一个全新的对象
-
- 这个新对象会被执行[[prototype]]链接(具体会在后面的关于对象和原型的文章中详细介绍)
-
- 这个新对象会绑定到函数调用的this
-
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这对象
例10:
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这对象
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
8.6、箭头函数的this绑定规则
- 上面的例子中,我们介绍了四种this绑定规则,但是这些对于箭头函数来说都不适用!!!
- 箭头函数的没有this绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则this继承最近一层的非箭头函数的this,否则,this为undefined。
例11:
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( () => {
this.func1()
},100);
}
};
a.func2() // Cherry
- 在这个例子中,箭头函数被非箭头函数func2包含,所以箭头函数的this便是func2的this,因为func2在对象a中引用,所以fun2的this指向a,所以箭头函数的this也指向a