<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[janlay's blog]]></title>
  <link href="http://janlay.com/atom.xml" rel="self"/>
  <link href="http://janlay.com/"/>
  <updated>2012-04-16T17:55:31+08:00</updated>
  <id>http://janlay.com/</id>
  <author>
    <name><![CDATA[Janlay Wu]]></name>
    <email><![CDATA[janlay@gmail.com]]></email>
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[JavaScript 奇技淫巧背后的秘密]]></title>
    <link href="http://janlay.com/blog/2012/javascript-the-secret-behind-the-tricks/"/>
    <updated>2012-03-29T15:28:00+08:00</updated>
    <id>http://janlay.com/blog/2012/javascript-the-secret-behind-the-tricks</id>
    <content type="html"><![CDATA[<p>毫无疑问，JavaScript 是当前<a href="https://github.com/languages">最流行</a>脚本语言，不过我认为它获此境遇是历史原因造成的，而非源自优秀的设计。相反，JavaScript 的设计过于灵活和随意，以致坊间流传着各种“杂技”，其中不乏冠之以“高性能”的技巧。而初学者和 JavaScript “熟手” 面对这样的耍杂代码无所适从，不求甚解之下就往往忽略了它。</p>

<p>但是，为什么这些奇技淫巧的代码没有导致语法错误？为什么它也能运行？如何举一反三？解开这些问题，需要了解被大多数人忽略的 JavaScript 基础知识。这里试图解析一些 tricks, 告诉你它们背后的秘密。<!-- More --></p>

<blockquote><p>提示：</p>

<ul>
<li>要尝试下面的代码，你需要使用 JavaScript 控制台，或一个交互式 JavaScript 解析器。</li>
<li>下面代码中的变量如无特别注明，均假定其已通过 <code>var</code> 定义。</li>
</ul>
</blockquote>

<h2>短路语法：3 个为什么</h2>

<h3><code>1 || alert('ok')</code></h3>

<blockquote><p>先上甜点，这是大家司空见惯的短路语法。这里后面的代码不会被执行，因此它不是我要说的重点。但是，它为什么不会报错？因为它是普通的运算。</p></blockquote>

<h3><code>1 &amp;&amp; foo = 1</code></h3>

<blockquote><p>这行代码什么会报错？经工友指出，是因为 <code>=</code> 运算符优先级比 <code>&amp;&amp;</code> 低，它实现执行等价于 <code>(1 &amp;&amp; foo) = 1</code>，显示赋值给另一个值是不允许的。</p></blockquote>

<h3><code>1 &amp;&amp; (foo = 1)</code></h3>

<blockquote><p>这行代码为什么能运行？因为 <code>()</code> 运算符优先级比 <code>&amp;&amp;</code> 高。</p></blockquote>

<h3>更多优先级问题</h3>

<ul>
<li><code>1 &amp;&amp; 1, foo = 2</code> 可以运行，因为 <code>,</code> 优先级最低，表达式等级于 <code>(1 &amp;&amp; 1), foo = 2</code>。</li>
<li><p><code>1 &amp;&amp; function() { foo = 2 }()</code> 也没有问题，因为 前面说了 <code>()</code> 优先级比 <code>&amp;&amp;</code> 高。</p>

<p>PS:  使用 <code>()</code> 求值即获得 JavaScript 内部可以接受的值。例如 <code>(2.0000000000000001)</code> 在一些机器上可能会返回 <code>2</code>。感谢工友 <a href="http://twitter.com/hanyee">hanyee</a>  &amp; <a href="http://t.qq.com/dovapour">vapour</a> 对运算符优先级问题的指正。</p></li>
</ul>


<h2>运算符技巧：把字符串转换为数值类型</h2>

<p>先从一个单目运算符开始。什么是单目？只有一只眼睛，也就是说，运算符只接收一个参数。</p>

<h3>转型：<code>+'1'</code></h3>

<blockquote><p>我们知道 <code>-</code> 作为减法运算符时接收两个参数，而作为求负运算符时，接收一个参数。JavaScript 支持另一个不多见的“求正”的 <code>+</code> 运算符。显然，<code>+</code> 会尝试把任何接收到的参数转换为数值型。如此，我们就有了一个廉价转型方法，考虑一下：<code>parseInt('123')</code> vs <code>+'123'</code>.</p></blockquote>

<h3>转型并取整：<code>'123.4' | 0</code></h3>

<blockquote><p>这里使用“或运算”将左边的字符串隐式转换为数值型，再与 <code>0</code> 或。<strong>所有位运算都要求使用 32 位整数参与运算</strong>。所以这又是一个廉价的转型取整方法。考虑一下 <code>Math.floor('1234.5')</code> vs <code>'1234.5' | 0</code>.</p>

<p>但是，32 位整数表示的数据范围是有限的，因此这一招在数值超过 2<sup>31</sup> - 1 时不适用，考虑一下 <code>'12345678912.3' ^ 0</code></p></blockquote>

<h3>如何举一反三？</h3>

<ul>
<li><code>'1234.5' ^ 0</code> 完成转型并取整，与 <code>0</code> 异或，得到它本身，还记得这个<a href="http://baike.baidu.com/view/1452266.html#2">规则</a>吗？</li>
<li><code>~~'1234.5'</code> 同样可以完成转型并取整，因为两次取反得到相同的值。</li>
<li><code>--'1234.5'</code> 会失败。虽然我们说负负得正，但在 C 语系中，<code>--</code> 运算符优先级高于单个 <code>-</code>。解决办法很简单，把 <code>--</code> 写成 <code>- -</code> 就好啦，增加一个空格避免运算符被错误地识别。同理，<code>++</code> 也要这样处理。</li>
</ul>


<p>渐入佳境哈？接下来，我们把前面的知识综合运用，试图解析闭包中代码中括号的秘密。</p>

<h2>懒人的闭包</h2>

<p>闭包即 <code>function() {}</code> 代码块。通常需要控制 JavaScript 变量作用域时，我们把代码放在这个块中运行：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">foo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">bar</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span><span class='line'>    <span class="nx">sth</span><span class="p">(</span><span class="nx">foo</span><span class="p">,</span> <span class="nx">bar</span><span class="p">);</span>
</span><span class='line'><span class="p">})();</span>
</span></code></pre></td></tr></table></div></figure>


<h3>失败的闭包</h3>

<p>上面的代码中要写两对括号，如果代码块太长的话，上下的括号不方便对照。于是，你可能会有意无意漏掉 <code>function</code> 周围的括号：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">foo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">bar</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span><span class='line'>  <span class="nx">sth</span><span class="p">(</span><span class="nx">foo</span><span class="p">,</span> <span class="nx">bar</span><span class="p">);</span>
</span><span class='line'><span class="p">}();</span>
</span></code></pre></td></tr></table></div></figure>


<p>很不幸，运行这段代码会报语法错误（不同 JavaScript 执行程序抛出的异常信息可能会不一样）：</p>

<p><code>Exception: SyntaxError: Unexpected token '('</code></p>

<p>背景知识：<strong><code>()</code>只能用来求值、定义参数列表或调用函数表达式（<em>expression</em>）</strong>。本来，<code>function() {}</code> 定义的一个函数字面量（如同数组字面量 <code>[]</code>）表达式是可以拿来调用的，但是由于设计上的原因，<code>function</code> 有两种表达形式：</p>

<ul>
<li><code>function fn() {};</code>: 这是函数声明（<em>declaration</em>）的语句（<em>statement</em>）；</li>
<li><code>var foo = function() {}</code>: 这是函数字面量（<em>literal</em>）表达式。与上面雷同的写法 <code>var foo = function fn() {}</code> 也是合法的表达式，不过有一点小<a href="http://ejohn.org/apps/learn/#11">区别</a>。</li>
</ul>


<p>即是说，<strong>JavaScript 需要有足够的上下文（<em>context</em>）才能判断 <code>function</code> 的使用属于语句还是表达式</strong>。</p>

<p>所以，加上一对括号，就排除了 <code>function</code> 作为语句声明的目的，既然不是语句，那就是函数表达式咯。</p>

<p>对于单独（在语句前后加上分号将其表达为独立语句）的 <code>;function() {}();</code> 来说，JavaScript 无法区分其中的 <code>function</code> 是表达式还是语句。此时，JavaScript 选择了传统的语句识别，于是它被识别为两个语句————两个有问题的语句：前者缺少函数名称声明，后者不允许使用空的 <code>()</code> 进行求值。</p>

<p>于是懒人们行动了，网上流传了一些不写第一组括号也能正确运行的闭包。</p>

<h3>懒人的解决方案</h3>

<p>这是比较常见的懒人闭包：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="o">+</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">foo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">bar</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span><span class='line'>    <span class="k">return</span> <span class="mi">10</span><span class="p">;</span>
</span><span class='line'><span class="p">}();</span>
</span></code></pre></td></tr></table></div></figure>


<p>为什么它能执行？因为它很好地“营造”了一个让 JavaScript 将其中的 <code>function</code> 识别为表达式的环境————通过单目求正运算符，让 JavaScript 知道这里的 <code>function</code> 不可能是语句。哪有对语句进行运算的啊。</p>

<h3>开始举一反三吧</h3>

<p>既然写个 <code>+</code> 运算符就行，那就好玩了：</p>

<ul>
<li><code>-function() { return -1; }()</code> 求负也行哈。PS: 如果你运行这个语句，会得到 <code>1</code>, 因为函数返回的 <code>-1</code> 被你求负了一次。</li>
<li><code>!function() { return -1; }()</code> 取非也没问题。提问：运行它会返回什么？</li>
<li><code>1 + function() { return -1; }()</code> 非常标准的表达式，当然 OK 啦…</li>
<li><code>void function() { return -1; }()</code> 亲，<code>void</code> 也是运算符哦，<code>delete</code> 能用吗？当然可以！</li>
<li><code>1, function() { return -1; }()</code> 别把逗号不当运算符！</li>
<li><code>~function() { return -1; }()</code> 位运算符也来凑热闹了哈…</li>
<li>还可以写很多，随便怎么玩，只要组成表达式就行，自由发挥吧…</li>
</ul>


<p>嗯，先解析到这里吧。后续还有新的 trick, 我也会在这里继续更新。</p>

<p>最后，我想说的是，上面这些 tricks 大多都是 JavaScript 不好的设计导致的滥用，一些变种的代码让团队协作更困难。学会运用这些并不会显著增长你的技能，但了解背后的原理则会让你更深入地理解 JavaScript.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JavaScript 与浏览器插件互操作最佳实践]]></title>
    <link href="http://janlay.com/blog/2012/best-practice-in-javascript-interoperate-with-controls-or-plugins/"/>
    <updated>2012-03-18T23:34:00+08:00</updated>
    <id>http://janlay.com/blog/2012/best-practice-in-javascript-interoperate-with-controls-or-plugins</id>
    <content type="html"><![CDATA[<p>最近，同事遇到了 JavaScript 调用 Flash ActionScript 接口失败的问题，并总结了一些改进方法。看到分享后，结合之前遇到类似的问题，我也总结了一下浏览器插件，包括ActiveX 控件（在IE中）或插件（非IE中）中的互操作问题。<!-- more --></p>

<h2>为什么 JavaScript 调用 Flash 接口有时候会失败？</h2>

<p>Flash 实质是个浏览器插件，凡是插件都会有初始化的问题。具体来说，插件元素插入页面后，对应的插件代码才会在独立的线程中开始初始化，而页面线程会继续渲染并执行 JavaScript. 这时，如果插件初始化未完成，而 JavaScript 调用它的接口，那么调用会失败。等插件初始化好了，JavaScript 又不再调用，则业务会失败。</p>

<h2>选择合适的解决方案</h2>

<p>所以这里有两种解决方案：</p>

<ul>
<li>Flash 通知外部 JavaScript 它已经 ready, 外部 JavaScript 再调用 Flash 接口</li>
<li>外部 JavaScript 不断检测 Flash 某个接口是否可用，直到可用时才调用它</li>
</ul>


<p>两个方案的适用场景不同，前者适合用于 Flash 代码“可控”的环境，即开发人员有能力接触并修改 ActionScript, 还可以很方便地发布新版本; 后者适用于控件由第三方提供，不方便添加主动调用 JavaScript 的场景。</p>

<p>所以，通常 Flash 应用选择第一种方案，而像密码控件这类更新比较缓慢的应用选择第二种。</p>

<h2>控件接口检测方式</h2>

<p>注意检测控件接口可用时，不能使用 <code>if(element.api)</code> 这种形式，IE 会在 <code>api</code> 接口不可用时抛出异常，因为它不是普通的 JavaScript 方法或属性，即使可用的时候 <code>typeof</code> 运算符也会返回 <code>'unknown'</code>。这里应该使用 <code>if(typeof element.api !== 'undefined')</code>.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[方便地更换 Kindle 4 中文字体]]></title>
    <link href="http://janlay.com/blog/2012/a-convenient-way-to-replace-chinese-font-for-kindle-4/"/>
    <updated>2012-02-24T16:40:00+08:00</updated>
    <id>http://janlay.com/blog/2012/a-convenient-way-to-replace-chinese-font-for-kindle-4</id>
    <content type="html"><![CDATA[<p>入手 Kindle 4 之后，一直觉得用着还算顺手，就懒得折腾。除了不支持 epub 格式的电子书, 我觉得目前 Kindle 已经很不错了。听音乐、上网这些功能，个人以为，对电子阅读器来说有些画蛇添足。</p>

<p>但是再好的消费品也有折腾的空间（或者说乐趣），不是吗？嗯，那就先从字体开刀吧。<!-- More --></p>

<h2>理顺编码问题</h2>

<p>之前我已经注意到 Kindle 显示中文的问题：有些文档汉字显示差强人意，表现在拐角虚化而且线条粗细不一，有点像普通屏上的小号细圆体显示效果；而有的文档则显示还算好看的那种字体（内置的某种黑体）。</p>

<p>这是因为 Kindle 默认编码不是中文（据说是日文？）。解决方法很简单也有点繁琐：在书本列表页面调出键盘，依次输入以下三行命令：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>;debugOn
</span><span class='line'>~changeLocale zh-CN.utf8
</span><span class='line'>;debugOff</span></code></pre></td></tr></table></div></figure>


<p>每行结束时输入回车符，完成后无需重启设备即可生效。</p>

<p>通过上面的设置，可以让 Kindle 对所有中文电子书显示内置的字体，它已经可以满足大多数人的视觉要求了。</p>

<p>但是有没有想过，尝试一下其他中文字体在 Kindle 上的显示效果呢？<a href="https://twitter.com/wanderxjtu/statuses/170328311774445570">这里</a>就人推荐用雅宋体。</p>

<p>网上已经有很多介绍如何给 Kindle 更换字体的<a href="http://chuo.me/2012/01/kindle4">文章</a>，大多通过放置调试文件，然后重启 Kindle 进入调试，开启 USBnet 然后通过 SSH 登录进入修改的套路。这个流程过于繁琐，非常不利于折腾。下面介绍一下如何在不登录 SSH 的情况下更换 Kindle 4 字体的方法。</p>

<h2>准备字体</h2>

<p>首先，准备好要替换的中文字体。如果没有相应的黑体（Bold）版本，可以把 <code>foo.ttf</code> 复制一份为 <code>foo-Bold.ttf</code>。</p>

<p>然后，连接 Kindle 到电脑，并在 Kindle 上创建 <code>font</code> 目录，将刚才准备好的字体放进去。</p>

<h2>SSH 登录</h2>

<p>第一次替换字体时，你仍然需要登录 SSH 进入 Kindle 系统，具体方法请看前面的链接。</p>

<h2>写入符号连接</h2>

<p>登录后，首先要挂载 Kindle 的系统分区：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="o">[</span>root@<span class="o">[</span>192_168_15_244<span class="o">]</span> root<span class="o">]</span><span class="c"># mount /dev/mmcblk0p1 /mnt/base-mmc</span>
</span></code></pre></td></tr></table></div></figure>


<p>然后，创建字体文件的符号连接：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="o">[</span>root@<span class="o">[</span>192_168_15_244<span class="o">]</span> root<span class="o">]</span><span class="c"># ln -s /mnt/base-us/font/current.ttf /mnt/base-mmc/usr/java/lib/fonts/current.ttf</span>
</span><span class='line'><span class="o">[</span>root@<span class="o">[</span>192_168_15_244<span class="o">]</span> root<span class="o">]</span><span class="c"># ln -s /mnt/base-us/font/current-Bold.ttf  /mnt/base-mmc/usr/java/lib/fonts/current-Bold.ttf</span>
</span></code></pre></td></tr></table></div></figure>


<p>最后，修改字体配置文件：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="o">[</span>root@<span class="o">[</span>192_168_15_244<span class="o">]</span> root<span class="o">]</span><span class="c"># vi /mnt/base-mmc/usr/java/lib/font.properties</span>
</span></code></pre></td></tr></table></div></figure>


<p>打开 <code>font.properties</code> 文件后，搜索 <code>hans</code>, 将连续的四行修改为：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>hans.0<span class="o">=</span>current.ttf
</span><span class='line'>hans.plain<span class="o">=</span>current.ttf
</span><span class='line'>hans.1<span class="o">=</span>current-Bold.ttf
</span><span class='line'>hans.bold<span class="o">=</span>current-Bold.ttf
</span></code></pre></td></tr></table></div></figure>


<p>保存退出 vi 后，就可以退出 SSH, 重启查看新字体的显示效果了。</p>

<h2>后续更换字体</h2>

<p>以后，再换字体就非常方便了。只需把 Kindle 连接到电脑，复制字体文件到 <code>font</code> 目录，并确保新字体文件包括 <code>current.ttf</code> 和 <code>current-Bold.ttf</code>. 然后重启设备即可生效。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[保护密码：从客户端到服务器端]]></title>
    <link href="http://janlay.com/blog/2012/secure-your-password-from-client-side-to-server-side/"/>
    <updated>2012-02-14T22:01:00+08:00</updated>
    <id>http://janlay.com/blog/2012/secure-your-password-from-client-side-to-server-side</id>
    <content type="html"><![CDATA[<p><a href="http://sofish.de/">sofish</a> 在知乎上邀请俺回答知乎上的问题“<a href="http://www.zhihu.com/question/20060155">如何保证用户登陆时提交密码已经加密？</a>”，下面这些也算是对他那篇<a href="http://sofish.de/2020">博客</a>的补充吧。</p>

<p>如何保证用户登陆时提交密码已经加密？密码是否已加密，需要客户端和服务端建立约定，双方按约定办事就行了。</p>

<p>这里提到的另一个问题是，如何保证传输安全？</p>

<p>最理想的方案当然是走 HTTPS 协议. HTTPS 在理论上是可靠的，但在国内会打一些折扣：你可以随便找一台电脑看看有没有安装商业公司或机构的根证书，这些根证书为线路某节点成为中间人提供了可能性；同时，在木马横行的年代，密码在加密提交前可能就被拿到了，此时 HTTPS 成了摆设，这是为什么国内流行密码控件的一个重要原因。<!-- more --></p>

<p>从成本和需求上考虑，对于众多对安全性要求不高的个人网站，仍然可以考虑采用 HTTP 传输，密码提交前通过 JavaScript 加密。由于 JavaScript 代码暴露在客户端，因此一般通过不可逆的加密方法加密密码，而对于任何摘要式的加密算法，都可以通过类似 md5 字典的方式直接查表获知弱密码，所以要混入 salt 以增加制作字典的成本。可想而知，解密只是时间成本的问题。因此这里的重要前提是“对安全性要求不高”。</p>

<p>如何验证密码呢？一个可行的方法是，客户端提交 md5(password) 密码（如上所述，此方法只是简单保护了密码，是可能被查表获取密码的）。服务端数据库通过 md5(salt+md5(password)) 的规则存储密码，该 salt 仅存储在服务端，且在每次存储密码时都随机生成。这样即使被拖库，制作字典的成本也非常高。</p>

<p>密码被 md5() 提交到服务端之后，可通过 md5(salt + form[&#8216;password&#8217;]) 与数据库密码比对。此方法可以在避免明文存储密码的前提下，实现密码加密提交与验证。</p>

<p>这里还有防止 replay 攻击（请求被重新发出一次即可能通过验证）的问题，由服务端颁发并验证一个带有时间戳的可信 token （或一次性的）即可。</p>

<p>当然，传输过程再有 HTTPS 加持那就更好了。</p>

<p>最后，为什么要密码控件？原因之一是上面说的，要防止密码在提交前被截获。当然，还有一些其他原因，工作所限，这里就不说了。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[TP-LINK TL-WR703N 无线路由器的信号和 VPN 使用]]></title>
    <link href="http://janlay.com/blog/2011/using-vpn-with-tp-link-tl-wr703n-router/"/>
    <updated>2011-10-23T19:36:00+08:00</updated>
    <id>http://janlay.com/blog/2011/using-vpn-with-tp-link-tl-wr703n-router</id>
    <content type="html"><![CDATA[<p>这款无线路由器最大的特点是小巧，非常小，以至于超出你对路由器的认知，俨然就是个微型的 Mac Mini. 其他特点我就不多说了，<a href="http://www.tp-link.cn/pages/product-detail.asp?d=225">官网</a>和<a href="http://www.360buy.com/product/505129.html">京东</a>上都有详细介绍。</p>

<p>最近路由器开始流行 3 根天线，这款产品则完全没有外置天线，不由得让人怀疑它的信号是否够用。经过在家里实地测试，效果不错：<!-- more --></p>

<ol>
<li>信号非常好，家里隔墙仍然是满格信号</li>
<li>1M ADSL 满速下载 (133KB/s)</li>
</ol>


<p>VPN 问题，TP-LINK 路由器默认的 MTU 值是 <code>1480</code>, 在 Mac OS 系统上连接 VPN 会发生连上后有网络请求就立即断开的问题，在<em>网络参数 > WAN口设置 > 高级设置</em>中把 MTU 修改为 <code>1492</code>, 重启路由器即可正常连接 VPN. 其他网络上 VPN 类似问题亦可参考此设置。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iPhone 4 维修散记 & 注意事项]]></title>
    <link href="http://janlay.com/blog/2011/repairing-my-iphone-4-in-genius-bar/"/>
    <updated>2011-08-28T21:52:00+08:00</updated>
    <id>http://janlay.com/blog/2011/repairing-my-iphone-4-in-genius-bar</id>
    <content type="html"><![CDATA[<h2>一、起</h2>

<p>Home 键失灵应该是 iPhone 4 最普遍的问题了，老婆也不幸遭遇此问题，使用起来让人抓狂。更紧迫的是，一年有限保修还有两三个星期就要失效。</p>

<p>网上流传着各种大法，效果如何则基本上要看人品。上周五刚好有机会去魔都出差，就顺便去陆家嘴苹果店看看能不能修好。<!-- more --></p>

<h2>二、承</h2>

<p>地铁 2 号线坐到陆家嘴出来后，借助 GPS 和指南针定位，很快就可以确定苹果店的方向，一眼望过去就可以看到苹果的 Logo. 需要注意的是，作为一个乡下人，不要试图横穿马路，从头上的环形天桥就可以过去了。</p>

<p>苹果店其实是在地下，地上只能看到玻璃墙上的 Logo. 门口传说中的玻璃台阶非常厚，所以不用担心会被踩破；表面是一层毛玻璃，可以有效防滑，当然也可以防止楼下的色狼往上透视。不过即便如此，还是建议 MM 们不要穿裙子去，因为台阶每级之间是有缝隙的。</p>

<p>进入之后，环视一圈即可找到 Genius Bar (天才吧), 建议苹果在英文上面加一行“维修处”以照顾英语不好的用户。</p>

<p>由于天才吧预约非常困难，所以这次俺是去霸王修的。随机抓一个“天才”，开始陈述问题。后面的进展花了三个多小时，非常无聊，所以俺就把要点写一下。</p>

<h2>三、转</h2>

<ol>
<li>苹果店维修第一注意事项：不要拿越狱过的机器去。他们会以安全为理由，拒绝处理越狱机。不知道是否有程序可以经由数据线抓取苹果维修工作站的数据，嘿嘿。如果你已经越狱了，在已经备份过数据的前提下，可以自行恢复出厂设置，或是找个“天才”给你恢复一下系统。另外，他们对 iOS 5 Beta 版系统上遇到的问题不提供支持。</li>
<li>苹果店不承诺保护你的数据，所以务必事先做好备份。</li>
<li>维修时带上手机就行了，不需要携带任何单据或发票。</li>
<li>如果你没有在网上预约，那么到现场后一定要确认“天才”把你的维修要求加入他们的队列。具体来说，每个“天才”都配备一只 iPod Touch, 里面有个应用会管理当天所有的维修请求，必须确保你的信息在里面，否则等到关门也不会叫你的。</li>
<li>加入队列后，你应该到天才吧右边的长凳上去 standby. 左边是网上预约和取机的等候区域。</li>
<li>“天才”处理 Home 键失灵的问题，并不是用网上传说的各种大法，一旦确认问题可以重现，会给你更换除电池和后盖外的所有部件。这样，显示屏、主板、摄像头、无线模块等部件都会被替换掉。你拿到新机后，会发现序列号已经变更，MAC 地址也变了。</li>
<li>因为信任，所以简单。你把机器交进去之后，并不能确认他们到底有没有给你换电池（换新的或是一只更旧的）</li>
</ol>


<h2>四、合</h2>

<ol>
<li>等待大约 2 小时就可以取回新机了。拿到后再检查一下 Home 键问题是否存在，最好还要看看显示屏有没有问题。</li>
<li>港行手机换机后，主要部件就会全部成为国行，Youtube 应用会消失，是喜是悲各位自行判断。手机新序列号会记录到系统，不必担心保修问题。</li>
<li>换新机后，保修期限会有所变化，过期时间为原手机过期时间和拿到新手机后 3 个月中较晚的一个。</li>
<li>“天才”们的普通话中会夹杂大量的英语词汇，如果一时没有反应过来，不要不懂装懂，大可义正辞严地要求他说普通话。</li>
<li>不要称“天才”们为“服务员”，他们会在第一时间纠正你的错误。</li>
</ol>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[谈谈我对 OAuth 登录并关联新建帐户的看法]]></title>
    <link href="http://janlay.com/blog/2011/associate-with-another-account-system-via-oauth/"/>
    <updated>2011-08-25T10:42:00+08:00</updated>
    <id>http://janlay.com/blog/2011/associate-with-another-account-system-via-oauth</id>
    <content type="html"><![CDATA[<p>最早，我使用相关技术的产品体验，是从 <a href="http://www.yupoo.com">Yupoo</a> 开始的，当时还不是用 OAuth, 而是 OpenID. 那时候我也在关注 OpenID, 觉得 Yupoo 很潮，欣然试了一把。使用 OpenID 登录过程很顺利（我甚至开通了 <a href="https://www.myopenid.com">myopenid</a> 的语音检验），但是回到 Yupoo 之后，我发现系统居然要求我输入新用户名和密码。虽然有点纳闷，还是抱着小白鼠的心态，输入了新帐户信息。</p>

<p>而今，曾经被热捧的 OpenID 技术逐渐式微，OAuth 方案随着 Twitter 的崛起已成为事实上的帐户验证和连接标准。<!-- more --></p>

<p>毫无疑问，OAuth 可以加强用户登录的动力，而新用户登录即意味着注册用户量的增长。所以 OAuth 是个快速提升用户数的好方法（创业网站太需要这个数字了）。</p>

<p>用户首次 OAuth 登录时，可能需要输入两种密码（登录外部帐户和创建本系统帐户）才能完成全部登录流程，这样增加了用户的输入成本。不过，最近我注意到，越来越多的网站在站外登录后仍然要求创建本系统内的帐户，连一直和 Twitter 保持紧密联系的 bit.ly 都开始要求用户注册本地帐户。当然，还有今天了解到的丁香园站外登录。</p>

<p>为什么要这样？表面上看，原因很简单——用户是你网站的全部，你不能把最重要的资产托付给别人。进一步思考，即便你的网站现在完全信任外部登录提供方，你也必须走上这条路，因为：</p>

<ul>
<li>OAuth 得到的信息是有限并且受限的。你提供特定的服务往往需要进一步获知用户的资料，比方说你需要用户的真实姓名，QQ 无法提供给你。受限意味着你不能随心所欲访问你想要的信息，登录提供方和用户都可能有意无意调整授权策略，从而让你的数据请求无功而返。</li>
<li>外部登录帐户不可能永远满足你的需求。你的系统在壮大，你的业务方向可能会调整，你的系统架构必须不断改进——你需要灵活地控制帐户体系，才有可能满足这些变化。</li>
<li>关联使得新帐户仍然保有与外部网站交换信息的能力，这对社会化功能来说很重要。</li>
<li>生意场上没有永远的朋友。</li>
</ul>


<p>结论：OAuth 是个好技术，善用才能用好。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[寻找江南]]></title>
    <link href="http://janlay.com/blog/2010/looking-for-the-goodliness/"/>
    <updated>2010-12-28T10:53:00+08:00</updated>
    <id>http://janlay.com/blog/2010/looking-for-the-goodliness</id>
    <content type="html"><![CDATA[<p>每次听到看到“黄四郎”的名字，就条件反射地想起一句诗：黄四娘家花满溪，千朵万朵压枝低。还有一句“江南可采莲，莲叶何田田”、一句歌词“秦淮水暖烟波里”、《江畔独步寻花》一起构成了一个乡下少年对江南的初步印象，虽然后者不一定说的是江南。</p>

<p>那时候，头脑中YY的江南简直是人间仙境，去一趟江南似乎是个遥不可及的梦想。机缘巧合，22 岁时离开了家乡来到杭州工作，于是梦想照进现实，现实却并非梦想那样美好。<!-- more --></p>

<p>2003年，我观赏到了传说中的钱塘江大潮。在去海宁的路上，头脑中浮现的是小学课本中描述的壮观场面。而在回杭州的路上，我回味的却不是一线天，而是农庄旁、河道边、盛夏的垂柳和休憩的村民。“我打江南走过，那等在季节的容颜如莲花的开落”。</p>

<p>时光模糊了记忆，抹平了创伤。越走近江南，昔日朦胧的印象和梦想越发真实，越感距离。我开始意识到理想不过是各种现实的综合体。现实就是家、户口和莫名的距离。我寻找的不是江南，是归宿。</p>

<p>“念去去，千里烟波，暮霭沉沉楚天阔。”回家的意向日趋强烈，似乎家乡本来就很好呢。也许只是打江南走过，也许这里只是平淡人生的一座驿站。</p>

<p>人生总是充满了不期而遇，那个生命中的人终于出现。说起来只是因为她听说武汉很冷又很热，于是打消了回家的念头。于是，归宿的意义发生了变化。</p>

<p>原来，江南不在苏州，不在杭州，不在南京，它在人们的心里——它本来就在我心里。江南曾经是一幅飘渺的画卷，曾经是诗人的灵感源泉，曾经是人生旅途的风景。现在，它是一切美好的总和。</p>
]]></content>
  </entry>
  
</feed>

