数组去重
使用 indexOf
和 includes
indexOf(arr = []) {
const res = []
arr.forEach((item) => {
if (res.indexOf(item) === -1) { //!res.includes(item)
res.push(item)
}
})
return res
}
filter
filter(arr = []) {
return arr.filter((item, index, arr) => {
return arr.indexOf(item) !== item
})
}
filter(arr=[]) {
// arr.concat() 复制出来一份原有的数组,且对复制出来的新数组的操作不会影响到原有数组
return arr.concat()sort().filter((item,index,arr) => {
return !item || arr[index - 1] !== item
})
}
Object 键值对
const array = [{value: 1}, {value: 1}, {value: 2}];
unique(array = []) {
const obj = {};
return array.filter(function(item, index, array){
// console.log(typeof item + JSON.stringify(item))
return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
})
}
console.log(unique(array)); // [{value: 1}, {value: 2}]
Set 、Map 和 …rest
使用 Set 可以区分出
NAN
, 因为NAN
不等于自身,所以indexOf
不能查找到,而使用Set
就可以解决这个问题
注意: 由于{} !== {} // true
, 所以对象默认是不重复的,使用JSON.stringify
可以解决这个问题
setArr(arr = []) {
// Set 是值的集合,类似于数组,但是每个值都是唯一的
return [...new Set(arr)]
}
mapArr(arr = []) {
// Map 是一个键值对集合,先检查有没有这哥属性,没有就添加尽这个集合中
const seen = new Map(arr)
return arr.filter(item => !seen.has(a) && seen.set(a,1))
}
数组分组
使用 forEach
遍历分组
const list = [
{ label: 'a', value: 1 },
{ label: 'c', value: 3 },
{ label: 'b', value: 1 },
{ label: 'a', value: 3 },
{ label: 'c', value: 2 }
]
const collect = {}
list.forEach(item => {
// console.log(item)
if (!collect[item.label]) {
collect[item.label] = []
}
collect[item.label].push(item)
})
// 处理后的数据
collect = {
a: [1, 3],
b: [1],
c: [3, 2]
}
// 可以使用entries(),keys(),values()来获取所需要的值
使用 30 seconds of code
里 reduce
的方法
const groupBy = (arr, fn) =>
arr
.map(typeof fn === 'function' ? fn : val => val[fn])
.reduce((acc, val, i) => {
acc[val] = (acc[val] || []).concat(arr[i])
return acc
}, {})
const collect = groupBy(list, item => item.label)
浅拷贝和深拷贝
浅拷贝
只是对基本类型进行了拷贝,类似于数组和对象这一种的引用类型的值,只会拷贝其在内存中的引用,依然是通过引用指向同一块堆内存,无论新旧数组发生改变,都会改变内存中值,两者的值同时发生改变
- 数组自带的方法:
slice
、concat
、Array.from()
const arr = [1, 2, [3, 4]] arr.concat() arr.slice() Array.from(arr)
- 对象和数组的结构也可以实现浅拷贝
const [...arr] = [1, 2, [3, 4]] const obj = { ...{ a: 1, b: 2, f: { c: 3, d: 4 } } }
- 简单的函数实现
function clone(o) { const obj = {} for (let i in o) { obj[i] = o[i] } return obj }
深拷贝
JSON
字符串化(你不知道的 JavaScript(中))工具函数
JSON.stringify()
在将JSON
对象序列化为字符串时并非严格意义上的强制类型转换,而是使用了ToString
。
所有安全的 JSON 值(JSON-safe)都可以使用 JSON.stringify(..) 字符串化。安全的 JSON 值是指能够呈现为有效 JSON 格式的值。不安全的 JSON 值
- undefined => null
- function => null
- symbol => null
- 循环引用(对象之间循环引用,形成了无限循环) => 程序报错
JSON.stringify(undefined) // undefined JSON.stringify(function() {}) // undefined JSON.stringify([1, undefined, function() {}, 4]) //"[1, null, null, 4]" JSON.stringify({ a: 2, b: function() {} }) // "{"a": 2}"
如果队形中定义了
toJSON()
方法,JSON
字符串化会首先调用该方法,然后用返回值进行序列化,包含循环引用的对象执行JSON.stringify(...)
会报错toJSON()
应该“返回一个能够被字符串化的安全的JSON
值”,而不是“返回 一个JSON
字符串”const o = {} const a = { b: 42, c: o, d: function() {} } // 在a中创造一个循环引用 o.e = a JSON.stringify(a) //循环引用会报错 // 自定义JSON序列化 a.toJSON = () => { // 序列化尽包涵 b return { b: this.b } } JSON.stringify(a) // "{"b": 42}"
参数
- replacer(数组或者函数)
- 数组:必须是一个字符串数组,其中包含序列化要处理的对象 的属性名称,除此之外其他的属性则被忽略。
- 函数,它会对对象本身调用一次,然后对对象中的每个属性各调用一次,每次传递两个参数(key, value)。如果要忽略某个键就返回 undefined,否则返回指定的值。
const a = { b: 42, c: '42', d: [1, 2, 3] } JSON.stringify(a, ['b', 'c']) // "{"b":42,"c":"42"}" JSON.stringify(a, function(k, v) { if (k !== 'c') return v }) // "{"b":42,"d":[1,2,3]}"
- space: 指定输出的缩进格式
简单的深拷贝
const newObj = JSON.parse(JSON.stringify(oldObj))
缺陷:
- 无法实现对函数 、
RegExp
等特殊对象的克隆 - 会抛弃对象的
constructor
,所有的构造函数会指向Object
- 循环引用对象,会报错
2、真正意义上的深拷贝
在拷贝的时候判断一下属性值的类型,如果是对象,面对不同的对象(正则、数组、Date 等)要采用不同的处理方式,我们递归调用深拷贝函数
function deepCopy(obj) {
if (typeof obj !== 'object') return
const newObj = obj instanceof Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]
}
}
return newObj
}
更加完善的深拷贝
来自面试官:请你实现一个深克隆
我们需要通过正则的扩展了解到
flags
属性等等,因此我们需要实现一个提取flags
的函数.
const getRegExp = re => {
var flags = ''
if (re.global) flags += 'g'
if (re.ignoreCase) flags += 'i'
if (re.multiline) flags += 'm'
return flags
}
/**
* deep clone
* @param {[type]} parent object 需要进行克隆的对象
* @return {[type]} 深克隆后的对象
* classType 类型判断函数
*/
const clone = parent => {
// 维护两个储存循环引用的数组
const parents = []
const children = []
const _clone = parent => {
if (parent === null) return null
if (typeof parent !== 'object') return parent
let child, proto
if (classType(parent) === 'array')) {
// 对数组做特殊处理
child = []
} else if (classType(parent) === 'regexp')) {
// 对正则对象做特殊处理
child = new RegExp(parent.source, getRegExp(parent))
if (parent.lastIndex) child.lastIndex = parent.lastIndex
} else if (classType(parent) === 'date')) {
// 对Date对象做特殊处理
child = new Date(parent.getTime())
} else {
// 处理对象原型
proto = Object.getPrototypeOf(parent)
// 利用Object.create切断原型链
child = Object.create(proto)
}
// 处理循环引用
const index = parents.indexOf(parent)
if (index != -1) {
// 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
return children[index]
}
parents.push(parent)
children.push(child)
// 递归
for (let i in parent) {
//过滤原型上的属性
if(parent.hasOwnProperty(i)){
child[i] = _clone(parent[i])
}
}
return child
}
return _clone(parent)
}