1. Background

可以略过。

2. Asynchronicity

讲同步和异步的基本原理,可以略过。

3. The Node Command

首先,访问 nodejs.org 网站,安装node.js。

3.1. 执行js文件:

创建一个文件 hello.js,文件内容:

var message = "Hello world";
console.log(message);

在命令行下,运行:

$ node hello.js
输出:

Hello world

在node.js 环境下,console.log() 输出到 stdout。

3.2. 运行 node的CLI:

$ node
> 1 + 1
2
> [-1, -2, -3].map(Math.abs)
[1, 2, 3]
> process.exit(0)
$

像console一样,process也是一个全局对象。用来控制当前CLI的当前进程。

3.3. 访问命令行参数:

创建文件 showarv.js,文件内容:

console.log(process.argv);

没看错,就一行。 process.argv 是个数组,包含了命令行传入的参数。

$ node showargv.js one --and two
["node", "/home/marijn/showargv.js", "one", "--and", "two"]

3.4. 全局变量

标准的JavaScript全局变量在node.js中都可以访问,例如:Array, Math, JSON 等。

Browser相关的全局变量就不能访问了,例如:document,alert等。

在Browser环境下的全局对象 window,在node.js 中变成了 global。

4. Modules

node.js 环境下 built-in 的功能比较少,很多功能需要额外安装module。

node.js 内置了 CommonJS module 系统。我们可以直接使用 require 包含modules。

4.1. require 参数:

1. require("/home/marijn/elife/run.js");   绝对路径

2. require("./run.js");   当前目录

3. require("../world/world.js");  基于当前目录的相对路径

4. require("fs")   内置的module

5. require("elife")   安装在 node_modules/elife/ 目录下的module。 使用npm会把module安装在 node_modules 目录下。

4.2. 使用require引用当前目录下的module

创建module文件,garble.js:

module.exports = function(string) {return string.split("").map(function(ch) {return String.fromCharCode(ch.charCodeAt(0) + 5);}).join("");
};

创建main.js, 引用garble.js:

var garble = require("./garble");// Index 2 holds the first actual command-line argument
var argument = process.argv[2];console.log(garble(argument));

运行:

$ node main.js JavaScript
Of{fXhwnuy

5. Installing with NPM

NPM - Node Package Manager

当安装node.js时,同时也安装了npm。

$ npm install figlet

$ node

> var figlet = require("figlet");
> figlet.text("Hello world!", function(error, data) {
if (error)
console.error(error);
else
console.log(data);
});
_   _      _ _                            _     _ _
| | | | ___| | | ___   __      _____  _ __| | __| | |
| |_| |/ _ \ | |/ _ \  \ \ /\ / / _ \| '__| |/ _` | |
|  _  |  __/ | | (_) |  \ V  V / (_) | |  | | (_| |_|
|_| |_|\___|_|_|\___/    \_/\_/ \___/|_|  |_|\__,_(_)

运行npm install,会在当前目录创建 node_modules 文件夹,下载的modules就保存在这个文件夹中。

注意上面的 figlet.text() 函数,它是一个异步函数,它需要访问 figlet.text 文件,搜索每个字母对应的图形。

I/O 操作通常是比较费时的,所以,都要做成异步函数。它的第二个参数是个function,当I/O执行完之后被调用。

这是node.js 的通用模式,异步 I/O 函数通常都是这个写法。

我们也可以写一个 package.json 文件,在其中配置多个module,以及相互之间的依赖规则。当运行 npm install 时,它会自动搜寻此文件。

npm 的详细使用方法在 npmjs.org 。

6. The File System Module

6.1. 使用node.js 内置的 fs 模块读取文件:

var fs = require("fs");
fs.readFile("file.txt", "utf8", function(error, text) {if (error)throw error;console.log("The file contained:", text);
});

readFile() 的第二个参数是文件编码,但三个参数是function,在I/O完成后被调用。

6.2. 读取二进制文件:

var fs = require("fs");
fs.readFile("file.txt", function(error, buffer) {if (error)throw error;console.log("The file contained", buffer.length, "bytes.","The first byte is:", buffer[0]);
});

不写文件编码,就是按二进制读取,buffer是个数组,按字节存储文件内容。

6.3. 写入文件:

var fs = require("fs");
fs.writeFile("graffiti.txt", "Node was here", function(err) {if (err)console.log("Failed to write file:", err);elseconsole.log("File written.");
});

不指定文件编码,默认是utf8。

fs 模块还有好多方法。

6.4. 同步I/O

var fs = require("fs");
console.log(fs.readFileSync("file.txt", "utf8"));

7. The HTTP Module

使用内置的 http 模块可以构建完整的 HTTP Server。 (哈哈,相当于 nginx + PHP)

7.1. 创建 http server:

var http = require("http");
var server = http.createServer(function(request, response) {response.writeHead(200, {"Content-Type": "text/html"});response.write("<h1>Hello!</h1><p>You asked for <code>" +request.url + "</code></p>");response.end();
});
server.listen(8000);

运行这个文件会让控制台阻塞。

每来一个request请求都会调用一次 createServer()。

7.2. 创建 http client:

var http = require("http");
var req = {hostname: "eloquentjavascript.net",path: "/20_node.html",method: "GET",headers: {Accept: "text/html"}
};
var request = http.request(req, function(response) {console.log("Server responded with status code",response.statusCode);
});
request.end();

建立HTTPS连接,使用 https 模块,基本功能和http一样。

8. Streams

8.1. writable stream

7.1 中的response 和 7.2中的 request 都有个write() 方法,可以多次调用此方法发送数据。这叫 writable stream。

6.3 中的writeFile() 方法不是stream,因为,调用一次就会把文件清空,重新写一遍。

fs 也有stream方法。使用fs.createWriteStream() 可以创建一个stream对象,在此对象上调用 write() 方法就可以像流那样写入了。

8.2. readable stream

server 端的request对象,和client端的response对象都是 readable stream。在event handler中,才能从stream中读取数据。

有 “data" , "end" 事件。

fs.createReadStream() 创建文件 readable stream。

8.3. on

类似于 addEventListener()

8.4. 例子:server

var http = require("http");
http.createServer(function(request, response) {response.writeHead(200, {"Content-Type": "text/plain"});request.on("data", function(chunk) {response.write(chunk.toString().toUpperCase());});request.on("end", function() {response.end();});
}).listen(8000);

这是一个web server,把客户端发送来的字符串变成大写,再发送回去。chunk 是二进制buffer。

8.5. 例子:client

var http = require("http");
var request = http.request({hostname: "localhost",port: 8000,method: "POST"
}, function(response) {response.on("data", function(chunk) {process.stdout.write(chunk.toString());});
});
request.end("Hello server");

如果 8.4 的server正在运行,执行这个文件会在控制台输入:HELLO SERVER

process.stdout() 也是一个 writable stream。

这里不能使用 console.log() ,因为它会在每一次调用后面加换行符。

9. A Simple File Server

9.1. File Server 说明

构建一个HTTP server,用户可以通过http request访问server上的文件系统。

GET 方法读取文件,PUT 方法写入文件,DELETE方法删除文件。

只能访问server运行的当前目录,不能访问整个文件系统。

9.2. server 骨架

var http = require("http"), fs = require("fs");var methods = Object.create(null);http.createServer(function(request, response) {function respond(code, body, type) {if (!type) type = "text/plain";response.writeHead(code, {"Content-Type": type});if (body && body.pipe)body.pipe(response);elseresponse.end(body);}if (request.method in methods)methods[request.method](urlToPath(request.url),respond, request);elserespond(405, "Method " + request.method +" not allowed.");
}).listen(8000);

说明:

1. methods 存储文件操作方法,属性名是相应的http method(GET, PUT, DELETE),属性值是对应的function。

2. 如果在methods中找不到相应的方法,则返回405.

3. pipe() 在readable stream和writable stream之间建立管道,自动把数据传送过去。

9.3. urlToPath()

function urlToPath(url) {var path = require("url").parse(url).pathname;return "." + decodeURIComponent(path);
}

使用内置的url模块,把url转换成 pathname。

9.4. Content-Type

server给client返回文件时,需要知道文件的类型。这需要用到mime模块,用npm安装:

$ npm install mime

9.5. GET

methods.GET = function(path, respond) {fs.stat(path, function(error, stats) {if (error && error.code == "ENOENT")respond(404, "File not found");else if (error)respond(500, error.toString());else if (stats.isDirectory())fs.readdir(path, function(error, files) {if (error)respond(500, error.toString());elserespond(200, files.join("\n"));});elserespond(200, fs.createReadStream(path),require("mime").lookup(path));});
};

fs.stat() 读取文件状态。fs.readdir() 读取目录下的文件列表。这段代码挺直观。

9.6. DELETE

methods.DELETE = function(path, respond) {fs.stat(path, function(error, stats) {if (error && error.code == "ENOENT")respond(204);else if (error)respond(500, error.toString());else if (stats.isDirectory())fs.rmdir(path, respondErrorOrNothing(respond));elsefs.unlink(path, respondErrorOrNothing(respond));});
};

删除一个不存在的文件,返回 204,为什么呢? 2xx 代表成功,而不是error。

当一个文件不存在,我们可以说DELETE请求已经被满足了。而且,HTTP标准鼓励我们,多次响应一个请求,最好返回相同的结果。

function respondErrorOrNothing(respond) {return function(error) {if (error)respond(500, error.toString());elserespond(204);};
}

9.7. PUT

methods.PUT = function(path, respond, request) {var outStream = fs.createWriteStream(path);outStream.on("error", function(error) {respond(500, error.toString());});outStream.on("finish", function() {respond(204);});request.pipe(outStream);
};

这里没有检查文件是否存在。如果存在直接覆盖。又一次用到了 pipe, 把request直接连接到 file stream上。

9.8. 运行

把上面实现的server运行起来,使用curl测试它的功能:

$ curl http://localhost:8000/file.txt
File not found
$ curl -X PUT -d hello http://localhost:8000/file.txt
$ curl http://localhost:8000/file.txt
hello
$ curl -X DELETE http://localhost:8000/file.txt
$ curl http://localhost:8000/file.txt
File not found

10. Error Handling

如果上面的file server运行中抛出异常,会怎样? 崩溃。 需要try ... catch 捕获异常,try写在哪里呢? 所有的行为都是异步的,我们需要写好多的try,因为,每一个callback中都需要单独捕获异常,否则,异常会直接被抛到函数调用的栈顶。

写那么多的异常处理代码,本身就违背了 “异常” 的设计初衷。它的初衷是为了集中处理错误,避免错误处理代码层层嵌套。

很多node程序不怎么处理异常,因为,从某种角度来讲,出现异常就是出现了程序无法处理的错误,这时让程序崩溃是正确的反应。

另一种办法是使用Promise,它会捕获所有异常,转到错误分支。

看一个例子:

var Promise = require("promise");
var fs = require("fs");var readFile = Promise.denodeify(fs.readFile);
readFile("file.txt", "utf8").then(function(content) {console.log("The file contained: " + content);
}, function(error) {console.log("Failed to read file: " + error);
});

Promise.denodeify() 把node函数Promise化 —— 还实现原来的功能,但返回一个Promise对象。

用这种方法重写 file server 的GET方法:

methods.GET = function(path) {return inspectPath(path).then(function(stats) {if (!stats) // Does not existreturn {code: 404, body: "File not found"};else if (stats.isDirectory())return fsp.readdir(path).then(function(files) {return {code: 200, body: files.join("\n")};});elsereturn {code: 200,type: require("mime").lookup(path),body: fs.createReadStream(path)};});
};function inspectPath(path) {return fsp.stat(path).then(null, function(error) {if (error.code == "ENOENT") return null;else throw error;});
}

11. Exercise: Content Negotiation, Again

用http.request() 实现第17章的习题一。

var http = require("http");function readStreamAsString(stream, callback) {var data = "";stream.on("data", function(chunk) {data += chunk.toString();});stream.on("end", function() {callback(null, data);});stream.on("error", function(error) {callback(error);});
}["text/plain", "text/html", "application/json"].forEach(function (type) {var req = {hostname: "eloquentjavascript.net",path: "/author",method: "GET",headers: {"Accept": type}};var request = http.request(req, function (response) {if (response.statusCode != 200) {console.error("Request for " + type + " failed: " + response.statusMessage);}else {readStreamAsString(response, function (error, data) {if (error) throw error;console.log("Type " + type + ": " + data);});}});request.end();
});

概念都明白了,轮到自己写代码时,才发现快忘光了。 一定要打开编辑器,不看答案,手敲一遍。

12. Exercise: Fixing a Leak

function urlToPath(url) {var path = require("url").parse(url).pathname;var decoded = decodeURIComponent(path);return "." + decoded.replace(/(\/|\\)\.\.(\/|\\|$)/g, "/");
}

13. Exercise: Creating Directories

methods.MKCOL = function(path, respond) {fs.stat(path, function(error, stats) {if (error && error.code == "ENOENT")fs.mkdir(path, respondErrorOrNothing(respond));else if (error)respond(500, error.toString());else if (stats.isDirectory())respond(204);elserespond(400, "File exists");});
};

14. Exercise: A Public Space on The Web

这道题相当复杂,稍后再看。

Eloquent JavaScript 笔记 十九:Node.js相关推荐

  1. JavaScript学习(二十九)—JS常用的事件

    JavaScript学习(二十九)-JS常用的事件 一.页面相关事件 onload事件:当页面中所有的标签都加载完成后厨房该事件,格式:window.onload <body><sc ...

  2. JavaScript 编程精解 中文第三版 二十、Node.js

    二十.Node.js 原文:Node.js 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 部分参考了<JavaScript 编程精解(第 2 版)> A stude ...

  3. Bootstrap入门(二十九)JS插件6:弹出框

    Bootstrap入门(二十九)JS插件6:弹出框 加入小覆盖的内容,像在iPad上,用于存放非主要信息 弹出框是依赖于工具提示插件的,那它也和工具提示是一样的,是需要初始化才能够使用的 首先我们引入 ...

  4. 初识JavaScript (十九)

    初识JavaScript (十九) 1 正则的分组 2 正则的exec方法 3 正则的贪婪和懒惰模式 4 正则练习 5 算法-数组去重indexOf 6 算法-数组去重-splice 7 算法-数组去 ...

  5. Polyworks脚本开发学习笔记(十九)-将数据对象与参考对象对齐的方法

    Polyworks脚本开发学习笔记(十九)-将数据对象与参考对象对齐的方法 把开发手册理了一遍,发现还有几个点没有记录下来,其中一个就是使用点对的粗对齐和使用参考目标的精确对齐.为了把这个学习笔记凑够 ...

  6. 九阴真经 第十五层--node.js 第1天

    Node.js 修炼 node.js 官网 https://nodejs.org/zh-cn/ node.js API http://nodejs.cn/api/ 笔记: Javascript语言将任 ...

  7. 网站开发进阶(四十九)由JS报“未结束的字符串常量”引发的思考

    一.报错 在做公司项目开发过程中,后期生产环境上报JS出现"未结束的字符串常量"错,如下: 后期经过不断调试,发现是由于Js引擎在解析带有换行字符串时引起的异常.解析后的js代码类 ...

  8. Mr.J-- jQuery学习笔记(十九)--自定义动画实现图标特效

    之前有写过自定义动画Mr.J-- jQuery学习笔记(十八)--自定义动画 这次实现一个小demo 图标特效 页面渲染 <!DOCTYPE html> <html lang=&qu ...

  9. 十大 Node.js 的 Web 框架,快速提升工作效率

    Node.js 系统含有多种不同的结构,如 MVC.全栈.REST API 和生成器等.这些结构不仅提升了 Web 应用的开发效率,也优化了开发过程.在这里,我们收集整理了十个高效的 Node.js ...

最新文章

  1. x86的cpu处理int类型并不是处理char高效多少
  2. python中的datatype啥意思_案例中使用的是dataType,但是用在联系上面dataType不可用,必须改写成type:..._慕课问答...
  3. python培训深圳-深圳Python培训机构排名
  4. Java 设计模式 -- 建造者模式
  5. 【免费毕设】ASP.NET某中学图书馆系统的设计与实现(源代码+论文)
  6. 快速安装Tensorflow
  7. 我安装java了_我安装了JAVA为什么.......
  8. 【前端】创建元素并插入到现有文档
  9. powerdesigner安装之后会自动加载到word中怎么去除??
  10. 分享一份自己整理的PPT--数据分析师的业务流程和常规思维
  11. spring AOP概念及xml配置
  12. 连接共享打印机时提示无法访问计算机,win10共享打印机提示无法访问.你可能没有权限使用网络资源怎么解决...
  13. photoshop快速去掉图片背景颜色(白色背景)
  14. 操作系统-进程互斥的软件实现方法
  15. 达人评测 r7 7730U和R5 7530U选哪个好 锐龙r77730U和R57530U对比
  16. 计算机无法读取配置文件,由于权限不足,无法读取配置文件
  17. 【程序】Marvell 88W8801 WiFi模块连接路由器,并使用lwip2.0.3建立http服务器(20180729版)
  18. Intellij-出现Module ** must not contain source root **. The root already belongs to module **
  19. 使用Labelimg打标签
  20. 【ybt金牌导航8-7-1】数对统计 / 关于莫比乌斯函数的少量内容

热门文章

  1. 服务器血量显示插件,RealMobHealth 真实怪物血量数值新增全服务器怪物NPC血量缓存文件...
  2. 管家婆分销ERPV3A8单据第1行商品【】货位没有填写或不属于该仓库或已删除,不能保存
  3. 前端学习(三十)es6的一些问题(笔记)
  4. 用matlab对信号降噪,信号的小波降噪 matlab仿真程序
  5. 25个HTML5和JavaScript游戏引擎库(转)
  6. 陈玉福算法设计与分析期末考试题-简答部分
  7. python绘制条形统计图_python 绘制百度实时统计柱状图
  8. window server(wind10)将nginx 注册为服务,实现开机自启
  9. 快速学习COSMIC之一:COSMIC方法的简单案例
  10. 重读《C primer plus》