Comments, names, and the three dialects
How to write a note, what a name is allowed to contain, and why the same file means different things depending on how it is loaded.
Some of what you write in a file is not for the computer. A comment is text the engine skips entirely, there for whoever reads the code next, which is usually you in three months.
Put // in front of the console.log line and run it again.
Two forms, and only two. // ignores the rest of the line. /* ignores everything up to the next */, across as many lines as you like. There is no third form, and # is not a comment.
The hashbang
There is one exception to # not being a comment. If the very first two characters of a file are #!, that whole first line is ignored. It is called a hashbang, and it lets a Unix system know which program should run the file.
#!/usr/bin/env node
console.log("hello");greet, made executable with chmod +x greet, then run as ./greet.It has to be the first line: a blank line or a comment above it is a syntax error. Engines had allowed this for years before it was written into the specification in ES2023, which is a good example of the standard catching up with practice rather than leading it.
What a name may be made of
A name you invent, for a variable or a function, is called an identifier. The rules are looser than most people expect.
Rename café to something in another alphabet and run it again. Then try starting a name with a digit and read the error.
An identifier may start with a letter from any alphabet in Unicode, or with $ or _. After the first character it may also contain digits. It may not start with a digit, it may not contain a space or a hyphen, and case matters: total and Total are two different names.
It also may not be a reserved word, one of the names the grammar has already claimed: let, const, var, function, return, if, else, for, while, class, new, this, typeof, delete, import, export, null, true, false and a few dozen more.
Three dialects
The same characters in the same file can be read three different ways, and which one you get is decided outside the file, by how it was loaded. The three are called sloppy mode, strict mode and module. Here is the difference that matters most.
The name undeclaredThing was never declared. Change the number 1 to a piece of text and see what the answer becomes.
Assigning to a name that was never declared did not fail. It created a global variable, visible to the entire program, from inside a function. A single typo in a variable name silently makes a new one, and the original keeps its old value.
That is sloppy mode, the original behaviour of the language, kept forever because the web is full of pages that depend on it. Now the same code with one line added.
It stops with ReferenceError: totalCount is not defined. Add let in front of totalCount = 1; and run it again.
The line "use strict"; is a directive: a plain piece of text that the language treats as an instruction when it is the very first statement of a file or of a function body. Anywhere else it is an ordinary string that does nothing at all.
Strict mode was added in ES5 in 2009. It does not add features. It turns a list of silent mistakes into errors, and removes a few constructions that made the language hard to reason about. Once switched on for a scope it cannot be switched off again inside it.
| Written in the code | Sloppy | Strict | Module |
|---|---|---|---|
| Assign to a name never declared | Creates a global | ReferenceError | ReferenceError |
| Write to a frozen property | Ignored in silence | TypeError | TypeError |
| Two parameters with the same name | Allowed | SyntaxError | SyntaxError |
An old octal number such as 0755 | Allowed | SyntaxError | SyntaxError |
this inside a plain function call | globalThis | undefined | undefined |
import and export | SyntaxError | SyntaxError | The whole point |
delete applied to a variable name | Allowed, does nothing | SyntaxError | SyntaxError |
| Top-level declarations | Shared with everything else loaded | Shared with everything else loaded | Private to the file unless exported |
The this row is there for completeness; this has a lesson of its own in the section on objects, and the difference matters there rather than here.
How you end up in each one
- A
.jsfile run by Node.js in a package with no"type"setting, holding noimportorexport, is a CommonJS script, and it is sloppy unless the file starts with"use strict";. - The same file with an
importor anexportsomewhere in it is loaded as a module by recent versions of Node.js, and is therefore strict. Node.js tries it as a script first, fails, notices the module syntax, and reads it again as a module, printing a warning while it does so. - A
.mjsfile, or any.jsfile in a package whosepackage.jsonsays"type": "module", is a module, and modules are strict with no way to opt out. - In a browser,
<script>is sloppy and<script type="module">is a module. - The body of a
classis always strict, wherever it appears. Classes come later in the course.
You add "use strict"; as the tenth line of a file. What changes?
Nothing. A directive only counts when it is the first statement of a file or of a function body. On line ten it is an ordinary expression statement: a piece of text, evaluated and discarded. There is no warning, which is why the mistake survives.