The with statement
Learn how with makes names come from an object, where assignments go, what Symbol.unscopables hides, and why strict mode bans with.
- 01Predict a lookup inside withSay whether a name inside
with (cart)means a property ofcartor a variable outside, for reads and for writes. - 02Explain Symbol.unscopablesShow which array methods
withskips, and hide a property of your own fromwith. - 03Replace with in old codeExplain why strict mode bans
with, and rewrite it with destructuring or a short variable name.
The with statement: names that come from an object
with is one of the oldest statements in JavaScript. You write with (cart) { … }, and inside that block, JavaScript looks for every plain name on cart first. A plain name is a name written on its own, like price, with no object and dot before it.
with only works in sloppy mode, the mode for code without "use strict". The sloppy mode lesson explains that mode. Every example here is a classic script with no "use strict", so Edit & run runs it in sloppy mode.
var cart = { price: 3 };
var tax = 1;
with (cart) {
console.log(price + tax);
}Line 1 makes a cart object with one property, price, set to 3. Line 2 makes a variable tax with the value 1.
Line 3 starts a with block for cart. Line 4 uses two plain names. price is a property of cart, so it means cart.price, which is 3. cart has no tax, so JavaScript looks outside and finds the variable from line 2, which is 1. Line 4 prints 4. Line 5 ends the block.
with (cart), each name is looked for on cart first. price is on cart, so it gives 3. tax is not, so the lookup goes on to the script and finds 1. Line 4 prints 4.Old code used with to save typing: price + tax instead of cart.price + cart.tax. The cost is that you can no longer tell, by reading line 4, where each name comes from. That depends on what cart holds while the code runs.
Picture a shop clerk who answers questions about prices. Most days, the clerk checks the shelf in the back. But sometimes a tray of cards sits on the counter, and then the clerk always checks the tray first. You can't know which answer you'll get unless you know what is on the tray at that moment.
- In real life: The clerk puts a tray of price cards on the counter
- In JavaScript:
with (cart)putscartin front of the other names - In real life: Asked for "price", the clerk checks the tray first
- In JavaScript:
priceis looked for oncartfirst - In real life: No "tax" card on the tray, so the clerk checks the back shelf
- In JavaScript:
carthas notax, so the variable outside is used - In real life: Someone takes a card off the tray, and the same question gets a different answer
- In JavaScript: After
delete cart.price,pricemeans the variable outside
with (obj) { … } makes every plain name inside the block look on obj first. If obj has a property with that name, the name means that property, for reading and for writing. If not, the name means the variable outside, as usual. Strict mode, modules and classes don't allow with at all.
How a name is looked up inside with
When JavaScript meets a plain name, it searches for it in a fixed order. Without with, it looks in the current block, then the function around it, then the script, and last the global names. with (cart) adds one more place to the front of that list:
- the properties of
cart, including the onescartinherits, - then every place it would have looked without
with.
The search stops at the first place that has the name. It asks the real cart object every time, so the answer can change while the block runs.
var price = 5;
var cart = { price: 3 };
with (cart) {
console.log(price);
delete cart.price;
console.log(price);
}Line 1 makes a variable price with the value 5. Line 2 makes a cart with its own price, 3. Line 3 starts the with block.
Line 4 looks up price. cart has it, so line 4 prints 3. Line 5 removes the price property from cart. Line 6 is the same lookup as line 4, but cart has no price now, so the search goes outside and line 6 prints 5.
A replay of the example code, not a view inside a JavaScript engine. The example runs for real in sloppy mode.
script
var cart = { price: 3 };with (cart) { console.log(price); delete cart.price; console.log(price);}Press Step and watch lines 4 and 6. The text is the same, but the value comes from a different place each time. This replays the example code; it isn't a view inside the JavaScript engine.
Inherited properties count too. Every plain object gets methods such as toString and hasOwnProperty, and a constructor property, from Object.prototype. Inside with, those names are found on the object.
var constructor = "Asha";
var cart = { price: 3 };
with (cart) {
console.log(constructor === Object);
}
console.log(constructor);Line 1 makes a variable called constructor with the value "Asha". Line 2 makes a cart with only a price.
Line 4 looks up constructor inside with (cart). cart has no own property with that name, but it inherits one: the Object function. So line 4 prints true. Line 6 is outside the block, so it finds the variable and prints Asha.
The value in the brackets doesn't have to be an object. JavaScript turns a string, number or boolean into an object first, the same way it does for "tea".length.
with ("tea") {
console.log(length);
console.log(toUpperCase());
}Line 1 starts a with block for the string "tea". JavaScript wraps it in a String object, which has a length and all the string methods.
Line 2 finds length on that object and prints 3. Line 3 finds toUpperCase and calls it. A function found through with is called with the object as this, just like "tea".toUpperCase(). So line 3 prints TEA.
Only null and undefined can't be turned into an object. Using one of them throws an error before the block starts.
try {
with (null) {
console.log("inside");
}
} catch (error) {
console.log(error.name);
}Line 2 tries to start a with block for null. That throws a TypeError, so line 3 never runs. Lines 5 and 6 catch the error and print its name, TypeError.
Object Environment Records: the spec's name for it
The spec, the rulebook for JavaScript, keeps names in Environment Records. An Environment Record is a list of names and their values. Each block, function call and script gets one, and each one points to the record outside it. A lookup walks that chain from the inside out. The Environment Records lesson explains them in full.
Most records keep their own list of names. An Object Environment Record is different: it has no list. Its names are the properties of one object, called its binding object. with (cart) makes a new Object Environment Record whose binding object is cart, and puts it in front of the chain for the length of the block.
Spec steps read like a recipe. "Let x be …" means "call the result x". A ? means "if this step goes wrong, stop and throw the error". After each box, the same steps follow in plain words.
Here is what the spec does when it runs a with statement.
WithStatement : with ( Expression ) Statement
1. Let val be ? Evaluation of Expression.
2. Let obj be ? ToObject(? GetValue(val)).
3. Let oldEnv be the running execution context's LexicalEnvironment.
4. Let newEnv be NewObjectEnvironment(obj, true, oldEnv).
5. Set the running execution context's LexicalEnvironment to newEnv.
6. Let C be Completion(Evaluation of Statement).
7. Set the running execution context's LexicalEnvironment to oldEnv.
8. Return ? UpdateEmpty(C, undefined).In plain words, line by line:
- This rule is for code shaped like
with (something) statement. - Work out the expression in the brackets, such as
cart. - Turn its value into an object. A string becomes a String object.
nullorundefinedthrows aTypeErrorhere. - Remember the chain of records that is in use right now. Call it
oldEnv. - Make a new Object Environment Record for the object. The
truemarks it as awithrecord. Its outer record isoldEnv. - Make the new record the first place to look up names.
- Run the block. If it throws, keep the error for now.
- Put
oldEnvback, so names outside the block work as before. - Finish the statement with the block's result, or throw the kept error.
So with doesn't copy anything. It only puts the object in front of the chain, and takes it away again when the block ends, even if the block throws.
When a lookup reaches the new record, the spec asks the record whether it has the name. This step is called HasBinding.
HasBinding(N), for an Object Environment Record envRec
1. Let bindingObject be envRec.[[BindingObject]].
2. Let foundBinding be ? HasProperty(bindingObject, N).
3. If foundBinding is false, return false.
4. If envRec.[[IsWithEnvironment]] is false, return true.
5. Let unscopables be ? Get(bindingObject, %Symbol.unscopables%).
6. If unscopables is an Object, then
a. Let blocked be ToBoolean(? Get(unscopables, N)).
b. If blocked is true, return false.
7. Return true.In plain words, line by line:
- These steps answer: does this Object Environment Record have the name
N, such asprice? - Get the record's binding object, such as
cart. - Ask the object whether it has a property called
N, its own or inherited, like"price" in cart. - If it doesn't, answer no. The lookup moves on to the next record outside.
- If this record wasn't made by
with, answer yes. - For a
withrecord, read the object'sSymbol.unscopablesproperty. - If that property holds an object, check it.
- Read the property
Non it, and turn the result intotrueorfalse. - If it is
true, answer no: the name is skipped, even though the object has it. - Otherwise, answer yes.
Two facts follow. First, inherited properties count, because line 3 asks like the in operator. Second, a with record can be told to skip some names. That is Symbol.unscopables, which a later section covers.
The global names use an Object Environment Record too, with the global object as its binding object. That one isn't a with record, so line 5 answers yes and Symbol.unscopables is never read there.
Assignments inside with: on the object or outside
Writing to a name uses the same search as reading it. If the object has the name, the write changes the object. If not, the write goes to the variable outside. One rule surprises people: a write inside with never adds a new property to the object.
var cart = { price: 3 };
with (cart) {
price = 4;
total = 7;
}
console.log(cart.price);
console.log(cart.total);
console.log(total);Line 1 makes a cart with a price of 3. Line 2 starts the with block.
Line 3 assigns 4 to price. cart has a price, so the 4 goes into cart.price.
Line 4 assigns 7 to total. cart has no total, and no variable outside is called total either. In sloppy mode, an assignment to a name that exists nowhere makes a new global variable. So total becomes a global, and cart doesn't change.
Line 6 prints 4. Line 7 prints undefined, because cart never got a total. Line 8 prints 7, the global made by mistake on line 4.
price on cart, so cart.price becomes 4. Line 4 finds total nowhere, so sloppy mode adds it to the global object. That is why line 7 prints undefined and line 8 prints 7.A replay of the example code, not a view inside a JavaScript engine. The example runs for real in sloppy mode, and the global it makes is removed afterwards.
script
with (cart) { price = 4; total = 7;}console.log(cart.price);console.log(cart.total);console.log(total);Press Step and watch the side panel. After line 3, cart shows the new price. After line 4, a separate global total appears. This replays the example code; it isn't a view inside the JavaScript engine.
var inside with is even more confusing. A var always makes its variable in the function or script around it, never on the object. But the = 4 part is an assignment, and it runs inside the block.
var cart = { price: 3 };
with (cart) {
var price = 4;
}
console.log(cart.price);
console.log(price);Line 1 makes a cart with a price of 3. Before any line runs, line 3's var price makes a variable price in the script, with the value undefined.
Line 3 then runs price = 4 inside the block. The search finds cart.price first, so the 4 goes there. Line 5 prints 4. Line 6 prints undefined: the var made the variable, but it never got a value.
The table below sums up reads and writes. Read each row as: "when you write this inside with (cart), here is what happens in each case."
| When you write | If cart has price | If cart has no price |
|---|---|---|
Read price | It reads cart.price. | It reads the price variable outside. If there is none, it throws a ReferenceError. |
Assign price = 4 | It changes cart.price to 4. | It changes the price variable outside. If there is none, sloppy mode makes a new global. |
Write var price = 4 | It changes cart.price to 4. The var price outside stays undefined. | It gives the var price outside the value 4. |
Ask typeof price | It gives the type of cart.price. | It gives the type of the variable outside, or "undefined" if there is none. |
Use a name listed in cart[Symbol.unscopables] | It skips cart and looks outside. | It looks outside, like any missing name. |
Why with makes code hard to read and slow
Without with, you can find what a name means by reading the code: look for where it is declared, starting from the inside. Inside with, that isn't enough. The answer depends on which properties the object has when the line runs.
function label(item) {
var name = "Tea";
with (item) {
return name;
}
}
console.log(label({ price: 3 }));
console.log(label({ name: "Cake" }));Lines 1 to 6 make a function label. Line 2 makes a variable name with the value "Tea". Line 3 starts a with block for the item passed in. Line 4 returns name.
Line 7 passes an object with only a price. It has no name, so line 4 returns the variable, and line 7 prints Tea. Line 8 passes an object with a name property, so line 4 returns that, and line 8 prints Cake.
return name, can mean two different things. The first call passes an object with no name, so it gives the variable's Tea. The second passes an object with a name, so it gives Cake.The person who wrote line 4 probably meant one of the two. Nothing in the code says which. A caller can change the meaning of label just by passing an object with an extra property.
The same problem slows code down and gets in the way of tools:
- Engines make code fast by working out, before it runs, which variable each name means. Inside
with, they can't, so they must search at run time. Engines often skip their fastest paths for such code. - Tools that rename or remove variables, such as minifiers, must leave names near
withalone, because any of them might be a property. - Editors can't reliably jump to where a name inside
withis declared, because it might not be declared at all.
Symbol.unscopables: names that with skips
Arrays have many methods, and new ones keep being added. When newer JavaScript added array methods such as values and keys, some old pages stopped working. They used with on an array, and inside it, a name like values that used to mean the page's own variable now found the new array method instead.
The fix was Symbol.unscopables. A symbol is a special kind of property key that can't clash with any ordinary name. Symbol.unscopables is a built-in symbol. An object can keep, under that key, a list of its own property names that with must skip. Arrays use it to hide the newer methods.
var values = "outer";
var list = [3, 4, 5];
with (list) {
console.log(length);
console.log(values);
}Line 1 makes a variable values with the value "outer". Line 2 makes an array list. Line 3 starts a with block for list.
Line 4 finds length on list and prints 3. Line 5 looks up values. Every array has a values method, but arrays list values in Symbol.unscopables, so with skips it. The search goes outside, and line 5 prints outer.
length on list and prints 3. Arrays list values in Symbol.unscopables, so line 5 skips list and prints the outside values, outer.You can read the list yourself. It lives on Array.prototype, the object every array inherits its methods from.
var hidden = Array.prototype[Symbol.unscopables];
console.log(Object.keys(hidden).join(", "));
console.log(Object.getPrototypeOf(hidden));Line 1 reads the Symbol.unscopables object from Array.prototype and calls it hidden. Line 2 prints its property names, joined with commas. In a current browser, the list includes at, includes, keys, values, flat and toSorted. Older methods such as push, map and indexOf are not in it: old code may already rely on with finding them.
Line 3 prints null. The list object has no prototype, so it doesn't inherit names like toString. If it did, asking it about toString would wrongly answer yes.
A skipped name is treated as if the array didn't have it. So if nothing outside has that name either, it is a missing name.
var list = [3, 4, 5];
with (list) {
console.log(typeof push);
console.log(typeof includes);
}Line 1 makes list. Line 3 prints function: push isn't in the list, so it is found on list. Line 4 prints undefined: includes is skipped, and no variable is called includes. typeof is the one way to ask about a missing name without an error. A plain includes on line 4 would throw a ReferenceError.
Any object can have its own Symbol.unscopables list. Each property set to true on it names a property to skip.
var price = 5;
var cart = { price: 3, [Symbol.unscopables]: { price: true } };
with (cart) {
console.log(price);
}
console.log(cart.price);Line 1 makes a variable price with the value 5. Line 2 makes a cart with a price of 3, and a Symbol.unscopables object that says price: true.
Line 4 looks up price inside with (cart). cart has it, but its list says to skip it, so line 4 prints 5. Line 6 reads cart.price with a dot and prints 3. The list only changes with; the property itself is still there.
Now try it yourself. Choose what cart holds, and whether its price is hidden. For each choice, guess what the two lines print before you look at the result.
var price = 5;
var tax = 1;
var cart = { price: 3 };
with (cart) {
console.log(price);
console.log(tax);
}ran3, from cart1, from outsideprice is found on cart, so line 1's 5 is hidden. tax is not on cart, so it is found outside.
Turn off "cart has price: 3" and price comes from outside. Turn it back on and also hide it, and price comes from outside again, even though cart has it. tax is never hidden, so it follows only whether cart has it.
lengthpushmapvaluesincludeskeyscart, a variable declared outside
list is an array, [3, 4, 5]. Put each name in its group: found on list, or skipped by with and looked up outside.
Strict mode, modules and classes ban with
Strict mode leaves with out completely. In strict code, a with statement is a SyntaxError: the engine rejects the code while reading it, before any line runs. Strict code includes code after "use strict", every module, and every class body.
"use strict";
var cart = { price: 3 };
with (cart) {
console.log(price);
}Line 1 makes the whole script strict. Line 3 is a with statement, so the engine throws a SyntaxError while reading the script. Not even line 2 runs, and line 4 never prints.
A SyntaxError in the script you are running can't be caught by that same script. To see it as a value, build the code from text with new Function, which reads the text while your code runs.
try {
new Function('"use strict"; with ({}) {}');
} catch (error) {
console.log(error.name);
}Line 2 builds a function from text that starts with "use strict" and contains a with. Reading that text throws a SyntaxError. Lines 3 and 4 catch it and print its name, SyntaxError.
try {
new Function("class Cart { show() { with ({}) {} } }");
} catch (error) {
console.log(error.name);
}Line 2 builds a function from text with no "use strict". But the with sits inside a method of a class, and class bodies are always strict. So line 2 throws, and line 4 prints SyntaxError.
Modules are always strict too. The file below is loaded as a module, so the browser rejects it while reading it.
// app.js, loaded with <script type="module">
const cart = { price: 3 };
with (cart) {
console.log(price);
}Line 1 is a comment: this file is loaded with <script type="module">. Line 3 is a with statement, so the whole module fails with a SyntaxError and no line in it runs. That is why this box has no Run button.
Why ban it? The earlier sections show the reasons. Inside with, a name may be a property or a variable, and only the running code can tell which. Strict mode was designed so that every name can be worked out by reading the code. with can't fit that rule, so strict code doesn't allow it.
In sloppy code, with still works in every browser, because old pages depend on it. It is kept for them only. New code should never use it.
Replacing with in old code
You will meet with in old scripts, old template code and old libraries. To turn such a file into a module, or to add "use strict", every with must go. Here is a small function written the old way.
function render(order) {
with (order) {
return name + " costs " + price;
}
}
console.log(render({ name: "Tea", price: 3 }));Lines 1 to 5 make render. Line 2 starts a with block for order. Line 3 reads name and price, which are found on order, and returns a sentence. Line 6 prints Tea costs 3.
It works, until an order arrives without a name. Then line 3 quietly looks outside. In a browser there is even a global called name (window.name, usually an empty string), so the mistake gives a wrong sentence instead of an error.
The usual fix is destructuring: one line that copies chosen properties of an object into variables with the same names.
"use strict";
function render(order) {
const { name, price } = order;
return name + " costs " + price;
}
console.log(render({ name: "Tea", price: 3 }));Line 1 makes the script strict, to show this version is allowed there. Line 3 copies order.name into a variable name, and order.price into price. Line 4 is the same sentence as before. Line 6 prints Tea costs 3.
Now line 3 lists, in the code, every name that comes from order. A reader, an editor and the engine can all see it.
Old code also used with to avoid typing a long path again and again. A short variable name does the same job.
"use strict";
const shop = { cart: { first: { name: "Tea", price: 3 } } };
const item = shop.cart.first;
console.log(item.name + " costs " + item.price);Line 2 makes a shop with a deep path to one item. Line 3 saves that item in a short variable, item. Line 4 reads item.name and item.price and prints Tea costs 3. Every property read has a dot, so it can't be mixed up with a variable.
The table below lists common patterns. Read each row as: "old code like this can be written like that, and here is why the new version is clearer."
| Old code with with | Code without with | Why it is clearer |
|---|---|---|
with (order) { return name + price; } | const { name, price } = order; | The reader sees exactly which names come from order. |
with (shop.cart.first) { … } | const item = shop.cart.first; then item.name | Every property read has a dot, so it can't be confused with a variable. |
with (Math) { return max(a, b); } | Math.max(a, b) | The name max can't be taken over by a property you didn't expect. |
Common mix-ups
Most mix-ups about with come from thinking it copies the object, or that it works like a normal block of variables.
- "
with (cart)copies the properties ofcartinto variables." No. Nothing is copied. Every read and write asks the realcart, so a change tocartshows up at once. - "An assignment inside
with (cart)adds a property tocart." No. Ifcartdoesn't have the name, the write goes outside, and in sloppy mode it may make a new global. - "Only the object's own properties count." No. Inherited properties count too, such as
constructoron a plain object orpushon an array. - "
varinsidewithmakes a variable on the object." No. The variable is made outside. Only the= valuepart runs inside, and it may write to the object. - "
Symbol.unscopableshides a property everywhere." No. Onlywithreads it.list.valuesandcart.pricestill work with a dot. - "
withis removed from JavaScript." No. It still works in sloppy code in every browser. Strict code, modules and classes reject it.
The table below puts the three main terms side by side. Read each row as: "this does that, but it doesn't do this other thing."
| Term | What it does | What it doesn't do |
|---|---|---|
with (cart) | Makes the properties of cart usable as plain names inside the block. | It doesn't copy the properties. Every read and write goes to the real cart object. |
price = 4 inside with (cart) | Changes cart.price if cart has it. | If cart has no price, the write goes outside. It never adds a new property to cart. |
Symbol.unscopables | Lists property names that with must skip. | It doesn't hide the property anywhere else. list.values still works. |
Practice exercises
Every program below is a sloppy classic script, so with runs. For each plain name inside a with block, ask: does the object have it, its own or inherited? Is it skipped by Symbol.unscopables? Guess first, then run it.
A variable tax and a property cart.tax both exist. What does line 4 print?
var tax = 1;
var cart = { price: 3, tax: 2 };
with (cart) {
console.log(price + tax);
}var tax = 1;
var cart = { price: 3, tax: 2 };
console.log(cart.price + cart.tax);cart has its own tax, so inside with (cart) the name tax means cart.tax, which is 2. The variable tax, 1, is never reached. Line 4 prints 3 + 2, which is 5. The version shown writes cart.price + cart.tax, which prints 5 too and says where each value comes from.
word is a string, not an object. What does line 3 print?
var word = "tea";
with (word) {
console.log(length);
}var word = "tea";
console.log(word.length);with (word) wraps the string "tea" in a String object, and that object has a length of 3. So line 3 prints 3. The version shown reads word.length directly, which is clearer and prints 3 too.
The author expected count to end up on cart. What does line 6 print? Then rewrite the program without with so that every name is declared on purpose.
var cart = { price: 3 };
with (cart) {
price = 4;
count = 2;
}
console.log(typeof count, cart.count);var cart = { price: 3 };
cart.price = 4;
var count = 2;
console.log(typeof count, cart.count);Line 4 can't add count to cart. It finds no count anywhere, so sloppy mode makes a global. That is why typeof count is number and cart.count is undefined, and line 6 prints number undefined. The version shown removes with, writes cart.price with a dot, and declares count with var on purpose. It prints the same, but nothing happens by accident.
list is an array, and arrays have a keys method. What does line 4 print?
var keys = "mine";
var list = [3, 4, 5];
with (list) {
console.log(keys, length);
}var keys = "mine";
var list = [3, 4, 5];
console.log(keys, list.length);Arrays list keys in Symbol.unscopables, so with (list) skips it and finds the variable "mine". length isn't in the list, so it is found on list and is 3. Line 4 prints mine 3. The version shown writes list.length with a dot, so there is nothing to skip.
What does line 5 print? Then rewrite the program so the 4 goes into the variable size and tea.size stays 3.
var tea = { size: 3 };
with (tea) {
var size = 4;
}
console.log(tea.size, size);var tea = { size: 3 };
var size = 4;
console.log(tea.size, size);The original prints 4 undefined: the 4 lands in tea.size, and the variable size never gets a value. The version shown moves var size = 4 out of the block and drops with. Now the 4 goes into the variable, tea keeps 3, and it prints 3 4.
Rewrite render without with, so that it works with "use strict" on line 1. What does your program print?
function render(order) {
with (order) {
return name + " costs " + price;
}
}
console.log(render({ name: "Tea", price: 3 }));"use strict";
function render(order) {
const { name, price } = order;
return name + " costs " + price;
}
console.log(render({ name: "Tea", price: 3 }));Destructuring copies order.name and order.price into variables with the same names, so the return line stays the same. There is no with, so the file can be strict. It prints Tea costs 3, like the original.
Check your understanding
For each question, find the object in the brackets first. Then check each name against it, before you look outside. Each answer explains why it's right or wrong.
Question 1 of 7What does reading price from a cart that has no price print?
Read the code, then predictvar price = 5; var cart = { size: 3 }; with (cart) { console.log(price); }Choose an answer to see the explanation.
Question 2 of 7What does this program, which assigns price and size inside with, print?
Read the code, then predictvar cart = { price: 3 }; with (cart) { price = 4; size = 2; } console.log(cart.price, cart.size);Choose an answer to see the explanation.
Question 3 of 7What does reading at inside with (list) print?
Read the code, then predictvar at = "outer"; var list = [3, 4, 5]; with (list) { console.log(at); }Choose an answer to see the explanation.
Question 4 of 7What does
with (cart)put in front of the names outside, in the spec's words?Choose an answer to see the explanation.
Question 5 of 7Where does
var price = 4;insidewith (cart)store the 4 whencartalready hasprice?Choose an answer to see the explanation.
Question 6 of 7Why is
witha SyntaxError in strict mode?Choose an answer to see the explanation.
Question 7 of 7Which rewrite keeps the meaning of
with (order) { return name + " costs " + price; }and works in strict code?Choose an answer to see the explanation.
Key takeaways
with trades a little typing for names you can't understand by reading the code. Knowing its rules lets you read old code and remove it safely.
- Inside
with (obj), a plain name is looked for onobjfirst, including inherited properties, and then outside as usual. - The value in the brackets is turned into an object.
nullandundefinedthrow aTypeError. - The spec calls what
withmakes an Object Environment Record. Its names are the object's properties, and nothing is copied. - An assignment changes the object only if it already has the name. Otherwise it goes outside, and in sloppy mode may make a new global.
varinsidewithmakes the variable outside, but its= valuemay write to the object.Symbol.unscopableslists names thatwithskips. Arrays use it for newer methods such asvalues,keysandincludes.- Strict code, modules and classes make
withaSyntaxError. Replace it with destructuring or a short variable name.
Remember the one-liner.
Inside with (obj), every plain name asks obj first, so you can't tell what it means until the code runs.
Coming next: Annex B: web legacy features. It lists the other old features that browsers must keep so that old websites still work.