NodeJS


NodeJS是什么

  • Node.js is a JavaScript runtime built on Chrome’s V8 ( JS运行时 )
  • Node.js uses an event-driven , non-blocking I/O model ( 事件驱动、非阻塞I/O )

NodeJS的单线程

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

补充知识:

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

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

NodeJS常用场景

  • Web Server
  • 本地代码构建
  • 实用工具开发

环境

  • CommonJS
    • 每个文件都是一个模块,有自己的作用域
    • 在模块内部 module 变量代表模块本身
    • module.exports 属性代表模块对外的接口
  • global
  • process

require 规则

  • / 表示绝对路径,./表示相对于当前文件的路径
  • 支持 js、json、node扩展名,不写就依次尝试
  • 不写路径只写名字则认为是 build-in 模块 或者各级 node_modules

require 特性

  • module 被加载的时候执行,加载后缓存。(第一次加载的时候会执行代码,第二次就不再执行代码而是被缓存下来)
  • 一旦出现某个模块被循环加载,就只输出已经执行的部分,还未执行的部分不会输出

module.exports 和 exports的区别

1
2
3
4
5
6
7
const exports=module.exports; //exports是module.exports的快捷方式
exports.test = 100; //可以利用exports给模块添加属性
exports ={
a:1,
b:2,
test:100
};//修改了exports的指向,此时,exports和 module.exports 没有关系

global 全局对象

  • CommonJS
  • Buffer、process、console
  • timer

基础API

  • path
    • 和路径有关的一切
    • path.basename path.extname path.delimiter path.sep path.format path.parse path.resolve path.join path.normalize

注意点:

1. dirname、 filename总是返回文件的绝对路径

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

3.对于”./“ ,在require方法中总是相对当前文件所在的文件夹;在其他地方和 process.cwd()一样,相对 node 启动文件夹

  • Buffer

    • 挂载在全局对象global上

    • Buffer 用于处理二进制数据流

    • 实例类似整数数组,大小固定
    • C++ 代码在V8堆外分配物理内存
  • events

    • 所有能触发事件的对象都是 EventEmitter 类的实例。 这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上。 事件的命名通常是驼峰式的字符串。

    • eventEmitter.on() 用于注册监听器, eventEmitter.emit() 用于触发事件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const EventEmitter = require('events');

    class MyEmitter extends EventEmitter {}

    const myEmitter = new MyEmitter();
    myEmitter.on('event', () => {
    console.log('触发事件');
    });
    myEmitter.emit('event');
  • fs

    • 所有文件系统操作都具有同步和异步的形式。
    • 异步的形式总是将完成回调作为其最后一个参数。 传给完成回调的参数取决于具体方法,但第一个参数始终预留用于异常。 如果操作成功完成,则第一个参数将为 nullundefined
    1
    2
    3
    4
    5
    6
    const fs = require('fs');

    fs.unlink('/tmp/hello', (err) => {
    if (err) throw err;
    console.log('已成功删除 /tmp/hello');
    });

cli 版本号

  • 格式:x.y.z fix bugs的时候升级z位。新增兼容功能的时候升级y位。新增功能,不保证兼容,是新的API的时候升级x位。 如果只是修复bug,需要更新z位。如果是新增了功能,但是向下兼容,需要更新y位。如果有大变动,向下不兼容,需要更新x位。
  • 1.2.*等价于~1.2.0 自动升级z位,x、y位固定
  • 2.x等价于^2.0.0 自动升级y、z位,x位固定,大版本不任意升级
  • ~会匹配最近的小版本依赖包,比如~1.2.3会匹配所有1.2.x版本,但是不包括1.3.0
  • ^会匹配最新的大版本依赖包,比如^1.2.3会匹配所有1.x.x的包,包括1.3.0,但是不包括2.0.0

面试题

1、node有哪些特征,与其他服务器端对比

  特征:单线程、事件驱动、非阻塞I/O

  node 比其他服务端性能更好,速度更快

2、CommonJS中require/exports和ES6中import/export区别

  • 前者不支持动态导入,后者支持
  • 前者是异步导入,因为用于浏览器,需要下载文件,如果采用同步导入会对渲染有很大影响; 后者是同步导入,因为用于服务器,文件都在本地
  • 前者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会随导出值变化;后者在导出时都是值拷贝,就算导出值改变了,导入值也不会改变。如果想更新值,就要重新导入一次
  • ES Module 会编译成 require/exports 来执行

3、谈谈对node.js npm webpack的理解

​ Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。

​ npm全称为Node Package Manager,是一个基于Node.js的包管理器,也是整个Node.js社区最流行、支持的第三方模块最多的包管理器。

​ npm的使用场景:

  • 允许用户获取第三方包并使用。
  • 允许用户将自己编写的包或命令行程序进行发布分享。

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

4、使用npm有哪些好处?

  通过NPM,你可以安装和管理项目的依赖,并且能够指明依赖项的具体版本号,可以通过package.json文件来管理项目信息,配置脚本

5、AMD CMD规范的区别

  CommonJS和AMD都是JavaScript模块化规范

  CMD依赖就近,而AMD依赖前置

  CMD是延迟执行的,而AMD是提前执行的

6、如何判断当前脚本运行在浏览器还是node环境中

  通过判断 Global 对象是否为 window ,如果不为window ,当前脚本没有运行在浏览器中

7、简述同步和异步的区别,如何避免回调地狱

  同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为

  异步方法调用一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中,整个过程,不会阻碍调用者的工作

  避免回调地狱:

  1)Promise

  2)async/await

  3)generator

  4)事件发布/监听模式

  

8、几种常见模块化规范的简介

  • AMD/CMD 主要用于浏览器

    • Asynchronous Module Definition(AMD 异步模块定义) RequireJS在推广过程中对模块定义的规范化产出。特点是依赖前置,会先尽早地执行依赖
    • Common Module Definition(CMD 公共模块定义) SeaJS在推广过程中对模块定义的规范化产出。特点是依赖就近,延迟执行。
  • CommonJS 主要用于服务器

    • NodeJS是CommonJS规范的实现,webpack也是以CommonJS的形式写的
  • ES6 Module 浏览器和服务器通用的模块解决方案

    • 两个命令构成:import 和 export

    • ES6 Module 与 CommonJS的区别

      • 前者不支持动态导入,后者支持

      • 前者是异步导入,因为用于浏览器,需要下载文件,如果采用同步导入会对渲染有很大影响; 后者是同步导入,因为用于服务器,文件都在本地

      • 前者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会随导出值变化;后者在导出时都是值拷贝,就算导出值改变了,导入值也不会改变。如果想更新值,就要重新导入一次

      • ES Module 会编译成 require/exports 来执行

9、app.use和app.get区别

  app.use(path,callback)中的callback既可以是router(路由)对象又可以是函数

  app.get(path,callback)中的callback只能是函数

10、说一下事件循环eventloop

  • 六个阶段:timers,pending callbacks,idle、prepare,poll,check,close callbacks

  • 执行顺序:按阶段顺序执行,并且每一阶段结束都要查询是否存在微任务,若存在,则全部执行(nextTick优先)

  • timers 执行setTimeout、setInterval

  • poll 1.回到timers阶段执行回调2.执行I/O回调

  • check 执行setImmediate

11、node怎么跟MongoDB建立连接

  1)引入mongoose

  2)使用mongoose.connect()方法连接到MongoDB数据库

  3)监听连接是否成功

  4)然后通过node,书写接口,对数据库进行增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var mongoose=require('mongoose');
const url = "mongodb://mallAdmin:winniebear98813@127.0.0.1:27017/mall?authSource=mall"
//连接数据库
mongoose.connect(url,{ useNewUrlParser: true });

//连接不上的时候
mongoose.connection.on('disconnected',()=>{
console.log('MongoDB connected fail');
})

//数据库出现错误的时候
mongoose.connection.on('error',err=>{
console.log(err)
})

//连接打开的时候
mongoose.connection.once('open',()=>{
console.log('MongoDB Connected successfully!')
})

12、KOA

主要是async和await,下面是简单实现一个async函数

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
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch (e) {
return reject(e);
}
if (next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(
function(v) {
step(function() {
return gen.next(v);
});
},
function(e) {
step(function() {
return gen.throw(e);
});
}
);
}
step(function() {
return gen.next(undefined);
});
});
}

Express

路由

  • 支持类型

    • 字符串类型
    • 字符串模式类型
    • 正则表达式类型
    • 参数类型
  • 路由拆分

    • 路由拆分前,无论是新增还是修改路由,都要带着/user前缀,这对于代码的可维护性来说是大忌。
1
2
3
4
5
6
7
8
9
10
11
12
var express = require('express');
var app = express();

app.get('/user/list', function(req, res, next){
res.send('/list');
});

app.get('/user/detail', function(req, res, next){
res.send('/detail');
});

app.listen(3000);
  • 利用express.Router()进行了路由拆分,新增、修改路由都变得极为便利
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var express = require('express');
var app = express();

var user = express.Router();

user.get('/list', function(req, res, next){
res.send('/list');
});

user.get('/detail', function(req, res, next){
res.send('/detail');
});

app.use('/user', user); // mini app,通常做应用拆分

中间件

  • 中间件其实是一个函数,在响应发送之前对请求进行一些操作
  • express内部维护一个函数数组,这个函数数组表示在发出响应之前要执行的所有函数,也就是中间件数组。
  • 使用app.use(fn)后,传进来的fn就会被扔到这个数组里,执行完毕后调用next()方法执行函数数组里的下一个函数,如果没有调用next()的话,就不会调用下一个函数了,也就是说调用就会被终止。
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
//实现原理
function express() {

var funcs = []; // 待执行的函数数组

var app = function (req, res) {
var i = 0;

function next() {
var task = funcs[i++]; // 取出函数数组里的下一个函数
if (!task) { // 如果函数不存在,return
return;
}
task(req, res, next); // 否则,执行下一个函数
}

next();
}

/**
* use方法就是把函数添加到函数数组中
* @param task
*/
app.use = function (task) {
funcs.push(task);
}

return app; // 返回实例
}

模板引擎

  • jade

  • ejs

    • 语法

      • 1.<% code %>用于执行其中javascript代码
      • 2.<%= code %>会对code进行html转义
      • 3.<%- code %> 注释
    • 类似于JSP

      • 四大作用域:page、request、session、application

      • 九大内置对象:page、config、application、request、response、session、out、exception、pageContext

      • 指令

        • 格式:<%@ directive {attribute=value} %>

        • 例子

          • language

            <%@ page language=”java” contentType=”text/html charset=UTF-8” pageEncoding=”UTF-8”%>

          • import <%@ page import=”java.util.List,java.util.ArrayList”%>

          • include <%@ include file=”relativeURL”%>

      • 行为

        • 格式:<jsp:elements {attribute=”value”}/> 
        • <jsp:include />行为
        • <jsp:forward />行为
        • Java bean行为,包括useBean行为、setProperty行为、getProperty行为
      • 语法

        • 1.使用<% 编写java代码 %>,中间java代码必须遵循Java语法
        • 2.使用<%=xxx %>来输出结果
        • 3.使用 <%– –%> 来注释