javascript
JavaScript设计模式与开发实践 | 02 - this、call和apply
this
JavaScript的this總是指向一個(gè)對象,至于指向哪個(gè)對象,是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境的動(dòng)態(tài)綁定的,而非函數(shù)被聲明時(shí)的環(huán)境。
this的指向
this的指向大致可以分為以下4類:
作為對象的方法調(diào)用
作為普通函數(shù)調(diào)用
構(gòu)造器調(diào)用
Function.prototype.call或Function.prototype.apply調(diào)用
1.作為對象的方法調(diào)用
當(dāng)函數(shù)作為對象的方法被調(diào)用時(shí),this指向該對象:
// 聲明obj對象 var obj = {a: 'a屬性的值', // a 屬性getA: function(){ // getA()方法console.log(this === obj); // 輸出:trueconsole.log(this.a); // 輸出: a屬性的值} };obj.getA();2.作為普通函數(shù)調(diào)用
當(dāng)函數(shù)不作為對象的屬性被調(diào)用時(shí),也就是以普通函數(shù)方式,this指向全局對象。在瀏覽器的JavaScript里,全局對象是window對象。
window.name = 'globalName'; // 聲明全局對象的name屬性var getName = function(){ // 定義getName()函數(shù)return this.name; };// 調(diào)用函數(shù) console.log(getName()); //輸出: globalName window.name = 'globalName'; // 聲明全局對象的name屬性var myObject = { // 聲明myObject對象name: 'objectName';getName: function(){ // 定義getName()方法return this.name;} }var getName = myObject.getName; // 將getName()方法賦給變量getNameconsole.log(getName()); // 輸出: globalName3.構(gòu)造器調(diào)用
JavaScript沒有類,但可以從構(gòu)造器中創(chuàng)建對象,也提供了new運(yùn)算符用于調(diào)用構(gòu)造器。
大部分JavaScript函數(shù)都可以當(dāng)作構(gòu)造器使用。構(gòu)造器的外表跟普通函數(shù)一樣,他們的區(qū)別在于被調(diào)用的方式。即,使用new運(yùn)算符創(chuàng)建對象時(shí),就是將函數(shù)當(dāng)作構(gòu)造器調(diào)用。當(dāng)用new運(yùn)算符調(diào)用函數(shù)時(shí),該函數(shù)總會(huì)返回一個(gè)對象,此時(shí),構(gòu)造器里的this指向返回的這個(gè)對象。
var myClass = function(){this.name = 'className'; };var obj = new myClass(); console.log(obj.name); // 輸出:seven但,如果構(gòu)造器顯式地返回了一個(gè)object類型的對象,那么此函數(shù)將返回這個(gè)object類型的對象,而不是函數(shù)本身所定義的對象,例如:
var myClass = function(){this.name = 'className';return { //顯式地返回一個(gè)對象name: 'anne'} };var obj = new myClass(); console.log(obj.name); // 輸出:anne而,如果構(gòu)造器不顯式地返回任何數(shù)據(jù),或返回一個(gè)非對象類型的數(shù)據(jù),就不會(huì)造成上述情形。
var myClass = function(){this.name = 'className';return 'anne'; // 返回string類型 };var obj = new myClass(); console.log(obj.name); // 輸出:className4.Function.prototype.call 或 Function.prototype.apply調(diào)用
跟普通函數(shù)調(diào)用相比,用 Function.prototype.call 或 Function.prototype.apply 可以動(dòng)態(tài)地改變傳入函數(shù)的this。
var A = {name: 'ObjectA',getName: function(){return this.name;} };var B = {name: 'ObjectB' };console.log(A.getName()); // 作為對象的方法調(diào)用,輸出:ObjectA console.log(A.getName.call(B)); // 輸出:ObjectB丟失的this
我們經(jīng)常會(huì)因?yàn)閠his的指向與我們的期待不同,而出現(xiàn)undefined的情況,例如:
var obj = {name: 'objName';getName: function(){return this.name;} };// 作為對象的方法調(diào)用,指向obj對象 console.log(obj.getName()); // 輸出:objName// 作為普通函數(shù)調(diào)用,指向全局對象window,name屬性尚未定義 var getName2 = obj.getName; console.log(getName2()); // 輸出:Lundefinedcall 和 apply
ECAMScript3給Function的原型定義了兩個(gè)方法,分別是Function.prototype.call 或 Function.prototype.apply。在一些函數(shù)式風(fēng)格的代碼編寫中,call和apply方法尤為有用。
call和apply的區(qū)別
Function.prototype.call 或 Function.prototype.apply的作用一模一樣,區(qū)別僅在于傳入?yún)?shù)形式的不同。
apply接受兩個(gè)參數(shù),第一個(gè)參數(shù)制定了函數(shù)體內(nèi)this對象的指向,第二個(gè)函數(shù)為一個(gè)帶下標(biāo)的集合,這個(gè)集合可以是數(shù)組,也可以是類數(shù)組。apply方法把這個(gè)集合中的元素作為參數(shù)傳遞給被調(diào)用的函數(shù)。
var func = function(a, b, c){console.log([a, b, c]); // 輸出:[1,2,3] };func.apply(null, [1, 2, 3]);call傳入的參數(shù)數(shù)量不固定,第一個(gè)參數(shù)也是代表了函數(shù)體內(nèi)的this指向,從第二個(gè)參數(shù)開始往后,每個(gè)參數(shù)依次被傳入函數(shù):
var func = function(a, b, c){console.log([a, b, c]); // 輸出:[1,2,3] };func.call(null, 1, 2, 3);當(dāng)調(diào)用一個(gè)函數(shù)時(shí),JavaScript的解釋器并不會(huì)計(jì)較形參和實(shí)參在數(shù)量、類型、以及順序上的區(qū)別,JavaScript的參數(shù)在內(nèi)部就是用一個(gè)數(shù)組來表示的。從這個(gè)意義上說,apply比call的使用率更高,我們不必關(guān)心具體有多少參數(shù)被傳入函數(shù),只要用apply一股腦地推過去就可以了。
當(dāng)使用call或apply的時(shí)候,如果我們傳入的第一個(gè)參數(shù)為null,函數(shù)體內(nèi)的this會(huì)指向默認(rèn)的宿主對象,在瀏覽器中則是window:
var func = function(a, b, c){console.log(this); };func.apply(null, [1, 2, 3]); //輸出:window對象 func.call(null, 1, 2, 3); //輸出:window對象call和apply的用途
改變this指向
Function.prototype.bind
借用其他對象的方法
1.改變this指向
call和apply最常見的用途是改變函數(shù)內(nèi)部的this指向:
var A = {name: 'nameA'; };var B = {name: 'nameB'; };window.name = 'nameWindow';var getName = function(){conlole.log(this.name); };getName(); // 以普通函數(shù)調(diào)用,指向了window對象,輸出:nameWindow getName.call(A); // 改變了this的指向,指向了傳入的對象,輸出:nameA getName.call(B); // 改變了this的指向,指向了傳入的對象,輸出:nameB2.Function.prototype.bind
大部分高級瀏覽器都實(shí)現(xiàn)了內(nèi)置的Function.prototype.bind,用來指定函數(shù)內(nèi)部的this指向。
若沒有原生的Function.prototype.bind實(shí)現(xiàn),可以通過模擬一個(gè):
我們通過Function.prototype.bind來“包裝”func函數(shù),并且傳入一個(gè)對象context當(dāng)作參數(shù),這個(gè)context對象就是我們想要修正的this對象,即讓函數(shù)內(nèi)部的this指向這個(gè)對象。
3.借用其他對象的方法
我們知道,杜鵑即不會(huì)筑巢,也不會(huì)孵雛,而是把自己的蛋寄托給云雀等其他鳥類,讓它們代為孵化和養(yǎng)育。在JavaScript中也存在類似的借用現(xiàn)象。
場景一:借用構(gòu)造函數(shù)
通過這種技術(shù),能夠?qū)崿F(xiàn)一些類似繼承的效果:
場景二:類數(shù)組對象的操作
函數(shù)的參數(shù)列表arguments是一個(gè)類數(shù)組對象,雖然它也有下標(biāo),但它并非真正的數(shù)組,所以也不能像數(shù)組一樣,進(jìn)行排序操作或者往集合里添加一個(gè)新的元素。這時(shí),可以借用Array.prototype對象上的方法。
比如,想往arguments中添加一個(gè)新的元素,可以借用Array.prototype.push:
(function(){Array.prototype.push.call(arguments, 3);console.log(arguments); // 輸出:[1,2,3] })(1, 2);想把a(bǔ)rguments轉(zhuǎn)成真正的數(shù)組的時(shí)候,可以借用Array.prototype.slice方法;想截去arguments列表中的頭一個(gè)元素時(shí),可以借用Array.prototype.shift方法。
PS:本節(jié)內(nèi)容為《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)踐》第二章 筆記。
總結(jié)
以上是生活随笔為你收集整理的JavaScript设计模式与开发实践 | 02 - this、call和apply的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BZOJ 1079: [SCOI2008
- 下一篇: RequireJS入门(一) 转