Vue躬行記(1)——數據綁定

  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

1、實例

  若是要使用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:捕獲到後代組件的錯誤時回調。

2、模板語法

  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>&lt;span&gt;content&lt;/span&gt;</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>

3、過濾器

  過濾器可用來格式化模板中的文本,存在於佔位符和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>

4、計算屬性

  在模板中適合簡單的聲明式邏輯,而應避免頻繁的進行復雜計算,這樣既不利於維護,也會讓模板結構變得臃腫而混亂。爲了能合理的執行復雜表達式,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;

5、響應式原理

  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);
相關文章
相關標籤/搜索