同其它语言相比,JavaScript中函数的this关键字略有不同。This在strict mode和non-strict下也有区别。
大多数情况下,this的值是由函数如何被调用决定的。This在strict mode下不能在运行期间赋值,每次函数调用可能也不尽相同。
-
Global context
在全局执行的上下文(不在任何函数体之内),this指向global对象。
console.log(this.document === document); // true // In web browsers, the window object is also the global object: console.log(this === window); // true this.a = 37; console.log(window.a); // 37
-
函数上下文
函数体内this的值取决于函数如何调用。
-
简单调用
function f1() { return this; } f1() === window; // global object
在客户端的JavaScript中,window对象是浏览器执行的代码的global object。由于不是在strict mode,this缺省是window对象。
function f2() { "use strict"; // 使用 strict mode return this; } console.info(f2() === undefined); // true console.info(window.f2() === window); // true
由于f2使用了strict mode, 直接调用f2,this为undefined,因为未提供调用的对象。
-
作为对象的方法
当作为一个对象的方法调用时,this指向调用方法的对象。
var o = { prop: 37, f: function() { return this.prop; }}; console.log(o.f()); // logs 37
请注意调用结果不受函数如何以及在何处定义的影响。
例如,上述代码中f函数是作为对象o的方法定义的,我们同样可以先定义f函数然后将其赋值给对象o的属性。var o = {prop: 37}; function independent() { return this.prop; } o.f = independent; console.log(o.f()); // logs 37
This仅受其直接引用的影响。下例中g函数是o对象的b属性的一个属性,当通过o.b.g()调用时,在g函数的方法体内,this指向o.b而跟对象o无关系。
o.b = {g: independent, prop: 42}; console.log(o.b.g()); // logs 42
嵌套函数(nested function)
若嵌套函数作为对象的方法调用,如前所述,嵌套函数体内的this指向调用它的对象。var obj = { m: function() { // Method m of the object. this.f = f; function f() { // A nested function f console.log(this === obj); } } }; obj.m(); obj.f(); // "true": nested f is invoked as method of obj
若作为函数(function)直接调用,那么this在strict mode下为undefined,在non-strict mode下为global object,一个常见的错误是此时通过this获取outer function的执行上下文。若想获得outer function的上下文,需要在outer function内定义变量存储,这样所定义的变量在nested function可见。
var o = { m: function() { // Method m of the object. var self = this; // Save the this value in a variable. console.log(this === o); // Prints "true": this is the object o. f(); // Now call the helper function f(). function f() { // A nested function f console.log(this === o); // "false": this is global or undefined console.log(self === o); // "true": self is the outer this value. } } }; o.m();
在prototype chain上
JavaScript对象原型链上继承的方法中,this指向调用该方法的对象。
var o = {f:function(){ return this.a + this.b; }}; var p = Object.create(o); p.a = 1; p.b = 4; console.log(p.f()); // 5
作为 getter or setter
Getter和setter中this指向当前访问的属性所属的对象。function modulus(){ return Math.sqrt(this.re * this.re + this.im * this.im);} var o = { re: 1, im: -1, get phase(){ return Math.atan2(this.im, this.re); }}; Object.defineProperty(o, 'modulus', {get: modulus, enumerable:true, configurable:true}); console.log(o.phase, o.modulus); // logs -0.78 1.4142
-
作为构造器
当函数跟在new之后作为构造器时,this指向创建的function对象。
注: 构造器通常返回新对象的引用,但也可以通过return语句返回其它的对象function C() { this.a = 37; } var o = new C(); console.log(o.a); // logs 37 function C2() { this.a = 37; return {a:38}; } o = new C2(); console.log(o.a); // logs 38
函数C2返回了对象{a:38},this所指向的新创建的对象并未被返回,因此C2的第一条语句this.a = 37虽然执行过,但即便删除也不影响构造器函数的调用。
call和apply
Call和apply调用的this指向第一个参数即调用的对象。function add(c, d) { return this.a + this.b + c + d; } var o = {a:1, b:3}; add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
bind方法
ECMAScript 5引入了Function.prototype.bind. 调用f.bind(someObject)会创建一个与f拥有相同方法体和作用域的函数,该函数成为someObject的属性。function f() { return this.a; } var g = f.bind({a:"azerty"}); console.log(g()); // azerty var o = {a:37, f:f, g:g}; console.log(o.f(), o.g()); // 37, azerty
-
作为DOM event handler
先来看一段代码,定义了三个按钮btn1, btn2, btn3,分别指定了onclick事件setBtn1ToRed, setBtn2ToYellow, setBtn3ToBlue试图将按钮文字置为红黄蓝。
<input type="button" id="btn1" value="btn1" onclick="setBtn1ToRed();alert('btn1 is clicked');" /> <input type="button" id="btn2" value="btn2" /> <input type="button" id="btn3" value="btn3" onclick="setBtn3ToBlue(this, event)" /> <script type="text/javascript"> function setBtn1ToRed() { console.info(this == window); // true //this.style.color = 'red'; } function setBtn2ToYellow() { console.info(this.id); // btn2 this.style.color = 'yellow'; } document.getElementById("btn2").onclick = setBtn2ToYellow; function setBtn3ToBlue(target, event) { console.info(target.id); //btn3 console.info(event instanceof MouseEvent); //true target.style.color = 'blue'; } setBtn2ToYellow.newAttribute = "newValue"; document.getElementById("btn2").onclick.newAttribute == "newValue"; // true </script>
在F12调试工具中分别查看三个按钮的onclick属性
可以看到通过inline event注册的第一个按钮的onclick仅仅是将οnclick="setBtn1ToRed();alert('btn1 is clicked');"中的属性值作为方法体;而通过
element.onclick = setBtn2ToYellow;
我们将element的onclick属性设置为setBtn2ToYellow
的一个引用,点击element触发函数执行时,this指向element。图示如下
为了更进一步验证btn2的onlick是setBtn2ToYellow
的引用,我们通过为setBtn2ToYellow
新增一个属性,然后检查btn2的onclick属性是否也拥有了这个新增属性setBtn2ToYellow.newAttribute = "newValue"; document.getElementById("btn2").onclick.newAttribute == "newValue"; // true
若希望inline event registration获得目标DOM元素或事件,可以通过btn3的方式onclick="setBtn3ToBlue(this,
event)"
获取目标元素和事件。
注意onclick="setBtn3ToBlue(this, event)"
中的this为input元素,查看调用栈可见