您的位置:首页 > Web前端 > JavaScript

Javascript中this的取值

2013-05-09 15:14 56 查看

this是javascript中非常基础的一个知识点,也是一个令很多初学者迷惑的知识点。

Ecmascript中对其描述如下:

There is a this value associated with every active execution context. The this value depends on the caller and the type of code being executed and is determined when control enters the execution context. The this value associated with an execution context is immutable.

this的取值跟executable code的类型直接相关,execute code有三种Global code,Function code 和Eval code。跟this取值有关的是前两种。

this在global code中的取值

这种情况下问题比较简单,

this
的值就是global对象本身

this in global code

1
2
345
6
7
8
9
10
11
12
// explicit property definition of the global object
this.a = 10; // global.a = 10
alert(a); // 10
// implicit property definition of the global object
b = 20;
alert(this.b); // 20
// also implicit via variable declaration
// in global context: this =  global = VO
var c = 30;
alert(this.c); // 30

demo中的b没有通过var 声明,它实际上不是一个variable,而是window(global)的一个property。变量和属性的一个显著区别就是变量具有{ DontDelete },而后者没有。因此经常说的

不用var能直接声明全局变量

的说法是错误的,事实上它是为windows添加了一个属性。

variable and property

1
2
34
var a =5;
b = 4;  // equal to windows.b = 4
alert(delete a); // false
alert(delete b); // true

this在function code中的取值

在function code中

this
的值不像在global code中那么简单:

  • this的值不是与对应的函数绑定的,也就是说不是在函数创建时决定。
  • this的值在进入上下文时决定,并且在执行过程中不可变。ie.不能像变量赋值一样为this赋值。

this

1
2
345
6
7
8
9
10
11
1213
14
15
16
17
18
19
20
21
22
23
var foo = {x: 10};
var bar = {
x: 20,
test: function () {
alert(this === bar); // true
alert(this.x); // 20
this = foo; // error, can't change this value
alert(this.x); // if there wasn't an error, then would be 10, not 20
}
};
// on entering the context this value is
// determined as "bar" object;
bar.test(); // true, 20
foo.test = bar.test;
// however here this value will now refer
// to "foo" – even though we're calling the same function
foo.test(); // false, 10

那么是哪些因素影响this的取值呢?在有些文章和书中有这样的说法:

this的取值由函数的定义方式决定,如果函数被定义成全局函数,则对应的this是global;如果函数是一个对象的方法(method),则this的值是对应的对象。

这种说法是错误的。来看一个例子:

Is the definition of function determing this

1
2
345
6
7
8
9
10
function foo() {
bar: function() {
console.log(this);
}
}
foo.bar(); // foo
var baz = foo.bar;
console.log(baz === foo.bar); // true
baz(); // global

baz虽然和foo.bar的值一样,也就是指向同一个函数。但是调用结果不一样,因此this的取值不是由函数的定义决定的。

事实上,

this
的值由调用者提供(例如调用函数的父上下文),由函数的调用方式决定。

为什么

this
的取值由函数的调用方式决定?

要回答这个问题需要先看下ECMA中的一个内部类型(internal type) –

Reference
type

Reference type

Refference
是一个内部类型,没有任何函数能返回
Reference
类型的值,无论是built-in函数还是用户定义的函数。

规范中关于

Reference
描述如下:

The internal Reference type is not a language data type. It is defined by this specification purely for expository purposes. However, a value of type Reference is used only as an intermediate result of expression evaluation and cannot be stored as the value of a variable or property.

The Reference type is used to explain the behaviour of such operators as delete, typeof, and the assignment operators.

other use of the Reference type is to explain the determination of the this value for a function call.

A Reference is a reference to a property of an object. A Reference consists of two components, the base object and the property name.

The following abstract operations are used in this specification to access the components of references:

  1. GetBase(V). Returns the base object component of the reference V.
  2. GetPropertyName(V). Returns the property name component of the reference V.

Reference
的结构可以描述如下:

Reference的结构

1
2
34
var ReferenceType = {
base: <base object>,
propertyName: <property name>
};

根据规范,仅在以下两种情况时会返回

Reference
类型的值:

  • 标识符解析(identifiers resolution),标识符包括变量名,函数名,函数参数名,还有就是上面提到的不用var声明,直接赋值的变量。
  • 属性访问(Property access),属性访问有两种方式,通过
    []
    或者
    .
    ,如
    foo.bar
    foo['bar']

Reference

1
2
345
6
7
8
9
10
11
1213
14
15
16
17
18
19
20
21
22
23
24
// 标识符解析
var foo = 10;
function bar() {}
// 对应的Reference的值如下
var fooReference = {
base: global,
propertyName: 'foo'
};
var barReference = {
base: global,
propertyName: 'bar'
};
// 属性访问
foo.bar();
foo['bar']();
// 对应的Reference type值
var fooBarReference = {
base: foo,
propertyName: 'bar'
};

Reference
是一个中间值,要获取real value,需要用到
GetValue
方法(参见),其伪代码如下:

GetValue 伪代码

1
2
345
6
7
8
9
10
11
1213
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]]
函数会返回对象属性的值,该函数同时会考虑原型链上的继承属性。

综上,关于

this
的取值:

  • 函数中
    this
    的取值由调用者提供,由函数的当前调用方式决定
  • 在函数调用括号
    ()
    的左边如果是一个
    Reference
    类型的值,那么
    this
    的值将被设置为该值的base属性的值
  • 所有其它情况(比如
    ()
    左边的值不是
    Reference
    类型),
    this
    都将被设置为
    null
    ,因为
    null
    没有任何意义,它们将会被隐式的转换成global object
1
2
345
6
7
8
9
10
11
1213
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// eg1
function foo() {
return this;
}
foo(); // global
// 对应的Reference值
var fooReference = {
base: global,
propertyName: 'foo'
};
// eg2
var foo = {
bar: function () {
return this;
}
};
foo.bar(); // foo
// 对应的Reference值
var fooBarReference = {
base: foo,
propertyName: 'bar'
};
// eg3
// 我们换一种形式调用foo.bar
var test = foo.bar;
test(); // global
// 因为对应的Reference值变成下面这样
var testReference = {
base: global,
propertyName: 'test'
};

在上面的demo里

  • eg1中调用
    foo()
    时,根据上面提到的原则,foo是一个变量,经过标识符解析会返回一个
    Reference
    type的值fooReference,this被设置成fooReference对象的base属性的值–
    global
  • 在eg2中调用
    foo.bar()
    时,bar作为foo的一个属性,经过属性读取(property access)会返回一个
    Reference
    类型的值fooBarReference,this被设置成base属性的值–
    foo
  • 在eg3中我们把foo.bar赋值给了test,test作为标识符,在调用test的时候产生了testReference,因此返回的就是global

现在理解了同一个函数,使用不同的方式调用,为什么返回值不同了。

来看两个问题,考虑下执行结果将是怎样的:

思考

1
2
345
6
7
8
9
10
11
1213
14
15
16
17
18
19
20
21
22
// 思考题一:
function foo () {
console.log(this);
}
foo(); // ?
foo.prototype.constructor(); // ?
// 思考题二:
function foo() {
console.log(this.bar);
}
var x = {bar: 10};
var y = {bar: 20};
x.test = foo;
y.test = foo;
x.test(); // ?
y.test(); // ?

Function call and non-Reference type

上面提到过,当函数调用

()
的左侧不是Reference类型值的时候,this将被设置为null,继而被隐式的转换成global。

non-Reference call

1
2
3
(function () {
alert(this); // null => global
})();

上面例子中

()
的左侧是一个函数对象,既不是标识符也不是属性访问,因此不会返回Reference值,this将被设置为null,继而被转为global。

再来看几个复杂点的例子:

1
2
345
6
7
8
9
10
11
12
var foo = {
bar: function () {
alert(this);
}
};
foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo
(foo.bar = foo.bar)(); // global?
(false || foo.bar)(); // global?
(foo.bar, foo.bar)(); // global?
  阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: