[TOC]
## 概述
### 函數的聲明
(1)function命令
函數就是使用function命令命名的代碼區塊,便于反復調用。
~~~
function print(){
// ...
}
~~~
上面的代碼命名了一個print函數,以后使用print()這種形式,就可以調用相應的代碼。這叫做函數的聲明(Function Declaration)。
(2)函數表達式
除了用function命令聲明函數,還可以采用變量賦值的寫法。
~~~
var print = function (){
// ...
};
~~~
這種寫法將一個匿名函數賦值給變量。這時,這個匿名函數又稱函數表達式(Function Expression),因為賦值語句的等號右側只能放表達式。
采用函數表達式聲明函數時,function命令后面不帶有函數名。如果加上函數名,該函數名只在函數體內部有效,在函數體外部無效。
~~~
var print = function x(){
console.log(typeof x);
};
x
// ReferenceError: x is not defined
print()
// function
~~~
上面代碼在函數表達式中,加入了函數名x。這個x只在函數體內部可用,指代函數表達式本身,其他地方都不可用。這種寫法的用處有兩個,一是可以在函數體內部調用自身,二是方便除錯(除錯工具顯示函數調用棧時,將顯示函數名,而不再顯示這里是一個匿名函數)。因此,需要時,可以采用下面的形式聲明函數。
~~~
var f = function f(){};
~~~
需要注意的是,函數的表達式需要在語句的結尾加上分號,表示語句結束。而函數的聲明在結尾的大括號后面不用加分號。總的來說,這兩種聲明函數的方式,差別很細微(參閱后文《變量提升》一節),這里可以近似認為是等價的。
(3)Function構造函數
還有第三種聲明函數的方式:通過Function構造函數聲明。
~~~
var add = new Function("x","y","return (x+y)");
// 相當于定義了如下函數
// function add(x, y) {
// return (x+y);
// }
~~~
在上面代碼中,Function對象接受若干個參數,除了最后一個參數是add函數的“函數體”,其他參數都是add函數的參數。如果只有一個參數,該參數就是函數體。
~~~
var foo = new Function('return "hello world"');
// 相當于定義了如下函數
// function foo() {
// return "hello world";
// }
~~~
Function構造函數可以不使用new命令,返回結果完全一樣。
總的來說,這種聲明函數的方式非常不直觀,幾乎無人使用。
(4)函數的重復聲明
如果多次采用function命令,重復聲明同一個函數,則后面的聲明會覆蓋前面的聲明。
~~~
function f(){
console.log(1);
}
f() // 2
function f(){
console.log(2);
}
f() // 2
~~~
上面代碼說明,由于存在函數名的提升,前面的聲明在任何時候都是無效的,這一點要特別注意。
### 圓括號運算符和return語句
調用函數時,要使用圓括號運算符。圓括號之中,可以加入函數的參數。
~~~
function add(x,y) {
return x+y;
}
add(1,1) // 2
~~~
函數體內部的return語句,表示返回。JavaScript引擎遇到return語句,就直接返回return后面的那個表達式的值,后面即使還有語句,也不會得到執行。也就是說,return語句所帶的那個表達式,就是函數的返回值。return語句不是必需的,如果沒有的話,該函數就不返回任何值,或者說返回undefined。
函數可以調用自身,這就是遞歸(recursion)。下面就是使用遞歸,計算斐波那契數列的代碼。
~~~
function fib(num) {
if (num > 2) {
return fib(num - 2) + fib(num - 1);
} else {
return 1;
}
}
fib(6)
// 8
~~~
### 第一等公民
JavaScript的函數與其他數據類型處于同等地位,可以使用其他數據類型的地方就能使用函數。比如,可以把函數賦值給變量和對象的屬性,也可以當作參數傳入其他函數,或者作為函數的結果返回。這表示函數與其他數據類型的地方是平等,所以又稱函數為第一等公民。
~~~
function add(x,y){
return x+y;
}
// 將函數賦值給一個變量
var operator = add;
// 將函數作為參數和返回值
function a(op){
return op;
}
a(add)(1,1)
// 2
~~~
### 函數名的提升
JavaScript引擎將函數名視同變量名,所以采用function命令聲明函數時,整個函數會被提升到代碼頭部。所以,下面的代碼不會報錯。
~~~
f();
function f(){}
~~~
表面上,上面代碼好像在聲明之前就調用了函數f。但是實際上,由于“變量提升”,函數f被提升到了代碼頭部,也就是在調用之前已經聲明了。但是,如果采用賦值語句定義函數,JavaScript就會報錯。
~~~
f();
var f = function (){};
// TypeError: undefined is not a function
~~~
上面的代碼等同于
~~~
var f;
f();
f = function (){};
~~~
當調用f的時候,f只是被聲明,還沒有被賦值,等于undefined,所以會報錯。因此,如果同時采用function命令和賦值語句聲明同一個函數,最后總是采用賦值語句的定義。
~~~
var f = function() {
console.log ('1');
}
function f() {
console.log('2');
}
f()
// 1
~~~
### 不能在條件語句中聲明函數
根據ECMAScript的規范,不得在非函數的代碼塊中聲明函數,最常見的情況就是if和try語句。
~~~
if (foo) {
function x() { return; }
}
try {
function x() {return; }
} catch(e) {
console.log(e);
}
~~~
上面代碼分別在if代碼塊和try代碼塊中聲明了兩個函數,按照語言規范,這是不合法的。但是,實際情況是各家瀏覽器往往并不報錯,能夠運行。
但是由于存在函數名的提升,所以在條件語句中聲明函數是無效的,這是非常容易出錯的地方。
~~~
if (false){
function f(){}
}
f()
// 不報錯
~~~
由于函數f的聲明被提升到了if語句的前面,導致if語句無效,所以上面的代碼不會報錯。要達到在條件語句中定義函數的目的,只有使用函數表達式。
~~~
if (false){
var f = function (){};
}
f()
// undefined
~~~
## 函數的屬性和方法
### name屬性
name屬性返回緊跟在function關鍵字之后的那個函數名。
~~~
function f1() {}
f1.name // 'f1'
var f2 = function () {};
f2.name // ''
var f3 = function myName() {};
f3.name // 'myName'
~~~
上面代碼中,函數的name屬性總是返回緊跟在function關鍵字之后的那個函數名。對于f2來說,返回空字符串,匿名函數的name屬性總是為空字符串;對于f3來說,返回函數表達式的名字(真正的函數名還是f3,myName這個名字只在函數體內部可用)。
### length屬性
length屬性返回函數定義中參數的個數。
~~~
function f(a,b) {}
f.length
// 2
~~~
上面代碼定義了空函數f,它的length屬性就是定義時參數的個數。不管調用時輸入了多少個參數,length屬性始終等于2。
length屬性提供了一種機制,判斷定義時和調用時參數的差異,以便實現面向對象編程的”方法重載“(overload)。
## toString()
函數的toString方法返回函數的源碼。
~~~
function f() {
a();
b();
c();
}
f.toString()
// function f() {
// a();
// b();
// c();
// }
~~~
## 函數作用域
### 定義
作用域(scope)指的是變量存在的范圍。Javascript只有兩種作用域:一種是全局作用域,變量在整個程序中一直存在;另一種是函數作用域,變量只在函數內部存在。
在函數外部聲明的變量就是全局變量(global variable),它可以在函數內部讀取。
~~~
var v = 1;
function f(){
console.log(v);
}
f()
// 1
~~~
上面的代碼表明,函數f內部可以讀取全局變量v。
在函數內部定義的變量,外部無法讀取,稱為“局部變量”(local variable)。
~~~
function f(){
var v = 1;
}
v
// ReferenceError: v is not defined
~~~
函數內部定義的變量,會在該作用域內覆蓋同名全局變量。
~~~
var v = 1;
function f(){
var v = 2;
console.log(v);
}
f()
// 2
v
// 1
~~~
### 函數內部的變量提升
與全局作用域一樣,函數作用域內部也會產生“變量提升”現象。var命令聲明的變量,不管在什么位置,變量聲明都會被提升到函數體的頭部。
~~~
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
~~~
上面的代碼等同于
~~~
function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}
~~~
### 函數本身的作用域
函數本身也是一個值,也有自己的作用域。它的作用域綁定其聲明時所在的作用域。
~~~
var a = 1;
var x = function (){
console.log(a);
};
function f(){
var a = 2;
x();
}
f() // 1
~~~
上面代碼中,函數x是在函數f的外部聲明的,所以它的作用域綁定外層,內部變量a不會到函數f體內取值,所以輸出1,而不是2。
很容易犯錯的一點是,如果函數A調用函數B,卻沒考慮到函數B不會引用函數A的內部變量。
~~~
var x = function (){
console.log(a);
};
function y(f){
var a = 2;
f();
}
y(x)
// ReferenceError: a is not defined
~~~
上面代碼將函數x作為參數,傳入函數y。但是,函數x是在函數y體外聲明的,作用域綁定外層,因此找不到函數y的內部變量a,導致報錯。
## 參數
### 概述
函數運行的時候,有時需要提供外部數據,不同的外部數據會得到不同的結果,這種外部數據就叫參數。
~~~
function square(x){
return x*x;
}
square(2) // 4
square(3) // 9
~~~
上式的x就是square函數的參數。每次運行的時候,需要提供這個值,否則得不到結果。
### 參數的省略
參數不是必需的,Javascript語言允許省略參數。
~~~
function f(a,b){
return a;
}
f(1,2,3) // 1
f(1) // 1
f() // undefined
f.length // 2
~~~
上面代碼的函數f定義了兩個參數,但是運行時無論提供多少個參數(或者不提供參數),JavaScript都不會報錯。被省略的參數的值就變為undefined。需要注意的是,函數的length屬性與實際傳入的參數個數無關,只反映定義時的參數個數。
但是,沒有辦法只省略靠前的參數,而保留靠后的參數。如果一定要省略靠前的參數,只有顯式傳入undefined。
~~~
function f(a,b){
return a;
}
f(,1) // error
f(undefined,1) // undefined
~~~
### 默認值
通過下面的方法,可以為函數的參數設置默認值。
~~~
function f(a){
a = a || 1;
return a;
}
f('') // 1
f(0) // 1
~~~
上面代碼的||表示“或運算”,即如果a有值,則返回a,否則返回事先設定的默認值(上例為1)。
這種寫法會對a進行一次布爾運算,只有為true時,才會返回a。可是,除了undefined以外,0、空字符、null等的布爾值也是false。也就是說,在上面的函數中,不能讓a等于0或空字符串,否則在明明有參數的情況下,也會返回默認值。
為了避免這個問題,可以采用下面更精確的寫法。
~~~
function f(a){
(a !== undefined && a != null)?(a = a):(a = 1);
return a;
}
f('') // ""
f(0) // 0
~~~
### 傳遞方式
JavaScript的函數參數傳遞方式是傳值傳遞(passes by value),這意味著,在函數體內修改參數值,不會影響到函數外部。
~~~
// 修改原始類型的參數值
var p = 2;
function f(p){
p = 3;
}
f(p);
p // 2
// 修改復合類型的參數值
var o = [1,2,3];
function f(o){
o = [2,3,4];
}
f(o);
o // [1, 2, 3]
~~~
上面代碼分成兩段,分別修改原始類型的參數值和復合類型的參數值。兩種情況下,函數內部修改參數值,都不會影響到函數外部。
需要十分注意的是,雖然參數本身是傳值傳遞,但是對于復合類型的變量來說,屬性值是傳址傳遞(pass by reference),也就是說,屬性值是通過地址讀取的。所以在函數體內修改復合類型變量的屬性值,會影響到函數外部。
~~~
// 修改對象的屬性值
var o = { p:1 };
function f(obj){
obj.p = 2;
}
f(o);
o.p // 2
// 修改數組的屬性值
var a = [1,2,3];
function f(a){
a[0]=4;
}
f(a);
a // [4,2,3]
~~~
上面代碼在函數體內,分別修改對象和數組的屬性值,結果都影響到了函數外部,這證明復合類型變量的屬性值是傳址傳遞。
某些情況下,如果需要對某個變量達到傳址傳遞的效果,可以將它寫成全局對象的屬性。
~~~
var a = 1;
function f(p){
window[p]=2;
}
f('a');
a // 2
~~~
上面代碼中,變量a本來是傳值傳遞,但是寫成window對象的屬性,就達到了傳址傳遞的效果。
### 同名參數
如果有同名的參數,則取最后出現的那個值。
~~~
function f(a, a){
console.log(a);
}
f(1,2)
// 2
~~~
上面的函數f有兩個參數,且參數名都是a。取值的時候,以后面的a為準。即使后面的a沒有值或被省略,也是以其為準。
~~~
function f(a, a){
console.log(a);
}
f(1)
// undefined
~~~
調用函數f的時候,沒有提供第二個參數,a的取值就變成了undefined。這時,如果要獲得第一個a的值,可以使用arguments對象。
~~~
function f(a, a){
console.log(arguments[0]);
}
f(1)
// 1
~~~
### arguments對象
(1)定義
由于JavaScript允許函數有不定數目的參數,所以我們需要一種機制,可以在函數體內部讀取所有參數。這就是arguments對象的由來。
arguments對象包含了函數運行時的所有參數,arguments[0]就是第一個參數,arguments[1]就是第二個參數,依次類推。這個對象只有在函數體內部,才可以使用。
~~~
var f = function(one) {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
f(1, 2, 3)
// 1
// 2
// 3
~~~
arguments對象除了可以讀取參數,還可以為參數賦值(嚴格模式不允許這種用法)。
~~~
var f = function(a,b) {
arguments[0] = 3;
arguments[1] = 2;
return a+b;
}
f(1, 1)
// 5
~~~
可以通過arguments對象的length屬性,判斷函數調用時到底帶幾個參數。
~~~
function f(){
return arguments.length;
}
f(1,2,3) // 3
f(1) // 1
f() // 0
~~~
(2)與數組的關系
需要注意的是,雖然arguments很像數組,但它是一個對象。某些用于數組的方法(比如slice和forEach方法),不能在arguments對象上使用。
但是,有時arguments可以像數組一樣,用在某些只用于數組的方法。比如,用在apply方法中,或使用concat方法完成數組合并。
~~~
// 用于apply方法
myfunction.apply(obj, arguments).
// 使用與另一個數組合并
Array.prototype.concat.apply([1,2,3], arguments)
~~~
要讓arguments對象使用數組方法,真正的解決方法是將arguments轉為真正的數組。下面是兩種常用的轉換方法:slice方法和逐一填入新數組。
~~~
var args = Array.prototype.slice.call(arguments);
// or
var args = [];
for(var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
~~~
(3)callee屬性
arguments對象帶有一個callee屬性,返回它所對應的原函數。
~~~
var f = function(one) {
console.log(arguments.callee === f);
}
f()
// true
~~~
## 函數的其他知識點
### 閉包
閉包(closure)就是定義在函數體內部的函數。更理論性的表達是,閉包是函數與其生成時所在的作用域對象(scope object)的一種結合。
~~~
function f() {
var c = function (){};
}
~~~
上面的代碼中,c是定義在函數f內部的函數,就是閉包。
閉包的特點在于,在函數外部可以讀取函數的內部變量。
~~~
function f() {
var v = 1;
var c = function (){
return v;
};
return c;
}
var o = f();
o();
// 1
~~~
上面代碼表示,原先在函數f外部,我們是沒有辦法讀取內部變量v的。但是,借助閉包c,可以讀到這個變量。
閉包不僅可以讀取函數內部變量,還可以使得內部變量記住上一次調用時的運算結果。
~~~
function createIncrementor(start) {
return function () {
return start++;
}
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
~~~
上面代碼表示,函數內部的start變量,每一次調用時都是在上一次調用時的值的基礎上進行計算的。
### 立即調用的函數表達式(IIFE)
在Javascript中,一對圓括號“()”是一種運算符,跟在函數名之后,表示調用該函數。比如,print()就表示調用print函數。
有時,我們需要在定義函數之后,立即調用該函數。這時,你不能在函數的定義之后加上圓括號,這會產生語法錯誤。
~~~
function(){ /* code */ }();
// SyntaxError: Unexpected token (
~~~
產生這個錯誤的原因是,Javascript引擎看到function關鍵字之后,認為后面跟的是函數定義語句,不應該以圓括號結尾。
解決方法就是讓引擎知道,圓括號前面的部分不是函數定義語句,而是一個表達式,可以對此進行運算。你可以這樣寫:
~~~
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
~~~
這兩種寫法都是以圓括號開頭,引擎就會認為后面跟的是一個表示式,而不是函數定義,所以就避免了錯誤。這就叫做“立即調用的函數表達式”(Immediately-Invoked Function Expression),簡稱IIFE。
> 注意,上面的兩種寫法的結尾,都必須加上分號。
推而廣之,任何讓解釋器以表達式來處理函數定義的方法,都能產生同樣的效果,比如下面三種寫法。
~~~
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
~~~
甚至像這樣寫
~~~
!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();
~~~
new關鍵字也能達到這個效果。
~~~
new function(){ /* code */ }
new function(){ /* code */ }() // 只有傳遞參數時,才需要最后那個圓括號。
~~~
通常情況下,只對匿名函數使用這種“立即執行的函數表達式”。它的目的有兩個:一是不必為函數命名,避免了污染全局變量;二是IIFE內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變量。
~~~
// 寫法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 寫法二
(function (){
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
~~~
上面代碼中,寫法二比寫法一更好,因為完全避免了污染全局變量。
## eval命令
eval命令的作用是,將字符串當作語句執行。
~~~
eval('var a = 1;');
a // 1
~~~
上面代碼將字符串當作語句運行,生成了變量a。
放在eval中的字符串,應該有獨自存在的意義,不能用來與eval以外的命令配合使用。舉例來說,下面的代碼將會報錯。
~~~
eval('return;');
~~~
由于eval沒有自己的作用域,都在當前作用域內執行,因此可能會修改其他外部變量的值,造成安全問題。
~~~
var a = 1;
eval('a = 2');
a // 2
~~~
上面代碼中,eval命令修改了外部變量a的值。由于這個原因,所以eval有安全風險,無法做到作用域隔離,最好不要使用。此外,eval的命令字符串不會得到JavaScript引擎的優化,運行速度較慢,也是另一個不應該使用它的理由。通常情況下,eval最常見的場合是解析JSON數據字符串,正確的做法是這時應該使用瀏覽器提供的JSON.parse方法。
ECMAScript 5將eval的使用分成兩種情況,像上面這樣的調用,就叫做“直接使用”,這種情況下eval的作用域就是當前作用域(即全局作用域或函數作用域)。另一種情況是,eval不是直接調用,而是“間接調用”,此時eval的作用域總是全局作用域。
~~~
var a = 1;
function f(){
var a = 2;
var e = eval;
e('console.log(a)');
}
f() // 1
~~~
上面代碼中,eval是間接調用,所以即使它是在函數中,它的作用域還是全局作用域,因此輸出的a為全局變量。
eval的間接調用的形式五花八門,只要不是直接調用,幾乎都屬于間接調用。
~~~
eval.call(null, '...')
window.eval('...')
(1, eval)('...')
(eval, eval)('...')
(1 ? eval : 0)('...')
(__ = eval)('...')
var e = eval; e('...')
(function(e) { e('...') })(eval)
(function(e) { return e })(eval)('...')
(function() { arguments[0]('...') })(eval)
this.eval('...')
this['eval']('...')
[eval][0]('...')
eval.call(this, '...')
eval('eval')('...')
~~~
上面這些形式都是eval的間接調用,因此它們的作用域都是全局作用域。
與eval作用類似的還有Function構造函數。利用它生成一個函數,然后調用該函數,也能將字符串當作命令執行。
~~~
var jsonp = 'foo({"id":42})';
var f = new Function( "foo", jsonp );
// 相當于定義了如下函數
// function f(foo) {
// foo({"id":42});
// }
f(function(json){
console.log( json.id ); // 42
})
~~~
上面代碼中,jsonp是一個字符串,Function構造函數將這個字符串,變成了函數體。調用該函數的時候,jsonp就會執行。這種寫法的實質是將代碼放到函數作用域執行,避免對全局作用域造成影響。
## 參考鏈接
* Ben Alman,?[Immediately-Invoked Function Expression (IIFE)](http://benalman.com/news/2010/11/immediately-invoked-function-expression/)
* Mark Daggett,?[Functions Explained](http://markdaggett.com/blog/2013/02/15/functions-explained/)
* Juriy Zaytsev,?[Named function expressions demystified](http://kangax.github.com/nfe/)
* Marco Rogers polotek,?[What is the arguments object?](http://docs.nodejitsu.com/articles/javascript-conventions/what-is-the-arguments-object)
* Juriy Zaytsev,?[Global eval. What are the options?](http://perfectionkills.com/global-eval-what-are-the-options/)
* Axel Rauschmayer,?[Evaluating JavaScript code via eval() and new Function()](http://www.2ality.com/2014/01/eval.html)
- 第一章 導論
- 1.1 前言
- 1.2 為什么學習JavaScript?
- 1.3 JavaScript的歷史
- 第二章 基本語法
- 2.1 語法概述
- 2.2 數值
- 2.3 字符串
- 2.4 對象
- 2.5 數組
- 2.6 函數
- 2.7 運算符
- 2.8 數據類型轉換
- 2.9 錯誤處理機制
- 2.10 JavaScript 編程風格
- 第三章 標準庫
- 3.1 Object對象
- 3.2 Array 對象
- 3.3 包裝對象和Boolean對象
- 3.4 Number對象
- 3.5 String對象
- 3.6 Math對象
- 3.7 Date對象
- 3.8 RegExp對象
- 3.9 JSON對象
- 3.10 ArrayBuffer:類型化數組
- 第四章 面向對象編程
- 4.1 概述
- 4.2 封裝
- 4.3 繼承
- 4.4 模塊化編程
- 第五章 DOM
- 5.1 Node節點
- 5.2 document節點
- 5.3 Element對象
- 5.4 Text節點和DocumentFragment節點
- 5.5 Event對象
- 5.6 CSS操作
- 5.7 Mutation Observer
- 第六章 瀏覽器對象
- 6.1 瀏覽器的JavaScript引擎
- 6.2 定時器
- 6.3 window對象
- 6.4 history對象
- 6.5 Ajax
- 6.6 同域限制和window.postMessage方法
- 6.7 Web Storage:瀏覽器端數據儲存機制
- 6.8 IndexedDB:瀏覽器端數據庫
- 6.9 Web Notifications API
- 6.10 Performance API
- 6.11 移動設備API
- 第七章 HTML網頁的API
- 7.1 HTML網頁元素
- 7.2 Canvas API
- 7.3 SVG 圖像
- 7.4 表單
- 7.5 文件和二進制數據的操作
- 7.6 Web Worker
- 7.7 SSE:服務器發送事件
- 7.8 Page Visibility API
- 7.9 Fullscreen API:全屏操作
- 7.10 Web Speech
- 7.11 requestAnimationFrame
- 7.12 WebSocket
- 7.13 WebRTC
- 7.14 Web Components
- 第八章 開發工具
- 8.1 console對象
- 8.2 PhantomJS
- 8.3 Bower:客戶端庫管理工具
- 8.4 Grunt:任務自動管理工具
- 8.5 Gulp:任務自動管理工具
- 8.6 Browserify:瀏覽器加載Node.js模塊
- 8.7 RequireJS和AMD規范
- 8.8 Source Map
- 8.9 JavaScript 程序測試
- 第九章 JavaScript高級語法
- 9.1 Promise對象
- 9.2 有限狀態機
- 9.3 MVC框架與Backbone.js
- 9.4 嚴格模式
- 9.5 ECMAScript 6 介紹
- 附錄
- 10.1 JavaScript API列表
- 草稿一:函數庫
- 11.1 Underscore.js
- 11.2 Modernizr
- 11.3 Datejs
- 11.4 D3.js
- 11.5 設計模式
- 11.6 排序算法
- 草稿二:jQuery
- 12.1 jQuery概述
- 12.2 jQuery工具方法
- 12.3 jQuery插件開發
- 12.4 jQuery.Deferred對象
- 12.5 如何做到 jQuery-free?
- 草稿三:Node.js
- 13.1 Node.js 概述
- 13.2 CommonJS規范
- 13.3 package.json文件
- 13.4 npm模塊管理器
- 13.5 fs 模塊
- 13.6 Path模塊
- 13.7 process對象
- 13.8 Buffer對象
- 13.9 Events模塊
- 13.10 stream接口
- 13.11 Child Process模塊
- 13.12 Http模塊
- 13.13 assert 模塊
- 13.14 Cluster模塊
- 13.15 os模塊
- 13.16 Net模塊和DNS模塊
- 13.17 Express框架
- 13.18 Koa 框架