日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

如何手動(dòng)實(shí)現(xiàn)一個(gè) JavaScript 模塊執(zhí)行器

瀏覽:187日期:2023-10-11 08:04:28

如果給你下面這樣一個(gè)代碼片段(動(dòng)態(tài)獲取的代碼字符串),讓你在前端動(dòng)態(tài)引入這個(gè)模塊并執(zhí)行里面的函數(shù),你會(huì)如何處理呢?

module.exports = { name : ’ConardLi’, action : function(){ console.log(this.name); } };

node 環(huán)境的執(zhí)行

如果在 node 環(huán)境,我們可能會(huì)很快的想到使用 Module 模塊, Module 模塊中有一個(gè)私有函數(shù) _compile,可以動(dòng)態(tài)的加載一個(gè)模塊:

export function getRuleFromString(code) { const myModule = new Module(’my-module’); myModule._compile(code,’my-module’); return myModule.exports; }

實(shí)現(xiàn)就是這么簡單,后面我們會(huì)回顧一下 _compile 函數(shù)的原理,但是需求可不是這么簡單,我們?nèi)绻谇岸谁h(huán)境動(dòng)態(tài)引入這段代碼呢?

嗯,你沒聽錯(cuò),最近正好碰到了這樣的需求,需要在前端和 Node 端抹平動(dòng)態(tài)引入模塊的邏輯,好,下面我們來模仿 Module 模塊實(shí)現(xiàn)一個(gè)前端環(huán)境的 JavaScript 模塊執(zhí)行器。

首先我們先來回顧一下 node 中的模塊加載原理。

node Module 模塊加載原理

Node.js 遵循 CommonJS 規(guī)范,該規(guī)范的核心思想是允許模塊通過 require 方法來同步加載所要依賴的其他模塊,然后通過 exports 或 module.exports 來導(dǎo)出需要暴露的接口。其主要是為了解決 JavaScript 的作用域問題而定義的模塊形式,可以使每個(gè)模塊它自身的命名空間中執(zhí)行。

再在每個(gè) NodeJs 模塊中,我們都能取到 module、exports、__dirname、__filename 和 require 這些模塊。并且每個(gè)模塊的執(zhí)行作用域都是相互隔離的,互不影響。

其實(shí)上面整個(gè)模塊系統(tǒng)的核心就是 Module 類的 _compile 方法,我們直接來看 _compile 的源碼:

Module.prototype._compile = function(content, filename) { // 去除 Shebang 代碼 content = internalModule.stripShebang(content); // 1.創(chuàng)建封裝函數(shù) var wrapper = Module.wrap(content); // 2.在當(dāng)前上下文編譯模塊的封裝函數(shù)代碼 var compiledWrapper = vm.runInThisContext(wrapper, { filename: filename, lineOffset: 0, displayErrors: true }); var dirname = path.dirname(filename); var require = internalModule.makeRequireFunction(this); var depth = internalModule.requireDepth; // 3.運(yùn)行模塊的封裝函數(shù)并傳入 module、exports、__dirname、__filename、require var result = compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname); return result; };

整個(gè)執(zhí)行過程我將其分為三步:

創(chuàng)建封裝函數(shù)

第一步即調(diào)用 Module 內(nèi)部的 wrapper 函數(shù)對模塊的原始內(nèi)容進(jìn)行封裝,我們先來看看 wrapper 函數(shù)的實(shí)現(xiàn):

Module.wrap = function(script) { return Module.wrapper[0] + script + Module.wrapper[1]; }; Module.wrapper = [ ’(function (exports, require, module, __filename, __dirname) { ’, ’n});’ ];

CommonJS 的主要目的就是解決 JavaScript 的作用域問題,可以使每個(gè)模塊它自身的命名空間中執(zhí)行。在沒有模塊化方案的時(shí)候,我們一般會(huì)創(chuàng)建一個(gè)自執(zhí)行函數(shù)來避免變量污染:

(function(global){ // 執(zhí)行代碼。。 })(window)

所以這一步至關(guān)重要,首先 wrapper 函數(shù)就將模塊本身的代碼片段包裹在一個(gè)函數(shù)作用域內(nèi),并且將我們需要用到的對象作為參數(shù)引入。所以上面的代碼塊被包裹后就變成了:

(function (exports, require, module, __filename, __dirname) { module.exports = { name : ’ConardLi’, action : function(){ console.log(this.name); } }; });

編譯封裝函數(shù)代碼

NodeJs 中的 vm 模塊提供了一系列 API 用于在 V8 虛擬機(jī)環(huán)境中編譯和運(yùn)行代碼。JavaScript 代碼可以被編譯并立即運(yùn)行,或編譯、保存然后再運(yùn)行。

vm.runInThisContext() 在當(dāng)前的 global 對象的上下文中編譯并執(zhí)行 code,最后返回結(jié)果。運(yùn)行中的代碼無法獲取本地作用域,但可以獲取當(dāng)前的 global 對象。

var compiledWrapper = vm.runInThisContext(wrapper, { filename: filename, lineOffset: 0, displayErrors: true });

所以以上代碼執(zhí)行后,就將代碼片段字符串編譯成了一個(gè)真正的可執(zhí)行函數(shù):

(function (exports, require, module, __filename, __dirname) { module.exports = { name : ’ConardLi’, action : function(){ console.log(this.name); } }; });

運(yùn)行封裝函數(shù)

最后通過 call 來執(zhí)行編譯得到的可執(zhí)行函數(shù),并傳入對應(yīng)的對象。

var result = compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname);

所以看到這里你應(yīng)該會(huì)明白,我們在模塊中拿到的 module,就是 Module 模塊的實(shí)例本身,我們直接調(diào)用的 exports 實(shí)際上是 module.exports 的引用,所以我們既可以使用 module.exports 也可以使用 exports 來導(dǎo)出一個(gè)模塊。

實(shí)現(xiàn) Module 模塊

如果我們想在前端環(huán)境執(zhí)行一個(gè) CommonJS 模塊,那么我們只需要手動(dòng)實(shí)現(xiàn)一個(gè) Module 模塊就好了,重新梳理上面的流程,如果只考慮模塊代碼塊動(dòng)態(tài)引入的邏輯,我們可以抽象出下面的代碼:

export default class Module { exports = {} wrapper = [ ’return (function (exports, module) { ’, ’n});’ ]; wrap(script) { return `${this.wrapper[0]} ${script} ${this.wrapper[1]}`; }; compile(content) { const wrapper = this.wrap(content); const compiledWrapper = vm.runInContext(wrapper); compiledWrapper.call(this.exports, this.exports, this); } }

這里有個(gè)問題,在瀏覽器環(huán)境是沒有 VM 這個(gè)模塊的,VM 會(huì)將代碼加載到一個(gè)上下文環(huán)境中,置入沙箱(sandbox),讓代碼的整個(gè)操作執(zhí)行都在封閉的上下文環(huán)境中進(jìn)行,我們需要自己實(shí)現(xiàn)一個(gè)瀏覽器環(huán)境的沙箱。

實(shí)現(xiàn)瀏覽器沙箱

eval

在瀏覽器執(zhí)行一段代碼片段,我們首先想到的可能就是 eval, eval 函數(shù)可以將一個(gè) Javascript 字符串視作代碼片段執(zhí)行。

但是,由 eval() 執(zhí)行的代碼能夠訪問閉包和全局作用域,這會(huì)導(dǎo)致被稱為代碼注入 code injection 的安全隱患, eval 雖然好用,但是經(jīng)常被濫用,是 JavaScript 最臭名昭著的功能之一。

所以,后來又出現(xiàn)了很多在沙箱而非全局作用域中的執(zhí)行字符串代碼的值的替代方案。

new Function()

Function 構(gòu)造器是 eval() 的一個(gè)替代方案。new Function(...args, ’funcBody’) 對傳入的 ’funcBody’ 字符串進(jìn)行求值,并返回執(zhí)行這段代碼的函數(shù)。

fn = new Function(...args, ’functionBody’);

返回的 fn 是一個(gè)定義好的函數(shù),最后一個(gè)參數(shù)為函數(shù)體。它和 eval 有兩點(diǎn)區(qū)別:

fn 是一段編譯好的代碼,可以直接執(zhí)行,而 eval 需要編譯一次 fn 沒有對所在閉包的作用域訪問權(quán)限,不過它依然能夠訪問全局作用域

但是這仍然不能解決訪問全局作用域的問題。

with 關(guān)鍵詞

如何手動(dòng)實(shí)現(xiàn)一個(gè) JavaScript 模塊執(zhí)行器

with 是 JavaScript 一個(gè)冷門的關(guān)鍵字。它允許一個(gè)半沙箱的運(yùn)行環(huán)境。with 代碼塊中的代碼會(huì)首先試圖從傳入的沙箱對象獲得變量,但是如果沒找到,則會(huì)在閉包和全局作用域中尋找。閉包作用域的訪問可以用new Function() 來避免,所以我們只需要處理全局作用域。with 內(nèi)部使用 in 運(yùn)算符。在塊中訪問每個(gè)變量,都會(huì)使用 variable in sandbox 條件進(jìn)行判斷。若條件為真,則從沙箱對象中讀取變量。否則,它會(huì)在全局作用域中尋找變量。

function compileCode(src) { src = ’with (sandbox) {’ + src + ’}’ return new Function(’sandbox’, src) }

試想,如果 variable in sandbox 條件永遠(yuǎn)為真,沙箱環(huán)境不就永遠(yuǎn)也讀取不到環(huán)境變量了嗎?所以我們需要劫持沙箱對象的屬性,讓所有的屬性永遠(yuǎn)都能讀取到。

Proxy

如何手動(dòng)實(shí)現(xiàn)一個(gè) JavaScript 模塊執(zhí)行器

ES6 中提供了一個(gè) Proxy 函數(shù),它是訪問對象前的一個(gè)攔截器,我們可以利用 Proxy 來攔截 sandbox 的屬性,讓所有的屬性都可以讀取到:

function compileCode(code) { code = ’with (sandbox) {’ + code + ’}’; const fn = new Function(’sandbox’, code); return (sandbox) => { const proxy = new Proxy(sandbox, { has() { return true; } }); return fn(proxy); } }

Symbol.unscopables

Symbol.unscopables 是一個(gè)著名的標(biāo)記。一個(gè)著名的標(biāo)記即是一個(gè)內(nèi)置的 JavaScript Symbol,它可以用來代表內(nèi)部語言行為。

Symbol.unscopables 定義了一個(gè)對象的 unscopable(不可限定)屬性。在 with 語句中,不能從 Sandbox 對象中檢索 Unscopable 屬性,而是直接從閉包或全局作用域檢索屬性。

所以我們需要對 Symbol.unscopables 這種情況做一次加固,

function compileCode(code) { code = ’with (sandbox) {’ + code + ’}’; const fn = new Function(’sandbox’, code); return (sandbox) => { const proxy = new Proxy(sandbox, { has() { return true; }, get(target, key, receiver) { if (key === Symbol.unscopables) { return undefined; } Reflect.get(target, key, receiver); } }); return fn(proxy); } }

全局變量白名單

但是,這時(shí)沙箱里是執(zhí)行不了瀏覽器默認(rèn)為我們提供的各種工具類和函數(shù)的,它只能作為一個(gè)沒有任何副作用的純函數(shù),當(dāng)我們想要使用某些全局變量或類時(shí),可以自定義一個(gè)白名單:

const ALLOW_LIST = [’console’]; function compileCode(code) { code = ’with (sandbox) {’ + code + ’}’; const fn = new Function(’sandbox’, code); return (sandbox) => { const proxy = new Proxy(sandbox, { has() { if (!ALLOW_LIST.includes(key)) { return true; } }, get(target, key, receiver) { if (key === Symbol.unscopables) { return undefined; } Reflect.get(target, key, receiver); } }); return fn(proxy); } }

最終代碼:

好了,總結(jié)上面的代碼,我們就完成了一個(gè)簡易的 JavaScript 模塊執(zhí)行器:

const ALLOW_LIST = [’console’]; export default class Module { exports = {} wrapper = [ ’return (function (exports, module) { ’, ’n});’ ]; wrap(script) { return `${this.wrapper[0]} ${script} ${this.wrapper[1]}`; }; runInContext(code) { code = `with (sandbox) { $[code] }`; const fn = new Function(’sandbox’, code); return (sandbox) => { const proxy = new Proxy(sandbox, { has(target, key) { if (!ALLOW_LIST.includes(key)) { return true; } }, get(target, key, receiver) { if (key === Symbol.unscopables) { return undefined; } Reflect.get(target, key, receiver); } }); return fn(proxy); } } compile(content) { const wrapper = this.wrap(content); const compiledWrapper = this.runInContext(wrapper)({}); compiledWrapper.call(this.exports, this.exports, this); } }

測試執(zhí)行效果:

function getModuleFromString(code) { const scanModule = new Module(); scanModule.compile(code); return scanModule.exports; } const module = getModuleFromString(` module.exports = { name : ’ConardLi’, action : function(){ console.log(this.name); } }; `); module.action(); // ConardLi

以上就是如何手動(dòng)實(shí)現(xiàn)一個(gè) JavaScript 模塊執(zhí)行器的詳細(xì)內(nèi)容,更多關(guān)于JavaScript 模塊執(zhí)行器的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: JavaScript
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
色爱综合av| 日本不卡一区二区三区| 国产免费av国片精品草莓男男| 蜜臀精品久久久久久蜜臀| 女同性一区二区三区人了人一| 99精品视频在线观看免费播放| 久久狠狠婷婷| 欧美日韩国产高清| 国产亚洲高清视频| 好吊一区二区三区| 久久不射网站| 91成人在线网站| 加勒比视频一区| 日韩欧美视频专区| 黄色av一区| 日本久久一区| 动漫av一区| 91精品一区二区三区综合| 最新日韩欧美| 亚洲精品系列| 欧美激情亚洲| 在线看片福利| 99国内精品| 欧美精品中文字幕亚洲专区| 国产精选久久| 中文在线免费视频| 国产婷婷精品| 国产精品亚洲成在人线| 精品国产日韩欧美精品国产欧美日韩一区二区三区 | 一区福利视频| 三级亚洲高清视频| 国产欧美日韩影院| 日韩大片在线| 亚洲欧洲免费| 福利片在线一区二区| 亚洲男女av一区二区| 97成人在线| 国产精品字幕| 亚洲精品九九| 欧美韩日一区| 亚洲一区二区小说| 国产精品99久久久久久董美香| 另类专区亚洲| 一区二区不卡| 国产成人精品免费视| 日韩天堂av| 国产高清日韩| 久久电影一区| 国产精品黑丝在线播放| 水野朝阳av一区二区三区| 久久久久97| 亚洲欧美日本日韩| 国产成人精品一区二区免费看京 | 欧美精品影院| 国产在线日韩| 久久爱www成人| 樱桃成人精品视频在线播放| 国产精品一站二站| 欧美亚洲精品在线| 欧美日本二区| 国产综合色产| 精品资源在线| 亚洲欧美在线综合| 欧美成人基地 | 日本成人在线网站| 日韩免费福利视频| 91亚洲精品在看在线观看高清| 亚洲成人精品| 久久久91麻豆精品国产一区| 欧美综合二区| 久久久亚洲一区| 欧美激情日韩| 亚洲三级网址| 欧美二区视频| 国产精品99久久精品| 日韩高清电影免费| 自拍日韩欧美| 日产精品一区二区| 欧美一区自拍| 亚洲视频播放| 性欧美videohd高精| 国产乱人伦精品一区| 亚洲神马久久| 激情久久中文字幕| 精品久久国产一区| 日本aⅴ精品一区二区三区| 欧美日韩四区| 欧洲亚洲一区二区三区| 精品资源在线| 国产精品玖玖玖在线资源| 亚洲毛片一区| 日韩精品一卡二卡三卡四卡无卡| 在线日韩av| 欧美日韩视频免费观看| 欧美激情视频一区二区三区在线播放| 亚洲ww精品| 蜜桃视频在线观看一区二区| 婷婷综合社区| 欧美91福利在线观看| 高清av不卡| 精品国产黄a∨片高清在线| 国产欧美欧美| 日韩国产欧美在线播放| 亚洲男人在线| 亚洲精品字幕| 亚洲日本国产| 亚州国产精品| 日韩精品久久久久久久软件91| 亚洲精品成a人ⅴ香蕉片| 国产精品视区| 久久福利精品| 亚洲九九精品| 亚洲另类黄色| 日韩精品久久理论片| 亚洲午夜免费| 蜜臀av国产精品久久久久 | 亚洲精品免费观看| 蜜臀av一区二区在线免费观看 | 精品国产a一区二区三区v免费| 国产精品九九| 精品国产一区二| 国产精品13p| 久久青草久久| 极品日韩av| 亚洲免费网址| 视频一区二区三区中文字幕| 免费在线看一区| 一区二区三区国产在线| 亚洲精品成人一区| 久久国产婷婷国产香蕉| 国产精品久久久久av蜜臀| 国产精品久久久网站| 欧美激情aⅴ一区二区三区| 国产精品igao视频网网址不卡日韩| 国产精品一区二区三区www| 国产精品中文字幕制服诱惑| 麻豆国产精品视频| 久久男人天堂| 欧美1区免费| 最新国产精品久久久| 97成人在线| 国内精品麻豆美女在线播放视频| 麻豆视频在线看| 欧美日韩中文一区二区| 在线视频亚洲| 91福利精品在线观看| 国产在线观看91一区二区三区| 精品欧美一区二区三区在线观看| 欧美一区二区三区激情视频| 水蜜桃久久夜色精品一区的特点| 日本a级不卡| 狠狠久久伊人中文字幕| 久久国产电影| 免费成人性网站| 青青青国产精品| 欧美亚洲自偷自偷| 成人一二三区| 尹人成人综合网| 国产伦精品一区二区三区千人斩| 日韩综合一区| 好看不卡的中文字幕| 日韩中文一区二区| 久久wwww| 狠狠久久婷婷| 国产精品视频一区视频二区| 日韩精品第一区| 亚洲一区二区av| 美女视频免费精品| 国内精品99| 国产精品中文| 日韩视频中文| 啪啪国产精品| 亚洲欧洲一区二区天堂久久| 欧美成人高清| 肉色欧美久久久久久久免费看| 麻豆成人av在线| 国产农村妇女精品一二区| 欧美日韩国产一区二区在线观看| 日韩综合精品| 亚洲深夜av| 国产精区一区二区| 高清在线一区| 日韩毛片视频| 国产 日韩 欧美一区| 中文字幕成在线观看| 亚洲精品欧美| 国产精品嫩模av在线| 免费国产自久久久久三四区久久| 日韩va亚洲va欧美va久久| www成人在线视频| 国产欧美一区| 99在线精品免费视频九九视| 欧美国产视频| 天使萌一区二区三区免费观看| 狠狠久久伊人| 亚洲久久一区| 五月天av在线| 欧美日韩在线精品一区二区三区激情综合| 久久精品在线| 国产精品天天看天天狠|