开始
这一段时间在阅读 Vue 2.6 的源码,太多精妙的设计和写法令人叹为观止。虽然思维导图什么的已经画了很多,整个流程和关键的逻辑已经高通,但是本着纸上学来终觉浅的原则,决定自己动手撸一个 Vue 的简版。
需求分析
Vue 有几个重要的组成部分 ---> Observe,complier,Dep,Watcher,Patch。下面会逐一分析原理和我自己的实现。
数据响应部分
这个部分相信直到 Vue 原理的人都清楚,核心是 javascript 的语言特性 api Object,definePrototype ,即使你没用过这个 api,也一定直到它是 Vue 实现响应式的关键,但是它对于 Vue 的响应式系统来说是个入口,Vue 通过它实现了依赖收集和视图更新,这其中的涉及到的部分主要有这几个:Observe,Dep,Watcher。下面是概念代码实现:
function Observer(obj) {
const keys = Object.keys(obj)
keys.forEach( item => {
const dep = new Dep()
let value = obj[item]
Object.definePrototype( obj, item, {
get() {
if( Dep.target ) {
dep.add(Dep.target)
}
return value
},
set(val) {
if( val !== value ) {
value = val
dep.nodify()
}
}
} )
} )
}
这段代码基本是相应式数据依赖收集和触发更新的全部代码,如果你对这其中的细节感兴趣,或者想知道 Dep.targrt 是什么,可以点击这里查看 watcher.js 和 Dep.js 的更多细节实现。如果看的爽记得一个 Star。
Complier 部分
这个部分是 Vue 源码中逻辑最复杂的部分,它将平时定义的模版编译成了 render 函数,也就是说,只在实例初始化时对模版编译一次,后续只需要根据数据和 render 结果去做 patch 就可以了,Vue 的 Complier 来自另一个简易版的 html 解析器,尤大将器扩充和修改,就成了现在的 Vue Complier。这里我是直接采用了这个简易版的模版解析,具体实现代码在这里。
具体使用的代码在这里,它将<div>123</div> 就系出来,但是需要自己再对解析的每个节点的结果做一个有机的拼合,形成一个 vnode,以供 patch 使用,下面是配置编译器的具体代码:
const handler = {
startElement: function (sTagName, oAttrs) {
//标签开始
if (tag.length === 0) {
currentNode = { tag: sTagName, attr: parseProps(oAttrs), children: [] }
arr.push(currentNode)
} else {
const parentNode = arr[arr.length - 1]
currentNode = { tag: sTagName, attr: parseProps(oAttrs), children: [] }
parentNode.children.push(currentNode)
}
tag.push(sTagName) // 遇到开始标签,就入栈
// console.log("startElement", sTagName, oAttrs);
},
endElement: function (sTagName) {
// 标签解释
tag.pop() // 遇到结束标签,就弹出去一个
// console.log("endElement", sTagName);
},
characters: function (s) {
// 表示的是获取的字符
(currentNode && currentNode.children) && currentNode.children.push(s)
// console.log("characters", s);
},
comment: function (s) {
// 表示获取的注释
currentNode.children.push({ tag: "comment", text: s })
// console.log("comment", s);
}
};
编译过程还包含了像指令,属性等的解析,这里这个编译器只会将 <div v-model="sss"></div> 的 v-model 解析为:
{
name: "v-model",
value: "sss"
}
的形式,所以,需要自己再多做一部分工作,那就是将执行转换为标签属性,比如将 v-model 转换为 value 属性和 input 事件。这一部分的内容我是放在生成 render 函数时做的,具体代码在这里。处理思路和上述描述的一致。
Patch 部分
这个部分直接使用的 Snabbdom.js,这个 Vue patch 的基础版本实现,如有兴趣可以去官网研究。
数据和 Patch 进行链接
这个部分就是 Vue 中的核心部分。我们知道,对于每个实例,Vue 只会生成一个 render 函数,这样有利于批量更新,减少更新的次数(当然,还有异步调度的配合,代码在这里)。具体 render 函数的生成逻辑在这里。
这里是模仿 Vue 官方生成的样子就行递归生成的字符串,比如使用 with圈定作用域范围,使用不同的函数去生成不同的节点等等,这一部分需要深入研究,因为目前我的算法还存在瑕疵。(工作太忙,大家见谅。)
后续
这篇文章只是一个开头,后续会针对每一部分的源码和逻辑结合源码进行更深如的分析:
- Diff 的核心算法
- Vue 的初始化流程
- 实例更新的流程
- 组件的初始化,挂载,渲染流程解析
总结
项目的完整代码在这里, 里面也有其他知识可供学习查看。需要的同学可以自行查看,看的爽的别忘了给个 Star 给予一个小小的鼓励,谢谢。