我注意到this
Stack Overflow 网站上似乎没有明确解释关键字是什么以及它如何在 JavaScript 中正确(和错误)使用。
我目睹了它的一些非常奇怪的行为,并且无法理解它为什么会发生。
如何this
工作以及何时应该使用它?
我注意到this
Stack Overflow 网站上似乎没有明确解释关键字是什么以及它如何在 JavaScript 中正确(和错误)使用。
我目睹了它的一些非常奇怪的行为,并且无法理解它为什么会发生。
如何this
工作以及何时应该使用它?
this
是 JavaScript 中的关键字,是执行上下文的属性。它的主要用途是在函数和构造函数中。的规则this
非常简单(如果您坚持最佳实践)。
this
规范中的技术说明的ECMAScript标准定义this
经由抽象操作(缩写AO)ResolveThisBinding:
[AO] ResolveThisBinding […]
this
使用正在运行的执行上下文的 LexicalEnvironment 来确定关键字的绑定。[脚步]:
- 令envRec为GetThisEnvironment ()。
- 返回 ?envRec .GetThisBinding()。
全局环境记录、module环境记录和函数环境记录都有自己的 GetThisBinding 方法。
所述GetThisEnvironment AO找到当前正在运行的执行上下文的LexicalEnvironment和查找最接近方兴未艾环境记录(通过迭代地访问他们的[[OuterEnv]]特性),其具有这种结合(即HasThisBinding返回真)。此过程以三种环境记录类型之一结束。
的值this
通常取决于代码是否处于严格模式。
GetThisBinding 的返回值反映了this
当前执行上下文的值,因此无论何时建立新的执行上下文,都会this
解析为一个不同的值。当当前执行上下文被修改时,也会发生这种情况。以下小节列出了可能发生这种情况的五种情况。
您可以将代码示例放在AST 资源管理器中以跟随规范详细信息。
这是在顶层评估的脚本代码,例如直接在 a 中<script>
:
<script>
// Global context
console.log(this); // Logs global object.
setTimeout(function(){
console.log("Not global context");
});
</script>
当在脚本的初始全局执行上下文中时,评估this
会导致GetThisBinding采取以下步骤:
全局环境记录envRec [...] [这样做]的 GetThisBinding 具体方法:
- 返回envRec .[[GlobalThisValue]]。
全局环境记录的 [[GlobalThisValue]] 属性始终设置为主机定义的全局对象,可通过globalThis
(window
在 Web 上,global
在 Node.js 上;MDN上的文档)访问该对象。按照InitializeHostDefinedRealm的步骤了解 [[GlobalThisValue]] 属性是如何形成的。
ECMAScript 2015 中引入了module。
这适用于module,例如直接在 a 内部时<script type="module">
,而不是简单的<script>
.
在module的初始全局执行上下文中,求值this
会导致GetThisBinding采取以下步骤:
module环境记录的 GetThisBinding 具体方法 [...] [这样做]:
- 返回undefined。
在module中,的值this
总是undefined
在全局上下文中。module隐式处于严格模式。
有两种eval
调用:直接调用和间接调用。这种区别自 ECMAScript 第 5 版起就存在。
eval
调用通常看起来像eval(
……);
或(eval)(
…… );
(或((eval))(
……);
等)。1仅当调用表达式适合窄模式时才是直接的。2eval
调用涉及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
调用是直接的还是间接的:
意思是:
this
值不会改变;它取自称为eval
.this
值是全局对象 ( globalThis
)。怎么样new Function
? — new Function
与 类似eval
,但不会立即调用代码;它创建了一个函数。一本装订任何地方都不会在这里适用,除非当函数被调用,如在下一小节解释其正常工作。
调用函数时会出现输入函数代码。
调用函数有四类语法。
实际的函数调用发生在Call AO 处,调用时使用根据上下文确定的thisValue;这个参数在与调用相关的一长串调用中传递。Call调用函数的[[Call]]内部槽。这将调用PrepareForOrdinaryCall,其中创建了一个新的函数环境记录:
甲功能环境记录是一种声明环境记录被用于表示功能的顶层范围和,如果函数不是ArrowFunction,提供了一种
this
结合。如果函数不是ArrowFunction函数并引用了super
,则其函数环境记录还包含用于super
从函数内部执行方法调用的状态。
另外,在一个函数 Environment Record 中有 [[ThisValue]] 字段:
这是
this
用于此函数调用的值。
该NewFunctionEnvironment通话也设置功能环境的[[ThisBindingStatus]]属性。
[[Call]]还调用OrdinaryCallBindThis,其中适当的thisArgument是基于以下因素确定的:
一旦确定,对新创建的函数 Environment Record的BindThisValue方法的最终调用实际上将 [[ThisValue]] 字段设置为thisArgument。
最后,这个字段是函数 Environment Record 的 GetThisBinding AOthis
从以下位置获取值的地方:
GetThisBinding 函数的具体方法 Environment Record envRec [...] [这样做]:
[…]
3. 返回envRec .[[ThisValue]]。
同样,此值的确切确定方式取决于许多因素;这只是一个总体概述。有了这个技术背景,让我们检查所有具体的例子。
当评估箭头函数时,函数对象的 [[ThisMode]] 内部槽在OrdinaryFunctionCreate 中设置为“词法”。
在OrdinaryCallBindThis,它接受一个函数F:
- 令thisMode为F .[[ThisMode]]。
- 如果thisMode是词法的,则返回 NormalCompletion(
undefined
)。[…]
这只是意味着将跳过绑定this的算法的其余部分。箭头函数不绑定自己的this值。
那么,this
箭头函数内部是什么?回顾ResolveThisBinding和GetThisEnvironment,HasThisBinding 方法显式返回false。
HasThisBinding 函数的具体方法 Environment Record envRec […] [这样做]:
- 如果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.func
和Arguments 组成 ()
。
而且,refObj.func
并refObj
扮演三个角色,每个角色:
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的不确定这里。这在OrdinaryCallBindThis(F:函数对象;thisArgument:传递给Call的thisValue)中有所不同:
- 令thisMode为F .[[ThisMode]]。
[…]
- 如果thisMode是严格的,则让thisValue为thisArgument。
- 别的,
[…]
注意:第 5 步在严格模式下将 的实际值设置this
为提供的thisArgument - undefined
在这种情况下。在“草率模式”中,未定义或空的thisArgument导致this
成为全局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`.
}
和:
"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
行为的例外new
是class 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 }`.
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是一些您需要查找求值步骤的表达式。例如,评估MemberExpression或CallExpression是这些算法之一的结果。其中一些导致参考记录。
4:也有允许提供一些其他本地和主机方法这个值,特别是Array.prototype.map
,Array.prototype.forEach
等接受一个thisArg作为他们的第二个参数。任何人都可以做自己的方法来改变this
一样(func, thisArg) => func.bind(thisArg)
,(func, thisArg) => func.call(thisArg)
等像往常一样,MDN提供了巨大的文档。
对于每个代码片段,回答问题:“this
标记行的值是多少?为什么?” .
要显示答案,请单击灰色框。
if(true){
console.log(this); // What is `this` here?
}
globalThis
. 标记的行在初始全局执行上下文中进行评估。
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
。
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
) 创建绑定的方法对象。
const obj = {
myFun: () => ({ // What is `this` here?
"is obj": this === obj,
"is globalThis": this === globalThis
})
};
console.log(obj.myFun());
globalThis
. 箭头函数不会创建自己的this绑定。词汇范围是相同的初始全球范围内,所以this
是globalThis
。
function myFun(){
console.log(this); // What is `this` here?
}
const obj = {
myMethod: function(){
eval("myFun()");
}
};
obj.myMethod();
globalThis
. 在评估直接 eval 调用时,this
是obj
。但是,在 eval 代码中,myFun
并没有从对象中调用,因此将this绑定设置为全局对象。
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
作为第一个参数。
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
. 你得到你期望的结果了吗?
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])
设置obj1
为this
inside的值fun()
并调用fun()
传递的元素argsArray
作为其参数。fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
- 设置obj1
为this
inside的值fun()
并调用作为其参数的fun()
传递arg1, arg2, arg3, ...
。fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
-返回参照功能fun
与this
必然中的乐趣obj1
和参数fun
绑定到指定的参数arg1, arg2, arg3,...
。apply
,call
和之间的区别bind
应该已经很明显了。apply
允许将参数指定为类似数组的对象,即具有数字length
属性和相应的非负整数属性的对象。而call
允许直接指定函数的参数。双方apply
并call
立即调用函数中指定的上下文和用指定的参数。另一方面,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
。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. this
ES6中的箭头函数
在箭头函数中,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
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 中调用了像forEach
and 之类的本地方法slice
,您应该已经知道this
在这种情况下的变量是指Object
您调用该函数的变量(请注意,在 javascript 中,几乎所有内容都是 an Object
,包括Array
s 和Function
s)。以下面的代码为例。
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
关键字时会发生什么。
new
关键字调用函数将立即初始化Object
类型Person
。Object
的构造函数将其构造函数设置为Person
。另外,请注意,typeof awal
只会返回Object
。Object
将被分配的原型Person.prototype
。这意味着Person
原型中的任何方法或属性都可用于 的所有实例Person
,包括awal
.Person
现在调用函数本身;this
作为对新构造对象的引用awal
。很简单吧?
请注意,官方 ECMAScript 规范没有说明此类函数是实际constructor
函数。它们只是普通函数,new
可以用于任何函数。只是我们这样使用它们,所以我们只这样称呼它们。
call
和apply
所以是的,因为function
s 也是Objects
(实际上是 Javascript 中的第一类变量),即使函数也有方法......好吧,函数本身。
所有函数都继承自 global Function
,它的许多方法中有两个是call
and apply
,两者都可用于操作this
调用它们的函数中的值。
function foo () { console.log (this, arguments); }
var thisArg = {myObj: "is cool"};
foo.call(thisArg, 1, 2, 3);
这是使用call
. 它基本上采用第一个参数并this
在函数中设置foo
为对thisArg
. 传递给的所有其他参数call
都foo
作为参数传递给函数。
所以上面的代码会登录{myObj: "is cool"}, [1, 2, 3]
到控制台。改变this
任何函数中的值的好方法。
apply
几乎与call
accept相同,它只需要两个参数:thisArg
和一个包含要传递给函数的参数的数组。所以上面的call
调用可以翻译成apply
这样:
foo.apply(thisArg, [1,2,3])
请注意,call
andapply
可以覆盖this
我们在第二个项目符号中讨论的点方法调用设置的值。足够简单:)
bind
!bind
是call
and的兄弟apply
。它也是Function
Javascript 中所有函数从全局构造函数继承的方法。bind
和call
/之间的区别在于apply
两者call
和apply
实际上都会调用该函数。bind
,另一方面,返回一个带有thisArg
和arguments
预设的新函数。让我们举一个例子来更好地理解这一点:
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]`
看到三者的区别了吗?这很微妙,但它们的用法不同。像call
and一样apply
,bind
也会覆盖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
原始作用域中的 值。所以我们“复制”this
到that
并使用了副本而不是this
. 聪明,嗯?
指数:
this
默认保留什么?new
关键字呢?this
与call
和apply
?bind
.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);
试试看; 你会开始喜欢这种编程模式
由于此线程已增加,因此我为不熟悉该this
主题的读者整理了一些要点。
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 strict
,this
在未绑定到任何对象的全局和匿名函数中的值为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)
参考: