「读懂源码系列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。iterateecomparator 的用法,会在后面说到。

以 _.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 作为一个参数传入,将 targetarray 的每个 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/279884.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

有小伙伴问:上位机用QT还是winform/wpf好?

楔子有小伙伴问&#xff1a;上位机用QT还是winform/wpf好&#xff1f;Qt是C写的&#xff0c;跨平台的UI框架&#xff0c;Winform/wpf是C#写的不跨平台的Windows上运行的UI框架。这两个说到底是语言本质的争论或者区别。优点Qt的优点是可以跨平台运行UI界面&#xff0c;在Linux&…

使用jenkins进行项目的自动构建部署

jenkins 简介 Jenkins是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;功能包括&#xff1a;持续的软件版本发布/测试项目和监控外部调用执行的工作。 官网地址地址&#xff1a; https://jenkins.io 下载安装启动 CentOS 下用yum进行安装启动 …

如何删除Apple Music中的连接功能

Love Apple Music, but tired of the intrusive Connect feature taking up space on your favorite artist’s page? Well, don’t worry, because getting “dis-Connected” is just a matter of changing a few simple settings in your iPhone or iPad running iOS 8.0 o…

python设计模式(十四):模板方法模式

定义一个算法或者流程&#xff0c;部分环节设计为外部可变&#xff0c;用类似于模板的思想来实例化一个实体&#xff0c;可以往模板中填充不同的内容&#xff1b;在模板思想下&#xff0c;实体的整体框架是确定的&#xff0c;他是一个模板&#xff0c;但是模板下内容可变&#…

FirstBird--项目流程

创建项目(英文路径)—–img图片文件创建窗体–设置大小(Basic—size–>320*480)—最大化功能禁用(Expert–>setResizable(false))添加面板–设置布局方式(set Layout—>AbsoluteLayout)自己创建面板 GameMain中将Jpanel1改为WinJpanel–创建对应类–>extends JPane…

PeeringDB初探

做网络相关工作的&#xff0c;可能需要了解PeeringDB这个网站&#xff08;https://www.peeringdb.com)&#xff0c; 这里有大部分公开注册的 ASN&#xff08;Autonomous System Number) 以及他们相互直接做Peering的信息&#xff0c;这也是这个网站名字的由来。据统计&#xff…

网站排障分析命令

系统连接状态篇&#xff1a;1.查看TCP连接状态netstat-nat|awk{print$6}|sort|uniq-c|sort-rnnetstat-n|awk/^tcp/{print$NF}|sort|uniq-c|sort-rnnetstat-ant|awk{print$NF}|grep-v[a-z]|sort|uniq-c2.查找请求数请20个IP&#xff08;常用于查找攻来源&#xff09;&#xff1a…

修复windows脸部识别_如何在Windows 10中改善面部识别

修复windows脸部识别If you have the right hardware, Windows 10 lets you unlock your computer with nothing but a smile. However, Microsoft’s facial recognition isn’t always spot-on. Here’s how to help Windows recognize you better. 如果您拥有合适的硬件&…

使用组策略推送exchange自签名证书

一、导出证书打开证书颁发机构&#xff0c;在证书服务器上面选属性&#xff0c;然后按照下图进行导出操作。 在选择格式时按照上图标识选择。 二、导入证书新建一个组策略&#xff0c;在计算机配置-策略-windows设置-安全设置-公钥策略中选中“受信任的根证书颁发机构”并新建导…

基于.NetCore开发,前端支持Layui、React、Vue且前后端分离的快速开发框架

今天给大家推荐一个基于.Net Core开发的&#xff0c;前端框架支持Layui、React、Vue&#xff0c;并且前端和后端都支持代码一键生成&#xff0c;用于项目开发&#xff0c;可极大的提升开发效率。项目简介这是基于.net core的快速开发框架&#xff0c;前端框架可以根据自己需求选…

PHP常用工具方法集...

PHP常用工具方法集&#xff0c;更新时间 2018-7-14 <?php /*** 常用工具方法集* Author: zj*//** 工具总述 1.加密解密 2.生成随机字符串 3.获取文件扩展名&#xff08;后缀&#xff09; 4.文件大小格式化 5.替换标签字符 6.列出目录下的文件名 7.获取当前页面URL 8.让浏览…

一题多解 面试题

最近在其他论坛上看到几个网友的面试题&#xff0c;这些天&#xff0c;QQ群内的人都在讨论怎么解答才最简单&#xff0c;下面列出题目&#xff1a; 文件a&#xff1a; 文件b: a b c a b c b c a b c a c b a …

什么是Google On.Here,以及如何设置?

Google Wi-Fi is similar to other mesh Wi-Fi systems, but one big feature separates it from the pack: Google On.Here. Google Wi-Fi与其他网状Wi-Fi系统相似&#xff0c;但其中一个重要功能将其与众不同&#xff1a;Google On.Here。 发生什么了&#xff1f; (What Is O…

一张图看懂 SQL 的各种 join 用法

原文链接https://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins 转载于:https://www.cnblogs.com/xuchao0506/p/10559951.html

1Python全栈之路系列Web框架介绍

Python全栈之路系列之Web框架介绍 所有的语言Web框架本质其实就是起一个socket服务端,监听一个端口,然后运行起来 Web框架包含两部分,一部分是socket,另外一部分是业务的逻辑处理,根据请求的不同做不同的处理 Python的Web框架分成了两类, 即包含socket也包含业务逻辑处理的(tor…

『 再看.NET7』数值类型

在C#中&#xff0c;有int16&#xff0c;用short来定义&#xff1b;有int32&#xff0c;用int定义&#xff1b;用int64&#xff0c;用long来定义。在.NET7中&#xff0c;添加了int128&#xff0c;和unint128&#xff0c;位数更大的整型。var i16 short.MaxValue; Console.Write…

获取帮助命令

whatis 基于数据库的查找,查找内容比较慢 优点&#xff1a;查找速度快 缺点&#xff1a;没有实时性 [rootlocalhost ~]# whatis ls ls (1) - list directory contents ls (1p) - list directory contents 数据库文件 Centos6:/…

笔记本电脑升级固态硬盘好吗_如何升级笔记本电脑硬盘

笔记本电脑升级固态硬盘好吗Upgrading your laptop’s hard drive is a great way to get some extra life out of an old machine (or resurrect a dead one). Read on as we walk you through the prep work, the installation, and the followup. 升级笔记本电脑的硬盘驱动器…

购物单

小明刚刚找到工作&#xff0c;老板人很好&#xff0c;只是老板夫人很爱购物。老板忙的时候经常让小明帮忙到商场代为购物。小明很厌烦&#xff0c;但又不好推辞。 这不&#xff0c;XX大促销又来了&#xff01;老板夫人开出了长长的购物单&#xff0c;都是有打折优惠的。 …

Seay源代码审计系统

这是一款基于C#语言开发的一款针对PHP代码安全性审计的系统&#xff0c;主要运行于Windows系统上。这款软件能够发现SQL注入、代码执行、命令执行、文件包含、文件上传、绕过转义防护、拒绝服务、XSS跨站、信息泄露、任意URL跳转等漏洞。 下载链接 https://pan.baidu.com/s/1V…