Detect Strict Mode

Can we have a way to detect if code is running in strict mode?

See the issue and work-around hacks here:

I just spent an hour trying to figure out how to detect whether a variable had been declared, and whether or not it had a particular type, but accessing a non-declared variable is an error only in strict mode. Depending on what place and mode my code was running in, it would or would not work.

typeof variable !== undefined will check that a variable is both declared and not set to undefined.

Would you be able to expand on why you needed to check if a variable had been defined? detects if the environment supports strict mode; the same code can be used to detect if the current function is in strict mode or not.

However, it seems bizarre to be writing a function and not know what mode it's in, and indeed using typeof as described is the way it's always been done in JS, since long before strict mode existed.

Yep. My code that was problematic:

if( insecureImport && typeof insecureImport === "function" ) {

Ran fine in most cases, then suddenly failed with ReferenceError in strict mode.
The solution was to truncate it to:

if( typeof insecureImport === "function" )

typeof is apparently a completely undocumented and unique means of bypassing undeclared access ReferenceError in strict mode.

I've checked the MDN docs for typeof and ReferenceError and strict mode, as well as the 262 spec for all three. (The spec claims that ReferenceError only throws on unresolvable assignation, which is not the case at all.

The reason you need to check if a variable has been defined, if you really want to know:

When you're loading modules dynamically in insecure contexts (e.g. web tools that cannot be exposed to Internet traffic, served over workplace intranet to BYOD phones / tablets), you can't use import, so you just have to append a <script> element and wait for it to load. There's no way to check if a script element has finished executing, so you just have to keep checking if its global-variable exports have been defined.

Global !this detects if the environment supports strict mode, but for any function with this already bound, it's useless. (So, generally useless.)

typeof has an undocumented ability to bypass strict mode's unresolvable access ReferenceError, which can, in turn, also be used to detect strict mode (but any of strict mode's errors can be used this way), which is a hackish solution. There's no official way to detect it.
It's a minor headache when compiling inline pieces of JS. (I say minor because I've never wasted so much time tracking down this bug before today.)

Perhaps you just missed it?

It's right here: typeof - JavaScript | MDN (under "Interaction with undeclared and uninitialized variables")

If we're talking about global variables, there's another option.

'myGlobalVar' in globalThis;

I personally would use the above pattern whenever you want to check for the existence of a global variable - I feel like it does a better job at conveying intent. But, the typeof check works there too. Really, the only value I see in this typeof check is to check if a local variable has been declared or not, but there's no practical reason for such a feature, because one can always know if a local variable will be declared or not just by looking at their code. Because of this, I personally never use typeof to check for the existance of something undeclared.

I definitely missed it. I really really want to delete this whole post. It feels like a naive stackoverflow question, not an idea at all.

'insecureImport' in globalThis returns false for me, but from the top-level, global context, typeof insecureImport === "function". Is that really how global variables work? (I mean, it probably is and I'm just doing something else wrong. Thanks for all your patient help, feel free to drop this thread.)

in globalThis

Lexical declarations in the global scope will not go in the global object

// global scope
var myVar = 1
console.log("myVar" in globalThis) // true

let myLet = 2
console.log("myLet" in globalThis) // false

typeof can be used to detect these globals.

let myLet = 2
console.log(typeof myLet) // number

Assuming you do so outside the TDZ of the declaration since typeof will not save you there.

console.log(typeof myLet) // Error
let myLet = 2

But I think the only way you could get yourself into this situation is if you're working with generated/injected code that you have no control or knowledge of... you know, like "when compiling inline pieces of JS". I think at that point you'd have to account for the possibility of the error with a try..catch.

You said "insecureImport" is coming from something loaded via a script tag? How exactly is this piece of code registering this global?

If it's truly global, then it should be on globalThis. If it's not, then it should be local to that script tag, and the other script tag shouldn't see it - this scenario would be the "Lexical declarations in the global scope" that @senocular mentioned.

To elaborate, these are the ways I know of to declare a global.

  1. Explicitly attach it to globalThis or window
  globalThis.myExport = 'Hello World!';
  console.log('myExport' in globalThis); // true
  1. Declare a variables in sloppy mode without using let/var/const.
  myExport = 'Hello World!';
  console.log('myExport' in globalThis); // true
  1. Declare a variable using "var", in the global scope
  var myExport = 'Hello World!';
  console.log('myExport' in globalThis); // true

If any other scenario, the variable should end up local to the script tag, and another script tag will not be able to see it.

  let myExport = 'Hello World!';
  console.log('myExport' in globalThis);
  const x = myExports; // ReferenceError

To test your setup, I wouldn't rely on the dev-tools REPL - those are sometimes enhanced with ease-of-use features. For example, I noticed that even though I was unable to view the "myExport" variable in another script tag in this last example, I could still put "> myExport" in the dev-tools and get back out its value. It seems the dev-tools were allowing me to access variables local to individual scripts.

Not quite - a lexical declaration (let, const, class) still may declare a global variable that will be available in subsequent scripts, despite not creating a property on the global object. It will cause an error if you attempt to declare it a second time (unlike var).
The ReferenceError in your example occurs only because of the typo myExport<>myExports.

1 Like

You could use the script.onload event for that, but really this whole issue appears to be a solved problem. Use Asynchronous Module Definition instead of rolling your own! It's supported by tooling (bundlers, transpilers) since years, you can just use standard module syntax in your source code.

script.onload fires before a script executes; it's always been useless. (A script is not guaranteed to "finish" executing anyway; the halting problem.) The process I'm describing is exactly how you would implement AMD. You just have to wait and hope.

But I was unaware of the global distinction between lexical declarations vs. variables. That is definitely a quirk I needed to be aware of, so thank you for clarifying it.

Huh, learned something new today. Thanks for catching my type-o.

I guess that is one use for typeof possiblyDeclaredVariable then that nothing else can properly substitute.

Uh? Afaik the HTML spec guarantees exactly the opposite. I haven't used that technique in years, but onload firing after execution is also what I recall of the behavior.

If you have a synchronous infinite loop, no mechanism would be able to preempt it. If you have some kind of asynchronous loop or events triggering further execution, the load event will trigger right after the synchronous execution of the script is complete.

Oh! You are correct about that event order. I just checked the spec, then tested it. Learned another new thing on this thread. :-)

You can't predict whether a script will need asynchronous events before its exports are ready, so the event is probably still not usable.

Also, Firefox (and Chrome?) allows the user to preempt infinite loops while still keeping the page's DOM and other scripts alive. (You get a prompt asking if you want to stop a script.) The halting problem does apply. But in general, onload is not guaranteed to fire because onerror can fire instead; I stand by that part of my original statement: a script is never guaranteed to finish executing. (But as long as you listen to both events... maybe you can guarantee one or the other will fire? Maybe???)

If a script requires asynchronous init, it's that script's duty to synchronously install a mechanism that the loader can use to get a callback once the script is ready.

Regarding load failures, I believe onerror is guaranteed to trigger, which means you can rely on either onload or onerror to trigger. That's a question for the html spec.

Regarding host pre-emption mechanisms, I'm not sure how they're implemented. If I had to guess, it's an exception raised during the infinite loop? I am actually not aware of a case where a script doesn't run to completion but further execution in the same agent is allowed. I am actually curious about this because our team considered at some point using an infinite loop to "shim" uncatchable errors.

While it's true that we can't predict whether or not a script will stop executing, it's also true that if a script takes longer than a couple of eye blinks to load, then it's got a major bug, and it's perfectly valid to treat that as a timeout error if you wish - even if you don't, as you mentioned, the browser could treat it like a bug and present the end-user with a kill-switch prompt. Script-loading time is the wrong time to try and run fancy algorithms that could take an unknowable amount of time to execute. That kind of stuff should go into a function and be exported by the script. Script-loading time should be reserved for loading resources over the internet, and if wanted, performing some trivial initialization tasks, and that's it.

So, you should be able to listen to both onload and onerror, and if you wish, you can also set a timeout.

And, as @mhofman mentioned, if the script really does need to perform some async logic during load-time (I have yet to see a library that needed this - I know it can happen, but it's not common), it should export some sort of event function that will notify you when it's done loading.

Finally, I wonder if script-based loading is actually necessary. If you're allowed to do your loading from modules, then all of these problems will be solved for you.

Here's a normal load example, where you need to make sure that the first module finishes loading before the second.

import 'someModuleThatRegistersAGlobal';
import 'yourLibraryThatExpectsTheGlobal';

And, if you suspect that the module requires async logic as it loads, you can just do this.

await import 'someModuleThatRegistersAGlobal';
import 'yourLibraryThatExpectsTheGlobal';

Modules require secure contexts, which are not possible in the scenario I described. Because management reject basic security concepts and insist their vague Internet security ideas from the 90s are better. (Otherwise this wouldn't have been an issue in the first place.) I'm using both events, and logging a recurring load-timeout warning (which usually means a script didn't initialize something the way it's API said it was supposed to).

@mhofman since you said you were "not aware of a case" like this, here is an example of a script that (may) not run to completion. When (if) it is preempted, further execution on the page remains possible.

Warning, do not click this link! It is a long, memory-heavy loop!
You have been warned.

The example I've linked here is a searchable thesaurus I created that proved far too resource-intensive to use. I have uploaded it to GitHub and disabled the limiters / segmenters to force it into a single, long, memory-heavy loop.

Depending on how many other tabs I have open, whether I have run this page before in this browser session, whether I am using Firefox or Chrome, whether or not I have dev tools open, and perhaps other factors, I will get different behavior: including repeated prompts asking me if I would like to stop the script, a crashed tab, an out-of-memory-error, or a paused-before-potential-out-of-memory event. Sometimes, it simply runs to completion and becomes searchable (after a few minutes).

To execute in the agent after preemption (if it occurs), use the console. You can run a search despite the thesaurus being unprepared using executeSearch(), or trigger a download of a partially linked state via downloadParsedThesaurus().

The webapp loads the Moby thesaurus (~24MB), and links words:

  • If A > is a synonym of B > is a synonym of C,
  • Then A > is a synonym of C

Word links are weighted by frequency of found links.

The linked thesaurus is searchable or downloadable as JSON (~1.3GB)
It is not a practical tool for use, due to the long time necessary to build the links before searching, and due to the large memory requirements.