vue的数据驱动是通过什么模式来实现的,vue数据驱动是什么意思

  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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: