Node:03.Node模块化开发

require细节

require是个函数,可以帮助我们引入一个文件(模块)中导出的对象。


require查找规则

require的查找规则是什么:完整的文档很多,这里说下常见的规则。

  • 情况一:X是一个核心模块,如path,http... 则直接返回核心模块,并且停止查找。
  • 情况二:X是以./或../或/(根目录)开头,说明是本地文件,会从本地目录开始查找。
    第一步:将X当作一个文件在对应的目录下查找对应的文件。
    如果没有后缀名,会按如下顺序查找:文件X,X.js文件,X.json文件,X.node文件。

第二步:没有找到对应的文件,将X作为一个目录。
查找目录下的index文件,查找X/index.js,X/index.json和X/index.node。

如果都没有找到,那就报错:not found。

情况三:直接是一个X(没有路径),并且X不是核心模块。
先查找X是不是核心模块,若无,则在paths里面按顺序查询node_modules。
这个paths是module对象的一个属性module.paths。

查找paths

这里不是说我们不需要加后缀名,而是node/webpack帮我们做了一个拼接,所以不加也可以生效。

一.模块的加载过程

  1. 模块在被第一次引入时,模块中的js代码会被运行一次。实际上就是按当前文件的代码顺序来执行。
  2. 模块被多次引入时会被缓存,最终只加载(运行)一次。
  • 每个模块对象的module都有一个属性叫loaded。
  • loaded为false表示还未加载,为true表示已被加载。
// bar.js
let name = "wwq";
console.log(name);
name = "11111";
console.log(name);
// foo.js
require('./bar');
// main.js
require('./foo');
console.log('main中被执行');
// 结果
wwq
11111
main中被执行
  1. 循环引用的加载顺序是什么


    循环引用:图结构

    实际上就是图结构的遍历,这里是深度遍历,也就是广度优先(数据结构基础)。

二.cjs规范的缺点

  1. cjs加载模块是同步的;
  • 同步也就是说只有对应的模块加载完毕,当前模块中的内容才能被运行。在服务器的时候影响不大,但在浏览器中就有影响了。
  • 浏览器加载js文件需要先从服务器将文件下载下来,之后再加在运行。那么采用同步也就意味着后续的js代码无法正常运动,即使是一些简单的dom操作。
  • 所以在浏览器中我们通常不用cjs,但在webpack中使用cjs是另外的事情,因为它会将我们的代码转成浏览器可以直接执行的代码。
  • 早期为了在浏览器中使用模块化,通常会采用AMD或CMD,但是现在浏览器已经支持了ESM,另一方面借助于webpack等工具可以实现对cjs/esm代码的转换。AMD和CMD使用已经很少了,但也做个实例。

三.AMD规范(了解一下

AMD是应用于浏览器的一种模块化规范:

  • 是Asynchronous Module Definition(异步模块定义)的缩写。
  • 采用的是异步加载模块。
  • 事实上AMD要早于cjs,但是现在使用已经很少了。
    AMD的实现,常用的库有require.js,curl.js。


    目录结构(lib中有require.js源代码)
// 代码结构
// index.html
  <script src="./lib/require.js" data-main="./index.js"></script>

// index.js
(function () {
  require.config({
    baseUrl: "",
    paths: {
      bar: "./modules/bar",
      foo: "./modules/foo",
    },
  });
  require(["foo"], function (foo) {});
})();

// modules/bar.js
define(function () {
  const name = "coderwwq";
  const age = 18;
  const sayHello = function (name) {
    console.log("你好", name);
  };
  return {
    name,
    age,
    sayHello,
  };
});

// modules/foo.js
define(["bar"], function (bar) {
  console.log(bar.name);
  console.log(bar.age);
  bar.sayHello("12312wqwq");
});

四.CMD规范(了解一下

CMD也是应用于浏览器的一种模块化规范:

  • 是Common Module Definition(异步模块定义)的缩写。
  • 采用的是异步加载模块,拥有cjs的有点,现在使用也很少了。
  • 优秀的实现方案,SeaJS
  <script src="./lib/sea.js"></script>
  <script>
    // 核心代码
    seajs.use("./index.js")
  </script>

  // index.js
  define(function(require, exports, module) {
    const foo = require('./modules/foo');
    console.log(foo.name);
    console.log(foo.age);
    foo.sayHello('123123');
  })

  // ./modules/foo.js
  define(function(require, exports, module) {
    const name="wwq";
    const age = 20;
    const sayHello = function(name) {
      console.log("你好", name);
    }
    module.exports = {
      name,
      age,
      sayHello,
    }
  })

五.ES Module规范

ES Module和CommonJS的模块化有一些不同之处。

  • ESM使用了import和export关键字(解析的时候,交给js引擎对关键字进行解析)。
  • 另一方面采用编译期的静态分析,并且也加入了动态引用的方式。
  • 使用ES Module将会自动开启严格模式,use strict。
浏览器演示ES6模块化开发

type="module"表示这个模块是异步加载.

// index.html
    <script type="module" src="./index.js"></script>
// index.js
   console.log('hello 123');
报错

但是这些么写的话会有这个跨域的报错。
原因是:出于js模块安全性需要,通过本地加载html文件(比如file://,不支持file协议)时,会出现CORS错误。
解决方法:需要通过一个服务器来测试。在vscode里面有个插件叫「live server」,这个插件会开启一个本地服务,并且对我们的代码进行热更新。

常见导出方式有三种
// 1. 方式一:
export const name = "wwq";
export const age = 20;
export const sayHello = function (name) {
  console.log("你好", name);
};

// 2. 方式二:大括号内统一导出,但不是一个对象,
// { 大括号内放置要导出的变量的引用列表 }
export { name, age, sayHello };

// 3. 方式三:{}导出时,可以给变量起别名
export { name as fname, age as fAge, sayHello as fSayhello };
常见导入方式有三种
// 常见的导入方式
// 方式一:普通导入
import { name, age, sayHello } from "./modules/foo.js";

// 方式二:导出变量之后可以起别名
import {
  fName as wName,
  fAge as wAge,
  fSayHello as wSayHello,
} from "./modules/foo";

// 方式三:* as foo
import * as foo from "./modules/foo.js";
Export和Import结合使用

将希望暴露的所有接口放到一个文件中, 方便指定统一的接口规范, 也方便阅读.

// index.js
export { sum as barSum, reduce as barReduce } from './foo.js'
// foo.js
export { sum, reduce }
default用法

上面的一些代码示例是具名导出(named exports).

  • 这是因为在export的时候指定了名字.
  • 通过default, 在导入的时候不需要使用{}, 并且可以自己来指定名字.
  • 也方便我们和现有的cjs等规范互相操作.
  • 一个模块里面只能有一个.
// 导出
export default function() { 
  console.log('格式化');
}
// 导入
import format from './modules/foo.js';
format();
补充
  • import加载模块的时候, 不能放入逻辑代码中(esm在被js引擎解析的时候, 必须确定依赖关系, 属于解析时加载, 这个和cjs的require要区分开来)
  • 因为require本质是一个函数, 属于运行时(webpack环境下).
  let flag = true;
  if(flag) {
    import format from './modules/foo.js';
  }

在纯ES Module环境下可以使用import()函数, 属于异步加载

  • 大多数脚手架cli是基于webpack的, 所以可以使用import()函数, 在使用这个函数时, webpack会对相应的模块单独打包到一个js文件中, 有助于首屏渲染.
  const promise = import('./modules/foo.js').then(res => {
    clg(res.name);
    clg(res.age);
  }

下面表示了esm的引用图, 具体就不细说了, 有疑问的同学可以留言.


image.png
来自coderwhy

五.Node对ESModule的支持

image.png

该文件是js文件, 默认情况下一个js文件就是一个模块, 但我们这里指的模块是cjs模块, 并不是es6的模块, 所以不被当成一个esmodule.
上图Warning提示我们需要在package.json里添加“type”: “module”, 或者使用.mjs作为文件的拓展名.
但是加了之后还不可以运行, 这回提示我们没有找到模块. 这是因为之前导入的时候拓展名是foo.js而不是foo.mjs, 改正之后就可以正常运行了.


image.png

五.Node对ESModule的支持

结论一:通常情况下,cjs不能还在esm

  • cjs是同步加载的,但esm必须经过静态分析等,无法在这个时候执行js代码;
  • 但这个不绝对,某些平台在实现的时候可以对代码进行针对性的解析,可能会支持;
  • Node中不支持;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容