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
  

// 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
  
  

  // 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
    
// 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中不支持;

你可能感兴趣的:(Node:03.Node模块化开发)