Preface
let x = x => x + 1; seems to have become the opening move of ES6, just like return arr.map(fx).filter(isValid).reduce(accumulator) as the glossy black of ES5. If you know some ES6, starting with var will make people dislike it.
However, the least painful features in ES6 should be let and const, if you're already used to the little temper of var.
I. Why Do We Need let and const?
Because var has some little tempers, they think are "bugs" triggered by function scope, such as this small strange problem:
var x = 4;
(function() {
console.log(x); // undefined
console.log(x + 1); // NaN
var x = 1;
// 因为 var 的提升特性,以上代码等价于
// var x;
// console.log(x);
// console.log(x + 1);
// x = 1;
})();
This is called Hoisting, forcibly blamed:
Who asked you to hoist, creating a bunch of strange undefined, NaN, it's all your fault
Actually, if you don't deliberately save variable names, it's hard to encounter this problem.
And another problem all JS players have encountered, as follows:
(function() {
var arr = [1, 2, 3];
for (var i = 0; i < arr.length; i++) {
setTimeout(function() {
// 因为 50ms 后外部循环结束了,i === arr.length = 3
console.log(arr[i]); // undefined x 3
}, 50);
// 修复
// (function(i) {
// setTimeout(function() {
// console.log(arr[i]); // 1 2 3
// });
// })(i);
}
})();
The closure holds access to the outer scope, not the value of the variable. Accessing i after 50ms, of course you get 3. This is the characteristic of closures, closures in other functional languages are also like this. They also say:
Who asked you to be unreasonable, why didn't you store the state when the loop body executed, causing me to get a bunch of undefined
JS is powerless to defend itself, thinking to itself that it indeed has some things done wrong:
-
Variables declared with
varin global scope become properties of the global object -
No block scope, everyone has worked hard using IIFE for 20 years (something that should be handled by a pair of curly braces)
So there came let and const.
II. Characteristics of let
1. Variables Declared with let Have Block Scope
Yes, after 20 years, JS also has block scope.
for (let i = 0; i < 3; i++) {
//...
}
console.log(i); // Uncaught ReferenceError: i is not defined
Then there's a problem, what's the simplest way to create a "block"? If it's still IIFE, what's the difference? See the answer later, because it involves JS syntax details, won't expand here.
2. let Also Has Hoisting Characteristics
Directly try replacing the key var in the first example code with let:
var y = 4;
(function() {
console.log(y); // Uncaught ReferenceError: y is not defined
console.log(y + 1);
let y = 1;
// let 也有提升特性,以上代码不完全等价于
// let y;
// console.log(y); // undefined
// console.log(y + 1); // NaN
// y = 1;
})();
This time it errors directly, the outer y = 4 is shadowed, indicating let y indeed hoisted a block-level variable y. The error is because variable definition is only loaded when executing to the let line (6th characteristic).
Because of TDZ (see later), the uncommented part is not completely equivalent to the commented code (above errors, below doesn't error).
3. Exceptions Are Thrown on the Current Line
let helps locate errors. Except for NaN, exceptions are thrown on the current line, such as undefined. NaN is excluded because:
let a;
console.log(a + 1); // NaN === undefined + 1
JS's weak typing mechanism doesn't consider NaN as an exception.
Other exceptions are thrown on the current line, comparison visible:
// let 当前行报错
(() => {
x++; // Uncaught ReferenceError: x is not defined
[1, 2, 3][x][0];
let x = 1;
})();
// var 当前行不报错
(() => {
x++;
[1, 2, 3][x][0]; // Uncaught TypeError: Cannot read property '0' of undefined
var x = 1;
})();
Clearly went off track when x++, only errors when triggering other errors. let successfully avoids this situation.
P.S. The (() => {/* 新版 IIFE */})(); above is just to isolate effects for easier testing. let + class + ES6 modules are for eliminating IIFE. Reasonable ES6 code shouldn't have IIFE used only for isolating a block scope.
4. Global Variables Declared with let Are Not Properties of Global Object
let b = 2;
console.log(window.b); // undefined
This doesn't mean variable namespaces are no longer needed. script tags don't have scope isolation effect. Fewer custom properties on window, global variable problems still exist. As for cooperating with ES6 module scope, this seems very far away.
P.S. Although webpack and other build tools support ES6 modules, only when browsers support this module scope can the global variable problem be solved. By then perhaps really no need for namespaces.
P.S. V8 claimed 100% ES6 support several months ago, but ES6 modules are still not supported, probably don't plan to support either, because ES6 module mechanism doesn't suit browser environment. Will explain reasons in detail later.
5. Loop Variables Declared with let Are Re-bound Each Iteration
That is to say, closures in loop body retain copies of loop variable values, as follows:
(function() {
var arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
// 闭包保留了循环变量的值的副本
setTimeout(function() {
console.log(arr[i]); // 1 2 3
}, 50);
}
})();
Everyone hopes to save the state when loop body executes, so JS compromises according to everyone's wishes, but only took a small step back, only did some hack on loop variables. The big principle of closures cannot be乱了 (holding access rights to outer scope).
Note: Loop variable means applicable to existing three loop methods for-of, for-in, and traditional C-like loops separated by semicolons.
6. Variable Definition Is Only Loaded When Executing to let Line
Using the variable before this errors ReferenceError. During this time the variable is in scope but not yet loaded, located in TDZ (Temporal Dead Zone).
let is intentional, this way it's both compatible with Hoisting and can still error.
7. let Variable Scope Is Valid for Entire Block, Not Valid from Declaration to Block End
Different from C language, can be considered block-level Hoisting. There's a very apt description:
The scope of var declaration in JavaScript is like the Paint Bucket tool in Photoshop, diffusing forward and backward from the declaration point until touching function boundary and stopping
let's Hoisting method isn't much different from var (only boundary becomes block boundary), both are this kind of bidirectional diffusion.
8. Redefining let Variable Errors SyntaxError
Errors during lexical parsing phase, not runtime error, and SyntaxError cannot be caught by try-catch.
var's "fault tolerance" is very strong, as follows:
var x = 2;
var x;
var x = x++;
Well, x is still 2, second line is ignored, third line var is ignored, assignment is executed, no matter how you write it doesn't error.
let x = 2;
let x; // Uncaught SyntaxError: Identifier 'x' has already been declared
In this case, interview questions will be much simpler in the future:)
9. class Declaration Is Same as let, Same-Name Classes Error SyntaxError
class was born with cooperation terms signed with let, following let-style declaration rules:
class A {}
class A {} // Uncaught SyntaxError: Identifier 'A' has already been declared
III. Characteristics of const
const is similar to let, but const variables are read-only.
Characteristics:
-
Modifying const variable should error SyntaxError, but Chrome47 operation is invalid but doesn't error
-
const declaration must assign value simultaneously, otherwise errors, but Chrome47 doesn't error, value is undefined
Note: These two constraints already exist in Chrome51, now will error (don't know which update, saying these ES6 notes are from January 2016, accidentally witnessed the binding force of specifications).
Examples as follows:
// 尝试修改 const 变量
const PI = Math.PI;
PI = 3; // Uncaught TypeError: Assignment to constant variable.
PI++; // 同上
console.log(PI); // 注释掉上两句会输出 3.141592653589793
// 尝试声明时不赋值
const UNDEF; // 词法检查阶段报错
// Uncaught SyntaxError: Missing initializer in const declaration
console.log(UNDEF); // 前面报错了,到不了这
IV. Simplest Way to Create Block
ES6 has block scope, meaning IIFE scope isolation will become history. So what's the replacement?
{
let tip = '这是我的领地';
}; //!!! 千万千万注意这个不起眼的分号
console.log(tip); // Uncaught ReferenceError: tip is not defined
{}; is much cleaner than IIFE. Wait, what's that semicolon at the end, is it useful? Java code blocks obviously don't need semicolons, right?
Note: This inconspicuous semicolon is indispensable. Removing it will error, because {} will be parsed as object literal, triggering syntax error. Java indeed doesn't need this semicolon, because there's no object literal ambiguity, lexical parser won't be confused.
Curly Braces in JS
Actually there are 4 types of curly braces in JS, respectively:
// 1.对象字面量
{
a: 1,
b: 2
}
// 2.复合语句(一组代码,单语句可以省略花括号)
if (true) {
console.log(1);
console.log(2);
}
// 3.作为语法结构(花括号是语法结构,不能省略)
try {}
catch (ex) {}
// 4.label(也是代码分组,用来支持 break、continue 的跨层跳转)
label: {}
Block and object literal have ambiguity, both are
{
//...
}
JS will parse this thing as expression, therefore checks object literal syntax, if wrong then errors. If telling JS this thing should be parsed as block, ambiguity naturally disappears. Two methods:
// 1.分号强制语句(和逗号强制表达式一样)
{/* 我是一个块语句 */};
// 2.复合语句
{{/* 我是一个复合语句 */}}
So another slightly troublesome way to create block is:
// 一种可爱的方式
{{
let tip = '这是我的领地';
}}
console.log(tip); // Uncaught ReferenceError: tip is not defined
Of course intentionally using label to create block is also okay, anyway label is generally useless, as follows:
block: {
let tip = '这是我的领地';
}
console.log(tip); // Uncaught ReferenceError: tip is not defined
Counting gives 3 methods, maybe more waiting to be discovered. For more information about JS syntax please see "JavaScript Language Essence and Programming Practice".
V. Summary
let is a more perfect var.
Using let... as ES6 opening move is also correct, but things related to let are no less than var. Using let well is also not easy like using var well.
In addition, although let is a replacement for var, it doesn't mean you can do a find-replace on old code. let has more restrictions, "fault tolerance" naturally isn't as good as var. But anyway, what should pass will pass, var will eventually disappear. So, try to accept let.
References
-
"JavaScript Language Essence and Programming Practice": Very good book, if you have patience to finish reading
-
"ES6 in Depth": Free e-book provided by InfoQ Chinese station
No comments yet. Be the first to share your thoughts.