前端日记
今天是前端日记的第三天,断更了不好意思! [害羞] ,由于本人才疏学浅,花费比较多时间理解Vue原理,以后还是坚持至少每3天一个额外知识点,然后与大家一起分享。一个前端小白的学习之路。
分析Vue
<div id="app">
<p v-text="msg"></p>
</div>
<h1 v-text="msg"></h1>//不会被vue编译
<script src="./vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
msg: '我是信息',
}
})
</script>
首先分析简单的一个Vue片段,我们new Vue()其实是实例化Vue这个类,中传入了 el、data两个参数
1、el代表了整个vue挂载的根元素(可以传入字符串或者一个节点对象),意思是,在id为app以外的dom节点不会被vue这个类所编译,如上图的h1标签,渲染后的内容为空而不是 我是信息 这几个字。
2、data代表了整个Vue类的数据,如果dom元素上有指定data对象中的属性,则vue会根据指令把data对应属性的值替换到dom元素的值,如上图的p元素指定了自己的文本节点是data对象的msg属性,所以p元素渲染出来后的内容是 我是信息 这几个字。
实现Vue
class Vue {
constructor(option) {
this.$el = option.el;//入口
this.$data = option.data;//数据
this.$option = option;
this.$el && new Compile(this.$el, this)//创建模板编译类
}
}
Vue类实现是比较简单的,Vue考虑到性能问题,会先把根元素下(即div#app)下所有的节点放在文档碎片中,如果不了解文档碎片概念可自行百度,大概意思是在内存中开辟一个空间,存储所有待编译的节点,然后编译完成后在整体append到根元素下,这样只会操作一次dom,大大减少了回流(涉及重绘回流也可自行百度)
//编译模板类
class Compile {
constructor(el, vm) {
this.vm = vm;
//判断是否是元素节点
this.el = this.isElementNode(el) ? el : document.querySelector(el);
if (this.el) {
const f = this.createFragment(this.el) //返回文档碎片
this.compile(f)
this.el.appendChild(f)
}
}
// 判断是否元素节点
isElementNode(node) {
return node && node.nodeType === 1;
}
//判断是否指令
isDirective(name) {
return name.includes('v-')
}
//创建文档碎片
createFragment(node) {
let f = document.createDocumentFragment();
let first;
while (first = node.firstChild) {
f.appendChild(first)
}
return f;
}
//编译文档碎片
compile(fragment) {
let childNodes = fragment.childNodes;
[...childNodes].forEach(node => {
if (this.isElementNode(node)) {
//是元素节点
this.compileElement(node);//先编译元素节点
}
});
}
//编译元素节点
compileElement(node) {
const attributes = node.attributes;
//如果有属性
if (attributes.length) {
[...attributes].forEach(attribute => {
const { name, value } = attribute
//判断是否vue指令
if (this.isDirective(name)) {
const [, type] = name.split('-');//分割
//这里执行compileUtil[text]
compileUtil[directiveType](node, value, this.vm)
}
})
}
}
}
自定义一个对象存储,解析不同指令或者文本编译集合
// 解析不同指令或者文本编译集合
const compileUtil = {
getValue(expr, vm) {
//当遇到多重对象 如 person.age.fav =>[person,age,fav]
return expr.split('.').reduce((data, currentValue) => {
return data[currentValue]
}, vm.$data)
},
text(node, expr, vm) {
//v-text
//第一步获取的初始值
const value = this.getValue(expr, vm);
this.updater.textUpdater(node, value)
},
//更新的函数
updater: {
textUpdater(node, value) {
node.textContent = value;
}
}
}
至此,经过模板编译后的的html已经渲染出正确信息
下一节,实现如何实现多指令编译
谢谢观赏,如有不足,请下方留言,共勉之! —一个菜鸡前端的路程