【vue双向绑定原理浅析】
vue雙向綁定原理淺析
1、什么是雙向綁定?
? 所謂雙向綁定,指的是vue實例中的data與其渲染的DOM元素的內容保持一致,無論誰被改變,另一方會相應的更新為相同的數據。(數據變化更新視圖,視圖變化更新數據)
2、如何實現雙向綁定?
在vue中可以通過v-model實現雙向綁定
<template><div id="app">{{username}} <br/><input type="text" v-model="username"></div> </template> <script> export default {name: 'App',data(){return {username:''}} } </script>但其實v-model只是一個語法糖,他實際做了兩步動作:1、綁定數據元素;2、觸發輸入事件
ps: v-model 在內部為不同的輸入元素使用不同的屬性并拋出不同的事件:
text 和 textarea 元素使用 value 屬性和 input 事件;
checkbox 和 radio 使用 checked 屬性和 change 事件;
select 字段將 value 作為 prop 并將 change 作為事件;
也就是說其實v-model等同于如下代碼:
<template><div id="app">{{username}} <br/><input type="text" :value="username" @input="username=$event.target.value"></div> </template> <script> export default {name: 'App',data(){return {username:''}} } </script>但為什么 <input type=“text” :value=“username” @input=“username=$event.target.value”>這樣寫就會實現雙向綁定?他的核心是什么?
3、vue實現雙向數據綁定的核心是Object.defineProperty()方法
Object.defineProperty(obj,prop,descriptor)使用:
obj:要在其上定義屬性的對象。
prop:要定義或修改的屬性的名稱。
descriptor:將被定義或修改的屬性描述符。
descriptor的基本結構 {value: 屬性對應的值,默認為 undefined。configurable: true | false, //屬性是否可以被delete,或者再次修改descriptorenumerable: true | false, //屬性是否可以被for...in,Object.keys()枚舉writable: true | false, //對象是否可被賦值get:function(){} | undefined, set:function(){} | undefined }4、簡單雙向綁定代碼
<body><div id="demo"></div><input type="text" id="inp"> </body> <script type="text/javascript">var obj = {};var demo = document.querySelector('#demo')var inp = document.querySelector('#inp')Object.defineProperty(obj, 'name', {get: function() {return val;},set: function(newVal) { //當該屬性被賦值的時候觸發inp.value = newVal;demo.innerHTML = newVal;}})inp.addEventListener('input', function(e) {// 給obj的name屬性賦值,進而觸發該屬性的set方法obj.name = e.target.value;});obj.name = '測試'; //在給obj設置name屬性的時候,觸發了set這個方法 </script>由上得出Object.defineProperty可以先實現簡單的雙向綁定,但是如果有100個、1000個dom,我們不可能一個一個設置其值,這樣效率太低。這樣我們就要運用到發布訂閱模式
5、發布者-訂閱者模式
? 發布者-訂閱者模式定義了對象間的一種一對多的依賴關系,只要當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新,解決了主體對象與觀察者之間功能的耦合,即一個對象狀態改變給其他對象通知的問題。
? (ps:我們去商店買可樂時被老板告訴可樂售罄,但是老板告知你們可以添加到商店的微信群中,等可樂到貨后,我在通知你們。)
這就是一個簡單的發布者-訂閱者模式,可樂是觀察對象,我們是訂閱者,老板是觀察者,微信群是訂閱器,當老板知道可樂到貨后,就在微信群中通知我們,我們就回去買可樂。
同理vue也是這樣做的:
? 我們new vue({})傳入的data就是我們監聽器(Observer )的觀察對象,當初始化的時候,我們要把data的值默認渲染在dom中,在dom中使用({{}},v-model,v-bind)data的值就是訂閱者,在初始化的時候就要把訂閱者添加到訂閱器(Dep)中,當data的值發生的改變時,會通知到去告訴訂閱者們(Watcher)更新數據,最后指令解析器( Compile)解析對應的指令,進而會執行對應的更新函數,從而更新視圖。
? 1、實現一個數據監聽器Observer,能夠對數據對象的所有屬性進行監聽,如有變動可拿到最新值并通知訂閱者
? 2、實現一個指令解析器Compile,對每個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數
? 3、實現一個Watcher,作為連接Observer和Compile的橋梁,能夠訂閱并收到每個屬性變動的通知,執行指令綁定的相應回調函數,從而更新視圖
6、各部分實現
6、1監聽器Observer
? 監聽器的作用就是去監聽數據的每一個屬性,我們上面也說了使用 Object.defineProperty 方法,當我們監聽到屬性發生變化之后我們需要通知 Watcher 訂閱者執行更新函數去更新視圖,在這個過程中我們可能會有很多個訂閱者 Watcher 所以我們要創建一個容器 Dep 去做一個統一的管理。
以上我們就創建了一個監聽器 Observer,我們現在可以嘗試一下給一個對象添加監聽然后改變屬性會有何變化。這是侯監聽a,并修改a的值就會打印監聽成功
6.2、訂閱者Watcher
Watcher 主要是接受屬性變化的通知,然后去執行更新函數去更新視圖,所以我們做的主要是有兩步:
6、3解析器Compile
Compile 的主要作用一個是用來解析指令初始化模板,一個是用來添加添加訂閱者,綁定更新函數。
7、具體實現
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <body><div id="app"><input type="text" v-model="msg"></div></body> <script>//訂閱器class Dep {constructor() {this.watchers = [];}//添加add(Watcher) {this.watchers.push(Watcher);}//通知notify() {this.watchers.forEach(watcher => {watcher.update();})}}//觀察者class Watcher {constructor(vm, key, callback) {this.callback = callback;this.vm = vm;this.key = key;Dep.target = thisthis.value = this.vm[this.key];Dep.target = null}update() {this.callback();}}class myVue {constructor({el,data}) {this.dom = document.querySelector(el);this.data = data;//初始化數據this.initData();//初始化v-modelthis.initVModel();}initData() {Object.entries(this.data).forEach(([key, value]) => {let reactiveValue = value;const dep = new Dep();Object.defineProperty(this, key, {configurable: true,enumerable:true,get() {Dep.target && dep.add(Dep.target)return reactiveValue;},set(newVal) {if(newVal != reactiveValue) {reactiveValue = newVal;//觸發通知dep.notify();}}})});}initVModel() {const nodes = this.dom.querySelectorAll('[v-model]');nodes.forEach(node =>{const key = node.getAttribute('v-model');node.value = this[key];new Watcher(this,key,()=>{node.value = this[key]; })node.addEventListener('input', ev=>{this[key] = ev.target.value;})})}}</script><script>const vm = new myVue({el: "#app",data: {msg: "abc"}}) </script></html>8、總結
? 首先我們為每個vue屬性用Object.defineProperty()實現數據劫持,為每個屬性分配一個訂閱者集合的管理數組dep;
然后在編譯的時候在該屬性的數組dep中添加訂閱者,Vue中的v-model會添加一個訂閱者,{{}}也會,v-bind也會;
最后修改值就會為該屬性賦值,觸發該屬性的set方法,在set方法內通知訂閱者數組dep,訂閱者數組循環調用各訂閱者的update方法更新視圖。
總結
以上是生活随笔為你收集整理的【vue双向绑定原理浅析】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国科大学习资料--最优化计算方法(王晓)
- 下一篇: vue双向绑定经典案例