Created
March 13, 2017 14:53
-
-
Save Mrwaite/1c5bfbaf9daa0d8830fd67d8bc2e8540 to your computer and use it in GitHub Desktop.
vue-dataBind simple achieve
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 判断每个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