【前端知識體系-NodeJS相關】NodeJS高頻前端面試題整理


1. 為什么JavaScript是單線程?

  • 防止DOM渲染沖突的問題;
  • Html5中的Web Worker可以實現多線程

2.什么是任務隊列?

任務隊列"是一個先進先出的數據結構,排在前面的事件,優先被主線程讀取。主線程的讀取過程基本上是自動的,只要執行棧一清空,"任務隊列"上第一位的事件就自動進入主線程。

2.1 同步和異步任務

  1. 同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務;
  2. 異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執行了,該任務才會進入主線程執行。

2.2 執行流程

  1. 所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
  2. 主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
  3. 一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
  4. 主線程不斷重復上面的第三步。

3. 什么是事件循環(EventLoop)?

主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop(事件循環)。

3.1 定時器函數的基本使用方法對比?

  • setTimeout : 只是將事件插入了"任務隊列",必須等到當前代碼(執行棧)執行完,主線程才會去執行它指定的回調函數
  • process.nextTick : 在當前"執行棧"的尾部----下一次Event Loop(主線程讀取"任務隊列")之前----觸發回調函數。(所有的異步任務被觸發之前執行)
  • setImmediate:在當前"任務隊列"的尾部添加事件,也就是說,它指定的任務總是在下一次Event Loop時執行,這與setTimeout(fn, 0)很像。

3.2 setImmediate和setTimeout 哪個回調函數先執行呢?

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

[!NOTE]
上面代碼中,setImmediate和setTimeout被封裝在一個setImmediate里面,它的運行結果總是1--TIMEOUT FIRED--2,這時函數A一定在timeout前面觸發。至於2排在TIMEOUT FIRED的后面(即函數B在timeout后面觸發),是因為setImmediate總是將事件注冊到下一輪Event Loop,所以函數A和timeout是在同一輪Loop執行,而函數B在下一輪Loop執行。

3.3 process.nextTick和setImmediate的區別?

多個process.nextTick語句總是在當前"執行棧"一次執行完,多個setImmediate可能則需要多次loop才能執行完。

4. 說一下NodeJS的運行機制?

  1. V8引擎解析JavaScript腳本。
  2. 解析后的代碼,調用Node API。
  3. libuv庫負責Node API的執行。它將不同的任務分配給不同的線程,形成一個Event Loop(事件循環),以異步的方式將任務的執行結果返回給V8引擎。
  4. V8引擎再將結果返回給用戶。

5. Node創建線程的方法和區別?

5.1 Node的單線程

  1. Node.js 是以單線程的模式運行的,但它使用的是事件驅動來處理並發,這樣有助於我們在多核 cpu 的系統上創建多個子進程,從而提高性能。

  2. 每個子進程總是帶有三個流對象:child.stdin, child.stdout 和child.stderr。他們可能會共享父進程的 stdio 流,或者也可以是獨立的被導流的流對象。

  3. Node 提供了 child_process 模塊來創建子進程

5.2 創建進程的方法

  1. exec - child_process.exec 使用子進程執行命令,緩存子進程的輸出,並將子進程的輸出以回調函數參數的形式一次性返回。exec方法會從子進程中返回一個完整的buffer。默認情況下,這個buffer的大小應該是200k。如果子進程返回的數據大小超過了200k,程序將會崩潰,同時顯示錯誤信息“Error:maxBuffer exceeded”。你可以通過在exec的可選項中設置一個更大的buffer體積來解決這個問題,但是你不應該這樣做,因為exec本來就不是用來返回很多數據的方法。

  2. spawn - child_process.spawn 使用指定的命令行參數創建新進程。spawn 會返回一個帶有stdout和stderr流的對象。你可以通過stdout流來讀取子進程返回給Node.js的數據。stdout擁有’data’,’end’以及一般流所具有的事件。當你想要子進程返回大量數據給Node時,比如說圖像處理,讀取二進制數據等等,你最好使用spawn方法。

  3. fork - child_process.fork 是 spawn()的特殊形式,用於在子進程中運行的模塊,如 fork(‘./son.js’) 相當於 spawn(‘node’, [‘./son.js’]) 。與spawn方法不同的是,fork會在父進程與子進程之間,建立一個通信管道,用於進程之間的通信。

5.3 實例分析

5.3.1 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);
  });

5.3.2 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);
          }
      });
  });

5.3.3 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'});

6. 介紹一下express或koa框架的基本架構?

  1. Express 是一個Node.js的基礎框架,主要基於 Connect 中間件,並且自身封裝了路由(需要配合bodyParser)、視圖處理等功能,使用人數眾多,弊端是callback回調方式。

  2. Koa 是一個比Express更精簡,使用node新特性的中間件框架。其提供的是一個架子,而幾乎所有的功能都需要由第三方中間件完成,比如koa-router, koa-view等。

[!NOTE]
Koa 利用 co 作為底層運行框架,利用 Generator 的特性,實現“無回調”的異步處理

6.1 處理路由

6.1.1 Express

[!NOTE]
使用 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);

6.1.2 Koa

路由處理 Express 是自身集成的,而 Koa 需要引入中間件

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

6.2 HTTP Request

[!NOTE]
兩個框架都封裝了HTTP Request對象,有一點不同是 Koa v1 使用 this 取代 Express 的 req、res。

6.2.1 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)
  })

6.2.2 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)
  }))

6.3 區別

6.3.1 異步流程控制

Express 采用 callback 來處理異步,Koa v1 采用 generator,Koa v2 采用 async/await。

6.3.2 錯誤處理

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)
    }
  })

6.3.3 中間件處理

  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';
  });

[!NOTE]
當koa處理中間件遇到await next()的時候會暫停當前中間件進而處理下一個中間件,最后再回過頭來繼續處理剩下的任務


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM