一、 this 定义
this是 “执行上下文”的属性。this与上下文的可执行代码类型有关。this的值在进入执行上下文时就已经决定了,而且在执行代码阶段中是不能改变的。
activeExecutionContext = {
VO: {...},
this: thisValue
};
二、全局作用域下的this
- 全局作用域下的
this为 全局对象自己,即window对象。 即:this === window, true- 点击打开demo
this.a = 10; // 等价 window.a = 10;
console.log(a); // 10
b = 20;
console.log(this.b); // 20
var c = 30;
console.log(this.c); // 30
三、 函数作用域下的this
3.1 介绍
- 函数作用域下的
this的第一个特性(最主要的特征):this并不是静态绑定到函数上的。- 就像前面所讲的,
this的值决定于所进入的上下文,因此它在函数代码中的话,其值是可以不同的。- 但是,只要进入了执行代码阶段,
this的值就不可变了。
- 例如:我们根本不可能给
this赋一个新值,因为this根本就不是变量。- 点击打开demo
var foo = {x: 10};
var bar = {
x: 20,
test: function () {
console.log(this === bar); // true
console.log(this.x); // 20
this = foo; // 报错,不可以改变 this 的值
console.log(this.x); // 如果没有报错的话,这里是 10 而不是 20.
}
};
// 在进入上下文,this的值决定于 bar 对象
bar.test(); // true, 20
3.2 函数代码内影响this值变化的原因
// bar 的代码参考 3.1
// 将 bar.test 的引用 赋给 foo.test ,此时 this 的值变为 foo
foo.test = bar.test;
foo.test(); // false, 10
- 点击打开demo
- 在一般的函数调用中,
this的值是由 激活上下文代码的调用者 决定的,例:调用函数的外层上下文。this的值是由 调用表达式的形式 决定的。
- 一些关于
JavaScript的文章和书籍中指出的“this的值取决于函数的定义方式,如果是全局函数,则this的值就会设置为全局对象,如果是某个对象的方法,则this的值就会设置为该对象”,这个说法其实是错误的。请看以下例子:
function foo () {
console.log(this);
}
foo(); // window,因为
var fooReference = {
base: global,
propertyName: 'foo'
};
console.log(foo === foo.prototype.constructor); //true
foo.prototype.constructor(); // {constructor: f} -- foo.prototype, 因为
var fooPrototypeConstructorReference = {
base: foo.prototype,
propertyName: 'constructor'
}
- 从上述代码可以看出,
foo即使是全局函数,但它 被不同的调用表达式激活,即:foo()以及foo.prototype.constructor(),其this的值也会受到影响 — 即:this值不同。上述代码的结果展示:- 点击打开demo

- 在对象中定义的方法,其
this值并不全是这个对象。请看下面代码:- 点击打开demo
var foo = {
bar: function () {
console.log(this,this === foo);
}
};
foo.bar(); // foo, true
// 将 foo.bar 复制给全局变量 exampleFunc,使用另外一种形式的调用表达式
// foo.bar 和 exampleFunc 两个函数是相等的,但是其 this值是不一样的。
var exampleFunc = foo.bar;
console.log(exampleFunc === foo.bar); // true
exampleFunc(); // window, false
3.3 引用类型(reference type)
- 引用类型 与
this的值是哪一个有着很大的关系,我们可以通过引用类型回答3.2展示的例子 “会输出这些结果的原因”的问题。
- 通过使用伪代码,引用类型的值可以表示为具有两个属性的对象:
base:属性所属的对象。propertyName:base对象中的属性名。
var valueOfReferenceType = {
base: <base object>,
propertyName: <property name>
}
- 引用类型的值又是如何影响函数上下文中
this的值呢?- 在函数上下文中,
this的值由执行者提供以及由调用表达式的当前形式决定。- 如果在调用括号(…)的左侧,有引用类型的值,那么
this的值可设置此引用类型的base对象(即:伪代码中base的值)。- 在所有其他情况下(即非引用类型),
this的值始终为null。 但是由于null对于this来说没有任何意义,因此隐式转换为全局对象,global ===》 window。
- 根据上述观点,我们可以判断
this的取值了。不过,获取引用类型的值只能是以下两种情况:
- 1.当我们处理一个标识符时 【 标识符可以是变量名、函数名、形参以及全局对象的未受限属性;并且关于标识符的算法总是返回一个引用类型的值】。请看下面代码:
- 点击打开demo
var foo = 10;
function bar () {
console.log(this);
}
bar(); // window
- 伪代码表示:
var fooReference = {
base: global,
propertyName: 'foo'
};
var barReference = {
base: global,
propertyName: 'bar'
}
- 可以看到,调用
bar函数后,输出的this值为window。原因:调用括号的左侧是引用类型的值(bar是标识符),伪代码中的base值为global,即window,this就会设置为引用类型值的base对象window。
为了从引用类型的值中获取对象真正的值需使用
GetValue方法,使用伪代码描述GetValue方法:
function GetValue (value) {
if (Type(value) != Reference) {
return value;
}
var base = GetBase(value);
if (base === null) {
throw new ReferenceError;
}
return base.[[Get]](GetPropertyName(value));
}
// 内在的[[Get]]方法返回了对象的属性的实际值,包括从原型链上继承的属性值。
GetValue(fooReference); // 10
GetValue(barReference); // function object "bar"
- 2.当我们通过属性访问 【包括点表示法和方括号表示法】,请看以下代码:
- 点击打开demo
var foo = {
bar: function () {
console.log(this);
}
};
foo.bar(); // foo
foo['bar'](); // foo
- 伪代码
var fooBarReference = {
base: foo,
propertyName: bar
};
- 可以看到,调用
bar函数后,输出的this值为foo,原因:其base对象是foo对象,激活bar函数时,this就设为foo对象了。
- 不要忘记:即使是同一个函数,但是调用表达式的形式不同,其
this的值也是不同的。原因:处理过程中,是不同的引用类型的值。请看下面代码:- 点击打开demo
var foo = {
bar: function () {
console.log(this);
}
};
var test = foo.bar;
test(); // window
- 伪代码
var testReference = {
base: global,
propertyName: 'test'
};
test即使和foo.bar共享同一个函数,但是调用test后输出的this值是window而不是foo。原因:test是标识符,就会产生另外的引用类型的值,其中base对象(window)就是this的值。
- 以下是另外一种(典型的)利用调用表达式形式来动态决定
this值的例子:- 点击打开demo
function foo () {
console.log(this.bar);
}
var x = {
bar: 10
};
var y = {
bar: 20
};
x.test = foo;
y.test = foo;
x.test(); // 10
y.test(); // 20
3.4 函数调用和非引用类型
- 如果在调用括号的左边不是引用类型而是其他类型时,
this会自动设置为null,并最终会指向window对象。
(function() {
console.log(this); // null ===》 window
})()
- 在上述代码中,我们有函数对象但这个对象并不是引用类型(因为它不是标识符也不通过属性访问),因此,
this最终被设为window对象。
- 再看以下例子:
- 点击打开demo
var foo = {
bar: function() {
console.log(this);e
}
};
foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo
(foo.bar = foo.bar)(); // window
(false || foo.bar)(); // window
(foo.bar,foo.bar)(); // window
- 从上述代码我们可以看到:都是通过属性访问,但是其输出的
this值不全是foo,也有window,这是为什么?- 事实上,最后的三个调用,在添加了特定的操作后,调用括号左侧的值就不再是引用类型。
- 第一个调用,非常明确,是引用类型,最终
this的值设为base对象foo。- 第二个调用,有一个 组操作符(
grouping operator) ,该操作符不会触发调用获取引用类型实际值的方法,例如:GetValue方法。如果处理组操作符中间过程中——获得的仍然是一个引用类型的值,那么this值仍为base object,即foo。- 第三个调用,赋值操作符(
assignment operator) 不像组操作符,它会调用GetValue方法。它最后返回的是函数对象而不是引用类型的值,所以this会设为null,即:window。- 第四、五个调用,逗号(
comma)操作符 和 逻辑OR操作符都会调用GetValue方法,于是我们失去了原来的引用类型的值,变成了函数类型,因此,this的值为window。
3.5 引用类型 和 null
- 有一种情况:当调用表达式在调用括号左侧是引用类型的值时,但是
this被设置为null,结果是指向window。发生这种情况的条件是当引用类型的base对象是活动对象(activation object)有关。
- 当内部子函数在父函数中被调用就会发生这种情况。我们知道,局部变量、嵌套函数和形参都可以保存在指定函数的活动对象中。
function foo () {
function bar () {
console.log(this); // window
}
bar(); // 与 AO.bar() 一样
}
- 活动对象经常返回的
this值为null,即:伪代码的AO.bar()等价于null.bar()。因此,this的值仍然是window。
with:- 当函数调用在
with代码块中,并且with对象包含函数属性时,就会出现例外。with语句会将这个对象添加到作用域链的最前面,在活动对象之前。- 相应地,在引用类型的值(标识符或者属性访问)情况下,
base对象不是活动对象而是with语句里的对象。- 它不仅仅只针对内部函数,全局函数也是如此。
- 原因:
with对象掩盖了作用域链中更高层的对象。(全局对象或活跃对象)。- 点击打开demo
var x = 10;
with({
foo: function () {
console.log(this.x);
},
x: 20
}) {
foo(); // 20
}
// 因为
var fooReference = {
base: __withObject,
propertyName: 'foo'
}
try-catch- 当调用的函数是
catch子句的参数时,也有类似的情况:catch对象也会被添加到作用域链的前端,在活动对象或者全局对象之前。- 然而,这个行为在
ECMA-262-3中被指出是个bug,并且已经在ECMA-262-5中修正了;因此,在这种情况下,this的值应该设置为全局对象,而不是catch对象。- 点击打开demo
try {
throw function () {
console.log(`this:` + this);
}
} catch (e) {
e(); // __catchObject - in ES3, global - fixed in ES5
}
var eReference = {
base: __catchObjec,
propertyName: 'e'
}
// 然而,既然这是个bug
// 那就应该强制设置为全局对象
// null => global
var eReference = {
base: global,
propertyName: 'e'
};
- 相同的情况也会发生在 递归调用 一个 非匿名函数。
- 在第一次调用时,
base对象是外层的活动对象或者全局对象。- 在接下来的递归调用,
base对象应该是一个存储了可选函数表达式名的特殊对象。- 然而,事实却是:在这种情况下,
this的值永远是全局对象。- 点击打开demo
(function foo (bar) {
console.log(this);
!bar&&foo(1);
})();
3.6 函数作为构造器被调用时this的值
function A () {
console.log(this); // 新创建的对象 - 下面的`a`对象
this.x = 10;
}
var a = new A();
console.log(a.x); // 10
- 在这种情况下,
new操作符会调用“A”函数的内部[[Construct]]。 在对象创建之后,会调用内部的[[Call]]函数,然后所有“A”函数中this的值会设置为新创建的对象。- 点击打开demo
3.6 手动设置函数调用时this的值
Function.prototype上定义了两个方法,可以帮助我们手动指定函数调用时this的值 —–apply和call。- 详细请参考:javascript - 函数方法 apply()、call()、bind()