内功修炼之lodash

您所在的位置:网站首页 复制是干嘛的 内功修炼之lodash

内功修炼之lodash

2024-07-15 19:47:35| 来源: 网络整理| 查看: 265

如果觉得没有面试题,那么lodash每一个方法就可以当作一个题目,可以看着效果反过来实现,以不同的方法实现、多种方法实现,巩固基础。除了某些一瞬间就可以实现的函数,下面抽取部分函数作为试炼。时代在进步,下文所有的解法都采用es2015+

本文实现方法都是看效果倒推实现方法,并进行一些拓展和思考,和源码无关。lodash这个库在这里更像一个题库,给我们刷题的

能收获什么:

修炼代码基本功,了解常见的套路了解到一些操作的英文命名和规范积累经验,面对复杂逻辑问题可以迅速解决也许可以查到自己的js基础知识的漏洞

注意:

三星难度以上的会具体拓展和讲解文中使用的基本都是数组原生api以及es6+函数式编程,代码简洁且过程清晰如果说性能当然是命令式好,实现起来稍微麻烦一些而且比较枯燥无味时代在进步,人生苦短,我选择语法糖和api。面临大数据的性能瓶颈,才是考虑命令式编程的时候

还是老生常谈的深浅拷贝,但是我们这次彻底探究一遍各种对象的拷贝以及补回一些js冷门知识

clone & cloneDeep(不考虑不常用对象)

lodash除了常用的数据类型拷贝外,还会对各种奇怪对象进行拷贝。在实现lodash的之前,我们先实现一个正常的满足大部分场景的拷贝:

浅拷贝难度系数: ★建议最长用时:2min代码语言:javascript复制function shallowClone(v) { if (typeof v === 'object' && v !== null) { return { ...v } // ...包括symbol key } return Array.isArray(v) ? [ ...v ] : v } 复制代码深拷贝难度系数: ★★★建议最长用时:9min代码语言:javascript复制function deepCopy(target, cache = new Set()) { // 注意环引用 if ( (typeof target !== 'object' && target !== null) || cache.has(target) ) { return target } if (Array.isArray(target)) { 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 & cloneDeep(考虑各种对象)_.clone(value)创建一个 value 的浅拷贝。_.cloneDeep(value)创建一个 value 的深拷贝。注意: 这个方法参考自 structured clone algorithm 以及支持 arrays、array buffers、 booleans、 date objects、maps、 numbers, Object objects, regexes, sets, strings, symbols, 以及 typed arrays。 参数对象的可枚举属性会拷贝为普通对象。 一些不可拷贝的对象,例如error objects、functions, DOM nodes, 以及 WeakMaps 会返回空对象。源码中,clone是baseClone(value, CLONE_SYMBOLS_FLAG),cloneDeep是baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG),此外,cloneDeepWith函数也是baseClone实现,此时baseClone基于还多了第三个参数customizer,是一个函数(customizer(value, key)),对深拷贝前的值做预处理。下面我们要实现baseClone方法难度系数: ★★★★★★★★建议最长用时:25min

前置准备:CLONE_DEEP_FLAG, CLONE_SYMBOLS_FLAG这些是标记使用,或运算表示这次操作包含了哪些,传到函数里面会进行一次与操作,即可得出要做什么

代码语言:javascript复制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

为了使 Buffer 实例的创建更可靠且更不容易出错,各种形式的 new Buffer() 构造函数都已被弃用,且改为单独的 Buffer.from(),Buffer.alloc() 和 Buffer.allocUnsafe() 方法。 Buffer的实例方法slice: buffer.slice([start[, end]]),返回一个新的 Buffer,它引用与原始的 Buffer 相同的内存,但是由 start 和 end 索引进行偏移和裁剪。

克隆buffer的方法:

代码语言:javascript复制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 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 不能直接操作,而是要通过类型数组对象或 DataView 对象来操作

代码语言:javascript复制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

上面提了一下dataview

代码语言:javascript复制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

代码语言:javascript复制function cloneTypedArray(typedArray, isDeep) { const buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); } 复制代码RegExp

拷贝正则的时候,只需要读取他的source以及其他各种模式即可

代码语言:javascript复制/\w*$/.exec( /a/gim); // gim 复制代码

因此克隆正则代码也很简单,但是要特别注意lastIndex在加了g的匹配模式下的坑:

imageimage代码语言:javascript复制function cloneRegExp(regexp) { const result = new regexp.constructor(regexp.source, /\w*$/.exec(regexp)); result.lastIndex = regexp.lastIndex; // 有g的时候的坑 return result; } 复制代码Symbol

symbol类型的值,通过Symbol(value)产生,而且Symbol不能new。因此,克隆对象型的Symbol怎么办呢(如new Boolean、new Number这种手段产生的对象),其实只需要Object包一下即可,它的valueOf转换还是转换为正常的symbol类型的值

代码语言:javascript复制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属性:

代码语言:javascript复制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],深拷贝数组的方法和普通对象一样的

普通对象克隆初始化对象initCloneObject

创建对象,我们都知道一个花括号字面量即可:const o = {};。但是,我们拷贝的时候,要考虑到继承的情况以及是不是原型对象

代码语言:javascript复制// 是否是原型对象 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拷贝过来。对于key应该怎么拿,有几种case:

拷贝symbol key和拷贝原型链不拷贝symbol key和拷贝原型链不拷贝symbol key和不拷贝原型链拷贝symbol key和不拷贝原型链

lodash里面,isFlat表示是否拷贝原型链,isFull表示是否拷贝symbol key,keysFunc返回一个数组,给后面遍历使用

代码语言:javascript复制 const keysFunc = isFull ? (isFlat ? getAllKeysIn : getAllKeys) : (isFlat ? keysIn : keys); 复制代码

根据我们的目的,我们要把4个获取key的方法实现出来。但是,在实现之前,先铺垫一下一些坑:

代码语言:javascript复制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 复制代码

因此,实现代码如下:

代码语言:javascript复制// 普通获取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; } 复制代码递归赋值

一切准备就绪,就到了赋值阶段:

代码语言:javascript复制// 伪代码 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方法

全部代码

铺垫代码

代码语言:javascript复制/* 常量定义 */ 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主逻辑:

代码语言:javascript复制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; } 复制代码


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭