1. 认识Node.js
Node.js是一个javascript运行环境。它让javascript可以开发后端程序,实现几乎其他后端语言实现的所有功能,可以与PHP、Java、Python、.NET、Ruby等后端语言平起平坐。
Nodejs是基于V8引擎,V8是Google发布的开源JavaScript引擎,本身就是用于Chrome浏览器的js解释部分,但是Ryan Dahl 这哥们,鬼才般的,把这个V8搬到了服务器上,用于做服务器的软件。
01 nodejs的特性
Nodejs语法完全是js语法,只要你懂js基础就可以学会Nodejs后端开发
NodeJs超强的高并发能力,实现高性能服务器
开发周期短、开发成本低、学习成本低
02 使用 Node.js 需要了解多少 JavaScript
http://nodejs.cn/learn/how-much-javascript-do-you-need-to-know-to-use-nodejs
03 浏览器环境vs node环境
Node.js 可以解析JS代码(没有浏览器安全级别的限制)提供很多系统级别的API,如:
文件的读写 (File System) 1 2 3 4 5 const fs = require ('fs' )fs.readFile ('./ajax.png' , 'utf-8' , (err, content ) => { console .log (content) })
进程的管理 (Process) 1 2 3 4 5 6 function main (argv ) { console .log (argv) } main (process.argv .slice (2 ))
网络通信 (HTTP/HTTPS) 1 2 3 4 5 6 7 8 9 10 const http = require ("http" )http.createServer ((req,res ) => { res.writeHead (200 , { "content-type" : "text/plain" }) res.write ("hello nodejs" ) res.end () }).listen (3000 )
2. 开发环境搭建
http://nodejs.cn/download/
3. 模块、包、commonJS
02 CommonJS规范
03 modules模块化规范写法 我们可以把公共的功能 抽离成为一个单独的 js 文件 作为一个模块,默认情况下面这个模块里面的方法或者属性,外面是没法访问的。如果要让外部可以访问模块里面的方法或者属性,就必须在模块里面通过 exports 或者 module.exports 暴露属性或者方法。
m1.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const name = 'gp19' const sayName = ( ) => { console .log (name) } console .log ('module 1' )module .exports = { say : sayName } exports .say = sayNameexports = { say : sayName }
main.js:
1 2 const m1 = require ('./m1' )m1.say ()
4. Npm&Yarn 01 npm的使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 npm init npm install 包名 –g (uninstall,update) npm install 包名 --save-dev (uninstall,update) npm list -g (不加-g,列举当前目录下的安装包) npm info 包名(详细信息) npm info 包名 version (获取最新版本) npm install md5@1 (安装指定版本) npm outdated ( 检查包是否已经过时) "dependencies" : { "md5" : "^2.1.0" } ^ 表示 如果 直接npm install 将会 安md5 2. *.* 最新版本 "dependencies" : { "md5" : "~2.1.0" } ~ 表示 如果 直接npm install 将会 安装md5 2.1 .* 最新版本 "dependencies" : { "md5" : "*" } * 表示 如果 直接npm install 将会 安装 md5 最新版本
02 全局安装 nrm
NRM (npm registry manager)是npm的镜像源管理工具,有时候国外资源太慢,使用这个就可以快速地在 npm 源间切换。
手动切换方法: npm config set registry https://registry.npm.taobao.org
安装 nrm 在命令行执行命令,npm install -g nrm,全局安装nrm。
使用 nrm 执行命令 nrm ls 查看可选的源。 其中,带*的是当前使用的源,上面的输出表明当前源是官方源。
切换 nrm 如果要切换到taobao源,执行命令nrm use taobao。
测试速度
你还可以通过 nrm test 测试相应源的响应时间。
扩展:
1 npm install -g cnpm --registry=https://registry.npmmirror.com
03 yarn使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 对比npm : 速度超快: Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。 超级安全: 在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。 开始新项目 yarn init 添加依赖包 yarn add [package] yarn add [package]@[version] yarn add [package] --dev 升级依赖包 yarn upgrade [package]@[version] 移除依赖包 yarn remove [package] 安装项目的全部依赖 yarn install
5. 内置模块 01 http模块
要使用 HTTP 服务器和客户端,则必须 require('http')
。
1 2 3 4 5 6 7 8 9 10 11 12 const http = require ('http' );const server = http.createServer ((req, res ) => { res.writeHead (200 , { 'Content-Type' : 'application/json' }); res.end (JSON .stringify ({ data : 'Hello World!' })); }); server.listen (8000 );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const http = require ('http' );const server = http.createServer ();server.on ('request' , (request, res ) => { res.writeHead (200 , { 'Content-Type' : 'application/json' }); res.end (JSON .stringify ({ data : 'Hello World!' })); }); server.listen (8000 );
02 url模块 02.1 parse 1 2 3 4 const url = require ('url' )const urlString = 'https://www.baidu.com:443/ad/index.html?id=8&name=mouse#tag=110' const parsedStr = url.parse (urlString)console .log (parsedStr)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const url = require ('url' )const urlObject = { protocol : 'https:' , slashes : true , auth : null , host : 'www.baidu.com:443' , port : '443' , hostname : 'www.baidu.com' , hash : '#tag=110' , search : '?id=8&name=mouse' , query : { id : '8' , name : 'mouse' }, pathname : '/ad/index.html' , path : '/ad/index.html?id=8&name=mouse' } const parsedObj = url.format (urlObject)console .log (parsedObj)
02.3 resolve 1 2 3 4 5 const url = require ('url' )var a = url.resolve ('/one/two/three' , 'four' ) ( 注意最后加/ ,不加/的区别 )var b = url.resolve ('http://example.com/' , '/one' )var c = url.resolve ('http://example.com/one' , '/two' )console .log (a + "," + b + "," + c)
03 querystring模块 03.1 parse 1 2 3 4 5 const querystring = require ('querystring' )var qs = 'x=3&y=4' var parsed = querystring.parse (qs)console .log (parsed)
03.2 stringify 1 2 3 4 5 6 7 8 const querystring = require ('querystring' )var qo = { x : 3 , y : 4 } var parsed = querystring.stringify (qo)console .log (parsed)
03.3 escape/unescape
1 2 3 4 5 const querystring = require ('querystring' )var str = 'id=3&city=北京&url=https://www.baidu.com' var escaped = querystring.escape (str)console .log (escaped)
1 2 3 4 const querystring = require ('querystring' )var str = 'id%3D3%26city%3D%E5%8C%97%E4%BA%AC%26url%3Dhttps%3A%2F%2Fwww.baidu.com' var unescaped = querystring.unescape (str)console .log (unescaped)
04 http模块补充 04.1 接口:jsonp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const http = require ('http' )const url = require ('url' )const app = http.createServer ((req, res ) => { let urlObj = url.parse (req.url , true ) switch (urlObj.pathname ) { case '/api/user' : res.end (`${urlObj.query.cb} ({"name": "gp145"})` ) break default : res.end ('404.' ) break } }) app.listen (8080 , () => { console .log ('localhost:8080' ) })
04.2 跨域:CORS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 const http = require ('http' )const url = require ('url' )const querystring = require ('querystring' )const app = http.createServer ((req, res ) => { let data = '' let urlObj = url.parse (req.url , true ) res.writeHead (200 , { 'content-type' : 'application/json;charset=utf-8' , 'Access-Control-Allow-Origin' : '*' }) req.on ('data' , (chunk ) => { data += chunk }) req.on ('end' , () => { responseResult (querystring.parse (data)) }) function responseResult (data ) { switch (urlObj.pathname ) { case '/api/login' : res.end (JSON .stringify ({ message : data })) break default : res.end ('404.' ) break } } }) app.listen (8080 , () => { console .log ('localhost:8080' ) })
04.3 模拟get 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var http = require ('http' )var https = require ('https' )const server = http.createServer ((request, response ) => { var url = request.url .substr (1 ) var data = '' response.writeHeader (200 , { 'content-type' : 'application/json;charset=utf-8' , 'Access-Control-Allow-Origin' : '*' }) https.get (`https://m.lagou.com/listmore.json${url} ` , (res ) => { res.on ('data' , (chunk ) => { data += chunk }) res.on ('end' , () => { response.end (JSON .stringify ({ ret : true , data })) }) }) }) server.listen (8080 , () => { console .log ('localhost:8080' ) })
04.4 模拟post:服务器提交(攻击) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 const https = require ('https' )const querystring = require ('querystring' )const postData = querystring.stringify ({ province : '上海' , city : '上海' , district : '宝山区' , address : '同济支路199号智慧七立方3号楼2-4层' , latitude : 43.0 , longitude : 160.0 , message : '求购一条小鱼' , contact : '13666666' , type : 'sell' , time : 1571217561 }) const options = { protocol : 'https:' , hostname : 'ik9hkddr.qcloud.la' , method : 'POST' , port : 443 , path : '/index.php/trade/add_item' , headers : { 'Content-Type' : 'application/x-www-form-urlencoded' , 'Content-Length' : Buffer .byteLength (postData) } } function doPost ( ) { let data let req = https.request (options, (res ) => { res.on ('data' , chunk => data += chunk) res.on ('end' , () => { console .log (data) }) }) req.write (postData) req.end () }
04.5 爬虫 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 const https = require ('https' )const http = require ('http' )const cheerio = require ('cheerio' )http.createServer ((request, response ) => { response.writeHead (200 , { 'content-type' : 'application/json;charset=utf-8' }) const options = { hostname : 'i.maoyan.com' , port : 443 , path : '/' , method : 'GET' } const req = https.request (options, (res ) => { let data = '' res.on ('data' , (chunk ) => { data += chunk }) res.on ('end' , () => { filterData (data) }) }) function filterData (data ) { let $ = cheerio.load (data) let $movieList = $('.column.content' ) console .log ($movieList) let movies = [] $movieList.each ((index, value ) => { movies.push ({ title : $(value).find ('.movie-title .title' ).text (), detail : $(value).find ('.detail .actor' ).text (), }) }) response.end (JSON .stringify (movies)) } req.end () }).listen (3000 )
05 event模块 1 2 3 4 5 6 7 8 9 10 11 12 const EventEmitter = require ('events' )class MyEventEmitter extends EventEmitter {}const event = new MyEventEmitter ()event.on ('play' , (movie ) => { console .log (movie) }) event.emit ('play' , '我和我的祖国' ) event.emit ('play' , '中国机长' )
06 fs文件操作模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 const fs = require ('fs' )fs.mkdir ('./logs' , (err ) => { console .log ('done.' ) }) fs.rename ('./logs' , './log' , () => { console .log ('done' ) }) fs.rmdir ('./log' , () => { console .log ('done.' ) }) fs.writeFile ( './logs/log1.txt' , 'hello' , (err ) => { if (err) { console .log (err.message ) } else { console .log ('文件创建成功' ) } } ) fs.appendFile ('./logs/log1.txt' , '\nworld' , () => { console .log ('done.' ) }) fs.readFile ('./logs/log1.txt' , 'utf-8' , (err, data ) => { console .log (data) }) fs.unlink ('./logs/log1.txt' , (err ) => { console .log ('done.' ) }) for (var i = 0 ; i < 10 ; i++) { fs.writeFile (`./logs/log-${i} .txt` , `log-${i} ` , (err ) => { console .log ('done.' ) }) } fs.readdir ('./' , (err, data ) => { data.forEach ((value, index ) => { fs.stat (`./${value} ` , (err, stats ) => { console .log (value + ' is ' + (stats.isDirectory () ? 'directory' : 'file' )) }) }) }) try { const content = fs.readFileSync ('./logs/log-1.txt' , 'utf-8' ) console .log (content) console .log (0 ) } catch (e) { console .log (e.message ) } fs.readFile ('./logs/log-0.txt' , 'utf-8' , (err, content ) => { console .log (content) console .log (0 ) }) console .log (1 )const fs = require ("fs" ).promises fs.readFile ('./logs/log-0.txt' , 'utf-8' ).then (result => { console .log (result) })
在fs
模块中,提供同步方法是为了方便使用。那我们到底是应该用异步方法还是同步方法呢?
由于Node环境执行的JavaScript代码是服务器端代码,所以,绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码 ,否则,同步代码在执行时期,服务器将停止响应,因为JavaScript只有一个执行线程。
服务器启动时如果需要读取配置文件,或者结束时需要写入到状态文件时,可以使用同步代码,因为这些代码只在启动和结束时执行一次,不影响服务器正常运行时的异步执行。
07 stream流模块 stream
是Node.js提供的又一个仅在服务区端可用的模块,目的是支持“流”这种数据结构。
什么是流?流是一种抽象的数据结构。想象水流,当在水管中流动时,就可以从某个地方(例如自来水厂)源源不断地到达另一个地方(比如你家的洗手池)。我们也可以把数据看成是数据流,比如你敲键盘的时候,就可以把每个字符依次连起来,看成字符流。这个流是从键盘输入到应用程序,实际上它还对应着一个名字:标准输入流(stdin)。
如果应用程序把字符一个一个输出到显示器上,这也可以看成是一个流,这个流也有名字:标准输出流(stdout)。流的特点是数据是有序的,而且必须依次读取,或者依次写入,不能像Array那样随机定位。
有些流用来读取数据,比如从文件读取数据时,可以打开一个文件流,然后从文件流中不断地读取数据。有些流用来写入数据,比如向文件写入数据时,只需要把数据不断地往文件流中写进去就可以了。
在Node.js中,流也是一个对象,我们只需要响应流的事件就可以了:data
事件表示流的数据已经可以读取了,end
事件表示这个流已经到末尾了,没有数据可以读取了,error
事件表示出错了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var fs = require ('fs' );var rs = fs.createReadStream ('sample.txt' , 'utf-8' );rs.on ('data' , function (chunk ) { console .log ('DATA:' ) console .log (chunk); }); rs.on ('end' , function ( ) { console .log ('END' ); }); rs.on ('error' , function (err ) { console .log ('ERROR: ' + err); });
要注意,data
事件可能会有多次,每次传递的chunk
是流的一部分数据。
要以流的形式写入文件,只需要不断调用write()
方法,最后以end()
结束:
1 2 3 4 5 6 var fs = require ('fs' );var ws1 = fs.createWriteStream ('output1.txt' , 'utf-8' );ws1.write ('使用Stream写入文本数据...\n' ); ws1.write ('END.' ); ws1.end ();
pipe
就像可以把两个水管串成一个更长的水管一样,两个流也可以串起来。一个Readable
流和一个Writable
流串起来后,所有的数据自动从Readable
流进入Writable
流,这种操作叫pipe
。
在Node.js中,Readable
流有一个pipe()
方法,就是用来干这件事的。
让我们用pipe()
把一个文件流和另一个文件流串起来,这样源文件的所有数据就自动写入到目标文件里了,所以,这实际上是一个复制文件的程序:
1 2 3 4 5 6 const fs = require ('fs' )const readstream = fs.createReadStream ('./1.txt' )const writestream = fs.createWriteStream ('./2.txt' )readstream.pipe (writestream)
08 zlib
1 2 3 4 5 6 7 8 9 10 11 12 const fs = require ('fs' )const zlib = require ('zlib' )const gzip = zlib.createGzip ()const readstream = fs.createReadStream ('./note.txt' )const writestream = fs.createWriteStream ('./note2.txt' )readstream .pipe (gzip) .pipe (writestream)
09 crypto crypto模块的目的是为了提供通用的加密和哈希算法。用纯JavaScript代码实现这些功能不是不可能,但速度会非常慢。Nodejs用C/C++实现这些算法后,通过cypto这个模块暴露为JavaScript接口,这样用起来方便,运行速度也快。
MD5是一种常用的哈希算法,用于给任意数据一个“签名”。这个签名通常用一个十六进制的字符串表示:
1 2 3 4 5 6 7 8 9 10 const crypto = require ('crypto' );const hash = crypto.createHash ('md5' );hash.update ('Hello, world!' ); hash.update ('Hello, nodejs!' ); console .log (hash.digest ('hex' ));
update()
方法默认字符串编码为UTF-8
,也可以传入Buffer。
如果要计算SHA1,只需要把'md5'
改成'sha1'
,就可以得到SHA1的结果1f32b9c9932c02227819a4151feed43e131aca40
。
Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法。不同的是,Hmac还需要一个密钥:
1 2 3 4 5 6 7 8 9 const crypto = require('crypto'); const hmac = crypto.createHmac('sha256', 'secret-key'); hmac.update('Hello, world!'); hmac.update('Hello, nodejs!'); console.log(hmac.digest('hex')); // 80f7e22570...
只要密钥发生了变化,那么同样的输入数据也会得到不同的签名,因此,可以把Hmac理解为用随机数“增强”的哈希算法。
AES是一种常用的对称加密算法,加解密都用同一个密钥。crypto模块提供了AES支持,但是需要自己封装好函数,便于使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const crypto = require ("crypto" );function encrypt (key, iv, data) { let decipher = crypto.createCipheriv ('aes-128-cbc' , key, iv); return decipher.update (data, 'binary' , 'hex' ) + decipher.final ('hex' ); } function decrypt (key, iv, crypted) { crypted = Buffer .from (crypted, 'hex' ).toString ('binary' ); let decipher = crypto.createDecipheriv ('aes-128-cbc' , key, iv); return decipher.update (crypted, 'binary' , 'utf8' ) + decipher.final ('utf8' ); } key,iv必须是16 个字节
可以看出,加密后的字符串通过解密又得到了原始内容。
6. 路由 01 基础 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 var fs = require ("fs" )var path = require ("path" )function render (res, path ) { res.writeHead (200 , { "Content-Type" : "text/html;charset=utf8" }) res.write (fs.readFileSync (path, "utf8" )) res.end () } const route = { "/login" : (req, res ) => { render (res, "./static/login.html" ) }, "/home" : (req, res ) => { render (res, "./static/home.html" ) }, "/404" : (req, res ) => { res.writeHead (404 , { "Content-Type" : "text/html;charset=utf8" }) res.write (fs.readFileSync ("./static/404.html" , "utf8" )) } }
02 获取参数 get请求
1 2 3 4 5 "/api/login" :(req,res )=> { const myURL = new URL (req.url , 'http://127.0.0.1:3000' ); console .log (myURL.searchParams .get ("username" )) render (res,`{ok:1}` ) }
post请求 1 2 3 4 5 6 7 8 9 10 11 12 13 "/api/login" : (req, res ) => { var post = '' ; req.on ('data' , function (chunk ) { post += chunk; }); req.on ('end' , function ( ) { post = JSON .parse (post); render (res, `{ok:1}` ) }); }
03 静态资源处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function readStaticFile (req, res ) { const myURL = new URL (req.url , 'http://127.0.0.1:3000' ) var filePathname = path.join (__dirname, "/static" , myURL.pathname ); if (fs.existsSync (filePathname)) { res.writeHead (200 , { "Content-Type" : `${mime.getType(myURL.pathname.split("." )[1 ])} ;charset=utf8` }) res.write (fs.readFileSync (filePathname, "utf8" )) res.end () return true } else { return false } }