在實現雙向數據綁定的方法中主要有如下幾種方式:
(1)發佈訂閱者模式(backbone.js)
(2)髒檢查(angular.js)
(3)數據劫持
在vue中是結合了(1)和(3)兩種方式來實現的,主要通過Object.defineProperty()方式進行數據劫持,然後訂閱發佈者模式監測數據的變化並進行廣播。所謂的雙向數據綁定無非是在單向數據綁定的基礎上,利用change(H5中input)事件監聽表單元素的變化,在事件回調函數中修改vue中的屬性的值。
可以參考下面的文章:文章裏介紹的比較詳細
下面附上自己實現的代碼(本代碼中只實現了v-model指令的匹配):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>雙向數據綁定</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text">
{{ text }}
</div>
<script type="text/javascript">
/*幾種實現雙向數據綁定的方式
* (1)發佈訂閱者模式(backbone.js)
* (2)髒檢查 (angular.js)
* (3)數據劫持(vue.js)
* vue中則是利用的發佈訂閱者和數據劫持Object.defineProperty()相結合的方式來實現雙向數據綁定
* * */
function Vue(){
this.options = arguments[0];
data = this.options.data;
observe(data,this);
var id = this.options.el;
var dom = nodeToFragment(document.getElementById(id),this);
document.getElementById(id).appendChild(dom)
}
function nodeToFragment(node,vm){
var nodeFragment = document.createDocumentFragment();
var child;
while(child = node.firstChild){
compile(child,vm);
nodeFragment.append(child);
}
return nodeFragment;
}
function compile(node,vm) {
var reg = /\{\{(.*)\}\}/;
if(node.nodeType === 1){
var attr = node.attributes;/*獲取所有的屬性節點*/
for(var i=0;i<attr.length;i++){
if(attr[i].nodeName === "v-model"){
var name = attr[i].nodeValue; // 獲取v-model綁定的屬性名
node.addEventListener("input",function (e) {
vm[name] = e.target.value;
});
node.value = vm[name];
node.removeAttribute("v-model");
}
}
new Watcher(vm,node,name,'input');
}
if(node.nodeType === 3){
if(reg.test(node.nodeValue)){
var name = RegExp.$1;
name = name.trim();
new Watcher(vm,node,name,"text");
}
}
}
function observe(data,vm) {
if(!data || typeof data !== "object")
{
return;
}
Object.keys(data).forEach(function (key) {
defineReactive(vm,key,data[key]);
})
}
function defineReactive(obj,key,val) {
var dep =new Dep();
if(typeof val ==="object")
{
observe(val);
}
Object.defineProperty(obj,key,{
get:function () {
Dep.target && dep.addSub(Dep.target);
return val;
},
set:function (newVal) {
if(val === newVal){
return;
}
val = newVal;
dep.notify();
}
})
}
function Dep() {
this.subs = [];
}
Dep.prototype ={
addSub:function (sub) {
this.subs.push(sub);
},
notify:function () {
this.subs.forEach(function (sub) {
sub.update();
})
}
}
function Watcher (vm, node, name, nodeType) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.nodeType = nodeType;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function () {/*在實際的vue當中不會直接執行update方法,會將監聽到的變化壓入到堆棧中,在一定的時機之後在執行,進行一步優化,防止過於頻繁的操作dom*/
this.get();
if (this.nodeType == 'text') {
this.node.nodeValue = this.value;
}
if (this.nodeType == 'input') {
this.node.value = this.value;
}
},
// 獲取data中的屬性值
get: function () {
this.value = this.vm[this.name]; // 觸發相應屬性的get
}
}
var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
}
});
</script>
</body>
</html>