model - view - viewmodel
的縮寫,說都能直接說出來 model
:模型,view
:視圖,view-Model
:視圖模型
var dom = document.getElementById('xxx') dom.value = xxx; // 直接修改值 dom.innerHtml = xxx; //改變開始 和 結束標籤中的html
$('#name').text('Homer').css('color', 'red');
view
)和模型(model
)之間的關係再看看如今VUE框架中怎麼作到這種視圖和模型的聯動javascript
//html <input v-model = 'val' placeholder = 'edit here'> //script export defaults{ data:function(){ return { val:'' } } }
很簡單,很經常使用的v-model指令,那麼在input值修改的時候,data中的val變量值也會改變,直接在js中改變val的值的時候,input中的value也會改變??咱們作了什麼,咱們怎麼將數據和視圖聯繫起來的?自動會關聯這兩個東西css
可能,這就是VM吧~html
model
數據同步到view
顯示,也同時把view
修改的數據同步到model
,咱們無需關心中間的邏輯,開發者更多的是直接操做數據,至於更新視圖或者會寫model,都是咱們寫好的視圖模型(viewModel
)幫咱們處理概念:視圖模型層,是一個抽象化的邏輯模型,鏈接視圖(view
)和模型(model
),負責:數據到視圖的顯示,視圖到數據的回寫前端
vue框架中雙向綁定是最經常使用的一個實用功能。實現的方式也網上不少文章,vue2.x是Object.DefineProperty,vue3.x是Es6語法的proxy
代理語法vue
具體是怎麼作到的java
ps:暫時先看vue2.xjquery
第一步:監聽對象全部屬性值變化(Observer
)程序員
var data = {test: '1'}; observe(data); data.test = '2'; // changed 1 --> 2 function observe(data) { if (!data || typeof data !== 'object') { return; } // 取出全部屬性遍歷 Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); }); }; function defineReactive(data, key, val) { observe(val); // 監聽子屬性 Object.defineProperty(data, key, { enumerable: true, // 可枚舉 configurable: false, // 防止重複定義或者衝突 get: function() { return val; }, set: function(newVal) { console.log('changed ', val, ' --> ', newVal); val = newVal; } }); }
這裏是Observer做爲一個察覺數據變化的發佈者,發現數據變化時,觸發全部訂閱者(Watcher
)的更新update
事件,首先要擁有一個能存儲全部訂閱者隊列,而且能通知全部訂閱者的中間件(消息訂閱器Dep
)後端
function Dep () { // 訂閱者數組 this.subs = []; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { //通知全部訂閱者 this.subs.forEach(function(sub) { sub.update(); }); } };
而且在觀察者Observer
中修改當Object對象屬性發生變化時,觸發Dep
中的notify事件,全部訂閱者能夠接收到這個改變api
function defineReactive(data, key, val) { observe(val); var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: false, get: function() { return val; }, set: function(newVal) { //修改的在這裏 if(newVal === val){ return } // 若是新值不等於舊值發生變化,觸發全部訂閱中間件的notice方法,全部訂閱者發生變化 val = newVal console.log('changed ', val, ' --> ', newVal); dep.notify(); } }); }
可是有沒有發現還有一個問題,Dep訂閱中間件中的訂閱者數組一直是空的,何時把訂閱者添加進來咱們的訂閱中間件中間,哪些訂閱者須要添加到咱們的中間件數組中
function Watcher(vm, exp, cb) { this.cb = cb; // 構造函數中執行,只有可能在實例化的時候執行一遍 this.vm = vm; this.exp = exp; this.value = this.get(); // 將本身添加到訂閱器的操做---HACK開始 // 在構造函數中調用了一個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() { //get方法中首先緩存了本身自己到target屬性 Dep.target = this; // 獲取了一下Observer中的值,至關於調用了一下get方法 var value = this.vm.data[this.exp] // get 完成以後清除了本身的target屬性??? Dep.target = null; return value; } //很明顯,get方法只在實例化的時候調用了,知足了只有在Watcher實例化第一次的時候調用 //update方法接收了發佈者的notice 發佈消息,而且執行回調函數,這裏的回調函數仍是經過外部定義(簡化版) //可是,好像在get方法中有一個很神奇的操做,緩存本身,而後調用Observer的getter,而後清除本身 //這裏實際上是一步巧妙地操做把本身添加到Dep訂閱者數組中,固然Observer 的getter方法也要變化以下 }; //Observer.js function defineReactive(data, key, val) { observe(val); var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { if (Dep.target) {. dep.addSub(Dep.target); // 關鍵的在這裏,當第一次實例化時,調用Watcher的get方法,get方法內部會獲取Object的屬性,會觸發這個get方法,在這裏將Watcher 添加到Dep的訂閱者數組中 } return val; }, set: function(newVal) { if (val === newVal) { return; } val = newVal; dep.notify(); } }); } Dep.target = null;
看似好像發佈者訂閱者模式實現了,數據劫持也實現了,在數據改變的時候,觸發Object.setProperty中定義的set函數,set函數觸發Dep訂閱者中間件的notice方法,觸發全部訂閱者的update方法,而且訂閱者在實例化的時候就加入到了Dep訂閱者的數組內部,讓咱們來看看怎麼用
<body> <!-- 這裏其實仍是會直接顯示{{name}} --> <h1 id="name">{{name}}</h1> </body>
function SelfVue (data, el, exp) { //初始化data屬性 this.data = data; //將其設置爲觀察者 observe(data); //手動設置初始值 el.innerHTML = this.data[exp]; //初始化watcher,添加到訂閱者數組中,而且回調函數是從新渲染頁面,觸發update方法時經過回調函數重寫html節點 new Watcher(this, exp, function (value) { el.innerHTML = value; }); return this; }
var ele = document.querySelector('#name'); var selfVue = new SelfVue({ name: 'hello world' }, ele, 'name'); //設定延時函數,直接修改數據值,看可否綁定到頁面視圖節點 window.setTimeout(function () { console.log('name值改變了'); selfVue.data.name = 'canfoo'; }, 2000);
到上面爲止:基本實現了數據(model
)到視圖(view
)層的單向數據綁定,只有v-model是使用到了雙向綁定,不少vue的數據綁定的理解,和難點也就在上面的單向綁定
那麼:model->view單向綁定彷佛已經成功了,那麼view -> model呢?
var dom = document.getElementById('xx') dom.addEventListener('input',function(e){ selfVue.data.xxx = e.target.value })
很是感謝:下面的文章給了我不少的幫助,感謝各位前行者的辛苦付出,能夠點擊查閱更多信息