vue compile
super_wanan 人气:0概述
上篇文章我们已经介绍了Vue的响应式原理,并实现了对数据的监听,监听的目的是为了及时更新视图,所以这篇文章就来介绍下vue解析指令并初始化视图部分。
compile
在Vue的构造函数中对根元素进行编译
class MVue { constructor (options) { // 保存options this.$options = options this.$data = options.data // 将data进行响应式处理 observe(this.$data) // 代理 proxy(this) // 编译 /**核心部分**/ new Compile(options.el, this) } } class Compile { constructor (el, vm) { this.$vm = vm this.$el = document.querySelector(el) if (this.$el) { this.compile(this.$el) } } compile (node) { // 找到该元素的子节点 const childNodes = node.childNodes // childNodes是类数组对象 Array.from(childNodes).forEach(n => { // 判断类型 if (this.isElment(n)) { this.compileElement(n) // 递归 if (n.childNodes.length > 0) { this.compile(n) } } else if (this.isInter(n)) { // 动态插值表达式 编译文本 this.compileText(n) } }) } isElment (node) { return node.nodeType === 1 } isInter (n) { return n.nodeType === 3 && /\{\{(.*)\}\}/.test(n.textContent) } isDir (attrName) { return attrName.startsWith('m-') } isEvent (attrName) { return attrName.startsWith('@') } // 编译元素:遍历它的所有属性,看是否m-开头指令,或者@事件 compileElement (node) { const attrs = node.attributes Array.from(attrs).forEach(attr => { // m-text="XXX" // name = m-text, value=xxx const attrName = attr.name const exp = attr.value // 是指令 if (this.isDir(attrName)) { // 执行特定指令处理函数 const dir = attrName.substring(2) this[dir] && this[dir](node, exp) } else if (this.isEvent(attrName)) { //是事件 const event = attrName.substring(1) // 通过bind改变this指向为vm实例,以便方法内部访问vm.data中的数据 node.addEventListener(event, this.$vm.$methods[exp].bind(this.$vm)) } }) } compileText (n) { // 获取表达式 // n.textContent = this.$vm[RegExp.$1] n.textContent = this.$vm[RegExp.$1.trim()] } text (node, exp) { node.textContent = this.$vm[exp] || exp } html (node, exp) { node.innerHTML = this.$vm[exp] } model (node, exp) { node.value = this.$vm[exp] } }
测试代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <p>{{ counter }}</p> <p m-text="测试"></p> <p m-text="counter"></p> <p m-html="desc"></p> <input m-model="name" /> </div> </body> <script src="./index.js"></script> <script> const app = new MVue({ el: '#app', data: { counter: 1, desc: '<span style = "color: red">super wanan</span>', name: 'wanan' } }) // setInterval(() => { // app.counter++ // }, 1000) </script> </html>
结果
延伸及重点讲解
1. 类数组对象
node.childNodes和node.attributes都是一种类数组对象,NodeList用于保存一组有序的节点。可以通过方括号语法来访问NodeList的值,有item方法与length属性。它并不是Array的实例,没有数组对象的方法。
比如我们再compile方法中打印childNodes,会得到这样的结果:
因此需要使用Array.from()方法将类数组对象转换为真正数组。
2. RegExp.$1
RegExp 是javascript中的一个内置对象。为正则表达式。
RegExp.$1是RegExp的一个属性,指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串,以此类推,RegExp.$2,RegExp.$3,…RegExp.$99总共可以有99个匹配
其实RegExp这个对象会在我们调用了正则表达式的方法后, 自动将最近一次的结果保存在里面, 所以如果我们在使用正则表达式时, 有用到分组, 那么就可以直接在调用完以后直接使用RegExp.$xx来使用捕获到的分组内容。
所以我们在compileText时可以这样使用:
compileText (n) { // 获取表达式 // n.textContent = this.$vm[RegExp.$1] n.textContent = this.$vm[RegExp.$1.trim()] }
需要注意的是,上述代码中还使用了trim方法去除前后空格,这是为了将{{ counter }}前后的空格去掉,正确匹配到this.vm[‘counter’],否则会读取this.vm[’ counter ']导致读取不到。
3. nodeType
DOM(文档对象模型)可以将任何HTML和XML文档描绘成一个由多层node(节点)构成的结构。
DOM1级定义了一个Node接口,该接口将由DOM中的所有节点类型实现。这个Node接口在JavaScript中作为Node类型实现的。除了IE之外,在其他所有浏览器中都可以访问到这个类型。JavaScript中的所有节点类型都继承自Node类型,因此所有节点类型都共享相同的基本属性和方法。每个节点都有一个nodeType属性,用于表明节点的类型。
nodeType的取值如下:
类型 | nodeType常数值 | 描述 |
---|---|---|
Node.ELEMENT_NODE | 1 | 元素节点 |
Node.ATTRIBUTE_NODE | 2 | 属性节点 |
Node.TEXT_NODE | 3 | 文本节点 |
Node.CDATA_SECTION_NODE | 4 | 字符数据节点(文本不会被解析器解析) |
Node.ENTITY_REFERENCE_NODE | 5 | 实体引用节点 |
Node.ENTITY_NODE | 6 | 实体节点 |
Node.PROCESSING_INSTRUCTION_NODE | 7 | 处理指令节点 |
Node.COMMENT_NODE | 8 | 注释节点 |
Node.DOCUMENT_NODE | 9 | 文档节点(DOM树的根节点) |
Node.DOCUMENT_TYPE_NODE | 10 | 向为文档定义的实体提供接口 |
Node.DOCUMENT_FRAGMENT_NODE | 11 | 表示邻接节点和它们的子树 |
Node.NOTATION_NODE | 12 | 代表一个符号在DTD中的声明 |
加载全部内容