node.js学习

一.node知识

1.回调函数,即异步IO的node处理方式:时间循环,观察者,请求对象,io线程。

先在线程池处理函数,函数处理完了之后对应不同操作系统置标志位,事件循环的观察者一直在while(true)循环观察各个标志位,一批一批的拉,直到这个事件结束了查看这个函数有没有回调函数,有的话执行回调函数。
对于不同操作系统,事件完成时发送的信号,linux下面是通过epoll进行的,而windows下是通过内核(IOCP)实现的。

2.settimeout(),process.nextTick(),setInterval(),setImmediate()

settimeoutsetInterval实际上是相同的,不一样在于第一个是一次执行,而第二个是多次执行。
原理:设定时间的时候,在红黑树中将这个定时器插入到一个贯彻着内部的一个红黑树中,系统有个Tick定时器,每次Tick执行时候,会拿出这个红黑树中的定时器节点,看是否超过时间,超过就执行。所以不是精确的时间。

对于立即执行的任务,有些人会使用setTimeout(function(),0)来进行,而这个方法的经度不够,并且要使用红黑树,比较重。这时候可以采用process.nextTick()进行取代。
setImmediatenexttick比较类似,也有一些细小的额差别,就是nexttick的使用idle观察者,优先级高于I/O观察者,回调函数放在数组中,而setImmediate使用check观察者,优先级低于I/O观察者,回调放在链表中。最终的区别就是nexttick的优先级高于setImmediate

自己写的一个倒计时器

//倒计时器
var jb=setInterval(function(){
    console.log(time)
    time--;
    if(time==0){
        clearInterval(jb);
    }
},1000);

3.node中发布订阅模式

采用自带的events库,使用:

var events = require('events');//引入
var  x =new events.EventEmitter();//创建实例
x.on('y', function(a,b,c){
 console.log('it\'s work1!'+a+b+c);
});//订阅一个字段,可以是多个

x.emit('y','111','222', '3333');//发布一个字段
//注意:需要先订阅再发布

也可以将on改成once,代表只接收一次这个消息
events类似于android中的handle,massage和post

4.回调函数

用一个事例来讲

var i = 0;
function sleep(ms, callback) {
    setTimeout(function () {
        console.log('我执行完啦!');
        i++;
        if (i >= 2) callback(new Error('i大于2'), null);
        else callback(null, i);
    }, ms);
}

sleep(3000, function (err,val) {
    if(err) console.log('出错啦:'+err.message);
    else console.log(val);
})

上面,将callback函数通过高阶函数,参数的方式传入进去,然后再在里面直接调用,外面就能够获取到数据了。

5.node的异步编程,其中有Promise和Async/Await

events能够解决代码之间的耦合,使得事件的写法不在一块。对于流程化的,分模块之间的额异步编程,就必须使用新的框架。
建议:异步的优先级过程:async/await > promise > 事件events > 回调
对于上面sleep的函数,我想在成功之后再执行一次,就必须在sucess中嵌套写代码,多次呢,就要写多次。而使用promise可以这样写:

var i = 0;
//函数返回promise
function sleep(ms) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log('我执行好了');
            i++;
            if (i >= 2) reject(new Error('i>=2'));
            else resolve(i);
        }, ms);
    })
}

sleep(1000).then(function (val) {
    console.log(val);
    return sleep(1000)
}).then(function (val) {
    console.log(val);
    return sleep(1000)
}).then(function (val) {
    console.log(val);
    return sleep(1000)
}).catch(function (err) {
    console.log('出错啦:' + err.message);
})

返回一个Promise的对象,对象中还是采用回调,第一个参数是resolve,第二个参数是reject,第一个代表成功,第二个代表错误。
然后再调用then一步步处理,使用一个大的catch来处理错误。这样解决了函数嵌套的问题,可是还是没有解决函数回调的问题呀,回调的问题使用事件events可以解决,不使用回调,而是在成功挥着失败后直接抛出事件,然后再在别的地方捕获就行。

这时候async/await出现了。
async/await在node7.0中出现,需要使用harmony模式运行,在7.6以上就能够直接使用了。
定义一个异步函数:

async function fn(){
return 0;
}

其实返回的是一个Task(Promise)
await写在async中,类比C#, promise其实就是C#中的Task,async和await和C#中的async/await使用一样。
所以,只要是Task就能够await,而不一定是async返回的函数才能await。比如,一个http request返回的是一个promise,就能够进行await进行同步,或者一个settimeout的函数,返回的是promise,也能记性await进行同步,如下:

var i = 0;
//函数返回promise
function sleep(ms) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log('我执行好了');
            i++;
            if (i >= 2) reject(new Error('i>=2'));
            else resolve(i);
        }, ms);
    })
}

(async function () {
    try {
        var val;
        val = await sleep(1000);
        console.log(val);
        val = await sleep(1000);
        console.log(val);
        val = await sleep(1000);
        console.log(val);
    }
    catch (err) {
        console.log('出错啦:'+err.message);
    }
} ())

sleep函数是一个异步方法,需要执行一定时间之后才有返回。

  • 结论:使用Promise处理异步函数,使用async/await处理异步函数的同步和步骤控制问题。

写一下Promise的最基本用法:

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

6.Promise使用补充

使用resolve和reject进行数据传出,里面不用使用return字段,直接使用resolve(数据)和reject(数据)就能够传出,所以不管你的调用函数里面有回调有嵌套,都不管,该返回正确值的时候直接调用reslove()就行,有错误直接reject()就行啦!
另外,而async/await的作用是当你要调用这个promise的方法时,可以不使用then这样一步一步,而是使用await来控制进度,其本质是通过promise实现的。

7.node的class机制

node是为服务端进行的,所以使用面向对象是很关键的。现在node能够直接使用class进行对象操作
举例:

class HondClass{
    constructor(name){
        this.name = name;
    }
    setName(name){
        this.name=name;
    }
    getName(){
        return this.name;
    }

}
module.exports = {
    INIT : new HondClass("hond"),
}

这个js定义了一个class HondClass,导出了一个INIT初始化对象实例,使用constructor进行构造。
在另一个js中,

var HondClass =require('./HondClass.js');

可以获得这个对象,这种方式可以作为单例。

一般的,不使用单例,可以这样:

class HondClass{
    constructor(name){
        this.name = name;
    }
    setName(name){
        this.name=name;
    }
    getName(){
        return this.name;
    }

}
module.exports = HondClass;

然后在使用的时候

var HondClass =require('./HondClass.js');
var hond1 = new HondClass("namesadas");
console.log(hond1.getName())

这样的使用习惯和面向对象的C#和java一致。当然,class只是语法糖,es6之前,都是通过function创建class的,类似下面这样:

function HondClass(name){

    this.name=name;

    this.getName=function(){
        return this.name;
    }

}
module.exports = HondClass;

这个格式是es6之前的格式,很有js风格。而es6之后就简洁的多。其实差不多就是把function改成class,然后把输入放到construstor中去。
而调用就是一模一样。

8.node的buffer

buffer是字节数组的意思,那js对string支持这么好,为什么不直接使用string而要使用buffer呢,原因在于在网络传输中,最终都是要使用字节流进行数据传输,如果事先将一些固定的返回值变成字节流,那么就能够减少将string转换为buffer的时间消耗了。
demo代码如下:

var str1 = "hongyangdu";
//string --> buffer
var buf1 = new Buffer(str1,'UTF-8');
console.log(buf1)

//重写部分内容,覆盖原来的0-4位[)
buf1.write("12345",0,4,'UTF-8')

//buffer --> string
var str2 = buf1.toString('UTF-8');

//buffer拼接
//Buffer.concat(chunks,size)//参数是想要拼接的buffer list,size是想要拼接成的最终buffer的字节数
//举例
chunks=[]
chunks.push(new Buffer("1111",'UTF-8'));
chunks.push(new Buffer("2222",'UTF-8'));
var ss = Buffer.concat(chunks,8);
console.log(ss)

9.讲到这里,中途插一个node的使用场景

node是异步IO和无阻塞IO的代表,处理io密集项目很在行,当然因为其使用了C++的内核,所以处理cpu密集的也不差。《深入浅出nodejs》书中的斐波那契测试就验证了。
比较合适的使用场景:
(1)实时应用,比如在线聊天,实时推送等等,可以使用socket.io进行websocket推送
(2)restful的api。restful的api的大部分时间都在从io接口请求数据,然后从数据库拿数据,再将数据交给用户。或者往数据库写数据,然后异步返回结果。
(3)前后端统一的环境。毕竟node是架构在javascript上的语言,而前端用的就是js,所以如果想在前后端的数据上面进行完全统一,使用node是个不错的做法
(4)有大量ajax请求的应用,比如定位上传,地图应用等等。

10.网络请求

(1)自带的http和https
这里就不说了,感兴趣的可以去官网看事例。这里主要介绍一个强大的第三方库。
(2)第三方库request
request的github
基本使用

var request = require('request');
request('http://www.google.com', function (error, response, body) {
  console.log('error:', error); // Print the error if one occurred
  console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
  console.log('body:', body); // Print the HTML for the Google homepage.
});

表单提交
采用:application/x-www-form-urlencoded的Content-type
有三种方法:

request.post('http://service.com/upload', {form:{key:'value'}})
// or
request.post('http://service.com/upload').form({key:'value'})
// or
request.post({url:'http://service.com/upload', form: {key:'value'}}, function(err,httpResponse,body){ /* ... */ })

对于文件提交

request.post({url:'http://service.com/upload', formData: formData}, function optionalCallback(err, httpResponse, body) {
  if (err) {
    return console.error('upload failed:', err);
  }
  console.log('Upload successful!  Server responded with:', body);
});

然后再formdata里面写上一些参数,如下:

var formData = {
  // Pass a simple key-value pair
  my_field: 'my_value',
  // Pass data via Buffers
  my_buffer: new Buffer([1, 2, 3]),
  // Pass data via Streams
  my_file: fs.createReadStream(__dirname + '/unicycle.jpg'),
  // Pass multiple values /w an Array
  attachments: [
    fs.createReadStream(__dirname + '/attachment1.jpg'),
    fs.createReadStream(__dirname + '/attachment2.jpg')
  ],
  // Pass optional meta-data with an 'options' object with style: {value: DATA, options: OPTIONS}
  // Use case: for some types of streams, you'll need to provide "file"-related information manually.
  // See the `form-data` README for more information about options: https://github.com/form-data/form-data
  custom_file: {
    value:  fs.createReadStream('/dev/urandom'),
    options: {
      filename: 'topsecret.jpg',
      contentType: 'image/jpeg'
    }
  }
};

一般正常的使用如下:

var request = require('request');

var options = {
  url: 'https://api.github.com/repos/request/request',
  headers: {
    'User-Agent': 'request'
  }
};

function callback(error, response, body) {
  if (!error && response.statusCode == 200) {
    var info = JSON.parse(body);
    console.log(info.stargazers_count + " Stars");
    console.log(info.forks_count + " Forks");
  }
}

request(options, callback);

可以自己添加header,然后返回是json,使用options和callback分离request和response,
使用request.post和request.get分别代表get和post操作。

如果需要返回值,就使用第三种方法

11.websocket


推荐一篇文章通俗易懂
websock是客户端和服务器之间的全双工通信,使得服务器能够主动向客户端发送信息。
以前的流程是这样的:

  • sync
Note left of Client: blockServer-->Client: Response
  • async
    异步就需要每隔一段时间去轮询一次数据,直到得到数据

而websocket能够由某一方主动发出数据,这样客户端请求数据,server有数据的时候就会自己向客户端推送。
websocket流程:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术少了很多。

node.js中的websocket

现在socket.io是比较火的node.js中国的websock框架,下面简单介绍下其使用:
官网
官网里面有一个聊天程序,是一个非常好的例子。
(1).安装socket.io,express等
(2) 最简单的demo:

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendfile('index.html');
});
//下面就是socketio用法,有链接时打印connected
io.on('connection', function(socket){
  console.log('a user connected');
});

http.listen(3000, function(){
  console.log('listening on *:3000');
});

(3)从web前端发送数据到server:
首先是前端代码:

<script src="/socket.io/socket.io.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
<script>
  var socket = io();
  $('form').submit(function(){
    socket.emit('chat message', $('#m').val());
    $('#m').val('');
    return false;
  });
</script>

就是使用jquery提交了一个表单,表单里使用socket.emit来进行数据提交,提交用的是key-value形式。

然后再node的后端,

io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    console.log('message: ' + msg);
  });
});

使用socket.on来获取这个数据,完成了单向的数据传输

(4) 从server到web,如果是所有用户,
向每一个用户都发送一个event,则

io.emit('some event', { for: 'everyone' });

如果想发送给所有人(不包括自己),则

io.broadcast.emit('some event', { for: 'everyone' });

如果针对事件发送,则

socket.emit(event",'message');

双方一样,发送用emit,监听使用on.

socket.io的其他方法

set: 保存会话数据,get:获取临时会话数据,使用就如hashmap一般

demo

Server:

var app = require('express').createServer();
var io = require('socket.io')(app);

app.listen(80);

app.get('/', function (req, res) {
  res.sendfile(__dirname + '/index.html');
});

io.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});

client:

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io.connect('http://localhost');
  socket.on('news', function (data) {
    console.log(data);
    socket.emit('my other event', { my: 'data' });
  });
</script>

12.express中间件

参考网站:使用中间件
简单的中间件:

// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use(function (req, res, next) {
  console.log('Time:', Date.now());
  next();
});

// 挂载至 /user/:id 的中间件,任何指向 /user/:id 的请求都会执行它
app.use('/user/:id', function (req, res, next) {
  console.log('Request Type:', req.method);
  next();
});

// 路由和句柄函数(中间件系统),处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
  res.send('USER');
});

中间件可以传递下去,如下:

app.get('/user/:id', function (req, res, next) {
  console.log('ID:', req.params.id);
  next();
}, function (req, res, next) {
  res.send('User Info');
});

因此,可以将公共的中间件使用var定义下来,需要的时候加进去就好了。

错误处理中间件:

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

像cookie,auth验证,路由清洗,日志,错误处理。cookie解析中间件可以使用插件cookie-parser

13.多进程

为什么要使用多进程呢?node是事件驱动,单用单线程处理,避免了一些切换和开销。
而如果有好几个独立的处理任务,有多个处理器,那么不使用多处理器不就是浪费吗?这时候就可以采用多进程。每一个进程都是一个独立的空间和存储,基本使用:
main.js

var cp = require('child_process');
var n = cp.fork('./sub.js');
n.on('message', function(m) {
  console.log('PARENT got message:', m);
});
n.send({ hello: 'world' });

sub.js

process.on('message', function(m) {
  console.log('CHILD got message:', m);
});
process.send({ foo: 'bar' });

child_process有四个方法,spawn()exec(),execFile(),fork()。对于js文件,直接执行fork(),对于文件,执行execFile,可以自己添加命令和回调。
对spawn是最简单的,execspawn上面加了一个回调。这个过程是异步的,如果需要同步,则使用execSync,execFileSyncspawnSync
node的cluster模块能够方便地进行worker的分配,非常不错。

14.知识积累

在request中,有时候返回的body是string形式,有时候返回的body是object形式,能够直接当json使用,因此最好事先判断下。

二.读nodejs基础

nodejs基础

1.工程目录

node和javascript其实没有本质的关系,即和android与java没有本制度额关系一样。node只是代表了一种时间机制和io模型的框架结构,而javascript只是代表一种语言,使用commonjs来实现这么一种机制的框架是node。
一个node项目的目录结构一般如下:

- /home/user/workspace/node-echo/   # 工程目录
    - bin/                          # 存放命令行相关代码
        node-echo
    + doc/                          # 存放文档
    - lib/                          # 存放API相关代码
        echo.js
    - node_modules/                 # 存放三方包
        + argv/
    + tests/                        # 存放测试用例
    package.json                    # 元数据文件
    README.md                       # 说明文件

其中bin下面存放的命令行代码,和下面这这样差不多:

/* bin/node-echo */
var argv = require('argv'),
    echo = require('../lib/echo');
console.log(echo(argv.join(' ')));

即提取命令行用户输入,这个命令行在server端几乎没什么用,只对某些调试或者中间查看的js有些用。

2.package.json

一个package.json长成下面这样:

{
    "name": "node-echo",           # 包名,在NPM服务器上须要保持唯一
    "version": "1.0.0",            # 当前版本号
    "dependencies": {              # 三方包依赖,需要指定包名和版本号
        "argv": "0.0.2"
      },
    "main": "./lib/echo.js",       # 入口模块位置
    "bin" : {
        "node-echo": "./bin/node-echo"      # 命令行程序名和主模块位置
    }
}

3.文件操作

node是从v8而来,因此默认的内存可使用很小。对于一般我们的想法,例如拷贝文件,先从一个文件的file编程stream,然后再将stream编程第二个file。
程序如下:

var fs = require('fs');

function copy(src, dst) {
    fs.writeFileSync(dst, fs.readFileSync(src));
}

这种做法是很吃内存的使用管道传递stream会大大降低内存的使用量。

var fs = require('fs');

function copy(src, dst) {
    fs.createReadStream(src).pipe(fs.createWriteStream(dst));
}

对于那些无法一次装下需要的数据,或者为了节约内存(建议),则使用stream流进行处理。stream流不会将数据全部读进内存,只会在里面存一段,然后存下文件的初始位置,以及目前读到的位置开始和结尾。
有人有疑问了,数据流的读写,读和写的数据不一样,怎么保证读出来的数据能够一定送达到写的文件中去呢。其实在pipi管道内部已经封装好了,类似下面这样的:

var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);

rs.on('data', function (chunk) {
    if (ws.write(chunk) === false) {
        rs.pause();
    }
});

rs.on('end', function () {
    ws.end();
});

ws.on('drain', function () {
    rs.resume();
});

stream内部是继承了EventEmitter的,所以可不有许多消息机制在里面。采用等待通知消息的形式一步一步下去。
fs模块的基本方法:

fs.start,
fs.chmod,
fs.chown,
fs.readFile,
fs.readdir,
fs.writefile,
fs.mkdir,
fs.open,
fs.read,
fs.write,
fs.close

并且所有fs的操作都提供两个回调参数,一个error,一个是data,便是当前操作需要的data,具体的data与具体函数相关。

4.querystring模块

querystring模块将post上传的(urlencode格式)数据变成json的object,比如下面:

querystring.parse('foo=bar&baz=qux&baz=quux&corge');
/* =>
{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }
*/

非常实用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容