函数是这样一段代码,它只定义一次,但可能被执行或调用任意次。你可能从诸如子例程(subroutine)或者过程(PRocedure)这些名字里对函数概念有所了解。
Javascript函数是参数化的:函数定义会包括一个形参(parmeter)标识符列表。这些参数在函数中像局部变量一样工作。函数会调用会给形参提供实参的值。函数使用它们实参的值计算返回值,成为该函数的调用表达式的值。
除了实参之外,么次调用还会拥有一个值——本地调用的上下文——这就是this关键字值
如果函数挂载在一个对象上,作为对象的一个属性,就称为它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文(context),也就是该函数的this值。用于初始化一个新创建对象的函数称为构造函数(constructor).本文6节i会对构造函数进一步讲解:第9章还会再谈到它。
在javascript中,函数即对象,程序可随意操作它们。比如,javascript可以把函数赋值给变量,或者作为参数传递给其他函数。因为函数就是对象,所以可以给他们设置属性,甚至调用它们的方法。
javascript的函数可以嵌套在其他函数中定义,这样他们就可以访问它们被定义时所处的作用域变量。这意味着javascript函数构成了一个闭包(closere),它给javascript带来了非常强劲的编程能力。
1.函数的定义。
函数使用function关键字来定义。它可以用在函数定义表达式(4.iii)或者函数声明语句里。在这两种形式中,函数定义都从function关键字开始,其后跟随这些部分
下面的例子中分别展示了函数语句和表达式两种方式的函数定义。注意:以表达式来定义函数只适用于它作为一个大的表达式的一部分,比如在赋值和调用的过程中定义函数。
//定义javascript函数 //输出o的每个属性的名称和值,返回undefined function printprops(o) { for (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); }; //f(7)=>5040 //函数表达式也可以作为参数传给其它函数 data.sort(function(a, b) { return a - b; }); //函数表达式有时定义后立即使用 var tensquared = (function(x) { return x * x; }(10))
注意:以表达式定义的函数,函数的名称是可选的。一条函数声明语句实际上声明了一个变量。并把一个函数对象赋值给它。相对而言,定义函数表达式时并没有声明一个变量。函数可以命名,就像上面的阶乘函数,它需要一个名称来指代自己。
如果一个函数定义表达式包含名称,函数的局部变量作用域将会包含一个绑定到函数对象的名称。实际上,函数的名称将成为函数内部的一个局部变量。通常而言,以表达式方式定义函数时不需要名称,这会让定义它们的代码更紧凑。函数定义表达式特别适合用来那些只用到一次的函数,比如上面展示的最后两个例子。
在5.3.ii中,函数声明语句“被提前”到外部脚本或外部函数作用域的顶部,所以以这种方式声明的函数,可以被在它定义之前出现的代码所调用。不过,以表达式定义的函数就令当别论了。
为调用一个函数,必须要能引用它,而要使用一个表达式方式定义的函数之前,必须把它赋值给一个变量。变量的声明提前了(参见3.10.i),但给变量赋值是不会提前的。所以,以表达式定义的函数在定义之前无法调用。
请注意,上例中的大多数函数(但不是全部)包含一条return语句(5.6.iiii)。return语句导致函数停止执行。并返回它的表达式(如果有的话)的值给调用者。如果return语句没有一个与之相关的表达式,则它返回undefined值。如果一个函数不包含return语句。那它就执行函数体内的每条语句,并返回undefined值给调用者。
上面例子中的函数大多是用来计算出一个值的,他们使用return把值返回给调用者。而printprops()函数不同在于,它的任务是输出对象各属性的名称和值。没必要返回值,该函数不包含return语句,printprops()的返回值始终是undefined.(没有返回值的函数有时候被称为过程)。
嵌套函数
在javascript中,函数可以嵌套在其它函数里。例如
function hyuse(a, b) { function square(x) { return x * x } return Math.sqrt(square(a) + square(b)); }
嵌套函数的有趣之处在于它的变量作用域规则:它们可以访问嵌套它们(或者多重嵌套)的函数的参数和变量。
例如上面的代码里,内部函数square()可以读写外部函数hyuse()定义的参数a和b。这些作用域规则对内嵌函数非常重要。我们会在本文第6节在深入了解它们。
5.2.ii曾经说过,函数声明语句并非真正的语句。ECMAScript规范芝是允许它们作为顶级语句。它们可以出现在全局代码里,或者内嵌在其他函数中,但它们不能出现在循环、条件判断、或者try/cache/finally及with语句中(有些javascript并为严格遵循这条规则,比如Firefox就允许在if语句中出现条件函数声明)。注意:此限制仅适用于以语句形式定义的函数。函数定义表达式可以出现在javascript的任何地方。
2.函数调用
构成函数主题的javascript代码在定义之时并不会执行,只有调用该函数是,它们才会执行。有4种方式来调用javascript函数。
i.函数调用
使用调用表达式可以进行普通的函数调用也可以进行方法调用(4.5)。一个调用表达式由多个函数表达式组成,每个函数表达式都是由一个函数对象和左圆括号、参数列表和右圆括号组成,参数列表是由逗号分隔的逗号的零个或多个参数表达式组成。如果函数表达式是一个属性访问表达式,即该函数是一个对象的属性或数组中的一个元素。那么它就是一个方法调用表达式。下面展示了一些普通的函数调用表达式:
printprops({x: 1}); var total = distance(0,0,2,1) + distance(2,2,3,5); var probality = factorial(5)/factorial(13);
在一个调用中,每个参数表达式(圆括号之间的部分)都会计算出一个值,计算的结果作为参数传递给另外一个函数。这些值作为实参传递给声明函数时定义的行参。在函数体中存在一个形参的调用,指向当前传入的实参列表,通过它可以获得参数的值。
对于普通的函数调用,函数的返回值成为调用表达式的值。如果该函数返回是因为解释器到达结尾,返回值就是undefined。如果函数返回是因为解释器执行到一条return语句,返回的值就是return之后的表达式值,如果return语句没有值,则返回undefined。
根据ECMAScript3和非严格的ECMAScript5对函数的调用规定,调用上下文(this的值)是全局对象。然后在严格模型下,调用上下文则是undefined、以函数的形式调用的函数通常不使用this关键字。不过 ,“this”可以用来判断当前是否为严格模式。
//定义并调用一个函数来确定当前脚本运行是否为严格模式 var strict = (function() {return !this;}())
ii.方法调用
一个方法无非是个保存在一个对象的属性里的javascript函数。如果有一个函数f和一个对象o,则可以用下面的代码给o定义一个名为m()的方法:
o.m = f;
给o定义的方法m(),调用它时就像这样:
o.m()
如果m()需要两个实参,调用起来像这样:
o.m(x,y)
上面的代码是一个调用表达式:它包括一个函数表达式o.m,以及两个实参表达式x和y,函数表达式的本身就是一个属性访问表达(4.4节),这意味着该函数被当做了一个方法,而不是作为一个普通的函数来调用。
对方法调用的参数和返回值的处理,和上面所描述的普通函数调用完全一致。但是方法调用和函数调用有一个重要的区别,即:调用上下文。属性访问表达式由两部分组成:一个对象(本例中的o)和属性名称(m)。像这样的方法在调用表达式里,对象o成为调用上下文,函数体可以使用关键字this引用该对象。如下是具体的一个例子
var calcul = { //对象直接量 oprand1: 1, oprand2: 1, add: function() { //注意this关键字的用法,this指带当前对象 return this.result = this.oprand1 + this.oprand2; } }; calcul.add(); //这个方法调用计算1+1的结果 calcul.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(windth, height); setrectSize(rect, width, heigth);
我们假设这两行代码的功能完全一样,他们都作用域一个假定的对象rect。可以看出,第一行的方法调用语法非常清晰地表明了这个函数执行的载体是rect对象,函数中的所有操作都将基于这个对象。
方法链当方法的返回值是一个对象,这个对象还可以再调用它的方法。这种方法调用序列中(通常称为“链”或者“级联”)每次的调用结果都是另外一个表达式组成部分。比如基于jQuery(19章会讲到),我们常这样写代码: //找到所有的header,取得他们的id的映射,转换为数组并给它们进行排序 $(":header").map(function(){return this.id}).get().sort();当方法并不需要返回值时,最好直接返回this。如果在设计的API中一直采用这种方式(每个方法都返回this),使用API就可以进行“链式调用”风格的编程,在这种编程风格中,只要指定一次要调用的对象即可。余下的方法都看一基于此进行调用: 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的值是全局对象undefied console.log(self === o); //"true": slef指外部函数this的值 } } }; o.m();//调用对象o的方法m
在8.7.iiii的例子中,有var self = this更切合实际的用法。
iii.构造函数的调用
如果函数或者方法之前带有关键字new,它就构成构造函数调用(构造函数掉在4.6节和6.1.ii节有简单介绍,第9章会对构造函数做更详细的讨论)。构造函数调用和普通的函数调用方法以及方法调用在实参处理、调用上下文和返回值各方面都不同。如果构造函数调用圆括号内包含一组实参列表,先计算这些实参表达式,然后传入函数内,这和函数调用和方法调用是一致的。但如果构造函数没有形参,javascript构造函数调用的语法是允许省略形参列表和圆括号的。凡是没有形参的构造函数都可以省略圆括号。如下文两个代码是等价的
var o = Object(); var o = Object;
构造函数调用创建一个新的 空对象,这个对象继承自构造函数prototype属性。构造函数试图初始化这个新创建的对象,并将这个对象用做起调用上下文,因此构造函数可以用this关键字来引用对象做起调用上下文,因此,构造函数可以使用this关键字来引用这个新创建的对象。注意:尽管构造函数看起来像一个方法调用,它依然会使用
新闻热点
疑难解答