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

它的行为似乎很奇怪,我不完全明白为什么。

这是如何工作的,何时使用?


当前回答

我对这个问题的看法与我希望有帮助的其他答案不同。

查看JavaScript的一种方法是看到只有一种方法可以调用函数1。它是

functionObject.call(objectForThis, arg0, arg1, arg2, ...);

始终为objectForThis提供一些值。

其他一切都是functionObject.call的语法糖

因此,其他一切都可以通过它如何转换为functionObject.call来描述。

如果你只是调用一个函数,那么这就是“全局对象”,在浏览器中就是窗口

函数foo(){console.log(此);}foo();//这是窗口对象

换句话说,

foo();

被有效地转化为

foo.call(window);

请注意,如果使用严格模式,则这将是未定义的

“使用严格”;函数foo(){console.log(此);}foo();//这是窗口对象

这意味着

换句话说,

foo();

被有效地转化为

foo.call(undefined);

在JavaScript中有+、-和*等运算符。还有点运算符。

这个运算符与右侧的函数和左侧的对象一起使用时,实际上意味着“将对象作为this传递给函数”。

实例

常量bar={name:'bar',foo(){console.log(此);},};bar.foo();//这是酒吧

换句话说,bar.foo()转换为consttemp=bar.foo;临时呼叫(bar);

注意,函数的创建方式并不重要(主要是…)。所有这些都会产生相同的结果

常量bar={name:'bar',fn1(){console.log(this);},fn2:function(){console.log(this);},fn3:其他函数,};函数otherFunction(){console.log(this)};bar.fn1();//这是酒吧bar.fn2();//这是酒吧bar.fn3();//这是酒吧

同样,这些都只是语法糖

{ const temp = bar.fn1; temp.call(bar); }
{ const temp = bar.fn2; temp.call(bar); }
{ const temp = bar.fn3; temp.call(bar); }

另一个褶皱是原型链。当您使用.b时,JavaScript首先查找属性b的a直接引用的对象。如果在对象上找不到b,则JavaScript将在对象的原型中查找b。

定义对象原型的方法多种多样,2019年最常见的是class关键字。出于这个目的,尽管这并不重要。重要的是,当它在对象a中查找属性b时,如果它在对象上找到属性b,或者在它的原型链中找到属性b(如果b最终是一个函数),那么上述规则同样适用。函数b引用将使用call方法调用,并将a作为objectForThis传递,如该答案顶部所示。

现在让我们想象一下,在调用另一个函数之前,我们创建了一个显式设置此值的函数,然后使用调用它。(点)运算符

函数foo(){console.log(此);}函数栏(){const objectForThis={name:“moo”}foo.call(objectForThis);//显式传递objectForThis}常量对象={酒吧};obj.bar();

在转换为使用调用之后,obj.bar()变为consttemp=obj.bar;临时调用(obj);。当我们进入bar函数时,我们调用foo,但我们显式地为objectForThis传递了另一个对象,所以当我们到达foo时,这是内部对象。

这是bind和=>函数有效的作用。它们是语法糖。他们有效地构建了一个新的不可见函数,就像上面的bar一样,它在调用指定的函数之前显式地设置了这个函数。在绑定的情况下,这将设置为传递给绑定的任何值。

函数foo(){console.log(此);}const bar=foo.bind({name:“moo”});//bind创建了一个新的不可见函数,该函数使用绑定对象调用foo。bar();//我们在这里传递给bar的objectForThis被忽略,因为//绑定创建的不可见函数将使用//我们在上面绑定的对象bar.call({name:“other”});

注意,如果functionObject.bind不存在,我们可以像这样创建自己的

function bind(fn, objectForThis) {
  return function(...args) {
    return fn.call(objectForthis, ...args);
  };
}

然后我们可以这样称呼它

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

const bar = bind(foo, {name:'abc'});

箭头函数,=>运算符是绑定的语法糖

const a = () => {console.log(this)};

const tempFn = function() {console.log(this)}; 
const a = tempFn.bind(this);

与bind一样,会创建一个新的不可见函数,该函数使用objectForThis的绑定值调用给定函数,但与bind不同的是,要绑定的对象是隐式的。当使用=>运算符时,这就是发生的情况。

所以,就像上面的规则一样

const a = () => { console.log(this); }  // this is the global object
'use strict';
const a = () => { console.log(this); }  // this is undefined
function foo() {
  return () => { console.log(this); }
}

const obj = {
  foo,
};
const b = obj.foo();
b();

obj.foo()转换为consttemp=obj.foo;临时调用(obj);这意味着foo中的箭头运算符将obj绑定到一个新的不可见函数,并返回分配给b.b()的新的不可视函数。该不可见函数忽略传递给它的this,并将obj作为objectForThis`传递给箭头函数。

上面的代码翻译为

function foo() {
  function tempFn() {
    console.log(this);
  }
  return tempFn.bind(this);
}

const obj = {
  foo,
};
const b = obj.foo();
b.call(window or undefined if strict mode);

1apply是另一个类似于调用的函数

functionName.apply(objectForThis, arrayOfArgs);

但从概念上讲,ES6甚至可以将其转换为

functionName.call(objectForThis, ...arrayOfArgs);

其他回答

这能帮什么忙?(javascript中“this”的大部分混淆是因为它通常不链接到您的对象,而是链接到当前的执行范围——这可能不是它的确切工作方式,但对我来说总是这样——请参阅文章以获得完整的解释)

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

规范中的技术说明

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

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

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

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

这个值通常取决于代码是否处于严格模式。

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

您可以将代码样本放在AST资源管理器中,以遵循规范细节。

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

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

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

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

在脚本的初始全局执行上下文中,计算此值会导致GetThisBinding采取以下步骤:

全局环境记录envRec的GetThisBinding具体方法[…][执行此操作]:返回envRec。[[GlobalThisValue]]。

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

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

ECMAScript 2015中引入了模块。

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

当在模块的初始全局执行上下文中时,计算此值会导致GetThisBinding采取以下步骤:

模块Environment Record[…]的GetThisBinding具体方法[执行此操作]:返回未定义。

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

3.输入eval代码

有两种eval调用:直接调用和间接调用。这种区别自ECMAScript第5版以来就存在。

直接eval调用通常看起来像eval(…);或(eval)(…);(或((eval))(…);,1只有在调用表达式符合窄模式时才是直接的。2间接eval调用涉及以任何其他方式调用函数引用eval。可能是eval?。(…),(…,eval)(…)、window.eval(…)和eval.call(…,…)等。给定常量aliasVal1=eval;window.aliasVal2=eval;,它也可以是aliasVal1(…)、aliasVal2(…)。另外,给定const originalEval=eval;window.eval=(x)=>原始评估(x);,调用eval(…)也是间接的。

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

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

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

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

在直接求值中,它将基于当前运行的执行上下文的LexicalEnvironment。在间接求值中,它将基于执行间接求值的Realm Record的[[GlobalEnv]]属性(全局环境记录)。

这意味着:

在直接求值中,this值不变;它取自称为eval的词法范围。在间接求值中,this值是全局对象(globalThis)。

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

4.输入功能代码

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

调用函数有四类语法。

EvaluateCall AO针对以下三项进行:3正常函数调用可选链接调用标记的模板对这一项执行EvaluateNew:3构造函数调用

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

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

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

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

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

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

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

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

最后,这个字段是函数Environment Record的GetThisBinding AO从中获取值的位置:

函数Environment Record envRec〔…〕〔执行此操作〕的GetThisBinding具体方法:[…]3.返回envRec。[[ThisValue]]。

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

箭头功能

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

在OrdinaryCallBindThis,它接受函数F:

设thisMode为F.[[thisMode]]。如果thisMode是词法的,则返回NormalCompletion(未定义)。[…]

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

那么,在箭头函数中这是什么呢?回顾ResolveThisBinding和GetThisEnvironment,HasThisBinding方法显式返回false。

函数Environment Record envRec[…]的HasThisBinding具体方法[执行此操作]:如果envRec。[[ThisBindingStatus]]是词法的,返回false;否则,返回true。

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

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

箭头函数没有自己的this[…]绑定。相反,[这个标识符]像任何其他变量一样在词法范围内解析。这意味着,在箭头函数内部,this[指的是]在定义箭头函数的环境中的[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”]())中,CallMemberExpression是整个表达式refObj.func(),它由MemberExpression refObj.unc和Arguments()组成。

此外,refObj.func和refObj分别扮演三个角色:

它们都是表达式,他们都是推荐人,而且它们都是价值观。

refObj.func作为值是可调用函数对象;使用相应的引用来确定该绑定。

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

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

注意:关于这一点,Getters/Setter的工作方式与方法相同。简单的财产不会影响执行上下文,例如,这里是全局范围:

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

没有基引用、严格模式和

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

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

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

注意函数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调用此处未定义thisValue的Call。这在OrdinaryCallBindThis(F:函数对象;thisArgument:传递给Call的thisValue)中产生了不同:

设thisMode为F.[[thisMode]]。[…]如果thisMode是严格的,则将thisValue设为thisArgument。其他的如果thisArgument未定义或为null,则让globalEnv被称为Realm。[[GlobalEnv]]。[…]让thisValue为globalEnv。[[GlobalThisValue]]。其他的让这个价值成为现实!ToObject(thisArgument)。注意:ToObject生成包装对象[…]。[…]

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

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

设refEnv为ref[[Base]]。断言:refEnv是一个环境记录。让thisValue为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`.
}

and:

"use strict";

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

f(); // Logs `undefined`.

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

请注意,在计算此值时,在哪里定义正常函数并不重要。

.call、.apply、.bind、thisArg和基元

OrdinaryCallBindThis的第5步与第6.2步(规范中的6.b)相结合的另一个结果是,一个基元只有在“草率”模式下才会将此值强制到一个对象。

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

函数.原型.应用(thisArg,argArray)Function.prototype.{call,bind}(thisArg,…args)

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

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

设置自定义此值的示例:

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]]方法。如果函数是基构造函数(即不是类扩展…{…}),它将thisArgument设置为从构造函数的原型创建的新对象。在构造函数中对此设置的财产将结束于结果实例对象。这是隐式返回的,除非您显式返回自己的非基元值。

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

超级的

使用new的行为的例外是类扩展…{…},如上所述。派生类在调用时不会立即设置其此值;它们只在通过一系列超级调用到达基类时才会这样做(在没有自己的构造函数的情况下隐式发生)。不允许在调用super之前使用此选项。

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

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

对类求值时,执行ClassDefinitionEvaluation,修改正在运行的执行上下文。对于每个ClassElement:

如果一个字段是静态的,那么它指的是类本身,如果一个字段不是静态的,那么这将引用该实例。

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

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

注意,在方法和getters/setter中,这就像在普通函数财产中一样工作。

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()。这在本文(存档)中进行了解释。请特别查看如何计算带圆括号的表达式。

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

3:每当规范中说“让ref是对X求值的结果”,那么X就是需要找到求值步骤的表达式。例如,计算MemberExpression或CallExpression是这些算法之一的结果。其中一些会生成参考记录。

4:还有其他几种本机和主机方法允许提供this值,特别是Array.prototype.map、Array.prototype.forEach等,它们接受thisArg作为第二个参数。任何人都可以使用自己的方法来改变这一点,比如(func,thisArg)=>func.bind(thisArg),(func、thisAg)=>func.call(thisAg)等。一如既往,MDN提供了很好的文档。


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

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

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

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

Javascript就是这个

简单函数调用

考虑以下功能:

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

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

在浏览器中运行时,此值将作为窗口记录。这是因为window是web浏览器范围内的全局变量。

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

现在,如果我们通过添加语句“usestrict”在严格模式下运行此操作;在函数声明的开头,这将不再引用这两个环境中的全局变量。这样做是为了避免严格模式中的混淆。在这种情况下,这将只是log undefined,因为这就是它,它没有被定义。

在下面的例子中,我们将看到如何操纵这个值。

对对象调用函数

有不同的方法可以做到这一点。如果您在Javascript中调用了本机方法,如forEach和slice,那么您应该已经知道,在这种情况下,this变量指的是调用该函数的Object(注意,在Javascript中,几乎所有的东西都是Object,包括数组和函数)。以以下代码为例。

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

如果对象包含包含函数的属性,则该属性称为方法。调用此方法时,将始终将此变量设置为与其关联的对象。对于严格模式和非严格模式都是如此。

请注意,如果一个方法存储(或复制)在另一个变量中,则对该方法的引用将不再保留在新变量中。例如:

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

很简单,嗯?

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

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

所以,是的,因为函数也是Object(实际上是Javascript中的第一类变量),所以即使函数也有方法。。。嗯,功能本身。

所有函数都继承自全局函数,它的许多方法中有两个是调用和应用,这两个方法都可以用于在调用它们的函数中操纵此值。

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

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

apply与callaccept几乎相同,它只接受两个参数:thisArg和一个包含要传递给函数的参数的数组。因此,可以将上述调用转换为如下应用:

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

请注意,调用和应用可以覆盖我们在第二个项目中讨论的点方法调用集的值。足够简单:)

呈现。。。。绑定

bind是应召而来的兄弟。它也是Javascript中所有函数从全局函数构造函数继承的方法。绑定和调用/应用之间的区别在于,调用和应用实际上都会调用函数。另一方面,bind会返回一个预设了thisArg和参数的新函数。让我们举个例子来更好地理解这一点:

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]`

看到三者之间的区别了吗?这很微妙,但它们的用法不同。与调用和应用一样,bind也将通过点方法调用覆盖此集合的值。

还要注意,这三个函数都没有对原始函数进行任何更改。call和apply将返回新构造的函数的值,而bind将返回新构建的函数本身,以便调用。

额外的东西,复制这个

有时,您不喜欢这样一个事实,即这会随范围而改变,尤其是嵌套范围。看看下面的例子。

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"
        });
    }
  };

在上面的代码中,我们看到这个值随嵌套范围而改变,但我们希望从原始范围中获得这个值。所以我们将这个“复制”到那个,并使用副本代替这个。聪明,嗯?

索引:

默认情况下,此中包含什么?如果我们将函数作为一个带有Object点符号的方法调用呢?如果我们使用新关键字呢?我们如何通过调用和应用来处理这一点?使用bind。复制此以解决嵌套范围问题。

由于这篇文章的篇幅有所增加,我为读者整理了一些新的观点。

这个值是如何确定的?

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

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

在对象调用定义它的函数之前,不会为其赋值。在全局范围中,所有全局变量和函数都在窗口对象上定义。因此,这在全局函数中引用(并且具有的值)全局窗口对象。

当使用strict时,在未绑定到任何对象的全局函数和匿名函数中,它的值为undefined。

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

未来是什么

在ECMA脚本6中定义,箭头函数采用来自封闭(函数或全局)范围。

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)


参考文献:

凯尔·辛普森(Kyle Simpson)的《this&Object Prototypes》。©2014 Getify解决方案。javascriptissxy.com-http://goo.gl/pvl0GX 安格斯·克罗尔http://goo.gl/Z2RacU

关于如何在JavaScript中解释“this”关键字,存在很多困惑。希望这篇文章能让所有这些人一劳永逸。还有更多。请仔细阅读整篇文章。预先警告,这篇文章很长。

无论使用的上下文如何,“this”总是引用Javascript中的“当前对象”。然而,“当前对象”是什么根据上下文而不同。上下文可能正好是以下6项中的1项:

全局(即在所有功能之外)内部直接“非绑定函数”调用(即尚未通过调用functionName.bind绑定的函数)内部间接“非绑定函数”通过functionName.Call和functionName.apply调用在“绑定函数”内部调用(即通过调用functionName.bind绑定的函数)通过“新建”创建对象内联DOM事件处理程序内部

以下逐一介绍了每种情况:

全局上下文(即所有功能外部):在所有功能之外(即在全局上下文中)对象”(因此“this”的值)始终是浏览器的“窗口”对象。内部直接“非绑定函数”调用:在直接“非绑定函数”调用中调用的函数调用变为“当前对象”(因此“this”的值)。如果在没有显式当前对象的情况下调用函数,则当前对象要么是“窗口”对象(对于非严格模式),要么是未定义的(对于严格模式)。中定义的任何函数(或变量)全局上下文自动成为“窗口”对象的属性。例如,假设函数在全局上下文中定义为函数UserDefinedFunction(){警报(this)}它成为窗口对象的属性,就像您定义了它作为window.UserDefinedFunction=函数(){警报(this)} 在“非严格模式”下,直接通过“UserDefinedFunction()”调用/调用此函数将自动调用/调用它作为“window.UserDefinedFunction()”,将“window”作为“UserDefinedFunction”中的“当前对象”(以及“this”的值)。在“非严格模式”中调用此函数将导致以下结果UserDefinedFunction()//显示[object Window],因为它自动被调用为窗口。UserDefined Function()在“严格模式”下,通过“UserDefinedFunction()”将“NOT”自动将其调用为“window.UserDefinedFunctions()”。因此,“当前对象”(以及“this”的值)“UserDefinedFunction”应未定义。在“严格模式”下调用此函数将导致以下结果UserDefinedFunction()//显示未定义然而,使用窗口对象显式调用它将导致以下内容window.UserDefinedFunction()//“无论模式如何,始终显示[object window]。”让我们看看另一个例子。请查看以下代码函数UserDefinedFunction(){警报(this.a+“,”+this.b+“,“+this.c+”,“+this.d”)}变量o1={a: 1中,b: 2,f: 用户定义函数}无功氧气={c: 3中,d: 4中,f: 用户定义函数}o1.f()//应显示1,2,未定义,未定义o2.f()//应显示未定义、未定义、3,4在上面的示例中,我们看到当“UserDefinedFunction”通过o1调用,“this”取值为o1显示其财产“a”和“b”的值。价值“c”和“d”的定义与o1相同没有定义这些财产类似地,当通过o2调用“UserDefinedFunction”时,“this”取o2的值,并显示其财产“c”和“d”的值。“a”和“b”的值显示为未定义,因为o2未定义这些财产。间接“非绑定函数”内部通过functionName.Call和functionName.apply调用:当通过调用“非绑定函数”时functionName.call或functionName.apply,“当前对象”(因此“this”的值)设置为传递给调用/应用的“this”参数(第一个参数)。下面的代码也演示了这一点。函数UserDefinedFunction(){警报(this.a+“,”+this.b+“,“+this.c+”,“+this.d”)}变量o1={a: 1中,b: 2,f: 用户定义函数}无功氧气={c: 3中,d: 4中,f: 用户定义函数}UserDefinedFunction.call(o1)//应显示1,2,未定义,未定义UserDefinedFunction.apply(o1)//应显示1,2,未定义,未定义UserDefinedFunction.call(o2)//应显示未定义、未定义、3、4UserDefinedFunction.apply(o2)//应显示未定义、未定义、3、4o1.f.call(o2)//应显示未定义、未定义、3、4o1.f.apply(o2)//应显示未定义、未定义、3、4o2.f.call(o1)//应显示1,2,未定义,未定义o2.f.apply(o1)//应显示1,2,未定义,未定义上述代码清楚地表明,任何“NON”的“this”值绑定函数”可以通过调用/应用来更改。此外,如果“this”参数未显式传递给调用/应用,“current object”(因此“this”的值)在非严格模式下设置为“window”,在严格模式下为“undefined”。在“绑定函数”调用(即通过调用functionName.bind绑定的函数)内部:绑定函数是其“this”值为固定的下面的代码演示了“this”在有界函数的函数UserDefinedFunction(){警报(this.a+“,”+this.b+“,“+this.c+”,“+this.d”)}变量o1={a: 1中,b: 2,f: 用户定义函数,bf:空}无功氧气={c: 3中,d: 4中,f: 用户定义函数,bf:空}var bound1=用户定义函数.bind(o1);//将函数“bound1”的“this”值永久固定到对象o1bound1()//应显示1,2,未定义,未定义var bound2=UserDefinedFunction.bind(o2);//将函数“bound2”的“this”值永久固定到对象o2bound2()//应显示undefined,undefineed,3,4var边界3=o1.f.bind(o2);//将函数“bound3”的“this”值永久固定到对象o2bound3()//应显示undefined,undefineed,3,4var绑定4=o2.f.bind(o1);//永久地将函数“bound4”的“this”值固定到对象o1bound4()//应显示1,2,undefined,undefineo1.bf=UserDefinedFunction.bind(o2)//将函数“o1.bf”的“this”值永久固定到对象o2o1.bf()//应显示未定义、未定义、3,4o2.bf=UserDefinedFunction.bind(o1)//将函数“o2.bf”的“this”值永久固定到对象o1o2.bf()//应显示1,2,未定义,未定义bound1.call(o2)//仍应显示1,2,undefined,undefine。“call”不能更改绑定函数的“this”值bound1.apply(o2)//仍应显示1,2,undefined,undefine。“apply”不能更改绑定函数的“this”值o2.bf。

以下总结了整篇文章

在全局上下文中,“this”总是指“window”对象每当调用函数时,都会在对象(“当前对象”)。如果未明确提供当前对象,当前对象是NON Strict中的“窗口对象”默认情况下,模式和严格模式中的“未定义”。非绑定函数中“this”的值是对调用该函数的上下文中的对象(“当前对象”)的引用非绑定函数中“this”的值可以由调用和应用函数的方法。“this”的值对于Bound函数是固定的,不能被函数的调用和应用方法覆盖。绑定和已绑定函数不会更改“this”的值。它保持设置为第一个绑定函数设置的值。构造函数中“this”的值是已创建并初始化内联DOM事件处理程序中“this”的值是引用指定事件处理程序的元素。