Eloquent JavaScript 笔记 十九:Node.js
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
运行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相关推荐
- JavaScript学习(二十九)—JS常用的事件
JavaScript学习(二十九)-JS常用的事件 一.页面相关事件 onload事件:当页面中所有的标签都加载完成后厨房该事件,格式:window.onload <body><sc ...
- JavaScript 编程精解 中文第三版 二十、Node.js
二十.Node.js 原文:Node.js 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 部分参考了<JavaScript 编程精解(第 2 版)> A stude ...
- Bootstrap入门(二十九)JS插件6:弹出框
Bootstrap入门(二十九)JS插件6:弹出框 加入小覆盖的内容,像在iPad上,用于存放非主要信息 弹出框是依赖于工具提示插件的,那它也和工具提示是一样的,是需要初始化才能够使用的 首先我们引入 ...
- 初识JavaScript (十九)
初识JavaScript (十九) 1 正则的分组 2 正则的exec方法 3 正则的贪婪和懒惰模式 4 正则练习 5 算法-数组去重indexOf 6 算法-数组去重-splice 7 算法-数组去 ...
- Polyworks脚本开发学习笔记(十九)-将数据对象与参考对象对齐的方法
Polyworks脚本开发学习笔记(十九)-将数据对象与参考对象对齐的方法 把开发手册理了一遍,发现还有几个点没有记录下来,其中一个就是使用点对的粗对齐和使用参考目标的精确对齐.为了把这个学习笔记凑够 ...
- 九阴真经 第十五层--node.js 第1天
Node.js 修炼 node.js 官网 https://nodejs.org/zh-cn/ node.js API http://nodejs.cn/api/ 笔记: Javascript语言将任 ...
- 网站开发进阶(四十九)由JS报“未结束的字符串常量”引发的思考
一.报错 在做公司项目开发过程中,后期生产环境上报JS出现"未结束的字符串常量"错,如下: 后期经过不断调试,发现是由于Js引擎在解析带有换行字符串时引起的异常.解析后的js代码类 ...
- Mr.J-- jQuery学习笔记(十九)--自定义动画实现图标特效
之前有写过自定义动画Mr.J-- jQuery学习笔记(十八)--自定义动画 这次实现一个小demo 图标特效 页面渲染 <!DOCTYPE html> <html lang=&qu ...
- 十大 Node.js 的 Web 框架,快速提升工作效率
Node.js 系统含有多种不同的结构,如 MVC.全栈.REST API 和生成器等.这些结构不仅提升了 Web 应用的开发效率,也优化了开发过程.在这里,我们收集整理了十个高效的 Node.js ...
最新文章
- x86的cpu处理int类型并不是处理char高效多少
- python中的datatype啥意思_案例中使用的是dataType,但是用在联系上面dataType不可用,必须改写成type:..._慕课问答...
- python培训深圳-深圳Python培训机构排名
- Java 设计模式 -- 建造者模式
- 【免费毕设】ASP.NET某中学图书馆系统的设计与实现(源代码+论文)
- 快速安装Tensorflow
- 我安装java了_我安装了JAVA为什么.......
- 【前端】创建元素并插入到现有文档
- powerdesigner安装之后会自动加载到word中怎么去除??
- 分享一份自己整理的PPT--数据分析师的业务流程和常规思维
- spring AOP概念及xml配置
- 连接共享打印机时提示无法访问计算机,win10共享打印机提示无法访问.你可能没有权限使用网络资源怎么解决...
- photoshop快速去掉图片背景颜色(白色背景)
- 操作系统-进程互斥的软件实现方法
- 达人评测 r7 7730U和R5 7530U选哪个好 锐龙r77730U和R57530U对比
- 计算机无法读取配置文件,由于权限不足,无法读取配置文件
- 【程序】Marvell 88W8801 WiFi模块连接路由器,并使用lwip2.0.3建立http服务器(20180729版)
- Intellij-出现Module ** must not contain source root **. The root already belongs to module **
- 使用Labelimg打标签
- 【ybt金牌导航8-7-1】数对统计 / 关于莫比乌斯函数的少量内容
热门文章
- 服务器血量显示插件,RealMobHealth 真实怪物血量数值新增全服务器怪物NPC血量缓存文件...
- 管家婆分销ERPV3A8单据第1行商品【】货位没有填写或不属于该仓库或已删除,不能保存
- 前端学习(三十)es6的一些问题(笔记)
- 用matlab对信号降噪,信号的小波降噪 matlab仿真程序
- 25个HTML5和JavaScript游戏引擎库(转)
- 陈玉福算法设计与分析期末考试题-简答部分
- python绘制条形统计图_python 绘制百度实时统计柱状图
- window server(wind10)将nginx 注册为服务,实现开机自启
- 快速学习COSMIC之一:COSMIC方法的简单案例
- 重读《C primer plus》