ホームページ >ウェブフロントエンド >jsチュートリアル >この記事では、JavaScript のディープ コピーについて詳しく説明します。
ディープコピーに関する記事はインターネット上にたくさんありますが、質はさまざまで、よく考えられていないものも多く、書き方も比較的雑で満足のいくものではありません。この記事は 完璧なディープコピー を完成させることを目的としています。読んだ後に質問がある場合は、自由に追加および改善してください。
ディープ コピーが完了したかどうかを評価するには、次の質問が実装されているかどうかを確認してください:
基本的な型データを実行できるかどうか
コピーされる?
キーと値はどちらも基本的な型です通常のオブジェクト
コピーできますか?
#シンボルオブジェクトのキーはコピーできますか?
Date および
RegExp オブジェクト タイプはコピーできますか?
Map および
Set オブジェクト タイプはコピーできますか?
オブジェクト タイプはコピーできますか? (通常、関数にはディープ コピーを使用しません)
はコピーできますか?
コピーできますか?
コピーできますか? ##################どうやって?あなたが書いた詳細なコピーは十分に完璧ですか?
ディープ コピーの最終実装知りたい人の便宜のために、最終コード バージョンをここに直接示します。もちろん、段階的に理解したい場合は、引き続き記事の残りの部分を参照してください:
function deepClone(target) { const map = new WeakMap() function isObject(target) { return (typeof target === 'この記事では、JavaScript のディープ コピーについて詳しく説明します。ect' && 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) }
を除き、他はすべて基本型です):
2. 深くて浅いコピー
深くて浅いコピーは、主にオブジェクト タイプと基本の値をターゲットとします。タイプ自体はまったく同じにコピーされます。コピーは 1 つで、暗いコピーと明るいコピーの区別はありません。ここでは、まずテスト用のコピー オブジェクトを提供します。この
この記事では、JavaScript のディープ コピーについて詳しく説明します。 オブジェクトを使用して、作成したディープ コピー関数が完璧かどうかをテストできます:
// 测试的この記事では、JavaScript のディープ コピーについて詳しく説明します。对象 const この記事では、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类型 =========== // 普通对象 この記事では、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(この記事では、JavaScript のディープ コピーについて詳しく説明します。, 'innumerable', { enumerable: false, value: '不可枚举属性' }); // 5.设置原型对象 Object.setPrototypeOf(この記事では、JavaScript のディープ コピーについて詳しく説明します。, { proto: 'proto' }) // 6.设置loop成循环引用的属性 この記事では、JavaScript のディープ コピーについて詳しく説明します。.loop = この記事では、JavaScript のディープ コピーについて詳しく説明します。
: 受け入れる新しいオブジェクトを作成します再コピーまたは参照するオブジェクト値。オブジェクト属性が基本データ型の場合は、基本型の値が新しいオブジェクトにコピーされますが、属性が参照データ型の場合は、メモリ内のアドレスがコピーされます。オブジェクトの 1 つがポイントするアドレスを変更すると、そのアドレスが変更されます。記憶によれば、オブジェクトは間違いなく別のオブジェクトに影響を与えます。
まず、いくつかの浅いコピー メソッドを見てみましょう (詳細については、対応するメソッドへのハイパーリンクをクリックしてください):メソッドメソッドを使用する
注意事項
##Object.assign() | Object.assign(target, .. .sources) | 説明: 1 つ以上のソース オブジェクトからターゲット オブジェクトにすべての列挙可能なプロパティの値を割り当てるために使用されます。ターゲットオブジェクトを返します。 | |||||||
---|---|---|---|---|---|---|---|---|---|
構文を展開します | let この記事では、JavaScript のディープ コピーについて詳しく説明します。Clone = { ...この記事では、JavaScript のディープ コピーについて詳しく説明します。 };
欠陥と | Object.assign ()||||||||
Array.prototype.concat() 配列のコピー
| const new_array = old_array.concat(value1[, value2[, ...[, valueN] ]])#浅いコピー、基本的な型値の配列に適しています |
||||||||
arr.slice([begin[, end]])
| 浅いコピー、基本型値の配列に適しています|||||||||
存在的问题 | 改进方案 |
---|---|
1. 不能处理循环引用 | 使用 WeakMap 作为一个Hash表来进行查询 |
2. 只考虑了Object 对象 |
当参数为 Date 、RegExp 、Function 、Map 、Set ,则直接生成一个新的实例返回 |
3. 属性名为Symbol 的属性4. 丢失了不可枚举的属性 |
针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys()注: Reflect.ownKeys(この記事では、JavaScript のディープ コピーについて詳しく説明します。) 相当于[...Object.getOwnPropertyNames(この記事では、JavaScript のディープ コピーについて詳しく説明します。), ...Object.getOwnPropertySymbols(この記事では、JavaScript のディープ コピーについて詳しく説明します。)]
|
4. 原型上的属性 | Object.getOwnPropertyDescriptors()设置属性描述对象,以及Object.create()方式继承原型链 |
代码实现:
function deepClone(target) { // WeakMap作为记录对象Hash表(用于防止循环引用) const map = new WeakMap() // 判断是否为この記事では、JavaScript のディープ コピーについて詳しく説明します。ect类型的辅助函数,减少重复代码 function isObject(target) { return (typeof target === 'この記事では、JavaScript のディープ コピーについて詳しく説明します。ect' && 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中的值为この記事では、JavaScript のディープ コピーについて詳しく説明します。ect的话也得深拷贝 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中的值为この記事では、JavaScript のディープ コピーについて詳しく説明します。ect的话也得深拷贝 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(この記事では、JavaScript のディープ コピーについて詳しく説明します。) clonedObj === この記事では、JavaScript のディープ コピーについて詳しく説明します。 // false,返回的是一个新对象 clonedObj.arr === この記事では、JavaScript のディープ コピーについて詳しく説明します。.arr // false,说明拷贝的不是引用 clonedObj.func === この記事では、JavaScript のディープ コピーについて詳しく説明します。.func // false,说明function也复制了一份 clonedObj.proto // proto,可以取到原型的属性
详细的说明见代码中的注释,更多测试希望大家自己动手尝试验证一下以加深印象。
在遍历 Object
类型数据时,我们需要把 Symbol
类型的键名也考虑进来,所以不能通过 Object.keys
获取键名或 for...in
方式遍历,而是通过Reflect.ownKeys()
获取所有自身的键名(getOwnPropertyNames
和 getOwnPropertySymbols
函数将键名组合成数组也行:[...Object.getOwnPropertyNames(この記事では、JavaScript のディープ コピーについて詳しく説明します。), ...Object.getOwnPropertySymbols(この記事では、JavaScript のディープ コピーについて詳しく説明します。)]
),然后再遍历递归,最终实现拷贝。
浏览器执行结果:
可以发现我们的cloneObj对象
和原来的この記事では、JavaScript のディープ コピーについて詳しく説明します。对象
一模一样,并且修改cloneObj对象
的各个属性都不会对この記事では、JavaScript のディープ コピーについて詳しく説明します。对象
造成影响。其他的大家再多尝试体会哦!
【相关推荐:javascript视频教程、编程视频】
以上がこの記事では、JavaScript のディープ コピーについて詳しく説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。