1. 程式人生 > >[切圖仔救贖]炒冷飯--線上手擼vue2響應式原理

[切圖仔救贖]炒冷飯--線上手擼vue2響應式原理

--圖片來源vue2.6正式版本(代號:超時空要塞)釋出時,尤雨溪推送配圖。

前言

其實這個冷飯我並不想炒,畢竟vue3馬上都要出來。我還在這裡炒冷飯,那明顯就是搞事情。

起因:

作為切圖仔搬磚汪,長期切圖jq一把梭。重複繁瑣的切圖,讓自己陷入了一個無限的圍城。想出去切圖這個圍城看一看,但是又害怕因為切圖時間久了,自己會的也只有切圖了。

為了後面能夠繼續搬磚恰飯,幫助自己跳出切圖仔的圍城。也去看了vue相關文件,當時記憶深刻覺得還行。可是G胖這個時候發動小紫本和打折魔咒,不知不覺又沉迷於DOTA小本子上面了。關於vue響應式原理很快忘得一塌糊塗,只記得一個屬性Object.defindProperty

,然後就沒有然後了......

為了避免自己後面再次忘記,所以這裡炒一個冷飯加深記憶。

響應式vue

在講解vue響應式的原理之前,讓我們來一段Vue程式碼作為示例:

<div id="app">
  <div>主食: {{ food }}</div>
  <div>飲料: {{ drink }}</div>
  <div>選單: {{ menu }}</div>
</div>
<script>
  let vue = new Vue({
    el: '#app',
    data: {
      food: '煎餅果子',
      drink: '熱豆漿'
    },
    computed: {
      menu() {
        return  this.food + this.drink
      }
    }
  })
</script>

fooddrink發生變化後,Vue會做兩件事:

  • 在頁面上更新fooddrink的值。

  • 再次呼叫menu, 重新計算food + drink的值, 並在頁面上面更新。

更新值+計算值做的事情其實很簡單,幾行程式碼的事情。問題是當food或者drink變化時,Vue是怎麼知道誰變化,然後馬上響應其行為,去執行那"簡單的幾行程式碼"?

所以,當看到Vue案例時,詞窮的我當時第一反應就是牛皮

之所以發出感嘆,是因為通常的JavaScript程式碼是實現不了這樣的功能的。話不多說,讓我們直接上程式碼來說明:

    let food = "煎餅果子"
    let drink = "熱豆漿"
    let menu = null
    menu = food + drink
    food = '炸雞漢堡'
    drink = '快樂水'
    console.log(menu) 

最終控制檯列印結果:

煎餅果子熱豆漿

如果是在Vue當中,fooddrink發生了變化,那麼Vue會跟著做出響應動作,從而在控制檯輸出我們想要的結果:

炸雞漢堡快樂水

選單響應

這裡就出現第一個問題,當food或者drink 發生變化之後,menu並不會響應其變化。這個時候就需要我們來解決這個問題,滿足menu響應。

借鑑Vue一樣,我們先把menu的計算方法。也寫成一個函式,取名為target。然後每次food或者drink變化的時候呼叫target函式

    let food = "煎餅果子"
    let drink = "熱豆漿"
    let menu = null
    let target = () => {
        menu = food + drink
    }
    target() // 初始化選單menu
    food = '炸雞漢堡'
    drink = '快樂水'
    target()
    console.log(menu) 

控制檯輸出:

炸雞漢堡快樂水

浴室沉思

前面一把梭直接呼叫的滿足menu響應的問題,但是也間接留下一個新的疑惑點。這裡針對一個選單,就寫了一個target。假設有多個選單需要響應呢?

例如:

  • 單人早餐 = 煎餅果子 + 熱豆漿
  • 豪華套餐: 煎餅果子加兩雞蛋 + 熱豆漿 + 油條一根午餐
  • ......

如果這個時候切換成:

  • 單人午餐 = 炸雞漢堡 + 快樂水
  • 豪華套餐: 雙層炸雞漢堡 + 快樂水 + 快樂薯條一包
  • ......

按照前面的邏輯, 估計得寫N個target。這個時候響應式又是一個麻煩事情,可是有句話說的好。梭哈一時爽,一直梭哈一直爽。既然前面直接採用target一把梭完成,所以針對N個target方法,我也可以直接來個for迴圈一把梭能完成響應式問題。

for迴圈一把梭

  • 定義一個數組,每定義了一個target函式。就儲存到陣列當中。
let storge = [] // 用來儲存target
function record (){  // 
  storge.push(target)
}
  • 定義迴圈函式,每次data有變更。就呼叫這個函式,進行一把for迴圈.
function replay (){
  storge.forEach(run => run())
}
  • 合併成完整的程式碼:
    let food = "煎餅果子"
    let drink = "熱豆漿"
    let menu = null
    food = '炸雞漢堡'
    drink = '快樂水'
    let target = () => {
        menu = food + drink
    }
    let storge = []; //用來儲存更多的target
    function record(target) {
        storge.push(target)
    }    
    function replay() {
        storge.forEach(run => run())
    }
    record(target)
    replay()
    food = '炸雞漢堡'
    drink = '快樂水'
    replay()
    console.log(menu)

最後控制檯成功輸出:

炸雞漢堡快樂水

Dep依賴類

通過一把梭實現功能,那麼接下來就開始思考優化部分了。繼續記錄target這類的程式碼,這樣有點怪怪的。為了後面方便管理,我們把程式碼進行簡單的優化,封裝成一個類:

    class Dep {
        constructor() {
            this.subs = []
        }
        // 收集依賴
        depend(sub) {
            if (sub && !this.subs.includes(sub)) {  // 做一個判斷
                this.subs.push(sub)
            }
        }

        notify() {
            console.log("暗號:下雨啦,收衣服啦!")
            this.subs.forEach(sub => sub()) // 執行我們的target
        }
    }

就這樣target函式儲存在類的subs中,record也變成了depend,使用notify來代替replay

封裝成類之後,每次當data資料更新的時候,就會發出一個暗號下雨啦,收衣服啦! 然後就開始遍歷執行相應的target依賴了。

新的呼叫程式碼就更加清晰明瞭:

    let dep = new Dep()
    let food = "煎餅果子"
    let drink = "熱豆漿"
    let menu = null
    let target = () => {
        menu = food + drink
    }
    dep.depend(target)
    target() // 完成menu第一次初始化
    console.log(menu)
    food = '炸雞漢堡'
    drink = '快樂水'
    dep.notify()
    console.log(menu)

控制檯輸出:

煎餅果子熱豆漿
暗號:'下雨啦,收衣服啦!'
炸雞漢堡快樂水

觀察者亮相

當前的程式碼,是確定一個依賴事件,就定義target,然後呼叫依賴類dep.depend將其儲存起來。

let target = () => { menu = food + drink }
dep.depend(target)
target()

這個時候又新來一個target事件又該如何做:

新新增一個target事件?

let target2 = () => { 新的依賴事件 }
dep.depend(target2)
target2()

要是有幾百個依賴,那還不得上天。我估計要是這樣寫程式碼,估計你的同事要說你寫程式碼像CXK

觀察者函式

借鑑觀察者模式,封裝一個watcher函式. 幫你觀察記錄相關target事件,避免多次宣告變數。

    function watcher(myFun) {
        target = myFun
        dep.depend(target)
        target()
        target = null
    }
    watcher(() => {
        menu = food + drink
    })

正如你所看到的,watcher函式接受myFunc引數,將其賦給全域性的target上,呼叫dep.depend()將其新增到數組裡,之後呼叫並重置target

既然又封裝一個新的函式,那麼驗證又將是必不可少的了。這裡我們修改一下drink來試試:

drink = "快樂水"
console.log(menu)
dep.notify()
console.log(menu)

控制檯輸出結果:

煎餅果子熱豆漿
暗號:下雨啦,收衣服啦!
煎餅果子快樂水

Object.defineProperty()

基本用法

鋪墊了這麼久,一個關鍵性角色這個時候也登場了。

該方法允許精確新增或修改物件的屬性。通過賦值操作新增的普通屬性是可列舉的,能夠在屬性列舉期間呈現出來(for...in 或 Object.keys 方法), 這些屬性的值可以被改變,也可以被刪除。這個方法允許修改預設的額外選項(或配置)。預設情況下,使用 Object.defineProperty() 新增的屬性值是不可修改的。
--《MDN文件》

不明覺厲? 那就先熱身一下,進入快樂的舉例子環節:

    let data = {
         food: '煎餅果子',
         drink: '熱豆漿'
    }
    Object.defineProperty(data, 'food', {
        get() {
            console.log(`觸發get方法`)
    },
        set(newVal) {
            console.log(`設定food為${newVal}`)
        }
    })
data.food
data.food = 炸雞漢堡 

控制檯輸出:

觸發get方法
設定food為炸雞漢堡

簡單封裝

但是僅僅憑藉object.defineProperty是無法完成當一個數據更新了,完成資料響應。而且程式碼這裡也是隻是對food做了一個處理, 還有drink沒有處理,所以為了完成data所以屬性都做相應的處理。接下來就是對於Object.defineProperty()進行簡單的封裝處理了:

    Object.keys(data).forEach(key => {
        let value = data[key]
        Object.defineProperty(data, key, {
            get() {
                return value
            },
            set(newVal) {
                value = newVal
            }
        })
    })

遍歷了data每個屬性,然後對每個屬性進行偵聽。這樣data的屬性一旦改變,就會自動發出通知.

程式碼整合

前面零零散散分別講了 Depwatcherobject.defineProperty, 那麼接下來就讓我們把這個幾個部分整合到一起,完整檢視整個程式碼:

    let data = {
        food: '煎餅果子',
        drink: '熱豆漿'
    }
    class Dep {
        constructor() {
            this.subs = []
        }
        // 收集依賴
        depend(sub) {
            if (sub && !this.subs.includes(sub)) { // 做一個判斷
                this.subs.push(sub)
            }
        }

        notify() {
            console.log("暗號:下雨啦,收衣服啦!")
            this.subs.forEach(sub => sub()) // 執行我們的target
        }
    }
    Object.keys(data).forEach(key => {
        let value = data[key]
        let dep = new Dep()

        Object.defineProperty(data, key, {
            get() {
                dep.depend(target)
                return value
            },
            set(newVal) {
                value = newVal
                dep.notify()
            }
        })
    })

    function watcher(myFun) {
        target = myFun
        // dep.depend(target)  這裡修改,移動到Object.defineProperty當中去
        target()
        target = null
    }
    watcher(() => {
        data.menu = data.food + data.drink
    })
    console.log(data.menu)
    data.food = "炸雞漢堡"
    data.drink = "快樂水"
    console.log(data.menu)

控制檯輸出:

煎餅果子熱豆漿
暗號:下雨啦,收衣服啦!
炸雞漢堡快樂

這裡完全實現了文章開頭所提出的需求,每當fooddrink更新時,我們的menu也會跟著響應並更新。

這時候Vue文件的插圖的意義就很明顯了:

免責宣告

以上就是我的炒冷飯內容,怕忘記重寫總結一下,有說錯的地方多擔待。(特拿前端勸退師騷宣告一份,窺伺好久了。)

意思就是寫得略粗糙,別噴我。。。

我是車大棒,我為我自己插眼。

相關推薦

[]炒冷飯--上手vue2響應原理

--圖片來源vue2.6正式版本(代號:超時空要塞)釋出時,尤雨溪推送配圖。 前言 其實這個冷飯我並不想炒,畢竟vue3馬上都要出來。我還在這裡炒冷飯,那明顯就是搞事情。 起因: 作為切圖仔搬磚汪,長期切圖jq一把梭。重複繁瑣的切圖,讓自己陷入了一個無限的圍城。想出去切圖這個圍城看一看,但是又害怕因為

前端學習第一步----的誕生

一個 設計 動圖 學習 如何 雙擊 空格 pos 直接   剛剛學習了一下前端的入門課程,將設計師設計的logo等圖片從PSD文件中切出來,並保存為PNG格式。PNG格式的主要優點就是背景色可以為空白,不同於普通格式的圖片的白色。使用的工具是Photoshop   首先先了

ps在psd格式圖片裏面流程

mage bsp nbsp str ima col size round src 第一、 第二、   xx的地方自己重新命名 第三、 第四、 ps在psd格式圖片裏面切圖流程

技巧

隱藏 ctrl+c 參考 ron 參考線 str 操作 快捷 fit 1. 切一個組 組文件 -> 右鍵 -> 復制組 -> 新建 -> 確定 2. 柵格化圖層,可以進行復制操作 選中圖層 -> 右擊 -> 柵格化 -> 復制(ct

藍湖:你們要的“自動”功能來了!

需要 .cn target iad 兩件 hot 設計圖 sdg style 一般本湖君出來吆喝,必然是要搞大事情~ 繼“自動標註”功能後,藍湖再次解鎖重量級功能——“自動切圖”! 小夥伴們,驚喜不驚喜? ? ?延續“藍湖”老規矩,本“自動切圖”功能: ①同時支持Mac和

兩種快速方式

-h 動手 切片 文件 view 幫我 pack com upload 今天給大家分享一下我自己在前端工作中的一些切圖小技巧,雖然好的UI會給我們把圖切好,但是他們切的圖不一定百分之百符合我們的需求,所以還是自己動手豐衣足食嘛,看本教程之前希望大家能先看看慕課網的切圖教

UI設計規範整理一iOS字體和及規範

icon 頁面設計 nologin 例如 必須 協作 設計規範 ogr 喜歡 UI設計規範一iOS字體和切圖及規範 說明: 1.對象為程序員等開發人員。 2.方法有千種,僅供參考。 3.文檔的本質是備份與查看,對外方便協作與對內提升效率。 4.文檔不是萬能的,如果文檔查看

ps技巧

元素 觀察 疊加 參考 png 註意 導出 要點 否則 步驟1: ps打開psd文件 步驟2: 點擊移動工具,觀察左上角的自動選擇是否有勾選 ,如果沒有最好勾選,對應的選項有圖層和組,善於切換這個功能能夠有效快速的找到你要的區域 步驟3: 找到要切圖的元素,將其他疊加的圖層

程序員的自我---1.1: 解決方案命分層規範

facade 要求 alt mon mit 文件 bool ner 什麽 《目錄》 《Winner2.0框架解決方案命分層規範》 初學編程,難免要從Hello Word開始,學習Winner框架首先要知道如何建一個項目。有了第一個項目的框架結構就知道如何施

程序員的自我---1.2:代碼生成器的使用

很大的 dataguard ext mvc 這也 alc 我沒 測試 大型項目 《前言》 《目錄》 (一) Winner2.0 框架基礎分析 (二) 短信中心 (三)SSO單點登錄 (四)PLSQL報表系統 (五)錢包系統 (六)GPU支付中心 (七)權限系

移動端rem

footer thead table nts 等比例縮放 tab applet value details 1.為什麽用rem 根據屏幕大小,自動調整大小 2.如何使用rem 分以下幾步 a.用ps把設置稿弄成640px或者750px的(記得等比例縮放) b.調試時記得把瀏

程序員的自我---1.4.3: 核心框架講解(MVC)

登錄頁面 就會 技術 virtual mon status pan gpu aac 《前言》 《目錄》 (一) Winner2.0 框架基礎分析 (二) 短信中心 (三)SSO單點登錄 (四)PLSQL報表系統 (五)錢包系統 (六)GPU支付中心 (七)權

程序員的自我---11.4:FileSystem文件服務

救贖 頭像 規範 logo 講解 調用 get bsp 後臺管理 《前言》 (一) Winner2.0 框架基礎分析 (二)PLSQL報表系統 (三)SSO單點登錄 (四) 短信中心與消息中心 (五)錢包系統 (六)GPU支付中心 (七)權限系統 (八)監控

前端:調用百度地圖API

chm over ice element tle keyboard style mat 搜索 原型圖 圖片發自簡書App <!DOCTYPE html> <html> <head> &l

前端:手機端自適應布局demo

original shu 源碼 size 自適應 art 分享 nsh defined 手機端自適應布局demo 原型如下: 圖片發自簡書App 要求如下:適應各種機型源碼如下: <!DOCTYPE html > <html>

前端:CSS實現隱藏滾動條同時又可以滾動

content gin cap origin format cimage src eight 技術 CSS 實現隱藏滾動條同時又可以滾動 原始功能: 添加偽類之後的功能: 完整demo如下: <!DOCTYP

程序員的自我---3.2:SSO及應用案例

contain urn 前端框架 顯示 分開 十分 域名 酒店 刷新 《前言》 (一) Winner2.0 框架基礎分析 (二)PLSQL報表系統 (三)SSO單點登錄 (四) 短信中心與消息中心 (五)錢包系統 (六)GPU支付中心 (七)權限系統 (八)

程序員的自我---7.1:權限系統講解

讓我 mvc .cn 之前 目的 member targe 系統應用 頁面 《前言》 (一) Winner2.0 框架基礎分析 (二)PLSQL報表系統 (三)SSO單點登錄 (四) 短信中心與消息中心 (五)錢包系統 (六)GPU支付中心 (七)權限系統

程序員的自我---7.2:權限系統實際應用

登陸 這一 sso 控制系統 分享 實際應用 auth 加權 oba 《前言》 (一) Winner2.0 框架基礎分析 (二)PLSQL報表系統 (三)SSO單點登錄 (四) 短信中心與消息中心 (五)錢包系統 (六)GPU支付中心 (七)權限系統 (八)

前端PS必備快捷鍵

位置 shift 北京 ng- 放大縮小 鼠標 快捷 clas pos 1)m 選取工具 測量設計稿中元素得位置及大小 ctrl+d 取消選取 2)z 放大縮小,默認是放大 alt + 鼠標左鍵 縮小 3)alt + delete 向圖層中填充前景色