vue3 响应式对象如何实现方法的不同点

目录
  • vue3响应式对象实现方法的不同点
  • Vue2和Vue3响应式原理对比
    • 响应式原理实现逻辑
    • Vue2响应式原理简化
    • Vue2响应式原理弊端
    • Vue3响应式原理简化

vue3响应式对象实现方法的不同点

vue 响应式对象是利用 defindeProperty 实现,vue3 则是使用 Proxy 来实现响应式的。

二者,虽然都实现了 响应式的功能,但实现方式不一样,解决问题也有些不同。

vue2 中,如果想要实现,数组元素的更新,则是需要 用到检测更新的方法 set 之类的,但 vue3的响应式对象,就完美的解决了这个问题,不要其他的什么更新检测方法了。

Vue2和Vue3响应式原理对比

响应式原理实现逻辑

1.监听对象数组变化

2.设置拦截,读取的时候进行依赖收集,设置的时候进行派发更新操作

Vue2响应式原理简化

1.对象响应化:递归遍历每个key,使用Object.defineproperty方法定义getter、setter

2.数组响应化:采用函数拦截方式,覆盖数组原型方法,额外增加通知逻辑

//响应式处理
function observe(obj) {
	if (typeof obj !== 'object' || obj == null) {
		return
	}
	// 增加数组类型判断,若是数组则覆盖其原型
	if (Array.isArray(obj)) {
		Object.setPrototypeOf(obj, arrayProto)
	} else {
		//对象遍历处理
		const keys = Object.keys(obj)
		for (let i = 0; i < keys.length; i++) {
			const key = keys[i]defineReactive(obj, key, obj[key])
		}
	}
}
/*数组处理*/
const originalProto = Array.prototype
//拷贝一份数组原型方法
const arrayProto = Object.create(originalProto);
//这七个方法会让数组的长度或顺序发生变化,需要单独处理
['push', 'pop', 'shift','unshift','splice','reverse','sort'].forEach(
method => {
	//方法重写
	arrayProto[method] = function() {
		originalProto[method].apply(this, arguments)
		//处理项进行响应式化
		observe(inserted)
		//派发更新
		dep.notify()
	}
})
/*对象处理*/
function defineReactive(obj, key, val) {
	observe(val) // 解决嵌套对象问题
	Object.defineProperty(obj, key, {
		get() {
			//依赖收集
			dep.depend()
			return val
		},
		set(newVal) {
			if (newVal !== val) {
				observe(newVal) // 新值是对象的情况
				val = newVal
				//派发更新
				dep.notify()
			}
		}
	})
}

Vue2响应式原理弊端

响应化过程需要递归遍历消耗较大

新加或删除属性无法监听数组响应化需要额外实现

Map、Set、Class等无法响应式修改

语法有限制

Vue3响应式原理简化

vue3中使用ES6的Proxy特性来实现响应式

可以一次性友好的解决对象和数组

设计原理

  • effect:将回调函数保存起来备用,立即执行一次回调函数触发它里面一些响应数据的getter
  • track(依赖收集):getter中调用track,把前面存储的回调函数和当前target,key之间建立映射关系
  • trigger(派发更新):setter中调用trigger,把target,key对应的响应函数都执行一遍

const isObject = val => typeof val === 'object' && val !== null
//缓存已处理的对象,避免重复代理
//WeakMap 对象是一组键/值对的集合,其中键是弱引用的,必须是对象,而值可以是任意的。
const toProxy = new WeakMap() //形如obj:observed
const toRaw = new WeakMap() //形如observed:obj
function reactive(obj){
	if(!isObject(obj)){
		return obj
	}
	//查找缓存,避免重复代理
	if(toProxy.has(obj)){
		return toProxy.get(obj)
	}
	if(toRaw.has(obj)){
		return obj
	}
	/*
	Proxy两个参数
	target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
	handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
	*/
	const observed = new Proxy(obj,{
		//Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同
		get(target,key,receiver){
			const res = Reflect.get(target,key,receiver)
			//依赖收集
			track(target,key)
			//递归处理嵌套对象
			return isObject(res)?reactive(res):res
		},
		set(target,key,value,receiver){
			const res = Reflect.set(target,key,value,receiver)
			//触发响应函数
			trigger(target,key)
			return res
		},
		deleteProperty(target,key){
			const res = Reflect.deleteProperty(target,key)
			return res
		}
	})
	//缓存代理结果
	toProxy.set(obj,observed)
	toRaw.set(observed,obj)
	return observed
}
//保存当前活动响应函数作为getter和effect之间的桥梁
const effectStack = []
//设置响应函数,创建effect函数,执行fn并将其入栈
function effect(fn){
	const rxEffect = function(){
		//捕获可能的异常
		try{
			//入栈,用于后续依赖收集
			effectStack.push(rxEffect)
			//运行fn,触发依赖收集
			return fn()
		}finally{
			//执行结束,出栈
			effectStack.pop()
		}
	}
	//默认执行一次响应函数
	rxEffect()
	//返回响应函数
	return rxEffect
}
//映射关系表
//{target:{key:[fn1,fn2]}}
let targetMap = new WeakMap()
function track(target,key){
	//从栈中取出响应式函数
	const effect = effectStack[effectStack.length - 1]
	if(effect){
		let depsMap = targetMap.get(target)
		if(!depsMap){
			depsMap = new Map()
			targetMap.set(target,depsMap)
		}
		//获取key对应的响应函数集
		let deps = depsMap.get(key)
		if(!deps){
			deps = new Set()
			depsMap.set(key,deps)
		}
		//将响应函数加入到对应集合
		deps.add(effect)
	}
}
//触发target,key对应响应函数
function trigger(target,key){
	//获取依赖表
	const depsMap = targetMap.get(target)
	if(depsMap){
		//获取响应函数集合
		const deps = depsMap.get(key)
		console.log(deps)
		if(deps){
			//执行所有响应函数
			deps.forEach(effect=>{
				effect()
			})
		}
	}
}
//使用
//设置响应函数
const state = reactive({
	foo:"aaa"
})
effect(()=>{
	console.log(state.foo)//aaa   bbb
})
state.foo
state.foo = "bbb"

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 手写 Vue3 响应式系统(核心就一个数据结构)

    目录 前言 响应式 总结 前言 响应式是 Vue 的特色,如果你简历里写了 Vue 项目,那基本都会问响应式实现原理.而且不只是 Vue,状态管理库 Mobx 也是基于响应式实现的.那响应式是具体怎么实现的呢?与其空谈原理,不如让我们来手写一个简易版吧. 响应式 首先,什么是响应式呢? 响应式就是被观察的数据变化的时候做一系列联动处理.就像一个社会热点事件,当它有消息更新的时候,各方媒体都会跟进做相关报道.这里社会热点事件就是被观察的目标.那在前端框架里,这个被观察的目标是什么呢?很明显,是状态

  • setup+ref+reactive实现vue3响应式功能

    setup 是用来写组合式 api ,内部的数据和方法需要通过 return 之后,模板才能使用.在之前 vue2 中,data 返回的数据,可以直接进行双向绑定使用,如果我们把 setup 中数据类型直接双向绑定,发现变量并不能实时响应.接下来就看看setup如何实现data的响应式功能? 一.ref setup 内的自定义属性不具备响应式能力,所以引入了 ref ,ref 底层通过代理,把属性包装值包装成一个 proxy ,proxy 内部是一个对象,使得基础类型的数据具备响应式能力,使用之

  • 详解Vue3的响应式原理解析

    目录 Vue2响应式原理回顾 Vue3响应式原理剖析 嵌套对象响应式 避免重复代理 总结 Vue2响应式原理回顾 // 1.对象响应化:遍历每个key,定义getter.setter // 2.数组响应化:覆盖数组原型方法,额外增加通知逻辑 const originalProto = Array.prototype const arrayProto = Object.create(originalProto) ;['push', 'pop', 'shift', 'unshift', 'splic

  • 浅析vue3响应式数据与watch属性

    是Vue3的 composition API中2个最重要的响应式API ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式) 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象 ref内部: 通过给value属性添加getter/setter来实现对数据的劫持 reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据 ref的数据操作: 在js中要.value, 在模板中不需要(内部解析

  • vue3.x源码剖析之数据响应式的深入讲解

    目录 前言 什么是数据响应式 数据响应式的大体流程 vue2.x数据响应式和3.x响应式对比 大致流程图 实现依赖收集 代码仓库 结尾 前言 如果错过了秋枫和冬雪,那么春天的樱花一定会盛开吧.最近一直在准备自己的考试,考完试了,终于可以继续研究源码和写文章了,哈哈哈.学过vue的都知道,数据响应式在vue框架中极其重要,写代码也好,面试也罢,数据响应式都是核心的内容.在vue3的官网文档中,作者说如果想让数据更加响应式的话,可以把数据放在reactive里面,官方文档在讲述这里的时候一笔带过,笔

  • vue3中defineProps传值使用ref响应式失效详解

    子组件接收父组件的传参. 父组件: <template> <Son :data="data"/> </template> <script setup> import { ref } from "vue"; let data = ref('hello') setTimeout(() => { data.value = 'how are you doing' }, 2000) </script> 子组件:

  • 一文带你了解vue3.0响应式

    目录 使用案例 reactive API相关的流程 reactive createReactiveObject 创建响应式对象 mutableHandlers 处理函数 get函数 get函数的的调用时机 track 收集依赖 set函数 trigger 分发依赖 get和副作用渲染函数关联 副作用渲染函数的执行过滤 结尾 我们知道Vue 2.0是利用Ojbect.defineProperty对对象的已有属性值的读取和修改进行劫持,但是这个API不能监听对象属性的新增和删除,此外为了深度劫持对象

  • Vue3.0 响应式系统源码逐行分析讲解

    前言 关于响应式原理想必大家都很清楚了,下面我将会根据响应式API来具体讲解Vue3.0中的实现原理, 另外我只会针对get,set进行深入分析,本文包含以下API实现,推荐大家顺序阅读 effect reactive readonly computed ref 对了,大家一定要先知道怎么用哦~ 引子 先来段代码,大家可以直接复制哦,注意引用的文件 <!DOCTYPE html> <html lang="en"> <head> <meta ch

  • vue3.0响应式函数原理详细

    目录 1.reactive 2.ref 3.toRefs 4.computed 前言: Vue3重写了响应式系统,和Vue2相比底层采用Proxy对象实现,在初始化的时候不需要遍历所有的属性再把属性通过defineProperty转换成get和set.另外如果有多层属性嵌套的话只有访问某个属性的时候才会递归处理下一级的属性所以Vue3中响应式系统的性能要比Vue2好. 接下来我们自己实现Vue3响应式系统的核心函数(reactive/ref/toRefs/computed/effect/trac

  • vue3 响应式对象如何实现方法的不同点

    目录 vue3响应式对象实现方法的不同点 Vue2和Vue3响应式原理对比 响应式原理实现逻辑 Vue2响应式原理简化 Vue2响应式原理弊端 Vue3响应式原理简化 vue3响应式对象实现方法的不同点 vue 响应式对象是利用 defindeProperty 实现,vue3 则是使用 Proxy 来实现响应式的. 二者,虽然都实现了 响应式的功能,但实现方式不一样,解决问题也有些不同. vue2 中,如果想要实现,数组元素的更新,则是需要 用到检测更新的方法 set 之类的,但 vue3的响应

  • Vue3响应式对象Reactive和Ref的用法解读

    目录 一.内容简介 二.Reactive 1. 关键源码 2. 源码流程分析 三.代理拦截操作 1. 数组操作 2.Get操作 3. Set操作 4. 其余行为拦截操作 四.Ref对象 1. 思考一个问题 2. 简要说明 3. 关键源码 四. 源码解析 五.总结 一.内容简介 本篇文章着重结合源码版本V3.2.20介绍Reactive和Ref.前置技能需要了解Proxy对象的工作机制,以下贴出的源码均在关键位置备注了详细注释. 备注:本篇幅只讲到收集依赖和触发依赖更新的时机,并未讲到如何收集依赖

  • vue3常用响应式对象的api,你全用过了吗

    目录 Ⅰ. ref.reactive ( 递归监听 ) Ⅱ. isRef.isReactive ( 判断 ) Ⅲ. toRef 和 toRefs ( 解构 ) Ⅳ. toRaw . markRaw ( 解除代理) Ⅴ. unref ( 拷贝 ) Ⅵ. shallowRef .shallowReactive( 非递归监听 ) Ⅶ. triggerRef (强制更新) 总结 Ⅰ. ref.reactive ( 递归监听 ) import {ref,reactive} from 'vue'; setu

  • Vue3响应式对象是如何实现的(2)

    目录 前言 分支切换的优化 副作用函数嵌套产生的BUG 自增/自减操作产生的BUG 前言 在Vue3响应式对象是如何实现的(1)中,我们已经从功能上实现了一个响应式对象.如果仅仅满足于功能实现,我们就可以止步于此了.但在上篇中,我们仅考虑了最简单的情况,想要完成一个完整可用的响应式,需要我们继续对细节深入思考.在特定场景下,是否存在BUG?是否还能继续优化? 分支切换的优化 在上篇中,收集副作用函数是利用get自动收集.那么被get自动收集的副作用函数,是否有可能会产生多余的触发呢?或者说,我们

  • vue3响应式Object代理对象的读取示例详解

    目录 正文 读取属性 xx in obj for ... in 正文 从这一章开始,作者将更新深入的讲解响应式,尤其是vue3响应式的具体的实现.其实在前面一章,如果你仔细阅读,你是可以实现一个简单的响应式函数的,类似于@vue/reactive,当然那只是个demo,是个玩具,我能不能在生产环境上去使用的,它差了太多功能和边界条件. 现在,我们才是真正的深入@vue/reactive. 在vue中,obj.a是一个读取操作,但是仔细想来,读取这个操作很宽泛. obj.a // 访问一个属性 '

  • Vue3 响应式侦听与计算的实现

    响应式侦听和计算 有时我们需要依赖于其他状态的状态--在 Vue 中,这是用组件 计算属性 处理的,以直接创建计算值,我们可以使用 computed 方法:它接受 getter 函数并为 getter 返回的值返回一个不可变的响应式 ref 对象. 我们先来看看一个简单的例子,关于计算值的方式,同样我们在 src/TemplateM.vue 写下如下代码: <template> <div class="template-m-wrap"> count --->

  • 一文详解Vue3响应式原理

    目录 回顾 vue2.x 的响应式 vue3的响应式 Reflect 回顾 vue2.x 的响应式 实现原理: 对象类型:通过object.defineProperty()对属性的读取.修改进行拦截(数据劫持) 数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹) Object.defineProperty(data,'count ",{ get(){}, set(){} }) 存在问题: 新增属性.删除属性,界面不会更新 直接通过下标修改数组,界面不会自动更新 但是

  • Vue3 响应式系统实现 computed

    目录 前言 实现 computed 总结 前言 上篇文章我们实现了基本的响应式系统,这篇文章继续实现 computed. 首先,我们简单回顾一下: 响应式系统的核心就是一个 WeakMap --- Map --- Set 的数据结构. WeakMap 的 key 是原对象,value 是响应式的 Map.这样当对象销毁的时候,对应的 Map 也会销毁. Map 的 key 就是对象的每个属性,value 是依赖这个对象属性的 effect 函数的集合 Set.然后用 Proxy 代理对象的 ge

  • 详解vue3 响应式的实现原理

    目录 核心设计思想 Vue.js 2.x 响应式 Vue.js 3.x 响应式 依赖收集:get 函数 派发通知:set 函数 总结 源码参考 核心设计思想 除了组件化,Vue.js 另一个核心设计思想就是响应式.它的本质是当数据变化后会自动执行某个函数,映射到组件的实现就是,当数据变化后,会自动触发组件的重新渲染.响应式是 Vue.js 组件化更新渲染的一个核心机制. Vue.js 2.x 响应式 我们先来回顾一下 Vue.js 2.x 响应式实现的部分: 它在内部通过 Object.defi

随机推荐