Vue数据的响应式原理

编程入门 行业动态 更新时间:2024-10-13 04:22:58

Vue数据的响应式<a href=https://www.elefans.com/category/jswz/34/1770123.html style=原理"/>

Vue数据的响应式原理

目录

对象的响应式处理

Object.defineProperty() 方法介绍

属性的getter函数与setter函数

Object.defineProperty()方法

数据代理

defineReative()方法的定义

Observer类的创建

对象响应式的封装文件

数组的响应式处理

数组响应式的原理图

数组响应式原理代码

目录结构

observe.js文件 

Observer.js文件

def.js文件

defineReative.js文件

array.js文件


对象的响应式处理

Object.defineProperty() 方法介绍

官方说明:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

var obj={}
Object.defineProperty(obj,'a',{value:1
})
Object.defineProperty(obj,'b',{value:2
})console.log(obj.a);
console.log(obj.b);

为什么会使用Object.defineProperty() 方法来添加属性,而不是直接添加属性呢?

由于Object.defineProperty() 方法可以给新增的属性添加许多隐藏的性质,例如:

var obj={}
Object.defineProperty(obj,'a',{value:1,// 属性是否可写writable:true,// 判断属性是否可枚举enumerable:true
})
Object.defineProperty(obj,'b',{value:2,// 属性是否可写writable:false,// 判断属性是否可枚举enumerable:false
})
obj.a++    
obj.b++
console.log(obj.a);  //2,可写
console.log(obj.b);  //2,不可写for(var k in obj){console.log(k);
}   // 2  2  a(b属性不可枚举

属性的getter函数与setter函数

属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 undefined

属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 默认为 undefined

var obj={}
Object.defineProperty(obj,'a',{// value:1,get函数不能同时跟value使用get(){console.log('get函数被调用!');},Set(){console.log('set方法被调用!');}})
Object.defineProperty(obj,'b',{value:2,})
obj.a++
console.log(obj.a);  //get函数被调用!get函数被调用!undefined
console.log(obj.b);  //2

当访问对象属性的时候,会调用get()方法,同理,修改属性的时候,会调用set方法,为什么get方法不能跟value并存,由于当我们访问属性的时候(未定义get方法),会调用属性默认的get方法,来获取数据,如果我们自定义get方法,相当于方法的重写,会根据我们自定义的get方法来获取属性,因此,两者不能共存。

Object.defineProperty()方法

如果单纯的使用defineProperty()而不做任何处理的话是有弊端的,如下:

var obj={}
Object.defineProperty(obj,'a',{// value:1,get函数不能同时跟value使用get(){return 7;},set(newValue){console.log('set被调用'+newValue);       }})
Object.defineProperty(obj,'b',{value:2,})
console.log(obj.a);  //7
obj.a++     //set被调用8
console.log(obj.a);  //7

get方法与set方法其实是有弊端的,get()方法中返回的值会作为属性的值,因此输出属性a的值是7,但通过++去改变属性a的值时,虽然调用了set()方法,但set方法并没有对属性的值做出有用的改变,因此再次输出a属性的值时,结果还是7.

数据代理

针对上述问题,就需要用数据代理来解决

var obj={}
var temp
Object.defineProperty(obj,'a',{// value:1,get函数不能同时跟value使用get(){console.log('get被调用'); return temp;},set(newValue){console.log('set被调用'+newValue);  temp=newValue     }})
Object.defineProperty(obj,'b',{value:2,})
console.log(obj.a);  //get被调用 undefined
obj.a=1  //set被调用1
obj.a+=1    //get被调用   set被调用2
console.log(obj.a);  //get被调用 2

采用一个临时变量temp,作为数据的返回值,而数据更改,也是更改temp的值,这是defineReative()方法的前身。

defineReative()方法的定义

其实就是对上面步骤的一个封装

var obj={}function defineReactive(data,key,val){Object.defineProperty(data,key,{get(){console.log('get被调用'); return val;},set(newValue){console.log('set被调用'+newValue);  val=newValue     }})
}defineReactive(obj,'a',1)
console.log(obj.a);  //get被调用 1
obj.a+=1    //get被调用   set被调用2
console.log(obj.a);  //get被调用 2

由于我们不想要一个全局的temp作为临时变量,这时候就采用一个函数来进行封装,data代表处理的对象,key代表处理的属性,val代表修改属性的值,get()方法返回的是val,而set()方法修改后的值也将赋值给val,这样就将上面的步骤封装起来了。

Observer类的创建

上面的步骤只能实现对象单个属性的响应式,若想实现嵌套属性的响应式,就需要实现递归,下面是大概的原理图:

 首先通过observe()方法判断obj是否是对象,如果是对象,则创建new Observer()实例(这里先不创建__ob__)属性,再通过遍历对对象的属性进行defineReactive处理(其实就是将对象的每一层的每个属性进行响应式处理)

代码如下:

// 入口文件,主要是为了判断属性的值是不是对象,
// 如果不是,直接返回,如果是,构造Observer实例
function observe(value){if(typeof value!=='object'){return;}new Observer(value)
}
// 遍历对象的每个属性,把他们都变为响应式数据(可以通过 Object.defineProperty的get和set方法访问到)
// 构造函数的this是实例对象
class Observer{constructor(value){this.value=valuethis.work()}work(){Object.keys(this.value).forEach(key=>defineReactive(this.value,key))}
}function defineReactive(value,key,val){if(arguments.length==2){val=value[key]}observe(val)Object.defineProperty(value,key,{get(){console.log("属性"+key+"被监听");return val},set(newValue){console.log("属性"+key+"被监听");val=newValueobserve(newValue)}})
}var obj={a:{b:1},c:10
}observe(obj)console.log(obj.a.b);

对象响应式的封装文件

对上面的全部步骤进行封装,结构目录:

defineReative.js

import observe from "./observe"
// 创建与修改属性值
export default function defineReative(value,key,val=value[key]) {// 对属性的属性进行响应式observe(val)// 若没有属性,就创建属性,有属性就修改该属性Object.defineProperty(value,key,{get(){console.log(key+"属性正在被读取!");return val;},set(newValue){console.log(key+"属性正在被改写!"+newValue);val=newValue// 更改后的值也要进行响应式observe(newValue)}})
}

observe.js

import Observer from './Observer'
// 主要是用来判断是对象还是基本类型,如果是对象,添加响应式
export default function observe(value){if(typeof value!='object'){return}else{new Observer(value)}
}

Observer.js

import defineReative from './defineReative'
// 在这个类中添加响应式
export default class Observer{constructor(value){this.value=valuethis.wolk()}// 遍历添加响应式wolk(){Object.keys(this.value).forEach(key=>{defineReative(this.value,key)})}
}

index.js(测试文件) 

import observe from "./observe"// 实现简单对象的监听
var obj={a:{d:2},b:1
}observe(obj)
obj.a.d=5
obj.a.d++
console.log(obj.a.d);

数组的响应式处理

数组响应式的原理图

当使用数组的pop,push等方法时,并不会触发get,set方法,因此,数组是不能实现响应式的,这时候就需要对数组的push,pop等方法进行重写,添加响应式,那如何添加响应式呢?使用拦截器覆盖Array.prototype上的方法,在执行原型上的方法之外做数据的响应式。

1、首先应该以Array.prototype为原型创建arrayMethods对象,这时arrayMethods对象就拥有Array.prototype原型上的所有方法

2、将方法pop,push,unshift,shift,splice,sort,reverse进行响应式处理

3、将这些重写的方法覆盖掉arrayMethods对象上原有的方法

4、将数组的proto属性指向arrayMethods对象,默认的proto属性会指向Array.prototype

5、由于数组中的属性还可能是数组或对象,如果想要完全的实现响应式处理,需要遍历数组,就需要将数组中的数组进行响应式处理,数组中的对象进行wolk()方法处理

数组响应式原理代码

目录结构

observe.js文件 

import Observer from './Observer'
// 主要是用来判断是对象还是基本类型,如果是对象,添加响应式
export default function observe(value){if(typeof value!='object'){return}else{// value.__ob__相当于一个标识,如果已经创建了Observer就不用再创建// 若没有创建就直接创建实例let obif(value.__ob__!=undefined){ob=value.__ob__}else{ob=new Observer(value)}}
}

     在observe.js文件中,相比只有对象的observe.js文件,添加了一个value.__ob__属性,其实这个属性主要起到一个标识的作用,用来记录该对象是否已经响应式,避免重复给对象添加响应式。这个判断其实在单纯判断对象响应式的时候就可以添加,但我为了简单化,就没有,现在添加,是因为后面数组添加响应式的时候会用到。

Observer.js文件

import defineReative from './defineReative'
import def from './def'
import {arrayPrototype} from './array'
import observe from './observe'
// 在这个类中添加响应式
export default class Observer{constructor(value){def(value,'__ob__',this,false)this.value=valueif(Array.isArray(value)){Object.setPrototypeOf(value,arrayPrototype)this.observeArray(value)}else{this.wolk()}}// 遍历添加响应式wolk(){Object.keys(this.value).forEach(key=>{defineReative(this.value,key)})}// 数组的遍历observeArray(){for(let i=0,len=this.value.length;i<len;i++){observe(this.value[i])}}
}

      在Observer.js文件中,在构造函数中给传入的value值添加_ob__属性,它的属性值就是Observer实例,与observe.js文件中的判断相对应,随后还要对value进行判断,如果是对象,就进行对象的遍历,如果数组,就进行数组的遍历

def.js文件

// 新建一个属性
export default function def(obj,key,value,configurable){Object.defineProperty(obj,key,{value,Configurable:configurable,Writable:true})
}

  def.js文件主要是对属性添加功能的封装,主要用于添加__ob__属性,__ob__属性的作用前面已经说过了,这里不再重复

defineReative.js文件

import observe from "./observe"
// 创建与修改属性值
export default function defineReative(value,key,val=value[key]) {// 对属性的属性进行响应式observe(val)// 若没有属性,就创建属性,有属性就修改该属性Object.defineProperty(value,key,{get(){console.log(key+"属性正在被读取!");return val;},set(newValue){console.log(key+"属性正在被改写!"+newValue);val=newValue// 更改后的值也要进行响应式observe(newValue)}})
}

defineReative.js文件是对对象遍历添加响应式的封装,给对象的每个属性都添加响应式

array.js文件

import def from './def'
// 修改数组指向的原型对象
export const arrayPrototype=Object.create(Array.prototype)const ArrayMethods=['push','pop','shift','unshift','splice','sort','reverse'
]ArrayMethods.forEach(method=>{const original=Array.prototype[method]def(arrayPrototype,method,function(){const result = original.apply(this,arguments)// 有三个方法比较特殊,push,unshift,splice会添加新元素,因此也要将元素进行响应式const arg=[...arguments]let inserted=[]const ob =this.__ob__switch(method){case 'push':case 'unshift':inserted=argbreak;case 'splice':inserted=arg.slice(2)break}if(inserted){ob.observeArray(inserted)}return result},false)
})

     在Array.prototype为原型创建arrayMethods对象,这时候arrayMethods对象就拥有Array.prototype的所有方法,将方法pop,push,unshift,shift,splice,sort,reverse进行响应式处理,最后覆盖掉arrayMethods对象上原有的pop,push,unshift,shift,splice,sort,reverse方法,最后返回。有三个方法比较特殊,push,unshift,splice方法,,会添加新的元素,因此新的元素也要响应式,通过argument获取参数, inserted数组获取全部的新元素,再通过observeArray方法遍历数组(splice方法有三个参数,新添加的元素是从第三个参数开始,因此是arg.slice(2)

以上是我自己的一点小结,若果有错误的地方,请指正。

更多推荐

Vue数据的响应式原理

本文发布于:2024-02-12 12:01:03,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1687708.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:原理   数据   Vue

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!