node.js学习

作者: Daniel_adu | 来源:发表于2017-05-08 19:19 被阅读88次

一.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: '' }
*/

非常实用

相关文章

  • 12_Node.js Web 开发

    下面开始用 Node.js 进行 Web 开发。 我是通过《Node.js开发指南》这本书来学习 Node.js ...

  • 2016,9,13

    ##node.js的学习 #node.js的fs模块 var fs = require('fs'); functi...

  • (三)NodeJs快速入门

    本学习笔记是根据《Node.js开发指南》一书进行学习。前面的几篇是根据《深入浅出Node.js》学习,但是学习到...

  • 【灵魂拷问】你为什么要来学习Node.js呢?

    【灵魂拷问】你为什么要来学习Node.js呢? 学习node.js适合的人群: 需要必备一些HTML,CSS,Ja...

  • 如何优雅地学 Node.js

    作者:Draveness 学习 Node.js 的路线图我是在知乎找到的我目前学习 Node.js 的方式就是按照...

  • 如何优雅地学 Node.js

    作者:Draveness 学习 Node.js 的路线图我是在知乎找到的我目前学习 Node.js 的方式就是按照...

  • Node.js学习express框架

    Node.js框架学习 express.js express.js是什么 Express基于Node.js平台,快...

  • node.js web编程总结--重点语法

    目的 学习node.js重点语法,包括如下,目的:跳过基本的语法学习,进入node.js搭建web服务的重点知识:...

  • 【进阶系列】Node.js专题

    1 环境搭建 1.1 WebStorm集成Node.js Node.js入门学习笔记-IDE选择/配置之WebSt...

  • node.js OSX安装~

    根据node.js教程学习node.js遇到的问题及笔记O(∩_∩)O~ 官方网址 *** nvm:https:/...

网友评论

    本文标题:node.js学习

    本文链接:https://www.haomeiwen.com/subject/vhrvtxtx.html