溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

vue2.x雙向數(shù)據(jù)綁定原理是什么

發(fā)布時間:2023-02-27 10:40:32 來源:億速云 閱讀:90 作者:iii 欄目:開發(fā)技術

這篇文章主要介紹了vue2.x雙向數(shù)據(jù)綁定原理是什么的相關知識,內(nèi)容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇vue2.x雙向數(shù)據(jù)綁定原理是什么文章都會有所收獲,下面我們一起來看看吧。

    前言

    雙向數(shù)據(jù)綁定原理主要運用了發(fā)布訂閱模式來實現(xiàn)的,通過Object.defineProperty對數(shù)據(jù)劫持,觸發(fā)getter,setter方法。數(shù)據(jù)變化時通知訂閱者watcher觸發(fā)回調(diào)視圖更新。主要有四個重要的角色:

    • 監(jiān)聽器Observer:劫持并監(jiān)聽所有屬性,如果有變動的,就通知訂閱者。

    • 訂閱器 Dep:收集訂閱者,對監(jiān)聽器 Observer 和 訂閱者 Watcher 進行統(tǒng)一管理

    • 訂閱者Watcher:收到屬性的變化通知并執(zhí)行相應的函數(shù),從而更新視圖。

    • 解析器Compile:掃描和解析每個節(jié)點的相關指令,并根據(jù)初始化模板數(shù)據(jù)以及初始化相應的訂閱器

    vue2.x雙向數(shù)據(jù)綁定原理是什么

    一、index.html文件

    寫一個簡易的vue代碼,實例化Vue

    	<script type="module">
    		import { Vue } from "./vue.js "
    		let vm = new Vue({
    			el: document.querySelector('#app'),
    			data: {
    				message: "Hello,luyu",
    				num: "33"
    			},
    			methods: {
    				increase() {
    					this.num++;
    				},
    			}
    		})
    	</script>
    	<div id="app">
    		<h2>{{message}}</h2>
    		<h3>{{num}}</h3>
    		<input type="text" v-model="message">
    		<input type="text" v-model="num">
    		<button v-on:click="increase">【+】</button>
    	</div>

    二、vue.js文件

    在vue的原型對象添加_init方法進行初始化,主要干這幾件事:

    • 接受傳過來的options,并聲明$options,$el ,$data ,$methods

    • proxy代理,代理什么?this.$data 代理為 this,這樣我們直接就可以this.變量值

    • observer對data數(shù)據(jù)進行監(jiān)聽,變成響應式數(shù)據(jù)

    • compiler編譯代碼

    export function Vue(options = {}) {
    	this._init(options)
    }
    Vue.prototype._init = function (options) {
    	this.$options = options;
    	//假設這里就是一個el,已經(jīng)querySelector
    	this.$el = options.el;
    	this.$data = options.data;
    	this.$methods = options.methods;
    	// beforeCreate--initState--initData
    	proxy(this, this.$data)
    	//observer()
    	observer(this.$data)//對data監(jiān)聽,對data中數(shù)據(jù)變成響應式
    	new Compiler(this);
    }

    1.proxy代理發(fā)生了什么?

    proxy接收兩個參數(shù),一個是this(vue實例化對象),一個是需要代理的對象(this.$data),舉個例子來說就是不使用this. $options.message了,直接使用 this.message獲取數(shù)據(jù)。主要通過Object.defineProperty數(shù)據(jù)劫持,觸發(fā)屬性的getter或者setter方法。當然數(shù)據(jù)為NaN時,則不繼續(xù)執(zhí)行,故需要寫一個方法進行判斷。

    // 把this.$data 代理到 this
    function proxy(target, data) {
    	Object.keys(data).forEach(key => {
    		Object.defineProperty(target, key, {
    			enumerable: true,
    			configurable: true,
    			get() {
    				return data[key]
    			},
    			set(newValue) {
    				//需要考慮NaN的情況,故需要修改以下代碼
    				// if (data[key] !== newValue) data[key] = newValue
    				if (!isSameVal(data[key], newValue)) data[key] = newValue;
    			},
    		})
    	})
    }
    function isSameVal (val,newVal){
       //如果新值=舊值或者新值、舊值有一個為NaN,則不繼續(xù)執(zhí)行
       return val === newVal || (Number.isNaN(val)) && (Number.isNaN(newVal))
    }

    2.observer監(jiān)聽數(shù)據(jù)

    對data數(shù)據(jù)進監(jiān)聽,考慮到數(shù)據(jù)有嵌套,如果數(shù)據(jù)類型為object則需要遞歸循環(huán)遍歷監(jiān)聽數(shù)據(jù),一個非常出名的監(jiān)聽方法為defineReactive,接收三個參數(shù),一個數(shù)據(jù)data,一個屬性key,一個數(shù)值data[key]。那么observer監(jiān)聽數(shù)據(jù)主要做了什么事?

    • 初始化:遞歸循環(huán)數(shù)據(jù),批量進行響應式處理

    • 獲取數(shù)據(jù)時:收集依賴,每一個響應式數(shù)據(jù)都有一個依賴,把依賴添加到dep中。

    • 修改數(shù)據(jù)時:新增加的數(shù)據(jù)也不是響應式的,所以需要walk一下,將新增加的數(shù)據(jù)變成響應式。比如:this.A={name:'zhangsan'},然后修改后變成this.A = {age:18},剛開始A的值已經(jīng)做過響應式了,但是修改后的值沒有,所以需要進行walk一下。另外數(shù)據(jù)修改更新后,需要通知watcher進行頁面更新渲染。


    function observer(data) {
    	new Observer(data)
    }
    // 對data監(jiān)聽,把數(shù)據(jù)變成響應式
    class Observer {
    	constructor(data) {
    		this.walk(data)
    	}
    	//批量對數(shù)據(jù)進行監(jiān)聽
    	walk(data) {
    		if (data && typeof data === 'object') {
    			Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]))
    		}
    	}
    	//把每一個data里面的數(shù)據(jù)收集起來
    	defineReactive(obj, key, value) {
    		let that = this;
    		this.walk(value);//遞歸
    		
    		let dep = new Dep();
    
    		Object.defineProperty(obj, key, {
    			configurable: true,
    			enumerable: true,
    			get() {
    				// 4一旦獲取數(shù)據(jù),把watcher收集起來,給每一個數(shù)據(jù)加一個依賴的收集
    				//5num中的dep,就有了這個watcher
    				console.log(Dep.target, 'Dep.target')
    				Dep.target && dep.add(Dep.target)
    				return value
    			},
    			set(newValue) {
    				if (!isSameVal(value, newValue)) {
    					value = newValue;
    					//添加的新值也不是響應式的,所以需要調(diào)用walk 
    					that.walk(newValue);
    					//有了watcher之后,修改時就可以調(diào)用update方法 
    					//6 重新set時就通知更新
    					dep.notify()
    				}
    			}
    		})
    	}
    }

    3.訂閱者Watcher

    數(shù)據(jù)改變需要通知視圖層進行更新,更新僅需要調(diào)用Watcher中的update方法,然后執(zhí)行cb(視圖更新回調(diào)函數(shù))。Watcher干了啥事?

    • 初始化:獲取vue實例vm,屬性key,回調(diào)cb。注冊全局變量Dep.target=this,this即Watcher本身,緩存vm[key],this._old=vm[key]表達式會執(zhí)行屬性key的getter方法,getter方法為該屬性添加依賴,放到dep中,每一個屬性都會有一個依賴。

    • 數(shù)據(jù)更新時:調(diào)用update方法,執(zhí)行回調(diào)cb

    // watcher和dep的組合就是發(fā)布訂閱者模式
    // 視圖更新
    // 數(shù)據(jù)改變,視圖才會更新,需要去觀察
    // 1 new Watcher(vm, 'num', () => { 更新視圖上的num顯示 })
    class Watcher {
    	constructor(vm, key, cb) {
    		this.vm = vm;
    		this.key = key;
    		this.cb = cb;//試圖更新的函數(shù)
    
    		Dep.target = this;//2.全局變量,放的就是Watcher自己
    		//
    		console.log(vm[key], 'vm[key]')
    		this.__old = vm[key];//3.一旦進行了這句賦值。就會觸發(fā)這個值得getter,會執(zhí)行Observer中的get方法
    		Dep.target = null;
    	}
    	//執(zhí)行所有的cb函數(shù)
    	update() {
    		let newVal = this.vm[this.key];
    		if (!isSameVal(newVal, this.__old)) this.cb(newVal)
    	}
    }

    4.訂閱器Dep

    屬性變化可能是多個,所以就需要一個訂閱器來收集這些訂閱者。Dep主要完成什么工作?

    • 初始化:new set 初始化watchers

    • 獲取數(shù)據(jù)時:當Dep.target && dep.add(Dep.target)成立時,執(zhí)行add,收集訂閱者。其中Dep.target指的是Watcher本身,Watcher中含有update方法。

    • 數(shù)據(jù)更新時:調(diào)用notify方法,所有的watcher都執(zhí)行update方法

    // 每一個數(shù)據(jù)都要有一個 dep 的依賴
    class Dep {
    	constructor() {
    		this.watchers = new Set();
    	}
    	add(watcher) { 
    		console.log(watcher, 'watcher')
    		if (watcher && watcher.update) this.watchers.add(watcher)
    	}
    	//7讓所有的watcher執(zhí)行update方法
    	notify() {
    		console.log('333333')
    		console.log(this.watchers, 'watchers')
    		this.watchers.forEach(watc => watc.update())
    	}
    }

    5.編譯器Compiler

    編譯器主要的工作是遞歸編譯#app下的所有節(jié)點內(nèi)容。主要做了以下幾件事:

    • 初始化:獲取vm,并對掛載元素進行處理,分為文本節(jié)點處理,元素節(jié)點處理

    • 文本節(jié)點處理:當掛載節(jié)點是文本節(jié)點的話,判斷node.textContent是否有{{}},RegExp.$1取出雙括號包裹的屬性名。然后通過replace進行正則替換,用vm[key]取代之前的node.textContent內(nèi)容。

    • 元素節(jié)點處理:當掛載節(jié)點是元素節(jié)點的話,可能會有多個,所以需要循環(huán)處理。匹配到以v-開頭的指令時獲取它的值value,然后進行update更新,本文里的更新有兩種,一種是針對以v-開頭屬性值為model,另一種是針對v-開頭的屬性值為click。

    • model:先對node.value進行賦值,然后再對賦的值進行響應式處理

    • click:注冊監(jiān)聽函數(shù),執(zhí)行click事件。

    初始化編譯器流程圖如下所示:

    vue2.x雙向數(shù)據(jù)綁定原理是什么

    數(shù)據(jù)修改時,因為初始化已經(jīng)對數(shù)據(jù)做了響應式處理,所以當修改數(shù)據(jù)時,首先會走observer中的get方法,由于初始化已經(jīng)對該數(shù)據(jù)進行監(jiān)聽,添加了watcher,并且此時Dep.target為null,所以不會再次收集訂閱者信息,而是去通知視圖進行更新,走了set中的notify,notify去通知所有的watcher去執(zhí)行update方法。流程圖如下所示:

    vue2.x雙向數(shù)據(jù)綁定原理是什么

    class Compiler {
    	constructor(vm) {
    		this.el = vm.$el;
    		this.vm = vm;
    		this.methods = vm.$methods;
    		// console.log(vm.$methods, 'vm.$methods')
    		this.compile(vm.$el)
    	}
    	compile(el) {
    		let childNodes = el.childNodes;
    		//childNodes為類數(shù)組
    		Array.from(childNodes).forEach(node => {
    			if (node.nodeType === 3) {
    				this.compileText(node)
    			} else if (node.nodeType === 1) {
    				this.compileElement(node)
    			}
    			//遞歸 
    			if (node.childNodes && node.childNodes.length) this.compile(node)
    		})
    	}
    	//文本節(jié)點處理
    	compileText(node) {
    		//匹配出來 {{massage}}
    		let reg = /\{\{(.+?)\}\}/;
    		let value = node.textContent;
    		if (reg.test(value)) {
    			let key = RegExp.$1.trim()
    			// 開始時賦值
    			node.textContent = value.replace(reg, this.vm[key]);
    			//添加觀察者
    			new Watcher(this.vm, key, val => {
    				//數(shù)據(jù)改變時的更新
    				node.textContent = val;
    			})
    		}
    	}
    	//元素節(jié)點
    	compileElement(node) {
    		//簡化,只做v-on,v-model的匹配
    		if (node.attributes.length) {
    			Array.from(node.attributes).forEach(attr => {
    				let attrName = attr.name;
    				if (attrName.startsWith('v-')) {
    					//v-指令匹配成功可能是是v-on,v-model
    					attrName = attrName.indexOf(':') > -1 ? attrName.substr(5) : attrName.substr(2)
    					let key = attr.value;
    					this.update(node, key, attrName, this.vm[key])
    				}
    			})
    		}
    	}
    	update(node, key, attrName, value) {
    		console.log('更新')
    		if (attrName == "model") {
    			node.value = value;
    			new Watcher(this.vm, key, val => node.value = val);
    			node.addEventListener('input', () => {
    				this.vm[key] = node.value;
    			})
    		} else if (attrName == 'click') {
    			// console.log(this.methods,'key')
    			node.addEventListener(attrName, this.methods[key].bind(this.vm))
    		}
    	}
    }

    元素節(jié)點中node.attributes如下:

        //以下面代碼為例
    	<input type="text" v-model="num">

    vue2.x雙向數(shù)據(jù)綁定原理是什么

    三、文中用到的js基礎

    1.reg.exec

    reg.exec用來檢索字符串中的正則表達式的匹配,每次匹配完成后,reg.lastIndex被設定為匹配命中的結(jié)束位置。
    reg.exec傳入其它語句時,lastIndex不會自動重置為0,需要手動重置 reg.exec匹配結(jié)果可以直接從其返回值讀取

    let  reg=/jpg|jpg|jpeg/gi
    let str='jpg'
    if(reg.test(str)){
          // true
    }
    if(reg.test(str)){
          // false
    }
    if(reg.test(str)){
          // true
    }
    if(reg.test(str)){
          // false
    }
    (/jpg|jpg|jpeg/gi).test(str)  // true
    (/jpg|jpg|jpeg/gi).test(str)  // true
    (/jpg|jpg|jpeg/gi).test(str)  // true

    2.reg.test

    測試字符串是否與正則表達式匹配

    3.RegExp.$x

    保存了最近1次exec或test執(zhí)行產(chǎn)生的子表達式命中匹配。該特性是非標準的,請盡量不要在生產(chǎn)環(huán)境中使用它

    4.startsWith

    用于檢測字符串是否以指定的子字符串開始。如果是以指定的子字符串開頭返回 true,否則 false,該方法對大小寫敏感。

    var str = "Hello world, welcome to the Runoob.";
    var n = str.startsWith("Hello");//true

    關于“vue2.x雙向數(shù)據(jù)綁定原理是什么”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“vue2.x雙向數(shù)據(jù)綁定原理是什么”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

    vue
    AI