在介绍完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 评论:
发表评论