Node学习笔记

    NodeJS作为JavaScript在后端运行的宿主平台,保留了浏览器中许多常见的JavaScript接口,在不额外给JavaScript语言附加其他特性的基础之上,将前端 异步事件驱动单线程的思想迁移到了服务端。基于其自身的这些特点,也使得NodeJS成为更适于处理 I/O密集高并发 服务的后端技术选型。

模块机制

CommonJS

NodeJS借鉴了CommonJS的Module规范,实现了一套非常易用的同步模块系统。

模块引用

在CommonJS规范中,通过require函数,接受一个小驼峰规范命名的模块名或者URI,引入指定的模块API到当前上下文中。

1
const math = require('math');

模块定义

每一个模块(JS文件)都有一个类似于在浏览器中document的全局对象module,表示模块自身。它是NodeJS内部的Module类的对象实例。

而每一个模块都有自己的私有作用域,所以开发者完全不用考虑变量全局污染的情况。要使用全局数据的话,需要将变量绑定在全局对象global上(类似前端的window)。

module上的属性exports表示模块暴露给外部的内容,即其他模块可以通过require函数引入的部分。

需要注意:模块全局虽然存在一个exports对象,但它仅仅是一个指向module.exports的引用而已,直接给exports赋值只会改变它的引用对象,并不是修改了module.exports。这一点和浏览器环境下任何全局变量都会默认挂在window下是很不一样的。这也是为什么module更类似document而不是window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const function test() {
return 1 + 1;
}

// 导出方式1:直接给exports属性赋值
exports.test = test;

// 导出方式2:给整个modules.exports赋值
modules.exports = {
test,
};

/**
* 错误的导出方式
* exports = {
* test,
* };
*/

除了exports属性之外,module上的属性还有:

  • module.id: 模块的识别符,通常是带有绝对路径的模块文件名;

  • module.filename: 模块的文件名,带有绝对路径;

  • module.loaded: 返回一个布尔值,表示模块是否已经完成加载;

  • module.parent: 返回一个对象,表示调用该模块的模块;

  • module.children: 返回一个数组,表示该模块要用到的其他模块。

AMD

不同于CommonJS规范同步加载模块的方式,AMD(Asynchronous Module Definition)规范则是异步加载模块的,允许指定回调函数。由于Node环境下使用的模块都存在本地,不存在网络阻塞的场景,因此采用同步的模块机制CommonJS。而对于浏览器这种依赖网络加载的环境,引入模块则更适于使用AMD规范。例如采用此规范的 requireJS

模块的引入

通过require函数传入指定模块(模块名或模块URI)的数组,以及所有模块加载成功以后的回调函数。在所有模块加载成功以后,各个模块将作为参数传递给回调函数开始执行函数体。

1
2
3
4
require(['//module/math', '//module/ramda'], function (math, R) {
math.add(2, 3);
R.add(1, 2);
});

模块的定义

使用define函数定义模块,可以将模块对象作为参数,也可以传递一个导出模块数据的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 直接定义模块对象
define({
name: 'test'
});

// 通过回调定义模块
define(() => ({
name: 'test'
}));

// 当模块依赖其他模块时,基于回调定义
define(['./module/a.js', './module/b.js'], function(m1, m2) {
return {
name1: m1.name,
name2: m2.name,
};
});

CMD

CMD(Common Module Definition)即通用模块定义,是国内发展出来的,就像AMD有个requireJS,CMD也有个浏览器的实现 SeaJS ,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)的时机上有所不同。

在AMD中定义一个模块时,如果模块依赖其他模块,则需要在定义的同时声明模块依赖,也就是依赖前置。而CMD则推崇依赖就近原则,在真正需要使用依赖模块的时候再通过require引入。

模块引入

模块的引入方式和AMD类似。

1
2
3
4
seajs.use(['//module/math', '//module/ramda'], function (math, R) {
math.add(2, 3);
R.add(1, 2);
});

模块定义

1
2
3
4
5
6
7
8
9
10
// CMD模块定义
define(function(require, exports, module) {
const $ = require('jquery');

console.log(`module info ${JSON.stringify(module)}`)

exports.sayHello = function() {
console.log($('.app').html());
};
});

UMD

UMD(Universal Module Definition)规范即通用模块定义规范,是AMD、CommonJS和定义全局模块的兼容策略。AMD模块以浏览器第一的原则发展,异步加载模块。CommonJS模块以服务器第一原则发展,选择同步加载。这迫使人们又想出另一个更通用的模式UMD。以此解决了跨平台与全局引用的模块化场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
//AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
//Node, CommonJS之类的
module.exports = factory(require('jquery'));
} else {
//浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
//方法
function myFunc(){};
//暴露公共方法
return myFunc;
}));

ESM

ESM(ECMAScript Module)是用于处理模块化的 ECMAScript 标准。 虽然 NodeJS 长期使用 CommonJS 标准,但CommonJS只是社区提供的解决方案。而真正官方认定的统一模块标准,则是由ECMA(欧洲计算机制造商协会)伴随着ES6标准提出的ES Module。是同时支持服务端和前端的模块化标准

基本特性

  • ESM 自动采用严格模式,忽略 ‘use strict’;
  • 每个 ES Module 都是运行在单独的私有作用域中;
  • ESM 是通过 CORS(跨域) 的方式请求外部 JS 模块的;
  • ESM 的 script 标签会延迟执行脚本(浏览器页面渲染后执行)。

模块定义

ESM规定,模块必须是一个单独的文件,引入、导出的操作只允许在模块中进行(所以尝试在控制台或者script标签中使用import导入模块时,会抛出语法错误)。

NPM包管理工具

NPM是随着NodeJS一同安装的包管理工具,为用户提供了方便、快捷的管理和开发各种基于Node环境的依赖包。它的主要功能有:

  1. 允许用户从NPM服务器下载别人编写的第三方包到本地使用;
  2. 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用;
  3. 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。

常用命令

  1. NPM版本信息
    1
    npm -v // npm --version
  2. 在项目中安装NPM依赖,并依据package.json安装各类依赖(生成package-lock.json
    1
    npm install
  3. NPM版本更新
    1
    npm install npm@latest -g
  4. 全局安装依赖
    1
    npm install x -g
  5. 在项目中安装依赖(安装在项目node_modules目录下)
    1
    npm install x
  6. 在项目中安装依赖,并将依赖信息补充在package.json文件中的dependencies属性下,以便下次执行npm install时自动安装相关依赖
    1
    npm install x --save
  7. 在项目中安装依赖,并将依赖信息补充在package.json文件中的devDependencies属性下,以便下次执行npm install时自动安装相关依赖。但在生产环境中安装时(npm install x --production)不会安装该依赖
    1
    npm install x --save-dev
  8. 更新依赖模块
    1
    npm update x
  9. 搜索依赖模块
    1
    npm search x
  10. 自定义模块(生成package.json
    1
    npm init
  11. 注册NPM库用户
    1
    npm adduser
  12. 发布自定义模块
    1
    npm publish
  13. 撤销发布的模块
    1
    npm unpublish x@<version>
  14. 删除已安装的模块
    1
    2
    3
    npm uninstall x
    npm uninstall x --save
    npm uninstall x --save-dev
  15. 清除NPM缓存
    1
    npm cache clear

package.json与package-lock.json的区别

  1. package.json中每一项依赖的版本号^1.0.0中的^指向后兼容依赖,在执行npm install时,会依据最大版本号1安装最新的依赖版本(如1.2.0);
  2. package-lock.json中将记录上一次执行npm install实际安装各个依赖的版本号,使得其他开发者知道原来使用时的依赖的实际版本号,避免向后兼容依赖导致的问题。

npm install与npm i的区别(Windows下)

  1. npm i安装的模块无法用npm uninstall删除,用npm uninstall i才卸载掉;
  2. npm i会帮助检测与当前node版本最匹配的npm包版本号,并匹配出来相互依赖的npm包应该提升的版本号;
  3. 部分npm包在当前node版本下无法使用,必须使用建议版本;
  4. 安装报错时intall肯定会出现npm-debug.log文件,npm i不一定。

异步I/O与非阻塞I/O

异步编程

内存控制

Buffer

网络编程

构建Web应用

进程

测试

参考资料

《深入浅出Nodejs》——朴灵
npm i和npm install的区别
npm install、npm install –save与npm install –save-dev区别