深入理解Vue數(shù)據(jù)響應(yīng)式

Vue數(shù)據(jù)響應(yīng)式主要研究的是 Vue 構(gòu)造選項(xiàng)中 data 屬性的特性

深入響應(yīng)式 官方文檔 網(wǎng)址: https://cn.vuejs.org/v2/guide/reactivity.html

1. 首先理解 ES6 的 getter 與 setter 語法

obj = {         // 首先創(chuàng)建一個(gè)普通對(duì)象
    name:'小歐',
    age:18,
    姓名(){
        return this.name;
    }
}
console.log(obj.姓名());    //  小歐

// 如何使  obj.姓名   不帶括號(hào)也可以得到 name 值呢荒勇?
// 使用ES6新語法

obj = {
    name:'小歐',
    age:18,
    get 姓名(){     // get (讀) 一個(gè)值時(shí)執(zhí)行的函數(shù)  獲取時(shí)無需使用()執(zhí)行
        return this.name;
    }
}
console.log(obj.姓名);    //  小歐

obj = {
    name:'小歐',
    age:18,
    get 姓名(){
        return this.name;
    },
    set 姓名(set_name){     //  同樣 set  (寫) 一個(gè)值得時(shí)候也能執(zhí)行相應(yīng)的函數(shù)
        this.name = set_name + '你好'
    }
}
console.log(obj.姓名);    //  小歐
obj.姓名 = '高圓圓'
console.log(obj.name)   //  高圓圓你好

2.再看 data 屬性

當(dāng)我們?cè)趘ue實(shí)例創(chuàng)建之前定義一個(gè)數(shù)據(jù)對(duì)象,在沒有經(jīng)過 vue 實(shí)例化之前,是一個(gè)正常的對(duì)象

let tata = {
  n:0
 }
console.log(tata)  //   n:0  __proto__: Object

當(dāng)我們使用該對(duì)象充當(dāng) Vue 實(shí)例的data屬性之后

new Vue({
  // components:{Demo},
  template:`
    <div>
      <button @click="add"></button>
    </div>
  `,
  methods:{
    des(){
      this.visible = !this.visible
    },
    fn(){
      console.log('調(diào)用了fn')
    },
    add(){
      this.n++;
    }
  },
  data:tata      // 把自定義的對(duì)象充當(dāng) data 使用
}).$mount('#app')
 console.log(tata)   //  再次打印 tata 對(duì)象  如下圖
vue_get_set.png

數(shù)據(jù) n 此時(shí)是兩個(gè)函數(shù)的名字 通過 n 的獲取或者賦值可以執(zhí)行相應(yīng)的函數(shù)投储,n:(...) 表示實(shí)際上不存在 n 這個(gè)屬性了 只不過可以通過 get set 來模擬對(duì) n 的取值 和賦值根暑,在取值和賦值時(shí)完成更加靈活的操作(比如更新視圖)

Object.defineProperty() 方法

該方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性舵抹,或者修改一個(gè)對(duì)象的現(xiàn)有屬性榜贴,并返回此對(duì)象矢赁。

const object1 = {};

Object.defineProperty(object1, 'property1', {
  value: 42,       // 表示此屬性值 為 42
  writable: false    //   表示此屬性值 不可寫
});

object1.property1 = 77;
// throws an error in strict mode

console.log(object1.property1); 
// expected output: 42 

還可以設(shè)置 getter setter 屬性

ojb3 = {
    name:'小歐',
    age:18
}
let _look = null;
Object.defineProperty(ojb3,'look',{
    get(){
        return _look
    },
    set(value){
       _look = value
    }
})
ojb3.look = '好看'
console.log(ojb3.look)

總結(jié):關(guān)于 Object.defineProperty() 方法

  1. 可以給對(duì)象添加屬性value
  2. 可以給對(duì)象添加getter / setter
  3. getter / setter用于對(duì)屬性的讀寫進(jìn)行監(jiān)控
在 Vue 實(shí)例創(chuàng)建的時(shí)候糯笙,Vue會(huì)將 data 數(shù)據(jù)變?yōu)樘砑恿薵etter setter 的數(shù)據(jù)屬性

3.模擬 vue 的數(shù)據(jù)代理原理

需求一:

// 需求1:使用Object.defineProperty 定義 n
let data1 = {}   // 空對(duì)象

Object.defineProperty(data1,'n',{
    value:0
})
console.log(data1.n)   // 0

需求二:

// 需求二  n  的值不能被賦值為小于零的數(shù)
//  比如 data2.n = 1 有效  data2.n = -1 無效
let data2 = {}
data2._n = 0     // 使用_n來儲(chǔ)存n的值
Object.defineProperty(data2,'n',{
    get(){
        return this._n
    },
    set(value){
        if(value < 0) return
        this._n = value
    }
})
data2.n = -1
console.log(data2.n);    //   0    因?yàn)?1的賦值無效
data2.n = 1
console.log(data2.n);   //   1   大于0有效

但是需求二中 如果直接修改 data2._n 屬性也是無法攔截的,因此還要改進(jìn)

需求三:使用代理模式

//  需求三:使用代理
let data3 = proxy({data:{n:0}})
function proxy({data} /* 解構(gòu)賦值 */){
    const obj = {}
    //   這里的n理論上應(yīng)該遍歷data的所有key,這里簡化了
    Object.defineProperty(obj,'n',{
        get(){
            return data.n
        },
        set(value){
            if(value < 0) return
            data.n = value
        }
    })
    return obj    // obj就是代理對(duì)象
}
data3.n = -1     // 這里觸發(fā)了data3 的 setter 函數(shù) 不會(huì)賦值為負(fù)數(shù)
console.log(data3.n)    //   0 

上面這個(gè)代理如果 proxy函數(shù)的參數(shù)不是匿名對(duì)象那還是會(huì)被修改值

需求四:使用代理的加強(qiáng)版

// 需求五   用戶修改原始對(duì)象也能攔截
let myData = {n:0}
let data5 = proxy({data:myData})

function proxy({data}){
    //   這里的n理論上應(yīng)該遍歷data的所有key,這里簡化了
    let value = data.n
    delete data.n     // 這行可以不寫  因?yàn)橄旅鎰?chuàng)建的n屬性會(huì)被覆蓋
    Object.defineProperty(data,'n',{
        get(){
            return value
        },
        set(newValue){
            if(newValue < 0)return
            value = newValue
        }
    })

    // 上面這幾句會(huì)監(jiān)聽 data 對(duì)象數(shù)據(jù)的變化

    const obj = {}
    Object.defineProperty(obj,'n',{
        get(){
            return data.n
        },
        set(){
            if(value < 0)return
            data.n = value
        }
    })

    return obj
}
myData.n = -5
console.log(myData.n);    //   0  被監(jiān)聽攔截
data5.n = -6
console.log(data5.n);    //   0  被代理攔截

綜上所述撩银,vue實(shí)例創(chuàng)建時(shí)對(duì) data 的篡改就包含了這個(gè)原理

let data5 = proxy({data:myData})   // 類似于
const vm = new Vue(data:{n:0})   // Vue對(duì)于數(shù)據(jù)的篡改原理正是上面解釋的那樣
小結(jié):
vm = new Vue(data:{n:0}) 
// Vue會(huì)做出如下幾件事
  1. 會(huì)讓 vm 成為myData的代理 (proxy)
  2. 會(huì)對(duì)myData的所有屬性進(jìn)行監(jiān)控
  3. 監(jiān)控的目的就是在myData數(shù)據(jù)更改的時(shí)候给涕,通知vm實(shí)例對(duì)象從而調(diào)用render方法更新視圖
  4. 這便是Vue的看家本領(lǐng)

簡言之就是 Vue通過 Object.defineProperty 對(duì)實(shí)例對(duì)象中data進(jìn)行添加 getter / setter 從而用來對(duì)屬性的讀寫進(jìn)行監(jiān)控,從而在data數(shù)據(jù)變化時(shí)通知Vue實(shí)例進(jìn)行視圖的更新

4.Vue data屬性存在的問題

  1. Object.defineProperty的問題
Object.defineProperty(obj, 'n',{...})   // 必須要有一個(gè) n 屬性才能監(jiān)聽

如果沒有寫 n 的話 Vue會(huì)給出警告额获,無法監(jiān)聽 后面添加的屬性

new Vue({
  // components:{Demo},
  template:`
    <div>
    {{obj.b}}    
      <button @click="add">點(diǎn)擊</button>
    </div>
  `,
  methods:{
    add(){
        this.obj.b++      //  變量 b 并沒有提前寫好 頁面中就不會(huì)顯示
    }
  },
  data:{
      obj:{
          a:0     // obj.a  會(huì)被Vue監(jiān)聽 & 代理
      }
  }
}).$mount('#app')   

解決辦法:

使用 Vue.set() 方法的作用

  1. 新增 key
  2. 自動(dòng)創(chuàng)建代理和監(jiān)聽(如果沒有創(chuàng)建過)
  3. 觸發(fā)視圖更新(但并不會(huì)立刻更新)
new Vue({
  // components:{Demo},
  template:`
    <div>
    {{obj.b}}
      <button @click="add">點(diǎn)擊</button>
      <button @click="add2">點(diǎn)擊</button>
    </div>
  `,
  methods:{
    add(){
        Vue.set(this.obj,'b',1)    // 使用Vue.set()方法添加監(jiān)聽屬性
    },
      add2(){     // 點(diǎn)擊按鈕時(shí) 會(huì)更新視圖
          this.obj.b++
      }
  },
  data:{
      obj:{
          a:0,     // obj.a  會(huì)被Vue監(jiān)聽 & 代理
          b:undefined
      }
  }
}).$mount('#app')
  1. 數(shù)組的篡改該方法
new Vue({
  // components:{Demo},
  template:`
    <div>
    {{obj.array}}
      <button @click="add">點(diǎn)擊</button>
    </div>
  `,
  methods:{
    add(){
        this.obj.array.push('d')
        console.log(this.obj.array)
    }
  },
  data:{
      obj:{
         array:['a','b','c']
      }
  }
}).$mount('#app')

打印結(jié)果:

數(shù)組的篡改方法.png

為了防止每次修改數(shù)組都需要給后添加的數(shù)組項(xiàng)使用Vue.set()够庙,數(shù)組傳給Vue時(shí),數(shù)組的這七個(gè)方法會(huì)被篡改覆蓋抄邀,文檔中叫做變異方法耘眨,這些方法會(huì)自動(dòng)對(duì)數(shù)組新增項(xiàng)添加對(duì)應(yīng)的監(jiān)聽,并且會(huì)更新視圖

數(shù)組篡改的實(shí)現(xiàn)

所謂的方法篡改實(shí)際上就是在Vue實(shí)例上面加了一層原型鏈境肾,同名的方法會(huì)被最底層的原型覆蓋掉剔难,就實(shí)現(xiàn)了方法篡改

ES6寫法:以 push 方法為例 (模擬實(shí)現(xiàn),并非源碼)

class VueArray extends Array{
    push(...arsgs){
        const oldLength = this.length    //  this就是當(dāng)前數(shù)組
        super.push(...arsgs)
        console.log('我被篡改了')
        for(let i = oldLength; i < this.length; i++){
            Vue.set(this,i,this[i])  // 將每個(gè)新增的 key 都告訴 Vue 實(shí)例
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奥喻,一起剝皮案震驚了整個(gè)濱河市偶宫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌衫嵌,老刑警劉巖读宙,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異楔绞,居然都是意外死亡结闸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門酒朵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桦锄,“玉大人,你說我怎么就攤上這事蔫耽〗嵋” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵匙铡,是天一觀的道長图甜。 經(jīng)常有香客問我,道長鳖眼,這世上最難降的妖魔是什么黑毅? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮钦讳,結(jié)果婚禮上矿瘦,老公的妹妹穿的比我還像新娘。我一直安慰自己愿卒,他們只是感情好缚去,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著琼开,像睡著了一般易结。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上柜候,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天搞动,我揣著相機(jī)與錄音,去河邊找鬼改橘。 笑死滋尉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的飞主。 我是一名探鬼主播狮惜,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼碌识!你這毒婦竟也來了碾篡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤筏餐,失蹤者是張志新(化名)和其女友劉穎开泽,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體魁瞪,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡穆律,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年惠呼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片峦耘。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剔蹋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辅髓,到底是詐尸還是另有隱情泣崩,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布洛口,位于F島的核電站矫付,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏第焰。R本人自食惡果不足惜买优,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望樟遣。 院中可真熱鬧而叼,春花似錦、人聲如沸豹悬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瞻佛。三九已至脱篙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伤柄,已是汗流浹背绊困。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留适刀,地道東北人秤朗。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像笔喉,于是被迫代替她去往敵國和親取视。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

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