我们知道Vue可以实现数据双向绑定,Angular和Vue都是采用的MVVM 模式,意思就是当M(模型层)层数据进行修改时,VM层会监测到变化,并且通知V(视图层)层进行相应的修改,反之修改V层则会通知M层数据进行修改,实现了视图与模型层的相互解耦。其中Angular是采用的脏值检测实现的,Vue是采用的发布-订阅模式+数据劫持 实现的。
Vue是通过Object.defineProperty()
来实现对属性的劫持,达到监听数据变动的目的。当你把一个普通的 JavaScript 对象传给 Vue 实例的 data
选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
那么先看看Vue对数据的包装吧!使用 Object.getOwnPropertyDescriptor 查看数据属性描述。
var mydata={
name:'cc',
age:'2'
};
var app= new Vue({
el: '#app',
data: mydata
});
console.log(app.name===mydata.name);
console.log(Object.getOwnPropertyDescriptor(app,'name'));
console.log(Object.getOwnPropertyDescriptor(mydata,'name'));
控制台输出:
诶,可以看到确实Vue对data数据进行了处理,这个动作发生在 Vue生命周期的 beforeCreate 和 created 之间。(注意这里可以看到getter\setter的函数名不一样,这是因为将data对象进行数据劫持之后,要在vue实例对象上可访问,将根级响应式属性代理到了vue实例上)
可以试着使用一下Object.defineProperty ,在数据改变的时候得到响应。比如像这样:
var mydata={
name:'cc',
age:'2',
like:{
dog:'二狗子',
cat:'小哈'
}
};
//递归地设置
function DataProcess(data)
{
if (!data || typeof data !== 'object')
return;
Object.keys(data).forEach(function(item){
definePro(data,item,data[item])
}
);
}
function definePro(data,item,val)
{
DataProcess(val);
Object.defineProperty(data,item,{
enumerable: true,
configurable: true,
get: function getter () {
return val;
},
set: function setter (newVal) {
if(val!=newVal)
{
val=newVal;
console.log('数据发生了改变!新值为'+newVal);
}
}
});
}
DataProcess(mydata);
mydata.like.dog='eclipse'; //输出:数据发生了改变!新值为eclipse
mydata.like.dog='eclipse'; //这里无输出
实现过程:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
Observer是一个数据监听器,其实现核心方法就是前文所说的Object.defineProperty( )。如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行Object.defineProperty( )处理。
2.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
Compile在vue中这个动作发生在 Vue生命周期的 created 和 mounted之间
3.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:在自身实例化时往属性订阅器(dep)里面添加自己,自身必须有一个update()方法,待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#app{
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<input type="text" v-model="msg"/>
<div>
{{msg}}
</div>
<div>
<button v-on:click="change">click it!</button>
</div>
</div>
<script>
// myVue
function MyVue(options) {
var self = this;
this.data = options.data;
this.methods = options.methods;
//设置代理
Object.keys(this.data).forEach(function(key) {
self.proxyKeys(key);
});
observe(this.data);
new Compile(options.el, this);
}
MyVue.prototype = {
proxyKeys: function (key) {
var self = this;
Object.defineProperty(this, key, {
enumerable: false,
configurable: true,
get: function proxyGetter () {
return self.data[key];
},
set: function proxySetter (newVal) {
self.data[key] = newVal;
}
});
}
};
//observer
function observe(value, vm) {
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
};
function Observer(data) {
this.data = data;
this.walk(data);
}
Observer.prototype = {
walk: function(data) {
var self = this;
Object.keys(data).forEach(function(key) {
self.defineReactive(data, key, data[key]);
});
},
defineReactive: function(data, key, val) {
var dep = new Dep();
var childObj = observe(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
if (Dep.target) {
dep.addSub(Dep.target);
}
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) {
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();
});
}
};
Dep.target = null;
//compile
function Compile(el, vm) {
this.vm = vm;
this.el = document.querySelector(el);
this.fragment = null;
this.init();
}
Compile.prototype = {
init: function () {
if (this.el) {
this.fragment = this.nodeToFragment(this.el);
this.compileElement(this.fragment);
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;
},
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);
}
});
},
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)) {
var exp = attr.value;
var dir = attrName.substring(2);
if (self.isEventDirective(dir)) { // 事件指令
self.compileEvent(node, self.vm, exp, dir);
} else { // v-model 指令
self.compileModel(node, self.vm, exp, dir);
}
node.removeAttribute(attrName);
}
});
},
compileText: function(node, exp) {
var self = this;
var initText = this.vm[exp];
this.updateText(node, initText);
new Watcher(this.vm, exp, function (value) {
self.updateText(node, value);
});
},
compileEvent: function (node, vm, exp, dir) {
var eventType = dir.split(':')[1];
var cb = vm.methods && vm.methods[exp];
if (eventType && cb) {
node.addEventListener(eventType, cb.bind(vm), false);
}
},
compileModel: function (node, vm, exp, dir) {
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;
});
},
updateText: function (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
modelUpdater: function(node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
},
isDirective: function(attr) {
return attr.indexOf('v-') == 0;
},
isEventDirective: function(dir) {
return dir.indexOf('on:') === 0;
},
isElementNode: function (node) {
return node.nodeType == 1;
},
isTextNode: function(node) {
return node.nodeType == 3;
}
}
//watcher
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update: function() {
this.run();
},
run: function() {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function() {
Dep.target = this; // 缓存自己
var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
Dep.target = null; // 释放自己
return value;
}
};
//调用
var app= new MyVue({
el: '#app',
data:{
msg:'happy'
},
methods:{
change:function () {
this.msg='you click it!';
}
}
});
</script>
</body>
</html>
具体可以参考下面几篇:
其他参考: