安装Node
网上教程比较多,就不赘述了。
REPL工具
mac环境下,在终端输入node就可以运行Node的REPL。
- 可以方便地演这个一些Node API和JavaScript API 是否正确
- 有时候忘了某个API的用法,可以用来测试
创建一个简单的Node脚本
const http = require('http');
const serv = http.createServer(function(req,res){
res.writeHead(200,{'Content-Type': 'text/html'});
res.end('<div>hello Node</div>');
});
serv.listen(3000);
一个简单的HTTP服务器来托管一个简单的HTML文档,打开http://localhost:3000/就可以访问了。至于细节后面会讲到。
NPM
NPM包管理器(NPM)可以在项目中轻松地对模块进行管理
- 下载指定的包
- 解决包的依赖
- 运行测试脚本
- 安装命令行工具
NPM本身使用Node.js开发的,有二进制包的发布形式。
NPM有两个命令可以用来在仓库中搜索和查看模块:search和view
package.json
- 创建一个package.json模块,可以自定义模块。
- 你只需要将package.json文件发给别人,别人一执行
npm install
就可以将你项目中依赖的包也下一份带他自己的项目中。如果想交换依赖包的信息,每次都交换一个node_modules包简直就是馊主意。 - package.json必须遵行JSON格式,必须保证所有的字符串,都是使用双引号而不是单引号。
JavaScript基础
数据类型
基础类型:Number String Boolean null undefined
引用类型:Object Function Array
let a = 'test';
let b = new String('test');
typeof a; // "String"
typeof b;// "Object"
a instanceof string; // false;
b instanceof string; // true;
null instanceof Object; // false
[] instanceof Object; // true
typeof null; // "object"
typeof []; // "object"
还好,我们可以通过另一种方式来正确判断类型:
Object.prototype.toString.call([]); // "[object Array]"
函数
在JavaScript中,函数最为重要。
函数有一个很有意思的属性-参数数量,该属性致命函数申明时可以接收的参数数量。
let a = function(a,b,c){};
a.length; // 3
尽管这在浏览器很少使用,但是它对我们非常重要,因为一些流行的Node.js框架就是通过此属性来根据参数个数提供不同功能的。
闭包
在JavaScript中,每次调用函数,新的作用域就会出现。
自执行函数是一种机制,通过这种机制声明和调用一个匿名函数,能够达到仅定义一个新作用域的作用。
获取键
// 1、遍历包括从父类继承下来的属性
for key in obj {
}
// 2、为避免把父属性也取到,可以用a.hasOwnProperty(i)
// 3、Object.keys(a)也可以获取对象的键
数组
Array.isArray([]); // true
- 遍历数组 :forEach (arr.forEach(function(){}))
- 过滤数组:filter(arr.filter(function(){return xx;}))
- 改变数组的每个值:map(arr.map(function(){return xx;}))
- 接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值:reduce
- numbers.reduce(getSum, 0); // numbers是一个数组,0表示其实索引,getSum是计算方法。
__proto__
function a(){}
function b(){}
a.prototype.__proto__ = b.prototype;
存取器
简单理解就是取值之前经过计算,有点想vue里面的computed属性。
function a() {
this.a = 1;
this.b = 2;
}
a.prototype.__defineGetter__('a',function(){
return 3;
})
let obj = new a();
obj.a;
阻塞与非阻塞
**Node采用一个长期运行的进程。**即Node并不会删除执行环境。(这一地啊嘛对写出健壮的Nodejs程序,避免运行时错误是非常重要的!!!)
也就是说如果执行下面的代码,当有多个请求时,第一个请求能顺利拿到响应,而后面的请求就会得到一个空值。
let book = [
'hello',
'test'
];
function serveBooks () {
// 给客户端返回的HTML代码
let html = '<div>books.join('-')</div>';
// 修改了状态
books = [];
return html;
}
采用了事件轮询:Node会先注册事件,随后不停地询问内核这些事件是否已经分发。当事件分发时,对应的回调函数就会被触发,然后继续执行下去。如果没有事件触发,则继续执行其他代码,直到有新事件,再去执行对应的回调函数。
Node如何做到高并发,即用Node书写的简单的服务器就能够处理每秒上千个请求
首先要明白堆栈的概念:
即执行堆栈,如果一个函数调用又去调用另一个函数的话,v8就会把它添加到调用堆栈上。
由于Node是运行在单线程中的,所以,当调用堆栈展开时,Node就无法处理其他客户端或者HTTP请求了。这时候,你会想,这么说来Node的最大并发量不是为1嘛?!其实就是这样,Node并不提供真正的并行操作。实际上,Node能够处理高并发的原因在于,Node调用堆栈执行非常快,即使有一个请求需要访问数据库或者硬盘,也不会被挂起,Node只是告诉事件轮询访问完数据库或者磁盘之后通知一下,接着Node就继续执行下一个请求了。
Node应用依托在一个拥有大量共享状态的进程中。
所以,在一个HTTP请求中,如果某个回调函数发生了错误,整个进程都会遭殃。
var http = require('http');
http.createServer(function(){
throw new Error('错误不会被捕获');
}).listen(3000);
因为错误未被捕获,若访问web服务器,进程会崩溃。
Node之所以这么处理是因为,在发生未被捕获的错误时,进程的状态就不确定了。之后可能就无法正常工作了,并且如果错误始终不处理的话,就会一直抛出意料之外的错误,这样很难调试。
在Node中,许多像http、net这样的原声模块都会分发error事件。如果该事件未处理,就会抛出未捕获的异常。
在JavaScript中,当错误发生时,在错误信息中可以看到一系列的函数调用,这称为堆栈跟踪。
如下面的代码:
function c() {
b();
}
function b() {
a();
}
function a() {
throw new Error('here');
}
c();
从图片中可以看到导致错误发生的函数调用路径。
接下来,我们试一下引入事件轮询:
function c() {
b();
}
function b() {
a();
}
function a() {
setTimeout(function() {
throw new Error('here');
},10);
}
c();
你会发现有用的跟踪信息不见了。
所以,在Node.js中,每步都要正确进行错误处理。一旦遗漏,发生错误就很难追踪了,因为上下文信息都丢失了。
一定要记住一句话线程中的所有状态都是维护在一个内存空间的,写程序的时候一定要格外小心。
Node中的Javascript
global对象
在浏览器中,全局对象值得就是window对象。在window对象上定义的任何内容都可以被全局访问到。比如,setTimeout其实就是window.setTimeout,document其实就是window.document。
Node中有两个类似但却格子代表不同含义的对象,如下所示。
- global:和window一样,任何global对象上的属性都可以被全局访问到
- process:所有全局执行上下文中的内容都在process对象中。在浏览器中,只有一个window对象,在Node中,也只有一个process对象
绝对模块和相对模块
绝对模块是指Node通过在其内部node_modules查找到的模块,或者Node内置的如fs这样的模块。
相对模块是将require指向一个相对工作目录中的JavaScript文件。
如果没有通过NPM来安装贸易额不再node_modules目录中,而且Node自带模块中没有以此命名的模块,就会找不到模块而报错,因此需要在require参数前加上./
事件
Node.js中的基础API之一就是EventEmmitter。无论是在Node中还是在浏览器中,大量代码都依赖于所监听或者分发的事件。
在Node中,Node暴露了Event EmitterAPI,该API上定义了on、emit以及removeListener方法。它以process.EventEmitter形式暴露出来。
可以将事件API添加到自己的类中:
let EventEmitter = process.EventEmiiter,
MyClass = function(){};
MyClass.prototype.__proto__ = EventEmitter.prototype
这样,MyClass的实例都具备了事件功能。
事件是Node非阻塞设计的重要体现。Node通常不会直接返回数据(因为这样可能会在等待某个资源的时候发生线程阻塞),而是采用分发事件来传递数据的方式。
buffer
buffer是一个表示固定内存分配的全局对象(也就是说,要放到缓冲区的字节数需要提前定下),它就好比是一个由八位字节元素组成的数组,可以有效地在JavaScript中表示二进制数据。
在Node中,绝大部分进行数据I/O操作的API都用buffer来接收和返回数据。
参考《了不起的Node.js》