这是一个我即将作的一个《数据结构与算法在前端领域的应用》主题演讲的一个前菜。 但愿经过这个分享让你们认识到其实前端领域也有不少算法的,从而加深前端同窗对算法的认识。 若是你们对数据结构和算法感兴趣,欢迎关注个人我的公众号,或者入群和我交流,二维码在文章末尾。前端
我是一个对技术充满兴趣的程序员, 擅长前端工程化,前端性能优化,前端标准化等。vue
作过.net, 搞过Java,如今是一名前端工程师。node
除了个人本职工做外,我会在开源社区进行一些输出和分享,GitHub 共计得到1.5W star。比较受欢迎的项目有leetcode题解 , 宇宙最强的前端面试指南 和 个人第一本小书react
在个人职业生涯中,碰到不少非算法岗的研发同窗吐槽“算法在实际业务中没什么用”, 甚至在面试官也问过我这个问题。咱们姑且不来判断这句话正确与否,咱们先来看下为何你们会有这样的想法。webpack
我发现不少人喜欢用冰山这个图来表示这种只看到整体的一小部分的状况。 我也来借用一下这个创意。git
根据个人经验,咱们写的业务代码一般不到总包体的 5%, 下面是我之前作过的一个实际项目的代码分布。程序员
$ du -sh node_modules # 429M
$ du -sh src # 7.7M
复制代码
你们能够拿本身的实际项目看一下,看是否是这样的。github
其实不难看出业务代码在整个应用的比例是很小的,软件工程有一个至理名言,“软件开发的 90%的工做是由 10%的人完成的”, 这句话很对,那么剩下的 10 的工做却由剩下的 90%来完成。web
所以咱们感受算法没用,是由于咱们没用深刻剩下的“90%” 不少场景咱们接触不到,而且没有思考过,就很容易“井底之蛙”,到头来就变成“只会用别人造好的轮子组装代码”的“前端打字员”。面试
那剩下的 90% 究竟有哪些涉及到算法呢?是否能够举例说明呢? 那接下来让咱们来继续看一下。
说实话,这部份内容实在太多啦,为了让你们有一个直观的感觉,我画了一个图。
图中黄色的表明我本身实现过。
这些都是前端开发过程的一些东西, 他们多多少少涉及到了数据结构和算法的知识
下面咱们来简单分析一下。
事实上 VDOM 就是一种数据结构,可是它并非咱们在《数据结构与算法》课程中学习到的一些现成的数据结构。
逻辑上 VDOM 就是用来抽象 DOM 的,底层上 VDOM 广泛实现是基于 hash table 这种数据结构的。
一个典型的 VDOM 能够是:
{
type: 'div',
props: {
name: 'lucifer'
},
children: [{
type: 'span',
props: {},
children: []
}]
}
复制代码
不难看出,VDOM 是一种递归的数据结构,所以使用递归的方式来处理是很是直观和容易的。
我上面提到了 VDOM 是 DOM 的抽象(ye, a new level of abstraction)。 根据 VDOM 咱们能够建立一个对应的真实 DOM。
若是咱们只是作了这一层抽象的话,咱们至关于引入了一种编程模式,即从 面向 DOM 编程,切换到面向 VDOM 编程,而如今 VDOM 又是由数据驱动的,所以 咱们的编程模式切换到了“数据驱动”。
事实上,VDOM 部分还有一个 VDOM diff 算法,相信你们都据说过。关于 DOM diff 的算法,以及它是如何取舍和创新的,我以前在一个地方回答过,这里给一个连接地址: juejin.im/post/5d3d8c…
Hooks 是 React16 添加的一个新功能, 它要解决的问题是状态逻辑复用。
Hooks 逻辑上解决了纯函数没法持久化状态的“问题”,从而拓宽了纯函数组件的 适用范围。
底层上 Hooks 使用数据来实现状态的对应关系,关于这部分能够参考个人 [第一期]实现一个简化版的 React Hook - useState
Fiber 也是 React16 添加的一个新功能。
事实上 Fiber 相似 VDOM,也是一个数据结构,并且一样也是一个递归的数据结构。
为了解决 React 以前一次全量更新的"问题", React 引入了 fiber 这种数据结构, 并重写了整个调和算法,而且划分了多个阶段。 关于这部份内容,我只推荐一篇文章, Inside Fiber: in-depth overview of the new reconciliation algorithm in React
其实以前个人从零开始实现 React 系列教程 也欠了 fiber 😄, 等我开心的时候补充上去哈。
我以前写过一个 Git 终端(代码被我 rm -rf 啦)。 这过程仍是用到了不少数据结构和算法的, 我也学到了不少东西, 甚至 React16 新的调和算法也有 Git 思想。
很直观的,Git 在推送本地仓库到远程的时候会进行压缩,其实这里就用到了最小编辑距离算法。 Leetcode 有一个题目72. Edit Distance, 官方难度hard
, Git 的算法要是这个算法的复杂版本。
另外 Git 其实不少存储文件,数据的时候也用到了特殊的数据结构,我在这里 进行了详细的描述,感兴趣的能够去看看。
Webpack 是众所周知的一个前端构建工具,咱们能够用它作不少事情。 至今在前端构建领域仍是龙头老大 🐲 的位置。
Webpack 中最核心的 tapable 是什么,是如何配合插件系统的? webpack 是如何对资源进行抽象的, webpack 是如何对依赖进行处理的?更复杂一点 Tree Shaking 如何作,分包怎么作, 加速打包怎么作。
其实 webpack 的执行过程是基于事件驱动的,tapable 提供了一系列钩子, 让 plugin 深刻到这些过程之中去。听起来有点像事件总线,其实其中的设计思想和算法 细节要复杂和精妙不少。
关于这部分细节,我在个人从零实现一个 Webpack
以后会加入更多特性,好比 tapable
AST(抽象语法树)是前端 编译(严格意义上是转义)的理论基础, 你若是想深刻前端编译,就必定不能不会这个知识点。
和 AST 类似的,其实还有 CST,prettier 等格式化工具会用到, 有兴趣能够搜一下。
这个网站 可让你对 AST 有一个直观的认识。
AST 厉害就厉害在它自己不涉及到任何语法,所以你只要编写相应的转义规则,就能够将任何语法转义到任何语法。 这就是babel
, PostCSS
, prettier
, typescript
等的原理, 除此以外,还有不少应用场景,好比编辑器。
以前本身写过一个小型的生成 AST 的程序,源代码忘记放哪了。😅
像浏览器中的历史页面,移动端 webview 的 view stack
, 都用到了栈
这种数据结构。
剩下的我就不一一说了。其实都是有不少数据结构和算法的影子的。
OK,说了那么多。 这些都是“大牛”们作的事情,好像和我平常开发不要紧啊。 我只要用他们作好的东西,调用一下,同样能够完成个人平常工做啊。 让咱们带着这个问题继续往下看。
大神: “你能够先这样,再这样,而后就会抽象为纯算法问题了。”
我: “哇,好厉害。”
复制代码
其实就是你没有掌握,或者“再思考”,以致于不能融汇贯通。
好比你能够用 vue 组件写一个递归,实现一些递归的功能,也是能够的, 可是大多数人都想不到。
接下来,我会举几个例子来讲明“算法在平常开发中的应用”。 注意,如下全部的例子均来自个人实际业务需求。
某一天,可(gai)爱(si)的产品提了一个需求,”咱们的系统须要支持用户撤销和重作最近十次的操做。“
让咱们来回忆一下纯函数。
纯函数有一个特性是给定输入,输出老是同样的。
咱们对问题进行一些简化,假设咱们的应用是纯粹的数据驱动,也就是说知足纯
的特性。
咱们继续引入第二个知识点 - reducer
.
reducer 是一个纯函数,函数签名为(store1, action1) => store2
。 即给定 state 和 action,必定会返回肯定的新的 state。
本质上 reducer 是 reduce 的空间版本。
假设咱们的应用初始 state 为 state1, 咱们按照时间前后顺序分别发送了三个 action, action1, action2, action3。
咱们用一个图来表示就是这样的:
运用简单的数据知识,咱们不难推导出以下关系:
若是对这部分知识点还比较迷茫,能够看下我以前的一篇文章,从零实现一个 Redux
基础知识铺垫完了,咱们来看一下怎么解决这个问题。
第一种方案,咱们能够将每次的store,即store1, store2, store3都存起来。 好比我想回退到第二步,咱们只须要将store2取出来,覆盖当前store,而后从新渲染便可。 这种方案很直观,能够知足咱们的业务需求。 可是缺点也很明显,store在这里被存储了不少。 每次发送一个action都会有一个新的store被存起来。 当咱们应用比较大的时候,或者用户触发了不少action的时候,会占据大量内存。 实际场景中性能上咱们很难接受。
第二种方案,有了上面的铺垫,咱们发现, 事实上咱们不必存储全部的store。 由于store能够被计算出来。所以咱们只要存储action便可。 好比咱们要回退到第二步,咱们拿出来store1,而后和action运算一次,获得store2, 而后将store2覆盖到当前的store便可。
这种作法,只须要存储一个store1, 以及若干个action。 action相对于store来讲要小不少。 这是这种作法相比与上一种的优点。同时因为每次都须要从头(store1)开始计算, 所以是一种典型的“时间换空间”的优化手段。
实际上这种作法,咱们能够进行小小的优化。好比咱们设置多个snapshot, 而后咱们就没必要每次从头开始计算,而是算出最近的一个snapshot, 而后计算便可。 无疑这种作法能够减小不少计算量,可是会增长空间占用。 这是典型的“空间换时间”, 若是根据实际业务进行取舍是关键。
第三种方案,咱们能够用树来表示咱们的store。每次修改store,咱们不是将整个store 销毁而后建立一个新的,而是重用能够重用的部分。
如图我要修改 store.user.age
。咱们只须要将root和user的引用进行修改,同时替换age节点便可。
若是你们对immutable研究比较深的话应该能发现,这其实就是immutable的原理
因为业务须要,咱们须要在前端缓存一些HTTP请求。 咱们设计了以下的数据结构,其中key表示资源的URL, value会上次服务端的返回值。
如今咱们的项目中已经有上千个接口,当接口多起来以后,缓存占用会比较大,咱们如何对此进行优化?
注: 咱们的key中的前缀是有规律的,即有不少重复的数据在。 返回值也有多是有不少重复的。
这是一个典型的数据压缩算法。数据压缩算法有不少,我这里就不介绍了,你们能够自行了解一下。
对数据压缩算法感兴趣的,能够看下我以前写的游程编码和哈夫曼编码
如今不少输入框都带了自动联想的功能, 不少组件库也实现了自动填充组件。
如今须要你完成这个功能,你会怎么作?
咱们能够用前缀树,很高效的完成这个工做。
对这部分原理感兴趣的能够看下个人这个题解
因为业务须要,咱们须要对字符串进行类似度检测。 对于类似度超过必定阀值的数据,咱们认为它是同一个数据。
关于类似度检测,咱们其实能够借助“最小编辑距离”算法。 对于两个字符串a和b,若是a和b的编辑距离越小,咱们认为越类似, 反之越不类似。 特殊状况,若是编辑距离为0表示是相同的字符串, 类似度为100%。 咱们能够加入本身的计算因子,将类似度 离散在0 - 100%之间。
这部分的内容,我在介绍Git的时候介绍过了,这里再也不重复。
其实咱们能够进一步扩展一下,好比对于一些无心义的词不计入统计范围
,咱们能够怎么作?
这恐怕是不少人最关心的问题。
我虽然知道了算法有用,可是我不会怎么办?会有什么样的影响呢?
这就回到了咱们开头的问题,“为何不少人以为算法没用”。 事实上,咱们平常开发中真正用到算法的场景不多,大部分都被别人封装好了。 即便真正须要用到一些算法,咱们也能够经过一些“低劣”的手段完成,在不少对性能和质量要求 不高的业务场景都是没有问题的。 这就是为何“前端同窗更加以为算法没用”的缘由之一。
那既然这么说,是否是真的算法就没用呢? 或者说算法很差也不会怎么样了么? 固然不是, 若是算法很差,会很难创新和突破
。 想一想现在前端框架,工具的演进,哪个不是创建在 无数的算法之上。 将视角聚焦到咱们当下的业务上,若是算法很差,咱们也一样很难让业务不断精进, 不断赋能业务。
React框架就是一个很是典型的例子,它的出现改变了传统的编程模式。 Redux的做者,React团队现任领导者 dan 最近发表了一篇我的博客 Algebraic Effects for the Rest of Us 这里面也有不少算法相关的东西,你们有兴趣的能够读读看。
另外我最近在作的一个 stack-visualizer,一个用于跟踪浏览器堆栈信息,以便更好地调试地工具, 这个也是和算法有关系的。
最近我从新整理了下本身的公众号,而且我还给他换了一个名字《脑洞前端》,它是一个帮助你打开大前端新世界大门的钥匙🔑,在这里你能够听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。
我会尽可能经过图的形式来阐述一些概念和逻辑,帮助你们快速理解,图解前端是个人目标。
以后个人文章同步到微信公众号 脑洞前端 ,您能够关注获取最新的文章,或者和我进行交流。
如今仍是初级阶段,须要你们的意见和反馈,为了减小沟通成本,我组建了交流群。你们能够扫码进入
(因为微信的限制,100我的以上只能邀请加入, 你能够添加个人机器人回复“大前端”拉你进群)