Vue数据响应式

139 阅读2分钟
import Vue from "vue/dist/vue.js"; 
import App from "./App.vue";

Vue.config.productionTip = false;

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

new Vue({
  data: myData,
  template: `
    <div>{{n}}</div>
  `
}).$mount("#app");

setTimeout(()=>{
  myData.n += 10
  // console.log(myData) // n {...}
},3000)

问题一:首先需要弄明白,data传入new Vue后,data产生了变化,Vue对data做了什么?

要解决这个问题,需要两个前置知识

  • getter/setter
  • Object.defineProperty

1、getter和setter的用法?

getter读取属性,setter写入属性。

getter/setter就是对虚拟属性进行读/写。

let obj2 = {
  姓: "高",
  名: "圆圆",
  get 姓名() {
    return this.姓 + this.名;
  },
};
console.log("需求二:" + obj2.姓名);
// 总结:getter 就是这样用的。不加括号的函数,仅此而已。

let obj3 = {
  姓: "高",
  名: "圆圆",
  get 姓名() {
    return this.姓 + this.名;
  },
  set 姓名(xxx){
    this.姓 = xxx[0]
    this.名 = xxx.substring(1)
  },
};

obj3.姓名 = '高媛媛'

console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)
// 总结:setter 就是这样用的。用 = xxx 触发 set 函数

2、Object.defineProperty的用法?

Object.defineProperty用来在定义完一个对象后,继续在该对象上加入新的属性。

Object.defineProperty(
obj,//mydata
"n",//我要对哪个属性进行代理和监听
{value/get,set}
let obj ={
    属性1:xxx
    属性2:yyy
    get(){},
    set(){},
};

var _zzz = 0//声明一个属性zzz,_zzz是用来存放属性"zzz"的值。
Object.defineProperty(obj , 添加属性"zzz" , {
    get(){
        return _zzz
    },
    set(value){
        _zzz = value
    }//写入值value,把值value存入 _zzz
})
  • 给对象添加属性value
  • 给对象添加getter/setter
  • getter/setter用于对属性的读/写进行监控。

3、代理和监听

需要的前置知识

语法

const p = new Proxy(target, handler)
//target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
//handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

Proxy对象可以让你去对JavaScript中的一切合法对象的基本操作进行自定义,然后用你自定义的操作去覆盖其对象的基本操作。也就是当一个对象去执行一个基本操作时,其执行的过程和结果是你自定义的,而不是对象的.(引用于 juejin.cn/post/684490…

  • function proxy(){}是什么含义?

Proxy的作用:用代理包住对象,形成匿名对象,防止用户通过对象篡改属性。

如下是没有使用代理的对象,有名字的对象。对象名"data2".

let data2 = {};
data2._n = 0; // _n 用来偷偷存储 n 的值

其他人可以通过篡改data2._n来篡改对象data2的属性

如下是使用代理的对象,匿名对象{ n: 0 },代理为obj

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 就是代理
}

代理obj与匿名对象{ n: 0 }通过proxy(){}进行关联。

读取obj的n时,通过proxy(){}返回匿名对象{ n: 0 }的n; 设置obj的n时,通过proxy(){}设置匿名对象{ n: 0 }的n。

由于只暴露代理对象obj,不暴露匿名对象{ n: 0 },因此其他人无法修改{ n: 0 }的值。

监听data,防止在初始化的时候篡改n的值

let myData5 = { n: 0 };
let data5 = proxy2({ data: myData5 }); // 括号里是匿名对象,无法访问

function proxy2({ data } /* 解构赋值*/) {
  let value = data.n;//把n的原始值记录下来
  Object.defineProperty(data, "n", {//安装一个新的n
    get() {
      return value;
    },
    set(newValue) {
      if (newValue < 0) return;
      value = newValue;
    }
  });
  // 上面这几句话会监听 data

  const obj = {};
  Object.defineProperty(obj, "n", {
    get() {
      return data.n;
    },
    set(value) {
      if (value < 0) return; //这句话多余了
      data.n = value;
    }
  });

  return obj; // obj 就是代理
}

// data3 就是 obj
console.log(`需求五:${data5.n}`);
myData5.n = -1;
console.log(`需求五:${data5.n},设置为 -1 失败了`);
myData5.n = 1;
console.log(`需求五:${data5.n},设置为 1 成功了`);

代理防止篡改左边的data5,监听防止篡改右边的myData5

let data5 = proxy2({ data: myData5 });//与下面"const vm = new Vue({data: {n:0})"这段代码是相似的
const vm = new Vue({data: {n:0})//Vue内部的源代码
  • 对myData对象的属性读写,全权由另一个对象vm负责
  • vm就是myData的代理,代理是一种设计模式,很多地方都会用到代理。
  • 不直接使用myData.n,用vm.n来操作myData.n,或者使用this.n来操作myData.n。

回到最开头的问题一:data传入new Vue后,data产生了什么变化,vue对data做了什么?

vm = new Vue({data: myData})

1、会让vm成为myData的代理

2、会对myData的所有属性进行监听。

3、为什么要监控?

监控的目的是在你设置data的时候,去更新UI。 防止myData的属性变了,vm不知道。属性变了调用render(data) , UI=render(data)

如果data有多个属性n\m\k,那么就用循环和闭包,实现上述的代理和监听。

const vm = new Vue({
data1: {n:0}
data2: {m:1}
data3: {k:2}
)

第一步:对mydata加监听

while(){}或for(){}循环语句//每次循环产生一个单独的value
闭包//读取对应的value,有几个属性就对应几个value,他们互相之间没有干扰。
let value = 0
{
    get n (){return value}
    set n (v){value = v}
    get m (){return value}
    set m (v){value = v}
    get k (){return value}
    set k (v){value = v}
}
//监听代码全程只对原始对象的属性进行修改,没有删除该对象,修改后得到一个被修改的原始对象。
//在被修改的对象的基础上,生成一个新的代理,如下

第二步:生成新的代理

{
    get n (){
        return data1.n
    }
    set n(v){
        data1.n = v
    }
    get m (){...}
    set m (){...}
    ...
    ...
}

第三步:新的代理对象返回给vm

新的代理对象会来代理被修改过(被监听)的原始对象。

问题二:什么是Vue数据响应式?

let myData = { n: 0 };
const vm = new Vue({data: mydata})

我如果修改vm.n或者修改mydata.n,那么UI中的n就会出现相应的变化来响应,Vue2通过Object.defineProperty来实现数据响应式。

要深入理解options.data

主要内容可以在如下链接找到 cn.vuejs.org/v2/guide/re… --深入响应式原理