Last active
August 31, 2022 14:12
-
-
Save kriskowal/317feaea633f5a9542f82ed438a589b1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// So, in sloppy mode, eval and var can do interesting things. | |
globalThis.name = 'global'; | |
console.log(name === 'global'); | |
(function () { | |
const name = 'outer'; | |
// direct eval, using a var declaration. | |
(function () { | |
eval(' var name = "inner"; '); | |
console.log(name === 'inner'); | |
})(); | |
// direct eval, but with const or let binds the name in a new block scope. | |
(function () { | |
eval(' let name = "inner"; '); | |
console.log(name === 'outer'); | |
})(); | |
// direct eval is equivalent to replacing the eval syntax node | |
// with the syntax of the string, but in a block. | |
// let and const create bindings in block scope. | |
(function () { | |
{ let name = "inner"; } | |
console.log(name === 'outer'); | |
})(); | |
// But var declarations bind in the surrounding function scope, an older | |
// behavior called "hoisting". | |
(function () { | |
{ var name = "inner"; } | |
console.log(name === 'inner'); | |
})(); | |
// Thankfully, you can't just delete a lexical variable. | |
// That would be weird. | |
(function () { | |
var name = 'inner'; | |
eval('delete name'); | |
console.log(name === 'inner'); | |
})(); | |
// Pretty weird. | |
(function () { | |
const scope = {name: 'inner'}; | |
with (scope) { | |
eval('delete name;'); | |
console.log(name === 'outer'); | |
} | |
})(); | |
// But not weird because of eval. | |
(function () { | |
const scope = {name: 'inner'}; | |
with (scope) { | |
delete name; | |
console.log(name === 'outer'); | |
} | |
})(); | |
// indirect eval, using var executes your snippet in a block scope rooted in | |
// global scope, bypassing the lexical scope of eval. | |
// This was introduced later in the language to avoid some pitfalls. | |
// There needed to be a mechanism where some code could be evaluated with | |
// behavior that didn't vary with or alter the lexical scope. | |
const indirectEval = eval; | |
(function () { | |
indirectEval(' var name = "inner"; '); | |
console.log(name === 'outer'); | |
})(); | |
// There are a lot of ways to call globalThis.eval | |
// without using the special syntax of eval. | |
(function () { | |
(0, eval)(' var name = "inner"; '); | |
console.log(name === 'outer'); | |
})(); | |
// But what's really going to bake your noodle, | |
(function () { | |
const eval = indirectEval; | |
eval(' var name = "inner"; '); | |
console.log(name === 'inner'); | |
})(); | |
// Because the JavaScript VM decides whether the eval('') syntactic form | |
// (which looks like {type: 'Eval'} in the syntax tree instead of {type: | |
// 'FunctionCall'}) should imply a direct eval or indirect eval is by | |
// looking up the name 'eval' in the lexical scope and checking whether | |
// it matches the intrinsic 'eval' function it placed on globalThis.eval | |
// before any code ran: the "intrinsic" eval. | |
// It does not matter what path globalThis.eval took before it became a | |
// lexical variable in scope. | |
(function () { | |
const eval = globalThis.eval; | |
globalThis.eval = undefined; | |
eval(' var name = "inner"; '); | |
console.log(name === 'inner'); | |
})(); | |
// The function constructor is similar to indirect eval, which might make you | |
// wonder why indirect eval is even necessary. | |
// This creates a function scope for the new variable. | |
(function () { | |
new Function('', 'var name = "inner"; ')(); | |
console.log(name === 'outer'); | |
})(); | |
// The truth of course is that necessary is a strong word, | |
// but it is at least different. | |
// Variable declarations in sloppy indirect eval get promoted to the global | |
// object. | |
(function () { | |
indirectEval('var name = "override"; '); | |
console.log(globalThis.name === 'override'); | |
})(); | |
// But, on the other hand, assigning to a lexically unbound (free) variable | |
// is equivalent. | |
(function () { | |
indirectEval(' newName = "newValue" '); | |
console.log(globalThis.newName === 'newValue'); | |
})(); | |
// Shrug. | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment