一、块级作用域
ES6块级作用域
在ES5中只有全局作用域和函数作用域(如,我们必须用函数将变量包在里面来限制作用域),这样带来很多不合理的场景:
场景1: 内层变量覆盖外层变量
var tmp = new Date();
function f() {
console.log(tmp); //外层
if (false) {
var tmp = "hello world";//内层
}
}
f(); //undefined
if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。
场景2:循环计数变量泄露,成为全局变量
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
ES6提供了let 和 const 来代替var声明变量,增加了块级作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
同时允许块级作用域的任意嵌套。
{{{{
{let insane = 'Hello World'}
console.log(insane); // 报错
}}}};
块级作用域与函数声明
ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
// 情况一
if (true) {
function f() {}
}
// 情况二
try {
function f() {}
} catch(e) {
// ...
}
上面两种声明,根据ES5的规定都是非法的,但浏览器没有遵守这个规定,实际都能运行不会报错。
ES6 明确允许在块级作用域中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似let,在块级作用域之外不可引用。
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
上面代码在ES5中运行,得到“I am inside”,而在ES6 浏览器中运行,是会报错的。
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
原因在于,如果改变了块级作用域内声明的函数处理规则,显然会对老代码产生很大的影响。为了减轻因此产生的不兼容问题,ES6 在附录 B中规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式:
--允许在块级作用域内声明函数。
--函数声明类似于 var,即会提升到全局作用域或函数作用域的头部。
--同时,函数声明还会提升到所在的块级作用域的头部。
注意,上面三条规则只对ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let 处理。
根据这三条规则,浏览器的ES6 环境中,块级作用域内声明的函数,行为类似于var 声明的变量。上面的例子实际运行代码如下:
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
考虑环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
总而言之,块级作用域给我们带来了更多的好处:
1、不再需要立即执行的函数表达式
在ES6 中,我们使用大括号({}),用let 或者const 来代替var 来达到在ES5 中,需要构造一个立即执行的函数表达式去保证我们不污染全局作用域的效果。
2、循环体中的闭包不再有问题
在ES5 中, 如果循环体内有产生一个闭包,访问闭包外的变量,会产生问题。在ES6中,可以使用 let 来避免。
3、防止重复声明变量
ES6 不允许在同一个作用域内用let 或 const 重复声明同名变量。这对于防止在不同的js 库中存在重复声明的函数表达式十分有帮助。
二、模板字符串
传统的 JavaScript 语言,输出模板通常是这样写:
let basket = {count:12, onSale: 99}
$('#result').append(
'There are <b>' + basket.count + '</b> ' +
'items in your basket, ' +
'<em>' + basket.onSale +
'</em> are on sale!'
);
模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
模板字符串中嵌入变量和函数,需要将变量名写在${}之中。
let basket = {count:12, onSale: 99}
function makeTotal(count,onSale){
return count*onSale;
}
$('#result').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
total: ${makeTotal(basket.count, basket.onSale)}
`);
三、变量的解构赋值
1、什么称之为解构,有何用
在ES5及之前,从对象或数组中获取信息、并将特定数据存入本地变量,需要编写许多并且相似的代码。例如:
var expense = {
type: "es6",
amount:"45"
};
var type = expense.type;
var amount = expense.amount;
console.log(type,amount);// es6 45
此代码提取了expense对象的type与amount值,并将其存在同名的本地变量上。虽然 这段代码看起来简单,但想象一下若有大量变量需要处理,你就必须逐个为其赋值;并且若有一个嵌套的数据结构需要遍历以寻找信息,你可能会为了一点数据而挖掘整个结构。
所以在ES6 中允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
当把数据结构分解为更小的部分时,从中提取你要的数据会变得容易许多。
2、对象
上个例子中如果采用对象解构的方法,就很容易获取expense对象的type与amount值。
const { type,amount } = expense;
console.log(type,amount);// es6 45
我们再来看个例子:
let node = {type:"Identifier", name:"foo"},
type = "Literal",name = 5;
({type,name}= node);// 使用解构来分配不同的值
console.log(type); // "Identifier"
console.log(name); // "foo"
注意:必须用圆括号包裹解构赋值语句,这是因为暴露的花括号会被解析为代码块语句,而块语句不允许在赋值操作符(即等号)左侧出现。圆括号标示了里面的花括号并不是块语句、而应该被解释为表达式,从而允许完成赋值操作。
默认值:
可以选择性地定义一个默认值,以便在指定属性不存在时使用该值。若要这么做,需要在 属性名后面添加一个等号并指定默认值,就像这样:
let node = {
type: "Identifier",
name: "foo"
};
let {
type,
name,
value = true
} = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // true
嵌套对象解构:
使用类似于对象字面量的语法,可以深入到嵌套的对象结构中去提取你想要的数据。
let node = {
type: "Identifier",
name: "foo",
loc: {
start: {
line: 1,
column: 1
},
end: {
line: 1,
column: 4
}
}
};
let { loc: { start }} = node;
console.log(start.line); // 1
console.log(start.column); // 1
本例中的解构模式使用了花括号,表示应当下行到node对象的loc属性内部去寻找start属性。
必须传值的解构参数:
function setCookie(name, value, {
secure,
path,
domain,
expires
}) {
// 设置cookie的代码
}
setCookie("type", "js");//报错
在此函数内,name与value参数是必需的,而secure、path、domain与expires则不是。默认情况下调用函数时未给参数解构传值会抛出错误。像上例中如果setCookie不传第三个参数,就会报错。若解构参数是可选的,可以给解构的参数提供默认值来处理这种错误。
function setCookie(name, value, {
secure,
path,
domain,
expires
} = {}) {}
setCookie("type", "js");//不会报错
3、数组
const names = ["Henry","Bucky","Emily"];
const [name1,name2,name3] = names;
console.log(name1,name2,name3);//Henry Bucky Emily
const [name,...rest] = names;//结合展开运算符
console.log(rest);//["Bucky", "Emily"]
用{}解构返回数组个数:
const {length} = names;
console.log(length);//3
数组解构也可以用于赋值上下文,但不需要用小括号包裹表达式。这点跟对象解构的约定不同。
let colors = ["red", "green", "blue"],
firstColor = "black",
secondColor = "purple";
[firstColor, secondColor] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
默认值:数组解构赋值同样允许在数组任意位置指定默认值。当指定位置的项不存在、或其值为undefined,那么该默认值就会被使用。
let colors = ["red"];
let [firstColor, secondColor = "green"] = colors;
console.log(firstColor); // "red"
console.log(secondColor);// "green"
与rest参数搭配(下面会讲到rest参数 和扩展运算符)
在ES5中常常使用concat()方法来克隆数组,例如:
//在ES5中克隆数组
var colors = ["red", "green", "blue"];
var clonedColors = colors.concat();
console.log(clonedColors); //"[red,green,blue]"
在ES6中,你可以使用剩余项的语法来达到同样效果
//在ES6中克隆数组
let colors = ["red", "green", "blue"];
let [...clonedColors] = colors;
console.log(clonedColors); //[red,green,blue]
看下面这个例子:如何将数组转化为对象
const points = [
[4,5],
[10,1],
[0,40]
];
//期望得到的数据格式如下,如何实现?
// [
// {x:4,y:5},
// {x:10,y:1},
// {x:0,y:40}
// ]
let newPoints = points.map(pair => {
const [x,y] = pair;
return {x,y}
})
//还可以通过以下办法,更为简便
let newPoints = points.map(([x,y]) => {
return {x,y}
})
console.log(newPoints);
混合解构
const people = [
{name:"Henry",age:20},
{name:"Bucky",age:25},
{name:"Emily",age:30}
];
//es5 写法
var age = people[0].age;
console.log(age);
//es6 解构
const [age] = people;
console.log(age);//第一次解构数组 {name:"Henry",age:20}
const [{age}] = people;//再一次解构对象
console.log(age);//20
4、注意点
当使用解构来配合var、let、const来声明变量时,必须提供初始化程序(即等号右边的值)。下面的代码都会因为缺失初始化程序而抛出语法错误:
var { type, name }; // 语法错误!
let { type, name }; // 语法错误!
const { type, name }; // 语法错误
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
四、rest 参数
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。
rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
举个例子:如何实现一个求和函数
传统写法:
function addNumbers(a,b,c,d,e){
var numbers = [a,b,c,d,e];
return numbers.reduce((sum,number) => {
return sum + number;
},0)
}
console.log(addNumbers(1,2,3,4,5));//15
ES6 写法:
function addNumbers(...numbers){
return numbers.reduce((sum,number) => {
return sum + number;
},0)
}
console.log(addNumbers(1,2,3,4,5));//15
也可以与解构赋值组合使用。
var array = [1,2,3,4,5,6];
var [a,b,...c] = array;
console.log(a);//1
console.log(b);//2
console.log(c);//[3, 4, 5, 6]
下面是一个 rest 参数代替arguments变量的例子。
rest 参数还可以与箭头函数结合
// arguments变量的写法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)// [1,2,3,4,5]
注意:
1.每个函数最多只能声明一个rest参数,而且 rest参数必须是最后一个参数,否则报错。
2.rest参数不能用于对象字面量setter之中
let object = {
set name(...value){ //报错
//执行一些逻辑
}
}
五、扩展运算符
与剩余参数关联最密切的就是扩展运算符。剩余参数允许你把多个独立的参数合并到一个数组中;而扩展运算符则允许将一个数组分割,并将各个项作为分离的参数传给函数。
当用在字符串或数组前面时称为扩展运算符,个人觉得可以理解为rest参数的逆运算,用于将数组或字符串进行拆解。有些时候,函数不允许传入数组,此时使用展开运算符就很方便,不信的话,咱们看个例子:Math.max()方法,它接受任意数量的参数,并会返回其中的最大值。
let value1 = 25,
let value2 = 50;
console.log(Math.max(value1, value2)); // 50
但若想处理数组中的值,此时该如何找到最大值?Math.max()方法并不允许你传入一个数组。其实你可以像使用rest参数那样在该数组前添加...,并直接将其传递给 Math.max()
let values = [25,50,75, 100]
//等价于console.log(Math.max(25,50,75,100));
console.log(Math.max(...values)); //100
扩展运算符还可以与其他参数混用
let values = [-25,-50,-75,-100]
console.log(Math.max(...values,0)); //0
扩展运算符拆解字符串与数组
var array = [1,2,3,4,5];
console.log(...array);//1 2 3 4 5
var str = "String";
console.log(...str);//S t r i n g
还可以实现拼接
var defaultColors = ["red","greed"];
var favoriteColors = ["orange","yellow"];
var fallColors = ["fire red","fall orange"];
console.log(["blue","green",...fallColors,...defaultColors,...favoriteColors]
//["blue", "green", "fire red", "fall orange", "red", "greed", "orange", "yellow"]
参考文章
ECMAScript 6 入门
ES6 核心特性