前言:
好长一段时间没有写vue了,这段时间一直在做小程序,vue的知识都快忘了,刚好前端时间和朋友聊到了vue的源码,就对vue的模板编译做一下整理。查阅了很多资料和文档,尽量总结清晰出的逻辑路线,跟着思路很容易就能整明白(哈哈,为了方便理解特意手撸了一遍)。好了,废话不多说。开整!
前期准备:
这还需要准备?不,只需要创建两个文件就可以了。
- index.html // 用于挂载vue的实例
- vue.js // 用于自定义vue
html文件的内容如下:其中包含了v-text、v-bind、v-html等指令解析,以及v-on事件绑定、双大括号插值语法等。以便随时测试模板解析的效果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>{{person.name}}--{{person.age}}</h2>
<p>{{msg}}</p>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<div v-text="msg"></div>
<a v-bind:href="bindmsg">百度</a>
<div v-html="htmlStr"></div>
<input type="text" v-model='msg'>
<button v-on:click='handleClick'>点击</button>
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
person: {
name: '张三',
age: 30
},
msg: '实现代码编译',
bindmsg: 'www.baidu.com',
htmlStr: '<div>我是v-html渲染</div>'
},
methods:{
handleClick(){
console.log(555)
}
}
})
</script>
</body>
</html>
模板解析:
vue.js文件中开始创建模板解析(后续会在注释中说明):
class Vue {
constructor(options) {
// 储存数据到实例 options为HTML中new Vue({})时传入的配置对象
this.$options = options;
this.$data = options.data;
this.$el = options.el;
if (this.$el) {
//1. 实现一个数据观察者
// 暂时跳过
//2. 实现一个指令解析器
new Compile(this.$el, this);
}
}
}
class Compile {
constructor(el, vm) {
// 判断传入el是否为节点,若不是节点则获取
this.el = this.isElementNode(el) ? el : document.querySelector('#app');
this.vm = vm;
// 1. 获取文档碎片节点,放入内存中减少页面的回流和重绘
const fragment = this.node2Fragment(this.el);
// 2. 编译模板
this.compile(fragment);
// 3. 追加子元素到根元素
this.el.appendChild(fragment);
}
compile(fragment) {
// 1. 获取所有的子节点
const childNodes = fragment.childNodes;
[...childNodes].forEach(child => {
if (this.isElementNode(child)) {
// 元素节点
this.compileElement(child);
} else {
// 文本节点
this.compileText(child);
}
// 判断子节点中是否还有节点 递归调用
if (child.childNodes && child.childNodes.length) {
this.compile(child);
}
});
}
// 解析元素节点
compileElement(node) {
const attributes = node.attributes;
[...attributes].forEach((attr) => {
const { name, value } = attr;
if (!this.isDirective(name)) return;
const [, directive] = name.split('-');
const [dirName, eventName] = directive.split(':');
// 更新数据 数据驱动视图(compileUtil为下个模块定义了更新数据的方法)
compileUtil[dirName](node, value, this.vm, eventName);
// 删除指令属性
node.removeAttribute('v-' + directive);
});
}
// 解析文本节点
compileText(node) {
const content = node.textContent;
// 匹配插值语法
if (/\{\{.+?\}\}/.test(content)) {
//(compileUtil为下个模块定义了更新数据的方法)
compileUtil['text'](node, content, this.vm);
}
}
// 判断是否是vue指令
isDirective(name) {
return name.startsWith('v-');
}
// 判断是否是节点
isElementNode(node) {
return node.nodeType === 1;
}
// 把#app下所有的子节点放入文档碎片
node2Fragment(el) {
let firstChild;
const f = document.createDocumentFragment();
while (firstChild = el.firstChild) {
f.appendChild(firstChild);
}
return f;
}
}
const compileUtil = {
// 获取点式调用语法中的值 例如 a.b.c
getVal(expr, vm) {
return expr.split('.').reduce((p, c) => {
return p[c];
}, vm.$data);
},
// 处理v-text指令
text(node, expr, vm) {
let value;
if (expr.indexOf('{{') !== -1) {
value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(args[1], vm);
});
} else {
value = this.getVal(expr, vm);
}
this.updater.textUpdater(node, value);
},
// 处理v-html指令
html(node, expr, vm) {
const value = this.getVal(expr, vm);
this.updater.htmlUpdater(node, value);
},
// 处理v-model指令
model(node, expr, vm) {
const value = this.getVal(expr, vm);
this.updater.modelUpdater(node, value);
},
// 处理v-on指令
on(node, expr, vm, eventName) {
const value = this.getVal(expr, vm);
this.updater.onUpdater(node, value, eventName);
},
// 处理v-bind指令
bind(node, expr, vm, eventName) {
const value = this.getVal(expr, vm);
this.updater.bindUpdater(node, value, eventName);
},
// 操作原生dom的更新方法
updater: {
textUpdater(node, value) {
node.textContent = value;
},
htmlUpdater(node, value) {
node.innerHTML = value;
},
modelUpdater(node, value) {
node.value = value;
},
onUpdater(node, value, eventName) {
node.addevent;
},
bindUpdater(node, value, attrName) {
node.setAttribute(attrName, value);
},
}
};
vue的模板解析基本上就是这些了,逻辑很清晰,跟着思路来很容易就能理解。如果实在还是不明白,可以跟着敲一遍,看十遍不如写一遍,行动比思考更能让人印象深刻。基本上要说的也就是这些了,若文章中有什么不妥也可留言探讨、共同进步。 (撰文不易,点赞鼓励)
- 前端知识整理 ( 文档中有很多知识积累,相信对你会有更多的帮助哦!你会回来感谢我的!)