Node 语法

什么是 node

node 不是后台语言,它是一个工具或者环境,解析 js 的工具或者环境,而说它是后台语言,主要的原因是:我们一般会把 node 安装在服务器上,在服务器端写一些 js 代码,通过 node 执行这些代码,实现服务器要做的一些功能。所以我们可以说 js 是全栈语言,全栈是什么?

  1. 应用 node 环境做的一些事情,基于 v8 引擎渲染和解析 js ,类似于 webview / 谷歌浏览器等

  2. 怎么执行?

    基于命令: $ node xxx.js 把 js 代码在 node 环境中执行

    基于 repel 模式:(Read-Evaluate-Print-Loop, 输入-求值-输出-循环)$ node

webpack (自动化项目部署)就是基于 node 环境运行的
npm (Node Package Manager)是安装 node 自带的模块管理工具,基于它可以安装和卸载对应的模块,类似的模块管理工具:borwer、yarn

  • nmp 安装模块是从 npmjs.com 下载
  • borwer 安装模块是从 github.com 下载
    3.基于node.js 实现服务端功能, =》后台语言: java/python/php/go/c#(asp.net)…

项目架构1

项目架构1.png

项目架构2

项目架构2.png

项目架构3

项目架构3.png

I/O

I:input 输入,O:output 输出,I/O 一般指对文件的读写操作

  1. JS 在客户端浏览器中运行,能否对客户端本地的文件进行读写操作?

答案:不能,因为要保证客户端的信息安全
input:type = ‘file’ 文件上传这种除外,但是这种也需要用户手动选择后才可以

  1. JS 在服务器端运行(基于 NODE 运行),能否对服务器端的文件进行操作?

答案:可以的
NODE 赋予了 JS 进行 I/O 操作的能力(内置模块:fs)

window & global & globalThis

  1. 在客户端浏览器中运行 JS,JS 全局对象是:window(提供了很多内置的属性和方法)

  2. 在 node 中运行 JS,全局对象是:global

    • process :node 中进程管理的属性
      • process.nextTick()
      • process.env NODE 全局环境变量
    • Buffer
    • setImmediate 立即执行(类似于setTimeout(func, 0))
  3. 在REPL命令中输出的 this 是 global,但是在 xxx.js 中输出的 this 是当前模块本身

  4. globalThis

    • node和window node和浏览器环境通用的 ,要注意版本兼容

NPM

  1. 模块管理(安装和卸载)

    安装在全局环境下和安装在当前项目中

    • 全装在全局:$ npm install xxx --global ($ npm i -g xxx)
    • 安装在本地项目中:$ npm i xxx

    把模块设置为开发依赖(开发中):$ npm i xxx --save-dev
    把模块设置为生产依赖(部署到服务器):$ npm i xxx --save

  2. 安装在全局和本地的区别
    安装在全局后对任何项目都有作用(也有可能导致版本冲突),但是只能基于命令的方式管理,不能基于CommonJS 中的 require 导入使用(通俗说:就是不能导入到文件中基于代码来处理)

    	- node -v  查看安装的node版本
        - $ npm root -g 查看全局安装到的目录
        - 之所以可以使用命令操作,是因为在全局目录下生成了一个 xxx.cmd 的文件
    

    安装在本地默认不能基于命令管理,但是可以导入到文件中基于代码操作,只对当前项目有用
    在本地安装模块之前,最好先:$ npm init -y,生成 package.json 模块配置文件,把安装的模块生成配置清单,存放在 package.json 中,后期别人需要部署项目的时候,只需要执行 $ npm i 就可以把所有的依赖项重新安装一遍 “跑环境”

    • $ npm i 是把开发和生产依赖都安装一遍

    • $ npm i --production 只安装生产依赖的模块

    • 在 package.json 中,可以基于 scripts 选项配置本地可执行的脚本命令 $npm run xxx

      "scripts": {
      		// AAA 是命令,值是要做的事情
      		"AAA": "node xxx.js"
      }
      

      在配置可执行脚本命令的时候,基于process的环境变量区分开发还是生产环境

      "scripts": {
      	// set NODE_EVN=dev 设置全局环境变量(MAC下用 export NODE_EVN=dev)
          "serve": "set NODE_EVN=dev&&node test1.js",
         	"build": "set NODE_EVN=pro&&node test1.js"
      }
      

npm指令

npm -l 用于查看各个命令的简单用法(所以下面的可以用这个命令来查看)
npm init 用来初始化生成一个新的 package.json文件。
它会向用户提问一系列问题,如果你觉得不用修改默认配置,一路回车就可以了。
npm -h 或 npm help 查看npm命令的帮助信息
npm ls 或 npm list 查看npm已安装的包信息
npm -v 或 npm --version 查看npm版本信息
npm install npm -g npm更新自身
npm info version 查看某个模块最新发布版本信息,如npm info underscore version
npm search 查找与keyword匹配的模块信息
npm view version 查看一个包的最新发布版本
npm i 或 npm install npm安装当前目录package.json里面的所有包,
下面的i同样可以用install代替,当卸载时,i用uninstall代替
npm update [-g] 更新指定模块,有-g表示全局
npm i [-g] 安装指定模块,有-g表示全局
npm i @version [-g] 安装指定版本的模块,有-g表示全局
npm i --save 安装包的同时自动更新package.json的依赖
npm i --save-dev 安装包的同时自动更新package.json的开发依赖
npm i --save-optional 安装包的同时自动更新package.json的可选版本依赖
npm i --save-exact 安装包并写入确切版本依赖,而不是一个可选的版本范围.

CommonJS模块管理机制

AMD:require.js
CMD:sea.js
CommonJS:node.js
ES6 Module
这些模块化思想,规定了在JS中我们的模块该如何的创建、如何的导入以及如何导出

1. 内置模块

NODE中自带的

  • http/https 创建和管理服务的模块
  • fs 给予JS进行I/O操作的
  • url 解析URL地址的
  • path 管理路径的
2. 第三方模块

基于npm安装,别人写好供我们用的

  • mime
  • qs
  • express
  • express-session
  • body-parser
3. 自定义模块:自己写的模块

NODE中的模块管理

  1. 在 NODE 环境下,我们每创建一个 JS,都相当于创建了一个新的模块;模块中的方法也都是模块的私有方法,不同模块之间的同名方法不会有任何的冲突

  2. module.exports 就是 NODE 天生自带的用来导出模块中方法的方式

    module.exports = {
    	// 这些属性方法就是需要暴露给外面调取使用的
    	xxx:xxx
    };
    
  3. require 是 NODE 天生提供的用来导入模块的方法

    //语法:
    let [模块名] = require([模块的地址]);
    
    // 例如:
    // 1. 可以省略 .js 后缀名
    // 2. 如果是调取自己定义的模块,则需要加 /(根目录) ./(当前目录) ../(上级目录) 这三个中的某一个
    // 3.不加上述地址,则先找第三方模块(安装在自己本地的),如果没有安装,则找NODE中的内置模块,如果再没
    // 有,则报错
    let A = require('./A');
    let qs = require('qs');
    
  4. require 导入模块是同步的(没导入完成,后面的事情是不处理的);每一次导入模块都是把导入的模块中的 JS代码从上到下执行一遍(只执行一遍)

一个小应用

需求:创建 A / B / C 三个模块

  • A 中有一个 sum 方法实现任意数求和
  • B 中有一个办法 avg 是求平均数:去掉最大和最小值,剩余值求和(调取 A 中的 sum 方法,实现求和)
  • C 中调取 B 中的 avg ,传递:98 95 85 67 25,实现求一堆数中的平均数

A:

let sum = (...arg) => eval(arg.join('+'));
module.exports = {
	sum
};

B:

let A = require('./A');
let avg = (...arg) => {
	arg = arg.sort((a, b) => a - b).slice(1, arg.length - 1);
	return (A.sum(...arg) / arg.length).toFixed(2);
};
module.exports = {
	avg
};

C:

let {
	avg
} = require('./B');
console.log(avg(98, 95, 85, 67, 25));

node相关指令汇总

	- node -v  查看安装的node版本
    - $ npm root -g 查看全局安装到的目录
    - npm i -g tnpm --registry=http://registry.npm.alibaba-inc.com  切换npm源
  • nodejs全局安装路径的位置
    • 使用快捷键 win+R,输入cmd打开命令窗口,输入如下代码:npm config ls

在这里插入图片描述

切换node环境的版本

Node运行机制

Node.js的运行机制是基于事件驱动和非阻塞I/O的模型,这意味着它能够处理大量的并发连接,而不会因为阻塞操作而导致性能下降。以下是Node.js运行机制的核心组成部分:

  1. 单线程
    Node.js在主线程上运行JavaScript代码,这避免了多线程环境中常见的并发问题和线程管理的复杂性。尽管JavaScript执行在单个线程上,Node.js本身在底层使用多线程来处理非阻塞I/O操作。

  2. 非阻塞I/O
    Node.js对I/O操作采用非阻塞模式,当应用发起一个I/O操作时(如读取文件、网络请求等),Node.js会将这个操作交给底层系统,并继续执行后面的代码,而不会停下来等待I/O操作完成。完成后,I/O操作的结果会以事件的方式通知Node.js,然后适当的回调函数会被加入到事件队列中等待执行。

  3. 事件循环
    事件循环是Node.js运行机制的核心,它是一个在Node.js进程中不断运行的循环,负责监听事件并执行对应的回调函数。它使得Node.js可以在等待异步操作的结果时执行其他代码,提高了程序的效率和吞吐量。

  4. 事件队列
    事件队列是事件循环要处理的事件和回调函数的列表。当异步操作完成时,其回调函数会被放入事件队列中。事件循环会按照顺序依次取出队列中的事件并执行其回调。

  5. Node.js API
    Node.js提供了一系列内置的API,允许用户执行各种异步操作,如文件系统操作、网络请求等。这些API通常会返回Promise对象或允许用户提供回调函数,用于处理异步操作的结果。

  6. JavaScript引擎
    Node.js使用V8 JavaScript引擎(由Google开发,也用于Chrome浏览器)来执行JavaScript代码。V8引擎将JavaScript代码编译成机器码,提高了执行效率。

总结
当Node.js进程启动时,它会初始化事件循环,然后开始执行输入的脚本,该脚本可能会注册异步操作和定时器。当脚本执行完毕,事件循环会开始运作,处理异步操作的结果和定时器的回调。事件循环确保了每次只有一个回调在主线程上执行,直到队列中的事件被处理完毕。如果没有更多要处理的事件,Node.js进程将退出。

这个运行机制使得Node.js在处理高并发和I/O密集型任务时表现出色,尤其适合构建网络应用和服务。

Node.js事件循环

  • Node.js 的事件循环是一个运行时机制,它允许Node.js进行非阻塞I/O操作 - 尽管JavaScript是单线程的 - 通过将操作卸载到系统内核当中去。如果内核能够异步地进行I/O操作,事件循环就可以让Node.js在等待操作结果的同时执行其他代码。
    在这里插入图片描述

  • Node.js事件循环的工作原理可以分解为以下几个阶段:

    • timers阶段:
      • 这个阶段执行计时器队列中到期的 setTimeout() 和 setInterval() 回调。
    • I/O callbacks阶段:
      • 处理大多数异步I/O的回调,但是不包括由计时器调度的回调,也不包括setImmediate()的调用。
    • idle, prepare阶段:
      • 这是仅供内部使用的阶段,用来准备下一次事件循环的回调操作。
    • poll阶段:
      • 这个阶段主要是等待新的I/O事件,执行I/O相关的回调(除了那些与计时器相关的回调和setImmediate()回调),并处理poll队列中的事件。
    • check阶段:
      • setImmediate() 回调在这个阶段执行。
    • close callbacks阶段:
      • 一些关闭的回调,如 socket.on(‘close’, …)
  • 除此之外,还有两个特殊的阶段:

    nextTick队列:
    当每个阶段完成后,Node.js会处理process.nextTick()队列。如果在执行代码的过程中调用了process.nextTick(),则当当前阶段的所有操作完成后,会立即处理nextTick队列。这允许用户在事件循环继续之前安排回调,无论当前处于哪个阶段。

    microtask队列(或称为“Promise队列”):
    与nextTick队列类似,microtask队列在每个阶段之后都会被清空。这包括诸如Promise的then()和catch()回调。

事件循环允许Node.js执行非阻塞I/O操作,尽管JavaScript是单线程的,但由于大多数现代内核都是多线程的,它们可以在后台处理多个操作。当其中一个操作完成时,内核会通知Node.js,从而使得适当的回调加入到适当的事件循环队列中。

这种机制使得Node.js可以在处理大量并发客户端请求时保持高效,尤其是在构建网络应用和服务时。它是Node.js能够作为一个高性能服务器运行的关键原因之一。

示例

Node端事件循环中的异步队列也是这两种:macro(宏任务)队列和 micro(微任务)队列。

  • 常见的 macro-task 比如:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作等。
  • 常见的 micro-task 比如: process.nextTick、new Promise().then(回调)等。

浏览器环境下,microtask的任务队列是每个macrotask执行完之后执行。而在Node.js中,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。

console.log("start");
setTimeout(() => {
  console.log("timer1");
  Promise.resolve().then(function () {
    console.log("promise1");
  });
  Promise.resolve().then(function () {
    console.log("promise4");
  });
}, 0);
setTimeout(() => {
  console.log("timer2");
  Promise.resolve().then(function () {
    console.log("promise2");
  });
  Promise.resolve().then(function () {
    console.log("promise5");
  });
}, 0);
Promise.resolve().then(function () {
  console.log("promise3");
});
console.log("end");

// node 11及以后(https://nodejs.org/en/blog/release/v11.0.0) / 浏览器
// 执行栈的同步任务(这属于宏任务)=>微任务队列=> 第一宏任务=>微任务队列=>第二个宏任务=>微任务队列
// start=>end=>promise3=>timer1=>promise1=>promise4=>promise2=>promise5

// node 10及以前
// 执行栈的同步任务(这属于宏任务)=>微任务队列=> 第一宏任务=>第二个宏任务=>微任务队列
// start=>end=>promise3=>timer1=>timer2=>promise1=>promise4=>promise2=>promise5
  • timeout 和 immediate
// setTimeout(function timeout() {
//   console.log("timeout");
// }, 0);
// setImmediate(function immediate() {
//   console.log("immediate");
// });
// immediate
// timeout
// 或者
// timeout
// immediate

// 二者在异步i/o callback内部调用时,总是先执行setImmediate,再执行setTimeout
// IO 回调是在 poll 阶段执行,当回调执行完毕后队列为空,发现存在 setImmediate 回调,跳转到 check 阶段去执行回调了,然后循环到timers 阶段执行setTimeout
const fs = require("fs");
fs.readFile("", () => {
  setTimeout(() => {
    console.log("timeout");
  }, 0);
  setImmediate(() => {
    console.log("immediate");
  });
});
// immediate
// timeout
  • 事件循环首先执行 中的任务process.nextTick queue,然后执行promises microtask queue,然后执行macrotask queue。
const baz = () => console.log('baz');
const foo = () => console.log('foo');
const zoo = () => console.log('zoo');
const start = () => {
  console.log('start');
  setImmediate(baz);
  new Promise((resolve, reject) => {
    resolve('bar');
  }).then(resolve => {
    console.log(resolve);
    process.nextTick(zoo);
  });
  process.nextTick(foo);
};
start();
// start foo bar zoo baz
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐