MVVM是Model-View-ViewModel的简写,即模型-视图-视图模型。Model指的是后端传递的数据。View指的是所看到的页面。ViewModel是mvvm模式的核心,它是链接view和model的桥梁。它有两个方向:javascript
总结:在MVVM的框架下View和Model是不能直接通讯的。它们经过ViewModel来通讯,ViewModel一般要实现一个observer观察者,当数据发生变化,ViewModel可以监听到数据的这种变化,而后通知到对应的视图作自动更新,而当用户操做视图,ViewModel也能监听到视图的变化,而后通知数据作改动,这实际上就实现了数据的双向绑定。而且MVVM中的View 和 ViewModel能够互相通讯。MVVM流程图以下:vue
从前声明一个对象,并为其赋值,使用的如下的方式:java
var obj = {};
obj.name = 'hanson';
复制代码
可是从有了Object.defineProperty后,能够经过如下的方式为对象添加属性:node
var obj={};
Object.defineProperty(obj,'name',{
value:'hanson'
});
console.log(obj);//{}
复制代码
此时发现打印的结果为一个空对象,这是由于此时的enumerable属性默认为false,即不可枚举,因此加上enumerable后:后端
var obj={};
Object.defineProperty(obj,'name',{
enumerable: true,
value:'hanson'
});
console.log(obj);//{ name: 'hanson' }
obj.name = 'beauty';
console.log(obj)//{ name: 'hanson' }
复制代码
发现改变obj.name以后打印的仍是{name:'hanson'},这是由于此时writable为false,即不能够修改,因此加上writable后:设计模式
var obj={};
Object.defineProperty(obj,'name',{
writable :true,
enumerable: true,
value:'hanson'
});
console.log(obj);//{ name: 'hanson' }
obj.name = 'beauty';
console.log(obj)//{ name: 'beauty' }
delete obj.name;
console.log(obj);//{ name: 'beauty' }
复制代码
发现改变obj.name以后打印的是{name:'beauty'},这是由于此时configurable为false,即不能够删除,因此加上configurable后:数组
var obj={};
Object.defineProperty(obj,'name',{
configurable:true,
writable :true,
enumerable: true,
value:'hanson'
});
console.log(obj);//{ name: 'hanson' }
obj.name = 'beauty';
console.log(obj)//{ name: 'beauty' }
delete obj.name;
console.log(obj);//{}
复制代码
可是上面这样和普通的对象属性赋值没有区别,要想实现数据劫持必须使用set和get:bash
var obj={};
Object.defineProperty(obj,'name',{
configurable:true,
writable :true,
enumerable: true,
value:'hanson',
get(){
console.log('get')
return 'hanson'
},
set(newVal){
console.log('set'+ newVal)
}
});
console.log(obj);//{ name: 'hanson' }
obj.name = 'beauty';
console.log(obj)//{ name: 'beauty' }
delete obj.name;
console.log(obj);//{}
复制代码
此时发现会报错:TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute,由于出现set和get就不能有value或者writable,去掉以后:app
var obj={};
Object.defineProperty(obj,'name',{
configurable:true,//若是不涉及删除能够属性能够不加
enumerable: true,
get(){
console.log('get')
return 'hanson'
},
set(newVal){
console.log('set'+ newVal)
}
});
console.log(obj);//{ name: 'hanson' }
obj.name = 'beauty';
console.log(obj)//{ name: 'beauty' }
delete obj.name;
console.log(obj);//{}
复制代码
function myVue(options){//{el:'#app',data:{a:{a:3},b:5}}
this.$options = options;//将options挂载在vm.$options上
this._data = this.$options.data;//使用_data,后面会将data属性挂载到vm上
observe(this.$options.data);//数据劫持
}
var vm = new myVue({el:'#app',data:{a:{a:3},b:5}});
复制代码
function observe(data){
if(typeof data !== 'object'){//不是对象不进行数据劫持
return
}
return new Observe(data);
}
//将model->vm.data
function Observe(data){
for(let key in data){//遍历全部属性进行劫持
let val = data[key];
observe(val);//深刻递归数据劫持exp:data:{a:{a:3},b:5}}
Object.defineProperty(data,key,{
enumerable: true,
get(){
return val//此时的val已经进行了数据劫持,exp:{a:3}
},
set(newVal){
if(newVal === val ){//值不变则返回
return
}
val = newVal;
observe(newVal);//新赋的值也必须进行数据劫持
}
}
}
}
复制代码
function myVue(options){//{el:'#app',data:{a:{a:3},b:5}}
let self = this;
this.$options = options;
this._data = this.$options.data;
observe(this.$options.data);
for(let key in this._data){//会将data属性挂载到vm上,vm.a = {a:3}
Object.defineProperty(self,key,{
enumerable: true,
get(){
return self._data[key];
},
set(newVal){
self._data[key] = newVal;//会自动调用data某个属性的set方法,因此挂载data属性到vm上必须在劫持后执行
}
}
}
}
var vm = new myVue({el:'#app',data:{a:{a:3},b:5}});
conole.log(vm.a);//3
vm.a = 4;
console.log(vm.a);//4
复制代码
function myVue(options){//{el:'#app',data:{a:{a:3},b:5}}
let self = this;
this.$options = options;
this._data = this.$options.data;
observe(this.$options.data);
for(let key in this._data){
Object.defineProperty(self,key,{
enumerable: true,
get(){
return self._data[key];
},
set(newVal){
self._data[key] = newVal;
}
}
}
new Compile(options.el,this);//模板编译
}
//el—>vm.$el
function Compile (el, vm) {
vm.$el=document.querySelector(el);//将视图挂载到vm.$el上
let fragment = document.createDocumentFragment();
while(child = vm.$el.firstChild){
fragment.appendChild(child);//将全部的DOM移动到内存中操做,避免版没必要要DOM的渲染
}
function repalce(fragment){
Array.form(fragmrnt.childNodes).forEach(node=>{//将类数组转化为数组,而后遍历每个节点
let text=node.textContent,reg=/\{\{(.*)\}\}/;//获取节点的文本内容,并检测其中是否存在,exp:{{a.a}}
if(nodeType===3&&//reg.test(text)){
let arr=RegExp.$1.split('.'),val=vm;//分割RegExp.$1为a.a => [a,a]
arr.forEach(key=>val=val[key];);//vm => vm.a => vm.a.a=3
node.textContent=text.replace(reg,val);//替换{{a.a}} => 3
}
if(node.childNodes){//递归遍历全部的节点
replace(node)
}
})
}
replace(fragment);//模板替换,将{{xxxx}}替换成数据或者其余操做
vm.$el.appendChild(fragment);
}
复制代码
//发布者
function Dep () {
this.subs=[];
}
Dep.prototype.addSub=function (sub) {//添加订阅者
this.subs.push(sub)
};
Dep.prototype.notify=function () {//通知订阅者
this.subs.forEach((sub)=>sub.update())
};
//订阅者
function Watcher (vm,exp,fn) {
this.fn=fn;
}
Watcher.prototype.update=function () {//订阅者更新
this.fn();
};
复制代码
//el—>vm.$el
function Compile (el, vm) {
vm.$el=document.querySelector(el);
let fragment = document.createDocumentFragment();
while(child = vm.$el.firstChild){
fragment.appendChild(child);
}
function repalce(fragment){
Array.form(fragmrnt.childNodes).forEach(node=>{
let text=node.textContent,reg=/\{\{(.*)\}\}/;
if(nodeType===3&&//reg.test(text)){
let arr=RegExp.$1.split('.'),val=vm;
arr.forEach(key=>(val=val[key]););
node.textContent=text.replace(reg,val);
//建立一个订阅者用于更新视图
new Watcher(vm,RegExp.$1,function (newVal) {
node.textContent = text.replace(reg,newVal);
});
}
if(node.childNodes){
replace(node)
}
})
}
replace(fragment);//模板替换,将{{xxxx}}替换成数据或者其余操做
vm.$el.appendChild(fragment);
}
//Dep&&Watcher
function Dep () {
this.subs=[];
}
Dep.prototype.addSub=function (sub) {
this.subs.push(sub)
};
Dep.prototype.notify=function () {
this.subs.forEach((sub)=>sub.update())
};
function Watcher (vm,exp,fn) {//更新视图须要经过exp去获取数据,a.a
this.fn=fn;
this.vm=vm;
this.exp=exp;
Dep.target=this;
var arr=exp.split('.'),val=vm;
arr.forEach(key=>(val=val[key]););
Dep.target=null;
}
Watcher.prototype.update=function () {
var arr=this.exp.split('.'),val=this.vm;
arr.forEach(key=>(val=val[key]););//获取到更新后的值
this.fn(val);//更新视图
};
复制代码
//将model->vm.data
function Observe(data){
let dep = new Dep;//建立一个发布者,来存储全部的订阅者
for(let key in data){
let val = data[key];
observe(val);
Object.defineProperty(data,key,{
enumerable: true,
get(){
//添加订阅者,执行Observe的时候下面这行不执行,由于只用new Watcher时调用get时才会执行这行代码
Dep.target&&dep.addSub(Dep.target);
return val
},
set(newVal){
if(newVal === val ){
return
}
val = newVal;
observe(newVal);
dep.notify();//触发值的更新
}
}
}
}
//Dep&&Watcher
function Dep () {
this.subs=[];
}
Dep.prototype.addSub=function (sub) {
this.subs.push(sub)
};
Dep.prototype.notify=function () {
this.subs.forEach((sub)=>sub.update())
};
function Watcher (vm,exp,fn) {
this.fn=fn;
this.vm=vm;
this.exp=exp;
Dep.target=this;
var arr=exp.split('.'),val=vm;
arr.forEach(key=>(val=val[key]););//这里会调用vm.a的get和vm.a.a的get
Dep.target=null;
}
Watcher.prototype.update=function () {
var arr=this.exp.split('.'),val=this.vm;
arr.forEach(key=>(val=val[key]););//这里会调用vm.a.a的get和vm.a.a的get,可是Dep.target=null,不会再添加剧复添加这个订阅者
this.fn(val);
};
复制代码
function repalce(fragment){
Array.form(fragmrnt.childNodes).forEach(node=>{
let text=node.textContent,reg=/\{\{(.*)\}\}/;
if(nodeType===3&&//reg.test(text)){
let arr=RegExp.$1.split('.'),val=vm;
arr.forEach(key=>(val=val[key]););
node.textContent=text.replace(reg,val);
new Watcher(vm,RegExp.$1,function (newVal) {
node.textContent = text.replace(reg,newVal);
});
}
if(node.nodeType===1){//双向绑定通常为input,因此增长对DOM节点的处理
var attrs=node.attributes;
Array.from(attrs).forEach(function (attr) {//{name:'v-model',value:'a.a'}
var name=attr.name,exp=attr.value;//相似a.a
if(name.indexOf('v-')==0){//判断是否有v-model
node.value=vm[exp];//初次渲染DOM
node.addEventListener('input',function (e) {//监听input改变vm的值
var newVal=e.target.value;
vm[exp]=newVal
});
new Watcher(vm,exp,function (newVal) {//监听vm值更改view刷新
node.value=newVal;
});
}
})
}
if(node.childNodes){
replace(node)
}
})
}
复制代码
//computed将computed挂载在vm.computed属性上
function myVue(options){//{el:'#app',data:{a:{a:3},b:5}}
let self = this;
this.$options = options;
this._data = this.$options.data;
observe(this.$options.data);
for(let key in this._data){
Object.defineProperty(self,key,{
enumerable: true,
get(){
return self._data[key];
},
set(newVal){
self._data[key] = newVal;
}
}
}
initComputed.call(this);
new Compile(options.el,this);
}
function initComputed() {//computer:{c(){return this.a.a + this.b}}
var vm=this,computed=this.$options.computed;
Object.keys(computed).forEach(function (key) {
Object.defineProperty(vm,key,{
enumerable: true,
get:typeof computed[key]==='function'?computed[key]:computed[key].get
})
})
}
复制代码
但愿这篇文章可以让各位看官对Vue更熟悉,使用起来更顺手,若是以上有任何错误之处,但愿提出并请指正,若是对Vue使用还不清楚的朋友,请参考Vue官网教程,本文参考:框架