1. Map 和 Object 的不同
API 不同
// 初始化 const m = new Map([ ['key1', 'hello'], ['key2', 100], ['key3', { x: 10 }] ]) // 新增 m.set('name', '双越') // 删除 m.delete('key1') // 判断 m.has('key2') // 遍历 m.forEach((value, key) => console.log(key, value)) // 长度(Map 是有序的,下文会讲,所有有长度概念) m.size
以任意类型为 key
const m = new Map() const o = { p: 'Hello World' } m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false
Map 是有序结构
Object key 不能按照既定顺序输出
// Object keys 是无序的 const data1 = {'1':'aaa','2':'bbb','3':'ccc','测试':'000'} Object.keys(data1) // ["1", "2", "3", "测试"] const data2 = {'测试':'000','1':'aaa','3':'ccc','2':'bbb'}; Object.keys(data2); // ["1", "2", "3", "测试"]
Map key 可以按照既定顺序输出
const m1 = new Map([ ['1', 'aaa'], ['2', 'bbb'], ['3', 'ccc'], ['测试', '000'] ]) m1.forEach((val, key) => { console.log(key, val) }) const m2 = new Map([ ['测试', '000'], ['1', 'aaa'], ['3', 'ccc'], ['2', 'bbb'] ]) m2.forEach((val, key) => { console.log(key, val) })
Map 很快
Map 作为纯净的 key-value 数据结构,它比 Object 承载了更少的功能。
Map 虽然有序,但操作很快,和 Object 效率相当。
// Map const m = new Map() for (let i = 0; i < 1000 * 10000; i++) { m.set(i + '', i) } console.time('map find') m.has('2000000') console.timeEnd('map find') console.time('map delete') m.delete('2000000') console.timeEnd('map delete')
// Object const obj = {} for (let i = 0; i < 1000 * 10000; i++) { obj[i + ''] = i } console.time('obj find') obj['200000'] console.timeEnd('obj find') console.time('obj delete') delete obj['200000'] console.timeEnd('obj delete')
另外,Map 有序,指的是 key 能按照构架顺序输出,并不是说它像数组一样是一个有序结构 —— 否则就不会这么快了
但这就足够满足我们的需求了。
WeakMap
WeakMap 也是弱引用。但是,WeakMap 弱引用的只是键名 key ,而不是键值 value。
// 函数执行完,obj 会被销毁,因为外面的 WeakMap 是“弱引用”,不算在内 const wMap = new WeakMap() function fn() { const obj = { name: 'zhangsan' } // 注意,WeakMap 专门做弱引用的,因此 WeakMap 只接受对象作为键名(`null`除外),不接受其他类型的值作为键名。其他的无意义 wMap.set(obj, 100) } fn() // 代码执行完毕之后,obj 会被销毁,wMap 中也不再存在。但我们无法第一时间看到效果。因为: // 内存的垃圾回收机制,不是实时的,而且是 JS 代码控制不了的,因此这里不一定能直接看到效果。
另外,WeakMap 没有 forEach
和 size
,只能 add
delete
has
。因为弱引用,其中的 key 说不定啥时候就被销毁了,不能遍历。
WeakMap 可以做两个对象的关联关系,而不至于循环引用,例如:
const userInfo = { name: '双越' } const cityInfo = { city: '北京' } // // 常规关联,可能会造成循环引用 // userInfo.city = cityInfo // cityInfo.user = userInfo // 使用 WeakMap 做关联,则无任何副作用 const user_to_city = new WeakMap() user_to_city.set(userInfo, cityInfo)
总结
- key 可以是任意数据类型
- key 会按照构建顺序输出
- 很快
- WeakMap 弱引用
2. Set 和数组的区别
Set 元素不能重复
const arr = [10, 20, 30, 30, 40] const set = new Set([10, 20, 30, 30, 40]) // 会去重 console.log(set) // Set(4) {10, 20, 30, 40}
// 数组去重 function unique(arr) { const set = new Set(arr) return [...set] } unique([10, 20, 30, 30, 40])
API 不一样
// 初始化 const set = new Set([10, 20, 30, 30, 40]) // 新增(没有 push unshift ,因为 Set 是无序的,下文会讲) set.add(50) // 删除 set.delete(10) // 判断 set.has(20) // 长度 set.size // 遍历 set.forEach(val => console.log(val)) // set 没有 index ,因为是无序的
Set 是无序的,而数组是有序的 —— 这一点很少有人提到,却很关键!!!
先看几个测试
- 数组:前面插入元素 vs 后面插入元素
- 数组插入元素 vs Set 插入元素
- 数组寻找元素 vs Set 寻找元素
// 构造一个大数组 const arr = [] for (let i = 0; i < 1000000; i++) { arr.push(i) } // 数组 前面插入一个元素 console.time('arr unshift') arr.unshift('a') console.timeEnd('arr unshift') // unshift 非常慢 // 数组 后面插入一个元素 console.time('arr push') arr.push('a') console.timeEnd('arr push') // push 很快 // 构造一个大 set const set = new Set() for (let i = 0; i < 1000000; i++) { set.add(i) } // set 插入一个元素 console.time('set test') set.add('a') console.timeEnd('set test') // add 很快 // 最后,同时在 set 和数组中,寻找一个元素 console.time('set find') set.has(490000) console.timeEnd('set find') // set 寻找非常快 console.time('arr find') arr.includes(490000) console.timeEnd('arr find') // arr 寻找较慢
什么是无序,什么是有序?参考 x1-有序和无序.md
- 无序:插入、查找更快
- 有序:插入、查找更慢
因此,如果没有强有序的需求,请用 Set ,会让你更快更爽!
WeakSet
WeekSet 和 Set 类似,区别在于 —— 它不会对元素进行引用计数,更不容易造成内存泄漏。
// 函数执行完,obj 就会被 gc 销毁 function fn() { const obj = { name: 'zhangsan' } } fn()
// 函数执行完,obj 不会被销毁,因为一直被外面的 arr 引用着 const arr = [] function fn() { const obj = { name: 'zhangsan' } arr.push(obj) } fn()
// 函数执行完,obj 会被销毁,因为外面的 WeakSet 是“弱引用”,不算在内 const wSet = new WeakSet() function fn() { const obj = { name: 'zhangsan' } wSet.add(obj) // 注意,WeakSet 就是为了做弱引用的,因此不能 add 值类型!!!无意义 } fn()
【注意】内存的垃圾回收机制,不是实时的,而且是 JS 代码控制不了的,因此这里不一定能直接看到效果。
WeekSet 没有 forEach
和 size
,只能 add
delete
和 has
。因为垃圾回收机制不可控(js 引擎看时机做垃圾回收),那其中的成员也就不可控。
总结
- Set 值不能重复
- Set 是无序结构
- WeakSet 对元素若引用
3. 数组求和
传统方式
function sum(arr) { let res = 0 arr.forEach(n => res = res + n) return res } const arr = [10, 20, 30] console.log( sum(arr) )
reduce 方法的使用
// 累加器 const arr1 = [10, 20, 30, 40, 50] const arr1Sum = arr1.reduce((sum, curVal, index, arr) => { console.log('reduce function ......') console.log('sum', sum) console.log('curVal', curVal) console.log('index', index) console.log('arr', arr) return sum + curVal // 返回值,会作为下一次执行的 sum }, 0) console.log('arr1Sum', arr1Sum)
reduce 的其他用法
// 计数 function count(arr, value) { // 计算 arr 中有几个和 value 相等的数 return arr.reduce((c, item) => { return item === value ? c + 1 : c }, 0) } const arr2 = [10, 20, 30, 40, 50, 10, 20, 10] console.log( count(arr2, 20) )
// 数组输出字符串 const arr3 = [ { name: 'xialuo', number: '100' }, { name: 'madongmei', number: '101' }, { name: 'zhangyang', number: '102' } ] // // 普通做法 1(需要声明变量,不好) // let arr3Str = '' // arr3.forEach(item => { // arr3Str += `${item.name} - ${item.number}\n` // }) // console.log(arr3Str) // // 普通做法 2(map 生成数组,再进行 join 计算) // console.log( // arr3.map(item => { // return `${item.name} - ${item.number}` // }).join('\n') // ) // reduce 做法(只遍历一次,即可返回结果) console.log( arr3.reduce((str, item) => { return `${str}${item.name} - ${item.number}\n` }, '') )