2011年12月31日星期六

Jawbone UP 测评

感谢 Peter Fang 和他朋友的帮助,我星期一晚上拿到了我的 Jawbone UP。之前我一直对健康类的数码产品观望,最终 UP 引起了我的兴趣,因为它的设计理念足够好——只要你把它戴在手腕上,它自己就能够工作,你根本不需要注意到它的存在。这种纯被动式的产品往往能带来较好的体验,至少不会需要你去折腾。尽管它也有不完美的地方,例如不支持 Fitbit 那样的无线同步,因此每次同步时还是要脱下来的。

UP 发布后,各大网站对它的评价都不错,例如说 The VergeSummer Tomato。随之而来的是,用户反馈说他们的 UP 变砖了(尽管它的形状看起来无论如何都不像块砖),这确实影响了我对 UP 的信心。不过我想,既然变砖的都可以找 Jawbone 免费更换,那买一个回来试试也好嘛,如果真的出问题了再看看谁方便帮忙拿去更换咯。$100 对于一个健康数码设备来说还是可以接受的,于是我就下决心要买一个。

UP 到手后第一件事情自然是拆包装。iPhone 的 app 我早就装好了,但不把 UP 接上 app 就拒绝你使用。UP 的包装设计得还可以,没有任何难以拆开的地方,附件只有一本说明说和一个充电转换头。我把 UP 插到 iPhone 的耳机口后,app 就允许我注册了。首先需要注册一个账号,然后填写你的性别、生日、身高、体重,顺便让你设置你的目标(例如一天睡 7 个小时,走 5000 步,吃 2 餐能让你充满能量的)。完成向导后,你就可以开始使用 UP 了。首次同步时我的 UP 有 49% 的电量,所以我也没去充电,先用一晚上再说。

硬件

UP 的设计很简单,只有一个硬件按钮和两个指示灯,指示灯也仅仅在状态切换时亮一下,以表示你切换到什么状态了。第一天晚上我为了测试运动模式,在睡觉前使用 GAIN Fitness 运动了半小时。在运动前,先双击并按住按钮,UP 就会提示你它切换到运动模式了。接下来的运动过程 UP 会记录下来,不过由于它是戴在手腕上的,那意味着它只能记录上肢有活动幅度的运动,如果你戴着它做俯卧撑它就记录不了什么了。由于 GAIN Fitness 提供的是室内运动项目,包括热身、几组练习、放松,UP 的设计使得它认为热身是最激烈的部分,而放松(主要是瑜伽动作)是最轻松的部分。

UP 的另一个模式是睡眠模式,点击并按住按钮 UP 就会显示蓝色月亮,并且震动提示你模式切换成功。在睡眠模式里,它会跟踪你的上肢运动以便跟踪你是否在深度睡眠。智能闹钟能够利用这点避免在你深度睡眠时叫醒你,而且唤醒的方式也十分有趣——就是让 UP 震动起来。唯一不足是这个闹钟不支持贪睡模式,一旦点击按钮结束震动,就算你继续睡 UP 也不会再尝试叫醒你。

最后,UP 的一般模式只会跟踪你的步数。由于它不像 Fitbit 一样夹在衣服上,所以在你刷牙时它会记录非常多的步数,而在你端着一杯水走时我猜它会记录不到步数。这 UP 为便利性牺牲准确性的设计,不过我觉得如果它能成功刺激你多走动,效果就已经达到了,我那 $100 也不是白花了。此外,UP 的活动提醒功能也能够刺激你多活动。在每天指定的时段内(通常就是坐在办公室的时段),只要你连续若干分钟不活动(我设置为 30 分钟),UP 就震动一下提醒你起来活动活动。

软件

UP 的软件设计显然不如硬件优雅。尽管界面和交互设计得还可以,不过细节处理得还不是很好,性能尤其不行。如此简单的一个界面,在后台打开应用程序多时滚动起来还会卡,我实在想不明白为什么它需要如此多的资源。当然,如果你愿意为了 UP 而关闭所有后台应用的话,这就不是个问题了。

UP 的主界面显示你今天的目标完成度。例如说,如果你设置了一天要走 5000 步,它就会告诉你 4000 步只完成了 80%。在这个界面点击一下,或者旋转屏幕,UP 就会切换到时间轴界面,显示 UP 跟踪的历史记录。

UP 本地会缓存一天到两天的历史记录,再往前滚动的话就需要重新获取数据进行绘图。在这个图表里面,你可以清楚看到自己的活动分布是怎样的——在什么时候进食,在什么时候运动,在什么时候走的路比较多。通过这个图表,你可以反思自己的生活是否合理,然后调整自己的生活习惯。在这个图表上点击某一个时间段的话,可以查看该时间段的详细信息。

睡眠记录把你的睡眠时间划分为深度睡眠、浅层睡眠、清醒三个状态,通常进入睡眠模式后都会有一段时间的清醒然后才能入睡。睡眠质量分数我猜想是跟深度睡眠时间长短有关的,至于比例是否有所影响我暂时不确定。

运动记录同样是分成三个状态:剧烈、中等、轻度。我的猜想是,这跟你手臂活动的幅度和频率有关系,但暂时没有通过实验去证明,因为我不想把 UP 脱下来专门做实验并且污染我的数据。

其他

尽管 UP 说自己有一米的防水深度,并声称洗澡的时候也不用脱下来,但考虑到那么多 UP 变砖的事件我还是尽量让它不碰水。洗手的时候我尽量不会弄湿它,洗澡的话干脆就把它先脱下来。

UP 还有带 GPS 的运动功能,不过 GPS 实际上由 iPhone 提供,只是把 UP 的数据一起合并到 app 里面去。考虑到北京的空气质量如此糟糕,我是肯定不会进行任何户外活动的,还是等我回广州后去滨江跑步时再测试这个功能吧。

更新:实测表明,UP 的电力确实能够支撑至少 10 天。我充满后用了整整 10 天,电量还有 20% 剩余。

2011年12月14日星期三

前端工程师的职业发展路线在哪?

我猜想国内很多前端工程师都想过这个问题吧。前端工程师往往属于产品研发团队,但却很容易被边缘化——后端工程师觉得自己才是主力,没有后端工程师产品就不存在了,但没有前端工程师产品还能有,只是界面非常糟糕而已。这时候前端工程师就开始感觉自己像是个外包似的,只是来帮别人完成一些任务而已,对产品没有归宿感。这时候前端工程师的职业发展路线在哪?成为一个更好的外包吗?

要做关键任务

我觉得,要别人重视你的工作,不仅仅是你做得好就行了,还要求你的工作对别人来说足够重要。这跟产品定位有关——例如说对搜索引擎来说,前端对产品的影响不会非常大,用户只要能搜索到自己想要的结果就行了。搜索引擎最复杂的交互可能就是搜索框的自动完成了,但有自动完成和无自动完成的区别到底有多大呢?跟准确率和召回率相比,有没有自动完成实在没有多重要。况且,自动完成的结果本身也依赖于准确率和召回率,所以后端工程师比前端工程师重要得多。

因此,前端工程师在选择工作时首先要选择前端足够重要的工作。重要用什么来衡量?务实的话,是钱;务虚的话,是产品。如果一个功能只能在前端实现,并且这个实现能够提高多少的转化率,使得多少原本不产生利润的点击产生利润,那么前端对这个产品来说一定十分重要。可惜往往跟钱相关的事情不由前端工程师来研究和决定,所以这部分工作还是安心交给产品设计师来做吧,让他们来决定怎么样的产品能赚钱,然后由你来完成这个产品的实现,这时候你的目标就是把产品做好。

回到刚才的问题,有些产品更依赖于后端,例如搜索引擎,当然也有些产品更依赖于前端。什么样的产品更依赖于前端?就是后端难以建立起技术壁垒的产品。这类产品要抄袭一个功能差不多的并不难,因此只有细节做得最好的能够获得足够多的用户。这类产品在 iOS App Store 上很常见——有很多 app 拥有相似的功能,而其中只有一个交互设计得最好的能够获得绝大多数的用户。尽管 app 不存在 HTML + CSS + JS 这个前端,不过道理是一样的。当年 Tweetie 能够取代老牌的 Twitterrific 成为主流 Twitter 客户端,靠的就是交互上的创新,外加不差的性能和稳定性。如果交互对于一个 web app 来说十分重要,这个 web app 自然也就需要十分优秀的前端工程师。

总结一下,由于前端工程师的价值在于实现复杂的前端细节,因此如果可以选择的话尽量选择一个细节决定成败的产品。如果产品的成败已经由后端工程师决定了,例如某某数据规模要么能做要么不能做,那么这个产品就没你什么事了。

要懂核心业务

每一个公司,每一个项目,都有它的官方语言。不是指普通话,也不是指 C++,我指的是大家围绕什么问题来展开项目,什么问题的讨论能让大家为之兴奋。举个例子来说,百度的官方语言就是搜索,跟搜索没有关系的产品也会使用「准确率」、「召回率」这样的术语用来做比喻。前端工程师有多少知道什么是「准确率」、「召回率」的?估计不多,因为前端根本没有这样的概念。这时候前端工程师要跟后端工程师沟通也就不容易了。久而久之,你对人家很兴奋在讨论的什么 O(1) 还是 O(n) 不感兴趣,人家也不理解你的 {} != {} 是什么意思,你就被边缘化了。

如果不想被边缘化,就算前端不是公司的核心业务,你也必须懂公司的核心业务,然后说着官方语言,而不是前端的方言。这就意味着,如果你在一家后端技术很强大的公司,你最好也懂后端技术。我知道国内有很多前端工程师并不是计算机系毕业的,就算是国内的教育也不怎么样,这时候你只能恶补相关的基础知识了。如果你不懂这些,就算你能把整本《JavaScript 权威指南》背下来,你说的还是方言,说官话的人还是会鄙视你。如果公司主要服务于某个垂直领域的话,你必须对这个垂直领域十分了解,随时能用这个领域的行话来沟通。

总结一下,由于每个人已经熟悉的领域都不一样,所以没办法说哪个领域更适合前端工程师。如果你原本已经有某个领域的从业经验,进入服务于该领域的技术公司总是有显著优势的。如果你进入了一个自己不熟悉的领域,那就一定要补充相关基础知识,否则你对这个领域不感兴趣,这个领域也不会对你的前端工作感兴趣。

实际例子

为什么我选择加入豌豆荚?主要考虑的还是上面两点。

我在百度的时候一直就在想,既然前端对搜索引擎来说不重要,那对什么类型的应用来说比较重要呢?当时看到 Facebook 做得不错,所以觉得社区会需要复杂的交互,而如果复杂交互做不好则会影响用户使用,因此前端对社区来说应该十分重要。现在看来,也不完全是这样子。前端对社区来说确实重要,但 Facebook 并不是一个典型的例子,它是一个前端做得尤其优秀的例子。

在我了解到豌豆荚 Windows 客户端的实现方式时,我立即意识到它可以通过我的第一个判别标准——前端对它来说是关键任务。它使用 Webkit 做了一个容器,然后把所有的交互都通过 web app 的形式做在里面,然后通过一组接口跟 native 进行交互。如果一个应用决定要这样做了,那么前端就能影响到它的成败,因为这时候前端后端的分隔线已经很明确了。如果一项功能应该由前端来做那就必须由前端来做,后端基本不可能成为实现此项功能的备选方案,这时候前端就具备了无可替代的位置。

至于第二个判别标准——豌豆荚的核心业务是什么?我觉得豌豆荚做的很多事情都是以产品设计为起点的,而这至少是我感兴趣并且也有点感觉的东西。从细节上来说,就是大家喜欢谈论的事情是一致的,例如产品如何做一些很智能的设计,最新的技术方案如何能够巧妙地帮助这些设计得以实现。Junyu 说「设计就是创造性地解决问题」,这是我喜欢的解决问题方式。这个世界上能够把逻辑转化为代码的人非常多,同时有一定数学和计算机专业基础的人也不少,因此要拼谁的解决方案更好的话那还要加上创造力。

我知道国内有很多产品设计师,在考虑产品时首先想到的是百万千万级用户量,这样无论从单个用户身上赚到的钱多么的少,最终产品还是能赚大钱。百度曾经就属于这种思维方式,但这不是我喜欢的风格,因为没有明确的目标用户定位。我知道国内由很多工程师,在编写代码时用尽各种技巧以展示自己过人的才智,但是这样的代码还有可复用性吗?除了作者本人没有人能够维护啊。不同的人有不同的品味,能够跟品味一致的人一起工作是一件幸福的事情。

额外信息

这个话题到此就结束了吗?其实不是的。关于前端工程师的职业发展,我还有很多可以说的。不过我觉得找到一份让自己满意的工作必然是其中的第一步,因为你必须对工作充满兴趣,然后才能把事情做好,所以我把这部分内容放在最前面并且先发出来了。如果你不想错过后继讨论的话,欢迎订阅本博客。

此外,豌豆荚现在还招前端工程师,包括全职和实习,有兴趣的可以联系我:catchen@catchen.me。对于全职的前端工程师,我期望你熟悉 web app 的开发与调试,如果我让你手写一个 HTTP GET 请求你连个大概都写不出来,那我就要怀疑你平时都有多少时间对着 debug console 调试 AJAX 代码了。对于实习生,我期望你至少有扎实的 web page 基础,能够用简洁的代码实现符合语义的页面。至于豌豆荚提供什么?就是我前面所说的,但还有个前提——至少我们要有一致的品味。

2011年12月12日星期一

看对的书 (Part 1 - Tribal Leadership)

很久很久之前(准确来说是两年前),我开始了一个系列叫做「看对的书」,然后只写了一篇 intro 就没有写下去。背后的原因包括,我确实越来越懒了,同时写书评也不容易——剧透太多了,大家觉得没必要去看原书;剧透太少了,大家觉得看不出书的吸引力在哪里。如何把握剧透多少,怎么剧透才能吸引大家取看原书,我还没有想好,不过我可以先尝试着写。

我今天想要推荐的书是《Tribal Leadership》,也就是《部落领导力》,不过这本书暂时没有中文版。尽管这是一本讲「领导力」的书,但里面说的并不是你要做什么事情才能拥有领导力。书中说得更多的是部落——只有在你理解到部落的 5 个阶段后,你才能够使用你的能力把你的部落往更高的阶段迁移。那到底什么是部落了?不知道是否还有人记得「为什么早期手机只能存储 150 个联系人」的问题,答案是 150 是 Dunbar's number邓巴数),这个数是一个人能够维持的熟人数量。而一个部落,就是有 20 到 150 个熟人组成的组织。如果一个组织的人数超过 150 人,它会自动分裂为多个部落,从而形成一个由部落组成的部落。

在部落形成后,你就可以尝试去识别他们到底处于哪一个阶段。这本书的作者发现了一个有效识别部落所处阶段的方法,就是看部落成员说话的方式。例如说,如果你在医院的电梯里听到 3 个医生在交谈——第一个医生说「你看到我在《新英格兰医学杂志》上发表的文章了吗?」第二个医生用讽刺的语气说「看到了,印象深刻。然而在你忙于做研究的时候,我完成的手术比楼层内的任何一个人都要多。」大家笑了一笑,第三个医生接着说「当你忙于码字的时候,当你忙于切肉的时候,我教授的新人比本院的任何人都要多。」大家又笑了,互相拍拍肩膀走出电梯。这 3 个医生所说的话,背后的意思都是一致的——「我比你牛,而且我有数据支撑。」这时候,你就可以认定这是一个处于第 3 阶段的部落了。

部落的 5 个阶段对应的话语都很有特色,所以要鉴别一个部落处于哪个阶段并不难:
  1. 「人生就是个悲剧」——第 1 阶段的人认为没有谁的生活是好过的。
  2. 「我就是个悲剧」——第 2 阶段的人看得到别人生活美好的一面,只是觉得自己的生活很糟糕。
  3. 「我很牛,但你不是」——第 3 阶段的人觉得自己的生活不错,因为自己很牛,但觉得自己身边的人都不如自己牛,也不愿意给予自己更多支持,因此自己没办法完成更大的目标。
  4. 「我们很牛,但你们不是」——第 4 阶段的人觉得自己的部落很牛,而且竞争对手都不如自己。
  5. 「世界很美好」——第 5 阶段的人只专注于做一些很崇高的事业,无视竞争对手的存在。
就美国而言,有一半的人处于第 3 阶段的部落,而且绝大多数都是专业化的知识工作者,例如医生、律师,当然也包括工程师。由于现代教育体系奖励很牛的小朋友,而不是奖励很会跟别人合作的小朋友,所以大多数专业人士到达第 3 阶段后就难以获得突破,除非他们能够获得顿悟。顿悟的来源通常有两种:要么是明白到自己想要追求的目标实在是太大,无论自己有多牛都不可能实现,所以必须放弃单干的幻想,学会通过支持别人来换取自己的目标得以实现;要么是觉得自己足够牛了,获取更多的个人成就已经变得没意思了,那还不如帮助别人实现他们的目标。只有在不看重个人成就高低的前提下,部落才可能进入第 4 阶段。

此外,美国还有四分之一的人处于第 2 阶段。他们往往有一个处于第 3 阶段的老板,这些老板为了保证自己能够获得最好的职业发展而只雇佣能力比自己低的第 2 或第 3 阶段下属。第 2 阶段的人往往就在这样的老板手下痛苦挣扎,看着老板的生活很好,同时感觉自己生活很悲催,因此发誓将来成为老板的话一定不能这样子。但是,等第 2 阶段的人成为老板并且过上好生活后,他们会进入第 3 阶段,成为他们过去痛恨的老板。这时候,他们就不想放弃晋升带来的冲劲了,希望能够更好地证明自己也是很牛的,同时迅速往上爬。

这背后的道理是,人的行为变更是不可能跨越阶段进行的。书中甚至把每一个阶段分成了 3 个子阶段——初期、中期、末期。你不能指望一个人在脱离第 2 阶段后能够立即拥有第 4 阶段的意识,放弃个人成长优势而去帮助他人。因此,如果你想要成为一个部落领袖的话,你需要能够鉴别部落所处的阶段,同时你自己要能穿梭于不同的阶段,去到部落所处的阶段然后把部落带到更高级的阶段。至于每一个子阶段的特征是什么,以及如何能够帮助部落迁移到更高的阶段,我在这里就不详细解释了,大家去看书中的内容吧。如果不愿意看书,到 Tribal Leader 官网上下载有声读物也可以,而且还是免费的,只不过需要注册一下。如果买书的话,我推荐买 Kindle 版,因为看起来方便,不用想方设法把一本进口书弄回国内。

我之所以选择推荐这本书,是因为我很认同里面所说的部落阶段划分。我经历过不少第 3 阶段的部落——名校、名企大多如此。我知道,自己再牛能做的事情也有限;但是,我不知道如何能够突破。这本书解释清楚了一个问题——个人要突破,必须先放下职业发展的得失。然后还要推动整个部落突破,这需要部落拥有核心价值观和高尚的目标,同时制定达成高尚目标的战略。我相信国内也会有很多优秀的工程师卡在第 3 阶段找不到突破口,所以我建议大家读读这本书看看有什么启发。

2011年12月4日星期日

工具:开发者使用,企业埋单

我喜欢写一些小工具来简化我的工作,通常是一些小组件。利用这些小工具我可以提高自己代码的可读性,同时维持我的 DRY (Don't Repeat Yourself) 洁癖。工具对我来说很重要,因为时间对我来说很宝贵。能够用工具自动化完成的事情绝对不手工反复操作,能够用工具避免人为错误的地方一定让工具来确保质量。使用工具节省下来的时间用于玩游戏的话,绝对是值得的!

过去我主要做工具给自己用,或者是给自己所在的团队使用,所以觉得自己就是用户,自己设计产品给自己用肯定是没问题的。然而做工具给更多人用呢?看起来就不那么容易了。之前在百度经历过 Tangram 的需求分析与设计阶段,每个产品线都派出了前端工程师代表参与需求评审,通过多轮的辩论来决定哪些需求是要覆盖到的,哪些需求是可以忽略的。最后做出来的产品?只获得了一部分产品线的认可。推广也得不到公司层面的支持。

工具的需求难道不是来自使用者吗?使用者就是坐在自己隔壁并且做在同一领域开发的团队,这样都可能出现需求理解偏差吗?这个问题我一直没有想明白。

后来我到了 Yahoo,在对 YUI 表示恶心一端时间后,我开始理解到恶心背后的原因。无论是 Yahoo YUI 还是 Google Closure,目的都是一样的——通过工程性来保证,就算工程师素质很低,产出的代码质量很差,项目也能交付。我当时只是觉得大公司为了交付而造出一些压制工程师创意的工具是很恶心的事情,而没有考虑到这是否跟我要思考的问题相关。

到了豌豆实验室后,我开始着手构建一些前端工具,这一次我终于明白到需求源自于哪里了。工具确实是给开发者使用的,而需求来源于企业。企业在不同时期可能有不同的需求。企业刚刚成立时,肯定希望尽快把产品概念做出来,所以要求工具能够快速实现产品原型。等到产品成功发布后,企业又希望能够拓宽产品的市场,于是要求工具能保证产品复杂度增加的同时维护成本可控。随后企业会同时开发多个产品,这时候就会要求工具能够帮助降低产品共有的成本。

在文化相对开放的企业,工具的选择看起来是由开发者决定的。开发者爱用什么工具就用什么工具,企业不干预。实际上,企业不是因为文化相对开放而不干预,而是缺乏干预的动机。如果提供足够的动机,例如使用某种工具能够使得利润翻倍,任何企业都会愿意为之而干预。不干预的企业,往往只是没看清楚工具能给它的成本造成什么影响而已。

此外,在企业决定干预开发者使用什么工具时,企业肯定不会在乎工具使用起来爽不爽。开发者不爽是一种成本,但把这项成本计算在内后,如果特定的工具还是能降低企业成本,企业还是会选择这种工具。至于开发者不爽的成本,是将来可以通过优化工具来避免的。

回顾之前百度 Tangram 推广不利的问题,答案就显而易见了。为什么 Tangram 缺乏百度自顶而下的支持?为什么作为同类产品的 YUI 却能得到 Jerry Yang 的支持并在整个 Yahoo 得到推广?因为 Tangram 覆盖了开发者的需求,但没有很好地考虑到百度作为企业的需求,尤其是没有考虑到这种需求在百度成长中的变化。

Tangram 在设计初期,考虑的是如网页搜索这样的产品线的需求,要求 JavaScript 尽可能优化,少占用带宽。于是 Tangram 设计为支持函数级别的依赖项。然而等 Tangram 做出来时,贴吧则决定直接使用 jQuery。这是因为网页搜索和贴吧的成长进入了两个不同的阶段,网页搜索要求在细节上继续优化,而贴吧要求通过快速迭代尝试不同的产品概念。需求差异如此之大,一个工具又怎么可能都覆盖到?既然 Tangram 没办法在多个产品线证明它能有效降低成本,百度又如何下决心为它埋单?

这同时解释了一个问题:为什么企业内部工具应该由架构师来主导。(YUI 及后来的 Cocktails 都是由架构师主导的。)因为架构师的职位能让他更好地解企业的需求,同时他的位置也更靠近老板。此外,架构师在一家企业内的资历也会对此有帮助。跟随这家企业成长的经历能让他更好地看到这家企业将来的需求。因此,企业内部工具不是随随便便挑一个技术足够好的人来主导就行的,他的职位会影响到他是否胜任此项工作。

对此我可以补充一点信息。我见过 Tangram 推广所用的幻灯片,里面堆满了对开发者有用的信息——Tangram 在 gzip 前后跟其他库的体积比较、能够覆盖到多少功能点、在百度产品线的代码覆盖率能够达到多少。(代码覆盖率还曾经是 Tangram 的执行目标。)然而里面缺乏对老板有用的信息——使用 Tangram 能够省多少钱。由于位置的缘故,做 Tangram 的团队确实缺乏获取相关信息的途径,也更没办法获取到老板对不同产品线的长远规划。

再来看看 Yahoo 架构师 Bruno 是怎么介绍 Cocktails 项目的。幻灯片里面没有一点开发者关心的信息,前半部分说的都是 Yahoo 接下来的战略——必须同时抢占桌面、平板、移动 3 个平台,后半部分讲的是如果为这 3 个平台独立开发应有成本有多高,而使用 Cocktails 的话能够缩减多少成本。我听 Bruno 讲完后,就立即明白到这其实是用来向老板要钱的幻灯片,只是顺带再讲一遍以便让开发者知道公司战略而已。

老板实际上不明白也不在乎开发者对工具的需求是什么,幻灯片上他能看懂的就是那一堆 $ 后面跟着的数字。如果你给不出这堆数字,老板对工具的态度就会是既不支持也不反对。因此,找一个足够了解公司现有数字同时知道如何利用技术去改变这些数字的人,对内部工具项目来说尤其重要。这个人往往就是个架构师。

2011年8月8日星期一

「云端 JavaScript 漫游指南」

7 月 30 日在 w3ctech 的 JavaScript 活动广州场 讲了一节「云端 JavaScript 漫游指南」,实质上就是 Node.js 入门讲座。希望通过这一节讲座,让原本熟悉 JavaScript 的前端工程师尝试使用 Node.js 开发一些应用,探索 Node.js 为 Web 开发带来的可能性。

这场讲座上主要使用了两个小 demo 来解释 Node.js 的一些基础概念,并且展示了如何把自己的应用部署到云端。需要使用 Node.js ,最起码你要知道 CommonJS Modules 的概念,然后你才能在一个 js 文件里面使用另一个 js 文件实现的功能。这是通过 requireexports 这两个接口实现了。接下来,你还应该知道 CommonJS Packages 的概念,这样你就可以复用别人写好的包,无需样样从头开发。跟现在非常热门的脚本语言如 Python 、 Ruby 一样, Node.js 也有丰富的包仓库,基本上你能想得到的基础功能都有别人做好的包可以实现。

在有了一个好的产品创意后,你就可以以搭积木的形式构建自己的应用了。由于基础功能都有做好的包了,你可以专注于应用的业务逻辑,快速把产品做出来。在我的讲座中,我讲解了两个我自己编写给自己使用的小应用,一个用于把我的 catchen.biz 链接全部重定向到 catchen.me 去,另一个用于将短地址解析为原地址。关于这两个应用的技术细节我会在将来的文章中解释清楚,在这里我们就先跳过这部分的内容吧。

产品做好后,最后一步就是部署。过去我们需要购买或租用服务器,至少租用虚拟主机, OS 级别的事情还是要自己动手处理的。要在一个全裸的 OS 上把 Node.js 跑起来,还是要安装和配置一堆东西的。幸好现在有了如 Heroku 这样的云端解决方案,我们可以完全不管 OS 上跑着的是什么,把写好的 Node.js 做成包推到目标 Git 库上就可以了,所有的依赖项和配置都不需要自己来管理。

上述讲座内容都可以在我的 Github 上找到,包括幻灯片以及 biz-to-metraceurl 这两个 demo 。下面的是讲座现场的视频: YouTube | Youku

关于视频的任何问题,你都可以留言提问。如果你关注 Node.js 开发,欢迎订阅我的博客

2011年7月8日星期五

为什么 script 标签不能写成自关闭形式

今天早上在 Stack Overflow 看到了这个问题: Why don't self-closing script tags work? 。答案给出的解释是,在 XHTML 的标准里面规定非 EMPTY 标签不能使用自关闭形式。注意这里使用的是全大写的 EMPTY ,所以我不把它翻译为「空白」。

那么 EMPTY 到底是什么呢?写过 DTD 的人应该知道它是个关键字,用来指明一个标签的内容必须是空白,而不能包含文本内容或子节点。看看具体的例子就很容易明白了:

<!ELEMENT img EMPTY>
<!ATTLIST img
  %attrs;
  src %URI; #REQUIRED
  alt %Text; #REQUIRED
  longdesc %URI; #IMPLIED
  height %Length; #IMPLIED
  width %Length; #IMPLIED
  usemap %URI; #IMPLIED
  ismap (ismap) #IMPLIED
  >


这是 img 标签的定义。 ELEMENT 关键字说明它是一个元素, EMPTY 关键字说明它的内容必须是空白。因此,我们可以使用自关闭形式:

<img src="image.png" alt="some image" />

留意 ATTLIST 里面声明了两个属性是 #REQUIRED 的,所以必须提供。

接下来我们再看看 script 标签的定义:

<!ELEMENT script (#PCDATA)>
<!ATTLIST script
  id ID #IMPLIED
  charset %Charset; #IMPLIED
  type %ContentType; #REQUIRED
  language CDATA #IMPLIED
  src %URI; #IMPLIED
  defer (defer) #IMPLIED
  xml:space (preserve) #FIXED 'preserve'
  >


可以看到 script 标签通过 (#PCDATA) 声明了它的内部允许包含 CDATA 数据,因此它不是一个带 EMPTY 关键字的标签,也就不可能使用自关闭的写法。

总结一下:有空看看 Stack Overflow 还是挺有意思的。学习 DTD 的语法,并且看看 XHTML 1.0 Strict DTD 也会帮助你增加对 XHTML 的理解。

2011年6月25日星期六

如何购买 Amazon MP3 音乐

之前写过《如何购买 Amazon Kindle 书籍》,后来还发了一个更新版本。现在来说说如何购买 Amazon MP3 。

为什么要上 Amazon MP3 购买音乐呢?因为它比 iTunes Store 便宜,而且全部都是 DRM Free 的。例如说,同样是 Inception 的原声, Amazon MP3 的价格是 $5.99 ,而 iTunes Store 的价格是 $10.99 ,价格机会差一倍。

现在要购买 Amazon MP3 音乐,方法就跟最初买 Amazon Kindle 书籍一样。如果你要使用中国的信用卡就必须先买 Gift Card 给自己,然后把自己的地址和信用卡信息删除干净,再用 Gift Card 的余额来买音乐。

这个操作其实十分麻烦,因为你要不停地给自己的账号增删地址和信用卡信息。我就遇到过购买 Amazon MP3 音乐后忘记把信用卡信息加上去了,结果后来在 iPhone 上购买 Amazon Kindle 书籍时发现购买不了,然后又要拿信用卡然后在 iPhone 的小屏幕上输入上去。因此,我个人的建议是分开两个 Amazon 账号:一个有信用卡信息,专门负责购买 Gift Card 发给自己;另外一个只有一个美国地址,没有信用卡信息,专门消费 Gift Card 余额。

2011年5月29日星期日

在 JavaScript 中监听 IME 键盘输入事件

在 JavaScript 中监听用户的键盘输入是很容易的事情,但用户一旦使用了输入法,问题就变得复杂了。输入法应当如何触发键盘事件呢?是每一下击键都触发一次事件,还是选词完毕才触发事件呢?整句输入又该如何触发事件呢?不同的操作系统和不同的浏览器对此有不同的看法。在最糟糕的情况下,用户使用输入法后浏览器就只触发一次 keydown ,之后就没有任何的键盘事件了。这对于 Suggestion 控件的实现来说是个大问题,因为 Suggestion 控件需要监听文本输入框的变化,而事件是最准确也最节省计算资源的做法,如果换成轮询的话性能就可能受到影响。

首先,要监听启用输入法后的击键事件应当使用 keydown 事件,这是信息最丰富的一个事件,因为在启用输入法后别的键盘事件可能不会被触发。其次,大多数操作系统和浏览器都实现了一个事实标准,就是在用户使用输入法输入时, keydown 事件传入的 keyCode 取值为 229 。然而触发 keydown 的频率是不确定的,有些情况下每一下击键都触发事件,有些情况下只有选词完毕才触发事件。这时候,如果我们还是要实时监控文本框的内容变化,就必须使用轮询了。

var timer;
var imeKey = 229;

function keydownHandler (e) {
  clearInterval(timer)
  if (e.keyCode == imeKey) {
    timer = setInterval(checkTextValue, 50);
  } else {
    checkTextValue();
  }
}

function checkTextValue() {
  /* handle input text change */
}


Opera 是一款有趣的浏览器,别人做的事情它都不做,别人都不做的事情它都喜欢做。例如说,它偏偏不支持 keyCode == 229 这个事实标准,而要使用 keyCode == 197 来表示输入法的使用。因此,你需要在上述代码的基础上做一下改良,如果监测到是 Opera 浏览器,就换一个 keyCode 常量来做比较。

var imeKey = (UA.Opera == 0) ? 229 : 197;

最后,还有一个更不受重视的浏览器叫做 Firefox for Mac 。估计是因为 Mac 版本对于 Mozilla 来说实在是太不重要了,所以很多 Windows 版本都没问题的地方 Mac 版本就会出小问题,例如说对上述事件的支持。 Firefox for Mac 不会出现 keyCode == 229 的情况,而且在输入法启用后只有第一下击键会触发 keydown 事件,因此只能在击键后一直使用轮询。

if (e.keyCode == imeKey || UA.Firefox > 0 && UA.OS == 'Macintosh') {

在添加了这两项改进后,实时监控文本框的变化就没有问题了,即使用户启用了输入法。完整的代码如下:

var timer;
var imeKey = (UA.Opera == 0) ? 229 : 197;

function keydownHandler (e) {
  clearInterval(timer)
  if (e.keyCode == imeKey || UA.Firefox > 0 && UA.OS == 'Macintosh') {
    timer = setInterval(checkTextValue, 50);
  } else {
    checkTextValue();
  }
}

function checkTextValue() {
  /* handle input text change */
}