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官網教程,本文參考:框架