詳解callee和caller, apply()和call()的用法 — 第5.5.4節

一:函數內部的對象:arguments和this
  我們都知道js中arguments對象主要是保存函數的參數(如果不知道arguments,在有參數的函數中輸出一下就知道了),但是這個對象還有一個屬性爲callee,它是一個指針,指向擁有這個arguments對象的函數。例如階乘函數:

    function factorial(num){
        if(num <= 1){
            return 1;
        }else{
            return num * factorial(num-1);
        }
    }
    console.log(factorial(5));//120

由於函數的執行和函數名factorial緊緊耦合在了一起,爲了消除這種緊密耦合的現象,可以使用arguments.callee:

    function factorial2(num){
        if(num <= 1){
            return 1;
        }else{
            return num * arguments.callee(num-1);
        }
    }
    console.log(factorial2(5)); //120

兩者的區別如下:

    var trueFactorial = factorial2;
    factorial = function () {
        return 0;
    }
    console.log(trueFactorial(6)); //720
    console.log(factorial(6));      //0

  上面的trueFactorial實際上是在另一個位置上保存了一個函數的指針。然後又將一個簡單的函數賦值給factorial變量。如果不用arguments.callee,那麼,調用trueFactorial(6)的時候就返回0.但是上面的例子解除了代碼和函數的耦合狀態,因此trueFactorial仍然可以正常的計算階乘。
二:this的意義?
  this引用的是函數執行的環境對象(當我們在網頁的全局作用域中調用函數的時候,this對象就是window對象)。也就是說,在全局作用域中的所有變量和函數都是window對象的屬性和方法,例如:

    var color = 'green';  //相當於window.color = 'green'
    var obj1 = {color:"blue"};
    function saycolor(){
        console.log(this.color);
    }
    saycolor();         //green,這裏的this代表window
    window.saycolor()   //green
    obj1.saycolor = saycolor;
    obj1.saycolor();    //blue

三:caller屬性是什麼?
  caller屬性保存着調用當前函數的函數的引用。如果是在全局作用域中調用當前的函數,它的值就是null。例子如下:

    function a(){
        console.log(a.caller); //返回正在調用俺的函數
    }
    function b(){
        a();
    }
    a();    //null
    b();    //function b(){a();}

但是爲了實現更鬆散的耦合,也可以通過arguments.callee.caller來達到相同的目的:

    function a(){
        console.log(arguments.callee.caller); //返回正在調用俺的函數b
    }
    function b(){
        a();
    }
    a();    //null
    b();    //function b(){a();}

不過arguments.callee在嚴格模式下會導致錯誤。
四:call()和apply()的應用
  每個函數都包含兩個屬性:length和prototype。其中length表示函數希望接受地參數的個數,例如:

    function a(){
        console.log(000);
    }
    function b(arg1){
        console.log(111);
    }
    function c(arg1,arg2){
        console.log(222)
    }
    console.log(a.length); //0
    console.log(b.length); //1
    console.log(c.length); //2

  prototype屬性是非常重要的,它指向一個對象,包含了所有它生成實例下的所有屬性和方法,再次不再贅述,我的prototype博文中另有詳細講解。
每個函數都包含兩個非繼承而來的方法:apply()和call()。這兩個方法的用途都是在特定的作用域中調用函數,其實就是所在函數體內this對象的值。
1.apply()接受兩個參數:一個是在其中運行函數的作用域,另一個是參數數組。其中:第二個參數可以是array的實例,也可以是arguments對象。實例如下:

    function fun(arg1,arg2){
        return arg1 + arg2;
    }
    function callfun1(arg1,arg2){
        return fun.apply(this,arguments); //傳入arguments對象
    }
    function callfun2(arg1,arg2){
        return fun.apply(this,[arg1,arg2])//傳入參數數組
    }
    console.log(callfun1(3,7)); //10
    console.log(callfun2(3,2)); //5

2.call()方法和apply()方法的作用相同,曲別在於接收參數的方式不同。用call方法傳遞參數的時候必須直接傳遞,也就是傳遞給函數的參數必須一一列舉出來,例子如下:

    function fun(arg1,arg2){
        return arg1 + arg2;
    }
    function callfun3(arg1,arg2){
        return fun.call(this,arg1,arg2);
    }
    console.log(callfun3(2,6)); //8

但是傳遞參數並非apply()和call()用的最多的地方,他們真正強大的地方時能夠擴充函數賴以運行的作用域。例子如下:

    var age = 18;
    var person = {age:17};
    function say(){
        console.log(this.age);
    }
    say();
    say.call(this);  //18
    say.call(window) //18
    say.call(person) //17,此時運行的作用域爲person,相當於把person的作用域傳給了say函數裏面

3.bind()用法。bind()方法會創建一個函數的實例,其this值會被綁定到傳給bind()函數的值。例如:

    var age = 18;
    var person = {age:17};
    function say(){
        console.log(this.age);
    }
    var psay = say.bind(person);
    console.log(psay) //function say(){console.log(this.age);}
    psay();     //17,相當於把person作用域傳給了say()構造函數裏面,它和apply和call的區別就是,bind必須還要創建一個函數實例,也就是bind不能直接執行傳參後的函數。

上面代碼中person對象的作用域傳到了bind()新創建的psay()函數執行環境裏面,所以執行完輸出this.age=17.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章