Node.js快速高效的优点得益于:事件轮询(event loop)技术;以及其构建与V8之上(V8是Google为Chrome Web浏览器设计的JavaScript解释器和虚拟机,它运行JS非常快)。
Part 1 基础
JavaScript概览
这章总结了可能会用到的js基础知识,还蛮精要的。
- 基本类型:访问值,
number, boolean, string, null, undefined
复杂类型:访问引用,array, function, object
- 基本类型变量可以通过直接赋值创建或调用构造器创建,通过
typeof
和instanceof
判断基本类型时,会有很大差异,所以最好始终通过直观方式定义,避免使用new
构造器。 - 注意:在条件表达式中,这些特定值会被判定为
false
:null, undefined, ' ', 0
- 注意:
typeof
不会把null
识别类型为null
,也不会把数组识别类型为数组,即使是通过直观方式定义的:1234typeof null == 'object'; //truetypeof [] == 'object'; //true// 判断数组的hack方式 ==>Object.prototype.toString.call([]) == '[object Array]';
V8提供了判定是否为数组类型的方式,让我们免于使用hack(to be continued)
- 函数的
length
属性:函数声明时可接收的参数数量 - 用函数定义类,定义在类的
prototype
上的函数中的this
,并不像普通函数那样指向global
对象而是指向通过该类创建的实例对象。 - 一种比较好的继承实现:1234567891011121314//父类 Animalfunction Animal() {}Animal.prototype.eat = function(food) {}//子类 Ferretfunction Ferret() {}//实现继承Ferret.prototype = new Animal(); //这里后面有改进//为所有子类实例定义属性或方法Ferret.prototype.type = 'domestic';//通过prototype重写和调用父类函数Ferret.prototype.eat = function (food) {Animal.prototype.eat.call(this, food);//ferret特有的逻辑写在这里}
最大的不足:声明继承时总要初始化一个对象。两种改进方案:
(1)构造器中加判断条件
(2)定义一个新的空构造器,重写其原型
(3)V8提供了更简洁的方案(__proto__
)
- 如果不使用
try{} catch()
捕获异常,则抛出异常时代码会停止执行
V8中的JavaScript
- 迭代对象的键
for...in
会将通过prototype
扩展的属性也迭代到for...in
中加判断:hasOwnProperty
,可避免迭代prototype
扩展的属性,只获取对象的所有自有键Object.keys()
:V8用法,只获取对象的所有自有键 - 判断是否为数组:
Array.isArray(arr);
- 常用数组方法:
forEach, filter, map
- 字符串
trim()
,JSONJSON.stringify, JSON.parse
- 对函数使用
.bind()
改变this
的指向 函数声明时最好写明函数名,有利于抛出错误时V8内部的堆栈追踪:
1var a = function woot(){} //函数名为woot用
__proto__
继承,免去中间构造器123function Animal () {}function Ferret () {}Ferret.prototype.__proto__ = Animal.prototype;属性存取器
1234567891011121314151617//自定义新属性Date.prototype.__defineGetter__('ago', function(){var diff = ((new Date()).getTime() - this.getTime()) / 1000, //secondsday_diff = Math.floor(diff / 86400); //daysreturn day_diff == 0 && (diff < 60 && "just now" ||diff < 120 && "1 minute ago" ||diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||diff < 7200 && "1 hour ago" ||diff < 86400 && Math.floor( diff / 3600 ) + " hours ago" ) ||day_diff == 1 && "Yesterday" ||day_diff < 7 && day_diff + " days ago" ||Math.ceil( day_diff / 7 ) + " weeks ago"});//新属性的使用var a = new Date('12/12/1990');console.log(a.ago); //1379 weeks ago
阻塞与非阻塞IO
非阻塞,事件轮询
- Node与PHP基础架构上的区别:Node采用一个长期运行的进程,Apache会产出多个PHP线程(每个请求一个线程),每个PHP请求线程的解释器都会运行,刷新状态。 –> 返回请求的函数中。修改状态,则PHP和Node分别运行的效果不同。
- 阻塞与非阻塞:PHP的
sleep()
是同步的,会阻塞线程的执行;Node.js使用事件轮询机制,setTimeout
是异步、非阻塞的。Node仅仅是注册一个回调函数,随后不停地询问内核这些事件是否已经分发。当事件分发时,对应的回调函数被触发,如果没有事件触发,则继续执行其他代码,直到有新事件时再去执行相应回调函数。 - 除了
setTimeout
,http, net
中的IO部分也采用了事件轮询,触发文件描述符相关的消息通知。 - JS中的timeout并不能严格遵守时钟设置,因为回调函数可能需要执行很长时间,阻塞了事件轮询。
单线程 却 高并发
- 关于单线程:当V8首次调用一个函数时,创建一个调用堆栈。由于Node运行于单线程环境中,当调用堆栈展开时,Node无法处理其他客户端或HTTP请求。
- 关于V8 + 非阻塞IO的搭配: V8执行JS非常快,同一时刻不需要处理多个请求;非阻塞IO确保了单线程执行时不会有数据库访问或者硬盘访问等操作而导致被挂起。运用非阻塞IO的例子:亚马逊云(AWS)的硬件租用,搜索文件非阻塞;从数据库中读取数据非阻塞-读取数据并不阻塞Node处理其他事情,当读取数据库完毕后会有消息通知。
错误处理
Node应用依托在一个拥有大量共享状态的进程中,如果某个回调函数发生错误未被捕获,整个进程都会遭殃。需要进行错误处理: 可添加
uncaughtException
处理器1234process.on('uncaughtException', function(err){console.log(err);process.exit(1); //手动退出});许多像
http, net
这样的原生模块都会分发error事件,如果未处理,就会抛出未捕获的异常。1234567var net = require('net');net.createServer(function(connection){connection.on('error', function(err){//对错误对象err进行处理});}).listen(400);大部分Node异步API接收的回调函数,第一个参数都是错误对象或
null
123456var fs = require('fs');fs.readFile('etc/passwd', functon(err, data){if(err) return console.error(err); //处理错误对象console.log(data);});
堆栈追踪
- 当错误发生时,在错误信息中可以看到一系列的函数调用。
- 如果引入事件轮询,在
setTimout
函数中抛出错误,则堆栈信息会丢失很多有意义的调用信息,而是从事件轮询开始显示。 - 无法捕获未来才会执行到的函数所抛出的错误,这会直接抛出未捕获的异常,且
catch
代码块永远不会执行:12345try {setTimeout(function(){throw new Error('here');}, 10);} catch (e) {}
所以Node.js中每步都要正确进行错误处理。
Node中的JavaScript
1 global对象
global
对象:全局对象,其属性可以全局访问到process
对象:其中包含所有全局执行上下文中的内容。window.name
对应浏览器中窗口的名字,process.name
对应Node中进程的名字。- 对
process.nextTick
的理解:通过异步的方法在最近的将来调用该函数,或视作setTimeout(fn, 1)
12345console.log(1);process.nextTick(function(){console.log(3);});console.log(2);
上述执行结果为1,2,3
2 模块系统
- JS原生引入第三方模块时,第三方模块暴露一个或多个全局变量,然后引入JS全局空间。- - - Node模块系统有三个核心的全局对象:
require
,module
,exports
. - 绝对模块:通过npm安装的,可以在其内部
node_modules
目录中查找到的模块或Node内置的模块。绝对模块可以直接通过模块名字来require
。 - 相对模块:定义在相对工作目录下的JS文件,需要通过相对路径
require
。 - 暴露API:如果模块暴露了API,则可以通过调用
require
将其赋值给一个变量。
两种方式:直接将要暴露的变量和方法写作exports
对象的属性和方法;直接重写module.exports
对象
3 事件
EventEmitter
API:提供了on, emit, removeListener
方法
可以将这个API添加到自己的类中:MyClass.prototype.__proto__ = EventEmitter.prototype;
- 事件是Node非阻塞设计的重要体现。Node通常不会直接返回数据,否则可能在等待某个资源时发生线程阻塞,而是采用分发事件来传递数据。例如,HTTP服务器,当请求到达时,数据可能没有全部到达,Node调用回调函数,监听
data
和end
事件。 a.once()
:某个事件可能触发多次而回调函数只执行一次
4 buffer
buffer是一个表示固定内存分配的全局对象。放到buffer中的字节数需提前确定。
处理二进制数据。eg. 可以进行数据编码转换:将base64编码表示的图片(仅用ASCII字符书写二进制数据)转为二进制PNG图片存起来:
Part 2 Node重要的API
用fs模块实现Node应用:file-explorer
fs模块是唯一一个同时提供同步和异步API的模块
stream
process
全局对象中包含了三个流对象,分别对应三个UNIX标准流:stdin
:可读流,程序从键盘读入输入stdout
:可写流,程序输出数据来displaystderr
:可写流
流:持续不断地对数据进行读写。stream
对象继承自EventEmitter
对象。还有其他类型流:TCP套接字,HTTP请求等。
argv
process.argv
:Node程序运行时的参数数组,第一个元素是node路径,第二个元素是执行的文件路径,紧接着才是命令行后紧跟的参数: