nodejs 从入门到企业web开发中的应用

### nodejs 从入门到企业web开发中的应用

###第一章 课程内容介绍

1-1导学

课程目标:

        从零开始掌握大型互联网公司nodejs实际使用

课程内容:

        nodejs原理

        nodejs基础API

        静态资源服务器

        代码本地构建

        单元测试

        UI测试

        headless爬虫

技术栈:

        nodejs

        HTTP

1-2课程介绍

web开发所需要了解的nodejs

课程大纲:

        nodejs介绍

        调试&项目初始化

        基本API

        简单web Server

        单元测试&发布

        nodejs爬虫示例

###第二章 nodejs是什么,为什么偏爱nodejs

2-1 nodejs是什么

nodejs is a javascript runtime built on Chrome's V8

nodejs  uses an event-driven,non-blocking I/O model

非阻塞I/O:

       阻塞:I/O时进程休眠等待I/O完成后进行下一步

       非阻塞:I/O是函数立即返回,进程不等待I/O完成

事件驱动:

       I/O等异步才做结束后的通知

        观察者模式 

2-2 nodejs究竟好在哪里

前端职责范围变大,统一开发体验

在处理高并发,I/O密集场景性能优势明显

CPU密集VSI/O密集:

        CPU密集:压缩,解压,加密,解密

        I/O密集:文件操作,网络操作,数据库

web常用场景:

        静态资源读取

        数据库操作

        渲染页面

高并发应对之道:

        增加机器数

        增加每台机器的CPU数--多核

进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位

多线程:启动多个进程,多个进程可以一块执行多个任务

nodejs工作模型:

                    

线程:进程内一个相对独立的,可调度的执行单元,与同属一个进程的线程共享进程的资源

多线程:启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务

nodejs的单线程:

        单线程只是针对主进程,I/O操作系统底层多线程调度

        单线程并不是单进程

常用场景:

        web Server

        本地代码构建

        实用工具开发

###第三章 环境&调试

3-1 环境&调试--CommonJS1

环境:

        CommonJS(nodejs的模块管理规范)

        global

        process

CommonJS:

        每个文件是一个模块,有自己的作用域

        在模块内部module变量代表模块本身

        module.exports属性代表模块对外接口

require规则:

        /表示绝对路径,./表示相对于当前文件的

        支持js,json,node扩展名,不写依次尝试

        不写路径则认为是build-in模块或者各级node_modules内的第三方模块

require特性:

        module被加载的时候执行,加载后缓存

        一旦出现某个模块被循环加载,就只输出已经执行的部分,还未执行的部分不会输出

3-2 环境&调试--CommonJS2

见mycode

3-3 环境&调试--CommonJS3

见mycode

3-4 环境&调试--引用系统内置模块&引用第三方模块

见mycode

3-5 环境&调试--module.exports 与 exports 的区别

见mycode

3-6 环境&调试--global变量

global:

        CommonJS

        Buffer,process,console

        timer

3-7 环境&调试--process进程

常用的参数相关:

        argv

        argv0

        execArgv

        execPath

const {argv,argv0,execArgv,execPath}=process;

argv.forEach(element => {

console.log(element);

});

//argv的第一个值

console.log(argv0);

执行命令:

node 10_argv.js

执行结果:

C:\Program Files\nodejs\node.exe   //node安装路径

//当前执行文件的路径

E:\百度云\nodeJS从入门到实战\Node.js入门到企业Web开发中的应用\mycode\10_argv.js

C:\Program Files\nodejs\node.exe

execArgv

//调用node所产生的特殊参数

console.log(execArgv)

执行命令:

node --inspect 10_argv.js

执行结果:

[ '--inspect' ]

execArgv:调用node命令的路径

环境:

const {env}=process;

console.log(env);

cwd:

//打印当前process的执行路径

console.log(process.cwd());

//执行结果:

E:\百度云\nodeJS从入门到实战\Node.js入门到企业Web开发中的应用\mycode

setImmediate

//同步的执行完再去执行

setImmediate(()=>{

console.log('setImmediate');

});

setTimeout(() => {

console.log('timeout');

}, 0);

//在setImmediate之前执行,慎重使用

process.nextTick(()=>{

console.log('nextTick');

})

执行结果:

nextTick

timeout

setImmediate

3-8 环境&调试--debug1

inspector

vscode

3-9 环境&调试--debug2

###第四章 nodejs基础API

4-1基础API---path

path:和路径有关的一切

normalize :将路径格式化

join :将几个路径拼接,会先格式化

resolve :相对路径解析成绝对路径

basename :文件名

extname :拓展名

dirname:所在路径

parse :

代码:

                

执行结果:

                

format 

sep:路径的分隔符

delimiter: path的分隔符

win32     posix

执行结果:

path

__dirname,__filename总是返回文件的绝对路径

process.cwd()总是返回执行node命令所在文件夹

./

在require方法中,总是相对当前文件所在文件夹

在其他地方和process.cwd()一样,相对node启动文件夹

4-2 基础API--buffer

buffer:缓冲

        buffer用于处理二进制数据流

        实例类似于整数数组,大小固定

        C++代码在V8堆外分配物理内存

代码:

//长度10 ,默认用0填充

console.log(Buffer.alloc(10));

//长度20

console.log(Buffer.alloc(20));

//长度5,用1填充

console.log(Buffer.alloc(5,1));

//长度5 ,没有进行初始化

console.log(Buffer.allocUnsafe(5,1));

//按指定格式初始化

console.log(Buffer.from([1,2,3]));

//用test的ASCII码初始化,默认用utf-8

console.log(Buffer.from('test'));

//指定用一种编码格式

console.log(Buffer.from('test','base64'));

执行结果;

<Buffer 00 00 00 00 00 00 00 00 00 00>

<Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>

<Buffer 01 01 01 01 01>

<Buffer 30 d4 da 3c a4>

<Buffer 01 02 03>

<Buffer 74 65 73 74>

<Buffer b5 eb 2d>

Buffer.byteLength

Buffer.isBuffer()

Buffer.concat()

代码:

//占多少字节

console.log(Buffer.byteLength('test'));

console.log(Buffer.byteLength('测试'));

//判断是否为buffer

console.log(Buffer.isBuffer({}));

console.log(Buffer.isBuffer(Buffer.from([1,2,3])));

//拼接buffer

const buf1=Buffer.from('this');

const buf2=Buffer.from('is');

const buf3=Buffer.from('a');

const buf4=Buffer.from('test');

const buf5=Buffer.from('!');

const buf=Buffer.concat([buf1,buf2,buf3,buf4,buf5]);

console.log(buf.toString());

执行结果:

4

6

false

true

thisisatest!

buffer实例的属性和方法

//buffer实例的属性和方法

//buffer.length

const buf=Buffer.from('this is a test!');

console.log(buf.length);

const buf2=Buffer.alloc(10);

buf2[0]=2;

console.log(buf2.length);

//buffer.toString()

console.log(buf.toString('base64'));

//buffer.fill()

const buf3=Buffer.allocUnsafe(10);

console.log(buf3);

console.log(buf3.fill(10,2,6));

//buffer.equals()

const buf4=Buffer.from('test');

const buf5=Buffer.from('test');

const buf6=Buffer.from('test!');

console.log(buf4.equals(buf5));

console.log(buf4.equals(buf6));

//buffer.indexOf()

console.log(buf4.indexOf('es'));

console.log(buf4.indexOf('esa'));

执行结果:

15

10

dGhpcyBpcyBhIHRlc3Qh

<Buffer 00 00 00 00 00 00 00 00 01 00>

<Buffer 00 00 0a 0a 0a 0a 00 00 01 00>

true

false

1

-1

buffer的中文乱码问题:

//中文乱码问题

const StringDecoder=require('string_decoder').StringDecoder;

const decoder=new StringDecoder('utf8');

const buf=Buffer.from('中文字符串!');

for(let i=0;i<buf.length;i+=5){

const b=Buffer.allocUnsafe(5);

buf.copy(b,0,i);

console.log(b.toString());

}

for(let i=0;i<buf.length;i+=5){

const b=Buffer.allocUnsafe(5);

buf.copy(b,0,i);

console.log(decoder.write(b));

}

执行结果:

中�

�字�

��串

!╔

文字

符串

!.%�

4-3基础API--events

const EventEmitter=require('events');

class CustomEvent extends EventEmitter{

}

const ce=new CustomEvent();

ce.on('test',()=>{

console.log('this is a test!')

})

setInterval(()=>{

//emit触发事件

ce.emit('test')

},500)

传参的emit

const EventEmitter=require('events');

class CustomEvent extends EventEmitter{}

const ce=new CustomEvent();

//换成once,只调用一次

ce.on('error',(err,time)=>{

console.log(err);

console.log(time);

});

ce.emit('error',new Error('oops!'),Date.now());

执行结果:

Error: oops!

    at Object.<anonymous> (E:\百度云\nodeJS从入门到实战\Node.js入门到企业Web开发中的应用\mycode\event.js:26:17)

    at Module._compile (internal/modules/cjs/loader.js:689:30)

    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)

    at Module.load (internal/modules/cjs/loader.js:599:32)

    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)

    at Function.Module._load (internal/modules/cjs/loader.js:530:3)

    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)

    at startup (internal/bootstrap/node.js:266:19)

    at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)

1535614137791

const EventEmitter=require('events');

class CustomEvent extends EventEmitter{}

function fn1(){

console.log('fn1');

}

function fn2(){

console.log('fn2');

}

const ce=new CustomEvent();

ce.on('test',fn1);

ce.on('test',fn2);

setInterval(()=>{

ce.emit('test');

},500);

setTimeout(()=>{

// ce.removeListener('test',fn2);

// ce.removeListener('test',fn1);

ce.removeAllListeners('test');

},1500);

执行结果:

fn1

fn2

fn1

fn2

4-4 基础API--fs

文件I/O是由简单封装的标准POSIX函数提供.通过require('fs')使用该模块.所有的方法都有异步和同步的形式.

异步方法的最后一个蚕食都是一个回调函数.传给回调函数的参数取决于具体方法,但毁掉函数的第一个参数都会保留给异常.如果操作成功完成,则第一个参数会是null或undefined.

当使用同步方法时,任何异常都会被立即抛出,可以使用try/catch来处理异常,或让异常向上冒泡.

读文件:

const fs=require('fs');

fs.readFile('./readfile.js','utf8',(err,data)=>{

if(err) throw err;

console.log(data);

});

//同步操作,先执行

console.log(fs.readFileSync('event.js','utf8'));

写文件:

const fs=require('fs');

fs.writeFile('./text.txt','this is a test ',{

encoding:'utf8'

},err=>{

if(err) throw err;

console.log('done!')

})

stat:

const fs=require('fs');

fs.stat('./stat.js',(err,stats)=>{

if(err) throw err;

console.log(stats.isFile());

console.log(stats.isDirectory());

console.log(stats);

})

执行结果:

true

false

Stats {

  dev: 1446712323,

  mode: 33206,

  nlink: 1,

  uid: 0,

  gid: 0,

  rdev: 0,

  blksize: undefined,

  ino: 1970324837109880,

  size: 187,

  blocks: undefined,

  atimeMs: 1535681732342.411,

  mtimeMs: 1535681849574.2173,

  ctimeMs: 1535681849574.2173,

  birthtimeMs: 1535681732342.411,

  atime: 2018-08-31T02:15:32.342Z,

  mtime: 2018-08-31T02:17:29.574Z,

  ctime: 2018-08-31T02:17:29.574Z,

  birthtime: 2018-08-31T02:15:32.342Z }

rename:

fs.rename('./text.txt','text',err=>{

if(err) throw err;

console.log('done!')

})

//unlink

// fs.unlink('/text',err=>{});

//readdir 读取上层目录

fs.readdir('../',(err,files)=>{

if(err) throw err;

console.log(files);

})

mkdir,rmdir,watch

//创建文件夹

// fs.mkdir('test',err=>{});

//删除文件夹

// fs.rmdir('./test',err=>{});

//watch

fs.watch('./',{

//是否循环递归

recursive:true,

},(eventType,filename)=>{

console.log(eventType,filename);

})

readstream

const rs=fs.createReadStream('./stat.js');

//方向,往控制台输出

rs.pipe(process.stdout);

writestream

const ws=fs.createWriteStream('./test.txt');

const tid=setInterval(()=>{

const num=parseInt(Math.random()*10);

console.log(num);

if(num<7){

ws.write(num+'');

}else{

clearInterval(tid);

ws.end();

}

},200);

ws.on('finish',()=>{

console.log('done!')

})

回调地域问题:

//回调地域问题

const promisify=require('util').promisify;

const read=promisify(fs.readFile);

// read('/stat.js').then(data=>{

// console.log(data.toString());

// }).catch(ex =>{

// console.log(ex);

// });

async function test() {

try{

const content=await read('./stat.js');

console.log(content.toString());

}catch(ex){

console.log(ex);

}

}

test();

###第五章 项目初始化

静态资源服务器实战

先进入到项目目录下,再执行git init,再进行克隆

.gitignore规则:

        匹配模式前/代表项目根目录

        匹配模式最后加/代表是目录

        匹配模式前加!代表取反

        *代表任意个字符

        ?匹配任意一个字符

        **匹配多及目录

.npmignore:忽略src,test,node_modules

如果没有.npmignore,则自动匹配.gitignore

.editorconfig:定义团队的代码风格

###第六章 静态资源服务器

在app.js中写如下代码:

const http=require('http');

const chalk=require('chalk');

const conf=require('./config/defaultConfig');

const server=http.createServer((req,res)=>{

res.statusCode=200;

res.setHeader('Content-Type','text/html');

res.write('<html>');

res.write('<body>');

res.write('Hello my first server!');

res.write('</body>');

res.write('</html>');

res.end();

});

server.listen(conf.port,conf.hostname,()=>{

const addr=`http://${conf.hostname}:${conf.port}/`;

console.info(`Server started at ${chalk.green(addr)}`);

})

没有chalk模块要先安装,npm i chalk,类型应该是'text/html',代码才会被解读为HTML格式,如果为'text/plain',会解读为文本.

在含有变量的语句中的单引号,应为与波浪线一起的那歌单引号,$才会起作用

全局安装supervisor工具,便于调试,每次修改内容后不必重启服务器

npm i -g supervisor

服务器搭建:

const http=require('http');

const chalk=require('chalk');

const path=require('path');

const fs=require('fs');

const conf=require('./config/defaultConfig');

const server=http.createServer((req,res)=>{

const filePath=path.join(conf.root,req.url);

fs.stat(filePath,(err,stats)=>{

if(err){

res.statusCode=404;

res.setHeader('Content-Type','text/plain');

res.end(`${filePath} is not a directory or file!`);

return

}

if(stats.isFile()){

res.statusCode=200;

res.setHeader('Content-Type','text/plain');

//响应速度慢

// fs.readFile(filePath,(err,data){

// res.end(data);

// })

fs.createReadStream(filePath.pipe(res));

}else if(stats.isDirectory()){

fs.readdir(filePath,(err,files)=>{

res.statusCode=200;

res.setHeader('Content-Type','text/plain');

res.end(files.join(','));

});

}

});

});

server.listen(conf.port,conf.hostname,()=>{

const addr=`http://${conf.hostname}:${conf.port}/`;

console.info(`Server started at ${chalk.green(addr)}`);

})

改造异步调用:

route.js:

const fs=require('fs');

const promisify=require('util').promisify;

const stat=promisify(fs.stat);

const readdir=promisify(fs.readdir);

module.exports=async function (req,res,filePath) {

try{

const stats=await stat(filePath);

if(stats.isFile()){

res.statusCode=200;

res.setHeader('Content-Type','text/plain');

//响应速度慢

// fs.readFile(filePath,(err,data){

// res.end(data);

// })

fs.createReadStream(filePath.pipe(res));

}else if(stats.isDirectory()){

const files=await readdir(filePath);

res.statusCode=200;

res.setHeader('Content-Type','text/plain');

res.end(files.join(','));

}

}catch(ex){

console.error(ex);

res.statusCode=404;

res.setHeader('Content-Type','text/plain');

res.end(`${filePath} is not a directory or file!\n${ex.toString()}`);

}

}

app.js:

const http=require('http');

const chalk=require('chalk');

const path=require('path');

const route=require('./helper/route');

const conf=require('./config/defaultConfig');

const server=http.createServer((req,res)=>{

const filePath=path.join(conf.root,req.url);

route(req,res,filePath);

});

server.listen(conf.port,conf.hostname,()=>{

const addr=`http://${conf.hostname}:${conf.port}/`;

console.info(`Server started at ${chalk.green(addr)}`);

})

模板引擎handlebars:

安装,建立相应的模板:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<meta http-equiv="X-UA-Compatible" content="ie=edge">

<title>{{title}}</title>

<style>

body{

margin:30px;

}

a{

display: block;

font-size:30px;

}

</style>

</head>

<body>

{{#each files}}

<a href="{{../dir}}/{{this}}">{{this}}</a>

{{/each}}

</body>

</html>

修改后的route.js:

const fs=require('fs');

const path=require('path');

const Handlebars=require('handlebars');

const promisify=require('util').promisify;

const stat=promisify(fs.stat);

const readdir=promisify(fs.readdir);

const config=require('../config/defaultConfig')

const tplPath=path.join(__dirname,'../template/dir.hbs');

const source=fs.readFileSync(tplPath);

const template=Handlebars.compile(source.toString());

module.exports=async function (req,res,filePath) {

try{

const stats=await stat(filePath);

if(stats.isFile()){

res.statusCode=200;

res.setHeader('Content-Type','text/plain');

//响应速度慢

// fs.readFile(filePath,(err,data){

// res.end(data);

// })

fs.createReadStream(filePath).pipe(res);

}else if(stats.isDirectory()){

const files=await readdir(filePath);

res.statusCode=200;

res.setHeader('Content-Type','text/html');

const dir=path.relative(config.root,filePath);

const data={

title:path.basename(filePath),

dir:dir?`/${dir}`:'',

files

}

res.end(template(data));

}

}catch(ex){

console.error(ex);

res.statusCode=404;

res.setHeader('Content-Type','text/plain');

res.end(`${filePath} is not a directory or file!\n${ex.toString()}`);

}

}

为了使浏览器按文件的后缀名解析文件,在helper文件夹中新增:mime.js:

const path=require('path');

const mimeTypes={

'css':'text/css',

'gif':'image/gif',

'html':'text/html',

'ico':'image/x-icon',

'jpeg':'image/jpeg',

'jpg':'image/jpeg',

'js':'text/javascript',

'json':'application/json',

'pdf':'application/pdf',

'svg':'image/svg+xml',

'swf':'application/x-shockwave-flash',

'tiff':'image/tiff',

'txt':'text/plain',

'wav':'audio/x-wav',

'wma':'audio-x-ma-wma',

'wmv':'video/x-ms-wmv',

'xml':'text/xml'

};

module.exports=(filePath)=>{

let ext=path.extname(filePath).split('.').pop().toLowerCase();

if(!ext){

ext=filePath;

}

return mimeTypes[ext]||mimeTypes['txt'];

}

并在app.js中做相应的修改.

压缩文件

未完...

猜你喜欢

转载自blog.csdn.net/qq_32054169/article/details/84889464