javascript學習筆記--理解apply()、call()、bind() 以及caller、callee屬性

本文目錄如下:
- 前言
- apply()、all()函數、bind()函數
- caller屬性與callee屬性
- 總結


前言

在公司做一些全棧開發,實際上也不算,啥都會一點兒,其中就包括前端的javascript腳本語言。之前有過一些代碼經驗(代碼算不上精緻,用面嚮對象語言,寫面向過程的代碼邏輯),最近,突然想,無論什麼語言,多沉澱一下總是好的,於是有了這個博客專欄,希望用筆頭記下自己學習的點點滴滴,歡迎大家拍磚。

apply()函數與call()函數

  無論是小白還是老司機,相比對這兩個大名鼎鼎的函數不陌生。先來看看這兩個函數的基本描述:

  • 對於javascript腳本語言,所有的函數都包含這兩個函數,不需要任何的繼承
  • 他們的用途都是一樣的,都是爲了擴充函數的功能,有的說法也叫作用域,比較難以理解
  • 接收參數方面不同,apply()接收兩個參數,一個是函數運行的作用域(this),另一個是參數數組。
    call()方法第一個參數與apply()方法相同,但傳遞給函數的參數必須列舉出來。
      
      對於前面兩點,實際上還是比較好理解的,想必對於第三點,尤其是對於我這種小白來說,還是比較蒙逼的,什麼是函數運行作用域?下面舉個例子,然後看起運行的行爲,然後我們再來重新進行定義,讓第三點讀起來通俗易懂。
//聲明一個Person對象
function Person(name){
    this._name=name
    this.showName=function(){//打印自己名字的方法showName()
        return this._name;
    }
}
//聲明一個Chinese對象,無showName()
function Chines(name){
    this._name = name;
    }
}
//聲明一個American對象,showName()
function American(name){
    this._name = name;
    }
}
//從聲明來看,這三個函數沒有依賴關係;
//那麼現在Chinese和American都想有一個這樣的方法,有什麼解決思路呢?
/*
    1.繼承 已經超出本文的研究範圍,不做深入研究
    2.在這兩個對象中加上showName()方法,那如果是100個對象呢,是不是要寫100遍這個函數,這個簡直就是災難
    3.利用apply()和call方法
*/
//ok,討論完方案之後我們來改造下代碼,當然我們直接摒棄了方法2,因爲方法2太low了,我們來說下方法3
function Chinese(name){
    Persion.apply(this);
    this._name=name
}
//聲明一個American對象,showName()
function American(name){
    Persion.apply(this);
    this._name=name
}
//wait 這不就是第二種方法嗎,實際上還是要在每個對象上加代碼,不過我們的代碼看起來優雅了不少,下面來跑下測試結果看看
var chinese = new Chinese("chenglong");
var american = new American("john");

console.log(chinese.showName()); //輸出chenglong
console.log(american.showName()); //輸出john
//通過apply,兩對象就有了Person對象的方法了


//當然用call方法也是一樣的,不過call方法需要指定具體的參數,看起來像這個樣子
function Chinese(name){
    Person.call(this,name);
    this._name=name
}
//聲明一個American對象,showName()
function American(name){
    Person.call(this,name);
    this._name=name
}
var chinese = new Chinese("chenglong");
var american = new American("john");

console.log(chinese.showName()); //輸出chenglong
console.log(american.showName()); //輸出john

//你也可以不用繼承的方式 這就退化成了普通的調用了,這種方式不必改造任何的代碼
//聲明一個Chinese對象,無showName()
function Chinese(name){
    this._name = name;
}
//聲明一個American對象,showName()
function American(name){
    this._name = name;

}
var person = new Persion("person");
var chinese = new Chinese("chenglong");
var american = new American("john");
console.log(person.showName.apply(chinese)); //輸出chenglong
console.log(person.showName.call(chinese,"chenglong")); //輸出chenglong
console.log(person.showName.apply(american)); //輸出john
console.log(person.showName.call(american,"john")); //輸出john
//看起來這種情況下,apply更優雅,但是沒指定參數,總是發慌,用call限定了參數;而且多了個person對象,真讓人不爽,因此把apply和call運用到類中,一勞永逸

  現在,我們來總結一下,基本描述的第三點,在繼承中我們有一句這樣的代碼Persion.call(this,name)和 Persion.apply(this),當有了這個代碼之後,我們的Chineses從此就有了showName的方法,apply中文是啥意思?應用的意思,我們似乎可以這麼拆解:將Person的所有的方法(或者某個方法)應用在apply()/call()傳進來的第一個參數上,我們的函數對象(有一個對象A,有一些基礎的方法,比如打印名字什麼的,而有個對象B,不想重載這些方法,那這個時候就用apply()/call()來把A的方法擴展下吧,爲我所用)這就達到了,將Chinese對象的方法進行擴展的目的,不得不佩服javascript語言發明者的絕妙思維。
 

bind()函數

 實際上理解了call函數和apply函數之後,再來理解bind函數就沒那麼難了,bind函數從某種意義上來說,用法和上述兩個函數一模一樣。最大的區別是,bind函數具有懶加載,可以用着回調,該函數實際上返回的是一個更換了this指針的函數引用,你可以不立即執行它,將它保存在變量中,而apply和call就不行,你調用了就立即執行。舉個例子:
 

var obj = {
    x: 100,
};

var foo = {
    getX: function() {
        return this.x;
    }
}

console.log(foo.getX.bind(obj)());  //100
console.log(foo.getX.call(obj));    //100
console.log(foo.getX.apply(obj));   //100
//雖然結果都是一樣的,但是你可以發現bind函數在後面多了一對小括號,這就是區別

caller和callee屬性

  caller:調用者的意思,返回的是誰是這個函數的調用者的函數引用。舉個例子:
  

function callerDemo()
{
    if(arguments.caller){//調用者不是null,則返回調用者的函數引用
        console.log(callerDemo.caller.toString());
    }else
    {
        console.log("This function's caller is windows");
    }
}
function handleCaller(){
    callerDemo();
}
handleCaller();//輸出的是handleCaller函數的引用
callerDemo();//This function's caller is windows

callee:callee 屬性是 arguments 對象的一個成員,它表示對函數對象本身的引用。這個特性在遞歸函數中非常的有用。

function calleeDemo() {
    alert(arguments.callee);
}
callerDemo();//打印函數對象本身的引用

總結

最後,再總結一下:

  • apply 、 call 、bind 三者都是用來改變函數的this對象的指向的,第一個參數都是傳入的對象的this指針;
  • bind函數返回的是函數,具備回調性質,apply、call立即調用
  • caller 返回的是調用該函數的函數的引用;
  • calee是argument的一個屬性方法,返回的是這個函數的本身的引用
  • apply、call、bind的通用理解方式是:xxx對象的xx方法運用到xxx對象上(obj.method.apply,call,bind),使得目標對象的方法得到了擴展
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章