顿悟
捕捉技术与生活中的灵感
2016-08-05T04:07:35.000Z
http://sparkshining.com/
流星泉
Hexo
测试的道与术
http://sparkshining.com/2016/08/05/test-methodology/
2016-08-05T04:05:08.000Z
2016-08-05T04:07:35.000Z
<p>《Google软件测试之道》这本书2013年就已经出版了(英文版出版于2012年)。以前幼稚地以为,开发人员对于测试方法论没必要系统的学习,只要知道怎么与测试人员一起工作就好了,所以一直没有读。最近由于一些机缘巧合,拜读了一下,大有收获。
<br>注意,本文并非《Google软件测试知道》的读书笔记,里面有一些笔者个人思考的结果,如果想了解原书观点,还请阅读原书。</p>
<h3 id="测试之道">
<a href="#测试之道" class="headerlink" title="测试之道"></a>测试之道</h3>
<p>“道”即是世界观。每次听到新的道,都是一次对世界观的颠覆。这种打通任督二脉的爽快,正是笔者每次阅读的动力。《Google软件测试之道》书名中的“道”字其实是意译的结果(英文原版书名为《Google是如何测试软件的》,用来致敬《微软是如何测试软件的》),但是却非常贴切。
<br>让我们开始毁三观吧。
<br>如果对于测试只能问一个问题,然后围绕这个问题的答案来安排所有测试活动,笔者认为,这个问题应该是“测试的目标是什么?”。回答了这个问题,也就理解了测试之道。
<br>有人说测试是为了发现产品中的问题,更有经验的人说测试是为了保证产品的质量。笔者认为,这两者都不贴切。测试的目标,跟任何企业活动的目标一样,是减少企业的成本、增加企业的收入,从而提高企业的利润,简而言之,就是
<strong>让企业赚钱</strong>。换句话说,
<strong>不能帮企业赚钱的测试就是耍流氓</strong>(注,这里的钱指广义的企业价值,而非狭义的货币资本)。
<br>有人说,这不是废话吗?还真不是废话。因为只有这个目标,才能真正指导决策者在两难的环境下做出正确的抉择。举个例子,自动测试用例该不该写?看起来这似乎是一个绝对政治正确的说法,实则不然。我们套用前面提出的测试的目标,就很容易发现自动测试用例不见得是一件好事。如果一个项目还在原型阶段,存在很大被终止(出于市场、用户习惯等原因)的可能,这时编写自动测试用例就是完全没有必要的。
<br>在《Google软件测试之道》中有着大量的例子和指导方针。这些解决方案的思路都是围绕着“减少成本、提高收入”来展开的。</p>
<h3 id="测试之术">
<a href="#测试之术" class="headerlink" title="测试之术"></a>测试之术</h3>
<p>了解了“道”,也就有了行动纲领。那么,在测试的工作中,该如何应用呢?《Google软件测试之道》一书中,给出了大量的指导方针和示例。笔者在此摘录其中几个笔者曾经有所疑惑的问题。</p>
<h4 id="测试应该由谁进行?">
<a href="#测试应该由谁进行?" class="headerlink" title="测试应该由谁进行?"></a>测试应该由谁进行?</h4>
<p>有人说测试就是测试人员的事;有人说开发人员就应该保证自己代码的正确性,因此开发就应该进行测试。原作者给出的答案是:以上都不对。前者太狭隘,后者太理想主义。在实践中,绝大多数开发人员是不能站在用户的角度看问题的。这是一个思维模式的问题,并不是喊喊口号就能解决的。更具可操作性的办法是,专门招聘能够站在用户角度看问题的人。Google的做法是三权分立:开发辅助团队(测试开发工程师)负责搭建测试的基础架构,开发人员负责编写测试代码,而由测试工程师负责站在用户的角度进行自动化或手动测试。</p>
<h4 id="怎么决定测试什么?">
<a href="#怎么决定测试什么?" class="headerlink" title="怎么决定测试什么?"></a>怎么决定测试什么?</h4>
<p>由于人的精力有限,在从事每一个行动前,找出行动目标,比展开行动更重要。即使是同一款产品,不同特性所分配的精力也是不同的。那么,如何决定怎么分配精力呢?答案是,根据测试之道,最能让企业赚钱的产品特性,就应该分配最多的精力。
<br>Google在实践中,为了发现哪些产品特性更重要,采用了列举ACC的方式。A指产品的属性(attribute),表示产品对用户的核心价值;第一个C是组件(component),表示产品系统所组成的模块;第二个C是能力(capability),表示系统能完成的动作,通常是一个产品属性和组件的组合。如果一个能力没有对应的产品属性,那么这个能力是没什么用的。利用这一点可以验证产品特性是否重要。
<br>了解了产品的能力之后,根据产品能力来安排精力,这种做法最有效率。</p>
<h4 id="测试什么时候开始?">
<a href="#测试什么时候开始?" class="headerlink" title="测试什么时候开始?"></a>测试什么时候开始?</h4>
<p>系统的质量是内建的,不是外加的。外部的测试只能发现问题,不能改变系统质量,就像温度计不能帮人解暑一样。因此,测试的效能主要体现在两点:一是保证早期决策的正确性以影响后面的决策,这是因为,后面决策往往依赖于前面的决策,前面决策失误所造成的影响要远大于后面决策;二是保证系统质量不会明显下降。无论是哪一种,都要求测试必须尽早加入到产品当中。一旦测试对于产品出现意义(见前文是否应该编写自动测试一例),需要编写测试,那么测试的关键节点(提交,运行)就应该跟开发的关键节点同时进行,而不是滞后。</p>
<h4 id="如何让开发人员编写测试代码?">
<a href="#如何让开发人员编写测试代码?" class="headerlink" title="如何让开发人员编写测试代码?"></a>如何让开发人员编写测试代码?</h4>
<p>作为一名开发人员(曾经是,现在也是),深切地能够体会到当有人强制让你编写测试时的那种痛苦。开发人员不愿意编写测试代码主要有两个原因:投入高、效果差。这并不是开发人员“玻璃心”或者性格问题造成的:如果编写测试代码要占掉比编写代码多很多的时间,而且还要经常花精力来改正一些失败的测试,又看不到对产品质量的提升,任谁都不会喜欢去做这样的事情的。
<br>要解决这个问题,要多从流程上下功夫:</p>
<ol>
<li>不盲目追求代码覆盖率。要认识到代码的变动机会是不同的。变动机会大的代码代码覆盖率越高,维护成本越高。如果维护成本高于收益(不能帮企业赚钱的测试就是耍流氓)的时候,就应该停下来反思一下了。在Google测试能力评级的最高级标准中,整体覆盖也仅有60%而已。</li>
<li>根据产品能力(见“怎么决定测试什么”一条),删掉不重要的测试代码。如果认为测试代码留在产品里所带来的维护成本,大于所带来质量提高的收益,大胆地删掉他们吧。</li>
<li>即时加入重要功能的测试代码并修正失败的测试。这样开发人员可以知道,测试代码真的在保护着他们正在开发的应用的质量,而不是做做样子的面子工程。</li>
</ol>
<h4 id="如何分配各种自动化测试的比例?">
<a href="#如何分配各种自动化测试的比例?" class="headerlink" title="如何分配各种自动化测试的比例?"></a>如何分配各种自动化测试的比例?</h4>
<p>我们平常说的单元测试、集成测试与端到端测试,在Google被分为小型测试、中型测试和大型测试。简单来说,小型测试不依赖外部系统,全靠mock,用于验证代码单元功能;中型测试会依赖外部系统,但是并不涉及多UI或者网络服务的对接,用于验证多模块之间的交互;大型测试包含多个系统,通常用于验证系统作为一个整体的工作。小/中/大测试的分配比例为70%/20%/10%。</p>
<p>以上只是笔者的几点心得。原书提供了大量可操作的流程和工具,实在是不可多得的一本好书。</p>
《Google软件测试之道》这本书2013年就已经出版了(英文版出版于2012年)。以前幼稚地以为,开发人员对于测试方法论没必要系统的学习,只要知道怎么与测试人员一起工作就好了,所以一直没有读。最近由于一些机缘巧合,拜读了一下,大有收获。
关于招聘与面试
http://sparkshining.com/2016/08/03/about-interview/
2016-08-03T12:41:13.000Z
2016-08-03T09:55:58.000Z
<p>由于下面开出来了几个新职位,最近工作重心中很大一部分都用来招人与面试。一开始没经验,都是照着别的公司来,照猫画虎。随着经验的积累,慢慢感觉出好像哪里不太对。思考了很久,大概有了一套思路。笔者不是职业的人力,但愿这些怪谈不会太贻笑大方。</p>
<h3 id="雇员与企业">
<a href="#雇员与企业" class="headerlink" title="雇员与企业"></a>雇员与企业</h3>
<p>当一个面试人员坐在面试者的面前的时候,他就不再是他个人,而是企业的形象代言人。所以面试官与面试者的关系,不是简单的两个人的关系,而是人与企业的关系。这就是为什么本文要先谈雇员与企业关系的原因。值得注意的是,不同行业、不同地点下,雇员与企业关系都会有所不同。本文所讨论的是中国一线城市里IT行业的情况,至于其他行业、其他地点,笔者经验所限,不敢妄加评论。
<br>改革开放早期,笔者父辈一代的雇员是依附于企业的。这是因为雇员的养老医疗甚至住房教育都是靠企业来解决的。然而,随着福利制度的调整,养老医疗住房教育都改由社会或者市场来解决。加之找工作成本的下降,一线城市IT行业现在很难见到雇员依附企业的情况。如果工作不满意,完全可以换一家企业。在这样的条件下,企业和雇员之间是合作共生的关系。企业提供薪水、培训、发展机会;雇员提供劳务,并提高劳动效率,帮助企业节省成本。
<br>另一方面,随着信息科技的迅猛发展,企业对于IT从业人员的需求,要远大于培养IT从业人员的速度。节节攀升的薪资幅度侧面印证了,这是一个卖方市场。资本的持有并没有给企业带来额外的谈判筹码。
<br>因此,在招聘过程中,雇员与企业是一个双向选择的关系,
<strong>双方是平等的</strong>。
<br>也正是由于雇员与企业之间是平等的关系,本文采用了“企业”这两个词来与“雇员”对应,而没有采用“雇主”或者“用人单位”这两个明显带有偏向性的词汇。因为企业既不是“主”,更不能将雇员物化而“用”。同样,本文选用了“面试人员”和“面试者”,而不是“面试官”与“求职者”。</p>
<h3 id="面试的作用">
<a href="#面试的作用" class="headerlink" title="面试的作用"></a>面试的作用</h3>
<p>面试绝对不是单向的。当面试人员考察面试者的时候,面试者也在通过面试人员而了解企业。鉴于交流时间的短暂,面试人员所体现出来的非专业行为会严重影响到面试者对于企业的印象。因此,如果一个没什么面试经验的面试人员无理由地表示对于面试毫无压力,很可能这个面试要出问题。
<br>那么,面试人员和面试者双方之间是什么关系呢?笔者认为,是合作的关系。他们是在合作解决一个问题,即面试者是否适合这个岗位。注意,在这个问题上,面试者跟面试人员并不存在对立关系。因为工作在不合适的职位对面试者的心理和职业成长所带来的负面影响是极大的。即使可以短时拿到一笔可观的收入,面试者并不具有安全感,因为企业可以随时把他裁掉。
<br>要注意的是,既然面试双方是在合作,那么面试就不是一场选出优胜者的智力竞赛,那么,
<strong>一方感觉到占有智力优势所带来的快感对于面试没有任何帮助</strong>。这一条对于双方都是适用的。在面试中,如果任何一方感觉到“你被难住了吧”,就说明这一方已经偏离了面试的目的了。</p>
<h3 id="有效地面试">
<a href="#有效地面试" class="headerlink" title="有效地面试"></a>有效地面试</h3>
<p>面试双方对于面试所投入的成本是很高的,双方都会投入大量的时间成本准备或参与面试活动。因此,面试一定要高效,不在不合适的机会上浪费时间对双方都有利。
<br>面试的起点是简历的评估。如果一个面试者能够通过简历的筛查,那么只有两种可能的情况:一种可能是该面试者简历当中已经涵盖了该岗位的核心需求,在这种情况下,面试要做的就是验证简历的真实性,比如是否无中生有,是否夸大经历等等;另一种可能是该面试者简历当中未提及该岗位的核心需求,这时,面试要做的是了解面试者在该核心需求方面的能力。
<br>不管是上述两种情况中的哪一种,面试人员所要检查的东西都跟该岗位的核心需求相关。也就是说,
<strong>面试人员的问题,体现了面试人员对于该岗位的理解</strong>。如果面试问题奇葩,只能证明面试人员不够专业而且并不理解需要什么样的人。</p>
由于下面开出来了几个新职位,最近工作重心中很大一部分都用来招人与面试。一开始没经验,都是照着别的公司来,照猫画虎。随着经验的积累,慢慢感觉出好像哪里不太对。思考了很久,大概有了一套思路。笔者不是职业的人力,但愿这些怪谈不会太贻笑大方。
浏览器兼容之聚焦相关的事件
http://sparkshining.com/2016/05/31/focus-management-in-browsers/
2016-05-31T00:28:49.000Z
2016-05-31T02:58:21.000Z
<p>常年做前端,哪能不恶心?虽然这两年浏览器兼容性是越来越好了,但还是会偶尔遇到一些其他问题,让你欲哭无泪,求死不能。今天我们就来八一八浏览器中跟聚焦(focus)有关的事件。
<br>这个世界上有两种人:使鼠标的和使键盘的。鼠标是二维定位,精确度高;键盘是十个输入(像我爸爸那样的修炼二指禅的除外),速度非常。正因为各有利弊,所以虽然双方都看对方不太顺眼,也只能凑合着过。
<br>焦点这一设计是为键盘使用者准备的:当你双手飞速敲打键盘的时候,有一个问题也就来了:电脑怎么知道你在往哪儿输入呢?于是,在网页中,我们定义了一个激活元素(active element),键盘输入都是发往这个元素的。当用户用鼠标点击另一个可聚焦的元素或者按Tab键时,网页激活的元素也会响应的发生变化。</p>
<p>在浏览器中,跟焦点有关的API有以下这些(见
<a href="https://w3c.github.io/html/index.html" target="_blank" rel="external">HTML5 Editor’s draft</a>):
<br>1个属性——document.activeElement,用于获取文档中当前获得焦点的元素
<br>1个方法——element.focus(),用于让元素获得焦点
<br>若干
<a href="https://www.w3.org/TR/uievents/#events-focusevent" target="_blank" rel="external">事件</a>——blur, focus, focusin, focusout</p>
<p>现在让我们看看各浏览器的表现吧。为了易于对比,笔者使用了Chrome 50, Firefox 46, IE 11和Edge 20作为测试对象。Safari因为跟Chrome采用同样的引擎,因此结果与Chrome 50完全一致,这里不单独列出。测试代码见
<a href="https://jsfiddle.net/sasyomaru/yamkxf26/" target="_blank" rel="external">这里</a>。</p>
<h4 id="测试1-鼠标点击改变焦点">
<a href="#测试1-鼠标点击改变焦点" class="headerlink" title="测试1 鼠标点击改变焦点"></a>测试1 鼠标点击改变焦点</h4>
<p>初始时焦点位于输入框1上,通过点击输入框2将焦点移到它上面。</p>
<p>各浏览器中事件触发先后顺序如下:</p>
<table>
<thead>
<tr>
<th>浏览器</th>
<th>事件链</th>
</tr>
</thead>
<tbody>
<tr>
<td>Chrome 50</td>
<td>输入框2上mousedown -> 输入框1上blur -> 输入框1上focusout -> 输入框2上focus -> 输入框2上focusin -> 输入框2上mouseup -> 输入框2上click</td>
</tr>
<tr>
<td>Firefox 46</td>
<td>输入框2上mousedown -> 输入框1上blur -> 输入框2上focus -> 输入框2上mouseup -> 输入框2上click</td>
</tr>
<tr>
<td>IE 11</td>
<td>输入框2上mousedown -> 输入框1上focusout -> 输入框2上focusin -> 输入框1上blur -> 输入框2上focus -> 输入框2上mouseup -> 输入框2上click</td>
</tr>
<tr>
<td>Edge 20</td>
<td>输入框2上mousedown -> 输入框1上focusout -> 输入框1上blur -> 输入框2上focus -> 输入框2上focusin -> 输入框2上mouseup -> 输入框2上click</td>
</tr>
</tbody>
</table>
<p>所有浏览器的焦点转移行为都发生在鼠标压下的阶段(mousedown),鼠标抬起(mouseup)对焦点没有任何影响。
<br>可以看出主要分歧在于focusout和focusin的触发时间,Chrome认为两者只是相应事件(blur和focus)的冒泡版本,因此在相应事件后触发;而IE认为两者其实是预览事件(preview),因此在相应事件前触发;Edge是一个折中;而Firefox干脆就没有这两个事件(莫非因为看不清楚趋势,先明哲保身一下?)。
<br>值得一提的是,还未定稿的
<a href="https://www.w3.org/TR/uievents/#events-focusevent-event-order" target="_blank" rel="external">界面事件规范</a>中的定义,似乎与IE的行为更为一致。</p>
<p>那么,在这么多事件当中(mousedown / mouseup / click,focus / blur / focusin / focusout),焦点到底是什么时候转移的呢?
<br>这个问题上,浏览器自动分为了两个阵营:IE(包括Edge)一方当focusout触发的时候,焦点就已经转移到了新的输入框上;非IE(Firefox / Chrome)一方在失去焦点阶段内,焦点先回到默认值body上,而在获得焦点阶段,焦点才会转移到新的输入框上。</p>
<p>与所有其他浏览器都不同,IE 11默认情况下所有display为block或者table的元素都可以获得焦点。但Edge却与Chrome等非IE浏览器一致,典型的兄弟反水啊。</p>
<h4 id="测试2-Tab键改变焦点">
<a href="#测试2-Tab键改变焦点" class="headerlink" title="测试2 Tab键改变焦点"></a>测试2 Tab键改变焦点</h4>
<p>初始时焦点位于输入框1上,通过按下键盘上Tab键将焦点移到下一个输入框(输入框2)上</p>
<p>各大浏览器的事件触发与鼠标改变焦点基本一致,只不过触发的是keydown和keyup,而不是mousedown或mouseup,也没有click事件。不过这中间还是有两点值得注意的。</p>
<ol>
<li>Firefox 46会在紧随着keydown之后,再触发一次keypress;而其他浏览器不会。</li>
<li>如果是按住Tab键进行连续的焦点改变时,会在每个聚焦元素上分别触发keydown(Firefox会额外触发keypress),但是除最后一次外不会触发keyup事件。</li>
</ol>
<p>笔者试图寻找规范中的定义,可惜没有找到。其实这也正常,定义规范本就是一个特别繁杂的工程,难免挂一漏万。更何况规范定义好了,各家浏览器厂商是否执行还是个未知数。HTML5搞出来两套不就是因为大家没法一起玩的结果么?论天下大事,分久必合,合久必分,习惯就好。</p>
常年做前端,哪能不恶心?虽然这两年浏览器兼容性是越来越好了,但还是会偶尔遇到一些其他问题,让你欲哭无泪,求死不能。今天我们就来八一八浏览器中跟聚焦(focus)有关的事件。
经济危机中的倒牛奶行为
http://sparkshining.com/2015/12/16/dump-milk/
2015-12-16T12:27:20.000Z
2016-05-16T10:15:16.000Z
<p>最近在看经济学的书,确实脑洞大开。很多以前解释不清的问题,终于有了连贯的逻辑链条。</p>
<p>当年学马克思主义政治经济学的时候,那个经济危机中美国奶牛农场主把牛奶倒入海中的故事甚是触目惊心。然而,笔者完全无法认同“这反映了资本家剥削穷人的本质”这种观点。商人最懂得做顺水人情从而获取长远利益。因此,一定存在着一种理性且合乎逻辑的机制,来促成农场主做了如此的决策。</p>
<p>笔者看过的一篇材料显示,在1931年-1934年在美国中西部发生了奶农罢工事件,在这次事件中,部分有组织的罢工奶农强行倾倒其他奶农的牛奶。然而,一者笔者无法验证这则材料的真伪,二者倒牛奶事件不止发生在上述时间和上述地区,因此这个事件不在笔者讨论之列。</p>
<p>先让我们看看一个很官方的解释:倒牛奶会改变供求关系从而影响价格。这个答案相当具有迷惑性,因为推翻这句话就意味着推翻经济学大厦基础的需求曲线。但是请注意,这里有个重大的思维陷阱:这个答案陈述本身没有任何问题,而漏洞在于,这个答案跟问题之间并没有必然联系。
<br>首先,价格从来都不是资本家考虑的因素,利润才是。所以从来就没有“影响价格”这种需求,人们的需求是不要影响利润。
<br>当我们关注利润的时候,就出现了一个无法解释的问题,那就是如果倒牛奶的人不属于某个拥有强大统筹能力的组织,就会面临一个风险:要是其他人不倒,我不就白倒了?然而迄今为止,如果我们不考虑有组织的罢工奶农这件事,笔者没有看到任何资料表明这些倒牛奶的人是有组织的。
<br>当然这些都是小问题,这个观点最直接的漏洞在于,倒牛奶是否是为了改变供求关系。我们必须承认,倒牛奶确实会改变供求关系,但不见得这就是倒牛奶的原因。这就好比我吃火锅会花人民币,但是我吃火锅在绝大多数情况下不是因为我想花掉一笔钱,而是因为我想吃顿饭。衡量一件行为是否是一个原因造成的最简单的办法,就是看这个原因不再存在时,这个行为是否还会继续。根据原文,在奶农倒牛奶的同时,有些人是想喝牛奶喝不到的,也就是说倒牛奶行为并没有因为过剩需求的出现而停止。因此,改变供求关系是倒牛奶的”果”而不是“因”。如果倒牛奶是为了改变供求关系,那么这个时候明智的牛奶生产者应该做的是:找到这批喝不到牛奶的人并把牛奶以极低的价格卖给他们。</p>
<p>如果当你仔细地开始考虑“找到这批喝不到牛奶的人并卖给他们”这个解决方案的时候,也许你已经想到我要说的了:倒牛奶是交易成本高于收益的结果。这里交易成本高体现为三个方面:
<br>一是信息获取成本,生产牛奶的人没有办法获知谁想喝牛奶喝不到;这个问题在互联网时代似乎更容易解决一些,淘宝小店一开需求立即来,只要价格够低不怕没人买。
<br>二是仓储成本,由于牛奶保质期短,不易储藏,所以生产牛奶的人一般不会大规模的建立仓储,而是依赖快速销售;临时建立仓储成本太高,生产牛奶的人无法支付。假如出现了一种技术,喊一嗓子把牛奶扔到田地里就能保存三十年,没准还会有人趁机抄底呢。
<br>三是运输成本,哪怕生产牛奶的人愿意施舍给别人,运输的费用也是一笔不菲的支出。有两个假想的实验可以解释这一问题:假如倒牛奶的地方旁边就有人拿着容器等着喝牛奶,是否生产牛奶的人会阻止这一行为?假如倒牛奶的地方远离农场,是否生产牛奶的人会愿意将牛奶远距离运输到倾倒地点进行倾倒?显而易见,是运输成本阻碍了善良。</p>
<p>综上,倒牛奶是由于供求关系突变,导致供方交易所得低于交易成本的结果。跟人性没有任何关系,绝对的“你跺你也麻”。至于这种突变是否是由于社会制度造成的,以及正确的调控手段是什么,就不在本文讨论之列了。</p>
当年学马克思主义政治经济学的时候,那个经济危机中美国奶牛农场主把牛奶倒入海中的故事甚是触目惊心。然而,笔者完全无法认同“这反映了资本家剥削穷人的本质”这种观点。商人最懂得做顺水人情从而获取长远利益。因此,一定存在着一种理性且合乎逻辑的机制,来促成农场主做了如此的决策。
Lambda表达式中的arguments
http://sparkshining.com/2015/11/02/arguments-in-lambda/
2015-11-02T06:07:58.000Z
2015-11-02T06:22:41.000Z
<p>最近在一个项目中遇到了这个坑,记下来,以警来者。</p>
<p>JavaScript在ES2015中初次引入了箭头函数(Arrow Function)这一俗称“lambda表达式”的概念,用来解决代码中this容易用错的问题(注意,this的定义从来就没有歧义,但确实是有点“反直觉”,容易被曾使用过其他语言的开发者误用)。想要了解这一特性,可以参阅
<a href="http://es6.ruanyifeng.com/#docs/function" target="_blank" rel="external">这篇介绍ES6的文章</a>中“箭头函数”一节(原文中已对本文所述的问题做了介绍)。</p>
<p>在
<a href="http://www.ecma-international.org/ecma-262/6.0/#sec-arrow-function-definitions-runtime-semantics-evaluation" target="_blank" rel="external">ES2015语言规范</a>中,明确规定了箭头函数不定义arguments的本地绑定。换句话说,如果在箭头函数中使用了arguments,则其值为生成该函数的参数列表,比如下面代码:
<br>
<figure class="highlight javascript">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td>
<td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">getFunction</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">return</span> () => <span class="built_in">console</span>.log(<span class="built_in">arguments</span>[<span class="number">0</span>]);</div><div class="line">}</div><div class="line"><span class="keyword">var</span> praise = getFunction(<span class="string">'You suck!'</span>);</div><div class="line">praise(<span class="string">'You are great!'</span>);</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>如果在支持ES2015的浏览器(比如最新版的Chrome,笔者版本为46.0)上执行时你会发现,控制台会打印如下结果:
<br>
<figure class="highlight plain">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div></pre></td>
<td class="code"><pre><div class="line">You suck!</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>而不是直觉上的You are great!</p>
<p>本来这个问题比较简单,规范上面这么定义的,这么做就是了。偏偏有人来搅局了:在ES2015尚未发布之前,
<a href="http://www.typescriptlang.org/" target="_blank" rel="external">TypeScript</a>(一个微软推出的语言,为JavaScript的超集,由于与Angular 2和React的合作而逐渐被人所知)就定义了类似的功能并最初起名为Lambda(现已改为采用跟ES2015一致的术语“箭头函数”,但截至笔者写作本文时,有些文档尚未更新,比如
<a href="http://www.typescriptlang.org/Handbook#functions-lambdas-and-using-39this39" target="_blank" rel="external">这里</a>)。在TypeScript中,
<strong>起初</strong>并未对Lambda中的arguments做任何处理,因此,arguments所指向的就是最终函数的参数列表。也就是说如果你在TypeScript中编写了上文的程序,得到的打印结果就是You are great!。
<br>幸运的是,TypeScript的开发者即时地意识到了这一点(见
<a href="https://github.com/Microsoft/TypeScript/issues/1609" target="_blank" rel="external">这个TypeScript问题</a>)。因此,如果在lambda中使用了arguments,你会得到如下的结果:</p>
<ol>
<li>在TypeScript 1.5之前,该代码顺利通过编译,生成与E2015语义不一致的代码</li>
<li>在TypeScript 1.5之后,如果编译目标是ES6,该代码会顺利通过编译,生成箭头函数</li>
<li>在TypeScript 1.5之后,如果编译目标是ES3或者ES5,则该代码不会通过编译,错误如下:
<figure class="highlight plain">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div></pre></td>
<td class="code"><pre><div class="line">error TS2496: The 'arguments' object cannot be referenced in an arrow function in ES3 and ES5. Consider using a standard function expression.</div></pre></td>
</tr>
</table>
</figure>
</li>
</ol>
最近在一个项目中遇到了这个坑,记下来,以警来者。JavaScript在ES2015中初次引入了箭头函数(Arrow Function)这一俗称“lambda表达式”的概念,用来解决代码中this容易用错的问题。
我的中医观
http://sparkshining.com/2015/10/19/traditional-chinese-medical-science/
2015-10-19T12:48:40.000Z
2015-11-02T06:12:48.000Z
<p>最近也许是因为屠呦呦的诺贝尔获奖,千年大坑“中医是不是科学”又烽烟再起,争论愈演愈烈。思考良久,颇有心得,于是诉诸笔端。笔者知识有限,有些观点可能有误,欢迎斧正。</p>
<h3 id="中医是不是科学">
<a href="#中医是不是科学" class="headerlink" title="中医是不是科学"></a>中医是不是科学</h3>
<p>说实话,经过我的观察,我觉得这个问题其实大家彼此异议最小。别急,听我慢慢道来。
<br>前两天看到了一篇文章,说到人们讨论的问题可以分为三类:一是事实性问题,就是讨论一见事情是否符合事实,多半这类问题可以转变成答案是“是”或者“否”的问题,比如今早吃没吃鸡蛋,这类问题有着唯一正确的结论,因为符合和没符合事实是互斥的;二是偏好性问题,就是你是否喜欢一个事物,比如红的好看还是绿的好看,这类问题没有错误的结论,因为不同人喜好不同;三是判断性问题,就是你对一个事物或现象的看法,这类问题的答案有多个,取决于你观察问题的角度。
<br>然而在我看来,这个归类法存在一个陷阱,那就是一个问题如果要成为事实性问题,必须其表述上是没有歧义的,而且这个唯一的含义能够得到所有参与讨论问题的人的认同。
<br>比如下面这个问题:太阳是否从东方升起?表面上看起来这是个事实性问题,答案只有“是”或者“否”,而且正确答案应该是“是”。但是你有没有想过,什么叫做“东方”,什么叫做“升起”呢?当你考虑过这些维度之后,你就会发现这个问题的答案不那么简单了:</p>
<ol>
<li>太阳是不会自行升起的。我们只是在地球表面的一个观察点,基于地球自转的运动参照系观察到太阳在远离地心这个维度上有高度的上升变化。</li>
<li>这个观察点不包括南北极两个点,因为南极点只有北方而北极点只有南方。</li>
<li>东方表示的是以东方为中心,误差为23度26分21.448秒的一个方向的范围。否则,只有春分秋分那两天,太阳才是从非常接近东方的方向(还不能说是绝对的东方)升起的。有意思的是,对于我国绝大部分地区来说,冬至那天的太阳升起时的方向,距离正东北方反倒更接近些。</li>
</ol>
<p>那么,回到“中医是不是科学”这个问题:表面看起来,它是一个答案是“是”或者“否”的事实性问题。但是,如果认为这个问题有着唯一正确的结论,需要大家认可一个唯一的对“中医”和“科学”这两个概念的解释。很可惜,这样的无二义性的解释并不存在。笔者试着用一些没有二义性的方式来表述一些本人的看法,大家可以对照一下,是不是同自己的理解是一样的:</p>
<ol>
<li>以下表述中,中医的定义为:主流的研究并发展“中医”这一概念的学者所认同的基于中国传统的对于医疗方向知识所总结的一套学说体系。西医的定义为:基于解剖学、物理学、化学、生物学、分子生物学等其他学科,辅以实验以及数据分析的对于医疗方向知识所总结的一套学说体系。</li>
<li>中医学说体系与西医学说体系在问题定义以及验证方法尚存在分歧。换句话说,目前并不存在双方均认可的问题定义和验证方法。</li>
<li>按照中医所提出的问题定义和验证方法,中医学说体系是可以通过实验方法验证的。</li>
<li>目前尚无足够多的数据对中医学说体系的基础理论证实或者证伪。</li>
<li>西医中主流学者对于是否存在一种方式将中医与西医两种学说统一起来存在争议。</li>
</ol>
<p>之所以列了这么多条,是因为“科学”这一概念的解释方式很多,这里只是采纳了主流的几种解释(可验证论、证伪论、科学共同体论)。如果您认同上面所说的所有条目,那么我们的观点是一致的。至于中医是不是科学,那取决于您如何定义“中医”与“科学”了。</p>
<h3 id="该如何对待中医">
<a href="#该如何对待中医" class="headerlink" title="该如何对待中医"></a>该如何对待中医</h3>
<p>医学与其他学科的不同在于,医学的学说建立存在着较大的滞后性。也就是说,无论是西医或者中医,都存在着大量无法解释的现象,也都存在很多不明机理但却有效的治疗方式。作为在一个存在大量未知现象的领域的一种学说体系,简单地说保留或者废除都是不合时宜的。其实完全可以按照市场化的方式,让其自己发展或者消亡。在某一领域,至少如果一种学说体系无法完全给出完整的解决方案之前,其他学说体系的存在都是有意义的。
<br>作为每天需要使用医学结论的普通人,在面对治疗方案时,其实是一个对于投资回报的判断:如果我按照这个治疗方案进行投资,那么我有多大的可能会获得这个治疗方案所宣称的结果。这个时候,你要考虑的不是那个在诸多限制条件下的学术上的结论。治疗方案提出者是否了解学说本身,安慰剂等诸多浮动效应有多大可能发生都是考察的对象。比如说,假如有这样的一个理论,它虽然完全不能自洽,但是按照这个理论的手段能够保证百分之百出现安慰剂效应(当然很可能是由于某个尚未发现的原因)从而治好你的病,你愿不愿意采用呢?再比如,有一个医学理论,可以百分之百治好你的病,但是能够准确理解这个理论的医生万里也挑不出一个来,你愿不愿意采用眼前的医生基于这个理论提出来的解决方案呢?所以,是否采纳中/西医的解决方案,跟上面那个“中医是不是科学”问题完全是两个问题。这是一个个人做出的判断,风险自担,跟别人无关(只是不管是哪一种解决方案,准确地了解是否已研究过副作用或者是否有已知的副作用是基本的权利)。</p>
<h3 id="几个误区">
<a href="#几个误区" class="headerlink" title="几个误区"></a>几个误区</h3>
<p>我们在思考问题的时候,经常会走入一些误区。准确地识别出它们,是独立思考的一个必要的步骤。</p>
<ol>
<li>在一个学说体系中,曾经提出的一个学说,如果没有被现在该学说体系的主流学者认同,则并不能视为是该学说体系的一部分。比如亚里士多德曾提出力是维持物体匀速运动的原因,我们不能认为这是物理学所秉持的观点。因此,如果你发现某古医书里写治疟疾要让大公鸡喊两声,也不能认为这是中医的观点。</li>
<li>不能将一个学说的概念套用在另一个学说上并作为反驳的基础,这是因为两者问题定义不同。比如我们不能因为球面几何中不存在欧式几何中平行线这个概念而认为球面几何是错误的。因此,利用中药如果不能提供维生素就无法治疗维生素引发的疾病反驳中医也是没有道理的。</li>
<li>观察不到的概念不等于不存在。你不能因为观察不到某些微观粒子而否定某些量子物理学说,不能因为看不到插件而否定这一编程概念,同样你也不能因为无法从解剖中看不到经脉而否定它(当然你可以用其他方式来否定)。</li>
<li>表述方式原始的概念不等于不正确。有些概念有可能沿用了几千年,表述起来自然听起来不那么现代。但是概念是否正确跟这关系不大。比如苗这个概念用了上千年,却并不妨碍我们使用它来解释疫苗。中医的五行也并不是说你发烧的时候身体里就真的有物质在剧烈燃烧。只是如果一个学说体系想要得到发展,准确的阐释每个概念是非常必要的。</li>
</ol>
最近也许是因为屠呦呦的诺贝尔获奖,千年大坑“中医是不是科学”又烽烟再起,争论愈演愈烈。思考良久,颇有心得,于是诉诸笔端。笔者知识有限,有些观点可能有误,欢迎斧正。
通俗地解释第三方登录
http://sparkshining.com/2015/10/14/common-third-party-authentication/
2015-10-14T10:11:51.000Z
2015-10-14T10:14:47.000Z
<p>最近正在做SAML相关的事情,苦于没有一个第三方登录流程的通俗解释。结合以前做过的OpenID,试着自己解释一下。以下故事中机构名与人名均属化名,如有雷同,纯属巧合。</p>
<p>话说最近上海开了一家“人间天上”高级程序员休闲会所,供程序员在里面聊聊算法、思考人生。一天,有一个衣着入时的美女来找会所看门的王二狗,该美女自称是高级程序员,名叫郭小美,要求入会。王二狗又不是混程序圈的,咋知道这个人该不该放进去呢?于是王二狗请示安保主管雷老大。雷老大眼睛一瞪:老子又不是神仙,咋知道?
<br>不过雷老大不愧是雷老大,经得多见得广,这点小事难不倒他。雷老大亲自接见郭小美,几个来回,事情就办成了:</p>
<ol>
<li>雷老大拱了拱手:没听说过姑娘的名号,不知道是那路的?郭小美嫣然一笑:瞧您说的,没点背景还敢来?我是高级程序员协会的。</li>
<li>雷老大大腿一拍:这就好办了,认识高级程序员协会会长盖茨比么?郭小美一愣:当然认识了,我干爹呀。</li>
<li>雷老大嘿嘿一笑:这就更不是外人了。可惜这入会严格的规矩上面定的,我也没办法,要不你让盖茨比批个条子拿来,也让兄弟方便?郭小美皱了皱眉:真麻烦,好吧,就这么办!</li>
<li>郭小美去找盖茨比,凭借着两人深厚的革命友谊,很快条子拿来了。上面一行字:兹证明郭小美(女,身份证号XXX,三围XXX)是高级程序员协会会员——盖茨比。笔锋苍劲,一看就是练过的。</li>
</ol>
<p>看到条子,雷老大满脸堆笑:不好意思呀,还让郭小姐多跑了一趟。回头招呼王二狗:傻站着干啥,赶紧办理入会!</p>
最近正在做SAML相关的事情,苦于没有一个第三方登录流程的通俗解释。结合以前做过的OpenID,试着自己解释一下。以下故事中机构名与人名均属化名,如有雷同,纯属巧合。
zone.js简介
http://sparkshining.com/2015/10/10/introduction-to-zone-js/
2015-10-10T10:15:33.000Z
2015-10-12T12:06:07.000Z
<p>当你使用Angular 2的时候,你会惊喜地发现你不再需要$rootScope.$apply了,即使你直接调用addEventListener挂载一个事件处理器偷偷修改了模型,也不需要通知Angular。Angular似乎能够“未卜先知”地知道你的所有小动作。这一切的功劳都归功于zone.js。</p>
<p>zone.js的github地址见
<a href="https://github.com/angular/zone.js" target="_blank" rel="external">这里</a>。原代码库提供了一个
<a href="https://www.youtube.com/watch?v=3IqtmUscE_U" target="_blank" rel="external">ng-conf上的video</a>(需翻墙)来解释这个库的背景。注意
<strong>zone.js是一个独立的库,并不依赖于Angular</strong>,所以你无需担心被“某些别有用心的大户”挟持了。</p>
<p>简单地说,zone.js允许你为一段代码及其衍生出的代码提供了一个统一的上下文(zone.js称之为zone)。这个概念类似Node中的domain或者Java中的thread locals。
<br>能够在源于同一段代码的有着不同stack trace的程序间共享数据是一件很诱人的事情。比如说,你想统计程序的执行时间,然而你却有这样的一段奇葩程序:它自己要执行1秒,并且触发一个异步的操作;这个异步的操作又执行了3秒。如果你能够在这两段程序间共享数据,这个问题就简单多了。
<br>当然,你完全可以自己通过给每个入口的地方都加入一段代码的方式来解决这个问题,但是你会更希望能够使用一种类似zone.js提供的这种“面向切面”的优雅解决方案。</p>
<p>首先,如果用这样的方式来运行你的程序:
<br>
<figure class="highlight javascript">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div></pre></td>
<td class="code"><pre><div class="line">zone.run(yourCode);</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>这样你的这段代码以及其衍生的代码,包括:</p>
<ol>
<li>它注册的浏览器事件处理器</li>
<li>它通过setTimeout和setInterval在未来执行的代码</li>
</ol>
<p>以及衍生代码所衍生的代码就共享一个zone了。</p>
<p>那么,怎么使用这个zone呢?你可以通过类似下面这段代码这样通过zone.fork来插入一些监听器:
<br>
<figure class="highlight javascript">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td>
<td class="code"><pre><div class="line">zone.fork({</div><div class="line"> beforeTask: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="comment">// 在每段代码执行前都会执行的代码</span></div><div class="line"> },</div><div class="line"> afterTask: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="comment">// 在每段代码执行后都会执行的代码</span></div><div class="line"> }</div><div class="line">}).run(youCode);</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>比如,在beforeTask记录下开始时间,afterTask时记录下结束时间、与开始时间比较算出执行时间并且累加,你就可以解决前文说过的那个问题了。本文开头的Angular 2的$rootScope.$apply的迷雾也就解开了,因为Angular 2使用了zone并且在afterTask时帮你执行这个了。</p>
<p>zone.js的实现原理很简单,就是mock掉你的每个入口位置。多么地简单粗暴啊!但是非常有效。</p>
<p>说了这么多,来看一个最最简单地例子吧:我们想在每次执行代码(主函数、事件监听函数和setTimeout加入的macrotask)的前后各打印一行log。</p>
<p>首先,我们新创建一个空文件夹来装例子所需的文件。
<br>然后,因为zone支持bower,我们通过在该文件夹中运行下面的命令(有关bower使用,请见
<a href="http://bower.io/#getting-started" target="_blank" rel="external">这里</a>)下载zone:
<br>
<figure class="highlight bash">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div></pre></td>
<td class="code"><pre><div class="line">bower install zone</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>然后,在目录下创建index.html文件。那么默认情况下,相对于html文件,zone.js文件的位置应该是”bower_components/zone/dist/zone.js”。html文件内容如下
<br>
<figure class="highlight html">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div></pre></td>
<td class="code"><pre><div class="line"><span class="meta"><!doctype html></span></div><div class="line"><span class="tag"><<span class="name">html</span>></span></div><div class="line"><span class="tag"><<span class="name">head</span>></span></div><div class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span>></span></div><div class="line"> <span class="tag"><<span class="name">title</span>></span>Zone.js Basic Demo<span class="tag"></<span class="name">title</span>></span></div><div class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./bower_components/zone/dist/zone.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></div><div class="line"><span class="tag"></<span class="name">head</span>></span></div><div class="line"><span class="tag"><<span class="name">body</span>></span></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">h1</span>></span>Basic Example<span class="tag"></<span class="name">h1</span>></span></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"trigger"</span>></span>Run async code<span class="tag"></<span class="name">button</span>></span></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">script</span>></span><span class="javascript"></span></div><div class="line"> <span class="comment">/*</span></div><div class="line"> * This is a simple example of async stack traces with zones</div><div class="line"> */</div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">main</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Run main'</span>);</div><div class="line"> trigger.addEventListener(<span class="string">'click'</span>, clickHandler);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">function</span> <span class="title">clickHandler</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Run click handler'</span>);</div><div class="line"></div><div class="line"> setTimeout(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Run macro task'</span>);</div><div class="line"> });</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">/*</span></div><div class="line"> * Bootstrap the app</div><div class="line"> */</div><div class="line"> zone.fork({</div><div class="line"> beforeTask: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Enter zone'</span>);</div><div class="line"> },</div><div class="line"> afterTask: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'Leave zone'</span>);</div><div class="line"> }</div><div class="line"> }).run(main);</div><div class="line"><span class="tag"></<span class="name">script</span>></span></div><div class="line"><span class="tag"></<span class="name">body</span>></span></div><div class="line"><span class="tag"></<span class="name">html</span>></span></div></pre></td>
</tr>
</table>
</figure>
</p>
<p>然后你可以用类似
<a href="https://github.com/indexzero/http-server" target="_blank" rel="external">http-server</a>的方法来部署这个文件并用浏览器访问。
<br>在页面加载后,你会在控制台看到如下信息
<br>
<figure class="highlight python">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td>
<td class="code"><pre><div class="line">Enter zone</div><div class="line">Run main</div><div class="line">Leave zone</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>当点击页面中的按钮时,控制台会打印如下信息
<br>
<figure class="highlight python">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td>
<td class="code"><pre><div class="line">Enter zone</div><div class="line">Run click handler</div><div class="line">Leave zone</div><div class="line">Enter zone</div><div class="line">Run macro task</div><div class="line">Leave zone</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>值得指出的是,zone.js那个文件本身并不支持microtask(有关macrotask和microtask,日后我会专门写一篇文章来解释)。如果想支持microtask,你需要使用库里面的zone-microtask.js。这个文件比zone.js大很多,主要的原因是它用es6-promise完全替换掉了浏览器内置的Promise。</p>
当你使用Angular 2的时候,你会惊喜地发现你不再需要$rootScope.$apply了,即使你直接调用addEventListener挂载一个事件处理器偷偷修改了模型,也不需要通知Angular。Angular似乎能够“未卜先知”地知道你的所有小动作。这一切的功劳都归功于zone.js。
如何生成一个安全的随机数
http://sparkshining.com/2015/10/09/js-safe-get-random/
2015-10-09T09:54:03.000Z
2015-10-12T08:51:17.000Z
<p>提起随机数,大家的第一反应一般是Math.random。然而,
<a href="http://www.ecma-international.org/ecma-262/5.1/#sec-15.8.2.14" target="_blank" rel="external">ECMAScript里面</a>(中文版见
<a href="http://lzw.me/pages/ecmascript/#457" target="_blank" rel="external">这里</a>)并没有规定用什么算法来实现这个API,只是规定了生成的数字应该在[0, 1)内近似均匀分布。这就使得很多引擎实现在实现时采用了比较简单地算法,并不保证随机数的不可预测。
<br>这样的随机数显然不能用于数据加密。那么,有什么API可以获得一个安全的随机数呢?</p>
<p>如果是Node.js,你可以使用
<a href="https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback" target="_blank" rel="external">crypto.randomBytes(size[, callback])</a>这个API,比如下面的code获得了一个包含4个byte的随机数:
<br>
<figure class="highlight javascript">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td>
<td class="code"><pre><div class="line">crypto.randomBytes(<span class="number">4</span>, <span class="function"><span class="keyword">function</span>(<span class="params">err, buf</span>)</span>{</div><div class="line"> <span class="keyword">if</span> (err) <span class="keyword">return</span>;</div><div class="line"> <span class="built_in">console</span>.log(buf.length);</div><div class="line"> <span class="built_in">console</span>.log(buf[<span class="number">0</span>], buf[<span class="number">1</span>], buf[<span class="number">2</span>], buf[<span class="number">3</span>]);</div><div class="line">});</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>那浏览器端怎么办呢,你可以使用
<a href="http://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues" target="_blank" rel="external">crypto.getRandomValues(array)</a>这个API,现在很多浏览器都支持这个API了(具体浏览器支持情况请见
<a href="http://caniuse.com/#feat=getrandomvalues" target="_blank" rel="external">这里</a>),比如下面的code获得了一个Uint32(4个byte)的随机数:
<br>
<figure class="highlight javascript">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td>
<td class="code"><pre><div class="line"><span class="keyword">var</span> buffer = <span class="keyword">new</span> <span class="built_in">Uint32Array</span>(<span class="number">1</span>);</div><div class="line">crypto.getRandomValues(buffer);</div><div class="line"><span class="built_in">console</span>.log(buffer[<span class="number">0</span>]);</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>搞定,收工!</p>
提起随机数,大家的第一反应一般是Math.random。然而,ECMAScript里面并没有规定用什么算法来实现这个API,只是规定了生成的数字应该在[0, 1)内近似均匀分布。这就使得很多引擎实现在实现时采用了比较简单地算法,并不保证随机数的不可预测。这样的随机数显然不能用于数据加密。那么,有什么API可以获得一个安全的随机数呢?
自定义JS对象的序列化与反序列化过程
http://sparkshining.com/2015/10/08/jsobject-serialization-deserialization/
2015-10-08T08:32:08.000Z
2015-11-02T06:07:42.000Z
<p>先更正一个概念,JSON是一种数据表达的格式(见
<a href="http://www.ietf.org/rfc/rfc4627.txt" target="_blank" rel="external">RFC-4627</a>),就像我们平常说的xml、csv一样。它本身不是数据,也不依赖于具体编程语言。用.net、Java或者Python都能生成JSON格式的数据。
<br>
<a href="http://www.ecma-international.org/ecma-262/5.1/#sec-15.12" target="_blank" rel="external">JSON对象(JSON Object)</a>(中文文档见
<a href="http://lzw.me/pages/ecmascript/#609" target="_blank" rel="external">这里</a>)这个术语是有的,它是全局对象众多属性中的一个。它包含两个函数:parse和stringify,用于解析和构造JSON文本。但是JSON对象跟
<a href="http://www.ecma-international.org/ecma-262/5.1/#sec-11.1.5" target="_blank" rel="external">对象初始化(Object Initialiser)</a>(中文文档见
<a href="http://lzw.me/pages/ecmascript/#159" target="_blank" rel="external">这里</a>)是两码事,后者用于以直接量的方式初始化一个对象,比如
<br>
<figure class="highlight javascript">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div></pre></td>
<td class="code"><pre><div class="line"><span class="keyword">var</span> a = { myField: <span class="string">'myValue'</span> };</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>注意不要弄混了。</p>
<p>其实JSON.stringify和JSON.parse的参数都不止一个,根据ECMAScript 5.1规范,两者的声明分别是:
<br>
<figure class="highlight css">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td>
<td class="code"><pre><div class="line"><span class="selector-tag">JSON</span><span class="selector-class">.stringify</span>(<span class="selector-tag">value</span> <span class="selector-attr">[ , replacer [ , space ]</span> ] )</div><div class="line"><span class="selector-tag">JSON</span><span class="selector-class">.parse</span>(<span class="selector-tag">text</span> <span class="selector-attr">[ , reviver ]</span> )</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>这就使得我们可以控制序列化和反序列化的过程。比如下面的代码模拟了一个让函数在序列化并反序列化后仍旧保持不丢失的情况。
<br>
<figure class="highlight javascript">
<table>
<tr>
<td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div></pre></td>
<td class="code"><pre><div class="line"><span class="keyword">var</span> obj = {</div><div class="line"> myField: <span class="string">'myValue'</span>,</div><div class="line"> myFunction: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'function is not able to be serialized'</span>);</div><div class="line"> }</div><div class="line">};</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">replacer</span>(<span class="params">key, value</span>) </span>{</div><div class="line"> <span class="keyword">if</span>(<span class="keyword">typeof</span> value === <span class="string">'function'</span>) {</div><div class="line"> <span class="keyword">return</span> <span class="string">'__function'</span>;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span> value;</div><div class="line"> }</div><div class="line">};</div><div class="line"><span class="keyword">var</span> JSONString = <span class="built_in">JSON</span>.stringify(obj, replacer);</div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">reviver</span>(<span class="params">key, value</span>) </span>{</div><div class="line"> <span class="keyword">if</span> (value === <span class="string">'__function'</span>) {</div><div class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="built_in">console</span>.log(<span class="string">'function restored by reviver'</span>);</div><div class="line"> };</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span> value;</div><div class="line"> }</div><div class="line">}</div><div class="line"><span class="keyword">var</span> restoredObj = <span class="built_in">JSON</span>.parse(JSONString, reviver);</div><div class="line">restoredObj.myFunction();</div></pre></td>
</tr>
</table>
</figure>
</p>
<p>值得注意的是,除了函数外,replacer还支持其他几种类型。有兴趣的请翻阅
<a href="http://www.ecma-international.org/ecma-262/5.1/#sec-15.12.3" target="_blank" rel="external">ECMAScript规范相关部分</a>(中文版见
<a href="http://lzw.me/pages/ecmascript/#614" target="_blank" rel="external">这里</a>)。最有意思的是,你还可以通过定义toJSON函数来自定义序列化的过程。</p>
JSON.stringify和JSON.parse的第二个参数使得我们可以控制序列化和反序列化的过程。
开篇志
http://sparkshining.com/2015/08/27/hello-world/
2015-08-27T11:23:29.000Z
2015-10-12T11:51:47.000Z
<p>一个雨后的黄昏,一杯清茶,一支笔,一张纸。就此开始。</p>
<p>其实上大学那会儿,还是个挺矫情的人,没事就写点东西。等工了作,成了家,有了孩子,事情一多,也就慢慢变得大条了。
<br>当然,也是生活舒适了,没那么多伤春悲秋的事。
<br>一晃七八年,猛然发现竟然没留下什么印象。偶尔翻翻照片,一片陌生。
<br>觉得有点对不起自己,也对不起那些重要的人。本是萍水相逢,却逐渐印入对方的生活。其间曲曲折折,竟这样逐渐淡了。
<br>未来之路还很长,不求别的,只求能留下些印记,为自己,也为重要的人。</p>
<p>工作亦是如此,几年下来,颇有些心得。本是好为人师的,只因太懒,几次冲动最后都只是动了动嘴皮子。
<br>其间,跟着公司一位前辈一起出了一本书,写的是一个小众的框架,出版后据说风评还不错。
<br>那段时间废寝忘食,写的很是酣畅淋漓。等书出版以后,懒心又起,原来CSDN的博客就这样彻底荒掉了。</p>
<p>我虽然喜欢技术,却只限于工作或是猎奇。生活中却是保守得很。
<br>在移动上网3G、4G如火如荼的时候,我还揣着一台老旧的Nokia,除了打电话发短信,跟网络没有半毛钱关系。
<br>支付宝是去年才开始用的,滴滴打车是上个月才开始用的。人家咣咣往里砸钱,补贴满天飞的时候,我依旧去超市买东西,坐地铁上下班。
<br>个人网站也是这样,虽然我的工作一直跟网页相关,却从来没有一个自己的网站。
<br>没什么,不感冒而已。</p>
<p>偶然一个机会,一时冲动,买了个域名。买完之后,花了几分钟做了个巨丑陋的“网站正在建设中”的页面,而后就放在那儿。
<br>结果没出两个月,接了无数来自百度推广市场部的电话,后来连自己都烦了。既然已经被骚扰了,不妨就写点什么吧。</p>
<p>花了些时间,搞了个自己看着还算凑合的模板,用hexo搭了个台子。就这样,今天算是开张了。
<br>其实这里充其量只能算是个自留地,能记录下生活技术上的点点滴滴,也就知足了。</p>
一个雨后的黄昏,一杯清茶,一支笔,一张纸。就此开始。