一、关于this
this关键字是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。
1.1 为什么要用this
首先我们来看一段代码:
function identify() {
return this.name.toUpperCase();
}
function speak() {
var greeting = "Hello,I'm " + identify.call(this);
console.log(greeting);
}
var me = {
name: 'Kyle'
};
var you = {
name: 'Reader'
};
identify.call(me); // KYLE
identify.call(you); // READER
speak.call(me); // Hello,I'm KYLE
speak.call(you); // Hello,I'm READER
上面代码中,可以在不同的上下文对象(me和you)中重复使用函数iedentify()和speak(),不用针对每个对象编写不同版本的函数。
1.2 this的作用域
this在任何情况下都不指向函数的词法作用域。在JavaScript内部,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过JavaScript代码访问,它存在于JavaScript引擎内部。
1.3 this到底是什么?
this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也成为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。
二、this全面解析
2.1 this的四种绑定规则
1. 默认绑定
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2
在上面代码中,函数foo和变量a同在全局作用域中声明,调用函数foo时,直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定。当调用函数foo时,this.a被解析成了全局变量a。
2. 隐式绑定
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
上述代码中,函数foo被当作引用属性添加到obj中,foo不属于obj对象,但当foo()被调用时,它的落脚点确实指向obj对象。根据隐式绑定规则,会把函数调用中的this绑定到这个上下文对象。所以调用this.a得到的值等于obj.a。
3. 显式绑定
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj); // 2
上述代码中,通过foo.call(...),可以在调用foo时强制把它的this绑定到obj上。
4. new绑定
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
1.建一个全新的对象;
2.这个新对象会被执行[[原型]]连接;
3.这个新对象会绑定到函数调用的this;
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象;
下面来看一段代码:
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a);
使用new来调用foo(...)时,我们会构造一个新对象并把它绑定到foo(...)调用中的this上。
2.2 this词法(ES6箭头函数)
ES6中的箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
下面我们来通过一段代码看看箭头函数的词法作用域:
function foo() {
// 返回一个箭头函数
return (a) => {
// this继承自foo()
console.log(this.a);
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
var bar = foo.call(obj1);
bar.call(obj2); // 2
上述代码中,foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。