一. 展开运算符的使用
展开语法(
Spread syntax
):- 可以在函数调用或数组构造时,将数组表达式 或
string
在语法层面展开 - 还可以在构造字面量对象时,将对象表达式按
key-value
的方式展开
- 可以在函数调用或数组构造时,将数组表达式 或
展开语法的场景:
- 在函数调用时使用
- 在数组构造时使用
- 在构建对象字面量时,也可以使用展开运算符,进行克隆或属性拷贝,这个是在
ES2018(ES9)
中添加的新特性
基本使用:
js// 1.基本演练 // ES6 const names = [1, 2, 3] const newNames = [...names, 4, 5] console.log(newNames) // [1, 2, 3, 4, 5] const str = "Hello" console.log(...str) // H e l l o function foo(name1, name2, ...args) { console.log(name1, name2, args) } foo(...names) // 1 2 [3] foo(...str) // H e ['l', 'l', 'o'] // ES9(ES2018) const obj = { name: "why", age: 18 } // 不可以这样来使用 // foo(...obj) // 在函数的调用时, 用展开运算符, 将对应的展开数据, 进行迭代 // 可迭代对象: 数组/string/arguments const info = { ...obj, height: 1.88, address: "广州市" } console.log(info) // {name: 'why', age: 18, height: 1.88, address: '广州市'}
注意:展开运算符其实是一种浅拷贝
jsconst obj = { name: "why", age: 18, height: 1.88, friend: { name: "curry" } } // 1.引用赋值 内存中的表现:info1:0x100 obj:0x100 const info1 = obj // 2.浅拷贝 内存中的表现,只会对对象第一层的属性进行拷贝 const info2 = { ...obj } info2.name = "kobe" console.log(obj.name) // why console.log(info2.name) // kobe info2.friend.name = "james" console.log(obj.friend.name) // james // 3.深拷贝 // 方式一: 第三方库 // 方式二: 自己实现 function deepCopy(obj) {} // 方式三: 利用现有的js机制, 实现深拷贝JSON,利用JSON.stringify转成一份JSON字符串,再用JSON.parse解析为js对应的数据类型 const info3 = JSON.parse(JSON.stringify(obj)) console.log(info3.friend.name) // curry info3.friend.name = "james" console.log(info3.friend.name) // james console.log(obj.friend.name) // curry
二. 进制和长数字的表示
在
ES6
中规范了二进制和八进制的写法:jsconst num1 = 100 // binary const num2 = 0b100 // 二进制 以0b开头 // octonary const num3 = 0o100 // 八进制 以0o开头 // hexadecimal const num4 = 0x100 // 十六进制 以0x开头
- 严格模式下,不允许早期
以0开头
的八进制写法 - 打印的时候,浏览器为了方便观看,会自动转化为十进制
- 严格模式下,不允许早期
另外在
ES2021
新增特性:数字过长时,可以在数字任意位置使用_
作为连接符jsconst money = 1000000000000 const money = 1_0000_0000_0000
三. Symbol类型用法
1. Symbol的基本使用
Symbol
是什么呢?Symbol
是ES6
中新增的一个基本数据类型,翻译为符号那么为什么需要
Symbol
呢?- 在
ES6
之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突 - 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性
- 比如我们前面在讲
apply
、call
、bind
实现时,我们有给其中添加一个fn
属性,那么如果它内部原来已经有了fn
属性了呢? - 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉
- 在
Symbol
就是为了解决上面的问题,用来生成一个独一无二的值Symbol
值是通过Symbol
函数来生成的,生成后可以作为属性名- 也就是在
ES6
中,对象的属性名可以使用字符串,也可以使用Symbol
值
Symbol
即使多次创建值,它们也是不同的:Symbol
函数执行后每次创建出来的值都是独一无二的我们也可以在创建
Symbol
值的时候传入一个描述description
:这个是ES2019(ES10)
新增的特性js// ES6之后可以使用Symbol生成一个独一无二的值 const s1 = Symbol() // const info = { name: "why" } const obj = { [s1]: "aaa" } console.log(obj) // {Symbol(): 'aaa'} const s2 = Symbol() obj[s2] = "bbb" console.log(obj) // {Symbol(): 'aaa', Symbol(): 'bbb'} console.log(s1 == s2) // false console.log(s1 === s2) // false delete obj[s2] console.log(obj) // {Symbol(): 'aaa'}
2. Symbol作为属性名
我们通常会使用
Symbol
在对象中表示唯一的属性名:jsconst s1 = Symbol() const s2 = Symbol() const obj = {} // 写法一:属性名赋值 obj[s1] = 'abc' obj[s2] = 'nba' // 写法二:Object.defineProperty Object.defineProperty(obj, s1, { enumerable: true, configurable: true, writable: true, value: "abc" }) // 写法三:定义字面量时直接使用 const info = { [s1]: 'abc', [s2]: 'nba' } console.log(Object.getOwnPropertySymbols(info)) // [Symbol(), Symbol()] const symbolKeys = Object.getOwnPropertySymbols(info) // 返回一个给定对象自身的所有 Symbol 属性的数组 for (const key of symbolKeys) { console.log(info[key]) // abc nba }
3. 相同值的Symbol
前面我们讲
Symbol
的目的是为了创建一个独一无二的值,那么如果我们现在就是想创建相同的Symbol
应该怎么来做呢?- 我们可以使用
Symbol.for()
方法来做到这一点Symbol.for()
方法:返回由给定的key
找到的symbol
,否则就是返回新创建的symbol
- 并且我们可以通过
Symbol.keyFor()
方法来获取对应的key
Symbol.keyFor()
:用来获取全局symbol
注册表中与某个symbol
关联的键
js// 3.description // 3.1.Symbol函数直接生成的值, 都是独一无二 const s3 = Symbol("ccc") console.log(s3.description) // ccc const s4 = Symbol(s3.description) console.log(s3 === s4) // false // 3.2. 如果相同的key, 通过Symbol.for可以生成相同的Symbol值 const s5 = Symbol.for("ddd") const s6 = Symbol.for("ddd") console.log(s5 === s6) // true // 获取传入的key console.log(Symbol.keyFor(s5)) // ddd console.log(Symbol.keyFor(s3)) // undefined
- 我们可以使用
四. 数据结构 - Set集合
1. Set的基本使用
在
ES6
之前,我们存储数据的结构主要有两种:数组、对象- 在
ES6
中新增了另外两种数据结构:Set
、Map
,以及它们的另外形式WeakSet
、WeakMap
- 在
Set
是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复- 创建
Set
我们需要通过Set
构造函数(暂时没有字面量创建的方式) new Set([iterable])
iterable
(可选):如果传递一个可迭代对象,它的所有元素将不重复地被添加到新的Set
中。如果不指定此参数或其值为null
,则新的Set
为空
- 创建
我们可以发现中存放的元素是不会重复的,那么
Set
有一个非常常用的功能就是给数组去重js// 1.创建Set const set = new Set() console.log(set) // 2.添加元素 set.add(10) set.add(22) set.add(35) set.add(22) console.log(set) // Set(3) {10, 22, 35} const set2 = new Set() const info = {} const obj = {name: "obj"} set2.add(info) set2.add({}) set2.add(obj) set2.add(obj) console.log(set2) // {{}, {}, {name: 'obj'}} // 3.应用场景: 数组的去重~ const names = ["abc", "cba", "nba", "cba", "nba"] // const newNames = [] // for (const item of names) { // if (!newNames.includes(item)) { // newNames.push(item) // } // } // console.log(newNames) const newNamesSet = new Set(names) // const newNames = Array.from(newNamesSet) // 对类数组或可迭代对象创建一个新的浅拷贝的数组实例 const newNames = [...newNamesSet] // 展开语法,在构造数组字面量时,对类数组或可迭代对象创建一个新的浅拷贝的数组 console.log(newNames) // ['abc', 'cba', 'nba']
2. Set的常见方法
Set
常见的属性:size
:返回Set
中元素的个数
Set
常用的方法:add(value)
:添加某个元素,返回Set
对象本身delete(value)
:从set
中删除和这个值相等的元素,返回boolean
类型has(value)
:判断set
中是否存在某个元素,返回boolean
类型clear()
:清空set
中所有的元素,没有返回值forEach(callback, [, thisArg])
:通过forEach
遍历set
另外
Set
是支持for...of
遍历的,因为**Set
是可迭代对象**Set
使用for...in
是遍历不出key
的
jsconst arr = [1, 2, 3] const set = new Set(arr) // 属性 console.log(set.size) // 3 // 方法 // add方法 set.add(100) console.log(set) // Set(4) {1, 2, 3, 100} // delete方法 set.delete(100) console.log(set) // Set(3) {1, 2, 3} // has方法 console.log(set.has(3)) // true // clear方法 // set.clear() // console.log(set) // Set(0) {size: 0} // forEach方法 set.forEach(item => console.log(item)) // 1 2 3 // set支持for...of for (const item of set) { console.log(item) // 1 2 3 }
3. WeakSet的使用
和
Set
类似的另外一个数据结构称之为WeakSet
,也是内部元素不能重复的数据结构那么和
Set
有什么区别呢?- 区别一:
WeakSet
中只能存放对象类型(null
也不可),不能存放基本数据类型 - 区别二:
WeakSet
对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC
可以对该对象进行回收
- 区别一:
WeakSet
常见的方法:add(value)
:添加某个元素,返回WeakSet
对象本身delete(value)
:从WeakSet
中删除和这个值相等的元素,返回boolean
类型has(value)
:判断WeakSet
中是否存在某个元素,返回boolean
类型
js// 1.Weak Reference(弱引用)和 Strong Reference(强引用) let obj1 = { name: "why" } let obj2 = { name: "kobe" } let obj3 = { name: "jame" } let arr = [obj1, obj2, obj3] // 默认的引用都是强引用,从可达性的角度来说,arr中保存的是Obj1等数据的内存地址,即使后面对Obj1等数据修改了内存地址指向,也不影响到arr中的值 obj1 = null obj2 = null obj3 = null console.log(arr) // [{name: 'why'}, {name: 'kobe'}, {name: 'jame'}] const set = new Set(arr) // 即使后面修改了arr的值,指向新的内存地址,这一步已经保存了之前arr中的值的内存地址 arr = null console.log(set) // Set(3) {{name: 'why'}, {name: 'kobe'}, {name: 'jame'}} // 2.WeakSet的用法 // 2.1.和Set的区别一: 只能存放对象类型 const weakSet = new WeakSet() // weakSet.add(1) // Uncaught TypeError: Invalid value used in weak set weakSet.add({name: 'later'}) console.log(weakSet) // {{name: 'later'}} // 2.2.和Set的区别二: 对对象的引用都是弱引用
4. WeakSet的应用
注意:WeakSet不能遍历
- 因为
WeakSet
只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁 - 所以存储到
WeakSet
中的对象是没办法获取的
- 因为
那么这个东西有什么用呢?
- 事实上这个问题并不好回答,我们来使用一个
Stack Overflow
上的答案
js// WeakSet的应用 const pWeakSet = new WeakSet() // 这里之所以不用Set,是因为Set是强引用,如果在后续我们想对new构造的某个实例对象进行销毁时,强引用会导致该实例对象内存中的地址仍然被Set引用着,而WeekSet却不会,因为是弱引用,如果没有其他引用指向该实例对象,后续GC会回收掉 class Person { constructor() { pWeakSet.add(this) } running() { if (!pWeakSet.has(this)) { console.log("不能通过其他对象: ", this, "来调用") return } console.log("running~") } } let p = new Person() // p = null p.running() // running~ const runFn = p.running runFn() // 不能通过其他对象: undefined来调用 const obj = { run: runFn } obj.run() // 不能通过其他对象: {run: ƒ}来调用
- 事实上这个问题并不好回答,我们来使用一个
五. 数据结构-Map映射
1. Map的基本使用
另外一个新增的数据结构是
Map
,用于存储映射关系,允许使用对象类型作为key
但是我们可能会想,在之前我们可以使用对象来存储映射关系,他们有什么区别呢?
- 事实上我们对象存储映射关系只能用字符串(
ES6
新增了Symbol
)作为属性名(key
) - 某些情况下我们可能希望通过其他类型作为
key
,比如对象,这个时候会自动将对象转成字符串来作为key
- 事实上我们对象存储映射关系只能用字符串(
那么我们就可以使用
Map
:jsconst info = { name: 'later' } const info2 = { age: 18 } // 1. 对象类型的局限性:不可以使用复杂类型如对象,作为key const obj = { [info]: 'haha' // [info] 计算属性 对info对象转为字符串 => String(info) => [object Object] } console.log(obj) // {[object Object]: "haha"} console.log(obj.info) // undefined obj[info2] = 'yeye' console.log(obj) // {[object Object]: 'yeye'} console.log(obj.info2) // undefined // 2. Map映射类型 const map = new Map() map.set(info, 'aaa') map.set(info2, 'bbb') console.log(map) // {{name: 'later'} => "aaa", {age: 18} => "bbb"}
2. Map的常用方法
常见属性:
size
:返回Map
中元素的个数
常见方法:
set(key, value)
:在Map
中添加key
、value
,并且返回整个Map
对象get(key)
:根据key
获取Map
中的value
has(key)
:判断是否包括某一个key
,返回Boolean
类型delete(key)
:根据key
删除一个键值对,返回Boolean
类型clear()
:清空所有的元素forEach(callback, [, thisArg])
:通过forEach
遍历Map
Map
也可以通过for...of
进行遍历,可迭代对象jsconst obj1 = {name: 'later'} const obj2 = {age: 18} const obj3 = {sex: 'male'} const map = new Map( [ [obj1, 'aaa'], [obj2, 'bbb'] ] ) console.log(map) // Map(2) {{…} => 'aaa', {…} => 'bbb'} map.set(obj3, 'ccc') console.log(map) // Map(3) {{…} => 'aaa', {…} => 'bbb', {…} => 'ccc'} console.log(map.get(obj3)) // ccc console.log(map.has(obj3)) // true map.delete(obj3) console.log(map) // Map(2) {{…} => 'aaa', {…} => 'bbb'} // map.clear() // console.log(map) // Map(0) {size: 0} map.forEach(item => console.log(item)) // aaa bbb for (const item of map) { console.log(item) const [key, value] = item console.log(key, value) } // [{…}, 'aaa'] // {name: 'later'} 'aaa' // [{…}, 'bbb'] // {age: 18} 'bbb'
3. WeakMap的使用
和
Map
类型的另外一个数据结构称之为WeakMap
,也是以键值对的形式存在的那么和
Map
有什么区别呢?- 区别一:
WeakMap
的key
只接受对象类型作为key
- 区别二:
WeakMap
的key
对对象的引用是弱引用,如果没有其他引用引用这个对象,那么GC
可以回收该对象
jsconst weakMap = new WeakMap() weakMap.set({name: 'later'}, 'ccc') console.log(weakMap) // WeakMap {{…} => 'ccc'} weakMap.set(1, 'aaa') // Uncaught TypeError: Invalid value used as weak map key weakMap.set('hello', 'bbb') // Uncaught TypeError: Invalid value used as weak map key
- 区别一:
WeakMap
常见方法:set(key, value)
:在WeakMap
中添加key
、value
,并且返回整个WeakMap
对象get(key)
:根据key
获取WeakMap
中的value
has(key)
:判断是否包括某一个key
,返回Boolean
类型delete(key)
:根据key
删除一个键值对,返回Boolean
类型
4. WeakMap的应用
注意:
WeakMap
也是不能遍历的- 没有
forEach
方法,也不支持通过for...of
的方式进行遍历
- 没有
那么我们的
WeakMap
有什么作用呢?(后续专门讲解)js// WeakMap({key(对象):value}): key是一个对象,弱引用 const targetMap = new WeakMap() function getDep(target, key) { // 1. 根据对象(target)取出对应的Map对象 let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } // 2. 取出具体的dep对象 let dep = depsMap.get(key) if (!dep) { dep = new Dep() depsMap.set(key, dep) } return dep }