Terdapat banyak artikel mengenai penyalinan mendalam di Internet, tetapi kualitinya berbeza-beza, kebanyakannya tidak difikirkan dengan baik, dan kaedah penulisannya kasar dan tidak memuaskan. Artikel ini bertujuan untuk melengkapkan salinan dalam yang sempurna Jika anda mempunyai sebarang soalan selepas membacanya, sila tambah dan perbaikinya.
Untuk menilai sama ada salinan dalam telah lengkap, sila semak sama ada soalan berikut telah dilaksanakan:
基本类型数据
Bolehkah ia disalin?
Kunci dan nilai adalah kedua-dua jenis asas普通对象
Bolehkah ia disalin?
Symbol
Bolehkah kunci Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek disalin? Bolehkah jenis Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek
Date
dan RegExp
disalin? Bolehkah jenis Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek
Map
dan Set
disalin?
Function
Bolehkah jenis Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek disalin? (Kami biasanya tidak menggunakan salinan dalam untuk fungsi) Bolehkah
原型
boleh disalin?
不可枚举属性
Bolehkah saya menyalin?
循环引用
Bolehkah saya menyalin?
Bagaimana? Adakah salinan dalam yang anda tulis cukup sempurna?
Versi kod akhir diberikan terus di sini untuk kemudahan mereka yang ingin mengetahui Sudah tentu, jika anda ingin memahami langkah demi langkah, anda boleh terus membaca artikel yang lain:
function deepClone(target) { const map = new WeakMap() function isObject(target) { return (typeof target === 'Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptect' && target ) || typeof target === 'function' } function clone(data) { if (!isObject(data)) { return data } if ([Date, RegExp].includes(data.constructor)) { return new data.constructor(data) } if (typeof data === 'function') { return new Function('return ' + data.toString())() } const exist = map.get(data) if (exist) { return exist } if (data instanceof Map) { const result = new Map() map.set(data, result) data.forEach((val, key) => { if (isObject(val)) { result.set(key, clone(val)) } else { result.set(key, val) } }) return result } if (data instanceof Set) { const result = new Set() map.set(data, result) data.forEach(val => { if (isObject(val)) { result.add(clone(val)) } else { result.add(val) } }) return result } const keys = Reflect.ownKeys(data) const allDesc = Object.getOwnPropertyDescriptors(data) const result = Object.create(Object.getPrototypeOf(data), allDesc) map.set(data, result) keys.forEach(key => { const val = data[key] if (isObject(val)) { result[key] = clone(val) } else { result[key] = val } }) return result } return clone(target) }
Mari kita lihat dahulu jenis data JS Gambar (kecuali Object
, semua yang lain adalah jenis asas):
Dalam JavaScript, penyalinan nilai jenis asas adalah untuk menyalin terus data baru dan sama Kedua-dua keping data adalah bebas antara satu sama lain. Penyalinan nilai jenis rujukan (Jenis Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek) adalah untuk lulus rujukan Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek (iaitu, alamat memori di mana Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek itu terletak, iaitu, penunjuk ke Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek), yang bersamaan dengan berbilang pembolehubah menunjuk kepada Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek yang sama. Kemudian selagi salah satu pembolehubah mempunyai rujukan kepada Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek Apabila diubah suai, Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek yang ditunjuk oleh pembolehubah lain juga akan diubah suai (kerana ia menunjuk ke Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek yang sama). Seperti yang ditunjukkan di bawah:
Salinan gelap dan cetek terutamanya menyasarkan jenis Objek, dan nilai asas jenis itu sendiri disalin betul-betul sama Satu salinan, tiada perbezaan antara salinan gelap dan terang. Di sini kami mula-mula memberikan Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek salinan untuk ujian Anda boleh menggunakan Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript
ini untuk menguji sama ada fungsi salinan dalam yang anda tulis adalah sempurna: Hasil daripada Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek
// 测试的Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript对象 const Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript = { // =========== 1.基础数据类型 =========== num: 0, // number str: '', // string bool: true, // boolean unf: undefined, // undefined nul: null, // null sym: Symbol('sym'), // symbol bign: BigInt(1n), // bigint // =========== 2.Object类型 =========== // 普通对象 Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript: { name: '我是一个对象', id: 1 }, // 数组 arr: [0, 1, 2], // 函数 func: function () { console.log('我是一个函数') }, // 日期 date: new Date(0), // 正则 reg: new RegExp('/我是一个正则/ig'), // Map map: new Map().set('mapKey', 1), // Set set: new Set().add('set'), // =========== 3.其他 =========== [Symbol('1')]: 1 // Symbol作为key }; // 4.添加不可枚举属性 Object.defineProperty(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript, 'innumerable', { enumerable: false, value: '不可枚举属性' }); // 5.设置原型对象 Object.setPrototypeOf(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript, { proto: 'proto' }) // 6.设置loop成循环引用的属性 Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript.loop = Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript
Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript
dalam Chrome. pelayar :
Salinan cetek: Cipta Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek baharu untuk menerima apa yang anda mahu Nilai Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek yang disalin atau dirujuk semula. Jika atribut Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek ialah jenis data asas, nilai jenis asas disalin ke Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek baharu tetapi jika atribut adalah jenis data rujukan, alamat dalam memori akan disalin Jika salah satu Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek menukar alamat yang ditunjuk to by the memory Objek pasti akan menjejaskan Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek lain.
Mula-mula mari kita lihat beberapa kaedah penyalinan cetek (untuk butiran, klik pada hiperpautan ke kaedah yang sepadan):
Kaedah th> | Penggunaan | Nota | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Objek. assign () |
Object.assign(target, ...sources)
|
1. Sifat warisan Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptek tidak akan disalin; sifat boleh ditiru. | |||||||||||||||
Kembangkan sintaks | let Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptClone = { ...Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript }; | Kecacatan adalah serupa dengan ||||||||||||||||
Array.prototype.concat() copy array | const new_array = old_array.concat(value1[, value2[ , ...[, valueN]]]) |
Salinan cetek, sesuai untuk tatasusunan nilai jenis asas | |||||||||||||||
Array.prototype.slice()Copy array | arr.slice([begin[, end]]) |
Salinan cetek, sesuai untuk tatasusunan nilai jenis asas |
这里只列举了常用的几种方式,除此之外当然还有其他更多的方式。注意,我们直接使用=
赋值不是浅拷贝,因为它是直接指向同一个对象了,并没有返回一个新对象。
手动实现一个浅拷贝:
function shallowClone(target) { if (typeof target === 'Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptect' && target !== null) { const cloneTarget = Array.isArray(target) ? [] : {}; for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = target[prop]; } } return cloneTarget; } else { return target; } } // 测试 const shallowCloneObj = shallowClone(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript) shallowCloneObj === Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript // false,返回的是一个新对象 shallowCloneObj.arr === Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript.arr // true,对于对象类型只拷贝了引用
从上面这段代码可以看出,利用类型判断(查看typeof),针对引用类型的对象进行 for 循环遍历对象属性赋值给目标对象的属性(for...in
语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性,包含原型上的属性。查看for…in),基本就可以手工实现一个浅拷贝的代码了。
深拷贝:创建一个新的对象,将一个对象从内存中完整地拷贝出来一份给该新对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。
看看现存的一些深拷贝的方法:
JSON.stringfy()
其实就是将一个 JavaScript 对象或值转换为 JSON 字符串,最后再用 JSON.parse()
的方法将JSON 字符串生成一个新的对象。(点这了解:JSON.stringfy()、JSON.parse())
使用如下:
function deepClone(target) { if (typeof target === 'Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptect' && target !== null) { return JSON.parse(JSON.stringify(target)); } else { return target; } } // 开头的测试Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript存在BigInt类型、循环引用,JSON.stringfy()执行会报错,所以除去这两个条件进行测试 const clonedObj = deepClone(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript) // 测试 clonedObj === Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript // false,返回的是一个新对象 clonedObj.arr === Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript.arr // false,说明拷贝的不是引用
浏览器执行结果:
从以上结果我们可知JSON.stringfy()
存在以下一些问题:
执行会报错:存在BigInt
类型、循环引用。
拷贝Date
引用类型会变成字符串。
键值会消失:对象的值中为Function
、Undefined
、Symbol
这几种类型,。
键值变成空对象:对象的值中为Map
、Set
、RegExp
这几种类型。
无法拷贝:不可枚举属性、对象的原型链。
补充:其他更详细的内容请查看官方文档:JSON.stringify()
由于以上种种限制条件,JSON.stringfy()
方式仅限于深拷贝一些普通的对象,对于更复杂的数据类型,我们需要另寻他路。
手动递归实现深拷贝,我们只需要完成以下2点即可:
对于基础类型,我们只需要简单地赋值即可(使用=
)。
对于引用类型,我们需要创建新的对象,并通过遍历键来赋值对应的值,这个过程中如果遇到 Object 类型还需要再次进行遍历。
function deepClone(target) { if (typeof target === 'Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptect' && target) { let cloneObj = {} for (const key in target) { // 遍历 const val = target[key] if (typeof val === 'Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptect' && val) { cloneObj[key] = deepClone(val) // 是对象就再次调用该函数递归 } else { cloneObj[key] = val // 基本类型的话直接复制值 } } return cloneObj } else { return target; } } // 开头的测试Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript存在循环引用,除去这个条件进行测试 const clonedObj = deepClone(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript) // 测试 clonedObj === Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript // false,返回的是一个新对象 clonedObj.arr === Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript.arr // false,说明拷贝的不是引用
浏览器执行结果:
该基础版本存在许多问题:
不能处理循环引用。
只考虑了Object
对象,而Array
对象、Date
对象、RegExp
对象、Map
对象、Set
对象都变成了Object
对象,且值也不正确。
丢失了属性名为Symbol
类型的属性。
丢失了不可枚举的属性。
原型上的属性也被添加到拷贝的对象中了。
如果存在循环引用的话,以上代码会导致无限递归,从而使得堆栈溢出。如下例子:
const a = {} const b = {} a.b = b b.a = a deepClone(a)
对象 a 的键 b 指向对象 b,对象 b 的键 a 指向对象 a,查看a
对象,可以看到是无限循环的:
对对象a
执行深拷贝,会出现死循环,从而耗尽内存,进而报错:堆栈溢出
如何避免这种情况呢?一种简单的方式就是把已添加的对象记录下来,这样下次碰到相同的对象引用时,直接指向记录中的对象即可。要实现这个记录功能,我们可以借助 ES6 推出的 WeakMap
对象,该对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。(WeakMap
相关见这:WeakMap)
针对以上基础版深拷贝存在的缺陷,我们进一步去完善,实现一个完美的深拷贝。
对于基础版深拷贝存在的问题,我们一一改进:
存在的问题 | 改进方案 |
---|---|
1. 不能处理循环引用 | 使用 WeakMap 作为一个Hash表来进行查询 |
2. 只考虑了Object 对象 | 当参数为 Date 、RegExp 、Function 、Map 、Set ,则直接生成一个新的实例返回 |
3. 属性名为Symbol 的属性4. 丢失了不可枚举的属性 | 针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys()注: Reflect.ownKeys(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript) 相当于[...Object.getOwnPropertyNames(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript), ...Object.getOwnPropertySymbols(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript)] |
4. 原型上的属性 | Object.getOwnPropertyDescriptors()设置属性描述对象,以及Object.create()方式继承原型链 |
代码实现:
function deepClone(target) { // WeakMap作为记录对象Hash表(用于防止循环引用) const map = new WeakMap() // 判断是否为Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptect类型的辅助函数,减少重复代码 function isObject(target) { return (typeof target === 'Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptect' && target ) || typeof target === 'function' } function clone(data) { // 基础类型直接返回值 if (!isObject(data)) { return data } // 日期或者正则对象则直接构造一个新的对象返回 if ([Date, RegExp].includes(data.constructor)) { return new data.constructor(data) } // 处理函数对象 if (typeof data === 'function') { return new Function('return ' + data.toString())() } // 如果该对象已存在,则直接返回该对象 const exist = map.get(data) if (exist) { return exist } // 处理Map对象 if (data instanceof Map) { const result = new Map() map.set(data, result) data.forEach((val, key) => { // 注意:map中的值为Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptect的话也得深拷贝 if (isObject(val)) { result.set(key, clone(val)) } else { result.set(key, val) } }) return result } // 处理Set对象 if (data instanceof Set) { const result = new Set() map.set(data, result) data.forEach(val => { // 注意:set中的值为Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScriptect的话也得深拷贝 if (isObject(val)) { result.add(clone(val)) } else { result.add(val) } }) return result } // 收集键名(考虑了以Symbol作为key以及不可枚举的属性) const keys = Reflect.ownKeys(data) // 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性以及对应的属性描述 const allDesc = Object.getOwnPropertyDescriptors(data) // 结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链, 这里得到的result是对data的浅拷贝 const result = Object.create(Object.getPrototypeOf(data), allDesc) // 新对象加入到map中,进行记录 map.set(data, result) // Object.create()是浅拷贝,所以要判断并递归执行深拷贝 keys.forEach(key => { const val = data[key] if (isObject(val)) { // 属性值为 对象类型 或 函数对象 的话也需要进行深拷贝 result[key] = clone(val) } else { result[key] = val } }) return result } return clone(target) } // 测试 const clonedObj = deepClone(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript) clonedObj === Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript // false,返回的是一个新对象 clonedObj.arr === Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript.arr // false,说明拷贝的不是引用 clonedObj.func === Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript.func // false,说明function也复制了一份 clonedObj.proto // proto,可以取到原型的属性
详细的说明见代码中的注释,更多测试希望大家自己动手尝试验证一下以加深印象。
在遍历 Object
类型数据时,我们需要把 Symbol
类型的键名也考虑进来,所以不能通过 Object.keys
获取键名或 for...in
方式遍历,而是通过Reflect.ownKeys()
获取所有自身的键名(getOwnPropertyNames
和 getOwnPropertySymbols
函数将键名组合成数组也行:[...Object.getOwnPropertyNames(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript), ...Object.getOwnPropertySymbols(Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript)]
),然后再遍历递归,最终实现拷贝。
浏览器执行结果:
可以发现我们的cloneObj对象
和原来的Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript对象
一模一样,并且修改cloneObj对象
的各个属性都不会对Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript对象
造成影响。其他的大家再多尝试体会哦!
【相关推荐:javascript视频教程、编程视频】
Atas ialah kandungan terperinci Artikel ini akan memberi anda pemahaman terperinci tentang penyalinan mendalam dalam JavaScript. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!