"this"关键字是如何工作的,以及何时应该使用它?

1489

我希望找到一个清晰的解释,“this”关键字的作用是什么,以及如何正确使用它。

它似乎表现得很奇怪,我不完全明白为什么。

this如何工作,应该在什么情况下使用?


22个回答

1489

this是JavaScript中的一个关键字,它是执行上下文的属性。它在函数和构造函数中的主要用途。

this的规则非常简单(如果你遵循最佳实践)。

this在规范中的技术描述

ECMAScript标准通过抽象操作(缩写为AO) ResolveThisBinding来定义this:

[AO] ResolveThisBinding(…)使用运行时执行上下文的LexicalEnvironment确定关键字this的绑定。[步骤]:

  1. envRec成为GetThisEnvironment()。
  2. 返回?envRec.GetThisBinding()。

全局环境记录, 模块环境记录函数环境记录各自都有自己的GetThisBinding方法。

GetThisEnvironment AO查找当前运行执行上下文的词法环境,并找到最近的具有this绑定(即HasThisBinding返回true)的祖先环境记录(通过迭代访问它们的[[OuterEnv]]属性)。此过程以三种环境记录类型之一结束。

this的值通常取决于代码是否处于严格模式

GetThisBinding的返回值反映了当前执行上下文中this的值,因此每当建立新的执行上下文时,this就会解析为一个不同的值。这也可能发生在修改当前执行上下文时。以下子节列出了可以发生这种情况的五种情况。

您可以将代码示例放入AST explorer中,以遵循规范详细信息。

1.脚本中的全局执行上下文

这是在顶层评估的脚本代码,例如直接在<script>内部:

<script>
// Global context
console.log(this); // Logs global object.

setTimeout(function(){
  console.log("Not global context");
});
</script>

在脚本的初始全局执行上下文中,评估this会导致GetThisBinding执行以下步骤:

来自全局环境记录envRec的GetThisBinding具体方法[...][执行如下]:

  1. 返回envRec.[[GlobalThisValue]]。
全局环境记录的[[GlobalThisValue]]属性始终设置为由InitializeHostDefinedRealm步骤产生的主机定义的全局对象,可以通过globalThis(Web上的window,Node.js上的globalMDN文档)访问。

2. 模块中的全局执行上下文

ECMAScript 2015引入了模块。
这适用于模块,例如直接位于<script type="module">内,而不是简单的<script>
在模块的初始全局执行上下文中,评估 this 会导致 GetThisBinding 执行以下步骤:

模块环境记录的 GetThisBinding 具体方法 [...] [执行如下操作]:

  1. 返回 undefined
在模块中,this 的值始终为 undefined 在全局上下文中。模块隐式地处于严格模式

3. 进入eval代码

有两种类型的 eval 调用: directindirect。自 ECMAScript 第5版以来存在此区别。
  • 直接的eval调用通常看起来像eval();或者(eval)();(或者((eval))();等)。1只有当调用表达式符合一种狭窄的模式时才是直接的。2
  • 间接的eval调用涉及以任何其他方式调用函数引用eval。它可以是eval?.()(, eval)()window.eval()eval.call(,)等。给定const aliasEval1 = eval; window.aliasEval2 = eval;,它也可以是aliasEval1()aliasEval2()。另外,如果给定const originalEval = eval; window.eval = (x) => originalEval(x);,调用eval()也将是间接的。

请参考chuckj针对“(1, eval)('this') vs eval('this') in JavaScript?”的回答Dmitry Soshnikov的ECMA-262-5详解-第2章:严格模式已存档),了解何时可以使用间接的eval()调用。

PerformEval执行eval代码。它创建一个新的声明性环境记录作为其词法环境,在那里GetThisEnvironment从中获取this值。

然后,如果this出现在eval代码中,则会调用由GetThisEnvironment找到的环境记录的GetThisBinding方法,并返回其值。

创建的声明性环境记录取决于eval调用是直接还是间接的:

这意味着:

  • 在直接eval中,this值不会改变;它取自调用eval的词法作用域。
  • 在间接eval中,this值是全局对象(globalThis)。

new Function呢? — new Function类似于eval,但它不会立即调用代码;它创建一个函数。这里没有应用this绑定,除非函数被调用,在下一小节中将会解释。

4. 输入function代码

输入函数代码是在调用函数时发生的。

有四种语法类别可以调用函数。

实际的函数调用发生在Call AO,它是根据上下文确定的thisValue调用的;这个参数在一长串调用相关的调用中传递。Call调用函数的[[Call]]内部插槽。这将调用PrepareForOrdinaryCall, 创建一个新的function Environment Record

函数环境记录是一个声明性环境记录,用于表示函数的顶级范围,并提供this绑定(如果函数不是一个ArrowFunction)。如果一个函数不是ArrowFunction函数并且引用了super,它的函数环境记录还包含用于在函数内部执行super方法调用的状态。

此外,函数环境记录中还有[[ThisValue]]字段:

这是用于此函数调用的this值。

NewFunctionEnvironment 调用还设置了函数环境的 [[ThisBindingStatus]] 属性。

[[Call]] 也调用 OrdinaryCallBindThis,其中适当的 thisArgument 基于:

  • 原始引用,
  • 函数的种类, 和
  • 代码是否在 strict mode 中。

一旦确定,最终对新创建的函数环境记录的 BindThisValue 方法进行的调用实际上将 [[ThisValue]] 字段设置为 thisArgument

最后,这个字段就是 函数环境记录的 GetThisBinding AO 从中获取 this 的值的地方:

函数环境记录中的GetThisBinding具体方法envRec[...]执行以下操作:

[...]
3. 返回envRec.[[ThisValue]]。

再次强调,确定this值的方式取决于许多因素;这只是一个概述。在了解这个技术背景后,让我们来看看所有具体的例子。

箭头函数

当评估箭头函数时,函数对象的[[ThisMode]]内部插槽设置为"词法"OrdinaryFunctionCreate中。

OrdinaryCallBindThis中,它接受一个函数F

  1. thisMode成为F。[[ThisMode]]。
  2. 如果thisModelexical,则返回NormalCompletion(undefined)。 [...]

这意味着绑定this的其余算法被跳过。箭头函数不会绑定自己的this值。

因此,箭头函数中的this是什么?回顾ResolveThisBindingGetThisEnvironmentHasThisBinding方法明确返回false

函数环境记录的HasThisBinding具体方法envRec [...] [这样做]:

  1. 如果envRec。[[ThisBindingStatus]]为lexical,则返回false;否则,返回true
因此,外部环境将被迭代查找。该过程将在具有this绑定的三个环境之一中结束。
这仅意味着在箭头函数体中,this来自箭头函数的词法范围,或者换句话说(来自Arrow function vs function declaration / expressions: Are they equivalent / exchangeable?):

箭头函数没有自己的this[...]绑定。相反,[该标识符将]像其他变量一样在词法范围内解析。这意味着在箭头函数内部,this[指的是]箭头函数所在环境中定义this的值(即在箭头函数“外部”)。

函数属性

在普通函数(function方法)中,this函数被调用的方式确定。

这就是这些“语法变体”有用的地方。

考虑包含一个函数的对象:

const refObj = {
    func: function(){
      console.log(this);
    }
  };

或者:

const refObj = {
    func(){
      console.log(this);
    }
  };

在下列任何函数调用中,func内部的this值将为refObj1
  • refObj.func()
  • refObj["func"]()
  • refObj?.func()
  • refObj.func?.()
  • refObj.func``
如果被调用的函数在语法上是基本对象的属性,则该基本对象将成为调用的“引用”,通常情况下,它将是this的值。这是由上面链接的评估步骤解释的; 例如,在refObj.func()(或refObj["func"]())中,CallMemberExpression是整个表达式refObj.func(),其中包括MemberExpressionrefObj.funcArguments ()
但是,refObj.funcrefObj也各自扮演三种角色:
  • 它们都是表达式,
  • 它们都是引用,以及
  • 它们都是值。
作为refObj.func是可调用的函数对象;对应的引用用于确定this绑定。
可选链和标记模板示例的工作方式非常相似:基本上,引用是在?.()``()之前的所有内容。

EvaluateCall使用IsPropertyReference来确定它是否是对象的属性,从语法上讲。 它试图获取引用的[[Base]]属性(例如,当应用于refObj.func时为refObj;或者当应用于foo.bar.baz时为foo.bar)。 如果它被写成属性,则GetThisValue将获取此[[Base]]属性并将其用作this值。

注意:Getters / Setters与方法相同,关于this的处理方式。简单属性不会影响执行上下文,例如在此处,this位于全局范围内:

const o = {
    a: 1,
    b: this.a, // Is `globalThis.a`.
    [this.a]: 2 // Refers to `globalThis.a`.
  };

没有基础引用、严格模式和with的调用

没有基础引用的调用通常是一个不作为属性调用的函数。例如:

func(); // As opposed to `refObj.func();`.

这也会发生在传递或赋值方法,或使用逗号运算符时。这就是引用记录和值之间的区别相关的地方。
注意函数j:根据规范,您会注意到j只能返回函数对象(值)本身,而不是引用记录。因此,基础引用refObj丢失了。
const g = (f) => f(); // No base ref.
const h = refObj.func;
const j = () => refObj.func;

g(refObj.func);
h(); // No base ref.
j()(); // No base ref.
(0, refObj.func)(); // Another common pattern to remove the base ref.

EvaluateCall在这里使用undefined作为thisValue调用Call,这会影响OrdinaryCallBindThisF: 函数对象; thisArgument: 传递给CallthisValue):

  1. thisModeF.[[ThisMode]]。

[...]

  1. 如果thisModestrict,则让thisValuethisArgument
  2. 否则,
    1. 如果thisArgumentundefinednull,则
      1. globalEnvcalleeRealm.[[GlobalEnv]]。
      2. [...]
      3. thisValueglobalEnv.[[GlobalThisValue]]。
    2. 否则,
      1. thisValue为!ToObjectthisArgument)。
      2. 注意:ToObject会产生包装对象[...]。

[...]

注意:第5步在严格模式下将this的实际值设置为提供的thisArgument——在这种情况下为undefined。在“松散模式”下,未定义或null的thisArgument导致this成为全局this值。

如果IsPropertyReference返回< strong >false< /strong >,那么EvaluateCall会执行以下步骤:
  1. 让< em >refEnv< /em >成为< em >ref< /em >.[[Base]]。
  2. 断言:< em >refEnv< /em >是环境记录。
  3. 让< em >thisValue< /em >成为< em >refEnv< /em >.WithBaseObject()。
这就是未定义的< em >thisValue< /em >可能来自的地方:< em >refEnv< /em >.WithBaseObject()总是< strong >undefined< /strong >,除了在with< /code>语句中。在这种情况下,< em >thisValue< /em >将成为绑定对象。
还有Symbol.unscopables< /code>MDN上的文档)来控制< code >with< /code >绑定行为。
总结一下,到目前为止:
function f1(){
  console.log(this);
}

function f2(){
  console.log(this);
}

function f3(){
  console.log(this);
}

const o = {
    f1,
    f2,
    [Symbol.unscopables]: {
      f2: true
    }
  };

f1(); // Logs `globalThis`.

with(o){
  f1(); // Logs `o`.
  f2(); // `f2` is unscopable, so this logs `globalThis`.
  f3(); // `f3` is not on `o`, so this logs `globalThis`.
}

并且:

"use strict";

function f(){
  console.log(this);
}

f(); // Logs `undefined`.

// `with` statements are not allowed in strict-mode code.

请注意,在评估this时,普通函数定义的位置并不重要。

.call, .apply, .bind, thisArg和原始值

OrdinaryCallBindThis的第5步与第6.2步(规范中的6.b)相结合的另一个结果是,在“松散”模式下,原始的this值仅被强制转换为对象。
为了检查这一点,让我们介绍另一个源this值:覆盖this绑定的三个方法:4
  • Function.prototype.apply(thisArg, argArray)
  • Function.prototype. {call, bind} (thisArg, ...args)

.bind创建一个绑定函数,其this绑定设置为thisArg,并且不能再次更改。.call.apply立即调用该函数,并将this绑定设置为thisArg

.call.apply直接映射到Call,使用指定的thisArg.bind使用BoundFunctionCreate创建一个绑定函数。这些函数有自己的[[Call]] method,该方法查找函数对象的[[BoundThis]]内部插槽。

设置自定义this值的示例:

function f(){
  console.log(this);
}

const myObj = {},
  g = f.bind(myObj),
  h = (m) => m();

// All of these log `myObj`.
g();
f.bind(myObj)();
f.call(myObj);
h(g);

对于对象,在严格模式和非严格模式下是相同的。

现在,请尝试提供一个原始值:

function f(){
  console.log(this);
}

const myString = "s",
  g = f.bind(myString);

g();              // Logs `String { "s" }`.
f.call(myString); // Logs `String { "s" }`.

在非严格模式下,原始值会被强制转换为其包装对象形式。这与调用 Object("s")new String("s") 时获得的对象相同。在严格模式下,您可以使用原始值:
"use strict";

function f(){
  console.log(this);
}

const myString = "s",
  g = f.bind(myString);

g();              // Logs `"s"`.
f.call(myString); // Logs `"s"`.

库使用这些方法,例如jQuery将this设置为所选的DOM元素:

$("button").click(function(){
  console.log(this); // Logs the clicked button.
});

构造函数、new

使用 new 运算符调用函数作为构造函数时,EvaluateNew 调用 Construct,后者调用 [[Construct]] 方法。如果该函数是基础构造函数(即不是 class extends{}),则将 thisArgument 设置为从构造函数的原型创建的新对象。在构造函数中设置的属性将最终出现在生成的实例对象上的 this 上。除非您显式返回自己的非原始值,否则会隐式返回 this

class 是 ECMAScript 2015 中引入的一种创建构造函数的新方法。

function Old(a){
  this.p = a;
}

const o = new Old(1);

console.log(o);  // Logs `Old { p: 1 }`.

class New{
  constructor(a){
    this.p = a;
  }
}

const n = new New(1);

console.log(n); // Logs `New { p: 1 }`.

类定义隐式地处于严格模式中:

class A{
  m1(){
    return this;
  }
  m2(){
    const m1 = this.m1;
    
    console.log(m1());
  }
}

new A().m2(); // Logs `undefined`.

super

new关键字在派生类的使用上有例外,即class extends{},如上所述。派生类在调用时不会立即设置它们的this值;只有当通过一系列super调用到达基类时才会这样做(在没有自己的constructor的情况下隐式发生)。在调用super之前使用this是不允许的。

调用super会使用调用的词法作用域(函数环境记录)的this值调用超级构造函数。GetThisValue对于super调用有一个特殊规则。它使用BindThisValuethis设置为该环境记录。

class DerivedNew extends New{
  constructor(a, a2){
    // Using `this` before `super` results in a ReferenceError.
    super(a);
    this.p2 = a2;
  }
}

const n2 = new DerivedNew(1, 2);

console.log(n2); // Logs `DerivedNew { p: 1, p2: 2 }`.

5. 评估类字段

实例字段和静态字段在 ECMAScript 2022 中被引入。

当一个 class 被评估时,执行 ClassDefinitionEvaluation,修改 running execution context。对于每个 ClassElement

  • 如果字段是静态的,则 this 指的是类本身,
  • 如果字段不是静态的,则 this 指的是实例。

私有字段(例如 #x)和方法将添加到 PrivateEnvironment 中。

Static blocks 目前是一个 TC39 stage 3 proposal。静态块与静态字段和方法相同:其中的 this 指的是类本身。

请注意,在方法和 getter / setter 中,this 的工作方式与普通函数属性相同。

class Demo{
  a = this;
  b(){
    return this;
  }
  static c = this;
  static d(){
    return this;
  }
  // Getters, setters, private modifiers are also possible.
}

const demo = new Demo;

console.log(demo.a, demo.b()); // Both log `demo`.
console.log(Demo.c, Demo.d()); // Both log `Demo`.

1: (o.f)()相当于o.f(); (f)()相当于f()。这在这篇2ality文章存档)中有解释。特别看如何评估括号表达式

2: 它必须是一个MemberExpression,不能是属性,必须具有完全匹配的[[ReferencedName]] "eval",并且必须是%eval%内部对象。

3: 每当规范中说“让ref成为评估X的结果”,那么X是一个表达式,你需要找到它的评估步骤。例如,评估MemberExpressionCallExpression的结果是其中一种这些算法。其中一些会导致Reference Record

4: 还有其他几个本地和宿主方法允许提供this值,尤其是Array.prototype.mapArray.prototype.forEach等,它们接受thisArg作为第二个参数。任何人都可以制作自己的方法来更改this,例如(func, thisArg) => func.bind(thisArg)(func, thisArg) => func.call(thisArg)等。像往常一样,MDN提供了很好的文档。


只是为了好玩,用一些例子来测试你的理解

对于每个代码片段,请回答问题:“在标记行处this的值是什么?为什么?”

要显示答案,请单击灰色框。

  1. if(true){
      console.log(this); // What is `this` here?
    }
    

    globalThis. The marked line is evaluated in the initial global execution context.

  2. const obj = {};
    
    function myFun(){
      return { // What is `this` here?
        "is obj": this === obj,
        "is globalThis": this === globalThis
      };
    }
    
    obj.method = myFun;
    
    console.log(obj.method());
    
       

    obj. When calling a function as a property of an object, it is called with the this binding set to the base of the reference obj.method, i.e. obj.

  3. const obj = {
        myMethod: function(){
          return { // What is `this` here?
            "is obj": this === obj,
            "is globalThis": this === globalThis
          };
        }
      },
      myFun = obj.myMethod;
    
    console.log(myFun());
    
       

    globalThis. Since the function value myFun / obj.myMethod is not called off of an object, as a property, the this binding will be globalThis.

    This is different from Python, in which accessing a method (obj.myMethod) creates a bound method object.

  4. const obj = {
        myFun: () => ({ // What is `this` here?
          "is obj": this === obj,
          "is globalThis": this === globalThis
        })
      };
    
    console.log(obj.myFun());
    
       

    globalThis. Arrow functions don’t create their own this binding. The lexical scope is the same as the initial global scope, so this is globalThis.

  5. function myFun(){
      console.log(this); // What is `this` here?
    }
    
    const obj = {
        myMethod: function(){
          eval("myFun()");
        }
      };
    
    obj.myMethod();
    

    globalThis. When evaluating the direct eval call, this is obj. However, in the eval code, myFun is not called off of an object, so the this binding is set to the global object.

  6. function myFun() {
      // What is `this` here?
      return {
        "is obj": this === obj,
        "is globalThis": this === globalThis
      };
    }
    
    const obj = {};
    
    console.log(myFun.call(obj));
    
       

    obj. The line myFun.call(obj); is invoking the special built-in function Function.prototype.call, which accepts thisArg as the first argument.

  7. class MyCls{
      arrow = () => ({ // What is `this` here?
        "is MyCls": this === MyCls,
        "is globalThis": this === globalThis,
        "is instance": this instanceof MyCls
      });
    }
    
    console.log(new MyCls().arrow());
    
       

    It’s the instance of MyCls. Arrow functions don’t change the this binding, so it comes from lexical scope. Therefore, this is exactly the same as with the class fields mentioned above, like a = this;. Try changing it to static arrow. Do you get the result you expect?


1
另一个常见情况:EventHandlers被调用时,this设置为事件的currentTarget。这三个提案可以包含在未来中:绑定运算符::显式thisthis参数反射。DOM 0事件属性如onclick也值得注意:JS代码隐式地包装在一个document和一个点击元素的with作用域中,导致混淆this是具有该属性的元素。 - Sebastian Simon
2
@LRDPRDX 嗯,这个答案详细地讲解了this的每一个细节。但是在全局范围内,没有人真正需要thiswith已经被弃用,不鼓励使用eval,应该在任何地方都使用严格模式等等。所有剩下的就是obj.method()调用带有function或方法的method时,将obj作为this传递;func()调用func而不带任何this.bind.call.apply可以显式地绑定this;箭头函数不会得到this绑定。类:在静态事物中,this指的是类本身,在非静态事物中,指的是正在创建的实例。就是这样。 - Sebastian Simon

190

在 JavaScript 中,this 关键字与其他语言的行为不同。在面向对象的语言中,this 关键字引用类的当前实例。但在 JavaScript 中,this 的值由函数的调用上下文(context.function())和它被调用的位置决定。

1. 当在全局上下文中使用时

当你在全局上下文中使用 this 时,它会绑定到全局对象(在浏览器中是 window)。

document.write(this);  //[object Window]

当你在全局范围内定义的函数中使用this时,this仍然绑定到全局对象,因为该函数实际上是全局上下文的方法之一。

function f1()
{
   return this;
}
document.write(f1());  //[object Window]

上述代码将f1作为全局对象的方法定义,因此我们也可以在window对象上调用它,如下所示:

function f()
{
    return this;
}

document.write(window.f()); //[object Window]

2. 在对象方法中使用

当您在对象方法中使用this关键字时,this绑定到“直接”封闭对象。

var obj = {
    name: "obj",
    f: function () {
        return this + ":" + this.name;
    }
};
document.write(obj.f());  //[object Object]:obj

上面我用双引号括起来了单词“immediate”。这是为了表明,如果你将对象嵌套在另一个对象内部,那么this将绑定到直接的父级。

var obj = {
    name: "obj1",
    nestedobj: {
        name:"nestedobj",
        f: function () {
            return this + ":" + this.name;
        }
    }            
}

document.write(obj.nestedobj.f()); //[object Object]:nestedobj

即使您将函数显式添加到对象作为方法,它仍然遵循上述规则,即this仍然指向直接父对象。

var obj1 = {
    name: "obj1",
}

function returnName() {
    return this + ":" + this.name;
}

obj1.f = returnName; //add method to object
document.write(obj1.f()); //[object Object]:obj1

3. 当调用无上下文函数时

当您在没有任何上下文的情况下使用this来调用函数(即不在任何对象上),它将绑定到全局对象(浏览器中的window),即使该函数在对象内部定义。

var context = "global";

var obj = {  
    context: "object",
    method: function () {                  
        function f() {
            var context = "function";
            return this + ":" +this.context; 
        };
        return f(); //invoked without context
    }
};

document.write(obj.method()); //[object Window]:global 

使用函数尝试所有内容

我们也可以使用函数来尝试上面的点。但是有一些不同之处。

  • 在上面,我们使用对象字面量标记法向对象添加成员。我们可以使用this将成员添加到函数中来指定它们。
  • 对象字面量标记法创建一个对象实例,我们可以立即使用。对于函数,我们可能需要先使用new运算符创建其实例。
  • 此外,在对象字面量方法中,我们可以使用点运算符显式地向已定义的对象添加成员。这仅会添加到特定实例中。但我已将变量添加到函数原型中,以便在函数的所有实例中都能反映出来。

下面是我尝试了上面使用对象和this进行的所有操作,但首先创建了函数而不是直接编写对象。

/********************************************************************* 
  1. When you add variable to the function using this keyword, it 
     gets added to the function prototype, thus allowing all function 
     instances to have their own copy of the variables added.
*********************************************************************/
function functionDef()
{
    this.name = "ObjDefinition";
    this.getName = function(){                
        return this+":"+this.name;
    }
}        

obj1 = new functionDef();
document.write(obj1.getName() + "<br />"); //[object Object]:ObjDefinition   

/********************************************************************* 
   2. Members explicitly added to the function protorype also behave 
      as above: all function instances have their own copy of the 
      variable added.
*********************************************************************/
functionDef.prototype.version = 1;
functionDef.prototype.getVersion = function(){
    return "v"+this.version; //see how this.version refers to the
                             //version variable added through 
                             //prototype
}
document.write(obj1.getVersion() + "<br />"); //v1

/********************************************************************* 
   3. Illustrating that the function variables added by both above 
      ways have their own copies across function instances
*********************************************************************/
functionDef.prototype.incrementVersion = function(){
    this.version = this.version + 1;
}
var obj2 = new functionDef();
document.write(obj2.getVersion() + "<br />"); //v1

obj2.incrementVersion();      //incrementing version in obj2
                              //does not affect obj1 version

document.write(obj2.getVersion() + "<br />"); //v2
document.write(obj1.getVersion() + "<br />"); //v1

/********************************************************************* 
   4. `this` keyword refers to the immediate parent object. If you 
       nest the object through function prototype, then `this` inside 
       object refers to the nested object not the function instance
*********************************************************************/
functionDef.prototype.nestedObj = { name: 'nestedObj', 
                                    getName1 : function(){
                                        return this+":"+this.name;
                                    }                            
                                  };

document.write(obj2.nestedObj.getName1() + "<br />"); //[object Object]:nestedObj

/********************************************************************* 
   5. If the method is on an object's prototype chain, `this` refers 
      to the object the method was called on, as if the method was on 
      the object.
*********************************************************************/
var ProtoObj = { fun: function () { return this.a } };
var obj3 = Object.create(ProtoObj); //creating an object setting ProtoObj
                                    //as its prototype
obj3.a = 999;                       //adding instance member to obj3
document.write(obj3.fun()+"<br />");//999
                                    //calling obj3.fun() makes 
                                    //ProtoObj.fun() to access obj3.a as 
                                    //if fun() is defined on obj3

4. 在构造函数内使用.

当函数用作构造函数(即使用new关键字调用该函数)时,函数体内的this指向正在构造的新对象。

var myname = "global context";
function SimpleFun()
{
    this.myname = "simple function";
}

var obj1 = new SimpleFun(); //adds myname to obj1
//1. `new` causes `this` inside the SimpleFun() to point to the
//   object being constructed thus adding any member
//   created inside SimipleFun() using this.membername to the
//   object being constructed
//2. And by default `new` makes function to return newly 
//   constructed object if no explicit return value is specified

document.write(obj1.myname); //simple function

5. 在原型链上定义的函数中使用

如果方法在对象的原型链上,那么该方法内部的this关键字会指向调用该方法的对象本身,就好像该方法是在该对象上定义的一样。

var ProtoObj = {
    fun: function () {
        return this.a;
    }
};
//Object.create() creates object with ProtoObj as its
//prototype and assigns it to obj3, thus making fun() 
//to be the method on its prototype chain

var obj3 = Object.create(ProtoObj);
obj3.a = 999;
document.write(obj3.fun()); //999

//Notice that fun() is defined on obj3's prototype but 
//`this.a` inside fun() retrieves obj3.a   

6. 在call()、apply()和bind()函数内部

  • 这些方法都定义在Function.prototype上。
  • 这些方法允许我们编写一个函数,并在不同的上下文中调用它。换句话说,它们允许我们指定在执行函数时要使用的this的值,并传递任何要传递给原始函数的参数。
  • fun.apply(obj1 [, argsArray])obj1设置为fun()内的this值,并使用argsArray的元素作为其参数来调用fun()
  • fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]]) - 将obj1设置为fun()内的this值,并将arg1, arg2, arg3, ...作为其参数来调用fun()
  • fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]]) - 返回绑定了指向obj1this值和fun的参数到指定的arg1, arg2, arg3,...参数的函数fun的引用。
  • 现在,applycallbind之间的区别必须已经变得明显了。 apply允许我们将函数的参数指定为类似数组的对象,即具有数字length属性和相应非负整数属性的对象。而call允许我们直接指定函数的参数。 applycall都立即在指定的上下文中调用函数并使用指定的参数。另一方面,bind仅仅返回绑定到指定this值和参数的函数,我们可以通过将其分配给变量来捕获此返回的函数的引用,稍后可以随时调用它。
function add(inc1, inc2)
{
    return this.a + inc1 + inc2;
}

var o = { a : 4 };
document.write(add.call(o, 5, 6)+"<br />"); //15
      //above add.call(o,5,6) sets `this` inside
      //add() to `o` and calls add() resulting:
      // this.a + inc1 + inc2 = 
      // `o.a` i.e. 4 + 5 + 6 = 15
document.write(add.apply(o, [5, 6]) + "<br />"); //15
      // `o.a` i.e. 4 + 5 + 6 = 15

var g = add.bind(o, 5, 6);       //g: `o.a` i.e. 4 + 5 + 6
document.write(g()+"<br />");    //15

var h = add.bind(o, 5);          //h: `o.a` i.e. 4 + 5 + ?
document.write(h(6) + "<br />"); //15
      // 4 + 5 + 6 = 15
document.write(h() + "<br />");  //NaN
      //no parameter is passed to h()
      //thus inc2 inside add() is `undefined`
      //4 + 5 + undefined = NaN</code>

7. this 在事件处理程序中的作用

  • 当你直接将函数赋值给元素的事件处理程序时,在事件处理函数内部直接使用 this 关键字会引用到对应的元素。这种直接函数赋值可以通过 addeventListener 方法或传统的事件注册方法(如 onclick)实现。
  • 同样地,当你在元素的事件属性(如 <button onclick="...this..." >)中直接使用 this ,它会引用到该元素本身。
  • 然而,当你在事件处理函数或事件属性内部调用其他函数时,通过间接方式使用 this 将会解析为全局对象 window
  • 当我们使用微软的事件注册模型方法 attachEvent 将函数附加到事件处理程序时,会出现与上述相同的行为。它不是将函数分配给事件处理程序(从而使函数成为元素的方法),而是在事件上调用函数(实质上在全局上下文中调用函数)。

我建议在 JSFiddle 上尝试一下。

<script> 
    function clickedMe() {
       alert(this + " : " + this.tagName + " : " + this.id);
    } 
    document.getElementById("button1").addEventListener("click", clickedMe, false);
    document.getElementById("button2").onclick = clickedMe;
    document.getElementById("button5").attachEvent('onclick', clickedMe);   
</script>

<h3>Using `this` "directly" inside event handler or event property</h3>
<button id="button1">click() "assigned" using addEventListner() </button><br />
<button id="button2">click() "assigned" using click() </button><br />
<button id="button3" onclick="alert(this+ ' : ' + this.tagName + ' : ' + this.id);">used `this` directly in click event property</button>

<h3>Using `this` "indirectly" inside event handler or event property</h3>
<button onclick="alert((function(){return this + ' : ' + this.tagName + ' : ' + this.id;})());">`this` used indirectly, inside function <br /> defined & called inside event property</button><br />

<button id="button4" onclick="clickedMe()">`this` used indirectly, inside function <br /> called inside event property</button> <br />

IE only: <button id="button5">click() "attached" using attachEvent() </button>

8. ES6箭头函数中的this

在箭头函数中,this的行为类似于普通变量:它将从其词法作用域继承。箭头函数定义时所在的函数的this将成为箭头函数的this

因此,这与以下行为相同:

(function(){}).bind(this)

请看以下代码:

const globalArrowFunction = () => {
  return this;
};

console.log(globalArrowFunction()); //window

const contextObject = {
  method1: () => {return this},
  method2: function(){
    return () => {return this};
  }
};

console.log(contextObject.method1()); //window

const contextLessFunction = contextObject.method1;

console.log(contextLessFunction()); //window

console.log(contextObject.method2()()) //contextObject

const innerArrowFunction = contextObject.method2();

console.log(innerArrowFunction()); //contextObject 

77

Javascript的this

简单函数调用

考虑以下函数:

function foo() {
    console.log("bar");
    console.log(this);
}
foo(); // calling the function

请注意,我们正在正常模式下运行,即未使用严格模式。

在浏览器中运行时,this 的值将作为 window 记录。这是因为在 Web 浏览器的作用域中,window 是全局变量。

如果您在像 node.js 这样的环境中运行相同的代码,则 this 将引用您的应用程序中的全局变量。

现在,如果我们通过在函数声明开头添加语句 "use strict"; 来在严格模式下运行此代码,则在任何一个环境中,this 都不再引用全局变量。这样做是为了避免在严格模式下出现混淆。在这种情况下,this 仅会记录 undefined,因为它确实是未定义的。

接下来,我们将看到如何操作 this 的值。

调用对象上的函数

有不同的方法可以做到这一点。如果您已经在 JavaScript 中调用过本地方法,例如 forEachslice,那么您应该已经知道,在这种情况下,this 变量指的是您调用该函数的 Object(请注意,在 JavaScript 中,几乎所有东西都是 Object,包括 ArrayFunction)。例如,看下面的代码:

var myObj = {key: "Obj"};
myObj.logThis = function () {
    // I am a method
    console.log(this);
}
myObj.logThis(); // myObj is logged
如果一个 Object 包含一个保存 Function 的属性,则该属性称为方法。当调用此方法时,它的 this 变量始终被设置为与其关联的 Object。这适用于严格模式和非严格模式。
请注意,如果将方法存储(或复制)到另一个变量中,则不再保留对 this 的引用。例如:
// continuing with the previous code snippet

var myVar = myObj.logThis;
myVar();
// logs either of window/global/undefined based on mode of operation

考虑一个更为常见的实际场景:

var el = document.getElementById('idOfEl');
el.addEventListener('click', function() { console.log(this) });
// the function called by addEventListener contains this as the reference to the element
// so clicking on our element would log that element itself

new关键字

考虑一个Javascript中的构造函数:

function Person (name) {
    this.name = name;
    this.sayHello = function () {
        console.log ("Hello", this);
    }
}

var awal = new Person("Awal");
awal.sayHello();
// In `awal.sayHello`, `this` contains the reference to the variable `awal`

这是如何运作的呢?嗯,让我们看看当我们使用new关键字时会发生什么。

  1. 使用new关键字调用函数将立即初始化类型为PersonObject
  2. Object的构造函数设置为Person。另请注意,typeof awal只会返回Object
  3. 此新的Object将被分配为Person.prototype的原型。这意味着Person原型中的任何方法或属性都将对所有Person实例(包括awal)可用。
  4. 函数Person本身现在被调用;其中this是指新构造的对象awal

相当简单,不是吗?

请注意,官方ECMAScript规范没有明确说明这些类型的函数是实际的constructor函数。它们只是普通的函数,new可以用于任何函数。只是我们将其用作这样的函数,因此我们只称之为这样的函数。

在函数上调用函数:callapply

因此,由于function也是Objects(实际上是Javascript中的一类变量),即使函数也有它们自己的方法,这些方法本身也是函数。

所有函数都继承自全局的Function,它的许多方法之一是callapply,两者都可用于操作调用它们的函数中的this的值。

function foo () { console.log (this, arguments); }
var thisArg = {myObj: "is cool"};
foo.call(thisArg, 1, 2, 3);
这是使用 call 的典型示例。它基本上取第一个参数,并将 this 在函数 foo 中设置为对 thisArg 的引用。传递给 call 的所有其他参数都作为参数传递给函数 foo
因此,上面的代码将在控制台中记录 {myObj: "is cool"}, [1, 2, 3]。这是一种非常好的方法来更改任何函数中的 this 值。 apply 几乎与 call 相同,只不过它只接受两个参数:thisArg 和包含要传递给函数的参数的数组。因此,上面的 call 调用可以转换为如下的 apply
foo.apply(thisArg, [1,2,3])
注意,callapply可以覆盖我们在第二个bullet中讨论的点方法调用设置的this值。 很简单:)

呈现...... bind!

bindcallapply的兄弟。它也是JavaScript中所有函数从全局Function构造函数继承的一种方法。 bindcall/apply之间的区别在于,callapply实际上会调用函数。另一方面,bind返回一个具有预设thisArgarguments的新函数。让我们通过示例更好地理解这一点:
function foo (a, b) {
    console.log (this, arguments);
}
var thisArg = {myObj: "even more cool now"};
var bound = foo.bind(thisArg, 1, 2);
console.log (typeof bound); // logs `function`
console.log (bound);
/* logs `function () { native code }` */

bound(); // calling the function returned by `.bind`
// logs `{myObj: "even more cool now"}, [1, 2]`

看到它们之间的区别了吗?虽然微小,但它们的用法不同。与callapply一样,bind也会覆盖由点方法调用设置的this的值。

还要注意,这三个函数都不会对原函数进行任何更改。callapply将返回新构造函数的值,而bind将返回新构造的函数本身,准备好被调用。

额外内容,复制这个

有时,您不喜欢this随作用域(特别是嵌套作用域)的变化。请看以下示例。

var myObj = {
    hello: function () {
        return "world"
        },
    myMethod: function () {
        // copy this, variable names are case-sensitive
        var that = this;
        // callbacks ftw \o/
        foo.bar("args", function () {
            // I want to call `hello` here
            this.hello(); // error
            // but `this` references to `foo` damn!
            // oh wait we have a backup \o/
            that.hello(); // "world"
        });
    }
  };

在上面的代码中,我们可以看到this的值随着嵌套作用域而发生了变化,但我们希望使用来自原始作用域的this的值。所以我们将this '复制' 到that并使用副本而不是this。聪明吧?

索引:

  1. this 默认存储了什么内容?
  2. 如果我们使用对象点表示法将函数调用为方法会怎样?
  3. 如果我们使用 new关键字会怎样?
  4. 如何使用 callapply 来操作this
  5. 使用 bind
  6. 复制 this 以解决嵌套作用域问题。

49

"this" 与作用域有关。每个函数都有自己的作用域,由于 JS 中的一切都是对象,甚至一个函数也可以使用 "this" 将一些值存储到自身中。OOP(面向对象编程)101 教导我们,“this” 仅适用于对象的实例。因此,每次函数执行时,该函数的新“实例”都会有新的“this”含义。

当人们尝试在匿名闭包函数内部使用 "this" 时,大多数人会感到困惑,例如:

(function(value) {
    this.value = value;
    $('.some-elements').each(function(elt){
        elt.innerHTML = this.value;        // 哎呀!!可能是未定义的
    });
})(2);

所以,在 each() 内部,"this" 不会持有您期望的“value”(来自它上面的

this.value = value;
)。为了解决这个问题,开发人员可以:

(function(value) {
    var self = this;            // 小改动
    self.value = value;
    $('.some-elements').each(function(elt){
        elt.innerHTML = self.value;        // 啊!== 2 
    });
})(2);

试一下吧,你会开始喜欢这种编程模式。


6
"everything in JS is an object" 这句话并不正确,JavaScript 也有原始值。请参见 http://bclary.com/2004/11/07/#a-4.3.2。 - Marcel Korpel
6
原始值似乎有一些自己的方法,例如 String#substring()、Number#toString() 等。因此,尽管命名方式可能不同于该文章,它们的行为确实表现得好像它们是对象(它们都有原型,即 String#substring() 实际上是:String.prototype.substring = function(){...})。如果我有误,请纠正我。 - arunjitsingh
12
this 关键字与作用域无关,在非对象属性的函数中也有意义。 - Bergi
1
@arunjitsingh 原始类型没有任何方法或属性。当您在字符串原始类型上调用String对象方法时,会将原始类型隐式装箱为新的String对象包装实例。除非被捕获到变量中,否则操作完成后将丢弃该包装器。 - Scott Marcus
9
这并不完全是关于作用域的。这完全是关于执行上下文,而执行上下文并不等同于作用域。JavaScript 是词法作用域(意味着作用域由代码位置决定),但 this 取决于包含它的函数如何被调用——而不是该函数所在的位置。 - Scott Marcus
显示剩余2条评论

20

由于这个主题被顶起来了,我为那些对this话题不熟悉的读者编写了几个要点。

this的值是如何确定的?

我们使用this的方式类似于自然语言(如英语)中使用代词的方式:“John is running fast because he is trying to catch the train.” 相反,我们也可以写成“…John is trying to catch the train”。

var person = {    
    firstName: "Penelope",
    lastName: "Barrymore",
    fullName: function () {

    // We use "this" just as in the sentence above:
       console.log(this.firstName + " " + this.lastName);

    // We could have also written:
       console.log(person.firstName + " " + person.lastName);
    }
}

this在定义它的函数被对象调用之前,不会被赋值。在全局范围内,所有全局变量和函数都定义在window对象上。因此,全局函数中的this指代(并具有)全局window对象的值。

当使用严格模式use strict时,在全局和未绑定到任何对象的匿名函数中,this的值为undefined

当我们借用使用this的方法、将使用this的方法分配给变量、将使用this的函数作为回调函数传递以及在闭包——内部函数中使用this时,this关键字是最容易被误解的(2)

table

未来的支柱

ECMA Script 6中定义,箭头函数采用封闭作用域(函数或全局)的this绑定。

function foo() {
     // return an arrow function
     return (a) => {
     // `this` here is lexically inherited from `foo()`
     console.log(this.a);
  };
}
var obj1 = { a: 2 };
var obj2 = { a: 3 };

var bar = foo.call(obj1);
bar.call( obj2 ); // 2, not 3!

虽然箭头函数提供了一种替代使用 bind() 的方式,但重要的是要注意,它们实质上是禁用了传统的this 机制,而采用更广为人知的词法作用域。(1)

参考资料:

  1. this & Object Prototypes,作者:Kyle Simpson。© 2014 Getify Solutions。
  2. http://goo.gl/pvl0GX - javascriptissexy.com
  3. http://goo.gl/Z2RacU - Angus Croll

18

this在JavaScript中始终指向正在执行的函数的“所有者”。

如果没有明确的所有者定义,则引用最顶层的所有者,即window对象。

因此,如果我执行:

function someKindOfFunction() {
   this.style = 'foo';
}

element.onclick = someKindOfFunction;

this将引用元素对象。但要小心,很多人会犯这个错误。

<element onclick="someKindOfFunction()">

在后一种情况下,你仅仅是引用函数,而不是把它交给元素。因此,this将引用window对象。


15

JavaScript中的每个执行上下文都有一个this参数,由以下方式设置:

  1. 函数的调用方式(包括作为对象方法、使用callapply、使用new
  2. 使用bind
  3. 对于箭头函数,它们采用外部执行上下文的this
  4. 代码是否在严格模式或非严格模式下
  5. 代码是使用eval调用的

您可以使用func.callfunc.applyfunc.bind来设置this的值。

默认情况下,最常让初学者感到困惑的是,当DOM元素上抛出事件后调用监听器时,函数的this值是该DOM元素。

jQuery通过jQuery.proxy使更改这一点变得非常简单。


9
每个函数“调用”都有一个作用域,这样说更准确。换句话说,在JavaScript中让人感到困惑的是this不是函数本身的内在属性,而是函数被调用时的一种产物。请注意不要改变原意。 - Pointy
2
this 不是指函数的作用域。this 将引用特定的对象(或可能是 undefined),正如您所说,可以使用 .call().apply() 进行更改。函数的 作用域 是指它可以访问哪些变量(在简化时),这完全取决于函数声明的位置,不能更改。 - nnnnnn
@Pointy: "更准确地说,每个函数调用都有一个作用域。" 更准确的说,函数(以及现在的块)有作用域,函数调用上下文。作用域定义了可以被该作用域内代码使用的标识符,而上下文定义了这些标识符绑定的内容。 - T.J. Crowder
1
“无论作用域是什么,都会被“this”引用。” 不,ES5及之前的版本(例如在撰写本答案时)中,“this”和作用域根本没有任何关系。在ES2015(又名ES6)中,就箭头函数而言,“this”和作用域只有一种相对较小的关联方式(箭头函数中的“this”从其封闭作用域继承),但“this”从不指代作用域。 - T.J. Crowder
@Pointy: 我认为它可能与那个有关(例如,很久以前;你甚至不想知道我当时会错的事情)。 实际上,我本来就是想说这个的,但被分心了,抱歉。 - T.J. Crowder
显示剩余2条评论

11

Daniel,讲得很好!关于这个问题,请简单介绍一下并列出与事件处理程序相关的this执行上下文指针列表。

简而言之,在JavaScript中,this指向运行当前函数的对象(或其执行上下文所在的对象),它始终是只读的,无法设置(尝试设置将导致“无效的左侧赋值”消息)。

对于事件处理程序:内联事件处理程序(例如<element onclick="foo">)将覆盖任何早期和之前附加的其他处理程序,因此要小心,并最好完全避免使用内联事件委托。 感谢Zara Alaverdyan通过异议辩论激发我制作这个例子列表:)

  • el.onclick = foo; // 在foo中,this指向obj
  • el.onclick = function () {this.style.color = '#fff';} // this指向obj
  • el.onclick = function() {doSomething();} // 在doSomething中,this指向window
  • el.addEventListener('click',foo,false) // 在foo中,this指向obj
  • el.attachEvent('onclick, function () { // this }') // this指向window,IE中的所有兼容性 :)
  • <button onclick="this.style.color = '#fff';"> // this指向obj
  • <button onclick="foo"> // 在foo中,this指向window,但可以使用<button onclick="foo(this)">

11

这里是一个关于在JavaScript中使用this的好资源。

这里是摘要:

  • 全局的this

    在浏览器中,全局作用域下的thiswindow对象。

    <script type="text/javascript">
      console.log(this === window); // true
      var foo = "bar";
      console.log(this.foo); // "bar"
      console.log(window.foo); // "bar"
    

    在使用Node的repl时,this是顶级命名空间。你可以将其称为global

    >this
      { ArrayBuffer: [Function: ArrayBuffer],
        Int8Array: { [Function: Int8Array] BYTES_PER_ELEMENT: 1 },
        Uint8Array: { [Function: Uint8Array] BYTES_PER_ELEMENT: 1 },
        ...
    >global === this
     true
    
    在脚本中执行 node 时,全局作用域下的 this 开始时为空对象。它与 global 不同。
    \\test.js
    console.log(this);  \\ {}
    console.log(this === global); \\ fasle
    
  • 函数中的this

除非在DOM事件处理程序的情况下或提供了thisArg(请参见下文),否则无论是在节点还是在使用this的浏览器中,在未使用new调用的函数中使用this引用全局范围…

<script type="text/javascript">
    foo = "bar";

    function testThis() {
      this.foo = "foo";
    }

    console.log(this.foo); //logs "bar"
    testThis();
    console.log(this.foo); //logs "foo"
</script>

如果您使用use strict;,那么this将变为undefined

<script type="text/javascript">
    foo = "bar";

    function testThis() {
      "use strict";
      this.foo = "foo";
    }

    console.log(this.foo); //logs "bar"
    testThis();  //Uncaught TypeError: Cannot set property 'foo' of undefined 
</script>
如果您使用new调用函数,this将是一个新的上下文,它不会引用全局的this
<script type="text/javascript">
    foo = "bar";

    function testThis() {
      this.foo = "foo";
    }

    console.log(this.foo); //logs "bar"
    new testThis();
    console.log(this.foo); //logs "bar"

    console.log(new testThis().foo); //logs "foo"
</script>
  • 原型模式

您创建的函数将成为函数对象。它们会自动获得一个特殊的prototype属性,您可以向其分配值。当使用new调用函数创建实例时,您可以访问分配给prototype属性的值。您可以通过this来访问这些值。

function Thing() {
  console.log(this.foo);
}

Thing.prototype.foo = "bar";

var thing = new Thing(); //logs "bar"
console.log(thing.foo);  //logs "bar"

通常将数组或者对象赋值给prototype是一个错误的做法。如果你希望每个实例拥有自己的数组,应该在函数中创建它们而不是在原型中。

function Thing() {
    this.things = [];
}

var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");
console.log(thing1.things); //logs ["foo"]
console.log(thing2.things); //logs []
  • 这个对象

你可以在对象上的任何函数中使用this,来引用该对象上的其他属性。这与使用new创建的实例不同。

var obj = {
    foo: "bar",
    logFoo: function () {
        console.log(this.foo);
    }
};

obj.logFoo(); //logs "bar"
  • DOM事件中的this

在HTML DOM事件处理程序中,this始终是对附加了事件的DOM元素的引用。

function Listener() {
    document.getElementById("foo").addEventListener("click",
       this.handleClick);
}
Listener.prototype.handleClick = function (event) {
    console.log(this); //logs "<div id="foo"></div>"
}

var listener = new Listener();
document.getElementById("foo").click();

除非您将上下文绑定

function Listener() {
    document.getElementById("foo").addEventListener("click", 
        this.handleClick.bind(this));
}
Listener.prototype.handleClick = function (event) {
    console.log(this); //logs Listener {handleClick: function}
}

var listener = new Listener();
document.getElementById("foo").click();
  • HTML this

在 HTML 属性中可以放置 JavaScript,this 是一个指向元素的引用。

<div id="foo" onclick="console.log(this);"></div>
<script type="text/javascript">
document.getElementById("foo").click(); //logs <div id="foo"...
</script>
  • 评估这个

您可以使用eval访问this

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    eval("console.log(this.foo)"); //logs "bar"
}

var thing = new Thing();
thing.logFoo();
  • 使用 with

您可以使用 withthis添加到当前作用域,以便在不显式引用 this 的情况下读取和写入 this的值。

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    with (this) {
        console.log(foo);
        foo = "foo";
    }
}

var thing = new Thing();
thing.logFoo(); // logs "bar"
console.log(thing.foo); // logs "foo"
  • jQuery this

在许多情况下,jQuery中的this指的是DOM元素。

<div class="foo bar1"></div>
<div class="foo bar2"></div>
<script type="text/javascript">
$(".foo").each(function () {
    console.log(this); //logs <div class="foo...
});
$(".foo").on("click", function () {
    console.log(this); //logs <div class="foo...
});
$(".foo").each(function () {
    this.click();
});
</script>

9
有关JavaScript中"this"关键字的解释存在很多混淆。希望本文能够一劳永逸地解决这些问题。请认真阅读整篇文章,但要注意,本文较长。
无论在何种上下文中使用,"this"总是引用Javascript中的"当前对象"。然而,"当前对象"根据上下文的不同而异。上下文可能是以下6种之一
  1. 全局(即所有函数外部)
  2. 直接调用"非绑定函数"(即未通过调用functionName.bind绑定的函数)
  3. 间接调用"非绑定函数",通过functionName.callfunctionName.apply
  4. 调用"绑定函数"(即通过调用functionName.bind绑定的函数)
  5. "new"对象创建期间
  6. 内联DOM事件处理程序内部
下面分别描述了每个上下文:
  1. Global Context (i.e. Outside all functions):

    Outside all functions (i.e. in global context) the "current object" (and hence the value of "this") is always the "window" object for browsers.

  2. Inside Direct "Non Bound Function" Call:

    Inside a Direct "Non Bound Function" Call, the object that invoked the function call becomes the "current object" (and hence the value of "this"). If a function is called without a explicit current object, the current object is either the "window" object (For Non Strict Mode) or undefined (For Strict Mode) . Any function (or variable) defined in Global Context automatically becomes a property of the "window" object.For e.g Suppose function is defined in Global Context as

    function UserDefinedFunction(){
        alert(this)
        }
    

    it becomes the property of the window object, as if you have defined it as

    window.UserDefinedFunction=function(){
      alert(this)
    }  
    

    In "Non Strict Mode", Calling/Invoking this function directly through "UserDefinedFunction()" will automatically call/invoke it as "window.UserDefinedFunction()" making "window" as the "current object" (and hence the value of "this") within "UserDefinedFunction".Invoking this function in "Non Strict Mode" will result in the following

    UserDefinedFunction() // displays [object Window]  as it automatically gets invoked as window.UserDefinedFunction()
    

    In "Strict Mode", Calling/Invoking the function directly through "UserDefinedFunction()" will "NOT" automatically call/invoke it as "window.UserDefinedFunction()".Hence the "current object" (and the value of "this") within "UserDefinedFunction" shall be undefined. Invoking this function in "Strict Mode" will result in the following

    UserDefinedFunction() // displays undefined
    

    However, invoking it explicitly using window object shall result in the following

    window.UserDefinedFunction() // "always displays [object Window]   irrespective of mode."
    

    Let us look at another example. Please look at the following code

     function UserDefinedFunction()
        {
            alert(this.a + ","  + this.b + ","  + this.c  + ","  + this.d)
        }
    
    var o1={
                a:1,
                b:2,
                f:UserDefinedFunction
          }
    var o2={
                c:3,
                d:4,
                f:UserDefinedFunction
           }
    
    o1.f() // Shall display 1,2,undefined,undefined
    o2.f() // Shall display undefined,undefined,3,4
    

    In the above example we see that when "UserDefinedFunction" was invoked through o1, "this" takes value of o1 and the value of its properties "a" and "b" get displayed. The value of "c" and "d" were shown as undefined as o1 does not define these properties

    Similarly when "UserDefinedFunction" was invoked through o2, "this" takes value of o2 and the value of its properties "c" and "d" get displayed.The value of "a" and "b" were shown as undefined as o2 does not define these properties.

  3. Inside Indirect "Non Bound Function" Call through functionName.call and functionName.apply:

    When a "Non Bound Function" is called through functionName.call or functionName.apply, the "current object" (and hence the value of "this") is set to the value of "this" parameter (first parameter) passed to call/apply. The following code demonstrates the same.

    function UserDefinedFunction()
    {
        alert(this.a + ","  + this.b + ","  + this.c  + ","  + this.d)
    }
    var o1={
                a:1,
                b:2,
                f:UserDefinedFunction
           }
    var o2={
                c:3,
                d:4,
                f:UserDefinedFunction
           }
    
    UserDefinedFunction.call(o1) // Shall display 1,2,undefined,undefined
    UserDefinedFunction.apply(o1) // Shall display 1,2,undefined,undefined
    
    UserDefinedFunction.call(o2) // Shall display undefined,undefined,3,4
    UserDefinedFunction.apply(o2) // Shall display undefined,undefined,3,4
    
    o1.f.call(o2) // Shall display undefined,undefined,3,4
    o1.f.apply(o2) // Shall display undefined,undefined,3,4
    
    o2.f.call(o1) // Shall display 1,2,undefined,undefined
    o2.f.apply(o1) // Shall display 1,2,undefined,undefined
    

    The above code clearly shows that the "this" value for any "NON Bound Function" can be altered through call/apply. Also,if the "this" parameter is not explicitly passed to call/apply, "current object" (and hence the value of "this") is set to "window" in Non strict mode and "undefined" in strict mode.

  4. Inside "Bound Function" Call (i.e. a function that has been bound by calling functionName.bind):

    A bound function is a function whose "this" value has been fixed. The following code demonstrated how "this" works in case of bound function

    function UserDefinedFunction()
    {
        alert(this.a + ","  + this.b + ","  + this.c  + ","  + this.d)
    }
    var o1={
              a:1,
              b:2,
              f:UserDefinedFunction,
              bf:null
           }
    var o2={
               c:3,
               d:4,
               f:UserDefinedFunction,
               bf:null
            }
    
    var bound1=UserDefinedFunction.bind(o1); // permanantly fixes "this" value of function "bound1" to Object o1
    bound1() // Shall display 1,2,undefined,undefined
    
    var bound2=UserDefinedFunction.bind(o2); // permanantly fixes "this" value of function "bound2" to Object o2
    bound2() // Shall display undefined,undefined,3,4
    
    var bound3=o1.f.bind(o2); // permanantly fixes "this" value of function "bound3" to Object o2
    bound3() // Shall display undefined,undefined,3,4
    
    var bound4=o2.f.bind(o1); // permanantly fixes "this" value of function "bound4" to Object o1
    bound4() // Shall display 1,2,undefined,undefined
    
    o1.bf=UserDefinedFunction.bind(o2) // permanantly fixes "this" value of function "o1.bf" to Object o2
    o1.bf() // Shall display undefined,undefined,3,4
    
    o2.bf=UserDefinedFunction.bind(o1) // permanantly fixes "this" value of function "o2.bf" to Object o1
    o2.bf() // Shall display 1,2,undefined,undefined
    
    bound1.call(o2) // Shall still display 1,2,undefined,undefined. "call" cannot alter the value of "this" for bound function
    
    bound1.apply(o2) // Shall still display 1,2,undefined,undefined. "apply" cannot alter the value of "this" for bound function
    
    o2.bf.call(o2) // Shall still display 1,2,undefined,undefined. "call" cannot alter the value of "this" for bound function
    o2.bf.apply(o2) // Shall still display 1,2,undefined,undefined."apply" cannot alter the value of "this" for bound function
    

    As given in the code above, "this" value for any "Bound Function" CANNOT be altered through call/apply. Also, if the "this" parameter is not explicitly passed to bind, "current object" (and hence the value of "this" ) is set to "window" in Non strict mode and "undefined" in strict mode. One more thing. Binding an already bound function does not change the value of "this". It remains set as the value set by first bind function.

  5. While Object Creation through "new":

    Inside a constructor function, the "current object" (and hence the value of "this") references the object that is currently being created through "new" irrespective of the bind status of the function. However if the constructor is a bound function it shall get called with predefined set of arguments as set for the bound function.

  6. Inside Inline DOM event handler:

    Please look at the following HTML Snippet

    <button onclick='this.style.color=white'>Hello World</button>
    <div style='width:100px;height:100px;' onclick='OnDivClick(event,this)'>Hello World</div>
    

    The "this" in above examples refer to "button" element and the "div" element respectively.

    In the first example, the font color of the button shall be set to white when it is clicked.

    In the second example when the "div" element is clicked it shall call the OnDivClick function with its second parameter referencing the clicked div element. However the value of "this" within OnDivClick SHALL NOT reference the clicked div element. It shall be set as the "window object" or "undefined" in Non strict and Strict Modes respectively (if OnDivClick is an unbound function) or set to a predefined Bound value (if OnDivClick is a bound function)

以下是整篇文章的总结:
1. 在全局环境中,"this" 总是指向 "window" 对象。 2. 每当调用一个函数时,它都在一个对象的上下文中被调用("当前对象")。如果没有明确提供 "当前对象",则在非严格模式下 "当前对象" 是 "window" 对象,而在严格模式下默认为 "undefined"。 3. 非绑定函数中的 "this" 的值是指调用该函数时的上下文对象("当前对象")的引用。 4. 非绑定函数中 "this" 的值可以通过函数的 "call" 和 "apply" 方法进行覆盖。 5. 绑定函数中 "this" 的值是固定的,不能被函数的 "call" 和 "apply" 方法所覆盖。 6. 对已经绑定过的函数进行再次绑定不会改变 "this" 的值。它仍然是第一次绑定函数时设置的值。 7. 构造函数中 "this" 的值是正在创建和初始化的对象。 8. 内联 DOM 事件处理程序中 "this" 的值是给定事件处理程序的元素的引用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接