大连仟亿科技
客服中心
  • 电话
  • 电话咨询:0411-39943997
  • 手机
  • 手机咨询:15840979770
    手机咨询:13889672791
网络营销 >更多
您现在的位置:仟亿科技 > 新闻中心 > 常见问题

JavaScript需要Blocks

作者:billionnet 发布于:2012/5/4 18:15:49 点击量:

 

常常会遇到有人将Ruby的区块(Blocks)看作相当于JavaScript的“firstclassfunctions”的误解。由于传递功能,尤其是当你可以创建匿名的传递功能,这是非常强大的。事实上,JavaScript和Ruby有一个机制使其自然会认为等值。

人们在谈到为什么Ruby的区块不同于Python的函数时,通常会讲到一些关于Ruby和JavaScript的匿名分享,但Python没有。初看之下,一个Ruby区块就是一个“匿名函数”(或俗称一个“封装”),正如JavaScript函数就是其中之一。

作为一个早期的Ruby/JavaScript开发者,无可否认我也有过这样的观点分享。错过一个重要的细节,对结果会产生较大影响。这个原理常被称为“Tennent’sCorrespondencePrinciple”,这条原理说:“Foragivenexpressionexpr,lambdaexprshouldbeequivalent.”这就是被称为抽象的原则,因为这意味着,用“区块”的方法很容易重构通用代码。例如,常见文件资源管理的情况。试想在Ruby中,File.open块形式是不存在的,你会看到以下代码:

 

begin

f=File.open(filename,"r")

#dosomethingwithf

ensure

f.close

end

 

在一般情况下,“区块”代码有着相同的开始和结束编码、不同的内部编码。现在重构这段代码,你会这样写:

 

defread_file(filename)

f=File.open(filename,"r")

yieldf

ensure

f.close

end

 

代码中的模式与重构实例:

 

read_file(filename)do|f|

#dosomethingwithf

End

 

重要的是重构之后区块内的代码和以前一样。在以下情况时,我们可以重申抽象原则的对应原理:

 

#dosomethingwithf

 

应相等于:

 

do

#dosomethingwith

end

 

乍一看,在Ruby和JavaScript中确实如此。例如,假设你正在使用的文件打印它的mtime。您可以轻松地重构相当于在JavaScript:

 

try{

//imaginaryJSfileAPI

varf=File.open(filename,"r");

sys.print(f.mtime);

}finally{

f.close();

}

 

到这里:

 

read_file(function(f){

sys.print(f.mtime);

});

 

事实上,这样的情况往往给人错误的印象,Ruby和JavaScript有同样用匿名函数重构常用功能的能力。

不过,再来一个稍微复杂一些的例子。我们首先在Ruby中编写一个简单的类,计算文件的mtime和检索它的正文:

 

classFileInfo

definitialize(filename)

@name=filename

end

#calculatetheFile's+mtime+

defmtime

f=File.open(@name,"r")

mtime=mtime_for(f)

return"tooold"ifmtime<(Time.now-1000)

puts"recent!"

mtime

ensure

f.close

end

#retrievethatfile's+body+

defbody

f=File.open(@name,"r")

f.read

ensure

f.close

end

#ahelpermethodtoretrievethemtimeofafile

defmtime_for(f)

File.mtime(f)

end

end

 

我们可以用区块很容易地重构这段代码:

 

classFileInfo

definitialize(filename)

@name=filename

end

#refactorthecommonfilemanagementcodeintoamethod

#thattakesablock

defmtime

with_filedo|f|

mtime=mtime_for(f)

return"tooold"ifmtime<(Time.now-1000)

puts"recent!"

mtime

end

end

defbody

with_file{|f|f.read}

end

defmtime_for(f)

File.mtime(f)

end

private

#thismethodopensafile,callsablockwithit,and

#ensuresthatthefileisclosedoncetheblockhas

#finishedexecuting.

defwith_file

f=File.open(@name,"r")

yieldf

ensure

f.close

end

end

 

同样地,需要注意的重点是,我们构建区块却并不改变它的内部代码。但不幸的是,这个相同情况的例子无法在JavaScript中正常工作。让我们在JavaScript中来写等同的FileInfo类:

 

//constructorfortheFileInfoclass

FileInfo=function(filename){

this.name=filename;

};

FileInfo.prototype={

//retrievethefile'smtime

mtime:function(){

try{

varf=File.open(this.name,"r");

varmtime=this.mtimeFor(f);

if(mtime

return"tooold";

}

sys.print(mtime);

}finally{

f.close();

}

},

//retrievethefile'sbody

body:function(){

try{

varf=File.open(this.name,"r");

returnf.read();

}finally{

f.close();

}

},

//ahelpermethodtoretrievethemtimeofafile

mtimeFor:function(f){

returnFile.mtime(f);

}

};

 

如果我们试图将其转换成一个接受重复函数的代码,那mtime方法看起来将是:在这里有两个非常普遍的问题。首先是上下文改变了。我们可以通过允许绑定第二参数,但这意味着每次重构时需要确认并通过一个变量传递参数,就是说这一情况会在因为缺乏JavaScript信任组件时而出现。

 

function() {

// refactor the common file management code into a method

// that takes a block

this.withFile(function(f) {

var mtime = this.mtimeFor(f);

if (mtime < new Date() - 1000) {

return "too old";

}

sys.print(mtime);

});

}

 

这很烦人,更棘手的还在于,它是从内部返回结果而不是从函数外部。这个真实的结果违反了抽象原则中的对应原理。相反,在函数中用区块方法毫不费力地重构具有相同开始和结束的代码时,JavaScript库作者需要考虑使用者对API处理嵌套函数时进行的一些操作。作为一个JavaScript库资源的编写者和使用者看来,这提供了一个很好的基于区块的API。

迭代和回调

值得注意的是,区块lambda函数接受功能调用的案例包括迭代器、同步与互斥、资源管理(如区块形式的File.open)。

使用函数作为回调时,关键字不再有意义。从一个已经返回的函数返回是什么意思?在这种情况下,通常涉及回调函数lambda表达式做出了很大的意义。在我看来,这解释了为什么JavaScript事件触发代码,涉及了大量的回调。

由于这些问题,ECMA工作组负责的ECMAScript,TC39,正在考虑加入块lambda表达式语言。这将意味着,上面的例子可重构:

 

FileInfo=function(name){

this.name=name;

};

FileInfo.prototype={

mtime:function(){

//usetheproposedblocksyntax,`{|args|}`.

this.withFile{|f|

//inblocklambdas,+this+isunchanged

varmtime=this.mtimeFor(f);

if(mtime

//blocklambdasreturnfromtheirnearestfunction

return"tooold";

}

sys.print(mtime);

}

},

body:function(){

this.withFile{|f|f.read();}

},

mtimeFor:function(f){

returnFile.mtime(f);

},

withFile:function(block){

try{

varf=File.open(this.name,"r");

block(f);

}finally{

f.close();

}

}

};

 

TC39并没有实质性改变这个例子,并且要注意区块lambda表达式自动返回他们的后面一个语句。

经验显示,Smalltalk和Ruby不需要理解一种语言可怕的对应原理,满足它获得自己想要的结果。“迭代”概念并不内置到语言,而是被自然块定义的结果。这使得Ruby以及其他常用语言的开发者可建立自定义的丰富、内置的迭代设置。作为一个JavaScript实践者,我经常碰到的情况是,用一个for循环比使用forEach更为简单明了。

业界人士观点

munificent:Inordertohavealanguagewithreturn(andpossiblysuperandothersimilarkeywords)thatsatisfiesthecorrespondenceprinciple,thelanguagemust,likeRubyandSmalltalkbeforeit,haveafunctionlambdaandablocklambda.Keywordslikereturnalwaysreturnfromthefunctionlambda,eveninsideofblocklambdasnestedinside.Incaseyouwanttogetyourgoogle/wikipediaon,whatKatzistalkingabouthereisa"non-localreturn".

很多评论详细请点击这里>>

ericbb:Alternateformulationwithhypotheticalshift/reset(delimitedcontinuationsupport)andblocksthatreturnthesamewayfunctionsdo:

 

mtime:function(){

returnreset{

varmtime=shift(succeed){

this.withFile({|f|

varmtime=this.mtimeFor(f);

if(mtime

return"tooold";

}

returnsucceed(mtime);

});

};

sys.print(mtime);

return"youngenough";

};

},

Thesucceedfunctionisafirstclass,indefinite-extentfunctionequivalentto:

 

 

function(mtime){

sys.print(mtime);

return"youngenough";

}

 

  



分享到:


评论加载中...
内容:
评论者: 验证码:
  

Copyright@ 2011-2017 版权所有:大连仟亿科技有限公司 辽ICP备11013762-1号   google网站地图   百度网站地图   网站地图

公司地址:大连市沙河口区中山路692号辰熙星海国际2215 客服电话:0411-39943997 QQ:2088827823 42286563

法律声明:未经许可,任何模仿本站模板、转载本站内容等行为者,本站保留追究其法律责任的权利! 隐私权政策声明