深入Node.js技术栈 - 二. Node核心知识

275 阅读5分钟

1. 常用全局变量

  • __filename
  • __dirname
  • setTimeout
  • clearTimeout
  • setInterval
  • console
  • process
  • 等等..

2. 模块化开发

2.1 JavaScript 模块化规范

CommonJS 规范

AMD 规范

CMD 规范

CommonJS 使用

module

为了实现模块的导出,Node 中使用的是 Module 类,每一个模块都是 Module 的一个实例,也就是 module

故在 Node 中真正用于导出的其实根本不是 exports,而是 module.exports

exports

浅层拷贝 = 引用赋值 = 引用的赋值 = 指向同一个对象

module 和 exports 的关系

module 对象的 exports 属性是 exports 对象的一个引用;即 module.exports = exports

require

global

ES6 规范

认识 ES Module

  • 采用 exportimport 关键字来实现模块化
    • export 负责将模块内的内容导出
    • import 负责从其他模块导入内容
  • 采用编译期的静态分析,并且加入了动态引用的方式
  • 自动采用严格模式:use strict

案例代码结构

<script src="./modules/foo.js" type="module"></script>

注意:在浏览器中打开时,可能会报错,参考这里,解决办法:在 VSCode 安装插件:Live Server

export 关键字

export 关键字将一个模块中的变量、函数、类等导出。有三种方式

// foo.js
// 方式一:在语句声明的前面直接加上 export 关键字
export const name = "why";
export const sayHello = function (name) {
  console.log("你好" + name);
};

// 方式二:将所有需要导出的标识符,放到 export 后面的 {} 中
const name = "why";
const sayHello = function (name) {
  console.log("你好" + name);
};
export { name, sayHello };

// 注意:这里的 {} 里面不是ES6的对象字面量的增强写法,{} 也不是表示一个对象的;
// 所以: export {name: name},是错误的写法;

// 方式三:导出时给标识符起一个别名(很少用)
const name = "why";
const sayHello = function (name) {
  console.log("你好" + name);
};
export { 
  name as fName, 
  sayHello as fSayHello
};

import 关键字

import 关键字负责从另外一个模块中导入内容。

// index.js
// 方式一:import {标识符列表} from '模块'
import { name, sayHello } from "./modules/foo.js";
console.log(name);
sayHello("jack");

// 方式二:导入时给标识符起别名
import { name as fName, sayHello as fsayHello } from "./modules/foo.js";
console.log(fName);

// 方式三:通过 * 将模块功能放到一个模块功能对象(a module object)上
import * as foo from "./modules/foo.js";
console.log(foo.name);
foo.sayHello("Summer");

export 和 import 结合使用

在开发和封装一个功能库时,通常我们希望将暴露的所有接口放到一个文件中; 这样方便指定统一的接口规范,也方便阅读;这个时候,我们就可以使用 exportimport 结合使用。

export { sum as barSum } from './bar.js'

default 用法

默认导出 export 时可以不需要指定名字;在导入时不需要使用 {},并且可以自己来指定名字;它也方便我们和现有的 CommonJS 等规范相互操作。

// foo.js 
// 默认导出方式: format 可省略
export default function format() {
  console.log("格式化函数");
}

// index.js
// 默认导入方式:
import fmt from "./modules/foo.js";
fmt();

// 注意:在一个模块中,只能有一个默认导出(default export)

import 函数

如果想要动态的加载某一个模块,可以这样操作吗?

if (true) {
  import sub from "./modules/foo.js";
}

显然是不可以的。这是因为 ES Module 在被 JS 引擎解析时,就必须知道它的依赖关系。正确的做法如下:使用 import 函数。

let flag = true;
if (flag) {
  import("./modules/foo.js")
    .then((res) => {
      console.log("在then中的打印");
      console.log(res.name);
      console.log(res.age);
    })
    .catch((err) => {
      console.log(err);
    });
}

CommonJS 的加载过程

CommonJS 模块加载 js 文件的过程是运行时加载的,并且是同步的。

运行时加载意味着:js 引擎在执行 js 代码的过程中加载模块。

同步意味着:一个文件没有加载结束之前,后面的代码都不会执行。

CommonJS 通过 module.exports 导出的是一个对象

导出的是一个对象意味着可以将这个对象的引用在其他模块中赋值给其他变量。但是最终他们都指向的是同一个对象,那么一个变量修改了对象的属性,所有的地方都会被修改。

2.2 Node 模块化实现

CommonJSES Module 交互

结论一:通常情况下,CommonJS 不能加载 ES Module

因为CommonJS是同步加载的,但是ES Module必须经过静态分析等,无法在这个时候执行JavaScript代码; 但是这个并非绝对的,某些平台在实现的时候可以对代码进行针对性的解析,也可能会支持; 当然Node当中是不支持的;

结论二:多数情况下,ES Module可以加载CommonJS

ES Module 在加载 CommonJS 时,会将其 module.exports 导出的内容作为 default 导出方式来使用; 这个依然需要看具体的实现,比如 webpack 中是支持的、Node 最新的Current版本也是支持的。

// foo.js
const name = "coderwhy";
// CJS 导出
module.exports = {
  name,
};

// index.mjs
// ES Module 导入
import foo from "./modules/foo.js";
console.log(foo.name);

与 ES6 对比区别

值的拷贝和引用对比

运行时加载和编译时加载

6.Node 原理解析

6.1 进程和线程

操作系统 - 工厂,进程 - 车间,线程 - 工人

一个工厂(操作系统)可以有多个车间(进程),一个车间可以有多个工人(线程),即多进程和多线程

CPU - 为每一个车间开灯,这样工人可以正常工作。但开灯时间是固定的,即过一段时间,灯会自动熄灭。

6.2 阻塞和非阻塞

6.3 事件循环机制

事件循环是 JavaScript 应用程序和浏览器 或 Node 沟通的桥梁。 而回调函数充当车辆的角色,来回搬运数据。当然咯,这个桥梁上,还有收费站,检查站等设施。