模块化-实现一个简单的CommonJS

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、需求:首先CommonJS中我们主要实现的内容是:

1.模块加载器: 解析文件地址,通过node,这次我们直接给一段代码传入,先不写解析部分

2.模块解析器: 执行文件内容

二、代码

1.创建一个class,定义传参和变量等

javascript class Module { // 传入模块名称和文件内容(要执行的代码块) constructor(moduleName, source) { this.export = {}; // 定义模块要返回的export this.moduleName = moduleName; this.source = source; } }

2.Require

》创建模块
》执行文件内容
》返回export

```javascript require = (moduleName,source) => { // 创建一个模块 const module = new Module(moduleName, source);

// 执行文件得到结果
const exports = compile(module, source);

// 通过require返回结果
return exports;

} ```

》缓存

```javascript class Module { // 传入模块名称和文件内容(要执行的代码块) constructor(moduleName, source) { this.export = {}; // 定义模块要返回的export this.moduleName = moduleName; this.source = source; this.$cacheModule = new Map(); // 定义一个缓存变量 } require = (moduleName,source) => { // 缓存中有这个模块的数据的话就返回缓存的exports if($cacheModule.has(moduleName)){ return $cacheModule.get(moduleName).exports; } // 创建一个模块 const module = new Module(moduleName, source);

// 执行文件得到结果
const exports = compile(module, source);

// 把module存入缓存
this.$cacheModule.set(moduleName, module);

// 通过require返回结果
return exports;

} } ```

》IIFE

声明完之后便直接执行的函数,这类函数通常是一次性使用的,因此没必要给这类函数命名,直接让它执行就好了 IIFE的作用就是防止变量全局污染,以及保证内部变量的安全 他是通过模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,所以 ( function(){…} )() 内部定义的变量不会和外部的变量发生冲突,俗称“匿名包裹器”或“命名空间”

通过IIFE让内部代码不会污染全局,但在IIFE中能访问到全局变量。 javascript (function(arg1,arg2){...})(a,b)

javascript $wrap = (code) => { // 这里传入的参数就是想以后抛出到文件模块中用的接口 return `function(module, exports, require){${code}}` }

》然后现在就是这个code到底怎么执行了

这里我们要创建一个沙箱环境执行code, 因为我们要保证code执行时:1.不能访问闭包的变量 ,2.不能访问全局的变量 ,3.只能访问我们传入的变量 这里我们通过new Function() 不能访问闭包,

javascript const func1 = () => { const a = 123; return function() { const b = 456; console.log(a,b,window); // 123,456,window对象 const func = new Function('obj', 'console.log(obj.a + obj.b, window);'+ // 579, window对象 'console.log(a,b)' // a,b都不能访问(即不能访问) ); return func({a:123,b:456}); } } func1()() 通过with()包裹的对象,会被放到原型链的顶部,而且底层是通过 in 操作符判断的.,所有变量都会通过in从传入的对象中去取,但也能取到全局变量。

javascript const obj = {a: 123, b: 456} with(obj){ console.log(obj) // {a: 123, b: 456} console.log(a+b) // 579 } 通过proxy去拦截with的in操作符,让with在取变量的过程中(底层),所有除了白名单以外的变量都通过in操作,如果没有就返回underfined

javascript // 代理 const proxiedObject = new Proxy(sandbox, { // 专门处理 in 操作符的 has(target, key) { if (!whiteList.includes(key)) { return true; } }, get(target, key, receiver) { if (key === Symbol.unscopables) { return void 0; } return Reflect.get(target, key, receiver); } });

所以一个简单的沙箱如下

```javascript $runInThisContext = (code,whiteList=['console']) => {

const func = new Function('sandbox', with(sandbox){${code}});

return function(sandbox){ if(!sandbox || typeof sandbox !== 'object') { throw error("sandbox parameter must be a object") } const proxiedObject = new Proxy(sandbox, { has(target,key) { if(!whiteList.includes(key)) { return true; } }, get(target, key, receiver) { if(key === Symbol.unscopables) { return void 0; } return Reflect.get(target, key, receiver) } }) return func(proxiedObject) } } ```

》最后在沙箱环境下传入code,执行代码

javascript const compiler = this.$runInThisContext(this.$wrap(source))({}); compiler.call(module, module, module.exports, this.require) return module.exports;

3.验证

```javascript const m = new Module();

// a.js const aSourceCode = const b = require('b.js', 'const a = require("a.js"); console.log("module a:", a); exports.action = function() {console.log("excute action from B successfully!")}'); b.action(); exports.action = function() { console.log("excute action from A successfully!") } m.require('a.js',aSourceCode) // module a: {} // excute action from B successfully! ```

4.完整代码

```javascript class Module { constructor(moduleName, source) { this.exports = {}; this.moduleName = moduleName; this.$cacheModule = new Map(); this.$source = source; }

/** * @description: require方法 * @param {string} moduleName 其实就是路径信息 * @param {string} source 文件的源代码,因为省略了加载器解析路径得到源代码的过程 * @return {object} export出去的对象 */ require = (moduleName, source) => { if (this.$cacheModule.has(moduleName)) { return this.$cacheModule.get(moduleName).exports; }

// 创建模块
const module = new Module(moduleName, source);

// 执行文件
const exports = this.compile(module, source);

// 缓存
this.$cacheModule.set(moduleName, module);

return exports;

}; /* * @description: IIFE 拼一个闭包 * @param {string} code 代码字符串 * @return {} */ $wrap = (code) => { const wrapper = [ 'return (function(module,exports,require) {', '\n})' ] return wrapper[0] + code + wrapper[1] }

/* * @description: 简单实现一个能在浏览器跑的解释器 vm.runInThisContext * @param {string} code * @return {} */ $runInThisContext = (code,whiteList=['console']) => {

const func = new Function('sandbox', with(sandbox){${code}});

return function(sandbox){ if(!sandbox || typeof sandbox !== 'object') { throw error("sandbox parameter must be a object") } const proxiedObject = new Proxy(sandbox, { has(target,key) { if(!whiteList.includes(key)) { return true; } }, get(target, key, receiver) { if(key === Symbol.unscopables) { return void 0; } return Reflect.get(target, key, receiver) }

})
return func(proxiedObject)

} }

/* * @description: 执行文件 * @param {} * @return {*} */ compile = (module, source) => { const compiler = this.$runInThisContext(this.$wrap(source))({}); compiler.call(module, module, module.exports, this.require) return module.exports; }; }

const m = new Module();

// a.js const aSourceCode = const b = require('b.js', 'const a = require("a.js"); console.log("module a:", a); exports.action = function() {console.log("excute action from B successfully!")}'); b.action(); exports.action = function() { console.log("excute action from A successfully!") } m.require('a.js',code)

```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值