关于this的认知

对于JavaScript的学习,想必每一位童鞋都会对this呀,闭包呀感到十分的“头痛”。之前也是对this一知半解,后来看了一些相关的资料渐渐明白了一些。今天整理一下,希望对大家的学习有所帮助,当然啦,如果有错误之处,还请大家指出以加以改正。我将从以下四个方面对this进行讲解:

  • 默认绑定
  • 隐式绑定
  • 显式绑定
  • new绑定

默认绑定

每一个函数的this都是在调用的时候被绑定的,完全取决于函数的调用位置。关于独立函数调用,我们先来看一段代码:

1
2
3
4
5
6
var a = 9;
function foo(){
console.log(this.a);
}

foo(); // 9

此代码中,this.a被解析为了全局变量a。这就是应用了this的默认绑定,this指向全局对象。任何不带修饰的函数,只能使用默认绑定,无法应用其他规则

如果是严格模式下,全局对象无法使用默认绑定,this将绑定到undefined

1
2
3
4
5
6
7
var a = 9;
function foo(){
"use strict";
console.log(this.a);
}

foo(); // TypeError: this is undefined

隐式绑定

当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。看下面的一段代码:

1
2
3
4
5
6
7
8
9
function foo(){
console.log(this.a);
}
var obj = {
a: 9,
foo: foo
};

obj.foo(); // 9

foo()函数调用时会使用obj上下文来引用函数。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

对象属性引用链中只有最顶层或者说最后一层会影响调用位置。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo(){
console.log(this.a);
}

var obj1 = {
a: 9,
obj2: obj2
};
var obj2 = {
a: 19,
foo: foo
};

obj1.obj2.foo(); // 19

隐式丢失

被隐式绑定的函数会丢失绑定对象,,这时会应用默认绑定,从而把this绑定到全局对象或者undefined,取决于是否为严格模式。如:

1
2
3
4
5
6
7
8
9
10
function foo(){
console.log(this.a);
}
var obj = {
a: 9,
foo: foo
};
var bar = obj.foo;
var a = 19;
bar(); // 19

这里虽然barobj.foo的一个引用,但实际上,引用的是foo函数本身,此时的bar()是一个不带任何修饰的函数调用,因此会应用默认绑定。同理,回调函数传参也是如此。

参数传递以及把函数传入语言内置的函数都会应用默认绑定。

显示绑定

绝大多数的函数都可以使用call()apply()方法,它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在函数调用时指定这个this。可以直接指定this的绑定对象,因此我们称之为显示绑定。如:

1
2
3
4
5
6
7
8
function foo(){
console.log(this.a);
}
var obj = {
a: 9
};

foo.call(obj); //9

ES5中提供了一种内置方法Function.prototype.bind,用法如下:

1
2
3
4
5
6
7
8
9
10
11
function foo(something){
console.log(this.a,something);
return this.a + something;
}
var obj = {
a: 9
};
var bar = foo.bind(obj);

var b = bar(3);
console.log(b); // 12

new绑定

首先我们先来了解一下使用new调用函数时,发生的操作:

  • 创建一个全新的对象
  • 新对象被执行[[原型]]连接
  • 新对象被绑定到函数调用的this
  • 如果函数没有其他对象的返回值,new表达式中的函数就会自动调用这个新对象

看如下代码:

1
2
3
4
5
6
function foo(a){
this.a = a;
}

var bar = new foo(9);
console.log(bar.a); //9

优先级

判断函数在某个调用位置应用哪条规则,按照如下顺序进行判断:

  • 函数是否在new中调用?如果是的话this绑定的是新创建的对象。
  • 函数是否通过call()apply()调用?如果是的话,this绑定的是指定的对象。
  • 函数是否在某个上下文对象中调用?如果是的话,this绑定到那个上下文对象。
  • 如果都不是的话,使用默认绑定。严格模式下,绑定到undefined,否则绑定到全局对象。

补充

ES6中的箭头函数不会使用以上四条规则,而是根据当前词法作用域来决定this,也就是箭头函数会继承外层函数调用的this绑定,类似于self = this机制。