15032016

外文源网址:Understanding the "this" keyword in JavaScript.
许多人被JavaScript中的this关键字给困扰住了,我想混乱的根源来自人们理所当然地认为JavaScript中的this应该像Java中的this或Python中的self一样工作。尽管JavaScript的this有时有类似的效果,但它跟Java或其他语言中的this是完全不同的。尽管有点难理解,但它的原理并不神秘。事实上,this遵循一些简单的小规则,这篇文章就对这些规则作出了解释。
但首先,我想给出一些专业术语和一些JavaScript运行时环境的相关信息,希望能帮助你构建一个心智模型来更好的理解this。

执行上下文

每一行JavaScript代码都运行在一个“执行上下文”中。JavaScript运行时环境用一个栈维持着这些上下文,栈顶的一个就是当前正在运行的执行上下文。

有三类可执行代码:全局代码(global code)函数代码(function code)eval代码(eval code)。大概的说,全局代码是应用程序最顶层的代码,不被包含在任何方法中,函数代码是在函数(function)体中的代码,eval代码是被eval解释的全局代码。

this对象每次控制进入一个新的执行上下文时会被重新确定指向,直到控制切换到一个不同的上下文。this的值取决于两件事:被执行的代码的类型(global,function,eval)和调用代码的对象。

全局对象

所有的JavaScript运行时都有一个唯一的全局对象。他的属性包括内置的对象如Math和String,以及其他由主环境变量定义的属性。

在浏览器中,全局对象是window对象。在Node.js中,它就叫作“global object”。(我假定你将在浏览器中运行代码,然而,我所说的一切也同样适用于Node.js。)

确定this的值

第一条规则很简单:this指向全局对象在所有全局代码中。由于所有的程序都是由执行全局代码开始的,并且this在给定的执行上下文中会被修正,所以,默认的this指全局对象。

当控制进入一个新的执行上下文时发生了什么呢?只有三种this的值发生改变的情况:方法调用,new一个函数对象,函数被callapply调用。我将逐一解释。

方法调用

当我们通过点(例obj.foo())或者方括号(例obj["foo"])把一个方法作为一个对象的属性来调用时,this将指向方法体的父对象:

var counter = {
  val: 0,
  increment: function () {
    this.val += 1;
  }
};
counter.increment();
console.log(counter.val); // 1
counter['increment']();
console.log(counter.val); // 2

这是第二条规则:函数被当作父对象的属性来调用时,在函数代码中的this指向函数的父对象。
注意,如果我们直接调用相同的方法,即,不作为父对象的属性,this将不会指向counter对象:

var inc = counter.increment;
inc();
console.log(counter.val); // 2
console.log(val); // NaN

当inc被调用时,这里的this不会改变,所以它还是指向全局对象。
当inc执行

this.val += 1;

它等效于执行:

window.val += 1;

window.val是undefined,且undefined加1得到NaN。

new 运算符

任何JavaScript函数都可以通过new运算符当成构造函数使用。new运算符创建一个新对象并且设置函数中的this指向调用函数的新对象。举个栗子:

function F (v) {
  this.val = v;
}
var f = new F("Woohoo!");
console.log(f.val); // Woohoo!
console.log(val); // ReferenceError

这就是我们的第三条规则:在用new运算符调用的函数代码中的this指向新创建的对象。
注意F没有任何特殊。如果不通过new调用,this将指向全局对象:

var f = F("Oops!");
console.log(f.val); // undefined
console.log(val); // Oops!

在这个例子中,F("Oops!")是一个通常调用,this没有指向新的对象,因为没有用new创建新的对象,this继续指向全局对象。

Call & Apply

所有JavaScript函数都有两个方法,callapply,让你能够调用函数并且清楚的设置this指向的对象。apply函数有两个参数:一个是this指向的对象,一个(可选)传进函数的参数的数组:

var add = function (x, y) {
      this.val = x + y;
    },
    obj = {
      val: 0
    };
add.apply(obj, [2, 8]);
console.log(obj.val); // 10

callapply是完全一样的,只不过要逐个的传递参数,而不是用一个数组:

add.call(obj, 2, 8);
console.log(obj.val); // 10

这是第四条规则:当使用callapply调用函数时,函数代码中的this被设置为callapply中的第一个参数。

总结
  1. 默认的,this指向全局对象
  2. 当一个函数被作为一个父对象的属性调用时,函数中的this指向父对象
  3. 当一个函数被new运算符调用时,函数中的this指向新创建的对象
  4. 当使用call或apply调用函数时,函数代码中的this被设置为callapply中的第一个参数。如果第一个参数是null或不是个对象,this指向全局对象。
补充:eval打破上面所有规则
var obj = {
  val: 0,
  func: function() { 
    eval("console.log(this.val)");
  }
};
obj.func(); // 0
eval.call({val: 0}, "console.log(this.val)"); // depends on browser
 
延伸阅读
  1. 两个关于日期时间的JavaScript方法
  2. 几个JavaScript闭包的例子
  3. 理解JavaScript的闭包
  4. JavaScript学习笔记之Dom知识点总结
  5. JavaScript学习笔记之数组对象知识点总结
  6. 王子墨JavaScript思维导图(附下载地址)
  7. 45个实用的JavaScript技巧、窍门和最佳实践
 
发布观点
加载更多...
即将启用新域名 smohan.im  smohan.net将作为静态存储域名使用,请关注smohan.im动态