Vue.js的核心是通過基于HTML的模板語法聲明式地將數據綁定到DOM結構中,即通過模板將數據顯示在頁面上,如下所示。
~~~html
<div id="container">{{content}}</div>
<script>
var vm = new Vue({
el: "#container",
data: {
content: "strick"
}
});
</script>
~~~
  其中元素的內容是一個模板的插值,vm是一個Vue實例。
## 一、實例
  如果要使用Vue的功能,那么需要通過Vue()構造函數創建一個Vue實例,而Vue實例相當于MVVM模式中的ViewModel。注意,所有的Vue組件(后面篇章將會分析)都是Vue實例。
**1)選項對象**
  Vue的構造函數能接收一個選項對象,包含數據、計算屬性、方法、模板、生命周期鉤子等成員。上面代碼中的[el](https://cn.vuejs.org/v2/api/#el)是Vue實例的掛載目標,既可以是CSS選擇器,也可以是DOM元素;[data](https://cn.vuejs.org/v2/api/#data)是Vue實例的數據對象,其屬性會被加到Vue的響應式系統中,當修改data的屬性時,視圖會響應變更而重新渲染,即vm實例的對應屬性也會更新,反之亦然,如下所示。
~~~js
var data = {
content: "strick"
};
var vm = new Vue({
el: "#container",
data: data
});
data.content = "freedom";
console.log(vm.content); //"freedom"
vm.content = "justify";
console.log(data.content); //"justify"
~~~
  注意,如果data屬性使用了箭頭函數,那么this不會指向vm實例。
  在實例被創建之后,就能通過vm.$data訪問原來的數據對象,而vm.content是vm.$data.content的簡寫。注意,被凍結后的對象(即調用了Object.freeze()方法),其屬性是無法響應式的。
  除了$data屬性之外,Vue實例還提供了很多其它的屬性和方法,它們都會以“$”符號為前綴,而為了避免與內置的沖突,Vue實例不會代理以“\_”或“$”開頭的用戶自定義的屬性和方法。
**2)生命周期**
  Vue實例的生命周期包括初始化數據、編譯模板、掛載、渲染、更新和銷毀等,每個階段都存在對應的鉤子,以便執行相關的業務邏輯。由于生命周期鉤子都會自動把this和實例綁定在一起,因此不要用箭頭函數來聲明鉤子。
  常用的8個生命周期可分為4組(如下所列),每組有一個名稱帶before前綴,顧名思義,先于另一個鉤子執行,圖1描繪了實例的生命周期。
:-: 
圖1 Vue實例的生命周期
  (1)beforeCreate:實例初始化之后回調,無法訪問data、methods、computed等之中的數據或方法。
  (2)created:實例創建完成后回調,可訪問data、methods、computed等之中的數據或方法,由于還未掛載到DOM中,因此不能成功讀取$el。
  (3)beforeMount:實例掛載之前回調,將要使用的模板編譯成render()函數。
  (4)mounted:實例掛載到DOM后回調,已替換模板中的插值,可獲取el中的DOM元素,但要注意,不能保證其子組件也已被掛載。
  (5)beforeUpdate:數據更新時回調,發生在虛擬DOM之前,可操作現有DOM元素,例如移除其事件監聽器等。
  (6)updated:DOM重新渲染后回調,可執行依賴于DOM的操作,但要在此期間盡量不要更改狀態,以免陷入死循環,并且不能保證其子組件也已被重繪。
  (7)beforeDestroy:實例銷毀之前回調,此時實例還存在,this仍然能指向它。
  (8)destroyed:實例銷毀后回調,會解除數據綁定、移除事件、銷毀子組件等。
  除了這8個鉤子之外,還有3個鉤子,如下所列。
  (1)activated:\<keep-alive>元素激活時回調。
  (2)deactivated:\<keep-alive>元素停用時回調。
  (3)errorCaptured:捕獲到后代組件的錯誤時回調。
## 二、模板語法
  Vue的模板是一段特殊的HTML代碼,其語法包括插值、指令和修飾符。雖然Vue的模板語法非常簡潔,但是在內部Vue會進行一系列操作,例如將模板編譯成虛擬DOM的渲染函數render(),結合響應系統最大程度的優化DOM操作次數以及用最少的代價渲染組件等。
**1)插值**
  Vue會以插值的方式將數據傳遞給模板,而插值可以是文本、HTML代碼、特性和表達式。
  (1)文本插值是最常見的數據綁定形式,其寫法與Mustache中的占位符類似,也需要用兩個花括號包裹數據。當和v-once指令配合時,能實現單次插值,即阻止數據變化時的視圖更新。如下代碼所示,在修改數據對象的text屬性后,兩個\<p>元素所生成的內容會有所不同,具體可參考對應的注釋。
~~~html
<div id="container">
<!-- <p>strick</p> -->
<p>{{text}}</p>
<!-- <p>text</p> -->
<p v-once>{{text}}</p>
</div>
<script>
var data = {
text: "text"
};
var vm = new Vue({
el: "#container",
data: data
});
data.text = "strick";
</script>
~~~
  (2)由于模板占位符中的數據會被解釋成普通文本(為了預防XSS攻擊),因此如果要輸出HTML代碼,需要使用v-html指令。如下代碼所示,第一個\<p>元素在輸出HTML標簽時,它的兩個特殊字符都被轉義了。
~~~html
<div id="container">
<!-- <p><span>content</span></p> -->
<p>{{html}}</p>
<!-- <p><span>content</span></p> -->
<p v-html="html"></p>
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
html: "<span>content</span>"
}
});
</script>
~~~
  (3)如果要將數據對象的屬性值插到DOM元素的特性(即定義在HTML標簽中的標準或非標準屬性)中,那么得使用v-bind指令,如下代碼所示。注意,當屬性值為null、undefined或false時,相應的特性不會被輸出到元素中。
~~~html
<div id="container">
<!-- <p id="row"></p> -->
<p v-bind:id="id"></p>
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
id: "row"
}
});
</script>
~~~
  (4)模板占位符還支持表達式運算,如下代碼所示。注意,語句是不被允許的,并且在表達式中,只能訪問白名單里的全局變量,例如Math和Date。
~~~html
<div id="container">
<!-- <p>success</p> -->
<p>{{result ? "success" : "failure"}}</p>
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
result: true
}
});
</script>
~~~
**2)指令**
  Vue中的指令(Directives)是一組以“v-”為前綴的DOM元素特性,它能接收一個表達式或參數。其職責是告知Vue如何處理提供給它的數據,并且當表達式的結果發生變化時,將其產生的影響反映到DOM上。
  指令和參數之間會用冒號隔開,例如前文用于更新DOM元素特性的v-bind。還有一個常用的v-on指令,用于監聽事件,如下所示,其中click是事件類型,dot是事件處理程序。
~~~html
<button v-on:click="dot">提交</button>
~~~
  Vue為v-bind和v-on兩個指令提供了專用的縮寫(如下所示),分別用“:”和“@”符號表示。
~~~html
<!-- v-bind的縮寫 -->
<p :id="id"></p>
<!-- v-on的縮寫 -->
<button @click="dot">提交</button>
~~~
  從Vue 2.6.0開始,引入了動態參數的概念,在冒號后面跟一個用方括號包裹的表達式,如下所示,其中type是數據對象的屬性,其值會作為參數來使用。
~~~html
<button v-on:[type]="dot">提交</button>
~~~
  動態參數中的表達式會有一些語法約束,例如運算結果得是字符串類型、不能包含空格和引號、避免駝峰方式的變量命名,如下所示。
~~~html
<button v-on:[1234567]="dot">提交</button>
<button v-on:[type + ""]="dot">提交</button>
<button v-on:[eventType]="dot">提交</button>
~~~
  在DOM中使用模板時,eventType會被強制轉換成全小寫的eventtype,從而就無法在數據對象中讀取到它的值了。
**3)修飾符**
  Vue的修飾符(Modifier)是一種以“.”開頭的特殊后綴,能讓指令完成某種特殊行為,例如用.prevent修飾符取消默認操作,即調用事件對象的preventDefault()方法,如下所示。
~~~html
<form v-on:submit.prevent="dot"></form>
~~~
## 三、過濾器
  過濾器可用來格式化模板中的文本,存在于占位符和v-bind指令中,緊跟在表達式之后,其寫法如下所示,name是數據對象的屬性,lowercase是一個過濾器,兩者用“|”符號隔開。
~~~html
{{ name | lowercase }}
<button v-bind:name="name | lowercase"></button>
~~~
  注意,自Vue 2.0起,所有的內置過濾器(例如capitalize、uppercase、json等)都已被移除,官方推薦按需加載更專業的庫來實現過濾。
**1)創建**
  Vue允許用戶自定義過濾器,可在實例的filters選項中注冊局部過濾器,如下所示。
~~~js
var vm = new Vue({
filters: {
lowercase: function(value) {
return value.toLowerCase();
}
}
});
~~~
  也可以在創建Vue實例之前,通過Vue.filter()方法注冊全局過濾器,如下所示。
~~~js
Vue.filter("lowercase", function (value) {
return value.toLowerCase();
});
var vm = new Vue({...});
~~~
  當局部過濾器和全局過濾器重名時,會優先采用局部過濾器。
**2)鏈式調用**
  多個過濾器可通過“|”符號串聯實現鏈式調用,如下所示。
~~~html
{{ name | lowercase | capitalize }}
~~~
  lowercase過濾器會接收name的值,然后將其計算結果傳給capitalize過濾器。
**3)傳遞參數**
  由于過濾器本質上還是一個函數,因此它支持多個參數的傳入,如下所示,compare過濾器會接收三個參數,分別是number和threshold兩個數據對象的屬性,以及一個常量10。
~~~html
<p>{{number | compare(10, threshold)}}</p>
~~~
  注意,Vue 2.0取消了用空格來標記過濾器參數的方式,下面的調用是無效的。
~~~html
<p>{{number | compare 10 threshold }}</p>
~~~
## 四、計算屬性
  在模板中適合簡單的聲明式邏輯,而應避免頻繁的進行復雜計算,這樣既不利于維護,也會讓模板結構變得臃腫而混亂。為了能合理的執行復雜表達式,Vue引入了計算屬性的概念。
  計算屬性在模板中的數據綁定和普通屬性一樣,但需要以函數的方式來定義。在下面的代碼中,newName是一個計算屬性,用來讓name屬性重復兩次再提取末尾兩個字符,在它的getter函數中引用了一個指向vm實例的this。
~~~html
<div id="container">
<p>{{newName}}</p>
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
name: "strick"
},
computed: {
newName: function() {
return this.name.repeat(2).substr(-2);
}
}
});
</script>
~~~
  注意,計算屬性往往會依賴數據對象的屬性或其它計算屬性,也就是說,當依賴的屬性被修改時,計算屬性會自動更新。
**1)緩存**
  計算屬性和方法有一個很大的不同,那就是它能被緩存。在下面的代碼中,聲明了一個getName()方法,雖然它的返回結果和之前的計算屬性newName的值相同,但是當依賴的name屬性不發生變化時,兩者的執行方式會有所不同。
~~~js
var vm = new Vue({
methods: {
getName: function() {
return this.name.repeat(2).substr(-2);
}
}
});
~~~
  當多次訪問newName時,讀取的是其緩存的值,不會執行它的getter函數,而方法每次都會執行一遍。由于計算屬性能減少冗余的運算,因此它很適合處理那些耗時且性能開銷巨大的操作。
**2)寫入**
  默認情況下只需要定義計算屬性的getter函數,不過Vue也為其提供了setter函數,使得計算屬性在寫入時能處理更為復雜的業務邏輯,如下所示。
~~~js
var vm = new Vue({
el: "#container",
data: {
price: 10.2
},
computed: {
total: {
get: function() {
return this.price * 10;
},
set: function(value) {
this.price = value + Math.round(this.price);
}
}
}
});
~~~
  當為計算屬性total賦值時(如下所示),就會調用它的setter函數,并更新vm.price。
~~~js
vm.total = 10;
~~~
## 五、響應式原理
  Vue采用了非侵入性的響應式系統,當把數據對象傳給Vue實例的data屬性時,Vue會通過Object.defineProperty()方法將它的每個屬性替換成getter和setter兩個函數,下面用一個簡單的示例展示Vue的基本思路。
~~~js
const data = { //數據對象
name: "strick"
};
const proxyData = {
name: data.name
};
Object.defineProperty(data, "name", {
get() {
//注入監聽邏輯,并在必要時通知變更
return proxyData.name;
},
set(value) {
//注入監聽邏輯,并在必要時通知變更
proxyData.name = value;
},
configurable: true,
enumerable: true
});
~~~
  經過這波操作后,就能讓Vue擁有追蹤屬性變化的能力,并在屬性被訪問和修改時通知關聯的視圖重新渲染。在體驗響應式所帶來的便利的同時,也要知曉它的一些限制,接下來會分析Vue檢測對象和數組發生變動時的注意事項。
**1)對象**
  由于JavaScript無法監聽對象屬性的添加或刪除,因此只有在Vue實例化時才能對數據對象的根屬性做getter和setter的替換,即轉換成響應式的屬性。Vue不允許動態添加根級的響應式屬性,這些屬性必須預先聲明,如下所示,雖然age是vm實例的一個根屬性,但它是在實例化后聲明的,所以也就無法成為響應式的屬性了。
~~~js
var vm = new Vue({
data: {
name: "strick" //響應式屬性
}
});
vm.age = 28; //非響應式屬性
~~~
  除了內部的技術限制之外,提前聲明響應式屬性,也便于開發人員理解代碼的意圖。對于已創建的實例,有兩種方式聲明非根級的響應式屬性,第一種是用全局的Vue.set()方法或Vue實例的$set()方法,在下面的代碼中,為people對象聲明了一個響應式的age屬性。
~~~js
var vm = new Vue({
data: {
people: {
name: "freedom"
}
}
});
Vue.set(vm.people, "age", 28);
~~~
  第二種是用Object.assign()方法,可一次性添加多個屬性,如下所示,將原對象和新增的屬性合并成一個新對象,再賦給vm.people。
~~~js
vm.people = Object.assign({}, vm.people, { age: 28, school: "university" });
~~~
**2)數組**
  Vue無法檢測下面兩種數組的變動,以vm實例的names屬性為例。
  (1)通過索引設置數組的元素。
  (2)縮短數組的長度。
~~~js
var vm = new Vue({
data: {
names: ["strick", "freedom"]
}
});
vm.names[1] = "justify"; //第一種變動
vm.names.length = 1; //第二種變動
~~~
  要檢測第一種變動,可以使用Vue.set()方法或數組的splice()方法,而要檢測第二種變動,就只能使用splice()方法了,如下所示。
~~~js
//檢測第一種變動
Vue.set(vm.names, 1, "justify");
vm.names.splice(1, 1, "justify");
//檢測第二種變動
vm.names.splice(1, 1);
~~~
*****
> 原文出處:
[博客園-Vue躬行記](https://www.cnblogs.com/strick/category/1512864.html)
[知乎專欄-Vue躬行記](https://zhuanlan.zhihu.com/pwvue)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020