Управляемое чтение
Я видел много в последнее времяJavaScript
Резюме статьи о написанном от руки коде, в котором содержится многоJavaScript Api
рукописное исполнение.
Большинство тем в ней похожи, и если честно многие коды на мой взгляд очень простые.Если я увижу такой код как интервьюер, то в душе не буду квалифицированным.В этой статье я возьму самый простой 1. Давайте поговорим о глубоком копировании.
Прежде чем читать эту статью, задайте себе три вопроса:
-
Вы действительно понимаете, что такое глубокая копия?
-
В глазах интервьюера, какой глубокий текст считается квалифицированным?
-
Какой глубокий текст может удивить интервьюера?
Эта статья идет от простого к глубокому и шаг за шагом поможет вам создать глубокую копию замечательного интервьюера.
Тестовый код этой статьи:GitHub.com/con AR DL i/co…
Например: после локального клонирования кода выполните узел clone1.test.js, чтобы просмотреть результаты теста.
Рекомендуется читать вместе с тестовым кодом для лучших результатов.
Определение глубокого копирования и мелкого копирования
Глубокое копирование уже является распространенной темой, и теперь это также часто обсуждаемая тема на фронтенд-интервью, но я удивлен, что многие студенты не поняли разницы и определения глубокого копирования и поверхностного копирования. Например, вы говорили мне об этом несколько дней назад.issue
одноклассники:
Очевидно, этот одноклассник путает копирование и присваивание.Если у вас остались вопросы по присваиванию, хранению объектов в памяти, переменным и типам и т. д., вы можете прочитать мою статью:nuggets.capable/post/684490….
Вам просто нужно меньше понимать拷贝
а также赋值
разница.
Давайте уточним определение глубокого копирования и поверхностного копирования:
Мелкая копия:
Создает новый объект с точной копией значений свойств исходного объекта. Если атрибут является примитивным типом, копируется значение примитивного типа, а если атрибут является ссылочным типом, копируется адрес памяти, поэтому, если один объект изменит этот адрес, это повлияет на другой объект.
Глубокая копия:
Полностью скопируйте объект из памяти, откройте новую область в динамической памяти для хранения нового объекта, а изменение нового объекта не повлияет на исходный объект.
Нечего сказать, больше нечего сказать о поверхностном копировании, давайте сразу к делу:
нищенское издание
В случае неиспользования сторонних библиотек мы хотим глубоко скопировать объект, и наиболее часто используется следующий метод.
JSON.parse(JSON.stringify());
Этот способ написания очень прост и подходит для большинства сценариев приложений, но у него все еще есть большие недостатки, такие как копирование других типов ссылок, копирование функций и циклических ссылок.
Очевидно, что вы не пройдете собеседование, если будете говорить только таким способом.
Далее давайте реализуем метод глубокого копирования вручную.
базовая версия
Если это поверхностная копия, мы можем легко написать следующий код:
function clone(target) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
};
Создайте новый объект, обойдите объект, который нужно клонировать, добавьте свойства клонированного объекта к новому объекту по очереди и вернитесь.
Если это глубокая копия, учитывая, что объект, который мы хотим скопировать, не знает, сколько слоев в глубину, мы можем использовать рекурсию для решения проблемы и немного переписать приведенный выше код:
- Если это примитивный тип, не нужно продолжать копирование, возвращайтесь напрямую
- Если это ссылочный тип, создайте новый объект, перейдите к объекту, который нужно клонировать, и выполните свойства клонированного объекта.После глубокого копированияВ свою очередь добавлен новый объект.
Легко понять, что если есть более глубокие объекты, которые могут продолжать рекурсию до тех пор, пока свойство не станет примитивным типом, то мы выполнили простейшую глубокую копию:
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
Мы можем открыть тестовый код вclone1.test.jsПротестируйте следующий тестовый пример:
const target = {
field1: 1,
field2: undefined,
field3: 'ConardLi',
field4: {
child: 'child',
child2: {
child2: 'child2'
}
}
};
Результаты:
Это глубокая копия самой базовой версии, этот код позволяет показать интервьюеру, что можно решить задачу с помощью рекурсии, но явно имеет массу недоработок, например, пока не считает массивы.
Рассмотрим массив
В приведенной выше версии наши результаты инициализации учитывают только обычныеobject
, нам нужно только немного изменить код инициализации, чтобы сделать массив совместимым:
module.exports = function clone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
существуетclone2.test.jsВыполните следующий тестовый пример:
const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8]
};
Результаты:
Хорошо, нет проблем, ваш код на один маленький шаг ближе к прохождению.
циклическая ссылка
Мы выполняем следующий тестовый пример:
const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8]
};
target.target = target;
Вы можете увидеть следующие результаты:
Очевидно, стек переполняется, потому что рекурсия входит в бесконечный цикл.
Причина в том, что указанный выше объект имеет циклическую ссылку, то есть свойство объекта ссылается на себя косвенно или напрямую:
Чтобы решить проблему циклической ссылки, мы можем открыть дополнительное пространство для хранения для хранения соответствующей связи между текущим объектом и скопированным объектом.Когда текущий объект необходимо скопировать, сначала перейдите в пространство для хранения, чтобы узнать, есть ли объект был скопирован, и если это так, сразу верните, если нет, продолжите копирование, чтобы решить проблему циклической ссылки.
Это место для хранения должно иметь возможность хранитьkey-value
данные в видеkey
может быть ссылочным типом, мы можем выбратьMap
Эта структура данных:
- экзамен
map
Есть ли клонированные объекты? - да - вернуться напрямую
- Нет — текущий объект рассматривается как
key
, клонировать объект какvalue
хранить - продолжать клонировать
function clone(target, map = new Map()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = clone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
};
Затем выполните приведенный выше тестовый пример:
Видно, что ошибки в выполнении нет, иtarget
атрибут, становитсяCircular
Типа, то есть смысл кругового применения.
Далее мы можем использовать,WeakMap
ПриливMap
сделать код последним штрихом.
function clone(target, map = new WeakMap()) {
// ...
};
Почему ты делаешь это? Давайте взглянемWeakMap
Роль:
Объект WeakMap — это набор пар ключ/значение, где на ключи слабо ссылаются. Его ключи должны быть объектами, а значения могут быть произвольными.
Что такое слабая ссылка?
В компьютерном программировании слабая ссылка противоположна сильной ссылке и относится к ссылке, которая не может гарантировать, что объект, на который она ссылается, не будет утилизирован сборщиком мусора. Объект считается недоступным (или слабодоступным), если на него ссылаются только слабые ссылки, и поэтому его можно собрать в любое время.
Мы создаем объект по умолчанию:const obj = {}
, строго ссылочный объект создается по умолчанию, нам нужно только вручнуюobj = null
, он будет собран механизмом сборки мусора.Если это объект слабой ссылки, механизм сборки мусора автоматически поможет нам его собрать.
Например:
если мы используемMap
Если это так, то между объектами существует сильная ссылочная связь:
let obj = { name : 'ConardLi'}
const target = new Map();
target.set(obj,'code秘密花园');
obj = null;
Хотя мы вручнуюobj
, отпустить, тоtarget
все еще правobj
Существует сильная референтная связь, поэтому эта часть памяти все еще не может быть освобождена.
увидеть сноваWeakMap
:
let obj = { name : 'ConardLi'}
const target = new WeakMap();
target.set(obj,'code秘密花园');
obj = null;
еслиWeakMap
если,target
а такжеobj
Существует слабая ссылочная связь, и эта память будет освобождена при выполнении следующего механизма сборки мусора.
Представьте, что если объект, который мы хотим скопировать, очень большой, используйтеMap
Это вызовет очень большой дополнительный расход памяти, и нам нужно очистить ее вручнуюMap
Свойство для освобождения этой памяти иWeakMap
Это поможет нам решить эту проблему с умом.
Я также часто вижу, как люди используют в каком-то кодеWeakMap
решить проблему циклической ссылки, но объяснение неоднозначно, когда вы не совсем понимаетеWeakMap
реальный эффект. Я предлагаю вам не писать такой код на собеседовании, вы можете только выкопать яму для себя, даже если вы готовитесь к собеседованию, каждая строка кода, которую вы пишете, должна быть хорошо продумана и понята.
Можете принять во внимание проблему циклических ссылок, вы показали интервьюеру, насколько всесторонне вы думаете о проблеме, если вы все еще можете использоватьWeakMap
Решите проблему и четко объясните интервьюеру цель этого, тогда ваш код будет считаться квалифицированным в глазах интервьюера.
оптимизация производительности
В приведенном выше коде мы перебираем как массив, так и объект, используяfor in
Таким образом, на самом делеfor in
КПД при обходе очень низкий, давайте сравним три обычных циклаfor、while、for in
Эффективность исполнения:
можно увидеть,while
Эффективность наилучшая, поэтому мы можем найти способ поставитьfor in
обход изменен наwhile
траверс.
мы сначала используемwhile
реализовать общийforEach
траверс,iteratee
является функцией обратного вызова обхода, она может получать обходvalue
а такжеindex
Два параметра:
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}
ниже для нашегоcloen
Переписана функция: при обходе массива использовать ее напрямуюforEach
Для обхода при обходе объекта используйтеObject.keys
убрать всеkey
обход, то при обходеforEach
функция вызоваvalue
так какkey
использовать:
function clone(target, map = new WeakMap()) {
if (typeof target === 'object') {
const isArray = Array.isArray(target);
let cloneTarget = isArray ? [] : {};
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
const keys = isArray ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone2(target[key], map);
});
return cloneTarget;
} else {
return target;
}
}
Далее выполняемclone4.test.jsПротестируйте предыдущую функцию клонирования и переписанную функцию клонирования соответственно:
const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8],
f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: {} } } } } } } } } } } },
};
target.target = target;
console.time();
const result = clone1(target);
console.timeEnd();
console.time();
const result2 = clone2(target);
console.timeEnd();
Результаты:
Понятно, что наша оптимизация производительности эффективна.
На этом этапе вы показали интервьюеру, что думаете об эффективности программы при написании кода и что у вас есть возможность абстрагировать общие функции.
другие типы данных
В приведенном выше коде мы на самом деле рассматриваем только обычныеobject
а такжеarray
Два типа данных, на самом деле всех ссылочных типов гораздо больше, чем этих двух, их намного больше, давайте сначала попробуем получить точный тип объекта.
Разумное суждение об эталонном типе
Во-первых, чтобы определить, является ли это ссылочным типом, нам также необходимо рассмотретьfunction
а такжеnull
Два специальных типа данных:
function isObject(target) {
const type = typeof target;
return target !== null && (type === 'object' || type === 'function');
}
if (!isObject(target)) {
return target;
}
// ...
получить тип данных
мы можем использоватьtoString
чтобы получить точный ссылочный тип:
Каждый ссылочный тип имеет
toString
метод по умолчанию,toString()
метод каждогоObject
Наследование объекта. Если этот метод не переопределен в пользовательском объекте, toString()
вернуть"[object type]"
, где тип — это тип объекта.
Обратите внимание, что выше упомянуто, что если этот метод не переопределен в пользовательском объекте,toString
позволит достичь желаемого эффекта.На самом деле, большинство ссылочных типов, таких какArray、Date、RegExp
Это все переписаноtoString
метод.
Мы можем напрямую позвонитьObject
Раскрыто на прототипеtoString()
метод, используяcall
изменитьthis
Точка для достижения эффекта, который мы хотим.
function getType(target) {
return Object.prototype.toString.call(target);
}
Ниже мы извлекаем некоторые часто используемые типы данных для последующего использования:
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
В приведенных выше централизованных типах мы просто делим их на две категории:
- Типы, по которым можно продолжить обход
- Типы, которые нельзя пройти дальше
Делаем отдельные копии для каждого из них.
Типы, по которым можно продолжить обход
выше мы рассмотрелиobject
,array
Это все типы, по которым можно продолжать обход, потому что их память также может хранить данные других типов данных, и естьMap
,Set
И т. д. - это все типы, которые можно продолжать обходить. Здесь мы рассмотрим только эти четыре типа. Если вам интересно, вы можете продолжить изучение других типов.
Заказанные эти типы также должны продолжать рекурсию, нам сначала нужно получить их данные инициализации, такие как выше[]
а также{}
, мы можем получитьconstructor
способ получить общий доступ.
Например:const target = {}
то естьconst target = new Object()
синтаксический сахар. Кроме того, у этого метода есть еще одно преимущество: поскольку мы также используем метод построения исходного объекта, он может сохранять данные о прототипе объекта, если мы напрямую используем обычный{}
, то прототип должен быть потерян.
function getInit(target) {
const Ctor = target.constructor;
return new Ctor();
}
Далее мы переписываемclone
Функция, которая обрабатывает типы данных, которые можно продолжать просматривать:
function clone(target, map = new WeakMap()) {
// 克隆原始类型
if (!isObject(target)) {
return target;
}
// 初始化
const type = getType(target);
let cloneTarget;
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
}
// 防止循环引用
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value,map));
});
return cloneTarget;
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value,map));
});
return cloneTarget;
}
// 克隆对象和数组
const keys = type === arrayTag ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
мы выступаемclone5.test.jsПротестируйте следующий тестовый пример:
const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8],
empty: null,
map,
set,
};
Результаты:
Нет проблем, все готово, идем дальше, давайте перейдем к другим типам:
Типы, которые нельзя продолжать обходить
Для остальных оставшихся типов мы классифицируем их как необрабатываемые типы данных и обрабатываем их по очереди:
Bool
,Number
,String
,String
,Date
,Error
Для этих типов мы можем создать новый объект напрямую с помощью конструктора и исходных данных:
function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
default:
return null;
}
}
клонSymbol
Типы:
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
克隆正则:
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
На самом деле существует множество типов данных, о которых я здесь не писал, если вам интересно, вы можете продолжить их изучение и реализацию.
Можете написать это, интервьюер увидел строгость вашего рассмотрения проблемы, ваше понимание переменных и типов, правильноJS API
Уровень мастерства, я полагаю, интервьюер начал вас впечатлять.
функция клонирования
Наконец-то функцию клонирования я вынес отдельно.На самом деле функция клонирования не имеет практического сценария применения.Нет проблемы в использовании функции по одному и тому же адресу в памяти для двух объектов.Специально посмотрел.lodash
Обработка функций:
const isFunc = typeof value == 'function'
if (isFunc || !cloneableTags[tag]) {
return object ? value : {}
}
Можно видеть, что если будет обнаружено, что это функция, она вернется сразу без специальной обработки, но я обнаружил, что многие интервьюеры все еще стремятся задать этот вопрос, и, насколько я знаю, очень немногие могут быть решены. написано. . .
На самом деле этот способ не сложный, главное проверить, хорошо ли вы усвоили основы.
Во-первых, мы можем пройтиprototype
Чтобы отличить функцию стрелки вниз от обычной функции, функция стрелки неprototype
из.
мы можем напрямую использоватьeval
и строка функции для повторного создания функции стрелки, обратите внимание, что этот метод не работает для обычных функций.
Мы можем использовать регулярные выражения для обработки обычных функций:
Используйте обычный, чтобы вынуть тело функции и параметры функции, а затем используйтеnew Function ([arg1[, arg2[, ...argN]],] functionBody)
Конструктор реконструирует новую функцию:
function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
console.log('普通函数');
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
console.log('匹配到函数体:', body[0]);
if (param) {
const paramArr = param[0].split(',');
console.log('匹配到参数:', paramArr);
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
Наконец, давайте выполнимclone6.test.jsПротестируйте следующий тестовый пример:
const map = new Map();
map.set('key', 'value');
map.set('ConardLi', 'code秘密花园');
const set = new Set();
set.add('ConardLi');
set.add('code秘密花园');
const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8],
empty: null,
map,
set,
bool: new Boolean(true),
num: new Number(2),
str: new String(2),
symbol: Object(Symbol(1)),
date: new Date(),
reg: /\d+/,
error: new Error(),
func1: () => {
console.log('code秘密花园');
},
func2: function (a, b) {
return a + b;
}
};
Результаты:
наконец
Для лучшего чтения мы используем диаграмму, чтобы показать весь приведенный выше код:
Полный код:GitHub.com/con AR DL i/co…
Видно, что маленькая глубокая копия все же скрывает много очков знаний.
Не задавайте себе минимальный минимум, если вы просто имеете дело с одним вопросом в интервью, то вы, вероятно, собираетесь подготовиться к самой элементарным методам глубокого копирования выше.
Но цель интервьюера, который вас исследует, состоит в том, чтобы изучить ваши мыслительные способности во всех направлениях.Если вы напишете приведенный выше код, он может отразить ваши многогранные способности:
- Базовая реализация
- рекурсивная способность
- циклическая ссылка
- Учитывайте комплексность проблемы
- Понять истинное значение слабой карты
- много типов
- Учитывайте серьезность проблемы
- Методы создания различных типов ссылок, владение JS API
- Точно оценить тип данных и понять тип данных
- Общий обход:
- Написание кода может учитывать оптимизацию производительности
- Понимание эффективности централизованного обхода
- Возможность абстрагирования кода
- Функция копирования:
- Отличие стрелочных функций от обычных функций
- Знание регулярных выражений
Посмотрите, маленькая глубокая копия может проверить столько ваших способностей, если интервьюер увидит такой код, как ему не удивиться?
На самом деле, вы можете думать обо всех вопросах, заданных интервьюером, таким образом. Не запоминайте какой-то код только для того, чтобы попасть на собеседование, чтобы его могли показать опытным интервьюерам. Каждый фрагмент кода, который вы пишете, должен быть тщательно продуман, почему вы должны использовать его таким образом и как вы можете его оптимизировать... чтобы вы могли показать интервьюеру лучшую версию себя.
Ссылаться на
резюме
Надеюсь, что чтение этой статьи поможет вам в следующем:
- Поймите истинное значение глубокого и поверхностного копирования
- Я могу сделать глубокую копию всех имеющихся у меня точек и провести глубокий анализ проблемы
- Вы можете написать относительно полную глубокую копию вручную
Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь.
Если вы хотите читать больше качественных статей, то можете подписаться на мой блог на github.Ваша звезда✨, лайки и внимание — движущая сила моего постоянного творчества!
Рекомендую обратить внимание на мой паблик WeChat [code secret garden], каждый день выкладывать качественные статьи, будем общаться и расти вместе.
Мы являемся научно-исследовательской командой ByteDance Interactive Entertainment, включая Douyin Short Video, Douyin Volcano, TikTok, Faceu, Qingyan, Jianying и т. д. По состоянию на январь 2020 года Douyin Daily Active (DAU) превысил 400 миллионов человек и продолжает поддерживать быстрый рост. . Вы будете поддерживать разработку продуктов и связанные с ними архитектурные работы, где каждая строка кода может повлиять на сотни миллионов пользователей.
Код набора в школу 2021:DRZUM5Z
Официальный сайт доставки:job.toutiao.com/s/JR8SthH