FrankKai.github.io
FrankKai.github.io copied to clipboard
node 标准库常用语法
标准库可以说是node的基础,非常重要。
- path.join()与path.resolve()什么区别?
- process.cwd()
- fs.readFile()与fs.readFileSync()有什么区别?
- os.cpus()的times返回数据的user mode,nice mode,sys mode,idle mode,irq mode之间的区别?
- crypto
- REPL
- require.cache
path.join()与path.resolve()什么区别?
一个demonstration就可以说明主要问题:
const path = require('path');
const urlJoin = path.join(__dirname, '../../');
const urlResolve = path.resolve(__dirname, '../../');
console.log(urlJoin,urlResolve);
//urlJoin: /Users/frank/Desktop/
//urlResolve: /Users/frank/Desktop
细心的你一定发现了,join返会的路径以分隔符"/"结尾,而resolve以目录名结尾。
这是在传入"../../"的情况下,那如果直接传入目录名呢?
const urlJoinPersonal = path.join(urlJion,"./personal/");
const urlResolvePersonal = path.resolve(urlResolve,"./personal/");
console.log(urlJoinPersonal,urlResolvePersonal);
//urlJoinPersonal: /Users/frank/Desktop/personal/
//urlResolvePersonal: /Users/frank/Desktop/personal
path.join会始终保留路径原来的模样,预留一个空间。path.resolve会自动把多余的/去掉,从而保证是一个有效的目录。
因此我们可以做出总结:
- path.join适用于目录中确定有较多文件时,从而进行规范化,它得到的可以是filepath或者filepath + "/"。
- path.resolve适用于访问特定的确定的目录,从而保证绝对路径,它得到的只能是filepath。
path.join()
主要用于规范化路径。
- 参数是[...paths],path类型必须为string
- 根据最后出现的"/" 分隔符进行拼接path 片段
- 零宽字符串返回 ".",表示当前目录
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
// Returns: '/foo/bar/baz/asdf'
path.join('foo', {}, 'bar');
// throws 'TypeError: Path must be a string. Received {}'
path.resolve()
主要用于返回绝对路径。
- path.resolve()方法可以将路径解析成绝对路径
- 传入路径从右至左解析,遇到第一个绝对路径是完成解析,例如path.resolve('/foo', '/bar', 'baz') 将返回 /bar/baz
- 如果传入的绝对路径不存在,没有传参,或者没有"/"时,当前目录将被返回
path.resolve('/foo/bar', './baz');
// Returns: '/foo/bar/baz'
path.resolve('/foo/bar', '/tmp/file/');
// Returns: '/tmp/file'
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
// if the current working directory is /home/myself/node,
// this returns '/home/myself/node/wwwroot/static_files/gif/image.gif'
关于path.join与path.resolve,部门老大刚才从源码角度带着我理解了一波,源码地址在这里:https://github.com/nodejs/node/blob/master/lib/path.js
process.cwd()
由上面的path.js源码拓展出一个process.cwd()的问题。
老大说:
- 正常情况下,process.cwd()返回的是当前的工作目录。也就是最顶级的node index.js的路径。不一定是文件所在路径。
- 但是在使用pm2监控进程时,process.cwd()返回的是process.json的路径,并不是启动文件index.js或者app.js的路径。
由于工科男的执着,我们对上面的结论做两次实验:
普通实验
-path
-dir
foo.js
bar.js
index.js
index.js
const foo = require('./dir/foo');
const bar = require('./dir/bar');
console.log("dir/foo.js:",foo);
console.log("dir/bar.js:",bar);
console.log("index.js",process.cwd())
foo.js
function foo (){
return process.cwd();
}
module.exports = foo();
bar.js
function bar (){
return process.cwd();
}
module.exports = bar();
node index.js
输出结果:
dir/foo.js: /Users/frank/Desktop/path
dir/bar.js: /Users/frank/Desktop/path
index.js /Users/frank/Desktop/path
实验表明,正常情况下,process.cwd()返回的是当前的工作目录。也就是最顶级的node index.js的路径。不一定是文件所在路径。
正确。
2019.7.31更新
// cwd.js
console.log(process.cwd());
这个cwd()指的是node进程的工作目录: 假如是在/Users/frank/foo/bar目录,node cwd.js 返回/Users/frank/foo/bar。 假如是在/Users/frank/foo/bar/src目录,node ../cwd.js,返回的是/Users/frank/foo/bar/src。 假如是在/Users/frank/foo目录,node ./bar/cwd.js,返回的是/Users/frank/foo。
PM2实验
能力有限,未完待续。
fs.readFile()与fs.readFileSync()有什么区别?
二者返回的结果都是目录下文件名数组,最为关键的地方在于Sync关键字。在nodejs中,有大量的*Sync类型的标准库api,就拿fs来说,就有下面这么多。
without sync | with sync |
---|---|
fs.access | fs.accessSync |
fs.appendFile | fs.appendFileSync |
fs.chmod | fs.chmodSync |
fs.chown | fs.chownSync |
fs.close | fs.closeSync |
fs.copyFile | fs.copyFileSync |
fs.exists | fs.existsSync |
fs.fchmod | fs.fchmod Sync |
fs.fchown | fs.fchownSync |
fs.fdatasync | fs.fdatasyncSync |
fs.fsync | fs.fsyncSync |
fs.ftruncate | fs.ftruncateSync |
fs.futimes | fs.futimesSync |
fs.lchmod | fs.lchmod Sync |
fs.lchown | fs.lchownSync |
fs.link | fs.linkSync |
fs.lstat | fs.lstaSync |
fs.mkdir | fs.mkdirSync |
fs.mkdtemp | fs.mkdtempSync |
fs.open | fs.openSync |
fs.readdir | fs.readdirSync |
fs.readFile | fs.readFileSync |
fs.readlink | fs.readlinkSync |
fs.realpath | fs.realpathSync |
fs.realpath.native | fs.realpath.nativeSync |
fs.rename | fs.renameSync |
fs.rmdir | fs.rmdir Sync |
fs.stat | fs.statSync |
fs.symlink | fs.symlinkSync |
fs.truncate | fs.truncateSync |
fs.unlink | fs.unlinkSync |
fs.utimes | fs.utimesSync |
fs.writeFile | fs.writeFileSync |
*Sync 类型的是同步函数,它们会立即返回一个值,而其它的是异步函数,返回的是undefined,但是可以接收一个callback去处理它们的响应。
拿fs.readFile与fs.readFileSync来说。
# 同步 synchronous
data = fs.readFileSync ( filename )
# 现在我就可以使用data了
console.log ( data )
# 异步 asynchronous
fs.readFile ( filename, (err, data) =>{
# 现在data变量在回调函数上下文中是可用的
# **但是在fs.readFile函数的上下文中是不可调用的**
console.log(data)
})
虽然同步的方式比较直观,但是对于在node进程同步读取文件的时候,进程处于阻塞状态,不能去做其它事情。而采用异步的方式是非常畅通的。
再来想个问题:为什么不把fs.readFile写成fs.readFileAsync,这样更直观啊?
因为在nodejs的场景中,会大量用到异步的场景,这也是node的优势所在,相比fs.readFileAsync,fs.readFile的写法更简洁,少写了5个字母,可以略微提升coding的速度。
-
nodejs中同步的方式不好吗? 运行时不太好,启动时很有必要。 在node应用的启动过程中,有些操作必须是同步阻塞的,因为后面的一些require需要保证被require的对象的存在,才能进行后续的操作。举个例子来说,应用启动可能需要mkdir,可能需要遍历目录,而这些操作必须是同步的。
-
nodejs运行时不能使用同步吗? 不完全是。 如果一定要使用同步阻塞的方式,避免在阻塞主线程,可以使用child_process.fork,去开一个子线程,使用事件订阅发布的形式实现多线程通信。
os.cpus()的times返回数据的user mode,nice mode,sys mode,idle mode,irq mode是什么?
Operating modes
ARM7TDMI操作模式 User Modes是通常的ARM 程序执行状态,可以用于执行大多数应用程序。 Fast Interrupt (FIQ) mode支持数据转换或者渠道进程。 Interrupt (IRQ) mode被用于general-purpose中断处理。 Supervisor mode是操作系统的保护模式。 Abort mode在数据或者指令Prefetch Abort后输入。 System mode是一种操作系统的特权模式。
Mode | Mode identifier |
---|---|
User | usr |
Fast interrupt | fiq |
Interrupt | irq |
Supervisor | svc |
Abort | abt |
System | sys |
Undefined | und |
由此可见:
- CPU核的mode是一种ARM处理器模式,与操作系统有关,是一种切换操作系统运行时的方式。
- 并且每种模式都有对应的模式识别器。
- 每个核有多种操作模式。
- 题目中的几种mode是我的iMac CPU 下的8个核中的1个核的模式类型,由它的ARM型号决定 http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0210c/Cihhcjia.html
crypto
Crypto Module
crypto模块提供了密码学加密函数集,包括OpenSSL的hash,HMAC,cipher(暗号),decipher(破译),sign(签名)以及认证函数。
const crypto = require('crypto');
const secret = 'abcdefg';
const hash = crypto.createHmac('sha256', secret)
.update('I love you')
.digest('hex');
console.log(hash);
核心方法:createHmac(algorithm, key ,[options])
algorithm <string>
key <string> | <Buffer> | <TypedArray> |<DataView>
options <Object> stream.transform options
Returns: <Hmac>
crypto.createHmac()方法会创建一个Hmac实例。Hmac对象使用new操作符是实例不了的。 使用hmac.update和hmac.digest()生成的Hmac码:
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', 'a secret');//创建一个key为a secret的Hmac实例。
hmac.update('some data to hash');//使用给定的数据更新Hmac实例内容,数据类型与key类型一致。
console.log(hmac.digest('hex'));
// Prints:
// 7fd04df92f636fd450bc841c9418e5825c17f33ad9c87c518115a45971f7f77e
引申基础:
- 什么是TypedArray类型? 类数组底层二进制数据缓冲区。(binary data buffer)。 这种类型的设计用意为:反映数组的内容的视图,它的设计用意是:同样一份数据缓冲区中的内容可以用不同的格式来读取。 Typed Array包含多种类型, Init8Array(),Unit16Array(),Float32Array()等等。 在学习《HTML5 Canvas核心技术》部分有遇到,在201页,getImageData()方法返回的ImageData对象,用ArrayBuffer存放数据部分,而这个ArrayBuffer本质上是一种TypedArray类型,因为ArrayBuffer是Typed Array Object类型的一种。
- 什么是DataView类型? DataView视图提供了一个低级接口,用于在ArrayBuffer中读取和写入多个数字类型,而不管平台的字节顺序如何。
- 什么是Hmac对象? Hmac Class是一个用于创建加密HMAC码的工具。可以通过2种方式使用:
- 可读 可写的stream,写入数据以在可读侧生成计算的HMAC码
- 使用hmac.update()以及hmac.digest()方法,生成计算的HMAC码
实践:
- 阿里云LMQ 传入secretKey和groupId,加密算法为sha1,最终生成的Hmac码为base64格式。
return crypto.createHmac('sha1',secretKey).update(groupId).digest().toString('base64');
REPL
repl模块的全称是Read-Eval-Print-Loop(REPL),既可以独立使用也可以被其他应用程序使用。
const repl = require('repl');
设计初衷
- repl导出repl.REPLServer类,运行时repl.REPLServer接收用户输入的内容,根据用户自定义函数执行输入,然后输出结果。
- 输入输出可能来自stdin和stdout,或者可以连接到任何Node.js流。
- repl.REPLServer支持
自动输入、简化Emacs 式行编辑、多行输入、ANSI式输出、保存当前REPL会话状态、错误恢复,以及定制执行函数
命令和特殊键
下面的命令被REPL实例支持:
- .break-输入多行表达式以后,可以键入.break命令(键入
<ctrl>-C
)中止输入或者表达式的处理。 - .clear-重置REPL的context为空对象,清除所有已经输入的多行表达式,类似
<ctrl>-U、<command>-K
。 - .exit-关闭I/O stream,退出REPL。
- .help-显示出所有REPL的命令。
- .save-保存当前的REPL表达式到文件。>.save ./file/to/save.js
- .load-加载文件到当前的REPL会话。>.load ./file/to/load.js
- .editor-开启编辑模式。(
<ctrl>-D结束,<ctrl>-C取消
)
Last login: Mon Apr 29 11:35:27 on ttys001
frankdeiMac:~ frank$ node
> .editor
// Entering editor mode (^D to finish, ^C to cancel)
function welcome(name) {
return `Hello ${name}!`;
}
welcome('Node.js User');
'Hello Node.js User!'
>
下面的快捷键功能如下:
-
<ctrl>-C
键入一次,相当于一次.break,键入两次,相当于.exit。 -
<ctrl>-D
相当于.exit。 -
<tab>
- 会显示global以及本地作用域下的变量。输入变量名的一部分会自动补全。
默认执行
默认情况下,repl.REPLServer的所有实例会用一个执行函数执行js表达式并且允许调用nodejs内建模块的权限。这个默认行为可以通过传入一个可执行函数去覆盖。
js表达式
frankdeiMac:~ frank$ node
> 1+1
2
> const m =2
undefined
> m+1
3
>
全局和本地作用域
默认计算器赋权所有全局作用域中的变量。可以通过给REPLServer的context对象复制显式将变量暴露给REPL。 REPL实例上下文的属性:
> .editor
// Entering editor mode (^D to finish, ^C to cancel)
const repl = require('repl');
const msg = 'message';
repl.start('> ').context.m = msg;
> 'message'
可以通过Object.defineProperty()设置属性为只读:
Object.defineProperty(repl.start('> ').context, 'm', { enumerable: true, value: msg });
require.cache
在写vscode插件的过程中,发现通过require获取到的文件是旧的,不能获取到最新的。
原因是cjs的require有缓存机制,因此可以通过下面这种方式去获取:
// 避免require缓存
delete require.cache["/client/static/locales/zh/translation.json"];
const data = require("/client/static/locales/zh/translation.json");
Modules are cached in this object when they are required. By deleting a key value from this object, the next require will reload the module.