将 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
的可写属性,然后将其覆盖,并且 testVariable
和 window.testVariable
都评估为 12
。
相比之下,在您的第一个代码中,使用 let
声明的***变量会创建一个全局标识符,但不会分配给全局对象的属性,因此
Object.defineProperty(window, 'testVariable',
和
let testVariable =
指的是不同的东西。
为什么下面的代码运行正常,但是下面的却报错
这个很有意思。根据specification,当环境准备执行新的<script>
标签时,它会运行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 一起使用时会发生奇怪的事情的主要内容,如果未能解决你的问题,请参考以下文章