最近決定重拾node.js,用它來做一個合並JS文件的東西。由於忘得差不多了,先看能不能輸出一個頁面來再說。以下是我的一些筆記,省得以後又忘淨光……
安裝過程就不說了。如果成功是能使用node的命令。node.js調試是非常方便的。每種後台語言都有一個向那個黑黢黢的控制台團輸出語用的命令。node.js沿用FF那套東西,也就是console對象與其方法。我們首先建一個example.js文件,內容如下,然後在控制台打開它。
console.log(
"hello node.js"
)
for
(
var
i
in
console){
console.log(i+
" "
+console[i])
}
node example.js。
你千萬不要在node.js使用alert進行調試,那是浏覽器帶的全局方法,不報錯才怪。
輸出結果如下:
var
log =
function
() {
process.stdout.write(format.apply(
this
, arguments) +
'\n'
);
}
var
info =
function
() {
process.stdout.write(format.apply(
this
, arguments) +
'\n'
);
}
var
warn =
function
() {
writeError(format.apply(
this
, arguments) +
'\n'
);
}
var
error =
function
() {
writeError(format.apply(
this
, arguments) +
'\n'
);
}
var
dir =
function
(object) {
var
util = require(
'util'
);
process.stdout.write(util.inspect(object) +
'\n'
);
}
var
time =
function
(label) {
times[label] = Date.now();
}
var
timeEnd =
function
(label) {
var
duration = Date.now() - times[label];
exports.log(
'undefined: NaNms'
, label, duration);
}
var
trace =
function
(label) {
// TODO probably can to do this better with V8's debug object once that is
// exposed.
var
err =
new
Error;
err.name = 'Trace
';
err.message = label || '
';
Error.captureStackTrace(err, arguments.callee);
console.error(err.stack);
}
var assert = function (expression) {
if (!expression) {
var arr = Array.prototype.slice.call(arguments, 1);
require('
assert').ok(
false
, format.apply(
this
, arr));
}
}
通過這些函數,我們大概了解到node.js在全局作用域添加了些什麼,如require, process。但也不能武斷說是,因為它們可能是某個作用域的私有對象。不過,了解這些全局對象,並從這些對象上出發去了解其他對象,非常有助於我們了解node.js的生態結構。在前端,每當浏覽器升級,我就遍歷一下window對象以及其個元素節點就得知它又增加了什麼方法與屬性,然後再查文檔。那些更新日志不可能把全部細節都告訴你的,必須自己動手遍歷一下,這樣你就比別人知道得更多。好了,我們去找node.js的全局對象。
node.js的文檔告訴我們,有如下幾個全局對象:
global, process, require,__filename,__dirname, module
但我們為什麼能直接使用console.log呢?經驗告訴我們,console肯定是某全局對象的成員,正如我們可以alert, 也可以window.alert。好了,我們選遍歷一下global這個名字取得非常霸氣的對象
for
(
var
i
in
global){
console.log(
"var "
+ i+
" = "
+global[i])
}
結果如下:
var
global = [object global]
var
process = [object EventEmitter]
var
GLOBAL = [object global]
var
root = [object global]
var
Buffer =
function
Buffer(subject, encoding, offset) {
//太長了,省略
}
var
setTimeout =
function
() {
var
t = NativeModule.require(
'timers'
);
return
t.setTimeout.apply(
this
, arguments);
}
var
setInterval =
function
() {
var
t = NativeModule.require(
'timers'
);
return
t.setInterval.apply(
this
, arguments);
}
var
clearTimeout =
function
() {
var
t = NativeModule.require(
'timers'
);
return
t.clearTimeout.apply(
this
, arguments);
}
var
clearInterval =
function
() {
var
t = NativeModule.require(
'timers'
);
return
t.clearInterval.apply(
this
, arguments);
}
var
console = [object Object]
發現global與浏覽器的window一樣,都有個指向自身的同名成員。window === window.window, global === global.global。但node.js早期設計得不好,又一搞了個多余的GLOBAL成員。
console.log(global === global.global)
//true
console.log(global === global.GLOBAL)
//true
我們再遍歷module對象:
for
(
var
i
in
module){
console.log(
"var "
+ i +
" = "
+module[i])
}
結果如下:
var
id = .
var
exports = [object Object]
var
parent =
null
var
filename = /home/cheng19840218/node/example.js
var
loaded =
false
var
exited =
false
var
children =
var
paths = /home/cheng19840218/node/node_modules,/home/cheng19840218/node_modules,/home/node_modules,/node_modules
var
load =
function
(filename) {
//太長了,省略
}
var
_compile =
function
(content, filename) {
//太長了,省略
}
原來那個著名的exports是在此提供的,__filename大概也是filename的引用。只要遍歷一下,你就發現許多有趣的東西。但別以為一下秘密就暴光在你眼皮下,還有許多不可遍歷屬性。比如上面我遍歷global對象,只有尞尞可數幾個成員,我們可以使用ecma262v5新增的方法去考察一下:
console.log(Object.getOwnPropertyNames(global))
結果如下:
[
'clearInterval'
,
'TypeError'
,
'decodeURI'
,
'Buffer'
,
'parseFloat'
,
'Number'
,
'URIError'
,
'encodeURIComponent'
,
'RangeError'
,
'ReferenceError'
,
'RegExp'
,
'Array'
,
'isNaN'
,
'setTimeout'
,
'console'
,
'Date'
,
'Infinity'
,
'Boolean'
,
'Error'
,
'root'
,
'NaN'
,
'String'
,
'Function'
,
'Math'
,
'undefined'
,
'encodeURI'
,
'escape'
,
'unescape'
,
'process'
,
'decodeURIComponent'
,
'EvalError'
,
'clearTimeout'
,
'GLOBAL'
,
'setInterval'
,
'SyntaxError'
,
'Object'
,
'eval'
,
'global'
,
'parseInt'
,
'JSON'
,
'isFinite'
]
許多人學node.js就立即看其文檔,殊不知node.js本身所依賴的V8引擎就擁有許多要學的東西,這其中包括ecma262v5帶來的新方法新對象,還有效仿firefox的一些語法:
__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
set
get
__proto__
不過以"__"開頭的東西我是不建議用的,像set與get現在最新的浏覽器都支持,如IE9,可以在其開發人員工具下試試下面的腳本:
var
a = {
get latest () {
if
(
this
.log.length > 0) {
return
this
.log[
this
.log.length - 1];
}
else
{
return
null
;
}
},
log: []
}
a.log[0] =
"a"
;
a.log[1] =
"b"
;
console.log(a.latest)
在node.js基本上沒有兼容問題(如果你不是從早期的node.js玩起來),而且原生對象又加了這麼多擴展,再加上node.js自帶的庫,每個模塊都提供了花樣繁多的API,如果還嫌不夠,github上還有上千個插件。對於想向嘗試一下後端編程的JSer來說,這是極具誘惑力的。可能有人說,後端不是涉及數據庫操作嗎?這與比前端的DOM兼容比起來,不值一提。還有什麼文件夾與文件操作 ,你就當成是一種特殊的數組操作就是。因此你完全可以憤憤不平!
好了,我們來點實質的內容吧。node.js本來就是一個http服務器,它是要與前端交互的,因此少不了兩個對象:請求(request)與響應(response)。請求與響應顯然一種異步的東西,因為我們 不知道前端什麼時候發請求過來,響應也不能立即給前端,還要做日志,讀寫數據庫等操作呢。因此對於javascript來說,這用回調函數來實現最好。那麼由誰來接受這個回調呢?一個服務器對象!
var
http = require(
"http"
);
http.createServer(
function
(request, response) {
response.writeHead(200, {
"Content-Type"
:
"text/plain"
});
response.write(
"Hello node.js"
);
response.end();
}).listen(8888);
node.js有個特殊的require,用於同步加載其他模塊的對象,這與其他語言的require, import差不多。能同步就是好,不像前端那樣一層套一層。然後利用一個函數去實例化一個服務器對象,然後監聽8888端口。這是node.js官網最初的例子,大家都寫爛了。但這樣的程序在現實中一無是處,我們在地址欄上輸入URL,你起碼要返回一個完整頁面給我吧!
對此,我們首先要進行模塊化。模塊化是以文件為單位的,把example.js更名為server.js,然後再把裡面的內容改為一個模塊。對於一個node.js的文件,其實它裡面的內容是在一個封閉的環境中執行。要想共享給其他模塊使用,就必須綁定在exports對象上。
var
http = require(
"http"
);
exports.start =
function
(){
http.createServer(
function
(request, response) {
console.log(
"Request received..."
);
response.writeHead(200, {
"Content-Type"
:
"text/plain"
});
response.write(
"Hello node.js"
);
response.end();
}).listen(8888);
console.log(
"server start..."
);
}
然後我們再建一個index.js作為入口(index.js與server.js放在同一目錄下)。
var
server = require(
"./server"
);
server.start();
然後建一個index.html頁面。
<!doctype html>
<
html
>
<
head
>
<
title
>index</
title
>
<
meta
content
=
"IE=8"
http-equiv
=
"X-UA-Compatible"
/>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=UTF-8"
>
</
head
>
<
body
>
<
h2
>這是首頁</
h2
>
</
body
>
</
html
>
現在我們就在要請求過來時,把此頁的內容讀出來,返給用戶。這時我們就要用到fs模塊的方法了。
var
http = require(
"http"
);
var
fs = require(
'fs'
);
exports.start =
function
(){
http.createServer(
function
(request, response) {
fs.readFile(
'./index.html'
,
'utf-8'
,
function
(err, data) {
//讀取內容
if
(err)
throw
err;
response.writeHead(200, {
"Content-Type"
:
"text/html"
});
//注意這裡
response.write(data);
response.end();
});
}).listen(8888);
console.log(
"server start..."
);
}
好了,這時我們重啟再次輸入地址,就看到一個完整的頁面了。
但一個頁面除了HTML結構層外,還有javascript與css。那麼,我們在當前目錄建一個文件夾javascripts, 裡面建index.js,內容如下:
window.onload =
function
(){
var
p = document.createElement(
"p"
);
p.innerHTML =
"這是動態添加的"
document.body.appendChild(p);
}
再建一個styles目錄,裡面建index.css,內容如下:
html,body{
background:
#3671A5;
height: 100%
}
然後在index.html引入這兩個文件:
<!doctype html>
<
html
>
<
head
>
<
title
>index</
title
>
<
meta
content
=
"IE=8"
http-equiv
=
"X-UA-Compatible"
/>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=UTF-8"
>
<
link
type
=
"text/css"
rel
=
"stylesheet"
href
=
"styles/index.css"
/>
<
script
src
=
"/javascripts/index.js"
></
script
>
</
head
>
<
body
>
<
h2
>這是首頁</
h2
>
</
body
>
</
html
>
重新打開,發現沒有改變,google,說要處理js與css文件的請求。沒有辦法,取得request.url屬性,再判定後綴名,為它進行文件讀取與設置首部。
var
http = require(
"http"
);
var
fs = require(
'fs'
);
var
url = require(
'url'
);
exports.start =
function
(){
http.createServer(
function
(request, response) {
var
pathname = url.parse(request.url).pathname;
var
ext = pathname.match(/(\.[^.]+|)$/)[0];
//取得後綴名
switch
(ext){
case
".css"
:
case
".js"
:
fs.readFile(
"."
+request.url,
'utf-8'
,
function
(err, data) {
//讀取內容
if
(err)
throw
err;
response.writeHead(200, {
"Content-Type"
: {
".css"
:
"text/css"
,
".js"
:
"application/javascript"
,
}[ext]
});
response.write(data);
response.end();
});
break
;
default
:
fs.readFile(
'./index.html'
,
'utf-8'
,
function
(err, data) {
//讀取內容
if
(err)
throw
err;
response.writeHead(200, {
"Content-Type"
:
"text/html"
});
response.write(data);
response.end();
});
}
}).listen(8888);
console.log(
"server start..."
);
}
至此,本文的目的達到了。三個node.js文件,一個普通的js文件,一個css文件,一個html文件。下一個目的就是多頁了,一個網站是由多個目的構成的。它包含如下內容,能處理ajax請求,上傳文件,Session與Cookie支持,日志,MIME識別,路由派發,緩存系統......要做的事多得嚇人,因此有人一上來就框架,與學JS那樣,連API還沒有摸熟就用jQuery了,那學個毛!回顧一下我們上面的server.js中間的部分,其實就要把MIME與路由拆分出來的。但最重要的事還有一樣,如何處理這無窮的函數嵌套?本人覺得這與我的模塊加載系統還沒有什麼兩樣,下次就從這裡動手吧。