邵阳建网站多少钱,东莞企业网站建立报价,广州网站建设 app 小程序,北京网站改版有哪些好处前言 上一篇文章 「前端面试题系列8」数组去重(10 种浓缩版) 的最后#xff0c;简单介绍了 lodash 中的数组去重方法 _.uniq#xff0c;它可以实现我们日常工作中的去重需求#xff0c;能够去重 NaN#xff0c;并保留 {...}。 今天要讲的#xff0c;是我从 _.uniq 的源码实…前言 上一篇文章 「前端面试题系列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 : undefinedreturn (array ! null array.length)? baseUniq(array, undefined, comparator): []
}
复制代码baseUniq 的实现原理 baseUniq 的源码并不多但比较绕。先贴一下的源码。 const LARGE_ARRAY_SIZE 200function baseUniq(array, iteratee, comparator) {let index -1let includes arrayIncludeslet isCommon trueconst { length } arrayconst result []let seen resultif (comparator) {isCommon falseincludes arrayIncludesWith}else if (length LARGE_ARRAY_SIZE) {const set iteratee ? null : createSet(array)if (set) {return setToArray(set)}isCommon falseincludes cacheHasseen new SetCache}else {seen iteratee ? [] : result}outer:while (index length) {let value array[index]const computed iteratee ? iteratee(value) : valuevalue (comparator || value ! 0) ? value : 0if (isCommon computed computed) {let seenIndex seen.lengthwhile (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 -1const { length } arrayconst 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.lengthwhile (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; // false
Number.NaN NaN; // false
isNaN(NaN); // true
isNaN(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); // 返回 true
o.hasOwnProperty(toString); // 返回 false
o.hasOwnProperty(hasOwnProperty); // 返回 false
复制代码call 的用法可以参考这篇 细说 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欢迎关注我的公众号 “超哥前端小栈”交流更多的想法与技术。 转载于:https://juejin.im/post/5c8c6c26f265da2db3059c93