ES6 快速入门教程

ECMAScript

ECMAScript 简介

ECMAScript 是一种由 Ecma 国际(前身为欧洲计算机制造商协会,英文名称是 European Computer Manufacturers Association)通过 ECMA-262 标准化的脚本程序设计语言。ECMAScript 是浏览器脚本语言的规范,而 JavaScript 和 JScript 都是 ECMAScript 规范的实现者。

前端发展历程回顾

  • Web 1.0 时代

    • 最初的网页以 HTML 为主,是纯静态的网页。网页是只读的,信息流只能从服务到客户端单向流通。开发人员只关心页面的样式和内容即可。
  • Web 2.0 时代

    • 1995 年,网景工程师 Brendan Eich 花了 10 天时间设计了 JavaScript 语言。
    • 1996 年,微软发布了 JScript,其实是 JavaScript 的逆向工程实现。
    • 1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。
    • 1997 年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
    • JavaScript 和 JScript 都是 ECMAScript 规范的实现者,随后各大浏览器厂商纷纷实现了 ECMAScript 规范。

ES6 快速入门

ES6 简介

ECMAScript 6.0(简称 ES6)是 JavaScript 语言的下一代标准,在 2015 年 6 月正式发布,并且从 ECMAScript 6 开始,开始采用年号来做版本。因此,ECMAScript 2015,也被称为 ECMAScript 6。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。 ECMAScript 每年发布一个新版本,从版本发布的概念上,不同版本的 ECMAScript 可以类比不同版本的 Java JDK(例如 JDK 8、JDK 11、JDK 15)。

变量声明

let 声明变量

  • let 声明的变量有严格的局部作用域,而 var 声明的变量往往会越域
1
2
3
4
5
6
{
var a = 1;
let b = 2;
}
console.log(a); // 1
console.log(b); // ReferenceError: b is not defined
  • 同一个变量,let 只可以声明一次,而 var 可以声明多次
1
2
3
4
5
6
var a = 1;
var a = 2;
let b = 3;
// let b = 4; // 同一个变量声明多次会出现语法错误
console.log(a); // 2
console.log(b); // 3
  • let 不存在变量提升,而 var 存在变量提升
1
2
3
4
5
console.log(a);   // undefined
var a = 1;

console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 2;

const 声明常量

  • const 用于声明常量,声明后不允许改变常量的值,一旦声明必须初始化,否则会出现语法错误
1
2
const a = 1;
a = 2; // Uncaught TypeError: Assignment to constant variable.

解构表达式

数组解构

1
2
3
4
5
6
7
8
9
10
11
let arr = [1, 2, 3];

// 第一种写法(普通),通过角标获取数组元素
let a1 = arr[0];
let a2 = arr[1];
let a3 = arr[2];
console.log(a1, a2, a3); // 1 2 3

// 第二种写法(数组解构),b1, b2, b3 将与 arr 数组中的每个位置对应来取值
let [b1, b2, b3] = arr;
console.log(b1, b2, b3); // 1 2 3

对象解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const person = {
name: "jack",
age: 23
}

// 第一种写法(普通),通过对象来获取属性值
const name1 = person.name;
const age1 = person.age;
console.log(name1, age1);

// 第二种写法(对象解构),对象里面的每个属性和左边对应赋值
const {name, age} = person;
console.log(name, age);

// 第三种写法(对象解构),对象里面的每个属性和左边对应赋值,并使用变量别名
const {name:name2, age:age2} = person;
console.log(name2, age2);

字符串扩展

新 API 函数

  • includes():返回布尔值,表示是否找到了参数字符串
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部
1
2
3
4
5
let str = "hello world"; 
console.log(str.startsWith("hello")); // true
console.log(str.endsWith("world")); // true
console.log(str.includes("e")); // true
console.log(str.includes("hello")); // true

字符串模版

模板字符串相当于加强版的字符串,使用反引号 ` 包裹,除了可以作为普通字符串,还可以用来定义多行字符串,也可以在字符串中加入变量和表达式。

  • 定义多行字符串
1
2
3
4
5
6
let str = `
<div>
<span>Hello World</span>
</div>
`;
console.log(str);
  • 字符串中插入变量,变量名写在 ${}
1
2
3
4
let name = "Jack";
let age = 18;
let info = `我是${name}, 年龄是${age}岁`;
console.log(info);
  • 字符串中插入表达式,${} 内可以放入 JavaScript 表达式,例如函数调用
1
2
3
4
5
6
7
function fun() {
return "This is a function."
}

// 在字符串中调用函数
let str2 = `Return Message : ${fun()}`;
console.log(str2);

函数优化

函数参数默认值

ES6 支持给函数参数设置默认值,语法为 参数名称 = 默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在 ES6 之前,无法给一个函数参数设置默认值,只能采用变通的写法
function add(a, b) {
// 判断 b 是否为空,为空则赋默认值 1
b = b || 1;
return a + b;
}
// 只传递一个参数
console.log(add(10)); // 11

// ES6 可以这么写,直接给参数设置默认值,如果没有传递就会自动使用默认值
function sub(a, b = 1) {
return a - b;
}
// 只传递一个参数
console.log(sub(3)); // 2

不定参数

不定参数用来表示不确定参数个数,语法为 ... 变量名,由 ... 加上一个具名参数标识符组成。特别注意,具名参数只能放在参数列表的最后,并且有且只能有一个不定参数。

1
2
3
4
5
function fun(... params) {
console.log(params.length);
}
fun("a", "b"); // 2
fun("a", "b", "c", "d"); // 4

箭头函数

声明单个参数的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 在 ES6 之前,声明单个参数函数的写法
var print = function (param) {
console.log(param);
};
print("Hello World");

// 在 ES6 中,声明单个参数函数的写法(第一种)
var echo = (param) => {
console.log(param);
}
echo("Hello World")

// 在 ES6 中,声明单个参数函数的写法(第二种),只有一个参数时,可省略括号 ()
var output = param => {
console.log(param);
}
output("Hello World")

// 在 ES6 中,声明单个参数函数的写法(第三种),函数体只有一行语句时,可以省略 {}
var display = param => console.log(param);
display("Hello World")

声明多个参数的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在 ES6 之前,声明多个参数函数的写法
var sum = function (a, b) {
console.log(a + b);
}
sum(2, 5);

// 在 ES6 中,声明多个参数函数的写法(第一种)
var sub = (a, b) => {
console.log(a - b);
}
sub(5, 2);

// 在 ES6 中,声明多个参数函数的写法(第二种),函数体只有一行语句时,可以省略 {}
var div = (a, b) => console.log(a / b);
div(8, 4);

// 在 ES6 中,声明多个参数函数的写法(第三种),函数体只有一行语句时,并且需要返回结果时,可以省略 {},会自动返回结果
var multi = (a, b) => a * b;
console.log(multi(3, 7));

箭头函数结合解构表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const person = {
name: "jack",
age: 23
}

// 在 ES6 之前,声明一个对象,将对象作为函数参数,然后在函数体内访问对象属性的写法
function printPerson(person){
console.log("hello " + person.name);
}
printPerson(person);

// 在 ES6 中,声明一个对象,将对象作为函数参数,然后在函数体内访问对象属性的写法
var echoPerson = ({ name }) => {
console.log("hello " + name);
}
echoPerson(person);

对象优化

新 API 函数

ES6 给 Object 对象拓展了许多新的 API 函数,如:

  • keys(obj):获取对象的所有 key 形成的数组
  • values(obj):获取对象的所有 value 形成的数组
  • entries(obj):获取对象的所有 key 和 value 形成的二维数组,格式:[[k1,v1], [k2,v2], ...]
  • assign(dest, ...src):将多个 src 对象的值拷贝到 dest 中(第一层为深拷贝,第二层为浅拷贝)。
1
2
3
4
5
6
7
const person = {
name: "jack",
age: 25
}
console.log(Object.keys(person)); // ['name', 'age']
console.log(Object.values(person)); // ['jack', 25]
console.log(Object.entries(person)); // [Array(2), Array(2)]
1
2
3
4
5
6
7
const dest = {a : 1};
const source1 = {b: 2};
const source2 = {c: 3};

// Object.assign() 拷贝函数的第一个参数是目标对象,后面的参数都是源对象
Object.assign(dest, source1, source2);
console.log(dest); // {a:1, b:2, c:3}

声明对象简写

1
2
3
4
5
6
7
8
9
10
const age = 23;
const name = "Jack";

// 在 ES6 之前,声明对象的写法
const person1 = {age: age, name: name};
console.log(person1); // {age: 23, name: "Jack"}

// 在 ES6 中,声明对象时支持简写
const person2 = {age, name};
console.log(person2); // {age: 23, name: "Jack"}

对象的函数属性简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let person = {
name: "Jack",

// 在 ES6 之前,对象的函数属性的写法
eat: function (food) {
console.log(this.name + " eat " + food);
},

// 在 ES6 中,对象的函数属性的第一种写法(箭头函数版本),这里拿不到 this 对象
play1: (toy) => {
console.log(person.name + " play " + toy);
},

// 在 ES6 中,对象的函数属性的第二种写法(简写版本),这里可以拿到 this 对象
play2 (toy) {
console.log(this.name + " play " + toy);
}
}

person.eat('apple'); // Jack eat apple
person.play1("computer"); // Jack play computer
person.play2("computer"); // Jack play computer

对象拓展运算符

对象拓展运算符 ... 用于取出参数对象所有可遍历的属性,然后拷贝给当前对象。

1
2
3
4
5
6
7
8
9
10
// 拷贝对象(深拷贝)
let person1 = { name: "Jack", age: 23 };
let someone = { ...person1 };
console.log(someone); // {name: "Jack", age: 23}

// 合并对象,如果两个对象的属性有重复,后面对象的属性值会覆盖前面对象的属性值
let person2 = { age: 15 };
let person3 = { name: "Jack" };
let person4 = { ...person2, ...person3 };
console.log(person4); // {age: 15, name:"Jack"}

map 和 reduce 函数

ES6 在数组中新增了 mapreduce 函数。

map 函数

  • 语法:arr.map(callback)
  • 描述:map 函数的参数是一个回调函数,将原数组中的所有元素用这个回调函数处理后放入新数组并返回。
1
2
3
4
5
6
7
8
let arr = ["1", "23", "5", "16"];
console.log(arr); // ["1", "23", "5", "16"]

arr = arr.map(item => parseInt(item));
console.log(arr); // [1, 23, 5, 16]

arr = arr.map(item => item * 2);
console.log(arr); // [2, 46, 10, 32]

reduce 函数

  • 语法:arr.reduce(callback, [initialValue])

  • 描述:reduce 函数会为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素

  • callback:处理数组中每个元素的回调函数,包含四个参数

    • previousValue:上一次调用回调函数返回的值,或者是提供的初始值(initialValue)
    • currentValue:数组中当前被处理的元素
    • index:当前元素在数组中的索引
    • array:调用 reduce 函数的数组
  • initialValue:作为第一次调用 callback 回调函数的初始值(或者上一次回调函数的返回值)

1
2
3
4
5
6
7
8
9
const arr = [1, 20, -5, 3];

// 没有初始值
console.log(arr.reduce((a, b) => a + b)); // 19 = 1 + 20 + (-5) + 3
console.log(arr.reduce((a, b) => a * b)); // -300 = 1 * 20 * (-5) * 3

// 拥有初始值
console.log(arr.reduce((a, b) => a + b, 1)); // 20 = 1 + 1 + 20 + (-5) + 3
console.log(arr.reduce((a, b) => a * b, 0)); // -0 = 0 * 1 * 20 * (-5) * 3

Promise

在 JavaScript 的世界中,所有代码都是单线程执行的。由于这个 “缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现。一旦有一连串的 Ajax 请求 a,b,c,d … 后面的请求依赖前面的请求结果,就需要层层嵌套。这种缩进和层层嵌套的方式,非常容易造成上下文代码混乱,开发者不得不非常小心翼翼处理内层函数与外层函数的数据,一旦内层函数使用了上层函数的变量,这种混乱程度就会加剧。总之,这种层叠上下文的层层嵌套方式,着实增加了开发的难度。

传统 Ajax 回调使用案例

这里将演示传统 Ajax 回调的使用案例,目的是让读者对层层嵌套的代码有一个直观的了解。

需求说明

用户登录后,展示该用户的各科成绩。在页面发送三次请求:

  • 1、查询用户,查询成功说明可以登录
  • 2、根据用户的查询结果,查询科目信息
  • 3、根据科目的查询结果,获取科目成绩
后端接口

此时后台应该提供三个接口,一个提供用户的查询接口,一个提供科目的查询接口,一个提供各科成绩的查询接口。为了渲染方便,建议最好是响应 JSON 数据。在这里就不编写后台接口了,而是提供三个 JSON 文件,通过直接提供 JSON 数据的方式来模拟后台的接口。

  • user.json
1
2
3
4
5
{
"id": 1,
"name": "zhangsan",
"password": "123456"
}
  • user_corse_1.json
1
2
3
4
{
"id": 10,
"name": "chinese"
}
  • corse_score_10.json
1
2
3
4
{
"id": 100,
"score": 90
}
调用接口

在传统的 Jquery Ajax 回调方式中,回调函数层层嵌套,也被称为 回调地狱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$.ajax({
url: "mock/user.json",
success(data) {
console.log("查询到用户:", data);
$.ajax({
url: `mock/user_corse_${data.id}.json`,
success(data) {
console.log("查询到课程:", data);
$.ajax({
url: `mock/corse_score_${data.id}.json`,
success(data) {
console.log("查询到分数:", data);
},
error(error) {
console.log("出现异常了:" + error);
}
});
},
error(error) {
console.log("出现异常了:" + error);
}
});
},
error(error) {
console.log("出现异常了:" + error);
}
});

Promise 语法

  • 基础版的语法
1
2
3
4
5
6
7
8
9
10
const promise = new Promise(function (resolve, reject) {
// 执行异步操作
let result = true;
// 判断异步操作执行的结果
if (result) {
resolve(value); // 调用 resolve 函数,代表 Promise 将返回成功的结果
} else {
reject(error); // 调用 reject 函数,代表 Promise 将会返回失败结果
}
});
  • 箭头函数版的语法
1
2
3
4
5
6
7
8
9
10
const promise = new Promise((resolve, reject) => {
// 执行异步操作
let result = true;
// 判断异步操作执行的结果
if (result) {
resolve(value); // 调用 resolve 函数,代表 Promise 将返回成功的结果
} else {
reject(error); // 调用 reject 函数,代表 Promise 将会返回失败结果
}
});

Prmise 处理执行结果

如果想要等待异步执行完成后做一些事情,可以通过 Promise 的 then 函数来实现。如果想要处理 Promise 异步执行失败的事件,还可以使用 catch 函数。

1
2
3
4
5
promise.then(function (value) {
// 异步执行成功后的回调
}).catch(function (error) {
// 异步执行失败后的回调
})

Promise 使用案例

上述的 Jquery Ajax 回调代码,使用 Promise 改造后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
new Promise((resolve, reject) => {
$.ajax({
url: "mock/user.json",
success(data) {
console.log("查询到用户:", data);
resolve(data.id);
},
error(error) {
console.log("出现异常了:" + error);
}
});
}).then((userId) => {
return new Promise((resolve, reject) => {
$.ajax({
url: `mock/user_corse_${userId}.json`, success(data) {
console.log("查询到课程:", data);
resolve(data.id);
},
error(error) {
console.log("出现异常了:" + error);
}
});
});
}).then((corseId) => {
$.ajax({
url: `mock/corse_score_${corseId}.json`,
success(data) {
console.log("查询到分数:", data);
},
error(error) {
console.log("出现异常了:" + error);
}
});
});

Promise 优化处理

通常在企业开发中,会把 Promise 封装成通用方法,下述的代码封装了一个通用的 get 请求方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 企业开发中,往往会将 get 方法单独放到 common.js 中
let get = function (url, data) {
return new Promise((resolve, reject) => {
$.ajax({
url: url,
type: "GET",
data: data,
success(result) {
resolve(result);
},
error(error) {
reject(error);
}
});
})
}

// 使用封装的 get 方法,实现分数查询
get("mock/user.json").then((result) => {
console.log("查询到用户:", result);
return get(`mock/user_corse_${result.id}.json`);
}).then((result) => {
console.log("查询到课程:", result);
return get(`mock/corse_score_${result.id}.json`)
}).then((result) => {
console.log("查询到分数:", result);
}).catch(() => {
console.log("出现异常了:" + error);
});

模块化

模块化介绍

模块化就是把代码进行拆分,方便重复利用。类似 Java 中的导包操作:要使用一个包,必须先导包。而 JavaScript 中没有包的概念,换来的是模块。模块功能主要由两个指令构成: exportimport

  • export:用于规定模块的对外接口
  • import:用于导入其他模块提供的功能

export 指令

比如定义一个 JavaScript 文件 hello.js,里面有一个对象,可以使用 export 指令将这个对象导出:

1
2
3
4
5
6
7
const util = {
sum (a, b) {
return a + b;
}
}

export {util};

当然,也可以简写为:

1
2
3
4
5
export const util = {
sum (a, b) {
return a + b;
}
}

export 指定不仅可以导出对象,一切 JavaScript 变量都可以导出,包括基本类型变量、函数、数组、对象。当要导出多个值时,还可以简写,比如定义 user.js 文件:

1
2
3
var name = "jack"
var age = 21
export {name, age}

在上面的导出代码中,都明确指定了导出的变量名,这样其它人在导入使用时就必须准确写出变量名,否则就会出错。因此 JavaScript 提供了 default 关键字,可以对导出的变量名进行省略

1
2
3
4
5
export default {
sum (a, b) {
return a + b;
}
}

import 指令

使用 export 命令定义了模块的对外接口以后,其他 JavaScript 文件就可以通过 import 指令加载这个模块。

1
2
3
4
import util from 'hello.js'

// 调用util对象中的属性
util.sum(1, 2);

批量导入前面在 user.js 中导出的 nameage 变量

1
2
3
import {name, age} from 'user.js'

console.log(name + " , 今年" + age + "岁了");

浏览器支持说明

上面的代码暂时无法直接在浏览器运行,因为浏览器目前还不支持 ES6 的导入和导出功能。除非借助于第三方工具,将 ES6 的语法进行编译降级到 ES5,比如 Babel 工具。值得一提的是,Babel 是一个工具链,主要用于将 ES6+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。