В этой статье представлена рукописная версия глубокого и поверхностного копирования, основное внимание уделяется решению одной и той же проблемы циклической ссылки и ссылки на функцию глубокого копирования.
1. Тип данных
1. Основные типы данных
Число, строка, логическое значение, нуль, неопределенное значение, символ, большое число
Bigint — это новый базовый тип данных.
2. Справочные типы данных
Объект, Массив, Функция и т.д.
В этой статье основное внимание уделяется не типу данных, а реализации глубокого и поверхностного копирования.
下面是要copy的对象, 之后的代码都会直接使用$obj, 之后不会再次声明
var $obj = {
func: function () {
console.log('this is function')
},
date: new Date(),
symbol: Symbol(),
a: null,
b: undefined,
c: {
a: 1
},
e: new RegExp('regexp'),
f: new Error('error')
}
$obj.c.d = $obj
2. Поверхностное копирование
1. Что такое мелкое копирование
Одним предложением можно сказать, что: для объекта, если значение его атрибута первого уровня является базовым типом данных, копируется копия данных полностью, а если это ссылочный тип, копируется адрес памяти. Это действительно очень мелкая копия [смеется]
2. Осознайте
Object.assign()
let obj1 = {
name: 'yang',
res: {
value: 123
}
}
let obj2 = Object.assign({}, obj1)
obj2.res.value = 456
console.log(obj2) // {name: "yang", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}
obj2.name = 'haha'
console.log(obj2) // {name: "haha", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}
Развернуть синтаксисSpread
let obj1 = {
name: 'yang',
res: {
value: 123
}
}
let {...obj2} = obj1
obj2.res.value = 456
console.log(obj2) // {name: "yang", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}
obj2.name = 'haha'
console.log(obj2) // {name: "haha", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}
Array.prototype.slice
const arr1 = [
'yang',
{
value: 123
}
];
const arr2 = arr1.slice(0);
arr2[1].value = 456;
console.log(arr2); // ["yang", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]
arr2[0] = 'haha';
console.log(arr2); // ["haha", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]
Array.prototype.concat
const arr1 = [
'yang',
{
value: 123
}
];
const arr2 = [].concat(arr1);
arr2[1].value = 456;
console.log(arr2); // ["yang", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]
arr2[0] = 'haha';
console.log(arr2); // ["haha", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]
实际上对于数组来说, 只要不修改原数组, 重新返回一个新数组就可以实现浅拷贝,比如说map、filter、reduce等方法
3. Глубокое копирование
1. Что такое глубокая копия
Глубокая копия заключается в создании новой копии независимо от того, является ли она базовым типом данных или эталонным типом данных, и не существует явления общих данных.
2. Осознайте
насильственная версияJSON.parse(JSON.stringify(object))
let obj = JSON.parse(JSON.stringify($obj))
console.log(obj) // 不能解决循环引用
/*
VM348:1 Uncaught TypeError: Converting circular structure to JSON
at JSON.stringify (<anonymous>)
at <anonymous>:1:17
*/
delete $obj.c.d
let obj = JSON.parse(JSON.stringify($obj))
console.log(obj) // 丢失了大部分属性
/*
{
a: null
c: {a: 1}
date: "2020-04-05T09:51:32.610Z"
e: {}
f: {}
}
*/
Существующие проблемы:
1. будет проигнорированundefined
2. будет игнорироватьsymbol
3. Нельзя сериализовать функции
4. Объекты, которые не могут разрешать циклические ссылки
5, не может быть обработан правильноnew Date()
6, не может справиться с обычным
7. Не удается обработать новую ошибку()
Первое издание Базовое издание
Рекурсивный обход свойств объекта
function deepCopy (obj) {
if (obj === null || typeof obj !== 'object') {
return obj
}
let copy = Array.isArray(obj) ? [] : {}
Object.keys(obj).forEach(v => {
copy[key] = deepCopy(obj[key])
})
return copy
}
deepCopy($obj)
/*
VM601:23 Uncaught RangeError: Maximum call stack size exceeded
at <anonymous>:23:30
at Array.forEach (<anonymous>)
at deepCopy (<anonymous>:23:22)
*/
delete $obj.c.d
deepCopy($obj)
/*
{
a: null
b: undefined
c: {a: 1}
date: {}
e: {}
f: {}
func: ƒ ()
symbol: Symbol()
}
*/
Проблема в:
1. Объекты, которые не могут разрешать циклические ссылки
2, не может быть обработан правильноnew Date()
3, не может справиться с обычным
4. Не удается обработать новую ошибку()
Второе издание, разрешающее циркулярные ссылки
Сначала решите проблему обхода цикла.Решение состоит в том, чтобы сохранить объект и свойства объекта в массиве, чтобы проверить, есть ли объекты, которые были пройдены при следующем обходе, и вернуться напрямую, если они есть, в противном случае продолжить обход
function deepCopy (obj, cache = []) {
if (obj === null || typeof obj !== 'object') {
return obj
}
const item = cache.filter(item => item.original === obj)[0]
if (item) return item.copy
let copy = Array.isArray(obj) ? [] : {}
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
deepCopy($obj)
/*
{
a: null
b: undefined
c: {a: 1, d: {…}}
date: {}
e: {}
f: {}
func: ƒ ()
symbol: Symbol()
}
Отлично решает проблему круговой ссылки, но есть еще несколько мелких проблем, все из которых относятся к одному типу задач
Третье издание, решающее специальные значения
За обработку нескольких последних объектов можно судить о типе, а можно просто вернуть новый.
function deepCopy (obj, cache = []) {
if (obj === null || typeof obj !== 'object') {
return obj
}
if (Object.prototype.toString.call(obj) === '[object Date]') return new Date(obj)
if (Object.prototype.toString.call(obj) === '[object RegExp]') return new RegExp(obj)
if (Object.prototype.toString.call(obj) === '[object Error]') return new Error(obj)
const item = cache.filter(item => item.original === obj)[0]
if (item) return item.copy
let copy = Array.isArray(obj) ? [] : {}
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
deepCopy($obj)
/*
{
a: null
b: undefined
c: {a: 1, d: {…}}
date: Fri Apr 10 2020 20:06:08 GMT+0800 (中国标准时间) {}
e: /regexp/
f: Error: Error: error at deepCopy (<anonymous>:8:74) at <anonymous>:19:21 at Array.forEach (<anonymous>) at deepCopy (<anonymous>:18:22) at <anonymous>:24:1
func: ƒ ()
symbol: Symbol()
}
*/
除此之外Promise、Set、Map、WeakSet、WeakMap也可以用相同的办法处理
Четвертое издание разрешает ту же ссылку на функцию
Основные функции здесь вроде реализованы, но есть еще проблема, то есть функция ссылается на один и тот же адрес памяти, для этой проблемы большая часть онлайн-возвратов идет напрямую или возвращается как объекты, в том числе и lodash.
const isFunc = typeof value == 'function'
if (isFunc || !cloneableTags[tag]) {
return object ? value : {}
}
Итак, чтобы решить эту проблему, необходимо использовать функцию eval.Хотя эта функция устарела, она все же может решить проблему. Существует два типа функций:普通函数а также箭头函数, различие между ними нужно только увидеть, есть лиprototype, имеютprototypeСвойства — это обычные функции, и ни одна из них не является стрелочной функцией.
Обычные функции относятся к объявлениям функций, и eval не может использоваться напрямую, их нужно заключать в круглые скобки для формирования функциональных выражений, а сами стрелочные функции являются функциональными выражениями.
// lmran
function copyFunction(func) {
let fnStr = func.toString()
return func.prototype ? eval(`(${fnStr})`) : eval(fnStr)
}
function deepCopy (obj, cache = []) {
if (typeof obj === 'function') {
return copyFunction(obj)
}
if (obj === null || typeof obj !== 'object') {
return obj
}
if (Object.prototype.toString.call(obj) === '[object Date]') return new Date(obj)
if (Object.prototype.toString.call(obj) === '[object RegExp]') return new RegExp(obj)
if (Object.prototype.toString.call(obj) === '[object Error]') return new Error(obj)
const item = cache.filter(item => item.original === obj)[0]
if (item) return item.copy
let copy = Array.isArray(obj) ? [] : {}
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
deepCopy($obj).func === $obj.func // false
Суммировать
До сих пор были реализованы все глубокие и мелкие копии, но обучение бесконечно, мы также можем рассмотреть возможность использованияProxyУлучшена производительность глубокого копирования за счет перехватаsetа такжеgetдобиться, конечноObject.defineProperty()Также может. Заинтересованные студенты могут проверить эту статью, статья очень подробнаяИнтервьюер Toutiao: Вы знаете, как реализовать глубокую копию высокопроизводительной версии?
развеять сомнения
В ответ на ту же проблему, поднятую @Brota при поиске ссылок на функции, статья была исправлена, но если функция является встроенным кодом функции, проблема все еще существует, и теперь дается решение. встроенная функция, она возвращается напрямую. Но он возвращается к той же ссылке на функцию, есть копия самой встроенной функции, так что проблем с тем же адресом ссылки нет.
function copyFunction(func) {
let fnStr = func.toString()
if (fnStr === `function ${func.name}() { [native code] }`) {
return func
}
return func.prototype ? eval(`(${fnStr})`) : eval(fnStr)
}
目前还未实现的功能:闭包的处理, 暂未找到处理办法,有知道的可以在下面留言评论
Если в статье есть какие-либо проблемы, вы можете активно указать на них, большое спасибо! ! !