补充指令剖析器compile
补充下HTML节点范例的学问:
元素节点 Node.ELEMENT_NODE(1)
属性节点 Node.ATTRIBUTE_NODE(2)
文本节点 Node.TEXT_NODE(3)
CDATA节点 Node.CDATA_SECTION_NODE(4)
实体援用称号节点 Node.ENTRY_REFERENCE_NODE(5)
实体称号节点 Node.ENTITY_NODE(6)
处置惩罚指令节点 Node.PROCESSING_INSTRUCTION_NODE(7)
解释节点 Node.COMMENT_NODE(8)
文档节点 Node.DOCUMENT_NODE(9)
文档范例节点 Node.DOCUMENT_TYPE_NODE(10)
文档片断节点 Node.DOCUMENT_FRAGMENT_NODE(11)
DTD声明节点 Node.NOTATION_NODE(12)
Compile指令剖析器,剖析DOM节点,直接牢固某个节点举行替代数据的
剖析模板指令,替代模板数据,初始化试图
将模板指令对应的节点绑定对应的更新函数,初始化对应的定阅器
起首须要获取到DOM元素,然后对含有DOM元素上含有指令的节点举行处置惩罚,
因而这个环节须要对DOM操纵比较频仍,一切能够先建一个fragment片断,
将须要剖析的DOM节点存入fragment片断里再举行处置惩罚:
//直接上代码:(先推断"{{}}")
function Compile(elm){// el->"#name" ,vm->{el:;data:;}
this.vm = elm;
this.el = document.querySelector(elm.el);
this.fragment = null;
this.init();
}
Compile.prototype = {
init:function(){
if(this.el) {
//将须要剖析的DOM节点存入fragment片断里再举行处置惩罚
this.fragment = this.nodeToFragment(this.el);
//接下来遍历各个节点,对含有指定的节点特别处置惩罚,先处置惩罚指令“{{}}”:
this.compileElement(this.fragment);
//绑定到el上
this.el.appendChild(this.fragment);
}else{
console.log('DOM元素不存在');
}
},
//建立代码片断
nodeToFragment:function(el){
var fragment = document.createDocumentFragment();
var child = el.firstChild;
while(child){//将DOM元素移入fragment
fragment.appendChild(child);
child = el.firstChild;
}
return fragment;
},
//对一切子节点举行推断,1.初始化视图数据,2.绑定更新函数的定阅器
compileElement:function(el){
var childNodes = el.childNodes;
var self = this;
[].slice.call(childNodes).forEach(function(node){
var reg = /\{\{(.*)\}\}/;//婚配" {{}} "
var text = node.textContent;
if(self.isTextNode(node) && reg.test(text)) {//推断" {{}} "
self.compileText(node,reg.exec(text)[1]);
}
if(node.childNodes && node.childNodes.length){
self.compileElement(node);//// 继承递归遍历子节点
}
});
},
//初始化视图updateText和天生定阅器:
compileText:function(node,exp){
var self = this;
var initText = this.vm[exp]; //代办接见self_vue.data.name1 -> self_vue.name1
this.updateText(node,initText);//将初始化的数据初始化到视图中
new Watcher(this.vm,exp,function(value){//{},name, // 天生定阅器并绑定更新函数
self.updateText(node,value);
})
},
updateText: function (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
isTextNode:function(node){
return node.nodeType == 3;//文本节点
}
};
为了将剖析器Compile与监听器Observer和定阅者Watcher关联起来,我们须要再修正一下类SelfVue函数:
// function SelfVue(data,el,exp){ //first
function SelfVue(options){
var self = this;
// this.data = data; //first
this.data = options.data;
this.el = options.el;
this.vm = this; //second
console.log(this)
Object.keys(this.data).forEach(function (key) {
self.proxyKeys(key);//绑定代办属性
});
//监听数据:
observers(this.data);
//first:
/*el.innerHTML = this.data[exp];//初始化模板数据的值
new Watcher(this,exp,function(value){//绑定定阅器
el.innerHTML = value;
});*/
//初始化视图updateText和天生定阅器
new Compile(this);
return this;
}
到这里,大括号”{{}}”范例的双向数据绑定完成;
补充上v-model和事宜指令:
在compileElement函数加上对其他指令节点举行推断,然后遍历其一切属性
增加事宜指令
增加一个v-model指令
Compile.prototype = {
init:function(){
if(this.el) {
//将须要剖析的DOM节点存入fragment片断里再举行处置惩罚
this.fragment = this.nodeToFragment(this.el);
//接下来遍历各个节点,对含有指定的节点特别处置惩罚,先处置惩罚指令“{{}}”:
this.compileElement(this.fragment);
//绑定到el上
this.el.appendChild(this.fragment);
}else{
console.log('DOM元素不存在');
}
},
//建立代码片断
nodeToFragment:function(el){
var fragment = document.createDocumentFragment();
var child = el.firstChild;
while(child){//将DOM元素移入fragment
fragment.appendChild(child);
child = el.firstChild;
}
return fragment;
},
//对一切子节点举行推断,1.初始化视图数据,2.绑定更新函数的定阅器
compileElement:function(el){
var childNodes = el.childNodes;
var self = this;
[].slice.call(childNodes).forEach(function(node){
var reg = /\{\{(.*)\}\}/;//婚配" {{}} "
var text = node.textContent;
/* 补充推断: */
if(self.isElementNode(node)){//元素节点推断
self.compile(node);
}else if(self.isTextNode(node) && reg.test(text)) {//文本节点推断 ,推断" {{}} "
self.compileText(node,reg.exec(text)[1]);
}
if(node.childNodes && node.childNodes.length){
self.compileElement(node);//// 继承递归遍历子节点
}
});
},
//初始化视图updateText和天生定阅器:
compileText:function(node,exp){
var self = this;
var initText = this.vm[exp]; //代办接见self_vue.data.name1 -> self_vue.name1
this.updateText(node,initText);//将初始化的数据初始化到视图中
new Watcher(this.vm,exp,function(value){//{},name, // 天生定阅器并绑定更新函数
self.updateText(node,value);
})
},
updateText: function (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
compile:function(node){
var nodeAttrs = node.attributes;
var self = this;
Array.prototype.forEach.call(nodeAttrs,function(attr){
var attrName = attr.name;
if(self.isDirective(attrName)){//查到" v- "
var exp = attr.value;
var dir = attrName.substring(2);//" v-on/v-model "
if(self.isEventDirective(dir)){ // 事宜指令
self.compileEvent(node,self.vm,exp,dir);
}else{
self.compileModel(node,self.vm,exp,dir);
}
node.removeAttribute(attrName);
}
})
},
compileEvent:function(node,vm,exp,dir) {//代码片断<><>,{data:;vm:;el:;},v-on="add",on:
var eventType = dir.split(':')[1];//on
var cb = vm.methods && vm.methods[exp];
if(eventType && cb){
node.addEventListener(eventType,cb.bind(vm),false);
}
},
compileModel:function(node,vm,exp,dir){//代码片断<><>,{data:;vm:;el:;},v-on="addCounts",model:
var self = this;
var val = this.vm[exp];
this.modelUpdater(node,val);
new Watcher(this.vm,exp,function(value){
self.modelUpdater(node,value);
});
node.addEventListener('input',function(e){
var newValue = e.target.value;
if(val === newValue){
return;
}
self.vm[exp] = newValue;
val = newValue;
})
},
modelUpdater:function(node,value){
node.value = typeof value == 'undefined' ? '' : value;
},
isTextNode:function(node){
return node.nodeType == 3;//文本节点
},
isElementNode:function(node){
return node.nodeType == 1;//元素节点<p></p>
},
isDirective:function(attr){//查找自定义属性为:v- 的属性
return attr.indexOf('v-') == 0;
},
isEventDirective:function(dir){ // 事宜指令
return dir.indexOf('on:') === 0
}
};
再革新下类SelfVue,使它更像Vue的用法:
function SelfVue(options){
var self = this;
this.data = options.data;
this.el = options.el;
this.methods = options.methods;
this.vm = this; //second
Object.keys(this.data).forEach(function (key) {
self.proxyKeys(key);//绑定代办属性
});
//监听数据:
observers(this.data);
//初始化视图updateText和天生定阅器
new Compile(this);
options.mounted.call(this);
return this;
}
测试:
<body>
<div id="app">
<h2>{{name1}}</h2>
<h2>{{name2}}</h2>
<input type="text" v-model="title">
<h3>{{title}}</h3>
<button v-on:click="clickMe">v-on事宜</button>
<h3>{{event}}</h3>
</div>
<script src="js-second/observer.js"></script>
<script src="js-second/watcher.js"></script>
<script src="js-second/compile.js"></script>
<script src="js-second/selfVue.js"></script>
<script>
var self_vue = new SelfVue({
el:"#app",
data:{
name1:'我是name1',
name2:'我是name2',
event:'event',
title:'title初始值'
},
methods:{
clickMe:function(){
this.event = '事宜从新赋值'
}
},
mounted:function(){
console.log('mounted')
}
});
/* window.setTimeout(function(){
self_vue.name1 = 'name1再次赋值'
},2000);
window.setTimeout(function(){
self_vue.name2 = 'name2再次赋值'
},3000)*/
</script>
</body>
系列文章的目次:
Vue双向绑定的完成道理系列(一):Object.defineproperty
Vue双向绑定的完成道理系列(二):设想形式
Vue双向绑定的完成道理系列(三):监听器Observer和定阅者Watcher
Vue双向绑定的完成道理系列(四):补充指令剖析器compile