Vue有一个很方便的特性就是Vue的双向绑定,即响应式变化,在Vue2.X版本中,Vue响应式变化靠的是Object.defineProperty方法实现的,但是这个方法有个问题,就是对数组的支持不全面,如我们想要通过arr[0] = 11这种下标修改值的方式,Vue是不会监听并重新渲染组件的,以及arr.length = 0这种方式清空数组,也是不支持的。

那么在Vue中,是如何实现数据的双向绑定的呢?我们可以简单模拟一下。

实现原理就是:给数据绑定get/set方法,当修改对应的属性值时,会触发对应的get/set方法,在get/set方法中实现绑定逻辑。如果是数组,会重写数组的方法,如果是对象,会使用Object.defineProperty方法。

先定义一个最简单的对象obj

let obj = {

  name:"Alice",

  age:18

}

定义一个渲染函数

function render(){
console.log("渲染")
}

给对象obj的每一个属性都绑定get/set方法

let handler = function(obj,key,value){
Object.defineProperty(obj,key,{
get(){
return value;
},
set(newValue){
if(value!==newValue){
value = newValue;
// 修改属性值时,调用渲染函数
render();
}
}
});
}

调用handler方法给obj的属性绑定get/set

function observe(obj){
for(let k in obj){
handler(obj,k,obj[k])
}
}
observer(obj);
obj.name = "Fiona" // 此时修改obj的属性值,会触发name的set方法,然后调用render函数,重新渲染

上面介绍了最简单的对象的数据双向绑定逻辑,那如果我们的对象中的属性值又是一个对象呢?如下

obj = {

  name:{"name1":"Alice"}

}

这时候,我们给name绑定了get/set方法,但是name1并没有绑定,所以我们在使用obj.name.name1 = "Fiona"的时候,是没有办法触发name1的set方法的。

我们再对上面的方法进行改进:

var handler = function(obj,key,value){
// value可能还是对象,所以需要再次给它绑定get/set方法
observe(value);
Object.defineProperty(obj,key,{
get(){
return value;
},
set(newValue){
       // 设置的值,可能还是一个对象,所以需要将设置的值的对象属性也绑定get/set方法
       observe(value);
if(value !== newValue){
value = newValue;
render();
}
}
})
} function observe(obj){
if(typeof(obj)!=="object" || obj === null){
return obj
}
for (let k in obj){
handler(obj,k,obj[k]);
}
} observe(obj);
obj.name.name1 = 'Fiona'; //此时会触发name1的set方法,调用render()
obj.age = {"age1":19}
obj.age.age1 = 20

到这里,对象【非数组】的响应式变化就完成了,但是这里还有一个问题,就是如果想要给对象新增一个属性,也是不会触发的

如上面的obj,想给它新增一个gender,是不会触发set方法的,想要给对象新增属性同时触发set方法,需要使用$set方法

vm.$set(obj,newProperty,value)

接下来我们再看看数组的响应式变化的实现

数组需要重写数组的所有方法。但是还是不支持数组的长度变化,也不支持通过数组的下标修改值的方法

数组常用的方法有7个:pop,   push,   shift,   unshift,   splice,   sort,   reverse

// 数组的常用方法
let methods = ['pop','push', 'shift', 'unshift', 'splice', 'sort', 'reverse']; // 数组原型链上的方法
let ArrayProto = Array.prototype
// 我们需要重写数组的所有方法,但是又不能影响原有的原型链,所以这里需要将原型链重新拷贝一份
let proto = Object.create(ArrayProto); // 循环数组的所有方法,给拷贝的原型链复制
methods.forEach(fn=>{
proto[fn] = function(){
// 调用原有原型链的方法
ArrayProto[fn].call(this, ...arguments);
}
} // 定义渲染函数
function render(){
console.log("渲染...")
} var handler = function(obj, key, value){
observe(obj);
Object.defineProperty = function(obj, key, {
get(){
return value;
},
set(newValue){
observe(newValue);
if(value != newValue){
value = newValue;
render();
}
}
})
} function observe(obj){
if(Array.isArray(obj)){
// 如果obj是数组,将我们自定义的原型链复制给obj的原型链
obj.__proto__ = proto;
return;
}
if(typeof(obj) !=="object" || obj===null){
return obj;
}
for(let k in obj){
handler(obj, k, obj[k]);
}
}

到这里,数组的响应式变化也就完成了

let obj = [1,2,3];

observer(obj);

obj.push(4); // 这是,会直接调用自定义原型链的push方法,并调用render

let obj = {

  name:["alice"],

}

observe(obj)

obj.name.push("fiona")

前面说了,使用defineProperty会有两个问题,数组的长度改变和数组下标赋值这种修改数组的方法是不会被监听的,所以Vue3.x说会使用proxy来实现数据的绑定,可以解决这两个问题,但是proxy的兼容性是一个问题。

下面简单看一下,如何使用proxy实现数据的响应式

let obj = {
name:"Fiona",
loc:{x:100,y:200},
arr:[1]
} function render(){
console.log("渲染...")
} let handler = {
get(target, key){
// 如上面的loc,value还是一个对象,所以需要再次进行绑定
if(typeof(target[key] ==='object' && target[key] !==null)){
return new Proxy(target[key],handler)
}
return Reflect.get(target, key);
},
set(target, key, value){
render();
Reflect.set(target, key, value);
}
} // 给obj设置代理,绑定get/set方法
let proxy = new Proxy(obj, handler); // 此时应该使用proxy去修改obj属性值,使用obj是无效的
proxy.arr.push(2)
proxy.arr.length=0
proxy.loc.x=200

Vue响应式变化的更多相关文章

  1. vue响应式数据变化

    vue响应式数据变化 话不多说,先上代码: //拷贝一份数组原型,防止修改所有数组类型变量的原型方法 let arrayProto = Array.prototype;// 数组原型上的方法 let ...

  2. 深度解析 Vue 响应式原理

    深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进 ...

  3. Vue源码--解读vue响应式原理

    原文链接:https://geniuspeng.github.io/2018/01/05/vue-reactivity/ Vue的官方说明里有深入响应式原理这一节.在此官方也提到过: 当你把一个普通的 ...

  4. Vue响应式原理及总结

    Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 ...

  5. 详解Vue响应式原理

    摘要: 搞懂Vue响应式原理! 作者:浪里行舟 原文:深入浅出Vue响应式原理 Fundebug经授权转载,版权归原作者所有. 前言 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是 ...

  6. 深入Vue响应式原理

    深入Vue.js响应式原理 一.创建一个Vue应用 new Vue({ data() { return { name: 'yjh', }; }, router, store, render: h =& ...

  7. 浅谈Vue响应式(数组变异方法)

    很多初使用Vue的同学会发现,在改变数组的值的时候,值确实是改变了,但是视图却无动于衷,果然是因为数组太高冷了吗? 查看官方文档才发现,不是女神太高冷,而是你没用对方法. 看来想让女神自己动,关键得用 ...

  8. vue响应式原理,去掉优化,只看核心

    Vue响应式原理 作为写业务的码农,几乎不必知道原理.但是当你去找工作的时候,可是需要造原子弹的,什么都得知道一些才行.所以找工作之前可以先复习下,只要是关于vue的,必定会问响应式原理. 核心: / ...

  9. vue响应式原理解析

    # Vue响应式原理解析 首先定义了四个核心的js文件 - 1. observer.js 观察者函数,用来设置data的get和set函数,并且把watcher存放在dep中 - 2. watcher ...

随机推荐

  1. JVM常量的含义与反编译助记符详解

    1.定义一个常量 public class MyTest2 { public static void main(String[] args) { System.out.println(MyParent ...

  2. 在dubbo工程中,使用druid监控

    介绍:在dubbo项目中,使用druid的监控功能 问题:因为,在网上找勒,很多的资料,显示的都是需要在web.xml中配置 <servlet> <servlet-name>D ...

  3. postgre alter命令修改字段

    参考文档:https://www.yiibai.com/postgresql/postgresql_alter_command.html PostgreSQL ALTER TABLE命令用于添加,删除 ...

  4. 使用kindeditor获取不到富文本框中的值

    获取不到富文本框中的值,网上一搜一堆,但最终没有几个能解决问题的,折腾一番最终解决.注意就是红色代码,加上之后就可以解决问题了. KindEditor.ready(function (K) { var ...

  5. Python3基础 九九乘法表

             Python : 3.7.3          OS : Ubuntu 18.04.2 LTS         IDE : pycharm-community-2019.1.3    ...

  6. 003-结构型-04-外观模式(Facade)

    一.概述 Facade模式也叫外观模式,是由GoF提出的23种设计模式中的一种.Facade模式为一组具有类似功能的类群,比如类库,子系统等等,提供一个一致的简单的界面.这个一致的简单的界面被称作fa ...

  7. 查看Oracle中是否有锁表

    转: 查看Oracle中是否有锁表 2018-04-23 17:59 alapha 阅读(19450) 评论(0) 编辑 收藏 一.用dba用户登录,或者将用户赋权为DBA用户 命令: su - or ...

  8. 将一个多表关联的条件查询中的多表通过 create select 转化成一张单表的sql、改为会话级别临时表 【我】

    将一个多表关联的条件查询中的多表通过 create   select  转化成一张单表的sql 将结果改为创建一个会话级别的临时表: -- 根据下面这两个sql CREATE TABLE revenu ...

  9. Qt bug

    1.Qt5.2.1不支持QQuickwidget来承载qml 2.Qt5.12以及以上,不支持跨线程调用数据库连接 3.线程A不断产生sql语句,需要让两个数据库分别执行这个sql语句.所以在线程A中 ...

  10. extends 类的继承 / super关键字,调用继承类里面的函数和变量

    Son 继承Father 当其他脚本想调用 Father类里面的变量 or 方法 可以把 Son r=new Son()   等价于 Father r=new Father() 注意: 函数只能单继承 ...