Vue.js的核心是經過基於HTML的模板語法聲明式地將數據綁定到DOM結構中,即經過模板將數據顯示在頁面上,以下所示。html
<div id="container">{{content}}</div> <script> var vm = new Vue({ el: "#container", data: { content: "strick" } }); </script>
其中<div>元素的內容是一個模板的插值,vm是一個Vue實例。vue
若是要使用Vue的功能,那麼須要經過Vue()構造函數建立一個Vue實例,而Vue實例至關於MVVM模式中的ViewModel。注意,全部的Vue組件(後面篇章將會分析)都是Vue實例。json
1)選項對象api
Vue的構造函數能接收一個選項對象,包含數據、計算屬性、方法、模板、生命週期鉤子等成員。上面代碼中的el是Vue實例的掛載目標,既能夠是CSS選擇器,也能夠是DOM元素;data是Vue實例的數據對象,其屬性會被加到Vue的響應式系統中,當修改data的屬性時,視圖會響應變動而從新渲染,即vm實例的對應屬性也會更新,反之亦然,以下所示。數組
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()方法),其屬性是沒法響應式的。dom
除了$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>元素所生成的內容會有所不一樣,具體可參考對應的註釋。
<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標籤時,它的兩個特殊字符都被轉義了。
<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時,相應的特性不會被輸出到元素中。
<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。
<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是事件處理程序。
<button v-on:click="dot">提交</button>
Vue爲v-bind和v-on兩個指令提供了專用的縮寫(以下所示),分別用「:」和「@」符號表示。
<!-- v-bind的縮寫 --> <p :id="id"></p> <!-- v-on的縮寫 --> <button @click="dot">提交</button>
從Vue 2.6.0開始,引入了動態參數的概念,在冒號後面跟一個用方括號包裹的表達式,以下所示,其中type是數據對象的屬性,其值會做爲參數來使用。
<button v-on:[type]="dot">提交</button>
動態參數中的表達式會有一些語法約束,例如運算結果得是字符串類型、不能包含空格和引號、避免駝峯方式的變量命名,以下所示。
<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()方法,以下所示。
<form v-on:submit.prevent="dot"></form>
過濾器可用來格式化模板中的文本,存在於佔位符和v-bind指令中,緊跟在表達式以後,其寫法以下所示,name是數據對象的屬性,lowercase是一個過濾器,二者用「|」符號隔開。
{{ name | lowercase }} <button v-bind:name="name | lowercase"></button>
注意,自Vue 2.0起,全部的內置過濾器(例如capitalize、uppercase、json等)都已被移除,官方推薦按需加載更專業的庫來實現過濾。
1)建立
Vue容許用戶自定義過濾器,可在實例的filters選項中註冊局部過濾器,以下所示。
var vm = new Vue({ filters: { lowercase: function(value) { return value.toLowerCase(); } } });
也能夠在建立Vue實例以前,經過Vue.filter()方法註冊全局過濾器,以下所示。
Vue.filter("lowercase", function (value) { return value.toLowerCase(); }); var vm = new Vue({...});
當局部過濾器和全局過濾器重名時,會優先採用局部過濾器。
2)鏈式調用
多個過濾器可經過「|」符號串聯實現鏈式調用,以下所示。
{{ name | lowercase | capitalize }}
lowercase過濾器會接收name的值,而後將其計算結果傳給capitalize過濾器。
3)傳遞參數
因爲過濾器本質上仍是一個函數,所以它支持多個參數的傳入,以下所示,compare過濾器會接收三個參數,分別是number和threshold兩個數據對象的屬性,以及一個常量10。
<p>{{number | compare(10, threshold)}}</p>
注意,Vue 2.0取消了用空格來標記過濾器參數的方式,下面的調用是無效的。
<p>{{number | compare 10 threshold }}</p>
在模板中適合簡單的聲明式邏輯,而應避免頻繁的進行復雜計算,這樣既不利於維護,也會讓模板結構變得臃腫而混亂。爲了能合理的執行復雜表達式,Vue引入了計算屬性的概念。
計算屬性在模板中的數據綁定和普通屬性同樣,但須要以函數的方式來定義。在下面的代碼中,newName是一個計算屬性,用來讓name屬性重複兩次再提取末尾兩個字符,在它的getter函數中引用了一個指向vm實例的this。
<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屬性不發生變化時,二者的執行方式會有所不一樣。
var vm = new Vue({ methods: { getName: function() { return this.name.repeat(2).substr(-2); } } });
當屢次訪問newName時,讀取的是其緩存的值,不會執行它的getter函數,而方法每次都會執行一遍。因爲計算屬性能減小冗餘的運算,所以它很適合處理那些耗時且性能開銷巨大的操做。
2)寫入
默認狀況下只須要定義計算屬性的getter函數,不過Vue也爲其提供了setter函數,使得計算屬性在寫入時能處理更爲複雜的業務邏輯,以下所示。
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。
vm.total = 10;
Vue採用了非侵入性的響應式系統,當把數據對象傳給Vue實例的data屬性時,Vue會經過Object.defineProperty()方法將它的每一個屬性替換成getter和setter兩個函數,下面用一個簡單的示例展現Vue的基本思路。
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實例的一個根屬性,但它是在實例化後聲明的,因此也就沒法成爲響應式的屬性了。
var vm = new Vue({ data: { name: "strick" //響應式屬性 } }); vm.age = 28; //非響應式屬性
除了內部的技術限制以外,提早聲明響應式屬性,也便於開發人員理解代碼的意圖。對於已建立的實例,有兩種方式聲明非根級的響應式屬性,第一種是用全局的Vue.set()方法或Vue實例的$set()方法,在下面的代碼中,爲people對象聲明瞭一個響應式的age屬性。
var vm = new Vue({ data: { people: { name: "freedom" } } }); Vue.set(vm.people, "age", 28);
第二種是用Object.assign()方法,可一次性添加多個屬性,以下所示,將原對象和新增的屬性合併成一個新對象,再賦給vm.people。
vm.people = Object.assign({}, vm.people, { age: 28, school: "university" });
2)數組
Vue沒法檢測下面兩種數組的變更,以vm實例的names屬性爲例。
(1)經過索引設置數組的元素。
(2)縮短數組的長度。
var vm = new Vue({ data: { names: ["strick", "freedom"] } }); vm.names[1] = "justify"; //第一種變更 vm.names.length = 1; //第二種變更
要檢測第一種變更,可使用Vue.set()方法或數組的splice()方法,而要檢測第二種變更,就只能使用splice()方法了,以下所示。
//檢測第一種變更 Vue.set(vm.names, 1, "justify"); vm.names.splice(1, 1, "justify"); //檢測第二種變更 vm.names.splice(1, 1);