'this' 是 Javascript 中的全局对象的情况

IT技术 javascript
2021-03-17 06:19:49

我正在尝试使用不同的方法来调用作为 Javascript 中对象属性的函数,并查看哪种类型的调用将“this”设置为对象,将“this”设置为全局对象。

这是我的测试代码:

var foo = {
  bar: function(){ 
    console.log('this:' + this);
  }
}

console.log('calling foo.bar()');
foo.bar();

console.log('\ncalling (foo.bar)()');
(foo.bar)();

console.log('\ncalling f=foo; f.bar()');
f = foo; f.bar();

console.log('\ncalling f=foo.bar; f()');
f = foo.bar; f();

console.log('\ncalling (f=foo.bar)()');
(f = foo.bar)();

这是结果:

calling foo.bar()
this:[object Object]

calling (foo.bar)()
this:[object Object]

calling f=foo; f.bar()
this:[object Object]

calling f=foo.bar; f()
this:[object global]

calling (f=foo.bar)()
this:[object global]

我的问题是,为什么f=foo.bar; f();(f=foo.bar)();分配“这”是全局对象

4个回答

了解thison 函数调用的隐式行为的关键在于了解引用类型如何工作

参考类型由两个部分组成(在ECMAScript中3),所述的基础对象属性名称(在ECMAScript中5,它有一个第三部件的严格标志-I将谈论严格模式later- (1) )。

被调用的功能,该this值是通过获取参考的基础对象(由内部隐含地确定GetBase操作)。

例如,在foo.bar引用中,基础对象foo属性名称"bar"

foo.bar(); // `this` will point to `foo`

当您进行赋值时,引用丢失了,我们不再有基础对象属性名称,我们只有一个

(f=foo.bar)(); // `this` will point to the global object

它不仅发生在赋值中,还会发生在使用内部GetValue操作的其他操作中

// `this` will point to the global object    
(0, foo.bar)();   // The Comma Operator
(0 || foo.bar)(); // Binary Logical Operators
(1 && foo.bar)();
// etc..

例如,如果您用括号引用括起来(正式称为The Grouping Operator,则不会发生这种情况

(foo.bar)(); // `this` will point to `foo`

分组运算符(同样,括号 ;)不在GetValue内部使用,这是以这种方式设计的,因为允许typeofdelete运算符与括号表达式一起使用

delete (foo.bar);

如果使用分组运算符GetValue,则delete运算符将无法获取要删除属性基础对象bar

回到隐含this值,有一些棘手的情况,例如,使用with语句:

with (foo) {
  bar(); // `this` will refer to `foo` !
}

如您所见,bar();在 with 块内部调用,仍会将this绑定foo对象。

this值不是从reference设置的,它来自语句引入的当前环境记录(我可能稍后会写一些关于它的内容)with

此外,您可能会注意到,对于non-references,该this值将指向全局对象,例如:

(function () {})(); // this will point to the global object

我们只有一个,而不是引用(请记住,引用是解析的名称绑定)。


(1)注意:在 ECMAScript 5 Strict Mode 上,该this值将undefined适用于描述的所有情况,其中该this值被隐式设置为 Global 对象。

之所以这样做是作为一种安全措施,主要是因为人们new在调用构造函数时经常忘记使用运算符,从而导致不良行为和对全局范围的污染。

例如:

function Foo () {
  this.prop = 'foo';
}
Foo(); // no `new` operator, boom!

正如您现在所知,Foo引用没有直接的基础对象this它将指向全局对象,并且会无意中在其上创建一个属性。

在严格模式下,代码只会给你一个TypeError,因为this值是undefined

同样,您可能还记得一开始我提到引用类型有第三个组件,即strict标志。

您的原始示例:

(f=foo.bar)();

甚至可能不适用于严格模式,因为不允许分配给未声明的标识符(例如f 似乎),(另一种避免全局对象污染的安全措施)。

更多信息:

在前 3 个示例中,您使用foo对象并运行其函数,因此您可以获得运行函数的上下文,an Objectfoo对象。

在最后 2 个示例中,您将foo.bar函数复制到变量f并执行它。在这种情况下,函数是从全局上下文执行的,而不是从foo对象上下文执行的。这将与声明和运行相同

  function f() { 
    console.log('this:' + this);
  }

引用可能难以理解的 ECMA-262(第 5 版)(强调):

10.2.1.2 对象环境记录

对象环境记录可以配置为将它们的绑定对象作为隐式 this 值提供,以便在函数调用中使用。此功能用于指定 With Statement (12.10) 诱导绑定的行为。该能力由与每个对象环境记录相关联的 provideThis 布尔值控制。 默认情况下,任何对象环境记录的 provideThis 的值为 false

10.2.1.2.6 隐式ThisValue()

对象环境记录返回 undefined 作为它们的 ImplicitThisValue,除非它们的 provideThis 标志为真。

  1. 令 envRec 为调用该方法的对象环境记录。
  2. 如果 envRec 的 provideThis 标志为真,则返回 envRec 的绑定对象。
  3. 否则,返回 undefined

10.4.3 输入功能码

  1. 如果函数代码为严格代码,则将ThisBinding 设置为thisArg。
  2. 否则,如果 thisArg 为 null 或未定义,则将 ThisBinding 设置为全局对象

10.4.1.1 初始全局执行上下文

执行以下步骤以初始化 ECMAScript 代码 C 的全局执行上下文:

  1. 将变量环境设置为全局环境。
  2. 将 LexicalEnvironment 设置为全局环境。
  3. 将 ThisBinding 设置为全局对象。

10.4.2 输入验证码

当控制进入 eval 代码的执行上下文时,将执行以下步骤:

  1. 如果没有调用上下文或者 eval 代码不是通过直接调用 (15.1.2.1.1) 来评估 eval 函数,那么,
    1. 如 10.4.1.1 中所述,使用 eval 代码作为 C将执行上下文初始化为全局执行上下文
  2. 别的,
    1. 将 ThisBinding 设置为与调用执行上下文的 ThisBinding 相同的值。

11.1.1 this关键字

this 关键字计算为当前执行上下文的 ThisBinding 的值

11.2.3 函数调用

产品 CallExpression : MemberExpression Arguments 评估如下:

  1. 让 ref 是对 MemberExpression 求值的结果。
  2. 令 func 为 GetValue(ref)。
  3. 让 argList 是对 Arguments 求值的结果,产生一个内部参数值列表(见 11.2.4)。
  4. 如果 Type(func) 不是 Object,则抛出 TypeError 异常。
  5. 如果 IsCallable(func) 为 false,则抛出 TypeError 异常。
  6. 如果 Type(ref) 是 Reference,则
    1. 如果 IsPropertyReference(ref) 为真,则
      1. 令 thisValue 为 GetBase(ref)。
    2. 否则, ref 的基础是环境记录
      1. 令 thisValue 为调用 GetBase(ref) 的 ImplicitThisValue 具体方法的结果。
  7. 否则,Type(ref) 不是 Reference。
    1. 让 thisValue 未定义。
  8. 返回调用 func 上的 [[Call]] 内部方法的结果,提供 thisValue 作为 this 值并提供列表 argList 作为参数值。

12.10 with 语句

  1. 让 oldEnv 成为正在运行的执行上下文的 LexicalEnvironment。
  2. 让 newEnv 成为调用 NewObjectEnvironment 传递 obj 和 oldEnv 作为参数的结果
  3. 将 newEnv 的 provideThis 标志设置为 true。

this 在以下情况下分配给全局对象:

  1. 调用函数时thisArg未定义thisArg设置为:
    1. 通过Function.prototype.apply(15.3.4.3)的第一个参数
    2. 通过Function.prototype.call(15.3.4.4)的第一个参数
    3. 通过Function.prototype.bind(15.3.4.5)的第一个参数(不在 ECMA252-3 中)。
    4. 通过调用使用MemberExpression Arguments语法,意思是:
      1. foo(arguments)相当于foo.call((function() { }()), arguments);iff foois callable 并且如果您不在with声明中。
      2. foo.bar(arguments)等价于foo.bar.call(foo, arguments)foo只计算一次, ifffoo.bar是可调用的。
  2. 上下文是全局上下文,它发生在:
    1. 在默认范围内。
    2. evalif 它被直接调用的范围内

我的问题是,为什么f=foo.bar; f();(f=foo.bar)();分配“这”是全局对象

这在第 11.2.3 节第 6.ai 步(上文 6.1.1)中有详细说明;reff您的情况下表达式(不是它的值),并且GetBase(ref).ImplicitThisValue()undefined(因为变量有一个对象环境记录作为其基础,如果该变量不是来自provideThisfalse设置为with)。

提供过度完备性。
2021-05-16 06:19:49

前三种情况与后两种情况的区别在于您将 bar 函数存储在变量中。函数不“知道”它们是对象的属性。所以在最后两种情况下,您只是调用常规函数。

在最后两种情况下,您可以使用 Function.call 或 function.apply 使“this”指向“foo”

f.call(foo)

Call 和 apply 允许您控制“this”在您正在调用的函数中所指的内容。

虽然函数不知道它们是否附加到一个对象,但它们确实知道它们被声明在什么范围内。基于此你可以做很多有用的事情。在最后一个示例中,您通过执行 (f = foo.bar)() 创建了一个新范围,但它没有任何效果。