javascript學習總結之call()、apply()、bind() 的用法

前言

在學習javascript函數的時候,有幾個常常很容易混淆的方法,call,apply,bind,caller,callee,這些方法的使用,這些也能夠說是會頻繁使用的一些方法,在此經過查閱相關書籍和資料,整理了一篇博客,本文將詳細介紹call,apply,bind,caller,callee這些方法的用法,若是有講解錯誤的地方,還請你們海涵。javascript

Function函數

在學習call,apply,bind,caller,callee這些方法以前,咱們須要先了解函數是什麼東西,畢竟這些方法都是圍繞函數進行展開的。html

定義:

Function 構造函數 建立一個新的Function對象,在 JavaScript 中, 每一個函數實際上都是一個Function對象,使用Function構造器生成的Function對象是在函數建立時解析的。這比你使用函數聲明或者函數表達式(function)並在你的代碼中調用更爲低效,由於使用後者建立的函數是跟其餘代碼一塊兒解析的java

  • 全部被傳遞到構造函數中的參數,都將被視爲將被建立的函數的參數,而且是相同的標示符名稱和傳遞順序
  • javascript中的函數就是對象,對象就是「鍵/值」對的集合並擁有一個鏈接到原型對隱藏鏈接
  • 每一個函數都是Function類型的實例,並且都與其它引用類型同樣具備屬性和方法
  • 因爲函數是對象,所以函數名實際上也是一個指向函數對象的指針,不會與某個函數綁定

 函數的定義有兩種方法,第一種是函數聲明,第二種是函數表達式編程

函數聲明

            console.log(foo(10,10));//20
            function foo(a,b){
                return a+b
            }

函數表達式

            console.log(sum(10,10));//TypeError: sum is not a function
            var sum=function(a,b){
                return a+b;
            }

一樣是用來表達函數的方式,爲何函數聲明和函數表達式的差異那麼大了,且聽我一一道來數組

解析器在向執行函數環境中加載數據時,對函數聲明和函數表達式並不是一視同仁,解析器會率先讀取函數聲明,並使其在執行任何代碼以前可用(也就是咱們常說的變量提高),至於函數表達式,則必須等到解析器執行到它所在的代碼行,纔會真正的解析執行。app

對代碼求值時,javascript引擎在第一遍會聲明函數並將它們放到源代碼樹的頂部,因此,即便聲明函數的代碼在調用它的代碼後面,javascript引擎也能把函數聲明提高到頂部。編程語言

沒有重載(深刻理解)

不少編程語言中都有重載這個概念,好比java中的方法重載,構造函數重載等等,可是javascript這一門動態語言中恰恰沒有方法重載,咱們來看下示例函數

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>沒有重載(深刻理解)</title>
    </head>
    <body>
        <script type="text/javascript">
            function addSomeNumber(number){
                return number+100;
            }
            function addSomeNumber(num){
                return num+200;
            }
            var result=addSomeNumber(100);
            console.log(result);//300
        </script>
    </body>
</html>

顯然,這個例子中聲明瞭兩個同名函數,而結果則是後者的函數覆蓋了前面的函數,以上代碼實際上與下面的代碼沒有什麼區別學習

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>沒有重載(深刻理解)</title>
    </head>
    <body>
        <script type="text/javascript">
            var addSomeNumber=function(number){
                return number+100;
            }
            addSomeNumber=function(num){
                return num+200;
            }
            var result=addSomeNumber(100);
            console.log(result);//300
        </script>
    </body>
</html>

經過重寫以後的代碼,很容易看清楚究竟是怎麼回事,在建立第二個函數時,實際上覆蓋了引用第一個函數的變量addSomeNumber。this

做爲值的函數

由於ECMAScript中的函數名自己就是變量,因此函數也能夠做爲值來使用,也就是說不只能夠像傳遞參數同樣把一個函數傳遞給另外一個函數,並且還能夠將一個函數做爲另外一個函數的結果返回,好比常見的回調函數就是。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>做爲值的函數</title>
    </head>
    <body>
        <script type="text/javascript">
            function callSomeFunction(someFunction,someArgumet){
                return someFunction(someArgumet);
            }
            function add(num){
                return num+10
            }
            var result1=callSomeFunction(add,10);
            console.log(result1);//20
            function getGreeting(name){
                return 'hello'+name;
            }
            var result2=callSomeFunction(getGreeting,'一隻流浪的kk');
            console.log(result2);//hello 一隻流浪的kk
        </script>
    </body>
</html>

callSomeFunction函數接收兩個參數,第一個參數是一個函數,第二個參數是要傳遞給該函數的一個值,固然,能夠從一個函數中返回另外一個函數,咱們看下面的示例

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>做爲值的函數</title>
    </head>
    <body>
        <script type="text/javascript">
            function comPare(propertyName){
                return function(a,b){
                    var value1=a[propertyName];
                    var value2=b[propertyName];
                    if(value1>value2){
                        return -1
                    }else if(value1<value2){
                        return 1;
                    }else{
                        return 0;
                    }
                }
            }
            var data=[{name:'zhangsan',age:28},{name:'lisi',age:29}];
            data.sort(comPare('name'));
            console.log(data);//[{name:'lisi',age:29},{name:'zhangsan',age:28}];
            data.sort(comPare('age'));
            console.log(data);//[{name:'lisi',age:29},{name:'zhangsan',age:28}];
        </script>
    </body>
</html>

這個函數定義看起來有點複雜,但實際上無非就是在一個函數中嵌套了另外一個函數,並且內部函數前面加了一個return操做符,在內部函數接收到propertyName參數後,它會使用方括號表示法取得給定屬性的值,取得了想要的屬性值以後,定義比較函數就很是簡單了。

函數內部屬性arguments和this

在函數內部,有兩個特殊的對象:arguments和this,其中arguments它是一個類數組,包含着傳入函數中的全部參數,this引用的是函數執行的環境對象,或者也能夠說是this的值(當在網頁的全局做用域中調用函數時,this對象的引用就是window)

arguments

<script type="text/javascript">
            function counter(){
                var sum=0;
                for(var i=0;i<arguments.length;i++){
                    sum+=arguments[i];
                }
                return sum;
            }
            
            console.log(counter(199,991,1,2,3,4,5));//1205
            console.log(counter());//0
        </script>

這裏的arguments是一個隱式對象,不聲明也在函數中,內部函數能夠訪問外部函數的任意內容,可是不能直接訪問外部函數的arguments與this對象

        <script type="text/javascript">
            function f1(){
                console.log(arguments.length);//3
                 f2=function(){
                    console.log(arguments.length);//0
                   }
                return f2;
           }
            var f=f1(1,2,3);
            f();
        </script>

this

        <script type="text/javascript">
            window.color='red';
            var o={
                color:'blue'
            };
            function sayColor(){
                console.log(this.color);//red;
            }
            sayColor();
            o.sayColor=sayColor
            o.sayColor();//blue
        </script>

上面這個函數sayColor()是在全局做用域中定義的,它引用了this對象,因爲在調用函數以前,this的值並不肯定,所以this可能會在代碼執行過程當中,引用不一樣的對象,當在全局做用域中調用sayColor()時,this引用的是全局對象window,換句話說,對this.color求值會轉換成window.color求值,因而結果就返回red,而當把這個函數賦給對象o並調用o.sayColor()時,this的引用的是對象o,所以對this.color求值轉換成o.color求值,結果返回blue。

注意:函數的名字僅僅是一個包含指針的變量而已

構造函數

在javascript中對象構造函數能夠建立一個對象

<script type="text/javascript">
           /*構造函數*/
          //能夠簡單的認爲是一個類型的定義
           function Student(name,age){
                 this.name=name;
                 this.age=age;
                 this.show=function(){
                     console.log(this.name+","+this.age);
                 }
           }
           
           //經過new關鍵字調用構造函數,建立一個對象tom
           var rose=new Student("rose",18);
           var jack=new Student("jack",20);
           
           rose.show();//rose,18
           jack.show();//jack,20
        </script>

學會了函數的相關知識以後,咱們就開始學習,call(),apply(),caller,callee,bind()的相關用法,一塊兒來看看吧!

函數的屬性和方法

callee

當函數被調用時,它的arguments.callee對象就會指向自身,也就是一個對本身的引用,可能描述的不是太清楚,咱們經過案例進行講解,最多見的示例就是遞歸了,咱們看下示例

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script type="text/javascript">
            function factorial(num){
                if(num<=1){
                    return 1;
                }else{
                    return num*factorial(num-1)
                }
            }
            console.log(factorial(5));//120
        </script>
    </body>
</html>

這個示例中有一個很是明顯的弊端,就是這個函數的執行與函數名factorial僅僅耦合在了一塊兒,咱們編程講究的是高內聚,低耦合。爲了解決這個問題,咱們就會使用arguments.callee來代替函數名

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script type="text/javascript">
            function factorial(num){
                if(num<=1){
                    return 1;
                }else{
                    return num*arguments.callee(num-1)
                }
            }
            console.log(factorial(5));//120
        </script>
    </body>
</html>

這樣一來咱們就大大的下降了耦合度,不管函數名是什麼,怎麼執行都不會有影響

caller

這個屬性中保存着調用當前函數的函數的引用,若是是在全局做用域中調用當前函數,它的值爲null

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>caller的使用</title>
    </head>
    <body>
        <script type="text/javascript">
            function outer(){
                inner();
            }
            function inner(){
                console.log(inner.caller);
            }
            outer();
        </script>
    </body>
</html>

結果:

 以上代碼輸出outer()函數的源代碼,由於outer()調用了inner(),因此inner.caller就指向outer(),爲了實現更鬆散的耦合,也能夠經過arguments.callee.caller來訪問相同的信息

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>caller的使用</title>
    </head>
    <body>
        <script type="text/javascript">
            function outer(){
                inner();
            }
            function inner(){
                console.log(arguments.callee.caller);
            }
            outer();
        </script>
    </body>
</html>

輸出的結果和以前的同樣,在這裏咱們使用arguments.callee代替了inner

caller和this的區別

this是指調用方法的對象,而caller是指調用函數的函數

<script type="text/javascript">
        function add(n)
        {
            console.log("add被調用");
            if(n<=2){
                return 1;
            }
            return add.caller(n-1)+add.caller(n-2);
        }
        
        function calc(n){
            console.log("calc被調用");
            return add(n);
        }
        
        //1 1 2
        console.log(calc(3));
        </script>

每一個函數都包含兩個非繼承而來的方法,apply()和call(),這兩個方法的用途都是在特定的做用域中調用函數,實際上等於設置函數體內的this對象的值,

apply()方法接收兩個參數,一個是在其中運行函數的做用域,另外一個是參數數組,第二個參數能夠是array示例,也能夠是arguments對象

call()方法和apply()方法的做用相同,它們的區別在於接收參數的不一樣,對於call()而言,第一個參數是this的值沒有變化,變化的是其他參數都直接傳遞給函數,換句話說,在使用call()方法時,傳遞給函數的參數必須每一個列舉出來

call()

Function.call(obj,[param1[,param2[,…[,paramN]]]])
obj:這個對象將代替Function類裏this對象
params:這個是一個參數列表

調用一個對象的一個方法,以另外一個對象替換當前對象

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>call()方法的使用</title>
    </head>
    <body>
        <script type="text/javascript">
            function sum(num1,num2){
                return num1+num2;
            }
            function callSum(num1,num2){
                return sum.call(this,num1,num2);
            }
            console.log(callSum(10,10));//20
        </script>
    </body>
</html>

在使用call()方法的狀況下,callSum()必須明確地傳入每個參數

apply()

Function.apply(obj,args)方法能接收兩個參數
obj:這個對象將代替Function類裏this對象
args:這個是數組,它將做爲參數傳給Function(args-->arguments)

注意

  • 若是 argArray 不是一個有效的數組或者不是arguments對象,那麼將致使一個 TypeError
  • 若是沒有提供 argArray 和 thisObj 任何一個參數,那麼 Global 對象將被用做 thisObj, 而且沒法被傳遞任何參數
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>apply()方法的使用</title>
    </head>
    <body>
        <script type="text/javascript">
            //定義一我的類
            function Person(name,age){
                this.name=name;
                this.age=age;
            }
            //定義一個學生類
            function Student(name,age,grade){
                Person.apply(this,arguments);//此時的this指代Studnent
                this.grade=grade;
            }
            //建立一個學生
            var mark=new Student('zhagnsan',21,'七年級');
            console.log(mark.name,mark.age,mark.grade);//zhangsan,21,七年級
        </script>
    </body>
</html>

特別奇怪的現象,咱們明明沒有給name和age屬性賦值,爲何又存在這兩個屬性的值呢?

分析:Person.apply(this,arguments);

this:在建立對象在這個時候表明的是student

arguments:是一個數組,也就是[「zhangsan」,」21」,」一年級」];

也就是通俗一點講就是:用student去執行Person這個類裏面的內容,在Person這個類裏面存在this.name等之類的語句,這樣就將屬性建立到了student對象裏面。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>apply()方法的使用</title>
    </head>
    <body>
        <script type="text/javascript">
            function sum(num1,num2){
                return num1+num2;
            }
            function callSum1(num1,num2){
                return sum.apply(this,arguments);
            }
            function callSum2(num1,num2){
                return sum.apply(this,[num1,num2]);
            }
            console.log(callSum1(10,10));//20
            console.log(callSum2(10,10));//20
        </script>
    </body>
</html>

在上面這個示例中,callSum1()在執行sum()函數時傳入了this做爲this的值(由於是在全局做用域中調用的,因此傳入的值就是window對象)和arguments對象,二callSum2()一樣也調用了sum()函數,但它傳入的則是this和一個參數數組,這兩個函數都會正常執行並返回結果。

注意:在嚴格模式下,爲指定環境對象而調用函數,則this值不會轉型爲window,除非明確把函數添加到某個對象或者調用apply()或call(),不然this的值是undefined

什麼狀況下用apply,什麼狀況下用call

在給對象參數的狀況下,若是參數的形式是數組的時候,好比apply示例裏面傳遞了參數arguments,這個參數是數組類型,而且在調用Person的時候參數的列表是對應一致的(也就是Person和Student的參數列表前兩位是一致的) 就能夠採用 apply , 若是個人Person的參數列表是這樣的(age,name),而Student的參數列表是(name,age,grade),這樣就能夠用call來實現了,也就是直接指定參數列表對應值的位置(Person.call(this,age,name,grade));

事實上,傳遞參數並不是apply()和call()真正的用武之地,它們真正強大的地方是可以擴充函數賴以運行的做用域,看以下示例

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script type="text/javascript">
            window.color='red';
            var o={
                color:'blue'
            }
            function sayColor(color){
                console.log(this.color)
            }
            sayColor();//red
            sayColor.call(this);//red
            sayColor.call(window);//red
            sayColor.call(o);//blue
        </script>
    </body>
</html>

使用call()或者apply()來擴充做用域的最大好處,就是對象與方法不須要任何耦合關係

bind()

這個方法會建立一個函數的實例,其this的值會被綁定到傳給bind()函數的值

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>bind方法的使用</title>
    </head>
    <body>
        <script type="text/javascript">
            window.color='red';
            var o={
                color:'blue'
            }
            function sayColor(color){
                console.log(this.color)
            }
            var objSayColor=sayColor.bind(o);
            objSayColor();//blue
        </script>
    </body>
</html>

在這裏,sayColor()調用bind並傳入對象o,建立了objSayColor()函數,objSayColor()函數的this的值等於o,所以即便全局做用域中調用這個函數,也會看到blue

length

在聲明函數時指定的命名參數的個數

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Function</title>
    </head>
    <body>
        <h2>Function - length</h2>
        <script>
            function f1(n1,n2)
            {
                console.log("實際帶入的參數個數:"+arguments.length);//1
            }
            console.log("定義的命名參數個數:"+f1.length);
            f1(1);
            f1(1,2,3);
        </script>
    </body>
</html>

 apply()方法的妙處

apply在實際應用中有很是之多的妙處,在這裏我就補充兩點,一種是array種push的短板,二是使用Math.Max(),Math.min()求最大值,最小值等等。

(1)Array.prototype.push 能夠實現兩個數組合並

咱們知道數組的push方法沒有提供push一個數組,可是提供了push(param1,param,…paramN) 因此一樣也能夠經過apply來裝換一下這個數組,咱們看下示例

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>apply的應用一</title>
    </head>
    <body>
        <script type="text/javascript">
            var arr1=[1,2,3];
            var arr2=[4,5,6];
            Array.prototype.push.apply(arr1,arr2);
            console.log(arr1);//[1,2,3,4,5,6]
        </script>
    </body>
</html>

也能夠這樣理解,arr1調用了push方法,參數是經過apply將數組裝換爲參數列表的集合

(2)Math.maxh和Math.min求數組的最大值和最小值

Math.max(a,b)和Math.min(a,b)只能求兩個數中的最大值和最小值,可是咱們想要求數組中的最大值和最小值的時候卻沒法實現,而使用apply方法能夠巧妙的實現,以下示例

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>apply的應用二</title>
    </head>
    <body>
        <script type="text/javascript">
            var arr=[1,2,3,4,5];
            console.log(Math.max.apply(null,arr));//5
            console.log(Math.min.apply(null,arr));//1
        </script>
    </body>
</html>

咱們看到巧妙的使用apply能夠很是簡單的實現Math.max()和Math.min()求最大值和最小值

總結

call 、bind 、 apply 這三個函數的第一個參數都是 this 的指向對象,第二個參數差異就不同了,總結以下

  • call 的參數是直接放進去的,第二第三第 n 個參數全都用逗號分隔,直接放到後面 Function.call(this.Obj,arg0,arg1,...)
  • apply 的全部參數都必須放在一個數組裏面傳進去 Function.apply(thisObj,[arguments])
  • bind 除了返回是函數之外,它 的參數和 call 同樣

固然,三者的參數不限定是 string 類型,容許是各類類型,包括函數 、 object 等等

相關文章
相關標籤/搜索