开发者问题收集

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

2010-06-27
467681

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

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

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

3个回答

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

规范中 this 的技术描述

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

The [AO] ResolveThisBinding […] determines the binding of the keyword this using the LexicalEnvironment of the running execution context . [Steps]:

  1. Let envRec be GetThisEnvironment ().
  2. Return ? envRec .GetThisBinding().

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

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

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 采取以下步骤:

The GetThisBinding concrete method of a global Environment Record envRec […] [does this]:

  1. Return envRec .[[GlobalThisValue]].

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

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

ECMAScript 2015 中引入了模块。

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

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

The GetThisBinding concrete method of a module Environment Record […] [does this]:

  1. Return undefined .

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

3.输入 eval 代码

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 章:严格模式 ( archived ) 以了解何时可以使用间接 eval() 调用。

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

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

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

这意味着:

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

那么 new Function 呢?  —  new Function eval 类似,但它不会立即调用代码;它会创建一个函数。 this 绑定在这里的任何地方都不适用,除非调用函数,这可以正常工作,如下一小节所述。

4.输入 函数 代码

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

调用函数的语法有四类。

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

A function Environment Record is a declarative Environment Record that is used to represent the top-level scope of a function and, if the function is not an ArrowFunction , provides a this binding. If a function is not an ArrowFunction function and references super , its function Environment Record also contains the state that is used to perform super method invocations from within the function.

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

This is the this value used for this invocation of the function.

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

[[Call]] 还调用 OrdinaryCallBindThis ,其中适当的 thisArgument 取决于:

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

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

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

The GetThisBinding concrete method of a function Environment Record envRec […] [does this]:

[…]
3. Return envRec .[[ThisValue]].

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

箭头函数

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

OrdinaryCallBindThis ,它接受一个函数 F :

  1. Let thisMode be F .[[ThisMode]].
  2. If thisMode is lexical , return NormalCompletion( undefined ). […]

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

那么,箭头函数中的 this 是什么?回顾 ResolveThisBinding GetThisEnvironment HasThisBinding 方法明确返回 false

The HasThisBinding concrete method of a function Environment Record envRec […] [does this]:

  1. If envRec .[[ThisBindingStatus]] is lexical , return false ; otherwise, return true .

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

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

Arrow functions don’t have their own this […] binding. Instead, [this identifier is] resolved in the lexical scope like any other variable. That means that inside an arrow function, this [refers] to the [value of this ] in the environment the arrow function is defined in (i.e. “outside” the arrow function).

函数 属性

在普通函数( 函数 方法 )中, this 是取决于 函数的调用方式

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

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

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

或者:

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

在以下任何函数调用中, func 内的 this 值将是 refObj . 1

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

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

但同时, refObj.funcrefObj 分别扮演三种角色:

  • 它们都是表达式,
  • 它们都是引用,并且
  • 它们都是值。

refObj.func 作为 是可调用函数对象;相应的 reference 用于确定 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 在此处调用 Call ,其中 thisValue undefined 。这对 OrdinaryCallBindThis 产生了影响( F :函数对象; thisArgument :传递给 Call thisValue ):

  1. Let thisMode be F .[[ThisMode]].

[…]

  1. If thisMode is strict , let thisValue be thisArgument .
  2. Else,
    1. If thisArgument is undefined or null , then
      1. Let globalEnv be calleeRealm .[[GlobalEnv]].
      2. […]
      3. Let thisValue be globalEnv .[[GlobalThisValue]].
    2. Else,
      1. Let thisValue be ! ToObject (thisArgument).
      2. NOTE: ToObject produces wrapper objects […].

[…]

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

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

  1. Let refEnv be ref .[[Base]].
  2. Assert: refEnv is an Environment Record.
  3. Let thisValue be refEnv .WithBaseObject().

未定义的 thisValue 可能来自此处: refEnv WithBaseObject () 始终为 未定义 除了 with 语句。在这种情况下, thisValue 将成为绑定对象。

还有 Symbol.unscopables ( 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 与步骤 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 使用指定的 thisArg 直接映射到 Call .bind 使用 BoundFunctionCreate 创建一个绑定函数。它们有 自己的 [[Call]] 方法 ,用于查找函数对象的 [[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);

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

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

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 调用有特殊规则。它使用 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 中引入了实例字段和静态字段。

评估 class 时,将执行 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 ,不能是属性,必须具有恰好为 “eval” 的 [[ReferencedName]],并且必须是 %eval% 内在对象。

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

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); // 这里的 `this` 是什么?
    }
    

    globalThis 。标记的行在初始全局执行上下文中进行评估。

  2. const obj = {};
    
    function myFun(){
    return { // 这里的 `this` 是什么?
    "is obj": this === obj,
    "is globalThis": this === globalThis
    };
    }
    
    obj.method = myFun;
    
    console.log(obj.method());
    
    

    obj 。当将函数作为对象的属性调用时,它会将 this 绑定设置为引用 obj.method base ,即 obj

  3. const obj = {
    myMethod: function(){
    return { // 这里的 `this` 是什么?
    "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: () => ({ // 这里的 `this` 是什么?
    "is obj": this === obj,
    "is globalThis": this === globalThis
    })
    };
    
    console.log(obj.myFun());
    
    

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

  5. function myFun(){
    console.log(this); // 这里的 `this` 是什么?
    }
    
    const obj = {
    myMethod: function(){
    eval("myFun()");
    }
    };
    
    obj.myMethod();
    

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

  6. <c

2010-06-27

与其他语言相比,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 运算符创建其实例。
  • 同样在对象文字方法中,我们可以使用点运算符明确地将成员添加到已定义的对象中。这只会添加到特定实例中。但是,我已将变量添加到函数原型,以便它反映在函数的所有实例中。

下面,我尝试了上面使用 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 的引用,其中 fun 中的 this 绑定到 obj1 ,并且 fun 的参数绑定到指定的参数 arg1, arg2, arg3,...
  • 现在, 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
  • 当我们使用 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 箭头函数中的 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 
Mahesha999
2013-07-07

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 在这种情况下只会记录 undefined ,因为它就是这样,没有定义。

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

在对象上调用函数

有多种方法可以做到这一点。如果您已经在 J​​avascript 中调用过本机方法,如 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 关键字调用该函数将立即初始化 Person 类型的 Object
  2. Object 的构造函数将其构造函数设置为 Person 。另请注意, typeof awal 只会返回 Object
  3. 这个新的 Object 将被分配 Person.prototype 的原型。这意味着 Person 原型中的任何方法或属性都可用于 Person 的所有实例,包括 awal
  4. 函数 Person 本身现在被调用; this 是对新构造的对象 awal 的引用。

非常简单,对吧?

请注意,官方 ECMAScript 规范中没有任何地方指出此类函数是实际的 构造函数 函数。它们只是普通函数,并且 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 的典型示例。它基本上采用第一个参数并将函数 foo 中的 this 设置为对 thisArg 的引用。传递给 call 的所有其他参数都作为参数传递给函数 foo
因此,上述代码将在控制台中记录 {myObj: "is cool"}, [1, 2, 3] 。这是在任何函数中更改 this 值的相当不错的方法。

applycall 几乎相同,但它仅接受两个参数: thisArg 和一个包含要传递给函数的参数的数组。因此,上述 call 调用可以转换为 apply ,如下所示:

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

请注意, callapply 可以覆盖我们在第二条中讨论的点方法调用设置的 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. 如果我们使用 Object-dot 表示法将函数作为方法调用会怎样?
  3. 如果我们使用 new 关键字会怎样?
  4. 如何使用 callapply 操作 this
  5. 使用 bind
  6. 复制 this 以解决嵌套作用域问题。
user3459110
2014-10-26