深入node学习2 实现commonjs
Posted lin-fighting
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入node学习2 实现commonjs相关的知识,希望对你有一定的参考价值。
node中的模块
node主要有两种,esmodule,和commonjs,经过webpack编译,esmodule会被打包成commonjs的模式。
esmodule是静态模块,可以tree-shaking,而commonjs是动态的,无法tree-shaking。如
a.js
const path = require('path')
function A ()
moduel.exports =
b.js
if(true)
require('./a.js')
如上,commonjs支持这种写法,这种写法只能在运行的时候才知道if是否正确,所以rqeuire里面的东西webpack事先不知道,所以无法tree-shaking。而esmodule可以在静态编译,所以他知道里面的东西,也知道里面哪些东西可以用,哪些东西不能用。所以可以tree-shaking。
模块规范
- 1 每个文件都是一个模块,每个模块外面有一个函数包裹。
- 2 文件需要被别人所使用,需要导出module.exports = xxx
- 3 如果需要使用别人,就需要使用require
模块的分裂
- 1核心模块,内置模块(node自带的,如fs)
- 2 第三方模块(如co)
- 3 文件模块,别人引用的时候需要通过相对路劲或者绝对路劲来引用。
内置模块
- fs模块有readFile和readFileSync,一个是同步,一个是异步,当代码执行的时候,尽量少用同步,因为会阻塞。
- 而require内部就是通过readFileSync实现文件的同步读写的。
而且readFIleSync读取文件不存在的时候会发生异常。所以fs提供了fs.existsSync来判断存不存在,同步的。 - path是常见的Node内置模块,用来处理路劲,如path.resolve()
console.log(path.resolve('a','b','c'))
//解析绝对路劲,解析默认是采用了process.cwd(),来拼接上参数。
所以一般加上__dirname,__dirname获取当前执行文件所在的目录。
- process.cwd()遇到路劲’/'会回到根目录。
- path.join仅仅只会拼接路劲。
- path.extname(),取扩展名,.js .jsx这些
- path.basename(a.js, .js), 取文件名,得到a
- path.relative(‘a/b/c’, a) 得到…/…/,根据路劲获取相对路劲
- path.dirname(’/a/b/c’),取c文件的父路劲。__dirname的实现就是path.dirname
字符串如何变成js执行。
1。 eval,受执行环境影响
2. new Function()" 模板引擎的实现原理",不会受环境影响
new Function('console.log(1)')
new Function最后一个参数就是函数体。
这样需要给字符串包装一层函数才能实现。
node中实现了一个模块’vm’!!
Function只能访问自己的变量和全局变量。但还是可能污染。
而node中的vm是不受影响的,沙箱环境。(快照(执行前记录信息,执行后还原信息))(proxy实现)
vm
Vm提供runinThisContex,只在全局一个上下文中执行。runinThisContext不会产生函数。
require的实现
- 1 读取文件
- 2 读取文件后将里面的内容包装成一个函数
- 3 通过runInThiwContext将他变成Js语法。
require就相当于把q文件的内容包装到iffe函数里面去。
调式源码,node的require的实现大致分为了九步 - 1 require方法-》调用Module.prototype.require方法
- 2 Module._load 加载模块
- 3 Module.resolveFilename方法就是把路劲变成绝对路劲,加上后缀名,.js .json方便缓存
- 4 new Module拿到绝对路径创建一个模块
- 5 module.load对模块进行加载
- 6 根据文件后缀Module.extensions[’.js’]去做策略加载
- 7 采用同步读取文件
- 8 增加一个函数的壳子,并且让函数执行,通过vm.runInThisContext,让module.exports作为this
- 9 用户默认拿到module.exports返回结果
- 最终返回exports对象。
实现:
思路:首先要通过传入的路劲获取一个绝对路劲并且加上后缀名。接着创建一个模块module,然后缓存起来。然后通过module.load去同步加载模块,并给module.exports赋值,最后导出即可。
实现:
获取绝对路劲
实现resolveFilename先,
先判断传入的路劲是否存在,不存在加上后缀名后再判断。返回一个带后缀的绝对路劲。
接着创建module实例,然后缓存起来,再然后直接load加载模块。
load主要通过策略模式,通过不同的后缀名调用不同的办法。
先看json的
如果文件名为json,就同步读取文件内容并JSON.pars解析,再给module.exports赋值。
最后返回Module.exports.
这样就完成对json的require。接着看js的
js的稍微复杂一点,因为读取文件内容后,比如读取的文件内容是
需要外层包一层函数,通过字符串包一层函数,然后通过vm.runInThisContext在全局上下文中执行这个字符串,也就是定义函数。这个函数大概长这样
const fn = function(exports, module, __dirname, __filename)
//这里的内容是读取到的
const a = 1
module.exports = a //对module.exports赋值
然后在执行fn.call()将module等对应参数传入。这样module实例上的exprots属性就有值了,再return module.exports就完成了。
并且缓存效果也完成了。
这样简单的Commonjs规范就完成了。
require文件模块的查找规范
- 判断路劲是不是核心模块,再看下是不是第三方,不是的话就继续往下判断。
- 默认查找同名文件,如果没找到尝试添加查找.js和.json文件
- 如果没有就找同名文件夹(当成一个包使用),同名文件夹找到了,先查找package.json文件,有的话就根据main查找文件,没有package.json就查找index.js文件,如果index.js文件还没有就报错。
第三方模块
引用也是没有相对路劲。
代码中的第三方模块
- 1 首先默认会往上查找,查找nde_module下同名的文件夹,同名文件夹找到了,先查找package.json文件,有的话就根据main查找文件,没有package.json就查找index.js文件,如果index.js文件还没有就报错。
- 2 如果没找到,就继续查找上级的node_module,可以通过哦module.paths获取查找的路劲。
会沿着这个路劲一直向上找,没找到就报错。
全局模块
全局安装,安装到电脑中的npm下。比如全局下命令行执行node -v,npm安装的全局模块在npm下生成了一个快捷方式,只能在命令行中使用,执行对应的文件。(尽量不要使用cnpm,安装时无法锁定版本,会出现很多问题。)
实现全局模块使用
步骤: 1 配置package.json中的bin
2添加命令 #! usr/bin/env node 表示用node执行此文件
3 执行npm link连接。
运行npm link让他映射到npm全局。
这样运行gm就会打印aaa了。
本地安装模块
依赖关系(开发依赖,生成依赖,同等依赖),打包依赖,可选依赖。
- node_module下的.bin文件夹意味着你安装的一些模块可以在命令行运行。
- 如npx webpack,就会在找到当前node_module下的bin目录,如果模块不存在会先安装再使用,使用后可以自动删除。
- 使用npm run xxx的时候,默认是在执行之前,将环境变量添加到全局下,所以可以使用,但是命令执行完毕后会删除对应的path。
以上是关于深入node学习2 实现commonjs的主要内容,如果未能解决你的问题,请参考以下文章