• 三、ECMAScript 6 语法简介(1)


    本章概要

    • 块作用域构造
      • let 声明
      • const 声明
      • 全局块作用域绑定
    • 模板字面量
      • 多行字符串
      • 字符串占位符
    • 默认参数
    • rest 参数
    • 展开运算符

    3.1 块作用域构造

    块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域存在于:

    • 函数内部
    • 块中(字符“{”,“}”之前的区域)

    3.1.1 let 声明

    在函数作用域或全局作用域中通过关键字 var 声明变量,无论在哪里声明,都会被当成在当前作用域顶部声明的变量,这就是 JavaScript 的变量提升机制。

    //函数内部
    function changeState(flag){
        if(flag){
            var color = "red";
        }else{
            //此处可以访问变量 color ,其值为 undefined
            console.log(color);
            return null;
        }
    }
    changeState(false);
    
    //块中
    {
        var a = 1;
    }
    //此处可以访问变量 a,输出 a = 1
    console.log("a = " + a);
    
    //for 循环中
    for(var i = 1;i<10;i++){
    }
    //此处不能访问变量 i,输出 1 = 10
    console.log("i = " + i);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这种变量提升机制在开发时会造成很多的困扰,ES6 引入了 let 声明,用法与 var 相同,不过用 let 声明的变量不会被提升,可以将变量的作用域限制在当前代码块中。
    将上述代码中的变量用 let 进行声明。如下:

    //函数内部
    function changeState(flag){
        if(flag){
            let color = "red";
        }else{
            //此处不可以访问变量 color ,报错:color is not defined
            console.log(color);
            return null;
        }
    }
    changeState(false);
    
    //块中
    {
        let a = 1;
    }
    //此处不可以访问变量 a ,报错:a is not defined
    console.log("a = " + a);
    
    //for 循环中
    for(let i = 1;i<10;i++){
    }
    //此处不可以访问变量 i ,报错:i is not defined
    console.log("i = " + i);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    使用 let 声明变量,还可以防止变量的重复声明。例如,在某个作用域下已经存在某个标识符,此时再使用 let 关键字声明它,就会抛出错误。如下

    var index = 0;
    var index = 10;
    // 报错:Identifier 'index' has already been declared
    let index = 100;
    
    • 1
    • 2
    • 3
    • 4

    在同一作用域下,不能使用 let 重复声明已经存在的标识符,但如果在不同的作用域下则是可以的。

    3.1.2 const 声明

    ES6 还提供了 const 关键字,用于声明常量。每个通过 const 关键字声明的常量必须在声明的同事进行初始化。如下:

    const m = 10;
    //错误
    const n;
    n = 20;
    
    • 1
    • 2
    • 3
    • 4

    与 let 声明类似,在同一作用域下用 const 声明已经存在的标识符也会导致语法错误,无论该标识符是使用 var ,还是 let 声明的。
    如果使用 const 声明对象,对象本身的绑定不能修改,但对象的属性和值是可以修改的。如下:

    const person = { 
        name:"zhangsan"
    };
    person.name = "lisi";
    person.age = 20;
    //错误,报错:Assignment to constant variable
    person = {
        name:"wangwu"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.1.3 全局块作用域绑定

    在全局作用域中使用 var 声明的变量或对象,将作为浏览器环境中的 Windows 对象的属性,这意味着使用 var 很可能会无意中覆盖一个已经存在的全局属性。如下:

    var greeting = "Welcome";
    // 打印 Welcome
    console.log(window.greeting);
    // 打印 function Screen(){[native code]}
    console.log(window.Screen);
    var Screen = "aaa";
    // 打印 aaa
    console.log(window.Screen);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    greeting 被定义为一个全局变量,并立即成为 window 对象的属性。定义的全局变量 Screen,则覆盖了 window 对象中原有的 Screen 属性。
    如果在全局作用域下使用 let 或 const,则会在全局作用域下创建一个新的绑定,该绑定不会成为 window 对象的属性。如下:

    let greeting = "Welcome";
    // undefined
    console.log(window.greeting);
    const Screen = "aaa";
    // 打印 false
    console.log(window.Screen == Screen);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    综上所述,如果不想为全局对象 windows 创建属性,或者为了避免覆盖 windows 对象的属性,则应该使用 let 和 const 声明变量和常量。

    3.2 模板字面量

    ES6 引入了 模板字面量(Template Literals),对字符串的操作进行了增强:

    • 多行字符串:真正的多行字符串
    • 字符串占位符:可以将变量或 JavaScript 表达式嵌入占位符中并将其作为字符串的一部分输出到结果中

    3.2.1 多行字符串

    模板字面量的基础语法就是反文号“`”替换字符串的单、双引号。如下:

    let message = `hello`;
    
    • 1

    这句代码使用模板字面量语法创建了一个字符串,并赋值给 message 变量,这时变量的值与一个普通的字符串并无差异。
    如果想要在字符串中使用反引号,那么用反斜杠“\”将它转义即可,如下:

    let message = `hello \`world\``;
    
    • 1

    在模板字面量中,不需要转义单、双引号。
    在 ES5 中,如果一个字符串字面量要分为多行书写,那么可以采用两种方式来实现:在一行结尾的时候添加反斜杠“\” 表示承接下一行的代码,或者使用“+”来拼接字符串,如下:

    let a = "hello \
    world";
    
    let b = "hello"
    + "world";
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这两种实现方式,前者是利用 JavaScript 的语法 Bug 来实现,后者是利用字符串的拼接操作来实现。当把字符串 a 和 b 打印时,这两个字符串均未跨行显示,前者使用反斜杠只是代表行的延续,并未真正插入新的一行。如果要输出新的一行,需要手动加入换行符。如下:

    let a = "hello \n \
    world";
    
    let b = "hello"
    +"\n"
    + "world";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在 ES6 中,使用模板字面量语法,可以方便地实现多行字符串的创建。如果需要在字符串中添加新的一行,只需要在代码中直接换行即可。如下:

    let m = `ni
     hao`;
    
    • 1
    • 2

    注意:在反引号中的所有空白字符(包括但不限于空格、换行、制表符)都属于字符串的一部分。

    3.2.2 字符串占位符

    在一个模板字面量中,可以将 JavaScript 变量或任何合法的 JavaScript 表达式嵌入占位符并将其作为字符串的一部分输出到结果中。占位符由一个左侧的“${”和右侧的“}”符号组成,中间可以包含变量或JavaScript表达式。如下:

    let name = "zhangsan"
    let message = `ni hao,${name}`;
    console.log(message);
    
    let a = 5;
    let b = 10;
    let num = `jie guo : ${a * b}`;
    console.log(num)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    模板字面量本身也是 JavaScript 表达式,因此也可以在一个模板字面量中嵌入另一个模板字面量。如下:

    let name = "zhangsan"
    let message = `ni hao , ${ `wo shi ${name}`}`;
    console.log(message)
    
    • 1
    • 2
    • 3

    3.3 默认参数

    在 ES5 中,没有提供直接在函数的参数列表中指定参数默认值的语法,要想为函数参数指定默认值,只能通过下面方式实现:

    function makeRedirect(url,timeout){
        url = url || "/home";
        timeout = timeout || 2000;
        // 函数其余部分
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这个示例中,url 和 timeout 是可选参数,如果不传入对应的参数值,他们也将被赋予一个默认值。但是这种模式设置函数的默认值有一个缺陷,如果形参 timeout 传入值 0 ,及时这个值是合法的,也会被视为一个假值,并最终将 timeout 设置为 2000。
    在这种情况下,更安全的做法是通过 typeof 检查参数类型,如下:

    function makeRedirect(url,timeout){
        url = (typeof url != "undefined") ? url : "/home";
        timeout = (typeof timeout != "undefined") ? timeout : 2000;
        // 函数其余部分
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    尽管这种方式更加安全,但需要额外的代码来执行这种非常基础的操作。在 ES6 中,简化了为形参提供默认值的过程,可以直接在参数列表中为形参指定默认值。如下:

    function makeRedirect(url = "/home",timeout = 2000){
        // 函数其余部分
    }
    
    • 1
    • 2
    • 3

    如果调用 makeRedirect() ,则使用参数 url 和 timeout 的默认值;如果调用 makeRedirect(“/login”) ,则使用参数timeout的默认值;如果调用 makeRedirect() 时传入两个参数,则不使用默认值。
    此外,在 ES6 中声明函数时,可以为任意参数指定默认值,在已指定默认值的参数后还可以继续声明无默认值的参数,如下:

    function makeRedirect(url = "/home",timeout = 2000,callback){
        // 函数其余部分
    }
    
    • 1
    • 2
    • 3

    在这种情况下,只有在没有为 url 和 timeout 传值,或者主动为它们传入 undefined 时才会使用它们的默认值,如下:

    //使用 url 和 timeout 的默认值
    makeRedirect();
    makeRedirect(undefined,undefined,function(){});
    
    //使用 timeout 的默认值
    makeRedirect("/login");
    
    //不适用 timeout 的默认值
    makeRedirect("/login",null,function(){});
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    为一个具有默认值的参数传值 null 是合法的,所以,最后一次调用 makeRedirect() 时,将不会使用 timeout 的默认值,其值最终为 null。

    3.4 rest 参数

    JavaScript 函数一个特别的地方是,无论在函数中声明了多少形参,都可以传入任意数量的参数,在函数内部可以通过 arguments 对象接收传入的参数,如下:

    function calculate(op) {
        if (op === "+") {
            let result = 0;
            for (let i = 1; i < arguments.length; i++) {
                result += arguments[i];
            }
            return result;
        } else if (op === "*") {
            let result = 1;
            for (let i = 1; i < arguments.length; i++) {
                result *= arguments[i];
            }
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    calculate() 函数根据传入的操作符的不同而执行不同的计算,计算的数据可以是任意多个,因此在函数声明时无法明确地定义要传入的所有参数。但是,可以通过 arguments 对象解决任意数量参数传入的问题。
    不过这种方式也有一些不足的地方:首先,调用者需要知道该函数可以接受任意数量的参数,单从函数声明的参数列表是看不出来的;其次,因为第一个参数是命名参数且已被使用,因此遍历 arguments 对象时,索引要从1开始而不是0。

    ES6 引入了 rest 参数,在函数的命名参数前添加3个点,就表明这是一个rest参数,用于获取函数的多余参数。rest参数是一个数组,包含自它之后传入的所有参数,通过这个数组名就可以逐一访问里面的参数。

    使用rest 参数重写上例中的 calculate() 函数,如下:

    function calculate(op, ...data){
    	if(op === "+"){
    		let result = 0;
    		for(let i = 0; i < data.length; i++){
    			result += data[i];
    		}
    		return result;
    	} else if(op === "*"){
    		let result = 1;
    		for(let i = 0; i < data.length; i++){
    			result *= data[i];
    		}	
    		return result;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    rest 参数包含的是 op 之后传入的所有参数(arguments 对象包含的是所有传入的参数,包括 op)。可以看到,使用 rest 参数,函数可以处理的参数数量一目了然,代码则更加清晰。
    需要注意的是,每个函数最多只能声明一个 rest 参数,并且它只能是最后一个参数。

    3.5 展开运算符

    展开预算符在语法上与 rest 参数相似,也是3个点,它可以将一个数组转换为各个独立的参数,也可用于取出对象的所有可遍历属性,而 rest 参数是指定多个独立的参数,并通过整合后的数组来访问,如下:

    function sum(a, b, c){
    	return a + b + c;
    }
    
    let arr = [1, 2, 3];
    sum(...arr);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上述代码使用展开运算符提取数组 arr 中的各个值并传入 sum() 函数中。
    展开运算符可以用来复制数组,如下:

    let arr1 = [1, 2, 3];
    // arr2与arr1是同一个数组对象
    let arr2 = arr1;
    // arr3与arr1不是同一个数组对象
    let arr3  = [...arr1];   
    
    arr1[0] = 4;
    // arr2 中的元素同事被改变,输出4
    console.log(arr2[0]);
    // 输出1
    console.log(arr3[0]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    从上述代码可以看到,在需要复制一个新的数组对象时,可以使用展开运算符便捷地实现。
    展开运算符也可以用来合并数组,如下:

    let arr1 = ['a'];
    let arr2 = ['b', 'c'];
    let arr3 = ['d', 'e'];
    console.log([...arr1, ...arr2, ...arr3]); //[ 'a', 'b', 'c', 'd', 'e' ]
    
    • 1
    • 2
    • 3
    • 4

    展开运算符还可以用于取出对象的所有可遍历属性,复制到当前对象中,如下:

    let book = {
    	tille: "你好",
    	price: 98
    }
    	
    let bookDetail = {...book, desc: "world"}
    //{ tille: '你好', price: 98, desc: 'world' }
    console.log(bookDetail); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    Android 13.0 修改系统签名文件类型test-keys为release-keys
    Hash,位图,布隆过滤器
    【SSM框架】MyBatis的逆向工程
    Linux安装jrockit-jdk1.6.0_29-R28.2.0-4.1.0-linux-x64
    开发跨端微信小程序框架选型指南
    两年经验前端带你重学前端框架必会的ajax+node.js+webpack+git等技术 Day3
    Vue组件和Vue脚手架
    信创办公–基于WPS的EXCEL最佳实践系列 (单元格与行列)
    C# 调用 Rust 来删除文件夹 ( 包含大量软链接 和 无效链接)
    RabbitMQ学习笔记之Work Queues
  • 原文地址:https://blog.csdn.net/GXL_1012/article/details/126977915