萝三画室

仿网易云音乐PC-总结Part3(ES6)

本篇是仿网易云音乐PC-总结的第三部分,Part3-ES6。本文总结的并非ES6全部内容,而是是在项目中运用到的ES6新特性,包括let、模板字符串、对象简洁表示法、解构赋值、数组查找、rest参数、扩展运算符、箭头函数、for…of循环、模块。本文对上述每个新特性,会介绍它的用法、特性、应用。
仿网易云音乐PC全部项目请戳->>>>> https://github.com/love3forever/gogo3

关于ES6

ES6已于2015年6月正式发布,是ECMAScript历史上最大的一次版本省级,代表JavaScript语言的下一代标准。我对ES6的理解是,它提供了许多全新的特性,把现代编程语言中比较流行的思想带入ECMAScript中来;它也包括一些语法糖,让语法更精简,更具有表现力。使用ES6,可以让代码更加简洁易懂、安全优雅。

let命令-声明变量

语法

let命令用法类似于var,用于变量声明,如:

1
2
3
4
let a = 10;
let b = function (){
alert("I'm let");
};

特性-与var的区别

  • 创建了块级作用域,let声明的变量仅在声明所在的{}代码块内有效
1
2
3
4
5
6
7
{
let a = 10;
var b = 20;
}
console.log(a)//ReferenceError错误
console.log(b)//20
  • 不存在变量提升
    我们知道,var声明的变量,声明会提升到作用域顶端。比如下面代码执行后会在控制台输出undefined(变量声明但未赋值)而不是抛出ReferenceError错误(变量未声明)。
1
2
3
4
function init(){
console.log(a)//undefined
var a = 10;
}

原因就是变量提升会使得上面的代码在实际运行时变为:

1
2
3
4
5
function init(){
var a;
console.log(a)//10
a = 10;
}

如果使用let进行变量声明,不存在变量提升,则会抛出ReferenceError错误。

  • 存在暂时性死区
    块级作用域内的使用let声明的变量,会绑定这个区域,不能再声明之前使用变量。
1
2
3
4
5
6
7
8
function init(){
var a = 10;
if (true){
a = 20;//ReferenceError
let a = 30;
}
}
  • 不允许重复声明
    在同一个块级作用域内,let声明的变量只允许声明一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function init(e){
var a = 10;
let a = 10;//报错
let b = 20;
var b = 20;//报错
let c = 30;
let c = 30;//报错
var d = 40;
var d = 40;//不报错
let e;//报错
}
  • 全局let变量不是全局对象的属性
    var声明的全局变量会成为全局对象的属性,let声明的全局对象不会成为全局对象的属性。
1
2
3
4
5
6
7
8
window.a = 10;
a//10
a = 20;
window.a//20
let b = 30;
window.b//undefined

应用

传统for循环的计数器使用let声明可以避免计数器泄露出循环。
事实上let可以完全代替var,let与var语义相同且没有副作用。

模板字符串

ES6以前,字符串由双引号或单引号包围,由于不能多行书写,当字符串较长时,会采用使用+运算符将多个字符串拼接成一个字符串。字符串字面量与变量拼接时也需要采取上述办法。这种写法繁琐且不方便,因此ES6引入了模板字符串解决这个问题。

语法

反引号``中包含字符串,在反引号中用${}包含变量或JavaScript表达式。

1
2
3
4
let a = "world"
let old = "hello"+" "+a+","+"ES6"+" "+a;//hello world,ES6 world
let new = `hello ${a},ES6 ${a}`;//hello world,ES6 world

特性

  • ${}中的内容可以是任意JavaScript表达式,所以静态字符串、变量、函数调用、算数运算等都可以写在其中
  • 如果${}中表达式返回的不是字符串,则自动执行toString()方法
  • 不会自动转义特殊字符,中所有空格、新行、缩进等,都会被识别和保留。

对象简洁表示法

ES6运行直接斜土变量和函数作为对象的属性和方法。

语法

ES6允许对象只写属性名不写属性值,此时属性值等于属性名代表的变量。

1
2
3
4
5
var a = 1;
//ES5
var ob = { a: a}
//ES6
var ob = {a};

解构赋值

ES5允许按照一定的模式,直接从数组、对象、字符串、数值、布尔值、函数参数等中提取值并赋值给变量。

语法

解构赋值的本质是模式匹配,等号左边的变量会寻找等号右边具有相同模式的值。如果是数组,则按位置和模式匹配;如果是对象,则按属性名和模式匹配。

1
2
3
4
5
6
7
8
9
10
11
12
//ES5
var a = 1,
b = 2,
c = 3;
//ES6数组
var [a,b,c] = [1,2,3];//a=1,b=2,c=3
var [a,b,c] = [1,[2,3],4];//a=1,b=[2,3],c=4
var [a,[b],c] = [1,[2,3],4];//a=1,b=2,c=4
//ES6对象
let {a,b,c:{d:third}} = {a:1,b:2,c:{d:3}};//a=1,b=2,third=3,其中c:{d}是模式,third是模式对应值赋给的目标变量

特性

  • 具有Iteration接口的数据结构都可以采用数组形式解构赋值
  • 如果解构不成功,变量值为undefined
  • 允许为变量指定默认值,默认值在等号右边的对应值===undefined的时候生效

应用

数组查找

ES6数组实例的Array.find()和Array.findIndex方法用于找到数组中第一个符合要求的数组成员/位置,其参数为一个回调函数,所有数组成员依次执行回调函数直至找到第一个满足条件的成员。

语法

array.findIndex(function(value, index, arr), thisValue)

1
2
3
4
5
6
7
[1,2,3,4].find(function(value){
return value>3
})//4
[1,2,3,4].findIndex(function(value){
return value>3
})//3

特性

  • find和findIndex查找失败返回undefined和-1
  • 对于空数组不会执行
  • 不改变原数组
  • 可以识别indexOf方法无法识别的NaN,具有更强的语义

rest参数

ES6引入rest参数,用于获取函数的多余参数。

语法

…变量名,变量为一个数组。也就是说,如果函数有多余参数,rest参数会将这些参数存放在这个数组中。如下:

1
2
3
4
5
6
7
8
9
function add(...values){
let sum = 0;
for (let value of values){//values=[1,2,3];
sum += value;
}
return sum
}
add(1,2,3);//6

rest参数可以替代arguments对象,并且由于rest参数中的变量是一个数组,因此这个变量可以使用所有数组的特有方法。

特性

  • rest参数只能是最后一个参数,在它之后不可以存在其他参数
  • 函数的length属性不包括rest参数

应用

  • 与解构赋值结合生成数组
    1
    2
    3
    4
    5
    //ES5
    var a = list[0],
    rest = list.slice(1);
    //ES6
    var [a,...rest] = list;

扩展运算符

扩展运算符…是rest参数的逆运算,它将一个数组转为用,分隔的参数序列。

语法

…变量名,变量名为一个数组。在数组前加入扩展运算符,可以将数组展开为参数序列。

1
2
3
4
5
6
function push(arr,...items){
arr.push(...items);
}
let a = [1,2];
push (a,3,4);//arr = [1,2,3,4]

特性

  • 扩展运算符可以与正常的函数参数结合使用,无顺序要求
  • 具有Iterator接口的对象,都可以使用扩展运算符//typeof obj[Symbol.iterator] === ‘function’

应用

  • 在将数组转为参数序列时,可以取代apply、call方法
  • 合并数组
1
2
3
4
5
6
7
var a = [1,2];
var b = [3,4,5];
var c = [6];
//ES5
a.concat(b,c);//[1,2,3,4,5,6]
//ES6
[...a,...b,...c]//[1,2,3,4,5,6]
  • 将类数组的对象转化为数组
1
2
var nodeList = document.querySelectorAll('div');
var nodeArr = [...nodeList];

本项目中的例子详见
https://github.com/love3forever/gogo3/blob/master/src/components/generalComment.vue
line 119-135

箭头函数

ES6允许使用=>定义函数。

语法

( 参数 ) => { 函数体 }

  • 只有一个参数时,参数可不加圆括号()
  • 只有一条语句时,函数体可不加大括号{},并且可以省略return关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//一般函数
var a = function(v){
return v++;
};
//箭头函数
var a = v => v++;
var add = (num1,num2) => {
if (num1>0){
return num1 + num2;
} else {
return num2;
};
}

特性

  • 不存在arguments对象,如有需要可以使用rest参数代替
  • 一般函数的this指针指向使用是调用它的那个对象;箭头函数绑定了this指针,它总是指向函数定义时所在的对象。
  • 不可以使用new命令作为构造函数
  • 不可以使用yield命令

应用

  • 立即执行函数可以写为箭头函数,这样表达更清楚简洁
  • 需要使用函数表达式的场景、简单、单行、不会复用的函数,尽量使用箭头函数。由于箭头函数this指正总是指向函数定义时所在的对象,它修正了定义在函数中的函数的this指针指向问题。

在本项目中,所有响应服务器返回数据的函数都写为箭头函数
https://github.com/love3forever/gogo3/blob/master/src/components/play-list.vue
line 177-186
以及定义在函数中的函数修正this指针:
https://github.com/love3forever/gogo3/blob/master/src/components/mainleft.vue
line 111-137

for…of循环

ES6引入的for…of循环,可以遍历所有具有Iterator接口的对象,获取的是对象的键值

语法

1
2
3
for (let value of obj){
doSomething
}

特性

  • for…of循环获取的是键值而非键名或索引,如需获取索引,需要借助数组的entries或keys方法。
1
2
3
4
5
6
7
8
var arr = ['a','b','c'];
for ( let [index,value] of arr.entries()){
console.log(index,value);
}
//0 a
//1 b
//2 c
  • 遍历数组时,只返回具有数字索引的属性,并且返回索引类型为number(for…in循环返回的是string)
  • 对于类数组但不具有Iterator接口的对象,可以使用Array.from先将其转为数组
  • 对于普通的不具有Iterator接口的对象,可以使用object.keys方法将对象的键名生成一个数组,然后遍历此数组;也可以直接将数组是Symbol.iterator属性直接赋给对象。
  • 可以正确识别32位UTF-16字符

与其他循环比较

  • 与foreach
    foreach循环的实现效果与for…of一致,但break,return都无法生效,也就是说无法中途跳出。因此for…of可以完全代替foreach。
  • 与for…in
    for…in循环可以遍历没有Iterator接口的对象,而for…of不可以
    for…in循环遍历的是键名,键名以字符串形式存在,而for…of遍历的是键值
    for…in循环遍历的范围包括手动添加的其他键以及原型链上的键,而for…of不会
    for…in循环在一些情况下不按照顺序遍历,而for…of不会

综上,for…in更适合于遍历对象而不适合遍历数组,for…of更适合于遍历数组以及具有Iterator接口的其他对象

本项目中for…of应用见
https://github.com/love3forever/gogo3/blob/master/src/components/play-list.vue line 187-218

模块

ES6在语言规格上实现了模块功能,通过显式输出指定代码,在需要是通过静态命令引入,就可以实现方法加载。这种实现方法可以在编译时就完成加载,比现有的规范更高效。模块部分详细内容参见
http://es6.ruanyifeng.com/#docs/module,这里不再详述了。