JavaScript 浅拷贝、深拷贝

1|0概念

ECMAScript变量包含两种不同数据类型的值:基本数据类型和引用数据类型。
基本数据类型:名值存储在栈内存中;
引用数据类型:名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值。
目前基本数据类型有:Boolean、Null、Undefined、Number、String、Symbol,引用数据类型有:Object、Array、Function、RegExp、Date等
深拷贝与浅拷贝的概念只存在于引用数据类型

2|0浅拷贝

当变量被赋值对象时,两者的值会是同一个引用,其中一方改变,另一方也会相应改变,针对引用类型需要实现数据的拷贝。

1|0用 ... 实现
let copy = {...{key: 1}}
1|0用 concat 实现
let arr = ["One","Two","Three"]; let copy = arr.concat();
1|0用 Object.assign 实现
let copy = Object.assign({}, {key: 1})
1|0用 slice 实现
let arr = [1, 3, {key: 1}]; let copy = arr.slice();

3|0深拷贝

浅拷贝只能解决第一层的问题,如果接下去的值中还有对象的话,则需要使用深拷贝。

1|0通过 JSON 转换实现

缺点:

  • 对于 function、undefined,会丢失这些属性。
  • 对于 RegExp、Error 对象,只会得到空对象
  • 对于 date 对象,得到的结果是 string,而不是 date 对象
  • 对于 NaN、Infinity、-Infinity,会变成 null
  • 无法处理循环引用
const obj = {a: 1, b: {key: 1}} const copy = JSON.parse(JSON.stringify(obj))
1|0简单版
  • 判断类型是否为原始类型,如果是,无需拷贝,直接返回
  • 为避免出现循环引用,拷贝对象时先判断存储空间中是否存在当前对象,如果有就直接返回
  • 开辟一个存储空间,来存储当前对象和拷贝对象的对应关系
  • 对引用类型递归拷贝直到属性为原始类型
const deepClone = (target, cache = new WeakMap()) => { if(target === null || typeof target !== 'object') { return target } if(cache.get(target)) { return target } const copy = Array.isArray(target) ? [] : {} cache.set(target, copy) Object.keys(target).forEach(key => copy[key] = deepClone(target[key], cache)) return copy } 复制代码

缺点:无法拷贝函数、MapSet、正则等其他类型

1|0VUEX版

vuex源码

  • 原理与上一版类似
function find(list, f) { return list.filter(f)[0] } function deepCopy(obj, cache = []) { // just return if obj is immutable value if (obj === null || typeof obj !== 'object') { return obj } // if obj is hit, it is in circular structure const hit = find(cache, c => c.original === obj) if (hit) { return hit.copy } const copy = Array.isArray(obj) ? [] : {} // put the copy into cache at first // because we want to refer it in recursive deepCopy cache.push({ original: obj, copy }) Object.keys(obj).forEach(key => copy[key] = deepCopy(obj[key], cache)) return copy } 复制代码
1|0复杂兼容版

解析:如何写出一个惊艳面试官的深拷贝

const mapTag = '[object Map]'; const setTag = '[object Set]'; const arrayTag = '[object Array]'; const objectTag = '[object Object]'; const argsTag = '[object Arguments]'; const boolTag = '[object Boolean]'; const dateTag = '[object Date]'; const numberTag = '[object Number]'; const stringTag = '[object String]'; const symbolTag = '[object Symbol]'; const errorTag = '[object Error]'; const regexpTag = '[object RegExp]'; const funcTag = '[object Function]'; const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag]; function forEach(array, iteratee) { let index = -1; const length = array.length; while (++index < length) { iteratee(array[index], index); } return array; } function isObject(target) { const type = typeof target; return target !== null && (type === 'object' || type === 'function'); } function getType(target) { return Object.prototype.toString.call(target); } function getInit(target) { const Ctor = target.constructor; return new Ctor(); } function cloneSymbol(targe) { return Object(Symbol.prototype.valueOf.call(targe)); } function cloneReg(targe) { const reFlags = /\w*$/; const result = new targe.constructor(targe.source, reFlags.exec(targe)); result.lastIndex = targe.lastIndex; return result; } function cloneFunction(func) { const bodyReg = /(?<={)(.|\n)+(?=})/m; const paramReg = /(?<=\().+(?=\)\s+{)/; const funcString = func.toString(); if (func.prototype) { const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if (body) { if (param) { const paramArr = param[0].split(','); return new Function(...paramArr, body[0]); } else { return new Function(body[0]); } } else { return null; } } else { return eval(funcString); } } function cloneOtherType(targe, type) { const Ctor = targe.constructor; switch (type) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(targe); case regexpTag: return cloneReg(targe); case symbolTag: return cloneSymbol(targe); case funcTag: return cloneFunction(targe); default: return null; } } function clone(target, map = new WeakMap()) { // 克隆原始类型 if (!isObject(target)) { return target; } // 初始化 const type = getType(target); let cloneTarget; if (deepTag.includes(type)) { cloneTarget = getInit(target, type); } else { return cloneOtherType(target, type); } // 防止循环引用 if (map.get(target)) { return map.get(target); } map.set(target, cloneTarget); // 克隆set if (type === setTag) { target.forEach(value => { cloneTarget.add(clone(value, map)); }); return cloneTarget; } // 克隆map if (type === mapTag) { target.forEach((value, key) => { cloneTarget.set(key, clone(value, map)); }); return cloneTarget; } // 克隆对象和数组 const keys = type === arrayTag ? undefined : Object.keys(target); forEach(keys || target, (value, key) => { if (keys) { key = value; } cloneTarget[key] = clone(target[key], map); }); return cloneTarget; } 复制代码
1|0高性能版

解析:你不知道的高性能实现深拷贝的方式

const MY_IMMER = Symbol('my-immer1') const isPlainObject = value => { if ( !value || typeof value !== 'object' || {}.toString.call(value) != '[object Object]' ) { return false } var proto = Object.getPrototypeOf(value) if (proto === null) { return true } var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor return ( typeof Ctor == 'function' && Ctor instanceof Ctor && Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object) ) } const isProxy = value => !!value && !!value[MY_IMMER] function produce(baseState, fn) { const proxies = new Map() const copies = new Map() const objectTraps = { get(target, key) { if (key === MY_IMMER) return target const data = copies.get(target) || target return getProxy(data[key]) }, set(target, key, val) { const copy = getCopy(target) const newValue = getProxy(val) // 这里的判断用于拿 proxy 的 target // 否则直接 copy[key] = newValue 的话外部拿到的对象是个 proxy copy[key] = isProxy(newValue) ? newValue[MY_IMMER] : newValue return true } } const getProxy = data => { if (isProxy(data)) { return data } if (isPlainObject(data) || Array.isArray(data)) { if (proxies.has(data)) { return proxies.get(data) } const proxy = new Proxy(data, objectTraps) proxies.set(data, proxy) return proxy } return data } const getCopy = data => { if (copies.has(data)) { return copies.get(data) } const copy = Array.isArray(data) ? data.slice() : { ...data } copies.set(data, copy) return copy } const isChange = data => { if (proxies.has(data) || copies.has(data)) return true } const finalize = data => { if (isPlainObject(data) || Array.isArray(data)) { if (!isChange(data)) { return data } const copy = getCopy(data) Object.keys(copy).forEach(key => { copy[key] = finalize(copy[key]) }) return copy } return data } const proxy = getProxy(baseState) fn(proxy) return finalize(baseState) }

__EOF__

本文作者shmillly959
本文链接https://www.cnblogs.com/shmillly959/p/13723866.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   shmillly959  阅读(78)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示