node
node是运行环境,采取commonJS规范,输出模块:moudle.export,引入模块:require()
-
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
-
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
优点:轻量可扩展、非阻塞IO、高并发访问、单线程多进程
node的运行机制
- V8引擎解析JavaScript脚本。
- 解析后的代码,调用Node API。
- libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
- V8引擎再将结果返回给用户。
node模块类型
- 内置模块:node原生提供的功能,比如fs、http等,这些模块在node进程起来时就加载了
- 文件模块:第三方模块,node_modules下面的文件都是文件模块
node模块加载顺序require(X)
- 优先加载内置模块,即使有同名文件,也会优先使用内置模块。
- 不是内置模块,先去缓存找。
- 缓存没有就去找对应路径的文件。
- 不存在对应的文件,就将这个路径作为文件夹加载。
- 对应的文件和文件夹都找不到就去node_modules下面找。
- 还找不到就报错了。
加载文件夹顺序
前面提到找不到文件就找文件夹,但是不可能将整个文件夹都加载进来,加载文件夹的时候也是有一个加载顺序的:
- 先看看这个文件夹下面有没有
package.json,如果有就找里面的main字段,main字段有值就加载对应的文件。所以如果大家在看一些第三方库源码时找不到入口就看看他package.json里面的main字段吧,比如jquery的main字段就是这样:"main": "dist/jquery.js"。 - 如果没有
package.json或者package.json里面没有main就找index文件。 - 如果这两步都找不到就报错了。
require支持文件类型
- .js:.js文件是我们最常用的文件类型,加载的时候会先运行整个JS文件,然后将前面说的module.exports作为require的返回值。
- .json:.json文件是一个普通的文本文件,直接用JSON.parse将其转化为对象返回就行。
- .node:.node文件是C++编译后的二进制文件,纯前端一般很少接触这个类型。
node事件循环
Node端事件循环中的异步队列也是这两种:macro(宏任务)队列和 micro(微任务)队列。
- 常见的 macro-task
setTimeout、setInterval、setImmediate、script(整体代码)、I/O 操作 - 常见的 micro-task
process.nextTick、new Promise().then(回调)
微任务和宏任务在Node的执行顺序
Node 10以前:
- 执行完一个阶段的所有宏任务
- 执行完nextTick队列里面的内容
- 然后执行完微任务队列的内容
Node 11以后:
- 和浏览器的行为统一了,都是每执行一个宏任务就执行完微任务队列。
node大体的task(宏任务)执行顺序是这样的:
- timers 定时器:本阶段执行已经安排的 setTimeout() 和 setInterval() 的回调函数,并且是由 poll 阶段控制的。
- pending callbacks 待定回调:执行延迟到下一个循环迭代的 I/O 回调。
- idle, prepare:仅系统内部使用。
- poll 轮询:执行 I/O 回调,回到 timer 阶段执行回调。
- check 检测:setImmediate() 回调函数在这里执行。
- close callbacks 关闭的回调函数:一些准备关闭的回调函数,如:
socket.on('close', ...)
app.use和app.get区别
- app.use(path,callback)中的callback既可以是router(路由)对象又可以是函数
- app.get(path,callback)中的callback只能是函数
path模块的__dirname 和 __filename
__dirname 和 __filename 是模块中 的一个内置成员,他们分别是:
- __dirname 是当前文件夹的绝对路径
- __filename是当前文件的绝对路径
exports 和 module.exports 的区别
- 每一个模块中都有一个 module 对象, module 对象中有一个 exports 对象
- 我们可以把需要导出的成员都放到 module.exports 这个接口对象中,也就是 module.exports.xxx = xxx 的方式
- module.exports.xxx = xxx 的方式等价于 exports.xxx = xxx
- 当一个模块需要导出单个成员的时候,必须要使用 module.exports. = xxx
- 因为每个模块最终向外 return 的是 module.exports,而 exports 只是 module.exports 的一个引用,所以exports.xxx = xxx 赋值,也不会影响 module.exports
请问创建子进程的方法有哪些,简单说一下它们的区别
创建子进程的方法大致有:
- spawn(): 启动一个子进程来执行命令
- exec(): 启动一个子进程来执行命令,与spawn()不同的是其接口不同,它有一个回调函数获知子进程的状况
- execFlie(): 启动一个子进程来执行可执行文件
- fork(): 与spawn()类似,不同电在于它创建Node子进程需要执行js文件
- spawn()与exec()、execFile()不同的是,后两者创建时可以指定timeout属性设置超时时间,一旦创建的进程超过设定的时间就会被杀死
- exec()与execFile()不同的是,exec()适合执行已有命令,execFile()适合执行文件。
为什么会有Event Loop
JavaScript的任务分为两种同步和异步,它们的处理方式也各自不同,同步任务是直接放在主线程上排队依次执行,异步任务会放在任务队列中,若有多个异步任务则需要在任务队列中排队等待,任务队列类似于缓冲区,任务下一步会被移到调用栈然后主线程执行调用栈的任务。(处理ajax请求的方式是异步的)
// 例子
function test () {
console.log('start')
setTimeout(() => {
console.log('children2')
Promise.resolve().then(() => {console.log('children2-1')})
}, 0)
setTimeout(() => {
console.log('children3')
Promise.resolve().then(() => {console.log('children3-1')})
}, 0)
Promise.resolve().then(() => {console.log('children1')})
console.log('end')
}
test()
// 以上代码在node11以下版本的执行结果(先执行所有的宏任务,再执行微任务)
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1
// 以上代码在node11及浏览器的执行结果(顺序执行宏任务和微任务)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1
说一下事件循环eventloop
1)所有同步任务都在主线程上执行,形成一个执行栈
2)当主线程中的执行栈为空时,检查事件队列是否为空,如果为空,则继续检查;如不为空,则执行3
3)取出任务队列的首部,加入执行栈
4)执行任务
5)检查执行栈,如果执行栈为空,则跳回第 2 步;如不为空,则继续检查
node中间件(中间层)
node中间件指的是封装http请求细节处理的方法。引入中间件来简化和隔离基础设施与业务逻辑之间的细节,提升开发效率
ip过滤、查询字符串传递、请求体解析、cookie信息处理、权限验证、日志记录、会话管理中间件(session)、gzip压缩中间件(如compress) 、异常处理
- 代理:在开发环境下,我们可以利用代理来,解决最常见的跨域问题;在线上环境下,我们可以利用代理,转发请求到多个服务端
- 缓存:缓存其实是更靠近前端的需求,用户的动作触发数据的更新,node中间层可以直接处理一部分缓存需求
- 限流:node中间层,可以针对接口或者路由做响应的限流
- 日志:相比其他服务端语言,node中间层的日志记录,能更方便快捷的定位问题(是在浏览器端还是服务端)
- 监控:擅长高并发的请求处理,做监控也是合适的选项
- 鉴权:有一个中间层去鉴权,也是一种单一职责的实现
- 路由:前端更需要掌握页面路由的权限和逻辑
- 服务端渲染:node中间层的解决方案更灵活,以SSR来说,在服务端将页面渲染好,可以加快用户的首屏加载速度,避免请求时白屏,还有利于网站做SEO,他的好处是比较好理解的
中间层怎样做的请求合并转发
- 使用express中间件multifetch可以将请求批量合并
- 使用express+http-proxy-middleware实现接口代理转发
- 不使用用第三方模块手动实现一个nodejs代理服务器,实现请求合并转发
①搭建http服务器,使用Node的http模块的createServer方法
②接收客户端发送的请求,就是请求报文,请求报文中包括请求行、请求头、请求体
③将请求报文发送到目标服务器,使用http模块的request方法
使用NPM有哪些好处?
通过NPM,你可以安装和管理项目的依赖,并且能够指明依赖项的具体版本号。 对于Node应用开发而言,你可以通过package.json文件来管理项目信息,配置脚本, 以及指明项目依赖的具体版本。
封装node中间件
const http = require('http')
function compose(middlewareList) {
return function (ctx) {
function dispatch (i) {
const fn = middlewareList[i]
try {
return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)))
} catch (err) {
Promise.reject(err)
}
}
return dispatch(0)
}
}
class App {
constructor(){
this.middlewares = []
}
use(fn){
this.middlewares.push(fn)
return this
}
handleRequest(ctx, middleware) {
return middleware(ctx)
}
createContext (req, res) {
const ctx = {
req,
res
}
return ctx
}
callback () {
const fn = compose(this.middlewares)
return (req, res) => {
const ctx = this.createContext(req, res)
return this.handleRequest(ctx, fn)
}
}
listen(...args) {
const server = http.createServer(this.callback())
return server.listen(...args)
}
}
module.exports = App