Skip to content

Instantly share code, notes, and snippets.

@kriskowal
Last active August 31, 2022 14:12
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kriskowal/317feaea633f5a9542f82ed438a589b1 to your computer and use it in GitHub Desktop.
Save kriskowal/317feaea633f5a9542f82ed438a589b1 to your computer and use it in GitHub Desktop.
// 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