nodejs html引用js_NodeJS与模块系统
本文作者:刘观宇,360 奇舞团高级前端工程师、技术经理,W3C CSS 工作组成员。
孤山寺北贾亭西,水面初平云脚低。几处早莺争暖树,谁家新燕啄春泥。乱花渐欲迷人眼,浅草才能没马蹄。最爱湖东行不足,绿杨阴里白沙堤。 ———— 唐.白居易《钱塘湖春行》
自从 Node8.5 以后, Node 开始支持引入 ES 模块1。在新开的项目中,笔者尝试使用了这种方式。由于目前 NodeJS 对于 ES 模块尚属试验性支持,因此需要在启动时候加入参数:--experimental-modules,完整的命令如:node --experimental-modules index.mjs。这样程序就愉快地运行起来了。不过当笔者尝试加入一些新的依赖之后问题出现了:
这个问题在于,很多 NodeJS 依赖包,由于使用 CommonJS 的方式编写,对于 ES Module 的支持尚不完善。因此包在使用过程中,存在一定的兼容问题。
这篇文章我们就来谈谈,在 NodeJS 环境下,CommonJS 和 ES Module 区别和联系,以及互操作的方法。
在 NodeJS 诞生时候,ES Module 模块系统还没有诞生,因此 NodeJS 最初采用的是 CommonJS 模块系统。我们之前所使用的 NodeJS 包,大部分都是 CommonJS 模块。CommonJS 模块和 ES Module 之间有很大的区别。这也就是为什么ES Module使用.mjs区别对待的原因。 下面这张图阐述了 NodeJS 对于两种模块系统的解析流程。
那么它们在行为上有什么区别呢?
主要有两点:
一、引入模块时机的区别:CommonJS 模块是运行时加载,换句话说是在 NodeJS 脚本执行时才加载进来,而 ES Module 则是在静态分析时候就确定了引用关系。这有点像给目标模块建立了一个符号链接,或者说建立了一个指针。从这个意义上说,ES Module 的 import 的加载效率应该略高于 CommonJS。而 CommonJS 这种特性也给动态加载模块提供了可能。
二、CommonJS 模块加载实际上是把 module.exports 对象进行了一次拷贝。因此当原模块的存储在栈空间的值,也就是基本类型值在第一次加载过程中已经被缓存掉,当它发生了改变,不会影响引入这个模块的代码的值。读者可以尝试在加载后打印下require.cache看一看缓存的结构。下面看这个例子:
// cjs.js
var inner = 3;
let innerIncrease = () => {
inner++;
}
module.exports = {
inner,
innerIncrease
};
// main.js
var mod = require('./cjs');
console.log(mod.inner);
mod.innerIncrease();
console.log(mod.inner);
我们如果执行 node main.js 会发现两次执行的结果都为 3。那是因为 inner 的值在第一次引入时候被缓存掉了。
如果上述代码使用 ES module。因为都是引用值,所以外部可以感知内部变化。
// mjs.mjs
export let inner = 3;
export let innerIncrease = () => {
inner++;
}
// main.mjs
var mod = require('./mjs.mjs');
console.log(mod.inner);
mod.innerIncrease();
console.log(mod.inner);
这里因为"import"的都是引用,会内外同步,因此依次打印出 3 和 4。
如果你启用了--experimental-modules,则意味这几件事:
对于同样的文件名,当模块没有写明扩展名时,加入--experimental-modules的总是先去加载.mjs 扩展名的同名模块,如果没有才会去加载.js。
只要使用 import/export 命令的,必须使得扩展名为 .mjs。
.mjs 文件中不能使用 require 函数,否则会报ReferenceError: require is not defined错误。
import 命令是异步加载,意思是,后面的模块不会等待前面的加载完再去加载。如果有先后依赖关系的模块,不能在 import 中同级写,这个和 CommonJS 模块 不一样。
顶层文件,this指向undefined。arguments、require、module、exports、__filename和__dirname均为undefined
既然差异很难弥合,那么用扩展名来区分两套体系从目前来看是很必要的。但是有时候,互相操作又是不可避免的。
那么如何互操作呢?
如果 ES Module 需要加载 CommonJS 模块,NodeJS 将把 module.exports 当作一个对象赋给 default 对象,即相当于:export default {...module.exports 对象}
假设我们有 cjs 模块 cjs.js,如下:
// cjs.js
module.exports = {
"Hello": "你好",
"World": "世界"
}
我们可以在需要的 mjs 文件中这样使用:
// main.mjs
import cjsExport from './cjs.js'
// 以下两种也可以
/***
* import {default as cjsExport} from './cjs.js'
* import * as cjsExport from './cjs.js'
***/
const {Hello, World} = cjsExport
但是,这种方式是不允许的:import { someExports } from 'module'。
那么 CommonJS 能否加载 ES Module 呢?答案是肯定的,不过稍微有点麻烦。同时,使用这个特性需要在 NodeJS 9.7.0 版本以上,同时加入--experimental-modules,这里2是当时的 changelog
// es.mjs
export default {
"a" : 1,
"b" : 2
}
// main.mjs
(async _ => {
const es = await import('./es.mjs');
console.log(es);
})()
其它几种 export 语法也可以用这种方式导入,想要知晓所有 export 语法的,可以参考高峰老师的之前在周刊发表的这篇文章3。
回到我们开头提出的问题,在 mjs 模块里,可以使用上面提到的相应的加载 CommonJS 模块的写法,就可以正常运行了。
随着 ES Module 越来越完善,我们可以期待,今后 ES module 会越来越多。我们看到在现在这个多种模块机制并存的时代,很多重要的库在新版本的源码中都做了适配,基本的方案是:利用扩展名引入的优先级,提供多种扩展名的文件。如.js 适应 CommonJS,.mjs 文件适配 ES module,这种方案也使得库的使用者,比较平滑地切换各种模块系统。当然,各种预编译器也可以在不得已的时候提供必要的帮助。
参考资料
http://es6.ruanyifeng.com/#docs/module-loader
https://nodejs.org/api/esm.html
https://www.zcfy.cc/article/es-modules-and-node-js-hard-choices-477.html
文内链接
https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V8.md#8.5.0
https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V9.md#9.7.0
https://mp.weixin.qq.com/s/bIU_FvesizFJ3D_6KWRPHA
关于奇舞周刊
《奇舞周刊》是360公司专业前端团队「奇舞团」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。
nodejs html引用js_NodeJS与模块系统相关推荐
- nodejs html引用js_nodejs做出最简单的网页服务端。【501】
一.先去官网下载nodejs,按自己的系统一步一步操作,基本很简单,这里就不多介绍了. 二.安装完成后,通过cmd验证是否安装成功,输入node -v可以查看版本号. 三.用开发工具创建一个js文件, ...
- jdk 11 模块系统_JDK 9:模块系统状态的重点
jdk 11 模块系统 马克·雷因霍尔德 ( Mark Reinhold )的"模块系统状态 (SOMS)"已于本月初发布,它提供了信息丰富的可读性"对Jigsaw项目中 ...
- JDK 9:模块系统状态的重点
马克·雷因霍尔德 ( Mark Reinhold )的"模块系统状态 (SOMS)"已于本月初发布,它提供了信息丰富的可读性"对项目Jigsaw中原型的Java SE平台 ...
- Nodejs的模块系统以及require的机制
一.简介 Nodejs 有一个简单的模块加载系统.在 Nodejs 中,文件和模块是一一对应的(每个文件被视为一个独立的模块),这个文件可能是 JavaScript 代码,JSON 或编译过的C/C+ ...
- Nodejs中的模块系统
一.模块化的定义 ①具有文件作用域 ②具有通信规则:加载和导出规则 二.CommonJS模块规范 1.nodejs中的模块系统,具有文件作用域,也具有通信规则,使用require方法加载模块,使用ex ...
- NodeJS与模块系统
孤山寺北贾亭西,水面初平云脚低.几处早莺争暖树,谁家新燕啄春泥.乱花渐欲迷人眼,浅草才能没马蹄.最爱湖东行不足,绿杨阴里白沙堤. ---- 唐.白居易<钱塘湖春行> 自从 Node8.5 ...
- NodeJs 之模块系统
前言 随着前端的发展,工程模块儿化已经是必不可少的一部分了,为了让NodeJs的文件可以相互调用,NodeJs提供了一个简单的模块系统:简单点说:一个 nodeJs 文件就是一个模块儿 . 通过代码来 ...
- 基于源码剖析nodejs模块系统
nodejs模块系统 简介 为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统. 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的.换言之, 一个 No ...
- nodejs中的模块系统:exports导出模块
node中的模块系统 示例: 模块作用域 成功获取add: module.exports
最新文章
- Android读取短信和联系人
- KRPano JS 场景编辑器源码
- poj1743(后缀数组:最长不可重叠子串长度)
- php 配置(转载其他)
- python获取字典的值_python取出字典中的所有值的两种方法
- 【图像超分辨率】Perceptual Losses for Real-Time Style Transfer and Super-Resolution
- 头条 上传图片大小_【标签头条】北京市启用进口冷链食品追溯平台;全球包裹热潮助推标签业发展;数字水印实现大规模垃圾分类;安慕希的麻将酸奶包装好真实...
- keil运行c语言输入函数,keil 编译器V6 定义函数在ram中运行-和在指定地址定义常量,keil编译器...
- 不使用border-radius,实现一个可复用的高度和宽度都自适应的圆角矩形
- WGS-84坐标系转GCJ02坐标系
- Blender MMD 备忘
- 《计算机网络教程》(微课版 第五版)第三章 数据链路层 课后习题及答案
- win7 批处理文件默认以管理员身份运行及清除IE缓存脚本
- ROS2探索(一)Publisher-Subscriber的内部过程
- 新浪微博架构和FEED架构分析--人人架构
- 号码归属地及运营商查询工具
- PHP之实现 家谱树,子孙树
- 我的身体为什以会这样?如何诊治?
- 1504_AURIX_TC275参考手册_芯片介绍
- Java中tif转png,tif格式图片转换为gif、png、jpg格式(Java实战)
热门文章
- 学习 ---AJAX如何用于Web部件
- 《图解HTTP》读书笔记--第5章与HTTP协作的Web服务器
- mysql索引 钱缀_mysql字符串前缀索引
- 高校c语言程序设计比赛,分秒必争,力争上游,计算机学院举办第八届C语言程序设计挑战杯...
- 树莓派c语言访问mariadb,树莓派之MariaDB
- 行号 设置vim_Vim从小白到入门
- 服务器centos7系统更换网卡,Centos7更换网卡名称(示例代码)
- mysql存储过程遍历新增_MySQL存储过程:内部调用存储过程、存储过程实现遍历数据库建表以及修改字段...
- java中菜单分几级_JAVA构造多级菜单
- pandas的reindex功能