• 售前

  • 售后

热门帖子
入门百科

详解vue3沙箱机制

[复制链接]
123457294 显示全部楼层 发表于 2021-10-26 12:24:00 |阅读模式 打印 上一主题 下一主题
目录


  • 媒介
  • 浏览器编译版本
  • 当地预编译版本
  • 总结
  • 参考

媒介


vue3 沙箱主要分两种
       
  • 浏览器编译版本,浏览器版本是利用with语法加上proxy代理拦截   
  • 当地预编译版本,通过在模版预编译阶段转换阶段,利用转换插件transformExpression将非白名单标识符挂在在组件代理对象下

浏览器编译版本


render 函数编译效果
  1. <div>{{test}}</div>
  2. <div>{{Math.floor(1)}}</div>
复制代码
to
  1. const _Vue = Vue;
  2. return function render(_ctx, _cache, $props, $setup, $data, $options) {
  3.   with (_ctx) {
  4.     const {
  5.       toDisplayString: _toDisplayString,
  6.       createVNode: _createVNode,
  7.       Fragment: _Fragment,
  8.       openBlock: _openBlock,
  9.       createBlock: _createBlock,
  10.     } = _Vue;
  11.     return (
  12.       _openBlock(),
  13.       _createBlock(
  14.         _Fragment,
  15.         null,
  16.         [
  17.           _createVNode("div", null, _toDisplayString(test), 1 /* TEXT */),
  18.           _createVNode(
  19.             "div",
  20.             null,
  21.             _toDisplayString(Math.floor(1)),
  22.             1 /* TEXT */
  23.           ),
  24.         ],
  25.         64 /* STABLE_FRAGMENT */
  26.       )
  27.     );
  28.   }
  29. };
复制代码
从上面的代码,我们能发现,变量标识符没有增长前缀,只是用with语法包裹了一下,延长作用域链,那么是如何做到 js 沙箱拦截的呢?例如变量test,理论上说,当前作用域链没有test变量,变量会从上一层作用域查找,直到查找到全局作用域,但是,现实上只会在_ctx上查找,原理很简朴,_ctx是一个代理对象,那么我们如何利用Proxy做拦截,示例代码如下:
  1. const GLOBALS_WHITE_LISTED =
  2.   "Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI," +
  3.   "decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array," +
  4.   "Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt";
  5. const isGloballyWhitelisted = (key) => {
  6.   return GLOBALS_WHITE_LISTED.split(",").includes(key);
  7. };
  8. const hasOwn = (obj, key) => {
  9.   return Object.prototype.hasOwnProperty.call(obj, key);
  10. };
  11. const origin = {};
  12. const _ctx = new Proxy(origin, {
  13.   get(target, key, reciever) {
  14.     if (hasOwn(target, key)) {
  15.       Reflect.get(target, key, reciever);
  16.     } else {
  17.       console.warn(
  18.         `Property ${JSON.stringify(key)} was accessed during render ` +
  19.           `but is not defined on instance.`
  20.       );
  21.     }
  22.   },
  23.   has(target, key) {
  24.     // 如果是 全局对象 返回false,不触发get 拦截,从上一层作用域查找变量
  25.     // 如果不是 全局对象 返回true,触发get 拦截
  26.     return !isGloballyWhitelisted(key);
  27.   },
  28. });
复制代码
代码很简朴,为什么这么简朴的代码就能做到拦截?
由于 with 语句会触发 has 拦截,当 has 返回 true,就会 触发代理对象 get 拦截,如果返回 false, 则代理对象 get 拦截不会触发,变量不在当前代理对象查找,直接查找更上一层作用域


当地预编译版本

  1. <div>{{test}}</div>
  2. <div>{{Math.floor(1)}}</div>
复制代码
to
  1. import {
  2.   toDisplayString as _toDisplayString,
  3.   createVNode as _createVNode,
  4.   Fragment as _Fragment,
  5.   openBlock as _openBlock,
  6.   createBlock as _createBlock,
  7. } from "vue";
  8. export function render(_ctx, _cache, $props, $setup, $data, $options) {
  9.   return (
  10.     _openBlock(),
  11.     _createBlock(
  12.       _Fragment,
  13.       null,
  14.       [
  15.         _createVNode("div", null, _toDisplayString(_ctx.a), 1 /* TEXT */),
  16.         _createVNode(
  17.           "div",
  18.           null,
  19.           _toDisplayString(Math.floor(1)),
  20.           1 /* TEXT */
  21.         ),
  22.       ],
  23.       64 /* STABLE_FRAGMENT */
  24.     )
  25.   );
  26. }
复制代码
从上面的代码我们可以发现,非白名单标识符都添加了_ctx 变量前缀,那么是如何做到的呢?当当地编译 template 时,处于转换阶段时会对 变量表达式节点NodeTypes.SIMPLE_EXPRESSION举行添加前缀处置惩罚,示例代码如下:
  1. const GLOBALS_WHITE_LISTED =
  2.   "Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI," +
  3.   "decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array," +
  4.   "Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt";
  5. const isGloballyWhitelisted = (key) => {
  6.   return GLOBALS_WHITE_LISTED.split(",").includes(key);
  7. };
  8. const isLiteralWhitelisted = (key)=>{
  9.   return 'true,false,null,this'.split(',').includes(key)
  10. }
  11. export function processExpression(
  12.   node
  13. ) {
  14.   const rewriteIdentifier = (raw) => {
  15.     return `_ctx.${raw}`
  16.   }
  17.   const rawExp = node.content
  18.   if (isSimpleIdentifier(rawExp)) {
  19.     const isAllowedGlobal = isGloballyWhitelisted(rawExp)
  20.     const isLiteral = isLiteralWhitelisted(rawExp)
  21.     if (!isAllowedGlobal && !isLiteral) {
  22.       node.content = rewriteIdentifier(rawExp)
  23.     }
  24.     return node
  25.   }
复制代码
固然上面的代码只是简化版本,原版插件还做了精确到了__props $setup,减短变量查询路径,提高性能,还有通过babel编译复杂表达式比如:箭头函数。


总结


整个 vue3 js 沙箱机制就表明结束了,当初浏览器编译版本困扰了我好久,由于不知道 has 可以拦截 with 语句变量查询


参考

Proxy handler.has
说说 JS 中的沙箱
动手写 js 沙箱
到此这篇关于详解vue3 沙箱机制的文章就先容到这了,更多相关vue3 沙箱机制内容请搜刮脚本之家以前的文章或继续浏览下面的相关文章希望各人以后多多支持脚本之家!

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作