Javascript中this的值

在 javascript 中,任何函数中都可以出现this,但是它的值是在运行时决定的。说简单一点儿就是this的值取决与你如何调用这个函数,而不是函数定义所在的位置。另外在函数调用的时候可以使用apply/call指定this这样就实现了代码的复用。大多数javascript 的内置函数设计的尽量通用。

1.一般函数调用

在一个普通的函数中,this的值在浏览器中是window在nodejs中是global(后面不做区分,假设在浏览器中执行)。在最外层的作用域中thiswindow。例如

1
2
3
4
5
6
7
console.log(this); //输出 window
func(); //输出为 window

function func()
{
    console.info(this); 
}

可是这样存在一个问题,如果一个函数是构造函数,一般在这个函数中是会给this添加属性的。谁也能不能保证不犯错误,有时候当我们想把函数作为构造函数的时候,可是却写成了一般函数调用的形式,这个时候这一段代码就是给window添加属性,此时程序不会报错,但是明明又不是我们想要的(javascript的坑不止这一个)。所以为了让 javascript 变得更好,就提出了一种严格模式,在某一块代码的一开始写一句"use strict",对于不支持的浏览器来说这个只是一个字符串。但是对于支持严格模式的浏览器来说,会进入严格模式。在严格模式下,一般的函数调用中 this 的值是 undefined

1
2
3
4
5
6
7
func(); //输出为 undefined

function func()
{
    "use strict"
    console.info(this); 
}

2.作为对象的方法

如果函数作为对象的方法,也就是说调用方法是对象.方法(),那么这个时候所执行的函数中this就是这个对象的引用。

1
2
3
4
5
var obj={};
function func(){ console.info(this) }
obj.f = func;
obj.f(); //输出 obj
func(); //输出 window

尝试一下代码的输出:

1
2
3
4
5
6
7
8
function func() {console.info(this)};
var obj={};
obj.f=func;

obj.f();    // obj
(obj.f)();  // obj
(a=obj.f)(); // window
(0,obj.f)(); //  window

前两个是等价的(. 的优先级比函数调用高)。其实,.运算符的返回值不是obj.f中存储的值,而是一个包含obj.f信息的“对象”(这个只是 javascript内部实现时使用的,用 javascript 代码并不可访问)。在ECMAScript 5.1 标准中对这个返回值的描述是:

Retrun a value of type Reference whose base value is baseValue and whose referenced name is propertypname, and whose strict mode flag is strict.

本例中 baseValueobjpropertypname是字符串类型的属性名"f",后面那个strict是是否处于严格模式的标记。所以说这段话的意思就是obj.f的返回值是一个 baseValueobj, propertypname"f"的“对象”。而this的值其实是取上述返回值的 baseValue ,因此,在函数中thisobj。对于第三个,首先a=obj.f是一个赋值表达式,虽然 obj.f的返回值是一个包含obj.f信息的“对象”,但是赋值表达式的返回值是=右边表达式的返回值“求值”(GetValue),把“对象”中的值取出来,所以a=obj.f的返回值就是一个函数。然后再对这个函数进行调用调用,显然这个返回值的 baseValue 是没有定义的。而函数调用初始话作用域的时候,如果this为空值,那么就会让this等于window(非严格模式)。一般函数调用thiswindow的原因也是如此– baseValue 为空。最后一个与第三个相同,只不过是赋值表达式换成了逗号表达式。

1
2
3
4
5
6
7
8
function fn() { console.info(this); }
function fnstrict() { 
    'use strict'
    console.info(this); 
}
fnstrict.call(undefined); // undefined
fn.call(undefined); // window
fn.call(null);      // window

3. 构造函数(关键字new)

在new表达式中的 this 反而简单,统一规定为一个新创建的以这个函数为原型的对象。

1
2
function fn(){ console.info(this instanceof fn) };
var obj = new fn(); //输出 true

顺便说一下,new表达式的返回值:如果这个函数返回一个对象,那么new表达式的返回值是这个对象,否则返回值是新创建的这个对象。

4. 调用时指定this

也很简单,在调用函数时可以显示的指明用哪个值作为this,所以这仅仅是一个语法问题。javascript给出的方式就是apply/call这两个函数只是在参数的形式上有些区别(这个区别不是本文的主要内容在此不讨论)。call函数的语法是func.call(this的值,可选参数1,...),其中可选参数会传递给func

1
2
3
function fn(arg){ console.info(this) };
fn.call("thisvalue"); 
// 输出 “thisvalue”, 实际为 toObject("thisvalue"),把这个字符串转换成对象

这样做的好处就是可以实现代码复用,javascript 中的内置函数后设计的尽可能通用,当一个对象满足某些条件的时候就可以用作为这个函数的this。比如一个对象只要有length属性就可以作为Array.prototype中的函数的this,参考 《Array.prototype中函数的定义》

5. 上述说的都不对

还有一种特殊情况,javascript可以指定一个固定的值作为this,无论如何调用这个函数this的值不变。产生这种函数的方式就是调用一个普通函数的bind方法,bind不仅可以指定this还可以指定函数调用的参数,其语法是func.bind(this的值,可选参数1,....),其中可选参数是要绑定的参数,其余参数在调用的时候再给出。

1
2
3
4
5
function func() { console.info(this) }
bfunc = func.bind("bind this");
bfunc(); // bind this
// 原函数还是按照前面的方式确定 this
func();  // window

一种常用,但是代码怎么写都有点儿乱的绑定(ES2015可以解决),代码功能不用关心,只要知道如果this没有正确的值this.doSomething调用就会失败就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
var PageHandler = { 
    id: "123456", 
    init: function() {
        document.addEventListener("click", 
            (function(event) {
                this.doSomething(event.type); }
            ).bind(this), false); 
    }, 
    doSomething:function(type) { 
        console.log("Handling " + type  + " for " + this.id); 
    } 
}; 
PageHandler.init();

问题是上述代码为什么可以正常工作:因为bind后面的this是在init中的,而init的调用是PageHandler.init()。故这个this是PageHandler,所以事件监听函数绑定的this就是PageHandler。

6. DOM事件回调函数

作为浏览器中的javascript中还有一种函数调用,那就是事件回调函数。在事件回调函数中this 的值是当前触发该事件的DOM对象(部分浏览器只有使用addEventListener添加的事件才遵循此规定,说的就是IE)。

1
2
3
4
5
6
7
8
9
function evtHandle(e)
{
    console.log(this === e.currentTarget); // 总为true
    console.log(this === e.target);        // 只有在目标阶段为true
}
var elements = doucument.getElementsByTagName('*');
for(var i=0;i<elements.length; i++){
    elements[i].addEventListener('click',evtHandle,false);
}

事件内联代码

DOM元素中添加的事件内联代码中的this就是当前的这个DOM元素。

1
2
3
<button onclick="console.log(this)">
    show this
</button>

上面代码中的this是它所在的DOM元素(button)。但是需要注意的是只有最外层作用域的this是所在的DOM元素,内层作用域代码中this的确定同前面几条。还是那句话,函数中this的值主要看函数是怎么被调用的,函数(内层作用域代码)是如何调用的是我们自己的代码写的,所以我们可以确定this的值,只是初始(最外层)的作用域中的this我们不知道,需要统一规定。

1
2
3
<button onclick="console.log((function (){return this;})());">
    show inner this
</button>

上述代码中的匿名函数就是一次普通的函数调用,所以this的值是window(非严格模式下)。

万恶的IE

在IE下,用 attachEvent 添加的事件处理函数其中的this规定为 window

1
2
3
4
5
// IE 下运行
var el = document.getElementById("id");
el.attachEvent('onclick',function(){ 
    console.info(this);   // window
}); 

作为总结

在 javascript 中,this的值取决与你如何调用这个函数,而不是函数定义所在的位置。函数的调用分为6种情况,也就是this的来源有6种:

  1. 普通函数调用,this为全局对象或是undefined
  2. 作为对象的方法,this为那个对象
  3. new 表达式,this为以该函数为原型的新创建的对象
  4. 使用 apply/call指定 this
  5. bind绑定固定的this
  6. 事件处理函数中的this是当前的触发事件的DOM元素(event.currentTarget)
    IE attachEvent添加的事件处理函数中thiswindow

最后提一个问题 下面代码输出什么?

1
2
obj = { go: function() { console.info(this) } };
(0 || obj.go)() 

参考:

  1. ES5.1 属性运算符
  2. ES5.1 函数调用
  3. http://javascript.info/tutorial/this
  4. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this