-
函数的定义
下面分别展示了函数语句和表达式两种方式的函数定义。注意,以表达式来定义函数只适用于它
作为一个大的表达式的一部分,比如在赋值和调用过程中定义函数:
//输出o的每个属性的名称和值,返回undefined
function printprops(o){
for(var p in o)
console.log(p+":"+o[p]+"\n");
}
//计算两个笛卡尔坐标(x1,y1)和(x2,y2)之间的距离
function distance(x1,y1,x2,y2){
var dx=x2-x1;
var dy=y2-y1;
return Math.sqrt(dx*dx+dy*dy);
}
//计算阶乘的递归函数(调用自身的函数)
//x!的值是从x到x递减(步长为1)的值的累乘
function factorial(x){
if(x<=1)return 1;
return x*factorial(x-1);
}
//这个函数表达式定义了一个函数用来求传入参数的平方
//注意我们把它赋值给一个变量
var square=function(x){return x*x;}//函数表达式可以包含名称,这在递归时很有用
var f=function fact(x){if(x<=1)return 1;else return x*fact(x-1);};//函数表达
式也可以作为参数传给其他函数
data.sort(function(a,b){return a-b;});//函数表达式有时定义后立即调用
var tensquared=(function(x){return x*x;}(10));
函数名称通常是动词或以动词为前缀的词组。通常函数名的第一个字符为小写,这是一种编程约
定。当函数名包含多个单词时,一种约定是将单词以下划线分隔,就像like_this()。还有另外
一种约定,就是除了第一个单词之外的单词首字母使用大写字母,就像likeThis()。有一些函数
是用做内部函数或私有函数(不是作为公用API的一部分),这种函数名通常以一条下划线为前缀。
请注意,例8-1中的大多数函数(但不是全部)包含一条return语句。return语句导致函数停止
执行,并返回它的表达式(如果有的话)的值给调用者。如果return语句没有一个与之相关的表
达式,则它返回undefined值。如果一个函数不包含return语句,那它就只执行函数体中的每条
语句,并返回undefined值给调用者。
在JavaScript里,函数可以嵌套在其他函数里。例如:
function hypotenuse(a,b){
function square(x){return x*x;}
return Math.sqrt(square(a)+square(b));
}
嵌套函数的有趣之处在于它的变量作用域规则:它们可以访问嵌套它们(或多重嵌套)的函数的参数
和变量。例如,在上面的代码里,内部函数square()可以读写外部函数hypotenuse()定义的参数a
和b。
-
函数的调用
1.函数调用
使用调用表达式可以进行普通的函数调用也可进行方法调用。一个调用表达式由多个函
数表达式组成,每个函数表达式都是由一个函数对象和左圆括号、参数列表和右圆括号组成,参数列
表是由逗号分隔的零个或多个参数表达式组成。如果函数表达式是一个属性访问表达式,即该函数是
一个对象的属性或数组中的一个元素,那么它就是一个方法调用表达式。下面将会解释这种情形。下
面的代码展示了一些普通的函数调用表达式:
printprops({x:1});
var total=distance(0,0,2,1)+distance(2,1,3,5);
var probability=factorial(5)/factorial(13);
在一个调用中,每个参数表达式(圆括号之间的部分)都会计算出一个值,计算的结果作为参数传递
给另外一个函数。这些值作为实参传递给声明函数时定义的形参。在函数体中存在一个形参的引用,
指向当前传入的实参列表,通过它可以获得参数的值。
对于普通的函数调用,函数的返回值成为调用表达式的值。如果该函数返回是因为解释器到达结尾,
返回值就是undefined。如果函数返回是因为解释器执行到一条return语句,返回值就是return之
后的表达式的值,如果return语句没有值,则返回undefined。
根据ECMAScript 3和非严格的ECMAScript 5对函数调用的规定,调用上下文(this的值)是全局
对象。然而,在严格模式下,调用上下文则是undefined。
以函数形式调用的函数通常不使用this关键字。不过,"this"可以用来判断当前是否是严格模式。
//定义并调用一个函数来确定当前脚本运行时是否为严格模式
var strict=(function(){return!this;}());
2.方法调用
一个方法无非是个保存在一个对象的属性里的JavaScript函数。如果有一个函数f和一个对象
o,则可以用下面的代码给o定义一个名为m()的方法:
o.m=f;
给对象o定义了方法m(),调用它时就像这样:
o.m();
或者,如果m()需要两个实参,调用起来则像这样:
o.m(x,y);
上面的代码是一个调用表达式:它包括一个函数表达式o.m,以及两个实参表达式x和y,函数表
达式本身就是一个属性访问表达式,这意味着该函数被当做一个方法,而不是作为一个普通函数
来调用。
对方法调用的参数和返回值的处理,和上面所描述的普通函数调用完全一致。但是,方法调用和
函数调用有一个重要的区别,即:调用上下文。属性访问表达式由两部分组成:一个对象(本例
中的o)和属性名称(m)。在像这样的方法调用表达式里,对象o成为调用上下文,函数体可以
使用关键字this引用该对象。下面是一个具体的例子:
var calculator={//对象直接量
operand1:1,
operand2:1,
add:function(){//注意this关键字的用法,this指代当前对象
this.result=this.operand1+this.operand2;
}
};
calculator.add();//这个方法调用计算1+1的结果
calculator.result//=>2
大多数方法调用使用点符号来访问属性,使用方括号(的属性访问表达式)也可以进行属性访问
操作。下面两个例子都是函数调用:
o["m"](x,y);//o.m(x,y)的另外一种写法
a[0](z)//同样是一个方法调用(这里假设a[0]是一个函数)
方法调用可能包括更复杂的属性访问表达式:
customer.surname.toUpperCase();//调用customer.surname的方法
f().m();//在f()调用结束后继续调用返回值中的方法m()
方法和this关键字是面向对象编程范例的核心。任何函数只要作为方法调用实际上都会传入一个
隐式的实参——这个实参是一个对象,方法调用的母体就是这个对象。通常来讲,基于那个对象的
方法可以执行多种操作,方法调用的语法已经很清晰地表明了函数将基于一个对象进行操作,比
较下面两行代码:
rect.setSize(width,height);
setRectSize(rect,width,height);
我们假设这两行代码的功能完全一样,它们都作用于一个假定的对象rect。可以看出,第一行的
方法调用语法非常清晰地表明这个函数执行的载体是rect对象,函数中的所有操作都将基于这个对象。
方法链
当方法的返回值是一个对象,这个对象还可以再调用它的方法。这种方法调用序列中(通常称
为“链”或者“级联”)每次的调用结果都是另外一个表达式的组成部分。比如,基于jQuery库,我们常常会这样写代码:
//找到所有的header,取得它们id的映射,转换为数组并对它们进行排序
$(":header").map(function(){return this.id}).get().sort();
当方法并不需要返回值时,最好直接返回this。如果在设计的API中一直采用这种方式(每个方
法都返回this),使用API就可以进行“链式调用”[3]风格的编程,在这种编程风格中,只要指
定一次要调用的对象即可,余下的方法都可以基于此进行调用:
shape.setX(100).setY(100).setSize(50).setOutline("red").setFill("blue").draw();
不要将方法的链式调用和构造函数的链式调用混为一谈.
需要注意的是,this是一个关键字,不是变量,也不是属性名。JavaScript的语法不允许给this赋值。
和变量不同,关键字this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。如果
嵌套函数作为方法调用,其this的值指向调用它的对象。如果嵌套函数作为函数调用,其this值
不是全局对象(非严格模式下)就是undefined(严格模式下)。很多人误以为调用嵌套函数时
this会指向调用外层函数的上下文。如果你想访问这个外部函数的this值,需要将this的值保
存在一个变量里,这个变量和内部函数都同在一个作用域内。通常使用变量self来保存this,比如:
var o={//对象o
m:function(){//对象中的方法m()
var self=this;//将this的值保存至一个变量中
console.log(this===o);//输出true,this就是这个对象o
f();//调用辅助函数f()
function f(){//定义一个嵌套函数f()
console.log(this===o);//"false":this的值是全局对象或undefined
console.log(self===o);//"true":self指外部函数的this值
}
}
};
o.m();//调用对象o的方法m()
3.构造函数调用
如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。构造函数调用和普通的函数调
用以及方法调用在实参处理、调用上下文和返回值方面都有不同。
如果构造函数调用在圆括号内包含一组实参列表,先计算这些实参表达式,然后传入函数内,这和函
数调用和方法调用是一致的。但如果构造函数没有形参,JavaScript构造函数调用的语法是允许省
略实参列表和圆括号的。凡是没有形参的构造函数调用都可以省略圆括号,比如,下面这两行代码就
是等价的:
var o=new Object();
var o=new Object;
构造函数调用创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数试图初始
化这个新创建的对象,并将这个对象用做其调用上下文,因此构造函数可以使用this关键字来引用
这个新创建的对象。注意,尽管构造函数看起来像一个方法调用,它依然会使用这个新对象作为调用
上下文。也就是说,在表达式new o.m()中,调用上下文并不是o。
构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它
会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值。然而如果构造函
数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象。如果构造函数使用
return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对
象作为调用结果。
4.间接调用
JavaScript中的函数也是对象,和其他JavaScript对象没什么两样,函数对象也可以包含方法。其
中的两个方法call()和apply()可以用来间接地调用函数。两个方法都允许显式指定调用所需的
this值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方
法。两个方法都可以指定调用的实参。call()方法使用它自有的实参列表作为函数的实参,
apply()方法则要求以数组的形式传入参数。