在學習javascript函數的時候,有幾個常常很容易混淆的方法,call,apply,bind,caller,callee,這些方法的使用,這些也能夠說是會頻繁使用的一些方法,在此經過查閱相關書籍和資料,整理了一篇博客,本文將詳細介紹call,apply,bind,caller,callee這些方法的用法,若是有講解錯誤的地方,還請你們海涵。javascript
在學習call,apply,bind,caller,callee這些方法以前,咱們須要先了解函數是什麼東西,畢竟這些方法都是圍繞函數進行展開的。html
Function 構造函數 建立一個新的Function對象,在 JavaScript 中, 每一個函數實際上都是一個Function對象,使用Function構造器生成的Function對象是在函數建立時解析的。這比你使用函數聲明或者函數表達式(function)並在你的代碼中調用更爲低效,由於使用後者建立的函數是跟其餘代碼一塊兒解析的java
函數的定義有兩種方法,第一種是函數聲明,第二種是函數表達式編程
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引用的是函數執行的環境對象,或者也能夠說是this的值(當在網頁的全局做用域中調用函數時,this對象的引用就是window)
<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>
<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()的相關用法,一塊兒來看看吧!
當函數被調用時,它的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>
這樣一來咱們就大大的下降了耦合度,不管函數名是什麼,怎麼執行都不會有影響
這個屬性中保存着調用當前函數的函數的引用,若是是在全局做用域中調用當前函數,它的值爲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
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()方法時,傳遞給函數的參數必須每一個列舉出來
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()必須明確地傳入每個參數
Function.apply(obj,args)方法能接收兩個參數
obj:這個對象將代替Function類裏this對象
args:這個是數組,它將做爲參數傳給Function(args-->arguments)
注意
<!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示例裏面傳遞了參數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()來擴充做用域的最大好處,就是對象與方法不須要任何耦合關係
這個方法會建立一個函數的實例,其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
在聲明函數時指定的命名參數的個數
<!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在實際應用中有很是之多的妙處,在這裏我就補充兩點,一種是array種push的短板,二是使用Math.Max(),Math.min()求最大值,最小值等等。
咱們知道數組的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將數組裝換爲參數列表的集合
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 的指向對象,第二個參數差異就不同了,總結以下
固然,三者的參數不限定是 string 類型,容許是各類類型,包括函數 、 object 等等