Node.js 開發指南 讀書筆記

1.Node.js 簡介

Node.js 其實就是藉助谷歌的 V8 引擎,將桌面端的 js 帶到了服務器端,它的出現我將其歸結爲兩點:javascript

  1. V8 引擎的出色;
  2. js 異步 io 與事件驅動給服務器帶來極高的 吞吐量/硬件性能 比例。

Node.js的架構

2.安裝和配置Node.js

安裝的話基本是分爲 Windows 和 POSIX(爲*unx 和 Mac等系統的統稱)。php

2.1.在 Windows 上,很簡單,訪問官網,下載對應的安裝包安裝便可。

2.2.在 POSIX 上安裝

大均可以從相應的包管理器上進行安裝(非大神不推薦用源碼,由於源碼編譯涉及相關參數)。
不過包管理器上的並不是是最新的,還多是很老舊的版本,因此須要咱們去官網下載編譯好的二進制來進行安裝.
關於安裝,我在個人其餘文章中有說到,比較簡單的.
在安裝的時候,會默認自帶一個 npm 包管理器,這個 npm 上託管了幾乎全部的 node.js 程序包,你可以經過它下載各類流行的基於 node.js 的軟件.html

3.Node.js快速入門

3.1.Hello World

固然,快速入門少不了 Hello World,Node.js其實和瀏覽器上的 javascript 差不太多,只是Node.js上增長了一些引用的方法以及結構,而且去除了 Dom 的操做(Node上並無Dom能夠操做..),因此,你能夠直接用如下語句打印出一句"Hello World":java

console.log("Hello World");

3.2.創建一個Web Server

Node.js是一個服務器程序,固然就要提供Web服務,如下代碼能讓你創建起一個最基礎的Web服務器,由於它對全部的請求都只返回一樣的頁面:node

//app.js 
 
var http = require('http'); 
 
http.createServer(function(req, res) { 
  res.writeHead(200, {'Content-Type': 'text/html'}); 
  res.write('<h1>Node.js</h1>'); 
  res.end('<p>Hello World</p>'); 
}).listen(3000); 
console.log("HTTP server is listening at port 3000.");

3.3.異步IO

在js 中,最重要的部分之一就是它的異步編程,經過事件輪詢,在單線程上它可以接受更多的請求而且將資源最大化利用.
好比像下面的讀取文件程序,在調用讀取函數後,程序會繼續往下運行,等到讀取完成後,纔會調用讀取函數所綁定的那個回調函數:git

//readfilesync.js 
 
var fs = require('fs'); 
var data = fs.readFileSync('file.txt', 'utf-8'); 
console.log(data); 
console.log('end.');

3.4.模塊和包

3.4.1.建立模塊

node的幾乎全部程序都是經過各類模塊以及包組合完成的.每一個模塊或者包都有他特殊的功能以及特定的調用方法.
建立及加載模塊:github

// 讓咱們以一個例子來了解模塊。建立一個 module.js 的文件,內容是: 
//module.js 
 
var name; 
 
exports.setName = function(thyName) { 
  name = thyName; 
}; 
 
exports.sayHello = function() { 
  console.log('Hello ' + name); 
}; 

// 在同一目錄下建立 getmodule.js,內容是: 
//getmodule.js 
 
var myModule = require('./module'); 
myModule.setName('BYVoid'); 
myModule.sayHello();

3.4.2單次加載

在同一個程序中,同一個模塊只會被加載一次,node會經過將模塊的地址進行 hash並存起來,因此不會加載屢次.固然這樣的話,你在程序中引用兩次同一個模塊,他們實際上是一個.web

3.4.3.覆蓋exports

模塊經過 exports 暴露相關的方法或者屬性,未經過此方法暴露的方法和屬性是沒法被外界訪問到的(和java C++ C# 中的類差很少)
可是,你不能直接經過如下方式來覆蓋export函數,這樣的話不會有任何效果express

export = function(){
    console.log("Hello");
}

當咱們只須要暴露一個方法或屬性的時候,用exports 就略顯複雜,我麼能夠經過如下方式將 exports 進行覆蓋:npm

module.exports = function(){
    console.log("Hello");
}

3.5.建立包

每一個包中有不少的模塊,由於以前咱們知道,每一個模塊最好是單獨存放在一個js文件中,那包咱們就應該存放在一個文件夾中.而且提供一個外部訪問的接口(一般是一個模塊),用於總體管理及使用這個包的全部功能.

Node.js 在調用某個包時,會首先檢查包中 package.json 文件的 main 字段,將其做爲包的接口模塊,若是 package.json 或 main 字段不存在,會嘗試尋找 index.js 或 index.node 做
爲包的接口。

3.5.1.package.json文件

package.json 是 CommonJS 規定的用來描述包的文件,徹底符合規範的package.json 文件應該含有如下字段。

  • name:包的名稱,必須是惟一的,由小寫英文字母、數字和下劃線組成,不能包含

空格。

  • description:包的簡要說明。
  • version:符合語義化版本識別①規範的版本字符串。
  • keywords:關鍵字數組,一般用於搜索。
  • maintainers:維護者數組,每一個元素要包含 name、email (可選)、web (可選)

字段。

  • contributors:貢獻者數組,格式與maintainers相同。包的做者應該是貢獻者

數組的第一個元素。

  • bugs:提交bug的地址,能夠是網址或者電子郵件地址。
  • licenses:許可證數組,每一個元素要包含 type (許可證的名稱)和 url (連接到

許可證文本的地址)字段。

  • repositories:倉庫託管地址數組,每一個元素要包含 type (倉庫的類型,如 git )、

url (倉庫的地址)和 path (相對於倉庫的路徑,可選)字段。

  • dependencies:包的依賴,一個關聯數組,由包名稱和版本號組成。

下面是一個徹底符合 CommonJS 規範的 package.json 示例:

{ 
  "name": "mypackage", 
  "description": "Sample package for CommonJS. This package demonstrates the required 
elements of a CommonJS package.", 
  "version": "0.7.0", 
  "keywords": [ 
     "package", 
     "example"  
  ], 
  "maintainers": [ 
     { 
        "name": "Bill Smith", 
        "email": "bills@example.com", 
     }  
  ], 
  "contributors": [ 
     { 
        "name": "BYVoid", 
        "web": "http://www.byvoid.com/"  
     }  
  ], 
  "bugs": { 
     "mail": "dev@example.com", 
     "web": "http://www.example.com/bugs"  
  }, 
  "licenses": [ 
     { 
        "type": "GPLv2", 
        "url": "http://www.example.org/licenses/gpl.html"  
     }  
  ], 
  "repositories": [ 
     { 
        "type": "git", 
        "url": "http://github.com/BYVoid/mypackage.git"  
     }  
  ], 
  "dependencies": { 
     "webkit": "1.2", 
     "ssl": { 
        "gnutls": ["1.0", "2.0"], 
        "openssl": "0.9.8"  
     }  
  } 
}

3.6 Node.js包管理器

經過使用Node.js的包管理器,你能夠下載到幾乎全部的js包.
獲取一個包以下

npm install express

以上代碼將會將express安裝到當前項目的文件中
若是要將其安裝在全局(當前電腦一次安裝,全部地方均可以使用),能夠在參數中加入 -g,若是是要將其加入項目依賴,能夠加入如下參數 --save-dev,這樣node會自動將當前包的信息寫入到項目的 package.json 中.

3.7.包的發佈

這個略過,並不打算髮布,等須要時看也不遲

3.8.調試

3.8.1.命令行調試

調試其實方法不少,能夠直接在命令行中進行單步調試,如下是nodejs支持的調試命令:

命令 功能
run 執行腳本,在第一行暫停
restart 從新執行腳本
cont, c 繼續執行,直到遇到下一個斷點
next, n 單步執行
step, s 單步執行並進入函數
out, o 從函數中步出
setBreakpoint(), sb() 在當前行設置斷點
setBreakpoint(‘f()’), sb(...) 在函數f的第一行設置斷點
setBreakpoint(‘script.js’, 20), sb(...) 在 script.js 的第20行設置斷點
clearBreakpoint, cb(...) 清除全部斷點
backtrace, bt 顯示當前的調用棧
list(5) 顯示當前執行到的先後5行代碼
watch(expr) 把表達式 expr 加入監視列表
unwatch(expr) 把表達式 expr 從監視列表移除
watchers 顯示監視列表中全部的表達式和值
repl 在當前上下文打開即時求值環境
kill 終止當前執行的腳本
scripts 顯示當前已加載的全部腳本
version 顯示 V8 的版本

3.8.2.使用 node-inspector 調試 node.js程序

這個程序很是好用,雖然我沒有用過,可是看上去很像谷歌瀏覽器的調試,之後須要時能夠試試看.

4.Node.js核心模塊

4.1.全局對象

在Node中,有一個相似在瀏覽器中的window同樣的全局對象,叫作 global. console,process等都是其子.

如何定義全局對象就很簡單了:直接綁定在global上的,定義在最外層的,還有沒有使用var聲明的都是全局對象.

4.1.1.process對象

這個對象存儲了與當前運行環境相關的不少信息,而且也是標準輸入輸出的最底層接口,咱們在調用console.log()時,其背後也是經過process來實現的.

其含有如下幾個對象

  • process.argv 包含當前環境數據
  • process.stdout 標準輸出接口
  • process.stdin 標準輸入接口
  • process.nextTick(callback)的功能是爲事件循環設置一項任務,Node.js 會在

下次事件循環調響應時調用 callback。這個能夠用在將兩個大型的耗時操做進行拆散.

4.1.2.console對象

這個對象是和咱們打交道最多的對象,其含有如下方法:

  • console.log() 向標準輸出接口打印字符,其有多個參數,當只有一個參數時,會將當前參數轉換爲字符串並打印出來.
  • console.error()這個和剛纔的log很類似,只不過這個是標準錯誤輸出,也是就是製造一個bug!!!!
  • console.trace() 向標準錯誤輸出流輸出當前調用棧

4.2 經常使用工具util

4.2.1 util.inherits

這個方法提供了一個方便的途徑讓咱們進行繼承操做,用法以下

util.inherits(constructor, superConstructor)

將兩個對象進行繼承操做

注意,此處繼承只會繼承原型中定義的屬性和方法!!而且 console.log() 並不會打印出原型中的屬性或者方法.

4.2.2.util.inspect

util.inspect(object,[showHidden],[depth],[colors]) 是一個將任一對象轉換爲字符串的方法.它不會調用對象的 toString 方法.
除了以上咱們介紹的幾個函數以外, util還提供了util.isArray()、 util.isRegExp()、
util.isDate()、util.isError() 四個類型測試工具,以及 util.format()、util.debug() 等工具。

4.3.事件驅動 events

events 是 node最重要的模塊.由於node整個運行就是基於事件輪詢的.

4.3.1.事件發生器

events只提供了一個對象: events.EventEmitter.這個對象的核心就是封裝事件功能.下面的代碼向咱們演示了這個過程:

var events = require('events'); 
 
var emitter = new events.EventEmitter(); 
 
emitter.on('someEvent', function(arg1, arg2) { 
  console.log('listener1', arg1, arg2); 
}); 
 
emitter.on('someEvent', function(arg1, arg2) { 
  console.log('listener2', arg1, arg2); 
}); 
 
emitter.emit('someEvent', 'byvoid', 1991);

這就是EventEmitter最簡單的用法。接下來咱們介紹一下EventEmitter經常使用的API。

  • EventEmitter.on(event, listener) 爲指定事件註冊一個監聽器,接受一個字

符串 event 和一個回調函數 listener。

  • EventEmitter.emit(event, [arg1], [arg2], [...]) 發射 event 事件,傳

遞若干可選參數到事件監聽器的參數表。

  • EventEmitter.once(event, listener) 爲指定事件註冊一個單次監聽器,即

監聽器最多隻會觸發一次,觸發後馬上解除該監聽器。

  • EventEmitter.removeListener(event, listener) 移除指定事件的某個監聽

器,listener 必須是該事件已經註冊過的監聽器。

  • EventEmitter.removeAllListeners([event]) 移除全部事件的全部監聽器,

若是指定 event,則移除指定事件的全部監聽器。

4.3.2. error事件

EventEmitter定義了一個特殊的叫 error 的事件.當error被觸發時,若是沒有定義的響應監聽器,則會退出程序並打印調用棧.

因此咱們要發射 error 時,必需要爲其設置相應的監聽器,來捕獲並處理錯誤,這樣纔不會致使程序掛掉.

4.3.3.繼承EventEmitter

通常狀況下,咱們不會直接用到這個對象,而是在繼承中使用它.包括fs,http等都是這樣的操做.

爲何要這樣作呢?緣由有兩點。首先,具備某個實體功能的對象實現事件符合語義,
事件的監聽和發射應該是一個對象的方法。其次 JavaScript 的對象機制是基於原型的,支持部分多重繼承,繼承 EventEmitter 不會打亂對象原有的繼承關係。

4.4.文件系統fs

fs 模塊是文件操做的封裝,它提供了文件的讀取、寫入、改名、刪除、遍歷目錄、連接等 POSIX 文件系統操做。與其餘模塊不一樣的是,fs 模塊中全部的操做都提供了異步的和同步的兩個版本,例如讀取文件內容的函數有異步的 fs.readFile() 和同步的 fs.readFileSync()。咱們以幾個函數爲表明,介紹 fs 經常使用的功能,並列出 fs 全部函數的定義和功能。

4.4.1fs.readFile

這個函數可以讀取文件中的內容,其用法爲:
fs.readFile(filename,[encoding],[callback(err,data)])
如下是調用示例:

var fs = require('fs'); 
 
fs.readFile('content.txt', 'utf-8', function(err, data) { 
  if (err) { 
    console.error(err); 
  } else { 
    console.log(data); 
  } 
});

4.4.2.fs.readFileSync

fs.readFileSync(filename, [encoding])是 fs.readFile 同步的版本。它接受的參數和 fs.readFile 相同,而讀取到的文件內容會以函數返回值的形式返回。若是有錯誤發生,fs 將會拋出異常,你須要使用 try 和 catch 捕捉並處理異常。

4.4.3.fs.open

fs.open(path, flags, [mode], [callback(err, fd)])是 POSIX open 函數的封裝,與 C 語言標準庫中的 fopen 函數相似。它接受兩個必選參數,path 爲文件的路徑,flags 能夠是如下值:

  • r :以讀取模式打開文件。
  • r+ :以讀寫模式打開文件。
  • w :以寫入模式打開文件,若是文件不存在則建立。
  • w+ :以讀寫模式打開文件,若是文件不存在則建立。
  • a :以追加模式打開文件,若是文件不存在則建立。
  • a+ :以讀取追加模式打開文件,若是文件不存在則建立。

mode 參數用於建立文件時給文件指定權限,默認是 0666。回調函數將會傳遞一個文
件描述符 fd。

4.4.4.fs.read

fs.read(fd, buffer, offset, length, position, [callback(err, bytesRead, buffer)])是 POSIX read 函數的封裝,相比 fs.readFile 提供了更底層的接口。

如下是其示例:

var fs = require('fs'); 
 
fs.open('content.txt', 'r', function(err, fd) { 
  if (err) { 
    console.error(err); 
    return; 
  } 
   
  var buf = new Buffer(8); 
  fs.read(fd, buf, 0, 8, null, function(err, bytesRead, buffer) { 
    if (err) { 
      console.error(err); 
      return; 
    } 
     
    console.log('bytesRead: ' + bytesRead); 
    console.log(buffer); 
  }) 
});

固然,fs中的函數是不少的,若是須要,能夠去網上進行更詳細的查閱.

4.5 HTTP 服務器與客戶端

其實在node中就已經封裝了一個很底層的http模塊,http.Server,幾乎全部的網絡工做都能經過它來完成.

4.5.1 HTTP服務器

如下代碼

//app.js 
 
var http = require('http'); 
 
http.createServer(function(req, res) { 
  res.writeHead(200, {'Content-Type': 'text/html'}); 
  res.write('<h1>Node.js</h1>'); 
  res.end('<p>Hello World</p>'); 
}).listen(3000); 
 
console.log("HTTP server is listening at port 3000.");

會創建一個在3000端口監聽的程序

4.5.2 GET 請求

//httpserverrequestget.js 
 
var http = require('http'); 
var url = require('url'); 
var util = require('util'); 
 
http.createServer(function(req, res) { 
  res.writeHead(200, {'Content-Type': 'text/plain'}); 
  res.end(util.inspect(url.parse(req.url, true))); 
}).listen(3000);

4.5.3 POST 請求

//httpserverrequestpost.js 
 
var http = require('http'); 
var querystring = require('querystring'); 
var util = require('util'); 
 
http.createServer(function(req, res) { 
  var post = ''; 
 
  req.on('data', function(chunk) { 
    post += chunk; 
  }); 
 
  req.on('end', function() { 
    post = querystring.parse(post); 
    res.end(util.inspect(post)); 
  }); 
 
}).listen(3000);

4.5.4 http.serverResponse

服務器不只要接收數據,也要返回相應的數據給客戶端:

http.ServerResponse 是返回給客戶端的信息,決定了用戶最終能看到的結果。它
也是由 http.Server 的 request 事件發送的,做爲第二個參數傳遞,通常簡稱爲
response 或 res。

http.ServerResponse 有三個重要的成員函數,用於返回響應頭、響應內容以及結束
請求。

  • response.writeHead(statusCode, [headers]):向請求的客戶端發送響應頭。statusCode 是 HTTP 狀態碼,如 200 (請求成功)、404 (未找到)等。headers 是一個相似關聯數組的對象,表示響應頭的每一個屬性。該函數在一個請求內最多隻能調用一次,若是不調用,則會自動生成一個響應頭。
  • response.write(data, [encoding]):向請求的客戶端發送響應內容。data 是一個 Buffer 或字符串,表示要發送的內容。若是 data 是字符串,那麼須要指定

encoding 來講明它的編碼方式,默認是 utf-8。在 response.end 調用以前,
response.write 能夠被屢次調用。

  • response.end([data], [encoding]):結束響應,告知客戶端全部發送已經完

成。當全部要返回的內容發送完畢的時候,該函數 必須 被調用一次。它接受兩個可
選參數,意義和 response.write 相同。若是不調用該函數,客戶端將永遠處於等待狀態。

4.5.5 HTTP 客戶端

固然,node還能充當一個http的客戶端程序來使用 http 模塊提供了兩個函數 http.request 和 http.get,功能是做爲客戶端向 HTTP服務器發起請求。

4.5.5.1 http.request

http.request(options, callback) 發起 HTTP 請求。接受兩個參數, option 是一個相似關聯數組的對象,表示請求的參數, callback是請求的回調函數。 option經常使用的參數以下所示。

  • host :請求網站的域名或 IP 地址。
  • port :請求網站的端口,默認 80。
  • method :請求方法,默認是 GET。
  • path :請求的相對於根的路徑,默認是「/」。QueryString 應該包含在其中。例如 /search?query=byvoid。
  • headers :一個關聯數組對象,爲請求頭的內容。

callback 傳遞一個參數,爲 http.ClientResponse 的實例。
http.request 返回一個 http.ClientRequest 的實例。

下面是一個經過 http.request 發送 POST 請求的代碼:

//httprequest.js 
 
var http = require('http'); 
var querystring = require('querystring'); 
 
var contents = querystring.stringify({ 
  name: 'byvoid', 
  email: 'byvoid@byvoid.com', 
  address: 'Zijing 2#, Tsinghua University', 
}); 
 
var options = { 
  host: 'www.byvoid.com', 
  path: '/application/node/post.php', 
  method: 'POST', 
  headers: { 
    'Content-Type': 'application/x-www-form-urlencoded', 
    'Content-Length' : contents.length 
  } 
}; 
 
var req = http.request(options, function(res) { 
  res.setEncoding('utf8'); 
  res.on('data', function (data) { 
    console.log(data); 
  }); 
}); 
 
req.write(contents); 
req.end();

4.5.5.2 http.get

http.get(options, callback) http 模塊還提供了一個更加簡便的方法用於處
理GET請求: http.get。它是 http.request 的簡化版,惟一的區別在於http.get
自動將請求方法設爲了 GET 請求,同時不須要手動調用 req.end()。

//httpget.js 
 
var http = require('http'); 
 
http.get({host: 'www.byvoid.com'}, function(res) { 
  res.setEncoding('utf8'); 
  res.on('data', function (data) { 
    console.log(data); 
  }); 
});

post 和 get 返回的數據皆爲 http.ClientRequest 對象.其也提供了write和end函數,使用以下:

//httpresponse.js 
 
var http = require('http'); 
 
var req = http.get({host: 'www.byvoid.com'}); 
 
req.on('response', function(res) { 
  res.setEncoding('utf8'); 
  res.on('data', function (data) { 
    console.log(data); 
  }); 
});

其還提供瞭如下函數:

  • request.abort():終止正在發送的請求。
  • request.setTimeout(timeout, [callback]):設置請求超時時間,timeout 爲毫秒數。當請求超時之後,callback 將會被調用。

此外還有 request.setNoDelay([noDelay])、request.setSocketKeepAlive
([enable], [initialDelay]) 等函數。
同時也有 http.ClientResponse,其與 http:ServerResponse類似,提供... data,end和close,分別在數據到達,傳輸結束和連接結束時觸發,其中 data 事件傳遞一個參數 chunk,表示接收到的數據.

http.ClientResponse 還提供瞭如下幾個特殊的函數。

  • response.setEncoding([encoding]):設置默認的編碼,當 data 事件被觸發

時,數據將會以 encoding 編碼。默認值是 null,即不編碼,以 Buffer 的形式存
儲。經常使用編碼爲 utf8。

  • response.pause():暫停接收數據和發送事件,方便實現下載功能。
  • response.resume():從暫停的狀態中恢復。

5. 使用Node.js進行Web開發

5.1 使用Express 框架進行開發

安裝npm install -g express

5.2 創建工程

express -t ejs XXXX

接下來的 Nodejs 博客搭建暫未記錄,由於涉及到的知識點較多,須要的話最好仍是從新讀一次.

6. Node.js 進階

6.1 模塊加載機制

在node中,共有兩種類型的模塊:核心模塊,文件模塊.核心模塊是由node自帶的一些基礎模塊,具備最高的加載優先級,文件模塊則是咱們本身建立的或者引用的三方模塊.

6.1.1 按路徑查找

若是指定了加載路徑,則有如下兩種查找狀況

  • 當未指定模塊名稱,而只是制定了文件夾時,node會自動去制定目錄尋找index文件,而index文件的文件類型,按加載順序則有三種:js,json,node.若是這三種類型的index文件都沒有找到,則報錯.
  • 當指定了文件名稱,可是未指定文件後綴時,node也是按照以上的順序進行查找:js,json,node.

6.1.2 經過查找node_modules

當咱們沒有指定路徑時,node會在node_moudule目錄中去查找,有趣的是,在這裏有一個很大的性能問題:

好比你在/home/aiello/develop/foot.js中使用require("bar.js"),node會在當前目錄的node_modules文件夾中尋找該模塊,若是未找到,則會從新往上一級查找,因而node到/home/aiello/develop這級目錄進行查找,若是尚未它會繼續往上進行查找,知道找到根目錄,發現沒有,因而報錯.

能明顯看出來,node在模塊的查找中很是的費時,因此咱們應當將模塊放到距離當前應用最近的目錄,而且最好是指定模塊路徑.

6.2 控制流

在循環中使用異步方法,將會遇到

//forloop.js 
 
var fs = require('fs'); 
var files = ['a.txt', 'b.txt', 'c.txt']; 
 
for (var i = 0; i < files.length; i++) { 
  fs.readFile(files[i], 'utf-8', function(err, contents) { 
    console.log(files[i] + ': ' + contents); 
  }); 
}

這是因爲回調函數太慢了,早都循環完了,纔會調用到回調函數,因此最後回調函數中的i都是等於循環結束時的i(不必定),因此會有相關問題,如下代碼將其作一個閉包封裝,以達到理想效果:

//forloopclosure.js 
 
var fs = require('fs'); 
var files = ['a.txt', 'b.txt', 'c.txt']; 
 
for (var i = 0; i < files.length; i++) { 
  (function(i) { 
    fs.readFile(files[i], 'utf-8', function(err, contents) { 
      console.log(files[i] + ': ' + contents); 
    }); 
  })(i); 
}

6.3 Node.js 應用部署

普通 Node.js 應用存在如下問題:

  • 不支持故障恢復
  • 沒有日誌
  • 沒法利用多核提升性能
  • 獨佔端口
  • 須要手動啓動

6.3.1 日誌功能

這個簡單,不作概述

6.3.2 故障恢復

使用cluster模塊,能夠實現多進程以及主進程與工做進程的結構.

6.3.3 自動啓動

製做一個啓動腳本便可

6.3.4 共享端口

虛擬主機便可

6.4 Node 不是銀彈

so,這個我知道,他不適合作

  • 計算密集型
  • 單用戶多任務
  • 邏輯複雜的事務
  • Unicode與國際化

Javascript 高級特性

起始位置
PDF 154
頁碼 148

Node.js 編程規範

起始位置:PDF 173頁碼 168

相關文章
相關標籤/搜索