ECMAScript 6.0(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
1. 定义变量
1.1 let
ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
let 具有几个特点如下
1. 不存在变量提升
let不像var那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。
1 | console.log(foo); // 输出undefined |
上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量bar用let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。
2. 代码块内有效
1 | { |
上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效
for循环的计数器,就很合适使用let命令。
1 | for(let i = 0;i<10;i++){ |
3. 暂时性死区
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。1
2
3
4
5
6var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
但是如果代码块内没有声明,还是会去上一层找变量1
2
3
4
5let tmp = 123;
if (true) {
console.log(tmp);// 123
}
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
4. 不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。
1 | // 报错 |
1.2 块级作用域
- 场景一
内层变量可能会覆盖外层变量。
1 | var tmp = new Date(); |
1 | var tmp = new Date(); |
上面代码中,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。
- 场景二
用来计数的循环变量泄露为全局变量。
1 | var s = 'hello'; |
1 | var s = 'hello'; |
1.3 const
const声明一个只读的常量。一旦声明,常量的值就不能改变。
1. 不可变(地址不可变)
对于简单数据类型来说const不可以改变
1
2
3
4
5const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。
1
2
3
4
5
6
7const foo = {};
foo.prop = 123;
foo.prop
// 123
foo = {}; // TypeError: "foo" is read-only
上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
同理对于数组类型1
2
3
4const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
2. 声明必赋值
对于const来说,只声明不赋值,就会报错。1
2const foo;
// SyntaxError: Missing initializer in const declaration
1.4 顶层对象的属性
顶层对象,在浏览器环境指的是window对象,在Node指的是global对象。ES5之中,顶层对象的属性与全局变量是等价的。
1 | window.a = 1; |
- 一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;
- 另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性
2. 解构赋值
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
2.1 数组解构
以前,为变量赋值,只能直接指定值
1 | var a = 1; |
ES6允许写成下面这样。
1 | var [a, b, c] = [1, 2, 3]; |
这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
1 | let [foo, [[bar], baz]] = [1, [[2], 3]]; |
对于Set结构,也可以使用数组的解构赋值。
1 | let [x, y, z] = new Set(["a", "b", "c"]); |
2.2 默认值
解构赋值允许指定默认值。
1 | [x, y = 'b'] = ['a']; // x='a', y='b' |
如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。
1
2
3
4
5var [x = 1] = [undefined];
x // 1
var [x = 1] = [null];
x // null
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
1 | function f() { |
因为x能取到值,所以函数f根本不会执行
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
1 | let [x = 1, y = x] = []; // x=1; y=1 |
2.3 对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
不将大括号写在行首,避免JavaScript将其解释为代码块(在大括号两侧加()解决)
如果变量名与属性名不一致
1 | var { foo: baz } = { foo: 'aaa', bar: 'bbb' }; |
如果变量名与属性名一致
1 | var { foo, bar } = { foo: "aaa", bar: "bbb" }; |
变量不能重新声明
1 | let foo; |
嵌套赋值
1 | let obj = {}; |
2.4 字符串的解构赋值
字符串被转换成了一个类似数组的对象,然后再进行解构
同理,任何对象进行解构赋值都是转换成一个类似数组的对象,再进行赋值1
2
3
4
5
6const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
3 字符串扩展
3.1 字符串是否包含
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
includes()可以直接放到if中1
2
3
4
5
6
7
8
9
10var s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
3.2 字符串重复
repeat方法返回一个新字符串,表示将原字符串重复n次。
1 | 'x'.repeat(3) // "xxx" |
参数如果是小数,会被取整1
'na'.repeat(2.9) // "nana"
如果repeat的参数是负数或者Infinity,会报错。1
2
3
4'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
如果参数是0到-1之间的小数,则等同于0,这是因为会先进行取整运算。0到-1之间的小数,取整以后等于-0,repeat视同为0。
1 | 'na'.repeat(-0.9) // "" |
参数NaN等同于0。1
'na'.repeat(NaN) // ""
如果repeat的参数是字符串,则会先转换成数字。1
2'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"
3.3 字符串补全
ES7推出了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart用于头部补全,padEnd用于尾部补全。
第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。
如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。1
2
3
4
5
6
7
8'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
1 | 'abc'.padStart(10, '0123456789') |
如果省略第二个参数,则会用空格补全长度。
1 | 'x'.padStart(4) // ' x' |
3.3 模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。1
2
3
4
5
6
7
8
9
10
11
12
13// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);
// 字符串中嵌入变量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。
1 | var x = 1; |
模板字符串之中还能调用函数。
1 | function fn() { |
4 数组的扩展
4.1 Iterator接口
Iterator接口的目的就是为所有数据结构提供一种统一访问的机制,用for…of实现。
一个数据结构只要有Symbol.iterator属性,就可以认为是“可遍历的”
原型部署了Iterator接口的数据结构有三种,具体包含四种,分别是
- 数组
- 类似数组的对象
- Set
- Map
字符串是一个类似数组的对象,也原生具有Iterator接口。
还有几个别的场合。
- 解构赋值
- 扩展运算符(…)
1 | //创建一个构造函数 |
一个为对象添加Iterator接口
1 | let obj = { |
4.2 转换数组
Array.from方法用于将类对象转为真正的数组
1 | let arrayLike = { |
常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
1 | // NodeList对象 |
用扩展运算符也可以1
2
3
4
5
6
7// arguments对象
function foo() {
var args = [...arguments];
}
// NodeList对象
[...document.querySelectorAll('div')]
(…)和Array.from区别
只要是部署了Iterator接口的数据结构,Array.from都能将其转为数组。
扩展运算符(...)也可以将某些数据结构转为数组。
- 扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。
- 任何有length属性的对象,都可以通过Array.from方法转为数组
1 | Array.from({ length: 3 }); |
Array.from处理数据
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
1 | Array.from(arrayLike, x => x * x); |
下面的例子是取出一组DOM节点的文本内容。
1 | let spans = document.querySelectorAll('span.name'); |
字符串长度
Array.from()的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种Unicode字符,可以避免JavaScript将大于\uFFFF的Unicode字符,算作两个字符的bug。
1 | function countSymbols(string) { |
4.3 一组值转换为数组
Array.of方法用于将一组值,转换为数组。
1 | Array.of(3, 11, 8) // [3,11,8] |
Array()和Array.of
- Array方法没有参数、一个参数、三个参数时,返回结果都不一样。
- 只有当参数个数不少于2个时,Array()才会返回由参数组成的新数组。
- 参数个数只有一个时,实际上是指定数组的长度。
1
2
3Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。1
2
3
4Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]
4.4 成员复制
数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
1 | Array.prototype.copyWithin(target, start = 0, end = this.length) |
它接受三个参数。
- target(必需):从该位置开始替换数据。
- start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
这三个参数都应该是数值,如果不是,会自动转为数值。
1 | [1, 2, 3, 4, 5].copyWithin(0, 3) |
上面代码表示将从3号位直到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。
1 | // 将3号位复制到0号位 |
4.5 数组查找
find()
用于找出第一个符合条件的数组成员
参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。
如果没有符合条件的成员,则返回undefined。
找出数组中第一个小于0的成员。1
2[1, 4, -5, 10].find((n) => n < 0)
// -5
find方法的回调函数可以接受三个参数,依次为
- 当前的值
- 当前的位置
- 原数组。
找出第一个大于9的成员1
2
3[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
findIndex()
findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置
如果所有成员都不符合条件,则返回-1。
1 | [1, 5, 10, 15].findIndex(function(value, index, arr) { |
4.6 填充数组
fill方法使用给定值,填充一个数组。
- 对于空数组,fill方法用于空数组的初始化。
- 对于数组中已有的元素,会被全部抹去。
1 | ['a', 'b', 'c'].fill(7) |
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
1 | ['a', 'b', 'c'].fill(7, 1, 2) |
4.6 数组便利
ES6提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历,
- keys()是对键名的遍历
- values()是对键值的遍历
- entries()是对键值对的遍历
1 | for (let index of ['a', 'b'].keys()) { |
4.7 数组包含
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。
1 | [1, 2, 3].includes(2); // true |
第二个参数表示搜索的起始位置,默认为0。
如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。1
2
3
4
5
6
7
8[3, 2, 1].includes(3, 0);
// true
[3, 2, 1].includes(3, 1);
// false
[1, 2, 3].includes(3, 3);
// false
[1, 2, 3].includes(3, -1);
// true
4.8 回顾ES5中数组方法
- 2个索引方法:indexOf() 和 lastIndexOf();
- 5个迭代方法:forEach()、map()、filter()、some()、every();
- 2个归并方法:reduce()、reduceRight();
索引方法
索引方法包含indexOf()和lastIndexOf()两个方法,这两个方法都接收两个参数,
- 第一个参数是要查找的项,
- 第二个参数是查找起点位置的索引,该参数可选,如果缺省或是格式不正确,那么默认为0。两个方法都返回查找项在数组中的位置,如果没有找到,那么返回-1
indexOf():该方法从数组的开头开始向后查找。1
2
3
4
5var dataArray = [1, 7, 5, 7, 1, 3];
console.log(dataArray.indexOf(7)); // 1 从第一项开始查找
console.log(dataArray.indexOf(7, 's')); // 1 格式不正确, 从第一项开始查找
console.log(dataArray.indexOf(7, 2)); // 3 从第三个项之后开始查找
console.log(dataArray.indexOf (2)); // -1 未找到, 返回-1
lastIndexOf(): 该方法从数组的末尾开始向前查找1
2
3
4
5var dataArray = [1, 7, 5, 7, 1, 3];
console.log(dataArray.lastIndexOf (7)); // 3 从末尾第一项开始查找
console.log(dataArray.lastIndexOf (7, 's')); // 3 格式不正确, 从末尾第一项开始查找
console.log(dataArray.lastIndexOf (7, 2)); // 1 从末尾第三项往前查找
console.log(dataArray.lastIndexOf ('4')); // -1 未找到, 返回-1
迭代方法
这些方法都接收两个参数,
- 第一个参数是一个函数,他接收三个参数,
- 数组当前项的值
- 当前项在数组中的索引
- 数组对象本身。
- 第二个参数是执行第一个函数参数的作用域对象,也就是上面说的函数中this所指向的值。
- 注意,这几种方法都不会改变原数组。
every()
该方法对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回true。1
2
3
4
5
6
7
8
9var dataArray = [1, 7, 5, 7, 1, 3];
var result = dataArray.every(function(item,index,arry){
return item>1;
})
console.log(result)//false
var result = dataArray.every(function(item,index,arry){
return item>0;
})
console.log(result)//true
some()
该方法对数组中的每一项运行给定函数,如果该函数对任何一项返回 true,则返回true。some方法会在数组中任一项执行函数返回true之后,不在进行循环。
1
2
3
4
5var dataArray = [1, 7, 5, 7, 1, 3];
var result = dataArray.some(function(item,index,arry){
return item > 5;
})
console.log(result)//true
filter()
该方法对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。利用这个方法可对数组元素进行过滤筛选。
1 | var dataArray = [1, 7, 5, 7, 1, 3]; |
map()
该方法对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
我们为数组中的每一项乘以3,返回每一项相乘之后的数组。1
2
3
4
5var dataArray = [1, 7, 5, 7, 1, 3];
var result = dataArray.map(function(item,index,arry){
return item* 3;
})
console.log(result) // [3, 21, 15, 21, 3, 9]
forEach()
该方法对数组中的每一项运行给定函数。这个方法没有返回值。这个方法其实就是遍历循环,和for循环没有太大差别。
1 | var dataArray = [1, 7, 5, 7, 1, 3]; |
归并方法
归并方法包含reduce()和reduceRight()两个方法,这两个方法都会迭代数组中的所有项,然后生成一个最终返回值。
他们都接收两个参数,
- 第一个参数是每一项调用的函数,函数接受是个参数分别是
- 初始值
- 当前值
- 索引值
- 当前数组
函数需要返回一个值,这个值会在下一次迭代中作为初始值。
- 第二个参数是迭代初始值,参数可选,如果缺省,初始值为数组第一项,从数组第一个项开始叠加,缺省参数要比正常传值少一次运算。
reduce()
该方法从数组的第一项开始,逐个遍历到最后一项
1 | var dataArray = [1, 7, 5, 7, 1, 3]; |
设置了第二个参数,会多执行一次。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var dataArray = [1, 7, 5, 7, 1, 3];
var sum = dataArray.reduce(function(prev,cur,index,array) {
console.log(prev,cur,index);
return prev+cur;
},2)
console.log(sum);
//2 1 0
//3 7 1
//10 5 2
//15 7 3
//22 1 4
//23 3 5
//26
reduceRight()就是从右到左,就不详细说了。
最后更新: 2019年02月26日 20:23