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

您的位置:首頁技術文章
文章詳情頁

如何手動實現一個 JavaScript 模塊執行器

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

如果給你下面這樣一個代碼片段(動態獲取的代碼字符串),讓你在前端動態引入這個模塊并執行里面的函數,你會如何處理呢?

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

node 環境的執行

如果在 node 環境,我們可能會很快的想到使用 Module 模塊, Module 模塊中有一個私有函數 _compile,可以動態的加載一個模塊:

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

實現就是這么簡單,后面我們會回顧一下 _compile 函數的原理,但是需求可不是這么簡單,我們如果要在前端環境動態引入這段代碼呢?

嗯,你沒聽錯,最近正好碰到了這樣的需求,需要在前端和 Node 端抹平動態引入模塊的邏輯,好,下面我們來模仿 Module 模塊實現一個前端環境的 JavaScript 模塊執行器。

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

node Module 模塊加載原理

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

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

其實上面整個模塊系統的核心就是 Module 類的 _compile 方法,我們直接來看 _compile 的源碼:

Module.prototype._compile = function(content, filename) { // 去除 Shebang 代碼 content = internalModule.stripShebang(content); // 1.創建封裝函數 var wrapper = Module.wrap(content); // 2.在當前上下文編譯模塊的封裝函數代碼 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.運行模塊的封裝函數并傳入 module、exports、__dirname、__filename、require var result = compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname); return result; };

整個執行過程我將其分為三步:

創建封裝函數

第一步即調用 Module 內部的 wrapper 函數對模塊的原始內容進行封裝,我們先來看看 wrapper 函數的實現:

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

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

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

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

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

編譯封裝函數代碼

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

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

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

所以以上代碼執行后,就將代碼片段字符串編譯成了一個真正的可執行函數:

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

運行封裝函數

最后通過 call 來執行編譯得到的可執行函數,并傳入對應的對象。

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

所以看到這里你應該會明白,我們在模塊中拿到的 module,就是 Module 模塊的實例本身,我們直接調用的 exports 實際上是 module.exports 的引用,所以我們既可以使用 module.exports 也可以使用 exports 來導出一個模塊。

實現 Module 模塊

如果我們想在前端環境執行一個 CommonJS 模塊,那么我們只需要手動實現一個 Module 模塊就好了,重新梳理上面的流程,如果只考慮模塊代碼塊動態引入的邏輯,我們可以抽象出下面的代碼:

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); } }

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

實現瀏覽器沙箱

eval

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

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

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

new Function()

Function 構造器是 eval() 的一個替代方案。new Function(...args, ’funcBody’) 對傳入的 ’funcBody’ 字符串進行求值,并返回執行這段代碼的函數。

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

返回的 fn 是一個定義好的函數,最后一個參數為函數體。它和 eval 有兩點區別:

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

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

with 關鍵詞

如何手動實現一個 JavaScript 模塊執行器

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

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

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

Proxy

如何手動實現一個 JavaScript 模塊執行器

ES6 中提供了一個 Proxy 函數,它是訪問對象前的一個攔截器,我們可以利用 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 是一個著名的標記。一個著名的標記即是一個內置的 JavaScript Symbol,它可以用來代表內部語言行為。

Symbol.unscopables 定義了一個對象的 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); } }

全局變量白名單

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

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); } }

最終代碼:

好了,總結上面的代碼,我們就完成了一個簡易的 JavaScript 模塊執行器:

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); } }

測試執行效果:

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

以上就是如何手動實現一個 JavaScript 模塊執行器的詳細內容,更多關于JavaScript 模塊執行器的資料請關注好吧啦網其它相關文章!

標簽: JavaScript
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
久久只有精品| 中文字幕日本一区| 国产精品高潮呻吟久久久久| 国产精品天天看天天狠| 国产精品久久久久蜜臀| 免费欧美日韩| 国产成人在线中文字幕| 国产精品久久久久久久久免费高清| 亚洲精品97| 国产成人精品一区二区免费看京 | 蜜桃视频一区二区| 日韩一区二区三免费高清在线观看| 日韩高清三区| 99成人在线视频| 日本成人手机在线| 久久久久久久久丰满| 日韩精品福利一区二区三区| 免费不卡在线观看| 国产欧美一区二区三区米奇| 国产另类在线| 中文字幕日韩高清在线| 久久电影tv| 亚洲免费毛片| 国产欧美三级| 日本 国产 欧美色综合| 国产高清一区二区| 国产色播av在线| 国产亚洲第一伦理第一区| 国产91精品对白在线播放| 久久久久久久久99精品大| 精品黄色一级片| 日本不卡视频在线| 亚洲特级毛片| 国产99亚洲| 成人午夜亚洲| 国产日本亚洲| 日韩一区欧美二区| 免费视频一区二区三区在线观看| 一区二区国产在线观看| 玖玖精品视频| 深夜福利视频一区二区| 亚洲日韩中文字幕一区| 久久亚洲国产| 日韩成人精品一区| 国产精品白丝一区二区三区| 亚洲精品第一| 日韩精品一级二级| 电影91久久久| 麻豆国产91在线播放| 国产亚洲欧美日韩在线观看一区二区| 国产传媒在线观看| 国产精品久久免费视频| 午夜国产一区二区| 亚洲黄色免费av| 91嫩草精品| 日韩精品一二三四| 欧美13videosex性极品| 欧美专区一区| 激情综合婷婷| 视频在线观看91| 国产精品毛片一区二区三区| 国产精品免费看| 久久久亚洲一区| 欧美成人午夜| 久久精品国产网站| 久久久久久久久久久9不雅视频| 久久青青视频| 国产精品一区二区三区www| 国产精品高潮呻吟久久久久| 麻豆91精品视频| 吉吉日韩欧美| 97精品一区| 日韩精品欧美精品| 日韩在线黄色| 高清av不卡| 最新中文字幕在线播放 | 国产高清亚洲| 国产模特精品视频久久久久| 999精品一区| 亚洲精品一区二区妖精| 婷婷久久免费视频| 亚洲精品系列| 久久精品xxxxx| 成人一区不卡| 欧美亚洲综合视频| 麻豆国产精品777777在线| 97精品视频在线看| 久久国产电影| 日韩视频一二区| 国产精品亚洲综合在线观看| 久久精品国产99国产| 精品中国亚洲| 久久国产电影| 久久黄色影院| 成人羞羞在线观看网站| 国产66精品| 欧美精品高清| 999视频精品| 欧美日韩中文| 精品视频91| 激情婷婷欧美| 亚洲精品影视| 9久re热视频在线精品| 欧美日韩亚洲三区| 美女精品久久| 免费污视频在线一区| 亚洲午夜久久| 国产精品15p| 午夜精品影院| 国产欧美综合一区二区三区| 亚洲久久在线| 中文在线а√天堂 | 99成人在线视频| 男女性色大片免费观看一区二区| 国产黄色一区| 国产欧美大片| 欧美1区2区3区| 美日韩一区二区三区| 久久久久久黄| 老司机精品视频网| 国产精品成人自拍| 99视频一区| 欧美成人国产| 久久久国产精品网站| 久久婷婷av| 亚洲网址在线观看| 免费精品视频在线| 久久国产精品99国产| 福利精品在线| 爽爽淫人综合网网站| 久久精品福利| 老司机精品久久| 视频在线观看国产精品| 国产不卡精品在线| 五月激激激综合网色播| 99久精品视频在线观看视频| 久久久久亚洲| 日韩在线第七页| 国产亚洲一区二区三区啪| 国产69精品久久| 日韩精品a在线观看91| 91精品麻豆| 久久一二三区| 日本一区二区免费高清| 在线看片日韩| 亚洲一区资源| 福利一区视频| 日本不卡视频在线| 国产二区精品| 97人人精品| 欧美天堂亚洲电影院在线观看| 日韩欧美午夜| 国产另类在线| 欧美午夜不卡影院在线观看完整版免费| 久久国产尿小便嘘嘘| 国产99在线| 欧美在线精品一区| 亚洲一级淫片| 模特精品在线| 日韩网站中文字幕| 99xxxx成人网| 国产亚洲福利| 日产精品一区| 亚洲美女久久精品| av日韩中文| 日韩av福利| 欧美日韩亚洲国产精品| 99香蕉国产精品偷在线观看| 亚洲一级黄色| 丝袜脚交一区二区| 一二三区精品| 国产免费成人| 亚洲免费专区| 欧美日韩国产高清| 日本成人在线视频网站| 久久精品97| 岛国av在线网站| 蜜臀av亚洲一区中文字幕| 老司机精品视频在线播放| 91麻豆精品| 亚洲精品在线国产| 欧美国产专区| 先锋影音久久久| 国产国产精品| 午夜精品福利影院| 国产日韩精品视频一区二区三区| 日本欧美韩国一区三区| 久久99国产精品视频| 精品国产精品国产偷麻豆 | 国产精品红桃| 麻豆精品蜜桃视频网站| 国产精品日韩精品中文字幕| 91精品啪在线观看国产爱臀| 婷婷精品久久久久久久久久不卡| 69堂精品视频在线播放| 久久99高清| 亚洲精品伊人| 久久99蜜桃| 97久久亚洲| 国产欧美一区二区三区精品酒店|