- 关于Vue 3.0
- 前言:
- Vue3.0中的六大亮点
- Vue3.0是基于什么优化,如何做到更轻量,更快的?
- 示例
- **
- 标记查询列表
- **
- 然后我在options中打开事件侦听缓存(cachehandlers)
- **
- 使用vue3.0提供的Vite快速创建项目
- **
- vue3.0中的 reactive 用法
- **
- vue3.0中的 Composition API本质
- 生命周期中的 setup
- **
- 什么是 reactive
- 什么是 ref
- **
- ref 和 reactive 之间的不同
- **
- 递归监听
- **
- 非递归监听
- **
- 数据监听补充
- 数据监听方式选择
- **
- toRaw
- **
- markRaw
- **
- toRef
- **
- toRefs
- **
- 在 Vue3.0 中如何通过 ref 获取元素 ?
- **
- readonly
- **
- Vue3.0响应式数据本质
- **
- 实现shallowReactive和shallowRef
- **
- 实现 reactive 和 ref
- **
- 实现 shallowReadonly 和 readonly
关于Vue 3.0
配合李江南的Vue3.0学习效果较好
视频地址:``https://www.bilibili.com/video/BV14k4y117LL?share_source=copy_web
前言:
- 正式版:
Vue团队于2020.09.18日发布3.0正式版 - 前置条件:
Vue虽然保留了大量的2.0版本api,但由于是使用TypeScript重构,所以想要学习3.0起码要掌握TS的基础使用Vue3.0中的六大亮点
| 序号 | 特性 | 解析 | | :—-: | :—-: | :—-: | | 1 |Performance| 性能上比Vue2.0快1.3~2倍 | | 2 |Tree shaking support| 按需编译,体积更加轻量化 | | 3 |Composition API| 组合API,可参考React hooks理解 | | 4 |Better TypeScript support| 对 Ts 提供了更好的支持 | | 5 |Custom Renderer API| 暴露了自定义渲染API | | 6 |Fragment,Teleport(Protal),Suspense| 更先进的组件 |
Vue3.0是基于什么优化,如何做到更轻量,更快的?
- 一 、diff 算法优化
- Vue 2中的虚拟Dom是全量比较
- Vue 3新增静态标记(PatchFlag)
- 在与数据变化后,与上次虚拟DOM节点比较时,只比较带有PatchFlag标记的节点
- 并且可以从flag信息中得知具体需要比较的内容。
静态标记就是非全量比较,只会比较那些被标记的变量,比较的数量大大减少因此提升性能 这让我想到了JS垃圾回收机制里的标记清除,ORZ 感觉熟悉,但回收机是全标记只是清除具有离开环境的标记变量而已) 内存垃圾回收机制在我去年的博文中👉点击
比如下面这个示例
<div><a>土豆哇~ </a><p>静态文本</p><p>{{msg}}</p></div>
export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_createVNode("a", null, " 土豆哇~ "),_createVNode("p", null, "静态文本"),_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* text文本在这里标记为1 */)]))}
由以上可得知:
- 在vue2.0中对于数据变化后重新渲染的DOM树,会与上次渲染的DOM树逐个比较节点
- 在vue3.0的diff中,创建虚拟DOM时,会根据该DOM是否会变化而添加静态标记,数据更新需要生成新的虚拟DOM时,只会与上次渲染的且被标记的节点比较。
- 不同的动态变化类型,为了便于区分,标记的数值也不同
- 因此在vue3.0中比较次数更少,效率更高,速度更快。
示例
export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_createVNode("a", { id: _ctx.Poo }, " 土豆哇~ ", 8 /* PROPS */, ["id"]),_createVNode("p", { class: _ctx.style }, " 静态文本", 2 /* CLASS */),_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)]))}
**
标记查询列表
**TEXT = 1,// --取值是1---表示具有动态textContent的元素CLASS = 1 << 1, // --取值是2---表示有动态Class的元素STYLE = 1 << 2, // --取值是4---表示动态样式(静态如style="color: pink",也会提升至动态)PROPS = 1 << 3, // --取值是8--- 表示具有非类/样式动态道具的元素。FULL_PROPS = 1 << 4, // --取值是16---表示带有动态键的道具的元素,与上面三种相斥HYDRATE_EVENTS = 1 << 5, // --取值是32---表示带有事件监听器的元素STABLE_FRAGMENT = 1 << 6, // --取值是64---表示其子顺序不变,不会改变自顺序的片段。KEYED_FRAGMENT = 1 << 7, // --取值是128---表示带有键控或部分键控子元素的片段。UNKEYED_FRAGMENT = 1 << 8, // --取值是256---子节点无key绑定的片段(fragment)NEED_PATCH = 1 << 9, // --取值是512---表示只需要非属性补丁的元素,例如ref或hooksDYNAMIC_SLOTS = 1 << 10, // --取值是1024---表示具有动态插槽的元素
二 、hoistStatic 静态提升
- vue2.0中,在更新时,元素即使没有变化,也会重新创建进行渲染
- vue3.0中,不参与更新的元素;会静态提升,只创建一次下次渲染直接复用。
- 因此在vue3.0中复用更多,创建次数更少,速度更快。见下方示例:
<div><a>土豆哇~ </a><p>静态文本</p><p>{{msg}}</p><a href='https://vue-next-template-explorer.netlify.app/'>vue3.0编译地址</a></div>
/*** 在下方编译中(在options中勾选hoistStatic)进行静态提升,* 可以清晰看到不更新元素未参与重新创建*/const _hoisted_1 = /*#__PURE__*/_createVNode("a", null, "土豆哇~ ", -1 /* HOISTED */)export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_hoisted_1,_createVNode("p", { style: _ctx.myStyle }, "静态文本", 4 /* STYLE */),_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */),_createVNode("a", {style: _ctx.myStyle,href: "https://vue-next-template-explorer.netlify.app/"}, "vue3.0编译地址", 4 /* STYLE */)]))}}
三、cachehandlers 事件侦听缓存
- onClick默认视为动态绑定,因此会追踪它的变化
- 事件绑定的函数为同一个,因此不追踪它的变化,直接缓存后进行复用
- 同样的,我在编译中进行演示
<div><button @click='Pooo'>按钮</button></div>
/*** 开启事件侦听缓存前:* 下方为常规编译后,可以看到静态标记为8* 既然有静态标记,那么它就会进行比较*/export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_createVNode("button", { onClick: _ctx.Pooo }, "按钮", 8 /* PROPS */, ["onClick"])]))}
**
然后我在options中打开事件侦听缓存(cachehandlers)
**/*** 可以发现打开侦听缓存后,没有静态标记* 在diff算法中,没有静态标记的是不会进行比较和进行追踪的*/export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_createVNode("button", {onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.Pooo(...args)))}, "按钮")]))}复制代码
**
使用vue3.0提供的Vite快速创建项目
Vite是Vue作者开发的一款意图取代webpack的工具
- 原理是利用ES6的import发送请求加载文件的特性,进而拦截,然后做预编译,省去webpack冗长的打包
使用步骤
在2.0中对于业务实现
- 需要先在data中变更补充数据,然后在
methods或watch中补充业务逻辑 - 这样数据和逻辑是分模块的,查找不便,不利于业务的管理和维护
- 为解决这样的问题,Vue3.0中加入了
reactive - Vue3.0提供了setup 组合API的入口函数,可以把数据和业务逻辑组合在一起
import { reactive } from "vue"; //在Vue3.0使用中需要引入reactiveexport default {name: "App",//Vue3.0提供了setup 组合API的入口函数setup() {/*** ref一般用来监听简单类型变化(也可以用来监听复杂类型变化,先不讨论)* 通常使用reactive用来监听复杂类型变化(比如数组、函数之类)* 以下为一种常规的写法*/let stus = reactive({ stusList: [****its data****], });function removeVeget(index) {stus.stusList.splice(index, 1);}return { stus, removeVeget };// 必须暴露出去,组件中才可以使用},methods: {},};
- 另一种更加优雅的写法,也是非常非常推荐的写法是
```javascript
import { reactive } from “vue”;
export default {
name: “App”,
setup() {
let {stus, removeVeget }=removeItem();// 三、直接声明、获取
return { stus, removeVeget };//四、暴露给外界组件使用
},
methods: {},
};
/**
- 保证数据和业务不分散利于更新维护
- 也避免了setup中的大量数据函数填充
- 也不需要使用this指向Vue实例 / function removeItem() { let stus = reactive({ stusList: [*its data], }); function removeVeget(index) { stus.stusList.splice(index, 1); } return {stus,removeVeget} // 二、暴露给组合API使用 }
- 功能分离:- 乍一看上方把函数整合到下方,然后在`setup`中引用是很简洁- 若需要的业务功能多了呢,比如增加个`updateItem`,`addItem`- 虽然数据和逻辑代码还是在一块,但是各种功能聚集在一块还是显得文件臃肿- 那么还要继续优化,分离各个功能1. 新建一个单独的JS文件,如remove.js2. 在APP文件中引入这个JS文件3. 这样就可以在单独的JS文件中对某个功能进行维护了```javascriptimport { reactive } from "vue"; //引入依赖function removeItem() {//定义函数,实现功能let stus = reactive({stusList: [{ id: 1, Name: "potato", price: "2.5" },{ id: 2, Name: "tomato", price: "3.5" },{ id: 3, Name: "cucumber", price: "4.5" },],});function removeVeget(index) {stus.stusList.splice(index, 1);}return {stus,removeVeget}}export {removeItem};//暴露给外界使用
/*那么主文件就变成了如下形式(单独JS文件中已经引入reactive)*/import { removeItem } from "./remove"; //导入删除的业务逻辑模块export default {name: "App",setup() {let { stus, removeVeget } = removeItem();return { stus, removeVeget };},methods: {},};复制代码
**
vue3.0中的 Composition API本质
Option API:即在APP中为实现业务逻辑进行的配置
- 在2.0中比如你要实现一个点击按钮,弹出提示语功能,你需要
- 利用
Opaction API - 在
data中配置数据 - 在
methods中配置相应函数
- 利用
- 在3.0中通过上方
reactive的知识点我们知道,实现这个功能,你需要- 利用
Composition API - 在
setup中定义数据,编写函数 - 通过
return{ 数据,方法}暴露出去
- 利用
- 其实
Composition(也叫注入API)本质是在运行时- 把暴露出来的数据注入到
opaction中的data - 把暴露出来的函数注入到
opaction中的methods注: 具体它咋区分数据还是函数用以注入到相应配置中的,我也不知道(
标志位or传参顺序?)
- 把暴露出来的数据注入到
- 在2.0中比如你要实现一个点击按钮,弹出提示语功能,你需要
小结:
Opaction API中对配置项都进行了规定,比如:- 在data中配置数据,methods中编写方法,watch中进行监听。
- 保姆式的分配较为清晰,但也对代码层层分割,维护要扒拉半天跳来跳
Composition中更加自由,比如:
setup的执行时机在
beforeCreate和created之间,还是beforeCreate之前?官方文档原话:因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。
- 官网地址:v3.cn.vuejs.org/guide/compo…
- 依据官网描述及下方2、3点推断setup肯定是在created之前
- 还有一种是说法是在beforeCreate之前,但我不是很能理解
- 根据原文
beforeCreate中的任何代码可以直接在setup中编写 - 我更倾向于二者是 ‘平级’ 的关系,也欢迎各位帮忙解答
- 在Vue生命周期中我们知道:
beforeCreate时,刚初始化一个空Vue实例对象,data和methods中数据 未初始化created执行时,data和methods已经初始化完毕Composition需要把setup中的数据对应注入到data和methods中去
- 4.很显然setup必须要在
created之前执行 - 也因此,若你在Vue3.0中进行混合开发,不可以在
setup中使用data中的数据和methods中的方法 - 在3.0中
setup里的this也被修改为了undefiend - 在3.0中
setup里也不可以使用异步 - (下方贴的图我也加入了旁释,可帮助你回想下生命周期)
**
什么是 reactive
reactive是VUE3.0中提供的实现响应式数据的方法- 在Vue2.0中使用的是
defineProperty来实现的(我自己也手动实现过👉 点击) - 而
VUE3.0中使用的是ES6里的proxy实现的 reactive中需要注意的点:- 传递给它的类型必须是对象(JSON或者arr数组)
- 并且它会自动把传递进来条件再赋值给
Proxy对象 - 若传递的为上述以外的对象
- 在方法中直接修改它,界面上它也不会自动更新
- 若想更新只能通过重新赋值的方式
/*示例如下*/setup() {let testJson=reactive({tip:'its a Json!'})let testArray=reactive(['first','second','third'])let testString=reactive('Just a string')function showProxyPar(){testJson.tip='changed';testArray[2]='selected';testString='hengxipeng';console.log(testJson);// Proxy {tip: "changed"}console.log(testArray);// Proxy {0: "first", 1: "second", 2: "selected"}console.log(testString);// hengxipeng}return { testJson,testArray,testString,showProxyPar };},
效果正如下图所示,符合传递条件的参数会再赋值给Proxy,并且修改它也会直接影响视图
什么是 ref
- 它也是实现响应式数据的方法
reactivce向来都是进行传递对象,实际开发中若只想更改某简单变量则会显得大材小用- 所以vue3提供了ref方法,来实现对简单值的监听
ref本质也是使用reactive,给ref的值,它底层会自动转化/*** 实质是 ref('its a string')==>reactive({value:'its a string'})* 也因此更改时应该 testRef.value=XX 才能更改* (视图使用时不必加 value ,Vue会自动进行添加)*/setup() {let testRef = ref('its a string');function showProxyPar() {testRef.value='ref_string'console.log(testRef);}return { testRef, showProxyPar };},
如下图
**
ref 和 reactive 之间的不同
- 通过以上得知,使用
ref其实相当于使用reactive,只是省略了手动创建对象的步骤 ref中底层会添加一个value的键,并且在视图中可省略调用value- 经过我自己测试
- 使用
reactive,创建一个键值为value的Json对象,验证是否可省略value调用(不可以) - 得知,只有使用
ref传递参数时,视图才允许省略value调用 ```javascript /**
- 使用
- Vue在解析时,通过 __v_isRef 来判定当前参数是否由 ref 传递出来的
- 是的话,则会自动在调用当前参数时添加 value */ __v_isRef: true _rawValue: “its a string” _shallow: false _value: “its a string” value: “its a string” ```
- 经过我自己测试
其中Vue3.0中提供了两个方法,
isReactive和isRef用来判定数据来源import {isRef,isReactive } from "vue";setup() {let testReactive = reactive({value:'its a string'});let testRef = ref('its a string');function showProxyPar() {console.log('检测是否是Ref',isRef(testReactive));// falseconsole.log('检测是否是Ref',isRef(testRef));// true}return { testRef,testReactive, showProxyPar };}
**
递归监听
通常情况下
ref和reactive都会监听数据变化验证如下,点击按钮触发
recursion页面显示都会改变
//验证ref 只需添加value即可,如: parse.value.type='fruit';setup() {let parse = reactive({type: "vegetables",suchAS: {name: "tomato",info: {price: "0.4元/kg",size: {big: "50g",small: "20g",},},},});function recursion() {parse.type='fruit';parse.suchAS.name='cucumber';parse.suchAS.info.price='0.8元/kg';parse.suchAS.info.size.small='70g';parse.suchAS.info.size.big='90g';}return { parse,recursion };},
数据量较大时非常消耗性能
- 在之前< 什么是
reactive>中我们知道:reactive和ref通过递归取出参数中所有值,包装为proxy对象- 递归的优与劣我总结过,涉及压栈和弹出等,强烈建议回顾下👉点击
**
非递归监听
- 在之前< 什么是
上面知道了递归监听上的种种劣势,而
Vue3.0也提供了解决方案- 非递归监听,即:只能监听数据的第一层。方案如下:
- 引入
Vue3.0中提供的shallowReactive - 改用
shallowReactive({})传递参数 - 观察发现,控制台只有第一层转为了
proxy对象
- 引入

- 而对于
ref对应的shallowRef非递归监听则比较特殊- 首先试引入
Vue3.0中官方提供的shallowRef - 原理上与
reactive相同,只是它并不会监听JSON第一层数据 - 而是要直接修改
value的值,这样视图才会同步更新function recursion() {/** * shallowRef 对第一层修改不会监听,所以视图不变 */parse.value.type='fruit';parse.value.suchAS.name='cucumber';parse.value.suchAS.info.price='0.8元/kg';parse.value.suchAS.info.size.small='70g';parse.value.suchAS.info.size.big='90g';/** * 正确做法应该是整个修改 value */parse.value = {type: "fruit",suchAS: {name: "cucumber",info: {price: "0.8元/kg",size: {big: "70g",small: "90g",},},},};}
注意点:虽然他们只对第一层进行了监听,但若恰巧每次都更改了第一层数据,则也会引起下方数据和视图的同步更新,此时
shallowReactive或者shallowRef就和reactive、Ref效果一模一样!
- 首先试引入
- 非递归监听,即:只能监听数据的第一层。方案如下:
**
数据监听补充
通过以上这些知识点可知:
ref和reactive监听每一层数据,响应好但递归取值性能差。shallowReactive和shallowRef监听第一层(或value),性能好但更新值较麻烦shallowRef中,为了数据和视图一致,更新值要更新整个parse.value太繁琐- 场景:若我更新数据的第三层,不整个更新
value行不行?- 这就用到了
Vue3.0为ref准备的triggerRef(不用查啦 就一个) - 作用:根据传入的数据,主动去更新视图
- 老规矩,
import {shallowRef, triggerRef } from "vue" - 改完非首层的数据,而你使用的是
shallowRef还不想整个更新value - 使用
triggerRef大法,传入整个对象,就好啦 - (使用
reactive传入的数据,无法触发triggerRef) ```javascript function recursion() { /**
- 老规矩,
- 这就用到了
正常数据量时,通常使用
ref和reactive(递归监听)即可满足业务需要- 当数据量庞大且注重性能时,就需考虑
shallowReactive和shallowRef了(非递归监听)
shallowRef底层原理**
- 在看
ref时,我们知道它的本质其实是reactive({value:XX}) 那么
shallowRef其实是shallowReactive({value:XX})在之前的知识体系中我们知道
setup中定义参数对象,在函数中直接修改页面是不会同步更新。需要利用
Ref或者reactive进行包装,这样修改才生效let obj={ name:'花花',age:'3'}let test=reactive(obj);function myFun() {test.name='乐乐';}
obj和test是引用关系reactive会把传进来的参数包装为一个porxy对象并返回- 例子中 test 本质是一个
porxy对象,而这个对象也引用了obj- 那么请注意:
- 直接修改
obj或引用的test都会引起内存中数据变化 - 但是修改
obj因为没有proxy监听,所以视图不会更新
- 直接修改
- 那么请注意:
说了那么多,再绕回来说
toRaw- 作用:返回由
reactive或readonly等方法转换成响应式代理的普通对象 - 特点:
toRaw拿到的数据不会被监听变化,节省性能 - 场景:数据更改不需更新视图,为提高性能,通过
toRaw拿到数据修改 - 提示:因为是原始数据,风险较大,一般不建议使用、
- 注意:若想拿到的是
Ref创建的对象,记得加 valuelet obj={name:'花花',age:'3'}let testReactive=reactive(obj);let testRef=ref(obj);let rawReac=toRaw(testReactive);let rawRef=toRaw(testRef.value);console.log(rawReac===obj); //trueconsole.log(rawRef===obj); //true
**
markRaw
- 作用:返回由
在之前的知识体系中我们知道
- 作用:固定某数据,不追踪它值的变化,同时视图也不会更新
通过控制台查看,使用
markRaw的对象参数,被赋予v_skip监听跳过标识符let obj={name:'poo',age:'3'}console.log(obj);//{name: "poo", age: "3"}obj=markRaw(obj)//使其值的改变,不会被监听,视图不会发生变化let testReactive=reactive(obj);function myFun() {testReactive.name='地瓜';console.log(obj);//{name: "地瓜", age: "3", __v_skip: true}}
**
toRef
toRef和ref一样,同样也是创建响应式数据的- 先说结论:
ref将对象中某属性变为响应式,修改时原数据不受影响toRef会改变原数据- 并且
toRef创建的数据,改变时界面不会自动更新
应用场景:性能优化
- 想使创建的响应式数据与元数据关联起来
- 更新响应式数据后,不想更新UI
setup() {/** * toRef */let obj={ name:'poo' }let obj2={name:'boo'}//-注意:这里是让 toRef 知道是让 obj里的 name变成响应式let test_toRef=toRef(obj,'name');let test_ref=ref(obj2.name);console.log(test_toRef);function myFun() {test_toRef.value='土豆';test_ref.value='地瓜';console.log(obj,);// {name: "土豆"}console.log(obj2);// {name: "boo"}}return {obj,obj2, myFun };}
**
toRefs
toRef只能接受两个参数,当传递某对象多个属性值时会很麻烦结论:
toRefs是避免toRef对多个属性操作繁琐toRefs底层原理是使用toRef方法遍历对象属性值 ```javascript setup() { let obj={ name:’poo’, age:’3’ } let test_toRefs=toRefs(obj); /**
在 Vue2.0版本内,通常使用
this.$refs.XX获取元素- 在Vue3.0中,废除了类似
$的很多符号,如何获取指定元素 ? - 根据Vue生命周期图中可知,要操作DOM,最早也要在
mounted中 结论:
Vue3.0中提供的这个API,使得数据被保护,只读不可修改
- 默认所有层数据都只读,若只限制第一层只读,可使用
shallowReadonly isReadonly用来检测数据创建来源是否是readonly若进行修改,浏览器会提示操作失败,目标只读
setup() {let obj={name:'poo',age:'13'}let only=readonly(obj)function myFun() {only.name='HAHA';// failed: target is readonly}return {only, myFun };}
**
Vue3.0响应式数据本质
2.0中使用的
Object.defineProperty实现响应式数据3.0中使用的
Proxy来实现,如下let obj={name:'poo',age:'13'}let objProxy=new Proxy(obj,{//数据读会触发get(obj,key){console.log(obj);//{name: "poo", age: "13"}return obj[key]},//监听的数据被修改会触发set(obj,key,value){// 操作的对象,操作的属性,赋予的新值obj[key]=value //把外界赋予的新值更新到该对象console.log('进行UI之类的操作');//-补充,有时会多次操作,此时必须return true才不会影响下次操作return true;}})objProxy.name;
**
实现shallowReactive和shallowRef
它们二者也是通过参数传递,包装成
proxy对象进行监听- 在
Proxy的set监听中,同样只监听第一层 shallowRef只是在shallowReactive基础上默认添加value键名function shallowReactive(obj){return new Proxy(obj,{get(obj,key){return obj[key]},set(obj,key,value){obj[key]=valueconsole.log('更新');return true;}})}let obj={A:'A',B:{b1:'b1',b2:'b2',b3:{b3_1:'b3-1',b3_2:'b3-2'} } }let test=shallowReactive(obj)//-这里同样只会监听第一层test.A='apple';test.B.b2='banana';function shallowRef(obj){return shallowReactive(obj,{value:vl})}let state=shallowRef(obj);
**
实现 reactive 和 ref
它们与上方区别在于递归监听
- 上方因为直接传递参数对象,所以只监听第一层
- 为了递归监听,那么要把数据的每一层都给包装成
Proxy对象
function reactive(obj) {if (typeof obj === "object") {if (obj instanceof Array) {//当前参数为数组类型,则循环取出每一项obj.forEach((item, index) => {if (typeof item === "object") {//分析数组每一项,是对象则递归obj[index] = reactive(item);}});} else {// 当前参数是对象且不是数组,则取属性值并进行分析是否是多层对象for (let key in obj) {if (typeof obj[key] === "object") {obj[key] = reactive(item);}}}} else {console.log("当前传入为非对象参数");}//-正常情况下就进行 Proxy对象包装return new Proxy(obj, {get(obj, key) {return obj[key];},set(obj, key, value) {obj[key] = value;console.log("更新");return true;},});}
**
实现 shallowReadonly 和 readonly
- 二者区别只在于首层监听,只读拒绝修改和数据全层修改
- 下方实现的是
shallowReadonly readonly实现是在shallowReadonly基础上移除set中的return truefunction shallowReadonly(obj) {return new Proxy(obj, {get(obj, key) {return obj[key];},set(obj, key, value) {// obj[key] = value;console.error(`${key}为只读,不可修改-`);return true;//此行移除,则就是 readonly 全层数据只读},});}let parse = {type: "fruit",suchAS: {name: "cucumber",},};let fakeShowRe=shallowReadonly(parse);fakeShowRe.type='HAHA';// 此时修改不会生效fakeShowRe.suchAS.name='HAHA';// 非首层修改会生效
