zhangdizhangdi

类型判断、深浅克隆

参考

typeof

js
typeof operand

结果: "undefined" "boolean" "number" "string" "object" "function" "symbol" "bigint"

instanceof

js
object instanceof constructor

结果: true false

类型判断

js
function getType(target) {
  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()
  • 使用扩展运算符(…)实现的复制
js
function getType(target) {
  return Object.prototype.toString.call(target).slice(8, -1).toLowerCase()
}

function shallowClone(target) {
  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, b: 2, c: { c1: 3, c2: 4 } }
const sd = [4, 5, 5]
console.log('原:', sa, sb, sc, sd)
const ca = shallowClone(sa)
const cb = shallowClone(sb)
const cc = shallowClone(sc)
const cd = shallowClone(sd)
sd[2] = 90
sc.b = 23
sc.c.c1 = 90
console.log('原:', sa, sb, sc, sd)
console.log('复:', ca, cb, cc, cd)
执行结果
原: 1 bbb { a: 1, b: 2, c: { c1: 3, c2: 4 } } [ 4, 5, 5 ]
原: 1 bbb { a: 1, b: 23, c: { c1: 90, c2: 4 } } [ 4, 5, 90 ]
复: 1 bbb { a: 1, b: 2, c: { c1: 90, c2: 4 } } [ 4, 5, 5 ]

Object.assign

js
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('🌰 bo', bo)
console.log('🌰 c', c)
bo.name = 'b2222'
bo.book.price = 30
console.log('🌰 bo', bo)
console.log('🌰 c', c)
执行结果
🌰 bo { name: 'bbb', book: { title: "You Don't Know JS", price: 45 } }
🌰 c {
  name: 'bbb',
  age: 18,
  book: { title: "You Don't Know JS", price: 45 }
}
🌰 bo { name: 'b2222', book: { title: "You Don't Know JS", price: 30 } }
🌰 c {
  name: 'bbb',
  age: 18,
  book: { title: "You Don't Know JS", price: 30 }
}

深拷贝

常见的深拷贝方式有:

  1. lodash.cloneDeep()
  2. jQuery.extend()
  3. JSON.stringify() (会忽略 undefined、symbol、function、Map、Set,循环引用)
  4. 手写循环递归(考虑 Map、Set 等数据类型,及循环引用)
参考
js
function getType(target) {
  return Object.prototype.toString.call(target).slice(8, -1).toLowerCase()
}

// ??? Symbol
function deepClone(target, 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)
执行结果
a  {
  aset: Set(3) { 10, 20, 30 },
  amap: Map(3) { 'x' => 10, 'y' => 20, { a: 'test' } => 40 },
  info: { city: '北京' },
  date: 2025-08-02T14:09:04.164Z,
  fun: [Function: fun],
  self: [Circular *1],
  [Symbol(a)]: 'a'
}
b  {
  aset: Set(3) { 10, 20, 30 },
  amap: Map(3) { 'x' => 10, 'y' => 20, { a: 'd' } => 30 },
  info: { city: '北京' },
  date: 2025-08-02T14:09:04.164Z,
  fun: [Function: anonymous],
  self: [Circular *1],
  [Symbol(a)]: 'b'
}
set false
map false
info false
date false
fun false
self false