Vue响应式实现原理[通俗易懂]

Vue响应式实现原理[通俗易懂]Vue响应式原理Vue是数据驱动视图实现双向绑定的一种前端框架,采用的是非入侵性的响应式系统,不需要采用新的语法(扩展语法或者新的数据结构)实现对象(model)和视图(view)的自动更新,数据层(Model)仅仅是普通的Javascript对象,当Modle更新后view层自动完成更新,同理view层修改会导致model层数据更新。双向绑定实现机制Vue的双向绑定实现机制核心:依赖于Object.defineProperty()实现数据劫持订阅模式Object.defineProper

大家好,又见面了,我是你们的朋友全栈君。

Vue响应式原理

Vue是数据驱动视图实现双向绑定的一种前端框架,采用的是非入侵性的响应式系统,不需要采用新的语法(扩展语法或者新的数据结构)实现对象(model)和视图(view)的自动更新,数据层(Model)仅仅是普通的Javascript对象,当Modle更新后view层自动完成更新,同理view层修改会导致model层数据更新。

image

双向绑定实现机制

Vue的双向绑定实现机制核心:

  • 依赖于Object.defineProperty()实现数据劫持
  • 订阅模式

Object.defineProperty()

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

我们使用Object.defineProperty()进行简单的数据劫持操作:

var Book = { 
   
  name:"jsBook"
};

Object.defineProperty(Book, 'name', { 
   
  enumerable: true,
  configurable: false,
  set: function (value) { 
   
    this._name = `${ 
     value || "JavaScript编程思想"} `;
  },
  get:function() { 
   
    return `《${ 
     this._name}》`;
  },

});
Book.name = null;
console.log(Book.name) // 《JavaScript编程思想》

这只是对于一个属性的设置,当我们需要劫持对象的所有的属性的时候,可以封装 Object.defineProperty()方法并借用Object.keys()进行对象可枚举属性的遍历:

var Person = { 
   
  name:"smith",
  skill:"熟练使用Java"
};

let myReactive = function(obj, key , val) { 
   
  Object.defineProperty(obj, key, { 
   
    enumerable: true,
    configurable: false,
    set: function (value) { 
   
      val = value;
    },
    get:function() { 
   
      return val;
    },
  });
}

Object.keys(Person).forEach(key => { 
   
  myReactive(Person, key, Person[key])
})

Person.skill = "熟练使用JavaScript";
console.log(Person.name + Person.skill); // smith熟练使用JavaScript

通过简单的例子我们可以了解Object.defineProperty()的基本数据劫持操作,这也是Vue的响应式实现的基本原理,Vue在初始化对象的之前将数据定义在data对象中,初始化实例时对属性执行 getter/setter 转化过程,所以只有定义在data对象上的属性才能被劫持(被转化),同时因为JavaScript的限制Vue不能检测对象属性的添加和删除。

function observe(value, cb) { 
   
    Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
}

class Vue { 
   
    constructor(options) { 
   
        this._data = options.data;
        observe(this._data, options.render)
    }
}
let app = new Vue({ 
   
    el: '#app',
    data: { 
   
        text: 'text',
        text2: 'text2'
    },
    render(){ 
   
        console.log("render");
    }
})

Vue源码分析

  1. 初始化Vue实例
/*initMixin就做了一件事情,在Vue的原型上增加_init方法, * 构造Vue实例的时候会调用这个_init方法来初始化Vue实例 */
export function initMixin (Vue: Class<Component>) { 
   
  Vue.prototype._init = function (options?: Object) { 
    
    const vm: Component = this;
    vm._self = vm
    /*初始化生命周期*/
    initLifecycle(vm)
    /*初始化事件*/
    initEvents(vm)
    /*初始化render*/
    initRender(vm)
    /*调用beforeCreate钩子函数并且触发beforeCreate钩子事件*/
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    /*初始化props、methods、data、computed与watch*/
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    /*调用created钩子函数并且触发created钩子事件*/
    callHook(vm, 'created')
  }
}
  1. 初始化状态

我们可以了解initState(vm)方法用来初始化Vue我们配置的方法,数据等状态,所以我们重点研究一下initState()方法:

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  /*初始化props*/
  if (opts.props) initProps(vm, opts.props)
  /*初始化方法*/
  if (opts.methods) initMethods(vm, opts.methods)
  /*初始化data*/
  if (opts.data) {
    initData(vm)
  } else {
    /*该组件没有data的时候绑定一个空对象*/
    observe(vm._data = {}, true /* asRootData */)
  }
  /*初始化computed*/
  if (opts.computed) initComputed(vm, opts.computed)
  /*初始化watchers*/
  if (opts.watch) initWatch(vm, opts.watch)
}
  1. 初始化数据

在初始化数据的时候,我们需要判断data中的key 不能与props定义过的key重复,如果冲突将会以props定义的key优先,并且告警提示冲突。

/*初始化data*/
function initData (vm: Component) { 
   

/*得到data数据*/
let data = vm.$options.data

/*遍历data对象*/
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length

//遍历data中的数据
while (i--) { 
   
  if (props && hasOwn(props, keys[i])) { 
   
    process.env.NODE_ENV !== 'production' && warn(
      `The data property "${ 
     keys[i]}" is already declared as a prop. ` +
      `Use prop default value instead.`,
      vm
    )
  }
}
// observe data
/*通过observe实例化Observe对象,开始对数据进行绑定 * asRootData用来根数据,用来计算实例化根数据的个数 * 下面会进行递归observe进行对深层对象的绑定。则asRootData为非true */
observe(data, true /* asRootData */)
}
  1. 观察对象
export class Observer { 
   
constructor (value: any) { 
   
  if (!Array.isArray(value))  { 
   
    /*如果是对象则直接walk进行绑定*/
    this.walk(value)
  }
}

walk (obj: Object) { 
   
  const keys = Object.keys(obj)
  /*walk方法会遍历对象的每一个属性进行defineReactive绑定*/
  for (let i = 0; i < keys.length; i++) { 
   
    defineReactive(obj, keys[i], obj[keys[i]])
  }
}

订阅发布

  1. 创建Wahtcher

Vue对象init后会进入mount阶段,执行mountComponent函数:

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component { 
   
  vm.$el = el
  // 如果没有!vm.$options.render方法,就创建一个空的VNODE,不是生产环境啥的报错
  if (!vm.$options.render) { 
   
    vm.$options.render = createEmptyVNode
    // 报错的代码
  }
  // 调用一下回调函数
  callHook(vm, 'beforeMount')
  // 定义一个updateComponent方法
  let updateComponent

  // 如果啥啥啥条件,那么updateComponent定义成如下方式,否则直接调用_update方法
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) { 
   
    updateComponent = () => { 
   
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${ 
     id}`
      const endTag = `vue-perf-end:${ 
     id}`
      
      // 在这里的核心调用来一下_render方法创建来一个vnode
      const vnode = vm._render()
      vm._update(vnode, hydrating)
    }
  } else { 
   
      // 这里是定义的updateComponent是直接调用_update方法
    updateComponent = () => { 
   
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  
  // 实例化一个渲染watcher,用处是初始化的时候会执行回调函数,
  // 另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数
  new Watcher(vm, updateComponent, noop, { 
   
    before () { 
   
      if (vm._isMounted) { 
   
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // 函数最后判断为根节点的时候设置 vm._isMounted 为 true, 表示这个实例已经挂载了,
  // 同时执行 mounted 钩子函数
  if (vm.$vnode == null) { 
   
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}
  1. Wather构造函数

在mountComponent函数内部,通过new Wather()创建监听器 ,Vue Component都会经过一次mount阶段并创建一个Wather与之对应。

Wather的构造函数new Watcher(vm, updateComponent)

  • vm :与Wather对应的Vue Component实例,这种对应关系通过Wather去管理
  • updateComponent:可以理解成Vue Component的更新函数,调用实例render和update两个方法,render作用是将Vue对象渲染成虚拟DOM,update是通过虚拟DOM创建或者更新真实DOM

总结

  1. Vue Component都有一个对应的Wather实例
  2. Vue Component实例初始化的时候通过data绑定对象,data上的属性通过getter/setter转化
  3. Vue Component执行render方法的时候,data定义的数据对象会被读取执行getter方法,Vue Component 会记录自己依赖的data
  4. 当data数据被修改的时候,通过setter方法更新数据,Wather会通知所有依赖此data的组件去调用Vue Component 的render函数更新视图。

参考

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/144750.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • vmware15最新虚拟机激活码【在线注册码/序列号/破解码】

    vmware15最新虚拟机激活码【在线注册码/序列号/破解码】,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月18日
    121
  • iPAD越狱后下载激活成功教程版的pad软件方法总录[通俗易懂]

    iPAD越狱后下载激活成功教程版的pad软件方法总录[通俗易懂]声明:本文所说的安装软件方法都不是原创,都是前人的经验,只不过为了方便大家,做一个整理。一、事前的准备工作1、还是先说越狱,网上越狱的方法不止一种,建议按照下文操作办法(在ipad上操作)简单说就两步(1)用safari登陆www.jailbreakme.co

    2022年9月20日
    0
  • c++ 常量表达式_c++符号常量

    c++ 常量表达式_c++符号常量常量表达式主要是允许一些计算发生在编译时,即发生在代码编译阶段而不是代码运行阶段。这是很大的优化,因为如果有些事情可以在编译时做,那么它只会做一次,而不是每次程序运行时都计算。使用constexpr,你可以创建一个编译时的函数:constexprintgetConst(){ return3;}voidtest07(){ intarr[getConst()]={0}…

    2022年9月29日
    0
  • 树莓派4B系统搭建(超详细版)

    树莓派4B系统搭建(超详细版)初次使用树莓派,由于没有显示屏,配置搞了好久,然后写了这篇博客,记录一下自己的心酸史。内容有树莓派烧录,远程桌面搭建,换源。绝对的详细版教程。

    2022年6月11日
    66
  • python爬虫文件代码大全-23个Python爬虫开源项目代码

    python爬虫文件代码大全-23个Python爬虫开源项目代码今天为大家整理了23个Python爬虫项目。整理的原因是,爬虫入门简单快速,也非常适合新入门的小伙伴培养信心。所有链接指向GitHub,祝大家玩的愉快1、WechatSogou[1]–微信公众号爬虫。基于搜狗微信搜索的微信公众号爬虫接口,可以扩展成基于搜狗搜索的爬虫,返回结果是列表,每一项均是公众号具体信息字典。2、DouBanSpider[2]–豆瓣读书爬虫。可以爬下豆瓣读书标签下的所有…

    2022年5月13日
    135
  • 计算机中二进制减法的问题是什么_二进制的减法运算例子

    计算机中二进制减法的问题是什么_二进制的减法运算例子有一道作业题,要求完成下列二进制数的减法运算:   00001100-11110111这道题分析说先把减数化成补码的形式,也就是要把11110111化成补码。如果把一个二进制数化成补码,先在最高位取1,再把各位取反加1。但是上面那个题它的第一位已经是1了,怎么化呀??悬赏分:0-解决时间:2010-3-1221:21;—————————–

    2022年9月24日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号