《JavaScript設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》之this 北启、 call 和 apply

this 跛蛋、 call 和 apply

this

跟別的語(yǔ)言大相徑庭的是糕档,JavaScript的 this 總是指向一個(gè)對(duì)象莉恼,而具體指向哪個(gè)對(duì)象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境動(dòng)態(tài)綁定的,而非函數(shù)被聲明時(shí)的環(huán)境速那。

this 的指向

  • 作為對(duì)象的方法調(diào)用俐银。

    當(dāng)函數(shù)作為對(duì)象的方法被調(diào)用時(shí), this 指向該對(duì)象端仰。

var obj = {
  a: 1,
  getA: function() {
    console.log(this === obj); //true
    return this.a; //1
  }
}

console.log(obj.getA());
  • 作為普通函數(shù)調(diào)用捶惜。

    this 總是指向全局對(duì)象window。

window.name = 'globalName';
var getName = function(){
return this.name;
};
console.log( getName() ); // 輸出:globalName
  • 構(gòu)造器調(diào)用荔烧。

    當(dāng)用 new 運(yùn)算符調(diào)用函數(shù)時(shí)吱七,該函數(shù)總會(huì)返回一個(gè)對(duì)象汽久,通常情況下,構(gòu)造器里的this就指向返回的這個(gè)對(duì)象踊餐。

 var MyClass = function() {
   this.name = 'steven';
   return { // 顯式地返回一個(gè)對(duì)象
     name: 'anne'
   }
 };
var obj = new MyClass();
console.log("使用構(gòu)造器調(diào)用:" + obj.name); // 輸出:anne
  • Function.prototype.call 或 Function.prototype.apply 調(diào)用景醇。

    可以動(dòng)態(tài)地改變傳入函數(shù)的 this

var obj1 = {
name: 'steven',
getName: function() {
return this.name;
}
};

var obj2 = {
name: 'anne'
}
console.log(obj1.getName.call(obj2));// 輸出:anne

丟失的 this

?一個(gè)例子:使用getId變量來(lái)代替document.getElementById

var getId = function(id){
  return document.getElementById(id);
};
console.log(getId('div1').id)

? 如果getId直接 來(lái)引用 document.getElementById 之后吝岭,再調(diào)用getId 三痰,此時(shí)就成了普通函數(shù)調(diào)用,函數(shù)內(nèi)部的 this指向了window 窜管,而不是原來(lái)的 document酒觅,會(huì)出現(xiàn)報(bào)錯(cuò),情況如下:

 var getId = document.getElementById;
console.log(getId('div1').id); //Uncaught TypeError: Illegal invocation (非法調(diào)用)

? 我們可以嘗試?yán)?apply 把 document 當(dāng)作 this 傳入 getId 函數(shù),幫助“修正” this:

 var getId = (function(obj) {
   return function() {
     return obj.apply(document, arguments);
   }
 })(document.getElementById);
console.log(getId('div1').id)

call和apply

call和apply 的區(qū)別

Function.prototype.call 和 Function.prototype.apply 都是非常常用的方法微峰。它們的作用一模一樣舷丹,區(qū)別僅在于傳入?yún)?shù)形式的不同

  • apply 接受兩個(gè)參數(shù):

    • 第一個(gè)參數(shù)指定了函數(shù)體內(nèi) this 對(duì)象的指向
    • 第二個(gè)參數(shù)為一個(gè)帶下標(biāo)的集合(數(shù)組或類(lèi)數(shù)組)蜓肆, apply方法把這個(gè)集合中的元素作為參數(shù)傳遞給被調(diào)用的函數(shù)
    var func = function( a, b, c ){
    alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ]
    };
    func.apply( null, [ 1, 2, 3 ] );//1參為null颜凯,函數(shù)體內(nèi)的 this 會(huì)指向默認(rèn)的宿主對(duì)象,即window
    
  • call 傳入的參數(shù)數(shù)量不固定

    • 第一個(gè)參數(shù)指定了函數(shù)體內(nèi) this 對(duì)象的指向
    • 從第二個(gè)參數(shù)開(kāi)始往后仗扬,每個(gè)參數(shù)被依次傳入函數(shù)
    var func = function( a, b, c ){
    alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ]
    };
    func.call( null, 1, 2, 3 );//1參為null症概,函數(shù)體內(nèi)的 this 會(huì)指向默認(rèn)的宿主對(duì)象,即window
    

    當(dāng)調(diào)用一個(gè)函數(shù)時(shí)早芭,JavaScript 的解釋器并不會(huì)計(jì)較形參和實(shí)參在數(shù)量彼城、類(lèi)型以及順序上的區(qū)別,JavaScript的參數(shù)在內(nèi)部就是用一個(gè)數(shù)組來(lái)表示的退个。從這個(gè)意義上說(shuō)募壕, applycall的使用率更高,我們不必關(guān)心具體有多少參數(shù)被傳入函數(shù)语盈,只要用 apply 一股腦地推過(guò)去就可以了舱馅。

    當(dāng)使用 call或者 apply的時(shí)候,如果我們傳入的第一個(gè)參數(shù)為 null刀荒,函數(shù)體內(nèi)的 this 會(huì)指向默認(rèn)的宿主對(duì)象代嗤,在瀏覽器中則是 window ,但如果是在嚴(yán)格模式下,函數(shù)體內(nèi)的 this還是為 null 缠借。

    有時(shí)候我們使用 call或者 apply的目的不在于指定 this指向干毅,而是另有用途,比如借用其他對(duì)象的方法泼返。那么我們可以傳入 null來(lái)代替某個(gè)具體的對(duì)象硝逢。

call 和 apply 的用途

改變 this 指向

var obj1 = {
  name: 'steven'
};
var obj2 = {
  name: 'anne'
};
window.name = 'window';
var getName = function(name) {
  console.log(this.name);
};
getName(); //'window'
getName.apply(obj1); //'steven'
getName.apply(obj2); //'anne'

在執(zhí)行getName.apply(obj1)時(shí), getName函數(shù)體內(nèi)的 this就指向 obj1對(duì)象,實(shí)際相當(dāng)于

var getName = function(name) {
  console.log(obj1.name);
};

一個(gè)點(diǎn)擊的實(shí)例場(chǎng)景:

document.getElementById('div1').onclick = function() {
  var _that = this;  //需要額外申明一個(gè)中轉(zhuǎn)變量來(lái)存儲(chǔ)對(duì)象的this
  var foo = function() {
    alert(_that.id);
  }
  return foo();
};

在使用applycall后:

document.getElementById('div1').onclick = function() {
  var foo = function() {
    alert(this.id);
  }
  foo.apply(this);
  return foo();
};

Function.prototype.bind

大部分現(xiàn)代瀏覽器都實(shí)現(xiàn)了內(nèi)置的 Function.prototype.bind 趴捅,用來(lái)指定函數(shù)內(nèi)部的 this 指向垫毙,如果沒(méi)有的話(huà)模擬起來(lái)不困難:

Function.prototype.bind = function(context) {
   var self = this; //保存原函數(shù)
   return function() {
     // 這句代碼才是執(zhí)行原來(lái)的 func 函數(shù)霹疫,并且指定 context對(duì)象為 func 函數(shù)體內(nèi)的 this 拱绑,也是我們想修正的 this 對(duì)象
     return self.apply(context, arguments);
   }
 }
var obj = {
   name: 'steven'
 };
var func = function() {
  alert(this.name); //輸出'steven'
}.bind(obj);

func();

再稍微復(fù)雜一些,使得可以往func函數(shù)內(nèi)預(yù)先填寫(xiě)一些參數(shù)

Function.prototype.bind = function(){
var self = this, // 保存原函數(shù)
context = [].shift.call( arguments ), // 需要綁定的 this 上下文
args = [].slice.call( arguments ); // 剩余的參數(shù)轉(zhuǎn)成數(shù)組
return function(){ // 返回一個(gè)新的函數(shù)
return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
// 執(zhí)行新的函數(shù)的時(shí)候丽蝎,會(huì)把之前傳入的 context 當(dāng)作新函數(shù)體內(nèi)的 this
// 并且組合兩次分別傳入的參數(shù)猎拨,作為新函數(shù)的參數(shù)
}
};
var obj = {
name: 'sven'
};
var func = function( a, b, c, d ){
alert ( this.name ); // 輸出:sven
alert ( [ a, b, c, d ] ) // 輸出:[ 1, 2, 3, 4 ]
}.bind( obj, 1, 2 );
func( 3, 4 );

借用其他對(duì)象的方法

  • 場(chǎng)景一:鳩占鵲巢
 var A = function(name) {
   this.name = name;
 };

var B = function() {
  A.apply(this, arguments);
};

B.prototype.getName = function() {
  return this.name;
}

var b = new B('steven');
console.log(b.getName());
  • 場(chǎng)景二:arguments中添加一個(gè)新的元素,通常會(huì)借用Array.prototype.push
(function(){
  Array.prototype.push.call(arguments,3);
  console.log(arguments);
})(1,2,5,c)

在操作 arguments的時(shí)候屠阻,我們經(jīng)常非常頻繁地找Array.prototype對(duì)象借用方法红省,例如:

  • 想把 arguments 轉(zhuǎn)成真正的數(shù)組的時(shí)候,可以借用 Array.prototype.slice 方法国觉。
  • 想截去arguments 列表中的頭一個(gè)元素時(shí)吧恃,可以借用Array.prototype.shift方法。

我們甚至可以把“任意”對(duì)象傳入 Array.prototype.push麻诀,但是對(duì)象要滿(mǎn)足以下兩個(gè)條件:

  1. 對(duì)象本身要可以存取屬性
  2. 對(duì)象的 length 屬性可讀寫(xiě)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痕寓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蝇闭,更是在濱河造成了極大的恐慌呻率,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呻引,死亡現(xiàn)場(chǎng)離奇詭異礼仗,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)逻悠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)元践,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人童谒,你說(shuō)我怎么就攤上這事卢厂。” “怎么了惠啄?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵慎恒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我撵渡,道長(zhǎng)融柬,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任趋距,我火速辦了婚禮粒氧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘节腐。我一直安慰自己外盯,他們只是感情好摘盆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著饱苟,像睡著了一般孩擂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箱熬,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天类垦,我揣著相機(jī)與錄音,去河邊找鬼城须。 笑死蚤认,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的糕伐。 我是一名探鬼主播砰琢,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼良瞧!你這毒婦竟也來(lái)了陪汽?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤莺褒,失蹤者是張志新(化名)和其女友劉穎掩缓,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體遵岩,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡你辣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尘执。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舍哄。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖誊锭,靈堂內(nèi)的尸體忽然破棺而出表悬,到底是詐尸還是另有隱情,我是刑警寧澤丧靡,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布蟆沫,位于F島的核電站,受9級(jí)特大地震影響温治,放射性物質(zhì)發(fā)生泄漏饭庞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一熬荆、第九天 我趴在偏房一處隱蔽的房頂上張望舟山。 院中可真熱鬧,春花似錦、人聲如沸累盗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)若债。三九已至符相,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拆座,已是汗流浹背主巍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工冠息, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挪凑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓逛艰,卻偏偏與公主長(zhǎng)得像躏碳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子散怖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容