2007年3月9日

纵览 JavaScript语言

简介

这篇文章是为专业程序员介绍的JavaScript语言的,它是一种小巧的语言,如果你熟悉其他的编程语言,那么这篇文章对你来讲不是那么难以理解。

JavaScript不是Java,他们是两门完全不同的语言,JavaScript不是Java的子集,JavaScript不能被认为是Java(Java就是Java)。JavaScript分享了像Java一样分享C语言的语法,但从更深角度讲JavaScript更与SchemeSelf有相似之处。它是一门小巧的语言,但是它确实强大的和丰富的语言。你应该好好观察一下它,你会发现它不是一个玩具语言,而是一个拥有许多与众不同特性的、完整的语言。

JavaScript是一门不用花太多时间学习的正规语言。它能更好地适合一些任务,比如与Java相比它更适合客户端编程。以我的实践经验,我发现用JavaScript工作使我成为一个更好的Java程序员,因为它给我带来一套丰富的技巧。

当我最初接触JavaScript时,我没有理他以为它不值得我的注意。后来,我对它刮目相看,因为我发现隐藏在浏览器中的它是一个如此有效的程序语言。我最初是在JavaScript在Sun和Netscape公司的初始陈述注意到它的。他们对JavaScript做了许多错误陈述,以免使它和Java处于竞争的位置。这些错误陈述继续充斥这那些只为傀儡和业余爱好者提供的、不好的JavaScript书中。

历史

JavaScript是被Netscape的Brendan Eich,作为一个页面脚本语言,在Navigator 2中开发的。它是一个非常具有表现力的动态程序语言,因为和浏览器的关系,它立刻变的大红大紫了。它从来没有得到一个可以校正它的问题的和基于真正使用目的测试周期,这一切导致它很强大却也有缺陷。

这篇文章描述了ECMAScript 版本3 (也叫 JavaScript 1.5)。 Microsoft和Netscape一起开发了一个没有改正缺陷的呆滞版本。新版本的语言也许不能叫JavaScript,也不在这篇文章的讨论范围内。

数据类型

JavaScript包含一小套数据类型,它有3种简单的类型:boolean, number, 和 string;特殊的值:null,undefined;其他的所有基于object类型的变化。

布尔类型有2个值:truefalse

数字是64位的浮点值,与Java的Double类似。它没有整型。除法运算可能带来小数位的结果。数字包括特殊值NaN(不是一个数字)和Infinity

字符串(string)是从零到多个Unicode字符组成的。没有单独的字节类型。一个字节被描绘成一个长度为1的字符串。字符串被'符号或"符号引用到一起,单引号和双引号可以替换使用,但必须前后匹配使用。

'This is a string.'
"Isn't this a string? Yes!"
'A' // The character A
"" // An empty string

转义用\字符,就像Java一样。字符串是不可变的。字符串有一个length属性类查看字符串的用字符的个数。

var s = "Hello World!";
s.length == 12

可以给这些简单类型增加函数,你可以为所有数字类型增加一个int()的函数,当你调用Math.PI.int()的时候就会得到3这个结果。

一个实现可能提供其它类型,比如日期(Dates)类型,正则表达式类型,但是他们真正的类型是Object类型,所有其它类型都是Object类型。

对象

JavaScript拥有非常漂亮的符号便利以对付那些键值集合(哈希表)(hashtables)。

var myHashtable = {};

这条语句了声明了一个键值集合,并把它分配到一个局部变量里。JavaScript是弱类型的,所以我们不能用类型的名称来进行声明。我们用索引的方式从键值结合中增加、替换和获取对象。

myHashtable["name"] = "Carl Hollywood";

也可以使用“.”的方式带来一点便利。

myHashtable.city = "Anytown";

当索引是一个合法的系统保留字时,点符号就变得没用了。这是因为一个语言上的定义错误,保留字不能被用于点符号的形势下,但是可以用于索引的形式下。

你会发现JavaScript的哈希表符号与Java的对象和数组符号非常地相似。JavaScript把这更发扬广大了:对象和哈希表是一个回事,所以我可以这样写

var myHashtable = new Object();

这个结果确实是一样的。

for语句可以实现对象一个枚举的能力。

for (var n in myHashtable) {
    if (myHashtable.hasOwnProperty(n)) {
        document.writeln("<p>" + n + ": " + myHashtable[n] + "</p>");
    }
}

这个结果应该是

<p>name: Carl Hollywood</p>
<p>city: Anytown</p>

一个对象就是一个涉及到键/值对的集合,键是字符串(或者其他的元素比如那些被转化为字符串的数字),值可以是任何数据类型,包括其他对象。对象经常以哈希表的方式实现,但是没有任何哈希表(比如哈希函数或重写的函数)天性的可见。

对象很容易被嵌套到别的对象当中,并且对象的表达式可以内容对象里使用。

this.div = document.body.children[document.body.children.length - 1];

对象的文字表达中,一个对象描述了包含在大括弧中的一组用逗点隔开的键/值对。键可以是在其后用冒号的标识符或字符串。由于语言定义的错误,系统预留字不能以标识符方式出现,但可以用字符串方式出现。值可以是任何类型的文字或表达式。

var myObject = {name: "Jack B. Nimble", 'goto': 'Jail', grade: 'A', level: 3};
return {
    event: event,
    op: event.type,
    to: event.srcElement,
    x: event.clientX + document.body.scrollLeft,
    y: event.clientY + document.body.scrollTop};
emptyObject = {};

JavaScript的对象模式是JSON的数据交换格式的基础。

新的成员可以在任何时候被附着增加到任何对象上。

myObject.nickname = 'Jackie the Bee';

数组和函数以对象方式实现。

数组

JavaScript中数组也是哈希表对象,这使它非常好地与稀少的数组程序相称。当你声明了一个数组,你不需要声明它的大小,数组会自增长,这很像Java的向量(vectors)。数组的值根据键来定位,而不是根据偏移量,这使JavaScript数组非常方便地使用,但不适合那些数字分析应用。

对象和数组最主要的不同点是length属性。length属性总是比最大的整数键大1。有2种方式创建一个新数组。

var myArray = [];
var myArray = new Array();

数组不是强类型,它可以包括数字、字符串、布尔值、对象、函数和数组,你可以在一个数组中混合使用字符串、数字和对象。你以嵌套序列的方式使用数组,这和s-expressions的方式非常地类似。数组的第一个索引通常用0。

当数组增加一个新元素的时候,它的索引是比数组长度。然后数组的长度变成在索引基础上加1。这个便利的特性使数组很容易在for循环中增加元素的数量。

数组有个与对象类似的符号标记。

myList = ['oats', 'peas', 'beans', 'barley'];

emptyArray = [];

month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

slides = [
    {url: 'slide0001.html', title: 'Looking&nbsp;Ahead'},
    {url: 'slide0008.html', title: 'Forecast'},
    {url: 'slide0021.html', title: 'Summary'}
];

新元素以分配的方式增加到数组中。

a[i + j] = f(a[i], a[j]);

函数

JavaScript的函数看起来想C语言的函数一样,但JavaScript用function声明而不是类型关键字。当调用一个函数时,传递指定数目的参数不是必须的,过度的参数会被忽略,丢失的参数被赋值为undefined,这使写一个处理可选参数的函数变的很容易。

一个函数有权使用一个参数数组,这个数组包含了所有的实际上被调用传递进来的参数。这使处理一个带可变数目的参数变的很容易,例如:

function sum() {  // Take any number of parameters and return the sum
    var total = 0;
    for (var i = 0; i < arguments.length; ++i) {
        total += arguments[i];
    }
    return total;
}

JavaScript有内部函数,这与Java的内部类起了相同的作用,但是不是那么地明显。JavaScript也有也有扮演着lambda表达式的匿名函数。函数有词法上的作用域。

函数是对象的第一课,这意味着函数可以被存储在对象中,并且作为参数传递给其它函数。

定义函数

有3种方式定义一个函数:function声明,function操作符和function构造器。

function声明

function声明在当前的作用域内创建一个被命名的函数。

function name(argumentlist) block

函数可以嵌套,必须闭合,有0个或多个以逗号隔开的参数名称。block是由0个或多个{ }附着着。

function声明以function操作符形式速记:

var name = function name (argumentlist) block ;

function操作符

function操作符是一个产生一个对象的前缀操作符,它看起来和function声明很类似。

function name(argumentlist) block

名称是可选的,如果提供了,那么可以用于函数的递归调用。这也用于访问函数的对象成员(除了IE以外)。如果名称被省略了,那么它就是一个匿名函数。

函数的操作符经常用于把它分配给一个原型(prototype)。

函数的操作符也经常用于在适当的地方定义函数,比迅速地得到当写回调的时候。( The function operator can also be used to define functions in-place, which is handy when writing callbacks.)

function构造器

function构造器携带字符包含在参数和内部,从而产生一个function对象。

new Function(strings...)

不要使用这种方式,由于语言引用的惯例导致很难恰当的表达一个函数身体作为一个字符串。在字符形式中,早期的错误检查不起作用。这也是很慢的,因为编译器必须被触发每当构造器被调用的时候,并且这样浪费内存,因为每个函数需要它自己独立的实现。

对象和this

一个函数是一个对象,它可以包含成员就像其它对象一样。这就允许一个函数包含它自己的数据表,这也允许一个对象扮演一个“类”,包含构造函数和一套相关的函数。

一个方式可以是一个对象的成员,当一个函数是一个对象的成员的时候,被成为“方法”。有一个特殊的变量,叫做“this”,当一个对象的方式被调用时指向了对象。

例如:在表达式foo.bar()中,this变量就指向了对象foo,它作为了函数bar的一个额外的参数。函数bar可以使用this进入对象内部以找到有用的项目。

一个深一点的表示式do.re.mi.fa()this变量指向了对象do.re.mi,而不是对象do。在一个简单的函数调用中,this指向了没有太大用的Global Object (也叫 window)。当调用一个内部函数时,正确的行为是保护好当前this的值。

构造器

当函数被用于初始化对象的时候被叫做构造器,调用一个构造器的次序与调用一个普通的函数有一些细微的不同。一个构造器带着new前缀被调用:

new Constructor(parameters...)

根据习惯,构造器的名字的首字母使用大写的。

new前缀改变了this 的含义,不是平常的值,而是代表了“新对象”。构造器的身体将会初始化对象的成员。构造起将会返回一个新对象,除非明确的使用了return语句进行替换。

被构造的对象将会包含一个秘密的指向了构造器的prototype的成员的原型链接域。

原型(Prototype)

对象包含一个隐藏的连接属性,这个链接指向了prototype,就是构造器的实例。

当条目以点形式或索引形式从一个对象被访问的时候,如果条目没有在对象中被发现那么就检查他的链接对象,如果链接对象中也没发现,那么如果链接对象本身有一个链接对象,那么就继续检查这个链接对象,如果链接对象所有环节都检查完毕,那么就返回undefined

原型用于链接住一系列的继承

成员可以用分配的方式增加到prototype中来,这我们定义一个类Demo,它从Ancestor继承,并增加了它独有的方法foo

function Demo() {}
Demo.prototype = new Ancestor();
Demo.prototype.foo = function () {};

变量

定义变量使用var语句。当变量在函数内部定义时,var有着函数范围(function-scope)级别,这个变量不能在函数外被访问。JavaScript中没有其他的范围域粒度了,更具体的说,JavaScript中没有块域(block-scope)

任何在函数中使用但没有用var明确定义的变量,都会被假定属于全局对象。

任何没有被明确初始化的变量都会被赋值于undefined

变量不是强类型的,一个变量引用一个对象、字符串、数字、布尔值、null值或者是undefined

当函数被调用的时候会产生一套新的变量,这允许递归的调用。

闭合

函数可以被定义到其他函数中,内部函数有权使用在外部函数定义的变量,如果一个引用内部函数继续存在(例如一个回调函数),那么外部函数的变量也将继续存在。

返回值

JavaScript没有void类型,所以每个函数都必须返回一个值,除了构造器之外默认值是undefined,构造器的默认返回值是this

语句

语句包括 var, if, switch, for, while, do, break, continue, return, try, throw, and with,它们大多数与C类的语言相同。

var语句是一个或多个变量名称的列表,它们以逗号隔开,带着可选的初始化表达式。

var a, b = window.document.body;

如果var语句出现在所有的函数之外那么就声明了一个全局的变量。如果出现在一个函数内,就声明了一个局部的变量。

if语句,while语句,do语句, 和符合逻辑的操作符中,JavaScript视为falsenullundefined""(空字符串),和数字0作为假(false)。其它所有值被视为真(true)。

switch语句中的case标记可以是表达式,它们不必非得是常数,也可以是字符串。

有2种形式的for语句,第一种像这样 (init; test; inc)形式。第二种是对象重复器。

for (name in object) {
    if (object.hasOwnProperty(name)) {
        value = object[name];
    }
}

上面执行在object中的每一个name,名字产生的次序是不确定的。

语句可以有个前缀,就是一个标识符跟着一个冒号。

with语句不应该被使用

操作符

JavaScript有还算完整的操作符,大多数他们都与C类的语言工作的方式相同,但请注意一些细微的差异

+操作符用于加法和字符串联,如果任意操作数是字符串,那么进行串联,这可能会引起错误,比如说'$' + 3 + 4 会得到'$34',而不是'$7'

+可以作为一个前缀操作符把字符操作数转化为数字。

!!可以作为一个前缀操作符,转化操作数为布尔值。

&&操作符,一般被称为“逻辑与”(logical and),它也被称为guard,如果第一个操作数是falsenullundefined""(空字符串)或者数字0那么将返回第一个操作数,否则将返回第二个操作数。这提供了一个便利地测试null值的方法:

var value = p && p.name; /* The name value will
only be retrieved from p if p has a value, avoiding an error. */

||操作符一般被称为“逻辑或”(logical or),它也被称为default,如果第一个操作数是falsenullundefined""(空字符串)或者数字0, 那么将返回第二个操作数,否则返回第一个操作数。这提供了一个便利地设定默认值的方式:

value = v || 10; /* Use the value of v, but if v
doesn't have a value, use 10 instead. */

JavaScript支持一套逐位和移位的操作运算符,但却没有偶提供整型将他们应用。当一个数字操作数(一个64位浮点数)转化成32位整型的在运算之前,然后在转化回浮点数在操作在运算之后会发生什么呢?

在JavaScript中,void是一个前缀操作符,而不是一种类型,它总是返回undefined值。这仅仅有一点点价值,我提到它的原因是万一你不小心以习惯打出来它,而不会被它怪异的行为所迷惑。

typeof操作符返回一个基于它的操作数的字符串

错误产生了。

Object 'object'
Array 'object'
Function 'function'
String 'string'
Number 'number'
Boolean 'boolean'
null 'object'
undefined 'undefined'

杂目

全局对象

全局对象包含着所有的函数和所有的那些不在任何函数内定义的变量和对象,令人惊讶的是,全局变量在语言中并没有一个确切的名字。一些时候,this指向它,但大多数情况并不是的。在浏览器中windowself都是指向全局对象的全局对象成员,因此给了一个间接的对它的寻址方法。

如果一个变量可以被访问,但是没有在当前域下被找到,就会在全局对象范围查找,如果仍没有,就会返回错误。

ECMAScript规范没有谈论关于多个全局对象的可行性,或者上下文关系,但是浏览器支持这个,每个窗口都有它自己的全局对象。

分号的插入时机

这个语言的一个错误及时分号的插入,有一个技巧是用分号来终止语句。用各种开发工具去进行插入工作是合理的,没有必要为了语言的定义而让编译器去去做,请使用分号。

预留字

JavaScript对系统预留字的支持严重的过了头,这些预留字是:

abstract
boolean break byte
case catch char class const continue
debugger default delete do double
else enum export extends
false final finally float for function
goto
if implements import in instanceof int interface
long
native new null
package private protected public
return
short static super switch synchronized
this throw throws transient true try typeof
var volatile void
while with

很大一部分这些关键字并没有在JavaScript中使用,一个预留字不可以在如下的方式下被使用:

  1. 作为一个对象名字标记
  2. 作为点形式的的成员
  3. 作为函数的参数
  4. 作为一个var
  5. 作为一个绝对的全局变量
  6. 作为一个语句标号(As a statement label)

前2个限制是不可原谅的,绝对不要使用。接下来的2个的使用可以被接受,但是这样使用是非常不牢靠的。

特别声明,文章翻译于 A Survey of the JavaScript Programming Language
版权由原作者所有。

1 条评论: