一、函数是对象
js中的函数都是Function的实例,有自己的属性和方法。因此函数名也只一个指向函数对象的指针,并不是绑定的。
二、函数声明与函数表达
Js中声明都会被提到代码的最前面,而赋值则会执行到所在代码行才会进行。
console.log(sum(1,2));//3 function sum(num1,num2){ return num1+num2; } console.log(anothersum(1,2));//unexpected identifier var anothersum=function(num1,num2){ return num1+num2; }
所以在sum声明前调用sum函数不会出错,而在anothersum前调用anothersum就会出错。
同样变量的声明与赋值也有类似的规则。不过js中的变量没有作用域这个概念。即在一个函数内for语句中声明的变量在整个函数内都可以访问得到。
三、作为值和参数的函数
因为ECMAScript中的函数名本身就是变量,所以函数也可以当作值来使用。
做为参数传递:
1 var arr=[1,2,3,11,55,6,13]; 2 function compare(num1,num2){ 3 return num1-num2; 4 } 5 arr.sort(compare); 6 console.log(arr);
同样的道理,js中的函数也可以做为值返回。如:
1 function compare(proName){ 2 return function(object1,object2){ 3 var val1=object1[proName]; 4 var val2=object2[proName]; 5 if(val1<val2){ 6 return -1; 7 }else if(val1>val2){ 8 return 1; 9 }else{ 10 return 0; 11 } 12 }; 13 } 14 var data=[{name:'Zhang',age:'28'},{name:'Nicholas',age:'29'}]; 15 data.sort('name'); 16 console.log(data[0].name);//Nicholas 17 data.sort('age'); 18 console.log(data[0].age);//Zhang
另外js中函数的参数传递实际上值的传递,对于对象并不是引用传递,传递的只是一个地址。
四、函数内部的属性
在Js的函数中,有两个特殊的属性arguments与this。其中arguments是一个类数组对象,包含所有实际传入的参数,但这个对象一个callee的发生,始终指向拥有这个arguments的函数。而this由运行时的环境决定是window对象,还是某一个对象的值。
arguments.callee的用法(经典阶乘函数):
1 function factorial(num){ 2 if(num<=1){ 3 return 1; 4 }else{ 5 return num*arguments.callee(num-1); 6 } 7 } 8 console.log(factorial(4));//24
如果不用arguments.callee指向拥有这个arguments的函数,而使用函数名,会有什么情况出现呢?当你修改了函数名的指向时,就会出现错误。
1 function factorial(num){ 2 if(num<=1){ 3 return 1; 4 }else{ 5 return num*factorial(num-1); 6 } 7 } 8 console.log(factorial(4));//24 9 factorialNew=factorial; 10 factorial=function(num){ 11 return 0; 12 } 13 console.log(factorial(4));//0 14 console.log(factorialNew(4));//0
函数内部的this:this的值引用的是执行环境对象。
1 var obj={ 2 color:'red' 3 }; 4 var color='blue'; 5 function showColor(){ 6 console.log(this.color); 7 } 8 showColor();//blue 9 showColor.call(obj);//red
因为第一次调用showColor()是在全局环境中执行,所以this值为window,而第二次调用call把this变为obj这个对象。
五、函数的属性和方法
因为js中的函数是对象,所以函数也有属性和方法。
函数的length属性,代表函数实际希望传入的参数的个数。
1 function sayName(name){ 2 var name=name||'nothing'; 3 console.log(name,arguments.length); 4 } 5 sayName('k');//k 1 6 sayName();//nothing 0 7 console.log(sayName.length);//1
可以使用argumets.length==arguments.callee.length是否相等来判断期望传递的参数的个数与实际传递的参数是否相等。
caller属性:保存着调用当前函数的函数的引用。
1 function outer(){ 2 inner(); 3 } 4 function inner(){ 5 console.log(arguments.callee.caller); 6 } 7 outer();//function outer(){inner();}
prototype属性:
对于js中引用类型来说,prototype是保存实例方法的真正所在。也就是说valueOf()和toString()等方法实际上都存在于prototype名下。同一个类的所有实例共享prototype中的属性。prototype与js实例继续,创建类有关。详见下一篇博文。
apply()和call()。这两个方法的用途是在特定的作用域下调用函数,实际上等于设置函数体内this的值。
apply()方法接收两个参数,一个是运行函数的作用域,另一个是数组,可以是Array实例,也可以是arguments。
1 function sum(num1,num2){ 2 return num1+num2; 3 } 4 function callsum(num1,num2){ 5 console.log(sum.apply(this,arguments));//3 6 console.log(sum.apply(this,[num1,num2]));//3 7 } 8 callsum(1,2);
call()与apply()类似,第一个参数是运行函数的作用域,其余参数直接传递就行。
1 function sum(num1,num2){ 2 return num1+num2; 3 } 4 function callsum(num1,num2){ 5 console.log(sum.call(this,num1,num2));//3 6 } 7 callsum(1,2);
如果只有第一个参数,call()与apply()没有区别。
使用call()和apply()方法扩充作用域的最大好处是对象不需要与以一奉百任何的耦合关系。
在ECMAScript5中也可以使用bind()方法。