打印 window 对象,PageSpy 是如何处理的?

前端开发者福音,一定要试试的 Web 远程调试工具!

GitHub 仓库地址:github.com/HuolalaTech...

前言

PageSpy 自开源提供服务以来,社区中收到大量用户反馈说想了解 PageSpy 的一些工作原理,这么真挚而又热烈的要求,我们第一时间安排上了。本章就为大家带来深入实现原理系列之「打印 window 对象,PageSpy 是如何处理的?」。

Console 面板除了打印日志外,还支持在 Console 面板底部发送调试代码到客户端,获取运行时的变量信息。

还没有尝试过的小伙伴可以点击 在线体验,接下来将和大家一起进入主题:PageSpy 如何处理复杂数据的交互

设计目标

PageSpy Console 面板最起初的设计目标就是向浏览器控制台的 Console 面板看齐。用户如果打印复杂数据,单纯靠序列化源数据会面临以下问题:

  • 不支持序列化的属性值会丢失;
  • self-reference 的数据会报错;
  • Getter 属性值动态计算的交互特性会丢失;
  • 无法沿着原型链查看数据明细;

上述提到的每一项在各自的场景中都发挥着无比重要的作用。所以可以明确的是:

  • 想要打印复杂数据,肯定是需要先做什么处理后再使用;
  • 后续有查看属性明细的需求,所以需要将数据实体找个地方存起来;

什么是复杂数据?

对于数据「复杂不复杂」是按照如下定义的:

  • 可以点击查看明细的数据都是复杂数据,如 Object / Array / Set / Map 等类型的数据;
  • 一眼就看明白的都是不复杂数据,如 "Hello" / 12345 / true / Symbol(foo) / Error / undefined 等;

在 PageSpy 中,我们将对各种数据进行转换、缓存、查询、交互的逻辑封成 Atom 类(type: "atom" 代表复杂数据),结构体如下所示:

ts 复制代码
// SpyAtom.Overview
interface Overview {
  id: string;
  type:
    | 'string'
    | 'number'
    | 'bigint'
    | 'boolean'
    | 'symbol'
    | 'undefined'
    | 'object'
    | 'function'
    | 'null'
    | 'error'
    | 'debug-origin'
    | 'atom';
  value: string | PropertyDescriptor;
  __atomId?: string;
  instanceId?: string;
}

正是这 20 行类型代码决定了本次主题中的具体实现:打印 window 对象就是依赖这套接口类型定义。

为什么要强调打印的是 window 对象?因为 window 对象具备上述所有的 "问题" 于一身:

  • 有不支持序列化的属性数据,比如 window.alert() 这种全局方法的属性;
  • 有很多 Getter 属性,如 outerWidth / innerWidth / location 等;
  • self-reference,如 window.self
  • 可以沿着原型链查看数据明细;

如何实现

我们在上面提到了:「用户如果打印复杂数据,单纯靠序列化源数据会面临......」,那打印不复杂数据还会有那么多问题吗?答案是不会,对于这些不复杂的数据我们只需要拿到值,并获取到它的类型即可完成渲染。

明确了数据复不复杂之后,我们从以上类型定义中把 type: "atom" 复杂数据类型的定义单独拎出来:

ts 复制代码
// SpyAtom.Overview
interface Overview {
  id: string;
  type: 'atom';
  value: string | PropertyDescriptor;
  __atomId: string;
  instanceId: string;
}
  • id:作为数据的唯一标识。
  • __atomId :打印复杂数据时,数据实体会被记录在 Atom 的 storeMap 中,__atomId 在 storeMap 中作为 key,对应一个对象源数据。
  • instanceId :本质的值是由 __atomId 衍生。默认和 __atomId 相等、指向当前打印出来的对象;点击对象展开,每个内层属性都有一个 instanceId 指向父级(上一层)的对象;

除了以上三个 id 之外,我们还用到了一个被称为 parentId 的数据:

  • parentId :本质的值也是由 __atomId 衍生。用于获取 Getter 属性值,具体的获取行为原理是 Object.getOwnPropertyDescriptor(store[parentId], key).get.call(store[instanceId])

总结

通过以上的内容介绍,我们再来回顾下打印 window 对象会遇到的问题,现在都有了相对应的答案:

如何处理不支持序列化的属性数据,比如函数?

答:第一步先通过获取类型将数据以「复杂」维度区分,结合数据值来描述一个数据该如何显示;而函数属于不复杂数据,在确定是函数类型后、通过 fn.toString() 获取值即可在调试端渲染。

Getter 属性动态计算属性值的交互特性会丢失?

答:Getter 属性值的获取本质上是调用函数,函数体内会使用 this 指针。那么基于以上复杂数据结构的定义,我们有 parentId / key / instanceId,基于 parentId 和 key 可以获取 getter 函数本身,再通过 instanceId 知道了实例对象,那么接下来组合调用一下就能完美解决:Object.getOwnPropertyDescriptor(store[parentId], key).get.call(store[instanceId])

如何处理 self-reference 对象无法序列化的问题,如 window.self

答:PageSpy 只会转化当前对象层级下的不复杂的数据属性。展开 window 时,PageSpy 会拿到 self 属性,但判断出它是一个复杂数据,SDK 不会直接进行计算、转换处理,而是告诉调试端 window.self 的描述符具备 getter 属性,等待调试端下次来查询时,再进值计算并返回值。

如何沿着原型链查看数据明细?

答:虽然通过 Object.getOwnPropertyDescriptors(someObj) 返回的 descriptors 描述符不会给我们原型对象的数据,但是我们可以手动添加一个 [[Prototype]] 加上去就可以满足用户沿着原型链点击查看数据明细啦!

以上就是本次深入 PageSpy 实现原理系列文章的全部内容了,欢迎大家在工作中集成使用,遇到问题可以在 Github 反馈或者加入技术支持群(中文 README 有群二维码),我们将第一时间解答。

相关推荐
xiao-xiang12 分钟前
jenkins-通过api获取所有job及最新build信息
前端·servlet·jenkins
C语言魔术师28 分钟前
【小游戏篇】三子棋游戏
前端·算法·游戏
匹马夕阳2 小时前
Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制
前端·javascript·vue.js
你熬夜了吗?2 小时前
日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件
前端·vue.js·信息可视化
桂月二二8 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062069 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb9 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角9 小时前
CSS 颜色
前端·css
九酒10 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
硬汉嵌入式10 小时前
《安富莱嵌入式周报》第349期:VSCode正式支持Matlab调试,DIY录音室级麦克风,开源流体吊坠,物联网在军工领域的应用,Unicode字符压缩解压
vscode·matlab·开源