ChatGPT解决这个技术问题 Extra ChatGPT

“this”关键字是如何工作的,什么时候应该使用它?

我正在寻找关于“this”关键字的作用以及如何正确使用它的清晰解释。

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

this 是如何工作的以及何时应该使用它?


3
35 revs, 9 users 70%

this 是 JavaScript 中的关键字,是执行上下文的属性。它的主要用途是在函数和构造函数中。 this 的规则非常简单(如果您坚持最佳做法)。

规范中对此的技术描述

ECMAScript standard 通过抽象操作(缩写为 AOResolveThisBinding 定义 this

[AO] ResolveThisBinding […] 使用正在运行的执行上下文的 LexicalEnvironment 确定关键字 this 的绑定。 [步骤]:设 envRec 为 GetThisEnvironment()。返回 ? envRec.GetThisBinding()。

Global Environment Recordsmodule Environment Recordsfunction Environment Records 各有自己的 GetThisBinding 方法。

GetThisEnvironment AO 找到当前 running execution context 的 LexicalEnvironment 并找到最近的上升环境记录(通过迭代访问它们的 [[OuterEnv]] 属性),它具有 this 绑定(即 HasThisBinding 返回 <强>真)。此过程以三种环境记录类型之一结束。

this 的值通常取决于代码是否在 strict mode 中。

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 具体方法:返回 envRec.[[GlobalThisValue]]。

全局环境记录的 [[GlobalThisValue]] 属性始终设置为主机定义的 global object,可通过 globalThis 访问(Web 上的 window,Node.js 上的 globalDocs on MDN) .按照 InitializeHostDefinedRealm 的步骤了解 [[GlobalThisValue]] 属性是如何产生的。

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

模块已在 ECMAScript 2015 中引入。

这适用于模块,例如直接在 <script type="module"> 内时,而不是简单的 <script>

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

模块 Environment Record [...] [does this] 的 GetThisBinding 具体方法:返回未定义。

在模块中,this 的值在全局上下文中始终为 undefined。模块隐含在 strict mode 中。

3. 输入评估码

有两种 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(…) 也是间接的。

有关何时可以使用间接 eval() 调用,请参阅 chuckj’s answer to “(1, eval)('this') vs eval('this') in JavaScript?”Dmitry Soshnikov’s ECMA-262-5 in detail – Chapter 2: Strict Mode (archived)。

PerformEval 执行 eval 代码。它创建一个新的 declarative Environment Record 作为其 LexicalEnvironment,GetThisEnvironment 从中获取 this 值。

然后,如果 this 出现在 eval 代码中,则调用 GetThisEnvironment 找到的 Environment Record 的 GetThisBinding 方法并返回其值。

创建的 declarative Environment Record 取决于 eval 调用是直接调用还是间接调用:

在直接评估中,它将基于当前运行的执行上下文的 LexicalEnvironment。

在间接评估中,它将基于执行间接评估的领域记录的 [[GlobalEnv]] 属性(全局环境记录)。

意思是:

在直接评估中, this 值不会改变;它取自名为 eval 的词法范围。

在间接评估中,this 值是全局对象(globalThis)。

new Function 呢?new Function 类似于 eval,但它不会立即调用代码;它创建了一个函数。 this 绑定不适用于此处的任何地方,除非调用该函数,该函数正常工作,如下一小节所述。

4.输入功能码

调用函数时输入函数代码。

调用函数的语法有四种。

EvaluateCall AO 针对这三个执行:3 普通函数调用 可选链接调用 标记模板

普通函数调用

可选的链接调用

标记模板

并为此执行 EvaluateNew:3 构造函数调用

构造函数调用

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

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

另外,函数Environment Record中还有[[ThisValue]]字段:

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

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

[[Call]] 还调用 OrdinaryCallBindThis,其中适当的 thisArgument 是根据以下内容确定的:

原始参考,

函数的类型,以及

代码是否处于严格模式。

一旦确定,对新创建的函数 Environment Record 的 BindThisValue 方法的最终调用实际上会将 [[ThisValue]] 字段设置为 thisArgument

最后,这个字段是 function Environment Record’s GetThisBinding AO 从以下位置获取 this 的值:

函数Environment Record envRec [...] [does this]的GetThisBinding具体方法:[...] 3. 返回envRec.[[ThisValue]]。

同样,该值如何确定取决于许多因素;这只是一个总体概述。有了这个技术背景,让我们来看看所有的具体例子。

箭头函数

评估 arrow function 时,函数对象的 [[ThisMode]] 内部槽在 OrdinaryFunctionCreate 中设置为 “lexical”

OrdinaryCallBindThis,它采用函数 F

设 thisMode 为 F.[[ThisMode]]。如果 thisMode 是词法,则返回 NormalCompletion(undefined)。 […]

这只是意味着跳过绑定 this 的算法的其余部分。箭头函数不绑定自己的 this 值。

那么,箭头函数中的 this 是什么?回顾 ResolveThisBindingGetThisEnvironment,即 HasThisBinding method explicitly returns false

函数Environment Record envRec […] [does this]的HasThisBinding具体方法:如果envRec.[[ThisBindingStatus]]是词法的,则返回false;否则,返回真。

因此,外部环境被迭代地查找。该过程将在具有 this 绑定的三个环境之一中结束。

这只是意味着,在箭头函数体中,this 来自箭头函数的词法范围,或者换句话说(来自 Arrow function vs function declaration / expressions: Are they equivalent / exchangeable?):

箭头函数没有自己的 this […] 绑定。相反,[this identifier is] 像任何其他变量一样在词法范围内解析。这意味着在箭头函数内部,this [引用] 指向定义箭头函数的环境中的[this 值](即箭头函数“外部”)。

函数属性

在普通函数(functionmethods)中,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(),它由 MemberExpression refObj.funcArguments {9 }。

而且,refObj.funcrefObj 扮演三个角色,每个角色:

都是表情,

它们都是参考,并且

他们都是价值观。

refObj.func 作为 value 是可调用函数对象;相应的 reference 用于确定 this 绑定。

可选链接和标记模板示例的工作方式非常相似:基本上,引用是 ?.() 之前、`` 之前或 () 之前的所有内容。

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

注意:关于 thisGetters / Setters 的工作方式与方法相同。简单属性不会影响执行上下文,例如这里,this 在全局范围内:

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

不带基本引用、严格模式和带

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

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

passing or assigning methods 或使用 comma operator 时也会发生这种情况。这就是参考记录和价值之间的差异相关的地方。

注意函数j:遵循规范,您会注意到j只能返回函数对象(Value)本身,而不能返回引用记录。因此基本引用 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 在此处使用 thisValueundefined 调用 Call。这在 OrdinaryCallBindThis 中有所不同(F:函数对象;thisArgument:传递给 CallthisValue):

设 thisMode 为 F.[[ThisMode]]。 […] 如果 thisMode 是严格的,让 thisValue 成为 thisArgument。否则,如果 thisArgument 未定义或为 null,则令 globalEnv 为 calleeRealm.[[GlobalEnv]]。 […] 让 thisValue 为 globalEnv。[[GlobalThisValue]]。否则,让 thisValue 成为 ! ToObject(thisArgument)。注意:ToObject 产生包装对象 […]。 […]

注意:步骤 5 将 this 的实际值设置为在严格模式下提供的 thisArgument - 在本例中为 undefined。在“草率模式”中,未定义或 null thisArgument 会导致 this 成为全局 this 值。

如果 IsPropertyReference 返回 false,则 EvaluateCall 采取以下步骤:

设 refEnv 为 ref.[[Base]]。断言:refEnv 是一个环境记录。让 thisValue 为 refEnv.WithBaseObject()。

这就是未定义的 thisValue 的来源:refEnvWithBaseObject() 始终是 undefinedexceptwith 语句中。在这种情况下,thisValue 将是绑定对象。

还有 Symbol.unscopables (Docs on MDN) 用于控制 with 绑定行为。

总结一下,到目前为止:

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 值被强制only在“草率”模式下。

为了检查这一点,让我们介绍 this 值的另一个来源:覆盖 this 绑定的三个方法:4

Function.prototype.apply(thisArg, argArray)

函数.原型。 {call, bind} (thisArg, ...args)

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

.call.apply 使用指定的 thisArg 直接映射到 Call.bindBoundFunctionCreate 创建一个绑定函数。它们有它们自己的 [[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 运算符将函数作为构造函数调用时,EvaluateNew 调用 Construct,后者调用 [[Construct]] method。如果函数是基本构造函数(即不是 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 }`.

类定义隐含在 strict mode 中:

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

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

极好的

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

调用 super 使用调用的词法范围(函数环境记录)的 this 值调用超级构造函数。 GetThisValuesuper 调用有一个特殊规则。它使用 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()。这在 this 2ality article (archived) 中进行了解释。特别参见how a ParenthesizedExpression is evaluated

2:它必须是一个MemberExpression,不能是一个属性,必须有一个正好是“eval”的[[ReferencedName]] , 并且必须是 %eval% 内在对象。

3:只要规范说“让引用是评估 X的结果”,那么X就是一些您需要为其找到评估步骤的表达式。例如,评估 MemberExpressionCallExpressionthese algorithms 之一的结果。其中一些导致 Reference Record

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

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

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

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

if(true){ console.log(this); // 这里的 `this` 是什么? } globalThis。标记的行在初始全局执行上下文中进行评估。常量 obj = {}; function myFun(){ return { // 这里的 `this` 是什么? “是 obj”:这个 === obj,“是 globalThis”:这个 === globalThis }; } obj.method = myFun; console.log(obj.method());对象。当作为对象的属性调用函数时,调用它时将 this 绑定设置为引用 obj.method 的基础,即 obj。 const obj = { myMethod: function(){ return { // 这里的 `this` 是什么? “是 obj”:这个 === obj,“是 globalThis”:这个 === globalThis }; } }, myFun = obj.myMethod;控制台.log(myFun());全球这个。由于函数值 myFun / obj.myMethod 没有从对象中调用,因此作为属性,this 绑定将是 globalThis。这与 Python 不同,在 Python 中访问方法 (obj.myMethod) 会创建一个绑定的方法对象。 const obj = { myFun: () => ({ // 这里的 `this` 是什么? "is obj": this === obj, "is globalThis": this === globalThis }) };控制台.log(obj.myFun());全球这个。箭头函数不会创建自己的 this 绑定。词法作用域与初始全局作用域相同,所以 this 是 globalThis。功能 myFun(){ console.log(this); // 这里的 `this` 是什么? } const obj = { myMethod: function(){ eval("myFun()"); } }; obj.myMethod();全球这个。在评估直接 eval 调用时,这是 obj。但是,在 eval 代码中,myFun 没有从对象中调用,因此 this 绑定设置为全局对象。 function myFun() { // 这里的 `this` 是什么? return { "is obj": this === obj, "is globalThis": this === globalThis }; } 常量 obj = {}; console.log(myFun.call(obj));对象。行 myFun.call(obj);正在调用特殊的内置函数 Function.prototype.call,它接受 thisArg 作为第一个参数。 class MyCls{ arrow = () => ({ // 这里的 `this` 是什么? "is MyCls": this === MyCls, "is globalThis": this === globalThis, "is instance": this instanceof MyCls }); } console.log(new MyCls().arrow());它是 MyCls 的实例。箭头函数不会更改 this 绑定,因此它来自词法范围。因此,这与上面提到的类字段完全相同,例如a = this;。尝试将其更改为静态箭头。你得到你期望的结果了吗?


另一种常见情况:在将 this 设置为事件的 currentTarget 的情况下调用 EventHandler。这三个提案可以在未来包括:Bind operator ::Explicit thisthis argument reflection。像 onclick 这样的 DOM 0 事件属性也值得注意:JS 代码隐式包装在一个 with 范围内,用于 document 和一个用于单击元素的 causing confusionthis 是具有该属性的元素。
@LRPDRDX 好吧,这个答案包含了 this 的每一个细节。但是在全局范围内没有人真正需要 this,不推荐使用 with,不鼓励使用 eval,应该在任何地方使用严格模式,等等。剩下的就是 obj.method() 调用 method 并将 obj 作为 { 1} 如果 methodfunction 或方法; func() 在没有任何 this 的情况下调用 func.bind.call.apply 可用于显式绑定 this;箭头函数没有 this 绑定。类:在静态事物中,this 是指类本身,在非静态事物中是指正在创建的实例。而已。
你知道,这是一个很好的答案,但它确实可以用来编辑。它在某些地方非常草率。对于一个应该提供“百科全书式答案”的网站来说,最后的片段似乎是不必要的
H
Haider Ilahi

与其他语言相比,this 关键字在 JavaScript 中的行为不同。在面向对象的语言中,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

上面我已经把这个词放在双引号中。这是为了说明,如果您将对象嵌套在另一个对象中,则 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 运算符创建它的实例。

同样在对象字面量方法中,我们可以使用点运算符显式地将成员添加到已定义的对象中。这只会添加到特定实例。但是,我已将变量添加到函数原型中,以便它反映在函数的所有实例中。

下面我尝试了我们在上面使用 Object 和 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 的值,并调用 fun() 传递 argsArray 的元素作为其参数。

fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]]) - 将 obj1 设置为 fun() 中 this 的值,并调用 fun() 传递 arg1、arg2、arg3、.. . 作为它的论据。

fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]]) - 返回对函数 fun 的引用,其中 this 内部 fun 绑定到 obj1,fun 的参数绑定到指定的参数 arg1,参数 2,参数 3,....

到目前为止,apply、call 和 bind 之间的区别一定已经很明显了。 apply 允许将参数指定为类似数组的对象,即具有数字长度属性和相应的非负整数属性的对象。而 call 允许直接指定函数的参数。 apply 和 call 都立即在指定的上下文中使用指定的参数调用函数。另一方面,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 来完成。

同样,当您在元素的事件属性(如

W
WeiChing 林煒清

Javascript是这个

简单的函数调用

考虑以下函数:

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

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

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

如果您在 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

新关键字

考虑 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 关键字时会发生什么。

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

很简单,嗯?

请注意,官方的 ECMAScript 规范没有说明此类函数是实际的 constructor 函数。它们只是普通函数,new 可用于任何函数。只是我们这样使用它们,所以我们只这样称呼它们。

在 Functions 上调用函数:调用和应用

所以,是的,因为 function 也是 Objects(实际上是 Javascript 中的第一类变量),所以即使函数也有方法,它们本身就是......好吧,函数本身。

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

function foo () { console.log (this, arguments); }
var thisArg = {myObj: "is cool"};
foo.call(thisArg, 1, 2, 3);

这是使用 call 的典型示例。它基本上采用第一个参数并在函数 foo 中设置 this 作为对 thisArg 的引用。传递给 call 的所有其他参数都作为参数传递给函数 foo
所以上面的代码将在控制台中记录 {myObj: "is cool"}, [1, 2, 3]。在任何函数中更改 this 值的好方法。

applycall 几乎相同,接受它只需要两个参数:thisArg 和一个包含要传递给函数的参数的数组。所以上面的 call 调用可以像这样翻译成 apply

foo.apply(thisArg, [1,2,3])

请注意,callapply 可以覆盖我们在第二个项目符号中讨论的点方法调用设置的 this 的值。很简单:)

呈现....绑定!

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。聪明,嗯?

指数:

默认情况下保存的是什么?如果我们将函数调用为使用对象点表示法的方法会怎样?如果我们使用 new 关键字呢?我们如何通过调用和应用来操作它?使用绑定。复制它以解决嵌套范围问题。


D
Daniel Alexiuc

“这个”是关于范围的。每个函数都有自己的作用域,由于 JS 中的一切都是对象,因此即使是函数也可以使用“this”将一些值存储到自身中。 OOP 101 教导“this”仅适用于对象的实例。因此,每次执行一个函数时,该函数的一个新“实例”就有了一个新的“this”含义。

大多数人在尝试在匿名闭包函数中使用“this”时会感到困惑,例如:

(function(value) {
    this.value = value;
    $('.some-elements').each(function(elt){
        elt.innerHTML = this.value;        // uh oh!! possibly undefined
    });
})(2);

所以在这里,在 each() 内部,“this”不包含您期望的“值”(来自

this.value = value;

(function(value) {
    var self = this;            // small change
    self.value = value;
    $('.some-elements').each(function(elt){
        elt.innerHTML = self.value;        // phew!! == 2 
    });
})(2);

试试看;你会开始喜欢这种编程模式


“JS 中的一切都是对象”是不正确的,JavaScript 也有原始值,参见 bclary.com/2004/11/07/#a-4.3.2
原始值本身似乎有一些方法,例如 String#substring()、Number#toString() 等。因此,可能与那篇文章的命名法不同,它们的行为实际上就像是对象(它们是所有原型,即 String#substring() 实际上是:String.prototype.substring = function(){...})。如果我错了,请纠正我。
this 关键字与范围无关。此外,它在不是对象属性的函数中也有意义。
@arunjitsingh——对此有两种思想流派。我喜欢说“一切都是对象,但为了方便起见,有些可以用原语表示”的说法。 ;-)
this 不仅仅与范围有关。这完全是关于执行上下文,这与范围不同。 JavaScript 是词法范围的(意味着范围由代码的位置决定),但 this 由包含它的函数的调用方式决定 - 而不是该函数所在的位置。
c
carlodurso

自从这个话题出现以来,我为刚接触 this 主题的读者整理了一些要点。

这个值是怎么确定的?

我们使用这个类似于我们在英语等自然语言中使用代词的方式:“约翰跑得很快,因为他想赶火车。”相反,我们可以写成“……约翰正试图赶上火车”。

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 关键字是 most misunderstood,当:1) 我们借用一个使用 this 的方法,2) 我们将一个使用 this 的方法分配给一个变量,3) 一个使用 this 的函数被传递为一个回调函数,以及 4) this 用于闭包内部——一个内部函数。 (2)

https://i.stack.imgur.com/nPSkX.png

什么拥有未来

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)

参考:

这和对象原型,由凯尔辛普森。 © 2014 Getify 解决方案。 javascriptissexy.com - http://goo.gl/pvl0GX 安格斯克罗尔 - http://goo.gl/Z2RacU


P
Pang

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

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

所以如果我这样做了

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

element.onclick = someKindOfFunction;

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

<element onclick="someKindOfFunction()">

在后一种情况下,您只是引用该函数,而不是将其交给元素。因此,this 将引用窗口对象。


B
Ben Aston

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

如何调用函数(包括作为对象方法、调用和应用的使用、new 的使用) 箭头函数的词法上绑定的使用(它们采用其外部执行上下文的 this) 代码是严格的还是非严格的mode 是否使用 eval 调用代码

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

默认情况下,让大多数初学者感到困惑的是,当在 DOM 元素上引发事件后调用侦听器时,函数的 this 值就是 DOM 元素。

jQuery 使使用 jQuery.proxy 改变这一点变得微不足道。


说每个函数 call 都有一个作用域更正确一点。换句话说,Javascript 中的 this 令人困惑的是,它不是函数本身的固有属性,而是函数调用方式的产物。
@pointy 谢谢。在 js 中导致最混乱的原因是,在之前使用的所有语言(c#、c++)中, - this 不能被操纵 n 总是指向对象实例,而在 js 中它取决于并且可以在调用时更改使用 func.callfunc.bind 等的函数 – Sushil
this 引用函数的范围。 this 将引用特定对象(或可能是 undefined),正如您所说,可以使用 .call().apply() 更改该对象。函数的 范围 是(基本上,简化时)它可以访问哪些变量,这完全取决于函数的声明位置并且不能更改。
@Pointy:“说每个函数调用都有一个范围更正确一点。”更正确的说法是函数(现在是块)具有范围,函数调用具有上下文。范围定义了该范围内的代码可以使用的标识符。上下文定义了这些标识符绑定到什么。
“无论那个作用域是什么,都被“this”引用。” 不,this 和作用域在 ES5 和之前的版本中没有任何关系(例如,在编写这个答案时)。在 ES2015(又名 ES6)中,this 和范围是相关的一个 箭头函数的相当小方式(箭头函数中的 this 是从其封闭范围继承的),但 this 从不指范围。
A
Arman

丹尼尔,很棒的解释!在事件处理程序的情况下,关于这个和 this 执行上下文指针的良好列表的几句话。

简而言之,JavaScript 中的 this 指向当前函数从谁(或从谁的执行上下文)运行的对象,并且它始终是只读的,无论如何您都无法设置它(这样的尝试最终会出现 'Invalid分配信息的左侧。

对于事件处理程序: 内联事件处理程序,例如 <element onclick="foo">,会覆盖之前和之前附加的任何其他处理程序,所以要小心,最好不要使用内联事件委托。还要感谢 Zara Alaverdyan,他通过不同的辩论启发了我列出这个例子:)

el.onclick = foo; // 在 foo - obj 中

el.onclick = function () {this.style.color = '#fff';} // 对象

el.onclick = function() {doSomething();} // 在 doSomething - 窗口中

el.addEventListener('click',foo,false) // 在 foo - obj 中

el.attachEvent('onclick, function () { // this }') // 窗口,全部符合IE :)

g
gnerkus

HereJavaScriptthis 的一个很好的来源。

这是摘要:

global this 在浏览器中,在全局范围内,这是 windowobject