若是以爲沒有面試題,那麼lodash每個方法就能夠看成一個題目,能夠看着效果反過來實現,以不一樣的方法實現、多種方法實現,鞏固基礎。除了某些一瞬間就能夠實現的函數,下面抽取部分函數做爲試煉。時代在進步,下文全部的解法都採用es2015+前端
本文實現方法都是看效果倒推實現方法,並進行一些拓展和思考,和源碼無關。lodash這個庫在這裏更像一個題庫,給咱們刷題的node
能收穫什麼:es6
注意:面試
仍是老生常談的深淺拷貝,可是咱們此次完全探究一遍各類對象的拷貝以及補回一些js冷門知識編程
lodash除了經常使用的數據類型拷貝外,還會對各類奇怪對象進行拷貝。在實現lodash的以前,咱們先實現一個正常的知足大部分場景的拷貝:api
function shallowClone(v) {
const isArr = Array.isArray(v);
if (typeof v === 'object' && v !== null && !isArr) {
return { ...v } // ...包括symbol key
}
return isArr ? [ ...v ] : v
}
複製代碼
function deepCopy(target, cache = new Set()) {
const isArr = Array.isArray(target);
// 注意環引用
if (
(typeof target !== 'object' && target !== null && !isArr) ||
cache.has(target)
) {
return target
}
if (isArr) {
return target.map(t => {
cache.add(t)
return t
})
} else {
// 注意symbol key
return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => {
cache.add(target[key])
res[key] = deepCopy(target[key], cache)
return res
}, target.constructor !== Object ? Object.create(target.constructor.prototype) : {}) // 繼承
}
}
複製代碼
_.clone(value)
建立一個 value 的淺拷貝。_.cloneDeep(value)
建立一個 value 的深拷貝。baseClone(value, CLONE_SYMBOLS_FLAG)
,cloneDeep是baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG)
,此外,cloneDeepWith
函數也是baseClone
實現,此時baseClone
基於還多了第三個參數customizer
,是一個函數(customizer(value, key)
),對深拷貝前的值作預處理。下面咱們要實現baseClone
方法前置準備:CLONE_DEEP_FLAG, CLONE_SYMBOLS_FLAG這些是標記使用,或運算表示此次操做包含了哪些,傳到函數裏面會進行一次與操做,便可得出要作什麼數組
var CLONE_DEEP_FLAG = 1, // 001
CLONE_FLAT_FLAG = 2, // 010
CLONE_SYMBOLS_FLAG = 4; // 100
// 若是bitmask包含了那位的1,那麼與操做後,那一位就是1,也就是true
var isDeep = bitmask & CLONE_DEEP_FLAG,
isFlat = bitmask & CLONE_FLAT_FLAG,
isFull = bitmask & CLONE_SYMBOLS_FLAG;
複製代碼
爲了使 Buffer 實例的建立更可靠且更不容易出錯,各類形式的 new Buffer() 構造函數都已被棄用,且改成單獨的 Buffer.from(),Buffer.alloc() 和 Buffer.allocUnsafe() 方法。 Buffer的實例方法slice
: buffer.slice([start[, end]])
,返回一個新的 Buffer,它引用與原始的 Buffer 相同的內存,可是由 start 和 end 索引進行偏移和裁剪。安全
克隆buffer的方法:函數式編程
const allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined;
function cloneBuffer(buffer, isDeep) {
if (isDeep) {
return buffer.slice(); // 深拷貝
}
const length = buffer.length;
const result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);
// 爲何這就是淺拷貝呢?
// 其實和const o = { b: 1 }的淺拷貝是const a = new Object(); a.b = o.b一樣的道理
buffer.copy(result);
return result;
}
複製代碼
ArrayBuffer
對象用來表示通用的、固定長度的原始二進制數據緩衝區。ArrayBuffer 不能直接操做,而是要經過類型數組對象或 DataView 對象來操做函數
function cloneArrayBuffer(arrayBuffer) {
// 先new一個同樣長度的
const result = new arrayBuffer.constructor(arrayBuffer.byteLength);
// 使用Uint8Array操做ArrayBuffer,從新set一次
new Uint8Array(result).set(new Uint8Array(arrayBuffer));
// 或者使用DataView
// new DataView(result).setUint8(new Uint8Array(arrayBuffer));
return result;
}
複製代碼
上面提了一下dataview
function cloneDataView(dataView, isDeep) {
// 先把buffer拷貝
const buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
// 再new 的時候,傳入拷貝過的buffer
return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
}
複製代碼
其實dataview一些api和類型化數組(Float32Array, Float64Array, Int8Array, Int16Array, Int32Array, Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array)很像,套路都是同樣的,因此拷貝類型化數組直接改一下函數就ok
function cloneTypedArray(typedArray, isDeep) {
const buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
}
複製代碼
拷貝正則的時候,只須要讀取他的source以及其餘各類模式便可
/\w*$/.exec( /a/gim); // gim
複製代碼
所以克隆正則代碼也很簡單,可是要特別注意lastIndex
在加了g
的匹配模式下的坑:
function cloneRegExp(regexp) {
const result = new regexp.constructor(regexp.source, /\w*$/.exec(regexp));
result.lastIndex = regexp.lastIndex; // 有g的時候的坑
return result;
}
複製代碼
symbol類型的值,經過Symbol(value)
產生,並且Symbol不能new。所以,克隆對象型的Symbol怎麼辦呢(如new Boolean、new Number這種手段產生的對象),其實只須要Object包一下便可,它的valueOf轉換仍是轉換爲正常的symbol
類型的值
function cloneSymbol(symbol) {
return Symbol.prototype.valueOf ? Object(symbol.valueOf()) : {};
}
複製代碼
如new出來的基本數據類型:Number、Boolean、String,也是直接從新new一下便可。Boolean須要注意:!!new Boolean(false) === true
,可是+new Boolean(false) === 0
,因此new時轉數字便可: new Boolean(+boolObj)
Date對象,也是直接new便可。不過爲了安全起見能夠先轉成數字時間戳再new
至於Set、Map,從新new一個空的,而後一個個加進去。須要把遞歸後的結果加進去,由於加進去的元素也多是複雜數據類型哦
初始化通常就定義一個空數組就好了。沒錯,的確是的。可是,還有一種特殊的數組,就是正則match返回的數組,具備index、input屬性:
function initCloneArray(array) {
const length = array.length;
const result = new array.constructor(length);
// 正則match返回
if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
result.index = array.index;
result.input = array.input;
}
return result;
}
複製代碼
接下來就是克隆一個數組了,淺拷貝就直接返回[...array]
,深拷貝數組的方法和普通對象同樣的
建立對象,咱們都知道一個花括號字面量便可:const o = {};
。可是,咱們拷貝的時候,要考慮到繼承的狀況以及是否是原型對象
// 是不是原型對象
function isPrototype(value) {
return value === (value.constructor.prototype || Object.prototype);
}
function initCloneObject(object) {
// 不是原型對象,那就Object.create保持繼承關係
// 是原型對象,那就直接返回一個空對象
return (typeof object.constructor == 'function' && !isPrototype(object))
? Object.create(Object.getPrototypeOf(object))
: {};
}
複製代碼
有了新對象,那麼下一步就是從舊對象裏面一個個key拷貝過來。對於key應該怎麼拿,有幾種case:
lodash裏面,isFlat表示是否拷貝原型鏈,isFull表示是否拷貝symbol key,keysFunc返回一個數組,給後面遍歷使用
const keysFunc = isFull
? (isFlat ? getAllKeysIn : getAllKeys)
: (isFlat ? keysIn : keys);
複製代碼
根據咱們的目的,咱們要把4個獲取key的方法實現出來。可是,在實現以前,先鋪墊一下一些坑:
const o = { b: 1 }
Object.defineProperty(o, 'a', { value: 666, enumerable: false })
for(x in o) { console.log(x) } // 只有b
Object.keys(o) // 只有b
const sb = Symbol() // symbol 作key
Object.defineProperty(o, sb, { value: 666, enumerable: false })
Object.getOwnPropertySymbols(o) // Symbol
for(x in o) { console.log(x) } // 只有b
複製代碼
所以,實現代碼以下:
// 普通獲取key
function keys(object) {
return Object.keys(object)
}
// 普通獲取key,包含symbol key
function getAllKeys(object) {
// getOwnPropertySymbols會把不能枚舉的都拿到
return [
...Object.getOwnPropertySymbols(object).filter(key => object.propertyIsEnumerable(key)),
...Object.keys(object)
]
}
// 獲取原型鏈上的key
function keysIn(object) {
const res = [];
for (const key in object) {
// 拷貝全部的屬性(除了最大的原型對象)
if (key !== 'constructor' || (!isPrototype(object) && object.hasOwnProperty(key))) {
result.push(key);
}
}
return res;
}
function getAllKeysIn(object) {
const res = [];
// in拿不到symbol key
for (const key in object) {
// 拷貝全部的屬性(除了最大的原型對象)
if (key !== 'constructor' || (!isPrototype(object) && object.hasOwnProperty(key))) {
result.push(key);
}
}
let temp = object;
// 逐層獲取symbol key
while(temp) {
res.push(...Object.getOwnPropertySymbols(object).filter(key => object.propertyIsEnumerable(key)))
temp = Object.getPrototypeOf(object)
}
return res;
}
複製代碼
一切準備就緒,就到了賦值階段:
// 僞代碼
function deepCopy(o, ...rest) {
const result = {};
if (isPlainObject) {
keysFunc(o).forEach((key) => {
result[key] = deepCopy(o[key], ...rest)
});
}
}
複製代碼
對於Map、Set都是差很少的,先new一個空的Set、Map,而後遍歷賦值,Set就使用add
方法,Map使用set
方法
/* 常量定義 */
const CLONE_DEEP_FLAG = 1;
const CLONE_FLAT_FLAG = 2;
const CLONE_SYMBOLS_FLAG = 4;
globalThis.Buffer = globalThis.Buffer || undefined;
const argsTag = "[object Arguments]",
arrayTag = "[object Array]",
boolTag = "[object Boolean]",
dateTag = "[object Date]",
errorTag = "[object Error]",
funcTag = "[object Function]",
genTag = "[object GeneratorFunction]",
mapTag = "[object Map]",
numberTag = "[object Number]",
objectTag = "[object Object]",
regexpTag = "[object RegExp]",
setTag = "[object Set]",
stringTag = "[object String]",
symbolTag = "[object Symbol]",
weakMapTag = "[object WeakMap]";
const arrayBufferTag = "[object ArrayBuffer]",
dataViewTag = "[object DataView]",
float32Tag = "[object Float32Array]",
float64Tag = "[object Float64Array]",
int8Tag = "[object Int8Array]",
int16Tag = "[object Int16Array]",
int32Tag = "[object Int32Array]",
uint8Tag = "[object Uint8Array]",
uint8ClampedTag = "[object Uint8ClampedArray]",
uint16Tag = "[object Uint16Array]",
uint32Tag = "[object Uint32Array]";
/* 初始化系列 */
// 初始化數組
function initCloneArray(array) {
const length = array.length;
const result = new array.constructor(length);
// 正則match返回
if (
length &&
typeof array[0] == "string" &&
hasOwnProperty.call(array, "index")
) {
result.index = array.index;
result.input = array.input;
}
return result;
}
// 初始化普通對象
// 是不是原型對象
function isPrototype(value) {
return value === (value.constructor.prototype || Object.prototype);
}
function initCloneObject(object) {
// 不是原型對象,那就Object.create保持繼承關係
// 是原型對象,那就直接返回一個空對象
return typeof object.constructor == "function" && !isPrototype(object)
? Object.create(Object.getPrototypeOf(object))
: {};
}
// 獲取[Object xxx]
function getTag(v) {
return Object.prototype.toString.call(v);
}
// 普通獲取key
function keys(object) {
return Object.keys(object);
}
// 普通獲取key,包含symbol key
function getAllKeys(object) {
// getOwnPropertySymbols會把不能枚舉的都拿到
return [
...Object.getOwnPropertySymbols(object).filter(key =>
object.propertyIsEnumerable(key)
),
...Object.keys(object)
];
}
// 獲取原型鏈上的key
function keysIn(object) {
const res = [];
for (const key in object) {
// 拷貝全部的屬性(除了最大的原型對象)
if (
key !== "constructor" ||
(!isPrototype(object) && object.hasOwnProperty(key))
) {
result.push(key);
}
}
return res;
}
function getAllKeysIn(object) {
const res = [];
// in拿不到symbol key
for (const key in object) {
// 拷貝全部的屬性(除了最大的原型對象)
if (
key !== "constructor" ||
(!isPrototype(object) && object.hasOwnProperty(key))
) {
result.push(key);
}
}
let temp = object;
// 逐層獲取symbol key
while (temp) {
res.push(
...Object.getOwnPropertySymbols(object).filter(key =>
object.propertyIsEnumerable(key)
)
);
temp = Object.getPrototypeOf(object);
}
return res;
}
/* 克隆系列 */
// 克隆Buffer
const allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined;
function cloneBuffer(buffer, isDeep) {
if (isDeep) {
return buffer.slice(); // 深拷貝
}
const length = buffer.length;
const result = allocUnsafe
? allocUnsafe(length)
: new buffer.constructor(length);
// 爲何這就是淺拷貝呢?
// 其實和const o = { b: 1 }的淺拷貝是const a = new Object(); a.b = o.b一樣的道理
buffer.copy(result);
return result;
}
// 克隆ArrayBuffer
function cloneArrayBuffer(arrayBuffer) {
// 先new一個同樣長度的
const result = new arrayBuffer.constructor(arrayBuffer.byteLength);
// 使用Uint8Array操做ArrayBuffer,從新set一次
new Uint8Array(result).set(new Uint8Array(arrayBuffer));
// 或者使用DataView
// new DataView(result).setUint8(new Uint8Array(arrayBuffer));
return result;
}
// 克隆dataview
function cloneDataView(dataView, isDeep) {
// 先把buffer拷貝
const buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
// new的時候,傳入拷貝過的buffer
return new dataView.constructor(
buffer,
dataView.byteOffset,
dataView.byteLength
);
}
// 克隆類型化數組
function cloneTypedArray(typedArray, isDeep) {
const buffer = isDeep
? cloneArrayBuffer(typedArray.buffer)
: typedArray.buffer;
return new typedArray.constructor(
buffer,
typedArray.byteOffset,
typedArray.length
);
}
// 克隆正則對象
function cloneRegExp(regexp) {
const result = new regexp.constructor(regexp.source, /\w*$/.exec(regexp));
result.lastIndex = regexp.lastIndex; // 有g的時候的坑
return result;
}
// 一些對象的初始化或者克隆
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:
return new Ctor();
case numberTag:
case stringTag:
return new Ctor(object);
case regexpTag:
return cloneRegExp(object);
case setTag:
return new Ctor();
case symbolTag:
return Symbol.prototype.valueOf ? Object(object.valueOf()) : {};
}
}
複製代碼
baseClone主邏輯:
function baseClone(value, bitmask, customizer, key, object, cache = new Set()) {
const isDeep = bitmask & CLONE_DEEP_FLAG; // 是否深拷貝
const isFlat = bitmask & CLONE_FLAT_FLAG; // 是否拷貝原型鏈上的屬性
const isFull = bitmask & CLONE_SYMBOLS_FLAG; // 是否拷貝symbol key
const type = typeof value;
const isArr = Array.isArray(value);
let result;
// cloneWith會用到的customizer
if (customizer) {
// object是遞歸帶過來的
result = object ? customizer(value, key, object, cache) : customizer(value);
}
if (result !== undefined) {
return result;
}
// 不是複雜類型直接返回
if (!(value !== null && (type === "object" || type === "function"))) {
return value;
}
if (isArr) {
// 克隆數組
result = initCloneArray(value);
if (!isDeep) {
// 淺拷貝,直接連上去
return result.concat(value);
}
} else {
// 克隆其餘
const tag = getTag(value);
// 是否是函數,按照規範,函數不克隆
const isFunc = tag == funcTag || tag == genTag;
// 克隆Buffer
if (Buffer && Buffer.isBuffer(value)) {
return cloneBuffer(value, isDeep);
}
// 普通對象、argument、函數
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
// 初始化對象
result = isFlat || isFunc ? {} : initCloneObject(value);
// 淺拷貝
if (!isDeep) {
// 是否獲取原型鏈上的symbol key和普通key
const getKeysFunc = isFlat ? getAllKeysIn : getAllKeys;
return getKeysFunc(value).reduce((acc, shallowCopyKey) => {
// 一個個賦值
acc[shallowCopyKey] = value[shallowCopyKey];
return acc;
}, {});
}
} else {
// 不能拷貝的對象就放棄
if (!cloneableTags[tag]) {
return object ? value : {};
}
// arrayBuffer、typedarray、dataView、regexp、Object{[基本數據類型]}的拷貝
// set、map在這裏只是初始化一個空的
result = initCloneByTag(value, tag, isDeep);
}
}
// 檢查環引用
const cacheed = cache.has(value);
if (cacheed) {
return cacheed;
}
cache.add(value, result);
// set和map,一個個來
if (isSet(value)) {
value.forEach(subValue => {
result.add(
baseClone(subValue, bitmask, customizer, subValue, value, cache)
);
});
} else if (isMap(value)) {
value.forEach((subValue, key) => {
result.set(
key,
baseClone(subValue, bitmask, customizer, key, value, cache)
);
});
}
// 獲取key的函數
const keysFunc = isFull
? isFlat
? getAllKeysIn
: getAllKeys
: isFlat
? keysIn
: keys;
// 對象的屬性,只有普通對象有props
const props = isArr ? undefined : keysFunc(value);
(props || value).forEach((subValue, key) => {
let newKey = key; // 數組的index或者對象的key
let newValue = subValue; // subValue原本是所拷貝的對象裏面的key或者數組的一個元素值
// 是對象的時候
if (props) {
newKey = newValue; // 若是是對象,新的key便是forEach第一個參數
newValue = value[newKey]; // 所拷貝的對象裏面的key對應的value
}
// 賦值,遞歸還把當前的一些值帶下去(value、cache、newKey)
result[newKey] = baseClone(
newValue,
bitmask,
customizer,
newKey,
value,
cache
);
});
return result;
}
複製代碼
關注公衆號《不同的前端》,以不同的視角學習前端,快速成長,一塊兒把玩最新的技術、探索各類黑科技