一. 展开运算符的使用
展开语法(
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()方法来获取对应的keySymbol.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 集合
引言
在 ES6 之前,我们存储数据的结构主要有两种:数组、对象。
在 ES6 中新增了另外两种数据结构:Set、Map,以及它们的另外形式 WeakSet、WeakMap。
基本使用
Set 对象允许你存储任何类型(无论是原始值还是对象引用)的唯一值。
Set 对象是值的集合,集合中的每一个值都是唯一的,如果存在多个相等的值,则只会保留最后一个添加进去的值。
创建 Set 对象:需要通过 Set 构造函数(暂时没有字面量创建的方式)
jsnew 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']
属性和方法
常见属性:
size:返回 Set 中元素的个数jsconst set = new Set([1, 2, 3]) set.size // 3
常用方法:
add(value):添加某个元素,返回 Set 对象本身jsset.add(100) // Set(4) {1, 2, 3, 100}delete(value):从 Set 中删除和这个值相等的元素,返回 true / false。jsset.delete(100) // Set(3) {1, 2, 3}has(value):判断 set 中是否存在某个元素,返回 true / false。jsset.has(3) // trueclear():清空 Set 中所有的元素,无返回值。jsset.clear() console.log(set) // Set(0) {size: 0}forEach(callback, [, thisArg]):通过 forEach 遍历 setjsset.forEach(console.log) // 1 2 3
其他注意事项:
支持 for...of 遍历的,因为 Set 是可迭代对象jsfor (const i of set) console.log(i) // 1 2 3不支持 for...in 遍历,因为遍历不出 keyjsfor(const i in set) console.log('----') // 什么都不会输出
WeakSet
和 Set 类似的另外一个数据结构称之为 WeakSet,也是
内部元素不能重复的数据结构。
那么和 Set 有什么区别呢?
- 区别一:WeakSet 中
只能存对象类型(null 也不支持),不能存基本数据类型 - 区别二:WeakSet 对
元素是弱引用,如果没有其他引用对某个对象进行引用,那么该对象可以被 GC 回收
常见方法:
add(value):添加某个元素,返回 WeakSet 对象本身。delete(value):从 WeakSet 中删除和这个值相等的元素,返回 true / false。has(value):判断 WeakSet 中是否存在某个元素,返回 true / false。
// 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的区别二: 对对象的引用都是弱引用WeakSet 的应用
⚠️ 注意
【 WeakSet 不能遍历 】
因为 WeakSet 只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。
所以存储到 WeakSet 中的对象是没办法获取的。
那么这个东西有什么用呢?
事实上这个问题并不好回答,我们来使用一个 Stack Overflow 上的答案。
// WeakSet的应用
// 这里之所以不用 Set,是因为 Set 是强引用,如果在后续我们想对 new 构造的某个实例对象进行销毁时,
// 强引用会导致该实例对象内存中的地址仍然被 Set 引用着,而 WeekSet 却不会,
// 因为是弱引用,如果没有其他引用指向该实例对象,后续 GC 会回收掉。
const pWeakSet = new WeakSet()
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 映射
基本使用
另外一个新增的数据结构是 Map,用于存储映射关系。
但是我们可能会想,在之前我们可以使用对象来存储映射关系,为什么还需要 Map 来存储映射关系呢?
- 在对象中存储映射关系,我们只能用字符串作为属性名(key),后来 ES6 又新增了 Symbol。
- 某些情况下,我们可能希望通过其他类型作为 key ,比如对象类型,但在普通的对象中会自动将对象类型的 key 转成字符串类型的 key。
Map 允许使用对象类型作为 key。那么我们就可以使用 Map 来解决这个问题:
const info = { name: 'later' }
const info2 = { age: 18 }
// 1. 对象类型的局限性:不可以使用复杂类型如对象,作为key
const obj = {
[info]: 'haha' // [info] 计算属性 对象类型的key被转为字符串类型的key => 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"}属性和方法
常见属性:
size:返回 Map 中元素的个数。
常见方法:
set(key, value):往 Map 中添加 key 和 value,返回添加后的 Map 对象。get(key):获取 Map 中 指定 key 对应的 value,如果 key 不存在,则返回 undefined。has(key):判断是否包括某一个 key,返回 true / false。delete(key):根据 key 删除一个键值对,返回 true / false。clear():清空所有的元素。forEach(callback, [, thisArg]):通过 forEach 遍历 Map。
Map 也可以通过 for...of 进行遍历,可迭代对象:
const obj1 = {name: 'later'}
const obj2 = {age: 18}
const obj3 = {sex: 'male'}
// 使用常规的 Map 构造函数可以将一个二维的键值对数组转换成一个 Map 对象
const map = new Map(
[
[obj1, 'aaa'],
[obj2, 'bbb']
]
)
console.log(map) // Map(2) {{…} => 'aaa', {…} => 'bbb'}
console.log(map.get(obj1)) // 'aaa'
// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维的键值对数组
console.log(
Array.from(map)
) // [[obj1, 'aaa'],[obj2, 'bbb']]
// 更简洁的方法来做如上同样的事情,使用展开运算符
console.log(
[...map]
) // [[obj1, 'aaa'],[obj2, 'bbb']]
// 或者在键或者值的迭代器上使用 Array.from,进而得到只含有键或者值的数组
console.log(
Array.from(map.keys())
) // 输出 [obj1, obj2]
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(value => console.log(value)) // 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'WeakMap
和 Map 类型的另外一个数据结构称之为 WeakMap,也是以
键值对的形式存在的。
那么和 Map 有什么区别呢?
- 区别一:WeakMap
key 只能是对象类型。 - 区别二:WeakMap 的
key 对对象的引用是弱引用,如果没有其他引用指向这个对象,GC 可以回收该对象。
const 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常见方法:
set(key, value):往 WeakMap 中添加 key 和 value,返回添加后的 WeakMap 对象。get(key):获取 WeakMap 中指定 key 对应的 value。has(key):判断是否包括某一个 key,返回 true / false。delete(key):根据 key 删除一个键值对,返回 true / false。
WeakMap 的应用
⚠️ 注意
WeakMap 也是不能遍历的,没有 forEach 方法,也不支持通过 for...of 的方式进行遍历。
那么我们的 WeakMap有 什么作用呢?vue 响应式系统中有用到。
// 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
}