三、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);

这种变量提升机制在开发时会造成很多的困扰,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);

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

var index = 0;
var index = 10;
// 报错:Identifier 'index' has already been declared
let index = 100;

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

3.1.2 const 声明

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

const m = 10;
//错误
const n;
n = 20;

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

const person = { 
    name:"zhangsan"
};
person.name = "lisi";
person.age = 20;
//错误,报错:Assignment to constant variable
person = {
    name:"wangwu"
}

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);

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);

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

3.2 模板字面量

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

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

3.2.1 多行字符串

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

let message = `hello`;

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

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

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

let a = "hello \
world";

let b = "hello"
+ "world";

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

let a = "hello \n \
world";

let b = "hello"
+"\n"
+ "world";

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

let m = `ni
 hao`;

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

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)

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

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

3.3 默认参数

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

function makeRedirect(url,timeout){
    url = url || "/home";
    timeout = timeout || 2000;
    // 函数其余部分
}

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

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

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

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

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

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

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

//使用 url 和 timeout 的默认值
makeRedirect();
makeRedirect(undefined,undefined,function(){});

//使用 timeout 的默认值
makeRedirect("/login");

//不适用 timeout 的默认值
makeRedirect("/login",null,function(){});

为一个具有默认值的参数传值 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;
    }
}

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;
	}
}

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);

上述代码使用展开运算符提取数组 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]);

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

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

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

let book = {
	tille: "你好",
	price: 98
}
	
let bookDetail = {...book, desc: "world"}
//{ tille: '你好', price: 98, desc: 'world' }
console.log(bookDetail); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小熊猫呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值