电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

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

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

瀏覽:102日期: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)就是這么簡(jiǎn)單,后面我們會(huì)回顧一下 _compile 函數(shù)的原理,但是需求可不是這么簡(jiǎn)單,我們?nèi)绻谇岸谁h(huán)境動(dòng)態(tài)引入這段代碼呢?

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

首先我們先來(lái)回顧一下 node 中的模塊加載原理。

node Module 模塊加載原理

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

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

其實(shí)上面整個(gè)模塊系統(tǒng)的核心就是 Module 類(lèi)的 _compile 方法,我們直接來(lái)看 _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í)行過(guò)程我將其分為三步:

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

第一步即調(diào)用 Module 內(nèi)部的 wrapper 函數(shù)對(duì)模塊的原始內(nèi)容進(jìn)行封裝,我們先來(lái)看看 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 的作用域問(wèn)題,可以使每個(gè)模塊它自身的命名空間中執(zhí)行。在沒(méi)有模塊化方案的時(shí)候,我們一般會(huì)創(chuàng)建一個(gè)自執(zhí)行函數(shù)來(lái)避免變量污染:

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

所以這一步至關(guān)重要,首先 wrapper 函數(shù)就將模塊本身的代碼片段包裹在一個(gè)函數(shù)作用域內(nèi),并且將我們需要用到的對(duì)象作為參數(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 對(duì)象的上下文中編譯并執(zhí)行 code,最后返回結(jié)果。運(yùn)行中的代碼無(wú)法獲取本地作用域,但可以獲取當(dāng)前的 global 對(duì)象。

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ù)

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

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

所以看到這里你應(yīng)該會(huì)明白,我們?cè)谀K中拿到的 module,就是 Module 模塊的實(shí)例本身,我們直接調(diào)用的 exports 實(shí)際上是 module.exports 的引用,所以我們既可以使用 module.exports 也可以使用 exports 來(lái)導(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è)問(wèn)題,在瀏覽器環(huán)境是沒(méi)有 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í)行的代碼能夠訪問(wèn)閉包和全局作用域,這會(huì)導(dǎo)致被稱(chēng)為代碼注入 code injection 的安全隱患, eval 雖然好用,但是經(jīng)常被濫用,是 JavaScript 最臭名昭著的功能之一。

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

new Function()

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

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

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

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

但是這仍然不能解決訪問(wèn)全局作用域的問(wèn)題。

with 關(guān)鍵詞

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

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

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

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

Proxy

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

ES6 中提供了一個(gè) Proxy 函數(shù),它是訪問(wèn)對(duì)象前的一個(gè)攔截器,我們可以利用 Proxy 來(lái)攔截 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,它可以用來(lái)代表內(nèi)部語(yǔ)言行為。

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

所以我們需要對(duì) 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)為我們提供的各種工具類(lèi)和函數(shù)的,它只能作為一個(gè)沒(méi)有任何副作用的純函數(shù),當(dāng)我們想要使用某些全局變量或類(lèi)時(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è)簡(jiǎn)易的 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); } }

測(cè)試執(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í)行器的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: JavaScript
相關(guān)文章:
主站蜘蛛池模板: 圆盘鞋底注塑机_连帮鞋底成型注塑机-温州天钢机械有限公司 | 氧化锆纤维_1800度高温退火炉_1800度高温烧结炉-南京理工宇龙新材料股份有限公司 | 哈希余氯测定仪,分光光度计,ph在线监测仪,浊度测定仪,试剂-上海京灿精密机械有限公司 | 丝杆升降机-不锈钢丝杆升降机-非标定制丝杆升降机厂家-山东鑫光减速机有限公司 | 解放卡车|出口|济南重汽|报价大全|山东三维商贸有限公司 | 南京种植牙医院【官方挂号】_南京治疗种植牙医院那个好_南京看种植牙哪里好_南京茀莱堡口腔医院 尼龙PA610树脂,尼龙PA612树脂,尼龙PA1010树脂,透明尼龙-谷骐科技【官网】 | 高效复合碳源-多核碳源生产厂家-污水处理反硝化菌种一长隆科技库巴鲁 | Safety light curtain|Belt Sway Switches|Pull Rope Switch|ultrasonic flaw detector-Shandong Zhuoxin Machinery Co., Ltd | 组织研磨机-高通量组织研磨仪-实验室多样品组织研磨机-东方天净 传递窗_超净|洁净工作台_高效过滤器-传递窗厂家广州梓净公司 | 塑料熔指仪-塑料熔融指数仪-熔体流动速率试验机-广东宏拓仪器科技有限公司 | 等离子空气净化器_医用空气消毒机_空气净化消毒机_中央家用新风系统厂家_利安达官网 | 全自动实验室洗瓶机,移液管|培养皿|进样瓶清洗机,清洗剂-广州摩特伟希尔机械设备有限责任公司 | 青岛球场围网,青岛车间隔离网,青岛机器人围栏,青岛水源地围网,青岛围网,青岛隔离栅-青岛晟腾金属制品有限公司 | 伸缩器_伸缩接头_传力接头-巩义市润达管道设备制造有限公司 | 搜活动房网—活动房_集装箱活动房_集成房屋_活动房屋 | 杭州高温泵_热水泵_高温油泵|昆山奥兰克泵业制造有限公司 | 华禹护栏|锌钢护栏_阳台护栏_护栏厂家-华禹专注阳台护栏、楼梯栏杆、百叶窗、空调架、基坑护栏、道路护栏等锌钢护栏产品的生产销售。 | 肉嫩度仪-凝胶测试仪-国产质构仪-气味分析仪-上海保圣实业发展有限公司|总部 | 广东青藤环境科技有限公司-水质检测 | 哲力实业_专注汽车涂料汽车漆研发生产_汽车漆|修补油漆品牌厂家 长沙一级消防工程公司_智能化弱电_机电安装_亮化工程专业施工承包_湖南公共安全工程有限公司 | 粉末冶金注射成型厂家|MIM厂家|粉末冶金齿轮|MIM零件-深圳市新泰兴精密科技 | 挤奶设备过滤纸,牛奶过滤纸,挤奶机过滤袋-济南蓝贝尔工贸有限公司 | 齿轮减速马达一体式_蜗轮蜗杆减速机配电机-德国BOSERL齿轮减速电动机生产厂家 | 二手电脑回收_二手打印机回收_二手复印机回_硒鼓墨盒回收-广州益美二手电脑回收公司 | 艺术漆十大品牌_艺术涂料加盟代理_蒙太奇艺术涂料厂家品牌|艺术漆|微水泥|硅藻泥|乳胶漆 | 乳化沥青设备_改性沥青设备_沥青加温罐_德州市昊通路桥工程有限公司 | 斗式提升机,斗式提升机厂家-淄博宏建机械有限公司 | 我爱古诗词_古诗词名句赏析学习平台 | 济南侦探调查-济南调查取证-山东私家侦探-山东白豹调查咨询公司 密集架|电动密集架|移动密集架|黑龙江档案密集架-大量现货厂家销售 | 高扬程排污泵_隔膜泵_磁力泵_节能自吸离心水泵厂家-【上海博洋】 | DWS物流设备_扫码称重量方一体机_快递包裹分拣机_广东高臻智能装备有限公司 | hdpe土工膜-防渗膜-复合土工膜-长丝土工布价格-厂家直销「恒阳新材料」-山东恒阳新材料有限公司 ETFE膜结构_PTFE膜结构_空间钢结构_膜结构_张拉膜_浙江萬豪空间结构集团有限公司 | 磨煤机配件-高铬辊套-高铬衬板-立磨辊套-盐山县宏润电力设备有限公司 | 石家庄网站建设|石家庄网站制作|石家庄小程序开发|石家庄微信开发|网站建设公司|网站制作公司|微信小程序开发|手机APP开发|软件开发 | 水热合成反应釜-防爆高压消解罐-西安常仪仪器设备有限公司 | 大连海岛旅游网>>大连旅游,大连海岛游,旅游景点攻略,海岛旅游官网 | 爱德华真空泵油/罗茨泵维修,爱发科-比其尔产品供应东莞/杭州/上海等全国各地 | 杭州实验室尾气处理_实验台_实验室家具_杭州秋叶实验设备有限公司 | 网站建设-高端品牌网站设计制作一站式定制_杭州APP/微信小程序开发运营-鼎易科技 | 坏男孩影院-提供最新电影_动漫_综艺_电视剧_迅雷免费电影最新观看 | 新能源汽车电池软连接,铜铝复合膜柔性连接,电力母排-容发智能科技(无锡)有限公司 |