安装过程就不说了。如果成功是能使用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..."
);
}
|