詳解vue3沙箱機(jī)制
前言
vue3 沙箱主要分兩種
- 瀏覽器編譯版本,瀏覽器版本是使用with語(yǔ)法加上proxy代理攔截
- 本地預(yù)編譯版本,通過(guò)在模版預(yù)編譯階段轉(zhuǎn)換階段,使用轉(zhuǎn)換插件transformExpression將非白名單標(biāo)識(shí)符掛在在組件代理對(duì)象下
瀏覽器編譯版本
render 函數(shù)編譯結(jié)果
<div>{{test}}</div>
<div>{{Math.floor(1)}}</div>
to
const _Vue = Vue;
return function render(_ctx, _cache, $props, $setup, $data, $options) {
with (_ctx) {
const {
toDisplayString: _toDisplayString,
createVNode: _createVNode,
Fragment: _Fragment,
openBlock: _openBlock,
createBlock: _createBlock,
} = _Vue;
return (
_openBlock(),
_createBlock(
_Fragment,
null,
[
_createVNode("div", null, _toDisplayString(test), 1 /* TEXT */),
_createVNode(
"div",
null,
_toDisplayString(Math.floor(1)),
1 /* TEXT */
),
],
64 /* STABLE_FRAGMENT */
)
);
}
};
從上面的代碼,我們能發(fā)現(xiàn),變量標(biāo)識(shí)符沒(méi)有增加前綴,只是用with語(yǔ)法包裹了一下,延長(zhǎng)作用域鏈,那么是如何做到 js 沙箱攔截的呢?例如變量test,理論上說(shuō),當(dāng)前作用域鏈沒(méi)有test變量,變量會(huì)從上一層作用域查找,直到查找到全局作用域,但是,實(shí)際上只會(huì)在_ctx上查找,原理很簡(jiǎn)單,_ctx是一個(gè)代理對(duì)象,那么我們?nèi)绾问褂肞roxy做攔截,示例代碼如下:
const GLOBALS_WHITE_LISTED =
"Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI," +
"decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array," +
"Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt";
const isGloballyWhitelisted = (key) => {
return GLOBALS_WHITE_LISTED.split(",").includes(key);
};
const hasOwn = (obj, key) => {
return Object.prototype.hasOwnProperty.call(obj, key);
};
const origin = {};
const _ctx = new Proxy(origin, {
get(target, key, reciever) {
if (hasOwn(target, key)) {
Reflect.get(target, key, reciever);
} else {
console.warn(
`Property ${JSON.stringify(key)} was accessed during render ` +
`but is not defined on instance.`
);
}
},
has(target, key) {
// 如果是 全局對(duì)象 返回false,不觸發(fā)get 攔截,從上一層作用域查找變量
// 如果不是 全局對(duì)象 返回true,觸發(fā)get 攔截
return !isGloballyWhitelisted(key);
},
});
代碼很簡(jiǎn)單,為什么這么簡(jiǎn)單的代碼就能做到攔截?
因?yàn)?with 語(yǔ)句會(huì)觸發(fā) has 攔截,當(dāng) has 返回 true,就會(huì) 觸發(fā)代理對(duì)象 get 攔截,如果返回 false, 則代理對(duì)象 get 攔截不會(huì)觸發(fā),變量不在當(dāng)前代理對(duì)象查找,直接查找更上一層作用域
本地預(yù)編譯版本
<div>{{test}}</div>
<div>{{Math.floor(1)}}</div>
to
import {
toDisplayString as _toDisplayString,
createVNode as _createVNode,
Fragment as _Fragment,
openBlock as _openBlock,
createBlock as _createBlock,
} from "vue";
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (
_openBlock(),
_createBlock(
_Fragment,
null,
[
_createVNode("div", null, _toDisplayString(_ctx.a), 1 /* TEXT */),
_createVNode(
"div",
null,
_toDisplayString(Math.floor(1)),
1 /* TEXT */
),
],
64 /* STABLE_FRAGMENT */
)
);
}
從上面的代碼我們可以發(fā)現(xiàn),非白名單標(biāo)識(shí)符都添加了_ctx 變量前綴,那么是如何做到的呢?當(dāng)本地編譯 template 時(shí),處于轉(zhuǎn)換階段時(shí)會(huì)對(duì) 變量表達(dá)式節(jié)點(diǎn)NodeTypes.SIMPLE_EXPRESSION進(jìn)行添加前綴處理,示例代碼如下:
const GLOBALS_WHITE_LISTED =
"Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI," +
"decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array," +
"Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt";
const isGloballyWhitelisted = (key) => {
return GLOBALS_WHITE_LISTED.split(",").includes(key);
};
const isLiteralWhitelisted = (key)=>{
return 'true,false,null,this'.split(',').includes(key)
}
export function processExpression(
node
) {
const rewriteIdentifier = (raw) => {
return `_ctx.${raw}`
}
const rawExp = node.content
if (isSimpleIdentifier(rawExp)) {
const isAllowedGlobal = isGloballyWhitelisted(rawExp)
const isLiteral = isLiteralWhitelisted(rawExp)
if (!isAllowedGlobal && !isLiteral) {
node.content = rewriteIdentifier(rawExp)
}
return node
}
當(dāng)然上面的代碼只是簡(jiǎn)化版本,原版插件還做了精確到了__props $setup,減短變量查詢(xún)路徑,提高性能,還有通過(guò)babel編譯復(fù)雜表達(dá)式比如:箭頭函數(shù)。
總結(jié)
整個(gè) vue3 js 沙箱機(jī)制就解釋結(jié)束了,當(dāng)初瀏覽器編譯版本困擾了我很久,因?yàn)椴恢?has 可以攔截 with 語(yǔ)句變量查詢(xún)
參考
Proxy handler.has
說(shuō)說(shuō) JS 中的沙箱
動(dòng)手寫(xiě) js 沙箱
到此這篇關(guān)于詳解vue3 沙箱機(jī)制的文章就介紹到這了,更多相關(guān)vue3 沙箱機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue.set() this.$set()引發(fā)的視圖更新思考及注意事項(xiàng)
this.$set()和Vue.set()本質(zhì)方法一樣,前者可以用在methods中使用。這篇文章主要介紹了Vue.set() this.$set()引發(fā)的視圖更新思考及注意事項(xiàng),需要的朋友可以參考下2018-08-08
vue?openlayers實(shí)現(xiàn)臺(tái)風(fēng)軌跡示例詳解
這篇文章主要為大家介紹了vue?openlayers實(shí)現(xiàn)臺(tái)風(fēng)軌跡示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Vue利用AJAX請(qǐng)求獲取XML文件數(shù)據(jù)的操作方法
在現(xiàn)代Web開(kāi)發(fā)中,從前端框架到后端API的交互是必不可少的一部分,Vue.js作為一個(gè)輕量級(jí)且功能強(qiáng)大的前端框架,支持多種方式與服務(wù)器通信,從而獲取或發(fā)送數(shù)據(jù),本文將詳細(xì)介紹如何在Vue.js項(xiàng)目中使用AJAX請(qǐng)求來(lái)獲取XML格式的數(shù)據(jù),需要的朋友可以參考下2024-09-09
.netcore+vue 實(shí)現(xiàn)壓縮文件下載功能
這篇文章主要介紹了.netcore+vue 實(shí)現(xiàn)壓縮文件下載功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
uniapp微信小程序WebApi_openid、phone接口獲取代碼詳解
本文主要記錄了微信小程序接口調(diào)用的過(guò)程,首先查看uniapp文檔和微信API文檔,獲取openid和phone,然后通過(guò)uniapp實(shí)現(xiàn)獲取openid和電話(huà)號(hào)碼,但遇到了合法域名屏蔽的問(wèn)題,最后通過(guò)將微信訪(fǎng)問(wèn)遷移到后臺(tái)解決,需要的朋友可以參考下2024-10-10
vue項(xiàng)目搭建以及全家桶的使用詳細(xì)教程(小結(jié))
這篇文章主要介紹了vue項(xiàng)目搭建以及全家桶的使用詳細(xì)教程(小結(jié)),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12

