背景:
起源于一个前后端Json交互的场景,后端会返回5w+长度的json字符;前端渲染时会有明显卡顿;基于这个场景,来深入研究一下,一直在用的JSON.parse(),里面到底有什么秘密。
序列化
定义 :内存对象 (Heap Object) → 字符串/二进制流 (String/Byte Stream)。用于传输和存储。
意义:
1. 数据传输
跨语言通信: 由于语言的不同,前端的 JS 对象后端是无法直接理解的。
网络协议限制: HTTP 协议传输的是文本或字节流。
2. 数据本地存储
localStorage、sessionStorage、cookie本质上只能存储字符串。所以如果直接存储对象,浏览器会自动调用 .toString(),导致存储结果为 "[object Object]"。
**3. 深拷贝 (Deep Clone) **
序列化可以在js中快速简单实现深拷贝
为什么会卡顿
这里从两个维度分析:
Js单线程阻塞
JSON.parse() 和 JSON.stringify() 都会同步执行,在一次宏任务执行中将会直接阻塞代码执行。那么在此时浏览器将无法响应用户的其他操作,点击、页面重绘等……
响应式劫持导致大量的响应式递归
vue框架下,如果把这样的超大数据赋值为响应式,需要递归遍历这个巨大对象的每一个属性,为其添加 Getter/Setter。
问题一解决:Worker
直接看一段代码
1 | // parse.worker.js |
1 | import MyWorker from './parse.worker.js?worker'; |
原理:
通过使用worker单独开一个浏览器线程,去执行我们解析大数据的耗时任务,通过postMessage通信,最后异步处理好数据后再返回来。
‘./parse.worker.js?worker’ 解析:
这是vite对于Worker导入的特殊处理,因为Worker如果像平时的js导入一样,那么他就隶属于主线程下了。Worker原生api new Worker(‘path/myWorkeryFile.js’) 其实只是需要一个路径,浏览器用这个路径去向操作系统申请开一个新的线程,然后把这个文件里的代码加载进去跑。
那么vite在打包时如果遇到了?worker,会做两件事
- 独立打包:识别?worker后,单独打包这个文件(比如
assets/worker-123.js)。 - 自动封装:自动把这个文件的 URL 塞进
new Worker()构造函数里,然后返回一个可以直接new的类。
当然也可以直接用原生api去new,但是这里涉及到相对路径的问题;
1 | const worker = new Worker('./parse.worker.js'); // 新开的浏览器线程会在根目录找相对路径,这里会404 |
只有将parse.worker.js放在public目录下,才可以。vite不会动public里面的任何东西,但是相反的,我们的worker文件里就只能写原生js(不能import、不能用ts……)
1 | const worker = new Worker('/parse.worker.js') |
问题二解决:shallowRef
原理:
通过vue3提供的api,去创建一个浅层的响应式对象,只对对象的.value做响应式监听,对于里面的属性不再做响应式
1 | const list = shallowRef([{ id: 1, name: 'Jimu' }]); |
那么vue2呢
答案是可以用一个原生js API Object.freeze;Object.freeze()的本质其实就是将对象属性的描述符的configurable和writable设置为false,然后在vue源码转换响应式对象的时候,会去判断这两个属性,进而判断是否需要绑定响应式。
1 | export default { |