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初体验

准备:

  1. 下载vue-next

  2. 安装依赖 npm install

核心部分package,里面的vue整合package内各个文件,__tests__里面的单元测试文件(以spect.ts结尾)用来测试功能是否符合预期的

  1. 启动开发环境 npm run dev,默认将代码打包到dist,会把ts编译成js

  2. 引用编译出来的文件即可进行测试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响应式原理相关推荐

  1. vue2的响应式原理学“废”了吗?继续观摩vue3响应式原理Proxy

    一文了解Vue3的响应式原理 一.

  2. vue3响应式原理之Ref

    theme: fancy 一. Ref 用法 这是 ref 最基本的用法,返回来的count是一个响应式的代理值 const count = ref(0) 二. 实现 1. ref 函数 我们调用的r ...

  3. vue2 Object.definProperty响应式原理(面试题)

    注意: 响应式原理和双向数据绑定原理是两回事,一般面试官会先问响应式原理再问双向数据绑定的原理 详细文章 1.响应式原理 核心是数据劫持和依赖收集,是通过数据劫持结合发布者-订阅者模式的方式来实现的. ...

  4. Vue3 响应式原理

    如何实现响应式 作为一个高阶的概述,我们需要做到以下几点: 当一个值被读取时进行追踪 当某个值改变时进行检测 重新运行代码来读取原始值 Vue如何知道哪些代码在执行 为了能够在数值变化时,随时运行我们 ...

  5. vue3响应式原理-reflect

    proxy负责对某个数据进行增删改查的监听,不过vue3底层不是直接对target进行如下的简单操作.而是利用es6的window.reflect 利用reflect取一个对象的属性 利用reflec ...

  6. 手写简单vue3响应式原理(reactive ref toRef toRefs)

    reactive ref toRef toRefs // 判断对象是否是对象 const isObject = val => val !== null && typeof val ...

  7. Vue3的响应式原理解析

    Vue3的响应式原理解析 Vue2响应式原理回顾 // 1.对象响应化:遍历每个key,定义getter.setter // 2.数组响应化:覆盖数组原型方法,额外增加通知逻辑 const origi ...

  8. Vue2.0 —— 由设计模式切入,实现响应式原理

    Vue2.0 -- 由设计模式切入,实现响应式原理 <工欲善其事,必先利其器> 既然点进来了,麻烦你看下去,希望你有不一样的收获. 大家好,我是vk,好久不见,今天我们一起来盘一盘关于 V ...

  9. Day 05- Vue3 Vue2响应式原理

    Vue2的响应式 核心:通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持: 数据劫持  --> 给对象扩展属性 -->  属性设置 实现原理: ...

最新文章

  1. python实现简单的情感分析
  2. 华人科学家,Yang-Kieffer算法之父杨恩辉斩获Eric E.Summer奖
  3. cad二次开发--添加对象到模型空间中
  4. LED 模板驱动程序的改造:总线设备驱动模型
  5. 如何直接强制客户端刷新.js文件
  6. Ubuntu GNOME 15.10升级16.4LTS
  7. 浅谈Spring IOC和DI及Spring工厂类
  8. 波特率与频率的换算公式?_高中物理公式总结表
  9. ReentrantLock1.8源码
  10. 运算放大器权威指南(第3版) (op amps for everyone)_OP高质量,ED多版本,有钱的动物狂想曲就是能为所欲为...
  11. tracert 原理
  12. 【Django下载文件-Kml文件下载】
  13. matlab设置图片的比例,Matlab 画图字体,字号的设定,图片大小和比例
  14. 数据库中几个基本概念 主码 外码
  15. QPushButton 实现保持按下效果(转载​​)
  16. 孙鑫java高清完整版(课件+视频)_孙鑫Java高清完整版(课件+视频) - 程序语言 - 小木虫 - 学术 科研 互动社区...
  17. matlab里面幂指数怎么写,幂和指数 - MATLAB Simulink - MathWorks 中国
  18. 公司KPI考核代码行数,程序员神操作:10行变500行!
  19. 云计算学习7——云计算OpenStack运维基础
  20. mysql vacuum_Vacuum 和 Vacuum Full 的处理过程

热门文章

  1. 工资管理系统/c语言期末大作业学习经历
  2. 将机械硬盘换成固态硬盘的装机过程
  3. android三星定位闪退,三星手机闪退问题7种修复方法
  4. TI快充芯片-BQ25890
  5. 通信协议学习-485通信(1)
  6. 轻松管好团队,就靠RACI模型|优思学院
  7. 华为修改优先级命令_华为LTE重选参数命令简介
  8. 《From Captions to Visual Concepts and Back》阅读笔记
  9. 苹果退款48小时审核结果_金苹果花园车辆审核结果20191102
  10. matlab节点连通率,利用MATLAB仿真节点个数和节点通信半径与网络连通率的关系