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

一篇文章帶你徹底搞懂VUE響應(yīng)式原理

 更新時間:2022年06月28日 09:03:19   作者:Dddusty  
這篇文章主要介紹了一篇文章帶你徹底搞懂VUE響應(yīng)式原理,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可任意參考一下,需要的朋友可以參考下

首先上圖,下面這張圖,即為MVVM響應(yīng)式原理的整個過程圖,我們本篇都是圍繞著這張圖進(jìn)行分析,所以這張圖是重中之重。

響應(yīng)式原理圖

 一臉懵逼?沒關(guān)系,接下來我們將通過創(chuàng)建一個簡單的MVVM響應(yīng)系統(tǒng)來一步步了解這個上圖中的全過程。全文分為兩大塊,首先介紹實(shí)例模板的編譯過程,然后詳細(xì)介紹響應(yīng)式,這里先介紹編譯是為了給介紹響應(yīng)式奠定基礎(chǔ)。

編譯

我們把我們創(chuàng)建的這個微型響應(yīng)系統(tǒng)命名為miniVue,我們按照平常使用Vue的模式,首先創(chuàng)建一個miniVue的實(shí)例。

<scirpt>
    const vm = new miniVue({
 ? ? ? ?el: '#app',
 ? ? ? ?data: {
 ? ? ? ? ? ?obj: {
 ? ? ? ? ? ? ? ?name: "miniVue",
 ? ? ? ? ? ? ? ?auth: 'xxx'
 ? ? ? ? ?  },
 ? ? ? ? ? ?msg: "this is miniVue",
 ? ? ? ? ? ?htmlStr: "<h3>this is htmlStr</h3>"
 ? ? ?  },
 ? ? ? ?methods: {
 ? ? ? ? ? ?handleClick() {
 ? ? ? ? ? ? ? ?console.log(this);
 ? ? ? ? ?  }
 ? ? ?  }
 ?  });
</scirpt>

我們根據(jù)這個實(shí)例,我們可以創(chuàng)建出miniVue的類,這個類中我們肯定要保存該實(shí)例所綁定的DOM以及數(shù)據(jù)對象data。然后我們要開始解析模板,即解析我們所綁定的DOM

class miniVue {
    constructor(options) {
        this.$el = options.el
 ? ? ? ?this.$data = options.data
 ? ? ? ?this.$options = options
 ?  }
 ? ?if(this.$el) {
        // 解析模板 to Compile
 ?  }
}

這里我們來創(chuàng)建一個compile類來進(jìn)行解析模板的操作

創(chuàng)建compile類

Compile類是用來解析模板的,所以肯定要傳入要解析的DOM。拿到DOM后直接操作這個DOM會導(dǎo)致頁面頻繁的回流和重繪,所以我們把這個DOM放到一個文檔碎片中,然后操作這個文檔碎片。操作這個文檔碎片的過程中我們需要獲取到數(shù)據(jù)對象data中的屬性來填充一些節(jié)點(diǎn)的內(nèi)容,所以我們還需要傳入實(shí)例對象。最后將操作好的文檔碎片追加到原本的DOM上。

class Compile {
 ? ?constructor(el, vm) {
 ? ? ? ?// 判斷的原因是因?yàn)閭魅氲膃l有可能是DOM,也有可能是選擇器例如‘#app'
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
 ? ? ? ?this.vm = vm
 ? ? ? ?// 新建文檔碎片存儲DOM
 ? ? ?  const fragment = this.toFragment(this.el)

 ? ? ? ?// 操作文檔碎片 to handle fragment

 ? ? ? ?// 將操作好的文檔碎片追加到原本的DOM上面
 ? ? ? ?this.el.appendChild(fragment)
 ?  }

 ? ?// 判斷是否為元素節(jié)點(diǎn)
 ? ?isElementNode(node) {
 ? ? ? ?return node.nodeType === 1
 ?  }
 ? ?// dom碎片化
 ? ?toFragment(el) {
 ? ? ? ?const f = document.createDocumentFragment()
        f.appendChild(el.clone(true))
 ?  }
}
// 上面的miniVue實(shí)例相應(yīng)的改為
class miniVue {
    constructor(options) {
        this.$el = options.el
 ? ? ? ?this.$data = options.data
 ? ? ? ?this.$options = options
 ?  }
 ? ?if(this.$el) {
        // 解析模板 to Compile
 ? ? ? ?new Compile(this.$el, this) // 這里的this就是miniVue實(shí)例
 ?  }
}

操作fragment

操作保存好的文檔碎片,我們可以專門定義一個函數(shù),然后把文檔碎片通過參數(shù)傳入進(jìn)來。

操作文檔碎片我們又可以分為兩步。因?yàn)獒槍?strong>文本節(jié)點(diǎn)和元素節(jié)點(diǎn),我們需要進(jìn)行不同的操作,所以我們在遍歷所有節(jié)點(diǎn)后的第一步應(yīng)該先判斷它是元素節(jié)點(diǎn)還是文本節(jié)點(diǎn)。

handleFragment(fragment) {
 ? ?// 獲取文檔碎片的子節(jié)點(diǎn)
    const childNodes =  fragment.childNodes
 ? ?// 遍歷所有子節(jié)點(diǎn)
 ?  [...childNodes].forEach((child) => {
        if(this.isElementNode(child)) {
 ? ? ? ? ? ?// 元素節(jié)點(diǎn)
 ? ? ? ? ? ?this.compileElement(child)
 ? ? ?  } else {
 ? ? ? ? ? ?// 文本節(jié)點(diǎn)
 ? ? ? ? ? ?this.compileText(child)
 ? ? ?  }

 ?      // 遞歸遍歷
 ?      if(child.childNodes && child.childNodes.length) {
 ? ? ? ? ? ?handleFragment(child)
 ? ? ?  }
 ?  })
}

// 同樣的我們需要完善一下compile的構(gòu)造函數(shù)
constructor(el, vm) {
    this.el = this.isElementNode(el) ? el : document.querySelector(el)
 ? ?this.vm = vm
 ? ?// 新建文檔碎片存儲DOM
 ? ?const fragment = this.toFragment(this.el)

 ? ?// 操作文檔碎片 to handle fragment
 ? ?this.handleFragment(fragment)

 ? ?// 將操作好的文檔碎片追加到原本的DOM上面
 ? ?this.el.appendChild(fragment)
}

獲取元素節(jié)點(diǎn)上的信息

元素節(jié)點(diǎn)上的信息主要就是這個元素節(jié)點(diǎn)上面的屬性,然后拿到綁定在節(jié)點(diǎn)上面的vue指令,分離出來vue指令的名稱和值(注意:@開的頭的指令需要額外處理)。然后還有很重要的一步,那就是去掉這些指令(這些指令updater是不認(rèn)的)

compileElement(node) {
    const attrs = node.attributes
 ? ?// 遍歷節(jié)點(diǎn)上的全部屬性
 ?  [...attrs].forEach(({name, value}) => {
 ? ? ? ?// 分類看指令以什么開頭
 ?      if(this.headWithV(name)) {
            // 以v開頭
 ? ? ? ? ? ?const [,directive] = name.split("-") //分離出具體指令
 ? ? ? ? ? ?const [dir,event] = directive.split(":") // 考慮v-on的情況 例如v-on:click

 ? ? ? ? ? ?// 將指令的名稱、值、node節(jié)點(diǎn)、整個vm實(shí)例、事件名(如果有的話)一起傳給最后真正操作的node的函數(shù)
 ? ? ? ? ? ?handleNode[dir](node, value, this.vm, event)

 ? ? ?  }else if(this.headWithoutV(name)) {
            // 以@開投
 ? ? ? ? ? ?const [, event] = name.split("@")
 ? ? ? ? ? ?// 和上面一樣,但是指令名字是確定的,為“on” 因?yàn)锧是v-on的語法糖
 ? ? ? ? ? ?handleNode["on"](node, value, this.vm, event)
 ? ? ?  }
 ?  })
}
?
headWithV(name) {
    return name.startsWith("v-");
}
headWithoutV(name){
 ? ?return name.startsWith("@");
}

獲取文本節(jié)點(diǎn)信息

文本節(jié)點(diǎn)和元素節(jié)點(diǎn)類似,只不過文本節(jié)點(diǎn)的信息存儲在節(jié)點(diǎn)的textContent里面,主要用來替換mustache語法,(雙大括號插值)需要通過正則識別額外處理。如果是正常的文本節(jié)點(diǎn),則不進(jìn)行處理(原模原樣展示即可)。

compileText(node) {
 ? ?const content = node.textContent
 ? ?if(!/{{(.+?)}}/.test(content)) return
 ? ?// 識別到是mustache語法 處理方法其實(shí)和v-text一樣
 ? ?handleNode["text"](node, content,this.vm)
}

操作fragment

前面鋪墊了這么多,終于到了操作文檔碎片這一步了。按照上面的思路,handleNode應(yīng)該是一個對象,里面有多個屬性對應(yīng)不同的指令的處理方法。

// node--操作的node節(jié)點(diǎn)  exp--指令的值(或者是mustache語法內(nèi)部插入的內(nèi)容)  vm--vm實(shí)例  event--事件名稱
const handleNode = {
 ? ?// v-html
 ? ?html(node, exp, vm) {
 ?      // 去vm實(shí)例中找到這個表達(dá)式所對應(yīng)的值
 ? ? ? ?const value = this._get(vm, exp)
 ? ? ? ?// 更新node
 ? ? ? ?updater.htmlUpdater(node, value)
    },
 ? ?// v-model
 ? ?model(node, exp, vm) {
 ? ? ? ?// 同html
 ?      const value = this._get(vm, exp)
 ? ? ? ?updater.modelUpdater(node, value)
    },
 ? ?// v-on
 ? ?on(node, exp, vm, event) {
        // v-on特殊一點(diǎn),我們需要為該node綁定事件監(jiān)聽器
 ? ? ? ?const listener = vm.$options.methods && vm.$options.methods[exp] // 獲取監(jiān)聽器的回調(diào)
 ? ? ? ?// 綁定監(jiān)聽器,注意回調(diào)綁定使用bind把this指向vm實(shí)例,false代表事件冒泡時觸發(fā)監(jiān)聽器
 ? ? ? ?node.addEventListener(event, listener.bind(this), false) 
    },
 ? ?// v-text
 ? ?text(node, exp, vm) {
 ? ? ? ?// v-text是最復(fù)雜的,需要考慮兩種情況,一種是通過v-text指令操作node,另一種則是通過mustache語法操作node,需分類
 ? ? ? ?let value
 ? ? ? ?if(exp.indexOf("{{") !== -1) {
 ? ? ? ? ? ?// mustache語法操作node
 ? ? ? ? ? ?// 捕捉到所有的mustache語法,將其整個替換為vm實(shí)例中屬性對應(yīng)的值
 ? ? ? ? ? ?// 拿我們最初初始化實(shí)例的一個數(shù)據(jù)舉例:{{obj.auth}} -- 'xxx'
 ? ? ? ? ? ?value = exp.replace(/{{(.+?)}}/g, this._get(vm, exp)) 
 ? ? ?  }else {
 ? ? ? ? ? ?// v-text操作node
 ? ? ? ? ? ?value = this._get(vm, exp)
 ? ? ?  }
 ? ? ? ?// 更新node
 ? ? ? ?updater.textUpdater(node, value);
 ?  },
}
?
// 根據(jù)表達(dá)式去數(shù)據(jù)對象里面獲取值
_get(vm, exp) {
 ? ?const segments = exp.split('.')
 ? ?// 這里使用reduce是為了獲取嵌套對象內(nèi)部屬性的值,不熟悉的話去補(bǔ)一補(bǔ)reduce
 ? ?// 比如data.a.b.c,那么每次遍歷的值為data[a],data[a][b],最終結(jié)果是data[a][b][c]
    segments.reduce((pre, key) => {
        return pre[key]
 ?  }, vm.$data)
}

// 更新node (終于到了更新node這一步)
const updater = {
 ? ?textUpdater(node, value) {
        node.textContent = value;
 ?  },
 ? ?htmlUpdater(node, value) {
        node.innerHTML = value;
 ?  },
 ? ?modelUpdater(node, value){
        node.value = value;
 ?  }
}

至此我們已經(jīng)實(shí)現(xiàn)了vue實(shí)例模板編譯,并更新了node,其實(shí)到現(xiàn)在我們還沒有涉及到響應(yīng)式這三個字。下面我們開始介紹本篇的核心,即vue是如何實(shí)現(xiàn)響應(yīng)式的。

響應(yīng)式

數(shù)據(jù)劫持

關(guān)鍵點(diǎn):Object.defineProperty(具體用法參考MDN)

主要目的:為data中每個屬性添加gettersetter,然后在gettersetter中進(jìn)行數(shù)據(jù)劫持

思路很簡單,其實(shí)就是從最外層的data層開始遍歷屬性,通過Object.defineProperty給這些屬性都添加上gettersetter,需要注意對象的嵌套,所以需要使用遞歸來為嵌套的屬性添加gettersetter

function observe(data) {
    if(typeof data !== 'object') return
 ? ?Object.keys(data).forEach((key) => {
 ? ? ? ?defineReactive(data, key, data[key])
 ?  })
}

function defineReactive(data, key, value) {
 ? ?// 遞歸子屬性
    observe(value)

 ? ?Object.defineProperty(data, key, {
        get() {
 ? ? ? ? ? ?// 數(shù)據(jù)劫持 在這個地方進(jìn)行相關(guān)操作
            return value
 ? ? ?  }
 ? ? ? ?set(newVal) {
            if(newVal == value) return
 ? ? ?      value = newVal
 ? ? ?      // 為新數(shù)據(jù)添加getter和setter
 ? ? ?      observe(newVal)
 ? ? ?      // 數(shù)據(jù)劫持 在這個地方進(jìn)行相關(guān)操作
 ?      }
 ?  })
}

收集依賴

依賴其實(shí)說白了,就是數(shù)據(jù)的依賴,data中的某個屬性,可能在DOM中好幾個地方進(jìn)行了使用,那DOM中使用到該屬性的地方就都會產(chǎn)生一個對于該屬性的依賴,也就是watcher。當(dāng)該屬性的值發(fā)生了變化,那么就可以通知watcher來使得頁面中使用到這個屬性的地方進(jìn)行視圖更新。為每個屬性綁定watcher的過程其實(shí)就是訂閱,反過來,當(dāng)屬性的值發(fā)生了變化,通知所有watcher的過程就是發(fā)布。

下面我們來將依賴抽象化,即實(shí)現(xiàn)watcher

class Watcher {
    // data--最外層數(shù)據(jù)對象  exp--表達(dá)式  cb--數(shù)據(jù)更新后需要執(zhí)行的回調(diào)
    // 通過data和exp可以獲取watcher所依賴屬性的具體值
    constructor(data, exp, cb) {
	this.data = data
        this.exp = exp
        this.cb = cb
        // 每次初始化watcher實(shí)例時,對依賴屬性進(jìn)行訂閱
        this.value = this.subscribe()
    }
    // 訂閱
    subscribe() {
        // 獲取依賴屬性的值
	const value = _get(this.data, this.exp)
        return value
    }
    // 更新
    update() {
        // 獲取新值
	this.value = _get(this.data, this.exp)
        cb()
    }
}

// 根據(jù)表達(dá)式去數(shù)據(jù)對象里面獲取值 其實(shí)上面已經(jīng)定義過一個了,功能是一樣的,這里重復(fù)定義加深一下印象,也方便閱讀
function _get(obj, exp) {
    const segments = exp.split('.')
    // 這里使用reduce是為了獲取嵌套對象內(nèi)部屬性的值,不熟悉的話去補(bǔ)一補(bǔ)reduce
    // 比如data.a.b.c,那么每次遍歷的值為data[a],data[a][b],最終結(jié)果是data[a][b][c]
    segments.reduce((pre, key) => {
	return pre[key]
    }, obj)
}

依賴我們大概清楚了,但是我們上面講,需要把一個屬性全部的依賴(watcher)收集起來,所以我們該如何收集依賴呢?

首先我們先想第一個問題,一個屬性會有一個或者好多個watcher,我們應(yīng)該如何保存這些watcher呢,這個我們很容易想到,我們可以專門拿一個數(shù)組保存一個屬性的全部watcher,我們把這個數(shù)組命名為dep(dependency)。

第二個問題,我們應(yīng)該什么時候進(jìn)行收集watcher的操作呢。還記得我們上面提到的訂閱嗎,我們每次初始化watcher時,會為該watcher訂閱屬性,訂閱的過程中我們會首先獲取這個屬性的值,這時就可以發(fā)揮數(shù)據(jù)劫持的作用了,獲取這個屬性值的時候,我們就會進(jìn)到這個屬性的getter方法中,所以我們可以在這個時候完成收集watcher的操作。

第三個問題,我們說watcher的作用其實(shí)就是監(jiān)聽到訂閱屬性的變化(即監(jiān)聽發(fā)布),監(jiān)聽到變化后執(zhí)行其update方法,即執(zhí)行更新回調(diào),來更新視圖。那么我們怎樣才能讓watcher監(jiān)聽到“發(fā)布”呢,這時我們又需要用到數(shù)據(jù)劫持,即在setter中通知這個屬性所有的watcher。

function defineReactive(data, key, value) {
    // 新建用于存儲watcher的數(shù)據(jù)
    const dep = []

    // 遞歸子屬性
    observe(value)

    Object.defineProperty(data, key, {
        get() {
            // 數(shù)據(jù)劫持 在這個地方進(jìn)行相關(guān)操作
            dep.push(watcher) // 收集依賴
                        return value
        }
        set(newVal) {
            if(newVal == value) return
            value = newVal
            // 為新數(shù)據(jù)添加getter和setter
            observe(newVal)

            // 數(shù)據(jù)劫持 在這個地方進(jìn)行相關(guān)操作
            dep.notify() // 通知依賴
    	}
    })
}

現(xiàn)在我覺得我有必要理一下這個依賴收集的全過程。首先頁面初次渲染的時候,會遇到我們在data中定義的屬性(注意:此時屬性上面已經(jīng)定義好getter和setter了),遇到屬性后會初始化一個watcher實(shí)例,在此過程中watcher實(shí)例會獲取這個屬性的值,于是會進(jìn)入到這個屬性的getter中,于是我們通過數(shù)據(jù)劫持來收集這個watcher。那么又出現(xiàn)了一個問題,我們此時在getter中,如何獲取到初始化的watcher實(shí)例呢,也就是dep.push的時候,其實(shí)我們是沒有辦法直接拿到這個watcher的。因此,我們需要在初始化watcher的時候,把這個watcher放到全局,比如window.target。

subscribe() {
    // 獲取依賴屬性的值
    window.target = this // 這里的this即為此時初始化的watcher實(shí)例
	const value = _get(this.data, this.exp)
    return value
}
function defineReactive(data, key, value) {
    // 新建用于存儲watcher的數(shù)據(jù)
    const dep = []
    observe(value)
    Object.defineProperty(data, key, {
        get() {
            dep.push(window.target) // 改為window.target

            return value
        }
        set(newVal) {
            if(newVal == value) return
            value = newVal
            observe(newVal)

            dep.notify()
    	}
    })
}

響應(yīng)式代碼完善

Dep類

我們可以講dep數(shù)組抽象為一個類

class Dep {
    constructor() {
        this.subs = []
    }
    // 收集依賴
    addSub(watcher) {
        this.subs.push(watcher)
    }
    // 通知依賴
    notify() {
        [...this.subs].forEach((watcher) => {
            watcher.update()
        })
    }
}

defineReactive也要做出相應(yīng)的調(diào)整

function defineReactive(data, key, value) {
    // 新建用于存儲watcher的數(shù)據(jù)
    const dep = new Dep()
    observe(value)
    Object.defineProperty(data, key, {
        get() {
            // 收集依賴
            dep.addSub(window.target) 
            return value
        }
        set(newVal) {
            if(newVal == value) return
            value = newVal
            observe(newVal)
            // 通知依賴
            dep.notify()
    	}
    })
}

全局watcher用完清空

下面有一個場景,我們在訪問到data中的一個屬性a后,實(shí)例化了一個watcher1,此時在實(shí)例化這個watcher1的過程中,會把window.target設(shè)置為watcher1,之后我們在沒有實(shí)例化其他watcher的情況下直接去訪問其他的屬性,例如屬性b,那么屬性b中的getter會直接把watcher1推入到它的依賴數(shù)組中。這樣是不合理的,所以我們每次將watcher推入到依賴數(shù)組中后,要將這個watcher從全局中收回。(window.target這里改成Dep.target了,其實(shí)都是一樣的)

subscribe() {
    Dep.target = this // 這里的this即為此時初始化的watcher實(shí)例
    const value = _get(this.data, this.exp)
    Dep.target = null // 清空暴露在全局中的watcher
    return value
}

// 同時在收集依賴時添加一層過濾
addSub(watcher) {
    if(watcher) {
        this.subs.push(watcher)
    }
}

依賴的update方法

上面我們在watcher的update方法中更新了值并且執(zhí)行了數(shù)據(jù)更新后的回調(diào),為了讓豐富回調(diào)中的操作,我們可以將回調(diào)的this指向我們的最外層數(shù)據(jù)對象,這樣在回調(diào)中就可以通過this任意獲取數(shù)據(jù)對象中的其他屬性,并且將更新之前的舊值和新值一起傳入到update里面

update() {
  const oldValue = this.value // 獲取舊值
  this.value = parsePath(this.data, this.expression) // 獲取新值
  this.cb.call(this.data, this.value, oldValue)
}

需要注意的一個地方

下面是watcher中獲取所依賴屬性值的方法,這里需要說明一下,對于存在對象嵌套的情況,每一層屬性的依賴數(shù)組中都會添加這個watcher,想不明白的話可以看一下下面的注解。

// 根據(jù)表達(dá)式去數(shù)據(jù)對象里面獲取值
function _get(obj, exp) {
    const segments = exp.split('.')
    /*
    比如data.a.b.c,那么每次遍歷的值為data[a],data[a][b],最終結(jié)果是data[a][b][c]
    遍歷到data[a]、data[a][b]時,肯定會去訪問這兩個屬性的值,于是會進(jìn)入到這兩個屬性的getter里面
    所以這個watcher不僅僅會被添加到最內(nèi)層屬性的getter中,中間每一層屬性的getter中都會有這個watcher
    即如果data[a]的值發(fā)生了變化,也會通知這個watcher去更新視圖
    */ 
    segments.reduce((pre, key) => {
        return pre[key]
    }, obj)
}

雙劍合璧

怎樣將上面的編譯響應(yīng)式整合到一起形成一個完整的具有響應(yīng)式的miniVue類呢。其實(shí)很簡單,從我們最上面那張圖就可以看出來??偨Y(jié)一下就兩點(diǎn),在我們通過各種指令操作node節(jié)點(diǎn)的時候,同時初始化watcher,另一點(diǎn)即為初始化watcher時指定的回調(diào)內(nèi)部需要執(zhí)行updater里面對應(yīng)的方法來更新視圖

兩點(diǎn)分別對應(yīng)下圖的這兩根線:

這樣是不是就清晰多了。至此”雙劍合璧“完成,下面貼一下合璧后的代碼(只放需要合成的部分,這樣更清晰一點(diǎn))

// node--操作的node節(jié)點(diǎn)  exp--指令的值(或者是mustache語法內(nèi)部插入的內(nèi)容)  vm--vm實(shí)例  event--事件名稱
const handleNode = {
 ? ?// v-html
 ? ?html(node, exp, vm) {
 ? ? ? ?const value = this._get(vm, exp)
 ? ? ? ?// 新建watcher實(shí)例,并綁定更新回調(diào)
 ? ? ? ?new Watcher(vm, exp, (newVal, oldVal) => {
 ? ? ? ? ? ?// 這里是所依賴數(shù)據(jù)更新以后更新視圖
 ? ? ?      this.updater.htmlUpdater(node, newVal);
 ?      })
 ? ? ? ?// 這里是編譯的時候更新視圖
 ? ? ? ?updater.htmlUpdater(node, value)
    },
 ? ?// v-model
 ? ?model(node, exp, vm) {
 ?      const value = this._get(vm, exp)
 ? ? ? ?// 新建watcher實(shí)例,并綁定更新回調(diào)
 ? ? ? ?new Watcher(vm, exp, (newVal, oldVal) => {
 ? ?        this.updater.modelUpdater(node, newVal);
 ?      });
 ? ? ? ?updater.modelUpdater(node, value)
    },
 ? ?// v-on
 ? ?on(node, exp, vm, event) {
 ? ? ? ?// watcher只針對屬性 v-on這里不會生成watcher(方法名也沒什么好監(jiān)聽的,一般也不會操作方法名讓方法名發(fā)生變化)
 ? ? ? ?const listener = vm.$options.methods && vm.$options.methods[exp] 
 ? ? ? ?node.addEventListener(event, listener.bind(this), false) 
    },
 ? ?// v-text
 ? ?text(node, exp, vm) {
 ? ? ? ?let value
 ? ? ? ?if(exp.indexOf("{{") !== -1) {
 ? ? ? ? ? ?// mustache語法操作node
 ? ? ? ? ? ?value = exp.replace(/{{(.+?)}}/g, this._get(vm, exp)) 
 ? ? ? ? ? ?
 ? ? ?  }else {
 ? ? ? ? ? ?// v-text操作node
 ? ? ? ? ? ?value = this._get(vm, exp)
 ? ? ?  }
 ? ? ? ?// 新建watcher實(shí)例,并綁定更新回調(diào)
 ? ? ? ?new Watcher(vm, exp, (newVal, oldVal) => {
 ? ?        this.updater.textUpdater(node, newVal);
 ?      });
 ? ? ? ?updater.textUpdater(node, value);
 ?  },
}
_get(vm, exp) {
 ? ?const segments = exp.split('.')
    segments.reduce((pre, key) => {
        return pre[key]
 ?  }, vm.$data)
}
?
const updater = {
 ? ?textUpdater(node, value) {
        node.textContent = value;
 ?  },
 ? ?htmlUpdater(node, value) {
        node.innerHTML = value;
 ?  },
 ? ?modelUpdater(node, value){
        node.value = value;
 ?  }
}

最后的最后,修改一下我們最開始定義miniVue類的構(gòu)造函數(shù)

class miniVue {
    constructor(options) {
        this.$el = options.el
 ? ? ? ?this.$data = options.data
 ? ? ? ?this.$options = options
 ?  }
 ? ?if(this.$el) {
 ? ? ? ?// 添加數(shù)據(jù)劫持
        this.observe()
 ? ? ? ?// 編譯
 ? ? ? ?new Compile(this.$el, this);
 ?  }
}

大功告成。

總結(jié)

如果你是第一次閱讀本文,看到最后應(yīng)該還是會感覺到些許混亂。下面允許我為大家概括一下整體的流程。建議結(jié)合我們最上方的中心圖。

  • 1.初始化minivue實(shí)例 執(zhí)行其構(gòu)造函數(shù),首先對實(shí)例的數(shù)據(jù)對象data中全部屬性添加數(shù)據(jù)劫持功能(getterand setter
  • 2.開始編譯實(shí)例綁定的模板。
  • 3.首先編譯做準(zhǔn)備,創(chuàng)建compile類,拿到模板的整個DOM對象,遍歷其子節(jié)點(diǎn),獲取到每個子節(jié)點(diǎn)上的信息,這些信息中凡是引用過vm實(shí)例data中的屬性的,一律都新增一個watcher實(shí)例
  • 4.初始化watcher實(shí)例的時候,會訪問這個屬性,然后進(jìn)入這個屬性的getter中,在getter中,將這個watcher添加到這個屬性的Dep類中
  • 5.最后更新node,至此初始化編譯完成
  • 6.當(dāng)data中某一個屬性的值發(fā)生變化,會進(jìn)入這個屬性的setter中,setter會通知該屬性的Dep
  • 7.Dep類會通知存儲的所有相關(guān)watcher進(jìn)行更新,于是這些watcher分別執(zhí)行自己update中的回調(diào)。回調(diào)即會更新node。

到此這篇關(guān)于一篇文章帶你徹底搞懂VUE響應(yīng)式原理的文章就介紹到這了,更多相關(guān) VUE響應(yīng)式原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論