深入理解Vue数据响应式

Vue数据响应式主要研究的是 Vue 构造选项中 data 属性的特性

深入响应式 官方文档 网址: https://cn.vuejs.org/v2/guide/reactivity.html

1. 首先理解 ES6 的 getter 与 setter 语法

obj = {         // 首先创建一个普通对象
    name:'小欧',
    age:18,
    姓名(){
        return this.name;
    }
}
console.log(obj.姓名());    //  小欧

// 如何使  obj.姓名   不带括号也可以得到 name 值呢?
// 使用ES6新语法

obj = {
    name:'小欧',
    age:18,
    get 姓名(){     // get (读) 一个值时执行的函数  获取时无需使用()执行
        return this.name;
    }
}
console.log(obj.姓名);    //  小欧

obj = {
    name:'小欧',
    age:18,
    get 姓名(){
        return this.name;
    },
    set 姓名(set_name){     //  同样 set  (写) 一个值得时候也能执行相应的函数
        this.name = set_name + '你好'
    }
}
console.log(obj.姓名);    //  小欧
obj.姓名 = '高圆圆'
console.log(obj.name)   //  高圆圆你好

2.再看 data 属性

当我们在vue实例创建之前定义一个数据对象,在没有经过 vue 实例化之前,是一个正常的对象

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

当我们使用该对象充当 Vue 实例的data属性之后

new Vue({
  // components:{Demo},
  template:`
    <div>
      <button @click="add"></button>
    </div>
  `,
  methods:{
    des(){
      this.visible = !this.visible
    },
    fn(){
      console.log('调用了fn')
    },
    add(){
      this.n++;
    }
  },
  data:tata      // 把自定义的对象充当 data 使用
}).$mount('#app')
 console.log(tata)   //  再次打印 tata 对象  如下图
vue_get_set.png

数据 n 此时是两个函数的名字 通过 n 的获取或者赋值可以执行相应的函数,n:(...) 表示实际上不存在 n 这个属性了 只不过可以通过 get set 来模拟对 n 的取值 和赋值,在取值和赋值时完成更加灵活的操作(比如更新视图)

Object.defineProperty() 方法

该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

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 

还可以设置 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)

总结:关于 Object.defineProperty() 方法

  1. 可以给对象添加属性value
  2. 可以给对象添加getter / setter
  3. getter / setter用于对属性的读写进行监控
在 Vue 实例创建的时候,Vue会将 data 数据变为添加了getter setter 的数据属性

3.模拟 vue 的数据代理原理

需求一:

// 需求1:使用Object.defineProperty 定义 n
let data1 = {}   // 空对象

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

需求二:

// 需求二  n  的值不能被赋值为小于零的数
//  比如 data2.n = 1 有效  data2.n = -1 无效
let data2 = {}
data2._n = 0     // 使用_n来储存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    因为-1的赋值无效
data2.n = 1
console.log(data2.n);   //   1   大于0有效

但是需求二中 如果直接修改 data2._n 属性也是无法拦截的,因此还要改进

需求三:使用代理模式

//  需求三:使用代理
let data3 = proxy({data:{n:0}})
function proxy({data} /* 解构赋值 */){
    const obj = {}
    //   这里的n理论上应该遍历data的所有key,这里简化了
    Object.defineProperty(obj,'n',{
        get(){
            return data.n
        },
        set(value){
            if(value < 0) return
            data.n = value
        }
    })
    return obj    // obj就是代理对象
}
data3.n = -1     // 这里触发了data3 的 setter 函数 不会赋值为负数
console.log(data3.n)    //   0 

上面这个代理如果 proxy函数的参数不是匿名对象那还是会被修改值

需求四:使用代理的加强版

// 需求五   用户修改原始对象也能拦截
let myData = {n:0}
let data5 = proxy({data:myData})

function proxy({data}){
    //   这里的n理论上应该遍历data的所有key,这里简化了
    let value = data.n
    delete data.n     // 这行可以不写  因为下面创建的n属性会被覆盖
    Object.defineProperty(data,'n',{
        get(){
            return value
        },
        set(newValue){
            if(newValue < 0)return
            value = newValue
        }
    })

    // 上面这几句会监听 data 对象数据的变化

    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  被监听拦截
data5.n = -6
console.log(data5.n);    //   0  被代理拦截

综上所述,vue实例创建时对 data 的篡改就包含了这个原理

let data5 = proxy({data:myData})   // 类似于
const vm = new Vue(data:{n:0})   // Vue对于数据的篡改原理正是上面解释的那样
小结:
vm = new Vue(data:{n:0}) 
// Vue会做出如下几件事
  1. 会让 vm 成为myData的代理 (proxy)
  2. 会对myData的所有属性进行监控
  3. 监控的目的就是在myData数据更改的时候,通知vm实例对象从而调用render方法更新视图
  4. 这便是Vue的看家本领

简言之就是 Vue通过 Object.defineProperty 对实例对象中data进行添加 getter / setter 从而用来对属性的读写进行监控,从而在data数据变化时通知Vue实例进行视图的更新

4.Vue data属性存在的问题

  1. Object.defineProperty的问题
Object.defineProperty(obj, 'n',{...})   // 必须要有一个 n 属性才能监听

如果没有写 n 的话 Vue会给出警告,无法监听 后面添加的属性

new Vue({
  // components:{Demo},
  template:`
    <div>
    {{obj.b}}    
      <button @click="add">点击</button>
    </div>
  `,
  methods:{
    add(){
        this.obj.b++      //  变量 b 并没有提前写好 页面中就不会显示
    }
  },
  data:{
      obj:{
          a:0     // obj.a  会被Vue监听 & 代理
      }
  }
}).$mount('#app')   

解决办法:

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

  1. 新增 key
  2. 自动创建代理和监听(如果没有创建过)
  3. 触发视图更新(但并不会立刻更新)
new Vue({
  // components:{Demo},
  template:`
    <div>
    {{obj.b}}
      <button @click="add">点击</button>
      <button @click="add2">点击</button>
    </div>
  `,
  methods:{
    add(){
        Vue.set(this.obj,'b',1)    // 使用Vue.set()方法添加监听属性
    },
      add2(){     // 点击按钮时 会更新视图
          this.obj.b++
      }
  },
  data:{
      obj:{
          a:0,     // obj.a  会被Vue监听 & 代理
          b:undefined
      }
  }
}).$mount('#app')
  1. 数组的篡改该方法
new Vue({
  // components:{Demo},
  template:`
    <div>
    {{obj.array}}
      <button @click="add">点击</button>
    </div>
  `,
  methods:{
    add(){
        this.obj.array.push('d')
        console.log(this.obj.array)
    }
  },
  data:{
      obj:{
         array:['a','b','c']
      }
  }
}).$mount('#app')

打印结果:

数组的篡改方法.png

为了防止每次修改数组都需要给后添加的数组项使用Vue.set(),数组传给Vue时,数组的这七个方法会被篡改覆盖,文档中叫做变异方法,这些方法会自动对数组新增项添加对应的监听,并且会更新视图

数组篡改的实现

所谓的方法篡改实际上就是在Vue实例上面加了一层原型链,同名的方法会被最底层的原型覆盖掉,就实现了方法篡改

ES6写法:以 push 方法为例 (模拟实现,并非源码)

class VueArray extends Array{
    push(...arsgs){
        const oldLength = this.length    //  this就是当前数组
        super.push(...arsgs)
        console.log('我被篡改了')
        for(let i = oldLength; i < this.length; i++){
            Vue.set(this,i,this[i])  // 将每个新增的 key 都告诉 Vue 实例
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,198评论 6 514
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,334评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,643评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,495评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,502评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,156评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,743评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,659评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,200评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,282评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,424评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,107评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,789评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,264评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,390评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,798评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,435评论 2 359

推荐阅读更多精彩内容