欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue2.x雙向數(shù)據(jù)綁定原理解析

 更新時(shí)間:2023年02月27日 09:47:59   作者:不叫貓先生  
雙向數(shù)據(jù)綁定原理主要運(yùn)用了發(fā)布訂閱模式來(lái)實(shí)現(xiàn)的,通過(guò)Object.defineProperty對(duì)數(shù)據(jù)劫持,觸發(fā)getter,setter方法,這篇文章主要介紹了vue2.x雙向數(shù)據(jù)綁定原理,需要的朋友可以參考下

前言

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

  • 監(jiān)聽(tīng)器Observer:劫持并監(jiān)聽(tīng)所有屬性,如果有變動(dòng)的,就通知訂閱者。
  • 訂閱器 Dep:收集訂閱者,對(duì)監(jiān)聽(tīng)器 Observer 和 訂閱者 Watcher 進(jìn)行統(tǒng)一管理
  • 訂閱者Watcher:收到屬性的變化通知并執(zhí)行相應(yīng)的函數(shù),從而更新視圖。
  • 解析器Compile:掃描和解析每個(gè)節(jié)點(diǎn)的相關(guān)指令,并根據(jù)初始化模板數(shù)據(jù)以及初始化相應(yīng)的訂閱器

一、index.html文件

寫(xiě)一個(gè)簡(jiǎn)易的vue代碼,實(shí)例化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">
		<h1>{{message}}</h1>
		<h2>{{num}}</h2>
		<input type="text" v-model="message">
		<input type="text" v-model="num">
		<button v-on:click="increase">【+】</button>
	</div>

二、vue.js文件

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

  • 接受傳過(guò)來(lái)的options,并聲明$options,$el ,$data ,$methods
  • proxy代理,代理什么?this.$data 代理為 this,這樣我們直接就可以this.變量值
  • observer對(duì)data數(shù)據(jù)進(jìn)行監(jiān)聽(tīng),變成響應(yīng)式數(shù)據(jù)
  • compiler編譯代碼
export function Vue(options = {}) {
	this._init(options)
}
Vue.prototype._init = function (options) {
	this.$options = options;
	//假設(shè)這里就是一個(gè)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)//對(duì)data監(jiān)聽(tīng),對(duì)data中數(shù)據(jù)變成響應(yīng)式
	new Compiler(this);
}

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

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

// 把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){
   //如果新值=舊值或者新值、舊值有一個(gè)為NaN,則不繼續(xù)執(zhí)行
   return val === newVal || (Number.isNaN(val)) && (Number.isNaN(newVal))
}

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

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

  • 初始化:遞歸循環(huán)數(shù)據(jù),批量進(jìn)行響應(yīng)式處理
  • 獲取數(shù)據(jù)時(shí):收集依賴(lài),每一個(gè)響應(yīng)式數(shù)據(jù)都有一個(gè)依賴(lài),把依賴(lài)添加到dep中。
  • 修改數(shù)據(jù)時(shí):新增加的數(shù)據(jù)也不是響應(yīng)式的,所以需要walk一下,將新增加的數(shù)據(jù)變成響應(yīng)式。比如:this.A={name:'zhangsan'},然后修改后變成this.A = {age:18},剛開(kāi)始A的值已經(jīng)做過(guò)響應(yīng)式了,但是修改后的值沒(méi)有,所以需要進(jìn)行walk一下。另外數(shù)據(jù)修改更新后,需要通知watcher進(jìn)行頁(yè)面更新渲染。

function observer(data) {
	new Observer(data)
}
// 對(duì)data監(jiān)聽(tīng),把數(shù)據(jù)變成響應(yīng)式
class Observer {
	constructor(data) {
		this.walk(data)
	}
	//批量對(duì)數(shù)據(jù)進(jìn)行監(jiān)聽(tīng)
	walk(data) {
		if (data && typeof data === 'object') {
			Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]))
		}
	}
	//把每一個(gè)data里面的數(shù)據(jù)收集起來(lái)
	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收集起來(lái),給每一個(gè)數(shù)據(jù)加一個(gè)依賴(lài)的收集
				//5num中的dep,就有了這個(gè)watcher
				console.log(Dep.target, 'Dep.target')
				Dep.target && dep.add(Dep.target)
				return value
			},
			set(newValue) {
				if (!isSameVal(value, newValue)) {
					value = newValue;
					//添加的新值也不是響應(yīng)式的,所以需要調(diào)用walk 
					that.walk(newValue);
					//有了watcher之后,修改時(shí)就可以調(diào)用update方法 
					//6 重新set時(shí)就通知更新
					dep.notify()
				}
			}
		})
	}
}

3.訂閱者Watcher

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

  • 初始化:獲取vue實(shí)例vm,屬性key,回調(diào)cb。注冊(cè)全局變量Dep.target=this,this即Watcher本身,緩存vm[key],this._old=vm[key]表達(dá)式會(huì)執(zhí)行屬性key的getter方法,getter方法為該屬性添加依賴(lài),放到dep中,每一個(gè)屬性都會(huì)有一個(gè)依賴(lài)。
  • 數(shù)據(jù)更新時(shí):調(diào)用update方法,執(zhí)行回調(diào)cb
// watcher和dep的組合就是發(fā)布訂閱者模式
// 視圖更新
// 數(shù)據(jù)改變,視圖才會(huì)更新,需要去觀察
// 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.一旦進(jìn)行了這句賦值。就會(huì)觸發(fā)這個(gè)值得getter,會(huì)執(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

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

  • 初始化:new set 初始化watchers
  • 獲取數(shù)據(jù)時(shí):當(dāng)Dep.target && dep.add(Dep.target)成立時(shí),執(zhí)行add,收集訂閱者。其中Dep.target指的是Watcher本身,Watcher中含有update方法。
  • 數(shù)據(jù)更新時(shí):調(diào)用notify方法,所有的watcher都執(zhí)行update方法
// 每一個(gè)數(shù)據(jù)都要有一個(gè) dep 的依賴(lài)
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é)點(diǎn)內(nèi)容。主要做了以下幾件事:

  • 初始化:獲取vm,并對(duì)掛載元素進(jìn)行處理,分為文本節(jié)點(diǎn)處理,元素節(jié)點(diǎn)處理
  • 文本節(jié)點(diǎn)處理:當(dāng)掛載節(jié)點(diǎn)是文本節(jié)點(diǎn)的話,判斷node.textContent是否有{{}},RegExp.$1取出雙括號(hào)包裹的屬性名。然后通過(guò)replace進(jìn)行正則替換,用vm[key]取代之前的node.textContent內(nèi)容。
  • 元素節(jié)點(diǎn)處理:當(dāng)掛載節(jié)點(diǎn)是元素節(jié)點(diǎn)的話,可能會(huì)有多個(gè),所以需要循環(huán)處理。匹配到以v-開(kāi)頭的指令時(shí)獲取它的值value,然后進(jìn)行update更新,本文里的更新有兩種,一種是針對(duì)以v-開(kāi)頭屬性值為model,另一種是針對(duì)v-開(kāi)頭的屬性值為click。
  • model:先對(duì)node.value進(jìn)行賦值,然后再對(duì)賦的值進(jìn)行響應(yīng)式處理
  • click:注冊(cè)監(jiān)聽(tīng)函數(shù),執(zhí)行click事件。

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

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

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為類(lèi)數(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é)點(diǎn)處理
	compileText(node) {
		//匹配出來(lái) {{massage}}
		let reg = /\{\{(.+?)\}\}/;
		let value = node.textContent;
		if (reg.test(value)) {
			let key = RegExp.$1.trim()
			// 開(kāi)始時(shí)賦值
			node.textContent = value.replace(reg, this.vm[key]);
			//添加觀察者
			new Watcher(this.vm, key, val => {
				//數(shù)據(jù)改變時(shí)的更新
				node.textContent = val;
			})
		}
	}
	//元素節(jié)點(diǎn)
	compileElement(node) {
		//簡(jiǎn)化,只做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é)點(diǎn)中node.attributes如下:

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

三、文中用到的js基礎(chǔ)

1.reg.exec

reg.exec用來(lái)檢索字符串中的正則表達(dá)式的匹配,每次匹配完成后,reg.lastIndex被設(shè)定為匹配命中的結(jié)束位置。
reg.exec傳入其它語(yǔ)句時(shí),lastIndex不會(huì)自動(dòng)重置為0,需要手動(dòng)重置 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

測(cè)試字符串是否與正則表達(dá)式匹配

3.RegExp.$x

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

4.startsWith

用于檢測(cè)字符串是否以指定的子字符串開(kāi)始。如果是以指定的子字符串開(kāi)頭返回 true,否則 false,該方法對(duì)大小寫(xiě)敏感。

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

四、源碼

地址:鏈接跳轉(zhuǎn)

到此這篇關(guān)于vue2.x雙向數(shù)據(jù)綁定原理的文章就介紹到這了,更多相關(guān)vue2.x雙向數(shù)據(jù)綁定內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 在線使用iconfont字體圖標(biāo)的簡(jiǎn)單實(shí)現(xiàn)

    在線使用iconfont字體圖標(biāo)的簡(jiǎn)單實(shí)現(xiàn)

    這篇文章主要介紹了在線使用iconfont字體圖標(biāo)的簡(jiǎn)單實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • web前端vue之vuex單獨(dú)一文件使用方式實(shí)例詳解

    web前端vue之vuex單獨(dú)一文件使用方式實(shí)例詳解

    Vuex 是一個(gè)專(zhuān)為 Vue.js 應(yīng)用程序開(kāi)發(fā)的狀態(tài)管理模式。這篇文章主要介紹了web前端vue:vuex單獨(dú)一文件使用方式,需要的朋友可以參考下
    2018-01-01
  • 基于vue實(shí)現(xiàn)一個(gè)禪道主頁(yè)拖拽效果

    基于vue實(shí)現(xiàn)一個(gè)禪道主頁(yè)拖拽效果

    最近在做一個(gè)基于vue的后臺(tái)管理項(xiàng)目。接下來(lái)通過(guò)本文給大家分析一款基于vue做一個(gè)禪道主頁(yè)拖拽效果,需要的朋友可以參考下
    2019-05-05
  • Vue?FileManagerPlugin?報(bào)錯(cuò)問(wèn)題及解決

    Vue?FileManagerPlugin?報(bào)錯(cuò)問(wèn)題及解決

    這篇文章主要介紹了Vue?FileManagerPlugin?報(bào)錯(cuò)問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • Vue3模板引用的操作方式示例詳解

    Vue3模板引用的操作方式示例詳解

    這篇文章主要為大家介紹了Vue3模板引用的操作方式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • vue實(shí)現(xiàn)雙向綁定和依賴(lài)收集遇到的坑

    vue實(shí)現(xiàn)雙向綁定和依賴(lài)收集遇到的坑

    這篇文章主要介紹了vue的雙向綁定和依賴(lài)收集,主要是通過(guò)Object.defineProperty() 實(shí)現(xiàn)雙向綁定,具體思路代碼大家跟隨小編一起看看吧
    2018-11-11
  • vue+intro.js插件實(shí)現(xiàn)引導(dǎo)功能

    vue+intro.js插件實(shí)現(xiàn)引導(dǎo)功能

    使用 intro.js這個(gè)插件,來(lái)實(shí)現(xiàn)一個(gè)引導(dǎo)性的效果,經(jīng)常在一些新手引導(dǎo)頁(yè)遇到這樣的需求,下面通過(guò)本文給大家分享vue+intro.js插件實(shí)現(xiàn)引導(dǎo)功能,感興趣的朋友一起看看吧
    2024-06-06
  • vue組件傳值的11種方式總結(jié)

    vue組件傳值的11種方式總結(jié)

    這篇文章主要介紹了vue組件傳值的11種方式總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • 在Vue組件中使用 TypeScript的方法

    在Vue組件中使用 TypeScript的方法

    typescript不僅可以約束我們的編碼習(xí)慣,還能起到注釋的作用,當(dāng)我們看到一函數(shù)后我們立馬就能知道這個(gè)函數(shù)的用法。這篇文章主要介紹了在Vue組件中使用 TypeScript的方法,需要的朋友可以參考下
    2018-02-02
  • vue動(dòng)態(tài)繪制四分之三圓環(huán)圖效果

    vue動(dòng)態(tài)繪制四分之三圓環(huán)圖效果

    這篇文章主要介紹了vue動(dòng)態(tài)繪制四分之三圓環(huán)圖效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-09-09

最新評(píng)論