this
可以表示JavaScript中函数的运行环境,也叫上下文(context);可以简单理解为this
表示是谁在调用这个function
(为了避免混淆,这里指用function
关键字定义的代码,下同)。
在JavaScript中function
关键字定义的代码的触发方式可以分为三种:
不同的种类,代码中的this
关键字指向是不同的。
当在JavaScript对象里使用function
定义对象的属性(property)或者域(field)时,function
表示方法(method)。
Method Invocation指的是类似于object.methodName(arg)
的调用方式,或者是(expression)(arg)
的调用方式。这里的expression
是JavaScript里的表达式,当这个表达式最后的结果是个object
时,调用方式依然是Method Invocation。
Method Invocation里的this
被绑定到了调用时的object
上,例如:
var object = {
methodName: function () {
console.log(this);
}
};
object.methodName(); // {methodName: ƒ}
Function Invocation指的是类似于functionName(arg)
的调用方式,或者是(expression)(arg)
的调用方式。这里的expression
是JavaScript里的表达式,当这个表达式最后的结果是个function
时,调用方式依然是Function Invocation,即使表达式里包含形式与Method Invocation很相似的object.methodName
。
Function Invocation里的this
被绑定到了全局的Window对象上,例如:
var object = {
methodName: function () {
console.log(this);
}
};
(object.methodName = object.methodName)(); // Window
(false || object.methodName)(); // Window
// 逗号运算符:对两个表达式求值并返回后一个表达式的值
(1, object.methodName)(); // Window
注意在严格模式下,Function Invocation里的this默认不再绑定到Window对象上,而是undefined
。使用 'use strict'
进入严格模式。
'use strict'
var object = {
methodName: function () {
console.log(this);
}
};
(object.methodName = object.methodName)(); // undefined
(false || object.methodName)(); // undefined
(1, object.methodName)(); // undefined
在JavaScript里的面向对象编程:
function ClassName(name) {
var age; // 私有域
this.name = name; // 公有域
this.getName = function () {
return this.name;
};
/* ... */
}
var instance = new ClassName('foo');
console.log(instance.__proto__); // {constructor: ƒ}
console.log(ClassName.prototype); // {constructor: ƒ}
上面的代码中ClassName就是构造函数(constructor),使用new
关键字触发。这便是Constructor Invocation。
注意对于constructor,一定要使用new关键字触发。
这是因为new
关键字一定返回一个对象:
对于不含return
语句的构造函数:返回实例对象
对于包含return
语句的构造函数:
return
的是对象,则返回这个指定的对象return
的是基础类型,则忽略reutrn
语句,返回实例对象对于普通函数:返回空对象{}
若不使用new
命令,不含return
语句的构造函数将返回undefined
那么当我们使用new
命令时,具体发生了什么呢?
1️⃣创建一个空对象,作为将要返回的对象实例。
2️⃣将这个对象的原型(__proto__
),指向构造函数的prototype
属性
3️⃣将这个空对象赋值给函数内部的this
关键字。
4️⃣开始执行构造函数内部的代码。
可以看到
Constructor Invocation指的是类似于new ConstructorName(field)
的调用方式。
Constructor Invocation里的this
被绑定到了实例对象上。
JavaScript 语言之所以有 this 的设计,跟内存里面的数据结构有关系。
var obj = { foo: 5 };
上面的代码将一个对象赋值给变量obj
。JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 }
,然后把这个对象的内存地址赋值给变量obj
。也就是说,变量obj
是一个地址(reference)。后面如果要读取obj.foo
,引擎先从obj
拿到内存地址,然后再从该地址读出原始的对象,返回它的foo
属性。
原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo
属性,实际上是以下面的形式保存的。
{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
注意,foo属性的值保存在属性描述对象的value属性里面。
这样的结构是很清晰的,问题在于属性的值可能是一个函数。
var obj = { foo: function () {} };
这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性。
{
foo: {
[[value]]: 函数的地址
...
}
}
由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。
var f = function () {};
var obj = { f: f };
// 单独执行 Function Invocation
f()
// obj 环境执行 Method Invocation
obj.f()
JavaScript 允许在函数体内部,引用当前环境的其他变量。
var f = function () {
console.log(x);
};
上面代码中,函数体里面使用了变量x。该变量由运行环境提供。
现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this
就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
var f = function () {
console.log(this.x);
}
上面代码中,函数体里面的this.x
就是指当前运行环境的x。
var f = function () {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 单独执行
f() // 1
// obj 环境执行
obj.f() // 2
上面代码中,函数f
在全局环境执行,this.x
指向全局环境的x
;在obj
环境执行,this.x
指向obj.x
。
所以可以看出,JavaScript的this
本质上是一个内存问题:
function
触发时,function
的地址是怎么获得的?
this
指向对象。this
指向全局对象Window。new
运算符将this
指向实例对象。回想前面的例子:
var object = {
methodName: function () {
console.log(this);
}
};
object.methodName(); // Method Invocation: {methodName: ƒ}
(false || object).methodName(); // Method Invocation: {methodName: ƒ}
(object.methodName)(); // Method Invocation: {methodName: ƒ}
/* 下面的形式之所以为 Function Invocation
* 是因为在使用属性访问器得到 function 的地址后,还有运算的过程
* 运算完成后返回 function 的地址,然后使用 () 运算符触发
* 这样的话就是直接通过函数地址触发函数了
* 这时 this 指向全局对象 Window
*/
(object.methodName = object.methodName)(); // Window
(false || object.methodName)(); // Window
// 逗号运算符:对两个表达式求值并返回后一个表达式的值
(1, object.methodName)(); // Window
避免多层this
var object = {
methodName: function () {
console.log(this);
var func = function () { // 触发时,直接使用函数地址
console.log(this);
}(); // Function Invocation
}
};
// 触发时,使用属性访问器获得函数地址
object.methodName(); // Method Invocation
-------------console----------------------
>> {methodName: ƒ}
>> Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
一个解决方法是在第二层改用一个指向外层this
的变量。
var object = {
methodName: function () {
console.log(this);
var that = this;
var func = function () { // 触发时,直接使用函数地址
console.log(that);
}(); // Function Invocation
}
};
// 触发时,使用属性访问器获得函数地址
object.methodName(); // Method Invocation
-------------console----------------------
>> {methodName: ƒ}
>> {methodName: ƒ}
事实上,使用一个变量固定this
的值,然后内层函数调用这个变量,是非常常见的做法,请务必掌握。
避免数组处理方法里的this
数组的map和forEach方法,允许提供一个函数作为参数。这个函数内部不应该使用this
。
var object = {
msg: 'hello',
arr: [ 'a', 'b' ],
method: function func() {
this.arr.forEach(function (item) {
console.log(this); // function 触发时,this被绑定到了全局对象Window上
console.log(this.msg + ' ' + item);
});
}
};
object.method();
-------------console----------------------
>> Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
>> undefined a
>> Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
>> undefined b
要解决这个问题可以将this
作为第二个参数,传给forEach方法。也可以使用中间变量固定this
。
避免回调函数里的this
当回调函数调用时,往往运行环境(context)早已发生变化,所以尽量不要在回调函数里面使用this
。
JavaScript提供了三个方法,让我们灵活的指定运行环境,也就是this
。
this in javascript
this 关键字