1. 程式人生 > >web前端技術講解之call和apply的使用(很適合新手小白學習)

web前端技術講解之call和apply的使用(很適合新手小白學習)

就是想整理一篇關於call和apply的文件,不想編一些什麼最近學習的時候遇到問題研究之後想分享給大家之類的理由,就是想寫!就是想發!就是想!!!

以下是正文>>>>>>>>>

在使用call和apply之前,我們需要先做一些知識儲備:

一、window物件

window物件是js中的頂層物件,所有全域性變數和全域性函式都被繫結在了window物件身上,如何證明呢,我們可以先宣告一個全域性變數和函式,然後來觀察window物件。如程式碼1-1:

var a = 10;      //全域性變數

function abc(){    //全域性函式

  console.log("liyang");

}

console.log(window); //此時在window物件內已經出現了a屬性和abc函式

 

 

   

那我們在定義了全域性的變數a和全域性的函式abc之後,為什麼在使用他們時沒有加上window物件的字首呢,如程式碼1-2

window.a;     //10

window.abc();  //liyang

這是因為window物件作為一個全域性物件,一般情況下在使用的時候是可以省略的,也就是不寫,如程式碼1-3

a;         //10

abc();       //liyang

也是可以拿到a的值和執行abc函式。以上兩種書寫方式等價。

簡單總結,window可以說是js中最大的boss,所有在明面上的人員和交易,都是屬於window的,就算人員沒有特別說明,每筆交易也沒有單獨的署名,但是window物件永遠都是掌控一切。

二、this關鍵字,也就是所謂的執行上下文

說執行上下文可能有好多同學不明白,那麼我們就說this這個關鍵字的含義。

this關鍵字存在於函式中,表示是一個指向,或者說是一個系統"變數",值並不是固定的,但總是有跡可循。this的指向永遠是一個物件,我們看程式碼2-1:

var obj = {

   name:"liyang",

   show:function(){

     console.log(this);

   }

}

obj.name;     //liyang

obj.show();    //obj

從上面程式碼可以看出,this指向當前函式所在的obj物件,或者說this指向當前函式的呼叫物件,單隻一個案例看不出規律,那麼我們再來一個程式碼2-2:

function fn(){

  console.log(this);

}

fn();        //window

此處執行函式fn之後,打印出fn內部的this為window物件,結合window知識點,可知此時的fn是一個全域性函式,屬於window物件,執行fn時,相當於執行了window.fn(),fn在window物件那且被window物件呼叫,所以fn內部的this指向了window。

再看程式碼2-3:

var obox = document.getElementById("box");

obox.onclick = function(){

  console.log(this);

}

//單擊obox這個div時,控制檯打印出obox這個div標籤

因為obox是一個元素物件,給obox元素新增點選事件,相當於給obox元素物件新增一個onclick屬性,屬性值為function,函式內部有一個this,當點選obox觸發onclick,執行function時,打印出當前物件obox,依然符合this指向呼叫當前函式物件的原則。

   

綜上所述,this的指向為:誰掉用當前this所在的函式,this就指向誰。也就是說,當前呼叫函式的那個物件自身就是this,就是當前的執行上下文。

   

三、執行上下文(this)的改變

執行上下文(this)是可以被改變的,為什麼要改變執行上下文(this)呢,我們來模擬一種場景:

宿舍中,小A有每天洗頭的習慣,每次洗完之後,頭髮溼漉漉的不方便,於是就攢錢買了一個吹風機,洗完之後吹一吹,神清氣爽。小B洗頭沒有小A頻繁,偶爾洗一次,洗完之後也是溼漉漉的不方便,但是又因為自己洗的次數少,所以不想再單獨買一個吹風機,於是每次就借用小A的吹風機。

那麼此時,我們如果把小A和小B都理解成一個物件,吹風機就是小A方法,它的所有人就是小A,小A在使用吹風機的時候,小A就是吹風就的執行上下文(this)。小B偶爾會需要用到吹風機,因為使用頻次少,沒必要重新買一個造成資源浪費,所以每次都是借用小A的,那麼小B在使用吹風機的時候,吹風機被小B呼叫,此時小B就是吹風機的執行上下文(this)。此時吹風機的執行上下文(this)就被修改了。

(此情景僅為模擬,不適用於現實生活,現實生活中不推薦小B的做法)

在程式碼中,當一個物件A具有一個方法fn,另一個物件B沒有方法,但是需要用到同樣功能的fn方法時,可以通過改變A物件中函式fn的執行上下文(this)來實現呼叫,達到節約程式碼空間,不產生冗餘函式的目的。如程式碼3-1(字面量建立物件寫法):

var A = {

  name:"AAA",

  fn:function(skill){

     this.skill = skill;

     console.log("my name is " + this.name + ", my skills are " + this.skill);

   }

}

var B = {

   name:"BBB"

}

A.fn("sing");      //my name is AAA, my skills are sing

B.fn("dance");     //Uncaught TypeError: B.fn is not a function;

程式碼3-2(建構函式建立物件寫法):

function ProA(name, skill){

   this.name = name;

   this.skill = skill;

  this.fn = function(){

     console.log("my name is " + this.name + ", my skills are " + this.skill);

   }

}

function ProB(name, skill){

   

}

var A = new ProA("AAA","sing");

A.fn();     //AAA

var B = new ProB("BBB","dance");

B.fn();     //Uncaught TypeError: B.fn is not a function;

那麼當我們確定好需求之後,接下來的操作就簡單了,只要能找到一種方法,能夠將物件A中函式fn的上下文修改成B物件,就可以解決這些問題。

此時,就可以使用call和apply這兩個函式的方法,接下來我們只需要如何使用call和apply即可。

四、call和apply的使用

以上可得知call和apply這兩個方法的功能是:用來修改函式的執行上下文(this)。

call和apply其實都是函式的方法,我們知道方法是物件中的函式,那麼函式怎麼還可以有函式呢,我們可以結合js中萬物皆物件這句話,其實function在js中也是一個物件(可結合物件的原型來理解了,此處暫不做深究,瞭解原型請參考下篇文件),所以函式也有方法。

那麼這兩個方法如何使用呢,我們先來看完整語法:

call(thisObj,arg1,arg2,arg3,……)

call方法接收一個或一個以上的引數,當接收一個引數時,第一個引數表示要改變的原函式的執行上下文(this);接收多個引數時,第二個引數及後面所有引數用來替換原函式的引數。

將程式碼3-1使用call方法改成如下程式碼4-1方式,即可讓物件B具有物件A的fn方法,程式碼4-1:

var A = {

  name:"AAA",

  fn:function(skill){

     this.skill = skill;

     console.log("my name is " + this.name + ", my skills are " + this.skill);

   }

}

var B = {

   name:"BBB"

}

A.fn("sing");            //my name is AAA, my skills are sing

//此處改動產生的效果為:在執行A物件的函式fn時,通過call將函式fn的執行上下文(this)暫時修改為物件B,此時fn中的this指向物件B,同時修改原函式fn的引數為"dance",call方法自動執行改變之後的原函式

A.fn.call(B,"dance");      //my name is BBB, my skills are dance

將程式碼3-2使用call方法改成如下程式碼4-2方式,建構函式方式建立物件寫法,程式碼4-2

function ProA(name, skill){

   this.name = name;

   this.skill = skill;

  this.fn = function(){

     console.log("my name is " + this.name + ", my skills are " + this.skill);

   }

}

function ProB(name, skill){

  //此處改動產生的效果為:在ProB內,通過apply,執行,並改動ProA中的執行上下文(this),及修改ProA的引數為ProB所接收的引數

  //那麼在new呼叫ProB時,相當於呼叫了被修改了執行上下文和引數之後的ProA

  ProA.call(this, name, skill)

}

var A = new ProA("AAA","sing");

A.fn();     //AAA

var B = new ProB("BBB","dance");

B.fn();     //Uncaught TypeError: B.fn is not a function;

apply(thisObj,argArr)

apply方法接受一個或兩個引數,當接收一個引數時,第一個引數表示要改變的原函式的執行上下文(this);接收兩個引數時,第二個引數必須是陣列(或偽陣列),用於替換原函式中arguments儲存的引數,

將程式碼3-1使用apply方法改成如下程式碼4-3方式,即可讓物件B具有物件A的fn方法,程式碼4-3:

var A = {

  name:"AAA",

  fn:function(skill){

     this.skill = skill;

     console.log("my name is " + this.name + ", my skills are " + this.skill);

   }

}

var B = {

   name:"BBB"

}

A.fn("sing");             //my name is AAA, my skills are sing

//此處改動產生的效果為:在執行A物件的函式fn時,通過apply將函式fn的執行上下文(this)暫時修改為物件B,此時fn中的this指向物件B,同時修改原函式fn的引數為"dance"(注意"dance"引數必須是陣列的形式),call方法自動執行改變之後的原函式

A.fn.apply(B,["dance"]);      //my name is BBB, my skills are dance

將程式碼3-2使用call方法改成如下程式碼4-2方式,建構函式方式建立物件寫法

function ProA(name, skill){

   this.name = name;

   this.skill = skill;

  this.fn = function(){

     console.log("my name is " + this.name + ", my skills are " + this.skill);

   }

}

function ProB(name, skill){

  //此處改動產生的效果為:在ProB內,通過apply,執行,並改動ProA中的執行上下文(this),及修改ProA的引數為ProB所接收的引數(注意:此時的引數必須是一個數組的格式)

  //那麼在new呼叫ProB時,相當於呼叫了被修改了執行上下文和引數之後的ProA

  ProA.apply(this, [name, skill])

  //引數也可以寫成arguments的形式,arguments屬於偽陣列,但是也可以被apply所接收處理,如:

  //ProA.apply(this, arguments)

}

var A = new ProA("AAA","sing");

A.fn();     //AAA

var B = new ProB("BBB","dance");

B.fn();     //Uncaught TypeError: B.fn is not a function;

以上就是call和apply的使用,在我們明確需求的情況下,只需要掌握call或apply固定語法,就可以自由的轉換某個物件中函式的執行上下文(this)了。

同時,在OOP中,通過call和apply改變執行上下文(this),實現原本沒有某個方法的物件,具有這個方法,這個過程也叫繼承。

   

以上,如有描述不詳,或文中有誤,歡迎留言修改。

後期再出一篇《call和apply的擴充套件使用》,通過修改函式call和apply修改當前函式的執行上下文(this),我們可以讓一些原本不具有某個功能的物件,具有某個功能,從而更方便的進行程式設計。