vue2和vue3响应式原理
vue2响应式原理:核心使用Object.defineProperty给属性定义get和set方法
注意:对象的多次递归,针对数组需要重写数组方法
函数劫持:把函数内部进行重写同时继续调用老的方法,在继承原数组方法时使用到
主要方法介绍:
updataView:模拟更新视图时触发的方法
observer:观察者
defineReactive:核心部分,get和set方法
//针对数组部分
let oldArrayPrototype = Array.prototype;
//继承旧数组的方法
let proto = Object.create(oldArrayPrototype)
//js省略;写法注意在括号前面加分号
;['push','shift','unshift'].forEach(method=>{//函数劫持proto[method] = function(){updateView()oldArrayPrototype[method].call(this,...arguments)}
})
function observer(target){//基本数据类型不需要处理,直接返回if(typeof target !== 'object' || typeof target == null){return target}//拦截数组,给数组重写方法if(Array.isArray(target)){// Object.setPrototypeOf(target,proto)target.__proto__ = proto}//循环重新定义data里面的属性和值for (let key in target){defineReactive(target,key,target[key])}
}
//get和set方法
function defineReactive(target,key,value){observer(value)//递归观察Object.defineProperty(target,key,{get(){//需要进行依赖收集return value},set(newValue){if(value !== newValue){observer(newValue)//更新后的数据也需要递归观察updataView();value = newValue}}})
}//模拟更新视图
function updataView(){console.log("视图更新")
}let data = {name:'fur',hobby:{play:"code"},age:[1,2,3]}
observer(data)
//三种写法,触发视图更新三次
data.hobby.play = {name:"js"}//视图更新
data.hobby.play.name = "ts"//视图更新
data.age = [1]//视图更新
vue2的缺陷很明显:
- 需要响应式的数据不能是新增属性
- 会多次嵌套递归,而且是一上来就递归,造成浪费,内存增大
vue3.0初体验
准备:
下载vue-next
安装依赖
npm install
核心部分package,里面的vue整合package内各个文件,
__tests__
里面的单元测试文件(以spect.ts
结尾)用来测试功能是否符合预期的
启动开发环境
npm run dev
,默认将代码打包到dist,会把ts编译成js引用编译出来的文件即可进行测试vue3.0
packages里面的文件说明
compiler- 用于编译
reactivity- 用于响应式
runtime- 代码运行时的方法
server-randerer 服务器端渲染
shared 所有包中的共享方法
-core是核心,-dom是基于core封装针对浏览器的
没有对比没有伤害,复习:vue2响应式原理
vue2响应式原理:核心使用Object.defineProperty给属性定义get和set
vue3 reactive的基本使用
let proxy = Vue.reactive({name:'fur'})
//effect:副作用,默认会调用一次,数据变化时触发Vue.effect(()=>{console.log(proxy.name)
})
proxy.name = 'furfur-jiang'
//fur
//furfur-jiang
vue3 reactive原理例子
先分开逐个重点讲解,再整合起来
划重点部分:
弱映射表
需要用到弱引用映射表记录一下, 防止对象已经代理过了或者多次代理同一个对象,即防止多次出现 reactive(object) 与 reactive(proxy);弱引用映射表(es6),使用get和set方法进行存取
弱映射表定义
let toProxy = new WeakMap()//原对象=>被代理对象
let toRaw = new WeakMap()//被代理对象=>原对象
下面代码位于createReactive方法中,因为在createReactive会new observed,目的就是为了避免多次new observed
if(toProxy.get(target)){return proxy
}
//如果对象再次被代理,返回原对象
//即判断该对象已经是代理过的,不再次代理
//不需要使用get即不需要获取,直接判断target有无代理过既可
if(toRaw.has(target)){return target
}
多层递归
关键在于isObject(res)?reactive(res):res
;若res为对象,需要递归,与vue2一上来就递归不同,会判断需要递归再递归,
get(target,key,receiver){console.log("获取")let res = Reflect.get(target,key,receiver)return isObject(res)?reactive(res):res;// return target[key] 效果等同Reflect.get方法
}
Reflect
observed的参数baseHandler对象,Reflect.get,Reflect.set,Reflect.deleteProperty
reflect 优点:不会报错,而且有返回值,会替代掉Object上的方法
let baseHandler = {//receiver表示代理后对象
get(target,key,receiver){console.log("获取")let res = Reflect.get(target,key,receiver)return isObject(res)?reactive(res):res;//实现多层代理,若res为对象,需要递归// return target[key] 效果等同Reflect.get方法
},
set(target,key,value,receiver){console.log("设置")let res = Reflect.set(target,key,value,receiver)return res//target[key] = value //效果等同Reflect.set方法,但是上面会有布尔类型返回值,明确设置成功或者失败
},
deleteProperty(target,key){console.log("删除")return Reflect.deleteProperty(target,key)}
}
let observed = new Proxy(target,baseHandler)
数组
存在问题,因为会涉及length修改,所以如果不屏蔽,会触发两次,即两次视图更新,不需要
即第一次将元素 push 进去,第二次将 length 改成 对应的值,但是第二次是无意义的更新,需要屏蔽
关键:判断是否为新增属性
function hasOwn(target,key){return target.hasOwnProperty(key)
}
在set中进行处理
set(target,key,value,receiver){let hadKey = hasOwn(target,key)let oldValue = target[key]let res = Reflect.set(target,key,value,receiver)//判断是否新增属性if(!hadKey){console.log('新增属性')}else if(oldValue !== value){//如果修改的不等于原来的,才会执行,即需要手动改length才会执行console.log('修改属性')}return res//target[key] = value //效果等同Reflect.set方法,但是上面会有布尔类型返回值,明确设置成功或者失败
}
调用:
let arr = [1,2,3]
let proxyArr = reactive(arr)
proxyArr.push(4) //新增属性
proxyArr.length = 5 //修改属性
依赖收集
activeEffectStacks 栈:先进后出,目的让属性和方法关联,形成响应
effect方法:响应式副作用,默认先执行一次,依赖数据变了再执行
createReactiveEffect方法:创建响应式的副作用
run方法:1.让fn执行, 2.将effect存入栈
//响应式副作用
function effect(fn){//createReactiveEffect:创建响应式的副作用let effect = createReactiveEffect(fn)effect()//默认先执行一次
}
function createReactiveEffect(fn){let effect = function(){return run(effect,fn) }return effect;
}
function run(effect,fn){//防止由于报错而不继续执行try{activeEffectStacks.push(effect);fn();}finally{activeEffectStacks.pop()}
}let obj = reactive({name:'fur'})
effect(()=>{//运行get时进行依赖收集console.log(obj.name)
})
get方法调用(跟踪)track()和set方法调用(触发)trigger()
function track(target,key){//跟踪let effect = activeEffectStacks[activeEffectStacks.length-1]if(effect){//有对应关系才关联,动态创建依赖关系let depsMap = targetsMap.get(target)if(!depsMap){targetsMap.set(target,depsMap = new Map)}let deps = depsMap.get(key)if(!deps){depsMap.set(key,deps = new Set())}if(!deps.has(effect)){deps.add(effect)}}
}function trigger(target,type,key){//触发let depsMap = targetsMap.get(target)if(depsMap){let deps = depsMap.get(key)if(deps){//将当前key对应的effect一次执行deps.forEach(effect=>{effect()})}}
}
完整代码
下面贴出完整代码myreactive.js:包含详细注释
//弱映射表
let toProxy = new WeakMap()//原对象=>被代理对象
let toRaw = new WeakMap()//被代理对象=>原对象function isObject(val){return typeof val === 'object' && val !== null;
}//判断当前对象上有没有这个属性
function hasOwn(target,key){return target.hasOwnProperty(key)
}function reactive(target){return createReactive(target)
}function createReactive(target){//如果已经被代理过了,返回代理结果if(toProxy.get(target)){return proxy}//如果对象再次被代理,返回原对象//即判断该对象已经是代理过的,不再次代理//不需要使用get即不需要获取,直接判断target有无代理过既可if(toRaw.has(target)){return target}if(!isObject(target)){return target}let baseHandler = {//receiver表示代理后对象// reflect 优点:不回报错,而且有返回值,会替代掉Object上的方法get(target,key,receiver){// console.log("获取")let res = Reflect.get(target,key,receiver)//收集依赖,把当前key和effect对应起来//如果目标上的key变了,重新让数组里的fn执行即可track(target,key)return isObject(res)?reactive(res):res;//实现多层代理,若res为对象,需要递归// return target[key] 效果等同Reflect.get方法}, set(target,key,value,receiver){let hadKey = hasOwn(target,key)let oldValue = target[key]let res = Reflect.set(target,key,value,receiver)//判断是否新增属性if(!hadKey){trigger(target,'add',key)console.log('新增属性')}else if(oldValue !== value){trigger(target,'set',key)//如果修改的不等于原来的,才会执行,即需要手动改length才会执行console.log('修改属性')}console.log("设置")return res//target[key] = value //效果等同Reflect.set方法,但是上面会有布尔类型返回值,明确设置成功或者失败},deleteProperty(target,key){console.log("删除")return Reflect.deleteProperty(target,key)}}let observed = new Proxy(target,baseHandler)toProxy.set(target,observed)toRaw.set(observed,target)return observed
}let object = {name:'fur',hobby:{play:'code'}}
let proxy = reactive(object)
//需要记录一下, 防止对象已经代理过了或者多次代理同一个对象
//即防止多次出现 reactive(object) 与 reactive(proxy)
//利用弱引用映射表(es6),使用get和set方法进行存取proxy.name//获取
proxy.name = 'furfur-jiang' //设置 获取
console.log(proxy.name)//furfur-jiang
delete proxy.name //删除//验证实现了多层代理
proxy.hobby.play = 'study'
console.log(proxy.hobby.play)//study//对数组操作
//存在问题,因为会涉及length修改,所以如果不屏蔽,会触发两次,第一次将4push进去,第二次将length改成4
let arr = [1,2,3]
let proxyArr = reactive(arr)
proxyArr.push(4) //新增属性
proxyArr.length = 5 //修改属性
console.log(proxyArr)//依赖收集,也称发布订阅
//栈:先进后出,目的让属性和方法关联,形成响应
let activeEffectStacks = []let targetsMap = new WeakMap(); // 集合和hash表
function track(target,key){let effect = activeEffectStacks[activeEffectStacks.length-1]if(effect){//有对应关系才关联,动态创建依赖关系let depsMap = targetsMap.get(target)if(!depsMap){targetsMap.set(target,depsMap = new Map)}let deps = depsMap.get(key)if(!deps){depsMap.set(key,deps = new Set())}if(!deps.has(effect)){deps.add(effect)}}
}function trigger(target,type,key){let depsMap = targetsMap.get(target)if(depsMap){let deps = depsMap.get(key)if(deps){//将当前key对应的effect一次执行deps.forEach(effect=>{effect()})}}}//响应式副作用
function effect(fn){//createReactiveEffect:创建响应式的副作用let effect = createReactiveEffect(fn)effect()//默认先执行一次
}
function createReactiveEffect(fn){let effect = function(){return run(effect,fn) //1.让fn执行, 2.将effect存入栈}return effect;
}
function run(effect,fn){//防止由于报错而不继续执行try{activeEffectStacks.push(effect);fn();}finally{activeEffectStacks.pop()}
}let obj = reactive({name:'fur'})
//effect:响应式副作用,默认先执行一次,依赖数据变了再执行
effect(()=>{//运行get,console.log(obj.name)
})
obj.name = 'furfur-jiang'
obj.name = 'furfur-jiang'//无意义修改,只运行一次
obj.name = 'furfurJiang'//有意义修改,会运行两次
vue2和vue3响应式原理相关推荐
- vue2的响应式原理学“废”了吗?继续观摩vue3响应式原理Proxy
一文了解Vue3的响应式原理 一.
- vue3响应式原理之Ref
theme: fancy 一. Ref 用法 这是 ref 最基本的用法,返回来的count是一个响应式的代理值 const count = ref(0) 二. 实现 1. ref 函数 我们调用的r ...
- vue2 Object.definProperty响应式原理(面试题)
注意: 响应式原理和双向数据绑定原理是两回事,一般面试官会先问响应式原理再问双向数据绑定的原理 详细文章 1.响应式原理 核心是数据劫持和依赖收集,是通过数据劫持结合发布者-订阅者模式的方式来实现的. ...
- Vue3 响应式原理
如何实现响应式 作为一个高阶的概述,我们需要做到以下几点: 当一个值被读取时进行追踪 当某个值改变时进行检测 重新运行代码来读取原始值 Vue如何知道哪些代码在执行 为了能够在数值变化时,随时运行我们 ...
- vue3响应式原理-reflect
proxy负责对某个数据进行增删改查的监听,不过vue3底层不是直接对target进行如下的简单操作.而是利用es6的window.reflect 利用reflect取一个对象的属性 利用reflec ...
- 手写简单vue3响应式原理(reactive ref toRef toRefs)
reactive ref toRef toRefs // 判断对象是否是对象 const isObject = val => val !== null && typeof val ...
- Vue3的响应式原理解析
Vue3的响应式原理解析 Vue2响应式原理回顾 // 1.对象响应化:遍历每个key,定义getter.setter // 2.数组响应化:覆盖数组原型方法,额外增加通知逻辑 const origi ...
- Vue2.0 —— 由设计模式切入,实现响应式原理
Vue2.0 -- 由设计模式切入,实现响应式原理 <工欲善其事,必先利其器> 既然点进来了,麻烦你看下去,希望你有不一样的收获. 大家好,我是vk,好久不见,今天我们一起来盘一盘关于 V ...
- Day 05- Vue3 Vue2响应式原理
Vue2的响应式 核心:通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持: 数据劫持 --> 给对象扩展属性 --> 属性设置 实现原理: ...
最新文章
- python实现简单的情感分析
- 华人科学家,Yang-Kieffer算法之父杨恩辉斩获Eric E.Summer奖
- cad二次开发--添加对象到模型空间中
- LED 模板驱动程序的改造:总线设备驱动模型
- 如何直接强制客户端刷新.js文件
- Ubuntu GNOME 15.10升级16.4LTS
- 浅谈Spring IOC和DI及Spring工厂类
- 波特率与频率的换算公式?_高中物理公式总结表
- ReentrantLock1.8源码
- 运算放大器权威指南(第3版) (op amps for everyone)_OP高质量,ED多版本,有钱的动物狂想曲就是能为所欲为...
- tracert 原理
- 【Django下载文件-Kml文件下载】
- matlab设置图片的比例,Matlab 画图字体,字号的设定,图片大小和比例
- 数据库中几个基本概念 主码 外码
- QPushButton 实现保持按下效果(转载​​)
- 孙鑫java高清完整版(课件+视频)_孙鑫Java高清完整版(课件+视频) - 程序语言 - 小木虫 - 学术 科研 互动社区...
- matlab里面幂指数怎么写,幂和指数
- MATLAB Simulink
- MathWorks 中国
- 公司KPI考核代码行数,程序员神操作:10行变500行!
- 云计算学习7——云计算OpenStack运维基础
- mysql vacuum_Vacuum 和 Vacuum Full 的处理过程