我希望找到一个“this”关键字的明确解释,以及如何正确使用它。
它的行为似乎很奇怪,我不完全明白为什么。
这是如何工作的,何时使用?
我希望找到一个“this”关键字的明确解释,以及如何正确使用它。
它的行为似乎很奇怪,我不完全明白为什么。
这是如何工作的,何时使用?
当前回答
JavaScript中的this总是指正在执行的函数的“所有者”。
如果未定义显式所有者,则引用最顶层的所有者窗口对象。
所以如果我做了
function someKindOfFunction() {
this.style = 'foo';
}
element.onclick=someKindOfFunction;
这将引用元素对象。但是要小心,很多人都会犯这个错误。
<element onclick=“someKindOfFunction()”>
在后一种情况下,您只是引用函数,而不是将其交给元素。因此,这将引用窗口对象。
其他回答
总结此Javascript:
它的值取决于函数的调用方式,而不是创建位置!通常,该值由点左侧的对象确定。(全球空间中的窗口)在事件侦听器中,this的值引用调用事件的DOM元素。当使用new关键字调用函数时,this的值引用新创建的对象您可以使用以下函数操纵此值:call、apply、bind
例子:
let对象={prop1:function(){console.log(this);}}object.prop1();//对象位于点的左侧,因此这是对象constmyFunction=object.prop1//我们将函数存储在变量myFunction中myFunction();//我们在全球空间//myFunction是全局对象的属性//因此,它记录窗口对象
事件侦听器示例:
document.querySelector('.foo').addEventListener('click',函数(){console.log(this);//这是指调用eventListener的DOM元素})document.querySelector('.foo').addEventListener('click',()=>{console.log(this);//提示,es6箭头函数没有自己的绑定到此v})//因此,这将记录全局对象.foo:悬停{颜色:红色;光标:指针;}<div class=“foo”>单击我</div>
示例构造函数:
职能人员(姓名){this.name=名称;}const me=新人('Willem');//当使用new关键字时,构造函数中的this将引用新创建的对象console.log(me.name);//因此,name属性被放置在使用new关键字创建的对象上。
这是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中,作用域与变量的可见性有关,作用域通过使用函数实现。(阅读有关范围的更多信息)
上下文:上下文与对象相关。它指函数所属的对象。当您使用JavaScript“this”关键字时,它指的是函数所属的对象。例如,在函数内部,当你说:“this.accoutNumber”时,你指的是属性“accoutNumber”,该属性属于该函数所属的对象。
如果对象“myObj”有一个名为“getMyName”的方法,当JavaScript关键字“this”在“getMyName”中使用时,它将引用“myObj”。如果函数“getMyName”是在全局范围内执行的,那么“this”是指窗口对象(严格模式除外)。
现在让我们看看一些示例:
<script>
console.log('What is this: '+this);
console.log(this);
</script>
在浏览器输出中运行abobve代码将:
根据窗口对象上下文中的输出,也可以看到窗口原型引用了对象。
现在让我们尝试函数内部:
<script>
function myFunc(){
console.log('What is this: '+this);
console.log(this);
}
myFunc();
</script>
输出:
输出是相同的,因为我们将“this”变量记录在全局范围中,并将其记录在函数范围中,我们没有更改上下文。在这两种情况下,上下文都是相同的,与寡妇对象相关。
现在让我们创建自己的对象。在javascript中,可以通过多种方式创建对象。
<script>
var firstName = "Nora";
var lastName = "Zaman";
var myObj = {
firstName:"Lord",
lastName:'Baron',
printNameGetContext:function(){
console.log(firstName + " "+lastName);
console.log(this.firstName +" "+this.lastName);
return this;
}
}
var context = myObj.printNameGetContext();
console.log(context);
</script>
输出:
因此,从上面的示例中,我们发现“this”关键字引用的是与myObj相关的新上下文,而myObject也具有到Object的原型链。
让我们再举一个例子:
<body>
<button class="btn">Click Me</button>
<script>
function printMe(){
//Terminal2: this function declared inside window context so this function belongs to the window object.
console.log(this);
}
document.querySelector('.btn').addEventListener('click', function(){
//Terminal1: button context, this callback function belongs to DOM element
console.log(this);
printMe();
})
</script>
</body>
输出:有道理吧?(阅读评论)
如果您无法理解上面的示例,让我们尝试使用自己的回调;
<script>
var myObj = {
firstName:"Lord",
lastName:'Baron',
printName:function(callback1, callback2){
//Attaching callback1 with this myObj context
this.callback1 = callback1;
this.callback1(this.firstName +" "+this.lastName)
//We did not attached callback2 with myObj so, it's reamin with window context by default
callback2();
/*
//test bellow codes
this.callback2 = callback2;
this.callback2();
*/
}
}
var callback2 = function (){
console.log(this);
}
myObj.printName(function(data){
console.log(data);
console.log(this);
}, callback2);
</script>
输出:
现在,让我们了解范围、自我、IIFE和THIS的行为
var color = 'red'; // property of window
var obj = {
color:'blue', // property of window
printColor: function(){ // property of obj, attached with obj
var self = this;
console.log('In printColor -- this.color: '+this.color);
console.log('In printColor -- self.color: '+self.color);
(function(){ // decleard inside of printColor but not property of object, it will executed on window context.
console.log(this)
console.log('In IIFE -- this.color: '+this.color);
console.log('In IIFE -- self.color: '+self.color);
})();
function nestedFunc(){// decleard inside of printColor but not property of object, it will executed on window context.
console.log('nested fun -- this.color: '+this.color);
console.log('nested fun -- self.color: '+self.color);
}
nestedFunc(); // executed on window context
return nestedFunc;
}
};
obj.printColor()(); // returned function executed on window context
</script>
输出非常棒,对吧?
关于此关键字的一些信息
让我们将此关键字记录到全局范围内的控制台中,无需任何代码
console.log(this)
在客户端/浏览器中,此关键字是一个全局对象,它是窗口
console.log(this === window) // true
and
在Server/Node/Javascript运行时中,此关键字也是一个全局对象,即module.exports
console.log(this === module.exports) // true
console.log(this === exports) // true
请记住,导出只是对module.exports的引用
javascript中的每个执行上下文都有一个this参数,该参数由以下参数设置:
如何调用函数(包括作为对象方法、调用和应用的使用、new的使用)绑定的使用箭头函数的词汇(它们采用外部执行上下文的this)代码是严格模式还是非严格模式是否使用eval调用代码
可以使用func.all、func.apply或func.bind设置此值。
默认情况下,当在DOM元素上引发事件后调用监听器时,函数的这个值就是DOM元素,这让大多数初学者感到困惑。
jQuery使用jQuery.proxy进行更改变得很简单。