前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手摸手带你撸一个CommonJs规范

手摸手带你撸一个CommonJs规范

作者头像
Careteen
发布2022-02-14 16:14:45
2640
发布2022-02-14 16:14:45
举报
文章被收录于专栏:源码揭秘源码揭秘

Node系列-上一节事件循环详解

目录

  • 为什么会有模块化
    • 防止代码重名 变量污染全局
    • 太长,使用不爽
  • 怎么实现模块化的思路
    • 闭包
  • 以前的废弃了 AMD、CMD
  • 现代使用Node:CommonJs、es6:esModule
    • CommonJs:node主用
      • 如何自己撸一个
    • esModule
      • 待完善

为什么需要模块化

随着前端的发展,页面特效、交互都在前端层面实现,前端的代码逻辑复杂度增加。 写代码不可能一把唆,都写入一个文件当中,这样可读性不强也不利于后期的维护。 需要利用模块的思想将代码进行划分,使其职责单一且可替换。

并且需要防止变量污染全局,防止变量的重命名。

模块化发展历程

简单提一下模块化从概念引入到今天的一个发展历程。

直接定义依赖(1999)

直接定义依赖和现在流行的CommonJs相似,不同点在于CommonJs中定义一个文件即一个模块,而它则可以再任何文件中定义模块,模块和文件不关联。Dojo的思想。

namespace模式(2002)

使用命名空间主要是为了解决变量命名冲突的问题。YUI的思想。

闭包模式(2003)

引入闭包模式解决了变量污染的问题。

注释定义依赖(2006)

在文件头加上一些注释标记该文件的依赖,然后在编译时解析引入依赖。

依赖注入(2009)

Angular中引入了其思想。

CommonJs(2009)

现代流行的模块化解决方案,从Node端再引入到前端。也是本文着重讲解的一个知识点。

AMD(2009)

RequireJs的思想,核心是依赖前置。

CMD(已废弃)

SeaJs的思想,核心是按需加载。不过玉伯现在没维护了,相当于已经废弃了。

UMD(2011)

兼容了CommonJsAMD,核心思想是如果在CommonJs环境下,即存在module.exports,不存在define时将函数执行结果交给module.exports实现CommonJs;否则使用AMD环境的define

Es6 Module(2015)

现代的模块化方案,Node9+支持,可以通过启动加上flag--experimental-modules使用。

在浏览器端可以通过<script type="module" src="xxx"></script>的方式使用。

如何实现一个CommonJs规范

本文的重点是解析CommonJs规范的核心思想。

以下涉及到的代码均在仓库中,感兴趣的可以前往调试

断点调试作为切入点

首先我们可以先通过断点调试require(path/to/file)方法作为切入点了解其中机制。

准备了两个文件1.case.jsrequire.js

1.case.js 文件内容

代码语言:javascript
复制
let info = require('./1.require.js')
console.log(info)

require.js 文件内容

代码语言:javascript
复制
module.exports = {
  name: 'careteen',
  age: '23'
}

1、点击进入函数内调试两次即可进入

2、进入到了module.js源文件

里面提供了一个modModule类的实例,并且提供一个require方法,进入方法内查看

3、首先做了一些路径的断言,然后返回Module._load(),将当前文件名传入,进入方法内查看

4、里面有缓存的功能,由于我们是第一次加载这个文件,没有缓存,所以直接跳过

491行new了一个Module实例,在500行传入tryModuleLoad方法,再进入此方法内查看

5、做了一层try...catch,实际调用实例上的load方法,再进入此方法内查看

6、先会判断是否加载过,防止重复加载。

1标志位上会对文件名处理,我们可以再调试控制台中输入this.paths查看最终的处理结果

实际上就是对查找做了一层层的判断,如果当前目录没有node_modules,去上一级目录查找,如此递归.具体实现如下图

2标志位会取到文件的扩展名根据类别做处理,可进入Module._extensions()方法内查看

7、使用fs.readFileSync()方法同步读取到文件内容

然后再调用实例的_compile方法,进入方法内查看

8、1标志位会将读取的内容调用静态方法wrap进行包裹

包裹方法如下图

包裹后的内容如下

从以上可看出相当于使用了闭包,匿名函数中传入在module实例上的一些属性exports/require/module...

以便在require的时候能读取到。

2标志位使用了vm内置模块沙箱式运行代码。

其功能相当于eval('console.log(name)')new Function('console.log(name)')

9、以上步骤就模块化的大致思路,跳出以上所有方法内部。

10、对于文件类型处理还需考虑另外两种情况,实现原理类似。

只不过.json文件读取后会被转成一个JSON对象

实现一个简易的模块化

跟着上面的思路我们大致了解到了模块化的几个核心点

  • 缓存,提高读取性能
  • 处理文件的查找规则(下面将会给出详细查找规则)
    • 内置模块如何处理
    • 第三方模块如何处理
    • 文件模块如何处理
  • 通过fs.readFileSync()读取文件内容
  • 处理.js/json/node三种文件类型
    • .js通过内置模块vm使其沙箱式执行文件内容
    • .json读取后转为JSON对象
    • .node是一个二进制的C++文件,是可以直接运行的

下面针对以上各个核心点一一突破

如何实现缓存

挂一个缓存对象即可

处理文件的查找规则

一图胜千言

处理三种文件类型

.js文件左一层包裹

.json读取后转为JSON对象

简易实现代码见仓库

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-12-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 为什么需要模块化
  • 模块化发展历程
    • 直接定义依赖(1999)
      • namespace模式(2002)
        • 闭包模式(2003)
          • 注释定义依赖(2006)
            • 依赖注入(2009)
              • CommonJs(2009)
                • AMD(2009)
                  • CMD(已废弃)
                    • UMD(2011)
                      • Es6 Module(2015)
                      • 如何实现一个CommonJs规范
                        • 断点调试作为切入点
                        • 实现一个简易的模块化
                          • 如何实现缓存
                            • 处理文件的查找规则
                              • 处理三种文件类型
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档