logo头像
Snippet 博客主题

ES6回顾

1. 了解ES6

1.1 为什么要回顾ES6

  • 作为后端工程师,学习前端知识,遵循人无我有、人有我优的原则,保持竞争力
  • 现在使用的主流前端框架都是基于ES6,学好前端,ES6+就是必修课,原生JS玩的溜,什么流行框架都是浮云。
  • 想做全栈工程师,能够在公司独当一面的人绝对是受人(尤其是老板)欢迎的人,另外,自主创业的快速首选。
  • 站在项目宏观角度统筹,与不同技术栈团队相互配合,管理者前期着重点肯定业务和技术起步,后续考虑的角度就要转移到产品和市场。

1.2 什么是ES6

ES6,是ECMAScript 6的简称,它是 JavaScript 语言的下一代标准,己于 2015 年 6 月正式发布。

它的目标是使 JavaScript语言可以用于编写复杂的大型应用程序,成为企业级开发语言 。

1.3 前端发展史

  • web1.0时代

    最初的网页以HTML为主,是纯静态的网页。网页是只读的,信息流只能从服务的到客户端单向流通。开发人员
    也只关心页面的样式和内容
    即可 。网页是“只读的”,用户只能搜索信息,浏览信息。Web1.0是由内容驱动的,内容来自于商业机构,服务于消费者;

  • web2.0时代

    1995年,网景工程师Brendan Eich 花了10天时间设计了JavaScript语言。
    1996年,微软发布了JScript,其实是JavaScript的逆向工程实现。
    1997年,为了统一各种不同script脚本语言,ECMA(欧洲计算机制造商协会)以JavaScript为基础,制定了
    ECMAscript 标准规范。JavaScript和JScript都是 ECMAScript 的标准实现者,随后各大浏览器厂商纷纷实现了
    ECMAScript 标准 。

    所以,ECMAScript是浏览器脚本语言的规范,而各种我们熟知的js语言,如JavaScript则是规范的具体实现 。

    之后,ECMAScript就进入了快速发展期。

    1998年6月,ECMAScript 2.0 发布。
    1999年12月,ECMAScript 3.0 发布。这时,ECMAScript 规范本身也相对比较完善和稳定了,但是接下来的事
    情,就比较悲剧了。
    2007年10月。。。。ECMAScript 4.0 草案发布。
    这次的新规范,历时颇久,规范的新内容也有了很多争议。在制定ES4的时候,是分成了两个工作组同时工作:

    ​ a). 一边是以 Adobe, Mozilla, Opera 和 Google为主的 ECMAScript 4 工作组。

    ​ b). 一边是以 Microsoft 和 Yahoo 为主的 ECMAScript 3.1 工作组。

    ECMAScript 4 的很多主张比较激进,改动较大。而 ECMAScript 3.1 则主张小幅更新。最终经过 TC39 的会
    议,决定将一部分不那么激进的改动保留发布为 ECMAScript 3.1,而ES4的内容,则延续到了后来的
    ECMAScript5和6版本中 。

    2009年12月,ECMAScript 5 发布。
    2011年6月,ECMAScript 5.1 发布。
    2015年6月,ECMAScript 6,也就是 ECMAScript 2015 发布了。 并且从 ECMAScript 6 开始,开始采用年号来
    做版本。即 ECMAScript 2015,就是ECMAScript6。
    2016年 6月,小幅修订的《ECMAScript 2016标准》 (简称 ES2016)如期发布, 这个版本可以看作是 ES6.1版,
    因为两者的差异非常小(只新增了数组实例的 includes 方法 和指数运算符),基本上可以认为是同 一个标准 。
    2017 年 6 月发布了ES2017 标准。
    因此, ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版本以后的 JavaScript 的下一代标准,涵盖了 ES2015、
    ES2016、 ES2017、ES2018、ES2019等,而ES2015 则是正式名称,特指当年发布的正式 版本的语言标准

    Web 2.0允许用户自主上传内容,分享内容。

    它的初衷就在于让互联网更加贴近民主,使用户更好的互动

  • Web3.0时代

    Web3.0的一个关键元素是“语义网络”,Web 3.0使得在线应用和网站可以接收到已经在网络上的信息,并将新的信息和数据反馈给用户。比如智能推荐、区块链场景下的应用。

    关于Web3.0的实现存在一些挑战。目前处于概念场景状态。但是从长远来看,人类正在处于互联网变革时代的边缘,相信未来会颠覆现在的互联网格局。

2. ES6新特性

2.1 let和const变量修饰符

我们在JS中定义变量的时候,一直只有一个关键字:var,但是它有个问题,就是定义的变量有时会莫名其妙的成为全局变量。

例如这样一段代码:

1
2
3
4
for(var i = 0; i < 5; i++){
console.log(i); // 一次循环输出0-4
}
console.log(i); // 输出5

我们都知道JS是是弱语言,动态脚本语言,这门语言的作用域是分为全局作用域和全局作用就,并不是C、C++、Java的大括号的作用域。

这块相当于在for循环外面定义了一个i变量,js称为变量提升,只要在同一个作用域,大家都可以用。

上述代码for循环外是全局变量区域,所以这个i是全局变量,虽然在循环外部也可以获取到变量i的值,显然变量i的作用域范围太大了,在做复杂页面时,会带来很大的问题。

所以ES6位我们提供了let变量修饰符,let所声明的变量,只在ket命令所在的代码块内有效。

1
2
3
4
for(let i = 0; i < 5; i++){
console.log(i); // 一次循环输出0-4
}
console.log(i); // 会报错:Uncaught ReferenceError: i is not defined at <anonymous>:1:13

这样,就把变量i的作用域控制在循环范围内了。

const变量其实相当于我们Java的final变量修饰符,一旦定义了,不能被修改。

1
2
const a = 1;  // 定义一个常量a
a = 6; // 如果赋值会报错:Uncaught TypeError: Assignment to constant variable. at <anonymous>:1:3

2.2 字符串扩展

在ES6中,为字符串扩展了几个新的API:

  • includes() :返回布尔值,表示是否找到了参数字符串。
  • startsWith() :返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith() :返回布尔值,表示参数字符串是否在原字符串的尾部。
1
2
3
4
5
6
7
let str = "hello litong";
console.log(str, " 中是否包含了tong => ", str.includes("tong")); // true
console.log(str, " 中是否包含了lisi => ", str.includes("lisi")); // fasle
console.log(str, " 中是否以h开头 => ", str.startsWith("h")); // true
console.log(str, " 中是否以a开头 => ", str.startsWith("a")); // fasle
console.log(str, " 中是否以a结束 => ", str.endsWith("g")); // true
console.log(str, " 中是否以h结束 => ", str.endsWith("h")); // fasle

除此之外,ES6位我们提供了字符串模板标记,比如:

1
2
3
4
5
6
let str = 'litong\n未来很美\n你要加油\n';  // 以前的写法
let words = `
litong
未来很美
你要加油
`; // 现在的写法可以替代以前的写法

2.3 解构表达式

什么是解构? ES6中允许按照一定模式从数组和对象中提取值,然后对变量进行赋值,这被称为解构 (
Destructuring)。

2.3.1 数组解构

假设现有一个数组之前,我想获取其中的值,只能通过角标。ES6可以这样:

1
2
3
4
5
6
7
8
9
let arr = [1,2,3]; // 假设的数组
const [x,y,z] = arr; // x,y,z将与arr中的每个位置对应来取值
console.log(x,y,z); // 打印1 2 3

const [a] = arr; //只匹配1个参数
console.log(a); // 只打印1

const [b,,c] = arr; // 只匹配任意的几个参数
console.log(b,c); // 打印1 3

2.3.2 对象解构

例如有个staff对象:

1
2
3
4
5
const staff = {
name:"litong",
age:28,
skills: ['java', 'js', 'css', 'docker', 'python', 'linux', 'k8s', 'es', 'redis', 'MQ', 'MySQL']
}

我们可以这样赋值:

1
2
3
4
5
6
7
// 解构表达式获取值
const {name,age,skills} = staff;
console.log(name); // 打印 litong
console.log(age); // 打印 28
console.log(skills); // 打印 ["java", "js", "css", "docker", "python", "linux", "k8s", "es", "redis", "MQ", "MySQL"]
const {name:nickname} = staff;
console.log(nickname); // 如过想要用其它变量接收,需要额外指定别名,同样能够打印 litong

2.4 函数优化

在ES6中,对函数的操作做了优化,使得我们在操作函数时更加的便捷。

2.4.1 函数参数值默认值

在ES6以前,我们无法给一个函数参数设置默认值,只能采用变通写法:

1
2
3
4
5
function add(a , b) {
b = b || 1; // 判断b是否为空,为空就给默认值1
return a + b;
}
console.log(add(10));

现在可以这么写:

1
2
3
4
5
// 给b设置默认值
function add(a , b = 1) {
return a + b;
}
console.log(add(10));

2.4.2 箭头函数

ES6中定义函数的简写方式:

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
var print = function (obj) {
console.log(obj);
}
// 一个参数的函数可简写为:
var print2 = obj => console.log(obj);

var sum = function (a , b) {
return a + b;
}
// 多个参数的函数可简写为:
var sum2 = (a,b) => a+b;

// 没有参数时,需要通过()进行占位,代表参数部分
let sayHello = () => console.log("hello!");
sayHello(); // 输出hello!

// 代码不止一行,可以用 {} 括起来
var sum3 = (a,b) => {
b = 30;
return a + b;
}
// 多行,没有返回值
let sayHello = () => {
console.log("hello!");
console.log("world!");
}
sayHello()

2.4.3 对象的函数属性简写

1
2
3
4
5
6
7
8
9
10
11
12
13
let person = {
name: "jack",
// 以前写法:
eat: function (food) {
console.log(this.name + "在吃" + food);
},
// 箭头函数版:
eat2: food => console.log(person.name + "在吃" + food), // 这里拿不到this,就不能this.name
// 简写版:
eat3(food){
console.log(this.name + "在吃" + food);
}
}

2.4.4 箭头函数结合解构表达式

1
2
3
4
5
6
7
8
9
10
11
12
const person = {
name:"jack",
age:21,
language: ['java','js','css']
}
function hello(person) {
console.log("hello," + person.name)
}

// 用箭头函数和解构表达式
var hi = ({name}) => console.log("hello," + name);
hi(person)

2.5 map和reduce

ES6中,数组新增了map和reduce方法。

2.5.1 map()

map() :接收一个函数,将原数组中的所有元素用这个函数处理后放入新数组返回。
举例:有一个字符串数组,我们希望转为int数组。

1
2
3
4
let arr = ['1','20','-5','3'];
console.log(arr); // 打印['1', '20', '-5', '3']
let newArr = arr.map(s => parseInt(s));
console.log(newArr); // 打印[1, 20, -5, 3]

2.5.2 reduce()

reduce() :接收一个函数(必须)和一个初始值(可选),该函数接收两个参数:

  • 第一个参数是上一次reduce处理的结果
  • 第二个参数是数组中要处理的下一个元素

reduce() 会从左到右依次把数组中的元素用reduce处理,并把处理的结果作为下次reduce的第一个参数。如果是
第一次,会把前两个元素作为计算参数,或者把用户指定的初始值作为起始参数 。

1
2
3
4
const arr = [1,20,-5,3]; // 初始化数组
arr.reduce((a, b) => a + b); // 计算结果为19
arr.reduce((a, b) => a * b); // 计算结果为-300
arr.reduce((a, b) => a * b, 0); // 多加个初始值参数,计算结果为0

2.6 扩展运算符

扩展运算符(spread)是三个点(…), 将一个数组转为用逗号分隔的参数序列 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log (...[1, 2, 3]); // 1 2 3
console.log(1, ...[2, 3, 4], 5); // 1 2 3 4 5
function add(x, y) {
return x + y;
}
var numbers = [1, 2];
console.log(add(...numbers)); // 3
// 数组合并
let arr = [...[1,2,3],...[4,5,6]];
console.log(arr); // [1, 2, 3, 4, 5, 6]
// 与解构表达式结合
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first, rest) // 1 [2, 3, 4, 5]
//将字符串转成数组
console.log([...'hello']) // ["h", "e", "l", "l", "o"]

2.7 Promise

2.7.1 引入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
// 需求:封装一个方法,给一个要读取文件的路径,这个方法能帮我读取文件,并把内容返回给我

const fs = require('fs')
const path = require('path')

// 这是普通读取文件的方式,readFile()这个方法是异步方法
fs.readFile(path.join(__dirname, './files/1.txt'), 'utf-8', (err, dataStr) => {
// 思考:把err直接throw,合理吗?不应该是交给调用者去处理吗?
if (err) throw err
console.log(dataStr)
})
// 初衷: 给定文件路径,返回读取到的内容
// 我们可以规定一下, callback 中,有两个参数,第一个参数,是 失败的结果;第二个参数是成功的结果;
// 同时,我们规定了: 如果成功后,返回的结果,应该位于 callback 参数的第二个位置,此时, 第一个位置 由于没有出错,所以,放一个 null; 如果失败了,则 第一个位置放 Error对象,第二个位置防止一个 undefined
function getFileByPath(fpath, callback) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
// 如果报错了,进入if分支后,if后面的代码就没有必要执行了
if (err) return callback(err)
// console.log(dataStr)
// return dataStr
callback(null, dataStr)
})
}

/*
var result = getFileByPath(path.join(__dirname, './files/1.txt'))
console.log(result)
*/
getFileByPath(path.join(__dirname, './files/1.txt'), (err, dataStr) => {
// console.log(dataStr + '-----')
// 调用者处理异常
if (err) return console.log(err.message)
console.log(dataStr)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 需求: 先读取文件1,再读取文件2,最后再读取文件3
// 回调地狱
// 使用 ES6 中的 Promise,来解决 回调地狱的问题;
// 问: Promise 的本质是要干什么的:就是单纯的为了解决回调地狱问题;并不能帮我们减少代码量;
getFileByPath(path.join(__dirname, './files/1.txt'), function (data) {
console.log(data)

getFileByPath(path.join(__dirname, './files/2.txt'), function (data) {
console.log(data)

getFileByPath(path.join(__dirname, './files/3.txt'), function (data) {
console.log(data)
})
})
})
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 1. Promise 是一个 构造函数,既然是构造函数, 那么,我们就可以  new Promise() 得到一个 Promise 的实例;
// 2. 在 Promise 上,有两个函数,分别叫做 resolve(成功之后的回调函数) 和 reject(失败之后的回调函数)
// 3. 在 Promise 构造函数的 Prototype 属性上,有一个 .then() 方法,也就说,只要是 Promise 构造函数创建的实例,都可以访问到 .then() 方法
// 4. Promise 表示一个 异步操作;每当我们 new 一个 Promise 的实例,这个实例,就表示一个具体的异步操作;
// 5. 既然 Promise 创建的实例,是一个异步操作,那么,这个 异步操作的结果,只能有两种状态:
// 5.1 状态1: 异步执行成功了,需要在内部调用 成功的回调函数 resolve 把结果返回给调用者;
// 5.2 状态2: 异步执行失败了,需要在内部调用 失败的回调函数 reject 把结果返回给调用者;
// 5.3 由于 Promise 的实例,是一个异步操作,所以,内部拿到 操作的结果后,无法使用 return 把操作的结果返回给调用者; 这时候,只能使用回调函数的形式,来把 成功 或 失败的结果,返回给调用者;
// 6. 我们可以在 new 出来的 Promise 实例上,调用 .then() 方法,【预先】 为 这个 Promise 异步操作,指定 成功(resolve) 和 失败(reject) 回调函数;


// 注意:这里 new 出来的 promise, 只是代表 【形式上】的一个异步操作;
// 什么是形式上的异步操作:就是说,我们只知道它是一个异步操作,但是做什么具体的异步事情,目前还不清楚
// var promise = new Promise()


// 这是一个具体的异步操作,其中,使用 function 指定一个具体的异步操作
/* var promise = new Promise(function(){
// 这个 function 内部写的就是具体的异步操作!!!
}) */

const fs = require('fs')

// 每当 new 一个 Promise 实例的时候,就会立即 执行这个 异步操作中的代码
// 也就是说,new 的时候,除了能够得到 一个 promise 实例之外,还会立即调用 我们为 Promise 构造函数传递的那个 function,执行这个 function 中的 异步操作代码;
/* var promise = new Promise(function () {
fs.readFile('./files/2.txt', 'utf-8', (err, dataStr) => {
if (err) throw err
console.log(dataStr)
})
}) */


// 初衷: 给路径,返回读取到的内容
function getFileByPath(fpath) {
return new Promise(function (resolve, reject) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {

if (err) return reject(err)
resolve(dataStr)
})
})
}

/* getFileByPath('./files/2.txt')
.then(function (data) {
console.log(data + '-------')
}, function (err) {
console.log(err.message)
}) */

2.7.2 使用Promise

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法
上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样
的方法进行处理。
我们可以通过Promise的构造函数来创建Promise对象,并在内部封装一个异步执行的结果 。

语法:

1
2
3
4
5
6
7
8
const promise = new Promise(function(resolve, reject) {
// ... 执行异步操作
if (/* 异步操作成功 */){
resolve(value);// 调用resolve,代表Promise将返回成功的结果
} else {
reject(error);// 调用reject,代表Promise会返回失败结果
}
});

这样,在promise中就封装了一段异步执行的结果。

如果我们想要等待异步执行完成,做一些事情,我们可以通过promise的then方法来实现,语法:

1
2
3
promise.then(function(value){
// 异步执行成功后的回调
});

如果想要处理promise异步执行失败的事件,还可以跟上catch:

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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p = new Promise(function (resolve, reject) {
// 这里我们用定时任务模拟异步
setTimeout(() => {
const num = Math.random();
// 随机返回成功或失败
if (num < 0.5) {
resolve("成功!num:" + num); // 成功!num:0.4884028656598369
} else {
reject("出错了!num:" + num);
}
}, 300)
});
// 调用promise
p.then(function (msg) {
console.log(msg);
}).catch(function (msg) {
console.log(msg);
})

2.7.3 解决回调地狱

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
const fs = require('fs')

function getFileByPath(fpath) {
return new Promise(function (resolve, reject) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {

if (err) return reject(err)
resolve(dataStr)

})
})
}

// 先读取文件1,在读取2,最后读取3
// 注意: 通过 .then 指定 回调函数的时候,成功的 回调函数,必须传,但是,失败的回调,可以省略不传
// 这是一个 错误的示范,千万不要这么用; 硬是把 法拉利,开成了 拖拉机;
/* getFileByPath('./files/1.txt')
.then(function (data) {
console.log(data)

getFileByPath('./files/2.txt')
.then(function (data) {
console.log(data)

getFileByPath('./files/3.txt')
.then(function (data) {
console.log(data)
})
})
}) */

// 读取文件1
// 在上一个 .then 中,返回一个新的 promise 实例,可以继续用下一个 .then 来处理


// 如果 ,前面的 Promise 执行失败,我们不想让后续的Promise 操作被终止,可以为 每个 promise 指定 失败的回调
/* getFileByPath('./files/11.txt')
.then(function (data) {
console.log(data)

// 读取文件2
return getFileByPath('./files/2.txt')
}, function (err) {
console.log('这是失败的结果:' + err.message)
// return 一个 新的 Promise
return getFileByPath('./files/2.txt')
})
.then(function (data) {
console.log(data)

return getFileByPath('./files/3.txt')
})
.then(function (data) {
console.log(data)
}).then(function (data) {
console.log(data)
}) */

// console.log('OKOKOK')



// 当 我们有这样的需求: 哪怕前面的 Promise 执行失败了,但是,不要影响后续 promise 的正常执行,此时,我们可以单独为 每个 promise,通过 .then 指定一下失败的回调;

// 有时候,我们有这样的需求,个上面的需求刚好相反:如果 后续的Promise 执行,依赖于 前面 Promise 执行的结果,如果前面的失败了,则后面的就没有继续执行下去的意义了,此时,我们想要实现,一旦有报错,则立即终止所有 Promise的执行;

getFileByPath('./files/1.txt')
.then(function (data) {
console.log(data)

// 读取文件2
return getFileByPath('./files/22.txt')
})
.then(function (data) {
console.log(data)

return getFileByPath('./files/3.txt')
})
.then(function (data) {
console.log(data)
})
.catch(function (err) { // catch 的作用: 如果前面有任何的 Promise 执行失败,则立即终止所有 promise 的执行,并 马上进入 catch 去处理 Promise中 抛出的异常;
console.log('这是自己的处理方式:' + err.message)
})

2.7.4 JQuery Ajax 使用Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<input type="button" value="获取数据" id="btn">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
$(function () {
$('#btn').on('click', function () {
$.ajax({
url: 'http://192.168.5.137:9999/data.json',
type: 'get',
dataType: 'json'
}).then(function (data) {
console.log(data)
})
})
});
</script>
</body>

2.8 set和map

ES6提供了Set和Map的数据结构。
Set,本质与数组类似。不同在于Set中只能保存不同元素,如果元素相同会被忽略。和Java中的Set集合非常相似。

1
2
3
4
5
6
7
8
9
10
// Set构造函数可以接收一个数组或空
let set = new Set();
set.add(1);// [1]
let set2 = new Set([2,3,4,5,5]); // 接收数组得到[2,3,4,5]
set.add(1);// 添加
set.clear();// 清空
set.delete(2);// 删除指定元素
set.has(2); // 判断是否存在
set.forEach(function(){})//遍历元素
set.size; // 元素个数。是属性,不是方法

map,本质是与Object类似的结构。不同在于,Object强制规定key只能是字符串。而Map结构的key可以是任意对
象。即:

  • object是 <string,object>集合
  • map是<object,object>集合
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
// map接收一个数组,数组中的元素是键值对数组
const map = new Map([
['key1','value1'],
['key2','value2'],
])
const set = new Set([
['key1','value1'],
['key2','value2'],
])
const map2 = new Map(set) // 或者接收一个set
const map3 = new Map(map); // 或者其它map

map.set(key, value);// 添加
map.clear(); // 清空
map.delete(key);// 删除指定元素
map.has(key); // 判断是否存在
map.forEach(function(key,value){}) // 遍历元素
map.size; // 元素个数。是属性,不是方法
map.values() // 获取value的迭代器
map.keys() // 获取key的迭代器
map.entries() // 获取entry的迭代器

for (let key of map.keys()) { // 遍历
console.log(key);
}
console.log(...map.values()); //通过扩展运算符进行展开

2.9 class的基本语法

JavaScript 语言的传统方法是通过构造函数定义井生成新对象。ES6中引入了class的概念,通过class关键字自定义
类。 和Java定义类的方式类似,基本用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User {  // 定义一个class
constructor(name, age = 20){ // 构造方法
this.name = name; // 添加属性并且赋值
this.age = age;
}
sayHello(){ // 定义方法
return "hello";
}
static isAdult(age){ //静态方法
if(age >= 18){
return "成年人";
}
return "未成年人";
}
}
let user = new User("张三");
// 测试
console.log(user); // User {name: "张三", age: 20}
console.log(user.sayHello()); // hello
console.log(User.isAdult(20)); // 成年人

类的继承:

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
class User {  
constructor(name, age = 20){ // 构造方法
this.name = name; // 添加属性并且赋值
this.age = age;
}
sayHello(){
return "hello"; // 定义方法
}
static isAdult(age){ //静态方法
if(age >= 18){
return "成年人";
}
return "未成年人";
}
}
class ZhangSan extends User { // 类的继承
constructor(){
super("张三", 30); //如果父类中的构造方法有参数,那么子类必须通过super调用父类的构造方法
this.address = "上海"; //设置子类中的属性,位置必须处于super下面
}
}
// 测试
let zs = new ZhangSan();
console.log(zs.name, zs.address);
console.log(zs.sayHello());
console.log(ZhangSan.isAdult(20));

2.10 Generator迭代器

Generator 函数是 ES6 提供的 一种异步编程解决方案,语法行为与传统函数完全不同 。
Generator函数有两个特征:

  • 一是 function命令与函数名之间有一个星号:
  • 二是函数体内部使用 yield吾句定义不同的
    内部状态。
1
2
3
4
5
6
7
8
9
10
function* hello () { 
yield "hello";
yield "world";
return "done";
}
let h = hello();
console.log(h.next()); //{value: "hello", done: false}
console.log(h.next()); //{value: "world", done: false}
console.log(h.next()); //{value: "done", done: true}
console.log(h.next()); //{value: undefined, done: true

可以看到,通过hello()返回的h对象,每调用一次next()方法返回一个对象,该对象包含了value值和done状态。直到
遇到return关键字或者函数执行完毕,这个时候返回的状态为ture,表示已经执行结束了。

1
2
3
4
5
6
7
8
9
function* hello () {
yield "hello";
yield "world";
return "done";
}
let h = hello();
for (let obj of h) {
console.log(obj); // 依次打印hello和world
}

2.11 修饰器

修饰器(Decorator)是一个函数, 用来修改类的行为。 ES2017 引入了这项功能, 目前 Babel 转码器己经支持。 其实这个很像我们Java的注解。我们来看下面例子:

1
2
3
4
5
6
7
8
9
10
11
12
@T //通过@符号进行引用该方法,类似java中的注解
class User {
constructor(name, age = 20){
this.name = name;
this.age = age;
}
}
function T(target) { //定义一个普通的方法
console.log(target); //target对象为修饰的目标对象,这里是User对象
target.country = "中国"; //为User类添加一个静态属性country
}
console.log(User.country); // 理论上打印出country属性值,但实际上报错:Uncaught SyntaxError: Invalid or unexpected token

如果上述代码直接在浏览器运行会报错,原因是,在ES6中,并没有支持该用法,在ES2017中才有,所以我们不能直接运行了,需要进行编码后再运行。
转码的意思是:将ES6或ES2017转为ES5执行。类似这样 :

1
2
3
4
5
6
//转码前
input .map(item =>item + 1);
//转码后
input.map(function (item) {
return item + 1;
})

2.12 转码器

  • Babel (babeljs.io)是一个广为使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而 在浏览器或其他环境执
    行 。
  • Google 公司的 Traceur 转码器 Cgithub.com/google/traceur-compiler),也可以将 ES6 代码转为ES5的代码。

这2款都是非常优秀的转码工具,在这里我不会使用上述两个工具,而是会使用阿里的开源企业级react框架转码工具:UmiJS。 Vue CLI脚架的默认转码器是Babel。

2.12.1 UmiJS转码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 首先需要安装Node.js环境,Node环境自行百度
C:\Users\LT>node -v // 确保node环境正常
v12.13.0
#接下来,开始安装yarn,其中tyarn使用的是npm.taobao.org的源,速度要快一些
#可以把yarn看做了优化了的npm
npm i yarn tyarn -g #-g 是指全局安装
tyarn -v #进行测试,如果能够正常输出版本信息则说明安装成功了
1.21.1 # 安装成功
#如果安装失败,是由于将yarn添加到环境变量中导致
#下面开始安装umi
tyarn global add umi
umi #进行测试,如果找不到umi命令,继续
yarn global bin # 查看umi命令所处的路径,我的路径是:C:\Users\LT\AppData\Local\Yarn\bin,然后将该路径添加到环境变量里就可以了
C:\Users\LT>umi -v
2.12.9 # umi的版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#通过初始化命令将生成package.json文件,它是 NodeJS 约定的用来存放项目的信息和配置等信息的文件。
tyarn init -y
#通过umi命令创建index.js文件
umi g page index #可以看到在pages下创建好了index.js和index.css文件
#将下面内存拷贝到index.js文件中进行测试
@T //通过@符号进行引用该方法,类似java中的注解
class User {
constructor(name, age = 20){
this.name = name;
this.age = age;
}
}
function T(target) { //定义一个普通的方法
console.log(target); //target对象为修饰的目标对象,这里是User对象
target.country = "中国"; //为User类添加一个静态属性country
target.city = "上海"; //为User类添加一个静态属性city
}
console.log(User.country + ' - ' + User.city); //打印出country和city属性值,中国 - 上海
#通过命令行启动umi的后台服务,用于本地开发
umi dev
#通过浏览器进行访问:http://localhost:8000/,查看效果
#值得注意的是,这里访问的是umi的后台服务,不是idea提供的服务

查看转码后的index.js文件,找到自己写的那个代码,也就是对对象包裹了一层,没什么高大上的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var _class;

var //通过@符号进行引用该方法,类似java中的注解
User = T(_class = class User {
constructor(name) {
var age = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 20;
this.name = name;
this.age = age;
}

}) || _class;

function T(target) {
//定义一个普通的方法
console.log(target); //target对象为修饰的目标对象,这里是User对象

target.country = "中国"; //为User类添加一个静态属性country

target.city = "上海"; //为User类添加一个静态属性city
}

console.log(User.country + ' - ' + User.city); //打印出country和city属性值

2.12.2 Babel 转码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
npm install -g babel-cli  # 全局安装babel-cli
npm init # 初始化项目
npm install --save-dev babel-preset-es2015 # 安装es2015 babel规范, 当前你可以安装其他版本的规范
// ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个
npm install --save-dev babel-preset-stage-0
npm install --save-dev babel-preset-stage-1
npm install --save-dev babel-preset-stage-2
npm install --save-dev babel-preset-stage-3

npm install babel-plugin-transform-decorators-legacy -g # es7的装饰器babel转码插件
type nul > .babelrc # Windows创建.babelrc文件,Linux使用touch .babelrc命令创建该文件
# 将下面的json复制到.babelrc文件里
{
"presets": [
"es2015",
"stage-0"
],
"plugins": ["transform-decorators-legacy"]
}
# 文件所在目录 转码后的文件为index-after.js
babel ./index.js -o ./index-after.js

查看index-after.js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"use strict"; // 开启es6的严格模式

var _class;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var User = T(_class = function User(name) {
var age = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 20;

_classCallCheck(this, User);

this.name = name;
this.age = age;
}) || _class;

function T(target) {
//定义一个普通的方法
console.log(target); //target对象为修饰的目标对象,这里是User对象
target.country = "中国"; //为User类添加一个静态属性country
target.city = "上海"; //为User类添加一个静态属性city
}
console.log(User.country + ' - ' + User.city); //打印出country和city属性值,中国 - 上海

2.12.3 在线转码

Babel在线转码网站

2.13 模块化

2.13.1 什么是模块化

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

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

2.13.2 export

比如我定义一个js文件:Util.js,里面有一个Util类 :

1
2
3
4
5
class Util {
static sum = (a, b) => a + b;
}
// 导出该类
export default Util;

2.13.3 import

使用 export 命令定义了模块的对外接口以后,其他 JS 文件就可以通过 import 命令加载这个模块。
例如我要使用上面导出的Util:

1
2
3
4
5
//Index.js
//导入Util类
import Util from './Util'
//使用Util中的sum方法
console.log(Util.sum(1, 2));

2.14 async和await

async/await实际上是ES2017对Generator改进后的语法糖。顾名思义,async关键字代表后面的函数中有异步操作

  • async作为一个关键字放到函数前面
    • 任何一个async函数都会隐式返回一个promise
  • await关键字只能在使用async定义的函数中使用
    • ​ await后面可以直接跟一个 Promise实例对象
    • ​ await函数不能单独使用
  • async/await 让异步代码看起来、表现起来更像同步代码
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
// 1.  async 基础用法
// 1.1 async作为一个关键字放到函数前面
async function queryData() {
// 1.2 await关键字只能在使用async定义的函数中使用 await后面可以直接跟一个 Promise实例对象
var ret = await new Promise(function(resolve, reject){
setTimeout(function(){
resolve('nihao')
},1000);
})
// console.log(ret.data)
return ret;
}
// 1.3 任何一个async函数都会隐式返回一个promise 我们可以使用then 进行链式编程
queryData().then(function(data){
console.log(data)
})

// 2. async 函数处理多个异步函数
axios.defaults.baseURL = 'http://localhost:3000';

async function queryData() {
// 2.1 添加await之后 当前的await 返回结果之后才会执行后面的代码

var info = await axios.get('async1');
// 2.2 让异步代码看起来、表现起来更像同步代码
var ret = await axios.get('async2?info=' + info.data);
return ret.data;
}

queryData().then(function(data){
console.log(data)
})
支付宝打赏 微信打赏

请作者喝杯咖啡吧