站長資訊網
        最全最豐富的資訊網站

        Vue的響應式原理是什么?

        最近看了不少Vue原理的文章,在這些文章的幫助下,我也多次嘗試自己理解Vue的源碼。終于,我覺得是時候自己輸出一下內容了,希望可以從不同于其他文章的角度帶大家熟悉Vue。

        Vue的響應式原理是什么?

        Dep

        var Dep = function Dep() {   this.id = uid++   this.subs = [] }

        Dep 的含義,自然就是 dependency(也就是依賴,一個計算機領域的名詞)。

        就像編寫 node.js 程序,常會使用 npm 倉庫的依賴。在 Vue 中,依賴具體指的是響應式處理后的數據。后面會提到,響應式處理的關鍵函數之一是在很多 Vue 原理文章都會提到的 defineReactive。

        Dep 與每個響應式數據綁定后,該響應式數據就會成為一個依賴(名詞),下面介紹 Watcher 時會提到,響應式數據可能被 watch、computed、在模板中使用 3 種情況依賴(動詞)。

        subs

        Dep 對象下有一個 subs 屬性,是一個數組,很容易猜出,就是 subscriber(訂閱者)列表的意思咯。訂閱者可能是 watch 函數、computed 函數、視圖更新函數。

        Watcher

        Watcher 是 Dep 里提到的訂閱者(不要和后面的 Observer 觀察者搞混)。

        因為 Watcher 的功能在于及時響應 Dep 的更新,就像一些 App 的訂閱推送,你(Watcher)訂閱了某些資訊(Dep),資訊更新時會提醒你閱讀。

        deps

        與 Dep 擁有 subs 屬性類似,Watcher 對象也有 deps 屬性。這樣構成了 Watcher 和 Dep 就是一個多對多的關系,互相記錄的原因是當一方被清除的時候可以及時更新相關對象。

        Watcher 如何產生

        上面多次提到的 watch、computed、渲染模板產生 Watcher,在 Vue 源碼里都有簡明易懂的體現:

        • mountComponent 的 vm._watcher = new Watcher(vm, updateComponent, noop);
        • initComputed 的 watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
        • $watcher 的 var watcher = new Watcher(vm, expOrFn, cb, options);

        Observer

        Observer 是觀察者,他負責遞歸地觀察(或者說是處理)響應式對象(或數組)。在打印出的實例里,可以注意到響應式的對象都會帶著一個 __ob__,這是已經被觀察的證明。觀察者沒有上面的 Dep 和 Watcher 重要,稍微了解下就可以了。

        walk

        Observer.prototype.walk 是 Observer 初始化時遞歸處理的核心方法,不過此方法用于處理對象,另外還有 Observer.prototype.observeArray 處理數組。

        核心流程

        按照上面幾個概念的關系,如何搭配,該如何實現數據響應式更新?

        首先定下我們的目標:自然是在數據更新時,自動刷新視圖,顯示最新的數據。

        這就是上面提到的 Dep 和 Watcher 的關系,數據是 Dep,而 Watcher 觸發的是頁面渲染函數(這是最重要的 watcher)。

        但是新問題隨之而來,Dep 怎么知道有什么 Watcher 依賴于他?

        Vue 采用了一個很有意思的方法:

        • 在運行 Watcher 的回調函數前,先記下當前 Watcher 是什么(通過 Dep.target)

        • 運行回調函數中用到響應式數據,那么必然會調用響應式數據的 getter 函數

        • 在響應式數據的 getter 函數中就能記下當前的 Watcher,建立 Dep 和 Watcher 的關系

        • 之后,在響應式數據更新時,必然會調用響應式數據的 setter 函數

        • 基于之前建立的關系,在 setter 函數中就能觸發對應 Watcher 的回調函數了

        代碼

        上述邏輯就在 defineReactive 函數中。這個函數入口不少,這里先講比較重要的 observe 函數。

        在 observe 函數中會 new Observer 對象,其中使用 Observer.prototype.walk 對對象中的值進行逐個響應式處理,使用的就是 defineReactive 函數。

        因為 defineReactive 函數太重要了,而且也不長,所以直接貼到這邊講比較方便。

        function defineReactive(obj, key, val, customSetter, shallow) {   var dep = new Dep()   depsArray.push({ dep, obj, key })   var property = Object.getOwnPropertyDescriptor(obj, key)   if (property && property.configurable === false) {     return   }    // cater for pre-defined getter/setters   var getter = property && property.get   var setter = property && property.set    var childOb = !shallow && observe(val)   Object.defineProperty(obj, key, {     enumerable: true,     configurable: true,     get: function reactiveGetter() {       var value = getter ? getter.call(obj) : val       if (Dep.target) {         dep.depend()         if (childOb) {           childOb.dep.depend()           if (Array.isArray(value)) {             dependArray(value)           }         }       }       return value     },     set: function reactiveSetter(newVal) {       var value = getter ? getter.call(obj) : val       // 后半部分詭異的條件是用于判斷新舊值都是 NaN 的情況       if (newVal === value || (newVal !== newVal && value !== value)) {         return       }       // customSetter 用于提醒你設置的值可能存在問題       if ('development' !== 'production' && customSetter) {         customSetter()       }       if (setter) {         setter.call(obj, newVal)       } else {         val = newVal       }       childOb = !shallow && observe(newVal)       dep.notify()     },   }) }

        首先每個響應式的值都是一個“依賴",所以第一步我們先借閉包的能力給每個值造一個 Dep。(到 Vue 3 就不需要閉包啦)

        接著看核心的三個參數:

        • obj 當前需要響應式處理的值所在的對象

        • key 值的 key

        • val 當前的值

        這個值還可能之前就定義了自己的 getter、setter,所以在做 Vue 的響應式處理時先處理原本的 getter、setter。

        上面在核心流程中提到在 getter 函數會建立 Dep 和 Watcher 的關系,具體來說依靠的是 dep.depend()。

        下面貼一下 Dep 和 Watcher 互相調用的幾個方法:

        Dep.prototype.depend = function depend() {   if (Dep.target) {     Dep.target.addDep(this)   } } Watcher.prototype.addDep = function addDep(dep) {   var id = dep.id   if (!this.newDepIds.has(id)) {     this.newDepIds.add(id)     this.newDeps.push(dep)     if (!this.depIds.has(id)) {       dep.addSub(this)     }   } } Dep.prototype.addSub = function addSub(sub) {   this.subs.push(sub) }

        通過這幾個函數,可以領略到了 Dep 和 Watcher 錯綜復雜的關系……不過看起來迂回,簡單來說,其實做的就是上面說的互相添加到多對多列表。

        你可以在 Dep 的 subs 找到所有訂閱同一個 Dep 的 Watcher,也可以在 Watcher 的 deps 找到所有該 Watcher 訂閱的所有 Dep。

        但是里面還有一個隱藏問題,就是 Dep.target 怎么來呢?先放一放,后會作出解答。先接著看看 setter 函數,其中的關鍵是 dep.notify()。

        Dep.prototype.notify = function notify() {   // stabilize the subscriber list first   var subs = this.subs.slice()   for (var i = 0, l = subs.length; i < l; i++) {     subs[i].update()   } }

        不難理解,就是 Dep 提醒他的訂閱者列表(subs)里的所有人更新,所謂訂閱者都是 Watcher,subs[i].update() 調用的也就是 Watcher.prototype.update。

        那么來看一下 Watcher 的 update 做了什么——

        Watcher.prototype.update = function update() {   if (this.lazy) {     this.dirty = true   } else if (this.sync) {     this.run()   } else {     queueWatcher(this)   } }

        在這里我覺得有兩個點比較值得展開,所以挖點坑

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 久久99国产精品久久99| 无码精品日韩中文字幕| 亚洲一区二区三区国产精品| 香蕉依依精品视频在线播放 | 日本精品不卡视频| 欧美亚洲色综久久精品国产 | 亚洲国产精品无码久久| 国产99视频精品免费专区| 老司机91精品网站在线观看 | 国语自产精品视频| 无码精品久久久天天影视| 精品精品国产高清a毛片牛牛| 欧美亚洲综合免费精品高清在线观看| 久久精品中文闷骚内射| 亚洲国产精品尤物yw在线| 亚洲AV永久无码精品一区二区| 91亚洲精品麻豆| 精品福利视频一区二区三区| 秋霞午夜鲁丝片午夜精品久| 国产精品视频免费| 9re热国产这里只有精品| 四虎国产精品免费观看| 最新国产在线精品观看| 亚洲国产精品lv| 欧美成人精品欧美一级乱黄一区二区精品在线 | 精品国产不卡一区二区三区 | 国产精品户外野外| 精品午夜福利在线观看| 久久久久久久久久久免费精品 | 无码AV动漫精品一区二区免费| 麻豆精品成人免费国产片| 华人在线精品免费观看| 91精品国产高清91久久久久久| 精品欧洲AV无码一区二区男男| 久久精品国产久精国产思思| 欧美午夜精品久久久久免费视| 亚洲Av永久无码精品三区在线| 人妻少妇精品中文字幕AV| 国产精品无码成人午夜电影| 99精品国产自在现线观看| 久久99久久99小草精品免视看|