ES6 笔记

1. 基本知识

1.1 NRM 与 NPM 简写知识

NPM:node package manager,NRM:npm registry manager(npm 的镜像)

  1. 安装 NRM:npm install -g nrm
  2. nrm ls:可以查看所有源
  3. nrm test / nrm test taobao:测试所有源 / 淘宝源的速度
  4. nrm use taobao:切换到淘宝源
  5. nrm current:查看当前源
  6. nrm add imooc http://192.168.1.100:6666:增加定制源
  7. nrm del imooc:删除源

npm install => npm i
--save => -S
--save-dev => -D
npm init(一路回车默认)=> npm init -y
1
2
3
4

1.2 构建开发环境

安装:npm install -g imooc-es-cli

检查是否安装成功:imooc-es-cli --version / imooc-es-cli -V

查看哪些命令可以使用:imooc-es-cli -h

进行构建:

  1. imooc-es-cli init
  2. 进入该文件夹,输入 npm install
  3. npm run start 启动项目

1.3 let

好处:

  • 不属于顶层对象 window
  • 不允许重复声明
  • 规避了变量提升
  • 拥有暂时性死区
  • 块级作用域

  1. 不属于顶层对象 window

有 var 说明是变量,没 var 说明是 window 的属性

// var -> variable
var a = 5
console.log(a) // 5
delete a
console.log(a) // 5

b = 6
console.log(b) // 6
delete(b)
console.log(b) // uncaught referenceerror

// delete 只能删除属性,不能删除变量
1
2
3
4
5
6
7
8
9
10
11
12

JavaScript 设计败笔(污染全局变量):有 var 没 var 都可以使用 window.a

var a = 5
console.log(window.a) // 5

b = 6
console.log(window.b) // 6
1
2
3
4
5
  1. 不允许重复声明
  2. 规避了变量提升
  3. 拥有暂时性死区
// ok
var a = 5
if(true) {
    a = 6
    var a
}

// error
var a = 5 // 在全局作用域中,故不会影响块级作用域中的 a
if(true) {
    a = 6
    let a
}

/* ------------------分界线----------------- */

// ok
var a = 5
console.log("out ", a) // 5
if(true) {
    console.log("in", a) // 5,下面的 a 类型为 var,故大括号里是全局作用域,故可以提前输出 a
    var a = 6
}

// error
var a = 5
console.log("out ", a) // 5
if(true) {
    console.log("in", a) // error
    let a = 6 // 有了 let,故大括号里是块级作用域,不能提前输出 a 了
}
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
  1. 块级作用域

es5 只有全局作用域和函数作用域;es6 的块级作用域必须要有大括号,否则报错

// var 没有块级作用域(块级作用域:括号内的变量只能在括号内使用)
for (var i = 0; i < 3; i++) {
    console.log("circle in ", i)
}
console.log(i) // 3
1
2
3
4
5
if (false) {
    var a = 5
}
console.log(a) // undefined
1
2
3
4
if (true) let a = 5 // 报错,必须要有大括号
1
// 1.立即执行 3 次 setTimeout 函数(极快完成),每执行一次 setTimeout 函数,都会将其回调函数放入队列中等待,直到主线程(用于执行同步任务,在这里就是 for 循环)结束后再进行执行任务队列里的函数
// 2.由于 for 的括号里的 i 是全局作用域的,故无法影响到回调函数中的 i,只有开始执行任务队列的回调函数时,才能将全局作用域的 i 的值传给函数作用域里的 i,但是这时已经执行了 3 次 setTimeout 函数了,全局作用域的 i 已经变成 3 了
for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i) // 3 3 3
    });
}

// 闭包:内部函数调用外部函数的变量,外部函数的变量就不会释放
for (var i = 0; i < 3; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(j) // 0 1 2
        });
    })(i)
}

// 1.由于 for 括号里的 i 是块级作用域的,故回调函数的 i 与之是同一个。故在立即执行完 3 次 setTimeout 函数后,依次执行任务队列的回调函数
// 2.for 循环中的 let i 有特殊处理。循环的每一次迭代都会得到它自己的 i 的单独副本,这时 es6 的一个特殊特性
for (let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i)
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

1.4 const

好处:

  • 不属于顶层对象 window
  • 不允许重复声明
  • 不存在变量提升
  • 拥有暂时性死区
  • 拥有块级作用域

es5 与 es6 区别:

/* es5 */
Object.defineProperty(window, "PI", {
    value: 3.14,
    writable: false
})
console.log(PI) // 3.14
console.log(window.PI) // 3.14
PI = 2 // error

/* es6 */
const PI = 3.14
PI = 2 // error
1
2
3
4
5
6
7
8
9
10
11
12

const 的一些特点:

const obj = {
    name: "xiecheng",
    age: 34
}
console.log(obj)
obj.school = "imooc"
/* 
    下面能输出 school属性及其值,
    因为 const 定义后的 obj 是不变的,
    而不变的是其引用地址(位于栈内存 stack),而其引用的
    对象位于堆内存(heap),故往其对象中添加 school 是合法的
*/
console.log(obj) 

/* -------分界线------- */

const arr = [1, 2, 3]
arr.push(4)
console.log(arr) // [1, 2, 3, 4],原理如上

/* -------分界线------- */

// 如果在修改对象值前添加 `Object.freeze(obj)`,就可以阻止更改对象内容(浅层冻结)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

1.5 结构赋值

简单数组:

// let arr = [1, 2, 3]
// let a = arr[0]
// let b = arr[1]
// let c = arr[2]

let [a, b, c] = [1, 2, 3]
console.log(a, b, c) // 1 2 3
1
2
3
4
5
6
7

复杂数组:

let [a, b, [c, d]] = [1, 2, [3, 4]]
console.log(a, b, c, d) // 1 2 3 4

let [a, b, [c]] = [1, 2, [3, 4]]
console.log(a, b, c) // 1 2 3

let [a, b, c] = [1, 2, [3, 4]]
console.log(a, b, c) // 1 2 [3, 4]

let [a, b, c, d] = [1, 2, [3, 4]]
console.log(a, b, c, d) // 1 2 [3, 4] undefined

let [a, b, c, d = 5] = [1, 2, [3, 4], 6]
console.log(a, b, c, d) // 1 2 [3, 4] 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14

简单对象:

let user = {
    name: "xiecheng",
    age: 34
}

// let {name, age} = user
// console.log(name, age)

let {age, name} = user
console.log(name, age) // 结果与上同,数组看下标,对象看 key 值
1
2
3
4
5
6
7
8
9
10

复杂对象:

let user = {
    name: "xiecheng",
    age: 34
}

let {age: uage, name: uname} = user
console.log(uage, uname) // ok

/* ----- 分割线 ----- */

function foo() {
    console.log(123)
}
let [a = foo()] = [1] // 不输出,有默认值了
let [a = foo()] = [] // 123
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

字符串:

let str = "imooc"
let [a, b, c, d, e] = str
console.log(a, b, c, d, e) // i m o o c
1
2
3

函数参数解构:

function foo([a, b, c]) {
    console.log(a, b, c) // 1 2 3
}
let arr = [1, 2, 3]
foo(arr)

/* ----- 分割线 ----- */

function foo({name, age, school = "imooc"}) {
    console.log(name, age, school) // xiecheng 34 xxx
}
let obj = {
    name: "xiecheng",
    age: 34,
    school: "xxx"
}
foo(obj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

返回值解构:

function foo() {
    let obj = {
        name: "xiecheng",
        age: 34,
        school: "xxx"
    }
    return obj
}
let {name, age} = foo()
console.log(name, age) // ok
1
2
3
4
5
6
7
8
9
10

JSON 解构:

let json = '{"a": "hello", "b": "world"}'
let {a, b} = JSON.parse(json)
console.log(a, b)
1
2
3

1.6 (es5)数组遍历方式

  • for 循环
  • forEach():没有返回值,知识针对每个元素调用 func
  • map():返回新的 Array,每个元素为调用 func 的结果
  • filter():返回新的符合 func 条件的元素数组
  • some():返回 boolean,判断是否有元素符合 func 条件
  • every():返回 boolean,判断每个元素是否符合 func 条件
  • reduce():接收一个函数作为累加器
let arr = [1, 2, 3]

// for
for(let i = 0; i < arr.length; i++) {
    if(arr[i] === 2) break
    console.log(arr[i])
}

// forEach
arr.forEach((item, index, array) => {
    // if(arr[i] === 2) break // forEach 不支持 break、continue 关键字
    console.log(item, index, array)
})

// map
// 原数组不会更改,返回一个新的数组
let result = arr.map(function(value) {
    value += 1
    console.log("map ", value) // 2 3 4
    return value
})
console.log(arr, result) // 1 2 3  2 3 4

// filter
// 原数组不会更改,返回一个符合条件的新的数组
let result1 = arr.filter(function(value) {
    console.log("filter ", value) // filter 1 filter 2 filter 3
    return value === 2
})
console.log(arr, result1) // 1 2 3  2

// some
// 原数组不会更改,如果有符合条件的则返回 true
let result2 = arr.some(function(value) {
    console.log("some ", value) // 1 2
    return value === 2
})
console.log(result2) // true

// every
// 原数组不会更改,如果每个元素都符合条件的则返回 true
let result3 = arr.every(function(value) {
    console.log("every ", value) // 1
    return value === 2
})
console.log(result3) // false

// reduce
// 本例是求数组每项的和 / 求出较大值 / 去重
// function 四个参数:prev 上一次调用回调的返回值;cur 当前正在处理的元素;index 当前元素索引; arr 原数组
let sum = arr.reduce(function(prev, cur, index, arr) {
    return prev + cur
}, 0) // 累加时的初始值为 0
console.log(sum) // 6

let max = arr.reduce(function(prev, cur) {
    return Math.max(prev, cur)
})
console.log(max) // 3

let array = [1, 2, 2, 3, 4, 4]
let res = array.reduce(function(prev, cur) {
    prev.indexOf(cur) === -1 && prev.push(cur)
    return prev
}, [])
console.log(res) // 1 2 3 4

// for index in arr
let arr1 = [1, 2, 3]
Array.prototype.foo = function() {
    console.log("foo")
}
for(let index in arr1) {
    console.log(index, arr1[index]) // 会打印 foo,不建议用它遍历
}
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

1.7 (es6)数组遍历方式

  • find():返回第一个通过测试的元素
  • findIndex():返回的值为该通过第一个元素的索引
  • for of:
  • values():
  • keys():
  • entries():
let arr = [3, 2, 2, 3, 2, 1]

// find
let res = arr.find(function(value) {
    return value === 1
})
console.log(arr, res) // 3 2 2 3 2 1  1

// findIndex
let res1 = arr.findIndex(function(value) {
    return value === 1
})
console.log(arr, res1) // 3 2 2 3 2 1  5

let obj = {
    name: "zhangsan",
    age: 14
}

// for of
for(let item of arr.keys()) {
    console.log(item) // 0 1 2 3 4 5
}

for(let item of arr.values()) {
    console.log(item) // 3 2 2 3 2 1
}

for(let [index, item] of arr.entries()) {
    console.log(index, item) // 0 3 1 2 2 2 3 3 4 2 5 1
}
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

1.8 作业:默认参数带来的单独作用域

// c 有默认值的情况
function side1(arr) {
    arr[0] = arr[2]
}
function a(a, b, c = 3) {
    c = 10
    side1(arguments)
    return a + b + c
}
console.log(a(1, 1, 1)) // 12


// c 没有默认值的情况
function side2(arr) {
    arr[0] = arr[2]
}
function b(a, b, c) {
    c = 10
    side2(arguments)
    return a + b + c
}
console.log(b(1, 1, 1)) // 21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

**解题关键:**一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的

**解题思路:**c 变量没有设置默认值时,没有形成作用域,side1 函数可以影响数值,arguments 为 1, 1, 10,side1 函数执行后 a, b, c 为 10, 1, 10,函数 b 返回为 21;设置了默认值,参数有自己的作用域,arguments 为 1, 1, 1,a, b, c 为 1, 1, 10,函数 a 返回 12


1.9 作业:如何判断是否是数组

let arr = [1, 2, 3]
console.log(arr instanceof Array) // true
console.log(Array.isArray(arr)) // true
1
2
3

1.10 数组的扩展

  • 类数组 / 伪数组

  • Array.from()

  • Array.of()

  • copyWithin()

  • fill()

  • includes()

  1. 伪数组举例与使用 es5 方法转换为真正数组:
let divs = document.getElementsByTagName("div")
console.log(divs) // HTMLCollection []

let divs2 = document.getElementsByClassName("xx")
console.log(divs2) // HTMLCollection []

let divs3 = document.querySelectorAll(".xx")
console.log(divs3 instanceof Array) // NodeList []
// divs3.push(123) // error

// slice 将伪数组转换为真正的数组
let arr = Array.prototype.slice.call(divs3)
console.log(arr)
arr.push(123)
console.log(arr)

function foo() {
    console.log(arguments instanceof Array)
    let argument = Array.prototype.slice.call(arguments)
    console.log(argument)
}
foo(1, "imooc", true)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. Array.from:es6 将伪数组转换为真正数组
let arrayLike = {
    0: "es6",
    1: "es7",
    2: "es8",
    length: 3
}
let arr = Array.from(arrayLike)
console.log(arr) // es6 es7 es8
1
2
3
4
5
6
7
8
  1. Array.of:生成长度为 1 的有值数组
let arr1 = new Array(1, 2)
console.log(arr1) // [1, 2]

let arr2 = new Array(3)
console.log(arr2) // 长度为 3 的空数组

// 利用 Array.of 方法可以定义长度为 1 且值为 12 的数组
let arr3 = Array.of(12)
console.log(arr3)
1
2
3
4
5
6
7
8
9
  1. Array.of:将不同类型的值拼接成数组
let arr = Array.of(1, true, "imooc", [1, 2, 3])
console.log(arr) // [1, true, 'imooc', Array(3)]
1
2
  1. copyWithin:用数组中的任意值替换其中的任意值
let arr = [1, 2, 3, 4, 5]
// copyWithin 三个参数:被替换的下标 替换开始的下标 替换结束的下标(若没有则为最后一个元素的下标)
console.log(arr.copyWithin(1, 3)) // 1 4 5 4 5
1
2
3
  1. fill:填充数组
let arr = new Array(3).fill(7)
console.log(arr) // 777

let arr1 = [1, 2, 3, 4, 5]
// 参数包括开头,不包括结尾(1,2)
arr1.fill("imooc", 1, 3)
console.log(arr1) // [1, imooc, imooc, 4, 5]

let arr2 = [1, 2, 3, 4, 5]
arr2.fill(0)
console.log(arr2) // [0, 0, 0, 0, 0]
1
2
3
4
5
6
7
8
9
10
11
  1. includes:判断某值(含 NaN)是否在数组中去
// es5
let arr = [1, 2, 3, 5, NaN]
console.log(arr.indexOf(5)) // 3
console.log(arr.indexOf(NaN)) // -1,说明没找到 NaN
console.log(arr.includes(NaN)) // true 找到了 NaN
1
2
3
4
5

1.11 函数的参数

参数默认值:es5 写法:

function foo(x, y) {
    y = y || "world"
    console.log(x, y)
}
foo("hello") // hello world
foo("hello", "imooc") // hello imooc
foo("hello", 0) // hello world(应该为 hello 0)
1
2
3
4
5
6
7

参数默认值:es6 写法:

function foo(x, y = "world") {
    console.log(x, y)
}
foo("hello") // hello world
foo("hello", "imooc") // hello imooc
foo("hello", 0) // hello 0
1
2
3
4
5
6

解构传参

function foo1({x, y = 5}) {
    console.log(x, y)
}

foo1({
    x: 1
}) // 1 5

// 解构赋值
foo1({
    x: 1,
    y: 2
}) // 1 2
1
2
3
4
5
6
7
8
9
10
11
12
13

参数默认值

function ajax(url, {
    body = "",
    method = "GET",
    headers = {}
} = {}) {
    console.log(method)
}

ajax("https://www.baidu.com") // GET
ajax("https://www.baidu.com", {
    method: "POST"
}) // POST
1
2
3
4
5
6
7
8
9
10
11
12

length 作用

function foo(x, y = 1, z = 8) {
    console.log(x, y)
}

// length 能获得没有指定默认值的参数的个数
console.log(foo.length) // 1
1
2
3
4
5
6

参数的作用域

函数参数区域形成一个作用域,故 y 取参数 x 的值

let x = 1
function foo(x, y = x) {
    console.log(y)
}
foo(2) // 2
1
2
3
4
5

函数参数区的作用域没有找到 x 的值,故沿着作用域链在外面找到了全局变量 x = 1

let x = 1
function foo(y = x) {
    let x = 2
    console.log(y)
}
foo() // 1
1
2
3
4
5
6

函数参数区的作用域没有找到 x 的值,沿着作用域链没有找到对应的 x,故报错

function foo(y = x) {
    let x = 2
    console.log(y)
}
foo() // error
1
2
3
4
5

函数的 name

如何获得函数名?

function foo() {}
console.log(foo.name) // foo

console.log((new Function).name) // anonymous(匿名的意思)
1
2
3
4

bind 怎么用?

function foo(x, y) {
    console.log(this, x, y)
}

foo.bind({name: "xiaoming"})(1, 2) // {name: 'xiaoming'} 1 2
1
2
3
4
5
function foo(x, y) {
    console.log(this, x, y)
}

console.log(foo.bind({}).name) // bound foo
console.log((function(){}).bind({}).name) // bound
1
2
3
4
5
6

1.12 拓展运算符与 rest 参数

...写在等号左边或形参上面,为 rest 参数;等号右边或实参上,为拓展运算符

  • ...

  • 拓展运算符:把数组或者类数组展开成用逗号隔开的值

  • rest 参数:把都好隔开的值组合成一个数组

  1. 将数组解构为参数:
// 扩展运算符:将数组的值变成用逗号隔开的值
function foo(a, b, c) {
    console.log(a, b, c) // 1 2 3
}
let arr = [1, 2, 3]
foo(...arr)
1
2
3
4
5
6
  1. 合并数组(es5):
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
Array.prototype.push.apply(arr1, arr2)
console.log(arr1)
1
2
3
4
  1. 合并数组(es6):
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
arr1.push(...arr2)
console.log(arr1) // [1, 2, 3, 4, 5, 6]
1
2
3
4
  1. 将字符串的每个字符放进数组中:
let str = "imooc"
let arr = [...str]
console.log(arr) // ['i', 'm', 'o', 'o', 'c']
1
2
3
  1. 不定参数求和(es5):
function foo(x, y, z) {
    // console.log(arguments)
    let sum = 0
    Array.prototype.forEach.call(arguments, function(item) {
        sum += item
    })
    return sum
}
console.log(foo(1, 2, 3)) // 6
console.log(foo(1, 2)) // 3
1
2
3
4
5
6
7
8
9
10
  1. 不定参数求和(es6):

方法一:

function foo(x, y, z) {
    let sum = 0
    Array.from(arguments).forEach(item => {
        sum += item
    })
    return sum
}

console.log(foo(1, 11)) // 12
console.log(foo(1, 2, 3)) // 6
1
2
3
4
5
6
7
8
9
10

方法二:剩余运算符

// 对于不定参数,用剩余运算符
function foo(...args) {
    console.log(args)
    let sum = 0
    args.forEach(item => {
        sum += item
    })
    return sum
}
console.log(foo(1, 2)) // 3
console.log(foo(1, 2, 3)) // 6
1
2
3
4
5
6
7
8
9
10
11
function foo1(x, ...args) {
    console.log(x) // 1
    console.log(args) // [2, 3]
}
foo1(1, 2, 3)
1
2
3
4
5
let [x, ...y] = [1, 2, 3]
console.log(x) // 1
console.log(y) // [2, 3]
1
2
3

1.13 箭头函数

基本形式:

// let sum = (x, y) => {
//     return x + y
// }
let sum = (x, y) => x + y
1
2
3
4

注意事项:

  • this 指向定义时所在的对象,而不是调用时所在的对象
  • 不可以当做构造函数
  • 不可以使用 arguments 对象

es5 里函数的 this:

let oBtn = document.querySelector("#btn")
oBtn.addEventListener("click", function() {
    // setTimeout 是 window 下的方法,而当前对象指向 window
    setTimeout(function() { 
        console.log(this) 
        // 只有 call apply bind 才能改 this 指向
        console.log(this) // 指向了 window
    }.bind(this), 1000); // 这里的 this 同 console.log 里的 this,都是指向 oBtn
})
// 只有 bind 才能延迟执行,其他两个方法都是立即执行
1
2
3
4
5
6
7
8
9
10

es6 里的 this:(箭头函数里没有 this,需要去上面的上下文去寻找)

let oBtn = document.querySelector("#btn")
oBtn.addEventListener("click", function() {
    console.log(this)
    setTimeout(() => {
        console.log(this) // button
    }, 1000)
})
1
2
3
4
5
6
7

虽然箭头函数不支持 arguments 对象,但是可以这样代替:

let foo = (...args) => {
    console.log(args)
}
foo(1, 2, 3)
1
2
3
4

1.14 对象的扩展

  • 属性简洁表示法

  • 属性名表达式

  • Object.js()

  • 扩展运算符与 Object.assign()

  • in

  • 对象的遍历方式

  1. 对象的键值对中,key 是变量:
let name = "yuanxinyue"
let age = 34
let s = "school"

let obj = {
    name,
    age,
    [s]: "imooc" // 当 key 是变量时,要加上中括号 []
}

console.log(obj)
1
2
3
4
5
6
7
8
9
10
11
  1. 对象中的函数简写:
let name = "yuanxinyue"
let age = 34
let s = "school"

let obj = {
    name,
    age,
    [s]: "imooc", // 当 key 是变量时,要加上中括号 []

    // 不能用箭头函数,因为 this 不是指向调用的对象,而是定义的对象(window)
    // study: () => {
    //     console.log(this.name + "正在学习") 
    // }

    // 可以这样写但没必要
    // study: function() {
    //     console.log(this.name + "正在学习")
    // }

    // es6 中,在对象中的函数用这种形式最好了
    study() {
        console.log(this.name + "正在学习")
    }
}

obj.study()
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
  1. Object.is():
console.log(Object.is(2, "2")) // false
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(+0, -0)) // true

let obj1 = {
    name: "yuanxinyue",
    age: 20
}
let obj2 = {
    name: "yuanxinyue",
    age: 20
}

console.log(Object.is(obj1, obj2)) // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. 扩展运算符:(实现对象复制)

方法一:

let x = {
    a: 3,
    b: 4
}
let y = {...x}
console.log(y) // {a: 3, b: 4}
1
2
3
4
5
6

方法二:

let x = {
    a: 3,
    b: 4
}
let y = {}
Object.assign(y, x)
console.log(y) // {a: 3, b: 4}

let x1 = {
    a: 3,
    b: 4
}
let y1 = {
    c: 5,
    a: 6
}
Object.assign(y1, x1)
console.log(y1) // {a: 3, b: 4, c: 5}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. in 关键字
let x = {
    a: 3,
    b: 4
}
let y = {
    c: 5,
    a: 6
}
console.log("a" in x) // true,判断是否有某 key 值

let arr = [14, 30, 53, 22]
console.log(2 in arr) // true,判断位置是否有值
1
2
3
4
5
6
7
8
9
10
11
12
  1. 对象的 4 种遍历方法
let obj = {
    name: "yuanxinyue",
    age: 34,
    school: "imooc"
}
// 四种对象遍历方式
for(let key in obj) {
    console.log(key, obj[key])
}
Object.keys(obj).forEach(key => {
    console.log(key, obj[key])
})
Object.getOwnPropertyNames(obj).forEach(key => {
    console.log(key, obj[key])
})
Reflect.ownKeys(obj).forEach(key => {
    console.log(key, obj[key])
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

1.15 深拷贝与浅拷贝

  1. Object.assign 的问题:
let target = {
    a: {
        b: {
            c: 1
        },
        e: 4,
        f: 5,
        g: 6
    }
}
let sourse = {
    a: {
        b: {
            c: 1
        },
        e: 2,
        f: 3
    }
}
Object.assign(target, sourse)
console.log(target) // 没有 g,g 消失了!
// 基本类型随便拷贝,但是引用类型的拷贝就总是出问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 深拷贝引入:
// 将 JSON 字符串转成 JSON 对象
let obj = JSON.parse('{"a": "hello", "b": "world"}')
console.log(obj, typeof(obj)) // {a: 'hello', b: 'world'} 'object'
let str = JSON.stringify(obj)
console.log(str, typeof(str)) // {"a":"hello","b":"world"} string
1
2
3
4
5
  1. 对象的深拷贝:
let obj1 = {
    name: "yuanxinyue",
    age: 20
}

let str = JSON.stringify(obj1)
let obj2 = JSON.parse(str)
obj1.age = 18
console.log(obj2) // 20
1
2
3
4
5
6
7
8
9
  1. 对象的深拷贝
// 检查类型
// 判断数据类型
let checkType = data => {
    return (Object.prototype.toString.call(data).slice(8, -1))
}

let deepClone = target => {
    let targetType = checkType(target)
    let result
    if (targetType === "Object") {
        result = {}
    } else if (targetType === "Array") {
        result = []
    } else {
        return target
    }
    for (let i in target) {
        let value = target[i]
        let valueType = checkType(value)
        if (valueType === "Object" || valueType === "Array") {
            result[i] = deepClone(value) // 递归
        } else {
            result[i] = value
        }
    }
    return result
}

let arr1 = [1, 2, {age: 18}]
let arr2 = deepClone(arr1)
arr2[2].age = 34
console.log(arr1) // age 没有变成 34,故为深拷贝
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

2. 面向对象

2.1 es5 中的类

1. 构建类:

People.prototype 是 dog 原型,dog 的原型对象 __proto__ 指向 People.prototype

People.prototype 是由原型构造函数所创建的对象的原型

// 类
function People(name, age) {
    // console.log(this)
    this.name = name,
    this.age = age
}

People.prototype.showName = function() {
    console.log("我的名字是" + this.name)
}

let p1 = new People("xiecheng", 34)
console.log(p1)
p1.showName()

let p2 = new People("zhangsan", 20)
console.log(p2)
p2.showName()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

2. 静态方法举例

let str = new String("imooc")
console.log(str)

let arr = new Array(1, 2, 3)
console.log(arr)

let obj = new Object()
obj.name = "xiecheng"
console.log(obj)

// 静态方法,不用 new 一个 Math 就可以调用其方法
console.log(Math.max(4, 5))
console.log(Math.random())
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 构建静态方法
function People(name, age) {
    this.name = name
    this.age = age
    People.count++
}

// 静态属性
People.count = 0
// 静态方法
People.getCount = function() {
    console.log(this.age) // undefined
    console.log("当前共有" + People.count + "个人")
}

// 实例方法
People.prototype.showName = function() {
    console.log("我的名字是" + this.name)
}

let p1 = new People("yuanxinyue", 21)
let p2 = new People("zhangsan", 30)

console.log(People.count)
People.getCount()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

4. 类的继承

// 父类,构造函数
function Animal(name) {
  this.name = name
}
// Animal.prototype 是一个指针,指向的是原型对象。Animal 的实例对象的内部指针指向原型对象
// 原型对象的 constructor 指向构造函数,即 Animal.prototype.constructor = Animal
Animal.prototype.showName = function() {
  console.log("名字是" + this.name)
}

// 子类,构造函数
function Dog(name, color) {
  // 第一个参数是 Dog 里面的 this
  Animal.call(this, name) // 继承属性
  this.color = color
}

/*
	1.由于构造函数的 prototype 作为指针,指向原型对象。而实例对象是实例化构造函数得到的,且实例对象的内部指针指向了该原型对象,所以实例对象能访问与使用原型对象的属性和方法
	2.上面翻译:构造函数 Dog 的 prototype 作为指针,指向原型对象。该构造函数的实例对象 dog(假设) 可以访问原型对象的所有属性与方法
	3.下面的表达式中,实例对象 dog 是可以使用构造函数 Dog 的原型对象上的属性和方法的,但是 Dog 的原型对象却作为了 Animal 构造函数的实例对象,则 Dog 的原型对象(在这里作为了 Animal 的实例对象)可以访问 Animal 的原型对象上的方法。那么实例对象 dog 是可以通过原型链使用 Animal.prototype(Animal 原型对象)上的方法
*/
Dog.prototype = new Animal()

/*
	1.对于构造函数对象 Dog,Dog.prototype 指向了原型对象,其自带属性 constructor 又指回了 Dog 这个构造函数对象(Dog.prototype.constructor = Dog)
	2.每一个 prototype 对象都包含了它的构造函数对象,而在上面的代码中,Dog.prototype 是作为 Animal 构造函数的实例对象的,故其的 constructor 指向的是 Animal 这个构造函数对象,故需要手动改回,否则会引起原型链的紊乱
*/
Dog.prototype.constructor = Dog

let d1 = new Dog("wangcai", "white")
console.log(d1)
d1.showName()
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

2.2 es6 中的类

  1. 构建类
class People {
    constructor(name, age) {
        this.name = name,
        this.age = age
    }
    showName() {
        console.log(this.name)
    }
}

let p1 = new People("yuanxinyue", 21)
console.log(p1) // People {name: 'yuanxinyue', age: 21}
1
2
3
4
5
6
7
8
9
10
11
12
  1. 继承
class People {
    constructor(name, age) {
        this.name = name,
        this.age = age
    }
    showName() {
        console.log(this.name)
    }
}

class Coder extends People {
    constructor(name, age, company) {
        super(name, age)
        this.company = company
    }
    showCompany() {
        console.log(this.company)
    }
}

let c1 = new Coder("zhangsan", 25, "beijing")
console.log(c1) // Coder {name: 'zhangsan', age: 25, company: 'beijing'}
c1.showName() // zhangsan
c1.showCompany() // beijing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  1. 修改构建类的值:(子类也可以继承)
class People {
    constructor(name, age) {
        this.name = name,
        this.age = age,
        this._sex = -1
    }
    get sex() { // 属性
        if (this._sex === 1) {
            return "male"
        } else if (this._sex === 0) {
            return "female"
        } else {
            return this._sex
        }
    }
    set sex(val) { // 1: male | 0: female
        if (val === 0 || val === 1) {
            this._sex = val
        }
    }
    showName() {
        console.log(this.name)
    }
}

let p1 = new People("xiaoming", 20)
console.log(p1.sex) // -1
p1.sex = 1
console.log(p1.sex) // male

// 子类可以使用 c1.sex = 0 console.log(c1.sex) // female
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
  1. 静态方法与属性
class People {
    constructor(name, age) {
        this.name = name,
        this.age = age,
        this._sex = -1
    }

    // 静态属性
    static count = 99

    get sex() { // 属性
        if (this._sex === 1) {
            return "male"
        } else if (this._sex === 0) {
            return "female"
        } else {
            return this._sex
        }
    }

    set sex(val) { // 1: male | 0: female
        if (val === 0 || val === 1) {
            this._sex = val
        }
    }

    showName() {
        console.log(this.name)
    }

    // 静态方法
    static getCount() {
        return 5
    }
}

// // 静态属性
// People.count = 99

console.log(People.getCount()) // 5
console.log(People.count) // 9

class Coder extends People {
    constructor(name, age, company) {
        super(name, age)
        this.company = company
    }
    showCompany() {
        console.log(this.company)
    }
}

console.log(Coder.getCount()) // 5
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

2.3 新数据类型 - Symbol

表示独一无二的

  1. es5 中的数据类型:
    • null
    • undefined
    • boolean
    • string
    • number
    • object
  2. Symbol简单引入
// let s1 = Symbol()
// let s2 = Symbol()
// console.log(s1)
// console.log(s2)
// console.log(s1 === s2) // false

let s1 = Symbol("foo")
let s2 = Symbol("bar")
console.log(s1) // Symbol(foo)
console.log(s2) // Symbol(bar)
console.log(s1 === s2) // false

const obj = {
    name: "imooc",
    toString() { // 我是标记
        return this.name
    }
}
let s = Symbol(obj)
// 如果参数为 obj,会自动调用 toString 方法将之变成字符串
console.log(s) // Symbol([object Object]),如果加上上面的标记,就变成 Symbol(imooc)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  1. Symbol.for:(全局)
let s = Symbol()
s.name = "imooc"
// 由于不是对象,所以不能为之添加属性
console.log(s) // Symbol()
console.log(s.description) // undefined

let s1 = Symbol("foo")
console.log(s1.description) // foo

let s2 = Symbol.for("foo")
console.log(s2.description) // foo

let s3 = Symbol.for("foo")
console.log(s2 === s3) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. Symbol.keyFor:查找全局 Symbol 变量
// 创建一个全局 Symbol
var globalSym = Symbol.for("foo");
console.log(Symbol.keyFor(globalSym))  // foo

var localSym = Symbol();
console.log(Symbol.keyFor(localSym)) // undefined

// 以下 Symbol 不是保存在全局 Symbol 注册表中
console.log(Symbol.keyFor(Symbol.iterator)) // undefined
1
2
3
4
5
6
7
8
9
  1. 用 Symbol 保存 key 值
const grade = {
    张三: {address: "xxx", tel: "111"},
    李四: {address: "yyy", tel: "222"},
    李四: {address: "zzz", tel: "333"},
}
console.log(grade) // {张三: {…}, 李四: {…}}

// 失败的改进版:仍旧是老问题
const stu1 = "李四"
const stu2 = "李四"
const grade1 = {
    [stu1]: {address: "yyy", tel: "222"},
    [stu2]: {address: "zzz", tel: "333"}
}
console.log(grade1) // {李四: {…}}

// 改进版:
const stu3 = Symbol("李四")
const stu4 = Symbol("李四")
const grade2 = {
    [stu3]: {address: "yyy", tel: "222"},
    [stu4]: {address: "zzz", tel: "333"}
}
console.log(grade2) // {Symbol(李四): {…}, Symbol(李四): {…}}
console.log(grade2[stu3]) // {address: 'yyy', tel: '222'}
console.log(grade2[stu4]) // {address: 'zzz', tel: '333'}
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
  1. 遍历对象里的 Symbol
const sym = Symbol("imooc")
class User {
    constructor(name) {
        console.log(this)
        this.name = name
        this[sym] = "imooc.com"
    }
    getName() {
        console.log(this)
        return this.name + this[sym]
    }
}

const user = new User("xiecheng")
console.log(user.getName())

// 对象的遍历方法
// 使用 for in 无法遍历到 Symbol 属性
for(let key in user) {
    console.log(key)
}

// 数组的遍历方法
for(let key of Object.keys(user)) {
    console.log(key)
}

// 只能取到 Symbol
for(let key of Object.getOwnPropertySymbols(user)) {
    console.log(key)
}

// name 和 Symbol(imooc) 都能取到了
for(let key of Reflect.ownKeys(user)) {
    console.log(key)
}
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
  1. 消除魔术字符串

一、反例

function getArea(shape) {
    let area = 0
    switch (shape) {
        case "Triangle": // 这里一个 Triangle
            area = 1    
            break;
        case "Circle":
            area = 2
    }
    return area
}
console.log(getArea("Triangle")) // 这里又一个 Triangle,耦合度太高
1
2
3
4
5
6
7
8
9
10
11
12

二、正例

const shapeType = {
    // triangle: "Triangle",
    // circle: "Circle"
    triangle: Symbol(), // 能保证 triangle 是独一无二的
    circle: Symbol()
}
function getArea(shape) {
    let area = 0
    switch (shape) {
        case shapeType.triangle:
            area = 1    
            break;
        case shapeType.circle:
            area = 2
    }
    return area
}
console.log(getArea(shapeType.triangle)) // 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

2.4 新数据结构 - Set

  • 一种新的数据结构

  • 常用方法

  • 遍历

  • 应用场景

  • WeakSet

  1. 增删
let s = new Set([1, 2, 3, 2])

// 1.添加某值
s.add("imooc").add("es6") // 链式操作
console.log(s) // Set(5) {1, 2, 3, 'imooc', 'es6'}

// 2.删除某值
s.delete(2)
console.log(s)

// 3.清空某值
// s.clear()
// console.log(s)

console.log(s.has("imooc"))
console.log(s.size)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. 数组遍历
let s = new Set([1, 2, 3, 2])
s.add("imooc").add("es6")

// 遍历
// s.forEach(item => console.log(item))

// for(let key of s) {
//     console.log(key)
// }

// for(let item of s.keys()) {
//     console.log(item)
// }

// for(let item of s.values()) {
//     console.log(item)
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. 去重
// 去重
let arr = [1, 2, 3, 4, 2, 3]
let s = new Set(arr)
console.log([...s]) // [1, 2, 3, 4]

// 合并去重
let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]
let s1 = new Set([...arr1, ...arr2])
// console.log(Array.from(s))
console.log([...s1]) // [1, 2, 3, 4, 5, 6]
1
2
3
4
5
6
7
8
9
10
11
  1. 交集
// 交集
let arr1 = [1, 2, 3, 4, 5]
let arr2 = [3, 4, 5, 6, 7]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
let result = new Set(arr1.filter(item => s2.has(item)))
csole.log(Array.from(result)) // [3, 4, 5]
1
2
3
4
5
6
7
  1. 差集
// 差集
let arr1 = [1, 2, 3, 4, 5]
let arr2 = [3, 4, 5, 6, 7]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
let arr3 = new Set(arr1.filter(item => !s2.has(item)))
let arr4 = new Set(arr2.filter(item => !s1.has(item)))
// console.log(arr3) // [1, 2]
// console.log(arr4) // [6, 7]
console.log([...arr3, ...arr4]) // [1, 2, 6, 7]
1
2
3
4
5
6
7
8
9
10
  1. WeakSet
// 1.只能存放对象
// 2.不能使用数组的遍历方法
// 3.为弱引用。可以临时存储对象
let ws = new WeakSet()
const obj1 = {
    name: "imooc"
}
const obj2 = {
    age: 5
}
ws.add(obj1)
ws.add(obj2)
ws.delete(obj1)
console.log(ws.has(obj2)) // true

// 垃圾回收机制 GC +1 +1
// weakset 弱引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

2.5 新数据结构 - Map

与对象相比,map 对于频繁地增删改键值对更为简单且性能更好(size 可以看到有几个键值对、能轻易判断是否有 key 值等)

  • 一种新的数据结构

  • 常用方法

  • 遍历

  • 应用场景

  • WeakMap

  1. 增删改
let m = new Map()
let obj = {
    name: "imooc"
}
// 设置键值对,key 为 obj,值为 es
m.set(obj, "es")

// 删除键值对
m.delete(obj)

// 查询是否有 key
console.log(m.has(obj))

// 得到 key 的 value 值
console.log(m.get(obj))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 初始化
let map = new Map([
    ["name", "imooc"],
    ["age", 5]
])
console.log(map) // Map(2) {'name' => 'imooc', 'age' => 5}
console.log(map.size) // 2
console.log(map.has("name")) // true
console.log(map.get("name")) // imooc
map.set("name", "zhangsan")
console.log(map) // Map(2) {'name' => 'zhangsan', 'age' => 5}
1
2
3
4
5
6
7
8
9
10
  1. 遍历
let map = new Map([
    ["name", "imooc"],
    ["age", 5]
])

// 遍历
map.forEach((value, key) => {
    console.log(value, key)
    /*
        imooc name
        5 "age"
    */
})

for(let [key, value] of map) {
    console.log(key, value)
}

for(let key of map.keys()) {
    console.log(key)
}

for(let value of map.values()) {
    console.log(value)
}

for(let [key, value] of map.entries()) {
    console.log(key, value)
}
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
  1. WeakMap
// weakmap
// 1.key 必须为引用类型
// 2.不支持 clear()、size 方法
// 3.不支持遍历
// 4.弱引用,若被回收了,其对应键值也会被回收
let wm = new WeakMap()
wm.set([1], 2)
wm.set({
    name: "imooc"
}, "es")
console.log(wm)
1
2
3
4
5
6
7
8
9
10
11
let wm = new WeakMap()
let elem = document.getElementsByTagName("h1")
wm.set(elem, "info")
console.log(wm.get(elem))
1
2
3
4

2.6 字符串的拓展

  1. unicode 编码拓展
// unicode
// es6 \uxxxx 码点 0000~ffff
// \u20BB7 -> \u20BB+7
// \u{20BB7} 表示码点范围的 unicode 码
// 又例如 \u{41} -> A

console.log("\z" === "z") // true
// \HHH:八进制
console.log("\172" === "z") // true
// \xHH:十六进制
console.log("\x7A" === "z") // true
// unicode
console.log("\u007A" === "z") // true
console.log("\u{7A}" === "z") // true

for(let item of "imooc") {
    console.log(item) // i m o o c
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. 模板字符串

一、反例:

// 没有使用模板字符串,又要 + 又要括号的,很麻烦
const str1 = "节点覅将等分iOS的简欧风景的\n"
+ "搜if谨慎地偶家佛is点击覅就打死偶就佛is的金\n"
+ "佛is的减肥is的窘境发"
console.log(str1)

const a = 20
const b = 14
const c = "ES"
const str2 = "我的年龄是:" + (a + b) + ",我在讲" + c
console.log(str2)
1
2
3
4
5
6
7
8
9
10
11

二、正例:

// 嵌套模板
const isLargeScreen = () => {
    return true
}
let class1 = "icon"
class1 += isLargeScreen() ? " icon-big" : " icon-small"
console.log(class1) // icon icon-big

const class2 = `icon icon-${isLargeScreen() ? "big" : "small"}`
console.log(class2) // icon icon-big
1
2
3
4
5
6
7
8
9
10
  1. 带标签的模板字符串
// 带标签的模板字符串
const foo = (a, b, c, d) => {
    console.log(a)
    console.log(b)
    console.log(c)
    console.log(d)
}
// foo(1, 2, 3, 4)
const name = "xinyue"
const age = 20
foo`这是${name},他的年龄是${age}${123}`

/*
    输出:
    ['这是', ',他的年龄是', '岁', '', raw: Array(4)]
    xinyue
    20
    123
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

2.7 字符串的一些方法

  • String.fromCodePoint()
  • String.prototype.includes()
  • String.prototype.startsWith()
  • String.prototype.endsWith()
  • String.prototype.repeat()
console.log(String.fromCharCode(0x20BB7)) // es5,识别不出 码点超过 ffff 的字符
console.log(String.fromCodePoint(0x20BB7)) // es6 识别的了

const str = "imooc"
console.log(str.indexOf("ooc")) // 2
console.log(str.includes("mo1")) // false
console.log(str.startsWith("im")) // true
console.log(str.endsWith("im")) // false
const newStr = str.repeat(10)
console.log(newStr) // imoocimoocimoocimoocimoocimoocimoocimoocimoocimooc
1
2
3
4
5
6
7
8
9
10

2.8 正则表达式的拓展

  1. g 修饰符:这里会匹配一个停下,然后在还没匹配的地方开始继续匹配
const str = "aaa_aa_a"
const reg1 = /a+/g
console.log(reg1.exec(str))
console.log(reg1.exec(str))
console.log(reg1.exec(str))
1
2
3
4
5
  1. y 修饰符
// i(忽略大小写) m(多行匹配) g(全局匹配)
// y 修饰符 粘连修饰符

const str = "aaa_aa_a"
const reg1 = /a+/g // 每次匹配剩余的
const reg2 = /a+/y // 剩余的第一个开始匹配

console.log(reg1.exec(str)) // aaa
console.log(reg2.exec(str)) // aaa

console.log(reg1.exec(str)) // aa
console.log(reg2.exec(str)) // null

console.log(reg1.exec(str)) // a
console.log(reg2.exec(str)) // aaa,从头开始找了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. u 修饰符
// u 修饰符 unicode
const str = "\uD842\uDFB7" // 表示一个字符
// 是一个字符,但是 es5 匹配不到
console.log(/\uD842/.test(str)) // es5 true
console.log(/\uD842/u.test(str)) // es6 false

// .除了换行符以外的任意单个字符
console.log(/^.$/.test(str)) // false
console.log(/^.$/u.test(str)) // true

console.log(/\u{62}/.test("a")) // false
console.log(/\u{62}/u.test("a")) // true

console.log(/𠮷{2}/.test("𠮷𠮷")) // false
console.log(/𠮷{2}/u.test("𠮷𠮷")) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

2.9 数值的拓展

  • 二进制 0B,八进制 0O

  • Number.isFinite(),Number.isNaN()

  • Number.parseInt(),Number.parseFloat()

  • Number.isInteger()

  • 0.1 + 0.2 === 0.3 ??? => 精度缺失问题

  • Math 新增方法

  1. es5 中二进制与十进制转换
// 十进制 => 二进制
const a = 5 // 101
console.log(a.toString(2))

// 二进制 => 十进制
const b = 101
console.log(parseInt(b, 2))
1
2
3
4
5
6
7
  1. es6 中二进制与八进制的表示
// ES 0B二进制 0O八进制
const a = 0B0101
console.log(a) // 5

const b = 0O777
console.log(b) // 111
1
2
3
4
5
6
  1. 一些关于数字类型的函数
// 是否是有限值
console.log(Number.isFinite(5/0)) // false
console.log(Number.isFinite(0.5)) // true
console.log(Number.isFinite("imooc")) // false
console.log(Number.isFinite(true)) // false

// NaN:not a number
console.log(Number.isNaN(NaN)) // true
console.log(Number.isNaN(15)) // false

console.log(Number.parseInt(5.6)) // 5
console.log(Number.parseFloat(5.6)) // 5.6

console.log(Number.isInteger(5)) // true
console.log(Number.isInteger(5.5)) // false

// IEEE 754 双精度标准
// 35 => 00100011
// 0.375 => 0.011
// 0.1 => 0.000110011...
console.log(0.1000000000000001 === 0.1) // false
console.log(0.10000000000000001 === 0.1) // true

// es6 的最大数字
const max = Math.pow(2, 53)
console.log(max) // 9007199254740992
console.log(max + 1) // 9007199254740992

console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER) // -9007199254740991
console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER)) // true

// 去除小数
console.log(Math.trunc(5.5)) // 5
console.log(Math.trunc(true)) // 1
console.log(Math.trunc(NaN)) // NaN
console.log(Math.trunc(undefined)) // NaN

console.log(Number.parseInt(5.5)) // 5
console.log(Number.parseInt(true)) // NaN

// 判断是否为正数
console.log(Math.sign(5)) // 1
console.log(Math.sign(-44)) // -1
console.log(Math.sign(0)) // 0

// 求立方根
console.log(Math.cbrt(8)) // 2
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

2.10 代理 Proxy

  • 代理

  • 常用拦截方法

  1. ES5 代理
// ES5
let obj = {}
let newVal = ""
Object.defineProperty(obj, "name", {
    get() {
        console.log("get")
        return newVal
    },
    set(val) {
        console.log("set")
        // this.name = val
        newVal = val
    }
})
// 设置
obj.name = "es"
// 获取
console.log(obj.name)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. 拦截器 - get:拦截对象属性的读取,比如 proxy.foo 和 proxy["foo"]
// get
// 输入某个下标,判断该值是否存在于 arr 数组当中
let arr = [7, 8, 9]
arr = new Proxy(arr, {
    get(target, prop) {
        // console.log(target, prop)
        return prop in target ? target[prop] : "error"
    }
})
console.log(arr[1])

let dict = {
    "hello": "你好",
    "world": "世界"
}
dict = new Proxy(dict, {
    get(target, prop) {
        return prop in target ? target[prop] : prop
    }
})
console.log(dict["world"]) // 世界
console.log(dict["imooc"]) // imooc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 拦截器 - set:拦截对象属性的设置,返回一个布尔值,比如 proxy.foo = v 或 proxy["foo"] = v
// set
let arr = []
arr = new Proxy(arr, {
    set(target, prop, val) {
        if(typeof val === "number") {
            target[prop] = val
            return true
        } else {
            return false
        }
    }
})
arr.push(5)
console.log(arr[0]) // 5
arr.push(6)
console.log(arr[1]) // 6
arr[2] = "good" // 不起作用
console.log(arr) // Proxy {0: 5, 1: 6}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. has 进行拦截:拦截 propKey in proxy 的操作,返回一个布尔值
// has
let range = {
    start: 1, 
    end: 5
}

range = new Proxy(range, {
    has(target, prop) {
        return prop > target.start && prop <= target.end
    }
})
console.log(2 in range) // true
console.log(9 in range) // false
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. ownKeys 进行拦截:对循环遍历进行拦截,例如:Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)
// 希望 _password 是一个私有属性,不能被遍历
let userInfo = {
    username: "yuan",
    age: 20,
    _password: "***"
}
userInfo = new Proxy(userInfo, {
    ownKeys(target) {
        return Object.keys(target).filter(key => !key.startsWith("_"))
    }
})
for(let key in userInfo) {
    console.log(key) // username age
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. 前面的综合练习:deleteProperty 拦截 delete proxy[propKey] 的操作,返回一个布尔值
// 不能读取(get)、删除(delete)、修改(set)、遍历(ownKeys) _password
let user = {
    name: "xinyue",
    age: 30,
    _password: "***"
}

user = new Proxy(user, {
    get(target, prop) {
        if(prop.startsWith("_")) {
            throw new Error("不可访问")
        } else {
            return target[prop]
        }
    },
    set(target, prop, val) {
        if(prop.startsWith("_")) {
            throw new Error("不可访问")
        } else {
            target[prop] = val
            return true
        }
    },
    deleteProperty(target, prop) { // 拦截删除
        if(prop.startsWith("_")) {
            throw new Error("不可删除")
        } else {
            delete target[prop]
            return true
        }
    },
    ownKeys(target) {
        return Object.keys(target).filter(key => !key.startsWith("_"))
    }
})

// try {
//     console.log(user._password)
// } catch(e) {
//     console.log(e.message) // 不可访问
// }

// try {
//     user._password = "xyx"
// } catch(e) {
//     console.log(e.message) // 不可访问
// }

// try {
//     delete user._password
// } catch(e) {
//     console.log(e.message) // 不可删除
// }


// for(let key in user) {
//     console.log(key) // name age
// }
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
  1. apply 拦截函数:拦截函数的调用、call 和 apply 操作
// 目标:将求和结果乘以2
// apply
let sum = (...args) => {
    let num = 0
    args.forEach(item => {
        num += item
    })
    return num
}

sum = new Proxy(sum, {
    // 目标对象 上下文 参数
    apply(target, ctx, args) {
        return target(...args) * 2
    }
})
console.log(sum(1, 2)) // 6
// 第一个 null 表示不改变 this 指向
console.log(sum.call(null, 1, 2, 3)) // 12
console.log(sum.apply(null, [1, 2, 3])) // 12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  1. 拦截 construct:拦截 new 命令,返回一个对象
// construct new
let User = class {
    constructor(name) {
        this.name = name 
    }
}
User = new Proxy(User, {
    construct(target, args, newTarget) {
        return new target(...args)
    }
})
console.log(new User("imooc")) // User {name: 'imooc'}
1
2
3
4
5
6
7
8
9
10
11
12

2.11 Reflect

  • 将 Object 属于语言内部的方法放在 Reflect 上
  • 修改某些 Object 方法的返回结果,让其变得更加合理
  • 让 Object 操作变成函数行为
  • Reflect 对象的方法与 Proxy 对象的方法一一对应
// 1.将 Object 对象的方法直接用 Reflect 代替
Reflect.defineProperty(obj, "name", { // 本是 Object.defineProperty
    get() {
        return newVal
    },
    set(val) {
        console.log("set")
        // this.name = val
        newVal = val
    }
})

// 2.Reflect 定义的 defineProperty 返回布尔值,更合理
// try {
//     Object.defineProperty()
// } catch(e) {

// }
if(Reflect.defineProperty()) {

} else {

}

// 3.让 Object 操作变成函数行为
console.log("assign" in Object) // true,命令式操作
console.log(Reflect.has(Object, "assign")) // true,函数式操作
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
// 4.Reflect 对象的方法与 Proxy 对象的方法一一对应
let user = {
    name: "xinyue",
    age: 30,
    _password: "***"
}

user = new Proxy(user, {
    get(target, prop) {
        if(prop.startsWith("_")) {
            throw new Error("不可访问")
        } else {
            // return target[prop]
            return Reflect.get(target, prop)
        }
    },
    set(target, prop, val) {
        if(prop.startsWith("_")) {
            throw new Error("不可访问")
        } else {
            // target[prop] = val
            Reflect.set(target, prop, val)
            return true
        }
    },
    deleteProperty(target, prop) { // 拦截删除
        if(prop.startsWith("_")) {
            throw new Error("不可删除")
        } else {
            // delete target[prop]
            Reflect.deleteProperty(target, prop)
            return true
        }
    },
    ownKeys(target) {
        // return Object.keys(target).filter(key => !key.startsWith("_"))
        return Reflect.ownKeys(target).filter(key => !key.startsWith("_"))
    }
})

console.log(user.age)
try {
    console.log(user._password) // 30
} catch(e) {
    console.log(e.message) // 不可访问
}

user.age = 18
console.log(user.age) // 18
try {
    user._password = "YYY"
} catch(e) {
    console.log(e.message) // 不可访问
}

delete user.age
console.log(user.age) // undefined

for(let key in user) {
    console.log(key) // name
}

// ---------------------------------

sum = new Proxy(sum, {
    // 目标对象 上下文 参数
    apply(target, ctx, args) {
        // return target(...args) * 2
        return Reflect.apply(target, target, [...args]) * 2
    }
})
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

2.12 作业:双向绑定

<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <link rel="shortcut icon" href="static/favicon.ico" />
        <title>双向绑定</title>
    </head>

    <body>
        <div id="app">
            <h2>ToDo List</h2>
            <input type="text" id="input">
            <button id="btn">Add</button>
            <div>
                双向绑定:<span id="text"></span>
            </div>
            <ul id="list"></ul>
        </div>
    </body>

    <script>
        // 获取各个节点的对象
        const oInput = document.getElementById("input")
        const oText = document.getElementById("text")
        const oBtn = document.getElementById("btn")
        const oList = document.getElementById("list")

        let obj = {}
        obj = new Proxy(obj, {
            get(target, prop) {
                // 当想要读取 obj.text 时,调用此方法返回该值
                return Reflect.get(target, prop)
            },
            set(target, prop, val) {
                if(prop === "text") {
                    // 将输入框新值赋值给 span 里的值
                    oText.innerHTML = val
                    // 将输入框新值保存到 obj.text 中
                    target[prop] = val
                } else {
                    // 如果不是给 obj.text 赋值,则直接返回
                    return true
                }
            }
        })
        oInput.addEventListener("keyup", e => {
            // 将 e.target.value 赋值给 obj.text,会调用 set 方法
            obj.text = e.target.value
            // 读取 obj.text 时,会调用 get 方法
            console.log(obj.text)
        })
        oBtn.addEventListener("click", () => {
            let li = document.createElement("li")
            // 调用 get 方法读取 obj.text,将其值渲染在 li 元素里
            li.innerHTML = obj.text
            oList.appendChild(li)
        })
    </script>

</html>
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

3. 异步编程

3.1 异步操作前置知识

  • JS 是单线程的
  • 同步任务 与 异步任务
  • Ajax 原理
  • Callback Hell

123

  1. 如上图,举例一:
console.log(1) 
setTimeout(() => {
    console.log(2)
}, 0); // 0 就是 4ms
console.log(3)
// 输出 1 3 2
/*
    1.先执行 console.log(1),是同步任务,根据图,从左边执行完
    2.执行到 setTimeout,是异步任务,进入 event table,0 秒后
    进入 event queue(任务队列),将回调函数注册到 event queue
    3.主线程还有 console.log(2),是同步任务,执行
    4.最后读取任务列表中结果进入主线程执行
    总结:只有当主线程执行完毕空闲,且进入 event queue 当中的时候,
    才会将任务队列放入主线程中执行
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 如图,举例二:
// 伪代码
setTimeout(() => {
    task() // 表示一个任务
}, 2000)
sleep(5000) // 表示一个很复杂的同步任务
// 结果:2s 后并不会执行 task(),而是要等到同步任务完成后才能执行
1
2
3
4
5
6
  1. Ajax 的原理:
// Ajax 的原理
function ajax(url, callback) {
    // 1.创建 XMLHttpRequest 对象
    var xmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
    } else { // 兼容早期浏览器
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP")
    }

    // 2.发送请求
    xmlhttp.open("GET", url, true)
    xmlhttp.send()

    // 3.接收服务端响应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 3 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            callback(obj)
        }
    }
}

// es5
var url = "http://jsonplaceholder.typicode.com/users"
// ajax(url) // 如果想继续操作从服务器得到的 obj,可以向参数传入函数,通过回调函数取得 obj
ajax(url, res => {
    console.log(res)
})
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
  1. 回调地狱:
// 1 -> 2 -> 3
ajax("/static/a.json", res => {
    console.log(res)
    ajax("/static/b.json", res => {
        console.log(res)
        ajax("/static/c.json", res => {
            console.log(res)
        })
    })
})
1
2
3
4
5
6
7
8
9
10

3.2 Promise 执行顺序

  1. 引入:Promise 的执行顺序:Promise 里的代码会立即执行!其后面的 then 才是有异步性的!!
let p = new Promise((resolve, reject) => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
    }, 1000)
    resolve()
}).then(() => {
    setTimeout(() => {
        console.log(3)
    }, 1000);
    console.log(4)
})
console.log(5)

// 1 5 4 2 3
// 解释:在 Promise 里面不一定后执行的,这是固定思维
/*
    1.先执行同步任务 console.log(1) 输出 1,setTimeout 也立马执行完毕,将 setTimeout 里的任务放入 任务队列,等待同步任务执行完毕后才能进入主线程执行
    2.执行到 ".then",由于是异步任务,放入任务队列
    3.执行同步任务 console.log(5) 输出 5,此时外层同步任务执行完毕
    4.执行异步任务,现在任务队列有两个任务,其中仍有同步任务没有完成,执行console.log(4) 之后,开始执行任务队列
    5.由放入的先后依次读取值
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  1. promise 的状态

456

如上图:Promise 有三个状态:

  • pending:执行中
  • fulfilled(resolved):成功的
  • rejected:失败的
let p1 = new Promise((resolve, reject) => {
    resolve(1)
})

let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 1000)
})

let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(3)
    }, 1000)
})

console.log(p1)
console.log(p2)
console.log(p3)
setTimeout(() => {
    console.log(p3)
}, 2000)

p1.then((res) => {
    console.log(res)
})
p2.then((res) => {
    console.log(res)
})
p3.catch(err => {
    console.log(err)
})

/*
    Promise {<fulfilled>: 1}
    2-3.js:18 Promise {<pending>}
    2-3.js:19 Promise {<pending>}
    2-3.js:25 1
    2-3.js:28 2
    2-3.js:31 3
    2-3.js:21 Promise {<rejected>: 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
  1. Promise 状态的不可逆性
let p = new Promise((resolve, reject) => {
    resolve(1)
    reject(2)
})
p.then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

// 只输出了 1,Promise 状态不可以被改变
1
2
3
4
5
6
7
8
9
10
11

3.3 Promise 实例用法

  1. 笨方法解决回调地狱
new Promise((resolve, reject) => {
  ajax("../static/a.json", res => {
    console.log(res)
    resolve()
  })
}).then(res => {
  console.log("a 成功")
  // 记得一定要 return,这样才能返回新的 Promise 对象
  return new Promise((resolve, reject) => {
    ajax("../static/b.json", res => {
      console.log(res)
      resolve()
    })
  })
}).then(res => {
  console.log("b 成功")
}).then(res => {
  console.log("c 成功")
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  1. 改造上面
function getPromise(url) {
  return new Promise((resolve, reject) => {
    ajax(url, res => {
      resolve(res)
    })
  })
}
getPromise("../static/a.json").then(res => {
  console.log(res)
  return getPromise("../static/b.json")
}).then(res => {
  console.log(res)
  return getPromise("../static/c.json")
}).then(res => {
  console.log(res)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. reject 的情况
function getPromise(url) {
  return new Promise((resolve, reject) => {
    ajax(url, res => {
      resolve(res)
    }, err => {
      reject(err)
    })
  })
}

getPromise("../static/aa.json").then(res => {
  console.log(res)
  return getPromise("../static/b.json")
}, err => {
  console.log(err) // 如果不 return 则返回一个空 Promise
  // return getPromise("../static/b.json")
}).then(res => {
  console.log(res) // 因为是空 Promise,故为 undefined
  return getPromise("../static/c.json")
}).then(res => {
  console.log(res) // 仍能返回成功
})
// .catch(err => { // 如果只在最后写一个 catch,可以统一管理错误
//     console.log(err)
// })
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

3.4 Promise 静态方法

  • Promise.resolve()

  • Promise.reject()

  • Promise.all()

  • Promise.race()

  1. 简单形式:
let p1 = Promise.resolve("success") // 返回一个 resolved 的 Promise 请求
p1.then(res => {
  console.log(res)
})

let p2 = Promise.reject("fail") // 返回一个 rejected 的 Promise 请求
p2.catch(err => {
  console.log(err)
})
1
2
3
4
5
6
7
8
9
  1. Promise.resolve()、Promise.reject() 用处:例如函数返回值分别有返回 Promise 对象的和返回字符串的,这时候调用该函数时要用 .then 来取出其中的值。但是返回字符串不能用 .then,这时可以使用 Promise.resolve()/reject()
// 此例中,如果参数为 true 则执行异步操作后返回 Promise 对象
// 但是参数为 false 若返回字符串,但是有个 then。故使用 Promise 静态方法
function foo(flag) {
  if(flag) {
    return new Promise((resolve => {
      setTimeout(() => {
        console.log("执行异步操作")
      }, 0);
      resolve("success")
    }))
  } else {
    // return "fail"
    return Promise.reject("fail")
  }
}

foo(true).then(res => {
  console.log(res)
}, err => {
  console.log(err)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  1. Promise.all 用处:例如:服务器提供接口,只能一张一张上传图片,而现在需要上传三张图片,上传完后需要提示信息给用户。提示信息必须要等到上传完毕后才能显示,故此时需要三个异步操作都结束后才执行显示提示信息的代码(该例子在第二处代码)
// Promise.all
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log(1)
    resolve("1成功")
  }, 1000);
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log(2)
    reject("2失败")
  }, 2000);
})

let p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log(3)
    resolve("3成功")
  }, 3000);
})

Promise.all([p1, p2, p3]).then(res => {
  console.log(res)
}, err => {
  console.log(err)
})

/* 输出结果:
    1
    2
    2失败
    3
    注意:只要有一个失败就会立马进入 Promise.all 的 catch 方法(或者 err 回调)
*/
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
// 图片上传举例
const imgArr = ["i.jpg", "2.jpg", "3.jpg"]
let promiseArr = []
imgArr.forEach(item => {
  promiseArr.push(new Promise((resolve, reject) => {
    // 图片上传的操作(异步操作)
    /**
         * xxx 代码
         * xxx 代码
         */
    resolve(url) // 返回每一张图片的地址
  }))
})
Promise.all(promiseArr).then(res => {
  // res 就是 9 张图片的 url 的数组
  // 插入数据库的操作
  /**
     * xxx 代码
     * xxx 代码
     */
  console.log("图片全部上传完成!")
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. Promise.race() 用处:第二个代码有实例
// Promise.race
let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(1)
        resolve("1成功")
    }, 2000);
})

let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(2)
        reject("2成功")
    }, 1000);
})

let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(3)
        resolve("3成功")
    }, 3000);
})

Promise.race([p1, p2, p3]).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

/* 输出结果:
    2
    2成功
    1
    3
    注意:只要有一个成功就直接回到 Promise.race 中,只要有一个失败也是如此
*/
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
function getImg() {
    return new Promise((resolve, reject) => {
        let img = new Image()
        img.onload = function() {
            resolve(img)
        }
        // img.src = "https://www.xxx.com/xx.jpg"
        img.src = "https://cn.vuejs.org/images/logo.svg"
    })
}

function timeout() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("图片请求超时")
        }, 2000)
    })
}

Promise.race([getImg(), timeout()]).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

// 若未超时,控制台输出:<img src="https://cn.vuejs.org/images/logo.svg">
// 若超时,则控制台输出:图片请求超时
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

3.5 作业:手动实现 Promise.race()

Promise._race = function (promises) {
    return new Promise((resolve, reject) => {
        if(Array.isArray(promises)) {
            for(let item of promises) {
                item.then(resolve, reject)
            }
        } else {
            reject("请输入包含 Promise 实例的数组")
        }
    })

}

function getImg() {
    return new Promise((resolve, reject) => {
        let img = new Image()
        img.onload = function() {
            resolve(img)
        }
        // img.src = "https://www.xxx.com/xx.jpg"
        img.src = "https://cn.vuejs.org/images/logo.svg"
    })
}

function timeout() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("图片请求超时")
        }, 2000)
    })
}

Promise._race([getImg(), timeout()]).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

// Promise._race({name: "yuan"}).then(res => {
//     console.log(res)
// }).catch(err => {
//     console.log(err)
// })
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

3.6 作业:Promise 中 reject 和 catch 的区别

  1. reject 是用来抛出异常,catch 是用来处理异常
  2. reject 是 promise 的静态方法,catch 是 promise 实例的方法。前者将 promise 状态调整为 "rejected"
  3. reject 一定会进入 then 的第二个回调,如果不存在就会进入 catch
  4. 当网络中断时,会直接进入 catch

3.7 Generator

可以暂停的函数。不能作为构造函数使用

  1. 基本语句
function* foo() {
    for (let i = 0; i < 3; i++) {
        // console.log(i)
        yield i
    }
}
// console.log(foo())
let f = foo()
console.log(f.next()) // {value: 0, done: false}
console.log(f.next()) // {value: 1, done: false}
console.log(f.next()) // {value: 2, done: false}
console.log(f.next()) // {value: undefined, done: true}

// ------------------------

function* gen(x) {
    let y = 2 * (yield(x + 1)) // yield(6)
    let z = yield(y / 3) // yield(4)
    return x + y + z // 5 + 12 + 4 = 21
}
// let g = gen(5)
// console.log(g.next()) // 6
// console.log(g.next()) // NaN
// console.log(g.next()) // NaN

let g = gen(5)
console.log(g.next()) // 6
console.log(g.next(12)) // y = 2 * 12 / 3 = 24 / 3 = 8
console.log(g.next(13)) // z = 13 x = 5  return 42

// -------------------------

function* count(x = 1) {
    while(true) {
        if(x % 7 === 0) {
            yield x
        }
        x++
    }
}
let n = count()
console.log(n.next().value) // 7
console.log(n.next().value) // 14
console.log(n.next().value) // 21
console.log(n.next().value) // 28
console.log(n.next().value) // 35
console.log(n.next().value) // 42
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
  1. yield 只能写在 gen 函数的表内部
// 报错!因为 yield 写在了 forEach 函数里面
function* gen(args) {
    args.forEach(item => {
        yield item + 1
    })
}
1
2
3
4
5
6

3.8 使用 Generator 进行异步操作

/*
    整个流程:
    1.getData.next() 开始执行第一个 yield 右边的函数
    2.下次调用 getData 时,其参数是上一次 yield 的返回值,故 res1 得到值
    3.重复 2 步骤
*/

function request(url) {
    ajax(url, res => {
        getData.next(res)
    })
}

function* gen() {
    let res1 = yield request("../static/a.json")
    console.log(res1)
    let res2 = yield request("../static/b.json")
    console.log(res2)
    let res3 = yield request("../static/c.json")
    console.log(res3)
}

let getData = gen()
getData.next()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

3.9 Iterator

  • 是一种接口机制,为各种不同的数据结构提供统一访问的机制

  • 主要供 for...of 消费

  • 一句话:不支持遍历的数据结构“可遍历”

  1. 初见 iterator:下面的 it 相当于一个指针,nextIndex 是一个 下标,每 next 一下,指针就向右移一位
function makeIterator(arr) {
    let nextIndex = 0
    return {
        next() {
            return  nextIndex < arr.length ? {
                value: arr[nextIndex++],
                done: false
            } : {
                value: undefined,
                done: true
            }
        }
    }
}
let it = makeIterator(["a", "b", "c"])
console.log(it.next()) // {value: 'a', done: false}
console.log(it.next()) // {value: 'b', done: false}
console.log(it.next()) // {value: 'c', done: false}
console.log(it.next()) // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  1. 让数组 / map 使用 next()
let arr = ["a", "b", "c"]
console.log(arr) // 在控制台的 prototype 中能找到 Symbol.interator 属性
let it = arr[Symbol.iterator]()
console.log(it.next()) // {value: 'a', done: false}
console.log(it.next()) // {value: 'b', done: false}
console.log(it.next()) // {value: 'c', done: false}
console.log(it.next()) // {value: undefined, done: true}

// -----------------------------

let map = new Map()
map.set("name", "es")
map.set("age", 5)
map.set("school", "imooc")
console.log(map) // 在控制台的 prototype 中能找到 Symbol.interator 属性
let it = map[Symbol.iterator]()
// 其中 Array(2) 是 ["name", "es"]
console.log(it.next()) // {value: Array(2), done: false}
console.log(it.next())
console.log(it.next())
console.log(it.next())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  1. 原生具备 Interator 接口的数据结构

    • Array
    • Map
    • Set
    • String
    • TypedArray
    • 函数的 arguments 对象
    • NodeList 对象(document.getxxxByxxx() 等得到的)
  2. 自定义数据结构,使之支持遍历

let courses = {
  allCourse: {
    frontend: ['ES', '小程序', 'Vue', 'React'],
    backend: ['Java', 'Python', 'SpringBoot'],
    webapp: ['Android', 'IOS']
  }
}

// 可迭代协议:Symbol.iterator
// 迭代器协议:return {next() {return {value, done}}}

courses[Symbol.iterator] = function () {
  let allCourse = this.allCourse
  let keys = Reflect.ownKeys(allCourse)
  let values = []
  return {
    next() {
      if (!values.length) {
        if (keys.length) {
          values = allCourse[keys[0]]
          keys.shift()
        }
      }
      return {
        done: !values.length,
        value: values.shift()
      }
    }
  }
}

for (let c of courses) {
  console.log(c)
}
/* 输出结果:
  ES
  小程序
  Vue
  React
  Java
  Python
  SpringBoot
  Android
  IOS
*/

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

另一种使用 Generator 实现方式:

let courses = {
    allCourse: {
        frontend: ["ES", "小程序", "Vue", "React"],
        backend: ["Java", "Python", "SpringBoot"],
        webapp: ["Android", "IOS"]
    }
}

courses[Symbol.iterator] = function* () {
    let allCourse = this.allCourse
    let keys = Reflect.ownKeys(allCourse)
    let values = []
    while(1) {
        if(!values.length) {
            if(keys.length) {
                values = allCourse[keys[0]]
                keys.shift()
                yield values.shift
            } else {
                return false
            }
        } else {
            yield values.shift()
        }
    }
}

for(let c of courses) {
    console.log(c)
}
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

3.10 Module

  1. 导入导出模块 - 初等:import 时名字一定要相同,可以用 as 关键字取别名

模块 module.js

// export const a = 5
// export const b = "imooc"
// export const sum = (x, y) => x + y
// const obj = {
//     name: "es"
// }
// export { obj }

const a = 5
const b = "imooc"
const sum = (x, y) => x + y
const obj = {
    name: "es"
}
class People {
    constructor(name) {
        this.name = name
    }
    showName() {
        console.log(this.name)
    }
}

export {
    a,
    b,
    sum,
    obj,
    People
}
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

调用模块的 2-3.js

import {
    a as aa, // 起别名
    b, 
    sum,
    obj,
    People
} from "./module"
console.log(aa, b) // 5 "imooc"
console.log(sum(2, 5)) // 7
console.log(obj.name) // es
let p = new People("yuan")
p.showName() // yuan
1
2
3
4
5
6
7
8
9
10
11
12
  1. export default:一个模块只能有一个 export default,import 时可以随意取名

module.js

function sum(x, y) {
    return x + y
}
export default sum

export const str = "imooc"
1
2
3
4
5
6

2-3.js

import add, {str} from "./module"
console.log(add(2, 6), str) // 8 "imooc"
1
2
  1. export default 导出多个不同类型的值

方法一:

// module.js:
const a = 5
const b = "imooc"
const sum = (x, y) => x + y
const obj = {
    name: "es"
}
class People {
    constructor(name) {
        this.name = name
    }
    showName() {
        console.log(this.name)
    }
}
export default {
    a,
    b,
    sum,
    obj,
    People
}

// 2-3.js:
import mod from "./module"
console.log(mod.a) // 5
console.log(mod.b) // imooc
console.log(mod.sum(7, 8)) // 15
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

方法二:

// module.js 不变,改变 2-3.js
import * as mod from "./module"
console.log(mod)
console.log(mod.default.a) // 5
console.log(mod.default.b) // "imooc"
console.log(mod.default.sum(8, 9)) 
1
2
3
4
5
6

3. 11 Array.prototype.includes

  • Array.prototype.includes(searchElement, fromIndex):是一个实例方法,不是静态方法

  • includes VS indexOf

  1. 用法
const arr = ["es6", "es7", "es8"]
console.log(arr.includes("es7")) // true
console.log(arr.includes("es9")) // false
console.log(arr.includes("es7", 1)) // true(从下标为 1 的位置开始找)
console.log(arr.includes("es7", 2)) // false
console.log(arr.includes("es7", -1)) // false(从最后一个开始找)
console.log(arr.includes("es7", -2)) // true
1
2
3
4
5
6
7
  1. 与 indexOf() 的比较
/**
 * 总结:
 * 1.若要检测 NaN 是否存在,用 includes
 * 2.若只关心值是否存在,不考虑要不要返回值的位置,用 includes,否则用 indexOf
 * 3.includes 具有第二个参数,表示从该下标开始查找(优化性能)
 */
const arr = ["es6", "es7", "es8"]
console.log(arr.indexOf("es7")) // 1
console.log(arr.indexOf("es7") > -1) // true(用法与 includes 相同了)

const arr1 = ["es6", ["es7", "es8"], "es9"]
console.log(arr1.includes(["es7", "es8"])) // false(不支持引用类型)
console.log(arr1.indexOf(["es7", "es8"])) // false(不支持引用类型)

const arr2 = ["es6", "es7", NaN, "es8"]
console.log(arr2.includes(NaN)) // true
console.log(arr2.indexOf(NaN)) // false

const arr3 = ["es6", "es7", NaN, "es8"]
console.log(arr3.includes("2")) // false
console.log(arr3.indexOf("2")) // -1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

3.12 数值拓展:幂运算符

  • 幂运算符:**
  • 等同于 Math.pow()
console.log(2**10) // 1024
1

3.13 async / await

async / await 是 generator 的语法糖

  1. async 基本形式
async function foo() {
    return "imooc" // Promise.resolve("imooc")
}
console.log(foo()) // Promise {<fulfilled>: 'imooc'}
1
2
3
4
  1. async await 结合:输出 1 2,在 foo 函数中,先等待 timeout 执行完再执行 console.log(2)
// 封装一个异步函数
function timeout() {
    return new Promise(resolve => {
        setTimeout(() => {
            // console.log(1)
            resolve(1)
        }, 1000)
    })
}

async function foo() {
    let res = await timeout() // await 后接一个异步函数,异步函数的 resolve() 返回的数值赋值给 res
    console.log(res)
    console.log(2)
}

foo()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. async await 与 promise 结合:输出:success
function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // console.log(1)
      resolve('success')
      // reject()
    }, 1000)
  })
}

async function foo() {
  return await timeout()
}

foo()
  .then((res) => {
    console.log(res) // success
  })
  .catch((err) => {
    console.log(err)
  })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. async await 对比 Promise 的 ajax 请求
import ajax from './axios'

function request(url) {
  return new Promise((resolve) => {
    ajax(url, (res) => {
      resolve(res)
    })
  })
}

async function getData() {
  const res1 = await request('../static/a.json')
  console.log(res1)
  const res2 = await request('../static/b.json')
  console.log(res2)
  const res3 = await request('../static/c.json')
  console.log(res3)
}
getData()

// // Promise 获得数据
// request('../static/a.json')
//   .then((res) => {
//     console.log(res)
//     return request('../static/b.json')
//   })
//   .then((res) => {
//     console.log(res)
//     return request('../static/c.json')
//   })
//   .then((res) => {
//     console.log(res)
//   })

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

3.14 async / await 一定比 Promise 好用吗?

  1. 通常在写异步代码时,async / await 是更优雅的方式
  2. 但是。同时(并行)执行多个 promise 任务的时候,这时候 async / await 并没有很好的方法:
let p1 = new Promise((resolve, reject) => {
    xxx
    resolve()
})

let p2 = new Promise((resolve, reject) => {
    xxx
    reject()
})

Promise.all([p1, p2]).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

4. ES8

4.1 Object.values、Object.entries()

  1. 遍历 obj 的 value:(es5 和 es8)
const obj = {
    name: "imooc",
    web: "www.imooc.com",
    course: "es"
}

// es5
// const res = Object.keys(obj).map(key => obj[key])
// console.log(res) // ['imooc', 'www.imooc.com', 'es']

// es8
console.log(Object.values(obj)) // ['imooc', 'www.imooc.com', 'es']

console.log(Object.entries(obj))
/**
 * 结果是二维数组:[Array(2), Array(2), Array(2)]
 * ["name", "imooc"]
 * ["web", "www.imooc.com"]
 * ["course", "es"]
 */

for(let [key, val] of Object.entries(obj)) {
    console.log(`${key}: ${val}`)
}
/* 输出结果
    name: imooc
    web: www.imooc.com
    course: es
*/

console.log(Object.entries(["a", "b", "c"]))
/** 输出结果:(说明参数传数组也 ok)
 * [Array(2), Array(2), Array(2)]
 * ["0": "a"]
 * ["1", "b"]
 * ["2": "c"]
 */
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

4.2 Object.getOwnPropertyDescriptors()

对象属性描述符

  1. 使用标题方法,参数为对象,得到一个对象,对象中每个成员都有以下属性:
    • value
    • wirtable:是否可改
    • configurable:是否可用 delete 进行删除
    • enumerable:是否可以使用 for in 进行遍历
  2. 设置上述属性值:
const obj = {}
// 为对象添加属性
Reflect.defineProperty(obj, "name", {
    value: "xinyue",
    writable: false,
    configurable: false,
    enumerable: false
})

console.log(obj.name) // xinyue
obj.name = "zhangsan"
console.log(obj.name) // xinyue
delete obj.name
console.log(obj.name) // xinyue
for(let key in obj) {
    console.log(key) // ""
}

console.log(Object.getOwnPropertyDescriptor(obj, "name")) // {value: 'xinyue', writable: false, enumerable: false, configurable: false}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

4.3 字符串填充

  • String.prototype.padStyle(maxLength, fillingString)

  • String.prototype.padEnd(maxLength, fillingString)

  • 第二个参数不选的话默认是空格填充

  1. 举例:
const str = "imooc"
console.log(str.padStart(8, "x")) // xxximooc
console.log(str.padEnd(8, "y")) // imoocyyy
1
2
3
  1. 实际应用:
// 实例一:日期补零
// yyyy-mm--dd 2022-04-01
const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, "0")
const day = (now.getDate()).toString().padStart(2, "0")
console.log(year, month, day) // 2022 "03" "21"
console.log(`${year}-${month}-${day}`) // 2022-03-21

// 实例二:隐藏手机号
const tel = "13570245147"
const newTel = tel.slice(-4).padStart(tel.length, "*")
console.log(newTel)

// 时间戳补全:后端传的时间戳可能是以秒为单位的
// 伪代码
console.log(new Date().getTime()) // 13位 ms
timestamp.padEnd(13, "0")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

4.4 尾逗号 Trainling commas

function foo(a, b, c, ) {
    console.log(a, b, c)
}
foo(4, 5, 6, )
1
2
3
4

5. ES9

5.1 异步迭代:for await of

  • for-await-of

  • Symbol.asyncIterator:异步迭代协议

  1. 错误示范:遍历异步操作,使之一个一个执行
function getPromise(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(time)
    }, time)
  })
}
const arr = [getPromise(1000), getPromise(2000), getPromise(3000)]
for (let item of arr) {
  console.log(item)
}
// 输出 3 个都是 pending,说明是同步执行了
1
2
3
4
5
6
7
8
9
10
11
12
  1. 异步迭代
function getPromise(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        value: time,
        done: false
      })
    }, time)
  })
}

const arr = [getPromise(1000), getPromise(2000), getPromise(3000)]

arr[Symbol.asyncIterator] = function () {
  let nextIndex = 0
  return {
    next() {
      return nextIndex < arr.length
        ? arr[nextIndex++]
        : Promise.resolve({
            value: undefined,
            done: true
          })
    }
  }
}

async function test() {
  // // for await 等到一个异步操作结束之后再去取另一个异步操作
  for await (let item of arr) {
    console.log(item)
  }
}

test()
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

5.2 正则表达式拓展

  • dotAll

  • 具名组匹配

  • 后行断言

  1. dotAll
const reg = /./s // s:开启 dotAll 模式
console.log(reg.test("5")) // true
console.log(reg.test("x")) // true
// console.log(reg.test("\n")) // false(未开启 dotAll 模式时)
// console.log(reg.test("\u{2028}")) // false(未开启 dotAll 模式时)
1
2
3
4
5
  1. 具名组匹配(es6)
// 具名组匹配(es6)
const date = /(\d{4})-(\d{2})-(\d{2})/.exec(("2020-01-01"))
console.log(date[1]) // 2020
console.log(date[2]) // 01
console.log(date[3]) // 01

// ----------------------------

// 具名组匹配(es6)
const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
// console.log(reg.exec("2020-02-01"))
const groups = reg.exec("2020-02-01").groups

const {year, month, day} = groups
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. 先行断言、后行断言

先行断言:

// 先行断言
// 匹配 ecma,要求 ecma 后面必须要有 script 字样
// 若 str = "ecmaxxscript",就匹配不到了
const str = "ecmascript"
console.log(str.match(/ecma(?=script)/))
1
2
3
4
5

后行断言:

// 后行断言
// 匹配 script,要求 script 前面必须要有 ecma 字样
// 若 str = "ecmaxxscript",就匹配不到了
const str = "ecmascript"
console.log(str.match(/(?<=ecma)script/))
console.log(str.match(/(?<!ecma)script/)) // 前面不能是 ecma,输出:null
1
2
3
4
5
6

5.3 对象扩展

  • Rest & Spread
  1. 对象克隆 / 合并
// const arr1 = [1, 2, 3]
// const arr2 = [4, 5, 6]
// const arr3 = [...arr1, ...arr2]
// console.log(arr3)

const obj1 = {
    name: "yuan",
    age: 34
}
const obj2 = {
    school: "imooc",
    age: 18
}

// 克隆对象
const obj3 = {...obj1}
obj1.age = 18
console.log(obj3.age) // 34,不是引用,而是深拷贝

// 合并对象
const obj4 = {...obj1, ...obj2}
console.log(obj4) // {name: 'yuan', age: 18, school: 'imooc'}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 对象的剩余运算符
const obj1 = {
    name: "xiecheng",
    age: 34,
    school: "imooc",
    course: "es"
}

// 剩余运算符一定要放在参数最后一位
const {name, age, ...rest} = obj1
console.log(name)
console.log(age)
console.log(rest) // {school: 'imooc', course: 'es'}
1
2
3
4
5
6
7
8
9
10
11
12

5.4 Promise.prototype.finally()

  1. 示例
new Promise((resolve, reject) => {
    setTimeout(() => {
        // resolve("success")
        reject("fail")
    }, 1000);
}).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
}).finally(() => {
    console.log("finally")
})
1
2
3
4
5
6
7
8
9
10
11
12
  1. 实际应用
    • 发送 ajax 请求要等一会,需要加载框,当请求成功 / 失败后,需要清除加载框,该操作需要写在 finally 中
    • 操作数据库:无论成功还是失败,都需要关闭与数据库的连接

5.5 字符串扩展

  • 放松了模板字符串转义序列的语法限制
const foo = arg => {
    console.log(arg)
}
foo`\u{61} and \u{62}`
1
2
3
4

6. ES10

6.1 Object.fromEntries()

  1. 还原 Object.entries() 后的二维数组
const obj = {
  name: 'imooc',
  course: 'es'
}

const entries = Object.entries(obj)
console.log(entries) // 一个二维数组,其中有两个数组,每个数组的键与值都是成员

// es10
const fromEntries = Object.fromEntries(entries)
console.log(fromEntries) // 又还原为原来的对象形式

1
2
3
4
5
6
7
8
9
10
11
12
  1. map 转 对象
// map -> 对象
onst map = new Map()
map.set("name", "imooc")
map.set("course", "es")
console.log(map)
const fromEntries = Object.fromEntries(map)
console.log(fromEntries) // {name: 'imooc', course: 'es'}
1
2
3
4
5
6
7
  1. 筛选对象中的键值对并返回新的对象
const course = {
  math: 80,
  english: 85,
  chinese: 90
}

// // 旧方法:将 course 对象的键值对变成键值对数组
// let entries = Object.entries(course)
// let targetObj = {}
// // 将每个键值对数组进行遍历,若数组第二个值大于 90,则将之添加进一个新对象 targetObj
// for (let [key, value] of entries) {
//   if (value > 80) {
//     targetObj[key] = value
//   }
// }
// console.log(targetObj)

// 新方法:将 course 对象的键值对变成键值对数组并返回数组第二个值大于 90 的新数组
const res = Object.entries(course).filter(([key, value]) => value > 80)
console.log(res) // 是一个键值对数组
// 直接将该新的键值对数组还原为对象
console.log(Object.fromEntries(res))

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

6.2 String.prototype.trimStart()、String.prototype.trimEnd()

  1. 去字符串前后空格:(es10前)
let str = "    imooc      "
// 正则表达式去空格
// console.log(str.replace(/^\s+/g, "")) // 去掉前面的空格
// console.log(str.replace(/\s+$/g, "")) // 去掉后面的空格
str = str.replace(/^\s+/g, "1").replace(/\s+$/g, "2")
console.log(str) // 1imooc2
1
2
3
4
5
6
  1. 去字符串前后空格:(es10)
let str = "    imooc      "
console.log(str.trimStart()) // 去除前面空格,trimLeft 一样
console.log(str.trimEnd()) // 去除后面空格,trimRight 一样
str = str.trim()
console.log(str) // 去除了前后所有空格
1
2
3
4
5

6.3 Array.prototype.flat()、Array.prototype.flatMap()

对二维数组进行扁平化处理

  1. 拍平一层数组:参数为拍平层数,也可以为 Infinity,完全拍平多维数组
const arr = [1, 2, 3, [4, 5, 6, [7, 8, 9, [10, 11, 12]]]]
console.log(arr.flat()) // [1, 2, 3, 4, 5, 6, Array(4)]
console.log(arr.flat().flat()) // [1, 2, 3, 4, 5, 6, 7, 8, 9, Array(3)]
console.log(arr.flat().flat().flat()) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
console.log(arr.flat(3)) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
1
2
3
4
5
  1. flatMap()
const arr = [1, 2, 3, 4, 5]
const res1 = arr.map(x => [x + 1]).flat()
console.log(res1) // [2, 3, 4, 5, 6]

// 将 map 和 flat 相结合的新函数:flatMap
const res2 = arr.flatMap(x => [x + 1])
console.log(res2) // [2, 3, 4, 5, 6]
1
2
3
4
5
6
7

6.4 作业:数组扁平化的其他方法

我的方法(递归实现):

// 递归实现
let arr = [1, 2, [3, [4, 5]]]
// result 是存放结果的数组
let result = []
// 定义多维数组展开函数,运用递归完成
function arrFlat(arr) {
    for(let item of arr) {
        if(Object.prototype.toString.call(item).slice(8, -1) != "Array") {
            result.push(item)
        } else {
            arrFlat(item)
        }
    }
}
arrFlat(arr)
console.log(result)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

6.5 可选的 Catch Binding

const validJSON = json => {
    try {
        JSON.parse(json)
        return true
    } 
    // catch(e) { // 这里的 e 没有任何意义,对返回值没影响
    //     console.log(e)
    //     return false
    // }
    catch {
        return false
    }
}

const json = '{"name":"imooc","course":"es"}'
console.log(validJSON(json)) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

6.6 JSON.superset、JSON.stringify()

  • JSON superset
  • JSON.stringify() 增强能力
// JSON 超集 \u2029 段分隔符 \u2028 行分隔符
eval('var str = "imooc";\u2029 function foo(){return str;}')
console.log(foo())
1
2
3
// JSON 超集 \u2029 段分隔符 \u2028 行分隔符
eval('var str = "imooc";\u2029 function foo(){return str;}')
console.log(foo())

// 0xD800 ~ 0xDfff
console.log(JSON.stringify("\uD83D\uDE0E")) // 一个笑脸
console.log(JSON.stringify("\uD83D")) // \uD83D
1
2
3
4
5
6
7

6.7 Symbol.prototype.desciption

// Symbol,description 是只读属性
 const s = Symbol("imooc")
 console.log(s) // Symbol(imooc)
 console.log(s.description) // imooc

 const s2 = Symbol()
 console.log(s2.description) // undefined
1
2
3
4
5
6
7

7. ES11

7.1 (重要)String.prototype.matchAll()

  • 全局模式捕获:String.prototype.matchAll()
  1. 匹配 div 标签内内容
// 获得 div 标签中的内容
const str = `
    <html>
        <body>
            <div>第一个div</div>
            <p>这是p</p>
            <div>第二个div</div>
            <span>这是span</span>
        </body>>
    </html>
`

// exec g
function selectDiv(regExp, str) {
    let matches = []
    while(true) {
        const match = regExp.exec(str)
        if(match === null) {
            break
        }
        matches.push(match[1])
    }
    return matches
}
const regExp = /<div>(.*)<\/div>/g
const res = selectDiv(regExp, str)
console.log(res) // ['第一个div', '第二个div']
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
  1. match、replace 实现上述要求
// 获得 div 标签中的内容
const str = `
    <html>
        <body>
            <div>第一个div</div>
            <p>这是p</p>
            <div>第二个div</div>
            <span>这是span</span>
        </body>>
    </html>
`

// 方法一:match
const regExp = /<div>(.*)<\/div>/g
console.log(str.match(regExp)) // ['<div>第一个div</div>', '<div>第二个div</div>']

// 方法二:replace
function selectDiv(regExp, str) {
    let matches = []
    // replace 第二个参数可以是替换内容,也可以是一个函数
    str.replace(regExp, (all, first) => {
        matches.push(first)
    })
    return matches
}
const res = selectDiv(regExp, str)
console.log(res) // ['第一个div', '第二个div']
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
  1. matchAll 实现上述要求
// 方法三:matchAll,必须配合 \g
// 获得 div 标签中的内容
const str = `
    <html>
        <body>
            <div>第一个div</div>
            <p>这是p</p>
            <div>第二个div</div>
            <span>这是span</span>
        </body>>
    </html>
`

const regExp = /<div>(.*)<\/div>/g

function selectDiv(regExp, str) {
    let matches = []
    for(let match of str.matchAll(regExp)) {
        matches.push(match[1])
    }
    return matches
}
const res = selectDiv(regExp, str)
console.log(res)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

7.2 Dynamic import() 动态导入

可以想象为按需加载

  1. 点击按钮后再发送 ajax:(ajax.js 被 export default 了)
const oBtn = document.querySelector("#btn")
oBtn.addEventListener("click", () => {
    import("./ajax.js").then(mod => {
        // console.log(mod)
        mod.default("../static/a.json", res => {
            console.log(res)
        })
    })
})
1
2
3
4
5
6
7
8
9

7.3 新数据类型:BigInt

const max = 2**53
console.log(max)
console.log(Number.MAX_SAFE_INTEGER)
console.log(max === max + 1) // true

const bigInt = 9007199254740993n
console.log(typeof bigInt)
console.log(1n == 1) // true
console.log(1n === 1) // 不仅判断值,还要判断类型

const bigInt2 = BigInt(9007199254740993)
console.log(bigInt) // 9007199254740993n

const num = bigInt + bigInt2
console.log(num.toString()) // 只有字符串才能存储这么大的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

7.4 Promise.allSettled()

  • Promise.allSettled()

  • allSelected() VS all()

  1. allSettled 和 all 比较
// Promise.all 缺陷:当有一个 Promise 请求失败,就全失败,成功的不能处理
Promise.all([
    Promise.resolve({
        code: 200,
        data: [1, 2, 3]
    }),
    Promise.reject({
        code: 500,
        data: [4, 5, 6]
    }),
    Promise.resolve({
        code: 200,
        data: [7, 8, 9]
    })
]).then(res => {
    console.log(res)
    console.log("成功")
}).catch(err => {
    console.log(err)
    console.log("失败")
})
/* 结果:
    {code: 500, data: Array(3)}
    失败
*/

Promise.allSettled([
    Promise.resolve({
        code: 200,
        data: [1, 2, 3]
    }),
    Promise.reject({
        code: 500,
        data: [4, 5, 6]
    }),
    Promise.resolve({
        code: 200,
        data: [7, 8, 9]
    })
]).then(res => {
    console.log(res)
    console.log("成功")
}).catch(err => {
    console.log(err)
    console.log("失败")
})
/* 结果:
    [
        {status: 'fulfilled', value: {…}}, 
        {status: 'rejected', reason: {…}}, 
        {status: 'fulfilled', value: {…}}
    ]
    成功
*/
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
  1. 只获得 fulfilled 状态的 Promise
Promise.allSettled([
  Promise.resolve({
    code: 200,
    data: [1, 2, 3]
  }),
  Promise.reject({
    code: 500,
    data: [4, 5, 6]
  }),
  Promise.resolve({
    code: 200,
    data: [7, 8, 9]
  })
])
  .then((res) => {
    // console.log(res)
    // console.log("成功")
    const data = res.filter((item) => item.status === 'fulfilled')
    console.log(data)
  })
  .catch((err) => {
    console.log(err)
    console.log('失败')
  })
/* 结果:
  [
      {status: 'fulfilled', value: {…}},  
      {status: 'fulfilled', value: {…}}
  ]
  成功
*/
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

7.5 全局对象:globalThis

提供了一个标准的方式去获取不同环境下的全局对象

// node: global
// web: window self(就是 window)

// es11 之前
const getGlobal = () => {
    if (typeof self !== "undefined") {
        return self
    }
    if (typeof window !== "undefined") {
        return window
    }
    if (typeof global !== "undefined") {
        return global
    }
    throw new Error("无法找到全局对象")
}
const global = getGlobal()
console.log(global)

// es11
console.log(globalThis)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

7.6 (重要)可选链:Optional chaining

// es11 前
const user = {
    address: {
        street: "xx街道",
        getNum() {
            return "80号"
        }
    }
}
// const street = user && user.address && user.address.street
// console.log(street)
// const num = user && user.address && user.address.getNum && user.address.getNum()
// console.log(num)

// 可选链
const street = user?.address?.street
console.log(street)
const num = user?.address?.getNum?.()
console.log(num)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

7.7 Nullish coalescing Operator

  • 空值合并运算符
// 空值合并运算符
const b = 0 // 如果取 ""、false 则都是本身
const a = b ?? 6 // 只有 b 取 null 和 undefined 时 a 才是 6
console.log(a)
1
2
3
4

8. 实战:ES 新特性在 Vue 实战中的应用

8.1 Vue CLI 环境安装

  1. Window:npm install -g @vue/cli@4.3.1

  2. 安装完成,在终端输入:vue-Vvue --version

  3. 创建项目:vue create imooc-es-vue

  4. 进入项目:cd imooc-es-vue

  5. 运行项目:npm run serve


8.2 VSCode 保存时自动 ESLint 格式化

  1. 安装 ESLint 插件
  2. 在 Vue-cli 脚手架中,选择 Standard ESLint
  3. 每次修改完代码后都需要执行 npm run lint 之后才能格式化
  4. 为了解决 3 的问题,在根目录下创建文件夹 .vscode,再于此新建 settings.json
  5. 添加以下代码:
{
  "eslint.alwaysShowStatus": true, // 在下框显示ESLint图标
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true // 开启自动保存修复
  },
  "eslint.run": "onSave", // 也可以 "onType“,边写边检查
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "html",
    "vue"
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 以后需要格式化时,直接用 ctrl + s 解决
  2. 设置 ctrl + alt + f 进行 eslint 格式化:右键选择 使用...格式化文档,选择 eslint 而不是 vetur,这样就可以格式化了

若不使用 vue-cli,仅仅只是格式化普通 js,步骤如下:

  1. 安装 eslint:npm install -D eslint
  2. 进行初始化:npx eslint --init
  3. 删掉 settings.json 中的 eslint.validate 中的 html
  4. 设置完成后,配置 .vscode 里的 settings.json,重启 vscode

8.3 获取用户信息列表

  1. 安装 axios:npm i axios --save(生产环境依赖,生产环境依赖加个 -dev

  2. 代码如下:

<template>
  <div>
    <div>用户信息</div>
    <ul>
      <li v-for="(item, index) in userList" :key="index">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
// axios 被 npm 安装在 node_modules 里,故不需要详细路径
import axios from 'axios'
export default {
  /**
   * 不能写成 data: {} 是因为当多个地方调用 data 的值时,都是一样的,
   * 只有使用 return,才能返回不一样的值
   */
  data () {
    return {
      userList: []
    }
  },
  //   created () {
  //     axios.get('http://jsonplaceholder.typicode.com/users').then(res => {
  //       console.log(res)
  //       this.userList = res.data
  //     }).catch(err => {
  //       console.log(err)
  //     })
  //   }
  // }
  async created () {
    // 解构赋值
    const { data } = await axios.get('http://jsonplaceholder.typicode.com/users')
    console.log(data)
    this.userList = data
  }
}
</script>

<style>
  li {
    list-style: none;
  }
  /* ul 有 40px 的内边距,导致没对齐 */
  ul {
    padding: 0;
  }
</style>
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

8.4 使用 Proxy 代理用户信息

  • data.sort((a, b) => {}) 是默认升序排列的,但是是按位排序的,例如:9 > 17,a b 为任意二数,返回值大于 0 则 a 放在 b 的后面,反之后面
  • sort() 方法会改变原来的数组,使用 [].concat(data)
<template>
  <div>
    <div>用户信息</div>
    <button @click="asc">升序</button>
    <button @click="desc">降序</button>
    <button @click="reset">重置</button>
    <ul>
      <li v-for="(item, index) in userList" :key="index">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
// axios 被 npm 安装在 node_modules 里,故不需要详细路径
import axios from 'axios'
export default {
  /**
   * 不能写成 data: {} 是因为当多个地方调用 data 的值时,都是一样的,
   * 只有使用 return,才能返回不一样的值
   */
  data () {
    return {
      userList: []
    }
  },
  //   created () {
  //     axios.get('http://jsonplaceholder.typicode.com/users').then(res => {
  //       console.log(res)
  //       this.userList = res.data
  //     }).catch(err => {
  //       console.log(err)
  //     })
  //   }
  // }
  async created () {
    // 解构赋值
    const { data } = await axios.get('http://jsonplaceholder.typicode.com/users')
    // 代理
    this.proxy = new Proxy({}, {
      get (target, prop) {
        if (prop === 'asc') { // 升序
          // data.sort() 是默认升序排列的,但是是按位排序的,例如:9 > 17
          // 下面 a b 为任意二数,返回值大于 0 则 a 放在 b 的后面,反之后面
          // sort() 方法会改变原来的数组,使用 [].concat(data)
          // return data.sort((a, b) => a.name > b.name ? 1 : -1)
          return [].concat(data).sort((a, b) => a.name > b.name ? 1 : -1)
        } else if (prop === 'desc') { // 降序
          return [].concat(data).sort((a, b) => a.name < b.name ? 1 : -1)
        } else {
          console.log(data)
          return data
        }
      },
      set () {
        return false
      }
    })
    this.userList = this.proxy.default
  },
  methods: {
    asc () {
      this.userList = this.proxy.asc
    },
    desc () {
      this.userList = this.proxy.desc
    },
    reset () {
      this.userList = this.proxy.default
    }
  }
}
</script>

<style>
  li {
    list-style: none;
  }
  /* ul 有 40px 的内边距,导致没对齐 */
  ul {
    padding: 0;
  }
</style>
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

8.5 多图片上传到云存储

  • style 标签里的 scoped 表示:里面写的 class 样式不影响其他 .vue 文件的同名 class 的样式
  • 一般来说 input 标签隐藏,而显示 label 标签
<template>
  <div>
    <!-- <button>选择图片上传</button> -->
    <label for="upload" class="choose-img">选择图片上传</label>
    <input type="file" multiple id="upload"
    style="display: none;" accept="image/*"
    @change="onChange" ref="file"
    >
    <p class="tip">提示:一次可选择多张图片,最多不超过 9 张(单张图片大小 < 1M)</p>
  </div>
</template>

<script>
export default {
  methods: {
    onChange () {
      // const inputDOM = this.$refs.file
      // const newFiles = inputDOM.files
      // 可选链
      const newFiles = this.$refs?.file?.files
      console.log(newFiles)
      // 校验
      if (newFiles.length > 9) {
        alert('最多可以一次选择 9 张图片')
        return false
      }
      const files = []
      for(let file of newFiles) {
        const size = file.size / 1024 / 1024 // 把单位转化为 M
        if(size > 1) {
          alert('请选择 1M 以内的图片')
          return false
        }
        files.push(file)
      }
      this.uploadFilesByOSS(files)
    },
    // 上传多图到阿里云OSS
    uploadFilesByOSS (files) {

    }
  }
}
</script>

<style scoped>
  .choose-img {
    display: block;
    width: 150px;
    height: 50px;
    text-align: center;
    line-height: 50px;
    background-color: #42b983;
    color: #fff;
    border-radius: 5px;
    cursor: pointer;
  }
  .tip {
    color: #ccc;
  }
</style>
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

8.6异步操作多张图片上传云存储

  1. 安装阿里oss:npm i --save ali-oss
  2. 代码如下:
<template>
  <div>
    <!-- <button>选择图片上传</button> -->
    <label for="upload" class="choose-img" :class="{upLoading: isUploading}">选择图片上传</label>
    <input type="file" multiple id="upload"
    style="display: none;" accept="image/*"
    @change="onChange" ref="file" :disabled="isUploading"
    >
    <p class="tip">提示:一次可选择多张图片,最多不超过 9 张(单张图片大小 < 1M)</p>
    <ul class="img-container">
      <li v-for="(item, index) in imgList" :key="index"
      :style="{background: `url(${item}) no-repeat center/contain`}"></li>
    </ul>
  </div>
</template>

<script>
import OSS from 'ali-oss'
const ACCESSKEY = {
  ID: 'xxx',
  SECRET: 'xxx'
}
export default {
  data () {
    return {
      client: new OSS({
        region: 'oss-cn-guangzhou',
        bucket: 'fortest-yuanke',
        accessKeyId: ACCESSKEY.ID,
        accessKeySecret: ACCESSKEY.SECRET
      }),
      imgList: [], // 存放上传完成的图片的列表
      isUploading: false // 当前图片是否正在上传
    }
  },
  methods: {
    onChange () {
      // const inputDOM = this.$refs.file
      // const newFiles = inputDOM.files
      // 可选链
      const newFiles = this.$refs?.file?.files
      console.log(newFiles)
      // 校验
      if (newFiles.length > 9) {
        alert('最多可以一次选择 9 张图片')
        return false
      }
      const files = []
      for (const file of newFiles) {
        const size = file.size / 1024 / 1024 // 把单位转化为 M
        if (size > 1) {
          alert('请选择 1M 以内的图片')
          return false
        }
        files.push(file)
      }
      this.uploadFilesByOSS(files)
    },
    // 上传多图到阿里云OSS
    uploadFilesByOSS (files) {
      this.isUploading = true
      const uploadRequest = []
      for (const file of files) {
        uploadRequest.push(new Promise((resolve, reject) => {
          // 第一个参数:上传文件名称;第二个参数:上传文件本身
          this.client.put(`${Math.random()}-${file.name}`, file).then(res => {
            // console.log(res)
            // this.imgList = [...this.imgList, res.url]
            resolve(res.url)
          }).catch(err => {
            console.log(err)
            reject(err)
          })
        }))
      }
      Promise.allSettled(uploadRequest).then(res => {
        console.log(res)
        const imgs = []
        for (const item of res) {
          if (item.status === 'fulfilled') {
            imgs.push(item.value)
          }
        }
        this.imgList = imgs
        this.isUploading = false
      }).catch(err => {
        console.log(err)
      })
    }
  }
}
</script>

<style scoped>
  .choose-img {
    display: block;
    width: 150px;
    height: 50px;
    text-align: center;
    line-height: 50px;
    background-color: #42b983;
    color: #fff;
    border-radius: 5px;
    cursor: pointer;
  }
  .tip {
    color: #ccc;
  }
  .img-container > li {
    list-style: none;
    width: 150px;
    height: 150px;
    float: left;
    margin: 0 30px 30px 0;
    border: 1px solid #ccc;
  }
  .upLoading {
    background-color: #ccc;
  }
</style>
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

8.7 更优雅方式实现异步(非并行)

async await 适合串行操作,不适合并行操作。并行操作还是用 Promise

<template>
  <div>
    <!-- <button>选择图片上传</button> -->
    <label for="upload" class="choose-img" :class="{upLoading: isUploading}">选择图片上传</label>
    <input type="file" multiple id="upload"
    style="display: none;" accept="image/*"
    @change="onChange" ref="file" :disabled="isUploading"
    >
    <p class="tip">提示:一次可选择多张图片,最多不超过 9 张(单张图片大小 < 1M)</p>
    <ul class="img-container">
      <li v-for="(item, index) in imgList" :key="index"
      :style="{background: `url(${item}) no-repeat center/contain`}"></li>
    </ul>
  </div>
</template>

<script>
import OSS from 'ali-oss'
const ACCESSKEY = {
  ID: 'xxx',
  SECRET: 'xxx'
}
export default {
  data () {
    return {
      client: new OSS({
        region: 'oss-cn-guangzhou',
        bucket: 'fortest-yuanke',
        accessKeyId: ACCESSKEY.ID,
        accessKeySecret: ACCESSKEY.SECRET
      }),
      imgList: [], // 存放上传完成的图片的列表
      isUploading: false // 当前图片是否正在上传
    }
  },
  methods: {
    onChange () {
      // const inputDOM = this.$refs.file
      // const newFiles = inputDOM.files
      // 可选链
      const newFiles = this.$refs?.file?.files
      console.log(newFiles)
      // 校验
      if (newFiles.length > 9) {
        alert('最多可以一次选择 9 张图片')
        return false
      }
      const files = []
      for (const file of newFiles) {
        const size = file.size / 1024 / 1024 // 把单位转化为 M
        if (size > 1) {
          alert('请选择 1M 以内的图片')
          return false
        }
        files.push(file)
      }
      this.uploadFilesByOSS2(files)
    },
    // // 上传多图到阿里云OSS
    // uploadFilesByOSS (files) {
    //   this.isUploading = true
    //   const uploadRequest = []
    //   for (const file of files) {
    //     uploadRequest.push(new Promise((resolve, reject) => {
    //       // 第一个参数:上传文件名称;第二个参数:上传文件本身
    //       this.client.put(`${Math.random()}-${file.name}`, file).then(res => {
    //         // console.log(res)
    //         // this.imgList = [...this.imgList, res.url]
    //         resolve(res.url)
    //       }).catch(err => {
    //         console.log(err)
    //         reject(err)
    //       })
    //     }))
    //   }
    //   Promise.allSettled(uploadRequest).then(res => {
    //     console.log(res)
    //     const imgs = []
    //     for (const item of res) {
    //       if (item.status === 'fulfilled') {
    //         imgs.push(item.value)
    //       }
    //     }
    //     this.imgList = imgs
    //     this.isUploading = false
    //   }).catch(err => {
    //     console.log(err)
    //   })
    // },
    // async await
    async uploadFilesByOSS2 (files) {
      this.isUploading = true
      const imgs = []
      for (const file of files) {
        const result = await this.client.put(`${Math.random()}-${file.name}`, file)
        imgs.push(result.url)
      }
      this.imgList = imgs
      this.isUploading = false
    }
  }
}
</script>

<style scoped>
  .choose-img {
    display: block;
    width: 150px;
    height: 50px;
    text-align: center;
    line-height: 50px;
    background-color: #42b983;
    color: #fff;
    border-radius: 5px;
    cursor: pointer;
  }
  .tip {
    color: #ccc;
  }
  .img-container > li {
    list-style: none;
    width: 150px;
    height: 150px;
    float: left;
    margin: 0 30px 30px 0;
    border: 1px solid #ccc;
  }
  .upLoading {
    background-color: #ccc;
  }
</style>
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131

8.8 组件按需加载

index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import User from '../views/User.vue'
// import Upload from '../views/Upload.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'User',
    component: User
  },
  {
    path: '/upload',
    name: 'Upload',
    // 按需加载
    component: () => import('../views/Upload.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router
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

8.9 Webpack 核心概念

  1. 入口(entry)

入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖的开始

module.exports = {
    entry: './path/to/my/entry/file.js'
}
1
2
3
  1. 出口(output)

output 属性告诉 webpack 在哪里输出它所构建的 bundle,以及如何命名这些文件

const path = require('path')
module.exports = {
    entry: './path/to/my/entry/file.js',
    output: {
        path: path.resolve(__dirname, 'dist')
        filename: 'my-first-webpack.bundle.js'
    }
}
1
2
3
4
5
6
7
8
  1. Loader

Loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块

const path = require('path')
module.exports = {
    output: {
        filename: 'my-first-webpack-bundle.js'
    },
    module: {
        rules: [{
            test: /\.txt$/,
            use: 'raw-loader'
        }]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
  1. 插件(plugin)

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务

const HtmlWebpackPlugin = require('html-webpack-plugin') // 通过 npm 安装
const webpack = require('webpack') // 用于访问内置插件

module.exports = {
  module: {
    rules: [{
      tet: /\.txt$/,
      use: 'raw-loader'
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. 模式

通过选择 development、production 或 none 之中的一个,来设置 mode 参数

module.exports = {
    mode: 'production'
}
1
2
3

8.10 webpack 配置(插件去 npm 官网去搜用法)

  1. 生成 package.jsonnpm init / npm init -y(默认设置)
  2. 在项目内安装 webpack、webpack-cli、webpack-dev-server:npm i webpack webpack-cli webpack-dev-server --save-dev
  3. 根目录新建 webpack.config.js,内容:
module.exports = {
  mode: 'development',
  entry: './src/index.js'
}
1
2
3
4
  1. 根目录新建 src/index.js,内容:
const sum = (x, y) => x + y
1
  1. npx 是使用项目内部 module 的关键字,如果全局安装了就可以省略,输入命令进行打包:npx webpack

  2. 除了打包 js,还要打包 html 等,这时就要安装插件:npm i html-webpack-plugin --save-dev(--save-dev 简写 -D、--save 简写 -s)

  3. 如何使用 html-webpack-plugin

const HtmlWebpackPlugin = require('html-webpack-plugin') // 引入插件

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'index.js' // dist 中的输出文件名称
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html' // 以这个为模板
    })
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. 使 static 中的文件不进行打包,安装新插件:npm install copy-webpack-plugin --save-dev,代码:
const CopyPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'index.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CopyPlugin({
      patterns: [
        { from: 'static', to: 'static' }
      ]
    })
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

8.11 webpack 配置优化

  1. 将各个配置进行模块化,根目录新建 build 文件夹,在里面新建文件 webpack.base.config.js、webpack.config.js、webpack.dev.config.js、webpack.pro.config.js。然后安装插件,令 webpack 可识别模块进行合并:npm i webpack-merge -D
  2. webpack.base.config.js
const CopyPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'index.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CopyPlugin({
      patterns: [
        { from: 'static', to: 'static' }
      ]
    })
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  1. webpack.dev.config.js
module.exports = {
  devtool: 'eval-cheap-module-source-map'
}
1
2
3
  1. webpack.pro.config.js:安装插件,使每次打包时重新生成 dist 文件夹:npm install --save-dev clean-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  plugins: [
    new CleanWebpackPlugin()
  ]
}
1
2
3
4
5
6
  1. webpack.config.js:
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.config')
const devConfig = require('./webpack.dev.config')
const proConfig = require('./webpack.pro.config')

module.exports = (env, argv) => {
  const config = argv.mode === 'development' ? devConfig : proConfig
  return merge(baseConfig, config)
}
1
2
3
4
5
6
7
8
9
  1. package.json:判断是生产环境还是开发环境,通过 npm run startnpm run build 进行打包
"scripts": {
    "start": "webpack-dev-server --mode=development --config ./build/webpack.config.js",
    "build": "webpack --mode=production --config ./build/webpack.config.js"
},
1
2
3
4
  1. 大功告成!进入 localhost:8080,这时候如果在 src 目录的 index.js(entry 入口) 中写代码,就会立即在浏览器中进行热更新。index.js
const sum = (x, y) => x + y
console.log(sum(5, 6)) // 11
console.log(sum(7, 8)) // 15
1
2
3

8.12 Babel 配置

  1. 安装插件 babel-loader:npm install -D babel-loader @babel/core @babel/preset-env
  2. webpack.base.config.js 中使用插件:
const CopyPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'index.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CopyPlugin({
      patterns: [
        { from: 'static', to: 'static' }
      ]
    })
  ],
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', { "useBuiltIns": "entry" }]
            ]
          }
        }
      }
    ]
  }
}
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