предисловие
Осуществление глубокого клонирования — распространенный вопрос на собеседованиях, но большинство ответов интервьюируемых неполные или даже неправильные. В это время интервьюер будет продолжать задавать вопросы, чтобы узнать, понимаете ли вы принцип глубокого клонирования. Во многих случаях некоторые интервьюеры, мало знающие об этом, упустят правду.
Давайте посмотрим, как реализовать глубокий клон.Конечно, у вас нет полной реализации на собеседовании, но вы должны выяснить, где находятся ямки, чтобы вы могли легко отвечать на вопросы интервьюера.
Прежде чем мы сможем реализовать глубокий клон, нам нужно понять основные типы в javascript.
Примитивные типы JavaScript: Undefined, Null, Boolean, Number, String, Symbol
Тип ссылки JavaScript: Объект
1. Мелкий клон
Мелкий клонпочему это называетсяМелкий клон, потому что объект будет клонирован только в самом внешнем слое, а более глубокий объект по-прежнему указывает на ту же динамическую память по ссылке.
// 浅克隆函数
function shallowClone(o) {
const obj = {};
for ( let i in o) {
obj[i] = o[i];
}
return obj;
}
// 被克隆对象
const oldObj = {
a: 1,
b: [ 'e', 'f', 'g' ],
c: { h: { i: 2 } }
};
const newObj = shallowClone(oldObj);
console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 }
console.log(oldObj.c.h === newObj.c.h); // true
Как видим, ясно, что хотяoldObj.c.h
клонирован, но также связан сoldObj.c.h
равны, что означает, что они по-прежнему указывают на один и тот же кусок памяти кучи, что приводит кnewObj.c.h
Изменения коснутся иoldObj.c.h
, это не хороший клон.
newObj.c.h.i = 'change';
console.log(newObj.c.h, oldObj.c.h); // { i: 'change' } { i: 'change' }
мы изменилисьnewObj.c.h.i
значение ,oldObj.c.h.i
также изменен, и это проблема мелких клонов.
Конечно есть новый апиObject.assign()
Поверхностного копирования тоже можно добиться, но эффект ничем не отличается от приведенного выше, поэтому не будем вдаваться в подробности.
2. Глубокое клонирование
2.1 Метод JSON.parse
Несколько лет назад ходила легенда, что самый удобный метод глубокого клонирования был распространен на Weibo. Метод синтаксического анализа объектов JSON может десериализовать строки JSON в объекты JS, а метод stringify может сериализовать объекты JS в строки JSON. Сочетание этих двух методов может создать удобное глубокое клонирование.
const newObj = JSON.parse(JSON.stringify(oldObj));
Мы по-прежнему используем пример из предыдущего раздела для проверки
const oldObj = {
a: 1,
b: [ 'e', 'f', 'g' ],
c: { h: { i: 2 } }
};
const newObj = JSON.parse(JSON.stringify(oldObj));
console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 }
console.log(oldObj.c.h === newObj.c.h); // false
newObj.c.h.i = 'change';
console.log(newObj.c.h, oldObj.c.h); // { i: 'change' } { i: 2 }
Конечно, это хороший способ реализовать глубокое клонирование, но не слишком ли просто это решение.
Действительно, хотя этот метод может решить большинство сценариев использования, он имеет много подводных камней.
1. Он не может клонировать специальные объекты, такие как функции и RegExp.
2. Конструктор объекта будет отброшен, и все конструкторы будут указывать на объект
3. Объект имеет ссылку на цикл, сообщит об ошибке
Основные ямы — это вышеперечисленные точки, будем тестировать их по очереди.
// 构造函数
function person(pname) {
this.name = pname;
}
const Messi = new person('Messi');
// 函数
function say() {
console.log('hi');
};
const oldObj = {
a: say,
b: new Array(1),
c: new RegExp('ab+c', 'i'),
d: Messi
};
const newObj = JSON.parse(JSON.stringify(oldObj));
// 无法复制函数
console.log(newObj.a, oldObj.a); // undefined [Function: say]
// 稀疏数组复制错误
console.log(newObj.b[0], oldObj.b[0]); // null undefined
// 无法复制正则对象
console.log(newObj.c, oldObj.c); // {} /ab+c/i
// 构造函数指向错误
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person]
Мы видим, что есть исключения при клонировании таких объектов, как функции, обычные объекты, разреженные массивы и т. д., и ошибки в указателях конструктора.
const oldObj = {};
oldObj.a = oldObj;
const newObj = JSON.parse(JSON.stringify(oldObj));
console.log(newObj.a, oldObj.a); // TypeError: Converting circular structure to JSON
Циклическая ссылка на объект вызывает ошибку.
2.2 Построение функции глубокого клонирования
Мы знаем, что для реализации надежного метода глубокого клонированияПоследовательность/десериализацияЭто невозможно, и методы, упомянутые в учебнике, обычно ненадежны, и их проблемы согласуются с проблемами, выделенными в последней операции деупорядочения последовательности.
(Этот метод также будет иметь проблемы, упомянутые в предыдущем разделе)Поскольку нам приходится иметь дело с разными объектами (обычными, массивами, датами и т. д.) по-разному, нам необходимо реализовать функцию оценки типа объекта.
const isType = (obj, type) => {
if (typeof obj !== 'object') return false;
const typeString = Object.prototype.toString.call(obj);
let flag;
switch (type) {
case 'Array':
flag = typeString === '[object Array]';
break;
case 'Date':
flag = typeString === '[object Date]';
break;
case 'RegExp':
flag = typeString === '[object RegExp]';
break;
default:
flag = false;
}
return flag;
};
Таким образом, мы можем выполнять суждение о типах специальных объектов, чтобы принять целевую стратегию клонирования.
const arr = Array.of(3, 4, 5, 2);
console.log(isType(arr, 'Array')); // true
Для обычных объектов нам нужно добавить немного новых знаний перед обработкой.
нам нужно пройтирегулярное расширениепониматьflags
свойства и так далее, поэтому нам нужно реализовать функцию, извлекающую флаги.
const getRegExp = re => {
var flags = '';
if (re.global) flags += 'g';
if (re.ignoreCase) flags += 'i';
if (re.multiline) flags += 'm';
return flags;
};
После выполнения этих приготовлений мы можем реализовать глубокое клонирование.
/**
* deep clone
* @param {[type]} parent object 需要进行克隆的对象
* @return {[type]} 深克隆后的对象
*/
const clone = parent => {
// 维护两个储存循环引用的数组
const parents = [];
const children = [];
const _clone = parent => {
if (parent === null) return null;
if (typeof parent !== 'object') return parent;
let child, proto;
if (isType(parent, 'Array')) {
// 对数组做特殊处理
child = [];
} else if (isType(parent, 'RegExp')) {
// 对正则对象做特殊处理
child = new RegExp(parent.source, getRegExp(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (isType(parent, 'Date')) {
// 对Date对象做特殊处理
child = new Date(parent.getTime());
} else {
// 处理对象原型
proto = Object.getPrototypeOf(parent);
// 利用Object.create切断原型链
child = Object.create(proto);
}
// 处理循环引用
const index = parents.indexOf(parent);
if (index != -1) {
// 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
return children[index];
}
parents.push(parent);
children.push(child);
for (let i in parent) {
// 递归
child[i] = _clone(parent[i]);
}
return child;
};
return _clone(parent);
};
Давайте проведем тест
function person(pname) {
this.name = pname;
}
const Messi = new person('Messi');
function say() {
console.log('hi');
}
const oldObj = {
a: say,
c: new RegExp('ab+c', 'i'),
d: Messi,
};
oldObj.b = oldObj;
const newObj = clone(oldObj);
console.log(newObj.a, oldObj.a); // [Function: say] [Function: say]
console.log(newObj.b, oldObj.b); // { a: [Function: say], c: /ab+c/i, d: person { name: 'Messi' }, b: [Circular] } { a: [Function: say], c: /ab+c/i, d: person { name: 'Messi' }, b: [Circular] }
console.log(newObj.c, oldObj.c); // /ab+c/i /ab+c/i
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: person] [Function: person]
Конечно, наш глубокий клон не идеален. Например, для объектов Buffer, Promises, Sets и Maps может потребоваться специальная обработка. Кроме того, для объектов, которые не гарантируют отсутствие циклических ссылок, мы можем опустить специальную обработку для циклических ссылок. , потому что это очень трудоемкая, но базовая функция глубокого клонирования, которую мы реализовали.
Суммировать
Реализация полного глубокого клона требует много ям, а реализация некоторых библиотек на npm недостаточно полная, лучше всего использовать в производственной среде.lodash
Глубокий клон достижения.
Во время процесса собеседования многие ямы, которые мы упомянули выше, могут быть допрошены интервьюером. Вы должны знать, где ящики и могут ответить на них, это бонус для вас. Должен быть один или два ярких пятна в Интервью процесс. Если только знатьПоследовательность/десериализацияТакого рода конъюнктурный метод не только не даст очков при расспросе, но и может создать впечатление, что вы разбираетесь только в коже, ведь собеседование – это глубина ваших знаний.