Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

feibingbalabala/nodejs

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
25 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nodejs

nodejs是什么

Node.js is a JavaScript runtime built on Chrome's V8.

类似java的jre,在chrome's V8运行环境的runtime,语言在不同的运行环境上面跑就需要对应的runtime。

不能使用js的DOM和BOM,这是DOM结构和浏览器的属性,算是一个符合ECMA标准的web开发。

Node.js uses an event-driven,non-blocking I/O model.

非阻塞IO

I/O:input/output

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

非阻塞:I/O时函数立即返回,进程不等待I/O完成(I/O操作程序进程继续,等到I/O结束后通知程序相关操作)

I/O操作时间很慢

事件驱动

I/O等异步操作结束后的通知

观察这模式

适合场景

适合处理高并发、I/O密集场景性能优势明显

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

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

web常见场景

  1. 静态资源读取
  2. 数据库操作
  3. 渲染页面

高并发处理

  1. 增加机器数(负载均衡)
  2. 增加每台机器的CPU数-多核

进程

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

多进程:启用多个进程,多个进程可以一块执行多个任务。

nodeJs工作模型

线程

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

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

nodejs单线程

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

单线程不是单进程

常见场景

  1. web server
  2. 本地代码构建
  3. 使用工具开发

初探

(function (exports, require, module, __filename, __dirname) {
  // nodejs在执行的时候在外包裹了函数
  ...
})

exports(object):模块的输出(对外提供接口、属性)

require(function):需要依赖别的模块的时候所调用的function

module(object):模块本身

__filename:文件的直接路径(绝对地址)

__dirname:文件所在的文件夹路径(绝对地址)

commonJS模块规范

  1. 每个文件是一个模块,有自己的作用域(匿名函数)
  2. 在模块内部module变量代表模块本身
  3. module.exports属性代表模块对外接口

require规则

  1. /绝对路径,./表示相对于当前文件的路径
  2. 支持js、json、node拓展名,不写依次尝试
  3. 不写路径则认为是build-in模块或者各级node_modules内的第三方模块

require特性

  1. 只加载一次,加载后缓存(demo1/04cache.js)
  2. 一旦一个模块被循环加载,就值输出已经执行的部分,还未执行的部分不会加载(demo1/05_main.js)

引用系统内置模块和第三方模块

系统模块(demo1/06_fs.js)

const fs = require('fs');

const result = fs.readFile('./06_fs.js', (err, data) => {
  if (err) {
    console.log(err) // 报错信息,找不到文件
  } else {
    console.log(data.toString()) // 不加toString()会得到一堆二进制的文件
  }
});

console.log(result) // undefined,读取文件是异步的,这时候还没读取

第三方模块

首先npm下载依赖查找本模块,找不到就去node_modules文件找

const chalk = require('chalk')

console.log(chalk.red('red'))

npm root -g 可以知道本电脑所有的全局依赖

model.exports和exports的区别

exports默认是model.exports,但修改了exports,他就不在默认了

global全局变量

CommonJS

buffer:表示二级制数据处理

process:和进程相关挂载在global

const {argv, argv0, execArgv, execPath, env} = process;
// argv启动的参数 例如 node 08_process.js a
// 输出node所在的文件目录,08_process.js的文件目录,a
argv.forEach(item => {
  console.log(item)
});

console.log(argv0) // 输出第一个node所在的文件目录

console.log(execArgv) // 输出在文件名前面的参数

console.log(execPath) // 调用的程序路径

console.log(env) // 当前系统的执行环境

console.log(process.cwd()) // 一个函数,输出当前命令执行所在的文件

console: 打印

timer: 计时器

// 一样有setTimeOut(),setInterval()
setImmediate(() => {

})
// 和时间无关 等下一个事件队列,同步任务执行后执行他

process.nextTick(() => {
  // 会比setImmediate执行早,setTimeOut在他们之间
  // nextTick放在当前事件队列最后,setImmediate下一个事件队列之前
  // nextTick出现循环调用会导致后面的异步无法执行,选择需要慎重
})
// 暴露到全局
global.prop = 100

debug调试

在node命令和文件中间输入 --inspect-brk

例如 node --inspect-brk 10_debug.js

在chrome浏览器中输入chrome://inspect,点击target

node的API

normalize:把不规范的路径处理规范。(demo1/11_normalize.js)

const {normalize} = require('path')

console.log(normalize('/user///local'))

join:把传入的路径拼接起来,本身也会调用normalize。(demo1/12_join.js)

const {join} = require('path')

console.log(join('/user', 'local', 'path1'))

resolve:把相对路径变成绝对路径。(demo2/13_resolve.js)

const {resolve} = require('path');

console.log(resolve('./'))

basename、dirname、extname

const {basename, dirname, extname} = require('path')

const filePath = '/user/local/text.txt'

console.log("basename:", basename(filePath)) // 文件名text.txt
console.log("dirname", dirname(filePath)) // 文件目录/user/local
console.log("extname", extname(filePath)) // 文件拓展名.txt

parse、format

const {parse, format} = require('path');

const filePath = '/user/local/text.jpg'

const parseRes = parse(filePath)

console.log(parseRes)

// { root: '/',
//   dir: '/user/local',
//   base: 'text.jpg',
//   ext: '.jpg',
//   name: 'text'
// }

const formatRes = format(parseRes)

console.log(formatRes) // /user/local\text.jpg

path

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

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

Buffer

buffer用于处理二进制数据流

实例类似整数数组、大小固定

c++代码在v8堆外分配物理内存

console.log(Buffer.alloc(10)) // <Buffer 00 00 00 00 00 00 00 00 00 00>
console.log(Buffer.alloc(20)) // <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>
console.log(Buffer.alloc(5, 1)) // <Buffer 01 01 01 01 01>
console.log(Buffer.allocUnsafe(10)) // <Buffer 19 b7 58 98 bc 03 00 00 00 00>

console.log(Buffer.from([1, 2, 3])) // <Buffer 01 02 03>
console.log(Buffer.from('text')) // <Buffer 74 65 78 74> 默认utf8
console.log(Buffer.from('text', 'base64')) // <Buffer b5 ec 6d>

console.log(Buffer.byteLength('text')) // 4
console.log(Buffer.byteLength('文字')) // 6
// 判断是否二进制
console.log(Buffer.isBuffer({})); // false
console.log(Buffer.isBuffer(Buffer.from([1, 2, 3]))) // true
// 拼接buffer
const buf1 = Buffer.from('jiang')
const buf2 = Buffer.from('wen')
const buf3 = Buffer.from('yong')
const name = Buffer.concat([buf1, buf2, buf3])
console.log(name.toString()) // jiangwenyong
// buffer的长度,和申请空间有关
const buf = Buffer.from('jiangwenyong')
console.log(buf.length); // 12
// 字符串转换
console.log(buf.toString('base64'))
// 填充
const bufFill = Buffer.allocUnsafe(10);
console.log(bufFill) // <Buffer 60 13 bf f4 16 02 00 00 07 00>
console.log(bufFill.fill(10, 1, 9)) // Buffer 60 0a 0a 0a 0a 0a 0a 0a 0a 00>
// 比较buffer大小
const bufEq1 = Buffer.from('a');
const bufEq2 = Buffer.from('a');
const bufEq3 = Buffer.from('b');
console.log(bufEq1.equals(bufEq2)) // true
console.log(bufEq1.equals(bufEq3)) // false
// indexOf
const bufIdx = Buffer.from('abcd');
console.log(bufIdx.indexOf('bc')) // 1
console.log(bufIdx.indexOf('bca')) // -1
// copy方法,解决中文乱码
const { StringDecoder } = require('string_decoder')
const decoder = new StringDecoder('utf8')
const bufCopy = Buffer.from('中文乱码!');
for (let i = 0; i < bufCopy.length; i += 4) {
  const b = Buffer.allocUnsafe(4);
  bufCopy.copy(b, 0, i);
  console.log(b.toString())
}
for (let i = 0; i < bufCopy.length; i += 4) {
  const b = Buffer.allocUnsafe(4);
  bufCopy.copy(b, 0, i);
  console.log(decoder.write(b))
}

events事件

const EventEmitter = require('events')
// 触发一个事件
class CustomEvent extends EventEmitter {}
const ce = new CustomEvent();
ce.on('test', ()=> {
  console.log('test')
})
// setInterval(() => {
//   ce.emit('test')
// }, 500);
// 传递参数
class CustomError extends EventEmitter {}
const cb = new CustomError();
cb.on('error', (err, timer) => {
  console.log(err)
  console.log(timer)
})
// cb.emit('error', new Error('oops!'), Date.now());
// once
class CustomOnce extends EventEmitter {}
const cc = new CustomOnce();
cc.once('once', () => {
  console.log('once')
})
// setInterval(() => {
//   cc.emit('once')
// }, 500);
// remove
class CustomRemove extends EventEmitter {}
function fn1 () {
  console.log('fn1')
}
function fn2 () {
  console.log('fn2')
}
const cd = new CustomRemove()
cd.on('remove', fn1);
cd.on('remove', fn2);
setInterval(() => {
  cd.emit('remove')
}, 500);
setTimeout(() => {
  // cd.removeListener('remove', fn1)
  // cd.removeListener('remove', fn2)
  // 移除全部
  cd.removeAllListeners('remove')
}, 1500);

fs(文件系统)

const fs = require('fs')
// 读取文件
  // 异步
  fs.readFile('./19_fs.js', 'utf8', (err, data) => {
    if (err) throw err;
    console.log('异步', data)
  })
  // 同步
  const data = fs.readFileSync('./19_fs.js')
  console.log('同步', data)
// 写入文件
fs.writeFile('./text', 'hello', {
  encoding: 'utf8'
}, err => {
  if (err) throw err;
  console.log('done')
})
// 文件信息(可用于判断文件是否存在)
fs.stat('./19_fs.js', (err, stats) => {
  if (err) throw err;
  console.log(stats.isFile());
  console.log(stats.isDirectory());
  console.log(stats)
})
// 修改文件名
fs.rename('./text', 'text.txt', err => {
  if (err) throw err;
  console.log('done');
})
// 删除文件
fs.unlink('./text.txt', err => {
  if (err) {
    console.log(err)
  }
  console.log('done')
})
// 读取文件夹
fs.readdir('./', (err, files) => {
  if (err) throw err;
  console.log(files)
})
// 创建文件夹
fs.mkdir('test', err => {})
// 删除文件夹
fs.rmdir('test', err => {})
// 检查文件发生变化
fs.watch('./', {
  recursive: true
}, (eventType, filename) => {
  console.log(eventType, filename)
})

(demo1/19_fs.js)

stream(流)

const fs = require('fs');

const rs = fs.createReadStream('./20_stream.js')

rs.pipe(process.stdout) // 从上往下读取

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

const timer = setInterval(() => {
  const num = parseInt(Math.random() * 10);
  if (num < 8) {
    // 这里写入的必须是字符串
    ws.write(num.toString())
  } else {
    clearInterval(timer)
    ws.end();
  }
}, 200);

ws.on('finish', () => {
  console.log('done')
})

(demo1/20_stream.js)

解决回调地狱

解决A函数中执行B函数回调,B函数中执行C的回调

const fs = require('fs')

const { promisify } = require('util')

const read = promisify(fs.readFile);

// 方法1 promisify

read('./21_promisify.js').then(data => {
  console.log(data.toString())
}).catch(ex => {
  console.log(ex)
})

// async await

async function test () {
  try {
    const content = await read('./21_promisify.js')
    console.log(content.toString())
  } catch (ex) {
    console.log(ex)
  }
}

test()

(demo1/21_promisify.js)

实现一个静态资源服务器跟目录(anywhere)

压缩

在Headers中RequestHeaders的Accept-Encoding: gzip, deflate是浏览器发给服务器的信息,告诉服务器浏览器支持什么压缩格式。Response Header中content-encoding: 代表服务器发给浏览器的压缩格式。

(myanydoor/src/helper/compress)

range范围请求

range: bytes=[start]-[end]

Accept-Ranges: bytes

Content-Range: bytes start-end/total

(myanydoor/src/helper/range)

缓存

Expires(绝对时间), Cache-Control(相对时间): 来判断是否失效的时间

If-Modified-Since / Last-Modified: 服务器每次反问告诉你上次修改的时间

If-None-Match / ETag: 生成一个hash值

(myanydoor/src/helper/cache)

测试

assert-断言

一个简单的测试用例

  // math.js
  module.exports = {
    add: ( ...args ) => {
        return args.reduce((prev, curr) => {
            return prev + curr;
        })
    },

    mul: ( ...args ) => {
        return args.reduce((prev, curr) => {
            return prev * curr;
        });
    }
  }
  // simple.js
  const assert = require('assert');
  const { add, mul } = require('../src/math');

  if (add(2, 3) === 5) {
    console.log('add(2, 3) === 5,ok');
  } else {
    console.log('add(2, 3) !== 5,error');
  }

使用chai,mocha

  npm install chai
  npm install mocha

  // 在pagejson.js的scripts里面使用命令行
  // "test": "mocha ./mocha.js"
  // mocha.js
  const assert = require('assert');
  const { add, mul } = require('../src/math');

  describe('#math', () => {
    describe('add', () => {
        it ('should return 5 when 2 + 3', () => {
            assert.equal(add(2, 3), 5);
        });

        it ('should return -1 when 2 + -3', () => {
            assert.equal(add(2, -3), -1);
        });
    });

    describe('mul', () => {
        it ('should return 6 when 2 * 3', () => {
            assert.equal(mul(2, 3), 6);
        });
    });
  });

测试覆盖率

利用istanbul测试测试用例的执行顺序和执行覆盖率

npm install istanbul

// math.js
function min (a, b) {
    return b * a;
}

module.exports = {
    add: ( ...args ) => {
        return args.reduce((prev, curr) => {
            return prev + curr;
        });
    },

    mul: ( ...args ) => {
        return args.reduce((prev, curr) => {
            return prev * curr;
        });
    },

    cover: (a, b) => {
        if (a > b) {
            return a - b;
        } else if (a === b) {
            return a + b;
        } else {
            return min(a, b)
        }
    }
}

// mocha.js

const assert = require('assert');
const { add, mul, cover } = require('../src/math');

describe('#math', () => {
    describe('add', () => {
        it ('should return 5 when 2 + 3', () => {
            assert.equal(add(2, 3), 5);
        });

        it ('should return -1 when 2 + -3', () => {
            assert.equal(add(2, -3), -1);
        });
    });

    describe('mul', () => {
        it ('should return 6 when 2 * 3', () => {
            assert.equal(mul(2, 3), 6);
        });
    });

    describe('cover', () => {
        it ('should return 6 when cover(2, 3)', () => {
            assert.equal(cover(2, 3), 6);
        });

        it ('should return 1 when cover(3, 2)', () => {
            assert.equal(cover(3, 2), 1);
        });

        it ('should return 1 when cover(2, 2)', () => {
            assert.equal(cover(2, 2), 4);
        });
    });
});

持续集成

1、频繁地将代码集成到主干。

2、每次集成都通过自动化构建来验证。

优点:今早发现错误,防止分支大幅偏离主干

性能

使用benchmark做性能测试,ops/sec每秒操作次数,误差率,越大越好。

npm install benchmark

const Benchmark = require('benchmark');
const suite = new Benchmark.Suite;

suite.add('RefExp#test', function() {
    /o/.test('Hellow World!');
})
.add('String#indexOf', function() {
    'Hello World!'.indexOf('o') > -1;
})
.add('String#match', function() {
    !!'Hello World'.match(/o/);
})
.on('cycle', function(event) {
    console.log(String(event.target));
})
.on('complete', function() {
    console.log('Fastest is ' + this.filter('fastest').map('name'))
})
.run({'async': true})

爬虫

按照一定的规则自动抓取网络信息的程序

反爬虫

1、User-Agent、Referer,验证码。

2、单位时间访问次数、访问量。

3、关键信息图片混淆。

4、异步加载。

puppeteer

const puppeteer = require('puppeteer');
const { screenshot } = require('./config/defult');

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('https://www.baidu.com');
    await page.screenshot({
        path: `${screenshot}/${Date.now()}.png`
    });
    await browser.close();
})()

puppeteer安装失败

安装puppeteer时因为需要下载chrom的缘故总是安装失败提示,执行npm install puppeteer --ignore-scripts 跳过安装chromuin步骤

const puppeteer = require('puppeteer');


(async () => {
    const browser = await puppeteer.launch({
        executablePath: './Chromium'
    });
    const page = await browser.newPage();
    await page.goto('https://www.baidu.com');
    await page.screenshot({
      path: `${screenshot}/${Date.now()}.png`
    });
    browser.close();
})();

搭建一个基于express框架的运行环境

1、安装express

2、通过生成器自动生成目录

3、配置分析

安装

安装一个express-generator生成器

  npm install -g express-generator

新建一个项目

express server

启动

node bing/www
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var ejs = require('ejs');

var indexRouter = require('./routes/index'); // 加载路由模块
var usersRouter = require('./routes/users');

var app = express();

// view engine setup
// app.set('views', path.join(__dirname, 'views')); // 使用什么view模版
// app.set('view engine', 'jade'); // view模版使用什么引擎

// 使用html
app.set('views', path.join(__dirname, 'views/html'));
app.engine('.html', ejs.__express);
app.set('view engine', 'html');

app.use(logger('dev')); // 使用第三方插件
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); // 静态资源路径

app.use('/', indexRouter); // 通过不同地址,访问不同的模块
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) { // 全局404拦截,next是不是什么都拿不到,拿不到404
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) { // 全局错误拦截
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

具体在/demo/express_demo/server/app.js

tip: 修改内容有点麻烦要重启

如果需要使用html模版需要安装ejs

MongoDB

高可拓展性

分布式存储

低成本

结构灵活

安装MongoDB

1、下载安装包或者压缩包

2、添加db存储和日志存储文件夹

3、添加服务、配置环境变量、启用Mongo

配置MongoDB

安装时选用custom

a.在c:\MongoDB(可随意起)下面建一个data文件夹 c:\MongoDB\data。(目前安装的时候会有可视化指定)

b.在c:\MongoDB(可随意起)下面建一个logs文件夹 c:\MongoDB\logs ,在里面建一个文件mongo.log。(目前安装的时候会有可视化指定)

c.在c:\MongoDB(可随意起)下面建一个etc(随意起,放配置文件)文件夹 c:\MongoDB\etc ,在里面建一个文件mongo.conf。(目前安装的时候会有可视化指定)

d.打开mongo.conf文件,修改如下:

数据库路径

  dbpath=c:\MongoDB\data\

日志输出文件路径

  logpath=c:\MongoDB\logs\mongodb.log

错误日志采用追加模式,配置这个选项后mongodb的日志会追加到现有的日志文件,而不是从新创建一个新文件

  logappend=true

启用日志文件,默认启用

  journal=true

这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置为false

  quiet=false

端口号 默认为27017

  port=27017

指定存储引擎(默认先不加此引擎,如果报错了,大家在加进去)

  storageEngine=mmapv1

启动MongoDB

  // 执行看看如果能出现访问端口27017等等,并且通过localhost:27017能出现
  // It looks like you are trying to access MongoDB over HTTP on the native driver port.
  // 访问成功
  // 一定要进入Mongo安装的文件夹的bin目录才可以执行mongod.exe,如果觉得麻烦可以在环境变量上添加Mongo把路劲指向Mongo安装的文件夹的bin目录
  mongod --dbpath c:\MongoDB\data

http://www.imooc.com/article/18438

可以下载一个MongoVUE可视化数据库连接

SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
tablejoins 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

进入Mongo安装的文件夹的bin目录才能执行下列操作

查看目录下面有什么数据库

  show dbs

创建一个集合

// 方法1
db.createCollection("user")
// 方法2
db.user.insert({id:1,name:'jwy'})

查看集合

  show collections

删除数据库demo

  // 首先进入数据库
  use demo
  db.dropDatabase()

删除集合user

  // 首先进入数据库
  use demo
  // db代表当前数据库,user代表集合
  db.user.drop()

插入集合(注意每个命令里面都是对象)

  db.user.insert({userId:1,userName:'jwy',userAge:18,class:{name:'fee',num:12}})

查询集合的内容

  db.user.find()
  // 格式化
  db.user.find().pretty()
  // 查询第一条数据
  db.user.findOne()
  // 查询某条数据
  db.user.find({userName: 'jwy'})
  db.user.find({'class.name':'fee'})
  // 条件查询
  db.user.find({userAge:{$gt:20}}) // 年龄大于20
  // 条件查询
  db.user.find({userAge:{$lt:20}}) // 年龄小于20
  // 条件查询
  db.user.find({userAge:{$eq:20}}) // 年龄等于20
  // 条件查询
  db.user.find({userAge:{$gte:20}}) // 年龄大于等于20

更新集合的内容

  db.user.update({userName:"jwy"},{$set:{userAge:20}})
  // 更新集合内的子内容
  db.user.update({userName:"jwy"},{$set:{'class.num':20}})

删除文档

  db.user.remove({userName:"jwy"})

demo(新建两个集合)

  Goods: {
    productId: String,
    productName: String,
    salePrcie: Number,
    checked: String,
    productNum: Number,
    productImage: String
  }
  Users: {
    userId: String,
    userName: String,
    userPwd: String,
    orderList: Array,
    cartList: [
      {
        productId: String,
        productName: String,
        salePrcie: Number,
        checked: String,
        productNum: Number
      }
    ],
    addressList: [
      {
        addressId: String,
        userNmae: String,
        streetName: String,
        postCode: Number,
        tel: number,
        isDefault: Boolean
      }
    ]
  }
  // 命令导入
  mongoimport -d db_demo -c users --file ...
  -d: 数据库
  -c: 集合
  --file: 导入的文件地址

pm2

PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等。

npm install pm2 -g

pm2 start app.js --watch -i 2

重启

pm2 restart app.js

停止

停止特定的应用。可以先通过pm2 list获取应用的名字(--name指定的)或者进程id。

pm2 stop app_name|app_id

如果要停止所有应用,可以

pm2 stop all

删除

类似pm2 stop,如下

pm2 stop app_name|app_id

pm2 stop all

查看进程状态

pm2 list

在我项目中命令是

  pm2 start demo/express_demo/server/bin/www

About

nodejs学习

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Morty Proxy This is a proxified and sanitized view of the page, visit original site.