helloworld

    • 必会js基本语法
    • 熟悉api写法:error-first api写法和commonJS规范
    • 掌握EvenEmit事件驱动机制
    • 掌握Stream,以及HTTP模块中使用Stream的理解
    • 掌握Node.js SDK api用法
    • 掌握通过npm安装的外部模块用法

    区分Npm和CommonJS规范

    看一下npm

    • npm名称是Node Package Manager
    • npm是Node.js SDK以外的模块管理器,用于提供安装,卸载,依赖管理等
    • npm是管理基于CommonJS规范写的模块的

    而CommonJS规范

    • CommonJS是一套规范,是模块写法上约定
    • Node.js是基于CommonJS规范的实现,并做了改进

    总结

    • npm是管理基于CommonJS规范写的模块的
    • CommonJS是一套规范,是模块写法上约定

    创建 文件,并敲入如下代码:

    在终端里,通过node命令来执行

    1. $ node demo/helloworld.js
    2. hello world

    这是最简单的例子,要点如下

    • Node.js的语法使用前端JavaScript一样的语法
    • JavaScript是脚本语言,需要Node.js解释器 node 命令来解释并执行
    • console.log是一个用于输出日志的方法,区别在于日志会输出在浏览器端(前端)或terminal终端(Node.js)里

    所以,JS语法的掌握是学习Node.js的基础,非常重要,是必需要要掌握的。

    声明严格模式

    举个简单的例子,我们在helloworld.js上增加一个变量a的声明,采用es6里的let关键字

    1. console.log('hello world');
    2. let a = 1

    先通过nvm切换到4版本

    1. $ nvm use 4
    2. Now using node v4.7.0 (npm v2.15.11)

    执行

    1. $ node demo/helloworld
    2. demo/helloworld.js:3
    3. let a = 1
    4. ^^^
    5. SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
    6. at exports.runInThisContext (vm.js:53:16)
    7. at Module._compile (module.js:373:25)
    8. at Object.Module._extensions..js (module.js:416:10)
    9. at Module.load (module.js:343:32)
    10. at Function.Module._load (module.js:300:12)
    11. at Function.Module.runMain (module.js:441:10)
    12. at startup (node.js:139:18)
    13. at node.js:990:3

    此时,报错说是语法错误(SyntaxError),对于let, const, function, class等关键字的使用在严格模式外还不支持。所以它提示我们使用严格模式。即如下

    1. 'use strict'
    2. console.log('Hello World');
    3. let a = 1

    此时再执行就不会报错了。使用'use strict' 声明为严格模式,标记严格模式后的好处如下

    • 其一,如果在语法检测时发现语法问题,则整个代码块失效,并导致一个语法异常。
    • 其二,如果在运行期出现了违反严格模式的代码,则抛出执行异常。

    这是我们在使用es6代码,经常要做的事儿,一些低版本的Node.js SDK里很多特性默认是不支持的,需要使用打开严格模式才能使用。

    • Error-first callback
    • Event-driven

    Node.js需要依赖异步代码才能保证它的快速的执行速度。所以,严重的依赖callback模式是必然的。如果没有它,开发者将陷入到在每个模块之间维护不同签名和风格。将error-first模式引入到Node核心里来解决这个问题的,从此蔓延成为今天所谓的标准。对此褒贬不一,由于过度的callback调用而产生的cellbackhell也是Node.js这么多年里被诟病最多的点。从另一个角度看,也正是Node.js坦诚的暴露缺点,才有了后来前仆后继的异步流程控制的改进。

    典型的Error-first callback写法,如下

    1. var callback = function(err, data) {
    2. if (err) {
    3. /* do something if there was an error */
    4. }
    5. /* other logic here */
    6. };

    上面的代码包含了2个定义error-first callback写法的规则:

    • callback函数的第一个参数是error对象。如果发生error,它就被作为第一个参数穿进来,如果没有错误,则err值为null。
    • 第二个参数是任何成功响应的data,如果没有error产生,err会被设置为null,并且成功的返回的数据的会放到第二个参数里。

    约定是这样的,当往往可能需要传n(n>=2)个参数,其他参数依次排下去。

    比如express和connect的中间件

    err后面有3个参数,分别是req、res和next,在语义上有一定帮助的情况下,也是允许的。

    下面,通过Node.js读取文件内容的api所编写的例子,展示一下sdk里error-first callback用法

    1. 'use strict'
    2. const fs = require('fs')
    3. if (err) throw err
    4. console.log(data.toString())
    5. });

    执行

    1. $ node demo/readfile.js
    2. console.log('hello world');

    说明

    • 1) 关于返回的data类型

    推荐

    1. fs.readFile('/etc/passwd', 'utf8', callback);
    • 2)针对err的处理

    代码里是直接throw抛出异常:

    1. if (err) throw err

    这里的error其实和其他语言处理类似的。处理方式大致如下

    • 自己不管,抛出异常,交给外出调用的处理,或者干脆直接报错
    • 自己处理,输出日志或者其他
    • 可以结合try/catch或Promise来处理
    1. var childProcess = require('child_process');
    2. function runScript(scriptPath, callback) {
    3. // keep track of whether callback has been invoked to prevent multiple invocations
    4. var invoked = false;
    5. var process = childProcess.fork(scriptPath);
    6. // listen for errors as they may prevent the exit event from firing
    7. process.on('error', function (err) {
    8. invoked = true;
    9. callback(err);
    10. });
    11. // execute the callback once the process has finished running
    12. process.on('exit', function (code) {
    13. if (invoked) return;
    14. invoked = true;
    15. var err = code === 0 ? null : new Error('exit code ' + code);
    16. callback(err);
    17. });
    18. }
    19. // Now we can run a script and invoke a callback when complete, e.g.
    20. runScript('./some-script.js', function (err) {
    21. if (err) throw err;
    22. console.log('finished running some-script.js');
    23. });

    Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。很多Node.js核心API都是围绕事件驱动架构里的包括某种被称为 “emitters”的对象定时的发射命名事件来调用函数对象(“listeners”)。

    Much of the Node.js core API is built around an idiomatic asynchronous event-driven architecture in which certain kinds of objects (called “emitters”) periodically emit named events that cause Function objects (“listeners”) to be called.

    For instance: a net.Server object emits an event each time a peer connects to it; a fs.ReadStream emits an event when the file is opened; a stream emits an event whenever data is available to be read.

    EventEmitter类, 是node中事件的基础, 实现了事件模型需要的接口, 包括addListener,removeListener, emit及其它工具方法. 同前端jQuery事件等类似, 采用了发布/订阅(观察者)的方式, 使用内部_events列表来记录注册的事件处理器。在Node.js的sdk里

    1. BigEvent.prototype.on = function(eventName, func) {
    2. this._listeners = this._listeners || {};
    3. this._listeners[eventName] = this._listeners[eventName] || [];
    4. this._listeners[eventName].push(func);
    5. };
    6. BigEvent.prototype.off = function(eventName, func) {
    7. this._listeners = this._listeners || {};
    8. this._listeners[eventName].splice(this._listeners[eventName].indexOf(func), 1);
    9. };
    10. BigEvent.prototype.trigger = function(eventName) {
    11. this._listeners = this._listeners || {};
    12. var dataArgument = arguments[1] ? arguments[1] : null;
    13. var events = this._listeners[eventName] || [];
    14. for(var i = 0; i < events.length; i++) {
    15. var ev = events[i]
    16. if(dataArgument) {
    17. ev.call(this, dataArgument);
    18. } else {
    19. ev.call(this);
    20. };
    21. };

    这里的BigEvent只是仿照jQuery的事件api写的简单实现,如果把trigger改成emit,用法就和Node.js里的Event几乎一模一样了。从英文单词角度看,trigger是触发,emit是发射,感觉emit更加霸气一些。

    在Node.js很早就有EventEmiter模块,在Node.js 6之前,只能通过require("events").EventEmitter来获取Event对象,所以非常冗余。所以在Node.js 6里就deprecated了它,可以通过require("events")直接引用了。尤其是结合es6的类和继承机制,可以让代码更加优雅。在Node.js 7之后,彻底移除了EventEmitter的api。大家只要知道events就可以了。

    Node 4以前的old做法(es5)

    过渡做法(在Node.js 4和5)

    1. var EventEmitter = require("events").EventEmitter;
    2. class MyEmitter extends EventEmitter {
    3. constructor() {
    4. super(); //must call super for "this" to be defined.
    5. }
    6. }
    7. myEmitter.on('event', () => {
    8. console.log('an event occurred!');
    9. });
    10. myEmitter.emit('event');

    新做法(es6 + Node 6+)

    在Node.js 6里 process.EventEmitter 被 deprecated 了,在Node.js 7里移除了process.EventEmitter

    1. const EventEmitter = require('events');
    2. class MyEmitter extends EventEmitter {
    3. constructor() {
    4. super(); //must call super for "this" to be defined.
    5. }
    6. }
    7. const myEmitter = new MyEmitter();
    8. myEmitter.on('event', () => {
    9. console.log('an event occurred!');
    10. });
    11. myEmitter.emit('event');

    给出已有的expirable-hash-table模块,它是一个简单的哈希表,带有TTL(time to live)概念,即当超时后会自动清理该key,这和redis、mongodb里的ttl类似,只是它是一个更小的纯Node.js实现的模块而已。

    1. var ExpirableHashTable = require('expirable-hash-table')
    2. var myTable = new ExpirableHashTable(1000) // default timeout in miliseconds
    3. myTable.set('key', 'value', 3000) // optional timeout in miliseconds
    4. myTable.get('key') // -> value
    5. myTable.remove('key') // -> ExpirableHashTable
    6. myTable.has('key') // -> true/false
    7. myTable.purge() // -> ExpirableHashTable
    8. myTable.toArray() // -> Array
    9. myTable.size() // -> Integer
    10. myTable.on('change', function() {
    11. // A change event is emitted ever time an item is added, updated or removed
    12. })
    13. myTable.once('<key>:expired', function() {
    14. // A expired event is emitted when a given item expires. Useful if a specific item wants to be monitored.
    15. })

    它的风格是事件驱动的方式,能否使用Error-first callback写法?如何写?如果可以,请站在作者的角度上,思考作者这样做的初衷。

    Write a http server use Node.js

    1. var http = require('http');
    2. http.createServer(function(request,response){
    3. console.log(request);
    4. response.end('Hello world!');
    5. }).listen(8888);

    之前讲过的http的例子

    1. var http = require("http");
    2. http.createServer(function(request, response) {
    3. response.writeHead(200, {"Content-Type": "text/plain"});
    4. response.write("Hello World");
    5. response.end();
    6. }).listen(8888);
    1. server.on('error', function (e) {
    2. // Handle your error here
    3. console.log(e);

    疑问:如果在里面用emit呢?