Function
类型
函数实际上也是对象,也就是说每个函数其实都是Function
类型的实例,也具有方法和属性。既然函数是对象,那么函数名就是一恶搞指向函数对象的指针,不会与特定的函数绑定。函数声明的语句一般是:
function sum(num1, num2){ |
同样你也可以用下面的声明方式,这更好的表面了函数其实也是对象:
var sum = function(num1, num2){ |
这就跟声明其他变量时一样,注意末尾的分号,这就是一个声明sum
变量的语句,并且把sum
初始化一个函数的实例。这种定义的方式其实是使用的Function
类型的构造函数,它的构造函数接受任意的参数,但最后一个参数始终被看成是函数体,前面的参数都作为新函数的参数,也就是,我们还可以这样定义函数:
var sum = new Function("num1", "num2", "return num1 + num2"); //不推荐 |
从技术上讲,这更加符合函数是个对象的本质。但是我们并不推荐这样方式去定义函数,因为这样将导致两次代码的解析(第一次是常规的ECMAScript代码,第二次是传入构造函数的各种字符串),这将对性能有影响。
既然函数也是对象,函数名就是指向这个对象的指针,所以一个函数也可以有多个名字:
function sum(num1, num2){ |
将sum
设置为null
只是为了让sum
和函数断绝关系,还是可以继续通过anotherSum找到这个函数,继续使用。
深入理解没有重载
之前已经提到ECMAScript中的函数没有重载,但现在我们可以明确的说,没有重载的原因:函数名称只是一个变量,当你给这个变量赋值两次的时候,当然后面一次覆盖了前面的一次:
function addSomeNum(num){ |
这其实跟下面的代码是完全一致的,下面的代码更能说明发生了什么:
|
函数声明和函数表达式
上文说提到的两种定义函数的方式分别是函数声明和函数表达式,目前为止我们还没谈到它们的区别。但解释器在执行环境中加载数据时,它们也并不是一视同仁的:
- 对于函数声明,解析器会率先读取函数声明,并且使其在执行任何代码前可用;
- 对于函数表达式,必须等到解析器执行到它所在的行,才会被解释执行。
sum(10, 10); //20 |
以上代码完全可以正常运行。因为代码在执行前,解析器就已经通过一个名为函数声明提升的过程,读取并且把函数声明添加到执行环境中。但是如果改成函数表达式,则代码就会在运行期间出错:
|
作为值的函数
函数本身就是一个变量,所以也可以作为另一个函数的参数和返回值:
function testCondition(num, condition){ |
当然函数也能作为返回值:
|
函数内部属性
在函数内部,有两个特殊的对象:arguments
和this
。
arguments
arguments
是类数组对象,包含了传入函数的所有参数,并且还有一个属性callee
,该属性是个指针,指向拥有这个arguments
对象的函数。下面看一个经典的阶乘问题:
function factorial(num){ |
上面的例子中,递归函数把函数的执行和函数名factorial
耦合在一起了,所以当factorial
发生改变的时候,anotherFactorial
也失效了,因为在anotherFactorial
内部,还在调用factorial
。所以递归算法就可以用到arguments
的callee
属性,还是同样的代码:
function factorial(num){ |
这里我们就得到了正确的结果。
this
this
引用的是函数据以执行的环境对象,总的说来,就是this总是指向调用函数的那个对象,如果是全局函数,那么就指向全局对象,在网页中就是window
:
|
需要始终牢记的是函数只是一个变量,所以这里的tellColor()
和obj.tellColor()
指向是同一个函数,只是在函数中的this
对象不一样而已。
caller
ECMAScript 5中也规范了另一个函数对象的属性:caller
,指向调用该函数的函数的引用,如果是全局的函数,它的值为null
:
function inner(){ |
函数的属性
函数是对象,当然和其他对象一样,也具有属性和方法。函数有两个属性:length
和prototype
。
length
length
表示函数希望接受到的命名参数的个数:
|
prototype
对于ECMAScript的引用类型而言,prototype
是保存它们所有实例方法的所在。也就是诸如tostring()
,valueOf()
等方法都是保存在prototype
名下,只是通过各自对象的实例方法来调用。在创建自定义的引用类型以及实现继承时,prototype
也是很很重要的,我们将会在之后讲到。在ECMAScript 5中prototype
是不可枚举的。
函数的方法
每个函数也都有两个非继承而来的方法:apply()
, call()
,这两个函数的用途都是在特定的作用域中调用函数,实际上等于是设置了函数体内的this
对象的值。
apply()
和call()
apply()
接受两个参数,一个是其中运行函数的作用域,另一个是参数数组,其中第二个参数可以是Array的实例,也可以是arguments
对象。call()
跟apply()
方法的作用相同,区别仅仅是接受参数的方式不同,对call()
而言,第一个参数任然是运行函数的作用域,而之后的参数用于直接接受参数,而不是以数组的形式给出。
|
由上面的例子也可以看出,apply和call正真强大的地方是能够扩充函数运行的作用域,在上面的例子中,我们就并没有给obj添加方法。
bind()
ECMAScript 5中还定义了一个方法,bind()
。这个方法会创建一个函数的实例,其this
值会被绑定到传给bind()
的参数的值:
window.color = "red"; |
继承的方法
每个函数的继承的toLocaleString()
和 toString()
始终都返回函数的代码,只是返回代码的格式因浏览器而异,有的返回源代码中的一样的函数代码,有的返回函数代码的内部表示——由解析器删除注释并且对某些代码做了改动之后的代码。valueOf()
也同样返回函数代码。