实现Complie

模板的编译,即将模板中的指令进行编译。将模板中的data属性替换成data属性对应的值(比如将替换成data.name值),然后初始化渲染页面视图,并且为每个data属性添加一个监听数据的订阅者(new Watcher),一旦数据有变动,收到通知,更新视图。

遍历解析需要替换的根元素el下的HTML标签必然会涉及到多次的DOM节点操作,因此不可避免的会引发页面的重排或重绘,为了提高性能和效率,我们把根元素el下的所有节点转换为文档碎片fragment进行解析编译操作,解析完成,再将fragment添加回原来的真实dom节点中。

·         注:文档碎片本身也是一个节点,但是当将该节点append进页面时,该节点标签作为根节点不会显示html文档中,其里面的子节点则可以完全显示

  

function Compile(el,vm){
this.$vm = vm;//vm为当前实例
this.$el = document.querySelector(el);//获得要解析的根元素
if (this.$el){
this.$fragment = this.nodeToFragment(this.$el);
this.init();
this.$el.appendChild(this.$fragment);
}
}
Compile.prototype = {
nodeToFragment: function(el){
let fragment = document.createDocumentFragment();
let child;
while (child = el.firstChild){
fragment.appendChild(child);//append相当于剪切的功能
}
return fragment;

},
};

compileElement方法将遍历所有节点及其子节点,进行扫描解析编译,调用对应的指令渲染函数进行数据渲染,并调用对应的指令更新函数进行绑定,

因为我们的模板只含有一个文本节点#text,因此compileElement方法执行后会进入_this.compileText(node,reg.exec(node.textContent)[1]);//#text,'name'

Compile.prototype = {
nodeToFragment: function(el){
let fragment = document.createDocumentFragment();
let child;
while (child = el.firstChild){
fragment.appendChild(child);//append相当于剪切的功能
}
return fragment;

},

init: function(){
this.compileElement(this.$fragment);
},

compileElement: function(node){
let childNodes = node.childNodes;
const _this = this;
let reg = /\{\{(.*)\}\}/g;
[].slice.call(childNodes).forEach(function(node){

if (_this.isElementNode(node)){//如果为元素节点,则进行相应操作
_this.compile(node);
} else if (_this.isTextNode(node) && reg.test(node.textContent)){
//如果为文本节点,并且包含data属性(如),则进行相应操作
_this.compileText(node,reg.exec(node.textContent)[1]);//#text,'name'
}

if (node.childNodes && node.childNodes.length){
//如果节点内还有子节点,则递归继续解析节点
_this.compileElement(node);

}
})
},
compileText: function(node,exp){//#text,'name'
compileUtil.text(node,this.$vm,exp);//#text,vm,'name'
},
};

CompileText()函数实现初始化渲染页面视图(将data.name的值通过#text.textContent = data.name显示在页面上),并且为每个DOM节点添加一个监听数据的订阅者(这里是为#text节点新增一个Wather)。

let updater = {
textUpdater: function(node,value){
node.textContent = typeof value == 'undefined' ? '' : value;
},
}

let compileUtil = {
text: function(node,vm,exp){//#text,vm,'name'
this.bind(node,vm,exp,'text');
},

bind: function(node,vm,exp,dir){//#text,vm,'name','text'
let updaterFn = updater[dir + 'Updater'];
updaterFn && updaterFn(node,this._getVMVal(vm,exp));
new Watcher(vm,exp,function(value){
updaterFn && updaterFn(node,value)
});
console.log('加进去了');
}
};

整合MVVM

  

function MVVM(options) {
this.$options = options || {};
var data = this._data = this.$options.data;
var _this = this;
// 数据代理
// 实现 vm._data.xxx -> vm.xxx
Object.keys(data).forEach(function(key) {
_this._proxyData(key);
});
observe(data, this);
this.$compile = new Compile(options.el || document.body, this);
}