Node是一個可讓JavaScript運行在服務器端的平臺,拋棄了傳統平臺依靠多線程來實現高併發的設計思路,而採用單線程、異步式I/O、事件驅動式的程序設計模型。javascript
安裝配置簡單,無需多說。html
用異步的方式讀取一個文本內容爲「test content」的文件,代碼以下:java
var fs = require("fs"); fs.readFile("test.txt", "utf-8", function(err, data) { console.log(data); }); console.log("end");
運行的結果:node
end test content
使用同步的方式讀取文件:python
var fs = require("fs"); console.log(fs.readFileSync("test.txt", "utf-8")); console.log("end");
運行結果:web
test content end
同步的方式是阻塞線程等待文件讀取完畢後,將文件內容輸出,而後才繼續執行下一步代碼,所以控制檯先打印「test content」,而後再打印「end」。正則表達式
而異步式I/O是經過回調函數來實現的,經過將控制檯輸出文件內容的函數做爲參數傳給fs.readFile函數,fs.readFile調用時只是將異步式I/O請求發送給操做系統,而後當即返回並執行後面的語句,當fs接收到I/O請求完成的事件時,纔會調用回調函數。所以咱們先看到「end」,再看到「test.txt」的內容。算法
Node.js全部的異步I/O操做在完成時都會發送一個事件到事件隊列。事件由EventEmitter提供,前面提到的fs.readFile的回調函數就是經過EventEmitter來實現。mongodb
Node提供了require函數來調用其餘模塊。node中的模塊和包並無本質的區別,包能夠理解爲某個功能模塊的集合。數據庫
模塊是node應用程序的基本組成部分,文件和模塊是一一對應的。一個node.js文件就是一個模塊,這個文件多是JavaScript代碼、JSON或者編譯過的C/C++擴展。
node提供了exports和require兩個對象,其中exports是模塊的公開接口,require用於從外部獲取一個模塊的接口。
新建一個myModule.js文件,輸入以下代碼:
var text; function setText(myText){ text = myText; } function printText(){ console.log(text); } exports.setText = setText; exports.printText = printText;
再新建一個test.js文件:
var myModule = require("./myModule"); myModule.setText("Hello world!"); myModule.printText();
運行test.js,結果爲:
Hello world!
模塊的調用與建立一個對象不一樣,require不會重複加載模塊。
修改test.js:
var myModule1 = require("./myModule"); myModule1.setText("Hello world!"); var myModule2 = require("./myModule"); myModule2.setText("Hi baby!"); myModule1.printText(); myModule2.printText();
運行結果:
Hi bayby! Hi bayby!
這是由於模塊不會重複加載,myModule1和myModule2指向的都是同一個實例,所以myModule1的text值就被myModule2覆蓋了。
包是模塊基礎上更深一步的抽象,相似於C/C++的函數庫或者java/.net的類庫。
node包是一個目錄,其中包括一個JSON格式的包說明文件package.json。node對包的要求並不嚴格,但仍是建議製做包時遵循CommonJS規範。
創建一個名爲mypackage的文件夾,在文件夾中新建index.js:
exports.hello = function(){ console.log("Hello world"); };
而後在mypackage以外創建test.js:
var mypackage = require("./mypackage"); mypackage.hello();
這就是一個簡單的包了,經過定製package.json,咱們能夠建立更復雜的包。
打開package.json並輸入以下json文本:
{
"main": "./lib/interface.js"
}
運行test.js,依然能夠看到控制檯輸出「Hello world」。
node調用某個包時,會首先檢查package.json文件的main字段,將其做爲包的接口模塊,若是package.json或main字段不存在,就會去尋找index.js或index.node做爲包的接口。
npm默認爲本地模式,會從http://npmjs.org搜索或下載包 ,將包安裝到當前目錄的node_modules
子目錄下。
也可使用全局安裝,它會將包安裝在系統目錄:
「npm [install/i] -g [package_name]」
本地模式能夠直接經過require使用,可是不會註冊環境變量。
全局模式不能直接經過require使用,可是它會註冊環境變量。
經過npm link命令能夠在本地包和全局包之間建立符號連接,使全局包能夠經過require使用,例如要require全局模式安裝的express,能夠在工程目錄下運行:
npm link express
使用npm link命令還能夠將本地包連接到全局,可是npm link命令不支持windows。
例如要調試test.js,則在命令行中輸入:
node debug test.js
如下爲基本的調試命令:
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 的版本
使用如下語句之一能夠打開調試服務器:
node --debug[=port] test.js //腳本正常執行,調試客戶端能夠鏈接到調試服務器。 node --debug-brk[=port] test.js //腳本暫停執行等待客戶端鏈接。
如不指定端口,則默認爲5858。
客戶端:
node debug 127.0.0.1:5858
安裝node-inspector:
npm install -g node-inspector
在終端鏈接調試服務器:
node --debug-brk=5858 test.js
啓動node-inspector:
node-inspector
在瀏覽器中打開http://127.0.0.1:8080/debug?port=5858。
在瀏覽器javascript中,一般window就是全局對象,而在node中全局對象是global,它是全局變量的宿主,全局變量是它的屬性。
用於描述當前node進程狀態,提供一個與操做系統的簡單接口。
process.argv:
命令行參數數組,第一個元素是node,第二個元素是腳本文件名,從第三個元素開始每一個元素都是一個運行參數。
process.stdout:
標準輸出流,一般咱們使用的console.log()向標準輸出打印字符,而process.stdout.write()函數提供更底層的接口。
process.stdin:
標準輸入流。
process.platform; process.pid; process.execPath; process.memoryUsage()
用於提供控制檯標準輸出。
console.log():
向標準流打印字符並以換行符結束,可使用相似C語言printf()命令的格式輸出。
console.error():
向標準錯誤流輸出。
console.trace():
向標準錯誤流輸出當前的調用棧。
用於提供經常使用函數集合。
實現對象間原型繼承的函數。
var util = require("util"); function Base() { this.name = "base"; this.base = 1991; this.sayHello = function(){ console.log("hello " + this.name); }; } Base.prototype.showName = function(){ console.log(this.name); }; function Sub(){ this.name = "sub"; } util.inherits(Sub, Base); var objBase = new Base(); objBase.showName(); objBase.sayHello(); console.log(objBase); var objSub = new Sub(); objSub.showName(); console.log(objSub);
輸出結果:
base Hello base { name: 'base', base: 1991, sayHello: [Function] } sub { name: 'sub' }
Sub只繼承了Base在原型中定義的函數,而函數內部的base屬性和sayHello函數都沒有被繼承。並且,在原型中定義的屬性不會被console.log()做爲對象的屬性輸出。
將任意對象轉換爲字符串,一般用於調試和錯誤輸出。
object:要轉換的對象。
showHidden:若是爲true,將會輸出更多隱藏信息。
depth:表示最大遞歸的層數,默認會遞歸2層,指定爲null表示不限制最大遞歸數完整遍歷對象。
color:若是爲true,輸出格式將會以ANSI顏色編碼。
util.inspect不會簡單的直接將對象轉換成字符成,即使是對象定義了toString方法也不會調用。
util.isArray(); util.isRegExp(); util.isDate(); util.isError(); util.format(); util.debug();
events是node最重要的模塊,由於node自己架構就是事件式的,而它提供了惟一的接口。
events.EventEmitter的核心就是事件發射與事件監聽器功能的封裝。
var events = require("events"); var emitter = new events.EventEmitter(); emitter.on("testEvent", function( arg1, arg2){ console.log(arg1, arg2); }); emitter.emit("testEvent", "test", 234);
以上爲EventEmitter基本用法。
經常使用API:
EventEmitter.on(event, listener):
爲指定事件註冊一個監聽器,接收一個字符串event和一個回調函數listener。
EventEmitter.emit(event, [arg1], [arg2], [...]):
發射event事件。
EventEmitter.once(event, listener):
爲指定事件註冊一個單次監聽器,監聽器觸發後馬上解除。
EventEmitter.removeListener(event, listener):
移除指定事件的某個監聽器。
EventEmitter.removeAllListener([event]):
移除全部事件的全部監聽器,若是指定event,則移除指定事件的全部監聽器。
遇到異常時一般會發射error事件,當error被髮射時,若是沒有響應的監聽器,Node會把它當作異常,退出程序並打印調用棧。
大多數時候不要直接使用EventEmitter,而是在對象中繼承它。這樣作使某個實體功能的對象實現事件符合語義,並且javascript的對象機制是基於原型的,支持部分多重繼承。
fs模塊是文件操做的封裝,fs模塊全部操做都提供了異步和同步兩個版本。
fs.readFile(filename, [encoding], [callback(err, data)]):
讀取文件,默認以二進制模式讀取。
fs.readFileSync(filename, [encoding]):
以同步方式讀取文件,讀取的文件內容以返回值的形式返回。
fs.open(path, flags, [mode], [callback(err, fd)]):
其中flags能夠是如下值:
mode參數用於建立文件時給文件指定權限,默認爲0666。回調函數將會傳遞一個文件描述符fd。
fs.read(fd, buffer, offset, length, postion, [callback(err, bytesRead)]):
從指定的文件描述符fd中讀取數據並寫入buffer指向的緩衝區對象。offset是buffer的寫入偏移量。length是要從文件中讀取的字節數。position是文件讀取的起始位置,若是爲null,則從當前文件指針的位置讀取。回調函數傳遞bytesRead和buffer,分別表示讀取的字節數和緩衝區對象。
node提供了http模塊。http.server是一個基於事件的HTTP服務器,http.request則是一個HTTP客戶端工具。
http.server是HTTP服務器對象。
http.createServer(callback(request, response)):
建立一個http.server的實例,將一個函數做爲HTTP請求處理函數。
request:
當客戶端請求到來時,該事件被觸發,提供兩個參數http.ServerRequest和http.ServerResponse的實例。事實上http.createServer的顯示實現方式就是request事件:
var http = require('http'); var server = new http.Server(); server.on('request', function(req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); res.write('<h1>Node.js</h1>'); res.end('<p>Hello World</p>'); }); server.listen(3000);
connection:
當TCP鏈接創建時,該事件被觸發,提供一個socket參數,是net.Socket的實例。客戶端在Keep-Alive模式下可能會在同一個鏈接內發送屢次請求。
close:
當服務器關閉時,該事件觸發。
是HTTP請求的信息。通常由http.server的request事件發送,做爲第一個參數傳遞。
HTTP請求通常能夠分爲兩部分:請求頭(Reqeust Header)和請求體(Request Body)。http.ServerRequest提供瞭如下3個事件用於控制Request Body傳輸:
ServerRequest的屬性:
返回給客戶端的信息,由http.Server的request事件發送,做爲第二個參數傳遞。
成員函數:
** response.writeHead(statusCode, [headers]):
向請求的客戶端發送響應頭。statusCode是HTTP狀態碼。該函數在一次請求中最多隻能調用一次。
response.write(data, [encoding]):
向請求的客戶端發送響應內容。data是一個buffer或字符串,表示要發送的內容。若是data是字符串,須要指定encoding來講明它的編碼方式,默認爲utf-8。
response.end([data],[encoding]):
結束響應,當全部要返回的內容發送完畢的時候,該函數必須**被調用一次,不然客戶端永遠處於等待狀態。
http.request(options, callback):
發起HTTP請求,返回一個http.ClientRequest的實例。callback是請求的回調函數,傳遞一個參數爲http.ClientResponse的實例。option經常使用的參數以下:
http.get(options, callback):
http.request的簡化版,用於處理GET請求,同時不須要手動調用req.end()。
http.clientRequest:
表示一個已經產生並且正在進行的HTTP請求。提供一個response事件。http.clientRequest函數:
http.clientResponse:
與http.ServerResponse相似,提供了三個事件data、end和close。http.clientResponse特殊函數:
運行命令:
npm install -g express
若是npm安裝慢的話能夠指定中國鏡像(更新比官方慢,發佈包時須要切回來):
npm config set registry https://registry.npm.taobao.org npm info underscore
windows下可能還須要安裝express-generator:
npm install -g express-generator
進入工程目錄,創建工程:
express -e projectName
控制檯輸出命令提示還要進入工程目錄,執行npm install,按照提示執行後,node根據package.json文件自動安裝了ejs。無參數的 npm install 的功能就是檢查當前目錄下的 package.json,並自動安裝全部指定的依賴。
啓動服務器。express 4.x已經不能用以前的node app.js來啓動了,而應該使用:
npm start
要使用supervisor監控文件更改自動重啓服務器,可使用:
supervisor ./bin/www
app.js是工程的入口,用var app = express()建立一個應用實例,app.set(key, value)是Express的參數設置工具,可用參數以下:
Express依賴於connect,提供大量中間件,能夠經過app.use啓用。
路由文件,至關於MVC中的控制器。
模板文件。
能夠給路由配置規則,如:
router.get('/:username', function(req, res, next) { res.send(req.params.username); });
路徑規則還支持JavaScript正則表達式,如:app.get(/user/([^\/]+)/?, callback),可是這種方式參數是匿名的,所以須要經過req.params[0]這樣的方式獲取。
HTTP協議定義瞭如下8種標準的方法:
咱們經常使用到的是四種,根據REST設計模式,這四種分別用於如下功能:
Express支持全部HTTP請求的綁定函數,如:app.get(path, callback)、app.post(path, callback)等。
其中還包括一個app.all(path, callback)函數,它支持吧全部的請求方式綁定到同一個響應函數。
Express還支持同一路徑綁定多個路由響應函數,例:
var express = require('express'); var router = express.Router(); /* GET users listing. */ router.all('/:username', function(req, res, next) { res.send("all"); }); router.get('/:username', function(req, res, next) { res.send(req.params.username); }); module.exports = router;
若是訪問路徑同時匹配多個規則時,會優先匹配先定義的路由規則,請求總會被前一條路由規則捕獲,後一條被忽略,所以上面代碼將輸出「all」。
可是也能夠調用next,會將路由優先給後面的規則:
var express = require('express'); var router = express.Router(); /* GET users listing. */ router.all('/:username', function(req, res, next) { console.log("all"); next(); res.send("all"); }); router.get('/:username', function(req, res, next) { res.send(req.params.username); }); module.exports = router;
這時回先在控制檯打印「all」,而後被next()函數轉移,接着被第二條規則捕獲,向瀏覽器發送信息。
這一點很是有用,可讓咱們很是輕鬆實現中間件,提升代碼的複用,如:
var users = { 'byvoid': { name: 'Carbo', } }; app.all('/user/:username', function (req, res, next) { // 檢查用戶是否存在 if (users[req.params.username]) { next(); } else { next(new Error(req.params.username + ' does not exist.')); } }); app.get('/user/:username', function (req, res) { // 用戶必定存在,直接展現 res.send(JSON.stringify(users[req.params.username])); }); app.put('/user/:username', function (req, res) { // 修改用戶信息 res.send('Done'); });
這裏使用的ejs引擎,設置視圖的路徑及模板引擎的類型:
app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs');
在routers/index.js下使用render調用:
res.render('index', { title: 'Index' });
ejs只有3種標籤:
開源的NoSQL數據庫,適合數據規模大、事務性不強的場合。
NoSQL是Not Only SQL的簡稱,主要指非關係型、分佈式、不提供ACID的數據庫系統。
MongoDB是一個對象數據庫,沒有表、行等概念,也沒有固定的模式和結構,全部數據以文檔的形式存儲。所謂文檔就是一個關聯數組式的對象,它的內部由屬性組成,一個屬性對應的值多是一個數、字符串、日期、數組,甚至是一個嵌套的文檔。例:
{ "_id": ObjectId( "4f7fe8432b4a1077a7c551e8" ), "uid": 2004, "username": "byvoid", "net9": { "nickname": "BYVoid", "surname": "Kuo", "givenname": "Carbo", "fullname": "Carbo Kuo", "emails": [ "byvoid@byvoid.com", "byvoid.kcp@gmail.com" ], "website": "http://www.byvoid.com", "address": "Zijing 2#, Tsinghua University" } }
單線程特性。
如本地的命令行工具或圖形界面。
發揮不出高併發的優點,不擅長處理多進程相互協做。
Node.js控制流不是線性的,它被一個個事件拆散。Node更擅長處理邏輯簡單但訪問頻繁的任務。
不支持完整的Unicode。
不一樣於大多類C語言,由一對花括號封閉的代碼塊就是一個做用域,javascript的做用域是經過函數來定義的,在一個函數中定義的變量只對這個函數內部可見。在函數引用一個變量時,javascript會先搜索當前函數做用域,若是沒有則搜索其上層做用域,一直到全局做用域。例:
var scope = 'global'; var f = function () { console.log(scope); // 輸出 undefined var scope = 'f'; } f();
最後輸出結果不是global,而是undefined。在console.log函數訪問變量scope時,javascript會先搜索函數f的做用域,結果找到了var scope = 'f',因此就不會再往上層去找,但執行到console.log時,scope尚未被定義,因此是undefined。
全局做用域中的變量不論在什麼函數中均可以被直接引用,而沒必要經過全局對象。知足如下條件的變量屬於全局做用域:
須要格外注意的是第三點,在任何地方隱式定義的變量都會定義在全局做用域中,即不經過 var 聲明直接賦值的變量。這一點常常被人遺忘,而模塊化編程的一個重要原則就是避免使用全局變量,因此咱們在任何地方都不該該隱式定義變量。
閉包是由函數(環境)及其封閉的自由變量組成的集合體。
javascript中每個函數都是一個閉包,但一般意義上嵌套的函數更能體現閉包的特性。
var generateClosure = function () { var count = 0; var get = function () { count++; return count; }; return get; }; var counter1 = generateClosure(); var counter2 = generateClosure(); console.log(counter1()); // 輸出 1 console.log(counter2()); // 輸出 1 console.log(counter1()); // 輸出 2 console.log(counter1()); // 輸出 3 console.log(counter2()); // 輸出 2
閉包的特性,當一個函數返回它內部定義的一個函數時,就產生了一個閉包,閉包不但包括被返回的函數,還包括這個函數的定義環境。counter1和counter2分別調用了generateClosure()函數,生成兩個閉包實例,它們內部引用的count變量分別屬於各自的運行環境。咱們能夠理解成,在generateClosure()返回get函數時,私下將get可能引用到的generateClosure()函數的內部變量(也就是count變量)也返回了,並在內存中生成了一個副本。
閉包兩個主要用途,一是實現嵌套的回調函數,二是隱藏對象的細節。
exports.add_user = function (user_info, callback) { var uid = parseInt(user_info['uid']); mongodb.open(function (err, db) { if (err) { callback(err); return; } db.collection('users', function (err, collection) { if (err) { callback(err); return; } collection.ensureIndex("uid", function (err) { if (err) { callback(err); return; } collection.ensureIndex("username", function (err) { if (err) { callback(err); return; } collection.findOne({ uid: uid }, function (err) { if (err) { callback(err); return; } if (doc) { callback('occupied'); } else { var user = { uid: uid, user: user_info, }; collection.insert(user, function (err) { callback(err); }); } }); }); }); }); }); };
每一層的嵌套都是一個回調函數,回調函數不會當即執行,而是等待相應請求處理完後由請求的函數回調。咱們能夠看到在嵌套的每一層都有對callback的引用,而最裏層還用到了外層定義的uid變量。因爲閉包的存在,即便外層函數已經執行完畢,其做用域內申請的變量也不會釋放。
javascript對象沒有私有屬性,只能經過閉包實現。
var generateClosure = function () { var count = 0; var get = function () { count++; return count; }; return get; }; var counter1 = generateClosure(); var counter2 = generateClosure(); console.log(counter1()); // 輸出 1 console.log(counter2()); // 輸出 1 console.log(counter1()); // 輸出 2 console.log(counter1()); // 輸出 3 console.log(counter2()); // 輸出 2
只有調用counter()才能訪問到閉包內的count變量。
javascript中的對象不是基於類的實例的,而是基於原型。
javascript對象實際上就是一個由屬性組成的關聯數組,屬性由名稱和值組成,值的類型能夠是任何數據類型,或者函數和其餘對象。javascript具備函數式編程的特性,因此函數也是一種變量。
javascript中能夠用如下方法建立一個簡單的對象:
var foo = {}; foo.prop_1 = 'bar'; foo.prop_2 = false; foo.prop_3 = function(){ return 'hello world'; } console.log(foo.prop_3());
var foo = {}; foo['prop_1'] = 'bar'; foo['prop_2'] = false; foo['prop_3'] = function(){ return 'hello world'; } console.log(foo['prop_3']);
var foo = { prop1: 'bar', prop2: 'false', prop3: function (){ return 'hello world'; } };
function User(name, uri) { this.name = name; this.uri = uri; this.display = function() { console.log(this.name); } }
使用new來建立對象:
var user1 = new User('name', 'www.google.com');
javascript中,上下文對象就是this指針,即被調用函數所處的環境。上下文對象的做用是在一個函數內部引用調用它的對象本事。
var someuser = { name: 'byvoid', display: function () { console.log(this.name); } }; someuser.display(); // 輸出 byvoid var foo = { bar: someuser.display, name: 'foobar' }; foo.bar(); // 輸出 foobar
this指針不屬於某個函數,而是函數調用時所屬的對象。
javascript的函數式編程特性使得函數能夠像通常的變量同樣賦值、傳遞和計算。
var someuser = { name: 'byvoid', func: function () { console.log(this.name); } }; var foo = { name: 'foobar' }; someuser.func(); // 輸出 byvoid foo.func = someuser.func; foo.func(); // 輸出 foobar name = 'global'; func = someuser.func; func(); // 輸出 global
使用不一樣的引用來調用同一個函數,this指針永遠是這個引用所屬的對象。
call和apply的功能是以不一樣的對象做爲上下文來調用某個函數。簡而言之,就是容許一個對象去調用另外一個對象的成員函數。
call和apply的功能是一致的,二者細微的差異在於call以參數表來接受被調用函數的參數,而apply以數組來接受被調用函數的參數。
func.call(thisArg[, arg1[, arg2[, ...]]]) func.apply(thisArg[, argsArray])
看一個call的例子:
var someuser = { name: 'byvoid', display: function (words) { console.log(this.name + ' says ' + words); } }; var foo = { name: 'foobar' }; someuser.display.call(foo, 'hello'); // 輸出 foobar says hello
someuser.display是被調用的函數,它經過call將上下文改變爲foo對象,所以函數體內訪問this.name時,實際上訪問的是foo.name,於是輸出foobar。
使用call和apply方法能夠改變被調用函數的上下文,而使用bind能夠永久地綁定函數的上下文,使其不管被誰調用,上下文都是固定的。
func.bind(thisArg[, arg1[, arg2[, ...]]])
例:
var someuser = { name: 'byvoid', func: function() { console.log(this.name); } }; var foo = { name: 'foobar' }; foo.func = someuser.func; foo.func(); // 輸出 foobar foo.func1 = someuser.func.bind(someuser); foo.func1(); // 輸出 byvoid func = someuser.func.bind(foo); func(); // 輸出 foobar func2 = func; func2(); // 輸出 foobar
bind還有一個重要功能:綁定參數。
var person = { name: 'byvoid', says: function (act, obj) { console.log(this.name + ' ' + act + ' ' + obj); } }; person.says('loves', 'diovyb'); // 輸出 byvoid loves diovyb byvoidLoves = person.says.bind(person, 'loves'); byvoidLoves('you'); // 輸出 byvoid loves you
byvoidLoves將this指針綁定到了person,並將第一個參數綁定到loves,以後再調用byvoidLoves時,只須要傳入obj參數。這樣能夠在代碼多出調用時省略重複輸入相同的參數。
function Person() { } Person.prototype.name = 'BYVoid'; Person.prototype.showName = function () { console.log(this.name); }; var person = new Person(); person.showName();
上面代碼使用原型初始化對象,這樣與直接在構造函數內定義屬性的區別:
構造函數內定義的函數有運行時閉包的開銷。
function Foo() {
var innerVar = 'hello';
this.prop1 = 'BYVoid';
this.func1 = function () {
innerVar = '';
};
}
Foo.prototype.prop2 = 'Carbo';
Foo.prototype.func2 = function () {
console.log(this.prop2);
};
var foo1 = new Foo();
var foo2 = new Foo();
console.log(foo1.func1 == foo2.func1); // 輸出 false
console.log(foo1.func2 == foo2.func2); // 輸出 true
原型和構造函數適用場景:
javascript中有兩個特殊對象:Object和Function,它們都是構造函數,用於生產對象。Object.prototype是全部對象的祖先,Function.prototype是全部函數的原型,包括構造函數。
任何對象都有一個_proto_屬性,它指向該對象的原型,從任何對象沿着它開始遍歷均可以追溯到Object.prototype。
構造函數對象的prototype屬性,指向一個原型對象。原型對象有constructor屬性,指向它的構造函數。
function Foo() { } Object.prototype.name = 'My Object'; Foo.prototype.name = 'Bar'; var obj = new Object(); var foo = new Foo(); console.log(obj.name); // 輸出 My Object console.log(foo.name); // 輸出 Bar console.log(foo.__proto__.name); // 輸出 Bar console.log(foo.__proto__.__proto__.name); // 輸出 My Object console.log(foo.__proto__.constructor.prototype.name); // 輸出 Bar
javascript沒有C語言中的指針,全部對象類型的變量都是指向對象的引用,兩個變量之間的賦值是傳遞引用。若是須要完整地複製一個對象,就須要手動實現這樣的函數,一個簡單的作法就是複製對象的全部屬性。
Object.prototype.clone = function() { var newObj = {}; for (var i in this) { newObj[i] = this[i]; } return newObj; } var obj = { name: 'byvoid', likes: ['node'] }; var newObj = obj.clone(); obj.likes.push('python'); console.log(obj.likes); // 輸出 [ 'node', 'python' ] console.log(newObj.likes); // 輸出 [ 'node', 'python' ]
上面代碼是一個對象的淺拷貝,只複製基本類型的屬性。實現深拷貝須要使用遞歸的方式來實現:
Object.prototype.clone = function () { var newObj = {}; for (var i in this) { if (typeof (this[i]) == 'object' || typeof (this[i]) == 'function') { newObj[i] = this[i].clone(); } else { newObj[i] = this[i]; } } return newObj; }; Array.prototype.clone = function () { var newArray = []; for (var i = 0; i < this.length; i++) { if (typeof (this[i]) == 'object' || typeof (this[i]) == 'function') { newArray[i] = this[i].clone(); } else { newArray[i] = this[i]; } } return newArray; }; Function.prototype.clone = function () { var that = this; var newFunc = function () { return that.apply(this, arguments); }; for (var i in this) { newFunc[i] = this[i]; } return newFunc; }; var obj = { name: 'byvoid', likes: ['node'], display: function () { console.log(this.name); }, }; var newObj = obj.clone(); newObj.likes.push('python'); console.log(obj.likes); // 輸出 [ 'node' ] console.log(newObj.likes); // 輸出 [ 'node', 'python' ] console.log(newObj.display == obj.display); // 輸出 false
上面方法在大多數狀況下都沒有問題,可是遇到相互引用的對象時就會進入死循環,如:
var obj1 = { ref: null }; var obj2 = { ref: obj1 }; obj1.ref = obj2;
遇到這個問題必須設計一套圖論算法,分析對象之間的依賴關係,而後分別一次複製每一個頂點,並從新創建它們之間的關係。
由於node.js中很容易寫出深層的函數嵌套,所以選擇兩空格縮進。
80字符。
使用分號。
永遠使用var定義變量。
經過賦值隱式變量老是全局變量,會形成命名空間污染。
小駝峯式命名法。
通常的函數使用小駝峯式命名法。但對於對象的構造函數名稱,使用大駝峯式命名法。
使用單引號,由於JSON、XML都規定了必須是雙引號,這樣便於無轉義地直接引用。
除非鍵名之中有空格或非法字符,不然一概不使用引號。
儘可能使用===而不是==,由於==包含了隱式類型轉換,不少時候可能與預期不一樣。
var num = 9; var literal = '9'; if (num === literal) { console.log('9==="9"'); } var num = 9; var literal = '9'; if (num == literal) { console.log('9=="9"'); }
輸出
9=="9"
儘可能給構造函數和回調函數命名,這樣調試時能夠看見更清晰的調用棧。
對於回調函數,第一個參數是錯誤對象err,若是沒有錯誤發生,其值爲undefined。
儘可能將全部的成員函數經過原型定義,將屬性在構造函數內定義,而後對構造函數使用new關鍵字建立對象。
避免使用複雜的繼承,儘可能使用Node.js的util模塊中提供的inherits函數。