Skip to content

Instantly share code, notes, and snippets.

@Mrwaite
Created March 13, 2017 14:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Mrwaite/1c5bfbaf9daa0d8830fd67d8bc2e8540 to your computer and use it in GitHub Desktop.
Save Mrwaite/1c5bfbaf9daa0d8830fd67d8bc2e8540 to your computer and use it in GitHub Desktop.
vue-dataBind simple achieve
// 判断每个dom节点是否拥有子元素, 若有则返回该节点
function isChild(node){
if(node.childNodes.length == 0){
return false;
}
else {
return node;
}
}
//有个问题: 会遍历空白节点
//利用文档碎片劫持dom结构及数据, 进而进行dom的重构
function nodeToFragment(node, vm){
var frag = document.createDocumentFragment();
var child;
//这里对于while循环的理解
//之所以可以循环, 因为appendChild方法是如果被插入的节点已经存在于当前文档的文档树中,则那个节点会首先从原先的位置移除,然后再插入到新的位置.
//如果你需要保留这个子节点在原先位置的显示,则你需要先用Node.cloneNode方法复制出一个节点的副本,然后在插入到新位置.
while(child = node.firstChild){
//一级dom节点数据绑定
compile(child, vm);
//判断每一级dom节点是否有二级节点, 若有则递归梳理文档碎片
if(isChild(child)){
nodeToFragment(isChild(child), vm);
}
frag.appendChild(child);
}
//将文档碎片编译完成之后重新添加到node节点下面
node.appendChild(frag);
}
//初始化绑定数据
function compile(node, vm){
//node节点是元素节点时候
if(node.nodeType === 1){
var attr = node.attributes;
//遍历当前节点的所有属性
for(var i = 0;i < attr.length;i++){
//如果属性是v-model
if(attr[i].nodeName == 'v-model'){
//特殊处理input标签
var name = attr[i].nodeValue;
if(node.nodeName === 'INPUT'){
node.addEventListener('keyup', (e) => {
vm[name] = e.target.value;
})
}
//将data下面的对应的值赋给当前节点
node.value = vm[name];
//完成后删除v-model属性
node.removeAttribute(attr[i].nodeName);
//还要把数据绑定加上去
}
}
}
//node节点是text文本节点时候
if(node.nodeType === 3){
var reg = /\{\{(.*)\}\}/;
if(reg.test(node.nodeValue.trim())){
//将正则匹配到的{{}}中的字符串取出来
var name = RegExp.$1;
//把name对应的data中的值赋值相应节点
node.nodeValue = vm[name];
//为每个节点建立订阅者, 通过订阅者watcher初始化及更新视图数据
new Watcher(vm, node, name);
}
}
}
//订阅者(为每个节点的数据简历watcher队列,每次接受更新数据需求后,利用劫持数据执行对应节点的数据更新)
function Watcher(vm, node, name){
//将每个挂载了数据的dom节点添加到通知者列表, 要保证每次创建watcher时候只有一个添加目标,否则后面会因为是全局而被覆盖,所以每次都要清空目标.
Dep.target = this;
this.vm = vm;
this.node = node;
this.name = name;
//执行update的时候会调用坚挺着劫持的getter事件,,从而添加到watcher队列,因为update中有访问this.vm[this.name]
this.update();
//为保证只有一个全局的watcher, 添加到队列后,清空全局的watcher
Dep.target = null;
}
Watcher.prototype = {
update (){
this.get();
//input标签特殊处理
if(this.node.nodeName === 'INPUT'){
this.node.value = this.value;
}
else{
this.node.nodeValue = this.value;
}
},
get (){
//这里调用数据劫持的getter
this.value = this.vm[this.name]
}
};
//通知者(将监听者的更改需求发送给订阅者,告诉订阅者那些数据需要更新)
function Dep(){
this.subs = [];
}
Dep.prototype = {
addSub (watcher){
//添加用到的数据的节点进入watcher队列
this.subs.push(watcher);
},
notify (){
//遍历watcher队列,令相应数据节点更新view层数据, model=>view
this.subs.forEach((watcher) => {
watcher.update();
})
}
};
//监听者(利用setter监听view => model的数据变化,发出通知更改model数据后从而model=>view更新视图中所有用到该数据的地方)
function observer(data, vm){
//遍历劫持data下所有属性
Object.keys(data).forEach((key) => {
defindeReactive(vm, key, data[key]);
})
}
function defindeReactive(vm, key, val){
//新建通知者
var dep = new Dep();
//灵活使用setter与getter访问器属性
Object.defineProperty(vm, key, {
get (){
//初始化数据更新时将每个数据的watcher添加至队列栈中
if(Dep.target) dep.addSub(Dep.target);
return val;
},
set (newVal){
if(val === newVal) return;
//初始化后, 文档碎片中的虚拟dom已与model层数据绑定在一起了
val = newVal;
//同步更新model中的data数据
vm.data[key] = val;
//数据有改动向通知者发送通知
dep.notify();
}
})
}
//MVVM构造函数
function Vue(options){
this.id = options.el;
this.data = options.data;
observer(this.data, this);
//将根节点和实例化的对象作为参数传入
nodeToFragment(document.getElementById(this.id), this);
}
//实例化
var vm = new Vue({
el: 'app',
data: {
msg: 'hello, world!',
test: 'test key'
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment