Node.js学习
Node.js学习
npm
NPM(Node Package Manager)是一个 JavaScript 包管理工具,也是 Node.js 的默认包管理器。
安装包
npm 安装分为本地安装和全局安装:
- 
npm install <pkg>:本地安装,安装到当前目录的node_modules目录下,可以用npm root查看绝对路径;
- 
npm install -g <pkg>:全局安装,安装到安装Node.js时用npm config set prefix配置的文件夹下,可以用npm root -g查看全局包安装路径,用npm list -g --depth=0来查看所有全局包:PS C:\Users\xxx> npm list -g --depth=0 D:\Program Files\nodejs\node_global # 全局包安装的位置,下面是当前系统中已经安装的全局包 +-- @types/node@24.0.3 +-- @vue/cli-service-global@4.5.19 +-- @vue/cli@5.0.8 +-- axios@1.10.0 +-- ts-node@10.9.2 +-- typescript@5.8.3 `-- vue@3.5.22
本地包只能当前工程使用,全局包可以整个系统上的工程共享。因此对于提供命令行工具的包应当全局安装,其他包本地安装。
本地安装的包又分为开发依赖(devDependencies)和生产依赖(dependencies)。使用 npm install [--save/-S] 是放在生产依赖(dependencies),如果加上 --save-dev/-D 则是放在开发依赖(devDependencies)。而如果不想作为依赖,只是想下下来临时用下,则可以用 npm install --no-save 不作为依赖。
卸载包
# 卸载本地包
npm uninstall/un <pkg>
# 卸载全局包
npm uninstall/un -g <pkg>
package.json
详解package.json和package-lock.json虽然每天都用npm安装包,但是你们对package.js - 掘金

使用 npm init 创建package.json。主要字段如下:
{
  // 基本描述
  "name": "my-awesome-project", // 包名
  "version": "1.0.0", // 语义化版本
  "description": "A project to showcase package.json features", // 包描述
  "keywords": [
    "example",
    "package.json",
    "guide"
  ], // npm搜索时的关键字
  "author": "Jane Doe", // 作者
  "license": "MIT", // 开源许可证
  "private": true, // 指示此包为私有,不会被上传到npm仓库
  "repository": { // 代码仓库
    "type": "git",
    "url": "https://github.com/janedoe/my-awesome-project.git"
  },
  "homepage": "https://www.baidu.com", // 项目主页
  // 依赖配置
  "dependencies": { // 生产环境依赖
    "express": "^4.17.1",
    "mongoose": "^5.12.3"
  },
  "devDependencies": { // 开发环境依赖
    "jest": "^26.6.3",
    "eslint": "^7.22.0"
  },
  "peerDependencies": { // 如果当前包是react的插件,则这里的意思是当前包需要的react版本。不会自动安装这里指定的react
    "react": "^16.0.0"
  },
  "optionalDependencies": { // 在找不到这里的包时,仍允许npm继续往下执行
    "fsevents": "^2.0.0"
  },
  "bundledDependencies": [ // 指定需要随当前包打包时一起被打包的包。这里指定的包必须是在dependencies或devDependencies中出现过的
      "@npm/renderized",
      "@npm/super-streams"
  ],
  "engines": { // 指定项目兼容的Node.js或npm版本
    "node": ">=12.0.0",
    "npm": ">=6.0.0"
  },
  // 文件、目录配置
  "main": "index.js", // 包的入口文件(其中会导出js模块,当使用require导入该包时,返回的就是这里的文件中的module.exports属性的值)
  "browser": "index.js", // 如果包只跑在浏览器,而不允许在服务端使用,用这个属性指定入口文件
  
  // 命令、脚本配置 
  "scripts": { // 可以通过npm run <stage>运行的命令。名字随意,如果恰好是npm的生命周期stage的钩子名称,则会随着npm在特定时机自动执行
    "start": "node index.js",
    "build": "webpack --config webpack.config.js",
    "test": "jest",
    "lint": "eslint ."
  },
  
  // 三方工具的配置
  "eslintConfig": {
    "extends": "eslint:recommended",
    "rules": {
      "no-console": "warn"
    }
  },
  "resolutions": { // (yarn专用)
    "lodash": "4.17.21"
  }
}
模块
Node.js诞生时,Javascript尚未支持模块。所以Node.js利用Javascript的特性,通过函数的方式实现了模块,称为CommonJS模块。而ES6新增的模块语法称为ES模块。
CommonJS模块
在这个规范下,每个.js文件都是一个模块,它们内部各自使用的符号都互不冲突,例如,hello.js和main.js都有全局变量var s = 'xxx',但互不影响。
写一个模块
// hello.js
'use strict';
let g_var = 1.1;
function greet(name) {
    console.log(`greeting ${name}`);
}
// 给module对象的exports成员赋值,表示这些符号要从当前模块导出
module.exports = {
    greet,
    g_var,
}
// 也可以用
exports.greet = greet;
exports.g_var = g_var;
// 但是不可以用
exports = {
	greet,
    g_var
}
使用这个模块
'use strict';
// 引入模块,注意不要文件后缀
const mod1 = require('./hello');
console.log(mod1.g_var)
mod1.greet(s);
注意:
- 默认情况下,Node.js使用的就是 CommonJS 模块(因此node会认为所有 .js后缀的都是 CommonJS 模块),如果已经在 package.json 中配置了"type": "module",则需要将文件后缀改为.cjs才能被认为是 CommonJS 模块。
- require()是Node.js提供的内置函数,非Javascript关键字
- module是Node.js的内置对象,- exports是其成员,非Javascript关键字
- node会依次在 内置模块 -> 全局模块 -> 当前模块下查找hello.js
- 引入的模块内容会在 require()语句处被执行。
Node.js中CommonJS的实现原理
Node.js在调用 require() 函数时,会将其中引入的js文件包装为一个匿名函数,并就地调用:
// const mod1 = require('./hello')的效果简化后大概类似:
const mod1 = (function() {
    // 这里填充hello.js的源码
})();
从而使得原来hello.js中的所有符号都成为mod1的属性,而mod1是引入方定义的不重复名字,自然就避免了符号冲突。
而为了导出模块内符号,Node.js是为每个.js文件都在加载前都预定义了一个module对象:
// 实际上完整的hello.js文件
let module = {
    id: 'hello',
    exports: {}
};
let load = function (exports, module) { // 这里有exports参数是为了方便使用 exports.xxx = xxx 的语法
    // 这里填充hello.js中的源码,其中会为module.exports赋值,从而实现符号的导出
    return module.exports;
}
let exported = load(module.exports, module);
save(module, exported);
然后在导入方使用 require(模块路径) 时,require() 就会查找该文件对应的 module.id ,找到匹配的后就返回对应的 module.exports ,从而就可以使用其中的导出符号了(没赋值给 exports 自然就是未导出符号)。
另外,前面说了还可以直接 exports.xxx = xxx ,但是不允许 exports = {xxx} ,原因也显而易见了:exports = {xxx} 是创建了一个新的对象,赋值给的 exports 由于只是参数的指针拷贝,对其修改无法影响到外部的 module.exports ,自然使得 exports = {xxx} 不会其效果。
ECMAScript模块
ESM模块用 export 关键字导出一个JS对象,用 import 关键字导入一个模块的导出对象。
写一个模块
// hello.mjs
// 'use strict'; // ES6 默认开启严格模式
let g_var1 = 1.1;
export let g_var2 = 2.2;
export function greet1(name) {
  console.log(`greeting1 ${name}`);
}
export function greet2(name) {
  console.log(`greeting2 ${name}`);
}
// 默认导出
// 定义一个临时对象,存储所要导出的所有符号
const hello = {
  g_var1, // g_var1没有用命名导出,但是被默认导出了,所以也是导出符号
  g_var2, // g_var2已经被命名导出过了
  greet: greet1 // greet1默认导出时取了个导出名
};
// 设置这个临时对象位默认导出符号
export default hello;
注意:
- ECMAScript模块的文件名后缀是 .mjs。或者在package.json中配置"type": "module"字段来说明使用的当前包默认是 ES Module(因此node会认为所有.js后缀的都是 ECMAScript模块)。
- ES6 默认开启严格模式
- export 符号定义是命名导出,导入时需要用- import { 对应的符号名 } from ...来导入,使用时直接使用对应的符号名;
- export default 符号集合是默认导出,导入时需要用- import 默认导出名 from ...来导入,使用时需要使用- 默认导出名.符号名来访问;
- 默认导出和命名导出之间不冲突,可以同时写。默认导出的存在意义只是方便导入者不必在 import中全部写出想要导入的符号。
使用这个模块
import hello2 from './hello.mjs'; // 此时导入了g_var1、g_var2、greet1
import { g_var2, greet1, greet2 } from './mod2/hello.mjs'; // 此时导入了g_var2、greet1、greet2
console.log(hello2.g_var1);
console.log(g_var2);
hello2.greet('meha555'); // 只能使用默认导出名了
greet1('meha555');
greet2('meha555');
注意:
- 默认导出名 hello2随便起的,不必和模块定义的.mjs中的export default xxx的符号名称相同;
- 默认导入和命名导入不能混用,如果一个符号是用默认导入来导入的,则他就得按照默认导入的用法 默认导出名.符号名来使用。命名导入同理。
- 一旦一个符号被默认导出(尽管可能也命名导出了),则它就成为了默认导出符号,使用时只能按照默认导出符号的导入和使用方式来使用。
基本模块
内置全局对象
与浏览器提供的Web API提供的全局对象 window 类似,Node.js提供的全局对象是 global 。
process也是Node.js提供的一个对象,它代表当前Node.js进程。
 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号