关于lodash:lodash常用方法及应用场景

lodashLodash是一个风行的JavaScript实用工具库,提供了对JavaScript常见工作的高效和模块化的实现,使开发者能够更容易地编写高质量、可保护和可读性强的代码。Lodash库提供了大量的函数,如数组解决、字符串操作、对象解决、函数式编程、日期解决等性能,能够大大提高开发效率。Lodash库的特点包含:高效的性能:Lodash的实现是通过优化的,能够无效地进步JavaScript应用程序的性能。模块化的架构:Lodash的函数被组织成了模块,能够依据须要抉择加载,而不是一次性加载整个库。简洁易懂的API:Lodash提供了简洁易懂的API,能够轻松地实现常见的工作,缩小了开发者的代码量。反对链式调用:Lodash的函数反对链式调用,能够不便地进行函数组合和操作。 1 _.groupBy(): groupBy()办法能够将一个汇合中的元素依照指定的属性分组,并返回一个对象。它承受两个参数:要遍历的汇合和一个属性名。console.log( _.groupBy([{ name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }, { name: 'Charlie', age: 25 }], 'age'), 'groupBy-groupBy-groupBy-groupBy-groupBy')2 _.debounce(): 防抖 debounce()办法能够限度一个函数在短时间内屡次调用,就是在规定工夫间断输出时只取最初一次的值(只有输出时进展的工夫不超过规定值那么就不会执行)并返回一个新的函数。它承受两个参数:要限度的函数和延迟时间(以毫秒为单位)。const debounceFunc = _.debounce((num) => console.log(num), 1000);// debounceFunc('0')// setTimeout(() => {debounceFunc('300')}, 300)// setTimeout(() => {debounceFunc('500')}, 500)// setTimeout(() => {debounceFunc('1000')}, 1000)// setTimeout(() => {debounceFunc('1500')}, 1500)// setTimeout(() => {debounceFunc('2600')}, 2600)3 _.throttle():节流 throttle()办法能够限度一个函数在一段时间内只能调用一次,以规定数值为一个工夫节点,当小于以后工夫时取最初触发的值, 并返回一个新的函数。它承受两个参数:要限度的函数和工夫距离(以毫秒为单位)。 const throttleFunc = _.throttle((num) => console.log(num), 1000);// throttleFunc(0); // 第一次调用// setTimeout(() => { throttleFunc(500) }, 500);// setTimeout(() => { throttleFunc(600) }, 600);// setTimeout(() => { throttleFunc(1000) }, 1000);// setTimeout(() => { throttleFunc(1500) }, 1500);// setTimeout(() => { throttleFunc(1600) }, 1600);4 _.cloneDeep(): cloneDeep()办法能够深度复制一个对象或数组,返回一个新的对象或数组。const cloneDeepObj = { a: { b: { c: 1 } } };const newObj = _.cloneDeep(cloneDeepObj);newObj.a.b.c = 2;console.log(cloneDeepObj.a.b.c, newObj.a.b.c, 'cloneDeep-cloneDeep-cloneDeep-cloneDeep');5 _.pick(): pick()办法能够从一个对象中抉择指定的属性,并返回一个新的对象。const pickObj = { a: 1, b: 2, c: 3 };const newPickObj = _.pick(pickObj, ['a', 'c']);console.log(newPickObj, 'pick-pick-pick-pick');6 _.omit(): omit()办法能够从一个对象中排除指定的属性,并返回一个新的对象const omitObj = { a: 1, b: 2, c: 3 };const newOmitObj = _.omit(omitObj, ['b']);console.log(newOmitObj, 'omit-omit-omit-omit');7 _.flattenDeep(): flatten()办法能够将一个嵌套的数组开展成一个一维数组const flattenArr = [1, [2, [3, [4]], 5]];const newFlattenArr = _.flattenDeep(flattenArr);console.log(newFlattenArr, 'flattenDeep-flattenDeep-flattenDeep-flattenDeep');8 _.initial():去除数组array中的最初一个元素const initialArr = [{ a: 1 }, { a: 2 }]console.log(_.initial(initialArr), 'initial-initial-initial-initial')9 _.isEqual():执行深比拟来确定两者的值是否相等。常常须要比拟两个表单数据是否相等,以便判断用户是否批改了数据var object = { 'a': 1, b: 1 };var other = { 'a': 1, 'b': 1 };console.log(_.isEqual(object, other), 'isEqual-isEqual-isEqual-isEqual')10 _.camelCase():转换字符串为驼峰个别前端命名驼峰,而后端接口返回的 JSON 数据中可能采纳下划线格局。为了不便操作这些数据,须要将属性名转换为驼峰格局。console.log(_.camelCase('foo_bar'), 'camelCase-camelCase-camelCase-camelCase')11 _.endsWith():查看字符串string是否以给定的target字符串结尾。验证文件类型:在上传文件的时候,经常须要验证文件的类型是否符合要求,如果文件名以特定的字符串结尾,那么就能够判断文件类型是否正确。解决URL:在应用URL进行页面跳转或者发送申请时,有时须要查看URL是否以某个固定的字符串结尾,以此来确定须要跳转到哪个页面或者发送到哪个服务器。console.log(_.endsWith('abc/v', 'bc/v'), 'endsWith-endsWith-endsWith')12 _.escape():本义string中的 "&", "<", ">", '"', "'", 和 "`" 字符为HTML实体字符。当用户在表单中输出文本时,咱们须要查看文本是否蕴含 HTML 实体,如果蕴含则须要进行本义,以避免歹意脚本的注入。当咱们将数据渲染到页面上时,须要将其中蕴含的 HTML 实体本义,以防止将实体当成 HTML 标签来解析。console.log(_.escape('<script src="./loadsh"></script>'), 'escape-escape-escape-escape')13 _.zipObject():它承受2个数组,第一个数组中的值作为属性名,第二个数组中的值作为相应的属性值。当后端为了防止少建字段时,前端给table赋值时又须要属性名,那么能够前端定义属性名应用这种形式组成对象不便取值[ { name:'xxxx', sum:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] }, { name:'yyyy', sum:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] },]const frontArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'G', 'K'];const backendArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];console.log(_.zipObject(frontArr, backendArr), 'zipObject-zipObject-zipObject-zipObject')14 _.truncate():截断string字符串,如果字符串超出了限定的最大值。 被截断的字符串前面会以 omission 代替,omission 默认是 "..."。当咱们须要在 UI 中显示一些过长的字符串时,通常须要将其截断以适应显示区域const truncateStr = _.truncate('hi-diddly-ho there, neighborino', { 'length': 24,//截断长度 // 'separator': ' ',//依据哪个字符截断, // 'omission': ' [...]'//截断之后显示内容});console.log(truncateStr, 'truncateStr-truncateStr-truncateStr')15 _.merge():_.merge 和 _.assign 都是用于合并对象的函数,但它们的合并策略不同。_.merge 将源对象的属性递归地合并到指标对象上。如果指标对象中有雷同的属性名,则应用源对象的属性值笼罩指标对象的属性值。_.assign 将所有源对象的属性简略地合并到指标对象上。如果源对象和指标对象有雷同的属性名,则用源对象的属性值笼罩指标对象的属性值。如果有多个源对象有雷同的属性,则前面的源对象的属性值会笼罩后面的源对象和指标对象的属性值。因而,相比于 _.assign,_.merge 更适宜解决简单的对象嵌套状况,可能递归地合并对象。而 _.assign 更适宜简略的对象合并,具备覆盖性,前面的对象的属性会笼罩后面的对象的属性。var object = { 'a': [{ 'b': 2 }, { 'd': 4 }, { 'f': 6 }]};var other = { 'a': [{ 'c': 3 }, { 'e': 5 }, { f: 7 }]};console.log(_.merge(object, other))console.log(_.assign(object, other))

March 5, 2023 · 2 min · jiezi

关于lodash:使用Lodash工具后代码行数瞬间缩短

背景:最近在做报表.波及到echarts图表.多层柱状图叠加展现.而后后端给进去的构造是二维数组.须要前端自行处理成图表可用的数据格式.echarts数据是是动静的.需要效果图的样子: echarts类似的官网案例代码:option = { tooltip: { trigger: 'axis', }, legend: { data: ['Direct', 'Mail Ad', 'Affiliate Ad', 'Video Ad', 'Search Engine'] }, xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: [ { splitLine: { show: false, //去掉网格线 }, axisTick: { show: true, length: 4, lineStyle: { color: '#D8D8D8', }, }, axisLabel: { show: true, color: '#606C7B', }, axisLine: { show: true, lineStyle: { color: '#D8D8D8', }, }, name: '次数', type: 'value', }, ], series: [ { name: 'A', type: 'bar', stack: 'total', emphasis: { focus: 'series' }, data: [320, 302, 301, 334, 390, 330, 320] }, { name: 'B', type: 'bar', stack: 'total', emphasis: { focus: 'series' }, data: [120, 132, 101, 134, 90, 230, 210] }, { name: 'C', type: 'bar', stack: 'total', emphasis: { focus: 'series' }, data: [220, 182, 191, 234, 290, 330, 310] }, ]};官网的代码实现示例图 : ...

May 14, 2021 · 4 min · jiezi

关于lodash:lodash源码之fill方法

性能从start开始应用value填充数组到end为止。 _.fill([4, 6, 8, 10], '*', 1, 3); // => [4, '*', '*', 10]fill源码function fill(array, value, start, end) { var length = array == null ? 0 : array.length; if (!length) { return []; } if (start && typeof start != 'number' && isIterateeCall(array, value, start)) { start = 0; end = length; } return baseFill(array, value, start, end);}array参数如果不存在,就间接返回[]start参数存在,不是number类型的话,(这里还有个isIterateeCall判断,后边再对立剖析该办法。)设置start为0,end为数组的长度,就是填充整个数组通过对参数的解决之后,再去调用baseFill这个外围逻辑baseFillfunction baseFill(array, value, start, end) { var length = array.length; start = toInteger(start); if (start < 0) { start = -start > length ? 0 : (length + start); } end = (end === undefined || end > length) ? length : toInteger(end); if (end < 0) { end += length; } end = start > end ? 0 : toLength(end); while (start < end) { array[start++] = value; } return array;} if (start < 0) { start = -start > length ? 0 : (length + start); }对于入参start来讲,如果传的是一个正数,如果是-5,实际上数组长度是4,那么这么start显然是不符合条件的。 所以这里才以-start > end来做判断条件 ...

November 26, 2020 · 1 min · jiezi

学习-lodash-源码整体架构打造属于自己的函数式编程类库

前言这是学习源码整体架构系列第三篇。整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是主线的具体函数的实现。文章学习的是打包整合后的代码,不是实际仓库中的拆分的代码。 上上篇文章写了jQuery源码整体架构,学习 underscore 源码整体架构,打造属于自己的函数式编程类库 上一篇文章写了underscore源码整体架构,学习 jQuery 源码整体架构,打造属于自己的 js 类库 感兴趣的读者可以点击阅读。 underscore源码分析的文章比较多,而lodash源码分析的文章比较少。原因之一可能是由于lodash源码行数太多。注释加起来一万多行。 分析lodash整体代码结构的文章比较少,笔者利用谷歌、必应、github等搜索都没有找到,可能是找的方式不对。于是打算自己写一篇。平常开发大多数人都会使用lodash,而且都或多或少知道,lodash比underscore性能好,性能好的主要原因是使用了惰性求值这一特性。 本文章学习的lodash的版本是:v4.17.15。unpkg.com地址 https://unpkg.com/lodash@4.17... 文章篇幅可能比较长,可以先收藏再看,所以笔者使用了展开收缩的形式。 导读: 文章主要学习了runInContext() 导出_ lodash函数使用baseCreate方法原型继承LodashWrapper和LazyWrapper,mixin挂载方法到lodash.prototype、后文用结合例子解释lodash.prototype.value(wrapperValue)和Lazy.prototype.value(lazyValue)惰性求值的源码具体实现。匿名函数执行;(function() {}.call(this));暴露 lodash var _ = runInContext();runInContext 函数这里的简版源码,只关注函数入口和返回值。 var runInContext = (function runInContext(context) { // 浏览器中处理context为window // ... function lodash(value) {}{ // ... return new LodashWrapper(value); } // ... return lodash;});可以看到申明了一个runInContext函数。里面有一个lodash函数,最后处理返回这个lodash函数。 再看lodash函数中的返回值 new LodashWrapper(value)。 LodashWrapper 函数function LodashWrapper(value, chainAll) { this.__wrapped__ = value; this.__actions__ = []; this.__chain__ = !!chainAll; this.__index__ = 0; this.__values__ = undefined;}设置了这些属性: ...

September 10, 2019 · 10 min · jiezi

lodash数组篇之3-concat

创建一个新数组,将array与任何数组 或 值连接在一起 自行实现 concat:function(){ let length = arguments.length let result = [] if(!length) { return result } for(let i = 0;i<argu.length;i++) { result = result.concat(argu[i]) } return result }lodash 实现 function concat() { var length = arguments.length; if (!length) { return []; } var args = Array(length - 1), array = arguments[0], index = length; while (index--) { args[index - 1] = arguments[index]; } return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));}由于参数的数量不确定 所以首先想到函数免费赠送的一个参数 arguments。这点和官方的想法是一样的 。arguments是一个包含了所有参数的类数组对象,比如说第一个参数可以通过arguments[0]获取。但是arguments参数有一个很诡异的地方,无法在箭头函数内使用,只能在普通的function内使用。可能是和 this 一样的原因,由于箭头函数的原因,指向了父级作用域。 ...

July 11, 2019 · 1 min · jiezi

lodash-compact方法的实现

创建一个新数组,包含原数组中所有的非假值元素。例如false, null, 0, "", undefined, 和 NaN 都是被认为是“假值”。 以下是自己实现的compact() compact:(array)=>{ let result = [] //判断参数是否是数组 如果不是数组 直接返回一个空数组 //也可以用es6提供的Array.isArray(array)来判断 更简洁 if(!Object.prototype.toString.call(array)==='[object Array]') { return result } array.forEach(element => { if(element) { result.push(element) } }); return result }lodash的实现 compact:(array)=>{ var index = -1, length = array == null ? 0 : array.length, resIndex = 0, result = []; while (++index < length) { var value = array[index]; if (value) { result[resIndex++] = value; } } return result; }这样写的好处是 传入一个字符串会返回一个数组 好像也没其他作用,为什么不直接判断是否是数组呢? ...

July 10, 2019 · 1 min · jiezi

lodash源码分析之去重uniq方法

lodash.js包是node开发中常用的js工具包,里面有许多实用的方法,今天分析常用的一个去重方法---uniq用法 _.uniq([2, 1, 2]) // => [2, 1]源码包 // uniq.js import baseUniq from './.internal/baseUniq.js' function uniq(array) { return (array != null && array.length) ? baseUniq(array) : [] } export default uniq可以看到,uniq函数这边只做了一个针对baseUniq的封装,所以继续看baseUniq源码???? // baseUniq.js import SetCache from './SetCache.js' import arrayIncludes from './arrayIncludes.js' import arrayIncludesWith from './arrayIncludesWith.js' import cacheHas from './cacheHas.js' import createSet from './createSet.js' import setToArray from './setToArray.js' const LARGE_ARRAY_SIZE = 200 // 作为数组处理的最大数组长度 function baseUniq(array, iteratee, comparator) { let index = -1 let includes = arrayIncludes // 向下兼容,内部使用使用while做循环 let isCommon = true const { length } = array const result = [] let seen = result if (comparator) { // 如果有comparator,标注为非普通函数处理 isCommon = false includes = arrayIncludesWith // includes 判重方法更换为 arrayIncludesWith } else if (length >= LARGE_ARRAY_SIZE) { // 长度超过200后启用,大数组优化策略 // 判断是否有迭代器,没有则设为Set类型(支持Set类型的环境直接调用生成Set实例去重) const set = iteratee ? null : createSet(array) if (set) { return setToArray(set) //Set类型转数组(Set类型中不存在重复元素,相当于去重了)直接返回 } isCommon = false // 非普通模式 includes = cacheHas // includes 判重方法更换为hash判断 seen = new SetCache // 实例化hash缓存容器 } else { // 存在迭代器的情况下,新开辟内存空间为缓存容器,否则直接指向结果数组容器 seen = iteratee ? [] : result } outer: while (++index < length) { // 循环遍历每一个元素 let value = array[index] // 取出当前遍历值 // 存在迭代器函数执行迭代器函数后返回结果,否则直接返回自身 const computed = iteratee ? iteratee(value) : value value = (comparator || value !== 0) ? value : 0 if (isCommon && computed === computed) { // 普通模式执行下面代码 let seenIndex = seen.length // 取当前容器的长度为下一个元素的角标 while (seenIndex--) { // 循环遍历每一个容器中每一个元素 if (seen[seenIndex] === computed) { // 匹配到重复的元素 continue outer // 直接跳出当前循环直接进入下一轮outer: } } if (iteratee) { // 有迭代器的情况下 seen.push(computed) // 结果推入缓存容器 } result.push(value) // 追加入结果数组 } // 非正常数组处理模式下,调用includes方法,判断缓存容器中是否存在重复的值 else if (!includes(seen, computed, comparator)) { if (seen !== result) { // 非普通模式下,result和seen内存空间地址不一样 seen.push(computed) } result.push(value) // 追加入结果数组 } } return result // 循环完成,返回去重后的数组 } export default baseUniq大致的流程: ...

June 30, 2019 · 2 min · jiezi

说说JavaScript中函数的防抖-Debounce-与节流-Throttle

为何要防抖和节流有时候会在项目开发中频繁地触发一些事件,如 resize、 scroll、 keyup、 keydown等,或者诸如输入框的实时搜索功能,我们知道如果事件处理函数无限制调用,会大大加重浏览器的工作量,有可能导致页面卡顿影响体验;后台接口的频繁调用,不仅会影响客户端体验,还会大大增加服务器的负担。而如果对这些调用函数增加一个限制,让其减少调用频率,岂不美哉? 针对这个问题,一般有两个方案:防抖 (Debounce) 节流 (Throttle) 防抖(Debounce)我对函数防抖的定义:当函数被连续调用时,该函数并不执行,只有当其全部停止调用超过一定时间后才执行1次。 一个被经常提起的例子: 上电梯的时候,大家陆陆续续进来,电梯的门不会关上,只有当一段时间都没有人上来,电梯才会关门。Talk is cheap,我们直接 show code 吧。 先做基本的准备(篇幅原因,HTML部分省略): let container = document.getElementById('container');// 事件处理函数function handle(e) { onsole.log(Math.random()); }// 添加滚动事件container.addEventListener('scroll', handle);我们发现,每滚动一下,控制台就会打印出一行随机数。 基础防抖我们现在写一个最基础的防抖处理: function debounce(func, wait) { var timeout;//标记 return function() { clearTimeout(timeout); timeout = setTimeout(func, wait); }}事件也做如下改写: container.addEventListener('scroll', debounce(handle, 1000));现在试一下, 我们会发现只有我们停止滚动1秒钟的时候,控制台才会打印出一行随机数。 标准防抖以上基础版本会有两个问题,请看如下代码: // 处理函数function handle(e) { console.log(this); //输出Window对象 console.log(e); //undefined}没错,当我们不使用防抖处理时,handle()函数的this指向调用此函数的container,而在外层使用防抖处理后,this的指向会变成Window。其次,我们也要获取到事件对象event。 所以我们要对防抖函数做以下改写: function debounce(fn, wait) { let timeout; return function() { clearTimeout(timeout); timeout = setTimeout(()=>{ fn.apply(this,arguments)//使用apply改变this指向 }, wait); }}当然了,如果使用箭头函数便可以省去外层声明。 ...

June 17, 2019 · 1 min · jiezi

lodash之cloneDeep浅析

浅拷贝和深拷贝本质上的原因是对象引用的是地址,直接赋值会吧引用地址也复制给新值。浅复制只会将对象的各个属性进行依次复制,会把引用地址也复制。深拷贝是会递归源数据,吧新值得引用地址给换掉。 lodash的cloneDeep入口 <!-- 这两个flog是因为baseClone会被很多方法给调用,这两个适用于区分一些操作 --><!-- 1是是否深度复制,4是是否复制 Symbols类型-->const CLONE_DEEP_FLAG = 1const CLONE_SYMBOLS_FLAG = 4function cloneDeep(value) { return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG)}核心逻辑 function baseClone(value, bitmask, customizer, key, object, stack) { let result const isDeep = bitmask & CLONE_DEEP_FLAG const isFlat = bitmask & CLONE_FLAT_FLAG const isFull = bitmask & CLONE_SYMBOLS_FLAG 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) // 获取constructor const tag = getTag(value) if (isArr) { // 初始化一个长度和源相等的数组 result = initCloneArray(value) // 不是deep就直接复制了事 if (!isDeep) { return copyArray(value, result) } } else { const isFunc = typeof value == 'function' // Buffer.isBuffer, 对于buffer对象就直接复制了事 if (isBuffer(value)) { return cloneBuffer(value, isDeep) } if (tag == objectTag || tag == argsTag || (isFunc && !object)) { // 是否需要继承proto result = (isFlat || isFunc) ? {} : initCloneObject(value) if (!isDeep) { // 这里deepclone的isFlat是0,走copySymbols,这个方法主要是复制源上的Symbols return isFlat ? copySymbolsIn(value, copyObject(value, keysIn(value), result)) : copySymbols(value, Object.assign(result, value)) } } else { // 如果是func或error, WeakMap就直接返回了 if (isFunc || !cloneableTags[tag]) { return object ? value : {} } // 对tag位true的类型进行clone result = initCloneByTag(value, tag, isDeep) } } // Check for circular references and return its corresponding clone. // 检查循环引用并返回其相应的克隆。 stack || (stack = new Stack) const stacked = stack.get(value) if (stacked) { return stacked } stack.set(value, result) // 对map递归clone 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 } 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] } // Recursively populate clone (susceptible to call stack limits). assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack)) }) return result}数组这里主要是会有个exec的特殊数组 ...

May 9, 2019 · 2 min · jiezi

读懂源码系列3lodash-是如何实现深拷贝的上

前言上一篇文章 「前端面试题系列9」浅拷贝与深拷贝的含义、区别及实现 中提到了深拷贝的实现方法,从递归调用,到 JSON,再到终极方案 cloneForce。 不经让我想到,lodash 中的 _.cloneDeep 方法。它是如何实现深拷贝的呢?今天,就让我们来具体地解读一下 _.cloneDeep 的源码实现。 源码中的内容比较多,为了能将知识点讲明白,也为了更好的阅读体验,将会分为上下 2 篇进行解读。今天主要会涉及位掩码、对象判断、数组和正则的深拷贝写法。 ok,现在就让我们深入源码,共同探索吧~ _.cloneDeep 的源码实现它的源码内容很少,因为主要还是靠 baseClone 去实现。 /** Used to compose bitmasks for cloning. */const CLONE_DEEP_FLAG = 1const CLONE_SYMBOLS_FLAG = 4function cloneDeep(value) { return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG)}刚看到前两行的常量就懵了,它们的用意是什么?然后,传入 baseClone 的第二个参数,似乎还将那两个常量做了运算,其结果是什么?这么做的目的是什么? 一番查找之后,终于明白这里其实涉及到了 位掩码 与 位运算 的概念。下面就来详细讲解一下。 位掩码技术回到第一行注释:Used to compose bitmasks for cloning。意思是,用于构成克隆方法的位掩码。 从注释看,这里的 CLONE_DEEP_FLAG 和 CLONE_SYMBOLS_FLAG 就是位掩码了,而 CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG 其实是 位运算 中的 按位或 方法。 ...

May 8, 2019 · 4 min · jiezi

Lodash源码阅读

lodash github : https://github.com/lodash/lodash阅读的版本: 4.17.11.internalHash类 (Hash缓存)const HASH_UNDEFINED = ‘__lodash_hash_undefined’class Hash{ // Hash 只接收一个二维数组作为参数 // new Hash([[’tes1’,1],[’test2’,2],[’test3’,3]]) // => // { // size: 3, // data: { // test1: 1, // test2: 2, // test3: 3 // } // } constructor(entires) { let index = -1 const length = entries == null ? 0 : entries.length // 初始化 data 属性和 size this.clear() // 遍历传入的二维数组,调用 set 方法,初始化缓存的值 while(++index < length) { const entry = entries[index] this.set(entry[0], entry[1]) } } // 初始化 data 属性和 size // clear 的作用是清空缓存 clear() { // Object.create 的第一个参数为创建对象的原型对象, // 传入 null 的时候,返回的就是一个真空对象,即没有原型的对象,因此不会有原型属性的干扰,用来做缓存对象十分适合。 this.data = Object.create(null) this.size = 0 } //delete 方法用来删除指定 key 的缓存 // 成功删除返回 true, 否则返回 false // 删除操作同样需要维护 size 属性和缓存值。 delete (key) { //调用 has 方法来判断缓存是否存在,如果存在,用 delete 操作符将 data 中对应的属性删除。 const result = this.has(key) && delete this.data // delete 操作符在成功删除属性时会返回 true,如果成功删除,则需要将 size 减少 1 。 this.size -= result ? 1 : 0 return result } // get 方法是从缓存中取值 get(key) { const data = this.data const result = data[key] return result === HASH_UNDEFINED ? undefined : result } // has 用来判断是否已经有缓存数据,如果缓存数据已经存在,则返回 true // 这个判断有一个坑 has(key) { const data = this.data return data[key] !== undefined } // set 用来增加或者更新需要缓存的值 // set 的时候需要同时维护 size 和在缓存的值。 set(key, value) { const data = this.data // 判断对应的 key 是否已经被缓存过,如果已经缓存过,则 size 保持不变,否则 size 加 1 this.size += this.has(key)? 0 : 1 data[key] = value === undefined ? HASH_UNDEFINED : value return this // 在 has 中说到用 data[key] !== undefined 有一个坑,因为要缓存的值也可以是 undefined ,如果不做处理,肯定会导致判断错误。 // lodash 的处理方式是将 undefined 的值转换成 HASH_UNDEFINED ,也即一开始便定义的 lodash_hash_undefined 字符串来储存。 // 所以在缓存中,是用字符串 lodash_hash_undefined 来替代 undefined 的。 }}export default Hash参考pocket-lodashObject.create() ...

April 8, 2019 · 2 min · jiezi

节流 - 理解,实践与实现

节流(分流),与防抖(去抖)实现原理相似。本文主要讨论节流,镜像文章:防抖 - 理解,实践与实现。分开讨论防抖和节流,主要是为了让一些还不太了解节流防抖的读者能够有针对性地,逐一掌握它们。 如何用代码实现节流也是一个要点。本文采用循序渐进地方式,先绘制一个案例的流程图,再根据流程图的逻辑编写节流功能代码。文章包含多个可交互案例,可通过博客原文实时查看案例。欢迎Star和订阅我的原创前端技术博客。节流案例点击运行案例当鼠标移动时,mousemove事件频繁被触发。上方为未节流模式,每一次mousemove触发都会绘制一个圆点。而下方为节流模式,尽管mosuemove在鼠标移动时被多次触发,但只有在限定时间间隔才会绘制圆点。理解和实现节流通过上方案例,可以基本了解节流的作用: 频繁触发的事件,事件处理函数在一定的时间间隔内只执行一次。不过节流函数是如何做到的? 以上方案例为例,绘制其流程图如下。 核心参数:间隔时长计时器根据流程图的思路实现分流函数:function throttle( func, wait ) { let timer function throttled( …args ) { const self = this if ( timer == null ) { invokeFunc() addTimer() } function addTimer() { timer = setTimeout( () => { clearTimer() }, wait ) } function invokeFunc() { func.apply( self, args ) } } return throttled function clearTimer() { clearTimeout( timer ) timer = null }}接下来,用编写的节流函数实现上方案例点击运行案例应用场景无限的滚动条点击运行案例总结节流和防抖类似,都能有效优化系统性能,不过使用业务场景有所区别:防抖既可用于在多次触发的事件(如文本框逐个输入文字),也可用于在频繁触发的事件(如调整窗口尺寸)。节流多只用在频繁触发的事件(如滚动滚动条)上。感谢你花时间阅读这篇文章。如果你喜欢这篇文章,欢迎点赞、收藏和分享,让更多的人看到这篇文章,这也是对我最大的鼓励和支持! 欢迎Star和订阅我的原创前端技术博客。

April 2, 2019 · 1 min · jiezi

「读懂源码系列2」我从 lodash 源码中学到的几个知识点

前言上一篇文章 「前端面试题系列8」数组去重(10 种浓缩版) 的最后,简单介绍了 lodash 中的数组去重方法 .uniq,它可以实现我们日常工作中的去重需求,能够去重 NaN,并保留 {…}。今天要讲的,是我从 .uniq 的源码实现文件 baseUniq.js 中学到的几个很基础,却又容易被忽略的知识点。三个 API让我们先从三个功能相近的 API 讲起,他们分别是:.uniq、.uniqBy、_.uniqWith。它们三个背后的实现文件,都指向了 .internal 下的 baseUniq.js。区别在于 _.uniq 只需传入一个源数组 array, _.uniqBy 相较于 _.uniq 要多传一个迭代器 iteratee,而 _.uniqWith 要多传一个比较器 comparator。iteratee 和 comparator 的用法,会在后面说到。以 .uniqWith 为例,它是这样调用 .baseUniq 的:function uniqWith(array, comparator) { comparator = typeof comparator == ‘function’ ? comparator : undefined return (array != null && array.length) ? baseUniq(array, undefined, comparator) : []}baseUniq 的实现原理baseUniq 的源码并不多,但比较绕。先贴一下的源码。const LARGE_ARRAY_SIZE = 200function baseUniq(array, iteratee, comparator) { let index = -1 let includes = arrayIncludes let isCommon = true const { length } = array const result = [] let seen = result if (comparator) { isCommon = false includes = arrayIncludesWith } else if (length >= LARGE_ARRAY_SIZE) { const set = iteratee ? null : createSet(array) if (set) { return setToArray(set) } isCommon = false includes = cacheHas seen = new SetCache } else { seen = iteratee ? [] : result } outer: while (++index < length) { let value = array[index] const computed = iteratee ? iteratee(value) : value value = (comparator || value !== 0) ? value : 0 if (isCommon && computed === computed) { let seenIndex = seen.length while (seenIndex–) { if (seen[seenIndex] === computed) { continue outer } } if (iteratee) { seen.push(computed) } result.push(value) } else if (!includes(seen, computed, comparator)) { if (seen !== result) { seen.push(computed) } result.push(value) } } return result}为了兼容刚才说的三个 API,就产生了不少的干扰项。如果先从 .uniq 入手,去掉 iteratee 和 comparator 的干扰,就会清晰不少。function baseUniq(array) { let index = -1 const { length } = array const result = [] if (length >= 200) { const set = createSet(array) return setToArray(set) } outer: while (++index < length) { const value = array[index] if (value === value) { let resultIndex = result.length while (resultIndex–) { if (result[resultIndex] === value) { continue outer } } result.push(value) } else if (!includes(seen, value)) { result.push(value) } } return result}这里有 2 个知识点。知识点一、NaN === NaN 吗?在源码中有一个判断 value === value,乍一看,会觉得这是句废话!?!但其实,这是为了过滤 NaN 的情况。MDN 中对 NaN 的解释是:它是一个全局对象的属性,初始值就是 NaN。它通常都是在计算失败时,作为 Math 的某个方法的返回值出现的。判断一个值是否是 NaN,必须使用 Number.isNaN() 或 isNaN(),在执行自比较之中:NaN,也只有 NaN,比较之中不等于它自己。NaN === NaN; // falseNumber.NaN === NaN; // falseisNaN(NaN); // trueisNaN(Number.NaN); // true所以,在源码中,当遇到 NaN 的情况时,baseUniq 会转而去执行 !includes(seen, value) 的判断,去处理 NaN 。知识点二、冒号的特殊作用在源码的主体部分,while 语句之前,有一行 outer:,它是干什么用的呢? while 中还有一个 while 的内部,有一行 continue outer,从语义上理解,好像是继续执行 outer,这又是种什么写法呢?outer:while (++index < length) { … while (resultIndex–) { if (result[resultIndex] === value) { continue outer } }}我们都知道 Javascript 中,常用到冒号的地方有三处,分别是:A ? B : C 三元操作符、switch case 语句中、对象的键值对组成。但其实还有一种并不常见的特殊作用:标签语句。在 Javascript 中,任何语句都可以通过在它前面加上标志符和冒号来标记(identifier: statement),这样就可以在任何地方使用该标记,最常用于循环语句中。所以,在源码中,outer 只是看着有点不习惯,多看两遍就好了,语义上还是很好理解的。.uniqBy 的 iteratee.uniqBy 可根据指定的 key 给一个对象数组去重,一个官网的例子如下:// The _.property iteratee shorthand..uniqBy([{ ‘x’: 1 }, { ‘x’: 2 }, { ‘x’: 1 }], ‘x’);// => [{ ‘x’: 1 }, { ‘x’: 2 }]这里的 ‘x’ 是 .property(‘x’) 的缩写,它指的就是 iteratee。从给出的例子和语义上看,还挺好理解的。但是为什么 .property 就能实现对象数组的去重了呢?它又是如何实现的呢?@param {Array|string} path The path of the property to get.@returns {Function} Returns the new accessor function.function property(path) { return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path)}从注释看,property 方法会返回一个 Function,再看 baseProperty 的实现:@param {string} key The key of the property to get.@returns {Function} Returns the new accessor function.function baseProperty(key) { return (object) => object == null ? undefined : object[key]}咦?怎么返回的还是个 Function ?感觉它什么也没干呀,那个参数 object 又是哪里来的?知识点三、纯函数的概念纯函数,是函数式编程中的概念,它代表这样一类函数:对于指定输出,返回指定的结果。不存在副作用。// 这是一个简单的纯函数const addByOne = x => x + 1;也就是说,纯函数的返回值只依赖其参数,函数体内不能存在任何副作用。如果是同样的参数,则一定能得到一致的返回结果。function baseProperty(key) { return (object) => object == null ? undefined : object[key]}baseProperty 返回的就是一个纯函数,在符合条件的情况下,输出 object[key]。在函数式编程中,函数是“一等公民”,它可以只是根据参数,做简单的组合操作,再作为别的函数的返回值。所以,在源码中,object 是调用 baseProperty 时传入的对象。 baseProperty 的作用,是返回期望结果为 object[key] 的函数。.uniqWith 的 comparator还是先从官网的小例子说起,它会完全地给对象中所有的键值对,进行比较。var objects = [{ ‘x’: 1, ‘y’: 2 }, { ‘x’: 2, ‘y’: 1 }, { ‘x’: 1, ‘y’: 2 }];.uniqWith(objects, .isEqual);// => [{ ‘x’: 1, ‘y’: 2 }, { ‘x’: 2, ‘y’: 1 }]而在 baseUniq 的源码中,可以看到最终的实现,需要依赖 arrayIncludesWith 方法,以下是它的源码:function arrayIncludesWith(array, target, comparator) { if (array == null) { return false } for (const value of array) { if (comparator(target, value)) { return true } } return false}arrayIncludesWith 没什么复杂的。comparator 作为一个参数传入,将 target 和 array 的每个 value 进行处理。从官网的例子看,.isEqual 就是 comparator,就是要比较它们是否相等。接着就追溯到了 _.isEqual 的源码,它的实现文件是 baseIsEqualDeep.js。在里面看到一个让我犯迷糊的写法,这是一个判断。/** Used to check objects for own properties. */const hasOwnProperty = Object.prototype.hasOwnProperty…const objIsWrapped = objIsObj && hasOwnProperty.call(object, ‘wrapped’)hasOwnProperty ?call, ‘wrapped’ ?知识点四、对象的 hasOwnProperty再次查找到了 MDN 的解释:所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。它可以用来检测一个对象是否含有特定的自身属性;会忽略掉那些从原型链上继承到的属性。o = new Object();o.prop = ’exists’;o.hasOwnProperty(‘prop’); // 返回 trueo.hasOwnProperty(’toString’); // 返回 falseo.hasOwnProperty(‘hasOwnProperty’); // 返回 falsecall 的用法可以参考这篇 细说 call、apply 以及 bind 的区别和用法。那么 hasOwnProperty.call(object, ‘wrapped’) 的意思就是,判断 object 这个对象上是否存在 ‘wrapped’ 这个自身属性。wrapped 是什么属性?这就要说到 lodash 的延迟计算方法 _.chain,它是一种函数式风格,从名字就可以看出,它实现的是一种链式的写法。比如下面这个例子:var names = _.chain(users) .map(function(user){ return user.user; }) .join(" , “) .value();如果你没有显样的调用value方法,使其立即执行的话,将会得到如下的LodashWrapper延迟表达式:LodashWrapper {wrapped: LazyWrapper, actions: Array[1], chain: true, constructor: function, after: function…}因为延迟表达式的存在,因此我们可以多次增加方法链,但这并不会被执行,所以不会存在性能的问题,最后直到我们需要使用的时候,使用 value() 显式立即执行即可。所以,在 baseIsEqualDeep 源码中,才需要做 hasOwnProperty 的判断,然后在需要的情况下,执行 object.value()。总结阅读源码,在一开始会比较困难,因为会遇到一些看不明白的写法。就像一开始我卡在了 value === value 的写法,不明白它的用意。一旦知道了是为了过滤 NaN 用的,那后面就会通畅很多了。所以,阅读源码,是一种很棒的重温基础知识的方式。遇到看不明白的点,不要放过,多查多问多看,才能不断地夯实基础,读懂更多的源码思想,体会更多的原生精髓。如果我在一开始看到 value === value 时就放弃了,那或许就不会有今天的这篇文章了。PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

March 18, 2019 · 4 min · jiezi

函数节流(throttle)与函数去抖(debounce)

前言做过前端的童鞋应该都知道lodash这个强大的使用工具库。为什么要写这篇文章呢,主要今天遇到一个问题,socket推送消息太频繁,导致saga频繁更新,页面有所卡顿,需要通过函数节流控制,发现自己突然不会写这样的代码,而且模糊了节流和抖动的区别。简单实现一下,源码其实复杂的多。区别节流:一些场景频繁触发,导致页面崩溃,资源加载重复等行为,需要控制执行频率,这个时候就叫做节流。去抖:主要针对的是频繁触发某个事件后,然后进行后续处理的场景。常见的就是频繁输入停止3s(假设)后进行查询等操作。.debounce函数接口定义:@param fn实际需要调用的函数@param second 空闲时间@return callback 返回调用函数const debounce = (fn, second) => { let timer = null return () => { clearTimeout(timer) timer = setTimeout(() =>{ fn() }, second) }}.throttle函数接口定义:@param delay延迟时间@param fn需要调用的函数@return cb返回函数const throttle = (fn, delay) => { let last = 0 return () => { let current = new Date() if(current-last > delay) { fn() last = current } }}

March 6, 2019 · 1 min · jiezi

webpack最小化lodash

lodash作为一个比较常用的前端开发工具集,在使用webpack进行vendor分离的实践中,会遇到将整个lodash文件分离到vendor.js的问题。这样会使vendor.js文件变得特别大。webpack.config.js文件代码如下:var path = require(‘path’);module.exports = mode => { return { entry: { A: ‘./moduleA.js’, B: ‘./moduleB.js’, C: ‘./moduleC.js’, }, mode: mode, output: { path: path.resolve(__dirname, ‘dist/’), filename: ‘[name].js’ }, optimization: { usedExports: true, splitChunks: { cacheGroups: { commons: { chunks: ‘all’, minChunks: 2, maxInitialRequests: 5, minSize: 0 }, vendor: { test: /node_modules/, chunks: “initial”, name: “vendor”, priority: 10, enforce: true } } } }, module: { }, plugins: [ ] }}运行npm run test脚本命令,结果如下:Hash: 5d86af7ed04c57cca071Version: webpack 4.28.4Time: 5707msBuilt at: 2019-01-11 19:25:04 Asset Size Chunks Chunk Names A.js 1.46 KiB 3 [emitted] A B.js 1.53 KiB 4 [emitted] B C.js 1.54 KiB 5 [emitted] CcommonsABC.js 132 bytes 0 [emitted] commonsABC commonsAC.js 238 bytes 1 [emitted] commonsAC vendor.js 69.7 KiB 2 [emitted] vendorEntrypoint A = vendor.js commonsABC.js commonsAC.js A.jsEntrypoint B = commonsABC.js B.jsEntrypoint C = vendor.js commonsABC.js commonsAC.js C.js如上面的情况,vendor.js文件为69.7kb,如果再引用了其他第三方库,文件会更大。那么,如何在开发的过程中,压缩lodash呢?babel-loader & babel-plugin-lodash可以使用babel-loader在对*.js文件进行解析,然后借助于babel-plugin-lodash插件对引用的lodash进行类似tree shaking的操作,这样就可以去除未使用的lodash代码片段。安装所需依赖:yarn add babel-loader @babel/core @babel/preset-env babel-plugin-lodash –dev像下面这样修改webpack.config.js配置文件:…module: { rules: [ { test: /.js$/, exclude: /node_modules/, use: { loader: ‘babel-loader’, options: { presets: [’@babel/preset-env’], plugins: [’lodash’] } } } ]}…运行npm run test,脚本命令结果如下:Hash: 30def5521978552cc93dVersion: webpack 4.28.4Time: 3249msBuilt at: 2019-01-11 21:25:23 Asset Size Chunks Chunk Names A.js 1.46 KiB 3 [emitted] A B.js 1.53 KiB 4 [emitted] B C.js 1.54 KiB 5 [emitted] CcommonsABC.js 132 bytes 0 [emitted] commonsABC commonsAC.js 226 bytes 1 [emitted] commonsAC vendor.js 502 bytes 2 [emitted] vendorEntrypoint A = vendor.js commonsABC.js commonsAC.js A.jsEntrypoint B = commonsABC.js B.jsEntrypoint C = vendor.js commonsABC.js commonsAC.js C.jsvendor.js文件从69.7kb降至502bytes。根据babel-plugin-lodash参考文档介绍,使用lodash-webpack-plugin可以进一步压缩lodash。lodash-webpack-plugin安装lodash-webpack-plugin依赖:yarn add lodash-webpack-plugin –dev修改webpack.config.js配置文件如下:var LodashModuleReplacementPlugin = require(’lodash-webpack-plugin’);…plugins: [ new LodashModuleReplacementPlugin,]…运行npm run test脚本命令,结果如下所示:Hash: 30def5521978552cc93dVersion: webpack 4.28.4Time: 2481msBuilt at: 2019-01-11 21:07:23 Asset Size Chunks Chunk Names A.js 1.46 KiB 3 [emitted] A B.js 1.53 KiB 4 [emitted] B C.js 1.54 KiB 5 [emitted] CcommonsABC.js 132 bytes 0 [emitted] commonsABC commonsAC.js 226 bytes 1 [emitted] commonsAC vendor.js 502 bytes 2 [emitted] vendorEntrypoint A = vendor.js commonsABC.js commonsAC.js A.jsEntrypoint B = commonsABC.js B.jsEntrypoint C = vendor.js commonsABC.js commonsA~C.js C.jsvendor.js依然是502 bytes,问题不在loadsh-webpack-plugin,它虽然会进一步优化lodash,但是在无法进一步优化的情况下,它也没办法。一般情况下,不使用lodash-webpack-plugin就可以满足开发的需要,但是文件特别大的情况下,建议还是使用它。参考源代码 ...

January 11, 2019 · 2 min · jiezi

超火js库: Lodash API例子 (持续更新~~~)

lodash.js是一款超火的js库,在npm上平均周下载量达到了惊人的12,374,096,github start36K!大量框架都用到了lodash,包括拥有123kstart的vue本文对比lodash英文文档,加上一些小栗子和个人的经验~~,希望能帮到你们lodash采用了immutable的思想,永远不会改变原数据,而是会返回一个新的结果String 字符串操作camelCase 转换驼峰命名_.camelCase([string=’’])console.log(.camelCase(‘Foo Bar’))// => ‘fooBar’console.log(.camelCase(’–foo-bar–’))// => ‘fooBar’console.log(.camelCase(’FOO_BAR’))// => ‘fooBar’console.log(.camelCase(’/_FOO_BAR_*\9’))// ‘fooBar9’console.log(.camelCase(‘fooBarbar_bar’))// fooBarbarBar字符串中非数字和字母都会被过滤掉,然后再转换为驼峰capitalize 转换大写.capitalize([string=’’])console.log(.capitalize(‘FRED’));// => ‘Fred’联想: 同string.prototype.toLocaleUpperCase();deburr 清理符号.capitalize([string=’’])deburr转换 Latin-1 Supplement和Latin Extended-A 为普通拉丁字母并且移除变音符号_.deburr(‘déjà vu’);// => ‘deja vu’一般用不到…endsWith 判断是否是某个字符串结尾_.endsWith([string=’’], [target], [position=string.length])console.log(.endsWith(‘abcdef3’, ‘c’, 3))// trueconsole.log(.endsWith(‘abcdef3’, ‘c’, 2))// false主要是第三个参数,不填表示检查整个字符串,有值代表从左截取几个字符,从截取的字符中进行判断ECMAScript 6中已经加入string.prototype.endsWith()方法escape 转义html实体字符_.escape([string=’’])会将&装换成&amp, < -> &lt, > -> &gt ’’ -> &quot。其他转义字符,如:×(乘号),÷(除号)等不会转义,请用he这样的专业处理转义的库console.log(.escape(a as &lt;a&gt; &amp;'"" *))// a as &lt;a&gt; &amp;&#39;&quot;&quot; *escapeRegExp 转义正则表达式特殊字符.escapeRegExp([string=’’])正则表达式中的特殊字符都会加’‘处理console.log(.escapeRegExp(’lodash’))// [lodash]()kebabCase 转换成kebabCase格式总结: 存在四种case格式CamelCase: TheQuickBrownFoxJumpsOverTheLazyDogSnakeCase: the_quick_brown_fox_jumps_over_the_lazy_dogKebabCase: the-quick-brown-fox-jumps-over-the-lazy-dogStudlycaps: tHeqUicKBrOWnFoXJUmpsoVeRThElAzydOG查看case的具体文档其他转换case语法通camelCaselowerCase 转换小写.lowerCase([string=’’]).lowerCase(’–Foo-Bar–’);// => ‘foo bar’ .lowerCase(‘fooBar’);// => ‘foo bar’ .lowerCase(’FOO_BAR’);// => ‘foo bar’通capitalize联想: string.prototype.toLocaleLowerCaselowerFirst 转换第一个字符为小写console.log(.lowerFirst(‘DS’))// dSconsole.log(.lowerFirst(’__DS’))// _DS无法过滤非字母字符pad 填充字符.pad([string=’’], [length=0], [chars=’ ‘])有三个参数: 原字符串,长度,填充字符如果原字符串长度短于给定的长度,则原字符串左右两边会填充指定字符(默认为空格),如果不能平均分配则会被截断。.pad(‘abc’, 8);// => ’ abc ’ .pad(‘abc’, 8, ‘-’);// => ‘-abc-_’ _.pad(‘abc’, 3);// => ‘abc’ ...

December 27, 2018 · 1 min · jiezi

Lodash学习小记

项目里经常用到Lodash,来做一个小小的归纳总结吧!那么,什么是Lodash呢先要明白的是lodash的所有函数都不会在原有的数据上进行操作,而是复制出一个新的数据而不改变原有数据接下来就是Lodash的引用,简单粗暴常用的Lodash方法1、.forEach遍历.forEach(agent,function(n,key) { agent[key].agent_id= agent[key].agent_name})返回新的数组agent2、.compact过滤假值 去除假(将所有的空值,0,NaN过滤掉).compact([‘1’,‘2’,’ ‘,0]//=>[‘1’, ‘2’]3、.uniq 数组去重 用法同上(将数组中的对象去重,只能是数组去重,不能是对象去重。).uniq([1,1,3])// => [1,3]4、.filter和.reject 过滤集合,传入匿名函数。(二者放在一起讨论的原因是,两个函数类似但返回的值是相反。)这两个过滤器,第二个参数值是false的时候返回是reject的功能,相反是true的时候是filter_.filter([1,2],x => x = 1)// => [1] .reject([1,2],x => x=1)// => [2]5、.map和_.forEach,数组遍历。(相似)这两个方法ES6完美支持,所以一般就直接用原生的啦

December 26, 2018 · 1 min · jiezi

Lodash常用用法总结

Lodash是一个轻量级的JavaScript工具函数库,它方便了日常开发中对数据的操作,提高了开发效率。 日常开发中,通常会对数据,特别是数组和对象进行各种读写等操作:比如去重,拷贝,合并,过滤,求交集,求和等等。根据平时开发中对数据的操作,我对Lodash常见的用法做了以下总结,方便今后的学习和整理。ArrayCreate创建一个数组,元素为0, 1, 2, … , 23_.range([start=0], end, [step=1])let arr = .range(24)console.log(arr) // [0, 1, 2, 3, … , 23]创建一个数组,元素为100, 100, 100, 100, 100.fill(array, value, [start=0], [end=array.length])let arr = .fill(Array(5), 100)console.log(arr) // [100, 100, 100, 100, 100]Read获取数组中最后一个元素.last(array)let arr = [1, 2, 3, 4, 5]let lastElement = .last(arr) console.log(lastElement) // 5获取数组中倒数第二个元素.nth(array, [n=0])let arr = [1, 2, 3, 4, 5]let lastSecondElement = .nth(-2) console.log(lastSecondElement) // 4获取对象数组中某一同名属性的属性值集合.map(collection, [iteratee=.identity])let users = [{ id: 12, name: ‘Adam’, hobbies: [ {name: ‘running’, index: 100}, {name: ‘cycling’, index: 95} ] },{ id: 14, name: ‘Bob’, hobbies: [ {name: ‘movie’, index: 98}, {name: ‘music’, index: 85} ] },{ id: 16, name: ‘Charlie’, hobbies: [ {name: ’travelling’, index: 90}, {name: ‘fishing’, index: 88} ] },{ id: 18, name: ‘David’, hobbies: [ {name: ‘walking’, index: 99}, {name: ‘football’, index: 85} ] }]let userIds = .map(users, ‘id’)let mostFavouriteHobbies = .map(users, ‘hobbies[0].name’)console.log(userIds) // [12, 14, 16, 18]console.log(mostFavouriteHobbies) // [“running”, “movie”, “travelling”, “walking”]获取对象数组中某一属性值最大的对象.maxBy(array, [iteratee=.identity])let arr = [{a:1, b: 2, c: {d:4}}, {a:3, b: 4, c: {d:6}}]let maxBObj = .maxBy(arr, ‘b’)console.log(maxBObj) // {a: 3, b: 4, c: {d: 6}}找出两个数组中元素值相同的元素.intersection([arrays])let arr1 = [2, 1, {a: 1, b: 2}]let arr2 = [2, 3, {a: 1, b: 2}]let intersection = .intersection(arr1, arr2) console.log(intersection) // [2]求数值数组中元素值的平均数.mean(array)let numbers = [1, 2, 3, 4, 5]let average = .mean(numbers)console.log(average) // 3求对象数组中某个属性值的平均数.meanBy(array, [iteratee=.identity])let objects = [{ ’n’: 4 }, { ’n’: 2 }, { ’n’: 8 }, { ’n’: 6 }]let average = .meanBy(objects, ’n’)console.log(average) // 5获取数组中前n个元素,不改变原数组.take(array, [n=1])let arr = [1, 2, 3, 4, 5]let part1Arr = .take(arr, 4)let part2Arr = .take(arr, 6)let part3Arr = .take([], 5)console.log(part1Arr) // [1, 2, 3, 4]console.log(part2Arr) // [1, 2, 3, 4, 5]console.log(part3Arr) // []Delete删除数组中值为falsy的元素.compact(array)let arr = [0, 1, false, 2, ‘’, 3, null, undefined, NaN]let truthyArr = .compact(arr) console.log(truthyArr) // [1, 2, 3]Format去重。.uniq(array)let arr = [2, 1, 2, ‘2’, true]let uniqArr = .uniq(arr)console.log(uniqArr) // [2, 1, ‘2’, true]排序。对象数组,根据对象中的某个属性的值,升序或降序排序.orderBy(collection, [iteratees=[.identity]], [orders])let users = [ {user: ‘Tom’, age: 25}, {user: ‘Amy’, age: 23}, {user: ‘Perter’, age: 22}, {user: ‘Ben’, age: 29}]let sortedUsers = .orderBy(users, ‘age’, ‘desc’)console.log(sortedUsers)// [{user: “Ben”, age: 29}, {user: “Tom”, age: 25}, {user: “Amy”, age: 23}, {user: “Perter”, age: 22}]分割数组[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]为 [1, 2, 3, 4, 5] 和 [6, 7, 8, 9, 10].chunk(array, [size=1])let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]let [left, right] = .chunk(arr, 5)console.log(left) // [1, 2, 3, 4, 5]console.log(right) // [6, 7, 8, 9, 10]脱掉一层[].flatten(array)let address = { ‘江苏省’: [‘南京市’, ‘苏州市’], ‘浙江省’: [‘杭州市’, ‘绍兴市’]}let cities = .flatten(.values(address))console.log(cities) // [“南京市”, “苏州市”, “杭州市”, “绍兴市”]将多维数组转为一维数组.flattenDeep(array)let flattenedArr = .flattenDeep([1, [2, [3, [4]], 5]]);console.log(flattenedArr) // [1, 2, 3, 4, 5]ObjectCreate通过数组[“x”, “y”] 和 数组[10, 10] 创建对象 {x: 10, y: 10}.zipObject([props=[]], [values=[]])let keys = [“x”, “y”]let values = [10, 10]let obj = .zipObject(keys, values) console.log(obj) // {x: 10, y: 10}合并对象.assign(object, [sources])let desObj = {name: ‘’, gender: ‘male’, job: ‘developer’}let sourceObj = {name: ‘Tom’, job: ‘’}let mergedObj = .assign(desObj, sourceObj)console.log(mergedObj) // {name: “Tom”, gender: “male”, job: “"}深拷贝对象.cloneDeep(value)let sourceObj = {department_id: 1, permissions: {management: [1, 2, 3, 4], store: [11, 12, 13, 14]}}let desObj = .cloneDeep(sourceObj)desObj.permissions.store.push(15, 16)console.log(desObj)// {department_id: 1, permissions: {management: [1, 2, 3, 4], store: [11, 12, 13, 14, 15, 16]}}console.log(sourceObj)// {department_id: 1, permissions: {management: [1, 2, 3, 4], store: [11, 12, 13, 14]}}合并多个对象中key值相同的键值对.merge(object, [sources])let obj1 = {‘9’: {name: ‘乐购超市’}}let obj2 = {‘9’: {storeToken: ‘xxx’}}let obj3 = {‘9’: {storePosition: ‘Hangzhou’}}let mergedObj = .merge(obj1, obj2, obj3) console.log(mergedObj)// 9: {name: “乐购超市”, storeToken: “xxx”, storePosition: “Hangzhou”}Read判断对象中是否有某个属性.has(object, path)let obj = {a: [{b: {c: 3}}]}let hasC = .has(obj, ‘a[0].b.c’)console.log(hasC) // true获取对象中的某个属性的值.get(object, path, [defaultValue])let obj = {a: [{b: {c: 3}}]}let c = .get(obj, ‘a[0].b.c’)console.log(c) // 3Update设置对象中的某个属性的值.set(object, path, value)let obj = {a: [{b: {c: 3}}]}let newObj = .set(obj, ‘a[0].b.c’, 4);console.log(obj.a[0].b.c); // 4对多个对象相同属性的属性值求和。let customers = { new_customer: {0: 33, 1: 5, … , 23: 0}, old_customer: {0: 22, 1: 7, … , 24: 0}}let customer = {}let keys = .keys(customers.new_customer)let values = .values(customers).map(keys, key => { customer[key] = .sumBy(values, key)})customers.customer = customerconsole.log(customers)// console{ customer: {0: 55, 1: 12, … , 23: 0} new_customer: {0: 33, 1: 5, … , 23: 0} old_customer: {0: 22, 1: 7, … , 23: 0}}Number生成一个随机数,范围n~m.random([lower=0], [upper=1], [floating])let random1 = .random(2, 5)let random2 = .random(5)console.log(random1) // 2, 3, 4, 5console.log(random2) // 0, 1, 2, 3, 4, 5Data Type判断数据类型.isNumber(value).isInteger(value)….isPlainObject(value)let variable = ‘hello’;// Numberconsole.log(.isNumber(variable));// Integerconsole.log(.isInteger(variable));// Booleanconsole.log(.isBoolean(variable));// Stringconsole.log(.isString(variable));// Nullconsole.log(.isNull(variable));// Undefinedconsole.log(.isUndefined(variable));// Arrayconsole.log(.isArray(variable));// Functionconsole.log(.isFunction(variable));// Objectconsole.log(.isPlainObject(variable));// Dateconsole.log(.isDate(variable));// DOM elementconsole.log(.isElement(variable));数据类型转换_.toArray_.toArray(‘abc’) // [“a”, “b”, “c”].toInteger.toInteger(3.2); // 3_.toInteger(‘3.2’); // 3_.toNumber_.toNumber(‘3.2’) // 3.2_.toString_.toString(1); // “1”.toString([1, 2, 3]); // “1,2,3"Util重复多次某个元素.times(n, [iteratee=_.identity])const dateParams = _.times(2, () => ‘2018-08-27’);console.log(dateParams) // [“2018-08-27”, “2018-08-27”] ...

December 3, 2018 · 4 min · jiezi

lodash源码分析之数据类型获取的兼容性

焦虑和恐惧的区别是,恐惧是对世界上的存在的恐惧,而焦虑是在"我"面前的焦虑。——萨特《存在与虚无》本文为读 lodash 源码的第十九篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodashgitbook也会同步仓库的更新,gitbook地址:pocket-lodash前言在前文《lodash源码分析之获取数据类型》已经解释了获取数据类型的方法,但是在有些环境下,一些 es6 新增的对象获取到的类型都为 [object Object] ,这样就没办法做细致的区分。例如在 IE11 中,通过 Object.prototype.toString 获取到的 DataView 对象类型为 [object Object]。 因此在 getTag 中,lodash 针对这些对象做了一些兼容性的事情。依赖import baseGetTag from ‘./baseGetTag.js’《lodash源码分析之获取数据类型》源码分析const dataViewTag = ‘[object DataView]‘const mapTag = ‘[object Map]‘const objectTag = ‘[object Object]‘const promiseTag = ‘[object Promise]‘const setTag = ‘[object Set]‘const weakMapTag = ‘[object WeakMap]’/** Used to detect maps, sets, and weakmaps. */const dataViewCtorString = ${DataView}const mapCtorString = ${Map}const promiseCtorString = ${Promise}const setCtorString = ${Set}const weakMapCtorString = ${WeakMap}let getTag = baseGetTag// Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || (getTag(new Map) != mapTag) || (getTag(Promise.resolve()) != promiseTag) || (getTag(new Set) != setTag) || (getTag(new WeakMap) != weakMapTag)) { getTag = (value) => { const result = baseGetTag(value) const Ctor = result == objectTag ? value.constructor : undefined const ctorString = Ctor ? ${Ctor} : ’’ if (ctorString) { switch (ctorString) { case dataViewCtorString: return dataViewTag case mapCtorString: return mapTag case promiseCtorString: return promiseTag case setCtorString: return setTag case weakMapCtorString: return weakMapTag } } return result }}getTag 的源码很简单,处理的是 DataView、Map、Set、Promise、WeakMap 等对象,下面就关键的几点说明一下。函数的toString方法const dataViewCtorString = ${DataView}const mapCtorString = ${Map}const promiseCtorString = ${Promise}const setCtorString = ${Set}const weakMapCtorString = ${WeakMap}我们都知道,DataView 这些其实都是构造函数,函数有 toString 的方法,调用后返回的是 function DataView() { [native code] } 这样的格式,因为其实例调用 Object.prototype.toString 在某些环境下返回的是 [object Object],而构造函数的 toString 返回的字符串中,包含了构造函数名,可以通过这点来区分。实例中构造函数的获取 const Ctor = result == objectTag ? value.constructor : undefined const ctorString = Ctor ? ${Ctor} : ‘‘每个实例中都包含一个 constructor 的属性,这个属性指向的是实例的构造函数,在获取到这个构造函数后,就可以调用它的 toString 方法,然后就可以比较了。Promise.resolvegetTag(Promise.resolve()) != promiseTag在条件判断时,使用了 Promise.resolve() ,这样使用的目的是获取到 promise 对象,因为 Promise 是一个函数函数,如果直接调用 Object.prototype.toString,返回的是 [object Function]。License署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见: 作者:对角另一面 ...

September 24, 2018 · 2 min · jiezi