cf.completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Don't break the web

Learn why JavaScript can add features but almost never remove them, through SmooshGate, includes, globalThis, Symbol.unscopables and use counters.

By the end, you can
  • 01
    Explain the ruleSay why browsers can't ship a change that breaks old websites, and why the spec follows the browsers.
  • 02
    Trace a name clashShow, with real code, how a new built-in method like flatten broke a library's copy loop, and why flat didn't.
  • 03
    Write code that ages wellAvoid patching built-in objects, avoid for-in on arrays, and check for a feature before you add it.

Don't break the web: new features must fit old pages

Every year, JavaScript gets new features. But the web is full of old pages that nobody will ever update, and browsers must keep running them. So every new feature has to fit next to all the old code already out there. This rule is called "don't break the web", and it explains some odd names in JavaScript.

Two methods with surprising namesPop out in the code editor (opens in a new tab)JavaScript
var prices = [3, [4, [5]]];
console.log(prices.flat().length);
console.log(typeof prices.flatten);
console.log(typeof prices.includes, typeof prices.contains);

Line 1 makes an array with arrays inside it. Line 2 calls flat, which pulls the inner items up one level, so the array becomes [3, 4, [5]] and line 2 prints 3.

Line 3 prints undefined: there is no flatten method, even though that is the name most people expect. Line 4 prints function undefined: arrays have includes, but not contains.

Two rows. In the first, the name flatten, marked broke old sites, points to the name flat, marked shipped in 2019. In the second, contains, marked broke old sites, points to includes, marked shipped in 2016.first namename that shippedflattenrenamedflatbroke old sitesshipped in 2019containsrenamedincludesbroke old sitesshipped in 2016
The obvious names, flatten and contains, broke real websites when browsers tried them. So line 2 calls flat, and lines 3 and 4 find no flatten or contains.

The obvious names were tried first. In each case, a browser shipped the method for testing, and real websites broke. So the spec picked other names. This lesson shows exactly how that happens, and what it means for the code you write.

Real-life analogyNaming a new street

Picture a city opening a new street. It wants to call it Main Street. But thousands of old address books already list a Main Street, and letters with those addresses are still in the mail. If the city reuses the name, those letters go to the wrong house. Nobody can rewrite every old letter, so the city picks another name, Market Street, and every old letter still arrives.

In real life: Old address books and letters still in the mail
In JavaScript: Old websites that nobody updates
In real life: A new street the city wants to name
In JavaScript: A new method or global the spec wants to add
In real life: An old street already called Main Street in those books
In JavaScript: An old library that already uses the name flatten
In real life: Calling the new street Market Street instead
In JavaScript: Shipping the method as flat
The main idea

Browsers must keep every old website working. So a new JavaScript feature can't change what old code does, and its name can't clash with names old code already uses. When a name clashes, the feature gets a new name. Removing a feature is even harder, so it almost never happens.

Web compatibility: old pages must keep working

Web compatibility means that a browser keeps running the pages that already exist. The Space Jam movie website from 1996 still works in today's browsers, almost exactly as it was built.

Imagine a browser that ships a change and breaks a popular site. Visitors don't know what changed. They only see that the site works in another browser, so they switch. Other browsers then refuse to ship the change. And a spec that browsers won't follow is just a document nobody uses. So the spec follows what browsers can really ship.

Even a famous mistake has to stay. typeof null gives "object", a bug from the first version of JavaScript in 1995.

A 1995 mistake that can never be fixedPop out in the code editor (opens in a new tab)JavaScript
var nothing = null;
console.log(typeof nothing);
console.log(typeof nothing === "object");

Line 1 stores null, which means "no value". Line 2 prints object, which is wrong: null is not an object. Line 3 prints true.

A proposal once tried to make typeof null give "null". It was turned down, because old code like line 3 counts on the answer "object". The null internals lesson explains where the mistake came from.

New keywords are a problem too. Before 2015, let had no job in JavaScript, so old code could use it as a variable name.

Newer keywords still work as names in sloppy codePop out in the code editor (opens in a new tab)JavaScript
var let = 3;
var async = 4;
var of = 5;
console.log(let + async + of);

Lines 1 to 3 make variables named let, async and of. Each of these words has a job in modern JavaScript, but sloppy code, the code without "use strict", still allows them as names. Line 4 prints 12.

Strict mode was new in 2009, so no old page used it. That made it safe to reserve let there.

Strict code reserves letPop out in the code editor (opens in a new tab)JavaScript
"use strict";
var let = 3;
console.log(let);

Line 1 makes the script strict. Line 2 uses let as a name, which strict code doesn't allow, so the whole script is a SyntaxError.

Not every change is risky. Read each row of the table as: "a change of this kind does this to old websites."

What each kind of change does to old websites
The kind of changeWhat it does to old websites
New syntax that used to be a SyntaxError, such as a?.bUsually safe. No working page could contain it, because it didn't run before.
A new method name on a built-in object, such as flattenRisky. An old library may already use that name in its own way.
A new global name, such as globalRisky. Old code may test for that name to guess where it is running.
A new keyword, such as letRisky. Old code may use the word as a variable name, so sloppy code still allows it.
A change to what old code does, such as making typeof null give "null"Almost never allowed. Old code depends on the answer it always got.

New syntax is the safest change: code like a?.b was a SyntaxError before, so no working page contains it. New names are the risky ones, because old code may already use them.

SmooshGate: why flatten became flat

In 2018, a new array method called flatten was almost finished. Firefox turned it on for testing, and some websites broke. The proposal's author joked that it could be renamed smoosh. Many people took the joke seriously, and the fuss got the name SmooshGate.

The broken sites used MooTools, a library that was popular in the late 2000s. To see what went wrong, you need one idea first: enumerable. Every property has an enumerable setting. A for…in loop visits only the enumerable ones.

for…in visits only enumerable propertiesPop out in the code editor (opens in a new tab)JavaScript
var methods = {};
methods.flatten = function () {};
Object.defineProperty(methods, "flat", {
  value: function () {},
  enumerable: false,
});
for (var key in methods) {
  console.log(key);
}

Line 2 adds flatten with a plain assignment. A property made this way is enumerable.

Lines 3 to 6 add flat with Object.defineProperty, which lets you choose the settings. Line 5 makes it not enumerable, the way browsers add their built-in methods. Lines 7 to 9 loop over the names, and only flatten is printed.

The second idea is the surprise. Assigning to a property that already exists only changes its value. Its settings stay as they were.

Assigning keeps a hidden property hiddenPop out in the code editor (opens in a new tab)JavaScript
var methods = {};
Object.defineProperty(methods, "flatten", {
  value: function () {},
  enumerable: false,
  writable: true,
});
methods.flatten = function () {};
console.log(Object.keys(methods).length);

Lines 2 to 6 make a flatten property that is not enumerable but can be changed. Line 7 assigns a new function to it. The value changes, but the property stays not enumerable, so Object.keys, which lists enumerable names, finds nothing. Line 8 prints 0.

Now the MooTools pattern. It added its own flatten to every array, then copied all of its array methods to its own list type, Elements, with a for…in loop.

What MooTools did, in shortPop out in the code editor (opens in a new tab)JavaScript
Array.prototype.flatten = function () {
  return "library flatten";
};
var Elements = {};
for (var key in Array.prototype) {
  Elements[key] = Array.prototype[key];
}
console.log(typeof Elements.flatten);

Lines 1 to 3 add the library's flatten to Array.prototype, the object all arrays share their methods from. It is a plain assignment, so the property is enumerable.

Lines 5 to 7 copy every enumerable property into Elements. Built-in methods like map are skipped, but flatten is copied, so line 8 prints function. This is what happens in today's browsers.

Now pretend the browser had shipped a built-in flatten. The program below adds it first, the way a browser would.

The same library after a built-in flatten shipsPop out in the code editor (opens in a new tab)JavaScript
Object.defineProperty(Array.prototype, "flatten", {
  value: function () {},
  enumerable: false,
  writable: true,
  configurable: true,
});
Array.prototype.flatten = function () {
  return "library flatten";
};
var Elements = {};
for (var key in Array.prototype) {
  Elements[key] = Array.prototype[key];
}
console.log(typeof Elements.flatten);

Lines 1 to 6 add a built-in flatten, not enumerable. Lines 7 to 9 are the library's assignment. flatten already exists, so only its value changes, and it stays not enumerable.

Lines 11 to 13 copy the enumerable properties. flatten is skipped, so line 14 prints undefined. Every site that called flatten on an Elements list now throws a TypeError.

Two halves. On the left, the library's own flatten card is marked enumerable, and an arrow labeled for-in copies it goes down into a box labeled Elements, which holds flatten; the result is function. On the right, a built-in flatten that the library replaced is marked not enumerable, for-in skips it, and the Elements box holds nothing; the result is undefined.library adds flattenflattenenumerablefor-in copies itflattenElements"function"built-in flatten, replacedflattennot enumerablefor-in skips itnothingElements"undefined"
Left: the library adds flatten itself, so it is enumerable and the loop copies it. Right: the browser already had a built-in flatten. The library's assignment only changes its value, so it stays hidden, and Elements gets no flatten.
Step through a built-in flatten meeting an old library
Step 0 of 4Ready
Your turn: follow the blue line

A replay of the example code, not a view inside a JavaScript engine. It runs on a stand-in for Array.prototype, so this page's arrays don't change.

Running in
  1. script
Next: line 1
  value: function () {},  enumerable: false,  writable: true,  configurable: true,});Array.prototype.flatten = function () {  return "library flatten";};var Elements = {};for (var key in Array.prototype) {  Elements[key] = Array.prototype[key];}console.log(typeof Elements.flatten);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Press Step and watch the enumerable setting beside line 3. It stays false after line 7. This replays the example code; it isn't a view inside the JavaScript engine. It runs on a stand-in for Array.prototype, so this page's arrays don't change.

Why not make the built-in flatten enumerable? Then every old for…in loop over an array would suddenly visit an extra flatten name, and break different sites. Why not fix MooTools? A fixed version only helps sites that update, and the broken ones are the ones nobody updates. So in May 2018, the committee renamed it.

flat and flatMap: the names that shippedPop out in the code editor (opens in a new tab)JavaScript
var prices = [3, [4, [5]]];
console.log(prices.flat().length);
console.log(prices.flat(Infinity).join(" "));
console.log([3, 4].flatMap((p) => [p, p * 2]).join(" "));

Line 2 flattens one level, so the array has 3 items and line 2 prints 3. Line 3 passes Infinity, which flattens every level, and prints 3 4 5.

Line 4 uses flatMap, which runs a function on each item and flattens the results one level. Each price becomes a pair, so it prints 3 6 4 8.

contains became includes, and other renamed features

flatten wasn't the first time. In 2014, arrays were going to get a contains method. MooTools had its own array contains, with the same copy problem. So in November 2014, the committee renamed it to includes, and renamed the string method contains to match.

includes is the name that shippedPop out in the code editor (opens in a new tab)JavaScript
var menu = ["tea", "cake"];
console.log(menu.includes("tea"));
console.log("teacup".includes("cup"));
console.log(typeof menu.contains, typeof "tea".contains);

Line 2 asks whether the array holds "tea" and prints true. Line 3 asks whether the string holds "cup" and prints true. Line 4 prints undefined undefined: neither has contains.

The page's own objects still use the old word. The list of classes on an element, element.classList, has a contains method, so you will see both names in real code.

Global names can clash too. Browsers call the global object window, and Node.js calls it global. A proposal tried to add global to browsers as a name that works everywhere. Old code like this stopped working.

Old code that guesses where it runsPop out in the code editor (opens in a new tab)JavaScript
var place = typeof global !== "undefined" ? "Node.js" : "a browser";
console.log(place);

Line 1 checks whether a name global exists. In a browser it doesn't, so place is "a browser", and line 2 prints a browser.

If browsers added global, the same line would decide it was running in Node.js, and the site would take the wrong path. That broke flickr.com and other sites. So the name became globalThis.

globalThis works in every environmentPop out in the code editor (opens in a new tab)JavaScript
console.log(typeof globalThis);
console.log(globalThis === this);

Line 1 prints object. Line 2 prints true: at the top of a classic script, this is the global object too.

The newest rename was for grouping. The plan was an array method groupBy, but old versions of a library called Sugar added their own groupBy to arrays. The next try, group, clashed with other old code. So the committee moved it off arrays entirely.

Object.groupBy: a static method insteadPop out in the code editor (opens in a new tab)JavaScript
var menu = [
  { name: "tea", kind: "drink" },
  { name: "cake", kind: "food" },
  { name: "juice", kind: "drink" },
];
var groups = Object.groupBy(menu, (item) => item.kind);
console.log(groups.drink.length, groups.food.length);
console.log(typeof menu.groupBy, typeof menu.group);

Lines 1 to 5 make a menu. Line 6 calls Object.groupBy, a static method: one that lives on Object itself, not on every array. It sorts the items into groups by the kind the function returns.

Line 7 prints 2 1: two drinks and one food. Line 8 prints undefined undefined: arrays got neither name.

Read each row of the table as: "the first name was this, it couldn't ship for this reason, so this shipped instead."

Features renamed to avoid breaking websites
The first nameWhy it couldn't shipWhat shipped instead
Array.prototype.flattenMooTools set its own flatten, and its copy loop would skip a built-in one.flat and flatMap, in 2019
String.prototype.contains and Array.prototype.containsMooTools had the same copy problem with an array contains, and strings were renamed to match.includes: strings in 2015, arrays in 2016
globalSites such as flickr.com checked typeof global to guess they were in Node.js.globalThis, in 2020
Array.prototype.groupBy, then groupOld versions of the Sugar library, and other code, already used those names on arrays.Object.groupBy and Map.groupBy, in 2024

Symbol.unscopables: hiding new methods from old with blocks

Old code has one more trap: the with statement. Inside with (list) { … }, every plain name is looked up on list first. The with statement lesson explains it.

So every new array method is a new name that with could find first. When 2015 added methods such as values, keys and entries, old with blocks that used variables with those names would have found the methods instead.

The fix is a list called Symbol.unscopables. Names on that list are skipped by with.

with skips values, because it is on the listPop out in the code editor (opens in a new tab)JavaScript
var values = [3, 4];
with ([1, 2]) {
  console.log(values.join(" "));
}

Line 1 makes a variable values. Line 2 starts a with block for the array [1, 2]. That array has a values method, but the name is on the list, so line 3 skips it, finds the variable from line 1, and prints 3 4.

A card reading look up values points at a cart labeled [1, 2], which holds a values method. Under the cart: on the hidden list, so with skips it. An arrow goes on to a box labeled values holding [3, 4]. Under the box: found here, and prints 3 4.look up valuesvalues[1, 2]on the hidden list,so with skips it[3, 4]valuesfound hereprints 3 4
Inside with ([1, 2]), the name values is checked on the array first. The array has a values method, but it is on the Symbol.unscopables list, so with skips it and finds the variable from line 1.

Here is what old code would have seen without the list.

The same lookup with an empty listPop out in the code editor (opens in a new tab)JavaScript
var values = [3, 4];
var list = [1, 2];
Object.defineProperty(list, Symbol.unscopables, { value: {} });
with (list) {
  console.log(typeof values);
}

Line 3 gives this one array its own, empty Symbol.unscopables list. Now nothing is skipped. Inside the block, line 5 finds the array's values method before the variable, and prints function.

Which names are on the listPop out in the code editor (opens in a new tab)JavaScript
var hidden = Array.prototype[Symbol.unscopables];
console.log(hidden.values, hidden.flat, hidden.includes);
console.log(hidden.map);

Line 1 reads the list from Array.prototype. Line 2 prints true true true: values, flat and includes are all on it. Line 3 prints undefined: map is older than the list, and old with code already lived with it, so it was never added.

Most new array methods are added to this list as they ship, so old with code keeps working.

Use counters and deprecation: measuring before removing

Before a browser removes or changes anything, it needs to know how many pages would break. Guessing isn't enough. Browsers measure it.

A use counter is a small counter built into the browser. For each page load, it records whether the page used a feature, such as substr. Chrome adds up these counts from many users who allow it, and publishes the share of page loads that use each feature on chromestatus.com. Firefox collects similar numbers.

Here is a tiny model of a use counter, written for this lesson, with four page loads.

A tiny use counterPop out in the code editor (opens in a new tab)JavaScript
var pages = [
  ["substr", "map"],
  ["map"],
  ["escape", "map"],
  ["map", "substr"],
];
var used = {};
for (var page of pages) {
  for (var name of page) {
    used[name] = (used[name] || 0) + 1;
  }
}
for (var name in used) {
  console.log(name + ": " + (used[name] / pages.length) * 100 + "%");
}

Lines 1 to 6 list four page loads, and the features each one used. Line 7 makes an empty object, used, for the counts.

Lines 8 to 12 go through each page, and line 10 adds 1 for each feature that page used. Lines 13 to 15 divide each count by the 4 pages and print substr: 50%, map: 100% and escape: 25%.

Four browser windows, page 1 to page 4, list the features each page used: substr and map; map; escape and map; map and substr. An arrow points down to a card: substr, 2 of 4 pages, 50%.page 1substrmappage 2mappage 3escapemappage 4mapsubstrcount the pages that used substrsubstr: 2 of 4 pages = 50%
A use counter asks one question per page load: did this page use the feature? substr was used on page 1 and page 4, 2 of the 4 pages, so line 14 prints substr: 50%.
Step through a use counter
Step 0 of 8Ready
Your turn: follow the blue line

A replay of the example code, not a view inside a JavaScript engine. The counting code runs for real.

Running in
  1. script
Next: line 7
var pages = [  ["substr", "map"],  ["map"],  ["escape", "map"],  ["map", "substr"],];for (var page of pages) {  for (var name of page) {    used[name] = (used[name] || 0) + 1;  }}for (var name in used) {  console.log(name + ": " + (used[name] / pages.length) * 100 + "%");}
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Press Step and watch the counts beside line 7 grow, one page at a time. This replays the example code; it isn't a view inside the JavaScript engine.

The real numbers are much smaller. A feature used on even a tiny share of page loads can still mean many thousands of websites. When a browser team does decide to remove a feature, it usually follows these steps:

  1. Measure how many page loads use it, with a use counter.
  2. Deprecate it: print a warning in the developer console, so the people who run those sites can find out.
  3. Wait, and watch the number fall.
  4. Remove it, if the number is small enough and the other browsers agree.

Chrome removed Object.observe this way in 2016. For the language itself, removal is rare. More often, the spec is changed to describe what browsers already do. That is how the old features in the Annex B lesson got into the spec.

Writing code that keeps working as JavaScript grows

MooTools did nothing strange for its time. Its mistake was changing objects it didn't own. Your code can avoid that mistake, so it keeps working when JavaScript adds new features.

Adding your own methods to Array.prototype causes trouble even today.

An added method shows up in for…in loopsPop out in the code editor (opens in a new tab)JavaScript
Array.prototype.last = function () {
  return this[this.length - 1];
};
var prices = [3, 4];
for (var key in prices) {
  console.log(key);
}

Lines 1 to 3 add a last method to every array with a plain assignment, so it is enumerable. Lines 5 to 7 loop over an array with for…in, which visits every enumerable name. It prints 0, 1, and then last, which isn't an item at all.

And if JavaScript ever ships its own last, your version would replace it on every page that loads your code.

A plain function, and the standard at()Pop out in the code editor (opens in a new tab)JavaScript
function last(list) {
  return list[list.length - 1];
}
var prices = [3, 4];
console.log(last(prices));
console.log(prices.at(-1));

Lines 1 to 3 make last a plain function that takes the array as a value. It changes nothing shared, so it can't clash with anything. Line 5 prints 4.

Line 6 uses at(-1), a standard method that counts from the end when given a negative number. It also prints 4.

Sometimes you must support old browsers that lack a standard method. Code that adds a missing standard feature is called a polyfill. A careful polyfill checks first, and adds the method the way a browser would.

A careful polyfill for at()Pop out in the code editor (opens in a new tab)JavaScript
if (!Array.prototype.at) {
  Object.defineProperty(Array.prototype, "at", {
    value: function (index) {
      return this[index < 0 ? this.length + index : index];
    },
    writable: true,
    configurable: true,
  });
}
console.log([3, 4].at(-1));

Line 1 checks whether the browser already has at. Every modern browser does, so lines 2 to 8 are skipped. In an old browser, lines 2 to 8 would add at with Object.defineProperty, not enumerable, so for…in loops don't see it.

Line 4 handles a negative index by counting back from the length. Line 10 prints 4 either way.

Now try being the browser. The playground runs an old website with a MooTools-style library. Pick a name and ship a built-in method with it. Guess first: does the old site still work?

Playground: ship a new array method next to an old library
The browser ships a built-in methodJavaScript
Object.defineProperty(Array.prototype, "flatten", {
  value: function () {},
  enumerable: false,
  writable: true,
  configurable: true,
});
The old website and its libraryJavaScript
Array.prototype.flatten = function () {};
Array.prototype.contains = function () {};
var Elements = {};
for (var key in Array.prototype) {
  Elements[key] = Array.prototype[key];
}
console.log("flatten: " + typeof Elements.flatten);
console.log("contains: " + typeof Elements.contains);
What the old website printsbreaks
Elements.flattenundefined
Elements.containsfunction
Try it yourself
The old site breaks.

The library assigns its own flatten, but the built-in one was already there and not enumerable, so the loop skips it. Elements.flatten is undefined, and the old site's call to it would throw a TypeError.

Both parts run for real, one after the other. They run on a stand-in for Array.prototype, so this page's own arrays don't change.

Ship flatten or contains: the library's copy loop skips the clashing name, and the old site breaks. Ship flat or includes: the library never uses those names, and everything keeps working. That is exactly why the committee chose them.

Safe to ship, or would it break old websites?
  • New syntax a?.b, which used to be a SyntaxError
  • A built-in Array.prototype.flatten
  • A built-in Array.prototype.flat
  • A new global named global
  • A new global named globalThis
  • A static method Object.groupBy
  • A built-in Array.prototype.groupBy
  • Making let a reserved word in sloppy code
  • Making typeof null give "null"
  • A new array method that is also on Symbol.unscopables
Try it yourself
0 of 10 correct

Put each change in its group: safe to ship, or would break old websites.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Three habits keep your code safe as JavaScript grows: don't add to objects you don't own, such as Array.prototype; loop over arrays with for…of, not for…in; and check for a feature before you add it.

Common mix-ups

Most mix-ups come from thinking that only removing a feature can break things, or that a broken library is the library's problem alone.

  • "Adding a feature can't break old code." No. A new name can clash with a name old code already uses, as flatten, contains and global did.
  • "The committee renamed flatten to smoosh." No. That was a joke. The name that shipped is flat.
  • "Assigning to a property makes it enumerable." No. Assigning to a property that already exists keeps its settings. Only a new property made by assignment is enumerable.
  • "Browsers could just tell old sites to update." No. Many old sites have nobody looking after them, so they never update.
  • "let is reserved everywhere." No. Only in strict code. Sloppy code still allows it as a name.
  • "typeof null will be fixed one day." No. Too much code depends on "object", so the fix was turned down.
  • "A use counter counts every call." No. It records once per page load whether the page used the feature at all.

The table below puts three of these side by side. Read each row as: "people often think this about the topic, but this is true."

What is true about three ideas from this lesson
The topicWhat people thinkWhat is true
Adding a methodIt can't break anything, because old code doesn't call it.Old code may already use the name, or copy methods with a for-in loop.
A library's bugBrowsers should ship anyway and let the library fix it.Old sites never update their copy, so their visitors would get broken pages.
Symbol.unscopablesIt is a list you add your own names to.It is mainly for the language: it keeps new array methods out of old with blocks.

Practice exercises

Every program below is a sloppy classic script. For each one, ask which names old code and new code both want, and which properties are enumerable. Guess first, then run it.

Exercise 1 · Warm-upPredict which names Object.keys finds

What does line 5 print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
var shelf = {};
Object.defineProperty(shelf, "flat", { value: 1, enumerable: false, writable: true });
shelf.flat = 2;
shelf.extra = 3;
console.log(Object.keys(shelf).join(" "), shelf.flat);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upPredict keywords used as names

    What does line 4 print? Then rename the variables so the program would also work in strict code.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    var let = 1;
    var async = 2;
    var await = 3;
    console.log(let + async + await);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticePredict two names inside with

      What does line 3 print? Then rewrite the program without with.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      var keys = "outer";
      with ([3, 4]) {
        console.log(typeof keys, length);
      }

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFind the bug in a counting loop

        The array has 2 items. What does line 9 print? Then fix the program so it counts 2 and still sums the prices.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        Array.prototype.sum = function () {
          return this.reduce((a, b) => a + b, 0);
        };
        var prices = [3, 4];
        var count = 0;
        for (var key in prices) {
          count = count + 1;
        }
        console.log(count);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengePredict what happens when global exists

          Line 1 pretends a browser added a global called global. What does line 3 print? Then write a check that isn't fooled by it.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          var global = { name: "pretend" };
          var place = typeof global !== "undefined" ? "Node.js" : "a browser";
          console.log(place);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengePredict which library method survives

            What does line 9 print? Then change the name the browser ships on line 2 so the old site keeps both of its methods.

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            var arrayMethods = {};
            Object.defineProperty(arrayMethods, "contains", { value: function () {}, writable: true, configurable: true });
            arrayMethods.contains = function () { return "library"; };
            arrayMethods.flatten = function () { return "library"; };
            var Elements = {};
            for (var key in arrayMethods) {
              Elements[key] = arrayMethods[key];
            }
            console.log(typeof Elements.contains, typeof Elements.flatten);

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask what old code already does with a name, and whether a new feature would change that. Each answer explains why it's right or wrong.

              The don't break the web quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Why is the method called flat and not flatten?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does assigning to a hidden property print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                var box = {};
                Object.defineProperty(box, "size", { value: 1, writable: true });
                box.size = 2;
                console.log(Object.keys(box).length, box.size);

                Choose an answer to see the explanation.

              3. Question 3 of 7What does a sloppy script with a variable named let print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                var let = 5;
                console.log(let * 2);

                Choose an answer to see the explanation.

              4. Question 4 of 7What does entries mean inside with ([1, 2])?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                var entries = "mine";
                with ([1, 2]) {
                  console.log(entries);
                }

                Choose an answer to see the explanation.

              5. Question 5 of 7What does a use counter measure?

                Choose an answer to see the explanation.

              6. Question 6 of 7Why did globalThis get such an unusual name?

                Choose an answer to see the explanation.

              7. Question 7 of 7What do includes and contains give on an array today?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log(typeof [].includes, typeof [].contains);

                Choose an answer to see the explanation.

              Key takeaways

              JavaScript grows by adding, carefully, around everything that already exists. Knowing the rule explains odd names, and helps you write code that won't be the next MooTools.

              • Browsers must keep old websites working, and the spec follows what browsers can ship.
              • New syntax is usually safe. New names are risky, because old code may already use them.
              • Assigning to a property that already exists keeps it not enumerable. That is why a built-in flatten broke MooTools' copy loop.
              • flatten became flat, contains became includes, global became globalThis, and groupBy became Object.groupBy.
              • Symbol.unscopables keeps new array methods out of old with blocks.
              • Use counters measure how many page loads use a feature. Removal is rare, and comes after a deprecation warning.
              • Don't add to objects you don't own, don't use for…in on arrays, and check before you polyfill.

              Remember the one-liner.
              Old pages never update, so JavaScript only adds, and picks names no old code uses.

              Coming next: The early editions: ES1 to ES5. It tells how JavaScript was first standardized, and about the edition that almost broke it.

              CompleteFrontend Clear concepts. Working examples.