node采用CommonJS规范。每个文件都是一个单独的模块,具有自己独立的范围、变量和方法。这些对其他模块是不可见的。CommonJS规范规定,在每个模块内部,module代表当前模块。module是一个对象,它有一个exports属性,也就是module.exports。该属性是对外接口,将需要导出的内容放在该属性上。external可以通过require导入。需要导入的是导出中的内容。本文手动实现如下require方法,通过手写require方法获取另一个文件中exports中的内容。首先我们来看一下node环境中标准的require方法是如何引用模块的。新建一个文件夹,在文件夹中新建一个b.js。通过module.exports导出内容。b.js:letstr='b.js导出内容';模块.exports=str;然后创建另一个文件my-require.js。在my-require.js中的b.js中引入str。my-require.js:letstr=require('./b.js');控制台日志(海峡);运行代码,可以看到。打印出b的内容:b.js导出的内容。上面是参考了标准CommonJS中的require,然后手动实现:先梳理一下逻辑,require函数中传入的参数是路径,如果有路径加上node的fs模块,我们可以读取文件。那么有了这个文件的内容,从这个文件中得到exports就不难了。上代码:letpath=require('path');letfs=require('fs');letvm=require('vm');/**定义自己的require方法myrequire()*/functionmyrequire(modulePath){让absPath=path.resolve(__dirname,modulePath);函数查找(absPath){try{fs.accessSync(absPath);返回绝对路径;}catch(e){console.log(e);}}absPath=find(absPath);让模块=新模块(absPath);加载模块(模块);returnmodule.exports;}functionModule(id){this.id=id;this.exports={}}functionloadModule(module){letextension=path.extname(module.id);Module._extensions[extension](module);}Module._extensions={'.js'(module){letcontent=fs.readFileSync(module.id,'utf8');让fnStr=Module.wrapper[0]+content+Module.wrapper[1];让fn=vm.runInThisContext(fnStr);fn.call(module.exports,module.exports,module,myrequire);}}Module.wrapper=['(函数(导出,module,require,__dirname,__dirname){','})'];letstr=myrequire('./b.js');console.log(str);阅读顺序先介绍路径fs和vm模块。不用说,path和fs都懂。vm模块是node的核心模块。官方对核心功能的解释是:vm模块提供了在V8虚拟机上下文中编译和运行代码的API。vm模块不是安全机制。不要用它来运行不受信任的代码。这些文档中使用的术语“沙箱”只是指一个单独的上下文,并不提供任何安全保证。意思大概是:vm可以利用v8的VirtualMachinecontext来动态编译和执行代码,代码的执行上下文与当前进程是隔离的,但是这里的隔离并不是绝对安全的,不完全等同于浏览器的沙箱环境.其实这篇文章中的vm模块的作用就是执行字符串代码,好理解。首先,定义了一个myrequire方法。此方法传递一个相对路径。myrequire方法的第一步将相对路径转换为绝对路径。然后使用find方法检查路径是否存在。接下来通过构造函数Module传入绝对路径,创建新的实例模块。构造函数Module传入路径id,内部定义属性exports={}。该属性是文件导出的属性。然后通过loadModule方法传入实例模块加载文件。在loadModule方法中,首先获取文件名后缀.js。将文件名后缀.js传递给Module._extensions。在Module._extensions对象中,通过文件扩展名.js找到文件类型的解析方式。并传入实例模块。该方法中通过module.id路径和fs模块获取文件内容。注意下一步。(function(exports,modules,require,__dirname,__filename){})函数用于在文件内容之外包裹一层。这样做的目的是为了后面执行函数,在module.exports中获取导出的内容。但是我们刚才通过fs读取的文件内容只是一个字符串,用空函数包裹起来,还是一个字符串。下一步是使用vm模块。该模块可以执行字符串代码。通过vm.runInthisContext()方法,传入刚刚得到的字符串。至此,就得到了可以执行的方法fn。然后下一步就是执行方法fn。执行fn,传入刚才的参数。请注意,当前的实现是module.exports。这样才能获取到module.exports中的内容。最后,在myrequire的最后,返回exports的内容。返回模块。出口。好了,接下来就是验证效果了。右键代码运行,或者在浏览器中打开。可以看出b.js导出的内容已经获取到文件b.js中的内容并打印出来了。好了,现在我们实现了最简单的require。然而,我们并不满足于此。因为require方法还是有一些问题的。比如json文件还不能引用,没有后缀的不考虑。接下来继续完善myrequire方法:letpath=require('path');letfs=require('fs');letvm=require('vm');/**定义自己的require方法myrequire()*/函数myrequire(modulePath){letabsPath=path.resolve(__dirname,modulePath);让ext_name=Object.keys(Module._extensions);让索引=0;让old_absPath=absPath;函数查找(absPath){try{fs.accessSync(absPath);返回绝对路径;}catch(e){让ext=ext_name[index++];让newPath=old_absPath+ext;返回查找(新路径);}}absPath=find(absPath);让模块=新模块(absPath);加载模块(模块);returnmodule.exports;}functionModule(id){this.id=id;this.exports={}}functionloadModule(module){letextension=path.extname(module.id);Module._extensions[extension](module);}Module._extensions={'.js'(module){letcontent=fs.readFileSync(module.id,'utf8');让fnStr=Module.wrapper[0]+content+Module.wrapper[1];让fn=vm.runInThisContext(fnStr);fn.call(module.exports,module.exports,module,myrequire);},'.json'(module){letcontent=fs.readFileSync(module.id,'utf8');module.exports=内容;}}Module.wrapper=['(function(exports,module,require,__dirname,__dirname){','})'];letstr=myrequire('./b');console.log(str);console.log(myrequire('./a'));在myrequire方法的第二行,先获取Module._extensions中的所有后缀(目前是.js和.json),声明一个下标索引,最后保存路径old_absPath,在find方法中,如果用户不写文件后缀,后缀会自动拼接。循环查找,直到找到或者最后没有找到。在Module._extensions中添加了一个object.json的方法。这种方法比较简单。通过fs读取文件,将文件内容放入module.exports中。好了,来看看效果:可以看到b.js{"name":"contenttobeimported"}导出的内容。正常获取b.js中的内容,同时读取a.json中的内容。至此,我们已经实现了CommonJS中的require方法。写文章不容易,喜欢就??点一个吧?谢谢~
