var sum = function fn(){
var total = 0,
l = arguments.length;
for(; l; l--)
{
total += arguments[l-1];
}
console.info(typeof fn);
return total;
}
console.info(sum(1,2,3,4));//function,10
console.info(fn(1,2,3,4));//ReferenceError
上面是一个命名函数表达式在FireFox中的运行结果,在函数作用域内可以访问这个名称,但是在全局作用域中访问出现引用异常。不过命名函数表达式在IE9之前的IE浏览器中会被同时作为函数声明和函数表达式来解析,并且会创建两个对象,好在IE9已经修正。
除了全局作用域,还有一种函数作用域,在函数作用域中,参与到声明提升竞争的还有函数的参数。首先要明确的是,函数作用域在函数定义时不存在的,只有在函数实际调用才有函数作用域。
复制代码 代码如下:
// 参数与内部变量,参数优先
function fn(inner){
console.info(inner);// param
console.info(other);// undefined
var inner = 'inner';
var other = 'other';
console.info(inner);// inner
console.info(other);// other
}
fn('param');
// 参数与内部函数,内部函数优先
function gn(inner){
console.info(inner);// inner()函数
console.info(inner());// undefined
function inner(){
return other;
}
var other = 'other';
console.info(inner);// inner()函数
console.info(inner());// other
}
gn('param');
通过上面的输出结果,我们得出优先级:内部函数声明 > 函数参数 > 内部变量声明。
这里面的一个过程是:首先内部函数声明提升,并将函数名的类型设置为函数类型,然后解析函数参数,将传入的实际参数值赋给形式参数,最后再内部变量声明提升,只提升声明,不初始化,如果有重名,同优先级的后面覆盖前面的,不同优先级的不覆盖(已经解析了优先级高的,就不再解析优先级低的)。
说明一下,这只是我根据输出结果的推断,至于后台实现,也有可能步骤完全相反,并且每一步都覆盖前一步的结果,甚至是从中间开始,然后做一个优先级标志确定是否需要覆盖,当然,从效率上来看,应该是我推断的过程会更好。另外,全局作用域其实就是函数作用域的一个简化版,没有函数参数。
这里就不再举综合的例子了,建议将这篇文章和前面的基础语法那一篇一起阅读,可能效果会更好。关于优先级与覆盖,也引出下面要说的一个问题。
4、函数重载
函数是对象,函数名是指向函数对象的引用类型变量,这使得我们不可能像一般面向对象语言中那样实现重载:
复制代码 代码如下:
function fn(a){
return a;
}
function fn(a,b){
return a + b;
}
console.info(fn(1)); // NaN
console.info(fn(1,2));// 3
不要奇怪第8行为什么输出NaN,因为函数名只是一个变量而已,两次函数声明会依次解析,这个变量最终指向的函数就是第二个函数,而第8行只传入1个参数,在函数内部b就自动赋值为undefined,然后与1相加,结果就是NaN。换成函数表达式,也许就好理解多了,只是赋值了两次而已,自然后面的赋值会覆盖前面的:
复制代码 代码如下:
var fn = function (a){ return a; }
fn = function (a,b){ return a + b;}
那么,在ECMAScript中,怎么实现重载呢?回想一下简单数据类型包装对象(Boolean、Number、String),既可以作为构造函数创建对象,也可以作为转换函数转换数据类型,这是一个典型的重载。这个重载其实在前一篇文章中我们曾经讨论过:
(1)根据函数的作用来重载,这种方式的一般格式为:
复制代码 代码如下:
function fn(){
if(this instanceof fn)
{
// 功能1
}else
{
// 功能2
}
}
这种方式虽然可行,但是很明显作用也是有限的,比如就只能重载两次,并且只能重载包含构造函数的这种情形。当然,你可以结合apply()或者call()甚至ES5中新增的bind()来动态绑定函数内部的this值来扩展重载,但这已经有了根据函数内部属性重载的意思了。
(2)根据函数内部属性来重载
复制代码 代码如下:
function fn(){
var length = arguments.length;
if(0 == length)//将字面量放到左边是从Java中带过来的习惯,因为如果将比较操作符写成了赋值操作符(0=length)的话,编译器会提示我错误。如果你不习惯这种方式,请原谅我
{
return 0;
}else if(1 == length)
{
return +arguments[0];
}else{
return (+arguments[0])+(+arguments[1]);
}
}
console.info(fn());//0
console.info(fn(1));//1
console.info(fn(true));//1
console.info(fn(1,2));//3
console.info(fn('1','2'));//3
这里就是利用函数内部属性arguments来实现重载的。当然,在内部重载的方式可以多种多样,你还可以结合typeof、instanceof等操作符来实现你想要的功能。至于内部属性arguments具体是什么?这就是下面要讲的。
5、函数内部属性arguments
简单一点说,函数内部属性,就是只能在函数体内访问的属性,由于函数体只有在函数被调用的时候才会去执行,因此函数内部属性也只有在函数调用时才会去解析,每次调用都会有相应的解析,因此具有动态特性。这种属性有:this和arguments,这里先看arguments,在下一篇文章中再说this。
(1)在函数定义中的参数列表称为形式参数,而在函数调用时候实际传入的参数称为实际参数。一般的类C语言,要求在函数调用时实际参数要和形式参数一致,但是在ECMAScript中,这两者之间没有任何限制,你可以在定义的时候有2个形式参数,在调用的时候传入2个实际参数,但你也可以传入3个实际参数,还可以只传入1个实际参数,甚至你什么参数都不传也可以。这种特性,正是利用函数内部属性来实现重载的基础。
(2)形式参数甚至可以取相同的名称,只是在实际传入时会取后面的值作为形式参数的值(这种情况下可以使用arguments来访问前面的实际参数):
复制代码 代码如下: