在学习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 等等