隨筆記錄--call兵志、apply根穷、bind方法理解

apply,call和bind的基本介紹

語法

fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1,param2,...])
fun.bind(thisArg, param1, param2, ...)

返回值

all/apply: 返回fun執(zhí)行的結(jié)果
bind: 返回fun的拷貝姜骡,并擁有指定的this值和初始參數(shù)

參數(shù)

thisArg(可選)

  • fun的this指向thisArg對象。
  • 在非嚴(yán)格模式下屿良,thisArg指定null圈澈,undefined,fun中this指向window對象尘惧。
  • 在嚴(yán)格模式下康栈,fun的this指向undefined
  • 值是原始值(比如:數(shù)字,字符串喷橙,布爾值)的this會指向該原始值的自動包裝對象啥么,如:String,Number贰逾,Boolean悬荣。

param1,param2(可選): 傳給fun的參數(shù)。

如果param不傳或?yàn)?null/undefined疙剑,則表示不需要傳入任何參數(shù).
apply第二個參數(shù)為數(shù)組氯迂,數(shù)組內(nèi)的值為傳給fun的參數(shù)践叠。

call與apply

在JavaScript中,call和apply都是為了改變函數(shù)執(zhí)行的上下文而存在的嚼蚀,也就是
為了改變函數(shù)體內(nèi)部的this的指向禁灼。
JavaScript的一大特點(diǎn)是函數(shù)存在「定義是上下文」和「運(yùn)行是上下文」以及上下文是可以被改變的。

為何要改變執(zhí)行上下文轿曙?舉一個生活中的小例子:平時(shí)沒時(shí)間做飯的我弄捕,周末想給孩子燉個腌篤鮮嘗嘗。但是沒有適合的鍋导帝,而我又不想出去買守谓。所以就問鄰居借了一個鍋來用,這樣既達(dá)到了目的舟扎,又節(jié)省了開支分飞,一舉兩得悴务。
改變執(zhí)行上下文也是一樣的睹限,A 對象有一個方法,而 B 對象因?yàn)槟撤N原因讯檐,也需要用到同樣的方法羡疗,那么這時(shí)候我們是單獨(dú)為 B 對象擴(kuò)展一個方法呢,還是借用一下 A 對象的方法呢别洪?當(dāng)然是借用 A 對象的啦叨恨,既完成了需求,又減少了內(nèi)存的占用挖垛。

另外痒钝,它們的寫法也很類似,調(diào)用call和apply的對象必須包含一個函數(shù)Function痢毒。

function fruit() {}

fruit.prototype  = {
    color: 'red',
    say: function() {
        console.log("my color is " + this.color);
    }
}
var apple = new fruit();
apple.say();  //my color is red

這個時(shí)候如果我們又想重新定義一個banana={color: "yellow"};,我們不想重新定義一個say方法送矩,那么這個時(shí)候我們就可以使用call和apply方法:

banner = {
    color: "yellow"
};
apple.say.call(banana);  //my color is yellow
apple.say.apply(banana); //my color is yellow

所以從上面可以看出,call和apply是為了動態(tài)改變this而出現(xiàn)的哪替,當(dāng)一個對象沒有某個方法的時(shí)候(本例子中banner對象沒有say方法)栋荸,但是其他對象有某個方法(本例子中apple中有say方法),我們就可以借助call和apply用其他對象的方法來實(shí)現(xiàn)凭舶。

apply和call的區(qū)別

apply和call的作用是完全一樣的晌块,只是傳遞的參數(shù)不一樣而已。例如有一個函數(shù):

var func = function(arg1,arg2){

}; 

就可以通過下面的方式調(diào)帅霜。

func.call(this,arg1,arg2);
func.apply(this,[arg1,arg2]);

其中this是你想指定的上下文匆背,它可以是任何JavaScript對象,call把參數(shù)按照順序傳遞進(jìn)去身冀,而apply是把參數(shù)放在數(shù)組里再傳進(jìn)去钝尸。

apply和call該用哪個呢蜂大?

  • 參數(shù)數(shù)據(jù)、順序確定就用call蝶怔,參數(shù)數(shù)量/殊勛不確定的話就用apply
  • 考慮可讀性:參數(shù)數(shù)量不多就用call奶浦,參數(shù)數(shù)量比較多的話,把參數(shù)整合成數(shù)組踢星,使用apply澳叉。
  • 參數(shù)集合已經(jīng)是一個數(shù)組的情況,用apply沐悦,比如上下文的獲取數(shù)組最大值/最小值成洗。
    參數(shù)數(shù)量/順序不確定的話就用apply,比如以下示例:
const obj = {
    age: 24,
    name: 'linKGe'
}
const obj2 = {
    age: 27
}
callObj(obj, handle);
callObj(obj2, handle);
//根據(jù)某些條件決定要傳遞參數(shù)的數(shù)量藏否,以及順序
function callObj(thisAge,fn) {
    let params = [];
    if(thisAge.name) {
        params.push(thisAge.name);
    }
    if(thisAge.age) {
        params.push(thisAge.age);
    }
    fn.apply(thisAge,params)// 數(shù)量和順序不確定瓶殃,不能使用call
}
function handle(...params) {
    console.log('params',params);
}

結(jié)果:
params [ 'linKGe', 24 ]
params [ 27 ]

call和apply的應(yīng)用場景

下面會分別列舉 call 和 apply 的一些使用場景。聲明:例子中沒有哪個場景是必須用 call 或者必須用 apply 的副签,只是個人習(xí)慣這么用而已遥椿。

1.call的使用場景

1.1 對象的繼承

function superClass () {
    this.a = 1;
    this.print = function () {
        console.log(this.a);
    }
}

function subClass () {
    superClass.call(this);
    this.print();
}

subClass(); //1

subClass 通過 call 方法,繼承了 superClass 的 print 方法和 a 變量淆储。此外冠场,subClass 還可以擴(kuò)展自己的其他方法。

1.2 借用方法

如果一個類數(shù)組想使用Array原型上的方法本砰,可以使用:

let domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

這樣碴裙,domNodes 就可以使用 Array 下的所有方法了。

2.apply應(yīng)用場景

apply獲取數(shù)組最大值和最小值
apply直接傳遞數(shù)組做要調(diào)用方法的參數(shù)点额,也省一步展開數(shù)組舔株,比如使用Math.max、Math.min 來獲取數(shù)組的最大值和最小值还棱。

const arr = [15, 6, 12, 13, 16];
const max = Math.max.apply(Math, arr); // 16
const min = Math.min.apply(Math, arr); // 6

面試題

定義一個 log 方法载慈,讓它可以代理 console.log 方法,常見的解決方法是:

function log(msg) {
  console.log(msg);
}
log(1);    //1
log(1,2);    //1

上面方法可以解決最基本的需求诱贿,但是當(dāng)傳入?yún)?shù)的個數(shù)是不確定的時(shí)候娃肿,上面的方法就失效了,這個時(shí)候就可以考慮使用 apply 或者 call珠十,注意這里傳入多少個參數(shù)是不確定的料扰,所以使用apply是最好的,方法如下:

function log(){
  console.log.apply(console, arguments);
};
log(1);    //1
log(1,2);    //1 2

接下來的要求是給每一個 log 消息添加一個"(app)"的前輟焙蹭,比如:

log("hello world"); //(app)hello world

這個時(shí)候想到arguments是個偽數(shù)組晒杈,通過Array.prototype.slice.call 可以轉(zhuǎn)成標(biāo)準(zhǔn)的數(shù)組,再使用數(shù)組的unshift方法孔厉。

function log() {
    let arg = Array.prototype.slice.call(arguments);
    arg.unshift('(app)');
    console.log.apply(console,arg);
}
log('hello world');  //(app) hello world

bind

在學(xué)習(xí)bind之前我們先來看一下這道題題目:

var altwrite = document.write;
altwrite("hello");

結(jié)果報(bào)錯:Uncaught TypeError: Illegal invocation ,altwrite()函數(shù)改變了this的指向global或者window對象拯钻,導(dǎo)致執(zhí)行的時(shí)候提示非法調(diào)用異常帖努,正確的方案就是使用bind()方法。

altwrite.bind(document)('hello');

當(dāng)然也可以使用call()方法粪般。

altwrite.call(document,'hello');

綁定函數(shù)

bind()最簡單的方法就是綁定函數(shù)拼余,使這個函數(shù)無論怎么調(diào)用都有同樣的this值,常見的錯誤就像上面的例子一樣亩歹,將方法從對象中拿出來匙监,然后調(diào)用,并且希望this指向原來的對象小作。如果不做特殊處理亭姥,一般會丟失原來的對象。使用bind()方法能夠很好的解決這個問題:

this.num = 9;
var mymodule = {
  num: 81,
  getNum: function() {
      console.log(this.num);
  }
};
mymodule.getNum();  //81   //this是mymodule

var getNum = mymodule.getNum;
getNum(); //9  //這時(shí)候this是window

var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 81

bind() 方法與apply和call相似顾稀,也是可以改變函數(shù)體內(nèi)this的指向达罗。
bind()方法會創(chuàng)建一個新的函數(shù),稱為綁定函數(shù)静秆,當(dāng)調(diào)用這個綁定函數(shù)時(shí)粮揉,綁定函數(shù)會以創(chuàng)建它時(shí)傳入bind()方法的第一個參數(shù)作為this,傳入bind()方法的第二個以及以后的參數(shù)加上綁定函數(shù)運(yùn)行時(shí)本身的參數(shù)按照順序作為原函數(shù)的參數(shù)來調(diào)用原函數(shù)诡宗。
直接來看看具體如何使用滔蝉,在常見的單體模式中,通常我們會使用 _this , that , self 等保存 this 塔沃,這樣我們可以在改變了上下文之后繼續(xù)引用到它。 像這樣:

var foo = {
    bar : 1,
    eventBind: function(){
        $('.someClass').on('click',function(event) {
            /* Act on the event */
            console.log(this.bar);      //1
        }.bind(this));
    }
}

在上述代碼里阳谍,bind() 創(chuàng)建了一個函數(shù)蛀柴,當(dāng)這個click事件綁定在被調(diào)用的時(shí)候,它的 this 關(guān)鍵詞會被設(shè)置成被傳入的值(這里指調(diào)用bind()時(shí)傳入的參數(shù))矫夯。因此鸽疾,這里我們傳入想要的上下文 this(其實(shí)就是 foo ),到 bind() 函數(shù)中训貌。然后制肮,當(dāng)回調(diào)函數(shù)被執(zhí)行的時(shí)候, this 便指向 foo 對象递沪。再來一個簡單的例子:

var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
bar(); // undefined
var func = bar.bind(foo);
func(); // 3

這里我們創(chuàng)建了一個新的函數(shù)func豺鼻,當(dāng)使用bind()創(chuàng)建一個綁定函數(shù)之后,它被執(zhí)行的時(shí)候款慨,它的this會被設(shè)置成foo儒飒,而不是像我們調(diào)用bar()時(shí)全局作用域。

偏函數(shù)(Partial Functions)

Partial Function也叫Partial Application檩奠,這里截取一段關(guān)于偏函數(shù)的定義:

Partial application can be described as taking a function that accepts some number of arguments, binding values to one or more of those arguments, and returning a new function that only accepts the remaining, un-bound arguments.
可以將部分應(yīng)用程序描述為采用一個接受一些參數(shù)的函數(shù)桩了,將值綁定到這些參數(shù)中的一個或多個附帽,然后返回一個僅接受其余未綁定參數(shù)的新函數(shù)。
這是一個很好的特性井誉,使用bind()我們設(shè)定函數(shù)的預(yù)定義參數(shù)蕉扮,然后調(diào)用的時(shí)候傳入其他參數(shù)即可:

function list() {
    return Array.prototype.slice.call(arguments);
  }
  
  var list1 = list(1, 2, 3); // [1, 2, 3]
  
  // 預(yù)定義參數(shù)37
  var leadingThirtysevenList = list.bind(undefined, 37);
  
  var list2 = leadingThirtysevenList(); // [37]
  var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

和setTimeout一起使用

function Bloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// 1秒后調(diào)用declare函數(shù)
Bloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 100);
};

Bloomer.prototype.declare = function() {
  console.log('我有 ' + this.petalCount + ' 朵花瓣!');
};

var bloo = new Bloomer();
bloo.bloom(); //我有 5 朵花瓣!

注意:對于事件處理函數(shù)和setInterval方法也可以使用上面的方法

綁定函數(shù)和構(gòu)造函數(shù)

綁定函數(shù)也適用于使用new操作符來構(gòu)造目標(biāo)函數(shù)的實(shí)例,當(dāng)使用綁定函數(shù)來構(gòu)造實(shí)例颗圣,注意:this會被忽略慢显,但是傳入的參數(shù)仍然可用。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function() { 
  console.log(this.x + ',' + this.y);
};

var p = new Point(1, 2);
p.toString(); // '1,2'


var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
// 實(shí)現(xiàn)中的例子不支持,
// 原生bind支持:
var YAxisPoint = Point.bind(null, 0/*x*/);

var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'

axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new Point(17, 42) instanceof YAxisPoint; // true

捷徑

bind()也可以為需要特定this值的函數(shù)創(chuàng)造捷徑欠啤。
例如要將一個類數(shù)組對象轉(zhuǎn)換為真正的數(shù)組:

var slice = Array.prototype.slice;
slice.call(arguments);

如果使用bind()的話荚藻,情況變得更加簡單。

var  unboundSlice = Array.prototype.slice;
var slice = Function.protorype.call.bind(unboundSlice);
//...
slice(arguments);

面試題--bind的應(yīng)用場景

1. 保存函數(shù)參數(shù):

首先來看一下這一道經(jīng)典的面試題:

for (var i = 1; i <= 5; i++) {
   setTimeout(function test() {
        console.log(i) // 依次輸出:6 6 6 6 6
    }, i * 1000);
}

造成這個現(xiàn)象的原因是等到setTimeout異步執(zhí)行時(shí)洁段,i已經(jīng)變成6了应狱。
那么如何使它輸出:1,2祠丝,3疾呻,4,5呢写半?

  • 可以使用閉包保存變量
 for (var i = 1; i <= 5; i++) {
    (function (i) {
        setTimeout(function () {
            console.log('閉包:', i); // 依次輸出:1 2 3 4 5
        }, i * 1000);
    }(i));
}
  • bind
for (var i = 1; i <= 5; i++) {
    // 緩存參數(shù)
    setTimeout(function (i) {
        console.log('bind', i) // 依次輸出:1 2 3 4 5
    }.bind(null, i), i * 1000);
}

實(shí)際山這里也是用了閉包岸蜗,我們知道bind會返回一個函數(shù),這個函數(shù)也是閉包叠蝇。
它保存了函數(shù)的this指向璃岳、初始參數(shù),每次i的變更都會被bind的閉包存起來悔捶,所以輸出1-5.
具體細(xì)節(jié)可從下面的手寫bind深入研究铃慷。

  • let
    用let聲明i也可以輸出1-5;因?yàn)閘et是塊級作用域蜕该,所以每次都會創(chuàng)建一個新的變量犁柜,所以setTimeout每次讀的值都是不同的。

參考:
https://segmentfault.com/a/1190000018270750
https://www.imooc.com/article/290456

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堂淡,一起剝皮案震驚了整個濱河市馋缅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绢淀,老刑警劉巖萤悴,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異更啄,居然都是意外死亡稚疹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來内狗,“玉大人怪嫌,你說我怎么就攤上這事×常” “怎么了岩灭?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赂鲤。 經(jīng)常有香客問我噪径,道長,這世上最難降的妖魔是什么数初? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任找爱,我火速辦了婚禮,結(jié)果婚禮上泡孩,老公的妹妹穿的比我還像新娘车摄。我一直安慰自己,他們只是感情好仑鸥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布吮播。 她就那樣靜靜地躺著,像睡著了一般眼俊。 火紅的嫁衣襯著肌膚如雪意狠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天疮胖,我揣著相機(jī)與錄音环戈,去河邊找鬼。 笑死获列,一個胖子當(dāng)著我的面吹牛谷市,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播击孩,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鹏漆!你這毒婦竟也來了巩梢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤艺玲,失蹤者是張志新(化名)和其女友劉穎括蝠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饭聚,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忌警,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秒梳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片法绵。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡箕速,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朋譬,到底是詐尸還是另有隱情盐茎,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布徙赢,位于F島的核電站字柠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狡赐。R本人自食惡果不足惜窑业,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枕屉。 院中可真熱鬧常柄,春花似錦、人聲如沸搀庶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哥倔。三九已至秸架,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間咆蒿,已是汗流浹背东抹。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沃测,地道東北人缭黔。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像蒂破,于是被迫代替她去往敵國和親馏谨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354