ECMAScript 6 (ES6) 是最新的 JavaScript 语言的标准化规范,它的目标是使 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
本文在 “NodeJS系列(2)- 在 NPM 项目里使用 ECMAScript 6 (ES6) 规范” 的 npmdemo 的基础上,介绍并演示 let、const、Symbol 等 ES6 语法和概念。
NodeJS ES6:https://nodejs.org/en/docs/es6
ECMA:https://www.ecma-international.org/publications-and-standards/standards/ecma-262/
1. 系统环境
操作系统:Windows 10 (x64)NVM:1.1.11
NodeJS: 14.21.3 LTS
NPM:6.14.18
工作目录:D:\workshop\nodejs\npmdemo
2. let 和 const
ES6(ES2015)增加了两个重要的 JavaScript 语法(或关键字): let 和 const。let 声明的变量只在 let 命令所在的代码块内有效。
const 声明一个只读的常量,一旦声明,常量的值就不能改变,所以必须初始化,否则会报错。
let 和 var 的区别:
(1) let 不能重复声明,var 可以重复声明;
(2) let 不能变量提升,var 可以变量提升;
示例1,创建 D:\workshop\nodejs\npmdemo\es6_1.js 文件,内容如下
var a = 1 // 全局变量 function f() { var a = 2 // 函数范围局部变量 { let a = 3 // 代码块范围局部变量 console.log("level 3: a = " + a) } console.log("level 2: a = " + a) } console.log("level 1: a = " + a) f();
运行
D:\workshop\nodejs\npmdemo> node es6_1
level 1: a = 1
level 3: a = 3
level 2: a = 2
示例2,创建 D:\workshop\nodejs\npmdemo\es6_2.js 文件,内容如下
let a = 1 //let a = 2 // SyntaxError: Identifier 'a' has already been declared console.log("a = " + a) var b = 3 var b = 4 console.log("b = " + b) //console.log("1: c = " + c) // ReferenceError: c is not defined let c = "value c" console.log("2: c = " + c) console.log("1: d = " + d) // undefined var d = "value d" console.log("2: d = " + d) var e = 5; if (true) { //console.log("1: e = " + e) // Cannot access 'e' before initialization const e = 6 console.log("2: e = " + e) } console.log("3: e = " + e)
注:c 是 let 型,无法变量提升,换种说法就是 let 型不能先使用再声明。
代码块会对 let 和 const 声明的变量从块的开始就形成一个封闭作用域。代码块内,在声明 let 和 const 变量之前使用它会报错。
运行
D:\workshop\nodejs\npmdemo> node es6_2
a = 1 b = 4 2: c = value c 1: d = undefined 2: d = value d 2: e = 6 3: e = 5
3. 解构赋值 (Deconstruction Assignment)
解构赋值是对赋值运算符的扩展,它是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。在代码书写上简洁且易读,语义清晰明了,利于从复杂对象中获取数据字段。
1) 数组模型的解构(Array)
在数组的解构中,解构的目标若为可遍历对象,皆可进行解构赋值。可遍历对象即实现 Iterator 接口的数据。
示例,创建 D:\workshop\nodejs\npmdemo\es6_3.js 文件,内容如下
// 基本类型 let [a1, a2, a3] = [1, 2, 3] console.log("a1 = " + a1 + ", a2 = " + a2 + ", a3 = " + a3) // 嵌套 let [b1, [[b2], b3]] = [4, [[5], 6]] console.log("b1 = " + b1 + ", b2 = " + b2 + ", b3 = " + b3) // 忽略 let [c1, , c3] = [7, 8, 9] console.log("c1 = " + c1 + ", c3 = " + c3) // 不完全解构 let [d1 = 1, d2] = []; console.log("d1 = " + d1 + ", d2 = " + d2) // 剩余运算符 let [e1, ...e2] = [1, 2, 3] console.log("e1 = " + e1 + ", e2 = " + e2) // 字符串 let [s1, s2, s3, s4] = 'test' console.log("s1 = " + s1 + ", s2 = " + s2 + ", s3 = " + s3 + ", s4 = " + s4) // 解构默认值,当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。 let [x = 11] = [undefined] console.log("x = " + x) let [x1 = 3, x2 = x1] = [] console.log("x1 = " + x1 + ", x2 = " + x2) let [y1 = 5, y2 = y1] = [1] console.log("y1 = " + y1 + ", y2 = " + y2) let [z1= 7, z2 = z1] = [1, 2] console.log("z1 = " + z1 + ", z2 = " + z2)
运行
D:\workshop\nodejs\npmdemo> node es6_3
a1 = 1, a2 = 2, a3 = 3 b1 = 4, b2 = 5, b3 = 6 c1 = 7, c3 = 9 d1 = 1, d2 = undefined e1 = 1, e2 = 2,3 s1 = t, s2 = e, s3 = s, s4 = t x = 11 x1 = 3, x2 = 3 y1 = 1, y2 = 1 z1 = 1, z2 = 2
2) 对象模型的解构(Object)
数组的元素是按位置排序的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
如果变量名与属性名不一致,可以写成这样:
var {foo:baz} = {foo:"aaa",bar:"bbb"}
这里 foo 是属性名,baz 是变量名,即从对象中取出 foo 属性的值赋值给 baz 变量。
对象的解构赋值是内部机制,是先找到同名属性,然后再赋给对应的变量,真正被赋值的是后者,而不是前者。
示例,创建 D:\workshop\nodejs\npmdemo\es6_4.js 文件,内容如下
// 基本类型 let { a1, a2 } = { a1: 'aaa', a2: 'bbb' } console.log("a1 = " + a1 + ", a2 = " + a2) let { b1:bx } = { b1 : 'ccc', b2: 'ddd' } console.log("bx = " + bx) // 嵌套 let {data: [c1, { c2 }] } = {data: ['Hello', {c2: 'World'}] } console.log("c1 = " + c1 + ", c2 = " + c2) // 忽略 let {data: [d1, { }] } = {data: ['Hello', {d2: 'World'}] } console.log("d1 = " + d1) // 不完全解构 let {data: [{ e2 }, e1 ] } = {data: [{e2: 'world'}] } console.log("e1 = " + e1 + ", e2 = " + e2) // 剩余运算符 let {f1, f2, ...f3} = {f1: 1, f2: 2, f3: 3, f4: 4} console.log("f1 = " + f1 + ", f2 = " + f2 + ", f3 = " + f3) // 解构默认值 let {x = 11} = {undefined} console.log("x = " + x) let {x1 = 3, x2 = x1} = {} console.log("x1 = " + x1 + ", x2 = " + x2) let {y1 = 5, y2 = y1} = { y1: 1} console.log("y1 = " + y1 + ", y2 = " + y2) let {z1:aa = 105, z2:bb = 25, z3:cc = 15} = {z1: 3, z2: 4} console.log("aa = " + aa + ", bb = " + bb + ", cc = " + cc)
运行
D:\workshop\nodejs\npmdemo> node es6_4
a1 = aaa, a2 = bbb bx = ccc c1 = Hello, c2 = World d1 = Hello e1 = undefined, e2 = world f1 = 1, f2 = 2, f3 = [object Object] x = 11 x1 = 3, x2 = 3 y1 = 1, y2 = 1 aa = 3, bb = 4, cc = 15
4. Symbol
ES6 数据类型除了 Number 、 String 、 Boolean 、 Object、 null 和 undefined ,还新增了 Symbol 。Symbol 表示独一无二的值,最大的用法是用来定义对象的唯一属性名。Symbol 不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
1) 作为属性名
由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。
示例,创建 D:\workshop\nodejs\npmdemo\es6_5.js 文件,内容如下
let symbol_1 = Symbol("key") console.log(symbol_1) console.log("typeof(symbol_1): " + typeof(symbol_1)) let symbol_2 = Symbol("key") console.log(symbol_2) console.log("typeof(symbol_2): " + typeof(symbol_2)) // 相同参数 Symbol() 返回的值不相等 console.log("symbol_1 === symbol_2: " + (symbol_1 === symbol_2)) // 作为对象属性名 (写法1) let sy_1 = Symbol("key_1") let symbolObject_1 = {} symbolObject_1[sy_1] = "symbol object 1" console.log(symbolObject_1) console.log("symbolObject_1[sy_1]: " + symbolObject_1[sy_1]) console.log("symbolObject_1.sy_1: " + symbolObject_1.sy_1) // 作为对象属性名 (写法2) let sy_2 = Symbol("key_2") let symbolObject_2 = { [sy_2]: "symbol object 2" }; console.log(symbolObject_2) console.log("symbolObject_2[sy_2]: " + symbolObject_2[sy_2]) console.log("symbolObject_2.sy_2: " + symbolObject_2.sy_2) // 读取对象的 Symbol for (let i in symbolObject_1) { console.log(i); } Object.keys(symbolObject_1); Object.getOwnPropertySymbols(symbolObject_1); Reflect.ownKeys(symbolObject_1);
运行
D:\workshop\nodejs\npmdemo> node es6_5
Symbol(key) typeof(symbol_1): symbol Symbol(key) typeof(symbol_2): symbol symbol_1 === symbol_2: false { [Symbol(key_1)]: 'symbol object 1' } symbolObject_1[sy_1]: symbol object 1 symbolObject_1.sy_1: undefined { [Symbol(key_2)]: 'symbol object 2' } symbolObject_2[sy_2]: symbol object 2 symbolObject_2.sy_2: undefined [] [ Symbol(key_1) ] [ Symbol(key_1) ]
Symbol 作为对象属性名时不能用 . 运算符,要用方括号。因为 . 运算符后面是字符串,所以取到的是字符串 sy_1 和 sy_2 属性,而不是 Symbol 值 sy_1 和 sy_2 属性。
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
2) 定义常量
使用 Symbol 定义常量,这样就可以保证这一组常量的值都不相等。
示例,创建 D:\workshop\nodejs\npmdemo\es6_6.js 文件,内容如下
const COLOR_RED = Symbol("red") const COLOR_GREEN = Symbol("green") const COLOR_BLUE = Symbol("blue") const COLOR_YELLOW = Symbol("blue") // 和上一个 "blue" 参数 Symbol() 返回的值不相等 function getColor(color) { switch (color) { case COLOR_RED: return "COLOR_RED" case COLOR_GREEN: return "COLOR_GREEN" case COLOR_BLUE: return "COLOR_BLUE" case COLOR_YELLOW : return "COLOR_YELLOW" default: return "UNKNOWN" } } console.log(getColor(COLOR_RED)) console.log(getColor(COLOR_GREEN)) console.log(getColor(COLOR_BLUE)) console.log(getColor(COLOR_YELLOW)) console.log(getColor('gray'))
运行
D:\workshop\nodejs\npmdemo> node es6_6
COLOR_RED COLOR_GREEN COLOR_BLUE COLOR_YELLOW UNKNOWN
3) Symbol.for() 和 Symbol.keyFor()
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
let blue = Symbol("blue")
let blue_1 = Symbol.for("blue");
console.log(blue === blue_1); // false
let blue_2 = Symbol.for("blue");
console.log(blue_1 === blue_2); // true
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let blue = Symbol.for("blue");
Symbol.keyFor(blue); // "blue"