Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node 标准库常用语法 #77

Open
FrankKai opened this issue Jun 6, 2018 · 7 comments
Open

node 标准库常用语法 #77

FrankKai opened this issue Jun 6, 2018 · 7 comments

Comments

@FrankKai
Copy link
Owner

FrankKai commented Jun 6, 2018

标准库可以说是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
@FrankKai
Copy link
Owner Author

FrankKai commented Jun 6, 2018

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

@FrankKai
Copy link
Owner Author

FrankKai commented Jun 6, 2018

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实验

能力有限,未完待续。

@FrankKai
Copy link
Owner Author

FrankKai commented Jun 6, 2018

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,去开一个子线程,使用事件订阅发布的形式实现多线程通信。

@FrankKai
Copy link
Owner Author

FrankKai commented Jun 15, 2018

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

由此可见:

@FrankKai
Copy link
Owner Author

FrankKai commented Aug 4, 2018

crypto

image

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');

@FrankKai
Copy link
Owner Author

FrankKai commented Apr 29, 2019

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 });

@FrankKai
Copy link
Owner Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant