Открытие темы:
- В этой статье будет использоваться рекурсивное обучение для изучения точек знаний, связанных с «поверхностным копированием» и «глубоким копированием». Я надеюсь, что друзья смогут скопировать эти точки знаний в своем уме и гибко использовать их в интервью или работе.
const deepCloneKnowledgePoints = {
title: '浅拷贝和深拷贝',
chapterOne: {
title: '章节一',
point: [
'浅拷贝和深拷贝初探索',
'基本数据类型和引用数据类型',
],
},
chapterTwo: {
title: '章节二',
point: [
'手写浅拷贝',
'Object.assign()',
'Array.prototype.concat()',
'Array.prototype.slice()',
'...obj 展开运算符',
],
extend: [
'for...in',
'for...of',
'for...in 和 for...of 的区别',
'hasOwnProperty',
],
},
chapterThree: {
title: '章节三',
point: [
'手写深拷贝',
'JSON.parse(JSON.stringify())',
'函数库 Lodash',
'框架 jQuery',
],
extend: [
'typeof',
'instanceof',
'constructor',
{
point: 'Object.prototype.toString.call()',
extend: [
'Function.prototype.apply()',
'Function.prototype.bind()',
'Function.prototype.call()',
'apply()、bind() 以及 call() 的区别',
],
},
{
point: 'JSON.parse(JSON.stringify())',
extend: [
'JSON.parse()',
'JSON.stringify()',
]
}
],
},
};
каталог
Чем отличается передок без закидывания от соленой рыбы?
2 Предисловие
При написании определенного бизнес-кода внезапно возникла сцена:
- Внутреннему партнеру нужно, чтобы я отправил ему копию исходных данных, возвращаемых интерфейсом, и новые данные после модификации страницы.
Код выглядит так:
// 初始数据
const initData = {
// ...
};
// finalData 数据来源于 initData
// 但是 finalData 的修改不能影响到 initData
const finalData = _.deepClone(initData);
// 返回最终形式 result 给后端
// finalData 和 initData 是两份不同的数据
const result = {
initData: initData,
finalData: finalData,
};
return res;
Тогда возникает вопрос, еслиconst finalData = initData
, можно ли его изменить напрямуюfinalData
данные? повлияет ли этоinitData
?
Давайте попробуем:
const initData = { obj: '123' };
const finalData = initData;
finalData.obj = '456';
console.log(initData);
console.log(finalData);
// 猜猜上面两个 console.log 返回啥?
Если нет, что нам нужно сделать, чтобы вернуть старые и новые данные сравнения в серверную часть?
Три предварительных исследования мелкой копии и глубокой копии
Теперь мы начинаем раскрывать приведенный выше ответ.
Перед этим посмотриПримитивные типы данных и ссылочные типы данных.
Данные разделены на основные типы данных и справочные типы данных.
- базовый тип данных: строка, число, логическое значение, нуль, неопределенный, символ. Примитивные типы данных — это данные, хранящиеся непосредственно в стеке.
- ссылочный тип данных: Массив, Объект. Ссылочный тип данных хранит, что на объект ссылаются в стеке, а реальные данные хранятся в памяти.
Для визуального понимания давайте посмотрим на следующий код:
// 基本数据类型
let str1 = '123';
str2 = str1;
str2 = '456';
console.log(str1); // '123'
console.log(str2); // '456'
// 引用数据类型
let arr1 = [1, 2, 3];
arr2 = arr1;
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4]
console.log(arr2); // [1, 2, 3, 4]
Как и выше, поскольку основные типы данных хранятся напрямую, если мы скопируем основные типы данных, а затем изменим новые данные, исходные данные не будут затронуты.
И когда вы делаете копию эталонного типа данных, а затем изменяете новые данные, это влияет на исходные данные.
Пример изображения:
- У меня есть большая ферма с домами, машинами, овцами и прериями.
- Базовый тип данных: Раньше у меня была машина, я купил другую, значит у меня две, одна машина ломается, а на другую это не влияет. Раньше у меня был дом, и я построил еще один, поэтому у меня их два, и удар молнии в один не повлияет на другой.
- Тип справочных данных: Прерия находится перед моим домом, и овцы съели немного гнилой травы.Овца умерла от болезни из-за того, что съела гнилую траву. Пастбище остается пастбищем, но травы внутри меньше; стадо остается стадом, но внутри меньше овец.
В JavaScript дома, автомобили и т. д.ОдинАтрибут является базовым типом данных, имеет свое приватное поле, его можно клонировать, но новый не повлияет на исходный (старая машина сломана, а новая еще открыта).
А если это стая, прерия и т.коллективАтрибут — это данные данных объекта, если хранить их по одному, найти сразу будет проблематично, поэтому браузер откроет для него большую область, а потом подскажет, как его найти (индекс). Поэтому, когда вы изменяете содержимое в нем (овцы едят траву, трава уменьшается), оно меняется внутренне.
Ладно, понялПримитивные типы данных и ссылочные типы данныхПосле этого давайте продолжим изучение присваивания, поверхностного копирования и глубокого копирования:
/**
* @name 赋值
*/
const dataOne = {
title: 'study',
number: ['jsliang', 'JavaScriptLiang', 'LiangJunrong'],
};
const dataTwo = dataOne;
dataTwo.title = 'play';
dataTwo.number = ['null'];
console.log(dataOne);
// dataOne: { title: 'play', number: ['null'] }
console.log(dataTwo);
// dataTwo: { title: 'play', number: ['null'] }
/**
* @name 浅拷贝
*/
const dataThree = {
title: 'study',
number: ['jsliang', 'JavaScriptLiang', 'LiangJunrong'],
};
const dataFour = shallowClone(dataThree); // shallowClone 待实现
dataFour.title = 'play';
dataFour.number = ['null'];
console.log(datadataThreeOne);
// dataThree: { title: 'study', number: ['null'] }
console.log(dataFour);
// dataFour: { title: 'play', number: ['null'] }
/**
* @name 深拷贝
*/
const dataFive = {
title: 'study',
number: ['jsliang', 'JavaScriptLiang', 'LiangJunrong'],
};
const dataSix = deepClone(dataFive); // deepClone 待实现
dataSix.title = 'play';
dataSix.number = ['null'];
console.log(dataFive);
// dataFive: { title: 'study', number: ['jsliang', 'JavaScriptLiang', 'LiangJunrong'] }
console.log(dataSix);
// dataSix: { title: 'play', number: ['null'] }
Как и выше, делаем вывод:
- Назначение: Копия указанного адреса. Изменение назначенных данных, будь то базовый тип данных или ссылочный тип данных, повлияет на исходные данные.
- Неглубокая копия: копия одного слоя. В неглубокой копии изменение базового типа данных не повлияет на базовый тип исходных данных, а изменение эталонного типа данных повлияет на исходный тип данных.
- Глубокая копия: копия неограниченного уровня. В глубокой копии изменение базового типа данных и ссылочного типа данных не повлияет на исходный тип данных.
Может возникнуть проблема с приведенным выше утверждением:
1. Некоторые начальники думают, что мелкое копирование — это копирование одного слоя, то есть первый слой данных, будь то массив или строка, не влияют друг на друга;
2. Некоторые считают, что поверхностная копия допустима только для базовых типов данных, а на ее адрес будут ссылаться для ссылочных типов данных.
здесьjsliangЛичное понимание заключается в том, чтобы отличать поверхностную копию от задания, если у вас есть лучшая точка зрения, вы можете ее выставить ~
График выглядит следующим образом:
Указывает ли он на тот же объект, что и исходные данные | Исходные данные — это базовый тип данных. | Исходные данные содержат дочерние объекты | |
---|---|---|---|
назначать | да | Изменение [заставит] изменить исходные данные вместе | Изменение [заставит] изменить исходные данные вместе |
мелкая копия | нет | Изменение [не будет] заставлять исходные данные изменяться вместе | Изменение [заставит] изменить исходные данные вместе |
глубокая копия | нет | Изменение [не будет] заставлять исходные данные изменяться вместе | Изменение [не будет] заставлять исходные данные изменяться вместе |
Четыре мелкие копии
4.1 Рукописная неглубокая копия
Прежде чем обсуждать поверхностное копирование с помощью инструментов, давайте попробуем написать мелкую копию от руки:
const arr1 = [1, 2, ['jsliang', 'JavaScriptLiang'], 4];
const shallowClone = (arr) => {
const dst = [];
for (let prop in arr) {
if (arr.hasOwnProperty(prop)) {
dst[prop] = arr[prop];
}
}
return dst;
}
const arr2 = shallowClone(arr1);
arr2[2].push('LiangJunrong');
arr2[3] = 5;
console.log(arr1);
// [ 1, 2, [ 'jsliang', 'JavaScriptLiang', 'LiangJunrong' ], 4 ]
console.log(arr2);
// [ 1, 2, [ 'jsliang', 'JavaScriptLiang', 'LiangJunrong' ], 5 ]
Видно, что когда мы изменим ссылочный тип данных в ссылочном типе данных, это все равно повлияет на исходные данные, но если мы изменим в нем базовый тип данных, это не повлияет на исходные данные.
Итак, прежде чем углубиться в обсуждение, давайте поговорим о том, что я сделал, чтобы лучше объяснить приведенные выше идеи кода:
- найти
for...in
, оказалось принадлежащимЗаявления и объявления JavaScript, поэтому я создал каталог для своей библиотеки документовЗаявления и декларации. - Обнаружить
for...in
а такжеfor...of
похоже, поэтому мы проверилиfor...inа такжеfor...of, кстати, сравнил обадля ... в против для ... из. - Обнаружить
for...of
Это также может включать в себя наследование и цепочку прототипов, поэтому, кстати, я получил статью, написанную в предыдущем интервью:Наследование и цепочка прототипов. - найти
hasOwnProperty
, обнаружил, что я не построилObject
Классификация, поэтому был создан новый каталог:Object, и написатьhasOwnProperty.
После разговора давайте объясним, почему приведенный выше код может выполнять поверхностное копирование данных:
-
for...in
: траверсObject
объектarr1
, который перечисляет перечисляемые значения. -
hasOwnProperty()
: проверить, принадлежит ли значение перечисления объектуarr1
, если он унаследован, то будет удален, а если собственный, то скопирован.
Таким образом, мы успешно внедрили неглубокое копирование и поняли, какие знания задействованы ~
Здесь мы вводим три вида удобных форм, быстрой мелкой копии.
4.2 Object.assign()
Object.assign()
Метод может копировать любое количество перечисляемых свойств самого исходного объекта в целевой объект, а затем возвращать целевой объект.
Но следует отметить, что,Object.assgin()
Выполняется поверхностная копия, при этом копируется ссылка на свойства объекта, а не сам объект.
const obj1 = {
username: 'LiangJunrong',
skill: {
play: ['basketball', 'computer game'],
read: 'book',
},
girlfriend: ['1 号备胎', '2 号备胎', '3 号备胎'],
};
const obj2 = Object.assign({}, obj1);
obj2.username = 'jsliang'; // 修改基本类型
obj2.skill.read = 'computer book'; // 修改二层基本类型
obj2.skill.play = ['footboll']; // 修改二层引用类型
obj2.girlfriend = ['之前的都是瞎吹的!'];
console.log(obj1);
// { username: 'LiangJunrong',
// skill: { play: [ 'footboll' ], read: 'computer book' },
// girlfriend: [ '1 号备胎', '2 号备胎', '3 号备胎' ] }
console.log(obj2);
// { username: 'jsliang',
// skill: { play: [ 'footboll' ], read: 'computer book' },
// girlfriend: [ '之前的都是瞎吹的!' ] }
Видно, что,Object.assign()
Для данных первого слоя это глубокая копия, а для данных второго слоя и выше — мелкая копия.
4.3 Array.prototype.concat()
concat()
— это встроенный метод массивов, который позволяет пользователю объединять два или более массивов.
Этот метод не изменяет существующий массив, а возвращает новый массив.
Подробности можно увидетьjsliangстатью или см.MDNстатья:
Здесь мы проходимconcat()
Для мелкого копирования массива:
const arr1 = [
1,
{
username: 'jsliang',
},
];
let arr2 = arr1.concat();
arr2[0] = 2;
arr2[1].username = 'LiangJunrong';
console.log(arr1);
// [ 1, { username: 'LiangJunrong' } ]
console.log(arr2);
// [ 2, { username: 'LiangJunrong' } ]
Увидев это, друзья должны понять, что черезconcat()
Выполненная поверхностная копия может изменить в ней базовый тип данных, не затрагивая исходное значение, но изменение в ней ссылочного типа данных повлияет на исходное значение.
4.4 Array.prototype.slice()
slice()
Также встроенный метод массивов, который возвращает новый объект.
slice()
Исходный массив не будет изменен.
Подробности можно увидеть нижеjsliangизconcat
или смотреть прямо наMDNлитература:
const arr1 = [
1,
{
username: 'jsliang',
},
];
let arr2 = arr1.slice();
arr2[0] = 2;
arr2[1].username = 'LiangJunrong';
console.log(arr1);
// [ 1, { username: 'LiangJunrong' } ]
console.log(arr2);
// [ 2, { username: 'LiangJunrong' } ]
Видно, что он такой же, как и предыдущий.concat()
Неглубокая копия спектакля точно такая же, если хотите изучить более глубокое содержание, можете прочитатьArray.prototype.concat()
а такжеArray.prototype.slice()
Исходный код конкретной реализации.
4.5 ...оператор спреда объекта
Оператор распространения — это новый оператор, представленный в ES6.
Его можно использовать при копировании массивов, объектов и объединении массивов.
Здесь мы также можем попробовать использоватьconst obj2 = {...obj1}
сформировать неглубокую копию.
/**
* @name ...obj拷贝数组
*/
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // like arr.slice()
arr2.push(4);
console.log(arr1); // [ 1, 2, 3 ]
console.log(arr2); // [ 1, 2, 3, 4 ]
/**
* @name ...obj拷贝对象
*/
const obj1 = {
name: 'jsliang',
arr1: ['1', '2', '3'],
obj: {
name: 'JavaScriptLiang',
arr2: ['4', '5', '6'],
},
};
const obj2 = {...obj1};
obj2.name = 'LiangJunrong';
obj2.arr1 = ['null'];
obj2.obj.name = 'jsliang';
obj2.obj.arr2 = ['null'];
console.log(obj1);
// { name: 'jsliang',
// arr1: [ '1', '2', '3' ],
// obj: { name: 'jsliang', arr2: [ 'null' ] } }
console.log(obj2);
// { name: 'LiangJunrong',
// arr1: [ 'null' ],
// obj: { name: 'jsliang', arr2: [ 'null' ] } }
5 глубокая копия
5.1 Рукописная глубокая копия
---Секция 1---
Итак, как реализовать глубокую копию вручную?
Давайте попробуем немного изменить предыдущий код мелкой копии:
const deepClone = (source) => {
const target = {};
for (const i in source) {
if (source.hasOwnProperty(i)
&& target[i] === 'object') {
target[i] = deepClone(source[i]); // 注意这里
} else {
target[i] = source[i];
}
}
return target;
};
Конечно, есть проблема с этим кодом:
- Параметры не проверяются, если входящий объект не является объектом или массивом, мы можем вернуть его напрямую.
- пройти через
typeof
Логика оценки того, является ли объект недостаточно строгой.
---Раздел 2---
Поскольку есть проблемы, нам нужно попытаться преодолеть:
// 定义检测数据类型的功能函数
const checkedType = (target) => {
return Object.prototype.toString.call(target).slice(8, -1);
}
// 实现深度克隆对象或者数组
const deepClone = (target) => {
// 判断拷贝的数据类型
// 初始化变量 result 成为最终数据
let result, targetType = checkedType(target);
if (targetType === 'Object') {
result = {};
} else if (targetType === 'Array') {
result = [];
} else {
return target;
}
// 遍历目标数据
for (let i in target) {
// 获取遍历数据结构的每一项值
let value = target[i];
// 判断目标结构里的每一项值是否存在对象或者数组
if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
// 如果对象或者数组中还嵌套了对象或者数组,那么继续遍历
result[i] = deepClone(value);
} else {
result[i] = value;
}
}
// 返回最终值
return result;
}
const obj1 = [
1,
'Hello!',
{ name: 'jsliang1' },
[
{
name: 'LiangJunrong1',
}
],
]
const obj2 = deepClone(obj1);
obj2[0] = 2;
obj2[1] = 'Hi!';
obj2[2].name = 'jsliang2';
obj2[3][0].name = 'LiangJunrong2';
console.log(obj1);
// [
// 1,
// 'Hello!',
// { name: 'jsliang1' },
// [
// { name: 'LiangJunrong1' },
// ],
// ]
console.log(obj2);
// [
// 2,
// 'Hi!',
// { name: 'jsliang2' },
// [
// { name: 'LiangJunrong2' },
// ],
// ]
Ниже приведен код глубокого копирования:
первый, давайте сначала посмотрим на строку кода, которая проверяет тип:Object.prototype.toString.call(target).slice(8, -1)
.
Прежде чем говорить об этой строке кода, давайте сравним 4 способа определения типов данных JavaScript:
-
Метод 1: тип: Не могу судить
null
илиnew String()
и другие типы данных. -
Метод 2: экземпляр: Не могу судить
'jsliang'
,123
и другие типы данных. -
Способ 3: конструктор:судить
null
а такжеundefined
Об ошибке будет сообщено напрямую. - Метод 4: Object.prototype.toString.call(): метод надежной оценки типов данных JavaScript, который может соответствовать ожидаемой оценке базовых типов данных String, Undefined и т. д., а также может оценивать ссылочные типы данных, такие как Array и Object.
Подробное исследование см.jsliangУчебный документ для:
- «Оценка типов данных — агрегирование»
- "Оценка типа данных - typeof"
- "Оценка типа данных - instanceof"
- "Оценка типа данных - конструктор"
- "Оценка типа данных - toString"
потом, мы передаем методtargetType()
серединаObject.prototype.toString.call()
, оценивая, к какому типу данных принадлежат входящие данные, чтобы изменитьresult
ценность{}
,[]
или напрямую вернуть переданное значение (return target
).
наконец, то переходимfor...in
судитьtarget
все элементы , если они принадлежат{}
или[]
, затем рекурсивноclone()
Операция, если основные типы данных передаются непосредственно в массив данных, чтобы вернуть ...... глубокую копию последнего.
---Раздел 3---
Выше наш код выглядит нормально, не так ли? Предположим, что данные, которые нам нужно скопировать, выглядят следующим образом:
const obj1 = {};
obj1.a = obj1;
console.log(deepClone(obj1));
// RangeError: Maximum call stack size exceeded
Смотрите, мы прямо разработали бесконечный цикл!
Итак, как нам нужно ее решить? Чтобы реализоваться!
Прежде чем я смогу найти хорошее решение, вы можете прочитать следующую статью и подумать, можно ли решить эту проблему:
- Что такое глубокая и поверхностная копия js и как ее реализовать
- JavaScript поверхностное копирование и глубокое копирование
- Как добиться глубокого копирования вопросов на собеседовании - Му Иян
...Ладно, хоть я и сказал, что надеюсь, что мои друзья прочитают информацию сами, но чтобы клинок не прислали,jsliangИли запишите сюда код, который, по вашему мнению, подходит:
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
function deepClone(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
// 新增代码,查哈希表
if (hash.has(source)) return hash.get(source);
var target = Array.isArray(source) ? [] : {};
// 新增代码,哈希表设值
hash.set(source, target);
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
// 新增代码,传入哈希表
target[key] = deepClone(source[key], hash);
} else {
target[key] = source[key];
}
}
}
return target;
}
/**
* @name 正常深拷贝测试
*/
const a = {
name: 'jsliang',
book: {
title: '深拷贝学习',
price: 'free',
},
a1: undefined,
a2: null,
a3: 123
};
const b = deepClone(a);
b.name = 'JavaScriptLiang';
b.book.title = '教你如何泡妞';
b.a3 = 456;
console.log(a);
// { name: 'jsliang',
// book: { title: '深拷贝学习', price: 'free' },
// a1: undefined,
// a2: null,
// a3: 123 }
console.log(b);
// { name: 'JavaScriptLiang',
// book: { title: '教你如何泡妞', price: 'free' },
// a1: undefined,
// a2: null,
// a3: 456 }
/**
* @name 解决死循环
*/
const c = {};
c.test = c;
const d = deepClone(c);
console.log(c);
// { test: [Circular] }
console.log(d);
// { test: [Circular] }
---Раздел 4---
Теперь, когда бесконечный цикл закончен, давайте рассмотрим еще один вопрос:
const checkedType = (target) => {
return Object.prototype.toString.call(target).slice(8, -1);
}
const deepClone = (target) => {
let result, targetType = checkedType(target);
if (targetType === 'Object') {
result = {};
} else if (targetType === 'Array') {
result = [];
} else {
return target;
}
for (let i in target) {
let value = target[i];
if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
result[i] = deepClone(value);
} else {
result[i] = value;
}
}
return result;
}
// 检测深度和广度
const createData = (deep, breadth) => {
const data = {};
let temp = data;
for (let i = 0; i < deep; i++) {
temp = temp['data'] = {};
for (let j = 0; j < breadth; j++) {
temp[j] = j;
}
}
return data;
};
console.log(createData(1, 3));
// 1 层深度,每层有 3 个数据 { data: { '0': 0, '1': 1, '2': 2 } }
console.log(createData(3, 0));
// 3 层深度,每层有 0 个数据 { data: { data: { data: {} } } }
console.log(deepClone(createData(1000)));
// 1000 层深度,无压力 { data: { data: { data: [Object] } } }
console.log(deepClone(createData(10, 100000)));
// 100000 层广度,没问题,数据遍历需要时间
console.log(deepClone(createData(10000)));
// 10000 层深度,直接爆栈:Maximum call stack size exceeded
Да, ваша глубокая копия взорвалась! ! !
Хотя вероятность взрыва стека в бизнес-сценариях относительно невелика, ведь уровней данных не так много, но такая ситуация все же существует, как с ней быть?
Друзья, которые просто хотят иметь общее представление о возможных проблемах с глубоким копированием, могут пропустить следующий контент.
В качестве примера предположим, что есть структура данных:
const a = {
a1: 1,
a2: {
b1: 1,
b2: {
c1: 1
}
}
};
Если мы посмотрим на это как на число:
a
/ \
a1 a2
| / \
1 b1 b2
| |
1 c1
|
1
Затем мы можем использовать итеративный метод для прохода по дереву!
- Во-первых, нам нужно использовать стек. Когда стек пуст, обход завершается, и следующий узел для копирования сохраняется в стеке.
- Затем поместите начальные данные в стек,
key
Объект копирования дочернего элемента, используемый для хранения того, какой родительский элемент - Наконец, пройдитесь по дочерним элементам под текущим узлом, если это объект, поместите его в стек, в противном случае скопируйте его напрямую.
const deepClone = (x) => {
const root = {};
// 栈
const loopList = [
{
parent: root,
key: undefined,
data: x
}
];
while (loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key 为 undefined 则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== "undefined") {
res = parent[key] = {};
}
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === "object") {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k]
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
Затем мы проходимcreateData
Проверка широты и глубины покажет:
console.log(deepClone(createData(10, 100000)));
// 100000 层广度,没问题,数据遍历需要时间
console.log(deepClone(createData(100000)));
// 100000 层深度,也没问题了:{ data: { data: { data: [Object] } } }
Таким образом, мы решили проблему взрыва стека.
Вот рекомендуемые варианты цитирования из статьи большого парня:Окончательный поиск глубокого копирования, который затем поставляется с библиотекой глубокого копирования:@jsmini/clone, и заинтересованные друзья могут пойти и посмотреть.
5.2 JSON.parse(JSON.stringify())
На самом деле, использование инструментов для достижения целей — это очень умный подход, обсудим его ниже.JSON.parse(JSON.stringify())
.
-
JSON.stringify()
: преобразовать объект в строку JSON. -
JSON.parse()
: Разбирает строку в объект.
пройти черезJSON.parse(JSON.stringify())
Преобразуйте объект JavaScript в сериализацию (преобразуйте его в строку JSON), а затем восстановите его в объект JavaScript, мы создадим новый объект, и объект откроет новый стек для достижения глубокого копирования.
Обратите внимание на ограничения этого метода:
1. Функция или Undefined не могут быть сохранены, иначе функция или Undefined будут потеряны;
2. Не сохраняйте объект времени, иначе он станет строковой формой;
3. Объекты RegExp и Error хранить нельзя, иначе они станут пустыми объектами;
4. NaN, Infinity, -Infinity нельзя хранить, иначе он станет нулевым;
5. Пожалуйста, заполните яму для более подробной информации. В частности, существуют различия между JavaScript и JSON. Если они несовместимы, возникнут проблемы.
const arr1 = [
1,
{
username: 'jsliang',
},
];
let arr2 = JSON.parse(JSON.stringify(arr1));
arr2[0] = 2;
arr2[1].username = 'LiangJunrong';
console.log(arr1);
// [ 1, { username: 'jsliang' } ]
console.log(arr2);
// [ 2, { username: 'LiangJunrong' } ]
5.3 Функциональная библиотека Lodash
Как любимая и отличная библиотека функций/инструментов JavaScript, Lodash имеет очень полезные инкапсулированные функции.Вы можете попробовать это:
Вот и смотримcloneDeep()
метод:
Как видите, этот метод будет рекурсивно копироватьvalue
.
Здесь мы переживаем егоcloneDeep()
:
// npm i -S lodash
var _ = require('lodash');
const obj1 = [
1,
'Hello!',
{ name: 'jsliang1' },
[
{
name: 'LiangJunrong1',
}
],
]
const obj2 = _.cloneDeep(obj1);
obj2[0] = 2;
obj2[1] = 'Hi!';
obj2[2].name = 'jsliang2';
obj2[3][0].name = 'LiangJunrong2';
console.log(obj1);
// [
// 1,
// 'Hello!',
// { name: 'jsliang1' },
// [
// { name: 'LiangJunrong1' },
// ],
// ]
console.log(obj2);
// [
// 2,
// 'Hi!',
// { name: 'jsliang2' },
// [
// { name: 'LiangJunrong2' },
// ],
// ]
Здесь мы используем форму Node для установки его зависимостей.Если вам нужно использовать MDN и т. д., вы можете зайти на его официальный сайт, чтобы посмотреть. (Адрес в начале этого раздела)
5.4 Фреймворк jQuery
Конечно, вы не можете винить свою компанию в том, что она все еще использует jQuery, вам также может потребоваться совместимость с IE6/7/8 или вы используете React, но в некоторых сценариях также используется jQuery, в конце концов, jQuery — мощная среда.
Давайте попробуем использовать jQueryextend()
Сделайте глубокую копию:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<p>尝试 jQuery 深拷贝</p>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
$(function() {
const obj1 = [
1,
'Hello!',
{ name: 'jsliang1' },
[
{
name: 'LiangJunrong1',
}
],
]
const obj2 = {};
/**
* @name jQuery深拷贝
* @description $.extend(deep, target, object1, object2...)
* @param {Boolean} deep 可选 true 或者 false,默认是 false,所以一般如果需要填写,最好是 true。
* @param {Object} target 需要存放的位置
* @param {Object} object 可以有 n 个原数据
*/
$.extend(true, obj2, obj1);
obj2[0] = 2;
obj2[1] = 'Hi!';
obj2[2].name = 'jsliang2';
obj2[3][0].name = 'LiangJunrong2';
console.log(obj1);
// [
// 1,
// 'Hello!',
// { name: 'jsliang1' },
// [
// { name: 'LiangJunrong1'},
// ],
// ];
console.log(obj2);
// [
// 2,
// 'Hi!',
// { name: 'jsliang2' },
// [
// { name: 'LiangJunrong2' },
// ],
// ];
});
</script>
</body>
</html>
Здесь, поскольку кажется, что прямая ссылка Node на пакет не увенчалась успехом, мы передаемindex.html
, сослался на пакет CDN jQuery, чтобы попробовать его глубокую копию.
Рекомендуется пройти
live-server
отслеживать изменения в HTML-файлах в режиме реального времени
Проверьте, если необходимо
jQuery.extend()
Исходный код можете посмотреть в статье:
Реализация Deep Copy и Shallow Copy (1)
JavaScript поверхностное копирование и глубокое копирование
исходный код jQuery.extend
jQuery.extend = jQuery.fn.extend = function() {
var options,
name,
src,
copy,
copyIsArray,
clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if (typeof target === "boolean") {
deep = target;
// Skip the boolean and the target
target = arguments[i] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== "object" && !jQuery.isFunction(target)) {
target = {};
}
// Extend jQuery itself if only one argument is passed
if (i === length) {
target = this;
i--;
}
for (; i < length; i++) {
// Only deal with non-null/undefined values
if ((options = arguments[i]) != null) {
// Extend the base object
for (name in options) {
src = target[name];
copy = options[name];
// Prevent never-ending loop
if (target === copy) {
continue;
}
// Recurse if we're merging plain objects or arrays
if (
deep &&
copy &&
(jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))
) {
if (copyIsArray) {
copyIsArray = false;
clone = src && Array.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[name] = jQuery.extend(deep, clone, copy);
// Don't bring in undefined values
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
// Return the modified object
return target;
};
Шесть резюме
Жаль, что мы закончили на данный моментМелкие и глубокие копииПервый этап исследования~
При написании этой серииjsliangЯ колебался, не спал всю ночь, хотел сдаться... Но я все равно выстоял и прошел через это снова.
В процессе исследования материалов для обогащения этой серии знаний,jsliangЯ проигнорировал некоторые точки знаний для изучения, иначе будет больше сомнений, и, в конце концов, я все еще полон энергии со всей ночи до 04:00 ночи~
Вот некоторые моменты, которые мы не рассмотрели:
- Как Lodash реализует глубокую копию
- Как jQuery реализует глубокое копирование
- Принцип Object.assign и его реализация
- ...
Некоторые из вас могут почувствовать, что:
- Ах, как вам не стыдно, если вы сначала не закончите жеребьевку, не разберетесь досконально и не опубликуете!
enm...как бы сказать, в первую очередь при написании этой статьи,jsliangТо, что я делаю, это широкое исследование, то есть я узнаю каждую встреченную точку знания.На самом деле, писать таким образом очень утомительно, но прогресс очень велик, потому что вы обнаружили некоторые точки знаний.
Потом,jsliangВ начале целью было просто понять, как написать глубокую копию вручную и некоторые инструменты для быстрой реализации глубокой копии, поэтому я лично чувствую, что эта цель достигла или даже превзошла стандарт.
Наконец, есть некоторые знания, такие как почеркObject.assign()
Или чтобы понять глубокое копирование исходного кода Lodash, на самом деле друзья, которые хотят знать больше, обязательно изучат его сами.Конечно, если друзья хотят, чтобы «предшественники лежали в яме», то они могут ожидать к моему последующему улучшению.
после всего:Какая разница между передком без заброса и соленой рыбой!
Итак, если вы хотите продолжить следить, вы можете перейти кjsliangизРепозиторий GitHubНайдите мой официальный аккаунт, WeChat, QQ и другую контактную информацию на главной странице.
Что ж, увидимся позже~
- использованная литература:
- Серия клонов Lodash
- Поверхностная копия и глубокая копия
- Окончательный поиск глубокого копирования
- Реализация глубокого копирования и поверхностного копирования (1)
- Глубокая и простая глубокая копия и мелкая копия
- Что такое глубокая и поверхностная копия js и как ее реализовать
- JavaScript поверхностное копирование и глубокое копирование
- js глубокая копия против поверхностной копии
- Окончательный поиск глубокого копирования (99% людей не знают)
- Как реализовать глубокое копирование вопросов на собеседовании
jsliangРекламный толчок:
Может быть, друг хочет узнать об облачных серверах
Или друг хочет купить облачный сервер
Или маленькому партнеру необходимо обновить облачный сервер
Добро пожаловать, чтобы нажатьПродвижение облачного сервераПроверить!
библиотека документации jsliangЗависит отЛян ЦзюньронгиспользоватьCreative Commons Attribution-NonCommercial-ShareAlike 4.0 Международная лицензияЛицензия.
на основеGitHub.com/l ian Jun Ron…Создание работ выше.
Права на использование, отличные от разрешенных в настоящем Лицензионном соглашении, могут быть получены отCreative Commons.org/licenses/не…получено в.