[TOC]
----------
## MVVM是什么?
MVVM 是Model-View-ViewModel 的縮寫,它是一種基于前端開發的架構模式,其核心是提供對View 和 ViewModel 的雙向數據綁定,這使得ViewModel 的狀態改變可以自動傳遞給 View,即所謂的數據雙向綁定。

Vue.js 是一個提供了 MVVM 風格的雙向數據綁定的 Javascript 庫,專注于View 層。它的核心是 MVVM 中的 VM,也就是 ViewModel。 ViewModel負責連接 View 和 Model,保證視圖和數據的一致性,這種輕量級的架構讓前端開發更加高效、便捷。
### Vue 雙向綁定原理
Vue.js 是采用[ Object.defineProperty ](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)的 getter 和 setter,并結合觀察者模式來實現數據綁定的。當把一個普通 Javascript 對象傳給 Vue 實例來作為它的 data 選項時,Vue 將遍歷它的屬性,用 Object.defineProperty 將它們轉為 getter/setter。用戶看不到 getter/setter,但是在內部它們讓 Vue 追蹤依賴,在屬性被訪問和修改時通知變化。

>[success]預覽:https://ityanxi.github.io/Vue-tutorial/chapter02/js-mvvm.html
>[success]代碼示例如下:
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>it研習社-Vue雙向數據綁定實現原理</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text">
{{ text }}
</div>
<script>
function observe (obj, vm) {
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key]);
});
}
function defineReactive (obj, key, val) {
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function () {
// 添加訂閱者watcher到主題對象Dep
if (Dep.target) dep.addSub(Dep.target);
return val
},
set: function (newVal) {
if (newVal === val) return
val = newVal;
// 作為發布者發出通知
dep.notify();
}
});
}
function nodeToFragment (node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
compile(child, vm);
flag.append(child); // 將子節點劫持到文檔片段中
}
return flag;
}
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) {
// 給相應的data屬性賦值,進而觸發該屬性的set方法
vm[name] = e.target.value;
});
node.value = vm[name]; // 將data的值賦給該node
node.removeAttribute('v-model');
}
};
}
// 節點類型為text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; // 獲取匹配到的字符串
name = name.trim();
// node.nodeValue = vm[name]; // 將data的值賦給該node
new Watcher(vm, node, name);
}
}
}
function Watcher (vm, node, name) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.update();
Dep.target = null;
}
Watcher.prototype = {
update: function () {
this.get();
this.node.nodeValue = this.value;
},
// 獲取data中的屬性值
get: function () {
this.value = this.vm[this.name]; // 觸發相應屬性的get
}
}
function Dep () {
this.subs = []
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
function Vue (options) {
this.data = options.data;
var data = this.data;
observe(data, this);
var id = options.el;
var dom = nodeToFragment(document.getElementById(id), this);
// 編譯完成后,將dom返回到app中
document.getElementById(id).appendChild(dom);
}
var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
}
});
</script>
</body>
</html>
~~~

>**Observer 數據監聽器**,能夠對數據對象的所有屬性進行監聽,如有變動可拿到最新值并通知訂閱者,內部采用Object.defineProperty的getter和setter來實現。
**Compile 指令解析器**,它的作用對每個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數。
**Watcher 訂閱者**, 作為連接 Observer 和 Compile 的橋梁,能夠訂閱并收到每個屬性變動的通知,執行指令綁定的相應回調函數。
**Dep 消息訂閱器**,內部維護了一個數組,用來收集訂閱者(Watcher),數據變動觸發notify 函數,再調用訂閱者的 update 方法。
從圖中可以看出,當執行 new Vue() 時,Vue 就進入了初始化階段,一方面Vue 會遍歷 data 選項中的屬性,并用 Object.defineProperty 將它們轉為 getter/setter,實現數據變化監聽功能;另一方面,Vue 的指令編譯器Compile 對元素節點的指令進行掃描和解析,初始化視圖,并訂閱Watcher 來更新視圖, 此時Wather 會將自己添加到消息訂閱器中(Dep),初始化完畢。
當數據發生變化時,Observer 中的 setter 方法被觸發,setter 會立即調用Dep.notify(),Dep 開始遍歷所有的訂閱者,并調用訂閱者的 update 方法,訂閱者收到通知后對視圖進行相應的更新。
### 雙向綁定示例
MVVM模式本身是實現了雙向綁定的,在Vue.js中可以使用v-model指令在表單元素上創建雙向數據綁定。
~~~
<!--這是我們的View-->
<div id="app">
<p>{{ message }}</p>
<input type="text" v-model="message"/>
</div>
~~~
>[success]預覽:https://ityanxi.github.io/Vue-tutorial/chapter02/vue-mvvm.html
>[success]代碼示例如下:
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>it研習社-基于Vue實現的 數據雙向綁定</title>
<style type="text/css">
#demo{
width: 800px;
margin: 200px auto;
}
input{
width: 600px;
height: 50px;
border:10px solid green;
padding-left: 10px;
font:30px/50px "微軟雅黑";
}
.msg{
width: 600px;
font:30px/50px "微軟雅黑";
color:red;
}
</style>
<script src="vue.js"></script>
<script type="text/javascript">
window.onload=function(){
new Vue({
el: '#demo',
data: {
msg:'welcome vue.js',
}
})
}
</script>
</head>
<body>
<div id="demo">
<input v-model='msg'/>
<div class="msg">
{{msg}}
</div>
</div>
</body>
</html>
~~~
將message綁定到文本框,當更改文本框的值時,{{ message }} 中的內容也會被更新。

反過來,如果改變message的值,文本框的值也會被更新,我們可以在Chrome控制臺進行嘗試。

Vue實例的data屬性指向exampleData,它是一個引用類型,改變了exampleData對象的屬性,同時也會影響Vue實例的data屬性。
接下來,我們來做一個關于數據綁定的小練習
>[success]預覽:https://ityanxi.github.io/Vue-tutorial/chapter02/vue-mvvm1.html

>[success]代碼示例如下:
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue雙向數據綁定練習</title>
<script src="vue.js"></script>
</head>
<body>
<div id="demo">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
<hr>
姓名:{{firstName+lastName}}
</div>
<script>
var vm=new Vue({
el:"#demo",
data:{
firstName:"huang",
lastName:"jack",
}
});
</script>
</body>
</html>
~~~
- 前端新手村
- 前言
- 第1章 遇見Vue.js
- 第一個Vue.js程序
- vue嘗鮮
- 第2章 概念理解
- 漸進式框架
- 虛擬DOM
- MVVM模式
- MVX模式是什么
- 第3章 Vue基礎概覽
- 第4章 Vue內置指令詳解
- vue-text
- vue-html
- v-show
- v-if
- v-else
- v-else-if
- v-for
- v-on
- v-bind
- v-model
- v-pre
- v-cloak
- v-once
- 第5章 基礎demo小練習
- 圖書管理系統
- 頁面布局
- 列表渲染
- 功能實現
- 基于BootStrap+Vuejs實現用戶信息表
- 功能描述
- 布局實現
- 星座判斷
- 第6章 組件
- 什么是組件
- 使用組件
- Prop
- 自定義事件
- 使用Slot分發內容
- 動態組件
- 雜項
- 第7章-過渡
- 過渡效果
- 概述
- 單元素/組件的過渡
- 初始渲染的過渡
- 多個元素的過渡
- 多個組件的過渡
- 列表過渡
- 可復用的過渡
- 動態過渡
- 過渡狀態
- 狀態動畫與watcher
- 動態狀態轉換
- 通過組件組織過渡
- Render函數
- 基礎
- createElement參數
- 使用JavaScript代替模板功能
- JSX
- 函數化組件
- 模板編譯
- 自定義指令
- 簡介
- 鉤子函數
- 鉤子函數參數
- 函數簡寫
- 對象字面量
- Vuex狀態管理
- Vuex是什么?
- Vuex的安裝
- Vuex起步
- data的替代品-State和Getter
- 測試Getter
- Action-操作的執行者
- 測試Action
- 只用Mutation修改狀態
- 測試Mutations
- Vuex的基本結構
- 子狀態和模塊
- 用服務分離外部操作
- Vue-router
- Vue-router是什么
- Vue-router安裝
- 基本用法1
- 基本用法2
- Vue-cli
- Vue中的Node.js
- Vue中的npm、cnpm
- Vue中的webpack
- 安裝
- 基本使用
- 模板
- 全局API
- Vue.extend
- Vue.nextTick
- Vue.set
- Vue.delete
- Vue.directive
- Vue.filter
- Vue.component
- Vue.use
- Vue.mixin
- Vue.compile
- 附錄
- 相關網站
- 尤雨溪
- 第10章 webpack
- webpack安裝
- webpack基本使用
- webpack命令行
- webpack配置文件
- 單頁面應用SPA
- 第1章 Vue.js簡介
- 1.1 Vue.js簡介
- 1.1.1 Vue.js是什么
- 1.1.2 為什么要用Vue.js
- 1.1.3 Vue.js的發展歷史
- 1.1.4 Vue.js與其他框架的區別
- 1.2 如何使用Vue.js
- 1.2.1 第一個Vue.js程序
- 1.2.2 Vue.js小嘗鮮
- 1.3 概念詳解
- 1.3.1 什么是漸進式框架
- 1.3.2 虛擬DOM是什么
- 1.3.3 如何理解MVVM
- 第2章 基礎概覽
- 2.1 Vue實例
- 2.1.1 構造器
- 2.1.2 屬性與方法
- 2.1.3 實例生命周期
- 2.1.4 生命周期圖示
- 2.2 模板語法
- 2.2.1 插值
- 2.2.2 指令
- 2.2.3 過濾器
- 2.2.4 縮寫
- 第3章 Class與Style的綁定
- 第4章 模板渲染
- 第5章 事件詳解
- 第6章 表單控件綁定
- 第7章 指令詳解
- 7.1 內部指令
- 7.2 自定義指令
- 7.3 指令的高級選項
- 第8章 計算屬性
- 第9章 過濾器
- 第10章 組件
- 10.1 什么是組件
- 10.2 注冊組件
- 10.3 組件選項
- 10.4 組件間的通信
- 10.5 內容分發
- 10.6 動態組件