递归是一种重要的编程技术。该方法用于让一个函数从其内部调用其自身。一个信手可得的示例就是计算阶乘。0 和 1 的阶乘都被特别地定义为 1。 更大数的阶乘是通过计算 1 * 2 * ...来求得的,每次增加 1,直至达到要计算其阶乘的那个数。
下面的段落是用文字定义的计算阶乘的一个函数。
“如果这个数小于零,则拒绝接收。如果不是一个整数,则将其向下舍入为相邻的整数。如果这个数为 0 或 1,则其阶乘为 1。如果这个数大于 1,则将其与相邻较小的数的阶乘相乘。”
要计算任何大于 1 的数的阶乘,至少需要计算一个其他数的阶乘。用来实现这个功能的函数就是已经位于其中的函数;该函数在执行当前的这个数之前,必须调用它本身来计算相邻的较小数的阶乘。这就是一个递归示例。
显然,这样有可能会出现问题。可以很容易地创建一个递归函数,但该函数不能得到一个确定的结果,并且不能达到一个终点。这样的递归将导致计算机执行一个“无限”循环。下面就是一个示例:在计算阶乘的文字描述中遗漏了第一条规则(对负数的处理) ,并试图计算任何负数的阶乘。这将导致失败,因为按顺序计算 -24 的阶乘时,首先不得不计算 -25 的阶乘;然而这样又不得不计算 -26 的阶乘;如此继续。很明显,这样永远也不会到达一个终止点。
因此在设计递归函数时应特别仔细。如果怀疑其中存在着无限递归的可能,则可以让该函数记录它调用自身的次数。如果该函数调用自身的次数太多,即使您已决定了它应调用多少次,就自动退出。
下面仍然是阶乘函数,这次是用 JScript 代码编写的。
function factorial(aNumber) {
aNumber = Math.floor(aNumber); // 如果这个数不是一个整数,则向下舍入。
if (aNumber < 0) { // 如果这个数小于 0,拒绝接收。
return "not a defined quantity";
}
if ((anumber == 0) || (anumber == 1)) { // 如果为 0 或 1,则其阶乘为 1。
return 1;
}
else return (anumber * factorial(anumber - 1)); // 否则,递归直至完成。
}
变量范围
Microsoft JScript 有两种变量范围:全局和局部。如果在任何函数定义之外声明了一个 变量 ,则该变量为全局变量,且该变量的值在整个持续范围内都可以访问和修改。如果在函数定义内声明了一个变量,则该变量为局部变量。每次执行该函数时都会创建和破坏该变量;且它不能被该函数外的任何事物访问。
一个局部变量的名称可以与某个全局变量的名称相同,但这是完全不同和的两个变量。因此,更改一个变量的值不会影响另一个变量的值。在声明局部变量的函数内,只有该局部变量有意义。
var aCentaur = "a horse with rider,"; // aCentaur 的全局定义。
// JScript 代码,为简洁起见有省略。
function antiquities() // 在这个函数中声明了一个局部 aCentaur 变量。
{
// JScript 代码,为简洁起见有省略。
var aCentaur = "A centaur is probably a mounted Scythian warrior";
// JScript 代码,为简洁起见有省略。
aCentaur += ", misreported; that is, "; // 添加到局部变量。
// JScript 代码,为简洁起见有省略。
} // 函数结束。
var nothinginparticular = antiquities();
aCentaur += " as seen from a distance by a naive innocent.";
/*
在函数内,该变量的值为 "A centaur is probably a mounted Scythian warrior,
misreported; that is, ";在函数外,该变量的值为这句话的其余部分:
"a horse with rider, as seen from a distance by a naive innocent."
*/
很重要的一点是注意变量是否是在其所属范围的开始处声明的。有时这会导致意想不到的情况。
var aNumber = 100;
var withAdditive = 0;
withAdditive += aNumber; // withAdditive 现在是 100。
tweak();
withAdditive += aNumber; // withAdditive 现在是 200。
function tweak() {
var newThing = 0; // 显示声明 newThing 变量。
// 如果不注释下一条语句,将产生一个错误。
// newThing = aNumber;
// 下一条语句将值 42 赋给局部的 aNumber,该变量是隐式声明的。
aNumber = 42;
if (false) {
var aNumber; // 该语句永远不会执行。
aNumber = "Hello!"; // 该语句永远不会执行。
} // 条件语句结束。
} // 该函数定义结束。
被注释掉的语句试图将局部变量 aNumber 的值赋给局部变量 newThing 的值。尽管事实上在函数的其他地方定义了一个局部 aNumber 变量,并且因此在整个函数内都存在,这样做也不会成功。 aNumber 变量在代码中这条语句的位置处没有被赋值,因此是 undefined。
复制、传递和比较数据
在 Microsoft JScript 中,对数据的处理取决于该数据的类型。
按值和按引用的比较
Numbers 和 Boolean 类型的值 (true 和 false) 是按值来复制、传递和比较的。当按值复制或传递时,将在计算机内存中分配一块空间并将原值复制到其中。然后,即使更改原来的值,也不会影响所复制的值(反过来也一样),因为这两个值是的实体。
对象、数组以及函数在大多数环境中都是按引用来复制、传递和比较的。 当按地址复制或传递时,实际是创建一个指向原始项的指针,然后就像拷贝一样来使用该指针。如果随后更改原始项,则将同时更改原始项和复制项(反过来也一样)。实际上只有一个实体;“复本”并不是一个真正的复本,而只是该数据的又一个引用。
注意 可以更改对象和数组的该操作,方法是为其指定 assign( ) 方法。 | |
注意 鉴于 ASCII 和 ANSI 字符集的构造方法,按序列顺序大写字母位于小写字母的前面。例如 "Zoo" 小于 "aardvark"。 | |
按值传递一个参数给函数就是制作该参数的一个复本,即一个只存在于该函数内的复本。另外,如果按引用传递一个参数,且该函数更改了该参数的值,则这个值在脚本的任何位置都会被改变。
检验数据
当按值进行检验时,是比较两个截然不同的项以查看它们是否相等。通常,该比较是逐字节进行的。当按引用进行检验时,是看这两项是否是指向同一个原始项的指针。如果是,则比较结果是相等;如果不是,即使它们每个字节都包含完全一样的值,比较结果也为不相等。
按引用复制和传递字符串能节约内存;但是由于在字符串被创建后不能进行更改,因此可以按值进行比较。这样可以检查两个字符串是否包含相同的内容,即使它们是完全产生的。
使用数组
数组下标
JScript 中的数组是稀疏的。也就是说,如果一个数组具有三个元素,编号分别为 0、1 和 2,您就可以创建元素 50,而不必担心从 3 到 49 的参数。如果该数组有一个自动的 length 变量,(请参阅 固有对象了解有关数组长度的自动监控的说明),该 length 变量被设为 51,而不是 4。当然您可以创建各元素的编号之间没有间隙的数组,不过没有必要这样做。实际上在 JScript 中,数组根本没有编号的下标。
在 JScript 中,对象和数组实质上是一样的。真正的差异不在于数据,而在于您引用数组的成员或对象的属性 和 方法的方式。
数组寻址
有两种方式来引用数组中的成员。一般说来,可以使用方括号来引用数组。方括号中是一个数字或一个值为非负整数的表达式 。下面的示例假定在脚本的其他地方已定义了entryNum 变量,且已赋值。
theListing = addressBook[entryNum];
theFirstLine = theListing[1];
这种寻址方法与对象寻址的方法是等价的,不过在对象寻址中在圆点后必须使用实际的属性名。如果没有这样的属性,代码将产生一个错误。
数组寻址的第二种方式是制作一个包含编号属性的对象/数组,然后用一个循环产生这些编号。下面的示例将产生两个数组:一个代表名字,一个代表地址,名字和地址均来自 addressBook 中的一个列表。每一项包含四个属性。例如,从theListing的 [Name1] 到 [Name4] 属性产生的theName实例可能包含 "G." "Edward" "Heatherington" "IV", 或 "George" "" "Sand" ""。
theListing = addressBook[entryNum];
for (i = 1; i < 4; i++) {
theName[i] = theListing["Name" + i];
theAddress[i] = theListing["Address" + i];
}
虽然这个特定的实例很短,并且可以很容易地使用 "."这种记法来表示(也就是按地址而不是按数组来对 theListing、theName和 theAddress 寻址),但这并不总是可行的。有时特定的属性可能要在运行时才会存在,或者预先并不知道将是哪些项。例如,如果addressBook 数组不是编好号码的清单,而是按姓排列的,用户可能在正在运行该脚本时“随意”输入名称来查找人员。下面的实例假定在脚本的其他地方已提供了适当的函数定义。
theListing = addressBook[getName()];
theIndivListing = theListing[getFirstName()];
这是数组的关联寻址,也就是说,依靠完全任意的字符串来寻址。JScript 中的对象实际上就是关联数组。尽管可以(并且是频繁地〕使用 "."这种圆点寻址风格,但无论如何不是必选项。因为 JScript 对象的任何属性都可以使用数组标记法来寻址,因此 JScript 对象可以作为一个关联数组使用。
下面的代码创建并初始化数组的最常见格式:
var myArray = new Array("Athens", "Belgrade", "Cairo");
该数组的每个元素都是使用其元素编号来寻址的;在本例中是 0、1 或 2。使用 for...in 语句,该数组可以从 0 开始到 2 结束来进行遍历。例如:
for (key in myArray)
response.write("Element value is " + MyArray[key] + "
);
下面的代码创建并初始化一个包含三个元素的关联数组:
var MyArray = {"a" : "Athens", "b" : "Belgrade", "c" : "Cairo" };
在该数组中,可以使用字符串("a"、 "b" 或 "c") 来对元素寻址,而不是用数组元素的编号 (0、1 或 2)寻址。这样就可以以更直观的寻址方案来创建和使用数组。同样可以使用上面显示的 for...in 语句代码来遍历该数组。
创建高级对象
使用构造函数来创建对象
在 Microsoft JScript 中,可以使用构造函数来创建和生成对象类。您可以使用 new 语句来调用一个构造函数。该语句将返回其创建的内容。
Function 构造函数这个特例可用来创建匿名的函数。所谓匿名函数是指没有名称的函数。例如,可以使用 Function 构造函数来生成一个 “不工作”的函数,作为在另一个函数内的指示。这种函数只会从一个位置调用,不需要名称。
在下例中,这个匿名函数将生成一行“姓名和电子邮件地址”列表。该函数检查 firstNameFirst 变量的值来决定是先输入名还是姓,然后检查 emailNameFirst 变量的值来决定是先输入姓名还是电子邮件地址。该示例假定已在别的位置设置了firstNameFirst 和 emailNameFirst 的值。
for (j = 1; j < addressList[length]; j++)
{
oneListingLine = new Function(emailNameFirst, firstNameFirst, addressList, j, theName = new Function(firstNameFirst, addressList, j, var theName=(addressList[j].firstName + addressList[j].lastName);
if(firstNameFirst)
{
theName=(addressList[j].firstName + addressList[j].lastName);
},) ;
if (emailNameFirst)
{
theListing = addressList[j].emailName+ ":\" + theName
} else theListing = theName + ":\" + addressList[j].emailName; return theListing;)
document.write(oneListingLine + "
");
}
编写构造函数
要编写自己的构造函数,可以在构造函数中使用 this 关键字来引用新创建的对象。该构造函数将初始化该对象。
尽管在下面的示例中构造函数是从 0 索引开始的,但这并不是必选项。例如,如果希望某个参数指明数组或对象的实际索引数,您可以将 1 作为第一个索引开始。 在该示例中,该参数是 extent 来使其区别于由内置的 Array( ) 对象中自动维护的 length 参数。如果编写了给数组添加属性的代码,必需更新 extent 参数(或您自己的对应参数),因为该参数不是由 JScript 维护的。注意即使是这个极端简单的示例也使用了对象(点)和数组(方括号)的标注风格来引用当前对象。
function MakeStringArray(length) {
this.extent = length;
for (iNum = 0; iNum < length; i++) {
this[iNum] = "";
}
}
// 使用该构造函数来创建和初始化一个数组。
myStringArray = new MakeStringArray(63);
使用原型来创建对象
在编写对象定义时,可以使用 prototype 属性来创建由该定义生成且所有对象都具有的属性。原型属性将按引用复制给类中的每个对象,因此这一类中的所有对象的这个属性都相同。不过,可以在一个对象中更改原型属性的值,新的值将覆盖默认值,但仅在该实例中有效。属于这个类的其他对象不受此更改的影响。
使用这个原则,可以给 JScript 语言内部的对象定义附加属性,所有这些对象都具有原型。例如,如果要在计算中使用一个特殊的常数,而在 Math 和 Number 对象中提供的常数没有包括该常数,则可以自己定义,并对其指定各自的对象原型,或对象类的原型属性。
Math.prototype.Avogadro = 6.0232E23;
function howManyMolecules(wtGrams,molWt) {
return ((wtGrams/molWt)*Math.prototype.Avogadro);
}
document.write("There are " + howManyMolecules(window.prompt("How many grams?",0),window.prompt
("What's the molecular weight?",0)) +
" molecules in that amount.");
也许还需要指出的一点是,您可以定义一个函数,将其指定为 String.prototype 的一个方法,并在脚本的任何地方随字符串使用该函数。下面的示例假定已有一个在脚本的其他地方定义的、称为 "theElements" 的 元素周期表(Periodic Chart)数组,该数组中包含了元素的符号、名称、原子数以及其他相关信息。
function atomName(theSymbol) {
return(theElements[theSymbol].fullName);
}
String.prototype.atomName = atomName;
function decodeFormula(theFormula) {
var theCurrentPiece = "";
var theDecodedFormula = "";
for (i = 1; i = theFormula.length ; i++);
if (theFormtheCurrentPiece
// 将公式字符串分为符号和数字的数组。
// 循环该公式数组并装配拆开的字符串。每一项为:
theDecodedFormula += formula[n].number
theDecodedFormula += " ";
theDecodedFormula += formula[n].symbol.prototype.atomName;
theDecodedFormula += " "
// 循环结束。
return theDecodedFormula;
}
decodeFormula(window.prompt("Formula?
特殊字符
特殊字符
JScript 提供了一些特殊字符,允许在字符串中包括一些无法直接键入的字符。每个字符都以反斜杠开始。反斜杠是一个转义字符,表示 JScript 解释器下面的字符为特殊字符。
转义序列 | 字符 |
\\b | 退格 |
\\f | 走纸换页 |
\\n | 换行 |
\\r | 回车 |
\ | 横向跳格 (Ctrl-I) |
\\' | 单引号 |
\\" | 双引号 |
\\\ | 反斜杠 |
document.write('The caption reads, "After the snow of \\'97. Grandma\\'s house is covered."');
可以使用这些转义序列来控制位于
和标识之间的文本格式,此外,也可用来控制警告、提示和确认消息框中的文本格式。 脚本问题解答
如果不够细致,任何编程语言都有一些可能发生错误的地方,而且每种语言都有其特殊之处。例如,对于 null 值: Microsoft JScript 中这个值与 C 或 C++ 语言中的 null 值所起的作用是不一样的。
下面提供了一些在编写 JScript 脚本时可能遇到的问题。
语法错误
由于编程语言中的语法比自然语言的语法要严格得多,因此在编写脚本时对细节应倍加关注。例如,如果您本意是将字符串作为某个参数,但是在键入时忘了使用引号引起来,就会产生问题。
脚本解释顺序
对 JScript 的解释是 Web 浏览器的 HTML 语法分析处理的一部分。因此,如果在文档的
标识中放置了一个脚本,则将在检查所有的 标识之前加以解释。如果在 标识中将创建对象,但由于在分析处理 标识时这些对象尚不存在,因而不能被脚本操作。自动类型强制
JScript 是一种具有自动强制的自由类型语言。因此,尽管实际上不同类型的值是不相等的,但对下述示例中的表达式求值都将得到 true。
"100" == 100
false == 0
运算符优先级
在对表达式求值时某个特定运算符的执行主要是根据 运算符优先级 ,而不是表达式的位置。因此,在下面的示例中,乘法将先于减法执行,尽管在该表达式中第一个出现的运算符是减法。
theRadius = aPerimeterPoint - theCenterpoint * theCorrectionFactor;
对对象使用 for...in 循环
当使用 for...in 循环对某个对象的属性进行遍历时,不必预先确定或管理将要指定给该循环计数器变量的对象字段的顺序。此外,在该语言的不同实现方案中该顺序可能会不一样。
with 关键字
with 语句可以方便地用来引用某个特定对象中已有的属性,但是不能用来给对象添加属性。要给对象创建新的属性,必须明确地引用该对象。
this 关键字
尽管可以在对象的定义范围内使用 this 关键字来引用该对象本身,但是当函数不是该对象的定义时,就不能象普通情况那样使用 this 或类似的关键字来引用当前的执行函数。如果该函数被指定为某个对象的方法,则可以在该函数内使用 this 关键字来引用该对象。
编写一个写脚本的脚本
当解释程序遇到标记时会终止当前脚本。要显示"" 本身,请将其改写为至少两个字符串,例如 "",这样就可以在输出语句中将其连接在一起。
隐式窗口引用
由于同时可以打开多个窗口,任何隐式的窗口引用都被指向当前窗口。对于其他窗口必须使用显式引用。
在 Internet Explorer 中使用 Jscript
在浏览器中显示信息
Microsoft JScript 提供了两种方式来在浏览器中直接显示数据。可以使用 write( ) 和 writeln( ),这两个函数是document 对象的方法。也可以在浏览器中以表格的方式显示信息,以及用 警告、提示和确认 消息框来显示信息。
使用 document.write( ) 和 document.writeln( )
显示信息最常用的方式是 document 对象的 write( ) 方法。该方法用一个字符串作为其参数,并在浏览器中显示。该字符串可以是普通文本或 HTML。
字符串可以用单引号或双引号引起来。这样可以引用那些包含引号或撇号的内容。
document.write("Pi is approximately equal to " + Math.PI);
document.write( );
提示 下面的简单函数可以避免在浏览器中显示信息时不得不键入 "document.write"。该函数不能告知要显示的信息是否未定义,而是发布给命令 "w();",该命令将显示一个空行。 function w(m) { // 编写函数。 m = "" + m + ""; // 确保变量 m 是一个字符串。 if ("undefined" != m) { // 判别是否为空或其它未定义的项。 document.write(m); } document.write(" } w(); w("This is an engraving of a horse."); w(); | |
和标识,这个换行符会被解释,且在浏览器中显示。 在调用 write( ) 方法时,如果该文档不处于在调用 write( ) 方法时的打开和分析的过程中,该方法将打开并清除该文档,所以它可能是有危险的。该示例显示了一个每隔一分钟就显示时间的脚本,但是在第一次显示后由于它从过程中将自己清除,因此会导致失败。
如果使用 window 对象的 alert() 方法而不是 document.write(),则该脚本可以运行。
window.alert(theHour + " hours, " + theMinute + " minutes, Coordinated Universal Time.");
window.setTimeout("singOut();", 60000);
}
清除当前文档
document 对象的 clear() 方法将清空当前文档。该方法也将清除您的脚本(随文档的其他部分一起),因此要特别注意该方法的使用方式及在什么时候使用该方法。
document.clear();
使用消息框
使用警告、提示和确认
可以使用警告、确认和提示消息框来获得用户的输入。这些消息框是 window 对象的接口方法。由于 window 对象位于对象层次的顶层,因此实际应用中不必使用这些消息框的全名(例如 "window.alert()"),不过采用全名是一个好注意,这样有助于您记住这些消息框属于哪个对象。
警告消息框
alert 方法有一个参数,即希望对用户显示的文本字符串。该字符串不是 HTML 格式。该消息框提供了一个“确定”按钮让用户关闭该消息框,并且该消息框是模式对话框,也就是说,用户必须先关闭该消息框然后才能继续进行操作。
window.alert("欢迎!请按“确定”继续。");
确认消息框
使用确认消息框可向用户问一个“是-或-否”问题,并且用户可以选择单击“确定”按钮或者单击“取消”按钮。confirm 方法的返回值为 true 或 false。该消息框也是模式对话框:用户必须在响应该对话框(单击一个按钮)将其关闭后,才能进行下一步操作。
var truthBeTold = window.confirm("单击“确定”继续。单击“取消”停止。");
if (truthBeTold) {
window.alert("欢迎访问我们的 Web 页!");
} else window.alert("再见啦!");
提示消息框
提示消息框提供了一个文本字段,用户可以在此字段输入一个答案来响应您的提示。该消息框有一个“确定”按钮和一个“取消”按钮。如果您提供了一个辅助字符串参数,则提示消息框将在文本字段显示该辅助字符串作为默认响应。否则,默认文本为 "
"。 与alert( ) 和 confirm( ) 方法类似,prompt 方法也将显示一个模式消息框。用户在继续操作之前必须先关闭该消息框
var theResponse = window.prompt("欢迎?请在此输入您的姓名。");