今天看啥  ›  专栏  ›  星愿灯塔

ES6入门教程-读书笔记

星愿灯塔  · 掘金  ·  · 2020-06-08 03:38
阅读 30

ES6入门教程-读书笔记

前言

本文是阅读 阮一峰 的开源书籍 ECMAScript6 入门教程 后记录的一些读书笔记,内容比较基础,适合一些刚入门前端的开发人员阅读。若是大家阅读过程中觉得一些内容需要改进的,也请大家提出来,我会尽快完善文档。最后请各位大佬多多支持一下新人,阅读后随手点个👍,谢谢!

ES6前世今生

简介


ECMAScript 6 (简称 ES6 ) 是于2015年6月正式发布的 javaScript 语言的标准,正式名为ECMAScript 2015。另外,ES6 也泛指 ES2015 及之后的新增特性。


发展历史

年份 版本 事件
1997 1.0 发布ECMAScript 1.0
1998 2.0 发布ECMAScript 2.0
1999 3.0 发布ECMAScript 3.0,成为通行标准,奠定了 JavaScript 语言的基本语法
2007 4.0 4.0草案发布,由于各方有分歧,未发布正式版本
2009 5.0 ECMAScript 5.0 正式发布
2011 5.1 ECMAScript 5.1发布后,开始6.0版的制定
2015 6.0 ECMAScript6正式通过,成为国际标准,正式名称是“ECMAScript 2015”

let 和 const 命令

ES6 新增了 let 命令,用于声明变量。它的用法类似于var ,但是所声明的变量,只在 let 命令所在的代码块内有效。换句话说,let 所声明变量的生命周期只在大括号[^{}] 内,在另外的地方调用都会报错。

var a = []
// 使用let声明变量i且每轮循环都在块级作用域内,所以数组a每次循环存储的输出i值都是对应每次循环内的i值
for (let i = 0; i < 10; i++) {
  a[i] = function() {
    console.log(i)
  }
}
// 若是将let换为var则数组内的函数执行时所输出的值都为10,因为输出值都指向全局变量i
复制代码

letconst 命令的特性

  • 无变量提升现象

var 命令会发生变量提升现象,即变量可以在声明之前使用,值为 undefined 。而大多数语言不支持这种语法,都规定变量应该在声明语句之后才可以使用。为了纠正这种现象,let 命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

  • 暂时性死区

只要块级作用域(就是大括号{})存在 let 命令,它所声明的变量就"绑定"在这个区域,即仅在所属的大括号内有效,不再受外部的影响,内部调用也需要遵守先声明后调用如下所示:

var temp = 123
if (true) {
  tmp = 'abc' // error: ReferenceError
  let tmp //本行用let声明变量tmp,从当前行到第二行之间形成暂时性死区,不允许使用该变量
}
复制代码
  • 块级作用域

ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

第一种场景,内层变量可能会覆盖外层变量。

var tmp = new Date()
function f() {
	// tep = undefined; 本行注释代码相当于第六行var声明变量提升的效果  
  console.log(tmp)
  if (false) {
    var tmp = 'hello word'
  }
}
f() // undefined
// 由于var定义变量时,在预编译阶段会存在变量提升,如第三行代码所示; 在真正执行时,会从作用域链找到最近的变量值,即undefined
复制代码

第二种场景,用来计数的循环变量泄漏为全局变量

var s = 'hello'
for (var i = 0; i < s.length; i++) {
  console.log(s[i])
}
console.log(i) //5 i变量泄露为全局变量
复制代码

ES6 新增了 const 命令,用来声明常量。const 命令声明一个只读的常量,一旦声明,常量的值就不能改变。

  • const 声明的变量不得改变值,意味着, const 一旦声明变量,就必须立即初始化,不能留到以后赋值。

  • constlet 命令相同,只在声明所在的块级作用域内有效,也存在暂时性死区即必须先声明后调用

  • const 声明的常量,也与 let 一样不可重复声明。

  • const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单的数据类型的数据(numberstringboolean),值就保存在变量指向的那个内存地址,因此等于常量。但对于引用类型数据(ObjectArray),变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制。

  const foo = {}
  // 为 foo 添加一个属性,可以成功
  foo.prop = 123
  console.log(foo.prop) //123
  
  // 将 foo 指向另外一个对象,就会报错, 因为对象的保存地址改变了
  foo = {} // TypeError: "foo" is read-only
复制代码

简单的说,`const` 声明的简单数据类型常量无法改变初始值,而声明的引用类型变量是可以改变内部的属性值,因为声明的引用类型变量只保存了指向实际引用对象的内存地址,所以我们一般改变的只是引用对象的内容而没改变存储引用对象的内存地址。扩展:简单数据类型一般存储在栈里,引用类型一般存储在堆里。

变量的解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。

数组的解构赋值

// 以前为变量赋值,只能直接指定值,如下面这种方式
let a = 2, b = 2, c =3

// ES6允许写成下面这样
let [a, b, c] = [1, 2, 3]
复制代码

上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值,简单的描述就是将是将右边的数值按照左右对应的位置进行赋值(1 赋值给 a,2 赋值给 b,3 赋值给 c)。

如果解构不成功,变量的值就等于 undefined

以下两种情况都属于解构不成功,test的值都会等于undefined,因为待解构对象中没有左边对应的值
let [test] = []
let [bar, test] = [1]
复制代码

默认值

解构赋值允许指定默认值,值得注意的是ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数值成员严格等于undefined ,默认值才会生效。

let [x = 1] = [undefined]
x // x的值为默认值为1,因为解构赋值后x被赋值为undefined,此时 undefeated === undefeated,默认值有效
let [x = 1] = [null]
x // x的值为null,因为解构赋值后x被赋值为null,此时 null !== undefined,默认值无效

//对象解构赋值类似
let {x, y = 5} = {x: 1} // x = 1, y = 5
复制代码

对象的解构赋值

对象的解构赋值与数组不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性是没有次序的,变量必须与属性同名,才能取到正确的值。

let { bar, foo, other } = { foo: 'a', bar: 'b' }
foo // 'a'
bar // 'b'
other // undefined 当对象解构赋值不成功,与数组一样,变量的值为undefined
复制代码

如果变量名与属性名不一致,必须写成下面这样。

let { foo: baz } = { foo: 'a', bar: 'bbb'}
baz // 'a'
复制代码

从上面代码来看,对象的解构赋值的内部机制,是先找到同名属性,然后在赋值给对应的变量。真正被赋值的是后者,而不是前者。

嵌套结构的解构赋值

let obj = {
  p: [
    'arr',
    { y: 'obj' }
  ]
}

let { p: [x, { y }] } = obj
x // "arr"
y // "obj"
复制代码

对于嵌套解构的解构赋值,我认为就是剥🧅的过程,只要根据右边待解构对象的数据结构,一层一层往下找到对应的值再赋值给左边同样结构的变量即可。

常规用途

1.交换变量的值
let x = 1, y = 2
[x, y] = [y, x] // x = 2, y = 1

2. 解构从函数返回的多个值
function test() {
  return [1, 2, 3]
}
let [a, b, c] = test() //返回对象一样可以解构

3.提取JSON数据,解构赋值对提取JSON对象中的数据,尤其有用。与嵌套数据解构一样。

4.遍历Map解构
for (let [key, value] of map) {
  // 每次循环都可直接拿到相应的键名和键值
}
复制代码

字符串的扩展

模版字符串

传统的字符串拼接通常是使用 + 对多个字符串进行连接,过程十分繁琐。模版字符串是增强版的字符串,用反引号(``)标记。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中使用 ${} 插入变量和Javascript表达式。

// 插入变量
let name = 'jerry', date = 'today'
`Hello ${name},how are you ${date}`

//插入表达式
`Add result: ${ 1 + 2 }` // Add result: 3
复制代码

新增实例方法

传统上,Javascript 只有 indexOf 方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。

  • includes(str, n): 返回布尔值,表示是否找到了参数字符串( str ),第二个参数表示开始查找的位置。
  • startsWith(str, n): 返回布尔值,表示参数字符串( str )是否在原字符串的头部,第二个参数表示开始查找的位置。
  • endsWith(str, n): 返回布尔值,表示参数字符串( str )是否在原字符串的尾部,第二个参数针对前 n 个字符,而上面两个方法针对从第 n 个位置到字符串末尾。用数学闭区间表示一个是[0, n],两个是[n, length]
let str = 'Hello jerry!'
str.startsWith('Hello') // true
str.endsWith('!') // true
str.includes('o') // true

str.startsWith('jerry', 6) // true 相当于截取了原字符串为'jerry'作为新的查找源
str.endsWith('Hello', 5) // true 相当于截取了原字符串为'Hello'作为新的查找源
str.includes('Hello', 6) // false 相当于截取了原字符串为'jerry!'作为新的查找源

'x'.repeat(3) // "xxx"
复制代码

下面是 ES2017 引入的字符串补全长度功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd() 用于尾部补全。

  • padStart(n, str): 如果原字符串的长度小于 str.length ,那么就会往原字符串的头部重复插入 str,具体的重复次数决定于原始字符串的长度与 n 的差值以及 str 的长度有关。若原始字符串长度大于或等于 str 的长度,则该方法无效。
  • padEnd(n, str): 与上同理,只不过是往原字符串的尾部重复插入 str ,若 str 为空,则默认插入空格。

ES2019对字符串实例新增了 trimStart()trimEnd() 这两个方法。trimStart() 消除字符串头部空格,trimEnd() 消除尾部的空格。以前的 trim() 是将头部和尾部的空格全部消除。

const str = ' abc '
str.trim() // 'abc' 消除两边的空格
str.trimStart() // 'abc ' 消除头部空格
str.trimEnd() // ' abc' 消除尾部空格
复制代码

正则的扩展

1. RegExp构造函数

ES5 中,RegExp 构造函数的参数有两种情况:一种是第一个参数是字符串和第二个参数是修饰符;第二种是参数只有一个且参数值为正则表达式。而 ES6 允许第二种情况可以有第二个参数,作用是添加修饰符或者替换第一个参数正则表达式中的修饰符,总之一句话,第一个参数没有修饰符就添加,有修饰符就替换掉。

var reg1 = new RegExp('xyz', 'i') // ES5第一种写法
var reg2 = new RegExp(/xyz/i) // ES5第二种写法 不允许有第二个参数
var reg3 = new RegExp(/xyz/, 'i') // ES6允许有第二个参数

var reg = /xyz/i // 等价于上面代码

new RegExp(/xy/ig, 'i').flags // 'i' 把修饰符ig替换为i
复制代码

2. u修饰符

ES6 对正则表达式添加了u 修饰符,含义为 unicode模式 ,用来正确处理大于 \uFFFFunicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码,这里了解有这个概念即可。

/^\uD33D/u.test('\uD83D\uDC2A') // false 加u'\uD83D\uDC2A'表示一个字符
/^\uD83D/.test('\uD83D\uDC2A') // true 不加u'\uD83D\uDC2A'表示两个字符
复制代码

3. y修饰符

ES6 还为正则表达式添加了 y 修饰符,叫做“粘连[^sticky]” 修饰符。作用与 g 修饰符类似,也是全局匹配,后一次匹配都是从上一次匹配成功的下一个位置开始。不同之处在于,g 修饰符只要剩余位置中存在匹配即可,而 y 修饰符确保匹配必须从剩余的第一个位置开始,这就是“粘连“的含义。

var str = 'aaa_aa_a', r = /a+_/y
r.exec(str) // 结果为['aaa_']
r.exec(str) // 结果为['aa_'] 由于上面执行后剩余字符'aa_a'依然满足条件,所以还是会返回值
复制代码

4. s修饰符

ES2018 引入 s 修饰符,使得 . 可以匹配任意单个字符。

/foo.bar/s.test('foo\nbar') //true .符号匹配的是/n符号
复制代码

5. 具名组匹配

正则表达式使用圆括号进行组匹配。

const re_date = /(\d{4})-(\d{2})-(\d{2})/
const result = re_date.exec('1996-12-25')
const year = result[1] // 1996
const month = result[2] // 12
const day = result[3] // 25
复制代码

组匹配的一个问题是,每一个组的匹配含义不容易看出来,而且只能用数组下标引用,要是组的顺序变了,引用的时候就必须修改序号。ES2018 引入了具名组匹配,允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。方法是模式的头部添加"问号 + 尖括号 + 组名"(?<year>),然后就可以在 exec 方法返回结果的 groups的属性上引用该组名。具名组匹配等于为每一组匹配加上 ID ,便于描述匹配的目的。如果组的顺序变了,也不用改变匹配后的处理代码。

const re_date = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const result = re_date.exec('1996-12-25')
const year = result.groups.year // 1996
const month = result.groups.month // 12
const day = result.groups.day // 25
复制代码

**解构赋值和替换:**有了具名匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。

let {groups: {key, value}} = /^(?<key>.*):(?<value>.*)$/u.exec('str:test')
key // str
value // test

// 字符串替换时,使用$<组名>引用具名组
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
'1996-12-25'.replace(re, '$<day>/$<month>/$<year>') // '25/12/1996'
复制代码

函数的扩展

1. 函数参数的默认值

ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法,如下:

function add(x, y) {
  y = y || 10				//y在add方法调用时传入了y值,则默认值10不生效,相反则生效
  console.log(x + y)
}

add(10) // 默认值有效,输出结果为20
add(10, 5) // 默认值无效,输出结果为15
复制代码

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

function add(x, y = 10) {
  console.log(x + y)
}

add(10) // 默认值有效,输出结果为20
add(10, 5) // 默认值无效,输出结果为15
复制代码

值得注意的是参数变量是默认声明的,所以不能用 letconst 再次声明,因为在同一个块级作用域中,不能重复声明同一个变量。

function test(x = 5) {
  let x = 1 // error
  const x = 2 // error
}
复制代码

与解构赋值默认值结合使用

参数默认值可以与解构赋值的默认值结合使用。

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

test({}) // x = undefined, y = 5 默认值有效

test({x: 1}) // x = 1, y = 5 默认值有效

test({x: 1, y: 2}) // x = 1, y = 2 默认值无效

test() // error, 函数声明时,规定必须传参,所以报错
复制代码

2. rest 参数

ES6 引入rest 参数(格式为...变量名),用于获取函数的多余参数,这样就不需要使用 arguments 对象。rest 参数搭配的变量是一个数组,改变量将多余的参数放入数组中。注意,rest 参数之后不能再有其他参数,即 rest 参数只能放在多个函数参数的末尾,否则会报错。

function test(...arr) {
  console.log(arr)
}

test(1, 2, 3) // [1, 2, 3] arr变量是一个数组,将函数参数按顺序收集起来

// error,rest参数只能放在函数参数的末尾
function f(a, ...b, c) {
  //....
}
复制代码

3. 箭头函数

ES6 允许使用箭头符号(=>)定义函数。

var f = v => v

//等同于
var f = function(v) {
  return v
}
复制代码

如果箭头函数不需要参数或需要多个参数,就使用一个小括号代表参数部分;如果只有一个参数可以不要括号。

// 没有参数
var f = () => 5 // 等价于 var f = function() { return 5 }

// 一个参数
var f = val => val // 等价于 var f = function(val) { return val }

// 多个参数

var f = (num1, num2) => num1 + num2 // 等价于 var f = function(num1, num2) { return num1 + num2 }

复制代码
  • 如果箭头函数的代码块多于一条语句,就要使用大括号将它们括起来,并且使用return关键字返回执行结果。
  • 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
// 多条语句需要用{}包裹代码
var sum = (num1, num2) => {
  num1 *= 2
  num2 *= 3
  return num1 + num2
}

// error, 因为箭头函数解释{}为代码块而不是对象
let getObject = (id) => { id: id, name: 'jerry' }
// 在外层再添加个小括号就解决了
let getObject = (id) => ({ id: id, name: 'jerry' })
复制代码

使用注意点

  • 函数体内的 this 对象,并不是调用该函数的对象,而是函数调用的外层 this 对象。简单的说当前函数执行时没有自己的 this 对象,需要借用外层的 this 对象。
  • 不可以当作构造函数,也就是说,不可以使用 new 命令,否则或抛出错误。
  • 不可以使用 arguments 对象获取函数参数类数组,因为在箭头函数中不存在该对象。如果要用,可以用rest参数代替。
  • 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

上面四点中,第一点需要注意,在编程中容易出错。this 对象的指向可以通过一些方法如 bindapplycall改变,但是在箭头函数中,它是固定的。

function test() {
  setTimeout(() => { // 箭头函数
    console.log('name': this.name)
  }, 1000)
}

var name = 'Tom' // 全局变量,默认挂载在全局window下

test.call({name: 'Jerry'}) // 执行结果为: name: Jerry

// 由于test函数执行时,使用call方法将函数内部this对象改为指向传入的参数对象{name: Jerry},
// 若是不使用箭头函数,那么setTimeout执行时this对象指向的是全局对象window,所以输出结果会为:'Tom'
// 若是使用箭头函数,那么setTimeout执行时this对象指向的是传入的参数对象, 所以输出结果会为:'Jerry'
复制代码

数组的扩展

1. 扩展运算符

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。可以简单理解为扩展运算符是拆积木的过程,而 rest 参数是搭积木的过程。

console.log(...[1, 2, 3]) // 1 2 3

console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5
复制代码

该运算符主要用于函数调用,可以与正常的函数参数结合使用,也可以在扩展运算符后面放置表达式。相反 rest运算符主要用于函数声明,这点注意区分。

function add(x, y, z) {
  return x + y + z
}

const arr = [2, 3]
add(1, ...arr) // 相当于执行add(1, 2, 3),结果为: 6

// 在扩展运算符后面放表达式
let x = 1
const arr = [...(x > 0 ? ['a'] : []), 'b'] // ['a', 'b']
复制代码

扩展运算符的应用

  • 复制数组
const arr1 = [1, 2]
const arr2 = [...arr1] //写法一,相当于右边先展开arr1数组在用数组封装后,赋值给变量arr2
const [...arr3] = arr1 //写法二,结合结构赋值后,个人感觉有点像rest运算符了,将右边的值合并到arr3中
复制代码
  • 合并数组
const arr1 = ['a', 'b']
const arr2 = ['c', 'd']

const arr3 = [...arr1, arr2] // ['a', 'b', 'c', 'd']
复制代码
  • 与解构赋值结合,值得注意的是扩展运算符用于数组赋值时,只能放在参数的最后一位,否则会报错,这点与 rest 扩展运算符有点类似。
[1, ...rest] = [1, 23] // rest = [2, 3] // 有点过滤元素和rest运算符的作用,但原理是扩展运算符
复制代码
  • 字符串
let str = 'ab'
str = str.split('') // ['a', 'b'] // 使用split方法将字符串转化为数组

str = [...'ab'] // ['a', 'b'] 使用扩展运算符更加简洁
复制代码

2. Array对象内置方法

  • from: 用于将类数组对象和可遍历对象转化为真正的数组。
  • of: 用于将一组值,转换为数组,可以用来代替 Array()new Array()
  • find 和 findIndex: 都是用来查找一个符合条件的数组条件,它的参数是一个回调函数[^fn],所有数组成员依次执行该回调函数,不同的是返回值不同,前一个是返回的数组成员或 undefined,后一个是返回的数组成员下标或 -1。注意:这两个方法都可以接受第二个参数,用来绑定回调函数(第一个参数)的 this 对象。
  • **includes: ** 用来判断某个数组是否包含给定的值,与字符串的 includes 方法类似,返回值为一个布尔值。
  • flat 和 flatMap: ** 有时我们想将多维的数组(嵌套数组)拉平,变成一维数组,可以使用这两个方法。两个方法的区别是:flat 默认拉平一层,如果想要拉平n**层,可以写成 flat(n)flatMap 只能拉平一层,它相当于先对每个数组成员执行一遍回调函数,然后执行默认 flat() 操作,所以可以写成 flatMap(fn)。该方法可以有两个参数,第一个参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组,第二个参数用来绑定遍历函数的 this 对象。
// 将arguments对象转换为真正的数组
function test() {
  let args = Array.from(arguments)
}

Array.of(3, 11, 8) // [3, 11, 8]

[1, 2, 4, 5].find(function(value, index, arr) {
  return value < 2 // 为true就停止执行,返回数组成员
}) // 结果为: 1

[1, 2, 4, 5].findIndex(function(value, index, arr) {
  return value < 2 // 为true就停止执行,返回数组成员下标
}) //  结果为: 0

[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false

[1,2, [3, 4]].flat() // [1, 2, 3, 4]
[1, 3, 10].flatMap(function(value) {
  return [x, x*2]
}) // [1,2, 3,6, 10,100]
复制代码

对象的扩展

1. 属性的简洁表达法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。

const test = 'value'
const currrent = {test} // current为{test: 'value'}, 属性名和属性值变量相同时,可以直接写入变量

//等价于
const previous = {test: test}

// 方法的简写
const currentMethod = {
  method() {
    //...
  }
}

// 等价于
const previousMethod = {
  method: function() {
    //...
  }
}
复制代码

2. 属性名表达式

JavaScript 定义对象的属性,有两种方法。

obj.foo = true  // 方法一,通过.符号给对象的属性赋值

obj.['a' + 'bc'] = 123 // 方法二,通过中括号,先计算属性名结果,然后给属性名赋值,这就是属性名表达式。

// 属性名表达式还可以定义方法
let method = {
  ['t' + 'est']() {
    return 'test'
  }
}
复制代码

注意,属性名表达式与简洁表示法,不能同时使用,会报错。

3. 属性的遍历

ES6 一共有5种方法可以遍历对象的属性。

  • for...in: for key in obj 循环遍历对象自身和继承的可枚举属性(不含 Symbol 属性),个人不推荐这种方法。
  • Object.key(obj): 该方法返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
  • Object.getOwnPropertyNames(obj): 该方法返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
  • Object.getOwnPropertySymbols(obj): 该方法返回一个数组,包含对象自身的所 Symbol 属性的键名。
  • Reflect.ownKeys(obj): 该方法返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

4. super关键字

通常 this 关键字总是指向函数所在的当前对象,ES6 新增了另一个类似的关键字 super,指向当前对象的原型对象。

5. 对象的扩展运算符

  • 解构赋值

对象的解构赋值用于从一个对象取值,然后根据键名匹配赋值到另一个新对象。

let {x , y , ...z} = {x: 1, y: 2, a: 3, b: 4}
// x = 1, y = 2, z = {a: 3, b: 4}
复制代码
  • 扩展运算符

对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象中。这种拷贝对象的方法等同于使用 Object.assign() 方法。

let oldObj = {a: 3, b: 4}
let newObj = { ...oldObj } // 使用扩展运算符实现对象的浅拷贝,等价于Object.assign({}, oldObj)
newObj // {a: 3, b: 4}
复制代码

6. 链判断运算符

在编程中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在,如下:

const prop = (obj.usename && obj.password) || 'default'

const getVal = obj.username ? obj.username : 'undefined'
复制代码

这样的层层判断非常麻烦,所以 ES2020 引入链判断运算符(?.),简化上面的写法。

const prop = obj?.username && obj?.password || 'default'
const getVal = obj?.username
复制代码

7. Null 判断运算符

读取对象属性的时候,如果某个属性的值是 nullundefined,有时候需要为它们指定默认值,常见做法是通过 || 运算符指定默认值,但是如果这个属性值为空字符串或 fasle0,默认值也会生效。为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符 ??。它的行为类似 ||,但是只有运算符左侧的值为 nullundefined 时,才会返回右侧的值。

// 与链判断运算符配合使用,为null和undefined的值设置默认值
const result = response.data?.username ?? 'Jerry' 
复制代码

8. Object 新增方法

  • Object.is 用来比较两个值是否严格相等,与严格比较符(===)的行为基本一致。
+0 === -0 // true
NaN === NaN // false

// 可以使用ES6解决上面的问题
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
复制代码
  • Object.assign 方法用于对象的合并,将源对象的所有可枚举属性,复制到目标对象,常用于浅拷贝对象。而一般深拷贝对象是用 JSON.strify(obj)JSON.parse(obj) 两个方法。
const target = {a: 1}
const source1 = {b: 2}
const source2 = {c: 3}

Object.assign(target, source1, source2)

target // {a: 1, b: 2, c: 3}
复制代码
  • getPrototypeOf(obj,prototype) 用来设置一个对象的原型对象,返回参数对象本身。它是ES6 正式推荐的设置原型对象方法,以前使用的是 __proto__ 设置的原型对象。
  • getPrototypeOf(obj) 方法配套,用于读取一个对象的原型对象。
  • keys(obj) 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键名。
  • values(obj) 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。
  • entries(obj) 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对。
  • fromEntries() 方法是 entries() 的逆操作,用于将一个键值对数组转为对象。

新增数据类型 Symbol

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。Symbol 值通过 Symbol() 生成,注意的是前面不能使用 new 命令,因为它不是对象,而是一种类型于字符串的数据类型。为了方便区分,使用一个字符串参数表示对 Symbol 实例的描述。

let str1 = Symbol('first')  // Symbol(first)
let str2 = Symbol('second') // Symbol(second)

str1.toString() // "Symbol(first)"
复制代码

作为属性名的Symbol

由于每个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性,也就不会出现同名属性覆盖的问题。

let mySymbol = Symbol('test')

let obj = {
  test: 'test'
}

obj[mySymbol] = 'symbol'
复制代码

Set 和 Map 数据结构

1. Set 数据结构

ES6 提供了新的数据结构 Set,它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set 实例的属性和方法

  • Set.prototype.size: 返回Set实例的成员总数,相当于数组的 length 的属性。
  • Set.prototype.add(value): 添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value): 删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value): 返回一个布尔值,表示该值是否为 Set 的成员。
  • Set.prototype.clear(): 清除所有成员,没有返回值。
  • Set.prototype.forEach(fn): 遍历方法,使用回调函数遍历每个成员。
let set = new Set()

set.add(1)
set.size // 1
set.has(1) // true
set.delete(1) 
set.size // 0
set.clear()
复制代码

数组去重

// 使用Array.from()将Set数据转换为数组
Array.from(new Set([1, 1, 2, 2, 3, 3])) // [1, 2, 3]

// 使用扩展运算符和 Set 结构相结合,实现数组去重
let arr = [1, 1, 2, 2, 3, 3]
let unique = [...new Set(arr)] // [1, 2, 3]
复制代码

WeakSet

WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。一是 WeakSet 的成员只能是对象,而不能是其他类型的值。二是 weakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用。

因此,WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。根据前面的特性来看,WeakSet的成员是不适合引用的,因为它会随时消失。另外由于WeakSet内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此ES6规定WeakSet不可遍历。

WeakSet内置方法

  • WeakSet.prototype.add(value):weakSet 实例添加一个新对象。
  • WeakSet.prototype.delete(value): 清除 weakSet 实例的指定成员。
  • WeakSet.prototype.has(value): 返回一个布尔值,判断某个值是否在 weakSet 实例中。
const ws = new WeakSet()
ws.add(1) // error,只能添加引用类型数据,不能添加其它类型值

const obj = {}
ws.add(obj)
ws.has(obj) // true
ws.delete(obj)
复制代码

2. Map数据结构

ES6 提供了 Map 数据结构,它类似于对象,但是它的键不仅限于传统对象的键只能是字符串,而是各种类型的值(包括对象)都可以当作键。换句话说,Object 数据结构提供了"字符串 - 值"的映射关系,Map 数据结构提供了"值 - 值"的映射关系,是一种更完善的 Hash 结构实现。

实例属性和内置方法

  • size属性: size 属性返回 Map 结构的成员总数。
  • set(key,value): set 方法设置键名 key 对应的键值为 value,然后返回整个 Map 结构。如果 key 已经有值,则对应的键值会被更新,否则就新生成该键。
  • get(key): get 方法返回 key 对应的键值,如果找不到 key,返回 undefined
  • has(key): has 方法返回一个布尔值,表示某个键是否在当前Map对象之中。
  • delete(key): delete方法删除某个键,返回 true,如果删除失败,返回 false
  • clear(): clear 方法清除所有成员,没有返回值。
  • forEach(fn): 遍历方法,遍历 Map 的所有成员。

WeakMap

WeakMap 结构于 Map 结构类似,也是用于生成键值对的集合。区别:一是 WeakMap 只接受对象作为键名(null除外),不接受其他类型的值作为键名;二是 WeakMap 的键名所指向的对象,不计入垃圾回收机制。

WeakMap 的内置方法

WeakMap 只有四个方法可用: get()set()has()delete()

WeakMap 的用途

WeakMap 应用的典型场合就是 DOM 节点作为键名,另一个作用是部署私有属性。

let myWeakMap = new WeakMap()
let dom = document.getElementById('root')

myWeakMap.set(dom, {counter: 0})

dom.addEventListener('click', function() {
  let data = myWeakMap.get(dom)
  data.counter++
}, false)
复制代码

上面代码中,为DOM节点映射了一个状态对象,我们将状态对象作为键值放在WeakMap里,对应的键名就是这个节点对象。一旦这个DOM节点删除,该状态就会自动消失,不存在内存泄漏风险,这也是WeakMap数据结构的优势所在。

Proxy

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程,即对编程语言进行编程,它支持的拦截操作一共有13种。

Proxy这个词原意是代理,可以理解在目标对象外层架设一层隐形的"拦截",外层对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。举个生活中的例子,我们都知道到大多数明星都是有经纪公司的,明星就好比需要代理的目标对象,而经纪人就好比代理对象,明星的对外业务都是先经过经纪人商谈确认后,才会转发给明星,这就是代理的含义,好比以前是直接面对面交流,现在是插入一个中间人负责传递消息。

// new Proxy()表示生成一个Proxy实例, target参数表示要拦截的目标对象, handler参数也是一个对象,用来
// 定制拦截行为
let proxy = new Proxy(target, handler)

let obj = new Proxy({}, {
  get: function(target, propKey, receiver) {
    console.log(`获取 ${propKey}`)
    return Reflect.get(target, propKey, receiver)
  },
  set: function (target, propKey, value, receiver) {
    console.log(`设置 ${propKey}`)
    return Reflect.set(target, propKey, value, receiver)
  }
})
复制代码

Reflect

Reflect 对象与 Proxy 对象一样,也是ES6为了操作对象而提供的新API

Reflect对象的设计目的:

  • Object 对象的一些明显属于语言内部的方法(如Object.defineProperty),放到 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。
  • 修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty() 在无法定义属性时,会抛出错误,而 Reflect.defineProperty() 则会返回 false
  • Object 操作都变成函数行为。某些 Object 操作是命令式,比如 name in objdelete obj[name],而 Reflect.has(obj, name)Reflect.deleteProperty(obj, name) 让它们变成了函数行为。
  • Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。

Promise

Promise 是异步编程的一种解决方案,比传统的解决方案--回调函数和事件--更合理和更强大。所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上 Promise 是一个对象,从它可以获取异步操作的消息。

Promise 对象有两个特点:

  • 对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:pending(进行中)、 fufilled (已成功) 和 rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能: 从pending 变为 fulfilled 和 从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会在发生改变,会一直保持这个结果,这时称为 resolved(已定型)。

下面创造一个 Promise 实例作说明。

const promise = new Promise(function(resolve, reject) {
  if (/* 异步操作成功 */) {
    // resolve函数作用,是将状态机从 pending 状态变为 fulfilled 状态
    // 并将异步操作结果(data)传递给then方法中成功回调函数作参数(promise.then(function(value))
    resolve(data) 
  } else {
    // reject函数作用, 是将状态机从 pending 状态变为 rejected 状态
    // 并将异步操作失败结果(error)传递给then方法失败回调函数作参数(promise.then(function(error))
    reject(error)
  }
})
复制代码

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,它们是两个函数。而且这个函数代码块是同步执行代码,then方法里面的回调函数才是异步执行代码。

Promise 实例生成后,可以用 then 方法分别指定 resolved/fulfiled 状态和 rejected 的回调函数。

promise.then(function(data)
  // 成功状态回调
  // 当promise对象中执行的是resolve(data),则在当前函数中会接收传过来的异步操作结果data进行处理
}, function(error) {
  // 失败状态回调
   // 当promise对象中执行的是reject(data),则在当前函数中会接收传过来的异步操作结果error进行处理
})
复制代码

下面用一个 promise 对象实现 Ajax 操作的例子。

const getData = function(url) {
  const promise = new Promise(function(resolve, reject) {
    const handler = function() {
      if (this.readyState !== 4) return false
      if (this.status === 200) {
        // 异步操作成功,改变状态机状态,并将异步操作返回的数据data,传给成功回调函数处理。
        resolve(this.response)
      } else {
        // 异步操作失败,改变状态机状态,并将异步操作返回的请求失败信息,传给失败回调处理。
        reject(new Error(this.statusText))
      }
    }
    
    const client = new XMLHttpRequest()
    client.open('Get', url)
    client.onreadystatechange = handler
    client.responseType = 'json'
    client.setRequestHeader('Accept', 'application/json')
    client.send()
  })
  return promise
}

//调用例子
getData('/getList').then(function(data) {
  console.log('success callback: ', data)
}, function(error) {
  console.log('error callback: ', error)
})
复制代码

Promise对象内置方法

  • then(): 它的作用是为 Promise 实例添加状态改变时的回调函数。正如前面所说,then方法的第一个参数是 resolved/fulfiled 状态的回调函数,第二个参数(可选)是 rejected 状态的回调参数。then方法返回的是一个新的 Promise 实例(注意,不是原来的那个 Promise 实例),因此可以采用链式写法,即then方法后面再调用另一个then方法,可以参考 Jquery 库的链式调用,原理差不多。
  • catch(): 如果异步操作抛出错误,状态机就变为 rejected,就会调用该方法指定的回调函数,处理错误信息。另外,then() 方法指定的回调函数,如果运行中抛出错误,也会被 catch() 方法捕获。简单的说该方法负责处理 Promise 对象执行时同步代码和异步回调代码所报出的错误信息,使程序能够正常向下执行,可以参考一下 try-catch 语法的功能。一般来说,不要在 then 方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用 catch 方法。
  • finally(): 该方法用于指定不管 Promise 对象做后状态如何,都会执行的操作。而且该方法的回调函数不接受任何参数,这意味着不知道前面 Promise 的状态机是哪个状态,也进一步表明该方法里面的操作是与状态无关的,不依赖于 Promise 的执行结果。
  • all(): 该方法接受一个数组作为参数,例如 Promise.all([p1, p2, p3])p1、p2、p3 都是 Promise 实例。另外,该方法的参数也可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。状态机是由 p1、p2、p3 共同决定的,分成两种情况:一是当 p1、p2、p3 的状态都变成 fulfilled,状态机才会变成 fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给成功回调函数;二是若 p1、p2、p3中有一个状态为 rejected,则状态机就变成 rejected,此时第一个状态为 reject 实例的返回值,会传递给失败回调函数。可以简单的理解为与操作 (&&),更好理解一些。
  • race(): 该方法同样是将多个 Promise 实例包装成一个新的 Promise 实例。例如 Promise.race([p1, p2, p3])p1、p2、p3都是 Promise 实例。只要 p1、p2、p3 中有一个实例率先改变状态,状态机的状态就跟着改变。那个率先改变的 Promise 实例的返回值就传递给成功或者失败的回调函数。可以简单理解为抢答比赛,看谁回答得最快。
  • allSettled(): 该方法同样接受一组 Promise 实例作为参数包装成一个新的 promise 实例。只有等到所有的参数实例都返回结果,不管参数实例是 fulfilled 状态还是 rejected,包转实例才会结束。而且一旦结束,包装实例的状态总是 fulfilled,不会变成 rejected。状态变成 fulfilled 后,包装实例的回调函数接收的参数是一个数组,每个数组成员都是一个对象 ({ status:'fulfilled/rejected', value/reason: data/error })。
  • any(): 该方法同样接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例中有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态;如果所有参数实例都变成 rejected 状态,包装实例就会变成 rejected 状态。该方法目前还是一个第三方阶段的提案,所以暂时理解概念即可。
  • resolve(): 有时需要将现有对象转为 Promise 对象,该方法就起到这个作用。如 Promise.resolve('test') 等价于 new Promise(resolve => resolve('test'))
  • reject(): 该方法也会返回一个新的 Promise 实例,该实例的状态为 rejected

深入理解Promise

1. promise构造函数

/* Promise构造函数接收一个executor函数,executor函数执行完同步或异步操作后,
	 调用它的两个参数resolve和reject
*/
var promise = new Promise(function(resolve, reject) {
  // 若操作成功,调用resolve并传入value => rsolve(value)
  // 若操作失败,调用reject并传入reason => reject(reason)
})
复制代码
// 手动模拟一个promise构造函数
function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise当前的状态
  self.data = null // Promise的值
  self.onResolvedCallback = [] // resolve时的回调队列
  self.onRejectedCallback = [] // reject时的回调队列
  
  function resolve(value) {
    // 状态机改变状态后,执行回调函数
    if (self.status === 'pending') {
      self.status = 'resolved'
      self.data = value
      for (var i = 0; i < self.onResolvedCallback.length; i++) {
        self.onResolvedCallback[i](value)
      }
    }
  }
  
  function reject(reason) {
    if (self.status === 'pending') {
      self.status = 'rejected'
      self.data = reason
      for (var i = 0; i < self.onRejectedCallback.length; i++) {
        self.onRejectedCallback[i](reason)
      }
    }
  }
}
复制代码

Iterator遍历器 和 for...of循环

1. Iterator(遍历器)的概念

遍历器是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator共有三个作用:

  • 为各种数据结构提供一个统一的、简单的访问接口。
  • 使数据结构的成员能够按某种次序排列。
  • ES6创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 使用。

2. for...of应用

ES6 借鉴 C++、Java、C#、Python 语言,引入了 for...of 循环,作为遍历所有数据结构的统一方法。一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 Iterator 接口,就可以用 for...of 循环它的成员。换句话说,for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。

  • 数组原生具备 iterator 接口(即部署了 Symbol.iterator 属性),for...of 循环本质上就是调用这个接口产生的遍历器。
const arr = ['one', 'two', 'three']
for(let item of arr) {
  console.log(item) // one two three
}
复制代码
  • SetMap 结构也具有 Iterator 接口,可以直接使用 for...of 循环。
let set = new Set(['one', 'two', 'three'])
for (let item of set) {
  console.log(item) // one two three
}

let food = new Map()
food.set('meat', 'chicken')
food.set('fruit', 'cherry')
for (let [name, value] of food) {
  console.log(`name: ${value}`) // meat: chicken  fruit: cherry
}
复制代码
  • 类似数组的对象包括好几类,下面是 for...of 循环用于字符串、DOM NodeList 对象、arguments 对象的例子。
// 字符串
let str = 'jerry'
for (let item of str) {
  console.log(item) // j e r r y
}
// DOM NodeList对象
let doms = document.querySelectorAll('li')
for (let dom of doms) {
  dom.classList.add('test')
}
// arguments对象
function test() {
  for (let item of arguments) {
    console.log(item)
  }
}
test('a', 'b') // a b
复制代码

对象的应用

对于普通对象,for...of 结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。一种解决方法是使用 Object.keys 方法将对象的键名生成一个数组,然后遍历这个数组。

for (let key of Object.keys(obj)) {
  console.log(key + ': ' obj[key])
}
复制代码

async 函数

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。async 函数其实是 Generator 函数的语法糖。实现原理就是将 Generator 函数和自动执行器,包装在一个函数里。async 命令经常与 await 命令搭配使用,await 命令只能在 async 声明的函数内使用,其它的地方都不能使用,否则会报错。

async函数对Generator函数的改进,体现在以下四点:

  • **内置执行器: **Generator 函数的执行必须依靠执行器,所以才有了 co 模块,而 async 函数自带执行器。
  • 更好的语义: asyncawait,比起星号(*)和 yield,语义更清楚。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
  • 更广的适用性: co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的await 命令后面,可以是 Promise 对象和原始数据类型(数值、字符串和布尔值,但这时会自动转成立即 resolvedPromise 对象)。
  • 返回值是 Promise: async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了,你也可以用 then 方法指定下一步操作。

进一步说,async函数完全可以看作多个异步操作包装成的一个Promise对象,而await命令就是内部then命令的语法糖。

async function getListByType(name) {
  const types = await getTypeList(name)
  const list = await getList(types[0])
  return list
}

getListByType('pop').then(function(result) {
  console.log(result)
})
复制代码

Class 语法和继承

ES6 提供了更接近传统语言的写法,引入了 Class (类)这个概念,作为对象的模板,通过 class关键字,可以定义类。ES6class 只是一个语法糖,它的大部分功能,ES5 都可以完成,新的 class 写法只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。

// ES5
function Shape(x, y) {
  this.x = x
  this.y = y
}
Shap.prototype.area = function() {
  return this.x * this.y
}

// ES6
class Shape(x, y) {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
  area() {
    return this.x * this.y
  }
}
复制代码

Class 可以通过 extends 关键字实现继承,这比 ES5 通过修改原型链实现继承要清晰和方便很多。

class Shap {}
class square extends Shap {
  constructor(x, y, rate) {
    super(x, y) // 调用父类的constructor(x, y)
    this.rate = rate
  }
  changeArea() {
    return this.rate * this.x * this.y
  }
}
复制代码

Module 的语法

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出变量。ES6 模块不是对象,而是通过 export 命令显示指定输出的代码,再通过 import 命令输入。

1. export 命令

模块功能主要由两个命令构成: exportimportexport 命令用于规定模块的对外接口,import 命令用来输入其他模块提供的功能。

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。

// test.js
export const cat = 'Tom'
export const mouse = 'Jerry'

export {cat, mouse} // 也可以用对象封装一下导出

// 也可导出函数
export function add(x, y) {
  return x + y
}

// 也可以重命名
export {
	cat as name1,
  mouse as name2
}
复制代码

2. import 命令

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

// main.js
import { cat, mouse, add } from './test.js'

function getName() {
  console.log(`${cat} And ${mouse}`) // Tom And Jerry
}
复制代码

如果想为输入的变量重新取一个名字,import 命令要使用 as 关键字将输入的变量重命名,跟 exprot 命令相同。

import { lastName as surname } from './other.js'
复制代码

除了指定加载某个输出值,还可以使用整体加载(即全部导出),即用(*)指定一个对象,所有输出值都加载在这个对象上面。

import * as allMethod from './test.js'
复制代码

3. export default 命令

export default 命令用于指定模块的默认输出。一个模块只能有一个默认输出,因此 export default 命令只能使用一次。所以,import 命令后面才不用加大括号解构,因为只可能唯一对应 export default命令 。本质上, export default 就是输出一个叫做 default 的 变量或方法,系统允许你为它取任意名字。

// export-default.js
export default function() {
  console.log('default export')
}

// import customName from './export-default.js'
复制代码

4. export 与 import 的复合写法

如果在一个模块中,先输入后输出同一个模块,import命令可以与export语句写在一起。

export { add, test } from 'test_module'

// 上面可以理解为
import { add, test } from 'test_moudule'
export { add, test }
复制代码

5. import() 方法

由于前面的 import 命令无法在运行时加载模块,所以无法动态加载其他的模块。ES2020提案 引入 import()函数,支持动态加载模块。import() 类似于 Node 的 require 方法,区别主要是前者是异步加载,后者是同步加载。

import(specifier)//specifier指定所要加载模块的位置,与import命令的区别是,前者静态加载,后者动态加载
复制代码

适用场合

  • 按需加载: import() 可以在需要的时候,再加载某个模块,比如在项目中常见的异步加载组件。
  • 条件加载: import() 可以放在 if 代码块,根据不同的情况,加载不同的模块。
  • 动态的模块路径: import()允许模块路径动态加载。
const Home = () => import('...') // 动态获取组件

// 条件加载
if (condition) {
  import('moduleA').then(...)
}else {
 import('moduleB').then(...)
}

// 动态模块路径
import(f()).then(...) // 根据f的返回结果,加载不同的模块
复制代码



原文地址:访问原文地址
快照地址: 访问文章快照