Vue 的數(shù)據(jù)響應式

Vue到底對 data 做了什么凳怨?


import Vue from 'vue/dist/vue.js'

Vue.config.productionTip = false

const myData = {
    n: 0
}
console.log(myData)

const vm = new Vue({
    data: myData,
    template: `
        <div>{{n}}<button @click="add">+1</button></div>
    `,
    methods: {
        add(){
            this.n +=10
        }
    }
}).$mount("#app")

setTimeout(() => {
    this.n += 10
    console.log(myData)
}, 3000)
  1. 我們有如上的代碼叉讥,其中 data 中的的數(shù)據(jù)引用外面的變量 myData华临;
  2. 我們在兩處進行打印芯杀,一處是初始化的時候打印,一處是在變化之后進行打友盘丁揭厚;
  3. 我們可以看到初始化的時候打印這個myData就只是我們定義的對象,但是在變化之后再打印這個對象就會發(fā)生變化扶供,好像是被包裹了一層東西筛圆;


    image.png
  4. 一開始是 {n:0},傳給 new Vue 之后就立馬變成{n:(...)}椿浓;
  5. {n:(...)}是個什么東西太援,為什么變現(xiàn)和 {n:0}一致?
  6. 我們了解這個知識點需要知道什么是 ES6 的 getter 和 setter扳碍。

getter 和 setter


  1. 我們聲明一個變量提岔,這個變量的屬性如下,具有一個姓的屬性笋敞,一個名的屬性碱蒙,還有一個age屬性;
    let obj0 = {
        姓: "高",
        名: "圓圓",
        age: 18
    };
    
  2. 需要一:得到姓名夯巷,那么只要在里面添加一個方法就好了振亮,在對象中巧还,屬性名和屬性值一樣的時候,是可以直接寫函數(shù)的坊秸。
    let obj1 = {
        姓: "高",
        名: "圓圓",
        姓名() {
            return this.姓 + this.名;
        },
        age: 18
    };
    console.log("需求一:" + obj1.姓名())
    
    • 但是這個函數(shù)我們需要調用麸祷,也就是后面又括號,那么怎么去掉括號呢褒搔?
  3. 需求二:姓名之后不加括號也能得出值
    • 這個需要使用 ES6 的新語法阶牍,get方法
    •   let obj2 = {
            "姓": "高",
            "名": "圓圓",
            get 姓名() {
                return this.姓 + this.名;
            },
            age: 18
        }
        console.log("需求二:" + obj2.姓名)
      
    • 我們使用 get 之后發(fā)現(xiàn),這個姓名是一個屬性星瘾,不用加括號走孽,只不過是以函數(shù)形式定義的,看起來跟普通的屬性并沒有區(qū)別
    • 這種寫法是 getter琳状,用于獲取這個值
  4. 需求三:姓名可以被寫磕瓷,這個寫法是 setter
    •   let obj3 = {
            "姓": "高",
            "名": "圓圓",
            get 姓名() {
                return this.姓 + this.名;
            },
            set 姓名(xxx){
                this.姓 = xxx[0]
                this.名 = xxx.slice(1)
            },
            age: 18
        }
      
        obj3.姓名 = '劉詩詩'
      
        console.log(`需求三:姓 ${obj3.姓}, 名 ${obj3.名}`)
        console.log(obj3)
      
    • 我們將這個obj3打印出來看看
    • image.png
    • 這個姓名其實跟我們之前的那個data中的數(shù)據(jù)是一樣的形式,說明我們之前的那個數(shù)據(jù)也是getter和setter形成的
    • 這兒的姓名不是一個真實的屬性念逞,但是我們可以對這個屬性進行讀和寫困食,讀寫通過getter和setter進行操作

Object.defineProperty


  1. 我們之前在使用getter和setter的時候是在定義的時候直接使用的,當我們在定義好一個對象之后翎承,無法再添加get和set硕盹。
  2. 但是如果我們想在后面寫怎么辦?
  3. 這個時候我們就需要使用Object.defineProperty
  4. 下面是一個示例代碼
    •   let obj3 = {
            "姓": "高",
            "名": "圓圓",
            get 姓名() {
                return this.姓 + this.名;
            },
            set 姓名(xxx){
                this.姓 = xxx[0]
                this.名 = xxx.slice(1)
            },
            age: 18
        }
        var _新屬性 = 0
        Object.defineProperty(obj3, '新屬性', {
            get(){
                return _新屬性
            },
            set(value){
                _新屬性 = value
            }
        })
      
    • 上述代碼中叨咖,我們想要添加一個新屬性瘩例,其中_新屬性是為了盛放新屬性的值
    • 我們定義的屬性是不存在的,所以在get和set方法中不可以使用本屬性甸各,因為新屬性的使用是通過get和set方法的垛贤,如果在get和set中使用就會造成死循環(huán)
    • 這個_新屬性其實是一個代理

代理和監(jiān)聽


  1. 需求一:用 Object.defineProperty 定義 n
    •   let data1 = {}
        Object.defineProperty(data1, 'n', {
            value: 0
        })
        console.log(`需求一:${data1.n}`)
      
    • 我們定義了一個data1,想要添加一個屬性趣倾,并設置成0
  2. 需求二:n不能小于0
    •   let data2 = {}
        data._n = 0
        Object.defineProperty(data2, 'n', {
            get(){
                return this._n  // _n用來存儲n的值
            },
            set(value){
                if(value < 0) return
                this._n = value
            }
        })
        console.log(`需求二:${data2.n}`)
        data2.n = -1
      
    • 我們使用了一個臨時的變量_n去存儲
    • 如果對方直接使用這個data._n呢聘惦?
    • 我們怎么將這個data._n將其變得訪問不到呢?
    • 可以使用代理
  3. 需求三:使用代理
    •   let data3 = proxy({ data: {n:0} })  // 括號里面的匿名對象無法訪問誊酌,因為沒有名字
        function proxy({data}){
            const obj = {}
            // 這里的 n 寫死了部凑,理論上應該是遍歷屬性
            Object.defineProperty(obj, 'n' {
                get(){
                    return data.n
                },
                set(value){
                    if(value<0)return
                    data.n = value
                }
            })
            return obj  // obj就是代理
        }
        console.log(`需求三:${data3.n}`)
        data3.n = -1
      
    • 函數(shù)傳進去的匿名對象是無法訪問的
    • 函數(shù)里面使用的obj是一個代理
    • 我們將data3中的所有屬性值通過get和set的方式轉給代理obj
    • data3理論上跟obj是等價的露乏,我們通過在obj里面去設定一些機制碧浊,防止外界隨意篡改,如不可以小于0
    • 代理的作用就是做一些限制保護瘟仿,只要原始傳進去的對象不暴露在外面就行箱锐。
    • 但是如果對于一些個變量已經(jīng)暴露在外面怎么辦?
  4. 需求四:就算用戶擅自修改myData劳较,也要攔截它
    •   let myData = {n:0}
        let data4 = proxy({ data: myData })
        myData.n = -1
      
    • 上面這個代碼就直接繞過了代理驹止,修改了最原始的myData
    • 我們通過下面的代碼實現(xiàn)了怎樣能在用戶修改的時候做一個監(jiān)聽呢浩聋?
    •   let myData5 = {n:0}
        let data5 = proxy2({ data: myData5 })
        // 這個改進的代碼可以100%的防止超出規(guī)則的數(shù)據(jù)更改
        function proxy2({data}){
            // 使用這個value存儲原始的data中的n,先將其記下來臊恋,因為待會要刪除這個n
            let value = data.n
            // 我們創(chuàng)建一個新的n就是覆蓋原先的n衣洁,覆蓋就相當于刪除
            // 這個部分是監(jiān)聽邏輯,只要n發(fā)生更改抖仅,就出觸發(fā)這個坊夫,但是返回值還是需要通過判斷才能更改的
            // 我們將原先的屬性刪掉,通過get和set獲取以及重置撤卢,這樣就可以在其中設置規(guī)則环凿,即監(jiān)聽
            Object.defineProperty(data, 'n', {
                get(){
                    return value
                },
                set(newValue){
                    if(newValue<0)return
                    value = newValue
                }
            })
            // 這部分是代理邏輯
            const obj = {}
            Object.defineProperty(obj, 'n', {
                get(){
                    return data.n
                },
                set(value){
                    if(value<0)return
                    data.n = value
                }
            })
        }
      
    • 上面這串代碼改寫的代理函數(shù)分為兩部分,一部分是負責監(jiān)聽放吩,另一個部分是用于代理智听;
    • 負責監(jiān)聽的那部分通過使用閉包,拿到數(shù)據(jù)就在內(nèi)部存儲一下數(shù)據(jù)渡紫,并通過get和set的方式重新定義同名數(shù)據(jù)到推,這樣外界在訪問的時候就必須通過這個get和set,在這個里面做一些限制腻惠,就可以實現(xiàn)監(jiān)聽效果
    • 代理邏輯還是跟原先的一樣
  5. let data5 = proxy2({ data: myData5 })這段代碼跟const vm = new Vue({data: {n: 0}})很是類似

那么 Vue 到底對數(shù)據(jù)做了什么环肘?


  1. 啥是代理?(設計模式)
    • 對 myData 對象的屬性讀寫集灌,全權由另一個對象 vm 負責(通過將myData中的屬性用get和set的方式給vm)
    • 那么 vm 就是 myData 的代理
    • 比如 myData.n 不用悔雹,偏要用 vm.n 來操作 myData.n
  2. 有關 vm = new Vue({data: myData})
    1. 會讓 vm 成為 myData 的代理(proxy)
    2. 會對 myData 的所有屬性進行監(jiān)控
    3. 為什么要監(jiān)控?為了防止 myData 的屬性變了欣喧,vm不知道
    4. vm 知道了又如何腌零?知道屬性變了就可以調用 render(data)
    5. UI=render(data)
  3. image.png
  4. 全程最開始的對象是不變的,我們操作和展示的時候唆阿,只動vm中的對象

什么是數(shù)據(jù)響應式


  • 什么是響應式益涧?
    • 如果一個物體對于外界的刺激能做出反應,他就是響應式的
  • Vue 的 data 是響應式
    • const vm = new Vue({data: {n: 0}})
    • 我如果修改 vm.n驯鳖,那么 UI 中的 n 就會響應我
    • Vue 2 通過 Object.defineProperty 來實現(xiàn)數(shù)據(jù)響應式

Vue 的 data 的 bug


  • Object.defineProperty 的問題
    • Object.defineProperty(obj, 'n', {...})
    • 必須要有一個 'n'闲询,才能監(jiān)聽&代理obj.n
    • 如果前端工程師沒有給出n怎么辦?
    • 情況一:第一層屬性沒有n浅辙,Vue會給出一個警告
      • image.png
      • 會警告并沒有這個屬性
    • 情況二:屬性是一個對象扭弧,對象里面有屬性,這種情況下记舆,Vue只會檢查第一層屬性
      • 屬性里面的屬性如果不存在鸽捻,就無法進行后續(xù)操作的
      • image.png
      • image.png
      • 點擊之后,并不會顯示b的值
      • 為什么:因為Vue無法監(jiān)聽一開始就不存在的obj.b
  • 解決方法:
    • 一開始就將需要用到的key監(jiān)聽好,如b: undefined
    • 使用 Vue.set 或者 this.$set
      • this.$set(this.obj, 'b', 1)
      • Vue.set(this.obj, 'b', 1)

Vue.set 和 this.$set


  • 作用
    • 新增 key
    • 自動創(chuàng)建代理和監(jiān)聽(如果沒有創(chuàng)建過)
    • 觸發(fā)UI更新(但并不會立刻更新)
  • 舉例
    • this.$set(this.object, 'm', 100)

數(shù)組的變異方法


  1. 我們知道數(shù)組最終是一個對象御蒲;
  2. 如a = ['a', 'b', 'c']衣赶,實際上會是a = {0: 'a', 1: 'b', 2: 'c'}
  3. 這樣在 vue 中,當我們需要給數(shù)組添加元素的時候厚满,不可以直接使用原先的this.a[3] = "d"這樣的方法府瞄,因為vue對原先不存在的數(shù)據(jù)是無法進行監(jiān)聽的,需要使用this.$set()
  4. 這樣子碘箍,我們對于數(shù)組的所有操作摘能,都得使用$.set?
  5. 使用 this.a.push('d')敲街,這個方法經(jīng)過Vue改編了团搞,同名,可以支持在vm中使用多艇,就是在原先的方法那邊添加一個set


    image.png
  6. 篡改的方式是添加一層原型逻恐,在原型上面篡改
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市峻黍,隨后出現(xiàn)的幾起案子复隆,更是在濱河造成了極大的恐慌,老刑警劉巖姆涩,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挽拂,死亡現(xiàn)場離奇詭異,居然都是意外死亡骨饿,警方通過查閱死者的電腦和手機亏栈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宏赘,“玉大人绒北,你說我怎么就攤上這事〔焓穑” “怎么了闷游?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贴汪。 經(jīng)常有香客問我脐往,道長,這世上最難降的妖魔是什么扳埂? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任业簿,我火速辦了婚禮,結果婚禮上聂喇,老公的妹妹穿的比我還像新娘辖源。我一直安慰自己,他們只是感情好希太,可當我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布克饶。 她就那樣靜靜地躺著,像睡著了一般誊辉。 火紅的嫁衣襯著肌膚如雪矾湃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天堕澄,我揣著相機與錄音邀跃,去河邊找鬼。 笑死蛙紫,一個胖子當著我的面吹牛拍屑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坑傅,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼僵驰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了唁毒?” 一聲冷哼從身側響起蒜茴,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浆西,沒想到半個月后粉私,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡近零,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年诺核,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片久信。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡猪瞬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出入篮,到底是詐尸還是另有隱情陈瘦,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布潮售,位于F島的核電站痊项,受9級特大地震影響,放射性物質發(fā)生泄漏酥诽。R本人自食惡果不足惜鞍泉,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肮帐。 院中可真熱鬧咖驮,春花似錦边器、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至睦刃,卻和暖如春砚嘴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涩拙。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工际长, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兴泥。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓工育,卻偏偏與公主長得像,于是被迫代替她去往敵國和親搓彻。 傳聞我的和親對象是個殘疾皇子翅娶,可洞房花燭夜當晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容