JavaScript中的Function类型
Function与函数
函数是一段JavaScript代码,它只定义一次,但可能被执行或调用多次
Function类型是JavaScript提供的引用类型之一,通过Function类型创建Function对象
在JavaScript中,函数也是以对象的形式存在的。每个函数都是一个Function对象
函数名,本质就是一个变量名,是指向某个Function对象的引用
function fn(){ console.log('前端'); } console.log(fn instanceof Function);//true
构造函数
在JavaScript中,函数除了可以通过函数定义语句或字面量表达式两种方式定义之外,还可以通过Function类型进行定义
var fn=new Function('num1','num2','var sum = num1+num2;return sum');
通过Function类型定义函数的效率远不如通过函数定义语句或字面量表达式两种方式定义
目前,定义函数具有三种方式,这三种方式之间存在一定差别
函数定义语句:函数名被声明提前,不存在效率问题
字面量表达式:函数体固定,无法动态执行,不存在效率问题
Function类型定义:函数体是字符串,可以动态执行,效率低
Function的apply()方法
Function的apply()方法用于调用一个函数,并且接收指定的this值,以及一个数组作为参数,其语法结构如下:
func.apply(thisArg,[argsArray])
- thisArg参数:可选项,在func函数运行时使用的this值
- argsArray参数:可选项,一个数组或者类数组对象,其中的数组元素将作为单独的参数传给func函数。也可以使用arguments对象作为该参数
- 返回值:调用该函数的返回结果
var numbers=[1,2,3,4,5]; //通过apply()方法获取数组中最大值和最小值 var max=Math.max.apply(null,numbers); var min=Math.min.apply(null,numbers);
Function的call()方法
Function的call()方法用于调用一个函数,并且接收指定的this值作为参数,以及参数列表。其语法结构如下:
func.call(thisArg,arg1,arg2,...)
- thisArg参数:在func函数运行时使用的this值
- arg1,arg2,...参数:指定的参数列表
- 返回值:调用该函数的返回结果
apply()与call()非常相似,不同之处在于提供参数的方式
//通过call()方法获取数组中最大值和最小值 var max=Math.max.call(null,5,6,2,3,7); var min=Math.min.call(null,5,6,2,3,7);
Function的bind()方法
Function的bind()方法用于创建一个新的函数(称为绑定函数),并且接收指定的this值作为参数,以及参数列表,其语法结构如下:
fun.bind(thisArg[,arg1[,arg2[,...]]])
- thisArg参数:当绑定函数被调用时,该参数会作为原函数运行时的this指向
- arg1,arg2,...参数:当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法
- 返回值:返回由指定的this值和初始化参数改造的原函数
Function的bind()方法示例如下:
this.x=9; var module={ x:81, getX:function(){return this.x;} }; module.getX();//返回81 var retrieveX=module.getX; retrieveX();//返回9,在这种情况下,'this'指向全局作用域 //创建一个新函数,将'this'绑定到module对象 var boundGetX=retrieveX.bind(module); boundGetX();//返回 81
没有重载
在其他开发语言中,函数具有一种特性,叫做重载。所谓重载,就是定义多个同名的函数,但每一个函数接收的参数的个数不同,程序会根据调用时传递的实参个数进行判断,具体调用的是哪个函数。如下示例:
function add(a,b){ return a+b; } function add(a,b,c){ return a+b+c; } add(1,2);//结果为3 add(1,2,3);//结果为6
但在JavaScript中,函数是没有重载现象的,也就是说,如果同时定义多个同名的函数,只有最后一个定义的函数是有效的。
function add(a,b){ return a+b; } function add(a,b,c){ return a+b+c; } add(1,2);//结果为NaN add(1,2,3);//结果为6
不过,JavaScript提供了arguments对象,该对象可以模拟函数重载的现象。arguments对象是函数内部的本地变量;arguments已经不再是函数的属性了。
arguments对象可以获取函数的所有参数,但arguments对象并不是一个数组,而是一个类数组对象(没有数组特有的方法)
arguments对象的属性如下所示:
callee:表示当前执行的函数
length:表示传递给当前函数的参数数量
利用arguments对象实现模拟函数的重载现象,如下述示例
function doAdd(){ if(arguments.length==1){ console.log(arguments[0]+5); }else if(arguments.length==2){ console.log(arguments[0]+arguments[1]); } } doAdd(10);//输出'15' doAdd(40,20);//输出'60'
递归
在一个函数的函数体内,如果想调用自身函数的话,有如下两种方式:
- 通过使用自身函数名实现
- 通过使用arguments对象的callee属性实现
调用自身的函数被称之为递归函数,在某种意义上说,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件以避免无限循环或者无限递归。
function loop(x){ if(x>=10){return;} loop(x+1); } loop(0);
上述代码是一个经典的递归函数。虽然这个函数表面看起来并没有什么问题,如果执行下述代码可能会导致出错。
var anotherLoop=loop; loop=null; anotherLoop(0);//出错
上述代码将函数loop()保存到另一个变量anotherLoop中,然后将loop设置为null值。当执行anotherLoop时,一定会执行函数loop(),而loop已经不再是一个函数,最终导致出错
要想解决上述递归函数的问题,可以使用arguments对象的callee属性替换具体的函数名
function loop(x){ if(x>=10){ return; } arguments.callee(x+1); }
匿名函数
JavaScript可以将函数作为数据使用,作为函数本体,它像普通的数据一样,不一定要有名字。默认名字的函数被称之为匿名函数。如下示例:
function(a){return a}
匿名函数的两种方法:
- 可以将匿名函数作为参数传递给其他函数。这样,接收方函数就能利用所传递的函数来完成某些事情
- 可以定义某个匿名函数来执行某些一次性任务
回调函数
当一个函数作为参数传递给另一个函数时,作为参数的函数被称之为回调函数
function add(a,b){ return a()+b(); } var one=function(){return 1;} var two=function(){return 2;} console.log(add(one,two));//output 3 //可以直接使用匿名函数来替代one()和two(),以作为目标函数的参数 console.log(add(function(){return 1;},function(){return 2;}));
上述代码中,函数one()和two()都作为函数add()的参数传递。所以函数one()和two()都是回调函数。当将函数A传递给函数B,并由B来执行A时,A就成了一个回调函数,如果A还是一个无名函数,就称之为匿名回调函数
回调函数优点如下:
- 它可以在不做命名的情况下传递函数(这意味着可以节省全局变量)
- 可以将一个函数调用操作委托给另一个函数(这意味着可以节省一些代码编写工作)
- 回调函数也有助于提升性能
自调函数
所谓自调函数就是在定义函数后自行调用。如下示例:
(function(){ console.log('javascript'); })();
上述代码的含义如下:
- 第一对括号的作用,放置的是一个匿名函数。
- 第二对括号的作用,是'立即调用'
自调函数只需将匿名函数的定义放进一对括号中,然后外面再跟一对括号即可
自调函数也可以在调用时接收参数,如下示例:
(function(name){ console.log('hello'+name+'!'); })('javascript');//hello javascript
上述代码的含义如下:
- 第一个括号中的匿名函数接受一个参数
- 第二个括号,在调用时,向匿名函数传递参数内容
作为值的函数
将一个函数作为另一个函数的结果进行返回,作为结果返回的函数称之为作为值得函数
function fn(f,args){ return f(args); } function add(num){//作为值的函数 return num+10; } var result=fn(add,10); console.log(result);//20
上述代码还可以编写成如下方式:
function fn(args){ return function add(){ return args+10; } }
上述两段代码的区别在于
var f=fn(10);//function add(){return 10+10} var result=f();//20
作用域链
很多开发语言中都具有块级作用域,但ECMAScript5版本中并没有跨级作用域,这经常会导致理解上的困惑。如下示例:
if(true){ var color='blue'; } console.log(color);//blue
上述代码在if语句中定义了变量color。但该变量的作用域是全局域,原因是ECMAScript5版本中没有块级作用域
虽然ECMAScript5版本没有块级作用域,但具有函数作用域。在某个函数内部定义的变量的作用域就是该函数作用域。
function fun(){ var v='this is function'; } console.log(v);//输出报错
上述代码在函数fun内部定义了变量v,该变量的作用域是fun函数作用域。所以在全局域访问该变量时会出错
每一段JavaScript(全局代码或函数)都有一个与之关联的作用域链。这个作用域链是一个对象列表或链表,这组对象定义了这段代码'作用域中'的变量。如下示例:
var a=1; //在全局域中只能访问变量a function f(){ //在f函数作用域中可以访问变量a和b var b=2; }
闭包
JavaScript允许函数嵌套,并且内部函数可以访问定义在外部函数中的所有变量和函数,以及外部函数能访问的所有变量和函数。但是,外部函数却不能够访问定义在内部函数中的变量和函数。
当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了
闭包就是该函数能使用函数外定义的变量
如下代码就是一个最简单形式的闭包结构:
var b; function f(){ var a='a'; b=function(){ return a+'b'; } return a; } //测试 console.log(f());//a console.log(b());//ab
闭包的特点与作用
闭包的特点:
- 局部变量:在函数中定义有共享意义(如:缓存、计数器等)的局部变量。(注意:定义成全局变量会对外造成污染)
- 内部函数:在函数(f)中声明有内嵌函数,内嵌函数(g)对函数(f)中的局部变量进行访问
- 外部使用:函数(f)向外返回此内嵌函数(g),外部可以通过此内嵌函数持有并访问声明在函数(f)中的局部变量,而此变量在外部是通过其他途径无法访问的
闭包的作用
- 提供可共享的局部变量
- 保护共享的局部变量。提供专门的读写变量的函数
- 避免全局污染
闭包的应用
利用闭包保护共享的局部变量,提供专门的读写变量的函数
var getValue,setValue; (function(){ var secret=0; getValue=function(){ return secret; }; setValue=function(v){ secret=v; } })();