老生常談JavaScript設(shè)計模式之call,apply,this詳解
前言:
最近在看曾探老師的《JavaScript設(shè)計模式》,說實話,可能是我基礎(chǔ)還不夠扎實,書里的很多內(nèi)容對我來說不是“一看就懂”,而是“看了三遍才懂一點(diǎn)點(diǎn)”。特別是像代碼邏輯、概念解釋這些地方,常常是一次看不懂,就來兩次;兩次還迷糊,就再來一次。但我慢慢也想通了,學(xué)習(xí)本來就是一個不斷重復(fù)的過程。每一次重讀,都能從字里行間中發(fā)現(xiàn)新的理解,就像挖寶藏一樣,每次翻一翻都有新收獲。為了讓自己記得更牢,也為了讓以后復(fù)習(xí)的時候不那么枯燥,我在反復(fù)閱讀第二章“this、call、apply”之后,試著用自己的話,把知識點(diǎn)編成了一個小故事的形式,講給自己聽。
我想,這不僅是一種學(xué)習(xí)方式,也是我對這本書的一種致敬。
this找家
想象一下,this就像是一個四處旅行的人,他總在尋找它的“家”,但是問題來了,這個人(this)的家并不固定,取決于他是怎么被邀請這個地方的,在javaScript中,this就是一個旅行者,他的指向完全依賴于函數(shù)的調(diào)用方式。
如果是對象的方法調(diào)用:比如obj.myMethod(),那么this的家就安在obj里面。
如果是作為構(gòu)造函數(shù)調(diào)用:比如new MyClass(),這時候this就有了自己的新家,一個新的對象實例
如果是直接調(diào)用函數(shù),比如myFunction(),那么嚴(yán)格模式下,this就會變得無家可歸,即undefined,而在非嚴(yán)格模式下,則默認(rèn)回到了全局對象瀏覽器的window的懷抱。
call 和 apply 導(dǎo)游
現(xiàn)在,比如我們的this旅行者到了一個陌生的城市,不知道如何找到正確的家,這時候就可以請call和apply兩位熱心的導(dǎo)游出現(xiàn)了。call這個導(dǎo)游很細(xì)心,他會親自帶著this去正確的地方,并且還允許你指定額外的參數(shù)列表,就像myFunction.call(context,arg1,arg2)。在這里,context就是我們希望this去的家,而后面參數(shù)就是這次旅行所需要的裝備。apply與call不同的是,apply更喜歡一次性把所有的東西都準(zhǔn)備好再出發(fā),所以除了第一個參數(shù)用來指定this應(yīng)該去的"家"外,它接收第二個參數(shù)為一個數(shù)組或類數(shù)組對象,里面包含了所需要傳遞給函數(shù)的參數(shù),如myFunction.apply(context,[arg1,arg2]),總之,當(dāng)你想要控制this的指向,或者需要特定的方式傳遞參數(shù)的時候,call和apply就是你的得力導(dǎo)游,他們不僅能幫this找到回家的路,還能確保旅途愉快順利。
this的指向
第一站、作為對象的方法被調(diào)用(this回到家)
有一天,this被一個叫做obj的大叔收留了,成為了大叔家的傭人。
var obj = {
a: 1,
getA: function() {
console.log(this === obj); // true
console.log(this.a); // 1
}
};
obj.getA();著obj家里,this每天多的都很開心,this開心的喊:“我終于找到加啦!我是obj家的孩子”,因為他是在obj家里被調(diào)用的,所以他就是的孩子。
第二站、普通函數(shù)調(diào)用(this離家出走)
但是有一天,this不小心跑出了obj的家,在外面遇到一個叫g(shù)etName的陌生人:
var getName = obj.getName; getName(); // 輸出 undefined 或 globalName
??這時候的this已經(jīng)不是obj的孩子了,他又變成了流浪漢,回到了全局大哥window家,不過別擔(dān)心,如果this在全局如果window家過的不開心,可以開啟"use strict"【嚴(yán)格模式】模式,this更加自尊自愛,寧愿當(dāng)個“無家可歸的孤獨(dú)者”,也不愿意隨便認(rèn)爹! 很有骨氣吧,用孤獨(dú)換的骨氣 哈哈哈。
第三站、構(gòu)造器調(diào)用(this自己當(dāng)?shù)?
后來啊,有骨氣的this長大了,學(xué)會了獨(dú)立,決定自己當(dāng)爸爸!
function Person(name) {
this.name = name;
}
var tom = new Person('Tom');
console.log(tom.name); // Tom結(jié)果this發(fā)現(xiàn),自己辛辛苦苦當(dāng)?shù)牡?,娃娃居然被別人搶走了。
第四站、Function.prototype.call 或 Function.prototype.apply 調(diào)用
這時候,兩位神秘人物登場了,他們就是大名鼎鼎JavaScript劇場的導(dǎo)演call和apply!,他們告訴this:“this你很堅強(qiáng),我們被你打動了,現(xiàn)在開始,你想演誰都可以,我?guī)湍銚Q身份。”,于是:
var obj1 = { name: '鋼鐵俠' };
var obj2 = { name: '蜘蛛俠' };
function sayHi() {
console.log("我是" + this.name);
}
sayHi.call(obj1); // 我是鋼鐵俠
sayHi.apply(obj2); // 我是蜘蛛俠this得到兩位大哥的幫助,開始在不同的角色之間切換,一會是鋼鐵俠,一會是蜘蛛俠,忙的不亦樂乎,體驗?zāi)欠N既可以當(dāng)英雄,又可以有家的感覺。
有一天,this在div家做客,突然被一個叫做callback的函數(shù)騙走了身份信息:
<div id="div1">我是一個 div</div>
<script>
document.getElementById('div1').onclick = function () {
alert(this.id); // div1 ?
var callback = function () {
alert(this.id); // window ?
}
callback();
};
</script>這個時候,this又跑回了window家,而它本應(yīng)該屬于哪個div,怎么辦呢,this想了個辦法:
var that = this;
var callback = function () {
alert(that.id); // div1 ?
}就像this孩子給自己辦一張身份證,再也不怕他迷路啦。
修復(fù) document.getElementById 的 this
有一次,this被借去演習(xí),結(jié)果卻跑到了document.getElementById里面,導(dǎo)致整個劇組都亂了套。
<html> <body> <div id="div1">我是一個 div</div> </body> <script> var getId = document.getElementById; getId( 'div1' ); // 報錯,this不在指向document </script> </html>
getId( ‘div1’ );就會報錯,this不在指向document,原來this應(yīng)該是docuement家的人,結(jié)果卻被當(dāng)成window使用了,apply導(dǎo)演不舍得this,就決定動用自己的能力,立刻修復(fù):
document.getElementById = (function( func ){
return function(){
return func.apply( document, arguments );
}
})( document.getElementById );
var getId = document.getElementById;
var div = getId( 'div1' );
alert (div.id); // 輸出: div1終于,this回到了正確的家庭,拯救了整個劇組,經(jīng)過這次慘痛的教訓(xùn),導(dǎo)演對這次事故進(jìn)行分析總結(jié):

導(dǎo)演發(fā)現(xiàn),在Chrome執(zhí)行過后發(fā)現(xiàn),var getId = document.getElementById拋出了一個異常,因為許多的引擎document.getElementById方法的內(nèi)部實現(xiàn)張需要用到this。這個this本來被期望指向document,當(dāng)getElementById方法作為document對象的屬性被調(diào)用時,方法內(nèi)部的this確實是指向document的,但是當(dāng)getId來引用document.getElementById之后,再調(diào)用getId,此時就成了普通函數(shù)調(diào)用,函數(shù)內(nèi)部的this指向了Window,而不是原來的document。
導(dǎo)演發(fā)現(xiàn)可以使用apply,把document當(dāng)作this傳入getId函數(shù),幫助“修正”this:
<script>
document.getElementById = (function (func) {
return function () {
return func.apply(document, arguments);
};
})(document.getElementById);
var getId = document.getElementById;
var div = getId("div1");
alert(div.id); // 輸出: div1
</script>
終于通過導(dǎo)演的不懈努力與分析讓this回到了正確的家庭總結(jié)了整個劇組,甚至還總結(jié)了口訣:
this是誰調(diào)用了我,我就跟誰混。call/apply是this的導(dǎo)演,想讓this演誰都可以。
發(fā)生了上面的事情之后,this學(xué)會了如何在javaScript世界中找到自己的歸屬,它不再輕易迷路,也不再害怕被借來借去,從此要記?。?/p>
多問問this:“whu ar you ?" 你是誰?多請call和apply來幫忙。別忘了用bind或that=this給this上個保險!
call和Apply
Apply序
apply是一個豪放派的導(dǎo)演,他習(xí)慣把所有演員打包成一個團(tuán)隊(數(shù)組或類數(shù)組),然后一次性推到主演面前。
func.apply(thisArg, [arg1, arg2, ...]);
盡管風(fēng)格不同,但是目標(biāo)是一致的,幫助函數(shù)找到正確的舞臺背景(改變this的指向),并讓每個演員都能發(fā)揮最大的作用(正確的傳遞)。
Apply正文:
apply接收兩個參數(shù),第一個參數(shù)指定了函數(shù)體內(nèi)this的指向,第二個參數(shù)為一個帶下標(biāo)的集合,這個集合可以為數(shù)組,也可以為類數(shù)組,apply方法把這個結(jié)合中的元素作為參數(shù)傳遞給被調(diào)用的函數(shù):
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ]
};
func.apply( null, [ 1, 2, 3 ] ); 在這段代碼中,參數(shù) 1、2、3 被放在數(shù)組中一起傳入 func 函數(shù),它們分別對應(yīng) func 參數(shù)列表中的 a、b、c。
call序
call是一位細(xì)心講究的導(dǎo)演,他喜歡把演員(參數(shù))一個個親自介紹給主演(函數(shù)),確保每個演員都有明確的角色。
func.call(thisArg, arg1, arg2, ...);
call正文:
call傳入的參數(shù)數(shù)量不固定,跟apply相同的是,第一個參數(shù)也是代表函數(shù)體內(nèi)的this指向,從第二個參數(shù)開始往后,每個參數(shù)被依次傳入函數(shù):
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ]
};
func.call( null, 1, 2, 3 );當(dāng)調(diào)用一個函數(shù)的時候,JavaScript的解釋器并不會計較形參和實參的數(shù)量、類型、以及順序上的區(qū)別,JavaScript的參數(shù)在內(nèi)部就用一個數(shù)組來表示的,從這個意義上說,apply比call使用率更高,不必關(guān)心具體多少個參數(shù)被傳入函數(shù),只要作用apply一股腦地推過去即可。
注意:
當(dāng)使用call或者apply的時候,如果我們傳入的第一個參數(shù)為null,函數(shù)體內(nèi)的this會默認(rèn)值想宿主對象,在瀏覽器中則是windows。
Function.prototype.bind
Function.prototype.bind是一個秘密裝備,能讓你隨心所欲的控制函數(shù)內(nèi)部的this指向,即使在遙遠(yuǎn)的地方調(diào)用這個函數(shù)時也能保持“它”的初心,首先我們要打造一個屬于自己的 Function.prototype.bind我們先把 func 函數(shù)的引用保存起來,然后返回一個新的函數(shù)。當(dāng)我們在將來執(zhí)行 func 函數(shù)時,實際上先執(zhí)行的是這個剛剛返回的新函數(shù)。在新函數(shù)內(nèi)部,self.apply( context, arguments )這句代碼才是執(zhí)行原來的 func 函數(shù),并且指定 context對象為 func 函數(shù)體內(nèi)的 this。
Function.prototype.bind = function(context) {
var self = this; // 保存原函數(shù),就像把寶劍藏在腰間
return function() { // 返回一個新的函數(shù),這就是我們的新武器
return self.apply(context, arguments); // 當(dāng)使用這把寶劍時,它會自動指向正確的方向
}
};
var obj = { name: 'sven' }; // 我們的英雄 sven
var func = function() {
alert(this.name); // 輸出:sven
}.bind(obj);
func(); // 英雄登場!上面代碼中,bind就像給函數(shù)穿上了一層魔法鎧甲,無論什么時候,只要調(diào)用它,它就會帶著obj的這個身份出現(xiàn)。但是,真正的超級英雄不能只有一技之長!我們需要讓 bind更加強(qiáng)大,讓它不僅能綁定 this,還能預(yù)先填入一些參數(shù)。這就像是給我們的寶劍裝上了“魔法石”,讓它變得更強(qiáng)大:
Function.prototype.bind = function() {
var self = this, // 保存原函數(shù)
context = [].shift.call(arguments), // 需要綁定的 this 上下文,也就是英雄的身份
args = [].slice.call(arguments); // 把剩下的參數(shù)轉(zhuǎn)換成數(shù)組,作為魔法石
return function() { // 返回一個新的函數(shù),這是我們的終極武器
return self.apply(context, [].concat.call(args, [].slice.call(arguments)));
// 組合兩次傳入的參數(shù),作為新函數(shù)的參數(shù)
}
};
var obj = { name: 'sven' }; // 英雄 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); // 召喚英雄,帶上所有的魔法石!改變 this 指向:為函數(shù)找到真正的家
在一個戲劇性的場景中,有一名為getName的函數(shù),它渴望知道自己到底屬于哪個家族:
var obj1 = { name: 'sven' };
var obj2 = { name: 'anne' };
window.name = 'global';
function getName() {
alert(this.name);
}
getName(); // 輸出: global
// 現(xiàn)在,Call 導(dǎo)演登場,幫助 getName 找到了它的真正歸屬——obj1 家族
getName.call(obj1); // 輸出: sven通過call和apply大導(dǎo)演的幫助下,getName終于找到了它真正的家!
借用其他對象的方法:杜鵑鳥的智慧
JavaScript中也有一種"借殼生蛋"的藝術(shù),就像杜鵑鳥將自己的蛋托付給其他鳥類孵化一樣,這里call和apply也能幫我們借用其他對象的方法來完成一些任務(wù)。
例1:
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( 'sven' );
console.log( b.getName() ); // 輸出: 'sven'上面例子中,B借用了A的構(gòu)造函數(shù)邏輯來初始化自己的實例屬性,可以說是繼承了A的部分功能,準(zhǔn)確來說是構(gòu)造函數(shù)繼承或借用構(gòu)造函數(shù)。
函數(shù)的參數(shù)列表 arguments 是一個類數(shù)組對象,雖然它也有“下標(biāo)”,但它并非真正的數(shù)組,所以也不能像數(shù)組一樣,進(jìn)行排序操作或者往集合里添加一個新的元素。
我們常常會借用 Array.prototype 對象上的方法。比如想往 arguments 中添加一個新的元素,通常會借用Array.prototype.push:
(function() {
Array.prototype.push.call(arguments, 3);
console.log(arguments); // 輸出: [1, 2, 3]
})(1, 2);Call 導(dǎo)演巧妙地讓 Array.prototype.push 方法在非數(shù)組的對象上施展魔法,成功地添加了一個新成員。
無論是改變this的指向,還是靈活地傳遞參數(shù),甚至是借用其他對象的方法,Call 和 Apply 總能以其獨(dú)特的技巧讓每一個函數(shù)都成為舞臺上的明星。
到此這篇關(guān)于老生常談JavaScript設(shè)計模式之call,apply,this的文章就介紹到這了,更多相關(guān)js call,apply,this內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JavaScript改變this指向的四種方法(bind、call、apply 和箭頭函數(shù))
- JavaScript中的this/call/apply/bind的使用及區(qū)別
- JS中this的指向以及call、apply的作用
- 詳解JS中的this、apply、call、bind(經(jīng)典面試題)
- 淺談JavaScript中的apply/call/bind和this的使用
- JS中改變this指向的方法(call和apply、bind)
- JavaScript中的this,call,apply使用及區(qū)別詳解
- Javascript技術(shù)難點(diǎn)之a(chǎn)pply,call與this之間的銜接
- 簡單對比分析JavaScript中的apply,call與this的使用
- JavaScript call apply使用 JavaScript對象的方法綁定到DOM事件后this指向問題
相關(guān)文章
javascript generator生成器函數(shù)與asnyc/await語法糖
本文主要介紹了javascript generator生成器函數(shù)與asnyc/await語法糖,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
JavaScript如何讓select選擇框可輸入和可下拉選擇
我們知道一般select下拉框是只能選擇的,而有時我們會遇到下拉框中沒有要選擇的信息項或者下拉選項特別多時,需要允許用戶輸入想要的內(nèi)容,這篇文章主要給大家介紹了關(guān)于JavaScript如何讓select選擇框可輸入和可下拉選擇的相關(guān)資料,需要的朋友可以參考下2023-10-10
JS中數(shù)組與對象相互轉(zhuǎn)換的實現(xiàn)方式
這篇文章主要介紹了JS中數(shù)組與對象相互轉(zhuǎn)換的實現(xiàn)方式,文章通過代碼示例講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-04-04

