您好,登錄后才能下訂單哦!
這篇文章主要介紹了vue2.x雙向數據綁定原理是什么的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇vue2.x雙向數據綁定原理是什么文章都會有所收獲,下面我們一起來看看吧。
雙向數據綁定原理主要運用了發布訂閱模式來實現的,通過Object.defineProperty對數據劫持,觸發getter,setter方法。數據變化時通知訂閱者watcher觸發回調視圖更新。主要有四個重要的角色:
監聽器Observer:劫持并監聽所有屬性,如果有變動的,就通知訂閱者。
訂閱器 Dep:收集訂閱者,對監聽器 Observer 和 訂閱者 Watcher 進行統一管理
訂閱者Watcher:收到屬性的變化通知并執行相應的函數,從而更新視圖。
解析器Compile:掃描和解析每個節點的相關指令,并根據初始化模板數據以及初始化相應的訂閱器
寫一個簡易的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的原型對象添加_init
方法進行初始化,主要干這幾件事:
接受傳過來的options
,并聲明$options
,$el
,$data
,$methods
proxy代理,代理什么?this.$data
代理為 this
,這樣我們直接就可以this.變量值
observer對data數據進行監聽,變成響應式數據
compiler編譯代碼
export function Vue(options = {}) { this._init(options) } Vue.prototype._init = function (options) { this.$options = options; //假設這里就是一個el,已經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監聽,對data中數據變成響應式 new Compiler(this); }
proxy接收兩個參數,一個是this(vue實例化對象),一個是需要代理的對象(this.$data),舉個例子來說就是不使用this. $options.message
了,直接使用 this.message
獲取數據。主要通過Object.defineProperty數據劫持,觸發屬性的getter或者setter方法。當然數據為NaN時,則不繼續執行,故需要寫一個方法進行判斷。
// 把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,則不繼續執行 return val === newVal || (Number.isNaN(val)) && (Number.isNaN(newVal)) }
對data數據進監聽,考慮到數據有嵌套,如果數據類型為object則需要遞歸循環遍歷監聽數據,一個非常出名的監聽方法為defineReactive
,接收三個參數,一個數據data,一個屬性key,一個數值data[key]。那么observer監聽數據主要做了什么事?
初始化:遞歸循環數據,批量進行響應式處理
獲取數據時:收集依賴,每一個響應式數據都有一個依賴,把依賴添加到dep中。
修改數據時:新增加的數據也不是響應式的,所以需要walk一下,將新增加的數據變成響應式。比如:this.A={name:'zhangsan'},然后修改后變成this.A = {age:18},剛開始A的值已經做過響應式了,但是修改后的值沒有,所以需要進行walk一下。另外數據修改更新后,需要通知watcher進行頁面更新渲染。
function observer(data) { new Observer(data) } // 對data監聽,把數據變成響應式 class Observer { constructor(data) { this.walk(data) } //批量對數據進行監聽 walk(data) { if (data && typeof data === 'object') { Object.keys(data).forEach(key => this.defineReactive(data, key, data[key])) } } //把每一個data里面的數據收集起來 defineReactive(obj, key, value) { let that = this; this.walk(value);//遞歸 let dep = new Dep(); Object.defineProperty(obj, key, { configurable: true, enumerable: true, get() { // 4一旦獲取數據,把watcher收集起來,給每一個數據加一個依賴的收集 //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; //添加的新值也不是響應式的,所以需要調用walk that.walk(newValue); //有了watcher之后,修改時就可以調用update方法 //6 重新set時就通知更新 dep.notify() } } }) } }
數據改變需要通知視圖層進行更新,更新僅需要調用Watcher中的update方法,然后執行cb(視圖更新回調函數)。Watcher干了啥事?
初始化:獲取vue實例vm,屬性key,回調cb。注冊全局變量Dep.target=this,this即Watcher本身,緩存vm[key],this._old=vm[key]表達式會執行屬性key的getter方法,getter方法為該屬性添加依賴,放到dep中,每一個屬性都會有一個依賴。
數據更新時:調用update方法,執行回調cb
// watcher和dep的組合就是發布訂閱者模式 // 視圖更新 // 數據改變,視圖才會更新,需要去觀察 // 1 new Watcher(vm, 'num', () => { 更新視圖上的num顯示 }) class Watcher { constructor(vm, key, cb) { this.vm = vm; this.key = key; this.cb = cb;//試圖更新的函數 Dep.target = this;//2.全局變量,放的就是Watcher自己 // console.log(vm[key], 'vm[key]') this.__old = vm[key];//3.一旦進行了這句賦值。就會觸發這個值得getter,會執行Observer中的get方法 Dep.target = null; } //執行所有的cb函數 update() { let newVal = this.vm[this.key]; if (!isSameVal(newVal, this.__old)) this.cb(newVal) } }
屬性變化可能是多個,所以就需要一個訂閱器來收集這些訂閱者。Dep主要完成什么工作?
初始化:new set 初始化watchers
獲取數據時:當Dep.target && dep.add(Dep.target)成立時,執行add,收集訂閱者。其中Dep.target指的是Watcher本身,Watcher中含有update方法。
數據更新時:調用notify方法,所有的watcher都執行update方法
// 每一個數據都要有一個 dep 的依賴 class Dep { constructor() { this.watchers = new Set(); } add(watcher) { console.log(watcher, 'watcher') if (watcher && watcher.update) this.watchers.add(watcher) } //7讓所有的watcher執行update方法 notify() { console.log('333333') console.log(this.watchers, 'watchers') this.watchers.forEach(watc => watc.update()) } }
編譯器主要的工作是遞歸編譯#app下的所有節點內容。主要做了以下幾件事:
初始化:獲取vm,并對掛載元素進行處理,分為文本節點處理,元素節點處理
文本節點處理:當掛載節點是文本節點的話,判斷node.textContent是否有{{}},RegExp.$1取出雙括號包裹的屬性名。然后通過replace進行正則替換,用vm[key]取代之前的node.textContent內容。
元素節點處理:當掛載節點是元素節點的話,可能會有多個,所以需要循環處理。匹配到以v-開頭的指令時獲取它的值value,然后進行update更新,本文里的更新有兩種,一種是針對以v-開頭屬性值為model,另一種是針對v-開頭的屬性值為click。
model:先對node.value進行賦值,然后再對賦的值進行響應式處理
click:注冊監聽函數,執行click事件。
初始化編譯器流程圖如下所示:
數據修改時,因為初始化已經對數據做了響應式處理,所以當修改數據時,首先會走observer中的get方法,由于初始化已經對該數據進行監聽,添加了watcher,并且此時Dep.target為null,所以不會再次收集訂閱者信息,而是去通知視圖進行更新,走了set中的notify,notify去通知所有的watcher去執行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為類數組 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) }) } //文本節點處理 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 => { //數據改變時的更新 node.textContent = val; }) } } //元素節點 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)) } } }
元素節點中node.attributes如下:
//以下面代碼為例 <input type="text" v-model="num">
reg.exec用來檢索字符串中的正則表達式的匹配,每次匹配完成后,reg.lastIndex被設定為匹配命中的結束位置。
reg.exec傳入其它語句時,lastIndex不會自動重置為0,需要手動重置 reg.exec匹配結果可以直接從其返回值讀取
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
測試字符串是否與正則表達式匹配
保存了最近1次exec或test執行產生的子表達式命中匹配。該特性是非標準的,請盡量不要在生產環境中使用它
用于檢測字符串是否以指定的子字符串開始。如果是以指定的子字符串開頭返回 true,否則 false,該方法對大小寫敏感。
var str = "Hello world, welcome to the Runoob."; var n = str.startsWith("Hello");//true
關于“vue2.x雙向數據綁定原理是什么”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“vue2.x雙向數據綁定原理是什么”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。