W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎勵
“面向?qū)ο缶幊獭保∣bject Oriented Programming,縮寫為OOP)是目前主流的編程范式。它的核心思想是將真實(shí)世界中各種復(fù)雜的關(guān)系,抽象為一個個對象,然后由對象之間的分工與合作,完成對真實(shí)世界的模擬。
傳統(tǒng)的計(jì)算機(jī)程序由一系列函數(shù)或一系列指令組成,而面向?qū)ο缶幊痰某绦蛴梢幌盗袑ο蠼M成。每一個對象都是功能中心,具有明確分工,可以完成接受信息、處理數(shù)據(jù)、發(fā)出信息等任務(wù)。因此,面向?qū)ο缶幊叹哂徐`活性、代碼的可重用性、模塊性等特點(diǎn),容易維護(hù)和開發(fā),非常適合多人合作的大型軟件項(xiàng)目。
那么,“對象”(object)到底是什么?
我們從兩個層次來理解。
(1)“對象”是單個實(shí)物的抽象。
一本書、一輛汽車、一個人都可以是“對象”,一個數(shù)據(jù)庫、一張網(wǎng)頁、一個與遠(yuǎn)程服務(wù)器的連接也可以是“對象”。當(dāng)實(shí)物被抽象成“對象”,實(shí)物之間的關(guān)系就變成了“對象”之間的關(guān)系,從而就可以模擬現(xiàn)實(shí)情況,針對“對象”進(jìn)行編程。
(2)“對象”是一個容器,封裝了“屬性”(property)和“方法”(method)。
所謂“屬性”,就是對象的狀態(tài);所謂“方法”,就是對象的行為(完成某種任務(wù))。比如,我們可以把動物抽象為animal對象,“屬性”記錄具體是那一種動物,“方法”表示動物的某種行為(奔跑、捕獵、休息等等)。
雖然不同于傳統(tǒng)的面向?qū)ο缶幊陶Z言,但是JavaScript具有很強(qiáng)的面向?qū)ο缶幊棠芰?。本章介紹JavaScript如何進(jìn)行“面向?qū)ο缶幊獭薄?/p>
“面向?qū)ο缶幊獭钡牡谝徊?,就是要生成對象?/p>
前面說過,“對象”是單個實(shí)物的抽象。所以,通常需要一個模板,表示某一類實(shí)物的共同特征,然后“對象”根據(jù)這個模板生成。
典型的面向?qū)ο缶幊陶Z言(比如C++和Java),存在“類”(class)這樣一個概念。所謂“類”就是對象的模板,對象就是“類”的實(shí)例。JavaScript語言沒有“類”,而改用構(gòu)造函數(shù)(constructor)作為對象的模板。
所謂“構(gòu)造函數(shù)”,就是專門用來生成“對象”的函數(shù)。它提供模板,作為對象的基本結(jié)構(gòu)。一個構(gòu)造函數(shù),可以生成多個對象,這些對象都有相同的結(jié)構(gòu)。
構(gòu)造函數(shù)是一個正常的函數(shù),但是它的特征和用法與普通函數(shù)不一樣。下面就是一個構(gòu)造函數(shù):
var Vehicle = function() {
this.price = 1000;
};
上面代碼中,Vehicle就是構(gòu)造函數(shù),它提供模板,用來生成車輛對象。
構(gòu)造函數(shù)的最大特點(diǎn)就是,函數(shù)體內(nèi)部使用了this關(guān)鍵字,代表了所要生成的對象實(shí)例。生成對象的時候,必需用new命令,調(diào)用Vehicle函數(shù)。
new命令的作用,就是執(zhí)行構(gòu)造函數(shù),返回一個實(shí)例對象。
var Vehicle = function (){
this.price = 1000;
};
var v = new Vehicle();
v.price // 1000
上面代碼通過new命令,讓構(gòu)造函數(shù)Vehicle生成一個實(shí)例對象,保存在變量v中。這個新生成的實(shí)例對象,從構(gòu)造函數(shù)Vehicle繼承了price屬性。在new命令執(zhí)行時,構(gòu)造函數(shù)內(nèi)部的this,就代表了新生成的實(shí)例對象,this.price表示實(shí)例對象有一個price屬性,它的值是1000。
使用new命令時,根據(jù)需要,構(gòu)造函數(shù)也可以接受參數(shù)。
var Vehicle = function (p){
this.price = p;
};
var v = new Vehicle(500);
new命令本身就可以執(zhí)行構(gòu)造函數(shù),所以后面的構(gòu)造函數(shù)可以帶括號,也可以不帶括號。下面兩行代碼是等價的。
var v = new Vehicle();
var v = new Vehicle;
一個很自然的問題是,如果忘了使用new命令,直接調(diào)用構(gòu)造函數(shù)會發(fā)生什么事?
這種情況下,構(gòu)造函數(shù)就變成了普通函數(shù),并不會生成實(shí)例對象。而且由于下面會說到的原因,this這時代表全局對象,將造成一些意想不到的結(jié)果。
var Vehicle = function (){
this.price = 1000;
};
var v = Vehicle();
v.price
// Uncaught TypeError: Cannot read property 'price' of undefined
price
// 1000
上面代碼中,調(diào)用Vehicle構(gòu)造函數(shù)時,忘了加上new命令。結(jié)果,price屬性變成了全局變量,而變量v變成了undefined。
因此,應(yīng)該非常小心,避免出現(xiàn)不使用new命令、直接調(diào)用構(gòu)造函數(shù)的情況。為了保證構(gòu)造函數(shù)必須與new命令一起使用,一個解決辦法是,在構(gòu)造函數(shù)內(nèi)部使用嚴(yán)格模式,即第一行加上use strict
。
function Fubar(foo, bar){
"use strict";
this._foo = foo;
this._bar = bar;
}
Fubar()
// TypeError: Cannot set property '_foo' of undefined
上面代碼的Fubar為構(gòu)造函數(shù),use strict命令保證了該函數(shù)在嚴(yán)格模式下運(yùn)行。由于在嚴(yán)格模式中,函數(shù)內(nèi)部的this不能指向全局對象,默認(rèn)等于undefined,導(dǎo)致不加new調(diào)用會報(bào)錯(JavaScript不允許對undefined添加屬性)。
另一個解決辦法,是在構(gòu)造函數(shù)內(nèi)部判斷是否使用new命令,如果發(fā)現(xiàn)沒有使用,則直接返回一個實(shí)例對象。
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
上面代碼中的構(gòu)造函數(shù),不管加不加new命令,都會得到同樣的結(jié)果。
使用new命令時,它后面的函數(shù)調(diào)用就不是正常的調(diào)用,而是被new命令控制了。內(nèi)部的流程是,先創(chuàng)造一個空對象,作為上下文對象,賦值給函數(shù)內(nèi)部的this關(guān)鍵字。也就是說,this指的是一個新生成的空對象,所有針對this的操作,都會發(fā)生在這個空對象上。
構(gòu)造函數(shù)之所以叫“構(gòu)造函數(shù)”,就是說這個函數(shù)的目的,就是操作上下文對象(即this對象),將其“構(gòu)造”為需要的樣子。如果構(gòu)造函數(shù)的return語句返回的是對象,new命令會返回return語句指定的對象;否則,就會不管return語句,返回構(gòu)造后的上下文對象。
var Vehicle = function (){
this.price = 1000;
return 1000;
};
(new Vehicle()) === 1000
// false
上面代碼中,Vehicle是一個構(gòu)造函數(shù),它的return語句返回一個數(shù)值。這時,new命令就會忽略這個return語句,返回“構(gòu)造”后的this對象。
但是,如果return語句返回的是一個跟this無關(guān)的新對象,new命令會返回這個新對象,而不是this對象。這一點(diǎn)需要特別引起注意。
var Vehicle = function (){
this.price = 1000;
return { price: 2000 };
};
(new Vehicle()).price
// 2000
上面代碼中,構(gòu)造函數(shù)Vehicle的return語句,返回的是一個新對象。new命令會返回這個對象,而不是this對象。
new命令簡化的內(nèi)部流程,可以用下面的代碼表示。
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運(yùn)算符用來確定一個對象是否為某個構(gòu)造函數(shù)的實(shí)例。
var v = new Vehicle();
v instanceof Vehicle
// true
instanceof運(yùn)算符的左邊放置對象,右邊放置構(gòu)造函數(shù)。在JavaScript之中,只要是對象,就有對應(yīng)的構(gòu)造函數(shù)。因此,instanceof運(yùn)算符可以用來判斷值的類型。
[1, 2, 3] instanceof Array // true
({}) instanceof Object // true
上面代碼表示數(shù)組和對象則分別是Array對象和Object對象的實(shí)例。最后那一行的空對象外面,之所以要加括號,是因?yàn)槿绻患?,JavaScript引擎會把一對大括號解釋為一個代碼塊,而不是一個對象,從而導(dǎo)致這一行代碼被解釋為“{}; instanceof Object”,引擎就會報(bào)錯。
需要注意的是,由于原始類型的值不是對象,所以不能使用instanceof運(yùn)算符判斷類型。
"" instanceof String // false
1 instanceof Number // false
上面代碼中,字符串不是String對象的實(shí)例(因?yàn)樽址皇菍ο螅?,?shù)值1也不是Number對象的實(shí)例(因?yàn)閿?shù)值1不是對象)。
如果存在繼承關(guān)系,也就是某個對象可能是多個構(gòu)造函數(shù)的實(shí)例,那么instanceof運(yùn)算符對這些構(gòu)造函數(shù)都返回true。
var a = [];
a instanceof Array // true
a instanceof Object // true
上面代碼表示,a是一個數(shù)組,所以它是Array的實(shí)例;同時,a也是一個對象,所以它也是Object的實(shí)例。
利用instanceof運(yùn)算符,還可以巧妙地解決,調(diào)用構(gòu)造函數(shù)時,忘了加new命令的問題。
function Fubar (foo, bar) {
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
}
else return new Fubar(foo, bar);
}
上面代碼使用instanceof運(yùn)算符,在函數(shù)體內(nèi)部判斷this關(guān)鍵字是否為構(gòu)造函數(shù)Fubar的實(shí)例。如果不是,就表明忘了加new命令。
構(gòu)造函數(shù)內(nèi)部需要用到this關(guān)鍵字。那么,this關(guān)鍵字到底是什么意思呢?
簡單說,this就是指函數(shù)當(dāng)前的運(yùn)行環(huán)境。在JavaScript語言之中,所有函數(shù)都是在某個運(yùn)行環(huán)境之中運(yùn)行,this就是這個環(huán)境。對于JavaScipt語言來說,一切皆對象,運(yùn)行環(huán)境也是對象,所以可以理解成,所有函數(shù)總是在某個對象之中運(yùn)行,this就指向這個對象。這本來并不會讓用戶糊涂,但是JavaScript支持運(yùn)行環(huán)境動態(tài)切換,也就是說,this的指向是動態(tài)的,沒有辦法事先確定到底指向哪個對象,這才是最讓初學(xué)者感到困惑的地方。
舉例來說,有一個函數(shù)f,它同時充當(dāng)a對象和b對象的方法。JavaScript允許函數(shù)f的運(yùn)行環(huán)境動態(tài)切換,即一會屬于a對象,一會屬于b對象,這就要靠this關(guān)鍵字來辦到。
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
上面代碼中,函數(shù)f可以打印出當(dāng)前運(yùn)行環(huán)境中x變量的值。當(dāng)f屬于a對象時,this指向a;當(dāng)f屬于b對象時,this指向b,因此打印出了不同的值。由于this的指向可變,所以可以手動切換運(yùn)行環(huán)境,以達(dá)到某種特定的目的。
前面說過,所謂“運(yùn)行環(huán)境”就是對象,this指函數(shù)運(yùn)行時所在的那個對象。如果一個函數(shù)在全局環(huán)境中運(yùn)行,this就是指頂層對象(瀏覽器中為window對象);如果一個函數(shù)作為某個對象的方法運(yùn)行,this就是指那個對象。
可以近似地認(rèn)為,this是所有函數(shù)運(yùn)行時的一個隱藏參數(shù),決定了函數(shù)的運(yùn)行環(huán)境。
this的使用可以分成以下幾個場合。
(1)全局環(huán)境
在全局環(huán)境使用this,它指的就是頂層對象window。
this === window // true
function f() {
console.log(this === window); // true
}
上面代碼說明,不管是不是在函數(shù)內(nèi)部,只要是在全局環(huán)境下運(yùn)行,this就是指全局對象window。
(2)構(gòu)造函數(shù)
構(gòu)造函數(shù)中的this,指的是實(shí)例對象。
var O = function(p) {
this.p = p;
};
O.prototype.m = function() {
return this.p;
};
上面代碼定義了一個構(gòu)造函數(shù)O。由于this指向?qū)嵗龑ο?,所以在?gòu)造函數(shù)內(nèi)部定義this.p,就相當(dāng)于定義實(shí)例對象有一個p屬性;然后m方法可以返回這個p屬性。
var o = new O("Hello World!");
o.p // "Hello World!"
o.m() // "Hello World!"
(3)對象的方法
當(dāng)a對象的方法被賦予b對象,該方法就變成了普通函數(shù),其中的this就從指向a對象變成了指向b對象。這就是this取決于運(yùn)行時所在的對象的含義,所以要特別小心。如果將某個對象的方法賦值給另一個對象,會改變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上面調(diào)用這個方法,f方法中的this就會指向o2。這就說明JavaScript函數(shù)的運(yùn)行環(huán)境完全是動態(tài)綁定的,可以在運(yùn)行時切換。
如果不想改變this的指向,可以將o2.f改寫成下面這樣。
o2.f = function (){ o1.f() };
o2.f() // 1
上面代碼表示,由于f方法這時是在o1下面運(yùn)行,所以this就指向o1。
有時,某個方法位于多層對象的內(nèi)部,這時如果為了簡化書寫,把該方法賦值給一個變量,往往會得到意想不到的結(jié)果。
var a = {
b : {
m : function() {
console.log(this.p);
},
p : 'Hello'
}
};
var hello = a.b.m;
hello() // undefined
上面代碼表示,m屬于多層對象內(nèi)部的一個方法。為求簡寫,將其賦值給hello變量,結(jié)果調(diào)用時,this指向了全局對象。為了避免這個問題,可以只將m所在的對象賦值給hello,這樣調(diào)用時,this的指向就不會變。
var hello = a.b;
hello.m() // Hello
(4)Node.js
在Node.js中,this的指向又分成兩種情況。全局環(huán)境中,this指向全局對象global;模塊環(huán)境中,this指向module.exports。
// 全局環(huán)境
this === global // true
// 模塊環(huán)境
this === module.exports // true
(1)避免多層this
由于this的指向是不確定的,所以切勿在函數(shù)中包含多層的this。
var o = {
f1: function() {
console.log(this);
var f2 = function() {
console.log(this);
}();
}
}
o.f1()
// Object
// Window
上面代碼包含兩層this,結(jié)果運(yùn)行后,第一層指向該對象,第二層指向全局對象。一個解決方法是在第二層改用一個指向外層this的變量。
var o = {
f1: function() {
console.log(this);
var that = this;
var f2 = function() {
console.log(that);
}();
}
}
o.f1()
// Object
// Object
上面代碼定義了變量that,固定指向外層的this,然后在內(nèi)層使用that,就不會發(fā)生this指向的改變。
(2)避免數(shù)組處理方法中的this
數(shù)組的map和foreach方法,允許提供一個函數(shù)作為參數(shù)。這個函數(shù)內(nèi)部不應(yīng)該使用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方法的參數(shù)函數(shù)中的this,其實(shí)是指向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當(dāng)作foreach方法的第二個參數(shù),固定它的運(yùn)行環(huán)境。
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)避免回調(diào)函數(shù)中的this
回調(diào)函數(shù)中的this往往會改變指向,最好避免使用。
var o = new Object();
o.f = function (){
console.log(this === o);
}
o.f() // true
上面代碼表示,如果調(diào)用o對象的f方法,其中的this就是指向o對象。
但是,如果將f方法指定給某個按鈕的click事件,this的指向就變了。
$("#button").on("click", o.f);
點(diǎn)擊按鈕以后,控制臺會顯示false。原因是此時this不再指向o對象,而是指向按鈕的DOM對象,因?yàn)閒方法是在按鈕對象的環(huán)境中被調(diào)用的。這種細(xì)微的差別,很容易在編程中忽視,導(dǎo)致難以察覺的錯誤。
為了解決這個問題,可以采用下面的一些方法對this進(jìn)行綁定,也就是使得this固定指向某個對象,減少不確定性。
this的動態(tài)切換,固然為JavaScript創(chuàng)造了巨大的靈活性,但也使得編程變得困難和模糊。有時,需要把this固定下來,避免出現(xiàn)意想不到的情況。JavaScript提供了call、apply、bind這三個方法,來切換/固定this的指向。
函數(shù)的call方法,可以指定該函數(shù)內(nèi)部this的指向(即函數(shù)執(zhí)行時所在的作用域),然后在所指定的作用域中,調(diào)用該函數(shù)。
var o = {};
var f = function (){
return this;
};
f() === this // true
f.call(o) === o // true
上面代碼中,在全局環(huán)境運(yùn)行函數(shù)f時,this指向全局環(huán)境;call方法可以改變this的指向,指定this指向?qū)ο髈,然后在對象o的作用域中運(yùn)行函數(shù)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函數(shù)中的this關(guān)鍵字,如果指向全局對象,返回結(jié)果為123。如果使用call方法將this關(guān)鍵字指向o對象,返回結(jié)果為456??梢钥吹?,如果call方法沒有參數(shù),或者參數(shù)為null或undefined,則等同于指向全局對象。
call方法的完整使用格式如下。
func.call(thisValue, arg1, arg2, ...)
它的第一個參數(shù)就是this所要指向的那個對象,后面的參數(shù)則是函數(shù)調(diào)用時所需的參數(shù)。
function add(a,b) {
return a+b;
}
add.call(this,1,2) // 3
上面代碼中,call方法指定函數(shù)add在當(dāng)前環(huán)境(對象)中運(yùn)行,并且參數(shù)為1和2,因此函數(shù)add運(yùn)行后得到3。
call方法的一個應(yīng)用是調(diào)用對象的原生方法。
var obj = {};
obj.hasOwnProperty('toString') // false
obj.hasOwnProperty = function (){
return true;
};
obj.hasOwnProperty('toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false
上面代碼中,hasOwnProperty是obj對象繼承的方法,如果這個方法一旦被覆蓋,就不會得到正確結(jié)果。call方法可以解決這個方法,它將hasOwnProperty方法的原始定義放到obj對象上執(zhí)行,這樣無論obj上有沒有同名方法,都不會影響結(jié)果。
apply方法的作用與call方法類似,也是改變this指向,然后再調(diào)用該函數(shù)。唯一的區(qū)別就是,它接收一個數(shù)組作為函數(shù)執(zhí)行時的參數(shù),使用格式如下。
func.apply(thisValue, [arg1, arg2, ...])
apply方法的第一個參數(shù)也是this所要指向的那個對象,如果設(shè)為null或undefined,則等同于指定全局對象。第二個參數(shù)則是一個數(shù)組,該數(shù)組的所有成員依次作為參數(shù),傳入原函數(shù)。原函數(shù)的參數(shù),在call方法中必須一個個添加,但是在apply方法中,必須以數(shù)組形式添加。
請看下面的例子。
function f(x,y){
console.log(x+y);
}
f.call(null,1,1) // 2
f.apply(null,[1,1]) // 2
上面的f函數(shù)本來接受兩個參數(shù),使用apply方法以后,就變成可以接受一個數(shù)組作為參數(shù)。
利用這一點(diǎn),可以做一些有趣的應(yīng)用。
(1)找出數(shù)組最大元素
JavaScript不提供找出數(shù)組最大元素的函數(shù)。結(jié)合使用apply方法和Math.max方法,就可以返回?cái)?shù)組的最大元素。
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a)
// 15
(2)將數(shù)組的空元素變?yōu)閡ndefined
通過apply方法,利用Array構(gòu)造函數(shù)將數(shù)組的空元素變成undefined。
Array.apply(null, ["a",,"b"])
// [ 'a', undefined, 'b' ]
空元素與undefined的差別在于,數(shù)組的foreach方法會跳過空元素,但是不會跳過undefined。因此,遍歷內(nèi)部元素的時候,會得到不同的結(jié)果。
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)轉(zhuǎn)換類似數(shù)組的對象
另外,利用數(shù)組對象的slice方法,可以將一個類似數(shù)組的對象(比如arguments對象)轉(zhuǎn)為真正的數(shù)組。
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方法的參數(shù)都是對象,但是返回結(jié)果都是數(shù)組,這就起到了將對象轉(zhuǎn)成數(shù)組的目的。從上面代碼可以看到,這個方法起作用的前提是,被處理的對象必須有l(wèi)ength屬性,以及相對應(yīng)的數(shù)字鍵。
(4)綁定回調(diào)函數(shù)的對象
上一節(jié)按鈕點(diǎn)擊事件的例子,可以改寫成
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);
點(diǎn)擊按鈕以后,控制臺將會顯示true。由于apply方法(或者call方法)不僅綁定函數(shù)執(zhí)行時所在的對象,還會立即執(zhí)行函數(shù),因此不得不把綁定語句寫在一個函數(shù)體內(nèi)。更簡潔的寫法是采用下面介紹的bind方法。
bind方法用于將函數(shù)體內(nèi)的this綁定到某個對象,然后返回一個新函數(shù)。它的使用格式如下。
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對象上調(diào)用o1.m的時候,o1.m函數(shù)體內(nèi)部的this.p就不再到o2對象去尋找p屬性的值了。
bind比call方法和apply方法更進(jìn)一步的是,除了綁定this以外,還可以綁定原函數(shù)的參數(shù)。
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函數(shù)的第一個參數(shù),結(jié)果newAdd函數(shù)只要一個參數(shù)就能運(yùn)行了。
如果bind方法的第一個參數(shù)是null或undefined,等于將this綁定到全局對象,函數(shù)運(yùn)行時this指向全局對象(在瀏覽器中為window)。
function add(x,y) { return x+y; }
var plus5 = add.bind(null, 5);
plus5(10) // 15
上面代碼除了將add函數(shù)的運(yùn)行環(huán)境綁定為全局對象,還將add函數(shù)的第一個參數(shù)綁定為5,然后返回一個新函數(shù)。以后,每次運(yùn)行這個新函數(shù),就只需要提供另一個參數(shù)就夠了。
bind方法有一些使用注意點(diǎn)。
(1)每一次返回一個新函數(shù)
bind方法每運(yùn)行一次,就返回一個新函數(shù),這會產(chǎn)生一些問題。比如,監(jiān)聽事件的時候,不能寫成下面這樣。
element.addEventListener('click', o.m.bind(o));
上面代碼表示,click事件綁定bind方法生成的一個匿名函數(shù)。這樣會導(dǎo)致無法取消綁定,所以,下面的代碼是無效的。
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方法綁定函數(shù)運(yùn)行時所在的對象,還可以使用jQuery的$.proxy方法,它與bind方法的作用基本相同。
$("#button").on("click", $.proxy(o.f, o));
上面代碼表示,$.proxy方法將o.f方法綁定到o對象。
(4)結(jié)合call方法使用
利用bind方法,可以改寫一些JavaScript原生方法的使用形式,以數(shù)組的slice方法為例。
[1,2,3].slice(0,1)
// [1]
// 等同于
Array.prototype.slice.call([1,2,3], 0, 1)
// [1]
上面的代碼中,數(shù)組的slice方法從[1, 2, 3]里面,按照指定位置和長度切分出另一個數(shù)組。這樣做的本質(zhì)是在[1, 2, 3]上面調(diào)用Array.prototype.slice方法,因此可以用call方法表達(dá)這個過程,得到同樣的結(jié)果。
call方法實(shí)質(zhì)上是調(diào)用Function.prototype.call方法,因此上面的表達(dá)式可以用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)的形式。這種形式的改變還可以用于其他數(shù)組方法。
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]
如果再進(jìn)一步,將Function.prototype.call方法綁定到Function.prototype.bind對象,就意味著bind的調(diào)用形式也可以被改寫。
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)。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: