1.CommonJS 和 ES6 Module 究竟有什么區(qū)別?
CommonJS
CommonJS 的模塊主要由原生模塊 module 來實(shí)現(xiàn),這個(gè)類上的一些屬性對(duì)我們理解模塊機(jī)制有很大幫助
Module {
id: '.', // 如果是 mainModule id 固定為 '.',如果不是則為模塊絕對(duì)路徑
exports: {}, // 模塊最終 exports
filename: '/absolute/path/to/entry.js', // 當(dāng)前模塊的絕對(duì)路徑
loaded: false, // 模塊是否已加載完畢
children: [], // 被該模塊引用的模塊
parent: '', // 第一個(gè)引用該模塊的模塊
paths: [ // 模塊的搜索路徑
'/absolute/path/to/node_modules',
'/absolute/path/node_modules',
'/absolute/node_modules',
'/node_modules'
]
}
require 從哪里來?
在編寫 CommonJS 模塊的時(shí)候,我們會(huì)使用 require 來加載模塊,使用 exports 來做模塊輸出,還有 module,__filename, __dirname 這些變量,為什么它們不需要引入就能使用?
原因是 Node 在解析 JS 模塊時(shí),會(huì)先按文本讀取內(nèi)容,然后將模塊內(nèi)容進(jìn)行包裹,在外層裹了一個(gè) function,傳入變量
再通過 vm.runInThisContext 將字符串轉(zhuǎn)成 Function形成作用域,避免全局污染。
let wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
const wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
于是在 CommmonJS 的模塊中可以不需要 require,直接訪問到這些方法,變量。
參數(shù)中的 module 是當(dāng)前模塊的的 module 實(shí)例(盡管這個(gè)時(shí)候模塊代碼還沒編譯執(zhí)行),
exports 是 module.exports 的別名,最終被 require 的時(shí)候是輸出 module.exports 的值,
require 最終調(diào)用的也是 Module._load 方法。
__filename,__dirname 則分別是當(dāng)前模塊在系統(tǒng)中的絕對(duì)路徑和當(dāng)前文件夾路徑。
ES6 模塊
ES6 模塊是前端開發(fā)同學(xué)更為熟悉的方式,使用 import, export 關(guān)鍵字來進(jìn)行模塊輸入輸出。ES6 不再是使用閉包和函數(shù)封裝的方式進(jìn)行模塊化,而是從語法層面提供了模塊化的功能。
ES6 模塊中不存在 require, module.exports, __filename 等變量,CommonJS 中也不能使用 import。兩種規(guī)范是不兼容的,一般來說平日里寫的 ES6 模塊代碼最終都會(huì)經(jīng)由 Babel, Typescript 等工具處理成 CommonJS 代碼。
使用 Node 原生 ES6 模塊需要將 js 文件后綴改成 mjs,或者 package.json "type"`` 字段改為 "module",通過這種形式告知Node使用ES Module` 的形式加載模塊。
為什么平時(shí)開發(fā)可以混寫?
前面提到 ES6 模塊和 CommonJS 模塊有很大差異,不能直接混著寫。
這和開發(fā)中表現(xiàn)是不一樣的,原因是開發(fā)中寫的 ES6 模塊最終都會(huì)被打包工具處理成 CommonJS 模塊,以便兼容更多環(huán)境,
同時(shí)也能和當(dāng)前社區(qū)普通的 CommonJS 模塊融合。
2.關(guān)于webpack,babel,以及es6和commonJS之間的聯(lián)系
現(xiàn)在的瀏覽器很多都不支持es6的語法,或者僅僅是部分支持,比如你用.360瀏覽器,你會(huì)發(fā)現(xiàn)它支持let卻不支持箭頭函數(shù)等。
babel就承擔(dān)了“翻譯”的角色,把es6的寫法轉(zhuǎn)換成es5的寫法。
babel轉(zhuǎn)換后的代碼是遵循commonJS規(guī)范的,而這個(gè)規(guī)范,瀏覽器并不能識(shí)別。因此導(dǎo)入到瀏覽器中會(huì)報(bào)錯(cuò),而nodeJS是commonJS的實(shí)現(xiàn)者,所以在babel轉(zhuǎn)換后的代碼是可以在node中運(yùn)行的
為了將babel生成的commonJS規(guī)范的es5寫法能夠在瀏覽器上直接運(yùn)行,我們就借住了webpack這個(gè)打包工具來完成,
因?yàn)閣ebpack本身也是遵循commonJS這個(gè)規(guī)范的,從它的配置文件webpack.config.js中就可以看出來
//module.exports是commonJS的接口輸出規(guī)范,es6的規(guī)范是export
module.exports = {
entry: path.join(__dirname, 'index.js'),
output: {
path: path.join(__dirname, 'outs'),
filename: 'index.js'
},
};
babel:把es6的寫法轉(zhuǎn)換成es5的寫法
webpack:將babel生成的commonJS規(guī)范的es5寫法能夠在瀏覽器上直接運(yùn)行
3.path.resolve和path.join的區(qū)別?
兩者都是拼接文件路徑
path.resolve 獲取絕對(duì)路徑
path.join 獲取在相對(duì)路徑
4.Node.js的Event Loop
除了setTimeout和setInterval這兩個(gè)方法,Node.js還提供了另外兩個(gè)與"任務(wù)隊(duì)列"有關(guān)的方法:process.nextTick和setImmediate。它們可以幫助我們加深對(duì)"任務(wù)隊(duì)列"的理解。
process.nextTick方法可以在當(dāng)前"執(zhí)行棧"的尾部----下一次Event Loop(主線程讀取"任務(wù)隊(duì)列")之前----觸發(fā)回調(diào)函數(shù)。也就是說,它指定的任務(wù)總是發(fā)生在所有異步任務(wù)之前。setImmediate方法則是在當(dāng)前"任務(wù)隊(duì)列"的尾部添加事件,也就是說,它指定的任務(wù)總是在下一次Event Loop時(shí)執(zhí)行,這與setTimeout(fn, 0)很像
process.nextTick(function A() {
console.log(1);
process.nextTick(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED
上面代碼中,由于process.nextTick方法指定的回調(diào)函數(shù),總是在當(dāng)前"執(zhí)行棧"的尾部觸發(fā),所以不僅函數(shù)A比setTimeout指定的回調(diào)函數(shù)timeout先執(zhí)行,而且函數(shù)B也比timeout先執(zhí)行。這說明,如果有多個(gè)process.nextTick語句(不管它們是否嵌套),將全部在當(dāng)前"執(zhí)行棧"執(zhí)行。
setImmediate(function (){
setImmediate(function A() {
console.log(1);
setImmediate(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0);
});
// 1
// TIMEOUT FIRED
// 2
上面代碼中,setImmediate與setTimeout(fn,0)各自添加了一個(gè)回調(diào)函數(shù)A和timeout,都是在下一次Event Loop觸發(fā)。
那么,哪個(gè)回調(diào)函數(shù)先執(zhí)行呢?答案是不確定。運(yùn)行結(jié)果可能是1--TIMEOUT FIRED--2,也可能是TIMEOUT FIRED--1--2。
令人困惑的是,Node.js文檔中稱,setImmediate指定的回調(diào)函數(shù),總是排在setTimeout前面。實(shí)際上,這種情況只發(fā)生在遞歸調(diào)用的時(shí)候。
5.session如何實(shí)現(xiàn)登錄
session 存儲(chǔ)到redis
6.請(qǐng)描述koa2和express的中間件機(jī)制
koa2中間件實(shí)例
koa2的中間件是通過 async await 實(shí)現(xiàn)的,中間件執(zhí)行順序是“洋蔥圈”模型。
中間件之間通過next函數(shù)聯(lián)系,當(dāng)一個(gè)中間件調(diào)用 next() 后,會(huì)將控制權(quán)交給下一個(gè)中間件, 直到下一個(gè)中間件不再執(zhí)行 next() 后, 將會(huì)沿路折返,將控制權(quán)依次交換給前一個(gè)中間件
app.js
const Koa = require('koa');
const app = new Koa();
// logger
app.use(async (ctx, next) => {
console.log('第一層 - 開始')
await next();
const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ----------- ${ctx.url} ----------- ${rt}`);
console.log('第一層 - 結(jié)束')
});
// x-response-time
app.use(async (ctx, next) => {
console.log('第二層 - 開始')
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
console.log('第二層 - 結(jié)束')
});
// response
app.use(async ctx => {
console.log('第三層 - 開始')
ctx.body = 'Hello World';
console.log('第三層 - 結(jié)束')
});
app.listen(3000);
控制臺(tái)輸出:
第一層 - 開始
第二層 - 開始
第三層 - 開始
第三層 - 結(jié)束
第二層 - 結(jié)束
打印第一次執(zhí)行的結(jié)果: GET -------- /text ------ 4ms
第一層 - 結(jié)束
express中間件
與 koa2 中間件不同的是,express中間件一個(gè)接一個(gè)的順序執(zhí)行, 通常會(huì)將 response 響應(yīng)寫在最后一個(gè)中間件中
主要特點(diǎn):
app.use 用來注冊(cè)中間件
遇到 http 請(qǐng)求,根據(jù) path 和 method 判斷觸發(fā)哪些中間件
實(shí)現(xiàn) next 機(jī)制,即上一個(gè)中間件會(huì)通過 next 觸發(fā)下一個(gè)中間件
const express = require('express')
const app = express()
app.use((req, res, next) => {
console.log('第一層 - 開始')
setTimeout(() => {
next()
}, 0)
console.log('第一層 - 結(jié)束')
})
app.use((req, res, next) => {
console.log('第二層 - 開始')
setTimeout(() => {
next()
}, 0)
console.log('第二層 - 結(jié)束')
})
app.use('/api', (req, res, next) => {
console.log('第三層 - 開始')
res.json({
code: 0
})
console.log('第三層 - 結(jié)束')
})
app.listen(3000, () => {
console.log('server is running on port 3000')
})
控制臺(tái)輸出:
第一層 - 開始
第一層 - 結(jié)束
第二層 - 開始
第二層 - 結(jié)束
第三層 - 開始
第三層 - 結(jié)束
const express = require('express')
const app = express()
app.use((req, res, next) => {
console.log('第一層 - 開始')
next()
console.log('第一層 - 結(jié)束')
})
app.use((req, res, next) => {
console.log('第二層 - 開始')
next()
console.log('第二層 - 結(jié)束')
})
app.use('/api', (req, res, next) => {
console.log('第三層 - 開始')
res.json({
code: 0
})
console.log('第三層 - 結(jié)束')
})
app.listen(3000, () => {
console.log('server is running on port 3000')
})
控制臺(tái)輸出:
第一層 - 開始
第二層 - 開始
第三層 - 開始
第三層 - 結(jié)束
第二層 - 結(jié)束
第一層 - 結(jié)束
7.如何讀取一個(gè)1g大小的日志文件

8.nodejs是單線程還是多線程
Node.js的單線程指的是主線程是“單線程”,由主要線程去按照編碼順序一步步執(zhí)行程序代碼,假如遇到同步代碼阻塞,主線程被占用,后續(xù)的程序代碼執(zhí)行就會(huì)被卡住。
nodejs運(yùn)行機(jī)制:
a、V8引擎解析JavaScript腳本
b、解析后的代碼,調(diào)用Node API
c、libuv(c++庫)庫負(fù)責(zé)Node API的執(zhí)行。它將不同的任務(wù)分配給不同的線程,形成一個(gè)Event Loop(事件循環(huán)),以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給V8引擎
d、V8引擎再將結(jié)果返回給用戶
單線程怎么實(shí)現(xiàn)高并發(fā)
每個(gè)Node.js進(jìn)程只有一個(gè)主線程在執(zhí)行程序代碼,形成一個(gè)執(zhí)行棧(execution context stack)。
主線程之外,還維護(hù)了一個(gè)"事件隊(duì)列"(Event queue)。當(dāng)用戶的網(wǎng)絡(luò)請(qǐng)求或者其它的異步操作到來時(shí),node都會(huì)把它放到Event Queue之中,此時(shí)并不會(huì)立即執(zhí)行它,代碼也不會(huì)被阻塞,繼續(xù)往下走,直到主線程代碼執(zhí)行完畢。
主線程代碼執(zhí)行完畢完成后,然后通過Event Loop,也就是事件循環(huán)機(jī)制,開始到Event Queue的開頭取出第一個(gè)事件,從線程池中分配一個(gè)線程去執(zhí)行這個(gè)事件,接下來繼續(xù)取出第二個(gè)事件,再從線程池中分配一個(gè)線程去執(zhí)行,然后第三個(gè),第四個(gè)。主線程不斷的檢查事件隊(duì)列中是否有未執(zhí)行的事件,直到事件隊(duì)列中所有事件都執(zhí)行完了,此后每當(dāng)有新的事件加入到事件隊(duì)列中,都會(huì)通知主線程按順序取出交EventLoop處理。當(dāng)有事件執(zhí)行完畢后,會(huì)通知主線程,主線程執(zhí)行回調(diào),線程歸還給線程池。
3、單線程的好處:
(1)多線程占用內(nèi)存高
(2)多線程間切換使得CPU開銷大
(3)多線程由內(nèi)存同步開銷
(4)編寫單線程程序簡單
(5)線程安全
4、單線程的劣勢:
(1)CPU密集型任務(wù)占用CPU時(shí)間長(可通過cluster方式解決)
(2)無法利用CPU的多核(可通過cluster方式解決)
(3)單線程拋出異常使得程序停止(可通過try catch方式或自動(dòng)重啟機(jī)制解決)
node可以實(shí)現(xiàn)多進(jìn)程
1\. 創(chuàng)建多進(jìn)程的模塊
1.1 child_process
1.2 cluster
2. 創(chuàng)建多進(jìn)程的方法
2.1 child_process有4種方法:
1. spawn: 創(chuàng)建子進(jìn)程,執(zhí)行非node程序,執(zhí)行結(jié)果以流形式返回
2. execFile: 創(chuàng)建子進(jìn)程,執(zhí)行非node程序,執(zhí)行結(jié)果以回調(diào)返回
3. exec: 創(chuàng)建子進(jìn)程,執(zhí)行shell命令,執(zhí)行結(jié)果以回調(diào)返回,可以直接執(zhí)行一串shell命令
4. fork: 創(chuàng)建子進(jìn)程,執(zhí)行node程序,執(zhí)行結(jié)果以流返回
2.2 cluster有1種方法:
cluster.fork:創(chuàng)建一個(gè)子進(jìn)程
3. nodejs多進(jìn)程模型
nodejs的多進(jìn)程是master-work模式,一個(gè)主進(jìn)程中,創(chuàng)建出多個(gè)子進(jìn)程
4\ 進(jìn)程間消息傳遞
通過on('message')監(jiān)聽和send分發(fā)
5. 多個(gè)work監(jiān)聽同一個(gè)端口
nodejs中的多個(gè)work進(jìn)程可以同時(shí)監(jiān)聽同一個(gè)端口。具體實(shí)現(xiàn)方式,child_process和cluster有區(qū)別。
5.1 child_process方式
child_process通常在master進(jìn)程中創(chuàng)建socket或server,
通過send方法將socket或server,發(fā)送到worker進(jìn)程,在worker進(jìn)程中同時(shí)監(jiān)聽一個(gè)端口
5.2 cluster方式
cluster通過cluster.isMaster和cluster.isWorker來區(qū)分主進(jìn)程和worker進(jìn)程,在worker進(jìn)程中,
可以直接啟動(dòng)server,來監(jiān)聽同一個(gè)端口cluster主要是提供了對(duì)多個(gè)子進(jìn)程的管理,
子進(jìn)程的創(chuàng)建、退出和重啟,可以方便的進(jìn)行。
