Node概述
强在大吞吐量,可以接受大量的网络请求,不用切换进程/线程,因为他是异步单线程,也导致不适合处理高并发的大量计算。适合作为中间层(腾讯视频采用的nodejs作为中间层(搜索引擎的优化、首屏的优化))
优点:node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,异步编程,使其轻量又高效。
缺点:单进程,单线程,只支持单核cpu,不能充分的利用多核cpu服务器。一旦这个进程崩掉,那么整个web服务就崩掉了。
全局对象—global
类似window,global.global, window.window, 主要是为了访问自身。global里常用属性如下:
- setTimeout
- setInterval
同浏览器环境下机制一样,但这俩的返回结果不同于浏览器环境里的数字,它俩都是返回一个对象!
- setImmediate
- 类似于setTimeout, 0; 肯定有区别啊!到时在node生命周期里再谈
- console
- __dirname
- 它不是global的属性,那它从哪儿来呢?后续再论
- 表示获取当前模块所在的目录的绝对路径!!很常用
- __filename
- 它也不是global的属性
- 获取当前模块(文件)的绝对路径,常用
- Buffer:类型化数组,继承自Uint8Array。常用
- 它里面的每个数字都是无符号整数8位(8bit),即一个字节,即0-255
- 计算机中存储的基本单位:字节(8bit/byte)
- 使用时、输出时可能要用16进制表示每个数字,即两个0-e的表示一个数字
- 例:Buffer.from(“abcdegf”, “utf-8”),打印得到:
process:它的常用属性/方法如下
- cwd(): 返回当前nodejs进程的工作目录的绝对路径。就是在哪儿运行的node命令的目录路径
- exit(状态码: 默认0): 强制退出node进程,状态码为0时正常退出,为1是错误退出
- argv: 一个数组,[node进程绝对路径,运行文件绝对路径,…命令行参数]
- platform: 获取当前的操作系统信息
- 返回win32,代表编程的平台版本支持32位及以上,不代表我们的电脑是32位的
- kill(pid): 根据进程id杀死一个进程,如果无论它是什么id都要杀死它,该怎么做呢
- 要用到内置库:child process
- env: 系统的环境变量; 是一个对象
Node的模块化细节
目前node默认是commonjs(因为那会还没ES模块化)模块化,但因为ES是标准,所以日后一定会支持好关于模块的查找:
绝对路径:根据绝对路径直接夹在模块/文件
- 相对路径第一种:./或../开头
- 相对于当前模块所在目录,最终也是会转为绝对路径自行查找
- 相对路径第二种,查找规则如下:
- 检查是否是内置模块,如:fs、path等
- 检查当前目录中的node_modules
- 依次检查上层目录中的node_modules
- 找到后转为绝对路径,找不到就报错
- 加载模块
- 关于后缀名,规则如下:
- 如果不提供后缀名,则自动补全(按顺序):js、json、node、mjs
- 关于文件名:
- 如果仅提供目录,不提供文件名,则先 在当前目录自动补全后缀找文件,如果找不到,则找对应目录下的index.js(这个可以配置,默认是index.js)
- package.json中的main字段:
- 表示包(就是第三方库,不包括我们自己的src)的默认入口
- 导入或执行包时,若仅提供目录,则使用main补全入口
- main的默认值为index.js
module对象:
记录了当前模块的信息,它里面的属性有:
- id:这个模块的绝对路径。如果是根入口模块,则为一个点,并且其parent为null
- path:这个模块所在目录的绝对路径 —> __dirname
- exports:导出的对象
- parent:module对象,是该模块的父模块,即该模块被谁引用了
- filename:这个模块的绝对路径 —> __filename
- loaded:该模块是否加载完
- children:module数组,该模块的子模块有哪些
- paths:绝对路径数组,每条记录就是该模块要去哪个node_modules下查
require函数
它的属性如下:
- resolve:一个函数,返回一个绝对路径
- main:module对象,根入口起点模块的信息
- extensions:如何处理四种不同的后缀名文件,.js .json .node .mjs
- cache:对象,存储着哪些模块被缓存了,键值对,键名就是模块绝对路径,值就是modul对象信息
require函数原理
require怎么实现模块化的呢?
当执行一个模块或使用require时,会将模块放置在一个函数环境中
模拟require实现原理:
function require(modulePath) {
//1. 将modulePath转换为绝对路径:D:\repository\NodeJS\源码\myModule.js
//其实这个第一步不简单,因为要判断是哪种路径写法,然后采取不同策略,转换为绝对路径
//2. 判断是否该模块已有缓存
// if(require.cache["D:\\repository\\NodeJS\\源码\\myModule.js"]){
// return require.cache["D:\\repository\\NodeJS\\源码\\myModule.js"].result;
// }
//3. 读取文件内容, I/O流
//4. 把文件内容包裹到一个函数中
// 所以说这个__dirname和__filename不是global属性的原因就在这,因为我们在上面得到了绝对路径,就可以传给这个函数
function __temp(module, exports, require, __dirname, __filename) {
console.log("当前模块路径:", __dirname);
console.log("当前模块文件:", __filename);
exports.c = 3;
module.exports = {
a: 1,
b: 2
};
this.m = 5;
}
//6. 创建module对象
module.exports = {};
const exports = module.exports;
//7. 绑定this,并且执行包裹函数
__temp.call(module.exports, module, exports, require, module.path, module.filename)
//8. 最终返回module.exports
return module.exports;
}
所以,在一个文件最初,module.exports === exports === this,但是之后就不一定了,看个人操作
node中this指向
- 全局中的this,综上,默认指向module.exports,在无导出时,就是一个空对象 {}
- 函数中的this,默认指向 global
- 构造函数中的this,默认指向其实例
【扩展】Node中的ES模块化
目前,在Node中,的ES模块化仍然处于试验阶段。模块要么是commonjs,要么是ES
因为ES不是包裹在函数环境中,虽然v8能这么做,但是ES模块化不是这样,
转变为ES模块:
- 文件后缀名为.mjs
- 离得最近的package.json中type设为module,但这样那个目录都成.mjs不太好
- 当使用ES模块化运行时,启动命令必须添加—experimental-modules标记。
使用了ES模块,因为它不是在函数环境内运行,而是在引擎内部搞定的。"scripts": {
"start": "node --experimental-modules index.mjs"
},
部分commonjs部分ES模块化,则大部分情况下,是要出问题的!!
webpack是解决了,它把所有东西都放到了函数环境里
ES模块的异步加载:
import ("./a.mjs").then(r => console.log(r));
基本内置模块
os
os常用的属性/方法:
- EOL(end-of-line):mac、unix、Linux下是 \n,windows下是\r\n
- arch(): 获取cpu的架构名
- cpus(): 获取cpu每个内核的信息
- freemem(): 得到当前剩余空闲内存大小, 单位:字节
- homedir(): 获取用户目录的绝对路径
- hostname(): 获取主机名
-
path—常用
path模块常用属性/方法如下:注意:path里的方法都不会管传入路径对应模块是否真实存在!就当成字符串!
basename(): 获取整个路径中的最后一部分,比如:index.html
- delimiter: 分隔符,不同路径间的分割。windows上是 ; linux, mac上就是 :
- sep: 分隔符,一个路径内部的分割。windows上是 \ linux, mac上就是 /
- dirname(): 获取目录的绝对路径
- extname(): 得到传入路径的最后一个.的后缀名,比如:.js.mjs —>> .mjs
- join(): 可以传入几段路径,它会将其拼接为完整路径(不同操作系统值不一样)
- normalize(): 帮我们规范化一个路径
- relative(): 得到第二个路径相对于第一个路径 的 相对路径
resolve(): 返回一个绝对路径
// 这样的话无论在哪儿启动的node命令,都会正确得到该a.js的绝对路径。因为它也会在内部拼接
const absPath = path.resolve(__dirname, "./a.js");
关于./
路径只有在require里面才是相对于当前目录。在其他任何地方,都是相对于启动node命令的目录的路径!即process.cwd()
url
const URL = require("url");
const url = new URL.URL("https://nodejs.org:80/a/b/c?t=3&u=5#abc");
// const url = URL.parse("https://nodejs.org:80/a/b/c?t=3&u=5#abc");//它内部帮我们调用URL构造函数
console.log(url);
console.log(url.searchParams.has("a"));//searchParams是一个类似Map的东西,因为那会还没ES6+
const url = URL.format(obj);//将url对象格式的普通对象转为url链接
util
util的常用方法如下,主要当时还没有ES6^:
callbackify(original): 传入一个异步函数,返回一个回调模式的新函数 ```javascript async function delay(duration = 1000) { return new Promise(resolve => { setTimeout(() => {
resolve(duration);
}, duration); }); }
const delayCallback = util.callbackify(delay);
delayCallback(500, (err, d) => { console.log(d);//node习惯把error定义在第一个参数 });
- **promisify():** 传入一个回调模式的函数,返回一个新的异步函数
```javascript
function delayCallBack(duration, callback) {
setTimeout(() => {
callback(null, duration);
}, duration);
}
const delay = util.promisify(delayCallBack);
//delay(500).then(d => console.log(d));
(async () => {
const r = await delay(500);
console.log(r);
})();
- isDeepStrictEqual(obj1, obj2): 将两个对象深度严格比较
- inherits(子类,父类): ES6出来之前还比较常用