第一個 Node.js 的服務器應用
Node.js 官方提供的幫助文檔中,提供了一個非常適合入門的示例代碼。可以幫助我們快速了解 Node.js 的主要作用。
1. 創建 example.js 文件,並將官方幫助文檔提供的代碼進行粘貼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode =
200;
res.setHeader(
'Content-Type', 'text/plain');
res.end(
'Hello World\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
|
2. 打開命令行窗口,輸入如下 node 命令,運行 example.js:
1
|
node example.js
|
需要注意的是:上述命令必須在命令行模式下,進入到 example.js 文件所在的目錄。
命令運行成功后,在命令行窗口會看到如下效果:
3. 打開瀏覽器,在地址欄輸入命令行窗口提供的地址,訪問 Node.js 服務:
1
|
http://127.0.0.1:3000
|
由於所有示例代碼 Node.js 官方幫助文檔提供了,所以運行演示的操作步驟非常簡單。但,我們不能忽略其中的一些細節。
仔細閱讀上述示例代碼,我們會發現其中使用了很多有關 ECMAScript 2015 規范中的新內容。那接下來,就讓我們一一來了解一下吧。
const
、let
和var
的區別
首先,我們可以發現,在 Node.js 的官方幫助文檔提供的示例代碼中,大量地使用了 const
關鍵字。
1
2
3
4
5
6
|
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer()
|
const
關鍵字是 ECMAScript 2015 規范中的新內容,是用來定義常量的。在 ECMAScript 2015 規范中還新增了 let
關鍵字,來替換原本的 var
關鍵字。
const
、let
和 var
關鍵字,都是用來在 JavaScript 中定義變量的。
1. 關於 JavaScript 的變量
變量是具有名字存儲數據信息的容器。在代碼中,使用變量名為值命名,需要遵守一定的規則。
值得注意的是:
- 在 JavaScript 代碼中,必須先聲明一個變量,這個變量才能被使用。
- JavaScript 中的變量是弱類型的,也稱之為松散類型的。所謂弱類型/松散類型就是可以用來保存任何類型的數據。
1
2
|
var v = 100;
v =
"string";
|
2. 變量的聲明問題
1)重復聲明
使用 var
關鍵字重復聲明變量是合法且無害的。但是如果重復聲明並初始化的,這就表示重復聲明並初始化。由於 JavaScript 變量只能存儲一個數據,之前存儲的數據會被覆蓋。
1
2
|
var msg = "this is message";// 值為 this is message
var msg = 100;// 值為 100
|
2)遺漏聲明
- 直接讀取一個沒有聲明的變量的值,JavaScript 會報錯。
1
|
console.log(str);
|
上述示例代碼,直接讀取了一個名為 str 的變量,但該變量並沒有聲明。所以,JavaScript 會報如下錯誤:
1
|
ReferenceError: str is not defined
|
- 為一個沒有聲明的變量初始化,是合法的,但並不推薦這樣使用。
3)聲明提前
JavaScript 變量的另一特別之處是,你可以引用稍后聲明的變量,而不會引發異常。這一概念稱為變量聲明提升。
1
2
3
|
console.log( msg );// 不會報錯,輸出 undefined
var msg = "this is message";// 定義全局變量 msg
console.log( msg );// 輸出 this is message
|
上述代碼中的第一行輸出不會報錯,而是輸出 undefined值。效果等同於如下述代碼:
1
2
3
4
|
var msg;// 定義全局變量 msg,但未初始化
console.log( msg );// 不會報錯,輸出 undefined
msg =
"this is message";// 初始化全局變量 msg
console.log( msg );// 輸出 this is message
|
3. let
是更完美的 var
1)let
擁有塊級作用域
在 ECMAScript 2015 規范發布之前,JavaScript 只存在全局作用域和函數作用域。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var v1 = 'this is global variable';
function fn(){
var v2 = 'this is function variable';
console.log('v1 in function scope: '+v1);
console.log('v2 in function scope: '+v2);
}
fn();
// 在函數作用域中調用全局變量和局部變量
// 在全局作用域中調用全局變量和局部變量
console.log('v1 in global scope: '+v1);
console.log('v2 in global scope: '+v2);
|
上述示例代碼,運行時會報如下錯誤:
1
2
3
4
5
6
7
8
|
v1in function scope: this is global variable
v2in function scope: this is function variable
v1in global scope: this is global variable
scope.js:13
console.log('v2in global scope: '+v2);
^
ReferenceError: v2 is not defined
|
上述報錯的原因在於 v2
變量是被定義在 fn
函數中,是局部變量,並不能在全局作用域被調用。
接下來,我們再看另外一個示例:
1
2
3
4
|
for(var i=0;i<=9;i++){
console.log('use var define variable i in function scope: '+i);
}
console.log('use var define variable i in global scope: '+i);
|
上述示例代碼的運行結果如下:
上述結果表明,在 for
循環語句中定義的 i
變量是一個全局變量,因為在 ECMAScript 2015 之前,JavaScript 並不存在塊級作用域。
而將上述示例代碼中,定義 i
變量的關鍵字 var
改成 let
,會有什么變化呢?
1
2
3
4
|
for(let i=0;i<=9;i++){
console.log('use var define variable i in function scope: '+i);
}
console.log('use var define variable i in global scope: '+i);
|
上述修改過的示例代碼,運行時會報如下錯誤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
use var define variable i in function scope: 0
use var define variable i in function scope: 1
use var define variable i in function scope: 2
use var define variable i in function scope: 3
use var define variable i in function scope: 4
use var define variable i in function scope: 5
use var define variable i in function scope: 6
use var define variable i in function scope: 7
use var define variable i in function scope: 8
use var define variable i in function scope: 9
/Users/king/node_and_mongoDB_in_action/01_first_node_demo/block_scope.js:10
console.log('use var define variable i in global scope: '+i);
^
ReferenceError: i is not defined
|
根據上述報錯信息,我們可以知道在 for
循環外打印的 i
變量未定義。換句話講,就說明 i
變量只作用於 for
循環語句內部,而不是全局作用域。
像上述示例中的 i
變量的作用域,我們就可以稱之為 塊級作用域。
2)let
不存在聲明提前
let
與 var
的第二個區別在於,使用 let
關鍵字聲明的變量,是不存在聲明提前的。
1
2
3
|
console.log( msg );
let msg = "this is message";
console.log( msg );
|
上述示例代碼,運行是回報如下錯誤:
1
|
ReferenceError: msg is not defined
|
根據上述報錯信息,表示使用 let
定義變量時,必須先聲明,后調用。
3) let
不允許重復聲明
let
與 var
的第三個區別在於,使用 let
關鍵字聲明的變量,是不允許重復聲明的。
1
2
3
4
|
let msg = "this is message";
console.log( msg );
let msg = "this is msg too";
console.log(msg);
|
上述示例代碼,運行時會報如下錯誤:
1
|
SyntaxError: Identifier 'msg' has already been declared
|
根據上述報錯信息,表示使用 let
定義變量時,只允許聲明一次,不能重復聲明。
4. const
定義常量
const聲明的變量只可以在聲明時賦值,不可隨意修改。
1)const
聲明時必須賦值
1
|
const theFairest;
|
上述示例代碼,運行時會報如下錯誤:
1
|
SyntaxError: Missing initializer in const declaration
|
2) const
定義的值不能改變
1
2
3
4
5
6
7
8
9
|
// 定義常量MY_FAV並賦值7
const MY_FAV = 7;
// 在 Firefox 和 Chrome 這會失敗但不會報錯(在 Safari這個賦值會成功)
MY_FAV =
20;
console.log(MY_FAV); // 輸出 7
const MY_FAV = 20; // 嘗試重新聲明會報錯
var MY_FAV = 20;// MY_FAV 保留給上面的常量,這個操作會失敗
console.log(MY_FAV);// MY_FAV 依舊為7
|
上述示例代碼,運行時會報如下錯誤:
1
|
SyntaxError: Identifier 'MY_FAV' has already been declared
|
箭頭函數
在 Node.js 的官方幫助文檔提供的示例代碼中,我們可以看到如下形式的函數:
1
2
3
4
5
6
7
8
9
|
http.createServer(
(req, res) => {
res.statusCode =
200;
res.setHeader(
'Content-Type', 'text/plain');
res.end(
'Hello World\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
|
上述示例中的函數形式,看起來很怪異,我們將其進行改寫:
1
2
3
4
5
6
7
8
9
|
const server = http.createServer(function(req, res){
res.statusCode =
200;
res.setHeader(
'Content-Type', 'text/plain');
res.end(
'Hello World\n');
});
server.listen(port, hostname,
function(){
console.log(`Server running at http://${hostname}:${port}/`);
});
|
進行改寫后的代碼,是否更熟悉一些呢。那 Node.js 官方幫助文檔中提供的示例代碼里使用的又是什么呢?
1. 定義無參的箭頭函數
在 ECMAScript 5 之前,我們定義一個無參函數是這樣的:
1
2
3
|
var fn = function(){
return 'this is function';
}
|
而在 ECMAScript 2015 之后,我們可以利用箭頭函數定義是這樣的:
1
|
var fn = () => 'this is function';
|
上述兩個函數的定義是等價的。
2. 定義帶參的箭頭函數
如果想要定義帶有參數的箭頭函數,可以如下方式:
1
|
var fn = v => v;
|
上述代碼等同於如下:
1
2
3
|
var fn = function(v){
return v;
}
|
如果定義帶有多個參數的箭頭函數,可以將參數通過圓括號進行包裹。
1
|
var sum = (num1, num2) => num1 + num2;
|
上述代碼等同於如下:
1
2
3
|
var sum = function(num1, num2) {
return num1 + num2;
}
|
3. 箭頭函數體包含多條語句
上述示例代碼中,我們只在箭頭函數中定義了一條語句。那想要定義多條語句的話,可以將所有函數體內的語句通過大括號進行包裹。
1
2
3
4
5
6
7
|
var sum = (num1, num2) => {
if(num1 < num2){
return num1;
}
else{
return num2;
}
}
|
上述代碼等同於如下:
1
2
3
4
5
6
7
|
var sum = function(num1, num2) {
if(num1 < num2){
return num1;
}
else{
return num2;
}
}
|
大括號會被解析為代碼塊。如果箭頭函數想要返回的是復雜數據(例如對象),需要使用圓括號進行包裹。
1
|
var me = () => ({ name: "longestory" });
|
上述代碼等同於如下:
1
2
3
|
var me = function() {
return { name: "longestory" };
}
|
4. 箭頭函數的作用
通過上述內容,我們已經基本掌握了箭頭函數的用法。那箭頭函數究竟會有什么作用呢?我們再回過頭來看看 Node.js 官方幫助文檔的示例代碼。
1
2
3
4
5
6
|
// ECMAScript 5 中的寫法
http.createServer(
function(req, res){
res.statusCode =
200;
res.setHeader(
'Content-Type', 'text/plain');
res.end(
'Hello World\n');
});
|
上述示例代碼中,我們可以知道,通過 http
對象調用了 createServer
方法的同時向該方法傳遞了一個回調函數。
1
2
3
4
5
6
|
// ECMAScript 2015 中的寫法
http.createServer(
(req, res) => {
res.statusCode =
200;
res.setHeader(
'Content-Type', 'text/plain');
res.end(
'Hello World\n');
});
|
所以,箭頭函數的主要用法之一,就是用來簡化回調函數的使用。
模板字符串
在 Node.js 的官方幫助文檔提供的示例代碼中,我們還看到一行比較特殊的代碼。
1
|
console.log(`Server running at http://${hostname}:${port}/`);
|
上述示例代碼如果被改寫成這樣,相信你會更熟悉。
1
|
console.log('Server running at http://'+hostname+':'+port+'/');
|
實際上,在上述代碼中,其實是使用了 JavaScript 的字符串拼串。而 Node.js 的官方幫助文檔中的示例代碼,則使用 ECMAScript 2015 規范中的 模板字符串。
1. 模板字符串的基本使用
模板字符串(template string)是增強版的字符串,用反引號(`)標識。
1
|
console.log(`this is a string.`);
|
上述示例代碼的輸出結果如下:
1
|
this is a string.
|
你會發現上述示例代碼的輸出結果與 ECMAScript 5 中的普通字符串並沒有任何區別。
1
|
console.log('this is a string.');
|
但,如果我們想要輸出的字符串很復雜,或者是多行的。那 ECMAScript 5 中的寫法應該是這樣的:
1
2
3
4
5
6
|
$(
'#list').html(
'<ul>'+
'<li>first</li>'+
'<li>second</li>'+
'</ul>'
);
|
而使用 ECMAScript 2015 規范中的模板字符串,我們就可以寫成這樣:
1
2
3
4
5
6
|
$(
'#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`);
|
2. 模板字符串中使用變量
如果輸出的是一些文本加上變量的內容的話,在 ECMAScript 5 中的寫法是這樣的:
1
2
3
|
const hostname = '127.0.0.1';
const port = 3000;
console.log('Server running at http://'+hostname+':'+port+'/');
|
也就是說,我們在實際開發中,需要大量的字符串拼寫工作。這樣做的問題在於:
- 工作量巨大
- 比較容易出錯
而 ECMAScript 2015 規范中的模板字符串,則允許嵌入變量。只需要將需要嵌入的變量通過 ${}
進行包裹即可。
1
2
3
|
const hostname = '127.0.0.1';
const port = 3000;
console.log(`Server running at http://${hostname}:${port}/`);
|
在模板字符串中,甚至可以嵌入函數的調用。
1
2
3
4
|
function fn(){
return 'Hello';
}
console.log(`${fn()} World`);
|
上述示例代碼運行的結果如下:
1
|
Hello World
|
3. 模板字符串的注意事項
當然,模板字符串在使用過程中,也需要注意一些問題。
如果模板字符串中嵌入的變量沒有聲明,則會報錯。
1
|
console.log(`Server running at http://${hostname}/`);
|
上述示例代碼,運行后會報如下錯誤:
1
|
ReferenceError: hostname is not defined
|