惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
AI
AI
博客园 - 三生石上(FineUI控件)
腾讯CDC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Y
Y Combinator Blog
大猫的无限游戏
大猫的无限游戏
H
Hackread – Cybersecurity News, Data Breaches, AI and More
雷峰网
雷峰网
NISL@THU
NISL@THU
S
Schneier on Security
T
Threatpost
T
Tenable Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
IT之家
IT之家
宝玉的分享
宝玉的分享
T
Tailwind CSS Blog
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy & Cybersecurity Law Blog
I
Intezer
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
T
Threat Research - Cisco Blogs
SecWiki News
SecWiki News
AWS News Blog
AWS News Blog
博客园 - Franky
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Proofpoint News Feed
V
V2EX
Recorded Future
Recorded Future
Microsoft Security Blog
Microsoft Security Blog
S
Secure Thoughts
Google DeepMind News
Google DeepMind News
MongoDB | Blog
MongoDB | Blog
Apple Machine Learning Research
Apple Machine Learning Research
Project Zero
Project Zero
PCI Perspectives
PCI Perspectives
G
GRAHAM CLULEY
Help Net Security
Help Net Security
Cloudbric
Cloudbric
Recent Announcements
Recent Announcements
V
Visual Studio Blog
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
C
CERT Recently Published Vulnerability Notes
The Cloudflare Blog
Forbes - Security
Forbes - Security
C
Cisco Blogs
O
OpenAI News
www.infosecurity-magazine.com
www.infosecurity-magazine.com

囧克斯

奔四了,写给40岁的自己 有序退网 我和 Vue.js 的十年 我参与《代码之外 Beyond Code》的故事 博客站迁移至 VitePress 的备忘 写给我的奶奶 中文格式化小工具 zhlint 及其开发心得 vue-mark-display:用 markdown 语法轻松撰写幻灯片 我对技术会议的一些看法 VueConf Hangzhou 见闻 第四届 CSSConf CN 见闻 [译]Web 表单的未来 [译]苹果正在做一些他们的程序员明摆着不想要的东西 [译]为什么我不会无偿加班且你也不应该 [译]如果管理是唯一可走的路,那就完蛋了 [译]如何撰写 Git 提交信息 [译]C 程序的原则 Vue 2.0 来了! Weex 近 4 个月的开源之路 Weex 在 JS Runtime 内的多实例管理 我理解的 SPA 我理解的 Flux 架构 【整理】Vue 2.0 自 beta 1 到 beta 4 以来的主要更新 通过一张图走进 Vue 2.0 Code Review for Vue 2.0 Preview Vue 2.0 发布啦! 务实的小而美 Vue.js 1.0.0 发布了! [译]如何成为一名卓越的前端工程师 手机淘宝前端的图片相关工作流程梳理 [译]如何让办公室政治最小化 从原型到发布——“团队时间线” 1.0 开发心得 Vue + webpack 项目实践 用 Koa 写服务体验 webcomponents 笔记 之 配置管理 小秀个人的13~14年摄影作品 (共19张) 由今年D2前端论坛想到的 [译]CSS命名神马的真心难 [译]Git 分支的最佳实践 [译]撰写可测试的 JavaScript [译]语义化版本管理 [译]通过HTML5 Canvas API调节图像的亮度和颜色 [译]JavaScript V8性能小贴士 [译]视觉差,走起! [译]Chrome开发者工具中评估性能的五大新特性 精气神儿 细节无微不至,彩屏让人又爱又恨——新老“神机”大对决:Nokia 1050 vs Nokia 1202 秦升拿到红牌之后…… 用Sass重新整理自己的博客主题样式 Connect中间件使用手册 巧用 RequireJS Optimizer 给传统的前端项目打包 编辑器小调查结果 [译]JSLint 文档 烟火——写给蛇年的傲游和我 小秀个人的全年摄影作品 (共15张) 2012年终毫无正能量的总结 标签?ID?还是CLASS? 微创新=伪创新 HTML5峰会归来 分享bookmarklet一则:随意阅读 国际羽联和中国队之间的恶性循环 ZeroClipboard 学习笔记 “思考人生” 听杨东杰弹吉他 学习精髓 网站装修笔记20120426 网站装修笔记20120414 网站装修笔记20120406 分享Typecho插件:百度统计助手 我的得奖感言 网站装修笔记20120331 用CSS3制作尖角标签按钮样式 分享Typecho插件:Markdown 解析器 + 编辑器 分享Typecho皮肤:我的字很大 汇总自己过去的一些HTML5科普文章 html5中的消息通信简介 + 我的新网站开张 网站装修计划 Typed Arrays 是神马? 文明看球 HTML5中的文件处理 之 File Writer API HTML5中的文件处理 之 File API 独生子女、互相等和不耐烦 IndexedDB技术简介(四) IndexedDB技术简介(三) IndexedDB技术简介(二) IndexedDB技术简介(一) 把博客的字体进一步调大,同时去掉了侧边栏 “模仿别人是为了找到自己” 写给我未婚妻的2011年 写给自己的2011年 手把手教你入门EaselJS做HTML5动画 “吃了吧,不吃就浪费了” 写给傲游的2011年 所谓专业 写给HTML5的2011年 对HTML5中LocalStorage的一些使用建议 回味一下我2011年看到的最过瘾的5场足球比赛 浅浅浅谈开饭店被顾客吃出“异物”的用户体验 前端的本职 继续对Mac穷追猛打 之 修改原生按钮文字大小的技巧
Vue.js 源码学习笔记
勾三股四 · 2015-07-26 · via 囧克斯

最近饶有兴致的又把最新版 Vue.js 的源码学习了一下,觉得真心不错,个人觉得 Vue.js 的代码非常之优雅而且精辟,作者本身可能无 (bu) 意 (xie) 提及这些。那么,就让我来吧:)

程序结构梳理 ​

Vue 程序结构

Vue.js 是一个非常典型的 MVVM 的程序结构,整个程序从最上层大概分为

  1. 全局设计:包括全局接口、默认选项等
  2. vm 实例设计:包括接口设计 (vm 原型)、实例初始化过程设计 (vm 构造函数)

这里面大部分内容可以直接跟 Vue.js 的官方 API 参考文档对应起来,但文档里面没有且值得一提的是构造函数的设计,下面是我摘出的构造函数最核心的工作内容。

Vue 实例初始化

整个实例初始化的过程中,重中之重就是把数据 (Model) 和视图 (View) 建立起关联关系。Vue.js 和诸多 MVVM 的思路是类似的,主要做了三件事:

  1. 通过 observer 对 data 进行了监听,并且提供订阅某个数据项的变化的能力
  2. 把 template 解析成一段 document fragment,然后解析其中的 directive,得到每一个 directive 所依赖的数据项及其更新方法。比如 v-text="message" 被解析之后 (这里仅作示意,实际程序逻辑会更严谨而复杂):
    1. 所依赖的数据项 this.$data.message,以及
    2. 相应的视图更新方法 node.textContent = this.$data.message
  3. 通过 watcher 把上述两部分结合起来,即把 directive 中的数据依赖订阅在对应数据的 observer 上,这样当数据变化的时候,就会触发 observer,进而触发相关依赖对应的视图更新方法,最后达到模板原本的关联效果。

所以整个 vm 的核心,就是如何实现 observer, directive (parser), watcher 这三样东西


文件结构梳理 ​

Vue.js 源代码都存放在项目的 src 目录中,我们主要关注一下这个目录 (事实上 test/unit/specs 目录也值得一看,它是对应着每个源文件的测试用例)。

src 目录下有多个并列的文件夹,每个文件夹都是一部分独立而完整的程序设计。不过在我看来,这些目录之前也是有更立体的关系的:

Vue 文件结构

  • 首先是 api/* 目录,这几乎是最“上层”的接口封装,实际的实现都埋在了其它文件夹里
  • 然后是 instance/init.js,如果大家希望自顶向下了解所有 Vue.js 的工作原理的话,建议从这个文件开始看起
    • instance/scope.js:数据初始化,相关的子程序 (目录) 有 observer/*watcher.jsbatcher.js,而 observer/dep.js 又是数据观察和视图依赖相关联的关键
    • instance/compile.js:视图初始化,相关的子程序 (目录) 有 compiler/*directive.jsparsers/*
  • 其它核心要素:directives/*element-directives/*filters/*transition/*
  • 当然还有 util/* 目录,工具方法集合,其实还有一个类似的 cache.js
  • 最后是 config.js 默认配置项

篇幅有限,如果大家有意“通读” Vue.js 的话,个人建议顺着上面的整体介绍来阅读赏析。

接下来是一些自己觉得值得一提的代码细节

一些不容错过的代码/程序细节 ​

this._eventsCount 是什么? ​

一开始看 instance/init.js 的时候,我立刻注意到一个细节,就是 this._eventsCount = {} 这句,后面还有注释

eventsCount1

for $broadcast optimization

非常好奇,然后带着疑问继续看了下去,直到看到 api/events.js$broadcast 方法的实现,才知道这是为了避免不必要的深度遍历:在有广播事件到来时,如果当前 vm 的 _eventsCount0,则不必向其子 vm 继续传播该事件。而且这个文件稍后也有 _eventsCount 计数的实现方式。

eventsCount2

eventsCount3

这是一种很巧妙同时也可以在很多地方运用的性能优化方法。

数据更新的 diff 机制 ​

前阵子有很多关于视图更新效率的讨论,我猜主要是因为 virtual dom 这个概念的提出而导致的吧。这次我详细看了一下 Vue.js 的相关实现原理。

实际上,视图更新效率的焦点问题主要在于大列表的更新和深层数据更新这两方面,而被热烈讨论的主要是前者 (后者是因为需求小还是没争议我就不得而知了)。所以这里着重介绍一下 directives/repeat.js 里对于列表更新的相关代码。

diff1

首先 diff(data, oldVms) 这个函数的注释对整个比对更新机制做了个简要的阐述,大概意思是先比较新旧两个列表的 vm 的数据的状态,然后差量更新 DOM。

diff2

第一步:遍历新列表里的每一项,如果该项的 vm 之前就存在,则打一个 _reused 的标 (这个字段我一开始看 init.js 的时候也是困惑的…… 看到这里才明白意思),如果不存在对应的 vm,则创建一个新的。

diff3

第二步:遍历旧列表里的每一项,如果 _reused 的标没有被打上,则说明新列表里已经没有它了,就地销毁该 vm。

diff4

第三步:整理新的 vm 在视图里的顺序,同时还原之前打上的 _reused 标。就此列表更新完成。

顺带提一句 Vue.js 的元素过渡动画处理 (v-transition) 也设计得非常巧妙,感兴趣的自己看吧,就不展开介绍了

组件的 [keep-alive] 特性 ​

keepAlive1

keepAlive2

Vue.js 为其组件设计了一个 [keep-alive] 的特性,如果这个特性存在,那么在组件被重复创建的时候,会通过缓存机制快速创建组件,以提升视图更新的性能。代码在 directives/component.js

数据监听机制 ​

如何监听某一个对象属性的变化呢?我们很容易想到 Object.defineProperty 这个 API,为此属性设计一个特殊的 getter/setter,然后在 setter 里触发一个函数,就可以达到监听的效果。

ob

不过数组可能会有点麻烦,Vue.js 采取的是对几乎每一个可能改变数据的方法进行 prototype 更改:

ob_array1

但这个策略主要面临两个问题:

  1. 无法监听数据的 length,导致 arr.length 这样的数据改变无法被监听
  2. 通过角标更改数据,即类似 arr[2] = 1 这样的赋值操作,也无法被监听

为此 Vue.js 在文档中明确提示不建议直接角标修改数据

ob_array2

同时 Vue.js 提供了两个额外的“糖方法” $set$remove 来弥补这方面限制带来的不便。整体上看这是个取舍有度的设计。我个人之前在设计数据绑定库的时候也采取了类似的设计 (一个半途而废的内部项目就不具体献丑了),所以比较认同也有共鸣。

path 解析器的状态机设计 ​

首先要说 parsers 文件夹里有各种“财宝”等着大家挖掘!认真看一看一定不会后悔的

parsers/path.js 主要的职责是可以把一个 JSON 数据里的某一个“路径”下的数据取出来,比如:

var path = 'a.b[1].v'
var obj = {
  a: {
    b: [
      {v: 1},
      {v: 2},
      {v: 3}
    ]
  }
}
parse(obj, path) // 2

所以对 path 字符串的解析成为了它的关键。Vue.js 是通过状态机管理来实现对路径的解析的:

state1

咋一看很头大,不过如果再稍微梳理一下:

state graph

也许看得更清楚一点了,当然也能发现其中有一点小问题,就是源代码中 inIdent 这个状态是具有二义性的,它对应到了图中的三个地方,即 in ident 和两个 in (quoted) ident

实际上,我在看代码的过程中顺手提交了这个 bug,作者眼明手快,当天就进行了修复,现在最新的代码里已经不是这个样子了:

state2

而且状态机标识由字符串换成了数字常量,解析更准确的同时执行效率也会更高。

一点自己的思考 ​

首先是视图的解析过程,Vue.js 的策略是把 element 或 template string 先统一转换成 document fragment,然后再分解和解析其中的子组件和 directives。我觉得这里有一定的性能优化空间,毕竟 DOM 操作相比之余纯 JavaScript 运算还是会慢一些。

然后是基于移动端的思考,Vue.js 虽确实已经非常非常小巧了 (min+gzip 之后约 22 kb),但它是否可以更小,继续抽象出常用的核心功能,同时更快速,也是个值得思考的问题。

第三我非常喜欢通过 Vue.js 进行模块化开发的模式,Vue 是否也可以借助类似 web components + virtual dom 的形态把这样的开发模式带到更多的领域,也是件很有意义的事情。

总结 ​

Vue.js 里的代码细节还不仅于此,比如:

  • cache.js 里的缓存机制设计和场景运用 (如在 parsers/path.js 中)
  • parsers/template.js 里的 cloneNode 方法重写和对 HTML 自动补全机制的兼容
  • 在开发和生产环境分别用注释结点和不可见文本结点作为视图的“占位符”等等

自己也在阅读代码,了解 Vue.js 的同时学到了很多东西,同时我觉得代码实现只是 Vue.js 优秀的要素之一,整体的程序设计、API 设计、细节的取舍、项目的工程考量都非常棒!

总之,分享一些自己的收获和代码的细节,希望可以帮助大家开阔思路,提供灵感。