W3C Group的JavaScript1.7新特性介绍

2009年5月11日星期一

W3C Group的JavaScript1.7新特性介绍

在介绍完JavaScript1.6的新特性之后,W3CGroup继续为大家介绍JavaScript1.7,JavaScript1.7带来了一系列的新特性,除了向下兼容javascript1.6之外,还包括生成器、迭代器、数组领悟、let表达式,以及解构赋值等重要特性!

浏览器支持:

Firefox2Beta1或以上版本

如何使用JavaScript1.7

如果你想使用Javascript1.7的新特性,你只需要在写Script标签的时候引入javascript1.7的版本号即可,如:

scripttype="application/javascript;version=1.7"/

注意:"yield"和"let"是javascript1.7中的两个新特性,请不要当作变量名或函数名使用!

下面我们介绍一下javascript1.7给我们带来的这些新特性!

生成器和迭代器

每当开发那些涉及到迭代算法(如对列表的迭代或对同一数据集进行相同的演算)的代码时,通常都需要声明一些变量,并在整个计算过程中都保留它们的值。一般来说,你还需要使用一个回调函数来获得迭代算法的中间值。
生成器

下面这个迭代算法计算了斐波那契数列:

functiondo_callback(num){
document.write(num+"br\n");
}

functionfib(){
vari=0,j=1,n=0;
while(n10){
do_callback(i);
vart=i;
i=j;
j+=t;
n++;
}
}

fib();

这段代码使用了一个回调函数,来机械地执行算法每一步迭代的操作。这里,每一个斐波那契数都被简单地打印在控制台上。

使用生成器和迭代器,可以提供一个更新更好的方法。现在就让我们来看看使用了生成器的斐波那契数列:

functionfib(){
vari=0,j=1;
while(true){
yieldi;
vart=i;
i=j;
j+=t;
}
}

varg=fib();
for(vari=0;i10;i++){
document.write(g.next()+"br\n");
}

包含yield关键字的函数,就是一个生成器。当你调用它时,它的真正参数就会绑定到实际的变量上,但它自身其实并不做实际的计算,而是返回一个生成器-迭代器(generator-iterator)。每调用一次该生成器-迭代器的next()方法就再执行一次迭代算法。yield关键字指定了每一步的值,就像生成器-迭代器版的return一样,指出了算法的每次迭代之间的边界。你每调用一次next(),生成器代码就从yield以下的语句处恢复。

你可以重复调用一个生成器-迭代器的next()方法来使它转动,直到到达你期望的结果条件。在这个例子中,我们只需不停地调用g.next(),就可以获得任意多的斐波那契数,直到满足我们所需要的结果个数。

从指定位置恢复生成器-迭代器

一个生成器在被调用其next()方法启动之后,你可以使用send()方法传递一个指定的的值,作为上一个yield的结果。这个生成器将返回后续yield的操作数。

你无法强制生成器从某个位置启动,在你send()一个指定的值给生成器之前,你必须先以next()启动它。
注意:有意思的是,调用send(undefined)与调用next()是等价的。不过,用任何除undefined之外的值调用send()启动一个刚刚创建的生成器将引发TypeError异常。

生成器的异常

通过调用生成器的throw()方法并传递异常值,你可以强制生成器抛出异常。此异常将从该生成器当前挂起的上下文中被抛出,就好像在当前被挂起的yield中插入了一个throwvalue语句。

如果在抛出异常的过程中没有遇到yield,那么该异常将被通过调用throw()传播,且后续的next()调用将导致StopIteration异常抛出。
关闭生成器

生成器的close()方法能强行关闭生成器自身。关闭生成器的效果如下:

1.运行这个生成器函数中活动的finally语句。
2.一旦finally语句抛出任何除StopIteration之外的异常,该异常被传播给close()方法的调用者。
3.该生成器终结。

生成器示例

这段代码执行了一个每100次循环yield一次的生成器。

vargen=generator();

functiondriveGenerator(){
if(gen.next()){
window.setTimeout(driveGenerator,0);
}else{
gen.close();
}
}

functiongenerator(){
while(isomething){


++i;

if((i%100)==0){
yieldtrue;
}
}

yieldfalse;
}

迭代器

迭代器可以让你迭代数据,是一种特殊的对象。

在常规的用法中,迭代器对象是“不可见”的,你不需要显示地操作它们。取而代之,使用JavaScript的for...in和foreach...in语句自然地在对象的键、值之上循环。

varobjectWithIterator=getObjectSomehow();

for(variinobjectWithIterator)
{
document.write(objectWithIterator[i]+"br\n");
}

如果你想实现你自己的迭代器对象,或对直接操作迭代器有另外的需求,你就需要了解next方法、StopIteration异常和__iterator__方法了。

你可以通过调用Iterator(对象名)来为一个对象创建迭代器。调用一个对象的__iterator__方法时,需要查找它的迭代器;当这个方法不存在时,创建一个默认的迭代器。默认迭代器会根据传统的for...in和foreach...in模型产出该对象的属性。也可以提供一个自定义的迭代器,只需重载__iterator__方法,使它返回你自定义的迭代器的一个实例即可。你可以使用Iterator(对象)从脚本中获取一个对象的迭代器,以避免直接访问__iterator__的属性,因为只有前者才对数组起作用。

一旦你拥有了一个迭代器,你就能够轻松地获取对象中的下一项了,调用该迭代器的next()方法即可。对象中没有余下的数据时,抛出StopIteration异常。

这里有一个直接迭代器操作的简单示例:

varobj={name:"JackBauer",username:"JackB",id:12345,agency:"CTU",region:"LosAngeles"};

varit=Iterator(obj);

try{
while(true){
print(it.next()+"\n");
}
}catch(erriferrinstanceofStopIteration){
print("Endofrecord.\n");
}catch(err){
print("Unknownerror:"+err.description+"\n");
}

该程序的输出如下:

name,JackBauer
username,JackB
id,12345
agency,CTU
region,LosAngeles
Endofrecord.

你可以有选择性地指定创建迭代器的第二个参数,它是一个指示你调用其next()方法时是否需要返回keys的布尔值。这个参数被作为第一个参数传入给用户定义的__iterator__函数。在上面的例子中,把varit=Iterator(obj);改为varit=Iterator(obj,true);后的输出如下:

name
username
id
agency
region
Endofrecord.

对于每一种情况,返回数据的实际顺序只基于实现,数据的顺序是不能保证的。

迭代器是一种扫描对象中的数据的简便的方式。被扫描的对象的内容中可能包含你没有注意到的数据,所以当你需要保护那些你的应用程序并不期望的数据时,迭代器会显得非常有用。
数组领悟

数组领悟是一种对生成器的运用,展示了一种初始化数组的强大而方便的方式。例如:

functionrange(begin,end){
for(leti=begin;iend;++i){
yieldi;
}
}

range()是一个返回从begin到end之间值的生成器。知道了这些,我们就可以这样使用它:

varten_squares=[i*iforeach(iinrange(0,10))];

这段代码预初始化了一个新数组ten_squares,包含了范围在0..9之间的值的平方数。

在初始化数字时你可以使用任何限制条件。如果你想要初始化一个包含0到20之间偶数的数组,你可以使用下面的代码:

varevens=[iforeach(iinrange(0,21))if(i%2==0)];

在JavaScript1.7之前,这个工作必须要写出类似这样的代码:

varevens=[];
for(vari=0;i=20;i++){
if(i%2==0)
evens.push(i);
}

一旦你熟悉了数组领悟,你就会发现它不但紧凑,而且易于阅读。
作用域规则

数组领悟被暗含的块包围着,包含了方括号内所有的东西,类似隐含的let声明。

更多细节。

let的块作用域

有好几种使用let来管理数据和函数的块作用域的方式:

*let语句提供了一种在块作用域中联系值与变量、常量、函数的途径,且不会影响到块之外的同名变量。
*let表达式使你能在仅有一个表达式所限定的作用域内建立变量。
*let定义在一个块中,定义变量、常量和函数,并把它们的作用域限制在块内。从语法上看,let定义很类似var。
*你也可以使用let建立只存在于for循环上下文中的变量。

let语句

let语句为变量、常量和函数提供了本地作用域,其工作是把零个或多个变量绑定到单个代码块的词法作用域上,这个过程与块语句完全相同。特别要注意的是,在let语句内部使用var声明的变量,其作用域和它在let语句外被声明时相同,这样的变量仍然具有函数作用域。

例如:

varx=5;
vary=0;

let(x=x+10,y=12){
print(x+y+"\n");
}

print((x+y)+"\n");

这段程序的输出是:

27
5

此规则对任何JavaScript中的代码块适用。
使用let声明,可以给代码块建立属于它自己的本地变量。

注意:使用let语句这个语法时,跟在let后面的括号是必需的,丢掉它们将导致一个语法错误。

作用域规则

用let定义的变量的作用域是let块本身,覆盖了任何let块内部的块,除非这些内部块定义了同名的变量。

let表达式

你可以使用let建立作用域仅在一个表达式中的变量。

varx=5;
vary=0;
document.write(let(x=x+10,y=12)x+y+"br\n");
document.write(x+y+"br\n");

输出如下:

27
5

在这个例子中,对x的值x+10的绑定和对y的值12的绑定的作用域被限制于表达式x+y。
作用域规则

let表达式:

let(声明)表达式

在表达式周围创建了一个隐含的块。

let定义

let关键字也可被用于在块中定义变量、常量和函数。

if(xy)
{
letgamma=12.7+y;
i=gamma*x;
}

使用内部函数时,let语句,let表达式和let定义常常能使代码更加清晰。

varlist=document.getElementById("list");
for(vari=1;i=5;i++)
{
varitem=document.createElement("LI");
item.appendChild(document.createTextNode("Item"+i));
letj=i;
item.onclick=function(ev){
alert("Item"+j+"isclicked.");
};
list.appendChild(item);
}

上面的例子能像我们所期望的那样工作,因为五个匿名函数的实例引用了变量j的不同实例。请注意,如果你把let换成var,或者移除变量j并仅在内部函数中使用变量i,这段代码就不能正常工作了。

作用域规则

用let定义的变量的作用域覆盖了它们被定义的块,已经任何没有重定义它们的子块。从这一点来看,let的效果非常类似var。主要的区别在于用var定义的变量的作用域是整个闭合函数:

functionvarTest(){
varx=31;
if(true){
varx=71;//同一个变量!
alert(x);//71
}
alert(x);//71
}

functionletTest(){
letx=31;
if(true){
letx=71;//不是同一个变量
alert(x);//71
}
alert(x);//31
}

=右侧的表达式处于块的内部。这与let-表达式和let-语句的作用域不同:

functionletTests(){
letx=10;

//let-语句
let(x=x+20){
alert(x);//30
}

//let-表达式
alert(let(x=x+20)x);//30

//let-定义
{
letx=x+20;//这里的x的求值结果为undefined
alert(x);//undefined+20==NaN
}
}

在程序和类中,let不创建global对象上的属性,这与var的做法不同。但let会在一个隐含的块中创建属性,使其它上下文中的语句可以被求值。这就意味着,let不会覆写前面用var定义的变量。例如:

varx='global';
letx=42;
document.write(this.x+"br\n");

这段代码将输出"global"而不是"42"。

隐含的块指的不是那种被括弧括起的块,它只由JavaScript引擎隐式创建。

由()执行的let与var不同,它不会在变量对象(活动对象或在最内层绑定的组成部分)上创建属性。为使其它上下文中的语句可以被求值,它会在一个隐含的块中创建属性,这是()对于程序的操作与前述规则共同作用的结果。

换句话说,当你使用()执行代码时,这段代码就被当作一段独立程序处理,拥有自己的包围其代码的隐含的块。

for循环中的let-界定变量

你可以使用let关键字在for循环的作用域内绑定本地变量,和使用var类似。

**添加对象**
vari=0;
for(leti=i;i10;i++)
document.write(i+"br\n");

for(let[name,value]inobj)
document.write("Name:"+name+",Value:"+value+"br\n");

作用域规则

for(let表达式1;表达式2;表达式3)语句

在这个例子中,表达式1,表达式2,表达式3和语句都被闭合在一个隐含的块中,其中包含了用letexpr1声明的本地变量。上面例子中的第一个循环示范了这个用法。

for(let表达式1in表达式2)语句
foreach(let表达式1in表达式2)语句

在这两个例子中,都存在包含了每个statement的隐含的块。上面例子中的第二个循环展示了这里的第一种用法。
解构赋值

解构赋值使用了一种反映数组常量和对象常量结构的语法,使得从数组或对象中抽取数据成为可能。

通过使用对象常量和数组常量表达式,你可以轻松地按需创建成包的数据,然后以任意你想要的方式使用它们。你甚至可以从函数中返回它们。

而你使用解构赋值最重要的一件事就是,你可以从一个语句中读取整个结构。在这一节的余下的部分中会给出很多例子,你将看到你可以使用的很多有趣的功能。

解构赋值这种能力与Perl,Python等语言所显示出的特性很类似。
示例

用例子就可以很好地诠释解构赋值,所以这里几乎没有什么需要你通过阅读来学习的内容。

避免临时变量

你可以用解构赋值来交换两个变量的值。例如:

vara=1;
varb=3;

[a,b]=[b,a];

这段代码执行之后,b为1,a为3。不使用解构赋值的话,这个工作就需要一个临时变量。(当然了,在某些低级语言中,可以使用XOR-交换技巧)

与此类似,解构赋值也可以被用于三个或更多变量之间的轮换:

vara='o';
varb="spanstyle='color:green;'o/span";
varc='o';
vard='o';
vare='o';
varf="spanstyle='color:blue;'o/span";
varg='o';
varh='o';

for(lp=0;lp40;lp++)
{[a,b,c,d,e,f,g,h]=[b,c,d,e,f,g,h,a];
document.write(a+''+b+''+c+''+d+''+e+''+f+''+g+''+h+''+"br/");}

这段代码执行之后,将显示一个可视化的、彩色的变量轮换过程。

回到我们上文中的菲波那契数生成器的例子,我们可以使用一个成组赋值语句来计算"i"和"j"的新值,从而消除临时变量"t"。

functionfib(){
vari=0,j=1;
while(true){
yieldi;
[i,j]=[j,i+j];
}
}

varg=fib();
for(leti=0;i10;i++)
print(g.next());

多值返回

有了解构赋值之后,函数就可以返回多个值了。虽然在以前也可以从函数中返回数组,但解构赋值为此增加了极大的便利。

functionf(){
return[1,2];
}

正如你所看到的,用方括号括住所有要返回的值,使它们看上去像是数组,这就是多值返回。在上面的例子中,f()返回[1,2]作为其输出。

vara,b;
[a,b]=f();
document.write("Ais"+a+"Bis"+b+"br\n");

命令[a,b]=f()使用函数结果给方括号中的变量依次赋值。a被设为1,b被设为2。

你也可以把这些返回值恢复成一个数组:

vara=f();
document.write("Ais"+a);

在这个例子中,a是一个包含值1和2的数组。

在对象中循环

你也可以使用解构赋值把数据从对象中倾倒出来:

letobj={width:3,length:1.5,color:"orange"};

for(let[name,value]inIterator(obj)){
document.write("Name:"+name+",Value:"+value+"br\n");
}

这段代码循环了对象obj中的所有键值对,并显示出它们的名字和值。这个例子的输出如下:

Name:width,Value:3
Name:length,Value:1.5
Name:color,Value:orange

在JavaScript1.7中,obj周围的Iterator()不是必须的,不过JavaScript1.8就需要了,这样可以允许对数组的解构赋值(见bug366941)。

在对象组成的数组中循环

你可以循环一个由对象组成的数组,并倾倒出每个对象中你感兴趣的域。

varpeople=[
{
name:"MikeSmith",
family:{
mother:"JaneSmith",
father:"HarrySmith",
sister:"SamanthaSmith"
},
age:35
},
{
name:"TomJones",
family:{
mother:"NorahJones",
father:"RichardJones",
brother:"HowardJones"
},
age:25
}
];

foreach(let{name:n,family:{father:f}}inpeople){
document.write("Name:"+n+",Father:"+f+"br\n");
}

这段代码用name域给n赋值,用family.father域给f赋值,然后打印它们。这个过程被作用于people数组中的每一个对象。输出如下:

Name:MikeSmith,Father:HarrySmith
Name:TomJones,Father:RichardJones

忽略某些返回值

你也可以忽略掉那些你不感兴趣的返回值:

functionf(){
return[1,2,3];
}

var[a,,b]=f();
document.write("Ais"+a+"Bis"+b+"br\n");

这段代码运行之后,a为1,b为3,2这个值被忽略了。你也可以用这种方式忽略多个,甚至是全部返回值。例如:

[,,,]=f();

从正则表达式匹配结果中倾倒值

当正则表达式的exec()方法找到一个匹配时,它会返回一个数组,第一项是目标字符串中被匹配的完整部分,后面是被正则表达式中被括号括起的组所匹配的数段字符串。解构赋值使你能轻松地倾倒出该数组中的各个部分,并忽略掉其中不需要的匹配。

//一个简单的正则表达式,用来匹配http/https/ftp型的URL
varparsedURL=/^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
if(!parsedURL)
returnnull;
var[,protocol,fullhost,fullpath]=parsedURL;

0 评论:

发表评论