Vue3.js“非原始值”响应式实现基本原理笔记(一)

如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~

作者:前端小王hs

阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主

此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来

书籍:《Vue.js设计与实现》 作者:霍春阳

本篇博文将在书第5.1节5.4节的基础上进一步总结所提到的基础概念,附加了测试的代码运行示例,方便正在学习Vue3想分析Vue3源码的朋友快速阅读

如有帮助,不胜荣幸

5.1 Proxy和Reflect

代理与被代理

5.1节开篇给到了一个信息:Proxy 只能代理对象,无法代理非对象值,例如字符串、布尔值等(原文)

关于什么是代理,可以举个简单的例子,有ab两个对象,如果我们想通过b去访问a里的属性,那么这个b就是代理

可以理解这个b为中介,而这个b,就是new Proxyproxy对象,如下代码所示:

const b = new Proxy(a, {
})

对象a就是被代理对象ab代理了),位于new Proxy第一个参数,而(new Proxy,下同)第二个参数是一个对象,里面包含了如get()set()之类的拦截方法,关于这一点,在之前的笔记中深入理解Vue3.js响应式系统基础逻辑也提到了

代理的作用

代理的作用在于可以拦截一些基本操作,如读取修改对象等

逻辑也非常简单,原来通过a.foo可以访问到a对象里的foo属性,现在代理了就变成通过b.foo去访问了,当访问时就会触发第二个参数里设置的如get()set()之类的拦截方法,那么经过这些方法的拦截,就可以进行一些额外的操作,例如前文笔记里提到的响应式

Proxy拦截函数

函数也是对象,那么同理可以把一个函数fn当作a变为被代理对象,那么同理,当调用fn时会触发第二个参数内的拦截方法

apply与call

书中举例了一个使用代理对象去调用fn的例子,代码如下:

function fn(name) {return '我是:' + name;
}// 使用 Proxy 拦截对 fn 的调用  
const p = new Proxy(fn, {apply(target, thisArg, argArray) {return target.call(thisArg, ...argArray);}
})p('123') // 我是123

这里的applyproxy支持的13个拦截方法之一,在阮一峰大佬的ES6中对apply也有详细的介绍:

apply(target, object, args) :拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)

这里的三个参数分别是:

  1. target:目标对象
  2. object:目标对象的上下文对象(this)
  3. args:目标对象的参数数组

回看上面的例子,apply接收的三个形参的内容分别是:fnthis(undefined)、包含123的参数数组

接着返回了target.call(thisArg, ...argArray);,也就是fn.call(undefined,'123'),或者说fn('123')

这里的一个问题是,我们知道此时this指向的是调用者(使用了call),也就是fn,那第一个参数thisArg其实也就没有发挥作用,或者说一直都是undefined

这里其实有个潜在条件就是,使用proxy.apply的基本场景就是去拦截函数

整个流程其实就是:

  1. p代理了fn
  2. 传入123,被apply拦截
  3. 在拦截的逻辑里执行fn('123')

Reflect的作用

Proxy的方法,在Reflect都能找到,也就是说ProxygetReflect里也有

Reflect.get(target, name, receiver)Reflect.set(target, name, value, receiver)的作用和Proxy内的getset相同

书中提到Reflect原因是,使用target即原始对象去完成对属性的读取,无法完成与副作用函数的绑定

我们来分析一下书中的例子,下面是代码:

const obj = {foo: 1,get bar() {return this.foo;}
};  const p = new Proxy(obj, {get(target, key) {track(target, key);return target[key];},set(target, key, newVal) {  target[key] = newVal;trigger(target, key);}  
});  effect(() => {console.log(p.bar);
});  p.foo++;

这段代码的问题是,执行p.foo++,不会触发副作用函数,这是为什么?

直接看当前的执行逻辑是怎么样的:

  1. 执行effect,那么会输出p.bar,那么会触发get拦截
  2. track中,targetobjkey字符串bar,也就是objbar会和当前effect建立联系(这一步不重要)
  3. 返回target[key],那么触发getter,此时这里的this是指obj,也就是最终返回的是obj.foo副作用函数
effect(()=>{ obj.foo })

也就是说,匿名函数输出p.bar,返回的是obj.foo,那就很好理解了,obj不是代理对象,在副作用函数里输出obj.foo不会触发Proxy.get拦截,自然就不会与副作用函数进行联系了

解决的办法就是使用Reflect.get(target, key, receiver)代替target[key]

Reflect.get(target, key, receiver)返回也是obj.foo,但它的第三个参数receiver可以指出是谁在调用

那么代码就更新如下:

const p = new Proxy(obj, {get(target, key,receiver) {track(target, key);return Reflect.get(target, key, receiver);},// ...
}); 

那么现在,就可以把obj.foo变为p.foo

5.2 JavaScript 对象及 Proxy 的工作原理

对象分为两种,常规对象异质对象

区分常规对象异质对象的区别在于其内部方法是使用ECMA的哪一种规范决定的,这里不去详细赘述。需要明白的是Proxy是一个异质对象

如何区分普通对象函数对象呢?文中提到:对象的实际语义是由对象的内部方法(internal method)指定的(原文)

最简单的一个区分方法是,函数对象call()方法

内部方法具有多态性

在书中提到了代理透明性质,也就是如果定义了一个代理对象p,但是内部没有指定get(),那么通过p去访问被代理对象的某个属性,会调用原始对象的内部方法[[Get]]。这一点在阮一峰ES6关于proxy.get一节也有记载

Proxy对象部署的所有内部方法

内部方法处理器函数使用场景
[[GetPrototypeOf]]getPrototypeOf获取对象原型
[[SetPrototypeOf]]setPrototypeOf设置对象原型
[[IsExtensible]]isExtensible判断对象是否可以新增属性
[[PreventExtensions]]preventExtensions阻止对象新增属性
[[GetOwnProperty]]getOwnProperty获取对象自有属性的属性描述符
[[DefineOwnProperty]]defineProperty定义对象新属性或修改现有属性,返回镀锡
[[HasProperty]]has判断对象是否有指定的属性
[[Get]]get获取对象的属性值
[[Set]]set设置对象的属性值
[[Delete]]deleteProperty删除对象的属性
[[OwnPropertyKeys]]ownKeys获取对象所有自有属性的键
[[Call]]apply调用函数
[[OwnPropertyKeys]]Construct创建一个新的实例

书上的表格无使用场景,这里加上便于理解

在书上的例子是举例了deleteProperty,代码如下:

const obj = { foo: 1 }
const p = new Proxy(obj, {deleteProperty(target, key) {return Reflect.deleteProperty(target, key)}
})console.log(p.foo) // 1
delete p.foo
console.log(p.foo) // undefined

那么需要注意的是deletePropertyProxy对象即p的内部方法,只有删除p的属性时才会被调用,其实就和p读取obj属性时才会调用get一个意思。在上述代码中,上下文环境要删除的是obj.foo,所以调用了Reflect.deleteProperty,回想一下,Proxy的方法和Reflect里的方法是一样的名字

问题总结

  1. JS中的代理是什么
  2. 结合Vue3响应式理解apply和call
  3. 了解ES6的Reflect在拦截函数中的作用
  4. 什么是常规对象和异质对象?
  5. 如何区分普通对象和函数对象
  6. Proxy对象的内部方法及其使用场景

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/40928.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sentinel限流算法总结

文章目录 一、线程隔离二、滑动窗口算法三、令牌桶算法四、漏桶算法 一、线程隔离 线程隔离有两种方式实现: 线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果信号量隔离:不创建线程池,而是…

Xilinx FPGA:vivado关于同步fifo的两个小实验

一、实验一:在同步fifo里写一个读一个(写入是8个位宽,读出是16个位宽) 程序: timescale 1ns / 1ps //要求写一个读一个 //读写时钟一致,写是8个位宽,读是16个位宽 module sync_fifo_test(inpu…

QQ音乐Android一面凉经

最近面试了不少公司, 近期告一段落, 整理一下各家的面试问题, 打算陆续发布出来, 供有缘人参考。今天给大家带来的是QQ音乐Android一面凉经。 面试岗位: QQ音乐Android开发工程师面试时长: 50min(提问40min 反问10min)代码考核: 无 面试问题(40min) 自我介绍 工作经历, 重点…

银行信用卡风险大数据分析与挖掘2024

银行信用卡风险大数据分析与挖掘 使用excel数据挖掘功能完成 一、信用卡客户信用等级影响因素分析与挖掘 基于客户信用记录表 1. 数据预处理 浏览数据 客户等级占比,其中优质客户占比较少,风险客户很多,分析影响客户信用等级的原因 年…

vue3+ts项目中.env配置环境变量与情景配置

一、环境变量配置 官网https://cn.vitejs.dev/guide/env-and-mode.html#intellisense 1. 新建.env开头的文件在根目录 为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码 .env 所有环境默认加载 .env.developm…

数字化精益生产系统--MRP 需求管理系统

MRP(Material Requirements Planning,物料需求计划)需求管理系统是一种在制造业中广泛应用的计划工具,旨在通过分析和计划企业生产和库存需求,优化资源利用,提高生产效率。以下是对MRP需求管理系统的功能设…

Raylib 坐标系

draftx 符号调整为正数 发现采样坐标系原点0&#xff0c;0 在左上角&#xff0c;正方向 右&#xff0c;下 绘制坐标系 原点0&#xff0c;0 在左下角&#xff0c;正方向 右&#xff0c;上 拖拽可得 #include <raylib.h> // 重整原因&#xff1a;解决新函数放大缩小之下…

当需要对多个表进行联合更新操作时,怎样确保数据的一致性?

文章目录 一、问题分析二、解决方案三、示例代码&#xff08;以 MySQL 为例&#xff09;四、加锁机制示例五、测试和验证六、总结 在数据库管理中&#xff0c;经常会遇到需要对多个表进行联合更新的情况。这种操作带来了一定的复杂性&#xff0c;因为要确保在整个更新过程中数据…

为什么需要服务器?服务器可以做些什么

目录 一、服务器和电脑的区别二、什么是SSH三、什么是免密码登录四、服务器如何实现SSH免密码登录 一、服务器和电脑的区别 服务器和电脑是两种不同类型的计算机系统&#xff0c;它们在设计、功能和用途上存在明显的区别。首先&#xff0c;从硬件配置上看&#xff0c;服务器通…

vb.netcad二开自学笔记3:启动与销毁

Imports Autodesk.AutoCAD.ApplicationServicesImports Autodesk.AutoCAD.EditorInputImports Autodesk.AutoCAD.RuntimePublic Class WellcomCADImplements IExtensionApplicationPublic Sub Initialize() Implements IExtensionApplication.InitializeMsgBox("net程序已…

JDK都出到20多了,你还不会使用JDK8的Stream流写代码吗?

目录 前言 Stream流 是什么&#xff1f; 为什么要用Steam流 常见stream流使用案例 映射 map() & 集合 collect() 单字段映射 多字段映射 映射为其他的对象 映射为 Map 去重 distinct() 过滤 filter() Stream流的其他方法 使用Stream流的弊端 前言 当你某天看…

基于深度学习LightWeight的人体姿态检测跌倒系统源码

一. LightWeight概述 light weight openpose是openpose的简化版本&#xff0c;使用了openpose的大体流程。 Light weight openpose和openpose的区别是&#xff1a; a 前者使用的是Mobilenet V1&#xff08;到conv5_5&#xff09;&#xff0c;后者使用的是Vgg19&#xff08;前10…

公务员考试、事业编考试、教师资格证、面试、K12资料、电子书

点击上方△腾阳 关注 作者 l 腾阳 转载请联系授权 你好&#xff0c;我是腾阳。 在这个自媒体的海洋里&#xff0c;我曾是一只迷失方向的小鸟&#xff0c;多次尝试飞翔却总是跌跌撞撞。 但每一次跌倒&#xff0c;都让我更坚定地相信&#xff0c;只要不放弃&#xff0c;总…

【Unity2D 2022:Particle System】添加命中粒子特效

一、创建粒子特效游戏物体 二、修改粒子系统属性 1. 基础属性 &#xff08;1&#xff09;修改发射粒子持续时间&#xff08;Duration&#xff09;为1s &#xff08;2&#xff09;取消勾选循环&#xff08;Looping&#xff09; &#xff08;2&#xff09;修改粒子存在时间&…

2024全网最全面及最新且最为详细的网络安全技巧五 之 SSRF 漏洞EXP技巧,典例分析以及 如何修复 (上册)———— 作者:LJS

五——SSRF漏洞 EXP技巧&#xff0c;典例分析以及 如何修复 目录 五——SSRF EXP技巧&#xff0c;典例分析以及 如何修复 5.1Apache mod_proxy SSRF&#xff08;CVE-2021-40438&#xff09;的一点分析和延伸 0x01 Apache Module综述 0x02 漏洞原理分析 Apache在配置反代的后端…

Vue的学习之生命周期

一、生命周期 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>Vue的学习</title><script src"vue.js" type"text/javascript" charset"utf-8"></script></head>&l…

C#如何从中级进阶到高级开发

从中级C#开发进阶到高级开发&#xff0c;需要深入理解和掌握更复杂的技术和架构&#xff0c;同时培养解决问题的能力和创新思维。以下是一些关键的技能和步骤&#xff0c;可以帮助你从中级向高级开发迈进&#xff1a; 1. 深入理解C#语言特性 泛型&#xff1a;熟练使用泛型提高…

Java实现登录验证 -- JWT令牌实现

目录 1.实现登录验证的引出原因 2.JWT令牌2.1 使用JWT令牌时2.2 令牌的组成 3. JWT令牌&#xff08;token&#xff09;生成和校验3.1 引入JWT令牌的依赖3.2 使用Jar包中提供的API来实现JWT令牌的生成和校验3.3 使用JWT令牌验证登录3.4 令牌的优缺点 1.实现登录验证的引出 传统…

强化Linux系统安全性:从基础命令到高级管理

强化Linux系统安全性&#xff1a;从基础命令到高级管理 引言 在网络安全领域&#xff0c;Linux系统因其稳定性和安全性而广受欢迎。作为一名网络安全专家&#xff0c;我将分享如何通过Linux基础命令和高级管理技巧来加强系统的安全性。本文将基于《学神 IT 教育》提供的Linux…

Debezium报错处理系列之第110篇: ERROR Error during binlog processing.Access denied

Debezium报错处理系列之第110篇:ERROR Error during binlog processing. Last offset stored = null, binlog reader near position = /4 Access denied; you need at least one of the REPLICATION SLAVE privilege for this operation 一、完整报错二、错误原因三、解决方法…