node!!!
node.js
Node是搞后端的,不應該被被歸為前端,更不應該用前端的觀點去理解,去面試node開發人員。所以這份面試題大全,更側重后端應用與對Node核心的理解。
?
github地址:?https://github.com/jimuyouyou/node-interview-questions
注: 這是本人歷時一星期完成的小作品,github里面對一些關鍵代碼還有js源文件.直接node filename.js就可查看效果.
? ? ?第一個版本,寫的匆忙,能力有限,歡迎拍磚補充!后面持續更新會及時發布到github上.
node開發技能圖解
起源
- node正風生火起,很多介紹卻停留在入門階段,無法投入生產
- node相關的高質量面試題更是少之又少,很難全面考查應聘者的node能力
- 許多文章在講第三方類庫,可是這些庫質量差距較大,一旦遇到問題怎么辦
- 必需的,全面了解node核心才能成為一名合格的node開發人員
目標與原則
- 前后端兼顧,更側重后端
- 理論實戰兼顧,側重考察對實戰中應用較多的理論的理解
- 參考答案簡單明了,一針見血,不為追求嚴謹而浪費口舌,繞彎子
- 盡量用代碼講清理論的應用與區別,以接地氣
- 終極目標是讓大家對node有一個快速完整的認識
內容大綱
- javascript高級話題(面向對象,作用域,閉包,設計模式等)
- node核心內置類庫(事件,流,文件,網絡等)
- node高級話題(異步,部署,性能調優,異常調試等)
- 常用知名第三方類庫(Async, Express等)
- 其它相關后端常用技術(MongoDB, Redis, Apache, Nginx等)
- 常用前端技術(Html5, CSS3, JQuery等)
javascript高級話題(面向對象,作用域,閉包,設計模式等)
- 1. 常用js類定義的方法有哪些?
參考答案:主要有構造函數原型和對象創建兩種方法。原型法是通用老方法,對象創建是ES5推薦使用的方法.目前來看,原型法更普遍.
代碼演示
1) 構造函數方法定義類
function Person(){this.name = 'michaelqin'; } Person.prototype.sayName = function(){ alert(this.name); } var person = new Person(); person.sayName(); 2) 對象創建方法定義類
var Person = {name: 'michaelqin',sayName: function(){ alert(this.name); }};var person = Object.create(Person);person.sayName();
- 2. js類繼承的方法有哪些
參考答案:原型鏈法,屬性復制法和構造器應用法. 另外,由于每個對象可以是一個類,這些方法也可以用于對象類的繼承.
代碼演示
1) 原型鏈法
function Animal() {this.name = 'animal'; } Animal.prototype.sayName = { alert(this.name); }; function Person() {} Person.prototype = Animal.prototype; // 人繼承自動物 Person.prototype.constructor = 'Person'; // 更新構造函數為人 2) 屬性自制法
function Animal() {this.name = 'animal'; } Animal.prototype.sayName = { alert(this.name); }; function Person() {} for(prop in Animal.prototype) { Person.prototype[prop] = Animal.prototype[prop]; } // 復制動物的所有屬性到人量邊 Person.prototype.constructor = 'Person'; // 更新構造函數為人 3) 構造器應用法
function Animal() {this.name = 'animal'; } Animal.prototype.sayName = { alert(this.name); }; function Person() { Animal.call(this); // apply, call, bind方法都可以.細微區別,后面會提到. } - 3. js類多重繼承的實現方法是怎么樣的?
參考答案:就是類繼承里邊的屬性復制法來實現.因為當所有父類的prototype屬性被復制后,子類自然擁有類似行為和屬性.
- 4. js里的作用域是什么樣子的?
參考答案:大多數語言里邊都是塊作作用域,以{}進行限定,js里邊不是.js里邊叫函數作用域,就是一個變量在全函數里有效.比如有個變量p1在函數最后一行定義,第一行也有效,但是值是undefined.
代碼演示
var globalVar = 'global var';function test() { alert(globalVar); // undefined, 因為globalVar在本函數內被重定義了,導致全局失效,這里使用函數內的變量值,可是此時還沒定義 var globalVar = 'overrided var'; // globalVar在本函數內被重定義 alert(globalVar); // overrided var } alert(globalVar); // global var,使用全局變量 - 5. js里邊的this指的是什么?
參考答案: this指的是對象本身,而不是構造函數.
代碼演示
function Person() {}Person.prototype.sayName() { alert(this.name); } var person1 = new Person(); person1.name = 'michaelqin'; person1.sayName(); // michaelqin - 6. apply, call和bind有什么區別?
參考答案:三者都可以把一個函數應用到其他對象上,注意不是自身對象.apply,call是直接執行函數調用,bind是綁定,執行需要再次調用.apply和call的區別是apply接受數組作為參數,而call是接受逗號分隔的無限多個參數列表,
代碼演示
function Person() {}Person.prototype.sayName() { alert(this.name); } var obj = {name: 'michaelqin'}; // 注意這是一個普通對象,它不是Person的實例 1) apply Person.prototype.sayName.apply(obj, [param1, param2, param3]); 2) call Person.prototype.sayName.call(obj, param1, param2, param3); 3) bind var sn = Person.prototype.sayName.bind(obj); sn([param1, param2, param3]); // bind需要先綁定,再執行 sn(param1, param2, param3); // bind需要先綁定,再執行 - 7. caller, callee和arguments分別是什么?
參考答案: caller,callee之間的關系就像是employer和employee之間的關系,就是調用與被調用的關系,二者返回的都是函數對象引用.arguments是函數的所有參數列表,它是一個類數組的變量.
代碼演示
function parent(param1, param2, param3) {child(param1, param2, param3);}function child() { console.log(arguments); // { '0': 'mqin1', '1': 'mqin2', '2': 'mqin3' } console.log(arguments.callee); // [Function: child] console.log(child.caller); // [Function: parent] } parent('mqin1', 'mqin2', 'mqin3'); - 8. 什么是閉包,閉包有哪些用處?
參考答案: 閉包這個術語,無論中文翻譯還是英文解釋都太2B了,我必須罵人,因為它什么其實都不是.非要講它是什么的話,兩個字函數,更多字嵌套函數的父子自我引用關系.所有函數都是閉包.通俗的說,閉包就是作用域范圍,因為js是函數作用域,所以函數就是閉包.全局函數的作用域范圍就是全局,所以無須討論.更多的應用其實是在內嵌函數,這就會涉及到內嵌作用域,或者叫作用域鏈.說到內嵌,其實就是父子引用關系(父函數包含子函數,子函數因為函數作用域又引用父函數,這它媽不是死結嗎?所以叫閉包),這就會帶來另外一個問題,什么時候引用結束?如果不結束,就會一直占用內存,引起內存泄漏.好吧,不用的時候就引用設為空,死結就解開了.
- 9. defineProperty, hasOwnProperty, isEnumerable都是做什么用的?
參考答案:Object.defineProperty(obj, prop, descriptor)用來給對象定義屬性,有value,writable,configurable,enumerable,set/get等.hasOwnProerty用于檢查某一屬性是不是存在于對象本身,繼承來的父親的屬性不算.isEnumerable用來檢測某一屬性是否可遍歷,也就是能不能用for..in循環來取到.
- 10. js常用設計模式的實現思路,單例,工廠,代理,裝飾,觀察者模式等
參考答案:
1) 單例: 任意對象都是單例,無須特別處理var obj = {name: 'michaelqin', age: 30}; 2) 工廠: 就是同樣形式參數返回不同的實例 function Person() { this.name = 'Person1'; } function Animal() { this.name = 'Animal1'; } function Factory() {} Factory.prototype.getInstance = function(className) { return eval('new ' + className + '()'); } var factory = new Factory(); var obj1 = factory.getInstance('Person'); var obj2 = factory.getInstance('Animal'); console.log(obj1.name); // Person1 console.log(obj2.name); // Animal1 3) 代理: 就是新建個類調用老類的接口,包一下 function Person() { } Person.prototype.sayName = function() { console.log('michaelqin'); } Person.prototype.sayAge = function() { console.log(30); } function PersonProxy() { this.person = new Person(); var that = this; this.callMethod = function(functionName) { console.log('before proxy:', functionName); that.person[functionName](); // 代理 console.log('after proxy:', functionName); } } var pp = new PersonProxy(); pp.callMethod('sayName'); // 代理調用Person的方法sayName() pp.callMethod('sayAge'); // 代理調用Person的方法sayAge() 4) 觀察者: 就是事件模式,比如按鈕的onclick這樣的應用. function Publisher() { this.listeners = []; } Publisher.prototype = { 'addListener': function(listener) { this.listeners.push(listener); }, 'removeListener': function(listener) { delete this.listeners[listener]; }, 'notify': function(obj) { for(var i = 0; i < this.listeners.length; i++) { var listener = this.listeners[i]; if (typeof listener !== 'undefined') { listener.process(obj); } } } }; // 發布者 function Subscriber() { } Subscriber.prototype = { 'process': function(obj) { console.log(obj); } }; // 訂閱者 var publisher = new Publisher(); publisher.addListener(new Subscriber()); publisher.addListener(new Subscriber()); publisher.notify({name: 'michaelqin', ageo: 30}); // 發布一個對象到所有訂閱者 publisher.notify('2 subscribers will both perform process'); // 發布一個字符串到所有訂閱者 - 11. 列舉數組相關的常用方法
參考答案: push/pop, shift/unshift, split(字符串切成數組)/join(數組組合字符串), slice(剪切數組,返回新數組,被剪切的數組元素)/splice(x,y,z)(刪除原有的元素,增加新的元素)/concat, sort/reverse, map(映射,相當于便利數組)/reduce, forEach, filter
- 12. 列舉字符串相關的常用方法
參考答案: indexOf/lastIndexOf/charAt(根據字符串下表獲取字符), split/match(字符串匹配模式)/test(模式匹配字符串), slice/substring(剪切字符,(x,y)從x開始y個字符)/substr(剪切字符,從x開始,到y結束), toLowerCase/toUpperCase
node核心內置類庫(事件,流,文件,網絡等)
node概覽
- 1. 為什么要用node?
參考答案: 總結起來node有以下幾個特點:簡單強大,輕量可擴展.簡單體現在node使用的是javascript,json來進行編碼,人人都會;強大體現在非阻塞IO,可以適應分塊傳輸數據,較慢的網絡環境,尤其擅長高并發訪問;輕量體現在node本身既是代碼,又是服務器,前后端使用統一語言;可擴展體現在可以輕松應對多實例,多服務器架構,同時有海量的第三方應用組件.
- 2. node的構架是什么樣子的?
參考答案: 主要分為三層,應用app >> V8及node內置架構 >> 操作系統. V8是node運行的環境,可以理解為node虛擬機.node內置架構又可分為三層: 核心模塊(javascript實現) >> c++綁定 >> libuv + CAes + http.
- 3. node有哪些核心模塊?
參考答案: EventEmitter, Stream, FS, Net和全局對象
node全局對象
- 1. node有哪些全局對象?
參考答案: process, console, Buffer和exports
- 2. process有哪些常用方法?
參考答案: process.stdin, process.stdout, process.stderr, process.on, process.env, process.argv, process.arch, process.platform, process.exit
- 3. console有哪些常用方法?
參考答案: console.log/console.info, console.error/console.warning, console.time/console.timeEnd, console.trace, console.table
- 4. node有哪些定時功能?
參考答案: setTimeout/clearTimeout, setInterval/clearInterval, setImmediate/clearImmediate, process.nextTick
- 5. node中的事件循環是什么樣子的?
參考答案: event loop其實就是一個事件隊列,先加入先執行,執行完一次隊列,再次循環遍歷看有沒有新事件加入隊列.執行中的叫IO events, setImmediate是在當前隊列立即執行,setTimout/setInterval是把執行定時到下一個隊列,process.nextTick是在當前執行完,下次遍歷前執行.所以總體順序是: IO events >> setImmediate >> setTimeout/setInterval >> process.nextTick
- 6. node中的Buffer如何應用?
參考答案: Buffer是用來處理二進制數據的,比如圖片,mp3,數據庫文件等.Buffer支持各種編碼解碼,二進制字符串互轉.
EventEmitter
- 1. 什么是EventEmitter?
參考答案: EventEmitter是node中一個實現觀察者模式的類,主要功能是監聽和發射消息,用于處理多模塊交互問題.
- 2. 如何實現一個EventEmitter?
參考答案: 主要分三步:定義一個子類,調用構造函數,繼承EventEmitter
代碼演示
var util = require('util'); var EventEmitter = require('events').EventEmitter; function MyEmitter() { EventEmitter.call(this); } // 構造函數 util.inherits(MyEmitter, EventEmitter); // 繼承 var em = new MyEmitter(); em.on('hello', function(data) { console.log('收到事件hello的數據:', data); }); // 接收事件,并打印到控制臺 em.emit('hello', 'EventEmitter傳遞消息真方便!'); - 3. EventEmitter有哪些典型應用?
參考答案: 1) 模塊間傳遞消息 2) 回調函數內外傳遞消息 3) 處理流數據,因為流是在EventEmitter基礎上實現的. 4) 觀察者模式發射觸發機制相關應用
- 4. 怎么捕獲EventEmitter的錯誤事件?
參考答案: 監聽error事件即可.如果有多個EventEmitter,也可以用domain來統一處理錯誤事件.
代碼演示
var domain = require('domain'); var myDomain = domain.create(); myDomain.on('error', function(err){ console.log('domain接收到的錯誤事件:', err); }); // 接收事件并打印 myDomain.run(function(){ var emitter1 = new MyEmitter(); emitter1.emit('error', '錯誤事件來自emitter1'); emitter2 = new MyEmitter(); emitter2.emit('error', '錯誤事件來自emitter2'); }); - 5. EventEmitter中的newListenser事件有什么用處?
參考答案: newListener可以用來做事件機制的反射,特殊應用,事件管理等.當任何on事件添加到EventEmitter時,就會觸發newListener事件,基于這種模式,我們可以做很多自定義處理.
代碼演示
var emitter3 = new MyEmitter();
emitter3.on('newListener', function(name, listener) { console.log("新事件的名字:", name); console.log("新事件的代碼:", listener); setTimeout(function(){ console.log("我是自定義延時處理機制"); }, 1000); }); emitter3.on('hello', function(){ console.log('hello node'); }); Stream
- 1. 什么是Stream?
參考答案: stream是基于事件EventEmitter的數據管理模式.由各種不同的抽象接口組成,主要包括可寫,可讀,可讀寫,可轉換等幾種類型.
- 2. Stream有什么好處?
參考答案: 非阻塞式數據處理提升效率,片斷處理節省內存,管道處理方便可擴展等.
- 3. Stream有哪些典型應用?
參考答案: 文件,網絡,數據轉換,音頻視頻等.
- 4. 怎么捕獲Stream的錯誤事件?
參考答案: 監聽error事件,方法同EventEmitter.
- 5. 有哪些常用Stream,分別什么時候使用?
參考答案: Readable為可被讀流,在作為輸入數據源時使用;Writable為可被寫流,在作為輸出源時使用;Duplex為可讀寫流,它作為輸出源接受被寫入,同時又作為輸入源被后面的流讀出.Transform機制和Duplex一樣,都是雙向流,區別時Transfrom只需要實現一個函數_transfrom(chunk, encoding, callback);而Duplex需要分別實現_read(size)函數和_write(chunk, encoding, callback)函數.
- 6. 實現一個Writable Stream?
參考答案: 三步走:1)構造函數call Writable 2) 繼承Writable 3) 實現_write(chunk, encoding, callback)函數
代碼演示
var Writable = require('stream').Writable; var util = require('util'); function MyWritable(options) { Writable.call(this, options); } // 構造函數 util.inherits(MyWritable, Writable); // 繼承自Writable MyWritable.prototype._write = function(chunk, encoding, callback) { console.log("被寫入的數據是:", chunk.toString()); // 此處可對寫入的數據進行處理 callback(); }; process.stdin.pipe(new MyWritable()); // stdin作為輸入源,MyWritable作為輸出源 文件系統
- 1. 內置的fs模塊架構是什么樣子的?
參考答案: fs模塊主要由下面幾部分組成: 1) POSIX文件Wrapper,對應于操作系統的原生文件操作 2) 文件流 fs.createReadStream和fs.createWriteStream 3) 同步文件讀寫,fs.readFileSync和fs.writeFileSync 4) 異步文件讀寫, fs.readFile和fs.writeFile
- 2. 讀寫一個文件有多少種方法?
參考答案: 總體來說有四種: 1) POSIX式低層讀寫 2) 流式讀寫 3) 同步文件讀寫 4) 異步文件讀寫
- 3. 怎么讀取json配置文件?
參考答案: 主要有兩種方式,第一種是利用node內置的require('data.json')機制,直接得到js對象; 第二種是讀入文件入內容,然后用JSON.parse(content)轉換成js對象.二者的區別是require機制情況下,如果多個模塊都加載了同一個json文件,那么其中一個改變了js對象,其它跟著改變,這是由node模塊的緩存機制造成的,只有一個js模塊對象; 第二種方式則可以隨意改變加載后的js變量,而且各模塊互不影響,因為他們都是獨立的,是多個js對象.
- 4. fs.watch和fs.watchFile有什么區別,怎么應用?
參考答案: 二者主要用來監聽文件變動.fs.watch利用操作系統原生機制來監聽,可能不適用網絡文件系統; fs.watchFile則是定期檢查文件狀態變更,適用于網絡文件系統,但是相比fs.watch有些慢,因為不是實時機制.
網絡
- 1. node的網絡模塊架構是什么樣子的?
參考答案: node全面支持各種網絡服務器和客戶端,包括tcp, http/https, tcp, udp, dns, tls/ssl等.
- 2. node是怎樣支持https,tls的?
參考答案: 主要實現以下幾個步驟即可: 1) openssl生成公鑰私鑰 2) 服務器或客戶端使用https替代http 3) 服務器或客戶端加載公鑰私鑰證書
- 3. 實現一個簡單的http服務器?
參考答案: 經典又很沒毛意義的一個題目.思路是加載http模塊,創建服務器,監聽端口.
代碼演示
var http = require('http'); // 加載http模塊 http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); // 200代表狀態成功, 文檔類型是給瀏覽器識別用的 res.write('<meta charset="UTF-8"> <h1>我是標題啊!</h1> <font color="red">這么原生,初級的服務器,下輩子能用著嗎?!</font>'); // 返回給客戶端的html數據 res.end(); // 結束輸出流 }).listen(3000); // 綁定3ooo, 查看效果請訪問 http://localhost:3000 child-process
- 1. 為什么需要child-process?
參考答案: node是異步非阻塞的,這對高并發非常有效.可是我們還有其它一些常用需求,比如和操作系統shell命令交互,調用可執行文件,創建子進程進行阻塞式訪問或高CPU計算等,child-process就是為滿足這些需求而生的.child-process顧名思義,就是把node阻塞的工作交給子進程去做.
- 2. exec,execFile,spawn和fork都是做什么用的?
參考答案: exec可以用操作系統原生的方式執行各種命令,如管道 cat ab.txt | grep hello; execFile是執行一個文件; spawn是流式和操作系統進行交互; fork是兩個node程序(javascript)之間時行交互.
- 3. 實現一個簡單的命令行交互程序?
參考答案: 那就用spawn吧.
代碼演示
var cp = require('child_process'); var child = cp.spawn('echo', ['你好', "鉤子"]); // 執行命令 child.stdout.pipe(process.stdout); // child.stdout是輸入流,process.stdout是輸出流 // 這句的意思是將子進程的輸出作為當前程序的輸入流,然后重定向到當前程序的標準輸出,即控制臺 - 4. 兩個node程序之間怎樣交互?
參考答案: 用fork嘛,上面講過了.原理是子程序用process.on, process.send,父程序里用child.on,child.send進行交互.
代碼演示
1) fork-parent.jsvar cp = require('child_process'); var child = cp.fork('./fork-child.js'); child.on('message', function(msg){ console.log('老爸從兒子接受到數據:', msg); }); child.send('我是你爸爸,送關懷來了!'); 2) fork-child.js process.on('message', function(msg){ console.log("兒子從老爸接收到的數據:", msg); process.send("我不要關懷,我要銀民幣!"); }); - 5. 怎樣讓一個js文件變得像linux命令一樣可執行?
參考答案: 1) 在myCommand.js文件頭部加入 #!/usr/bin/env node 2) chmod命令把js文件改為可執行即可 3) 進入文件目錄,命令行輸入myComand就是相當于node myComand.js了
- 6. child-process和process的stdin,stdout,stderror是一樣的嗎?
參考答案: 概念都是一樣的,輸入,輸出,錯誤,都是流.區別是在父程序眼里,子程序的stdout是輸入流,stdin是輸出流.
node高級話題(異步,部署,性能調優,異常調試等)
- 1. node中的異步和同步怎么理解
參考答案: node是單線程的,異步是通過一次次的循環事件隊列來實現的.同步則是說阻塞式的IO,這在高并發環境會是一個很大的性能問題,所以同步一般只在基礎框架的啟動時使用,用來加載配置文件,初始化程序什么的.
- 2. 有哪些方法可以進行異步流程的控制?
參考答案: 1) 多層嵌套回調 2) 為每一個回調寫單獨的函數,函數里邊再回調 3) 用第三方框架比方async, q, promise等
- 3. 怎樣綁定node程序到80端口?
參考答案: 多種方式 1) sudo 2) apache/nginx代理 3) 用操作系統的firewall iptables進行端口重定向
- 4. 有哪些方法可以讓node程序遇到錯誤后自動重啟?
參考答案: 1) runit 2) forever 3) nohup npm start &
- 5. 怎樣充分利用多個CPU?
參考答案: 一個CPU運行一個node實例
- 6. 怎樣調節node執行單元的內存大小?
參考答案: 用--max-old-space-size 和 --max-new-space-size 來設置 v8 使用內存的上限
- 7. 程序總是崩潰,怎樣找出問題在哪里?
參考答案: 1) node --prof 查看哪些函數調用次數多 2) memwatch和heapdump獲得內存快照進行對比,查找內存溢出
- 8. 有哪些常用方法可以防止程序崩潰?
參考答案: 1) try-catch-finally 2) EventEmitter/Stream error事件處理 3) domain統一控制 4) jshint靜態檢查 5) jasmine/mocha進行單元測試
- 9. 怎樣調試node程序?
參考答案: node --debug app.js 和node-inspector
常用知名第三方類庫(Async, Express等)
- 1. async都有哪些常用方法,分別是怎么用?
參考答案: async是一個js類庫,它的目的是解決js中異常流程難以控制的問題.async不僅適用在node.js里,瀏覽器中也可以使用. 1) async.parallel并行執行完多個函數后,調用結束函數
async.parallel([function(){ ... },function(){ ... }], callback); 2) async.series串行執行完多個函數后,調用結束函數
async.series([function(){ ... },function(){ ... }]); 3) async.waterfall依次執行多個函數,后一個函數以前面函數的結果作為輸入參數
async.waterfall([function(callback) {callback(null, 'one', 'two'); }, function(arg1, arg2, callback) { // arg1 now equals 'one' and arg2 now equals 'two' callback(null, 'three'); }, function(arg1, callback) { // arg1 now equals 'three' callback(null, 'done'); } ], function (err, result) { // result now equals 'done' }); 4) async.map異步執行多個數組,返回結果數組
async.map(['file1','file2','file3'], fs.stat, function(err, results){ // results is now an array of stats for each file }); 5) async.filter異步過濾多個數組,返回結果數組
async.filter(['file1','file2','file3'], fs.exists, function(results){ // results now equals an array of the existing files }); - 2. express項目的目錄大致是什么樣子的
參考答案: app.js, package.json, bin/www, public, routes, views.
- 3. express常用函數
參考答案: express.Router路由組件,app.get路由定向,app.configure配置,app.set設定參數,app.use使用中間件
- 4. express中如何獲取路由的參數
參考答案: /users/:name使用req.params.name來獲取; req.body.username則是獲得表單傳入參數username; express路由支持常用通配符 ?, +, *, and ()
- 5. express response有哪些常用方法
參考答案: res.download() 彈出文件下載
res.end() 結束response
res.json() 返回json
res.jsonp() 返回jsonp
res.redirect() 重定向請求
res.render() 渲染模板
res.send() 返回多種形式數據
res.sendFile 返回文件
res.sendStatus() 返回狀態
其它相關后端常用技術(MongoDB, Redis, Apache, Nginx等)
- 1. mongodb有哪些常用優化措施
參考答案: 類似傳統數據庫,索引和分區.
- 2. redis支持哪些功能
參考答案: set/get, hset/hget, publish/subscribe, expire
- 3. redis最簡單的應用
參考答案:
var redis = require("redis"), client = redis.createClient(); client.set("foo_rand000000000000", "some fantastic value"); client.get("foo_rand000000000000", function (err, reply) { console.log(reply.toString()); }); client.end(); - 4. apache,nginx有什么區別?
參考答案: 二者都是代理服務器,功能類似.apache應用簡單,相當廣泛.nginx在分布式,靜態轉發方面比較有優勢.
轉載于:https://www.cnblogs.com/qqxwbtt/p/7862281.html
總結
- 上一篇: 留学需要多少钱啊?
- 下一篇: appium-chromedriver@