[TOC]
## 簡介
### 對象和面向對象編程
“面向對象編程”(Object Oriented Programming,縮寫為OOP)是目前主流的編程范式。它的核心思想是將真實世界中各種復雜的關系,抽象為一個個對象,然后由對象之間的分工與合作,完成對真實世界的模擬。
傳統的計算機程序由一系列函數或一系列指令組成,而面向對象編程的程序由一系列對象組成。每一個對象都是功能中心,具有明確分工,可以完成接受信息、處理數據、發出信息等任務。因此,面向對象編程具有靈活性、代碼的可重用性、模塊性等特點,容易維護和開發,非常適合多人合作的大型軟件項目。
那么,“對象”(object)到底是什么?
我們從兩個層次來理解。
(1)“對象”是單個實物的抽象。
一本書、一輛汽車、一個人都可以是“對象”,一個數據庫、一張網頁、一個與遠程服務器的連接也可以是“對象”。當實物被抽象成“對象”,實物之間的關系就變成了“對象”之間的關系,從而就可以模擬現實情況,針對“對象”進行編程。
(2)“對象”是一個容器,封裝了“屬性”(property)和“方法”(method)。
所謂“屬性”,就是對象的狀態;所謂“方法”,就是對象的行為(完成某種任務)。比如,我們可以把動物抽象為animal對象,“屬性”記錄具體是那一種動物,“方法”表示動物的某種行為(奔跑、捕獵、休息等等)。
雖然不同于傳統的面向對象編程語言,但是JavaScript具有很強的面向對象編程能力。本章介紹JavaScript如何進行“面向對象編程”。
### 構造函數
“面向對象編程”的第一步,就是要生成對象。
前面說過,“對象”是單個實物的抽象。所以,通常需要一個模板,表示某一類實物的共同特征,然后“對象”根據這個模板生成。
典型的面向對象編程語言(比如C++和Java),存在“類”(class)這樣一個概念。所謂“類”就是對象的模板,對象就是“類”的實例。JavaScript語言沒有“類”,而改用構造函數(constructor)作為對象的模板。
所謂“構造函數”,就是專門用來生成“對象”的函數。它提供模板,作為對象的基本結構。一個構造函數,可以生成多個對象,這些對象都有相同的結構。
構造函數是一個正常的函數,但是它的特征和用法與普通函數不一樣。下面就是一個構造函數:
~~~
var Vehicle = function() {
this.price = 1000;
};
~~~
上面代碼中,Vehicle就是構造函數,它提供模板,用來生成車輛對象。
構造函數的最大特點就是,函數體內部使用了this關鍵字,代表了所要生成的對象實例。生成對象的時候,必需用new命令,調用Vehicle函數。
### new命令
new命令的作用,就是執行構造函數,返回一個實例對象。
~~~
var Vehicle = function (){
this.price = 1000;
};
var v = new Vehicle();
v.price // 1000
~~~
上面代碼通過new命令,讓構造函數Vehicle生成一個實例對象,保存在變量v中。這個新生成的實例對象,從構造函數Vehicle繼承了price屬性。在new命令執行時,構造函數內部的this,就代表了新生成的實例對象,this.price表示實例對象有一個price屬性,它的值是1000。
使用new命令時,根據需要,構造函數也可以接受參數。
~~~
var Vehicle = function (p){
this.price = p;
};
var v = new Vehicle(500);
~~~
new命令本身就可以執行構造函數,所以后面的構造函數可以帶括號,也可以不帶括號。下面兩行代碼是等價的。
~~~
var v = new Vehicle();
var v = new Vehicle;
~~~
一個很自然的問題是,如果忘了使用new命令,直接調用構造函數會發生什么事?
這種情況下,構造函數就變成了普通函數,并不會生成實例對象。而且由于下面會說到的原因,this這時代表全局對象,將造成一些意想不到的結果。
~~~
var Vehicle = function (){
this.price = 1000;
};
var v = Vehicle();
v.price
// Uncaught TypeError: Cannot read property 'price' of undefined
price
// 1000
~~~
上面代碼中,調用Vehicle構造函數時,忘了加上new命令。結果,price屬性變成了全局變量,而變量v變成了undefined。
因此,應該非常小心,避免出現不使用new命令、直接調用構造函數的情況。為了保證構造函數必須與new命令一起使用,一個解決辦法是,在構造函數內部使用嚴格模式,即第一行加上`use strict`。
~~~
function Fubar(foo, bar){
"use strict";
this._foo = foo;
this._bar = bar;
}
Fubar()
// TypeError: Cannot set property '_foo' of undefined
~~~
上面代碼的Fubar為構造函數,use strict命令保證了該函數在嚴格模式下運行。由于在嚴格模式中,函數內部的this不能指向全局對象,默認等于undefined,導致不加new調用會報錯(JavaScript不允許對undefined添加屬性)。
另一個解決辦法,是在構造函數內部判斷是否使用new命令,如果發現沒有使用,則直接返回一個實例對象。
~~~
function Fubar(foo, bar){
if (!(this instanceof Fubar)) {
return new Fubar(foo, bar);
}
this._foo = foo;
this._bar = bar;
}
Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1
~~~
上面代碼中的構造函數,不管加不加new命令,都會得到同樣的結果。
### new命令的原理
使用new命令時,它后面的函數調用就不是正常的調用,而是被new命令控制了。內部的流程是,先創造一個空對象,作為上下文對象,賦值給函數內部的this關鍵字。也就是說,this指的是一個新生成的空對象,所有針對this的操作,都會發生在這個空對象上。
構造函數之所以叫“構造函數”,就是說這個函數的目的,就是操作上下文對象(即this對象),將其“構造”為需要的樣子。如果構造函數的return語句返回的是對象,new命令會返回return語句指定的對象;否則,就會不管return語句,返回構造后的上下文對象。
~~~
var Vehicle = function (){
this.price = 1000;
return 1000;
};
(new Vehicle()) === 1000
// false
~~~
上面代碼中,Vehicle是一個構造函數,它的return語句返回一個數值。這時,new命令就會忽略這個return語句,返回“構造”后的this對象。
但是,如果return語句返回的是一個跟this無關的新對象,new命令會返回這個新對象,而不是this對象。這一點需要特別引起注意。
~~~
var Vehicle = function (){
this.price = 1000;
return { price: 2000 };
};
(new Vehicle()).price
// 2000
~~~
上面代碼中,構造函數Vehicle的return語句,返回的是一個新對象。new命令會返回這個對象,而不是this對象。
new命令簡化的內部流程,可以用下面的代碼表示。
~~~
function _new(/* constructor, param, ... */) {
var args = [].slice.call(arguments);
var constructor = args.shift();
var context = Object.create(constructor.prototype);
var result = constructor.apply(context, args);
return (typeof result === 'object' && result != null) ? result : context;
}
var actor = _new(Person, "張三", 28);
~~~
### instanceof運算符
instanceof運算符用來確定一個對象是否為某個構造函數的實例。
~~~
var v = new Vehicle();
v instanceof Vehicle
// true
~~~
instanceof運算符的左邊放置對象,右邊放置構造函數。在JavaScript之中,只要是對象,就有對應的構造函數。因此,instanceof運算符可以用來判斷值的類型。
~~~
[1, 2, 3] instanceof Array // true
({}) instanceof Object // true
~~~
上面代碼表示數組和對象則分別是Array對象和Object對象的實例。最后那一行的空對象外面,之所以要加括號,是因為如果不加,JavaScript引擎會把一對大括號解釋為一個代碼塊,而不是一個對象,從而導致這一行代碼被解釋為“{}; instanceof Object”,引擎就會報錯。
需要注意的是,由于原始類型的值不是對象,所以不能使用instanceof運算符判斷類型。
~~~
"" instanceof String // false
1 instanceof Number // false
~~~
上面代碼中,字符串不是String對象的實例(因為字符串不是對象),數值1也不是Number對象的實例(因為數值1不是對象)。
如果存在繼承關系,也就是某個對象可能是多個構造函數的實例,那么instanceof運算符對這些構造函數都返回true。
~~~
var a = [];
a instanceof Array // true
a instanceof Object // true
~~~
上面代碼表示,a是一個數組,所以它是Array的實例;同時,a也是一個對象,所以它也是Object的實例。
利用instanceof運算符,還可以巧妙地解決,調用構造函數時,忘了加new命令的問題。
~~~
function Fubar (foo, bar) {
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
}
else return new Fubar(foo, bar);
}
~~~
上面代碼使用instanceof運算符,在函數體內部判斷this關鍵字是否為構造函數Fubar的實例。如果不是,就表明忘了加new命令。
## this關鍵字
### 涵義
構造函數內部需要用到this關鍵字。那么,this關鍵字到底是什么意思呢?
簡單說,this就是指函數當前的運行環境。在JavaScript語言之中,所有函數都是在某個運行環境之中運行,this就是這個環境。對于JavaScipt語言來說,一切皆對象,運行環境也是對象,所以可以理解成,所有函數總是在某個對象之中運行,this就指向這個對象。這本來并不會讓用戶糊涂,但是JavaScript支持運行環境動態切換,也就是說,this的指向是動態的,沒有辦法事先確定到底指向哪個對象,這才是最讓初學者感到困惑的地方。
舉例來說,有一個函數f,它同時充當a對象和b對象的方法。JavaScript允許函數f的運行環境動態切換,即一會屬于a對象,一會屬于b對象,這就要靠this關鍵字來辦到。
~~~
function f(){ console.log(this.x); };
var a = {x:'a'};
a.m = f;
var b = {x:'b'};
b.m = f;
a.m() // a
b.m() // b
~~~
上面代碼中,函數f可以打印出當前運行環境中x變量的值。當f屬于a對象時,this指向a;當f屬于b對象時,this指向b,因此打印出了不同的值。由于this的指向可變,所以可以手動切換運行環境,以達到某種特定的目的。
前面說過,所謂“運行環境”就是對象,this指函數運行時所在的那個對象。如果一個函數在全局環境中運行,this就是指頂層對象(瀏覽器中為window對象);如果一個函數作為某個對象的方法運行,this就是指那個對象。
可以近似地認為,this是所有函數運行時的一個隱藏參數,決定了函數的運行環境。
### 使用場合
this的使用可以分成以下幾個場合。
(1)全局環境
在全局環境使用this,它指的就是頂層對象window。
~~~
this === window // true
function f() {
console.log(this === window); // true
}
~~~
上面代碼說明,不管是不是在函數內部,只要是在全局環境下運行,this就是指全局對象window。
(2)構造函數
構造函數中的this,指的是實例對象。
~~~
var O = function(p) {
this.p = p;
};
O.prototype.m = function() {
return this.p;
};
~~~
上面代碼定義了一個構造函數O。由于this指向實例對象,所以在構造函數內部定義this.p,就相當于定義實例對象有一個p屬性;然后m方法可以返回這個p屬性。
~~~
var o = new O("Hello World!");
o.p // "Hello World!"
o.m() // "Hello World!"
~~~
(3)對象的方法
當a對象的方法被賦予b對象,該方法就變成了普通函數,其中的this就從指向a對象變成了指向b對象。這就是this取決于運行時所在的對象的含義,所以要特別小心。如果將某個對象的方法賦值給另一個對象,會改變this的指向。
~~~
var o1 = new Object();
o1.m = 1;
o1.f = function (){ console.log(this.m);};
o1.f() // 1
var o2 = new Object();
o2.m = 2;
o2.f = o1.f
o2.f() // 2
~~~
從上面代碼可以看到,f是o1的方法,但是如果在o2上面調用這個方法,f方法中的this就會指向o2。這就說明JavaScript函數的運行環境完全是動態綁定的,可以在運行時切換。
如果不想改變this的指向,可以將o2.f改寫成下面這樣。
~~~
o2.f = function (){ o1.f() };
o2.f() // 1
~~~
上面代碼表示,由于f方法這時是在o1下面運行,所以this就指向o1。
有時,某個方法位于多層對象的內部,這時如果為了簡化書寫,把該方法賦值給一個變量,往往會得到意想不到的結果。
~~~
var a = {
b : {
m : function() {
console.log(this.p);
},
p : 'Hello'
}
};
var hello = a.b.m;
hello() // undefined
~~~
上面代碼表示,m屬于多層對象內部的一個方法。為求簡寫,將其賦值給hello變量,結果調用時,this指向了全局對象。為了避免這個問題,可以只將m所在的對象賦值給hello,這樣調用時,this的指向就不會變。
~~~
var hello = a.b;
hello.m() // Hello
~~~
(4)Node.js
在Node.js中,this的指向又分成兩種情況。全局環境中,this指向全局對象global;模塊環境中,this指向module.exports。
~~~
// 全局環境
this === global // true
// 模塊環境
this === module.exports // true
~~~
### 使用注意點
(1)避免多層this
由于this的指向是不確定的,所以切勿在函數中包含多層的this。
~~~
var o = {
f1: function() {
console.log(this);
var f2 = function() {
console.log(this);
}();
}
}
o.f1()
// Object
// Window
~~~
上面代碼包含兩層this,結果運行后,第一層指向該對象,第二層指向全局對象。一個解決方法是在第二層改用一個指向外層this的變量。
~~~
var o = {
f1: function() {
console.log(this);
var that = this;
var f2 = function() {
console.log(that);
}();
}
}
o.f1()
// Object
// Object
~~~
上面代碼定義了變量that,固定指向外層的this,然后在內層使用that,就不會發生this指向的改變。
(2)避免數組處理方法中的this
數組的map和foreach方法,允許提供一個函數作為參數。這個函數內部不應該使用this。
~~~
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v+' '+item);
});
}
}
o.f()
// undefined a1
// undefined a2
~~~
上面代碼中,foreach方法的參數函數中的this,其實是指向window對象,因此取不到o.v的值。
解決這個問題的一種方法,是使用中間變量。
~~~
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
var that = this;
this.p.forEach(function (item) {
console.log(that.v+' '+item);
});
}
}
o.f()
// hello a1
// hello a2
~~~
另一種方法是將this當作foreach方法的第二個參數,固定它的運行環境。
~~~
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v+' '+item);
}, this);
}
}
o.f()
// hello a1
// hello a2
~~~
(3)避免回調函數中的this
回調函數中的this往往會改變指向,最好避免使用。
~~~
var o = new Object();
o.f = function (){
console.log(this === o);
}
o.f() // true
~~~
上面代碼表示,如果調用o對象的f方法,其中的this就是指向o對象。
但是,如果將f方法指定給某個按鈕的click事件,this的指向就變了。
~~~
$("#button").on("click", o.f);
~~~
點擊按鈕以后,控制臺會顯示false。原因是此時this不再指向o對象,而是指向按鈕的DOM對象,因為f方法是在按鈕對象的環境中被調用的。這種細微的差別,很容易在編程中忽視,導致難以察覺的錯誤。
為了解決這個問題,可以采用下面的一些方法對this進行綁定,也就是使得this固定指向某個對象,減少不確定性。
## 固定this的方法
this的動態切換,固然為JavaScript創造了巨大的靈活性,但也使得編程變得困難和模糊。有時,需要把this固定下來,避免出現意想不到的情況。JavaScript提供了call、apply、bind這三個方法,來切換/固定this的指向。
### call方法
函數的call方法,可以指定該函數內部this的指向(即函數執行時所在的作用域),然后在所指定的作用域中,調用該函數。
~~~
var o = {};
var f = function (){
return this;
};
f() === this // true
f.call(o) === o // true
~~~
上面代碼中,在全局環境運行函數f時,this指向全局環境;call方法可以改變this的指向,指定this指向對象o,然后在對象o的作用域中運行函數f。
再看一個例子。
~~~
var n = 123;
var o = { n : 456 };
function a() {
console.log(this.n);
}
a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(o) // 456
~~~
上面代碼中,a函數中的this關鍵字,如果指向全局對象,返回結果為123。如果使用call方法將this關鍵字指向o對象,返回結果為456。可以看到,如果call方法沒有參數,或者參數為null或undefined,則等同于指向全局對象。
call方法的完整使用格式如下。
~~~
func.call(thisValue, arg1, arg2, ...)
~~~
它的第一個參數就是this所要指向的那個對象,后面的參數則是函數調用時所需的參數。
~~~
function add(a,b) {
return a+b;
}
add.call(this,1,2) // 3
~~~
上面代碼中,call方法指定函數add在當前環境(對象)中運行,并且參數為1和2,因此函數add運行后得到3。
call方法的一個應用是調用對象的原生方法。
~~~
var obj = {};
obj.hasOwnProperty('toString') // false
obj.hasOwnProperty = function (){
return true;
};
obj.hasOwnProperty('toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false
~~~
上面代碼中,hasOwnProperty是obj對象繼承的方法,如果這個方法一旦被覆蓋,就不會得到正確結果。call方法可以解決這個方法,它將hasOwnProperty方法的原始定義放到obj對象上執行,這樣無論obj上有沒有同名方法,都不會影響結果。
### apply方法
apply方法的作用與call方法類似,也是改變this指向,然后再調用該函數。唯一的區別就是,它接收一個數組作為函數執行時的參數,使用格式如下。
~~~
func.apply(thisValue, [arg1, arg2, ...])
~~~
apply方法的第一個參數也是this所要指向的那個對象,如果設為null或undefined,則等同于指定全局對象。第二個參數則是一個數組,該數組的所有成員依次作為參數,傳入原函數。原函數的參數,在call方法中必須一個個添加,但是在apply方法中,必須以數組形式添加。
請看下面的例子。
~~~
function f(x,y){
console.log(x+y);
}
f.call(null,1,1) // 2
f.apply(null,[1,1]) // 2
~~~
上面的f函數本來接受兩個參數,使用apply方法以后,就變成可以接受一個數組作為參數。
利用這一點,可以做一些有趣的應用。
(1)找出數組最大元素
JavaScript不提供找出數組最大元素的函數。結合使用apply方法和Math.max方法,就可以返回數組的最大元素。
~~~
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a)
// 15
~~~
(2)將數組的空元素變為undefined
通過apply方法,利用Array構造函數將數組的空元素變成undefined。
~~~
Array.apply(null, ["a",,"b"])
// [ 'a', undefined, 'b' ]
~~~
空元素與undefined的差別在于,數組的foreach方法會跳過空元素,但是不會跳過undefined。因此,遍歷內部元素的時候,會得到不同的結果。
~~~
var a = ["a",,"b"];
function print(i) {
console.log(i);
}
a.forEach(print)
// a
// b
Array.apply(null,a).forEach(print)
// a
// undefined
// b
~~~
(3)轉換類似數組的對象
另外,利用數組對象的slice方法,可以將一個類似數組的對象(比如arguments對象)轉為真正的數組。
~~~
Array.prototype.slice.apply({0:1,length:1})
// [1]
Array.prototype.slice.apply({0:1})
// []
Array.prototype.slice.apply({0:1,length:2})
// [1, undefined]
Array.prototype.slice.apply({length:1})
// [undefined]
~~~
上面代碼的apply方法的參數都是對象,但是返回結果都是數組,這就起到了將對象轉成數組的目的。從上面代碼可以看到,這個方法起作用的前提是,被處理的對象必須有length屬性,以及相對應的數字鍵。
(4)綁定回調函數的對象
上一節按鈕點擊事件的例子,可以改寫成
~~~
var o = new Object();
o.f = function (){
console.log(this === o);
}
var f = function (){
o.f.apply(o);
// 或者 o.f.call(o);
};
$("#button").on("click", f);
~~~
點擊按鈕以后,控制臺將會顯示true。由于apply方法(或者call方法)不僅綁定函數執行時所在的對象,還會立即執行函數,因此不得不把綁定語句寫在一個函數體內。更簡潔的寫法是采用下面介紹的bind方法。
### bind方法
bind方法用于將函數體內的this綁定到某個對象,然后返回一個新函數。它的使用格式如下。
~~~
func.bind(thisValue, arg1, arg2,...)
~~~
下面是一個例子。
~~~
var o1 = new Object();
o1.p = 123;
o1.m = function (){
console.log(this.p);
};
o1.m() // 123
var o2 = new Object();
o2.p = 456;
o2.m = o1.m;
o2.m() // 456
o2.m = o1.m.bind(o1);
o2.m() // 123
~~~
上面代碼使用bind方法將o1.m方法綁定到o1以后,在o2對象上調用o1.m的時候,o1.m函數體內部的this.p就不再到o2對象去尋找p屬性的值了。
bind比call方法和apply方法更進一步的是,除了綁定this以外,還可以綁定原函數的參數。
~~~
var add = function (x,y) {
return x*this.m + y*this.n;
}
var obj = {
m: 2,
n: 2
};
var newAdd = add.bind(obj, 5);
newAdd(5)
// 20
~~~
上面代碼中,bind方法除了綁定this對象,還綁定了add函數的第一個參數,結果newAdd函數只要一個參數就能運行了。
如果bind方法的第一個參數是null或undefined,等于將this綁定到全局對象,函數運行時this指向全局對象(在瀏覽器中為window)。
~~~
function add(x,y) { return x+y; }
var plus5 = add.bind(null, 5);
plus5(10) // 15
~~~
上面代碼除了將add函數的運行環境綁定為全局對象,還將add函數的第一個參數綁定為5,然后返回一個新函數。以后,每次運行這個新函數,就只需要提供另一個參數就夠了。
bind方法有一些使用注意點。
(1)每一次返回一個新函數
bind方法每運行一次,就返回一個新函數,這會產生一些問題。比如,監聽事件的時候,不能寫成下面這樣。
~~~
element.addEventListener('click', o.m.bind(o));
~~~
上面代碼表示,click事件綁定bind方法生成的一個匿名函數。這樣會導致無法取消綁定,所以,下面的代碼是無效的。
~~~
element.removeEventListener('click', o.m.bind(o));
~~~
正確的方法是寫成下面這樣:
~~~
var listener = o.m.bind(o);
element.addEventListener('click', listener);
// ...
element.removeEventListener('click', listener);
~~~
(2)bind方法的自定義代碼
對于那些不支持bind方法的老式瀏覽器,可以自行定義bind方法。
~~~
if(!('bind' in Function.prototype)){
Function.prototype.bind = function(){
var fn = this;
var context = arguments[0];
var args = Array.prototype.slice.call(arguments, 1);
return function(){
return fn.apply(context, args);
}
}
}
~~~
(3)jQuery的proxy方法
除了用bind方法綁定函數運行時所在的對象,還可以使用jQuery的$.proxy方法,它與bind方法的作用基本相同。
~~~
$("#button").on("click", $.proxy(o.f, o));
~~~
上面代碼表示,$.proxy方法將o.f方法綁定到o對象。
(4)結合call方法使用
利用bind方法,可以改寫一些JavaScript原生方法的使用形式,以數組的slice方法為例。
~~~
[1,2,3].slice(0,1)
// [1]
// 等同于
Array.prototype.slice.call([1,2,3], 0, 1)
// [1]
~~~
上面的代碼中,數組的slice方法從[1, 2, 3]里面,按照指定位置和長度切分出另一個數組。這樣做的本質是在[1, 2, 3]上面調用Array.prototype.slice方法,因此可以用call方法表達這個過程,得到同樣的結果。
call方法實質上是調用Function.prototype.call方法,因此上面的表達式可以用bind方法改寫。
~~~
var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]
~~~
可以看到,利用bind方法,將[1, 2, 3].slice(0, 1)變成了slice([1, 2, 3], 0, 1)的形式。這種形式的改變還可以用于其他數組方法。
~~~
var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);
var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]
pop(a)
a // [1, 2, 3]
~~~
如果再進一步,將Function.prototype.call方法綁定到Function.prototype.bind對象,就意味著bind的調用形式也可以被改寫。
~~~
function f(){
console.log(this.v);
}
var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f,o)() // 123
~~~
上面代碼表示,將Function.prototype.call方法綁定Function.prototype.bind以后,bind方法的使用形式從f.bind(o),變成了bind(f, o)。
## 參考鏈接
* Jonathan Creamer,?[Avoiding the "this" problem in JavaScript](http://tech.pro/tutorial/1192/avoiding-the-this-problem-in-javascript)
* Erik Kronberg,?[Bind, Call and Apply in JavaScript](https://variadic.me/posts/2013-10-22-bind-call-and-apply-in-javascript.html)
* Axel Rauschmayer,?[JavaScript’s this: how it works, where it can trip you up](http://www.2ality.com/2014/05/this.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 框架