Reputation: 169
testVariable
has two different ouputs when using let
. And why there isn't any runtime error when varibles with the same name are defined in window
object? Object.defineProperty(window, 'testVariable', {
value: 22
})
let testVariable = 12
console.log(window.testVariable) // result: 22
console.log(testVariable) // result: 12
var
, the outputs are the same. 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>
Upvotes: 1
Views: 749
Reputation: 371069
When a variable is declared with var
on the top level, as soon as the script tag starts, it's assigned as a writable property of the global object:
console.log(Object.getOwnPropertyDescriptor(window, 'testVariable'));
var testVariable = 12;
The same is not true for variables declared with let
- they don't get put onto the global object:
console.log(Object.getOwnPropertyDescriptor(window, 'testVariable'));
let testVariable = 12;
When you use Object.defineProperty
to define a property that's already defined on the object, and you pass a value
, like with
Object.defineProperty(window, 'testVariable', {
value: 22
})
The prior value that existed on the object gets overwritten. So with your second code, you're defining a writable property named testVariable
on the global object which then gets overwritten, and both testVariable
and window.testVariable
evaluate to 12
.
In contrast, with your first code, top-level variables declared with let
create a global identifier, but do not get assigned to properties of the global object, so
Object.defineProperty(window, 'testVariable', {
and
let testVariable =
are referring to different things.
Why following code runs correctly, but the following throws an error
This one is quite interesting. According to the specification, when the environment prepares to execute a new <script>
tag, it runs GlobalDeclarationInstantiation
to set things up. It does, among other things:
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.
Where envRec
is the global Environment Record. (An Environment Record, or Lexical Environment, is a record of which variable names in a given scope refer to which values.)
With your code, the section of GlobalDeclarationInstantiation
that is throwing is this section of 5:
If envRec.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
In your third code, before the first script tag starts, when GlobalDeclarationInstantiation
runs, the global environment record doesn't have a variable named a
, so no error is thrown. In contrast, in your fourth code, when the second script tag starts, and GlobalDeclarationInstantiation
runs, a variable named a
declared with let
already exists in the global environment record, so the HasLexicalDeclaration
call returns true
, and an error is thrown. (Variables declared with let
create lexical declarations; variables declared with var
create var declarations.)
Upvotes: 1