Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active August 24, 2023 20:55
Show Gist options
  • Save dfkaye/8c1b6e202b0cc5ce4fae80b4b1faddfa to your computer and use it in GitHub Desktop.
Save dfkaye/8c1b6e202b0cc5ce4fae80b4b1faddfa to your computer and use it in GitHub Desktop.
Hazards of working everything out in the console (or a REPL) without strict mode.
// 23 August 2023
// Hazards of working everything out in the console (or a REPL).
// I really thought I'd found something interesting, a possible quirk in the
// `form.elements` collection, but it turned out to be anti-pattern that strict
// mode prevents.
// We relive some of the embarrassment for you now.
// First, we'll set up the DOM from the console.
var html = `
<input id="a.b.c" name="x.y.z" value="* test *"/>
`;
document.body.replaceChildren();
var form = document.createElement("form");
// setHTML() is coming, more on that at MDN,
// https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML
typeof form.setHTML == 'function'
? form.setHTML(html)
: form.innerHTML = html;
document.body.appendChild(form);
var input = form.firstElementChild;
console.assert(input.id === "a.b.c", "should find input");
// Now find the input by the form.elements collection.
// by ID
console.assert(form.elements["a.b.c"] === input, `"a.b.c"`);
// by name
console.assert(form.elements["x.y.z"] === input, `"x.y.z"`);
// Now the quirk I thought I'd found was that I could prefix the quoted search
// key with `name` or `id` or anything and the equals sign, `=`.
// The next two examples are run in IIFEs (i.e., "immediately invoked function
// expressions").
// In the first example, we prefix `id=`, `name=`, and `any=` to the search
// keys, respectively.
(function () {
console.group("IIFE");
// prefix ID
console.assert(form.elements[id="a.b.c"] === input, `id="a.b.c"`);
// prefix name
console.assert(form.elements[name="x.y.z"] === input, `name="x.y.z"`);
// prefix anything
console.assert(form.elements[any="x.y.z"] === input, `any="x.y.z"`);
console.groupEnd("IIFE")
})('IIFE');
// It turns out that such prefixing also works in object and array indexes.
// In the next example, we prefix `id=` and `name=` to the search keys,
// respectively.
(function () {
console.group("O & A");
var o = { "d.e.f": "value" }
console.assert(o[id="d.e.f"] === "value", `o[id="d.e.f"]`);
var a = ['a', 'b']
console.assert(a[name=1] === 'b', `a[name=1]`);
console.groupEnd("O & A");
})('IIFE');
// On reflection, however, it turns out all we've really done is to create
// accidental global variables `id`, `name`, and `any`.
// They are "accidental" in the sense that they have been created without the
// intentional keywords "var", "let", or "const" preceding them.
// Even from inside a function, when a variable name is assigned a value without
// these keywords, the variable is set as a global entry at the top of the
// current scope (in this case, `self` or `window` or `globalThis`)...
console.assert(globalThis.id === id, "id is global");
console.assert(self.name === name, "name is global");
console.assert(window.any === any, "any is global");
// `id` and `name` have been assigned from the object and array test...
console.assert(id === "d.e.f", `id assigned "d.e.f"`);
// ...and note that `name` is a string, not a number...
console.assert(name === "1", `name assigned 1`);
console.assert(typeof name == 'string', "expected name to be a string");
// while `any` is still assigned from the first IIFE test...
console.assert(any === "x.y.z", `any assigned "x.y.z"`);
// So let's clean up `id` and `any` before explaining what went wrong...
delete id, delete any;
console.assert(!('id' in self), `should clear id`);
console.assert(!('any' in globalThis), `should clear any`);
// Why not delete `name`?
// We should not delete `name` from the console context because of another
// quirk where re-running this console script without re-loading the console
// will set name as a number, instead of its default "stringly" behavior.
// Instead, reassign it to the empty string (its default value in a newly
// constructed window).
name = "";
// These variables were created from our function and console context because we
// did not run these examples in "strict" mode, which prevents accidental global
// assignment.
// ECMAScript modules run in strict mode by default, but older contexts - such
// as the browser console - do not.
// In those contexts we have to declare the `"use strict";`` pragma at the
// beginning of the scope we are interested in.
// If we do that in the following IIFE, there should be no failing assertions.
(function (error) {
"use strict";
console.group("id");
try {
console.assert(form.elements[id="a.b.c"] === input, `id="a.b.c"`);
}
catch (e) {
error = e;
}
finally {
console.assert(
error.constructor === ReferenceError
&& error.message.indexOf("id") != -1,
"should be ReferenceError for id"
);
}
console.groupEnd("id");
console.group("any");
try {
console.assert(form.elements[any="x.y.z"] === input, `any="x.y.z"`);
}
catch (e) {
error = e;
}
finally {
console.assert(
error.constructor === ReferenceError
&& error.message.indexOf("any") != -1,
"should be ReferenceError for any"
);
}
console.groupEnd("any");
})();
// Most of the time, console fragments don't fall into such trouble.
// But, if you're like me, and you like trying out potentially clever or chaotic
// ideas in the console, then the "use strict"; pragma is your friend.
// It will save you some - maybe hours of - confusion or embarrassment.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment