深入 Vue 2.x 数据响应式原理 ★

简介: 深入 Vue 2.x 数据响应式原理 ★

1、Vue 对 data 做了什么

Vue 的数据响应式是它的一大特点,也是它的一大优势,Vue 中的 data 数据模型仅仅是普通的 JavaScript 对象,而当你修改它们时,视图会进行更新,这使得状态管理非常简单直接,那 Vue 到底对 data 做了什么呢? Vue 官方解释:传送门

const myData = {    //将实例中的data抽出来
  n: 0
}
console.log(myData);    //页面会打印{n:10}
new Vue({
  data: myData,    //将myData传递给Vue实例的data属性
  template: ` <div>{{n}}</div> `
}).$mount("#app");
setTimeout(()=>{
  myData.n += 10;
  console.log(myData);  //页面会打印{n:(...)}
},3000)

上述实例中,第一次打印的 myData 数据是 { n:0 } 的形式,第二次打印的 myData 数据是 { n:(...) } 的形式,这中间经历了将 myData 传递给 Vue 实例的 data 属性的过程,这个过程肯定是 Vue 对 myData 做了什么导致它的形式变化,往下看!

2、ES6 的 getter 和 setter

了在解 Vue 数据响应式原理以及上述实例中的打印 myData 发生变化的原因之前,我们先来了解一下 ES6 的 getter 和 setter,对象属性可以是一个 函数gettersetter 方法。get 和 set 也是对象的属性,只不过它们是以函数的形式定义的。

let person = {                                     let obj = {  //对象属性可以是一个 函数、getter、setter 方法
  姓: "程",                                           property: function ([parameters]) {},
  名: "序员",                                         get property() {},
  get 姓名() {                                        set property(value) {},
    return this.姓 + this.名;                      };
  },
  set 姓名(xxx){
    this.姓 = xxx[0];
    this.名 = xxx.slice(1);
  }
};
console.log(person.姓名);  //打印:程序员    //getter 相当于不加 () 的函数
person.姓名 = '舒化奶';     //用 = xxx 触发 set 函数
console.log(person);       //打印:{age:18,姓:舒,名:化奶,姓名:(...)}    和Vue中的myData一样的效果

上述实例在打印 person 对象时,会发现 person 对象中多了一个 姓名 属性,它的形式和 Vue 中的 myData 的形式类似。我们将 person 对象的 姓名:(...) 属性展开,里面包含了 get 姓名:f 姓名() 和 set 姓名:f 姓名(xxx) 。


这表示,用户确实可以对 person 的姓名属性进行读写,但实际上 person 对象并不存在该姓名属性。由此类比 Vue 中的 myData 对象,打印后的 { n:(...) } 表示 Vue 实例上的 data 对象中的 n,并不会原始属性,而用户却可以通过 get/set 操作它。


那 Vue 为什么要把 data 变成这种 get/set 的形式呢,这和 Vue 的数据响应式原理有什么关系?往下看!

3、Object.defineProperty

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象,附上该属性的 mdn 文档,感兴趣的同学可以移步了解一下 → 传送门

let person = {
  _age: 18
};
//如果一个对象被声明完了之后需要对其新增 get/set... 属性的话,需使用Object.defindProperty
Object.defindProperty(person,'age',{
  value: 88,    //给 person 对象添加一个值为 88 的 age 属性!
  get(){ return this._age },
  set(value){   //这里可对定义的属性进行筛选操作,比如添加 if 语句,可以保证设置的值满足条件才生效
    if(value < 0) return;
    this._age = value 
  }
})

上述这种方式可以实现筛选功能,比如在 set() 方法中添加 if 判断语句,从而实现有条件的设置对象的属性值,如果设置的值是正数就进行赋值,如果是负数就不进行设置。但是要注意,getter/setter 是在对象声明的时候直接用的, 如果想在声明后继续给对象添加 getter/setter 属性,则需要通过 Object.defineProperty() 的方法。

4、proxy 代理数据的安全性

上述实例可以实现数据的筛选设置功能,但是并不能保证 person._age 属性的安全性,因为其他开发人员可以通过 persong._age = -1;的方式直接将其设置为一个负值,为了解决这种安全性问题,我们可以使用下面这种 proxy 代理的方法!

let data1 = proxy({ data:{n:0} }); //括号里是匿名对象,无法访问
function proxy({data}){  //{data}是结构赋值的写法
  const obj = {}
  Object.defineProperty(obj, 'n', { //这里的'n'写死了,理论上应该遍历data的所有key
    get(){
      return data.n;
    },
    set(value){
      if(value < 0) return;
      data.n = value
    }
  })
  return obj; // obj 就是代理,这样可以保证 data 中的数据不会被直接通过 data.n 的方式进行篡改
};    //但是这种方式并不是银弹!
console.log(data1.n);    //可以拿到 data1.n 属性,但是不能通过 data1.n=-1 进行篡改
let myData = {n:0};    //但是如果将data设置成一个引用值
let data = proxy({ data:myData }); //括号里是匿名对象,无法访问
myData.n = -1;    //依然可以对属性 n 进行直接篡改! 往下看!
let myData = {n:0}
let data2 = proxy({ data:myData }) // 括号里是匿名对象,无法直接访问,但是可以访问引用 myData
function proxy({data}){  //{data}是结构赋值的写法
  let value = data.n;    //获取data中的myData的n属性
  Object.defineProperty(data, 'n', {    //创建一个n的虚拟属性,进行监听,防止被偷改:delete 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(value){
      if(value < 0) return    //这句话多余了
      data.n = value;
    }
  })
  return obj; // obj 就是代理
}

上述实例是只向外暴露代理对象,开发人员无法直接通过 data.n = -1;的方式设置 data 的属性。示例链接:传送门

5、Vue 的数据响应式原理

上述讲到的实例其实和 Vue 的数据响应式原理类似,如下代码:

let data2 = proxy({ data: myData }) 
let vm = new Vue({ data: myData })

实际上 Vue 在声明实例的时候:const vm = new Vue({data:myData});做了如下两件事情:


一、会让 vm 成为 myData 的代理(proxy)。

二、会对 myData 的所有属性进行监控,实际上是代理的内部虚拟属性。

监控的目的是为了防止 myData 的属性变了,但 vm 却不知道。如果实现监控的话,只要 myData 的属性改变都会被 vm 监听到,然后就可以调用 render(data),将数据在视图层进行重新渲染,实现数据的响应式。

Vue 的 options.data 在传入 Vue 后,会被 vue 监听,data 会被篡改,本来的 n 变成 get(n) set(n)


Vue 的 options.data 在传入 Vue 后,会被 vue 实例代理,const vm = new Vue(),vm 就是 options.data 的代理


options.data 的所有读写都会被 Vue 监控,不管是对 data 本身,还是它的代理都会被监控,vue会在data变化时更新UI

6、实现一个 input 双向绑定

原理都说的差不多了,然后说一个在面试过程中关于 Vue 双向绑定的一个高频题吧,毕竟纸上得来终觉浅,万一面试官让自己手写一个的话,你只会说理论很显然是不行的。


首先我们来看一下如何做到保证 视图层 => 数据层 方向的绑定,当调用属性的地方改变了这个属性(通常是一个表单元素),那么这个对象(或变量)的属性也随之改变,具体代码如下:

<input type="text" id="model">
<script>
  let user = {age: 1};
  model.value = user.age; //设置初始化默认值
  model.addEventListener('input', (event) => {    //监听 input 事件
    user.age = event.target.value;
    console.log('在 input 框内修改视图层的值!')
    console.log('此时 model 层的 user.age 为:' + user.age);
  })
</script>

然后我们再来看一下如何做到保证 数据层 => 视图层 方向的绑定,当一个对象(或变量)的属性改变,那么调用这个属性地方也应该改变,具体代码如下:

<input type="text" id="model">
<button onclick="addAge()">年龄 +1</button>
<script>
  let user = {age: 1};
  model.value = user.age; //设置初始化默认值
  Object.defineProperty(user, 'age', {
    get: function() {
      return model.value;
    },
    set: function(v) {
      model.value = v;
      console.log('此时 view 层的 model.value 为:' + model.value);
    }
  })
  function addAge() {
    user.age += 1;
    console.log('点击按钮,修改 model 层的 user.age!');
  }
</script>
目录
相关文章
|
22天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
126 64
|
25天前
|
JavaScript 前端开发 开发者
Vue是如何劫持响应式对象的
Vue是如何劫持响应式对象的
23 1
|
25天前
|
JavaScript 前端开发 API
介绍一下Vue中的响应式原理
介绍一下Vue中的响应式原理
27 1
|
28天前
|
监控 JavaScript 算法
深度剖析 Vue.js 响应式原理:从数据劫持到视图更新的全流程详解
本文深入解析Vue.js的响应式机制,从数据劫持到视图更新的全过程,详细讲解了其实现原理和运作流程。
|
1月前
|
JavaScript
Vue 双向数据绑定原理
Vue的双向数据绑定通过其核心的响应式系统实现,主要由Observer、Compiler和Watcher三个部分组成。Observer负责观察数据对象的所有属性,将其转换为getter和setter;Compiler解析模板指令,初始化视图并订阅数据变化;Watcher作为连接Observer和Compiler的桥梁,当数据变化时触发相应的更新操作。这种机制确保了数据模型与视图之间的自动同步。
|
1月前
|
缓存 JavaScript 搜索推荐
Vue SSR(服务端渲染)预渲染的工作原理
【10月更文挑战第23天】Vue SSR 预渲染通过一系列复杂的步骤和机制,实现了在服务器端生成静态 HTML 页面的目标。它为提升 Vue 应用的性能、SEO 效果以及用户体验提供了有力的支持。随着技术的不断发展,Vue SSR 预渲染技术也将不断完善和创新,以适应不断变化的互联网环境和用户需求。
74 9
|
1月前
|
JavaScript 数据管理 Java
在 Vue 3 中使用 Proxy 实现数据双向绑定的性能如何?
【10月更文挑战第23天】Vue 3中使用Proxy实现数据双向绑定在多个方面都带来了性能的提升,从更高效的响应式追踪、更好的初始化性能、对数组操作的优化到更优的内存管理等,使得Vue 3在处理复杂的应用场景和大量数据时能够更加高效和稳定地运行。
59 1
|
1月前
|
JavaScript 开发者
在 Vue 3 中使用 Proxy 实现数据的双向绑定
【10月更文挑战第23天】Vue 3利用 `Proxy` 实现了数据的双向绑定,无论是使用内置的指令如 `v-model`,还是通过自定义事件或自定义指令,都能够方便地实现数据与视图之间的双向交互,满足不同场景下的开发需求。
62 1
|
25天前
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
54 0
|
2月前
|
API
vue3知识点:响应式数据的判断
vue3知识点:响应式数据的判断
30 3