
Function类型
函数实际上是对象。
每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常是使用函数声明语法定义的,例:
function sum(){
return 1+2
}
同理:
var sum = function(){
return 1+2
}
以上代码定义了变量sum并将其初始化为一个函数。function关键字后面没有函数名。这是因为在使用函数表达式定义函数时,没有逼要使用函数名--通过变量sum即可以引用函数。另外还要注意函数末尾有一个分号,就像声明其他变量时一样。
最后一种定义函数的方式时使用Function构造函数,Function构造函数可以接受任意数量的参数,但最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数,例:
var sum = new Fcuntion(“num1”,“num2”,“return num1 + num2”)
从技术角度来讲,这是一个函数表达式。但是不推荐使用这种方法定义函数,因为这种写法会导致解析两次代码(第一次是解析常规ECMAScript代码,第二次是解析传入构造函数中的字符串),从而影响性能。不过,这种语法对于理解“函数是对象,函数名是指针”的概念是非常直观的。
由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字。例:
function sum(sum1,sum2){
return sum1 + sum2
}
var anotherSum = sum;
anotherSum(10,10); // 20
sum = null;
anotherSum(10,10); // 20
将函数名想象为指针,也有助于理解为什么ECMAScript中没有函数重载的概念
function addSomeNumber(num){
return num + 100
}
function addSomeNumber(num){
return num + 200
}
var result = addSomeNumber(100); //300
显然,这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。以上函数实际上与下面的函数没有什么区别
var addSomeNumber = function (num){
return num + 100
}
var addSomeNumber = function (num){
return num + 200
}
var reslut = addSomeNumber(100); //300
通过观察重写之后的代码,很容易看清楚到底是怎么回事儿--在创建第二个函数时,实际上股改了引用第一个函数的变量addSomeNumber。
函数声明与函数表达式
实际上,解析其在执行环境中加载数据时,对声明函数和函数表达式并非一视同仁,解析器会率先读取函数声明,并使其在执行任何代码之前可用;至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。
示例一:
alert(sum(10,10));
function sum(num1,num2){
return num1 + num2
}
以上代码完全可以正常运行,因为在代码开始执行之前,解析其就已经通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中。对代码求值时,javaScript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码后面,JavaScript引擎也能把函数声明提升到顶部,
alert(sum(10,10));
var sum = function(num1,num2){
return num1 + num2
}
以上代码会在运行期间产生错误,原因在于函数位于一个初始化语句中,而不是一个函数声明。换句话说,在执行导函数所在的语句之前,变量sum中不会保存有对函数的引用;而且,由于第一行代码会导致”unexpected identifier“(意外标识符)错误,实际上也不会执行到下一行。
注:除了什么时候可以通过变量访问函数这一点却别之外,函数声明与函数表达式的语法其实是等价的。

函数内部属性:
在函数内部,有两个特殊的对象:arguments和this。其中arguments是一个类数组对象,包含着传入函数中的所有参数。虽然arguments的主要用途时保存函数参数,但这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。例:经典阶乘函数
function factorial(num){
if(num <= 1){
return -1
}else{
return num*factorial(num - 1)
}
}
定义阶乘函数一般都要用到递归算法;如上面的代码所示,在函数有名字,而且名字以后也不会变得情况下,这样定义没有问题。但问题是这个函数的执行与函数名factorial紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用arguments.callee。
fucntion factorial(num){
if(num <= 1){
return 1
}else{
return num*arguments.callee(num - 1)
}
}
这个重写后的factorial()函数的函数体内,没有在引用函数名factorial。这样,无论引用函数时使用的时什么名字,都可以保证正常完成递归调用。
函数内部的另一个特殊对象时this,其行为与java和c#中的this大致类似。话句话说,this引用的是函数据以执行的环境对象--或者也可以说时this值(当在网页的全局作用域中调用函数时,this对象引用的就是window)。
window.color = "red"
var o = {color : "blue"}
function sayColor(){
alert(this.color);
}
sayColor(); //"red"
o.sayColor = sayColor;
o.sayColor(); //blue

函数对象的属性:caller。这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null

严格模式下,arguments.callee或者arguments.calle方式会出错,为了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。
严格模式还有一个限制:不能为函数的caller属性赋值,否则会导致错误。
函数属性和方法:
ECMAScript中的函数时对象,因此函数也有属性和方法。每个函数都包含两个属性:length和prototype。其中,length属性表示函数希望接收的命名参数的个数如下所示:

在ECMAScript核心所定义的全部属性中,最耐人寻味的就要数prototype属性了。对于ECMAScript中的引用类型而言,prototype时保存它们所有实例方法的真正所在。换句话说,注入toString和valueOf等方法实际都保存在prototype名下,只不过时通过各自对象的实例访问罢了

prototype属性不可枚举,因此使用for-in无法发现

每个函数都包含两个非继承二来的方法:apply(),call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
apply():接收两个参数,一个时在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array的实例也可以是arguments对象。例:
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]);
}
alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20
在严格模式下,未指定环境对象而调用函数,则this值不会转型为window。除非明确吧函数添加到某个对象或者调用apply()或者call(),否则this值将是undefined

call()方法与applay()方法作用相同,它们的区别仅在于接收参数的方式不同。对于call而言,第一个参数是this值没有变化,变化的是其余参数都直接传递给哈桑农户,换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来。例:

事实上,传递参数并非apply()和call()真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。例:

ECMAScript还定义了一个方法:bind()。这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。例:

在这里,sayblindColor()调用bind()并传入对象o,创建了objSayColor()函数。objSayColor()韩素的this等于o,因此即使势在全局作用域中调用这个函数,也会看到“blue”。