cf.completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Deprecated & obsolete features

Learn the deprecated and obsolete JavaScript features you meet in old code, such as fn.caller, for each and Object.observe, and what replaced them.

By the end, you can
  • 01
    Tell the three kinds apartSay whether an old feature is deprecated, obsolete or non-standard, and what that means for code that uses it.
  • 02
    Read old codeRecognize fn.caller, legacy generators, for each, Object.observe and uneval when you meet them.
  • 03
    Replace each oneRewrite each old feature with its modern replacement, such as rest parameters, function*, for…of and Proxy.

Deprecated, obsolete and non-standard features

Old JavaScript code is full of features that you should not write today. Some of them still run. Some of them were removed, so the old code fails. And some of them only ever worked in one browser. This lesson shows the ones you are most likely to meet, what each one did, and what to write instead.

Three words describe these old features:

  • Deprecated means "still works, but don't use it". Browsers keep it for old code, and the guides tell you to use something newer.
  • Obsolete means "removed". Old syntax is now a SyntaxError, and old functions are undefined.
  • Non-standard means "never in the spec". The spec is the rulebook every JavaScript engine follows. A non-standard feature works only in the engines that chose to add it.
One deprecated method and two obsolete featuresPop out in the code editor (opens in a new tab)JavaScript
console.log(typeof "tea".substr);
console.log(typeof Object.observe);
console.log(typeof uneval);

typeof gives the type of a value as a string, and it gives "undefined" for a name or property that doesn't exist. So it is a safe way to ask "is this feature here?"

Line 1 asks about substr, an old string method. It is deprecated, but every browser still has it, so line 1 prints function. The Annex B lesson covers it and the other old features browsers must keep.

Line 2 asks about Object.observe, which Chrome had from 2014 to 2016. Line 3 asks about uneval, which only Firefox had. Both were removed, so both lines print undefined.

Three rows. typeof "tea".substr points at "function", labeled deprecated. typeof Object.observe points at "undefined", labeled obsolete. typeof uneval points at "undefined", labeled obsolete.typeof "tea".substr"function"deprecatedtypeof Object.observe"undefined"obsoletetypeof uneval"undefined"obsolete
Line 1 finds substr, a deprecated method that still works. Lines 2 and 3 find nothing, because Object.observe and uneval are obsolete: browsers removed them.

Notice that both removed features were only ever in one browser. Websites rarely depended on them, so the browser could remove them. A feature that every browser has, and that the spec describes, almost never goes away.

Read each row of the table as: "a feature of this kind does this today, and here are some examples from this lesson."

The three kinds of old feature
The kindWhat it means todayExamples in this lesson
DeprecatedIt still works, but the spec or MDN tells you not to use it in new code.brew.caller, brew.arguments, /\a/ without the u flag
ObsoleteIt was removed. Old syntax is a SyntaxError, and old functions are undefined.for each…in, Object.observe, uneval
Non-standardIt was never in the spec. Only some engines have it.InternalError, error.fileName, displayName
Real-life analogyBus routes: running, closed and private

Picture a city's buses. Some old routes still run, but a sign at the stop says the route will close and points you to a newer one. Some routes have closed: the stop sign is gone, and waiting there gets you nowhere. And one hotel runs its own shuttle that isn't on the city map at all. Only that hotel's guests can ride it.

In real life: The city's official route map
In JavaScript: The JavaScript spec
In real life: A route that still runs, with a sign pointing to a newer one
In JavaScript: A deprecated feature, such as brew.caller
In real life: A closed route whose stop sign was taken down
In JavaScript: An obsolete feature, such as Object.observe
In real life: A hotel's private shuttle, not on the city map
In JavaScript: A non-standard feature, such as InternalError
The main idea

A deprecated feature still works, but new code should use its replacement. An obsolete feature was removed, so old code that uses it fails. A non-standard feature was never in the spec, so it works only in some engines. For each one, there is a modern way to do the same job.

fn.caller and fn.arguments: a function looking at its own call

In old code, a function could look at its own call from the outside, through two properties on the function itself:

  • brew.caller is the function that called brew.
  • brew.arguments holds the values passed to brew in its current call.

Both are deprecated. They still work in sloppy functions, the functions in code without "use strict". The sloppy mode lesson explains that mode.

brew finds out who called itPop out in the code editor (opens in a new tab)JavaScript
function brew() {
  return brew.caller.name;
}
function serve() {
  return brew();
}
console.log(serve());

Lines 1 to 3 make a function brew. Line 2 reads brew.caller, the function that called brew, and returns that function's name.

Lines 4 to 6 make serve, which calls brew on line 5. Line 7 calls serve. While brew runs, its caller is serve, so line 7 prints serve.

Two function cards, serve on the left and brew on the right. An arrow from serve to brew is labeled calls on line 5. An arrow back from brew to serve is labeled brew.caller. Under serve: waiting for brew. Under brew: running now.servebrewcalls on line 5brew.callerwaiting for brewrunning now
While brew runs, brew.caller points back at the function that called it, serve. So line 2 returns the name serve, and line 7 prints it.
Step through brew.caller
Step 0 of 6Ready
Your turn: follow the blue line

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

Running in
  1. script
Next: line 7
function brew() {  return brew.caller.name;}function serve() {  return brew();}
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 brew.caller beside line 2. It holds serve only while brew runs. Once brew returns, it is null again. This replays the example code; it isn't a view inside the JavaScript engine.

When no function called brew, there is no caller to give.

caller when the script itself calls brewPop out in the code editor (opens in a new tab)JavaScript
function brew() {
  return brew.caller;
}
console.log(brew());

Line 4 calls brew from the script itself, not from inside a function. So line 2 gives null, and line 4 prints null.

brew.arguments works the same way: it only means something while the function runs.

total.arguments during and after the callPop out in the code editor (opens in a new tab)JavaScript
function total(price, tax) {
  return total.arguments.length;
}
console.log(total(3, 1, 5));
console.log(total.arguments);

Line 1 names two parameters, price and tax. Line 4 calls total with three values. Inside the call, line 2 reads total.arguments, which holds all three values, so line 4 prints 3.

Line 5 reads total.arguments after the call has finished. No call is running, so it prints null.

Strict code blocks both properties. A strict function has no caller or arguments of its own, so reading them finds a guard on Function.prototype, the object every function inherits from. That guard always throws a TypeError.

caller in a strict function throwsPop out in the code editor (opens in a new tab)JavaScript
"use strict";
function brew() {
  return brew.caller;
}
try {
  brew();
} catch (error) {
  console.log(error.name);
}

Line 1 makes the script strict. Line 6 calls brew, and line 3 reads brew.caller, which throws. Lines 7 and 8 catch the error and print its name, TypeError.

Newer kinds of function never got these properties, even in sloppy code: methods written as brew() { … } in an object, arrow functions, classes, generators and async functions.

caller on a method throws, even in sloppy codePop out in the code editor (opens in a new tab)JavaScript
var shop = {
  brew() {
    return shop.brew.caller;
  },
};
try {
  shop.brew();
} catch (error) {
  console.log(error.name);
}

Lines 1 to 5 make an object shop with a method brew. There is no "use strict". Line 7 calls the method, and line 3 reads its caller. Methods have no caller of their own, so the guard throws, and line 9 prints TypeError.

The modern way is simple: if a function needs to know something, pass it in. For any number of values, use rest parameters, written ...extras, which collect the extra values into a real array.

Pass the caller's name, and collect extra valuesPop out in the code editor (opens in a new tab)JavaScript
function brew(from, ...extras) {
  return from + " sent " + extras.length;
}
function serve() {
  return brew("serve", 3, 1);
}
console.log(serve());

Line 1 makes brew with a parameter from and a rest parameter extras. Line 5 passes "serve" first, then 3 and 1. So from is "serve" and extras is [3, 1]. Line 7 prints serve sent 2.

This works in strict code too, and a reader can see where every value comes from. To find out who called a function while debugging, use console.trace() or a breakpoint in your browser's developer tools.

Legacy regex syntax without the u flag

A regular expression, or regex, is a pattern that text can match, such as /tea/. Flags after the last slash change how the pattern works. The u flag turns on Unicode-aware mode, which reads the pattern with strict, modern rules. The newer v flag does too.

Without u or v, a pattern is in Unicode-unaware mode. In that mode, browsers still accept some loose, old pattern syntax. It is deprecated, and with the u flag it is a SyntaxError.

Loose patterns that still work without a flagPop out in the code editor (opens in a new tab)JavaScript
console.log(/\a/.test("a"));
console.log(/{/.test("{"));
console.log(/]/.test("]"));
console.log(/\101/.test("A"));

Line 1 uses \a. That is not a real escape, so the old rules read it as the plain letter a, and line 1 prints true.

Lines 2 and 3 use { and ] alone. These characters normally have a job in a pattern, but alone they are read as plain characters, so both lines print true.

Line 4 uses \101, an old octal escape: octal 101 is 65, the character number of A. It prints true.

The loose rules can hide real mistakes. \p{L} means "any letter", but only with the u flag.

The same \p{L} with and without the u flagPop out in the code editor (opens in a new tab)JavaScript
console.log(/\p{L}/.test("é"));
console.log(/\p{L}/.test("p{L}"));
console.log(/\p{L}/u.test("é"));

Line 1 has no flag, so \p is just the letter p, and {L} is plain text. The pattern looks for the text p{L}, which isn't in "é", so line 1 prints false.

Line 2 tests the text "p{L}" itself, so it prints true. Line 3 adds the u flag. Now \p{L} means any letter, é is a letter, and line 3 prints true.

Two rows. The pattern /\p{L}/ with no u flag means the text p{L}, and matches "p{L}". The pattern /\p{L}/u means any letter, and matches "é".patternwhat it meansa match/\p{L}/the text p{L}"p{L}"no u flag/\p{L}/uany letter"é"with the u flag
Without the u flag, \p is just the letter p, and {L} is plain text. So line 1 prints false and line 2 prints true. With u, \p{L} means any letter, so line 3 prints true.

With the u flag, the loose forms are errors you can see. new RegExp(text, flags) builds a pattern from text while the code runs, so its SyntaxError can be caught.

The u flag rejects a made-up escapePop out in the code editor (opens in a new tab)JavaScript
try {
  new RegExp("\\a", "u");
} catch (error) {
  console.log(error.name);
}
console.log(new RegExp("\\{", "u").test("{"));

Line 2 builds the pattern \a with the u flag. In a string, \\ means one backslash. The pattern is rejected, and line 4 prints SyntaxError.

Line 6 builds \{ with the u flag. A backslash before { is the clear way to say "a plain brace", so it is allowed, and line 6 prints true.

A pattern written straight into the code, between slashes, is checked before anything runs.

A bad pattern with the u flag stops the whole scriptPop out in the code editor (opens in a new tab)JavaScript
console.log("start");
console.log(/\a/u.test("a"));

Line 2 has /\a/u. The engine finds the error while reading the script, before line 1 runs. So the script is a SyntaxError, and not even start is printed.

"use strict" doesn't change any of this. Strict mode changes how code runs, not how a pattern is read.

Strict code still allows loose patternsPop out in the code editor (opens in a new tab)JavaScript
"use strict";
console.log(/\a/.test("a"));
console.log(/{/.test("{"));

Line 1 makes the script strict. Lines 2 and 3 have no flag, so the old rules still apply, and both print true.

Read each row of the table as: "this pattern means this without the u flag, and this with it."

The same pattern, with and without the u flag
The patternWithout the u flagWith the u flag
/\a/Matches the letter a.A SyntaxError: \a is not a known escape.
/{/ and /]/Match the characters { and ].A SyntaxError: write \{ and \].
/\101/Matches A, the character with octal number 101.A SyntaxError: there is no group number 101.
/\p{L}/Matches the text p{L}.Matches any letter, such as é.

So add the u flag, or the v flag, to new patterns. Then \p{L} works, and a typo is an error instead of a silent wrong match.

Legacy generators, comprehensions and StopIteration

A generator is a function that can pause. Each time you ask it for the next value, it runs until the next yield, hands out that value, and waits. Before generators were standard, Firefox had its own version: any normal function with yield inside became a generator. Firefox removed this in 2018.

A legacy generator is a SyntaxError todayPop out in the code editor (opens in a new tab)JavaScript
function count() {
  yield 1;
  yield 2;
}

Lines 1 to 4 make a normal function with yield inside. Today, in a normal sloppy function, yield is just a name, like price. A name followed by 1 makes no sense, so line 2 is a SyntaxError.

Old Firefox code also ended its loops in an unusual way. Each next() call gave the next value itself. When there were no values left, next() threw a special object called StopIteration. Here is how old code read every value. It doesn't run anywhere today, so this box has no Run button.

How old Firefox code read a generatorJavaScript
var steps = count();
try {
  while (true) {
    console.log(steps.next());
  }
} catch (error) {
  if (error !== StopIteration) throw error;
}

Line 1 starts the generator. Lines 3 to 5 call next() forever and print each value. When the generator runs out, next() throws StopIteration, which ends the loop.

Line 7 checks that the error really was StopIteration. Any other error is thrown again.

The standard generator, added in 2015, is marked with * after function. Its next() returns an object with two parts: value, the value, and done, which is true when it has finished.

A standard generator with function*Pop out in the code editor (opens in a new tab)JavaScript
function* count() {
  yield 1;
  yield 2;
}
var steps = count();
console.log(steps.next().value);
console.log(steps.next().value);
console.log(steps.next().done);

Line 1 makes count a generator with function*. Line 5 starts it. Lines 6 and 7 each run it to the next yield and print 1, then 2.

Line 8 asks again. Nothing is left, so next() returns an object with done set to true, and line 8 prints true. Nothing is thrown.

StopIteration is gone; done says when to stopPop out in the code editor (opens in a new tab)JavaScript
console.log(typeof StopIteration);
var steps = [3, 4].values();
steps.next();
steps.next();
console.log(steps.next().done);

Line 1 prints undefined, because StopIteration no longer exists. Line 2 asks an array for its values, which works like a generator. Lines 3 and 4 take the 3 and the 4. Line 5 asks once more and prints true.

Most of the time you don't call next() yourself. A for…of loop calls it for you and stops when done is true.

Firefox also had comprehensions: a short way to build a new array from an old one, inside square brackets. They were planned for the 2015 standard, then dropped.

An array comprehension is a SyntaxError todayPop out in the code editor (opens in a new tab)JavaScript
var prices = [3, 4, 5];
var doubled = [for (p of prices) p * 2];

Line 1 makes an array of prices. Line 2 was meant to double each price, but for can't appear inside square brackets today, so the script is a SyntaxError. Firefox also had a version in round brackets that made a generator; it is gone too.

map and filter do the same jobPop out in the code editor (opens in a new tab)JavaScript
var prices = [3, 4, 5];
var doubled = prices.map((p) => p * 2);
var cheap = prices.filter((p) => p < 5);
console.log(doubled.join(" "));
console.log(cheap.join(" "));

Line 2 uses map, which makes a new array by calling the function on each item. Line 3 uses filter, which keeps only the items where the function returns true.

Line 4 prints 6 8 10, and line 5 prints 3 4. join(" ") puts the items into one string with spaces between them.

for each…in, let blocks and expression closures

Three more pieces of Firefox-only syntax show up in old code, often in old Firefox add-ons. Firefox removed all three by 2018. Each one is a SyntaxError today, and each one has a standard replacement.

The first is for each…in. A normal for…in loop visits the property names of an object. The old for each…in visited the values.

for each…in is a SyntaxError todayPop out in the code editor (opens in a new tab)JavaScript
var cart = { tea: 3, cake: 4 };
for each (var price in cart) {
  console.log(price);
}

Line 1 makes a cart with two prices. Line 2 has the word each after for, which no engine accepts today, so the script is a SyntaxError, and line 3 never runs.

for…of with Object.values visits the valuesPop out in the code editor (opens in a new tab)JavaScript
var cart = { tea: 3, cake: 4 };
for (var price of Object.values(cart)) {
  console.log(price);
}

Line 2 uses Object.values(cart), which gives an array of the values, [3, 4]. for…of visits each item of that array, so line 3 prints 3, then 4.

The second is the let block: let with round brackets made new variables that lived only in the block after them.

A let block is a SyntaxError todayPop out in the code editor (opens in a new tab)JavaScript
var price = 5;
let (price = 3) { console.log(price); }

Line 1 makes a variable price with the value 5. Line 2 meant "a new price of 3, only inside these braces". In sloppy code today, let (price = 3) reads like a call to a function named let, and a brace right after a call is a SyntaxError.

A normal block with let insidePop out in the code editor (opens in a new tab)JavaScript
var price = 5;
{
  let price = 3;
  console.log(price);
}
console.log(price);

Lines 2 to 5 are a plain block. Line 3 makes a new price with let, which lives only inside the block, so line 4 prints 3. Line 6 is outside the block, so it reads the first price and prints 5.

The third is the expression closure: a function with no braces and no return, whose body was a single value.

An expression closure is a SyntaxError todayPop out in the code editor (opens in a new tab)JavaScript
var square = function (n) n * n;
console.log(square(4));

Line 1 meant "a function that returns n * n". Today a function body must be inside braces, so the script is a SyntaxError, and line 2 never runs.

An arrow function is the short form todayPop out in the code editor (opens in a new tab)JavaScript
var square = (n) => n * n;
console.log(square(4));

Line 1 makes an arrow function. With no braces after =>, it returns the value n * n. Line 2 prints 16.

Object.observe, watch and __noSuchMethod__

Programs often need to react when an object changes, for example to redraw a page when a price changes. Old browsers each had their own way to do this:

  • Object.observe(cart, fn) was in Chrome from 2014 to 2016. It called fn later with a list of the changes.
  • cart.watch("price", fn) was in Firefox. It called fn each time price was set.

Object.observe was planned for the standard, but the plan was dropped in 2015, and Chrome removed it. Firefox removed watch in 2018.

Both ways to watch an object are gonePop out in the code editor (opens in a new tab)JavaScript
console.log(typeof Object.observe);
console.log(typeof Object.prototype.watch);
var cart = { price: 3 };
try {
  cart.watch("price", function () {});
} catch (error) {
  console.log(error.name);
}

Lines 1 and 2 print undefined: neither function exists. Line 5 still tries to call cart.watch. Calling undefined throws, so line 7 prints TypeError.

The standard replacement is a Proxy. A Proxy is an object that stands in front of another object, called the target. You give it traps: functions that run when code reads or sets a property on the proxy.

A set trap sees every changePop out in the code editor (opens in a new tab)JavaScript
var cart = new Proxy({ price: 3 }, {
  set(target, name, value) {
    console.log(name + " changed to " + value);
    target[name] = value;
    return true;
  },
});
cart.price = 4;
console.log(cart.price);

Line 1 makes a proxy whose target is { price: 3 }. Lines 2 to 6 give it a set trap. The trap gets the target, the property's name and the new value.

Line 8 sets cart.price to 4. Because cart is a proxy, the trap runs: line 3 prints price changed to 4, and line 4 stores 4 on the target. Line 5 returns true to say the change worked.

Line 9 reads cart.price. There is no get trap, so the read goes straight to the target, and it prints 4.

A card cart.price = 4 points at a function card labeled set trap. Under the trap: prints price changed to 4. The trap points at a cart labeled target, which holds price: 4.cart.price = 4line 8set trapprintsprice changed to 4price: 4target
Line 8 doesn't store 4 straight away. The proxy calls the set trap, which prints the change on line 3 and stores the value on the target on line 4.
Step through a set trap
Step 0 of 6Ready
Your turn: follow the blue line

A replay of the example code, not a view inside a JavaScript engine. The proxy and its trap run for real.

Running in
  1. script
Next: line 1
  set(target, name, value) {    console.log(name + " changed to " + value);    target[name] = value;    return true;  },});cart.price = 4;console.log(cart.price);
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 to follow line 8 into the trap and back out. One difference from Object.observe: the trap runs right away, during the assignment. Object.observe reported changes later, in a batch. This replays the example code; it isn't a view inside the JavaScript engine.

Firefox had one more hook, __noSuchMethod__. If an object had it, calling a method that didn't exist ran __noSuchMethod__ instead of throwing. Firefox removed it in 2016.

__noSuchMethod__ is just a property todayPop out in the code editor (opens in a new tab)JavaScript
var shop = {
  __noSuchMethod__: function (name) {
    return "no " + name;
  },
};
try {
  shop.checkout();
} catch (error) {
  console.log(error.name);
}

Lines 1 to 5 give shop a __noSuchMethod__ function. Old Firefox would have run it for line 7 and returned no checkout.

Today __noSuchMethod__ is a normal property with no special meaning. shop.checkout is undefined, calling it throws, and line 9 prints TypeError.

A get trap can answer missing methodsPop out in the code editor (opens in a new tab)JavaScript
var shop = new Proxy({}, {
  get(target, name) {
    return function () {
      return "no " + String(name);
    };
  },
});
console.log(shop.checkout());

Lines 1 to 7 make a proxy with a get trap. The trap runs on every property read and gets the property's name. Here it always returns a new function.

Line 8 reads shop.checkout, so the trap returns a function. The call brackets then call that function, which returns "no " + "checkout". Line 8 prints no checkout.

String(name) on line 4 is there because a property name can also be a symbol, and adding a symbol to a string throws.

toSource() and uneval(): values written back as code

Firefox had a method toSource() on every value, and a global function uneval(). Both gave back text that looked like the code for the value. For the cart below, both gave text like (({item:"tea", price:3})).

Old code used them to save a value as text and to copy objects. Firefox removed both in 2020, and no other browser ever had them.

toSource and uneval are gone; JSON.stringify is standardPop out in the code editor (opens in a new tab)JavaScript
var cart = { item: "tea", price: 3 };
console.log(typeof cart.toSource);
console.log(typeof uneval);
console.log(JSON.stringify(cart));

Line 1 makes a cart. Lines 2 and 3 print undefined, because neither feature exists.

Line 4 uses JSON.stringify, which turns plain data into text in the JSON format, and prints {"item":"tea","price":3}. JSON.parse turns that text back into an object.

JSON can't hold functions. To see the code of a function, use toString(), which every function has.

A function's toString gives its codePop out in the code editor (opens in a new tab)JavaScript
function brew(cups) {
  return cups * 2;
}
console.log(brew.toString());

Lines 1 to 3 make brew. Line 4 prints the function's own code, all three lines exactly as they were written, from function brew(cups) { to the closing brace.

Old code often copied an object with eval(uneval(cart)): turn it into code, then run the code to build a new object. The standard way to make a deep copy, a copy where even the inner arrays and objects are new, is structuredClone.

structuredClone makes a deep copyPop out in the code editor (opens in a new tab)JavaScript
var cart = { item: "tea", sizes: [3, 4] };
var copy = structuredClone(cart);
copy.sizes.push(5);
console.log(cart.sizes.length);
console.log(copy.sizes.length);

Line 1 makes a cart with an inner array, sizes. Line 2 makes a deep copy. Line 3 adds 5 to the copy's sizes only.

Line 4 prints 2: the original array still has two items. Line 5 prints 3. The copy got its own array, so changing it didn't change the original.

Non-standard extras: InternalError, displayName and fileName

Some features were never in the spec, yet one engine still has them today. They aren't deprecated or removed; they are just not everywhere. Code that uses them behaves differently in different browsers.

InternalError is one. Firefox throws it when its engine hits a limit, most often when a function calls itself forever. The spec says the same problem is a RangeError, which is what Chrome and Safari throw.

The error for a function that calls itself foreverPop out in the code editor (opens in a new tab)JavaScript
function down() {
  return down();
}
try {
  down();
} catch (error) {
  console.log(error.name);
  console.log(error instanceof RangeError);
}

Lines 1 to 3 make down, which calls itself with no way to stop. Line 5 calls it. Each call waits for the next, until the engine runs out of room for waiting calls and throws.

Lines 7 and 8 print the error's name, and whether it is a RangeError. In Chrome, Safari and Node.js they print RangeError and true. In Firefox they print InternalError and false.

So don't check for one engine's error name. If you must catch this error, check error instanceof Error, which is true in every browser.

displayName is another. You can set it on a function to give it a friendlier name in tools. The standard name is name, which JavaScript sets for you.

displayName is just a property you setPop out in the code editor (opens in a new tab)JavaScript
var brew = function () {};
brew.displayName = "Make tea";
console.log(brew.name);
console.log(brew.displayName);

Line 1 stores a function in brew. JavaScript gives the function the name brew from the variable. Line 2 sets displayName.

Line 3 prints brew, and line 4 prints Make tea. Any engine lets you set the property, but among browsers only the Firefox console shows it. React's developer tools also read displayName on components.

The last one is fileName. Firefox adds fileName, lineNumber and columnNumber to every error. Other engines don't.

Which error properties does this browser add?Pop out in the code editor (opens in a new tab)JavaScript
var error = new Error("no tea");
console.log(typeof error.fileName);
console.log(typeof error.lineNumber);
console.log(typeof error.stack);

Line 1 makes an error. In Chrome and Safari, lines 2 and 3 print undefined. In Firefox, they print string and number.

Line 4 prints string in every major browser. stack, the list of calls that led to the error, isn't in the spec yet either, but every engine has it. Its exact text is different in each one.

The safe way to use any non-standard feature is a feature check: test that it exists before you use it, and have a plan for when it doesn't.

Check for fileName before using itPop out in the code editor (opens in a new tab)JavaScript
function whereFrom(error) {
  if (typeof error.fileName === "string") {
    return error.fileName + ":" + error.lineNumber;
  }
  return "unknown";
}
console.log(whereFrom(new Error("no tea")));

Line 2 checks whether fileName is a string. If it is, line 3 returns the file and the line number. If not, line 5 returns unknown.

In Chrome and Safari, line 7 prints unknown. In Firefox, it prints the address of the file and the line number where the error was made, 7. Either way, nothing throws.

Finding and replacing old features in old code

Obsolete syntax announces itself: the script fails with a SyntaxError and doesn't run at all. Deprecated features are quieter. They keep working until someone adds "use strict", turns the file into a module, or moves code into a class. Here is an old helper that logs its calls.

An old helper that uses caller and argumentsPop out in the code editor (opens in a new tab)JavaScript
function logCall() {
  var from = logCall.caller ? logCall.caller.name : "top";
  return from + " sent " + logCall.arguments.length;
}
function checkout() {
  return logCall("tea", 3);
}
console.log(checkout());
console.log(logCall());

Line 2 reads logCall.caller. If a function called logCall, it takes that function's name; if not, it uses "top". Line 3 counts the values passed in, with logCall.arguments.length.

Line 6 calls logCall from checkout with two values, so line 8 prints checkout sent 2. Line 9 calls it from the script itself with no values, so it prints top sent 0.

The same helper without caller or arguments, in strict modePop out in the code editor (opens in a new tab)JavaScript
"use strict";
function logCall(from, ...values) {
  return from + " sent " + values.length;
}
function checkout() {
  return logCall("checkout", "tea", 3);
}
console.log(checkout());
console.log(logCall("top"));

Line 1 makes the script strict, to show this version is allowed there. Line 2 takes the caller's name as the first parameter, from, and collects the other values with the rest parameter ...values.

Line 6 passes "checkout" itself. Line 9 passes "top". Lines 8 and 9 print the same two lines as before.

Before you rewrite, sort what you find. Deprecated features still run, so you can replace them one at a time. Obsolete ones already fail. Non-standard ones work only in some browsers, so check them first.

Deprecated, obsolete or non-standard?
  • brew.caller
  • brew.arguments
  • /\a/ with no flag
  • for each (var p in cart)
  • let (price = 3) { … }
  • function count() { yield 1; }
  • Object.observe(cart, fn)
  • uneval(cart)
  • InternalError
  • error.fileName
  • brew.displayName = "Make tea"
Try it yourself
0 of 11 correct

Put each piece of old code in its group: deprecated and still running, obsolete and removed, or non-standard and only in some engines.

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

The playground runs an old feature and its modern replacement side by side, for real, in this browser. Pick a feature, guess what the old program does, then add "use strict" on top and guess again.

Playground: run an old feature next to its replacement
The old programPop out in the code editor (opens in a new tab)JavaScript
function brew() {
  return brew.caller.name;
}
function serve() {
  return brew();
}
console.log(serve());
The modern programPop out in the code editor (opens in a new tab)JavaScript
function brew(from) {
  return from;
}
function serve() {
  return brew("serve");
}
console.log(serve());
What each program printssame
oldserve
modernserve
Try it yourself
Same result.

Both programs print the same thing. The old feature is deprecated: it still works, but write the modern one in new code.

Both runs are real. This page runs each program as the body of new Function, which is sloppy code unless it starts with "use strict".

Try fn.caller: both programs print serve, until you add "use strict" and the old one throws a TypeError. Try for each…in: the old program is a SyntaxError in both modes. Try uneval(): the old program throws a ReferenceError, because uneval doesn't exist.

The table lists every replacement in this lesson. Read each row as: "old code like this can be written like that, and here is why the new version is better."

Modern replacements for deprecated, obsolete and non-standard features
Old codeWhat to write insteadWhy it is better
brew.callerPass what brew needs as a parameter.It works in strict code, and the reader can see where the value comes from.
brew.argumentsRest parameters: function brew(...values).values is a real array, and it works in strict code.
/\a/ and /{/ with no flag/a/u and /\{/uWith the u flag, a mistake in a pattern is a SyntaxError you can see.
function count() { yield 1; }function* count() { yield 1; }The * marks a generator. Without it, the code is a SyntaxError today.
StopIterationCheck next().done, or use for…of.next() returns { value, done } instead of throwing at the end.
[for (p of prices) p * 2]prices.map((p) => p * 2)map and filter work in every engine.
for each (var p in cart)for (var p of Object.values(cart))for…of with Object.values visits each value.
let (price = 3) { … }{ let price = 3; … }A normal block with let inside does the same job.
function (n) n * n(n) => n * nAn arrow function is the short form every engine knows.
Object.observe and cart.watchA Proxy with a set trapThe trap sees each change as it happens, in every engine.
__noSuchMethod__A Proxy with a get trapThe get trap runs for every property read, including missing methods.
cart.toSource() and uneval(cart)JSON.stringify(cart) and structuredClone(cart)They are standard, and they don't need eval to turn text back into a value.
error.fileName and error.lineNumbererror.stack, after checking it existsEvery major engine has stack. Only Firefox has fileName.
InternalErrorCheck error instanceof ErrorChrome and Safari throw a RangeError for the same problem.

A linter can find some of these for you. ESLint, a popular one, has rules such as no-caller for arguments.callee and arguments.caller, and require-unicode-regexp for regexes with no u flag.

Common mix-ups

Most mix-ups come from treating "deprecated" as "gone", or from expecting strict mode to change things it doesn't touch.

  • "Deprecated means it no longer works." No. A deprecated feature still runs. Obsolete means removed.
  • "brew.caller always throws." No. It works in sloppy functions. Only strict functions, methods, arrow functions, classes, generators and async functions throw.
  • ""use strict" makes regexes strict too." No. Only the u or v flag turns on the strict pattern rules.
  • "/\p{L}/ matches any letter." No. Only with the u or v flag. Without one, it matches the text p{L}.
  • "A modern generator throws when it runs out." No. That was old Firefox's StopIteration. Today next() returns done: true.
  • "Object.observe is the standard way to watch an object." No. It was dropped and removed. Use a Proxy with a set trap.
  • "InternalError is a standard error type." No. Only Firefox has it. The spec uses RangeError.
  • "error.stack is in the spec." No. Every engine has it, but its text is different in each one.

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

What is true about three old features
The featureWhat people thinkWhat is true
brew.callerIt is gone from JavaScript.It still works in sloppy functions. Strict functions, methods and arrow functions throw a TypeError.
"use strict"It turns on the strict regex rules.Only the u or v flag does. /\a/ still works in strict code.
InternalErrorIt is a normal error type.Only Firefox has it. Other engines throw a RangeError when a function calls itself forever.

Practice exercises

Every program below is a sloppy classic script unless its line 1 says "use strict". For each one, find the old feature first and ask: is it deprecated, obsolete or non-standard? Guess first, then run it.

Exercise 1 · Warm-upPredict which function brew.caller names

serve calls order, and order calls brew. What does line 10 print? Then rewrite the program without caller.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function brew() {
  return brew.caller.name;
}
function order() {
  return brew();
}
function serve() {
  return order();
}
console.log(serve());

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

    Exercise 2 · Warm-upPredict \p{N} with and without the u flag

    What does line 1 print? Then write one pattern, with the u flag, that matches the text p{N}.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    console.log(/\p{N}/.test("5"), /\p{N}/u.test("5"));

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

      Exercise 3 · PracticeFind the bug in a strict function that counts its values

      What does the program print? Then fix track so that it prints 2 and the script stays strict.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      "use strict";
      function track(price) {
        return track.arguments.length;
      }
      try {
        console.log(track(3, 4));
      } catch (error) {
        console.log(error.name);
      }

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

        Exercise 4 · PracticeRun an old Firefox loop, then rewrite it

        This loop was written for old Firefox. What happens when you run it today? Then rewrite it so it prints 3 and 4.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        var cart = { tea: 3, cake: 4 };
        for each (var price in cart) {
          console.log(price);
        }

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

          Exercise 5 · ChallengePredict what a set trap records

          This is how you would watch a cart today, instead of Object.observe. What does line 12 print?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          var log = [];
          var cart = new Proxy({ price: 3 }, {
            set(target, name, value) {
              log.push(name + "=" + value);
              target[name] = value;
              return true;
            },
          });
          cart.price = 4;
          cart.tax = 1;
          cart.price += 1;
          console.log(log.join(" "));

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

            Exercise 6 · ChallengeFix a get trap that hides real methods

            This proxy replaces __noSuchMethod__, but it has a bug. What does line 13 print? Then fix the trap so shop.tea() prints tea ready and shop.cake() still prints no cake.

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            var menu = {
              tea() {
                return "tea ready";
              },
            };
            var shop = new Proxy(menu, {
              get(target, name) {
                return function () {
                  return "no " + String(name);
                };
              },
            });
            console.log(shop.tea());
            console.log(shop.cake());

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

              Check your understanding

              For each question, first ask what kind of feature it is: deprecated, obsolete or non-standard. Then ask whether the code is sloppy or strict. Each answer explains why it's right or wrong.

              The deprecated features quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does brew.caller give when the script itself calls brew?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function brew() {
                  return brew.caller;
                }
                console.log(brew());

                Choose an answer to see the explanation.

              2. Question 2 of 7Which of these still runs in Chrome today?

                Choose an answer to see the explanation.

              3. Question 3 of 7What does a regex with \q print in a strict script?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                "use strict";
                console.log(/\q/.test("q"));

                Choose an answer to see the explanation.

              4. Question 4 of 7How does a modern generator say it has finished?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function* count() {
                  yield 3;
                }
                var steps = count();
                steps.next();
                console.log(steps.next().done);

                Choose an answer to see the explanation.

              5. Question 5 of 7What replaced Object.observe and watch for reacting to changes on an object?

                Choose an answer to see the explanation.

              6. Question 6 of 7What happens when old code calls uneval today?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                try {
                  uneval({ price: 3 });
                } catch (error) {
                  console.log(error.name);
                }

                Choose an answer to see the explanation.

              7. Question 7 of 7What does it mean that InternalError is non-standard?

                Choose an answer to see the explanation.

              Key takeaways

              Old code will keep showing you these features. Knowing which kind each one is tells you whether it still runs, and what to write instead.

              • Deprecated features still run, obsolete ones were removed, and non-standard ones were never in the spec.
              • fn.caller and fn.arguments work only in sloppy functions. Pass values as parameters, and use rest parameters.
              • Without the u or v flag, patterns like /\a/ still work. Add u to turn mistakes into errors and to use \p{…}.
              • Legacy generators, comprehensions, for each, let blocks and expression closures are SyntaxErrors today. Write function*, map, for…of, a block with let, and arrow functions.
              • Object.observe, watch and __noSuchMethod__ are gone. A Proxy with set and get traps does their jobs.
              • toSource and uneval are gone. Use JSON.stringify, toString and structuredClone.
              • InternalError, displayName and fileName are non-standard. Check that a feature exists before you use it.

              Remember the one-liner.
              Deprecated still runs, obsolete is gone, non-standard is only somewhere; in new code, write the standard replacement.

              Coming next: Don't break the web. It explains why JavaScript can add features but almost never remove the standard ones.

              CompleteFrontend Clear concepts. Working examples.