第872期学习Vue.js源码
Posted 前端早读课
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第872期学习Vue.js源码相关的知识,希望对你有一定的参考价值。
前言
3月12号又周末了,今日早读文章由臭豆腐前端@Fedora带来分享。
正文从这开始~
Vue.js是目前MVVM框架中比较流行的一种,在之前的项目中也用过Vue+Vuex。在使用的过程中,也是一边看文档一边进行开发。对Vue的了解也仅限官方文档和一些社区上回答,很大程度上是知其然而不知其所以然。所以在项目告一段落之后,决定从Vue的源码入手,学习一下其内部架构。
Vue源码目录
目标Vue.js版本是2.0.5 /src目录
对应的目录分别为
/compiler、/core、/entries、/platforms、/server、/sfc、/shared
/compiler目录是编译模版;
/core目录是Vue.js的核心(也是后面的重点);
/entries目录是生产打包的入口;
/platforms目录是针对核心模块的’平台’模块,platforms目录下暂时只有web目录(在最新的开发目录里面已经有weex目录了)。web目录下有对应的/compiler、/runtime、/server、/util目录;
/server目录是处理服务端渲染;
/sfc目录处理单文件.vue;
/shared目录提供全局用到的工具函数。
在刚学习源码时,会对类似
这样函数申明或者变量申明感动疑惑的,可以先了解一下flow。
独立构建&&运行时构建
Vue.js从2.0以后开始出现两个不同的构建版本,详情可以查看官网文档。一开始说到这个,是因为从Vue的build命令里面可以看到有7个build版本,这对学习其源码非常有帮助。
这里,关注的重点是runtime的版本。
Vue.js结构
通过上面的分析,可以看到Vue.js的组成是由core+对应的‘平台’补充代码构成(独立构建和运行时构建只是platforms下web平台的两种选择)。
core目录下面对应的components、global-api、instance、observer、util、vdom模块。
Vue.js的目标是通过尽可能简单的API实现响应的数据绑定和组合的视图组件
Vue2.0在保持实现‘响应的数据绑定’的同时又引入了’virtual-dom’,那么它是怎么实现的呢?
响应的数据绑定
Vue.js实现数据绑定的关键是Object.defineProperty(obj, prop, descriptor),这也是为什么2.0不支持IE8原因之一,IE8下无法实现defineProperty的腻子脚本。当然实现类似功能的现代语法还有Object.observe(已经废弃)和Proxy。
Vue源码对此实现的逻辑在core/observer目录下。 关注三个类class Observer,class Dep,class Watcher
class Observer在core/observer/index.js中
Observer类实例是用来附加到每个被观察的对象(后面称之为响应式对象)上的。普通对象通常是不会变成‘响应式对象’的。经过defineReactive函数的调用,才会将传入的普通对象变成‘响应式对象’。而defineReactive就是利用了Object.defineProperty这个方法。defineReactive源码如下:
Object.defineProperty(obj, prop, descriptor)中的第三个参数就是对象描述符。对象描述符的传入是有要求的,分为赋值描述符合存取描述符。而每次传入的参数只能是其中之一,很显然在defineReactive中传入的就是存取描述符,在传入的存取描述符对象中有get,set方法。set方法会实例化一个Observer,get方法会关联到一个class Dep的实例。但是仔细看get方法,发现只有在Dep.target值为true的时候才会发生关联。所以,接下来分析一下class Dep的源码。
class Dep就是连接class Observer和class Watch类的介质。因为在set方法里面最终会调用dep.notify()方法。class Dep类中的notify方法会使这个dep实例下所有的watch数组更新一次。class Watch类的update方法会调用对应的回调方法,进行对应的更新。同时在前面提到的get方法关联class Dep实例时,是在Dep.target为true的时候才会执行。通过源码可以看到Dep.target的初始值是null,也就是默认是不会执行关联的。源码上对此做了注释,可以看到Dep.target是被赋予全局性质,用来保证同一时刻只有一个Watcher实例在被‘关联’(源码注释的地方是’evaluated’)。而激活Dep.target这个属性的是函数pushTarget。pushTarget函数就在class Watcher中调用了。class Watcher的源码如下:
到这里,class Observer、class Dep和class Watch三个类的关系就清楚了。这也是Vue中实现数据绑定用到的观察者模式的体现。
Virtual-dom
React的大热,是因为其带来了’Virtual-dom’和数据驱动视图的理念(尽管很多人觉得后者更重要)。这里并不想比较‘Virtual-dom’和原生的DOM操作谁快谁慢的问题(事实上在dom结构改动很多的情况下,原生DOM操作比较快。。。),仅仅是理解一下Virtual-dom。
‘Virtual-dom’是一系列的模块集合,用来提供声明式的DOM渲染。来看一个简单的DOM片段
对DOM片段结构抽象一下:一个根节点div,三个元素子节点span(对应的内部文本节点)。然后用javascript对象表示:
进而扩展到整个html页面的结构。整个HTML页面结构其实可以用一个JavaScript对象表示,通过这个抽象,对dom对象的修改就会影响到HTML页面的结构。所以在改变HTML结构的时候,我们仅仅是修改JavaScript对象。相对以前修改HTML页面结构式通过直接修改DOM元素,现在变成修改对应的JavaScript对象。Vue.js在对DOM的抽象做的更细致,具体代码可以看core/vdom/create-element.js。
在实现了对HTML结构的映射后,接下来就是‘Virtual-dom’的重点,如何比较两个不同HTML结构树的对象–diff算法。diff算法比较的就是两颗’树’的差异。而传统的’树’比较是一个时间复杂度为O(n^3),这个效率明显是不够的。而HTML的结构树的改变不同于传统的’树’。HTML结构树的改变,很少会出现跨越不同层级的改变。基于这个实际上的差异化改变,‘Virtual-dom’的diff算法的时间复杂度是O(n)。有兴趣研究diff算法的,可以看下这里。
在解决了diff算法核心问题后,就要把’新HTML结构树’相对’老HTML结构树’的差异应用到’老HTML结构树’上–‘patch’。Vue.js在’patch’的解决方案上参考了开源项目Snabbdom。源码篇幅有点长,也有些复杂,想深入了解的可以配合开源的项目一起分析。
至此,‘Virtual-dom’大致的实现逻辑也清楚了。Vue.js的源码在架构组织上还有很多可以学习的,比如区分’核心模块’和’平台模块’。另外内部实现的’keep-alive’组件也是值得关注的地方,更多了解请到这里。以上只是很’粗浅’的学习,不对的地方希望大家指出来(认真脸.jpg)。
参考资料
Vue官网文档
What is Virtual Dom
The difference between Virtual DOM and DOM
最后,你可能还需要:
关于本文
以上是关于第872期学习Vue.js源码的主要内容,如果未能解决你的问题,请参考以下文章
[第18期] Vue.js最佳实践(五招让你成为Vue.js大师)
[第7期] 基于 Nuxt 的 Vue.js 服务端渲染实践