是什么使得JavaScript不同于其他程序设计语言,在浏览器修饰方面表现出其优异的特性?毫无疑问,JavaScript在Web应用领域受到的好评,既源于它自身灵活的动态特性,也源于浏览器对它充分的支持。
JavaScript是一种深受浏览器“宠爱”的语言,浏览器为其提供了丰富的资源和广阔的舞台。
下面的这段代码在网络上广为流传,被众多JavaScript爱好者奉为代表JavaScript魔力的经典:
例1.2 神奇的“魔法代码”
JavaScript:R=0; x1=.1; y1=.05; x2=.25; y2=.24; x3=1.6; y3=.24; x4=300; y4=200; x5=300; y5=200; DI=document.images; DIL=DI.length; function A(){for(i=0; i-DIL; i++){DIS=DI[ i ].style; DIS.position='absolute'; DIS.left=Math.cos(R*x1+i*x2+x3)*x4+x5; DIS.top=Math.sin(R*y1+i*y2+y3)*y4+y5}R++}setInterval('A()',5); void(0);
打开一个带有几张图片的网页(图片稍微多一些并且每张图片大小相当的话,效果会比较好),将上面这段代码输入到IE浏览器的地址栏(不要换行),敲回车,就会看到页面上的所有图片围成一圈绕着一个点旋转。事实上,这是一段有些故弄玄虚的指令,很容易让初学者觉得新奇和迷惑,而对于资深的JavaScript程序员来说,它几乎恰如其分地表现出了JavaScript大部分操作客户端浏览器的特性(除了故意的糟糕排版和蹩脚的变量命名方式之外)。
在这里,我们先简要地列举一下这些特性,而具体的内容将会在后续的章节里详细展开。
首先,一些浏览器(不是所有的)支持JavaScript伪协议,你可以在浏览器的地址栏里通过“JavaScript:”的形式来执行JavaScript代码。实际上这种良好的执行方式为JavaScript爱好者带来了一个便捷的测试手段,使得他们能够以类似命令行的方式来简易地测试一个没有用过的JavaScript特性,而不必写一大堆文本和HTML标签。
其次,JavaScript支持缺省声明直接赋值的方式来使用全局变量,唯一的约束是命名规则和保留字,作为一种脚本语言,这个特性无疑提供了一种快速便利的执行手段,缺点则也是很明显的,缺乏严谨的约束,为不良代码的产生提供了可能。
大部分程序设计语言中,变量被设计为在声明之后引用,也就是说,要使用某个对象,必须先告知该对象存在之后才能赋值,即先“(在……之中)有一个A”,然后才能说“A是一个……”,在JavaScript中,如果对象的作用域是全局的,则不强制要求“有一个A”的声明。关于变量定义和声明的内容,在第4章将会有详细的讨论。
作为程序员,如果你不管理好自己代码里的变量,那么总有一天你或者你的继任会为它们整天头疼不已。可能出现在任何地方的变量,像缺乏约束四处乱窜的野马,随时都可能导致整个系统崩溃。一个好的习惯是用良好的自我约束来限制变量的定义和使用,并且避免定义过多的全局变量。在JavaScript中,利用闭包是一种代替临时变量的好习惯,在后续的章节中,我们会详细讨论这些现在听起来有些深奥的技巧。
注意到document.images的用法,这个指令枚举出页面文档中所有的图片元素,并把这个元素集合的引用赋值给临时变量DI。
DI=document.images;
Document是一个非常有用的接口,它是JavaScript访问页面文档对象的主要方式。除了访问图片的document.images之外,document提供的属性还能够方便地引用页面文档对象中的表单、链接和其他元素。
document 接口还提供了一组更为标准的方法来创建和访问文档元素,它们是document.getElementById、 document.getElementsByTagName和document.createElement,通常我们认为以上三个方法是 document对象提供的最主要的DOM接口。关于DOM话题我们将会在第12章里详细讨论。
除了Document之外,另一个有用的接口是Window,它提供了对浏览器、窗口、框架、对话框以及状态栏的访问方法,在第三部分里,我们会用很多篇幅仔细地讨论以上两个接口。
另一个需要重点关注的特性是函数定义,function A()声明了一个名字叫做“A”的函数,其后的一对大括号内的指令是对这个函数的定义。提供函数文法使得JavaScript成为一种完善的过程式语言。
function A(){for(i=0; i-DIL; i++){DIS=DI[ i ].style; DIS.position='absolute'; DIS.left=Math.cos(R*x1+i*x2+x3)*x4+x5; DIS.top=Math.sin(R*y1+i*y2+y3)*y4+y5}R++}
除了命名函数之外,JavaScript提供了缺省函数名的定义方法,在某些特定情况下,定义在函数体内部的匿名函数在执行的过程中形成“闭包”。除此以外,JavaScript还提供了一种new操作符来实例化函数对象。以上的两个特性使得JavaScript同时兼有函数式和面向对象的特点,也使得函数成为了JavaScript的第一型。在第6章、第22章、第23章我们将会分别详细讨论JavaScript函数的各种特性和使用技巧。
在函数定义体内,我们可以看到像Math.cos(R*x1+i*x2+x3)这样的用法,Math是JavaScript的一个有用的内置对象,它为JavaScript的使用者提供了一组有用的数学函数,Math.cos返回表达式的余弦值。
在 这之后我们通过一个循环将数学计算的结果赋值给document.images集合中提供的图片样式属性,这里引用的是style.top和 style.left属性,这两个属性分别定义了图片元素左上角距参照系原点的横坐标和纵坐标的值,默认的单位是像素点(关于元素的定位问题我们将会在后 续的章节中有详细的讨论),这样我们相当于将页面文档中的图片元素抽取出来,重新计算了它们的位置,并按照新的位置进行排列。
最后,我们在排列的过程中改变参量R的值,并通过定时器函数setInterval每隔5个毫秒调用一次A()函数,就实现了例子中的图片旋转的特效。
setInterval('A()',5);
setInterval是JavaScript中 一个重要的系统函数,它提供了一种定时执行函数的方法,另一个类似的函数是setTimeout,我们将在第16章里详细地讨论它们。在一些稍为复杂的应 用中,setInterval和setTimeout被大量用于实现动态效果、模拟异步执行、实现拦截器和一些控制型模式,以及实现自定义事件接口。
在结束话题之前,顺便提一个不太常用的特性。也许你已经注意到例子末尾的那个不起眼的void(0),如果你将它去掉,你会发现一切令人惊讶的特效都消失了,甚至连浏览器中的页面也不见踪迹,取而代之的是孤零零地显示在浏览器窗口左上角的一组奇怪的数字,这是怎么回事呢?
原来JavaScript伪协议默认将页面带到一个新的document中并显示程序返回结果,所以正常情况下运算的结果会在一个空文档对象内显示,这样也就没有图片可以展现特效,而void(0)阻止了这个跳转动作。
void是JavaScript的 一个特殊的运算符,它的作用是舍弃任何参数表达式的值,这意味着要求解析器检验并计算参数表达式内容,但是却忽略其结果。如果你刻意去检查void运算的 返回值,会发现它返回一个undefined标记(事实上任何一个不带return指令的函数运算的默认返回值都是undefined)。在浏览器的缺省 行为中,undefined阻止了页面的跳转。
undefined对于JavaScript来说是一个特殊的值,它令我想起了某些宗教和物理学。如果说程序中的null代表着“空”的话,那么undefined则代表着“无”。“空”依然是一种存在,而“无”则是存在的对立面。JavaScript的一个巧妙设计就在于把“无”概念化了,由于它没有强制检验对象存在的机制,所以它承认“无”的概念,任何一个未经定义和使用的标识,均可以用“无”来表示。这个“无”在JavaScript文法中即是undefined。
typeof 操作符用来检查变量的类型,如果你直接引用一个未声明的标识,或者声明了一个变量却未对其进行赋值,那么typeof操作返回的结果将是 undefined。事实上我觉得最好能够用一种新的标识来区分未声明和已声明未赋值的变量,如unknown(未知)区别undefined(无)。当 然JavaScript并没有这么实现。尽管如此,大多数时候拥有undefined和null,就已经足够了。
将以下各行代码分别输入到浏览器的地址栏,体会一下undefined和null的区别:
JavaScript:alert(typeof(x));
JavaScript:var x;alert(typeof(x));
JavaScript:var x=null;alert(typeof(x));
在例1.2代码中,我们用表达式undefined;取代void(0),也能得到相同的结果。
实际上undefined远比想象得要有用得多,我们在后续章节里还会多次接触到undefined这个特殊的值。
至此,我们对例子代码的分析就告一段落。这段代码的经典之处不但在于它实现的效果令人惊叹,还在于它在短短的几行指令中体现了客户端JavaScript中大多数重要的特性,这些特性包括我们前面提到的伪协议、全局变量、文档接口、集合对象、函数、内置对象、元素样式属性、定时器以及void()和undefined,除此以外还提到了代码中没有出现的闭包、函数实例化以及typeof操作符,这些特性几乎构成了客户端JavaScript的全部,在后面的章节中我们也将重点围绕着这些特性展开讨论,相信一段时间之后你再回头看这段代码,会有更加深刻的理解和新的收获。