普通视图

发现新文章,点击刷新页面。
昨天以前囧克斯 勾三股四

奔四了,写给40岁的自己

作者 勾三股四
2025年12月31日 18:22

年关将至,写给已经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 自己的这几段话。

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

作者 勾三股四
2024年3月26日 14:30

关注我的人可能已经知道我最近作为常驻嘉宾加入了一个叫做《代码之外 Beyond Code》的播客节目。

代码之外 logo

这里简单分享一些我参与《代码之外》的故事:


什么是《代码之外》? ​

这是一档由 GeekPluxRandy 共同主持的程序员闲聊节目。虽然二位主播都是程序员,但节目基本不谈编程写代码,而是谈一些程序员在工作和生活上比较有共鸣的话题,或是从程序员的视角看待一些社会热点。截至目前我主要参与的部分是听众来信栏目,在节目上公开回答听众们的问题。

听众来信提问入口

我是如何加入《代码之外》的? ​

我其实和 GeekPlux 和 Randy 之前都相互认识。GeekPlux 是我之前在阿里云的同事,短暂共事过一阵子;而 Randy 是因为之前我做 Weex 这个项目有机会和他认识。后来我从阿里离职,也逐渐跟他们比较少联络了,直到最近我在社交网络上发现他们做了这个节目。我从“第 0 期”就开始听,一方面是因为好奇他们二位最近的工作和生活状态,另一方面也是被节目内容所吸引。

因为我一直觉得技术类的话题在网上包括线下的交流会到处都是,但是技术以外其实还有很多值得探讨的话题,在国内并没有什么平台和机会进行交流和分享。这个观点我之前在关于 D2 前端论坛的一篇博客里曾有提及:

我希望 D2 今后可以有一些关于团队、关于人的主题。我们的技术能搞上去,业绩能搞上去,人,怎么样?

《代码之外》在我心目中恰恰是这样的节目,它让很多值得被关注的非技术议题浮出水面,引发大家的讨论,在我看来是非常非常有意义的,也是我一直想做但一直没有付诸实际行动的事情。所以我每一期都听得津津有味,而且非常有带入感。

大概听了两三期之后,《代码之外》有了自己的 telegram 听友群,我也跟着其他听众一起进了群,跟 GeekPlux 和 Randy 恢复了联络。我跟二位说,节目有我能“插上话”的地方都可以随时找我,非常乐意贡献自己的观点和看法。他们非常爽快地答应了。

后来发生的事情就是我被邀请作为嘉宾录了一期节目:

第 6 集节目封面

第 6 集 | 勾股如何看待 Weex 被指 KPI 项目、转管理的经验、向上管理的技巧、如何建立个人品牌、双十一的经历

在这期节目中,我们顺带讨论了一封关于如何建立个人影响力 (reputation) 听众来信。节目播出后效果似乎还不错,再之后我开始作为固定嘉宾参与每一期听众来信的录制。不得不说来信的问题质量都很高,每一个问题都是好问题,我们也是来者不拒,从职场技能聊到个人发展,再聊到开源项目心得等方方面面。

我为什么选择加入《代码之外》? ​

首先是因为之前所提到的我对非技术类话题的价值认同,我觉得这个是核心原因吧。

其次,我非常欣赏 GeekPlux 和 Randy 在节目中展现出的个人气质和魅力,我相信这也是有这么多人收听这个节目的重要原因之一。有机会和他们二位对谈,讨论有价值的议题,碰撞思想,是一件非常珍贵、也是非常酷的事情。

节目截图

同时从自身的角度,我也希望可以运用好自身多年编码和管理的经验,输出一些自己平时对生活和工作的观察和思考。这段时间对于很多国内的从业者来说,可能是一段特别艰难的时期,我能直接参与和提供的帮助有限。但用现在流行的说法讲,也许我可以通过这个节目提供一些“情绪价值”:)

最后,我也希望自己可以保持一个不断接触新事物的学习的心态。一方面是暗中观察 podcast 前前后后的工作,不只是在节目上传递信息,更包括参与完整的制作和宣发流程,学习相关的专业知识和技巧,结实这个圈子里的有识之士;另一方面也可以被更多年轻的听众认识,近距离观察和学习每个人的经历和看问题的角度。这些长期来看一定会给我自己带来很多收获。

当然也有时间的因素,我现在在新加坡的工作和生活逐渐趋于稳定,有了些闲暇的时间,正好可以支撑我做一些额外的事情。各方面因素综合在一起,让这件事情顺理成章。

几期节目做下来之后的心得感受 ​

截至目前,我和 GeekPlux 和 Randy 二位已经陆陆续续一起做了 7 期节目,包括 1 期我自己的人物访谈和 6 期听众来信:

第一个感受就是每次跟二位聊天都特别开心,一聊就忘记时间停不下来。大家不止一次聊到某个观点的时候发自内心的相互认同,有一种找到知音的感觉。再有就是参与这个节目让我以一种更认真负责细致的态度审视看似平实的东西,每次跟着二位在节目前准备素材、节目中互相碰撞、节目后关注和复盘的过程中,自己都有非常多的收获。

另一个有趣的事情是,我在这个过程中改进了我的声音。尤其是开始参与后期制作的时候,我发现自己有的时候说话有气无力,或者有一些不好的说话习惯,包括那些啧啧啧、嘶嘶嘶的下意识发出的声音,真的是觉得影响听感又非常难克服。我还在持续改进中,并感慨每件事情做到极致都是不容易的。

对于音频/视频的处理,我也算是入了个坑,至少了解了一些常用的软件和专有名词,尤其是音频的剪辑和处理,门道其实还挺多的。由于自己还是菜鸟阶段,没到系统性总结的时候,这里就不展开了。

我通过这个节目还认识了一些优秀的播客们和主持人们,前段时间还有幸跟另外几个播客节目的主播们串了个台,估计节目很快就会上线了,也尽请关注。

当然最开心的莫过于得到了很多来自听众们正面的反馈。我深知做好一档节目有很多地方需要继续努力,这些正面的反馈给了我自己莫大的鼓励和坚持下去的动力。

听勾股聊天还蛮有启发的,建议常驻😆
-- wavever

通过读书来学习管理,这挺有趣,因为之前看到一些观点说管理并不能从书本学习到。记录一下《合作的进化》《领导学》
-- piero_cli

这期真的学习了很多 Beyond Code and Far more than I learned
-- @user-fv9po5tt3r

2个小时,看完了,干货很足,期待后续的节目~
-- @user-ue9kv1tr8o

受益良多[打call]
-- 牛头梗0

一口气听完,比看场电影都爽
-- 码农有梦想

听完最大的感受是,勾股情商好高[吃瓜][打call]
-- 杜府UP

很好的节目[支持][支持]目前也在备考雅思 中间嘉宾的发言真是太认同了
1.学习乐器和英语和学习编码的区别之一在于一个相对枯燥,一个相对新奇
2.将最困难最枯燥的事情放在早上精力充沛的时候做
3.对于这种反馈周期长的学习任务,给自己制造一些正反馈
-- 黑丸子丶

超级开心啊,之前勾股的访谈那期真的学到了很多,以后变成常驻嘉宾真的太好啦!
-- UsagiCake

筹备自己的一对一咨询服务 ​

有一期节目录完之后,跟 Randy 和 GeekPlux 二位主播 after cut 闲聊,他们都鼓励我做个一对一私人咨询的服务,觉得我的观点和经验会帮助到更多的人,尤其是我们都意识到,现如今听众来信里的很多话题,都逐渐从小众转化为了大众、甚至社会性的话题;同时一对一私人咨询也可以做得比节目更深入,更好的保护当事人或公司的隐私需求,因为节目的时间和机会毕竟有限,且是公开形式的,很难针对性 + 回合式地聊太深。于是我打算抱着试试看的心态做起来,也许近期就会有进展跟大家再分享

2024-04 更新:我的一对一私人咨询服务已经启动,请移步至:

点击预约我的一对一私人咨询服务 (已暂停)

2025-11 更新:目前我的一对一私人咨询服务处于暂停状态,未来会考虑以一些别的更好的形式与大家交流,感谢大家的理解

同时,《代码之外》和听众来信栏目我也会和二位主播一起继续做下去,继续解答大家的问题。

听众来信提问入口

如果大家对于这个一对一咨询服务的形式有什么兴趣、想法或建议,也欢迎通过任何方式让我或《代码之外》节目知道。

以上

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

作者 勾三股四
2020年4月26日 11:53

介绍要给小工具给大家:zhlint

zhlint logo

这个工具可以帮助你快速格式化中文或中英混排的文本。比如常见的中英文之间要不要用空格、标点符号要用全角字符之类的。

看上去这工具似乎和自己的工作和职业关系不大,但其实也是有一定由来的。


项目的由来 ​

自己之前参与过一些 W3C 规范的翻译工作,这其中除了需要一定的词汇量、语法知识和表达技巧之外,最主要的部分应该就是格式了。因为大家对诸如空格、标点符号等细节的使用其实不太统一,这在团队协作的时候其实会变成问题,大家都花了一些不必要的时间在格式讨论和校对上。感觉这部分工作比较枯燥且机械,但又不得不做。只能花更多时间在上面。

后来因为接触 Vue.js 的关系。这个项目在早期并没有太多人知道它,而且当时社区普遍比较迷信像 Google 这种大厂官方推出的技术方案,对“野生”项目都不是很有兴趣,所以我希望可以把这个项目介绍给更多人认识。结合我之前的翻译经验,我觉得翻译文档是一个比较好的途径,于是就发起了 Vue 中文文档的翻译,结果没想到这件事一发不可收拾,我就不知不觉从 2014/2015 年做到了今天。随着 Vue 的不断发展,关注文档的人也越来越多,中间发生了很多故事,这些故事也让我自己逐步对翻译和中文格式的细节有了更多的认识。

真正触发我做这个项目的事情,是去年的一个翻译讨论:如何翻译 attributeproperty。这个问题几乎从我接触技术翻译的第一天起就一直是个噩梦。我和周围的小伙伴尝试了各种译法,都不能让所有人满意,无奈之下通过刻意的区分和强化教育,把它们分别译成“特性”和“属性”。这个状态持续了很长一段时间,Vue 的文档也基本都是这么翻译的。直到去年的一段时间,我逐步意识到,也许这两个词不翻译会更好,索性直接保留英文原词,这样不会有歧义,同时随着整个社区的英文程度在提高,像这样的词不翻译大家应该也能顺畅的理解了,中英文混着读也逐渐可以接受了。所以就在 GitHub 开了个 issue,同时也扩散到了 W3C Web 中文兴趣组。没想到这次讨论大家的意见出奇的一致,几乎“全票通过”。看上去困扰我多年的问题终于要解开了……

然而在这之后,我意识到,如何对已经翻译好的大量文档做关键词批量替换并不是一件容易的事情——主要还是格式细节太多了。不能做简单粗暴的文本批量替换。

比如把“特性”换回“attribute”之后,如果“特性”一词的两边也都还是中文,那么“attribute”两边就都需要加一个空格,而如果是标点符号就不需要,而如果是英文,那理论上这个空格已经加过了。所以情况很多很复杂。你读到这里可能觉得那我们稍微加个正则表达式也许可以解决,那我会在告诉你,如果这个词的边上还可能有 HTML 标记或 Markdown 标记,那这个正则该如何写呢?或许也不那么容易了。

因此这个译法改动在去年就已经有定论了,但是实际上到今年上半年我才真正改好。原因是我觉得这次我不打算再靠蛮力去解决问题了,而打算通过工具来解决——这就是我做这个项目的由来和动机——没错我陆陆续续做了一年左右,最近终于做出一个比较文档的版本了,然后才完成了这次译法的替换。

另外一个促使我做这个工具的原因其实是我个人希望尝试一些语法分析之类的技术,因为觉得作为一个前端工程师,未来这个方向的可能性和空间比较大。如之前和很多人都聊到过的,现如今的前端框架全部都开始在编辑器这个环节大做文章。因为它可以帮助你突破一些 JS 语言的限制。所以大家武装到牙齿之后这部分是一定会碰的。我预测接下来这个趋势会从框架往上发展,逐步延申到前端工作的更多环节。做这个工具看上去似乎有那么一点可以积累到的知识和经验,于是就想先做个这个试试看。

快速开始 ​

接下来回到 zhlint 这个工具,介绍一下我设计的基本用法:

基本用法 ​

  1. 在安装 Node 和 Yarn 之后,运行 yarn global add zhlintnpm install -g zhlint。这样 zhlint 就安装好了。
  2. 在命令行里运行 zhlint,就可以启动这个工具并看到关于它的帮助信息。
  3. 如果想真正校验一个文本文件,可以运行 zhlint <filepath>,比如我们创建一个文件叫做 foo.md,其中的文本内容是 中文English。那么运行 zhlint foo.md 会收到一个错误提示,提醒你中英文之间应该有一个空格。
  4. 现在我们更进一步,运行 zhlint foo.md --fix,顾名思义这个命令会自动修复文件中的格式错误。所以运行之后文件 foo.md 内部的文本内容会变成 中文 English
  5. 如果现在有一批文件都需要做格式校验,zhlint 还支持批量多文件匹配。比如 zhlint src/*.md 可以校验 src 目录下的所有 md 文件。同理也可以加 --fix 做批量自动修复。

常见格式问题 &ZeroWidthSpace;

  1. 空格问题
  • 中文English中文 -> 中文 English 中文
  • 中文 , 中文 -> 中文,中文
  • 1+1=2 -> 1 + 1 = 2
  1. 全角/半角问题

中文, 中文. -> 中文,中文。

  1. 特殊组合用法问题
  • Mr. (不转换全角句号)
  • 2020/01/02 01:20:30 (在描述时间和日期的时候冒号和斜杠两边没有空格)
  1. 特殊个例问题
  • 33.30KB min+gzip (这里的加号两边不会加空格,该 case 没有普遍规律)

研发过程和心得 &ZeroWidthSpace;

现在回顾之前的研发过程,首先是做得比较懒散,陆陆续续一点一点做,其次是返工了无数次,发现哪里走不通了就推倒重来,所以经历了太长的时间。

第一版 (未完成 + 未发布) &ZeroWidthSpace;

在最初的版本里,我想的比较简单,就只是把中文内容分为几个颗粒度去处理:char、token、full text。所以我当时只做了五件事:

  1. 逐个字符分类识别 (全角字符、半角字符、标点符号、空格)
  2. 把字符连接成若干个 token 并分类识别 (中文、英文、标点符号)
  3. 实现一个基本的 token 遍历函数
  4. 利用这个 token 遍历函数指定校验规则并遍历处理 (比如发现一个中文和一个英文的 token 挨着,就强制塞一个空格进去)
  5. 把处理过后的 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 这个函数越写越大,逐渐失控。原因有这么几个:

  1. 中文里对括号和引号的使用非常灵活,设计之初低估了它的难度和复杂度。比如:我们需要 (先做一件事,然后再做一件事,最后再) 做一件事。括号可以断在任意的地方,可以跨越多个句子,可以包含最前边或最后边的标点符号,也可以把它们留在外边,被截断的前后句子单独拿出来也未必是完整的。
  2. 有一些非常特殊的 case 需要绕过,比如括号可以用在英文的单复数变化中 (minute(s))、单引号可以用在英文缩写中 (doesn't) 等等。再比如在描述时间和日期的时候,我们不太习惯在每个数字之间都加空格所以会省略空格 (2020年1月1日 而不是 2020 年 1 月 1 日)。

这些都导致设计之初通过简单的线性 token 机制处理很难做好这件事。“Travel and process” 这部分的代码越来越臃肿。逐渐我意识到,这里需要更多的结构化设计。于是我停下来考虑了一段时间。

第二版:部分重构 &ZeroWidthSpace;

之后我逐渐想到两个主意:

  1. 第一版尝试把 token 从线性结构转变成树形结构,但这棵树并不是规范的树,尤其是括号,所以我把括号从树形结构中抽离了出来,改为记号 (mark)。记号不会影响树形结构本身,可以单独识别和处理。这有点类似 HTML 之于 text 的区别,也就是某种“超文本标记”。事后证明 mark 这个结构和思路对后续的功能研发还有很大帮助。
  2. 把需要 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 格式 &ZeroWidthSpace;

截至目前,我们 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。所以拜托了一些低级别的重复性问题之后,整个研发过程越往后其实会变得越清晰越简单。

特殊情况处理 &ZeroWidthSpace;

在设计和实现 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 解析、还有被忽略的个别情况的注释解析。

测试 &ZeroWidthSpace;

这次研发过程中,我比较早,也比较严格的实践了测试驱动开发。基于 Jest 写了很多用例,通过这些用例把工具的行为“卡死”,这样当后期引入更多复杂度的时候 (比如决定重构第二版的时候、或决定支持 Markdown 格式之后),可以通过锁住测试用例进行大胆的重构和尝试,并且在重构的时候一旦发现一些之前没有覆盖到的 edge case,就立刻补充进去,然后重构至这个 case 跑通为止再继续。久而久之整套测试用例也越来越见状。总体下来还是受益匪浅的,帮自己省了很多时间和脑细胞——上一次有这种感觉的项目是 Weex JS runtime 第一版。如一些朋友知道的,当时我们只有 2 个月时间,要从零写一个 JS 框架用在双十一移动主会场,所以除了测试用例我当时谁也没法相信。

最终收尾 &ZeroWidthSpace;

完成上述核心功能之后,差不多已经过去一年时间了,最后的一些工作留给了下述这些“外包装”。

  • 支持 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 之后遇到了一些比较特别的问题:

  1. zhlint 处理的文本多半是自然语言,每个段落的字数长短不一,所以大概率在打印的时候会折行 (相对来说代码书写长期的最佳实践是每行少于 80 个字符,所以这个问题并不明显)。设想一下如果被定位的字符在一大段话的正中间,那么再下一行的小尖角字符已经完全失去了辅助定位的作用。
  2. zhlint 处理的多半是中文文本,所以这产生了另外一个问题,中英文混排的时候字符是不等宽的。所以小尖角字符之前的空格数很难算准。

为此我采取了一种不太一样的定位展示效果:

  1. 不会打印一整行文本,只会取目标字符前后一段距离的字符串片段 (如前后各 20 个字符),然后在其片段两边加入省略号。
  2. 引入日志着色包 chalk,这样就可以为日志上色。
  3. 把小尖叫符号同一行之前的空格用相同的字符串片段在此字符之前截断的部分替代,并同时设置背景色和文本色为同一个颜色 (黑色)。

所以最终看到的效果,如果把特别的着色去掉的话,看到的效果是:

自动在中文和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 的使用情况 &ZeroWidthSpace;

目前 zhlint 已经集成到了 Vue 的中文文档项目中。通过简单的 CI 配置,就可以轻松做到为每个 PR 自动 lint 并返回处理结果。

有了这个工具之后,我们就可以比较没有心智负担地批量替换文本了,替换之后运行一遍类似 zhlint src/*.md --fix 的命令,即可把因批量替换产生的格式问题全部修复。

然后,我就立刻完成了对 attribute 和 property 的替换……

所以“为了批量替换两个单词的译法,我花了差不多一年的时间” (这原本是我设想的这篇文章标题党版本的标题)

之后我们在 Vue 文档中又陆续遇到了讨论 mutationref 译法的问题。产生的相应改动也都可以基于 zhlint 很容易的得以实现。

有趣的是,在我在微博Twitter 简单分享了这个小工具之后,也有人留言说其实写博客的时候这个工具也非常有用。或许这是 zhlint 可能的更多用途吧😉!

最后的回顾 &ZeroWidthSpace;

最后,关于心得体会和收获,我觉得有这么几个:

  • 首先觉得写 parser 和 linter 是个蛮有趣的事情,有很多不一样的体验,尤其对于前端工程师来说,既熟悉 (处理字符串) 又陌生 (没有 UI,生吃字符串)。未来对 ast 相关的技术会持续自我投资。
  • 测试驱动 FTW。
  • 越来越少有机会真正自己从 0 到 1 做些东西出来了,很珍惜这次机会和类似的工作。
  • 如果发现不对劲,及时停下来或调头。不必也不能太过纠结。在功能成型之前,永远只为最终的理想形态而努力。
  • 要耐得住性子,这次做这个小工具很考验自己的性格。虽然业余时间已经很有限了,但是持之以恒,就是胜利。

另外 zhlint 其实还没有做完,我已经想到了更多的 feature 和改进点,其实也已知了不少不理想不完美不够好的地方。所以会继续做下去。

以上

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

作者 勾三股四
2019年4月5日 07:21

为大家介绍一个刚刚开源,但其实自己已经使用超过 5 年的小工具:vue-mark-display。你可以用它把 markdown 格式的文本转换成幻灯片并在浏览器中播放和控制。

开发背景 &ZeroWidthSpace;

我自己工作中经常需要准备各式各样的幻灯片,所以逐渐觉得用 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 制作出非常精美的幻灯片。


基本语法 &ZeroWidthSpace;

首先,我们在 markdown 语法的基础上做了一个扩展:通过 ---- 分割线把一整篇 markdown 文档划分成为若干张幻灯片。

md
# 这里是第一页

以及一些基本的自我介绍

----

### 这里是第二页

- 这里有内容
- 这里有内容
- 这里有内容

----

没了,讲完了

__谢谢__

例如上面的例子就会被生成为三张幻灯片。

这样你就可以利用 markdown 支持的 h1 - h6 标题、列表、表格、图片、链接、加粗等格式加分页符用极快的速度写出幻灯片了。

通常情况下,你再为自己的幻灯片设置一套全局的 CSS 样式并固化下来成为你的样式风格,基本就可以拿来演示了。以下是我自己喜欢的样式风格:

自定义样式 &ZeroWidthSpace;

在这个基础上,我们对 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>

其它自定义选项 &ZeroWidthSpace;

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 &ZeroWidthSpace;

vue-mark-display 还支持直接导出成为 PDF 格式文件。因为我们的幻灯片有可能通过各式各样的方式进行传播:有可能是一个链接,也可能是一个文件。所以 vue-mark-display 利用 W3C 的 CSS Page Media 相关规范满足了这一需求,你只需要简单的打开浏览器的打印对话框,然后选择导出成为 PDF,就可以轻松的把自己的幻灯片发到钉钉好友、微信群、邮件附件等各个地方。

最后想说 vue-mark-display 已经开源了 &ZeroWidthSpace;

欢迎大家试用并提出宝贵的意见,如能一同参与建设和维护,我会更加感激。

同时我也把自己最近在社区分享过的幻灯片全部整理到了这个地址:

https://jinjiang.github.io/slides/

接下里这个项目还有一些规划中的特性会逐步实现并发布,尽请关注。

最后的最后 &ZeroWidthSpace;

如果你不想学 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/,完毕。

不过这个工具非常初期,还请大家多多提意见,接下来我也会逐步为这个工具增加更多贴心的功能。

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

作者 勾三股四
2017年4月1日 16:06

译自: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年3月28日 20:34

译自: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 花了大量的时间碰撞想法,很快会实施一个新的职位/团队的结构,最终付诸实践,我对此充满期待。

现在我会为一个在其工作岗位上做的很优秀的一线员工表达对管理的兴趣而担心。我担心这种渴望的很重要的一部分不源自真正的管理责任感,而是因为他们想要在职业生涯和/或影响力上得到提高,并且认为这是唯一的办法。

我画了这张图来辅助说明两种角色之间的不同:

ics-vs-pws-small
(大图)


一线员工为他们自己及其工作负责。因为他们以一线员工的方式取得了长足的发展,所以他们的影响力变得更加广泛。一个在 Moz 的好的例子就是 Dr. Pete,他判断公司的战略指示并随之协力投入。他通过审查来协助大数据操作,通过战术指导和策略输入来辅助市场,发表如此高质量的博客指南,甚至从头开始设计整个项目并基于他们的创意执行。他的影响遍及整个公司,横跨多个团队,和他们一同成长。他通过自己的影响力定义了这个角色,这比其它方式都好。

另一方面,优秀的经理人有义务让他们的团队开心、团结自主,也要负责审查、指导等等。他们发展的越顺利,就越不需要“待在壕沟里”了。很多情况下,他们只会协助定义战略问题。剩下的定义范围、搜索相关答案、实现和执行统统都交给一线员工来做。一个在 Moz 的好的例子就是 Samantha Britney。她长期是一个一线员工,但现如今已经成为了经理人,帮助产品团队的几个一线员工,给予他们工作的自主性,通过工具、资源和协助把事做好,并提供作为一个经理人必要的辅导、一对一、回顾和 HR 工作。她的报告中从不会提及任何细枝末节,但总会驱动他们的项目向前。

基本上,如果你喜欢并且能够把这件事做好,那么你应该做一个一线员工。如果你喜欢 (且擅长) 把自主权交给其他人,帮助他们成长和成功,那么你应该做一个经理人。

这些一线员工和经理人之间有这样一些差别

  • 随着一线员工们的发展,他们希望和经理人的职责范围有更多的重叠。对经理人来说恰恰相反——随着他们的发展,他们实际干的活越来越少。
  • 资深的一线员工的角色更加灵活——他们能够在各种地方工作,并且由于工作得到认可,收到的会议和活动邀约就越来越多。资深的经理人相反——他们在办公室的时间更为关键,所以很少出差,也经常身居幕后 (很明显 CEO 在这方面是一个例外)。
  • 如果你有很多一线员工却只有很少的经理人,那么你会发现汇报和管理很有挑战。但是如果你有一堆经理人而没几个一线员工,你会面临“厨师太多伙计不够了”的问题 (并且这通常意味着你们的组织和文化已经一团糟了)。
  • 优秀的一线员工有时会发展成为经理人,并因此在新的角色上变得平庸或失败。这绝对是个悲剧。公司不仅失去了一个卓越的一线员工,而且连管理也被搞砸了,因为这会造成大量传染式的问题。如果一个一线员工表现平平,问题往往不至于这么严重。
  • 待遇是个技巧活儿。在我理想的世界里 (对了我们在 Moz 创建的工资范围是跨发展路线的),一线员工和经理人的级别是大致等价的。假设在某一条路线上有 7 个级别,那么 level 3 的一线员工可以做 level 3 的经理人的事情。最高级别的一线员工应该能够和 CXO 挣得一样多。

我和他人分享这些观点时,大部分情况是直观的。我遇到过的最大问题和一个简单的概念有关——战略战术的所有权。有一天一个 Mozzer 同事和我在这方面的看法就不一致。他觉得在 Moz 的历史上,有些团队的经理人掌握着战略和战术的所有权。个人开发者没有定义他们做什么,怎么做,如何衡量,也没有定义执行过程,他们只是接受命令。

是的这样做也行得通并且这种情况确实发生过。但是我不同意我的同事,这样做相比于,把更大的所有权交给一线员工,让他们决定做什么、何时何地、如何做,让经理人指决定谁来做已经为什么要做,效果不可能一样。诚然,很多初级经理人和一线员工之间会具有更大的内容重叠,而很多高级个人开发者会决定谁来做和为什么要做 (如上所述)。但是我强烈的相信,从长期来看,我们应该走这条路。人们的快乐便在此之上。

当 Daniel Pink 问道“是什么让我们的工作快乐?”时,答案已经很明显 (并且被很多其他学者和不太正式调查者证实):

  1. 自治——主导我们自己生活的渴望。
  2. 精通——在关键的事情上越做越好的欲望。
  3. 目的——渴望做好比我们自己更重要的事情

如果个人开发者无法控制自己的工作内容并且能够掌握工作技能,他们中间优秀的人就会离开,去那些提供这种机会的公司。我们将只留下经理人,而且这会很快。

很奇怪,我是那种一线员工风格的 CEO (也许这并不都是奇怪)。我是一个高级别的一线员工,所以我和经理人有很大的职责重叠,但是我会服务所有的团队、工作和细节。我可能是最直接参与到产品和市场的人,我也经常让这些团队 Mozzer 们像对待资源和工具一样对待我。你让我写篇博客我就会去写,你让我答复一个客户我就会冲上去,你需要聊聊一个项目如何匹配更广泛的目标,以及如何改变你的做法,那就一起聊聊呗。我喜欢我汇报给这些 Moz 员工的感觉——而不是其它方式。我想这件事情永远也不会改变。

p.s. 我很喜欢 Phil Scarr 的这篇文章,它描述了自己从经理人转变为一线员工的经历以及为什么。Carin 从带领着我们的大数据团队,转变为一个产品团队的资深一线员工,我为之感到骄傲。

p.p.s. 如果我的思路不对 (或者对) 而你也有相关的经验,不妨也留言给我。我一定虚心学习——因为我一直在提醒自己,我是第一次当 CEO

Weex 在 JS Runtime 内的多实例管理

作者 勾三股四
2016年8月31日 19:17

本文早些时候发表在 weexteam 的博客 https://github.com/weexteam/article/issues/71

Weex 的技术架构和传统的客户端渲染机制相比有一个显著的差别,就是引入了 JavaScript,通过 JS Runtime 完成一些动态性的运算,再把运算结果和外界进行通信,完成界面渲染等相关操作指令。而客户端面对多个甚至可能同时共存的 Weex 页面时,并没有为每个 Weex 页面提供各自独立的 JS Runtime,相反我们只有一个 JS Runtime,这意味着所有的 Weex 页面共享同一份 JS Runtime,共用全局环境、变量、内存、和外界通信的接口等等。这篇文章会循序渐进的介绍 Weex JS Runtime 这部分的内容,大概的章节设计是这样的:

  1. 为什么需要多实例
  2. 多实例管理面临的挑战
  3. 解决问题的思路
  4. 几个特殊处理的地方
  5. 总结

为什么在 JS Runtime 内部手动管理多实例? &ZeroWidthSpace;

如果只用一个词来回答,那就是“性能”

如果要用一段话来回答:手机上的资源是很宝贵的,包括CPU、内存、电量等等,而 Weex 团队从设计初期就决定以页面为单位对产品实现进行划分,一个完整的应用是多个相互独立解耦的页面通过一定的路由规则和链接跳转互联起来组合而成。所以为每个页面都单独提供一份 JS Runtime 代价还是比较昂贵的,这会引起大量的资源开销,手机发烫,反应迟钝,甚至应用或操作系统的崩溃。尤其是在国内一些中低端机型上面,反应尤其明显。

从另外一个角度讲,我们通过同一个 JS Runtime,可以更直接方便的做一些运行时的资源共享,比如 JS Framework 的初始化过程,只需要应用启动的时候执行一次就可以了,不必每个页面被打开的时候才进行。目前 JS Framework 的启动过程一般会在几百毫秒不等,相当于每个页面打开的时候,这几百毫秒都被节省下来了。

多实例管理的 JS Runtime 需要额外关注哪些问题? &ZeroWidthSpace;

首先不同的 Weex 页面肯定需要执行各自的 JavaScript 运算,完成各自的 native 指令收发。所以如何避免多个 Weex 页面在同一个 JS Runtime 里相互“打架”就变得至关重要。

这里的“打架”有以下几个细节:

  • 数据和状态的记录,能够正确的完成并且不会被其它页面的运算所干扰或截获
  • 和 native 之间的收发指令或通信,能够准确的调度不同的 native 端页面
  • 对系统资源的利用,遇到大运算量的页面时,其它页面有机会快速得到响应

除了“打架”的问题之外,传统 HTML5 页面里,每个 JS Runtime 的生命周期是对应页面本身的生命周期的,相对是个短效的实例,而且一旦页面被关闭,对应这个页面的 JS Runtime 就可以大方的 kill 掉,没有任何后顾之忧;而 Weex 的 JS Runtime 需要在应用被开启之后至始至终存在并不间断工作,所以长期运转的内存管理也变成了一个不得不正视的问题。

Weex 解决上述问题的过程 &ZeroWidthSpace;

  • 首先,我们会为每个新打开的 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 里的闭包原理把不同实例的运算和数据状态管理隔离在了不同的闭包里,达到相互不“打架”的目的。

初级形态 &ZeroWidthSpace;

形如:

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)
}

我们在闭包中设置了这么几个东西,保障隔离效果:

  1. define: 用来自定义一个复合组件
  2. 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 页面里的 definebootstrap 表面上是全局方法,实际上只会针对当前的 Weex instance 在一个更小的作用域下执行,而不会干扰或污染全局环境或其它 Weex 页面。

这是我们最初的版本的形态。

配合更开放的前端包管理工具 &ZeroWidthSpace;

随着 Weex JS Framework 代码的不断演进,功能也逐渐丰富起来,上层的 Weex 页面也写得越来越复杂,之前简单的 define + bootstrap 已经满足不了工程上的需求和设想了。这个时候我们需要引入前端资源包管理的概念,而且拥抱现有的各种成熟的包管理规范和工具。这其中包括 AMD、CMD、CommonJS、ES6 Modules 等等。这个时候 definebootstrap 这两个名字就显得起得有点太大了,尤其是 define,和 AMD 里的语法重叠,所以和很多兼容 AMD 语法的打包工具都会产生冲突。所以我们逐步把这些方法转变成了带有 Weex 特殊前缀的方法:

  1. __weex_define__: define 的别名,用来自定义一个复合组件
  2. __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')

/***/ }
/******/ ]);

evalnew Function &ZeroWidthSpace;

之后我们在最终执行 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 的写法更便于清晰的管理和对应这些内容。

性能优化 &ZeroWidthSpace;

可能很多同学注意到了,不论是 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 的源代码放进去,效果和之前的实现是等价的,但是由于没有用到 evalnew Function,性能有了一定的提升,在我们实验室数据中,JavaScript 运算的时间缩短了 10%~25%。

当然,由于在浏览器的环境下,我们没有机会在执行 JavaScript 之前对内容进行高性能的处理,所以 HTML5 Renderer 还没有办法通过这样的改造提升执行效率,在这方面我们还会继续探索。

其它多实例管理接口设计 &ZeroWidthSpace;

包括上述提到的 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 之间的通信,同时可以做到多实例之间的隔离。

几个特殊处理的地方 &ZeroWidthSpace;

篇幅有限,所有问题不能一一展开,这里提几个我们比较有心得的地方

如何避免某个页面大数据量通信阻塞其它页面的通信 &ZeroWidthSpace;

绝对的避免和杜绝是很难的,我们通过以下集中方式尝试缓解和回避这种现象出现,部分想法还在论证当中:

  1. 持续优化 JS 代码的算法实现,这个是肯定要做的。
  2. 如果一个页面的内容在运算到一半的时候,用户就关掉了这个页面,尽管不能像关闭一个浏览器标签时那样杀掉这个 JS Runtime,但是可以通过在 sendTasks 的时候返回一个特殊的值来提示 JS 代码可以省去后续的计算,让整个 JS 阻塞的状态立即恢复。
  3. 部分用户交互可以跳过 JS 执行逻辑直接相应,比如为按钮监听点击事件,并在事件被触发的时候执行 openURL 这个打开网址的命令是个很长的链路,但如果我们支持 <a href> 这样的标签,用户点击链接的时候,页面可以不经过 JS 运算直接跳走,这样回避了 JS 阻塞带来的问题。
  4. 通过用户体验上的一些技巧尽量回避界面一直无相应致使用户一直等待的体验,比如通过伪类规则让用户点击一个按钮的时候第一时间感受到“hover”效果等。
  5. 可以考虑“双核” JS Runtime,永远有一个闲置的随时等待打开新页面的 JS Runtime,这样在页面切换的时候,新页面的加载和运算不会被旧页面阻塞。当然这样做存在对架构和资源的挑战。

更多的思路和想法等待大家的挖掘和探讨。

安全、隐私和稳定性 &ZeroWidthSpace;

其实现在在 Weex 页面里,不经过声明给一个变量赋值还是会产生全局环境的污染,我们短期只能通过宣导的方式,教育开发者避免使用全局变量——这在传统的 HTML5 和 JavaScript 开发中都是特别不推荐的做法。

长期来看,我们可以提供一些发布前的语法检测工具,帮助开发者更好的驾驭自己的代码。

从隐私性的角度,如果你的客户端是多个团队共同研发的,相互之间希望不被打扰,我们也可以考虑引入浏览器中比较广泛实践的“同源策略”,根据 JS Bundle URL 的域名区分对待。

更高更复杂的课题:支持多个 Framework 共存 &ZeroWidthSpace;

让 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 的 页面了。

总结 &ZeroWidthSpace;

这篇文章主要介绍了 Weex 在 JS Runtime 这个环节的一些现状,以及它的来龙去脉,同时介绍了一些心得经验和特别的地方。篇幅有限,有些东西描述的还是比较简略,感兴趣的同学可以移步我们的 github 了解更多细节,同时欢迎大家一起参与到我们的开源项目建设当中来!

谢谢

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

作者 勾三股四
2016年7月28日 13:33

主要内容来自 https://github.com/vuejs/vue/releases

之前 Vue 2.0 发布技术预览版 到现在差不多三个月了,之前写过一篇简单的 code review,如今三个月过去了,Vue 2.0 在这个基础之上又带来了不少更新,这里汇总 beta 以来 (最新的版本是 beta 4) 的主要更新,大家随意学习感受一下

alpha 和 beta 版本的侧重点会有所不同 &ZeroWidthSpace;

首先 Vue 2.0 对 alpha、beta 有自己的理解和设定:alpha 版本旨在完善 API、考虑所需的特性;而来到 beta 版则会对未来的正式发布进行充分的“消化”,比如提前进行一些必要的 breaking change,增强框架的稳定性、完善文档和周边工具 (如 vue-router 2.0 等)

最后的几个 alpha 版本主要更新 &ZeroWidthSpace;

Vue 本身的语法基础这里就不多赘述了,网上有很多资料可以查阅,我们已经假定你比较熟悉 Vue 并对 2.0 的理念和技术预览版的状态有一定的了解。


alpha 5 &ZeroWidthSpace;

  1. ref 的写法由 <comp v-ref:foo> 变成了 <comp ref="foo">,更加简单,同时动态数据的写法是 <comp :ref="x">

  2. 支持 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 &ZeroWidthSpace;

可以设置特殊的 keyCode,比如 Vue.config.keyCodes.a = 65,然后你就可以写 <input @keyup.a="aPressed">

alpha7 &ZeroWidthSpace;

  1. 一个组件的生命周期名由 init 改成了 beforeCreated (大家可以在 Vuex 的源码里看到对应的改变哦)
  2. Vue.transition 的 hook 支持第二个参数,把 vm 传递进去

如:

Vue.transition('name', {
    onEnter (el, vm) {
        ...
    }
})

Beta 1 ~ Beta 4 &ZeroWidthSpace;

beta 1 &ZeroWidthSpace;

  1. 自定义 directive 里 update 的触发时机发生了变化,由于 functional component 等概念的引入,一个 directive 的变更的颗粒度也不完全是 directive 本身引起的,所以这里做了一个更具有通用性的调整;同时 hook 名 postupdate 也相应的更名为 componentUpdated——如果你想让 update 保持原有的触发时机,可以加入一句 binding.value !== binding.oldValue 即可。
  2. Vue.traisition 的 hook 名做了简化
    • onEnter -> enter
    • onLeave -> leave
  3. server-side rendering
    • server.getCacheKey 更名为 serverCacheKey,避免多一层结构嵌套
    • createRenderer/createBundleRenderer 方法不会强制应用 lru-cache,而是开发者手动选择

beta 2 &ZeroWidthSpace;

<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 &ZeroWidthSpace;

  1. 支持在自定义组件中使用原生事件。因为在 Vue 2.0 的设计中,自定义组件上是不能绑定原生事件的,自定义组件上的事件绑定被默认理解为组件的自定义事件,而不是原生事件。针对这个问题我很早就提了 issue 当时小右提出了一个新的语法设计,就是 <comp @click.native="..."></comp>,beta 3 的时候终于看到它被实现了,嘿嘿,有点小激动
  2. 支持两种语法 <div :xxx.prop="x"><div v-bind:prop="{ xxx: x }"> 来对 DOM 的 property 进行绑定,最近我自己也在思考一些在 virtual-DOM 上支持 properties 而不只是 attributes 的想法,这个设计让我也多了一些新的思路。

beta 4 &ZeroWidthSpace;

2 天前发布的,其实这个版本以 bugfix 为主

总结 &ZeroWidthSpace;

以上是近期 Vue 2.0 的一些更新,让我自己比较兴奋的主要是 functional component 以及基于这个设计的 <transition><transition-group> 标签和自定义 transition 标签的能力拓展,还有就是久违的 <comp @click.native="..."></comp>

最后希望大家可以多多试用,有更大兴趣的可以多多学习 Vue 的源码!

Vue 2.0 发布啦!

作者 勾三股四
2016年4月27日 16:19

原文:https://medium.com/the-vue-point/announcing-vue-js-2-0-8af1bde7ab9#.cyoou0ivk

今天我们非常激动的首发 Vue 2.0 preview 版本,这个版本带来了很多激动人心的改进和新特性。我们来看看这里面都有些什么!


更轻,更快 &ZeroWidthSpace;

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 &ZeroWidthSpace;

现在 virtual-DOM 有点让人听腻了,因为社区里有太多种实现,但是 Vue 2.0 的实现有与众不同的地方。和 Vue 的响应式系统结合在一起之后,它可以让你不必做任何事就获得完全优化的重渲染。由于每个组件都会在渲染时追踪其响应依赖,所以系统精确地知道应该何时重渲染、应该重渲染哪些组件。不需要 shouldComponentUpdate,也不需要 immutable 数据 - it just works.

除此之外,Vue 2.0 从模板到 virtuel-DOM 的编译阶段使用了一些高阶优化:

  1. 它会检测出静态的 class 名和 attributes 这样它们在初始化渲染之后就永远都不会再被比对。

  2. 它会检测出最大静态子树 (就是不需要动态性的子树) 并且从渲染函数中萃取出来。这样在每次重渲染的时候,它就会直接重用完全相同的 virtual nodes 同时跳过比对。

这些高阶优化通常只会在使用 JSX 时通过 Babel plugin 来做,但是 Vue 2.0 即使在使用浏览器内的编译器时也能做到。

新的渲染系统同时允许你通过简单的冻结数据来禁用响应式转换,配以手动的强制更新,这意味着你对于重渲染的流程实际上有着完全的控制权。

以上这些技术组合在一起,确保了 Vue 2.0 在每一个场景下都能够拥有高性能的表现,同时把开发者的负担和成本降到了最低。

Templates, JSX, or Hyperscript? &ZeroWidthSpace;

开发者对于用模板还是 JSX 有很多的争执。一方面,模板更接近 HTML - 它能更好地反映你的 app 的语义结构,并且易于思考视觉上的设计、布局和样式。另一方面,模板作为一个 DSL 也有它的局限性 - 相比之下 JSX/hyperscript 的程序本质使得它们具有图灵完备的表达能力。

作为一个兼顾设计和开发的人,我喜欢用模板来写大部分的界面,但在某些情况下我也希望能拥有 JSX/hyperscript 的灵活性。举例来说,当你想在一个组件中程序化的处理其子元素时,基于模板的 slot 机制会显得比较有局限性。

那么,为什么不能同时拥有它们呢?在 Vue 2.0 中,你可以继续使用熟悉的模板语法,但当你觉得受限制的时候,你也可以直接写底层的 virtual-DOM 代码,只需用一个 render 函数替换掉 template 选项。你甚至可以直接在你的模板里使用一个特殊的 <render> 标签来嵌入渲染函数!一个框架,两全其美。

流式服务端渲染 &ZeroWidthSpace;

既然迁移到了 virtual-DOM,Vue 2.0 自然支持服务端渲染和客户端的 hydration(直接使用服务端渲染的 DOM 元素)。当前服务端渲染的实现有一个痛点,比如在 React 里,渲染是同步的,所以如果这个 app 比较复杂的话它会阻塞服务器的 event loop。同步的服务端渲染在优化不当的情况下甚至会对客户端获得内容的速度带来负面影响。Vue 2.0 提供了内建的流式服务端渲染 - 在渲染组件时返回一个可读的 stream,然后直接 pipe 到 HTTP response。流式渲染能够确保服务端的响应度,也能让用户更快地获得渲染内容。

解锁更多可能性 &ZeroWidthSpace;

基于新的架构,我们还有更多的可能性有待开发 - 比如在手机端渲染到 native 界面。目前我们正在探索一个 Vue.js 2.0 的端,它会用 weex:一个由中国最大科技公司之一的阿里巴巴的工程师们维护的项目,作为一个 native 的渲染层。同时从技术角度 Vue 2.0 运行在 ReactNative 上也是可行的。让我们拭目以待!

兼容性以及接下来的计划 &ZeroWidthSpace;

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年1月6日 09:48

随意分享几则自己近期的感悟

人不够? &ZeroWidthSpace;

我自己发现周围的同学越来越把一句话挂在嘴边,那就是:我们就这么些人,想搞定这件事情是远远不够的

说白了,大家觉得“人多好办事”,“规模”这个词很多情况下几乎就是个褒义词。

但是今天有些变化正在悄然发生,比如人类的学习能力越来越强,同时信息技术越来越发达,学习新知识的门槛越来越低,比如各技术领域的成熟度越来越高,比如移动时代前所未有的技术挑战,在有限的硬件性能和网络带宽的情况下,越是“规模”的东西越难以维持;比如体力和知识都逐渐远离核心竞争优势等等

拥抱社区,“我们有更多人” &ZeroWidthSpace;

一件事搞得起来搞不起来,逐渐不取决于我们的团队有多少人,而取决于我们有没有让这件事情发生在最广阔的舞台上,因为一个技术团队再多人,哪怕一百多人,相比起任何一个知名技术社区都是九牛一毛,而一个集团、一个国家、一门语言的社区,相比起全球的互联网社区,也都是渺小的。所以今天,我们想在技术上成就一件事,未必需要很大的团队,未必需要很多人在身边

同时,我们也不能小看个体在科技发展中所起到的关键作用。技术的不断融合和碰撞也加速了这件事情。所以更重要的不是团队有多少人,而是团队有没有能够真正起到关键作用的个体,找到对的人,比组建一支上百人的团队要重要得多

从社区汲取最好的技术 &ZeroWidthSpace;

我敢说,抛开具有真正技术驱动力的公司,绝大多数公司的技术工作都能够在社区找到现成并且适合的方案,也都不太需要最高精简的科研探索。一个务实的技术方案,一定是在大量的社区成熟技术基础上建立起来的,再加上一点点针对自身业务独特性的技术实践。不过看上去“没什么自己的东西”罢了,如果大家很介意这东西“得是我自己的”,那么你需要继续考虑这个问题:自己搞出来的东西能不能比今天社区的更好,能不能发展成更好的社区,是否可以在社区中的深度参与从而把“别人的”变成“自己的”

在社区中施展+检验自己的技术 &ZeroWidthSpace;

然而社区不应该只有索取,还应该有奉献,如果你发现有件事情是别人也会再次遇到的那种事情,但社区没有合适现成的东西,那么恭喜你,赶紧开工吧

有的时候觉得社区化的技术发展也有它残酷的一面,同类的方案或工具库基本上也就全球数一数二的几个能生存下来,并且体现出可观的价值,稍微差一点的,也许绝对实力没有差很多,但只要不是第一,所产生的价值和影响力就少得可怜了。所以想掂量掂量自己,看看是不是这块料,不妨放到社区里跟大家一起来一场真正的PK吧!也别自己憋着,别觉得“等我弄得再完善一点再给你们看”,因为在社区化的技术发展中,这样做只会让自己越来越没有价值和勇气把它拿出来了。当然也不必贪多,哪怕是一个小小的功能,如果做到最好,能够被全球的开发者使用,那也是极好的

合理的把技术运用在工作中 &ZeroWidthSpace;

我们在工作中逐渐对人的评价,会由评价其绝对的技术实力,转向评价其运用各方面技术解决实际问题的能力。这两者之间有一个比较形象的比喻,就好比我们学生时期背单词,拿出任何一个单词来,都能立刻说出它的含义、发音、常用短语、同义词、反义词、各种形态,但面对具体的对话场景不知道该用哪个词最合适最得体,这是比较典型的一种现象。其实前者就像是一个人的技术实力,后者就像是一个人的解决实际问题的能力。所以新知识新技术不光要学,还要学以致用,如何合理运用技术解决实际问题才是我们真正希望看到的。

把工作中的必经之路和常见任务萃取出来沉淀为最佳实践 &ZeroWidthSpace;

现在回到“小而美的务实方案”这件事情上。我们认为看上去很重要、工作量很大、需要很多人来做的工作,是可以充分拥抱社区,汲取最好的技术,同时发挥关键角色的关键作用,并把技术合理运用在实际需求上。整件事情的成败有很多因素,但是“规模”这个因素显然不在考前的位置了

基于这样的思考,我觉得,自己喜欢的团队,是一个小而美的团队

Vue.js 1.0.0 发布了!

作者 勾三股四
2015年10月29日 15:36

作为“打入敌人内部”的第一件事,转载一下 Vue.js 1.0.0 新世纪福音战士 (其实发这篇文章的时候已经 1.0.3 了) 正式发布的博文 ^_^

Vue.js


Hi HN (Hacker News)! 如果你还不熟悉 Vue.js 的话,可以通过这篇文章 (英文)对其有个总体印象。

在经历了 300+ 次提交、8 次 alpha、4 次 beta 和 2 次 rc 之后,今天我很荣幸的向大家宣布 Vue.js 1.0.0 Evangelion 正式发布了!非常感谢所有参与 API 重设计的同学们——没有来自社区的贡献这是不可能完成的。


模板语法改进 &ZeroWidthSpace;

1.0 的模板语法解决了很多微小的一致性问题,并使得 Vue 的模板更加简洁且易于阅读。最值得留意的新特性就是 v-onv-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 清理 &ZeroWidthSpace;

Vue.js 1.0 的总体目标是使其适用于更大型的项目。这也是很多 API 被弃用的原因。在被弃用的 API 中,除了很少被用及之外,最常见的理由就是它会导致可维护性被破坏。特别是,我们弃用了难以维护的功能,并把组件提炼隔离开,使其不会对项目其它部分产生影响。

比如,在 0.12 中默认资源 (asset) 方案会隐性降级到组件树中的父级。这使得组件中的可用资源非常不确定,并且取决于在运行时的用法。在 1.0 中,所有的资源都基于严格模式进行解析,也没有了隐性降级。inherit 选项也被移除了,因为它很容易导致组件强耦合,无法提炼。

更快的初始化渲染 &ZeroWidthSpace;

1.0 用 v-for 指令 (directive) 取代了 v-repeat。除了提供相同的功能和更直观的作用域之外,v-for 将初始化渲染大列表和大表格时的性能提升了 100%

更强大的工具 &ZeroWidthSpace;

在 Vue.js core 之外,还有很多令人激动的东西:vue-loadervueify 的新升级,包括:

  • 组件热加载。当一个 *.vue 组件被编辑之后,其所有活跃实例都可以在页面不刷新的情况下完成热转换。这意味着你不需要重新加载 app 就可以完成诸如样式或模板的小调整;而 app 本身及其被转换的组件的状态可以被保留,这大大提升了开发体验。

  • 局部 CSS。通过在你的 *.vue 组件的 style 标签上简单加入一个 scoped 特性,该组件的模板和最终生成的 CSS 就会被奇妙的重写以保证组件的样式只被应用在其自身的元素上。最重要的是,父组件的特殊样式不会泄露到嵌套的子组件当中。

  • 默认支持 ES2015。JavaScript 一直在进化。你可以用最新的语法写出更简洁生动的代码。vue-loadervueify 现在会直接转换你的 *.vue 组件中的 JavaScript,无需额外的设置。今天,就来写未来的 JavaScript 吧!

结合 vue-router,Vue.js 现在不只是一个库了——它提供了一个构建复杂单页应用的稳固基础。

下一步? &ZeroWidthSpace;

如一般 1.0.0 所提倡的,核心 API 将会保持稳定服务于可预见的未来,库也做好了产品级别的准备。未来的开发会专注于:

  1. 改善 vue-loader 并使其做好产品级别的准备。

  2. 捋顺开发体验,比如更好的开发者工具和 Vue.js 项目/组件脚手架的 CLI。

  3. 提供更多诸如教程和示例的学习资料。

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

作者 勾三股四
2015年8月10日 15:00

译自 Philip Walton 的博客

看过之后非常有感触,很多观点都是自己长期非常坚持和认同的,所以翻译出来分享给更多的前端同学!


最近我收到一封读者来信让我陷入了思考,信是这么写的:

Hi Philip,您是否介意我问您是如何成为一名卓越 (great) 的前端工程师的?对此您有什么建议吗?

我不得不承认,我很惊讶被问这样的问题,因为我从来不觉得自己是个很卓越的前端工程师。甚至我入行头几年时并不认为自己可以做好这一行。我只确定自己比自己想象中还才疏学浅,而且大家面试我的时候都不知道从何问起

话虽这么说,我到现在做得还算不错,而且成为了团队中有价值的一员。但我最终离开 (去寻求新的挑战——即我还不能够胜任的工作) 的时候,我经常会被要求招聘我的继任者。现在回看这些面试,我不禁感叹当我刚开始的时候自己在这方面的知识是多么的匮乏。我现在或许不会按照我自己的模型进行招聘,即便我个人的这种经历也有可能成功。

我在 web 领域工作越长时间,我就越意识到区分人才和顶尖人才的并不是他们的知识——而是他们思考问题的方式。很显然,知识在很多情况下是非常重要而且关键的——但是在一个快速发展的领域,你前进和获取知识的方式 (至少在相当长的一段时间里) 会比你已经掌握的知识显得更加重要。更重要的是:你是如何运用这些知识解决每天的问题的。

这里有许许多多的文章谈论你工作中需要的语言、框架、工具等等。我希望给一些不一样的建议。在这篇文章里,我想谈一谈一个前端工程师的心态,希望可以帮助大家找到通往卓越的道路。


别光解决问题,想想究竟发生了什么 &ZeroWidthSpace;

很多人埋头写 CSS 和 JavaScript 直到程序工作起来了,然后就去做别的事情了。我通过 code review 发现这种事经常发生。

我总会问大家:“为什么你会在这里添加 float: left?”或者“这里的 overflow: hidden 是必要的吗?”,他们往往答道:“我也不知道,可是我一删掉它们,页面就乱套了。”

JavaScript 也是一样,我总会在一个条件竞争的地方看到一个 setTimeout,或者有些人无意中阻止了事件传播,却不知道它会影响到页面中其它的事件处理。

我发现很多情况下,当你遇到问题的时候,你只是解决当下的问题罢了。但是如果你永远不花时间理解问题的本源,你将一次又一次的面对相同的问题。

花一些时间找出为什么,这看上去费时费力,但是我保证它会节省你未来的时间。在完全理解整个系统之后,你就不需要总去猜测和论证了。

学会预见未来的浏览器发展趋势 &ZeroWidthSpace;

前后端开发的一个主要区别在于后端代码通常都运行在完全由你掌控的环境下。前端相对来说不那么在你的掌控之中。不同用户的平台或设备是前端永恒的话题,你的代码需要优雅掌控这一切。

我记得自己 2011 年之前曾经阅读某主流 JavaScript 框架的时候看到过下面这样的代码 (简化过的):

var isIE6 = !isIE7 && !isIE8 && !isIE9;

在这个例子中变量 IE6 为了判断 IE 浏览器版本是否是 6 或更低的版本。那么在 IE10 发布时,我们的程序判断还是会出问题。

我理解在真实世界特性检测并不 100% 工作,而且有的时候你不得不依赖有 bug 的特性或根据浏览器特性检测的错误设计白名单。但你为此做的每一件事都非常关键,因为你预见到了不再有 bug 的未来。

对于我们当中的很多人来说,我们今天写的代码都会比我们的工作周期要长。有些我写的代码已经过去 8 年多了还在产品线上运行。这让人很满足又很不安。

阅读规范文档 &ZeroWidthSpace;

浏览器有 bug 是很难免的事,但是当同一份代码在两个浏览器渲染出来的效果不一样,人们总会不假思索的推测,那个“广受好评”的浏览器是对的,而“不起眼”的浏览器是错的。但事实并不一定如此,当你的假设出现错误时,你选取的变通办法都会在未来遭遇问题。

一个就近的例子是 flex 元素的默认最小尺寸问题。根据规范的描述,flex 元素初始化的 min-widthmin-height 的值是 auto (而不是 0),也就是说它们默认应该收缩到自己内容的最小尺寸。但是在过去长达 8 个月的时间里,只有 Firefox 的实现是准确的。[1]

如果你遇到了这个浏览器兼容性的问题并且发现 Chrome、IE、Opera、Safari 的效果相同而 Firefox 和它们不同时,你很可能会认为是 Firefox 搞错了。事实上这种情况我见多了。很多我在自己 Flexbugs 项目上报的问题都是这样的。而且这些解决方案的问题会在两周之后 Chrome 44 修复之后被体现出来。和遵循标准的解决方案相比,这些方案都伤害到了正确的规范行为。[2]

当同一份代码在两个或更多浏览器的渲染结果不同时,你应该花些时间确定哪个效果是正确的,并且以此为标准写代码。你的解决方案应该是对未来友好的。

额外的,所谓“卓越”的前端工程师是时刻感受变化,在某项技术成为主流之前就去适应它的,甚至在为这样的技术做着贡献。如果你锻炼自己看到规范就能在浏览器支持它之前想象出它如何工作的,那么你将成为谈论并影响其规范开发的那群人。

阅读别人的代码 &ZeroWidthSpace;

出于乐趣阅读别人的代码可能并不是你每周六晚上会想到的娱乐项目,但是这毫无疑问是你成为优秀工程师的最佳途径。

自己独立解决问题绝对是个不错的方式,但是这不应该是你唯一的方式,因为它很快就会让你稳定在某个层次。阅读别人的代码会让你开阔思维,并且阅读和理解别人写的代码也是团队协作或开源贡献必须具备的能力。

我着实认为很多公司在招聘新员工的时候犯的最大错误是他们只评估应聘者从轮廓开始写新代码的能力。我几乎没有见过一场面试会要求应聘者阅读现有的代码,找出其中的问题,并修复它们。缺少这样的面试流程真的非常不好,因为你作为工程师的很多时间都花费在了在现有的代码的基础上增加或改变上面,而不是搭建新的东西。

与比你聪明的人一起工作 &ZeroWidthSpace;

我印象中的很多前端开发者 (相比于全职工作来说) 都是自由职业者,有同类想法的后端开发者并没有那么多。可能是因为很多前端都是自学成才的而后端则多是学校里学出来的。

不论是自我学习还是自我工作,我们都面对一个问题:你并没有机会从比你聪明的家伙那里学到什么。没有人帮你 review 代码,也没有人与你碰撞灵感。

我强烈建议,最起码在你职业发展的前期,你要在一个团队里工作,尤其是一个普遍比你聪明而且有经验的团队里工作。

如果你最终会在你职业发展的某个阶段选择独立工作,一定要让自己投身在开源社区当中。保持对开源项目的活跃贡献,这会给你团队工作相同甚至更多的益处。

“造轮子” &ZeroWidthSpace;

造轮子在商业上是非常糟糕的,但是从学习的角度是非常好的。你可能很想把那些库和小工具直接从 npm 里拿下来用,但也可以想象一下你独立建造它们能够学到多少东西。

我知道有些人读到这里是特别不赞成的。别误会,我并没有说你不应该使用第三方代码。那些经过充分测试的库具有多年的测试用例积累和已知问题积累,使用它们绝对是非常明智的选择。

但在这里我想说的是如何从优秀到卓越。我觉得这个领域很多卓越的人都是我每天在用的非常流行的库的作者或维护者。

你可能不曾打造过自己的 JavaScript 库也拥有一个成功的职业发展,但是你从不把自己手弄脏是几乎不可能淘到金子的。

在这一行大家普遍会问的一个问题是:我接下来应该做点什么?如果你没有试着学一个新的工具创建一个新的应用,那不妨试着重新造一个你喜欢的 JavaScript 库或 CSS 框架。这样做的一个好消息是,在你遇到困难的时候,所有现成的库的源代码都会为你提供帮助。

把你学到的东西都记录下来 &ZeroWidthSpace;

最后,但丝毫不逊色的是,你应该把你学到的东西记录下来。这样做有很多原因,但也许最重要的原因是它强迫你更好的理解这件事。如果你无法讲清楚它的工作原理,在整个过程中它会推动你自己把并不真正理解的东西弄清楚。很多情况下你根本意识不到自己还不理解它们——直到自己动手写的时候。

根据我的经验,写作、演讲、做 demo 是强迫自己完全深入理解一件事的最佳方式。就算你写的东西没有人看,整个过程也会让你受益匪浅。

Footnotes: &ZeroWidthSpace;

  1. 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.
  2. You can refer to Flexbug #1 for a future-friendly, cross-browser workaround to this issue.

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

作者 勾三股四
2015年8月10日 12:15

本文首发自阿里无线前端博客

注:本文摘自阿里内网的无线前端博客《无线前端的图片相关工作流程梳理》。其实是一个月前写的,鉴于团队在中国第二届 CSS Conf 上做了《手机淘宝 CSS 实践启示录》的分享,而图片工作流程梳理是其中的一个子话题,故在此一并分享出来,希望仍可以给大家一些经验和启发。另外,考虑到这是一篇公开分享,原版内容有部分删节和调整,里面有一些经验和产出是和我们的工作环境相关的,不完全具有普遍性,还请见谅。

今天很荣幸的跟大家分享一件事情,就是经过差不多半年多的努力,尤其是最近 2 周的“突击扫尾”,无线前端团队又在工具流程方面有了一个不小的突破:我们暂且称其为“图片工作流”梳理。

图片!图片!图片! &ZeroWidthSpace;

要说最近 1 年里,无线前端开发的一线同学最“难搞”的几件事,图片处理绝对可以排在前三。

  • 首先,我们首先要从视觉稿 (绝大部分出自 photoshop) 里把图片合理的分解、测量、切割、导出——俗称“切图”
  • 然后,我们要把切好的图放入页面代码中,完成相关的本地调试
  • 第三步,把本地图片通过一个内部网站 (名叫 TPS) 上传到我们的图片 CDN 上,并复制图片的 CDN 地址,把本地调试用的相对路径替换掉
  • 第四步,不同的图片、不同的外部环境下 (比如 3g 还是 wifi),我们需要给图片不一样的尺寸、画质展现,并有一系列的配置需要遵循
  • 如果视觉稿有更改 (不要小看这件事,微观上还是很频繁的哦),不好意思,从第一步开始再重新走一遍……

这里面“难搞”在哪些地方呢?我们逐一分析一下:

  1. “切图”的效率并不高,而且每一步都很容易出现返工或再沟通
  2. 打开 TPS 网站上传图片放到前端开发流程中并不是一个连贯流畅的步骤,而且 GUI 相比于命令行工具的缺陷在于无法和其它工具更好的集成
  3. 替换 CDN 图片路径的工作机械而繁琐,并且代码中替换后的图片地址失去了原本的可读性,非常容易造成后期的维护困惑甚至混乱
  4. 适配工作异常繁杂和辛苦,也很容易漏掉其中的某个环节
  5. 视觉变更的成本高,web 的快速响应的特点在丧失

所以可能把这些东西画成一张图表的话:

团队的单点突破 &ZeroWidthSpace;

在最近半年的一段时间里,无线前端团队先后发起了下面几项工作,从某个点上尝试解决这些问题:


lib.flexible &ZeroWidthSpace;

首先,我们和 UED 团队共同协商约定了一套 REM 方案 (后更名为 flexible 方案,进而演进为 lib.flexible 库),通过对视觉稿的产出格式的约定,从工作流程的源头把控质量,同时在技术上产出了配套的 lib.flexible 库,可以“抹平”不同设备屏幕的尺寸差异,同时对清晰度进行了智能判断。这部分工作前端的部分是 @wintercn 寒老师和 @terrykingcha 共同创建的。

视觉稿辅助工具普及 &ZeroWidthSpace;

其次,我们于去年 12 月开始启动了一个“视觉稿工具效率提升”的开放课题,由团队的 @songsiqi 负责牵头,我们从课题的一开始就确立了 KPI 和 roadmap,经过一段时间的调研和落实,收罗了很多实用的辅助工具帮助我们提升效率,同时布道给了整个团队。比如 cuttermanparkerSize Marks

img-uploader &ZeroWidthSpace;

在 @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 &ZeroWidthSpace;

lib.img 是团队 @chenerlang666 主持开发的一个基础库,它是一套图片自动处理优化方案。可以同时解决屏幕尺寸判断、清晰度判断、网络环境判断、域名收敛、尺寸后缀计算、画质后缀计算、锐化度后缀计算、懒加载等一系列图片和性能相关的问题。这个库的意义和实用性都非常之高,并且始终保持着快速的业务响应和迭代周期,也算是无线前端团队的一个明星作品,也报送了当年度的无线技术金码奖。

px2rem &ZeroWidthSpace;

px2rem 是 @songsiqi 主持开发的另一个小工具,它因 lib.flexible 方案而生,因为我们统一采用 rem 单位来最终记录界面的尺寸,且对于个别1像素边框、文本字号来说,还有特殊的规则作为补充 (详见 lib.flexible 的文档)。

同样的,它也有 gulp / browser 的各种版本。

img4dpr &ZeroWidthSpace;

img4dpr 则是一个可以把 CSS 中的 CDN URL 自动转成 3 种 dpr 下不同的尺寸后缀。算是对 lib.img 的一个补充。如果你的图片不是产生在 <img> 标签或 JavaScript 中,而是写在了 CSS 文件里,那么即使是 lib.img 恐怕也无能为力,img4dpr 恰恰是在解决这个问题。

完事儿了吗? &ZeroWidthSpace;

看上去,团队为团队做了很多事情,每件事情都在单点上有所突破,解决了一定的问题。

但我们并没有为此停止思考

有一个很明显的改进空间在这里:今天我们的前端开发流程是一整套工程链路,每个环节之间都紧密相扣, 解决了单点的问题并不是终点,基于场景而不是功能点的思考方式,才能够把每个环节都流畅的串联起来,才能给前端开发者在业务支持的过程当中提供完美高效畅通无阻的体验——这是我们为之努力的更大的价值!也是我认为真正“临门一脚”的最终价值体现!

基于场景的思维方式 &ZeroWidthSpace;

这种思维方式听上去很玄幻,其实想做到很简单,我们不要单个儿看某个工具好不好用,牛不牛掰,模拟真实工程场景,创建个新项目,从“切图”的第一步连续走到发布的最后一步,看看中间哪里断掉了?哪里衔接的不自然?哪里不完备?哪里重复设计了?哪里可以整合?通常这些问题都会变得一目了然。

首先,在 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) 指的是我有机会在这个时机把图片的域名收敛/尺寸/画质/锐化度等需求处理掉。

经过这样一整理,我们很容易发现问题:

  1. 图片上传存在很多种可选的时机,并没有形成最佳实践
  2. 有些链路完全没有机会做必要的处理 (如 to HTML -> src 的链路无法优化图片地址)
  3. 有些链路处理图片的逻辑并不够智能 (比如需要手动确定优化图片选项的链路)
  4. 图片上传 CDN 之后必须手动替换掉源代码里的图片路径,这个问题在任何一个链路里都没有得到解决
  5. CSS 相关的小工具很多,比较零散,学习和使用的成本在逐步变高变复杂
  6. 没有统一完善的项目脚手架,大家创建新项目都需要初始化好多小工具的 gulp 配置 (当然有个土办法就是从就项目里 copy 一份 package.json 和一份 gulpfile.js)

基于场景的“查漏补缺” &ZeroWidthSpace;

在完善场景的“最后一公里”,我们做了如下的工作:

  1. 把所有的 CSS 工具集成到了 postcss,再通过 postcss 的 gulp 插件、webpack 插件、browserify 插件令其未来有机会灵活运用到多种场景而不需要做多种工具链的适配,即:postcss-px2rem、postcss-img4dpr,同时额外的,借此机会引入 postcss-autoprefixer,让团队拜托旧的 webkit 前缀,拥抱标准的写法
  2. 把图片上传的时机由最早的 or-imgex-gulp 在最后阶段分析网页的html/css代码上传替换其中的图片,变为在 images 目录下约定一个名为 _cdnurl.json 的文件,记录图片的 hash 值和线上 CDN 地址,并写了一个 @ali/gulp-img-uploader 的 gulp 插件,每次运行的时候会便利 images 文件夹中的图片,如果出现新的 hash 值,就自动上传到 CDN,并把相应生成的 CDN URL 写入 _cdnurl.json
  3. 同时,这个文件可以引入到页面的 JavaScript 环境中,引入到 img4dpr 工具中,引入到 lib.img 的逻辑中,让 HTML/CSS/JavaScript 的各种使用图片的场景都可以访问到 _cdnurl.json 中记录的本地图片路径和线上地址的对应关系
  4. 这也意味着 lib.img, img4dpr 需要做相应的改动,同时
  5. 页面本身要默认把 _cdnurl.json 的信息引入以做准备
  6. 创建一个 lib.cdnurl 的库,在图片未上传的情况下,返回本地路径,在已经上传的情况下,返回 CDN URL,这样通过这个库作支持,外加 lib.img、img4dpr,开发者可以做到在源代码中完全使用本地路径,源代码的可读性得到了最大程度的保证
  7. 基于 adam 创建一个包含全套工具链路的项目模板 (脚手架)

上述几件事我们于上周一做了统一讨论和分工,这里要感谢 @mingelz @songsiqi @chenerlang666 的共同努力!!

夹带私货 (偷笑) &ZeroWidthSpace;

我在这个过程中,融入了之前一段时间集中实践的 vue 和 webpack 的工程体系,在 vue 的基础上进行组件化开发,在 webpack 的基础上管理资源打包、集成和发布,最终合并在了最新的 just-vue 的 adam template 里面。

之前不是在文章的最后卖了个“最后一公里”的关子吗,这里介绍的图片工作流改进就是其中的一部分:)

同时,我基于 lib.img 的思路,结合 vue.js 自身的特点,写了一个 v-src 的 directive,在做到 lib.img 里 [data-src] 相同目的的同时,更好的融入了 vue.js 的体系,同时加入了更高集成度的功能,稍后会再介绍。

夹带了私货之后是不是我就没法用了?

最后我想强调的是,除了自己的这些“私货”之外,上面提到的几个改进点和这些个人的内容是完全解耦的,如果你不选择 vue.js 或 webpack 而是别的同类型工具或自己研发的一套工具,它依然可以灵活的融入你的工作流程中。

最终效果 &ZeroWidthSpace;

我们在团队内部把这些工作流程以脚手架的方式进行了沉淀,并放在了团队内部叫做 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

目录结构剖析 &ZeroWidthSpace;

然后大家会看到项目目录里默认就有:

  • 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 里的细节,这才是真正让你真切感受到未来开发体验的地方。

图片工作场景 &ZeroWidthSpace;

首先,新产生任何图片,尽管丢到 images 目录,别忘了起个好理解的文件名

CSS 中的图片 &ZeroWidthSpace;

然后,在 main.vue 的第 11 行看到了一个 CSS 的 background-image 的场景,我们只是把 url(../images/taobao.jpg) 设为其背景图片:

background-image: url(../images/taobao.jpg);

完成了!就这样!你在发布之前不需要再关注额外的事情了。没有手动上传图片、没有另外的GUI、没有重命名、没有 CDN 地址替换、没有图片地址优化、没有不可读的代码

HTML 中的图片 &ZeroWidthSpace;

我们再来看看 HTML 里的图片,来到 39 行:

<img id="test-img" v-src="../images/one.png" size="cover">

一个 [v-src] 特性搞定!就这样!你在发布之前不需要再关注额外的事情了 (这里 [size] 特性提供了更多的图片地址优化策略,篇幅有限,大家感兴趣可以移步到 lib/vue-src.js 看其中的实现原理)。

JavaScript 中的图片 &ZeroWidthSpace;

最后再看看在 JavaScript 里使用图片,来到 68 行:

this.$el.style.backgroundImage = 'url(' + cdn('../images/logo.png') + ')'

只加入了一步 cdn(...) 的图片生成,也搞定了!就这样!你在发布之前不需要再关注额外的事情了。

发布 &ZeroWidthSpace;

那有人可能会怀疑: “那你都说发布之前很方便,发布的时候会不会太麻烦啊?”

好问题,发布就两行命令:

# 图片增量上传、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

最终这个初始化工程的示例页面的效果如下

设计变更了? &ZeroWidthSpace;

这条链路是我们之前最不愿意面对的,今天,我们来看看这条链路变成了什么,假设有一张设计图要换:

  1. 在 Photoshop 里把图重新切下来
  2. 同名图片文件放入 images 文件夹
  3. 运行 gulp && awp

就这样!

额外的,如果尺寸有变化,就加一步:更改相应的 CSS 尺寸代码

总结 &ZeroWidthSpace;

在整个团队架构的过程中,大家都在不断尝试,如何以更贴近开发者真实场景的方式,还原真实的问题,找出切实有效的解决方案,而不仅仅是单个功能或特性。这样我们往往会找到问题的关键,用最精细有效的方式把工作的价值最大化。其实“基于场景的思维方式”不只是流程设计的专利,我们业务上的产品设计、交互设计更需要这样的思维。我个人也正是受到了一些产品经理朋友们的思维方式的影响,把这种方式运用在了我自己的工作内容当中。希望我们产出的这套方案能够给大家创造一些价值,更是向大家传递我们的心得体会,希望这样的思维方式和做事方式可以有更多更广的用武之地。

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

作者 勾三股四
2015年8月2日 12:53

来来来,看看办公室政治是个什么东西,以及如何将其最小化
翻译如有疏漏还请指正

译自:How to Minimize Politics in Your Company via www.bhorowitz.com

更新:跟身边一些朋友讨论之后,觉得之前翻译的标题“杜绝”言过了,还是规规矩矩翻译成了“最小化”


Who the f@#k you think you f$&kin’ with
I’m the f%*kin’ boss

—Rick Ross, Hustlin'

在我所有的从商经历中,我从没听过有人说:“我喜欢办公室政治”。但在我们的周围,令人深恶痛绝的政治又到处都是,甚至自己的公司就是如此。既然大家都不喜欢政治,那为什么它无处不在呢?

政治行为几乎都源自 CEO。也许你会觉得:“我讨厌政治,我也不关心政治,但是我的周围充满了政治气味。这显然不是我造成的。”很遗憾,你并不需要怎么关心政治就会让你的周围充斥政治手段。实际上,很少关心政治的 CEO 才会让办公室充斥政治手段。不关心政治的 CEO 们往往会直接助涨政治行为。

我这里说的政治,就是指员工追求自我职业发展多于价值产出和贡献。也许还有别样的政治类型,但是这类政治行为真的很烦。


为什么会这样 &ZeroWidthSpace;

CEO 无意识的激励甚至有时刺激了政治行为,办公室政治由此而生。举个非常简单的例子,我们想象一下薪酬决策。作为一个 CEO,资深的员工会反复找你索要加薪。他们会提醒你自己得到的回报已经比市场行情低多了。他们甚至已经手握外面的 offer 了。你大可给他们加个薪。这听起来没什么问题,但你就这样强烈刺激了大家的政治行为。

尤其是你在为一些对你的业务毫无价值的东西做奖励,员工会在你主动为他们的杰出表现嘉奖之前赚取更多的回报。为什么这样做很糟糕?我们仔细分析一下:

  1. 其他“好胜”的同学会立刻感到不爽。注意不管是这种不爽还是之前的奖励都和实际的业绩毫无关系。你得花很多精力处理这些跟业绩无关的鸟事。重要的是,如果你有能力上限 (competent board) 的话,你不可能给每个人预期之外的涨薪,所以这会变成一件“先到先得”的事情。
  2. 比较“与世无争”的 (但有可能很给力的) 同学会因为不关心政治而失去期末的涨薪。
  3. 公司全员都上了一课:会哭的孩子有奶吃,关心政治的人会加薪。让暴风雨来的更猛烈些吧。

现在我们来到一个更复杂的例子。你的 CFO 找到你说他希望在管理方面更进一步。他说他希望最终成为 COO,想了解自己需要具备什么样的技能才能胜任公司的这一职位。作为一个积极的主管,你可能会鼓励他实现自己的梦想。你告诉他你觉得他将来一定会是一个合格的 COO,并且应该开发某些方面的技能。额外的,你告诉他应该在管理上变得够强,这样其他高管 (executes) 才会愿意为他工作。一周之后,你的另一个高管就来找你诉苦了。她说 CFO 问她愿不愿意为他工作。她说你看好他成为最终的 COO。你之前遇到过这种事情吗?恭喜你摊上大事儿了。

如何政治最小化 &ZeroWidthSpace;

专家 vs. 菜鸟 &ZeroWidthSpace;

避免政治往往会觉得不自然。它挑战了诸如开明思想或鼓励员工发展等管理最佳实践。

管理高层和初级雇员的不同好比跟业余选手和职业拳手过招的不同。如果你跟一个普通人交手,你尽可自然为之不必担心。如果你想退一步,你可以先抬起你迈在前面的一只脚。如果你对垒一位专业拳手,估计就被击到了。职业拳手经过了年复一年的训练,他们善于利用你的每一处微小的失误。先抬起你迈在前面的一只脚向后退会让你在那一瞬间失去重心,这就是你的对手一直等待的机会。

同样的,如果你管理一名初级雇员,他们跟你探讨职业发展的时候,你大可忘掉那些顾虑随性作答。但就像我们之前看到的,在对待那些高度敏感的老家伙时就不一样了。为了不被政治手段击倒,你需要在这方面提炼自己的技巧。

技巧 &ZeroWidthSpace;

我作为一个 CEO 发展至今,我发现三条非常有效的将政治最小化的秘诀

1. 雇佣有正确目标的人

我之前描述的例子可能卷入了有目标,但本质并不关心政治的人。并不是所有的情况都是这样的。毫无疑问,把你的公司政治搞成美国参议院级别的方式就是雇佣错误目标的人。正如 Andy Grove 所说,正确的目标是把主管的个人成功和公司成功和公司产品的胜利息息相关。错误的目标是把主管的个人成功和公司的收入划清界限。

2. 为潜在的政治行为建立严格的机制并坚持贯彻

某些行动会助涨政治,比如这三点:

  • 绩效评估和薪酬调整
  • 组织结构设计与调整
  • 晋升

我们来审视在每一种情况下,你该如何制定程序来杜绝不好的行为和政治的动机。

绩效管理和薪酬调整

公司的绩效管理和薪酬调整通常都有一些滞后。这并不意味着他们没有认可员工或不给员工加薪,这仅仅是因为他们仓促特许此事在政治手段面前是非常脆弱的。通过规范合理的结构、正规的绩效评估和薪酬评估,你会在更高的高度明确薪水和股票的涨幅情况。尤其对高管的薪酬调整尤为重要,因为这样做会杜绝政治。在上面的例子中,CEO 应该有一套滴水不漏的绩效和薪酬政策,并且跟高管明确他的薪酬会被其他所有人评估。理想状态下,高管的薪酬体系应该有董事会的参与。这会 a) 有助于更好的管理 b) 让意外更难出现。

组织结构设计与调整

如果你管理高级员工,他们会一次又一次希望扩展自己的职责范围。在上面的例子中,CFO 希望成为 COO。在其它情形下,市场的一把手都希望把销售和市场一起运作起来,或工程的一把手希望把研发和产品管理都握在手上。当有些人向你提出类似的要求时,你要非常小心作答,因为你所讲的每一句话都会变成定时炸弹。一般情况下最好什么都别说。最多问问为什么,并且要牢记不要对对方提及的原因做出任何回应和解释。如果你表明了你的想法,那它一定会被传出去的,谣言会变得到处都是,你的周围会被业务无关的讨论所淹没。你应该基于常规的考虑评估你的组织结构,为了做出正确的决定,你可以获取必要的信息,但不要把你的计划透露或暗示出去。一旦你做了决定,那么就立刻执行组织结构调整:别让谣言先传到园区里。

晋升

每次你的公司提拔某些人的时候,其他同级别的人都会对此指指点点,探讨这个人是因为业绩好还是会来事儿才得到晋升的。如果答案是后者,那么其他人的反应无外乎是下面这三种:

  1. 他们闷闷不乐,感觉被低估了
  2. 他们表现出不认同,跟这个人对着干,并在背后使坏
  3. 他们决定也学这个人的政治手段,因此产生了更多不合理的晋升

很明显哪种行为你都不希望看到。因此你必须有一个正式的、透明的、有正当理由的晋升流程来决定每个人的晋升。通常这个流程是由其他团队成员参与的 (一般晋升流程要让其他主管参与,这些主管的工作性质和这个人类似,高管的晋升流程里应该有董事会的参与)。这个流程的目的是两面的。一方面它会让组织相信公司至少是基于业绩进行晋升评估的,另一方面流程的结果足以充分解释你的晋升。

3. 小心别人打来的“小报告”

一旦你的组织壮大到一定规模,团队成员就会不断相互投诉和抱怨。有时这些批评是非常激进的。要非常小心留意你听到的话以及它背后传递的信息。如果单纯的没有任何防备的解答员工提出的问题,你很容易把你内心认同的信息传递出去。如果大家在公司认为你觉得某个高管不够好,这样的信息会迅速传播开来,并且不会有人求证真相的。最后,大家都不再相信那个“问题高管”,做事也不再有效率。

这里有两种典型的你会听到的抱怨:

  1. 抱怨一个高管的行为
  2. 抱怨一个高管的能力和业绩

对于第一种问题,一般最好的处理方式就是找投诉和被投诉的双方高管拉到一个小黑屋里当面把事情解释清楚。通常一个当面的沟通就可以挽回冲突和错误 (如有)。不要试图隔空解决问题,那样只会带来问题和政治。

第二种问题会更少见同时处理起来也更复杂。如果你的一个高管鼓起勇气质疑他同伴的能力,那么很有可能,这两个人之间有很严重的问题。如果你遇到了这种问题,你一般会得到下面两种回复中的一种:a) 你会从他们那里得知一些你已经知道的事情,或者 b) 他们会给你“惊喜”。

如果他们告诉你的是你已经知道的事情,那么最主要的问题就是你已经让这个问题存在太久了。不论你迟迟不解决问题的理由是什么,你把这件事拖太久了导致现在你的团队已经向这名主管发难。你必须用最快速度解决这个问题。基本上你是要解雇这名高管了,因为我看到过的高官们只有提升得自己业绩和技能的,却从没有重拾起团队的信任和支持的。

而如果你收到的是信息是你从未了解过的,那么你必须立刻打断他,让这位抱怨别人的高管明确,你没办法确定这个评判。你不希望在重新审视他的表现之前就做处理。你也不希望大家觉得投诉是万能的。一旦你终止了谈话,你必须立刻重新审视这名被抱怨的员工。如果你发现他表现的很好,那么你必须找出抱怨他的人的动机并把它处理好,不要让这种言论占上风。如果你发现这个员工确实有问题,那么再回到抱怨他的人提供的信息,这时你应该明确处理表现不好的人。

总结 &ZeroWidthSpace;

作为 CEO,你必须系统的考虑自己的一言一行导致的结果。开放,负责,目标导向才是王道,尽量避免错误的激励方式。

入手新手机一部

作者 勾三股四
2009年7月23日 20:19

本文摘自 勾三股四 更早时期的 不老歌 博客。


Nokia 1202


虽然型号很低,但拥有好多先进智能手机没有的优点:
1. 单色白屏,节能省电,待机时间超长;
2. 短小精干,操作简单,低辐射;
3. 铃声够大,字体够大,可以用到老年…… 囧rz;
4. 价钱便宜。

再搭配上我的小i~ 无敌鸟!
有谁想体验一下由 Nokia 1202 的 mic 传出去的声音的,可以给我打个电话感受一下~
每体验一次,您将为中国电信捐出一份爱心

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

作者 勾三股四
2009年4月14日 06:28

本文摘自 勾三股四 更早时期的 不老歌 博客。


找了好久,精挑细选,我选择了在不老歌开始新的网志生活。

前阵子被人说自己是火星人,只用QQ聊天,只懂得专研代码,就知道踢球;不爱看小说,不爱看电影,不会做饭,不爱逛街,也不怎么用电话。总之一点都不像正常人

我想了想,好像勉强属实。但又一想,以前的自己似乎不完全是这样的——最起码,从前的我,还总跟人讲电话发短信,人缘不错也爱凑热闹,每个礼拜都给家里的老爸老妈通电话问寒暖报平安的。

或许以前的自己真的太累了,想休息了。我逐渐喜欢比较清静的环境,喜欢一个人在夜深人静的时候默默思考,写点东西。
当世界的纷乱在我们的眼中还可以用缤纷来形容时,一切都是那么会让人蠢蠢欲动。

在校内网崩坏的那一刻,
在之前的烂摊子逐渐冷却的那一刻,
在脚踝在篮球场上扭伤的那一刻,
在夜深人静诚实分析自己的那一刻,我突然觉得这里好惬意。

你们也喜欢这里吗?

❌
❌