Обновление: Спасибо за вашу поддержку. Недавно я бросил сводку данных для всех, чтобы прочитать систему. В будущем будет больше контента и больше оптимизации.Нажмите здесь, чтобы просмотреть
------ Далее идет текст ------
введение
В предыдущей статье я представил, как реализовать глубокую копию, объясняя объекты, массивы, циклические ссылки, потерянные ссылки,Symbol
И практика глубокого копирования в случае рекурсивного взрыва стека, давайте посмотрим сегодняLodash
Как реализовать практику глубокого копирования в функциях, отличных от вышеперечисленных, обычных, Date, Buffer, Map, Set, цепочки прототипов и т. д. Исходный код этой статьи основан наLodash
Версия 4.17.11.
Узнать большеGitHub
Общий процесс
Вход
Входной файлcloneDeep.js
, вызвать основной файл напрямуюbaseClone.js
Методы.
// 木易杨
const CLONE_DEEP_FLAG = 1
const CLONE_SYMBOLS_FLAG = 4
function cloneDeep(value) {
return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG)
}
Первый параметр — это копируемый объект, а второй — побитовая маска (Bitwise).Для подробного ознакомления с битовой маской см. раздел расширения ниже.
метод baseClone
затем мы входим./.internal/baseClone.js
Путь для просмотра конкретного метода, основная логика реализации находится в этом методе.
Сначала введем параметры этого методаbaseClone(value, bitmask, customizer, key, object, stack)
-
значение: объект для копирования
-
битовая маска: битовая маска, где 1 — глубокая копия, 2 — копия свойств в цепочке прототипов, а 4 — копия свойства Symbols
-
настройщик: индивидуальный
clone
функция -
key: ключ для передачи значения
-
объект: родительский объект переданного значения
-
стек: стек стека, используемый для обработки циклических ссылок.
Я объясню это в следующих частях, вы можете выбрать интересующую вас часть для чтения.
- битовая маска
- сделанный на заказ
clone
функция - не объект
- Массив и обычный
- Объекты и функции
- циклическая ссылка
- Map & Set
- Цепочка символов и прототипов
полный код baseClone
Эта часть является основным кодом. Функции разделены следующим образом. Подробная часть реализации функций подробно объяснит каждую функцию.
// 木易杨
function baseClone(value, bitmask, customizer, key, object, stack) {
let result
// 标志位
const isDeep = bitmask & CLONE_DEEP_FLAG // 深拷贝,true
const isFlat = bitmask & CLONE_FLAT_FLAG // 拷贝原型链,false
const isFull = bitmask & CLONE_SYMBOLS_FLAG // 拷贝 Symbol,true
// 自定义 clone 函数
if (customizer) {
result = object ? customizer(value, key, object, stack) : customizer(value)
}
if (result !== undefined) {
return result
}
// 非对象
if (!isObject(value)) {
return value
}
const isArr = Array.isArray(value)
const tag = getTag(value)
if (isArr) {
// 数组
result = initCloneArray(value)
if (!isDeep) {
return copyArray(value, result)
}
} else {
// 对象
const isFunc = typeof value == 'function'
if (isBuffer(value)) {
return cloneBuffer(value, isDeep)
}
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
result = (isFlat || isFunc) ? {} : initCloneObject(value)
if (!isDeep) {
return isFlat
? copySymbolsIn(value, copyObject(value, keysIn(value), result))
: copySymbols(value, Object.assign(result, value))
}
} else {
if (isFunc || !cloneableTags[tag]) {
return object ? value : {}
}
result = initCloneByTag(value, tag, isDeep)
}
}
// 循环引用
stack || (stack = new Stack)
const stacked = stack.get(value)
if (stacked) {
return stacked
}
stack.set(value, result)
// Map
if (tag == mapTag) {
value.forEach((subValue, key) => {
result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
return result
}
// Set
if (tag == setTag) {
value.forEach((subValue) => {
result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))
})
return result
}
// TypedArray
if (isTypedArray(value)) {
return result
}
// Symbol & 原型链
const keysFunc = isFull
? (isFlat ? getAllKeysIn : getAllKeys)
: (isFlat ? keysIn : keys)
const props = isArr ? undefined : keysFunc(value)
// 遍历赋值
arrayEach(props || value, (subValue, key) => {
if (props) {
key = subValue
subValue = value[key]
}
assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
// 返回结果
return result
}
Подробная реализация функций
битовая маска
Битовая маска кратко представлена выше, а параметры определены следующим образом.
// 木易杨
// 主线代码
const CLONE_DEEP_FLAG = 1 // 1 即 0001,深拷贝标志位
const CLONE_FLAT_FLAG = 2 // 2 即 0010,拷贝原型链标志位,
const CLONE_SYMBOLS_FLAG = 4 // 4 即 0100,拷贝 Symbols 标志位
Битовые маски используются для обработки случаев, когда одновременно присутствует несколько логических опций, гдеЗначение каждого параметра в маске равно степени числа 2.. Преимущество по сравнению с прямым использованием переменных состоит в том, что вы можете сэкономить память (1/32) (отMDN)
// 木易杨
// 主线代码
// cloneDeep.js 添加标志位,1 | 4 即 0001 | 0100 即 0101 即 5
CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG
// baseClone.js 取出标志位
let result // 初始化返回结果,后续代码需要,和位掩码无关
const isDeep = bitmask & CLONE_DEEP_FLAG // 5 & 1 即 1 即 true
const isFlat = bitmask & CLONE_FLAT_FLAG // 5 & 2 即 0 即 false
const isFull = bitmask & CLONE_SYMBOLS_FLAG // 5 & 4 即 4 即 true
Обычно используются следующие основные операции:
-
a | b
: добавить флаги a и b -
mask & a
: Выньте флаг немного -
mask & ~a
: Очистить флаг a -
mask ^ a
: выньте часть, отличную от
// 木易杨
var FLAG_A = 1; // 0001
var FLAG_B = 4; // 0100
// 添加标志位 a 和 b => a | b
var mask = FLAG_A | FLAG_B => 0101 => 5
// 取出标志位 a => mask & a
mask & FLAG_A => 0001 => 1
mask & FLAG_B => 0100 => 4
// 清除标记位 a => mask & ~a
mask & ~FLAG_A => 0100 => 4
// 取出与 a 的不同部分 => mask ^ a
mask ^ FLAG_A => 0100 => 4
mask ^ FLAG_B => 0001 => 1
FLAG_A ^ FLAG_B => 0101 => 5
сделанный на заказclone
функция
// 木易杨
// 主线代码
if (customizer) {
result = object ? customizer(value, key, object, stack) : customizer(value)
}
if (result !== undefined) {
return result
}
Приведенный выше код относительно понятен, есть настройкаclone
При использовании функции, если есть родительский объект значения value, он передается вvalue、key、object、stack
Эти значения, нет родительского объекта, переданного напрямую вvalue
Выполнить пользовательскую функцию. возвращаемое значение функцииresult
Возвращает результат выполнения, если он не пуст.
Эта часть предназначена для настройкиclone
Метод, предоставляемый функцией.
не объект
// 木易杨
// 主线代码
//判断要拷贝的值是否是对象,非对象直接返回本来的值
if (!isObject(value)) {
return value;
}
// ../isObject.js
function isObject(value) {
const type = typeof value;
return value != null && (type == 'object' || type ='function');
}
Обработка здесь такая же, как моя обработка в [Дополнительно 3-3], разница в том, что добавлено суждение об объекте.function
, для копирования функций см. раздел «Функции» ниже.
Массив и обычный
// 木易杨
// 主线代码
const isArr = Array.isArray(value)
const hasOwnProperty = Object.prototype.hasOwnProperty
if (isArr) {
// 数组
result = initCloneArray(value)
if (!isDeep) {
return copyArray(value, result)
}
} else {
... // 非数组,后面解析
}
// 初始化一个数组
function initCloneArray(array) {
const { length } = array
// 构造相同长度的新数组
const result = new array.constructor(length)
// 正则 `RegExp#exec` 返回的数组
if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
result.index = array.index
result.input = array.input
}
return result
}
// ... 未完待续,最后部分有数组遍历赋值
Когда входящий объект является массивом, создайте массив той же длиныnew array.constructor(length)
, что эквивалентноnew Array(length)
,так какarray.constructor === Array
.
// 木易杨
var a = [];
a.constructor === Array; // true
var a = new Array;
a.constructor === Array // true
если есть обычныйRegExp#exec
Возвращаемый массив, копирование свойствindex
иinput
. Логика оценки: 1. Длина массива больше 0. 2. Первый элемент массива имеет строковый тип 3. Массив существует.index
Атрибуты.
// 木易杨
if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
result.index = array.index
result.input = array.input
}
где регулярное выражениеregexObj.exec(str)
При успешном совпадении возвращается массив и обновляются свойства объекта регулярного выражения. Возвращаемый массив будет использовать текст, который полностью соответствует первому элементу, и заполнит заднюю часть массива успешным совпадением в обычных скобках. Возвращается, когда совпадение не удаетсяnull
.
// 木易杨
var re = /quick\s(brown).+?(jumps)/ig;
var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');
console.log(result);
// [
// 0: "Quick Brown Fox Jumps" // 匹配的全部字符串
// 1: "Brown" // 括号中的分组捕获
// 2: "Jumps"
// groups: undefined
// index: 4 // 匹配到的字符位于原始字符串的基于0的索引值
// input: "The Quick Brown Fox Jumps Over The Lazy Dog" // 原始字符串
// length: 3
// ]
Если не глубокая копия, передайтеvalue
иresult
, который напрямую возвращает массив после поверхностной копии. Метод поверхностного копирования здесь заключается в зацикливании, а затем копировании.
// 木易杨
if (!isDeep) {
return copyArray(value, result)
}
// 浅拷贝数组
function copyArray(source, array) {
let index = -1
const length = source.length
array || (array = new Array(length))
while (++index < length) {
array[index] = source[index]
}
return array
}
Объекты и функции
// 木易杨
// 主线代码
const isArr = Array.isArray(value)
const tag = getTag(value)
if (isArr) {
... // 数组情况,详见上面解析
} else {
// 函数
const isFunc = typeof value == 'function'
// 如果是 Buffer 对象,拷贝并返回
if (isBuffer(value)) {
return cloneBuffer(value, isDeep)
}
// Object 对象、类数组、或者是函数但没有父对象
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
// 拷贝原型链或者 value 是函数时,返回 {},不然初始化对象
result = (isFlat || isFunc) ? {} : initCloneObject(value)
if (!isDeep) {
return isFlat
? copySymbolsIn(value, copyObject(value, keysIn(value), result))
: copySymbols(value, Object.assign(result, value))
}
} else {
// 在 cloneableTags 中,只有 error 和 weakmap 返回 false
// 函数或者 error 或者 weakmap 时,
if (isFunc || !cloneableTags[tag]) {
// 存在父对象返回value,不然返回空对象 {}
return object ? value : {}
}
// 初始化非常规类型
result = initCloneByTag(value, tag, isDeep)
}
}
Из приведенного выше кода видно, что функцияerror
иweakmap
возвращает пустой объект {} и фактически не копирует функцию.
value
типObject
Объект и массив при вызовеinitCloneObject
Инициализируйте объект и, наконец, вызовите Object.create
Создавайте новые объекты.
// 木易杨
function initCloneObject(object) {
// 构造函数并且自己不在自己的原型链上
return (typeof object.constructor == 'function' && !isPrototype(object))
? Object.create(Object.getPrototypeOf(object))
: {}
}
// 本质上实现了一个instanceof,用来测试自己是否在自己的原型链上
function isPrototype(value) {
const Ctor = value && value.constructor
// 寻找对应原型
const proto = (typeof Ctor == 'function' && Ctor.prototype) || Object.prototype
return value === proto
}
вObject
Конструктор является функциональным объектом.
// 木易杨
var obj = new Object();
typeof obj.constructor;
// 'function'
var obj2 = {};
typeof obj2.constructor;
// 'function'
Для объектов нетрадиционного типа они инициализируются соответствующими типами.
// 木易杨
function initCloneByTag(object, tag, isDeep) {
const Ctor = object.constructor
switch (tag) {
case arrayBufferTag:
return cloneArrayBuffer(object)
case boolTag: // 布尔与时间类型
case dateTag:
return new Ctor(+object) // + 转换为数字
case dataViewTag:
return cloneDataView(object, isDeep)
case float32Tag: case float64Tag:
case int8Tag: case int16Tag: case int32Tag:
case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
return cloneTypedArray(object, isDeep)
case mapTag: // Map 类型
return new Ctor
case numberTag: // 数字和字符串类型
case stringTag:
return new Ctor(object)
case regexpTag: // 正则
return cloneRegExp(object)
case setTag: // Set 类型
return new Ctor
case symbolTag: // Symbol 类型
return cloneSymbol(object)
}
}
скопировать обычный тип
// 木易杨
// \w 用于匹配字母,数字或下划线字符,相当于[A-Za-z0-9_]
const reFlags = /\w*$/
function cloneRegExp(regexp) {
// 返回当前匹配的文本
const result = new regexp.constructor(regexp.source, reFlags.exec(regexp))
// 下一次匹配的起始索引
result.lastIndex = regexp.lastIndex
return result
}
инициализацияSymbol
тип
// 木易杨
const symbolValueOf = Symbol.prototype.valueOf
function cloneSymbol(symbol) {
return Object(symbolValueOf.call(symbol))
}
циклическая ссылка
Стек создан для решения проблемы циклических ссылок.
// 木易杨
// 主线代码
stack || (stack = new Stack)
const stacked = stack.get(value)
// 已存在
if (stacked) {
return stacked
}
stack.set(value, result)
Если значение, которое нужно скопировать, уже есть в стеке, значит есть цикл, и вы можете просто вернуть его напрямую. Когда значение не находится в стеке, оно сохраняется в стеке и передается вvalue
иresult
. здесьresult
является ссылкой на объект, следующей заresult
Изменения также отражаются в стеке.
Map & Set
value
значениеMap
набирать, повторятьvalue
и рекурсивно егоsubValue
, обход завершается и возвращаетсяresult
результат.
// 木易杨
// 主线代码
if (tag == mapTag) {
value.forEach((subValue, key) => {
result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
return result
}
value
значениеSet
набирать, повторятьvalue
и рекурсивно егоsubValue
, обход завершается и возвращаетсяresult
результат.
// 木易杨
// 主线代码
if (tag == setTag) {
value.forEach((subValue) => {
result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))
})
return result
}
Разница выше в том, что API для добавления элементов отличается, т.е.Map.set
иSet.add
.
Цепочка символов и прототипов
Здесь мы представляемSymbol
и копия свойств цепочки прототипов через флагиisFull
иisFlat
контролировать, копировать или нет.
// 木易杨
// 主线代码
// 类型化数组对象
if (isTypedArray(value)) {
return result
}
const keysFunc = isFull // 拷贝 Symbol 标志位
? (isFlat // 拷贝原型链属性标志位
? getAllKeysIn // 包含自身和原型链上可枚举属性名以及 Symbol
: getAllKeys) // 仅包含自身可枚举属性名以及 Symbol
: (isFlat
? keysIn // 包含自身和原型链上可枚举属性名的数组
: keys) // 仅包含自身可枚举属性名的数组
const props = isArr ? undefined : keysFunc(value)
arrayEach(props || value, (subValue, key) => {
if (props) {
key = subValue
subValue = value[key]
}
// 递归拷贝(易受调用堆栈限制)
assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
return result
Давайте сначала посмотрим, как получить массив атрибутов, таких как self, цепочка прототипов и Symbol.keys
.
// 木易杨
// 创建一个包含自身和原型链上可枚举属性名以及 Symbol 的数组
// 使用 for...in 遍历
function getAllKeysIn(object) {
const result = keysIn(object)
if (!Array.isArray(object)) {
result.push(...getSymbolsIn(object))
}
return result
}
// 创建一个仅包含自身可枚举属性名以及 Symbol 的数组
// 非 ArrayLike 数组使用 Object.keys
function getAllKeys(object) {
const result = keys(object)
if (!Array.isArray(object)) {
result.push(...getSymbols(object))
}
return result
}
пройти вышеkeysIn
иkeys
Получите обычные перечисляемые свойства черезgetSymbolsIn
иgetSymbols
Получать Symbol
Перечислимые свойства.
// 木易杨
// 创建一个包含自身和原型链上可枚举属性名的数组
// 使用 for...in 遍历
function keysIn(object) {
const result = []
for (const key in object) {
result.push(key)
}
return result
}
// 创建一个仅包含自身可枚举属性名的数组
// 非 ArrayLike 数组使用 Object.keys
function keys(object) {
return isArrayLike(object)
? arrayLikeKeys(object)
: Object.keys(Object(object))
}
// 测试代码
function Foo() {
this.a = 1
this.b = 2
}
Foo.prototype.c = 3
keysIn(new Foo)
// ['a', 'b', 'c'] (迭代顺序无法保证)
keys(new Foo)
// ['a', 'b'] (迭代顺序无法保证)
Обычные свойства пересекают цепочку прототипов, используяfor.. in
,ТакSymbol
Как пройти цепь прототипа, вот велосипедные и использованныеObject.getPrototypeOf
Войдите в цепочку прототиповSymbol
.
// 木易杨
// 创建一个包含自身和原型链上可枚举 Symbol 的数组
// 通过循环和使用 Object.getPrototypeOf 获取原型链上的 Symbol
function getSymbolsIn (object) {
const result = []
while (object) { // 循环
result.push(...getSymbols(object))
object = Object.getPrototypeOf(Object(object))
}
return result
}
// 创建一个仅包含自身可枚举 Symbol 的数组
// 通过 Object.getOwnPropertySymbols 获取 Symbol 属性
const nativeGetSymbols = Object.getOwnPropertySymbols
const propertyIsEnumerable = Object.prototype.propertyIsEnumerable
function getSymbols (object) {
if (object == null) { // 判空
return []
}
object = Object(object)
return nativeGetSymbols(object)
.filter((symbol) => propertyIsEnumerable.call(object, symbol))
}
Возвращаемся к основному коду и получаемkeys
состоит изprops
После массива пройдите и рекурсивно.
// 木易杨
// 主线代码
const props = isArr ? undefined : keysFunc(value)
arrayEach(props || value, (subValue, key) => {
// props 时替换 key 和 subValue,因为 props 里面的 subValue 只是 value 的 key
if (props) {
key = subValue
subValue = value[key]
}
// 递归拷贝(易受调用堆栈限制)
assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
// 返回结果,主线结束
return result
ПосмотримarrayEach
Реализация , в основном реализует обход, а вiteratee
Выход, когда он возвращает false.
// 木易杨
// 迭代数组
// iteratee 是每次迭代调用的函数
function arrayEach(array, iteratee) {
let index = -1
const length = array.length
while (++index < length) {
if (iteratee(array[index], index, array) === false) {
break
}
}
return array
}
ПосмотримassignValue
реализация, которая присваивает значениеobject[key]
.
// 木易杨
const hasOwnProperty = Object.prototype.hasOwnProperty
// 如果现有值不相等,则将 value 分配给 object[key]。
function assignValue(object, key, value) {
const objValue = object[key]
// 不相等
if (! (hasOwnProperty.call(object, key) && eq(objValue, value)) ) {
// 值可用
if (value !== 0 || (1 / value) == (1 / objValue)) {
baseAssignValue(object, key, value)
}
// 值未定义而且键 key 不在对象中
} else if (value === undefined && !(key in object)) {
baseAssignValue(object, key, value)
}
}
// 赋值基本实现,其中没有值检查。
function baseAssignValue(object, key, value) {
if (key == '__proto__') {
Object.defineProperty(object, key, {
'configurable': true,
'enumerable': true,
'value': value,
'writable': true
})
} else {
object[key] = value
}
}
// 比较两个值是否相等
// (value !== value && other !== other) 是为了判断 NaN
function eq(value, other) {
return value === other || (value !== value && other !== other)
}