NodeJS系列(3)- ECMAScript 6 (ES6) 语法(一)

发布时间 2023-06-20 20:26:23作者: 垄山小站

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"