├── README.md
├── SUMMARY.md
├── closure.md
├── compile.md
├── file
├── README.md
├── benchmark-scores.png
├── client_server_connect.png
├── docker_pull_registry.png
├── improvements-per-website.png
├── inspect_colors.PNG
├── launching-ignition-and-turbofan.md
├── net_createserver.png
├── node_memory_leak.PNG
├── nodejs.jpg
├── pull_from_private.png
├── push_to_private.png
├── pushing_to_ptivate.png
├── rl_prompt.png
├── scope.png
├── scope_chain.png
├── typeof.png
├── v8-ignition.svg
└── v8-turbofan.svg
├── node
├── buffer-chapter1.md
├── buffer-chapter2.md
├── console.md
├── events.md
├── http.md
├── net.md
├── path.md
├── qs_url.md
├── readline.md
├── stream-chapter1.md
├── stream-chapter2.md
├── stream-chapter3.md
├── token_hold.md
└── util.md
├── object.md
├── prototype.md
├── scope.md
├── this.md
├── type.md
├── v8.dev
└── launching-ignition-and-turbofan.md
└── wechat
└── mp
├── callapi.md
└── start.md
/README.md:
--------------------------------------------------------------------------------
1 | ##### 序言
2 | 先写点非技术类的文字吧,当做开篇吧。
3 |
4 | ##### 我
5 | 高中毕业的时候,为了正大光明的玩电脑,直接选择了计算机专业(其实打dota的时间远大于学习,相信大多数程序猿都差不多)。大学毕业之后供职于苏州一家小公司,后来公司资金链断裂,倒闭了(我保证不是我代码烂导致的)。后来(2012.6)到了新公司,一段时间后开始转做Node.js(之前一直做.NET),开启了我的Node之旅。
6 |
7 | ##### 现在
8 | + 2012年6月-2016年7月 做一些框架和底层类的工作,并潜心研究JavaScript。
9 | + 2016年7月-2020年3月 供职于上海一家互联网P2P公司。
10 | + 2016年4月-2020年6月 供职于某图形公司。
11 | + 2020年6月-2022年1月 被朋友忽悠进了工程行业,开始接触工程建设解决方案。
12 | + 2021年1月-至今 整装待发...
13 |
14 | ##### 博客指引
15 | * [JavaScript基础学习心得](https://github.com/swfbarhr/blog)
16 | * [node的基本使用](https://github.com/swfbarhr/blog/tree/master/node)
17 | * [公众号部分](https://github.com/swfbarhr/blog/tree/master/wechat/mp)
18 | * [蹩脚的v8.blog文章翻译](https://github.com/swfbarhr/blog/tree/master/v8.dev)
19 |
20 | ##### 联系我
21 | + 邮箱:swfbarhr@gmail.com
22 | + github:https://github.com/swfbarhr
23 | + 博客:https://swfbarhr.gitbooks.io/erblog/content/
24 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | - [关于我](README.md)
4 | - [JavaScript 之作用域](scope.md)
5 | - [JavaScript 之闭包](closure.md)
6 | - [JavaScript 之编译](compile.md)
7 | - [JavaScript 之 this](this.md)
8 | - [JavaScript 之基本类型](type.md)
9 | - [JavaScript 之 object](object.md)
10 | - [JavaScript 之 prototype](prototype.md)
11 | - [Node.js 之 HTTP](node/http.md)
12 | - [Node.js 之 console](node/console.md)
13 | - [Node.js 之 Buffer(一)](node/buffer-chapter1.md)
14 | - [Node.js 之 Buffer(二)](node/buffer-chapter2.md)
15 | - [Node.js 之 util](node/util.md)
16 | - [Node.js 之 events](node/events.md)
17 | - [Node.js 之 Path](node/path.md)
18 | - [Node.js 之 Query Strings&URL](node/qs_url.md)
19 | - [Node.js 之 Readline](node/readline.md)
20 | - [Node.js 之 Net](node/net.md)
21 | - [Node.js 之 Stream(一)](node/stream-chapter1.md)
22 | - [Node.js 之 Stream(二)](node/stream-chapter2.md)
23 | - [Node.js 之 Stream(三)](node/stream-chapter3.md)
24 | - [Node.js 之高并发下微信 access_token 的存储与更新](node/token_hold.md)
25 | - [微信公众号之开篇](wechat/mp/start.md)
26 | - [微信公众号之调用 API](wechat/mp/callapi.md)
27 |
--------------------------------------------------------------------------------
/closure.md:
--------------------------------------------------------------------------------
1 | ##### 一句话概括闭包
2 | 相信很多写了很多JavaScript代码的老程序猿都知道有闭包这个概念,但是如果真的要大家用语言来描述闭包的话,很多程序猿都吱吱呜呜说不清楚(包括很大一部分前端攻城狮)。闭包简单来讲就是:当函数记住并访问所在作用域时,就产生了闭包。
3 |
4 | ##### 示例
5 | ```js
6 | function foo(){
7 | var a = 123;
8 |
9 | return function(){
10 | return a;
11 | }
12 | }
13 |
14 | var baz = foo();
15 | console.log(baz()); // 123
16 | ```
17 | 上面是一个典型的闭包。我们来分析下代码:
18 | 首先我们定义了一个函数foo,然后在其作用域内部又定义了一个匿名函数并且将此匿名函数(在实际编码过程中尽量少用匿名函数,原因我会在后面的相关博客中说明)当做返回值return了出来。当我们调用foo并把结果赋值给baz变量(函数表达式),我们在foo外调用baz时,竟然能访问到foo的内部变量“a”(妈呀!我穿越了吗?)。在词法上,foo和baz是在同一层次的不同函数,应该互不影响才对,那现在这种情况又是怎么回事呢?
19 |
20 | ##### JavaScript中闭包的实现原理
21 | 要知道闭包不是JavaScript独有的,其他语言像C#、JAVA都有闭包的概念。但是各自的实现原理是不一样的,其他语言暂且不论,JavaScript中闭包的实现其实就是靠的作用域链(就是ES5中的词法环境)。在JavaScript作用域一文中我简单地提到过作用域链的概念,现在我就来详细的解读下什么是作用域链,它的形成过程和实现闭包的方法。先思考一下代码:
22 |
23 | ```js
24 | var arg1, foob;
25 |
26 | arg1 = 'world';
27 |
28 | function fooa(){
29 | var v1;
30 |
31 | v1 = 'hello'
32 |
33 | return function(arg1){
34 |
35 | if(arg1){
36 | return v1 + ' ' + arg1;
37 | }
38 |
39 | return v1 + ' everyone';
40 | }
41 | }
42 |
43 | foob = fooa();
44 | foob('Bob'); // hello Bob
45 | ```
46 | 代码在执行过程中,fooa会把全局作用域的变量(可以认为fooa把全局作用的所有变量放入了一个堆栈里面以备用)引用至其作用域。同样的,fooa中return的匿名函数也会把fooa中的变量加入到这个“堆栈”中并且在其作用域中引用(如图1所示)。
47 |
48 | 
49 |
50 | 其实在执行foob时,fooa的运行状态已经不存在了,之所以foob中能访问fooa的变量就是因为foob保存了对fooa作用域变量的引用,这样foob可以在需要时取出fooa中的变量来使用,也就是我们常说的:产生了闭包。(哎妈呀!原来如此!)闭包就是这么简单!
51 |
52 | ##### 闭包的应用
53 | 前面已经介绍了什么是闭包以及闭包的实现原理,那闭包在我们实际编码过程中到底有怎样的用途呢?
54 | 示例1:
55 | ```js
56 | var firFunc, secFunc;
57 |
58 | function func1(arg1){
59 | return function(arg2){
60 | return arg1 +' ' + arg2;
61 | }
62 | }
63 |
64 | firFunc = func1('hello');
65 | secFunc = func1('hello');
66 |
67 | console.log(firFunc('Lily')); // hello Lily
68 | console.log(secFunc('Lucy')); // hello Lucy
69 | ```
70 | 我们可以利用闭包来暂时记录参数(例如:配置项、数据库连接等)。就是这么的优雅!就是这么酸爽!
71 | 示例2:
72 | ```js
73 | function foo(logInfo){
74 | setTimeout(function(){
75 | console.log(logInfo);
76 | }, 3000);
77 | }
78 |
79 | foo('pizza'); // pizza
80 | ```
81 | setTimeout大家都很熟悉:延迟指定的时间执行代码。这里也涉及到了一个闭包,setTimeout的第一个参数(function)记住了foo的作用域内的变量logInfo。
82 | 示例3:
83 | ```js
84 | var isIE = true;
85 |
86 | $('#dvTest').click(function(){
87 | if(isIE){
88 | console.log('IE');
89 | }
90 | else{
91 | console.log('other');
92 | }
93 | });
94 | ```
95 | 这是一段大家都很熟悉的JQuery事件绑定代码,里面也有一个非常明显的闭包。还有很多的例子,可能大家在编写代码的过程中已经很熟练地使用了闭包,自己却没有发现。现在去回顾下自己的代码,从中找出闭包!
96 |
97 | ##### 总结
98 | 讲到这里,我们就把闭包的概念叙述完了。其实也不复杂,很多人一直搞不清楚闭包,并不是因为闭包有多难,只是没有用心去理解。希望我上面的描述能帮助大家理解闭包,拥抱JavaScript。下一篇我们就来谈谈我在[JavaScript之作用域](scope.md)一文中提到的JavaScript编译。
99 |
--------------------------------------------------------------------------------
/compile.md:
--------------------------------------------------------------------------------
1 | ##### JavaScript的编译
2 | 很多人说JavaScript是解释型或者动态语言,是不需要编译的。其实不然,JavaScript是需要编译的,其编译发生在代码执行前的一瞬间(几微妙或者更短的时间内)。大多数编译型的语言编译大致都可以分3个步骤:分词,解析,生成代码。JavaScript也不例外,但是JavaScript会有更复杂的过程。
3 |
4 | ##### 分词
5 | 大家知道,其实我们99.99%的代码都是文本类型。也就是说编译器拿到的是一堆实现约定好格式的文本,编译器拿到文本要干的第一件事情就是分词(也可以叫做词法分析)。就拿赋值语句来说(var a = 0;),编译器经过分词之后就会拿到这样的结果:var、a、=、0。
6 |
7 | ##### 解析
8 | 经过词法分析,编译器在拿到分词结果之后便会根据分词的结果生成一个叫做“抽象语法树”(Abstract Syntax Tree)的树形结构,简称AST。就拿(var a = 0;)来说,树结构下一般会有以下节点:存储a的名称(也就是“a”)、a的类型、a的值。
9 |
10 | ##### 生成代码
11 | 在这阶段,编译器根据生成的“抽象语法树”来生成计算机可以识别的指令。在从JavaScript到机器指令之间,JavaScript引擎还做了很多事情,以var a = 0;为例:
12 | 其实这条语句,JavaScript编译器会分为2个步骤执行(感谢[TylerPeng](https://github.com/TylerPeng)的提醒):
13 |
14 | + var a;此时,JavaScript引擎会询问当前作用域中是否已经申明了a变量,如果a已经申明,那么编译器会忽略此次申明;如果没有找到a的身影,那么编译器会在当前作用域中申明一个a变量。
15 |
16 | + a = 0;赋值时,询问当前作用域中是否已经申明了a变量,如果没有则询问当前作用域的上级(此过程与我在[JavaScript之闭包](https://github.com/swfbarhr/blog/blob/master/closure.md)一文中类似)。如果最终没有找到a,那么就会报错;如果找到了a,那么就会直接使用。
17 |
18 | ##### 其他的事
19 | 其实JavaScript引擎除了基本的3个编译步骤,还做了很多其他的事情,例如:代码优化、整理等。
20 |
21 | ##### 变量申明提升
22 | 既然已经讲到了这里,也不得不提到这个概念。其实在JavaScript中,变量申明是会被提升到当前作用域的最顶部,思考一下代码:
23 | ```js
24 | function foo(){
25 | console.log(a);
26 |
27 | var a = 3;
28 | }
29 |
30 | foo(); // undefined
31 | ```
32 | 但是按照编译规则,在执行到console.log(a)时,代码应该会报错才对,现在为什么又打印出“undefined”呢,打印出“undefined”很显然变量已经被申明了。上面我有提到,JavaScript引擎会对我们的代码进行优化、整理,真正编译器拿到的代码是这个样子的:
33 | ```js
34 | function foo(){
35 | var a;
36 |
37 | console.log(a);
38 |
39 | a = 3;
40 | }
41 |
42 | foo(); // undefined
43 | ```
44 | 就像我们上面说到的一样,申明赋值这个操作,JavaScript会分成2个步骤完成:申明和赋值。
45 | 其实不止变量申明的提升,函数的申明也会提升,思考一下代码:
46 | ```js
47 | foo(); // 3
48 |
49 | function foo(){
50 | var a = 3;
51 |
52 | console.log(a);
53 | }
54 | ```
55 | 我们在调用foo时,foo函数还没有申明。但是我们居然能得到我们所期望的值,这说明函数也会提升。
56 |
57 | ##### 总结
58 | 了解了JavaScript的编译规则,应该对它会有不同的认识。其实JavaScript是一门被精心设计的语言,其优势不言而喻。当今有很多人吐槽JavaScript,吐槽JavaScript各种令人讨厌的坑。其实学习和工作哪个不是不断的在填自己知识上填坑呢?
59 |
--------------------------------------------------------------------------------
/file/README.md:
--------------------------------------------------------------------------------
1 | # 资源目录
2 |
--------------------------------------------------------------------------------
/file/benchmark-scores.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/benchmark-scores.png
--------------------------------------------------------------------------------
/file/client_server_connect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/client_server_connect.png
--------------------------------------------------------------------------------
/file/docker_pull_registry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/docker_pull_registry.png
--------------------------------------------------------------------------------
/file/improvements-per-website.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/improvements-per-website.png
--------------------------------------------------------------------------------
/file/inspect_colors.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/inspect_colors.PNG
--------------------------------------------------------------------------------
/file/launching-ignition-and-turbofan.md:
--------------------------------------------------------------------------------
1 | # Ignition 与 TurboFan 发布
2 |
3 | 今天我们很激动地宣布:在 V8 v5.9 版本(对应 Chrome v59)中将发布一个新的 JavaScript 执行管道。使用这条新的执行管道,当前的 JavaScript 应用将会在性能上有大幅度的提升并且消耗的内存将会更少。在本文的末尾,我们将会讨论更加详细的数据,在此之前我们先来介绍下这条执行流本身
4 |
5 | 这条新的执行流建立在 V8 的解释器 Ignition 和 V8 的最新优化编译器 TurboFan 的基础之上。对于一直关注我们 V8 blog 的同学来说,这些技术应该还是比较熟悉的,但是切换到新的执行流对于 Ignition 和 TurboFan 都是一个重大的里程碑。
6 |
7 |
8 |
Ignition logo,V8新解释器
9 |
10 |
11 | TurboFan logo,V8新优化编译器
12 |
13 | V8 v5.9 中的全面并且仅仅使用 Ignition 和 TurboFan 还是历史首次。并且从 v5.9 开始,Full-codegen 和 Crankshaft(从 2010 年开始服务于 V8 的技术)将不再使用于 V8 的 JavaScript 执行管道,从此它们将不会与新的语言特新和优化需求保持同步更新。我们打算尽快将它们完全移除,这就意味着 V8 将会朝着一个更加简单、更加可维护的方向发展。
14 |
15 | # 漫长的旅程
16 |
17 | Ignition 和 TurboFan 组合管道已经开发了 3 个半年的时间。它是 V8 团队搜集现实世界 JavaScript 性能表现和对 Full-codegen 与 Crankshaft 缺点考虑的结果,是我们可以持续优化 JavaScript 语言的基础。
18 |
19 | TurboFan 项目最初开始于 2013 年底,旨在解决 Crankshaft 的不足之处。Crankshaft 只能优化 JavaScript 的一部分代码。举个栗子,最初设计时,它是不能优化异常处理例如被 try...catch...finally 包起来的代码块。我们很难在 Crankshaft 中加入新的语言特性,因为这些特性需要我们在 9 个所支持的平台根据不能架构编写对应架构匹配的代码。而且,Crankshaft 架构在某种程度上生成最佳的机器码受到限制。尽管 V8 团队在每个架构上都维护了成千上万行代码,Crankshaft 也就只能挤出那一丁点儿性能了。
20 |
21 | TurboFan 从一开始就被设计成不仅支持现有 JavaScript 标准 ES5 还可以支持计划中的 ES2015 和之后更新的版本。它引入了一个叫分层编译器的设计,使得在高等级编译优化和底层编译器优化有一个明显的分界,这样的设计可以使我们很容易优化新的语言特性而不需要修改面向不同架构的代码(architecture-specific code)。TurboFan 增加了一个显式的指令选择编译阶段,使得用更少的(不同架构的)代码就可以支持不同平台成为可能。引入这个阶段,不同架构的代码只需写一次,而且几乎不需要更改。基于以上以及一些其他因素,一个支持所有 V8 所支持架构的,可维护的、可扩展的优化编译器就产生了。
22 |
23 | V8 Ignition 解释器最初的背后动机是降低在移动设备上的内存消耗。在 Ignition 之前,Full-codegen 基本编译器所生成的代码几乎占了 Chrome 内存堆的 1/3。而实际留给 web 应用的空间就变少了。当在一台内存受限的安卓一定设备的 Chrome M53 上启用 Ignition 时,未被优化的 JavaScript 代码所需要的基本内存消耗会减少 9 倍(基于 ARM64 的移动设备)。
24 |
25 | 之后 V8 团队充分利用了 Ignition 字节码可以配合 TurboFan 直接生成机器码而不是重新编译源码(如 Crankshaft)这一优点。Ignition 的字节码在 V8 中提供了一个更干净、更少错误的基线执行模型,简化了脱优化(deoptimization)机制是 V8 自适应优化的一个关键功能。最后,因为生成字节码要比 Full-codegen 生成编译的代码更快,使用 Ignition 一般来说会改善脚本的启动时间、切换和页面加载。
26 |
27 | 随着 Ignition 和 TurboFan 的组合越来越紧密,在总体架构上还带来了更多的好处。比如 V8 团队使用 TurboFan 的中间语言来表示这些处理器的功能性,让 TurboFan 做优化和为各支持平台生成对应的代码,而不是手写高性能的字节码处理器。这使得 Ignition 在 V8 所支持的所有架构上表现良好,同时也减轻了 9 个相对独立的平台的维护负担。
28 |
29 | # 实测数据
30 |
31 | 撇开历史不谈,让我们来看下新管道在现实世界的表现和内存消耗。
32 |
33 | V8 团队使用 Telemetry - Catapult 框架来持续监控显示世界 V8 的性能表现。之前我们在博客中讨论过为什么使用来自现实世界的数据来驱动我们的性能优化工作是如此重要以及我们是如何使用 WebPageReplay 与 Telemetry 一起工作的。切换到 Ignition 和 TurboFan 展示了现实世界测试用例的性能提升。特别是新的管道显著加快了著名网站的用户交互测试速度。
34 |
35 | 尽管 Speedometer 使用的合成的基准测试,不过我们之前已经揭示过,与其他合成的基准相比,它在现在 JavaScript 工作负载方面的表现更接近现实世界。切换到 Ignition 和 TurboFan,V8 的 Speedometer 跑分根据平台和设备的不同有 5%-10%的提升。
36 |
37 | 新的管道也加快了服务端 JavaScript 速度。AcmeAir:Node.js 的基准测试,模拟虚拟管道的后端实现,在使用 V8 v5.9 后使速度加快了 10%。
38 |
39 | Ignition 与 TurboFan 同时也减少了 V8 的内存占用。在 Chrome M59,新的流程缩减了桌面程序和高端移动设备 V8 的内存约 5%-10%。这个减少(5%-10%)是之前在本博客提到过的 Ignition 对于 V8 所支持的所有设备和平台内存节省(策略)的结果。
40 |
41 | 这些提升仅仅是个开始。新的 Ignition 与 TurboFan 管道在未来的几年为进一步优化 JavaScript 性能和缩小 V8 性能开销铺平道路(同时在 Chrome 浏览器和 Node.js 端)。我们期待在我们向开发者和用户推出它们的时候与您分享这些提升。尽情期待。
42 |
43 | ##### V8 团队发表于 v8.dev( 原文:https://v8.dev/blog/launching-ignition-and-turbofan )
44 |
--------------------------------------------------------------------------------
/file/net_createserver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/net_createserver.png
--------------------------------------------------------------------------------
/file/node_memory_leak.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/node_memory_leak.PNG
--------------------------------------------------------------------------------
/file/nodejs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/nodejs.jpg
--------------------------------------------------------------------------------
/file/pull_from_private.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/pull_from_private.png
--------------------------------------------------------------------------------
/file/push_to_private.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/push_to_private.png
--------------------------------------------------------------------------------
/file/pushing_to_ptivate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/pushing_to_ptivate.png
--------------------------------------------------------------------------------
/file/rl_prompt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/rl_prompt.png
--------------------------------------------------------------------------------
/file/scope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/scope.png
--------------------------------------------------------------------------------
/file/scope_chain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/scope_chain.png
--------------------------------------------------------------------------------
/file/typeof.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swfbarhr/blog/94f951b947ff439c0020305a0c823e7f14ff0543/file/typeof.png
--------------------------------------------------------------------------------
/file/v8-ignition.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/file/v8-turbofan.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/node/buffer-chapter1.md:
--------------------------------------------------------------------------------
1 | ##### JavaScript不足
2 | JavaScript原生支持Unicode,并且对Unicode非常友好。但是,纯JavaScript对于处理二进制数据却是一个弱项。但是node既然是后端,那么不免要和二进制数据打交道,这样Buffer类就应运而生了。
3 |
4 | ##### 全局变量
5 | 与一般的node模块不同,Buffer在node中是全局存在的(这也说明了Buffer模块在node中的重要性)。既然Buffer是全局变量,那么我们不需要显示的引用,直接使用即可:
6 |
7 | ```js
8 | var bfData = new Buffer(4);
9 | ```
10 |
11 | ##### 格式转换
12 | 我们在Buffer与JavaScript字符串之间转换时,需要显示的指定格式。当前支持的格式有:
13 |
14 | + ascii 目前只支持7比特的ASCII数据(需要注意的是,在此编码中,node会把null转换成空格)
15 | + utf8
16 | + utf16le、ucs2
17 | + base64
18 | + hex
19 |
20 | ```js
21 | var bfASCII = new Buffer('abcd', 'ascii');
22 | console.log(bfASCII); //
23 | console.log(bfASCII.toString()); // abcd
24 |
25 | var bfHex = new Buffer('b4c127c3d7', 'hex');
26 | console.log(bfHex); //
27 | console.log(bfHex.toString('hex')); // b4c127c3d7
28 | ```
29 |
30 | ##### 创建一个Buffer实例
31 | 我们可以有3中创建Buffer实例的方法。
32 |
33 | + new Buffer(size) 创建指定大小的Buffer实例
34 |
35 | ```js
36 | var bfSize = new Buffer(6);
37 |
38 | bfSize.write('abcdef', 0, 6, 'utf8');
39 |
40 | console.log(bfSize); //
41 | console.log(bfSize.toString('utf8')); // abcdef
42 | ```
43 |
44 | + new Buffer(array) 给定指定数组创建Buffer实例
45 |
46 | ```js
47 | var bfArray = new Buffer(['97', 98]);
48 |
49 | console.log(bfArray.toString('utf8')); // ab
50 | ```
51 |
52 | + new Buffer(str, [encoding]) 使用指定的字符串和编码格式(默认为utf8)创建新的Buffer实例
53 |
54 | ```js
55 | var bfStr = new Buffer('你好', 'utf8');
56 |
57 | console.log(bfStr); //
58 | console.log(bfStr.toString('utf8')); // 你好
59 | ```
60 |
61 | ##### Buffer.isEncoding(encoding)
62 | 此方法可以方便的知晓给定的字符编码是否在node支持范围内:
63 |
64 | ```js
65 | console.log(Buffer.isEncoding('utf8')); // true
66 | console.log(Buffer.isEncoding('ascii')); // true
67 | console.log(Buffer.isEncoding('hex')); // true
68 | console.log(Buffer.isEncoding('base64')); // true
69 | console.log(Buffer.isEncoding('ucs2')); // true
70 | console.log(Buffer.isEncoding('binary')); // true
71 | console.log(Buffer.isEncoding('utf108')); // false
72 | ```
73 |
74 | ##### buf.write(string, [offset], [length], [encoding])
75 | 将指定字符串写入到Buffer实例,并且可以指定从偏移量、写入长度和编码
76 |
77 | ```js
78 | var bfWrite = new Buffer(6);
79 |
80 | bfSize.write('abcdef', 0, 5, 'utf8');
81 |
82 | console.log(bfWrite); //
83 | console.log(bfWrite.toString('utf8')); // abcde
84 | ```
85 |
86 | ##### buf.toString([encoding], [start], [end])
87 | 将Buffer实例转换成字符串
88 |
89 | ```js
90 | var bfToStr = new Buffer(2);
91 |
92 | bfToStr[0] = 108;
93 | bfToStr[1] = 109;
94 |
95 | console.log(bfToStr.toString('ascii')); // lm
96 | ```
97 |
98 | ##### 未完待续
99 |
--------------------------------------------------------------------------------
/node/buffer-chapter2.md:
--------------------------------------------------------------------------------
1 | ##### buf.toJSON()
2 | 将Buffer实例转换成JSON形式的数组。
3 |
4 | ```js
5 | var bfJson = new Buffer('hello');
6 |
7 | console.log(bfJson.toJSON()); // [ 104, 101, 108, 108, 111 ]
8 |
9 | console.log(new Buffer(bfJson.toJSON()).toString()); // hello
10 | ```
11 |
12 | ##### Buffer.isBuffer(obj)
13 | 判断对象是否是Buffer实例
14 |
15 | ```js
16 | console.log(Buffer.isBuffer({})); // false
17 | console.log(Buffer.isBuffer(new Buffer(3))); // true
18 | console.log(Buffer.isBuffer(new Buffer('test'))); // true
19 | console.log(Buffer.isBuffer('world')); // false
20 | console.log(Buffer.isBuffer(123)); // false
21 | ```
22 |
23 | ##### Buffer.byteLength(string, [encoding])
24 | 获取字符串的字节数
25 |
26 | ```js
27 | console.log(Buffer.byteLength('hello')); // 5
28 | console.log(Buffer.byteLength('你好')); // 6
29 | ```
30 |
31 | ##### Buffer.concat(list, [totalLength])
32 | 拼接Buffer实例
33 |
34 | ```js
35 | var buf1 = new Buffer('小明');
36 | var buf2 = new Buffer('今年');
37 | var buf3 = new Buffer('5');
38 | var buf4 = new Buffer('岁');
39 |
40 | var bufList = [];
41 |
42 | bufList.push(buf1);
43 | bufList.push(buf2);
44 | bufList.push(buf3);
45 | bufList.push(buf4);
46 |
47 | var bufTotal = Buffer.concat(bufList, buf1.length + buf2.length + buf3.length + buf4.length);
48 |
49 | console.log(bufTotal.toString()); // 小明今年5岁
50 | ```
51 |
52 | ##### buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
53 | 将Buffer实例拷贝到目标对象上
54 |
55 | ```js
56 | var bfSource = new Buffer('可可是个乖宝宝');
57 | var bfTarget = new Buffer(bfSource.length);
58 |
59 | bfSource.copy(bfTarget, 0, 0, bfSource.length);
60 |
61 | console.log(bfTarget.toString()); // 可可是个乖宝宝
62 | ```
63 |
64 | ##### buf.slice([start], [end])
65 | 取Buffer实例片段(不是将原始数据中片段复制出来,而是直接引用。所有当原数据改变时,取出的片段也将随之改变)
66 |
67 | ```js
68 | var bufFull = new Buffer('abcde', 'ascii');
69 | var bufSlice = bufFull.slice(2, 3);
70 |
71 | console.log(bufSlice.toString('ascii')); // c
72 |
73 | bufFull[2] = 97;
74 |
75 | console.log(bufSlice.toString('ascii')); // a
76 | ```
77 |
78 | 上面的代码中,我们在调用slice之后,又改变了bufFull的第三个元素值,之后我们打印bufSlice发现bufSlice中的值也随之改变了。
79 |
80 | ##### buf.readUInt8(offset, [noAssert])
81 | 读取一个8位的无符号整型
82 |
83 | ```js
84 | var bfRead = new Buffer(1);
85 |
86 | bfRead[0] = 0x12;
87 |
88 | console.log(bfRead.readUInt8(0)); // 18
89 |
90 | console.log(bfRead.readUInt8(1, true)); // undefined
91 |
92 | console.log(bfRead.readUInt8(1)); // 程序出错
93 | ```
94 |
95 | buf.readUInt8只读取一个8位无符号整数,并且可以指定读取的位置。第二个参数(noAssert)表示如果读取位置超过Buffer实例的长度时是否抛出错误,默认为false。如果为false,即使超过Buffer.length,系统也不会抛错,这是之后返回undefined。
96 |
97 | ##### 其他的read方法
98 |
99 | + buf.readUInt16LE(offset, [noAssert])
100 | + buf.readUInt16BE(offset, [noAssert])
101 | + buf.readUInt32LE(offset, [noAssert])
102 | + buf.readUInt32BE(offset, [noAssert])
103 | + buf.readInt8(offset, [noAssert])
104 | + buf.readInt16LE(offset, [noAssert])
105 | + buf.readInt16BE(offset, [noAssert])
106 | + buf.readInt32LE(offset, [noAssert])
107 | + buf.readInt32BE(offset, [noAssert])
108 | + buf.readFloatLE(offset, [noAssert])
109 | + buf.readFloatBE(offset, [noAssert])
110 | + buf.readDoubleLE(offset, [noAssert])
111 | + buf.readDoubleBE(offset, [noAssert])
112 |
113 | 以上方法与buf.readUInt8类似,就不多做赘述。
114 |
115 | ##### buf.writeUInt8(value, offset, [noAssert])
116 | 将值写入指定Buffer实例的指定位置
117 |
118 | ```js
119 | var bfWrite = new Buffer(1);
120 |
121 | bfWrite.writeUInt8(0x12, 0);
122 |
123 | console.log(bfWrite); //
124 | ```
125 |
126 | ##### 其他的write方法
127 |
128 | + buf.writeUInt16LE(value, offset, [noAssert])
129 | + buf.writeUInt16BE(value, offset, [noAssert])
130 | + buf.writeUInt32LE(value, offset, [noAssert])
131 | + buf.writeUInt32BE(value, offset, [noAssert])
132 | + buf.writeInt8(value, offset, [noAssert])
133 | + buf.writeInt16LE(value, offset, [noAssert])
134 | + buf.writeInt16BE(value, offset, [noAssert])
135 | + buf.writeInt32LE(value, offset, [noAssert])
136 | + buf.writeInt32BE(value, offset, [noAssert])
137 | + buf.writeFloatLE(value, offset, [noAssert])
138 | + buf.writeFloatBE(value, offset, [noAssert])
139 | + buf.writeDoubleLE(value, offset, [noAssert])
140 | + buf.writeDoubleBE(value, offset, [noAssert])
141 |
142 | 以上方法与buf.writeUInt8类似,就不多做赘述。
143 |
144 | ##### buf.fill(value, [offset], [end])
145 | 向Buffer实例中填充指定的值
146 |
147 | ```js
148 | var bfFill1 = new Buffer(1);
149 | var bfFill2 = new Buffer(6);
150 |
151 | bfFill1.fill(97, 0, 1);
152 |
153 | console.log(bfFill1.toString()); // a
154 |
155 | bfFill2.fill(97);
156 |
157 | console.log(bfFill2.toString()); // aaaaaa
158 | ```
159 |
160 | 如果不指定offset和end,那么fill方法会把当前Buffer实例填满。
161 |
162 | ##### 总结
163 | 好了,Buffer模块介绍到这里就介绍完了。此模块的方法很多,但是大多数都是读取和写入的方法,形式都差不多。只要我们掌握了一个,其他的稍稍看下API就可以轻松使用了。
--------------------------------------------------------------------------------
/node/console.md:
--------------------------------------------------------------------------------
1 | ##### 调式
2 | 众所周知,node的调试是一个令人头疼的工作,著名的node大神TJ也抱怨过。由此见得,想要像.NET那样方便的调试是很困难的(微软最近出了VSCode倒是可以很好的调式,有时间可以试试)。其实node最快捷、方便的调试方式还是控制台输出日志信息。今天我们就来研究下如何使用node中的console模块,console模块是大多数noder的调试神器。
3 |
4 | ##### console.log
5 | console.log(console是个全局变量)是node最常用的一个日志打印函数,此函数与C和C++中的printf很像,可以设置占位符,并且与前2者一致。
6 |
7 | ```js
8 | console.log('娜塔莎是个%s', '好孩子'); // 娜塔莎是个好孩子
9 |
10 | var info = {
11 | status: 200,
12 | msg: 'everything is ok'
13 | }
14 |
15 | console.log(info); // { status: 200, msg: 'everything is ok' }
16 | ```
17 |
18 | console.log的参数是可变的,可以有很多个,就像这样:
19 |
20 | ```js
21 | var a = 'hello Peter';
22 | var b = 'hi Lily';
23 |
24 | console.log(a, b); // hello Peter hi Lily
25 | ```
26 |
27 | 这样就可以打印多个变量或者表达式的结果,当我们在调试程序的时候(特别是需要同时打印多个变量的值)特别有用。就如上面演示的一样,console.log方法可以打印数字、字符串、对象。可以说console.log方法非常强大,但是它不是万能的,当我们打印的对象的树层次太深的话就会出现意料之外的结果。
28 |
29 | ```js
30 | var info = {
31 | status: 200,
32 | msg: {
33 | user:{
34 | name: 'Peter',
35 | country: 'Japan',
36 | company: {
37 | name: '全球无限皮包公司',
38 | ceo: {
39 | name: 'Lucy'
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
46 | console.log(info); // { status: 200, msg: { user: { name: 'Peter', country: 'Japan', company: [Object] } } }
47 | ```
48 |
49 | 可以看到,当我们打印info变量时,到第四层级“company”只打印出了一个[object]。说明console.log在打印object的时候,最多只支持到第三层级,如果需要打印对象所有层级的话,我们可以与util.inspect方法配合使用(之后我们讲到util模块的时候会讲到这个方法)。
50 |
51 | ```js
52 | var util = require('util');
53 |
54 | var info = {
55 | status: 200,
56 | msg: {
57 | user:{
58 | name: 'Peter',
59 | country: 'Japan',
60 | company: {
61 | name: '全球无限皮包公司',
62 | ceo: {
63 | name: 'Lucy'
64 | }
65 | }
66 | }
67 | }
68 | }
69 |
70 |
71 | console.log(util.inspect(info, {depth: null})); // { status: 200,
72 | // msg:
73 | // { user:
74 | // { name: 'Peter',
75 | // country: 'Japan',
76 | // company: { name: '全球无限皮包公司',
77 | // ceo: { name: 'Lucy'
78 | // }
79 | // }
80 | // }
81 | // }
82 | // }
83 | ```
84 |
85 | 此时,我们就可以打印出info中所有的层级了。
86 |
87 | ##### console.error
88 | 此方法如果我们没有在运行时指定输出文件(包括一般输出和错误输出),在我们运行node的时候可以手动指定日志和错误的输出文件,console.error方法就会输出到错误文件中。
89 |
90 | ```js
91 | node app.js 2> error.log | tee info.log
92 | ```
93 |
94 | 运行了以上代码,我们的错误信息都会记录到error.log文件中,普通日志信息都会记录到info.log文件中。如果我们在运行的时候没有指定输出文件,那么所有的信息都会输出到控制台,这时我们调用console.log和console.error是一样的效果。
95 |
96 | ##### console.warn、console.info
97 | console.warn的实现与console.error是一样的,console.info的实现与console.log是一样的。
98 |
99 | ##### console.dir
100 | console.dir方法就是与我们在上面介绍的util.inspect方法的效果一致。
101 |
102 | ```js
103 | var info = {
104 | status: 200,
105 | msg: {
106 | user:{
107 | name: 'Peter',
108 | country: 'Japan',
109 | company: {
110 | name: '全球无限皮包公司',
111 | ceo: {
112 | name: 'Lucy'
113 | }
114 | }
115 | }
116 | }
117 | }
118 |
119 |
120 | console.dir(info); // { status: 200, msg: { user: { name: 'Peter', country: 'Japan', company: [Object] } } }
121 | ```
122 |
123 | ##### console.time、console.timeEnd
124 | 一般console.time与console.tiemEnd成对出现,用于记录两者之间的代码运行时间
125 |
126 | ```js
127 | console.time('for-time');
128 |
129 | for(var i = 0;i < 10000000;i++){
130 | ;
131 | }
132 |
133 | console.timeEnd('for-time'); // for-time: 173ms
134 | ```
135 |
136 | 以上代码说明,在我的电脑上循环0到10000000需要耗费173毫秒。
137 |
138 | ##### console.trance
139 | 此方法会输出到错误文件中,在日志前添加“Trace:”字符串。
140 |
141 | ##### console.assert
142 | 此方法是一个断言函数,第一个参数是布尔值,如果第一个参数的结果为真,那么什么事情都不做;如果第一个布尔值为假,那么系统就会抛出错误,并且把后面的参数打印出来,并且会打印出调用栈。
143 |
144 | #### 总结
145 | console模块是一个非常简单地模块,所有的API我都已经解释或者演示过了。非常容易理解,这些方法在我们平常的开发过程中是很常见的,给noder提供了单接单并不唯一的调试node程序的方法(还有其他调试方法:node-inspector、vscode等,我以后有机会也会开博演示)。
146 |
--------------------------------------------------------------------------------
/node/events.md:
--------------------------------------------------------------------------------
1 | ##### 继承
2 | 之前我们在介绍Utilities模块的时候,遗留了最后一个方法(util.inherits)没有说。这是因为把此方法放到events模块一起讲更合适,不是说events模块对此方法有什么依赖,而是我们在使用events时,利用node提供的继承方法可以更方便和快捷。
3 |
4 | ##### util.inherits(constructor, superConstructor)
5 | 从字面上我们可以很清楚的知道,此方法是用来做JavaScript里面的继承功能的。参数如下:
6 |
7 | + 第一个参数(constructor)表示子类
8 |
9 | + 第二个参数(superConstructor)表示父类
10 |
11 | 与JavaScript复杂的继承代码相比,使用此函数确实非常方便。
12 |
13 | ```js
14 | var util = require('util');
15 |
16 | var People = function(info){
17 | this.name = info.name;
18 | this.age = info.age;
19 | }
20 |
21 | People.prototype.showName = function() {
22 | return util.format('My name is %s,', this.name);
23 | }
24 |
25 | People.prototype.showAge = function() {
26 | return util.format('I am %d years old.', this.age);
27 | }
28 |
29 | var Chinese = function(info){
30 | People.call(this, info);
31 | }
32 | util.inherits(Chinese, People);
33 |
34 | Chinese.prototype.introduce = function(){
35 | return '你好!' + this.showName() + this.showAge();
36 | }
37 |
38 | var chinese_poople = new Chinese({name: 'peter', age: 28});
39 |
40 | console.log(chinese_poople.introduce()); // 你好!My name is peter,I am 28 years old.
41 | ```
42 |
43 | 首先我们定义了People(父类)和Chinese(子类),然后利用util.inherits使Chinese继承了People。在Chinese中直接可以调用showName和showAge方法,表明util.inherits方法的继承效果已经生效了。util.inherits非常简单,只要提供2个参数即可,没有太多的花花肠子。
44 |
45 | ##### events
46 | 现在进入我们今天真正的主题,events(下称事件)模块是node的核心模块之一。在node中,事件可谓是无处不在。例如在[Node.js之HTTP](https://github.com/swfbarhr/blog/blob/master/node/http.md)提到过的server.on('request', function(){})就是处理request事件。而如果想要实现事件的推送与接收,我们必须要要继承events.EventEmitter
47 |
48 | ##### events.EventEmitter
49 | 只要继承了events.EventEmitter类,我们就可以正常使用emit方法和on方法来推送、接收事件。这里需要注意的是,'error'事件很特殊。如果我们没有监听'error'事件的话,一旦程序发成了错误,node就会在控制台上打印出调用栈,并且退出程序。
50 |
51 | ##### 监听事件 emitter.addListener(event, listener)、emitter.on(event, listener)
52 | 监听事件可以有2中写法一种是addListener,addListener方法有2个参数:第一个参数是事件名称(命名格式一般是驼峰式),第二个参数是对事件的处理函数。
53 |
54 | ```js
55 | var util = require('util');
56 | var EventEmitter = require('events').EventEmitter;
57 |
58 | var _eventDemo = function(){
59 | EventEmitter.call(this);
60 | }
61 |
62 | util.inherits(_eventDemo, EventEmitter);
63 |
64 | var _eventInstance = new _eventDemo();
65 |
66 | _eventInstance.addListener('connection', function(params){
67 | util.log('连接成功!'); // 3 Mar 14:11:36 - 连接成功!
68 | console.log(params); // { username: 'admin', pwd: '123' }
69 | });
70 |
71 | util.log('开始连接...'); // 3 Mar 14:11:31 - 开始连接...
72 | setTimeout(function(){
73 | _eventInstance.emit('connection', {username:'admin', pwd:'123'});
74 | }, 5000);
75 | ```
76 |
77 | 首先我们定义了_eventDemo并继承了EventEmitter。既然继承了EventEmitter,就可以使用addListener和on。然后我们就定义了事件'connection'和处理函数,为了方便起见,我在合适的位置打印了日志信息。当我们运行以上代码的时候,首先会打印“开始连接...”,等待5秒之后屏幕上回出现“连接成功!”,并且接着打印我们在触发事件(emit)时所传入的参数。这就表明我们的监听是可以正常运行的。
78 |
79 | 监听事件的另一个方法(on),与addListener用法是一模一样的,大家可以自己试试,这里就不多讲了。
80 |
81 | ##### emitter.once(event, listener)
82 | 此方法与emitter.once方法功能一致,唯一的却别就是,此方法对应一个emitter实例即使推送多次,其所监听的事件也只触发一次:
83 |
84 | ```js
85 | var util = require('util');
86 | var EventEmitter = require('events').EventEmitter;
87 |
88 | var _eventDemo = function(){
89 | EventEmitter.call(this);
90 | }
91 |
92 | util.inherits(_eventDemo, EventEmitter);
93 |
94 | var _eventInstance = new _eventDemo();
95 |
96 | _eventInstance.once('connection', function(){
97 | console.log('once');
98 | });
99 |
100 | _eventInstance.on('connection', function(){
101 | console.log('ok')
102 | });
103 |
104 | _eventInstance.emit('connection'); // once
105 | // ok
106 | _eventInstance.emit('connection'); // ok
107 | ```
108 |
109 | 如果我们运行以上代码,可以看到,即使我们触发了2次'connection'事件。当我们用_eventInstance.on来监听事件时,确实会触发2次;但是_eventInstance.once监听的事件却只触发了一次。这就是emitter.once的与emitter.on最大的区别。
110 |
111 | ##### emitter.removeListener(event, listener)
112 | 此方法是emitter.on方法的逆,此方法会移除掉对应的监听事件。需要注意的是,如果同一事件被监听了多次,那么也需要多次调用emitter.removeListener才能将其全部移除。
113 |
114 | ```js
115 | var util = require('util');
116 | var EventEmitter = require('events').EventEmitter;
117 |
118 | var _eventDemo = function() {
119 | EventEmitter.call(this);
120 | }
121 |
122 | util.inherits(_eventDemo, EventEmitter);
123 |
124 | var _eventInstance = new _eventDemo();
125 |
126 | var cb = function() {
127 | console.log('ok')
128 | }
129 |
130 | _eventInstance.on('connection', cb);
131 |
132 | _eventInstance.removeListener('connection', cb);
133 |
134 | _eventInstance.emit('connection'); // 这里什么都不会发生
135 | ```
136 |
137 | ```js
138 | var util = require('util');
139 | var EventEmitter = require('events').EventEmitter;
140 |
141 | var _eventDemo = function() {
142 | EventEmitter.call(this);
143 | }
144 |
145 | util.inherits(_eventDemo, EventEmitter);
146 |
147 | var _eventInstance = new _eventDemo();
148 |
149 | var cb = function() {
150 | console.log('ok')
151 | }
152 |
153 | _eventInstance.on('connection', cb);
154 | _eventInstance.on('connection', cb);
155 |
156 | _eventInstance.removeListener('connection', cb);
157 |
158 | _eventInstance.emit('connection'); // ok
159 | ```
160 |
161 | 上面2段代码,第一段代码我们添加了一个'connection'监听事件,并且移除了一次,运行代码之后什么事情都没有发生。第二段代码我们添加了两个'connection'监听事件,只移除了一次,所有在推送'connection'事件之后,会触发一次。
162 |
163 | ##### emitter.removeAllListeners([event])
164 | 移除所有监听事件
165 |
166 | ```js
167 | var util = require('util');
168 | var EventEmitter = require('events').EventEmitter;
169 |
170 | var Server = function() {
171 | EventEmitter.call(this);
172 | }
173 |
174 | util.inherits(Server, EventEmitter);
175 |
176 | var server1 = new Server();
177 |
178 | var connected = function() {
179 | console.log('connected');
180 | }
181 |
182 | var data = function() {
183 | console.log('data')
184 | }
185 |
186 | server1.on('connected', connected);
187 | server1.on('data', data);
188 |
189 | server1.emit('connected'); // connected
190 | server1.emit('data'); // data
191 |
192 | server1.removeAllListeners();
193 |
194 | console.log('all listeners are removed');
195 |
196 | server1.emit('connected'); // 什么事也没发生
197 | server1.emit('data'); // 什么事也没发生
198 | ```
199 |
200 | emitter.removeAllListeners有一个可选参数,如果传入该参数,表示移除所有指定事件名称的所有监听。大家可以自己试一下。
201 |
202 | ##### emitter.setMaxListeners(n)
203 | node默认同一对象的同一事件监听超过十次,就会发出警告信息。这可以帮助我们这个noder更好的找出令人烦恼的且蛋疼的内存泄露问题,因为内存泄露很多时候我们连原因都不知道,更不要说去修复问题了。
204 |
205 | ```js
206 | var util = require('util');
207 | var EventEmitter = require('events').EventEmitter;
208 |
209 | var Server = function() {
210 | EventEmitter.call(this);
211 | }
212 |
213 | util.inherits(Server, EventEmitter);
214 |
215 | var server1 = new Server();
216 |
217 | var connected = function() {
218 | console.log('connected');
219 | }
220 |
221 | for(var i = 0;i <= 10;i++){
222 | server1.on('connected', connected);
223 | }
224 | ```
225 |
226 | 如果我们运行以上代码,并且不会任何其他设置的话,就会得到以下的结果:
227 |
228 | 
229 |
230 | 但是如果对以上代码稍作修改的话,就会避免警告的发生
231 |
232 | ```js
233 | var util = require('util');
234 | var EventEmitter = require('events').EventEmitter;
235 |
236 | var Server = function() {
237 | EventEmitter.call(this);
238 | }
239 |
240 | util.inherits(Server, EventEmitter);
241 |
242 | var server1 = new Server();
243 |
244 | var connected = function() {
245 | console.log('connected');
246 | }
247 |
248 | server1.setMaxListeners(11);
249 |
250 | for(var i = 0;i <= 10;i++){
251 | server1.on('connected', connected);
252 | }
253 |
254 | console.log(emitter.listeners('connected'))
255 | ```
256 |
257 | 我们可以指定上限数量,也可以这样
258 |
259 | ```js
260 | emitter.setMaxListeners(0);
261 | ```
262 |
263 | 将参数设为0表示不设置监听上限数量,意味着这样一来,我们可以添加任意多个监听事件。
264 |
265 | ##### emitter.listeners(event)
266 | 返回指定事件的所有监听函数的集合。
267 |
268 | ```js
269 | var util = require('util');
270 | var EventEmitter = require('events').EventEmitter;
271 |
272 | var Server = function() {
273 | EventEmitter.call(this);
274 | }
275 |
276 | util.inherits(Server, EventEmitter);
277 |
278 | var server1 = new Server();
279 |
280 | var connected = function() {
281 | console.log('connected');
282 | }
283 |
284 | for(var i = 0;i <= 2;i++){
285 | server1.on('connected', connected);
286 | }
287 |
288 | console.log(server1.listeners('connected')); // [ [Function], [Function], [Function] ]
289 | ```
290 |
291 | ##### emitter.emit(event, [arg1], [arg2], [...])
292 | 触发或推送事件和指定参数(之前已经用的够多了,不需要再额外解释了)。
293 |
294 | ##### EventEmitter.listenerCount(emitter, event)
295 | 获取指定实例上,指定事件的数量。
296 |
297 | ```js
298 | var util = require('util');
299 | var EventEmitter = require('events').EventEmitter;
300 |
301 | var Server = function() {
302 | EventEmitter.call(this);
303 | }
304 |
305 | util.inherits(Server, EventEmitter);
306 |
307 | var server1 = new Server();
308 |
309 | var connected = function() {
310 | console.log('connected');
311 | }
312 |
313 | for(var i = 0;i <= 2;i++){
314 | server1.on('connected', connected);
315 | }
316 |
317 | console.log(EventEmitter.listenerCount(server1, 'connected')); // 3
318 | ```
319 |
320 | ##### 事件'newListener'和'removeListener'
321 | 每次为实例添加任意事件时触发'newListener'、每次为实例移除任意事件时触发'removeListener'
322 |
323 | ```js
324 | var util = require('util');
325 | var EventEmitter = require('events').EventEmitter;
326 |
327 | var Server = function() {
328 | EventEmitter.call(this);
329 | }
330 |
331 | util.inherits(Server, EventEmitter);
332 |
333 | var server1 = new Server();
334 |
335 | var connected = function() {
336 | console.log('connected');
337 | }
338 |
339 | server1.on('newListener', function(){
340 | console.log('new listener is added!');
341 | });
342 |
343 | server1.on('removeListener', function(){
344 | console.log('a listener is removed!');
345 | });
346 |
347 | for(var i = 0;i <= 9;i++){
348 | server1.on('connected', connected);
349 | }
350 |
351 | for(var i = 0;i <= 3;i++){
352 | server1.removeListener('connected', connected);
353 | }
354 | ```
355 |
356 | ##### 总结
357 | 如果想要学好node,必须要彻底的了解事件的机制。因为事件时node的基础中的基础。好了,事件模块介绍到这里,就全部介绍完成了,如果需要熟练掌握,还是要多练习或者在实际项目中多运用才行。
358 |
--------------------------------------------------------------------------------
/node/http.md:
--------------------------------------------------------------------------------
1 | 
2 | ##### 前言
3 | 在开始node.js(以后简称node)之前,我犹豫了好久:到底我第一篇用什么主题开始,是node的整体框架还是Hello world还是一堆废话呢。最后我决定还是直接上API,简单粗暴有效果,因为大家用node除了node有强大的社区之外,还因为node的API简单易用,容易上手。OK,下面我们就开始node之旅。
4 |
5 | ##### HTTP
6 | 大部分的node使用者,都是用node来做Web API的,而HTTP模块是提供Web API的基础。为了支持所有的HTTP应用,node中的HTTTP模块提供的API是偏向底层化的。利用HTTP模块,我们可以简单快速搭建一个Web Server。
7 |
8 | ##### 引入HTTP模块
9 | 在node[官方API](https://nodejs.org/dist/latest-v5.x/docs/api)中,第一句话是这么说的:“To use the HTTP server and client one must require('http')”,如果想要使用HTTP的服务器端和客户端的方法,首先第一步我们需要使用“require('http')”引入HTTP模块。
10 |
11 | ```js
12 | var http = require('http');
13 | ```
14 |
15 | 我们引入了HTTP模块,并且将结果赋值给了http变量,这样http就拥有了HTTP模块的所有方法和对象(关于requirejs的原理,与主题没什么太大关系,就略过。但是记住,require的过程是一个同步的过程,而前端的引入define是一个异步过程)。
16 |
17 | ##### 创建一个简单的Web Server
18 | 在HTTP模块中,node提供给我们一个createServer方法,来创建一个Web Server。代码可以这样写:
19 |
20 | ```js
21 | var http = require('http');
22 | var server = http.createServer();
23 |
24 | server.on('request', function(request, response) {
25 | console.log('有人请求了服务器!');
26 |
27 | response.writeHead(200, {
28 | 'Content-Type': 'text/plain'
29 | });
30 | response.write('OK');
31 | response.end();
32 | });
33 |
34 | server.listen(88, function() {
35 | console.log('服务器已经开始监听88端口');
36 | });
37 | ```
38 |
39 | 上面的代码:
40 |
41 | + 我们首先用http.createServer(从express4.X时代进入的同学可能有点陌生,express在4.X后把该方法封装到了其内部,但是我们在创建HTTPS连接时,还是要用到此方法)函数创建了一个服务器对象。
42 |
43 | + 然后监听了该对象的“request”事件(了解过node的同学应该知道,node是事件驱动的,一般事件触发和事件接收是成对出现的),这边我们定义接收事件。也就是说,如果当前服务器对象监听的端口一旦被请求,那么就会触发该事件。“request”的回调中,我们首先调用了response.writeHead方法:该方法的第一个参数表示HTTP的响应状态(200)表示一切正常;第二个参数是“Content-Type”,表示我响应给客户端的内容类型。然后我们调用了response.write方法,写入我们需要传递给客户端的内容。最后一步我们调用了response.end,表示此次请求已处理完成。
44 |
45 | + 最后我们调用了server.listen函数,此函数有两个参数,第一个参数表示我们需要监听的端口,第二个参数是回调函数(其实是listening事件),当监听开启后立刻触发。所以当我们用node命令来启动当前的js文件后,在cmd窗口会马上出现“服务器已经开始监听88端口”的字样,表明服务器正常且已经开始监听88端口。
46 |
47 | 当我们请求了服务器的88端口后,服务器会响应给我们“OK”字符串。到此我们的简单Web Server已经搭建完成,但是此时我们的代码还不具备任何业务能力,但是我们只要把代码稍稍修改下,就可以入眼了:
48 |
49 | ```js
50 | var url = require('url');
51 | var http = require('http');
52 | var server = http.createServer();
53 |
54 | function bizLogic(url) {
55 |
56 | if (url.path.toLowerCase() === '/login') {
57 | return {
58 | msg: '登录成功!'
59 | }
60 | } else if (url.path.toLowerCase() === '/user') {
61 | return {
62 | msg: [{
63 | name: '小明',
64 | class: '一年级1班'
65 | }, {
66 | name: '小红',
67 | class: '一年级3班'
68 | }]
69 | }
70 | } else {
71 | return {
72 | msg: 404
73 | }
74 | }
75 | }
76 |
77 | server.on('request', function(request, response) {
78 |
79 | if ('url' in request) {
80 | response.writeHead(200, {
81 | 'Content-Type': 'application/json; charset=utf-8'
82 | });
83 |
84 | response.write(JSON.stringify(bizLogic(url.parse(request.url))), 'utf8');
85 | } else {
86 | response.writeHead(404, {
87 | 'Content-Type': 'application/json; charset=utf-8'
88 | });
89 |
90 | response.write('找不到页面');
91 | }
92 |
93 | response.end();
94 | });
95 |
96 | server.listen(88, function() {
97 | console.log('服务器已经开始监听88端口');
98 | });
99 | ```
100 |
101 | + 首先我们引入了一个新的模块:“url”。这个模块主要帮助我们将整理请求url中的信息即url.parse方法(如果有兴趣,你可以去研究下url的所有API,其实就几个)。url.parse转换完成之后,url参数信息就变成这样了:
102 |
103 | ```js
104 | {
105 | protocol: null,
106 | slashes: null,
107 | auth: null,
108 | host: null,
109 | port: null,
110 | hostname: null,
111 | hash: null,
112 | search: null,
113 | query: null,
114 | pathname: '/login',
115 | path: '/login',
116 | href: '/login'
117 | }
118 | ```
119 |
120 | 这里我们取了我们需要的参数:path,简单的根据path来判断接口。然后响应客户端不同的内容。
121 |
122 | + 如果我们直接“response.write(bizLogic(url.parse(request.url)), 'utf8');”系统就会报错,response.write第一个参数必须是string或者Buffer,所以我们在这里将object转换成了string,然后接着write。
123 |
124 | + 最后我们直接结束连接(response.end)。
125 |
126 | 好了,一个简单的业务已经处理完成了,但是实际开过过程没有那么简单:HTTP方法有“GET”、“POST”、“PUT”等等。这也是要我们来处理的,所以如果我们用node来做Web API的话,一般会选择第三方的Web框架如:express、koa、restify等等(好吧,这是后话了,以后有机会我会去研究下他们各自的源码给大家分享)。
127 |
128 | ##### http.request
129 | 上面讲了那么多,其实都是在将HTTP Server的内容,既然node那么强大,当然除了可以作为HTTP Server之外,也可以做HTTP Client。现在就以我们刚刚建立的服务端为服务器,利用http.request来请求刚刚的服务器,看看会发生什么事情。
130 |
131 | ```js
132 | var http = require('http');
133 |
134 | var option, req;
135 |
136 | option = {
137 | host: '127.0.0.1',
138 | port: 88,
139 | path: '/login'
140 | };
141 |
142 | req = http.request(option, function(res){
143 |
144 | if(res.statusCode === 200) {
145 | console.log('ok');
146 | }
147 |
148 | res.on('data', function(chunk){
149 | console.log('the result is ', chunk.toString());
150 | });
151 | });
152 |
153 | req.on('error', function(error){
154 | console.log('something is wrong:', error.message);
155 | });
156 |
157 | req.end();
158 | ```
159 |
160 | + 首先我们当然要引入http模块,之后又定义了option和req对象。option是我们一会需要请求服务器的一些参数,req是请求对象用于接收request返回的结果(并不是请求的结果)
161 |
162 | + option参数是一个对象,使我们对此次请求设置的参数,包括:我们要请求的主机地址(host,注意此处不需要“http://”)、请求的请求的端口号(port)、请求的路由及参数(path)
163 |
164 | + 调用http.request请求,第一个参数就是我们刚刚设置的option,第二个参数是回调函数(用于返回我们请求的结果)
165 |
166 | + 回调函数有一个参数,这个参数是http.IncomingMessage的实例。字面意思就是“进来的消息”,也就是请求收到的响应结果对象。http.IncomingMessage实现了可读流接口,也就是说我们的“data”事件被触发时,回调函数中的结果是Buffer(关于Buffer我以后会详细解释---另一篇博客)对象,所以我在打印的时候调用了Bufer对象的toString方法把Buffer对象转换成字符串打印出来。
167 |
168 | + 最后我们监听了req的错误事件(在node中,我们如果出错不定义error事件的话,程序就会直接把错误抛出,如果没有做错误捕获的话,node程序就会崩掉),用于记录错误信息。最后我们关闭了请求。
169 |
170 | 经过这几个简单地步骤,我们此次请求就完成了,如果服务端没有错误的话,使用node命令运行我们刚刚写好的客户端代码,在cmd窗口先会打印“ok”,然后带打印“the result is {"msg":"登录成功!"}”。一旦请求出错,就会直接出发我们的“error”事件。当然如果不想使用原生的HTTP模块的话,你也可以使用第三方的包:[request](https://github.com/request/request)、[superagent](https://github.com/visionmedia/superagent)等都是非常优秀且方便的第三方包。(原生的HTTP模块可能会遇到各种奇葩的问题,使用第三方包的话吗,坑会少一些)。
171 |
172 | ##### 其他方法和事件
173 |
174 | + http.get是http.request的“GET”方法的方便版本,只能执行“GET”操作。
175 |
176 | + http.ClientRequest的“connection”事件,当TCP(HTTP其实也是TCP)的连接通道建立之后触发的事件。
177 |
178 | + http.ClientRequest的“close”事件,当服务端关闭连接时触发。
179 |
180 | + http.ServerResponse的setTimeout方法,设置响应超时。如果没有设置的话,只能等待socket或服务器超时。(还有很多方法和事件。就不一一列举了。)
181 |
182 | ##### 总结
183 | 在node v0.10.x时代,HTTP模块的稳定系数就已经是3了(一般node的API有4个级别,分别是:Deprecated-0、Experimental-1、Stable-2、Locked-3).所以说HTTP模块的API非常基础和稳定的。我们使用HTTP模块时,可以很放心的使用其方法和事件,不必担心版本兼容问题。需要提到的是,学习node的最好最捷径的方法就是学会读官方API,官方API虽然是英文的,但是里面的解释和英文单词都是很戳中要点和易于理解的。
184 |
--------------------------------------------------------------------------------
/node/net.md:
--------------------------------------------------------------------------------
1 | ##### 前言
2 | 最近在公司多个项目同时开工,连更新博客的时间都没了。但是该做的还得做,不能找理由、找借口,今天就挤点时间出来写完一篇吧。今天要说的node模块可以一个大头戏:net。此模块可以很多其他模块的基础,稳定性和重要性不言而喻。
3 |
4 | ##### net.createServer([options], [connectionListener])
5 | 创建一个TCP服务器,此函数有2个可选参数:
6 |
7 | + options 第一个参数是一个配置项,只有一个配置(allowHalfOpen)。表示允许服务器保持半开连接。
8 |
9 | + connectionListener 第二个参数是连接触发的事件函数,只要有客户端连接上来,此事件就会被触发。
10 |
11 | ```js
12 | // 创建一个服务器
13 | var net = require('net');
14 |
15 | var server = net.createServer(function(){
16 | console.log('有客户端连接!');
17 | });
18 |
19 | server.listen(8008, function(){
20 | console.log('服务器一开始监听8008端口!');
21 | });
22 | ```
23 |
24 | 运行以上代码,就会开启本机8008端口的TCP监听,当有客户端连接上来就会打印指定字符串:"有客户端连接!"(效果如下图)。
25 |
26 | 
27 |
28 | ##### net.connect(options, [connectionListener])、net.createConnection(options, [connectionListener])
29 | 这2个方法是一个实现逻辑,其实就是一个方法,node源码是这么写的:
30 |
31 | ```js
32 | exports.connect = exports.createConnection = function() {
33 | // ...
34 | };
35 | ```
36 |
37 | 此方法就是作为客户端去连接一个服务器端。此方法有2个参数,第一个参数(options)同样是配置项,第二个参数是一个可选参数,表示连接建立后推送的事件。
38 |
39 | + options 此参数是一个配置项,并且是必须的。配置项包括:
40 | + port 连接到的端口
41 | + host 对方(服务器)的主机地址,默认是"localhost"
42 | + localAddress 需要绑定的本地接口
43 | + path 在UNIX机器上的套接字路径
44 | + allowHalfOpen 是否允许半连接
45 |
46 | + connectionListener 连接建立之后推送的事件
47 |
48 | ```js
49 | // 创建一个服务器
50 | var net = require('net');
51 |
52 | var server = net.createServer(function(){
53 | console.log('有客户端连接!');
54 | });
55 |
56 | server.listen(8008, function(){
57 | console.log('服务器一开始监听8008端口!');
58 | });
59 | ```
60 |
61 | ```js
62 | // 创建一个客户端
63 | var net = require('net');
64 |
65 | net.connect({
66 | port: 8008
67 | }, function(){
68 | console.log('已经连接到本机的8008端口');
69 | });
70 | ```
71 | 分别运行上面的服务器和客户端代码,效果如下:
72 |
73 | 
74 |
75 | 另外这个方法还有简单版本,不需要配置options。
76 |
77 | ```js
78 | // 创建一个客户端
79 | var net = require('net');
80 |
81 | net.connect(8008, function(){
82 | console.log('已经连接到本机的8008端口');
83 | });
84 | ```
85 |
86 | 或者
87 |
88 | ```js
89 | // 创建一个客户端
90 | var net = require('net');
91 |
92 | net.connect('/root/c.sock', function(){
93 | console.log('已经连接到本机的8008端口');
94 | });
95 | ```
96 |
97 | ##### net.Server
98 | 这是一个类,用来创建TCPserver(上面我们提到的net.createServer方法返回的就是一个net.Server实例)
99 |
100 | #### server.listen(port, [host], [backlog], [callback])
101 | 开启接受连接。此方法有4个参数:
102 |
103 | + port 需要监听的端口,如果端口传入0的话,当前服务器会监听一个随机端口。
104 | + host 此参数是一个可选参数,如果不传此参数,当前服务器会接受所有IPV4的连接。
105 | + backlog 挂起连接的数量(默认值为511)
106 | + callback 监听开启回调函数(即listening事件)
107 |
108 | ```js
109 | var Server = require('net').Server;
110 | var tcpServer = new Server();
111 |
112 | // 开启监听8800端口
113 | tcpServer.listen(8800);
114 |
115 | // 监听开启事件
116 | tcpServer.on('listening', function(){
117 | console.log('监听服务已开启,端口:8800');
118 | });
119 | ```
120 |
121 | 以上代码很简单,首先开启了8800端口的监听,然后定义了listening事件,一旦开启8800端口监听成功,就会推送listening事件。当然,server.listen方法还有很多用法:
122 |
123 | ##### server.listen(path, [callback])、server.listen(handle, [callback])
124 |
125 | + server.listen(path, [callback]) 表示连接一个UNIX套接字
126 | + server.listen(handle, [callback]) 表示根据传入的处理对象(server或者socket)进行连接
127 |
128 | ##### server.close([callback])
129 | 停止接受新的连接,但是此方法调用之后现有的连接会保持,直到所有的连接都断开并且服务器触发了'close'事件之后,server才会真正的关闭。
130 |
131 | ```js
132 | var net = require('net');
133 | var server = new net.Server();
134 |
135 | server.listen(8800);
136 |
137 | server.on('listening', function() {
138 | console.log('started.');
139 | });
140 |
141 | setTimeout(function() {
142 | server.close(function() {
143 | console.log('closed.');
144 | });
145 | }, 3000);
146 | ```
147 |
148 | 运行以上代码之后,如果3秒内没有连接,那么服务器会自动退出,否则会等待所有连接处理完毕,才会关闭服务器端。另外以上代码也可以这样写:
149 |
150 | ```js
151 | var net = require('net');
152 | var server = new net.Server();
153 |
154 | server.on('listening', function() {
155 | console.log('started.');
156 | });
157 |
158 | server.on('close', function() {
159 | console.log('closed.');
160 | });
161 |
162 | server.listen(8800);
163 |
164 | setTimeout(function() {
165 | server.close();
166 | }, 3000);
167 | ```
168 |
169 | ##### server.address()
170 | 此方法返回一个JSON数据,包括绑定的IP地址、IP类型、端口号。
171 |
172 | ```js
173 | var net = require('net');
174 | var server = new net.Server();
175 |
176 | server.on('listening', function() {
177 | console.log(server.address()); // { address: '0.0.0.0', family: 'IPv4', port: 8000 }
178 | });
179 |
180 | server.listen(8000);
181 | ```
182 |
183 | 注意,只有在服务器触发了'listening'事件之后,server.address方法才可以调用,否则此方法会返回null值:
184 |
185 | ```js
186 | var net = require('net');
187 | var server = new net.Server();
188 |
189 | console.log(server.address()); // null
190 | server.listen(8000);
191 | ```
192 |
193 | ##### server.unref()、server.ref()
194 | server.unref方法会允许程序退出(在'事件系统'中只有当前服务器时)
195 |
196 | ```js
197 | var net = require('net');
198 | var server = new net.Server();
199 |
200 | server.listen(8000);
201 | server.unref();
202 | ```
203 |
204 | server.ref方法是server.unref的反操作,会阻止服务器退出(之前调用了server.unref)
205 |
206 | ```js
207 | var net = require('net');
208 | var server = new net.Server();
209 |
210 | server.listen(8000);
211 | server.unref();
212 |
213 | setTimeout(function() {
214 | server.ref();
215 | }, 3000);
216 | ```
217 |
218 | 此时服务器不会因为调用了server.unref方法而退出。
219 |
220 | ##### server.getConnections(callback)
221 | 获取当前连接数。
222 |
223 | ```js
224 | var net = require('net');
225 | var server = new net.Server();
226 |
227 | server.on('listening', function() {
228 | console.log('server is listening.');
229 | });
230 |
231 | server.on('connection', function() {
232 | server.getConnections(function(err, count) {
233 | if(err) return;
234 |
235 | console.log('当前的连接数:%d', count);
236 | });
237 | });
238 |
239 | server.listen(8000);
240 | ```
241 |
242 | ##### net.Server的事件
243 | net.Server的事件的事件有:'listening'、'connection'、'close'、'error',所有的事件都在上面的代码中间接介绍了。
244 |
245 | ##### net.Socket
246 | net.Socket是一个类,是TCP或UNIX套接字抽象。并且实现了复杂模式的Stream接口(关于Stream的更多信息,我会在以后的博客中介绍)。
247 |
248 | ##### new net.Socket([options])
249 | 创建一个socket实例,此方法有一个可选的配置参数:options,其中有4个属性可以配置
250 |
251 | + fd 指定socket文件描述符(默认值为null)
252 | + allowHalfOpen 是否允许半连接(默认值为false)
253 | + readable 是否可读(默认值为false)
254 | + writable 是否可写(默认值为false)
255 |
256 | ```js
257 | var net = require('net');
258 | var socket = new net.Socket({
259 | allowHalfOpen: true
260 | });
261 |
262 | socket.connect(8088);
263 |
264 | socket.on('connect', function() {
265 | console.log('The socket is established.');
266 | });
267 |
268 | socket.on('error', function() {
269 | console.log('Something is wrong.');
270 | });
271 | ```
272 |
273 | ##### socket.connect(port, [host], [connectListener])、socket.connect(path, [connectListener])
274 | 建立一个socket连接,并且可以指定端口、主机或者是连接文件。如果我们需要建立一个socket连接,可以这个干:
275 |
276 | 服务器端:
277 |
278 | ```js
279 | var net = require('net');
280 | var server = new net.Server();
281 |
282 | server.listen(8088, function() {
283 | console.log('listening.');
284 | });
285 |
286 | server.on('connection', function(socket) {
287 | console.log('someone is in.');
288 | });
289 | ```
290 |
291 | 在服务器端,我们需要创建一个net.Server实例,用来作为服务器等待连接。
292 |
293 | 客户端:
294 |
295 | ```js
296 | var net = require('net');
297 | var socket = new net.Socket({
298 | allowHalfOpen: true
299 | });
300 |
301 | socket.connect(8088);
302 |
303 | socket.on('connect', function() {
304 | console.log('The socket is established.');
305 | });
306 |
307 | socket.on('error', function() {
308 | console.log('Something is wrong.');
309 | });
310 | ```
311 |
312 | 在客户端我们利用net.connect方法连接上了我们刚刚创建的服务器实例,并且定义了'connect'事件(socket连接建立立即推送的事件)和'error'事件。如果连接建立成功,在服务器端就会打印出“listening.”和“someone is in.”,在客户端并打印“The socket is established.”。如果出现出错,客户端会触发'error'事件(打印“Something is wrong.”)。
313 |
314 | ##### socket.bufferSize
315 | 待写入字符大小
316 |
317 | ```js
318 | var net = require('net');
319 | var socket = new net.Socket({
320 | allowHalfOpen: true
321 | });
322 |
323 | socket.connect(8088);
324 |
325 | socket.on('connect', function() {
326 | console.log('The socket is established.');
327 | console.log(socket.bufferSize); // 0
328 | });
329 | ```
330 |
331 | ##### socket.setEncoding([encoding])
332 | 当socket被设置成可读时,此函数可用来设置Stream编码格式。
333 |
334 | ##### socket.write(data, [encoding], [callback])
335 | 使用socket发送数据,此函数有2个可选参数:
336 |
337 | + encoding 如果发送的数据为字符串,可以设置编码(默认为UTF-8)
338 | + callback 当数据全部被处理之后调用的回调
339 |
340 | 客户端:
341 |
342 | ```js
343 | var net = require('net');
344 | var socket = new net.Socket({
345 | allowHalfOpen: true
346 | });
347 |
348 | socket.connect(8088);
349 |
350 | socket.on('connect', function() {
351 | console.log('The socket is established.');
352 |
353 | socket.write('Hello Socket', function() {
354 | console.log('done.');
355 | });
356 | });
357 | ```
358 |
359 | 服务器端:
360 |
361 | ```js
362 | var net = require('net');
363 | var server = new net.Server();
364 |
365 | server.listen(8088, function() {
366 | console.log('listening.');
367 | });
368 |
369 | server.on('connection', function(socket) {
370 | console.log('someone is in.');
371 |
372 | socket.on('data', function(data) {
373 | console.log(data); // Hello Socket
374 | });
375 | });
376 | ```
377 |
378 | ##### socket.end([data], [encoding])
379 | 半关闭socket,如果传入了可选参数data,那么相当于调用了socket.write(data)
380 |
381 | ```js
382 | var net = require('net');
383 | var socket = new net.Socket({
384 | allowHalfOpen: true
385 | });
386 |
387 | socket.connect(8088);
388 |
389 | socket.on('connect', function() {
390 | console.log('The socket is established.');
391 |
392 | socket.end('bye.');
393 | });
394 | ```
395 |
396 | ##### socket.destroy()
397 | 调用此方法后,除了一些出错信息,其他一切I/O都不可进行(可以认为是客户端主动关闭连接)
398 |
399 | ```js
400 | var net = require('net');
401 | var socket = new net.Socket({
402 | allowHalfOpen: true
403 | });
404 |
405 | socket.connect(8088);
406 |
407 | socket.on('error', function(err) {
408 | console.log('error:' + err.message); // error:This socket is closed.
409 | });
410 |
411 | socket.on('connect', function() {
412 | console.log('The socket is established.');
413 |
414 | socket.destroy();
415 |
416 | socket.write('Hello'); // 此数据将不被传输,因为连接已经关闭
417 | });
418 | ```
419 |
420 | ##### socket.pause()、socket.resume()
421 | 暂停读取数据、回复读取数据
422 |
423 | 发送端代码:
424 | ```js
425 | var net = require('net');
426 | var socket = new net.Socket({
427 | allowHalfOpen: true
428 | });
429 |
430 | socket.connect(8088);
431 |
432 | socket.on('error', function(err) {
433 | console.log('error:' + err.message);
434 | });
435 |
436 | socket.on('connect', function() {
437 | console.log('The socket is established.');
438 |
439 | setInterval(function(){
440 | socket.write(new Date() + 'Hello');
441 | }, 1000);
442 | });
443 | ```
444 |
445 | 接收端代码:
446 | ```js
447 | var net = require('net');
448 | var server = new net.Server({
449 | allowHalfOpen: true
450 | });
451 |
452 | server.listen(8088, function() {
453 | console.log('listening.');
454 | });
455 |
456 | server.on('connection', function(socket) {
457 | console.log('someone is in.');
458 |
459 | socket.on('data', function(data) {
460 | console.log(data.toString());
461 | });
462 |
463 | socket.pause();
464 |
465 | setTimeout(function(){
466 | socket.resume();
467 | }, 1500);
468 | });
469 | ```
470 |
471 | ##### socket.setTimeout(timeout, [callback])
472 | 设置socket超时,超时(指定时间)被触发之后,此socket将继续可用,我们必须手动调用socket.end或socket.destroy方法来中断连接
473 |
474 | 服务器端:
475 | ```js
476 | var net = require('net');
477 | var server = new net.Server({
478 | allowHalfOpen: true
479 | });
480 |
481 | server.listen(8088, function() {
482 | console.log('listening.');
483 | });
484 |
485 | server.on('connection', function(socket) {
486 | console.log('someone is in.');
487 |
488 | socket.on('data', function(data) {
489 | console.log(data.toString());
490 | });
491 | });
492 | ```
493 |
494 | 客户端:
495 | ```js
496 | var net = require('net');
497 | var socket = new net.Socket({
498 | allowHalfOpen: true
499 | });
500 |
501 | socket.on('timeout', function() {
502 | console.log('timeout');
503 |
504 | socket.end('end');
505 | });
506 |
507 | socket.setTimeout(2000);
508 | socket.connect(8088);
509 | ```
510 |
511 | ##### socket.setNoDelay([noDelay])
512 | 设置无延迟发送(关闭Nagle算法)。将数据立即发送出去,而无需使用Nagle算法。
513 |
514 | ##### socket.setKeepAlive([enable], [initialDelay])
515 | 开启心跳
516 |
517 | + enable 是否开启心跳(默认false)
518 | + initialDelay 心跳间隔
519 |
520 | ##### socket.address()、socket.unref()、socket.ref()
521 | 这3个方法与上面net.Server的3个方法类似,这里就不赘述
522 |
523 | ##### 其他属性
524 |
525 | + socket.remoteAddress 远程地址
526 | + socket.localAddress 本地地址
527 | + socket.remotePort 远程端口
528 | + socket.localPort 本地端口
529 | + socket.bytesRead 已接收字节数
530 | + socket.bytesWritten 已发送字节数
531 |
532 | ##### 事件'connect'、'data'、 'end'、'timeout'、'error'、'close'
533 | 以上的事件都在上面的代码中使用过,已经熟悉了。而'drain'事件表示写入buffer为空时触发的事件(可用于上传逻辑)。
534 |
535 | ##### net.isIP(input)、net.isIPv4(input)、net.isIPv6(input)
536 |
537 | + net.isIP(input) 判断是否是IP地址(如果是IPV4就返回数字4,如果是IPV6就返回数字6,如果都不是则返回0):
538 |
539 | ```js
540 | var net = require('net');
541 |
542 | console.log(net.isIP('172.16.0.90')); // 4
543 | console.log(net.isIP('2001:0DB8:02de::0e13')); // 6
544 | ```
545 |
546 | + net.isIPv4(input) 判断是否是IPV4
547 |
548 | ```js
549 | var net = require('net');
550 |
551 | console.log(net.isIPv4('172.16.0.90')); // true
552 | console.log(net.isIPv4('2001:0DB8:02de::0e13')); // false
553 | ```
554 |
555 | + net.isIPv6(input) 判断是否是IPV6
556 |
557 | ```js
558 | var net = require('net');
559 |
560 | console.log(net.isIPv6('172.16.0.90')); // false
561 | console.log(net.isIPv6('2001:0DB8:02de::0e13')); // true
562 | ```
563 |
564 | ##### 总结
565 | 讲到这里,net模块就讲完了。其实与其他语言比较起来,使用node来做socket代码非常简洁并且天生异步的模式也非常符合生产环境使用。
566 |
567 | ##### 题外话
568 | 这边博客与上一篇间隔时间很长,大概有20多天的样子。期间忙于公司的事情与自我学习(主要是docker方向的),并且看到在[cnode](https://cnodejs.org/)已经有人在写关于node源码的文章了,所以我觉得关于node源码的部分我不全部讲解,只是在个别模块与注意点上写几篇跟人观点。
569 |
--------------------------------------------------------------------------------
/node/path.md:
--------------------------------------------------------------------------------
1 | ##### 介绍
2 | path模块其实就是对路径的处理,而路径实际就是字符串。那么,说到底path模块就是对字符串的一些特殊处理。这个模块非常简单,拥有很少的API,但是非常实用,在实际开发过程中,如果没有了解path模块或者不够熟悉的话,可能会走很多弯路(这也说明了基础API的重要性)。
3 |
4 | ##### path.normalize(p)
5 | 我们知道,一般而言在程序中“..”和“.”表示上一级文件夹路径和当前路径。而path.normalize方法就是专注于处理这两个符号,并返回对应的路径。
6 |
7 | ```js
8 | var path = require('path');
9 |
10 | console.log(path.normalize('/a/b/./c')); // \a\b\c
11 | console.log(path.normalize('/a/b/../c')); // \a\c
12 | console.log(path.normalize('/a/b/c/../')); // \a\b\
13 | console.log(path.normalize('/a/b//c')); // \a\b\c
14 | console.log(path.normalize('/a/b/c/.../')); // \a\b\c\...\
15 | ```
16 |
17 | 根据以上代码我们可以看出,此方法只是对“..”和“.”进行处理,并且返回处理结果。需要注意的是,如果是多个斜杠(比如上述代码第四个log),path.normalize会把其处理成一个斜杠。
18 |
19 | ##### path.join([path1], [path2], [...])
20 | 把多个路径参数拼接成一个路径,并且会用path.normalize处理拼装结果。
21 |
22 | ```js
23 | var path = require('path');
24 |
25 | console.log(path.join('/a', '/n', '/b', '/h')); // \a\n\b\h
26 | console.log(path.join('/a', '/n', '..', '/h')); // \a\h
27 | console.log(path.join('/a', '/../../', '/h')); // \h
28 | ```
29 |
30 | path.join的所有参数都是字符串,但是如果你非要捣蛋的话(比如我传入一个object),那会出现什么情况呢。
31 |
32 | ```js
33 | var path = require('path');
34 |
35 | console.log(path.join('/a', {}, '/h')); // throw new TypeError('Arguments to path.join must be strings');
36 | ```
37 |
38 | 好吧,报错了!看来还是不能瞎搞的!
39 |
40 | ##### path.resolve([from ...], to)
41 | 这个方法在官方有两种解释,第一种解释我噼里啪啦讲了半天都没看懂,然后看到下面才发现有第二种解释(简单容易理解)。
42 |
43 | ```js
44 | var path = require('path');
45 |
46 | console.log(path.resolve('/foo/bar', '/bar/faa', '..', 'a/../c')); // C:\bar\c
47 | ```
48 |
49 | 以上的代码就相当于一堆shell脚本
50 |
51 | ```
52 | cd /foo/bar
53 | cd /bar/faa
54 | cd ..
55 | cd a/../c
56 | pwd
57 | ```
58 |
59 | 代码与shell脚本的区别在于,代码所针对的路径不一定要存在并且可以是文件:
60 |
61 | ```js
62 | var path = require('path');
63 |
64 | console.log(path.resolve('/foo/bar', '/bar/faa', '..', 'a/../c.txt')); // C:\bar\c.txt
65 | ```
66 |
67 | ##### path.relative(from, to)
68 | 返回从from到to的相对路径,比如我们要在from对应的文件夹中引用to文件夹中的文件,那么我们就可以使用这个方法,例如:
69 |
70 | 首先我们有一个test文件夹,里面有一个文件:test.js(F:\jsresearch\test\test.js)
71 |
72 | ```js
73 | function showLevel(){
74 | return 'The level is 5';
75 | }
76 |
77 | exports.showLevel = showLevel;
78 | ```
79 |
80 | 然后我们在与test文件夹平级的nodejs文件夹中引用test.js文件,我们可以这么做
81 |
82 | ```js
83 | var path = require('path');
84 | var test = require(path.relative(__dirname, 'F:\\jsresearch\\test\\test.js'));
85 |
86 | console.log(test.showLevel()); // The level is 5
87 | ```
88 |
89 | 当我们调用showLevel方法时,就可以正常调用了。其实以上的代码等价于:
90 |
91 | ```js
92 | var path = require('path');
93 | var test = require('../test/test.js');
94 |
95 | console.log(test.showLevel()); // The level is 5
96 | ```
97 |
98 | ##### path.dirname(p)
99 | 获取指定路径的目录
100 |
101 | ```js
102 | var path = require('path');
103 |
104 | console.log(path.dirname('/ab/cd/a.txt')); // /ab/cd
105 | console.log(path.dirname('/ab/cd/a')); // /ab/cd
106 | console.log(path.dirname('/ab/cd/a/')); // /ab/cd
107 | ```
108 |
109 | ##### path.basename(p, [ext])
110 | 获取指定路径文件的文件名
111 |
112 | ```js
113 | var path = require('path');
114 |
115 | console.log(path.basename('/ab/c.txt')); // c.txt
116 | console.log(path.basename('/ab/c.txt', '.txt')); // c
117 | console.log(path.basename('/ab/c.txt', 'txt')); // c.
118 | console.log(path.basename('/ab/c.txt', 'xt')); // c.t
119 | ```
120 |
121 | 第一个参数表示路径,第二个参数表示需要除去的结尾字符。举个栗子:如果第二个参数是“xt”那么我们得到的结果就会删除“xt”字符。
122 |
123 | ##### path.extname(p)
124 | 获取对应文件的扩展名
125 |
126 | ```js
127 | var path = require('path');
128 |
129 | console.log(path.extname('/ab/c.txt')); // .txt
130 | console.log(path.extname('/ab/c.html')); // .html
131 | console.log(path.extname('/ab/c.md')); // .md
132 | console.log(path.extname('/ab/c.')); // .
133 | ```
134 |
135 | ##### path.sep、path.delimiter
136 | + path.sep 路径分隔符号,一般在Windows是反斜杠,在linux是斜杠
137 | + path.delimiter 路径分割符号,一般在Windows是分号,在linux是冒号,用于分割多个路径
138 |
139 | ##### 总结
140 | path模块非常简单,此模块专注于路径(也就是字符串)的特殊处理。
141 |
--------------------------------------------------------------------------------
/node/qs_url.md:
--------------------------------------------------------------------------------
1 | ##### 前言
2 | 由于Query Strings和URL模块的API都比较少和简单,所以我把这两个模块放在一起讲。
3 |
4 | ##### Query Strings
5 | 相信从字面上理解,大家就已经知道此模块是用于处理Query String的。
6 |
7 | ##### querystring.stringify(obj, [sep], [eq])
8 | 此方法可以将object转换成字符串,三个参数分别的意义是:
9 |
10 | + obj 待转换的对象
11 |
12 | + sep 此参数是一个可选参数,表示多个参数之间的分隔符(一般使用'&'作为分隔符,默认是'&')
13 |
14 | + eq 此参数也是一个可选参数,表示参数与对应值之间的分隔符(一般使用'='作为分隔符,默认是'=')
15 |
16 | ```js
17 | var querystring = require('querystring');
18 |
19 | var obj = {
20 | method: 'add',
21 | user: 'admin',
22 | age: 31,
23 | score: [98, 91]
24 | };
25 |
26 | console.log(querystring.stringify(obj)); // method=add&user=admin&age=31&score=98&score=91
27 | console.log(querystring.stringify(obj, ';', ':')); // method:add;user:admin;age:31;score:98;score:91
28 | ```
29 |
30 | ##### querystring.parse(str, [sep], [eq], [options])
31 | querystring.parse是querystring.stringify方法的逆。参数sep和eq与querystring.stringify有着相同的意义,但是此方法有第四个可选参数,此参数是可以配置项内目前为止就一个配置参数:maxKeys,表示最大处理的keys数量,超过此数量就默认略过(maxKeys的默认值为1000)
32 |
33 | ```js
34 | var querystring = require('querystring');
35 |
36 | console.log(querystring.parse('method=add&user=admin&age=31&score=98&score=91')); // { method: 'add', user: 'admin', age: '31', score: [ '98', '91' ] }
37 | console.log(querystring.parse('method:add;user:admin;age:31;score:98;score:91', ';', ':')); // { method: 'add', user: 'admin', age: '31', score: [ '98', '91' ] }
38 | console.log(querystring.parse('method=add&user=admin&age=31&score=98&score=91', '&', '=', { maxKeys: 2 })); // { method: 'add', user: 'admin' }
39 | ```
40 |
41 | 这里需要注意的是,如果我们需要用到options参数,那么sep和eq是不可以省略的。不然的话,解析会出乎于预期:
42 |
43 | ```js
44 | var querystring = require('querystring');
45 |
46 | console.log(querystring.parse('method=add&user=admin&age=31&score=98&score=91', { maxKeys: 2 })); // { method: 'add&user=admin&age=31&score=98&score=91' }
47 | ```
48 |
49 | ##### querystring.escape、querystring.unescape
50 | querystring.escape其实就是对字符串进行urlencode,而querystring.unescape则是对字符串进行urldecode
51 |
52 | ```js
53 | var querystring = require('querystring');
54 |
55 | console.log(querystring.escape('a=ss&b=111')); // a%3Dss%26b%3D111
56 | console.log(querystring.unescape('a%3Dss%26b%3D111')); // a=ss&b=111
57 | ```
58 |
59 | ##### URL
60 | 此模块是处理url相关的问题。
61 |
62 | ##### url.parse(urlStr, [parseQueryString], [slashesDenoteHost])
63 | 将字符串转换成object对象,与querystring.parse功能类似。
64 |
65 | + urlStr 表示需要转换的字符串
66 |
67 | + parseQueryString 如果将此项设置为true,则表示使用querystring模块转换querystring
68 |
69 | + slashesDenoteHost 如果此项设置为true,则表示将双斜线后的当做为host
70 |
71 | ```js
72 | var url = require('url');
73 |
74 | console.log(url.parse('HTTP://admin:123@172.16.0.1:3000/a/b/c?test=true#list')); // { protocol: 'http:',
75 | // slashes: true,
76 | // auth: 'admin:123',
77 | // host: '172.16.0.1:3000',
78 | // port: '3000',
79 | // hostname: '172.16.0.1',
80 | // hash: '#list',
81 | // search: '?test=true',
82 | // query: 'test=true',
83 | // pathname: '/a/b/c',
84 | // path: '/a/b/c?test=true',
85 | // href: 'http://admin:123@172.16.0.1:3000/a/b/c?test=true#list' }
86 |
87 | console.log(url.parse('HTTP://admin:123@172.16.0.1:3000/a/b/c?test=true#list', true)); // { protocol: 'http:',
88 | // slashes: true,
89 | // auth: 'admin:123',
90 | // host: '172.16.0.1:3000',
91 | // port: '3000',
92 | // hostname: '172.16.0.1',
93 | // hash: '#list',
94 | // search: '?test=true',
95 | // query: { test: 'true' },
96 | // pathname: '/a/b/c',
97 | // path: '/a/b/c?test=true',
98 | // href: 'http://admin:123@172.16.0.1:3000/a/b/c?test=true#list' }
99 |
100 | console.log(url.parse('//admin:123@172.16.0.1:3000/a/b/c?test=true#list', true, true)); // { protocol: null,
101 | // slashes: true,
102 | // auth: 'admin:123',
103 | // host: '172.16.0.1:3000',
104 | // port: '3000',
105 | // hostname: '172.16.0.1',
106 | // hash: '#list',
107 | // search: '?test=true',
108 | // query: { test: 'true' },
109 | // pathname: '/a/b/c',
110 | // path: '/a/b/c?test=true',
111 | // href: 'http://admin:123@172.16.0.1:3000/a/b/c?test=true#list' }
112 | ```
113 |
114 | ##### url.format(urlObj)
115 | 此方法是url.parse的逆方法,就是将object转换成字符串
116 |
117 | ```js
118 | var url = require('url');
119 |
120 | var obj = {
121 | protocol: 'http:',
122 | slashes: true,
123 | auth: 'admin:123',
124 | host: '172.16.0.1:3000',
125 | port: '3000',
126 | hostname: '172.16.0.1',
127 | hash: '#list',
128 | search: '?test=true',
129 | query: {
130 | test: 'true'
131 | },
132 | pathname: '/a/b/c',
133 | path: '/a/b/c?test=true',
134 | href: 'http://admin:123@172.16.0.1:3000/a/b/c?test=true#list'
135 | };
136 |
137 | console.log(url.format(obj)); // http://admin:123@172.16.0.1:3000/a/b/c?test=true#list
138 | ```
139 |
140 | ##### url.resolve(from, to)
141 | 可以简单地理解成:用后面的对应的属性替换前面的属性。过程可以理解成(内部过程可能更加复杂,以下步骤仅供理解,不是官方步骤):
142 |
143 | + 第一步:调用url.parse
144 | + 第二步:Object.assign
145 | + 第三部:url.stringify
146 |
147 | ```js
148 | var url = require('url');
149 |
150 | var url = require('url');
151 |
152 | console.log(url.resolve('/one/two/three', '//host.com:300/four')) // //host.com:300/four
153 | console.log(url.resolve('http://example.com/', '/one')) // 'http://example.com/one'
154 | console.log(url.resolve('http://example.com/one', 'http://ssss.com/one/one/two')) // 'http://ssss.com/one/one/two'
155 | ```
156 |
157 | ##### 总结
158 | Query Strings模块和URL模块其实都是非常实用的两个模块,但是由于我们做node一般都使用第三方框架,如:express、koa、restify等。里面都已经封装好了对应的方法,所以这两个模块可能在实际开发中用到的不多,但是在做node原生开发时(不使用第三方框架),会非常有用。
159 |
--------------------------------------------------------------------------------
/node/readline.md:
--------------------------------------------------------------------------------
1 | ##### 前言
2 | 近一段时间工作太忙,并且在研究docker相关的技术,好久没更新博客了。今天趁任务完成的早,在公司补上一篇。今天要讲的这个模块,让我瞬间就回到了刚学C#的时候,做一些小程序逗同学的年代。今天我要介绍的模块是Readline(记得C#考试的时候Console.ReadLine这个方法我是运用了我的英语知识拼出来的,现在想想都是泪呀!)
3 |
4 | ##### Readline
5 | 这个模块可以读取stream,比如说process.stdin。以前对stdin也是不太理解,最近研究docker就对stdin概念稍稍了解了一下下。std是standard(标准)的缩写,in就是输入,组合起来解释就是标准输入,而stdout我的理解就是标准输出(见识浅薄,大神勿喷)。而Readline可以读取process.stdin,我们就可以利用这一点,使得程序通过控制台的标准输入输出与用户进行简单的交互。
6 |
7 | ```js
8 | var readline = require('readline');
9 |
10 | var rl = readline.createInterface({
11 | input: process.stdin,
12 | output: process.stdout
13 | });
14 |
15 | rl.question('你是2B吗?', function(answer){
16 |
17 | if(answer === '是'){
18 | console.log('好吧,再见!2B!');
19 | rl.close();
20 | }
21 |
22 | if(answer === '不是'){
23 | console.log('no no no,显然你就是2B,有本事来咬我呀!咬我呀!咬我呀!');
24 | }
25 | });
26 | ```
27 |
28 | 好吧,我承认代码写的是有点逗逼(以前也经常这样逗同学)。但是需要注意的是,如果我们激活了Readline模块,并且使用了它,node程序是不会自动退出的(除非我们使用代码,比如上面的rl.close()或者直接关闭控制台)。
29 |
30 | ##### readline.createInterface(options)
31 | 创建一个readline的Interface实例,使用Interface实例,我们就可以与用户交互了。此方法有一个参数,此参数是一个配置项:
32 |
33 | + input 设置需要监听哪个可读流,例如:process.stdin
34 |
35 | + output 设置需要将可写流写入到哪里
36 |
37 | + completer 自动完成函数
38 |
39 | + terminal 如果设置为true则把输入输出看做一个虚拟终端
40 |
41 | 输入、输出就不多讲了,我们把输入输出分别设置成process.stdin和process.stdout。先来讲讲completer
42 |
43 | ```js
44 | var readline = require('readline');
45 |
46 | var completer = function(line){
47 | var completions = ['Hello', 'World', 'You', 'are', 'man'];
48 |
49 | var shots = completions.filter(function(s){
50 | return s.indexOf(line) == 0;
51 | });
52 |
53 | return [shots, line];
54 | }
55 |
56 | var rl = readline.createInterface({
57 | input: process.stdin,
58 | output: process.stdout,
59 | completer: completer
60 | });
61 | ```
62 |
63 | 运行以上代码,当我们输入'H'然后按TAB键时,就会自动补全'Hello'这个单词。
64 |
65 | ##### rl.setPrompt(prompt, length)
66 | 此方法包含两个参数,第一个参数表示命令行最前面的“前缀”,就像我们直接运行node安装路径下的node.exe程序,最前面会有一个'>'符号,我们也可以通过这个方法在Readline中设置我们自己的“前缀”:
67 |
68 | ```js
69 | var readline = require('readline');
70 |
71 | var completer = function(line){
72 | var completions = ['Hello', 'World', 'You', 'are', 'man'];
73 |
74 | var shots = completions.filter(function(s){
75 | return s.indexOf(line) == 0;
76 | });
77 |
78 | return [shots, line];
79 | }
80 |
81 | var rl = readline.createInterface({
82 | input: process.stdin,
83 | output: process.stdout,
84 | completer: completer
85 | });
86 |
87 | rl.setPrompt('test>>', 6);
88 | ```
89 |
90 | 就拿我们刚刚的“自动补全代码”为例,在最后添加了一句代码。我们的在进行刚刚的操作:输入'H'然后按TAB键。就会出现'test>>'在'Hello'之前。此方法第二个参数官方API中也没有说明,并且在最新版本的API(V5.9.1)中,已经没有这个参数。但是以我打破砂锅问到底的精神,我去翻阅了老版本的node源码,发现这个length参数是用来设置光标位置的。其实上面的代码非常傻,只有我们在进行了一系列操作后才会出现“前缀”,还有一个看起来更舒服的处理方式,让我们的程序看起来更专业。
91 |
92 | ##### rl.prompt([preserveCursor]);
93 | 此方法效果就是在新的行前主动添加我们设置的“前缀”,参数为布尔型,我们如果传入true,可以防止光标位置被重置为0:
94 |
95 | ```js
96 | var readline = require('readline');
97 |
98 | var rl = readline.createInterface({
99 | input: process.stdin,
100 | output: process.stdout
101 | });
102 |
103 | rl.setPrompt('test>>', 6);
104 | rl.on('line', function (cmd) {
105 | rl.prompt();
106 | });
107 | rl.prompt();
108 | ```
109 |
110 | 以上代码的效果如下:
111 |
112 | 
113 |
114 | ##### rl.question(query, callback)
115 | 此方法就是我在最开始使用的引入示例,此方法的功能就是先输出query,然后接收用户输入的内容,当检测到用户输入回车时,调用callback并且将用户刚刚输入的内容以回调参数的形式给出,过多的示例我就不举了,上面“2B”示例已经很2了,不想再2一次了。
116 |
117 | ##### rl.pause()、rl.resume()、rl.close()
118 | 暂停接收输入、恢复接收输入。
119 |
120 | ```js
121 | var readline = require('readline');
122 |
123 | var rl = readline.createInterface({
124 | input: process.stdin,
125 | output: process.stdout
126 | });
127 |
128 | setTimeout(function(){
129 | rl.pause();
130 | }, 1000);
131 |
132 | setTimeout(function(){
133 | rl.resume();
134 | }, 4000);
135 |
136 | setTimeout(function(){
137 | rl.resume();
138 | }, 6000);
139 | ```
140 |
141 | 运行以上代码,1秒之后会有3秒的空白(不可输入),到4秒之后又可以输入。然后6秒的时候程序自动退出了。
142 |
143 | ##### rl.write(data, [key])
144 | 想stdout输出data,key是一个object对象,可以模拟键盘按键。以下代码就是模拟了ctrl+u
145 |
146 | ```js
147 | var readline = require('readline');
148 |
149 | var rl = readline.createInterface({
150 | input: process.stdin,
151 | output: process.stdout
152 | });
153 |
154 | rl.write('这些文字回本显示出来!3秒后消失!');
155 |
156 | setTimeout(function(){
157 | rl.write(null, {ctrl: true, name: 'u'});
158 | }, 3000);
159 | ```
160 |
161 | ##### 事件:'line'、'pause'、'resume'、'close'
162 | 分别在换行、暂停、恢复、关闭时触发事件,都非常简单,这里就不多做介绍。
163 |
164 | ##### 事件'SIGINT'
165 | 表示终端信号(一般ctrl+c表示终端信号)
166 |
167 | ```js
168 | var readline = require('readline');
169 |
170 | var rl = readline.createInterface({
171 | input: process.stdin,
172 | output: process.stdout
173 | });
174 |
175 | rl.on('SIGINT', function(){
176 | rl.question('是否要退出程序?', function(answer) {
177 | if (answer.match(/^y(es)?$/i)) rl.close();
178 | else rl.prompt();
179 | });
180 | });
181 | ```
182 |
183 | ##### 事件'SIGTSTP'、'SIGCONT'
184 | 分别表示接收到暂停信号、程序切换到后台运行时触发(这2个事件在Windows上是无法正常工作的)
185 |
186 | ##### 其他方法
187 | + readline.cursorTo(stream, x, y) 将光标移动到stream的指定位置
188 |
189 | + readline.moveCursor(stream, dx, dy) 将光标移动到stream的相对位置置
190 | + readline.clearLine(stream, dir) 清除当前行
191 |
192 | + readline.clearScreenDown(stream) 清除当屏幕
193 |
194 |
195 | ##### 总结
196 | 此模块不是一个非常常用的模块,但是我们可以利用此模块写出很好玩的程序,例如:一个简单的终端程序,或者可以整一下不懂程序的同学。
197 |
--------------------------------------------------------------------------------
/node/stream-chapter1.md:
--------------------------------------------------------------------------------
1 | # 前言
2 | 今天要讲的模块(stream,下称流)比较特殊,在node官网的API文档中分成了3个部分:第一部分是介绍流的用法,第二部分是介绍如何实现自定义的流,第三部分是介绍流的原理与内部实现机制。针对这3个部分,我会分3篇博客来讲,现在就来讲讲流的用法(第一部分,也是最基础的部分)
3 |
4 | # 概念
5 | 流其实不是一个实质的对象或方法,流是一些抽象的接口,只有这些接口被实现了,流才有了真正的意义。所有的流都是EventEmitter的实例,也就是说所有的流都可以触发、响应事件。
6 |
7 | # 分类
8 | 从功能上讲,流一般可以分为“读取流”、“写入流”、“复杂流”:读取流用来读取,写入流用来写入,复杂流可以读写。
9 |
10 | # stream.Readable
11 | stream.Readable是一个类,利用这个类,我们可以读取流里面的内容。可读流有2中模式:flowing-mode、non-flowing-mode
12 |
13 | + flowing-mode 此模式会尽快把数据读取出来
14 |
15 | + non-flowing-mode 此模式需要显示调用读取方法,才会把数据读取出来
16 |
17 | ### 事件 'readable'
18 | 当流中有可读取的数据块时,就会触发该事件。
19 |
20 | ```js
21 | var fs = require('fs');
22 |
23 | var readableStream = fs.createReadStream('file.txt');
24 |
25 | readableStream.on('readable', function(){
26 | console.log('有数据进来!');
27 | });
28 | ```
29 |
30 | 如果读取到file.txt的数据,那么'readable'就会被触发,在终端上回打印“有数据进来!”
31 |
32 | ### 事件 'data'
33 | 一旦监听了'data'事件,会将流的模式转换成“flowing-mode”,也就是说数据会被尽快读出。
34 |
35 | ```js
36 | var fs = require('fs');
37 |
38 | var readableStream = fs.createReadStream('file.txt');
39 |
40 | readableStream.on('data', function(data){
41 | console.log('data:%s', data);
42 | });
43 | ```
44 |
45 | ### 事件 'end'
46 | 当没有可以读数据之后,会触发此事件
47 |
48 | ```js
49 | var fs = require('fs');
50 |
51 | var readableStream = fs.createReadStream('file.txt');
52 |
53 | readableStream.on('data', function(data){
54 | console.log('data:%s', data);
55 | });
56 |
57 | readableStream.on('end', function(){
58 | console.log('所有数据读取完毕!');
59 | });
60 | ```
61 |
62 | ### 事件 'close'
63 | 当基础资源被关闭之后,会触发此事件。
64 |
65 | ### 事件 'error'
66 | 当接收数据出错时,触发此事件
67 |
68 | ```js
69 | var fs = require('fs');
70 |
71 | var readableStream = fs.createReadStream('/NotExistPath/file.txt');
72 |
73 | readableStream.on('error', function(err){
74 | console.log('错误:%s', err.message); // 错误:ENOENT, open 'F:\NotExistPath\file.txt'
75 | });
76 | ```
77 |
78 | ### readable.read([size])
79 | 显示读取数据(此方法只能在“non-flowing-mode”使用),此方法有一个可选参数size,表示一次要读取的数据。如果不传入size的话,表示读取所有数据,另外如果没有数据的话此方法会返回null。
80 |
81 | ```js
82 | var fs = require('fs');
83 |
84 | var readableStream = fs.createReadStream('file.txt');
85 |
86 | readableStream.on('readable', function(){
87 | console.log('有数据进来!数据:' + readableStream.read()); // 有数据进来!数据:我到底二不二
88 | });
89 | ```
90 |
91 | ### readable.setEncoding(encoding)
92 | 设置可读流的读出数据的编码格式,并且会强制将数据转换成字符串
93 |
94 | ### readable.resume()
95 | 恢复推送'data'事件,并且将模式转换成“flowing-mode”
96 |
97 | ```js
98 | var fs = require('fs');
99 |
100 | var readableStream = fs.createReadStream('file.txt');
101 |
102 | readableStream.resume();
103 |
104 | readableStream.on('end', function(){
105 | console.log('所有数据读取完毕!');
106 | });
107 | ```
108 |
109 | ### readable.pause()
110 | 暂停触发'data'事件
111 |
112 | ```js
113 | var fs = require('fs');
114 |
115 | var readableStream = fs.createReadStream('file.txt');
116 |
117 | readableStream.pause();
118 |
119 | readableStream.on('data', function(data){
120 | console.log('data:%s', data); // 此处不会得到数据,因为我们调用了readableStream.pause
121 | });
122 | ```
123 |
124 | ### readable.pipe(destination, [options])
125 | 此方法会将可读流里的所有数据读取出来,并写入destination(通常是一个写入流对象)。此方法还有一个可选的配置参数options:
126 |
127 | + end 默认为true,表示数据读取结束之后是否需要关闭写入流。
128 |
129 | ```js
130 | var fs = require('fs');
131 |
132 | var readableStream = fs.createReadStream('file.txt');
133 | var writableStream = fs.createWriteStream('file1.txt');
134 |
135 | readableStream.pipe(writableStream);
136 | ```
137 |
138 | 需要注意的是,此方法的返回值还是一个可读的流,所以可以链式调用。
139 |
140 | ```js
141 | var fs = require('fs');
142 | var zlib = require('zlib');
143 |
144 | var readableStream = fs.createReadStream('file.txt');
145 | var writableStream = fs.createWriteStream('file1.txt');
146 | var zStream = zlib.createGzip();
147 |
148 | readableStream.pipe(zStream).pipe(writableStream);
149 | ```
150 |
151 | ### readable.unpipe([destination])
152 | 此操作为readable.pipe的逆操作,调用之后readable中读取出来的数据块不会再写入destination,destination是可选参数。如果我们没有指定destination,那么readable数据块不会任何一个destination中。
153 |
154 | ### readable.unshift(chunk)
155 | 这个方法比较牛逼,我一开始很不理解,因为它颠覆了我的观念。一般从流中读取是“单向”的,也就是说读出来的数据是不可以再回去的。但是readable.unshift居然能把“吐”出来的东西又“吃”回去(感觉有点恶心),但是有的场景还是很有用的。
156 |
157 | ### readable.wrap(stream)
158 | 此方法是为了兼容老版本而设的,老版本stream没有实现现有的API,我们就可以使用此方法来弥补,这里引用node官方一个例子:
159 |
160 | ```js
161 | var OldReader = require('./old-api-module.js').OldReader;
162 | var oreader = new OldReader;
163 | var Readable = require('stream').Readable;
164 | var myReader = new Readable().wrap(oreader);
165 |
166 | myReader.on('readable', function() {
167 | myReader.read(); // etc.
168 | });
169 | ```
170 |
171 | OldReader是老版本的类库,使用wrap方法包裹之后返回的myReader就可以使用当前所有的API了
172 |
173 | # stream.Writable
174 | 可写流表示数据最终目的地的抽象。
175 |
176 | ### writable.write(chunk, [encoding], [callback])
177 | 写入数据,writable.write有三个参数,第一个参数是需要写入的数据、第二个参数表示写入的编码格式、第三个参数表示写入完成之后的回调。此方法的返回值表示数据是否已经被处理完成,这样就可以判断是否需要继续写入数据。
178 |
179 | ```js
180 | var fs = require('fs');
181 |
182 | var writableStream = fs.createWriteStream('file.txt');
183 |
184 | var result = writableStream.write('test write method' , 'utf-8', function() {
185 | console.log('complete'); // complete
186 | console.log(result); // true
187 | });
188 | ```
189 |
190 | ### 事件 'drain'
191 | writable.write返回false,那么数据再次被写入时,事件'drain'就会触发
192 |
193 | ```js
194 | var fs = require('fs');
195 |
196 | var writableStream = fs.createWriteStream('file.txt');
197 |
198 | writableStream.on('drain', function() {
199 | console.log('data is drain!');
200 | });
201 |
202 | var result = writableStream.write('big data');
203 |
204 | console.log(result); // false
205 | ```
206 |
207 | 以上代码中,如果我们给出的数据足够大,导致一次处理完成不了,我们得到的result将会是false。然后,在剩余数据继续被处理开始时,就会触发'drain'事件。
208 |
209 | ### writable.end([chunk], [encoding], [callback])
210 | 写入数据并且结束流通道(此参数与writable.write方法参数一致,这里就不做重复解释)。
211 |
212 | ### 事件 'finish'
213 | 当调用writable.end方法之后,所有数据都被写入基础系统中,就会触发此事件
214 |
215 | ```js
216 | var fs = require('fs');
217 |
218 | var writableStream = fs.createWriteStream('file.txt');
219 |
220 | writableStream.write('test data line1');
221 | writableStream.end('test data line2');
222 |
223 | writableStream.on('finish', function() {
224 | console.log('line1 and line2 finished!');
225 | });
226 | ```
227 |
228 | ### 事件 'pipe'、'unpipe'、'error'
229 |
230 | + pipe 调用readable.pipe到此可写流时触发
231 | + unpipe 调用readable.unpipe到此可写流时触发
232 | + error 写入或者pipe数据时出错时,触发此事件(是可读流的'error'事件类似)
233 |
234 | # 总结
235 | stream在整个node的核心模块运用都非常广泛,例如:http模块、File System模块等。这说明此模块地位非常重要,并且当我们需要处理大文件时,通常第一个想到的也是stream模块。所以,少年!好好学习,天天向上吧!
236 |
--------------------------------------------------------------------------------
/node/stream-chapter2.md:
--------------------------------------------------------------------------------
1 | # 前言
2 | [上一篇](stream-chapter1.md)博客中,我们介绍了stream的基本用法,今天我们来讲讲stream接口如何实现,之后我们就可实现自己的stream类了。
3 |
4 | # stream.Readable
5 | 简单来说,如果需要自定义stream.Readable类,那么我们需要做的就是一件事:重写_read方法。
6 |
7 | ```js
8 | var Readable = require('stream').Readable;
9 | var util = require('util');
10 | util.inherits(Prefix, Readable);
11 |
12 | function Prefix(pre, opt){
13 | Readable.call(this, opt);
14 |
15 | this._prefix = pre || 'node:';
16 | this._count = 0;
17 | this._max = 9;
18 | }
19 |
20 | Prefix.prototype._read = function(){
21 |
22 | if(this._count++ >this._max) {
23 | this.push(null);
24 | } else {
25 | this.push(new Buffer(this._prefix + this._count.toString()));
26 | }
27 | }
28 |
29 | var preObj = new Prefix('node v6:');
30 |
31 | console.log(preObj.read().toString()); // node v6:1
32 | console.log(preObj.read().toString()); // node v6:2
33 | console.log(preObj.read().toString()); // node v6:3
34 | ```
35 |
36 | ### new stream.Readable([options])
37 | 当我们实现了Readable,就可以使用new来创建一个实例对象,我们可以传入一个可选的options配置参数。options有3项:
38 |
39 | + highWaterMark 表示内部buffer中最大的字节数
40 | + encoding 如果指定了此参数,buffer会被按照此格式转换成字符串
41 | + objectMode 如果此参数设置为true,当我们调用read方法时,每次只能读取一个值即使我们传入了size参数
42 |
43 | 以上面的Prefix为例:
44 |
45 | ```js
46 | var preObj1 = new Prefix('node v6:');
47 |
48 | console.log(preObj1.read()); //
49 | console.log(preObj1.read(1).toString()); // n
50 | console.log(preObj1.read().toString()); // ode v6:2node v6:3
51 |
52 | console.log('---------------超华丽分割线----------------');
53 |
54 | var preObj2 = new Prefix('node v6:', {highWaterMark:10, encoding:'utf8', objectMode:true});
55 |
56 | console.log(preObj2.read()); // node v6:1
57 | console.log(preObj2.read(8)); // node v6:2
58 | console.log(preObj2.read()); // node v6:3
59 | ```
60 |
61 | 通过对比可以看出:
62 |
63 | + 当我们没有设置encoding时,调用read方法返回的是buffer对象
64 | + 当我们设置objectMode为true时,每次只返回单个值(不可以根据size大小返回值)
65 |
66 | ### readable._read(size)
67 | 这是一个接口方法,此方法是用来实现的并且不可以直接调用(一般默认以下划线开始的方法为内部方法)。具体用法在上面第一个代码示例中已经展示,这里就不多赘述。
68 |
69 | ### readable.push(chunk, [encoding])
70 | 此方法是向读取队列中添加数据,当我们插入null时,表示数据插入完成。此方法有2个参数:
71 |
72 | + chunk chunk参数接收Buffer或者String或者null
73 |
74 | + encoding 表示当chunk为String时的编码格式
75 |
76 | ```js
77 | var Readable = require('stream').Readable;
78 | var util = require('util');
79 | util.inherits(PushTest, Readable);
80 |
81 | function PushTest(options) {
82 | Readable.call(this, options);
83 |
84 | // 假设souce对象一有数据就会调用data方法
85 | // 并且数据全部消费完之后调用end方法
86 | this._source = source;
87 |
88 | this._source.data = function(chunk) {
89 | if (!self.push(chunk)){
90 | this._source.readStop();
91 | }
92 | }
93 |
94 | this._source.end = function(){
95 | this.push(null);
96 | }
97 | }
98 |
99 | PushTest.prototype._read = function(size) {
100 | this._source.readStart();
101 | }
102 | ```
103 |
104 | # stream.Writable
105 | 与stream.Readable相似,stream.Writable也是一个抽象类,其中Writable.\_write方法不可以直接被调用,Writable.\_write需要被实现。
106 |
107 | ### new stream.Writable([options])
108 | 与stream.Readable一样,我们自定义的写入流类需要使用new来创建一个实例,当我们创建实例的时候,需要传入一个可选的options,此options同样有3个配置项:
109 |
110 | + highWaterMark 表示写入的缓冲区大小,我们调用write方法时单次写入的大小如果超过设定的阈值,就会返回false,highWaterMark就是这个阈值(默认大小16kb)
111 | + decodeStrings 在写入之前(调用内部_write之前),是否将字符串转成Buffer(默认为true)
112 | + objectMode 是否可以写入任意类型的数据,而不仅仅是Buffer或String(默认为false)
113 |
114 | ### writable._write(chunk, encoding, callback)
115 | 同样的,如果我们需要实现自定义的写入流,我们需要实现一个内部接口,此接口在node源码中是这样的:
116 |
117 | ```js
118 | Writable.prototype._write = function(chunk, encoding, cb) {
119 | cb(new Error('not implemented'));
120 | };
121 | ```
122 |
123 | 由此可知,如果我们需要实现自定义的写入流(可读流也是一样),就必须实现\_write方法,否则就会报错。\_write有三个参数:
124 |
125 | + chunk 需要写入的数据
126 | + encoding 如果写入数据是字符串,那么此参数表示该数据的编码格式
127 | + callback 回调函数(数据写入完成后触发)
128 |
129 | ```js
130 | var util = require('util');
131 | var Writable = require('stream').Writable;
132 | var fs = require('fs');
133 |
134 | util.inherits(CustomFS, Writable);
135 |
136 | function CustomFS(path, options) {
137 | Writable.call(this, options);
138 |
139 | this._path = path;
140 | }
141 |
142 | CustomFS.prototype._write = function(chunk, encoding, cb) {
143 |
144 | // 判断文件是否存在
145 | fs.exists(this._path, function(exists) {
146 |
147 | if (exists) {
148 | fs.writeFile(this._path, +new Date() + ':' + chunk + '\n', cb);
149 | } else {
150 | cb('找不到文件!');
151 | }
152 | }.bind(this));
153 | }
154 |
155 | // 创建实例
156 | var writeFile = new CustomFS('./file.txt', {
157 | highWaterMark: 1024
158 | });
159 |
160 | // 写入数据
161 | writeFile.write('窗前明月光,地上鞋两双!', function(err){
162 | if(err) {
163 | return console.log('报错啦!错误:' + err.message);
164 | }
165 |
166 | console.log('写入完成!');
167 | });
168 | ```
169 |
170 | 以上代码在每次写入文本之前,都会在最前面加入时间戳。
171 |
172 | # stream.Duplex类
173 | “复杂”类型的流,在我们实现它时,既要实现可读流的\_read方法,又要实现写入流的\_write方法。也就是说,只有同时实现了可读、可写两个功能,才可以叫做“复杂”型流即'duplex'。具体可读和写入流的自定义实现代码我们在上面已经介绍过了,这里就不多讲。
174 |
175 | # stream.Transform类
176 | 'transform'类型的流属于“复杂”类型,但是我们不需要实现\_read和\_write,但是我们要实现另一个方法:_transform。'transform'类型的流的特点在于输入决定输出。就像crypto(加密模块)一样,输入决定了输出(比如哈希值)。
177 |
178 | ### new stream.Transform([options])
179 | 当然,如果要创建一个'transform'流的实例,还是要通过new关键字(这样可以保证正确的初始化实例)。同样,新建实例时也需要传入配置参数,此配置参数就是只读流和写入流配置项的和。
180 |
181 | ### transform._transform(chunk, encoding, callback)
182 | '_transform'是我们需要实现一个自定义'transform'流的必须要实现的一个方法,其参数意义与“写入流”的_write方法类似。
183 |
184 | ### transform._flush(callback)
185 | 此方法会在调用end方法之后,但是在触发'end'或'finish'事件之前调用,便于我们做一些事情。一下是一个'transform'类型流例子:
186 |
187 | ```js
188 | var util = require('util');
189 | var Transform = require('stream').Transform;
190 |
191 | util.inherits(Trans, Transform);
192 |
193 | function Trans(options) {
194 | Transform.call(this, options);
195 | }
196 |
197 | Trans.prototype._transform = function(chunk, encoding, callback) {
198 |
199 | this.push(new Buffer('Transform: ' + chunk.toString()));
200 |
201 | callback();
202 | }
203 |
204 | Trans.prototype._flush = function(callback) {
205 | console.log('flushed!');
206 | callback();
207 | }
208 |
209 | var myTrans = new Trans();
210 |
211 | myTrans.write('你好,小强!', function(err) {
212 | if (err) {
213 | return console.log('报错啦!错误:' + err.message);
214 | }
215 |
216 | console.log('写入完成!');
217 | console.log(myTrans.read().toString());
218 | myTrans.end();
219 | });
220 |
221 | myTrans.on('finish', function() {
222 | console.log('finished!');
223 | });
224 | ```
225 |
226 | # 总结
227 | 关于stream第二部分,到这里就算是介绍完了。自定义的stream类只需要我们按照实现规则,去丰富不同的内部方法而已。这些对于混迹于JavaScript界的老司机来说,是一件非常轻松的事情,但是我们需要理解stream并融会贯通之后再来做这些事情的话,又会有另一层理解。
228 |
--------------------------------------------------------------------------------
/node/stream-chapter3.md:
--------------------------------------------------------------------------------
1 | # 前言
2 | 前面2个部分,分别介绍了Stream的基础使用和自定义进阶。为了更好的帮助大家理解这个模块的,node官方还有第三部分,可以使我们进一步理解Stream模块的内部实现原理
3 |
4 | # 缓冲
5 | 可读流和写入流各自都有一个缓冲区,分别存放在内部对象\_writableState和\_readableState的'buffer'属性中。在可读流中,自定义实现时我们可以通过stream.push方法将数据推入此属性中('buffer'属性),在通过stream.read方法来消费缓冲区的内容;在写入流中,我们通过stream.write方法将我们的数据块写入到'buffer'属性中
6 |
7 | ### stream.read(0)
8 | 当我们不需要读取流中内容,但是又需要触发内部刷新机制。这时,我们可以在调用stream.read方法时传入参数0,此调用的返回值永远是null并且不会消耗任何缓冲信息。这种调用的应用场景并不常见,但是在node源码中确实有这样的调用方式。
9 |
10 | ### stream.push('')
11 | 当我们调用stream.push的时候传入空字符串或者空Buffer时,会产生一个副作用:终止读取过程。
12 |
13 | ```js
14 | var Readable = require('stream').Readable;
15 | var util = require('util');
16 | util.inherits(Prefix, Readable);
17 |
18 | function Prefix(pre, opt) {
19 | Readable.call(this, opt);
20 |
21 | this._prefix = pre || 'node:';
22 | this._count = 0;
23 | this._max = 9;
24 | }
25 |
26 | Prefix.prototype._read = function() {
27 |
28 | if (this._count > this._max) {
29 | this.push(null);
30 | } else if (this._count === 2) {
31 | this.push('');
32 | } else {
33 | this.push(new Buffer(this._prefix + this._count.toString()));
34 | }
35 |
36 | this._count++;
37 | }
38 |
39 | var preObj = new Prefix('node v6:');
40 |
41 | console.log(preObj.read().toString()); // node v6:0
42 | console.log(preObj.read().toString()); // node v6:1
43 | console.log(preObj.read().toString()); // TypeError: Cannot call method 'toString' of null
44 | ```
45 |
46 | 可以看到当我们往缓冲区中push了空字符串之后,会导致调用read方法返回null值。其实,需要用到此场景的地方极其少,如果非要使用stream.push(''),最好还是先考虑另择他法。
47 |
48 | # 兼容老版本
49 | 在node v0.10之前,可读流(Readable)接口非常简单,功能也简单:
50 |
51 | + 老版本中不会等待read方法调用之后才会输出数据,而是会立即触发'data'事件,开始推送数据。
52 | + pause方法只是建议型的方法,不作出任何数据保证。我们还是需要使用'data'事件来接收数据,即使当前状态是'暂停状态'
53 |
54 | 为了兼容老版本,node v0.10中一旦我们添加了'data'事件的处理函数或者调用了pause或resume方法,此时stream会切换至flowing-mode,数据会尽快推送至'data'事件中。而在node v0.10我们就再也不用担心数据丢失的问题了。
55 |
56 | # Object Mode
57 | 当我们在创建一个stream实例的时候,将配置参数objectMode设置为true时,此时流就可以推送JavaScript对象(一般情况下流是专注于处理String和Buffer)。当我们处于obejct mode模式下,stream.read(size)中的size参数会被忽略,而每次读取一个单独的值、stream.write(data, encoding)则会忽略编码。
58 |
59 | # State Objects
60 | 一般的,可读流在内部会有一个内置的对象:\_readableState、而写入流也会相应的有一个内置对象:\_writableState。当我们在自定义子类的时候,应该避免修改这2个内置对象,否则会导致不可预料的后果。
61 |
62 | #总结
63 | 这一篇基本是关于流模块的一些收尾工作以及对于流的进一步认识。进一步了解流内部的机制以及实现原理,可以更好的帮助我们在以后用到此模块的时候更加得心应手。好了,关于流的介绍到这里就画上一个句号。
64 |
--------------------------------------------------------------------------------
/node/token_hold.md:
--------------------------------------------------------------------------------
1 | # 前言
2 | 消失了快2个月,俺又回来了。最近换比较忙,好久没写博客,但是学习的脚步一直没停下。前段时间在[cnode](https://www.cnodejs.org)上看到一个关于微信access_token的问题:[高并发如何保证微信token的有效](https://cnodejs.org/topic/57b330b6670139eb7bf6fd4c#57b51330d6124db37b1d13cb)。其中本人也在上面回复了一下,但是觉得解决方案还是不够好,于是就有了本篇:本文主要以渐进的方式,来一步一步解决高并发情况下access_token的获取与保存。
3 |
4 | # 前提条件
5 | 由于本文讨论是基于微信公众平台开发展开的,所以如果对微信公众平台开发不熟悉的同学可以先去看下微信公众平台的[开发文档](https://mp.weixin.qq.com/wiki/home/)
6 |
7 | # 需要解决的问题
8 | 本文讨论的其实是access_token获取与保存在高并发情况下的边界问题:
9 | + 1.在node单进程情况下 第一个请求(请求A)过来,程序发现access_token过期,这时就会去向微信服务器获取新的access_token,然后更新到全局变量中。这个过程本身没有问题,但是如果请求A在向微信服务器请求新的access_token期间,来了第二个请求(请求B),因为这时新的access_token还未更新到全局变量中,请求B就会认为access_token已过期,也会同请求A一样,去微信服务器请求新的access_token,这样就会导致请求A得到的access_token失效。想象一下,如果在请求A后面又来了N个请求,并且此时请求A还未成功更新access_token,那么后面的所有请求都会去微信服务器获取新的access_token。以上的情况,不仅会导致浪费带宽,而且会导致最后一个请求之前获取的access_token都失效。那我们应该如何做控制,在高并发情况下仅请求一次微信服务器呢?
10 |
11 | + 2.在node多进程模式下 如果我们的项目开启了node多进程,情况将更加复杂:我们不仅会遇到在单进程情况下的问题,而且由于node进程之间是不共享内存的,也就是说上面说到的全局变量是无法使用的,如果第一个请求由进程1处理,而此时access_token已过期,然后第二个请求由进程2处理。即使在进程1成功更新了access_token,但是由于进程1与进程2内存不共享,所以如果不借助外部存储的话,后面一个请求也无法得知access_token已更新。那么,这种情况有该如何解决呢?
12 |
13 | # (单进程模式下)思路
14 | 首先我们来讨论,如何解决单进程模式下高并发遇到的问题。
15 | + 第一步我们需要引入一个新的全局变量,用来标记已经有请求在获取新的access_token
16 | + 第二步就是将后续的请求缓存到node的事件队列中去
17 | + 第三步统一触发事件
18 |
19 | 具体实现代码:
20 |
21 | ```js
22 | var Emitter = require('events').Emitter;
23 | var util = require('util');
24 |
25 | var access_token, flag;
26 |
27 | function TokenEmitter() {
28 | Emitter.call(this);
29 | }
30 | util.inherits(TokenEmitter, Emitter);
31 |
32 | myEmitter = new TokenEmitter();
33 | // 消除警告
34 | myEmitter.setMaxListeners(0);
35 |
36 | function getAccessToken(appID, appSecret, callback) {
37 | // 将callback缓存到事件队列中,等待触发
38 | myEmitter.once('token', callback);
39 |
40 | // 判断access_token是否过期
41 | if (!isValid(access_token) && !flag) {
42 | // 标记已经向微信服务器发送获取access_token的请求
43 | flag = true;
44 |
45 | // 向微信服务器请求新的access_token
46 | requestForAccessToken(appID, appSecret, function(err, newToken) {
47 | if (err) {
48 | // 通知出错
49 | return myEmitter.emit('token', err);
50 | }
51 |
52 | // 更新access_token
53 | access_token = newToken;
54 | // 触发所有监听回调函数
55 | myEmitter.emit('token', null, newToken.access_token);
56 | // 还原标记
57 | flag = false;
58 | });
59 | } else {
60 | process.nextTick(function(){
61 | callback(null, access_token.access_token);
62 | });
63 | }
64 | }
65 | ```
66 |
67 | 以上代码主要的思路就是利用,node自带的事件监听器,也就是代码中的'myEmitter.once()'方法,在access_token失效的情况下把所有调用的回调方法添加为'token'事件监听函数。并且只有第一个调用者可以去更新access_token的值(主要用flag来控制)。当获得新的access_token后,以新access_token为参数,去触发'token'事件。此时,所有监听了'token'事件的函数都会被调用,也就是说,所有调用者的回调函数都会被调用。这样,我们就实现了高并发情况下,防止access_token被多次更新的问题,也就是解决了问题1。
68 |
69 | # (多进程模式下)思路
70 | 解决了单进程模式下的问题,可以说我们多进程问题也解决了一部分。在多进程模式下,我们的主题思路还是与单进程一直,将调用缓存到事件队列中。但是,多进程的各个进程是不共享内存的,所以我们的access_token和flag标记不可以存储在变量中,因此需要引入外部存储:redis。使用redis作为外部存储有以下几个原因:
71 | + 1.在redis中统一存储access_token,各个进程都可以自由访问
72 | + 2.利用redis作为锁媒介(相当于单进程中的flag)
73 | + 3.利用redis发布订阅功能来触发事件(相当于单进程中的emit)
74 |
75 | ## 统一存储access_token
76 | 这一点大家都应该没什么疑问,access_token统一存储的好处就是不需要面对复杂的进程见通信。
77 |
78 | ## 锁媒介
79 | 当我们标记“正在请求微信服务器”的flag标志不可以放在代码的变量中时,那就要寻求代码之外的解决方法,其实我们可以存在mongodb、mysql等等可以存储的媒介中,甚至可以存放在文本文件中。但是为了保证速度,我还是考虑将其存放在速度更快的redis中。
80 |
81 | ## redis发布订阅功能
82 | 当然,如果我们的程序使用的是node的cluster模块开启的多进程模式,进程间通信还是相对容易一些:每个worker都可以向master发送message,利用这一点把master当做中心,来交换数据。但是如果我们是使用pm2开启了多实例,pm2虽然提供了实例间通信的API,但是使用起来各种不顺畅,最终选择redis来作为各个实例接受通知的发起方。
83 |
84 | 以上思路的实现代码大致如下:
85 |
86 | 1.第一步需要做的就是判断access_token是否过期(为了方便起见,直接用appID + appSecret作为存储access_token的键):从redis获取键为appID + appSecret的内容,因为我们在设置access_token时,是将其设为了过期键(设置过程涉及到锁,将在之后给出),所以只要能取到值,就说明access_token没有过期。代码如下:
87 |
88 | ```js
89 | function isValid(appID, appSecret, callback) {
90 | redis.get(appID + appSecret, function(err, token) {
91 | if (err) {
92 | return callback(err);
93 | }
94 |
95 | // 可以取到值
96 | if (tokenInfo) {
97 | return callback(null, token);
98 | }
99 |
100 | // 未取到值
101 | callback(null);
102 | });
103 | }
104 | ```
105 |
106 | 2.如果在第一步的判断中,我们得出结论:access_token已经过期,那么我们需要做的下一步就是设置一个代码级别的锁,防止之后的程序访问之后的代码:
107 | ```js
108 | function aquireLock(callback) {
109 | redis.setnx('lock', callback);
110 | }
111 |
112 | function releaseLock(callback) {
113 | redis.del('lock', callback);
114 | }
115 | ```
116 | 这2个函数,一个用于设置锁,一个用于释放锁。我们设置锁是利用了redis的setnx命令原理:setnx只可以设置不存在的key,即使同一时间有多个setnx命令来设置同一个key,最终只有一个客户端可以成功设置'lock'键,也就是说只有一个请求获得了锁的权限。这样就控制了并发产生的问题。
117 |
118 | 3.最后我们将所有程序写入主函数中:
119 | * 1).主函数中一进来,首先添加监听器
120 | * 2).第二步我们需要订阅redis的new_token和new_token_err频道
121 | * 3).第三步判断access_token是否过期
122 | * 4).如果过期,就尝试去获取锁权限
123 | * 5).如果获取锁失败,就什么都不做,等待事件触发;如果获取锁权限成功后,就可以请求微信服务器来获取新的access_token,当获得新的access_token之后将其更新到redis中,并且设置合理的过期时间
124 | * 6).释放锁并且发出通知
125 | ```js
126 | function getAccessToken(appID, appSecret, callback) {
127 | // 将callback缓存到事件队列中,等待触发
128 | myEmitter.once('token', callback);
129 |
130 | // 处理订阅消息
131 | subscribe.on('message', (channel, message) => {
132 | switch (channel) {
133 | case 'new_token':
134 | myEmitter.emit('token', null, message);
135 | break;
136 | case 'new_token_err':
137 | myEmitter.emit('token', new Error(message));
138 | break;
139 | default:
140 | break;
141 | }
142 | });
143 |
144 | // 判断access_token是否过期
145 | isValid(appID, appSecret, function(err, token) {
146 | // 出错
147 | if (err) {
148 | return myEmitter.emit('token', err);
149 | }
150 |
151 | // token正常
152 | if (token) {
153 | return myEmitter.emit('token', null, token.access_token);
154 | }
155 |
156 | // token已过期,获取锁
157 | aquireLock(function(err, result) {
158 | // 如果获取锁成功,则开始更新access_token,如果未得到锁,等待'token'触发
159 | if (result) {
160 | // 向微信服务器请求新的access_token
161 | requestForAccessToken(appID, appSecret, function(err, newToken) {
162 | if (err) {
163 | // 释放锁标记
164 | releaseLock();
165 | // 通知出错
166 | return myEmitter.emit('token', err);
167 | }
168 |
169 | // 更新access_token,将新的access_token保存到redis,并且提前5分钟过期
170 | redis.setex(appID + appSecret, (newToken.expires_in - 300), newToken.access_token);
171 | // 发布更新
172 | publish.publish('new_token', newToken.access_token);
173 | // 释放锁标记
174 | releaseLock();
175 | });
176 | }
177 | });
178 |
179 | // 订阅
180 | subscribe.subscribe('new_token');
181 | });
182 | }
183 | ```
184 |
185 | # 进一步思考
186 | 到此,一个简单多进程控制access_token并发的解决方法已经呈现在眼前,但是我们还需要考虑一下边界情况:
187 | + 1.当我们获得到锁权限的进程在未知因素下崩溃了,并且此时用于标记锁状态的'lock'已被成功设置
188 | + 2.微信服务器崩溃了(无法正常提供服务)
189 | 以上2个情况,都会导致锁状态永远无法释放,针对这一类问题,我们可以引入超时的概念,在设置'lock'的时候给'lock'键设置一个过期时间,如果过了指定的时间还没有释放锁,那么锁权限就会被收回。代码如下:
190 |
191 | ```js
192 | function aquireLock(callback) {
193 | redis.setnx('lock', function(err, result){
194 | if(err){
195 | return callback(err);
196 | }
197 |
198 | redis.expire('lock', 2, function(err, success){
199 | if(err){
200 | return callback(err);
201 | }
202 |
203 | callback(null, true);
204 | });
205 | });
206 | }
207 | ```
208 |
209 | # 更进一步的思考
210 | 虽然我们解决了锁问题,但是此时所有未获得锁的请求还处于pending状态,等待着access_token的到来,但是由于获得锁的请求已经走在天堂的路上,已经无法再来给其他这些个请求触发事件了。所以为了解决此类问题,我们需要引入另一个超时,那就是函数调用超时,在一定时间内未完成的话,我们就回调超时错误给调用者:
211 | ```js
212 | function getAccessToken(appID, appSecret, callback) {
213 | // 将callback缓存到事件队列中,等待触发
214 | myEmitter.once('token', callback);
215 |
216 | // 设置函数调用超时
217 | setTimeout(function () {
218 | callback(null, new Error('time out'));
219 | }, 2000);
220 |
221 | // ...
222 | }
223 | ```
224 |
225 | # 总结
226 | 其实在使用redis的订阅功能之前,我还考虑过[tj](https://github.com/tj)的[axon](https://github.com/tj/axon)作为进程通信的手段,但是由于axon初始化过程有一定的延迟,不符合我的预期,所以放弃了。但是不得不说axon是一个非常好的项目,有条件的话可以用在项目当中。好了,以上就是我对高并发下处理access_token的一些自己的看法。
227 |
--------------------------------------------------------------------------------
/node/util.md:
--------------------------------------------------------------------------------
1 | ##### 引子
2 | 话说大家在做框架的时候,除了引用大量的引入第三方类库之外,不免自己要实现一些。例如:判断对象是否是某个类型等。其实node也提供了一部分实用的函数(需要引入“util”模块),下面我们就来一一介绍。
3 |
4 | ##### util.format(format, [...])
5 | 此方法与C语言中的prinf类似,第一个参数是一个字符串,后面是可变参数(可以有任意多个参数)
6 |
7 | + %s String 可变参数对应位置是字符串。
8 |
9 | ```js
10 | var util = require('util');
11 |
12 | console.log(util.format('%s%s', '你好!')); // 你好!%s
13 | console.log(util.format('Hello %s', 'World!')); // Hello World!
14 | ```
15 |
16 | + %d Number 可变参数对应位置是数字(整型或浮点型)
17 |
18 | ```js
19 | var util = require('util');
20 |
21 | console.log(util.format('你的幸运数字是%d', 6)); // 你的幸运数字是6
22 | ```
23 |
24 | + %j JSON 可变参数为JSON对象
25 |
26 | ```js
27 | var util = require('util');
28 |
29 | console.log(util.format('这个接口需要的参数为:%j,正确的返回结果为:%j', {type: 'pet'}, {status: 200})); // 这个接口需要的参数为:{"type":"pet"},正确的返回结果为:{"status":200}
30 | ```
31 |
32 | 需要注意的是,如果第一个参数不是拥有占位符的字符串,那么此方法会把所有参数拼接成一个字符串,并且每个参数之间有空格分开
33 |
34 | ```js
35 | var util = require('util');
36 |
37 | console.log(util.format(1,2,3); // 1 2 3
38 | ```
39 |
40 | ##### util.debug(string)
41 | 在打印的字符串之前添加“DEBUG:”字样,就如我在[Node.js之console](console.md)一文中提到的一样,util.debug会将输出的信息记录到错误日志文件中去,如果没有指定错误文件,那么就会直接输出到控制台
42 |
43 | ```js
44 | var util = require('util');
45 |
46 | util.debug('这是一条调试信息'); // DEBUG: 这是一条调试信息
47 | ```
48 |
49 | ##### util.error([...])
50 | 与util.debug一样,日志会记录到错误日志文件中去,但是不带“DEBUG:”,并且可以记录多个参数
51 |
52 | ```js
53 | var util = require('util');
54 |
55 | util.error('error1', 'error2', 'error2'); // error1
56 | // error2
57 | // error2
58 | ```
59 |
60 | ##### util.puts([...])、util.print([...])
61 | util.puts与util.print功能一致,会将日志信息记录到正常的日志文件中,两者唯一的区别在于:util.puts会在新行输出每一个参数,而util.print将所有参数输出到一行。
62 |
63 | ```js
64 | var util = require('util');
65 |
66 | uti.puts('arg1', 'arg2', 'arg3'); // error1
67 | // error2
68 | // error2
69 | console.log('======优雅的分割线========='); // ======优雅的分割线=========
70 | uti.print('arg1', 'arg2', 'arg3'); // arg1arg2arg3
71 | ```
72 |
73 | ##### util.log(string)
74 | 从node源码可以看出util.log在内部内部其实就是调用了console.log方法,只不过在日志前加入了时间点。
75 |
76 | ```js
77 | function timestamp() {
78 | var d = new Date();
79 | var time = [pad(d.getHours()),
80 | pad(d.getMinutes()),
81 | pad(d.getSeconds())].join(':');
82 | return [d.getDate(), months[d.getMonth()], time].join(' ');
83 | }
84 |
85 | // log is just a thin wrapper to console.log that prepends a timestamp
86 | exports.log = function() {
87 | console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
88 | };
89 | ```
90 |
91 | 以上是util模块的node源码:timestamp是产生时间字符串的函数,当我们调用util.log时,timestamp()产生的字符串会被填充到原日志的前面(更多的模块源码,我以后会有专门的博客介绍)。下面我们来看下util.log(string)用法与效果:
92 |
93 | ```js
94 | var util = require('util');
95 |
96 | util.log('我是一条日志!'); // 29 Feb 15:50:34 - 我是一条日志!
97 | ```
98 |
99 | ##### util.inspect(object, [options])
100 | 这个函数我们在[Node.js之console](console.md)一文中也提到过,功能我就不多做介绍。第一个参数应该没什么异议,第二个参数是可选配置项,通过传入不同的option值,会有不同的效果,下面就来介绍下这个option:
101 |
102 | + showHidden表示是否要返回不可枚举选项,我们在[JavaScript之object](../object.md)提到过,object对象的属性可以设置为“不可枚举”,这样我们的for...in结构就不能访问了。这里也是一样,showHidden默认是false,也就是说使用util.inspect时,默认不返回不可枚举属性的:
103 |
104 | ```js
105 | var util = require('util');
106 |
107 | var obj = Object.create({}, {
108 | 'enumerable_prop':{
109 | value: 'a',
110 | enumerable: true
111 | },
112 | 'non_enumerable_prop':{
113 | value: 'b',
114 | enumerable: false
115 | }
116 | });
117 |
118 | var strObjShow = util.inspect(obj, {showHidden: true});
119 | var strObjHide = util.inspect(obj, {showHidden: false});
120 |
121 | console.log('strObjShow is %s', strObjShow); // strObjShow is { enumerable_prop: 'a', [non_enumerable_prop]: 'b' }
122 | console.log('strObjHide is %s', strObjHide); // strObjHide is { enumerable_prop: 'a' }
123 | ```
124 |
125 | + depth表示深度,我们在[Node.js之console](console.md)介绍过,这里就不再赘述。
126 |
127 | + colors 可以在控制台输出有颜色的对象(并且颜色是可以自定义的)
128 |
129 | ```js
130 | var util = require('util');
131 |
132 | console.log(util.inspect({a:1, b:'123'}, {colors: true})); // { a: 1, b: '123' }
133 | ```
134 |
135 | 以上代码的实际效果如下:
136 |
137 | 
138 |
139 | + customInspect如果我们在对象像定义inspect方法,那么util.inspect就会直接调用该对象的inspect方法,如果将customInspect设置为false,则将依旧调用node定义的方法:
140 |
141 | ```js
142 | var util = require('util');
143 |
144 | var obj = {
145 | name: '张三',
146 | inspect: function(){
147 | return 'inspect is called!';
148 | }
149 | }
150 |
151 | console.log(util.inspect(obj)); // inspect is called!
152 | console.log(util.inspect(obj, {customInspect: false})); // { name: '张三', inspect: [Function] }
153 | ```
154 |
155 | ##### util.isArray(object)、util.isRegExp(object)、util.isDate(object)、util.isError(object)
156 | 这几个方法从英文语义上就可以知道功能了,我就不多述了。(需要提到的是在以前,判断一个对象是否是数组,很长时间没有人给出优雅的解决方法。直到有人利用调用原型上的方法才给出了优雅的解决方案,这就告诉我们,群众的力量是无穷的)。
157 |
158 | ##### util.pump(readableStream, writableStream, [callback])
159 | 这个方法在v0.10.42时代就被废弃了,就不介绍了。
160 |
161 | ##### util.inherits(constructor, superConstructor)
162 | 虽然我们可以手动做JavaScript的“继承”,但是node还是给出了实用的工具函数。但是我在这里不做介绍,我会把此方法放到events模块一起介绍。
163 |
164 | ##### 总结
165 | 总的来说,介绍API是比较枯燥的(我也觉得)。大家可以作为知识点学习学习,熟悉熟悉,为以后工作中遇到的问题打打基础。另外之前我在[v站](http://v2ex.com)上发的帖子很多人回复,非常感谢大家给我的建议,我会坚持写技术博客的。
166 |
--------------------------------------------------------------------------------
/object.md:
--------------------------------------------------------------------------------
1 | ##### 便捷的object对象
2 | 在通常的开发过程中,我们很多时候都会用到object对象。例如:传递object参数、可选择的配置项、数据库输出对象,都可以使用object。这样做使得我们的程序简洁、易用、可扩展。需要使用时,就像如下代码:
3 |
4 | ```js
5 | function getUer(name, age, country){
6 | var user;
7 |
8 | user = {
9 | name: name,
10 | age: age,
11 | country: country
12 | }
13 |
14 | return user;
15 | }
16 |
17 | console.log(getUer('peter', 28, 'America')); // {
18 | // name: 'peter',
19 | // age: 28,
20 | // country: 'America'
21 | // }
22 | ```
23 | user = {...}是大家常用的一种定义对象的方式,这样非常的方便、直接。这样定义可以满足99%的业务需求,但是有的时候我们需要满足一些特殊的需求。
24 |
25 | ##### Object.create()
26 | 除了直接创建object对象外,我们还可以使用Object.create方法来创建对象,这个方法给我提供了更多的不同情况下的API。Object.create有1个必传参数和一个可选参数。
27 |
28 | + 第一个参数是必传参数,指定即将创建的对象的prototype(原型,这个我下一篇博客会详细介绍,这里就不赘述)。
29 | + 第二个参数一个可选参数,可以设置我们需要初始化给即将创建的对象的属性及对属性的控制。
30 |
31 | ```js
32 | var newObj = Object.create({}, {
33 | 'a': {
34 | value: 'this is a',
35 | configurable: false,
36 | enumerable: false,
37 | writable: false
38 | }
39 | });
40 |
41 | console.log(newObj); // {a: 'this is a'}
42 | ```
43 |
44 | 上面的代码中我们把空对象“{}”作为了newObj的原型,并给newObj设置了一个值为“this is a”的属性“a”。我们看到,除了value属性,还有configurable、enumerable、writable三个配置属性。
45 |
46 | + configurable表示可否对当前对象进行配置。默认情况下使用Object.create创建的对象是不可配置的,也就是说configurable默认是false。
47 |
48 | ```js
49 | var newObj = Object.create({}, {
50 | 'a': {
51 | value: 'this is a',
52 | configurable: false,
53 | enumerable: false,
54 | writable: false
55 | }
56 | });
57 |
58 | Object.defineProperty(newObj, 'a', {
59 | value: 'this is not a',
60 | enumerable: true
61 | }); // Uncaught TypeError: Cannot redefine property
62 | ```
63 | 当我们试图对newObj的属性进行修改时,JavaScript引擎就会抛出错误“Cannot redefine property”,因为默认属性“a”是不可配置的。如果把configurable设置成true的话,错误就不会出现。
64 |
65 | + enumerable表示该属性不可枚举。如果需要枚举一个object对象,可以使用for...in方法,一旦我们把当前属性设置成false,for...in将访问不到此属性。
66 |
67 | ```js
68 | var user = Object.create({}, {
69 | 'name': {
70 | value: 'Lucy',
71 | enumerable: true
72 | },
73 | 'age': {
74 | value: 24,
75 | enumerable: false
76 | },
77 | 'country': {
78 | value: 'Australia',
79 | enumerable: true
80 | }
81 | });
82 |
83 | for(p in user){
84 | console.log(user[p]); // Lucy
85 | // Australia
86 | }
87 | ```
88 |
89 | 这里age属性的enumerable设置成了false,然后我们对user进行属性枚举的时候,发现age并没有被输出。
90 |
91 | + writable表示是否可以被赋值运算符改变。
92 |
93 | ```js
94 | (function(){
95 | "use strict";
96 |
97 | var song = Object.create({}, {
98 | 'name': {
99 | value: 'Welcome to Beijing',
100 | configurable: true,
101 | writable: false
102 | }
103 | });
104 |
105 | Object.defineProperty(song, 'name', {
106 | value: 'WELCOME TO BEIJING'
107 | });
108 |
109 | console.log(song);
110 |
111 | song.name = '北京欢迎您'; // Uncaught TypeError: Cannot assign to read only property 'name' of object '#