﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>博客园-Cat in dotNET</title><link>http://www.cnblogs.com/cathsfz/</link><description /><language>zh-cn</language><lastBuildDate>Sun, 05 Jul 2009 23:46:06 GMT</lastBuildDate><pubDate>Sun, 05 Jul 2009 23:46:06 GMT</pubDate><ttl>60</ttl><item><title>让 JavaScript 轻松支持函数重载 (Part 2 - 实现)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/07/02/1515566.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Thu, 02 Jul 2009 07:51:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/07/02/1515566.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1515566.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/07/02/1515566.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1515566.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1515566.html</trackback:ping><description><![CDATA[ <p>在<a href="http://www.cnblogs.com/cathsfz/archive/2009/07/02/1515188.html">上一篇文章</a>里，我们设计了一套能在JavaScript中描述函数重载的方法，这套方法依赖于一个叫做Overload的静态类，现在我们就来看看如何实现这个静态类。</p>

<h4>识别文本签名</h4>

<p>我们先来回顾一下上一篇文章中提到的Overload用例：</p>

<p><code> var extend = Overload<br />&nbsp; .add("*, ...",<br />&nbsp; &nbsp; function(target) { })<br />&nbsp; .add("Boolean, *, ...",<br />&nbsp; &nbsp; function(deep, target) { });</code></p>

<p>我们允许用户输入一个字符串，表示某一个重载的签名。在用户调用函数时，我们需要拿着用户输入的参数实例去跟签名上的每一个参数类型作比较，因此我们需要先把这个字符串转换为类型数组。也就是说，字符串"Boolean, Number, Array"应该转换为数组[Boolean, Number, Array]。</p>

<p>在进行转换之前，我们先要考虑处理两个特殊类型，就是代表任意类型的"*"，和代表任意数量的"..."。我们可以为它们定义两个专有的类型，以便在Overload内对它们做出特殊的兼容性处理：</p>

<p><code>Overload.Any = function() {};<br />Overload.More = function() {};</code></p>

<p>在有了这两个类型之后，字符串"Boolean, *, ..."就会被正确转换为数组[Boolean, Overload.Any, Overload.More]。由于Overload.Any和Overload.More都是函数，自然也都可以看做类型。</p>

<p>在这两个类型得到正确处理后，我们就可以开始编写识别文本签名的转换函数了：</p>

<p><code>if (signature.replace(/(^\s+|\s+$)/ig, "") == "") {<br />&nbsp; signature = [];<br />} else {<br />&nbsp; signature = signature.split(",");<br />&nbsp; for (var i = 0; i &lt; signature.length; i++) {<br />&nbsp; &nbsp; var typeExpression = signature[i].replace(/(^\s+|\s+$)/ig, "");<br />&nbsp; &nbsp; var type = null;<br />&nbsp; &nbsp; if (typeExpression == "*") {<br />&nbsp; &nbsp; &nbsp; type = Overload.Any;<br />&nbsp; &nbsp; } else if (typeExpression == "...") {<br />&nbsp; &nbsp; &nbsp; type = Overload.More;<br />&nbsp; &nbsp; } else {<br />&nbsp; &nbsp; &nbsp; type = eval("(" + typeExpression + ")");<br />&nbsp; &nbsp; }<br />&nbsp; &nbsp; signature[i] = type;<br />&nbsp; }<br />}</code></p>

<p>我想这段代码相当容易理解，因此就不再解释了。我第一次写这段代码时忘记写上面的第一个if了，导致空白签名字符串""无法被正确识别为空白签名数组[]，幸好我的unit test代码第一时间发现了这个缺陷。看来编写unit test代码还是十分重要的。</p>

<h4>匹配函数签名</h4>

<p>在我们得到函数签名的类型数组后，我们就可以用它和输入参数的实例数组做匹配了，以此找出正确的重载。在讨论具体如何匹配函数签名以前，我们先来看看<a href="http://msdn.microsoft.com/en-us/library/aa691336%28VS.71%29.aspx">C#</a>或<a href="http://msdn.microsoft.com/en-us/library/tb18a48w.aspx">VB.NET</a>这样的语言是如何处理函数重载匹配的。一般语言进行函数重载匹配的流程都是这样子的：<ol><li><strong>参数个数</strong> - 参数个数不对的重载会被排除掉</li><li><strong>参数类型</strong> - 参数类型无法隐式转换为签名类型的会被排除掉</li><li><strong>匹配个数</strong> - 排除完毕后，剩下匹配的签名个数不同处理方法也不同<ul><li><strong>0个匹配</strong> - 没有命中的匹配</li><li><strong>1个匹配</strong> - 这个就是命中的匹配</li><li><strong>2个或以上的匹配</strong> - 如果能在这些匹配中找出一个最佳匹配，那就命中最佳匹配；否则不命中任何匹配</li></ul></ol>在这一节里面，我们先处理流程中的前两个步骤，把参数个数或参数类型不一致的签名去掉：</p>

<p><code>var matchSignature = function(argumentsArray, signature) {<br />&nbsp; if (argumentsArray.length &lt; signature.length) {<br />&nbsp; &nbsp; return false;<br />&nbsp; } else if (argumentsArray.length > signature.length && !signature.more) {<br />&nbsp; &nbsp; return false;<br />&nbsp; }<br />&nbsp; for (var i = 0; i &lt; signature.length; i++) {<br />&nbsp; &nbsp; if (!(signature[i] == Overload.Any<br />&nbsp; &nbsp; &nbsp; || argumentsArray[i] instanceof signature[i]<br />&nbsp; &nbsp; &nbsp; || argumentsArray[i].constructor == signature[i])) {<br />&nbsp; &nbsp; &nbsp; &nbsp; return false;<br />&nbsp; &nbsp; }<br />&nbsp; }<br />&nbsp; return true;<br />};</code></p>

<p>为了作长度对比，我们需要在这个函数外对表示任何参数个数的"..."作一下特殊处理：</p>

<p><code>if (signature[signature.length - 1] == Overload.More) {<br />&nbsp; signature.length = signature.length - 1;<br />&nbsp; signature.more = true;<br />}</code></p>

<p>这一段代码将会整合到第一节的转换函数末端，以便matchSignature函数能够轻易判断出参数与签名是否匹配。在最理想的情况下，我们对输入参数类型匹配到0个或1个重载，这样我们很容易就判断出命中哪个重载了。但如果有2个或以上的重载匹配，那么我们就要从中挑选一个最优的了，这正是下一节要讨论的内容。</p>

<h4>处理多重匹配</h4>

<p>关于C#是如何从多重匹配中选出较为匹配的重载，可以看C# Language Specification中的<a href="http://msdn.microsoft.com/en-us/library/aa691338%28VS.71%29.aspx">有关章节</a>。我觉得通过三个简单的例子就能说明问题：</p>

<p><code> long Sum(int x, int y) { return x + y; }<br /> long Sum(long x, long y) { return x + y; }<br />Sum(0, 1);</code></p>

<p>由于0和1这两个参数会被编译器理解为int类型，对于第1个重载它们都不用进行类型转换，都与第2个重载它们都要进行类型转换，因此第1个重载较优。</p>

<p><code> long Sum(int x, long y) { return x + y; }<br /> long Sum(long x, int y) { return x + y; }<br />Sum(0, 1);</code></p>

<p>在第1个参数上，第1个重载较优；在第2个参数上，第2个重载较优。在这种情况下，任何一个重载都不优于另一个，找不到较优重载编译器就报错。</p>

<p><code> long Sum(int x, int y) { return x + y; }<br /> long Sum(int x, long y) { return x + y; }<br /> long Sum(long x, int y) { return x + y; }<br />Sum(0, 1);</code></p>

<p>在第1个参数上，第1个重载优于第3个重载，于第2个重载无异；在第2个参数上，第1个重载优于第2个重载，于第3个重载无异。尽管第2个重载于第3个重载分不出个优劣来，但我们可以确定第1个重载比它们都要好，因此编译器选择了第1个重载。</p>

<p>假设我们有一个overloadComparator的比较函数，可以比较任意两个签名之间的优劣，我们需要对签名仅仅两两比较，以找出最优重载吗？事实上是不需要的，我们可以利用Array的sort方法，让它调用overloadComparator进行排序，排序后再验证前两名的关系就可以了&#8212;&#8212;如果并列，则不命中任何一个；如果有先后之分，则命中第一个。</p>

<p>具体的overloadComparator代码就不在这里给出了，它依赖于另一个名为inheritanceComparator的比较函数来对比两个签名的参数类型哪一个更贴实际传入的参数类型，里面用到了一种比较巧妙的方法来判断两个类型是否为继承关系，以及是谁继承自谁。</p>

<h4>小结</h4>

<p>现在我们有了一个JavaScript的函数重载库，完整代码请看这里：<a href="http://www.cnblogs.com/cathsfz/articles/1515479.html">函数重载库Overload</a>。希望这个库能有效帮助大家提升JavaScript代码的可读性，降低大型Ajax项目的维护成本。如果大家希望将来继续读到类似的JavaScript开发模式相关的文章，不妨考虑订阅我的博客：</p><ul><li><a href="http://dotnet.catchen.biz/">Cat in dotNET</a> (<a href="http://feedproxy.google.com/CatChen/dotNET">feed</a>)</li><li><a href="http://chinese.catchen.biz/">Cat in
Chinese</a> (<a
href="http://feedproxy.google.com/CatChen/Chinese">feed</a>)</li></ul>
 <img src ="http://www.cnblogs.com/cathsfz/aggbug/1515566.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47989/" target="_blank">Twitter无处不在 魔兽世界Twitter发送器插件发布</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>让 JavaScript 轻松支持函数重载 (Part 1 - 设计)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/07/02/1515188.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Thu, 02 Jul 2009 01:18:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/07/02/1515188.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1515188.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/07/02/1515188.html#Feedback</comments><slash:comments>11</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1515188.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1515188.html</trackback:ping><description><![CDATA[<h4>JavaScript支持重载吗？</h4>
<p>JavaScript支持函数重载吗？可以说不支持，也可以说支持。说不支持，是因为JavaScript不能好像其它原生支持函数重载的语言一样，直接写多个同名函数，让编译器来判断某个调用对应的是哪一个重载。说支持，是因为JavaScript函数对参数列表不作任何限制，可以在函数内部模拟对函数重载的支持。</p>
<p>实际上，在很多著名的开源库当中，我们都可以看到函数内部模拟重载支持的设计。例如说jQuery的<a href="http://docs.jquery.com/Utilities/jQuery.extend">jQuery.extend</a>方法，就是通过参数类型判断出可选参数是否存在，如果不存在的话就对参数进行移位以确保后面的逻辑正确运行。我相信很多人在写JavaScript时也写过类似的代码，以求为功能丰富的函数提供一个（或多个）简单的调用入口。</p>
<p>不过做种做法一个根本的问题，那就是违反了<a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>原则。每个支持重载的函数内部都多出来一段代码，用于根据参数个数和参数类型处理重载，这些代码暗含着重复的逻辑，写出来却又每一段都不一样。此外，这些代码要维护起来也不容易，因为阅读代码时你并不能一眼看出函数支持的几种重载方式是什么，要对重载做出维护自然也困难。</p>
<h4>描述重载入口的DSL</h4>
<p>我希望能够在JavaScript中以一种简单的方式来描述重载入口。最好就如同在其它语言中一样，使用函数签名来区分重载入口，因为我认为函数签名就是这方面最好的<a href="http://en.wikipedia.org/wiki/Domain_specific_language">DSL</a>。我假想中最符合JavaScript语法的重载入口描述DSL应该是这样子的：</p>
<p><code>var sum = new Overload();<br />
sum.add("Number, Number",<br />
&nbsp; function(x, y) { return x + y; });<br />
sum.add("Number, Number, Number",<br />
&nbsp; function(x, y, z) { return x + y + z; });</code></p>
<p>在描述好重载入口与对应函数体后，对sum函数的调用应该是这样子的：</p>
<p><code>sum(1, 2);<br />
sum(1, 2, 3);</code></p>
<p>上述代码在我看来非常清晰，也非常容易维护&#8212;&#8212;你可以一眼看得出重载入口的签名，并且要修改或者增加重载入口都是很容易的事情。但是我们遇到了一个问题，那就是JavaScript里面的函数是不能new出来的，通过new Overload()获得的对象一定不能被调用，为此我们只能把Overload做成一个静态类，静态方法返回的是Function实例：</p>
<p><code>var sum = Overload<br />
&nbsp; .add("Number, Number",<br />
&nbsp; &nbsp; function(x, y) { return x + y; })<br />
&nbsp; .add("Number, Number, Number",<br />
&nbsp; &nbsp; function(x, y, z) { return x + y + z; });</code></p>
<h4>必要的重载入口支持</h4>
<p>想象一下，有哪些常见的JavaScript函数入口是用上述DSL无法描述的？我所知道的有两种：</p>
<h5>任意类型参数</h5>
<p>假想我们要写一个each函数，对于Array就迭代它的下标，对于其它类型就迭代它的所有成员，这两个函数入口的参数列表如何声明？如果用C#，我们会如此描述两个函数入口：</p>
<p><code>void Each(IEnumerable iterator) { }<br />
void Each(object iterator) { }</code></p>
<p>然而在JavaScript当中，Object不是一切类型的基类，(100) instanceof Object的结果为false，所以我们不能用Object来指代任意类型，必须引入一个新的符号来指代任意类型。考虑到这个符号不应该与任何可能存在的类名冲突，所以我选择了用"*"来表示任意类型。上述C#代码对应的JavaScript应该是这样子的：</p>
<p><code>var each = Overload<br />
&nbsp; .add("Array",<br />
&nbsp; &nbsp; function(array) { })<br />
&nbsp; .add("*",<br />
&nbsp; &nbsp; function(object) { });</code></p>
<h5>任意数量参数</h5>
<p>在JavaScript的函数里面，要求支持任意数量参数是很常见的需求，相信使用率比C#里面的params关键字要多得多。在我们之前制定的规则当中，这也无法描述的，因此我们要引入一个不和类名冲突的符号来表示C#中的params。我选择了用"..."表示params，意思是这里出现任意多个参数都是可以接受的。让我们看看jQuery.extend的重载应该如何描述：</p>
<p><code> var extend = Overload<br />
&nbsp; .add("*, ...",<br />
&nbsp; &nbsp; function(target) { })<br />
&nbsp; .add("Boolean, *, ...",<br />
&nbsp; &nbsp; function(deep, target) { });</code></p>
<h4>小结</h4>
<p>在这篇文章当中，我们尝试设计出一种适用于JavaScript且易读易维护的函数重载写法。在下一篇文章当中，我们将会尝试编写Overload类，以实现这一设计。如果你不希望错过的话，欢迎订阅：</p>
<ul>
     <li><a href="http://dotnet.catchen.biz/">Cat in dotNET</a> (<a href="http://feedproxy.google.com/CatChen/dotNET">feed</a>)</li>
     <li><a href="http://chinese.catchen.biz/">Cat in
Chinese</a> (<a
href="http://feedproxy.google.com/CatChen/Chinese">feed</a>)</li>
</ul>
<img src ="http://www.cnblogs.com/cathsfz/aggbug/1515188.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47989/" target="_blank">Twitter无处不在 魔兽世界Twitter发送器插件发布</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>写个 JavaScript 异步调用框架 (Part 6 - 实例 &amp;amp; 模式)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/07/01/1514983.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Wed, 01 Jul 2009 13:59:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/07/01/1514983.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1514983.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/07/01/1514983.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1514983.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1514983.html</trackback:ping><description><![CDATA[<p>我们用了5篇文章来讨论如何编写一个JavaScript异步调用框架（<a href="http://www.cnblogs.com/cathsfz/archive/2009/05/06/1450332.html">问题 &amp; 场景</a>、<a href="http://www.cnblogs.com/cathsfz/archive/2009/05/07/1451200.html">用例设计</a>、<a href="http://www.cnblogs.com/cathsfz/archive/2009/05/07/1451937.html">代码实现</a>、<a href="http://www.cnblogs.com/cathsfz/archive/2009/05/09/1452875.html">链式调用</a>、<a href="http://www.cnblogs.com/cathsfz/archive/2009/06/30/1514339.html">链式实现</a>），现在是时候让我们看一下在各种常见开发情景中如何使用它了。</p>
<h4>封装Ajax</h4>
<p>设计Async.Operation的最初目的就是解决Ajax调用需要传递callback参数的问题，为此我们先把Ajax请求封装为Async.Operation。我在这里使用的是jQuery，当然无论你用什么基础库，在使用Async.Operation时都可以做这种简单的封装。</p>
<p><code>var Ajax = {};<br />
<br />
Ajax.get = function(url, data) {<br />
&nbsp; var operation = new Async.Operation();<br />
&nbsp; $.get(url, data, function(result) { operation.yield(result); }, "json");<br />
&nbsp; return operation;<br />
};<br />
<br />
Ajax.post = function(url, data) {<br />
&nbsp; var operation = new Async.Operation();<br />
&nbsp; $.post(url, data, function(result) { operation.yield(result); }, "json");<br />
&nbsp; return operation;<br />
};</code></p>
<p>在我所调用的服务器端API中，只需要GET和POST，且数据都为JSON，所以我就直接把jQuery提供的其它Ajax选项屏蔽掉了，并设置数据类型为JSON。在你的项目当中，也可以用类似的方式将Ajax封装为若干仅仅返回Async.Operation的方法，将jQuery提供的选项都封装在Ajax这一层内，不再向上层暴露这些选项。</p>
<h4>调用Ajax</h4>
<p>把Ajax封装好后，我们就可以开始专心写业务逻辑了。</p>
<p>假设我们有一个Friend对象，它的get方法用于返回单个好友对象，而getAll方法用于返回所有好友对象。于此对应的是两个服务器端API，friend接口会返回单个好友JSON，而friendlist接口会返回所有好友名称组成的JSON。</p>
<p>首先我们看看较为基础的get方法怎么写：</p>
<p><code>function get(name) {<br />
&nbsp; return Ajax.get("/friend", "name=" + encodeURIComponent(name));<br />
}</code></p>
<p>就这么简单？对的，假如服务器端API返回的JSON结构正好就是你要的好友对象结构的话。如果JSON结构和好友对象结构是异构的，或许你还要加点代码来把JSON映射为对象：</p>
<p><code>function get(name) {<br />
&nbsp; var operation = new Async.Operation()<br />
&nbsp; Ajax.get("/friend", "name=" + encodeURIComponent(name))<br />
&nbsp; &nbsp; .addCallback(function(json) {<br />
&nbsp; &nbsp; &nbsp; operation.yield(createFriendFromJson(json));<br />
&nbsp; &nbsp; });<br />
&nbsp; return operation;<br />
}</code></p>
<h4>Ajax队列</h4>
<p>接下来我们要编写的是getAll方法。因为friendlist接口只返回好友名称列表，因此在取得这份列表后我们还要逐一调用get方法获取具体的好友对象。考虑到在同时进行多个friend接口调用可能触发服务器的防攻击策略，导致被关小黑屋一段时间，所以对friend接口的调用必须排队。</p>
<p><code>function getAll(){<br />
&nbsp; var operation = new Async.Operation();<br />
&nbsp; var friends = [];<br />
&nbsp; var chain = Async.chain();<br />
&nbsp; Ajax.get("/friendlist", "")<br />
&nbsp; &nbsp; .addCallback(function(json) {<br />
&nbsp; &nbsp; &nbsp; for (var i = 0; i &lt; json.length; i++) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; chain.next(function() {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return get(json.shift())<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .addCallback(function(friend) { friends.push(friend); });<br />
&nbsp; &nbsp; &nbsp; &nbsp; });<br />
&nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; &nbsp; chain<br />
&nbsp; &nbsp; &nbsp; &nbsp; .next(function() { operation.yield(friends); })<br />
&nbsp; &nbsp; &nbsp; &nbsp; .go();<br />
&nbsp; &nbsp; })<br />
&nbsp; return operation;<br />
}</code></p>
<p>在这里，我们假设friendlist接口返回的JSON就是一个Array，在获取到这个Array后构造一个等长的异步调用队列，其中每一个调用的逻辑都是一样的&#8212;&#8212;取出Array中首个好友的名称，用get方法获取对应的好友对象，再将好友对象放入另一个Array中。在调用队列的末端，我们再追加了一个调用，用于返回保存好友对象的Array。</p>
<p>在这个例子当中，我们没有利用调用队列会把上一个函数的结果传递给下一个函数的特性，不过也足够展示调用队列的用途了&#8212;&#8212;让多个底层为Ajax请求的异步操作按照固定的顺序阻塞式执行。</p>
<p>由于底层异步函数返回的就是Async.Operation，你可以直接把它传递给next方法，也可以用匿名函数包装后传递给next方法，而匿名函数内部只需要一个return。</p>
<h4>延时函数</h4>
<p>在上面的例子中，使用队列是为了避免触发服务器的防攻击策略，但有时候这还是不够的。例如说，服务器要求两个请求之间至少间隔500毫秒，否则就认为是攻击，那么我们就要在队列里面插入这个间隔了。</p>
<p>在原本next方法调用的匿名函数中手动加入setTimeout是一个办法，但为什么我们不写一个辅助函数来解决这类问题呢？让我们来写一个辅助方法并让它和Async.Operation无缝结合起来。</p>
<p><code>Async.wait = function(delay, context) {<br />
&nbsp; var operation = new Async.Operation();<br />
&nbsp; setTimeout(function() { operation.yield(context); }, delay);<br />
&nbsp; return operation;<br />
};<br />
<br />
Async.Operation.prototype.wait = function(delay, context) {<br />
&nbsp; this.next(function(context) { return Async.wait(delay, context); });<br />
}</code></p>
<p>在有了这个辅助方法后，我们就可以在上述getAll方法中轻松实现在每个Ajax请求之间间隔500毫秒。在for循环内的加上对wait的调用就可以了。</p>
<p><code>for (var i = 0; i &lt; json.length; i++) {<br />
&nbsp; chain<br />
&nbsp; &nbsp; .wait(500)<br />
&nbsp; &nbsp; .next(function() {<br />
&nbsp; &nbsp; &nbsp; return get(json.shift())<br />
&nbsp; &nbsp; &nbsp; &nbsp; .addCallback(function(friend) { friends.push(friend); });<br />
&nbsp; });<br />
}</code></p>
<h4>小结</h4>
<p>通过一些简单的例子，我们了解到了Async.Operation常见的使用方式，以及在有需要的时候如何扩展它的功能。希望Async.Operation能够有效帮助大家提高Ajax应用的代码可读性。</p>
<p>最后，如果大家希望将来继续读到类似的JavaScript开发模式相关的文章，不妨考虑订阅我的博客：</p>
<ul>
     <li><a href="http://dotnet.catchen.biz/">Cat in
dotNET</a> (<a href="http://feedproxy.google.com/CatChen/dotNET">feed</a>)</li>
     <li><a href="http://chinese.catchen.biz/">Cat in Chinese</a> (<a href="http://feedproxy.google.com/CatChen/Chinese">feed</a>)</li>
</ul> <img src ="http://www.cnblogs.com/cathsfz/aggbug/1514983.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47988/" target="_blank">Firefox 3.5匆忙推出漏洞多 Mozilla本月将更新</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>写个 JavaScript 异步调用框架 (Part 5 - 链式实现)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/06/30/1514339.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Tue, 30 Jun 2009 15:36:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/06/30/1514339.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1514339.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/06/30/1514339.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1514339.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1514339.html</trackback:ping><description><![CDATA[<p>在上一篇文章里面，我们为异步调用框架设计了一种链式调用方式，来增强异步调用队列的代码可读性，现在我们就来编写实现这部分功能的代码。</p>
<h4>调用入口</h4>
<p>链式调用存在Async.go方法和Async.chain方法两个入口，这两个入口本质上是一致的，只是Async.chain方法在调用时先不提供初始参数，而Async.go方法在调用时提供了初始参数并启动异步调用链。</p>
<code>Async.chain = function() {<br />
&nbsp; var chain = new Async.Operation({ chain: true });<br />
&nbsp; return chain;<br />
};<br />
<br />
Async.go = function(initialArgument) {<br />
&nbsp; return Async.chain().go(initialArgument);<br />
}</code>
<p>在这里我们可以看到，链式调用本身也是一个Async.Operation，链式调用所需的go方法和next方法都是在Async.Operation上面做的扩展，并且这个扩展不会很难，这将在下一小节说明。</p>
<h4>扩展方法</h4>
<p>我们都知道，通过addCallback方法添加的回调函数是会被逐一执行的，至少同步函数如此，因此我们可以用Async.Operation的这一特性来维护异步调用队列，前提是我们为它加上对异步调用进行队列的支持。</p>
<p>对于异步调用进行队列的支持，我们稍后再来处理，首先我们利用现成的addCallback方法和yield方法扩展出go方法和next方法。</p>
<code>this.go = function(initialArgument) {<br />
&nbsp; return this.yield(initialArgument);<br />
}<br />
<br />
this.next = function(nextFunction) {<br />
&nbsp; return this.addCallback(nextFunction);<br />
};</code>
<p>实际上，go方法和next方法直接调用的正是yield方法和addCallback方法。go方法的语义与yield方法一样，传递一个参数给Async.Operation实例，并且启动调用队列。同时，next方法的语义和addCallback方法，添加一个调用到队列的末端。</p>
<h4>异步队列</h4>
<p>如何才能让原本仅支持同步的队列变得也支持异步？这需要检测队列中的每一个调用的返回，如果返回类型为Async.Operation，我们知道是异步调用，从而使用特殊的方法等它执行完后再执行下去。</p>
<code>callbackResult = callback(self.result);<br />
self.result = callbackResult;<br />
if (callbackResult &amp;&amp; callbackResult instanceof Async.Operation) {<br />
&nbsp; innerChain = Async.chain();<br />
&nbsp; while (callbackQueue.length &gt; 0) {<br />
&nbsp; &nbsp; innerChain.next(callbackQueue.shift());<br />
&nbsp; }<br />
&nbsp; innerChain.next(function(result) {<br />
&nbsp; &nbsp; self.result = result;<br />
&nbsp; &nbsp; self.state = "completed";<br />
&nbsp; &nbsp; self.completed = true;<br />
&nbsp; &nbsp; return result;<br />
&nbsp; });<br />
&nbsp; callbackResult.addCallback(function(result) {<br />
&nbsp; &nbsp; self.result = result;<br />
&nbsp; &nbsp; innerChain.go(result);<br />
&nbsp; });<br />
}</code>
<p>如果调用返回了一个Async.Operation实例，我们就利用它自身的addCallback方法帮我们执行队列中余下的调用。准确来说，是我们构造了一个新的调用链，把队列余下的调用都转移到新的调用链上，然后让当前异步调用在回调中启动这个新的调用链。</p>
<p>此外还有一些地方我们需要略作修改，以兼容新的异步调用队列的。例如result、state、completed的状态变更，在链式调用中是有所不同的。</p>
<h4>小结</h4>
<p>我们在原有的Async.Operation上略作修改，使得它支持异步调用队列，完整的代码看这里：<a href="http://www.cnblogs.com/cathsfz/articles/1514190.html">支持链式调用的异步调用框架Async.Operation</a>。</p>
<p>现在我们已经拥有了一个功能强大的Async.Operation，接下来我们就要看看如何将它投入到更多常见的使用模式中去，如果你不希望错过相关讨论的话，欢迎订阅我的博客：</p>
<ul>
     <li><a href="http://dotnet.catchen.biz/">Cat in dotNET</a> (<a href="http://feedproxy.google.com/CatChen/dotNET">feed</a>)</li>
     <li><a href="http://chinese.catchen.biz/">Cat in
Chinese</a> (<a
href="http://feedproxy.google.com/CatChen/Chinese">feed</a>)</li>
</ul>
<img src ="http://www.cnblogs.com/cathsfz/aggbug/1514339.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47987/" target="_blank">预测：Twitter最可能收购的十家公司</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>拆分自然数：纯while实现 (Part 2 - 实现)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/06/28/1512747.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Sun, 28 Jun 2009 07:35:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/06/28/1512747.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1512747.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/06/28/1512747.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1512747.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1512747.html</trackback:ping><description><![CDATA[<p>在《<a href="http://www.cnblogs.com/cathsfz/archive/2009/06/24/1509807.html">拆分自然数：纯while实现 (Part 1 - 思路)</a>》这篇文章里面，我提供了解答Jeff《<a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/06/21/1507847.html">编程小练习：拆分自然数</a>》问题的一种解答思路，并且使用了两个例子来解释这种思路，不知道你是否已经成功利用这种思路解题了呢？</p>
<p>首先，这道题的搜索域是什么？那就是[min, min, min, ..., min]到[max, max, max, ..., max]，或者是它的子集。就算你完全不懂算法，我相信你凭直觉也知道要在[min, min, min, ..., min]到[max, max, max, ..., max]之间搜索。因此，一个算法好不好，就看你能否有效缩小搜索域了。</p>
<p>在不缩小搜索域的情况下，你可以排列组合出搜索域内的所有可能性，逐一验证是否为所求解，这也就是Jeff的DoSimple示例所做的。接着，为了保持解的不重复性，你可能想到了有效解一定是一个不下降序列，因此对所有非不下降序列进行剪枝，这也就是Jeff的DoBetter示例所做的事情。然后，你需要把不下降序列中总和不等于 sum的情况给砍掉，这个剪枝就是最难的一个剪枝了，也就是DoBest所做的事情。<br />
</p>
<p>Jeff 在DoBest中的做法是，对于当前操作的第i位，求得itemMinInclusive与itemMaxInclusive，且 minInclusive &lt;= itemMinInclusive &lt;= itemMaxInclusive &lt;= maxInclusive，其中[minInclusive, itemMinInclusive)和(itemMaxInclusive, maxInclusive]这两个区间的取值是无效的，只有[itemMinInclusive, itemMaxInclusive]区间的取值才是有效的。</p>
<p><a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/06/21/1507847.html#1565398">dragonpig的展开</a>采用了try方式的剪枝，虽然也把无效枝剪掉了，但在循环上还是要遍历到无效枝的首个无效节点，必须在上面try一下看返回false才剪掉，因此性能介于DoBetter与DoBest之间。<a href="http://www.cnblogs.com/Chinese-xu/archive/2009/06/23/1509396.html">徐少侠的展开</a>进行了有效的剪枝，在时间复杂度上是与DoBest一致的，不过缓存数据占用空间为实际状态所需空间的3倍，这些空间其实是浪费掉的。我认为比较理想的做法是仅仅缓存sum值，而不缓存下界（即itemMinInclusive）与上界（即itemMaxInclusive），这样是保证性能的前提下最省空间的。至于为什么我说混存下界与上界没用，看一下我的写法就知道了：<br />
</p>
<div class="cnblogs_code"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #0000ff;">function</span><span style="color: #000000;">&nbsp;main(m,&nbsp;n,&nbsp;min,&nbsp;max)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;array&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">new</span><span style="color: #000000;">&nbsp;Array(n);<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;i&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;write&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log(m&nbsp;</span><span style="color: #000000;">+</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">'</span><span style="color: #000000;">&nbsp;=&nbsp;</span><span style="color: #000000;">'</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">+</span><span style="color: #000000;">&nbsp;array.join(</span><span style="color: #000000;">'</span><span style="color: #000000;">&nbsp;+&nbsp;</span><span style="color: #000000;">'</span><span style="color: #000000;">));<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;scan&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(scan.start)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scan.start&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">false</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scan.sum&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scan.sum&nbsp;</span><span style="color: #000000;">+=</span><span style="color: #000000;">&nbsp;array[i];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;(array[i]&nbsp;</span><span style="color: #000000;">&gt;</span><span style="color: #000000;">&nbsp;array[n&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">]&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">2</span><span style="color: #000000;">);<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;step&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;array[i]</span><span style="color: #000000;">++</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fill.sum&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;scan.sum&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">&nbsp;array[i];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">++</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;fill&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(i&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">&nbsp;n)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;array[i]&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;Math.max((fill.sum&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">&nbsp;(n&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">&nbsp;i&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">)&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">&nbsp;max),&nbsp;((i&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">)&nbsp;</span><span style="color: #000000;">?</span><span style="color: #000000;">&nbsp;min&nbsp;:&nbsp;array[i&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">]));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fill.sum&nbsp;</span><span style="color: #000000;">-=</span><span style="color: #000000;">&nbsp;array[i];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">++</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">--</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;fill.sum&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;m;<br />
&nbsp;&nbsp;&nbsp;&nbsp;fill();<br />
&nbsp;&nbsp;&nbsp;&nbsp;write();<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(</span><span style="color: #0000ff;">true</span><span style="color: #000000;">)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scan.start&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">true</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(i&nbsp;</span><span style="color: #000000;">&gt;=</span><span style="color: #000000;">0</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;scan())&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">--</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(i&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;step();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fill();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;write();<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
}</span></div>
<p>可能有些人还没有理解scan、step、fill这种结构，所以我先解释一下它们分别负责什么：</p>
<ul>
     <li><strong>scan</strong> - 剪枝。scan所需要做的工作，就是从最深的节点开始向根部遍历，找到第一个可以进行合法迭代的节点。scan是整个算法实现的核心，它必须有一个有效的判断准则，使得它跳过所有无法再做合法迭代的子节点，直到它找到可以合法迭代的节点为止。</li>
     <li><strong>step</strong> - 迭代。在当前节点迭代到下一个合法取值。由于scan保证了当前位必然可以迭代出下一个合法取值，所以step的工作就超级轻松了，只需轻轻向前跳一步。</li>
     <li><strong>fill</strong> - 填充。在迭代结束后，需要把低位的值全部填充为字典顺序中排在最前面的第一个合法取值组合。<br />
     </li>
</ul>
<p> 在这几个函数里面，最容易写的就是step了，基本上任何人闭上眼睛都能写，它就是把当前位的取值进行加一操作。其次就是fill了，无论是我的 fill，还是dragonpig的Reset，抑或是徐少侠那段没有独立成函数的变量j迭代，本质上都是一样的。只不过，dragonpig的 Reset带有了try，因为调用Reset的代码并不知道调用时是否可能生成合法解；而徐少侠的变量j迭代生成了大量中间变量，并且都缓存起来，但我认为其中的上界和下界是没必要缓存的，甚至是没必要生成的。<br />
</p>
<p>最难写的代码，其实在于scan，也就是scan中return的那一个条件语句。这个条件语句用于判定当前节点是否还能迭代出下一个合法解，如果这个判断你写对了，那么其它都不会是问题；如果这个判断你写错了，或者写不出来，那么整套思路都没用了。在说这道题目的scan条件之前，我们先回想一下上一篇文章两道题目的scan是怎么写的。<br />
</p>
<p>在第一道题目里面，我们只要简单的迭代指定长度内所有的二进制数，因此scan的条件很简单。如果当前为的值为1，也就是说当前位已经达到上界了，我们就返回true，让scan继续执行下去，直到遇到值为0的位。因为值为0的位还没有达到上界，所以它可以迭代下去，于是返回false，结束scan。在第二道题目里面，我们看0110这个具体例子，末端的0不能迭代，否则成为011<strong><span style="color: red;">1</span></strong>，值为1的位数改变了；值为1的位也不能迭代，因为这是上界。因此，我们设计scan条件，在至少经历过一个1后找首个0，因此迭代为<strong><span style="color: red;">1</span></strong>110，随后再通过fill修正低位1的位数，获得 1<strong><span style="color: red;">001</span></strong>。<br />
</p>
<p> 在Jeff的题目里面，我们可以通过一些例子来观察scan应有的条件。对于(5, 3, 1, 3)的输入，[1, 1, 3]和[1, 2, 2]是两个有效解，也就是说对[1, 1, 3]进行scan时，应该跳过末尾的3，而定位到中间的1上面去。那么为什么[1, 1, <span style="color: red;"><strong>4</strong></span>]不是合法的迭代结果呢？一方面4越了上界，另一方面总和超出sum了，而最低位的迭代是无法通过后面的fill来重新调整总和的。假如我们再考虑一下(5, 3, 1, 4)的输入，那么我们可以知道4越了上界这个条件其实是可有可无的，总和超出sum这个条件才是关键的。</p>
<p> 那为什么[1, 1, 3]迭代为[1, 2, *]是合法的，但是[1, 2, 2]迭代为[1, 3, *]就不是合法的呢？通过观察我们可以得知，因为[1, 1, 3]中末两位相差2，所以我们可以确信进行[1, 1 + 1, 3 - 1]调整后，这仍然是一个不下降序列。但是[1, 2, 2]就不符合这个条件了，因为[1, 2 + 1, 2 - 1]不是一个不下降序列。为此，我们可以认为寻找相邻两个相差大于或等于2的位，就是scan的目标。<br />
</p>
<p> 接着我们再来观察一个例子，输入为(15, 3, 4, 6) ，初始解为[4, 5, 6]，还能迭代下去吗？用上面的scan判定是无法迭代下去了，因为任何两位之间都仅仅相差1。但实际上，还有一个解是[5, 5, 5]。问题出在哪呢？我们之前的观察忽略了非相邻位的调整。没错，[4 + 1, 5 - 1, 6]和[4, 5 + 1, 6 - 1]都不是合法解，但[4 + 1, 5, 6 - 1]是合法解，这就是我们所忽略了的。因此，scan需要寻找的不是相差大于或等于2的相邻两位，而是相差大于或等于2的任意两位，并且要求这两位尽量低位（因为迭代优先从较深节点展开）。这看起来使得scan的逻辑复杂无比，首先要把任何相差大于或等于2的两位配对找出来，再在里面找最低位的一对。<br />
</p>
<p>实际上，不下降序列的特性为我们很好地解决了这个问题，我们只需要利用scan找到首个跟末位相差大于或等于2的位就可以了。例如[1, 2, 3, 4, 5, 6]，我们知道[1, 6], [2, 6], ..., [4, 6], [1, 5], [2, 5], ..., [1, 3]都是符合条件的配对，但最靠近低位的一定是[*, 6]。我们假设[x, 5]符合条件，对于同一个x，[x, 6]一定是符合条件的，同理[x, 4], [x, 3], ...也不用看了，只要针对[x, 6]找到x最大的合法取值就可以了。为此，我们可以写下简单的scan继续执行条件，那就是array[i] &gt; array[n - 1] - 2。<br />
</p>
<p>看到这里，相信大家都明白了为什么我说scan的一个判定条件如此重要，因为它肩负着不依赖于上下文信息进行剪枝的任务&#8212;&#8212;不知道下界与上界为何物，也不知道整个数组的sum是多少，所有这些状态信息都暗含在上一次fill好的数组中，无需为scan提供额外的信息（包括缓存信息）它就能工作。scan结束后，它只需要为fill提供两个正确的信息&#8212;&#8212;i和sum，随后fill必然能够给出下一个迭代解的低位。<br />
</p>
<p>最后，我来解释一下为什么不应该缓存下界和上界，只要缓存sum。首先，我们要为缓存这件事达成一个共识，那就是只有当一个东西会一次写入多次读取时，我们才需要进行缓存。下界信息是一定不需要缓存的，在首次fill后每一位的下界就确定下来了，随后迭代总是在某一位加一，下界这个值永远无需被重新访问，也就没必要缓存下来。同理，上界信息其实也在首次fill后确定下来了，并且随着每一次fill动态调整，因为array[n - 1]的上界就是它自身，而其它array[i]的上界必然小于或等于array[n - 1] - 2，这个逻辑已经暗含在scan里面了，自然就没必要缓存上界了。最后，sum的缓存是我在scan里面没做的，所以每次scan都要重新计算sum值，缓存sum值有助于性能的提升。</p>
<p>P.S.临摹其实是会限制你的思维的。由于Jeff的DoBest使用了itemMinInclusive和itemMaxInclusive进行剪枝，这影响到其它人在做递归展开时也优先考虑用同样的方法进行剪枝，然而在递归中最好的剪枝方法并不一定在展开后仍然是最好的，因为递归时桟信息是暗含的，你无法直接访问，在展开后你就能直接访问了。</p><img src ="http://www.cnblogs.com/cathsfz/aggbug/1512747.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47986/" target="_blank">网易澄清:与暴雪合资公司仅提供技术支持</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>拆分自然数：纯while实现 (Part 1 - 思路)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/06/24/1509807.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Wed, 24 Jun 2009 05:38:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/06/24/1509807.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1509807.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/06/24/1509807.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1509807.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1509807.html</trackback:ping><description><![CDATA[<p>关于Jeff的《<a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/06/21/1507847.html">编程小练习：拆分自然数</a>》问题，在此我提供一种思路，希望大家理解这种思路后能够自己把答案写出来。</p>
<p>我先问大家一个问题，写一个函数按顺序输出所有的4位二进制数（即：0000, 0001, 0010, 0011, ..., 1110, 1111），你会怎么写？ 如果把固定4位变成任意n位，你又会怎么写？我知道那些会写高精度加法的人会跳出来说，就做一个高精度的二进制加法，Array的每一位保存二进制数的每一位，每次在末尾加1，然后让高精度加法自己处理进位，最后打印结果。老实说，我也做过这种蠢事，直到我在高中的一次竞赛中看到了一种比较巧妙的做法：（我最近一年都是写JavaScript，所以我在这里就用JavaScript写了）</p>
<div class="cnblogs_code"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #0000ff;">function</span><span style="color: #000000;">&nbsp;main(n)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;array&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">new</span><span style="color: #000000;">&nbsp;Array(n);<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;i&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;write&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log(array.join(</span><span style="color: #000000;">''</span><span style="color: #000000;">));<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;scan&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;array[i]&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;step&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;array[i]&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">++</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;fill&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(i&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">&nbsp;n)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;array[i]&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">++</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">--</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;fill();<br />
&nbsp;&nbsp;&nbsp;&nbsp;write();<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(</span><span style="color: #0000ff;">true</span><span style="color: #000000;">)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(i&nbsp;</span><span style="color: #000000;">&gt;=</span><span style="color: #000000;">0</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;scan())&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">--</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(i&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;step();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fill();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;write();<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
}</span></div>
<p>如果你一眼就能看明白这是怎么做的，我相信你能够立即理解为什么我跟Jeff说两重while能够解决问题，因为本质上是相同的问题。（如果你不熟悉JavaScript的闭包概念的话，你可以认为array和i是全局变量，在write、scan、step、fill以及main的主逻辑中用到的array和i都是指同一个东西。）</p>
<p>道理其实很简单，根本不需要考虑使用加法，只需要用while从低位开始遍历，找到第一个为0的位（scan），把它置为1（step），然后再回过头来把比它低的位都由1置为0就可以了（fill）。例如1001110011，它从低位数起的第一个值为0的位是百位（如果用十进制数的叫法的话），把它置为1就成了1001110<span style="color: #ff0000;"><strong>1</strong></span>11，然后再把比它低的位都置为0，那就成了10011101<span style="color: #ff0000;"><strong>00</strong></span>。</p>
<p>简单吧？那么我们再来考虑一个略作修改的问题。写一个函数，按顺序输出所有含有2个1的4位二进制数（即：0011, 0101, 0110, 1001, 1010, 1100），你会怎么写？如果把固定的2和4这两个条件换成任意的m和n，你又会怎么写？</p>
<div class="cnblogs_code"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000ff;">function</span><span style="color: #000000;">&nbsp;main(m,&nbsp;n)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;array&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">new</span><span style="color: #000000;">&nbsp;Array(n);<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;i&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;write&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log(array.join(</span><span style="color: #000000;">''</span><span style="color: #000000;">));<br />&nbsp;&nbsp;&nbsp;&nbsp;};<br />&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;scan&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(scan.start)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scan.start&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">false</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scan.sum&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scan.sum&nbsp;</span><span style="color: #000000;">+=</span><span style="color: #000000;">&nbsp;array[i];<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;(array[i]&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">||</span><span style="color: #000000;">&nbsp;scan.sum&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">);<br />&nbsp;&nbsp;&nbsp;&nbsp;};<br />&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;step&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;array[i]&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fill.sum&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;scan.sum&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">++</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;};<br />&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">var</span><span style="color: #000000;">&nbsp;fill&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">function</span><span style="color: #000000;">()&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(i&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">&nbsp;n&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">&nbsp;fill.sum)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;array[i]&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">++</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(i&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">&nbsp;n)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;array[i]&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">++</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">--</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;};<br />&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;fill.sum&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;m;<br />&nbsp;&nbsp;&nbsp;&nbsp;fill();<br />&nbsp;&nbsp;&nbsp;&nbsp;write();<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(</span><span style="color: #0000ff;">true</span><span style="color: #000000;">)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scan.start&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">true</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(i&nbsp;</span><span style="color: #000000;">&gt;=</span><span style="color: #000000;">0</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;scan())&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i</span><span style="color: #000000;">--</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(i&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;step();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fill();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;write();<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}</span></div>
<p>还是类似的写法，用while从低位开始遍历，找到第一个为1的位，再找它之后第一个为0的位（scan），把它置为1（step），然后再回过头来填充比它低的位（fill）。这次填充逻辑要复杂一些，它需要知道刚才scan跳过了多少个为1的位，把这些1都填充到较低位，而较高位用0填充。</p>
<p>大家应该能够注意到，第二道题的main代码主题逻辑是和第一道题的几乎一致的，改变的只是scan、step、fill。main代码的主逻辑不同之处在于，scan多了一个start的状态，用于每一轮scan开始的时候初始化sum。此外，scan与fill之间多了一个sum的共享状态。实际上，我们可以对第一道题的scan、step、fill略作修改，容忍掉无用的start与sum状态，使得它们能够运行在第二道题的main代码主逻辑中。</p>
<p>这是一种什么模式？策略模式。main代码主逻辑按照固定的方式进行scan、step、fill。对于类似的题目，你只要写出正确的scan、step、fill就可以了，main代码的主逻辑完全不需要动。我所出的第二道题，其实是对Jeff题目特化。特化的地方在于，Jeff的题目要求能够接受sum, n, min, max，而我的题目把sum等同为m，把min特化为0，把max特化为1。此外，Jeff要求排除重复的组合，换句话说，就是得到的序列要求是不下降序列，但如果我的题目也如此限制为不下降序列的话，解就永远只有一个了（例如，m=2且n=4时，不下降序列就只有0011），因此我把这个限制条件去掉了。<br />
</p>
<p>总的来说，Jeff的题目与我所说的这两道题本质上是相同的，你想要解答Jeff的题目，只需要在我给出的main代码主逻辑基础上写出正确的scan、step、fill就可以了。具体的解答我稍后给出，大家可以先自己尝试写一下。 <br />
</p><img src ="http://www.cnblogs.com/cathsfz/aggbug/1509807.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47985/" target="_blank">杰克逊悼念仪式或成史上最大规模Web活动</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>中国程序员有美国梦吗？</title><link>http://www.cnblogs.com/cathsfz/archive/2009/06/15/1503337.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Sun, 14 Jun 2009 18:40:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/06/15/1503337.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1503337.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/06/15/1503337.html#Feedback</comments><slash:comments>54</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1503337.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1503337.html</trackback:ping><description><![CDATA[<p><a href="http://www.cnblogs.com/JeffreyZhao/">Jeff</a>最近转载了一篇名为《贺计算机成&#8220;就业最困难专业&#8221;》的文章，然后抛出了一个<a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/06/12/1501834.html">问题</a>来，问大家对此看法如何，接着自然又引起了新一轮博客园首页发文热潮。对此，我站在我的角度说说我的看法。</p>
<h4>大浪淘沙，金子难寻</h4>
<p>1848年，美国爆发了<a href="http://en.wikipedia.org/wiki/California_Gold_Rush">加州淘金热潮</a>，大量人口涌到加州进行淘金，其直接后果就是让一个叫<a href="http://en.wikipedia.org/wiki/San_Francisco">旧金山</a>小村庄的转眼间变成了一座大城市。在淘金热潮之初，你拿个筛子在河床里筛泥沙也能找到金子，这是何其容易的事情。但我们假想一下，如果当时一个淘金者拿着他的筛子跑到中国来，他能够淘到金子吗？我觉得是不可以的。这不是说中国的黄金矿藏量就比不上美国的一个州，而是中国没有如此高浓度而且可以进行露天开采的金矿。</p>
<p>1971年，旧金山<a href="http://en.wikipedia.org/wiki/Bay_area">湾区</a>南端由于&#8220;淘沙子&#8221;出了名，因而被大家叫做<a href="http://en.wikipedia.org/wiki/Silicon_Valley">硅谷</a>。1998年，Google在硅谷诞生了，然后开始疯狂地吸收硅谷乃至全球的技术人才。2006至2007年，Google在中国进行大规模招聘，这时候他们遇到了跟淘金者中国之行一样的难题&#8230;&#8230;</p>
<p>中国是第一个成功迫使Google进行笔试的国家。这句话说出来可能会让大家觉得十分搞笑，国内哪家公司招聘一线工程师不是先笔试再面试的。在人力资源供求相当的国家里，确实不需要笔试这轮筛选。有资质并且自信有资格来应聘的人，一般都能够得到充分的沟通，然后再确定这份工作是否适合。但这在人力资源明显供过于求的中国来说，就一点也不现实。</p>
<p>Google中国也有高层承认，以ACM/ICPC竞赛成绩来挑人确实有所缺陷，很多适合这份工作的人会被直接淘汰掉，连面对面或电话沟通的机会也没有。但假如你去看一下Google中国的校园招聘场面，看一下在各省重点大学里连开多个大教室进行笔试的情景，你不得不承认，要跟所有这些学生进行沟通的成本实在是连Google中国也支付不起。</p>
<p>总结一下，中国不是没有人才，也不是培养人才的方法有严重缺陷，至少这些都不是根本问题。根本问题是，在如此庞大的人口基数下，再好的企业都只能选用一套折中的人才选拔方案。你不能怪企业，说假如能生在美国你就能被Google美国所挖掘，而现在则被Google中国拒之门外。</p>
<h4>人人都会发光的时候你该怎么办？</h4>
<p>有时候用&#8220;是金子早晚会发光&#8221;的说法来自我安慰一下，也不是什么坏事。但当你发现人人都会发光的时候，你就知道事情有多不好了。
我们来做个数学建模，假设美国计算机人才中的top 100, top 1,000, top 10,000分别叫做A类、B类、C类。那么美国的人力资源供需关系大概是这样的：</p>
<ul style="list-style-type: upper-alpha;">
     <li>需求：100人；供应：100人。</li>
     <li>需求：1,000人；供应：1,000人。</li>
     <li>需求：10,000人；供应：10,000人。</li>
</ul>
<p>在保持A类、B类、C类界线不变的情况下，放到中国来很可能就是这样子的：</p>
<ul style="list-style-type: upper-alpha;">
     <li>需求：500人；供应：1,000人。</li>
     <li>需求：5,000人；供应：100,000人。</li>
     <li>需求：50,000人；供应：10,000,000人。</li>
</ul>
<p>就算中国的人才需求是美国的5倍，但依然供过于求。就算顶级人才的供求矛盾不明显，人才结构上的不合理也会给底层造成巨大的压力。</p>
<p>在A类里面，只有500人做不了自己应做的工作，被迫降级去做B类的工作。对于人口如此众多的一个国家来说，让500人屈就一下不是什么大问题。在B类里面，会有95,000人做不了自己应做的工作。而到了C类里面，会有9,950,000人做不了自己应做的工作，这时候问题就很严重了。</p>
<p>如果你搞不懂上面那堆数字说的是什么，那么我尝试换用浅白一些的语言来再说一次。如果你在美国，并且你达到了Microsoft美国招聘的要求，那么你成功加入Microsoft的概率就相当高了，因为你和Microsoft相互需要对方。</p>
<p>但如果你在中国，并且你达到了Microsoft中国招聘的要求，那么你成功加入Microsoft的概率并不怎么高，因为跟你一样符合这一条件的人数要远多于Microsoft的需求。如果你想要确保一个较高的应聘成功率，那么你必须远高于Microsoft的招聘要求。</p>
<h4>没有压力的都跳槽了</h4>
<p>假如我们把胜任某个职位的人简单分为两类：刚刚好胜任的，以及过度胜任的。那么，我们可以得到一个有趣的推论。</p>
<p>刚刚好胜任的人，是一定很有压力的。如果上面的分析没错的话，你之所以能够获得这个职位，一定程度上依赖于运气。好几个拥有同等实力的人跟你抢这个职位，尽管他们没能抢到，但依旧对此虎视眈眈，时刻准备着抢你饭碗。企业没理由对此坐视不理，一定会让你一直处于失业的边缘，以此作为筹码逼你好好干活，这时候你没压力才怪。</p>
<p>过度胜任的人，<a href="http://en.wikipedia.org/wiki/Overqualification">忠诚度一定会打折扣</a>。总有更好的职位在对你招手，尽管你知道那些职位你只能凭运气获得，尽管你知道得到了你也站到了失业的边缘上，但你就不心动吗？肯定还是会心动的。</p>
<p>知足常乐者？估计在顶端会多一些。我和Jeff这样的，属于在国内少数算是享有<a href="http://en.wikipedia.org/wiki/American_Dream">American Dream</a>的人，也就是能够通过自己的奋斗来获得应有的财富与社会地位。Jeff倾向于认为这对广大程序员普遍适用，并且鼓励大家都去追求自己的梦想。然而我得到的结论却并非如此。对于这事情，你怎么看？</p><img src ="http://www.cnblogs.com/cathsfz/aggbug/1503337.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47984/" target="_blank">《商业周刊》:Mozilla的志愿者开发模式被复制</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>写个 JavaScript 异步调用框架 (Part 4 - 链式调用)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/05/09/1452875.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Sat, 09 May 2009 15:46:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/05/09/1452875.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1452875.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/05/09/1452875.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1452875.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1452875.html</trackback:ping><description><![CDATA[<p>我们已经实现了一个简单的异步调用框架，然而还有一些美中不足，那就是顺序执行的异步函数需要用嵌套的方式来声明。</p>
<p>现实开发中，要按顺序执行一系列的同步异步操作又是很常见的。还是用<a href="http://web.im.baidu.com/" target="_blank">百度Hi网页版</a>中的例子，我们先要异步获取联系人列表，然后再异步获取每一个联系人的具体信息，而且后者是分页获取的，每次请求发送10个联系人的名称然后取回对应的具体信息。这就是多个需要顺序执行的异步请求。</p>
<p>为此，我们需要设计一种新的操作方式来优化代码可读性，让顺序异步操作代码看起来和传统的顺序同步操作代码一样优雅。</p>
<h4>传统做法</h4>
<p>大多数程序员都能够很好的理解顺序执行的代码，例如这样子的：</p>
<p><code>var firstResult = firstOperation(initialArgument);<br />
var secondResult = secondOperation(firstResult);<br />
var finalResult = thirdOperation(secondResult);<br />
alert(finalResult);</code></p>
<p>其中先执行的函数为后执行的函数提供所需的数据。然而使用我们的异步调用框架后，同样的逻辑必须变成这样子：</p>
<p><code>firstAsyncOperation(initialArgument).addCallback(function(firstResult) {<br />
&nbsp; secondAsyncOperation(firstResult).addCallback(function(secondResult) {<br />
&nbsp; &nbsp; thirdAsyncOperation(secondResult).addCallback(function(finalResult) {&nbsp; <br />
&nbsp; &nbsp; &nbsp; alert(finalResult);<br />
&nbsp; &nbsp; });<br />
&nbsp; });<br />
});</code></p>
<h4>链式写法</h4>
<p>我认为上面的代码实在是太不美观了，并且希望能够改造为jQuery风格的链式写法。为此，我们先构造一个用例：</p>
<p><code>Async.go(initialArgument)<br />
&nbsp; .next(firstAsyncOperation)<br />
&nbsp; .next(secondAsyncOperation)<br />
&nbsp; .next(thirdAsyncOperation)<br />
&nbsp; .next(function(finalResult) { alert(finalResult); })</code></p>
<p>在这个用例当中，我们在go传入初始化数据，然后每一个next后面传入一个数据处理函数，这些处理函数按顺序对数据进行处理。</p>
<h4>同步并存</h4>
<p>上面的用例调用到的全部都是异步函数，不过我们最好能够兼容同步函数，让使用者无需关心函数的具体实现，也能使用这项功能。为此我们再写一个这样的用例：</p>
<p><code>Async.go(0)<br />
&nbsp; .next(function(i) { alert(i); return i + 1; })<br />
&nbsp; .next(function(i) {<br />
&nbsp; &nbsp; alert(i);<br />
&nbsp; &nbsp; var operation = new Async.Operation();<br />
&nbsp; &nbsp; setTimeout(function() { operation.yield(i + 1); }, 1000);<br />
&nbsp; &nbsp; return operation;<br />
&nbsp; })<br />
&nbsp; .next(function(i) { alert(i); return i + 1; })<br />
&nbsp; .next(function(i) { alert(i); return i; });</code></p>
<p>在上述用例中，我们期待能够看到0, 1, 2, 3的提示信息序列，并且1和2之间间隔为1000毫秒。</p>
<h4>异步本质</h4>
<p>一个链式调用，本质上也是一个异步调用，所以它返回的也是一个Operation实例。这个实例自然也有result、state和completed这几个字段，并且当整个链式调用完成时，result等于最后一个调用返回的结果，而completed自然是等于true。</p>
<p>我们可以扩展一下上一个用例，得到如下用例代码：</p>
<p><code>var chainOperation = Async.go(0)<br />
&nbsp; .next(function(i) { alert(i); return i + 1; })<br />
&nbsp; .next(function(i) {<br />
&nbsp; &nbsp; alert(i);<br />
&nbsp; &nbsp; var operation = new Async.Operation();<br />
&nbsp; &nbsp; setTimeout(function() { operation.yield(i + 1); }, 1000);<br />
&nbsp; &nbsp; return operation;<br />
&nbsp; })<br />
&nbsp; .next(function(i) { alert(i); return i + 1; })<br />
&nbsp; .next(function(i) { alert(i); return i; });<br />
<br />
setTiemout(function() { alert(chainOperation.result; }, 2000);</code></p>
<p>把链式调用的返回保存下来，在链式调用完成时，它的result应该与最后一个操作的返回一致。在上述用例中，也就是3。</p>
<h4>调用时机</h4>
<p>尽管我们提供了一种链式调用方式，但是用户不一定会按照这种固定的方式来调用，所以我们仍然要考虑兼容用户的各种可能用法，例如说异步地用next往调用链添加操作：</p>
<p><code>var chainOperation = Async.go(0);<br />
chainOperation.next(function(i) { alert(i); return i + 1; });<br />
setTimeout(function() {<br />
&nbsp; chainOperation.next(function(i) {<br />
&nbsp; &nbsp; alert(i);<br />
&nbsp; &nbsp; var operation = new Async.Operation();<br />
&nbsp; &nbsp; setTimeout(function() { operation.yield(i + 1); }, 2000);<br />
&nbsp; &nbsp; return operation;<br />
&nbsp; })<br />
}, 1000);<br />
setTimeout(function() {<br />
&nbsp; chainOperation.next(function(i) { alert(i); return i + 1; });<br />
}, 2000);</code></p>
<p>在这个用例当中，用户每隔1000毫秒添加一个操作，而其中第二个操作耗时2000毫秒。也就是说，添加第三个操作时第二个操作还没返回。作为一个健壮的框架，必须要能兼容这样的使用方式。</p>
<p>此外我们还要考虑，用户可能想要先构造调用链，然后再执行调用链。这时候用户就会先使用next方法添加操作，再使用go方法执行。</p>
<p><code>var chainOperation = Async<br />
&nbsp; .chain(function(i) { alert(i); return i + 1; })<br />
&nbsp; .next(function(i) {<br />
&nbsp; &nbsp; alert(i);<br />
&nbsp; &nbsp; var operation = new Async.Operation();<br />
&nbsp; &nbsp; setTimeout(function() { operation.yield(i + 1); }, 2000);<br />
&nbsp; &nbsp; return operation;<br />
&nbsp; })<br />
&nbsp; .go(0)<br />
setTimeout(function() {<br />
&nbsp; chainOperation.next(function(i) { alert(i); return i + 1; })<br />
}, 1000);</code></p>
<p>在上述用例中，用户通过chain和next添加了头同步操作和异步操作各一个，然后用go执行调用链，在调用链执行完毕之前又用next异步追加了一个操作。一个健壮的框架，在这样的用例当中应该能够如同用户所期望的那样提示0, 1, 2。</p>
<h4>小结</h4>
<p>针对链式调用的需求，我们设计了如此多的用例，包括各种奇怪的异步调用方式。最终如何实现这样的功能呢？如果你想知道的话，欢迎订阅我的博客：</p>
<ul>
     <li><a href="http://dotnet.catchen.biz/">Cat in
     dotNET</a> (<a href="http://feedproxy.google.com/CatChen/dotNET">feed</a>)</li>
     <li><a href="http://chinese.catchen.biz/">Cat in Chinese</a> (<a href="http://feedproxy.google.com/CatChen/Chinese">feed</a>)</li>
</ul><img src ="http://www.cnblogs.com/cathsfz/aggbug/1452875.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47977/" target="_blank">Mono 的Virtual PC 虚拟机</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>写个 JavaScript 异步调用框架 (Part 3 - 代码实现)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/05/07/1451937.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Thu, 07 May 2009 06:54:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/05/07/1451937.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1451937.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/05/07/1451937.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1451937.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1451937.html</trackback:ping><description><![CDATA[<p>在上一篇文章里，我们说到了要实现一个Async.Operation类，通过addCallback方法传递回调函数，并且通过yield方法返回回调结果。现在我们就来实现这个类吧。</p>
<h4>类结构</h4>
<p>首先我们来搭一个架子，把需要用到的似有变量都列出来。我们需要一个数组，来保存回调函数列表；需要一个标志位，来表示异步操作是否已完成；还可以学IAsyncResult，加一个state，允许异步操作的实现者对外暴露自定义的执行状态；最后加一个变量保存异步操作结果。</p>
<p><code>Async = {<br />
&nbsp; Operation: {<br />
&nbsp; &nbsp; var callbackQueue = [];<br />
&nbsp; &nbsp; this.result = undefined;<br />
&nbsp; &nbsp; this.state = "waiting";<br />
&nbsp; &nbsp; this.completed = false;<br />
&nbsp; }<br />
}</code></p>
<h4>addCallback方法</h4>
<p>接下来，我们要实现addCallback方法，它的工作职责很简单，就是把回调函数放到callbackQueue中。此外，如果此时completed为true，说明异步操作已经yield过了，则立即调用此回调。</p>
<p><code>this.yield = function(callback) {<br />
&nbsp; callbackQueue.push(callback);<br />
&nbsp; if (this.completed) {<br />
&nbsp; &nbsp; this.yield(this.result);<br />
&nbsp; }<br />
&nbsp; return this;<br />
}</code></p>
<p>我们假设yield方法会把callbackQueue中的回调函数逐个取出来然后调用，因此如果compeleted为true，则使用已有的result再调用一次yield就可以了，这样yield自然会调用这次添加到callbackQueue的回调函数。</p>
<p>至于最后的return this;，只是为了方便jQuery风格的链式写法，可以通过点号分隔连续添加多个回调函数：</p>
<p><code>asyncOperation(argument)<br />
&nbsp; .addCallback(firstCallback)<br />
&nbsp; .addCallback(secondCallback);</code></p>
<h4>yield方法</h4>
<p>最后，我们要实现yield方法。它需要将callbackQueue中的回调函数逐个取出来，然后都调用一遍，并且保证这个操作是异步吧。</p>
<p><code>this.yield = function(result) {<br />
&nbsp; var self = this;<br />
&nbsp; setTimeout(function() {<br />
&nbsp; &nbsp; self.result = result;<br />
&nbsp; &nbsp; self.state = "completed";<br />
&nbsp; &nbsp; self.completed = true;<br />
&nbsp; &nbsp; while (callbackQueue.length &gt; 0) {<br />
&nbsp; &nbsp; &nbsp; var callback = callbackQueue.shift();<br />
&nbsp; &nbsp; &nbsp; callback(self.result);<br />
&nbsp; &nbsp; }<br />
&nbsp; }, 1);<br />
&nbsp; return this;<br />
}</code></p>
<p>通过使用setTimeout，我们确保了yield的实际操作是异步进行的。然后我们把用户传入yield的结果及相关状态更新到对象属性之上，最后遍历callbackQueue调用所有的回调函数。</p>
<h4>小结</h4>
<p>这样我们就做好了一个简单的JavaScript异步调用框架，完整的代码可以看这里：<a href="http://www.cnblogs.com/cathsfz/articles/1451428.html">异步调用框架Async.Operation</a>。</p>
<p>这个框架能够很好的解决调用栈中出现同步异步操作并存的情况，假设所有函数都返回Async.Operation，框架的使用者可以使用一种统一的模式来编写代码，处理函数返回，而无需关心这个函数实际上是同步返回了还是异步返回了。</p>
<p>对于串行调用多个异步函数的情况，我们现在可以用嵌套addCallback的方式来书写，但随着嵌套层数的增多，代码会变得越来越不美观：</p>
<p><code>firstAsyncOperation().addCallback(function() {<br />
&nbsp; secondAsyncOperation().addCallback(function() {<br />
&nbsp; &nbsp; thirdAsyncOperation().addCallback(function() {<br />
&nbsp; &nbsp; &nbsp; finalSyncOperation();<br />
&nbsp; &nbsp; });<br />
&nbsp; });<br />
});</code></p>
<p>我们能否把嵌套形式改为jQuery风格的链式写法呢？这是我们接下来要思考的问题，如果你不希望错过相关讨论的话，欢迎订阅我的博客：</p>
<ul>
     <li><a href="http://dotnet.catchen.biz/">Cat in dotNET</a> (<a href="http://feedproxy.google.com/CatChen/dotNET">feed</a>)</li>
     <li><a href="http://chinese.catchen.biz/">Cat in
Chinese</a> (<a href="http://feedproxy.google.com/CatChen/Chinese">feed</a>)</li>
</ul><img src ="http://www.cnblogs.com/cathsfz/aggbug/1451937.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47970/" target="_blank">19岁天才黑客发布首个iPhone 3GS破解软件</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>写个 JavaScript 异步调用框架 (Part 2 - 用例设计)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/05/07/1451200.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Wed, 06 May 2009 17:12:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/05/07/1451200.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1451200.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/05/07/1451200.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1451200.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1451200.html</trackback:ping><description><![CDATA[<p>在上一篇文章里说到，我们要设计一个异步调用框架，最好能够统一同步异步调用的接口，同时具体调用顺序与实现方式无关。那么我们现在就来设计这样一个框架的用例。</p>
<h4>传递回调</h4>
<p>我们首先要考虑的一个问题是，如何传递回调入口。在最传统的XHR调用当中，回调函数会被作为最后一个参数传递给异步函数：</p>
<p><code>function asyncOperation(argument, callback)</code></p>
<p>在参数相当多的时候，我们可以把参数放到一个JSON里面，这样参数就如同具名参数一样，可以通过参数名选择性的传递参数，不传递的参数相当于使用默认值。这是从Prototype开始就流行起来的做法：</p>
<p><code>function asyncOperation(argument, options)</code></p>
<p>然而这两种做法都有一个坏处，就是把同步函数改为异步函数（或同步异步混合函数）时，必须显式地修改函数签名，在最后增加一个（或多个）参数。</p>
<p>由于在调用栈的底层引入异步函数对我们来说太常见了，为此可能要更改一大堆上层调用函数签名的成本实在是太高了，所以我们还是想一个不用修改函数签名的做法吧。</p>
<p>在这里我参考了.NET Framework的IAsyncResult设计，把异步操作有关的一切信息集中到一个对象上来，从而避免了对函数签名的修改。在此，我们假设一个异步函数的调用原型是这样子的：</p>
<p><code>function asyncOperation(argument) {<br />
&nbsp; operation = new Async.Operation();<br />
&nbsp; setTimeout(function() { operation.yield("hello world"); }, 1000);<br />
&nbsp; return operation;<br />
}</code></p>
<p>在这段代码里，我们返回了一个Operation对象，用于将来传递回调函数。同时，我们通过setTimeout模拟了异步返回结果，而具体的返回方式就是yield方法。</p>
<p>接着，我们还要设计传递回调函数的方法。由于我们不能好像C#那样重载+=运算符，所以只能用函数传递回调函数：</p>
<p><code>var operation = asyncOperation(argument);<br />
operation.addCallback(function(result) { alert(result); });</code></p>
<p>在C#里面做这样的设计是不安全的，因为在异步操作可能在添加回调之前就完成了。但在JavaScript里面这样写是安全的，因为JavaScript是单线程的，紧接着asyncOperation的同步addCallback必然先执行，asyncOperation中的异步yield必然后执行。</p>
<h4>调用顺序</h4>
<p>可能有人要问，如果用户使用同步的方式来调用yield，这时候执行顺序不一样依赖于yield的实现吗？没错，不过yeild是在框架中一次性实现的，我们只要把它做成异步的就可以了，这样即使对它进行同步调用，也不影响执行顺序：</p>
<p><code>function psudoAsyncOperation(argument) {<br />
&nbsp; operation = new Async.Operation();<br />
&nbsp; operation.yield("hello world");<br />
&nbsp; return operation;<br />
}<br />
var operation = asyncOperation(argument);<br />
operation.addCallback(function(result) { alert(result); });</code></p>
<p>就算把代码写成这个样子，我们也能确保addCallback先于yield的实际逻辑执行。</p>
<h4>事后回调</h4>
<p>有时候，框架的使用者可能真的写出了先yield后addCallback的代码。这时候，我认为必须保证addCallback中添加的回调函数会被立即触发。因为用户添加这个回调函数，意味着他期望当异步操作有结果时通知这个回调函数，而这与添加回调函数时异步操作是否完成无关。为此，我们再添加一个用例：</p>
<p><code>function psudoAsyncOperation(argument) {<br />
&nbsp; operation = new Async.Operation();<br />
&nbsp; operation.yield("hello world");<br />
&nbsp; return operation;<br />
}<br />
var operation = asyncOperation(argument);<br />
setTimeout(function() {<br />
&nbsp; operation.addCallback(function(result) { alert(result); });<br />
}, 1000);</code></p>
<h4>小结</h4>
<p>到这里，我们就设计好了一个名为Async.Operation的异步操作对象，具体如何实现关键的yield方法和addCallback方法将在下一篇文章讲述如果。你不希望错过的话，欢迎订阅我的博客：</p>
<ul>
<li><a href="http://dotnet.catchen.biz/">Cat in
dotNET</a> (<a href="http://feedproxy.google.com/CatChen/dotNET">feed</a>)</li>
<li><a href="http://chinese.catchen.biz/">Cat in Chinese</a> (<a href="http://feedproxy.google.com/CatChen/Chinese">feed</a>)</li>
</ul><img src ="http://www.cnblogs.com/cathsfz/aggbug/1451200.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47970/" target="_blank">19岁天才黑客发布首个iPhone 3GS破解软件</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>写个 JavaScript 异步调用框架 (Part 1 - 问题 &amp; 场景)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/05/06/1450332.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Tue, 05 May 2009 16:18:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/05/06/1450332.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1450332.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/05/06/1450332.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1450332.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1450332.html</trackback:ping><description><![CDATA[<h4>问题</h4>
<p>在Ajax应用中，调用XMLHttpRequest是很常见的情况。特别是以客户端为中心的Ajax应用，各种需要从服务器端获取数据的操作都通过XHR异步调用完成。然而在单线程的JavaScript编程中，XHR异步调用的代码风格实在是与一般的JavaScript代码格格不入。</p>
<h5>额外参数</h5>
<p>考虑一个除法函数，如果它是纯客户端的同步函数，那么签名会是这样的：</p>
<p><code>function divide(operand1, operand2)</code></p>
<p>然而假设我们对客户端除法的精度不满意，于是把除法转移到服务器端来执行，那么它是个需要调用XHR的异步函数，签名也就可能会是以下几种之一：</p>
<p><code>function divide(operand1, operand2, callback)<br />
function divide(operand1, operand2, successCallback, failureCallback)<br />
function divide(operand1, operand2, options)</code></p>
<p>我们必须在签名中引入新的参数来传递回调函数，不能选择让函数变成阻塞式的同步调用。</p>
<h5>可传递性</h5>
<p>不仅仅直接操作XHR的函数需要引入新的参数，这种复杂性还会顺着调用栈向外传递。例如说，我们对加减乘除四则运算作了封装，只向外暴露一个运算接口：</p>
<p><code>function calculate(operand1, operand2, operator)</code></p>
<p>这个calculate函数根据operator参数来调用内部的plus, subtract, multiply, divide函数。然而，因为divide函数变成了异步函数，所以整个calculate函数不得不也转变为异步函数：</p>
<p><code>function calculate(operand1, operand2, operator, callback)</code></p>
<p>同时，在调用栈之上凡是需要调用到calculate的函数，都必须变成异步的，除非它并不需要向上一级调用函数返回结果。</p>
<h5>同步并存</h5>
<p>尽管calculate函数变成了一个异步函数，它所调用的plus, subtract, multiply函数还是同步函数，只有调用divide时是异步的，这时候calculate就是一个异步同步并存函数。</p>
<p>这会带来什么问题？calculate的调用者看到函数签名自然会认为calculate是个异步函数，因为它需要传递回调函数，然而calculate的执行方式却是不确定的。考虑如下调用：</p>
<p><code>calculate(operand1, operand2, operator, callback);<br />
next();</code></p>
<p>这里涉及到callback和next两个函数，它们哪个先执行哪个后执行是不确定的，或者说是依赖于calculate具体实现的。</p>
<p>如果calculate的实现是，当不需要进行异步操作时，直接调用callback。那么，在执行加减乘法时callback会在next之前被调用；在执行除法时next会在callback之前调用。</p>
<p>如果我们不喜欢这种不确定性，我们可以改变一下calculate的实现，把同步调用也都改为setTimeout形式的，这样在任何情况下next都一定会在callback之前被调用。</p>
<p>然而后面一种做法依赖于成本较高的实现方式，开发者一个不小心（或者摆明偷懒）就会漏掉setTimeout，导致函数调用顺序变得不确定，所以我们会希望这是框架帮助实现的功能，在使用框架时无法把这绕过。</p>
<h4>场景</h4>
<p>在这里，我举一个关于上述问题的具体应用场景。（为简化问题，描述已略作修改，与实际应用并不一致。）</p>
<p>在<a href="http://web.im.baidu.com/" target="_blank">百度Hi网页版</a>里面，我们会在客户端保存一个用户对象列表，在打开和这个用户的聊天窗口时，我们需要从中读取这个用户的信息。这个操作就涉及很多可能同步又可能异步的分支：</p>
<ul>
     <li>用户对象未缓存
     <ul>
         <li>异步读取用户信息</li>
     </ul>
     </li>
     <li>用户对象已缓存
     <ul>
         <li>用户是好友（信息更新会由服务器端推送）
         <ul>
             <li>同步读取用户信息</li>
         </ul>
         </li>
         <li>用户不是好友（信息更新需要由客户端拉取）
         <ul>
             <li>可以接受缓存信息
             <ul>
                 <li>同步读取用户信息</li>
             </ul>
             </li>
             <li>必须获取最新信息
             <ul>
                 <li>异步读取用户信息</li>
             </ul>
             </li>
         </ul>
         </li>
     </ul>
     </li>
</ul>
<p>可以看到，分支的结果最终既有同步的，也有异步的。并且这些分支还不是在一个函数里完成，而是在几个函数里实现。也就是说，按照传统的模式，这些函数一部分是同步的，一部分是异步的，由于异步的传递性，最终调用栈顶层的函数都是异步的。</p>
<p>为了解决这个问题，我们需要写一个异步调用框架，用一种统一的方式来进行调用，把同步和异步调用都合并为一种返回方式。</p>
<p>具体的解决方案会在下一篇文章中给出，如果你不希望错过的话，欢迎订阅我的博客：</p>
<ul>
     <li><a href="http://dotnet.catchen.biz/">Cat in dotNET</a> (<a href="http://feedproxy.google.com/CatChen/dotNET">feed</a>)</li>
     <li><a href="http://chinese.catchen.biz/">Cat in
Chinese</a> (<a
href="http://feedproxy.google.com/CatChen/Chinese">feed</a>)</li>
</ul>
<img src ="http://www.cnblogs.com/cathsfz/aggbug/1450332.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47969/" target="_blank">新浪邮箱大本营粉墨登场！Sina.cn开放注册</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>十分钟内学会：避免用户刷新导致重复POST提交</title><link>http://www.cnblogs.com/cathsfz/archive/2009/04/03/1428857.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Fri, 03 Apr 2009 03:56:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/04/03/1428857.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1428857.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/04/03/1428857.html#Feedback</comments><slash:comments>10</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1428857.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1428857.html</trackback:ping><description><![CDATA[<h3>Question</h3>
<p>在Web应用中，采用POST提交信息是非常常见的，然而如果目标页面打开得太慢，用户就可能会刷新页面，这时候之前已经提交过的信息就会被重复提交。即使用户成功打开了POST提交目标页，之后他通过链接导航到别处了，再退回到POST提交目标页时仍可能会重复提交页面（例如因为浏览器缓存已失效）。而且，只要碰到重复POST提交的场景，浏览器就会问用户是否确认重做此操作，用户并不一定能正确理解重做意味着重做什么，浏览器又不允许网站向用户解释清楚，所以这属于非常不友善的设计。那么我们应该如何避免用户刷新带来的重复提交呢？</p>
<h3>Answer</h3>
<p>有一种最简单的模式能够解决这个问题，叫做PRG，也就是Post-Redirect-Get。在用户提交信息后，我们不要在POST提交的目标URL返回结果页面，而返回一个302将浏览器重定向到真正的结果显示页，然后浏览器通过GET去获取那个页面。</p>
<p>这样做的话，用户刷新结果页，或者通过历史记录回到该页面，都不会导致浏览器要重新进行POST，自然也就不会出现烦人的是否重做对话框了。而对于你来说，也有效避免了用户重复提交信息的可能性。</p>
<p>
</p>
<img src ="http://www.cnblogs.com/cathsfz/aggbug/1428857.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47968/" target="_blank">IE市场份额首次跌破60%</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>ASP.NET AJAX 4.0 Preview 3 (Part 2 - ASP.NET AJAX Template)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/03/14/1411312.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Sat, 14 Mar 2009 02:43:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/03/14/1411312.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1411312.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/03/14/1411312.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1411312.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1411312.html</trackback:ping><description><![CDATA[<p>在上一篇文章里，我们说到了如何使用ADO.NET Data Service Client Library能够轻松访问到存在服务器端的数据，然而将数据展现出来仍需要人手拼接HTML这点就实在是让人难以接受，所以我们现在就来看看如何利用ASP.NET AJAX Template解决这个问题。文章中所用到的示例代码，可以在这里下载：<a href="http://files.cnblogs.com/cathsfz/AspNetAjaxPreview.zip">ASP.NET AJAX 4.0 Preview 3 Demo</a>，然后参考里面的AspNetAjaxTemplateDemo.aspx。</p> <h4>Sys.UI.DataView</h4> <p>为了解决展示数据的问题，我们需要用到一个全新的客户端控件，那就是<code>Sys.UI.DataView</code>了，简称<code>DataView</code>。我们会用<code>DataView</code>替换掉上一篇文章中所说到的人手拼接HTML的部分，用于迭代生成一个ul中的li元素，因此看起来是把<code>DataView</code>当作<code>Repeater</code>来用。实际上，<code>DataView</code>的功能类似于<code>ListView</code>加上<code>DetailsView</code>。</p> <p>如果你把一个Array绑定到<code>DataView</code>上，它会显示为一个<code>ListView</code>。与<code>ListView</code>的<code>LayoutTemplate</code>相类似的是，它也能够定义控件展示的整体布局，并且仅仅是迭代输出其中的一小部分。例如说，编写一个有thead的table，并且仅仅是迭代输出thead之后的tr。在这方面，是<code>DataView</code>和<code>ListView</code>完全一致的。唯一不同的是，客户端暂时还没有<code>DataPager</code>这样的控件，所以<code>DataView</code>必须一次性的完整显示整个Array的数据。</p> <p>如果你把单个Object绑定到<code>DataView</code>上，它就会显示为一个<code>DetailsView</code>。这使得你可以使用两个<code>DetailsView</code>就做出经典的Master-Details展示模式，和在服务器端分别用<code>ListView</code>和<code>DetailsView</code>做出来的一样。当然，你不能指望<code>DataView</code>能够好像<code>DetailsView</code>那样，自动帮你分析每一个数据项并映射出对应的HTML模板，因此HTML模板还是要你自己手写的，但至少那是在HTML中编写模板，编写时能够享受IDE带来的各种好处，维护时也更容易看懂自己（或别人）原来写下的HTML。</p> <h4>JavaScript语法</h4> <p>接下来，我们就要把<code>DataView</code>投入到实际应用中去了。首先，我们说一下如何用JavaScript来实例化一个<code>DataView</code>。有编写ASP.NET AJAX客户端代码经验的人对<code>$create</code>应该不会觉得陌生，因为<code>DataView</code>继承自<code>Sys.UI.Control</code>，我们仍然可以用<code>$create</code>来实例化它。不过，在此之前，我们先要把对应的HTML编写好： </p> <p><code>&lt;ul id="itemTemplate" class="sysTemplate"&gt;<br>&nbsp; &lt;li&gt;<br>&nbsp;&nbsp;&nbsp; &lt;span class="award"&gt;{{ Award }}&lt;/span&gt;<br>&nbsp;&nbsp;&nbsp; &lt;span class="winner"&gt;{{ Winner }}&lt;/span&gt;<br>&nbsp;&nbsp;&nbsp; &lt;span class="film"&gt;{{ Film }}&lt;/span&gt;<br>&nbsp; &lt;/li&gt;<br>&lt;/ul&gt;</code></p> <p>然后我们就可以基于itemTemplate这个HTML元素创建控件了：</p> <p><code>$create(Sys.UI.DataView, {<br>&nbsp;&nbsp;&nbsp; dataSource: new Sys.Data.AdoDataSource(),<br>&nbsp;&nbsp;&nbsp; serviceUri: "WebDataService.svc",<br>&nbsp;&nbsp;&nbsp; query: "OscarWinners"<br>&nbsp; }, {}, {}, $get("itemTemplate"));</code></p> <p>现在，页面显示出来的结果和之前我们人手拼接HTML的版本完全一致，不过我们已经不在需要维护嵌入在JavaScript中的HTML代码了。</p> <h4>声明性语法</h4> <p>如果你觉得上面的做法还不够好，要在<code>pageLoad()</code>里面写一个<code>$create</code>，那么声明性语法可能正是你需要的。大家应该记得很久很久之前，在ASP.NET AJAX还叫做Atlas的时候，就已经有过声明性语法的设计，那就是xml-script。不知为何，后来Microsoft放弃了这一设计，现在声明性语法又回来了，而且设计得比以前的xml-script还要更好。假如不用<code>$create</code>的话，通过声明性语法实例化一个<code>DataView</code>仅需要这样做：</p> <p><code>&lt;body<br>&nbsp; xmlns:sys="javascript:Sys"<br>&nbsp; xmlns:dataView="javascript:Sys.UI.DataView"<br>&nbsp; sys:activate="*"&gt;<br>&nbsp; &lt;ul id="itemTemplate" class="sysTemplate"<br>&nbsp;&nbsp;&nbsp; sys:attach="dataView"<br>&nbsp;&nbsp;&nbsp; dataView:datasource="{{ new Sys.Data.AdoNetDataSource() }}"<br>&nbsp;&nbsp;&nbsp; dataView:serviceuri="WebDataService.svc"<br>&nbsp;&nbsp;&nbsp; dataView:query="OscarWinners"&gt;<br>&nbsp;&nbsp;&nbsp; &lt;li&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;span class="award"&gt;{{ Award }}&lt;/span&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;span class="winner"&gt;{{ Winner }}&lt;/span&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;span class="film"&gt;{{ Film }}&lt;/span&gt;<br>&nbsp;&nbsp;&nbsp; &lt;/li&gt;<br>&nbsp; &lt;/ul&gt;<br>&lt;/body&gt;</code></p> <p>我们所需要更改的代码包括：</p> <ol> <li>在body元素上声明有关的xmlns，将JavaScript中的名字空间映射到HTML上，或者你可以理解为映射到XML/XHTML上。  <li>通过sys:activate="*"这个声明，让ASP.NET AJAX知道它需要去解释页面上所有的声明性语法，并激活对应的组件。  <li>将原本使用<code>$create</code>初始化时传递给实例的属性、事件、引用改为用声明性语法，直接写在HTML元素的定义上。</li></ol> <p>经过这三步，我们就可以将原来使用<code>$create</code>创建的组件改为使用声明性语法创建了。</p> <h4>小结</h4> <p><code>DataView</code>使得我们能够使用HTML模板，来避免手工拼接HTML带来的种种问题，同时声明性语法让我们能够如同声明服务器端控件一样声明客户端组件。虽然在ASP.NET AJAX 4.0 Preview 3中这些功能仍有小bug，例如我想用声明性语法创建我自己编写的<code>InPlaceEditoBehavior</code>，这在初始化阶段毫无问题，但却会在页面卸载销毁对象之时抛出脚本错误。</p> <p>由于我觉得ASP.NET AJAX 4.0 Preview 4很快就要出来了，所以我也就不准备去深入研究Preview 3了，等Preview 4出来了再去好好看看源代码。如果你有兴趣关注这方面的技术文章，欢迎订阅我的博客，点击侧栏上的订阅链接就可以了。</p> <img src ="http://www.cnblogs.com/cathsfz/aggbug/1411312.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47965/" target="_blank">Google App Engine宕机6小时——云的安全在哪里？</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>ASP.NET AJAX 4.0 Preview 3 (Part 1 - ADO.NET Data Service Client Library)</title><link>http://www.cnblogs.com/cathsfz/archive/2009/03/11/1408290.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Tue, 10 Mar 2009 16:18:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2009/03/11/1408290.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1408290.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2009/03/11/1408290.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1408290.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1408290.html</trackback:ping><description><![CDATA[<p>自从Microsoft与jQuery合作以来，ASP.NET AJAX与jQuery就被定位为两个互补的AJAX库。既然jQuery已经实现了如此多轻量级的AJAX特性，自然ASP.NET AJAX会继续专注于富客户端所需的一些重量级特性。</p> <p>在ASP.NET AJAX 4.0 Preview 3里面，开发人员能够接触到的两个重要的新特性就是ADO.NET Data Service Client Library以及ASP.NET AJAX Template。对于熟悉ASP.NET服务器端开发但不熟悉客户端开发的人来说，你可以简单地把这两个特性理解为存在于客户端的DataSource以及ListView，只要把数据通过ADO.NET Data Service输出到前端，你就可以如同使用DataSource和ListView的组合一样在客户端开发数据驱动的应用程序了。</p> <p>在这篇文章里，我们将来看看如何使用ADO.NET Data Service Client Library，将ADO.NET Data Service暴露的REST数据接口直接拿到客户端JavaScript代码中去调用。文章中所用到的示例代码，可以在这里下载：<a href="http://files.cnblogs.com/cathsfz/AspNetAjaxPreview.zip">ASP.NET AJAX 4.0 Preview 3 Demo</a>，然后参考里面的AdoNetDataServiceDemo.aspx。</p> <h4>服务器端准备工作</h4> <p>在我们接下来要讲到的示例当中，我们会用到一个SQL Server 2005 Express Edition的数据库，里面有一张名为OscarWinners的表，记录的是本年度奥斯卡获奖名单，字段包括AwardID、Award、Winner、Film。然后我们为这张表创建ADO.NET Entity Model，接着再为它生成的实体类创建ADO.NET Data Service。这些都是在Visual Studio 2008中点几下鼠标就能完成的操作，就不再详细解释了。在ADO.NET Data Service的InitializeService()方法内，我们仅仅给它提供一个最宽松的规则：</p> <p><code>config.SetEntitySetAccessRule("*", EntitySetRights.All);</code></p> <p>到这里，我们就把服务器端的要做的工作都准备好了。打开你创建的ADO.NET Data Service地址，看看是否输出了正确的Atom格式数据。如果没有，请检查一下你机器上的WCF是否已经正确安装和配置好了。确保服务器端的准备工作都做好了，然后再进入客户端的开发工作。</p> <h4>连接Data Service</h4> <p>在客户端使用ADO.NET Data Service，我们需要接触到的类只有一个，那就是<code>Sys.Data.AdoNetServiceProxy</code>。首先，我们要连接到ADO.NET Data Service，也就是使用ADO.NET Data Service的URL来实例化此类：</p> <p><code>var dataService = new Sys.Data.AdoNetServiceProxy("WebDataService.svc");</code></p> <p>然后，我们就可以利用<code>dataService</code>来调用ADO.NET Data Service进行CRUD操作了。</p> <h4>CRUD操作</h4> <p>所有的CRUD操作都在<code>Sys.Data.AdoNetServiceProxy</code>对象上执行，方法分别名为<code>query()</code>、<code>insert()</code>、<code>update()</code>、<code>remove()</code>。在我们的示例当中，会用到<code>query()</code>和<code>update()</code>方法，另外两个方法是用起来和<code>update()</code>很类似，就不再详细说明了。</p> <h5>查询操作</h5> <p><code>dataService.query("OscarWinners", function(result, context, operation) {<br>&nbsp; /* display result */<br>}, errorHandler);</code></p>  <p>使用上述语句，我们查询出了OscarWinners表中的所有数据。随后的第一个回调函数会在查询成功时被调用，因此我们可以在其中编写拼接HTML以显示结果的逻辑，具体的代码请参考下载中的AdoNetDataServiceDemo.aspx。第二个回调函数会在查询失败时被调用，我们可以编写一个统一的错误处理函数，名为errorHandler，然后将它传递给此参数。</p> <p>如果需要传递复杂的查询参数，使用ADO.NET Data Service的格式就可以了，这可以在<a href="http://msdn.microsoft.com/zh-cn/library/cc907912(en-us).aspx">MSDN</a>上查到。例如说查询<em>Slumdog Millionaire</em>这部电影夺取了多少个奥斯卡奖项，然后把奖项按照名称排序输出，可以这样子写：</p> <p><code>dataService.query("OscarWinners?$filter=Film eq 'Slumdog Millionaire'&amp;$orderby=Award", function(result, context, operation) {<br>&nbsp; /* display result */<br>}, errorHandler);</code></p> <h5>更新操作</h5> <p><code>dataService.update(item, function(result, context, operation) { }, errorHandler);</code></p> <p>尽管将查询结果保存下来成为items集合，并且根据用户在界面上执行的操作修改item上的属性，这些逻辑都需要我们手动维护，然而最后将item更新到服务器上则只需要如此简单的一句调用。</p> <p>在我给出的示例代码中，我自己写了一个InPlaceEditBehavior，也就是所谓的&#8220;就地编辑器&#8221;，能够让用户点击显示文本后把显示文本变成输入框。然后我把这个InPlaceEditBehavior绑定到每一条记录显示的Winner字段和Film字段的span上，使得这些span都能接收用户输入。最后，我为InPlaceEditBehavior添加了一个onchanged事件，并在该事件的处理函数中完成更新item以及调用<code>update()</code>的操作。</p> <h4>小结</h4> <p>在这篇文章里，我简单地介绍了ADO.NET Data Service Client Library的易用性，并且通过一个具体的示例说明了如何用它来节省大量的数据交互代码。</p> <p>如果你曾经写过AJAX-Enabled WCF Service，你应该知道把实体类暴露为WCF Service接口是多么麻烦的事情，就算每个实体类就简单地支持CRUD方法，你也必须手动编写这4个方法。ADO.NET Data Service相当于帮你把这一切都做好了，只要给它实体类和规则，它就帮你生成一个Data Service。另外，通过AJAX-Enabled WCF Service所包括的数据接口，会自动生成一大堆客户端代理类，而ADO.NET Data Service Client Library则只有一个固定的代理类，客户端代码体积不会随着接口复杂度的增加而增加。</p> <p>说了ADO.NET Data Service Client Library的那么多好处，那么这个示例中又有什么做得不够好的地方呢？我觉得最难维护的地方就是获取到数据后拼接HTML的代码了，人手写的HTML拼接代码难免容易出错，而且日后更新起来也很麻烦，出错了调试时也不容易定位问题。ASP.NET AJAX Template能够帮助我们解决这个问题，这就是下一篇文章中将会讲到的内容。</p> <p>如果你不想错过接下来的文章，欢迎订阅我的博客，点击侧栏上的订阅链接就可以了。</p> <img src ="http://www.cnblogs.com/cathsfz/aggbug/1408290.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47961/" target="_blank">微软新推社交网站Windows Live Planet</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>假如你愿意以原版或影印版价格购买翻译书籍的话</title><link>http://www.cnblogs.com/cathsfz/archive/2008/12/12/1353581.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Fri, 12 Dec 2008 04:00:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/12/12/1353581.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1353581.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/12/12/1353581.html#Feedback</comments><slash:comments>75</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1353581.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1353581.html</trackback:ping><description><![CDATA[<p>这篇文章是接着Tony Qu的《<a href="http://www.cnblogs.com/tonyqus/archive/2008/12/12/1353372.html">批&#8220;觉得有必要记一下的东西——关于翻译&#8221;一文</a>》写的，就讨论一个问题——你原意以原版或影印版的价格购买翻译书籍吗？</p>
<p>为什么问这个问题？这源自Tony Qu批判的<a href="http://www.cnblogs.com/PatrickChen/archive/2008/12/11/1352800.html">原文</a>中的一句话：</p>
<blockquote>很多国内程序员看书就只看英文原版，开发就只用原版VS，就是这个原因。</blockquote>我承认，在英语能力相当的程序员当中，这句话说的是事实。能够看原版书的尽量看原版书，不仅仅因为看起来舒服，更因为容易在P2P网络上找到OCR后完美排版的PDF（甚至是官方PDF）。但这部分人当中，又有多少是真真正正是去买原版书的（原价格按汇率算），或者至少去买影印版的（价格是翻译版的一倍以上，原版的一半以下）？我想就很少了，我仅仅知道<a href="http://www.cnblogs.com/JeffreyZhao/">Jeffrey Zhao</a>会从国外买二手原版书。很老实说，假如读者原意为翻译版支付引进版的价格，那么翻译版能够做得比现在的要好得多。但有多少人是连翻译版的价格也不愿意支付的，宁愿上网找极度难看的未OCR扫描版？此时，结论已经很明确，翻译版质量差不完全是出版社或者译者的问题，是经济利益驱使，这有电影工业与唱片工业在大陆的做法为证！
<p>&nbsp;</p>
<p>虽然现在DVD的分区对购买者来说已经毫无意义，但当初就把大陆分作一个6区是有理由的。话说刚刚有DVD的时候，大陆的DVD市场跟国际市场同一个运作方法，电影上映后至少3个月才发行DVD，以免影响票房。但DVD就是卖不出去，不仅仅是因为太贵，还因为国内的购买者不愿意等那3个月，当然也不愿意买票进电影院看。他们买什么？买盗版DVD。因为DVD必须有正版片源出来后，才可能有正版片源的盗版DVD，所以3个月未到，所有的盗版都只可能是枪版DVD。然而大陆消费者就是不在乎画质和音质，他们就是想以最低的价格抢先看到电影，看完就算。当这种需求被中国电影工业从业人员知道以后，一种新的商业模式发展出来了，以满足这部分消费者的需求——大陆的引进版DVD的发行和电影的上映几乎同步进行，价格仅仅比盗版略高，并且最重要的是，人为地把画质和音质降低到枪版的水平。就这样，电影工业能够在大陆盗版市场上分到一杯羹，我的意思是他们算是卖官方枪版的了，同时也不影响他们的国际市场价格——即使存在购买渠道，港台消费者也不会接受这样廉价的劣质DVD，仍然会等上映3个月后买原版DVD。</p>
<p>唱片工业做的事情是类似的，大陆所谓的引进版虽然价格更容易被消费者接受，但音质是经过人为降低的。这就是价格歧视，因为大陆消费者只愿意支付一个更低的价格，也只需要购买质量更低的商品，所以供应商就可以把这划分为一个独立的价格歧视区域市场。我不知道图书行业是否存在这样人为控制的&#8220;阴谋&#8221;，但只要大陆消费者的需求还是这样的&#8220;另类&#8221;，价格歧视原则就会一直适用，这样的事情就会无意识的继续存在，以远低于影印版价格售卖的翻译版质量就肯定还是那么低。对了，顺带说一句，港台翻译版的价格是和大陆影印版差不多的。</p>
<p>总结一下，我也是译者，翻译工作低廉的价格以及编辑工作的效率低下Tony Qu说过我就不再说了。如果大陆翻译版最终按照港台翻译版的价格去销售也能获得同样的销量的话，我相信译者可能愿意为这份价钱做全职的翻译工作，我相信出版社愿意聘请更多专业编辑并且合理安排他们的工作量。</p>
<p>P.S.如果要认真学习和研究中国的传统语言文化，我个人还是建议去台湾。</p><img src ="http://www.cnblogs.com/cathsfz/aggbug/1353581.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47960/" target="_blank">火狐3.5版被指推出太匆忙：存在50多个漏洞</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>SD2C 2008</title><link>http://www.cnblogs.com/cathsfz/archive/2008/12/08/1349803.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Sun, 07 Dec 2008 16:01:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/12/08/1349803.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1349803.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/12/08/1349803.html#Feedback</comments><slash:comments>14</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1349803.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1349803.html</trackback:ping><description><![CDATA[<p>这几天参加了SD2C，也就是&#8220;软件开发2.0技术大会&#8221;。规矩当然是照旧的，social第一，session第二。</p> <h3>Day 1</h3> <p>第一天想着12:00开始签到，于是慢吞吞地准备出发，去到九华山庄已经是11:00。这是我第一次在没有车接送的前提下一个人跑来九华山庄，路上浪费了不少时间。签到后开始乱逛，然后陆续找到了公司不同部门来的同事，发现大家都没吃午餐。虽然我自己从McDonald's带了一个汉堡来，不过还是跟大家一起打车到小汤山街口的镇上吃饭。吃完饭就13:00了，应该开场了，可是打车回九华山庄实在太难，最后无奈打了黑车回去。我个人认为，把开场放在13:00但又不提供午餐是十分不体贴的。在市区的话也就算了，在九华山庄这样一个三流装一流的地方，选择不多又不怎么好吃的自助餐竟然要&#165;68一位，方便面也要&#165;20一盒，不提供午餐这样的做法实在是不人道。</p> <p>下午的议程看起来全是keynote级别，听过后才发现那完全是赞助商专场——逐个赞助商轮流上去宣传自己的技术或者观点，而且还不提供任何课程反馈的机会，它讲得再烂我也不能表达一下我的不满。赞助商专场分为上下两个半场，上半场的人都是来吹水的，就吹嘘自己公司的东西，泛泛而谈，听了也没什么收获；下半场讲的东西稍微实在一些，雷军和戴志康上来讲的话都很真诚，也很有感染力。总之，要是早知道第一天下午是赞助商专场的话，我就去WinHEC玩到15:00再过来好了，然后18:00吃饭，接着参加晚上的活动。这只能怪我参加会议的经验还是太少了，没有把这么明显的安排给看出来。</p> <p>下午听了那么多个小时，最让我感到震撼观点来自于雷军。他在说他投资的<a href="http://www.ucweb.com/">UCWeb</a>时提到了这样一个观点：我们曾经都是互联网用户中的领头羊，有什么新鲜的东西出来我们都先去尝试，然后慢慢普及到大众用户。但在移动互联网就不是这样，移动互联网的主要用户是那些比较少机会通过计算机接入互联网的人，例如学生、民工和军人。也就是说，我们这些每天使用计算机连接着互联网做开发的人，其实是不可能知道移动互联网的真实需求的，我们无法体会使用不到计算机接入互联网会使怎样的。过往我一直认为移动互联网用户就是高端移动用户和高端互联网用户的交集，现在我终于明白到不是这么一回事了。</p> <p>晚上去各个沙龙看了一下，没有什么印象特别深刻的。只记得在&#8220;职业规划与成长论坛&#8221;上听到这样一句搞笑的话——&#8220;我们的HR自裁了，也就没有HR了&#8221;。晚会后有班车把我们送回到地铁站，然后回到家也就是23:00了。</p> <h3>Day 2</h3> <p>第二天早上9:00出发，去到就是11:00了。我觉得把第一个session放在9:00开始是很不合理的，九华山庄这么偏僻的地方外加不包住宿，谁原意那么早起床赶7:00从市区开出的班车啊，结果当然是大家都很晚才到啊。对于speaker来说，如果自己的session被放在早上，可能也会觉得大会不重视他的session，因为早上能赶来参加session的人就是少一些，这个大家都明白的。我去到时已经是11:00，然后去听了一下郭安定的课，中午和郭安定、老孟、莫依等MVP一起吃饭，还是挺好玩的。</p> <p>从郭安定讲的内容来看，他很注重企业生态圈。也就是说，只要你能够帮助别人解决实际问题，赚钱的机会总是有的，但也没必要自己把什么都做了，也要留机会让别人帮你，赚你的钱。把这个道理传播出去，让更多人赚得到钱，让更多人受惠。这种思想我是十分认同的，如果帮助别人的同时又帮助了你自己，这有什么不好呢？</p> <p>下午我去听了一下周爱民的JavaScript+Delphi+ErLang，语言层面的东西不用怎么在乎，他也没做很应用性的展示，我觉得最重要的一个思想还是分层——只要实现了分层，层与层之间是针对接口设计的，那么随时换掉一个层都是可以的，只要接口保持不便，什么语言并不重要，不同层的需求不同实现语言自然也可以不同。另外还听了李建忠的.NET设计模式，讲的是一些老生常谈的内容，没什么非常特别的。其实讲什么技术并不重要，最重要是把那种思想说清楚，让听众都能领悟到，那就算是成功了。</p> <p>晚上是CSDN专家沙龙，本来说是让各位专家说说对CSDN服务的不满与建议的，结果大家的自我介绍都拖得很长，介绍完也就完了，赠送给各位专家一人一本绝影签名的《疯狂的程序员》，然后就赶班车去了。其中最有趣的一幕，就是莫依问老孟说，&#8220;是什么让你多年坚持在论坛上帮助别人解答问题&#8221;，旁边的郭安定小小声说了两个字，&#8220;伟哥&#8221;。</p> <h3>Day 3</h3> <p>最后一天，依旧是9:00出发11:00到。原本计划去听听iPhone开发的，发现基础部分都讲完了，开始讲OpenGL的使用，我又完全听不懂，听了三分钟就睡着了。</p> <p>下午听的是使用YSlow2.0优化网络性能，主要是了解一下YSlow2.0的新特性，也就是如何自己设计符合自身网络特性的网络性能评分规则。接着去听了Amazon运计算服务的架构分析介绍，确实比较介绍性，让你知道就是有那么多的服务在运行着，并且能为企业省钱，就这么简单。最后听的一个session是面向对象的JavaScript，这是作为当今JavaScript开发人员必须知道的基础知识啦，听他说一遍只是为了好玩。</p> <p>SD2C结束后，马宁准备把我和莫依还是陈敏送到地铁站，最后大发慈悲把我们都送回家了，超级好人，在这里需要感谢一下。建议明年真的不要选择九华山庄这样的地方了，交通实在是不方便。</p> <p>P.S.发现自己最近参加的会议不少啊，整个blog塞满了关于各个会议的文章，如<a href="http://chinese.catchen.biz/2008/10/china-mvp-open-day-2008.html">China MVP Open Day 2008</a>、TechED 2008 China、<a href="http://chinese.catchen.biz/2008/11/chinese-blogger-conference-2008.html">Chinese Blogger Conference 2008</a>、<a href="http://chinese.catchen.biz/2008/12/winhec-2008-china.html">WinHEC 2008 China</a>。</p> <img src ="http://www.cnblogs.com/cathsfz/aggbug/1349803.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47959/" target="_blank">Google对手机搜索进行优化升级</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>WinHEC 2008 China</title><link>http://www.cnblogs.com/cathsfz/archive/2008/12/03/1347018.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Wed, 03 Dec 2008 12:28:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/12/03/1347018.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1347018.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/12/03/1347018.html#Feedback</comments><slash:comments>19</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1347018.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1347018.html</trackback:ping><description><![CDATA[<p>今天是WinHEC第一天。WinHEC就是Windows Hardware Engineering Conference，中文名是做<a href="http://www.microsoft.com/china/whdc/winhec/default.mspx">Windows硬件工程大会</a>。昨晚紫柔告诉我9:00到场签到就可以了，因为9:30正式开始，但我早上还是忍不住多睡了一会儿，所以10:00才到场。当时还是张亚勤的演讲，我进入主会场后迅速找到剩余的几个空位坐下来了。</p>
<p>张亚勤的演讲当然紧接着若干个demo，其中Surface的demo在MVP Open Day已经看过了，这次还是同一个人来做。还有那个health care的video，也是<a href="http://chinese.catchen.biz/2008/10/china-mvp-open-day-2008.html">MVP Open Day</a>上面放过的，看来这两个月没什么变化嘛。整体而言，张亚勤演讲后的中文demo都是做一些我们已经知道的东西，没什么值得看的。</p>
<p>原本张亚勤的演讲只有1个小时，结果延长了很多，然后Dennis Flanagan才上来讲Windows 7。Windows 7的demo就非常有冲击力了，特别是device stage这样的特性。</p>
<p>在Windows
7里面，每个外设（包括网络外设）都有自己的图标，厂商可以设计一个和外设外观一样的图标，使得用户更容易在Windows
7中识别这一设备。例如你有黑白两个USB drive，都插入到电脑后，你会发现不知道哪个盘符是哪个USB
drive的，要看看里面的内容才知道。在Windows
7里面，只要厂商制作了对应的图标，用户就能在图形界面上看到有区别的两个图片，也就可以在不了解何谓盘符的前提下正确操作这两个USB drive了。</p>
<p>此外，每一个外设都可以有自己的device
stage，例如连接一部Zune后可以显示容量、电量，还可以显示为你推荐的MP3销售信息；又例如连接一部Nokia手机后可以显示有关的内部信息，
以及Nokia网站上为这部手机推荐的新游戏。简单来说，硬件设备销售后不再是与硬件厂商脱离关系了，只要它连接到安装了Windows
7的PC，并且这台PC还连接着网络，硬件厂商就能得知这台设备的使用情况，并且为你推介更多的商品和服务，当然也可能包括免费的服务与技术支持。肯定有
人会说这会导致隐私问题之类之类的，我相信最终Windows 7和硬件厂商会引入访问控制机制，让用户选择一台设备连接到Windows
7后是否允许相关信息通过网络传输，这个大家不用担心。</p>
<p>两个keynote结束后，就是抽奖环节。早上每一个人进来时都拍了一张照片用于
抽奖，然后用这些照片拼成了一个巨大的奥运五环徽标，最终用Silverlight Deep Zoom来随机zoom
in到某一个人的照片之上。这样的抽奖形式还是挺有创意的，不过有些照片实在太不清晰了，最后拿到了大奖的人到底是不是照片上的人，我们都不知道。</p>
<p>午餐是跟紫柔以及其它MVP一起吃的，紫柔带来了一个90后的实习生，是一个拿了国家奖学金去新加坡读高中的女生。她应该是紫柔的助手吧，不过我们说的东西她都听不懂，应该会觉得十分无聊。</p>
<p>随后，让我最不满的事情就发生了！今年的WinHEC和TechED一样，通过盖章来增加互动，总共有1200份Windows 7光盘和1000只2G
USB drive可以通过盖全10个章后获得，并且日程表写着下午2:00开始换领的。但我们2:00回到会场发现今天的400只2G USB
drive已经换领完毕了，虽然Windows
7光盘还是有的，但因为必须同时换领，所以也就相当于不能换领。既然日程表写着下午2:00开始就应该下午2:00开始啊，怎么能够提前开始发呢？！</p>
<p>下午的课程我们都没有去听，一群MVP聚在一起聊天。最有趣的一幕就是，竟然有人来主动搭讪那位90后女生。是不是非80后的人就分不清什么人是90后的
呢？又或者是IT人士太闭塞了？反正我觉得90后的化妆和穿衣风格挺容易辨别的。我们分散地坐在媒体展示区的长沙发上，那个人直接无视我们问她还有没有盖
章的那张纸。她说没有，那个人就问她是哪里的以及有没有名片。这时候作为大版主的紫柔自然冲出来说——我们都是微软中文技术论坛的！我相信那个人已经很无
语了，但又不知道我们是干什么的，还是问我们有没有名片。紫柔就告诉他，你上微软中文论坛技术就知道啦。他估计还是不知道微软中文技术论坛是什么，不过很
无奈地走了。真是难得见到有人如此直接搭讪的，而且还是在硬件工程大会上哦！</p>
<p>最后一个session解释时，会根据收回的反馈表抽奖，于
是我们就找了一个讲用VHD（虚拟硬盘）引导启动的session去听听。最后提问阶段，有人问了能否引导启动一个装有Linux的
VHD，speaker说VHD的技术都是开放的，虽然Microsoft没有实现的意向，但开源社区可以去做这样的事情。其实我有个问题，不过一直懒得
问，那就是Microsoft能否和Apple合作，做一个Mac上从VHD引导启动的东东，那么将来我就可以在MacBook装Windows
7并且仅仅装到VHD里。</p>
<p>会议结束后，MVP在讨论是否组织一起吃饭，因为我约了人，要去拿张CF卡，所以就先走了。明天我要去参加<a href="http://www.sd2china2008.com/">SD2C</a>，也就无法参加WinHEC了。</p>
<p>P.S.这篇文章不是因为MVP换取赠票而必须写的，而是因为换领礼品这件事实在搞得太不合理了，当时我就决定要写WinHEC的负面。</p><img src ="http://www.cnblogs.com/cathsfz/aggbug/1347018.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47958/" target="_blank">风声又起 Windows 7 RTM版7月13日完成</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>《Introducing Microsoft Silverlight 2》书评</title><link>http://www.cnblogs.com/cathsfz/archive/2008/11/23/1339551.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Sun, 23 Nov 2008 13:54:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/11/23/1339551.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1339551.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/11/23/1339551.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1339551.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1339551.html</trackback:ping><description><![CDATA[<p>和上次的<a href="http://www.cnblogs.com/cathsfz/archive/2007/10/04/914164.html">《Microsoft SQL Server 2005: 数据库基础由入门到精通》书评</a>一样，同样是MVP的书评活动。</p> <p>这本书适合什么人看？适合好像我这样的，对Silverlight 2感兴趣但是又不知道从何入手的人。整本书非常完整地覆盖到Silverlight每一个技术细节，当然这是Microsoft Press的习惯做法了。如果你想用Silverlight 2来做点什么，却发现开发过程中困难重重，有很多技术难点并不是看官方文档就能轻易得出答案的，那么我建议你来看看这本书。如果你已经相当熟悉Silverlight 2，但是仍想买一本手册随时参考一下，这本书也是一个不错的选择。</p> <p>这本书不适合什么人看？不适合做设计的人看，无论是界面设计还是大型项目设计。当然，这属于Silverlight技术发展至今的限制，它对界面设计人员的友好程度还是比不上Adobe的产品。作为一本Microsoft Press的书，只谈技术，完全回避了如何使用技术来实现优秀的设计，这也是挺无奈的事实。什么时候Silverlight才有好像CSS Zen Garden这样的事情发生？当设计师掌握Silverlight的时候。暂时Silverlight还是技术人员的工具，所以你在这本书里面看不到任何与界面设计思想有关的内容。同样，Silverlight至今仍然未出现什么最佳实践，所以你在书中只能找到完成某项任务的途径，却学不到完成某项任务的最佳实践。</p> <p>总的来说，Silverlight这门技术还有很长的路要走，但如果你想在必须使用这门技术，或者你有兴趣先看看，那么这本书都能够为你提供不少的帮助。</p> <p>P.S.如果你确定开始看这本书，或者已经在读了，请记得到作者的博客上<a href="http://blogs.msdn.com/webnext/archive/2008/10/09/introducing-silverlight-2-code-updates-for-rc0-rtm.aspx">下载RTM示例代码更新</a>。如果你想从侧面更多地了解这本书，可以在作者的博客上逛逛。</p> <img src ="http://www.cnblogs.com/cathsfz/aggbug/1339551.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47957/" target="_blank">乔布斯和埃利森</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>软件安装时到底是否应该让用户选择路径</title><link>http://www.cnblogs.com/cathsfz/archive/2008/11/04/1325937.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Mon, 03 Nov 2008 16:38:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/11/04/1325937.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1325937.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/11/04/1325937.html#Feedback</comments><slash:comments>35</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1325937.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1325937.html</trackback:ping><description><![CDATA[<p>周末和Google UX Team的<a href="http://blog.wangjunyu.net/">Junyu</a>聊天，说到Google现在统一使用Google Installer安装软件了，点一下Web上面的链接就自动开始下载安装并运行，开头我还以为是Microsoft的ClickOnce呢。由于安装过程是全自动的，就如同Microsoft的ClickOnce和Adobe的AIR一样，所以是不允许用户选择安装路径的。</p>
<p>到底不让用户选择安装目标好不好？我们认为用户分为三个层次：</p>
<ol>
    <li>入门用户 - 购买的是品牌机，可能就一个C:和一个用于一键恢复的D:。这时候有什么好选择的呢？当然是不选择最好了。</li>
    <li>熟练用户 - 经受过文件难以搜索或者忘记及时备份误删文件的种种磨难，学会了分门别类存放文件，自己有计划地把硬盘分成C:, D:, E:, F:四个盘。这时候你不满足他分类存放文件的习惯，他就会觉得很不爽了。</li>
    <li>高级用户 - 经过无数次备份与重装后又在返璞归真，只用一个C:，安装时多数选项都直接next，不选择安装路径。</li>
</ol>
当然，这个分类主要是针对Windows用户，因为Windows的成长过程就是典型的&#8220;问题少年&#8221;——不停地向用户抛出各种问题，这个你选什么啊，那个你选什么啊。对于Linux和Mac用户来说，大家早已习惯了安装时一路next到底。Linux命令行安装甚至不用next，只是本地编译的话有时候需要yes一下。
事实上，我认为给用户那么多选择是没必要的，文件的搜索与备份应该通过其它手段来改进，而不是让用户手动归档。要求用户理解文件系统，就如同要求调用者理解API实现方式一样，其实是很不合理的。
<p>&nbsp;</p>
<p>对于现在的品牌机而言，只有一个C:，培养Windows用户好像Mac用户那样，什么都装到一个盘里面，这才是正确的发展道路。Mac有Spotlight，Windows Vista现在也有不错的磁盘索引与搜索系统，将来实现了WinFS或许能做得更好。</p>
<p>现在Windows缺的是一套好用的备份与迁移方案，问题来源于Windows软件都没有规范地把可执行代码与数据分离存放，所以没有一种统一的模式来备份软件数据。如果Windows上面的软件能够按照Vista的指引，把数据都存储到C:\ProgramData，那么接下来的事情就容易多了。况且我之前也说过了，<a href="http://chinese.catchen.biz/2008/09/vista-uac.html">这也是UAC的意义所在</a>，有助于提高Windows的安全性。</p><img src ="http://www.cnblogs.com/cathsfz/aggbug/1325937.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47956/" target="_blank">Xbox Live将投放Silverlight广告</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>以服务器端为中心的 ASP.NET AJAX 模式 (Part 2 - Control)</title><link>http://www.cnblogs.com/cathsfz/archive/2008/10/30/1322620.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Wed, 29 Oct 2008 16:50:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/10/30/1322620.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1322620.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/10/30/1322620.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1322620.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1322620.html</trackback:ping><description><![CDATA[<p>在上一篇文章当中，也就是《<a href="http://www.cnblogs.com/cathsfz/archive/2008/10/26/1319932.html">以服务器端为中心的 ASP.NET AJAX 模式 (Part 1 - Behavior)</a>》，我们探讨了较为易用的Behavior模式。之所以说它较为易用，是因为它不涉及和原有Page处理流程的交互，即使访问网络也是访问独立的Web Service（包括Page上的[WebMethod]），因此和Page处理流程的设计绝对是正交的。但有时候我们需要的就是与Page处理流程的交互，这时我们不得不使用与服务器端逻辑紧耦合的Control了，这正是本次文章要讨论的内容。</p> <p>在基本的ASP.NET AJAX框架下，我们有三种方法来做基于Control的Ajax操作，它们分别是UpdatePanel、ICallbackEventHandler和IScriptControl，下面我们就分别看看它们的特点和使用场景。</p> <h3>UpdatePanel</h3> <p>UpdatePanel是与服务器端逻辑进行交互的多种方案中最易用的一个，甚至就不能称之为交互——你根本就不需要触及任何客户端逻辑。一个服务器端操作，经过UpdatePanel的&#8220;劫持&#8221;，变成了一个客户端操作，而这个客户端操作又直接调用对应的服务器端操作，就这么简单。</p> <p>如果用UpdatePanel来做一个带分支的选择对话框，那应该如何设计？思路可别跑到客户端的confirm方法上去，那可太绕了，或者说太不ASP.NET AJAX了。用UpdatePanel，就应该坚持它的理念，一切客户端操作都是幻象，所有操作其实都是在服务器端进行的，包括选择对话框。要按ASP.NET的思路来做，我会做一个选择对话框控件，它的实质可能是一个浮动层模拟的对话框，这属于实现细节，我们不用太关注。重点是，这个选择对话框的分支逻辑是完全在服务器端进行的，Async PostBack之后服务器端根据提交回来的数据决定如何触发事件。这样做整个分支选择的逻辑就是内嵌在Page处理流程当中的，不需要通过Cookies或者Session来做数据的中转媒介，避免了Page处理流程与更大作用域中的数据的紧耦合。</p> <p>UpdatePanel适用于逻辑完全在服务器端的开发，并且我建议使用UpdatePanel时也就把所有逻辑放在服务器端，不要去写一些混合服务器端逻辑与客户端逻辑的代码。有人会说，你看<a href="http://jeffreyzhao.cnblogs.com/">老赵</a>就很喜欢去动那个Sys.Net.WebRequestExecutor来改变UpdatePanel的行为啊，但其实这属于分层设计思想中的一部分，他去动那个东西改变的也就是一个分层内的逻辑，只要层与层之间的接口不变，具体实现是可以按需设计的。但如果你用了UpdatePanel，同时又用Cookies或者Session来传值，这就跨越了n个层，增加了不少耦合度。</p> <h3>ICallbackEventHandler</h3> <p>关于ICallbackEventHandler，我已经说过无数次了，重点还是你必须用Page处理流程来思考，只要你理解了Page处理流程，你就明白为什么ICallbackEventHandler在.NET Framework 2.0 Beta2中只有一个方法，而到了RTM要分拆成两个方法。具体可以参考《<a href="http://www.cnblogs.com/cathsfz/archive/2006/10/22/536414.html">ASP.NET 2.0 ClientScript Callback</a>》，我就不再重复了。</p> <p>如果用ICallbackEventHandler实现一个带分支的选择对话框，又如何做？和使用UpdatePanel的做法类似，我还是会做一个选择对话框控件，并且这个控件继承自ICallbackEventHandler。为这个控件编写JavaScript并实现ICallbackEventHandler接口时，我会确保JavaScript对Callback给出正确的调用参数，并在接口方法的实现中接收这些参数然后触发正确的事件，就这么简单。和UpdatePanel一样，不要偏离了ICallbackEventHandler的设计思想，它的处理流程必须是合并到Page处理流程中的，你的控件也就必须这样设计。</p> <p>至于在什么情况下选择ICallbackEventHandler？如果你有一个轻量级的Ajax操作，但使用UpdatePanel更新整个区域的HTML开销很大的话，那么你可以考虑使用ICallbackEventHandler。当然，前提是你懂得控件开发和JavaScript。</p> <h3>IScriptControl</h3> <p>这是最复杂的解决方案了，你需要实现一个Control的两个副本——一个服务器端的，一个客户端的。有一部分逻辑，是要在客户端和服务器端重复实现两次的，而另外一部分逻辑，只需要在客户端或服务器端之中的一个实现一次。IScriptControl的经典例子，当然是ASP.NET AJAX自带的Timer控件。它的计时器是纯粹的客户端逻辑，然而Tick事件却在服务器端触发，Async PostBack成为了两者之间的桥梁。当然，就Control本身而言，它并不在乎PostBack是不是异步的，Tick事件只因PostBack而触发。</p> <p>如果用IScriptControl来实现带分支的选择对话框，那将会和ICallbackEventHandler的版本十分相似，唯一不同的地方就是它在客户端的逻辑会被封装为一个Sys.UI.Control的派生类，而ICallbackEventHandler的客户端逻辑往往是不封装的。这样的好处显而易见，那就是代码更容易维护了，并且客户端的Control可以同样可以加入事件支持，并提供和服务器端一样的代码分支事件。要知道在CTP阶段的Timer控件，其客户端版本Sys.Timer（而非RTM的Sys.UI._Timer）是拥有tick事件的，和服务器端的Tick事件对应，只不过RTM取消了此项功能，因为ASP.NET AJAX 1.0的侧重点完全就是服务器端功能，客户端功能都被砍掉了。</p> <p>什么情况下选用IScriptControl？如果你认为你的客户端逻辑应该封装为Sys.UI.Control的派生类，那就选择IScriptControl吧。</p> <h3>小结</h3> <p>我们分别讨论了三种通过Control实现Ajax调用的方案，并且一再强调了设计必须基于Page处理流程，不要在此流程之外增加不必要的复杂度和耦合度。值得一提的是，有很多人质疑为什么要在Web上提供这样一个支持分支的选择对话框功能，我的看法是这样的：既然客户端软件的流程会有此功能，那么Web应用也有此功能就实在是太正常了，你删除blog post的时候问你一下是否确认删除，难道会有人觉得这个功能是设计错误？可能不同的只是表现形式而已，到底是confirm还是弹出层，甚至是一个专用的过渡页面。然而从用户体验的角度来说，这其实并不是最优的方案，多数时候用户删除就是确认删除，并不需要再问一次是否确认之类的愚蠢问题，但开发人员觉得用户错手删除的后果应当由用户自己承担，所以就做了这样一个对话框来推卸责任。真正好的用户体验是不需要确认的删除，但用户一定能够恢复，最好是按一下Ctrl+Z就可以了，然而对于开发人员来说还是有很多操作是无法做到可恢复的，这时候除了显示对话框也没有更好的解决方案了。</p> <p>最后，如果你喜欢我的文章，可以通过订阅feed来及时获得更新：</p> <ul> <li><a href="http://dotnet.catchen.biz">Cat in dotNET</a> (<a href="http://feedproxy.google.com/CatChen/dotNET">feed</a>) <li><a href="http://chinese.catchen.biz">Cat in Chinese</a> (<a href="http://feedproxy.google.com/CatChen/Chinese">feed</a>)</li></ul>  <img src ="http://www.cnblogs.com/cathsfz/aggbug/1322620.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47951/" target="_blank">Debian无视GNU创始人警告 接受Mono</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>以服务器端为中心的 ASP.NET AJAX 模式 (Part 1 - Behavior)</title><link>http://www.cnblogs.com/cathsfz/archive/2008/10/26/1319932.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Sun, 26 Oct 2008 12:33:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/10/26/1319932.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1319932.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/10/26/1319932.html#Feedback</comments><slash:comments>12</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1319932.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1319932.html</trackback:ping><description><![CDATA[<p>早在ASP.NET AJAX从CTP转向Beta再转向RTM时，看着客户端的Control被逐步放弃，与此同时ASP.NET AJAX Control Toolkit越来越多地使用Behavior，我就想深入说说ASP.NET AJAX的模式。不过由于我比较懒，所以这个话题只在《<a href="http://www.cnblogs.com/cathsfz/archive/2007/07/31/838240.html">理想的 ASP.NET AJAX (Part 2 - Server Centric)</a>》中一笔带过，没有深入讨论。今天看到volnet的《<a href="http://www.cnblogs.com/volnet/archive/2008/10/26/web-not-a-winform.html">我们究竟是否有在“Asp.net中模仿Winform的MessageBox ”的必要？</a>》，决定写一个文章系列来说说ASP.NET AJAX的模式。</p> <h3>什么是Behavior？</h3> <p>什么是Behavior？Behavior与Control有什么不同？这是首先需要回答的问题。</p> <p>在Windows开发当中，Behavior的概念是不存在的，有的只是Control。ASP开发连Control都没有的，到了ASP.NET才引入了Control的概念。为什么Ajax开发要引入Behavior这样的概念呢？因为Behavior意味着不需要改变原有的组件逻辑，而改变原有组件的逻辑在客户端往往是不可行的，至少是难以实现的。</p> <p>举个最简单的例子，一个&lt;input type=”text” /&gt;就是一个浏览器内部的对象，你无法扩展这个对象的类型，也无法为它加上新的属性与方法，至少并非所有浏览器都允许你在JavaScript中这样做。然而如果你想要让它加上auto-complete（或曰suggest）的功能，这是可以做到的，并且很多人都做过了，例如ASP.NET AJAX Control Toolkit的<a href="http://www.asp.net/AJAX/AjaxControlToolkit/Samples/AutoComplete/AutoComplete.aspx">AutoComplete</a>，或者是script.aculo.us的<a href="http://demo.script.aculo.us/ajax/autocompleter">autocompleter</a>。这些实现都基于同一种方式，就是尝试基于input已有的接口在它之外添加新功能，而非尝试继承input并在它之内添加功能。</p> <p>类似的做法在Ajax开发中普遍存在。例如说拖放吧，现在离HTML5拖放的全面普及不知道还有多远，所以大家都只能基于现有的鼠标事件来开发拖放功能。又或者说带有验证功能的输入框，无论是input还是select，无论是客户端验证还是服务器端验证，也都是基于现有HTML元素的事件来完成的。这一切都是Behavior。</p> <h3>什么情况下使用Behavior？</h3> <p>简单归纳，就两个条件：</p> <ol> <li>需要基于特定的一个组建进行扩展</li> <li>组建本身所处的环境缺乏可扩展性</li></ol> <p>单看第1个条件，我们有丰富的选择。很多人的第一反应就是继承自该组件，把扩展功能做到子类里面。熟悉设计模式的话，可能还会想到decorator pattern。然而在浏览器的环境当中，受到第2个条件的限制，继承或者decorator pattern都是不可行的。这时候，我们就需要使用Behavior了。</p> <p>在一定程度上，我们可以把Behavior看作一种折衷了的decorator pattern。在decorator pattern中，decorator也继承自组件，因此当一个组件使用decorator pattern后，我们就把decorator放在原组件所处的位置上，而原组件就成了decorator的一个子节点（基于树的角度来看的话）。如果再加一个decorator，原decorator就会如同一个普通组件那样再被封装一次。在多个decorator的情况下，decorator之间是串联的关系。Behavior本身不是一个浏览器内部的组件，它无法继承自input的基类，因此Behavior也不能串联。Behavior本身本也不会取代input在树中的位置，这使得Behavior可以并联起来——一个input可以有多个Behavior，例如一个是自动完成，另外一个是输入验证。</p> <p>总之，如果你原来的工作就是和UI打交道，并且熟练使用decorator pattern，那么在进行Ajax开发时把decorator pattern换成Behavior就可以了。</p> <h3>小结</h3> <p>回过头来看文章开头所说的MessageBox（或曰confirm）的问题，如果这个功能不需要对服务器端进行反馈，完全可以使用Behavior实现。ASP.NET AJAX Control Toolkit就有<a href="http://www.asp.net/AJAX/AjaxControlToolkit/Samples/ConfirmButton/ConfirmButton.aspx">ConfirmButton</a>这样一个东西，在客户端叫Behavior，在服务器端叫Extender，其实Extender就是对Behavior在服务器端做一下封装而已。当然，ConfirmButton不能完全实现MessageBox的功能，它只能在用户选择“取消”时取消整个提交操作，但不能够执行另外一个服务器端的代码分支。这算是Behavior的一个限制，就是它只能在客户端添加额外的功能，但是它不能影响到与服务器端的交互。但很多时候，正是这种限制确保了我们开发的组件是与服务器端解耦的。</p> <p>如果我真的需要一个与服务器端交互的客户端组件，例如支持调用服务器端代码分支的confirm，怎么办？这时候你就真的需要一个与服务器端Control对应的客户端Control了。请关注本系列的下一篇文章，《以服务器端为中心的 ASP.NET AJAX 模式 (Part 2 – Control)》。通过订阅feed，你可以及时获得文章更新：</p> <ul> <li><a href="http://dotnet.catchen.biz">Cat in dotNET</a> (<a href="http://feedproxy.google.com/CatChen/dotNET">feed</a>)</li> <li><a href="http://chinese.catchen.biz">Cat in Chinese</a> (<a href="http://feedproxy.google.com/CatChen/Chinese">feed</a>)</li></ul> <p>P.S.既然提到了Behavior和Extender的一一对应关系，就不得不说一下，服务器端的Extender就是真正的decorator pattern。</p><img src ="http://www.cnblogs.com/cathsfz/aggbug/1319932.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47950/" target="_blank">iPhone 3GS首发日创AT&T多项纪录</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>Vista 为什么要引入 UAC</title><link>http://www.cnblogs.com/cathsfz/archive/2008/09/21/1295597.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Sun, 21 Sep 2008 15:48:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/09/21/1295597.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1295597.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/09/21/1295597.html#Feedback</comments><slash:comments>21</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1295597.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1295597.html</trackback:ping><description><![CDATA[<p>UAC的目标就是：干扰用户！这其实不是我独创的，而是从<a href="http://twit.tv/">TWiT</a>上听到的一种见解。</p>
<p>UNIX的命令行有sudo，Mac在GUI上也能够在需要sudo调用时弹出对话框要求输入密码，然而这两者的出现频率比Vista的UAC要低得多。为什么UAC的出现频率那么高呢，难道是Vista的用户体验设计得有问题？显然不是这样，Vista的UAC就是设计来不停地干扰用户的。</p>
<p>干扰用户随之而来的是什么？就是用户觉得这个软件很烦，然后逐步放弃使用这个软件，转而使用同类软件中不那么烦人的。这个过程会逐步把经常进行UAC调用的软件从是场中淘汰掉，如果你的软件不希望被市场淘汰，你就必须尽量减少UAC调用，也就是减少系统调用。</p>
<p>最终，市面上大量滥用系统调用的软件会逐步消失，要么自身改进，要么自然淘汰，从而提高了Windows的安全性。等一下，这跟Windows的安全性有什么关系？假若你的代码要执行系统调用，同时你的代码是有漏洞的，就可能由于你的程序漏洞而导致系统受到攻击。但如果你的软件本来就不需要做任何系统调用，就算有漏洞也不会连累Windows，Windows也就显得安全多了。</p>
<p>P.S.当然，还有一些非系统调用也需要UAC，例如对C:\Program Files的写入操作。这是因为Vista认为这也是个危险操作，而程序应该将数据存放到C:\ProgramData来实现可执行代码与数据的分离。
</p>
<img src ="http://www.cnblogs.com/cathsfz/aggbug/1295597.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47949/" target="_blank">Silverlight开发大赛奖金高达10000美元</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>如何动态加载 JavaScript 与 CSS</title><link>http://www.cnblogs.com/cathsfz/archive/2008/09/02/1282415.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Tue, 02 Sep 2008 15:01:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/09/02/1282415.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1282415.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/09/02/1282415.html#Feedback</comments><slash:comments>22</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1282415.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1282415.html</trackback:ping><description><![CDATA[<p><a href="http://msmvps.com/blogs/omar/default.aspx">Omar AL Zabir</a>这位MVP总是喜欢搞些稀奇古怪同时又很实用的小东西，并且还十分值得参考。最近他就做了一个叫做<a href="http://www.codeplex.com/ensure">ensure</a>的小工具用于动态加载JavaScript、CSS与HTML，而且IE、Firefox、Opera、Safari都支持了，那么我们就来看看ensure是如何做到动态加载JavaScript与CSS的。</p>
<p>在介绍ensure内部的实现之前，让我们先来看看其功能：</p>
<blockquote>ensure({<br />
&nbsp; &nbsp; html: "popup.html",<br />
&nbsp; &nbsp; javascript: "popup.js",<br />
&nbsp; &nbsp; css: "popup.css"<br />
&nbsp; }, function() {<br />
&nbsp; &nbsp; Popup.show("hello world");<br />
&nbsp; }<br />
);
</blockquote>
<p>在这段代码中，ensure首先会确保popup.html、popup.js、popup.css这3个文件的加载，如果都没加载过ensure就会动态加载它们；如果已经加载过了，ensure不会再次加载。在确保这3个文件都加载后，ensure会调用后面的匿名函数，也就是执行Popup.show("hello world");。</p>
<p>接下来，就让我们看看ensure是如何动态加载JavaScript与CSS的。</p>
<h3>加载JavaScript</h3>
<p>在ensure当中，加载JavaScript分两种情况来执行，也就是Safari与非Safari这两种情况。</p>
<h4>在IE、Firefox、Opera中加载JavaScript</h4>
<p>在这三款浏览器中加载JavaScript，其实只需要创建一个script元素，把src指向要加载的URL，最后把script元素追加到head元素上，那就搞掂了。此项工作是在<strong>HttpLibrary.createScriptTag()</strong>中完成的。不过我们不仅仅要加载JavaScript，同时还需要知道它什么时候完成加载，这可以通过script元素的onload事件或onreadystatechange事件来实现。</p>
<h4>在Safari中加载JavaScript</h4>
<p>因为Safari 2不支持onload或者onreadystatechange，所以只能手动通过XHR把URL读去过来，然后再手动eval这段代码，这就带来了一个限制──只能加载本域的JavaScript文件。在ensure当中，eval的工作是通过<strong>HttpLibrary.globalEval()</strong>来完成的。为了让JavaScript代码在全局（global）上下文中eval，ensure还是使用了创建script元素的方法，并将要eval的JavaScript置于其内，最后把script元素追加到head元素内。</p>
<p>细心的人肯定要问，为什么<strong>HttpLibrary.globalEval()</strong>要如此设计，而非直接<strong>window.eval</strong>或者<strong>eval.call</strong>。这是因为，<strong>window.eval</strong>和<strong>eval.call</strong>都无法在IE6中实现和script标签加载JavaScript代码一模一样的效果，这两种做法的eval在IE6下仍然不是在全局上下文中执行的。搜索一下你就会发现一些相关的讨论，例如jQuery就曾经使用<strong>window.execScript()</strong>来完成此项任务。不过最终大家都发现添加script元素才是最好的跨浏览器解决方案，所以现在的jQuery和ensure都是如此实现的了。</p>
<h3>加载CSS</h3>
<p>相对于加载JavaScript而言，加载CSS就简单多了，而且方法也是类似的：在head元素内直接加入link元素就可以了。这也正是<strong>loadCSS()</strong>所完成的工作。</p>
<p>实际上，ensure没有确保CSS完成加载后再执行下去。这估计是因为浏览器都能够在CSS加载完成后自动应用到页面上，因此Omar AL Zabir就认为CSS的加载顺序是无关紧要的，不过假如CSS加载速度实在太慢，其实还是会影响显示效果的。</p>
<h4>在IE6中加载CSS</h4>
<p>这次需要特别照顾的是IE6，而非Safari。IE6在往head元素添加link元素时，必须在window的上下文中完成，因此添加link的函数通过call调用切换了上下文。</p>
<h3>总结</h3>
<p>实际上动态加载JavaScript与CSS都并不难，在大多数情况下只需要向head元素追加对应的子元素就可以了，只有Safari2和IE6这两款古老的浏览器是需要特殊照顾的。</p><img src ="http://www.cnblogs.com/cathsfz/aggbug/1282415.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47947/" target="_blank">微软告攒机商第一案胜诉</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>英语阅读推荐：创建Silverlight用户控件 &amp;amp; A*路径搜索中使用二叉堆</title><link>http://www.cnblogs.com/cathsfz/archive/2008/04/07/1139710.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Sun, 06 Apr 2008 20:07:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/04/07/1139710.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1139710.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/04/07/1139710.html#Feedback</comments><slash:comments>14</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1139710.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1139710.html</trackback:ping><description><![CDATA[<p>本期<a href="http://www.cnblogs.com/cathsfz/category/76688.html">Random Clippings</a>推荐两篇文章，一篇关于时下热门的Silverlight，而另一篇则是相对学术风格的二叉堆使用方法介绍。</p>
<h3>创建Silverlight用户控件 (<a href="http://weblogs.asp.net/scottgu/archive/2008/04/04/tip-trick-creating-and-using-silverlight-and-wpf-user-controls.aspx">Creating and Using Silverlight and WPF User Controls</a>)</h3>
<p>Scott Guthrie的文章总是那么简单易懂，有时候还有丰富的插图，简直就如看连环画一般容易，因此作为入门级别的英语阅读练习文章就最适合不过了。这次Scott讲的是如何创建Silverlight（或WPF）的用户控件，不过用的不是VS2008，而是Expression Blend。</p>
<p>文章开头应该只有一个词是需要查字典的，也就是&#8220;encapsulate&#8221;，而这正是重点。为什么我们需要用户控件？因为我们需要对重复的逻辑进行&#8220;封装&#8221;，这样才能复用描述这些逻辑的代码，&#8220;encapsulate&#8221;正是&#8220;封装&#8221;的意思。在看懂了这个词之后，就是看图说话了，文章一步一步地教你如何将一组已有的控件圈选出来，然后生成一个用户控件。我想你看完图片，就一定能读懂图片下方对应的文字，甚至你可以为这张图配上更好的英文描述。</p>
<p>在完成了用户控件的创建后，Scott以数据绑定为例介绍了其使用方式。首先，你要定义待绑定的类型，实例化并填充数据，最后在XAML中加上绑定语句，之后就是继续看图说话了。在此问大家一个问题，知道阅读英语文章时你的障碍除了来自于你个人的英语能力，还来自于什么吗？答案是文化背景的差异，甚至是文章结构风格的差异，中国人喜欢把重点（或者是真正要表述的意思）放到文章的最后，并称之为&#8220;压轴&#8221;，然而美国人的思维习惯恰好是相反的，打个比喻就是——&#8220;重点请先说，后面的可能我没时间听下去&#8221;。</p>
<p>文化背景差异造成的障碍就更大了，一些约定俗成的东西可能你无法理解，从而某些幽默可能你当成了事实，这就导致更大范围的误解了。举一个简单的例子，知道Scott这篇文章中的这张图片有什么好笑吗？</p>
<p><img src="http://www.scottgu.com/blogposts/extractusercontrol/step11.png"  alt="" /> </p>
<p>如果你理解使用信用卡时&#8220;ship to&#8221;和&#8220;bill to&#8221;的用途，并且你知道上述两个地址分别是哪里，那么你看到这样图就会哈哈大笑——&#8220;货物送往<a href="http://www.google.com/search?q=One+Microsoft+Way%2C+98052&amp;sourceid=navclient-ff&amp;ie=UTF-8&amp;rlz=1B3GGGL_enCN250">Microsoft</a>，并请<a href="http://www.google.com/search?q=One+Infinite+Loop%2C+95014&amp;sourceid=navclient-ff&amp;ie=UTF-8&amp;rlz=1B3GGGL_enCN250">Apple</a>买单&#8221;，这太恶搞了！（如果你还是不明白的话，请点击Microsoft与Apple上面的链接。）</p>
<p>在这里，你就看到了文化背景的影响。然而英语是一个不能逃避的问题，要说因为背景不同从而避免阅读英文文章，还不如多读从而逐步了解这些背景。在新东方，我碰到过一个教TOEFL的老师，他说他从来没到过美国读大学，但却对美国大学里面的生活情景了如指掌，例如如何申请宿舍，停车场是区分教工车位和学生车位，代号101的课程通常是入门课程，等等。为什么呢？他说因为他做了大量的TOEFL听力题，而一般听力题都是这类情景下的对话，因此即使你不是生活在那种文化中你也可以对它有所掌握。</p>
<h3>A*路径搜索中使用二叉堆 (<a href="http://www.policyalmanac.org/games/binaryHeaps.htm">Using Binary Heaps in A* Pathfinding</a>)</h3>
<p>如果你要深入研究某些技术，不可不勉地你必须阅读一些很学术的英文文章，这篇文章就是一个例子。</p>
<p>在这篇文章中，首先说的是一维列表的解决方案，并且说明为什么效率是问题。然后解决方案自然是binary heap，这就要说说binary heap的二叉树结构如何能够保存在一维数组中。接下来是对binary heap添加删除元素操作的简单说明，并且解释了为什么binary heap的效率比简单的一维列表要高。请注意，到此为止文章都没有深入binary heap的实线细节，这就是美国人比较习惯的写法，先briefing一次，再深入，因此后面的部分你看不懂也至少懂了一般。如果在前面就直接引入细节，你看不懂，就算后面还是有显浅的内容你可能也会错过。</p>
<p>在文章的后半部分中，作者深入地解释了binary heap的数据结构、操作算法，读完这一部分你就知道如何在自己的程序中实现binary heap了。并且在文章的最后，还有若干个延伸阅读的链接。通过读这篇文章，你就能了解到美国人一般是如何组织科学类文章的主干是如何安排的。</p>
<p>最后，如果你觉得Random Clipping的推荐不错，可以考虑长期<a href="http://feeds.feedburner.com/CatChen/dotNET">订阅Cat in dotNET</a>，这样你将不会错过将来的任何一篇推荐。</p>
<img src ="http://www.cnblogs.com/cathsfz/aggbug/1139710.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47946/" target="_blank">Mono这只猴子招惹了谁？</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item><item><title>救救 Web Developers ，拒绝 IE6 ！</title><link>http://www.cnblogs.com/cathsfz/archive/2008/04/06/1139262.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Sun, 06 Apr 2008 08:41:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/04/06/1139262.html</guid><wfw:comment>http://www.cnblogs.com/cathsfz/comments/1139262.html</wfw:comment><comments>http://www.cnblogs.com/cathsfz/archive/2008/04/06/1139262.html#Feedback</comments><slash:comments>59</slash:comments><wfw:commentRss>http://www.cnblogs.com/cathsfz/comments/commentRss/1139262.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/cathsfz/services/trackbacks/1139262.html</trackback:ping><description><![CDATA[<p>这是最近的一场于web developers相关的campaign，官方站点是<a href="http://www.savethedevelopers.org/">SaveTheDevelopes.org</a>。在上面你可以下载一段脚本和对应的图片，放到你自己的网站上，然后只要有用户使用IE6访问你的网站，他就会看到一个小小的提示框，建议他升级到IE7或选用其它非IE浏览器。当然，你也可以直接引用SaveTheDevelopers.org上面的脚本文件，但大家都知道潜在的风险，因此最好不要这样做，如果你有自己的空间的话最好还是下载下来放到自己的空间上。</p>
<p>为了支持这个campaign，我特意为他们做了一个<a href="http://www.savethedevelopers.org/lang/zh/">中文翻译</a>。我本来是想和<a href="http://www.awflasher.com/blog/">aw</a>一起做的，不过他们把内容发给我的那个晚上找不到aw，于是有翻译问题就直接找<a href="http://blog.wangjunyu.net/">Junyu</a>问了，最后在Junyu的帮忙下把翻译搞定了。我在想是不是应该多做一个粤语翻译，这样会比较好玩，反正也就一个晚上的事情而已。</p>
<img src ="http://www.cnblogs.com/cathsfz/aggbug/1139262.html?type=1" width = "1" height = "1" /><br/><br/>--------------------------<br/>新闻：<a href="http://news.cnblogs.com/n/47945/" target="_blank">Firefox 3.5本月晚些时候即首次升级</a><br/>网站导航: <a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://dotnet.cnblogs.com" target="_blank">.NET频道</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/q/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://space.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://zzk.cnblogs.com" target="_blank">找找看</a>]]></description></item></channel></rss>