前端知识总结之node.js篇

1.概念

Node.js 就是运行在服务端的 JavaScript,起初段定位是后端开发语言,由于技术的不够成熟,一般小型项目会完全使用node.js作为后台支撑,大项目中,运行不够稳定,不会轻易使用。具有高并发优良特性,Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好.Node.js是事件驱动,非阻塞式I/O模型,Node.js使用npm包管理器。
使用Node.js不仅实现了一个应用,也实现了整个HTTP服务器,所以不需要单独搭建Apache或nginx。

2.主要模块:

​ 1.fs 文件操作
var fs=require(“fs”)
a,创建文件,并且写入数据

 fs.writeFile("demo.txt","hello,world!",function(err,data){
    if(err)console.log(err)
    console.log("create file success")
})

b重命名文件或者移动文件

fs.rename("demo.txt","demo1.txt",function(err){
//把demo.txt重命名为demo1.txt
    if(err)console.log(err);
    console.log("rename success")
})

fs.rename("demo.txt","demo/demo.txt",function(err){
//把demo.txt移动到demo文件夹下,
    if(err)console.log(err);
    console.log("rename success")
})

c删除文件

fs.unlink("text.txt", function(err) {
  if (err) throw err;
  console.log('successfully deleted');
});

D读取文件

fs.readFile('demo.txt','utf-8',function(err,data){  
//使用utf-8的编码异步读取文件
    if(err){  
        console.log(data);  
    }else{  
        console.log(data);    
    }  
}) 
//使用utf-8的方式同步读取某个文件
var data = fs.readFileSync('demo.txt','utf-8');  
console.log(data);        

​ 2.http ,http服务 ,创建服务器

​ response 响应

​ request 请求

​ 3.global 全局对象(类似于window对象)

​ 4.module(commonJS模块开发工具)

3.运行机制

  1. V8引擎解析JavaScript脚本。
  2. 解析后的代码,调用Node API。
  3. libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
  4. V8引擎再将结果返回给用户。

4.node.js中的javascript

Node.js 中 Javascript 的组成部分

ECMAScript 核心 + 全局成员 + 核心 API 模块

全局成员:console、setInterval、setTimeout。。。

核心 API 模块:就是 Node 平台 单独提供的一些API,这些API是Node平台所独有的;

注意:Node.js 中 没有 BOM 和 DOM

由于 Node 服务器端运行环境中,没有浏览器 和 HTML 的概念,所以,Node中的javascript 提出了 DOM 和 BOM 这两个对象模型,取而代之的,是 全局成员 和 核心 API 模块;

5.常用命令

npm init 然后后面直接按照提示按 y / n , 注意: -y 表示一切默认
安装项目依赖包:

npm install express   

注意: --save 将安装记录写入到package.json的项目依赖中
卸载依赖包:

npm uninstall express 

更新依赖包:

npm update express

搜索依赖包:

npm search express

安装全局淘宝镜像

1npm install -g cnpm --registry=https://registry.npm.taobao.org
//退出环境  
ctrl + c

6.主要的全局对象

与浏览器中window作为全局不同,在node中global是全局对象,我们可以直接在全局对象上定义属性,那么就可以访问到了,指的注意的是,由于每一个nodejs模块都是一个作用域,所以直接var是局部变量,而不是全局变量。

__filename — 这个全局变量表示运行的nodejs的文件名。

__dirname — 表示目录名称(不包含文件名)

其中__filename是包含了路径的。 而__dirname仅仅是缺少了文件名,只有路径。
console、setTimeout、clearInterval等等都是全局对象,

可以看到,由于setInterval和setTimeout是非阻塞的,所以后面的语句先执行,指的注意的是其中的console.log("%d",19950628)的应用,这与C语言中的printf是非常相似的。另外console.trace()可以追踪调用栈。

另外还有一个比较重要的api,即setImmediate(handler); 他是IE10中支持的。其他的浏览器一律不支持,但是node是支持的,这个解决单线程阻塞的问题,当然用setTimeout(handler, 0);也可以,但是后者的延迟时间较前者更长一些。

process也是一个全局变量,在node环境下输入global.process就可以看到其中具有的变量,因为process本身就是一个对象。不难理解process是描述进程(process即进程的意思)的一个全局对象。

他还有一些事件,如下:
process.on(“exit”, function (code) {
console.log(“exitCode is:”, code);
});

即进程一旦结束,就会触发监听器。 注意:这里使用on的方式,所以我们可以认为process是eventEmitter的实例。

退出码为0是什么意思呢? 因为每当exit事件触发,都会有一个code即退出码,表示这个退出的方式,0表示正常退出,一般还有如下几种退出码:

process 不仅提供了上述事件,还提供了非常多的有关进程的属性,如pid(进程号)、platform(程序运行的平台)、archf(当前CPU的架构)、title(进程名,默认为node)、versions(包含了node的版本和依赖)、version(node的版本)、execPath(当前脚本的二进制文件路径)、stdin、stdout、stderr。

:其中的stdout.write是在终端输出,那么什么时候才能在页面上输出呢? 显然,由于node是服务器端语言,所以说只能通过响应(response)的方式才能返回给客户端。

console.log(process.memoryUsage());
console.log(process.cwd());cwd(current working directory)即当前工作目录。
rss、heapTotal、heapUsed表示了内存使用情况。

7. node中的npm:

	安装node时就已经安装了npm,即一个包管理工具,我们一般利用它来安装一些包,即npm install 如果后面添加 -g ,那么就会将包安全到全局环境,即user路径下。如果不添加,就会安装在当前文件夹下,当然首先会创建node_modules文件,在此之下。
	一般,我们要创建一个项目时,我们可以先npm init,通过它,我们就可以创建一个package.json文件,然后通过配置该文件说明我们的项目信息。其中主要的参数有:

(1)name — 包名
(2)version — 版本号
(3) description — 包的描述
(4) homepage — 包的官网url
(5) author — 包的作者名字
(6) contributors — 包的其他贡献者名字
(7)dependencies — 依赖包列表(如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下)
(8)repository — 包代码存放的地方
(9)main — main 字段是一个模块ID,它是一个指向你程序的主要项目。就是说,如果你包的名字叫 express,然后用户安装它,然后require(“express”)
(10) keywords — 关键字

8.node.js的优缺点

• 事件驱动,通过闭包很容易实现客户端的生命活期。
• 不用担心多线程,锁,并行计算的问题
• V8引擎速度非常快
• 对于游戏来说,写一遍游戏逻辑代码,前端后端通用
当然Nodejs也有一些缺点:
• nodejs更新很快,可能会出现版本兼容
• nodejs还不算成熟,还没有大制作
• nodejs不像其他的服务器,对于不同的链接,不支持进程和线程操作
9.odejs事件循环

nodejs事件循环利用的是观察者模式,也就是发布订阅模式。简单的理解,DOM元素绑定事件就是这样的模式。其中绑定的元素是发布者,函数是订阅者,当元素发生了变化时(被点击等),就会通知所有的订阅者。

nodejs使用事件驱动模型,当服务器接受到了请求之后,就会关闭这个请求,然后再处理,为的是等待下一个请求。这样,请求就不会被耽搁。这个模型的效率非常高,因为他一直在接受请求,而没有等待任何读写操作。在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数:

虽然这里没有所谓的DOM元素,但是实现应当是一样的,即观察者模式的最好理解是好莱坞电影中的一句话: 你不要打电话给我,我会打电话给你。

在node中我们常常使用events模块来实现,即首先引入events,然后创建一个对象,利用这个对象的on方法绑定时间,利用对象的emit方法来触发事件,如下所示:

varevents = require("events");vareventEmitter =new events.EventEmitter();
eventEmitter.on("selfDefine", function () {
    console.log("This is selfDefine1");
});
eventEmitter.on("selfDefine", function () {

    console.log("This is selfDefine2");

});
eventEmitter.on("selfDefine", function () {

    console.log("This is selfDefine3");

});

eventEmitter.on("selfDefine", function () {

    console.log("This is selfDefine4");

});
eventEmitter.emit("selfDefine");

console.log("over");

在这里,我们就可以认为这是发布订阅者模式,首先可以知道发布者是selfDefine事件,订阅者是4个,一旦selfDefine被触发,那么就会通知订阅者,方法是将订阅者添加到了Event Loop中去,然后通过事件循环来监听,一旦被触发,就会通知,即我给你打电话,你没有给我打电话。

当事件触发时,注册到这个事件的事件监听器被依次调用。
另外,在on和emit中是可以传递参数的,如下所示:

varevents = require("events");vareventEmitter =new events.EventEmitter();

eventEmitter.on("selfDefine", function (x) {
    console.log("This is selfDefine1 "+ x);
});
eventEmitter.on("selfDefine", function (x) {
    console.log("This is selfDefine2 "+ x);
});

eventEmitter.on("selfDefine", function (x) {

    console.log("This is selfDefine3 "+ x);

});

eventEmitter.on("selfDefine", function (x) {

    console.log("This is selfDefine4 "+ x);

});

eventEmitter.emit("selfDefine","argument");

console.log("over");

关于 EventEmitter 还有其他的属性,如下所示:

addListener(event, listener) — 它和on是类似的,都是添加某一个事件的监听器。

removeListener(event, listener) — 即通过此API可以将监听器取消(特定的listener)。

removeAllListeners(event) — 可以取消event下的所有监听器。

newListener(event, listener); — 该事件在添加新的监听器时被触发。

listenerCount(emitter, event); — 返回指定监听器的数量。

listeners(event) — 返回指定事件的监听器数组。

once(event, listener) — 通过once就可以知道,这个监听器只会监听一次,后面再调用,就不会监听了。

举例如下:

varevents = require("events");vareventEmitter =new events.EventEmitter();

eventEmitter.on("foo", function () {

    console.log("via on");

});

eventEmitter.once("foo", function () {

    console.log("via once");

});

eventEmitter.emit("foo");

setTimeout(function () {

    eventEmitter.emit("foo");

}, 1000);

在执行过程中vai on和via once是同时出现的,过了1s之后,via on 出现, via once不再出现,因为通过once添加的监听器只会监听一次,然后就被销毁了(即后面不再监听)。

10.Node模块机制

Node中,每个文件模块都是一个对象,它的定义如下:

function Module(id, parent) {
 this.id = id;
 this.exports = {};
 this.parent = parent;
 this.filename = null;
 this.loaded = false;
 this.children = [];
} 
module.exports = Module;

var module = new Module(filename, parent);
所有的模块都是 Module 的实例。可以看到,当前模块(module.js)也是 Module 的一个实例。

11. require的模块加载机制

1、先计算模块路径
2、如果模块在缓存里面,取出缓存
3、加载模块
4、的输出模块的exports属性即可

	// require 其实内部调用 Module._load 方法
Module._load = function(request, parent, isMain) {
 // 计算绝对路径
 var filename = Module._resolveFilename(request, parent);
 
 // 第一步:如果有缓存,取出缓存
 var cachedModule = Module._cache[filename];
 if (cachedModule) {
 return cachedModule.exports;
 
 // 第二步:是否为内置模块
 if (NativeModule.exists(filename)) {
 return NativeModule.require(filename);
 }
  
 /********************************这里注意了**************************/
 // 第三步:生成模块实例,存入缓存
 // 这里的Module就是我们上面的1.1定义的Module
 var module = new Module(filename, parent);
 Module._cache[filename] = module;
 
 /********************************这里注意了**************************/
 // 第四步:加载模块
 // 下面的module.load实际上是Module原型上有一个方法叫Module.prototype.load
 try {
 module.load(filename);
 hadException = false;
 } finally {
 if (hadException) {
  delete Module._cache[filename];
 }
 } 
 // 第五步:输出模块的exports属性
 return module.exports;
};

12.任务队列?

12.1任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。
12.1 同步和异步任务#

  1. 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
  2. 异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
    12.2 执行流程#
  3. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  4. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  5. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  6. 主线程不断重复上面的第三步。

13.express或koa框架的基本架构

区别
一、框架

  1. Express 是一个Node.js的基础框架,主要基于 Connect 中间件,并且自身封装了路由(需要配合bodyParser)、视图处理等功能,使用人数众多,弊端是callback回调方式。
  2. Koa 是一个比Express更精简,使用node新特性的中间件框架。其提供的是一个架子,而几乎所有的功能都需要由第三方中间件完成,比如koa-router, koa-view等。
    [!NOTE]
    Koa 利用 co 作为底层运行框架,利用 Generator 的特性,实现“无回调”的异步处理

二、处理路由#
Express使用 express.Router 类来创建可安装的模块化路由处理程序。Router 实例是完整的中间件和路由系统,以下示例将路由器创建为模块,在其中装入中间件,定义一些路由,然后安装在主应用程序的路径中。

var express = require('express');
  var router = express.Router();
  router.use(function timeLog(req, res, next) {
    console.log('Time: ', Date.now());
    next();
  });
  // define the home page route
  router.get('/', function(req, res) {
    res.send('Birds home page');
  });
  // define the about route
  router.get('/about', function(req, res) {
    res.send('About birds');
  });
  module.exports = router;

接着,在应用程序中装入路由器模块:

 var routes = require('./route');
  ...
  app.use('/route', routes);

Koa路由处理Koa 需要引入中间件

var koa = require('koa')
var route = require('koa-route') //中间件
var app = koa()
app.use(route.get('/', function *(){
  this.body = 'Hello World'
}))

三、 HTTP Request
两个框架都封装了HTTP Request对象,有一点不同是 Koa v1 使用 this 取代 Express 的 req、res。
Express:

 var app = require('express')()
  app.get('/room/:id', function (req, res) {
    console.log(req.params)
  })
  // 获取POST数据需要 body-parser 中间件
  var bodyParser = require('body-parser')
  app.use(bodyParser.json())
  app.post('/sendgift', function (req, res) {
    console.log(req.body)
  })

Koa:

var app = require('koa')()
  var route = require('koa-route')
  app.use(route.get('/room/:id', function *() {
    console.log(this.req.query)
  }))
  // 获取POST数据需要 co-body 中间件
  var parse = require('co-body')
  app.use(route.post('/sendgift', function *() {
    var post = yield parse(this.request)
    console.log(post)
  }))

三 异步流程控制#
Express 采用 callback 来处理异步,
Koa v1 采用 generator,
Koa v2 采用 async/await。
四、错误处理
Express 使用 callback 捕获异常,对于深层次的异常捕获不了,

Koa 使用 try catch,能更好地解决异常捕获。

  // Express callback
  app.use(function (err, req, res, next) {
    console.error(err.stack)
    res.status(500).send('Something broke!')
  })
  // Koa generator
  app.use(function *(next) {
    try {
      yield next
    } catch (err) {
      this.status = err.status || 500
      this.body = { message: err.message }
      this.app.emit('error', err, this)
    }
  })
  // Koa async/await
  app.use(async (ctx, next) => {
    try {
      await next()
    } catch (err) {
      ctx.status = err.status || 500
      ctx.body = { message: err.message }
      ctx.app.emit('error', err, this)
    }
  })

五、中间件处理

  1. Express中app.use就是往中间件数组中塞入新的中间件,中间件处理方式是线性的,next过后继续寻找下一个中间件。 一个请求进来经过一系列中间件处理后再响应给用户,清晰明了。
  2. 缺点:基于 callback 组合业务逻辑,业务逻辑复杂时嵌套过多,异常捕获困难。
  3. Koa的中间件处理方式是一个洋葱模型,koa处理完中间件后还会回来走一趟,这就给了我们更加大的操作空间。

const Koa = require(‘koa’);
const app = new Koa();
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set(‘X-Response-Time’, ${ms}ms);
});
// logger
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(${ctx.method} ${ctx.url} - ${ms});
});

// response
app.use(async ctx => {
ctx.body = ‘Hello World’;

14. Node创建线程的方法和区别

一、Node的单线程:

  1. Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。
  2. 每个子进程总是带有三个流对象:child.stdin, child.stdout 和child.stderr。他们可能会共享父进程的 stdio 流,或者也可以是独立的被导流的流对象。
  3. Node 提供了 child_process 模块来创建子进程
    二、创建进程的方法
  4. exec - child_process.exec 使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式一次性返回。exec方法会从子进程中返回一个完整的buffer。默认情况下,这个buffer的大小应该是200k。如果子进程返回的数据大小超过了200k,程序将会崩溃,同时显示错误信息“Error:maxBuffer exceeded”。你可以通过在exec的可选项中设置一个更大的buffer体积来解决这个问题,但是你不应该这样做,因为exec本来就不是用来返回很多数据的方法。
  5. spawn - child_process.spawn 使用指定的命令行参数创建新进程。spawn 会返回一个带有stdout和stderr流的对象。你可以通过stdout流来读取子进程返回给Node.js的数据。stdout拥有’data’,’end’以及一般流所具有的事件。当你想要子进程返回大量数据给Node时,比如说图像处理,读取二进制数据等等,你最好使用spawn方法。
  6. fork - child_process.fork 是 spawn()的特殊形式,用于在子进程中运行的模块,如 fork(‘./son.js’) 相当于 spawn(‘node’, [‘./son.js’]) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。
    实例分析
    exec:
 require('child_process').exec('dir', {encoding: ‘utf-8’}, function(err, stdout, stderr) {
      if (err) {
          console.log(error.stack);
          console.log('Error code: ' + error.code);
          console.log('Signal received: ' + error.signal);
      }
      //console.log(err, stdout, stderr);
      console.log('data : ' + stdout);
  }).on('exit', function (code) {
      console.log('子进程已退出, 退出码 ' + code);
  });

spawn:

 var child_process = require('child_process');
  var spawnObj = child_process.spawn('ping', ['127.0.0.1'], {encoding: 'utf-8'});
  spawnObj.stdout.on('data', function(chunk) {
      console.log(chunk.toString());
  });
  spawnObj.stderr.on('data', (data) => {
    console.log(data);
  });
  spawnObj.on('close', function(code) {
      console.log('close code : ' + code);
  }
  spawnObj.on('exit', (code) => {
      console.log('exit code : ' + code);
      fs.close(fd, function(err) {
          if(err) {
              console.error(err);
          }
      });
  });

fork:
分为 “父进程”(parent.js) 和”子进程”(child.js)。在命令行执行的时候要切换到上述文件的目录中,否则会找不到子进程。

//parent.js
  console.log('parent pid: ' + process.pid);
  var fork = require('child_process').fork;
  //fork方法返回的是子进程
  var child = fork('./child.js');
  console.log('fork return pid: ' + child.pid);
  child.on('message', function(msg){
      console.log('parent get message: ' + JSON.stringify(msg));
  });
  child.send({key: 'parent value'});
//child.js:
  console.log('child pid: ' + process.pid);
  process.on('message', function(msg){
      console.log('child get message: ' + JSON.stringify(msg));
  });
  process.send({key: 'child value'});

15、什么是stub

• stub用于模块的行为。测试时,stub可以为函数调用返回模拟的结果。比如说,我们写文件时,实际上并不需要真正去写。

 var fs = require('fs');
  var writeFileStub = sinon.stub(fs, 'writeFile', function(path, data, cb)	 
  {
      return cb(null);
  }); 
      expect(writeFileStub).to.be.called;	 
      writeFileStub.restore();

16、测试金字塔

• 测试金字塔反应了需要写的单元测试,集成测试以及端到端测试的比例:
• 测试HTTP接口时应该是这样的:
• 很多单元测试,分别测试各个模块(依赖需要stub)
• 较少的集成测试,测试各个模块之间的交互(依赖不能stub)
• 少量端到端测试,去调用真正地接口(依赖不能stub)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值