From 0dee69d4620b5b75983cd6f7dca655494bdc2b11 Mon Sep 17 00:00:00 2001 From: Lellansin Date: Wed, 22 Feb 2017 16:27:52 +0800 Subject: [PATCH] Add stdio dsrp --- README.md | 68 ++++++++++++++++---------------- sections/io.md | 94 ++++++++++++++++++++++++++++++++++++++++++-- sections/js-basic.md | 23 +++++------ 3 files changed, 137 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index e61ffda..16ba04b 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,19 @@ # 如何通过饿了么 Node.js 面试 -Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过饿了么大前端的面试, 职位是 2~3 年经验的 Node.js 服务端程序员, 如果你对这个职位感兴趣或者想了解一下相关情况,那么欢迎阅读. +Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过饿了么大前端的面试, 职位是 2~3 年经验的 Node.js 服务端程序员, 如果你对这个职位感兴趣或者学习 Node.js 一些进阶的内容, 那么欢迎阅读. -需要注意的是, 本文针对的并不是零基础的同学, 你需要有一定的 JavaScript/Node 基础, 并且有一定的工作经验. +需要注意的是, 本文针对的并不是零基础的同学, 你需要有一定的 JavaScript/Node.js 基础, 并且有一定的工作经验. 另外本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分. -如果你觉得大多不了解, 就不用投简历了 (这样两边都节约了时间), 如果你觉得大都有了解或者光看大纲都都觉得很简单那么欢迎投递简历至 ElemeFe (fe.job@ele.me). +如果你觉得大多不了解, 就不用投简历了 (这样两边都节约了时间), 如果你觉得大都有了解或者***光看大纲都都觉得很简单那么欢迎投递简历至 ElemeFe (fe.job@ele.me)***. ### 导读 -虽然说目的是要通过面试, 但是本教程并不是简单的把所有面试题列出来, 而主要是将面试中需要确认你是否懂的点列举出来, 并进行一定程度的讨论. 本文将一些常见的问题划分归类, 每类标明涵盖的一些`覆盖点`, 并且列举几个`常见问题`, 通常这些问题都是 2~3 年工作经验需要了解或者面对的. 如果你对某类问题感兴趣, 或者想知道其中列举问题的答案, 可以通过该类下方的 `阅读更多` 查看更多的内容. +虽然说目的是要通过面试, 但是本教程并不是简单的把所有面试题列出来, 而主要是将面试中需要确认你是否懂的点列举出来, 并进行一定程度的讨论. -大纲列举的并不是很全面, 细节上覆盖率不高, 很多讨论只是点到即止, 就是希望大家带着问题去思考. +本文将一些常见的问题划分归类, 每类标明涵盖的一些`覆盖点`, 并且列举几个`常见问题`, 通常这些问题都是 2~3 年工作经验需要了解或者面对的. 如果你对某类问题感兴趣, 或者想知道其中列举问题的答案, 可以通过该类下方的 `阅读更多` 查看更多的内容. + +整体上大纲列举的并不是很全面, 细节上覆盖率不高, 很多讨论只是点到即止, 希望大家带着问题去思考. ## [Js 基础问题](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md) @@ -26,12 +28,12 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ### 常见问题 -* js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#q-value) +* js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#q-value) * js 中, 0.1 + 0.2 === 0.3 是否为 true ? 在不知道浮点数位数时应该怎样判断两个浮点数之和与第三数是否相等? -* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 的意义是? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#q-const) -* Javascript 中不同类型以及不同环境下变量的内存都是何时释放? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#q-mem) +* const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 的意义是? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#q-const) +* Javascript 中不同类型以及不同环境下变量的内存都是何时释放? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#q-mem) -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md) +[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md) ## [node 基础问题](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md) @@ -46,11 +48,11 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ### 常见问题 -* a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md#q-loop) -* 如果 a.js require 了 b.js, 那么在 b 中定义全局变量 `t = 111` 能否在 a 中直接打印出来? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md#q-global) -* 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md#q-hot) +* a.js 和 b.js 两个文件互相 require 是否会死循环? 双方是否能导出变量? 如何从设计上避免这种问题? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md#q-loop) +* 如果 a.js require 了 b.js, 那么在 b 中定义全局变量 `t = 111` 能否在 a 中直接打印出来? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md#q-global) +* 如何在不重启 node 进程的情况下热更新一个 js/json 文件? 这个问题本身是否有问题? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md#q-hot) -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md) +[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/node-basic.md) ## [事件/异步](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md) @@ -62,14 +64,14 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ### 常见问题 -* Promise 中 .then 的第二参数与 .catch 有什么区别? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-1) -* Eventemitter 的 emit 是同步还是异步? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-2) -* 如何判断接口是否异步? 是否只要有回调函数就是异步? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-3) -* nextTick, setTimeout 以及 setImmediate 三者有什么区别? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-4) -* 如何实现一个 sleep 函数? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-5) -* 如何实现一个异步的 reduce? (注:不是异步完了之后同步 reduce) [more](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-6) +* Promise 中 .then 的第二参数与 .catch 有什么区别? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-1) +* Eventemitter 的 emit 是同步还是异步? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-2) +* 如何判断接口是否异步? 是否只要有回调函数就是异步? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-3) +* nextTick, setTimeout 以及 setImmediate 三者有什么区别? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-4) +* 如何实现一个 sleep 函数? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-5) +* 如何实现一个异步的 reduce? (注:不是异步完了之后同步 reduce) [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md#q-6) -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md) +[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/event-async.md) ## [进程](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md) @@ -81,12 +83,12 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ### 常见问题 -* 进程的当前工作目录是什么? 有什么作用? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-cwd) -* child_process.fork 与 POSIX 的 fork 有什么区别? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-fork) -* 父进程或子进程的死亡是否会影响对方? 什么是僵死进程? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-child) -* 什么是守护进程? 如何实现守护进程? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#守护进程) +* 进程的当前工作目录是什么? 有什么作用? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-cwd) +* child_process.fork 与 POSIX 的 fork 有什么区别? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-fork) +* 父进程或子进程的死亡是否会影响对方? 什么是僵死进程? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#q-child) +* 什么是守护进程? 如何实现守护进程? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md#守护进程) -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md) +[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/process.md) ## [IO](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md) @@ -101,15 +103,15 @@ Hi, 欢迎来到 ElemeFE, 如标题所示本教程的目的是教你如何通过 ### 常见问题 -* Buffer 一般用于处理什么数据? 其长度能否动态变化? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#buffer) -* Stream 的 highWaterMark 与 drain 事件是什么? 二者之间的关系是? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#缓冲区) -* Stream 的 pipe 的作用是? 在 pipe 的过程中数据是引用传递还是拷贝传递? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#pipe) -* 什么是文件描述符? 输入流/输出流/错误流是什么? -* console.log 是同步还是异步? 如何实现一个 console.log? [more](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#console) -* 如何同步的获取用户的输入? -* Readline 是如何实现的? (有思路即可) [more](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#readline) +* Buffer 一般用于处理什么数据? 其长度能否动态变化? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#buffer) +* Stream 的 highWaterMark 与 drain 事件是什么? 二者之间的关系是? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#缓冲区) +* Stream 的 pipe 的作用是? 在 pipe 的过程中数据是引用传递还是拷贝传递? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#pipe) +* 什么是文件描述符? 输入流/输出流/错误流是什么? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#file) +* console.log 是同步还是异步? 如何实现一个 console.log? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#console) +* 如何同步的获取用户的输入? [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#如何同步的获取用户的输入) +* Readline 是如何实现的? (有思路即可) [[more]](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md#readline) -[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md) +[阅读更多](https://github.com/ElemeFE/node-interview/blob/master/sections/io.md) ## Network diff --git a/sections/io.md b/sections/io.md index 91a6ee8..02a61d7 100644 --- a/sections/io.md +++ b/sections/io.md @@ -28,7 +28,7 @@ Buffer.allocUnsafe()|创建一个未初始化的 Buffer 对象 Node.js 的 Buffer 在 ES6 增加了 TypedArray 类型之后, 修改了原来的 Buffer 的实现, 选择基于 TypedArray 中 Uint8Array 来实现, 从而提升了一波性能. -使用上, 你需要了解如下例子: +使用上, 你需要了解如下情况: ```javascript const arr = new Uint16Array(2); @@ -147,7 +147,7 @@ int copy(const char *src, const char *dest) ### 缓冲区 -Node.js 中 stream 的缓冲区, 以开头的 C语言 拷贝文件的代码为模板讨论, (抛开异步的区别看) 则是从 src 中读出数据到 buf 中后, 并没有直接写入 dest 中, 而是先放在一个比较大的缓冲区中, 等待写入(消费) dest 中. 即, 在缓冲区的帮助下可以使读与写的过程分离. +Node.js 中 stream 的缓冲区, 以开头的 C语言 拷贝文件的代码为模板讨论, (抛开异步的区别看) 则是从 `src` 中读出数据到 `buf` 中后, 并没有直接写入 `dest` 中, 而是先放在一个比较大的缓冲区中, 等待写入(消费) `dest` 中. 即, 在缓冲区的帮助下可以使读与写的过程分离. Readable 和 Writable 流都会将数据储存在内部的缓冲区中. 缓冲区可以分别通过 `writable._writableState.getBuffer()` 和 `readable._readableState.buffer` 来访问. 缓冲区的大小, 由构造 stream 时候的 `highWaterMark` 标志指定可容纳的 byte 大小, 对于 `objectMode` 的 stream, 该标志表示可以容纳的对象个数. @@ -244,11 +244,95 @@ Node.js 封装了标准 POSIX 文件 I/O 操作的集合. 通过 require('fs') ### stdio -标准的 IO 流, 即输入流 (stdin), 输出流 (stdout), 错误流 (stderr). +stdio (standard input output) 标准的输入输出流, 即输入流 (stdin), 输出流 (stdout), 错误流 (stderr) 三者. 在 Node.js 中分别对应 `process.stdin` (Readable), `process.stdout` (Writable) 以及 `process.stderr` (Writable) 三个 stream. +输出函数是每个人在学习任何一门编程语言时所需要学到的第一个函数. 例如 C语言的 `printf("hello, world!");` python/ruby 的 `print 'hello, world!'` 以及 Javascript 中的 `console.log('hello, world!');` +以 C语言的伪代码来看的话, 这类输出函数的实现思路如下: -整理中 +```c +int printf(FILE *stream, 要打印的内容) +{ + // ... + + // 1. 申请一个临时内存空间 + char *s = malloc(4096); + + // 2. 处理好要打印的的内容, 其值存储在 s 中 + // ... + + // 3. 将 s 上的内容写入到 stream 中 + fwrite(s, stream); + + // 4. 释放临时空间 + free(s); + + // ... +} +``` + +我们需要了解的是第 3 步, 其中的 stream 则是指 stdout (输出流). 实际上在 shell 上运行一个应用程序的时候, shell 做的第一个操作是 fork 当前 shell 的进程 (所以, 如果你通过 ps 去查看你从 shell 上启动的进程, 其父进程 pid 就是当前 shell 的 pid), 在这个过程中也把 shell 的 stdio 继承给了你当前的应用进程, 所以你在当前进程里面将数据写入到 stdout, 也就是写入到了 shell 的 stdout, 即在当前 shell 上显示了. + +输入也是同理, 当前进程继承了 shell 的 stdin, 所以当你从 stdin 中读取数据时, 其实就获取到你在 shell 上输入的数据. (PS: shell 可以是 windows 下的 cmd, powershell, 也可以是 linux 下 bash 或者 zsh 等) + +当你使用 ssh 在远程服务器上运行一个命令的时候, 在服务器上的命令输出虽然也是写入到服务器上 shell 的 stdout, 但是这个远程的 shell 是从 sshd 服务上 fork 出来的, 其 stdout 是继承自 sshd 的一个 fd, 这个 fd 其实是个 socket, 所以最终其实是写入到了一个 socket 中, 通过这个 socket 传输你本地的计算机上的 shell 的 stdout. + +另外 `console.error` 则是将数据写入到错误流中 (`process.stderr`). + +### 如何同步的获取用户的输入? + +如果你理解了上述的内容, 那么放到 Node.js 中来看, 获取用户的输入其实就是读取 Node.js 进程中的输入流 (即 process.stdin 这个 stream) 的数据. + +而要同步读取, 则是不用异步的 read 接口, 而是用同步的 readSync 接口去读取 stdin 的数据即可实现. 以下来自万能的 stackoverflow: + +```javascript +/* + * http://stackoverflow.com/questions/3430939/node-js-readsync-from-stdin + * @mklement0 + */ +var fs = require('fs'); + +var BUFSIZE = 256; +var buf = new Buffer(BUFSIZE); +var bytesRead; + +module.exports = function() { + var fd = ('win32' === process.platform) ? process.stdin.fd : fs.openSync('/dev/stdin', 'rs'); + bytesRead = 0; + + try { + bytesRead = fs.readSync(fd, buf, 0, BUFSIZE); + } catch (e) { + if (e.code === 'EAGAIN') { // 'resource temporarily unavailable' + // Happens on OS X 10.8.3 (not Windows 7!), if there's no + // stdin input - typically when invoking a script without any + // input (for interactive stdin input). + // If you were to just continue, you'd create a tight loop. + console.error('ERROR: interactive stdin input not supported.'); + process.exit(1); + } else if (e.code === 'EOF') { + // Happens on Windows 7, but not OS X 10.8.3: + // simply signals the end of *piped* stdin input. + return ''; + } + throw e; // unexpected exception + } + + if (bytesRead === 0) { + // No more stdin input available. + // OS X 10.8.3: regardless of input method, this is how the end + // of input is signaled. + // Windows 7: this is how the end of input is signaled for + // *interactive* stdin input. + return ''; + } + // Process the chunk read. + + var content = buf.toString(null, 0, bytesRead - 1); + + return content; +}; +``` ## Readline @@ -269,6 +353,8 @@ rl.on('line', (line) => { 实现上, realine 在读取 TTY 的数据时, 是通过 `input.on('keypress', onkeypress)` 时发现用户按下了回车键来判断是新的 line 的, 而读取一般的 stream 时, 则是通过缓存数据然后用正则 .test 来判断是否为 new line 的. +PS: 打个广告, 如果在编写脚本时, 不习惯这样异步获取输入, 想要同步获取同步的用户输入可以看一看这个 Node.js 版本类 C语言使用的 [scanf](https://github.com/Lellansin/node-scanf/) 模块 (支持 ts). + ## REPL Read-Eval-Print-Loop (REPL) diff --git a/sections/js-basic.md b/sections/js-basic.md index e041e78..b759d26 100644 --- a/sections/js-basic.md +++ b/sections/js-basic.md @@ -6,17 +6,6 @@ * [`[Basic]` 内存释放](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#内存释放) * [`[Basic]` ES6 新特性](https://github.com/ElemeFE/node-interview/blob/master/sections/js-basic.md#es6-新特性) -> js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? - -简单点说, 对象是引用传递, 基础类型是值传递, 通过将基础类型包装 (boxing) 可以以引用的方式传递.(复杂见注①) - -> const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 的意义是? - -可以被修改, const 可以避免类型改变, 可以保护引用不变, 引用不变对使用 Map 有很大的意义. - -> Javascript 中不同类型以及不同环境下变量的内存都是何时释放? - -引用类型是在没有引用之后, 通过 v8 的 GC 自动回收, 值类型如果是处于闭包的情况下, 要等闭包没有引用才会被 GC 回收, 非闭包的情况下等待 v8 的新生代 (new space) 切换的时候回收. ## 简述 @@ -49,6 +38,10 @@ Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 ## 引用传递 +> js 中什么类型是引用传递, 什么类型是值传递? 如何将值类型的变量以引用的方式传递? + +简单点说, 对象是引用传递, 基础类型是值传递, 通过将基础类型包装 (boxing) 可以以引用的方式传递.(复杂见注①) + 引用传递和值传递是一个非常简单的问题, 也是理解 Javascript 中的内存方面问题的一个基础. 如果不了解引用可能很难去看很多问题. 面试写代码的话, 可以通过 `如何编写一个 json 对象的拷贝函数` 等类似的问题来考察对引用的了解. @@ -60,6 +53,10 @@ Javascript 的类型判断其实是个挺折磨人的话题, 不然也不会有 ## 内存释放 +> Javascript 中不同类型以及不同环境下变量的内存都是何时释放? + +引用类型是在没有引用之后, 通过 v8 的 GC 自动回收, 值类型如果是处于闭包的情况下, 要等闭包没有引用才会被 GC 回收, 非闭包的情况下等待 v8 的新生代 (new space) 切换的时候回收. + 与前端 Js 不同, 2年以上经验的 Node.js 一定要开始注意内存了, 不说对 v8 的 GC 有多了解, 基础的内存释放一定有概念了, 并且要开始注意内存泄漏的问题了. 你需要了解哪些操作一定会导致内存泄漏, 或者可以崩掉内存. 比如如下代码能否爆掉 V8 的内存? @@ -122,4 +119,8 @@ setInterval(replaceThing, 1000) `...` 的使用上, 如何实现一个数组的去重 (使用 Set 可以加分). +> const 定义的 Array 中间元素能否被修改? 如果可以, 那 const 的意义是? + +可以被修改, const 可以避免类型改变, 可以保护引用不变, 引用不变对使用 Map 有很大的意义. + 暂时写上这些, 之后会慢慢整理, 如果内容比较多可能单独归一类来讨论.