开始
这一段时间在阅读 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 给予一个小小的鼓励,谢谢。