“this”关键字如何工作?

IT技术 javascript this
2020-12-10 21:18:39

我注意到thisStack Overflow 网站上似乎没有明确解释关键字是什么以及它如何在 JavaScript 中正确(和错误)使用。

我目睹了它的一些非常奇怪的行为,并且无法理解它为什么会发生。

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

6个回答

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

this规范中的技术说明

ECMAScript标准定义this经由抽象操作(缩写AOResolveThisBinding

[AO] ResolveThisBinding […]this使用正在运行的执行上下文的 LexicalEnvironment 来确定关键字的绑定[脚步]:

  1. envRecGetThisEnvironment ()。
  2. 返回 ?envRec .GetThisBinding()。

全局环境记录module环境记录函数环境记录都有自己的 GetThisBinding 方法。

所述GetThisEnvironment AO找到当前正在运行的执行上下文的LexicalEnvironment和查找最接近方兴未艾环境记录(通过迭代地访问他们的[[OuterEnv]]特性),其具有这种结合(即HasThisBinding返回)。此过程以三种环境记录类型之一结束。

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

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

您可以将代码示例放在AST 资源管理器中以跟随规范详细信息。

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

这是在顶层评估的脚本代码,例如直接在 a 中<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]] 属性始终设置为主机定义的全局对象,可通过globalThiswindow在 Web 上,global在 Node.js 上;MDN上的文档访问该对象按照InitializeHostDefinedRealm的步骤了解 [[GlobalThisValue]] 属性是如何形成的。

2.module中的全局执行上下文

ECMAScript 2015 中引入了module。

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

在module的初始全局执行上下文中,求值this会导致GetThisBinding采取以下步骤:

module环境记录的 GetThisBinding 具体方法 [...] [这样做]:

  1. 返回undefined

在module中,的值this总是undefined在全局上下文中。module隐式处于严格模式

3. 输入评估

有两种eval调用:直接调用间接调用这种区别自 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 对 JavaScript 中的(1, eval)('this') vs eval('this')?”的回答Dmitry Soshnikov 的 ECMA-262-5 的详细信息 - 第 2 章:严格模式已存档),了解何时可能使用间接eval()调用。

PerformEval执行eval代码。它创建一个新的声明性环境记录作为其 LexicalEnvironment,这是GetThisEnvironment 从中获取this值的地方。

然后,如果this出现在eval代码中,环境记录的GetThisBinding方法发现GetThisEnvironment被称为其返回值。

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

意思是:

  • 在直接 eval 中,this值不会改变;它取自称为eval.
  • 在间接 eval 中,this值是全局对象 ( globalThis)。

怎么样new Function — new Function与 类似eval,但不会立即调用代码;它创建了一个函数。装订任何地方都不会在这里适用,除非当函数被调用,如在下一小节解释其正常工作。

4. 输入功能

调用函数时会出现输入函数代码

调用函数有四类语法。

实际的函数调用发生在Call AO 处,调用时使用根据上下文确定thisValue这个参数在与调用相关的一长串调用中传递。Call调用函数[[Call]]内部槽。这将调用PrepareForOrdinaryCall,其中创建了一个新的函数环境记录

功能环境记录是一种声明环境记录被用于表示功能的顶层范围和,如果函数不是ArrowFunction,提供了一种this结合。如果函数不是ArrowFunction函数并引用了super,则其函数环境记录还包含用于super从函数内部执行方法调用的状态

另外,在一个函数 Environment Record 中有 [[ThisValue]] 字段:

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

NewFunctionEnvironment通话也设置功能环境的[[ThisBindingStatus]]属性。

[[Call]]还调用OrdinaryCallBindThis,其中适当的thisArgument是基于以下因素确定的:

  • 原始参考,
  • 函数的类型,以及
  • 代码是否处于严格模式

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

最后,这个字段是函数 Environment Record 的 GetThisBinding AOthis从以下位置获取值的地方:

GetThisBinding 函数的具体方法 Environment Record envRec [...] [这样做]:

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

同样,值的确切确定方式取决于许多因素;这只是一个总体概述。有了这个技术背景,让我们检查所有具体的例子。

箭头函数

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

OrdinaryCallBindThis,它接受一个函数F

  1. thisModeF .[[ThisMode]]。
  2. 如果thisMode词法的,则返回 NormalCompletion( undefined)。[…]

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

那么,this箭头函数内部是什么回顾ResolveThisBindingGetThisEnvironmentHasThisBinding 方法显式返回false

HasThisBinding 函数的具体方法 Environment Record envRec […] [这样做]:

  1. 如果envRec .[[ThisBindingStatus]] 是词法的,则返回false否则,返回true

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

这只是意味着,在箭头函数体中,this来自箭头函数的词法范围,或者换句话说(来自箭头函数 vs 函数声明/表达式:它们是否等价/可交换?):

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

功能属性

在正常功能(function方法),this来确定由所述函数是如何被调用

这就是这些“语法变体”派上用场的地方。

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

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

或者:

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

在以下任何一个函数调用中,this里面func都是refObj. 1

  • refObj.func()
  • refObj["func"]()
  • refObj?.func()
  • refObj.func?.()
  • refObj.func``

如果被调用的函数在语法上是基对象的一个​​属性,那么这个基将是调用的“引用”,在通常情况下,它是 的值this上面链接的评估步骤对此进行了解释;例如,在refObj.func()(或refObj["func"]())中,CallMemberExpression是整个表达式refObj.func(),它由MemberExpression refObj.funcArguments 组成 ()

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

  • 它们都是表达,
  • 它们都是参考,并且
  • 他们都是value观。

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

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

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

注意:关于.getter/setter 的工作方式与方法相同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电话呼叫thisValue不确定这里。这在OrdinaryCallBindThisF:函数对象;thisArgument传递给CallthisValue)中有所不同

  1. thisModeF .[[ThisMode]]。

[…]

  1. 如果thisMode严格的,则让thisValuethisArgument
  2. 别的,
    1. 如果thisArgumentundefinednull,则
      1. globalEnvcalleeRealm .[[GlobalEnv]]。
      2. […]
      3. thisValueglobalEnv .[[GlobalThisValue]]。
    2. 别的,
      1. thisValue成为!ToObject (thisArgument)。
      2. 注意:ToObject生成包装对象 [...]。

[…]

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

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

  1. refEnvref .[[Base]]。
  2. 断言:refEnv是环境记录。
  3. thisValuerefEnv .WithBaseObject()。

这就是未定义的thisValue可能来自:refEnvWithBaseObject()总是不确定的除了with声明。在这种情况下,thisValue将是绑定对象。

还有Symbol.unscopablesMDN 上的文档)来控制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值强制转换为对象

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

  • Function.prototype.apply(thisArg, argArray)
  • Function.prototype.{ call, bind}(thisArg, ...args)

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

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

设置自定义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")or时获得的对象类型相同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隐式返回,除非您显式返回您自己的非原始值。

Aclass是一种创建构造函数的新方法,在 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

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

调用使用super调用的词法范围(函数环境记录)this调用超级构造函数。GetThisValue有一个特殊的super调用规则它使用BindThisValue设置this为该环境记录。

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 中引入了实例字段和静态字段。

当 aclass被评估时,执行ClassDefinitionEvaluation,修改正在运行的执行上下文对于每个ClassElement

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

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

静态块目前是TC39 第 3 阶段的提案静态块的工作方式与静态字段和方法相同: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 文章存档)中进行了解释。特别是查看ParenthesizedExpression如何计算的

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

3:每当规范说“让ref成为对X求值的结果。”,那么X是一些您需要查找求值步骤的表达式。例如,评估MemberExpressionCallExpression这些算法之一的结果其中一些导致参考记录

4:也有允许提供一些其他本地和主机方法这个值,特别是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. 标记的行在初始全局执行上下文中进行评估。

  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. 当将函数作为对象的属性调用时,调用时会将this绑定设置为引用obj.method,即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. 由于函数值myFun/obj.myMethod不是从对象中调用的,因此作为属性,this绑定将是globalThis这与 Python 不同,在 Python 中访问方法 ( obj.myMethod) 创建绑定的方法对象

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

    globalThis. 箭头函数不会创建自己的this绑定。词汇范围是相同的初始全球范围内,所以thisglobalThis

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

    globalThis. 在评估直接 eval 调用时,thisobj但是,在 eval 代码中,myFun并没有从对象中调用,因此将this绑定设置为全局对象。

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

    obj. 该行myFun.call(obj);正在调用特殊的内置函数Function.prototype.call,它接受thisArg作为第一个参数。

  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());
    
       

    这是 的实例MyCls箭头函数不会改变this绑定,所以它来自词法作用域。因此,这与上面提到的类字段完全相同,例如a = this;. 尝试将其更改为static arrow. 你得到你期望的结果了吗?

另一种常见情况:EventHandlers 被调用时this设置为currentTarget事件的。这三个提议可以在未来包含:绑定运算符::显式 thisthis参数反射DOM 0 事件属性 likeonclick也值得注意:JS 代码隐式包装在一个with作用域中,一个作用域用于document单击元素,造成混乱this是具有属性的元素。
2021-03-05 21:18:39

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])设置obj1thisinside的值fun()并调用fun()传递的元素argsArray作为其参数。
  • fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])- 设置obj1thisinside的值fun()并调用作为其参数的fun()传递arg1, arg2, arg3, ...
  • fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])-返回参照功能funthis必然中的乐趣obj1和参数fun绑定到指定的参数arg1, arg2, arg3,...
  • 到现在为止applycall之间的区别bind应该已经很明显了。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.
  • 同样,当您this直接在<button onclick="...this..." >元素的事件属性(如内部使用时,它指的是该元素。
  • 然而,this通过在事件处理函数或事件属性内部调用的其他函数间接地使用解析为全局对象window
  • 当我们使用 Microsoft 的事件注册模型方法将函数附加到事件处理程序时,可以实现上述相同的行为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. thisES6中的箭头函数

在箭头函数中,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 

Javascript的 this

简单的函数调用

考虑以下函数:

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

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

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

如果您在 node.js 之类的环境中运行同一段代码,this将引用您应用程序中的全局变量。

现在,如果我们通过将语句添加"use strict";到函数声明的开头以严格模式运行它this将不再引用任一环境中的全局变量。这样做是为了避免严格模式下的混淆。this会,在这种情况下只是 log undefined,因为它就是这样,它没有定义。

在以下情况下,我们将看到如何操作 的值this

在对象上调用函数

有不同的方法可以做到这一点。如果您在 Javascript 中调用了像forEachand 之类的本地方法slice,您应该已经知道this在这种情况下变量是指Object您调用该函数的变量(请注意,在 javascript 中,几乎所有内容都是 an Object,包括Arrays 和Functions)。以下面的代码为例。

var myObj = {key: "Obj"};
myObj.logThis = function () {
    // I am a method
    console.log(this);
}
myObj.logThis(); // myObj is logged

如果 anObject包含持有 aFunction的属性,则该属性称为方法。此方法在调用时将始终将其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关键字调用函数将立即初始化Object类型Person
  2. thisObject的构造函数将其构造函数设置为Person另外,请注意,typeof awal只会返回Object
  3. 这个新的Object将被分配的原型Person.prototype这意味着Person原型中的任何方法或属性都可用于 的所有实例Person,包括awal.
  4. Person现在调用函数本身;this作为对新构造对象的引用awal

很简单吧?

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

在函数上调用函数:callapply

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

所有函数都继承自 global Function,它的许多方法中有两个是calland apply,两者都可用于操作this调用它们的函数中的值

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

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

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

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

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

介绍…… bind

bindcalland的兄弟apply它也是FunctionJavascript 中所有函数从全局构造函数继承的方法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]`

看到三者的区别了吗?这很微妙,但它们的用法不同。calland一样applybind也会覆盖this通过点方法调用设置的值

另请注意,这三个函数都没有对原始函数进行任何更改。call并且apply会从新构造的函数返回值,同时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原始作用域中的 值所以我们“复制”thisthat并使用了副本而不是this. 聪明,嗯?

指数:

  1. this默认保留什么
  2. 如果我们使用 Object-dot 符号将函数作为方法调用会怎样?
  3. 如果我们使用new关键字呢?
  4. 我们如何处理thiscallapply
  5. 使用bind.
  6. 复制this以解决嵌套范围问题。

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

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

(功能(值){
    this.value = 值;
    $('.some-elements').each(function(elt){
        elt.innerHTML = this.value; // 哦哦!!可能未定义
    });
})(2);

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

this.value = 值;
它上面)。因此,为了克服这个(无双关语)问题,开发人员可以:

(功能(值){
    var self = this; // 小变化
    self.value = value;
    $('.some-elements').each(function(elt){
        elt.innerHTML = self.value; // 呸!!== 2
    });
})(2);

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

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

由于此线程已增加,因此我为不熟悉该this主题的读者整理了一些要点

的value是如何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 strictthis在未绑定到任何对象的全局和匿名函数中的值为undefined

在以下情况下,this关键字最容易被误解:1) 我们借用了一个使用 的方法this,2) 我们将一个使用的方法分配给了this一个变量,3) 一个使用的函数this作为回调函数传递,以及 4)this在闭包中使用——一个内部函数。(2)

桌子

什么把握未来

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!

虽然箭头函数提供了 using 的替代方案bind(),但重要的是要注意,它们本质上是禁用传统this机制,以支持更广泛理解的词法范围。(1)


参考:

  1. this & Object Prototypes,作者:Kyle Simpson。© 2014 Getify 解决方案。
  2. javascriptissexy.com - http://goo.gl/pvl0GX
  3. 安格斯·克罗尔 - http://goo.gl/Z2RacU