Node加载

概述

Node 对 ES6模块的处理比较麻烦,因为它有自己的 CommonJS模块格式,与 ES6模块格式是不兼容的。目前的解决方案是,将两者分开,ES6模块和 CommonJS 采用各自的加载方案。

Node 要求 ES6模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。require命令不能加载.mjs文件,会报错,只有import命令才可以加载.mjs文件。反过来,.mjs文件里面也不能使用require命令,必须使用import

目前,这项功能还在试验阶段。安装 Node v8.5.0 或以上版本,要用--experimental-modules参数才能打开该功能。

  1. $ node --experimental-modules my-app.mjs

为了与浏览器的import加载规则相同,Node 的.mjs文件支持 URL 路径。

  1. import './foo?query=1'; // 加载 ./foo 传入参数 ?query=1

上面代码中,脚本路径带有参数?query=1,Node 会按 URL 规则解读。同一个脚本只要参数不同,就会被加载多次,并且保存成不同的缓存。由于这个原因,只要文件名中含有:%#?等特殊字符,最好对这些字符进行转义。

目前,Node 的import命令只支持加载本地模块(file:协议),不支持加载远程模块。

如果模块名不含路径,那么import命令会去node_modules目录寻找这个模块。

  1. import 'baz';
  2. import 'abc/123';

如果模块名包含路径,那么import命令会按照路径去寻找这个名字的脚本文件。

  1. import 'file:///etc/config/app.json';
  2. import './foo';
  3. import './foo?search';
  4. import '../bar';
  5. import '/baz';

如果脚本文件省略了后缀名,比如import './foo',Node 会依次尝试四个后缀名:./foo.mjs./foo.js./foo.json./foo.node。如果这些脚本文件都不存在,Node 就会去加载./foo/package.jsonmain字段指定的脚本。如果./foo/package.json不存在或者没有main字段,那么就会依次加载./foo/index.mjs./foo/index.js./foo/index.json./foo/index.node。如果以上四个文件还是都不存在,就会抛出错误。

最后,Node 的import命令是异步加载,这一点与浏览器的处理方法相同。

内部变量

ES6模块应该是通用的,同一个模块不用修改,就可以用在浏览器环境和服务器环境。为了达到这个目标,Node 规定 ES6模块之中不能使用 CommonJS模块的特有的一些内部变量。

首先,就是this关键字。ES6模块之中,顶层的this指向undefined;CommonJS模块的顶层this指向当前模块,这是两者的一个重大差异。

其次,以下这些顶层变量在 ES6模块之中都是不存在的。

  • arguments
  • require
  • module
  • exports
  • __filename
  • __dirname

如果你一定要使用这些变量,有一个变通方法,就是写一个 CommonJS模块输出这些变量,然后再用 ES6模块加载这个 CommonJS模块。但是这样一来,该 ES6模块就不能直接用于浏览器环境了,所以不推荐这样做。

  1. // expose.js
  2. module.exports = {__dirname};
  3. // use.mjs
  4. import expose from './expose.js';
  5. const {__dirname} = expose;

上面代码中,expose.js是一个 CommonJS模块,输出变量__dirname,该变量在 ES6模块之中不存在。ES6模块加载expose.js,就可以得到__dirname

ES6模块加载 CommonJS模块

CommonJS模块的输出都定义在module.exports这个属性上面。Node 的import命令加载 CommonJS模块,Node 会自动将module.exports属性,当作模块的默认输出,即等同于export default xxx

下面是一个 CommonJS模块。

  1. // a.js
  2. module.exports = {
  3. foo: 'hello',
  4. bar: 'world'
  5. };
  6. // 等同于
  7. export default {
  8. foo: 'hello',
  9. bar: 'world'
  10. };

import命令加载上面的模块,module.exports会被视为默认输出,即import命令实际上输入的是这样一个对象{ default: module.exports }

所以,一共有三种写法,可以拿到 CommonJS模块的module.exports

  1. // 写法一
  2. import baz from './a';
  3. // baz = {foo: 'hello', bar: 'world'};
  4. // 写法二
  5. import {default as baz} from './a';
  6. // baz = {foo: 'hello', bar: 'world'};
  7. // 写法三
  8. import * as baz from './a';
  9. // baz = {
  10. // get default() {return module.exports;},
  11. // get foo() {return this.default.foo}.bind(baz),
  12. // get bar() {return this.default.bar}.bind(baz)
  13. // }

上面代码的第三种写法,可以通过baz.default拿到module.exportsfoo属性和bar属性就是可以通过这种方法拿到了module.exports

下面是一些例子。

  1. // b.js
  2. module.exports = null;
  3. // es.js
  4. import foo from './b';
  5. // foo = null;
  6. import * as bar from './b';
  7. // bar = { default:null };

上面代码中,es.js采用第二种写法时,要通过bar.default这样的写法,才能拿到module.exports

  1. // c.js
  2. module.exports = function two() {
  3. return 2;
  4. };
  5. // es.js
  6. import foo from './c';
  7. foo(); // 2
  8. import * as bar from './c';
  9. bar.default(); // 2
  10. bar(); // throws, bar is not a function

上面代码中,bar本身是一个对象,不能当作函数调用,只能通过bar.default调用。

CommonJS模块的输出缓存机制,在 ES6 加载方式下依然有效。

  1. // foo.js
  2. module.exports = 123;
  3. setTimeout(_ => module.exports = null);

上面代码中,对于加载foo.js的脚本,module.exports将一直是123,而不会变成null

由于 ES6模块是编译时确定输出接口,CommonJS模块是运行时确定输出接口,所以采用import命令加载 CommonJS模块时,不允许采用下面的写法。

  1. // 不正确
  2. import { readfile } from 'fs';

上面的写法不正确,因为fs是 CommonJS 格式,只有在运行时才能确定readfile接口,而import命令要求编译时就确定这个接口。解决方法就是改为整体输入。

  1. // 正确的写法一
  2. import * as express from 'express';
  3. const app = express.default();
  4. // 正确的写法二
  5. import express from 'express';
  6. const app = express();

CommonJS模块加载 ES6模块

CommonJS模块加载 ES6模块,不能使用require命令,而要使用import()函数。ES6模块的所有输出接口,会成为输入对象的属性。

  1. // es.mjs
  2. let foo = { bar: 'my-default' };
  3. export default foo;
  4. foo = null;
  5. // cjs.js
  6. const es_namespace = await import('./es');
  7. // es_namespace = {
  8. // get default() {
  9. // ...
  10. // }
  11. // }
  12. console.log(es_namespace.default);
  13. // { bar:'my-default' }

上面代码中,default接口变成了es_namespace.default属性。另外,由于存在缓存机制,es.mjsfoo的重新赋值没有在模块外部反映出来。

下面是另一个例子。

  1. // es.js
  2. export let foo = { bar:'my-default' };
  3. export { foo as bar };
  4. export function f() {};
  5. export class c {};
  6. // cjs.js
  7. const es_namespace = await import('./es');
  8. // es_namespace = {
  9. // get foo() {return foo;}
  10. // get bar() {return foo;}
  11. // get f() {return f;}
  12. // get c() {return c;}
  13. // }

es6 Node加载相关推荐

  1. es6 循环加载ES6模块

    循环加载ES6模块 "循环加载"(circular dependency)指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本. // a.js var b = requir ...

  2. ES6 模块加载export 、import、export default 、import() 语法与区别,笔记总结

    ES6模块加载export .import.export default .import() 语法与区别 在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种. ...

  3. ES6模块加载方案 CommonJS和AMD ES6和CommonJS

    目录 CommonJS CommonJS和AMD的对比 ES6和CommonJS 改成ES6 exports和module.exports CommonJS 每个文件就是一个模块,有自己的作用域.在一 ...

  4. ES6之Module 的加载实现(2)

    3.Node 加载 Node 对 ES6 模块的处理比较麻烦,因为它有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的.目前的解决方案是,将两者分开,ES6 模块和 CommonJS ...

  5. 【ES6】阮一峰ES6学习之Module的加载实现

    Module的加载实现 1. 浏览器加载 传统方法 加载规则 ES6 模块与 CommonJS 模块的差异 1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用. 2. Co ...

  6. thymeleaf加载不了js引用_web前端教程之js中的模块化一

    web前端教程之js中的模块化一:我们知道最常见的模块化方案有CommonJS.AMD.CMD.ES6,AMD规范一般用于浏览器,异步的,因为模块加载是异步的,js解释是同步的,所以有时候导致依赖还没 ...

  7. “睡服”面试官系列第十一篇之module加载实现(建议收藏学习)

    目录 1. 浏览器加载 1.1传统方法 1.2加载规则 2. ES6 模块与 CommonJS 模块的差异 3. Node 加载 3.1概述 3.2内部变量 4ES6 模块加载 CommonJS 模块 ...

  8. 21.Module 的加载实现

    Module 的加载实现 Module 的加载实现 上一章介绍了模块的语法,本章介绍如何在浏览器和 Node 之中加载 ES6 模块,以及实际开发中经常遇到的一些问题(比如循环加载). 浏览器加载 传 ...

  9. Javascript模块加载捆绑器Browserify Webpack和SystemJS用法

    Javascript模块加载捆绑器Browserify Webpack和SystemJS用法 转自 http://www.jdon.com/idea/js/javascript-module-load ...

最新文章

  1. 利用IPSec实现网络安全之三(身份验证和加密数据)
  2. 【收藏】Vue+elementUI的this.$refs.对象名.方法名的理解
  3. RabbitMQ学习(七)_RabbitMQ Consumer获取消息的两种方式(poll,subscribe)解析
  4. 常用Arthas命令
  5. css知识笔记(一)——基础知识、选择器、元素分类
  6. 作者:牛怡晗,女,就职于上海浦东发展银行昆明分行。
  7. 收到朋友寄来的煎饼了
  8. ubunt18.04LTS+vscode+anaconda3下的python+C++调试
  9. K8s稳居容器榜首,Docker冲顶技术热词,微服务应用热度不减,2021云原生开发者现状
  10. cocos2dx 3.10 网狐土豪金版PC+手机端棋牌平台搭建
  11. python视频教程-Python视频教程
  12. 淘宝现重大BUG,是程序员报复?官方回应
  13. matlab中appdesigner的控件简单讲解
  14. 初学 Click 路由器
  15. 显示器分辨率的英文(XGA、SXGA、UXGA、WSXGA等等来表示)
  16. 使用opencv-python快速读取视频——进阶版
  17. J9数字论:什么是Web3.0概念?
  18. 数据库连接中useSSL是否为true 或者 false的选择
  19. 【毕业设计_课程设计】基于网络爬虫的新闻采集和订阅系统的设计与实现(源码+论文)
  20. HTTP协议中 POST和GET的区别

热门文章

  1. CES神吐槽:人工智能快要“烂大街”了?
  2. C++ 类的深拷贝和浅拷贝完美解决
  3. js 学习笔记(一)
  4. [九度][何海涛] 旋转数组的最小数字
  5. Lucene6.5.0 下中文分词IKAnalyzer编译和使用
  6. Struts2 过滤器与拦截器
  7. TransE:Translating Embedding多元关系数据嵌入(知识图谱嵌入)2013 NIPS
  8. 安装Sarge(二) 配置基本系统
  9. Ansible 入门:安装 简例 playbook应用
  10. 2015年12月16日 Oracle语句实现有则更新无则插入