“this”关键字如何工作,何时应使用?

2022-08-29 22:01:20

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

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

如何工作,何时应使用?this


答案 1

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

技术说明书中的技术说明this

ECMAScript 标准通过抽象操作(缩写为 AOResolveThisBinding 对此进行了定义:

[AO] ResolveThisBinding [...] 使用正在运行的执行上下文的词法环境确定关键字的绑定。[步骤]:this

  1. Let envRec be GetThisEnvironment().
  2. 返回?envRec.GetThisBinding().

全局环境记录模块环境记录函数环境记录都有自己的 GetThis 绑定方法。

GetThisEnvironment AO 查找当前正在运行的执行上下文的 LexicalEnvironment,并查找最接近的上升环境记录(通过迭代访问其 [[OuterEnv]] 属性),该记录具有绑定(即 HasThisBinding 返回 true)。此过程以三种环境记录类型之一结束。

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

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

可以将代码示例放在 AST 资源管理器中,以便遵循规范详细信息。

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

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

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

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

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

GetThisBinding具体方法的全球环境记录环境[...][做这个]:

  1. Return envRec.[[GlobalThisValue]]。

全局环境记录的 [[GlobalThisValue]] 属性始终设置为主机定义的全局对象,该对象可通过 globalThis(在 Web 上,在 Node 上.js;MDN上的文档)。按照 InitializeHostDefinedRealm 的步骤,了解 [[GlobalThisValue]] 属性是如何形成的。windowglobal

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

模块已在 ECMAScript 2015 中引入。

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

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

该模块环境记录的具体方法 [...][做这个]:

  1. 未定义返回。

在模块中,的值始终位于全局上下文中。模块隐式处于严格模式thisundefined

3. 输入评估

有两种类型的呼叫:直接呼叫和间接呼叫。这种区别自 ECMAScript 第 5 版以来就存在。eval

  • 直接呼叫通常看起来像...或。。。(或...等)。1 仅当调用表达式适合窄模式时,它才是直接的。阿拉伯数字evaleval();(eval)();((eval))();
  • 间接调用涉及以任何其他方式调用函数引用。它可能是...,...,...,...,等等。鉴于 ,它也将是..., ....另外,给定 ,呼叫 ...也将是间接的。evalevaleval?.()(, 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 – Chapter 2: Strict Modearchived),了解何时可以使用间接调用。eval()

执行Val 执行代码。它创建一个新的声明性环境记录作为其词法环境,这是 GetThisEnvironment 获取值的位置。evalthis

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

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

这意味着:

  • 在直接评估中,值不会改变;它取自名为 .thiseval
  • 在间接评估中,该值是全局对象 ()。thisglobalThis

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

4. 输入函数代码

调用函数时,将发生输入函数代码的情况。

有四类语法可用于调用函数。

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

函数环境记录是声明性环境记录,用于表示函数的顶级作用域,如果该函数不是 ArrowFunction,则提供绑定。如果一个函数不是 ArrowFunction 函数和引用,它的函数环境记录也包含用于从函数内部执行方法调用的状态。thissupersuper

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

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

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

[[调用]]也调用普通调用BindThis,其中适当的此参数是根据以下因素确定的:

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

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

最后,正是这个字段是函数环境记录的 GetThisBinding AO 从以下位置获取值的位置:this

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

[...]
3. 返回环境。[[此值]]。

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

箭头函数

当计算箭头函数时,函数对象的 [[ThisMode]] 内部槽在普通函数创建中设置为“词法”。

普通呼叫BindThis,它采用函数F

  1. 这个模式成为F。[[此模式]]。
  2. 如果此模式词法模式,则返回 NormalCompletion()。[...]undefined

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

那么,箭头函数内部是什么呢?回顾 ResolveThisBindingGetThisEnvironmentHasThisBinding 方法显式返回 falsethis

HasThis绑定功能的具体方法 环境记录环境 [...][做这个]:

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

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

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

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

函数属性

在普通函数(,方法)中,由函数的调用方式决定。functionthis

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

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

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

或者:

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

在以下任何函数调用中,其中的值为 。1 个thisfuncrefObj

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

如果被调用的函数在语法上是基对象的属性,则此基函数将是调用的“引用”,在通常情况下,该调用的值将是 。这可以通过上面链接的评估步骤来解释;例如,在 (or) 中,CallMemberExpression 是整个表达式 ,它由 MemberExpressionArguments 组成。thisrefObj.func()refObj["func"]()refObj.func()refObj.func()

但是,也扮演三个角色,每个角色:refObj.funcrefObj

  • 它们都是表达,
  • 它们都是参考,并且
  • 它们都是值。

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

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

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

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

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

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

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

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

在传递或分配方法或使用逗号运算符时,也会发生这种情况。这就是参考记录和值之间的差异相关的地方。

注意函数:遵循规范,您会注意到只能返回函数对象(Value)本身,而不能返回参考记录。因此,基本引用将丢失。jjrefObj

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 调用此处this 值未定义。这在普通呼叫BindThisF:函数对象;thisArgumentthisValue 传递给 Call):

  1. 这个模式成为F。[[此模式]]。

[...]

  1. 如果此模式严格的,则让此值成为此参数
  2. 埃尔默
    1. 如果此参数定义为空,则
      1. globalEnv 成为 calleeRealm。[[GlobalEnv]]。
      2. [...]
      3. thisValue 成为 globalEnv。[[GlobalThisValue]]。
    2. 埃尔默
      1. 这个值成为!ToObject(thisArgument).
      2. 注意:ToObject 生成包装对象 [...]。

[...]

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

如果 IsPropertyReference 返回 false,则 EvaluateCall 将执行以下步骤:

  1. refEnv 成为 ref。[[基地]]。
  2. 断言:refEnv 是环境记录。
  3. Let thisValue be refEnv.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.bindthisArg 和基元

OrdinaryCallBind的步骤5的另一个结果,结合步骤6.2(规范中的6.b),是一个基元这个仅在“草率”模式下被强制到对象。

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

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

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

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

设置自定义值的示例:

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 将 设置为此处选择的 DOM 元素:this

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

构造函数、新的

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

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

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

调用 super 调用具有调用的词法作用域(函数环境记录)的值的超级构造函数。GetThisValue 对调用有特殊规则。它使用 BindThisValue 设置为该环境记录。superthis

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

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

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

静态块目前是 TC39 第 3 阶段的提案。静态块的工作方式与静态字段和方法相同:它们内部引用类本身。this

请注意,在方法和 getters/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:相当于; 等效于 。这在这篇2ality文章存档)中进行了解释。特别要了解如何计算括号表达式(o.f)()o.f()(f)()f()

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

3:每当规范说“让 ref 成为计算 X 的结果”时,X 就是你需要找到其评估步骤的某个表达式。例如,评估成员表达式CallExpression这些算法之一的结果。其中一些会产生参考记录

4:还有其他几个本机和宿主方法允许提供值,特别是 、等,它们接受 thisArg 作为其第二个参数。任何人都可以自己制作方法来改变,如,等。与往常一样,MDN提供了出色的文档。Array.prototype.mapArray.prototype.forEachthis(func, thisArg) => func.bind(thisArg)(func, thisArg) => func.call(thisArg)


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

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

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

  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.methodobj

  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.由于函数值 / 不是从对象中调用的,因此作为属性,绑定将为 。这与Python不同,在Python中,访问方法()会创建绑定的方法对象myFunobj.myMethodglobalThisobj.myMethod

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

    globalThis.箭头函数不会创建自己的绑定。词法作用域与初始全局作用域相同,也是如此。thisglobalThis

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

    globalThis.评估直接评估调用时,为 。但是,在评估代码中,不会从对象中调用,因此 this 绑定设置为全局对象。thisobjmyFun

  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.callthisArg

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

    它是 的实例。箭头函数不会更改绑定,因此它来自词法范围。因此,这与上面提到的类字段完全相同,例如 。请尝试将其更改为 。你得到你期望的结果吗?MyClsa = this;static arrow


答案 2

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

1. 在全球上下文中使用时

在全局上下文中使用时,它被绑定到全局对象(在浏览器中)thiswindow

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

当您在全局上下文中定义的函数内部使用时,仍然绑定到全局对象,因为该函数实际上是全局上下文的方法。thisthis

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

以上是全局对象的方法。因此,我们也可以在对象上调用它,如下所示:f1window

function f()
{
    return this;
}

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

2. 在对象方法内部使用时

当您在对象方法中使用关键字时,将绑定到“即时”封闭对象。thisthis

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. 调用无上下文功能时

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

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. 在构造函数内部使用时

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

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. 内部调用()、apply() 和 bind() 函数

  • 所有这些方法都在 上定义。Function.prototype
  • 这些方法允许编写一次函数,然后在不同的上下文中调用它。换句话说,它们允许指定在执行函数时将使用的值。它们还采用在调用原始函数时传递给原始函数的任何参数。this
  • fun.apply(obj1 [, argsArray])设置为 内部 的值,并调用 传递的元素 作为其参数。obj1thisfun()fun()argsArray
  • fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])- 设置为 inside 的值,调用传递作为其参数。obj1thisfun()fun()arg1, arg2, arg3, ...
  • fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])- 返回对函数的引用,其中内部 fun 绑定到 ,参数绑定到指定的参数。funthisobj1funarg1, arg2, arg3,...
  • 到现在为止,和 之间的区别一定已经变得很明显。 允许将参数指定为类似数组的对象,即具有数值属性和相应非负整数属性的对象。而允许直接指定函数的参数。两者都立即在指定的上下文中使用指定的参数调用函数。另一方面,仅返回绑定到指定值和参数的函数。我们可以通过将此返回的函数分配给变量来捕获对此函数的引用,稍后我们可以随时调用它。applycallbindapplylengthcallapplycallbindthis
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. 内部事件处理程序

  • 将函数直接分配给元素的事件处理程序时,直接在事件处理函数内使用将引用相应的元素。这种直接函数分配可以使用方法或通过传统的事件注册方法(如.)来完成。thisaddeventListeneronclick
  • 同样,当您直接在元素的事件属性(如 )中使用时,它引用元素。this<button onclick="...this..." >
  • 但是,间接使用通过事件处理函数内部调用的其他函数或事件属性解析为全局对象。thiswindow
  • 当我们使用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.这在ES6中箭头功能

在箭头函数中,将像公共变量一样运行:它将从其词法范围继承。定义箭头函数的 函数将是箭头函数的 。thisthisthis

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

(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