【译】深入理解JavaScript系列:3. this
this的定义
this是执行环境对象的属性,指向执行环境运行时所在的对象;
this与可执行代码的类型直接相关,其值在进入执行环境阶段确定,而且在执行过程中保持不变;
全局环境中的this
在全局执行环境中,this总是执行全局对象本身;
//显式的定义全局对象的属性
this.a=10;
console.log(a);
//隐式的定义全局对象的属性
b=20;
console.log(b);
//通过变量声明间接的定义全局对象的属性
var c=30;
console.log(this.c);//30
函数执行环境中的this
在函数执行环境中,this值不是静态绑定到函数的;
this值是在进入执行环境时确定的,指向调用函数时的执行环境,对于同一函数通过不同方式调用其this指向不同的对象;
this值确定之后,在代码执行阶段其值是不变的,this值不是变量所以不能为其分配新值;
var foo = { x:10 };
var bar={
x:20,
test: function(){
console.log( this ); // bar, { x: 20, test: [Function] }
console.log( this.x ); // 20
}
};
bar.test();
foo.test=bar.test;
foo.test(); //this指向foo, { x: 10, test: [Function] }
this是由激活函数执行环境的caller提供, 比如调用函数的父级执行环境,但决定this值的惟一因素是调用表达式的形式,即调用函数的语法形式 ;
一般会说this值取决于函数的定义方式,全局环境中的函数this值指向全局对象,作为对象的方法的函数this指向该对象,这种说法是错误的;
- 全局函数的this不指向全局对象的情形:
var variable = 22;
function foo(){
var variable = 11;
console.log(this.variable);
}
console.log( "here" );
foo(); //22
console.log( foo === foo.prototype.constructor );// true
foo.prototype.constructor(); // undefined
- 对象方法的this不指向对象的情形:
var foo={
bar:function(){
console.log(this===foo);
}
};
foo.bar(); // true
var test=foo.bar;
console.log(test === foo.bar); // true
test(); // false
Reference类型
引用类型的值
引用类型的值可以表示为包含两个属性的对象:
- base属性: 表示拥有属性的对象;
- propertyName属性: 属性名;
var valueOfReferenceType={
base:<base object>,
propertyName: <property name>
};
引用类型的值只适用于标识符解析和属性访问两种情况:
标识符解析
标识符解析返回的值是一个引用类型的值;
标识符包括变量名、函数名、函数的形式参数名和全局对象中的unqualified的属性名;
var foo=10;
function bar(){};
以上代码对应的引用类型如下所示:
// 变量 foo
var fooReference={
base:global,
propertyName:"foo"
}
// 函数 bar
var barReference={
base:global,
propertyName:"bar"
}
为了从引用类型的值得到对真正的值,在内部使用GetValue()方法,用伪代码表示如下:
内部方法[[Get]]用于从对象获取指定属性,包括从原型链继承而来的属性;
function GetValue(value){
//非引用类型的值直接返回
if( Type(value) != Reference){
return value;
}
//获取值所属的对象
var base=GetBase( value );
if( base===null ){
throw new ReferenceError;
}
//返回从值所属的对象获取的属性值,通过 [[Get]] 方法
var propertyName = GetPropertyName( value );
return base.[[Get]]( propertyName );
}
获取fooReference和barReference的值:
GetValue(fooReference); // 10
GetValue(barReference); // 函数对象 bar
属性访问
属性访问分为点访问法和方括号访问法;
点访问法中的属性必须是标识符;
foo.bar();
foo["bar"]();
引用类型的值与函数执行环境中的this值的关系
函数执行环境中的this由激活函数执行环境的caller提供,但决定this值的惟一因素是调用表达式的形式,即调用函数的语法形式;
如果表示函数调用的圆括号的左侧是一个引用类型的值,则函数中的this指向该引用类型值的base对象;
标识符:
function foo(){
return this;
}
foo(); // window
// 标识符foo的引用类型表示
var fooReference={
base: global,
propertyName: "foo"
}
foo.prototype.constructor(); // foo.prototype
// foo.prototype.constructor是属性
var fooPrototypeConstructorReference = {
base:foo.prototype,
propertyName:"constructor"
}
属性访问:
var foo={
bar:function(){
return this;
}
};
foo.bar(); // foo
//属性foo.bar的引用类型表示
var fooBarReference={
base: foo,
propertyName: "bar"
};
//通过不同形式的调用表达式调用同一函数:
var test = foo.bar;
test(); // window
// test是标识符
var testReference={
base:global,
propertyName:"test"
}
当表示函数调用的圆括号的左侧不是引用类型的值时,this总是设置为null;
因为将this设置为null值没有意义,所以隐式的将其转换为全局对象;
在ES5的严格模式下this值不再强迫转换为全局对象,而是设置为undefined;
- 圆括号左侧不是标识符或属性访问表达式,而是一个函数对象:
(function(){
console.log(this); // window
})();
- 复杂情形:
var foo={
bar: function(){
console.log(this);
}
};
foo.bar(); // foo
(foo.bar)(); //foo
//第一个圆括号是一个组运算符,从引用类型获取值的GetValue()方法不适用与该运算符(why),其返回值仍然是引用类型
(foo.bar=foo.bar)();
//赋值运算符, 使用GetValue()方法进行求值,返回值是一个函数对象,所以null
(false || foo.bar)();
// 返回foo.bar表示的值,函数对象,所以null
(foo.bar,foo.bar)();
// 返回函数对象,所以null
当引用类型值的base对象是活动对象时,this值将指向null,并转换为全局对象;
function foo(){
function bar(){
console.log(this); // gloabl
}
bar(); //等价于AO.bar(), 但AO作为base对象返回null
}
with, catch和递归调用
通过with调用函数时,with对象被添加到作用域链最前端,屏蔽外层函数的活动对象或者全局对象,函数中的this总是指向with对象;
var x=10;
with({
foo:function(){
console.log(this.x)
},
x:20
}){
foo(); // 20
}
foo的引用类型:
var fooReference={
base:__withObject,
propertyName:"foo"
};
ES3中调用catch语句传入的参数函数时,函数中的this指向catch对象,而不是全局对象或活动对象;
这被认为是一个bug, 在ES5中得到修正,this将指向全局对象;
try{
throw function(){
console.log(this);
};
}catch(e){
e();
}
var eReference={
base:global,
propertyName:"e"
};
在递归的调用命名函数表达式时,第一次调用中base对象是外层函数的活动对象或者全局对象,在之后的递归调用中base对象应该是存储函数表达式名的特殊对象,但实际上this值总是指向全局对象;
(function foo(bar){
console.log(this);
!bar && foo(1);
})()
//window window
构造函数中的this
在构造函数内this总是指向新创建的对象;
new操作符调用构造函数的内部方法[[construct]]创建新对象,对象创建之后在构造函数上调用[[call]]方法,this指定为新建的对象,初始化新对象;
function Foo(){
console.log(this);
this.x=10;
}
var foo=new Foo();
console.log(foo.x);
手动设置函数中的this
在Function.prototype上定义了apply()和call()方法用于手动的指定函数调用中的this;
call()和apply()的第一个参数是在函数执行环境中使用的this值, 其他参数传入调用的函数;
call接受任意数量的参数,apply数组作为参数;
var variable = 1;
function act(arg) {
console.log(this.variable);
console.log(arg);
}
act(2);
// 1, 2
act.call({variable : 3}, 4);
// 3, 4
act.apply({variable : 5}, [6]);
// 5, 6

浙公网安备 33010602011771号