奔四了,写给40岁的自己

年关将至,写给已经40岁的自己。 鉴于大家在互联网上认识我大多是因为我的工作,那就先从工作聊起。 十年时间足以改变很多事情。英国脱欧、川普从胜选到败选再到胜选、新冠、战争、C罗从皇马去了尤文又去了曼联又去了沙特、比特币从两百多美金涨到了五位数、AI 的代名词从 AlphaGo 变成了 ChatGPT。说唱、街舞、乐团、站立喜剧从小众走到了主流。高铁、外卖、电动车进入了平常百姓家。 遥想十年前,刚过30岁的我还身处杭州,那时的自己逐渐适应了阿里的工作文化和游戏规则,工作是辛苦的,但也是充实的。 2015年的11月,也就是当年的双十一过后没几天,我代表手机淘宝前端团队连续发布了三篇《对无线电商动态化方案的思考》1, 2, 3。后来,这三篇文章的内容演变成了阿里巴巴的开源项目 Weex,这也算是我个人在阿里时期最被人熟知的一件事。同期,我还加入了一个叫做 Vue.js 的开源项目,也因此结识了它的作者尤雨溪 (Evan You),并与他保持了多年的友谊。十年后的今天,我们两家人都在新加坡生活,真是奇妙。当然比起 Vue 这么多年长足的发展,Weex 的生命周期短暂了许多,我尽力了,也学到了成长了。这也是我十年职业生涯的一部分。 除了自己在 Weex、Vue 以及其他工作上的努力,我觉得自己过去的10年是非常幸运的。首先我赶上了中国互联网的 BAT 黄金时代,且有幸在这个时代为阿里这样的大厂工作;其次,这十年也是 Web/JavaScript/前端 技术高速发展的时期,作为一名前端工程师,能够见证并参与其中,也非常幸运;再者,我赶上了开源社区蓬勃发展的十年,也在2019前后赶上了东南亚互联网的快速发展。这些都不是我提前规划出来的,也都不是仅靠个人努力就能够得到的。我在赶上这些潮流的当下,也并不像身边涌进来的人一样以投机的心态在参与,这就更让我觉得幸运来之不易。 但与此同时,我也非常欣慰自己能够坚持住一些自己的微不足道的追求,比如我希望可以探索一条纯技术的职业路线,希望可以出国闯一闯,希望可以尝试远程工作,希望通过英文与世界交流对话。当然纯技术路线这件事情更多的是我给自己的定位,我知道在很多人看来我在阿里后期以及来新加坡的初期,做的就是管理岗,我也确实因此也学到了很多管理技能和经验,但我还是在做好管理的同时额外把可观的精力放在对技术的钻研上,尽管那些东西都写不进我的绩效里。总之,上述这些坚持都不是世俗成功所必备的,甚至我身边的几位挚友都在劝我“现实”一点,觉得我的想法太理想化了。我知道他们也都是好意,也都是经验之谈,但我很开心以我自己的方式坚守住了。如果有机会,我还是乐于再回国见他们一面,聊聊这些年的变化,也当面告诉他们,你看我真的做到了,有梦多好。 再聊聊生活吧。 这十年最大的两个变化就是我们全家在新加坡安顿了下来,且有了自己的孩子。我们跟很多家长一样经历了从小孩出生到不断成长的每一个阶段,其中的酸甜苦辣也都全方位体会到了。我们也跟很多初代移民一样,经历了从适应到融入的过程。我个人对比下来,新加坡本身是一个生活非常简单的社会,杂七杂八的“破事”很少,同时有了孩子之后生活的很多方面都经历了从无序到再次有序的化学反应。总体上每天要应对的问题还是动态平衡的——以某种积极的方式。关于移民,关于育儿,都是特别大的话题,但不是今天的重点。所以就不多展开了。 现在的我也比十年前的自己多了更多业余生活和爱好。有句英文叫 live a little,说的就是我现在的心态。我逐渐把之前放在工作上的时间匀出来一些,用来阅读、旅行、追剧看电影、听演唱会、看体育比赛等等。同时为了刻意维持自己的身体状态且避免每天坐在家里盯着屏幕一整天,我在疫情过后也恢复了上学和刚工作的时候定期踢球的习惯,并开始跑步。我第一次只能跑一公里,而且上气不接下气,但只要坚持,进步是肉眼可见的,直到上个月我跑完了新加坡马拉松42公里195米的全程,算是一个里程碑。自己非常开心。 另外,虽然我这十年陆续玩过了微博、Twitter/X、各种社交网络,再到去年开始有序退网,折腾来折腾去,这十年一直坚持下来的,反倒是写博客这件事。 对于未来,我现在的心境是非常复杂的,有憧憬和期待、也有忐忑和担忧。 我总体上对自己的工作是满意的,但对未来是模糊的。我的内心里有一个声音告诉我,要充分利用好自己之前的积累和经验,做点东西出来;但也有另外一个声音告诉我,这个世界正在经历前所未有的变革,是时候大胆走出自己的舒适区了;还有第三个声音说,你可以把自己的专长和生活中的兴趣做一些结合,找到新的机会;最后还有一个微弱的声音说,如果真的想做大事,你的积累和客观条件还不够,现在不是时候。这些声音需要我在接下来的一段时间里慢慢理清楚。 我的家庭生活总体上是富足而充实的,但未来尤其在育儿方面是模糊的。作为一个在典型的中国式家庭长大的人,我很容易凭借自己的亲身经历,从童年的所谓创伤中找出那些 DON'Ts,但没有人告诉我相应的 DOs 是什么。那些书本上或其他文化里的 DOs 跟现在的时代以及你身边的其他生活情况放在一起也未必那么适用,未必能够形成一个新的平衡的系统,就更别说那些 DON'Ts 不是你想避免就一定能避免的。有的时候你找不到替代品,就只能硬着头皮做下去。或者他们根本就是你的 DNA 的一部分,在不经意间从你的身体里冒出来。随着孩子慢慢长大,有了自己的想法和主见,那些副作用都会像回旋镖一发一发飞回来砸到你的头上。 我对孝敬长辈的方式也是模糊的。 我对自己的健康状况也有理由保持警惕。 这些都是会伴随40岁以后我的人生的重要课题。 我对40岁以后的生活的预判是:它会变成一个没有最优解的多元复杂问题,需要更综合的智慧去面对,去管理好这个“复杂”,并且和各种历史遗留问题友善共存。这样的问题没有标准答案,上限可以很高,下限也可以很低。想想还挺刺激的。 希望自己能够走好接下来的十年吧。我十年以后会回来 review 自己的这几段话。

2025/12/31
articleCard.readMore

有序退网

该文章部分内容与我在《代码之外》参与录制的 2024 年终总结内容有所重叠,算是一个文字版的整理。 直奔主题,经过深思熟虑,我决定开始渐进式“退网”,并分享一些我在这个过程中的思考。 首先,别误会我,互联网很好——总体上它是的。作为一个标准的从业者,它伴随我从我的第一份实习工作到现在很多年了。我可以自豪的说,我亲身经历并见证了很多互联网改变生活甚至改变世界的高光时刻,并作为参与者与有荣焉。但同时,我也逐渐觉得,自己深陷其中,到了一种过度和对自己不利的程度。 这次的“退网”更多的是针对一些我个人觉得特别“使人上瘾的 (addictive)”部分,尤其但不仅限于互联网社交媒体。Paul Graham 在 2010 年曾经写过一篇关于“加速成瘾”的文章,其中也提到,这个世界客观上就是会变得越来越“addictive”。现在这个加速成瘾的主战场无疑就是互联网。 当然也不要误会,把东西变得 addictive 总体上也有很多好处,只是要在正确的方向上,且不能过度。 如果说还有什么次要的东西,那可能是把互联网当做锤子,觉得如果通过互联网可以做那就一定要或最好通过互联网来做的事情,比如社交、学习、获取知识等等。我都根据个人的实际情况做了反思和调整。 另外完全退网很难也未必真的有意义,我给自己界定了一个范围,希望可以从这个范围开始尝试。 为什么要“退网”? ​ 促使我思考这些问题的因素有两个: 今年我短暂的尝试了两次所谓的“闭关”,效果意外的好,进而萌生了“退网”的想法; 年底连续读了几本书,有了一些感慨。 算是理性与感性、理论与实践相结合的结果。 连续读了几本书 ​ 我们倒叙来看,先说自己最近看过的几本书: The Psychology of Social Media Disconnected: How to Stay Human in an Online World Dopamine Nation: Finding Balance in the Age of Indulgence 读过之后我自己划了几个重点: 人类能够维系的社交网络客观上是有能力上限的,差不多就是 150 人左右,并且互联网的出现并没有让这个数字变得更大 大家在线上的和在线下的社交网络重叠度越来越高,换句话说,早期的互联网更多的是每个人的第二虚拟人生,现在逐渐变成真实世界的镜像或加分工具 (两个标志性的节点是实名制 Facebook 的诞生和 COVID-19) 最新一代的互联网社交平台已经 addictive 到榨干内容消费者的所有碎片时间,同时把内容创作者在平台上的积累剥夺得一干二净,且沦为维系平台生意的奴隶 最后这一条可能总结得太抽象了,说实际一点,就是作为看内容的人你可能觉得差别不大甚至更好了;但是从内容产生和创作的角度,你个人在社交平台上的影响力 (流量) 不再是严格取决于你自己有多少粉丝/follower/subscriber,即便你有很多粉丝,如果发的东西不是平台所鼓励的、或做了什么平台不喜欢的事情 (比如自己的活跃度降低)、再或者就是平台今天心情不好了想整点儿活,你的内容都可能被限流,表面上你觉得给粉丝发了很多内容,但有可能根本就没人看到。多年苦心经营的账号积累瞬间变得毫无意义。而且这一切都没有什么道理或正义在背后,单纯就是以平台方的利益最大化为出发点和结果导向。总之他们表面上给你的形象都是一个畅所欲言的自由空间,但本质上都是生意,这也导致越来越多的人对社交网络产生了被欺骗的感觉。 我顺着这个逻辑看了看自己周围的互联网产品,也可能是我自己孤陋寡闻的关系,总之绝大多数都是这样的 (有少数不是,我后面会提到),有些唏嘘。 另外我还学到一个特别有启发的知识点,就是人类 (通过多巴胺 dopamine 的分泌) 对每个人开心和痛苦之间的动态平衡有调节作用。就像 work-life balance 一样,每个人也有属于自己的 pleasure-pain balance,这个平衡点是可以刻意调整的。例如你总做一件事情并总能获得一个正反馈刺激 (比如刷社交媒体),你的平衡点就会被前移,导致你需要不断刷得更多才能得到满足,甚至不刷或少刷会感到痛苦。 P.S. 这三本书给我的收获远不止这些,只是我略去了和主题无关的部分。 事情就这样自己到眼前了 ​ 也许你觉得这也不稀奇了,尤其是如果你还是互联网从业者或稍微懂一点心理学,可能早就发现这些了。或者你觉得即便如此,日子还是能照过,为什么要因为这些就选择“退网”这种看似很极端的做法呢? 这跟我当前的个人生活状态有很大关系。 首先,我觉得 work-life balance 自从来到新加坡之后——确切地说是自从有了小孩之后——被我放在了非常重要的位置,同时也有了非常大的改进。这是我自己对长期的生活和职业发展充满信心的源头之一。 在这个过程中,我有了更多的户外和线下的时间,也逐渐发展了自己的本地人际关系网、兴趣爱好、业余活动等。 当你不断在其中收获了知识、愉悦、健康、各方面的成长之后,当然希望可以进一步加码,做得更多 (具体想加码什么就不多提了)。这个时候精力就永远会是一个问题,你需要不断地审视你的时间规划,把最没有价值的东西拿出来,换成你更希望去做的事情。随着我的不断实践,花在网上的这些时间就自然而然成为了值得思考的要不要取舍的东西。 事情就这样自己到眼前了 今年两次“闭关”的体验 ​ 今年我先后因为不同的原因“闭关”了两个月,第一次是因为做个人项目,第二次是因为全家出去玩。所谓闭关就是不看新闻不回消息不刷社交账号。那两个月我自己的状态特别好。而且之前每天都觉得自己在跟时间赛跑,所有的时间都当稀缺资源省着用,弦崩得特别近;那两个月就从容许多了,而且觉得自己的精力和专注力都集中在了最值得的地方。甚至第二次闭关结束的时候,我已经不渴望立刻恢复通网了,说明自己已经习惯了。自己的平衡点差不多已经归零了。 按照我之前的生活习惯,自己每天花在网上的时间 (工作除外) 零零总总少说也有一个小时,稍不留心放纵一下一整晚甚至一整天就过去了,想想还挺可怕的。如果能把这些时间利用得更好,就等于我每天又多了一个多小时。目前我最新的尝试是:每天主要的精力当然首先是放在工作上;在每个工作日的8小时以外,我给自己定了一套健身的计划,包括重量训练、跑步、游泳还有足球,差不多每天一个半小时;再额外的就是学习英语和阅读的时间,每天一个小时;再往下是参与开源和自己的一些别的副业,如播客、私人咨询等,最后是一些个人兴趣爱好。当然我的时间里还包括一些亲子互动还有线下聚会的环节,这些时间基本是每周固定的,换句话说某种程度上算是优先级最高的,所以我没有拿出来比较或讨论。 至于碎片时间,我基本都从之前的刷社交媒体变成现在的看新闻或看电子书。我每周有一个小时的时间把所有自己订阅的 newsletter 过一遍,然后把认为值得进一步阅读的文章链接存起来,这样我的碎片时间就可以靠这些内容来打发。 “退网”的范围 ​ 那两个月的“闭关”几乎就是我向往的生活的样子了,但如果想长期维持下去,可能消息多少是要回一下的,社交账号哪怕出于工作或某些需要也是得用的。所以我稍微缩小了一下范围: 互动频度高于一小时一次的产品,就是之前每个小时都忍不住要看的东西 内容流不严格遵循关注或订阅逻辑的“流氓”平台 这两类东西我会完全戒掉。如果有什么东西需要转发或公告,只会偶尔发一下,不会特别互动。 我认为的互联网中的清流 ​ 正如我之前提到的,少数互联网产品仍然满足我的使用条件。我这次调整自己作息规律的时候发现,播客 (Podcast) 就是其中之一,另外还有 newsletter/blog。所以我基本上就只会保留自己的 blog 和参与的播客节目。坦白说写 blog 可能就是个情怀了,更多的是写给我自己;但我觉得在这个时候,播客的魅力逐渐体现出来了,我一定程度觉得最近几年播客的兴起跟主流社交媒体毫无节制地消费用户是有直接关联的。 既然说到 Podcast 了,就还想叉开一个话题,每当我觉得互联网越来越“流氓”与“邪恶”的时候,时不时还是会看到一些像屏幕时间、屏幕距离这样的反商业的小功能,觉得还是有人在把科技往正确的方向推进,尽管速度并不快——仅个人观点。 会担心有什么损失吗? ​ 再说到不刷社媒,自己会担心有什么损失吗?损失肯定是有的,不然这网瘾也太好戒了。比如你可能会错过认识一些很棒的同行或朋友,比如你对新闻动态的掌握可能不会像以前那么敏感了,比如你的个人影响力可能会受到很大的影响,比如你可能会变成一个不会上网的老人家等等。我仔细想了想,一方面是自己对互联网有些疲倦了,很多市面上新出来的东西我自己都觉得还好,就是换汤不换药而已;另一方面我找到和发展了很多线下的替代品,比如线下聊天聚会、线下技术交流、去图书馆借纸质书来看,不论是效率还是效果都出奇的好,所以也就让自己下了这个决心。 至于个人影响力,我越来越觉得没啥所谓了,或者说那是一种流量焦虑,还是太把自己当回事儿了;更何况现在流量都是平台的了,做这件事情的投入产出比越来越低,所以我已经完全想开了。 其实以上这些同时也都是我之前潜意识里一直忍不住想分享个人动态以及跟别人互动的一些背后的因素,我很开心自己已经慢慢调整过来了。 再其次 ​ 我接下来会更专注地在 GitHub 参与开源项目,同时通过《代码之外》这样的播客节目分享自己的观点。 所有 DM 消息在自己有安排的时间段都会异步集中处理 所有非业务聊天群都静音,只保留 3 个在消息列表里,其他的都隐藏。群聊特别浪费时间!群聊特别浪费时间!群聊特别浪费时间! 总结 ​ 明年我大概率会: 退出所有线上的社交媒体,主要是 Twitter/X,我的关注会清零,除了业务需要不再发帖和互动 (以其平台调性,经过这个月的闭关,我现在发布东西应该已经没有什么流量了),Facebook 和 Instagram 的账号我已经删了。 绝大部分我之前关注的 Twitter/X,要么我有联系方式,要么就转为 newsletter 订阅 (其实我目前已有的 newsletter 已经完全够用了) 降低群聊和消息的活跃度 继续活跃在 GitHub 和 Podcast 继续坚持写这个 blog 和做私人咨询 (更新:目前我的一对一私人咨询服务处于暂停状态,未来会考虑以一些别的更好的形式与大家交流,感谢大家的理解) 多学习,多看书,多锻炼 希望这套做法行得通,下一步可能就是进一步删掉 Twitter/X 账号或退群什么的,总之到我认为比较健康的程度。 也希望对大家有所帮助或启发。

2024/12/30
articleCard.readMore

我和 Vue.js 的十年

本文是我在 VueConf 24 CN 的开场演讲稿,分享我和 Vue.js 的十年的故事。 Paper Mario 的主题风格,希望大家喜欢。 VueConf 24 CN 官网:https://vueconf.cn/ 在线幻灯片地址:https://jinjiang.dev/slides/my-10-years-with-vuejs/ 演讲 B 站视频地址:https://www.bilibili.com/video/BV16W421R7ga/ 一转眼,Vue.js 已经十周年了,这同时也几乎是我个人参与 Vue.js、参与开源项目的十年。 我和 Vue 缘分的开始应该是自己 2014 年在阿里内部创建的一个项目,名字如果没有记错的话叫 lib-noble。这个库算是一个 data observer 的 JavaScript 实现,后来我们管这种东西叫 reactivity API,再后来叫 signals。在我看来大同小异。写了这个库没多久,我就在 GitHub 上发现了 Vue.js,其中数据处理的相关设计和实现都跟我自己写的东西不谋而合。我当时的第一反应是,这个库比我写的好多了,而且还有很多其他功能,比如组件系统、模板编译等等。于是我就开始关注 Vue.js,并一路参与到了今天。 在这十年里,Vue.js 本身,包括团队,都经历了很多事情。我自己所经历的故事,已经出圈且被大家熟知或调侃的,也许就是自己在 Vue 的第一个 PR (只改了两个空格)、维护中文官网 (今年愚人节我们玩了个“威优易”的彩蛋)、创建了开源项目 Weex 并和 Vue 有一段时间的双向官方合作、以及在 Vue 的纪录片中出镜等等。相关的内容不打算赘述了。这次我主要想分享几则背后发生在 Vue 和我自己身上的小故事。尽可能还原一个更加立体的历经 10 年的开源项目。 一、阿里巴巴西溪园区星巴克 ​ 时间:2015 年 1 月 5 日 背景是一个工作日的下午,尤雨溪来阿里巴巴西溪园区和团队分享介绍他的新项目 Vue.js,那也是我在团队内部推广 Vue.js 的时间点。分享结束后,我们和另外几个阿里的同事一起去星巴克闲聊。 我们讨论了很多技术相关的话题,另外印象最深刻的是,我通过尤雨溪的介绍认识了很多讨论技术的国际化的社区,这在我之前的工作习惯中是很少接触到的。这也对我后来参与开源项目、通过国际化技术社区了解和交流技术有了很大的帮助。 拥有一个开放的技术视野,对于一个开发者来说是非常重要的。这也是我在与 Vue.js 的十年中的一个不大不小的收获。 二、和天猫谈框架选型 ​ 时间:2016 + 2017 连续两年 当时我是阿里无线事业部的前端架构负责人,得知天猫前端团队有一个框架选型的窗口,于是试着推荐了一下 Vue.js 给他们。我们第一次谈的时候是 2016 年末,当时天猫前端团队对 Vue.js 的主要质疑是基础功能层面的,比如是否可以支持 IE6 之类的问题。我因此也和小右做了简短的交流,发现其实是有机会的,比如通过 VBScript 做一个数据监听的兼容层。我们还围绕 Vue.js 的一些别的特性做了讨论和交流,但很遗憾的是最终天猫前端团队还是选择了另外一个框架,那个框架的名字叫做 React。这是我们第一次就框架选型进行的交流。 虽然交流结束了,但对于我个人推广 Vue.js 的尝试来说,这只是一个开始。2017 是 Vue.js 快速发展的一年,不但发布了全新的 2.0,同时 Weex 这个项目也宣布开源,和 Vue.js 展开全面的官方合作。2017 年末,听闻天猫前端团队又有一个框架选型的窗口期,我决定跟他们再谈一次。这次的交流更多的是关于 Vue.js 的生态和社区,但仍然是充满了各种质疑,比如天猫方面觉得当时 Vue.js 的生态不够完善,谈话再一次陷入了僵局。我当时觉得完全没有办法改变天猫对 Vue.js 的感官,这次可能又要败兴而归了,临走之前我还是不甘心,甩下了一句接近是发牢骚的话,我说你看去年你们在担心 Vue.js 的特性,我们用了一年时间证明框架的特性完全不再是问题;今天你们再次担心 Vue.js 的生态和社区不够成熟,我虽然没有办法立刻回答这个问题,但我敢打赌,明年我们再见面的时候你们不会再问这个问题了。这段话说完之后自己扬长而去。 后面发生的故事大家可能都知道了,天猫前端团队稍后决定选择 Vue.js + Weex 作为天猫前端的主要框架。 这次“争取客户”的经历让我印象深刻。和我们平时在公司做的项目不同,开源项目的用户不是天生就有的,而且没有专业的市场和运营团队替你做这些事情。唯有自己不断去主动地、反复地争取,才会有转机出现,才会有真正的用户。这是我参与开源前所未有的体验之一。 三、阿里食堂一顿普通的工作餐 ​ 时间:2016 年末 当时已经是我在 Weex 团队的末期了,和 Vue.js 的官方合作也进行了一段时间。我和自己当时的主管一起在食堂排队打饭。因为队伍很长,我们俩闲来无事,就随便聊天。在谈论到一些工作上的问题时,我在想也许这是一个不错的时机,就把自己心里憋了很久的一段话说了出来。我说自己其实不得不坦承,做开源做到现在,心态已经逐渐发生了微妙的变化,虽然深知自己是阿里的员工,但归属感更加倾向于开源本身,而不是公司,这是一种近乎本能的感受。我的主管听完之后,没有说什么,意味深长地点了点头。 我想说,我们很多人对待开源,都是在自己有工作的基础上利用从业余时间起步的,也许或多或少都经历过这种做事方法和意识形态上的挣扎。当矛盾和冲突出现的时候,是什么让你处于纠结的位置?你会本能地站在哪一边?自己如何分配自己的精力?这些问题,我想,是每一个参与开源项目的人都会遇到的。我把这个称之为开源人的“身份认同”感。 四、微博上的(激烈)争论 ​ 时间:2017 年 8 月 也许人们已经逐渐忘记 2017 年 8 月前端圈发生过什么样的争论,那个时候 Vue.js 逐渐走入了主流的视野,伴随而来的是很多人的挑战和不屑。 我得说,简中互联网一直就不是一个和谐的存在,很多言论都充满了攻击性。在那个时期,每个人的价值观、表达方式、看问题的角度都是不同的,这种差异在互联网上被放大了。我也因为一些言论而被人攻击,也可能不经意间冒犯到了别人。但发生在那段时间对 Vue.js 和小右本人的争论,非常火爆而惨烈,而且看不到收敛的趋势。最后这件事情已经严重影响到了当事人正常的生活,而小右也不得不被老婆没收手机,通过这种“非常规操作”,整件事情才告一段落,大家也才逐渐冷静下来。 当时大家具体的讨论内容,我们今天不必多谈,因为 Vue.js 通过这么多年的发展已经把各种争议逐一摆平。但在那个时刻,作为一个开源项目,当你被更多人看到的那一刻,也就是会被更多人拿起放大镜审视的一刻。这是一个槛儿,一条长征路上的必经之路。 同样地,如果只跟自己公司的客户打交道,你可能永远也不会想到,作为一个开源项目,你无时不刻会遇到各种各样的人,你甚至不知道他们来自哪里,但却需要面对他们的每一个问题和质疑。这是做开源的另一门必修课。 五、在 Vue Fes Japan 碰面 ​ 时间:2018 年 11 月 3 日 这是我第一次去日本参会,也是我第一次参加 Vue.js 的一个国际性的大会。我在会场里碰到了很多来自世界各地的开发者,他们对 Vue.js 的热情和专业程度让我印象深刻。 那次会议给我印象最深的其实是我和小右在会后的一次交流。在那次交流中,我们聊了很多关于个人和项目长期发展的话题,比如要有长期的规划,要有壮大团队的准备,要有合理的经营模式等等,包括“被动收入”这个词,就是我第一次从这次交流中听到的。 这让我想到,做开源,不是把源码放到 GitHub 上就完事了,而是需要有一个长期的规划和经营。这个规划和经营,不仅仅是技术上的,还有很多其他方面的,比如社区、商业、生态等等。 六、一个周末下午的上海 ​ 时间:2016 年夏 那个时候我还在 Weex 团队,和小右的合作已经进行了一段时间,我们在上海的一个咖啡馆里见面,本身是聊 Vue.js 和 Weex 集成的一些技术细节,但聊完工作之后,大家都还有些时间,就继续闲聊了很多别的技术。印象中同行的还有 Hax,一位非常资深的同行,后来成为了国内为数不多的 TC39 成员。 那是我们接下来无数次讨论打包构建工具的开始,当时 webpack 已经变成毫无争议的主流,但性能和复杂工程的配置问题也逐渐变成了前端工程家家户户难念的经。同年 Rollup.js 也开始崭露头角,我们也讨论了很多关于 Rollup.js 的优势和劣势,以及它和 webpack 的区别。后来大家都知道了 Vite 的诞生,从我的视角来看,Vite 的诞生看似是一次偶然的尝试,但实际上是一个长期的技术积累和思考的结果。直到一个基于上述的创新的想法被证实,才会被大家看到。 有的时候做成一个开源项目,不是因为你知道的东西更多,而是因为你在大家都知道的地方,产生了独创的智慧的想法,做了一些别人没有做到的事情。你需要一双慧眼,哪怕只是改变这个世界一点点。 七、开源赞助都蒸发了? ​ 时间:2023 年 3 月 时间来到去年春天,当时 GitHub Sponsor 终止了对 PayPal 的支持,导致我周围很多以接受社区赞助为主要收入来源的开发者都遭遇了一次收入的大幅度下降。很多人都在网上晒自己损失掉的赞助,或表达各种负面的情绪。 我虽然严格意义上不算是靠开源赞助谋生的开发者,但我看到周围的朋友都在失去赞助,心里也特别不是滋味,觉得应该做点什么。于是决定在社交平台上号召更多人加入到赞助的行列。我自己也开始在 GitHub 上赞助自己周围的开源人,虽然不是很多,但我希望能通过自己的实际行动给社区提提气,也希望这是一个好的开始。 通过这次经历,也让我设身处地的感受到,很多开源项目的维护者,尤其是那些靠开源项目谋生的人,他们的生活其实并不容易。即便是那些大名鼎鼎的开源项目,维护者的收入也随时处在不稳定的状态,这种不稳定的状态和不安全感,是我们习惯在大厂上班,有固定的薪水和五险一金,甚至年底还能拿大红包的人所无法体会的。 八、在新加坡的不期而遇 ​ 时间:2021 年至今 我和小右两家人都先后搬来了新加坡,也许你和我一样曾设想过,我们两个每天待在新加坡的某个咖啡馆里讨论技术的场景。但实际上是,我们每次见面绝大部分的话题都是技术以外的琐事,比如如何带小孩,如何管理好自己的时间,当然偶尔也会一起打打游戏看看比赛之类的。 更近距离和小右接触下来,我觉得小右对我来说已经不仅仅是技术上的同行,而更像是生活上的伙伴,一个活生生的人。这种人情味,是我们在 GitHub 上看不到的。但这也让我想到,开源项目的背后,其实都是这样一群人。大家一开始做开源也许只是一个简单纯粹的动作。随着时间的推移,逐渐拥有更多自己的生活、家庭、甚至上有老下有小之后,做开源不再那么简单,而变得复杂。这是难免会遇到的、同时也是需要我们正面面对的问题。 总结 ​ 其实除了上述这几则故事,这 10 年间发生在 Vue.js 和我自己身上的珍贵回忆还有非常多,包括项目的 GitHub star 一路飙升,超过别的我们之前仰慕的开源项目的时候;包括每次有大公司宣布选用或赞助 Vue.js 的时候;包括每次新版本 Vue.js 发布的时候;也包括每次参加的团队线下聚会;也包括每次新人加入、旧人离开;这些都是我这 10 年来参与 Vue.js 的一部分。每一段旅程都有值得细品的独一无二的内容。 然而我选择上述这八则故事的原因,是我觉得他们充分体现了五个字:开源之不易。 开源之不易,不仅仅是技术上的高瞻远瞩和灵光乍现,还有很多其他方面的。开源之不易,是因为你需要不断地去争取用户,去解决用户的问题,去回答用户的质疑。开源之不易,是因为你需要不断地去面对各种各样的人,去解决各种各样的问题。开源之不易,是因为你需要不断地去思考未来,去规划未来,去经营未来。开源之不易,是因为你需要不断地去适应变化,去接受挑战,去面对困难。 所以,每次回首 Vue.js 走过的十年,我越来越觉得,这是一段不易的旅程,也越来越庆幸自己能够参与其中,更由衷地感谢所有支持 Vue.js 的人。 在前端技术快速发展迭代的今天,你也许会觉得一个 10 年的框架已经算是“老掉牙的框架”了,但我觉得 Vue.js 并不是,引用我特别喜欢的电视节目《圆桌派》中的一句话:“心中有大志”。如果心中没有伟大的志向,那人才是真的老了。我想说,Vue.js 在 10 年后的今天,依然活力十足,我们有非常多年轻有为的新鲜血液不断加入,也一直在推出新的特性和工具,包括不仅限于这次 VueConf 上介绍的 Vapor mode、新版 DevTools、当然也包括新的 Vite 和 Rolldown 等等。我们有理由期待 Vue.js 的未来。 也希望大家可以跟 Vue.js 一起,再战十年!

2024/7/11
articleCard.readMore

我参与《代码之外 Beyond Code》的故事

关注我的人可能已经知道我最近作为常驻嘉宾加入了一个叫做《代码之外 Beyond Code》的播客节目。 官网 Apple Podcasts 小宇宙 Bilibili (视频版) YouTube (视频版) 这里简单分享一些我参与《代码之外》的故事: 什么是《代码之外》? ​ 这是一档由 GeekPlux 和 Randy 共同主持的程序员闲聊节目。虽然二位主播都是程序员,但节目基本不谈编程写代码,而是谈一些程序员在工作和生活上比较有共鸣的话题,或是从程序员的视角看待一些社会热点。截至目前我主要参与的部分是听众来信栏目,在节目上公开回答听众们的问题。 听众来信提问入口 我是如何加入《代码之外》的? ​ 我其实和 GeekPlux 和 Randy 之前都相互认识。GeekPlux 是我之前在阿里云的同事,短暂共事过一阵子;而 Randy 是因为之前我做 Weex 这个项目有机会和他认识。后来我从阿里离职,也逐渐跟他们比较少联络了,直到最近我在社交网络上发现他们做了这个节目。我从“第 0 期”就开始听,一方面是因为好奇他们二位最近的工作和生活状态,另一方面也是被节目内容所吸引。 因为我一直觉得技术类的话题在网上包括线下的交流会到处都是,但是技术以外其实还有很多值得探讨的话题,在国内并没有什么平台和机会进行交流和分享。这个观点我之前在关于 D2 前端论坛的一篇博客里曾有提及: 我希望 D2 今后可以有一些关于团队、关于人的主题。我们的技术能搞上去,业绩能搞上去,人,怎么样? 《代码之外》在我心目中恰恰是这样的节目,它让很多值得被关注的非技术议题浮出水面,引发大家的讨论,在我看来是非常非常有意义的,也是我一直想做但一直没有付诸实际行动的事情。所以我每一期都听得津津有味,而且非常有带入感。 大概听了两三期之后,《代码之外》有了自己的 telegram 听友群,我也跟着其他听众一起进了群,跟 GeekPlux 和 Randy 恢复了联络。我跟二位说,节目有我能“插上话”的地方都可以随时找我,非常乐意贡献自己的观点和看法。他们非常爽快地答应了。 后来发生的事情就是我被邀请作为嘉宾录了一期节目: 第 6 集 | 勾股如何看待 Weex 被指 KPI 项目、转管理的经验、向上管理的技巧、如何建立个人品牌、双十一的经历 在这期节目中,我们顺带讨论了一封关于如何建立个人影响力 (reputation) 听众来信。节目播出后效果似乎还不错,再之后我开始作为固定嘉宾参与每一期听众来信的录制。不得不说来信的问题质量都很高,每一个问题都是好问题,我们也是来者不拒,从职场技能聊到个人发展,再聊到开源项目心得等方方面面。 我为什么选择加入《代码之外》? ​ 首先是因为之前所提到的我对非技术类话题的价值认同,我觉得这个是核心原因吧。 其次,我非常欣赏 GeekPlux 和 Randy 在节目中展现出的个人气质和魅力,我相信这也是有这么多人收听这个节目的重要原因之一。有机会和他们二位对谈,讨论有价值的议题,碰撞思想,是一件非常珍贵、也是非常酷的事情。 同时从自身的角度,我也希望可以运用好自身多年编码和管理的经验,输出一些自己平时对生活和工作的观察和思考。这段时间对于很多国内的从业者来说,可能是一段特别艰难的时期,我能直接参与和提供的帮助有限。但用现在流行的说法讲,也许我可以通过这个节目提供一些“情绪价值”:) 最后,我也希望自己可以保持一个不断接触新事物的学习的心态。一方面是暗中观察 podcast 前前后后的工作,不只是在节目上传递信息,更包括参与完整的制作和宣发流程,学习相关的专业知识和技巧,结实这个圈子里的有识之士;另一方面也可以被更多年轻的听众认识,近距离观察和学习每个人的经历和看问题的角度。这些长期来看一定会给我自己带来很多收获。 当然也有时间的因素,我现在在新加坡的工作和生活逐渐趋于稳定,有了些闲暇的时间,正好可以支撑我做一些额外的事情。各方面因素综合在一起,让这件事情顺理成章。 几期节目做下来之后的心得感受 ​ 截至目前,我和 GeekPlux 和 Randy 二位已经陆陆续续一起做了 7 期节目,包括 1 期我自己的人物访谈和 6 期听众来信: 第 6 集 | 勾股如何看待 Weex 被指 KPI 项目、转管理的经验、向上管理的技巧、如何建立个人品牌、双十一的经历 听众来信 #6 女性程序员的职业社交困境、如何在工作中建立人际关系 听众来信 #7 如何平衡写代码和其它学习计划? 听众来信 #8 995 没有自己的时间,如何调整? 听众来信 #9 AI 时代,如何应对? 听众来信 #10 曾赚到一千万,35 岁失业,有哪些现实的出路? 听众来信 #11 如何学英语?“半途而废”的开源 第一个感受就是每次跟二位聊天都特别开心,一聊就忘记时间停不下来。大家不止一次聊到某个观点的时候发自内心的相互认同,有一种找到知音的感觉。再有就是参与这个节目让我以一种更认真负责细致的态度审视看似平实的东西,每次跟着二位在节目前准备素材、节目中互相碰撞、节目后关注和复盘的过程中,自己都有非常多的收获。 另一个有趣的事情是,我在这个过程中改进了我的声音。尤其是开始参与后期制作的时候,我发现自己有的时候说话有气无力,或者有一些不好的说话习惯,包括那些啧啧啧、嘶嘶嘶的下意识发出的声音,真的是觉得影响听感又非常难克服。我还在持续改进中,并感慨每件事情做到极致都是不容易的。 对于音频/视频的处理,我也算是入了个坑,至少了解了一些常用的软件和专有名词,尤其是音频的剪辑和处理,门道其实还挺多的。由于自己还是菜鸟阶段,没到系统性总结的时候,这里就不展开了。 我通过这个节目还认识了一些优秀的播客们和主持人们,前段时间还有幸跟另外几个播客节目的主播们串了个台,估计节目很快就会上线了,也尽请关注。 当然最开心的莫过于得到了很多来自听众们正面的反馈。我深知做好一档节目有很多地方需要继续努力,这些正面的反馈给了我自己莫大的鼓励和坚持下去的动力。 听勾股聊天还蛮有启发的,建议常驻😆 通过读书来学习管理,这挺有趣,因为之前看到一些观点说管理并不能从书本学习到。记录一下《合作的进化》《领导学》 这期真的学习了很多 Beyond Code and Far more than I learned 2个小时,看完了,干货很足,期待后续的节目~ 受益良多[打call] 一口气听完,比看场电影都爽 听完最大的感受是,勾股情商好高[吃瓜][打call] 很好的节目[支持][支持]目前也在备考雅思 中间嘉宾的发言真是太认同了 超级开心啊,之前勾股的访谈那期真的学到了很多,以后变成常驻嘉宾真的太好啦! 筹备自己的一对一咨询服务 ​ 有一期节目录完之后,跟 Randy 和 GeekPlux 二位主播 after cut 闲聊,他们都鼓励我做个一对一私人咨询的服务,觉得我的观点和经验会帮助到更多的人,尤其是我们都意识到,现如今听众来信里的很多话题,都逐渐从小众转化为了大众、甚至社会性的话题;同时一对一私人咨询也可以做得比节目更深入,更好的保护当事人或公司的隐私需求,因为节目的时间和机会毕竟有限,且是公开形式的,很难针对性 + 回合式地聊太深。于是我打算抱着试试看的心态做起来,也许近期就会有进展跟大家再分享。 2024-04 更新:我的一对一私人咨询服务已经启动,请移步至: 点击预约我的一对一私人咨询服务 (已暂停) 2025-11 更新:目前我的一对一私人咨询服务处于暂停状态,未来会考虑以一些别的更好的形式与大家交流,感谢大家的理解 同时,《代码之外》和听众来信栏目我也会和二位主播一起继续做下去,继续解答大家的问题。 听众来信提问入口 如果大家对于这个一对一咨询服务的形式有什么兴趣、想法或建议,也欢迎通过任何方式让我或《代码之外》节目知道。 以上

2024/3/26
articleCard.readMore

博客站迁移至 VitePress 的备忘

水一篇。这周集中把博客由 Hexo 迁移到了 VitePress,顺便把主题也换了。简单记录一下迁移过程。 根据我的观察 VitePress 目前用的最多的是文档站,比如 Vue 的官方网站、Vite 的官方网站、Rollup 的官方网站等。但是拿它来做博客站的不多,但也完全没有问题,比如 Vue 的官方博客。另外我还找到一个叫 vitepressblog.dev 的站点,它是一个 VitePress 的博客主题。对两者做了简单的比对之后,我觉得 Vue 的官方博客的主题更简单更适合我的需求,所以我就直接把源文件拿来用了。 主体迁移过程 ​ 我之前的博客内容主要分了四部分: 独立页面:比如“关于”页面,一共有三个 文章:有将尽 200 篇 文章导航:即文章列表页面和归档页面,按照时间倒序分页呈现,其实首页也算这个类别 静态资源:一些前端的小 demo,作为静态资源存在 我把这四部分进行了逐步迁移 先把独立页面迁移过来,这个很简单,把每个文件复制到相同的路径就好了 把所有文章都拷贝到新仓库的 blog 目录,但不急于调试文章的展示页面,因为这些文章还暂时没有入口,所以我紧接着先迁移了文章导航 实现文章导航。Vue 的博客站主题有一个明显的局限性,就是它的博客列表不支持分页。我这里运用的是 VitePress 自身的 Dynamic Routes 特性,在工程里同时创建了 page/[page].md 和 page/[page].paths.js 来计算和生产分页信息。如 paths.js 的内容如下: js import { readdirSync } from 'fs' const PAGE_SIZE = 10 // 计算文章数量 const data = readdirSync('./blog').filter(x => x.match(/\.md$/)) // 计算页面数量 const pageMax = Math.ceil(data.length / PAGE_SIZE) // 创建分页参数 const pageParams = new Array(pageMax).fill(0) .map((_, i) => ({ params: { page: i + 1 } })) export default { paths() { return pageParams } } 这样在相应的渲染组件里就可以: js import { useData } from 'vitepress' // 这里的 `./posts.data.js` 和 Vue 博客站的实现相同,所以就不赘述了 import { data as posts } from './posts.data.js' const PAGE_SIZE = 10 const { params } = useData() const currentPosts = computed(() => { const { page } = params.value || { page: 1 } const start = (page - 1) * PAGE_SIZE const end = start + PAGE_SIZE return posts.slice(start, end) }) 同理,对于归档页面来说,也是对应的实现 archives/page/[page].md 和 archives/page/[page].paths.js,只不过 PAGE_SIZE 我定为了 100。 另外我实现了一个简单的 Pagination.vue,用来实现文章列表页面上的分页器。代码很简单就不贴了。 接下来确保每一篇文章都能够被识别和渲染。因为我之前有些 blog 写得略随意,有些直接用 HTML 写的而不是 Markdown,且标签没有严格闭合和转码,还有些索性往文章里塞了一段内联的 <style> 和 <script> 直接出 demo。这些细节在 VitePress 里多多少少都引发了一些解析错误,我也都逐个修复了。 最后就是静态资源的迁移了。这个比较简单,直接把之前的静态文件拷贝到 public 目录就好了。 经过了这些粗枝大叶的迁移之后,网站差不多可以跑起来了,内容也都正常展示出来了。接下来就是一些细节的完善和优化了。 细节完善和优化 ​ 首先是把模板里不必要的信息注释掉或删掉。Vue Blog 的模板里有一些我这里不需要的信息,比如作者、头像不需要,因为就我一个人写;另外我在写作的时候越来越希望内容本身经得住时间考验,不论是什么时候写的都值得一读,所以发布时间之类的信息我个人觉得干扰阅读,为了极致的阅读场景我把展示这些信息的组件也都拿掉了。同时布局方面我也把侧边栏拿掉了。这完全是我的个人偏好。 还有一个小细节是关于文章列表中每一篇文章的摘要,之前 Hexo 是通过寻找内容中的 <!--more--> 记号并以此为分界来截取摘要的,但 VitePress 中是通过 --- 来识别的,并且在我迁移的那一刻还没有支持自定义分界记号。所以我只能手动 (当然是批量处理的) 把文章内容之前的分界记号全部都改成 ---。不过好消息是我把这个问题提给了 VitePress 并很快得到了支持,最新的版本已经支持自定义分界记号了。大家如果再遇到这个问题就不用手动改了。相关 issue 再接下来是处理文章评论,这里有两部分,第一部分是早期我的博客基于 Typecho 的时候产生的评论,数据在 MySQL 里,当然这部分内容现在看是纯静态的了,不会再有更新了;第二部分是现在我的博客基于 Hexo 的时候产生的评论,数据在 GitHub issues 里,通过一个叫 gitalk 的库进行加载。后者相对容易,我把 gitalk 这个库导入评论组件的 <script setup> 里调用就可以了,并且监听路由改变,如果文章换了就重新初始化并加载对应的评论。 前者就比较麻烦了,因为我不想继续把 Typecho 的数据库挂在服务器上,所以我把数据导出成了 JSON 文件,然后在 transformPageData() 的时候把数据作为 earlyComments 灌入 frontmatter 元数据。这样在 build 的时候,每个页面都可以通过 VitePress 自带的 useData() 访问 useData().frontmatter.value.earlyComments 获取到这个数据,进而在页面上渲染对应的早期评论。 下一步是支持 open graph 之类的元数据。这里绝大多数信息我都是通过 config 中的 transformHead(context) 函数实现的。基本原理就是从 context.pageData 中分析出要展示的元数据,然后生成 <meta> 标签信息返回。但这里有三个特例: description:首先 pageData 里没有现成的信息,所以我自己写了个很简单粗暴的函数,从文章对应的源文件中读取内容,提取纯文本,然后截断前 200 个字符作为描述信息。不算特别严谨,但反正我平时写作时对格式对运用也比较规矩,所以足够了。大概的实现我提取了一个函数 ts import fs from 'fs' import matter from 'gray-matter' import { markdownToTxt } from 'markdown-to-txt' export const genDescription = (filepath: string): string | undefined => { if (fs.existsSync(filepath)) { const content = fs.readFileSync(filepath, 'utf-8') const data = matter(content) const result = markdownToTxt(data.content.replace(/<[^>]+>/g, '')).replace(/\s+/g, ' ') return result.length > 200 ? result.slice(0, 197) + '...' : result } } description 这个元数据 VitePress 本身也会生成,并且在我迁移的那段时间是不支持合并或覆盖的,导致页面生成出来的 HTML 里会有两段 <meta name="description">。这个问题估计你们猜到了,我也提 issue 了,最新版已经修复了。但我在这个 issue 被修复之前采取了另外一个临时的解决办法,就是通过 config 里的 transformHtml(code) 字段,把多余的 <meta name="description"> 标签删掉。 ts async transformHtml(code) { // dedupe <meta name="description"> const results = [] const regExp = /<meta name="description"[^>]+>/gi while (regExp.exec(code)) { results.push(regExp.lastIndex) } if (results.length > 1) { return code.replace(/<meta name="description"[^>]+>/, '') } }, og:image / twitter:image 这两个字段目前社区最热门的实现方式是在服务端根据文章信息渲染一张图然后返回 URL (进而让用户觉得反正都已经上 Node 了不然就全站 SSR 吧,或者说至少这个功能跑不掉了那肯定得上 SSR)。说得好像不用这种服务博客都没法写了一样。我冷静的想了想好像也不必,选了个自己能接受的笨办法,就是找个离线工具生成自己想要的缩略图放到静态资源目录,然后在 frontmatter 里手写一个 manual_og_image 字段指向这个文件就好了。所以最终我的博客站用的依然是纯静态服务器。 最终的 transformHead(context) 实现大概是这样 (其中 genMeta 和 getIdFromFilePath 逻辑并不复杂,也不是这里讨论的重点,就不展开了): ts async transformHead(context): Promise<HeadConfig[]> { // add <meta>s const description = genDescription(context.page) const title = context.pageData.title const url = `https://jiongks.name/${getIdFromFilePath(context.page)}` const published = context.pageData.frontmatter.date const updated = context.pageData.frontmatter.updated const ogImage = context.pageData.frontmatter.manual_og_image const tags = context.pageData.frontmatter.tags || [] const type = context.page.startsWith('blog/') ? 'article' : 'website' const head: HeadConfig[] = [ // Basic description ? genMeta('description', description) : undefined, // Open Graph description ? genMeta('og:description', description) : undefined, genMeta('og:title', title), genMeta('og:url', url), genMeta('og:type', type), ogImage ? genMeta('og:image', `https://jiongks.name/${ogImage}`): undefined, // Twitter description ? genMeta('twitter:description', description) : undefined, genMeta('twitter:title', title), genMeta('twitter:url', url), ogImage ? genMeta('twitter:image', `https://jiongks.name/${ogImage}`): undefined, genMeta('twitter:card', ogImage ? 'summary_large_image' : 'summary'), // Article published ? genMeta('article:published_time', published) : undefined, updated ? genMeta('article:modified_time', updated) : undefined, ...tags.map((tag: string) => genMeta('article:tag', tag)), ].filter(Boolean) return head }, 最终生成的代码如下: html <!DOCTYPE html> <html lang="zh-CN" dir="ltr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>中文格式化小工具 zhlint 及其开发心得 | 囧克斯</title> ... <meta name="description" content="介绍要给小工具给大家:**zhlint** zhlint logo 这个工具可以帮助你快速格式化中文或中英混排的文本。比如常见的中英文之间要不要用空格、标点符号要用全角字符之类的。 看上去这工具似乎和自己的工作和职业关系不大,但其实也是有一定由来的。 项目的由来 自己之前参与过一些 W3C 规范的翻译工作,这其中除了需要一定的词汇量、语法知识和表达技巧之外,最主要的部分应该就是格式了。因为大..."> <meta name="og:description" content="介绍要给小工具给大家:**zhlint** zhlint logo 这个工具可以帮助你快速格式化中文或中英混排的文本。比如常见的中英文之间要不要用空格、标点符号要用全角字符之类的。 看上去这工具似乎和自己的工作和职业关系不大,但其实也是有一定由来的。 项目的由来 自己之前参与过一些 W3C 规范的翻译工作,这其中除了需要一定的词汇量、语法知识和表达技巧之外,最主要的部分应该就是格式了。因为大..."> <meta name="og:title" content="中文格式化小工具 zhlint 及其开发心得"> <meta name="og:url" content="https://jiongks.name/blog/introducing-zhlint"> <meta name="og:type" content="article"> <meta name="og:image" content="https://jiongks.name/og/introducing-zhlint.png"> <meta name="twitter:description" content="介绍要给小工具给大家:**zhlint** zhlint logo 这个工具可以帮助你快速格式化中文或中英混排的文本。比如常见的中英文之间要不要用空格、标点符号要用全角字符之类的。 看上去这工具似乎和自己的工作和职业关系不大,但其实也是有一定由来的。 项目的由来 自己之前参与过一些 W3C 规范的翻译工作,这其中除了需要一定的词汇量、语法知识和表达技巧之外,最主要的部分应该就是格式了。因为大..."> <meta name="twitter:title" content="中文格式化小工具 zhlint 及其开发心得"> <meta name="twitter:url" content="https://jiongks.name/blog/introducing-zhlint"> <meta name="twitter:image" content="https://jiongks.name/og/introducing-zhlint.png"> <meta name="twitter:card" content="summary_large_image"> <meta name="article:published_time" content="2020/04/26 03:53:59"> <meta name="article:modified_time" content="2020/04/27 12:29:56"> <meta name="article:tag" content="Chinese"> <meta name="article:tag" content="lint"> <meta name="article:tag" content="tool"> </head> <body> ... 最后处理了两个 Vue Blog 样式上的小问题: Dark mode (中文翻译是夜间模式?暗黑模式?深色模式?) 下 <mark> 标签内默认的文字配色有问题,我做了简单的修复。 css /* fix color theme in <mark>s */ .dark\:prose-invert mark a, .dark\:prose-invert mark strong, .dark\:prose-invert mark code { color: #111827; } 为了避免过长的 URL 无法折行导致页面布局被破坏,我在必要的地方加了 word-break: keep-all,对应的 Tailwind class 是 break-all (2014-07-15 更新:仅在必要的 URL 前后包裹了一层 <span class="break-all">...</span>,把负面影响降到了最低)。 最后的最后支持了一下统计代码和 RSS 订阅文件 (这个 Vue Blog 就有,我做了些微调,沿用了我的博客站之前的输出格式),大功告成。 简单回顾一下,有几个 config 字段在整个迁移过程中起到了关键的作用,它们基本都在 build hooks 分类里。如果你也有类似的旧版本迁移或想给自己的网站定制一些特殊的功能,可以留意: transformPageData():预处理页面数据,比如早期的评论 transformHead():预处理 <head> 标签内的内容,比如 <meta> transformHtml():后处理 <body> 标签内的内容,比如 <meta> 去重 buildEnd():后处理全站的 HTML 内容,比如 RSS 订阅文件 锦上添花 ​ 把该迁移的都迁移完毕过后,我又想了想,能不能顺便再给自己的网站加点什么呢?于是我又做了几个小功能: manual_og_image:这个字段是用来在 <meta> 里指向文章缩略图的,上一节其实已经提到过了,其实算是个新东西,之前没有仔细弄过。不重复介绍了。 View transitions:这是一个相对较新的 W3C 规范,用来定制页面跳转之间的动画。关键代码片段: vue <script setup> import { useRouter } from 'vitepress' const router = useRouter() router.onBeforePageLoad = async () => { if ((document as any).startViewTransition) { await (document as any).startViewTransition() } } </script> <template>...</template> <style> #app { view-transition-name: app; } @keyframes fade-in { from { opacity: 0; transform-origin: bottom center; transform: rotate(-5deg); } } @keyframes fade-out { to { opacity: 0; transform-origin: bottom center; transform: rotate(5deg); } } ::view-transition-old(app) { animation-name: fade-out; /* Ease-out Back. Overshoots. */ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); } ::view-transition-new(app) { animation-name: fade-in; /* Ease-out Back. Overshoots. */ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); } </style> 动图效果: Progress animations:这是一个更新的 W3C 规范,用来定制各种和滚动条进度自动绑定的动画效果。关键代码片段: vue <template> <div class="progress" /> </template> <style> /* progress animations */ .progress { height: 4px; background: transparent; position: fixed; bottom: 0; left: 0; width: 100%; transform-origin: 0 50%; animation: scaleProgress auto linear, colorChange auto linear; animation-timeline: scroll(root); } @keyframes scaleProgress { 0% { transform: scaleX(0); } 100% { transform: scaleX(1); } } @keyframes colorChange { 0% { background-color: blue; } 50% { background-color: yellow; } 100% { background-color: red; } } </style> 动图效果: 外链预览图:这是我效仿 WikiPedia 中词条链接的效果,用来在鼠标悬停在外部链接上时显示一个预览图,以方便用户更好地预判要不要将其打开。我用了 floating-vue 这个库,结合一个三方的缩略图生成服务实现的。实际体验够不够好还有待观察。 以上就是我近期对自己的网站做的一些小改动,希望能给你一些启发。如果你也有类似的需求,不妨参考一下我的做法。如果你有更好的想法,欢迎在评论区留言。 相关链接: VitePress 文档 Vue Blog 源码 Open Graph Image 生成器 View transitions 介绍 Progress animations 介绍 floating-vue 文档

2023/8/5
articleCard.readMore

写给我的奶奶

(本文写于 2022 年 11 月) 大约一周之前,我突然接到父母亲的消息,说九十多岁高龄的奶奶身体突然变得非常不好,感觉很难坚持了,虽然立刻飞回去不太现实,但还是希望可以安排个远程视频,见一面。 我非常清楚这意味着什么,这一面就是最后一面了。 隔天早上,我们安排了视频见面。我们上一次见面也是视频见面,当时我们还能相互交谈个几句。这一次,奶奶的身体靠在家人身旁勉强可以坐起来,除了睁大眼睛看着屏幕对面的我,已经动弹不得,也说不出话了。我不确定她还能不能听到我说话,总之我努力说了很多。那一刻,自己心里特别难受——一是不愿看到这一幕,二是悔自己没有办法来得及回去亲自看看或身体力行帮着做点什么。 除了伤感,我决定写一点关于我奶奶的东西,让更多人认识和记住她。 关于我奶奶最早的记忆,应该是自己很小还没有上学的时候,我们是个大家庭,从爷爷奶奶到我父母还有几个亲戚家都住得特别近,很多时候我们也直接住在爷爷奶奶家陪他们。白天我爸妈上班的时候,都是爷爷奶奶在家带我。那是一段其乐融融的日子,当时的街坊邻里也都关系很好,经常有不认识的叔叔阿姨来我们家串门,逗我玩送我小礼物什么的。虽然印象已经很模糊了,但我有一个细节一直到现在都记得,就是奶奶每天很早起床给一家人准备早餐。尤其在冬天的时候,天还没亮,奶奶就先起床了。然后我才会被叫醒吃饭,直到我后来上了学都一直这样。那个时候生活很简单,每天早上能饱饱得吃上一顿早餐,感觉一整天都是开心的。 渐渐的,我自己也慢慢长大,一点一点懂事,有了自己的想法和观点。我能回忆起的事情也具体了起来。我记得自己变得叛逆,不想每天生活在长辈们的教唆和监护之下。比如我吃东西的时候不喜欢别人看着我——现在想起来很荒唐不知道为什么——然而我的小孩现在也这样对我说过相同的话。作为家长,希望确定小孩吃完了吃饱了,然后就可以安排你出门上学或什么别的事情,连我自己现在都觉得理所应当,但是当时自己就是赌气不喜欢。有一次我早上上学前一个人吃早饭,我的奶奶为了迁就我的情绪,就坐得远远的,但是又放心不下,就一边远远坐着一边偷偷看我吃,结果被我发现了,我还因为这个发脾气,把气氛搞得非常不愉快;我自己出门上学,那是大概十分钟左右的路途,我奶奶总是在我出门之后站在阳台上看我,直到我走出从阳台能够看到的视野才罢休,我因为不喜欢这样,总是想办法躲开她的视线,每天贴着墙根走,但中间还是总会有一两段路会被她看到,于是我就每天尝试不同的走法,直到找到了一条路奶奶完全从阳台上看不到,然而晚上放学回家之后才听我爸妈说我奶奶就在阳台上看了一整天,一直放心不下不知道是不是我出了什么事。这大概就是我跟我的奶奶童年时期的典型互动模式,有点像猫捉老鼠,但现在回想起来又觉得自己的做法很幼稚。而这个循环似乎又延续到了我跟我的小孩之间。 再往后,我逐渐考到了一个离家比较远的高中,然后是大学,然后工作,跟家人见面的机会一点一点变少了,偶尔过年过节才回去。虽然见面的机会少了,但是每次回家见爷爷奶奶,有件事仍然格外重要,就是吃饭。奶奶总是在知道我要回去看他们的第一天就开始问我喜欢吃什么,一定要做给我吃,然后就开始每天盼我回去。所以每次我回去,总能吃到我最馋的家乡菜,什么莜面栲栳栳、小炒肉拌面、过油肉,到现在回忆起来,自己还是会流口水,怀念那些风味。我奶奶一直觉得不管是上学还是打工,出门在外就是吃得没有家里好,所以做一顿好饭就是对我最好的欢迎仪式。再有就是每次一顿饭下来,只要桌上的饭菜没吃干净,奶奶就会小声念叨说是不是做得不好吃;只要桌上的饭菜吃完了,奶奶又会小声念叨说今天东西做少了不够我们吃。总之永远有操不完的心。那时的我,虽然自己的味蕾得到了极大程度的满足,但还是有些不解风情,甚至有的时候觉得,自己在北京、杭州、还有其他工作和生活过的地方,其实吃的一点也不差。现在想想,老人家可能就是觉得别的也帮不上什么忙,也不一定懂,但是谈到吃的,老人家最自信了,而且确实拿手。也许在我们之间,这是唯一也是最重要的情感联结的纽带。不论念叨什么,也可能就是想找个话茬聊聊天吧,哪怕是尬聊。倒是自己那么当真,较劲。 渐渐的,奶奶年纪越来越大,家里人也不让奶奶亲自下厨了,我每次回家看爷爷奶奶,大家也就基本聊聊天寒轩个几句。奶奶还是忍不住会问吃得好不好——这真是个永恒的话题。再有就是我们每次家庭聚餐的时候,爷爷奶奶都会找我爸或我姑姑之类的亲戚,拿出相机或手机拍几张照片,录个视频什么的。说实话我到现在都没有完全接受这件事,因为人吃饭的时候难免嘴上手上弄得脏兮兮的,表情也难免怪怪的,就更别说我们这代人好多都是拍完照会 P 个半天直到所有人满意为止的。后来爷爷跟我说,现在他们年纪大了就特别怀旧,平时想见见我们没有什么机会,就喜欢看照片。而每次过年过节正是一大家人团聚的时候,就非常想借这个机会把大家的一举一动都尽可能记录下来,等我们各自回去工作上学了,他们可以拿出来看…… 是啊,还有什么比这个更重要的呢。在我们自己每个月用手机拍上百张漂漂亮亮的照片的时候,爷爷奶奶只在乎有没有照片看,有就愿意津津有味得看。 从那以后,我虽然有时家庭聚会吃饭拍照还是有点抗拒,但同时也告诉自己,平时生活在外,要尽量拍些自己日常的生活照发送回去。而且给爷爷奶奶带了个 iPad 回去,这样看最新的照片和视频也方便。 现在回想起我的奶奶,脑海里浮现出来的,永远是她慈祥的笑容和无微不至的关怀。她老人家一生经历了很多我们这一代人无法想象和感同身受的事情,心有永远惦记着的是家庭,还有默默无私的付出。奶奶的想法也很传统,也会跟其他中国式家长一样,在你长大之后问你什么时候找对象,什么时候结婚,什么时候生小孩,要不要再生一个,也会说想让我们生个男的这样的很传统甚至今天大家觉得“政治不正确”的话。我觉得这就是他们这一代人极其朴素、务实而又单纯的一面。奶奶的眼里、心里,一直在盼着这个、盼着那个,照顾这个、照顾那个,我很少见到她谈论她自己,或者说她自己想要个什么东西,想去哪里玩,想过什么样的生活。奶奶自己很舍不得花钱,但是却每年都包大红包给我们晚辈们。她把自己完完全全奉献给了这个大家庭,奉献给了她身边的人,可能也就是除了想多看几张照片,到最后想见大家一面罢了。现在想起来,除了感动,还是感动。 我回忆和记录这些,其实也不只是为了我的奶奶,还包括我的爷爷、已故的姥姥 (外婆)、已故的姥爷 (外公)、爸爸妈妈、丈母娘、老丈人以及其他生活中的长辈们。不论走到哪里,身处何处,他们都是你的家人,都是你生活的一部分。他们有着跟我们不一样的人生经历、不同的想法、甚至跟我们有代沟,但在生活和生命面前,这些都只是插曲,永恒的是那些抹不去的情感记忆和最后对一切的和解。随着时代和科技的进步,我们感受着一些变化,也感受着一些变化。人们的距离被拉进,但也被疏远。每个人在不得不向前走的同时,也值得时不时回头看看吧。奶奶和其他长辈们与我之间这些共同的记忆,会时刻提醒着我,自己来自何方,又该向哪里而去。 如果还有机会,我会由衷的跟奶奶说,我今天吃得特别好,我们全家人都是。 最后,我很喜欢也相信一部动画片《Coco》里的设定:只要一直被人记得,你就在。我会一直记得奶奶,也相信她和我的姥姥、姥爷及其他已故的亲人们一样,一直都还在❤️ 这是我在 B 站上找到的那部电影让我最感动的片段,也把电影里的这首歌送给我的奶奶和所有看到这篇文章的人。 Remember me though I have to say goodbye Remember me though I have to travel far Remember me

2022/11/26
articleCard.readMore

中文格式化小工具 zhlint 及其开发心得

介绍要给小工具给大家:zhlint 这个工具可以帮助你快速格式化中文或中英混排的文本。比如常见的中英文之间要不要用空格、标点符号要用全角字符之类的。 看上去这工具似乎和自己的工作和职业关系不大,但其实也是有一定由来的。 项目的由来 ​ 自己之前参与过一些 W3C 规范的翻译工作,这其中除了需要一定的词汇量、语法知识和表达技巧之外,最主要的部分应该就是格式了。因为大家对诸如空格、标点符号等细节的使用其实不太统一,这在团队协作的时候其实会变成问题,大家都花了一些不必要的时间在格式讨论和校对上。感觉这部分工作比较枯燥且机械,但又不得不做。只能花更多时间在上面。 后来因为接触 Vue.js 的关系。这个项目在早期并没有太多人知道它,而且当时社区普遍比较迷信像 Google 这种大厂官方推出的技术方案,对“野生”项目都不是很有兴趣,所以我希望可以把这个项目介绍给更多人认识。结合我之前的翻译经验,我觉得翻译文档是一个比较好的途径,于是就发起了 Vue 中文文档的翻译,结果没想到这件事一发不可收拾,我就不知不觉从 2014/2015 年做到了今天。随着 Vue 的不断发展,关注文档的人也越来越多,中间发生了很多故事,这些故事也让我自己逐步对翻译和中文格式的细节有了更多的认识。 真正触发我做这个项目的事情,是去年的一个翻译讨论:如何翻译 attribute 和 property。这个问题几乎从我接触技术翻译的第一天起就一直是个噩梦。我和周围的小伙伴尝试了各种译法,都不能让所有人满意,无奈之下通过刻意的区分和强化教育,把它们分别译成“特性”和“属性”。这个状态持续了很长一段时间,Vue 的文档也基本都是这么翻译的。直到去年的一段时间,我逐步意识到,也许这两个词不翻译会更好,索性直接保留英文原词,这样不会有歧义,同时随着整个社区的英文程度在提高,像这样的词不翻译大家应该也能顺畅的理解了,中英文混着读也逐渐可以接受了。所以就在 GitHub 开了个 issue,同时也扩散到了 W3C Web 中文兴趣组。没想到这次讨论大家的意见出奇的一致,几乎“全票通过”。看上去困扰我多年的问题终于要解开了…… 然而在这之后,我意识到,如何对已经翻译好的大量文档做关键词批量替换并不是一件容易的事情——主要还是格式细节太多了。不能做简单粗暴的文本批量替换。 比如把“特性”换回“attribute”之后,如果“特性”一词的两边也都还是中文,那么“attribute”两边就都需要加一个空格,而如果是标点符号就不需要,而如果是英文,那理论上这个空格已经加过了。所以情况很多很复杂。你读到这里可能觉得那我们稍微加个正则表达式也许可以解决,那我会在告诉你,如果这个词的边上还可能有 HTML 标记或 Markdown 标记,那这个正则该如何写呢?或许也不那么容易了。 因此这个译法改动在去年就已经有定论了,但是实际上到今年上半年我才真正改好。原因是我觉得这次我不打算再靠蛮力去解决问题了,而打算通过工具来解决——这就是我做这个项目的由来和动机——没错我陆陆续续做了一年左右,最近终于做出一个比较文档的版本了,然后才完成了这次译法的替换。 另外一个促使我做这个工具的原因其实是我个人希望尝试一些语法分析之类的技术,因为觉得作为一个前端工程师,未来这个方向的可能性和空间比较大。如之前和很多人都聊到过的,现如今的前端框架全部都开始在编辑器这个环节大做文章。因为它可以帮助你突破一些 JS 语言的限制。所以大家武装到牙齿之后这部分是一定会碰的。我预测接下来这个趋势会从框架往上发展,逐步延申到前端工作的更多环节。做这个工具看上去似乎有那么一点可以积累到的知识和经验,于是就想先做个这个试试看。 快速开始 ​ 接下来回到 zhlint 这个工具,介绍一下我设计的基本用法: 基本用法 ​ 在安装 Node 和 Yarn 之后,运行 yarn global add zhlint 或 npm install -g zhlint。这样 zhlint 就安装好了。 在命令行里运行 zhlint,就可以启动这个工具并看到关于它的帮助信息。 如果想真正校验一个文本文件,可以运行 zhlint <filepath>,比如我们创建一个文件叫做 foo.md,其中的文本内容是 中文English。那么运行 zhlint foo.md 会收到一个错误提示,提醒你中英文之间应该有一个空格。 现在我们更进一步,运行 zhlint foo.md --fix,顾名思义这个命令会自动修复文件中的格式错误。所以运行之后文件 foo.md 内部的文本内容会变成 中文 English。 如果现在有一批文件都需要做格式校验,zhlint 还支持批量多文件匹配。比如 zhlint src/*.md 可以校验 src 目录下的所有 md 文件。同理也可以加 --fix 做批量自动修复。 常见格式问题 ​ 空格问题 中文English中文 -> 中文 English 中文 中文 , 中文 -> 中文,中文 1+1=2 -> 1 + 1 = 2 全角/半角问题 中文, 中文. -> 中文,中文。 特殊组合用法问题 Mr. (不转换全角句号) 2020/01/02 01:20:30 (在描述时间和日期的时候冒号和斜杠两边没有空格) 特殊个例问题 33.30KB min+gzip (这里的加号两边不会加空格,该 case 没有普遍规律) 研发过程和心得 ​ 现在回顾之前的研发过程,首先是做得比较懒散,陆陆续续一点一点做,其次是返工了无数次,发现哪里走不通了就推倒重来,所以经历了太长的时间。 第一版 (未完成 + 未发布) ​ 在最初的版本里,我想的比较简单,就只是把中文内容分为几个颗粒度去处理:char、token、full text。所以我当时只做了五件事: 逐个字符分类识别 (全角字符、半角字符、标点符号、空格) 把字符连接成若干个 token 并分类识别 (中文、英文、标点符号) 实现一个基本的 token 遍历函数 利用这个 token 遍历函数指定校验规则并遍历处理 (比如发现一个中文和一个英文的 token 挨着,就强制塞一个空格进去) 把处理过后的 token 再重新连接起来,得出最终的处理结果 大概的代码结构是这样的: js const checkCharType = char => {...} const parse = str => {...} const travel = (tokens, filter, handler) => {...} const lint = (str, options) => { // parse options // travel and process tokens // join tokens } 然后我逐渐发现 lint 这个函数越写越大,逐渐失控。原因有这么几个: 中文里对括号和引号的使用非常灵活,设计之初低估了它的难度和复杂度。比如:我们需要 (先做一件事,然后再做一件事,最后再) 做一件事。括号可以断在任意的地方,可以跨越多个句子,可以包含最前边或最后边的标点符号,也可以把它们留在外边,被截断的前后句子单独拿出来也未必是完整的。 有一些非常特殊的 case 需要绕过,比如括号可以用在英文的单复数变化中 (minute(s))、单引号可以用在英文缩写中 (doesn't) 等等。再比如在描述时间和日期的时候,我们不太习惯在每个数字之间都加空格所以会省略空格 (2020年1月1日 而不是 2020 年 1 月 1 日)。 这些都导致设计之初通过简单的线性 token 机制处理很难做好这件事。“Travel and process” 这部分的代码越来越臃肿。逐渐我意识到,这里需要更多的结构化设计。于是我停下来考虑了一段时间。 第二版:部分重构 ​ 之后我逐渐想到两个主意: 第一版尝试把 token 从线性结构转变成树形结构,但这棵树并不是规范的树,尤其是括号,所以我把括号从树形结构中抽离了出来,改为记号 (mark)。记号不会影响树形结构本身,可以单独识别和处理。这有点类似 HTML 之于 text 的区别,也就是某种“超文本标记”。事后证明 mark 这个结构和思路对后续的功能研发还有很大帮助。 把需要 lint 的格式细节整理成一个一个独立的规则,然后轮流处理,这样庞大的“travel and process tokens”就有机会变成 const rules = [...]; rules.reduce(processRule, str)。这个思路其实我一开始想到过,但觉得把每条规则都抽象并独立出来是很有难度的,所以一直没有下定决心做。经过这次深思熟虑之后我鼓起勇气试了一下,看起来还是可行的,效果也还可以。 于是我决定把之前的主分支退役,重新开启一个新的分支,开始以上述思路重构代码。 重构之后的处理流程更像是: js // separated files const rules = [ (token, index, group, matched, marks) => {...}, (token, index, group, matched, marks) => {...}, (token, index, group, matched, marks) => {...}, // ... ] // index.js const checkCharType = char => {...} const parse = str => {...} const travel = (tokens, filter, handler) => {...} const processRule = ({ tokens, marks, ... }, rule) => {...} const join = (tokens) => {...} const lint = (str, options) => {...} 有了这个结构,我就可以更加专注在格式规则的定义和实现上了。随着工作的深入,我也逐渐加入了一些务实的功能和设计。 支持 Markdown/HTML 格式 ​ 截至目前,我们 lint 的假设性目标都是一个字符串——确切的说是单行字符串。但实际上我们需要处理的真实的文本内容是更复杂的。目前绝大多数待处理的文本内容都是 Markdown 格式,可能还夹带了一些 HTML 标记,而且是多行文本。 为了解决真实的问题,我稍微花了一些时间去了解如何解析 Markdown 语法。之前用到 Markdown 的地方基本都是从 Markdown 渲染出最终的 HTML 代码,但这次我们不太需要最终的 HTML 代码,而是 AST,也就是抽象语法树。最终我找到了一个叫做 unified.js 的库,它可以把各种格式的文本内容解析成为相应格式的 AST。其中 remark.js 就是在这个库的基础上用来解析 Markdown 语法的,其 AST 格式为 mdast。大致的用法如下: js const unified = require('unified') const markdown = require('remark-parse') const frontmatter = require('remark-frontmatter') // the content const content = '...' const ast = unified().use(markdown).use(frontmatter).parse(content) // process the Markdown AST 接下来就是根据 mdast 庖丁解牛的时刻了。经过研究 mdast 的文档,我发现在 Markdown 语法里,所有的语法节点都可以简单粗暴的区分为两大类:inline 和 block。而 zhlint 要处理的其实就是找出所有不能再拆解的 block,然后把其中的 inline 节点在 zhlint 中标注为我们之前提到过的 mark 类 token。当然其中 inline 节点还要再分为两类:一类是包含文本内容的 (例如加粗、斜体、链接等),需要继续 lint 处理;一类不包含 (例如图片),需要原文保留。对于代码片段,我们从自然语言分析的角度认为它不是文本内容,所以也算后者。更妙的是其实在 Markdown 的 parser 里其实是包含了对 HTML 标记的解析的,所以我们不需要额外引入 HTML parser 就可以完成对 HTML 标记的支持。 源代码中大致的语法节点分类如下: js // 不能再拆解的 block const blockTypes = [ 'paragraph', 'heading', 'table-cell' ] // 包含文本的 inline const inlineMarkTypes = [ 'emphasis', 'strong', 'delete', 'footnote', 'link', 'linkReference' ] // 不包含文本的 inline const rawMarkTypes = [ 'inlineCode', 'break', 'image', 'imageReference', 'footnoteReference', 'html' ] 这样我们就可以先把所有的文本中不可拆解的 block 找出来,同时对这些 block 内部出现的超文本做好 mark 标记,然后带着这些 mark 逐个 lint,最后再把这些结果填入之前的 block 所在的位置。大致思路如下: js const blocks = parseMarkdown(str).blocks const blockResults = blocks.map(lintBlock) const result = replaceBlocks(str, blocksResult.map( // 意在强调主要处理的信息是处理后的结果和之前所在字符串中的位置 ({ value, position }) => ({ value, position }) )) 当然要想把 Markdown/HTML 语法处理好这还不算完,因为相应的 lint 规则也变得更加复杂了。举个例子,当我们处理空格的时候,希望空格始终出现在 inline mark 的外侧 (中文 [English](a-link-here) 中文 而不是 中文[ English ](a-link-here)中文)。所以对已有规则处理上的复杂度相当于是指数级增长了一倍。而且实际上到最后还需要特别添加一些针对 Markdown/HTML 语法的规则。这里我其实在过程中反复做了各种尝试和搭配组合,才变成了现在的样子。现在的规则已经相对比较稳定了。同时我也在实现类似的规则过程中逐步积累了很多 util functions。所以拜托了一些低级别的重复性问题之后,整个研发过程越往后其实会变得越清晰越简单。 特殊情况处理 ​ 在设计和实现 lint 规则的过程中,自己也积累了一些心得,总体上所有的 lint 规则或选项被分为了四部分,分别对应四种需求: 基本规则:对空格、标点符号、超文本标记用法的基本定义。这部分规则会抽象成一个 rule。 特殊 case 规则:需要打破上述基本规则,但同时具有一定的领域普遍性,比如时间日期的格式、数学表达式、英文中的单引号缩写等。这部分规则也会抽象成一个 rule,但会在很小范围内做定向分析。 忽略个别情况:针对具体文本的具体特殊片段,采取保留原文格式的措施,比如加号前后通常是需要空格的,但在具体到 min+gzip 的时候,之间没有空格。针对这部分规则我们提供了一种注释语法,可以被 zhlint 识别,从而在 join 的时候跳过。 例如上述例子,我们可以在整个被处理的文本内容的任意地方加入注释 <!-- zhlint ignore: min+gzip -->。 除此之外,我们还支持了更复杂的通配规则,这里主要参考了一个个人觉得非常棒的 W3C 新提案:Scroll to Text Fragment。我们引入了类似的语法 [prefix-,]textStart[,textEnd][,-suffix]。这样用户就可以更灵活的使用这一功能。 Hexo tag plugin:在解析的过程中忽略所有 Hexo tag plugin 语法。这个更特殊一点,实际上是针对 Vue 的中文文档加上的。 因为做这件事情意味着需要多个 parser 逐个调用处理,因此我在之前的 parseMarkdown 机制的基础上加入了 hyper parser chain 的机制,每段文本在真正运行每条 lint 规则之前,都会链式运行所有的 hyper parser。最终包括了 Markdown 解析、Hexo tag plugin 解析、还有被忽略的个别情况的注释解析。 测试 ​ 这次研发过程中,我比较早,也比较严格的实践了测试驱动开发。基于 Jest 写了很多用例,通过这些用例把工具的行为“卡死”,这样当后期引入更多复杂度的时候 (比如决定重构第二版的时候、或决定支持 Markdown 格式之后),可以通过锁住测试用例进行大胆的重构和尝试,并且在重构的时候一旦发现一些之前没有覆盖到的 edge case,就立刻补充进去,然后重构至这个 case 跑通为止再继续。久而久之整套测试用例也越来越见状。总体下来还是受益匪浅的,帮自己省了很多时间和脑细胞——上一次有这种感觉的项目是 Weex JS runtime 第一版。如一些朋友知道的,当时我们只有 2 个月时间,要从零写一个 JS 框架用在双十一移动主会场,所以除了测试用例我当时谁也没法相信。 最终收尾 ​ 完成上述核心功能之后,差不多已经过去一年时间了,最后的一些工作留给了下述这些“外包装”。 支持 CLI 命令 错误报告 打印日志 构建 standalone 版本 发布到 npm 值得一提的是自己在打印日志的时候,想实现类似 TSC 或 Vue 3.0 模板编译的错误打印格式,即打印出错误所在的那一行代码,并且在再下一行的出错位置放一个小尖角字符 (^) 以方便用户定位问题,例如这是 Vue 3.0 模板编译里的效果: 2 | <template key=\\"one\\"></template> 3 | <ul> 4 | <li v-for=\\"foobar\\">hi</li> | ^^^^^^^^^^^^^^ 5 | </ul> 6 | <template key=\\"two\\"></template> 但问题来到 zhlint 之后遇到了一些比较特别的问题: zhlint 处理的文本多半是自然语言,每个段落的字数长短不一,所以大概率在打印的时候会折行 (相对来说代码书写长期的最佳实践是每行少于 80 个字符,所以这个问题并不明显)。设想一下如果被定位的字符在一大段话的正中间,那么再下一行的小尖角字符已经完全失去了辅助定位的作用。 zhlint 处理的多半是中文文本,所以这产生了另外一个问题,中英文混排的时候字符是不等宽的。所以小尖角字符之前的空格数很难算准。 为此我采取了一种不太一样的定位展示效果: 不会打印一整行文本,只会取目标字符前后一段距离的字符串片段 (如前后各 20 个字符),然后在其片段两边加入省略号。 引入日志着色包 chalk,这样就可以为日志上色。 把小尖叫符号同一行之前的空格用相同的字符串片段在此字符之前截断的部分替代,并同时设置背景色和文本色为同一个颜色 (黑色)。 所以最终看到的效果,如果把特别的着色去掉的话,看到的效果是: 自动在中文和English之间加入空格 自动在中文和^ 但实际效果中第二行的“自动在中文和”是看不到的,只会看到一条黑色矩形。运气好的话,如果你的命令行背景也是黑色的,那么就完全看不出差别了。 另外一个测试的时候的小技巧,如果你不希望日志打印把测试报告搞得乱七八糟,可以结合 Jest 的环境变量判断 + 自定义 Console 对象把日志打印到别的流,然后做二次处理或直接抛掉,代码类似: js let stdout = process.stdout let stderr = process.stderr let defaultLogger = console // Jest env if (global.__DEV__) { const fs = require('fs') const { Console } = require('console') stdout = fs.createWriteStream('./stdout.log', { encoding: 'utf-8' }) stderr = fs.createWriteStream('./stderr.log', { encoding: 'utf-8' }) defaultLogger = new Console({ stdout, stderr }) } // usage defaultLogger.log(...) 最后推荐几个我用到的 npm 包,如果大家想做类似的事情,可以做个参考 (当然如果你有更好的推荐也可以): chalk:着色打印日志 glob:批量匹配文件,支持简单的通配符语法,用于批量 lint 文本文件 minimist:自动解析 CLI 的命令参数 zhlint 的使用情况 ​ 目前 zhlint 已经集成到了 Vue 的中文文档项目中。通过简单的 CI 配置,就可以轻松做到为每个 PR 自动 lint 并返回处理结果。 有了这个工具之后,我们就可以比较没有心智负担地批量替换文本了,替换之后运行一遍类似 zhlint src/*.md --fix 的命令,即可把因批量替换产生的格式问题全部修复。 然后,我就立刻完成了对 attribute 和 property 的替换…… 所以“为了批量替换两个单词的译法,我花了差不多一年的时间” (这原本是我设想的这篇文章标题党版本的标题) 之后我们在 Vue 文档中又陆续遇到了讨论 mutation、ref 译法的问题。产生的相应改动也都可以基于 zhlint 很容易的得以实现。 有趣的是,在我在微博和 Twitter 简单分享了这个小工具之后,也有人留言说其实写博客的时候这个工具也非常有用。或许这是 zhlint 可能的更多用途吧😉! 最后的回顾 ​ 最后,关于心得体会和收获,我觉得有这么几个: 首先觉得写 parser 和 linter 是个蛮有趣的事情,有很多不一样的体验,尤其对于前端工程师来说,既熟悉 (处理字符串) 又陌生 (没有 UI,生吃字符串)。未来对 ast 相关的技术会持续自我投资。 测试驱动 FTW。 越来越少有机会真正自己从 0 到 1 做些东西出来了,很珍惜这次机会和类似的工作。 如果发现不对劲,及时停下来或调头。不必也不能太过纠结。在功能成型之前,永远只为最终的理想形态而努力。 要耐得住性子,这次做这个小工具很考验自己的性格。虽然业余时间已经很有限了,但是持之以恒,就是胜利。 另外 zhlint 其实还没有做完,我已经想到了更多的 feature 和改进点,其实也已知了不少不理想不完美不够好的地方。所以会继续做下去。 以上

2020/4/26
articleCard.readMore

vue-mark-display:用 markdown 语法轻松撰写幻灯片

为大家介绍一个刚刚开源,但其实自己已经使用超过 5 年的小工具:vue-mark-display。你可以用它把 markdown 格式的文本转换成幻灯片并在浏览器中播放和控制。 开发背景 ​ 我自己工作中经常需要准备各式各样的幻灯片,所以逐渐觉得用 PowerPoint 或 Keynote 来做幻灯演示略微显得有些笨重。这体现在板式和样式设计、文件大小、打开、编辑和播放的方式等很多方面。加上我从事的就是前端开发的工作,对语义化的信息格式非常敏感,深刻的认为,那些你表面上想编辑的“样式”其实是信息的“类型”+“配套的样式”罢了。所以决定用 markdown 外加自己扩展的一些小功能,来撰写幻灯片,并研发了相应的工具,也就是最近开源的 vue-mark-display。 最早这个工具是用 vue v0.10 写的,当时源代码里还有像 v-attr, v-repeat, v-transition 这样的“古董级”语法,而且还在依赖 Zepto。最近准备开源这个项目的时候,我也基于最新的前端知识和技能进行了重构。所以大家看到的是比较新的版本。 其实在此之前,我也写过很多类似的小工具了,但都没有坚持使用很久,这次开源的 vue-mark-display 我差不多持续使用了 5 年。经历了差不多这 5 年时间,准备过了无数的幻灯片和公开演讲,我想说基于 markdown 以及这些小功能撰写幻灯片真的很酷。如果你也有兴趣试一试用 markdown 为主体来撰写自己的幻灯片,那么不妨了解并体验一下 vue-mark-display。 另外,事实上,如果只是想使用它,你是不需要学习任何关于 Vue 的知识的 —— 文章最后会提供一个不需要 Vue 知识的开箱即用的办法 —— 所以它也对 React、Angular 等社区的同学友好 —— 只要你会写 markdown 和简单的 HTML5 代码,你就可以使用 vue-mark-display 制作出非常精美的幻灯片。 基本语法 ​ 首先,我们在 markdown 语法的基础上做了一个扩展:通过 ---- 分割线把一整篇 markdown 文档划分成为若干张幻灯片。 md # 这里是第一页 以及一些基本的自我介绍 ---- ### 这里是第二页 - 这里有内容 - 这里有内容 - 这里有内容 ---- 没了,讲完了 __谢谢__ 例如上面的例子就会被生成为三张幻灯片。 这样你就可以利用 markdown 支持的 h1 - h6 标题、列表、表格、图片、链接、加粗等格式加分页符用极快的速度写出幻灯片了。 通常情况下,你再为自己的幻灯片设置一套全局的 CSS 样式并固化下来成为你的样式风格,基本就可以拿来演示了。以下是我自己喜欢的样式风格: 自定义样式 ​ 在这个基础上,我们对 markdown 格式做了一点点进一步的扩展 —— 通过在每一页幻灯片开头撰写 html 注释来设置这页幻灯片的特殊样式。 比如我们为第二页幻灯片换一个不一样的背景,同时正文文字颜色变成白色: md # 这里是第一页 以及一些基本的自我介绍 ---- <!-- style: background: #4fc08d; color: white; --> ### 这里是第二页 - 这里有内容 - 这里有内容 - 这里有内容 ---- 没了,讲完了 __谢谢__ 现在翻到第二页看看,背景和文字的颜色就已经改变了 当然在 markdown 中你也可以撰写任意 HTML5 代码,比如嵌入一段 HTML 甚至全局有效的 <style> 标签,都可以被解析: md # 这里是第一页 以及一些基本的自我介绍 <div class="notification">Welcome!</div> ---- <!-- style: background: #4fc08d; color: white; --> ### 这里是第二页 - 这里有内容 - 这里有内容 - 这里有内容 ---- 没了,讲完了 <div class="notification">Thanks!</div> <style> .notification { position: absolute; top: 20px; right: 20px; border-radius: 3px; padding: 0.25em 1em; background-color: yellow; color: #666; } </style> 其它自定义选项 ​ vue-mark-display 还提供了一些方便的 prop 配置项和 event 组件事件,如: @setTitle({ title }) 方便从组件外部获取当前幻灯片的主标题,也就是第一页幻灯片的第一行文字,你可以用这个事件来设置 document.title autoFontSize 根据屏幕大小自动调节默认字号,这个字号是根据自己的演示经验设置的。保证不论屏幕大小,一页幻灯片可以放下的字数是相对稳定的,这样就基本杜绝了因为演示现场屏幕分辨率不一致而导致的适配问题。当然你如果对这个默认的适配结果不满意,也可以自己手动设置组件的 font-size 样式。 supportPreview 可以在点击链接的时候,按住键盘上的 Alt 键,这样链接会从当前屏幕的一个 <iframe> 打开,并悬浮在屏幕上方,同时右上角有个关闭按钮可以将其关掉。如果你希望整个幻灯演示过程不被中途访问链接而打断,相信这个功能你会非常喜欢。 还有一些选项包括:urlHashCtrl 能自动把当前页码和网页的 URL hash 对应、keyboardCtrl 可以支持默认键盘左右键翻页、autoBlankTarget 所有链接都默认在新标签打开、baseUrl 能将链接和图片的相对路径改成你自己的网站等。 同时 vue-mark-display 还可以方面得利用第三方手势库支持触摸屏的左右滑动翻页。 上述比较完整的用例可以看这里: One More Thing ​ vue-mark-display 还支持直接导出成为 PDF 格式文件。因为我们的幻灯片有可能通过各式各样的方式进行传播:有可能是一个链接,也可能是一个文件。所以 vue-mark-display 利用 W3C 的 CSS Page Media 相关规范满足了这一需求,你只需要简单的打开浏览器的打印对话框,然后选择导出成为 PDF,就可以轻松的把自己的幻灯片发到钉钉好友、微信群、邮件附件等各个地方。 最后想说 vue-mark-display 已经开源了 ​ 欢迎大家试用并提出宝贵的意见,如能一同参与建设和维护,我会更加感激。 Homepage: https://jinjiang.github.io/vue-mark-display/ GitHub:https://github.com/jinjiang/vue-mark-display npm: https://www.npmjs.com/package/vue-mark-display 同时我也把自己最近在社区分享过的幻灯片全部整理到了这个地址: https://jinjiang.github.io/slides/ 接下里这个项目还有一些规划中的特性会逐步实现并发布,尽请关注。 最后的最后 ​ 如果你不想学 Vue,希望开箱即用,直接把 markdown 导出成在线幻灯片: 来用 mark2slides 吧,一键导出! https://www.npmjs.com/package/mark2slides 用法: bash npm install --global mark2slides m2s my-slides.md 然后去 dist 目录开启一个 web server: bash cd dist npx serve 打开 http://localhost:5000/,完毕。 不过这个工具非常初期,还请大家多多提意见,接下来我也会逐步为这个工具增加更多贴心的功能。

2019/4/5
articleCard.readMore

我对技术会议的一些看法

如题,今年陆续参加了一些技术会议,有些新的感触,尤其是今年第一次尝试多参加了一些境外的会议,有在 HK 的,有在 TW 的,有在 JP 的,感受很不一样,当然也参加了年初的厦门 CSSConfCN 和刚结束的 VueConfCN。另外网上越来越多人在讨论或是质疑参加技术会议到底有什么意义、好的技术会议应该是什么样子的,我也以写这篇博客的方式参与一下。 会议的意义 ​ 我发现越来越多人讨论说会议里的内容很多都不是独家的,会后还会把幻灯片和视频放网上,那去会场的意义何在?我觉得有这么几点: 沉浸式体验:参考电影,你在家看电影和在电影院看电影效果肯定不一样。而且这次 CSSConfCN 和 VueConfCN 刚好都是在 IMAX 影院办的,这个我觉得不需要多解释了。 细节和时效性:你在会场可以看到分享中的每一个细节,比如分享者对每一张幻灯片中晦涩内容的解释,甚至在讲的过程中的一个表情,一个动作,一个互动,都在传递着很丰富的信息。这不是你会后看幻灯片或看视频能够切身体会的。很多关键的东西都在细节里。而且你如果有任何问题现场就可以趁热打铁问出来并得到回答或讨论。另外时效性其实对于真的技术狂热者来说是很重要的,比如有些人,包括我在内,是习惯于每次苹果发布会都会准时看直播的,而不会接受第二天早上起来再看重播或听比人二次解读,某种角度这是你对技术的态度和信仰问题。这个观点可能有些主观,但我还是愿意把这个观点抛出来,大家可以对号入座看看自己有没有这方面的感觉。再稍微延伸一点,就是我们周围似乎不太有那种欣赏“live”的文化,因为“live”有一种完全真实的魅力。这个世界有太多不真实,我们应该珍惜身边这些为数不多的真实的东西。 社交的好机会:这一方面裕波在上个月月底的 VueConfCN 也谈过。一个技术会议除了会议内容之外同时也是同好聚会的好时机,这是一个很明显的附加价值。你肯定认同,在网上跟人讨论问题和面对面交流的效率和效果是完全不同的,而且很多人线上线下是完全两种性格。所以不管你是对人有兴趣还是对事有兴趣,技术会议都是个好机会。不过我想补充的一点是,相比起会议内容来说,社交机会只是一个附加价值,不然就不能算是技术会议了。我之前参加过的一些会议似乎不是这么理解的,搞得花里胡哨的,内容沦为辅助品。这种会议的社交质量通常也不高。 关于门票和票价 ​ 这个问题我不用复述了,大家应该知道这是个什么问题,以我自己的角度和观察来讲: 我们周围的现状就是大家创造知识和内容很难赚钱,同理你利用这些知识再创造也是很廉价的,用来写书做教程也不赚钱,用来找工作薪水也不高,所以大家都觉得自己没钱,然后没钱就越不愿意为知识和内容付费。所以为知识和内容明码标价和付费是一个好的良性循环。整体上带来的正面的东西大过负面的。技术会议本质上是一种知识传授,至少是交流我觉得。 你可能客观上明白这个道理,但主观上心态上接受不了,或者仍然觉得这些知识不值票价这个钱。我提供两个参考建议:第一,你去看看我这篇文章第一段贴出来的三个会议,打听一下票价都多少钱——我们周围早期的技术会议都不收钱,多半会方是有“投资”或“奉献”心态的,但请你了解每个技术会议背后真正的一般社会必要劳动时间,也就是价值;第二,只要是你有选择的事情,都没有必要站在某种高度去吐槽或谴责,就我而言,有价值的会,我会参加,而且参加过后有收获也愿意分享出来,这足矣。别的会我看都不看一眼,那不关我的事。那些会可能就不是为我准备的,能一直办下来说明有他们的目的和受众,他们开心就好,跟我没关系。希望给你个参考,懂得尊重,更懂得选择。 是给会务方的小建议,你永远无法满足所有人,所以网上有人吐槽这本身不可怕,我觉得重点是 会务方的目标用户满不满意,这比什么都重要 你有没有宣传到你的目标用户 你有没有尽量避免频繁宣传给不是你的目标用户还特别喜欢对别人指指点点的那帮人 (如果他不是你的目标用户,又被过度宣传,这就构成了干扰,他当然有权利发表各种主观抱怨) 吐槽有没有道理,有没有值得反思的地方,就算再难听。吐槽有理+有礼,可以考虑回馈一下,难听的不必 会议质量 ​ 质量是会议的最核心,相信这一点大家都认同,我觉得最重要的三个部分包括:内容质量、会务质量、社交质量。 内容质量 ​ 技术会议的内容我总结绝大多数可归为三大类: 知识分享:客观存在的知识点和方法论 观点分享:分享者个人对某些事情的主观看法 案例分享:分享对某些知识的实践经验 三类分享都有各自的价值和侧重点。有人说其实今天的分享还有第四种就是广告,我一半认同一半不认同,认同的部分是确实有广告,而且有很烦人甚至有很恶心的广告,不认同的部分是它们都是上述三种形式的广告。所以广告本身不是重点,甚至理论上所有的分享都是广义上的“广告”,重点是烦人、恶心、手段拙劣、违和的广告。 所以这个问题的重点来了,如何鉴定一个东西是不是这种狭义的广告?如果这个问题有答案,那么内容质量是不是就可以有进一步的保证? 我觉得这个更多是会议组织者要思考的问题,但答案来自参会者。我有一个建议的判断方式:你判断听众会从这个话题得到什么真正的价值。总体上征集来的话题都可以这样判断一下,而主办方主动联系到的讲师,我建议在沟通好大框架的前提下,尽可能的给予讲师们信任和尊重——至少我个人比较反感主办方安排的什么试讲、审稿之类的环节,这种会除非有什么我不得不参加的理由,否则我一定不参加。 除此之外,有些狭义的广告是会议组织者为了收支平衡有意加进去的,甚至有些会议是明示这个分享是来自赞助商的。说到这里你再想想之前门票和票价那个问题,如果你觉得技术会议没价值,是不是也有你自己的问题?是不是你自己总是图便宜去参加了那些廉价的会才有了这种感受? 会务质量 ​ 会务质量我觉得说白了就是做会场体验吧,可能也包括讲师体验。现场的屏幕、话筒、灯光、座位这些硬件就不多说了,很多体验其实是体现在软件上,也就是一些人性化的细节。再详细的我也说不上来了,但是参加的会议越多,尤其是今年参加的这几个境外的会议,让我越发觉得,这是个很开放的话题,没有最好,只有更好。 我只有一个细节想展开谈一下,就是语言和翻译的问题。众所周知,很多优秀的技术都是来自于国外的,更准确的说是来自全球的。如果一个以中文为主的技术会议有“老外”的存在,那么很多大家习以为常的事情都会变成问题。有些问题很明显,比如参会者可能会听不懂“老外”在讲什么;有些问题不那么明显,比如“老外”听不懂其他讲师在讲什么;还有一些问题可能藏得更深,比如“老外”们需要长途跋涉来开会并且很有可能全天听不懂都别人在讲什么,白白浪费一天时间,但是还要愿意精心准备一个技术分享等等。 我想稍微分享一些参加几个境外会议发现的小细节: HK 的 WebConf Asia 是全英文的,这意味着一种化学反应的发生,就是作为讲师本身来参加这个会也是非常有收获的,他们可以现场听其他几位讲师的分享,相互探讨技术问题,所以更有参与感,更有意愿和动力参加这样的会议,并准备更优质饱满的内容出来。VueFes JP 则是一半英文讲师一半本地日文讲师,分两个会场,英文讲师们可以全程听到英文的分享并且有机会参与互动。 VueFes JP 会非常细心的提前一段时间和讲师确认 slides 的内容,并且做逐片的日文翻译,尽可能的让更多的英语有障碍的同学能够理解和跟上分享的内容。除此之外,小右在早上主讲 keynote 的时候,屏幕上会有实时的日文弹幕对当前的内容进行解释——即便 slides 已经全部是日文翻译过的。我猜测会议组织者可能会根据现场参会者的反应或主讲者的语速来补充一些信息,但同时又不会干扰主讲者的节奏,所以采取这样的措施。而且弹幕不是我们常见的那种很张扬很屌丝的弹幕,只是很低调的在屏幕下方缓缓划过。觉得这样的做法很人性化同时又不失体面。 在 VueFes JP 的英文讲师的问答环节,所有的问题都是提问者直接,或委托主持人,用英文和讲师提问交流,英文讲师会第一时间得到完整的沟通,然后提问者或被委托的主持人谢过讲师之后,再转身用日语复述他的问题以及他们之间完整的对话,以保证在场的所有人都不错过任何信息。这种注重礼仪的细节让我印象深刻。咱们周围的技术会议都怎么做的我就不做对比了,还是那句话,没有最好,只有更好。稍微对礼数有一些敏感的人应该都能明白我在说什么。 VueFes JP 的主办方为英文讲师们安排的热情款待我全程看在眼里。除了为讲师们提前沟通安排好全部的行程,包括会前会后的出行,还精心安排了一些感受日本文化的活动,比如品尝日式烧烤、泡温泉、唱卡拉OK (没错别忘了这可是日本的伟大发明,老外对这项活动超新鲜超喜欢) 等等,并且特意安排英文比较好的同学始终在陪同着。更让我没想到的是安排了一位在日本工作的华人在会场帮助我,可以说是非常用心了。 社交质量 ​ 我个人的经验是:对于一个周六全天的会来说,会议可能只是一白天的,但是社交活动可以是从前一天晚上开始到会议第二天白天才结束的。 首先,大家为了参加白天的会议,不管是不是本地人,绝大多数情况前一天晚上都应该到了。所以可以以各种方式联系一些同仁吃顿夜宵聊一聊,同时也借此机会认识一些“朋友的朋友”。有些会议会组织官方的 pre-party,比如 WebConf Asia 就组织了一个 pre-party 大家都可以自由参加,费用 AA 结算。通常这种 pre-party 都会让大家挨个儿介绍自己相互认识,然后再开吃开聊。这样在第二天的会议之前,你已经和很多人混熟了,开会的时候就不会显得拘束,而且大概率上,你第二天开会会跟前一天聊得比较好的人坐在一起,一边听讲,一边和周围的朋友探讨相关问题。 会议过程中,QA和茶歇的机会都是非常宝贵的交流机会,甚至包括用餐的机会。在 ModernWeb TW 和 VueFes JP 的时候,讲师们是有比较独立的用餐选项的,这个时候可能更多的是讲师们彼此交流,在 WebConf Asia 的时候,大家 pre-party、午餐、after-party 都是和讲师们在一块儿的,所以你有任何问题都可以和讲师们做充分的交流。而 QA 这个环节同样是很宝贵,因为通常这是讲师们刚刚分享完自己的想法,大家都处在趁热打铁的状态。我在 ModernWeb TW 的时候经历了一种不太一样的 QA 体验,就是在分享结束之后,讲师会在公共休息区有一张属于自己的桌子,大家对讲师有任何问题都可以来到这张桌子排队提问。这样的好处是:1 所有的交流都是一对一的,所以交流的质量非常高,并且你如果没有问题要问也可以站在边上旁听,同样可以听到很多内容;2 因为讲师已经离开了讲台,所以你不用担心问题问太多影响第二个分享,基本上每一位讲师都是等大家全都问完为止,然后再回去听别的分享,所以你只要有问题是一定问得到的。 在茶歇的时候,建议大家不要害羞,主动认识更多的人,你可以围观别人的讨论,并且在适当的时机参与进去,提出自己的观点或问题,也可以主动向讲师或一些你已经认识的人抛出一些问题,如果话题足够吸引人的话,一定也会有更多的人参与进来,这样你就有机会认识更多的人,也了解每个人的技术看法和性格。同理 after-party 很多会议也是会安排的,同样是很好的机会,一般也有挨个儿自我介绍的环节。 参加完一天的会还意犹未尽吗?你还可以考虑第二天参加一些大家自发组织的当地一日游,这是一种更加轻松自在的活动,如果你真的发现在会上遇到了一些特别聊得来的人,可以考虑相互约一下。一边旅游一边交朋友一边聊技术聊人生。 大概就是这样。 最后我想鼓励大家多学习英文,多出去走走。

2018/12/7
articleCard.readMore

VueConf Hangzhou 见闻

先说分享一些自己参加 VueConf Hangzhou 的流水账见闻。 Vue 3.0 ​ 这是绕不开的话题。也是来自小右的第一个大会分享。 坦白说这部分内容我之前是有所了解的,也参与过内部讨论和之前的一些宣讲。所以听的时候心态相对比较平静。我个人觉得比较值得关注的地方是: 数据系统升级,支持新的 ES 数据类型,基于 ES Proxy/Reflect 实现,支持 reactive/immutable 两种数据结构 更友好的 virtual-DOM 底层设计 基于 ES Class 的组件定义方式 支持非源码级别的,甚至可以运行时的,定义并替换渲染器:如浏览器、Weex、(将来很可能会有) 小程序等 支持模块级维护和替换:observer, compiler, runtime-core, runtime-web, runtime-server, ... 当然其它贴心功能,包括模板支持 source map、time slicing、hooks、typescript 支持以及性能和包大小方面的进一步改进等,相信也让很多人为之兴奋。这里不一一列举。 另外分享一下我看到和理解的 Vue 3.0 相关计划背后的一些东西: 为更健壮的技术核心服务:Vue 发展到今天,可能大家会有不同的解读,但我体会一个强健的核心是成败的最关键,3.0 在框架核心的健壮性方面做了很多改进,包括全部使用 ts 撰写、分多模块维护等,让更多人可以参与到核心代码工作中来,也让维护成本更低、犯错成本更高。 为建设更健壮的技术生态服务:包括 virtual-DOM 底层改造和非源码级别耦合的自定义渲染器在内的改进,都可以使上层技术生态更加优质和收敛。这也有助于 Vue 开发者之间的相互交流和协作,顺便提一句 Vue 的官方文档现在除了教程和 API 之外,已经加入了更多的包括 style guide、cookbook 这样的部分,也是在为相同的目标而努力。 为新技术时代的到来铺路:Vue 一直选择主动拥抱 ES Class、Proxy/Reflect、Set/Map、TypeScript、Web Components 这样的新技术,一方面在对未来作准备,同时也一定程度引导了开发者对新技术有更深刻的了解和更直观的体会。小程序算不算新技术我觉得还是有争议,但反正渲染器也可以自定义了,未来的想象空间还是很大的。 不出意外的话,接下来 3.0 会经历一个逐步迭代和打磨的过程。在这个过程中,可能还会有一些新的特性或变化产生。但总体上,我预测翻天覆地的变化不太可能出现,新版本的主体思想应该也不会变。我个人理解,近期前端开发的模式,尤其是框架这一层,有逐渐稳定的趋势,大家基本都不约而同的专注在组件系统和数据管理这两个大的部分。技术手段在语言特性本身没有 ES5、ES6 那两波重大革新和突破的前提下,也鲜有质的提升。所以这个时候上层生态的作用和意义会越来越凸显出来。这个时候,严把框架质量关 + 更多为上层生态服务是非常明智的选择。 当然再次强调这是我个人的观点和判断,接受反驳。 其它议题的感受 ​ Vue 声明式交互能力 ​ 这个话题我之前跟分享者 寒老师 也有过私下讨论,当时讨论的内容我甚至认为要比 寒老师 会上分享的还要深入,包括延伸到了 MVVM 设计理念的一些细节,所以 Vue 模板的图灵完备性跟这些相比可能只是冰山一角了。比如我相信今天几乎每个前端工程,不仅限基于 Vue 开发的项目,都有很复杂的数据,也就是 MVVM 里的 Model 或 Flux 里的 Store,需要设计、维护和“定期治理”。我觉得从今天前端工程的角度看,MVVM 描述的有些抽象,和实际的场景和语言特性有距离;而 Flux 实践性更强,但是理论层面没有 MVVM 探讨得那么细致和严谨,所以走到某些特殊的业务逻辑的时候你还是会困惑。在和 寒老师 讨论之后,我有了一些新的思路,打算接下来花精力做一些试验。更具体的内容就要等以后有了什么新的结论再做分享了。 Vue CLI 相关 ​ CLI 应该算是 Vue 最近一年时间里在工具层面最重要的一个发布了。在维护者 胖茶 分享的每一个设计细节中,我感触比较深的,一个是为 vue-loader 和 webpack config 解压,同时避免社区和生态在这个层面过度碎片化;另外 CLI 在项目的技术升级和配置模板等方面下了很多功夫,这是很多开发者有需求但是不敢乱动或越改越花的两个地方;最后想说 韦元悦 的闪电分享有些仓促,但有两个重点大家可能 get 到了:一个是 Vue CLI 的可扩展性是很高的,另一个是 Vue CLI 的 GUI 界面,因为随着前端工程化的发展,一个可视化的项目管理与配置变得越来越有价值了。 顺便提一下,除了 CLI,还有一个工具也值得推荐大家关注,就是 VuePress。而且这两个工具目前都是国人在维护。很赞。 Vue 生态 ​ 下午的 And Design Vue、Electron-Vue、Hippy-Vue 三个分享都是 Vue 社区和生态的部分,分别对应了组件库、桌面应用、移动应用三个大的板块。虽然大家未必都用得到,但我理解这是 Vue 社区和生态广度的一个展现。 另外两个跟 Vue 的招牌特性实践有关:SFC 和 SSR。SFC 实践的分享来自淘宝,这算是淘宝对 Vue 的一个认可吧我觉得,前东家的东西我就不做更多解读和演绎了;SSR 实践的分享我和分享者 天翔 之前也聊过一些,其实这个分享没有很多对 SSR 本身特性的介绍,更多的是探讨在 SSR 业务场景下的数据管理问题,我意识到这个话题其实和 寒老师 的话题存在着某种诡异的联系,因为本质上都是 UI 和数据打交道。同样的今天先不展开了。 最后 CRDT 这个分享的内容坦白说是在我意料之外的,第一是我没想到 ai 哥直接从 PostCSS 跳到了看上去完全不搭嘎的新领域,来以分布式的视角探讨数据交互;第二是我没想到能够学到“从 CS 学科过去几十年的积累中寻求新问题灵感”的这种方法论。它带给我的启发远远大过了 CRDT 本身。 Bonus:Hax 关于 ES 新规范的闪电分享 ​ 因为 Vue 3.0 会提供 ES class 形式的 API,所以与此相关的 ES 规范也会逐渐成为每一个 Vue 开发者关注的话题。关于 Hax 在会上提到的 ES 新规范,我个人比较明确的态度就是我不喜欢用 # 来表示私有成员,比如 this#foo。不喜欢的原因实在太多了我都不知道该从哪条开始说了,而且 Hax 基本都提到过了,我就不复述了,另外你可能会说你不喜欢就给个喜欢的啊不然还废什么话,坦白说我现在也给不出更好的方案,但我觉得如果一个新规范讨论不到够好的话,我宁愿没有这个规范,这不是一个一定要立刻有结论的事情。另外现在可能更大的问题是它眼瞅着要被通过了,我们该怎么办?这可能也是 Hax 想表达的一个很重要的点。如果你有关注规范和语言特性的习惯的话,建议多了解一下这方面的内容。 总体上,我自己觉得这次 VueConf Hangzhou 非常棒! 当然也因为自己有幸参与开场的乐队表演,而且表演了自己参与创作的歌,觉得很荣幸。 本来这篇文章的标题打算定为《VueConf Hangzhou 见闻,以及一些对技术会议的看法》,因为这次 VueConf Hangzhou 不出意外是我今年参加的最后一个技术会议了,不过写到这里发现内容已经不少了,另外也是个人时间的关系,所以打算先写到这里了。对技术会议的看法我会将来再找机会写。

2018/12/5
articleCard.readMore

第四届 CSSConf CN 见闻

上周末作为一名分享者参加了 CSSConf CN,在厦门。 其实除了自己的分享内容,这次我是带着很明确的目的参会的,因为有两个主题我特别关注,就是: 第一个:响应式的组件 第三个:从 API 的角度看组件的 CSS (两个分享的标题都被我稍微“演绎”了一下) 这两件事都是自己工作上正在特别关注的事情,一方面,我们很少从 API 的角度去理解一个组件的 CSS 该如何组织和管理,所以这个标题就特别吸引我,另一方面响应式组件的分享者是来自新加坡的前端工程师 Zell (我个人一直觉得国内的响应式都是在瞎搞,看了很多周围团队都没有认真做这件事,甚至不相信响应式的价值,从设计师到工程师),因此非常珍惜这个机会能近距离学习一些国外的同行们是怎么看待和实践响应式的。 所以尽管我们团队的差旅经费已经用完了,还是决定自费来厦门近距离交流一下。 现在证明这次真的不虚此行。 当然参加这种线下活动,“面基”的目的是一定有的……恩,这个不值一提。 响应式组件 ​ 在谈这次分享内容给我的收获之前,我想说,实际上我不只是从几十分钟的分享中学习了响应式组件的东西。我这次去厦门的行程特地提前了两天,周四就到厦门了,就是希望能多一些分享的准备和现场交流。正好 Zell 也到的比较早,于是乎我在周末的会议之前就跟 Zell 聊了很多。真的是很难得的机会。 回到分享的内容,我听过 Zell 的分享之后简单整理了一些要点: Proportional scaling:让所有的组件成比例 注意字号:font-size 尽量使用 em 单位,而不是如 px 的绝对单位 注意视口/窗口大小 (viewport):尽量使用 vw、vh、vmin、vmax 单位,必要的时候可以配合 calc() Responsive scaling:处理好响应式的断点 善用 media query 尽可能使用 min-width,以小屏幕为基础 必要的时候使用 min-width 和 max-width 的交集,避免样式间不必要的相互干扰 Modular scaling:像乐高一样拼合模块时的注意事项 不要在子元素上直接设置外边距 取而代之的是在父元素上整体设置每个子元素的边距 巧用 + 选择器处理最后一个元素多余的边距 在需要对其的地方使用 rem 作为边距的单位以避免被组件的字号影响布局 Morphable blocks:尽可能透过不同的表象提炼相同结构的组件,以达到尽可能的复用和组件数最小化 找出相同点和不同点 得出最佳的 HTML 结构 通过设置不同的样式展示出不同的效果 让 modifier (BEM 中的 M) 的数量尽可能的少 额外的小贴士:命名规范和约定、避免在选择器上使用 ID 从而使得选择器的权重失控 实际上每一条都不是很难,也不是没有见到过,但是总结的非常有系统性,给了大家一些很好开始的着手点。 另外 Zell 在聊天过程中也提到了很多我非常认同的观点和细节,想分享给大家: 前端工程师是真正用户体验的“最后一道门”,身为前端工程师一定要有意识捍卫用户体验 (这句话后来跟 裕波 聊天的时候他说了一模一样的话,可见大家都十分认同)。 前端工程师一定要跟设计师“坐在一起”工作,一定要强沟通,才能把工作做好。 跟 Zell 聊到响应式在自己的工作中遇到很多困难的时候,Zell 出乎意料的大方对我说“你找我就对了”,即大方又自信。看得出来响应式在 Zell 看来已经是吃饭喝水一样了,我们自己包括周围的人却连一步都还没走出来,真是觉得惭愧。 从 API 的角度看组件的 CSS ​ 我记录了一些要点和自己的理解: 首先,通过 CSS 的发展史来看,我们一直试图通过一种特定的语法 (CSS) 来描述网页的样式,但在 web page -> web app 的划时代趋势下,我们组织代码的方式也发生了变化,尤其是 CSS 的部分容易被人们忽视。 分享者 E0 把今天的 CSS 设计问题,从 API 的角度划分成了三个层次: 对于组件的维护者来说:要把一部分 CSS 样式封装起来,供自己维护,同时也要避免被组件的使用者误修改或滥修改,通常这些样式会被内联或编译为无语义不可编辑的 CSS class 对于组件的使用者来说:要把一部分 CSS 用法暴露出来,供开发者灵活配置或修改,很多组件库会在文档中列出可以配置和修改的 CSS class,但除此之外,还有一些库做了很巧妙的不一样的封装,比如通过扩展的伪类或伪元素。这种方式我之前没有留意到过,有点开眼界 对于浏览器插件作者:这个角度也是我之前没有认真思考过的,就是说还有这样一群开发者,希望通过浏览器插件处理一些不同用户的特定需求。如果我们的组件希望对这些人友好,那么就多了一层考量。这时我们就想到了一些更加通用或规范化的,和具体工具、库、编译方式无关的设计,比如 CSS 自定义属性、无权重的选择器等 听过之后非常受启发。 这两份 slides 我也第一时间分享给了我们团队组件库的同学们了解学习了:) 关于自己分享的 CSS Houdini ​ 这些内容可能真的是有一点“超前”了,因为很多浏览器都还没实现,而且规范本身也没有稳定下来。但是我迫不及待的想分享出来,是因为我也听到了一些说法,说“CSS 很久没有什么新闻了”。那 CSS Houdini 绝对是一个可以让 CSS 更上一层楼的“重量级”的东西。希望可以通过分享 CSS Houdini 让大家对 CSS 更有信心和期待。 同时很多技术的“风向”都是由最底层的东西决定的,我们从规范层面对 CSS 有更多的了解,一定会对我们的实际工作有很多指导和借鉴的价值。如果你同时还是一个有开源精神的人,那么你可以从今天开始就构思一些基于 CSS Houdini 的工具和库了对吧,用这些工具和库加速 CSS Houdini 的落地,同时也尽快把一些之前没有 CSS Houdini 的时候大家用起来很别扭很勉强的东西汰换掉,更在这个过程中体会 web 带给我们的乐趣:) 其它 ​ 另外几个分享也各有特色,总体上,我觉得这次 CSSConf CN 同时包含了 CSS 的规范、理念、工具、技巧、动画、八卦、吐槽各个方面,应该是尽可能照顾到了大家的兴趣和需求了。还是觉得这样的会议非常的棒。 我觉得 CSS 和动画、SVG、字体设计、3D 图形学、可访问性、语义化的 web 等话题有着非常紧密的联系,有很多有意义的延伸,并且这些话题也很难有独立的 Conf 了吧我估计。再加上 CSSConf 的主办者们,尤其是 裕波,是非常懂前端开发者们的,他们经营 CSSConf 的理念和方式我一直非常认同和欣赏,所以也许未来有一天,CSSConf 会比 JSConf 更受人关注。 以上

2018/4/3
articleCard.readMore

[译]Web 表单的未来

译自:https://blog.prototypr.io/the-future-of-web-forms-4578485e1461 Matt West 的 The Future of Web Forms license: CC BY-SA 4.0 如何通过会话式的界面让数据收集更加人性化。 Web 表单是从纸质媒介进化而来的。即设计一组标签和线框来限制输入,同时让数据处理变得跟容易。 毕竟,表单的目的是收集数据,以便执行操作。为了执行该操作,我们需要把收集的数据统一汇总。我们在界面上设计了一些约束以便达到统一汇总的目的。表单旨在符合流程上的需求,而非用户本身。 表单经常给人的感觉是冷冰冰的,没有人情味。因此,我们得到的回应往往也是冷酷和不人性的。我们不深入细节,如果一个朋友问你相同的问题,你可能会多一些回复,但这是一台电脑。他想要的只是数据,别的不在乎。就好像你在跟人说话但是人家并没有在听。为什么没人听的话说出来会让人觉得烦呢? Image by Ken Teegardin. 和许多数字化的东西一样,表单已经被之前的形态严重影响。我们之所以往线框里填东西是因为我们以前在纸上就是这么画的。 我们在纸上主要的输入法是钢笔或铅笔。现在已经不一样了,我们被上百年的约束限制了自己。 技术已经从这些约束中解放了我们。我们已经拥有了创建更人性化的人机交互的工具。 Spike Jonze’s film “Her” provides an interesting prediction for how we might interact with computers in the future. 我们已经很接近在语音识别、自然语言处理和人工智能等方面与人类进行有意义的对话了。甚至我们的工具已经在构建足够优秀的体验了。 所以我们回到表单。我们该如何使用这些工具使得表单更人性化呢? 我们需要摆脱之前对于表单界面的预设。聚焦在通过技术构建一个更佳自然的体验,而不是去除操作层面的约束。 A conversation with Facebooks chat bot “Poncho”. 在过去几年中,我们已经看到了一些新产品致力于通过科技让我们的交互更自然。Siri、Alexa、chat bots 都让我们朝着正确的方向发展,但是我们还没有看到这些创新以某种方式融入到浏览器界面中。 我们有非常多的潜力在更加会话式的 web 界面上,当我们需要收集数据时,我们仍然从一堆输入框和下拉框中构建表单。 有些人在推动这件事。保险服务 Jack 最近发布了一个令人印象深刻的页面来收集保险报价所需的细节。 The “Get a Quote” page from withjack.co.uk 虽然回复依然是被约束的,但是这个收集数据的设计流程已经创造出了更加愉悦和友善的体验。 向用户展示一个标准的 web 表单因此而变得更加容易,但是这样的用户交互更像是一个会话的过程,Jack 已经创造出了更加自然的感受。 Adrian Zumbrunnen’s conversations website azumbrunnen.me Adrian Zumbrunnen 在发布他的会话式的个人网站之后引起了互联网的关注。Adrian 设计了一个界面,通过一些回复选项来引导用户浏览他的 UI/UX 作品。Adrian 的网站巧妙的考虑到了用户如何到达他的网站并以此为信号来理解用户所处的情景。 我们的方向是对的,但感觉还是少了什么。从技术角度看,构建一个真实的会话式界面需要理解用户的意图和语境,而不仅仅是一些回复选项和位置摆放很聪明的文本框。我们需要基于已经做好的 chat bot 且开发出能够让人们用自然语言与其交流的界面。 界面甚至在开始之前就应该知道我们是谁。底层技术已经有现成的了,那就是浏览器的自动填表。你所有的细节都存在同一个地方,对于一个网站来说,一个简单的请求就可以访问。 该界面应该能够适配当前所处的情境。会话是你是从网站的帮助支持页面开始的还是从营销站点的首页开始的?这些信号可以帮助我们理解用户的语境并为其定制适当的系统回复。这些事情 Adrian 的网站并没有做。 让我们体验一下如何把用户注册流程变得更加会话式。 What would the sign up flow look like if we moved beyond forms? 我们今天拥有做这件事相应的数据,但是把所有的东西一起提供出来以创造这样一个自然的体验是真正难的地方。 再考虑牵连到可访问性、隐私、多语言支持、赋予情绪和同理心的设计。如果我们打算通过技术引入一个全新的更有意义的交互设计,这些都是我们今天要去面临和克服的挑战。 没想到我们已经走到这么远了,但是这里仍然有很多事情要做。未来就在不远处的转角,但我们要敢于去做才行。

2017/8/4
articleCard.readMore

[译]苹果正在做一些他们的程序员明摆着不想要的东西

译自: https://medium.com/make-better-software/apple-is-about-to-do-something-their-programmers-definitely-dont-want-fc19f5f4487 Jul 29, 2017 作者 Anil Dash 是 Fog Creek Software 的 CEO,致力于让科技变得更人性和道德一些,同时他也是 Medium 的顾问。 苹果在 Apple Park 这个漂亮的新办公楼上花了 50 亿美金,却犯了一个完全可以避免的极其昂贵的错误:让他们的程序员工作在一个开放式的格局中。这真让人惊讶。 我在 Fog Creek Software 工作,我们的联合创始人兼前 CEO Joel Spolsky 在至少 17 年前就已经针对开放式办公室对于程序员产能的糟糕影响撰文了。他在这方面的洞察基于了 Tom DeMarco 和 Tim Lister 的经典书籍《Peopleware》——该书已经出版了三十年。所以这其实不是什么全新的观点。当然在这数十年里,也已经有无数的学术研究确认了同一个结论:人们在开放式空间办公是烦躁的、注意力不集中的、常常不开心的。 这不是说开放式办公环境一无是处——它能够营造很好的协作和联络的氛围。对于市场或销售团队来说,共享空间是非常有意义的。但是对于需要身处某种工作流状态的任务来说呢?这个问题从科学的角度是有定论的。 那就是把门关上。 保持在工作流中 ​ 现在,如果我们的工作或角色需要特定工作流的时候,那事情就能通过排除一切干扰而获益,编程或许就是这种绝无仅有的最好的例子。而苹果拥有一批这个世界上最顶尖的程序员,所以很显然应该给予他们非常好的环境。 这就是为什么华尔街日报的这篇醒目的文章里会出现这段关于苹果新总部的尤其刺眼的边注: 数以千计的 Apple Park 的雇员都会出现在 Ive 的办公视线内。很多人都将坐在开放空间,而不是以往的小办公室。程序员们都会担心他们的工作氛围太过嘈杂和注意力不集中。…… 通常,公司会以预算为由把程序员安排在开放式办公室。确实让每个程序员都有一个自己的封闭办公室是一笔不小的开销。但是鉴于苹果已经在这个新园区投资了 50 亿美金,且用上了被 iPhone 设计影响了的定制抽水马桶,你很难确信这是因为省钱而做的决定。 取消私人办公室的另一个可能的原因是,也许公司并不知道它的员工们的喜好。但是这个问题是可测试的——我们不带任何倾向性的暗示,来询问一下大家希望办公室是什么样子的,看看大家的回应如何。 你曾经或现在拥有的办公室里的最好的特点是什么? Anil Dash (@anildash) Twitter Link 在数百则回复中,你会发现许多人在谈论他们多么高兴自己有一个,或希望自己有一个可以把门关上的私人办公室。 毫无疑问如果苹果和他们自己的团队交流过后,能够得到相同的回复。所以唯一剩下的可能就是在这个行业里,没有足够的人真正相信程序员值得拥有一个私人办公室。所以我们会继续大声呼吁这件事情。 近距离看看 ​ 来参观一下 Fog Creek 在纽约的总部 Google Map Link 将近二十年前,我记得自己在 Joel 的博客上看到了有关 Fog Creek 新办公室的构造,后来当它完工的时候,我记得看到纽约时报热切的报道它的创新。在那时,我从未想象过我有一天会在那里工作。我看着那篇文章,脑海里幻想着自己在那里的样子,带有一点怀疑的问这些努力是否值得。 现在我得承认,当开始想象自己在 Fog Creek 拥有一间新办公室的时候,我一度被诱惑到了,我想知道通过像其它几乎每一家公司一样使用开放式办公室来省一些钱是否真的很好。这样做是很容易的。 现在 Fog Creek 已经改变了很多——我们公司大约三分之二的工作是远程进行的,尽管几乎所有人都在家中的办公室办公,你猜到了,都是关门的。同时像销售和客服这样的团队从开放式的办公区域中获益。我们可以讨论到所有人的意见一致为止。 不过即便是共享空间的团队也会因为我们在新办公室里提供了电话亭而兴奋,这会方便他们在一个私人空间里打电话。同时我们的技术人员仍然和以前一样拥有超高的工作效率,没有低效的时段,这可以归功于他们有适合他们工作的正确的环境。 最重要的是,专注于创造一个非常棒的工作环境,甚至让我们考虑新的想法,来帮助人们同时拥有两个最好的世界,犹如一辆“安静的汽车”置身于一个大型的会议室。这样的地方能让人们坐在一起但同时仍然可以享受安静和平静,灵感来源于大家最喜欢的 Amtrak amenity。 结语 ​ 如果一家公司想把产品做得像苹果一样成功,我们永远无法给出建议。尽管我们为 Glitch 和 FogBugz 而无比的自豪,我们仍然对苹果近几十年来所做的一切深表敬意。但是我们不希望对这个有机会避免的错误袖手旁观而让其继续下去,因为这是一个苹果 (同时也是每一家自己有程序员的公司!) 不费吹灰之力就可以解决的缺点。 我们很高兴这么多公司知道在他们的雇员身上投资——从医疗保健到最新最棒的计算机硬件的一切东西。但是当他们需要集中精神的时候,也强求每个工作人员都要共享一个开放式的空间时,哪怕是最大最成功的公司也是时候 think different 了吧?

2017/8/1
articleCard.readMore

[译]为什么我不会无偿加班且你也不应该

译自:http://thecodist.com/article/why_i_don_39_t_do_unpaid_overtime_and_neither_should_you 原文写于 2012 年,至今已经有一段时间了,前段时间这篇文章又被大家翻出来热烈讨论,看过之后有些感触,所以翻译了一下 我是一个在美国待了 30 年的程序员,我有过一周工作超过 40 小时的经历,这在行业里面并不常见,但是我很难因此而得到更多的薪水。 总之,我现在发现整个做法很恶心。 我并不是针对自营或创业等多干活儿就能得到更多回报的情况。我曾经在 80 年代中期到 90 年代开过两个小的软件公司,并且工作时间也很长,但是我们会共享全部的成果,而第二家公司我们在合同里就定好了多劳多得的规矩。当然这不是我们今天讨论的重点。 如果我为一家大公司工作并且谈好了薪水,那我的预期就是我在标准的时间内,即公认的 (至少在美国) 一天 8 小时一周 5 天,尽我所能完成工作。如果他们希望我每周工作 70 个小时或有些主管期望团队每天都来上班,现在的我是会拒绝的。为什么呢? 当我们决定工作赚钱的时候,我们假定工作的主要原因是为了换取我们生活所需的开销。雇员的预期是他们会获得等价于这笔薪水的产出。但问题在于,雇主概念中的价值经常和雇员的不一样,尤其在美国和亚洲。许多公司期望薪水是固定的,但是他们创造这些价值需要完成的工作是不确定的。雇主觉得只要提高对雇员的预期和要求,就能够获得更大的回报,这样他们就可以通过为每份薪水延长工作时间来降低实质的劳动成本。 这对于雇员来说意味着什么?如果你同意了,那么你实际上就认同了自己的工作更廉价。甚至这种工作其实就是无偿的。那么作为雇员你在这样的无偿工作中收获了什么呢?在绝大多数雇主面前,你什么也没有得到。如果你是一个主管,也许会得到晋升,但是作为程序员你职业发展的道路不只是做管理这一条。如果你连续几个月每周编码超过 80 个小时,通常情况下得到的回报和一周努力工作 40 小时差不多。 在一些行业里,比如 AAA 游戏工作室,准备发布大型游戏这样的关键时刻的经历都是非常痛苦的。而你看了很多人们玩命工作然后发布没多久就下岗了的故事。当然你是可以选择休息的,但是付出的代价是多少?收益又是多少? 现在想象一下你自己是一个供应商 (我现在就是)。如果你要求在协议之上做更多的工作,那么公司付钱,供应商付出劳动。也许不会有更高的回报但不会比正常情况少。现在你是在为工作获取应有的回报。但奇怪之处在于,显然公司更倾向于根据时间付钱给你而不是你的实际产出,所以他们有的时候不会允许供应商加班。那他们为什么简单的要求雇员无偿工作或自告奋勇呢? 美国工人一般都有 10 天左右的年休假,有的时候还额外有几天病假;但是全职的美国工人评价一年只休息 5~7 天。在世界上很多地方,尤其是欧洲,政府授权 20~30 天年假,人们基本上都会把这些假期用掉。在很多国家加班并不普遍,无偿加班是极少的,甚至是非法的。人们配得上工作之外的生活,对于他们来说,只为雇主埋头工作是极其愚蠢的。而我们在美国 (以及亚洲很多地方) 很少这么思考问题。 我曾经有一个朋友,他的老板希望她的黑莓手机保持 24x7 待命状态。一年以后她拒绝并辞职了。她的老板为此大为恼火。而在那段时间她没有获得任何多余的回报。那我们为什么还这么做呢? 在欧美之间有一个很大的不同,美国的健康保险通常是和你的雇主绑定的,几乎没有其他地方给你实质的保障。如果你失业了,那么你得在有限的时间里支付一大笔 (COBRA) 费用,即便你找到了一份新工作,你的健康保险在 6 个月内也没法生效。所以对失业的恐惧感以及健康保险会让你更倾向于接受更长的无偿工作时间。感觉这个系统设计之初就想阻止你跳来跳去 (尤其是你成家之后)。在欧洲你的健康保险不会和雇主做任何绑定。如果公司想留住一个有价值的员工,他们就得采取一些积极的措施把你留住。很多欧洲国家 (和欧元区) 你很难期待或要求别人无偿加班。 另外一个无偿加班的副作用是更少的人被雇佣。如果你长期让你的雇员每周工作 60~80 小时,你就不需要雇佣更多的人。但是对于雇员来说他们收获了什么呢?基本是没有什么收获的。 我想说的重点是,如果你付钱给我,那么我为你好好工作 40 小时,如果其他人愿意工作 60 或 80 小时,他们就更有价值而我就贬值了?我就应该由于没有把人生的全部都放在工作上而被解雇?那些愿意工作两倍时间的人就真的交付了两倍于我交付的价值吗?你可以反驳如果公司是根据工作时间给员工回报的,那么工作 80 小时的人就得到两倍回报,但这只是从雇主的角度来看。而雇员创造了更多的价值 (为公司带来了更多的收入) 但是没有得到任何更多的回报。当然你可以无视我,找到更多这样的公司,但是我对这种现代的“奴隶制”并不感冒。 工作不能也不应该是一个人的生活的全部,这绝对是欧洲式的思维。生活对我来说也意味着更多。然而在美国有一种非常商业化的观点,就是如果你根据工作时间付钱,那么公司就不会成功;如果人们每年要休假 20 天,那么他们就会失败;一个雇员工作之外的生活一文不值。 我从 Steve Jobs 听到的一个有意思的故事是,在 iPhone 装备的几周前 Steve 要求他们把塑料屏幕换成玻璃的,所以他们通知中国的工厂,那边立刻把上千名工人叫起来,每人发一块饼干一杯茶水,让他们每天连续工作 12 小时,直到 iPhone 装配好。真是一个神奇的故事,但同时也是一个悲伤的故事。他们如此轻易的放弃了生活 (我相信他们还是根据工作时间得到了报酬) 甚至乐在其中,就为了有份工作。而且我从雇主这里听到用这个故事来激励大家做相同的事情——“如果你每周不工作 80 个小时,那么在中国某些人就会顶替你的工作”。而企业会通过这些引起笑声的国家取得成功。 经济是一门复杂的“科学”,我不想为此争辩太多。但是从一个个人工作者的角度看,就是一份付出一分回报。我有技能,公司有需求,我能作为一个有价值的工作者,但是这里是有度的。我不能对你或你的处境说什么,但是对我来说我的工作能力或工作期待是一个有限的范围。可能这是我的德国人的遗传,可能是因为我曾经在我的小公司每周工作 80 个小时的结果,可能我变老了也变聪明了,但是我更愿意享受工作和生活之间的平衡。 当我在 General Dynamics 的第一份工作时,我认识一位年轻的经理,他每周七天连续工作并且每天工作很长时间。有天在一个会上他突然猝死了。你说他没日没夜的工作最后得到了什么呢? 不为别人,也不为我自己。我努力工作,但到点该回家就会回家。你也应该这样。

2017/4/1
articleCard.readMore

寄语应届生:走出校园的几个人生转变

最近收到个邀请,有机会和大学生的在线互动交流,体裁不限。我选了上面这个题目,整理了一些心得感想,虽然活动很快就结束了,但是这些想法我打算还是以文字形式备忘一下。如果能给更多人帮助或参考,我会倍感欣慰。 目标的转变 ​ 作为学生,毫无疑问是以学业为重。在校园里,大家不出意外都会把学习定为最大的目标,围着功课转。但是工作之后,你可能面对很多事情要处理,怎么有所成就?怎么赚钱?怎么照顾好自己?怎么照顾好家庭……除了目标本身不同之外,我觉得更大的不一样在于,在相当长且连续的学生生涯中,我们没有太多选择的余地和必要,即便是有什么目标,也基本上是被动接受或被灌输的。但是走出校园之后,你面对的是一个无比自由开放的社会。这个时候你需要的不是意见,而是主见。甚至你是否做好了准备,有了足够的本事从事一项工作,还是继续学习深造修炼自我,直到自己准备好为止再工作?这也需要你的主见 (并为这个主见承担相应的后果,某种角度上)。最终很多选择是开放的,权衡的,遵从自己内心的结果。 还有一点我想说的是,因为你拥有了新的目标,而且不再会有人逼着你学习,从外部给你学习上的压力,所以客观上学习的环境也没有那么好那么纯粹了,学习这件事情会逐渐变得让你渴望、珍惜、喜欢。希望你还没有因为繁重的学业对新事物新知识,尤其是表面上枯燥但实际上对你有很多帮助的东西,失去动力。我们在校园里更多的是学习知识,然后推导这些知识可以用在什么地方;走出校园之后更多的需要思考,我遇到一个问题想解决它,究竟有多少知识哪些知识可以为我所用?也许在未来的某一天你会突然觉得,当时在校园里,学习环境那么好,怎么没有多看两本书,多做些训练。所以最好不要丢下自己看书学习的习惯,给自己定个长期的学习计划,有空就多看两本书。 学会社交 ​ 坦白地讲我觉得这在校园里并不是必备的技能。但是走出校园之后,它非常非常重要。这也是为什么几乎所有的公司都会给员工做沟通培训 (尽管那并不一定管用)。社交并不只是沟通而已,也不只是表达,更是聆听,是待人接物的每一个细节。我发现很多人在工作中,最简单的两件事情做不好,也不知道该怎么做,那就是:1 如何写邮件、2 如何开会。这表面上是职场礼数的范畴,也有很多文章介绍这些职场礼数,看上去就是个知识点罢了。但实际上一个人在理解和实践它的背后,反映的是修养,甚至是教养。这不是一两篇文章能够教会你的,是你平时为人处事方式的积累和感悟。 另外社交能力的重要性不止体现在工作中,体现在你步入社会之后可能会面对的各种场合的各种人,比如和同事下班之后一起组织些活动放松消遣一下,和老乡或老同学叙叙旧——这些也都是我刚毕业的时候经常会做的事情。但是这里我认为更重要的场景是:学会如何跟陌生人打交道,如何更主动的和陌生人交流,和这个社会交流。从最简单的跟陌生人问路、跟陌生的房东租房子、甚至搭讪对吧 ^_^,到跟不同性格的服务员、乘务员、售票员、公交车司机、出租车司机沟通或寻求帮助,再到你如何主动但又不会让对方和周围人尴尬的帮助别人,包括你是否会本能的路见不平挺身而出……抱歉这可能有点超出社交这个话题了。但这些都是有关联的不是吗?每个人在这个社会中都是一个独立的个体,但又是相互依赖相互依靠的一个集体社会。想真正融入这个社会,我觉得这些都是必须的。 最后,关于社交,还有一点很重要,就是在你刚刚加入一个公司或团队的时候,你周围的人一开始对你来说都是陌生人。这个社会上可能有很多适合你的机会,但它们不会主动找到你头上,这个时候需要你学会跟陌生人打交道。好的社交能力会让你的人生更有安全感,对自己办好一件事更有信心。和别人交流同样是一个完善自我认知的过程,尝试接受更多不同的观点,发现并理解不同的看问题的角度,有助于更认清自己,避免自以为是 (相信我,这种事情别人帮不上忙的,只能你自己领悟)。 个人发展 ​ 我觉得找工作这件事情,更多的要从兴趣出发,而不是所谓的“前途”。我逐渐越来越认同和相信一句老话:“三百六十行,行行出状元”。首先,如果对这件事情没有兴趣,你很难“用心”做好它,这样的状态也不是长久的;第二,我们已经看得见摸得着的“前途”和“机会”,往往已经不是什么好机会了,尤其是如今互联网时代事情变化发展这么快。而且那些真正抓住机会的人往往都是在完全没人看好的时候就开始全身心的投入在这个行业或方向上,才能在“机会”来临的时候抓住它,我不觉得这些人只是更厉害的“投机主义者”,如果没有兴趣在背后趋势着这些人,他们又是怎样才能在枯燥 (往往还伴随着高风险和不确定性) 的领域里这么坚定的做到今天呢?所以我的个人建议是,找工作,就完全追随自己的兴趣和内心就好了,不要想太多“这个行业比较赚钱比较有前途”之类的——它最多是你的一个参考项。找到自己的兴趣所在,相信它,相信自己,保持专注,一定有最好的回报,剩下的东西交给运(时)气(间)就好了。 再有就是要相信专业的力量,对学问保有“敬畏之心”。这里的学问不只是纯职业技术,也包括做事方式方法等等一切社科类的研究。如果一个行业的专业性丢掉了,整个行业也就被毁掉了,最后大家会一起丢掉工作,一起失败。 这种东西我觉得跟很多已经被一份工作或长期不良型的环境所固化思想的“老家伙”们讲已经不一定有意义和效果了。但是对于打算或刚刚从校园步入社会,有理想,有抱负,承载着社会的未来的大学生们来说,我希望有机会在这方面多呼吁一下。尊重和相信专业的力量,耐得住性子,不要被一时的挫折、不走运或委屈左右,多多磨练自己,你一定不会后悔。 So Are You Ready? ​ :)

2017/3/30
articleCard.readMore

[译]如果管理是唯一可走的路,那就完蛋了

译自:https://moz.com/rand/if-management-is-the-only-way-up-were-all-fd/ 注:作者 Rand 是 Moz 的 CEO,文中反复出现两个词:IC (Individual Contributor) 和 PW (People Wrangler),分别翻译成了一线员工和经理人。 Geraldine 很喜欢她曾经在 Cranium 的工作 (西雅图的桌游初创公司,在 Hasbro 收购他们并裁员之前)。她为桌游撰写问题,并为包装盒和营销材料撰写文案。她很擅长这个。但是发生了一些奇怪的事情——他们想让她晋升。我记得她晚上回家后非常的苦恼。她不想让人们向她汇报。她不想在团队中拥有更大的责任。她只想写写东西。 这很奇怪。当我们审视一家公司的结构时,很容易发现团队需要很多高质量的一线员工 (IC) 以及少数高质量的经理人。然而我们的公司文化和这个世界的“模式”已经让我觉得除非你要带人,否则你的影响力、薪水、利益、职位和自我价值都不会增长。 这都什么乱七八糟的。 我过去写过关于多样化成长轨迹的重要性——一线员工和经理人——但是我们最近在 Moz 花了大量的时间碰撞想法,很快会实施一个新的职位/团队的结构,最终付诸实践,我对此充满期待。 现在我会为一个在其工作岗位上做的很优秀的一线员工表达对管理的兴趣而担心。我担心这种渴望的很重要的一部分不源自真正的管理责任感,而是因为他们想要在职业生涯和/或影响力上得到提高,并且认为这是唯一的办法。 我画了这张图来辅助说明两种角色之间的不同: 大图) 一线员工为他们自己及其工作负责。因为他们以一线员工的方式取得了长足的发展,所以他们的影响力变得更加广泛。一个在 Moz 的好的例子就是 Dr. Pete,他判断公司的战略指示并随之协力投入。他通过审查来协助大数据操作,通过战术指导和策略输入来辅助市场,发表如此高质量的博客和指南,甚至从头开始设计整个项目并基于他们的创意执行。他的影响遍及整个公司,横跨多个团队,和他们一同成长。他通过自己的影响力定义了这个角色,这比其它方式都好。 另一方面,优秀的经理人有义务让他们的团队开心、团结、自主,也要负责审查、指导等等。他们发展的越顺利,就越不需要“待在壕沟里”了。很多情况下,他们只会协助定义战略问题。剩下的定义范围、搜索相关答案、实现和执行统统都交给一线员工来做。一个在 Moz 的好的例子就是 Samantha Britney。她长期是一个一线员工,但现如今已经成为了经理人,帮助产品团队的几个一线员工,给予他们工作的自主性,通过工具、资源和协助把事做好,并提供作为一个经理人必要的辅导、一对一、回顾和 HR 工作。她的报告中从不会提及任何细枝末节,但总会驱动他们的项目向前。 基本上,如果你喜欢并且能够把这件事做好,那么你应该做一个一线员工。如果你喜欢 (且擅长) 把自主权交给其他人,帮助他们成长和成功,那么你应该做一个经理人。 这些一线员工和经理人之间有这样一些差别: 随着一线员工们的发展,他们希望和经理人的职责范围有更多的重叠。对经理人来说恰恰相反——随着他们的发展,他们实际干的活越来越少。 资深的一线员工的角色更加灵活——他们能够在各种地方工作,并且由于工作得到认可,收到的会议和活动邀约就越来越多。资深的经理人相反——他们在办公室的时间更为关键,所以很少出差,也经常身居幕后 (很明显 CEO 在这方面是一个例外)。 如果你有很多一线员工却只有很少的经理人,那么你会发现汇报和管理很有挑战。但是如果你有一堆经理人而没几个一线员工,你会面临“厨师太多伙计不够了”的问题 (并且这通常意味着你们的组织和文化已经一团糟了)。 优秀的一线员工有时会发展成为经理人,并因此在新的角色上变得平庸或失败。这绝对是个悲剧。公司不仅失去了一个卓越的一线员工,而且连管理也被搞砸了,因为这会造成大量传染式的问题。而如果一个一线员工表现平平,问题往往不至于这么严重。 待遇是个技巧活儿。在我理想的世界里 (对了我们在 Moz 创建的工资范围是跨发展路线的),一线员工和经理人的级别是大致等价的。假设在某一条路线上有 7 个级别,那么 level 3 的一线员工可以做 level 3 的经理人的事情。最高级别的一线员工应该能够和 CXO 挣得一样多。 我和他人分享这些观点时,大部分情况是直观的。我遇到过的最大问题和一个简单的概念有关——战略战术的所有权。有一天一个 Mozzer 同事和我在这方面的看法就不一致。他觉得在 Moz 的历史上,有些团队的经理人掌握着战略和战术的所有权。个人开发者没有定义他们做什么,怎么做,如何衡量,也没有定义执行过程,他们只是接受命令。 是的这样做也行得通并且这种情况确实发生过。但是我不同意我的同事,这样做相比于,把更大的所有权交给一线员工,让他们决定做什么、何时何地、如何做,让经理人指决定谁来做已经为什么要做,效果不可能一样。诚然,很多初级经理人和一线员工之间会具有更大的内容重叠,而很多高级个人开发者会决定谁来做和为什么要做 (如上所述)。但是我强烈的相信,从长期来看,我们应该走这条路。人们的快乐便在此之上。 当 Daniel Pink 问道“是什么让我们的工作快乐?”时,答案已经很明显 (并且被很多其他学者和不太正式调查者证实): 自治——主导我们自己生活的渴望。 精通——在关键的事情上越做越好的欲望。 目的——渴望做好比我们自己更重要的事情 如果个人开发者无法控制自己的工作内容并且能够掌握工作技能,他们中间优秀的人就会离开,去那些提供这种机会的公司。我们将只留下经理人,而且这会很快。 很奇怪,我是那种一线员工风格的 CEO (也许这并不都是奇怪)。我是一个高级别的一线员工,所以我和经理人有很大的职责重叠,但是我会服务所有的团队、工作和细节。我可能是最直接参与到产品和市场的人,我也经常让这些团队 Mozzer 们像对待资源和工具一样对待我。你让我写篇博客我就会去写,你让我答复一个客户我就会冲上去,你需要聊聊一个项目如何匹配更广泛的目标,以及如何改变你的做法,那就一起聊聊呗。我喜欢我汇报给这些 Moz 员工的感觉——而不是其它方式。我想这件事情永远也不会改变。 p.s. 我很喜欢 Phil Scarr 的这篇文章,它描述了自己从经理人转变为一线员工的经历以及为什么。Carin 从带领着我们的大数据团队,转变为一个产品团队的资深一线员工,我为之感到骄傲。 p.p.s. 如果我的思路不对 (或者对) 而你也有相关的经验,不妨也留言给我。我一定虚心学习——因为我一直在提醒自己,我是第一次当 CEO

2017/3/28
articleCard.readMore

[译]如何撰写 Git 提交信息

译自:https://chris.beams.io/posts/git-commit/ 介绍:为什么好的提交信息非常重要 ​ 如果你浏览任何 Git 仓库的日志,你可能会发现那些提交信息多少有些混乱。比如,看看这些我早年提交给 Spring 的精品: $ git log --oneline -5 --author cbeams --before "Fri Mar 26 2009" e5f4b49 Re-adding ConfigurationPostProcessorTests after its brief removal in r814. @Ignore-ing the testCglibClassesAreLoadedJustInTimeForEnhancement() method as it turns out this was one of the culprits in the recent build breakage. The classloader hacking causes subtle downstream effects, breaking unrelated tests. The test method is still useful, but should only be run on a manual basis to ensure CGLIB is not prematurely classloaded, and should not be run as part of the automated build. 2db0f12 fixed two build-breaking issues: + reverted ClassMetadataReadingVisitor to revision 794 + eliminated ConfigurationPostProcessorTests until further investigation determines why it causes downstream tests to fail (such as the seemingly unrelated ClassPathXmlApplicationContextTests) 147709f Tweaks to package-info.java files 22b25e0 Consolidated Util and MutableAnnotationUtils classes into existing AsmUtils 7f96f57 polishing 呀,比较一下这个仓库最近的提交: $ git log --oneline -5 --author pwebb --before "Sat Aug 30 2014" 5ba3db6 Fix failing CompositePropertySourceTests 84564a0 Rework @PropertySource early parsing logic e142fd1 Add tests for ImportSelector meta-data 887815f Update docbook dependency and generate epub ac8326d Polish mockito usage 你更喜欢读哪个呢? 过去的信息从长度到形式都很多样;最近的信息比较简洁且一致。过去的信息是一般情况下会发生的;最近的信息绝不是偶然发生。 虽然很多仓库的日志看起来像是过去的,但也有例外。Linux 内核和 Git 自身就是伟大的例子。再比如 Spring Boot 或其它由 Tim Pope 管理的仓库。 这些仓库的贡献者知道,对于一个开发同事来说 (其实对未来的自己也是一样),一条用心撰写的 Git 提交信息是用来沟通这则改动最好的上下文。一个 diff 会告诉你什么改变了,但是只有提交信息能正确的告诉你为什么。Peter Hutterer 阐述得非常好: 重建一段代码的上下文是非常费时费力的,这是无法完全避免的。所以我们应该努力尽可能的减少它。提交信息可以帮上这个忙,也正因为此,一个提交信息反应了一名开发者是不是个好的协作者。 如果你对于创建一个伟大的提交信息还没有想过太多,那说明你可能还没有在 git log 及相关的工具上花费太多的时间。这里有一个恶性循环:因为提交历史不成体系且不一致,我们就不会花更多的时间使用和关心它。因为它得不到使用和关注,所以它就一直不成体系且不一致。 但是用心写出来的日志是美丽且实用的。git blame、revert、rebase、log、shortlog 以及其它子命令就是生命的一部分。回顾其他人的提交和 pull requests 变成了值得去做的事情,并且可以快速独立完成。理解最近几个月或几年为什么发生了这些事情不止是可能的并且是高效的。 一个项目的长期成功靠的是其可维护性,以及一个拥有比项目的日志更强大的工具的维护者。这里值得花时间学习一下如何正确的考虑它。一开始可能是个麻烦的东西很快会变成习惯,并且最终变成一切投入的自豪和产能的源泉。 在这篇文章中,我只会致力于保障一个健康的提交历史的最基本要素:如何撰写一份个人提交信息。这里还有其它重要的实践比如压缩提交 (commit squashing) 就不是我在这里想说的。可能会为此再写一篇吧。 大多数编程语言都建立了良好的编码规约,以形成惯用的风格,比如命名、格式化等。当然在这些编码规约中有一些差异,但是大多数开发者赞同取其一并养成习惯好过每个人都选择自己的风格而发生混乱。 一个团队的提交日志方法应该是一致的。为了建立一个有用的修订历史,团队应该首先约定一个提交信息的规约,该规约至少定义以下三方面: **样式。**标记句法、缠绕边距、语法、大小写、标点符号。把这些东西都找出来,去除猜测,把规则定的尽量简单可行。最终的产出将会是不同寻常的一致的日志,不只是乐于阅读,实际上也让阅读变成了一种习惯。 **内容。**提交信息的正文 (body) (如有) 应该包含什么样的信息?不应该包含什么? **元数据。**Issue 追踪 ID、pull request 号等信息如何放进来? 幸运的是,这里有一些已经被良好建立的规约,用来创建惯用的 Git 提交信息。事实上,有些规约中很多都是以某种 Git 命令的方式工作的。不需要你重新发明任何东西。只需遵循下面七大法则,你就可以像专家一样进行提交: 伟大的 Git 提交信息七大法则 ​ 注意:这些 法则 之前 在别的地方 也 提到过。 用一个空行把主题和主题隔离开 把主题行限制在 50 个字符以内 主题行大写开头 主题行不必以句号结尾 在主题行中使用祈使句 正文在 72 个字符处折行 使用正文解释是什么和为什么而不是怎么样 比如: Summarize changes in around 50 characters or less More detailed explanatory text, if necessary. Wrap it to about 72 characters or so. In some contexts, the first line is treated as the subject of the commit and the rest of the text as the body. The blank line separating the summary from the body is critical (unless you omit the body entirely); various tools like `log`, `shortlog` and `rebase` can get confused if you run the two together. Explain the problem that this commit is solving. Focus on why you are making this change as opposed to how (the code explains that). Are there side effects or other unintuitive consequences of this change? Here's the place to explain them. Further paragraphs come after blank lines. - Bullet points are okay, too - Typically a hyphen or asterisk is used for the bullet, preceded by a single space, with blank lines in between, but conventions vary here If you use an issue tracker, put references to them at the bottom, like this: Resolves: #123 See also: #456, #789 1. 用一个空行把主题和正文隔离开 ​ 在 git commit 的 manpage 手册中写到: 虽然不是必须的,但是你最好以一句少于 50 个字符的话简短概括你的改动,然后空一行,再深入描述。提交信息中空行之上的文本会被当作提交的标题,该标题在 Git 中到处都会用到。比如 Git-format-patch(1) 会把一个提交转换为一封电子邮件,它会把这个标题作为邮件的主题,其余的部分会作为邮件的正文。 首先,不是每一次提交都同时需要一个主题和一段正文。有的时候单独一行就可以了,尤其是当改动很简单没有更多必要的上下文的时候。比如: Fix typo in introduction to user guide 无需说更多;如果读者好奇到底修复了什么 typo,她可以通过诸如 git show 或 git diff 或 git log -p 简单看看改动的内容就可以了。 如果你是在命令行中提交,则很容易使用 git commit 的 -m 选项: $ git commit -m"Fix typo in introduction to user guide" 然而,当一个提交值得一些解释和上下文的时候,你需要撰写正文。比如: Derezz the master control program MCP turned out to be evil and had become intent on world domination. This commit throws Tron's disc into MCP (causing its deresolution) and turns it back into a chess game. 带正文的提交信息并不便于通过 -m 选项来撰写。你最好找一个合适的文本编辑器撰写信息。如果你并没有在命令行中为 Git 设置过编辑器,那么请移步阅读 Pro Git 的这个章节。 当你在任何情况下浏览日志的时候,都会觉得把主题从正文中分离出来是值得的。这里有一整段日志: $ git log commit 42e769bdf4894310333942ffc5a15151222a87be Author: Kevin Flynn <kevin@flynnsarcade.com> Date: Fri Jan 01 00:00:00 1982 -0200 Derezz the master control program MCP turned out to be evil and had become intent on world domination. This commit throws Tron's disc into MCP (causing its deresolution) and turns it back into a chess game. 现在运行 git log --oneline,这个命令只会打印主题行: $ git log --oneline 42e769 Derezz the master control program 或者,git shortlog,这个命令会把提交按照用户分组,同样出于简洁的考虑只会打印主题行: $ git shortlog Kevin Flynn (1): Derezz the master control program Alan Bradley (1): Introduce security program "Tron" Ed Dillinger (3): Rename chess program to "MCP" Modify chess program Upgrade chess program Walter Gibbs (1): Introduce protoype chess program 在 Git 里还有一些其它的情况下,会区分主题行和正文——但是如果没有它们中间的空行的话是不会正常工作的。 2. 把主题行限制在 50 个字符以内 ​ 50 个字符并不是一个严格的限制,只是个经验之谈。保持主题行的长度以确保它可读且促使作者考虑一下最简略的表达方式足矣。 提示:如果你做总结很艰难,你可能是一次性提交太多东西了。把原子提交从中剥离出来吧 (每个主题是一个独立的提交)。 GitHub 的 UI 都会提醒这些规约。如果你输入超过 50 个字符的限制,它会警告: 而且会主题行超过 75 个字符的部分会被截断,留下一个省略号: 所以奔着 50 个字符去写,但是 72 个字符是底线。 3. 主题行大写开头 ​ 如题。比如: Accelerate to 88 miles per hour 而不是: accelerate to 88 miles per hour 4. 主题行不必以句号结尾 ​ 主题行结尾的标点符号用法不是必要的。而且,当你打算控制在 50 个字符以内时,连空格都是很宝贵的。比如: Open the pod bay doors 而不是: Open the pod bay doors. 5. 在主题行中使用祈使句 ​ *祈使句*就是指“说起来或写起来像是在发号施令”。举几个例子: Clean your room Close the door Take out the trash 其实这七大法则的每一条读起来都是祈使句的 (“正文在 72 个字符处折行”等)。 祈使句听起来有一点粗鲁;这也是我们为什么不常用它的原因。但是这非常适合写在 Git 提交的主题行中。其中一个的原因就是 Git 本身就是根据你的意志命令式的创建一个提交的。 例如,使用 git merge 的默认信息读起来是这样的: Merge branch 'myfeature' 而用 git revert 的时候是: Revert "Add the thing with the stuff" This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d. 再或者在一个 GitHub pull request 上点击“Merge”按钮时: Merge pull request #123 from someuser/somebranch 所以当你以祈使句撰写你的提交信息时,你遵循了 Git 自己内建的规约。比如: Refactor subsystem X for readability Update getting started documentation Remove deprecated methods Release version 1.0.0 这样撰写一开始会觉得有点怪怪的。我们更多的在说话的时候使用陈述句来陈述事实。这是为什么提交信息经常读起来像: Fixed bug with Y Changing behavior of X 有的时候提交信息写起来像是对于其内容的描述: More fixes for broken stuff Sweet new API methods 为了避免混淆,这里有一个简单原则,可以用在每一个地方。 一个 Git 提交的主题行的准确的格式应该始终完全遵循下面的句式: If applied, this commit will 这里是你的主题行 比如: If applied, this commit will refactor subsystem X for readability If applied, this commit will update getting started documentation If applied, this commit will remove deprecated methods If applied, this commit will release version 1.0.0 If applied, this commit will merge pull request #123 from user/branch 注意非祈使句在这里别扭的地方: If applied, this commit will fixed bug with Y If applied, this commit will changing behavior of X If applied, this commit will more fixes for broken stuff If applied, this commit will sweet new API methods 注意:使用祈使句只在主题行中至关重要。当你撰写正文的时候就可以放下这些限制了。 6. 正文在 72 个字符处折行 ​ Git 不会自动给文本折行。当你为一个提交撰写消息正文的时候,你必须意识到它正确的边距,并且手动折行。 这里推荐在 72 个字符处折行,这样 Git 有足够的空间,即便缩进文本也可以保证所有东西在 80 个字符以内。 一个好的文本编辑器是可以帮上忙的。比如在 Vim 中配置在 Git 提交的 72 个字符处折行非常容易。然而传统的 IDE 在给提交信息文本折行方面提供的智能支持很糟糕 (尽管 IntelliJ IDEA 在最近的版本中终于在这方面做得好一些了)。 7. 使用正文解释是什么和为什么而不是怎么样 ​ 这个来自比特币核心的提交是一个非常好的解释改动是什么和为什么的例子: commit eb0b56b19017ab5c16c745e6da39c53126924ed6 Author: Pieter Wuille <pieter.wuille@gmail.com> Date: Fri Aug 1 22:57:55 2014 +0200 Simplify serialize.h's exception handling Remove the 'state' and 'exceptmask' from serialize.h's stream implementations, as well as related methods. As exceptmask always included 'failbit', and setstate was always called with bits = failbit, all it did was immediately raise an exception. Get rid of those variables, and replace the setstate with direct exception throwing (which also removes some dead code). As a result, good() is never reached after a failure (there are only 2 calls, one of which is in tests), and can just be replaced by !eof(). fail(), clear(n) and exceptions() are just never called. Delete them. 看一眼完整的 diff,想一下作者此时此刻通过提供这样的上下文为同事以及未来的提交者节省了多少时间。如果他不这样做,这些信息可能永远找不回来了。 在很多情况下,你可以忽略这个改动发生时的各种细节。从这个角度看,代码自己会说话 (如果代码很复杂以至于需要长篇大论的解释,那也是代码注释该做的事情)。请首先专注于弄清你产生这个改动的理由——改动前的工作方式,改动后的工作方式 (以及这样做哪里不对),以及为什么你决定以这样的方式解决问题。 你将来某一天维护它的时候也许会感激今天的你! 提示 ​ 学着爱上命令行。远离 IDE。 ​ [和 Git 子命令同样多的原因][For-as-many-reasons-at-there-are-Git-subcommands],拥抱命令行是明智的。Git 是超级强大的;IDE 也一样,但是套路不同。我每天都使用 IDE (IntelliJ IDEA) 也用过很多其它的 (Eclipse),但是我从未见到 IDE 对 Git 的集成能够配得上命令行的易用和强大 (一旦你意识到这一点)。 某些 Git 相关的 IDE 功能是非常宝贵的,比如当你删除一个文件时调用 git rm、当你重命名一个文件时完成相应的 git 命令。但是当你尝试提交、合并、rebase、或通过 IDE 做复杂的历史分析时,事情就分崩离析了。 当你想发挥出 Git 全部的能量的时候,命令行始终是不二之选。 记住不论你是用的是 Bash 还是 Z shell,都有 tab 补全脚本减轻忘记子命令和开关的痛苦。 阅读 Pro Git ​ Pro Git 这本书已经可以免费在线阅读,这本书非常棒。用好它吧!

2017/3/20
articleCard.readMore

[译]C 程序的原则

译自:Principles for C programming 按照 Doug Gwyn 的话说:“Unix 不会阻止你做愚蠢的事情,因为那会同样阻止你做聪明的事情”。C 是一个非常强大的工具,但使用它的时候需要非常小心和自律。学习这些纪律是绝对值得的,因为 C 是所有程序语言中最优秀的。一个自律的 C 程序员将会…… 喜欢可维护性。不要在不必要的地方自作聪明。取而代之的是,找出最简单最易懂的满足需求的方案。诸如性能之类考量是放在第二位的。你应该为你的代码做一个性能预算,并自在的支配它。 随着你对这门语言越来越了解,掌握了越来越多能够从中获益的特性,你也应该学会什么时候不能使用它们。相比用到了很多新奇的方式去解决问题,易于新手理解是更重要的。最好是让一个新手理解你的代码并从中有所收获。像你大概去年就在维护它一样去撰写代码。 避免使用魔法。不要使用宏 (macros)——尽管用它定义常量是没问题的。不要使用 typedef 来隐藏指针或回避撰写“结构”。避免撰写复杂的抽象。保持你的构建系统简单透明。不要因为一个愚蠢的 hacky 的废物解决问题的方式酷炫就使用它。你的代码在行为之下应该是明显的,甚至不需要上下文。 C 最大的优势之一就是透明和简单。这应该被信奉,而不是被颠覆。但是 C 的优良传统是给你足够的空间施展自己,所以你可以为了一些魔术般的目的使用它。但最好还是不要这样,做个麻瓜挺好的。 辨识并回避危险的模式。不要使用固定尺寸的 buffers (有人指出这种说法并不是完全正确。我之前打草稿的时候提到了这些,但还是删掉了)——始终计算你需要分配的空间。阅读你使用的函数的 man 手册并掌握他的成功有出错模式。立刻把不安全的用户输入转换为干净的 C 结构。如果你之后会把这些数据展现给用户,那么尽可能把 C 结构保持到最后。要学会在使用例如 strcat 的敏感函数时多加留意。 撰写 C 有的时候像握着一把枪。枪是很重要的工具,但是和枪有关的事故都是非常糟糕的。你对待枪要非常小心:不要用枪指着任何你喜爱的东西,要有好的用枪纪律,把它当作始终上膛一样谨慎。而就像枪善于拿来打孔一样,C 也善于用来撰写内核。 **用心组织代码。**永远不要把代码写到 header 里。永远不要使用 inline 关键字。把独立的东西分开写成不同的文件。大量使用静态方法组织你的逻辑。用一套编码规范让一切都有足够的空间且易于阅读。当目的显而易见的情况下使用单字符变量名,反之则使用描述性的变量名。 我喜欢把我的代码组织成目录,每个目录实现一组函数,每个函数有属于自己的文件。这些文件通常会包含很多静态函数,但是它们全部用于组织这个文件所要实现的行为。写一个 header 允许这个模块被外部访问。并使用 Linux 内核编码规范,该死。 只使用标准的特性。不要把平台假设为 Linux。不要把编译器假设为 gcc。不要把 libc 假设为 glibc。不要把架构假设为 x86 的。不要把核心工具假设为 GNU。不要定义 _GNU_SOURCE。 如果你一定要使用平台相关的特性,为这样的特性描述一个接口,然后撰写各自平台相关的支持代码。在任何情况下都不要使用 gcc 扩展或 glibc 扩展。GNU 是枯萎的,不要让它传染到你的代码。 使用严谨的工作流。也要有严谨的版本控制方法。撰写提交记录的时候要用心——在第一行简短解释变动,然后在扩展提交记录中加上改变它的理由。在 feature 分支上工作要明确定义目标,不要包含和这个目标不相关的改动。不要害怕在 rebase 时编辑你的分支的历史,它会让你的改动展示得更清晰。 当你稍后不得不回退你的代码时,你将会感激你之前详尽撰写的提交记录。其他人和你的代码互动时也同样会心存感激。当你看到一些愚蠢的代码时,也可以知道这个白痴当时是怎么想的,尤其是当这个白痴是你自己的时候。 严格测试和回顾。找出你的改动可能会经过的代码路径。测试每条路径的行为是正确的。给它不正确的输入。给它“永远不可能发生”的输入。对有错误倾向的模式格外小心。寻找可以简化代码的地方并让过程变得更清晰。 接下来,把你的改动交给另外一个人进行回顾。这个人应该运用相同的程序并签署你的改动。而且回顾要严格,标准始终如一。回顾的时候应该想着,如果由于这些代码出了问题,自己会感到耻辱。 从错误中学习。首先,修复 bug。然后,修复实际的 bug:你的流程允许里这个错误的发生。拉回顾你代码的人讨论——这是你们共同的过错。严格的检查撰写、回顾和部署这些代码的流程,找出根源所在。 解决方案可以简单,比如把 strcat 加入到你的触发“认真回顾”条件反射的函数列表。它可以通过电脑进行静态分析,帮你检测到这个问题。可能这些代码需要重构,这样找出问题变得简单容易。疏于避免未来的错误才是真的大错。 重要的是记住规则就是用来打破的。可能有些情况下,不被鼓励的行为是有用的,被鼓励的行为是应该被忽视的。你应该力争把这些情况当作例外而不是常态,并当它们发生时仔细的证明它们。 C 是狗屎。我爱它,并希望更多的人可以学到我做事的方式。祝好运!

2017/3/19
articleCard.readMore

Vue 2.0 来了!

终于发布了! 原文:https://medium.com/the-vue-point/vue-2-0-is-here-ef1f26acf4b8#.6r9xjmu6x 今天我非常兴奋的官宣 Vue.js 2.0 的发布:Ghost in the Shell。历经 8 个 alpha 版本、8 个 beta 版本和 8 个 rc 版本 (矮油好巧!),Vue.js 2.0 已经为生产环境准备好了!我们的官方教程 vuejs.org/guide 也已经全面更新。 2.0 的工作自今年 4 月启动以来,核心团队为 API 设计、bugfix、文档、类型声明做出了很重要的贡献,社区中的同学们也反馈了很多有价值的 API 建议——在此为每一位参与者致以大大的感谢! 2.0 有哪些新东西 ​ 性能 ​ 基于第三方 benchmark,数值越低越好 2.0 用一个 fork 自 snabbdom 的轻量 Virtual DOM 实现对渲染层进行了重写。在其上层,Vue 的模板编译器能够在编译时做一些智能的优化处理,例如分析并提炼出静态子树以避免界面重绘时不必要的比对。新的渲染层较之 v1 带来了巨大的性能提升,也让 Vue 2.0 成为了最快速的框架之一。除此之外,它把你在优化方面需要做的努力降到了最低,因为 Vue 的响应系统能够在巨大而且复杂的组件树中精准的判断其中需要被重绘的那部分。 还有个值得一提的地方,就是 2.0 的 runtime-only 包大小 min+gzip 过后只有 16kb,即便把 vue-router 和 vuex 都包含进去也只有 26kb,和 v1 核心的包大小相当! 渲染函数 ​ 尽管渲染层全面更新,Vue 2.0 兼容了绝大部分的 1.0 模板语法,仅废弃掉了其中的一小部分。这些模板在背后被编译成了 Virtual DOM 渲染函数,但是如果用户需要更复杂的 JavaScript,也可以选择在其中直接撰写渲染函数。同时我们为喜欢 JSX 的同学提供了支持选项 渲染函数使得这种基于组件的开发模式变得异常强大,并打开了各种可能性——比如现在新的 transition 系统就是完全基于组件的,内部由渲染函数实现。 服务端渲染 ​ Vue 2.0 支持服务端渲染 (SSR),并且是流式的,可以做组件级的缓存,这使得极速渲染成为可能。同时,vue-router 和 vuex 2.0 也都支持了可以通用路由和客户端状态“hydration”的服务端渲染。你可以通过 vue-hackernews-2.0 的 demo app 了解到它们是如何协同工作的。 辅助库 ​ 官方支持的库和工具——vue-router、vuex、vue-loader 和 vueify——都已经升级并支持 2.0 了。vue-cli 现在已经默认生成 2.0 的脚手架了。 特别之处在于,vue-router 和 vuex 在它们的 2.0 版本中都已经有了很多改进: vue-router 支持多命名的 <router-view> 通过 <router-link> 组件改进了导航功能 简化了导航的 hooks API 可定制的滚动行为控制 更多复杂示例 vuex 简化了组件内的用法 通过改进 modules API 提供更好的代码组织方式 可聚合的异步 actions 它们各自的 2.0 文档里有更多的细节: https://router.vuejs.org/ https://vuex.vuejs.org/ 社区项目 ​ 中国最大的在线订餐平台饿了么的团队已经基于 Vue 2.0 构建了一套完整的桌面 UI 组件库。不过还没有英文文档,但是他们正在为此而努力! 很多其他社区的项目也都在为 2.0 做兼容——请移步到 awesome-vue 搜索关键字“2.0”。 从 1.0 迁移 ​ 如果你是一个 Vue 的新同学,现在就可以“无脑”使用 Vue 2.0 了。最大的问题其实是目前 1.0 的用户如何迁移到新的版本。 为了帮助大家完成迁移,团队已经在配合 CLI 迁移辅助工具制作非常详实的迁移教程。这个工具不一定捕获每一处被废弃的东西,但相信能帮你开个好头。 One More Thing…… ​ 中国最大的电商公司阿里巴巴的工程师们已经发起了一个叫做 Weex 的项目,通过 Vue-inspired 语法在移动端渲染 native UI 组件。但是很快,“Vue-inspired” 将会成为 “Vue-powered”——我们已经启动了官方合作,让 Vue 2.0 真正成为 Weex 的 JavaScript 运行时框架。这让用户能够撰写横跨 Web、iOS 和 Android 的通用 Vue 组件!我们的合作才刚刚开始,这将会是 2.0 发布后未来我们专注的重点,请大家拭目以待! Vue 从一个不起眼的 side project 开始如今已经有了长足的发展。今天它已经是社区资助的,被实际广泛认可的,并且根据 stats.js.org 统计在所有 JavaScript 库中增势最强劲的一个。我们相信 2.0 会走得更远。这是 Vue 自启动以来最大的一次更新,我们期待大家用 Vue 创造出更多好产品!

2016/10/1
articleCard.readMore

Weex 近 4 个月的开源之路

本文早些时候发表在 weexteam 的博客 https://github.com/weexteam/article/issues/73 仅从我个人角度跟大家分享一下自己参与 Weex 开源这几个月以来的感受,中间可能会有写观点是偏颇的或者片面的,希望大家指正,另外不论怎样,这些都是我心里真实的想法和感受。 为什么选择开源 ​ 有两个关键字:加速、共赢 我们提出来要开源的时候,在网上被很多人质疑过。有人质疑说这是个“KPI项目”,作者折腾完要“弃坑”了,所以就把它开源了;也有人质疑它的成色,也有人质疑“电商”的标签,是不是只有你们阿里用得到,别人都不太用得到。 我觉得开源最大的意义在于找到志同道合的人做出更伟大的事情,如果我们只是为了“弃坑”,那显然在4个月之前我们的工作就完成了,也不会有接下来的研发迭代、宣传、开发者服务和社区经营——最起码我自己从 Weex 还没有开源甚至还没有这个名字的时候就参与其中,一直参与到现在。我喜欢这个项目,也愿意接受这个项目带给我的各种刺激和挑战,他一直让我不断进步,有满满的收获。 话说回来,我确实看到很多社区的开源项目,自己厂的、友商的、个人的,确实有“弃坑”的意味,感觉源代码丢到 github 就没事了。我觉得这种开源不是没有价值,但价值是约等于 0 的。这段时间关注奥运会,也一下子想起奥林匹克之父顾拜旦老人家的一句名言:“生活的本质不在于索取,而在于奋斗!”我觉得这句话在开源社区更是如此。把代码开源出去然后撒手不管等着别人来捡,这实际上是索取,是没有意义的。关注开源项目的开发者表面上是索取,但是开发者提交的每一个 pull request、每一条 issue、甚至每一句评论和吐槽,也是在为项目做贡献。作为开源项目的参与者或作者,一定要在这方面有一个健康的心态,才能真正做出好的项目。 我还记得自己 2010 年参加 WebRebuild 交流会的时候,蒋定宇 的分享 让我印象深刻,他其中一句话我到今天还记得: “最好的 solution 是讨论出来的” 所以如果想做出优秀的开源项目,除了摆正自己的心态,还要有一颗和别人 (甚至竞争对手和讨厌你的人) 一起共赢的心。尽可能团结一切可以团结的力量。让这个项目变得更好! 筹备过程:从心态到模式的全面转变 ​ 团队内部从去年双十一之后宣布开源计划,到从4月份 QCon 开始邀请开发者陆续参与进来,再到6月底正式全面开源,一共经历了大概了大半年的时间。团队是把 Weex 开源这件事情当做一个工程来认真对待的,这里可以跟大家分享一些我们背后做的准备工作: 例行公事:脱敏、回避公司账号 ​ 这是集团很早就定下来的规矩,我理解这件事情更大程度上是“怕出事”,不要不小心把不该公开的信息公开出去导致集团不必要的商业损失。我觉得这理所当然,同时这只是个最低要求。 当然集团今天对待开源已经不是“怕出事”这么简单了,我自己能够感觉到,集团新成立的开源委员会,除了通过这个流程帮助开发者打消不必要的顾虑之外,更多的希望我们能够通过开源的方式让一件事加速和共赢。这是我参与 Weex 开源过程中明显感受到和之前不一样的地方。 开放的工作环境和工作流程:issues、异步沟通的习惯、熟悉远程沟通的模式 ​ 除了上面的“硬性”准备工作之外,对 Weex 团队更大的挑战在于工作方式的转变。在阿里有句土话,“能电话不邮件”,讲求的是密切沟通、快速响应,阿里的很多团队也是因此具有其他团队不曾想象的做事决心和执行力。在开源社区,面对海量的开发者一起参与,还要做好开发者服务,这种工作方式是不合适的。我们需要大量依赖线上的、异步的、远程的、开放的工作模式。 在团队内部,我们有意识的把所有的工作讨论和任务安排,能公开出来的,就全部公开在 github issue 里。随着团队成员的增多,我们的团队有杭州、北京、广州的,大家分散办公,然后在线上沟通,把不需要当面或同步沟通的工作大方的区分出来。表面上看,异步沟通增加了团队的沟通成本,但实际上,异步沟通让能够参与进来的人变多了,而且不仅限于杭州的某一个办公区或会议室的人,彼此也可以更自在灵活的安排自己的工作和行程。这给了项目组很多想象和发挥的空间。 甚至不只是 Weex 这个项目,我希望集团层面都可以更多的尝试远程协作和异步沟通,这是一种有魔力的体验。 培养兴趣和认同感,把开源当做马拉松,分配好体能,不能一蹴而就 ​ 在筹备期间,我有幸和集团的几位开源的前辈聊到过我们的开源设想,印象最深刻的一个问题就是: “你是否确定,如果有一天你的 KPI 里没有这个项目了,甚至你有一天不在阿里工作了,你会发自内心的去投入和维护它吗?” 我听完觉得前辈把话说到我们心坎儿里了。项目组核心团队里的每一个人,是把 Weex 简单当一份工作,还是发自内心的认同,做出来的东西我相信是完全不一样的。你会全职参与到一个项目里,还是兼职,有的时候兼职的效果更好,更健康。尤其是当我们从长计议的时候,对这几方面更加有感触。我们有大量的已经开源的项目,更新频度是大于半年的。这样的项目出发点都是很好的,但是结果很可惜。 我们在后期组建团队让更多人参与进来的时候特别思考了这个问题,今天在集团内部,Weex 的很多东西都是业务的同学在帮忙打理的。从项目组的角度,工作压力得到了分担和缓解;从个人的角度,在支持业务的同时,能够把一些比较解耦的工作拿来业余时间独自承担,松散的参与一些技术讨论,有自己的收获。这是两全其美的事情。 另外团队的既定工作安排是非饱和的,我们鼓励团员主动寻找值得参与和付出的地方,把项目的方方面面打理好,毕竟项目是完全对外的嘛,要“出去见人”总得把自己“打扮的漂漂亮亮的”。这是每个人都会有的心态。坦白讲这方面我们还不算做得特别好。所以有很多工作要继续做,也有很多空间给到团队。 欢迎把分散的工作内容到不同的团队和个人 ​ 我们主动联系了很多和 Weex 有共同志向或相关联的团队和事业部,大家在不同的角度能够看到更多不同层次的问题,也有各自擅长的领域和空间。我们希望在 Weex 项目组之外,把一个围绕着 Weex 的生态建立起来,他会让 Weex 变得更丰富饱满,更有意义,更有价值。 今天在阿里,Weex 杭州的团队已经只是参与 Weex 的所有人中一小部分了,不同的业务方,不同的技术层次上,都有不同的小伙伴在参与。 团队选择 6-30 正式开源 ​ 经过两个多月的筹备,我们于6月30日晚把项目正式开源了,我们在微博上做了个简单的宣传,但实际上团队当时内部压力是蛮大的,大家都很辛苦,所以我们搞了个小的 party,煞有介事的搬来一个“重大决策按钮”,很有仪式感的让大家一起把这个按钮按下去,把项目开源出来,尽量把这个过程搞得轻松愉悦一点。 开源之前大概就是这样,团队紧接着要面对的,是开源之后的漫长之路。 开源之后:更好的服务开发者 ​ 我总结的开源社区经营就是一个“帽子戏法”的过程,就像开淘宝店是一样的,有三顶帽子你需要轮流得把他带到自己头上: 如果你要开店,那么你首先需要备货,拥有用户满意的商品;然后找入口买流量,让别人看到你的商品;用户发现你的商品之后,你要有很好的承接和售后服务;等到商品卖出去了,用户肯定会给你沟通、评价和建议,这会作为你拥有更好商品的筹码。每个环节之间都是紧密联系的,哪个做得不够平衡都会很痛苦。 做开源项目也是一样,首先你要做出好的技术产品;然后通过各种技术宣讲机会介绍给别人;当开发者来到项目的首页或 github 仓库时,要做好服务,帮助开发者解答参与过程中的疑惑;然后在这个过程中收集到用户的反馈和意见再做产品的迭代改进。 利用 QCon 等机会推广宣传 ​ 包括4月份我们参与的 QCon 北京在内,团队先后参加了大大小小的很多场技术交流分享活动,同时在线上我们也在陆续写一些介绍 Weex 的技术文章,在宣传 Weex 的技术设想和理念的同时,也鼓励开发者更多的参与进来。 解答问题时遇到的问题 ​ 现在回想起来,最早接触开发者的时候,团队对自己还是太过自信了,心想我们一起研发并且准备了这么久,开发者过来看过一定觉得很厉害。没想到遇到了大家的各种挑战。而且被问到最多的问题是完全没有想到的: “怎么让程序跑起来?” 然后就发现了一堆问题:比如 Windows 环境下的命令行问题、路径分隔符问题、Node 版本问题、Android 环境翻墙的问题、npm/cocoapods 镜像的问题、NDK 的问题、x86 模拟器的问题等等…… 这里面有些是团队自己知道的,只是觉得太顺理成章了,没觉得应该写清楚,结果就让开发者们误解了;有些确实是自己的工作环境很单一,而社区里开发者们的工作环境是千差万别的;还有些是交代得不够清楚,明明知道也写了,但是没能让开发者很好的充分理解。 后来我才留意到技术社区里一个流传很久的笑话: “所有的开源软件都有一个特点:根据官方文档的步骤是跑不起来的。” 原来 Landing Page、README 和 文档这么重要,这给了团队当头一棒,大家认为最简单的问题都折腾得很狼狈。看起来搞开源真的“不是你一片赤诚就能够面对的” 经历了从 issue 邮件爆仓到 QQ 群再到 gitter 的过程 ​ 最早期我们和开发者所有的沟通基本都是通过 github issues 来进行的,这也蛮正常的,看人家开源项目都在 issues 上讨论的火热,好有气氛好羡慕,巴不得有人在我们自己的 issues 上多聊个两句,哪怕是闲聊,总比冷冷清清无人问津的好。 后来发现完全不是我们想象的那样,我们真的是想多了,实际情况是各种 issue 洪水猛兽版袭来,大家的 github 账号默认都是可以收到每个 issue 的邮件提醒的,然后瞬间邮箱就被炸瘫痪了,正常的研发迭代也被应付这些 issue 变得支离破碎。 后来我们发现其实 issue 其实并不都适合处理所有的问题,有些使用上的小问题,在 issue 上几个来回讨论清楚,一个小时甚至一上午就这样过去了。而且问题多了之后,把 issues 上正常的工作内容讨论和安排都给淹没了。 这个时候就有非常热心的开发者帮我们建立了 QQ 群、微信群等社区,这种沟通方式更直接简单,回合更快,开发者遇到一个编译不通过的问题,问题抛出来在线等个几分钟就有人帮忙回应了。这样 github issues 的压力暂时得到了缓解。 后来经过几个同学的调研,我们最终把疑难杂症的解答和及时的线上讨论放到了 gitter 上,把需要跟进的事项、发现的 bug、值得追踪探讨的话题留在了 github issues 里。这样差不多是今天团队和社区开发者们协作的最终方式了。 根据开发者参与程度分场景提供不一样的支持 ​ 我们根据开发者的参与度划分了几个维度:随便看看、试一试、用起来、交流互动、参与贡献。背后的需求和服务方式应该是不一样的 如果开发者只是想先来了解个概念,我们要做的就是做个漂漂亮亮的欢迎页、还有 README,把主要功能、特点和适用范围交代清楚 如果开发者看过之后觉得有兴趣尝试一下,我们要准备的是入门教程、预览工具、代码示例和 cli 入口集成工具 如果开发者尝试过之后觉得不错,准备在实际工作中使用 Weex,那么我们要提供的东西就更复杂,包括详实的参考文档、丰富的工程示例、必备的所有工程工具集、还有常见问题的整理等等,更重要的是,要有稳定的版本。 如果开发者自己用过之后,还乐于和众多其他 Weex 的开发者交流互动,我们要提供的就是 github、gitter 这样的平台,方便大家一起管理事物、及时解答和探讨各种问题,必要的情况下,我们会主动和我们的用户建立联系,了解更多大家平时不一定愿意主动说出来的观点和细节。 最后,对于有能力和意愿为 Weex 做出更大贡献的开发者,我们需要提供更完整和演进的开发规范、技术约定和质量保障机制,让大家更低成本的参与贡献,同时还可以保障基本的代码质量和工程质量。 文章和讨论逐步沉淀 ​ 随着 Weex 社区参与者的增多,我们也不需要鼓励大家有事没事写个 issue 打肿脸充胖子了,而是比较自然而合理的做各种事情。我们看到两个很好的势头: 一个是开发者在 issues 里参与了很多基于 proposal 的新功能讨论,之前团队在迭代新功能的时候是自行设计排期研发实现的,逐渐的,我们把整个技术设计的过程也透明出来并且有意识的在这个阶段放慢节奏,让这个功能经过足够充分的讨论之后,再付诸实现; 另一个是很多开发者开始在集团内网和 github articles 下写了越来越多对 Weex 的理解和相关讨论,很多文章团队自己看完都私下表示开发者们写得比我自己写得都好 [偷笑],这也让团队的每一个人更受鼓舞,也更愿意跟社区分享自己的想法和真知灼见。 这两方面不论哪一方面,对 Weex 社区来说都是很好的迹象,也都一定程度鼓舞了 Weex 团队本身做得更好! 有秩序分版本迭代和 release,因为有了上述的影响,最近的迭代也加快了节奏 ​ 就像开源之后第一段提到的,Weex 团队除了宣传和服务开发者之外,还在保持有条不紊的版本迭代。去年 Weex 初期启动的时候,是一个7人左右的团队,通过不那么标准的 Scrum 的敏捷方式快速迭代。双十一过后,团队的规模扩大了,同时也有了非常专业的项目经理为团队保驾护航。我们基本保持着每个月一次迭代,每两个迭代发布一个版本的节奏,所以我们于5月份发布了0.5版本,7月份发布了0.6版本。 从0.7版本开始,随着团队默契度的提升,再加上整个社区逐步成型,也通过 proposal 讨论等机会给了团队很多回馈,我们加快了迭代频率,现在每个月都会完成一个新的版本,所以本月初我们发布了0.7版本。目前0.8版本也已经启动,正在紧锣密鼓的迭代过程中。 同时今年的双十一也要邻近了,团队针对今年双十一提出了更高的目标,具体内容这里不详细提及了,先卖个关子,请大家拭目以待。 未来的努力方向 ​ 今天,Weex 在近4个月的开源之路后,累计了5000+个star,并且保持着比较高的迭代速度和社区活跃度。我们在欣喜的同时,更多的是感恩,觉得自己应该对得起大家的这份关注和信任,继续做出更好的产品给大家。除了之前提到的各方面细节和感触,将来我们还有很多地方值得改进 首先是把文档和网站做得更好,经过项目初期的摸爬滚打之后,我们对文档和网站本身也有了新的认识,同时我们有了更多的精力在这方面,所以未来我们会重新梳理我们的文档和网站,希望以一个更好的面膜提供给广大开发者 促进交流:除了 github issues 和 gitter,我们希望随着开发者诉求和参与程度的增加不断引入效果更好效率更高的交流和协作方式,比如 Playground 网站、Marketplace 之类的设想目前已经提上了日程 更透明:除了 proposal 的讨论透明化之外,我们会把整个团队的 Roadmap 也透明化,同时拿出更加民主的决策机制,让所有的开发者一同参与核心团队的决策和迭代计划 我们希望毫无保留的把我们的心得经验教训全部分享给希望在开源社区有所作为的朋友们,带动更多的开源实践,不论是阿里内部的,还是整个开源社区范围内的 最后的展望 ​ 借近期参加开源中国源创汇和JSConf的活动,也给了我一个机会从开源经历的角度重新审视了一些自己和团队做的事情。同时 Weex 在 github 的 star 也即将迈过 6000 大关,有一些感触,分享给大家。未来我们会继续努力,用自己的实际行动。

2016/9/5
articleCard.readMore

Weex 在 JS Runtime 内的多实例管理

本文早些时候发表在 weexteam 的博客 https://github.com/weexteam/article/issues/71 Weex 的技术架构和传统的客户端渲染机制相比有一个显著的差别,就是引入了 JavaScript,通过 JS Runtime 完成一些动态性的运算,再把运算结果和外界进行通信,完成界面渲染等相关操作指令。而客户端面对多个甚至可能同时共存的 Weex 页面时,并没有为每个 Weex 页面提供各自独立的 JS Runtime,相反我们只有一个 JS Runtime,这意味着所有的 Weex 页面共享同一份 JS Runtime,共用全局环境、变量、内存、和外界通信的接口等等。这篇文章会循序渐进的介绍 Weex JS Runtime 这部分的内容,大概的章节设计是这样的: 为什么需要多实例 多实例管理面临的挑战 解决问题的思路 几个特殊处理的地方 总结 为什么在 JS Runtime 内部手动管理多实例? ​ 如果只用一个词来回答,那就是“性能” 如果要用一段话来回答:手机上的资源是很宝贵的,包括CPU、内存、电量等等,而 Weex 团队从设计初期就决定以页面为单位对产品实现进行划分,一个完整的应用是多个相互独立解耦的页面通过一定的路由规则和链接跳转互联起来组合而成。所以为每个页面都单独提供一份 JS Runtime 代价还是比较昂贵的,这会引起大量的资源开销,手机发烫,反应迟钝,甚至应用或操作系统的崩溃。尤其是在国内一些中低端机型上面,反应尤其明显。 从另外一个角度讲,我们通过同一个 JS Runtime,可以更直接方便的做一些运行时的资源共享,比如 JS Framework 的初始化过程,只需要应用启动的时候执行一次就可以了,不必每个页面被打开的时候才进行。目前 JS Framework 的启动过程一般会在几百毫秒不等,相当于每个页面打开的时候,这几百毫秒都被节省下来了。 多实例管理的 JS Runtime 需要额外关注哪些问题? ​ 首先不同的 Weex 页面肯定需要执行各自的 JavaScript 运算,完成各自的 native 指令收发。所以如何避免多个 Weex 页面在同一个 JS Runtime 里相互“打架”就变得至关重要。 这里的“打架”有以下几个细节: 数据和状态的记录,能够正确的完成并且不会被其它页面的运算所干扰或截获 和 native 之间的收发指令或通信,能够准确的调度不同的 native 端页面 对系统资源的利用,遇到大运算量的页面时,其它页面有机会快速得到响应 除了“打架”的问题之外,传统 HTML5 页面里,每个 JS Runtime 的生命周期是对应页面本身的生命周期的,相对是个短效的实例,而且一旦页面被关闭,对应这个页面的 JS Runtime 就可以大方的 kill 掉,没有任何后顾之忧;而 Weex 的 JS Runtime 需要在应用被开启之后至始至终存在并不间断工作,所以长期运转的内存管理也变成了一个不得不正视的问题。 Weex 解决上述问题的过程 ​ 首先,我们会为每个新打开的 Weex 页面创建一个唯一的 instance id 其次,JS Runtime 里所有的 native 通信接口,不管是发送还是接收,全部需要传递 instance id 作为第一个参数,这样 JS Runtime 和 native 端都可以快速准确的识别并分发给每个 Weex 页面,比如: createInstance(id, code, config, data):创建一个新的 Weex 页面,通过一整段 Weex JS Bundle 的代码,在 JS Runtime 开辟一块新的空间用来存储、记录和运算 sendTasks(id, tasks):从 JS Runtime 发送指令到 native 端 receiveTasks(id, tasks):从 native 端发送指令到 JS Runtime 然后,我们根据不同的 instance id 在 JS Runtime 里进行独立的运算和数据、状态记录。这里我们通过 JavaScript 里的闭包原理把不同实例的运算和数据状态管理隔离在了不同的闭包里,达到相互不“打架”的目的。 初级形态 ​ 形如: javascript // old version of Weex JS Runtime function createInstance(id, code) { const customComponents = {} function define(name, definition) { // todo: register a weex component in this Weex instance ... customComponents[name] = definition ... } function bootstrap(name) { // todo: start to render this Weex instance from a certain named component ... sendTasks(id, [...]) ... } // run eval(code) } 我们在闭包中设置了这么几个东西,保障隔离效果: define: 用来自定义一个复合组件 bootstrap: 用来以某个复合组件为根结点渲染页面 这样的话,假设有一个 Weex 页面,它的代码是这样的: js // 伪代码,并不能实际运行 // A Weex JS Bundle File // define a component named `foo` define('foo', { type: 'div', children: [ { type: 'text', attr: { value: 'Hello World' }} ] }) // render the page with `foo` component bootstrap('foo') 那么 Weex 页面里的 define 和 bootstrap 表面上是全局方法,实际上只会针对当前的 Weex instance 在一个更小的作用域下执行,而不会干扰或污染全局环境或其它 Weex 页面。 这是我们最初的版本的形态。 配合更开放的前端包管理工具 ​ 随着 Weex JS Framework 代码的不断演进,功能也逐渐丰富起来,上层的 Weex 页面也写得越来越复杂,之前简单的 define + bootstrap 已经满足不了工程上的需求和设想了。这个时候我们需要引入前端资源包管理的概念,而且拥抱现有的各种成熟的包管理规范和工具。这其中包括 AMD、CMD、CommonJS、ES6 Modules 等等。这个时候 define 和 bootstrap 这两个名字就显得起得有点太大了,尤其是 define,和 AMD 里的语法重叠,所以和很多兼容 AMD 语法的打包工具都会产生冲突。所以我们逐步把这些方法转变成了带有 Weex 特殊前缀的方法: __weex_define__: define 的别名,用来自定义一个复合组件 __weex_bootstrap__: bootstrap 的别名,用来以某个复合组件为根结点渲染页面 同时我们可以借助各种打包工具把 Weex 页面拆成多个文件开发和维护,然后打包成一个文件完成发布和运行,以 webpack 为例,上述的例子会打包生成类似: js // 伪代码,并不能实际运行 /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports) { __weex_define__('foo', { type: 'div', children: [ { type: 'text', attr: { value: 'Hello World' }} ] }) __weex_bootstrap__('foo') /***/ } /******/ ]); 从 eval 到 new Function ​ 之后我们在最终执行 Weex JS Bundle 代码时,从略显简陋的 eval 命令改写成了 new Function,即: js // old version function define() {...} function bootstrap() {...} eval(code) // new version import { aaa, bbb } from 'xxx' // place and name your methods as you like const fn = new Function('define', 'bootstrap', code) fn(aaa, bbb) 用 new Function 的前几个参数定义了即将执行的 Weex JS Bundle 中“伪装”的几个全局变量或全局方法,然后运行的时候把那些背后的“伪装”传递进去,形式上更灵活,运行时更安全。 同时也是因为闭包中需要准备的变量和方法也逐渐多起来了,new Function 的写法更便于清晰的管理和对应这些内容。 性能优化 ​ 可能很多同学注意到了,不论是 eval 还是 new Function 其实效率都是不高的,为什么还要这样用呢?主要的原因还是因为我们需要动态的为每个 Weex 页面创造这样的闭包。后来在 native 端我们还想到了一些变通的优化办法,即在 native 端将 Weex JS Bundle 代码包装在一个闭包里,再丢给 JavaScript 去执行。所以,如果一个 Weex JS Bundle 大代码如下: js // 伪代码,并不能实际运行 __weex_define__('foo', { type: 'div', children: [ { type: 'text', attr: { value: 'Hello World' }} ] }) __weex_bootstrap__('foo') 而客户端现在要基于这个 Weex JS Bundle 创建一个页面,instance id 为 x,那么客户端会先为这段代码加上特殊的头和尾: js // 伪代码,并不能实际运行 // 特殊的头部代码 (function (global) { const env = global.prepareInstance('x') (function (__weex_define__, __weex_bootstrap__) { // 特殊的头部代码 __weex_define__('foo', { type: 'div', children: [ { type: 'text', attr: { value: 'Hello World' }} ] }) __weex_bootstrap__('foo') // 特殊的尾部代码 })(env.define, env.bootstrap) })(this) // 特殊的尾部代码 这样的话我们先通过 prepareInstance('x') 创建一个属于 x 这个 id 的方法,然后通过 function (__weex_define__, __weex_bootstrap__) 创造一个闭包,把 JS Bundle 的源代码放进去,效果和之前的实现是等价的,但是由于没有用到 eval 和 new Function,性能有了一定的提升,在我们实验室数据中,JavaScript 运算的时间缩短了 10%~25%。 当然,由于在浏览器的环境下,我们没有机会在执行 JavaScript 之前对内容进行高性能的处理,所以 HTML5 Renderer 还没有办法通过这样的改造提升执行效率,在这方面我们还会继续探索。 其它多实例管理接口设计 ​ 包括上述提到的 createInstance() 接口在内,JS Runtime 还提供了以下几个和 native 通信的方式: 首先是 native 配置的导入: registerComponents(components) registerModules(modules) 这两个 API 用来让 JS Runtime 知道当前 Weex 支持哪些原生组件和原生的功能模块,及其相关的细节。 其次是实例的生命周期管理: createInstance(id, code, ...) refreshInstance(id, data) destroyInstance(id) 这三个 API 用来让 JS Runtime 知道每一个页面的创建和销毁的时机,特别的,我们还提供了一个 refreshInstance 的接口,可以便捷的更新这个 Weex 页面的“顶级”根组件的数据。 最后,每个 Weex 页面在具体工作的时候会更频繁的使用到下面这两个 API sendTasks(id, tasks):从 JS Runtime 发送指令到 native 端 receiveTasks(id, tasks):从 native 端发送指令到 JS Runtime 这其中,sendTasks 中的指令会以 native 的功能模块进行分类和标识,比如 DOM 操作 (dom 模块)、弹框操作 (modal 模块) 等,每个功能模块又提供了多种方法可以调用,一个指令其实就是由指定的功能模块名、方法名以及参数决定的。比如一个 DOM 操作的指令 sendTasks(id, [{ module: 'dom', method: 'removeElement', args: [elementRef]}])。 receiveTasks 中的指令一共有两种,一种是 fireEvent,相应客户端在某个 DOM 元素上触发的事件,比如 fireEvent(titleElementRef, 'click', eventObject);而另一种则是 callback,即前面功能模块调用之后产生的回调,比如我们通过 fetch 接口向 native 端发送一个 HTTP 请求,并设置了一个回调函数,这个时候,我们会先在 JavaScript 端为这个回调函数生成一个 callbackId,比如字符串 "x"——其实我们实际上发送给 native 端的是这个 callbackId,当请求结束之后,native 需要把请求结果返还给 JS Runtime,为了能够前后对得上,这个回调最终会成为类似 callback(callbackId, result) 的格式。 至此,我们就拥有了 7 个主要的接口,来完成 native 和 JS Runtime 之间的通信,同时可以做到多实例之间的隔离。 几个特殊处理的地方 ​ 篇幅有限,所有问题不能一一展开,这里提几个我们比较有心得的地方 如何避免某个页面大数据量通信阻塞其它页面的通信 ​ 绝对的避免和杜绝是很难的,我们通过以下集中方式尝试缓解和回避这种现象出现,部分想法还在论证当中: 持续优化 JS 代码的算法实现,这个是肯定要做的。 如果一个页面的内容在运算到一半的时候,用户就关掉了这个页面,尽管不能像关闭一个浏览器标签时那样杀掉这个 JS Runtime,但是可以通过在 sendTasks 的时候返回一个特殊的值来提示 JS 代码可以省去后续的计算,让整个 JS 阻塞的状态立即恢复。 部分用户交互可以跳过 JS 执行逻辑直接相应,比如为按钮监听点击事件,并在事件被触发的时候执行 openURL 这个打开网址的命令是个很长的链路,但如果我们支持 <a href> 这样的标签,用户点击链接的时候,页面可以不经过 JS 运算直接跳走,这样回避了 JS 阻塞带来的问题。 通过用户体验上的一些技巧尽量回避界面一直无相应致使用户一直等待的体验,比如通过伪类规则让用户点击一个按钮的时候第一时间感受到“hover”效果等。 可以考虑“双核” JS Runtime,永远有一个闲置的随时等待打开新页面的 JS Runtime,这样在页面切换的时候,新页面的加载和运算不会被旧页面阻塞。当然这样做存在对架构和资源的挑战。 更多的思路和想法等待大家的挖掘和探讨。 安全、隐私和稳定性 ​ 其实现在在 Weex 页面里,不经过声明给一个变量赋值还是会产生全局环境的污染,我们短期只能通过宣导的方式,教育开发者避免使用全局变量——这在传统的 HTML5 和 JavaScript 开发中都是特别不推荐的做法。 长期来看,我们可以提供一些发布前的语法检测工具,帮助开发者更好的驾驭自己的代码。 从隐私性的角度,如果你的客户端是多个团队共同研发的,相互之间希望不被打扰,我们也可以考虑引入浏览器中比较广泛实践的“同源策略”,根据 JS Bundle URL 的域名区分对待。 更高更复杂的课题:支持多个 Framework 共存 ​ 让 Weex 能够支持多种 Framework 共存,既是满足多方业务团队不同技术栈和需求的一个重要决定,同时也是尊重前端社区固有的开放自由的精神,更是让 Weex 在快速更迭的前端技术栈中立于不败之地的基础。 早期的 Weex 是重度依赖我们自身研发的 JS Framework 的,它基于 Vue 1.x 的数据监听机制,配合 Weex virtual-DOM APIs 进行数据绑定,并沿用了 mustache 的经典模板语法。现如今,Vue 2.0 迎来了很多颠覆式的革新和改进、React 也被越来越多的工程师所接受,Angular、Zepto/jQuery、VanillaJS 也都有众多的前端开发者在使用。所以我们在支持 native 端多实例指令分发的同时,也支持了多 JS Framework 本地部署并相互隔离,可以支持不同的 Weex 页面基于各自的 JS Framework 开发运行。 首先我们约定,每个 Weex 页面的 JS Bundle 第一行需要出现一行特殊格式的注释,比如: js // { "framework": "Vue" } ... ... ... 它能够识别当前 Weex 页面所对应的 JS Framework,比如这个例子是需要 Vue 来解析的。如果没有识别出合法的注释,则被认为对应到默认的 Weex JS Framework。 然后把每个 Weex 页面及其对应的 JS Framework 名称的关联关系记录下来。 最后把上面提到的 JS 和 native 通信的 createInstance, refreshInstance, destroyInstance, sendTasks, receiveTasks 等接口在每个 JS Framework 中都封装一遍,然后每次这些全局方法被调用的时候,JS 都可以根据记录下来的页面和 JS Framework 的对应关系找到相应的 JS Framework 封装的方法,并完成调用。 这样每个 JS Framework,只要:1. 封装了这几个接口,2. 给自己的 JS Bundle 第一行写好特殊格式的注释,Weex 就可以正常的运行基于各种 JS Framework 的 页面了。 总结 ​ 这篇文章主要介绍了 Weex 在 JS Runtime 这个环节的一些现状,以及它的来龙去脉,同时介绍了一些心得经验和特别的地方。篇幅有限,有些东西描述的还是比较简略,感兴趣的同学可以移步我们的 github 了解更多细节,同时欢迎大家一起参与到我们的开源项目建设当中来! 谢谢

2016/8/31
articleCard.readMore

我理解的 SPA

如题,SPA (Single Page Application - 单页应用) 这个话题已经在 Weex 社区讨论了有一段时间,在传统的前端开发领域中大家也在长期探讨这个话题。这里谈谈我个人的理解和看法。 SPA 的背景 ​ SPA 往往和 Router、页面间通信、页面间数据共享这些词汇联系在一起,不少同学直接问到这些词汇,实际上都是以 SPA 为前提的,因为脱离 SPA 的概念,这些词汇将失去它原有的意义,或者变成了完全不同的东西。 那 SPA 不是理所当然、天经地义、不容挑战的吗?干嘛要脱离 SPA 的概念讨论这些问题? 我觉得不是 SPA 背后的命题是如何管理复杂的页面关系,最终构成一个产品的整体。传统的页面之间是通过简单粗暴的“页面跳转”和“浏览器前进/后退”建立联系的,SPA 提出的观念是在浏览器中模拟页面的跳转、切换和前进/后退,相关的很多周边命题也随之而生。 (其实你不觉得这也是个"全家桶"么……) 所以我们先把问题回归到如何管理复杂的页面关系 如何管理复杂的页面关系 ​ 我觉得有以下几个子命题 —— 在这之前,我想先把接下来所有的讨论,从形态上收敛到手机应用,PC 端的暂时不涉及。 如何拆分页面 ​ 怎么把一个完整的产品以页面为维度进行拆分,这里是有学问的。 其中最关键的一个认知问题就是页面的颗粒度是否就是整个手机屏幕同时可以呈现的所有内容? 如果是,那么页面本身的结构就是简单的一维模型,即所有的页面都可以完全独立工作运行。 如果不是,那么有可能某个页面是另外一个页面的一部分,两者之间有包含关系。这样的话页面结构就变成了多维的。 在 PC 上,答案显然是后者,因为 PC 的屏幕比手机要大得多,所有页面都要整屏更换和依赖,显然是不合理的,但是在手机屏幕上,我们有机会简化为前者。 如何在工程上把不同的页面解耦 ​ 也可以从另外一个角度讲,不说怎么解耦,而是不同的页面之间保留了哪些耦合。 这个地方的设计直接决定了大规模并行研发产品的可能性和实际的效率效果 独立页面之间是如何建立联系的 ​ 如何跳转和切换:在手机上常见的跳转和切换就是 navigator 和 tabbar 两种模式,一种是产生历史记录的,有栈式结构的;另一种则是平级切换的,页面之间是并列关系 如何传递信息:开发者是否有机会在一个页面调用到另外一个页面的方法或接口 如何共享数据:用户在前一个页面提交了个人信息,如何体现在下一个页面上 看看 SPA 是怎么解决这些问题的 ​ 拆分页面的方式:如最一开始所描述的,SPA 会在浏览器中模拟页面切换和跳转,因此他需要路由控制,而控制的方式就是把每个页面都定义一个 URL,当页面切换或跳转时,URL 就会发生相应的变化,同理我们直接打开不同 URL 的时候,浏览器可以准确定位到不同的页面。常见的 URL 区分方式包括不同的路径 (path)、不同的参数 (query) 和不同的锚点 (hash) 等。所以如果父页面中有子页面存在不同的状态,则 URL 设计应该在父页面的基础上追加不同的 path/query/hash 来达到定位子页面状态的目的。 工程耦合度:首先 SPA 可以通过 URL 精准定位到不同的页面,就决定了它的路由规则是中心化约定的,当页面比较多了之后,为了避免冲突,每新建一个或一组页面都要有中心化注册的过程;另外每个页面中的代码从资源加载和管理的角度是中心化耦合的 (当然有具体优化的解法,比如按需加载等,这里不展开了) 页面之间的联络方式 跳转和切换:主要是通过代理浏览器的跳转、前进、后退、替换等行为,然后自行在页面内完成相应的变化,几个明显的入口包括 <a> 链接、location 设置、历史管理 API等 信息传递:典型的场景就是子页面和父页面之间同步变化,一般通过在 SPA 框架层面提供通信机制,而且通常是消息广播的模型 数据共享:现在普遍被接受和认同的是基于 Flux 架构的设想,开辟一块全局共享的 Store,这样应用从 A 页面切换或跳转到 B 页面的时候,可以通过这块全局的 Store 来同步数据和状态 SPA 这样的方案的特点或局限 过渡体验:SPA 因为代理了所有页面切换和跳转的行为,所有整个页面之间过渡的过程是可以定制的,比如左滑右滑、淡入淡出等等,结合一些 CSS3 变换的效果,可以做得很酷炫,传统页面跳转是很难做出这种效果的 首次加载:因为有中心化的路由管理等相关逻辑,同时 SPA 首页首次加载往往需要消耗更长的时间,如果用户的回头率不高的话,总体上这个其实是个劣势 长效 JS Runtime:这意味着用户会长期开着同一个页面,JS 的内存控制是一个非常敏感的命题,一不小心写出个内存泄漏,整个应用就会越跑越慢直到存尽应亡 需要手动处理 Security/Privacy 等问题:因为本质上所有的页面即便设计和研发商再独立,它也是运行在同一个浏览器页面中,没有绝对的信息和资源的隔离 手机淘宝是怎么解决这些问题的 ​ 手机淘宝的工程传统是一种非常简单粗暴直接有效的理念 —— 可能你看下来会有这种感觉 首先我们没有实践 SPA —— 准确的讲鲜有成功实践的 SPA 案例,但解决上述问题有一些自己细节上的思考 拆分页面的方式:没有子页面的概念,父页面某个地方不同的状态均由子页面自行设计体现,不做中心化设计和管理 工程耦合度:每个页面都是完全不同的浏览器实例,所以工程上是以页面为颗粒度完全解耦的,不同页面可以使用完全不同的前端框架、资源策略、内部通信方式等 页面之间的联络方式 跳转和切换:就是浏览器的跳转,额外的我们会通过 SPM/SCM 参数在服务端直接统计出流量转化的关系,是纯天然无污染的,SPA 做这个事情就需要另外想办法了 信息传递:因为没有父页面子页面的问题了,所以这种需求几乎是不存在的,如果真的有父子页面的关系存在,一般会是 <iframe> 可以使用 Web Messaging API 来进行通信,但总体上确实场景非常少 数据共享:首先 Flux 的全局 Store 应该不太排的上用场,因为页面之间是完全隔离的,所以这里有三个套路:1 是通过 URL 传参数;2 是通过 W3C 的一系列本地持久化存储机制,包括 cache/localStorage/WebSQL/IndexedDB/appcache 等;3 可以通过 hybrid API 或服务器记录状态并进行中转。这里额外强调一下,1 和 3 是我们使用最多的方式,2 反而用得很少,因为对于手机淘宝这种体量的产品,本地空间很快就被塞爆了,所以业务上重度依赖这种技术不是很明智的选择 手机淘宝方案的特点或局限 native过渡效果:页面之间的过渡效果是不可定制的,这是一个局限 资源重复加载:不会面临首次加载内容过多的问题,但是不同的页面资源会重复加载,当然就像 SPA 的方案一样,这里也有很多预加载或缓存的上层机制可以缓解这一矛盾 短效 JS Runtime:比较省心,反正页面跳走之后旧页面的相关资源就彻底回收了 天生 Security/Privacy:页面之间没有直接共享的系统资源,而且基于 W3C 的同源策略,所以也比较省心 所以大家会发现,我们为了更好的工程实践和大规模并行研发方面做了一定的取舍 Weex 打算怎么解决这些问题 ​ 经过上述分析和论述,我们也逐步滤清了 Weex 在这个复杂问题上的思路:如何解决 Weex 中路由管理的问题?如何在 Weex 上进行 SPA 实践?如何让 Weex 页面之间通信或共享数据?其实面对这么多看似复杂混乱的问题,只要从上述几个角度抓住重点问题,提出关键解法,就可以把复杂问题逐一解开。 拆分页面的方式:提供 <web>/<embed> 这样的组件,可以管理子页面,但这里我们延续了手机淘宝对待子页面状态定位的看法,不做中心化的设计,每个页面可以自由定义识别规则 —— 当然这个规则范围就不包含 path 了 —— 因为这需要模拟页面的跳转和切换,所以只有 query 和 hash 可以识别 工程耦合度:每个页面独立研发,完全解耦,同时可以利用 SPM/SCM 数据统计机制,和手机淘宝的工程实践经验保持一致 页面之间的联络方式 跳转和切换:目前就两个入口 <a> 链接和 openURL() 方法,对于 <web>, <embed> 中产生的跳转和切换命令,我们目前还没有具体的设计和实现,将来可以提供类似 a[target] 的配置项,让页面跳转和切换的时候可以指定父页面或子页面作为目标 信息传递:目前是没有办法传递的,未来可以设计一个类似 SPA 感觉的系统级的消息广播机制来满足业务上的需求 数据共享:同样的我们推荐的也是手机淘宝的最佳实践:URL 参数传递、本地持久化存储、服务端中转;同时,由于 Weex 的 JS Runtime 是唯一的,这也就意味着我们未来是有机会在系统层面提供类似 Flux 架构中全局 Store 的东西供开发者使用,但是这里随之而来的隐患也是比较多的,而且一旦这个功能进入系统层面,所有的问题都会被无限放大,所以我们在这方面持谨慎态度。 该方案整体的特点或局限 过渡效果:我们不可能像 SPA 那样完全用 CSS3 变换来定制页面之间的过渡效果,但是有机会归纳抽象几种常见的效果,供业务方选择,这样页面之间的过渡效果不至于像传统的浏览器效果那么生硬 资源重复加载:可以完全复用手机淘宝目前所有的最佳实践 长效 JS Runtime、Security/Privacy 问题:这方面我们和 SPA 面临的问题是相同的,目前这方面的问题我们不算解决的特别好,有很多工作需要做。也是因为如此,我们上述的系统级 Flux 架构和全局 Store 的方案需要三思而行 接下来的 Actions ​ 最后总结下来,如果大家认可我们上述的设想和取舍的话,Weex 在应用级别的工程实践上还欠缺这么几个地方: 提供子页面精确定位识别的最佳实践和必要的工具库或辅助库 提供类似 a[target] 的配置项,在页面跳转或切换时可以指定目标页面 系统级的全局消息广播机制,做到跨页面信息传递 抽象归纳几种常见的页面过渡效果,供上层业务选择和配置 有效控制长效 JS Runtime 存在的各种问题 最后的最后,非常谨慎的引入 Flux 架构中的全局 Store —— 其实系统级的 Store 和本地持久化存储只有一点点区别:就是 Store 是被进一步抽象且可以通过绑定机制自动触发视图更新的 总结 ​ 这里首先谈了谈个人对 SPA 的认识,同时觉得 SPA 背后的命题本质上是“如何管理复杂的页面关系”。然后列出了几个关键的维度,包括如何拆分页面、如何管理耦合、如何跳转和切换页面、页面间如何通信和数据共享等等,并对比了 SPA 在这方面的表现,和手机淘宝传统的实践经验和取舍判断,最后按照相同的思维模型得出了 Weex 在这方面的选择,列出了接下来的 Actions。在整个过程中,我们可能还是会在细节问题上展开更具体的讨论,届时我们可以再伺机探讨。 谢谢

2016/8/27
articleCard.readMore

我理解的 Flux 架构

本文早些时候发表在 云栖社区 https://yq.aliyun.com/articles/59357 之前 review 业务代码的时候就一直想说写一篇自己对 Flux 的理解和看法,不知不觉也过去蛮久了,于是这周末打起精神写了这么一篇。 这篇文章将谈一些我对 Flux 的理解和个人看法。如果您还不太了解什么是 Flux,请先移步这里。 另外文中没有特别大段的代码,以讨论架构设计和背后的道理为主,可能会显得有点枯燥,大家可以选个不太困的时候耐心读读看:) Flux 中的几个基本概念 ​ 这是 Flux 官方提供的一张说明图: 图中有四个名词: View Store Action Dispatcher 下面逐个以我的角度做个讲解: 首先 View 是视图,是用户看得见摸得着的地方,同时也是产生主要用户交互的地方,这个概念在 MVC 和 MVVM 架构中都是有的,有些观点认为虽然这几种架构里都有 View,但是定义不太一致,有细微的差别,我自己觉得这种差异确实是存在的,但在一开始这并不妨碍我们理解 View 这个名词。 然后是 Store,它对应我们传统意义上的 Data,和 MVC、MVVM 里的 Model 有一定对应关系。你问我它们为啥不直接叫 Data 算了,那这就是文化人和小老百姓表达方式的差别。当然了我只是想尽量降低理解成本,尝试用比较通俗的说法把问题说清楚。 然后是 Action,这看上去是一个新概念,实际上我还是能找到一些帮助大家理解的名词,叫做 Event。就是一个结构化的信息,从一个地方传递到另一个地方,整个过程就是一个 Action/Event。 最后是 Dispatcher,多说一句,我觉得正是因为有了 Dispatcher 才让前面三个名词变得有新鲜感。也是理解 Flux 的关键。言归正传,Dispatcher 算是从 Action 触发到导致 Store 改变的镇流器。比一般架构设计里直接在“Event”逻辑中修改“Data”更“正规”。所以土得掉渣的 Event 变成了 Action,土得掉渣的 Data 变成了 Store,土得掉渣的 View 仍然是土得掉渣的 View。 为什么多了 Dispatcher,这些 Store、View、Action 就变得神奇了呢? ​ 因为“正规” 传统 MVC 被 Flux 团队吐槽最深的,表面上是 Controller 中心化不利于扩展,实际上是 Controller 需要处理大量复杂的 Event 导致。这里的 Event 可能来自各个方向是 Flux 吐槽的第二个点,所以不同的数据被不同方向的不同类型的 Event 修改,数据和数据之间可能还有联系,难免就会乱。 所以和 Dispatcher 配合的 Store 只剩下了一个修改来源,和 Dispatcher 配合的 Action 只剩下了约定好的有限几种操作。一下子最混乱的地方变得异常“正规”了。架构复杂度自然就得到了有效的控制。 另外还有一个蛮好理解的点是:Action 不仅仅把修改 Store 的方式约束了起来,同时还加大了 Store 操作的颗粒度,让琐碎的数据变更变得清晰有意义。 另外,这两个地方抽象之后数据操作变得“无状态”了,所以可以根据 Action 的历史记录确定 Store 的状态。这个让很多撤销恢复管理等场景成为了可能。 综上所述,在 Flux 架构中,数据修改的颗粒度变大,更有语义;上层数据操作的行为更抽象化,碎片化程度降低。 Flux 架构是 React 技术栈独占的吗? ​ 不是,只要在传统架构的基础上注重对数据操作和用户/客户端/服务器行为的抽象定义,Flux 架构中提到的各种好处大家都享受得到。 我们就拿被 Flux 黑得最惨的那个“一大堆 V 和一大堆 M 只有一个 C”的例子好了,图中每个 View 找到不一样 Model 进行操作时,我们把这些操作抽象成 Action,然后通过中心化的逻辑找到相应的 Model 完成修改,其实就是 Flux 了。这里抽象出来的 Action 一定要和图中 Controller 能够接受到 Action 一样,没有什么特殊的地方。 基于这样的理解,Redux 提出了另外的对 Flux 架构的理解: 首先 Store 是通过 Creator 创建出来的 每个 Store 都有自己的 state 用来记录当前状态 在创建 Store 的时候,通过 Reducer 把 state 和 action 的关系建立起来 后期通过在 Store 对象上 dispatch 不同的 action 达到对 state 的修改 本质上同样是对数据操作和上层行为的抽象,另外从实现层面更加 functional。 Vuex 是基于 Vue.js 的架构设计,稍后再展开说我的看法。 Flux 架构有什么不为人知的坑吗?我们就像看人黑 Flux! ​ (咳咳咳~~~ 这个问题我得谨慎回答) 我觉得 Flux 架构没有把一个事实告诉大家,就是它的 Store 是中心化的,Flux 用中心化的 Store 取代了它吐槽的中心化的 Controller。 我看了一些基于 Flux/Redux/Vuex 架构的实现,基本上多个 Store 之间完全解耦不建立任何联系是不可能的——除非它们完全从数据行为各方面都是解耦的——这种程序用什么架构都无所谓的坦白讲。 为什么中心化的 Store 无人吐槽呢?因为中心化的数据复杂度绝对低于中心化的行为控制。你甚至没有意识到它是中心化的,这其实从另外一个侧面就证实了这一点。 所以我觉得透过 Flux 看架构的本质:这里不算是坑或吐槽,我更想说的是,放下 Flux 这把锤子,我们该怎么看世界,怎么看待自己每天在设计和架构的软件。 中心化管理数据,避免数据孤立,一旦数据被孤立,就需要通过其它程序做串联,导致复杂。这是避免各路行为乱改数据导致混乱的一个潜在条件,或者说这是一个结论。 把行为做个归纳,抽象度提高,不管是用户操作导致的,还是从服务器 pull 过来的,还是系统本身操作的。 把修改数据的操作做个归纳,颗粒度变大,大到纯粹“无状态”的极限。 另外一个没有被过多谈论的细节,就是从 Model 到 View 要简单直接,这一点各路架构都是有共识的,就不多说了。 在这几个方面,如果一个架构师能够做到极致,去TM的各种架构缩写,用哪个都一样。 Vuex 怎么样? ​ 我先说我觉得 Vue.js 怎么样,Vue.js 天生做了几件事: components,即组件化,把视图分解开 通过 computed options 简化 data 到 template 的对应关系 通过 methods options 明确各路行为的抽象 通过双向 computed options 增大了对 data 操作的颗粒度 部分 methods options 也可以用来完成纯粹的 data 操作,增大对 data 操作的颗粒度 所以 Vue.js 本身已经提供了很多很好的架构实践。但这在 Flux 看来还不够纯粹,它缺 2 点: 数据有 components 之间的树形关联,但是修改起来是分散的 相应的 computed、methods 也应该不是分散的,需要改造 所以 Vuex 需要做的事情很简单: 中心化的 store,所有 components 都共用一份数据,即一份 state;更复杂的情况下,定义有限的几种 getters,用在 computed options 中 定义有限的几种 mutations (类比从 Dispatcher 到 Store 的约定),可以直接用在 methods options 中;更复杂情况下,定义有限的几种 actions (类比从各路行为到 Dispatcher 的约定),用在 methods options 中,背后调用的是各种定义好的 mutations。 这样在 Vue 的基础上,再加上如虎添翼的 Vuex,开发者就可以享受到类似 Flux 的感觉了。 都快说完了都没提“单向数据流”这个词 ​ 是的,我觉得这是一个被用烂的词,以至于很多人在求职面试的时候一被问到 Flux 就脱口而出“单向数据流”,几乎当做 Flux 这个词的中文翻译在回答。就好像一说到 Scrum 就脱口而出“看板”一样…… 我觉得单向数据流的讲法太过表面,不足够体现出 Flux 的设想和用意。现在一提单向数据流,我脑中第一个浮现的画面其实是这个: 都快说完了都没提“时空穿梭 (time travel)”这个词 ​ 这是数据操作颗粒度变大之后的名词。我觉得它只是个名词,为什么这样说? 所为“时空穿梭”,本质就是记录下每一次数据修改,只要每次修改都是无状态的,那么我们理论上就可以通过修改记录还原之前任意时刻的数据。 大家设想一下,其实我们每次对数据最小颗粒度的、不能再分解的、最直接的操作基本 (比如赋值、删除、增减数据项目等) 都是无状态的,其实我们如果写个简单的程序,把每次直接修改数据的操作记录下来,同样可以很精细的进行“时空穿梭”,但没有人提这个词,因为它颗粒度太细了,没有语义,没有人愿意在这样琐碎的数据操作中提炼“时空”。因为数据操作的颗粒度变大了,所以变得直观,有语义,易于理解,对我们的功能研发和调试有实际帮助,所以才有了“时空穿梭”这个概念。 Weex 什么时候支持 Flux/Vuex? ​ 这是我最后想说的,首先不管有没有 Flux/Vuex,一个好的架构实践已经足以满足日常的研发需求,尤其是在手机上,界面、数据和行为都不会特别复杂。 其次,如果基于 Vue 2.0 来开发 Weex 页面或应用的话,Vuex 是天生支持的,不需要额外做什么。大家如果已经在浏览器中,不论是桌面还是手机上实践过 Vuex,应该是感觉不到任何不一样的。 最后,上周我简单写了个 Vuex 的复刻版,能够在 Weex 的 JS Framework 上工作,这里不想占太多篇幅介绍。坦白讲我希望大家更多的精力在理解 Flux 和 Vue 上。其它问题都是顺理成章的。 总结 ​ 这篇文章整理了我个人对 Flux 的理解和个人看法,首先解释一下 Flux 核心的四个名词:View, Store, Action, Dispatcher,然后提出 Dispatcher 在 Flux 架构中的关键位置,并解释为什么 Dispatcher 让其他三者变得更好更“正规”,然后是一些我通过了解 Flux 认识到的背后倡导的架构设计的最佳实践的提炼。 真的没有代码…… ……好吧如果一定要看代码可以看看这里 https://github.com/reactjs/redux https://github.com/vuejs/vuex https://github.com/Jinjiang/weex-x 谢谢

2016/8/23
articleCard.readMore

【整理】Vue 2.0 自 beta 1 到 beta 4 以来的主要更新

主要内容来自 https://github.com/vuejs/vue/releases 之前 Vue 2.0 发布技术预览版 到现在差不多三个月了,之前写过一篇简单的 code review,如今三个月过去了,Vue 2.0 在这个基础之上又带来了不少更新,这里汇总 beta 以来 (最新的版本是 beta 4) 的主要更新,大家随意学习感受一下 alpha 和 beta 版本的侧重点会有所不同 ​ 首先 Vue 2.0 对 alpha、beta 有自己的理解和设定:alpha 版本旨在完善 API、考虑所需的特性;而来到 beta 版则会对未来的正式发布进行充分的“消化”,比如提前进行一些必要的 breaking change,增强框架的稳定性、完善文档和周边工具 (如 vue-router 2.0 等) 最后的几个 alpha 版本主要更新 ​ Vue 本身的语法基础这里就不多赘述了,网上有很多资料可以查阅,我们已经假定你比较熟悉 Vue 并对 2.0 的理念和技术预览版的状态有一定的了解。 alpha 5 ​ ref 的写法由 <comp v-ref:foo> 变成了 <comp ref="foo">,更加简单,同时动态数据的写法是 <comp :ref="x"> 支持 functional components,这个特性蛮酷的,可以把一个组件的生成过程完全变成一个高度自定义的函数执行过程,比如: Vue.component('name', { functional: true, props: ['x'], render: (h, props, children) { return h(props.tag, null, children) } }) 你可以在 render() 函数里写各种特殊的逻辑,这样标签的含义和能力都得到了非常大的扩展,在后续的几次更新中,你马上会感受到一些 functional components 的威力 另外剧透一下,h 方法里的第二个参数如果是 null 就可以省略,这个改动出现在了 beta 1 alpha 6 ​ 可以设置特殊的 keyCode,比如 Vue.config.keyCodes.a = 65,然后你就可以写 <input @keyup.a="aPressed"> 了 alpha7 ​ 一个组件的生命周期名由 init 改成了 beforeCreated (大家可以在 Vuex 的源码里看到对应的改变哦) Vue.transition 的 hook 支持第二个参数,把 vm 传递进去 如: Vue.transition('name', { onEnter (el, vm) { ... } }) Beta 1 ~ Beta 4 ​ beta 1 ​ 自定义 directive 里 update 的触发时机发生了变化,由于 functional component 等概念的引入,一个 directive 的变更的颗粒度也不完全是 directive 本身引起的,所以这里做了一个更具有通用性的调整;同时 hook 名 postupdate 也相应的更名为 componentUpdated——如果你想让 update 保持原有的触发时机,可以加入一句 binding.value !== binding.oldValue 即可。 Vue.traisition 的 hook 名做了简化 onEnter -> enter onLeave -> leave server-side rendering server.getCacheKey 更名为 serverCacheKey,避免多一层结构嵌套 createRenderer/createBundleRenderer 方法不会强制应用 lru-cache,而是开发者手动选择 beta 2 ​ <transition> 标签来了! 其实这个玩意儿我之前在 polymer 等其他框架里也见到过,不过看到 Vue 的语法设计,还是觉得巧妙而简洁: <transition> <div v-if="...">...</div> </traisition> <transition-group tag="ul"> <li v-for="...">...</li> </traisition-group> 更牛掰的在这里,还记得 functional components 吧,你今天可以这样抽象一个动画效果的标签: Vue.component('fade', { functional: true, render (h, children) { return h('transition', { props: {...}, on: { beforeEnter, afterEnter } }, children) } }) 然后 <fade>...</fade> 就可以实现高度自定义的动画效果了,这个我个人觉得是非常赞的设计和实现! beta 3 ​ 支持在自定义组件中使用原生事件。因为在 Vue 2.0 的设计中,自定义组件上是不能绑定原生事件的,自定义组件上的事件绑定被默认理解为组件的自定义事件,而不是原生事件。针对这个问题我很早就提了 issue 当时小右提出了一个新的语法设计,就是 <comp @click.native="..."></comp>,beta 3 的时候终于看到它被实现了,嘿嘿,有点小激动 支持两种语法 <div :xxx.prop="x"> 和 <div v-bind:prop="{ xxx: x }"> 来对 DOM 的 property 进行绑定,最近我自己也在思考一些在 virtual-DOM 上支持 properties 而不只是 attributes 的想法,这个设计让我也多了一些新的思路。 beta 4 ​ 2 天前发布的,其实这个版本以 bugfix 为主 总结 ​ 以上是近期 Vue 2.0 的一些更新,让我自己比较兴奋的主要是 functional component 以及基于这个设计的 <transition> 和 <transition-group> 标签和自定义 transition 标签的能力拓展,还有就是久违的 <comp @click.native="..."></comp> 最后希望大家可以多多试用,有更大兴趣的可以多多学习 Vue 的源码!

2016/7/28
articleCard.readMore

通过一张图走进 Vue 2.0

这可能是字最少的一篇了,都在图里 - - 文字介绍稍后抽空再补补

2016/5/12
articleCard.readMore

Code Review for Vue 2.0 Preview

是的!Vue 2.0 发布了! 源代码仓库在此 首先,当我第一次看到 Vue 2.0 的真面目的时候,我的内心是非常激动的 Demo ​ 来个简单的 demo,首先把 dist/vue.js 导入到一个空白的网页里,然后写: 当然,在大家阅读下面所有的内容之前,先想象一下,这是一个运行时 min+gzip 后只有 12kb 大小的库 <script src="./dist/vue.js"></script> <div id="app"> Hello {{who}} </div> <script> new Vue({ el: '#app', data: {who: 'Vue'} }) </script> 你将看到 "Hello Vue" 然后再看一个神奇的: <script src="./dist/vue.js"></script> <div id="app"></div> <script> new Vue({ el: '#app', render: function () { with (this) { __h__('div', {staticAttrs:{"id":"app"}}, [("\n Hello "+__toString__(who)+"\n")], '' ) } } data: {who: 'Vue'} }) </script> 这个是 compile 过后的格式,大家会发现首先 #app 下不需要写模板了,然后 <script> 里多了一个 render 字段,Vue 在运行时其实是会把模板内容先转换成渲染方法存入 render 字段,然后再执行,如果发现 render 已经存在,就跳过模板解析过程直接渲染。所以在 Vue 2.0 中写一段模板和写一个 render option 是等价的。为什么要这样设计,稍后会我们会涉及到。 Code Review ​ 废话不说,来看仓库 哎呀好东西太多我都不知道该先讲哪个啦! package.json - - ​ https://github.com/vuejs/vue/blob/next/package.json 先看这里,我个人习惯是拿到仓库之后除了 README (它没写) 就先看这个。和 1.x 相比,开发工具链还是以 rollup + webpack + karma 为主,开发的时候用 webpack 加 watch;打包的时候用 rollup 快速而且可以自动删掉没用到的代码片段;测试的时候用 karma 各种组合,包括 e2e、spec、coverage、sauce等。语法检查用了 eslint 这个似乎没什么争议和悬念。另外我发现了两个新东西:nightwatch 和 selenium-server 另外你们就选眼睛再迟钝也会看到 ssr 这个词吧!对,就是服务端渲染 Server-Side Rendering!先不急,这个最后说,你们可以先去 high 一会儿 src ​ 作为一个见证了一小段 Vue 2.0 成长过程的脑残粉,我得跟大家从时间线的角度介绍一下这个文件夹: compiler + runtime ​ 早些时候 Vue 2.0 的代码还是这样分的,一半运行时,一半(预)编译时,中间会通过一个 JavaScript 的格式严格划清界限,即源代码 template + JavaScript 经过编译之后变成了一段纯 JavaScript 代码,然后这段纯 JavaScript 的代码又可以在运行时被执行渲染。 这里面奇妙的地方是:编译时的代码完全可以脱离浏览器预执行,也可以在浏览器里执行。所以你可以把代码提前编译好,减轻运行时的负担。 由于 Vue 2.0 对 template 的解析没有借助 DOM 以及 fragment document,而是在 John Resig 的 HTML Parser 基础上实现的,所以完全可以在任何主流的 JavaScript 环境中执行,这也为 ssr 提供了必要的基础 Vue 最早会打包生成三个文件,一个是 runtime only 的文件 vue.common.js,一个是 compiler only 的文件 compiler.js,一个是 runtime + compiler 的文件 vue.js,它们有三个打包入口,都放在了 entries 目录下,这是 src 里的第三个文件夹,第四个文件夹是 shared,放置一些运行时和编译时都会用到的工具方法集。 compiler + runtime + platforms ​ Wahahaha~ 这要说到 Vue 2.0 的第二个优点:virtual-DOM!virtual-DOM 有很多优点,也被很多人热议,而 Vue 2.0 里面的 virtual-DOM 简直是把它做到了极致!代码非常简练,而且性能超高 (据说秒杀 React,我自己没试过,大家可以自己比比看)。在这一点上编译器的前置起到了非常重要的作用,而且很多 diff 算法的优化点而且是在运行时之前就准备好的。 另外 virtual-DOM 的另一个优点当然就是可以对渲染引擎做一般化的抽象,进而适配到更多类型的终端渲染引擎上!所以在我的怂恿下,小右把本来在 runtime 下的 runtime/dom 文件夹挪到了一个名叫 platforms 的新文件夹下,改名叫 platforms/web/runtime,把本来 compiler 文件夹下 web 相关的 modules 挪到了 platforms/web/compiler! (是的没错,今天在 Weex 的子仓库里已经有另外一个 platforms/weex 文件夹了耶) compiler + runtime + platforms + server ​ 是的没有错!Vue 2.0 既然已经有了 virtual-DOM,也有了运行环境无关的 compiler,为什么不能 ssr 呢?!Vue 2.0 不只是简单的把预渲染拿到服务端这么简单,Vue 2.0 ssr 除了提供传统的由源文件编译出字符串之外,还提供了输出 stream 的功能,这样服务端的渲染不会因为大量的同步字符串处理而变慢。即:createRenderer() 会返回 renderToString() 和 renderToStream() 两个方法。同时,在 platforms/web 文件夹下除了 runtime 和 compiler 之外又多了一个 server 目录,这样编译器、服务端流式预渲染、运行时的铁三角架构就这样达成了! test ​ 说到测试,我惊奇的发现,在带来了这么多颠覆性的改变之后,Vue 2.0 竟然完好保留了绝大多数 1.0 的 API 设计,而且更快更小巧延展性更强。Vue 2.0 在前期研发阶段主要是通过粗线条的 e2e 测试进行质量保障的,因为版本延续性做得非常好,所以这部分在 1.x 的积累已经帮上很大忙了。现在 Vue 2.0 逐渐的在从 feature 的角度在进一步覆盖测试用例,对每个 API 和每个流程进行测试。目前以我个人的感觉主要的常见的链路都已经比较畅通了,具体功能细节上偶尔还是会遇到 bug 待修复,不过作为一个新兴的 Vue 2.0 来说,相信这已经远远超过大家的预期了! 其它 ​ 我觉得 Vue 2.0 在编译器和运行时的解耦上做得超级棒!中间格式设计得也非常巧妙,把静态的部分在编译时就分析出来,而且通过非常简单的 __h__, __renderList__ 等方法就搞定了几乎所有的逻辑控制和数据绑定。之前我个人在实践 Weex 的时候也是会把 template 提前 compile,但只是 compile 成一段 JSON,逻辑分析还是在运行时做的,当时和小右交流的时候就在讨论,能不能把分析过程也前置,无奈自己功力不够啊,一直没搞出来。看到 2.0 横空出世,简直是泪流满面有木有!! 还有一件事情也是之前跟小右聊到过,就是目前 Vue 提供的很多 directive 包括 filter 也都是有机会前置处理的,所以在 Vue 2.0 里,有相当一部分 directive 是前置处理成一般格式的,运行时只是针对各端的渲染机制保留了 attr, style, class, event 等几个最基础简单的解析过程,比如 if, for, else 都直接在 compile 的时候被解开了。而且 Vue 2.0 把这部分内容抽象得如此清晰,除了赞叹还是赞叹!! 还有就是,你们去看看 Vue 2.0 的提交记录,300+ 次提交,上万行高效优质的代码,总共花了差不多两周的时间,而且提交时间几乎遍布二十四个小时…… 别的不多啰嗦了,我觉得大家还是亲自看过 Vue 2.0 的源码,会对这些内容有更深刻的了解。从今天起,fork + clone Vue 2.0,写写 demo、写写测试、练练英文 XD go!

2016/4/28
articleCard.readMore

Vue 2.0 发布啦!

原文:https://medium.com/the-vue-point/announcing-vue-js-2-0-8af1bde7ab9#.cyoou0ivk 今天我们非常激动的首发 Vue 2.0 preview 版本,这个版本带来了很多激动人心的改进和新特性。我们来看看这里面都有些什么! 更轻,更快 ​ Vue.js 始终聚焦在轻量和快速上面,而 2.0 把它做得更好。现在的渲染层基于一个轻量级的 virtual-DOM 实现,在大多数场景下初试化渲染速度和内存消耗都提升了 2~4 倍 (详见这里的 benchmarks)。从模板到 virtuel-DOM 的编译器和运行时是可以独立开来的,所以你可以将模板预编译并只通过 Vue 的运行时让你的应用工作起来,而这份运行时的代码 min+gzip 之后只有不到 12kb (提一下,React 15 在 min+gzip 之后的大小是 44kb)。编译器同样可以在浏览器中工作,也就是说你也可以写一段 script 标签然后开始你的工作,就像以前一样。而即便你把编译器加进去,build 出来的文件 min+gzip 之后也仅有 17kb,仍然小于目前的 1.0 版本。 不是普通的 Virtual-DOM ​ 现在 virtual-DOM 有点让人听腻了,因为社区里有太多种实现,但是 Vue 2.0 的实现有与众不同的地方。和 Vue 的响应式系统结合在一起之后,它可以让你不必做任何事就获得完全优化的重渲染。由于每个组件都会在渲染时追踪其响应依赖,所以系统精确地知道应该何时重渲染、应该重渲染哪些组件。不需要 shouldComponentUpdate,也不需要 immutable 数据 - it just works. 除此之外,Vue 2.0 从模板到 virtuel-DOM 的编译阶段使用了一些高阶优化: 它会检测出静态的 class 名和 attributes 这样它们在初始化渲染之后就永远都不会再被比对。 它会检测出最大静态子树 (就是不需要动态性的子树) 并且从渲染函数中萃取出来。这样在每次重渲染的时候,它就会直接重用完全相同的 virtual nodes 同时跳过比对。 这些高阶优化通常只会在使用 JSX 时通过 Babel plugin 来做,但是 Vue 2.0 即使在使用浏览器内的编译器时也能做到。 新的渲染系统同时允许你通过简单的冻结数据来禁用响应式转换,配以手动的强制更新,这意味着你对于重渲染的流程实际上有着完全的控制权。 以上这些技术组合在一起,确保了 Vue 2.0 在每一个场景下都能够拥有高性能的表现,同时把开发者的负担和成本降到了最低。 Templates, JSX, or Hyperscript? ​ 开发者对于用模板还是 JSX 有很多的争执。一方面,模板更接近 HTML - 它能更好地反映你的 app 的语义结构,并且易于思考视觉上的设计、布局和样式。另一方面,模板作为一个 DSL 也有它的局限性 - 相比之下 JSX/hyperscript 的程序本质使得它们具有图灵完备的表达能力。 作为一个兼顾设计和开发的人,我喜欢用模板来写大部分的界面,但在某些情况下我也希望能拥有 JSX/hyperscript 的灵活性。举例来说,当你想在一个组件中程序化的处理其子元素时,基于模板的 slot 机制会显得比较有局限性。 那么,为什么不能同时拥有它们呢?在 Vue 2.0 中,你可以继续使用熟悉的模板语法,但当你觉得受限制的时候,你也可以直接写底层的 virtual-DOM 代码,只需用一个 render 函数替换掉 template 选项。你甚至可以直接在你的模板里使用一个特殊的 <render> 标签来嵌入渲染函数!一个框架,两全其美。 流式服务端渲染 ​ 既然迁移到了 virtual-DOM,Vue 2.0 自然支持服务端渲染和客户端的 hydration(直接使用服务端渲染的 DOM 元素)。当前服务端渲染的实现有一个痛点,比如在 React 里,渲染是同步的,所以如果这个 app 比较复杂的话它会阻塞服务器的 event loop。同步的服务端渲染在优化不当的情况下甚至会对客户端获得内容的速度带来负面影响。Vue 2.0 提供了内建的流式服务端渲染 - 在渲染组件时返回一个可读的 stream,然后直接 pipe 到 HTTP response。流式渲染能够确保服务端的响应度,也能让用户更快地获得渲染内容。 解锁更多可能性 ​ 基于新的架构,我们还有更多的可能性有待开发 - 比如在手机端渲染到 native 界面。目前我们正在探索一个 Vue.js 2.0 的端,它会用 weex:一个由中国最大科技公司之一的阿里巴巴的工程师们维护的项目,作为一个 native 的渲染层。同时从技术角度 Vue 2.0 运行在 ReactNative 上也是可行的。让我们拭目以待! 兼容性以及接下来的计划 ​ Vue.js 2.0 仍然处在 pre-alpha 阶段,但是你可以来这里 查看源代码。尽管 2.0 是一个完全重写的项目,但是除了一些有意废弃掉的功能,API 和 1.0 是大部分兼容的。看看 2.0 中一模一样的官方例子 - 你会发现几乎没有什么变化! 对于部分功能的废弃,本质上是为了提供更简洁的 API 从而提高开发者的效率。你可以移步这里 查看 1.0 和 2.0 的特性比对。如果你在现有的项目中大量地使用着一些被废弃的特性,这意味着会有一定的迁移成本,不过我们在未来会提供更详实的升级指导。 现在我们还有很多工作没有完成。一旦我们达到了令人满意的测试覆盖率,我们将会推出 alpha 版本,同时我们希望能在五月底六月初推出 beta 版。除了更多的测试之外,我们也需要更新相关库(如 vue-router, Vuex, vue-loader, vueify...)的支持。目前只有 Vuex 在 2.0 下可以直接使用,但是我们会确保在 2.0 正式发布时所有东西都会顺畅地工作。 我们不会因此而忘记 1.x 哦!1.1 将会和 2.0 beta 独立发布,提供六个月 critical bug fixes 和九个月安全升级的长效服务 (LTS)。同时 1.1 还会包含可选的废弃特性警告,让你为升级到 2.0 做好充足的准备。尽请期待!

2016/4/27
articleCard.readMore

务实的小而美

随意分享几则自己近期的感悟 人不够? ​ 我自己发现周围的同学越来越把一句话挂在嘴边,那就是:我们就这么些人,想搞定这件事情是远远不够的 说白了,大家觉得“人多好办事”,“规模”这个词很多情况下几乎就是个褒义词。 但是今天有些变化正在悄然发生,比如人类的学习能力越来越强,同时信息技术越来越发达,学习新知识的门槛越来越低,比如各技术领域的成熟度越来越高,比如移动时代前所未有的技术挑战,在有限的硬件性能和网络带宽的情况下,越是“规模”的东西越难以维持;比如体力和知识都逐渐远离核心竞争优势等等 拥抱社区,“我们有更多人” ​ 一件事搞得起来搞不起来,逐渐不取决于我们的团队有多少人,而取决于我们有没有让这件事情发生在最广阔的舞台上,因为一个技术团队再多人,哪怕一百多人,相比起任何一个知名技术社区都是九牛一毛,而一个集团、一个国家、一门语言的社区,相比起全球的互联网社区,也都是渺小的。所以今天,我们想在技术上成就一件事,未必需要很大的团队,未必需要很多人在身边 同时,我们也不能小看个体在科技发展中所起到的关键作用。技术的不断融合和碰撞也加速了这件事情。所以更重要的不是团队有多少人,而是团队有没有能够真正起到关键作用的个体,找到对的人,比组建一支上百人的团队要重要得多 从社区汲取最好的技术 ​ 我敢说,抛开具有真正技术驱动力的公司,绝大多数公司的技术工作都能够在社区找到现成并且适合的方案,也都不太需要最高精简的科研探索。一个务实的技术方案,一定是在大量的社区成熟技术基础上建立起来的,再加上一点点针对自身业务独特性的技术实践。不过看上去“没什么自己的东西”罢了,如果大家很介意这东西“得是我自己的”,那么你需要继续考虑这个问题:自己搞出来的东西能不能比今天社区的更好,能不能发展成更好的社区,是否可以在社区中的深度参与从而把“别人的”变成“自己的” 在社区中施展+检验自己的技术 ​ 然而社区不应该只有索取,还应该有奉献,如果你发现有件事情是别人也会再次遇到的那种事情,但社区没有合适现成的东西,那么恭喜你,赶紧开工吧 有的时候觉得社区化的技术发展也有它残酷的一面,同类的方案或工具库基本上也就全球数一数二的几个能生存下来,并且体现出可观的价值,稍微差一点的,也许绝对实力没有差很多,但只要不是第一,所产生的价值和影响力就少得可怜了。所以想掂量掂量自己,看看是不是这块料,不妨放到社区里跟大家一起来一场真正的PK吧!也别自己憋着,别觉得“等我弄得再完善一点再给你们看”,因为在社区化的技术发展中,这样做只会让自己越来越没有价值和勇气把它拿出来了。当然也不必贪多,哪怕是一个小小的功能,如果做到最好,能够被全球的开发者使用,那也是极好的 合理的把技术运用在工作中 ​ 我们在工作中逐渐对人的评价,会由评价其绝对的技术实力,转向评价其运用各方面技术解决实际问题的能力。这两者之间有一个比较形象的比喻,就好比我们学生时期背单词,拿出任何一个单词来,都能立刻说出它的含义、发音、常用短语、同义词、反义词、各种形态,但面对具体的对话场景不知道该用哪个词最合适最得体,这是比较典型的一种现象。其实前者就像是一个人的技术实力,后者就像是一个人的解决实际问题的能力。所以新知识新技术不光要学,还要学以致用,如何合理运用技术解决实际问题才是我们真正希望看到的。 把工作中的必经之路和常见任务萃取出来沉淀为最佳实践 ​ 现在回到“小而美的务实方案”这件事情上。我们认为看上去很重要、工作量很大、需要很多人来做的工作,是可以充分拥抱社区,汲取最好的技术,同时发挥关键角色的关键作用,并把技术合理运用在实际需求上。整件事情的成败有很多因素,但是“规模”这个因素显然不在考前的位置了 基于这样的思考,我觉得,自己喜欢的团队,是一个小而美的团队

2016/1/6
articleCard.readMore

Vue.js 1.0.0 发布了!

作为“打入敌人内部”的第一件事,转载一下 Vue.js 1.0.0 新世纪福音战士 (其实发这篇文章的时候已经 1.0.3 了) 正式发布的博文 ^_^ Hi HN (Hacker News)! 如果你还不熟悉 Vue.js 的话,可以通过这篇文章 (英文)对其有个总体印象。 在经历了 300+ 次提交、8 次 alpha、4 次 beta 和 2 次 rc 之后,今天我很荣幸的向大家宣布 Vue.js 1.0.0 Evangelion 正式发布了!非常感谢所有参与 API 重设计的同学们——没有来自社区的贡献这是不可能完成的。 模板语法改进 ​ 1.0 的模板语法解决了很多微小的一致性问题,并使得 Vue 的模板更加简洁且易于阅读。最值得留意的新特性就是 v-on 和 v-bind 的语法简写: <!-- 简写版 v-bind:href --> <a :href="someURL"></a> <!-- 简写版 v-on:click --> <button @click="onClick"></button> 当使用一个子组件的时候,v-on 用来监听自定义事件,v-bind 用来绑定属性 (props)。这些简写让子组件变得更易用: <item-list :items="items" @ready="onItemsReady" @update="onItemsUpdate"> </item-list> API 清理 ​ Vue.js 1.0 的总体目标是使其适用于更大型的项目。这也是很多 API 被弃用的原因。在被弃用的 API 中,除了很少被用及之外,最常见的理由就是它会导致可维护性被破坏。特别是,我们弃用了难以维护的功能,并把组件提炼隔离开,使其不会对项目其它部分产生影响。 比如,在 0.12 中默认资源 (asset) 方案会隐性降级到组件树中的父级。这使得组件中的可用资源非常不确定,并且取决于在运行时的用法。在 1.0 中,所有的资源都基于严格模式进行解析,也没有了隐性降级。inherit 选项也被移除了,因为它很容易导致组件强耦合,无法提炼。 更快的初始化渲染 ​ 1.0 用 v-for 指令 (directive) 取代了 v-repeat。除了提供相同的功能和更直观的作用域之外,v-for 将初始化渲染大列表和大表格时的性能提升了 100%! 更强大的工具 ​ 在 Vue.js core 之外,还有很多令人激动的东西:vue-loader 和 vueify 的新升级,包括: 组件热加载。当一个 *.vue 组件被编辑之后,其所有活跃实例都可以在页面不刷新的情况下完成热转换。这意味着你不需要重新加载 app 就可以完成诸如样式或模板的小调整;而 app 本身及其被转换的组件的状态可以被保留,这大大提升了开发体验。 局部 CSS。通过在你的 *.vue 组件的 style 标签上简单加入一个 scoped 特性,该组件的模板和最终生成的 CSS 就会被奇妙的重写以保证组件的样式只被应用在其自身的元素上。最重要的是,父组件的特殊样式不会泄露到嵌套的子组件当中。 默认支持 ES2015。JavaScript 一直在进化。你可以用最新的语法写出更简洁生动的代码。vue-loader 和 vueify 现在会直接转换你的 *.vue 组件中的 JavaScript,无需额外的设置。今天,就来写未来的 JavaScript 吧! 结合 vue-router,Vue.js 现在不只是一个库了——它提供了一个构建复杂单页应用的稳固基础。 下一步? ​ 如一般 1.0.0 所提倡的,核心 API 将会保持稳定服务于可预见的未来,库也做好了产品级别的准备。未来的开发会专注于: 改善 vue-loader 并使其做好产品级别的准备。 捋顺开发体验,比如更好的开发者工具和 Vue.js 项目/组件脚手架的 CLI。 提供更多诸如教程和示例的学习资料。

2015/10/29
articleCard.readMore

[译]如何成为一名卓越的前端工程师

译自 Philip Walton 的博客 看过之后非常有感触,很多观点都是自己长期非常坚持和认同的,所以翻译出来分享给更多的前端同学! 最近我收到一封读者来信让我陷入了思考,信是这么写的: Hi Philip,您是否介意我问您是如何成为一名卓越 (great) 的前端工程师的?对此您有什么建议吗? 我不得不承认,我很惊讶被问这样的问题,因为我从来不觉得自己是个很卓越的前端工程师。甚至我入行头几年时并不认为自己可以做好这一行。我只确定自己比自己想象中还才疏学浅,而且大家面试我的时候都不知道从何问起 话虽这么说,我到现在做得还算不错,而且成为了团队中有价值的一员。但我最终离开 (去寻求新的挑战——即我还不能够胜任的工作) 的时候,我经常会被要求招聘我的继任者。现在回看这些面试,我不禁感叹当我刚开始的时候自己在这方面的知识是多么的匮乏。我现在或许不会按照我自己的模型进行招聘,即便我个人的这种经历也有可能成功。 我在 web 领域工作越长时间,我就越意识到区分人才和顶尖人才的并不是他们的知识——而是他们思考问题的方式。很显然,知识在很多情况下是非常重要而且关键的——但是在一个快速发展的领域,你前进和获取知识的方式 (至少在相当长的一段时间里) 会比你已经掌握的知识显得更加重要。更重要的是:你是如何运用这些知识解决每天的问题的。 这里有许许多多的文章谈论你工作中需要的语言、框架、工具等等。我希望给一些不一样的建议。在这篇文章里,我想谈一谈一个前端工程师的心态,希望可以帮助大家找到通往卓越的道路。 别光解决问题,想想究竟发生了什么 ​ 很多人埋头写 CSS 和 JavaScript 直到程序工作起来了,然后就去做别的事情了。我通过 code review 发现这种事经常发生。 我总会问大家:“为什么你会在这里添加 float: left?”或者“这里的 overflow: hidden 是必要的吗?”,他们往往答道:“我也不知道,可是我一删掉它们,页面就乱套了。” JavaScript 也是一样,我总会在一个条件竞争的地方看到一个 setTimeout,或者有些人无意中阻止了事件传播,却不知道它会影响到页面中其它的事件处理。 我发现很多情况下,当你遇到问题的时候,你只是解决当下的问题罢了。但是如果你永远不花时间理解问题的本源,你将一次又一次的面对相同的问题。 花一些时间找出为什么,这看上去费时费力,但是我保证它会节省你未来的时间。在完全理解整个系统之后,你就不需要总去猜测和论证了。 学会预见未来的浏览器发展趋势 ​ 前后端开发的一个主要区别在于后端代码通常都运行在完全由你掌控的环境下。前端相对来说不那么在你的掌控之中。不同用户的平台或设备是前端永恒的话题,你的代码需要优雅掌控这一切。 我记得自己 2011 年之前曾经阅读某主流 JavaScript 框架的时候看到过下面这样的代码 (简化过的): var isIE6 = !isIE7 && !isIE8 && !isIE9; 在这个例子中变量 IE6 为了判断 IE 浏览器版本是否是 6 或更低的版本。那么在 IE10 发布时,我们的程序判断还是会出问题。 我理解在真实世界特性检测并不 100% 工作,而且有的时候你不得不依赖有 bug 的特性或根据浏览器特性检测的错误设计白名单。但你为此做的每一件事都非常关键,因为你预见到了不再有 bug 的未来。 对于我们当中的很多人来说,我们今天写的代码都会比我们的工作周期要长。有些我写的代码已经过去 8 年多了还在产品线上运行。这让人很满足又很不安。 阅读规范文档 ​ 浏览器有 bug 是很难免的事,但是当同一份代码在两个浏览器渲染出来的效果不一样,人们总会不假思索的推测,那个“广受好评”的浏览器是对的,而“不起眼”的浏览器是错的。但事实并不一定如此,当你的假设出现错误时,你选取的变通办法都会在未来遭遇问题。 一个就近的例子是 flex 元素的默认最小尺寸问题。根据规范的描述,flex 元素初始化的 min-width 和 min-height 的值是 auto (而不是 0),也就是说它们默认应该收缩到自己内容的最小尺寸。但是在过去长达 8 个月的时间里,只有 Firefox 的实现是准确的。[1] 如果你遇到了这个浏览器兼容性的问题并且发现 Chrome、IE、Opera、Safari 的效果相同而 Firefox 和它们不同时,你很可能会认为是 Firefox 搞错了。事实上这种情况我见多了。很多我在自己 Flexbugs 项目上报的问题都是这样的。而且这些解决方案的问题会在两周之后 Chrome 44 修复之后被体现出来。和遵循标准的解决方案相比,这些方案都伤害到了正确的规范行为。[2] 当同一份代码在两个或更多浏览器的渲染结果不同时,你应该花些时间确定哪个效果是正确的,并且以此为标准写代码。你的解决方案应该是对未来友好的。 额外的,所谓“卓越”的前端工程师是时刻感受变化,在某项技术成为主流之前就去适应它的,甚至在为这样的技术做着贡献。如果你锻炼自己看到规范就能在浏览器支持它之前想象出它如何工作的,那么你将成为谈论并影响其规范开发的那群人。 阅读别人的代码 ​ 出于乐趣阅读别人的代码可能并不是你每周六晚上会想到的娱乐项目,但是这毫无疑问是你成为优秀工程师的最佳途径。 自己独立解决问题绝对是个不错的方式,但是这不应该是你唯一的方式,因为它很快就会让你稳定在某个层次。阅读别人的代码会让你开阔思维,并且阅读和理解别人写的代码也是团队协作或开源贡献必须具备的能力。 我着实认为很多公司在招聘新员工的时候犯的最大错误是他们只评估应聘者从轮廓开始写新代码的能力。我几乎没有见过一场面试会要求应聘者阅读现有的代码,找出其中的问题,并修复它们。缺少这样的面试流程真的非常不好,因为你作为工程师的很多时间都花费在了在现有的代码的基础上增加或改变上面,而不是搭建新的东西。 与比你聪明的人一起工作 ​ 我印象中的很多前端开发者 (相比于全职工作来说) 都是自由职业者,有同类想法的后端开发者并没有那么多。可能是因为很多前端都是自学成才的而后端则多是学校里学出来的。 不论是自我学习还是自我工作,我们都面对一个问题:你并没有机会从比你聪明的家伙那里学到什么。没有人帮你 review 代码,也没有人与你碰撞灵感。 我强烈建议,最起码在你职业发展的前期,你要在一个团队里工作,尤其是一个普遍比你聪明而且有经验的团队里工作。 如果你最终会在你职业发展的某个阶段选择独立工作,一定要让自己投身在开源社区当中。保持对开源项目的活跃贡献,这会给你团队工作相同甚至更多的益处。 “造轮子” ​ 造轮子在商业上是非常糟糕的,但是从学习的角度是非常好的。你可能很想把那些库和小工具直接从 npm 里拿下来用,但也可以想象一下你独立建造它们能够学到多少东西。 我知道有些人读到这里是特别不赞成的。别误会,我并没有说你不应该使用第三方代码。那些经过充分测试的库具有多年的测试用例积累和已知问题积累,使用它们绝对是非常明智的选择。 但在这里我想说的是如何从优秀到卓越。我觉得这个领域很多卓越的人都是我每天在用的非常流行的库的作者或维护者。 你可能不曾打造过自己的 JavaScript 库也拥有一个成功的职业发展,但是你从不把自己手弄脏是几乎不可能淘到金子的。 在这一行大家普遍会问的一个问题是:我接下来应该做点什么?如果你没有试着学一个新的工具创建一个新的应用,那不妨试着重新造一个你喜欢的 JavaScript 库或 CSS 框架。这样做的一个好消息是,在你遇到困难的时候,所有现成的库的源代码都会为你提供帮助。 把你学到的东西都记录下来 ​ 最后,但丝毫不逊色的是,你应该把你学到的东西记录下来。这样做有很多原因,但也许最重要的原因是它强迫你更好的理解这件事。如果你无法讲清楚它的工作原理,在整个过程中它会推动你自己把并不真正理解的东西弄清楚。很多情况下你根本意识不到自己还不理解它们——直到自己动手写的时候。 根据我的经验,写作、演讲、做 demo 是强迫自己完全深入理解一件事的最佳方式。就算你写的东西没有人看,整个过程也会让你受益匪浅。 Footnotes: ​ Firefox implemented the spec change in version 34 on December 1, 2014. Chrome implemented it in version 44 on July 21, 2015, which means Opera will get it shortly. Edge shipped with this implemented on July 29, 2015. A Safari implementation appears to be in progress. You can refer to Flexbug #1 for a future-friendly, cross-browser workaround to this issue.

2015/8/10
articleCard.readMore

手机淘宝前端的图片相关工作流程梳理

本文首发自阿里无线前端博客 注:本文摘自阿里内网的无线前端博客《无线前端的图片相关工作流程梳理》。其实是一个月前写的,鉴于团队在中国第二届 CSS Conf 上做了《手机淘宝 CSS 实践启示录》的分享,而图片工作流程梳理是其中的一个子话题,故在此一并分享出来,希望仍可以给大家一些经验和启发。另外,考虑到这是一篇公开分享,原版内容有部分删节和调整,里面有一些经验和产出是和我们的工作环境相关的,不完全具有普遍性,还请见谅。 今天很荣幸的跟大家分享一件事情,就是经过差不多半年多的努力,尤其是最近 2 周的“突击扫尾”,无线前端团队又在工具流程方面有了一个不小的突破:我们暂且称其为“图片工作流”梳理。 图片!图片!图片! ​ 要说最近 1 年里,无线前端开发的一线同学最“难搞”的几件事,图片处理绝对可以排在前三。 首先,我们首先要从视觉稿 (绝大部分出自 photoshop) 里把图片合理的分解、测量、切割、导出——俗称“切图” 然后,我们要把切好的图放入页面代码中,完成相关的本地调试 第三步,把本地图片通过一个内部网站 (名叫 TPS) 上传到我们的图片 CDN 上,并复制图片的 CDN 地址,把本地调试用的相对路径替换掉 第四步,不同的图片、不同的外部环境下 (比如 3g 还是 wifi),我们需要给图片不一样的尺寸、画质展现,并有一系列的配置需要遵循 如果视觉稿有更改 (不要小看这件事,微观上还是很频繁的哦),不好意思,从第一步开始再重新走一遍…… 这里面“难搞”在哪些地方呢?我们逐一分析一下: “切图”的效率并不高,而且每一步都很容易出现返工或再沟通 打开 TPS 网站上传图片放到前端开发流程中并不是一个连贯流畅的步骤,而且 GUI 相比于命令行工具的缺陷在于无法和其它工具更好的集成 替换 CDN 图片路径的工作机械而繁琐,并且代码中替换后的图片地址失去了原本的可读性,非常容易造成后期的维护困惑甚至混乱 适配工作异常繁杂和辛苦,也很容易漏掉其中的某个环节 视觉变更的成本高,web 的快速响应的特点在丧失 所以可能把这些东西画成一张图表的话: 团队的单点突破 ​ 在最近半年的一段时间里,无线前端团队先后发起了下面几项工作,从某个点上尝试解决这些问题: lib.flexible ​ 首先,我们和 UED 团队共同协商约定了一套 REM 方案 (后更名为 flexible 方案,进而演进为 lib.flexible 库),通过对视觉稿的产出格式的约定,从工作流程的源头把控质量,同时在技术上产出了配套的 lib.flexible 库,可以“抹平”不同设备屏幕的尺寸差异,同时对清晰度进行了智能判断。这部分工作前端的部分是 @wintercn 寒老师和 @terrykingcha 共同创建的。 视觉稿辅助工具普及 ​ 其次,我们于去年 12 月开始启动了一个“视觉稿工具效率提升”的开放课题,由团队的 @songsiqi 负责牵头,我们从课题的一开始就确立了 KPI 和 roadmap,经过一段时间的调研和落实,收罗了很多实用的辅助工具帮助我们提升效率,同时布道给了整个团队。比如 cutterman、parker、Size Marks 等 img-uploader ​ 在 @hongru 去年主持完成的一系列 One-Request 前端工具集当中,有一个很有意义的名叫 or-uploadimg 的图片上传工具。它把 TPS 的图片上传服务命令化了。这给我们对图片上传工作批量化、集成化提供了一个非常重要的基础!这个工具同时也和淘宝网前端团队的另一个 TPS 图片上传工具有异曲同工之妙。大概用法是这样的,大家可以感受一下: var uploader = require('@ali/or-uploadimg'); // 上传 glob 多张图 uploader('./**/*.jpg', function (list) { console.log(list) }); // 上传多张 uploader(['./1.jpg', './3d-base.jpg'], function (list1, list2) { console.log(list1, list2); }) // 上传单张 uploader('./3d-base.jpg', function (list1) { console.log(list1) }) 随后团队又出现了这一工具的 gulp 插件,可以对图片上传的工作流程做一个简单的集成,具体集成方式是分析网页的 html/css 代码,找到其中的相对图片地址并上传+替换 CDN URL。 var gulp = require('gulp'); var imgex = require('@ali/gulp-imgex'); gulp.task('imgex', function() { gulp.src(['./*.html']) .pipe(imgex()) .pipe(gulp.dest('./')); gulp.src('./css/*.css') .pipe(imgex({ base64Limit: 8000, // base64化的图片size上限,小于这个size会直接base64化,否则上传cdn uploadDest: 'tps' // 或者 `mt` })) .pipe(gulp.dest('./css')); }); lib.img ​ lib.img 是团队 @chenerlang666 主持开发的一个基础库,它是一套图片自动处理优化方案。可以同时解决屏幕尺寸判断、清晰度判断、网络环境判断、域名收敛、尺寸后缀计算、画质后缀计算、锐化度后缀计算、懒加载等一系列图片和性能相关的问题。这个库的意义和实用性都非常之高,并且始终保持着快速的业务响应和迭代周期,也算是无线前端团队的一个明星作品,也报送了当年度的无线技术金码奖。 px2rem ​ px2rem 是 @songsiqi 主持开发的另一个小工具,它因 lib.flexible 方案而生,因为我们统一采用 rem 单位来最终记录界面的尺寸,且对于个别1像素边框、文本字号来说,还有特殊的规则作为补充 (详见 lib.flexible 的文档)。 同样的,它也有 gulp / browser 的各种版本。 img4dpr ​ img4dpr 则是一个可以把 CSS 中的 CDN URL 自动转成 3 种 dpr 下不同的尺寸后缀。算是对 lib.img 的一个补充。如果你的图片不是产生在 <img> 标签或 JavaScript 中,而是写在了 CSS 文件里,那么即使是 lib.img 恐怕也无能为力,img4dpr 恰恰是在解决这个问题。 完事儿了吗? ​ 看上去,团队为团队做了很多事情,每件事情都在单点上有所突破,解决了一定的问题。 但我们并没有为此停止思考 有一个很明显的改进空间在这里:今天我们的前端开发流程是一整套工程链路,每个环节之间都紧密相扣, 解决了单点的问题并不是终点,基于场景而不是功能点的思考方式,才能够把每个环节都流畅的串联起来,才能给前端开发者在业务支持的过程当中提供完美高效畅通无阻的体验——这是我们为之努力的更大的价值!也是我认为真正“临门一脚”的最终价值体现! 基于场景的思维方式 ​ 这种思维方式听上去很玄幻,其实想做到很简单,我们不要单个儿看某个工具好不好用,牛不牛掰,模拟真实工程场景,创建个新项目,从“切图”的第一步连续走到发布的最后一步,看看中间哪里断掉了?哪里衔接的不自然?哪里不完备?哪里重复设计了?哪里可以整合?通常这些问题都会变得一目了然。 首先,在 Photoshop 中“切图”本身的过程对于后续的开发流程来说是相对独立的,所以这里并没有做更多的融合 (从另外一个角度看,这里其实有潜在的改造空间,如何让“切图”的工作也能集成到前端工具链路中,这值得我们长期思考) 然后,从图片导出产生的那一刻起,它所经历的场景大概会是这么几种: 放入 images 文件夹 to HTML src: (upload time) -> set [src] -> webpack require -> hash filename (upload time) -> file-loader data-src (upload time) -> set [data-src] -> lib.img (auto resize) to JavaScript: element.src, element.style.backgroundImage (upload time) -> set [data-src] data (upload time) -> set [src] (manually resize) (upload time) -> set element.style.background -> lib.img (manually resize) to CSS: background-image (upload time) -> set background -> postcss (upload time) -> px2rem, img4dpr 其中 (upload time) 指的是我有机会在这个时机把图片上传到 CDN 并把代码里的图片地址替换掉;(* resize) 指的是我有机会在这个时机把图片的域名收敛/尺寸/画质/锐化度等需求处理掉。 经过这样一整理,我们很容易发现问题: 图片上传存在很多种可选的时机,并没有形成最佳实践 有些链路完全没有机会做必要的处理 (如 to HTML -> src 的链路无法优化图片地址) 有些链路处理图片的逻辑并不够智能 (比如需要手动确定优化图片选项的链路) 图片上传 CDN 之后必须手动替换掉源代码里的图片路径,这个问题在任何一个链路里都没有得到解决 CSS 相关的小工具很多,比较零散,学习和使用的成本在逐步变高变复杂 没有统一完善的项目脚手架,大家创建新项目都需要初始化好多小工具的 gulp 配置 (当然有个土办法就是从就项目里 copy 一份 package.json 和一份 gulpfile.js) 基于场景的“查漏补缺” ​ 在完善场景的“最后一公里”,我们做了如下的工作: 把所有的 CSS 工具集成到了 postcss,再通过 postcss 的 gulp 插件、webpack 插件、browserify 插件令其未来有机会灵活运用到多种场景而不需要做多种工具链的适配,即:postcss-px2rem、postcss-img4dpr,同时额外的,借此机会引入 postcss-autoprefixer,让团队拜托旧的 webkit 前缀,拥抱标准的写法 把图片上传的时机由最早的 or-imgex-gulp 在最后阶段分析网页的html/css代码上传替换其中的图片,变为在 images 目录下约定一个名为 _cdnurl.json 的文件,记录图片的 hash 值和线上 CDN 地址,并写了一个 @ali/gulp-img-uploader 的 gulp 插件,每次运行的时候会便利 images 文件夹中的图片,如果出现新的 hash 值,就自动上传到 CDN,并把相应生成的 CDN URL 写入 _cdnurl.json 同时,这个文件可以引入到页面的 JavaScript 环境中,引入到 img4dpr 工具中,引入到 lib.img 的逻辑中,让 HTML/CSS/JavaScript 的各种使用图片的场景都可以访问到 _cdnurl.json 中记录的本地图片路径和线上地址的对应关系 这也意味着 lib.img, img4dpr 需要做相应的改动,同时 页面本身要默认把 _cdnurl.json 的信息引入以做准备 创建一个 lib.cdnurl 的库,在图片未上传的情况下,返回本地路径,在已经上传的情况下,返回 CDN URL,这样通过这个库作支持,外加 lib.img、img4dpr,开发者可以做到在源代码中完全使用本地路径,源代码的可读性得到了最大程度的保证 基于 adam 创建一个包含全套工具链路的项目模板 (脚手架) 上述几件事我们于上周一做了统一讨论和分工,这里要感谢 @mingelz @songsiqi @chenerlang666 的共同努力!! 夹带私货 (偷笑) ​ 我在这个过程中,融入了之前一段时间集中实践的 vue 和 webpack 的工程体系,在 vue 的基础上进行组件化开发,在 webpack 的基础上管理资源打包、集成和发布,最终合并在了最新的 just-vue 的 adam template 里面。 之前不是在文章的最后卖了个“最后一公里”的关子吗,这里介绍的图片工作流改进就是其中的一部分:) 同时,我基于 lib.img 的思路,结合 vue.js 自身的特点,写了一个 v-src 的 directive,在做到 lib.img 里 [data-src] 相同目的的同时,更好的融入了 vue.js 的体系,同时加入了更高集成度的功能,稍后会再介绍。 夹带了私货之后是不是我就没法用了? 最后我想强调的是,除了自己的这些“私货”之外,上面提到的几个改进点和这些个人的内容是完全解耦的,如果你不选择 vue.js 或 webpack 而是别的同类型工具或自己研发的一套工具,它依然可以灵活的融入你的工作流程中。 最终效果 ​ 我们在团队内部把这些工作流程以脚手架的方式进行了沉淀,并放在了团队内部叫做 adam 的 generator 平台上 (后续会有介绍) 取名叫做 just-vue (时间仓促,adam 和相关的 generator 未来会在适当的时机开放出来)。大致用法: 安装 adam 和 just-vue 模板: tnpm install -g @ali/adam adam tmpl add <just-vue git repo> 交互式初始化新项目: $ adam ? Choose a template: just-vue ? Project Name: y ? Git User or Project Author: ... ? Your email address: ... Awesome! Your project is created! |--.gitignore |--components |--|--foo.vue |--gulpfile.js |--images |--|--_cdnurl.json |--|--logo.png |--|--one.png |--|--taobao.jpg |--lib |--|--lib-cdnurl.js |--|--lib-img.js |--|--vue-src.js |--package.json |--README.md |--src |--|--main.html |--|--main.js |--|--main.vue 目录结构剖析 ​ 然后大家会看到项目目录里默认就有: gulpfile.js,里面默认写好了图片批量上传并更新 _cdnurl.json、webpack 打包、htmlone 合并 等常见任务 images 目录,里面放好了关键的 _cdnurl.json,还有几张图片作为示例,它们的 hash 和 CDN URL 已经写好了 src/main.*,主页面入口,包括一个 htmlone 文件 (main.html),一个 webpack 文件 (main.js) 和一个 vue 主文件 (main.vue),默认引入了需要的所有样式和脚本,比如 lib.img, lib.flexible, lib.cdnurl, _cdnurl.json, v-src.js 等,我们将来主要的代码都会从 main.vue 写起——额外的,我们为 MT 模板开发者贴心的引入了默认的 mock 数据的 <script data-mt-variable="data"> 标签,不需要 MT 模板开发环境的将其删掉即可 components 目录,这里会把我们拆分下来的子组件都放在这里,我们示范性的放了一个 foo.vue 的组件在里面,并默认引入了 lib.cdnurl 库 lib 这里默认放入了 lib.img, lib.cdnurl, v-src.js 几个库,这几个库在未来逐步稳定之后都会通过 tnpm + CommonJS 的方式进行管理,目前团队 tnpm + CommonJS 的组件整合还需要一定时间,这里是个方便调整迭代的临时状态。 然后,我们来看一看 main.vue 里的细节,这才是真正让你真切感受到未来开发体验的地方。 图片工作场景 ​ 首先,新产生任何图片,尽管丢到 images 目录,别忘了起个好理解的文件名 CSS 中的图片 ​ 然后,在 main.vue 的第 11 行看到了一个 CSS 的 background-image 的场景,我们只是把 url(../images/taobao.jpg) 设为其背景图片: background-image: url(../images/taobao.jpg); 完成了!就这样!你在发布之前不需要再关注额外的事情了。没有手动上传图片、没有另外的GUI、没有重命名、没有 CDN 地址替换、没有图片地址优化、没有不可读的代码 HTML 中的图片 ​ 我们再来看看 HTML 里的图片,来到 39 行: <img id="test-img" v-src="../images/one.png" size="cover"> 一个 [v-src] 特性搞定!就这样!你在发布之前不需要再关注额外的事情了 (这里 [size] 特性提供了更多的图片地址优化策略,篇幅有限,大家感兴趣可以移步到 lib/vue-src.js 看其中的实现原理)。 JavaScript 中的图片 ​ 最后再看看在 JavaScript 里使用图片,来到 68 行: this.$el.style.backgroundImage = 'url(' + cdn('../images/logo.png') + ')' 只加入了一步 cdn(...) 的图片生成,也搞定了!就这样!你在发布之前不需要再关注额外的事情了。 发布 ​ 那有人可能会怀疑: “那你都说发布之前很方便,发布的时候会不会太麻烦啊?” 好问题,发布就两行命令: # 图片增量上传、webpack 打包、htmlone 合并,最终生成在 dist 目录 gulp # 交互式上传到 awp awp 正常的命令行反应是类似这样的: $ gulp [04:46:48] Using gulpfile ~/Sites/alibaba/samples/y/gulpfile.js [04:46:48] Starting 'images'... uploaded ../images/logo.png e1ea82cb1c39656b925012efe60f22ea http://gw.alicdn.com/tfscom/TB1SDNqIFXXXXaTaXXX7WcCNVXX-400-400.png uploaded ../images/one.png 64eb2181ebb96809c7202a162b9289fb http://gw.alicdn.com/tfscom/TB1G7JHIFXXXXbTXpXX_g.pNVXX-400-300.png uploaded ../images/taobao.jpg 4771bae84dfc0e57f841147b86844363 http://gw.alicdn.com/tfscom/TB1f2xSIFXXXXa1XXXXuLfz_XXX-1125-422.jpg [04:46:48] Finished 'images' after 46 ms [04:46:48] Starting 'bundle'... [04:46:49] Version: webpack 1.10.1 Asset Size Chunks Chunk Names main.js 17.1 kB 0 [emitted] main main.js.map 23.5 kB 0 [emitted] main [04:46:49] Finished 'bundle' after 1.28 s [04:46:49] Starting 'build'... "htmlone_temp/cdn_combo_1.css" downloaded! "htmlone_temp/cdn_combo_0.js" downloaded! [04:46:57] >> All html done! [04:46:57] Finished 'build' after 8.07 s [04:46:57] Starting 'default'... done [04:46:57] Finished 'default' after 130 μs $ awp (交互式过程略) 你甚至可以写成一行: gulp && awp 最终这个初始化工程的示例页面的效果如下 设计变更了? ​ 这条链路是我们之前最不愿意面对的,今天,我们来看看这条链路变成了什么,假设有一张设计图要换: 在 Photoshop 里把图重新切下来 同名图片文件放入 images 文件夹 运行 gulp && awp 就这样! 额外的,如果尺寸有变化,就加一步:更改相应的 CSS 尺寸代码 总结 ​ 在整个团队架构的过程中,大家都在不断尝试,如何以更贴近开发者真实场景的方式,还原真实的问题,找出切实有效的解决方案,而不仅仅是单个功能或特性。这样我们往往会找到问题的关键,用最精细有效的方式把工作的价值最大化。其实“基于场景的思维方式”不只是流程设计的专利,我们业务上的产品设计、交互设计更需要这样的思维。我个人也正是受到了一些产品经理朋友们的思维方式的影响,把这种方式运用在了我自己的工作内容当中。希望我们产出的这套方案能够给大家创造一些价值,更是向大家传递我们的心得体会,希望这样的思维方式和做事方式可以有更多更广的用武之地。

2015/8/10
articleCard.readMore

[译]如何让办公室政治最小化

来来来,看看办公室政治是个什么东西,以及如何将其最小化 译自:How to Minimize Politics in Your Company via www.bhorowitz.com 更新:跟身边一些朋友讨论之后,觉得之前翻译的标题“杜绝”言过了,还是规规矩矩翻译成了“最小化” Who the f@#k you think you f$&kin’ with —Rick Ross, Hustlin' 在我所有的从商经历中,我从没听过有人说:“我喜欢办公室政治”。但在我们的周围,令人深恶痛绝的政治又到处都是,甚至自己的公司就是如此。既然大家都不喜欢政治,那为什么它无处不在呢? 政治行为几乎都源自 CEO。也许你会觉得:“我讨厌政治,我也不关心政治,但是我的周围充满了政治气味。这显然不是我造成的。”很遗憾,你并不需要怎么关心政治就会让你的周围充斥政治手段。实际上,很少关心政治的 CEO 才会让办公室充斥政治手段。不关心政治的 CEO 们往往会直接助涨政治行为。 我这里说的政治,就是指员工追求自我职业发展多于价值产出和贡献。也许还有别样的政治类型,但是这类政治行为真的很烦。 为什么会这样 ​ CEO 无意识的激励甚至有时刺激了政治行为,办公室政治由此而生。举个非常简单的例子,我们想象一下薪酬决策。作为一个 CEO,资深的员工会反复找你索要加薪。他们会提醒你自己得到的回报已经比市场行情低多了。他们甚至已经手握外面的 offer 了。你大可给他们加个薪。这听起来没什么问题,但你就这样强烈刺激了大家的政治行为。 尤其是你在为一些对你的业务毫无价值的东西做奖励,员工会在你主动为他们的杰出表现嘉奖之前赚取更多的回报。为什么这样做很糟糕?我们仔细分析一下: 其他“好胜”的同学会立刻感到不爽。注意不管是这种不爽还是之前的奖励都和实际的业绩毫无关系。你得花很多精力处理这些跟业绩无关的鸟事。重要的是,如果你有能力上限 (competent board) 的话,你不可能给每个人预期之外的涨薪,所以这会变成一件“先到先得”的事情。 比较“与世无争”的 (但有可能很给力的) 同学会因为不关心政治而失去期末的涨薪。 公司全员都上了一课:会哭的孩子有奶吃,关心政治的人会加薪。让暴风雨来的更猛烈些吧。 现在我们来到一个更复杂的例子。你的 CFO 找到你说他希望在管理方面更进一步。他说他希望最终成为 COO,想了解自己需要具备什么样的技能才能胜任公司的这一职位。作为一个积极的主管,你可能会鼓励他实现自己的梦想。你告诉他你觉得他将来一定会是一个合格的 COO,并且应该开发某些方面的技能。额外的,你告诉他应该在管理上变得够强,这样其他高管 (executes) 才会愿意为他工作。一周之后,你的另一个高管就来找你诉苦了。她说 CFO 问她愿不愿意为他工作。她说你看好他成为最终的 COO。你之前遇到过这种事情吗?恭喜你摊上大事儿了。 如何政治最小化 ​ 专家 vs. 菜鸟 ​ 避免政治往往会觉得不自然。它挑战了诸如开明思想或鼓励员工发展等管理最佳实践。 管理高层和初级雇员的不同好比跟业余选手和职业拳手过招的不同。如果你跟一个普通人交手,你尽可自然为之不必担心。如果你想退一步,你可以先抬起你迈在前面的一只脚。如果你对垒一位专业拳手,估计就被击到了。职业拳手经过了年复一年的训练,他们善于利用你的每一处微小的失误。先抬起你迈在前面的一只脚向后退会让你在那一瞬间失去重心,这就是你的对手一直等待的机会。 同样的,如果你管理一名初级雇员,他们跟你探讨职业发展的时候,你大可忘掉那些顾虑随性作答。但就像我们之前看到的,在对待那些高度敏感的老家伙时就不一样了。为了不被政治手段击倒,你需要在这方面提炼自己的技巧。 技巧 ​ 我作为一个 CEO 发展至今,我发现三条非常有效的将政治最小化的秘诀 1. 雇佣有正确目标的人 我之前描述的例子可能卷入了有目标,但本质并不关心政治的人。并不是所有的情况都是这样的。毫无疑问,把你的公司政治搞成美国参议院级别的方式就是雇佣错误目标的人。正如 Andy Grove 所说,正确的目标是把主管的个人成功和公司成功和公司产品的胜利息息相关。错误的目标是把主管的个人成功和公司的收入划清界限。 2. 为潜在的政治行为建立严格的机制并坚持贯彻 某些行动会助涨政治,比如这三点: 绩效评估和薪酬调整 组织结构设计与调整 晋升 我们来审视在每一种情况下,你该如何制定程序来杜绝不好的行为和政治的动机。 绩效管理和薪酬调整 公司的绩效管理和薪酬调整通常都有一些滞后。这并不意味着他们没有认可员工或不给员工加薪,这仅仅是因为他们仓促特许此事在政治手段面前是非常脆弱的。通过规范合理的结构、正规的绩效评估和薪酬评估,你会在更高的高度明确薪水和股票的涨幅情况。尤其对高管的薪酬调整尤为重要,因为这样做会杜绝政治。在上面的例子中,CEO 应该有一套滴水不漏的绩效和薪酬政策,并且跟高管明确他的薪酬会被其他所有人评估。理想状态下,高管的薪酬体系应该有董事会的参与。这会 a) 有助于更好的管理 b) 让意外更难出现。 组织结构设计与调整 如果你管理高级员工,他们会一次又一次希望扩展自己的职责范围。在上面的例子中,CFO 希望成为 COO。在其它情形下,市场的一把手都希望把销售和市场一起运作起来,或工程的一把手希望把研发和产品管理都握在手上。当有些人向你提出类似的要求时,你要非常小心作答,因为你所讲的每一句话都会变成定时炸弹。一般情况下最好什么都别说。最多问问为什么,并且要牢记不要对对方提及的原因做出任何回应和解释。如果你表明了你的想法,那它一定会被传出去的,谣言会变得到处都是,你的周围会被业务无关的讨论所淹没。你应该基于常规的考虑评估你的组织结构,为了做出正确的决定,你可以获取必要的信息,但不要把你的计划透露或暗示出去。一旦你做了决定,那么就立刻执行组织结构调整:别让谣言先传到园区里。 晋升 每次你的公司提拔某些人的时候,其他同级别的人都会对此指指点点,探讨这个人是因为业绩好还是会来事儿才得到晋升的。如果答案是后者,那么其他人的反应无外乎是下面这三种: 他们闷闷不乐,感觉被低估了 他们表现出不认同,跟这个人对着干,并在背后使坏 他们决定也学这个人的政治手段,因此产生了更多不合理的晋升 很明显哪种行为你都不希望看到。因此你必须有一个正式的、透明的、有正当理由的晋升流程来决定每个人的晋升。通常这个流程是由其他团队成员参与的 (一般晋升流程要让其他主管参与,这些主管的工作性质和这个人类似,高管的晋升流程里应该有董事会的参与)。这个流程的目的是两面的。一方面它会让组织相信公司至少是基于业绩进行晋升评估的,另一方面流程的结果足以充分解释你的晋升。 3. 小心别人打来的“小报告” 一旦你的组织壮大到一定规模,团队成员就会不断相互投诉和抱怨。有时这些批评是非常激进的。要非常小心留意你听到的话以及它背后传递的信息。如果单纯的没有任何防备的解答员工提出的问题,你很容易把你内心认同的信息传递出去。如果大家在公司认为你觉得某个高管不够好,这样的信息会迅速传播开来,并且不会有人求证真相的。最后,大家都不再相信那个“问题高管”,做事也不再有效率。 这里有两种典型的你会听到的抱怨: 抱怨一个高管的行为 抱怨一个高管的能力和业绩 对于第一种问题,一般最好的处理方式就是找投诉和被投诉的双方高管拉到一个小黑屋里当面把事情解释清楚。通常一个当面的沟通就可以挽回冲突和错误 (如有)。不要试图隔空解决问题,那样只会带来问题和政治。 第二种问题会更少见同时处理起来也更复杂。如果你的一个高管鼓起勇气质疑他同伴的能力,那么很有可能,这两个人之间有很严重的问题。如果你遇到了这种问题,你一般会得到下面两种回复中的一种:a) 你会从他们那里得知一些你已经知道的事情,或者 b) 他们会给你“惊喜”。 如果他们告诉你的是你已经知道的事情,那么最主要的问题就是你已经让这个问题存在太久了。不论你迟迟不解决问题的理由是什么,你把这件事拖太久了导致现在你的团队已经向这名主管发难。你必须用最快速度解决这个问题。基本上你是要解雇这名高管了,因为我看到过的高官们只有提升得自己业绩和技能的,却从没有重拾起团队的信任和支持的。 而如果你收到的是信息是你从未了解过的,那么你必须立刻打断他,让这位抱怨别人的高管明确,你没办法确定这个评判。你不希望在重新审视他的表现之前就做处理。你也不希望大家觉得投诉是万能的。一旦你终止了谈话,你必须立刻重新审视这名被抱怨的员工。如果你发现他表现的很好,那么你必须找出抱怨他的人的动机并把它处理好,不要让这种言论占上风。如果你发现这个员工确实有问题,那么再回到抱怨他的人提供的信息,这时你应该明确处理表现不好的人。 总结 ​ 作为 CEO,你必须系统的考虑自己的一言一行导致的结果。开放,负责,目标导向才是王道,尽量避免错误的激励方式。

2015/8/2
articleCard.readMore

Vue.js 源码学习笔记

最近饶有兴致的又把最新版 Vue.js 的源码学习了一下,觉得真心不错,个人觉得 Vue.js 的代码非常之优雅而且精辟,作者本身可能无 (bu) 意 (xie) 提及这些。那么,就让我来吧:) 程序结构梳理 ​ Vue.js 是一个非常典型的 MVVM 的程序结构,整个程序从最上层大概分为 全局设计:包括全局接口、默认选项等 vm 实例设计:包括接口设计 (vm 原型)、实例初始化过程设计 (vm 构造函数) 这里面大部分内容可以直接跟 Vue.js 的官方 API 参考文档对应起来,但文档里面没有且值得一提的是构造函数的设计,下面是我摘出的构造函数最核心的工作内容。 整个实例初始化的过程中,重中之重就是把数据 (Model) 和视图 (View) 建立起关联关系。Vue.js 和诸多 MVVM 的思路是类似的,主要做了三件事: 通过 observer 对 data 进行了监听,并且提供订阅某个数据项的变化的能力 把 template 解析成一段 document fragment,然后解析其中的 directive,得到每一个 directive 所依赖的数据项及其更新方法。比如 v-text="message" 被解析之后 (这里仅作示意,实际程序逻辑会更严谨而复杂): 所依赖的数据项 this.$data.message,以及 相应的视图更新方法 node.textContent = this.$data.message 通过 watcher 把上述两部分结合起来,即把 directive 中的数据依赖订阅在对应数据的 observer 上,这样当数据变化的时候,就会触发 observer,进而触发相关依赖对应的视图更新方法,最后达到模板原本的关联效果。 所以整个 vm 的核心,就是如何实现 observer, directive (parser), watcher 这三样东西 文件结构梳理 ​ Vue.js 源代码都存放在项目的 src 目录中,我们主要关注一下这个目录 (事实上 test/unit/specs 目录也值得一看,它是对应着每个源文件的测试用例)。 src 目录下有多个并列的文件夹,每个文件夹都是一部分独立而完整的程序设计。不过在我看来,这些目录之前也是有更立体的关系的: 首先是 api/* 目录,这几乎是最“上层”的接口封装,实际的实现都埋在了其它文件夹里 然后是 instance/init.js,如果大家希望自顶向下了解所有 Vue.js 的工作原理的话,建议从这个文件开始看起 instance/scope.js:数据初始化,相关的子程序 (目录) 有 observer/*、watcher.js、batcher.js,而 observer/dep.js 又是数据观察和视图依赖相关联的关键 instance/compile.js:视图初始化,相关的子程序 (目录) 有 compiler/*、directive.js、parsers/* 其它核心要素:directives/*、element-directives/*、filters/*、transition/* 当然还有 util/* 目录,工具方法集合,其实还有一个类似的 cache.js 最后是 config.js 默认配置项 篇幅有限,如果大家有意“通读” Vue.js 的话,个人建议顺着上面的整体介绍来阅读赏析。 接下来是一些自己觉得值得一提的代码细节 一些不容错过的代码/程序细节 ​ this._eventsCount 是什么? ​ 一开始看 instance/init.js 的时候,我立刻注意到一个细节,就是 this._eventsCount = {} 这句,后面还有注释 for $broadcast optimization 非常好奇,然后带着疑问继续看了下去,直到看到 api/events.js 中 $broadcast 方法的实现,才知道这是为了避免不必要的深度遍历:在有广播事件到来时,如果当前 vm 的 _eventsCount 为 0,则不必向其子 vm 继续传播该事件。而且这个文件稍后也有 _eventsCount 计数的实现方式。 这是一种很巧妙同时也可以在很多地方运用的性能优化方法。 数据更新的 diff 机制 ​ 前阵子有很多关于视图更新效率的讨论,我猜主要是因为 virtual dom 这个概念的提出而导致的吧。这次我详细看了一下 Vue.js 的相关实现原理。 实际上,视图更新效率的焦点问题主要在于大列表的更新和深层数据更新这两方面,而被热烈讨论的主要是前者 (后者是因为需求小还是没争议我就不得而知了)。所以这里着重介绍一下 directives/repeat.js 里对于列表更新的相关代码。 首先 diff(data, oldVms) 这个函数的注释对整个比对更新机制做了个简要的阐述,大概意思是先比较新旧两个列表的 vm 的数据的状态,然后差量更新 DOM。 第一步:遍历新列表里的每一项,如果该项的 vm 之前就存在,则打一个 _reused 的标 (这个字段我一开始看 init.js 的时候也是困惑的…… 看到这里才明白意思),如果不存在对应的 vm,则创建一个新的。 第二步:遍历旧列表里的每一项,如果 _reused 的标没有被打上,则说明新列表里已经没有它了,就地销毁该 vm。 第三步:整理新的 vm 在视图里的顺序,同时还原之前打上的 _reused 标。就此列表更新完成。 顺带提一句 Vue.js 的元素过渡动画处理 (v-transition) 也设计得非常巧妙,感兴趣的自己看吧,就不展开介绍了 组件的 [keep-alive] 特性 ​ Vue.js 为其组件设计了一个 [keep-alive] 的特性,如果这个特性存在,那么在组件被重复创建的时候,会通过缓存机制快速创建组件,以提升视图更新的性能。代码在 directives/component.js。 数据监听机制 ​ 如何监听某一个对象属性的变化呢?我们很容易想到 Object.defineProperty 这个 API,为此属性设计一个特殊的 getter/setter,然后在 setter 里触发一个函数,就可以达到监听的效果。 不过数组可能会有点麻烦,Vue.js 采取的是对几乎每一个可能改变数据的方法进行 prototype 更改: 但这个策略主要面临两个问题: 无法监听数据的 length,导致 arr.length 这样的数据改变无法被监听 通过角标更改数据,即类似 arr[2] = 1 这样的赋值操作,也无法被监听 为此 Vue.js 在文档中明确提示不建议直接角标修改数据 同时 Vue.js 提供了两个额外的“糖方法” $set 和 $remove 来弥补这方面限制带来的不便。整体上看这是个取舍有度的设计。我个人之前在设计数据绑定库的时候也采取了类似的设计 (一个半途而废的内部项目就不具体献丑了),所以比较认同也有共鸣。 path 解析器的状态机设计 ​ 首先要说 parsers 文件夹里有各种“财宝”等着大家挖掘!认真看一看一定不会后悔的 parsers/path.js 主要的职责是可以把一个 JSON 数据里的某一个“路径”下的数据取出来,比如: var path = 'a.b[1].v' var obj = { a: { b: [ {v: 1}, {v: 2}, {v: 3} ] } } parse(obj, path) // 2 所以对 path 字符串的解析成为了它的关键。Vue.js 是通过状态机管理来实现对路径的解析的: 咋一看很头大,不过如果再稍微梳理一下: 也许看得更清楚一点了,当然也能发现其中有一点小问题,就是源代码中 inIdent 这个状态是具有二义性的,它对应到了图中的三个地方,即 in ident 和两个 in (quoted) ident。 实际上,我在看代码的过程中顺手提交了这个 bug,作者眼明手快,当天就进行了修复,现在最新的代码里已经不是这个样子了: 而且状态机标识由字符串换成了数字常量,解析更准确的同时执行效率也会更高。 一点自己的思考 ​ 首先是视图的解析过程,Vue.js 的策略是把 element 或 template string 先统一转换成 document fragment,然后再分解和解析其中的子组件和 directives。我觉得这里有一定的性能优化空间,毕竟 DOM 操作相比之余纯 JavaScript 运算还是会慢一些。 然后是基于移动端的思考,Vue.js 虽确实已经非常非常小巧了 (min+gzip 之后约 22 kb),但它是否可以更小,继续抽象出常用的核心功能,同时更快速,也是个值得思考的问题。 第三我非常喜欢通过 Vue.js 进行模块化开发的模式,Vue 是否也可以借助类似 web components + virtual dom 的形态把这样的开发模式带到更多的领域,也是件很有意义的事情。 总结 ​ Vue.js 里的代码细节还不仅于此,比如: cache.js 里的缓存机制设计和场景运用 (如在 parsers/path.js 中) parsers/template.js 里的 cloneNode 方法重写和对 HTML 自动补全机制的兼容 在开发和生产环境分别用注释结点和不可见文本结点作为视图的“占位符”等等 自己也在阅读代码,了解 Vue.js 的同时学到了很多东西,同时我觉得代码实现只是 Vue.js 优秀的要素之一,整体的程序设计、API 设计、细节的取舍、项目的工程考量都非常棒! 总之,分享一些自己的收获和代码的细节,希望可以帮助大家开阔思路,提供灵感。

2015/7/26
articleCard.readMore

从原型到发布——“团队时间线” 1.0 开发心得

这篇文章将会记录我自己最近一周时间里,从产生“团队时间线”这个想法,到产品设计、交互设计、开发、迭代、再到 1.0 发布的整个过程。整件事情跨越了多个分工职能,所以这件事情虽然并不大,但对我来说是一种不一样的做事方式和经历,所以觉得应该记录下来。 “团队时间线”是个可视化展示团队所有同学时间分配/管理的平台。每个人都可以在“我的时间管理”页面极简的记录自己的时间,比如从某天到另外一天做了一个项目、或者昨天开了一个重要的会等等。 从 idea 开始 ​ (下面这段阐述偏管理思考,只对技术感兴趣的同学可以跳过) 为什么要做这件事情呢?是从一个我观察到的团队现状开始的: 今天无线前端产品线基本上是人均2到3个项目在支持,几乎没有多人同时协作一个项目或一个任务 前端团队并不是以业务为颗粒度存在的团队,而是团队个人分别参与到不同业务线的工作当中,业务线团队自身也有各自的项目管理机制和丰富的经验 我们通常,也应该做事专注在事情上,围绕着任务、业务目标,安排不同的人参与进来,在一起协作。但这样的方式给前端团队带来了很多管理上的死角,我们并没有以人为本,关注人的状态。张三在参与的两个项目里工作安排都是合理的,但是两份工作叠加在一个人身上的时候就不一定了,但任何单个项目团队的负责人都看不到这件事。 所以作为补充,我希望引入“团队时间线”,从人的维度换个角度来看问题。 还有一个和时间线概念很接近的东西是“甘特图”,市面上也有不少现成的组件库,我也参与开发过一个叫 jquery.gantt 的插件。但是同样的,甘特图是专注在团队共同做好一件事情上的,而不是以人为中心的,所以每一行基本是一个子任务,像瀑布一样一步步排下来,一件事情就能占满整个屏幕的宽度和高度。而我希望时间线是可以同时让大家一眼看到前端团队全局的工作状态的。所以甘特图其实不是我想要的东西。 汇总一下自己的想法: 以人为本,专注一个人同时做多件事情时的时间管理问题 能够看到团队整体的工作状态,任务的精细管理由项目团队自己来做,不产生重复管理 这就是“团队时间线”想法的由来 设计 ​ (下面这段描述请设计师和艺术家们轻拍……) 前两天看优设哥 (优秀网页设计) 有一篇《术语小科普!聊聊线框稿、视觉稿与原型的区别》的文章,很多人可能会觉得这都是“别人家的设计”,没见过周围有人设计界面先画手稿的啊?不都是直接PS么?而且很多产品和交互文档都是拿同类产品截图来的,做完项目都不知道这文档是我们写出来的还是竞争对手写出来的…… 我自己觉得,也许对于牛掰的设计师来说,他们只是因为太牛掰了以至于可以脑补那些初级的设计手段罢了。反正自己没那个专业能力,还是照猫画虎中规中矩一点,所以就试着先画框线图,然后实现原型功能,再做出高保真视觉 (视觉稿),再做出高保真原型来。走一遍自己认为比较“规矩“的流程 当然,凑巧我找到了一个利器——53 Paper + 53 Pencil,它可以把 iPad 变成你的画板,而且 app 本身的品质是非常之高的!我现在不光拿它来画框线图,而且开会也可以拿它当会议记录本。 先把线框图画出来 ​ 然后趁热打铁,把组件划分出来,并定下来基本的几个组件名和方法/属性名 整个的时间大概用了半个小时 交互体验版本 ​ 之后,我快速在 web 端实现了一个可交互的体验版本 (UI 很“抽象”,几乎没有美感可言),大概用了一个晚上。包括时间线是可以在表头点击左右滚动的,且有平移的动画效果。 坦白讲,做到这个程度花费的时间比我想象中的少了很多,因为我用到了另外的开发利器,晚些时候介绍:) 有了这样的一个可操作版本,基本上就可以把设计好的功能和操作流程都走通一遍了。 我把做好的这个抽象版本发给某同事,得到这样的评价: 作为一个死码农,觉得这代码比UI漂亮,哈哈~ Design in Browser ​ 好吧,我紧接着就做了第三步:视觉设计,我直接利用团队已有的 Bootstrap 皮肤 + 浏览器就完成了这件事,用时 1 个小时,然后又发给了刚才那个同事: 什么鬼,为什么突然就这么好看了 融合 ​ 但其实这个设计出来的页面是不能操作的,只是个静态效果。最后我又花了差不多半个小时时间,把静态效果套到之前的“抽象”界面中。第四部也完成了。前后差不多经过了 1 天时间。 前端开发 ​ 前面已经卖了个关子了,能够用一个晚上搞定这个界面的全套功能,包括每个组件的完备性、交互反映、动画效果。Vue + webpack 功不可没,前几天我刚博客介绍过一些 vue + webpack 的内容,没错,就是用这一套技术基础快速搭建起来的。再加上前期产品设计的时候已经把组件划分和主要属性/方法命名都确定过了,所以整个开发过程完全是自顶向下的。大应用拆成子组件,子组件再拆成更小的组件;大函数写成几行伪代码,每行伪代码再拆成不同的子函数……非常之顺畅,几乎没有返工。很多地方的逻辑都是一次浏览器刷新就通过的 (那天工作状态也确实不错) 除了之前博客介绍到的 vue 和 webpack 以及 gulpfile.js 我又加入了 htmlone 打包和 awp 发布等更高集成化的工作方式。让调试、打包、发布都非常简单省时。 好用的库的积累 ​ xhr: 可以在浏览器端被依赖的 xhr 请求函数 parse-color: 处理 CSS 中的颜色值,可以返回各种格式,用在了不同任务生成的不同颜色中,同时保证颜色是半透明的。 后端开发 ​ 就在我正准备搭 Node 服务的时候,刚好找到了 @子之 子大爷,跟他聊起这件事,他也非常认同,后端的服务器设备、数据库环境和身份验证等机制都是现成的,于是大家一拍即合,迅速敲定了数据格式和服务器接口,数据基本都是通过 GET、POST、PUT、DELETE 四个 HTTP 方法完成查询、增加、更新、删除某个工作任务的。 @子之 当时同时在处理的事情也比较多,但开发效率也是奇高的,早上跟他说了想法,晚上接口就准备好了。 联调 & 发布 ​ 又经过几轮松散的调试,过了不到一周时间吧。想法、设计、前端、后端都已经完成了,我们对程序进行了部署。并开始在无线前端的导购产品小组和卖家产品小组中先试用起来。 小结 ​ 如上所述,我在大概一周的时间里,参与并见证了“团队时间线”的从无到有。它给我带来的收获,一方面是从团队管理角度一个更好的方式,另一方面实践了很多自己专业范围以外的流程和事情,再一方面也实践了 vue + webpack 的项目流程设计。一举三得。 希望这些产品、经验和收获可以在未来产生更大的价值,尤其是后两者

2015/6/27
articleCard.readMore

Vue + webpack 项目实践

最近在内部项目中做了一些基于 vue + webpack 的尝试,在小范围和同事们探讨之后,还是蛮多同学认可和喜欢的,所以通过 blog 分享给更多人。 首先,我会先简单介绍一下 vue 和 webpack: (当然如果你已经比较熟悉它们的话前两个部分可以直接跳过) 介绍 vue ​ Vue.js 是一款极简的 mvvm 框架,如果让我用一个词来形容它,就是 “轻·巧” 。如果用一句话来描述它,它能够集众多优秀逐流的前端框架之大成,但同时保持简单易用。废话不多说,来看几个例子: <script src="vue.js"></script> <div id="demo"> {{message}} <input v-model="message"> </div> <script> var vm = new Vue({ el: '#demo', data: { message: 'Hello Vue.js!' } }) </script> 首先,代码分两部分,一部分是 html,同时也是视图模板,里面包含一个值为 message 的文本何一个相同值的输入框;另一部分是 script,它创建了一个 vm 对象,其中绑定的 dom 结点是 #demo,绑定的数据是 {message: 'Hello Vue.js'},最终页面的显示效果就是一段 Hello Vue.js 文本加一个含相同文字的输入框,更关键的是,由于数据是双向绑定的,所以我们修改文本框内文本的同时,第一段文本和被绑定的数据的 message 字段的值都会同步更新——而这底层的复杂逻辑,Vue.js 已经全部帮你做好了。 再多介绍一点 ​ 我们还可以加入更多的 directive,比如: <script src="vue.js"></script> <div id="demo2"> <img title="{{name}}" alt="{{name}}" v-attr="src: url"> <input v-model="name"> <input v-model="url"> </div> <script> var vm = new Vue({ el: '#demo2', data: { name: 'taobao', url: 'https://www.taobao.com/favicon.ico' } }) </script> 这里的视图模板加入了一个 <img> 标签,同时我们看到了 2 个特性的值都写作了 {{name}}。这样的话,图片的 title 和 alt 特性值就都会被绑定为字符串 'taobao'。 如果想绑定的特性是像 img[src] 这样的不能在 html 中随意初始化的 (可能默认会产生预期外的网络请求),没关系,有 v-attr="src: url" 这样的写法,把被绑定的数据里的 url 同步过来。 没有介绍到的功能还有很多,推荐大家来我(发起并)翻译的Vue.js 中文文档 web 组件化 ​ 最后要介绍 Vue.js 对于 web 组件化开发的思考和设计 如果我们要开发更大型的网页或 web 应用,web 组件化的思维是非常重要的,这也是今天整个前端社区长久不衰的话题。 Vue.js 设计了一个 *.vue 格式的文件,令每一个组件的样式、模板和脚本集合成了一整个文件, 每个文件就是一个组件,同时还包含了组件之间的依赖关系,麻雀虽小五脏俱全,整个组件从外观到结构到特性再到依赖关系都一览无余 : 并且支持预编译各种方言: 这样再大的系统、在复杂的界面,也可以用这样的方式庖丁解牛。当然这种组件的写法是需要编译工具才能最终在浏览器端工作的,下面会提到一个基于 webpack 的具体方案。 小结 ​ 从功能角度,template, directive, data-binding, components 各种实用功能都齐全,而 filter, computed var, var watcher, custom event 这样的高级功能也都洋溢着作者的巧思;从开发体验角度,这些设计几乎是完全自然的,没有刻意设计过或欠考虑的感觉,只有个别不得已的地方带了自己框架专属的 v- 前缀。从性能、体积角度评估,Vue.js 也非常有竞争力! 介绍 webpack ​ webpack 是另一个近期发现的好东西。它主要的用途是通过 CommonJS 的语法把所有浏览器端需要发布的静态资源做相应的准备,比如资源的合并和打包。 举个例子,现在有个脚本主文件 app.js 依赖了另一个脚本 module.js // app.js var module = require('./module.js') ... module.x ... // module.js exports.x = ... 则通过 webpack app.js bundle.js 命令,可以把 app.js 和 module.js 打包在一起并保存到 bundle.js 同时 webpack 提供了强大的 loader 机制和 plugin 机制,loader 机制支持载入各种各样的静态资源,不只是 js 脚本、连 html, css, images 等各种资源都有相应的 loader 来做依赖管理和打包;而 plugin 则可以对整个 webpack 的流程进行一定的控制。 比如在安装并配置了 css-loader 和 style-loader 之后,就可以通过 require('./bootstrap.css') 这样的方式给网页载入一份样式表。非常方便。 webpack 背后的原理其实就是把所有的非 js 资源都转换成 js (如把一个 css 文件转换成“创建一个 style 标签并把它插入 document”的脚本、把图片转换成一个图片地址的 js 变量或 base64 编码等),然后用 CommonJS 的机制管理起来。一开始对于这种技术形态我个人还是不太喜欢的,不过随着不断的实践和体验,也逐渐习惯并认同了。 最后,对于之前提到的 Vue.js,作者也提供了一个叫做 vue-loader 的 npm 包,可以把 *.vue 文件转换成 webpack 包,和整个打包过程融合起来。所以有了 Vue.js、webpack 和 vue-loader,我们自然就可以把它们组合在一起试试看! 项目实践流程 ​ 回到正题。今天要分享的是,是基于上面两个东西:Vue.js 和 webpack,以及把它们串联起来的 vue-loader Vue.js 的作者以及提供了一个基于它们三者的项目示例 (链接已失效)。而我们的例子会更贴近实际工作的场景,同时和团队之前总结出来的项目特点和项目流程相吻合。 目录结构设计 ​ <components> 组件目录,一个组件一个 .vue 文件 a.vue b.vue <lib> 如果实在有不能算组件,但也不来自外部 (tnpm) 的代码,可以放在这里 foo.css bar.js <src> 主应用/页面相关文件 app.html 主 html app.vue 主 vue app.js 通常做的事情只是 var Vue = require('vue'); new Vue(require('./app.vue')) <dist> (ignored) <node_modules> (ignored) gulpfile.js 设计项目打包/监听等任务 package.json 记录项目基本信息,包括模块依赖关系 README.md 项目基本介绍 打包 ​ 通过 gulpfile.js 我们可以设计整套基于 webpack 的打包/监听/调试的任务 在 gulp-webpack 包的官方文档里推荐的写法是这样的: var gulp = require('gulp'); var webpack = require('gulp-webpack'); var named = require('vinyl-named'); gulp.task('default', function() { return gulp.src(['src/app.js', 'test/test.js']) .pipe(named()) .pipe(webpack()) .pipe(gulp.dest('dist/')); }); 我们对这个文件稍加修改,首先加入 vue-loader tnpm install vue-loader --save .pipe(webpack({ module: { loaders: [ { test: /\.vue$/, loader: 'vue'} ] } })) 其次,把要打包的文件列表从 gulp.src(...) 中抽出来,方便将来维护,也有机会把这个信息共享到别的任务 var appList = ['main', 'sub1', 'sub2'] gulp.task('default', function() { return gulp.src(mapFiles(appList, 'js')) ... }) /** * @private */ function mapFiles(list, extname) { return list.map(function (app) {return 'src/' + app + '.' + extname}) } 现在运行 gulp 命令,相应的文件应该就打包好并生成在了 dist 目录下。然后我们在 src/*.html 中加入对这些生成好的 js 文件的引入: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Main</title> </head> <body> <div id="app"></div> <script src="../dist/main.js"></script> </body> </html> 用浏览器打开 src/main.html 这时页面已经可以正常工作了 加入监听 ​ 监听更加简单,只要在刚才 webpack(opt) 的参数中加入 watch: true 就可以了。 .pipe(webpack({ module: { loaders: [ { test: /\.vue$/, loader: 'vue'} ] }, watch: true })) 当然最好把打包和监听设计成两个任务,分别起名为 bundle 和 watch: gulp.task('bundle', function() { return gulp.src(mapFiles(appList, 'js')) .pipe(named()) .pipe(webpack(getConfig())) .pipe(gulp.dest('dist/')) }) gulp.task('watch', function() { return gulp.src(mapFiles(appList, 'js')) .pipe(named()) .pipe(webpack(getConfig({watch: true}))) .pipe(gulp.dest('dist/')) }) /** * @private */ function getConfig(opt) { var config = { module: { loaders: [ { test: /\.vue$/, loader: 'vue'} ] } } if (!opt) { return config } for (var i in opt) { config[i] = opt } return config } 现在你可以不必每次修改文件之后都运行 gulp bundle 才能看到最新的效果,每次改动之后直接刷新浏览器即可。 调试 ​ 打包好的代码已经不那么易读了,直接在这样的代码上调试还是不那么方便的。这个时候,webpack + vue 有另外一个现成的东西:source map 支持。为 webpack 加入这个配置字段 devtool: 'source-map': var config = { module: { loaders: [ { test: /.vue$/, loader: 'vue'} ] }, devtool: 'source-map' } 再次运行 gulp bundle 或 gulp watch 试试看,是不是开发者工具里 debug 的时候,可以追踪断点到源代码了呢:) 完整的 javascript 代码如下: var gulp = require('gulp') var webpack = require('gulp-webpack') var named = require('vinyl-named') var appList = ['main'] gulp.task('default', ['bundle'], function() { console.log('done') }) gulp.task('bundle', function() { return gulp.src(mapFiles(appList, 'js')) .pipe(named()) .pipe(webpack(getConfig())) .pipe(gulp.dest('dist/')) }) gulp.task('watch', function() { return gulp.src(mapFiles(appList, 'js')) .pipe(named()) .pipe(webpack(getConfig({watch: true}))) .pipe(gulp.dest('dist/')) }) /** * @private */ function getConfig(opt) { var config = { module: { loaders: [ { test: /\.vue$/, loader: 'vue'} ] }, devtool: 'source-map' } if (!opt) { return config } for (var i in opt) { config[i] = opt[i] } return config } /** * @private */ function mapFiles(list, extname) { return list.map(function (app) {return 'src/' + app + '.' + extname}) } 最后,杜拉拉不如紫罗兰 ​ 做出一个 vue + webpack 的 generator,把这样的项目体验分享给更多的人。目前我基于团队内部在使用的轻量级脚手架工具写了一份名叫 just-vue 的 generator,目前这个 generator 还在小范围试用当中,待比较成熟之后,再分享出来 总结 ​ 其实上面提到的 just-vue 脚手架已经远不止文章中介绍的东西了, 我们在业务落地的“最后一公里”做了更多的沉淀和积累,比如自动图片上传与画质处理、rem单位自动换算、服务端/客户端/数据埋点接口的梳理与整合、自动化 htmlone 打包与 awp 发布等等。它们为支持业务的开发者提供了更简单高效的工作体验。 篇幅有限,更多内容我也希望将来有机会再多分享出来。 最后再次希望大家如果有兴趣的话可以来玩一下,无线前端组内的同学我都愿意提供一对一入门指导:) Just Vue!

2015/6/25
articleCard.readMore

用 Koa 写服务体验

晒一下自己用 Koa next generation web framework for node.js 写的一个 web 服务 这个 web 服务主要是做内容的列表展示和搜索的 (可能说得比较抽象,但确实是 web 服务最常需要做的事情) 主要的文件一共就2个: app.js 主程序 lib/model.js 数据层 其中 model.js 是和具体业务逻辑相关的,就不多介绍了,这也不是 Koa 的核心;而 app.js 的代码可以体现 Koa 的很多优点,也使得代码可以写得非常简练而去清晰——这是我自己都完全没有想到的事情 加载资源和相关依赖库 ​ // resources var koa = require('koa') var app = koa() var logger = require('koa-logger') var route = require('koa-route') var fs = require('fs') var path = require('path') var extname = path.extname var views = require('co-views') var render = views('./views', { map: { html: 'ejs' } }) var model = require('./lib/model') 其中: koa 是最核心的库,app 是 koa 生成的 web 服务主程序 koa-logger 和 koa-route 都是koa官方开发的“中间件”,分别用来打印日志和路由设置,路由设置稍后还会提到 fs 和 path 都是 Node 的官方包,用来进行本地文件和路径相关的处理,辅助性质的 co-views 是用来渲染模板的库,而 render 是它生成的实例,这个用法也跟传统用法不太一样,稍后会提及 Web 服务工作流 ​ // workflow app.use(logger()) app.use(route.get('/', list)) app.use(route.get('/page/:page', list)) app.use(route.get('/search/:keywords', search)) app.use(route.get('/search/:keywords/:page', search)) app.use(function *(next) { if (!this.path.match(/^\/assets\//)) { yield* next return } var path = __dirname + this.path var fstat = yield stat(path) if (fstat.isFile()) { this.type = extname(path) this.body = fs.createReadStream(path) } }) app.use(function *(next) { if (this.needRendered) { this.body = yield render(this.templateView, {cache: false, data: this.templateModel}) } yield* next }) // utils function stat(file) { return function (done) { fs.stat(file, done) } } 这部分代码是用来规划服务器工作流的,从请求被接受到响应被发出,整个过程都在这段代码里一览无余。工作流设计的主要的用法是 app.use(...)。里面的参数其实就是一个 generator。 首先是打开日志 然后是分发路由,这里可以看到,有首页、列表、搜索、搜索列表 4 种设计,分别对应到了各自的处理方,list 和 search 其实都是在利用 lib/model 在生成数据,准备给模板进行渲染。这里的原理也有特殊之处,稍后会看到 再看紧随其后的两个 app.use,分别是处理静态资源目录 assets 和对模板+数据进行渲染 所以完整的工作流可以理解为: 请求页面 (列表或搜索) -> logger -> 路由分发 -> list 或 search -> 模板渲染 -> 回应 请求静态资源 -> logger -> 找到对应的 assets 文件 -> 回应 function *() {} 和 yield 是啥? ​ 这个其实是 Koa 的精髓所在,在介绍它之前,我们先把 list 和 search 的代码也贴出来: // routes function *list(page, next) { next = arguments[arguments.length - 1] this.templateView = 'page' this.templateModel = yield model.list({page: page}) this.needRendered = true yield *next } function *search(keywords, page, next) { next = arguments[arguments.length - 1] this.templateView = 'search' this.templateModel = yield model.search({keywords: keywords, page: page}) this.needRendered = true yield *next } 大家会发现,首先 app.use(...) 和 route.get(path, ...) 传入的参数都是一种写得很像函数的东西,但不同之处是函数的写法是 function foo() {...},而这里的写法多了一个星号,即 function *foo() {}。这种写法其实就是 ES6 里的 generator。而 yield 正是配合这个写法的一种语法。 有关 ES6 generator 的基础知识,建议大家来 @兔哥 的这个 ES6 教程网页来学习,这里不做原理方面的赘述。但我想说的是,由于 web 服务的处理本身就是“一层一层”的,并且有些处理是可以同步的,有些是只能异步的,我们不免要精心设计很多中间件并保障它的可扩展性,同时尽量简化异步操作的写法保障它的可读性。 有了 ES6 generator 和 yield 之后,我们的每一层中间件都可以从流程上看成一个以 yield *next 语句切分出来的 “三明治”: function *(next) { // 下一步之前的操作 yield *next // 进行下一步 // 所有逻辑处理完之后的补充操作 } 而且这个“下一步”是不介意是不是异步行为,都可以这样简单描述清楚的。 后头看我们设计的整个工作流的实现: 我们这里的逻辑基于全部是出现在 yield *next 之前的,但是如果你需要在临发出响应之前做点什么,就可以写在其后面了 co-views 的用法 ​ co-views 其实是对通用模板引擎渲染平台 consolidate 的封装,consolidate 应该算是 express.js 时代非常重要的一个库,它支持包括 ejs, mustache, swig 等各种模板渲染并提供统一的 api 调用方法。根据对 co-views 源码的分析,它把 consolidate 统一的 api 又封装成了 return function (done) {...} 的形态,这样源代码中的 yield render(view, model) 就能够融入 generator 的逻辑之中。 值得一提的是,源代码中 yield render(view, model) 这里的 model 传入了一个 {cache: false} 的参数,这会意味着模板不会被缓存,每次修改模板文件之后,在不重启服务的情况下,刷新页面就可以看到最新的效果。这个选项是针对开发环境设置的,为了保障线上环境的运行性能和效率,这个选项应该是不需要的。 lib/model 的用法 ​ 同上,我们在 lib/model.js 里封装的 yield model.list({page: page}) 和 yield model.search({keywords: keywords, page: page}) 也都会生成形如 return function (done) {...} 的返回值,以融入 generator 的逻辑之中。 最后,监听端口 ​ // listen app.listen(3000) console.log('listening on port 3000') That's it 后记 ​ 在首次尝试用 generator 的方式编写 web 服务的时候,我自己一开始总会把 yield 的位置、yield 后面要不要加星号、function 后面要不要加星号、app.use() 的调用顺序这几件事情弄得乱糟糟的,可能还是对 generator 和 koa 的理解不够深入,不过逐渐写着写着,感受到了更多的爽和快感。到最后用如此简单的一个 js 文件完成了全部的功能和逻辑串联,还是觉得很兴奋的。大家如果感兴趣也可以搞来玩一玩,写点自己平时用得到用不到的小玩意儿体验一下:)

2015/6/21
articleCard.readMore

webcomponents 笔记 之 配置管理

话说上周末看到一个吐槽腾讯“内部开源”的微博,后来我想了想,自己那么骚包的在项目还没做完之前,就在 CSSConf 上说我们将来要开源一个名叫 Zorro 的库。结果好几个月过去了还是没有准备好,也就不敢再笑话别人了…… 我觉得把东西开源出来之前,有几件事要准备好,不然除了自己刷存在感之外,真的没意义。比如: 是否有了 (或阐述清楚了) 明确的目标和方向,不然找不到合适的合作者和贡献者 是否有了 (或阐述清楚了) 明确的设计哲学和开发原则,不然大家无法形成合力,项目很容易陷入混乱 是否有了最小的可工作版本,不然雪球滚不起来 是否有了充分的文档、demo和测试用例,让大家更直观的了解项目,利用项目,也对项目的质量更有信心 印象中我见到的优秀的开源项目,基本都在被大家广泛认识之前,都已经把这些事情打理好了——这也是我一直推崇的。 不过在这之前,我愿意在此分享一些自己开发中的心得,跟大家一起探讨相关的话题。 -- 以上是一些比较啰嗦的铺陈 -- 组件分解的方式及其衍变 ​ 在开发大型应用的时候,难免要用到一些组件化的分解方式。比如:把一个相册浏览界面分解成:“相册列表”和“大图预览”两个区域,“相册列表”又由一个个“相册缩略图”组成,每个“相册缩略图”包含了一个“小图片”以及“预览按钮”、“删除按钮”、“排序按钮”等操作按钮…… 而如何管理和划分组件逐渐变成了前端工程里的一门学问。 最简单的分解方式是树形分解,自上而下。比如刚才的那个相册浏览界面的例子。 同时,我们会发现,树形的最末端往往存在着有共性的组件,比如按钮、文本框之类的组件,它们无处不在。这时,就有了所谓的“基础组件”和“业务组件”之分。“基础组件”是共享的,树形结构中的任何一个结点(“业务组件”)都可以直接使用这些“基础组件”。 如果程序的结构再复杂,那么就在“业务”和“基础”之间分更多的层,每一层有自己明确的职能范围,同时,较高层的组件可以自由调用较低层的组件。 组件的配置信息 ​ 配置信息大多是在组件在兼顾通用性抽象和特殊性业务时出现的。好的配置设计可以避免大量重复的组件设计和实现。 较简单的配置信息通常都是组件本身的一些属性 (properties) 或特性 (attributes),在 webcomponents (polymer) 的场景下,就是: <polymer-element name="x-person" attributes="name, age, gender, avatar, ..."> ... </polymer-element> 进一步的,有时候我们需要把上层组件的配置信息带到更下层的组件: <polymer-element name="x-person-avatar" attributes="avatar"> ... </polymer-element> <polymer-element name="x-person" attributes="name, age, gender, avatar, ..."> <template> ... <x-person-avatar avatar="{{avatar}}"></x-person-avatar> ... </template> ... </polymer-element> 如果一个程序的组件层次太深,则可能出现下面两个问题: 会有一些很小的底层组件的配置信息,需要从最外层配置一层一层的“透传”到最底层,每一层组件上都要定义一个配置字段 最外层组件的配置字段往往会被各种七七八八的小组件配置占领 于是顺着这个思路,我们发现,有一种不太起眼的办法在很早的时候就被忽略掉了——这就是全局配置信息。 什么时候全局配置更合适 ​ 通常情况下,和整体应用所处环境相关,同时和上层组件无关的配置适合做全局配置。 举一个实践中的例子:在开发图片上传组件的时候,我们发现,图片上传组件往往需要一个上传图片的服务器地址,这个地址在固定的用户、固定的应用之中,通常是一致的,只是在不同的应用中,图片可能需要长传到不同的服务器地址。 这种情况下,通过组件的配置字段一层一层向下传递找到图片上传组件显然是很繁琐的。于是我们想了个简单的办法: 在页面最外层 (<body>里面) 写入一些 <input type="hidden"> 的标签,注明服务器相关配置 在组件中写一段脚本,查询所有的这样的标签,并且把键值对记录下来——这其中就包括应用自身的图片上传的服务器地址 2015-03-31 追加说明:感谢民工哥 @民工精髓 在微博上的指点。也是因为很多服务器的配置是后端同学决定的,所以我们创造了这种对传统后端配置友好的 <input type="hidden"> 写法。这样的写法对于后端的友好之处我就不一一列举了。如果是纯前端程序,配置来自前端,确实直接定义全局变量就好,不必这么麻烦。 于是整个程序变成了: <polymer-element ...> <script> Polymer({ ready: function () { var inputList = document.querySelectorAll('input[type="hidden"]'); ... } }); </script> </polymer-element> ... <body> <input type="hidden" name="uploadUrl" value="/pathToUpload.do"> ... </body> 这样不管图片上传组件用在哪里,其它组件都不会因此而产生负担,同时这些配置的管理也变得很清晰——这甚至和前端工程师平时和后端工程师协作的流程是完全吻合的:前端负责写好 components,后端负责把 <input type="hidden"> 配置好。 如何做好配置管理 ​ 我们顺着上面的思路继续想:如果程序中很多组件都有类似的配置需求,那么: <input type="hidden"> 不能被滥用 组件内部的读取配置信息的方式可以抽象出来 于是,就有了 Zorro 现在的一个组件:<z-config>。它的大致功能如下: 首先它可以快速的提取 <body> 最外层的 <input type="hidden"> 配置信息——这显然是和后端工程师约定过的 其次,它可以被任何组件作为“基础组件”引用 它提供方便的接口,供任何上层组件访问配置信息 自从有了这个组件,很多配置相关的问题在 webcomponents 中都显得很轻松了。毫不夸张的说前端工程师和后端工程师的关系也因此不像之前那么紧张了。 管理更多的信息 ​ 后来,我们在配置管理的基础上,加入了更多的实用信息。比如: 可以读取 location.href 中的字段信息 可以像 localStorage 一样提供一块全局共享的键值对空间,方便组件之间共享状态信息 可以在 <z-config> 在被创建时,根据不同的场景设置一些初始化数据等 以上这些,构成了今天的 <z-config> 组件 实现机制并不复杂,这里就暂不贴代码出来了,但终归是会开源的,我保证。 总结 ​ 说回配置管理本身,它实际上是一种信息在程序和组件之间的流通方式。我们基于对 webcomponents 自身特点和形态的理解,加上业务实践中的一些体会,设计了这样的一个标签。希望给大家一些启发,同时也欢迎大家的讨论和观点。

2015/3/30
articleCard.readMore

14}, {15

去年年底的时候刚换了工作,刚到杭州,一到杭州就赶上996,匆匆忙忙,年底就稀里糊涂过去了 今天晚上,改完了自己名下的最后一个bug,也算是给2014简单收个尾吧,也突然觉得自己有一点时间回顾一下了 先从13年说起吧,最大的变化就是我换工作了,也搬家了,从北京搬到了杭州,从此过上了南方人的生活: 夏天更热,冬天屋里更冷,这一点嘛,已经差不多习惯了。无非是多开空调呗…… 空气更好,雨水更多 交通更好,上班路上很畅通,而且有班车坐很方便 也赶在限牌之前买到了自己心仪已久的那款车 有了车以后,生活确实方便了许多,晚上或者周末出门有了更多选择,近郊出游也很方便,还去了一趟上海、去了一趟宁波、去了一趟横店 (说道横店,我超喜欢秦宫,气势恢宏,有刀枪剑戟的感觉) 杭州本来也是个旅游城市,西湖、龙井、西溪湿地,也给平日的生活增添了很多乐趣 饮食方面,这边吃海鲜/日料的机会比在北京的时候多多了,相对的火锅和面食少了 运动比以前少了,以前在北京常踢球的,来杭州之后几乎没怎么踢,可能是因为自己没找对组织,现在逐渐开始多游泳了 经过了1年多的时间,我想说,我很喜欢杭州这个地方。 然后说说工作吧。 这一年多时间我其实没做太多事情,更多的还是在熟悉环境,熟悉人、熟悉业务、熟悉流程、熟悉沟通方式、熟悉做事风格。阿里确实是个巨大的集团,有超过十年的历史和沉淀。我希望可以帮助团队做得更好,但首当其冲的事情是要先弄清楚游戏规则,努力找到问题的症结,谨慎的提出可实施方案,然后循序渐进的往前走。可以说每一步都要走得小心谨慎,稍有疏漏,可能就前功尽弃了,这一点在阿里可能体现的尤为明显。我想这也正是我之前的工作经验可以体现出价值的地方。 从工程和管理的角度,我今年主要是以培训或布道的方式为大家分享一些系统而又务实的最佳实践,包括如何高效开会、合作、项目管理、代码管理、时间管理,也包括组织团队讨论编码规范、项目目录结构、文档格式、工具链使用等。希望可以全面的提升团队的工作效率和效果。这些工作会继续延续到2015年。 团队的技术视野也是一个亟待提高的地方,这同时也和团队每一位同学的职业发展有着密切的联系,这也是在新的一年我希望可以做好的一件事。 技术上,今年我主要的精力聚焦在了2件事情上:前半年花了一些时间在触摸屏幕操作上,研究了几个手势库,也尝试自己写,不过没有太像样的成果,算是个失败的过程吧,我今天回顾这件事情,主要是没有抓到重点,上来就研究多点复杂操作,也许这些东西在未来可能会显得更有价值,还是决定放一放,什么时候有了更成熟的思路,再考虑拿起来;后半年花了更多时间在 web components 上,还有 polymer 框架,我觉得这是我们看得见的未来,非常值得投入进去看一看,我和几个同事一起做了些实践,也有很多心得。 在全年技术探索和实践的过程中,我有一个深刻的感悟,是和“造轮子”这件事有关系的,在今天这个技术世界里,与其说“不要重复造轮子”,不如说“造轮子”的和“用轮子”的已经是两种性质的工作了。我们今天很多人选择的工作,首要任务是造汽车而不是造轮子。我觉得这是很多讨论“到底要不要造轮子”的问题的根源。造汽车和造轮子有各自的技术含量,有各自需要认真思考的问题,如果没有认清这个问题,明明是造汽车的,结果觉得造轮子更牛掰而去造轮子,必定没有好结果,或者事倍功半;如果真想造轮子,应该好好找个造轮子的地方,才会造出最好的轮子,不然也只会做个半调子。篇幅有限,这事儿暂不细说了…… 技术方面还有一件事,就是开始组织 W3C 中文兴趣组的技术讨论,我们这一年组织了几次《ig在线talk》,也发起了一些标准翻译,也讨论了一些诸如首屏渲染的提案,还有中文字体和排版的制定,有的时候电话会议的时间已经超了,但是大家还会津津有味的在技术的领域里讨论闲聊,这种感觉是很少有的,相信经历过这些讨论的同学们也感受得到。 另外自己在2014年初暗自下决心要看10本以上的书——这对于我这种平时没有看书习惯的人来说其实并不容易——这个目标算是已经达成了。这里面《习惯的力量》、《合作的进化》、《反脆弱》、《rework》、《remote》都是令我印象深刻的书,看过之后有非常多的收获。 2015年,我有这么几个简单的想法: 第一,要有健康的身体。这似乎是我从来没有过的对健康如此重视,也是因为现在工作和生活能够协调的更好的一个结果吧。不要连续熬夜或者休息不够,更重要的是保持运动,控制饮食,眼睛、颈椎、牙齿和肝这4个上班族体检最多出现的问题都要特别注意起来。 第二,多花时间陪家人、老同学、周围的朋友,多参与一些社交活动 第三,要把自己的编码量降下来,把团队打理好。我也深刻的感觉到,自己应该花更多的时间关怀身边的每一位同事,业务上的、技术上的、心态上的,都有很多值得去做的事情。让团队每一个人更好,比自己一个人更好重要的多。 第四,把有限的个人精力集中投入在1件或几件事上 第五,看15本书 到明年这个时候,我会把这篇blog翻出来看看完成的怎么样:)

2015/1/1
articleCard.readMore

小秀个人的13~14年摄影作品 (共19张)

2 年前曾经也发过几张自己拍的照片,似乎大家对于我的拍照技术还是蛮宽容的。下面几张同样是我认真挑选过的这 2 年来拍的,同样求轻拍 ^_^ p.s. 这 2 年真的经历了不少事情 港版风景 唐老鸭 春天的气球 旋转木马 快到推车里来 山西博物馆 索菲亚大教堂 年轮 晚霞 杭州生活 杭州一角 圣诞 阿里巴巴滨江园区 我的世界杯 手机屏保 之 淘宝城 手机屏保 之 龙井品茶 虾米音乐节 双11演唱会 朋友的婚礼

2014/10/29
articleCard.readMore

由今年D2前端论坛想到的

之前参加过 2 次,今年的 D2 是我第一次以“自己人”的身份参加的。和往年一样,受益匪浅,但也有了一些不一样的想法。 纵览这次 D2 的主题 张可竞:《指尖上的数据》 苏 千:《支付宝前后端分离的思考与实践》 林 楠:《nodejs一小步 前端开发一大步》 祝 犁:《Listen to the buzz of Angular.JS — 阿里云控制台AngularJS实践》 周 杰:《第三方开发前端实践》 不 四:《企业级 NPM 服务在阿里的实践》 贝 勒:《面向多端的蘑菇街前端技术架构》 弘 树:《航旅无线H5技术体系成长之路》 刘 威:《京东前端工业化实践之路》 一 位:《淘宝前端工程与自动化体系》 贺师俊:《透过ES6看JS未来》 邓 钢:《架构与IBM前端》 张克军:《豆瓣的前端发展思路》 我们不难发现,这次的主题方向有一个很明显的趋势,就是工程体系化和整体架构。大家不约而同的在这里摸索、实践、分享,我觉得这不是一件偶然的事情。我记得自己 2011 年的时候写过一篇关于当时如火如荼的 HTML5 的文,我当时的一个观点是: 我觉得HTML5需要更多模式化和工程化的思维,而不仅仅是技术上实现某个效果的可能性……唯有更多更丰富的上层建筑,才会让HTML5真正发挥威力……最后,优秀的规范、工具、函数库、平台、引擎、理念,才会催生真正优秀的HTML5作品甚至是HTML5产业。 今天,我们已经不光拘泥于一份实用的新规范、一个酷炫的前端效果、一个满足需求设计合理的代码库,想得更远了,站得更高了。或许这一次的“绽放”过后,就是开花结果的时节了。 同时我也觉得,我们今天已经有了这么大篇幅的主题了。分享有余,讨论不足。我认为分享和讨论应该是结合在一起才最有意义的,分享让人身临其境,讨论则会碰撞出更大的火花,产生更大的效应。我们是否可以在各自的“独门手艺”中找到灵感和认同,进而融合、演进,产生质的飞跃,共同提升到一个更高的高度,最后发现工程与架构的普世真理,这是我由这次 D2 想到的第一件事。 第二件事是前端团队。 大家在技术分享之余聊得最多的应该就是这个话题了。怎么找工作,怎么招聘,怎么应付老板,怎么带团队。 对于前两个问题,我个人的观点是要注重技术基础和学习能力。这两点主要看个人追求和造化,而经验与工程能力是更易于在工作过程中积累和培养的。那么问题也来了:我们假设有一部分,或者说相当一部分人来参加 D2 或业余时间任何别的技术交流会,是为了自我提升而来,我们是否应该更多保留一些技术本身的话题呢? 我不禁想到一个极端的情况,就是我们每个人的“格局”都很大,看得都很远,规划得也很好,但是真正做起事情来,需要动手了,就手忙脚乱了,这似乎不太好吧。也许基础的东西是更通用的、直接受益的、可以举一反三的——尽管它不那么光鲜亮丽。 我也不相信我们今天纯技术的层面都很好了、也没有值得深入琢磨的玩意儿了。如果我们在这个地方都有长足的进步,我不担心大家找不到合适的工作,老板也不担心找不到合适的人。 而对于后两个问题,我的感觉是,实际工作中,总会有一些让人觉得脏、累、烦,谁都不想处理的事情。很多团队的问题都出自如何优雅的处置这些“dirty work”。强势的主管向下施加压力,结果员工疲于奔命对主管失去信心;心软的主管仍凭大家随波逐流结果没有业绩员工也失去了信心。似乎是个解不开的锁。这确实是需要很多智慧的。那么问题又来了,**我希望 D2 今后可以有一些关于团队、关于人的主题。我们的技术能搞上去,业绩能搞上去,人,怎么样?**大家不妨一起聊聊看 :) 所以,我最终想说的是,把大家茶歇、酒会环节,私下交流最多的东西找出来,可以变成我们搬到台面上,认认真真探讨的话题。 这些,都是我由今年 D2 前端论坛活动所想到的。 当然最后,D2 也让我重逢了很多老朋友,结实了很多新朋友。期待与大家的再次见面。

2014/10/26
articleCard.readMore

[译]CSS命名神马的真心难

译自:Naming CSS Stuff Is Really Hard 找到的这篇文章算是对我之前写的 《标签?ID?还是CLASS?》 的再深入。我当时写那篇文章的时候,就有朋友提出了“非语义化”的 class 命名的问题,我当时确实觉得很纠结,简单的想法是“框架性质的表象 class 我没异议……框架的实质是通过降低灵活性达成更广泛的共识,我们个人不要再创造这样的样式就好了”,但没有想到特别好的“套路”,更多的是在实际情况中再分辨。看过这篇文章,我似乎找到了更好的答案。同时顺着文中提到的 Nicolas 那篇文章看下去,也对 OOCSS、BEM 之类的提法有了更多的认同感。特译给大家参考。 这并不是一篇有关 CSS 架构的文章,也并不是一篇有关命名规范的文章,而关乎我们如何定位元素,关乎命名本身,关乎我们如何把元素及其相关的一段样式连接起来。 10 个开发者里有 9 个都同意:在撰写 CSS 中命名什么的部分是最难的了。因为我们无法预知未来。一个 class 名可以在今天完美的应景,但是明天设计发生改变了,可能就不适用了。所以我们需要提炼应景的标记和样式。嗷~ 如何面对这一状况呢?那便是让命名尽量显得不太会改的样子。 我们通常会根据三类情况给定一个 class 名: 功能性 class 名 内容性 class 名 展示性 class 名 这几类 class 名是趋向于稳定特质的。如果我们遵循这些命名原则,就会显得更明智,而且我们的 CSS 会更好的适应未来的改变。 功能性 class 名 ​ <button class="positive-button">Send Message</button> 功能性 class 名例如 positive-button、important-text 或 selected-tab。这些元素的样式是基于其功能或含义的。所以其 class 名、样式及这样引用样式的理由,都是强连接的。因此 class 名和样式是相关的。 因为有这些强连接,所以样式是几乎不会被改变的。如果你真的要改变一个 positive-button 的样子,那这个改变也是每个肯定语气的按钮都要改变的。如果你的设计师的想法是改变“肯定语气的按钮”,而不是设置菜单里“增加用户”的按钮,那么这件事就很轻松且易于维护。你考虑的不是哪个独立的页面,而是整个系统。 功能性的 class 名很棒。只要有这个可能,这应该就是你想要撰写样式的方式。但是功能性的 class 名不是所有情况都适用的。 设计并不一定都有逻辑性。当我们讨论按钮的时候,给出一个功能性的 class 名是很容易的。大部分情况下其功能和样式也紧密相关。但是我们撰写的其它 99% 的样式都不太容易给出这样的例子。有的时候这个块区域需要一个内阴影,因为这样看起来很漂亮;有的时候图标需要在 hover 的时候长大一点因为这样很 cute;有的时候文本需要是橙色的,好吧,因为它就是橙色的。一个网站不是每个视觉的部分背后都有功能性的理由,这无可厚非。 所以开发者该怎么应对呢?我们站在了这个十字路口。如果我们不能想出一个合适的功能性 class 名,那么我们不妨基于内容、或者展现给它起个名字。它们也各自暗示着不同的可维护性。 内容性 class 名 ​ <button class="submit-button">Send Message</button> 基于内容的 class 名是描述它们包含的内容的 class 名。如果你曾经见到过类似 submit-button、intro-text 或 profile-photo 的 class 名,那些名字就是基于内容起的。 这些 class 名感觉很干净。它们让你的内容 (HTML) 和样式 (CSS) 之间保持简洁的分离。理论上,这可以让你完全改版网站的样式和感官而无需触碰到 HTML。CSS Zen Garden 就是这样的。 现在我们回到最初的问题:“这样做的好处是什么?” 我从来没有被要求改版一个网站而不触碰 HTML。变化是存在各种可能的。当然一些 HTML 可以作为后台系统的集成成果固定下来,但是随着你对 HTML 的失控,这时你还是需要写一些非理想化的 hacky CSS 来应付设计的变化。想想看,CSS Zen Garden 更多的是一个 CSS 技术演示而不是一个可维护的 CSS 的例子。没有必要一味追求在改版的时候只改 CSS。 当你开发一个小网站的时候,内容性的 class 名非常好用。而随着你的网站不断成长,它就感觉越来越不合适了。它们并不易于样式重用。如果你的 login-button 和 submit-button 看起来一样该怎么处理呢?在你的 CSS 架构里该如何展示这些东西?为保持展现样式块,你不得不写一堆用逗号分隔开的选择器,或者通过预处理器展开。这些组织方式对于大型的项目来说都比较困难。 除非有更好的方式重用样式块…… 展示性 class 名 ​ <button class="green-button">Send Message</button> 展示性 class 名用诸如 green-button、big-text 或 squiggle-border 的方式描述一个元素。其名字本身就是对样式的描述。 这些 class 是有助于代码复用的。它们不关心是否用在产品标题上还是名户名或页头。它们只知道这会让文字变大加粗。同时这样的方式还有一个好处是可以优雅的扩展。如你开发一个新组件的时候,你可以把现成的样式贴在你的新标签上。你无须担心在已有的架构中产生并适配新的样式,因为你使用的都是已有的样式。 展示性 class 名也非常易于自我描述。一个开发者在审查代码的时候,round-image 会比 profile-photo 更多的推断出这个元素的样子。 会有争议认为展示性 class 增加了维护成本。因为它模糊了标记和展现之间的界限,很多设计的改变都将会导致 HTML 的改变。如果你预见到了这方面的问题,那么请谨慎的使用。 ……但这不是语义化的! ​ 展示性 class 的名声并不好。尽管很多人回避它们因为它们“不是语义化的”,但这里是存在误区的,也被 Nicolas Gallagher 质疑。重要的是区分“语义化的 HTML”和“语义化的 class”。Nicolas 说的非常好: 撰写“语义化的 HTML”的原则是现代化、专业化的前端开发的基础。大部分的语义化都关乎现有的或预期的内容的本质…… ……不过并不是所有的语义化都需要源自内容的。Class 名可以是“非语义化的”。不管使用什么名字,它们都有意义,都有目的。Class 名的语义化是不同于那些 HTML 元素的。 如我写的这些,“非语义化”这个词下面是有红色波浪线的。非语义化的 class 名并不是问题。每个 class 名都有背后的意义。在你写 class 名时,不必刻意追求它是最“语义上适合的” class 名,而要创建为开发者和未来的你提供尽量多信息的 class 名。 总结 ​ 功能性 class 名通常是你的最佳选择。当你能够使用它们的时候就尽量使用。如果你无法提取出完全功能性的名字,可以考虑你的项目的本质及其发展。原则上,内容性 class 名更适合小型站点;而展示性 class 名更适合大型站点。 开发者会很在意这种用法。没有人希望一个项目变得难以维护,但是每个人都有不同的 想法通过 class 名来应对这些特殊情况。这时不妨思考一下我们使用的不同类型 class 名的本质,问问自己这样做是否更好的帮助你的项目达成目标。

2014/9/23
articleCard.readMore

[译]Git 分支的最佳实践

译自:A successful Git branching model» nvie.com 本文将展示我一年前在自己的项目中成功运用的开发模型。我一直打算把这些东西写出来,但总是没有抽出时间,现在终于写好了。这里介绍的不是任何项目的细节,而是有关分支的策略以及对发布的管理。 在我的演示中,所有的操作都是通过 git 完成的。 为什么选择 git ? ​ 为了了断 git 和中心源代码控制系统的比较和争论,请移步这里看看 链接1 链接2。作为一个开发者,我喜欢 git 超过其它任何现有的工具。Git 真正改变了开发者对于合并和分支的认识。在传统的 CVS/SVN 里,合并/分支总是有点令人害怕的(“注意合并冲突,它们会搞死你的”)。 但是 git 中的这些操作是如此的简单有效,它们真正作为你每天工作流程的一部分。比如,在 CVS/SVN 的书籍里,分支和合并总是最后一个章节的讨论重点(对于高级用户),而在每一本 git 的书里 链接1 链接2 链接3,这些内容已经被包含在第三章(基础)里了。 因为它的简单直接和重复性,分支和合并不再令人害怕。版本控制工具比其它任何东西都支持分支/合并。 有关工具就介绍到这里,我们现在进入开发模型这个正题。我要展现的模型本质上无外乎是一个流程的集合,每个团队成员都有必要遵守这些流程,来达到管理软件开发流程的目的。 分散但也集中 ​ 我们的分支模型中使用良好的代码库的设置方式,是围绕一个真实的中心代码库的。注意,这里的代码库仅仅被看做是一个中心代码库(因为 git 是 DVCS,即分散版本控制系统,从技术层面看,是没有所谓的中心代码库的)。我们习惯于把这个中心代码库命名为 origin,这同时也是所有 git 用户的习惯。 每一位开发者都向 origin 这个中心结点 pull 和 push。但是除此之外,每一位开发者也可以向其它结点 pull 改变形成子团队。比如,对于两个以上开发者同时开发一项大的新特性来说,为了不必过早向 origin 推送开发进度,这就非常有用。在上面的这个例子中,Alice 和 Bob、Alice 和 David、Clair 和 David 都是这样的子团队。 从技术角度,这无非意味着 Alice 定义一个名为 Bob 的 git remote,指向 Bob 的代码库,反之亦然。 主分支 ​ 该开发模型的核心基本和现有的模型是一样的。中心代码库永远维持着两个主要的分支: master develop 在 origin 上的 master 分支和每个 git 用户的保持一致。而和 master 分支并行的另一个分支叫做 develop。 我们认为 origin/master 是其 HEAD 源代码总是代表了生产环境准备就绪的状态的主分支。 我们认为 origin/develop 是其 HEAD 源代码总是代表了最后一次交付的可以赶上下一次发布的状态的主分支。有人也把它叫做“集成分支”。该源代码还被作为了 nightly build 自动化任务的来源。 每当 develop 分支到达一个稳定的阶段,可以对外发布时,所有的改变都会被合并到 master 分支,并打一个发布版本的 tag。具体操作方法我们稍后讨论。 因此,每次改动被合并到 master 的时候,这就是一个真正的新的发布产品。我们建议对此进行严格的控制,因此理论上我们可以为每次 master 分支的提交都挂一个钩子脚本,向生产环境自动化构建并发布我们的软件。 支持型分支 ​ 我们的开发模型里,紧接着 master 和 develop 主分支的,是多种多样的支持型分支。它们的目的是帮助团队成员并行处理每次追踪特性、准备发布、快速修复线上问题等开发任务。和之前的主分支不同,这些分支的生命周期都是有限的,它们最终都会被删除掉。 我们可能会用到的不同类型的分支有: feature 分支 release 分支 hotfix 分支 每一种分支都有一个特别的目的,并且有严格的规则,诸如哪些分支是它们的起始分支、哪些分支必须是它们合并的目标等。我们快速把它们过一遍。 这些“特殊”的分支在技术上是没有任何特殊的。分支的类型取决于我们如何运用它们。它们完完全全都是普通而又平凡的 git 分支。 feature 分支 ​ 可能派发自:develop 必须合并回:develop 分支命名规范:除了 master、develop、release-* 或 hotfix-* 的任何名字 Feature 分支(有时也被称作 topic 分支)用来开发包括即将发布或远期发布的新的特性。当我们开始开发一个特性的时候,发布合并的目标可能还不太确定。Feature 分支的生命周期会和新特性的开发周期保持同步,但是最终会合并回 develop (恩,下次发布的时候把这个新特性带上)或被抛弃(真是一次杯具的尝试啊)。 Feature 分支通常仅存在于开发者的代码库中,并不出现在 origin 里。 创建一个 feature 分支 ​ 当开始一个新特性的时候,从 develop 分支派发出一个分支 $ git checkout -b myfeature develop Switched to a new branch "myfeature" 把完成的特性合并回 develop ​ 完成的特性可以合并回 develop 分支并赶上下一次发布: $ git checkout develop Switched to a new branch "develop" $ git merge --no-ff myfeature Updating ea1b82a..05e9557 (Summary of changes) $ git branch -d myfeature Deleted branch myfeature (was 05e9557) $ git push origin develop -no-ff 标记使得合并操作总是产生一次新的提交,哪怕合并操作可以快速完成。这个标记避免将 feature 分支和团队协作的所有提交的历史信息混在主分支的其它提交之后。比较一下: 在右边的例子里,我们不可能从 git 的历史记录中看出来哪些提交实现了这一特性——你可能不得不查看每一笔提交日志。恢复一个完整的特性(比如通过一组提交)在右边变成了一个头疼事情,而如果使用了 --no-ff 之后,就变得简单了。 是的,这会创造一些没有必要的(空的)提交记录,但是得到的是大量的好处。 不幸的是,我还没有找到一个在 git merge 时默认就把 --no-ff 标记打上的办法,但这很重要。 release 分支 ​ 可能派发自:develop 必须合并回:develop 和 master 分支命名规范:release-* Release 分支用来支持新的生产环境发布的准备工作。允许在最后阶段产生提交点(dotting i's)和交汇点(crossing t's)。而且允许小幅度的问题修复以及准备发布时的meta数据(比如版本号、发布日期等)。在 release 分支做了上述这些工作之后,develop 分支会被“翻篇儿”,开始接收下一次发布的新特性。 我们选择(几近)完成所有预期的开发的时候,作为从 develop 派发出 release 分支的时机。最起码所有准备构建发布的功能都已经及时合并到了 develop 分支。而往后才会发布的功能则不应该合并到 develop 分支——他们必须等到 release 分支派发出去之后再做合并。 在一个 release 分支的开始,我们就赋予其一个明确的版本号。直到该分支创建之前,develop 分支上的描述都是“下一次”release 的改动,但这个“下一次”release 其实也没说清楚是 0.3 release 还是 1.0 release。而在一个 release 分支的开始时这一点就会确定。这将成为有关项目版本号晋升的一个守则。 创建一个 release 分支 ​ Release 分支派发自 develop 分支。比如,我们当前的生产环境发布的版本是 1.1.5,马上有一个 release 要发布了。develop 分支已经为“下一次”release 做好了准备,并且我们已经决定把新的版本号定为 1.2 (而不是 1.1.6 或 2.0)。所以我们派发一个 release 分支并以新的版本号为其命名: $ git checkout -b release-1.2 develop Switched to a new branch "release-1.2" $ ./bump-version.sh 1.2 Files modified successfully, version bumped to 1.2. $ git commit -a -m "Bumped version number to 1.2" [release-1.2 74d9424] Bumped version number to 1.2 1 files changed, 1 insertions(+), 1 deletions(-) 创建好并切换到新的分支之后,我们完成对版本号的晋升。这里的 bump-version.sh 是一个虚构的用来改变代码库中某些文件以反映新版本的 shell 脚本。(当然你也可以手动完成这些改变——重点是有些文件发生了改变)然后,晋升了的版本号会被提交。 这个新的分支会存在一段时间,直到它确实发布出去了为止。期间可能会有 bug 修复(这比在 develop 做更合理)。但我们严格禁止在此开发庞大的新特性,它们应该合并到 develop 分支,并放入下次发布。 完成一个 release 分支 ​ 当 release 分支真正发布成功之后,还有些事情需要收尾。首先,release 分支会被合并到 master (别忘了,master 上的每一次提交都代表一个真正的新的发布);然后,为 master 上的这次提交打一个 tag,以便作为版本历史的重要参考;最后,还要把 release 分支产生的改动合并回 develop,以便后续的发布同样包含对这些 bug 的修复。 前两部在 git 下是这样操作的: $ git checkout master Switched to branch 'master' $ git merge --no-ff release-1.2 Merge made by recursive (Summary of changes) $ git tag -a 1.2 现在发布工作已经完成了,同时 tag 也打好了,用在未来做参考。 补充:你也可以通过 -s 或 -u <key> 标记打 tag。 为了保留 release 分支里的改动记录,我们需要把这些改动合并回 develop。git 操作如下: $ git checkout develop Switched to branch 'develop' $ git merge --no-ff release-1.2 Merge made by recursive. (Summary of changes) 这一步有可能导致冲突的发生(只是有理论上的可能性,因为我们已经改变了版本号),一旦发现,解决冲突然后提交就好了。 现在我们真正完成了一个 release 分支,该把它删掉了,因为它的使命已经完成了: $ git branch -d release-1.2 Deleted branch release-1.2 (was ff452fe). hotfix 分支 ​ 可能派发自:master 必须合并回:develop 和 master 分支命名规范:hotfix-* Hotfix 分支和 release 分支非常类似,因为他们都意味着会产生一个新的生产环境的发布,尽管 hotfix 分支不是先前就计划好的。他们在实时的生产环境版本出现意外需要快速响应时,从 master 分支相应的 tag 被派发。 我们这样做的根本原因,是为了让团队其中一个人来快速修复生产环境的问题,其他成员可以按工作计划继续工作下去而不受太大影响。 创建一个 hotfix 分支 ​ Hotfix 分支创建自 master 分支。例如,假设 1.2 版本是目前的生产环境且出现了一个严重的 bug,但是目前的 develop 并不足够稳定。那么我们可以派发出一个 hotfix 分支来开始我们的修复工作: $ git checkout -b hotfix-1.2.1 master Switched to a new branch "hotfix-1.2.1" $ ./bump-version.sh 1.2.1 Files modified successfully, version bumped to 1.2.1. $ git commit -a -m "Bumped version number to 1.2.1" [hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1 1 files changed, 1 insertions(+), 1 deletions(-) 别忘了在派发出分支之后晋升版本号! 然后,修复 bug,提交改动。通过一个或多个提交都可以。 $ git commit -m "Fixed severe production problem" [hotfix-1.2.1 abbe5d6] Fixed severe production problem 5 files changed, 32 insertions(+), 17 deletions(-) 完成一个 hotfix 分支 ​ 当我们完成之后,对 bug 的修复需要合并回 master,同时也需要合并回 develop,以保证接下来的发布也都已经解决了这个 bug。这和 release 分支的完成方式是完全一样的。 首先,更新 master 并为本次发布打一个 tag: $ git checkout master Switched to branch 'master' $ git merge --no-ff hotfix-1.2.1 Merge made by recursive (Summary of changes) $ git tag -a 1.2.1 补充:你也可以通过 -s 或 -u <key> 标记打 tag。 然后,把已修复的 bug 合并到 develop: $ git checkout develop Switched to branch 'develop' $ git merge --no-ff hotfix-1.2.1 Merge made by recursive (Summary of changes) 这个规矩的一个额外之处是:如果此时已经存在了一个 release 分支,那么 hotfix 的改变需要合并到这个 release 分支,而不是 develop 分支。因为把对 bug 的修复合并回 release 分支之后,release 分支最终还是会合并回 develop 分支的。(如果在 develop 分支中立刻需要对这个 bug 的修复,且等不及 release 分支合并回来,则你还是可以直接合并回 develop 分支的,这是绝对没问题的) 最后,删掉这个临时的分支: $ git branch -d hotfix-1.2.1 Deleted branch hotfix-1.2.1 (was abbe5d6). 摘要 ​ 其实这个分支模型里没有什么新奇的东西。文章开头的那张大图对我们的项目来说非常有用。它非常易于团队成员理解这个优雅有效的模型,并在团队内部达成共识。 这里还有一份那张大图的 高清PDF版本,你可以把它当做手册放在手边快速浏览。 补充:还有,如果你们需要的话,这里还有一份 Keynote 版本

2014/1/1
articleCard.readMore

[译]撰写可测试的 JavaScript

译自:Writing Testable JavaScript - A List Apart 这篇文章算是 A List Apart 系列文章中,包括滑动门在内,令我印象最深刻的文章之一。最近有时间翻译了一下,分享给更多人,希望对大家有所帮助! 我们已经面对到了这一窘境:一开始我们写的 JavaScript 只有区区几行代码,但是它的代码量一直在增长,我们不断的加参数、加条件。最后,粗 bug 了…… 我们才不得不收拾这个烂摊子。 如上所述,今天的客户端代码确实承载了更多的责任,浏览器里的整个应用都越变越复杂。我们发现两个明显的趋势:1、我们没法通过单纯的鼠标定位和点击来检验代码是否正常工作,自动化的测试才会真正让我们放心;2、我们也许应该在撰写代码的时候就考虑到,让它变得可测试。 神马?我们需要改变自己的编码方式?是的。因为即使我们意识到自动化测试的好,大部分人可能只是写写集成测试(integration tests)罢了。集成测试的侧重点是让整个系统的每一部分和谐共存,但是这并没有告诉我们每个独立的功能单元运转起来是否都和我们预期的一样。 这就是为什么我们要引入单元测试。我们已经准备好经历一段痛苦的撰写单元测试的过程了,但最终我们能够撰写可测试的 JavaScript。 单元与集成:有什么不同? ​ 撰写集成测试通常是相当直接的:我们单纯的撰写代码,描述用户如何和这个应用进行交互、会得到怎样的结果就好。Selenium 是这类浏览器自动化工具中的佼佼者。而 Capybara 可以便于 Ruby 和 Selenium 取得联系。在其它语言中,这类工具也举不胜举。 下面就是搜索应用的一部分集成测试: def test_search fill_in('q', :with => 'cat') find('.btn').click assert( find('#results li').has_content?('cat'), 'Search results are shown' ) assert( page.has_no_selector?('#results li.no-results'), 'No results is not shown' ) end 集成测试对用户的交互行为感兴趣,而单元测试往往仅专注于一小段代码: 当我伴随特定的输入调用一个函数的时候,我是否收到了我预期中的结果? 我们按照传统思路撰写的程序是很难进行单元测试的,同时也很难维护、调试和扩展。但是如果我们在撰写代码的时候就考虑到我将来要做单元测试,那么这样的思路不仅会让我们发现测试代码写起来很直接,也会让我们真正写出更优质的代码。 我们通过一个简单的搜索应用的例子来做个示范: 当用户搜索时,该应用会向服务器发送一个 XHR (Ajax 请求) 取得相应的搜索结果。并当服务器以 JSON 格式返回数据之后,通过前端模板把结果显示在页面中。用户在搜索结果中点“赞”,这个人的名字就会出现在右侧的点“赞”列表里。 一个“传统”的 JavaScript 实现大概是这个样子的: // 模板缓存,缓存的内容均为 jqXHR 对象 var tmplCache = {}; /** * 载入模板 * 从 '/templates/{name}' 载入模板,存入 tmplCache * @param {string} name 模板名称 * @return {object} 模板请求的 jqXHR 对象 */ function loadTemplate (name) { if (!tmplCache[name]) { tmplCache[name] = $.get('/templates/' + name); } return tmplCache[name]; } /** * 页面主要逻辑 * 1. 支持搜索行为并展示结果 * 2. 支持点“赞”,被赞过的人会出现在点“赞”列表里 */ $(function () { var resultsList = $('#results'); var liked = $('#liked'); var pending = false; // 用来标识之前的搜索是否尚未结束 // 用户搜索行为,表单提交事件 $('#searchForm').on('submit', function (e) { // 屏蔽默认表单事件 e.preventDefault(); // 如果之前的搜索尚未结束,则不开始新的搜索 if (pending) { return; } // 得到要搜索的关键字 var form = $(this); var query = $.trim( form.find('input[name="q"]').val() ); // 如果搜索关键字为空则不进行搜索 if (!query) { return; } // 开始新的搜索 pending = true; // 发送 XHR $.ajax('/data/search.json', { data : { q: query }, dataType : 'json', success : function (data) { // 得到 people-detailed 模板 loadTemplate('people-detailed.tmpl').then(function (t) { var tmpl = _.template(t); // 通过模板渲染搜索结果 resultsList.html( tmpl({ people : data.results }) ); // 结束本次搜索 pending = false; }); } }); // 在得到服务器响应之前,清空搜索结果,并出现等待提示 $('<li>', { 'class' : 'pending', html : 'Searching &hellip;' }).appendTo( resultsList.empty() ); }); // 绑定点“赞”的行为,鼠标点击事件 resultsList.on('click', '.like', function (e) { // 屏蔽默认点击事件 e.preventDefault(); // 找到当前人的名字 var name = $(this).closest('li').find('h2').text(); // 清除点“赞”列表的占位元素 liked.find('.no-results').remove(); // 在点“赞”列表加入新的项目 $('<li>', { text: name }).appendTo(liked); }); }); 我的朋友 Adam Sontag 称之为*“自己给自己挖坑”的代码:展现、数据、用户交互、应用状态全部分散在了每一行代码里。这种代码是很容易进行集成测试的,但几乎不可能针对功能单元*进行单独的测试。 单元测试为什么这么难?有四大罪魁祸首: 没有清晰的结构。几乎所有的工作都是在 $(document).ready() 回调里进行的,而这一切在一个匿名函数里,它在测试中无法暴露出任何接口。 函数太复杂。如果一个函数超过了 10 行,比如提交表单的那个函数,估计大家都觉得它太忙了,一口气做了很多事。 隐藏状态还是共享状态。比如,因为 pending 在一个闭包里,所以我们没有办法测试在每个步骤中这个状态是否正确。 强耦合。比如这里 $.ajax 成功的回调函数不应该依赖 DOM 操作。 组织我们的代码 ​ 首当其冲的是把我们代码的逻辑缕一缕,根据职责的不同把整段代码分为几个方面: 展现和交互 数据管理和保存 应用的状态 把上述代码建立并串连起来 在之前的“传统”实现里,这四类代码是混在一起的,前一行我们还在处理界面展现,后两行就在和服务器通信了。 我们绝对可以写出集成测试的代码,但我们应该很难写出单元测试了。在功能测试里,我们可以做出诸如“当用户搜索东西的时候,他会看到相应的搜索结果”的断言,但是无法再具体下去了。如果里面出了什么问题,我们还是得追踪进去,找到确切的出错位置。这样的话功能测试其实也没帮上什么忙。 如果我们反思自己的代码,那不妨从单元测试写起,通过单元测试这个角度,更好的观察,是哪里出了问题。这进而会帮助我们改进代码,让代码变得更易于重用、易于维护、易于扩展。 我们的新版代码遵循下面几个原则: 根据上述四类职责,列出每个互不相干的行为,并分别用一个对象来表示。对象之前互不依赖,以避免不同的代码混在一起。 用可配置的内容代替写死的内容,以避免我们为了测试而复刻整个 HTML 环境。 保持对象方法的简单明了。这会把测试工作变得简单易懂。 通过构造函数创建对象实例。这让我们可以根据测试的需要复刻每一段代码的内容。 作为起步,我们有必要搞清楚,该如何把应用分解成不同的部分。我们有三块展现和交互的内容:搜索框、搜索结果和点“赞”列表。 我们还有一块内容是从服务器获取数据的、一块内容是把所有的内容粘合在一起的。 我们从整个应用最简单的一部分开始吧:点“赞”列表。在原版应用中,这部分代码的职责就是更新点“赞”列表: var liked = $('#liked'); var resultsList = $('#results'); // ... resultsList.on('click', '.like', function (e) { e.preventDefault(); var name = $(this).closest('li').find('h2').text(); liked.find( '.no-results' ).remove(); $('<li>', { text: name }).appendTo(liked); }); 搜索结果这部分是完全和点“赞”列表搅在一起的,并且需要很多 DOM 处理。更好的易于测试的写法是创建一个点“赞”列表的对象,它的职责就是封装点“赞”列表的 DOM 操作。 var Likes = function (el) { this.el = $(el); return this; }; Likes.prototype.add = function (name) { this.el.find('.no-results').remove(); $('<li>', { text: name }).appendTo(this.el); }; 这段代码提供了创建一个点“赞”列表对象的构造函数。它有 .add() 方法,可以在产生新的赞的时候使用。这样我们就可以写很多测试代码来保障它的正常工作了: var ul; // 设置测试的初始状态:生成一个搜索结果列表 setup(function(){ ul = $('<ul><li class="no-results"></li></ul>'); }); test('测试构造函数', function () { var l = new Likes(ul); // 断言对象存在 assert(l); }); test('点一个“赞”', function () { var l = new Likes(ul); l.add('Brendan Eich'); // 断言列表长度为1 assert.equal(ul.find('li').length, 1); // 断言列表第一个元素的 HTML 代码是 'Brendan Eich' assert.equal(ul.find('li').first().html(), 'Brendan Eich'); // 断言占位元素已经不存在了 assert.equal(ul.find('li.no-results').length, 0); }); 怎么样?并不难吧 😃 我们这里用到了名为 Mocha 的测试框架,以及名为 Chai 的断言库。Mocha 提供了 test 和 setup 函数;而 Chai 提供了 assert。测试框架和断言库的选择还有很多,我们出于介绍的目的给大家展示这两款。你可以找到属于适合自己的项目——除了 Mocha 之外,QUnit 也比较流行。另外 Intern 也是一个测试框架,它运用了大量的 promise 方式。 我们的测试代码是从点“赞”列表这一容器开始的。然后它运行了两个测试:一个是确定点“赞”列表是存在的;另一个是确保 .add() 方法达到了我们预期的效果。有这些测试做后盾,我们就可以放心重构点“赞”列表这部分的代码了,即使代码被破坏了,我们也有信心把它修复好。 我们新应用的代码现在看起来是这样的: var liked = new Likes('#liked'); // 新的点“赞”列表对象 var resultsList = $('#results'); // ... resultsList.on('click', '.like', function (e) { e.preventDefault(); var name = $(this).closest('li').find('h2').text(); liked.add(name); // 新的点“赞”操作的封装 }); 搜索结果这部分比点“赞”列表更复杂一些,不过我们也该拿它开刀了。和我们为点“赞”列表创建一个 .add() 方法一样,我们要创建一个与搜索结果有交互的方法。我们需要一个点“赞”的入口,向整个应用“广播”自己发生了什么变化——比如有人点了个“赞”。 // 为每一条搜索结果的点“赞”按钮绑定点击事件 var SearchResults = function (el) { this.el = $(el); this.el.on( 'click', '.btn.like', _.bind(this._handleClick, this) ); }; // 展示搜索结果,获取模板,然后渲染 SearchResults.prototype.setResults = function (results) { var templateRequest = $.get('people-detailed.tmpl'); templateRequest.then( _.bind(this._populate, this, results) ); }; // 处理点“赞” SearchResults.prototype._handleClick = function (evt) { var name = $(evt.target).closest('li.result').attr('data-name'); $(document).trigger('like', [ name ]); }; // 对模板渲染数据的封装 SearchResults.prototype._populate = function (results, tmpl) { var html = _.template(tmpl, { people: results }); this.el.html(html); }; 现在我们旧版应用中管理搜索结果和点“赞”列表之间交互的代码如下: var liked = new Likes('#liked'); var resultsList = new SearchResults('#results'); // ... $(document).on('like', function (evt, name) { liked.add(name); }) 这就更简单更清晰了,因为我们通过 document 在各个独立的组件之间进行消息传递,而组件之间是互不依赖的。(值得注意的是,在真正的应用当中,我们会使用一些诸如 Backbone 或 RSVP 库来管理事件。我们出于让例子尽量简单的考虑,使用了 document 来触发事件) 我们同时隐藏了很多脏活累活:比如在搜索结果对象里寻找被点“赞”的人,要比放在整个应用的代码里更好。更重要的是,我们现在可以写出保障搜索结果对象正常工作的测试代码了: var ul; var data = [ /* 填入假数据 */ ]; // 确保点“赞”列表存在 setup(function () { ul = $('<ul><li class="no-results"></li></ul>'); }); test('测试构造函数', function () { var sr = new SearchResults(ul); // 断言对象存在 assert(sr); }); test('测试收到的搜索结果', function () { var sr = new SearchResults(ul); sr.setResults(data); // 断言搜索结果占位元素已经不存在 assert.equal(ul.find('.no-results').length, 0); // 断言搜索结果的子元素个数和搜索结果的个数相同 assert.equal(ul.find('li.result').length, data.length); // 断言搜索结果的第一个子元素的 'data-name' 的值和第一个搜索结果相同 assert.equal( ul.find('li.result').first().attr('data-name'), data[0].name ); }); test('测试点“赞”按钮', function() { var sr = new SearchResults(ul); var flag; var spy = function () { flag = [].slice.call(arguments); }; sr.setResults(data); $(document).on('like', spy); ul.find('li').first().find('.like.btn').click(); // 断言 `document` 收到了点“赞”的消息 assert(flag, '事件被收到了'); // 断言 `document` 收到的点“赞”消息,其中的名字是第一个搜索结果 assert.equal(flag[1], data[0].name, '事件里的数据被收到了' ); }); 和服务器直接的交互是另外一个有趣的话题。原版的代码包括一个 $.ajax() 的请求,以及一个直接操作 DOM 的回调函数: $.ajax('/data/search.json', { data : { q: query }, dataType : 'json', success : function( data ) { loadTemplate('people-detailed.tmpl').then(function(t) { var tmpl = _.template( t ); resultsList.html( tmpl({ people : data.results }) ); pending = false; }); } }); 同样,我们很难为这样的代码撰写测试。因为很多不同的工作同时发生在这一小段代码中。我们可以重新组织一下数据处理的部分: var SearchData = function () { }; SearchData.prototype.fetch = function (query) { var dfd; // 如果搜索关键字为空,则不做任何事,立刻 `promise()` if (!query) { dfd = $.Deferred(); dfd.resolve([]); return dfd.promise(); } // 否则,向服务器请求搜索结果并把在得到结果之后对其数据进行包装 return $.ajax( '/data/search.json', { data : { q: query }, dataType : 'json' }).pipe(function( resp ) { return resp.results; }); }; 现在我们改变了获得搜索结果这部分的代码: var resultList = new SearchResults('#results'); var searchData = new SearchData(); // ... searchData.fetch(query).then(resultList.setResults); 我们再一次简化了代码,并通过 SearchData 对象抛弃了之前应用程序主函数里杂乱的代码。同时我们已经让搜索接口变得可测试了,尽管现在和服务器通信这里还有事情要做。 首先我们不是真的要跟服务器通信——不然这又变成集成测试了:诸如我们是有责任感的开发者,我们已经确保服务器一定不会犯错等等,是这样吗?为了替代这些东西,我们应该“mock”(伪造) 与服务器之间的通信。Sinon 这个库就可以做这件事。第二个障碍是我们的测试应该覆盖非理想环境,比如关键字为空。 test('测试构造函数', function () { var sd = new SearchData(); assert(sd); }); suite('取数据', function () { var xhr, requests; setup(function () { requests = []; xhr = sinon.useFakeXMLHttpRequest(); xhr.onCreate = function (req) { requests.push(req); }; }); teardown(function () { xhr.restore(); }); test('通过正确的 URL 获取数据', function () { var sd = new SearchData(); sd.fetch('cat'); assert.equal(requests[0].url, '/data/search.json?q=cat'); }); test('返回一个 promise', function () { var sd = new SearchData(); var req = sd.fetch('cat'); assert.isFunction(req.then); }); test('如果关键字为空则不查询', function () { var sd = new SearchData(); var req = sd.fetch(); assert.equal(requests.length, 0); }); test('如果关键字为空也会有 promise', function () { var sd = new SearchData(); var req = sd.fetch(); assert.isFunction( req.then ); }); test('关键字为空的 promise 会返回一个空数组', function () { var sd = new SearchData(); var req = sd.fetch(); var spy = sinon.spy(); req.then(spy); assert.deepEqual(spy.args[0][0], []); }); test('返回与搜索结果相对应的对象', function () { var sd = new SearchData(); var req = sd.fetch('cat'); var spy = sinon.spy(); requests[0].respond( 200, { 'Content-type': 'text/json' }, JSON.stringify({ results: [ 1, 2, 3 ] }) ); req.then(spy); assert.deepEqual(spy.args[0][0], [ 1, 2, 3 ]); }); }); 出于篇幅的考虑,这里对搜索框的重构及其相关的单元测试就不一一介绍了。完整的代码可以移步至此查阅。 当我们按照可测试的 JavaScript 的思路重构代码之后,我们最后用下面这段代码开启程序: $(function() { var pending = false; var searchForm = new SearchForm('#searchForm'); var searchResults = new SearchResults('#results'); var likes = new Likes('#liked'); var searchData = new SearchData(); $(document).on('search', function (event, query) { if (pending) { return; } pending = true; searchData.fetch(query).then(function (results) { searchResults.setResults(results); pending = false; }); searchResults.pending(); }); $(document).on('like', function (evt, name) { likes.add(name); }); }); 比干净整洁的代码更重要的,是我们的代码拥有了更健壮的测试基础作为后盾。这也意味着我们可以放心的重构任意部分的代码而不必担心程序遭到破坏。我们还可以继续为新功能撰写新的测试代码,并确保新的程序可以通过所有的测试。 测试会在宏观上让你变轻松 ​ 看完这些的长篇大论你一定会说:“纳尼?我多写了这么多代码,结果还是做了这么一点事情?” 关键在于,你做的东西早晚要放到网上的。同样是花时间解决问题,你会选择在浏览器里点来点去?还是自动化测试?还是直接在线上让你的用户做你的小白鼠?无论你写了多少测试,你写好代码,别人一用,多少会发现点 bug。 至于测试,它可能会花掉你一些额外的时间,但是它到最后真的是为你省下了时间。写测试代码测出一个问题,总比你发布到线上之后才发现有问题要好。如果有一个系统能让你意识到它真的能避免一个 bug 的流出,你一定会心存感激。 额外的资源 ​ 这篇文章只能算是 JavaScript 测试的一点皮毛,但是如果你对此抱有兴趣,那么可以继续移步至: 幻灯演示 2012 Full Frontal conference in Brighton, UK Grunt 一个可以进行自动化测试等诸多事情的工具 测试驱动的 JavaScript 开发 及其 中文版

2013/12/11
articleCard.readMore

[译]语义化版本管理

译自:语义化版本管理 2.0.0 摘要 ​ 对于一个给定的版本号 MAJOR.MINOR.PATCH (主、次、补丁),其变化的规律是: MAJOR version (主版本) 会在 API 发生不可向下兼容的改变时增大。 MINOR version (次版本) 会在有向下兼容的新功能加入时增大。 PATCH version (补丁版本) 会在bug以向下兼容的方式被修复时增大。 我们还可以根据预发布、构建元数据 (build metadata) 的实际需求,在 MAJOR.MINOR.PATCH 格式之上扩展出额外的标记。 介绍 ​ 在软件管理领域,存在一个叫做“dependency hell (依赖地狱)”的坑。随着系统越变越大,你集成了越多的软件包,也越发觉得,有一天,你会陷入绝望。 对于有很多依赖关系的系统来说,发布新版本的软件包会迅速变成一场噩梦。如果依赖性规定得太紧,你会陷入 version lock (版本锁,即每次软件包的升级无法产生新的版本)。如果依赖性规定得太松,你会不可避免的面对 version promiscuity (版本泛滥,假设未来版本是需要考虑兼容性的)。当 version lock 和 version promiscuity 让你的项目无法安全而又轻松的向前推进时,这就是所谓的 dependency hell。 作为一种解决问题的办法,我提出了一套简单的规则和要求来表明版本号该如何确定和增加。这套规则基于但不仅限用于已经广泛存在的开源闭源软件的一般实践。为了让这个系统工作起来,你首先需要声明一个公有的 API,它可以由文档组成或在代码层面强制实现,且必须是清晰准确的。一旦你标识了你的公有 API,你就可以通过不同的版本号的增加来交流 API 的各种改变。设想一个形如 X.Y.Z 的版本,不影响 API 的 bug 修复会增大补丁版本,向下兼容的 API 增加或改变会增大次版本,而不兼容的 API 改变会增大主版本。 我把这套系统称作“语义化版本管理”。在这套系统之下,版本号及其改变传递了代码背后的含义,以及每个相邻版本之间的变化。 语义化版本管理规范 (SemVer) ​ 原文中的关键字 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" (必须、禁止、要求、应该、不应该、推荐、可以、可选的) 在 RFC 2119 中有相应的解释和描述。 使用语义化版本管理的软件必须声明一个公有 API。该 API 可声明于代码或文档中,并且精确而全面。 一个普通的版本号必须遵循 X.Y.Z 的格式,其中 X、Y、Z 都是非负整数,禁止包含前置的 0。X 是主版本,Y 是次版本,Z 是补丁版本。每个元素必须以数字形式变大。比如:1.9.0 -> 1.10.0 -> 1.11.0。 一旦版本化的软件包被发布,那么该版本的内容就禁止被更改了。今后的任何变化都必须通过新版本的发布而产生。 主版本 0 (0.Y.Z) 表示初始开发阶段。在这个阶段任何事情都是可以更改的,公有 API 不应该被认为是稳定的。 1.0.0 版本定义了公有 API。从该版本往后,版本号的增加取决于发布的公有 API 及其变化。 只有当向下兼容的 bug 修复被引入时,补丁版本 Z (x.y.Z | x > 0) 必须被增大——bug 修复的定义是通过内部改变修复错误的特性。 如果新的向下兼容的功能被引入公有 API、如果任何公有 API 功能被废弃,次版本 Y (x.Y.z | x > 0) 必须被增大。如果显著的新功能或改进通过私有代码被引入,次版本可以被增大。次版本的被增大可以包括补丁级别的变化。当次版本被增大时,补丁版本必须被重置为 0。 如果任何不向下兼容的变化被引入时,主版本 X (X.y.z | X > 0) 必须被增大。主版本的被增大可以包括次级别和补丁级别的变化,当主版本被增大时,次版本和补丁版本必须被重置为 0。 一个预发布版本可以表示为在补丁版本之后加上一个连字符,再加上一系列由点分隔开的标识符。标识符必须仅由 ACSII 字母数字和连字符组成 [0-9A-Za-z-]。标识符禁止为空。数字标识符禁止有前置的 0。预发布版本比相应的普通版本重要性更低。一个预发布版本意味着该版本不稳定,也许没有满足相应版本既定的兼容性需求。比如:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。 构建元数据可以表示为在补丁版本或预发布版本之后加上一个加号,再加上一系列由点分隔开的标识符。标识符必须仅由 ASCII 字母数字和连字符组成 [0-9A-Za-z-]。标识符禁止为空。构建元数据应该在决定版本重要性的时候被忽略。也就是说,如果两个版本只有构建元数据不一样,那么它们的重要性是一样的。比如:1.0.0-alpha+01、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。 重要性用于版本之间在排序时的比较。重要性的计算必须分别通过主、次、补丁、预发布标识符进行 (构建元数据并不参与重要性比较)。重要性取决于下面从左到右依次比较时出现的第一个不一样的标识符:主、次、补丁版本,都是数字形式的比较。比如:1.0.0 > 2.0.0 > 2.1.0 > 2.1.1。当主、次、补丁都相同的时候,预发布版本比普通版本重要性低。比如:1.0.0-alpha < 1.0.0。两个主、次、补丁版本相同的预发布版本之间的重要性必须取决于比较每个用点分隔出的标识符,按如下形式从左到右比较出的第一个不同的标识符:纯数字的标识符用数字形式进行比较,带有字母或连字符的标识符用 ASCII 文本形式进行比较。数字标识符重要性低于非数字标识符。如果公有的字段都相同,则字段多的预发布版本重要性高于字段少的。比如:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc。 为什么要使用语义化版本管理? ​ 这并不是什么新的或革命性的东西。事实上你的做事习惯可能已经很接近它了。但问题是“接近”是不够的。如果不接受一些正式的规范,版本号对依赖性管理是没有实际意义的。基于上述想法,给定名词和定义,它就变得易于交流。一旦这些意图变得清晰,灵活 (且不过分的) 的依赖性规范就会最终产生。 一个简单的例子就可以向人们展示语义化版本管理会使依赖地狱成为历史。想像一个叫做“消防车”的库,它需要一个名叫“梯子”的经过语义化版本管理的软件包。当消防车被创建时,梯子的版本是 3.1.0。因为消防车使用一些在 3.1.0 被首次引入的功能,你可以安全的制定梯子的依赖关系为大于 3.1.0 且小于 4.0.0。现在当梯子的版本 3.1.1 和 3.2.0 可用时,你可以把它们发布到你的软件包管理系统之中并很清楚它们可以和现存的依赖性软件和平共处。 作为一个有责任感的开发者,你一定想要验证任何被公示的软件包的功能升级。在现实世界中这是一个混乱的地方,我们对此需要警惕但又无能为力。我们能做的就是让语义化版本管理提供给你一个清晰的路线,去发布和升级软件包,无需在依赖性软件包的不同版本中翻滚,省去你的时间和烦恼。 如果这一切是你所渴望的,你需要做的就是声明你开始遵循上述规则来进行语义化的版本管理。在你的 README 中附带这个网站的链接,让其他人了解这个规则,并从中获益。 问答时间 ​ 我应该如何处理 0.y.z 的初始化开发阶段呢? 最简单的事情就是当你开始初始化开发时,从 0.1.0 开始,然后在后续的发布过程中不断增加次版本。 我怎么知道什么时候发布 1.0.0? 如果你的软件已经用到了产品环境,它可能应该已经是 1.0.0 了。如果你有一个稳定的用户依赖的 API,它应该是 1.0.0 了。如果你担心很多向下兼容的问题,它可能应该已经是 1.0.0 了。 这和快速开发快速迭代的理念是否有冲突? 主版本 0 是快速开发时期。如果你每天都在改变 API 你应该还处在 0.y.z 或一个独立的开发分支中,这是为下一个主版本服务的。 如果最小的向下不兼容的公有 API 改变都会导致新的主版本,那么我岂不是很快就做到 42.0.0 了? 这是一个开发责任感和长远意识的问题。不兼容的改变不应该很轻松就被引入到一个被大量依赖的软件当中。这必定导致升级的代价昂贵。改变主版本才可以发布不兼容的改变也在变相的促使你思考这一变化带来的影响及其投入产出比。 写出整个 API 的文档是一项艰巨的工作! 作为一名开发者,撰写软件文档以供其他人使用是你的责任。管理软件复杂度是保障一个项目高效运作的及其重要的部分,如果没人知道如何使用你的软件,什么方法使用起来比较安全,项目将会变得很困难。长期来看,语义化版本管理以及一个被良好定义的公有 API 能够保障每个人每件事都运转顺利。 如果我在此版本中不小心发布了一个不向下兼容的改变,我该怎么办? 当你意识到你打破了语义化版本管理规范之后,立即修复这个问题并且发布一个新的次版本去修复此问题并恢复向下兼容性。甚至在这个周期里,不要接受任何其它版本的发布。如果方便合适的话,记录下出错的版本并向你的用户告知这一问题以便他们警惕这个出错的版本。 如果我更新了我自己的依赖性但是没有改变公有 API,我该怎么做? 这回被认为是兼容的,因为它并没有影响到公有 API。软件显式依赖你的软件包相同的依赖,应该有自身的以来规范,作者自然会注意任何冲突。决定这个改变是一个补丁级别还是次级别,取决于你把依赖关系的改变用在了修复一个bug上还是用在了引入新功能上。我通常会在后期期待额外的代码,很明显这是一个次级别的增大。 如果我不经意改变了公有 API,而且它并没有遵循版本号的改变 (比如把补丁发布引入到了一个错误的主版本),我该怎么办? 做出做合理的判断。如果你有一大群用户,因为有意把行为改回到公有 API 会收到剧烈的影响,那么最好发布一个主版本,尽管实际的改动也许只是发布一个补丁。记住,语义化版本管理就是通过版本号的变化传递信息。如果这些改变对用户很重要,就用版本号去通知他们。 我该如何处理废弃的功能? 废弃已存在的功能是软件开发的一个正常部分。而且它经常需要提前行动。当你废弃部分你的公有 API 时,你应该做两件事:(1) 更新你的文档,让用户知道这一变化,(2) 创建一个此版本发布的任务。当你发布主版本完全移除功能之前,至少要有一个次版本发布,该发布包含废弃的动作,以便让用户可以平稳的过度到新的 API。 语义化版本管理是否存在版本字符串的长度限制? 没有限制,但要合理使用。比如一个 255 字符的版本字符串就算是有点长了。同样的,规范系统可以实行自己的字符串长度限制。 关于 ​ 语义化版本管理规范由 Gravatars 发明者、Github 的联合创始人 Tom Preston-Werner 撰写。 如果你想留下宝贵意见,请来 Github 开一个 issue 吧。 License ​ Creative Commons - CC BY 3.0

2013/12/1
articleCard.readMore

[译]通过HTML5 Canvas API调节图像的亮度和颜色

译自:Adjusting Image Brightness and Color Using the HTML5 Canvas API 你曾否需要调节一张图片的亮度?或者增强红色通道让它变得温暖一些? 这是我之前两篇文章“如何通过HTML5 Canvas处理图片酷效”和“如何创建一个HTML5的大头贴应用”的后续。在之前的那些文章里,我提供了一些可分离的颜色滤镜代码:灰度、灰褐色、红色、变亮、变暗等。这些滤镜都是经典的颜色滤镜,每个像素点的颜色都是独立运算的,互不影响。我们的可以将其建模成一个单独数据驱动的称为颜色矩阵滤镜(Color Matrix Filter)的东西。这一概念将会遍布本文。这种滤镜将会以一个包含权重(即系数)的颜色矩阵作为输入,并决定输出的颜色组件(color component)如何和输入的颜色组建相对应。 这个应用实例允许你在一个表格里编辑颜色矩阵,并立即把矩阵应用到当前加载的图片中。下图的表格展示了灰褐色滤镜的矩阵: 通过这个例子,每个像素的新红色组件r’都将会根据给定的r、g、b、a进行如下计算: r' = 0.393r + 0.769g + 0.189b + 0 作为每个颜色组件值额外的系数i被加载了表格的最后,用来变量或变暗最终的计算值。同理新的g’、b’、a’也进行相似的计算。下面的代码展示了等同于灰褐色颜色矩阵的JavaScript数组: var sepiaMatrix = [ 0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0, ]; 下面这段代码展示了等同于灰度特效矩阵的JavaScript数组: var grayscaleMatrix = [ 0.33, 0.34, 0.33, 0, 0, 0.33, 0.34, 0.33, 0, 0, 0.33, 0.34, 0.33, 0, 0, 0, 0, 0, 1, 0, ]; 颜色矩阵滤镜的代码如下: colorMatrixFilter = function (pixels, m) { var d = pixels.data; for (var i = 0; i < d.length; i += 4) { var r = d[i]; var g = d[i + 1]; var b = d[i + 2]; var a = d[i + 3]; d[i] = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4]; d[i+1] = r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9]; d[i+2] = r * m[10]+ g * m[11]+ b * m[12]+ a * m[13]+ m[14]; d[i+3] = r * m[15]+ g * m[16]+ b * m[17]+ a * m[18]+ m[19]; } return pixels; }; 我希望你已经乐在其中了。颜色矩阵提供了一个应用颜色滤镜的强大通用工具。 demo & 源码

2013/10/11
articleCard.readMore

[译]JavaScript V8性能小贴士

译自:Performance Tips for JavaScript in V8 简介 ​ 关于如何巧妙提高V8 JavaScript性能的话题,Daniel Clifford在Google I/O上做了一次非常精彩的分享。Daniel鼓励我们“追求更快”,认真的分析C++和JavaScript之间的性能差距,根据JavaScript的工作原理撰写代码。在Daniel的分享中,有一个核心要点的归纳,我们也会根据性能指导的变化保持对这篇文章的更新。 最重要的建议 ​ 最重要的是要把任何性能建议放在特定的情境当中。性能建议是附加的东西,有时一开始就特别注意深层的建议反而会对我们造成干扰。你需要从一个综合的角度看待你的Web应用的性能——在关注这些性能建议之前,你应该找PageSpeed之类的工具大概分析一下你的代码,也算是跑个分先。这会防止你过度优化。 对Web应用的性能优化,几个原则性的建议是: 首先,未雨绸缪 然后,找到症结 最后,修复它 为了完成这几个步骤,理解V8如何优化JS是一件很重要的事情,这样你就可以根据其对JS运行时的设计撰写代码。同样重要的是掌握一些帮得上忙的工具。Daniel也交代了一些开发者工具的用法,它们刚好抓住了一些V8引擎设计上最重要的部分。 OK。开始V8小贴士。 隐藏类 ​ JavaScript限制编译时的类型信息:类型可以在运行时被改变,可想而知这导致JS类型在编译时代价昂贵。那么你一定会问:JavaScript的性能有机会和C++相提并论吗?尽管如此,V8在运行时隐藏了内部创建对象的类型,隐藏类相同的对象可以使用相同的生成码以达到优化的目的。 比如: function Point(x, y) { this.x = x; this.y = y; } var p1 = new Point(11, 22); var p2 = new Point(33, 44); // At this point, p1 and p2 have a shared hidden class // 这里的p1和p2拥有共享的隐藏类 p2.z = 55; // warning! p1 and p2 now have different hidden classes! // 注意!这时p1和p2的隐藏类已经不同了! 在我们为p2添加“z”这个成员之前,p1和p2一直共享相同的内部隐藏类——所以V8可以生成一段单独版本的优化汇编码,这段代码可以同时封装p1和p2的JavaScript代码。我们越避免隐藏类的派生,就会获得越高的性能。 结论 ​ 在构造函数里初始化所有对象的成员(所以这些实例之后不会改变其隐藏类) 总是以相同的次序初始化对象成员 数字 ​ 当类型可以改变时,V8使用标记来高效的标识其值。V8通过其值来推断你会以什么类型的数字来对待它。因为这些类型可以动态改变,所以一旦V8完成了推断,就会通过标记高效完成值的标识。不过有的时候改变类型标记还是比较消耗性能的,我们最好保持数字的类型始终不变。通常标识为有符号的31位整数是最优的。 比如: var i = 42; // 这是一个31位有符号整数 var j = 4.2; // 这是一个双精度浮点数 结论 ​ 尽量使用可以用31位有符号整数表示的数。 数组 ​ 为了掌控大而稀疏的数组,V8内部有两种数组存储方式: 快速元素:对于紧凑型关键字集合,进行线性存储 字典元素:对于其它情况,使用哈希表 最好别导致数组存储方式在两者之间切换。 结论 ​ 使用从0开始连续的数组关键字 别预分配大数组(比如大于64K个元素)到其最大尺寸,令其尺寸顺其自然发展就好 别删除数组里的元素,尤其是数字数组 别加载未初始化或已删除的元素: a = new Array(); for (var b = 0; b < 10; b++) { a[0] |= b; // 杯具! } //vs. a = new Array(); a[0] = 0; for (var b = 0; b < 10; b++) { a[0] |= b; // 比上面快2倍 } 同样的,双精度数组会更快——数组的隐藏类会根据元素类型而定,而只包含双精度的数组会被拆箱(unbox),这导致隐藏类的变化。对数组不经意的封装就可能因为装箱/拆箱(boxing/unboxing)而导致额外的开销。比如: var a = new Array(); a[0] = 77; // 分配 a[1] = 88; a[2] = 0.5; // 分配,转换 a[3] = true; // 分配,转换 下面的写法效率更高: var a = [77, 88, 0.5, true]; 因为第一个例子是一个一个分配赋值的,并且对a[2]的赋值导致数组被拆箱为了双精度。但是对a[3]的赋值又将数组重新装箱回了任意值(数字或对象)。第二种写法时,编译器一次性知道了所有元素的字面上的类型,隐藏隐藏类可以直接确定。 结论 ​ 初始化小额定长数组时,用字面量进行初始化 小数组(小于64k)在使用之前先预分配正确的尺寸 请勿在数字数组中存放非数字的值(对象) 如果通过非字面量进行初始化小数组时,切勿触发类型的重新转换 JavaScript编译 ​ 尽管JavaScript是个非常动态的语言,且原本的实现是解释性的,但现代的JavaScript运行时引擎都会进行编译。V8(Chrome的JavaScript)有两个不同的运行时(JIT)编译器: “完全”编译器,可以为任何JavaScript生成优秀的代码 优化编译器,可以为大部分JavaScript生成伟大(汗一下自己的翻译)的代码,但会更耗时 完全编译器 ​ 在V8中,完全编译器会以最快的速度运行在任何代码上,快速生成优秀但不伟大的代码。该编译器在编译时几乎不做任何有关类型的假设——它预测类型在运行时会发生改变。完全编译器的生成码通过内联缓存(ICs)在程序运行时提炼类型相关的知识,以便将来改进和优化。 内联缓存的目的是,通过缓存依赖类型的代码进行操作,更有效率的掌控类型。当代吗运行时,它会先验证对类型的假设,然后使用内联缓存快速执行操作。这也意味着可以接受多种类型的操作会变得效率低下。 结论 ​ 单态操作优于多态操作 如果一个操作的输入总是相同类型的,则其为单态操作。否则,操作调用时的某个参数可以跨越不同的类型,那就是多态操作。比如add()的第二个调用就触发了多态操作: function add(x, y) { return x + y; } add(1, 2); // add中的+操作是单态操作 add("a", "b"); // add中的+操作变成了多态操作 优化编译器 ​ V8有一个和完全编译器并行的优化编译器,它会重编那些最“热门”(即被调用多次)的函数。优化编译器通过类型反馈来使得编译过的代码更快——事实上它就是使用了我们之前谈到的ICs的类型信息! 在优化编译器里,操作都是内联的(直接出现在被调用的地方)。它加速了执行(拿内存空间换来的),同时也进行了各种优化。单态操作的函数和构造函数可以整个内联起来(这是V8中单态操作的有一个好处)。 你可以使用单独的“d8”版本的V8引擎来获取优化记录: d8 --trace-opt primes.js (其会把被优化的函数名输出出来) 不是所有的函数都可以被优化,有些特性会阻止优化编译器运行一个已知函数(bail-out)。目前优化编译器会排除有try/catch的代码块的函数。 结论 ​ 如果存在try/catch代码快,则将性能敏感的代码放到一个嵌套的函数中: function perf_sentitive() { // 把性能敏感的工作放置于此 } try { perf_sentitive() } catch (e) { // 在此处理异常 } 这个建议可能会在未来发生改变,因为我们会在优化编译器里开启try/catch代码块。你可以通过使用上述的d8选项“--trace-opt”得到更多有关这些函数的信息来检验优化编译器如何排除这些函数。 d8 --trace-opt primes.js 取消优化 ​ 最终,编译器的性能优化是有针对性的——有时它的变现并不好,我们就不得不回退。“取消优化”的过程实际上就是把优化过的代码扔掉,恢复执行完全编译器的代码。重优化可能稍后再打开,但是短期内性能会下降。尤其是取消优化的发生会导致其函数的变量的隐藏类的变化。 结论 ​ 回避在优化过后函数内隐藏类改变 你可以像其它优化一样,通过V8的一个日志标识来取消优化。 d8 --trace-deopt primes.js 其它V8工具 ​ 顺便提一下,你还可以在Chrome启动时传递V8跟踪选项: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --js-flags="--trace-opt --trace-deopt" 额外使用开发者工具分析,你可以使用d8进行分析: % out/ia32.release/d8 primes.js --prof 它通过内建的采样分析器,对每毫秒进行采样,并写入v8.log。 回到摘要…… ​ 重要的是认识和理解V8引擎如何处理你的代码,进而为优化JavaScript做好准备。再次强调我们的基础建议: 首先,未雨绸缪 然后,找到症结 最后,修复它 这意味着你应该通过PageSpeed之类的工具先确定你的JavaScript中的问题,在收集指标之前尽可能减少至纯粹的JavaScript(没有DOM),然后通过指标来定位瓶颈所在,评估重要程度。希望Daniel的分享会帮助你更好的理解V8如何运行JavaScript——但是也要确保专注于优化你自身的算法! 参考资料 ​ YouTube上的Daniel分享 Deck上的Danial的幻灯演示 V8工程师Vyacheslav Egorov的“我要基于V8优化我们的JS应用”清单

2013/9/29
articleCard.readMore

[译]视觉差,走起!

译自:https://www.html5rocks.com/en/tutorials/speed/parallax/ 简介 ​ 现在满大街都是视觉差(parallax)网站了,我们随便看几个: Old Pulteney Row to the Pole Adidas Snowboarding BBC News - James Bond: Cars, catchphrases and kisses 也许你对这玩意儿还不太熟,视觉差其实就是它的视觉结构会随着页面的滚动而变化。通常情况下页面里的元素会根据页面的滚动位置而缩放、旋转或移动。 不管你喜不喜欢视觉差网站,有一件事毫无疑问,它是一个性能的黑洞。因为当页面滚动时,浏览器的优化都倾向于新内容随滚动而出现于屏幕的最上方或最下方的情况。一般来说,内容改变得越少浏览器性能越高。而对于一个视觉差网站来说,在页面滚动时,好多元素都在发生改变,大多数情况下整个页面的大块可视元素都在发生变化,所以浏览器不得不重绘整个页面。 我们有理由这样归纳一个视觉差的网站: 背景元素会在你向上或向下滚动页面时改变位置、旋转或缩放。 页面内容,如文字或小的图片,在页面滚动时会按照传统的方式进行上下移动。 建议大家先阅读我们之前介绍过的滚动性能来改进你的app的响应速度。本篇文章是基于那篇文章所写的。 所以文字是如果你在建立一个视觉差网站,那么你是否受困于高昂的重绘开销?有没有别的改进建议使得性能最大化?让我们看看这几个方案: 方案1:使用DOM元素和绝对定位 ​ 这是很多人默认采取的方案。页面里有一大堆元素,任何时候只要触发滚动事件,这些元素就会进行各种变换来完成视觉上的更新。我已经用这个方式写好了一个demo页面。 如果你打开开发者工具的时间线的帧模式的话,滚动页面,你会发现各种全屏重绘,这个代价是很高的。如果你滚动多一些,你会发现在一个单个帧里出现了好多滚动事件,每个事件都会触发布局操作。 需要铭记的要点是为了达到60fps(匹配传统显示器60赫兹的刷新频率),我们需要在16毫秒之内搞定一切。在这个版本中我们使得每次滚动事件都造成了视觉上的变化。但是我们之前的文章用requestAnimationFrame做出更经济实惠的动画和滚动性能已经讨论过,这样做和浏览器的更新机制并不相符,所以我们要么会错过帧,要么会在同一帧里做了多余的工作。这样的网站无法给人一种纯天然不刺激的感觉,用户就会不爽。 让我们把更新界面的代码从滚动事件里拿出来,放到requestAnimationFrame的回调函数里吧,滚动事件只是简单的不惑滚动的值。我们的第二个demo页面在此。 如果你重复滚动测试,你可能会注意到一个轻微的改进,尽管不算明显。原因是因滚动而触发的布局操作并不总是代价昂贵了,但在其他用例中它很可能是。现在至少我们把布局操作限制在了每帧一次。 现在我们可以每帧绑定一个也可以绑定一百个滚动事件,但我们只记录requestAnimationFrame回调函数运行时最近的值并更新到视图上。这里的重点是之前每次滚动事件触发时都强制更新视图,现在则是请求浏览器提供一个合适的窗口来做这件事。怎么样?不错吧! 这个方式的主要问题在于,不论requestAnimationFrame与否,整个页面基本上是一个层。通过移动周围的这些可视元素,我们需要大块的重绘。通俗地讲,绘制是一个阻塞操作(虽然这已经在改变),也就是说浏览器无法做任何其它的工作,我们经常会超出每帧16毫秒的预算,页面还是无法纯天然不刺激。 方案2:使用DOM元素和3D变换 ​ 不同于绝对定位的另一个方案是我们可以将元素应用到3D变换当中。在这种情形下,我们将这些应用3D变换的元素视为一个新的层,并且在WebKit浏览器中,它通常会导致一个硬件层面的转变。在方案1种,相比之下,我们有一个大的重绘的层,这个层的任何改变都会在CPU中绘制和组合。 也就是说,在这个方案中,一切变得不一样了:我们为应用3D变换的任何元素提供一个潜在的层。如果我们从这一点出发进行元素的变换那么我们无需重绘这个层,而GPU可以处理这些元素的移动并组合成最后的页面。 这里有另一个demo展示了3D变换的使用。如果你滚动页面你将会发现效果得到了大幅度的改善。 人们多次使用-webkit-transform: translateZ(0);做hack并看到惊人的性能提升,但今天看来有几个问题: 这并不是跨浏览器兼容的 它强制浏览器为每个元素创建一个新的层。大量的层同样会带来性能瓶颈。所以请尽量少用。 有些WebKit ports是禁用这个的。 如果你谨慎使用3D变换的话,这确实是一个临时解决方案!理想化的讲,我们可以在2D变换时看到和3D同样的渲染特性。浏览器正在以惊人的速度一步一步发展,所以希望这就是我们将会看到的。 最后,你应该针对性的避免绘制任何你可以在页面内简单移动的元素。举个视觉差网站通用的例子,固定div的高度并改变齐背景的位置来提供视觉差效果。不行的是这个元素需要在每次运动的时候都进行重绘,这会带来性能的损耗。取而代之的是,如果可以,你应该创建元素(有必要的话将其包裹在一个overflow: hidden的div中)并对齐进行简单的移动。 方案3:使用固定位置的canvas或WebGL ​ 我们考虑的终极方案,就是使用一个固定位置的canvas放在页面最底层,把我们想要绘制的各种变换图形都画在里面。一眼看上去这并不像是最优方案,但是这个方案确实有它的一些优势: 我们不再需要组合工作了,只需要一个canvas元素就行了 我们通过硬件加速高效处理一张大位图 Canvas2D API善于处理我们需要的各种变换,开发和维护都变得很容易。 使用canvas元素给了我们一个新的层,但是仅此一个层,而在方案2种,我们实际是为每个应用3D变换的元素都创建了一个新的层。所以我们需要组合所有的层到一起,这是一个会增长的工作量。鉴于不同浏览器对变换的不同实现,这同时也是跨浏览器兼容性最好的方案。 如果你看看基于这个方案的这个demo,在开发者工具里测试一下,你会发现性能非常好。这个方案我们简单的使用了canvas的drawImage API调用,并且我们将其背景图片和每个色块都绘制在屏幕上正确的位置。 /** * Updates and draws in the underlying visual elements to the canvas. */ function updateElements () { var relativeY = lastScrollY / h; // Fill the canvas up context.fillStyle = "#1e2124"; context.fillRect(0, 0, canvas.width, canvas.height); // Draw the background context.drawImage(bg, 0, pos(0, -3600, relativeY, 0)); // Draw each of the blobs in turn context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0)); context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0)); context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0)); context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0)); context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0)); context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0)); context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0)); context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0)); context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0)); // Allow another rAF call to be scheduled ticking = false; } /** * Calculates a relative disposition given the page’s scroll * range normalized from 0 to 1 * @param {number} base The starting value. * @param {number} range The amount of pixels it can move. * @param {number} relY The normalized scroll value. * @param {number} offset A base normalized value from which to start the scroll behavior. * @returns {number} The updated position value. */ function pos(base, range, relY, offset) { return base + limit(0, 1, relY - offset) * range; } /** * Clamps a number to a range. * @param {number} min The minimum value. * @param {number} max The maximum value. * @param {number} value The value to limit. * @returns {number} The clamped value. */ function limit(min, max, value) { return Math.max(min, Math.min(max, value)); } 当你处理大图片(或其它可方便绘制到canvas中的元素)的时候,这个方案效果不错,但是处理大块文字的时候这个方案会遇到更多的挑战,但还是可以根据你的网站的情况成为最合适的方案。如果你不得不在canvas里处理文本,你可以使用fillText API方法,但是它的可访问性会打折扣(你把文字转成了位图!)并且你不得不处理文字的折行等一些列细节。如果你可以避免它,你真的应该,也更有可能更好的使用上面的变换方案。 既然我们尽可能往远了想,那么没有理由断定视觉差的工作应该在一个canvas元素内完成。如果浏览器支持的话,我们可以使用WebGL。这里的关键在于WebGL有最直接的显卡API调用方式,也是你最有可能达到60fps的方式,尤其在网站效果比较复杂的时候。 你立刻觉得使用WebGL有点过于夸张了,或者WebGL尚未被广泛的支持,但是如果你使用类似Three.js的工具,你总是可以降级到使用canvas元素同时你的代码被抽象为了一致且友好的形态。所有我们需要的是使用Modernizr检查相关的API支持情况: // check for WebGL support, otherwise switch to canvas if (Modernizr.webgl) { renderer = new THREE.WebGLRenderer(); } else if (Modernizr.canvas) { renderer = new THREE.CanvasRenderer(); } 然后使用Three.js的API替换掉我们对上下文的处理。这里的demo同时支持了两个渲染方式,假设你的浏览器也会如此! 作为这个方案的最终思考,如果你不会在页面里放太多额外的元素的话,你可以总是使用canvas作为背景元素,这Firefox和基于WebKit的浏览器中都可以。很明显这确实不是无处不在的,所以我们平时使用的时候要小心谨慎。 取决于你们的实际情况 ​ 开发者默认更多使用绝对定位的元素实现视觉差的主要原因其实就是其特性的支持程度。这在某种程度上是幻觉,因为老的目标浏览器很可能提供的是一个极其糟糕的渲染体验。甚至在今天的现代浏览器中,使用绝对定位元素还是无法保障好的性能。 3D变换为你提供了直接操作DOM元素的能力,并可以达到不错的帧率。成功的关键就是在你简单的移动周围元素时避免了绘制。一定记住,WebKit浏览器在这个过程中创建了层,但这和其它浏览器并不相关,所以要在提交方案之前一定要测试确认。 如果你只是定位于顶级浏览器,且可以通过canvas渲染网站,拿canvas可能是你最好的选择。当然如果你使用Three.js,你应该可以根据你需要的支持情况选择在不同的渲染方式之间进行切换。 总结 ​ 我们已经评估了几个视觉差网站的实现方案,从决定定位元素到使用固定位置的canvas。当然,你需要的实现方式,依赖于你希望达到的特定的设计效果,但有这几个可选方案总是好的。 还是那句话,不论你用哪个方案:别妄加猜测,试试就知道了。

2013/9/22
articleCard.readMore

[译]Chrome开发者工具中评估性能的五大新特性

摘自:Chrome DevTools Revolutions 2013 本次开发者工具的改进中有几项新特性是针对性能的: 持续绘制模式 显示绘制矩形及其层的边框 每秒帧数的测量仪 找到强制同步布局(layout thrashing) 对象分配跟踪 持续绘制模式 ​ 持续绘制模式是开发者工具设置中的一个选项(渲染>开启持续页面绘制),这个选项可以帮助你识别单个元素或CSS样式的渲染开销。 通常Chrome只在响应一个布局或样式的变化时绘制屏幕,并且只是绘制屏幕中需要更新的区域。当你开启持续页面绘制选项时,整个屏幕都会不断的重绘。一个置顶的界面会展示Chrome在绘制页面时所花费的时间,以及近期绘制时间的分布图。穿过整个直方图的那条横线代表16.6毫秒标记线。 这样做的好处是你可以走遍DOM树中的元素面板,隐藏单个元素(隐藏当前选中元素的快捷键是H)或关闭一个元素的CSS样式。通过留意页面绘制时间的变化,你可以看到单个元素或样式为页面渲染所增加的“负担”。如果隐藏一个元素使得绘制时间明显下降,那么你要重点关照一下这个元素的样式或构造了。 开启持续绘制模式的方法: 打开开发者工具的设置 打开常规选项卡,在渲染中,打开开启持续页面重绘 注意:如果你看不到这个设置项,请打开about:flags,打开在所有页面中使用GPU合成,并重启Chrome。 更多信息,请移步至:用开发者工具的持续绘制模式进行长绘制时间的性能分析 显示绘制矩形及其层的边框 ​ 另一个开发者工具的选项是展示正在被绘制的矩形区域(设置>渲染>展示绘制矩形)。比如,在下面这个屏幕截图中,一个矩形正在被绘制,在这里,CSS悬停效果被应用到了紫色图形中。 你得回避导致整个界面被重绘的设计实践与开发实践。比如,在下面这个屏幕截图中,用户正在滚动页面。一个绘制矩形覆盖在了滚动条上,另有一个绘制矩形覆盖在了整个页面的剩余部分。它的罪魁祸首是body元素的背景图片。该背景图片是fixed定位的,它要求Chrome每次滚动页面的时候都得重绘整个页面。 每秒帧数测量仪 ​ 每秒帧数测量仪显示了页面当前的帧率、最小帧率和最大帧率、一个展示帧率随时间变化的条形图、以及不同帧率分布的直方图。 开启每秒帧数测量仪的方法: 打开开发者工具的设置 打开常规选项卡 在渲染中,打开强制加速合成以及显示每秒帧数测量仪 你可以通过打开about:flags,然后开启每秒帧数计数器并重启Chrome,来强制每秒帧数测量仪始终显示。 寻找强制同步布局(layout thrashing) ​ 为了最大化渲染性能,Chrome通常会在应用程序中批处理布局变化请求,并制定一个日程来异步计算和渲染这些变化请求。尽管如此,当一个应用程序获取依赖于布局的属性值的时候(比如offsetHeight或offsetWidth),Chrome会强制立刻同步渲染页面布局。我们称之为强制同步布局。这会明显的降低渲染的性能,在大DOM树中重复运行时尤为明显。这种情形也被称之为“layout thrashing”。 当我们检测到一个强制同步布局的时候,时间线记录中会有警告,它会在相应的时间线记录边上显示一个黄色的警告图标。鼠标悬停在这些记录上会看到无效的布局的代码堆栈记录、以及造成强制布局的代码堆栈记录。 该弹泡同时展示了需要布局的结点数量、重新布局的树的尺寸、布局的范围和布局的根。 更多信息,请移步至:时间线Demo:诊断强制同步布局 对象分配跟踪 ​ 对象分配跟踪是一个新型的内存描述资料,它可以实时展示内容分配的情况。当你开始分配跟踪时,开发者工具实时持续生成堆的快照。堆分配的描述资料展示了对象在哪里被创建,且识别被保留的路径。 跟踪对象分配的方法: 打开开发者工具,点击描述资料选项卡 选择记录堆分配然后点击开始 当你完成数据的收集之后,点击停止记录堆描述信息(描述资料面板左下角的红色的圆)。

2013/9/18
articleCard.readMore

精气神儿

“国足打出了精气神儿” 相关新闻:东亚杯-王永珀2球孙可建功 中国两球落后3-3日本 当我再一次看到这样的标题的时候,我就知道,言外之意是国足的状况一定非常糟糕。 精气神儿是个什么东西?我觉得是一种最基本的态度,它只是个精神层面的很虚的过程。我举个例子,当你一无所有的时候,你只能说:哦,至少我还有节操。——这就是拿精气神儿说事儿的节奏。 国足说我们努力了,大家说其实人家从上到下还是很努力的,这一点我们还是要认可的……好吧你们确实真的很努力,但为时已晚。而且国足真的很差,现在才知道努力有个屁用?未雨绸缪的事情怎么从来没见足协做过? 以前国足被叫作头球队,叫热身赛之王,如今头球也没了,热身赛也能输个精光,连博彩公司的小伙伴们都惊呆了。 以前几年赢不了韩国就说恐韩,如今15年不胜日本了,也没人造出个什么恐日了,因为觉得跟人家比不自量力,丢不起那人。 以前国足98年,当时还叫东亚四强赛,国足在日本的主场2比0羞辱对手,范志毅还踢飞丢了一个点球,不然就是3比0的大胜(对,两边都是成人队,而且都是男足)。如今在一片铺天盖地的唾骂声中,国足才开始努力,开始打出精气神儿,勉强在最后时刻,逼平了日本二线队。 国足的努力掩盖不了一个事实,那就是战绩糟糕,排名持续下滑,多年无缘各项国际大赛。 这有什么可高兴的? 所以请别扯淡!拿成绩说话!! 看看今天的国足,还剩下什么?答:“只剩下两滴冰冷的泪水:一滴化斗酒添一份麻醉;一滴沉落于岁月的潮水。” 好。我这里对国足的吐槽完毕。 其实我没想太多聊国足。 接下来,请把“国足”二字换成你看到“其实很努力”之后首先联想到的事物,然后把上面全文的“国足”二字换掉重读一遍。相信你会很有乐趣和感悟。

2013/7/23
articleCard.readMore

细节无微不至,彩屏让人又爱又恨——新老“神机”大对决:Nokia 1050 vs Nokia 1202

吐在前面的槽…… ​ 今年,在老罗锤子手机一路跳票、累不死手机活蹦乱跳、魅族手机版本号即将输给小米、iOS被拍扁、Windows Phone在卖萌、安卓在卖身等诸多鸟事相继雷到众生之后,人们无不感叹,手机这个行业还有救吗?站在智能和愚蠢的十字路口,我们该何去何从?囧rz 就在大家迷茫之时,Nokia发布了它的又一力作——1050!整个业界犹如刮来了一股春风,无不感到清新舒畅。大家纷纷感叹,那个曾经的科技巨人就要王者归来鸟!这个夏天,This summer,最受瞩目的大事件,big event!就是: Nokia 1050 的发布!!! 作为一个反智能化手机操作系统的支持者,我很自豪的宣布,经历了前两轮的预定失败之后,我终于在上周成功订到了这款神机——要不要卖得这么好啊 - - 我之前用的手机是Nokia 1202,2008年的神机,其实也不算太老,要不是1050发布,它也其实已经是一个非常现代化、非常新款的愚蠢手机了,无奈在这个1050的时代趋势下也不得不接受停产的命运。 1050基本上继承了1202所有的成员函数和成员变量,小巧大方,简单易用,低碳环保,超长待机,便宜实惠,这已经足以吊起广大消费者的胃口了。然而真正的1050到底表现如何?它能否在1202的光环之下更进一步再创辉煌?带着这些疑问,记者走访了不少xxx,挖掘出了很多珍贵的xxxx,也听到了各种xxxxxxxx…… OK 马上开始 目录 ​ 开箱/外观 屏幕 主界面 基本功能 (电话/短信/通讯录) 特色功能 实用工具 性能/续航能力 综述 开箱/外观 ​ 从体型上看,1050比1202略大了一圈,基本上没有怎么影响便携性,因为它已经真的足够小巧了,放包里、裤子口袋里、憋在身体的各种地方都没有负担。相比之下,1050不论是蓝色还是黑色更时尚一些,尤其是她的背影,但略略略显厚重点点点。 充电器还是和以前一样,很小巧,绝对耐用耐看 电池和1202想比,由860mAh减少到了800mAh 屏幕 ​ 毫无疑问,1050到1202最大的变化就是从白屏到彩屏了,1202的屏幕更宽更扁一些,1050则是更方更高一些,1202正常情况下可以显示8x5个汉字,而1050则是在8x5的基础上还能在上面放一行小标题和信号、电量的信息。这样的话,1202之前只能在具体功能页面中隐藏信号、电量信息、然后上面显示一行标题,下面显示左右两个按钮,正文只有3三行;而1050不但可以始终显示信号、电量信息,还可以显示4行正文,这比之前是有提升的。但另一方面,其实屏幕的绝对精度是提高了,所以屏幕高度只有略微的增加,而且宽度还小了一些。这是屏幕上的变化。 主界面 ​ 正常情况下,两款手机的主界面基本一样,无非是1050在时钟下面多空了一行,不过主界面的变化还是很明显的: 待机 ​ 先说待机,1202在待机的时候是关闭背景灯光,并始终显示时钟;而1050由于彩屏的缘故,可能会更耗电一些,所以待机的时候背景灯光和时钟全部都是关掉的。在导致的体验差异是:如果你需要看表,1202在白天可以直接拿起来看,1050则需要按任意键点亮屏幕。我觉得这一点上体验是下降的,当然这也可以理解为彩屏的代价吧 锁屏 ​ 在众多智能手机还在为如何解锁、会不会有专利问题伤透脑筋的时候,Nokia系列愚蠢手机在就完美解决了这个问题:“开锁键->星号键”,pretty simple!什么?怕忘?没关系,系统在必要的时候都有提示的。 说到提示,这是我想说的一个重点:1202对于解锁的提示是一个非常强的提示,在锁屏状态下,只要你不是在键入“开锁键->星号键”,就会有一个全屏提示说:“要想解锁请按开锁键再按星号键”,但其实我有的时候只是想在光线比较暗的场合看一下时间,这其实是很令人恼火的一件事。1050抓住了这个用户痛点!它在待机状态下只接受开锁键和开机键这两个事件,开机键用来点亮屏幕,这个时候可以直接看表,除非用户再按了别的键,才会出现那个提示。 快捷键 ​ 1050多了两个非常实用的快捷键! 星号键上多画了一个时钟和气泡的结合体的图案,长按这个键,系统会自动语音报时。——天呐,有语音功能的愚蠢手机!没想到能愚蠢成这样!! 与之对应的,井号键上多画了一个被划掉的音符,没错,长按这个键,可以快速切换到静音模式。真是太方便了!前一个功能我还不太用得到,这个功能真的是设计得很贴心啊!! 主菜单 ​ 1050的主菜单相比较1202的主菜单也发生了视觉上的变化 1202的主菜单是一个图片轮播一样的界面,同时只显示一个命令,外加左右键的提示 1050的主菜单则为用户提供了两种选择,一种是列表,每屏可以显示两个命令,可以上下翻阅,它的特点是字大看得清楚;另一种则是launchpad一样的图标列表,它的好处是对图形敏感的用户可以快速的找到最下面的命令,不必在列表模式下一直翻到最下面才知道有什么。 基本功能 ​ 要说一个手机应该做什么事?这回到一个非常严肃的问题:什么叫智能手机?什么叫愚蠢手机?(此处省略1000字) 一个手机最应该做好的,当然是电话、短信、通讯录了!1020和1050在这方面做得非常出色,它可以在如此小巧的一亩三分田里做到智能手机才能做到的事。1050更是在1202的基础上做出了多项功能改进。 电话 ​ 1202和1050的电话功能基本一致,不过在国内这种嘈杂的环境中,这两款神机的嗓门显得有点力不从心。1202的听筒音量根据我多年的使用经验其实是可以的,1050的听筒音量比1202更小一些,我有的时候索性打开免提模式了,那个音量在人多嘈杂的环境中是刚好的。但这两款手机有个不太好的地方是周围的人同样会听得很清楚,尤其是1202,貌似声音是直接通过扬声器传出来的,没有特别的听筒发音,所以手机正反面发出去的音量差不多,有一定的隐私问题。如果有什么私密事宜要在电话里谈,建议选好地方。 短信 ​ 这个就值得多说一说了,1050在短信上的改进还是很贴心的。 首先是由于屏幕由3行正文变成了4行正文,在输入短信的时候有了更多的预览空间,这是很明显的一个改进。 第二,在选择发送人时加入了一个最近发出人的列表界面。 之前只有两个选择: 输入手机号->右键发送,要么 左键进入通讯录->选择联系人->右键发送。 现在的流程变成: 选择最近发出人->左键发送 选择输入号码->输入手机号->右键发送 选择输入号码->左键进入通讯录->选择联系人->右键发送 这样的话,发给常发的人变得更快捷了。我个人的使用经验是90%的短信都是直接在这个列表里选的。真的省了很多时间。 不过我在这里还有一点点改进的建议,因为选择联系人发送的步骤其实由3步操作变成了4步操作,我建议把选择联系人的选项直接放在选择最近发出人列表的最上面作为默认选项,选择输入号码放在第二选项或最下面,保持3步操作 第三,一键回复短信。 在Nokia的键盘设计理念里,每一个按键都有很鲜明的特色和传统。说到这里不得不系统的介绍一下: 上方控制区 两侧功能键 左侧两个按键:永远表示确认、前进、接听电话等含义,且左下比左上的思想感情更强烈,我们暂定它们分别是确认键和接听键(因为上面画了接听电话的图案) 右侧两个按键:永远表示取消、退出、挂断电话、关机等含义,同理右下比右上的表意更直接,我们暂停它们分别是取消键和挂断键(因为上面画了挂断电话和电源的图案) 中间方向键 下方数字区 0-9数字键 左下方星号键:特殊字符输入、键盘锁组合键之一等 右下方井号键:输入法切换等 介绍这个是因为在最新的设计里,发送短信如果按下确认键,会打开菜单,提供发送等各种功能选择,然后才跳入我们刚才介绍的发送短信的流程;如果按下接听键,可以直接来到这个流程。 而更贴心的是,如果我是在回复一则短信而不是发送一条新短信,这时按下接听键,系统会直接把短信发出去——没错就直接发给来短信的这个号码。怎么样,是不是感觉很贴心呢 ^_^ 第四,支持调整短信文字大小。 你也许不会相信,这种简朴的屏幕居然还能调“分辨率”?!没错,1050支持三种文字大小——汉字!如果你眼神不好,可以选择最大字号,如果你想看到更多内容,可以选择最小字号。老少皆宜!感受一下 其实除此之外,1202也已经有很多优质的特性了,1050也同样支持——这就是传承!有底蕴的科技公司!! 比如这个免打扰列表,可以屏蔽各种垃圾短信:怎么样!别以为只有智能机会做到这一点 比如这个经典的中文输入法,说实话我至今觉得Nokia的中文输入法是手机里最好的输入法,你可以很快让自己的打字速度达到一个很高的境界,不管是拼音输入还是笔画输入。而且它的智能匹配足够精准,反应速度也飞快,按键触感也很舒服。神马叫在键盘上跳舞的感觉!用过Nokia就知道了 当然了,还是要客观的说,这里同样有一些不足。但其实有的时候作为一名用户,它不一定是坏事。比如它不支持彩信。我擦!我太喜欢这个不足了!!! 还有就是从打字速度上看,1050比1202略略略卡了一点点,但依然在“流畅”的范围内 通讯录 ​ Nokia的通讯录管理也是很经典的,我有点不打算多介绍了,我只想说一点:把通讯录存在SIM卡上!秒杀一切所谓的云同步,它们全都是炒概念,想挖你的数据,像Siri一样。如果三方服务你都信不过,那这个绝对是最实用最可靠的! 特色功能 ​ 说到这里,先大笑三声:哈~哈~哈~哈~ 同步时间 ​ 在1202里有一个软肋,就是它不计时间,只要电池拔下来,再装上,就要重新设定时间。它们真是会压缩成本啊 - - 1050不光解决了这一顽疾,而且支持“自动更新日期和时间”,你能想象吗?这是多么先进的理念! 日历直接显示农历 ​ 1050做到了!10个字:贴心! 而且绑定备忘录、支持公历农历快速查询——这是1202和1050都具备的 情景模式 ​ 1202和1050都支持情景模式,按下挂断键,就会看到这个选项列表 刚才介绍了,在1050中,还支持长按井号键快速切换到静音模式,其实是对这个功能的一个进化 主题切换 ​ 没错!1050有主题切换哦,只需简单的2-3步操作——这可是iOS需要在6和7之间升级/降级才做得到的事情哦! one more thing:你还可以修改配色方案的细节!天呐,这是神马神机呢!200块的手机可以如此DIY,我三观都改变了!! 铃声 ​ 1050的手机铃声有点不如1202了,可能是我个人品味的问题吧,Nokia的默认手机铃声其实旋律是很经典的,但是音色一直在变化,从最早的蜂鸣声开始。1202是钢琴音色,自己听着很舒服,而1050换了个说不上来的音色,我个人还是比较喜欢钢琴音色的那个版本。无奈之下我换了另外一个铃声,不过也算不错了,Nokia的铃声选择可比iOS的丰富多了,并且震动可以根据铃声的节奏震动。这个特性连HTML5都还没讨论出结论呢。 再有就是有个小乐趣消失了,就是自创铃声。1202是支持的,1050把这个功能去掉了。不过平心而论,我很少用这个,所以不太在乎了。不知道大家是不是这种音乐发烧友,如果是的话,建议还是不要升级新版本了,1202也不错。 实用工具 ​ 我觉得nokia推出的各种“实用工具”虽然在我看来是不务正业,但这是对智能手机最大的讽刺。你们烧硬件、烧系统、烧应用、各种烧,不就是做了这点事么? 手电筒 ​ 苹果从上周的iOS7开始才意识到手机可以用来当手电,我觉得这好讽刺,因为他们没有重新定义手电,而是在视图定义手机的时候不小心做成了手电。我觉得Nokia才是重新定义了手电,它诚意十足,它不是闪光灯的副产品,而是真的有意配了个灯泡给你当手电啊亲!!Tim-Cock你不觉得惭愧吗? 在临床作业中,我们发现,1050的手电筒效果比1202的光线更集中,发光效果更好,更符合手电筒的使用需求。细节!细节!! 单位转换 ​ 足球场上人墙的距离为嘛是9.15米呢?iPhone用户该怎么办?打开Safari,在搜索框里艰难的输入问题,然后在一系列的网络不给力之后,比赛结束了,你还在思考人生。 看看Nokia是什么态度!内嵌各种单位转换,无需网络。 你也许会说,就那么几个写死的单位换算,一点都不高级。错!我要拿截图和亲身经历告诉你Nokia的单位转换是有API的!!只要你知道公式,想换算什么都可以!! 记账 ​ OMG!别挖财了,看看Nokia是怎么给你提供解决方案的。 游戏 ​ 我很想把Nokia手机里的游戏秀给你们看,真的。早就不是神马贪吃蛇数独之辈了。It's AMAZING!It's SO GREAT!That is FANTASTIC!UNBELIEVABLE!我觉得这个应该是一个真正的诺基亚用户独享的东西,所以,想即刻拥有来自诺基亚的经典游戏么?还在等什么!赶紧去排队预购吧!! 收音机 ​ 我要崩溃了……连这个都有 美中不足是需要耳机啊,要是能功放就更完美了,不然这绝对是在地铁里公交车上秀给那些iOS安卓屌丝们的最佳利器!! 捷径 ​ 常用功能的快捷方式都在这里了,可以自由定制,比如我就把收件箱、闹钟、未接电话和已接来电放到了这里 砸核桃 ​ 这个我没测,愚蠢手机也是手机啊!不能浪费的好吧。诸位大神谁帮忙测一下呗,绝对的业界良心! 小结 ​ 我觉得Nokia要是够聪明,就适可而止吧,做成这样刚刚好。这些功能也不用做太多,做个意思,输出一下自己的价值观就足够了。另外该操作系统不提供截屏功能,这给它的市场传播带来了一定的困难。希望将来Nokia可以支持截图上传微博的功能,这个就真心碉堡了,毕竟Nokia还是一家老牌的有实力的科技公司。 性能/续航能力 ​ 从总体的性能情况看,就像之前介绍到的,1050比1202各方面响应速度,在同样合理的范围内,要略慢一些,有两种可能,一种可能是彩屏拖慢了速度,另一种可能就是Nokia其实也在成本和性能之间找寻平衡。从这一点上讲,1202的硬件配置其实是很奢侈的,1202才是一台真正的超值高性能主机。难怪它会停产啊,我为曾经用过这样的神机而骄傲!所以,1050不但可以比1202做得更好用,甚至在商业利益上也超越了前作。 从续航能力看,实际实用时间没有理论待机时间35天那么长,充一次电差不多可以正常使用1周,和1202差不多。但实际上它的电池容量从860mAh降到了800mAh。我们不得不再次感叹,Nokia在传说中如此成熟的直板机制造业,还能不断突破自我,勇往直前!换个图标把UI拍平了神马的就敢说自己突破自我绝对是百事可乐喝多了有木有 综述 ​ 综上所述。Nokia 1050的最大变化来自于彩屏,其实细细想来,整款手机的一些列变化其实都是围绕着屏幕展开的。作为一个环保主义者,我其实觉得彩屏是个很奢侈的改变,它几乎违背了Nokia科技以人为本的办学理念。但是它由此带来的改进和便捷却是感同身受的。另外可以看出来Nokia在什么是手机、手机应该是什么样子、手机的未来在哪里这三个问题上有着非常睿智的独到见解。这家老牌的手机厂商、曾经的移动终端霸主,在经历了一段短暂的迷茫之后,终于找到了正确的发展方向。从前两轮预购都被秒光的态势来看,我们有理由相信,1050将引领一波轰轰烈烈的反智能化手机的热潮,并有理由期待Nokia的未来带给我们的更多惊喜。 完

2013/6/15
articleCard.readMore

秦升拿到红牌之后……

工体上空飘扬着“傻X”的洪亮口号,晚上天津牌照的车又被砸了不少 比赛收视率直线下降——比赛才开始了13分钟,还有80分钟呢 赞助商的脸全都绿了,下次国足的比赛就没有再打广告了,不过无非是又有别的厂商挨宰了 谭晶从此霉运不断 看台上两国外交部长正在谈一个上百亿的项目,荷兰大爷若有所思的说:这个我们回去再考虑考虑…… 对面:恩,下次我们还是改一起看乒乓球吧 刘建宏紧紧攥着双拳:完了完了,今天这场球大Boss都在看啊,这种球我该肿么说比较得体呢?肿么办肿么办…… 小区里:还是很安静的,现在大家看球都不是看彩电了,要么是笔记本电脑,不舍得扔下去,要么是大平板电视,扔不动 之前荷兰队在踢友谊赛,国足在踢训练赛,裁判在吹国际A级比赛;之后荷兰队在踢训练赛,国足在踢国际A级比赛,裁判在吹友谊赛 国家队队友们一脸茫然,以前友谊赛都是中国裁判,都给足面子。再一次国家队的比赛,裁判仍然是中国裁判 范加尔赛后接受采访时称自己不开心,被荷兰足协粗卖了,荷兰众将罢训,范佩西代表球员出席了新闻发布会称,这是让我们去送死 从此世界上再没有一支球队愿意跟中国队过招,除非他们国家的足协为了金钱利益出卖了他的球队 以穆里奇、孔卡为首的外援球员代表联名发表声明,要求足协: 修改足球规则,国内球员犯规即黄牌,两次犯规直接红牌罚下,累计5次犯规赛季报销,并且该判罚带入下一年的亚冠联赛; 取消外援名额限制,全外援球队可以免税,国内球员只能进攻不能防守,且只能穿平底鞋; 外援球员年龄超过28岁还能健康上场的,政府每分钟给500欧元补助 各大俱乐部听闻此消息,纷纷抢购秦升,最后上海申花以创纪录的8000万RMB签下了这名才华横溢的屠夫,他和戴琳组成了令人闻风丧胆的秦岭组合 德容本想来中超安度职业生涯的晚年,结果第二天他直接宣布退役 范佩西和罗本对视了一下:兄弟,下次有这种五五开的球你就别争了,跳起来躲过去算了,别说我没提醒过你 罗本:我们这样不好吧,开场13分钟就把人家给逼疯了,以后人家不请我们来了。于是几个荷兰老乡达成一致默契,只踢门柱,踢到门柱算得分,踢进算丢分,最终凭借斯内德的一记乌龙球,比分定格在了2比2,但是赛后罗本遭到了范佩西的痛斥…… 里皮:X,买秦升的钱算是白花了 卡马乔:X,我下次再用秦升我就是孙子!p.s.我还有下次么 某:明天叫小蔡来我办公室一趟 米卢:呵呵 阿里汉:呵呵 克劳琛:呵呵 霍顿:呵呵 最后 ​ 中国队输球之后回到更衣室,卡马乔甩下一句中国国骂就离去了,翻译一时糊涂还翻译成了西班牙国骂,随后也尴尬离去,气氛很凝重,大家都闷不吭声……30秒后,秦升终于忍不住先开口了:“哎呀今天这球输了都算我的,晚上我请客,咱们去三里屯不醉不归!”,顿时更衣室云开雾散,大家又快快乐乐的在一起了^_^ 博君一笑 (配图皆来自于互联网)

2013/6/11
articleCard.readMore

用Sass重新整理自己的博客主题样式

远远关注Sass很久了,今天终于鼓起勇气写了我的第一个Sass文件 Sass简介 ​ 一种CSS的预处理程序,基于Ruby运行。安装过程和相关的准备工作非常简单: 当然首先要安装Ruby gem install ruby,必要的环境下需要在命令前加上sudo 进入我的博客主题文件夹,运行sass-convert style.css style.sass,把我的css文件先转换成sass文件 运行sass --watch style.sass:style.css,使得程序自动把style.sass文件接下来的任何改动自动同步转换到style.css 这时,新的Sass文件就创建完毕了!^_^ 去碎觉…… 呵呵,开个玩笑。其实这样的Sass文件虽然格式上没有任何问题,但和直接撰写CSS几乎没区别。而Sass除了可以让我们少写几个花括号和分号之外,其实还有很多实用的特性是我们真正需要的。 无论如何,现在的这个Sass文件是一个整理的基础,接下来,我们就来一步一步整理这个文件,同时也一步一步熟悉Sass的特性。 Variables: 变量与计算 ​ 我把CSS文件中通用的字体、颜色等等属性值归纳了出来,并找到其中的相关性,比如文本框的边框颜色始终比链接的文字颜色亮一些,即: a { color: #0c0; } input:not([type]), input[type="text"], input[type="password"] { border: 2px lime solid; } 在Sass中将其改写为: $color-link: #0c0 a color: $color-link input:not([type]), input[type="text"], input[type="password"] border: 2px lighten($color-link, 10%) solid 即可,输出的结果不变。 Nesting:选择器嵌套 ​ 把嵌套着的选择器添加不同的缩进,同时把重复表达的外层选择器去掉。如: h2 { margin: 0.75em 0; padding: 0.25em 0.5em; background-color: rgba(0, 204, 0, 0.4); color: #339933; } h2 strong { color: white; } h2 a { color: #009900; } h2 a:hover { background: yellow; color: black; } 可以转换为: h2 margin: 0.75em 0 padding: 0.25em 0.5em background-color: rgba($color-link, 0.4) // 链接颜色,透明度40% color: desaturate($color-link, 50%) // 链接颜色,饱和度减少50% strong color: white a color: darken($color-link, 10%) // 链接颜色,变暗10% &:hover background: $color-mark // 特殊标记的颜色 color: black Mixin:将可重用的CSS属性值混入不同的CSS规则当中 ​ 这个特性是我花最多时间整理的,整理过后发现整个文件结构真的清晰很多。 提取出成套的样式 ​ 比如我的博客主题中,3D的标题和按钮就是两套集成度重用性都很高的样式,我创建了@mixin title-3d和@mixin button-3d两个大的属性集合: @mixin title-3d ... &::before, &::after ... ... @mixin button-3d ... &::before, &::after ... &:hover::before ... &:hover::after ... ... 随后我把这些属性集合应用到了标题和按钮上: h2 @include title-3d button, input[type="button"], input[type="submit"], .button-3d @include button-3d 继续归纳和抽象 ​ 整理完上面这两个大的集合之后,我发现,其实3d的标题和按钮样式上其实也有很多想通的地方,于是我进一步抽象出了一些3d模型的通用集合: @mixin short-transform -webkit-transition: -webkit-transform 0.3s @mixin transform-origin-0 -webkit-transform-origin: 50% 50% @mixin transform-origin-1 left: 0 top: 0 -webkit-transform-origin: 0% 0% @mixin transform-origin-2 right: 0 top: 0 -webkit-transform-origin: 100% 0% @mixin transform-origin-3 right: 0 bottom: 0 -webkit-transform-origin: 100% 100% @mixin transform-origin-4 left: 0 bottom: 0 -webkit-transform-origin: 0% 100% @mixin init-3d @include short-transform @include transform-origin-0 -webkit-transform-style: preserve-3d @mixin p-element position: absolute content: "" 然后到之前的集合把这些通用集合抽象出来: @mixin title-3d @include init-3d ... &::after, &::before @include p-element @include short-transform background-color: lighten($color-text, 30%) &::before height: 100% width: $button-3d-height*2 @include transform-origin-1 ... &::after height: 100% width: $button-3d-height*2 @include transform-origin-2 ... 统一封装不同浏览器的CSS前缀 ​ @mixin box-sizing($sizing: border-box) -moz-box-sizing: $sizing box-sizing: $sizing @mixin transform($value...) -webkit-transform: $value transform: $value @mixin transform-origin($value) -webkit-transform-origin: $value transform-origin: $value @mixin transform-3d($value: preserve-3d) -webkit-transform-style: $value transform-style: $value @mixin transform-perspective($value: 350px) -webkit-perspective: $value perspective: $value @mixin transition($value...) -webkit-transition: $value transition: $value @mixin transition-transform($duration: 0.3s) -webkit-transition: -webkit-transform $duration transition: transform $duration ... Extend:选择器的继承 ​ 其实这个特性和mixin很多时候可以二选一使用,比如这里的.button-3d选择器可以不必和button、input[type="button"]、input[type="submit"]写在一起,可以写成: .button-3d ... button, input[type="button"], input[type="submit"] @extend .button-3d 不过我这里这样写的必要性不太大,所以就没有实际的例子可以分享了。 结语 ​ 最终我的第一个Sass文件就这样整理完毕了。(p.s.当然这个文件未来可能还是会有改动,届时可能会和本篇文章描述的内容不符) 经过上面这几轮代码的整理,这个Sass文件才真的很Sass了。回顾这次整理的过程,先后用到了变量、运算、嵌套、重用属性等Sass的特性,简单明了,而且sass --watch命令、sass-convert命令可以方便的对文件进行监听和格式转换。我几乎在感觉不到学习成本的情况下提高了开发效率。这里也推荐大家试试看。

2013/6/5
articleCard.readMore

Connect中间件使用手册

以下内容大多译自Connect官网 2013-06-02 Connect是基于Node的中间件框架(middleware framework),提供超过18种官方中间件以及更多的第三方中间件。 示例: var app = connect() .use(connect.logger('dev')) .use(connect.static('public')) .use(function(req, res){ res.end('hello world\n'); }) .listen(3000); 安装方式: $ npm install connect 依次介绍官方中间件 1. 日志 logger ​ 服务器请求日志,支持自定义格式,支持传入 options 选项对象或 format 字符串。 选项 ​ format 表示日志格式的字符串,由各种记号(token)组合而成 stream 表示输出到哪里。默认是 stdout buffer 表示缓冲的时间间隔,默认为 1000ms immediate 是否在请求(request)的时候立即写日志,而不是在回应(response)的时候 记号(Tokens) ​ :req[header] (如 :req[Accept]) :res[header] (如 :res[Content-Length]) :http-version、:response-time、:remote-addr、:date、:method、:url、:referrer、:user-agent、:status 默认的日志格式(Formats) ​ default 、 short 、 tiny 其中 default 代表的格式是: :remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" 另外还有 dev 格式,可以着色输出响应状态,开发时适用。 其它 ​ 记号和格式都是可以自定义更多的,通过 connect.logger.token(name, function (req, res) {...}) 和 connect.logger.format(name, stringOrFunction) 更多细节请移步至此 2. 防止跨域伪造请求 csrf ​ 默认情况下该中间件会生成一个名为“_csrf”的记号,该记号可以作为请求的状态、表单提交的隐藏属性值或查询字符串等等,并在服务器端与 req.session._csrf 属性进行核对。如果核对出错,则会出现403错误。 默认的 value 函数会以此核对 bodyParser() 中间件生成的 req.body 、 query() 生成的 req.query 以及名为“X-CSRF-Token”的头信息。 该中间件需要会话支持,因此必须出现在 session() 和 cookieParse() 中间件之后。 默认的 defaultValue() 实现如下: function defaultValue(req) { return (req.body && req.body._csrf) || (req.query && req.query._csrf) || (req.headers['x-csrf-token']); } 更多细节请移步至此 3. 压缩 compress ​ Gzip压缩的中间件 支持的方法都在 connect.compress.methods 中,通过 connect.compress.filter(req, res) 方法判断文件是否需要压缩,默认压缩Content-Type含json、text或javascript的文件。 更高级的操作是可以将具体压缩方法的参数通过options参数传进去: connect.compress({ chunkSize: ..., // default 16*1024 windowBits: ..., level: ..., // 0-9 memLevel: ..., // 1-9 strategy: ... }) 更多细节请移步至此 4. HTTP基础认证 basicAuth ​ 提供回调函数 connect.basicAuth(function (user, pass) {...}) ,如果这个回调函数返回 true ,则获得访问权限。 提供异步的调用方式 connect.basicAuth(function (user, pass, callback)) 直接有效的单一用户名密码的方式 connect.basicAuth('username', 'password') 更多细节请移步至此 5. 主体解析器 Body Parser ​ 可扩展的解析器,对请求的body进行解析。支持_application/json_、application/x-www-form-urlencoded、multipart/form-data 其等同于: app.use(connect.json()); app.use(connect.urlencoded()); app.use(connect.multipart()); 更多细节请移步至此 5.1 json ​ _application/json_解析器,并将结果放至 req.body 选项 ​ strict 是否严格解析,当值为 false 时,理论上 JSON.parse() 能解析的数据都是被允许的 reviver 用作 JSON.parse() 方法的第二参数 limit 字节数限制,默认不开启 更多细节请移步至此 5.2 urlencoded ​ _application/x-www-form-urlencoded_解析器,并将结果放至 req.body 选项 ​ limit 字节数限制,默认不开启 更多细节请移步至此 5.3 multipart ​ _multipart/form-data_解析器,并将结果放至 req.body 和 req.files 选项 ​ limit 字节数限制,默认不开启 defer 延时处理并不等 end 事件触发就调用 req.form.next() 展示大表单。该选项在需要绑定 progress 事件时可用。 更多细节请移步至此 6. 超时时间 timeout ​ 用法: connect.timeout(ms) 。如果请求超时则指向408错误。 另, req 对象会多一个 req.clearTimeout() 方法,用来在必要的情况下取消计时。 更多细节请移步至此 7. Cookie解析器 cookieParser ​ 解析头中的_Cookie_并将结果放至 req.cookies 。你还可以通过 connect.cookieParser(secret) 中的 secret 参数对cookie进行加密。该密码可以通过 req.secret 进行取值。 更多细节请移步至此 8. 会话 session ​ 详情略。 更多细节请移步至此 9. 基于cookie的会话支持 cookieSession ​ connect.cookieSession({ secret: 'tobo!', cookie: { maxAge: 60 * 60 * 1000 }}); 选项 ​ key cookie名,默认是 connect.sess secret 密码 cookie 会话cookie的设置,默认是 { path: '/', httpOnly: true, maxAge: null } proxy 信任反向代理 清除会话 ​ req.session = null; 更多细节请移步至此 10. 支持伪造HTTP方法 methodOverride ​ 当检查到方法重载的时候,把原方法存入 req.originalMethod ,检查的字段可以通过参数 key 设置,默认为 _method connect.methodOverride(key) 更多细节请移步至此 11. 响应时间 responseTime ​ 计算响应时间并展示为 X-Response-Time 头 更多细节请移步至此 12. 静态服务缓存 staticCache ​ 在内存中建立static中间件的缓存。默认最大缓存对象为128个,每个对象的最大体积是256k,总共大约32mb。 选项 ​ maxObjects 最大缓存对象个数,默认128个 maxLength 最大缓存对象体积,默认256kb 更多细节请移步至此 12.1 静态文件服务 static ​ 为给定的 root 路径提供静态文件服务,例如 connect.static(__dirname + '/public', {maxAge: 86400000}) 选项 ​ maxAge 浏览器缓存时间,默认是 0 hidden 是否允许访问隐藏文件,默认是 false redirect 路径是目录时是否在结尾自动加 / ,默认是 true MIME表 ​ 展示MIME模块,可读写 connect.static.mime 更多细节请移步至此 13. 目录 directory ​ 列出目录的文件列表 选项 ​ hidden 是否显示点(.)开头的文件,默认是 false icons 是否显示文件图标,默认是 false filter 过滤文件的函数,默认是 false 图标文件在 lib/public/icons/ 目录中 其它 ​ connect.directory.html() 输出html格式的内容 connect.directory.json() 输出json格式的内容 connect.directory.plain() 输出文本格式的内容 更多细节请移步至此 14. 虚拟主机 vhost ​ 例如: connect() .use(connect.vhost('foo.com', fooApp)) .use(connect.vhost('bar.com', barApp)) .use(connect.vhost('*.com', mainApp)) 更多细节请移步至此 15. 站点图标 favicon ​ 默认图标为 lib/public/favicon.ico ,可更改,调用方式: connect.favicon('public/favicion.ico', {maxAge: 86400000}) 选项 ​ maxAge 过期时间,默认是1天(86400000) 更多细节请移步至此 16. 请求大小限制 limit ​ 限制请求的body字节数,可传入一个数字或代表容量大小的字符串,比如: 5mb 、 200kb 、 1gb connect.limit('5.5mb') 更多细节请移步至此 17. 查询字符串 query ​ 自动解析查询字符串,生成 req.query 更多细节请移步至此 18. 错误处理 errorHandler ​ 灵活的错误处理机制,开发环境下提供出错信息和栈追踪,回应信息支持纯文本、HTML和JSON 在_text/plain_的情况下回应文本格式的错误信息 在_application/json_情况下,回应 { "error": error } 在允许的情况下回应HTML错误信息 更多细节请移步至此 (完)

2013/6/2
articleCard.readMore

实践

上周末在图灵的技术沙龙分享了自己Node.JS的一些项目实践心得。 Node.JS这东西说起来也是3年前就听说的东西了,最近一段时间才真正拿它做东西。这种感觉既熟悉又陌生,熟悉在听大家谈过无数次,陌生在自己没怎么亲自动过手。这回的一些尝试和项目实践,让我更多的了解了这门技术,也更好的了解了我自己。 分享的在线链接在此:/slides/node-js-practice,这里就不重复其中的内容了。 我想更多说的是,我们有幸在HTML5快速发展的时代,这里几乎每天都会有新的规范、新的工具、新的库、新的框架、新的理论。玲琅满目,目不暇接。也许我们每天走马灯似的看它们,都不一定看得过来。我感觉自己就长期处于这种状态。可是当这些东西都只是我们眼前的匆匆过客,来不及细细体验、品味其中的内涵,那和从来都没看过相比,有多少实质上的差别呢? 我觉得这里有两件事情值得考虑:第一,要再多些时间在新技术的关注和尝试上;第二,有选择性的深入其中。 先说第一点,我们不能始终沉醉在对现有技术或业务的娴熟之中,同时要相信技术和产品、设计、市场、商务一样,可以驱动业务的发展和进步。我们唯有带着这样的信念去工作,去和同事交流,去和领导沟通,去在团队里一起探讨问题,才会令自己有这种空间和时间。凡事都有难处,但总要走出这第一步。 第二,前端这个词逐渐由html/css/js三门语言和基本ps技巧的集合,变成一个无限宽泛的概念。我们在鼓励全面发展的同时,也不能一把抓,也需要量力而行,循序渐进,找到属于自己的突破口,找到和自身工作最佳的结合点。最关键的是,要有实践的机会。 人生有时难免会站在命运的十字路口,左右为难。最后发现,真正让自己踏实下来的,往往是这些真材实料的东西。

2013/5/22
articleCard.readMore

巧用 RequireJS Optimizer 给传统的前端项目打包

r.js 本是 RequireJS 的一个附属产品,支持在 NodeJS、Rhino 等环境下运行 AMD 程序,并且其包含了一个名为 RequireJS Optimizer 的工具,可以为项目完成合并脚本等优化操作。 r.js 的介绍中明确写道它是 RequireJS 项目的一部分,和 RequireJS 协同工作。但我发现,RequireJS Optimizer 提供了丰富的配置参数,可以让我们完全跳出 AMD 和 RequireJS 程序的束缚,为我们的前端程序服务。 RequireJS Optimizer 常规用法 ​ 首先,简单介绍一下 RequireJS Optimizer 的“正派”用法 (以 NodeJS 环境为例): 事先写好一个配置文件,比如 config.js,它是 JSON 格式的,常用属性有: { // 程序的根路径 appDir: "some/path/trunk", // 脚本的根路径 // 相对于程序的根路径 baseUrl: "./js", // 打包输出到的路径 dir: "../some/path/release", // 需要打包合并的js模块,数组形式,可以有多个 // name 以 baseUrl 为相对路径,无需写 .js 后缀 // 比如 main 依赖 a 和 b,a 又依赖 c,则 {name: 'main'} 会把 c.js a.js b.js main.js 合并成一个 main.js modules: [ {name: 'main'} ... ] // 通过正则以文件名排除文件/文件夹 // 比如当前的正则表示排除 .svn、.git 这类的隐藏文件 fileExclusionRegExp: /^\./ } 然后运行: node r.js -o config.js 这时 RequireJS Optimizer 就会: 把配置信息的 modules 下的所有模块建立好完整的依赖关系,再把相应的文件打包合并到 dir 目录 把所有的 css 文件中,使用 @import 语法的文件自动打包合并到 dir 目录 把其它文件复制到 dir 目录,比如图片、附件等等 我已经把 RequireJS 和 r.js 整套东西用到了 H5Slides 上。觉得蛮方便的。 不过工作中的前端开发工作并不是绝对理想化的,有些旧的项目,并不是 AMD 的模块化开发方式,而是传统的 js 程序,开发一个页面时可能需要一口气引入三到五个 css 文件、十来个 js 文件…… 上线的时候为了减少流量及 HTTP 请求数又需要把代码尽可能重用和合并。这个时候就需要一个方便快捷的打包工具帮助我们了,下面就介绍一下 RequireJS Optimizer 是如何完成这项工作的。 用到的几个关键参数 ​ 说到这里,必须要额外介绍几个 RequireJS Optimizer 的参数了: modules[i].include ​ modules: [ { name: "main", include: ["d", "e"] } ] 这里的 include 字段提供了“强制建立依赖关系”的功能,也就是说,即使在 main.js 的代码里没有依赖 d.js 和 e.js,它们也会在合并代码的时候插入到 main.js 的前面 skipModuleInsertion ​ 在介绍这个参数之前需要说明的是,RequireJS Optimizer 有一个很智能的功能,就是为没有写明 define(...) 函数的模块代码自动将其放入 define(...) 之中。如果我们写明: skipModuleInsertion: true 则这种处理将会被取消。 onBuildRead ​ 这个参数可以定义一个函数,在处理每个 js 文件之前,会先对文件的文本内容进行预处理。比如下面这个例子里,我会把 main.js 里的代码全部清除: onBuildRead: function (moduleName, path, contents) { if (moduleName === 'main') { contents = '/* empty code */'; } return contents; } 巧妙应用到传统项目 ​ 这时,我们的资源已经足够了。比如我现在的项目有: 1 个 html index.html 代码: <!doctype html> <html> <head> <meta charset="utf-8"> <title>Index</title> <link rel="stylesheet" href="css/a.css"> <link rel="stylesheet" href="css/b.css"> </head> <body> ... <script src="js/a.js"></script> <script src="js/b.js"></script> <script src="js/c.js"></script> </body> </html> 2 个 css css/a.css css/b.css 3 个 js js/a.js js/b.js js/c.js 1 个图片文件夹 images 合并 css 文件 ​ 新建一个 css 文件,叫 css/main.css,内容为: @import url(a.css); @import url(b.css); 然后把 index.html 中的 2 个 <link> 标签改成一个 <link rel="stylesheet" href="css/main.css"> 合并 js 文件 ​ 合并 js 文件的步骤略复杂些。首先也是新建一个 js 文件,叫 js/main.js: document.write('<script src="js/a.js"></script>'); document.write('<script src="js/b.js"></script>'); document.write('<script src="js/c.js"></script>'); 然后把 index.html 中的 3 个 <script> 标签改成一个 <script src="js/main.js"></script> 接下来就是配置打包工具的时间了。 禁止自动补齐 define(...) 的头尾 ​ skipModuleInsertion: true 强制建立依赖 ​ modules: [{name: 'main', include: ['a', 'b', 'c']}] 这样打包出来的 main.js 是这样的: // code from a.js // code from b.js // code from c.js document.write('<script src="js/a.js"></script>'); document.write('<script src="js/b.js"></script>'); document.write('<script src="js/c.js"></script>'); 打包时去掉多余的 js/main.js 的代码 ​ onBuildRead: function (moduleName, path, contents) { if (moduleName === 'main') { contents = '/* empty code */'; } return contents; } 这样的话,打包工具就会把 document.write(...) 的代码去掉,得到干净的 // code from a.js // code from b.js // code from c.js /* empty code */ 运行 node r.js -o config.js 就可以得到一个打包成功的项目了,并且打包前后的代码都可以正常运行 附件是这个项目例子的源代码:project.zip

2013/3/30
articleCard.readMore

编辑器小调查结果

我在侧边栏放了一阵子编辑器的小调查,时间过去比较久了,是时候统计一下了,供大家参考: 我一共放了5个默认选项,使用情况排名依次是: SublimeText 2 Notepad++ Dreamweaver EditPlus Vim 为了避免自己孤陋寡闻,把其它主流编辑器遗漏,所以我保留了“其它”这个选项,从结果看,确实有疏漏,其中不乏Eclipse、Emacs、WebStorm、Aptana这样的神器,还看到了一个熟悉的身影Expression Web,那是我之前使用过的一款编辑器,微软出的,还不错:

2013/2/17
articleCard.readMore

[译]JSLint 文档

译自:https://www.jslint.com/lint.html 什么是JSLint? ​ JSLint 是一个用来查找各种 JavaScript 程序中的问题的 JavaScript 程序。它是一个代码之类工具。 在早些年的 C 语言中,有些程序的常见错误是主流的编译器无法抓住的。所以出现了一个名叫 lint 的附带程序,可以通过搜索源文件寻找错误。 随着语言的成熟,其定义的健壮性足以消除一些不安因素,编译器也在问题警告方面越做越好,lint 也不再需要了。 JavaScript 是一个年轻的语言。它原本只是用在网页上完成一些无需劳驾 Java 的小任务。但 JavaScript 是一个强大得惊人的语言,现在它已经在大项目中派上用场了。当项目变得复杂之后,之前从易用角度出发的语言特性就带来了一些麻烦。这是一个为 JavaScript 而生的 lint 呼之欲出:它就是 JSLint,一个检查 JavaScript 语法、判断 JavaScript 语法有效性的工具。 JSLint 会拿来一段 JavaScript 源代码并对其进行检索。一旦发现问题,它就会返回一则消息,用来描述这个问题以及源代码中的大概位置。发现的问题不一定是,但通常是语法上的错误。JSLint 通过一些代码规范来杜绝结构性的问题。这并不证明你的程序是正确的,只是提供另一种发现问题的眼光。 JSLint 定义了一个专业的 JavaScript 的子集,它比 ECMAScript 标准第三版的定义更严格,和 JavaScript 编码规范中的建议相对应。 JavaScript 是一个粗中有细的语言,它比你想象中的更好。JSLint 帮助你回避很多问题,在这个更好的语言中撰写程序。JSLint 会拒绝一些浏览器支持的程序,因为浏览器并不关心代码的质量。你应该接受 JSLint 的所有建议。 JSLint 在 JavaScript 源代码、HTML 源代码、CSS 源代码或 JSON 文本中都可以运行。 全局变量 ​ JavaScript 的最大问题就是其依赖的全局变量,特别隐含的全局变量。如果一个变量没有被显性的声明 (通常是通过 var 语句),则 JavaScript 会假定这个变量是全局变量。这会掩盖拼写错误等其它问题。 JSLint 希望所有的变量和函数都要在使用或调用之前被声明。这样我们就可以探测隐含着的全局变量。同时,这也让程序的可读性增强了。 有的时候一个文件会依赖于在别处定义好的全局变量或全局函数。这时你可以通过一个 var 语句让 JSLint 识别该程序依赖的这些全局函数和对象。 一个全局声明大概如下所示: var getElementByAttribute, breakCycles, hanoi; 该声明应该出现在靠近文件最上方的位置,且必须出现在使用这些变量之前。 同时,我们有必要在一个变量被赋值之前通过 var 语句对其进行声明。 JSLint 同样可以识别一段 /*global*/ 指令,该指令可以为 JSLint 注明在该文件中使用但在其它文件中定义好的变量。该指令可以包含一串变量名,用逗号隔开。每个名字可以跟随一个可选的冒号以及 true 或 false,true 表示该变量可以被该文件赋值,而 false 则表示该变量不能被赋值 (这是默认行为)。在函数作用域也是如此。 有些全局变量可以被你预定义。选择假设为浏览器 (browser) 选项可以预定义浏览器提供的标准的全局属性,比如 document 和 addEventListener。它等同于: /*global clearInterval: false, clearTimeout: false, document: false, event: false, frames: false, history: false, Image: false, location: false, name: false, navigator: false, Option: false, parent: false, screen: false, setInterval: false, setTimeout: false, window: false, XMLHttpRequest: false */ 选择假设为 Node.js (node) 选项可以预定义 Node.js 环境下的全局变量。它等同于: /*global Buffer: false, clearInterval: false, clearTimeout: false, console: false, exports: false, global: false, module: false, process: false, querystring: false, require: false, setInterval: false, setTimeout: false, __filename: false, __dirname: false */ 选择假设为 Rhino (rhino) 选项可以预定义 Rhino 环境下的全局属性。它等同于: /*global defineClass: false, deserialize: false, gc: false, help: false, load: false, loadClass: false, print: false, quit: false, readFile: false, readUrl: false, runCommand: false, seal: false, serialize: false, spawn: false, sync: false, toint32: false, version: false */ 选择假设为 Windows (windows) 选项可以预定义 Microsoft Windows 提供的全局属性。它等同于: /*global ActiveXObject: false, CScript: false, Debug: false, Enumerator: false, System: false, VBArray: false, WScript: false, WSH: false */ 分号 ​ JavaScript 使用近似于 C 语言的语法,它要求使用分号来分割确定的语句。JavaScript 试图通过一个自动插入分号的机制让这些分号变得可有可无。这是比较危险的,因为它可以掩盖你的错误。 和 C 一样,JavaScript 有 ++ 和 -- 和 ( 操作符,这些操作符可以作为前缀或后缀。分号可以用来消除这里的二义性。 在 JavaScript 中,一个折行可以是一个空格,也可以当做分号使用,两者容易产生混淆。 JSLint 希望除了 for、function、if、switch、try和while,每个语句都以 ; 结尾。同时 JSLint 不希望看到没有必要的分号或空白语句。 逗号 ​ 我们可以用逗号操作符写出极度巧妙的表达式。同时也可以掩盖一些程序上的错误。 JSLint 希望逗号只用来当做分隔符,而不是操作符 (for 语句中的初始化部分和自增部分除外)。数组直接量里不希望有被省略的元素,多余的逗号不应该被使用,逗号不应该出现在数组直接量或对象直接量的最后一个元素的后面,因为一些浏览器无法正常识别。 作用域 ​ 在很多语言中,每个块都产生一个作用域,块内产生的变量在块外是看不到的。 在 JavaScript 中,块并不产生新的作用域,只有函数作用域。在一个函数中任意位置声明的变量都在整个函数中可见。JavaScript 的块混淆了有经验的程序员,并导致了错误的出现,因为用相似的语法做了一个错误的承诺。 JSLint 希望 function、if、switch、while、for、do 和 try 语句之外不要产生其它块。 在有块级作用域的语言里,通常都推荐变量声明在第一次使用的地方。但是因为 JavaScript 没有块级作用域,所以明智的选择就是在函数的最顶端声明所有的函数变量。这里推荐每个函数用单一的一个 var 语句完成变量声明。这个要求可以通过 vars 选项取消掉。 必要的块 ​ JSLint 希望 if、while、do 和 for 语句都由块生成,{ 也就是说,语句是被大括号包住的 }。 JavaScript 允许一个 if 语句这样书写: if (condition) statement; 这个格式是公认的:当很多程序员都基于相同的代码工作时,会给整个项目带来很多错误。这也是为什么 JSLint 希望如下使用块: if (condition) { statement; } 经验告诉我们这个格式更保险一些。 表达式语句 ​ 我们希望一个表达式语句是一个赋值或一个函数/方法调用或 delete。其它表达式语句都可能导致错误。 for in ​ for in 语句允许在一个对象的所有属性名内进行循环。不幸的是,这样的循环也会覆盖到其原型链中所有被继承而来的属性。其副作用就是当我们只关心数据的时候,它连方法函数也会处理一遍。如果此时该程序没有任何预防措施,那么就会导致失败。 每个 for in 语句的主体部分都应该包裹一个起过滤效果的 if 语句。该语句可以选择特殊类型或范围的值、或排除函数类型的属性、或排除原型的属性。比如: for (name in object) { if (object.hasOwnProperty(name)) { .... } } switch ​ 一个 switch 语句中常见的错误就是忘记在每个条件的结尾写上 break 语句,导致程序没有及时跳走。JSLint 希望语句的下一个 case 之前或 default 之前必须有下面三个当中的一个:break、return、throw。 var ​ JavaScript 允许 var 在一个函数内的任何位置完成定义。JSLint 的要求则会更严格一些。 JSLint 希望一个 var 只被声明一次,并且是在使用之前被声明。 JSLint 希望一个 function 在使用之前被声明。 JSLint 希望函数的参数不要被声明为变量。 JSLint 不希望 arguments 数组被声明为 var。 JSLint 不希望一个变量被定义在块中。这是因为 JavaScript 的块并不具有块级作用域。这会造成意料之外的结果。把所有的变量都定义在函数的最顶部。 with ​ with 语句的初衷是提供一个访问对象嵌套属性的简写方式。不幸的是,当我们设置新属性时这个行为非常糟糕。永远不要使用 with 语句。请用一个 var 替代之。 = ​ JSLint 不希望看到 if、for、while 或 do 语句的条件判断中出现赋值语句。这是因为似乎: if (a = b) { ... } 的本意是: if (a == b) { ... } 当我们难以从常用语句中察觉出明显的错误时,是很难把程序写对的。 == 和 != ​ == 和 != 操作会在比较之前做类型转换。这是不提倡的,因为它导致 ' \t\r\n' == 0 的结果是 true。这会掩盖一些类型错误。JSLint 无法依赖 == 进行判断,所以最好别再使用 == 和 != 了,以后都用更可靠的 === 和 !== 操作替代之。 如果你只是在乎一个值是真的还是假的,那么可以使用简写方式,比如把: (foo != 0) 替换为: (foo) 而将: (foo == 0) 替换为: (!foo) 我们有一个 eqeq 选项,允许使用 == 和 !=。 标签 (labels) ​ JavaScript 允许任何语句拥有标签,标签拥有各自的命名空间。JSLint 的要求会更严格。 JSLint 希望标签只出现在响应 break、swtich、while、do 和 for 的语句上。JSLint 希望标签可以同变量和参数区分开来。 无法到达的代码 ​ JSLint 希望一个 return、break、continue 或 throw 语句后面都会跟着一个 } 或 case 或 default。 混乱的加号减号 ​ JSLint 希望 + 不会紧跟着 + 或 ++,而 - 不会紧跟着 - 或 --。少写一个空格就会把 + + 变成 ++,这种错误是很难被发现的。为了避免混乱,请善用括号。 ++ 和 -- ​ 自增操作符 ++ 和自减操作符 -- 是公认的会鼓励过度使用技巧而导致糟糕的代码。它们是导致病毒和安全威胁的错误构建。同样的,乱用前自增和后自增会产生 off-by-one 的错误,排查这样的错误是极为困难的。我们有一个 plusplus 选项来允许使用这些操作符。 位操作符 ​ JavaScript 没有整数类型 (*),但是其具备位操作符。位操作符会先把操作对象由浮点数转换回整数,所以这样的位操作效率远不及 C 或其它语言。位操作符极少用在浏览器应用里。与逻辑操作符的相似性也会掩盖一些程序的错误。bitwise 选项允许使用 <<、>>、>>>、~、&、| 这些操作符。 eval 是魔鬼 ​ eval 函数 (及其相似的 Function、setTimeout 和 setInterval) 提供了 JavaScript 编译器的访问形式。这在一些情况下是有必要的,但是在大多数情况下它代表了相当糟糕的代码。eval 函数是 JavaScript 最不应该被使用的特性。 void ​ 在大多数近似于 C 的语言中,void 是一个类型。在 JavaScript 中,void 是一个前缀操作符,它总是返回 undefined。JSLint 不希望看到 void 因为它具有迷惑性且没有实质的用处。 正则表达式 ​ 正则表达式写起来既简洁又神秘。JSLint 会检测一些可能导致可移植性问题的问题。同时会推荐把具有视觉混淆性质的字符全部转义。 JavaScript 语法中,正则表达式直接量会多写一对 / 字符。为了避免混淆,JSLint 希望一个正则表达式字面量的开头是一个 ( 或 = 或 : 或 , 字符。 构造函数和 new ​ 构造函数是设计为通过 new 前缀使用的函数。new 前缀基于函数的 prototype 创建一个新的对象,并把函数隐性提供的 this 参数绑定到那个对象。如果你忽略了 new 前缀的使用,则不会有新的对象被创建,而 this 则会绑定到全局对象上。这是非常严重的错误。 JSLint 强制规范构造函数的函数名大写开头。JSLint不希望看到一个函数调用的函数名是大写开头但没有 new 前缀。JSLint 也不希望看到 new 前缀用在一个函数名不是大写开头的函数上。这一要求可以通过 newcap 选项关掉。 JSLint 不希望看到 new Number、new String、new Boolean 的包裹形式。 JSLint 不希望看到 new Object,用 {} 替换之。 JSLint 不希望看到 new Array,用 [] 替换之。 属性 ​ 因为 JavaScript 是个弱类型动态对象语言,所以不太可能在编译的时候判定一个属性名是否拼写正确。JSLint 在这方面提供了一些帮助。 在其报告的最底端,JSLint 显示了一个 /*properties*/ 指令。它包含了一些名称和字符串直接量,它们是所有使用过的点标记、下标标记和对象直接量中用来命名对象属性的。你可以查阅这个列表来找到拼错的词。这是个比较简单的办法。 你还可以复制 /*properties*/ 指令到你的脚本文件的顶端。JSLint 会根据这个列表检查所有的属性名。这样,你就可以使用 JSLint 检查拼写错误了。 比如: /*properties charAt, slice */ 不安全字符 ​ 有一些字符是不同的浏览器显示起来不一致的,在放入字符串之前必须先转义。 \u0000-\u001f \u007f-\u009f \u00ad \u0600-\u0604 \u070f \u17b4 \u17b5 \u200c-\u200f \u2028-\u202f \u2060-\u206f \ufeff \ufff0-\uffff 不检查的内容 ​ JSLint 不会做流程分析,也不会判断变量在使用之前是否已经被赋值。这是因为变量都有默认值 (undefined),很多应用本身就是这样处理的。 JSLint 不会做任何类型的全局分析,不会尝试判断伴随 new 使用的函数是真正的构造函数 (只是遵循大小写规范),不会检查属性名拼写是否正确 (只是针对 /*properties*/ 指令进行匹配)。 HTML ​ JSLint 可以处理 HTML 文本。它可以找到包含在 <script> ... </script> 标签中的 JavaScript 内容。也可以通过 JavaScript 找到 HTML 内容中公认的问题: 所有的标签必须小写。 所有的标签必须闭合 (比如 </p>)。 所有的标签必须正确的嵌套 必须用实体 &lt; 表示字面量 >。 JSLint 比 XHTML 的要求要低,但是比主流浏览器的要求要严格。 JSLint 也会检查 '</' 在字符串字面量的出现情况,你应该用 '<\/' 替换之。额外的反斜杠会被 JavaScript 编译器忽略掉,但 HTML 解析器不会。类似的技巧都不是必要的,但就是这样。 这里有一个 fragment 选项,用来检查一个合格的 HTML 片段。如果 adsafe 选项也选用,则片段必须是一个遵守 ADSafe widget 规则的 <div>。 CSS ​ JSLint 可以检查 CSS 文件。它检查 CSS 文件的第一行是不是: @charset "UTF-8"; 这个特性是试验性的。请把任何问题或束缚报告出来。这里有一个 css 选项,可以容忍一些非标准的使用习惯。 选项 ​ JSLint 提供了很多选项,它们控制了其操作和敏感度。在网页版中,选项是可以通过复选框进行勾选的。 我们还提供了通过构造 /*jslint*/ 指令和 /*properties*/ 指令的方式进行辅助。 当 JSLint 被当做函数调用时,它接受一个 option 对象参数,这个参数允许你判定你可接受的 JavaScript 子集。网页版的 JSLint 在 https://www.JSLint.com,就是这样工作的。 选项还可以在 /*jslint*/ 指令中被定义: /*jslint nomen: true, debug: true, evil: false, vars: true*/ 选项指令起始于 '/*jslint'。注意 j 前面没有空格。本规范包含了一系列的键值对,这些键是 JSLint 的选项,值是 true 或 false。indent 选项可以取一个数字。一个 /*jslint*/ 指令优先于 option 对象。指令遵照函数作用域。 (表格略,详见:https://www.jslint.com/lint.html#options) 报告 ​ 如果 JSLint 可以完成检查,那么它会生成一个函数报告。其列出下面每个函数: 起始行号。 名称。如果是匿名函数,JSLint 会“猜”出这个名称。 参数。 Closure:被声明的变量和参数之中,被子函数使用的部分。 Variables:被声明并只在该函数中使用的变量。 Exceptions:被 try 语句声明的变量。 Unused:被声明但从未在该函数中使用的变量。这可能意味着一个错误。 Outer:被其它函数声明且在该函数中使用的变量。 Global:在该函数中使用的全局变量。把这个用量降到最少。 Label:该函数中使用的语句标记。 报告还会包含一个使用过的属性名列表。这里是JSLint的消息列表。

2013/2/17
articleCard.readMore

烟火——写给蛇年的傲游和我

世界上没有怯懦的高楼 ——1976《烟火》 看完春晚正准备要睡觉,看到祥子一条微博:八千块的烟火换来一小时的快乐,多少的代价可以换来一生的幸福。于是我就这样陷入了沉思。 祥子我恨你…… 此时此刻,估计整个城市,乃至全中国的“一小时的快乐”,都已经告一段落了。这对我来说,也意味着,去年的成绩、收获、经验教训,也停留在这里了;新年的奋斗史,从这一刻,就要开始书写了。 每到过年的时候,也是让我猛一抬头,想起自己和过去的老师同学又多一整年不见的时候,想起自己在傲游又多待了一年的时候,想起自己和一年前相比又度过了一年美好时光的时候。 来傲游第六年了,去年这个时候的五周年纪念的情形仿佛还历历在目。我也在此兑现自己一年前的承诺,给自己在傲游的第六年留个“快照”。 现在小学时期的稳定性记录已经被打破了,人生会不会从此变得没有追求了呢?呵呵 在我眼里,这一年,自己和公司都发生了很多变化。公司的办公区又一次扩张了,从“两居室小户型”演变成“两套大房子”了;高管走了一批、也来了一批;有些部门拆散了,有些部门合并了,有些部门洗牌了……无非都为的是个活字。我自己呢,团队角色和工作要求也发生了不小的变化,无非为的是能继续走下去。其实这些都不算什么伟大的志向。所以,尽管过程轰轰烈烈,结果其实很惨淡。 这和我刚来公司时的感受已经截然不同了。刚来傲游的时候,觉得像天堂一样,哪儿都好,各种优越感,每天无忧无虑,技术交流的时候、同学聚会的时候、校园招聘等各种时候,一说自己是傲游的,大家都好待见你;现如今,觉得公司哪儿都有问题,哪儿都做得不好,哪儿都有改进空间。上周一个好朋友的电脑重装系统了,跟我说他把Program Files/Maxthon整个文件夹都备份了,问我怎么恢复收藏——聊到最后我们两个都很震惊:他震惊自己最信任的傲游浏览器把他的收藏搞丢了,我震惊他不知道我们有傲游账户和在线收藏。 烟火固然美丽,但也稍纵即逝。 去年年初的时候看到过一句话:“即使如日中天的Apple,也一定会有股票下跌的那一天” (如今它已经跌了)。一家公司再好,一份工作再适合你,也会有不如意的地方。其实问题总是会有的,如果每次面对问题的时候都选择逃避,这个问题不会自动消失,反而会永远缠着你。 我觉得去年一年,是我让自己从理想乐观回归现实的一年。从某种角度将,它让我看清傲游是一个有很多问题的公司,同时这些问题也是大家需要很大的勇气去承认、面对和解决的——这其实对于我,以及每一个傲游人来说,其实非常重要。它比我们去年赚了多少钱、发展了多少用户、打开了多大的市场这些成绩,都更重要。 所以,做事不能只看眼前成绩,不然什么也做不成。面对问题,不能逃避,只有勇敢面对,久而久之,养成习惯,就可以战胜一切! 过去的六年,对我来说,就当是一场烟火表演吧。璀璨、辉煌、这一切的一切,都无法奠定你未来的坦途。幸福的人生,需要的不是一场价值连城的烟火表演,也不是每年一度的烟火表演,而是一个持续的状态,一步一个脚印的存在感和成就感。 是时候面对久违的挑战了。

2013/2/10
articleCard.readMore

小秀个人的全年摄影作品 (共15张)

今年年初家里买了台微单相机,外加一台iPhone,我也算是个曾经摸了摸镜头的人。相比起周遭的摄影发烧友,我觉得自己真的很业余,以至于我自己说出摄影作品这个词的时候都有点心虚:不就是拍了几张照片么。还整的跟是回事儿似的。 不久之前刚在自己微博上晒了几张自己拍照片时的个人恶趣味,算娱乐一下吧。下面几张是我真的认真挑选过的,如果诸位摄影达人看到觉得拍得太烂,还求轻拍 ^_^ 车水马龙 多彩地砖 蓝天·高楼 店铺一角 西湖夜景 厦门夜景 厦门夜景 两款涂鸦,主要不是我拍的好,是我喜欢这个涂鸦的内容 ​ 两张全景模式 ​ 两张适合手机屏保的 ​ 做个幸福的吃货!Yeah! 最后来张自己的 ​ 照照镜子,看看自己

2013/1/4
articleCard.readMore

2012年终毫无正能量的总结

“丰功伟绩”、“甜言蜜语”就不在这里恶心大家了,如果有人问我你幸福吗,我会说我很幸福,以及关于我怎么幸福的长篇大论。但我不会把下面的话也说出来。 乱七八糟的2012年就这样糊里糊涂的过去了。 今年发生了不少冠冕堂皇的大事儿,但好像没什么实质性的收获。可能此时此刻的我心思还是在事业上多一些,但无奈力不从心。作为一个有家有老婆的人,不能加班,不能熬夜,工作上几乎没了弹性,想冲冲不起来,有劲儿不能一口气使出来;另外我们的休闲娱乐需求也很旺盛,下馆子看电视电影到处旅游总是好的,可它们基本上填满了我的业余时间,没有充足的时间用心学习这种感觉比什么都糟糕。这些事儿其实也不是我改变不了,更多的是自己也不忍心改变吧。 我觉得这是我觉得2012糊里糊涂的主要原因。 第二点原因是我觉得自己变懒了也变邋遢了。搬了家这么久也结婚了这么久,很多早就想请客的人也一直没请到。每到周末就跟自己怕麻烦的心妥协了。洗碗洗衣服倒洗脚水擦地吃早餐之类的事情也越来越不积极了,我最近反思了一下,可能是太想把烂摊子都寄托给老婆来收拾了,家里的脏活累活总要有人做,己所不欲勿施于人,以后还是身先士卒吧。 在工作上,我比较欣慰的是自己有机会也有勇气接触更广泛的技术领域,让自己的知识面更广,更充实,但达到比较理想的状态是需要时间的。为了能够掌握更广泛的开发技能,我正在重读很多计算机的基本课程。在上学的时候它像是我毕业路上的敌人,我在乎的只是战胜那些题目;如今重读,它们更像是我的朋友,为我弥补一块块盲区,让我面对困难的时候更加自信。真的有点二次启蒙的感觉,我突然觉得这才是真正的编程。我希望在新的一年我可以处于一种低调踏实追求卓越的心态,不要像今年这样疯疯癫癫的,说得多做得少。 公司这一年的形势很微妙,我觉得全公司现在最纠结的地方在于,我们想做个典型的带有鲜明中国特色的公司,还是一个超凡脱俗的洋气公司。说白了就是一群屌丝和另一群高富帅白富美凑在一起想搞个团队活动,大家一起商量是去吃西餐呢还是吃大排档呢。正在犹豫之时,眼瞅着周围的人眼疾手快,把西餐厅的位子订光了,把大排档也挤了个水泄不通,顿时就都傻眼了,可还是没有相互妥协的迹象。咳,只说这么多了… 别的都还行。 2013不能比今年更烂了。

2013/1/1
articleCard.readMore

标签?ID?还是CLASS?

想谈一下几个基本的HTML问题,都是围绕着应该怎样使用HTML。 1. 多用有语义的标签,少用div和span,避免使用没有class的div和span。 ​ 设想一下HTML的世界最初只有div和span这两个标签,其实网页依然可以写得出来。更多标签的出现,其实是为了替代利用率高但不好书写的 <div class="{tagname}"> 和 <span class="{tagname}"> 来的。 想再接着多说一句的是,在HTML5里越来越多常见的div class组合或div id组合被直接命名为了新的标签。理由也是相同的,像header/footer/aside/nav/section/article都是我之前经常使用的class或id。我甚至觉得w3c创造新html标签的工作很简单,定期统计一下最常用的class和id,然后取前几名作为新的标签名就行了。 上周还有人在微博上感慨那个“史上最牛的HTML代码”: <div class="mod"> <div class="hd"></div> <div class="bd"></div> </div> 再过几年它真的也许会消失的。 反过来思考也可以,尽量使用有语义的标签名,实在想不出来合适的标签名了,再用div然后起个class或id。这样的思路也不错。 2. 不要滥用class而回避id,该出手时就出手。 ​ 和问题1同理,设想一下HTML的世界最初只有class没有id,其实网页依然可以写得出来,语义依然表达得出来。无非就是会出现很多特殊的class呗。因此,我们也很好理解,id的出现,就是可以和class一起协作,使某些语义即使没有现成的标签可以表示它,但依然可以把一般性和唯一性完美的结合在一起。 这里驳斥一个观点:“尽量都使用class,因为控制样式的时候class的优先级是同级的,id的优先级更高,它的出现会破坏样式优先级的平衡”。首先我觉得这是一个假命题,所谓的“平衡”是不存在的,也没有必要去刻意维护,通过id来表示的内容一定是相对特殊的,优先级自然高一些,这样的优先级设计是如此的自然。我能够接受的全部是class的适用范围仅是一些底层的css基础样式,如oocss里的基础样式,或很多网站都会有common.css文件或general.css文件,里面的东西尽量用class没问题。 另一个更重要的理由是,在HTML5里,除了id和class这两个特性可以控制样式之外,还可以通过特性选择器来定义样式,类似E[attr="..."]的写法。我们会发现可以控制样式的方式越来越灵活,选择越来越多。这是Web发展的必然趋势。当其他人已经在用id/class/data-*/tagname对样式展开多重维度攻势的时候,我们实在没有必要把自己还关在class的世界里。 3. 尽量给每个表示布局的class或id换一个站在内容角度的合理的名字。 ​ 比如两列布局的左右侧多半是正文和辅助信息的关系,那么就不建议用class="left"和class="right"而是倾向于class="main-content"和class="sidebar",或者直接用article和aside。 在自适应Web设计(responsive web design)如火如荼的今天,页面上的某个元素处在网页什么位置更像是个变量,所以通过位置来定义一个元素显然是会承受很多额外的维护成本和扩展成本。当改变发生的那一天,你发现自己的HTML代码变得文不对题。曾经的left跑到最上面去了,right变成了底部通栏,这都是很正常的变化。 实在没什么语义的,比如为了给IE加圆角而增设的标签,或清除浮动用的额外的标签,再或者是基础样式的,和具体内容无关的,再用div加表象的class来描述。 4. 尽量避免表示纯样式的class或id。 ​ 比如class="f14 red"。印象中网上有很多拙劣的例子,也有很多深刻批判这种用法的文章,我想说的是,如果你非要这样改样式,那不如直接写内联style来得直观。 最后想说的是…… ​ 互联网是一个快速发展的领域,它的快速发展甚至让人们忘却了很多传统领域的停滞不前。在这样的领域里工作,勇敢尝试,关注新技术,把握新趋势是如此的重要。不要拒绝新事物,不要被不思进取的人拖累,不要对大千世界失去好奇心和求知欲,方可永葆青春。

2012/11/27
articleCard.readMore

微创新=伪创新

先说一个跟创新无关的事儿: 最近有个刷火车票的插件非常流行,名字我不想提了。朋友发链接给我的时候,我打开一看,吓了一跳,网站风格几乎和傲游2006年的官网一模一样…… 傲游2006年的官网 这是今天看到的网页 这跟创新毫无关系,但和创新的一个反面话题有关——抄袭。虽然公司的设计和创意被别人照搬过去对我这种在傲游有些年头的人已经不是什么新鲜事了,但是在今年的新网站,还能够抄回2006年,好设计在国内死绝了吗? 我感觉在我们周围的互联网行业中,创新能力正在急剧群体退化。不论是设计、产品还是技术层面。1.封闭的互联网给山寨版的国外网站带来了__巨大需求__;2.再加上产权保护不给力,让抄袭变成了“0成本”。所以抄袭者顺风顺水,不甘心抄的人走起路来就变得相对困难。 另外,我不太认同微创新这个说法,原因很简单,__如果微小的不同之处也可以看做是创新,那什么又是真正的抄袭呢?__我们今天认为的抄袭,哪个会像做蜡像一样真的做个一模一样的东西出来呢?所以没什么争议,抄了就是抄了。如果微创新被鼓励,就等于抄袭被鼓励。 设计:“直接把傲游的官网样式抓下来用吧” 我担心在这样的游戏规则之下,人的创新意识和创新能力会在不知不觉中退化。前段时间参加了互联网大会,本来是去给我们CEO捧场的,只听了其中几个专场。直观的感觉是:大家都在讨论别人,上来就说Facebook今年怎么样,Apple今年怎么样,Google今年怎么样,说得不亦乐乎,好像Facebook、Apple、Google是他们自家的公司;一说到自己,就一两句话结束了。听到最后,觉得讲得最有水平的,同时也是中国互联网最有前途的行业,其实是搞数据分析的,因为他们的工作就是名正言顺的研究别人。 隔天凌晨,我又见证了苹果iPhone5等一些列产品的发布会。对比甚是明显: 白天的互联网大会上,国内的某些大公司会怎么“做”产品是没悬念的,但大家都在好奇他们接下来会“做”什么产品; 晚上的苹果发布会,他们在做什么产品是没悬念的,但大家都在好奇他们接下来会把这款产品做成什么样。 我想这就是对国内互联网怪现象的最好总结了…… 看过听过经历过后,我更坚定了的是,要拥有一颗强大的内心,面对抄袭,不能妥协,坚持创新的思考和行动。

2012/9/29
articleCard.readMore

HTML5峰会归来

这次 @HTML5梦工场 的活动真是太棒了!我们了解企业,了解同行,了解技术,了解趋势,吃喝玩乐,雅俗共赏,天南海北共聚一堂,还有什么活动能跟这样的 #html5峰会# 相媲美呢?!虽然2天脚都站酸了,但真心觉得值! 了解企业 ​ 这次峰会上,诸多企业都在会场搭建了属于自己的展台:有浏览器,有应用平台、有技术图书出版社,这样不够,后台技术来了,电视来了,物联网来了,广告服务来了,这样还不够,更夸张的是眼镜和糕点也来了。其实仔细想想,中间这部分领域是早晚要和HTML5打交道的,而后面这部分呢,也几乎是每一个前端工程师生活的一部分(你看,干这行的视力都不太好,还经常以庆祝xxx为借口买蛋糕吃)。我们了解形形色色的行业和企业,体验工程师生活的每一部分。 当然说到企业展台,这也包括我的老东家傲游。今年公司启动了全平台的浏览器研发项目,除了已经有的PC、安卓版本,还陆续发布了Mac、iPad、iPhone版本。所以这次的展台内容很丰富,有些朋友甚至来到展台前把所有的设备都试用一遍,才意尤未尽的离开,这让我们这两天“辛苦站台”的同事们倍感欣慰。 了解同行 ​ 我想说第二天的HTML5作品展真棒!一口气看58份优秀作品,结识58组前端精英,感受大家的创意和热情!这次给我印象深刻的,有一个是用Canvas写毛笔字的,可以根据书写的速度模拟出毛笔的力道来,非常神奇;还有一个作品用到了最新的摄像头媒介接口,做出了类似Kinect的游戏体验,他们的作者自豪的说:“我们的目标就是秒杀Kinect!”;另外我非常有幸结识到了HTML5图形库ichartjs的作者,我觉得他的作品像一阵及时雨,刚好填补了图表制作这个HTML5非常大的领域空白。除此之外的其它作品也都各有千秋,看得出他们呕心沥血的投入和付出。 我也有机会展示了自己参与的一个开源项目:就是H5Slides,我们专门为了这次的峰会搭建了一个供大家试用和体验的网站。不少开发者也对我们的作品产生了浓厚的兴趣。还有些人在网上看到过,现场认出我来了,哈哈好害羞滴说! 了解技术 ​ 周六下午的五大专场,安排的都满满当当,我主要参加了其中的两个:浏览器、前端开发。 浏览器这边对罗志宇和我们公司小红姐姐的分享印象深刻,一个是从底层实现的角度看前端性能,受到了很多启发,尤其是“CSS其实就是个和DOM多度多匹配的数据库”这点我一直没有悟出来,也没想到由此推衍出的性能话题,罗的话一语惊醒了我;另一个则是从应用角度看Web标准,嘿嘿,当然也是因为这是我们自己的声音嘛,小红在介绍我们内核研发工作的同时,也把我自己的工作内容描述的很优美很伟大,听完脸上有光:-) 前端开发这边看到了会前听 @adamlu 推荐给我的鸡尾酒(Cocktails)框架,听口音是一位来自台湾的工程师介绍的,讲得很精彩也很幽默。@苏震巍 的话题我赶上个尾巴,没听全,小可惜。回头等PPT全公开了我打算挨个儿下载看看。听说还有老外用中文讲技术,这是多么刺激的事情 XD 了解趋势 ​ 这次从TV专场的成功举办可以看出,智能电视已经在为HTML5跃跃欲试了。加上前阵子参加了一个三星的智能电视的沙龙,和现场的朋友们进行了很多的互动,总体的感觉是智能电视的厂商都算是比较传统的厂商,在与HTML5相遇的时候,产生了很多有趣奇妙的火花。投资人、HTML5开发者、软硬件厂商聚在一起的时候,话题总是那么精彩。看到移动互联网这么火热,大家都坐不住了。哈哈 还有就是大会开场时提到的HTML.next话题,我们应该庆幸自己处在一个Web技术日新月异,高速发展的时代,亲身参与和感受这样的变化。别以为HTML5就是终点了:“成功?我才刚上路呢” 吃喝玩乐,雅俗共赏 ​ 这个太贴心了,会场居然可以办成一个技术与生活的大杂烩,我亲眼看到身为工程师的丈夫带着一家三口来参加周日的嘉年华的。老公在展台展示自己的成就,老婆孩子在玩棋牌,和灰太狼喜洋洋的公仔互动,渴了有乐可,饿了有奥利奥饼干和现做的蛋糕,其乐融融。我之前觉得对于成家的人来说,周末两天不休息来参会太残忍了,现做我就不觉得了,反而双手推荐! 工程师们也不闲着,除了技术交流,还有机会参加各种抽奖、砸蛋、作品投票、你说我猜之类的游戏。我也不免俗的去砸了一个金蛋,中了一套便利贴。 最后,天南海北共聚一堂 ​ 和太多神交已久的朋友们相见了啊!!!!神飞、李秉骏、苏震巍、IsNull、还有好久不见的winter、老赵、大城小胖、杨东杰、梦工场的几个城市的负责人…… 和大家聊在一起非常开心,同时觉得好多来自武汉、杭州、上海、深圳、广州的优秀作品和精英们的涌现,北京虽然能承办得到这么豪华的峰会,但有很多地方应该和全国各地的朋友虚心请教和学习。收获满满的两天! 我们还敢期待更精彩更大规模的HTML5峰会吗?@田爱娜

2012/8/20
articleCard.readMore

分享bookmarklet一则:随意阅读

这款bookmarklet是我对主流阅读模式智能识别规则的一种吐槽。 当然,首先,阅读模式本身是一项非常棒的功能!他可以增强文章的阅读体验,统一不同页面的视觉风格。可以让人专注在文章内容中。等等。 而我要吐槽的点,是这个所谓的“智能判断文章内容”的算法。 “智能分析”的阅读器 ​ 阅读模式的核心任务之一,就是能够找出我们在各种凌乱广告、装饰物、页眉页脚之中的正文内容。在找内容这件事上,大家不约而同的使用了智能分析的路数。这里是早期开源的Readability脚本库的核心代码,我们可以看到其中包含着大量用来识别某dom结点是否是正文根结点的算法公示。而这套算法,又是从大量已存在的网页中归纳而来的。 阻力 ​ 既然是归纳的结果,而现如今已存在的网页已经数以亿计五花八门了,那不可避免的会存在判断误差。我们希望把内容识别的准确率提到最高,但会遇到一个问题:就是广告主、站长都有各自的小算盘,他们时刻准备着违背这些智能,降低判别的准确率,不让它正常工作,以谋取自己更高的收益或二次点击率。因此,这一智能的识别规则无法完全公开(Readability随后停止了开放源代码,我猜也有这方面的考量)。 how to enable? ​ 可黑盒的智能规则又导致了另外一个问题:智能识别如果出现判断误差,对于那些希望支持阅读模式的网站,又无奈于找不到这些计算规则去适配。于是我们又会看到类似“how to enable safari reader”这样的问题满天飞。 最后阅读模式的识别规则、支持者、反对者扭打在了一起。 “智能分析”的弊端 ​ 所以,归纳起来,智能识别的劣势有两个:一个是无法100%判断准确,这样无法完全满足用户需求;一个是面临多方面利益的冲突和挑战,这样会阻碍其快速发展和完善。 “随意阅读”的理念 ​ 我们希望网页上的任何内容,只要是值得认真看一看的,都可以提供“阅读模式”的体验——就像iOS中屏幕上的任意区域被双击之后都可以智能缩放到合适大小一样。所以我们可以给用户另外一种“阅读模式”,它可以让用户主动选择想阅读的区域,然后把用户选中的区域设定为阅读器的正文。 有了这个东西,智能规则的压力会减小;支持者不必担心自己的内容无法很舒服的被用户阅读;挑战者也无计可施。一举三得! 技术实现 ​ 首先,准备一套易于阅读的css样式。我直接利用了自己blog的主题,同时把文字区域的最大宽度固定在800px。 然后,让用户可以通过鼠标选择页面上的一个box,并高光显示,作为内容根结点的识别。之前我写过一个简单的dom inspector的例子,现在刚好用上了。 接下来要做的,就是把页面上的其它内容“藏起来”,把网页中原有的样式去掉,把我定义好的css样式潜入。 最后,把用到的资源放到网上,把脚本入口做成bookmarklet。大功告成。 demo 改进空间 ​ 随意阅读并无法取代现有的阅读模式,但会让阅读这件事变得更完整。同时,我们可以改进的地方有很多: 第一,bookmarklet中的alert体验显然是可以优化的。 第二,开始阅读之后,原有的网页都被“覆盖”掉了,如果在阅读过后还想有后续操作,可以考虑把阅读器在新窗口打开。 第三,用户在选择阅读区域的时候,是没有鼠标hover交互效果的,这里我写的dom-inspector不太够用了,没有深入进去。也是一个优化的空间。 第四,我们有很多外延的功能可以加入,比如“超级下一页”、全屏模式、分享、评论、打印等等。

2012/8/20
articleCard.readMore

国际羽联和中国队之间的恶性循环

直观的感觉是这次“冷”敦奥运会上,国际上对中国集体发难。这样的感觉起码有这些因素作祟:第一,自从北京奥运会中国在家门口历史上首次拿到金牌第一,国际媒体就开始越来越关注中国;第二,以前我们都陶醉在某些洗脑工具中,听不到国外的声音;第三,称赞中国的声音不够劲爆,我们的舆论也有选择性的把更多偏激的言论带回了国内;第四,新媒体全面占领传统媒体,消息更快速,也更不准确;第五,我们喜欢“拿来”,却不喜欢回馈。 图片来自和讯新闻 话说这次奥运会4对羽毛球选手被判消极比赛取消资格一事,绝对是国际羽联对中国长期以来忍无可忍的一次报复。毫无疑问中国现在在羽毛球这个项目上太过强大,尤其是女双,强大到对手连打败你的信心都没有了。这样的话选手的成绩在淘汰赛制中变得偶然性很大:第一轮就碰中国,名次肯定倒数,晚点碰中国,甚至可以拿奖牌。 我猜国际羽联把羽毛球由纯淘汰赛改成循环赛加淘汰赛,这也直接导致了这一出闹剧的出现。其实规则的改变就是给那些名次不好的人更多机会,给中国队更多危险。但我不认为这是一种良性的改变。 首先,奥运精神是更快、更高、更强。 ​ 我们希望在奥运会看到的是全人类的突破,把优秀的成绩变得更优秀,再优秀,不断突破自我,战胜自我。可国际羽联这样的改变只是单纯的为了“照顾弱者”而放慢优秀者的脚步,这同时也放慢了全人类的角度——你觉得人类不可能把羽毛球打得更好了吗?外国打不过中国,应该更多从他们自己身上找原因,想通过规则打败中国,只会让他们变得更加不堪一击。 其次,我们之前也见过一些别的运动,因为有一个个体或队伍太强,而修改规则,限制强者发挥的情况。但都是针对比赛内容的,而不是赛制。 ​ 比如,NBA历史上就多次因为限制个别“变态”球员而修改规则,感兴趣的还可以再查查张伯伦改变了多少NBA规则,离我们比较近的则是乒乓球总在改规则:把乒乓球变大变重,发球不能遮挡等等。 这两者差在哪里呢?改比赛内容的话,也算是让运动员挑战更高的极限,比如三分线变得更远了,就需要你有更精准的投篮才行;可修改赛制,对这项运动本身的发展有何帮助? 第三,我觉得循环赛不适合差距悬殊的较量,循环赛往往是竞争激烈且过程复杂的运动才有的。 ​ 比如团队运动,如足篮排三大球,一堆人忙活半天,一场比赛就回家了,直观的感觉去一趟很不值;而且这些运动对抗性强竞争激烈,实力相当,比赛变数多,运气成分也大,所以起码让你小组赛多打几场,检验成色。 再比如田径射击游泳举重项目,成绩是很客观的,谁快谁慢一眼就看出来了,没必要总是1v1循环。 再有就是网球,就一个或两个人比赛,盘局设计让比赛的悬念和优胜劣汰巧妙的达到平衡,同样是大淘汰赛制。 其实羽毛球的运动性质和网球的情况很相似。所以循环赛的安排违背常理。 从上述三点看,国际羽联只能拿出一个合理的解释:不择手段影响中国队。 ​ 以前我们说规则是死的,人是活的。可今天连规则也开始变了,那么请允许我认为:在规则的“怂恿”下,中国、印尼、韩国的这几对选手被迫需要面对这样的尴尬。在这种规则下,给了谁,站在今天中国队的位置,都无法忽视规则与利益之间的矛盾冲突;而印尼和韩国呢,如果今天刚好是马来西亚选手和中国队交锋,大家觉得马来西亚球员会不会做出同样的事情?我想还是会,因为__在一个恶劣的体制下,每个人都会成为“共犯”中的一员__。 我再举个例子:自己遇到红灯却眼瞅着要迟到了,看马路上的人都闯红灯过去了,你心里会不会痒痒的? 就算不是每个人都会真正去做,至少这是一个正常人都会产生的尴尬。从这个角度讲,造成今天的局面,国际羽联难逃其咎。 当然中国队也长期做得很过分,我同样觉得这也是恶性循环的一部分。 ​ 我们的运动员真的是为了更高、更快、更强的奥运精神在参加奥运会吗?还是国家尊严、个人名利这些跟奥运精神比起来显得低俗下流的东西?索性很长一段时间这两件事对我们来说是完全重合的,所以不怎么深究。今天国际羽联就给了我们一把尺,在这把尺上,我们的丑陋被人家捉个正着。就算国际羽联是刻意的,我们也没有经得住“考验”。 而在金牌至上的长期思想下,即使个人有那么一点冲动和想法,也被大环境打磨掉了。 另外,当天确实假得太过分了。观众不买账了,事情才闹大的。这也是恶性循环的一部分。 最后,大家一起毁了羽毛球…… ​ 这样的恶性循环还要在我们的身边上演多少次?!

2012/8/5
articleCard.readMore

ZeroClipboard 学习笔记

如题,周末抽空学习了一下。 ZeroClipboard是在桌面电脑的浏览器上,通过flash技术实现“复制到剪切板”功能的一个程序。它的好处是可以兼容所有浏览器,完成剪切板的操作。 我们在使用的时候主要就用到两个文件:一个是js文件ZeroClipboard.js,用来引用在网页中;另一个则是swf文件ZeroClipboard.swf,它无需我们在代码里引用,而是被之前的那个ZeroClipboard.js二次调用的。 ZeroClipboard的工作原理大概是,在网页的“复制”按钮上层遮罩一个透明的flash,这个flash在被点击之后,会调用其的剪切板处理功能,完成对特定文本的复制。这里有几件事需要我们来完成: 创建一个透明的flash 将这个flash浮在按钮上层 确定要复制的文本是什么 监听这个透明flash的鼠标点击事件 该flash被点击之后,完成剪切板处理 对于这几件事,ZeroClipboard分别提供了不同的api,来完成整个需求。 创建flash ​ 创建的过程其实就是一个var clip = new ZeroClipboard.Client()的过程,这时ZeroClipboard.swf会被载入。值得注意的时,这里的swf文件默认需要放在和网页相同的目录下,且文件名固定。如果我们需要移动这个swf文件的位置或改名,则需要在创建swf文件之前运行: ZeroClipboard.setMoviePath( 'http://YOURSERVER/path/ZeroClipboard.swf' ); 或 ZeroClipboard.setMoviePath( './src/ZeroClipboard.swf' ); 里面的参数可以是相对地址也可以是绝对地址。 将透明flash浮在按钮上层 ​ 这里有一个很有趣的英文单词:glue。我们可以通过下面这个api,将flash和按钮重叠,且浮在按钮之上: clip.glue( 'clip-button-id' ); 或 clip.glue( document.getElementById('clip-button-id' )); 即第一个参数为id或dom对象都可以。如果按钮在网页运行中位置发生了变化,flash是不会自动调整位置的,为此我们提供了另一个api可以手动更新flash的位置: clip.reposition(); flash的相对浮动 ​ 这里还提供了一种更巧妙的方式:如果按钮的上层有任何position:relative的块状元素,比如div,而按钮和这个块状元素的位置又是相对固定的,那么可以在调用glue函数时,将这个div的id作为第二个参数传进去,不过同时reposition这个api就失效了。比如: clip.glue( 'clip-button-id', 'clip-container-id' ); 设置要复制的文本 ​ 这一步很简单: clip.setText('要复制的文本在这里'); 监听事件 ​ 通过addEventListener进行事件绑定,可以绑定的事件有以下几个: onload:flash文件加载成功 onmousedown:鼠标在flash上按下 onmouseup:鼠标在flash上释放 onmouseover:鼠标经过flash onmouseout:鼠标移开flash oncomplete:剪切板操作完成 (用鼠标点击该flash浮层的时候会触发事件复制到剪切板) 剪切板操作完成之后可以通过api销毁flash ​ clip.destroy(); DEMO 更多细节和高阶操作的介绍

2012/7/29
articleCard.readMore

“思考人生”

前几天欧洲杯,“巴神”巴洛特利一个散步式单刀被后卫回追成功,解说员戏称他停下来思考人生了,一时间“思考人生”变成了个热门词,以至于我们看到谁发呆、低头,都说他们在“思考人生”。其实我最近也在思考人生,也许比他们的严肃一些了。 来北京的第6年,同时也是在傲游的第6年,一转眼小半年快过去了。 这半年过得算是波澜不惊吧,事情异常多,公司的,家里的,亲戚朋友的,各种活动的,还狗屎运出了一趟国。总会有些事情照顾不周,处理不妥,也在意料之中。我只想说:感谢经历,你就自己想办法挺过去吧。 在这不到6年的时间里,我一直没有停止做一件事,就是不断努力发现自己的问题和不足,并尝试改变它——相信很多人也都会这样。这期间有些改变是令我兴奋的;有些事情是立竿见影的,富有成就感的;还有很多事情,在潜移默化的发生着,你无法通过一两件事情看清楚,但经过长时间的积累,暮然回首,你会发现,它真的变了,有好的也有不好的。 毅力不足 ​ 前阵子看《锵锵》,老窦聊起减肥食谱,说有一次问医生道: 究竟有多少人能按这个健康食谱坚持下来呢? 答曰: 工作上能成事儿的,基本能坚持下来。 从这个角度讲,我发现今天自己比较满意或比较有成就感的事情,基本也都一直坚持着了。那些自己不满意的地方,平时也确实是三天打鱼两天晒网。这似乎也没什么好讲的。 精益求精 ​ 于是乎又想起自己上大学的时候,跟已毕业多年的一位学长聊天,学长说道: 大学同学多年后聚会会有人问别的老同学:“以前在班上咱俩的差距无非也就是我考个85分,你考个90分,没啥感觉啊?怎么现在两个人的发展差距这么大呢?”。其实这说明了一个问题,你做每件事都用85分要求自己,这会形成习惯,而别人都用90分要求自己,一两次考试确实看不出啥差距,可时间长了,剪刀差的差距就明显了。 对于人生,我们的态度究竟是多少分呢?对我来说,通过6年的时间,我隐约看到了一些答案。再加上那些没有坚持下来的事情,我觉得自己的生活状态必须有所改变了——如果我还有梦想的话。 打好基础 ​ 何为基础呢?如果学好A可以让你更快的掌握B,那大体上A应该算是B的基础吧。如果希望事半功倍的学习,有的时候似乎不能由着自己的性子,想起什么就去学什么,而是找到那个最大的A。对于现在的我而言,这个最大的A似乎是英语。正如我之前思考如何学习时想到的那样,英语太TM重要了! 除此之外,几个基本的计算机学科也是我接下来深入学习的对象。这些东西的价值,在多年后的今天、自己熟悉的领域,可能不太明显了,因为已有大量的经验可供参考。但是在处理更复杂或跟广泛的问题时,才发掘自己搞不定更多事情了,琢磨一个在别人看来入门级别的程序,都要搞很久,这种感觉真是糟透了…… 也许对于一个前端开发来说,为不懂浏览器内核或后台服务器编程而忧伤算是一种强迫症吧。后来我仔细想了想,这些糟糕的感觉之所以出现,是因为给自己定了一个更高的目标吧,这不是什么坏事。每次想到这里,自己又重新振作了起来。 用好时间 ​ 一方面是要把各方面的时间协调好吧,有意识管理自己的事务和行程;另一方面也希望自己可以对事情的轻重缓急始终有一个清醒的认识。我尤其觉得自己在电脑面前管不住自己 囧。这体现在自己不愿意起身做别的事情,有严重的拖延症;还有就是总是没法早睡早起(比如今天)、吃规律的早餐;还有就是总想着开TMD微博、人人网、网易体育和直播吧…… 良性循环 ​ 如果一个生活状态可以让自己变得更好,那么就去保持这个状态吧。否则,改变是刻不容缓的。 保持乐观,心无杂念:) ​ 有句广告语是:“心情好,一切都美好!”。世界总会有纷乱,悲观的人会被击垮,乐观的人会重生。过往云烟,留下来的才是自己的。尽量把问题想的远一些,并坚定信念。 以上

2012/6/15
articleCard.readMore

听杨东杰弹吉他

这是一个灰常活泼并且有怀旧感的标题。 原本打算记我上周末成都之行的一篇日志,我觉得除了参加HTML5梦工厂成都站的技术交流活动之外,最大的收获,莫过于同杨东杰童鞋的畅谈。另外今天连续在微博上看了两个罗纳尔多和萨内蒂年轻时候的足球集锦,突然觉得这几年间,很多事情在发生着悄然的改变,比如曾经有个美少女组合叫S.H.E,昨天在地铁外看到Ella的一个活动海报,才发觉,这个团早已淡出了我们的视野。我们上大学的时候,有几个舍友特别喜欢挺S.H.E,导致我也比较熟悉他们的歌曲,后来让我对他们真正产生印象的是《Play》那张专辑,觉得它的概念蛮有意思的,整张唱片都是轻松欢快的气氛。于是《听袁惟仁弹吉他》这首歌一闪而过,于是就有了这个标题…… 有点扯远了 聊成都 ​ 杨东杰是前CSDN移动频道的主编,之前见过几次面,但没怎么细聊过。这次我去成都参加活动,他也刚好跑到成都出差了,我们就这样不期而遇。估计后面的东西我会写得有点像采访稿,呵呵,估计杨兄采访过那么多人,被采访的经历不太多吧,另外像这种通篇没什么他的观点都是我自己的观点的采访估计也几乎没有过 😃 说起成都,第一感觉是这里的生活压力会相对小一些,其次是成都的美食、美女、美景,然后就是戏称“离方舟比较近”。杨兄对于这三点有多兴奋,去看看他的微博就能感受到了。 然后是成都的天府软件园,我想说这个软件园足以构成一个独立的城市了。好大一块地,而且是全新的,和成都的其它区域有着明显的界限。关于成都的IT行业有两个特点:一个是很多大的互联网公司都已经开始在这里“圈地”了;另一个是有很多小的创业公司享受到了当地政府的扶持和优惠政策,拥有优越的创业环境。所以总体上感觉,成都的软件业还处在一个比较基础的阶段,未来的潜力和发展空间都是巨大的。 聊互联网 ​ 我想说杨兄在CSDN的视野非常开阔,也有很多独到的见解。这次交谈,我们聊到了很多互联网的话题,诸如国内微博运营、开放平台、移动应用趋势等,受益良多。其中一个话题印象深刻,是和起点中文网有关的,杨兄反复称赞这个网站不得了,通过起点中的文学作品,可以看到很多现状和未来,很多被认为是“高端”的人士,其实都在默默关注起点上的作品。所以这绝对是一个被低估的网站,而盛大也是一个被外界低估的企业。另外,说到这里,确实喜欢看书的中国人太少了,我自己也应该多看点书。 这里还想插播一则别的事情(越来越不像采访了 - -),这次在成都还有幸造访了中国电信的基地,他们的天翼空间也正在对HTML5跃跃欲试,跟这边的朋友也聊了很多,发现他们虽然主营业户是电信,但对HTML5非常感兴趣,也非常了解互联网这个圈子。现在真是越来越多的人,越来越多的领域加入到了移动互联网和HTML5当中来了,未来的互联网会更丰富、更精彩、更多元化。 聊个人发展 ​ 还聊到个蛮有趣的话题,就是如何提高英语水平。刚好杨兄经常在WebAppTrend翻译英文的技术文章,我也刚刚出国开了一个W3C的会,都有各自的心得和困惑,聊到很多有趣的经历,不亦乐乎。还有就是如何看待创业这件事,杨兄说,在今天的中国,你去创业,是没有“失败”的——即使公司失败了,对个人而言,一定有很多收获,绝对不算失败。我确实觉得创业这种事不是一般人能做得来的,因为有太多事情要照顾到,相信这样的经历一定会让人快速的成长。所以,他也非常看好成都这个地方的创业者们,看好成都的发展。 再就是些生活习惯和生活态度的话题,最近“屌丝”这个词非常火,他也说了说自己的屌丝心态之痛,叛逆和对主流的质疑都不足矣导致不好的结果,但关键是要让事情带入一个良性的循环,走向更宽阔的道路。不然这个代价在时间的面前是非常惨痛和巨大的。这是一种“剪刀差”式的变化。 差不多就是这些了。最后,国王杯比赛快结束了,巴萨遥遥领先,比较无聊。略显无厘头的日志也要结束了。其实关于我们聊天的内容,这里都先点到为止,就不细说了。希望杨兄在未来有更好的发展。希望成都有更好的发展。这也算是我对这次的成都之行有个回顾吧。 完毕

2012/5/26
articleCard.readMore

学习精髓

首先,很久没写blog了,最近小忙,今天下午难得清闲,把最近的一些思考写下来。 如何学习XXX?是一个在我周围经常听到和谈论的话题。 比如,如何学习JavaScript,如何学习HTML5,如何学习移动开发。我发现,在这个知识爆炸的年代,人们汲取知识的方式确实非常多元化,有人选择看书,有人选择上课,有人选择实践,有人选择逛微博,有人喜欢订阅RSS,有人喜欢跟牛人交流。。。 从形式上看,他们都各有优劣,我也确实觉得因人而异,因需求而定。同时,学习的内容非常关键,学习的态度也非常关键。 首先,我觉得最正统的学习,自然是边看书,边实践,理论与实际相结合的。书,最好是比较系统的,优质的,经得起推敲和实践考验的;实践,最好是简明的,直观的,循序渐进的。 但这样的学习,要想保证效果,势必需要一些整块的时间和精力,需要耐心和专注:这似乎在大学毕业之后,就比较难了。 我们在高节奏的IT生活中,似乎很难沉住气,于是我们见到了更多“入门级”或“速成”的学习内容,不求深度和广度,但求迅速上手,迈出第一步。再往后,或许生活节奏更快了,人们压力也更大,似乎“速成”都不够快了,人们把之前一本书的东西,浓缩成了一篇文章。。。然后,抽象成“十大要点”。。。最后,变成了一条微博。。。 请允许我以这样的方式归纳如今主流的学习方法吧。尽管并不完备。 我记得自己在Web标准化交流会,被裕波第一次建议分享的技术话题,就是《前端工程师如何学习JavaScript》,那会儿我的想法是,要因人而异,选择不同的内容和方式,大块的新东西,要“啃骨头”,沉住气,要不得半点马虎;已经有一定基础的情况下,可以选择“吃零食”,日积月累,汇流成河。今天看来,我依然是这么想的,但如果方法不对,会很痛苦,或事倍功半,或无法坚持。 而学习的另一个要素,则是内容。有的时候,跟同行朋友交流时,会遇到这类的讨论:我记得好像有人说过这样是不行的(不好的)、XXX(某名人或某本书)说这样是最好的方案。可有的时候,这些观点并不绝对,它可能是片面的,或是过时的,甚至是复述的人记错了理解错了,甚至它本来就是错的。当这样的讨论频繁出现时,我觉得出了问题:首先,他们在学习知识的时候,没有足够的基础,导致无法辨别上层知识的真伪;二来,对知识的学习缺乏深究的精神,知其然不知其所以然;第三,没有找到真正的起点。 导致这些问题的因素可能是多方面的,比如很多基础的材料是英文的,而语言障碍多多少少客观存在;比如我们的视野总是有限的,比如周围的人都是“那样”学来的。可真正的精髓,就这样被错过了,实在可惜。 作为比较普世的道理,我觉得有几件事值得我们参考: 首先,要追根溯源,我想得到的途径有三种,看官方文档,读源码,拜读技术发明人或创始人的书籍,这些都是原汁原味的,不经其他人转述,没有信息偏移和衰减的(说道这里,大家应该见人玩过那种“传声筒”的游戏吧,一句话经过五个人传到最后,变成了完全不同的另一个意思,就是这个信息偏移和信息衰减的道理)。 然后,开放自己的心态,多多参与更广泛交流,避免被狭隘和片面的观点所误导,关于这一点,国内有好多线上线下的技术交流活动,如果大家有心参与,应该足够了(不过,虽然不太严重,但值得指出的是,国内和国际的技术认知还是会有些许偏差,必要的情况下,要走出国门,做跨国交流)。 所以,还有第三点,英文太TM重要了!而且是基础中的基础! 有了基础,了解了本源,开阔了视野,然后再不断积累零散琐碎的知识,此乃成才成功之道。我们能有多大作为,一个重要方面,取决于我们做了多少准备,下了多少功夫。 再说得远一点,技术立本的公司的发展和生存,也需要紧跟趋势,深刻理解行业标准和规范,才有可能真正做到技术领先,甚至技术超前,引领行业趋势和行业标准。 以此作为2年前“如何学习JavaScript”话题的延伸和二次思考。

2012/5/21
articleCard.readMore

网站装修笔记20120426

按照严计划,给我的网站加上了投票功能。他已经出现在了网站的侧边栏中。当然在某些media queries条件下,这些内容不在侧边,而在网页的最下面。 这从没有花太多时间在编码上。我直接引入了三方的问卷调查工具:Wufoo。等于嵌入了一个iframe。我会定期更换这里的投票内容,有些投票可能是为了我更了解大家,更好的优化我的网站为大家服务的,我会根据大家的反馈默默做出改变;而方便公开的统计数据和分析结果,我也会公布在这里跟大家分享。 第一期的投票内容比较“肤浅” 囧 在添加投票功能的时候,我注意到Typecho的一个不足,就是在创建新的侧边栏面板的时候,不免需要修改主题模板的sidebar.php文件,因为Typecho本身并没有给侧边栏预留插件接口,所以不可以生成自定义的侧边栏面板。我觉得改进的方式可以是这样的: 创建新的配置项,注明要显示的侧边栏面板和排序; 同时封装面板的html结构,不允许自定义面板结构,只允许自定义内容; 然后在Plugin一边加入自定义面板的接口。 这样主题模板和插件就进一步解耦,开发者就可以在不改动主题模板的情况下调整侧边栏的内容了。 一点个人心得,不知Typecho是否愿意接纳这个意见。 以上

2012/4/26
articleCard.readMore

网站装修笔记20120414

这周主要把我的几个HTML5幻灯片做了一下整理,在这里。从这次播放器的实现上看,幻灯片数据的结构约定有所改变,同时适配了更多的浏览器和设备。 在这个过程中,我也尝试了更多CSS3的变换效果,让幻灯演示多一点精彩,所以最终我为每个幻灯演示分别使用了不同的幻灯片切换动画。有3d的、有2d的,也有WP7中的Metro风格动画等等。 另外说到幻灯演示,我正在发起一个这方面的开源项目(估计经常关注我的朋友都已经听腻了),本月之内会启动。 接下来我想给网站做一个投票系统,放在侧边栏。

2012/4/14
articleCard.readMore

网站装修笔记20120406

今天为我的新网站做了两件事情: 第一件事是为我的主题皮肤加入了侧边栏显示,并使用了css3 media queries技术进行了响应式设计,并借鉴了一些metro ui的思路 第二件事是加入了友情链接插件,可以在侧边栏显示一些自定义的链接,方便将来和博友们交换链接 友情链接 ​ 先说友情链接插件的事情吧,我在Typecho的插件站找到了一款友情链接的插件,名字就叫Links,非常方便实用。我现在随便放了3个链接,看看样子。大家希望跟我交换链接的,可暂时留言至此,回头我会另外做个交换链接的页面。 响应式侧边栏 ​ 然后是侧边栏,我把最近文章和最近评论两个侧边栏的widget加入了名为large的css class。这类widget会在条件允许的情况下占用更大的空间。一般情况下宽度是普通widget的两倍。在一些特殊的界面宽度下,widget的宽度是一样的,大家没有什么分别。 对于css3 media queries的利用,我这里按窗口宽度分了5档1400px+/1050px+/650px+/400px+/400px-,进行响应式设计。每一档的内容宽度、侧边栏宽度和布局都不太一样。以适应不同的终端。举其中一个例子(1050px~1400px之间): @media all and (min-width: 1050px) and (max-width: 1400px) { #wrapper { position: relative; padding-right: 280px; } #sidebar { position: absolute; width: 260px; top: 160px; right: 0; } #sidebar .widget, #sidebar .large { width: 98%; } } 最终效果如图: 这款皮肤我会稍后更新共享在这里。 另外我最近抽空研究过了SAE Storage,接下来的事情是做一些有趣的侧边栏小控件出来,比如投票、相册、之类的。 先记下这么多

2012/4/6
articleCard.readMore

分享Typecho插件:百度统计助手

百度统计助手 只支持http协议,不支持https协议 在插件的“设置”里把统计代码最后的那串随机码填入保存即可 如果不开启统计,把填入的随机码清空即可 设置成功后页脚会多出一个百度的小图标 使用步骤 ​ 首先我们当然需要有一个百度统计的账号,然后通过这个账号生成我们要统计的统计代码(如下图): 其中有一串随机码,我们把这串随机码记下来。值得注意的是,随机码前面有一个转意后的问号(%3F),不要把这几个字符算入随机码。 然后在插件设置中填入这串随机码(如下图),即可正常工作。 有需要的童鞋可以移步到这里下载

2012/4/3
articleCard.readMore

我的得奖感言

本文摘自 勾三股四 更早时期的 不老歌 博客。 我想说的是,作为一个长期游离于非核心业务和技术的网站开发者,能够在一家浏览器公司得到这个奖,无疑是对我工作莫大的认可。 这篇得奖感言是为我未来可能会在傲游拿到的任何荣誉或奖项所准备的得奖感言。 得了奖以后不知道自己该干啥) 就算我以后真的没得什么奖,也无所谓了…… 就算借愚人节壮壮胆吧

2012/4/1
articleCard.readMore

网站装修笔记20120331

今天给我的网站加入了著名的百度统计。对,就是下面这个东西: 相信这个小家伙会帮我更加了解我的网站的读者。 更深入的了解了一下Typecho的插件机制,经过群友的提醒,才发现Typecho源码中有一组辅助插件开发的代码,它们被汇总放在了/var/Helper.php中。也弄清楚了Typecho中的Widget机制和命名规律。这让我决定通过插件的方式加入百度统计。 我不在主题footer.php中直接加入百度统计,还有一层原因,就是为了保持主题的纯净,让主题可以被其他人也使用同时不被百度统计在内。 通过插件加入百度统计之后,我又想到,可否让这个插件也分享给更多的百度统计的使用者。而不同的使用者,统计码都是不同的,这就需要我们把统计码做成一个插件的参数,而不是写死在插件里。于是又了解了一下插件的参数设置方式,把统计码做成了插件的参数。 最后,我同时研究了一些Typecho的路由机制(Routing)和SAE的Storage服务接口,这为我给我的网站加入更多的单元或板块提供了更多想象空间。 接下来的计划是把这个插件分享出来,然后重新梳理并设计一下网站的路由规则。 以上

2012/3/31
articleCard.readMore

用CSS3制作尖角标签按钮样式

演示地址:https://jiongks.name/demos/css3-tag/ 如图的效果。标签有背景色,且左侧有一个三角形,三角形中间有个白色的圆圈。 你一定在想这个效果是背景图切出来的吧——答案是没有用到任何图片 那你会不会在想这个效果的html结构很复杂呢——答案是最简单的html结构 <p> 之所以可以达到这样的效果,是因为我们运用了一些比较巧妙的技术。接下来告诉你实现方式: 结构 ​ 我们通过a:before和a:after这两个伪元素,通过绝对定位的方式,为<a>标签做了扩展:首先把一个伪元素a:before当做最左侧的三角形,然后再把另外一个伪元素a:after作为中间的小圆点显示到界面中。 a { display: inline-block; position: relative; } a:before, a:after { position: absulote; content: " "; } 左侧的三角形 ​ 三角形的实现方式略带技巧性,其实就是把宽高都设为0,边框宽度设为文本高度的一半。然后将其右边框上色border-right-color,其余三面边框颜色全部设为透明tranaparent,就可以了。当然,在设定边框宽度之前,我们需要确定文本的高度,这里有一个非常合适的单位:em。我们将链接的行高设置为1.5em,然后将伪元素的边框设置为0.75em即可。 a { background: #ccc; color: green; line-height: 1.5; } a:before { border: transparent 0.75em solid; border-right-color: #ccc; top: 0; left: -1.5em; height: 0; width: 0; } 左侧三角形中间的小圆点 ​ 这个小圆点同样需要用css3实现,相比之下,它的实现略简单,设置背景为白色、宽高均为0.5em、上下边距均为0.5em、圆角半径是0.25em的矩形。这需要合理的坐标计算和尺寸计算。我们同样选择了通过em这个单位来计算。 a:after { background: white; width: 0.5em; height: 0.5em; top: 0.5em; left: -0.125em; border-radius: 0.25em; } 把这些内容凑在一起,会发现横向的距离会有些不合适,那我们再做一点微调: a { padding: 0px 10px; margin-left: 1em; } 这样看起来样子比较协调了。 鼠标悬停效果 ​ 最后,加入:hover效果: a:hover { background: gray; color: white; } a:hover:before { border-right-color: gray; } 这样,就大功告成了! https://jiongks.name/demos/css3-tag/

2012/3/30
articleCard.readMore

分享Typecho插件:Markdown 解析器 + 编辑器

该插件是在 明城 的“使用 Markdown 解析器”的基础上完善的。 使用 Markdown 语法 编写和发布文章。 在正文上方加入了简单的格式按钮; tab键自动输入4个空格; 正文下方加入了预览功能; 在侧边栏加入了语法提示。 下面是三张截图,一个是工具栏,一个是预览区域,一个是语法提示区域 有需要的童鞋可以移步到这里下载,但__特别注意__:一定要放在/usr/plugins/Markdown文件夹下,其它命名可能会导致500错误(感谢大家的反馈,我之前没有注意到这个细节)。

2012/3/28
articleCard.readMore

分享Typecho皮肤:我的字很大

没错,就是前面提到过的字很大的皮肤,我给这款皮肤起了个非常土的名字叫:“我的字很大” 有几点说明: 侧边栏我隐藏掉了,并没有预留合适的位置,因为希望界面简介 搜索栏我隐藏掉了,但预留了位置,就是右上角,在style.css文件里去掉#search {display: none;}那一段就好 右下角的文字算是版权信息,是通过创建body的一个css伪元素实现的,略有一些技巧,如果大家觉得碍事,当然可以在css文件中去掉 对IE6效果做了简单的降级 有需要的童鞋可以移步到这里下载

2012/3/28
articleCard.readMore

汇总自己过去的一些HTML5科普文章

新网站开张总得找点东西充数,刚好我这阵子集中看了一些w3c关于html5的文档,所以顺道把之前看过的东西汇总了一下。这些内容如果今后继续有积累的话,还会保持更新。 另外诸位对哪些html5新特性有科普需求的,不妨留言在下面,我会尽力而为 😃 目前这些内容的最近更新时间是2012年3月 JavaScript APIs ​ LocalStorage:一些使用心得 IndexedDB(一)、 (二)、 (三)、 (四) File API File Writer API File System API Typed Arrays:另外提及了 XML Http Request Level 2 Web Messaging:包括 WebSocket Server-Sent Event Cross-Document Messaging Channel Messaging CSS 3 ​ color value颜色值 border-radius:圆角矩形 Bézier curve:贝赛尔曲线 Transform 和 Matrix:几何变换和矩阵支持,不是黑客帝国里的变形金刚……囧 Transition:渐进变化 相关demo与ppt ​ 最后,上述内容还有一些是通过demo和ppt分享给大家的,它们会被汇总出现在all-demos和all-slides这两个页面中。

2012/3/25
articleCard.readMore

html5中的消息通信简介 + 我的新网站开张

本文摘自 勾三股四 更早时期的 不老歌 博客。 随着网页时效性需求的增强和其所涉及范围的扩大,各种消息通信的运用越来越多。在html5中,有很一部分消息通信的新规范,旨在让这些工作更加方便实用。接下来会为大家介绍4个比较主要的通信方式: WebSocket socket = new WebSocket(url[, protocols]); socket.url; socket.readyState; // CONNECTING|OPEN|CLOSING|CLOSED socket.send(...); // string|arraybuffer|blob socket.onmessage = function (e) { e.data; }; socket.onopen = function (e) {...}; socket.onerror = function (e) {...}; socket.onclose = function (e) {...}; 更详细的介绍可以移步到:http://www.w3.org/TR/websockets/ Server-Sent Event var source = new EventSource('./sse.php'); source.addEventListener('message', function (e) {e.data;...}, false); source.addEventListener('add', function (e) {e.data;...}, false); source.addEventListener('customEventType', function (e) {e.data;...}, false); 然后服务器端返回特定的格式: echo "event:customEventType"; // 确定事件类型 echo "data:content string here..."; // 返回数据内容 echo "retry:5000" // 5秒之后请求第二次; 就可以配合工作了。 http://dev.w3.org/html5/eventsource/ Cross-Document Messaging // on top iframe.postMessage('hello'); // on iframe window.onmessage = function (e) {e.data;...} 值得注意的是,这项技术可以解决跨域问题——当然,有个限制,就是被请求的页面通过head信息明确授权跨域访问。 Channel Messaging var channel = new MessageChannel(); var port1 = channel.port1; var port2 = channel.port2; port1.onmessage = function (e) {e.data;...}; port2.postMessage('Hello Port1!'); Channel Messaging给了我们很大的想象空间,做一些有趣的技术实现。 http://www.w3.org/TR/webmessaging/ 更多的消息通信内容 Demo http://jiongks.name/demos/web-msg/ 我的新网站 囧克斯,空间架设在sae上:http://jiongks.sinaapp.com/ 大家也可以通过 http://jiongks.name/ 直接访问,这个域名我前阵子刚买下来。 现在刚开始,里面东西并不多,我放了之前的demo和ppt充数,新网站的一些未来打算我也已经想到了一部分写上去了。另外不知道新设计的皮肤经不经得起大家的审美考验。欢迎大家参观留言助兴 :-) 今后不老歌会继续保持更新,但也会考虑把内容和访问量逐渐迁移引导过去。 恩,就是这么多了。

2012/3/24
articleCard.readMore

网站装修计划

写给自己的网站装修计划。 首先,选个现成的程序。这个已经选好了,就是Typecho了。 然后,给这个程序做个皮肤,经过了前后设计三个版本之后,我觉得现在这个看着靠谱一些。为了避免以后换了别的皮肤。在此截图留念 然后,把我之前分享过的一些demo、ppt等内容汇聚过来。现在基本都照原样拿过来了。不过我觉得还不够,因为之前的幻灯片都是针对webkit设计和开发的,另外url规则还是不够简单明了。回头要做的事情是:把url规则弄得更简单、兼容更多的浏览器。 然后,了解一些SEO和网站统计的知识,在这里做点尝试,也让自己更认识我的读者。(说实话我在启动这个网站的时候,并不太清楚我的读者是哪些人,喜欢什么,突然觉得这个重要) 然后,我希望尝试着放一些照片的展示。其实现在即将是视频时代了,拍照上传微博这种事情都快out了。不过我还是想跟风一把,试试看,放一些自己的想法在里面。 然后,我还希望尝试着做一些后台开发的学习和分享。这也是我转向sae的原因。最开始写博客的时候,我只是奢望一个能够直接写html代码的博客,给文章里做点样式和特效;到后来,我找到了github,通过gh-pages放静态页面;现在,我想通过sae写写php神马的。 以上是我近期的计划

2012/3/23
articleCard.readMore

Typed Arrays 是神马?

本文摘自 勾三股四 更早时期的 不老歌 博客。 在越来越多的W3C规范中见到TypedArrays和arrayBuffer这两个名词了,由于实际开发中还用不到,每次读到,就跳过去了。 JavaScript typed arrays 简介 for (var i = 0; i < str.length; i++) { console.log(str.charCodeAt(i)); } 的操作,才有可能让我们进行二进制级别的数据操作。 原理 接口介绍 Int8Array 1B Uint8Array 1B Uint8ClampedArray 1B Int16Array 2B Uint16Array 2B Int32Array 4B Uint32Array 4B Float32Array 4B Float64Array 8B const BYTES_PER_ELEMENT // 取决于具体的单位字节数 readonly length; // 长度 getter get(index); // 数组取值 setter set(index, value); // 数组赋值 void set(array[, offset]); // 设置数据 TypedArray subarray(begin[, end]); // 截取其中一部分 举个简单的例子 http://www.baidu.com/,然后在命令行里敲入下面的代码: var a = new XMLHttpRequest; a.open('GET', 'http://www.baidu.com/favicon.ico'); a.responseType = 'arraybuffer'; a.send(); 这样a.response就会得到一个ArrayBuffer对象,对象的数据内容正代表了站点图标的二进制数据。然后我们继续运行 var b = new Int16Array(a.response); 这样就得到了一份16进制格式的数据。我们将其在命令行输出,这时就会看到: Int16Array 0: 0 1: 1 2: 1 3: 4112 4: 16 5: 1 6: 4 7: 296 8: 0 9: 22 10: 0 11: 40 12: 0 13: 16 14: 0 15: 32 16: 0 17: 1 18: 4 19: 0 20: 0 21: 128 22: 0 23: 0 24: 0 25: 0 26: 0 27: 0 28: 0 29: 0 30: 0 31: 0 32: 0 33: 0 34: 128 35: -32768 36: 0 37: -32768 38: 128 39: 128 40: 0 41: 128 42: 128 43: -32640 44: 0 45: -32640 46: 128 47: -16192 48: 192 49: 0 50: 255 51: -256 52: 0 53: -256 54: 255 55: 255 56: 0 57: 255 58: 255 59: -1 60: 0 61: -1 62: 255 63: -13108 64: -13108 65: -13108 66: -13108 67: -49 68: -1 69: -1 70: -769 71: -1841 72: -14388 73: -13188 74: -881 75: -817 76: -13108 77: -13108 78: -817 79: -817 80: -13108 81: -13108 82: -817 83: -1841 84: -13108 85: -13108 86: -881 87: -49 88: -13172 89: -14132 90: -769 91: -29489 92: -13176 93: -30516 94: -824 95: -13105 96: -29489 97: -824 98: -820 99: -13105 100: -49 101: -1793 102: -824 103: -29489 104: -14200 105: -14088 106: -769 107: -49 108: -13060 109: -13060 110: -769 111: -49 112: -13060 113: -13060 114: -769 115: -49 116: -14088 117: -28673 118: -769 119: -49 120: -1 121: -1 122: -769 123: -13108 124: -13108 125: -13108 126: -13108 127: 0 128: 0 129: 0 130: 0 131: 0 132: 0 133: 0 134: 0 135: 0 136: 0 137: 0 138: 0 139: 0 140: 0 141: 0 142: 0 143: 0 144: 0 145: 0 146: 0 147: 0 148: 0 149: 0 150: 0 151: 0 152: 0 153: 0 154: 0 155: 0 156: 0 157: 0 158: 0 buffer: ArrayBuffer byteLength: 318 byteOffset: 0 length: 159 proto: Int16Array 这正是站点图标的数据,数据非常可视化,非常友好。 总结 zip.js去看看他们的实现。 XMLHttpRequest Level 2,新增的接口我刚才的例子中也提到了一部分。其它新增的接口还有请求与发送更丰富的数据格式、ajax请求可以显示进度(onprogress)等等,这里不做展开介绍了,感兴趣的可以自行移步学习。 以上

2012/3/19
articleCard.readMore

Hello World

这里就是我囧克斯的新家了,先囧一个: ╔囧╗╔囧╝╚囧╝╚囧╗ ╔囧╗╔囧╝╚囧╝╚囧╗ ╔囧╗╔囧╝╚囧╝╚囧╗ ╔囧╗╔囧╝╚囧╝╚囧╗

2012/3/17
articleCard.readMore

文明看球

本文摘自 勾三股四 更早时期的 不老歌 博客。 这个话题要从北京和山西之间的CBA半决赛大战开始。昨天山西主场赢了北京,把比赛拖入了第五场大决战,结果赛后发生了骚乱,两地球迷在网上也开始了骂站。说来也好笑,作为一个身在北京的山西人,我更关注山西队多一些,在网上参与讨论的身份却是“北京海淀网友”。你们想象得到,我在现如今,地域攻击如此严重的互联网,上去发表一下观点,多半会被自己的老乡误会,反之也一样。索性还是不参与了。后来又想买第五场大决战的球票去现场看看,不料网上开票7分钟之后就宣告售罄,自己没订到。此事也就作罢了。 都说文明看球,北京球迷指责地方球迷不能文明看球,结果北京球迷自己看球也不文明,我每次去工体看国安比赛,都能听到球迷“傻逼~傻逼~”,电视上也能听到,没完没了的。再加上山东球迷、广东球迷、上海球迷之间长期结下的“梁子”,结果大家就都扭打在了一起。 球场是个什么样子的地方呢?我在西安上大学的时候,就爱看陕西国力的足球比赛,想当年国力西北狼的威风可不是盖的。有一次做出租车,刚好司机师傅也是国力的球迷,师傅戏言,西安就是闲人多,懒汉多,爱凑热闹,所以球市特别好,只要有比赛,甭管足球还是篮球,甭管中国队还是外国队,老少爷们都一块儿上阵了。 其实这个世界上的任何地方都是如此。球赛,就是个热闹,有直播信号的热闹。球场内外发生的事情,包括常规的竞技,还有谩骂、争吵、不守秩序、扔水瓶、推搡、斗殴等等,都不是球赛这种环境所特有的,而是一个城市社会生活的写照;所谓的球场问题,其实是社会问题;所谓的球场文明,其实是社会文明。 举个反例,我甚至不敢想象,自己周围的这群球友,前一天见面还跟你一副大老粗的模样;结果第二天进了球场,就摇身一变,整得跟利物浦Kop球迷一样,脸上涂了各种颜色,带个高高的礼帽,动作整齐划一,随便一个人起个头,就跟着一起唱《You will never walk alone》?这比他冲客队球员骂脏话更震惊吧。所以,文明看球是个荒谬的提法,它似乎想把问题归咎于看球,其实你就是个凑热闹的。人在球场里的表现,都是平时生活中的习惯,看到周围人都叠纸飞机往下飞,你也就忍不住飞了。另外我没觉得在网上对骂的那些人比现场的球迷文明到哪里去了。 还有人讽刺现场的DJ“极端的”煽动情绪活跃气氛,这也是个很有趣的问题。任何群体活动都是有民意做潜在支撑的,不管是理性的还是极端的。DJ虽然是个人行为,但他喊的口号若没人搭理,自然会换别的花样或被其他DJ换掉,所以现在的DJ正是渐渐和球迷默契平衡到一起的结果。 所以我不认为他极端:这句话您第一次看的时候,请把重音放在“极端”上;第二次再看的时候,请把重音放在“他”上;如果再多看几次,那就请把重音放在“我”上。 我们不是从小就在利物浦长大的人,我们有自己凑热闹的习惯,有自己的表达方式,有自己的社会价值观。所以我们不能归咎于看球,也不能归咎于文明。这些文明的形成是自然而然的,只有比较,没有对错;或者说白了,文明是被一些社会因素和历史因素所形成的,对错不应该在老百姓这一边做评判。如果我们希望看到那个所谓的“文明”,不妨去尝试解决那些社会问题和历史遗留问题,而不要假装自己是从小在利物浦长大的球迷。

2012/3/13
articleCard.readMore

HTML5中的文件处理 之 File Writer API

本文摘自 勾三股四 更早时期的 不老歌 博客。 这里是介绍HTML5文件处理的第二部分,之前已经介绍过了基础的FileAPI,接下来是如何通过JS创造文件的部分。我们称之为FileWriterAPI。 总览 BlobBuilder接口:创建Blob FileSaver接口:提供一些列方法和事件监听方式,代表一个保存文件的过程 FileWriter接口:是从FileSaver扩展来的,提供更丰富的输出选择 FileSaver和FileWriter是不能通过接口指定要保存或要写入的文件的,它们都是对象创建时就已经确定的并且不可更改。同时FileSaver更是不提供控制写入什么内容的接口,要写入的内容也是对象创建时就已经确定的并且不可更改的;而FileWriter可以通过接口控制要写入的内容。 接口描述 #Foo”表示任意Foo类型的对象): BlobBuilder接口 #BlobBuilder.getBlob([contentType]) // 返回目前已放入的所有Blob数据构成的对象 #BlobBuilder.append(text[, contentType]) // 放入文本数据 #BlobBuilder.append(data) // 放入数据(Blob或ArrayBugger) FileSaver接口 #FileSaver.abort() // 中断保存操作 #FileSaver.readyState // 保存工作的状态(DONE、INIT、WRITING) #FileSaver.error // 最后一次出错的错误信息 #FileSaver.onwritestart // 写入操作开始时触发 #FileSaver.onwrite // 写入操作成功时触发 #FileSaver.onwriteend // 写入操作完成时触发(不管成功还是失败) #FileSaver.onprogress // 写入操作过程中触发 #FileSaver.onabort // 写入操作被中断时触发 #FileSaver.onerror // 写入操作失败时触发 FileWriter接口 #FileWriter.position // 当前写入操作所处的位置 #FileWriter.length // 文件长度(或在无权读取文件信息的情况下返回已写入的长度) #FileWriter.write(blob) // 在position处写入blob数据 #FileWriter.seek(offset) // 设置position属性为offset #FileWriter.truncate(size) // 在size处截断文件 #FileWriter.abort() // 继承自FileSaver #FileWriter.readyState // 继承自FileSaver #FileWriter.error // 继承自FileSaver #FileWriter.onwritestart // 继承自FileSaver #FileWriter.onwrite // 继承自FileSaver #FileWriter.onwriteend // 继承自FileSaver #FileWriter.onprogress // 继承自FileSaver #FileWriter.onabort // 继承自FileSaver #FileWriter.onerror // 继承自FileSaver 代码示例 Blob对象 var blobBuilder = new BlobBuilder(); // 创建BlobBuilder对象 blobBuilder.append("我今天只说三句话;"); // 连续放入文本 blobBuilder.append("包括这一句;"); blobBuilder.append("我的话完了。"); var url = window.URL.createObjectURL(blobBuilder.getBlob()); // 返回Blob对象并以此创建URL window.open(url); // 通过URL打开这个Blob对象 举例二:使用FileWriter fileEntry.createWriter(function (fileWriter) { fileWriter.write(blobBuilder.getBlob()); // 返回Blob对象并通过fileWriter写入 fileWriter.onwriteend = function () {...}; // 绑定写入操作完成后的事件 }); 这里需要注意的是: BlobBuilder和window.URL依然存在着不同的前缀,比如webkit下的接口分别为WebKitBlobBuilder和webkitURL; fileEntry和陌生的方法createWriter,这正是接下来要介绍的一套比较复杂的API:FileSystemAPI的一部分内容。想弄清楚fileWriter具体运行的环境,还得继续学习,不过抛开外部环境的形成过程,fileWriter的用法应该可以体会到了; FileSaver还没有用得到的地方,毕竟标准还没有最终形成,也许他将来某一天,借助其它规范的优势,摇身一变,就用在“另存为对话框”之类的地方了。 以上就是对FileWriterAPI的介绍,紧接着就是FileSystemAPI了,届时HTML5所有文件操作的神秘面纱都会被揭开。 (未完待续)

2012/3/7
articleCard.readMore

HTML5中的文件处理 之 File API

本文摘自 勾三股四 更早时期的 不老歌 博客。 在众多HTML5规范中,有一部分规范是跟文件处理有关的,在早期的浏览器技术中,处理小量字符串是js最擅长的处理之一。但文件处理,尤其是二进制文件处理,一直是个空白。在一些情况下,我们不得不通过Flash/ActiveX/NP插件或云端的服务器处理较为复杂或底层的数据。今天,HTML5的一系列新规范正在致力于让浏览器具备更强大的文件处理能力。 FileAPI,就是为解决这类问题而生的。 总览 FileList接口: 可以用来代表一组文件的JS对象,比如用户通过input[type="file"]元素选中的本地文件列表 Blob接口: 用来代表一段二进制数据,并且允许我们通过JS对其数据以字节为单位进行“切割” File接口: 用来代步一个文件,是从Blob接口继承而来的,并在此基础上增加了诸如文件名、MIME类型之类的特性 FileReader接口: 提供读取文件的方法和事件 input[type="file"]元素都是选中单个文件,其本身是允许同时选中多个文件的,所以会用到FileList Blob接口和File接口可以返回数据的字节数等信息,也可以“切割”,但无法获取真正的内容,这也正是FileReader存在的意义,而文件大小不一时,读取文件可能存在明显的时间花费,所以我们用异步的方式,通过触发另外的事件来返回读取到的文件内容 接口描述 #Foo”表示任意Foo类型的对象): FileList接口 #FileList[index] // 得到第index个文件 Blob接口 #Blob.size // 只读特性,数据的字节数 #Blob.type // 只读属性,数据的MIME类型 #Blob.slice(start, end) // 将当前文件切割并将结果返回 File接口 #File.size // 继承自Blob,意义同上 #File.slice(start, length) // 继承自Blob,意义同上 #File.name // 只读属性,文件名 #File.type // 只读属性,文件的MIME类型 #File.lastModifiedDate // 只读属性,文件的上次修改日期 FileReader方法 #FileReader.readAsArrayBuffer(blob/file) // 以ArrayBuffer格式读取文件内容 #FileReader.readAsBinaryString(blob/file) // 以二进制格式读取文件内容(该方法已不推荐使用,感谢 @超人与酱油瓶 指正) #FileReader.readAsText(file, [encoding]) // 以文本(及字符串)格式读取文件内容,并且可以强制选择文件编码 #FileReader.readAsDataURL(file) // 以DataURL格式读取文件内容 #FileReader.abort() // 终止读取操作 FileReader事件 #FileReader.onloadstart // 读取操作开始时触发 #FileReader.onload // 读取操作成功时触发 #FileReader.onloadend // 读取操作完成时触发(不论成功还是失败) #FileReader.onprogress // 读取操作过程中触发 #FileReader.onabort // 读取操作被中断时触发 #FileReader.onerror // 读取操作失败时触发 FileReader属性 #FileReader.result // 读取的结果(二进制、文本或DataURL格式) #FileReader.readyState // 读取操作的状态(EMPTY、LOADING、DONE) 代码示例 var input = document.querySelector('input[type="file"]'); // 找到第一个file控件 var firstFile = input.files[0]; // file控件的files特性其实就是一个FileList类型的对象 var secondFile = input.files[1]; // 当file控件的multiple特性为true时,我们可以同时选择多个文件,通过input.files[n]可以按序访问这些文件 var reader = new FileReader(); // 新建一个FileReader类型的对象 reader.readAsText(secondFile); // 按文本格式读取file控件中的第二个文件 reader.onloadend = function (e) { // 绑定读取操作完成的事件 console.log(reader.result); // 取得读取结果并输出 }; 举例二:给一个含utf-8编码的文本文件file去掉BOM头信息 var size = file.size; // 先取得文件总字节数 var result = file.slice(3, size - 3); // 用slice方法去掉开头的3个字节 最后,对FileAPI实践的两点注意 #Blob.slice在webkit内核中加入了前缀,即#Blob.webkitSlice; W3C官网自由查阅 Blob URI -------- Blob对象提供URI访问的方式和可能,内存中的Blob对象无法很方便的传递给其它页面,于是我们设计了Blob URI,使得这个Blob对象可以在同源的任何网页中,通过向这个URI发送Ajax请求等方式进行访问。 window.URL.createObjectURL(blob[, options]) window.URL.revokeObjectURL(url) 前者可以为blob创建一个URI,后者可以取消url对应的blob关联。这里面还有一个options值得注意,目前options中只有oneTimeOnly特性有效,表示该url第一次被调用后自动revoke。 File继承自Blob,所以我们也可以将某个File对象转化成URI供同源网页访问和使用。 var url = window.URL.createObjectURL(imageFile); ... img.src = url; ... window.URL.revokeBlobURL(); -------- 补充完毕 -------- 以上就是FileAPI的简单介绍。万丈高楼平地起,后面的文件操作会更神奇更有趣。 (未完待续)

2012/2/23
articleCard.readMore

独生子女、互相等和不耐烦

本文摘自 勾三股四 更早时期的 不老歌 博客。 我自己在家里就是独生子,从小就知道传说中的亲兄弟亲姐妹是什么意思——无非就是比我的堂哥堂姐表弟表妹“亲”一些呗;无非就是爸爸妈妈是一样的呗;无非就是除了放暑假放寒假的其它时候也可以一起玩呗。感觉都不是很有所谓的,甚至觉得“天呐那个表妹一放假就每天缠着我这要是亲妹妹可不得把我给烦死”。至于别的事情,就一直没什么特殊感觉了。 恩,衣来伸手饭来张口,没错我觉得我有点儿;恩,贪婪自私,没错我也觉得我有点儿;恩,被宠坏了,没错我也觉得我有点儿…… 因为我自己是独生女啊,所以一开始很不习惯集体生活啊,什么事情都要等啊,就很不耐烦啊。 好吧,这句话对我来说是很有画面感的。 我猜,每一段这样小小的经历,都是很宝贵的,它会和你一起长大。 不然我当时就没事了。 我的人性还需要继续磨练。 赵锦江,你真的还是没做完,没准备好。

2012/2/20
articleCard.readMore

IndexedDB技术简介(四)

本文摘自 勾三股四 更早时期的 不老歌 博客。 这篇文章会接着介绍IndexedDB(以下简称IDB)。我们会介绍如何解决在webkit内核下、新旧版本规范的兼容问题。 window.indexedDB被改为了带有webkit前缀的变量window.webkitIndexedDB。同时发生变化的还有两个对象IDBKeyRange和IDBTransaction。如果想兼容gecko和webkit内核,那么可以在程序的开头加入如下代码: if ('webkitIndexedDB' in window) { window.indexedDB = webkitIndexedDB; window.IDBKeyRange = webkitIDBKeyRange; window.IDBTransaction = window.webkitIDBTransaction; }else if ('mozIndexedDB' in window) { window.indexedDB = mozIndexedDB; } var req = window.indexedDB.open(dbName); // 旧版在这里不需要写明dbVersion req.onsuccess = function (e) { var db = this.result; if (db.version != '1.0') { var subReq = db.setVersion('1.0'); // 通过setVersion修改版本号,而不是onupgradeneeded事件 subReq.onsuccess = function (e) { // TODO: real success code }; } else { // TODO: real success code } }; 除了上述的两点不同,新旧两版的接口设计基本上是相同的。 var req = window.indexedDB.open(dbName, dbVersion); // 对于旧版而言,会忽略第二个参数,因此这里可以兼容 req.onsuccess = function (e) { var db = this.result; if (db.version != dbVersion) { // 新版中两者绝对一致,否则只会触发onupgradeneeded事件,因此这里也可以兼容 // TODO: code of changing object stores for new version var subReq = db.setVersion(dbVersion); subReq.onsuccess = function (e) { // TODO: real success code }; } else { // TODO: real success code } }; req.onupgradeneeded = function (e) { // TODO: code of changing object stores for new version }; ---------------- 兼容IE的分割线 --------------- window.webkitIndexedDB和window.mozIndexedDB类似,IE10中对应的变量名为window.msIndexedDB,所以,相兼容IE,把上面第一部分的代码改为: if ('webkitIndexedDB' in window) { window.indexedDB = webkitIndexedDB; window.IDBKeyRange = webkitIDBKeyRange; window.IDBTransaction = window.webkitIDBTransaction; }else if ('mozIndexedDB' in window) { window.indexedDB = mozIndexedDB; }else if ('msIndexedDB' in window) { window.indexedDB = msIndexedDB; }即可。下面的“全兼容”的例子已经用到了这段代码。 上一篇文章中的demo吗?我通过上面的兼容方法,对这个例子做了进一步的兼容性处理和接口封装,得到了另一个demo: DEMO演示链接 (firefox/chrome/maxthon) 基本原理、接口定义、并通过一个简单的例子,进行了gecko/webkit内核下的新旧规范的兼容和适配,希望诸位看过之后有所收获。IndexedDB的用途和用法还有很多,在此不一一列举,大家可以在W3C的官方文档中继续研究和探索。 完

2012/2/15
articleCard.readMore

IndexedDB技术简介(三)

本文摘自 勾三股四 更早时期的 不老歌 博客。 今天做一个IndexedDB(以下简称IDB)的demo,运行环境是Firefox 10。 DEMO演示链接 (firefox 10+ only) <body onload="<strong>init()</strong>"> _button onclick="<strong>clickAddBtn()</strong>">Add_/button> _ul <strong>id="list"</strong>>_/ul> </body> 为了演示方便,我们引入jQuery作界面处理,再声明一个全局变量db,作为数据库连接的句柄;再声明一个全局变量list,作为网页中列表元素的jQuery句柄。 var db; var list = $('#list'); 然后定义数据库初始化的行数init function init() { var req = window.<strong>mozIndexedDB</strong>.open('readinglist', '1.0'); req.onsuccess = function (e) { <strong>db = this.result;</strong> // TODO: 连接成功后展示列表 }; req.onupgradeneeded = function (e) { <strong>db = this.result;</strong> // TODO: 版本不同时创建一个新的object store }; } 这段代码的作用是初始化数据库(readinglist)连接,并在第一次连接数据库时创建表(links)。我们把展示列表的函数定义为showList(),把创建表的代码也补充完整,即: function init() { var req = window.mozIndexedDB.open('readinglist', '1.0'); req.onsuccess = function (e) { db = this.result; <strong>showList();</strong> }; req.onupgradeneeded = function (e) { db = this.result; <strong>db.createObjectStore('links', {keyPath: 'url'});</strong> }; } 然后我们定义添加/删除/展示链接的函数:add(title, url)/remove(url)/showList() function add(<strong>title, url</strong>) { var <strong>link</strong> = { title: title, url: url }; // 创建要存储的对象 var transaction = db.transaction('links', IDBTransaction.READ_WRITE); var store = transaction.objectStore('links'); <strong>var req = store.put(link);</strong> // put的作用是key存在时做更新处理,不存在是做添加处理 <strong>req.onsuccess = showList;</strong> // 添加成功后重新展示列表 } function remove(<strong>url</strong>) { var transaction = db.transaction('links', IDBTransaction.READ_WRITE); var store = transaction.objectStore('links'); <strong>var req = store.delete(url);</strong> // 删除此链接 <strong>req.onsuccess = showList;</strong> // 删除成功后重新展示列表 } function showList() { // TODO: clear element: #list var transaction = db.transaction('links'); var store = transaction.objectStore('links'); <strong>var range = IDBKeyRange.lowerBound(0);</strong> // 创建关键字范围描述 <strong>var req = store.openCursor(range);</strong> // 创建在上述范围内遍历的游标 req.onsuccess = function (e) { var result = this.result; if (result) { var link = result.value; // TODO: append this link to element: #list <strong>result.continue();</strong> } }; } 注意这里的IDBKeyRange和store.openCursor是用来遍历列表的,前者确定遍历的范围,后者根据前者的范围逐条触发onsuccess事件,这里定义的遍历范围是大于0,即所有非空的url,其实所有js类型的值都是可以在一起比大小的,如果想测试比较任意两个key的大小,可以运行函数window.mozIndexedDB.cmp(any first, any second)。 TODO的部分补充完整,再把界面上的事件绑定好。编码工作就完成了。 function showList() { <strong>list.empty();</strong> var transaction = db.transaction('links'); var store = transaction.objectStore('links'); var range = IDBKeyRange.lowerBound(0); // 创建关键字范围描述 var req = store.openCursor(range); // 创建在上述范围内遍历的游标 req.onsuccess = function (e) { var result = this.result; if (result) { var link = result.value; <strong>appendLink(link);</strong> result.continue(); } }; } function appendLink(link) { var url = link.url; var title = link.title; var li = $('_li>_a href="#" target="_blank">_/a> _button>X_/button>_/li>'); li.find('a').attr('title', title).attr('href', url).text(title); li.find('button').click(function (e) { <strong>remove(link.url);</strong> }); list.append(li); } function clickAddBtn(e) { var title = prompt('please input the title') || '[No title]'; var url = prompt('please input the url', 'http://'); if (title && url) { <strong>add(title, url);</strong> } } DEMO演示链接 (firefox 10+ only) 下一篇讨论webkit下使用IDB的注意事项,并提供兼容问题的解决办法。 (未完待续)

2012/2/13
articleCard.readMore

IndexedDB技术简介(二)

本文摘自 勾三股四 更早时期的 不老歌 博客。 接下来介绍IndexedDB(以下简称IDB)的JS接口设计 如图所示,我们按照操作过程,把IDB的接口分成三部分来介绍: 初始化数据库连接 var req = window.IndexedDB.open(dbName, dbVersion); req.onsuccess = function (e) {...} req.onupgradeneeded = function (e) {...} req.onerror = function (e) {...} 这里有两个重要的参数,dbName是数据库的名称,dbVersion是数据库的“版本”。第2个参数“版本”可能不太好理解,IDB不允许数据库中的表在同一个版本中发生变化,所以当我们创建新表或删除旧表的时候,必须使用一个不一样的版本号。他的作用在于避免重复修改数据库的表结构。默认的版本是空字符串"",我们在使用时,可以使用"1.0"。如果请求中的版本号和当前数据库的版本号相同,则会触发onsuccess事件,如果版本号不同,则会触发onupgradeneeded事件,我们在这一事件中可以对数据库的表结构进行修改,然后再触发onsuccess事件。 在数据库中建表 req.onupgradeneeded = function (e) { var db = req.result; vardb.createObjectStore(storeName, optionParameters); }; 之前提到了,当被访问的数据库版本号需要发生改变时,onupgradeneeded事件会被触发,我们就从这个事件继续说起。通过req.result我们可以得到当前的数据库对象db。db有一个方法createObjectStore,是专门用来创建表的。第一个参数是表的名称,第二个参数是可选的,它决定了我们要创建的表是内联关键字还是外部关键字,关键字是否需要自动生成,代表这两个设置的字段分别是keyPath和autoIncrement。比如,当第二个参数是 {keyPath: 'profile.id', autoIncrement: false} 时,说明这个表采用内联关键字,且keyPath是profile.id,同时关键字不是自增的,需要每次插入数据时手动设定;当第二个参数是 {autoIncrement: true} 时,说明这个表采用外部关键字,并且关键字是自增的。 onupgradeneeded事件中删除一个表,方法是db.deleteObjectStore(storeName)。道理很简单,就不展开论述了。 在表中存取数据 req.onsuccess = function (e) { var db = req.result; var transaction = db.transaction(storeNames, mode); var store = transaction.objectStore(storeName); var subReq = store.add(value, key); // var subReq = store.get(key); // var subReq = store.delete(key); // var subReq = store.clear(); subReq.onsuccess = function (e) { console.log(subReq.result); }; }; 对表中数据的存取通常是在onsuccess事件之后进行的。同样的,我们可以通过req.result获取数据库对象db,并随时通过db进行各种存取数据的操作。 transaction,两个参数分别是会涉及到的表的名字和读写模式,表的名字可以是数据也可以是字符串,如"users"或["users", "articles"],读写模式可以是IDBTransaction.READ_ONLY或IDBTransaction.READ_WRITE。 transaction对象获取一个表,需要传入的参数是表的名称。 store这个对象进行的基本的表操作——添加数据、获取数据、删除数据、清空表。参数也都很好理解,有一个要注意的地方是,添加数据时,key是可选项,如果我们已经在表里定义了keyPath或表本身有自增关键字,则key是不需要写的。 subReq.onsuccess事件中,通过访问subReq.result获取操作结果。添加操作的操作结果是关键字,获取数据的操作结果是对象的值,删除操作和清空操作无需返回结果。 localStorage一样通过键值对来存取json对象。 下一篇文章,会进一步介绍遍历操作,并结合firefox 10对IndexedDB的支持情况做一个简单的demo。 (未完待续)

2012/2/9
articleCard.readMore

IndexedDB技术简介(一)

本文摘自 勾三股四 更早时期的 不老歌 博客。 IndexedDB 是HTML5中的一种数据存储方式。用来帮助网站,在浏览器本地,存储结构比较复杂的数据。它和HTML5中其它的数据存储方式有一些共性: cookies类似,IndexedDB是每个域名独立存储数据的。 localStorage相比,IndexedDB可以存储任意格式的json object,而localStorage则只能存string,我们在使用localStorage存储复杂数据的时候,常常会协同JSON.parse和JSON.stringify一起工作,而IndexedDB则可以直接存取对象,无需转换成字符串。 w3c官方文档,这里有一份我参与翻译的中文版文档,这里还有一些localStorage的使用建议。 web sql database类似,IndexedDB也分数据库,每个数据库可以建立多个不同配置的表,而且所有的操作都在事务(transaction)中完成,不同之处在于web sql database是通过SQL执行语句来完成操作的,而IndexedDB则直接通过JS API完成操作。 IndexedDB的整体存储结构 同源策略,每个源都拥有独立的大存储空间;每个大存储空间内,又可以通过当前源下的页面脚本创建多个数据库;每个数据库可以包含多个表(ObjectStore);每个表都是一个json对象列表,可以存储多个json对象,比如{"name": "jinjiang", "age": 26}。 ObjectStore中的key out-of-line keys: key-value pair) inline keys: keyPath) "a" => "b" [3, 7] => 21 第2种是通过value中的某个属性字段直接用作key。因为value都是json数据,所以我们可以这样做,假如我们想创建一个表,里面的数据是类似这种感觉的: {profile: {id: 1, name: "葛优"}, girls: [...]} {profile: {id: 7, name: "James Bond"}, girls: [...]} {profile: {id: 8, name: "周星驰"}, girls: [...]} 那么我们就可以把profile里的id作为key,方法是为这个表指定一个keyPath keyPath => 'profile.id' 这样ObjectStore就会自动按照每个value的value.profile.id进行识别和匹配。 key generator),这一特性可以让IDB在添加数据时自动分配一个唯一的key,如果是第2种key,IDB还会把key存在响应的keyPath下。 以上就是对IndexedDB存数结构的介绍,先告一段落。 下一篇文章,会通过简单的接口介绍,帮助大家进一步认识IndexedDB。 (未完待续)

2012/2/8
articleCard.readMore

把博客的字体进一步调大,同时去掉了侧边栏

本文摘自 勾三股四 更早时期的 不老歌 博客。 如题 现在正文大小已经调整到了20像素。之前是14像素。 同时文章标题的大小调整到了24像素。之前是16像素。 最大图片显示大小调整到了800像素,之前是500像素。 侧边栏基本都是些没有用的东西,所以与其占地方,干脆去掉得了。 希望此次改动可以尽量接近“阅读模式”

2012/1/31
articleCard.readMore

“模仿别人是为了找到自己”

本文摘自 勾三股四 更早时期的 不老歌 博客。 偶像这个词在我脑子里一直是个纠结的名词。有的时候我觉得崇拜偶像或模仿偶像很媚俗,另一方面在做事的时候又希望可以找到一些方法和经验。在一开始懵懵懂懂的时候,一定是完全照搬偶像的东西,至于自己的想法,都是到后段才开始融入的。 我们需要更理性的思考和判断,而不是一味的崇拜和相信,不然会很可怕。到最后,偶像和神话将不复存在。大家都和你一样是个普通人。换句话说,我们一直觉得自己很普通,但有一天也变成了别人眼中的偶像。 有一个模仿的表演,几个演员分别饰演葛优、周润发、李小龙、李宗盛和梅艳芳,惟妙惟肖。评委周立波在给了这个表演“YES”的同时,跟他们说了一句话: “模仿别人是为了找到自己。” 顿时感慨万千。 希望大家都可以在新的一年里找到更真的自己。

2012/1/28
articleCard.readMore

写给我未婚妻的2011年

本文摘自 勾三股四 更早时期的 不老歌 博客。 Hi, 我之前讲个人感情问题时省略的那1000字的时候了。 1000这个数字是我随口说的,以前高考的时候,要求作文需要写满800字,那在当时几乎是我写单篇文章的上限了,超过800字,我就语无伦次了。 现在在不老歌,如果你写的文章超过了1600个字符,那么在确认发表的时候,不老歌网站会提示你,你的文章超过了1600个字符,是否要单独为文章写个摘要?因为这样,博客首页就会只显示摘要,太长的正文就会被隐藏,点击链接进入文章的页面才会看到正文。也因为不老歌有了这个功能,我很清楚,自己越来越可以轻而易举的写出一篇“千字文”。我去年一共写了48篇博客,70%都被提示过写摘要了。这让我觉得勤动笔头还是有收获的,最起码收获了一些自信。期间,我还在外准备过3次100~200人规模的技术分享,当我想把这三次的分享内容呈现在博客上时,发现它们都超过了8000字符——这是不老歌的又一个“槛儿”,也就是一篇文章的最大长度。我不得已把前两个分享内容分成了上下两篇,第三个分享直接做成了在线PPT。上述的这些今年的“文学成就”,都是我读书时不敢想象的。但今天,我通过一些有意识的锻炼和学习,做得到了。 写上面这些,是想说,也许这是我为什么随口说出1000这个数字的原因,这应该是我能够有把握掌控,同时也是可以把话说清楚的字数,多一些少一些好像都不太对劲。 不过这里也有些区别,我以前写的东西都是自己即兴的心得和随想,一切都是很自然的,但还从来没有被如此安排写一篇“命题作文”,一下子不知从何说起了,顿时就又想到了高考…… 还有一个区别是,我很少这样给身边的人写东西,感觉很特别。我以前跟你讲话,都是当面沟通,直来直去,就算是用电话、QQ、短信,也是有互动的,就会假想出你的模样,感觉你就在我对面,那种感觉是很美妙很亲切很甜蜜的,但坐在一台冷冰冰的电脑面前,更像是在跟电脑说话,跟程序说话,始终打不卡话匣子。也许我再多写点废话,会慢慢进入状态,能够在这个白色的文本框背后,看到你在对我笑,在听我跟你讲话;我是不是还可以凑过去抱一抱你,蹭一蹭你的小脸蛋,搂着你一起坐在大床上看康熙来了 :-) 每当我回忆自己的工作、学习、生活的时候,我都会把自己以前写过的博客、换过的签名档翻出来,傻傻的看一遍,然后就知道该怎么写了。但是我从来没有在任何地方上提起过你,所以接下来会提起的那些事情,我敢保证,绝对都是我内心深处的记号,不需要任何方式帮我提醒的。 1月,元旦,我们一起来太原见过了我家里人,奶奶说了,一看就是自家人,显得特别亲切,能被自己的家人也一样喜欢,这对我来说是何等的幸福。我同时也托你的福,和你一样,第一次去了一直想去没机会去的乔家大院和平遥古城。 2月,过年了,我们总是彼此感叹过年的时间分开的太久了,就像现在这样。情人节那天我给你准备了你最爱吃的草莓和巧克力,你给我讲了个兔子的故事。 3月,我跟爸妈一起去了趟香港和澳门,因为一些难言之隐,我们没有办法同行,不过我为你挑了很多礼物,而且我忍住没有去迪士尼哦!留着以后有机会和你一起去XD 4月,我们去了青岛和威海,看了满脑子的沙滩和大海,吃了一肚子的海鲜和酱汤,哈哈。你在海边的那张照片最美了!我拿它作桌面作了很久呢。 5月,我们在鸟巢看了滚石30周年的演唱会,一口气看到了好多大明星,从下午4点High到晚上10点半。我记得那么多明星里面,莫文蔚和刘若英最让你疯狂。我这个人来疯反而显得没有你投入,只管给你看包了。 6月,我被带去你家过粽子节了,那几天一直鸭梨很大,我还记得我最后一天喝多了……最不堪回首的一幕。 7月,8月是最热的两个月,好像没有发生太多的故事,但我在这段时间记住了一家名叫“雕刻时光”的餐厅,你说,我们不是在雕刻时光,就是在去雕刻时光的路上,令我印象深刻。还有就是我的房租提前到期了,那段时间挺不好过的,说真的我一度心脏都受不了了,谢谢你陪我帮我想各种办法度过这段日子。 9月,是在搬家中度过的。 10月,我们订婚了,预谋已久的。我跟你求婚的日子你应该不会忘记吧(对吧,真的不会吧,告诉我你不会忘记的对吧……)。地点就在你最爱的雕刻时光,我第一次给店员服了小费,把他们提前都“买通”了——天呐为了你我这都干得出来!另外,我们买了电压力锅,拿它蒸饭炖排骨神马的最给力了!“排骨就在锅里面自己炖自己”。 11月,是属于订婚综合症的一个月,过得飞快,每天都在讨论各种严肃的问题。我觉得我当时有些事情太坚持了,没啥意思。也许凡事不要太纠结,严肃的问题也可以变成轻松愉快的事情,这是我在11月学到的东西。光棍节那天我们去看了失恋33天,原来除了跟几个同事一起蹭Jeff的披萨、跟几个孤男寡女去欢乐谷之外,这个节日也能这么过。真幸福! 12月,你因为几个好姐妹忘了你的生日还哭了一鼻子。亲,就算全世界都把你忘了,我保证我一直都在。而且还会唱歌给你听。 还有几件事情,我也想特别谢谢你。 谢谢你总提醒我多喝水,多吃早餐,早点休息,有段时间我胃不舒服,多亏你照顾我。 谢谢你在你老爸老妈面前给我说了那么多好话,你把我说的那么好让我很惭愧,尤其是我其实不太“顾家”,是个工作狂,也不太会挑衣服买鞋,衣服鞋帽都是你陪我看的。 谢谢你给我做那么多好吃的饭菜,今年我也要多多学习厨艺了——先从我们家拿手的面食开始,今天我刚跟老爸学来了刀削面哦!另外我也会在每个周末继续骑车载你去早市买菜。 谢谢你包容我的个性,也让我开始学着控制情绪。 谢谢你支持我的事业,这让我更加珍惜和你在一起的时间。 谢谢你支持我看足球踢足球这件事,呵呵。 谢谢你给我这个机会把它们写出来。 谢谢你,fery! 我们的2012会更精彩! I Love You. 大兔子

2012/1/26
articleCard.readMore

写给自己的2011年

本文摘自 勾三股四 更早时期的 不老歌 博客。 2011年,说过了技术和工作,最后是生活。 槐花、柳絮、蟑螂。 也许人生就是这样,接受着蟑螂、烦恼着无孔不入的一阵阵柳絮、发掘着来无影去无踪的槐花香味。 如今,大千世界一如既往的魅力十足,槐花香味依旧;而虽有风沙和“毒气”,有糟糕的餐饮服务,有黑心的房东中介,有拥堵的交通和下水管道,有与日俱增的物价,那都算是蟑螂的部分了;柳絮已全然不觉。我觉得能够接受或放下一些内心的理想化的纠结,也算是成熟了吧。 正如我们老板Jeff在新春邮件中所说,基本上,人工作的时间,已经是多于了跟家人、朋友在一起的时间的。我从去年(指农历虎年)开始意识到这是一个问题,因为当我年末回家参加我姐婚礼的时候,才发现,这是我自己今年第一次回家,还是家人提醒我的,当时的我非常惭愧。回想一下,自己确实都把时间给了工作,虽然乐在其中,不觉得什么辛苦或劳累,但经过这次的反思之后,觉得有些残忍。于是暗自决定,以后只要有假期,就一定回家看看家人。今年,我一共回了6次家,算不少了,昨天奶奶还说我今年比以前上学的时候还回来得多,呵呵,对我自己有些安慰了。 还有就是我在北京的老同学和好朋友们,能不时一起聚餐,而且能找马哥下场踢一次球。这两件事情都没有以前疯狂了,以前每个月都有同学聚会啊有木有,都是先逛游乐园然后吃大餐然后KTV的啊有木有,每周去两次交大操场的啊有木有……这个还是应该保持一下。当然,我今年还认识了太多的新朋友了,也非常开心。 个人感情生活在此省略1000字 最后,英文、健身、作息——新年三大目标,一定要做到 就这些,没了 新年快乐

2012/1/22
articleCard.readMore

手把手教你入门EaselJS做HTML5动画

本文摘自 勾三股四 更早时期的 不老歌 博客。 今天借由EaselJS这个脚本库,写一个简单的动画给大家当做为示范。 DEMO演示链接 信息和文档 素材 我们今天要完成的动画是通过Canvas技术,一帧一帧绘制出来的,那么首先需要每一帧动画的素材。我们从http://cuttherope.ie/找到了一些素材,并把每一帧动画都切割成100*100像素的图像。这里我们选择了小青蛙的两个状态动画:点头的状态和撅嘴的失败的状态,一共19+13=32帧。 sprites.png。 准备工作 首先,导入easeljs脚本库 _script src="easeljs.js">_/script> 然后,创建canvas标签 _canvas id="canvas" width="100" height="100">_/canvas> 准备编写JS 创建舞台(stage) // create a new stage and point it at our canvas var stage = new Stage(document.getElementById('canvas')); 创建小青蛙 小青蛙是由一个 BitmapAnimation 类创建出来的全局变量 icon,这类对象可以在舞台中播放动画。而动画是由 sprites.png 中的图片循环播放生成的,这里我们需要通过另一个类创建对象:SpriteSheet,整个小青蛙的创建方法如下: // create a SpriteSheet using "sprites.png" with a frame size of 100x100 var spriteSheet = new SpriteSheet({ images: ["sprites.png"], frames: {width: 100, height: 100} }); // create a BitmapAnimation to display frames from the sprite sheet icon = new BitmapAnimation(spriteSheet); 播放动画 创建完毕之后,这个小青蛙的对象需要添加到舞台中: // append into stage and start animations stage.addChild(icon); icon.gotoAndPlay(0); Ticker.addListener(stage); 对上面的代码做一下解释:第一行代码是把小青蛙的对象添加到舞台,用到了舞台的 Stage.prototype.addChild 接口;第二行代码是让动画从第1帧开始播放,用到了 BitmapAnimation.prototype.gotoAndPlay 接口;第三行代码则是开始播放动画,它的原理有点意思,Ticker 是EaselJS中的又一个类,用来作为控制动画播放的“节拍器”。当我们使用 Ticker.addListener 方法时,节拍器会有节奏的触发第一个参数的 tick 函数(默认是20帧/秒),而 stage 对象刚好有一个默认函数 Stage.prototype.tick,会把舞台中的所有动画元素向前播放一帧。这样,小青蛙的动作就会连贯的播放出来了。 此时的阶段性DEMO演示链接 1 加入动画的控制,让小青蛙连续点头 接下来加入一些动画的控制,比如让小青蛙处于循环点头的状态——事实上这才是割绳子游戏中的正常状态。 // bind animation events icon.tick = function () { if (icon.currentFrame == 18) { icon.currentFrame = 0; } }; 这里给icon.tick赋值一个函数,这个函数会在每一帧动画播放之后被触发。我们在这个函数中判断,如果播放到第19帧时,立即从第1帧继续播放。这样,小青蛙点头的动画就可以重复播放了。设置当前帧数的方法是修改 icon.currentFrame 的值。 此时的阶段性DEMO演示链接 2 加入小青蛙撅嘴状态的切换 在这个基础上,我们再加入让青蛙撅嘴的状态,并且可以通过回车键使小青蛙切换到撅嘴的状态。 window.onkeydown = function (event) { if (event.keyCode == 13) { icon.gotoAndPlay(19); } }; 刷新页面,这时,我们看到,小青蛙还是处在连续点头的状态,当我们按下回车键时,小青蛙立刻从第20帧开始继续播放,即撅嘴。但有个小问题,因为节拍器是循环播放的,所以当撅嘴的动画播放完毕之后,动画会自动跳到第1帧。即重新回到连续点头的状态。 此时的阶段性DEMO演示链接 3 最终完成 首先,加入toggle函数,通过flag记录当前的状态是连续点头还是撅嘴: function toggle() { if (flag) { icon.gotoAndPlay(0); } else { icon.gotoAndPlay(19); } flag = !flag; } window.onkeydown = function (event) { if (event.keyCode == 13) { toggle(); } }; 然后,绑定 icon 的另一个事件 onAnimationEnd icon.onAnimationEnd = function () { icon.paused = true; icon.currentFrame = 31; }这个事件会在动画播放到最后一帧时触发,即第32帧时,我们在这个事件中要处理的就是让动画暂停下来,同时让动画停留在第32帧。 DEMO演示链接 总结 以上就是我们对EaselJS脚本库制作动画的一个简单实践。通过这次实践,我们会发现其实动画无非是图片的快速连续播放,而帧的概念、多帧图片合并(sprites)的方法,让动画制作更加简单,我们结合节拍器对动画的每一帧进行控制。除了EaselJS,类似的Canvas脚本库还有一些,这个是我觉得相对简单易懂的。在此推荐给各位! 这应该是我今年最后一篇技术博客了,明年我会继续坚持写博客,多多写一些技术类的文章,多多跟大家分享和探讨HTML5。最后预祝大家,尤其是诸位技术宅男们,新年快乐!工作顺利! 以上

2012/1/22
articleCard.readMore

“吃了吧,不吃就浪费了”

本文摘自 勾三股四 更早时期的 不老歌 博客。 我不太确定是不是中国人都听人说过这句话: 吃了吧,不吃就浪费了。 东西吃不完的时候,我们的第一反应是剩下多可惜啊!多浪费啊!尤其是稍微上点年纪,对物质不那么丰富的年代还有记忆的人,是绝对不忍心的。要么撑着肚子硬着头皮吃下去,要么打包或放冰箱第二顿热一热继续吃,实在不行才会倒掉。 吃了吧,不吃就浪费了。 现在的我会思考另外两个问题: 第一,吃完是否就等于“不浪费”? 第二,“浪费”这件事情是怎么产生的? 说得极端一些,一开始菜量超标了,后来怎么着都是浪费。 做饭和点菜就好比做战略、定计划;吃饭的过程就好比实施计划、执行战略。这两个环节分得很清楚,同时又相辅相成。所以不能做得好,我们为战略欢呼;做得不好,我们说执行不力。异或遇到问题喜欢相互推搡、指指点点:“你们帮我们弄一下就搞定了嘛~很简单吧?”。这样的思维已经在我们的社会里根深蒂固了,如果不是刻意观察,谁能避免这样的问题呢?连吃饭这点小事,都做不到。 首先是认同决策时深思熟虑,执行时宽以待人;其次是保证我们可以做到这两点。为什么这样说呢?因为知易行难。决策的事一般人管得了吗?监督得了吗?影响得了吗?更何况这本来就没有好坏是否的标准。但我刚好在研究Scrum项目管理理论的时候看到这样一个好办法:所有的工作任务都必须是为执行者所接受的,而不是强制授予的,并且每天都对任务安排进行回顾,及时沟通和调整。我觉得这是个很巧妙的方法,大量的事实也证明这样做是可行的。比如同学聚餐的时候,点菜的人就会随时问其他人,点够了没?要不要再点点儿?征求大家的意见;吃的时候也不时关照一下,要不要再加个菜?或大家是不是吃得差不多了,那个还没上的菜不然退掉吧?这顿饭往往会吃得比较舒心。 @许子东 老人家的《讲稿》,尤其是其第三卷《越界言论》,深受启发的同时,感慨身边的小事情处处都蕴含着大哲理。 另外,今天又吃撑了,胃不舒服,有感而发…… 另另外,马上要过年了。呵~呵~呵~呵~

2012/1/16
articleCard.readMore

写给傲游的2011年

本文摘自 勾三股四 更早时期的 不老歌 博客。 说到一年来的体会,当然少不了公司这一块。这部分是最难以言表的,因为节奏很快,经历的事情很多。 心得之一是坚持技术交流与分享 心得之二是做好上传与下达 心得之三是保持专注,少揽活儿,多用心 心得之四是用人不疑疑人不用,把握好“四个象限”的人才 低能力高能力 高意愿辅导授权 低意愿放弃激励 表格的内容应该很好理解,就不做过多说明了,很受用。 心得之五是解放生产力 心得之六是铁打的营盘流水的兵 一次、一次回首过去的老朋友。那些年,我们一起工作过的傲游,我会永远记在心里。新的一年,我们一起工作着的傲游,我会充满期待! 人生就像巧克力,在你吃下去之前,你永远不知道他是什么口味。 这首歌之前是送给Richard、光光、阿拉丁、Benjamin、fish、DJ、Grace们的。今天,我想把它送给更多曾经共事过的人们。 http://www.tudou.com/programs/view/_KLXKJQRifg/ --> 最后也祝福大家在今后的人生道路上越走越顺,越走越精彩! 以上是我写给傲游的2011年

2012/1/14
articleCard.readMore

所谓专业

本文摘自 勾三股四 更早时期的 不老歌 博客。 记得年初 @糖拌西红柿 他老人家写过一篇大作《Be Pro》。说的是Web标准的开放性与工程师的专业性,也给了我很大启发。 我相信不管是被要求用HTML5还是XHTML2,写得出gmail的公司还是写得出gmail,写不出的还是写不出。为什么我们感觉自己的工作受到了威胁?其实还是我们自己不够专业,或是因门槛的降低而降低了对自己的专业要求。 换句话说,开放的标准给了我们的工作很大的“弹性”,对力求完美和偷工减料的工作结果都是包容的,但这仅仅体现在了当下的一些简单工作上。优秀的前端工程师是不必纠结眼前的成果,如果我们写的页面结构很清晰、语义很明确、可读性可扩展性可维护型都没的说,那么在日后需要改版或加入新功能,或被用户玩出bug来的时候,甚至在你交接工作的时候,绝对毫无压力!那些“不够专业”的童鞋估计就得埋头重新再做一遍了,而你则可以喝杯咖啡或做更多的事情。久而久之,专业与不专业的差别就体现出来了。 很多事情看到大家都在玩,感觉自己也行,其实只是门槛降低了,而不是自己多有本事。最后成功的还是那些成功人士,而且比以前更加成功,而你会输得更惨。 有的时候我甚至在琢磨,专业二字带给我们的,是否就是那种“不管我用不用心怒不努力态度端不端正都能让你求我做事同时我还能把事情做好”的神奇的东西。 总之,我觉得不论如何,专业二字依然是非常神圣的。相信专业,追寻专业,绝对没错。

2012/1/11
articleCard.readMore

写给HTML5的2011年

本文摘自 勾三股四 更早时期的 不老歌 博客。 伴随着今天公司年会的结束,我才真正觉得2011年已经过去了。想为2011年的自己写一些东西,先从HTML5开始 HTML5研究小组、w3ctech等组织的技术交流,另外一个则是开通了微博,可以结识更多优秀的同仁,了解更丰富的知识。HTML5对于我来说,和大家一样,都是个新东西,都需要“现学现卖”,但有了这两辆顺风车的帮忙,让参与其中的人可以先行一步。进而把自己的心得和收获分享给更多的人,在教学相长的同时,也收获了更多的信心、机会和成就感。这些其实是我最核心的感受,论理论深度、造诣、成果,我其实都没什么,仅仅是先行一步罢了。可能是由于HTML5研究小组主要成员这一身份的关系,现在逐渐会有周围的朋友联系我做讲座、翻译、写书什么的,我非常感激,但没有太多底气去做,尤其越是有偿的,我反而觉得无功不受禄。 @尼奥_ 的团队在 code jam 第二期中做了一个时间管理工具。我觉得那个很棒,真正觉得HTML5在给人们的生活带来便利。 HTML5说来说去就是这么多东西了,唯有更多更丰富的上层建筑,才会让HTML5真正发挥威力。果不其然,今年各种基于HTML5的工具、函数库层出不穷,尤其在Canvas、WebGL、CSS3、SVG等图形处理方面非常众多,这些上层建筑的出现会令HTML5的思路逐步变得清晰和明确。而工具和函数库的在上层,就是更多的框架、引擎和理念了。我觉得这将会是HTML5走向成熟的下一个重要标志,最后,优秀的规范、工具、函数库、平台、引擎、理念,才会催生真正优秀的HTML5作品甚至是HTML5产业。 跑出347分的高分之后,更要持续加强对HTML5的多方面支持,真的令我振奋! 看文档这件事情上多一些,从最基本的知识层面开始做起。另外我希望可以继续专注在桌面应用这个看起来很土的方向上。我觉得未来的天下是移动和游戏的,它们就好比海阔天空;而桌面应用则是大地母亲,所有知识、灵感和创意的源泉。当然这也多少跟我的工作有更多的契合。 以上是我写给HTML5的2011年

2012/1/8
articleCard.readMore

对HTML5中LocalStorage的一些使用建议

本文摘自 勾三股四 更早时期的 不老歌 博客。 上个月末的w3ctech上,有同行提到了LocalStorage这个话题,我觉得在HTML5的众多新特性中,LocalStorage算是比较实际同时浏览器也比较好实现的特性。 http://dev.w3.org/html5/webstorage/ setItem时传入int型数据(比如localStorage.setItem("a", 1)),但是它会转换成字符串之后再进行存储和准备随时调用,当我们用getItem访问"a"时(localStorage.getItem("a")),得到的是字符串"1"而非数字1。 localStorage[key] = value的写法主流的浏览器都是支持的,但不推荐这样书写代码(更正一下,标准里有getter/setter的明确规定,上书写法也是标准的一部分,感谢Kenny提醒)。而且很显而易见的问题是:对length、setItem、getItem、clear这样的key进行读写是会产生问题的。假如我们执行: localStorage.setItem = null; localStoarge.removeItem = null; localStorage.clear = null; 那么整个LocalStorage的接口完备性将会遭到破坏。 JSON.parse/JSON.stringify配合使用。这样我们可以方便复杂数据结构和字符串之间的转换,获取数据的时候使用JSON.parse(localStorage.getItem("a")),写入数据的时候使用localStorage.setItem("a", JSON.stringify(obj))。 setItem时如果超过容量上限,会触发QuotaExceededError异常。我的经验是,如果你是存文本的,一般碰不到这根线,可以无视;如果用DataURI方式存二进制文件,就需要特别注意了,视频的话,基本没有5MB以下的,所以不会考虑LocalStorage的,也不用特别注意;但如果是图片,很容易几百K的图片多存几张就够5MB了,所以有必要提个醒。当然有些浏览器也会通过提醒用户确认来允许网站使用更多的容量,那个是另一说了。 window.onstorage事件。其实这也不算多高级,只是用的地方比较少罢了。假如我们同时打开了同域下的多个页面,这时我在一个页面里操作localStorage.setItem、localStorage.removeItem或localStorage.clear,其它同域的页面就会触发这个事件。事件附带的参数是这样的: window.onstorage = function (event) { var key = event.key // 被修改的键名 var oldValue = event.oldValue // 旧的值 var newValue = event.newValue // 新的值 var url = event.url // 触发改变的网页的url var storage = event.storageArea // 当前localStorage的引用(当sessionStorage改变时,这里就是当前sessionStorage的引用,好吧扯远了,看不懂可以先无视) } 这个特性可以帮助我们在多个页面间实现简单的通信、同步和数据交互,比如在一个网站的用户设置页面中修改用户昵称,那么你的所有页面中的昵称也都瞬间改变了。当然,与之产生的注意事项是要回避循环修改,以免浏览器进入死循环。 以上 其实对于LocalStorage还有很多细节,对HTML5感兴趣的童鞋可以进一步挖掘。来年的交流会上,我们一定还会聊到LocalStorage。到那时,我们可以再做更深入的讨论和交流。

2012/1/5
articleCard.readMore

回味一下我2011年看到的最过瘾的5场足球比赛

本文摘自 勾三股四 更早时期的 不老歌 博客。 排名不分先后: 欧冠决赛:巴塞罗那 3比1 曼联 女足世界杯决赛:日本 120分钟2比2 点球击败 美国 女足世界杯:美国 120分钟3比2 巴西 欧冠1/4决赛:拜仁慕尼黑 2比3 国际米兰 西班牙国王杯决赛:皇马 120分钟1比0 巴塞罗那 我在我今年所有看过全场直播的比赛中,选出了5场比赛,以此作为纪念。 作为一个中国的巴塞罗那球迷,我每年都看巴塞罗那的比赛最多,其次是每天下班晚饭时间的中超,其他比赛挑着看,这种状态持续了三五年了。今年花在工作和家庭上的时间逐渐多了,巴萨的比赛还是看很多,但其他比赛相对看得少了。 今年尤其对两场女足世界杯上反败为胜的比赛印象深刻,一次是美国在被罚下一人的情况下在加时赛最后一分钟超级逆转巴西,其中玛塔的两粒经常进球、整场观众高喊“USA”的场景以及瓦姆巴赫在进球后的疯狂庆祝都让我印象深刻;另一场则是日本用精湛的技术和过硬的心理素质顽强得连续两次追平,最后令美国队在点球大战中精神崩溃。看得都是荡气回肠。 我今年尤其觉得女足比赛越来越精彩了,甚至比男足比赛更有观赏性。就连第二天的《锵锵三人行》节目里,梁文道和徐子东都忍不住在节目中大声说出了“女足真好看”这几个字! 另外就是众人皆知的欧冠决赛。“我萨”踢得气势恢宏啊!呵呵。记得赛前张路分析说:曼联的战术就是“我跑死你!”,巴萨的战术就是“我遛死你!”,比赛中果然如他所料。巴萨的三粒进球个个精彩,朴智星虚脱的身影、弗格森微微颤抖着的手以及阿比达尔的带队举杯,都是值得回味的片段。这场比赛也应该算是巴萨真正脱变成为“宇宙队”的里程碑吧。 其余的两场比赛,真的看过直播会觉得异常精彩,张力十足!精彩程度绝对不是进球就能代表的,而且很明显的感受到相互对垒的两队球员都陷入了疯狂!我这两场比赛都是站在输家的立场上看的,但看过之后完全没有沮丧的感觉,因为比赛实在太精彩太刺激了。 不知道其他人有没有2011年让你印象深刻的足球赛呢?

2012/1/1
articleCard.readMore

浅浅浅谈开饭店被顾客吃出“异物”的用户体验

本文摘自 勾三股四 更早时期的 不老歌 博客。 如今我们说餐饮业是个“服务业”,应该不会有太多争议了吧。其实我对餐饮并没有什么追求和兴趣,但说到服务,就有不少感触了。 服务二字通俗的讲就是不管我们费了多大劲,只关心顾客要的东西有没有做到,那个才是最重要的。 以前老板跟顾客结账基本都在算“我做了一盘什么,你该给我多少钱”,后来变成“你点了多少东西,该给我多少钱”。我觉得非常欣慰,但我今天不是吐槽这个的,因为我觉得真正的“服务”可以做得更好。 如今的服务员都被洗脑成“东西坏了就立刻重做一个”的机器。拿豆浆的例子说,服务员根本不关心那杯豆浆怎么了,更不会去深究这中间是什么环节出现了问题,其他顾客的豆浆会不会有类似的问题,如何在将来的服务中避免……而且我跟服务员说豆浆味道不对,服务员二话不说把豆浆扔到一边给我换一杯新的,顾客真的会满意吗?觉得这样做很爽快吗?实话说我都是抱着“试试看会不会又喝着味道不对”的心态去喝那第一口的,用餐结束后带着反胃的感觉离开的。 “重做一个”的道理在哪里?为什么不能退款?这是真正“服务顾客”的态度吗?我们发现并提出饭菜质量问题都是吃到一半的时候,这时顾客也许已经吃个半饱了,你再蛮横的端上一大盘来给我,是想撑死我吗?我觉得,这更像是通过自残来博得顾客的同情,故意做给顾客看的。而且这样做从顾客的立场浪费时间,从饭店的立场还浪费劳动成本,站在社会的立场更是浪费粮食啊! 今天在永和大王点了一杯冰豆浆,结果喝到一多半的时候喝出一些酸味的东西。我找服务员寻问,被不加过问和思索的直接换了一杯新满的。其实我不想再喝了,只是想让他们反思一下这个异味怎么来的,怕更多的豆浆出问题。他们却没有人思考,反而觉得我像没喝饱图便宜…哎 http://www.weibo.com/1712131295/zF4mVgq0KV 我觉得更好的服务,不应该局限于“你点了什么,该付多少钱”,而是“我把您伺候好了,收您多少钱”。上菜太慢了、吃出异物了、时间耽搁了、服务态度不好了,我都少收或免单,甚至按顾客的意愿进行补偿;用餐期间遇到什么困难了,也能够体谅和帮助。这里其实有一个观念转变的问题,而不是做不做得到的问题。 唉,还有就是民以食为天,是人就得吃饭,这是“刚需”,所以即使服务再差,只要价格实惠地段不差,生意还是可以照做。服务二字什么时候才能摆脱一个机械的动作真正深入人心啊真是愁死我了……

2011/12/29
articleCard.readMore

前端的本职

本文摘自 勾三股四 更早时期的 不老歌 博客。 这周末好忙,前后参加了两个前端交流活动,一个是关于前端MVC和Node.JS的,一个是HTML5的。其实说到这里,我不得不重新审视一下我称呼这两个活动为前端交流活动是否合适。 在周六的前端MVC技术交流过程中,有位多次参加这一交流活动的同行,若有所思的谈到了这样一个话题:感觉这些东西已经不是前端了。说罢,又补充道,不是前端,而是JavaScript。随后大家的话匣子被这句话彻底打卡了,开始了热烈的讨论,不过这个话题没有讨论太多,很快焦点就转移到了MVC的好处在哪里、按照MVC分工有什么优劣、分工好还是不分工好之类的……不过我后来一直在惦记着这个问题,也非常理解这位同行的想法:前端不就是展示页面么?以前大家都把焦点放在了界面上,放在了特效上,放在了浏览器兼容性上,现在哪里来的这么多数据通信、逻辑处理、对象模型?这还是“前端”么? MVC也就罢了,紧接着Node.JS来了。主讲人石头津津有味、慢条斯理的介绍着如何通过Node.JS,用前端工程师熟悉的JavaScript搭建一个博客后台……我又深深的陷入了思考。如果说MVC还有前端的影子的话,那么说Node.JS也是前端,会不会太扯了?我们身为一个前端工程师,到底应该去做什么?创造哪些价值? 还没把这个问题想清楚,又参加了另一个活动:HTML5 Code Jam。看到了五彩斑斓的HTML5世界:Canvas、WebGL、Audio、Notifications、LBS、WebSocket……LBS和WebSocket已经是完全跟前端没有关系的东西了,Canvas、WebGL虽然是界面展现层的东西,可这些都是纯编程出来的,都是枯燥的物理碰撞、矩阵变换、坐标计算,这跟Win32绘图、OpenGL渲染有什么差别呢?HTML5带给我们的,远不止是前端的世界,更是应用软件开发的世界。 我突然感觉,作为一名有计算机相关专业背景的前端工程师,我已经在这种模糊的定位中生活了很久。也许相比起传统的Win32开发、网站后台开发、新兴的Objective-C开发来说,前端开发都算是“档次”最低的。在这种长期的潜意识刺激下,当我们遇到了琳琅满目的JavaScript APIs的时候,一种找平衡的心态油然而生。我觉得HTML5在引领技术革命的同时,也抓住了这群每天被以iOS开发暴发户为首的一群传统开发者鄙视的前端开发者的“用户需求”:借HTML5抬高自己的身价,跟Native开发平起平坐,同时跟其他前端划清界限。 但是事实上,要想真的跟Native开发平起平坐,你需要的不是熟练的JavaScript,而是传统开发的眼界和思路。而对视图和样式是否敏感似乎又变得不太重要了。一个写得来Node.JS、WebSocket、LBS的人,搞不定传统前端中基本的三列布局,是很有可能的事情。 那你们说,作为一名前端,还要不要去学HTML5了? 我觉得,如果你想专注在真正的前端领域,看一看新的HTML标签跟属性,还有CSS3,还是很有必要的,其它的东西简单了解一下就够了。与其花时间深究数据结构和算法、网络通信与分布式计算、数据库管理等这些高深的编程知识,我们不妨多看看用户体验与界面设计,了解一些图像处理软件的“切图”技巧,再多多品味生活,寻找一些前端的灵感和技巧。 如果你是以程序员自居的,那么你真应该认认真真的积累一些传统应用程序的开发经验,深入的学习一些软件工程的理论和数学理论,以及产品本身所涉及的相关专业的理论,把这份工作当做一次纯粹的编程。 两者兼顾当然也是可以的,总之,要清楚这是两件事就对了。 希望我们今后在讨论HTML5的时候,也不要忽视真正的前端,正确看待前端的本职工作,正确看待自己的身份。比如在前不久刚刚结束的WebRebuild.ORG年会上,当我们看到久违的已经略显生疏的“网页重构”四个字时,再想想最近一年我们周围的变化,很有趣。 最起码我今天觉得前端和HTML5是两件事了

2011/11/28
articleCard.readMore

继续对Mac穷追猛打 之 修改原生按钮文字大小的技巧

本文摘自 勾三股四 更早时期的 不老歌 博客。 在 Mac 下,原生按钮的样子灰常口爱,起码我觉得比 Windows 下的口爱一些。虽然好多自定义按钮的样式也非常棒,但我还是固执的喜欢原版的。 -webkit-appearance: push-button;这一属性设置使得button标签的按钮保留原生按钮的样式,但同时font-size属性值也跟input[type="..."]的按钮一样,失效了。 至今没有找到一个完美的方案,mac也有难搞的一面啊 (当然,如果你打算自定义按钮的样式,就没有这些烦恼了)

2011/11/17
articleCard.readMore

WebApp时代的界面布局浅析(我在WebReBuild.ORG第五届年会北京站上的主题分享)

本文摘自 勾三股四 更早时期的 不老歌 博客。 在线幻灯片地址: http://jinjiang.github.com/slides/think-about-webapp-layout/ 文艺浏览器,那么您使用的是普通浏览器或XX浏览器,则幻灯片的内容会以普通网页的方式呈现。 WebPage时代的布局 WebPage * 其功效集中在内容的展现 WebPage++时代 不同之处: WebApp时代的到来 什么是WebApp?两条线索: 界面布局正在悄然改变…… WebPage * 内容 → 网页 → 浏览区域 WebApp * 内容 → 可视区域 WebPage vs. WebApp WebPage 回归到界面的本质 * 直接根据可视空间设计界面 换个角度考虑界面布局 以可视空间为本 → 找了几个别的领域的前端技术 介绍几种界面布局的思路 WPF * StackPanel:朝一个特定的方向(横向或纵向),依次排列子元素 Appkit (Cocoa) 坐标、尺寸及其自动调整的方式 一些特定界面元素 * Toolbar:一种特定的布局方式,只能出现在窗口的最上部。子元素从左到右排布,可以放置按钮、输入框,也可以放置“可无限撑大”的空白区域,将其右侧的子元素挤到工具栏的最后边 更多子元素的内部布局 * TableView HTMLayout height: 100% * 可以让元素撑满整个窗口的高度 新长度单位:%% * 在剩余的总长度中选取相应比例的长度,相当于WPF中的*单位 新布局方式:flow * vertical | horizontal | vertical-flow | horizontal-flow | grid HTMLayout 实践 * 傲游3浏览器皮肤系统 简单回顾一下 * 从 NativeApp 得到的启发 流式布局其实很棒 * 遭遇了流式布局缺失的界面库,才懂得流式布局的强大! DockPanel 式布局很讨喜 * 较为复杂的应用程序几乎都可以从DockPanel作为界面布局的起点 绝对定位应用最为广泛 * 在较为简单的界面中,绝对定位是最直观、成本最低的方式 绝对定位可能面临的问题 * DPI的改变 几个WebApp的“好朋友” box-sizing * 可以选择根据边框、内边距或实际内容计算盒模型,适用于边框大小、边距大小会动态变化的元素 background-origin (-clip) * 可以选择根据边框、内边距或实际内容计算背景图片的坐标起点和覆盖区域,同样适用于边框大小、边距大小会动态变化的元素 绝对定位 * 快速定位一个元素在窗口中的位置和大小,适用于尺寸需要根据窗口变化而变化的元素 display: box / flex-* * 类似 grid 的布局,可以让若干子元素,通过具体长度或长度比例,撑满整行/整列容器空间,但它是单行或单列的,并没有网格形式,适用于工具栏等元素,以及界面上大块的布局划分 transform (-origin) * 等比例伸缩盒模型中的几乎一切内容,适用于尺寸需要等比例变化的展示性元素 grid 网格化布局,从设计的角度看,非常实用 media queries 通过不同的界面尺寸,应用不同的CSS样式 multi-column / regions * 两者均属于对于界面布局细节的雕琢 参考资料 * w3c官网 - CSS:http://www.w3.org/Style/CSS/current-work 其实,这张幻灯片也是一个WebApp! HTML5幻灯片系统 传统PPT HTML5幻灯片系统 * 带有交互性的演示 希望重新定义幻灯演示 欢迎加入我们的行列! 迎接挑战吧! THANKS & QA

2011/11/16
articleCard.readMore

给我的博客换了几个Mac字体

本文摘自 勾三股四 更早时期的 不老歌 博客。 不老歌的默认字体一直是 "Courier New" 对于经常写代码的人来说,这种等宽字体非常亲切,看着也很舒服,不觉得费眼。 body, td, input, button, select, textarea { font-family: Consolas, "Courier New"; } body, td, input, button, select, text area { font-family: Optima, Consolas, "Courier New"; }code, pre { font-family: Monaco, Consolas, "Courier New"; } WebReBuild.ORG 准备了一个前端技术的分享,在设计幻灯演示的时候,也一直在纠结字体。我发现在Mac下的那个叫做“字体库”的应用程序非常贴心,可以很方便的浏览Mac操作系统下的所有可用字体,并且按照不同的需求进行了分类,还可以预览不同字号、应用到不同文字上的样子,这让我更有兴趣把所有的Mac字体都看过一遍——要知道在Windows里,浏览一种字体是一件很不方便的事情。 Monaco 个人感觉比较“可爱”的一款等宽字体 Gill Sans 比较有现代感——事实上,这款字体正是被归类为“现代”类字体,网上见到不少Keynote文档在使用这个字体,不过它被加粗的时候,就不太好看了…… Futura 这款字体跟上一个Gill Sans感觉差不多,加粗后的效果顺眼一些 Optima 就是现在我们在Mac下看到的字体,当然可能以后我的博客的字体还会变就是了,上述三个字体都是“现代”类的字体 Lucida Grande 最后一个推荐的字体,它虽然不在任何子分类里,却非常好看非常常见——因为它是Mac OS的默认字体 以上

2011/11/14
articleCard.readMore

今天又被chrome给伤了

本文摘自 勾三股四 更早时期的 不老歌 博客。 今天为了实验一个file api的效果,不得不放弃mac下自带的safari浏览器,装上了chrome——在这之前我觉得safari应该足够了,因为从开发者的角度看,我一直觉得chrome在内核方面的改造步伐过于激进,而safari相对稳妥一些,不过file api,这个东东safari确实没有支持,很纠结。在深思熟虑之后,还是决定把chrome装上了。 http://html5demos.com/file-api,其实我做的效果跟这个大同小异。 这件事情chrome for mac完成的很好,但接下来,我想把这个程序稍作改进,就遇到了问题 由于被拖拽进去的文件不见得是图片文件,这导致有些情况下,文件拖进去了,却无法正常显示成图片,我想加入一个智能判断的环节,如果图片生成失败,就进行提示。其实现成的方案是存在的,就是先把base64编码赋值给一个img标签的src属性,然后分别通过捕获这个img标签的onload和onerror事件来判断图片是否可以正常显示。 这样我的实现方案就变成了3个步骤,4段代码: * 第一步,用ondrop捕获外部图片文件拖入的事件,和刚开始一样; * 第二步,可以从该事件的event.target.result中获取图片的base64编码,然后把这段编码赋值给一个img标签的src属性,监听onload和onerror事件; * 第三步,如果onload事件触发,则再把这段base64编码赋值给background-image属性;而如果on error事件触发,则提示图片生成失败。 把代码写好以后,刷新网页,把图片文件再次拖入定义好的矩形区域,这时chrome for mac就崩溃了…… 问题基本定位在了img标签在onload中再次处理这段base64编码的时候,更具体的原因就不得而知了。无奈之下,我只好把这个新加入的功能又去掉。 这件事情就算这样过去了,但是我更觉得chrome越来越不靠谱了,除了这次的问题,我之前还遇到过或亲眼目睹周围的开发者遭遇web socket协议更改导致之前写好的程序不能工作、Audio内存管理不善导致播放大量音频时内存不能释放直至崩溃等等——更何况我这次安装的不是beta版也不是dev版,而是面向大众的正式版。如果未来的chrome一直是一个“千疮百孔”的产品,恐怕会给前端开发者带来又一轮的灾难。

2011/11/10
articleCard.readMore

搬家了!介绍一下我的新屋子 ^_^

本文摘自 勾三股四 更早时期的 不老歌 博客。 前阵子都是随便找个技术一顿瞎扯,今天扯点别的吧 现在回过头来看,这几年的漂泊对我来说有着非常重要的意义。它帮助我学会和各种人相处,解决各种“家务事”,沉着面对各种突发问题;也懂得了很多做人的道理、明白了如何礼貌待人,怎样做是会打扰到舍友的、需要注意的,怎样化解邻里之间的误会和矛盾等等这些细小的东西。当然这些东西我很难说自己已经做得很不错了,起码我已经学到了很多。 初来北京的那年五一长假,漫步在槐花香味的大街上时,才会有的感觉。翻开这篇之前有感于槐花香味的博客,我还发现,当年的我有着与今天相似的感慨: You have changed a lot. ——这是我一个同学对我近期状况的评价,我猜这里的changed是改善的意思。确实过去的几个月是我这辈子过得最爽的几个月,爽得有点忘乎所以。我现在真的觉着我是幸运的,能够自由自在的漫步在北京的街头,享受着槐花的芳香,而且不必为一日三餐发愁,这对于现在的我来说已经足够了,相比于很多仍在为自己前途命运发愁和困惑的同龄人来说,我真的很幸运,不管我们这一代人是不是幸运的,我已经知足了。不过这不代表我会安逸于现状,在转变身份的同时,我自己也多了一份稳重和成长,也感到了更多的责任感和压力。相比起现在的自由和快乐,大千世界的精彩以及自己的鸿鹄之志更加吸引我,更让我热血沸腾!!我相信,前方还有更美丽的风景,那里应该不只有槐花的香味…… Yes. I have changed a lot. 如果想夺走我的快乐,无非只有两种办法,一种是让我碌碌无为,一种是让我不劳而获。 来北京这么多年后的今天,我有这么一个好房子住,真的很开心。 欢迎大家来做客

2011/10/31
articleCard.readMore

记实践 CSS 3 中几个 background-* 属性时的陷阱一则

本文摘自 勾三股四 更早时期的 不老歌 博客。 发现最近自己的CSS编码能力有生疏的迹象…… 一切正常,跟预想的效果是一样的 li { background: #999; border: 1px #333 solid; padding: 0.5em; -*-box-sizing: border-box; -*-background-origin: border-box; } 但是发现这一改动没有“生效”,背景图片依然会错位。 li:hover { background: #ccc; border-width: 3px; } 它的出现使得li中的background-origin被覆盖为了默认值(即padding-box)。当这一属性不被修改时,只有background-image被覆盖,但li.classA刚好再次覆盖了背景图片的值,所以这一问题在当时被掩盖了,可只要稍微修改一下background-repeat、background-position、background-clip、background-origin等属性,问题就立刻被暴露了出来。 background-origin和background-clip这两个CSS3属性的简介

2011/10/27
articleCard.readMore

做一个长期规划究竟有多难?——看国足失利之后有感而发

本文摘自 勾三股四 更早时期的 不老歌 博客。 俗话说得好: 看到了一些可喜的变化,我当时觉得,中国队“把现有的资源充分利用,脚踏实地,把路一步一步走踏实,就已经够厉害了”。直到今天晚上,看到伊拉克队在大名鼎鼎的济科教练的调教下,踢出了更有创造力和侵略性的足球之后,发现这还是不太够…… 大道理谁都懂,专业的都在细节里——这对于一个公司和一个部门或一个项目组来说,都是一样的。 “红灯笼”体育场又0比2输给了帕尔梅拉斯青年队。4年之后,这支连CCTV解说员刘建宏都戏称为“史上最烂的国奥队”,肩负起冲击2018俄罗斯世界杯光荣使命的时候,就算足协能把弗格森、穆里尼奥、瓜迪奥拉都请来,又能怎样呢? 长期规划这四个字似乎从来都和急功近利的中国社会不太搭调,但10年之后的杯具,其实从今天开始就在上演了。在我们互联网这个圈子里,你说投资个东西,10年以后才成气,不管能成多大的气,准没人理你;相反你说今年能赚个几十万几百万,但明年就不好说了,大家准都抢着上。 那么2年的规划能不能打动人呢?呵呵,我还真想打算试试 我希望中国足球,可以给全天下的球迷许一个承诺。从现在开始,好好的把那些失去的东西找回来,不要再一味盯着眼前成绩了,青训、文化、产业,都认真反思一下,拿出个实实在在的10年规划出来。12年以后,也就是在我们筹备2026之前,拿出一个“成品”和“正品”来——不要半成品!不要残次品! 其实10年对于足球来说不算特别长期的规划,日本足球一规划就是50年,说50年之内能拿世界杯,一开始好多好事的中国球迷都等着看笑话,结果今年女足世界杯日本就已经兑现了这一诺言。这就是站得高看得远,科学发展观。之前嘲笑日本足球的人,现在只能自己找个地方钻起来了…… 国足,10年之内,我不再指望什么了;10年之后,我再来看你

2011/10/12
articleCard.readMore

记上周末的HTML5 Code Jam活动

本文摘自 勾三股四 更早时期的 不老歌 博客。 如题,就是这个活动:http://www.mhtml5.com/events/code-jam http://jinjiang.github.com/rich-game https://github.com/jinjiang/rich-game IT'S COOL!!! Jam is COOL! Yes! Jam is COOL! 这种为期两天的Jam正是我们这些 Web Game 爱好者尝鲜和圆梦的好舞台,没有特别高的门槛,也不需要长年累月的精力,我们只要有梦想,有激情,有志同道合的几个伙伴,就足够了。我相信参加过Jam的朋友们一定感同身受! Game is COOL! 同样的,开发游戏和开发一款传统软件或是网站是完全两个层次的工作,游戏需要的是创意,是打破常规,是逻辑思维、流程规则、界面展现、用户心理、甚至物理引擎、几何计算等很多智慧的结晶。如果没有广泛而系统的知识体系,很难完成一款游戏。而且一般的软件和网页做到“能用”和“用得舒服”就已经很好了,而游戏则必须需要做到“好玩”、“玩的时候让人过瘾”、“玩过还意犹未尽”。质量上的差别可想而知。作为一个传统应用软件的开发者,能够有机会开发游戏,真的是一个宝贵的经历! HTML5 is COOL! 这里的html5当然是指广义的Web App开发模式了。Web开发要考量的东西,除了美观程度、交互效果这些东西之外,还加入了越来越多的数据处理和更加酷炫同时开发起来也也更复杂的界面,Web可以搞定越来越多的事情了,这一点我之前就深有体会。如果你是一位多年的前端工程师,那么html5一定会让你豁然开朗! 关于我们的团队 说完活动的感受,然后再说些我们团队在整个活动过程中的小故事吧 成功秘诀之 有备而来 话说这次活动开始之前,我其实提前做了一些功课,先是看了一些WebGame的微型开发教程,然后也看了一些demo,还有一些开发框架和函数库,对WebGame有了一个概念上的深入和开发模式的了解,这里我还是想再推荐一次这篇教程:《Build your First Game with HTML5》。这让我在活动当天的策划和开发中有了更多的底气。 成功秘诀之 知识管理(wiki) 我在活动的前一天,在我的电脑里搭了一个简单的wiki,并了个字号较大,布局较简洁的皮肤。时候证明这个wiki在第二天派上了大用场! 我们没有漏掉整个团队2天之内的任何想法、讨论过程、内容细节和结论; https://github.com/Jinjiang/rich-game/wiki 正好用来做代码的版本控制。于是大家就以文件为单位,纷纷提交各自负责的代码,最后由雷毅从wiki上把所有的代码拿下来,合并在一起。 成功秘诀之 QQ群 对于一些文件传输、代码讨论的东西,难免需要即时通讯,这个时候wiki也会显得比较笨重,所以我们果断选择了QQ群。这也让我们的团队效率提速不少。 成功秘诀之 动手之前先经过充分的讨论和沟通 在开发中,返工和需求变更是最令人恐怖的事情,尤其是在这么短的开发时间内,我们是不能出任何大的差错的,否则将没有任何可以弥补的余地。我的做法是充分的讨论和沟通,让每一个细节都不再有争议、对于游戏的描述和理解也都没有二义性,才下手写代码。这一点开起来没什么,但是对于工程化的软件开发控制真的非常重要。所以我们都是到下午2~3点才开始编码的,但很快就把代码写好了。 成功秘诀之 分工明确 模块化开发 我们的团队一共6人,我把工作内容大概分成了5部分:游戏策划和美工、游戏规则和逻辑、界面上的地图、界面上的人物和路障、界面上的各种提示信息和各种操作界面,大家根据每个人的特长和兴趣自由认领工作。我划分这5个部分的时候,把每个部分之间的耦合度降到了最低——甚至不惜牺牲了一些性能和代码量,因为这样大家可以完全并行工作。这也是我们在开发上得意顺利进行的原因之一吧。 成功秘诀之 接口一定要定仔细 包括参数格式和返回值 呵呵,这里尤其强调参数格式和返回值。因为我们定js接口的时候,经常会图省事,只约定一个函数名,导致的结果就是接口联调的成本巨大。这方面其实我们在开发的时候也走了一些弯路。索性出现的问题并没有涉及核心代码的修改,不过还是让我们第二天的代码合并工作有惊无险。 成功秘诀之 互帮互助 6个人分别完成5个难易程度略有不同的模块,最后开发所用的时间还是有长有短的,但做得快的组员也没有闲着,他们都很积极热心的立刻去帮助其他人,包括review代码、做测试、写文档等等。这也一定程度上保证了代码质量。 最后介绍一下我们的团队成员吧 我们团队的名字就叫Rich,职位名称是根据《盗梦空间》里的角色起的。团队除了我之外,还有: 张绍珑:筑梦师,游戏策划和美工设计 张超:筑梦师,游戏底层逻辑和规则的实现者之一 王潇:盗梦人,游戏地图和房屋的界面实现者 雷毅:盗梦人,游戏任务和道具的界面实现者 黄雅哲:药剂师,游戏中的功能界面和提示信息的实现者 对于我们的团队,我要由衷的说些感恩的话,非常荣幸有机会和诸位一起共事,我们的合作很愉快,沟通也很顺畅,每个人也都表现出了高度的专业和尽责!只可惜活动总是会结束的,现在我们的组员已经都各奔前程了,呵呵。相信他们各自的事业也一定会非常的成功! 以上

2011/9/21
articleCard.readMore

BUILD Win8 发布大会观后感

本文摘自 勾三股四 更早时期的 不老歌 博客。 前两天Windows8的发布会真是气势恢宏,发布当天,Win8的新闻立刻占据了几乎所有科技新闻的头版头条。那天的发布会我也通过网络视频的方式看了现场直播。这里简单记录了一些观摩Windows8发布会之后的感想。 人多 这么大的会场,人塞得满满当当,看来虽然苹果和谷歌也都做得风生水起,微软的魅力是大家有目共睹的。 设备多 为了展示Win8的海纳百川,他们准备了各种电脑、笔记本、平板、手机等设备,排出了长长的一行纵队,看着真虎气啊!估计乔布斯在家一边看直播一边心想:一堆没用的东西 群龙无首 微软的发布会,有好几个人轮番上阵,有单口相声,有双口相声,有捧有逗,好不热闹。只是觉得语速都很快,手忙脚乱的感觉,都不太有范儿,也没怎么看见鲍尔默和他标志性的小宇宙燃烧。估计乔布斯又在嘀咕了:一群没用的小家伙 到处都是三星 上次在Google IO上就看到了大量的三星设备和三星赞助的“大礼包”,这次微软的发布会也不例外,突然觉得他们很吃得开,哪边的油水都不剩下。 HTML5 Metro App? 虽然早有这方面的传闻,但是当Win8中允许用户通过HTML/CSS/JS编写Metro风格的应用程序被宣告的时候,还是有一种被震撼的感觉。产生这种感觉的原因是,求同存异一直是标准和规范的常态,之前舆论都在抨击IE的陈旧和Firefox/Chrome的标准,但现在,微软也带着标准化的决心和它的Metro战舰袭来之时,我突然觉得,标准一词已经变得不那么绝对了。微软的HTML5支持较之Chrome,虽然没有那么迅速和激进,但每一步走的都很沉稳,越来越有一个平台和对用户负责任的姿态。而Chrome的OS还停留在理论层面,并且它的一些领先的理念正在被微软迎头赶上。我估计,随着浏览器技术的持续发展和竞争,Web将不再被视为Google/Webkit的囊中之物,也许到那时,一个“标准”的形态才会真正形成。而之前前端开发一直梦想的兼容性统一问题,会长期存在与更广阔的前端领域之中。 旗舰?工具?玩具? 操作系统越做越像“玩具”了,这个怎么理解呢?Windows一直以来,在我的印象中是一个万能的航空母舰,什么资源都可以利用,什么程序都可以写,什么程序都可以运行;而相比较而言,最近在移动端炙手可热的iOS系统更像是一个工具,我只确定你想要的东西,多余的自由度、资源和权限我是不会轻易给你的,这样就规避了很多安全问题和权责问题,还给了应用开发者很好的引导,间接的传授他们应该把重心放在什么地方;Windows8也应该在这个方向上前进,我估计操作系统再往后发展,会变成“玩具”一样,也就是说,除了开发者不必担心程序相互影响之外,我们可以随意的、任意的玩弄操作系统,而不会把他“玩”坏。也正因为如此,这样的系统可以适用在更广阔的领域和设备上。 开机速度 这个没得说,真的很快,不过我猜,这里的快实际上是以降低平台复杂度和界面复杂度为代价的。和上一条观点结合在一起,我觉得这将会是一个不错的方向。 UI设计这活儿越来越没法干了 做UI做到Metro UI这份儿上,光会个Photoshop、Flash或Fireworks还好意思跟人打招呼么?未来的软件界面设计,更看重的应该是界面的系统性、可用性、交互性这些层面的问题了,而不是华丽程度之类的美学问题——好吧我觉得配色这个问题还行。说白了,一个好的软件界面设计师,应该懂点前端技术——最起码要深入了解软件界面的设计思想和最佳实践。 尽管有很多美好的东西,但Windows8还是个过渡产品 从旧Windows风格和新Metro风格之间切换的设计来看,Windows8想重整旗鼓,拿旧的系统开刀,但还不想完全抛弃传统,所以就提供了这种模式切换。说实话两个东西放到一块感觉很不搭,就像你在同一个站台两侧看到一列动车跟一列绿皮儿车一样的纠结。恩,如果我能忍住,我会继续用Win7,并期待Win9,没准到那时,它都不叫Windows了。

2011/9/16
articleCard.readMore

这里是BTV体有木有!!

本文摘自 勾三股四 更早时期的 不老歌 博客。 工作嘛,只要有钱赚,开不开心不重要 产品好不好用,老板喜不喜欢,大家都在想的 我们从来都不考虑5毛的感受 都还光了,迟早得出来混的…… 我饿了,你还不给我煮碗面吃?!

2011/9/9
articleCard.readMore

传统、现代与后现代

本文摘自 勾三股四 更早时期的 不老歌 博客。 周末回老家参加小强童鞋的婚礼,顺便探亲。 说完这句话,我突然惊讶的发现,过去自己身边的“朋友”,被我用互联网这个狗屁玩意儿划分成了两类:一类是常联系的,一类是不联系的。 ——这种想法真是愚蠢至极 比如我们是不是真的可以因为QQ的存在而丢掉手机呢?是不是有电脑就可以接受提笔忘字这件事呢?是不是因为有网络就把自己锁在家里隔人与千里之外呢? 就像中国电子大厦门前的人行道一样,翻新了多少次,换了多少种砖,也没觉得好到哪里去,雨天走着一样打滑,还把沿街原本就经不起风雨的小树苗折腾得够呛…… 做软件做网站也是一样(此处省略200字) 写到这里,感觉这个题目说得有点大了,我又“后现代”了一回

2011/7/25
articleCard.readMore

Happy Elements 工程师大会归来,以及一些个人感触

本文摘自 勾三股四 更早时期的 不老歌 博客。 上周末的HappyElements(乐元素)工程师大会之行也是我和游戏公司的第一次亲密接触,收获颇多。 1、一个团队的组织架构不能没有长远规划,不能总盯着作坊去发展 公司由1个作坊变成10个作坊,总体实力不见得会翻10倍,而我们需要的正是合理的分工和编制。公司想在自己的行业领域里生存,必须具备战胜强大对手的实力!我们的工具能够从帆船进化到快艇、轮船,已经实属不易,但对手的工具都是航空母舰级别的,所以永远不能掉以轻心,而且要有信心和决心让自己成为航母。 2、木桶能盛多少水? 除了受限于短板之外,还取决于木桶的桶底有多大。你的“盘”有多大是永远都不能忽视的东西。 3、创新是一种态度和习惯,一种成功的基因 比起创新,抄袭太容易了。如果你的第一款产品就是抄来的,你就会不自觉的一直抄下去,逐渐的,你就失去了创新的能力,有一天想自己做点东西的时候你会发现比登天还难;如果第一款产品就是自主创新的,并坚持下去,早晚会成大器! 4、工欲善其事,必先利其器 在乐元素,有超过一半的工程师不是开发线上产品的,而是完善内部系统和工具的,他们在这方面的不屑努力,也让乐元素得以如此快速的发展和进步。而且在我看来,对内的工作任务对员工和公司来讲,都是一块非常好的“试验田”。不是所有的将军一生下来就打硬仗的,需要经历一些磨砺才能够真正变得英勇威武,而上了真正战场再谈磨砺就已经来不及了。 5、浓厚的工程师文化 乐元素的工程师文化有三个核心价值观:第一,Greats works with greats;第二,为工程师打造成长的环境;第三,为工程师“增值”。尤其是第二条,诸如贴发票报销之类的各种跟技术无关的事情,在乐元素都有专人负责打点,完全解放工程师的生产力。 6、我们该如何看待困难? 首先当然是正面的看法,我们面对困难会有多种选择:抱怨?回避?发现?解决?这到底是世界末日呢?还是一个机会?还是不用考虑太多呢?完全取决于我们自己的选择。 7、责任感(试问:你觉得让一个女生愿意托付一生的男人是个什么样的人?) 我觉得这个问题问得非常好!现在很多员工都不能让自己的老板“省心”,对外界说很多抱怨,对自己做很多解释,为什么要这样呢?想想自己对另一半是怎么百依百顺,尽职尽责的吧,老板当然也希望你像对待自己的爱人一样对公司好一点。 8、“风大的时候,连猪都能吹起来” 公司要想成功,最好可以找一个风口,借助风势,让自己飞起来!! 以上 总之学到的东西还是不少的,当然也包括一些技术方面的收获。希望以后可以有更多这样的机会,近距离感受其他公司的文化。也希望可以把优秀的理念带回自己的公司,帮助公司发展得更好

2011/7/20
articleCard.readMore

HTML5的视觉方案及其技术简介(下)

本文摘自 勾三股四 更早时期的 不老歌 博客。 接上一篇HTML5的视觉方案及其技术简介(上) CSS 3 前端开发的最爱! 技术介绍和代码实现 圆角 (link) 颜色渐变 (link) 多重背景 (link) 阴影 (link) 几何变换 (link) 渐进变换 (link) 动画 (link) 选择器扩展 {} :伪元素 CSS 3 的特点 优点 符合传统Web前端工程师的习惯 尊重主流的网页设计风格和技术需求 有强大的交互机制 和网页元素完美结合,不影响语义 适合较为系统或简约的界面需求 缺陷和其它不确定因素 归根结底都是矩形变换 没有处理复杂光影效果的能力 部分效果需要取巧的方式来实现 运行效能尚不成熟 兼容问题阴魂不散 实例演示 傲游浏览器在这方面的实践 CSS 3 Logos (傲游/新浪/腾讯/百度/机器猫) “2.5D”视觉效果 延伸阅读 css3.info css-tricks.com SVG 期待重生! 技术介绍和代码实现 实例1:一个简单的例子 (link) 实例2:各种简单的框线和图形 (link) 实例3:贝塞尔曲线 (link) 实例4:二次曲线 (link) 实例5:二次曲线之二 (link) 实例6:填充和描边 (link) 实例7:框线边缘处理 (link) 实例8:绘制虚线 (link) 实例9:CSS样式表 (link) 实例10:模式集成 (link) 实例11:元素集成 (link) 其它细节 元素的层次控制 遮罩和过滤 几何变换 文字渲染 SVG的特点 优点 基于矢量图和矢量变换 适用于相对静态的视觉内容 各种集成管理方式 全平台解决方案 (vml for IE) 处理不规则形状的元素 兼顾灵活度和前端开发的习惯 缺陷和其它不确定因素 标记和属性都非常琐碎,不易于学习和记忆 必须严格遵循 xml 文件格式,代码健壮性不够 无法进行像素级编辑 同样存在不同平台和浏览器的细节绘制差异 实例演示 gRapael报表 延伸阅读 《SVG开发实践》 Raphael + gRaphael 总结   Canvas CSS 3 SVG 优势 灵活、跨平台、像素级处理 事件交互性强、小巧方便、学习成本最低 兼顾灵活性和前端开发习惯、学习难度低 劣势 事件判定、平台的视觉体系无法利用、全部都是编程 效果有限 比较陌生,平台差异、代码健壮性 适用产品 大型或复杂的界面 文档类、功能型界面 规模不大,但对设计要求较高的界面 适用人群 游戏开发者 前端设计师、前端开发者 矢量图设计师、艺术家 横向实例对比:头像编辑 Canvas方案:傲游3设置头像 CSS方案:傲游账户中心设置头像 SVG方案:用Raphael实现的demo 引用说明 部分代码片段引用自: MDC: http://developer.mozilla.org/ HTML5 Presentation: http://slides.html5rocks.com/ 迎接挑战吧! Thanks & Q-n-A

2011/7/17
articleCard.readMore

HTML5的视觉方案及其技术简介(上)

本文摘自 勾三股四 更早时期的 不老歌 博客。 在此贴上本人受邀在 Happy Elements 的工程师年会的主题分享 http://jinjiang.github.com/slides/new-generated-html5-ui/ https://github.com/Jinjiang/slides/tree/gh-pages/new-generated-html5-ui 自我介绍 赵锦江(勾三股四) Tags: mhtml5/maxthon/f2e/music/football QQ: 110698041 E/M: zhaojinjiang@yahoo.com.cn Blog: http://bulaoge.net/?g3g4 Weibo: http://weibo.com/mx006 主题概要 传统网页视觉体系介绍 HTML5带来新的选择 Canvas CSS 3 SVG 技术介绍 优劣对比 实例展示 修炼之道 传统网页 Web Page → Web App 由于贵公司是游戏公司 所以我们从游戏开始谈起…… 传统网页中的“游戏”? 文字游戏 (link) 升级版的文字游戏:“字符游戏” 符号版的“俄罗斯方块” (link) 接下来我们看看传统网页是什么样子的,如何衍进的? 传统文档中的元素 大标题、小标题 段落、列表 多媒体内容(插图/音频/视频)、表格 加粗、斜体、下划线、角标 特殊效果:字号、颜色、背景、跑马灯 网页的亮点元素 书签、链接:建立章节和文档间的联系 表单:最早的交互 更多的样式和版式 (CSS) 样式 边框、边距 特殊文字效果 主题、皮肤的概念陆续出现 版式 文档流布局(横向布局) 浮动布局(纵向布局) 叠层布局(自由布局) 模板的概念陆续出现 网页不再只是“在线版Word文档”了 配套的交互开发 (JavaScript) 管理窗口状态(window/location/screen/history) 管理文档内容(dom) 管理界面样式(style) 管理界面交互(events) 设备:鼠标、键盘 管理表单控件(form) 管理用户数据(cookie) 更多的控制器 (Ajax) 链接逐渐变为按钮 表单提交逐渐变为Ajax Request 站点逐渐变为一个或几个页面 各类插件飘过…… Flash、Java、类似的还有Silverlight 音频视频解码、数据加密、本地存储、高级网络通信、高级图像处理等 视频播放、网上银行、大量网页游戏 复制到剪贴板 终于…… 贪婪的人类啊! HTML5横空出世 (Web App) 语义增强 (新增语义化标记/表单扩充/更多输入设备匹配/微格式) 多媒体增强 (视频/音频/编码格式) 通信连接增强 (多进程/Socket/消息提示器) 设备支持增强 (原生拖拽/文件系统管理/地理位置信息) 样式、特效增强:稍后详细介绍 数据管理增强 (本地缓存/本地数据存储/本地数据库) 性能功能增强 (部分属性、接口的优化和改进) Web真正有了应用的感觉 大量应用横空出世! Web Game 首当其冲! 以上就是一个前端开发工作者眼中的Web世界 扯了半天跟游戏没有关系的东东,不知道大家是不是快睡着了…… 诸位游戏开发工作者是怎么看待Web的呢? HTML5中的视觉新选择 Canvas/CSS3/SVG CANVAS 游戏开发者的最爱! 技术介绍和代码实现 实例1:简单的框线绘制 (link) 实例2:使用路径:二次曲线和贝塞尔曲线 (link) 实例3:画布状态管理:记录和恢复 (link) 实例4:导入位图:Image 文件 / DataURI (link) 实例5:动画的做法:擦了画,画了擦 (link) 其它细节 图像合成规则 框线边缘样式 文字绘制 画布变形 像素级操作 Canvas的特点 优点 符合传统游戏开发的习惯 执行效率高 效果绝对统一 可以进行像素级别的雕琢 开发级别非常底层,发挥空间很大 缺陷和其它不确定因素 事件判定的工作比较复杂,需要计算坐标 实现动画的工作比较繁琐,需要深入到每一帧 无法利用任何平台特性(表单控件、系统主题) 是一块真正的“布” 自己动手丰衣足食 实例演示 愤怒的小鸟 坦克大战 书道 延伸阅读 WebGL MDC Canvas Canvas手册 Canvas实践机会不多,就简单介绍到这里吧,不敢再班门弄斧了…… 继续查看下半部分

2011/7/17
articleCard.readMore

git和github简介(下)

本文摘自 勾三股四 更早时期的 不老歌 博客。 接上一篇git和github简介(上) 创建一个git仓库 $ git init 初始化仓库 $ git status 查看当前仓库状态 # On branch master nothing ot commit (...) $ git add . $ git add index.htm style.css ... 临时区域,前者为添加全部 $ git commit --all --message "xxx" $ git commit -am "xxx" $ git log 查看修改记录 .gitignore 文件 config.php $ git mv ... ...移动 $ git rm ... ...删除 git mv/rm和mv/rm 管理分支和标签 $ git branch 列出所有分支 * master xxx yyy $ git branch xxx 创建名为xxx的分支 $ git checkout xxx 切换至名为xxx的分支 $ git merge xxx 合并xxx分支的修改 $ git tag 列出所有标签 v3.1 v3.0 v2.5 v2.0 v1.0 $ git tag v2.0 生成一个新标签 $ git show v2.0 tag v2.0 Tagger: Jinks Zhao ... Date: Sat Jul 3 10:06:16 2010 -0400 half-way to release 1 commit 08eaf7c6b... Merge: be96dbe 39a1b50 Author: Jinks Zhao ... Date: Sat Jul 3 07:28:16 2010 -0400 Merge branch 'xxx' $ git describe v2.0-2-g8ee0f4a 部分git高级操作 $ git add -i 交互式添加 $ git reset --hard HEAD $ git reset --soft xxx $ git checkout -- <file.ext> $ git diff $ git diff <file.ext> $ git diff <commit> $ git diff <commit> >file.ext> $ git diff <branch1>..>branch2> $ git diff <branch1>...>branch2> $ git stash $ git stash apply 更多的git操作 查阅相关文档和手册: http://www.kernel.org/pub/software/scm/git/docs/ git + github.com配合使用 发布、管理、派生 远程操作相关的git命令 $ git clone git://github.com/nickname/projectname.git [localprojectname] $ git remote add <remoteName> <remoteURL> $ git fetch origin master $ git merge origin/master $ git pull origin master $ git push origin master 在github.com创建自己的仓库 在github.com创建自己的仓库 在github.com创建自己的仓库 登录 github.com 并新建一个仓库 起好名字,完成配置 本地是否已经有现成的代码? 添加本地现有的代码 或者创建全新的空仓库 $ cd existing_git_repo $ git remote add origin git@github.com:nickname/projectname.git $ git push origin master 发布、同步本地的代码 $ git pull origin master $ git push origin master 注:直接pull/push主分支并不是很好的习惯 创建自己的github pages 项目的 "landing page" 形如:http://user.github.com/project/ 做法: 创建一个名为 gh-pages 的远程分支 $ git push origin gh-pages 或者利用 Project Page Generator 自动生成 实例:实例1 实例2 官方说明:http://pages.github.com/ 从github.com派生别人的仓库 从github.com派生别人的仓库 在别人仓库的主页,点击 "fork" 按钮派生别人的仓库 完成自己的开发之后,点击 "pull request" 按钮,申请原作者将自己的改动合并到原仓库中 相关资源和参考文献 方便后续学习、实践 Getting Good with GIT 其它学习资料 http://git-scm.com/ http://book.git-scm.com/ http://help.github.com/ 《pro git》(http://progit.org/book/zh) 《Git基础》潘魏增 (http://panweizeng.com/) 引用材料 《Why Git Is Better Than X》 github.com 木有了! 欢迎提问

2011/6/27
articleCard.readMore

git和github简介(上)

本文摘自 勾三股四 更早时期的 不老歌 博客。 在此贴上本人在Web标准化交流会6月25日北京站的主题分享 http://jinjiang.github.com/slides/learning-git/ https://github.com/Jinjiang/slides/tree/gh-pages/learning-git 自我介绍 赵锦江(勾三股四) Tags: maxthon/f2e/music/football QQ: 110698041 E/M: zhaojinjiang@yahoo.com.cn Blog: http://bulaoge.net/?g3g4 Weibo: http://weibo.com/mx006 目录 什么是 git? github.com 及“开源”文化 如何运用 git? 创建自己的程序 获取别人的程序 改良别人的程序 后续学习和实践推荐 交流讨论 什么是git? git 简介 git是一种分布式版本控制系统 开发者工具 git 并不是一门语言、一个概念或一种“最佳实践” 版本控制系统 结点:每年为家里的小孩拍一张照片 分布式系统 没有服务器和客户端的区别,安全、完整。 我们为什么需要版本控制 自由发挥!自由分支!自由分享! 与开源、自由的精神相吻合! git从何而来? Linux 内核代码管理程序 git 是由 Linux 内核的的创始人 Linus Torvalds 创造的。事实上,git 是 Linus 为管理 Linux 内核的代码而建立起来的。他看过了现成的几个源代码管理系统,得出的结论是,没有一个系统是足够快的。所以他自己建立了一个这样的系统。 git的特点 小巧而快速 真正的分布式 临时区域(staging area)设计很贴心 可以和 github 完美结合 WhyGitIsBetterThanX.com github和“开源”文化 http://github.com/ “开源”文化简介 自由(linux core) / 开源(firefox) / 免费(msn) 自由软件运动 和 开源运动 舞台:互联网 (邮件列表、wiki、论坛、社区) 个人和公司都已经大量参与其中 团结就是力量! 催生了大量优秀的资源和产品 webkit 就是开源中的典型 如何参与开源项目? 寻找优秀的社区和自己感兴趣的项目 看和学:了解情况,阅读代码 从小处着手,尝试修正 bug/issue 制作补丁 (patch) 熟悉版本控制技巧 遵循编码规范和项目规范 更进一步…… 参与开源,更进一步…… 参与项目讨论和项目管理 成为“固定成员” 建立/派生自己的分支或版本 从长计议 核心在于分享和付出,而不是索取! 谁在使用github.com? git最佳实践讨论 git本地与远程的关系 如何运用git? 下载、发布、派生…… 最简单的运用 在 github.com 上寻找并下载 搜索项目或开发者 关注 / 追随 / 下载 在github.com上淘金子 搜索关键字 | follow开发者 | watch项目 在线查看或预览 点击下载按钮、解压 这也算会用GIT啊 - - 安装和配置git 从基本的命令行开始 命令行常用命令 whoami - 命令行里的"hello world" ls(dir)/cd/mkdir/rmdir 查看/进入/创建/删除目录 cp(copy)/mv(move)/rm(del) 移动/复制/删除文件 <cmd-name> -x --xxxx (/xx) 执行命令 <cmd-name> -h --help (/?) 帮助信息 注:括号内为windows中的写法 安装git Mac: Linux: sudo apt-get git ... Windows: msysgit cygwin devel → git editors → vim/nano $ cygwin/bin/ash.exe $ /bin/rebaseall 选择安装git模块 cygwin目录结构 cygwin初始化 配置git git config --global user.name "..." git config --global user.email ... 配置github账户: http://github.com/guides/providing-your-ssh-key 继续查看下半部分

2011/6/27
articleCard.readMore

记乐队在 HTML5 in China 大会上的首演

本文摘自 勾三股四 更早时期的 不老歌 博客。 wa band (wa = web app) 的表演 HTML5技术尝试 HTMl5和音乐有什么交集,当然了Audio标签了。通过在HTML文件内创建Audio标签,然后控制它的播放,就可以让浏览器输出美妙的音乐。 元我顺手写了个audio,然后写好src,再play()。 集合 立刻约了堂主一起来商量,在一个周五晚上,三里屯找了个咖啡馆,2个小时的讨论,程序的雏形大概有个模样了: 稳定性接受考验 在HTML5的世界里,理想和现实的差距往往就是透过浏览器才发现的。尽管我们尝试了认为对HTML5支持较为得力的Webkit内核,要想让一个页面连续播放上百上千个Audio还能面不改色心不跳,也是有很大难度的。我们发现Audio播放完毕之后,内存并没有我们预想中的那样被释放掉,而会不断累计,直至页面崩溃。。。各种神马方法都试过了,内存就是释放不掉。当天我和堂主虽然做出了原型来,但是一想到还有这个大问题要解决,就让人很沮丧。。。 Webkit显灵! 又过了几天,“神奇”的事情出现了。最新的Webkit版本悄悄的对Audio播放的内存滞留问题做了个小改进:当内存涨到一定程度的时候,集中释放一次内存,然后等待第二次内存峰值的到来。 头脑风暴,勾勒方案 经过了一段时间的技术尝试,我和堂主觉得可以找更多的人来一起商量节目的设计了,于是我们找田爱娜搬来了几位援兵:主要是朱尧和Sammy(朱尧?主要?傻傻分不清楚。。。),两位都是有技术背景同时很懂音乐的人!另外我在傲游的同事战歌,听说我在准备这次节目,也表示了非常浓厚的兴趣,我把他也拉了进来。 “车库咖啡”排排坐 这家咖啡店成了我们接下来的常据点。我们把大家都请了来,智刚那天也在,我们一起就现在的技术原型和节目编排展开了热烈的讨论。每个人都提供了很多优秀的灵感。 电脑、平板、移动设备,一个都不能少 为了展现HTML5世界的广度,我们希望尽可能运用所有可以支持HTML5的设备。并分布跟他们不同的乐器,演奏方式要尽量和传统乐器的演奏方式接近或吻合。 用Node.JS贯穿一切 Node.JS可以和Web Socket很好的配合,通过它们之间的配合,我们可以把所有的乐器和音频输出、视频输出都贯穿在一起。 音乐的部分 技术讨论得差不多了,如何从音乐的角度去编排一个有质感的节目变成了核心问题。其实我们并不在行,要么太技术了没有表演效果,要么效果想象得太夸张了技术难度和风险太大: 融入更多的灵感 在准备这套方案的过程中,我们又不断发现并攻克了一些技术问题,从用户体验上改进了演奏的方式以尽可能保证演出效果,同时也冒出了一些节目编排上的灵感,找到了一些更合适的曲目。 傲游小聚 期间,我、战歌和Sammy在我们公司小聚了一次,就几个核心问题做了当面沟通。这一次的沟通非常高效而且确定了很多细节,包括: 职能智能手机(后来简化成了iPod Touch)。软件的话,就是一个HTML5的幻灯演示页面、一个钢琴的演奏界面、一个鼓的演奏界面和一个沙筒的演奏界面。钢琴是通过笔记本键盘演奏的,鼓是通过触摸演奏的,沙筒是通过体感演奏的。 搬援兵v2 再次跟田爱娜报备了我们的准备情况,这次我们又得到了小组的第二次支援:亚波为我们提供了路由器和平板电脑,另外请来了赵义龙做主唱(推荐里有写东北林俊杰哦)。嘿嘿,义龙一开始很没自信,怕唱不好,我和朱尧给他做了不少思想工作滴说。 开发和彩排 开发的工作并不轻松,问题依然围绕着程序的性能和稳定性。我和战歌做了大量的实验。 遇到不少小问题 我和战歌发现通过Web Socket传输控制信号到另一台设备上播放音乐会有较为明显的延时。这样的话,只有节奏感较强的乐器才能够通过Web Socket进行演奏并保证效果。 PPT制作 PPT是我自己独立完成的,这里融入了一些我自己的灵感和创意,包括手稿风格的乐器、设备、框线图等等,都是我自己用绘图软件一点一点勾勒出来的。记得我画路由器的时候,构思了好几个手稿图,结果画出来感觉都不理想,最后换成了wifi热点的图标模板,感觉辨识度还不错,终于达到自己满意的效果了,才长舒一口气。 彩排的那点儿事儿 大家又在车库咖啡见面了,再次见面,大家都比过去更有信心了,我们的排练总体上还是比较顺利的,不过每次想到我们将会在李开复博士已经500位HTML5追随者面前表演,还是会给大家提出一些较高的要求。 延时!延时! 移动设备的延时现象,在实际演奏中,比我和战歌想象的更加明显,由其是堂主的鼓,作为整个乐队表演的基础,是不能有半点马虎的,那几天我们的程序把堂主折磨得够呛,因为堂主必须有操作的提前量,还得始终保证统一的节奏。我自己也觉得很头痛,心想这种效果应该是会被500人轰下去的那种效果吧,特别没底。最后,我给堂主减了压,把程序做得傻瓜了一些,只要第一个节奏对,后面的节奏就能得到保证。说实话,我其实是通过程序逃避了延时的问题。这件事情到现在我都深感内疚,作为技术负责人,没有真正搞定这个问题。下次再有类似的技术需求的时候,希望我可以找到更好的方案。 节奏!节奏! 前几次排练的时候,大家有点各自为战的感觉,有人节奏快,有人节奏慢,总体感觉很不协调。这种问题我们旁观者听起来听得很明显,也觉得掌握起来很简单,其实不然。我也想了不少办法尽量避免这一问题的出现,比如让大家都以鼓为标准,万一发现节奏乱了,就听鼓的声音重新找到节奏继续进行下去;比如通过调节不同乐器和演唱者的音量,让大家都可以听到自己和鼓的声音,比如故意将钢琴弹错,考验主唱能不能找到鼓的节奏继续唱等等。 表演当天踩点儿 表演将于下午1点半开始,我们早上8点半就到了,开始准备各种设备、线路、灯光、音响、舞台、投影,还联系了两个聚光灯和一个喷干冰的东东(哇,当时觉得这个活动好有范儿~)。。。 开复老师驾到,表演马上就要开始了! 开复老师1点半准时莅临大会现场,粉丝们都上前索要签名,现场一阵小小骚动,我又趁这个机会检查了一下所有的设备是否间接正常,演员是否到齐,注意事项又给大家交代了一遍。恩,万事俱备,只欠东风! 傲游3.1浏览器的表现 在这次节目的准备过程中,我毛遂自荐,推荐我们公司的傲游浏览器进行幻灯演示,以表现一下我们对HTML5的支持,尤其是傲游3.1浏览器在CSS3和Web Socket方面的表现。傲游3和谷歌苹果的浏览器一样,同属Webkit开源内核阵营,同时还针对国内的网站特色,提供了IE兼容模式。希望可以通过这次大会的表演,让更多人了解傲游已经不再是做“IE外壳”的浏览器了,并且尊重标准,拥抱标准,为开源的标准内核做一点自己的贡献,为标准的推广做一点自己的贡献。演示当天的傲游3.1浏览器表现非常完美,除了配合曲目表演的幻灯演示之外,在接下来的技术实现要点讲解的环节,也很好的实现了transform/transition/web font/text-shadow等特效。 感恩,致谢 话说回来,除了我觉得自己出现了两个失误之外,我的团队当天都表现的非常完美!非常感谢他们每一个人的全程付出! 战歌,和我长期一起配合完成技术实现,兢兢业业,和他在傲游的工作一样完美! 堂主,同样提供了很多中肯的技术建议!鼓的部分万无一失。 义龙和海泉,为整个乐队画龙点睛!演唱和演奏的最好结合! Sammy,他的钢琴很棒!在演奏的过程中适度的加入了一些额外的音乐元素,让整个表演赋予了生命力! 朱尧,低音钢琴,配合堂主,很好的控制了整个表演的节奏!表演当天帮我做了很多份外的事情! 亚波和智刚,嘿嘿,二位也帮了我们大忙,亚波提供了很棒的设备支持,智刚则帮我们出谋划策,丰富了我们的创作灵感! 口光兄,你最给力了!傲游的幕后英雄!我笔记本上的贴纸就是出自口光之巧手! 终于轮到田爱娜了,乐队的发起人,而且总是在团队陷入困境的时候伸出援手。非常感谢!! 最后,HTML5研究小组和傲游浏览器,真正促成我完成这件事情的源动力!今后我会继续为HTML5研究小组和傲游而努力奋斗!!

2011/6/12
articleCard.readMore

我们每个人都有义务让这个世界清静一点

本文摘自 勾三股四 更早时期的 不老歌 博客。 斯坦尼斯拉夫斯基 Konstantin Stanislavski (1863 – 1938) 百度百科上有他的详细介绍),今天把这位老爷子搬出来,是因为最近看到了他表演理论中的一句话: A character's actions will lead to his / her emotions. 动作引起真情 关于这句话,还有一个很形象的例子: A character is sitting at a dinner table. All of a sudden the character quickly stands up and throws the plate at the wall, thus causing more anger in the character. Rather than just trying to be mad, the character made an angry motion, throwing a plate, that made the anger greater. 把它摔碎,你会发现他产生莫名的很愤怒。这种激烈的行为会令人冲动,而且愈演愈烈。 很多人在现实中都被这一理论所影响或利用了。 有的时候走在大街上,到处都是熙熙攘攘的流行音乐和叫卖声,见缝插针的广告和推销,就觉得自己生活的世界好不清静,周围的人也好不安宁,浮躁、善变、急功近利…… 在这样嘈杂的世界里,不会有伟大的作品催生,这样的世界也无法造就伟大的人 为了自己,为了别人,也为了这个世界更美好,我们有义务让周围的环境变得清静 首先,在公共场所,不要大吵大嚷。如果想说话给谁听,离他/她近一点说,控制音量,做到尽量不要让不相干的人听到,最忌讳的就是“隔空喊话”。 然后,不要靠“视觉强奸”和分贝吸引眼球,或争得别人的共识,有理不在声高。 再次,即使这样,我们还是应该随时提醒自己,用正面的眼光看待周围的一切。 还想到一些,比较杂,也不方便都讲…… 另外道理即使都清楚,有些地方我自己之前也做的不好,同样需要注意,共勉

2011/5/26
articleCard.readMore

@自己

本文摘自 勾三股四 更早时期的 不老歌 博客。 上)(下) 新专辑为什么叫《@自己》? 当谈到新专辑为什么用了微博上常用的 @ 语法时,海泉道: 因为这个时代 羽凡还说,他很欣慰在这么多年之后,仍然找得到创业般的感觉和激情,并享受其中,觉得自己仍站在一条跑道的起点上,要更努力。作为一个“成功人士”能保持如此有斗志的一颗心,非常令人钦佩。 忠于自己比不负众望更重要 刚好又看了一期《锵锵三人行》 之 忠于自己比不负众望更重要 你要拒绝别人给你贴标签 如今的人们似乎习惯了生活在别人的世界里,而且面对不同的人,扮演着各种各样不同的角色;也习惯了东张西望,习惯了用相对于自己的双重标准,对别人指指点点,说三道四,当面说完背地里还会再议论,什么事情都要搞得乌烟瘴气;却不曾想过自己真正想要什么?是不是一直坚持着自己的梦想?有没有偷偷降低对自己的要求? 说得太多,做得太少。这似乎是当代社会的一个缩影 我们恰恰需要的是多观察、少评价,然后看准了,再踏踏实实、认认真真做事情。 有的时候周遭的朋友碰到我,会善意的跟我说,为什么不找个稳定一些的大公司,或者为什么不找个机会去创业,也会有更不客气的(也许仍然是善意的),说你现在的工作怎么会有创业的感觉,你现在的公司怎么会成功…… 子非鱼,安知鱼之乐 反正我很难去以他们这样的方式去对别人做评价 我喜欢现在的工作,喜欢现在的团队。我在这样的团队中有一种强烈的创业的感觉和使命,我现在在做的,就是脚踏实地,在这家公司完成自己的创业梦想。我也很清楚自己在做什么,付出总会有回报,出来混,也是迟早要还的,这比别人为我和我的东家贴什么标签都要重要。 有志同道合的,欢迎加入,共创辉煌;道不同,彼此祝福一下,不伤和气

2011/5/23
articleCard.readMore

如何制作简易的HTML5幻灯片

本文摘自 勾三股四 更早时期的 不老歌 博客。 在此贴上本人在HTML5研究小组5月14日技术分享沙龙的主题分享。和当天分享的内容略有出入,“出入部分”属个人言论,不代表HTML5研究小组立场 http://jinjiang.github.com/html5-slides-20110512 https://github.com/Jinjiang/html5-slides-20110512 如何制作简易的HTML5幻灯片 赵锦江 自我介绍 赵锦江(勾三股四) Tags: maxthon/f2e/music/football QQ: 110698041 E/M: zhaojinjiang@yahoo.com.cn Blog: http://bulaoge.net/?g3g4 HTML5 幻灯片演示 《HTML5游戏开发实例分享》(罗睿 2011.02) 之html5重构版 原版链接 http://slides.html5rocks.com/ S5: A Simple Standards-Based Slide Show System CSSS: CSS-based SlideShow System 主要内容 HTML5简简简介 我们该如何看待HTML5 前端vs前端工程师 前端的发展和浏览器的发展 How HTML5? Why Slide Shows? HTML5幻灯片的制作过程 功能点摘要 结合代码讲解实现思路 发散思维 一、HTML5简简简介 HTML 5 === Web Application 二、我们该如何看待HTML5 前端 != 前端工程师 前端开发需要更广阔的技术视野 而不仅仅是JS语言专家 前端开发中界面和交互所占的比例越来越小 文件处理、图形处理、网络通信、数据库操作…… 前端开发有了真正的开发的味道 面向对象思想、数据结构与算法、编译原理…… 面临严峻挑战,转变观念非常重要! 前端工程师将无法胜任前端的工作 前端的发展和浏览器的发展 IE6 份额居高不下 HTML5 对开发人员的帮助是直接的,对用户的帮助是间接的 Web App 还没有在国内市场扮演起重要的角色 尊重技术,同时尊重用户 IE6可以死…… 药家鑫必需死! 三、How HTML5? Why Slide Shows? How HTML5? How HTML5? Why Slide Shows? 《让子弹飞》:步子迈大了,容易扯着蛋! 国内现在都在谈概念、秀技术,至今没有真正的王牌产品问世 幻灯片是常见应用,接触的机会非常频繁 网上已经有一些HTML5幻灯片的出现,但我们缺乏对其深入的了解 通过共鸣度较高的简单应用的实战,拉进我们和HTML5之间的距离 大家平时既是观察者,又是使用者,今天大家都会变成开发者! 四、HTML5幻灯片的制作过程 功能点摘要 HTML结构设计 分步实现JS逻辑和CSS表现 特殊元素特殊处理 其它高级处理 结合源代码逐步讲解 注:部分动画和缩放特效暂只针对 Webkit v533.9 内核进行开发 1. 功能点整理 上翻页,下翻页 快捷键:上、下、左、右 快捷键:PAGE UP、PAGE DOWN 鼠标单击事件、鼠标滚轮事件 触摸屏幕事件 翻页到最前、最后: 快捷键:HOME、END 翻到指定的页 通过location hash,即#slideX 注意:要考虑hash纠错 通过快捷键,Ctrl + G 2. HTML结构设计 原则:尽量让结构简单、尽量沿用Powerpoint的结构 只要body > div(.slide) 确定id命名规律:#slideX 提供统一的.tmpl-xxx模板类型,这里和Powerpoint中的模板对应 每个页面都可以由标题(h1)、次标题(h2)、正文、段落、图片等固定标签组合而成,和Powerpoint中的元素对应 3. 分步实现JS逻辑和CSS表现 初始化基本样式 通过:target伪类实现当前显示哪张幻灯片 为不同的模板类设置不一样的显示效果 设置三个基本的翻页的js接口:gotoPage/gotoNext/gotoPrev 在页面载入时进行初始化调整 绑定相关的鼠标、键盘事件 4. 特殊元素特殊处理 幻灯片内部动画 通过为动画元素增加一个特殊的class来进行识别 当幻灯片内有input/textarea/select,且用户聚焦在这些表单控件上时,需要屏蔽全局快捷键和鼠标事件 5. 其它高级处理 加入幻灯之间的切换动画,未来可尝试插件化和多样化 完善幻灯片样式模板机制,未来可尝试模板代码独立 模板展示 设计了几个简单的程序钩子(hooks) 兼容 IE 6 通过条件注释 + 程序钩子 (+ flash)实现 用一颗 Web App 的心去看待 IE 6,那么它也是 HTML5 的一部分 ^_^ 6. 结合源代码逐步讲解 HTML代码 (index.html, 右键菜单→查看源代码) 样式代码 (style.css) 兼容IE的样式代码 (style.ie.css) 脚本代码 (script.js) 兼容IE的脚本代码 (script.ie.js) 五、发散思路 更好的标签语义 根据文字内容多少自适应文字大小 快速预览多张幻灯片的全局查看模式 页面悬停左右区域时显示翻页按钮 辅助工具:马克笔等 开发配套的可视化编辑器和页面生成程序 可以将网页格式在博客和幻灯片之间自由转换 完成一个带账户系统的幻灯片分享网站 应对不同的浏览器和操作系统 无障碍操作、演示用的移动端简易控制器 木有了! 欢迎提问

2011/5/22
articleCard.readMore

对《科普几个傲游浏览器的小知识》一文的补充

本文摘自 勾三股四 更早时期的 不老歌 博客。 上周为老东家写了一篇科普文章,也被几位热心的同事和朋友转发转帖了。有些读者批评我说这些问题太不值一提了,还拿出来说事儿。我虚心接受大家的批评。 HTML5研究小组 的 @秀野堂主 一起聊到了傲游3,他之前对傲游3并不了解,听过我介绍傲游3已经在使用webkit内核并拥有web inspector开发工具之后,觉得不错,并给予了我们更多的关注。这些老鸟们眼中的“常识”就是这样通过我们耐心的解答和宣传一点一点被大家知道和熟悉的。 每次开会、宣讲,都是那一份ppt,但还是会天天讲,月月讲,确实有人看腻了,但有些人还不了解,这就是当今前端的现状。这样做就是因为还有很多人需要了解这些知识,我们也希望拉上更多的人跟我们一起参与进来。 以上

2011/5/3
articleCard.readMore

“作为一名前端开发者,傲游浏览器适合我吗?”——科普几个傲游浏览器的小知识

本文摘自 勾三股四 更早时期的 不老歌 博客。 下面的问题是我遇到的周围人问的频度较高的几个问题 问:你们傲游浏览器用的是IE几的内核啊? 答:这个问题分两部分进行解答:第一,如果你使用的是傲游2浏览器的话,这主要取决于你电脑中的IE版本,如果你电脑里的IE是IE6,那么傲游就会使用IE6内核;如果你电脑里的IE是IE7,那么傲游就会使用IE7内核;至于IE8、IE9、IE10...由于这些浏览器都同时拥有标准模式和兼容模式两种效果,处于对网页最大程度兼容的原则,我们默认会使用IE8、IE9、IE10...的兼容模式的效果;如果你希望使用标准模式,在傲游设置中心里有“是否使用标准模式”的选项,对这个选项打勾即可。 第二,如果你使用的是傲游3浏览器的话,我们默认不会使用IE内核,而是使用更为强大快速高效的Webkit内核(也就是和谷歌浏览器、苹果Safari浏览器相同的内核);目前Webkit内核只有一个缺点,那就是国内有些网站无法兼容Webkit内核,导致网页显示不正常。如果默认的Webkit内核对网页的显示不正常,可以尝试点击傲游3地址栏右侧的“切换浏览模式”按钮,这时页面会改用IE内核打开,至于这次打开的IE内核的版本,可参见傲游2下的内核版本判断方式。 傲游会自动选择使用你的电脑里最合适的一款内核! 问:傲游3支持html5吗?为什么同样是Webkit内核,有些网页效果和谷歌浏览器中的不一样? 答:目前的傲游3已经支持了包括语义化标签、CSS3、Web Storage、Web Worker等一部分html5新特性,并且在不断完善之中。至于显示效果和同为Webkit内核的谷歌浏览器有所出入,有两种情况:第一种情况,请确认你的网页处于“急速模式”下,而非“兼容模式”下;第二种情况,Webkit也是有版本区别的,每个Webkit的版本都会对html5新特性提供更多的支持,傲游3和谷歌浏览器都会定期更新Webkit内核的版本,但频率和进度不尽相同,所以会导致一些显示效果上的差异。请关注浏览器们最新发布的版本。 问:作为一名前端开发者,傲游浏览器适合我吗? 答:每个版本的傲游浏览器都有针对前端开发者设计的贴心功能,最新的傲游3也不例外,我们在浏览器内嵌入了和Webkit内核完美结合的前端开发工具:web inspector,其中包括DOM接口查看、CSS样式查看和分解、JS设断点调试、WebStorage存储状况查看和编辑、命令行等贴心功能,方便大家调试网页,同时我们可以通过存储Bookmarklet的方式保留一些常用的调试工具和插件的入口。傲游3还同时提供“隐私模式”,方便大家在相对封闭的环境内完成开发,而不影响日常的上网行为。如果你在开发网页的同时希望享受在线收藏、鼠标手势、超级拖拽、翻墙代理、视频弹出、资源嗅探、网络便签等特色功能,那么傲游3绝对是大家不二的选择!! 问:傲游浏览器越来越慢也越来越卡了,是新版浏览器存在性能问题吗? 答:速度和性能是浏览器和网页之间博弈的永恒话题,也是浏览器技术发展的主要矛盾。傲游浏览器的速度和性能一直在提升,同时大家平时访问到的网站和网页也越做越复杂。大家对于快与慢、卡与流畅的直观感觉,其实都是对于上述两方面的综合反映。 问:我现在使用的是傲游2浏览器,想升级到傲游3,可以直接覆盖安装吗? 答:傲游3浏览器是一款全新的浏览器,并不是简单的傲游2升级版,而且两者可以共存。我们为傲游2和傲游3设计了不同的默认安装路径,也建议大家安装在不同的目录。(这部分安装和升级的过程设计我保留个人看法,这里仅做个解答) 傲游?遨游?傻傻分不清楚…… 答:Maxthon标准的中文名字是傲游(单人旁) 至于另外那个字,我刚来公司的时候特别忌讳别人把字写错,但媒体、同学、朋友写错的频率确实很高,有些同事也不时写错,所以现在看开一些了,当通假字吧。

2011/4/26
articleCard.readMore

#拥抱html5# 技术大会归来

本文摘自 勾三股四 更早时期的 不老歌 博客。 之所以在标题中把“拥抱html5”前后加井号,是因为这次参会第一次用微博和大家一起充分互动了一下,井号已经逐渐当strong标签用了。 html5为webapp而生 在这里想补充几句,winter分享中的几句话点醒了我一直对于html5精神的误解:众所周知html5规范的前身是一个叫做web application的规范。这很明显,html5毫无疑问就是为webapp而生的!之前脑袋秀逗了,觉得html5是要强调语义啥的,其实都不对。觉得html5离我们很远?那正是因为我们没有拿html技术做应用的打算。 游戏是未来html5的主力军 在传统应用的世界里,游戏有多么光鲜亮丽,我们从苹果应用商店的热门应用排名中应该可以管中窥豹,可见一斑。这也不难理解,首先游戏基本都需要高科技含量的软件开发,其次游戏很赚钱。html5绝对也会顺从这一规律。不过现在webgame的底层建筑还很少,没有特别成熟的游戏引擎供开发者驾驭,所以发展速度和时间上还需要观望。另外这次会上认识了一些做游戏的朋友,他们计划在今年下半年发布一款html5的游戏引擎,非常令人期待! 其他话题回顾 针对这次技术大会的非游戏类主题,w3c中国的分享让人们更近距离的了解了w3c,同时开放了 email 联系方式和 #html5中文兴趣小组# 的参与方式;zibin的《不做html5非好汉》以及Jeremy大神的《html5设计原理》都是之前在其它场合分享过的内容,这次有幸现场接受二位的亲临指教,同样受益匪浅;盛大、优酷和腾讯的分别展示了他们各自独特的网页应用及其巧妙设计;还有几个手机平台的相关话题,令人大开眼界,包括一些手机浏览器的技术架构讲解,非常深入,看得出他们非常用心在做自己的产品! 视频:http://www.w3ctech.com/2011/html5/video http://www.everbox.com/f/vBsCh9XnDvmhIOFixRFk4uoJak 对这次技术大会的建议和吐槽 对于这两天会议的组织情况也想说两句…… 想提点建议,裕波、hax、几位好朋友,希望别介意,如有冒犯还望多多包涵 1.两天的会议,时间有点长,实在难以集中精力顾及每一位讲师的分享内容,而且双休日全部被占用了,希望活动可以不这么紧张 2.第一天提问的环节比较混乱,谁抢着话筒谁问,我觉得应该让讲师选择举手的同学回答问题,一来可以很好的控制提问的数量和次序,二来也是对讲师的尊重 3.考虑到会场前场需要较暗的灯光以保证投影效果,讲师的位置最好准备一个聚光灯,讲师不应该有了ppt就变成幕后人员了,不然脸都是黑的,台下的人除了需要看清ppt们更需要看到讲师的神情和动作。要是再考虑的多一些,有个聚光灯,给讲师拍照也拍得清楚嘛大家说是不是?去年的webrebuild北京其实也有同样的问题 4.想替你们的赞助商和承办商说几句话,介绍和感谢的部分还是应该规矩一点,不能想起什么说什么,想不起来的就“实在不好意思”了,哪家赞助你不希望能提一下人家名字啊,绝对应该同等对待,另外亲口说出来和发到微博上致谢,力度完全不一样,还是在台上体面的对大家逐个介绍并致谢比较妥当,大家也不会吝啬自己的掌声 5.不管来了多少人,没来的就认了吧,在来了的人里吐槽没来的,无异于“没来的请举手”这样的举动,也正因为此,来了的人才更显得对w3ctech的支持和守信,更应该盛情款待。有人没来,多少也找找偶然中的必然吧,强制报名的人都来显然已经不是组织者本分的事情了,不要让参会的人有压力。 总之像 #w3ctech# 这样长期致力于web技术推广,以及 #拥抱html5# 这样的有营养的技术大会,国内一直很少,大家的需求有很高,只要有活动,就会有很多人参加。所以只要活动有大腕,话题足够精彩,上面的问题都不算大问题。只是希望活动可以做得更好,做得更久,让更多的人喜欢 #html5#,喜欢 #w3ctech# 以上

2011/4/21
articleCard.readMore

重装系统或MySQL程序之后,如何恢复使用旧文件数据

本文摘自 勾三股四 更早时期的 不老歌 博客。 今天办公室的私人数据库出了点小问题,对mysql进行了重装,但由于疏忽了部分配置信息,导致旧数据没有办法继续使用了,MySQL数据库服务无法启动。险些及时找到了问题所在,虚惊一场。事后觉得这类问题还是很容易遇到的,所以打算写个简单的备忘。 我的mysql是在windows下,用msi安装包直接安装的,我们经常会自定义或配置的信息有这么几个: 1. 程序目录 2. Server Datafiles 目录 3. InnoDB Tablespace 目录 4. 默认编码 5. 管理员密码 6. 各种封闭性选项(多为三选一或二选一) 对于6,估计相同的人安装会选择相同的选项,出错的几率应该不大 对于5,是比较有特征的信息且是一定需要设置的,出错的几率也不大 对于1,这个目标太过明显了,估计大家也不会装错,就算装错应该很快反映的过来吧…… 那就剩下2、3和4了 我个人习惯,会把4设为utf-8(默认值不是这个,貌似是latin1) 另外2和3那两个目录很重要,一定要设置得和你的数据所在的目录相吻合,才可以正常工作。两者的设置时机不尽相同,前者在设置安装程序目录时一并设置,后者在安装完成后的初始化配置向导中进行设置。 这次虚惊一场,就是因为重装MySQL之后Server Datafiles目录忘记设置了,结果默认的安装目录里的数据是空白的,和InnoDB Tablespace目录信息无法匹配。导致1076错误,同时MySQL无法启动。 最后,提供一个最可靠的办法,就是寻找my.ini文件。不同版本的MySQL,它的my.ini文件可能在程序目录中,也可能在个人文件夹中或windows文件夹中,如果找不到,可以在本地硬盘里搜索。上述所有的设置项基本都可以在这个ini文件中找到。可以通过这个文件确认修改结果。

2011/4/12
articleCard.readMore

港澳印象

本文摘自 勾三股四 更早时期的 不老歌 博客。 上个月末和家人一起随团玩了个港澳五日游。 交通规则 香港是一个很有秩序的地方,所有的红绿灯、人行横道、地铁站、酒店、商场、公园、超市……一切都是那么井井有条。几乎所有公共场所的行为都会有明确的规范和约定。交通系统对那些特殊人群也考虑得细致入微。过再小在不起眼的马路有红绿灯和声音信号,地铁宽敞大方(感觉比北京宽敞2~3倍),站台入口处配有盲人地图。总之无时无刻不感到香港严谨的一面。 各有各的特色产业 香港和澳门也有各自的龙头产业,香港是著名的购物天堂,免税港,尤其是奢侈品、金银首饰、钟表、化妆品、电子产品是人们一定不会错过的消费项目——只有烟酒有税且很税高;澳门则是博彩和色情业发达,尤其是博彩业,在澳门遍地都是赌场,而且据导游介绍博彩业想澳门政府缴纳的税款几乎占到了政府税收总数的90%。 在香港,一般员工的全勤奖和工资一样多 香港是管理非常严谨的,在香港人的价值观里,守时、可信是非常重要的!对员工的出勤情况当然也是格外看重的,只要请一次事假(病假除外),一半薪水就没有了:(相比起我们公司,年全勤奖也就几百块,重视程度可见一斑。 “我晚上请你吃饭” 这是你给你的香港朋友打电话时最常听到的一句话。香港人工作压力都很大,不能轻易请事假(理由如上),住的地方普遍也很简陋,基本没有办法“会客”,所以约人晚上出来找个地方吃顿饭是最好的选择。这也是香港人生活的真实写照。 辛苦赚钱自在花 这是当地人比较信奉和认可的一句话。工作不辛苦,消费的时候也体会不到快感;花钱太纠结,消费完了也不痛快,跟白花钱一样,不如花得自在一点。香港人也沿袭了一些西方人的消费观念、文化和价值观,比如会通过一些奢侈品体现自己的身份和地位,男的看手表,女的看首饰。也由此引伸出很多讲究和文化。听导游一介绍,真是大开眼界。 算命学 除了香港这个地方,我还很少听到哪里的人把命运当成一种学问的,而且还和风水之类的东西结合在一块。而香港人则大方的把这些成为“学”。其中最著名的被人津津乐道的无疑就是中国银行大楼和汇丰银行大楼的风水斗法。关于香港的“算命学”,还有很多有趣的故事, 垃圾虫广告让香港变干净 香港也不是一直治理得很好很干净,也曾经买大街都是垃圾乱丢,在上个世纪80年代,有一个公益广告对香港人的自律能力产生了深远的影响,大概是一个大人随手乱丢垃圾被一个小孩看到了,然后小孩指着那个大人说:“看!le4~ se4~ 虫~~”这个被小朋友耻笑的广告让当时每个香港人都深深的反思,从此产生了神奇的作用,大家都再也不乱丢垃圾了。 赌徒心态 在澳门玩的时候,发现通往当地赌场的大巴都是免费的,几分钟一趟。赌场内吃喝住基本都免费或很便宜。虽然像我这样只贪便宜不下注的人大有人在,但更多人会忍不住去赌,只要赌一把,钱就回来了。企业管理也应该从这个角度考虑一下如何激发员工的热情,工作要诱人,奖惩也不能太纠结,大方点效果会更好。 最后上图(多图杀猫) 香港澳门不虚此行,希望以后还有机会再来。

2011/4/7
articleCard.readMore

CSS 3 中的 Transition 用法

本文摘自 勾三股四 更早时期的 不老歌 博客。 W3C 官方文档在此:http://www.w3.org/TR/css3-transitions/ 概述 举例 p { transition-property: width, background-color; transition-duration: 2s; } transition-property 属性 Name: transition-property Value: none | all | [ 〈IDENT〉 ] [ ‘,’ 〈IDENT〉 ]* Initial: all Applies to: all elements, :before and :after pseudo elements Inherited: no Percentages: N/A Media: visual Computed value :Same as specified value. transition-duration 属性 Name: transition-duration Value: 〈time〉 [, 〈time〉]* Initial: 0 Applies to: all elements, :before and :after pseudo elements Inherited: no Percentages: N/A Media: interactive Computed value: Same as specified value. transition-timing-function 属性 Name: transition-timing-function Value: ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier(〈number〉, 〈number〉, 〈number〉, 〈number〉) [, ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier(〈number〉, 〈number〉, 〈number〉, 〈number〉)]* Initial: ease Applies to: all elements, :before and :after pseudo elements Inherited: no Percentages: N/A Media: interactive Computed value: Same as specified value. http://www.cnblogs.com/cloudgamer/archive/2009/01/06/Tween.html (准确的讲不是推荐文章本身,而是里面的那个 transition-timing-function 预览功能) transition-delay 属性 Name: transition-delay Value: 〈time〉 [, 〈time〉]* Initial: 0 Applies to: all elements, :before and :after pseudo elements Inherited: no Percentages: N/A Media: interactive Computed value: Same as specified value. transition 简写属性 Name: transition Value: [〈‘transition-property’〉 || 〈‘transition-duration’〉 || 〈‘transition-timing-function’〉 || 〈‘transition-delay’〉 [, [〈‘transition-property’〉 || 〈‘transition-duration’〉 || 〈‘transition-timing-function’〉 || 〈‘transition-delay’〉]]* Initial: see individual properties Applies to: all elements, :before and :after pseudo elements Inherited: no Percentages: N/A Media: interactive Computed value: Same as specified value. p { transition: color 2s ease 1s, width 0.5s; } 值得注意的几个地方 background-color color background-image only gradients background-position percentage, length border-bottom-color color border-bottom-width length border-color color border-left-color color border-left-width length border-right-color color border-right-width length border-spacing length border-top-color color border-top-width length border-width length bottom length, percentage color color crop rectangle font-size length, percentage font-weight number grid-* various height length, percentage left length, percentage letter-spacing length line-height number, length, percentage margin-bottom length margin-left length margin-right length margin-top length max-height length, percentage max-width length, percentage min-height length, percentage min-width length, percentage opacity number outline-color color outline-offset integer outline-width length padding-bottom length padding-left length padding-right length padding-top length right length, percentage text-indent length, percentage text-shadow shadow top length, percentage vertical-align keywords, length, percentage visibility visibility width length, percentage word-spacing length, percentage z-index integer zoom number

2011/3/23
articleCard.readMore

记解答QQ群里的一个JavaScript问题

本文摘自 勾三股四 更早时期的 不老歌 博客。 问题是这样的: window.onload = function () { var jsonStr = '{"name": "w", "sex": "male"}'; //method 1 var json = (new Function("return " + jsonStr))(); //method 2 function strToJson(str) { return str; }; var json = new strToJson(jsonStr); } 问 method 2 为什么不能达到 method 1 的效果? new Function('...') var funcName = new Function('return 1;'); function funcName() {return 1;} var json = (function () {return {"name": "w", "sex": "male"}})() (function () {...})() function funcName() {return 1;} var a = funcName(); var a = (function funcName() {return 1;})(); var a = (function () {return 1;})(); function funcName() {return {"name": "w", "sex": "male"}} var json = funcName(); (function () {...})()写法的好处在哪里? (function () {var a = ...; ...})() var a = ...; ... 其实在实际运用的时候需要和 eval 同等对待但它和(function () {...})()的道理相同,会比eval更好的解决命名空间被污染的问题(感谢egg的补充)。

2011/3/14
articleCard.readMore

北京交通伤不起啊!北京交通大学也伤不起!

本文摘自 勾三股四 更早时期的 不老歌 博客。 堵车堵得越来越严重了有木有! 地铁里面人越来越拥挤了有木有!! 有木有!!!有木有!!! 所以, 这些交通工具在北京都已经不靠谱了。 所以, 我决定做出些改变。 所以, 我把家搬到公司附近了,近到可以走着去上班。 而且,这个地方离地铁站购近,以备不时之需。 另外,我还把马哥的自行车给搞来了!很拉风吧有木有! 以后会以走路为主,骑自行车为辅,必要的时候再坐地铁。 ---------------- 马哥在此的分割线 ---------------- 说到马哥,这位仁兄应该也在交大待够了吧,我们同年上大学,我去西安上学了,他就在交大;我中间转专业学软件了,他还在交大;我大学毕业来北京了,他还在交大;后来他离开北京深造了1年,回来以后还在交大;我们在交大的老同学张鹏毕业了,他还在交大;我们以前在交大的老同学慧姐找到工作了,他还在交大;我们在交大的老同学刘磊毕业了,他还在交大;我来北京中间搬过4次家了,他还在交大;我们公司搬家了,他还在交大…… 今天,马哥终于要和交大说拜拜了,连自行车也送我了。看来这次是真的要从交大毕业了,以后我就没法去交大找马哥踢球了啊有木有!以后交大就少了马哥这个镇校之宝了有木有!! 交大你伤不起啊!

2011/3/13
articleCard.readMore

CSS 中的颜色属性 color 知多少?

本文摘自 勾三股四 更早时期的 不老歌 博客。 真相在此:http://www.w3.org/TR/2003/CR-css3-color-20030514/#colorunits color 属性值,可以是一个关键字定义,也可以通过数值来定义。且两者都有多种定义的形式。 HTML 4 颜色关键字(HTML 4 color keywords) ■ Black = #000000 ■ Green = #008000 ■ Silver = #C0C0C0 ■ Lime = #00FF00 ■ Gray = #808080 ■ Olive = #808000 □ White = #FFFFFF ■ Yellow = #FFFF00 ■ Maroon = #800000 ■ Navy = #000080 ■ Red = #FF0000 ■ Blue = #0000FF ■ Purple = #800080 ■ Teal = #008080 ■ Fuchsia = #FF00FF ■ Aqua = #00FFFF body {color: black; background: white } h1 { color: maroon } h2 { color: olive } 数值定义 RGB颜色值 这个最常见了,不过一共有两种写法,例如: em { color: #f00 } /* #rgb */ em { color: #ff0000 } /* #rrggbb */ em { color: rgb(255,0,0) } /* integer range 0 - 255 */ em { color: rgb(100%, 0%, 0%) } /* float range 0.0% - 100.0% */ 还有一点需要注意,如果数值超出了正常范围(比如负数或超过255的整数等等),则会取合理范围内的极限值,比如: em { color: rgb(255,0,0) } /* integer range 0 - 255 */ em { color: rgb(300,0,0) } /* clipped to rgb(255,0,0) */ em { color: rgb(255,-10,0) } /* clipped to rgb(255,0,0) */ em { color: rgb(110%, 0%, 0%) } /* clipped to rgb(100%,0%,0%) */ RGBA颜色值 在RGBA的基础上,多了一个alpha通道透明度的定义,例如: em { color: rgb(255,0,0) } /* integer range 0 - 255 */ em { color: rgba(255,0,0,1) /* the same, with explicit opacity of 1 */ 穿插:transparent 关键字 透明,无颜色,相当于rgba(0,0,0,0) HSL颜色值 一个整数 + 两个百分数,分别表示色相、饱和度和亮度。例如: * { color: hsl(0, 100%, 50%) } /* red */ * { color: hsl(120, 100%, 50%) } /* green */ * { color: hsl(120, 100%, 25%) } /* light green */ * { color: hsl(120, 100%, 75%) } /* dark green */ * { color: hsl(120, 50%, 50%) } /* pastel green, and so on */ 这样的写法目前还不太常见,但这样的定义方式给颜色渐变的计算带来了很多方便,或许将来会大有所为哦:) HSLA颜色值 同理,在HSL的基础上加入了alpha透明通道。比如: em { color: hsl(120, 100%, 50%) } /* green */ em { color: hsla(120, 100%, 50%, 1) } /* the same, with explicit opacity of 1 */ SVG颜色关键字 道理和HTML4的颜色关键字类似但范围更广,这里略去。 currentColor颜色关键字 表示与当前元素的color属性值相同,多用在使得边框颜色和文字颜色一直等地方。 系统颜色(CSS System Colors) 这些颜色很神奇,它会根据你的运行环境,代表与之相匹配颜色。比如我们希望某个div的文字颜色、背景色和操作系统tooltip的颜色保持一致,就可以写 .tooltip {color: InfoText; background: InfoBackground} 有的时候我们希望页面样式和我们的操作系统完美融合到一体,不妨试试这些属性值: CSS 2 系统颜色 ■ ActiveBorder ■ ActiveCaption ■ AppWorkspace ■ Background ■ ButtonFace ■ ButtonHighlight ■ ButtonShadow ■ ButtonText ■ CaptionText ■ GrayText ■ Highlight ■ HighlightText ■ InactiveBorder ■ InactiveCaption ■ InactiveCaptionText ■ InfoBackground ■ InfoText ■ Menu ■ MenuText ■ Scrollbar ■ ThreeDDarkShadow ■ ThreeDFace ■ ThreeDHighlight ■ ThreeDLightShadow ■ ThreeDShadow ■ Window ■ WindowFrame ■ WindowText Flavor系统颜色 这个关键字有点生僻了,我自己都没用过也没见过,文档里第一次看到,暂不细说了 最后,尽管color属性值可以有很多种方式,可以显示出各种效果,但别让网页的内容过分依赖于颜色,尽量让网页内容不通过颜色的修饰,也可以正常的表达意思(Ensure that text and graphics are understandable when viewed without color.)。

2011/3/10
articleCard.readMore

关于前后端开发协作的思考

本文摘自 勾三股四 更早时期的 不老歌 博客。 Web标准化交流会有关的东西吧。 D2前端论坛上了,杜欢的演讲主题《可复制的前后端分离开发模式》(在线ppt、ppt下载、现场视频)也提出了类似的观念和实践方式。同样让我受益匪浅。也许今年前后端分离会逐渐成为大家茶余饭后共同关注的话题——这可比html5实际得多。

2011/3/9
articleCard.readMore

帮助大家理解贝赛尔曲线(Bézier curve)

本文摘自 勾三股四 更早时期的 不老歌 博客。 在包括Photoshop钢笔工具在内的很多绘图软件里,大家会发现一般的曲线都可以通过对几个结点的拖拽完成修改,曲线的两端有两个端点,拖拽端点,就会改变曲线的起始点和结束点;两个端点周围又各自延伸出一个控制点,通过一条辅助的直线段和端点连接起来。拖拽两个控制点,曲线的弯曲度就会发生改变。总体感觉有点像织毛衣:一条弯曲的毛线,毛线两端有两根毛线针,呵呵。 这可能是大家对曲线直观的感觉吧,但其实这四个点构成的曲线就是一条三维贝塞尔曲线。 n x = ∑ C(n,i) * t^i * (1-t)^(n-i) * xi 0<i<1 n y = ∑ C(n,i) * t^i * (1-t)^(n-i) * yi 0<i<1 如果到我们这里的三维贝赛尔曲线,那就是: x = C(3,0) * (1-t)^3 * x0 + C(3,1) * t * (1-t)^2 * x1 + C(3,2) * t^2 * (1-t) * x2 + C(3,3) * t^3 * x3 = (1-t)^3 * x0 + 3t(1-t)^2 * x1 + 3t^2(1-t) * x2 + t^3 * x3 = x0 (1-t)^3 + 3 x1 t(1-t)^2 + 3 x2 t^2(1-t) + x3 t^3 y = y0 (1-t)^3 + 3 y1 t(1-t)^2 + 3 y2 t^2(1-t) + y3 t^3 举个例子,如果起始点为(0,0),结束点为(2,0),两个控制点分别为(0,1)和(2,1),那么x0~x3分别为0,0,2,2,y0~y3分别为0,1,1,0。上面的公式就变为: x = 0 + 0 + 6 t^2(1-t) + 2 t^3 y = 0 + 3 t(1-t)^2 + 3 t^2(1-t) + 0 这就是通过4个结点得到的贝塞尔曲线的二元参数方程式。 在实际应用中,我个人接触到的方程示意比较少,基本都是大家通过可视化的作图工具将曲线调整出理想的结果,然后再记录或推断出端点、控制点的坐标的。这里仅供了解理论知识。

2011/2/24
articleCard.readMore

CSS 3 中的 Transform 用法,含Matrix(注意:不是黑客帝国里的变形金刚……囧)

本文摘自 勾三股四 更早时期的 不老歌 博客。 首先,权威说明在此:http://www.w3.org/TR/css3-2d-transforms/ SVG开发实践。这本书写得蛮不错,虽然理论知识略显凌乱,不过作为一本开发实践的书,已经极大满足了我对SVG的求知欲! 傲游3、谷歌、沙发里(不是春天里)、火狐、歌剧浏览器中使用,并且属性名要加-webkit-、-moz-、-o-的前缀。 transform-origin Name: transform-origin Value: [ [ <percentage> | <length> | left | center | right ] [ <percentage> | <length> | top | center | bottom ]? ] | [ [ left | center | right ] || [ top | center | bottom ] ] Initial: 50% 50% Applies to: block-level and inline-level elements Inherited: no Percentages: refer to the size of the element's box Media: visual Computed value: For <length> the absolute value, otherwise a percentage transform Name: transform Value: none | <transform-function> [ <transform-function> ]* Initial: none Applies to: block-level and inline-level elements Inherited: no Percentages: refer to the size of the element's box Media: visual Computed value: Same as specified value. translate:平移,有translate(value[, value])、translateX(value)、translateY(value)三种写法,表示要平移的横纵坐标,很好理解。 scale:缩放的比例,有scale(number[, number])、scaleX(number)、scaleY(number)三种写法,里面的number参数分别表示了横向、纵向需要缩放的比例,比如1为不缩放,2为放大1倍,0.5为缩小一倍等等 skew:倾斜的比例,同样有skew(angle[, angle])、skewX(angle)、skewY(angle)三种写法。里面的angle参数分别表示横坐标、纵坐标要分别顺时针旋转多少度,比如skewX(45deg)表示横坐标顺时针旋转45度,可以把一个矩形变成一个锐角45度的平行四边形。 rotate:旋转,只有一种写法rotate(angle),表示顺时针旋转的角度。 这四个东东可以写在同一个css属性里,比如: <div style="transform:translate(-10px,-20px) scale(2) rotate(45deg) translate(5px,10px)"/> <div style="transform:translate(-10px,-20px)"> <div style="transform:scale(2)"> <div style="transform:rotate(45deg)"> <div style="transform:translate(5px,10px)"> </div> </div> </div> </div> transform: matrix transform: matrix(a, b, c, d, e, f); |a c e| |b d f| |0 0 1| |x| |a c e| |x'| |y| = |b d f| . |y'| |1| |0 0 1| |1 | http://www.css88.com/tool/css3Preview/Transform.html,你可以在左侧的“变形参数”区域自由的调整matrix中a~f的值,右侧的div自然会随之变换,随便改一改那些属性值看看预览效果,matrix就非常好理解了!

2011/2/23
articleCard.readMore

CSS 3 中的圆角边框 border-radius 知多少?

本文摘自 勾三股四 更早时期的 不老歌 博客。 摘自W3C的官方文档: Name: border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-top-left-radius</strong> Value: [ <length> | <percentage> ] [ <length> | <percentage> ]? Initial: 0 Applies to: all elements (but see prose) Inherited: no Percentages: Refer to corresponding dimension of the border box. Media: visual Computed value: two absolute <length> or percentages <strong>Name: border-radius</strong> Value: [ <length> | <percentage> ]{1,4} [ / [ <length> | <percentage> ]{1,4} ]? Initial: 0 Applies to: all elements, except table element when ‘border-collapse’ is ‘collapse’ Inherited: no Percentages: Refer to corresponding dimension of the border box. Media: visual Computed value: see individual properties</pre> 解读: border-*-radius 值的最规矩的写法应该是两个参数,参数类型为长度值或百分数。这两个参数分别代表这个角落的边框圆角的横向半径和纵向半径,当横纵向半径相同时,第二个参数可以省略。 border-top-right-radius: 20px 10px; border-bottom-left-radius: 50% 20%; border-bottom-right-radius: 30px; border-radius 值的最规矩的写法应该是4组参数,每一组参数都是中间有一个斜杠的两个参数。如果某一组参数中的两个参数值相同,则第二个参数和斜杠均可以省略。如果第二组参数和第四组参数相同,则第四组参数可以省略。如果第一组参数和第三组参数相同同时第二组参数和第四组参数相同,则第三组和第四组参数均可以省略。如果四组参数均相同,则第二组、第三组和第四组参数均可以省略。 border-radius: 20px / 10px; /* 椭圆 */ border-radius: 20px 10px; /* 一个“扭曲”的圆角,不解释 */ border-radius: 30px; /* 最常见的用法 */ 注意前两个意义完全不同哦 图片来源:http://www.w3.org/TR/css3-background/transition-region.png DEMO

2011/2/16
articleCard.readMore

成人世界

本文摘自 勾三股四 更早时期的 不老歌 博客。 以前每次过年过生日,都会给自己许各种各样的愿望,盼着自己长大,让自己变得更好,同时憧憬新的一年会更美好。 过马路等红灯是件再小不过的事情,但在北京的大街上,你会发现很多人做不到,梁文道曾说“中国人普遍觉得被监督的规则才叫规则”,我有一天夜里12点走在高梁桥斜街,看到老外在空无一车的人行横道旁等行人的绿灯亮起,才过马路,在对其肃然起敬的同时我立刻就想起了这句话。 Fusion乐团的《成人世界》,回味了一下自己四年的工作,看看周遭朋友接踵而至的结婚请柬,想想自己的未来,想想我身边的人,再看看现在的自己。五味俱全…… --> 《成人世界》 词:陶步升 曲:陶步升 怀念着过往的感动 就像轻柔的风 静静的吹走 成长带来的承重 有时候也会冲动 试后才知道痛 才会往前走 一步一步的挪动 经历些许感动 克制了的冲动 琢磨不定的我学会包容 总是不停遇见 熟悉陌生的脸 在忙碌之中 寻找幸福原点 有时疲倦 还略带埋怨 试着让自己学会适应一些 可太多人改变 带着面具的脸 倔强微笑着不愿多说一些 到头来才发现 这是成人世界 恍然发现自己已踏入好几年 我也渐渐改变 戴着面具的脸 倔强微笑着不愿多说一些 到头来才发现 这是成人世界 恍然发现自己已踏入好几年

2011/2/8
articleCard.readMore

浅浅浅谈2011春节联欢晚会的用户体验

本文摘自 勾三股四 更早时期的 不老歌 博客。 即公司的五星级厕所开张和60周年国庆阅兵+晚会之后,春晚我也是不会放过的。春晚我来啦!! 小品依旧变本加厉的拿诚信开涮,只是它越开涮我越不安。 6. 志玲姐姐亮了。亮点有三:第一,董卿两次想把她招呼过来未果,林志玲急中生智还让人家表演人员站在中间演示那个空坐着弹琵琶的道具,全国人民都看出来了,当然董卿第三次伸出她的魔爪时林志玲还是没能招架住;第二,她紧张颤抖着的声音反复说了两遍:“这是一个空~~~箱子”;第三,连续换了三套服装,追平了当年小品演员李文启在《有事儿您说话》中创下的记录(主持人除外)。 7. 本山大叔的小品如期而至,导致我们楼上楼下街坊邻居开始集体放鞭炮捣乱以示抗议,本山大叔也很拾取,前后省略了一百来字就表演完了。 8. 汪峰没上春晚,旭日阳刚却借着他的歌火了,上春晚了,汪童鞋泪流满面。 9. 周杰伦用歌曲《兰亭序》提醒他的粉丝,自己已经一年多没出唱片了。 10. 一直接受不了那种歌曲串烧的形式,每一首歌都只唱一段,还意犹未尽的时候就被阉割了,直接换下一首。听音乐本来是个挺轻松愉悦的事儿,结果听这些串烧搞得自己很紧张很纠结。 11. 前年春晚12点以后觉得春晚已经奄奄一息了,就准备关电视,差点儿就错过了纵贯线;吃一堑长一智,今年果然等到宝贝了,李健和萧敬腾、方大同站在一起范儿有点不太正,不过节目还是“可圈可点”,两位港台的喷药估计也想着出来跟大陆的70、80后等中老年潜在消费者混个脸熟,没想到12点以后叔叔阿姨们都洗洗睡了,萧大侠还杯具的感冒唱破音鸟,你注定成不了刘谦小沈阳啊。 12. 我又发现一个规律,哪一家互联网公司去年被“整”了,到了年底必定会砸钱做广告上春晚,这个就不细说了,你们懂的:)

2011/2/5
articleCard.readMore

发现一则春晚小品的惊“天”规律

本文摘自 勾三股四 更早时期的 不老歌 博客。 今天看完春晚的第一个小品《美好时代》之后,突然发现一个规律,就在某SNS留言到: 我仔细观察过了,连续好几年,春晚的小品都包含了一个字,我想这应该可以反向论证某种社会主旋律的存在。这个字是:骗 此话一出,不料也引起了一些共鸣,我怕我自己把话给说重了,忐忑的(啊啊啊啊啊哦~啊啊啊啊啊啊伊呦伊~)看完了剩下的小品,竟然发现: ——骗别人离婚,然后借此机会谋取新房子的题材 ——刘威假装“房子是我自己买的” ——打电话撒谎说跟谁在一起,然后电话打来就说上厕所了,“你心里阳光一点儿好不好” ——收到了假钱,想靠骗脱手的题材 ——自己孩子卖土豆,母亲花钱雇人去买,假装生意很好 ——捐3千点错小数点了,假装大公无私,编出一堆可笑的理由 ——保安假装是个公安,“我骄傲” ——去北京看奥运会,把门票弄丢了,假装去看了 ——老爸想冒充北京奥运会志愿者,偷穿儿子的志愿者衣服,还在其他人面前假装正派,不借别人穿 ——“这个可以有”,“这个真没有” 诚信何在?!天理难容啊!! “天”呐,我都不敢再往下翻了,2008、2007、2006…… 3点了,还是赶紧打起精神看巴萨吧 BANGCF21F14862336DB232A690DAXIANGUO

2011/2/3
articleCard.readMore

呼唤简洁!

本文摘自 勾三股四 更早时期的 不老歌 博客。 过年回家没带自己的笔记本回去,用了家里的XP台式机上网。 今天再次启动TM2007的时候,忽然发现,今天的软件对于好多功能和体验的追求,其实以前早就做到了,现在技术发达了,选择更多了,却迷失了,不知道何去何从了。 我回头非常愿意多夸一夸,深入的夸一夸,狠狠的夸一夸,TM2007有多TM的好!! 大概有这么些: 首先,常规菜单 第二,联系人“instance”搜索 第三,联系人头像 第四,只显示在线联系人的时候 第五,最常联系的梯度列表 第六,移动硬盘 第七,我的小秘书 第八,商务风格的、简洁的、在此基础上还可定制的界面 今天时候不早了,先欠着,回头图文并茂写给大家 现在大家做软件,总想与众不同,我不晓得是不是在为了创新而创新,但就是把一些很简洁的东西搞复杂了,搞晕了,搞退步了。 拿Web里的表单控件来说,系统原生的、现成的input/select/button/textarea/form放着不用,就因为“自定义空间有限”,硬要拿各种div模拟——尤其是select,最后搞了一堆恶心的float,勉强模拟出样式了,跟其它文字对齐了;点出列表来还要弹出个层,再模拟点击效果;然后接下来是没有tab聚焦功能,再绑定键盘事件;然后更恶心的来了,聚焦以后回车、上下键也要模拟,你又写了一堆js;好不容易都弄完了,扔给测试测一下,除了可能出现的各种诡异的bug之外,他们/她们会不断挑战你的想象力:文字为空的时候行高不够了,文字过长折行或看不见了或者样式乱了,选择框在页面最下面的时候列表层应该从上面弹出来——你一开始写的时候可能都没法考虑得这么周到,从此你陷入了无尽的维护任务中……别急,这还没完,有些事情会更让你绝望:弹出的列表需要是“模态”的(你可能会疯掉,MD神马是模态?!)、网页特别小或者列表特别长的时候列表本身需要弹出网页边框之外等等等等…… 等你回过头来看自己做得这档子事儿,忙活了半天,要死要活,结果做出来的东西还是不完美,还让产品和设计嫌弃和鄙视,毫无质感,自己也憋屈,可其实就为了解决了一个所谓的“自定义空间”问题。 更可笑的是,这里面95%的设计和开发,select自己已经做到完美了。 这时候你把自己开发出来的“控件”换成select标签,通过简单的jsapi或option标签加上数据,体验一下,什么感觉? 除了TM2007和select这两个例子,我建议大家有条件的,把暴风影音1、pplive1、mini迅雷1、搜狗拼音输入法1.0都找出来用用,体验一下,什么感觉? 杯具的是,我突然发现Maxthon 2.x和MyIE相比,也是如此…… 今天老爸还跟我说,你们这个傲游浏览器有些地方还不如IE方便呢,我一开始听了不敢相信,后来他在电脑面前一五一十指给我看,说了说他的理解,句句在理!我顿时石化了,觉得自己一个从业者,在大众网民面前,一无是处,像个白痴一样…… 最后再多说两句。WEB本身是一个很简洁的世界,它的简洁美只有前端开发自己最了解。即使没有HTML5的帮忙,WEB界面几乎可以快速实现你想要的任何功能需求——其优雅之处正在于此;我们可以在看此简单到不能再简单的iOS系统里做各种各样的事情,可以在mac os里通过数得着的几款软件完成所有的日常办公、休闲、娱乐、创作,也是如此优雅自如,这一点,相信苹果产品的用户也感同身受。 今天的软件和网站,搞了这么多的花样,做了这么多功能和特效,换了一套又一套界面库,风格改了一版又一版,有几个越做越简洁,越做越实用的呢? 呼唤简洁!

2011/2/1
articleCard.readMore

我们该如何在工作时,将HTML 5/CSS 3这些新特性引入我们的Web产品之中呢?

本文摘自 勾三股四 更早时期的 不老歌 博客。 前几个月基本处于闭门造车的阶段,傲游3的RSS阅读器功能终于有个雏形了,目前正在通过傲游浏览器3测试版本非常谨慎的对外发布。时间紧,任务重,忙得一塌糊涂,快要忘记自己是谁了,跟周围的同行也基本没怎么交流。 “如果PM出需求 美工设计 前端切图 后端接代码 请问谁会设计这种动画?” 此话一出,我立刻追问winter自己对这个问题是否有自己的答案,于是大讨论就这样开始了…… 这不是HTML5 真是一语中的。 职位需求,欢迎各路前端/非前端(前端和传统开发的界线真的不大了,有程序员基础的童鞋都可以在这里找到归宿,同时为我们的团队创造价值)英豪的加盟! 崔凯很用心的做了这份招聘广告,我觉得把我们组说得特别牛逼。我愿意很不要脸的,一厢情愿的,在崔凯的介绍之后再补充一句: “这公司有我……” :)

2011/1/28
articleCard.readMore

前端开发越来越需要对数据敏感的工程师了

本文摘自 勾三股四 更早时期的 不老歌 博客。 这是我从杭州D2前端论坛归来,结合最近工作内容后的感受。 记得我刚加入傲游的时候,我的职位名称还不带“前端”这么冠冕堂皇的字眼——也就是个“网页开发”人员。那时每天最主要的工作,就是和页面布局、表单、事件打交道,随着ajax技术被越来越多的使用,我的工作范围扩大到了xml解析和较为复杂的字符串分割和拼接,但工作核心还是界面。那会儿的工作,翻来覆去好像也就是这些,如果xml或正则不太熟,简单问问边上后端或客户端的程序员就搞定了,核心竞争力就是dom/css还有firebug。 随着Web技术的丰富,“前端”这个概念的到来,以及人们对“前端”技术的高期望值,这种状况逐渐在转变…… 我们在工作中接触的“传统模式”的网页越来越少,相比之下,网站的样子做得和应用程序越来越接近,现成的表单、css样式、html元素已经无法满足产品要求了,同时数据处理和逻辑控制变得越来越复杂,过去以界面-事件为驱动的开发模式逐渐变得不适用了,而以数据-模块为驱动的模式被更多的认同和运用。 再看看最近几次Web标准化交流会、WebRebuild大会、D2前端论坛、Google Fast Dev大会等等前端技术活动,探讨的很多内容都是非常抽象化、结构化的,不然就是算法优化和性能优化。对于每天只接触div+css重构网页,再利用jQuery绑几个提交按钮、表单验证、弹出几个用div模拟的对话框的前端工程师来说,可能体会不到那些新思想、新技术带来的欣喜,无法汲取真正的营养。但对前端抱有极大期待、拥有极大野心的童鞋们来说,无异于久旱逢甘露的感觉。而如何处理好前端世界里的各种数据结构与算法,就在其中扮演了非常关键的角色。所以未来前端的驱动力必然会在数据这个层面。 说“驱动”这个词可能比较晦涩不易理解,如果再解释一下,那就是我们在打算完成这项工作之前,最先要考虑的事情是什么,然后顺着这个方面起步,完成所有的工作。 如果想做好更为复杂的网站或Web应用,上来就先写html/css代码然后在静态效果上绑定各种事件进而完成js的开发模式会让你很容易就陷入了数据和逻辑的苦海——这其实就是界面、事件驱动的做法。而数据、模块为驱动的模式则是在一开始抽象出整个网站或应用的数据结构和数据逻辑,然后把界面以模块为单位进行拆分(粒度并非在事件这么小的级别),这样程序基础非常牢固,可以应付负责的业务逻辑和数据结构,也可以应付各种后期维护和调整。在这个时候,再去完善界面,绑好事件,就变得顺理成章,这部分的开发也不会因此变得复杂或费力。 想做好这件事,就需要深厚的程序员功底了。所以前端开发,在朝界面-事件驱动转型为数据-模块驱动的过程中,需要更多对数据敏感的工程师的加入。 前端开发,任重道远

2011/1/16
articleCard.readMore

科普:如何运用OPML格式向阅读器导入RSS列表

本文摘自 勾三股四 更早时期的 不老歌 博客。 最近做RSS相关内容的原因,特意去opml.org学习了一下大名鼎鼎的OPML(Outline Processor Markup Language)文件格式。 <?xml version="1.0" encoding="UTF-8"?> <opml> <head> <title>xxx</title> ... </head> <body> <outline ... /> ... </body> </opml> head结点中的内容和具体的数据关系不大,大概有下面这么几个(最显眼的莫过于title了) <head> <title>xxx</title> <dateCreated>xxx</dateCreated> <dateModified>xxx</dateModified> <ownerName>xxx</ownerName> <ownerEmail>xxx</ownerEmail> <ownerId>xxx</ownerId> <docs>xxx</docs> <expansionState>xxx</expansionState> <vertScrollState>xxx</vertScrollState> <windowTop>xxx</windowTop> <windowBottom>xxx</windowBottom> <windowLeft>xxx</windowLeft> <windowRight>xxx</windowRight> </head> body结点中全部都是outline结点,表示了RSS列表的内容和目录结构,比如: <body> <outline text="Folder 1" ...> <outline type="rss" text="Title 1" xmlUrl="http://xxx.rss" ... /> </outline> </body> 这里就是opml最主要的部分了,格式对于html/xml开发者来讲应该相当熟悉了。值得一提的是,几个关键的属性text、title、xmlUrl、htmlUrl、type、description中,title/description/htmlUrl都是可选的,如果text/title同时存在,会以text为准,而不是更常见的title,另外xmlUrl和htmlUrl分别表示这个outline的RSS地址和网站地址,而不是所有的RSS都有htmlUrl的。另外RSS的type必须是"rss"。 opml.org。

2011/1/10
articleCard.readMore

哇!互联网改变生活

本文摘自 勾三股四 更早时期的 不老歌 博客。 这次回家最大的感触就是,电视闭路线越来越鸡肋了。 更深刻的感触是,家里的电视用掉的带宽远比用掉的闭路信号多得多。 以前我每次回家都有个习惯,就是把自己的移动硬盘带回家,里面拷满了各种各样的像电视剧、电影之类的视频文件,带回家以后用家里的液晶电视体验一把“家庭影院”的感觉。一家人围在一起看大片,其乐融融。 这次回家比较匆忙,硬盘里也没带太多东西,原本觉得挺负罪的,不过回家之后发现老爸老妈已经习惯了使用在线视频网站观看“高清”甚至“超清”的视频了。我体验了一下,除了大概每20分钟卡一小下,非常清晰!非常流畅!甚至比我用硬盘带回来的视频还清楚还方便……突然觉得自己好土=。= 我又想起我的一个高中同学,他现在还在学校念书,用的是校园网,平时访问校外网络是要按流量计费的,所以他们平时就习惯去校内的bt网下东西,而校内的bt种子永远只有最新的东东才找得到,所以必须下载下来,找个硬盘保存好,以备不时之需,上个月,他刚刚又新买了一个500G的移动硬盘,至今仍然沉浸在幸福和喜悦之中。 看到家里的超/高清网络电视,再想想这位还没“解放”的老同学。不禁感慨,互联网的发展真快,对生活的改变真大。也许很快,移动硬盘这种东西就会逐渐被各种在线服务和移动终端所取代吧。 再看看网购的那些事儿,我记得第一次见到活人网购,是我大四毕业之前,通过舍友帮我充手机费的机会发现的;直到我第一次网购;又直到我在网上给家里订购了一台液晶电视,老爸老妈第一次坐在家里,等着工作人员送货上门,然后非常体贴的把电视装好,然后礼貌的告辞;直到家人自己学会了网购,直到刚上大一的老弟告诉我,他们宿舍所有人都所有日用品、服装鞋帽的消费几乎都是通过互联网的时候…… 哇! 互联网诶!! 我的工作诶!!!

2011/1/2
articleCard.readMore

我和jQuery作者John的合影

本文摘自 勾三股四 更早时期的 不老歌 博客。 如图: 有幸参加了John的北京见面会,故合影留念。 照片中John右侧的两位分别是我过去的战友和未来的战友。

2010/11/24
articleCard.readMore

何以解忧,唯有架构

本文摘自 勾三股四 更早时期的 不老歌 博客。 最近比较忙,也有机会参与了很多前端项目的架构工作。 性感的活,感叹优秀的架构师是如何在各种复杂的项目中都能够考虑到方方面面的。 优秀的技术和优秀的设计往往是不谋而合的。一个技术架构的过程,其实就是将技术和艺术巧妙结合的过程。做架构不光是个性感的活,还是一门艺术。 其实架构还是“体力活” - - 虽然被认为是“体力活”,但因为有性感和艺术的相伴,所以还是乐在其中:) funny everyday with front-end architecture

2010/11/21
articleCard.readMore

兼容ie6,是一个创造价值的过程

本文摘自 勾三股四 更早时期的 不老歌 博客。 本月末的Web标准化交流会将要讨论的两个话题,其实都是在说“前端开发帮浏览器开发擦屁股”的问题:第一个话题是是否应该擦,第二个问题是该怎么擦,擦的怎么样。 首先,兼容ie6,是在创造价值,而不是浪费时间 其次,“IE6 must die”是喊给谁听的? 我觉得这句口号最开始应该是Firefox/Opera/Safari/Chrome(或他们的五毛党,在黑社会里真正出手的一般都不是老大)喊给前端开发者的口号。目的是忽悠大家灭掉微软,而不是为了用户。 那么我们究竟在喊给谁听呢? 喊给前端开发同行吗?让他们停止为70%的老百姓服务? 喊给用户吗?难不成也让用户“二选一”?更何况是向用户喊出如此不礼貌的口号 喊给浏览器们吗?这是他们自己喊出来的口号,怎么又喊回来了? 所以,这句口号自己意会就好了,在不认为这句口号自私之前,我是永远不会跟任何人喊这个口号的。 最后,我不代表前端开发界,我只代表我自己。所以我很感兴趣大家会在这个月底的交流会上说些什么 我们交流会见

2010/11/15
articleCard.readMore

咱今天也来玩玩用户体验

本文摘自 勾三股四 更早时期的 不老歌 博客。 今天在人人网上看到收起/展开面板的按钮做成了大概下面这个样子 “状态”,说的深入一点,是“引导用户往哪个方向看”的问题。如果目录是收起的,那么只有右边的标题可以看,所以用【→】;如果目录是展开的,则引导用户顺着思路看下面展开的正文或详细内容,所以用【↓】。 可以展开的目录 可以收起的目录 “动作”的表述了,再说的深入一点,是“预告用户点了这个按钮会怎样”的问题。 产品是做不完的,对体验这东西的精益求精应该是永无止境的。 前端开发也是如此 学英语也是如此

2010/11/14
articleCard.readMore

HTML 5 翻来覆去也就这么多东西了

本文摘自 勾三股四 更早时期的 不老歌 博客。 先贴我为上周六Web标准化交流会准备的ppt《HTML 5 实例演示》: http://www.slideshare.net/jinjiang/html-5demo20101030/ http://github.com/Jinjiang/HTML-5-Demo-20101030/ 崔凯先生的博客): 顺便说一句,这是我第一次使用github(恩,有点意思,以后应该多玩玩)。 同时,在会上了解到很多html 5的特性已经在移动端或浏览器扩展这类的环境中发挥优势了——非常令人振奋的消息! 不过我也发现一些“有趣”的现象:很多团队都会通过 qt + webkit 建立一个属于自己的网页环境并打包成app发布,这样有一个明显的弊端:就是再小的app,也需要内嵌qt和webkit的代码,所以大小几乎逃不掉5兆的。 想真正把web技术广泛运用,路还很长…… 我想 html 5 的概念今后应该不用再讲了,接下来我们应该多多探索其中的内涵,完善它们的上层建筑——基于 html 5 代码库和代码框架,多多了解甚至参与相关的开源项目,多多学习“跨界”的技术知识——比如数据库操作、图形学编程、甚至是月影兄在会上提到的高中几何;再做更多的demo进行亲身体会。相信大家会为 html 5 和前端开发“春天”的到来做好更充分的准备!!

2010/11/1
articleCard.readMore

为web sql database默哀!

本文摘自 勾三股四 更早时期的 不老歌 博客。 最近在W3C的网站闲逛,囧然发现《Web SQL Database》草案被无情的归为“Obsolete”一类。这意味着web数据库的一个重要js api就此倒下。 Offline Web Applications》中还介绍大家通过web sql database的相关方法进行离线存储…… 简单的了解。其实我对web sql database被淘汰出局早有预感,因为它的问题和优势同样明显:不同的数据库程序,sql的写法都不尽相同,没有非常统一的规范。在没有统一规范的语法基础上制定上层规范显然是不明智的,因为web开发者面对这样的规范,依然需要为各类数据库底层程序做兼容处理——相信很多php等后端语言的程序员对sql语法及其兼容层的运用应该有非常深刻的感触。 Indexed Database API》,它抛弃了主流但却无法统一的sql语法,与之对应的是一套全新的语法——就像后端程序里的数据库操作接口封装一样。它保证了不同数据库、不同浏览器下的接口一致性。相信会更令web开发者感到满意! 不禁感叹,优胜劣汰的残酷。 1年前还在用webkit玩web sql database,觉得好酷,开发者工具的配合也很好,但转眼之间,随着indexed db的推出和完善,web sql database立刻就被抛弃了。 为web sql database默哀的同时,也为新技术鼓掌!!

2010/10/26
articleCard.readMore

前端开发需要一个纯粹的html5世界,更需要一个传统程序员的眼界

本文摘自 勾三股四 更早时期的 不老歌 博客。 上周参加了Google互联网开发技术交流会。 这个html5幻灯演示基本就已经修炼成功了。 lifesinger的会后点评,真是一针见血! 给前端开发一个纯粹的html5世界,这个世界里全部都是崇尚html5的同道前端工程师,大家在同一个世界里工作,交流,奋斗;这个世界不需要再考虑兼容问题了——反正兼容了也没什么意义,何必劳民伤财;这个世界彻底摆脱了旧事物的束缚,会更加敏捷,完成更有冲击力的新产品,产生更广泛有效的社会影响力! ---------------------------------------- 这样的环境真的会有么? 想要就会有!你懂的。 让兼容两个字成为历史吧!让大E就等于上网的时代成为历史吧!

2010/9/11
articleCard.readMore

!important到底有多important!

本文摘自 勾三股四 更早时期的 不老歌 博客。 <!DOCTYPE /> <html> <head> <style type="text/css"> body { color: blue !important; } </style> </head> <body style="color: red;"> hello world </body> </html> 今天在某技术群里的小发现 看上面的代码,hello world最终的颜色是什么? 答案是蓝色 比内联的 inline css 的优先级还高! 以前一直没注意过这个细节,今天学起来了

2010/8/12
articleCard.readMore

补充几个学习Javascript的想法

本文摘自 勾三股四 更早时期的 不老歌 博客。 接着上周的Web标准化交流会说起 我嘞个去,这货不是我,这货不是我 会场的样子 大家的合影 http://www.douban.com/photos/album/31291526/ 那是我第一次在交流会上为大家做正式的分享,大神仙老早之前就跟我说下期交流会要讨论如何学习Javascript了——我当时觉得兴奋,因为这个问题困惑了我很久,我也想知道答案。正准备把我的这些想法反馈回去,大神仙又发话了:那就你给我们大家做个分享吧。 呃,我一时没回过神来,是让我给大家讲么?其实我本来是想去取经的…… OK,我觉得我参加了这么多次交流会了,学到了很多东西,真的从心里感激大神仙他们组织这种活动,如果我想为交流会做些什么回馈的话,这应该是个好机会了。虽然觉得自己给大家谈论学习js这个话题有点不够资格,不过还是决定:认真准备。就当为大家抛砖引玉吧! 我首先想到的是学英语——其实自然语言和程序语言有很多相通的地方,我回顾了一下自己漫长但并不成功的英语学习过程,发现有这么几点: 另外我对学习一个东西,有啃骨头+吃零食的习惯,我会定期给自己安排一些大块的时间,强迫自己看完一本砖头书、或看完一个网站的所有文档、或完成一个自己构思的前段项目;也会每天看鲜果,每天和大家讨论技术问题,每天从工作中发现各种问题和技巧。这两件事情都对我js水平的提高有很大的帮助,而且日积月累的效果是很明显的。所以也借这个机会分享给大家。 循序渐进,是大神仙看过我的分享之后总结出来的,我也觉得这个很重要。另外他会后跟我耳语,这次分享没有涉及什么具体的案例,大家听着会觉得云里雾里的,没有切身的感受,分享效果自然会打折扣。 还有一点,学习是永恒的话题。我有的时候会觉得,光把javascript学好,其实啥也干不成。只有结合了更多的语言和环境,javascript才会发挥更大的作用,所以好多javascript外延或相关的知识都是我们的学习目标。html5/css3/JsApi这些就不多说了,后台程序思维、前台设计思维,也是我们应该去了解的,最新的前端技术也是我们需要去关注的。 最后,会上遇到了几位老熟人,也有幸认识了子斌等大神,最后还跟360的hr搭上了……(老板饶命啊,我可跟她啥也没说 -.-) p.s.通过交流会招聘来的的新同事下周一就入职了^_^

2010/8/7
articleCard.readMore

终于把蒋定宇分享的modev代码跑通了!

本文摘自 勾三股四 更早时期的 不老歌 博客。 资料来源:http://www.slideshare.net/josephj/webrebuild 这里有真相! 这对于我这种完全不懂linux/apache的人来说,真是花了不少功夫…… 1.首先要在linux上面安装apache/php,我使用了Ubuntu自己提供的安装包和直接安装的方式。 2.然后认真学习了apache的配置文件,了解了Ubuntu里apache的默认配置信息,也看懂了蒋定宇的配置文件。 3.把apache的配置文件也写好了,重启apache,发现网站还是没有跑起来——系统提示apc_fetch()函数执行失败 4.又去php.net查阅了一翻,发现它是属于PECL的一个包,默认是没有被安装的。于是开始想尽一切办法把APC包装好 5.一开始安装了PEAR PECL,然后把网上的最新版本APC包下载下来,可怎么也安装不上,似乎文件包和安装命令不太匹配的样子,这个问题纠结了我很久,直到刚才…… 6.昨天我请教了我们公司后端开发的同事,他说,我按照Ubuntu自己提供的安装包安装,这个是一个关键:即安装目录是被拆开放到不同的文件夹中,这使得安装第三方扩展包的时候无从下手。刚才我又想到了这句话,心想如果APC也通过Ubuntu自己提供的安装包安装,应该就100%没有问题了。于是打开“新立得”软件包管理者,搜索,果然搜到了一个叫做“php5-apc”的模块,安装完成。 7.于是欣喜得去刷新页面——还是没打开 8.其实这离成功非常近了,重启apache,或,重启Ubuntu,东西就都出来了(就是上图的样子) 太TM兴奋啦!!

2010/8/1
articleCard.readMore

好多东西要学啊!加油啦!

本文摘自 勾三股四 更早时期的 不老歌 博客。 这周抽空研习了一下蒋同胞的modev,突然发现,这么好的营养自己却吸收困难…… YUI3的主站也看到了玉伯先生的blog和克军先生的ppt。恩,一块大骨头!坚决啃之…… 然后是linux/apache/git…… 真是悔恨平时开发太依赖windows/iis和图形化管理工具了,git可能还好搞定,毕竟对svn指令有一些接触,rapidsvn也用了那么久,看过下面的console代码跑(囧)。apache/类unix 就得多看书多实践了,我得给我硬盘里的Ubuntu扫扫灰了,另外周末再去跑一趟书店XD 再有就是jslint/jsdoc/phpdoc/cssdoc这些东西了(哎我越写越惭愧,连这个都一直没学起来……),最近周围讨论编码规范和习惯的比较多,得把现成的工具用起来! 最后就是英语啊 英语啊 英语啊 前阵子跟美国的同事讨论议题 好囧 讲到一半没词儿了 还得我们老大给翻译 -.- 总之 要加油了 明天早起参加公司的“粉丝同乐会”去,希望跟我们的老用户深入交流,擦出些火花来~

2010/7/24
articleCard.readMore

实践出真知!——趁热把今天的webrebuild与会心得分享出来

本文摘自 勾三股四 更早时期的 不老歌 博客。 webrebuild.org几个小时前刚刚结束 蒋定宇同学(awoo)的前端团队建设的实践经验(他称为“实做”)非常有学习价值! Fiddler的软件,貌似会场好多人都在用了,很酷,但很惭愧我还没有接触过;另外通过yui compressor + yui combo将css/js文件合并打包,并通过不同的请求参数获取不同格式的代码结果让我眼前一亮!很有趣,Awoo主要是来讲思路的,我却被他提到的工具吸引了 囧 看来我的思想层面有局限性啊 《scalable-javascript-application-architecture》我也有认真研习过,也非常赞同其中的观点——即使我也看过实践过,awoo的“实做”还是给了我惊喜:他把Sandbox那一层在实做中转变为了API,更加符合实际的需求和架构。Great!我们在应用这套架构的时候其实也注意到了这里在实际情况中显得有点牵强,但却没有想到大胆的改变它的观念,而是硬着头皮继续起名为Sandbox……(我心理素质真好 囧) 我们想开发一个OS,也可以照这个思路来做。问题来了,我们针对实际的js架构任务,是否需要一字不落的把这套架构搬来用呢?或许可以根据实际情况,在实做中简化和调整的,像Awoo把Sandbox简化为API就是个很好的例子。同理,如果我们实际的模块通信是很少很明确的,那么broadcast和listen的部分也可以简化。 “0和1的程序世界不应该有妥协”的时候,真是讲得我热血澎湃啊!觉得自己不“机车”一点对不齐这门神圣的职业了! 后来场地的灯光成了主角,我完全看不见自己的笔和便笺了,就没有记录太多东西。但有一个话题印象深刻,就是那个“把骆驼按倒”的邪恶问题 如何向自己的老板或一起协作的团队推行自己认为先进的技术和理念 现行标准——这是我从公司的产品老大那里学来的词。比如我们产品老大总说,IE6就是中国浏览器的“现行标准”,他不一定和学术上的标准(比如W3C的Web标准)完全吻合。学术标准意味着技术可行性和合理性,现行标准则意味着我们的目标和需求——它也是“准”的。 本着技术“以人为本”的原则,我们但凡有再先进的技术,最后也是为人服务的、满足需求的、匹配现行标准的。 当然我们也可以在微观环境下扮演催化剂的角色——我想这才是大家真正想做的事情。这里再引出另外一本书:《leadership(领导学)》。其中有一个章节,系统的阐述了“如何改革”,大家有兴趣的话可以从中有所汲取。 最后,还有一些有趣的瞬间,在下面做个备案。以后每次回忆到这里,都应该会很有画面感吧:) 来自Opera的嘉宾,先是被大会要求亲手送出Firefox的礼品,接着又被要求送Chrome的礼品 嘉宾的Apple设备,有点争奇斗艳的意思 head-body仔又在游说邪恶的“tidy模式故意减速”的政策了 真正的html5原来是不包括JS API的 flash也可以作为压缩图片的一种选择,真是实践出真知! 欲波们即使不在会场如此寒暄,你们的热情、努力和呕心沥血的付出大家也都看在眼里了,向你们致敬!

2010/7/18
articleCard.readMore

世界杯结束了,我也好久没扯淡了

本文摘自 勾三股四 更早时期的 不老歌 博客。 说点跟工作无关的吧。本次世界杯对我来说,有几个特殊的感觉 1.周围是个人都在看世界杯 这是我今年感触特别深刻的,以前总觉得这种比赛,如果不是球迷,不会真感兴趣的,最多装个样子,陪周围朋友看两眼甚至聊个两句就了事的,而且坚决跟球迷划清界限——看来我一直低估世界杯的社会影响力和魅力了,因为我发现周围的人,男女老少,都在为世界杯而疯狂,连以前班上、现在公司里的女生,都在cry for agentina了,而且为能够被男生称为球迷而自豪——甚至我们叫她们“伪球迷”她们都很自豪!以前从不聊足球的哥们儿,见面就是满嘴的哈维、穆勒、克洛泽、弗兰。我突然觉得我以前每个周末深夜都不是自己一个人在战斗啊 2.预言帝、阴谋论频繁出现,足球的魅力正在褪色 前阵子看了个谈话节目,说足球和电视剧的区别是什么,回答就是足球只能看live,如果比赛不“新鲜”了,你很可能是在看到赛果新闻的情况下看球,什么悬念、什么刺激就都没有了,而电视剧随时可以按下暂停或直接看重播,没有什么新闻会来烦你,你可以永远保持那份悬念和刺激。可是,如果未来真的是可以预见的,或者球赛的结果是被操纵的,足球比赛还会有人愿意看么?我不太相信时光机这种东西的,但我逐渐接受操纵比赛这种可能性了 3.国际足球需要差异化竞争 感觉32强的打法越来越相似,越来越功利,比赛也越来越焦灼,回首64场比赛,强弱分明或令人荡气回肠的比赛都屈指可数了——顺便跑个题,在看世界杯官方演唱会的时候CCTV的主持人提到了一个词叫“世界音乐”,意思是把所有地域音乐的元素揉和在一起的音乐风格。我觉得各个国家队的足球风格也有点走向“世界足球风格”的意思,这对绝大多数国家来说可能都是好事,但从国际足球的角度看,其实并不令人推崇。我们希望看到个性鲜明的足球的激情碰撞,而不是PES 20XX的反复重播——仅仅是球衣和国旗不一样罢了。 4.我萨让我很有面子! 喜欢巴塞罗那的足球有一阵子了,前阵子球队不管是赢切尔西,还是输国米,看着都挺不得劲儿的。现在西班牙夺冠了,对中有7名主力来自巴塞罗那,有一种释放和解脱的感觉——球迷的心态总是很夸张的。 5.每天晚上终于可以约到人下场踢球了 耶!

2010/7/15
articleCard.readMore

Web标准化交流会归来:分享一些自己深入理解的“设计模式”

本文摘自 勾三股四 更早时期的 不老歌 博客。 上个月的Web标准化交流会中,看到了有关“Javascript”设计模式的探讨。交流会的探讨虽然结束了,但还是有点意犹未尽的感觉。 遍历迭代器”就是个最简单也是最常用的“模式”,也就是说我们需要对一个集合中的每一个元素进行相同的操作。像: for (var i in obj) { var member = obj[i]; ... } for (var i = 0; i < arr.length; i++) { var item = arr[i]; ... } 都是遍历迭代器模式,如果遇到其它的数据结构(比如dom链式表),就需要不同的方法,但模式是不会变的: 通过一个确定的规则,逐个访问到集合中的元素,最后结束,期间要保证不能重复访问元素。 遍历迭代器”模式的含义。今后遇到类似的技术需求,就可以大胆、机械的直接运用“遍历迭代器”模式了,不用费时费力胡思乱想了——这是模式希望带给我们的。 专门把和“原材料”无关的界面表现独立开来,正是运用了这种“模板方法”模式。 把外部依赖性不强却又复杂的工作拿给“工厂”里,狭义意义里的程序都是不知疲倦的,所以这个工厂比现实中的血汗工厂还要无情而又保证产出质量。是每一个准求利益的人的福音。 分久必合、合久必分”。 文件和文件夹组成的文件系统了,文件可以放入文件夹,但文件夹本身和文件一样都是文件系统的最小单位;对数据内容的创建和包装都看作是生成新内容,从这个角度讲,对数据的创建和修改可以统一。此所谓“分久必合”。 将数据本身和数据操作分离;“职责链”则是把状态本身和状态操作分离。此也所谓“分久必合”。 把各自解决的结论组合起来;或者根据上下文不同,选择不同的处理策略,走不一样的发展道路。此所谓“合久必分”。 编写各种底层js框架的时候,就需要大量这方面的思考。但即使不是底层的实现,我们在设计上层建筑的时候同样要时刻注意,是否可以把某些程序段变成“元”,并补充到类库或框架或全局变量中以供大家重用。 各种log日志就是在做这件事情;而“解释器”则旨在创建一种语法规则,通过这个规则,对相同数据类型的不同数据实体解读出不同含义的内容,各种协议(比如HTTP协议、FTP协议)就是在做这件事情。 ------------------------------------------- 至此,还有若干个设计模式没有提及,不过上述内容基本覆盖了70%我们在程序设计和编写中需要考虑的各种问题了。这些模式是从思路层面为大家提供一种“最佳实践”,让我们更高效准确的完成程序的设计或算法的设计。同时也指导我们创造出更多的“模式”和“最佳实践”。如果诸位通过上述讲解有所领悟,那将是我莫大的荣幸。 以上

2010/6/3
articleCard.readMore

浏览器、词典、输入法,一个也不能少

本文摘自 勾三股四 更早时期的 不老歌 博客。 在个人应用软件的战场,打得最热闹的恐怕就数这三块了。 有竞争,才会有进化,当然也会有优胜劣汰。 比如词典,他们始终懒得翻译那些Webkit里的文字,直到最近有道词典新版的出现; 比如输入法,他们始终懒得做个多平台的输入法——当然了,Web输入法勉强算是做到了…… 那些“懒惰”的软件,最终会被市场无情的淘汰。 所以我们不应该仅仅拿“创新”二字来衡量成败得失。

2010/5/8
articleCard.readMore

快给你的钱包洗洗澡吧~

本文摘自 勾三股四 更早时期的 不老歌 博客。 注意——不是肠子!(一个冷笑话) 最方便、省心的方式,就是和各种衣服一齐放入洗衣机…… 当洗完衣服之后,你会在洗衣机里发现一个崭新的钱包~ 钞票会干干净净的躺在里面,银行卡、公交卡、饭卡也会干干净净的!当然了,钢镚儿你得仔细找找…… Wowww~ It's COOL! Wait! That's not over. 接下来,把湿漉漉钱和各种磁卡贴在你们家柜子上。这下你可以放心休息了!明天一大早起来,你会发现地上掉了好多钱和卡——这是一件多么令人兴奋的事情啊!! 相信我。我介绍的方法真的不错。 而且我今天刚刚试过。 尽管我做这些都不是故意的。

2010/5/3
articleCard.readMore

IE 9 来势汹汹

本文摘自 勾三股四 更早时期的 不老歌 博客。 IE 9 Preview 发布有一阵子了,给人两个突出的感觉:软件平台化和对硬件的解放和充分利用。 首先是软件平台化——这当然不只是从 IE 9 的全名上看得出来的(Internet Explorer Platform)。对 W3C 标准的支持已经很彻底了,性能也优化了,“功耗”大大降低了,看得出微软在这方面的决心和信心;除此之外对 SVG 的支持很不错,而且可以通过 js 控制其动态显示效果,也许我们会在不久的将来看到很多基于SVG的酷炫富应用;同时还有很多诸如Web数据库、LocalStorage之类东东的支持。当图形处理和各种数据通信与存储的方式都得以实现之后,出现在我们面前的,俨然一个平台。 另一个方面则是,通过对GPU的充分利用而解放CPU,从而在保证流畅的显示效果的同时,用能力做出更复杂高效的逻辑运算和数据处理。从一些微软提供的浏览器端图像处理的Demo来看,GPU加速的效果是相当明显的!我相信GPU加速是浏览器业界的一个“好开始”,同时也会孕育更多的Web应用甚至Web系统。 相比之下,其他浏览器也在紧张的忙碌着,只是方向和策略有所不同。新一季的浏览器大战愈演愈烈了!(每每想到这里,脑海中就会浮现红警3的激昂旋律) 如果你是一名前端开发者,赶紧学习HTML5、CSS3、DOM3、SVG、Canvas、Web Socket、Web Worker、Web Databaes、Local Storage……等等这些新东西吧。或许它们会给互联网带来更多的惊喜! So does your life.

2010/5/1
articleCard.readMore

裸奔啦~ 裸奔啦~

本文摘自 勾三股四 更早时期的 不老歌 博客。 刚通过朋友提醒,今天是著名的CSS裸奔节~ var list = document.head.getElementsByTagName('link'); for (var i = 0; i < list.length; i++) { if (list[i].rel == 'stylesheet') { document.head.removeChild(list[i]); } } var tempList = document.head.getElementsByTagName('style'); for (var i = 0; i < list.length; i++) { document.head.removeChild(list[i]); }

2010/4/9
articleCard.readMore

孔子说过:一定要把中国的前端开发搞好!

本文摘自 勾三股四 更早时期的 不老歌 博客。 昨天有幸参与了一个前端研发流程及其高效协作的相关讨论。 博客 当今的前端开发,俨然用户需求、视觉设计以及后台程序的中心。却又势单力薄,缺乏三方的“保护”。所以在一个团队中,前端与上述三者之间的“距离”往往会是很致命的。 我们每个前端开发者,作为团队中的个体,能为团队做些什么积极的影响呢?首先要尽量填补自己和用户、设计、程序之间的“无人区”;其次通过有效的沟通和反馈减少误会和分歧,使得整个团队步调一致的向同一方向大踏步前进。 同时,我们也都处在前端开发这个圈子当中,都处在互联网这个行业当中。个人对团队、行业的影响毕竟都是有限的,所以更重要的是建立合理的上层建筑。标准、规范、约定、倡议、接口、文档,都是不错的方式,通过这些方式,可以把团队的各个分工串起来,而且让大家更有目的的、并行的协作。 如果我们是有能力或机会参与建设所谓的“上层建筑”,这再好不过。 让我们一起把前端开发搞得更好! p.s.月影在会上拿两个杯具为例,阐述了一个很简单的沟通哲理,很巧妙很有趣! p.p.s. kejun 的 html 试题有点小变态了…… 个人观点是,这道题目的考点安排很赞!只是题目要求过于开放,在用作招聘用笔试题时,往往无法控制答题人的思路,致使大家想阐述的东西和出题人想考察的内容产生了一定的错位(囧),唯恐答卷的分数体现不出答题人的真实技术水准,或无法将不同层次的应聘者通过考核结果拉开档次——我甚至担心在那些“没有答对”的人中是有很多人明白其中的道理,只是作答时不知从何讲起——我甚至猜测那些“没有答对”的人多少都会有些不服气——或许我的观点保守了。当然这不妨碍我们从这道题目中体会到 html 的博大精深! p.p.p.s 红楼梦里也有记载:前端开发是非常重要的……

2010/3/29
articleCard.readMore

不以己喜,不以物悲

本文摘自 勾三股四 更早时期的 不老歌 博客。 有句话叫:不以物喜,不以己悲。凡事保持一颗平常心,是一种高超的思想境界。 不以己喜,不以物悲。 最近有感而发,想说一说。 首先,不要因为自己的进步或好运而得意忘形,也不要一时兴趣而乱了规矩。时刻保持冷静的头脑,虚心接受别人的指点,坦然面对自己的不足——只有这样我们才会不断超越自我,不断进步,不断拥有更多的欣喜。人生漫漫长路,遇之欣喜,应该按耐自己的兴奋,集中注意继续往前走;一旦被自己的成就所陶醉——这或许就是迷失人生方向的开始了。而且会令人自我陶醉、会让自己夸出声来(比如跟周围的人说“我真是个天才”)的事情,通常就是一些鸡毛蒜皮的小事;真正取得伟大成就的人,通常就是那些“来不及庆祝自己”的人。 其次,不要被外界的纷乱而“悲”。我们并不是世界的中心,我们的各种遭遇,那都是世界的一部分,所以大可不必像祥林嫂那样每天重复着“哎,我真傻……”。和沉溺于自己的成就一样,一味的抱怨不会解决任何问题,我们不能总是计较这些东西,反而应该看得更远,坚持自己的信念。 除了以物而“悲”,我们的身边还有更严重的事情发生,那就是因物败坏情绪,甚至出言不逊、反唇相讥。我们应该以更加宽容的心态去面对一切,不论善恶美丑,做到尊重二字、做到礼貌二字——这不是对圣人的要求,而是如今作为一名社会人的基本素质。不论是对人还是对事、尤其是对路人及其外界环境,总把“不好”二字甚至更加恶毒的词汇挂在嘴边,都是不应该的。之前我们都“不惮以最坏的恶意来推测……”,但时代在发展,人类文明也在进步,我们不能总是活在没有大同信任可言的世界——其实这个世界上没有那么多“不好”,但如果把所有有悖自我的事物都当做“不好”,则会很多很多。过分以自我为中心,而对他人和环境毫无(或者“选择性”的)顾忌和迁就,长此以往,人们就会把自己跟“另外那个没有自己的世界”划清界限,对待熟人和路人采用双重标准,对各种外界刺激的敏感程度就会过分加剧,性格就会变得孤僻和扭曲。 与其这样,我更希望自己生活在一个彼此信任、谦卑、友善、宽容的世界。 以上。 随便写写,没有任何奢求的意思,但如果诸位觉得有道理,望共勉。

2010/2/19
articleCard.readMore

中国足球 真不容易

本文摘自 勾三股四 更早时期的 不老歌 博客。 中国队终于击败了韩国队,而且是3比0的大比分。 对于我这种看了十二三年足球的人来说,真是无比激动! 我始终相信中国足球的未来会美好的,看过我之前blog的童鞋应该明白我想说的是什么:首先足球本身是很有正面力量的运动,任何负面新闻都改变不了这个事实;第二足球是职业化高度发展的运动,中国市场这么大,没有搞不好的理由;第三,也许很多人不关注我们的青少年成长环境,徐根宝的“中国曼联”正在孕育着一批优秀球员,并已经为国家队贡献出了张琳芃这样的国脚,郝海东也投资了自己的足球俱乐部,以百年俱乐部为目标,高瞻远瞩,为我们勾画出了一幅足球的宏伟蓝图;最后也是最重要的一点,这是我们自己的球队,我们没有理由不支持! 另外有人猜疑这次大胜韩国会不会有假球的嫌疑,我的观点是一贯的,那就是我们可以保证中国队的每一位球员都是全力以赴的!香港裁判对中国队有没有偏袒呢?我看了比赛觉得裁判很职业,没有偏袒的迹象。那韩国队的发挥有没有问题呢?这个问题我们就不用替他们发愁了吧……如果有,那也完全是他们的损失和耻辱。不能以此否定中国队的成绩。 哎,讲得有点严肃了 现在中国队终于迈过了一道坎,可喜可贺,可歌可泣。未来还有很多里程碑有待完成。我愿意继续关注足球,继续期待。 有句话讲得好:“足球小世界,社会大球场” 我们每天的工作和生活又何尝不是类似的情形呢?不断面临着挑战,面对着各种外界的诱惑和困惑……我们只有知难而上,坚持不懈,才会继续前进,才会有出头之日! 过年了说点励志的吧 哈哈哈

2010/2/14
articleCard.readMore

Javascript语言特性学习笔记(二)

本文摘自 勾三股四 更早时期的 不老歌 博客。 之前写过一篇Javascript的学习笔记,貌似过去很久了…… Javascript中的非函数式语言特性 1. Javascript中提供的变量作用域共有三种:表达式(直接量)、语句、函数(局部变量)和全局(全局变量) 2. typeof null == 'Object'; 3. 对象只有“构造于某个原型”,并不存在“持有某个原型”,所以构造器(实质为一个函数)的原型是一个实例(对象),而实例(实质为一个对象)的构造器是一个函数(构造器) 4. 函数只有在需要引用到原型时,才具有构造器的特性。而且函数的原型总是一个Object()构造器的实例。不过该实例创建后,constructor属性总先被赋值为当前函数,即:MyObj.prototype.constructor == MyObj 5. 多个原型和多个构造器之间交替相联系,形成了一条原型链 6. 万物皆为prototype链 + props链,空对象{}可理解为其prototype链为Object.prototype,props链为空 7. 一般Javascript的对象(构造器)组成为:内置对象(Number/String/.../Math/Global/Arguments)、引擎扩展对象(ActiveXObject/COM/XML/...)、宿主对象(window/navigator/image/...) 写得有点粗略了,有些东西还是挺晦涩的,没有写出具体的例子…… 暂总结如下,下一次是函数式语言特性的部分

2010/1/20
articleCard.readMore

D2前端技术论坛归来有一阵子了

本文摘自 勾三股四 更早时期的 不老歌 博客。 其实是去年12月19号是事情了。 阿里巴巴园区外 会场的坐席 未来前端关注的,是更自然的交互行为。 模板语言与大前端: 不能看见显示,就对显示编程(显示只可以看做是数据的拷贝) YUI3与前端演变: “自助餐”和“点菜”来比喻不同时期的前端脚本开发,非常形象。克军的语录: 配置 技术 SilverlightQQ:这个主题听睡着了…… 我比较关心的是对于需要Silverlight这样的新技术支持的工程,团队是如何建设和运作的,设计师和开发者又是如何协作的——因为我们的公司也面临着类似的问题。至今仍然没有很清晰的答案,还在摸索中。 前端安全: 过滤输入转移输出、控制Cookie域和GET/POST方法、综合运用Cookie/Referer/Session/Token/IP等等。另外Flash/Flex/Silverlight/HTML5等方面的安全话题这里并未涉及……应该也是有些环节值得关注的。 前端性能: 另外想称赞一下这个小家伙~ 现场鲜活、实时的交流——以前都在馋老外玩这个,现在我们也做到了,很棒!我会后还去注册了一个人间网的账户,在里面又认识了不少童鞋。 西湖景-1 西湖景-2 我在阿里巴巴园区 西湖边-1 西湖边-2 西湖边-3

2010/1/8
articleCard.readMore

JS 对 XML 文件与 XML 字符串的解析

本文摘自 勾三股四 更早时期的 不老歌 博客。 最近在协助某神秘的“有关部门”调试一个神秘的 B/S 系统,发现其中有一段代码是对 XML 数据进行解析的,XML 数据来源可能是字符串,也可能是一个 XML 文件。在原来的系统中,这一部分的实现方式是 IE only 的,即使用 ActiveX 对象,实现 load/loadXML 的方法。所以现在要解决的问题之一,就是使这一段代码可以在IE以外的浏览器环境中正常运行。 function loadXml(xml) { var doc, parser; if (typeof ActiveXObject != 'undefined') { parser = new ActiveXObject ("MSXML2.DOMDocument"); parser.async = false; var flag = parser.loadXML(xml); doc = parser.documentElement; } else { parser = new DOMParser(); doc = parser.parseFromString(xml, "text/xml"); doc = trim(doc); } return doc; } 对于读取 XML 文件,有查到一个document.implementation.createDocument('', '', null)方法,但只能在 Firefox 下使用,放到 Webkit 核心的浏览器中还是不能正常工作。所以这里直接用到了 Ajax 请求的同步处理方式: function load(url) { var doc, loader; if (window.ActiveXObject) { try { loader = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { loader = new ActiveXObject("Microsoft.XMLHTTP"); } } else if (window.XMLHttpRequest) { loader = new XMLHttpRequest(); } if (loader != null) { loader.open('GET', url, false); loader.send(null); doc = loader.responseXML; } return trim(doc); } 这两大段函数体中都出现了一个共同的函数: function trim(doc) { if (doc && doc.childNodes) { var firstChild = doc.childNodes[0]; if (firstChild && firstChild.nodeType == 7) { doc.removeChild(firstChild); } } return doc.firstChild; } 这个函数是为了统一各种不同的 XML 解析结果的根结点(有些解析结果会附带编码说明,或直接取编码说明后的兄弟结点)。 理论阐述如此,而且已经在自己的本子上试过了。明天去有关部门实践一下~

2009/12/29
articleCard.readMore

分享一些最近看到的技术好文

本文摘自 勾三股四 更早时期的 不老歌 博客。 这几天在家休假,不过也没有忘了看书学习。摘一些给大家: http://www.gracecode.com/archives/2987/ http://www.gracecode.com/archives/2997/ http://css-tricks.com/new-poll-formatting-css/ http://css-tricks.com/different-ways-to-format-css/ http://blog.csdn.net/java060515/archive/2007/08/09/1733396.aspx http://www.qianduan.net/ http://www.showeb20.com/?p=2431 http://paranimage.com/9-tabs-based-javascript/ http://readitlaterlist.com/ 这个应用 我个人很有需求 就是临时存一下用的更轻便一些的在线收藏 不过他们做得不够方便 我司做这个应该有先天优势吧 ---------------------------------------- 就这么多了,好久不写东西了,技术先行。 恩恩

2009/11/16
articleCard.readMore

Ubuntu 910 was Amazing!

本文摘自 勾三股四 更早时期的 不老歌 博客。 I download Ubuntu 910 Operating System at the first time. Just as the news said, U910 updates the loading screen, and shows mail icon, im icon, bluetooth icon, network icon and sound volumn icon etc in a smart place. That's very convenient for me. And then, I installed Firebug in Firefox, QQ for Linux & Google Chrome for Linux. All of them are my indispensable applications. I'm writing this post in Google Chrome for Linux. Pity that I havn't install any Chinese input method yet. So just an English post here.

2009/10/31
articleCard.readMore

关于软件流程的三个思考

本文摘自 勾三股四 更早时期的 不老歌 博客。 从工作和人员安排上看(尤其是Web): 1.切图和设计工作统一安排比较合适还是和开发统一安排? 2.产品和需求工作统一安排比较合适还是和设计统一安排? 3.维护和开发工作统一安排比较合适还是和部署统一安排? 在我看来,切图和开发在一起完成更合适、把产品和设计统一考量更合适、开发和维护分开比较合适。 不知道大家的观点如何

2009/10/20
articleCard.readMore

聊天室似乎没有想象中那么难做

本文摘自 勾三股四 更早时期的 不老歌 博客。 最近看到一个聊天室的例子:http://css-tricks.com/jquery-php-chat/ 通信机制 updateChat() sendChat(message, nickname) getStateOfChat() 我们可以在这个模型之上,根据我们的需求进行扩展。比如可以筛选信息,管理历史记录等等。 身份识别机制 数据存储机制 表现机制 其实我们可以把这样的消息机制包装成各种通信服务,除了最简单的聊天室,像IM、短消息、在线广播,都可以做到。同时,除了Web Client,也可以通过exe执行程序等方式的客户端进行操作。 以上是关于此聊天室demo程序的一些思考,突然觉得在软件开发中,原型设计很重要。呵呵 有点后知后觉

2009/10/19
articleCard.readMore

有一种曲折式的发展,叫穷折腾

本文摘自 勾三股四 更早时期的 不老歌 博客。 分分合合,都有讲法,都有政绩。 但它终究是个圈,原地踏步。 总说弯路是一定会走的,发展一定是曲折的…… 在我看来,更像是推卸责任 而且总拿这种理论说事,把人搞得一点信心和耐心都没有了 把自己搞得很忙,觉得自己比以前进步了,都只是对得起自己罢了 要对得起大家,更需要智慧,更需要绝对能力 不然就都是穷折腾

2009/10/18
articleCard.readMore

浅浅浅谈国庆阅兵和谋导晚会编排和转播上的用户体验

本文摘自 勾三股四 更早时期的 不老歌 博客。 非常浅 浅浅的…… 看到国庆阅兵和晚会,不免会想到一年前的奥运会开闭幕式,分享一些我自己注意到的细节 1. 主席阅兵时站的车换大灯泡了,蛮“口爱”的 2. 朱尔镕尔基戴着墨镜,头发全白了…… 3. 广场两边装了大屏幕,这个体验非常好! 4. 直升机航拍的效果太玄了,阅兵的时候,“看到地图上整齐的小坦克,好像用鼠标划住编队啊!Ctrl+1,Ctrl+2...”;到了晚上再看,我自己在北京呆了这么久都没像那天晚上觉得北京的夜景如此绚烂! 5. 导播切了几个以往被认为是“状况外”的镜头:一个是战机从机场起飞的镜头,一个是海军舰艇的镜头,很好很意外 6. 探头摄影机越用越有心得了,知道从近处标兵的简章拉到远景了——貌似这个早该会了 囧 7. 说说解说员吧,首先,我个人已经习惯了罗京先生的解说……在此缅怀一下罗哥 8. 阅兵快结束的时候,也就是小少先队员们想他们10年前的先先锋队一样放飞气球的时候,二位解说员还没来得及“对本场比赛进行总结”,随后出现的一幕是:“出席本次阅兵的有……”一直念到先锋队收队以后才念完,画面很混乱 9. 晚会的中央翻板跟历届的阅兵相比终于没有墨守陈规,但跟奥运会相比嘛……好吧,不能说没新意,算个加强版吧。So does the fireworks. 10. 《大中国》这首歌唱得我很激动,尤其是唱到副歌:“中国~~祝福你~~~” 11. 广场两边装了大屏幕——这个之前有了——我现在想说的是我们终于可以在国内玩“在大屏幕上找自己”的游戏了!唱看NBA、足球转播的朋友们应该对这个游戏不陌生吧,这个很好!以前就算有大屏幕,都在显示比分、字母什么的,包括意大利超级杯的时候……无聊死了 12. 大神们与民同乐! 13. 中秋晚会的舞台太炫了!而且是第一次把主会场搬到北京以外(不如说是国庆直播任务太重,直接承包给了江西卫视……)表演者的阵容也很强大,可惜假唱……有点小遗憾 暂时想到这么多,零零碎碎的,大家有什么新发现可以留言补充

2009/10/5
articleCard.readMore

迭戈·弗兰简单的几句话打动了我

本文摘自 勾三股四 更早时期的 不老歌 博客。 今天在网易体育看了一则专访欧洲金靴弗兰的新闻。摘弗兰回答的几句话送给大家: 我记忆最深刻的进球不是为哪一家俱乐部的,而是为乌拉圭进的。在祖国的旗帜下,我除了竭尽全力战胜对手没有第二选择。我知道,阿根廷很危险,与乌拉圭的比赛很可能决定一切,但是,乌拉圭队也想去世界杯,我们一定会拼到底。如果赢了阿根廷,我只能对阿奎罗说一声,对不起,对马克西道声抱歉。(注:弗兰是乌拉圭人,而阿奎罗和马克西是弗兰在俱乐部的两位阿根廷籍队友) 关于中国足球: 中国足球就应该学习维拉里尔这样的俱乐部,一个5万人的城市经常亮相欧洲赛场,因为那里人人热爱足球。 关于择业: 有些俱乐部有8,给你3,有的俱乐部给你2,因为他只有3,后者更能打动我。

2009/10/2
articleCard.readMore

继续研究 Aero 特效在 WPF 下的扩展

本文摘自 勾三股四 更早时期的 不老歌 博客。 WPF 的语法对于一名 Ajax 选手来讲,是非常有诱惑力的! 之前那个例子是在 Forms Application 下实现的——由于之前不太清楚 WPF 中如何找到 DwmExtendFrameIntoClientArea 的第一个 hWnd 类参数,今天查了下: IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle; HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr); mainWindowSrc.CompositionTarget.BackgroundColor = System.Windows.Media.Color.FromArgb(0, 0, 0, 0); 然后 DwmExtendFrameIntoClientArea(mainWindowSrc.Handle, new MARGINS(-1, -1, -1, -1)); 即可。 对 WPF 的兴趣越来越浓了,打算继续深入学习!

2009/9/28
articleCard.readMore

浅浅浅谈将 Aero 特效应用到整个窗体

本文摘自 勾三股四 更早时期的 不老歌 博客。 传说中的 Aero 这个词,是指 Windows 窗体的一种半透明的显示效果。 Firefox 4.0 的未来设计图,整个窗体都是 Aero 特效的,看过一直流口水。经过一些无聊的探索,我发现了将 Aero 特效从边框扩展到其它区域的实现方法。 internal class DwmApi { [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, MARGINS pMargins); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern bool DwmIsCompositionEnabled(); [StructLayout(LayoutKind.Sequential)] public class MARGINS { public int cxLeftWidth, cxRightWidth, cyTopHeight, cyBottomHeight; public MARGINS(int left, int top, int right, int bottom) { cxLeftWidth = left; cyTopHeight = top; cxRightWidth = right; cyBottomHeight = bottom; } } } 这段代码引入了: 扩展窗体“实体”区域的方法 判断当前操作系统是否开启了 Aero 显示方式的方法 描述“实体”区域的数据格式 private void Form1_Load(object sender, EventArgs e) { if (DwmApi.DwmIsCompositionEnabled()) { DwmApi.DwmExtendFrameIntoClientArea(this.Handle, new DwmApi.MARGINS(-1, -1, -1, -1)); } } private void OnPaint(object sender, PaintEventArgs e) { if (DwmApi.DwmIsCompositionEnabled()) { e.Graphics.FillRectangle(Brushes.Black, this.ClientRectangle); } } protected override void WndProc(ref Message m) { base.WndProc(ref m); const int WM_DWMCOMPOSITIONCHANGED = 0x031E; switch (m.Msg) { case WM_DWMCOMPOSITIONCHANGED: if (DwmApi.DwmIsCompositionEnabled()) { DwmApi.DwmExtendFrameIntoClientArea(this.Handle, new DwmApi.MARGINS(-1, -1, -1, -1)); } break; } } 这三个函数分别做的是: 窗体载入成功后将“实体”窗体区域扩展到“窗体外面”,这样窗体就仿佛全 Aero 了,实际是“实体”区域不可见罢了——当然这不影响我们往非“实体”区域放置控件或别的任何对象 绘制窗体时,将“实体”区域涂黑(这样做的缘由还没完全研究明白,总之这个函数不可或缺,等我弄明白了会把理由补上) 窗体监听到操作系统显示方式改变是,判断是否还支持 Aero 特效,如果是,则“实体”区域需要重新扩展 http://tech.ddvip.com/2008-11/122569523088390.html

2009/9/26
articleCard.readMore

有图有真相之二:意大利超级杯归来

本文摘自 勾三股四 更早时期的 不老歌 博客。 照片憋了很久才放给大家看,是因为一直没空整理。 鸟巢全景 鸟巢全景2 (开赛前) 强插广告:有一点遗憾的是这里不允许上传更高画质的图片,诸位没有眼福看大图了(其实这两句话是广告情境设计),想看大图请通过屏幕下方的联系方式请与我联系,只需支付$5.99。 我在鸟巢外 我在鸟巢里 (背后是拉齐奥球迷阵营) 白天的鸟巢,这张可以做壁纸 夜里的鸟巢,这张可以做壁纸么? 意大利超级杯伪特写 他拍到的才是真特写 最后是一张我们鸟巢伟大的 ML!

2009/8/23
articleCard.readMore

有图有真相:我有帅啊我有车~

本文摘自 勾三股四 更早时期的 不老歌 博客。 上周末跟几个同事去爬了一下中央电视塔。 图1:我在中央电视塔上 图2:我们在中央电视塔上 图3:CCTV-5 奥运赛事直播节目 主持人:刚才那场 110 栏比赛真是太精彩了! 图5:帅…… 图6:要有车…… 图7:的就是一盘象棋…… 一点点小小的行为技术~

2009/7/28
articleCard.readMore

入手新手机一部

本文摘自 勾三股四 更早时期的 不老歌 博客。 Nokia 1202 虽然型号很低,但拥有好多先进智能手机没有的优点: 1. 单色白屏,节能省电,待机时间超长; 2. 短小精干,操作简单,低辐射; 3. 铃声够大,字体够大,可以用到老年…… 囧rz; 4. 价钱便宜。 再搭配上我的小i~ 无敌鸟! 有谁想体验一下由 Nokia 1202 的 mic 传出去的声音的,可以给我打个电话感受一下~ 每体验一次,您将为中国电信捐出一份爱心

2009/7/23
articleCard.readMore

日全食有什么稀罕的,我们身边全食众多

本文摘自 勾三股四 更早时期的 不老歌 博客。 明天上午9点,日全食,长江流域众多地区可以看到。 好多人想去看的样子,蠢蠢欲动。其实没什么稀罕的。 优全食、 推全食、 维全食、 非全食、 饭全食、 科全食1、科全食2 躲都躲不及呢,还凑什么热闹啊

2009/7/21
articleCard.readMore

浅浅浅谈公共厕所的用户体验设计

本文摘自 勾三股四 更早时期的 不老歌 博客。 浅浅的 淡淡的 最近公司所在楼层的厕所进行了改装,传说中已经算是5星级的了(也不知道是怎么算出来5星的)。但真的比以前的厕所强么?我个人可不这么认为,如下: 1. 本公共厕所加装了一个扬声器,循环播放古典或传统民族音乐,这个问题不大; 2. 本公共厕所似乎采用了先进的声控(或红外控或正太控或萝莉控)系统,只要“控不到人”,就关灯关音乐关换气扇关空调,抛开控得准确不准确不说,那个换气扇和空调能不能多开一会儿? 3. 更何况“控”得还不准。有的时候人明明在,灯就关了,过一会儿立刻又好了,有人没人的技术判定很恶心…… 4. 不管是坑还是斗还是池,都改红外控放水了(这些可以确定是红外控的),但是坑和斗都提供手动按钮了,池没有,有的时候天热想在池边洗把脸真是一件痛苦的事情…… 5. 既然坑、斗、池都有红外控了,难道不能跟灯控、音乐控、出入气控结合一下么,不然怎么会在人家大便的时候就把灯给关了…… 6. 地板劳驾不要铺那种太高级的,我的意思是,不要铺太反光的地板砖,不然隔壁的隐私会透过挡板与地板之间的缝隙…… 7. 也是最后一点: 为什么周末还他妈的锁门?!丫算什么5星级厕所到底像不像让人家上啊还不如我们老家的茅坑呢24小时营业是骡子是马都可以随便去还环保……(终于语无伦次崩溃中)

2009/7/20
articleCard.readMore

php 中的 json

本文摘自 勾三股四 更早时期的 不老歌 博客。 用惯了 javascript 中的 json,格式很简单,很灵活。发现 php 中也有这个东西可以用。 通过 json_encode(obj) 和 json_decode(str, [bool=false]) 可以在 php 中实现 json code 与 json object/array 之间的转换。 比如: var_dump(json_decode('{"title": "Blog", "catogery": "php"}')); 会输出下列内容: object(stdClass)#1 (2) { ["title"]=> string(4) "Blog" ["catogery"]=> string(3) "php" } 而第二个参数设为 true 后: var_dump(json_decode('{"title": "Blog", "catogery": "php"}', true)); 会输出下列内容: array(2) { ["title"]=> string(4) "Blog" ["catogery"]=> string(3) "php" } 最后有一点需要注意的是,json code 中的 key 必需要求使用双引号标注起来,而 javascript 中没有这个硬性要求,只要是符合变量名规则的 key 都不需要加双引号。

2009/7/17
articleCard.readMore

关于 html dom table 的相关操作

本文摘自 勾三股四 更早时期的 不老歌 博客。 [HTML DOM Table].cells[] [HTML DOM Table].rows[] [HTML DOM Table].insertRow(index) [HTML DOM Table].deleteRow(index) [HTML DOM TableRow].cells[] [HTML DOM TableRow].rowIndex [HTML DOM TableRow].insertCell(index) [HTML DOM TableRow].deleteCell(index) [HTML DOM TableCell].cellIndex ----------------------------------- 在部分“现代浏览器”中,table 的 innerHTML 是会引起错误的,而且再加上 tbody/thead/tfoot 这种幽灵般的标签的存在,个人认为上述的方法和属性无疑是控制 table 内容的更好选择。 最后是一则本人的最近引起小骚动的签名档: 一个厕所三个坑——W3C

2009/7/14
articleCard.readMore

介绍我自己

本文摘自 勾三股四 更早时期的 不老歌 博客。 勾三股四 http://bulaoge.net/?g3g4 http://slideshare.net/jinjiang/ http://github.com/jinjiang/ 每个Blog都是有过去的……

2009/7/11
articleCard.readMore

穿墙看 YouTube 也是一种 Rock'N Roll 的 Style!

本文摘自 勾三股四 更早时期的 不老歌 博客。 Yeah! 大家好我是卢广仲! Yeah! 我爱看 YouTube ! Yeah! 我现在终于知道怎么穿墙看 YouTube 了! Yeah! 绑 hosts Yeah! C:\WINDOWS\system32\drivers\etc\hosts 203.208.39.104 www.youtube.com 就可以啦! Yeah! 最后记得每天吃早餐! Yeah!

2009/7/3
articleCard.readMore

防止页面被 iframe 内脚本“打散”的两种方法

本文摘自 勾三股四 更早时期的 不老歌 博客。 页面被 iframe 内脚本“打散”的情况越来越常见。比如,在百度的搜索结果页面中有这样的脚本: if (top.location != self.location) { top.location=self.location; } 这时,如果我们把这个页面作为 iframe 放到另一个页面里,则外层页面会被直接跳转至搜索页面。这个代码对页面本身做了保护——即不会被其它页面引用,也给我们在需要引用这些页面时带来了一些困扰。下面介绍两种解决方法: if(document.all){ var isIE = true; var location = ""; var domain = document.domain; } 再对 top.location 赋值就不会将页面“打散”了。 window.onbeforeunload = function(evt) { return '(请用户选择留在此页面的提示)'; }; 这样,在页面跳转之前,会弹出一个确认对话框提示用户要不要继续执行页面跳转(即“打散”)的操作。如果用户选择了“否”,则页面会终止跳转操作, iframe 里的脚本也就不会“打散”整个页面。 这个方法也有弱点,就是它总是弹出确认对话框,有的时候这会让人很受不了。 介绍完毕,两种方法各有优劣,可视状况采用不同的方法。

2009/6/24
articleCard.readMore

其实我不懂 Javascript

本文摘自 勾三股四 更早时期的 不老歌 博客。 最近在读一本有关 javascript 的书。 1. 'abc' 和 new String('abc') 的区别在于前者传递的是值,后者是引用; 2. string 的值可以使用下面的方法书写:var str = 'abcdefg\ 反斜杠代表书写时要折行,字符本身并不属于字符串,它的值会是一个没有换行符的 26 个字符的字符串,但有一点要注意,反斜杠后不能跟注释; 3. == 和 === 的比较规律和原则如下: 运算符两个直接量比较直接量和引用比较两个引用比较 ==比较两者的值是否相同先将引用转化为值,再与另一个值进行比较比较引用是否相同 ===同时比较类型和数值肯定不相同比较引用是否相同 大于、小于等直接比较两者的“序列”大小先将引用转化为值,再与另一个值进行比较无法比较,直接返回 false 4. 正则表达式中的“引用匹配”,如:(\d)\.\1表示两个相同的数字用点连接起来的情况(如"12345.12345"); 5. SpiderMonkey Javascript 引擎(即 ff 等浏览器的 js 引擎)中,表达式中的具名函数不会存在与整个命名空间中,如:((function foo(a,b) {...})(x,y));6. eval 语句中应该是一个完整的语句,而非表达式,此函数共执行三件事:解析语句 - 执行语句 - 返回语句的返回值; 7. 逻辑语句可以简写成下面的样子:while (...);if (...); 8. 标签的写法以及 break 的一个不常用的用法:my_label: { continue 也有类似的用法; 9. new constructor();在没有参数时可以简写成new constructor;这里 constructor() 并不能认为是函数调用,因为不能将其写成下面的样子:new (constructor());10. 对象的隐形属性(如一些 native code)被重写后在有些 js 引擎中会变为显性属性(即出现在 for ... in 循环中); 11. delete 不能删除的内容有: 12. 可以通过 in 语法判断对象是否具有某个属性:if (property in object) {...} 这本书还没看完,应该还有很多可以拿来完善我们的 js 知识体系的内容。这里做一个阶段性的自我整理。同时分享出来。

2009/6/14
articleCard.readMore

夏天来了

本文摘自 勾三股四 更早时期的 不老歌 博客。 这个礼拜应该算是北京夏天真正到来的一刻。 上周下雨,很凉快,之前也没有热得很离谱。最近几天嘛,哎呦~不一样了~ 四季都有分明的特点,有人喜欢夏天,有人喜欢冬天,有人喜欢春秋,有人喜欢战国。。。 我暂时抱着一种抗拒的心理来迎接夏天,因为很热,连晚上都是,整夜整夜睡不好觉,吃饭也吃得不香,蚊子也很多,每天晚上都被咬很多口,第二天浑身痒痒,生不如死。 昨天有人跟我说还蛮喜欢夏天的。 你们真的喜欢夏天么?反正我看不出夏天哪点好。。。

2009/6/14
articleCard.readMore

PHP+MySQL 学习心得

本文摘自 勾三股四 更早时期的 不老歌 博客。 最近在看一本跟 PHP/MySQL 相关的书籍,使我更深入的了解和认识了这些技术。这里会不定期更新一些自己的学习心得。以备将来复习用,同时也分享给大家: 《select multiple name="province[]"》...《/select》 这样选中的项会存到名为 $_GET["province"] 或 $POST["province"] 的数组当中,如果把其 name 值设为 province 而不是 province[],则提交表单后只会解析出最后一个选中的项的值。

2009/5/18
articleCard.readMore

失去信仰是一件很可怕的事情

本文摘自 勾三股四 更早时期的 不老歌 博客。 总而言之,言而总之,我们总是把信仰认为是最可靠、最值得信赖的东西,当有一天,我们发现连自己的信仰都不靠谱的时候,那种失去信仰的感觉,是很可怕的。 如今,人们的信仰多种多样,千姿百态,有宗教信仰,有文化信仰,还有一种信仰我个人将其称作“崇拜信仰”,即把自己崇拜的事物(如老爸、明星、球队或产品等等)奉为信仰。只要是老爸的话,就一定要听;只要是偶像做的事情,就会去模仿和谈论;只要是苹果出品的东西,就会买来用或大加赞赏;只要是中国队的比赛,就一定会赢(这个有点夸张了)。我们有想象过那个自己心中的“偶像”、“苹果”或是“中国队”被人扒得一干二净,最后发现其本质是如此猥琐时的感觉么——那种诺大世界中找不到可以信赖的东西的无助感觉…… 记得曾听我一个朋友评价电影《阿甘正传》轰动一时的原因和意义。其中就提到了当时的美国社会和那一代美国人多处于丧失信仰、自我迷失的阶段,这时需要一种正面力量的刺激,让大家重新找到各自的信仰,也需要“造神”,通过个人英雄主义来带动周围的人。 我觉得“造神”这个词用在这里很准确,这个世界上没有常胜将军,人总是不完美的,但越接近神的人,越能够给周围的人正面的影响。有时觉得那些“造”出来的神很无聊,甚至很鄙视他们,不过我逐渐感受到了大家在“造神”其中的良苦用心和其深远意义。 也希望这些“无限接近神的人”,应该了解和清楚,自己的一言一行,背负着什么样的社会责任。 所以, 尤文图斯的庸者一定经历了一段失去信仰的过程, 黄健翔的忠实听众一定也反问过自己是不是神志不清了, 五月天的迷妹们也肯定在贴吧喷完口水后悄悄地问自己每天在听的音乐到底算不算真摇滚…… 再所以, 科比比姚明更接近神, 湖人队也是一定会晋级的, 巴萨的球迷此时此刻一定是迷失自我的, 我对我身边的大神,也真的很失望……

2009/5/9
articleCard.readMore

“谢谢你的参与”

本文摘自 勾三股四 更早时期的 不老歌 博客。 我们应该把每一位参与者,都看作是与我们并肩奋战的人。即使他笨手笨脚的,没有帮到什么忙,甚至帮了倒忙,我们还是应该肯定他的贡献和态度。 有的时候自己会被一些不好的结果影响了对过程的认识。也许是因为我总是很着急想把事情一步做好。 写篇日志提醒一下自己,如果自己再遇到这种状况,但愿可以想起这则日志,跟他们说声谢谢。

2009/5/6
articleCard.readMore

有一种动弹不得的感觉

本文摘自 勾三股四 更早时期的 不老歌 博客。 这个世界好小,以至于我们在任何时候、任何场合、做任何事情,都要顾及“所有的人”——哪怕不是我们当时所处圈子里的人。 我们每个人都有自己各式各样的圈子。圈子与圈子之间相对独立,如果工作圈里遇到了困惑,可以找生活圈的人来分担,如果在生活圈里找到了快乐,也可以在学生圈里跟大家分享;可这又是藕断丝连的,因为各自圈子里的人也是可以通过其它途径相互认识和熟悉的。尤其是困惑这种东西,通常我们都不想将其公开,尽量低调解决——甚至是更为私密的内容,这时我们往往希望可以有一个相对可信、封闭、保密的圈子来帮助你。 但,这些我们平时觉得值得信赖的“圈”,真的就是可信?封闭?保密的么? 我逐渐觉得,在当今信息化的社会,这样的“圈”似乎是不存在的 我总觉得自己是一个存不住话的人,有什么想法,都一定要说出来才痛快,只是我可以选择说给谁听罢了。 或许,我将来也应该学会,在必要的时候,把想法默默吞下去。 有一种动弹不得的感觉……

2009/5/4
articleCard.readMore

青春,就是……

本文摘自 勾三股四 更早时期的 不老歌 博客。 燃烧! 趁我们还有满腔热情 趁我们还可以过青年节 趁我们还有理想 趁我们还有抱负 趁我们还有体力 趁我们还没有伤痕累累 都无怨无悔 只是 希望在我们都化为灰烬的同时 还可以照亮什么 ------------------------------ 我们都还年轻,不是么……

2009/4/23
articleCard.readMore

推荐一个 Javascript 监听输入框改动事件的实现方案

本文摘自 勾三股四 更早时期的 不老歌 博客。 if (window.addEventListener) { domInput.addEventListener('input', handler, false); } else { domInput.onpropertychange = handler; } 如题,以上方案,可以兼容目前的在所有主流操作系统上,所有主流浏览器里,用所有主流输入法输入文本(包括粘贴)的事件。 我们一般监听输入框改动的方案是通过 onkeyup/onkeypress,其属于 web 标准方法,兼容各浏览器,但局限性在于跟一些特定输入法和特定操作系统之间的结合并不完美。比如在 ubuntu 下切换到中文输入法,就触发不了 onkeyup/onkeypress 了,直到关闭输入法,才会触发相关事件。 如果大家有更好的解决方案,欢迎在此交流。

2009/4/22
articleCard.readMore

令人荡气回肠的切尔西·斯坦福桥

本文摘自 勾三股四 更早时期的 不老歌 博客。 刚刚看过今晨的欧冠录像,切尔西和利物浦战成4比4,红军的激情和蓝军的顽强都在这里展现的淋漓尽致! 通过摄像机的镜头,我又看到了熟悉的斯坦福桥球场,想起了4年前切尔西和巴塞罗那在这里的一番激战。当时的比分是4比2,切尔西和巴塞罗那这两支当时欧洲最优秀的队伍,为了一个欧冠8强席位都拼得你死我活,出线权也随着双方一个接一个的进球4度易主,也缔造了罗纳尔迪尼奥那一脚惊世骇俗的年度最佳进球。 那个时候的我,还是个大学生,大学的时候宿舍没有电视,又想看球,就约了几个球友晚上出去租一间带电视的小破屋子一起看,斯坦福桥的那场比赛是我学生时代看得最过瘾的一场,而且印象如此深刻。 今天再次看到斯坦福桥的又一场经典战役诞生,不得不感叹切尔西金元的能量和职业足球的魅力! 也希望自己在未来的某一天可以有机会亲身经历这一切。 以此为我的博客新分类“今天的业余爱好也许就是明天的职业”作序。

2009/4/15
articleCard.readMore

我已经离不开了一个名叫博客的东西

本文摘自 勾三股四 更早时期的 不老歌 博客。 找了好久,精挑细选,我选择了在不老歌开始新的网志生活。 前阵子被人说自己是火星人,只用QQ聊天,只懂得专研代码,就知道踢球;不爱看小说,不爱看电影,不会做饭,不爱逛街,也不怎么用电话。总之一点都不像正常人 我想了想,好像勉强属实。但又一想,以前的自己似乎不完全是这样的——最起码,从前的我,还总跟人讲电话发短信,人缘不错也爱凑热闹,每个礼拜都给家里的老爸老妈通电话问寒暖报平安的。 或许以前的自己真的太累了,想休息了。我逐渐喜欢比较清静的环境,喜欢一个人在夜深人静的时候默默思考,写点东西。 当世界的纷乱在我们的眼中还可以用缤纷来形容时,一切都是那么会让人蠢蠢欲动。 在校内网崩坏的那一刻, 在之前的烂摊子逐渐冷却的那一刻, 在脚踝在篮球场上扭伤的那一刻, 在夜深人静诚实分析自己的那一刻,我突然觉得这里好惬意。 你们也喜欢这里吗?

2009/4/14
articleCard.readMore