类型判断、深浅克隆
参考
- JavaScript 数据类型和数据结构 - MDN
- typeof - MDN
- instanceof - MDN
typeof、instanceof
typeof
js
typeof operand
结果: "undefined"
"boolean"
"number"
"string"
"object"
"function"
"symbol"
"bigint"
instanceof
js
object instanceof constructor
结果: true
false
类型判断
ts
function getType(target: any) {
return Object.prototype.toString.call(target).slice(8, -1).toLowerCase()
}
toString
是 Object 的原型方法,而 Array、Function 等类型作为 Object 的实例,都重写了 toString 方法。
不同的对象类型调用 toString 方法时,根据原型链的知识,调用的是对应的重写之后的 toString 方法(Function 类型返回内容为函数体的字符串,Array 类型返回元素组成的字符串),而不会去调用 Object 上原型 toString 方法(返回对象的具体类型)。
浅拷贝
在 JavaScript 中,存在浅拷贝的现象有:
- Object.assign
- Array.prototype.slice(), Array.prototype.concat()
- 使用扩展运算符(...)实现的复制
ts
function shallowClone(target: any) {
const type = getType(target)
if (['object', 'array'].includes(type)) {
const cloneTarget = type === 'object' ? {} : []
for (let key in target) {
// if (target.hasOwnProperty(key)) {
if (Object.prototype.hasOwnProperty.call(target, key) === true) {
cloneTarget[key] = target[key]
}
}
return cloneTarget
} else {
return target
}
}
const sa = 1
const sb = 'bbb'
const sc = { a: 1, ba: 2 }
const sd = [4, 5, 5]
const ca = shallowClone(sa)
const cb = shallowClone(sb)
const cc = shallowClone(sc)
const cd = shallowClone(sd)
cd[2] = 90
console.log(ca, cb, cc, cd)
Object.assign
- Object.assign 原理及其实现 - 木易杨 博客
ts
Object.defineProperty(Object, '_assign', {
value: function (target, ...args) {
if (target == null) {
return new TypeError('Cannot convert undefined or null to object')
}
const to = Object(target)
for (let i = 0; i < args.length; i++) {
const obj = args[i]
if (obj !== null) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key) === true) {
// if (obj.hasOwnProperty(key)) {
to[key] = obj[key]
}
}
}
}
return to
},
// 不可枚举
enumerable: false,
writable: true,
configurable: true,
})
const ao = {
name: 'aaa',
age: 18,
}
const bo = {
name: 'bbb',
book: {
title: "You Don't Know JS",
price: 45,
},
}
let c = Object._assign(ao, bo)
console.log(c)
bo.book.price = 30
console.log(c)
深拷贝
常见的深拷贝方式有:
- lodash.cloneDeep()
- jQuery.extend()
- JSON.stringify() (会忽略 undefined、symbol、function、Map、Set,循环引用)
- 手写循环递归(考虑 Map、Set 等数据类型,及循环引用)
参考
- 详细解析赋值、浅拷贝和深拷贝的区别 - 木易杨 博客
没有考虑 map、set
- 2021 年前端各大公司都考了那些手写题(附带代码) - 掘金
答案详细
- 精进 JavaScript | 这些手写你都会吗 ? - 掘金
很简洁
ts
// ??? Symbol
function deepClone(target: any, map = new WeakMap()) {
// 基本数据类型,直接返回
if (
(typeof target !== 'object' && typeof target !== 'function') ||
target === null
)
return target
const type = getType(target)
// 函数 正则 日期,执行构造体,返回新的对象
const constructor = target.constructor
if (['function', 'regexp', 'date'].includes(type))
return new constructor(target)
// map标记每一个出现过的属性,避免循环引用
if (map.get(target)) return map.get(target)
let cloneTarget = Array.isArray(target) ? [] : {}
map.set(target, cloneTarget)
//处理Map类型
switch (type) {
//Map,Set的key深拷贝,key可以是任意类型
case 'map':
cloneTarget = new Map()
target.forEach((item, key) => {
cloneTarget.set(deepClone(key, map), deepClone(item, map))
})
break
case 'set':
cloneTarget = new Set()
target.forEach(item => {
cloneTarget.add(deepClone(item, map))
})
break
default:
//object array
//for in循环不出symbol
Reflect.ownKeys(target).forEach(key => {
cloneTarget[key] = deepClone(target[key], map)
})
break
}
return cloneTarget
}
const a = {
aset: new Set([10, 20, 30]),
amap: new Map([
['x', 10],
['y', 20],
]),
info: {
city: '北京',
},
date: new Date(),
fun: () => {
console.log('100')
},
}
a.self = a
const mkey = { a: 'd' }
a.amap.set(mkey, 30)
const skey = Symbol('a')
a[skey] = 'a'
const b = deepClone(a)
a.amap.set(mkey, 40)
mkey.a = 'test'
b[skey] = 'b'
console.log('a', a)
console.log('b', b)
console.log('set', a.aset === b.aset)
console.log('map', a.amap === b.amap)
console.log('info', a.info === b.info)
console.log('date', a.date === b.date)
console.log('fun', a.fun === b.fun)
console.log('self', a.self === b.self)