Enode.js 是基于v8 JavaScript引擎的 JavaScript运行时环境
任何可用的 JavaScript所有实现的应用最终都会实现 使用 JavaScript 实现
1.全局对象和模块化开发
1.1 给node 程序传递参数
当我们 执行 node ./index.js 其实后面可以拼接参数,参数就是 node 的全局对象 process ,比如 node ./index.js ddg age=20
argv: [ 'E:\\node\\node.exe', 'F:\\web前端开发\\自学\\coderwhy-node\\code\\01_learn-node\\02_给node传递参数\\index.js', 'ddg', 'age=20' ],
// console.log(process); // process 是 node 的 全局对象 // process 第一层节点,有一个 argv 节点, 它表示参数 // argv : argument vector 缩写,表示 具体参数进入 console.log(process.argv); process.argv.forEach(item => {
console.log(item); })
1.2 node 的输出方式
- console.log() 输入内容最常用的方式 console.log
- 清空控制台 console.clear
- console.trace() 查看函数的 调用栈
// console.log(process); // process 是 node 的 全局对象 // process 第一层节点,有一个 argv 节点, 它表示参数 // argv : argument vector 缩写,表示 具体参数进入 console.log(process.argv); console.log(process.argv[2]); console.log(process.argv[3]); console.clear() // 清空 控制台 process.argv.forEach(item => {
console.log(item); }) function foo() {
ar()
}
function bar() {
console.trace()
// trace 是可以打印 函数 的 调用栈的
// 输出结构如下
// at bar 表示 这个输出 在 bar 函数 里面执行
// at foo 表示 bar 函数 是在 foo 函数里面被调用
// at Object 表示 foo 在全局调用, node 会把他当成 匿名函数调用
/* Trace at bar (F:\web前端开发\自学\coderwhy-node\code\01_learn-node\02_给node传递参数\02_Node程序的输出.js:20:13) at foo (F:\web前端开发\自学\coderwhy-node\code\01_learn-node\02_给node传递参数\02_Node程序的输出.js:17:5) at Object.<anonymous> (F:\web前端开发\自学\coderwhy-node\code\01_learn-node\02_给node传递参数\02_Node程序的输出.js:36:1) at Module._compile (internal/modules/cjs/loader.js:1133:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1153:10) at Module.load (internal/modules/cjs/loader.js:977:32) at Function.Module._load (internal/modules/cjs/loader.js:877:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12) at internal/main/run_main_module.js:18:47 */
}
foo()
node的输出方式官方文档
1.3 node 全局对象
1.3.1 特殊的全局对象
这些全局对象实际上是模块中的变量,只是每个模块都有,看起来像是全局变量,在命令行交互中是不可以使用的
包括 __dirname、__filename、exports、module、require()
- dirname: 获取当前文件所在的路径,不包括后面的文件名
- filename 获取当前文件所在的路径和文件名称,包括文件名称
console.log(__dirname);
// F:\web前端开发\自学\coderwhy-node\code\01_learn-node\03_node中的全局变量
console.log(__filename);
// F:\web前端开发\自学\coderwhy-node\code\01_learn-node\03_node中的全局变量\01_特殊的全局对象.js
1.3.2 常见全局对象
- process 提供了 node 进程中相关的信息
- 后面在项目中,我也会讲解,如何将一些环境变量读取到 process 的 env 中
- node 的运行环境、参数信息等等
可以看官方文档
- setTimeout(callback,delay[,…args]):callback,在 delay毫秒后执行一次
- setIneterval(callback,delay[,…args]):callback 每 delay 毫秒重复执行一次
- setImmediate(callback[,…args]):callback I/O 事件后的回调的 “立即” 执行
- process.nextTick(callback[,…args]):添加到下一次 tick 队列中
global 全局对象
console.log(global);
console.log(global.process);
// 定义变量
var name = "呆呆狗"
console.log(name);
console.log(global.name);// undefined
// node 的顶级对象 是 global ,浏览器的顶级对象是 window,我们在浏览器运行的js文件中var定义的全局变量,是会被挂载到 window 上的,而 global 不会
// global 默认会挂载 process
// 再 node 中,其实每一个文件都是一个模块
2、js 的模块化
2.1 common.js 和 node
commonJS 是一个 规范,最初提出来是再浏览器以外的地方使用,并且当时被命名为 Server.js,后来为了体现他的广泛性,修改为 CommonJS , 平时我们也会简称为 CJS
- node 是 commonJS 在服务器端 一个具有代表性的实现
- Browserify 是 CommonJS 在浏览器中的一种实现
- webpack 打包工具具备对 CommonJS 的支持和转换
- exports 和 module.exports 是负责对模块中的内容进行导出
- require 函数 可以帮助我们导入 其他模块 (自定义模块、系统模块、第三方库模块) 中的内容
// bar.js
const name = "coderwhy"
const age =20
function sayHello(name){
console.log('hello' + name);
}
exports.name = name
exports.age = age
exports.sayHello = sayHello
// main.js
const bar = require('./bar')
// 就相当于 bar = exports
// bar 也可以 换成 { name . age .sayHello } 解构赋值
console.log(bar);//{ name: 'coderwhy', age: 20, sayHello: [Function: sayHello] }
console.log(bar.name);
console.log(bar.age);
bar.sayHello('ddg')
2.2 module.exports
- commonJS 中是没有 module.exports 的概念的
- 为了实现模块的导出,node 中使用的是 module 的类,每一个模块都是 module 的一个实例 也就是 module
- 在node 中真正用于导出的其实根本不是 exports 而是 module.exports
- 因为 module 才是真正的实现者
2.3 require
查找规则
- require(传入的是核心模块 fs path 等等) ,则会直接返回,并且停止查找
- require(./或者…/ 或者 、/ 根目录) 开头的。没有后缀名则会 直接查找 这个文件 ;查找 文件.js文件 ; 查找 文件.json 文件;查找 文件.node 文件。 如果直接传一个目录,则会先找这个目录下的 index.js 文件 ; index.json 文件 ; index.node 文件。 如果 这两者都没找到 则报错
- 直接是一个字符串 比如 require(‘abc’) ,abc 不是核心模块,则会从当前目录下的 node_modules 文件夹下查找,没有找到 再去上一级目录下的 node_modules 下查找,直到 根目录下的 node_modules 根目录在没找到则会报错
paths: [
'F:\\web前端开发\\自学\\coderwhy-node\\code\\01_learn-node\\04_js-module\\02_commonjs\\node_modules',
'F:\\web前端开发\\自学\\coderwhy-node\\code\\01_learn-node\\node_modules',
'F:\\web前端开发\\自学\\coderwhy-node\\code\\node_modules',
'F:\\web前端开发\\自学\\coderwhy-node\\node_modules',
'F:\\web前端开发\\自学\\node_modules',
'F:\\web前端开发\\node_modules',
'F:\\node_modules'
]
// console.log(module)
2.4 模块加载的过程
- 模块在被第一次引入的时候,模块中的js代码会被运行一次。是同步执行的
- 模块被多次引入时,会缓存,最终只会被加载(运行)一次,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZKm8W7UQ-1625664086040)(F:\web前端开发\自学\coderwhy-node\coderwhy-node.assets\image-20210629185221783.png)]
此图中,就是循环引入, 就是 图 数据结构, 图结构在遍历的时候,node 采用了 深度优先搜索,所以 main => aaa => ccc => ddd => eee
走到 eee 里面没有引入了,会返回上一层,看看上一层还有没有引入其他的,没有在返回,直到 main,看到还引入了 bbb 则 引入 bbb => ccc
2.5 AMD 规范
AMD 主要是应用于浏览器的一种模块化规范
它采用的是 异步加载模块
事实上 AMD 的规范 还要早于 CommonJS,但是 CommonJS目前依然在被使用,而 AMD 使用的较少了
用起来 是 有点复杂了。需要引入一个 require.js
2.6 CMD 规范
也是一个异步加载模块
但是他将 commonJS 的 优点吸收过来了
seajs 需要用这个依赖文件
2.7 es module
他是 js 的一个模块化系统
- 它采用了 import 和 export 关键字
- 采用编译期的静态分析,并且也加入了动态引用的方式
- export 负责将模块的内容导出
- import 负责从其他模块导入内容
导出的方式
- export const age = 20
- export { } 这只是一个大括号,不是一个对象,这个大括号放置要导出的引用列表
- export { name as myName } 可以给 name 起一个别名
导入的方式
- import { name } from ‘路径’ 按需导入
- import { name as heName } from ‘路径’ 也可以起别名
- import * as foo from ‘路径’ 把导出的东西,都放到 foo 对象里面
在开发和封装功能的时候,通常希望将暴露 的所有接口放到一个文件中
export {
} from './bar.js'
// 这样表示的是,先导入,然后 再导出
默认导出
export default function (){}
import format from ‘./’
我们可以直接给这个默认导出的函数起一个名字
如果 把 import 加载一个模块,放到 逻辑代码中,是不可以的
因为 es module 在被 JS 引擎解析时,就必须要知道他的依赖关系,由于这个时候,无法执行,所以不能确定依赖关系,所有会报错
let flag = true
if (flag) {
import('./bar.js').then(aaa => {
console.log()
})
}
3、常见内置模块
3.1 path
const path = require('path')
// 1、获取路径的信息
const filepath = '/User/ddg/abc.md'
console.log(path.dirname(filepath));
// /User/ddg
console.log(path.basename(filepath));
// abc.md
console.log(path.extname(filepath));
// .md
// 2、join 路径拼接
const basepath = "/User/ddg"
const filename= '/abc.md'
const joinUrl = path.join(basepath,filename)
console.log(joinUrl);// \User\ddg\abc.md
// 3、resolve 路径拼接
// 它会解析第一个参数 的 / ./ ../ ,
// / 表示这个文件地址的根磁盘
// 如果第一个参数 没有加 / ./ ../ 那么,则会拼接上 绝对路径
// 如果第一个往后的参数有 / 则会 直接返回最后一个参数的路径
const joinUrl2 = path.resolve(basepath,filename)
console.log(joinUrl2);// F:\User\ddg\abc.md
3.2 fs
const fs = require('fs')
// 读取文件的信息
const filepath = './abc.txt'
// 1、同步操作 读取文件信息
const info = fs.statSync(filepath)
// console.log('后续要执行的代码会被阻塞');
// console.log(info);
// 2、异步操作 读取文件信息
fs.stat(filepath, (err, info) => {
if(err){
console.log(err);
return
}
console.log(info);
})
// console.log('后续要执行的代码会被阻塞');
// 3、promise
fs.promises.stat(filepath).then(info=>{
console.log(info);
}).catch((err)=>{
{
console.log(err);}})
console.log('后续要执行的代码会被阻塞');
const fs = require('fs')
fs.open('./abc.txt', (err, fd) => {
if (err) {
console.log(err);
return
}
// 通过描述符获取信息
fs.fstat(fd, (err, info) => {
if(err){
console.log(err);
return
}
console.log(info);
})
})
const fs = require('fs')
// 文件写入
fs.writeFile('./abc.txt','呆呆狗2',{
flag:"a"},err=>{
console.log(err);
})
/* 1、 w 打开文件写入 默认值。 会覆盖掉原先的内容 2、 w+ 打开文件进行读写,如果不存在则创建文件 3、 r+ 打开文件进行读写,如果不存在那么抛出异常 4、 r 打开文件读取,读取时的默认值 5、 a 打开要写入的文件,将流放在文件末尾。如果不存在则创建文件 6、 a+ 打开文件以进行读写,将流放在文件末尾,如果不存在则创建文件 */
// 文件读取
fs.readFile('./abc.txt',{
encoding:'utf-8'},(err,data)=>{
console.log(data);
// 不设置 encoding:'utf-8' 就显示二进制的编码
})
const fs = require('fs')
// 1、创建文件夹
const dirname = './ddg'
if (!fs.existsSync(dirname)) {
fs.mkdir(dirname,err=>{
console.log(err);
})
}
// 2、读取文件夹中的所有文件
fs.readdir(dirname,(err,files)=>{
console.log(files);// [ 'a.txt', 'b.txt' ]
})
// 3、重命名 文件
// 旧路径 新路径 回调函数
fs.rename('./ddg','./ddg2',err=>{
console.log(err);
})
3.3 events 模块
node中的核心API 都是基于异步事件驱动的。
在这个体系中,某些对象(发射器(Emiteers))发出某一个事件
我们可以监听这个事件 (监听器 Listeners),并且传入的回调函数,这个回调函数会在监听到事件时调用
发出事件和监听事件 都是通过 EventEmitter 类 来完成的,他们都属于 events 对象
emitter.on(eventName,listener): // 监听事件,也可以使用 addListener
emitter.off(eventName,listener): // 移除事件,也可以使用 removeListener
emitter.emit(eventName[,...args]): // 发出事件,可以携带一些参数
4、包管理工具
4.1 配置文件
每一个项目都对应一个配置文件(package.json),包括项目名称、使用的插件、版本号、项目描述等等
npm init -y
4.2 配置文件常见的属性
- scripts属性,用于配置一些脚本命令,以键值对的形式存在,配置后我们可以通过 npm run 命令的key 来执行这个命令
- dependencies 属性 是无论开发环境还是生产环境都需要的依赖包
- devDependencies属性 是 生产环境不需要的依赖包
4.3 版本管理的问题
比如版本 2.23.8
- 第一个数字,表示可能不兼容之前的版本,这个版本号一般很少修改
- 第二个数字,向下兼容的功能性新增,就是 新功能增加,但是兼容之前的版本
- 第三个数字,前面两个没有任何修改,但是 修复了 之前的 bug
^2.23.8 表示 第一个数保持不变的, 第二个和第三个 永远安装最新的版本
~2.23.8 表示 第一个和第二个保持不变的,第三个永远安装最新的版本
5、buffer 和 浏览器的事件循环
5.1 数据的二进制
计算机追踪所有的内容:文字、数字、图片、音频、视频都终会使用二进制来表示
5.2 buffer 和二进制
对于前端来说,很少会和二进制打交道,但是对于服务器端为了做很多的功能,必须直接去操作二进制的数据
所以 node 为了 可以方便开发者完成更多功能,提供给了我们一个类 Buffer , 并且它是全局的
Buffer 看成是一个存储二进制的数组,数组中的每一项,可以保存 8位 二进制
5.3 Buffer 对字符串的存储
const message = "hello world"
// 第一种 1、创建 Buffer
// 打印出来的,是和 message 一一对应的,打印出来的 每一组数字,是 16进制的
//const buffer = new Buffer(message)
//console.log(buffer); //<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
// 第二种 2、
const buffer2 = Buffer.from(message)
console.log(buffer2);
5.4 Buffer 对文字的存储
const message = "呆呆狗"
// 其实 一个汉字 对应 三个字节码,
const buffer2 = Buffer.from(message)
console.log(buffer2);
// <Buffer e5 91 86 e5 91 86 e7 8b 97>
// 解码
console.log(buffer2.toString());// 呆呆狗
5.5 对 文件的处理
const fs = require('fs')
// 1、对文本文件的操作
fs.readFile("./foo.txt", {
encoding: 'utf-8' }, (err, data) => {
console.log(data);
// 如果不传 utf-8 输出的,其实是 Buffer。本质上,我们读取到的东西都是二进制
})
// 2、对 图片的操作
fs.readFile('./one.png', (err, data) => {
console.log(data); // 读取到的 也是 Buffer
// 我们可以在写入 文件
fs.writeFile('./one_copy.png', data, err => {
// 这里的 data 表示要写入谁 就是 图片的 Buffer
console.log(err);
})
})
// npm i sharp 先安装依赖,然后 裁剪,然后输出
sharp('./one.png').resize(200,200).toFile('./baz.png')
6、事件循环
6.1 事件循环定义
事件循环 可以理解成 我们编写 js 代码 和 浏览器 或者 node 之间的一个桥梁
浏览器的事件循环是 一个我们编写的 js 代码和 浏览器 API 调用 的一个桥梁,桥梁之间他们通过回调函数进行沟通
node 的事件循环是一个我们编写 js 代码 和 系统调用 (file、system、network) 之间的一个桥梁,桥梁 之间他们通过回调函数进行沟通的
6.2 进程和线程
进程: 计算机已经运行的程序
线程:操作系统能够运行运算调度的最小单位
操作系统就像是一个工厂,工厂里面有很多车间,这个车间就是进程,工人就是线程
6.3 多进程多线程开发
6.4 浏览器 事件循环 面试题
setTimeout(function () {
console.log("set1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2");
});
});
new Promise(function (resolve) {
console.log("pr1");
resolve();
}).then(function () {
console.log("then1");
});
setTimeout(function () {
console.log("set2");
});
console.log(2);
queueMicrotask(() => {
console.log("queueMicrotask1")
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
// pr1
// 2
// then1
// queuemicrotask1
// then3
// set1
// then2
// then4
// set2
分析:
- 先输出 pr1 , then1 进入 微任务队列
- 输出 2 , queueMicrotask 创建一个微任务
- then3 加入微任务
- 主线程 执行完毕,执行 第一波微任务 依次输出 then1,queueMicrotask,then3,此时已经没有微任务了,继续执行下一个宏任务
- 第一个定时器, 输出 set1 ,把 第一个定时器的 then 加入微任务队列
- 输出 then2
- 这一次的宏任务执行完毕,继续执行这一次的微任务,输出 then4
- 执行第二个定时器
async function async1 () { console.log('async1 start') await async2(); console.log('async1 end') } async function async2 () { console.log('async2') } console.log('script start') setTimeout(function () { console.log('setTimeout') }, 0) async1(); new Promise (function (resolve) { console.log('promise1') resolve(); }).then (function () { console.log('promise2') }) console.log(