1. 程式人生 > >面試官:能解釋一下javascript中bind、apply和call這三個函式的用法嗎

面試官:能解釋一下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