//判断节点是否相同 functioncheckSameVnode(a, b) { return (a.sel == b.sel && a.key == b.key) } /* 函数作用是对比新旧节点的子节点内容,并对旧节点进行更新 接收三个参数:父节点,旧的子节点,新的子节点 */ exportdefaultfunctionupdateChildren(parentElm, oldCh, newCh) { console.log(parentElm, oldCh, newCh); //旧前指针 let oldStartIdx = 0 //新前指针 let newStartIdx = 0 //旧后指针 let oldEndIdx = oldCh.length - 1 //新后指针 let newEndIdx = newCh.length - 1
//旧前节点 let oldStartVnode = oldCh[oldStartIdx] //新前节点 let newStartVnode = newCh[newStartIdx] //旧后节点 let oldEndVnode = oldCh[oldEndIdx] //新后节点 let newEndVnode = newCh[newEndIdx]
exportdefaultfunctionupdateChildren(parentElm, oldCh, newCh) { //旧前指针 let oldStartIdx = 0 //新前指针 let newStartIdx = 0 //旧后指针 let oldEndIdx = oldCh.length - 1 //新后指针 let newEndIdx = newCh.length - 1
//旧前节点 let oldStartVnode = oldCh[oldStartIdx] //新前节点 let newStartVnode = newCh[newStartIdx] //旧后节点 let oldEndVnode = oldCh[oldEndIdx] //新后节点 let newEndVnode = newCh[newEndIdx]
exportdefaultfunctionupdateChildren(parentElm, oldCh, newCh) { //旧前指针 let oldStartIdx = 0 //新前指针 let newStartIdx = 0 //旧后指针 let oldEndIdx = oldCh.length - 1 //新后指针 let newEndIdx = newCh.length - 1
//旧前节点 let oldStartVnode = oldCh[oldStartIdx] //新前节点 let newStartVnode = newCh[newStartIdx] //旧后节点 let oldEndVnode = oldCh[oldEndIdx] //新后节点 let newEndVnode = newCh[newEndIdx]
node2Fragment(el) { var child //创建一个虚拟节点对象 var fragment = document.createDocumentFragment() //将挂载容器中的第一个孩子赋值给child,因为是引用类型值,将child添加到fragment对象中会使el的原dom树中的第一个孩子节点下树, //循环一直到el中的所有子节点都被添加fragment中 while(child = el.firstChild) { fragment.appendChild(child) } return fragment }
compile(fragment) { console.log(fragment); //获取挂载容器的所有子节点 const childNodes = fragment.childNodes //保存一份this,因为在下面用到的箭头函数中this不一定指向这个函数的上下文 var self = this //捕获双大括号中内容 var reg = /\{\{(.+)\}\}/ childNodes.forEach(node => { console.log('node:',node)
if(node.nodeType == 1) { self.compileElement(node) } elseif(node.nodeType == 3){ let text = node.textContent if(reg.test(text)) { console.log('匹配成功'); //双括号中的内容,即双括号语法中的数据 let name = text.match(reg)[1] self.compileText(node, name)
} } }) }
compileElement(node) { //使用attributes可以得到dom节点上的属性, //它的方便之处在于得到的不是字符串类型的属性,而是属性列表 let nodeAttrs = node.attributes var self = this
//类数组对象转化成数组 Array.prototype.slice.call(nodeAttrs).forEach(attr => { //在这里对v-if等指令进行分析 //得到属性名和属性值 let attrName = attr.name let attrValue = attr.value if(attrName.indexOf('v-') == 0) { //如果是v-if等指令,那么我们需要得到'v-'之后的东西,来确定具体的指令操作 let dir = attrName.substring(2) if(dir == 'model') { let v = self.getVueVal(self.$vue, attrValue) node.value = v
newWatcher(self.$vue, attrValue, val => { node.value = val }) node.addEventListener('input', e => { var newVal = e.target.value self.setVueVal(self.$vue, attrValue, newVal) }) } } }) }
compileText(node, name) { //在Vue类的实例中寻找双大括号中的元素,并将他解析好 node.textContent = this.getVueVal(this.$vue, name) newWatcher(this.$vue, name, val => { node.textContent = val }) }
getVueVal(vue, exp) { var val = vue //这个属性可能需要连续点语法处理,例如a.m.n //我们就需要将他拆分开再使用中括号语法组合起来,因为js无法识别obj[a.m.n]的形式 exp = exp.split('.') exp.forEach(k => { val = val[k] })
return val }
setVueVal(vue, exp, value) { var val = vue exp = exp.split('.')
<script> console.log(Mustache); let data = { arr: [ {name: 'cqy', sex: 'man'}, {name: 'kyrie', sex: 'man'}, ] } let templateStr = ` <ul> {{#arr}} <li> <divclass="hd">{{name}}的基本信息</div> <divclass="bd">sex: {{sex}}</div> </li> {{/arr}} </ul> ` let domStr = Mustache.render(templateStr, data) console.log(domStr); let container = document.getElementById('container') container.innerHTML = domStr </script>
不进行循环,直接写入数据 模板语法: {{}}
1 2 3 4 5 6 7 8 9 10 11
<script> let data = { name: 'cqy', age: '20' } //使用{{}}模板语法 let templateStr = `my name is {{name}}, age is {{age}}` let domStr = Mustache.render(templateStr, data) let container = document.getElementById('container') container.innerHTML = domStr </script>
let templateStr = "<h2>My name is {{name}}, Age is {{age}}</h2>" //模拟简单的数据替换 functionmyRender(templateStr,data) { // /\{\{(\w+)\}\}/g 匹配整个{{}},并为双大括号内的数据设置捕获组 return templateStr.replace(/\{\{(\w+)\}\}/g, function(findStr, $1) { //findStr就是匹配到的整个{{}} $1是捕获组 console.log($1); return data[$1]; }) } let res = myRender(templateStr, data) console.log(res); </script>
例如:模板字符串 'my name is {{myName}}, age is {{age}}'要转化为tokens 主要过程就是:定义一个指针指向模板字符串的开头,然后向后面扫描,当指针指向第一个指定内容{时,返回在这之前扫描到的字符串,然后跳过{{`,接着扫描直到遇到指定内容`}`时,返回在这之前扫描到的字符串,然后跳过`}},重复以上步骤直到模板字符串末尾
exportdefaultfunctionparse(templateStr) { //定义指针 var index = 0 //剩余字符串 var rest = '' //开始标记 var startRegExp = /^\<([a-z]+[1-9]?)\>/ //结束标记 var endRegExp = /^\<\/([a-z]+[1-9]?)\>/ //定义两个栈 var stack1 = [] var stack2 = []
//扫描传入的整个字符串 while(index < templateStr.length - 1) { //设置rest为未被扫描的部分 rest = templateStr.substring(index) //识别到开始标签 if(startRegExp.test(rest)) { //获得标签名 let tag = rest.match(startRegExp)[1] console.log('开始标记:',tag);
exportdefaultfunctionparse(templateStr) { var index = 0 var rest = '' //开始标记 var startRegExp = /^\<([a-z]+[1-9]?)\>/ //结束标记 var endRegExp = /^\<\/([a-z]+[1-9]?)\>/ //文字标记 var wordRegExp = /^([^\<]+)\<\/([a-z]+[1-9]?)\>/
exportdefaultfunctionparse(templateStr) { var index = 0 var rest = '' //开始标记 var startRegExp = /^\<([a-z]+[1-9]?)\>/ //结束标记 var endRegExp = /^\<\/([a-z]+[1-9]?)\>/ //文字标记 var wordRegExp = /^([^\<]+)\<\/([a-z]+[1-9]?)\>/
//定义两个栈 var stack1 = [] var stack2 = [{'children': []}]
exportdefaultfunctionparse(templateStr) { var index = 0 var rest = '' //开始标记 var startRegExp = /^\<([a-z]+[1-9]?)(\s[^\<]+)?\>/ //结束标记 var endRegExp = /^\<\/([a-z]+[1-9]?)\>/ //文字标记 var wordRegExp = /^([^\<]+)\<\/([a-z]+[1-9]?)\>/
//定义两个栈 var stack1 = [] var stack2 = [{'children': []}]
while(index < templateStr.length - 1) { rest = templateStr.substring(index) if(startRegExp.test(rest)) { let tag = rest.match(startRegExp)[1] //这个时候的attrsStr不一定有内容,因为不是每个标签都有属性, //会在parseAttrsStr函数中进行判断 let attrsStr = rest.match(startRegExp)[2]
//解析开始标签上的属性 let attrs = parseAttrsStr(attrsStr) //console.log('attrs:',attrs);
exportdefaultfunctionparse(templateStr) { var index = 0 var rest = '' //开始标记 var startRegExp = /^\<([a-z]+[1-9]?)(\s[^\<]+)?\>/ //结束标记 var endRegExp = /^\<\/([a-z]+[1-9]?)\>/ //文字标记 var wordRegExp = /^([^\<]+)\<\/([a-z]+[1-9]?)\>/
//定义两个栈 var stack1 = [] var stack2 = [{'children': []}]
while(index < templateStr.length - 1) { rest = templateStr.substring(index) if(startRegExp.test(rest)) { let tag = rest.match(startRegExp)[1] let attrsStr = rest.match(startRegExp)[2] //解析开始标签上的属性 let attrs = parseAttrsStr(attrsStr) //把开始标记推入栈1 stack1.push(tag) //将空数组推入栈2 stack2.push({'tag': tag, 'children': [],'attrs': attrs}) console.log('开始标记:',tag);