首页 > 网站 > WEB开发 > 正文

第八章:Javascript函数

2024-04-27 14:12:57
字体:
来源:转载
供稿:网友

第八章:javascript函数

函数是这样一段代码,它只定义一次,但可能被执行或调用任意次。你可能从诸如子例程(subroutine)或者过程(PRocedure)这些名字里对函数概念有所了解。

Javascript函数是参数化的:函数定义会包括一个形参(parmeter)标识符列表。这些参数在函数中像局部变量一样工作。函数会调用会给形参提供实参的值。函数使用它们实参的值计算返回值,成为该函数的调用表达式的值。

除了实参之外,么次调用还会拥有一个值——本地调用的上下文——这就是this关键字值

如果函数挂载在一个对象上,作为对象的一个属性,就称为它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文(context),也就是该函数的this值。用于初始化一个新创建对象的函数称为构造函数(constructor).本文6节i会对构造函数进一步讲解:第9章还会再谈到它。

在javascript中,函数即对象,程序可随意操作它们。比如,javascript可以把函数赋值给变量,或者作为参数传递给其他函数。因为函数就是对象,所以可以给他们设置属性,甚至调用它们的方法。

javascript的函数可以嵌套在其他函数中定义,这样他们就可以访问它们被定义时所处的作用域变量。这意味着javascript函数构成了一个闭包(closere),它给javascript带来了非常强劲的编程能力。

1.函数的定义。

函数使用function关键字来定义。它可以用在函数定义表达式(4.iii)或者函数声明语句里。在这两种形式中,函数定义都从function关键字开始,其后跟随这些部分

  1. 函数名称标识符:函数明川是函数声明语句必须的部分。它的用途就像是变量的名字,新定义的函数对象会赋值给这个变量。对函数定义表达式来说,这个名字是可选的:如果存在,该名字只存在函数中,并代指函数对象本身。
  2. 一对圆括号:其中包含由0个或者多个逗号隔开的标识符组成的列表。这些标识符是函数的参数明川,它们就像函数体中的局部变量一样。
  3. 一对花括号,里边包含0条或者多条javascript语句。这些语句构成了函数体:一旦调用函数,就会执行这些语句。

下面的例子中分别展示了函数语句和表达式两种方式的函数定义。注意:以表达式来定义函数只适用于它作为一个大的表达式的一部分,比如在赋值和调用的过程中定义函数。

             //定义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函数。

  • 作为函数
  • 作为方法
  • 作为构造函数
  • 通过它们的call()或apply()方法间接调用

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关键字来引用这个新创建的对象。注意:尽管构造函数看起来像一个方法调用,它依然会使用

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表