打印 window 对象, PageSpy 是如何处理的? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
blucas01
V2EX    程序员

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

  •  
  •   blucas01 2023-12-26 10:05:26 +08:00 1163 次点击
    这是一个创建于 658 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    GitHub 仓库地址: https://github.com/HuolalaTech/page-spy-web

    前言

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

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

    [video-to-gif output image]

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

    设计目标

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

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

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

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

    什么是复杂数据?

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

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

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

    // 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" 复杂数据类型的定义单独拎出来:

    // 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 有群二维码),我们将第一时间解答。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5009 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 48ms UTC 01:08 PVG 09:08 LX 18:08 JFK 21:08
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86