大家好,今天我给大家讲解一下Vue中模板编译是如何实现的。
1. 首先我们先创建一个Vue的构造函数,在Vue中,如果有 el 的值我们就 new 一个 Compile 模板的实例,当然这个实例还没有创建哈!
class Vue{ constructor(options) { this.$el = options.el; this.$data = options.data; //这个根元素存在则编译模板 if(this.$el){ //模板编译 new Compiler(this.$el,this); } } }
2. 这个模板编译呢,主要是有这样几步
接下来就让我们创建一个 Compile 模板的构造函数吧!
class Compile{ constructor(el,vm) { this.vm = vm; //判断el属性 是不是一个元素 不是就获取 this.el = this.isElementNode(el) ? el : document.querySelector(el); console.log(this.el); //把当前的节点元素 获取到 放到内存中 创建文档碎片 let fragment = this.node2fragment(this.el); //模板编译 用数据编译 this.compile(fragment); //把内容在塞到页面中 this.el.appendChild(fragment); } }
上面的呢我们是不是用到了几个方法,当然了,这个方法也还没有写呢。。。
这几个方法呢,都是Compile的原型上的! 我们来写一下啦!
//获取所有元素,放到内存中 node2fragment(node){ //创建一个文档碎片 let fragment = document.createDocumentFragment(); let firstChild; //将node节点的的第一个节点给firstChild 如果node节点的的第一个节点为空则结束 while(firstChild = node.firstChild){ //appendChild具有移动性 fragment.appendChild(firstChild); } return fragment; } // 是不是元素节点 isElementNode(node){ return node.nodeType === 1; }
下面这个方法呢,就是模板的核心方法啦,用它来实现数据编译
//用来编译内存中的dom节点 核心方法 compile(node){ let childNodes = node.childNodes; //获取node的所有子节点 [...childNodes].forEach(child=>{ if(this.isElementNode(child)){ //判断是不是元素节点 this.compileElement(child); //编译元素指令 //如果是元素节点的话 需要把自己传不进去 再去遍历子元素节点 this.compile(child); }else{ //文本元素 this.compileText(child); //编译文本指令 } }) }
这个几个方法就是 compile 这个核心方法所用的方法!
CompileUtil 是全局对象对象,分别储存对应着不同的方法在下面将会创建
//判断属性是不是以 v- 开头 isDirective(attrName){ return attrName.startsWith(‘v-‘); // return /^v-/.test(attrName) } //编译元素的 compileElement(node){ let attributes = node.attributes; //类数组, 获取所有node节点的属性和属性值 attributes = [...attributes] //console.log(attributes) attributes.forEach(attr=>{ //是一个属性对象attr let {name, value:expr} = attr; //:expr是给value起一个别名叫 expr **school.name //判断是不是vue指令 if(this.isDirective(name)){ let [,directive] = name.split(‘-‘); let [directiveName, eventName] = directive.split(‘:‘); //需要调用不同的指令来处理 *** v-if v-modle v-show v-else CompileUtil[directiveName](node,expr,this.vm,eventName); } }) } //编译文本的 compileText(node){ //判断节点中是否包含 {{}} let content = node.textContent; if(/\{\{.+?\}\}/.test(content)){ CompileUtil[‘text‘](node,content,this.vm); } }
因为 vue 中的指令不同,所以我们要调用不同的方法,这里呢,就创建了一个 CompileUtil 的全局对象对象,分别储存对应着不同的方法
CompileUtil = { //根据表达式获取对应的数据 getVal(vm,expr){ // reduce() 方法 // 函数的参数 (第一参数)1.相加的初始值,2.循环出来的那一项,3.索引 4.循环的数组 // (第二个参数)初始值 // 返回值:总和的结果 return expr.split(‘.‘).reduce((data,current)=>{ //[school,name] return data[current]; },vm.$data); }, model(node,expr,vm){ //node是节点 expr是表达式 vm是实例 let fn = this.updater[‘modelUpdater‘]; let value = this.getVal(vm,expr); fn(node,value); }, html(node,expr,vm,eventName){ let fn = this.updater[‘htmlUpdater‘] let value = this.getVal(vm,expr); fn(node,value); }, text(node,expr,vm){ let fn = this.updater[‘textUpdater‘] //console.log(expr) :{{ school.name }} let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{ //console.log(args) :["{{ school.name }}", " school.name ", 0, "{{ school.name }}"] return this.getVal(vm,args[1].trim()); }) // console.log(content) fn(node,content); }, updater: { //把数据插入到value中 modelUpdater(node,value){ node.value = value; }, htmlUpdater(node,value){ node.innerHTML = value; }, //处理文本节点 textUpdater(node,value){ //textContent 属性设置或返回指定节点的文本内容,以及它的所有后代。 node.textContent = value; } } }
这样的话,我们的模板编译就完成啦!复制代码去试一下吧!
下面这个是一个模板编译的整体代码
/** * 模板编译 */ class Compiler{ constructor(el,vm) { this.vm = vm; //判断el属性 是不是一个元素 不是就获取 this.el = this.isElementNode(el) ? el : document.querySelector(el); console.log(this.el); //把当前的节点元素 获取到 放到内存中 创建文档碎片 let fragment = this.node2fragment(this.el); //把节点中的内容进行替换 //模板编译 用数据编译 this.compile(fragment); //把内容在塞到页面中 this.el.appendChild(fragment); } //判断属性是不是以 v- 开头 isDirective(attrName){ return attrName.startsWith(‘v-‘); // return /^v-/.test(attrName) } //编译元素的 compileElement(node){ let attributes = node.attributes; //类数组, 获取所有node节点的属性和属性值 attributes = [...attributes] //console.log(attributes) attributes.forEach(attr=>{ //是一个属性对象attr let {name, value:expr} = attr; //:expr是给value起一个别名叫 expr **school.name //判断是不是vue指令 if(this.isDirective(name)){ let [,directive] = name.split(‘-‘); let [directiveName, eventName] = directive.split(‘:‘); //需要调用不同的指令来处理 *** v-if v-modle v-show v-else CompileUtil[directiveName](node,expr,this.vm,eventName); } }) } //编译文本的 compileText(node){ //判断节点中是否包含 {{}} let content = node.textContent; if(/\{\{.+?\}\}/.test(content)){ CompileUtil[‘text‘](node,content,this.vm); } } //用来编译内存中的dom节点 核心方法 compile(node){ let childNodes = node.childNodes; //获取node的所有子节点 [...childNodes].forEach(child=>{ if(this.isElementNode(child)){ //判断是不是元素节点 this.compileElement(child); //编译元素指令 //如果是元素节点的话 需要把自己传不进去 再去遍历子元素节点 this.compile(child); }else{ //文本元素 this.compileText(child); //编译文本指令 } }) } //获取所有元素,放到内存中 node2fragment(node){ //创建一个文档碎片 let fragment = document.createDocumentFragment(); let firstChild; //将node节点的的第一个节点给firstChild 如果node节点的的第一个节点为空则结束 while(firstChild = node.firstChild){ //appendChild具有移动性 fragment.appendChild(firstChild); } return fragment; } // 是不是元素节点 isElementNode(node){ return node.nodeType === 1; } } CompileUtil = { //根据表达式获取对应的数据 getVal(vm,expr){ // 7. reduce() 方法 // 函数的参数 (第一参数)1.相加的初始值,2.循环出来的那一项,3.索引 4.循环的数组 // (第二个参数)初始值 // 返回值:总和的结果 return expr.split(‘.‘).reduce((data,current)=>{ //[school,name] // console.log(data,current) return data[current]; },vm.$data); }, model(node,expr,vm){ //node是节点 expr是表达式 vm是实例 console.log(node) let fn = this.updater[‘modelUpdater‘]; let value = this.getVal(vm,expr); // console.log(value) fn(node,value); }, html(node,expr,vm){ let fn = this.updater[‘htmlUpdater‘] let value = this.getVal(vm,expr); fn(node,value); }, text(node,expr,vm){ let fn = this.updater[‘textUpdater‘] //console.log(expr) :{{ school.name }} let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{ //console.log(args) :["{{ school.name }}", " school.name ", 0, "{{ school.name }}"] return this.getVal(vm,args[1].trim()); }) // console.log(content) fn(node,content); }, updater: { //把数据插入到value中 modelUpdater(node,value){ node.value = value; }, htmlUpdater(node,value){ node.innerHTML = value; }, //处理文本节点 textUpdater(node,value){ //textContent 属性设置或返回指定节点的文本内容,以及它的所有后代。 node.textContent = value; } } } class Vue{ constructor(options) { this.$el = options.el; this.$data = options.data; //这个根元素存在则编译模板 if(this.$el){ //模板编译 new Compiler(this.$el,this); } } }
原文:https://www.cnblogs.com/nie5135257/p/12110027.html