Vue之雙向綁定

Vue之雙向綁定

雙向數據綁定

將DOM與Vue實例的data數據綁定到一起,彼此間互相影響。

  • 數據的變化會引起DOM的改變
  • DOM的改變也會引起數據的變化

原理

當把一個普通的Javascript對象傳入Vue實例作爲data選項時,Vue會遍歷這個對象的所有屬性,並使用Object.defineProperty( )來把每個屬性轉爲setter和getter。這些setter和getter對用戶來說不可見,但是在內部會讓Vue能夠追蹤依賴,在屬性被訪問和修改時通知變更。
每個組件實例都對應一個watcher實例,它會在組件渲染的過程中把“接觸”過的數據屬性記錄爲依賴。之後,當依賴項的sette觸發時,會通知watcher,從而使它關聯的組件重新渲染。

什麼是響應式:簡單來說,當你的數據發生變化時,視圖也進行相應的更新。

響應式數據

只有data中的數據(程序創建時data裏的屬性)纔是響應式的,動態添加進來的數據默認爲非響應式的。

<!DOCTYPE html>
<html>
<head>
	<title></title>
</head>
<body>
	<div id ="app">
		<p>{{stu}}</p>
	</div>
	<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
	<script type="text/javascript">
		var vm = new Vue({
			el:"#app",
			data: {
				stu: {
					name:"Jona",
					age:19
				}
			}
		})
		vm.stu.width = "666";
		Vue.set(vm.stu,"gender","male");
	</script>
</body>
</html>

此時,如果在console中修改stu.width的值,視圖不會發生更新。

這主要因爲,data中的屬性,Vue會把它通過Object.defineProperty()修改轉成setter和getter,因此當修改時,會點用屬性的setter方法,實現視圖立即更新。而對於非響應式數據,即動態添加的,由於在Vue中,DOM的更新是異步的,因此視圖不會立即更新。

添加響應式數據的方式

  • 使用Vue.set(object, key, value)
    • 主要適用於添加單個屬性
    • 也可以使用vm.$set()實例方法
  • 使用Object.assign()
    • 主要使用添加多個
    • eg: vm.stu = Object.assign({}, vm.stu, {width:120, height:100})

異步DOM更新

Vue在更新DOM時是異步更新的。只要偵聽到數據變化,Vue會開啓一個隊列,並緩衝在同一事件循環中所有的數據變更。如果一個watcher被多次觸發,只會被推入隊列中一次。這樣會去除重複數據以避免不必要的計算和DOM操作。在下一個事件循環“tick”中,Vue會刷新隊列並執行實際(已去重的)工作。Vue在內部對異步隊列嘗試使用原生的Promise.then( ), MutationObserver和 setImmediate , 如果執行環境不支持,則會採用setTimeout(fn , o)代替。
例如,當設置vm.someData = "new value",該組件不會立即進行重新渲染。當刷新隊列時,組件會在下一個事件循環"tick"中更新。爲了在數據變化之後等待 Vue 完成更新 DOM,可以在數據變化之後立即使用 Vue.nextTick(callback)。在組件內使用 vm.$nextTick() 實例方法也特別方便。因爲它不需要全局 Vue,並且回調函數中的 this 將自動綁定到當前的 Vue 實例上。
Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: '未更新'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = '已更新'
      console.log(this.$el.textContent) // => '未更新'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '已更新'
      })
    }
  }
})

Event Loop

js是單線程語言,即所有任務需要排隊,一個任務只有等到前一個任務執行完畢之後才能執行。如果錢一個任務需要花費大量時間,後一個任務就不得不一直等待。
一些時候,當執行IO操作等這些耗時,但卻未佔用CPU的任務時,如果等當前耗時等待的任務執行完之後再執行下一個任務,會帶來效率上的問題,而且也不友好。因此,設計者把這些等待中的任務掛起,從而可以執行後續任務。等掛起的任務有結果了,再把掛起的任務執行下去。
於是,js中的任務分爲兩種:同步任務異步任務
- 同步任務:在主線程上排列執行的任務,按順序排列執行,一個任務只有前一個任務執行完畢之後纔會執行。
- 異步任務:不進入主線程,而進入“任務隊列”的任務。只有“任務隊列”通知主線程,某個異步任務可以執行了,該任務纔會進入主線程執行。
異步任務執行機制

  • 所有同步任務都在主線程上執行,形成一個執行棧。
  • 主線程之外還有一個“任務隊列”。只要異步任務有了結果,就在任務隊列中放置一個事件。
  • 執行棧中的同步任務執行完畢後,系統就會讀取任務隊列。那些對應的異步任務,就結束了等待狀態,進入執行棧,開始執行。
  • 主線程不斷重複以上過程。

事件和回調函數

任務隊列是一個事件的隊列,IO設備完成一項任務就會在任務隊列中添加一個事件,表示相應的任務可以進入執行棧了。這些事件,除了IO事件,還包括用戶事件。
只要指定過回調函數,這些事件發生時就會進入任務隊列,等待主線程讀取。異步任務必須指定回調函數,當主線程開始執行異步任務,就是執行對應的回調函數。
在任務隊列中的任務進入主線程時,主線程首先要檢查執行時間,因爲對於setTimeout(),setTimeIntervel()這類定時器,只有到了規定時間才能返回主線程。

			console.log("1");
			setTimeout(function(){
				console.log("2")
			});
			(function(){
				console.log("3");
				setTimeout(function(){
					console.log("4");
				},1000)
			})();
			console.log("5");
			console.log("6");
			(function(){
				console.log("7");
				setTimeout(function(){
					console.log("8");
				})
			})();

在這裏插入圖片描述
這裏的2, 4, 8 均在任務隊列中,當執行棧中的任務執行完之後,由於對2沒有設置推遲時間,且它在隊列的最前端,因此先打印2。隊列中第二個,由於設置了1000ms,而8沒有設置推遲時間,因此先打印8,等到了1000ms時纔會打印4。

任務隊列

任務隊列分爲microtask 和 macortask,通常稱爲微任務和宏任務。

  • 執行的優先級: 主線程任務 > micortask > macrotask
  • macrotask有:setTimeout, setTimeInterval , setImmediate等
  • microtask有:Promise, MutationObserver等

Vue的nextTick

Vue在內部嘗試對異步隊列使用原生的setImmediate Promise.then和MessageChannel,如果當前執行環境不支持,就採用setTimeout(fn, 0)代替。
在這裏插入圖片描述
每次執行棧的同步任務執行完畢,就會去任務隊列中取出完成的異步任務,隊列中又分爲microtasks queues和宏任務隊列等到把microtasks queues所有的microtasks都執行完畢,注意是所有的,他纔會從宏任務隊列中取事件。等到把隊列中的事件取出一個,放入執行棧執行完成,就算一次循環結束,之後event loop還會繼續循環,他會再去microtasks queues執行所有的任務,然後再從宏任務隊列裏面取一個,如此反覆循環。

console.log(1);
			setTimeout(function(){
				console.log(2)
			});

			console.log(3);
			new Promise((resolve, reject) => {
				console.log(4);
				resolve()
			}).then(() => {
				console.log(5)
			}).then(() => {
				console.log(8)
				show()
			})

 			function show()
 			{
 			 console.log(9);
			 setTimeout(function(){
				console.log(10)
			 });
			 console.log(11);
			 new Promise((resolve, reject) => {
				console.log(12);
				resolve()
			 }).then(() => {
				console.log(13)
			 })
 			}

			setTimeout(function() {
				console.log(6)
			})
			console.log(7)
			}

在這裏插入圖片描述

也就是說,Vue會把裏面的數據變更(非data中的屬性)通過Promise.then等方法加入到任務隊列中, vue中的nextTick 也會加入到任務隊列中,因此,在 nextTick之前的數據變更可以在nextTick中訪問到變更後的數據

參考文獻

  1. node基礎面試事件環?微任務、宏任務?一篇帶你飛
  2. Event Loop是個什麼玩意:從 Vue 的 nextTick 說起
  3. JavaScript 運行機制詳解:再談Event Loop
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章