Vue3.0数据响应式原理详解_vue.js

来源:脚本之家  责任编辑:小易  

前言 最近深入学习了Vue实现响应式的部分源码,将我的些许收获和思考记录下来,希望能对看到这篇文章的人有所帮助。有什么问题欢迎指出,大家共同进步。 什么是响应式系统 一句话概括:数据变更驱动视图更新。这样我们就可以以“数据驱动”的思维来编写我们的代码,更多的关注业务,而不是dom操作。其实Vue响应式的实现是一个变化追踪和变化应用的过程。 vue响应式原理 以数据劫持方式,拦截数据变化;以依赖收集方式,触发视图更新。利用es5 Object.defineProperty拦截数据的setter、getter;getter收集依赖,setter触发依赖更新,而组件render也会变为一个watcher callback被加入相应数据的依赖中。 发布订阅 利用发布订阅设计模式实现,Observer作为发布者,Watcher作为订阅者,两者无直接交互,通过Dep进行统一调度。 Observer负责拦截get, set;get时触发dep添加依赖,set时调度dep发布;添加Watcher时会触发订阅数据的get,并加入到dep调度中心的订阅者队列中。 以下的UML类图是Vue实现响应式功能的类,以及他们之间的引用关系。 只包含部分属性方法 上图中的类已经标识的蛮清楚了,但是还是需要一个调用关系图,让调用过程更加清晰,如下图所示。 响应式data对象中,每一项key的劫持get/set函数都闭包了Dep调度实例,这张图显示了一个key更改过程中的数据流转。 部分源码 数据变更过程中的订阅/发布模型上图已经清晰的展示了,从图中我们已经知道了可以通过增加watcher来订阅某一项数据的变更。那么,我们只需要把组件render作为一个watcher订阅的话,数据驱动视图的渲染岂不是水到渠成了。Vue正是这么做的! 以下代码片段来自Vue.prototype._mount函数 callHook(vm, 'beforeMount') vm._watcher = new Watcher(vm, () => { vm._update(vm._render(), hydrating) }, noop) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } 一些问题思考 #person赋值新的对象,新对象里的属性是否也是响应式的呢? var vm = new Vue({ el: '#app', data: () => ({ person: null }) }) vm.person = {name: 'zs'} setTimeout(() => { // 更改name vm.person.name = 'finally zs' }, 3000) 答案:是响应式的。 原因:因为Vue劫持set时,会对value再次做observe,源码如下。 function reactiveSetter (newVal) { /* ...省略部分代码 */ // 这里会再次对新的value做拦截 childOb = observe(newVal) dep.notify() } #当我们监听多层属性时,上层引用变更,是否会触发回调? var vm = new Vue({ data: () => ({ person: {name: '令狐洋葱'} }), watch: { 'person.name'(val) { console.log('name updated', val) } } }) vm.person = {} 答案:会。 原因:person.name作为一个表达式传入Watcher时,会被解析成类似这样的函数 () => {this.vm.person.name} 这样就会先触发person get, 然后触发name get;所以我们配置的回调函数,不仅仅加入到了name依赖中,person也有。 #接着上个问题,person如果被赋值了新的对象,老对象和老对象上的依赖如何垃圾回收的? 老对象的回收:由于老对象的直接引用只有vue实例上的person,person切换到了新的引用,所以老对象没有引用了,就会被回收掉。 老对象上的依赖dep,watcher的依赖里还存在;但是在run执行时,会调用watcher的get() 获取当前值;get中会执行新的依赖收集,并在收集完毕后,清空老的依赖。 具体源码如下: /** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) const value = this.getter.call(this.vm, this.vm) // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() return value } #当我们多次同步修改name时,回调函数是否会触发多次? var vm = new Vue({ data: () => ({ person: {name: '令狐洋葱'} }), watch: { 'person.name': (val) { console.log('name updated: ' + val) } } }) vm.person = {name: 'zs'} vm.person.name = '无敌' 答案: 不会,因为watch回调函数执行是异步的,且会去重。可以通过sync强制配置成同步run,就会执行2次了。 自己实现一个响应式系统 只包含核心功能,具体源码可以看这里https://github.com/Zenser/z-vue,欢迎来star。 实现功能非常基础啦,重在理解,功能不全的。 Observer class Observe { constructor(obj) { Object.keys(obj).forEach(prop => { reactive(obj, prop, obj[prop]) }) } } function reactive(obj, prop) { let value = obj[prop] // 闭包绑定依赖 let dep = new Dep() Object.defineProperty(obj, prop, { configurable: true, enumerable: true, get() { //利用js单线程,在get时绑定订阅者 if (Dep.target) { // 绑定订阅者 dep.addSub(Dep.target) } return value }, set(newVal) { value = newVal // 更新时,触发订阅者更新 dep.notify() } }) // 对象监听 if (typeof value === 'object' && value !== null) { Object.keys(value).forEach(valueProp => { reactive(value, valueProp) }) } } Dep class Dep { constructor() { this.subs = [] } addSub(sub) { if (this.subs.indexOf(sub) === -1) { this.subs.push(sub) } } notify() { this.subs.forEach(sub => { const oldVal = sub.value sub.cb && sub.cb(sub.get(), oldVal) }) } } Watcher class Watcher { constructor(data, exp, cb) { this.data = data this.exp = exp this.cb = cb this.get() } get() { Dep.target = this this.value = (function calcValue(data, prop) { for (let i = 0, len = prop.length; i < len; i++ ) { data = data[prop[i]] } return data })(this.data, this.exp.split('.')) Dep.target = null return this.value } } 参考文档:https://cn.vuejs.org/v2/guide/reactivity.html 您可能感兴趣的文章:Vue实现双向绑定的原理以及响应式数据的方法基于Vue2x实现响应式自适应轮播组件插件VueSliderShow功能浅谈Vue 数据响应式原理浅谈Vue响应式(数组变异方法)浅谈实现vue2.0响应式的基本思路Vue的事件响应式进度条组件实例详解代码详解Vuejs响应式原理Vue响应式原理深入解析及注意事项www.zgxue.com防采集请勿采集本网。

基于Vue3.0发布在GitHub上的第一版源码(2019.10.05)整理

响应式的 —— 模型只是普通对象,修改它则更新视图。这让状态管理非常简单且直观,不过理解它的原理也很重要,可以避免一些常见问题。多去学习vue里面的功能插件实现效果

预备知识

    ES6 Proxy,整个响应式系统的基础。 新的composition-API的基本使用,目前还没有中文文档,可以先通过这个仓库(composition-api-rfc)了解,里面也有对应的在线文档。

回忆 在上一篇Vue响应式原理-理解Observer、Dep、Watcher简单讲解了Observer、Dep、Watcher三者的关系。 在Observer的伪代码中我们模拟了如下代码: class Observ

先把Vue3.0跑起来

可以看到是使用Object.defineProperty实现对数据改变的监听。Vue主要使用了观察者模 去初始化用户传入的data,最后一步操作就是为data添加响应式。 实现 在Vue内部存在三

先把vue-next仓库的代码clone下来,安装依赖然后构建一下,vue的package下的dist目录下找到构建的脚本,引入脚本即可。

而组件的通信更是vue数据驱动的灵魂,下面这篇文章将给大家介绍关于Vue2组件间通信 。需要注意 $children 并不保证顺序,也不是响应式的。 Bus中央通信 目前中央通信是解

下面一个简单计数器的DEMO:

, $interval ) 执行 $digest() 或 $apply() 3.数据劫持(Vue.js) 思路: vue.js 则是采用数据劫持 },最后渲染到页面中。接着我们需要和双向绑定联系起来,实现{{text}}响应式的数据绑定

您可能感兴趣的文章:vue.js实现数据动态响应 Vue.set的简单应用vue.js实现输入框输入值内容实时响应变化示例Vue.js每天必学之内部响应式原理探究

<!DOCTYPE html><html lang="en"><body> <div id='app'></div></body><script src="./dist/vue.global.js"></script><script>const { createApp, reactive, computed } = Vue;const RootComponent = { template: ` <button @click="increment"> Count is: {{ state.count }} </button> `, setup() { const state = reactive({ count: 0, }) function increment() { state.count++ } return { state, increment } }}createApp().mount(RootComponent, '#app')</script></html>

响应系统 —— 模型只是普通对象,修改它则更新视图。这让状态管理非常简单且直观,不过理解它的原理也很重要,可以避免一些常见问题。下面我们开始深挖 Vue.js 响应系统的

template和之前一样,同样Vue3也支持手写render的写法,template和render同时存在的情况,优先render。

// 定义响应式的 _route 对象 Vue.util.defineReactive(this, '_route', this._router.history.current) } } }) // 注册组件 Vue.component('router-view', View) Vue.com

setup选项是新增的主要变动,顾名思义,setup函数会在组件挂载前(beforeCreate和created生命周期之间)运行一次,类似组件初始化的作用,setup需要返回一个对象或者函数。返回对象会被赋值给组件实例的renderContext,在组件的模板作用域可以被访问到,类似data的返回值。返回函数会被当做是组件的render。具体可以细看文档。

Hello Vue 数据和DOM已经绑定在一起, 验证是否是响应式的, 修改控制台里面app.msg 详解VueJs与ReactJS和AngularJS的异同点Angular和Vue双向数据绑定的实现原理(重点

reactive的作用是将对象包装成响应式对象,通过Proxy代理后的对象。

这里包含了Vue响应式原理的知识点。 // 因为 JavaScript 的限制,Vue.js 不能检测到下面数组变化: // 1.直接用索引设置元素,如 vm.items[0] = {}; // 2.修改数据的长度,如 vm.items.l

上面的计数器的例子,在组件的setup函数中,创建了一个响应式对象state包含一个count属性。然后创建了一个increment递增的函数,最后将state和increment返回给作用域,这样template里的button按钮就能访问到increment函数绑定到点击的回调,count也能显示在按钮上。我们点击按钮,按钮上的数值就能跟着递增。

 深入响应式原理 在组件绑定数据时,如何绑定才能够有效,并且可动态修改、添加属性?看看下面的原理介绍。 如何追踪变化:把一个不同对象传给vue实例作为data的

下面切入正题,我们就来探究下按钮上count值跟着响应式更新的原理

原理。在实际业务中,陆金所100多个的react基础业务组件,react-to-vue可以转化90%以 react和vue实现相同的面试题组件React组件refs的使用详解详解如何在项目中使用jest测

数据结构

首先列一下主要的一些数据结构,先列在这里,后面提到可以翻回来看看。

并且它是非响应式的。它仅仅作为一个直接访问子组件的应急方案——应当避免在模版或计算属性中使用 $refs 。 响应原理 vue中的数据双向绑定中,只有改变vue实例上的属性

ReactiveEffect 一个Function对象,用于执行组件的挂载和更新。

您可能感兴趣的文章:vue.js实现数据动态响应 Vue.set的简单应用vue.js实现输入框输入值内容实时响应变化示例Vue.js每天必学之内部响应式原理探究

interface ReactiveEffect { (): any isEffect: true active: boolean raw: Function // 具体执行的函数 deps: Array<Dep> computed?: boolean scheduler?: (run: Function) => void onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void onStop?: () => void}

targetMap 类似 {target -> key -> dep}的一个Map结构,用于缓存所有响应式对象和依赖收集。

您可能感兴趣的文章:vue.js实现数据动态响应 Vue.set的简单应用vue.js实现输入框输入值内容实时响应变化示例Vue.js每天必学之内部响应式原理探究

export type Dep = Set<ReactiveEffect>export type KeyToDepMap = Map<string | symbol, Dep>export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()

Proxy代理拦截

reactive函数执行,会将传入的target对象通过Proxy包装,拦截它的get,set等,并将代理的target缓存到targetMap,targetMap.set(target, new Map())。

代理的get的时候会调用一个track函数,而set会调用一个triger函数。分别对应依赖收集和触发更新。

您可能感兴趣的文章:vue.js实现数据动态响应 Vue.set的简单应用vue.js实现输入框输入值内容实时响应变化示例Vue.js每天必学之内部响应式原理探究

// Proxy get 简化function get(target: any, key: string | symbol, receiver: any) { // 通过key拿到原始值res const res = Reflect.get(target, key, receiver) // 过滤不需要代理的情况 // ... // 依赖收集 track(target, OperationTypes.GET, key) // 如果取到的值是个对象,将对象再代理包装一下 // Proxy只能代理对象第一层级 return isObject(res) ? reactive(res) : res}// Proxy set 简化function set( target: any, key: string | symbol, value: any, receiver: any): boolean { // 一些不需要代理设置的场景 // ... // 设置原始对象的值 const result = Reflect.set(target, key, value, receiver) // 避免重复trigger的逻辑 // ... // 触发通知更新 trigger(target, '更新的类型, 新增key或更新key', key) return result}

依赖收集和触发更新

组件在render阶段,视图会读取数据对象上的值进行渲染,此时便触发了Proxy的get,由此触发对应的track函数,记录下了对应的ReactiveEffect,也就是常说的依赖收集。

ReactiveEffect其实就可以看作是组件的更新(mount是特殊的update),数据的变更触发trigger,trigger遍历调用track收集的对应的数据的ReactiveEffect,也就是对应有关联的组件的更新。

trigger触发的组件的更新,在render阶段又触发了新一轮的track依赖收集,更新依赖。

您可能感兴趣的文章:vue.js实现数据动态响应 Vue.set的简单应用vue.js实现输入框输入值内容实时响应变化示例Vue.js每天必学之内部响应式原理探究

// 简化的 trackfunction track( target: any, type: OperationTypes, key?: string | symbol) { // 只有在依赖收集阶段才进行依赖收集 // 除了render,其他场景也可能会触发Proxy的get,但不需要进行依赖收集 // activeReactiveEffectStack栈顶包装了当前render的组件的mount和update的逻辑 const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1] // 如果effect为空,说明当前不在render阶段 if (effect) { // ... // =====>初始化对应{target -> key -> dep}的结构 let depsMap = targetMap.get(target) if (depsMap === void 0) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key as string | symbol) if (!dep) { depsMap.set(key as string | symbol, (dep = new Set())) } // <=====初始化对应{target -> key -> dep}的结构 // 依赖列表里如果没有,add if (!dep.has(effect)) { // 这里将effect作为依赖,缓存到依赖列表 dep.add(effect) effect.deps.push(dep) } }}// 简化的triggerfunction trigger( target: any, type: OperationTypes, key?: string | symbol, extraInfo?: any) { // 获取对应target在track过程中缓存的依赖 const depsMap = targetMap.get(target) const effects: Set<ReactiveEffect> = new Set() // 省略分类逻辑 depsMap.forEach(dep => { // 将effect分类过滤添加到effects }) const run = (effect: ReactiveEffect) => { // 有个异步调度的过程,nextTick scheduleRun(effect, target, type, key, extraInfo) } effects.forEach(run)}

大致流程:

总结

现在的代码只有新特性的实现,而且ES6+TS的组合可读性大大提高,编辑器支持也很好,所以相对会好读很多。这里只是简单的理了一下vue 3.0 reactive的整体流程,细节还有很多地方值得学习,继续加油。

一、响应式的底层实现 1、Vue与MVVM Vue是一个 MVVM框架,其各层的对应关系如下 View层:在Vue中是绑定dom对象的HTML ViewModel层:在Vue中是实例的vm对象 Model层:在Vue中是data、computed、methods等中的数据 在 Model 层的数据变化时,View层会在ViewModel的作用下,实现自动更新 2、Vue的响应式原理 Vue响应式底层实现方法是 Object.defineProperty() 方法,该方法中存在一个getter和setter的可选项,可以对属性值的获取和设置造成影响 Vue中编写了一个wather来处理数据 在使用getter方法时,总会通知wather实例对view层渲染页面 同样的,在使用setter方法时,总会在变更值的同时,通知wather实例对view层进行更新 3、响应式原理与兼容 由于 Object.defineProperty() 方法只部分支持IE9,所以Vue兼容IE版本最低为IE9,在IE9中,Vue的核心框架、vue-router、vuex是确保可以正常使用的 4、响应式原理示意图 1、在实例前声明 var vm = new Vue({ data: { name: "failte" } }) 在实例前声明的属性会在实例时添加 getter()、setter() 方法,因此此时的name是响应式的,每当name变化时,会自动更新视图 2、在实例后添加 vm.name = "failte" 由于data中没有该属性,因此实例后,此时的name是非响应式的,name变化时,不会更新视图 若需要转换为响应式数据,需要使用 Vue.set() 方法手动添加为响应式属性 Vue.set(vm.data, "name", "ajaccio") //Vue.$set是该方法的别名 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。 您可能感兴趣的文章:vue响应式系统之observe、watcher、dep的源码解析vue.js响应式原理解析与实现Vue如何实现响应式系统Vue实现双向绑定的原理以及响应式数据的方法内容来自www.zgxue.com请勿采集。


  • 本文相关:
  • 稍微学一下vue的数据响应式(vue2及vue3区别)
  • 茶余饭后聊聊vue3.0响应式数据那些事儿
  • vue3.0 响应式系统源码逐行分析讲解
  • 你了解vue3.0响应式数据怎么实现吗
  • vue3 的响应式和以前有什么区别,proxy 无敌?
  • vux-scroller实现移动端上拉加载功能过程解析
  • vue.js 打包时出现空白页和路径错误问题及解决方法
  • vue-cli随机生成port源码的方法
  • vue实现表格增删改查效果的实例代码
  • vue-devtools的安装和使用步骤详解
  • vue登录路由验证的实现
  • 一文快速详解前端框架 vue 最强大的功能
  • vue 父子组件的数据传递、修改和更新方法
  • vue使用中的内存泄漏【推荐】
  • 改变vue请求过来的数据中的某一项值的方法(详解)
  • 浅谈Vue的响应式原理
  • Vue如何实现响应式系统
  • 详解vue的数据劫持以及操作数组的坑
  • Vue 理解之白话 getter/setter详解
  • 在Vue中如何实现事件响应式进度条组件
  • 详解Vue 如何监听Array的变化
  • 详解Vue源码学习之双向绑定
  • 详解Vue2中组件间通信的解决全方案
  • 解析Vue2.0双向绑定实现原理
  • Vue.js实现数据响应的方法
  • vue.js做出来的网页是响应式的吗
  • vue实现路由跳转的原理是什么,是调用js底层什么方法
  • 深入浅析angular和vue还有jquery的区别
  • 基于Vue.js实现数字拼图游戏
  • 强大Vue.js组件浅析
  • React 组件转 Vue 组件的命令写法
  • Vue.js在使用中的一些注意知识点
  • 学人工智能10本必看书
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全yui.ext相关prototypejqueryangularjsjsonlib_jsjs面向对象extjsmootoolsseajsdojovue.jsbackbone.js其它首页javascriptjavascript类库vue.js稍微学一下vue的数据响应式(vue2及vue3区别)茶余饭后聊聊vue3.0响应式数据那些事儿vue3.0 响应式系统源码逐行分析讲解你了解vue3.0响应式数据怎么实现吗vue3 的响应式和以前有什么区别,proxy 无敌?vux-scroller实现移动端上拉加载功能过程解析vue.js 打包时出现空白页和路径错误问题及解决方法vue-cli随机生成port源码的方法vue实现表格增删改查效果的实例代码vue-devtools的安装和使用步骤详解vue登录路由验证的实现一文快速详解前端框架 vue 最强大的功能vue 父子组件的数据传递、修改和更新方法vue使用中的内存泄漏【推荐】改变vue请求过来的数据中的某一项值的方法(详解)vue引用js文件的多种方式(推荐)简单理解vue中props属性vue之父子组件间通信实例讲解(prvue props用法详解(小结)vue元素的隐藏和显示(v-show指令vue.js常用指令汇总(v-if、v-fovue 进阶教程之v-model详解使用vue实现图片上传的三种方式vue.js实战之利用vue-router实现vue.js中的图片引用路径的方式对vux点击事件的优化详解vue下载excel的实现代码后台用post方法基于vue自定义指令实现按钮级权限控制思路vue-swiper的使用教程vue项目中使用vue-i18n报错的解决方法vue table表格动态添加一列数据,新增的这解决cordova+vue 项目打包成apk应用遇到的webpack+vue.js实现组件化详解vue.js获取手机系统型号、版本、浏览器类
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved