将 Object.defineProperty 与 let 或 var 一起使用时会发生奇怪的事情

Posted

技术标签:

【中文标题】将 Object.defineProperty 与 let 或 var 一起使用时会发生奇怪的事情【英文标题】:Strange things happen when using Object.defineProperty with let or var 【发布时间】:2020-04-19 16:40:40 【问题描述】:
    谁能解释为什么testVariable 在使用let 时有两种不同的输出。为什么在window 对象中定义同名变量时没有任何运行时错误?
    Object.defineProperty(window, 'testVariable', 
      value: 22
    )

    let testVariable = 12

    console.log(window.testVariable)   // result: 22
    console.log(testVariable)          // result: 12
    但是当使用var时,输出是一样的。
    Object.defineProperty(window, 'testVariable', 
      value: 22
    )

    var testVariable = 12

    console.log(window.testVariable)   // result: 12
    console.log(testVariable)          // result: 12
    为什么下面的代码运行正确
  <script>
    Object.defineProperty(window, 'a', 
      value: 33
    )

    let a = 13
  </script>

  <script>
    console.log(a)    // result: 13
  </script>
    但以下会引发错误。
  <script>
    Object.defineProperty(window, 'a', 
      value: 33
    )
  </script>

  <script>
    let a = 13
    console.log(a)   // Uncaught SyntaxError: Identifier 'a' has already been declared
  </script>

【问题讨论】:

【参考方案1】:

当一个变量在顶层用var声明时,一旦脚本标签开始,它就被分配为全局对象的可写属性:

console.log(Object.getOwnPropertyDescriptor(window, 'testVariable'));
var testVariable = 12;

使用let 声明的变量并非如此——它们不会被放到全局对象中:

console.log(Object.getOwnPropertyDescriptor(window, 'testVariable'));
let testVariable = 12;

当您使用Object.defineProperty 定义对象上已定义的属性,并传递value 时,就像使用

Object.defineProperty(window, 'testVariable', 
    value: 22
)

对象上存在的先前值被覆盖。因此,使用您的第二个代码,您将在全局对象上定义一个名为 testVariable 的可写属性,然后将其覆盖,并且 testVariablewindow.testVariable 都评估为 12

相比之下,在您的第一个代码中,使用 let 声明的***变量会创建一个全局标识符,但不会分配给全局对象的属性,因此

Object.defineProperty(window, 'testVariable', 

let testVariable =

指的是不同的东西。


为什么下面的代码运行正常,但是下面的却报错

这个很有意思。根据specification,当环境准备执行新的&lt;script&gt; 标签时,它会运行GlobalDeclarationInstantiation 进行设置。它确实如此,除其他外:

1. Let envRec be env's EnvironmentRecord.
2. Assert: envRec is a global Environment Record.
3. Let lexNames be the LexicallyDeclaredNames of script.
4. Let varNames be the VarDeclaredNames of script.
5. For each name in lexNames, do
 - If envRec.HasVarDeclaration(name) is true, throw a SyntaxError exception.
 - If envRec.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
 - Let hasRestrictedGlobal be ? envRec.HasRestrictedGlobalProperty(name).
 - If hasRestrictedGlobal is true, throw a SyntaxError exception.
6. For each name in varNames, do
 - If envRec.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.

其中envRec 是全局环境记录。 (环境记录或词法环境是给定范围内的哪些变量名引用哪些值的记录。)

使用您的代码,正在抛出的GlobalDeclarationInstantiation 部分是 5 的这一部分:

如果 envRec.HasLexicalDeclaration(name) 为真,则抛出 SyntaxError 异常。

在您的第三个代码中,在第一个脚本标记开始之前,当GlobalDeclarationInstantiation 运行时,全局环境记录没有名为a 的变量,因此不会引发错误。相反,在您的第四个代码中,当 second 脚本标记开始并运行 GlobalDeclarationInstantiation 时,使用 let 声明的名为 a 的变量已存在于全局环境记录中,因此 @ 987654346@调用返回true,并抛出错误。 (使用let 声明的变量创建词法声明;使用var 声明的变量创建var 声明。)

【讨论】:

你的意思是 Object.defineProperty(window, 'testVariable', value: 22 ) 和 var testVariable = 22 一样吗? 有时。对于Object.defineProperty,如果该属性不存在,则默认为writable: false。但是当您在 same script 标签 中使用var 在顶层创建变量时。该属性以writable: true 放在窗口上。因此,如果您先使用Object.defineProperty,然后在另一个脚本标签中使用var testVariable,您将无法更改testVariable 的原始值。 (严格模式下会报错。) 非常感谢@CertainPerformance,你告诉我的对我来说有点复杂,我会根据你的回答做更多的研究。

以上是关于将 Object.defineProperty 与 let 或 var 一起使用时会发生奇怪的事情的主要内容,如果未能解决你的问题,请参考以下文章

利用object.defineProperty实现数据与视图绑定

Object.defineProperty 与 属性描述符

Object.defineProperty()

Object.defineProperty()

深入浅出Object.defineProperty()

Object.defineProperty方法