`
shfzhzhr
  • 浏览: 69524 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Javascript中this的取值

    博客分类:
  • Web
 
阅读更多

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
3
4
5
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
3
4
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
3
4
5
6
7
8
9
10
11
12
13
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
3
4
5
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
3
4
var ReferenceType = {
    base: <base object>,
    propertyName: <property name>
};

根据规范,仅在以下两种情况时会返回Reference类型的值:

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

Reference

1
2
3
4
5
6
7
8
9
10
11
12
13
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
3
4
5
6
7
8
9
10
11
12
13
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
3
4
5
6
7
8
9
10
11
12
13
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
3
4
5
6
7
8
9
10
11
12
13
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
3
4
5
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?
 
分享到:
评论

相关推荐

    javascript的this关键字详解

    this 的定义 表示当前执行代码的环境对象  因此可将 this 的剖析分为“全局环境” 和 “函数环境” 两种类型的环境对象 全局环境 ...javascript 引擎会先在内存中生成 { name: ‘Tom’ } 对象,接着再

    JavaScript中的this基本问题实例小结

    本文实例讲述了JavaScript中的this基本问题.分享给大家供大家参考,具体如下: 在函数中 this 到底取何值,是在函数真正被调用执行的时候确定下来的,函数定义的时候确定不了。 执行上下文环境 :  **定义**:执行...

    Javascript浅谈之this

    在Javascript中,this的取值取决于调用的模式。调用模式一共有四种:方法调用模式、函数调用模式、构造器调用模式和apply调用模式。 调用模式方法调用模式当一个函数被保存为对象的一个属性时,我们称它为一个方法。...

    JavaScript箭头函数中的this详解

    箭头函数极大地简化了this的取值规则。 普通函数与箭头函数 普通函数指的是用function定义的函数: var hello = function () { console.log("Hello, Fundebug!"); } 箭头函数指的是用=&gt;定义的函数: var hello...

    浅谈javascript中的call、apply、bind

    在JavaScript中,call、apply和bind 是Function对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向,从而可以达到`接花移木`的效果。本文将对这三个方法进行详细的讲解,并列出几个经典应用场景。  ...

    理解javascript封装

    在JavaScript中,并没有显示的声明私有成员的关键字等。所以要想实现封装/信息隐藏就需要从另外的思路出发。我们可以使用闭包的概念来创建只允许从对象内部访问的方法和属性,来达到封装的要求。 基本方式 一般来说...

    JavaScript 私有成员分析

    对象 JavaScript操作都是关于对象的。数组(Array)是对象,函数(Function)是对象。Object(类型)是对象。那么什么是对象呢?对象就是“名称-值”对(name-value)。名称是字符串,值可以是字符串、数值、布尔值或对象...

    js使用小技巧

    Javascript小技巧一箩筐 事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElement.setCapture(); event.srcElement.releaseCapture(); 事件按键 event.keyCode ...

    ExtAspNet_v2.3.2_dll

    -修正extjs最新版本(v3.2.2)中的一个bug,如果下拉列表中存在两个相同的Text,则SelectedValue返回值永远是第一个Text的值(feedback:ben.zhou)。 -应用补丁#6593, #6621(feedback:vbelyaev)。 +修正IE7下Grid分页...

    出现问题a is defined高手帮忙

    if (this.globals.options.backButtonEnabled) this.backbuttonclick_()}; DragZoomControl.prototype.initButton_ = function(buttonContainerDiv) { var G = this.globals; var buttonDiv = ...

    ExtAspNet v2.2.1 (2009-4-1) 值得一看

    ExtAspNet v2.2.1 ExtAspNet是一组专业的Asp.net控件库,拥有原生的AJAX支持和丰富的UI效果, 目标是创建没有JavaScript,没有... -在Page_Load中设置了哪些需要在AJAX中更新的Asp.net控件会在回发时保持状态,可以...

    工程硕士学位论文 基于Android+HTML5的移动Web项目高效开发探究

    Chrome Frame 会把最新版的Chrome Webkit 内核和JavaScript 引擎注入到IE中, IE浏览器将获得Chrome的性能和功能 目录 摘要 I ABSTRACT II 专业名词清单 III 第一章 绪论 1 1.1 研究背景与意义 1 1.2国内外相关...

    freemarker总结

    使用import指令导入库到模板中,Freemarker会为导入的库创建新的名字空间,并可以通过import指令中指定的散列变量访问库中的变量: ${my.mail} ${mail} 输出结果: &lt;p&gt;Copyright (C) 1999-2002 ...

Global site tag (gtag.js) - Google Analytics