vue的数据驱动是通过什么模式来实现的,vue数据驱动是什么意思
这篇文章主要介绍了详解某视频剪辑软件数据驱动原理的相关资料,帮助大家更好的理解和学习某视频剪辑软件框架的相关知识,感兴趣的朋友可以了解下
前言
某视频剪辑软件区别于传统的射流研究…库,例如JQuery,其中一个最大的特点就是不用手动去操作多姆,只需要对数据进行变更之后,视图也会随之更新。比如你想修改部门#应用程序里的内容:
///JQuery
div id=app/div
脚本
$(#app ).文本(“lxb”)
/脚本
模板
div id= app"{ message } }/div
按钮@click=更改点击修改消息/按钮
/模板
脚本
导出默认值{
data () {
返回{
消息:“lxb”
}
},
方法:{
更改(){
this.message=lxb1 //触发视图更新
}
}
}
/脚本
在代码层面上的最大区别就是,JQuery直接对数字正射影像图进行了操作,而某视频剪辑软件则对数据进行了操作,接下来我们通过分析源码来进一步分析,Vue是如何做到数据驱动的,而数据驱动主要分成两个部分依赖收集和派发更新。
数据驱动
//_init方法中
初始生命周期(虚拟机)
初始化事件(虚拟机)
初始化渲染(虚拟机)
呼叫挂钩(虚拟机,"创建前")
初始注入(虚拟机)//在数据/属性之前解析注入
初始状态(虚拟机)//重点分析
initProvide(vm) //在数据/属性之后解析提供
呼叫挂钩(虚拟机,"已创建")
在某视频剪辑软件初始化会执行_init方法,并调用初始状态方法。初始状态相关代码在src/核心/实例/状态。射流研究…下
导出函数initState(虚拟机:组件){
虚拟机._watchers=[]
const opts=vm .$选项
if (opts.props) initProps(vm,opts.props) //初始化小道具
if (opts.methods) initMethods(vm,opts.methods) //初始化方法
if (opts.data) {
初始化数据(虚拟机)//初始化数据
}否则{
观察(虚拟机._data={},true /* asRootData */)
}
如果(opts。computed)init computed(VM,opts.computed) //初始化计算
if (opts.watch opts.watch!==nativeWatch) { //初始化看
initWatch(vm,opts.watch)
}
}
我们具体看看initData是如何定义的。
函数initData(虚拟机:组件){
设数据=vm .$options.data
数据=虚拟机. data=数据类型===函数//把数据挂载到了虚拟机._数据上
?getData(data,vm) //执行数据调用(虚拟机)
:data {}
如果(!isPlainObject(data)) {
data={} //这也是为什么数据函数需要返回一个目标不然就会报这个警告
process.env.NODE_ENV==生产警告(
数据函数应该返回对象:\n
https://vuejs。org/v2/guide/组件。 html #数据必须是函数,
伏特计
)
}
//实例上的代理数据
const keys=Object.keys(data) //取到数据中所有的键值所组成的数组
常量属性=vm .$选项。道具
常数方法=vm .$options.methods
设i=密钥.长度
while (i - ) {
const key=keys[i]
if (process.env.NODE_ENV!==生产){
if (methods hasOwn(methods,key)) { //避免方法名与数据的键重复
警告(
"方法" ${key} "已被定义为数据属性。
伏特计
)
}
}
if (props hasOwn(props,key)) { //避免小道具的键与数据的键重复
process.env.NODE_ENV==生产警告(
数据属性" ${key} "已被声明为属性。`
`请改用合适默认值。`,
伏特计
)
} else if(!isReserved(key)) { //判断是不是保留字段
proxy(vm,` _data `,key) //代理
}
}
//观察数据
观察(data,true /* asRootData */) //响应式处理
}
其中有两个重要的函数分别是代理人跟观察,在往下阅读之前,如果还有不明白对象。定义属性作用的同学,可以点击这里进行了解,依赖收集跟派发更新都需要依靠这个函数进行实现。
proxy
代理人分别传入虚拟机, _数据,数据中的键值,定义如下:
const sharedPropertyDefinition={
可枚举:真,
可配置:真,
get: noop,
设置:noop
}
导出函数代理(目标:对象,源密钥:字符串,密钥:字符串){
sharedpropertydefinition。get=函数代理getter(){
返回此[源密钥][密钥]
}
sharedpropertydefinition。set=函数代理设置器(val){
这个[源密钥][密钥]=val
}
Object.defineProperty(target,key,sharedPropertyDefinition)
}
代理人函数的逻辑很简单,就是对虚拟机._数据上的数据进行代理,虚拟机._数据上保存的就是数据数据。通过代理的之后我们就可以直接通过this.xxx访问到数据上的数据,实际上访问的就是这个. data.xxx。
observe
oberse定义在src/core/oberse/index.js下,关于数据驱动的文件都存放在src/核心/观察者这个目录中:
导出函数观察(值:any,asRootData:布尔):观察者无效{
如果(!是对象(值) VNode的值实例){//判断是否是对象或者是虚拟节点
返回
}
让ob:观察者无效
//是否拥有__ob__属性有的话证明已经监听过了,直接返回该属性
if (hasOwn(value, __ob__ )值观察员的实例){
ob=值. ob__
} else if(
应该遵守//能否被观察
!isServerRendering() //是否是服务端渲染
(数组。is array(value) isPlainObject(value))//是否是数组、对象、能否被扩展、是否是某视频剪辑软件函数
Object.isExtensible(值)
!价值. isVue
) {
ob=新观察者(值)//对价值进行观察
}
if (asRootData ob) {
ob.vmCount
}
返回鄂毕河(Ob)
}
观察函数会对传入的价值进行判断,在我们初始化过程会走到新观察者(值),其他情况可以看上面的注释。
Observer类
导出类观察者{
值:任意;//观察的数据
Dep:Dep;//dep实例用于派发更新
虚拟机数量:数字;//将此对象作为根$数据的虚拟机数量
构造函数(值:任意){
这个值=值
this.dep=new Dep()
this.vmCount=0
//把__ob__变成不可枚举的,因为没有必要改变看守人本身
定义(值, __ob__ ,这个)会执行价值. ob_=这个(观察者实例)操作
if (Array.isArray(value)) { //当价值是数组
if (hasProto) {
protoAugment(value,arrayMethods) //重写数组。原型的相关方法
}否则{
copyAugment(value,arrayMethods,arrayKeys) //重写数组。原型的相关方法
}
this.observeArray(值)
}否则{
散步(值)//当价值为对象
}
}
/**
*遍历所有属性,并将其转换为
* getter/setter .只有在以下情况下才应调用此方法
*值类型是对象。
*/
walk (obj: Object) {
常量键=Object.keys(obj)
对于(设I=0;I键.长度i ) {
defineReactive(obj,keys[i]) //对数据进行响应式处理
}
}
/**
*观察数组项目列表。
*/
观察者数组(物品:数组任意){
对于(设i=0,l=items.lengthI li ) {
观察(项目[i]) //遍历价值数组的每一项并调用观察函数,进行响应式处理
}
}
}
观察类要做的事情通过查看源码也是清晰明了,对数据进行响应式处理,并对数组的原型方法进行重写!定义活动函数就是实现依赖收集和派发更新的核心函数了,实现代码如下。
依赖收集
定义活动
导出函数定义活动(
obj:对象,//数据数据
关键:字符串,//数据中对应的键值
val: any,//给数据[关键字]赋值可选
customSetter?函数,//自定义作曲者可选
浅薄?boolean //是否对数据[关键字]为对象的值进行观察递归可选
) {
const dep=new Dep() //Dep实例**每一个键对应一个资料执行防止实例**
const属性=对象。getownpropertydescriptor(obj,key) //拿到对象的属性描述
if(property属性。可配置===false){//判断对象是否可配置
返回
}
//满足预定义的getter/setter
const getter=property属性。得到
const setter=属性属性。设置
如果((!getter setter)参数。长度===2){//没有吸气剂或者有二传手,并且传入的参数有两个
val=obj[key]
}
let childOb=!浅层观察(val) //根据浅薄,递归遍历英国压力单位对象,相当于英国压力单位当做数据传入
Object.defineProperty(obj,key,{
可枚举:真,
可配置:真,
获取:函数reactiveGetter () {
const value=getter?getter.call(obj) : val
if (Dep.target) { //当前的全部的看守人实例
dep.depend() //把当前的副目标加入到副潜艇数组中
if (childOb) { //如果英国压力单位是对象,
childOb.dep.depend() //会在价值. ob_的副潜艇数组中加入副目标,忘记鄂毕河(Ob)实例属性的同学可往回翻一番
if (Array.isArray(value)) {
从属数组(值)//定义如下,逻辑也比较简单
}
}
}
返回值
},
设置:函数反应设置器(新值){
//.
}
})
}
函数依赖数组(值:数组){
对于(设e,i=0,l=value.lengthI li ) {
e=值[我]
e。e . _ _ ob _ _ e . _ _ ob _ _。离开depend()//如果e是响应式数据,则往产科医生。离开潜水艇数组中加入副目标
if (Array.isArray(e)) {
受抚养人(e) //递归遍历
}
}
}
代码中多次用到了资料执行防止类和副目标,理解清楚了它们的作用,我们就离某视频剪辑软件数据驱动的原理更近一步了,相关的代码如下:
Dep
让uid=0
/**
* dep是一个可以有多个
*订阅它的指令。
*/
导出默认类副主任
静态目标:观察者;
id:数字;
subs:数组监视器;
构造函数(){
this.id=uid //每一个资料执行防止都有一个唯一的身份
this.subs=[] //存放看守人实例的数组
}
addSub (sub: Watcher) {
this.subs.push(sub) //往this.subs加入看守人
}
removeSub (sub: Watcher) {
remove(this.subs,sub) //删除this.subs对应的看守人
}
依赖(){
if (Dep.target) {
//watcher.addDep(this)其实
Dep.target.addDep(this) //在看守人类中查看
}
}
通知(){
//首先稳定订户列表
const subs=this.subs.slice()
if (process.env.NODE_ENV!==生产!config.async) {
//如果没有运行异步,subs在调度程序中不排序
//我们现在需要对它们进行排序,以确保它们能够正确启动
//订单
subs.sort((a,b)=a.id - b.id) //根据看守人的编号进行排序
}
对于(设i=0,l=subs.lengthI li ) {
分句[我].update() //遍历潜水艇数组中的每一个看守人执行更新方法
}
}
}
//正在评估的当前目标观察器。
//这是全局唯一的,因为只有一个观察器
//可以一次求值。
Dep.target=null //Dep.target代表当前全局的看守人
const targetStack=[]
导出函数推送目标(目标:观察者){
targetStack.push(目标)
Dep.target=target //赋值
}
导出函数popTarget () {
targetStack.pop()
离开target=目标堆栈[目标堆栈。长度-1]//赋值
}
资料执行防止的定义还是非常清晰的,代码注释如上,很明显资料执行防止跟看守人就跟捆绑销售一样,互相依赖。我们在分析denfineReactive的时候,在对数据进行响应式操作的时候,通过对象。定义属性重写了吸气剂函数。
Object.defineProperty(obj,key,{
可枚举:真,
可配置:真,
获取:函数reactiveGetter () {
const value=getter?getter.call(obj) : val
if (Dep.target) { //当前的全部的看守人实例
dep.depend() //把当前的副目标加入到副潜艇数组中
//.
}
返回值
},
其中的依赖于赖()实际上就是执行了Dep.target.addDep(这个),这个指向资料执行防止实例,而副目标是一个看守人实例,即执行watcher.addDep(this)函数。我们接下来在看看这个函数做了什么:
类监视器{
addDep (dep: Dep) {
const id=dep.id
如果(!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep) //
如果(!this.depIds.has(id)) {
dep.addSub(this) //会把看守人插入到副潜艇数组中
}
}
}
}
可以通过下图以便理解数据、Dep、观察者的关系:
回到代码,其中dep.addSub(this)会将wathcer的当前实例插入dep.subs的数组中,为后续的调度更新做准备,从而完成依赖关系的收集。但是到目前为止,我们只分析了依赖收集是如何实现的,但是依赖收集的时机是什么时候呢?什么时候触发getter函数实现依赖收集?在收集依赖项时,Dep.tagrget对应的wathcer是什么?
观察器大致可以分为三类:*渲染观察器:每个实例对应一个唯一的(只有一个)*计算观察器:每个实例可以有多个,由计算属性生成(computed有多少keyy,就有多少个computedWatcher) *用户观察器:每个实例可以有多个,由观察属性生成(和computed一样,用户观察器的数量由键的数量决定)。为避免混淆,接下来我们说的观察器都是渲染观察器。我们知道在Vue初始化的过程中,执行mountComponent函数时,会执行newWatcher (VM,updateComponent,{},true),这里的Watcher就是渲染Watcher。
沃希特级
get () {
push target(this)//dep . target=this
传能线密度值
const vm=this.vm
尝试{
Value=this.getter.call (VM,VM)//更新视图
} catch (e) {
if (this.user) {
handleError(e,vm,“监视器的getter”$ { this . expression } `)
}否则{
扔e
}
}最后{
//“触摸”每个属性,以便它们都被跟踪为
//深度观察的依赖性
如果(this.deep) {
遍历(值)
}
popTarget()
this.cleanupDeps()
}
返回值
}
}
新的watcher会直接执行this.get()方法,然后pushTarget(this)进行渲染watcher,所以当前Dep.target是渲染Watcher(用于更新视图)。当我们执行this.getter时,将调用render函数,此时将读取vm实例上的数据。此时会触发getter函数,进行依赖收集。这是依赖项收集的时间,例如
{{ message }} //将读取vm。_data.message并触发getters函数。
派发更新
让我们继续看电抗函数的定义。
导出函数defineReactive(
对象,
键:字符串,
瓦尔:任何,
customSetter?功能,
浅薄?布尔型
) {
const dep=新dep()
//.
Object.defineProperty(obj,key,{
可枚举:真,
可配置:真,
get:函数reactiveGetter () {
//.
},
set:函数reactiveSetter (newVal) {
/* eslint-禁用无自比较*/
if (newVal===value (newVal!==newVal值!==value)) {
返回
}
/* eslint-启用无自比较*/
if (process.env.NODE_ENV!==production customSetter) {
customSetter()
}
//#7981:对于不带setter的访问器属性
如果(getter!setter)返回
if (setter) {
setter.call(obj,new val)https://cn . vue js . org//images/data . png
}否则{
val=newVal
}
childOb=!浅层观察(纽瓦尔)
Dep.notify() //遍历dep.subs数组,取出所有wathcer并执行更新操作。
}
})
}
当我们修改数据时,setter函数就会被触发。此时,dep.notify、dep.subs被执行。dep.subs中的所有观察器都将执行update方法。对于渲染观察者来说,就是执行this.get()方法,更新视图。这样就实现了数据驱动。至此,我们已经分析完了Vue的数据驱动原理。如果你还不清楚这个流程,可以参考官方示意图:
摘要
数据的getter和setter函数由Object.defineProperty函数重写,实现更新的收集和分发。
一个键值对应一个Dep实例,一个Dep实例可以包含多个观察器,一个观察器也可以包含多个Dep。
Dep用于收集和管理依赖关系,并通知相应的观察器执行相应的操作。
收集时间是执行render方法、读取vm上的数据并触发getter函数的时间。而调度更新是指当数据发生变化时,触发setter函数,通过dep.notify()通知被收集的watcher执行相应的操作。
以上是对Vue数据驱动原理的详细说明。更多关于Vue数据驱动原理的信息,请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。