面試官:能解釋一下javascript中bind、apply和call這三個函式的用法嗎
一.前言
不知道大家還記不記得前一篇文章:《面試官:能解釋一下javascript中的this嗎》
那今天這篇文章雖然是介紹javascript中bind、apply和call函式,但是多少也和this有點關聯。
假如在前面那場面試末尾,面試官不依不饒繼續問你javascript中的this,那看完本篇文章後一定還會有收穫。
(本篇文章不會站在this的角度去回答問題,而是重於解釋bind、apply和call這三個函式的用法和使用場景)
二.正戲開始
面試官:能解釋一下javascript中bind、apply和call這三個函式的用法嗎?
我:(這三個函式我也只是瞭解它們的用法,僅此而已)
我:這三個函式都是用於改變函式執行時內部this的指向的,只是每個函式的用法不一樣。
面試官:那你分別說一下具體都怎麼用吧。
我:(接著我邊回憶之前做過的小練習邊回答面試官)
(以下描述和回答均基於瀏覽環境)
首先是一個很簡單的示例
1 var objMM = { 2 name: 'MM', 3 age: 18, 4 getPresonInfo: function(addr){ 5 console.log(this.name + "年齡" + this.age + " 地址: " + addr); 6 } 7 }; 8 9 var objZZ = { 10 name: 'ZZ', 11 age: 28, 12 getPresonInfo: function(addr){ 13 console.log(this.name + "年齡" + this.age + " 地址: " + addr); 14 } 15 } 16 17 18 objMM.getPresonInfo('上海'); 19 20 objZZ.getPresonInfo('深圳');
18行和20行的列印資訊分別為:
bind方法
首先是bind方法,它的基本語法為 targetFunction.bind(thisArg,arg1,arg2,...)。
第一個引數thisArg會作為目標函式targetFunction執行時的this值傳遞給目標函式。
後面的引數列表arg1,arg2,... 是傳遞給目標函式的引數。
bind方法的返回值是一個目標函式的一個拷貝。
這個拷貝出來的函式執行時this指向的就是呼叫bind傳遞的thisArg引數。
並且拷貝函式還擁有呼叫bind時傳遞的arg1,arg2,...多個引數。
感覺這段描述把我自己都說暈了。
所以如果覺得語言描述不清楚,就寫一個簡單的用法示例:
var copyF = objMM.getPresonInfo.bind(objZZ,'遠方');
copyF();
這個示例是要在前面第一個示例的基礎上執行的。
在結合前面那段晦澀難懂的文字描述,可以這樣理解這兩行程式碼:
copyF為objMM.getPresonInfo函式的一個拷貝。
copyF執行時內部的this指向objZZ;
copy函式擁有一個引數'遠方'
這樣呼叫copy函式的結果就很顯而易見了。
apply方法
apply方法的基本語法為 targetFunction.apply(thisArg,[arg1,arg2])。
第一個引數的作用同bind方法。
第二個引數的作用也是和bind方法相同,只是將引數列表變為陣列的形式進行傳遞。
apply方法的返回值和bind方法就完全不同了,它會直接呼叫並執行目標函式。
那話不多說,在寫一個示例
objMM.getPresonInfo.bind(objZZ,['你管我在哪']);
列印結果:
call方法
call方法的基本語法為 targetFunction.call(thisArg,arg1,arg2,...)。
第一個引數的作用同bind方法,也同apply方法。
第二個引數的作用也是和bind、apply相同,只是形式同bind方法是引數列表形式。
call方法的返回值同apply方法,也是直接呼叫並執行目標函式。
objMM.getPresonInfo.call(objZZ,'我愛在哪在哪');
列印結果:
面試官:那這些函式你平時用過嗎,具體有什麼使用場景。
我:(這下慘了,平時還真沒咋用過,如實回答)平時在寫程式碼的時候,基本沒咋用過。
面試官:那好吧
我:(涼涼)......
三.自我反思
回家後深刻進行了自我反思:平時好像還真的沒有使用過這個三個函式呀,不過沒關係,現在學還來得及。
於是我開始各種蒐羅,然後依照個人理解,將其分為兩種使用場景。
(怎麼分類不重要,後面的示例才重要)
1.使用場景一:借用函式
借用函式大概意思就是借用現有方法去自己的需求。
在文章開始的第一個示例的基礎上,稍作一下修改
1 var objMM = { 2 name: 'MM', 3 age: 18, 4 getPresonInfo: function(addr){ 5 console.log(this.name + "年齡" + this.age + " 地址: " + addr); 6 } 7 }; 8 9 var objZZ = { 10 name: 'ZZ', 11 age: 28 12 }
這個程式碼中,objZZ已經沒有getPersonInfo這個方法了,假如我們想像objMM那樣去列印物件自身的資訊怎麼辦呢?
此時這三個函式就能派上用場了。
1 objMM.getPresonInfo.bind(objZZ,'我愛在哪在哪')(); // ZZ年齡28 地址: 我愛在哪在哪 2 objMM.getPresonInfo.apply(objZZ,['我愛在哪在哪']); // ZZ年齡28 地址: 我愛在哪在哪 3 objMM.getPresonInfo.call(objZZ,'我愛在哪在哪'); // ZZ年齡28 地址: 我愛在哪在哪
在就是javascript裡面有很多工具物件(我自己這樣叫),比如Math。
Math類有兩個函式max和min,一般情況下依照這兩個函式的語法只能這樣使用:
var maxNum = Math.max(23,197,88,35,109,11); console.log(maxNum); // 197 var minNum = Math.min(23,197,88,35,109,11); console.log(minNum); //11
假設現在程式碼裡面有一個數組變數要求出最大最小值,我們又不想自己去實現。
那我們就只能藉助Math提供的max和min方法,使用apply函式去實現這個功能。
var arr = [23,197,88,35,109,11]; var maxNum = Math.max.apply(Math,arr); console.log(maxNum); // 197 var minNum = Math.min.apply(Math,arr); console.log(minNum); //11
除了Math類之外,陣列也有很多api,比如最常見的forEach。
這個方法也只能是陣列型別的變數才能使用,那非陣列型別的變數要使用怎麼辦呢?
1 var divCollections = document.getElementsByTagName('html'); 2 3 Array.prototype.forEach.bind(divCollections,function(item){ 4 console.log(item); 5 })(); 6 Array.prototype.forEach.apply(divCollections,[function(item){ 7 console.log(item); 8 }]); 9 Array.prototype.forEach.call(divCollections,function(item){ 10 console.log(item); 11 });
借用函式的最後一個使用場景就是資料型別判斷。
我們知道javascript中使用typeof可以判斷一個變數的型別,但是僅限於基礎的型別。
比如:number、string、boolean、undefined型別。
其他型別的例如:array、object、null使用typeof 判斷型別列印均為“object”
所以我們可以藉助Object物件提供的一個函式,準確的知道一個數據的型別。
1 Object.prototype.toString.call(1); // "[Object Number]" 2 Object.prototype.toString.call('1'); // "[Object String]" 3 Object.prototype.toString.call(true); // "[Object Boolean]" 4 Object.prototype.toString.call(undefined); // "[Object Undefined]" 5 Object.prototype.toString.call([]); // "[Object Array]" 6 Object.prototype.toString.call({}); // "[Object Object]" 7 Object.prototype.toString.call(null); // "[Object Null]"
2.使用場景二:實現繼承
我們都知道,javascript中最簡單的繼承程式碼是通過將子類原型指向父類例項實現的。
1 function Father(name,age){ 2 this.name = name; 3 this.age = age; 4 this.sayInfo = function(){ 5 console.log(this.name + "年齡: "+ this.age); 6 } 7 } 8 9 10 function Son(name,age){ 11 this.name = name; 12 this.age = age; 13 } 14 15 //將子類原型指向父類例項 16 Son.prototype = new Father('我是你爸爸',); 17 18 var s = new Son('Son',1) 19 s.sayInfo(); //列印:Son年齡: 1
那我們可以藉助這三個函式實現javascript中的繼承。
(這裡只寫call方法的實現)
1 function Father(name,age){ 2 this.name = name; 3 this.age = age; 4 this.sayInfo = function(){ 5 console.log(this.name + "年齡: "+ this.age); 6 } 7 } 8 9 function Son(name,age){ 10 Father.call(this,name,age) 11 } 12 13 14 var s = new Son('Son',1) 15 s.sayInfo(); //列印:Son年齡: 1
可以看到使用call實現繼承時,只需要在子類Son中呼叫父類的建構函式,並且按照call函式的語法傳入所需引數即可。
後面直接使用Son的例項就能呼叫sayInfo函式。
這種方式說來有點意思,因為前面es5語法的繼承是子類原型指向父類例項,也就是通過原型鏈實現的。
而這種方式的原理又是什麼呢?
好奇心驅使,我分別列印了前面es5中原型鏈實現繼承後建立的例項s和使用call實現繼承後建立的例項s
從結果可以看到,使用call實現繼承,例項化後的物件s本身已經擁有了sayInfo方法。
所以說原型鏈式的繼承和call實現的繼承還是有本質的區別的。
那到底裡,關於bind、apply、call函式的使用場景就整理完了,下次遇到面試官問應該就不虛了。
(使用場景有可能不全,歡迎大家補充)
四.總結
本篇到此就基本結束了,結合前一篇關於this的文章,javascript中的this基本就沒啥大問題了。
當然實際的專案千變萬化,還是需要謹慎使用this。
介於本篇文章主要還是解釋javascript中bind、apply和call函式的用法,因此後續會在補一篇總結《使用原生Javascript實現bind、apply和call函式》。
最近作者新開通了一個微信公眾號。
微信公眾號會分享一些自己日常的東西,包括個人總結呀,吸貓日常呀,同時也會分享一些部落格上的前端技術文章。
歡迎大家掃碼關注~
&n