Vue底层学习4——编译器框架搭建

vue

全手打原创,转载请标明出处:https://www.cnblogs.com/dreamsqin/p/15006455.html, 多谢,=。=~(如果对你有帮助的话请帮我点个赞啦)

作为一个Web前端开发人员,使用Vue框架进行项目开发已经有一阵子,掐指一算,是时候认真探索一下Vue的底层了,以前的了解比较偏理论,这一次打算在弄清基本原理的前提下自己手写Vue中的核心部分,也许这样我才敢说自己“深入理解”了Vue。上一篇完成发布订阅模式的编写,实现DepWatcher,但到目前为止涉及视图的部分都是预留的状态,原因是我们还缺乏一个解析视图代码的功能,从本篇开始手撸编译器~

为什么要进行编译?因为我们实际在书写Vue模板的时候加入了很多浏览器不认识的代码,所以需要进行额外的转换与处理。compile的核心逻辑是获取DOM、遍历DOM,遍历时找到{{}}格式的变量、每个DOM的属性,与此同时截获v-@开头的响应式指令。

为了方便我们手撸编译器,简化流程后如下图所示,后续编码建议结合下图看思路会更清晰哦~:

目标功能

老规矩,先上一个日常开发的例子,帮助我们搞清楚最终需要实现的目标,这里我重新创建了一个demo2的html文件:

<!-- demo2.html -->

<!DOCTYPE html>

<html lang="zh-cn">

<head>

<meta charset="UTF-8">

<title>demo2</title>

</head>

<body>

<div >

<p>{{name}}</p>

<p v-text="name"></p>

<p>{{location}}</p>

<p>

{{locationAgain}}

</p>

<input type="text" v-model="name" />

<button @click="changeName">改名儿</button>

<div v-html="html"></div>

</div>

<script src="compile.js"></script>

<script src="MVue.js"></script>

<script>

const app = new MVue({

el: '#app',

data: {

name: 'dreamsyang',

location: 'chongqing',

html: '<button>这是一个按钮</button>'

},

created() {

console.log('开始啦');

setTimeout(() => {

this.name = '我是测试';

}, 1500);

},

methods: {

changeName() {

this.name = 'hello, dreamsyang!';

this.location = 'oh, chongqing!';

}

}

})

</script>

</body>

</html>

根据上面的例子汇总3个目标:

  • 目标一:插值绑定,也就是{{}}中的变量绑定,例如{{name}}{{location}}{{locationAgain}}
  • 目标二:指令解析,也就是v-开头的Dom属性,例如v-textv-model(涉及双向绑定的实现)、v-html(涉及html内容解析);
  • 目标三:事件的处理,也就是@开头的Dom属性,例如@click

编译器框架搭建

获取Dom

首先创建一个文件compile.js,也就是目标例子中引入的编译器,主要接收两个参数:el:需要解析的Dom元素选择器,vm:当前的Vue实例。

/*** compile.js ***/

// new Compile(el, vm)

class Compile{

constructor(el, vm) {

// 需要遍历的Dom节点

this.$el = document.querySelector(el);

// 数据缓存

this.$vm = vm;

}

}

遍历子节点

  • 如果获取的Dom节点存在就进行子节点内容提取

    通过document.createDocumentFragment将元素附加到文档片段,因为文档片段存在于内存中,并不在Dom树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算),方便后续编译,减少Dom操作,提高性能。

/*** compile.js ***/

// new Compile(el, vm)

class Compile{

constructor(el, vm) {

// 需要遍历的Dom节点

this.$el = document.querySelector(el);

// 数据缓存

this.$vm = vm;

// 编译

if (this.$el) {

// 提取指定节点中的内容,提高效率,减少Dom操作

this.$fragment = this.node2Fragment(this.$el);

}

// 提取指定Dom节点中的代码片段

node2Fragment(el) {

const fragment = document.createDocumentFragment();

// 将el中的所有子元素移动至fragment中

let child = null;

while(child = el.firstChild) {

fragment.appendChild(child);

}

return fragment;

}

}

  • 遍历并判断子节点类型为节点还是插值文本

    编译前先遍历子节点并配合节点的nodeType属性判断节点类型,然后针对不同类型进行对应的编译处理。

/*** compile.js ***/

// new Compile(el, vm)

class Compile{

constructor(el, vm) {

// 需要遍历的Dom节点

this.$el = document.querySelector(el);

// 数据缓存

this.$vm = vm;

// 编译

if (this.$el) {

// 提取指定节点中的内容,提高效率,减少Dom操作

this.$fragment = this.node2Fragment(this.$el);

// 执行编译

this.compile(this.$fragment);

// 将编译完的html追加至$el

this.$el.appendChild(this.$fragment);

}

}

// 提取指定Dom节点中的代码片段

node2Fragment(el) {...}

// 编译过程

compile(el) {

const childNodes = el.childNodes;

Array.from(childNodes).forEach(node => {

// 类型判断

if (this.isElement(node)) {

// 节点

console.log('编译节点' + node.nodeName);

} else if(this.isInterpolation(node)) {

// 插值文本

console.log('编译插值文本' + node.textContent);

}

// 递归子节点

if (node.childNodes && node.childNodes.length > 0) {

this.compile(node);

}

})

}

isElement(node) {

return node.nodeType === 1;

}

isInterpolation(node) {

return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);

}

}

  • demo2中测试一下

    先去掉MVue.jsconstructor中之前模拟Watcher的部分,因为后续属性的getter激活会加入到编译器中,接着初始化一个Compile实例,并将需要解析的Dom元素选择器以及当前的Vue实例作为参数传递进去。

/*** MVue.js ***/

// new MVue({ data: {...} })

class MVue {

constructor(options) {

// 数据缓存

this.$options = options;

this.$data = options.data;

// 数据遍历

this.observe(this.$data);

new Compile(options.el, this);

}

}

运行结果如下,可以看到,我们想要根据不同的节点类型做区别编译的分流已经实现,后续就是实打实的编译操作,且听下回分解:

参考资料

1、Document.createDocumentFragment():https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createDocumentFragment;

2、Vue源码:https://github.com/vuejs/vue;

以上是 Vue底层学习4——编译器框架搭建 的全部内容, 来源链接: utcz.com/z/378203.html

回到顶部