diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..0beb0e7dec6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hi@xitu.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/README.md b/README.md index 0916246a4b9..83caa8fa331 100644 --- a/README.md +++ b/README.md @@ -5,48 +5,48 @@ [掘金翻译计划](https://juejin.im/tag/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [437](#近期文章列表) 篇文章,共有 [270](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [556](#近期文章列表) 篇文章,共有 [330](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 -# 官方指南: +# 官方指南 -- [**推荐优质英文文章到掘金翻译计划**](https://github.com/xitu/gold-miner/issues/new?title=推荐优秀英文文章&body=-\ 原文链接:%0A-\ 简要介绍:%0A) +- [**推荐优质英文文章到掘金翻译计划**](https://github.com/xitu/gold-miner/issues/new?title=推荐优秀英文文章&body=-%20原文链接:%0A-%20简要介绍:介绍一下好不好啦,毕竟小编也看不太懂哎_(:з」∠)_) - [如何参与翻译](https://github.com/xitu/gold-miner/wiki/如何参与翻译) - [十万个为什么](https://github.com/xitu/gold-miner/wiki/十万个为什么) - [译文排版规则指北](https://github.com/xitu/gold-miner/wiki/译文排版规则指北) - [参与翻译所得的积分能做什么?](https://github.com/xitu/gold-miner/wiki/) +# 合作伙伴 + +- [虚位以待]() # 近期文章列表 ## Android -* [拉模式和推模式,命令式和响应式 – 响应式编程 [Android RxJava2](这到底是什么):第二部分](https://juejin.im/post/58d78547a22b9d006465ca57/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([XHShirley](https://github.com/XHShirley) 翻译) -* [离线支持:不再『稍后重试』](https://juejin.im/post/58d491a8128fe1006cb6e750/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([skyar2009](https://github.com/skyar2009) 翻译) -* [当发布安卓开源库时我希望知道的东西](https://juejin.im/post/58d247b00ce4630057e92e9c/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([jifaxu](https://github.com/jifaxu) 翻译) -* [单元测试试图告诉我们关于 Activity 的什么事情:第二部分](https://gold.xitu.io/entry/58cf988b2f301e007e54c434/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([tanglie1993](https://github.com/tanglie1993/) 翻译) +* [Kotlin:小菜一碟](https://juejin.im/entry/5956f47a5188250d9848abe7/detail?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([CACppuccino](https://github.com/CACppuccino) 翻译) +* [说服Kotlin编译器代码安全](https://juejin.im/post/5955a78cf265da6c2810a9ff?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([wilsonandusa](https://github.com/wilsonandusa) 翻译) +* [当设计模式遇上 Kotlin](https://juejin.im/post/594b2ac00ce4630057425bd5?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([boileryao](https://github.com/boileryao) 翻译) +* [Kotlin 中我讨厌什么](https://juejin.im/entry/594335c18d6d810058ce06a0/detail?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Zhiw](https://github.com/Zhiw) 翻译) * [所有 Android 译文>>](https://github.com/xitu/gold-miner/blob/master/android.md) - ## iOS -* [原生 iOS(Swift) 和 React-Native 的性能比较](https://juejin.im/post/58ca6d5f44d90400682a215c/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([DeepMissea](https://github.com/DeepMissea)) 翻译) -* [如果只有一个月入门 iOS:我该如何学习呢?](https://juejin.im/post/58c9f436a22b9d0064187e39/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Gocy015(Gocy)](https://github.com/Gocy015) 翻译) -* [在 Xcode 项目中使用 swift package fetch](https://juejin.im/post/58c7a4cb1b69e6006bec354c/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Gocy015(Gocy)](https://github.com/Gocy015)翻译) -* [Bob,函数式编程是什么鬼?](https://juejin.im/post/58c24c35a22b9d00589a56d4/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([DeepMissea](https://github.com/DeepMissea)翻译) +* [把 UUID 或者 GUID 作为主键?你得小心啦!](https://juejin.im/post/59561e5b6fb9a06bbf6fdf16?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([zaraguo](https://github.com/zaraguo) 翻译) +* [iOS 11:机器学习人人有份](https://juejin.im/post/59533049f265da6c3a54b739?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([changkun](https://github.com/changkun) 翻译) +* [Swift 中关于并发的一切:第一部分 — 当前](https://juejin.im/post/5954571af265da6c4c4fc3f8?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([DeepMissea](https://github.com/DeepMissea) 翻译) +* [iOS 11:UIKit 中值得注意的新能力](https://juejin.im/post/5944adfffe88c2006a6f6f3a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([zhangqippp](https://github.com/zhangqippp) 翻译) * [所有 iOS 译文>>](https://github.com/xitu/gold-miner/blob/master/ios.md) ## 前端 -* [生活在 JavaScript 之中:学习第二门语言的好处](https://juejin.im/post/58d908deac502e0058db544b/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([gy134340](http://gy134340.com/) 翻译) -* [震惊,还可以用这种姿势学习编程](https://juejin.im/post/58d3ebd4128fe1006cb43722/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([iloveivyxuan](https://github.com/iloveivyxuan) 翻译) -* [如何让你的 React 应用完全的函数式,响应式,并且能处理所有令人发狂的副作用](https://juejin.im/post/58d3287f61ff4b006cb3f56d/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ZhangFe](https://github.com/ZhangFe) 翻译) -* [编写整洁 CSS 代码的黄金法则](https://juejin.im/post/58d34bed128fe1006caf8b6b/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([reid3290](https://github.com/reid3290) 翻译) +* [React 在服务端渲染的实现](https://juejin.im/post/595b01ad6fb9a06bb15a13b2?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([MuYunyun](https://github.com/MuYunyun) 翻译) +* [V8 性能优化杀手](https://juejin.im/post/5959edfc5188250d83241399?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([lsvih](https://github.com/lsvih) 翻译) +* [如何充分利用 JavaScript 控制台](https://juejin.im/post/59510ac45188250d8860c908?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([sunui](https://github.com/sunui) 翻译) +* [统一样式语言](https://juejin.im/post/595311fa6fb9a06b9d5b5f08?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ZhangFe](https://github.com/ZhangFe) 翻译) * [所有前端译文>>](https://github.com/xitu/gold-miner/blob/master/front-end.md) - ## React -* [Node.js 支持 ES6 模块的进展](http://gold.xitu.io/entry/58b393f08d6d8100586955fa?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([hikerpig](https://github.com/hikerpig) 翻译) * [[译] 实践 Redux,第 1 部分: Redux-ORM 基础](https://gold.xitu.io/entry/58249792a0bb9f0058dd30ab?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([luoyaqifei](https://github.com/luoyaqifei) 翻译) * [[译] 如何用 React 完成图片上传功能?](https://gold.xitu.io/entry/57b923225bbb50005b794943?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([DeadLion](https://github.com/DeadLion) 翻译) * [[译] 为你定制的 React 学习路线](https://gold.xitu.io/entry/578375b85bbb5000610cc247?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([aleen42](http://aleen42.github.io/pc.html) 翻译) @@ -55,10 +55,10 @@ ## 后端 -* [在你沉迷于包的海洋之前,还是了解一下运行时 Node.js 的本身](https://juejin.im/post/58cf4a3144d90400690b7be7/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([fghpdf](https://github.com/fghpdf) 翻译) -* [Pull request review 的十大错误](https://juejin.im/post/58ce3b3e61ff4b006c988f63/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([luoyaqifei](https://github.com/luoyaqifei) 翻译) -* [震惊!RxJava 5 个不为人知的小秘密](https://juejin.im/post/58cb833b8ac247218c2632e5/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([skyar2009](https://github.com/skyar2009) 翻译) -* [跨站请求伪造已死!](https://juejin.im/post/58c669b6a22b9d0058b3c630?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([XatMassacrE](https://github.com/XatMassacrE) 翻译) +* [如何在无损的情况下让图片变的更小](https://juejin.im/post/5959fbe0f265da6c2518d740?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([XatMassacrE](https://github.com/XatMassacrE) 翻译) +* [在 Reddit 中代码部署的演进](https://juejin.im/entry/594b7fd21b69e60062a4cb01/detail?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([steinliber](https://github.com/steinliber) 翻译) +* [REST 2.0 在此,它的名字叫 GraphQL](https://juejin.im/post/5947b45c128fe1006a505189?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([mnikn](https://github.com/mnikn) 翻译) +* [用 Go 语言理解 Tensorflow](https://juejin.im/post/59420951128fe1006a1960f8?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([lsvih](https://github.com/lsvih) 翻译) * [所有后端译文>>](https://github.com/xitu/gold-miner/blob/master/backend.md) ## 教程 @@ -72,17 +72,17 @@ ## 设计 -* [某些2017年的 UX 趋势啊,扎心了](https://juejin.im/post/58cf5dc22f301e007e52fb2b/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Ruixi](https://github.com/Ruixi) 翻译) -* [像开发人员一样做设计](https://gold.xitu.io/entry/58b7ba6f8fd9c56d16be6bb0/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Airmacho](https://github.com/Airmacho) 翻译) -* [设计预期的用户体验](https://gold.xitu.io/entry/58b2d8e9570c3500696f53a5/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([jifaxu](https://github.com/jifaxu) 翻译) -* [优秀产品背后的设计原则](https://gold.xitu.io/entry/58b04c49570c35006960d764/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([jifaxu](https://github.com/jifaxu) 翻译) +* [使登录页面变得正确](https://juejin.im/post/5951e7905188250d98489c6a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([LisaPeng](https://github.com/LisaPeng) 翻译) +* [可口可乐自由风格售卖亭界面用户体验的回顾和重新设计](https://juejin.im/post/594bd720f265da6c4c4fb134?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ylq167](https://github.com/ylq167) 翻译) +* [为复杂产品制定设计规范](https://juejin.im/post/5944b8e55c497d006bdc261a?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([changkun](https://github.com/changkun) 翻译) +* [是的,重新设计](https://juejin.im/post/5940e74fa0bb9f006b76a841?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([dongpeiguo](https://github.com/dongpeiguo) 翻译) * [所有设计译文>>](https://github.com/xitu/gold-miner/blob/master/design.md) ## 产品 +* [针对失败者的体验设计](https://juejin.im/post/59013f6eda2f60005de40516/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ylq167](https://github.com/ylq167) 翻译) +* [细节是产品设计的重中之重](https://juejin.im/post/58ed96aaa22b9d00634732e9/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([iloveivyxuan](https://github.com/iloveivyxuan) 翻译) * [单元测试,精益创业,以及两者之间的关系](https://juejin.im/post/58d90a3b44d90400694505c4/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([gy134340](http://gy134340.com/) 翻译) * [你正在阅读的用户体验文章是不是在向你进行推销?](https://juejin.im/post/58d4c501a22b9d00645544d9/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([ylq167](https://github.com/ylq167) 翻译) -* [直观设计 vs. 共享式设计](https://gold.xitu.io/entry/5862650a128fe1006d04d398/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([Funtrip](https://www.behance.net/Funtrip) 翻译) -* [为何而设计?](https://gold.xitu.io/entry/5857969761ff4b00686ad66b/?utm_source=gold-miner&utm_medium=readme&utm_campaign=github) ([王子建](https://github.com/Romeo0906) 翻译) * [所有产品译文>>](https://github.com/xitu/gold-miner/blob/master/product.md) diff --git a/TODO/11-things-i-learned-reading-the-flexbox-spec.md b/TODO/11-things-i-learned-reading-the-flexbox-spec.md new file mode 100644 index 00000000000..726b429bc66 --- /dev/null +++ b/TODO/11-things-i-learned-reading-the-flexbox-spec.md @@ -0,0 +1,281 @@ +> * 原文地址:[11 things I learned reading the flexbox spec](https://hackernoon.com/11-things-i-learned-reading-the-flexbox-spec-5f0c799c776b) +> * 原文作者:本文已获原作者 [David Gilbertson](https://hackernoon.com/@david.gilbertson) 授权 +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者:[XatMassacrE](https://github.com/XatMassacrE) +> * 校对者:[zaraguo](https://github.com/zaraguo),[reid3290](https://github.com/reid3290) + +# 读完 flexbox 细则之后学到的 11 件事 + +在经历了多年的浮动布局和清除浮动的折磨之后,flexbox 就像新鲜空气一般,使用起来是如此的简单方便。 + +然而最近我发现了一些问题。当我认为它不应该是弹性的时候它却是弹性的。修复了之后,别的地方又出问题了。再次修复之后,一些元素又被推到了屏幕的最右边。这到底是什么情况? + +当然了,最后我把它们都解决了,但是黄花菜都凉了而且我的处理方式也基本上没什么规范,就好像那个砸地鼠的游戏,当你砸一个地鼠的时候,另一个地鼠又冒出来,很烦。 + +不管怎么说,我发现要成为一个成熟的开发者并且真正地学会 flexbox 是需要花时间的。但是不是再去翻阅另外的 10 篇博客,而是决定直接去追寻它的源头,那就是阅读 [The CSS Flexible Box Layout Module Level 1 Spec](https://www.w3.org/TR/css-flexbox-1/)。 + +下面这些就是我的收获。 + +### 1. Margins 有特别的功能 + +我过去常常想,如果你想要一个 logo 和 title 在左边,sign in 按钮在右边的 header ... + +![](https://cdn-images-1.medium.com/max/800/1*Y1xY5s_DFPRaZzTwpfb_WQ.png) + +点线为了更清晰 + +... 那么你应该给 title 的 flex 属性设置为 1 就可以把其他的条目推到两头了。 + +``` +.header { + display: flex; +} +.header .logo { + /* nothing needed! */ +} +.header .title { + flex: 1; +} +.header .sign-in { + /* nothing needed! */ +} +``` + +这就是为什么说 flexbox 是个好东西了。看看代码,多简单啊。 + +但是,从某种角度讲,你并不想仅仅为了把一个元素推到右边就拉伸其他的元素。它有可能是一个有下划线的盒子,一张图片或者是因为其他的什么元素需要这样做。 + +好消息!你可以不用说“把这么条目推到右边去”而是更直接地给那个条目定义 `margin-left: auto`,就像 `float: right`。 + +举个例子,如果左边的条目是一张图片: + +![](https://cdn-images-1.medium.com/max/800/1*hFLefXP4fsgnFDIjPIcrTQ.png) + +我不需要给图片使用任何的 flex,也不需要给 flex 容器设置 `space-between`,只需要给 'Sign in' 按钮设置 `margin-left: auto` 就可以了。 + +``` +.header { + display: flex; +} +.header .logo { + /* nothing needed! */ +} +.header .sign-in { + margin-left: auto; +} +``` + +你或许会想这有一点钻空子,但是并不是,在 [概述](https://www.w3.org/TR/css-flexbox-1/#overview) 里面**这个**方法就是用来将一个 flex 条目推到 flexbox 的末端的。它甚至还有自己单独的章节,[使用 auto margins 对齐](https://www.w3.org/TR/css-flexbox-1/#auto-margins)。 + +哦对了,我应该在这里添加一个说明,在这篇博客中我会假设所有的地方都设置了 `flex-direction: row`。但是对于 `row-reverse`,`column` 和 `column-reverse` 也都是适用的。 + +### 2. min-width 问题 + +你或许会想一定有一个直截了当的方法确保在一个容器中所有的 flex 条目都适应地收缩。当然了,如果你给所有的条目设置 `flex-shrink: 1`,这不就是它的作用吗? + +还是举例说吧。 + +假设你有很多的 DOM 元素来显示出售的书籍并且有个按钮来购买它。 + +![](https://cdn-images-1.medium.com/max/800/1*kx1Xl4o5at3whroR9gB0Dw.png) + +(剧透:蝴蝶最后死了) + +你已经用 flexbox 安排地很好了。 + +``` +.book { + display: flex; +} +.book .description { + font-size: 30px; +} +.book .buy { + margin-left: auto; + width: 80px; + text-align: center; + align-self: center; +} +``` + +(你想让 'Buy now' 按钮在右边,即使是很短的标题的时候,那么你就要给他设置 `margin-left: auto`。) + +这个标题太长了,所以他占用了尽可能多的空间,然后换到了下一行。你很开心,生活真美好。你洋洋得意地将代码发布到生产环境并且自信地认为没有任何问题。 + +然后你就会得到一个惊喜,但不是好的那种。 + +一些自命不凡的作者在标题中用了一个很长的单词。 + +![](https://cdn-images-1.medium.com/max/800/1*skXsBLXnoul3J64xKb1HmA.png) + +那就完了! + +如果那个红色的边框代表手机的宽度,并且你隐藏了溢出,那么你就失去你的 'Buy now' 按钮。你的转换率,可怜的作者的自我感觉都会遭殃。 + +(注:幸运的是我工作的地方有一个很棒的 QA 团队,他们维护了一个拥有各种类似于这样的令人不爽的文本的数据库。也正是这个问题特别的促使我去阅读这些细则。) + +就像图片展示的那样,这样的表现是因为描述条目的 `min-width` 初始被设置为 `auto`,在这种情况下就相当于 **Electroencephalographically** 这个单词的宽度。这个 flex 条目就如它的字面意思一样不允许被任何的压缩。 + +那么解决办法是什么呢?重写这个有问题的属性,将 `min-width: auto` 改为 `min-width: 0`,给 flexbox 指明了对于这个条目可以比它里面的内容更窄。 + +这样就可以在条目里面处理文本了。我建议包裹单词。那么你的 CSS 代码就会是下面这个样子: + +``` +.book { + display: flex; +} +.book .description { + font-size: 30px; + min-width: 0; + word-wrap: break-word; +} +.book .buy { + margin-left: auto; + width: 80px; + text-align: center; + align-self: center; +} +``` + +这样的结果就是这个样子: + +![](https://cdn-images-1.medium.com/max/800/1*lM96U8XNZJEGPrVwqJk91w.png) + +重申一下,`min-width: 0` 不是什么为了特定结果取巧的技术,它是[细则中建议的行为 ](https://www.w3.org/TR/css-flexbox-1/#min-size-auto)。 + +下个章节我会处理尽管我明确写明了但是 ‘Buy now’ 按钮仍然不总是 80px 宽的问题。 + +### 3. flexbox 作者的水晶球 + +就像你知道的,`flex` 属性其实是 `flex-grow`,`flex-shrink` 和 `flex-basis` 的简写。 + +我必须承认为了达到我想要的效果,我在不停地尝试和验证这三个属性上面花费了很多时间。 + +但是直到现在我才明白,我其实只是需要这三者的一个组合。 + +- 如果我想当空间不够的时候条目可以被压缩,但是不要伸展,那么我们需要:`flex: 0 1 auto` +- 如果我的条目需要尽可能地填满空间,并且空间不够时也可以被压缩,那么我们需要:`flex: 1 1 auto` +- 如果我们要求条目既不伸展也不压缩,那么我们需要:`flex: 0 0 auto` + +我希望你还不是很惊奇,因为还有让你更惊奇的。 + +你看,Flexbox Crew (我通常认为 flexbox 团队的皮衣是男女都能穿的尺寸)。对,Flexbox Crew 知道我用得最多的就是这三个属性的组合,所以他们给予了这些组合 [对应的关键字](https://www.w3.org/TR/css-flexbox-1/#flex-common)。 + +第一个场景是 `initial` 的值,所以并不需要关键字。`flex: auto` 适用于第二种场景,`flex: none` 是条目不伸缩的最简单的解决办法。 + +早就该想到它了。 + +它就好像用 `box-shadow: garish` 来默认表示 `2px 2px 4px hotpink`,因为它被认为是一个 ‘有用的默认值’。 + +让我们再回到之前那个丑陋的图书的例子。让我们的 'Buy now' 按钮更胖一点... + +![](https://cdn-images-1.medium.com/max/800/1*oaBk_GjcSHAvSkdhJhwkSA.png) + +... 我只要设置 `flex: none`: + +``` +.book { + display: flex; +} +.book .description { + font-size: 30px; + min-width: 0; + word-wrap: break-word; +} +.book .buy { + margin-left: auto; + flex: none; + width: 80px; + text-align: center; + align-self: center; +} +``` + +(是的,我可以设置 `flex: 0 0 80px;` 来节省一行 CSS。但是设置为 `flex: none`可以更清楚地表示代码的语义。这对于那些忘记这些代码是如何工作的人来说就友好多了。 ) + +### 4. inline-flex + +坦白讲,几个月前我才知道 `display: inline-flex` 这个属性。它会代替块容器创建一个内联的 flex 容器。 + +但是我估计有 28% 的人还不知道这件事,所以现在你就不是那 28% 了。 + +### 5. vertical-align 不会对 flex 条目起作用 + +或者这件事我并不是完全的懂,但是从某种意义上我可以确定,当使用 `vertical-align: middle` 来尝试对齐的时候,它并不会起作用。 + +现在我知道了,细则里面直接写了,[vertical-align 在 flex 条目上不起作用](https://www.w3.org/TR/css-flexbox-1/#flex-containers)” (注意:就好像 `float` 一样)。 + +### 6. margins 和 padding 不要使用 % + +这并不仅仅是一个最佳实践,它类似于外婆说的话,去遵守就好了,不要问为什么。 + +"开发者们在 flex 条目上使用 paddings 和 margins 时,应该避免使用百分比" — 爱你的,flexbox 细则。 + +下面是我在细则里面看到的最喜欢的一段话。 + +> 注解:这个变化糟透了,但是它精准地抓住了世界的当前状态(实现无定法,CSS 无定则) + +> 当心,糖衣炮弹进行中。 + +### 7. 相邻的 flex 条目的边缘不会塌陷 + +你或许知道有时候会出现相邻条目的边缘塌陷。你或许也知道其他的时候**不会**出现边缘塌陷。 + +现在我们都知道相邻的 flex 条目是不会发生边缘塌陷的。 + +### 8. 即使 position: static,z-index 也会有效 + +我不确定我是否真的在乎这一点。但是我想到或许有一天,它就会真地有用。就好像我冰箱里有一瓶柠檬汁。 + +某一天我家来了其他人,然后他会问:"嗨,你这里有柠檬汁吗?",我这时就会告诉他:"有的,就在冰箱里",他会接着说:"谢谢,大兄弟。那么如果我想给一个 flex 条目设置 z-index,我需要指定 position 吗?",我会说:"兄弟,不需要,flex 条目不需要这样。" + +### 9. Flex-basis 是精细且重要的 + +一旦 `initial`,`auto` 和 `none` 都不能满足你的需求时,事情就有点复杂了,但是我们**有** `flex-basis`,有趣的是,你知道的,我不知道怎么结束这句话。如果你们有好的建议的话,欢迎留言。 + +如果你有 3 个 flex 条目,它们的 flex 值分别为 3,3 和 4。那么当 `flex-basis` 为 `0` 的话它们就会忽略他们的内容,占据可用空间的 30%,30%,40%。 + +然而,如果你想要 flex 更友好但是有点不太可预测的话,使用 `flex-basis: auto`。这个会将你的 flex 的值设置得更合理,同时也会考虑到一些其他因素,然后为你给出相对合理的宽度。 + +看看这个很棒的示意图。 + +![](https://cdn-images-1.medium.com/max/800/1*eiAn12jGzun4F7U3mfqUtQ.png) + +我十分确定我读到的关于 flex 的博客中至少有一篇提到了这一点,但是我也不知道为什么,直到我看到上面这张图才想起来。 + +### 10. align-items: baseline + +如果我想让我的 flex 条目垂直对齐,我总是使用 `align-items: center`。但是就像 `vertical-align`一样,这样当你的条目有不同的字体大小并且你希望它们基于 baselines 对齐的时,你需要设置 `baseline` 才能对齐的更完美。 + +`align-self: baseline` 也可以,或许更直观。 + +### 11. 我很蠢 + +下面这段话不论我读几遍,都无法理解它的含义... + +> 在主轴上内容大小是最小内容大小的尺寸,并且是加紧的,如果它有一个宽高比,那么任何定义的 min 和 max 的大小属性都会通过宽高比转换,并且如果主轴的 max 尺寸是确定的话会进一步加紧。 + +这些单词通过我的眼睛被转化成电信号穿过我的视神经,刚刚抵达的时候就看到我的大脑打开后门一溜烟跑了。 + +就像米老鼠和疯狂麦克斯 7 年前生了个孩子,现在和薄荷酒喝醉了,使用他从爸爸妈妈吵架时学到的语言肆意的辱骂周围的人。 + +女士们,先生们,我已经放弃了体面开始胡言乱语了,这意味着你可以关闭这篇文章了(如果你看这个是为了学习的话你可以在这里停止了)。 + + +读这篇细则我学到的最有趣的事情是,尽管我看过大量的博文,以及 flexbox 也算是相对简单的知识点,但是我对其的了解曾是那么的不彻底。事实证明 '经验' 不总是起作用的。 + +我可以很开心的说花时间来阅读这些细则已经得到了回报。我已经优化的我的代码,设置了 auto margins,flex 的值也设置成了 auto 或者 none,并在需要的地方定义了 min-width 为 0。 + +现在这些代码看起来好多了,因为我知道这样做是正确的。 + +我的另外一个收获就是,尽管这些细则在某些方面正如我所想的基于编者视角并有些庞杂,但是仍然有有很多友好的说明和例子。甚至还高亮了那些初级开发者容易忽略的部分。 + +然而,这个是多余的,因为我已经告诉了你所有有用知识点,你就不用再自己去阅读了。 + +现在,如果你们要求,那么我会再去阅读所有其他的 CSS 细则。 + +PS:我强烈建议读读这个,一个浏览器 flexbox bugs 的清单:[https://github.com/philipwalton/flexbugs](https://github.com/philipwalton/flexbugs). + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 diff --git a/TODO/Optimization-killers.md b/TODO/Optimization-killers.md new file mode 100644 index 00000000000..6678718f5c4 --- /dev/null +++ b/TODO/Optimization-killers.md @@ -0,0 +1,450 @@ +> * 原文地址:[Optimization killers](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) +> * 原文作者:[github.com/petkaantonov/bluebird](https://github.com/petkaantonov/bluebird) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者:[lsvih](https://github.com/lsvih) +> * 校对者:[Aladdin-ADD](https://github.com/Aladdin-ADD),[zhaochuanxing](https://github.com/zhaochuanxing) + +# V8 性能优化杀手 + +## 简介 + +这篇文章给出了一些建议,让你避免写出性能远低于期望的代码。特别指出有一些代码会导致 V8 引擎(涉及到 Node.JS、Opera、Chromium 等)无法对相关函数进行优化。 + +vhf 正在做一个类似的项目,试图将 V8 引擎的性能杀手全部列出来:[V8 Bailout Reasons](https://github.com/vhf/v8-bailout-reasons)。 + +### V8 引擎背景知识 + +V8 引擎中没有解释器,但有 2 种不同的编译器:普通编译器与优化编译器。编译器会将你的 JavaScript 代码编译成汇编语言后直接运行。但这并不意味着运行速度会很快。被编译成汇编语言后的代码并不能显著地提高其性能,它只能省去解释器的性能开销,如果你的代码没有被优化的话速度依然会很慢。 + +例如,在普通编译器中 `a + b` 将会被编译成下面这样: + +```nasm +mov eax, a +mov ebx, b +call RuntimeAdd +``` + +换句话说,其实它仅仅调用了 runtime 函数。但如果 `a` 和 `b` 能确定都是整型变量,那么编译结果会是下面这样: + +```nasm +mov eax, a +mov ebx, b +add eax, ebx +``` + +它的执行速度会比前面那种去在 runtime 中调用复杂的 JavaScript 加法算法快得多。 + +通常来说,使用普通编译器将会得到前面那种代码,使用优化编译器将会得到后面那种代码。走优化编译器的代码可以说比走普通编译器的代码性能好上 100 倍。但是请注意,并不是任何类型的 JavaScript 代码都能被优化。在 JS 中,有很多种情况(甚至包括一些我们常用的语法)是不能被优化编译器优化的(这种情况被称为“bailout”,从优化编译器降级到普通编译器)。 + +记住一些会导致整个函数无法被优化的情况是很重要的。JS 代码被优化时,将会逐个优化函数,在优化各个函数的时候不会关心其它的代码做了什么(除非那些代码被内联在即将优化的函数中。)。 + +这篇文章涵盖了大多数会导致函数坠入“无法被优化的深渊”的情况。不过在未来,优化编译器进行更新后能够识别越来越多的情况时,下面给出的建议与各种变通方法可能也会变的不再必要或者需要修改。 + +## 主题 + +1. [工具](#1-工具) +2. [不支持的语法](#2-不支持的语法) +3. [使用 `arguments`](#3-使用-arguments) +4. [Switch-case](#4-switch-case) +5. [For-in](#5-for-in) +6. [退出条件藏的很深,或者没有定义明确出口的无限循环](#6-退出条件藏的很深-或者没有定义明确出口的无限循环) + +## 1. 工具 + +你可以在 node.js 中使用一些 V8 自带的标记来验证不同的代码用法对优化的影响。通常来说你可以创建一个包括特定模式的函数,然后使用所有允许的参数类型去调用它,再使用 V8 的内部去优化与检查它: + +test.js: + +```js +//创建包含需要检查的情况的函数(检查使用 `eval` 语句是否能被优化) +function exampleFunction() { + return 3; + eval(''); +} + +function printStatus(fn) { + switch(%GetOptimizationStatus(fn)) { + case 1: console.log("Function is optimized"); break; + case 2: console.log("Function is not optimized"); break; + case 3: console.log("Function is always optimized"); break; + case 4: console.log("Function is never optimized"); break; + case 6: console.log("Function is maybe deoptimized"); break; + case 7: console.log("Function is optimized by TurboFan"); break; + default: console.log("Unknown optimization status"); break; + } +} + +//识别类型信息 +exampleFunction(); +//这里调用 2 次是为了让这个函数状态从 uninitialized -> pre-monomorphic -> monomorphic +exampleFunction(); + +%OptimizeFunctionOnNextCall(exampleFunction); +//再次调用 +exampleFunction(); + +//检查 +printStatus(exampleFunction); +``` + +运行它: + +``` +$ node --trace_opt --trace_deopt --allow-natives-syntax test.js +(v0.12.7) Function is not optimized +(v4.0.0) Function is optimized by TurboFan +``` + +https://codereview.chromium.org/1962103003 + +为了检验我们做的这个工具是否真的有用,注释掉 `eval` 语句然后再运行一次: + +```bash +$ node --trace_opt --trace_deopt --allow-natives-syntax test.js +[optimizing 000003FFCBF74231 - took 0.345, 0.042, 0.010 ms] +Function is optimized +``` + +事实证明,使用这个工具来验证处理方法是可行且必要的。 + +## 2. 不支持的语法 + +有一些语法结构是不支持被编译器优化的,用这类语法将会导致包含在其中的函数不能被优化。 + +**请注意**,即使这些语句不会被访问到或者不会被执行,它仍然会导致整个函数不能被优化。 + +例如下面这样做是没用的: + +```js +if (DEVELOPMENT) { + debugger; +} +``` + +即使 debugger 语句根本不会被执行到,上面的代码将会导致包含它的整个函数都不能被优化。 + +目前不可被优化的语法有: + +- ~~Generator 函数~~ ([V8 5.7](https://v8project.blogspot.de/2017/02/v8-release-57.html) 对其做了优化) +- ~~包含 for of 语句的函数~~ (V8 commit [11e1e20](https://github.com/v8/v8/commit/11e1e20) 对其做了优化) +- ~~包含 try catch 语句的函数~~ (V8 commit [9aac80f](https://github.com/v8/v8/commit/9aac80f) / V8 5.3 / node 7.x 对其做了优化) +- ~~包含 try finally 语句的函数~~ (V8 commit [9aac80f](https://github.com/v8/v8/commit/9aac80f) / V8 5.3 / node 7.x 对其做了优化) +- ~~包含[`let` 复合赋值](http://stackoverflow.com/q/34595356/504611)的函数~~ (Chrome 56 / V8 5.6! 对其做了优化) +- ~~包含 `const` 复合赋值的函数~~ (Chrome 56 / V8 5.6! 对其做了优化) +- 包含 `__proto__` 对象字面量、`get` 声明、`set` 声明的函数 + +看起来永远不会被优化的语法有: + +- 包含 `debugger` 语句的函数 +- 包含字面调用 `eval()` 的函数 +- 包含 `with` 语句的函数 + +最后明确一下:如果你用了下面任何一种情况,整个函数将不能被优化: + +```js +function containsObjectLiteralWithProto() { + return {__proto__: 3}; +} +``` + +```js +function containsObjectLiteralWithGetter() { + return { + get prop() { + return 3; + } + }; +} +``` + +```js +function containsObjectLiteralWithSetter() { + return { + set prop(val) { + this.val = val; + } + }; +} +``` + +另外在此要特别提一下 `eval` 和 `with`,它们会导致它们的调用栈链变成动态作用域,可能会导致其它的函数也受到影响,因为这种情况无法从字面上判断各个变量的有效范围。 + +**变通办法** + +前面提到的不能被优化的语句用在生产环境代码中是无法避免的,例如 `try-finally` 和 `try-catch`。为了让使用这些语句的影响尽量减小,它们需要被隔离在一个最小化的函数中,这样主要的函数就不会被影响: + +```js +var errorObject = {value: null}; +function tryCatch(fn, ctx, args) { + try { + return fn.apply(ctx, args); + } + catch(e) { + errorObject.value = e; + return errorObject; + } +} + +var result = tryCatch(mightThrow, void 0, [1,2,3]); +//明确地报出 try-catch 会抛出什么 +if(result === errorObject) { + var error = errorObject.value; +} +else { + //result 是返回值 +} +``` + + + +## 3. 使用 `arguments` + +有许多种使用 `arguments` 的方式会导致函数不能被优化。因此当使用 `arguments` 的时候需要格外小心。 + +#### 3.1. 在非严格模式中,对一个已经被定义,同时在函数体中被 `arguments` 引用的参数重新赋值。典型案例: + +```js +function defaultArgsReassign(a, b) { + if (arguments.length < 2) b = 5; +} +``` + +**变通方法** 是将参数值保存在一个新的变量中: + +```js +function reAssignParam(a, b_) { + var b = b_; + //与 b_ 不同,可以安全地对 b 进行重新赋值 + if (arguments.length < 2) b = 5; +} +``` + +如果仅仅是像上面这样用 `arguments`(上面代码作用为检测第二个参数是否存在,如果不存在则赋值为 5),也可以用 `undefined` 检测来代替这段代码: + +```js +function reAssignParam(a, b) { + if (b === void 0) b = 5; +} +``` + +但是之后如果需要用到 `arguments`,很容易忘记需要在这儿加上重新赋值的语句。 + +**变通方法 2**:为整个文件或者整个函数开启严格模式 (`'use strict'`)。 + +#### 3.2. arguments 泄露: + +```js +function leaksArguments1() { + return arguments; +} +``` + +```js +function leaksArguments2() { + var args = [].slice.call(arguments); +} +``` + +```js +function leaksArguments3() { + var a = arguments; + return function() { + return a; + }; +} +``` + +`arguments` 对象在任何地方都不允许被传递或者被泄露。 + +**变通方法** 可以通过创建一个数组来代理 `arguments` 对象: + +```js +function doesntLeakArguments() { + //.length 仅仅是一个整数,不存在泄露 + //arguments 对象本身的问题 + var args = new Array(arguments.length); + for(var i = 0; i < args.length; ++i) { + //i 是 arguments 对象的合法索引值 + args[i] = arguments[i]; + } + return args; +} + +function anotherNotLeakingExample() { + var i = arguments.length; + var args = []; + while (i--) args[i] = arguments[i]; + return args +} +``` + +但是这样要写很多让人烦的代码,因此得判断是否真的值得这么做。后面一次又一次的优化会代理更多的代码,越来越多的代码意味着代码本身的意义会被逐渐淹没。 + +不过,如果你有 build 这个过程,可以将上面这一系列过程由一个不需要 source map 的宏来实现,保证代码为合法的 JavaScript: + +```js +function doesntLeakArguments() { + INLINE_SLICE(args, arguments); + return args; +} +``` + +Bluebird 就使用了这个技术,上面的代码经过 build 之后会被拓展成下面这样: + +```js +function doesntLeakArguments() { + var $_len = arguments.length; + var args = new Array($_len); + for(var $_i = 0; $_i < $_len; ++$_i) { + args[$_i] = arguments[$_i]; + } + return args; +} +``` + +#### 3.3. 对 arguments 进行赋值: + +在非严格模式下可以这么做: + +```js +function assignToArguments() { + arguments = 3; + return arguments; +} +``` + +**变通方法**:犯不着写这么蠢的代码。另外,在严格模式下它会报错。 + +#### 那么如何安全地使用 `arguments` 呢? + +只使用: + +- `arguments.length` +- `arguments[i]` **`i` 需要始终为 arguments 的合法整型索引,且不允许越界** +- 除了 `.length` 和 `[i] `,不要直接使用 `arguments` +- 严格来说用 `fn.apply(y, arguments)` 是没问题的,但除此之外都不行(例如 `.slice`)。 `Function#apply` 是特别的存在。 +- 请注意,给函数添加属性值(例如 `fn.$inject = ...`)和绑定函数(即 `Function#bind` 的结果)会生成隐藏类,因此此时使用 `#apply` 不安全。 + +如果你按照上面的安全方式做,毋需担心使用 `arguments` 导致不确定 arguments 对象的分配。 + +## 4. Switch-case + +在以前,一个 switch-case 语句最多只能包含 128 个 case 代码块,超过这个限制的 switch-case 语句以及包含这种语句的函数将不能被优化。 + +```js +function over128Cases(c) { + switch(c) { + case 1: break; + case 2: break; + case 3: break; + ... + case 128: break; + case 129: break; + } +} +``` +你需要让 case 代码块的数量保持在 128 个之内,否则应使用函数数组或者 if-else。 + +这个限制现在已经被解除了,请参阅此 [comment](https://bugs.chromium.org/p/v8/issues/detail?id=2275#c9)。 + +## 5. For-in + +For-in 语句在某些情况下会导致整个函数无法被优化。 + +这也解释了”For-in 速度不快“之类的说法。 + +#### 5\.1\. 键不是局部变量: + +```js +function nonLocalKey1() { + var obj = {} + for(var key in obj); + return function() { + return key; + }; +} +``` + +```js +var key; +function nonLocalKey2() { + var obj = {} + for(key in obj); +} +``` + +这两种用法db都将会导致函数不能被优化的问题。因此键不能在上级作用域定义,也不能在下级作用域被引用。它必须是一个局部变量。 + +#### 5.2. 被遍历的对象不是一个”简单可枚举对象“ + +##### 5.2.1. 处于”哈希表模式“(又被称为”归一化对象“或”字典模式对象“ - 这种对象将哈希表作为其数据结构)的对象不是简单可枚举对象。 + +```js +function hashTableIteration() { + var hashTable = {"-": 3}; + for(var key in hashTable); +} +``` +如果你给一个对象动态增加了很多的属性(在构造函数外)、`delete` 属性或者使用不合法的标识符作为属性,这个对象将会变成哈希表模式。换句话说,当你把一个对象当做哈希表来用,它就真的会变成哈希表。请不要对这种对象使用 `for-in`。你可以用过开启 Node.JS 的 `--allow-natives-syntax`,调用 `console.log(%HasFastProperties(obj))` 来判断一个对象是否为哈希表模式。 + +
+ +##### 5.2.2. 对象的原型链中存在可枚举属性 + +```js +Object.prototype.fn = function() {}; +``` + +上面这么做会给所有对象(除了用 `Object.create(null)` 创建的对象)的原型链中添加一个可枚举属性。此时任何包含了 `for-in` 语法的函数都不会被优化(除非仅遍历 `Object.create(null)` 创建的对象)。 + +你可以使用 `Object.defineProperty` 创建不可枚举属性(不推荐在 runtime 中调用,但是在定义一些例如原型属性之类的静态数据的时候它很高效)。 + +
+ +##### 5.2.3. 对象中包含可枚举数组索引 + +[ECMAScript 262 规范](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4) 定义了一个属性是否有数组索引: + +> 数组对象会给予一些种类的属性名特殊待遇。对一个属性名 P(字符串形式),当且仅当 ToString(ToUint32(P)) 等于 P 并且 ToUint32(P) 不等于 232−1 时,它是个 数组索引 。一个属性名是数组索引的属性也叫做元素 。 + +一般只有数组有数组索引,但是有时候一般的对象也可能拥有数组索引: `normalObj[0] = value;` + +```js +function iteratesOverArray() { + var arr = [1, 2, 3]; + for (var index in arr) { + + } +} +``` + +因此使用 `for-in` 进行数组遍历不仅会比 for 循环要慢,还会导致整个包含 `for-in` 语句的函数不能被优化。 + +
+ +如果你试图使用 `for-in` 遍历一个非简单可枚举对象,它会导致包含它的整个函数不能被优化。 + +**变通方法**:只对 `Object.keys` 使用 `for-in`,如果要遍历数组需使用 for 循环。如果非要遍历整个原型链上的属性,需要将 `for-in` 隔离在一个辅助函数中以降低影响: + +```js +function inheritedKeys(obj) { + var ret = []; + for(var key in obj) { + ret.push(key); + } + return ret; +} +``` +## 6. 退出条件藏的很深,或者没有定义明确出口的无限循环 + +有时候在你写代码的时候,你需要用到循环,但是不确定循环体内的代码之后会是什么样子。所以这时候你用了一个 `while (true) {` 或者 `for (;;) {`,在之后将终止条件放在循环体中,打断循环进行后面的代码。然而你写完这些之后就忘了这回事。在重构时,你发现这个函数很慢,出现了反优化情况 - 上面的循环很可能就是罪魁祸首。 + +重构时将循环内的退出条件放到循环的条件部分并不是那么简单。 + +1. 如果代码中的退出条件是循环最后的 if 语句的一部分,且代码至少要运行一轮,那么你可以将这个循环重构为 `do{} while ();`。 +2. 如果退出条件在循环的开头,请将它放在循环的条件部分中去。 +3. 如果退出条件在循环体中部,你可以尝试”滚动“代码:试着依次将一部分退出条件前的代码移到后面去,然后在之前的位置留下它的引用。当退出条件可以放在循环条件部分,或者至少变成一个浅显的逻辑判断时,这个循环就不再会出现反优化的情况了。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 diff --git a/TODO/PHP-7-Virtual-machine.md b/TODO/PHP-7-Virtual-machine.md new file mode 100644 index 00000000000..b2f3c218844 --- /dev/null +++ b/TODO/PHP-7-Virtual-machine.md @@ -0,0 +1,1314 @@ +> * 原文地址:[PHP 7 Virtual Machine](http://nikic.github.io/2017/04/14/PHP-7-Virtual-machine.html) +> * 原文作者:[nikic](http://nikic.github.io/aboutMe.html) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +# PHP 7 Virtual Machine # + +This article aims to provide an overview of the Zend Virtual Machine, as it is found in PHP 7. This is not a +comprehensive description, but I try to cover most of the important parts, as well as some of the finer details. + +This description targets PHP version 7.2 (currently in development), but nearly everything also applies to PHP 7.0/7.1. +However, the differences to the PHP 5.x series VM are significant and I will generally not bother to draw parallels. + +Most of this post will consider things at the level of instruction listings and only a few sections at the end deal with +the actual C level implementation of the VM. However, I do want to provide some links to the main files that make up the +VM upfront: + +- [zend_vm_def.h](https://github.com/php/php-src/blob/master/Zend/zend_vm_def.h): The VM definition file. +- [zend_vm_execute.h](https://github.com/php/php-src/blob/master/Zend/zend_vm_execute.h): The generated virtual machine. +- [zend_vm_gen.php](https://github.com/php/php-src/blob/master/Zend/zend_vm_gen.php): The generating script. +- [zend_execute.c](https://github.com/php/php-src/blob/master/Zend/zend_execute.c): Most of the direct support code. + +## Opcodes ## + +In the beginning, there was the opcode. “Opcode” is how we refer to a full VM instruction (including operands), but may +also designate only the “actual” operation code, which is a small integer determining the type of instruction. The +intended meaning should be clear from context. In source code, full instructions are usually called “oplines”. + +An individual instruction conforms to the following `zend_op` structure: + +``` +struct _zend_op { + const void *handler; + znode_op op1; + znode_op op2; + znode_op result; + uint32_t extended_value; + uint32_t lineno; + zend_uchar opcode; + zend_uchar op1_type; + zend_uchar op2_type; + zend_uchar result_type; +}; +``` + +As such, opcodes are essentially a “three-address code” instruction format. There is an `opcode` determining the +instruction type, there are two input operands `op1` and `op2` and one output operand `result`. + +Not all instructions use all operands. An `ADD` instruction (representing the `+` operator) will use all three. A +`BOOL_NOT` instruction (representing the `!` operator) uses only op1 and result. An `ECHO` instruction uses only op1. +Some instructions may either use or not use an operand. For example `DO_FCALL` may or may not have a result operand, +depending on whether the return value of the function call is used. Some instructions require more than two input +operands, in which case they will simply use a second dummy instruction (`OP_DATA`) to carry additional operands. + +Next to these three standard operands, there exists an additional numeric `extended_value` field, which can be used to +hold additional instruction modifiers. For example for a `CAST` it might contain the target type to cast to. + +Each operand has a type, stored in `op1_type`, `op2_type` and `result_type` respectively. The possible types are +`IS_UNUSED`, `IS_CONST`, `IS_TMPVAR`, `IS_VAR` and `IS_CV`. + +The three latter types designated a variable operand (with three different types of VM variables), `IS_CONST` denotes +a constant operand (`5` or `"string"` or even `[1, 2, 3]`), while `IS_UNUSED` denotes an operand that is either actually +unused, or which is used as a 32-bit numeric value (an “immediate”, in assembly jargon). Jump instructions for example +will store the jump target in an `UNUSED` operand. + +### Obtaining opcode dumps ### + +In the following, I’ll often show opcode sequences that PHP generates for some example code. There are currently three +ways by which such opcode dumps may be obtained: + +``` +# Opcache, since PHP 7.1 +php -d opcache.opt_debug_level=0x10000 test.php + +# phpdbg, since PHP 5.6 +phpdbg -p* test.php + +# vld, third-party extension +php -d vld.active=1 test.php + +``` + +Of these, opcache provides the highest-quality output. The listings used in this article are based on opcache dumps, +with minor syntax adjustments. The magic number `0x10000` is short for “before optimization”, so that we see the opcodes +as the PHP compiler produced them. `0x20000` would give you optimized opcodes. Opcache can also generate a lot more +information, for example `0x40000` will produce a CFG, while `0x200000` will produce type- and range-inferred SSA form. +But that’s getting ahead of ourselves: plain old linearized opcode dumps are sufficient for our purposes. + +## Variable types ## + +Likely one of the most important points to understand when dealing with the PHP virtual machine, are the three distinct +variable types it uses. In PHP 5 TMPVAR, VAR and CV had very different representations on the VM stack, along with +different ways of accessing them. In PHP 7 they have become very similar in that they share the same storage mechanism. +However there are important differences in the values they can contain and their semantics. + +CV is short for “compiled variable” and refers to a “real” PHP variable. If a function uses variable `$a`, there will be +a corresponding CV for `$a`. + +CVs can have `UNDEF` type, to denote undefined variables. If an UNDEF CV is used in an instruction, it will (in most +cases) throw the well-known “undefined variable” notice. On function entry all non-argument CVs are initialized to be +UNDEF. + +CVs are not consumed by instructions, e.g. an instruction `ADD $a, $b` will *not* destroy the values stored in CVs `$a` +and `$b`. Instead all CVs are destroyed together on scope exit. This also implies that all CVs are “live” for the +entire duration of function, where “live” here refers to containing a valid value (not live in the data flow sense). + +TMPVARs and VARs on the other hand are virtual machine temporaries. They are typically introduced as the result operand +of some operation. For example the code `$a = $b + $c + $d` will result in an opcode sequence similar to the following: + +``` +T0 = ADD $b, $c +T1 = ADD T0, $d +ASSIGN $a, T1 + +``` + +TMP/VARs are always defined before use and as such cannot hold an UNDEF value. Unlike CVs, these variable types *are* +consumed by the instructions they’re used in. In the above example, the second ADD will destroy the value of the T0 +operand and T0 must not be used after this point (unless it is written to beforehand). Similarly, the ASSIGN will +consume the value of T1, invalidating T1. + +It follows that TMP/VARs are usually very short-lived. In a large number of cases a temporary only lives for the space +of a single instruction. Outside this short liveness interval, the value in the temporary is garbage. + +So what’s the difference between TMP and VAR? Not much. The distinction was inherited from PHP 5, where TMPs were VM +stack allocated, while VARs were heap allocated. In PHP 7 all variables are stack allocated. As such, nowadays the main +difference between TMPs and VARs is that only the latter are allowed to contain REFERENCEs (this allows us to elide +DEREFs on TMPs). Furthermore VARs may hold two types of special values, namely class entries and INDIRECT values. The +latter are used to handle non-trivial assignments. + +The following table attempts to summarize the main differences: + +``` + | UNDEF | REF | INDIRECT | Consumed? | Named? | +-------|-------|-----|----------|-----------|--------| +CV | yes | yes | no | no | yes | +TMPVAR | no | no | no | yes | no | +VAR | no | yes | yes | yes | no | + +``` + +## Op arrays ## + +All PHP functions are represented as structures that have a common `zend_function` header. “Function” here is to be +understood somewhat broadly and includes everything from “real” functions, over methods, down to free-standing +“pseudo-main” code and “eval” code. + +Userland functions use the `zend_op_array` structure. It has more than 30 members, so I’m starting with a reduced +version for now: + +``` +struct _zend_op_array { + /* Common zend_function header here */ + + /* ... */ + uint32_t last; + zend_op *opcodes; + int last_var; + uint32_t T; + zend_string **vars; + /* ... */ + int last_literal; + zval *literals; + /* ... */ +}; +``` + +The most important part here are of course the `opcodes`, which is an array of opcodes (instructions). `last` is the +number of opcodes in this array. Note that the terminology is confusing here, as `last` sounds like it should be the +index of the last opcode, while it really is the number of opcodes (which is one greater than the last index). The same +applies to all other `last_*` values in the op array structure. + +`last_var` is the number of CVs, and `T` is the number of TMPs and VARs (in most places we make no strong distinction +between them). `vars` in array of names for CVs. + +`literals` is an array of literal values occurring in the code. This array is what `CONST` operands reference. Depending +on the ABI, each `CONST` operand will either a store a pointer into this `literals` table, or store an offset relative +to its start. + +There is more to the op array structure than this, but it can wait for later. + +## Stack frame layout ## + +Apart from some executor globals (EG), all execution state is stored on the virtual machine stack. The VM stack is +allocated in pages of 256 KiB and individual pages are connected through a linked list. + +On each function call, a new stack frame is allocated on the VM stack, with the following layout: + +``` ++----------------------------------------+ +| zend_execute_data | ++----------------------------------------+ +| VAR[0] = ARG[1] | arguments +| ... | +| VAR[num_args-1] = ARG[N] | +| VAR[num_args] = CV[num_args] | remaining CVs +| ... | +| VAR[last_var-1] = CV[last_var-1] | +| VAR[last_var] = TMP[0] | TMP/VARs +| ... | +| VAR[last_var+T-1] = TMP[T] | +| ARG[N+1] (extra_args) | extra arguments +| ... | ++----------------------------------------+ + +``` + +The frame starts with a `zend_execute_data` structure, followed by an array of variable slots. The slots are all the +same (simple zvals), but are used for different purposes. The first `last_var` slots are CVs, of which the first +`num_args` holds function arguments. The CV slots are followed by `T` slots for TMP/VARs. Lastly, there can sometimes be +“extra” arguments stored at the end of the frame. These are used for handling `func_get_args()`. + +CV and TMP/VAR operands in instructions are encoded as offsets relative to the start of the stack frame, so fetching +a certain variable is simply an offseted read from the `execute_data` location. + +The execute data at the start of the frame is defined as follows: + +``` +struct _zend_execute_data { + const zend_op *opline; + zend_execute_data *call; + zval *return_value; + zend_function *func; + zval This; /* this + call_info + num_args */ + zend_class_entry *called_scope; + zend_execute_data *prev_execute_data; + zend_array *symbol_table; + void **run_time_cache; /* cache op_array->run_time_cache */ + zval *literals; /* cache op_array->literals */ +}; +``` + +Most importantly, this structure contains `opline`, which is the currently executed instruction, and `func`, which is +the currently executed function. Furthermore: + +- `return_value` is a pointer to the zval into the which the return value will be stored. +- `This` is the `$this` object, but also encodes the number of function arguments and a couple of call metadata flags +in some unused zval space. +- `called_scope` is the scope that `static::` refers to in PHP code. +- `prev_execute_data` points to the previous stack frame, to which execution will return after this function finished +running. +- `symbol_table` is a typically unused symbol table used in case some crazy person actually uses variable variables or +similar features. +- `run_time_cache` caches the op array runtime cache, in order to avoid one pointer indirection when accessing this +structure (which is discussed later). +- `literals` caches the op array literals table for the same reason. + +## Function calls ## + +I’ve skipped one field in the execute_data structure, namely `call`, as it requires some further context about how +function calls work. + +All calls use a variation on the same instruction sequence. A `var_dump($a, $b)` in global scope will compile to: + +``` +INIT_FCALL (2 args) "var_dump" +SEND_VAR $a +SEND_VAR $b +V0 = DO_ICALL # or just DO_ICALL if retval unused + +``` + +There are eight different types of INIT instructions depending on what kind of call it is. INIT_FCALL is used for calls +to free functions that we recognize at compile time. Similarly there are ten different SEND opcodes depending on the +type of the arguments and the function. There is only a modest number of four DO_CALL opcodes, where ICALL is used for +calls to internal functions. + +While the specific instructions may differ, the structure is always the same: INIT, SEND, DO. The main issue that the +call sequence has to contend with are nested function calls, which compile something like this: + +``` +# var_dump(foo($a), bar($b)) +INIT_FCALL (2 args) "var_dump" + INIT_FCALL (1 arg) "foo" + SEND_VAR $a + V0 = DO_UCALL +SEND_VAR V0 + INIT_FCALL (1 arg) "bar" + SEND_VAR $b + V1 = DO_UCALL +SEND_VAR V1 +V2 = DO_ICALL + +``` + +I’ve indented the opcode sequence to visualize which instructions correspond to which call. + +The INIT opcode pushes a call frame on the stack, which contains enough space for all the variables in the function and +the number of arguments we know about (if argument unpacking is involved, we may end up with more arguments). This call +frame is initialized with the called function, `$this` and the `called_scope` (in this case the latter are both NULL, as +we’re calling free functions). + +A pointer to the new frame is stored into `execute_data->call`, where `execute_data` is the frame of the calling +function. In the following we’ll denote such accesses as `EX(call)`. Notably, the `prev_execute_data` of the new frame +is set to the old `EX(call)` value. For example, the INIT_FCALL for call `foo` will set the prev_execute_data to the +stack frame of the `var_dump` (rather than that of the surrounding function). As such, prev_execute_data in this case +forms a linked list of “unfinished” calls, while usually it would provide the backtrace chain. + +The SEND opcodes then proceed to push arguments into the variable slots of `EX(call)`. At this point the arguments are +all consecutive and may overflow from the section designated for arguments into other CVs or TMPs. This will be fixed +later. + +Lastly DO_FCALL performs the actual call. What was `EX(call)` becomes the current function and `prev_execute_data` is +relinked to the calling function. Apart from that, the call procedure depends on what kind of function it is. Internal +functions only need to invoke a handler function, while userland functions need to finish initialization of the stack +frame. + +This initialization involves fixing up the argument stack. PHP allows passing more arguments to a function than it +expects (and `func_get_args` relies on this). However, only the actually declared arguments have corresponding CVs. +Any arguments beyond this will write into memory reserved for other CVs and TMPs. As such, these arguments will be moved +after the TMPs, ending up with arguments segmented into two non-continuous chunks. + +To have it clearly stated, userland function calls do not involve recursion at the virtual machine level. They only +involve a switch from one execute_data to another, but the VM continues running in a linear loop. Recursive virtual +machine invocations only occur if internal functions invoke userland callbacks (e.g. through `array_map`). This is the +reason why infinite recursion in PHP usually results in a memory limit or OOM error, but it is possible to trigger a +stack overflow by recursion through callback-functions or magic methods. + +### Argument sending ### + +PHP uses a large number of different argument sending opcodes, whose differences can be confusing, no thanks to some +unfortunate naming. + +SEND_VAL and SEND_VAR are the simplest variants, which handle sending of by-value arguments that are known to be +by-value at compile time. SEND_VAL is used for CONST and TMP operands, while SEND_VAR is for VARs and CVs. + +SEND_REF conversely, is used for arguments that are known to be by-reference during compilation. As only variables can +be sent by reference, this opcode only accepts VARs and CVs. + +SEND_VAL_EX and SEND_VAR_EX are variants of SEND_VAL/SEND_VAR for cases where we cannot determine statically whether the +argument is by-value or by-reference. These opcodes will check the kind of the argument based on arginfo and behave +accordingly. In most cases the actual arginfo structure is not used, but rather a compact bit vector representation +directly in the function structure. + +And then there is SEND_VAR_NO_REF_EX. Don’t try to read anything into its name, it’s outright lying. This opcode is +used when passing something that isn’t really a “variable” but does return a VAR to a statically unknown argument. Two +particular examples where it is used are passing the result of a function call as an argument, or passing the result of +an assignment. + +This case needs a separate opcode for two reasons: Firstly, it will generate the familiar “Only variables should be +passed by reference” notice if you try to pass something like an assignment by ref (if SEND_VAR_EX were used instead, it +would have been silently allowed). Secondly, this opcode deals with the case that you might want to pass the result of +a reference-returning function to a by-reference argument (which should not throw anything). The SEND_VAR_NO_REF variant +of this opcode (without the _EX) is a specialized variant for the case where we statically know that a reference is +expected (but we don’t know whether the argument is one). + +The SEND_UNPACK and SEND_ARRAY opcodes deal with argument unpacking and inlined `call_user_func_array` calls +respectively. They both push the elements from an array onto the argument stack and differ in various details (e.g. +unpacking supports Traversables while call_user_func_array does not). If unpacking/cufa is used, it may be necessary +to extend the stack frame beyond its previous size (as the real number of function arguments is not known at the time +of initialization). In most cases this extension can happen simply by moving the stack top pointer. However if this +would cross a stack page boundary, a new page has to be allocated and the entire call frame (including already pushed +arguments) needs to be copied to the new page (we are not be able to handle a call frame crossing a page boundary). + +The last opcode is SEND_USER, which is used for inlined `call_user_func` calls and deals with some of its peculiarities. + +While we haven’t yet discussed the different variable fetch modes, this seems like a good place to introduce the +FUNC_ARG fetch mode. Consider a simple call like `func($a[0][1][2])`, for which we do not know at compile-time whether +the argument will be passed by-value or by-reference. In both cases the behavior will be wildly different. If the pass is +by-value and `$a` was previously empty, this could would have to generate a bunch of “undefined index” notices. If the +pass is by-reference we’d have to silently initialize the nested arrays instead. + +The FUNC_ARG fetch mode will dynamically choose one of the two behaviors (R or W), by inspecting the arginfo of the +current `EX(call)` function. For the `func($a[0][1][2])` example, the opcode sequence might look something like this: + +``` +INIT_FCALL_BY_NAME "func" +V0 = FETCH_DIM_FUNC_ARG (arg 1) $a, 0 +V1 = FETCH_DIM_FUNC_ARG (arg 1) V0, 1 +V2 = FETCH_DIM_FUNC_ARG (arg 1) V1, 2 +SEND_VAR_EX V2 +DO_FCALL + +``` + +## Fetch modes ## + +The PHP virtual machine has four classes of fetch opcodes: + +``` +FETCH_* // $_GET, $$var +FETCH_DIM_* // $arr[0] +FETCH_OBJ_* // $obj->prop +FETCH_STATIC_PROP_* // A::$prop + +``` + +These do precisely what one would expect them to do, with the caveat that the basic FETCH_* variant is only used to +access variable-variables and superglobals: normal variable accesses go through the much faster CV mechanism instead. + +These fetch opcodes each come in six variants: + +``` +_R +_RW +_W +_IS +_UNSET +_FUNC_ARG + +``` + +We’ve already learned that _FUNC_ARG chooses between _R and _W depending on whether a function argument is by-value or +by-reference. Let’s try to create some situations where we would expect the different fetch types to appear: + +``` +// $arr[0]; +V2 = FETCH_DIM_R $arr int(0) +FREE V2 + +// $arr[0] = $val; +ASSIGN_DIM $arr int(0) +OP_DATA $val + +// $arr[0] += 1; +ASSIGN_ADD (dim) $arr int(0) +OP_DATA int(1) + +// isset($arr[0]); +T5 = ISSET_ISEMPTY_DIM_OBJ (isset) $arr int(0) +FREE T5 + +// unset($arr[0]); +UNSET_DIM $arr int(0) + +``` + +Unfortunately, the only actual fetch this produced is FETCH_DIM_R: Everything else is handled through special opcodes. +Note that ASSIGN_DIM and ASSIGN_ADD both use an extra OP_DATA, because they need more than two input operands. The +reason why special opcodes like ASSIGN_DIM are used, instead of something like FETCH_DIM_W + ASSIGN, is (apart from +performance) that these operations may be overloaded, e.g., in the ASSIGN_DIM case by means of an object implementing +ArrayAccess::offsetSet(). To actually generate the different fetch types we need to increase the level of nesting: + +``` +// $arr[0][1]; +V2 = FETCH_DIM_R $arr int(0) +V3 = FETCH_DIM_R V2 int(1) +FREE V3 + +// $arr[0][1] = $val; +V4 = FETCH_DIM_W $arr int(0) +ASSIGN_DIM V4 int(1) +OP_DATA $val + +// $arr[0][1] += 1; +V6 = FETCH_DIM_RW $arr int(0) +ASSIGN_ADD (dim) V6 int(1) +OP_DATA int(1) + +// isset($arr[0][1]); +V8 = FETCH_DIM_IS $arr int(0) +T9 = ISSET_ISEMPTY_DIM_OBJ (isset) V8 int(1) +FREE T9 + +// unset($arr[0][1]); +V10 = FETCH_DIM_UNSET $arr int(0) +UNSET_DIM V10 int(1) + +``` + +Here we see that while the outermost access uses specialized opcodes, the nested indexes will be handled using FETCHes +with an appropriate fetch mode. The fetch modes essentially differ by a) whether they generate an “undefined offset” +notice if the index doesn’t exist, and whether they fetch the value for writing: + +``` + | Notice? | Write? +R | yes | no +W | no | yes +RW | yes | yes +IS | no | no +UNSET | no | yes-ish + +``` + +The case of UNSET is a bit peculiar, in that it will only fetch existing offsets for writing, and leave undefined ones +alone. A normal write-fetch would initialize undefined offsets instead. + +### Writes and memory safety ### + +Write fetches return VARs that may contain either a normal zval or an INDIRECT pointer to another zval. Of course, in +the former case any changes applied to the zval will not be visible, as the value is only accessible through a VM +temporary. While PHP prohibits expression such as `[][0] = 42`, we still need to handle this for cases like +`call()[0] = 42`. Depending on whether `call()` returns by-value or by-reference, this expression may or may not have an +observable effect. + +The more typical case is when the fetch returns an INDIRECT, which contains a pointer to the storage location that is +being modified, for example a certain location in a hashtable data array. Unfortunately, such pointers are fragile +things and easily invalidated: any concurrent write to the array might trigger a reallocation, leaving behind a dangling +pointer. As such, it is critical to prevent the execution of user code between the point where an INDIRECT value is +created and where it is consumed. + +Consider this example: + +``` +$arr[a()][b()]=c(); +``` + +Which generates: + +``` +INIT_FCALL_BY_NAME (0 args) "a" +V1 = DO_FCALL_BY_NAME +INIT_FCALL_BY_NAME (0 args) "b" +V3 = DO_FCALL_BY_NAME +INIT_FCALL_BY_NAME (0 args) "c" +V5 = DO_FCALL_BY_NAME +V2 = FETCH_DIM_W $arr V1 +ASSIGN_DIM V2 V3 +OP_DATA V5 + +``` + +Notably, this sequence first executes all side-effects from left to right and only then performs any necessary write +fetches (we refer to the FETCH_DIM_W here as a “delayed opline”). This ensures that the write-fetch and the consuming +instruction are directly adjacent. + +Consider another example: + +``` +$arr[0]=&$arr[1]; +``` + +Here we have a bit of problem: Both sides of the assignment must be fetched for write. However, if we fetch `$arr[0]` +for write and then `$arr[1]` for write, the latter might invalidate the former. This problem is solved as follows: + +``` +V2 = FETCH_DIM_W $arr 1 +V3 = MAKE_REF V2 +V1 = FETCH_DIM_W $arr 0 +ASSIGN_REF V1 V3 + +``` + +Here `$arr[1]` is fetched for write first, then turned into a reference using MAKE_REF. The result of MAKE_REF is no +longer INDIRECT and not subject to invalidation, as such the fetch of `$arr[0]` can be performed safely. + +## Exception handling ## + +Exceptions are the root of all evil. + +An exception is generated by writing an exception into `EG(exception)`, where EG refers to executor globals. Throwing +exceptions from C code does not involve stack unwinding, instead the abortion will propagate upwards through return +value failure codes or checks for `EG(exception)`. The exception is only actually handled when control reenters the +virtual machine code. + +Nearly all VM instructions can directly or indirectly result in an exception under some circumstances. For example any +“undefined variable” notice can result in an exception if a custom error handler is used. We want to avoid checking +whether `EG(exception)` has been set after each VM instruction. Instead a small trick is used: + +When an exception is thrown the current opline of the current execute data is replaced with a dummy HANDLE_EXCEPTION +opline (this obviously does not modify the op array, it only redirects a pointer). The opline at which the exception +originated is backed up into `EG(opline_before_exception)`. + +This means that when control returns into the main virtual machine dispatch loop, the HANDLE_EXCEPTION opcode will be +invoked. There is a slight problem with this scheme: It requires that a) the opline stored in the execute data is +actually the currently executed opline (otherwise opline_before_exception would be wrong) and b) the virtual machine +uses the opline from the execute data to continue execution (otherwise HANDLE_EXCEPTION will not be invoked). + +While these requirements may sound trivial, they are not. The reason is that the virtual machine may be working on a +different opline variable that is out-of-sync with the opline stored in execute data. Before PHP 7 this only happened +in the rarely used GOTO and SWITCH virtual machines, while in PHP 7 this is actually the default mode of operation: If +the compiler supports it, the opline is stored in a global register. + +As such, before performing any operation that might possibly throw, the local opline must be written back into the +execute data (SAVE_OPLINE operation). Similarly, after any potentially throwing operation the local opline must be +populated from execute data (mostly a CHECK_EXCEPTION operation). + +Now, this machinery is what causes a HANDLE_EXCEPTION opcode to execute after an exception is thrown. But what does it +do? First of all, it determines whether the exception was thrown inside a try block. For this purpose the op array +contains an array of try_catch_elements that track opline offsets for try, catch and finally blocks: + +``` +typedef struct _zend_try_catch_element { + uint32_t try_op; + uint32_t catch_op; /* ketchup! */ + uint32_t finally_op; + uint32_t finally_end; +} zend_try_catch_element; +``` + +For now we will pretend that finally blocks do not exist, as they are a whole different rabbit hole. Assuming that we +are indeed inside a try block, the VM needs to clean up all unfinished operations that started before the throwing +opline and don’t span past the end of the try block. + +This involves freeing the stack frames and associated data of all calls currently in flight, as well as freeing live +temporaries. In the majority of cases temporaries are short-lived to the point that the consuming instruction directly +follows the generating one. However it can happen that the live-range spans multiple, potentially throwing instructions: + +``` +# (array)[] + throwing() +L0: T0 = CAST (array) [] +L1: INIT_FCALL (0 args) "throwing" +L2: V1 = DO_FCALL +L3: T2 = ADD T0, V1 + +``` + +In this case the T0 variable is live during instructions L1 and L2, and as such would need to be destroyed if the +function call throws. One particular type of temporary tends to have particularly long live ranges: Loop variables. +For example: + +``` +# foreach ($array as $value) throw $ex; +L0: V0 = FE_RESET_R $array, ->L4 +L1: FE_FETCH_R V0, $value, ->L4 +L2: THROW $ex +L3: JMP ->L1 +L4: FE_FREE V0 + +``` + +Here the “loop variable” V0 lives from L1 to L3 (generally always spanning the entire loop body). Live ranges are stored +in the op array using the following structure: + +``` +typedef struct _zend_live_range { + uint32_t var; /* low bits are used for variable type (ZEND_LIVE_* macros) */ + uint32_t start; + uint32_t end; +} zend_live_range; +``` + +Here `var` is the (operand encoded) variable the range applies to, `start` is the start opline offset (not including the +generating instruction), while `end` if the end opline offset (including the consuming instruction). Of course live +ranges are only stored if the temporary is not immediately consumed. + +The lower bits of `var` are used to store the type of the variable, which can be one of: + +- ZEND_LIVE_TMPVAR: This is a “normal” variable. It holds an ordinary zval value. Freeing this variable behaves like a +FREE opcode. +- ZEND_LIVE_LOOP: This is a foreach loop variable, which holds more than a simple zval. This corresponds to a FE_FREE +opcode. +- ZEND_LIVE_SILENCE: This is used for implementing the error suppression operator. The old error reporting level is +backed up into a temporary and later restored. If an exception is thrown we obviously want to restore it as well. +This corresponds to END_SILENCE. +- ZEND_LIVE_ROPE: This is used for rope string concatenations, in which case the temporary is a fixed-sized array of +`zend_string*` pointers living on the stack. In this case all the strings that have already been populated must be +freed. Corresponds approximately to END_ROPE. + +A tricky question to consider in this context is whether temporaries should be freed, if either their generating or +their consuming instruction throws. Consider the following simple code: + +``` +T2 = ADD T0, T1 +ASSIGN $v, T2 + +``` + +If an exception is thrown by the ADD, should the T2 temporary be automatically freed, or is the ADD instruction +responsible for this? Similarly, if the ASSIGN throws, should T2 be freed automatically, or must the ASSIGN take care +of this itself? In the latter case the answer is clear: An instruction is always responsible for freeing its operands, +even if an exception is thrown. + +The case of the result operand is more tricky, because the answer here changed between PHP 7.1 and 7.2: In PHP 7.1 the +instruction was responsible for freeing the result in case of an exception. In PHP 7.2 it is automatically freed (and +the instruction is responsible for making sure the result is *always* populated). The motivation for this change is the +way that many basic instructions (such as ADD) are implemented. Their usual structure goes roughly as follows: + +``` +1. read input operands +2. perform operation, write it into result operand +3. free input operands (if necessary) + +``` + +This is problematic, because PHP is in the very unfortunate position of not only supporting exceptions and destructors, +but also supporting throwing destructors (this is the point where compiler engineers cry out in horror). As such, step 3 +can throw, at which point the result is already populated. To avoid memory leaks in this edge-case, responsiblility for +freeing the result operand has been shifted from the instruction to the exception handling mechanism. + +Once we have performed these cleanup operations, we can continue executing the catch block. If there is no catch (and +no finally) we unwind the stack, i.e. destroy the current stack frame and give the parent frame a shot at handling the +exception. + +So you get a full appreciation for how ugly the whole exception handling business is, I’ll relate another tidbit related +to throwing destructors. It’s not remotely relevant in practice, but we still need to handle it to ensure correctness. +Consider this code: + +``` +foreach (new Dtor as $value) { + try { + echo "Return"; + return; + } catch (Exception $e) { + echo "Catch"; + } +} + +``` + +Now imagine that `Dtor` is a Traversable class with a throwing destructor. This code will result in the following opcode +sequence, with the loop body indented for readability: + +``` +L0: V0 = NEW 'Dtor', ->L2 +L1: DO_FCALL +L2: V2 = FE_RESET_R V0, ->L11 +L3: FE_FETCH_R V2, $value +L4: ECHO 'Return' +L5: FE_FREE (free on return) V2 # <- return +L6: RETURN null # <- return +L7: JMP ->L10 +L8: CATCH 'Exception' $e +L9: ECHO 'Catch' +L10: JMP ->L3 +L11: FE_FREE V2 # <- the duplicated instr + +``` + +Importantly, note that the “return” is compiled to a FE_FREE of the loop variable and a RETURN. Now, what happens if +that FE_FREE throws, because `Dtor` has a throwing destructor? Normally, we would say that this instruction is within +the try block, so we should be invoking the catch. However, at this point the loop variable has already been destroyed! +The catch discards the exception and we’ll try to continue iterating an already dead loop variable. + +The cause of this problem is that, while the throwing FE_FREE is inside the try block, it is a copy of the FE_FREE in +L11. Logically that is where the exception “really” occurred. This is why the FE_FREE generated by the break is +annotated as being a FREE_ON_RETURN. This instructs the exception handling mechanism to move the source of the exception +to the original freeing instruction. As such the above code will not run the catch block, it will generate an uncaught +exception instead. + +## Finally handling ## + +PHP’s history with finally blocks is somewhat troubled. PHP 5.5 first introduced finally blocks, or rather: a really +buggy implementation of finally blocks. Each of PHP 5.6, 7.0 and 7.1 shipped with major rewrites of the finally +implementation, each fixing a whole slew of bugs, but not quite managing to reach a fully correct implementation. It +looks like PHP 7.1 finally managed to hit the nail (fingers crossed). + +While writing this section, I was surprised to find that from the perspective of the current implementation and my +current understanding, finally handling is actually not all that complicated. Indeed, in many ways the implementation +became simpler through the different iterations, rather than more complex. This goes to show how an insufficient +understanding of a problem can result in an implementation that is both excessively complex and buggy (although, to be +fair, part of the complexity of the PHP 5 implementation stemmed directly from the lack of an AST). + +Finally blocks are run whenever control exits a try block, either normally (e.g. using return) or abnormally (by +throwing). There are a couple interesting edge-cases to consider, which I’ll quickly illustrate before going into the +implementation. Consider: + +``` +try { + throw new Exception(); +} finally { + return 42; +} +``` + +What happens? Finally wins and the function returns 42. Consider: + +``` +try { + return 24; +} finally { + return 42; +} +``` + +Again finally wins and the function returns 42. The finally always wins. + +PHP prohibits jumps out of finally blocks. For example the following is forbidden: + +``` +foreach ($array as $value) { + try { + return 42; + } finally { + continue; + } +} +``` + +The “continue” in the above code sample will generate a compile-error. It is important to understand that this +limitation is purely cosmetic and can be easily worked around by using the “well-known” catch control delegation +pattern: + +``` +foreach ($array as $value) { + try { + try { + return 42; + } finally { + throw new JumpException; + } + } catch (JumpException $e) { + continue; + } +} +``` + +The only real limitation that exists is that it is not possible to jump *into* a finally block, e.g. performing a goto +from outside a finally to a label inside a finally is forbidden. + +With the preliminaries out of the way, we can look at how finally works. The implementation uses two opcodes, FAST_CALL +and FAST_RET. Roughly, FAST_CALL is for jumping into a finally block and FAST_RET is for jumping out of it. Let’s +consider the simplest case: + +``` +try { + echo "try"; +} finally { + echo "finally"; +} +echo "finished"; +``` + +This code compiles down to the following opcode sequence: + +``` +L0: ECHO string("try") +L1: T0 = FAST_CALL ->L3 +L2: JMP ->L5 +L3: ECHO string("finally") +L4: FAST_RET T0 +L5: ECHO string("finished") +L6: RETURN int(1) + +``` + +The FAST_CALL stores its own location into T0 and jumps into the finally block at L3. When FAST_RET is reached, it jumps +back to (one after) the location stored in T0. In this case this would be L2, which is just a jump around the finally +block. This is the base case where no special control flow (returns or exceptions) occurs. Let’s now consider the +exceptional case: + +``` +try { + throw new Exception("try"); +} catch (Exception $e) { + throw new Exception("catch"); +} finally { + throw new Exception("finally"); +} +``` + +When handling an exception, we have to consider the position of the thrown exception relative to the closest surrounding +try/catch/finally block: + +1. Throw from try, with matching catch: Populate `$e` and jump into catch. +2. Throw from catch or try without matching catch, if there is a finally block: Jump into finally block and this +time back up the exception into the FAST_CALL temporary (instead of storing the return address there.) +3. Throw from finally: If there is a backed-up exception in the FAST_CALL temporary, chain it as the previous exception +of the thrown one. Continue bubbling the exception up to the next try/catch/finally. +4. Otherwise: Continue bubbling the exception up to the next try/catch/finally. + +In this example we’ll go through the first three steps: First try throws, triggering a jump into catch. Catch also +throws, triggering a jump into the finally block, with the exception backed up in the FAST_CALL temporary. The finally +block then also throws, so that the “finally” exception will bubble up with the “catch” exception set as its previous +exception. + +A small variation on the previous example is the following code: + +``` +try { + try { + throw new Exception("try"); + } finally {} +} catch (Exception $e) { + try { + throw new Exception("catch"); + } finally {} +} finally { + try { + throw new Exception("finally"); + } finally {} +} +``` + +All the inner finally blocks here are entered exceptionally, but left normally (via FAST_RET). In this case the +previously described exception handling procedure is resumed starting from the parent try/catch/finally block. This +parent try/catch is stored in the FAST_RET opcode (here “try-catch(0)”). + +This essentially covers the interaction of finally and exceptions. But what about a return in finally? + +``` +try { + throw new Exception("try"); +} finally { + return 42; +} +``` + +The relevant portion of the opcode sequence is this: + +``` +L4: T0 = FAST_CALL ->L6 +L5: JMP ->L9 +L6: DISCARD_EXCEPTION T0 +L7: RETURN 42 +L8: FAST_RET T0 + +``` + +The additional DISCARD_EXCEPTION opcode is responsible for discarding the exception thrown in the try block (remember: +the return in the finally wins). What about a return in try? + +``` +try { + $a = 42; + return $a; +} finally { + ++$a; +} +``` + +The excepted return value here is 42, not 43. The return value is determined by the `return $a` line, any further +modification of `$a` should not matter. The code results in: + +``` +L0: ASSIGN $a, 42 +L1: T3 = QM_ASSIGN $a +L2: T1 = FAST_CALL ->L6, T3 +L3: RETURN T3 +L4: T1 = FAST_CALL ->L6 # unreachable +L5: JMP ->L8 # unreachable +L6: PRE_INC $a +L7: FAST_RET T1 +L8: RETURN null + +``` + +Two of the opcodes are unreachable, as they occur directly after a return. These will be removed during optimization, +but I’m showing unoptimized opcodes here. There are two interesting things here: Firstly, `$a` is copied into T3 using +QM_ASSIGN (which is basically a “copy into temporary” instruction). This is what prevents the later modification of `$a` +from affecting the return value. Secondly, T3 is also passed to FAST_CALL, which will back up the value in T1. If the +return from the try block is later discarded (e.g, because finally throws or returns), this mechanism will be used to +free the unused return value. + +All of these individual mechanisms are simple, but some care needs to taken when they are composed. Consider the +following example, where `Dtor` is again some Traversable class with a throwing destructor: + +``` +try { + foreach (new Dtor as $v) { + try { + return 1; + } finally { + return 2; + } + } +} finally { + echo "finally"; +} +``` + +This code generates the following opcodes: + +``` +L0: V2 = NEW (0 args) "Dtor" +L1: DO_FCALL +L2: V4 = FE_RESET_R V2 ->L16 +L3: FE_FETCH_R V4 $v ->L16 +L4: T5 = FAST_CALL ->L10 # inner try +L5: FE_FREE (free on return) V4 +L6: T1 = FAST_CALL ->L19 +L7: RETURN 1 +L8: T5 = FAST_CALL ->L10 # unreachable +L9: JMP ->L15 +L10: DISCARD_EXCEPTION T5 # inner finally +L11: FE_FREE (free on return) V4 +L12: T1 = FAST_CALL ->L19 +L13: RETURN 2 +L14: FAST_RET T5 try-catch(0) +L15: JMP ->L3 +L16: FE_FREE V4 +L17: T1 = FAST_CALL ->L19 +L18: JMP ->L21 +L19: ECHO "finally" # outer finally +L20: FAST_RET T1 + +``` + +The sequence for the first return (from inner try) is FAST_CALL L10, FE_FREE V4, FAST_CALL L19, RETURN. This will first +call into the inner finally block, then free the foreach loop variable, then call into the outer finally block and +then return. The sequence for the second return (from inner finally) is DISCARD_EXCEPTION T5, FE_FREE V4, +FAST_CALL L19. This first discards the exception (or here: return value) of the inner try block, then frees the foreach +loop variable and finally calls into the outer finally block. Note how in both cases the order of these instructions is +the reverse order of the relevant blocks in the source code. + +## Generators ## + +Generator functions may be paused and resumed, and consequently require special VM stack management. Here’s a simple +generator: + +``` +function gen($x) { + foo(yield $x); +} +``` + +This yields the following opcodes: + +``` +$x = RECV 1 +GENERATOR_CREATE +INIT_FCALL_BY_NAME (1 args) string("foo") +V1 = YIELD $x +SEND_VAR_NO_REF_EX V1 1 +DO_FCALL_BY_NAME +GENERATOR_RETURN null + +``` + +Until GENERATOR_CREATE is reached, this is executed as a normal function, on the normal VM stack. GENERATOR_CREATE then +creates a `Generator` object, as well as a heap-allocated execute_data structure (including slots for variables and +arguments, as usual), into which the execute_data on the VM stack is copied. + +When the generator is resumed again, the executor will use the heap-allocated execute_data, but will continue to use the +main VM stack to push call frames. An obvious problem with this is that it’s possible to interrupt a generator while a +call is in progress, as the previous example shows. Here the YIELD is executed at a point where the call frame for the +call foo() has already been pushed onto the VM stack. + +This relatively uncommon case is handled by copying the active call frames into the generator structure when control is +yielded, and restoring them when the generator is resumed. + +This design is used since PHP 7.1. Previously, each generator had its own 4KiB VM page, which would be swapped into the +executor when a generator was restored. This avoids the need for copying call frames, but increases memory usage. + +## Smart branches ## + +It is very common that comparison instructions are directly followed by condition jumps. For example: + +``` +L0: T2 = IS_EQUAL $a, $b +L1: JMPZ T2 ->L3 +L2: ECHO "equal" + +``` + +Because this pattern is so common, all the comparison opcodes (such as IS_EQUAL) implement a smart branch mechanism: +they check if the next instruction is a JMPZ or JMPNZ instruction and if so, perform the respective jump operation +themselves. + +The smart branch mechanism only checks whether the next instruction is a JMPZ/JMPNZ, but does not actually check whether +its operand is actually the result of the comparison, or something else. This requires special care in cases where the +comparison and subsequent jump are unrelated. For example, the code `($a == $b) + ($d ? $e : $f)` generates: + +``` +L0: T5 = IS_EQUAL $a, $b +L1: NOP +L2: JMPZ $d ->L5 +L3: T6 = QM_ASSIGN $e +L4: JMP ->L6 +L5: T6 = QM_ASSIGN $f +L6: T7 = ADD T5 T6 +L7: FREE T7 + +``` + +Note that a NOP has been inserted between the IS_EQUAL and the JMPZ. If this NOP weren’t present, the branch would end +up using the IS_EQUAL result, rather than the JMPZ operand. + +## Runtime cache ## + +Because opcode arrays are shared (without locks) between multiple processes, they are strictly immutable. However, +runtime values may be cached in a separate “runtime cache”, which is basically an array of pointers. Literals may have +an associated runtime cache entry (or more than one), which is stored in their u2 slot. + +Runtime cache entries come in two types: The first are ordinary cache entries, such as the one used by INIT_FCALL. After +INIT_FCALL has looked up the called function once (based on its name), the function pointer will be cached in the +associated runtime cache slot. + +The second type are polymorphic cache entries, which are just two consecutive cache slots, where the first stores a +class entry and the second the actual datum. These are used for operations like FETCH_OBJ_R, where the offset of the +property in the property table for a certain class is cached. If the next access happens on the same class (which is +quite likely), the cached value will be used. Otherwise a more expensive lookup operation is performed, and the result +is cached for the new class entry. + +## VM interrupts ## + +Prior to PHP 7.0, execution timeouts used to handled by a longjump into the shutdown sequence directly from the signal +handler. As you may imagine, this caused all manner of unpleasantness. Since PHP 7.0 timeouts are instead delayed until +control returns to the virtual machine. If it doesn’t return within a certain grace period, the process is aborted. +Since PHP 7.1 pcntl signal handlers use the same mechanism as execution timeouts. + +When a signal is pending, a VM interrupt flag is set and this flag is checked by the virtual machine at certain points. +A check is not performed at every instruction, but rather only on jumps and calls. As such the interrupt will not be +handled immediately on return to the VM, but rather at the end of the current section of linear control flow. + +## Specialization ## + +If you take a look at the [VM definition](https://github.com/php/php-src/blob/master/Zend/zend_vm_def.h) file, you’ll +find that opcode handlers are defined as follows: + +``` +ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMPVAR|CV, CONST|TMPVAR|CV) + +``` + +The `1` here is the opcode number, `ZEND_ADD` its name, while the other two arguments specify which operand types the +instruction accepts. The [generated virtual machine code](https://github.com/php/php-src/blob/master/Zend/zend_vm_execute.h) +(generated by [zend_vm_gen.php](https://github.com/php/php-src/blob/master/Zend/zend_vm_gen.php)) will then contain +specialized handlers for each of the possible operand type combinations. These will have names like +ZEND_ADD_SPEC_CONST_CONST_HANDLER. + +The specialized handlers are generated by replacing certain macros in the handler body. The obvious ones are OP1_TYPE +and OP2_TYPE, but operations such as GET_OP1_ZVAL_PTR() and FREE_OP1() are also specialized. + +The handler for ADD specified that it accepts `CONST|TMPVAR|CV` operands. The TMPVAR here means that the opcode accepts +both TMPs and VARs, but asks for these to not be specialized separately. Remember that for most purposes the only +difference between TMP and VAR is that the latter can contain references. For an opcode like ADD (where references are +on the slow-path anyway) having a separate specialization for this is not worthwhile. Some other opcodes that do make +this distinction will use `TMP|VAR` in their operand list. + +Next to the operand-type based specialization, handlers can also be specialized on other factors, such as whether their +return value is used. ASSIGN_DIM specializes based on the operand type of the following OP_DATA opcode: + +``` +ZEND_VM_HANDLER(147, ZEND_ASSIGN_DIM, + VAR|CV, CONST|TMPVAR|UNUSED|NEXT|CV, SPEC(OP_DATA=CONST|TMP|VAR|CV)) + +``` + +Based on this signature, 2*4*4=32 different variants of ASSIGN_DIM will be generated. The specification for the second +operand also contains an entry for `NEXT`. This is not related to specialization, instead it specifies what the meaning +of an UNUSED operand is in this context: it means that this is an append operations (`$arr[]`). Another example: + +``` +ZEND_VM_HANDLER(23, ZEND_ASSIGN_ADD, + VAR|UNUSED|THIS|CV, CONST|TMPVAR|UNUSED|NEXT|CV, DIM_OBJ, SPEC(DIM_OBJ)) + +``` + +Here we have that the first operand being UNUSED implies an access on `$this`. This is a general convention for object +related opcodes, for example `FETCH_OBJ_R UNUSED, 'prop'` corresponds to `$this->prop`. An UNUSED second operand again +implies an append operation. The third argument here specifies the meaning of the extended_value operand: It contains +a flag that distinguishes between `$a += 1`, `$a[$b] += 1` and `$a->b += 1`. Finally, the `SPEC(DIM_OBJ)` instructs that +a specialized handler should be generated for each of those. (In this case the number of total handlers that will be +generated is non-trivial, because the VM generator knows that certain combination are impossible. For example an UNUSED +op1 is only relevant for the OBJ case, etc.) + +Finally, the virtual machine generator supports an additional, more sophisticated specialization mechanism. Towards the +end of the definition file, you will find a number of handlers of this form: + +``` +ZEND_VM_TYPE_SPEC_HANDLER( + ZEND_ADD, + (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG), + ZEND_ADD_LONG_NO_OVERFLOW, + CONST|TMPVARCV, CONST|TMPVARCV, SPEC(NO_CONST_CONST,COMMUTATIVE) +) + +``` + +These handlers specialize not only based on the VM operand type, but also based on the possible types the operand might +take at runtime. The mechanism by which possible operand types are determined is part of the opcache optimization +infrastructure and quite outside the scope of this article. However, assuming such information is available, it should +be clear that this is a handler for an addition of the form `int + int -> int`. Additionally, the SPEC annotation tells +the specializer that variants for two const operands should not be generated and that the operation is commutative, so +that if we already have a CONST+TMPVARCV specialization, we do not need to generate TMPVARCV+CONST as well. + +## Fast-path / slow-path split ## + +Many opcode handlers are implemented using a fast-path / slow-path split, where first a few common cases are handled, +before falling back to a generic implementation. It’s about time we looked at some actual code, so I’ll just paste the +entirety of the SL (shift-left) implementation here: + +``` +ZEND_VM_HANDLER(6, ZEND_SL, CONST|TMPVAR|CV, CONST|TMPVAR|CV) +{ + USE_OPLINE + zend_free_op free_op1, free_op2; + zval *op1, *op2; + + op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R); + op2 = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R); + if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_LONG) + && EXPECTED(Z_TYPE_INFO_P(op2) == IS_LONG) + && EXPECTED((zend_ulong)Z_LVAL_P(op2) < SIZEOF_ZEND_LONG * 8)) { + ZVAL_LONG(EX_VAR(opline->result.var), Z_LVAL_P(op1) << Z_LVAL_P(op2)); + ZEND_VM_NEXT_OPCODE(); + } + + SAVE_OPLINE(); + if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(op1) == IS_UNDEF)) { + op1 = GET_OP1_UNDEF_CV(op1, BP_VAR_R); + } + if (OP2_TYPE == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(op2) == IS_UNDEF)) { + op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); + } + shift_left_function(EX_VAR(opline->result.var), op1, op2); + FREE_OP1(); + FREE_OP2(); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} +``` + +The implementation starts by fetching the operands using `GET_OPn_ZVAL_PTR_UNDEF` in BP_VAR_R mode. The `UNDEF` part +here means that no check for undefined variables is performed in the CV case, instead you’ll just get back an UNDEF +value as-is. Once we have the operands, we check whether both are integers and the shift width is in range, in which +case the result can be directly computed and we advance to the next opcode. Notably, the type check here doesn’t care +whether the operands are UNDEF, so the use of GET_OPn_ZVAL_PTR_UNDEF is justified. + +If the operands do not happen to satisfy the fast-path, we fall back to the generic implementation, which starts with +SAVE_OPLINE(). This is our signal for “potentially throwing operations follow”. Before going any further, the case of +undefined variables is handled. GET_OPn_UNDEF_CV will in this case emit an undefined variable notice and return a NULL +value. + +Next, the generic shift_left_function is called and writes its result into `EX_VAR(opline->result.var)`. Finally, the +input operands are freed (if necessary) and we advance to the next opcode with an exception check (which means the +opline is reloaded before advancing). + +As such, the fast-path here saves two checks for undefined variables, a call to a generic operator function, freeing +of operand, as well as saving and reloading of the opline for exception handling. Most of the performance sensitive +opcodes are lain out in a similar fashion. + +## VM macros ## + +As can be seen from the previous code listing, the virtual machine implementation makes liberal use of macros. Some of +these are normal C macros, while others are resolved during generation of the virtual machine. In particular, this +includes a number of macros for fetching and freeing instruction operands: + +``` +OPn_TYPE +OP_DATA_TYPE + +GET_OPn_ZVAL_PTR(BP_VAR_*) +GET_OPn_ZVAL_PTR_DEREF(BP_VAR_*) +GET_OPn_ZVAL_PTR_UNDEF(BP_VAR_*) +GET_OPn_ZVAL_PTR_PTR(BP_VAR_*) +GET_OPn_ZVAL_PTR_PTR_UNDEF(BP_VAR_*) +GET_OPn_OBJ_ZVAL_PTR(BP_VAR_*) +GET_OPn_OBJ_ZVAL_PTR_UNDEF(BP_VAR_*) +GET_OPn_OBJ_ZVAL_PTR_DEREF(BP_VAR_*) +GET_OPn_OBJ_ZVAL_PTR_PTR(BP_VAR_*) +GET_OPn_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_*) +GET_OP_DATA_ZVAL_PTR() +GET_OP_DATA_ZVAL_PTR_DEREF() + +FREE_OPn() +FREE_OPn_IF_VAR() +FREE_OPn_VAR_PTR() +FREE_UNFETCHED_OPn() +FREE_OP_DATA() +FREE_UNFETCHED_OP_DATA() + +``` + +As you can see, there are quite a few variations here. The `BP_VAR_*` arguments specify the fetch mode and support the +same modes as the FETCH_* instructions (with the exception of FUNC_ARG). + +`GET_OPn_ZVAL_PTR()` is the basic operand fetch. It will throw a notice on undefined CV and will not dereference the +operand. `GET_OPn_ZVAL_PTR_UNDEF()` is, as we already learned, a variant that does not check for undefined CVs. +`GET_OPn_ZVAL_PTR_DEREF()` includes a DEREF of the zval. This is part of the specialized GET operation, because +dereferencing is only necessary for CVs and VARs, but not for CONSTs and TMPs. Because this macro needs to distinguish +between TMPs and VARs, it can only be used with `TMP|VAR` specialization (but not `TMPVAR`). + +The `GET_OPn_OBJ_ZVAL_PTR*()` variants additionally handle the case of an UNUSED operand. As mentioned before, by +convention `$this` accesses use an UNUSED operand, so the `GET_OPn_OBJ_ZVAL_PTR*()` macros will return a reference to +`EX(This)` for UNUSED ops. + +Finally, there are some `PTR_PTR` variants. The naming here is a leftover from PHP 5 times, where this actually used +doubly-indirected zval pointers. These macros are used in write operations and as such only support CV and VAR types +(anything else returns NULL). They differ from normal PTR fetches in that that they de-INDIRECT VAR operands. + +The `FREE_OP*()` macros are then used to free the fetched operands. To operate, they require the definition of a +`zend_free_op free_opN` variable, into which the GET operation stores the value to free. The baseline `FREE_OPn()` +operation will free TMPs and VARs, but not free CVs and CONSTs. `FREE_OPn_IF_VAR()` does exactly what it says: free the +operand only if it is a VAR. + +The `FREE_OP*_VAR_PTR()` variant is used in conjunction with `PTR_PTR` fetches. It will only free VAR operands and only +if they are not INDIRECTed. + +The `FREE_UNFETCHED_OP*()` variants are used in cases where an operand must be freed before it has been fetched with +GET. This typically occurs if an exception is thrown prior to operand fetching. + +Apart from these specialized macros, there are also quite a few macros of the more ordinary sort. The VM defines three +macros which control what happens after an opcode handler has run: + +``` +ZEND_VM_CONTINUE() +ZEND_VM_ENTER() +ZEND_VM_LEAVE() +ZEND_VM_RETURN() + +``` + +CONTINUE will continue executing opcodes as normal, while ENTER and LEAVE are used to enter/leave a nested function +call. The specifics of how these operate depends on precisely how the VM is compiled (e.g., whether global registers are +used, and if so, which). In broad terms, these will synchronize some state from globals before continuing. RETURN is +used to actually exit the main VM loop. + +ZEND_VM_CONTINUE() expects that the opline is updated beforehand. Of course, there are more macros related to that: + +``` + | Continue? | Check exception? | Check interrupt? +ZEND_VM_NEXT_OPCODE() | yes | no | no +ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION() | yes | yes | no +ZEND_VM_SET_NEXT_OPCODE(op) | no | no | no +ZEND_VM_SET_OPCODE(op) | no | no | yes +ZEND_VM_SET_RELATIVE_OPCODE(op, offset) | no | no | yes +ZEND_VM_JMP(op) | yes | yes | yes + +``` + +The table shows whether the macro includes an implicit ZEND_VM_CONTINUE(), whether it will check for exceptions and +whether it will check for VM interrupts. + +Next to these, there are also `SAVE_OPLINE()`, `LOAD_OPLINE()` and `HANDLE_EXCEPTION()`. As has been mentioned in the +section on exception handling, SAVE_OPLINE() is used before the first potentially throwing operation in an opcode +handler. If necessary, it writes back the opline used by the VM (which might be in a global register) into the execute +data. LOAD_OPLINE() is the reverse operation, but nowadays it sees little use, because it has effectively been rolled +into ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION() and ZEND_VM_JMP(). + +HANDLE_EXCEPTION() is used to return from an opcode handler after you already know that an exception has been thrown. It +performs a combination of LOAD_OPLINE and CONTINUE, which will effectively dispatch to the HANDLE_EXCEPTION opcode. + +Of course, there are more macros (there are always more macros…), but this should cover the most important parts. + +If you liked this article, you may want to [browse my other articles](http://nikic.github.io/) or + [follow me on Twitter](https://twitter.com/#!/nikita_ppv). + +[blog comments powered by Disqus](http://disqus.com) + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 diff --git a/TODO/What-would-be-your-advice-to-a-software-engineer-who-wants-to-learn-machine-learning.md b/TODO/What-would-be-your-advice-to-a-software-engineer-who-wants-to-learn-machine-learning.md new file mode 100644 index 00000000000..65613611260 --- /dev/null +++ b/TODO/What-would-be-your-advice-to-a-software-engineer-who-wants-to-learn-machine-learning.md @@ -0,0 +1,78 @@ + +> * 原文地址:[What would be your advice to a software engineer who wants to learn machine learning?](https://www.quora.com/What-would-be-your-advice-to-a-software-engineer-who-wants-to-learn-machine-learning-3/answer/Alex-Smola-1) +> * 原文作者:[Alex Smola](https://www.quora.com/profile/Alex-Smola-1) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO/What-would-be-your-advice-to-a-software-engineer-who-wants-to-learn-machine-learning.md](https://github.com/xitu/gold-miner/blob/master/TODO/What-would-be-your-advice-to-a-software-engineer-who-wants-to-learn-machine-learning.md) +> * 译者: +> * 校对者: + +# What would be your advice to a software engineer who wants to learn machine learning? + +This depends a lot on the background of the software engineer. And it depends on which part of machine learning you want to master. So, for the sake of concreteness, let's assume that we're talking about a junior engineer who has 4 years of university and a year of two in industry. And let's assume that this is someone who wants to work on computational advertising, natural language processing, image analysis, social networks, search and ranking. Let's start with the requirements for doing machine learning (disclaimer to my academic colleagues - this list is very incomplete, apologies in advance if your papers aren't included). + +- Linear algebra +A lot of machine learning, statistics and optimization needs this. And this is incidentally why GPUs are so much better than CPUs for doing deep learning. You need to have at least a basic proficiency in the following + + - Scalars, vectors, matrices, tensors. Think of them as zero, one, two, three and higher-dimensional objects that you can compose and use to transform another. A bit like Lego. They provide the basic data transformations. + - Eigenvectors, norms, matrix approximations, decompositions. This is essentially all about getting comfortable with the things linear algebra objects do. If you want to analyze how a matrix works (e.g. to check why your gradients are vanishing in a recurrent neural network or why your controller is diverging in a reinforcement learning algorithm) you need to be able to understand by how much things can grow or shrink when applying matrices and vectors to it. Matrix approximations such as low rank or then Cholesky factorization help a lot when trying to get good performance and stability out of the code. + - Numerical Linear Algebra +This is relevant if you want to do something that's fairly optimization heavy. Think kernel methods and deep learning. Not quite so important for graphical models and samplers. + - Books + [Serge Lang, Linear Algebra](http://www.amazon.com/Linear-Algebra-Undergraduate-Texts-Mathematics/dp/0387964126) + A basic linear algebra book and it's well written for undergrads. + [Bela Bolobas, Linear Analysis](http://www.amazon.com/Linear-Analysis-Introductory-Cambridge-Mathematical/dp/0521655773) + This is much more difficult and more relevant for anyone who wants to do a lot of math and functional analysis. Probably a good idea if you want to aim for a PhD. + [Lloyd Trefethen and David Bau, Numerical Linear Algebra](http://www.amazon.com/Numerical-Linear-Algebra-Lloyd-Trefethen/dp/0898713617) + This is one of many books that you can use for this. [Numerical Recipes](http://www.amazon.com/Numerical-Recipes-Scientific-Computing-Second/dp/0521431085/) is another one but the algorithms in there are a bit dated. And then there's the Golub and van Loan book ([Matrix Computations](http://www.amazon.com/Computations-Hopkins-Studies-Mathematical-Sciences/dp/1421407949/)). + +- Optimization (and basic calculus) + In many cases setting up the question to ask is rather easy, but getting the answer is not. For instance, if you want to perform linear regression (i.e. fit a line) to some data, you probably want to minimize the sum of the squared distances to the observations. Likewise, if you want to get a good model for click prediction, you want to maximize the accuracy of your probability estimates that someone will click on the ad. This means that we have the general problem of some objective, some parameters, lots of data, and we need a way to get there. This matters, in particular since we usually don't have a closed form solution. + + - Convex Optimization + In many cases optimization problems are nice insofar as they don't have many local solutions. This happens whenever the problem is convex. + (A set is convex if you can draw a line between any two points in the set and the entire line is in the set. A function is convex if you can draw a line between any two points on the graph and the line is above the graph.) + Probably the canonical book in the field is the one by [Steven Boyd and Lieven Vandenberghe](http://stanford.edu/~boyd/cvxbook/). It's free and awesome. Also, there are lots of great slide sets from [Boyd's classes](http://web.stanford.edu/~boyd/). [Dimitri Bertsekas](http://www.mit.edu/~dimitrib/home.html) has generated a treasure trove of books on optimiation, control, etc. This should suffice to get anyone started in this area. + - Stochastic Gradient Descent + Much of this started as a special case of convex optimization (at least the early theorems did) but it's taken off quite a bit, not the least due to the increase in data. Here's why - imagine that you have some data to go through and that your algorithm needs to look at all the data before it takes an update step. Now, if I maliciously give you 10 copies of the same data you'll have to do 10 times the work without any real benefit from this. Obviously reality isn't quite that bad but it helps if you take many small update steps, one after each instance is observed. This has been quite transformational in machine learning. Plus many of the associated algorithms are much easier. + The challenge, however has been to parallelize this. Probably one of the first steps in this direction was our [Slow Learners are Fast ](http://arxiv.org/abs/0911.0491)paper from 2009. A rather pretty recent version of this are the lock free variants, such as the [Hogwild](https://www.eecs.berkeley.edu/~brecht/papers/hogwildTR.pdf) paper by Niu et al. in 2013. In a nutshell, these algorithms work by computing local gradients on worker machines and updating a consensus parameter set asynchronously. + The other challenge is how to deal with ways of controlling overfitting e.g. by regularization. For convex penalties there are what is called proximal gradient algorithms. One of the more popular choices are the rahter unfortunately named [FISTA algorithm](http://people.rennes.inria.fr/Cedric.Herzet/Cedric.Herzet/Sparse_Seminar/Entrees/2012/11/12_A_Fast_Iterative_Shrinkage-Thresholding_Algorithmfor_Linear_Inverse_Problems_(A._Beck,_M._Teboulle)_files/Breck_2009.pdf) of Amir Beck and Marc Teboulle. For some code look at Francis Bach's [SPAM toolbox](http://spams-devel.gforge.inria.fr/). + - Nonconvex Methods + Many machine learning problems are nonconvex. Essentially anything related to deep learning is. But so are clustering, topic models, pretty much any latent variable methods and pretty much anything else that's interesting in machine learning nowadays. Some of the acceleration techniques can help. For instance, my student [Sashank Reddy](http://www.cs.cmu.edu/~sjakkamr/) showed recently how to get [good rates](http://arxiv.org/abs/1603.06159) of [convergence](http://arxiv.org/abs/1603.06160) in this case. + There are lots of techniques called Spectral Methods that can be used. [Anima Anandkumar](http://newport.eecs.uci.edu/anandkumar/) has answered this in amazing detail in her recent [Quora session](/profile/Anima-Anandkumar-1). Please read her responses since they're super detailed. In a nutshell, convex problems aren't the only ones that can be solved reliably. In some cases you can work out the mathematical equivalent of a puzzle to show that only a certain choice of parameters can make sense to find all the clusters, topics, relevant dimensions, neurons or whatever in your data. This is great if you are able and willing to throw a lot of math at it. + There are lots of recent tricks when it comes to training Deep Networks. I'll get to them below but in some cases the goal isn't just optimization but to engineer a specific choice of solution (almost akin to The Journey is the goal). + +- Systems + Much of the reason why machine learning is becoming the key ingredient in pretty much anything related to people, measurements, sensors and data has to do with the breakthroughs over the past decade in scaling algorithms. It isn't a complete coincidence that [Jeff Dean](http://research.google.com/pubs/jeff.html) gave half a dozen machine learning tutorials in the past year. For anyone who slept the past decade under a rock - he's the man behind MapReduce, the Google File System, BigTable, and another dozen of key technologies that have made Google great. Some facts about him can be found [here](http://www.informatika.bg/jeffdean). + Joking aside, systems research offers a treasure trove of tools for solving problems that are distributed, asynchronous, fault tolerant, scalable, and simple. The latter is something that machine learning researchers often overlook. Simplicity is a feature, not a bug. Some of the basic techniques that will get you quite far: + + - Distributed hash table + This is essentially what methods such as [memcached](https://memcached.org/), [dynamo](http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf), [pastry](http://research.microsoft.com/en-us/um/people/antr/PAST/pastry.pdf), or [ceph](http://docs.ceph.com/docs/hammer/rados/) are built around. They all solve the problem - how to distribute objects over many machines in such a way as to avoid having to ask a central repository where things went. To make this work you need to encode the location in a randomized yet deterministic fashion (hence hashing). Moreover, you need to figure out who will take care of things if any machine fails. + This is what we used for the [data layout](https://www.cs.cmu.edu/~dga/papers/osdi14-paper-li_mu.pdf) in the Parameter Server. My student [Mu Li](http://www.cs.cmu.edu/~muli/) is the brains behind this project. See [DMLC](http://dmlc.ml/) for a collection of tools. + - Consistency and Messaging + The godfather of all of this is Leslie Lamport's [PAXOS](http://research.microsoft.com/en-us/um/people/lamport/pubs/paxos-simple.pdf) protocol. It solves the problem of having machines come to a consensus while not all machines are available at all times and some might fail (yes, I'm playing fast and loose here). If you've ever used version control you probably know how it works intuitively - you have lots of machines (or developers) generating updates (or pieces of code) and you want to combine them all in a way that makes sense (e.g. you shouldn't apply a diff twice) while not requiring that everyone talks to everyone all the time. + In systems the solution is to use what's called a vector clock (see e.g. Google's [Chubby](http://blogoscoped.com/archive/2008-07-24-n69.html)). We used a variant of this in the Parameter Server. The key difference was (all credits to Mu Li) to use vector clocks for parameter ranges. This ensures that you don't run out of memory for timestamps, just like a file system doesn't need a timestamp for every single byte. + - Fault Tolerance, Scaling and the Cloud + The easiest way to teach yourself this is to run algorithms on [Amazon AWS](http://aws.amazon.com), [Google GWC](http://console.google.com), [Microsoft Azure](http://azure.microsoft.com), or on the [many other providers](http://serverbear.com/) that you can find. It's quite exciting the first time you fire up 1,000 servers and you realize that you're now commanding what amounts to essentially a perfectly legal botnet. In one case while I used to work at Google we took over 5,000 high end machines somewhere in Europe for inference in topic models. This was a sizable fraction of a nuclear power plant what we racked up for an energy bill. My manager took me aside and told me that this was an expensive experiment ... + Probably the easiest way to get started is to learn about [docker](http://www.docker.com). They've been on a tear to develop lots of tools of making scaling easier. In particular [Docker Machine](https://docs.docker.com/machine/) and [Docker Cloud](https://docs.docker.com/cloud/) are probably some of their nicest recent additions that allow you to connect to different clouds just like swapping printer drivers. + - Hardware + It kind of sounds obvious but it really helps if you know what hardware your algorithms run on. This helps you to find out whether your code is anywhere near peak performance. For starters look at Jeff Dean's [Numbers every engineer should know](https://gist.github.com/jboner/2841832). One of my favorite interview questions is (probably by now was) how fast the candidate's laptop is. Knowing what the limitations for the algorithm are really helps. Is it cache, memory bandwidth, latency, disks, etc. [Anandtech](http://www.anandtech.com) has really great introductory articles and reviews of microprocessor architectures and related stuff. Check them out whenever Intel / ARM / AMD releases new hardware. + +- Statistics + I've deliberately put this last. Simply since everyone thinks that this is key (yes, it is) and overlooks all the rest. Statistics is the key to let you ask good questions. It also helps you to understand how much of an approximation you're making when you are modeling some data. + A lot of the improvements in anything from graphical models, kernel methods, deep learning, etc. really arise from being able to ask the right questions, aka, defining sensible objective functions that you can then optimize. + + - Statistics Proper + A good intro is [Larry Wasserman](http://www.stat.cmu.edu/~larry/)'s book on [All of Statistics](http://www.stat.cmu.edu/~larry/all-of-statistics/). Alternatively you can check out David McKay's [Machine Learning](http://www.inference.phy.cam.ac.uk/itprnn/book.pdf) book. It's free (and huge and comprehensive). There are plenty of other great books around, such as those by [Kevin Murphy](https://mitpress.mit.edu/books/machine-learning-0), [Chris Bishop](http://research.microsoft.com/en-us/um/people/cmbishop/prml/), [Trevor Hastie, Rob Tibshirani and Jerome Friedman](http://statweb.stanford.edu/~tibs/ElemStatLearn/). And yes, Bernhard Scholkopf and I [wrote one, too](https://mitpress.mit.edu/books/learning-kernels). + - Randomized Algorithms and Probabilistic Computing + This is essentially computer science addressing the same problems. The key difference is that they're used as a tool to design algorithms rather than a problem to fit parameters to. I really love the one by [Michael Mitzenmacher and Eli Upfal](http://www.amazon.com/Probability-Computing-Randomized-Algorithms-Probabilistic/dp/0521835402). It's deceptively easy to read even though it covers lots of profound problems. Another one, if you want to go deeper into tools is the one by the late [Rajeev Motwani and Prabhakar Raghavan](http://www.amazon.com/Randomized-Algorithms-Rajeev-Motwani/dp/0521474655). It's well written but harder to understand without having a good statistics background. + +This reply is probably long enough now that hardly anyone will have made it to here. Hence I should keep the rest short. There is lots of awesome video content online. Many faculty by now have a YouTube channel where they post their classes. This helps when going through the sometimes tricky set of tools. Here's [mine](https://www.youtube.com/user/smolix/playlists). [Nando de Freitas](https://www.youtube.com/user/ProfNandoDF)' is much better. + +And then there are tools. [DMLC](http://www.dmlc.ml) is a good place to start. It has plenty of algorithms for distributed scalable inference. Including neural networks via MXNET. + +Lots more things missing: programming languages, data sources, etc. But this reply is already way too long. More in the other answers. + + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 diff --git a/TODO/a-case-for-using-storyboards-on-ios.md b/TODO/a-case-for-using-storyboards-on-ios.md new file mode 100644 index 00000000000..36b67970b7b --- /dev/null +++ b/TODO/a-case-for-using-storyboards-on-ios.md @@ -0,0 +1,147 @@ +> * 原文地址:[A Case For Using Storyboards on iOS](https://medium.cobeisfresh.com/a-case-for-using-storyboards-on-ios-3bbe69efbdf4) +> * 原文作者:[Marin Benčević](https://medium.cobeisfresh.com/@marinbenc) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +# A Case For Using Storyboards on iOS # + +![](https://cdn-images-1.medium.com/max/2000/1*YsN0CVtTY3I5d6UUEtUv6Q.png) + +I’ve seen a lot of articles recently that argue against using storyboards when creating iOS apps. The most commonly mentioned arguments are that storyboards are not human readable, they are slow and they cause git conflicts. These are all valid concerns, but can be avoided. I want to tell you how we use storyboards on non-trivial projects, and how you can avoid these concerns and still get the nice things storyboards give you. + +#### Why use storyboards? + +> A picture is worth a thousand words. + +Humans are visual thinkers. The vast majority of information we receive is through our eyes, and our brains are incredibly complex visual pattern matching machines, which help us understand the world around us. + +Storyboards give you an overview of a screen in your app, unmatched by code representation, whether it’s XML or plain Swift. When you open up a storyboard, you can see all views, their positions and their hierarchies in a second. For each view, you can see all the constraints that affect it, and how it interacts with other views. The efficiency of transferring information visually can’t be matched with text. + +Another benefit of storyboards is that it makes auto layout more intuitive. Auto layout is an inherently visual system. It might be a set of mathematical equations under the hood, but we don’t think like that. We think in terms of “this view needs to be next to this one at all times”. Doing auto layout visually is a natural fit. + +![](https://cdn-images-1.medium.com/max/800/1*MS3ALafvQX2fmK-5onF0SQ.png) + +Also, doing auto layout in storyboards gives you some compile-time safety. Most missing or ambiguous constraints are caught during the creation of the layout, not when you open the app. This means less time spent on tracking down ambiguous layouts, or finding out why a view is missing from the screen. + +#### How you should do it #### + +**One storyboard per UIViewController** + +![](https://cdn-images-1.medium.com/max/800/1*5MgjKAMD4kH-3clAiaDT2A.png) + +You wouldn’t write your whole app inside a single UIViewController. The same goes for storyboards. Each view controller deserves its own storyboard. This has several advantages. + +1. **Git conflicts occur only if two developers are working on the same UIViewController in a storyboard at the same time.** In my experience, this doesn’t happen often, and it’s not hard to fix when it does. + +2. **The storyboard is no longer slow to load, since it only loads one UIViewController.** + +3. **You are free to instantiate any UIViewController whichever way you like, just by getting the initial view controller of a storyboard.** Whether you’re using segues or pushing them through code. + +When I’m creating a new screen, my first step is to create a UIViewController. Once I did that, I create a storyboard **with the same name** as the view controller I just created. This lets you do a pretty cool thing: instantiate UIViewControllers in a type safe way, without hard-coded strings. + + let feed = FeedViewController.instance() + // `feed` is of type `FeedViewController` + +This method works by finding a storyboard with the same name as the class name, and getting the initial view controller from that storyboard. + +I know that’s how NIBs are used. But the NIB format is outdated, and some features (like creating UITableViewCells in the actual UIViewController’s nib) are not supported in the .xib editor. I have a feeling that the list of unsupported features will only grow, and that’s why I use storyboards over nibs. + +**No segues** + +Segues seem cool at first, but as soon as you have to transmit data from one screen to the next, it becomes a pain. You have to store the data in some temporary variable somewhere, and then set that value inside the `prepare(for segue:, sender:)` method. + + class UsersViewController: UIViewController, UITableViewDelegate { + + private enum SegueIdentifier { + static let showUserDetails = "showUserDetails" + } + + var usernames: [String] = ["Marin"] + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + usernameToSend = usernames[indexPath.row] + performSegue(withIdentifier: SegueIdentifier.showUserDetails, sender: nil) + } + + + private var usernameToSend: String? + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + + switch segue.identifier { + case SegueIdentifier.showUserDetails?: + + guard let usernameToSend = usernameToSend else { + assertionFailure("No username provided!") + return + } + + let destination = segue.destination as! UserDetailViewController + destination.username = usernameToSend + + default: + break + } + } + + } + +This code has a lot of problems. `prepare(for:sender:)` is not a pure function since it depends on the temporary variable defined above it. Even worse, that variable is optional, and it’s unclear what should happen if it’s nil. + +You need to remember to manually set the *usernameToSend* property, which adds mutable state to our code. You also need to cast the segue’s destination to the type you expect. That’s lot of boilerplate and more than one point of failure. + +I would much rather have a function that takes a non-optional value, and pushes the next view controller with that value. Simple and easy. + + class UsersViewController: UIViewController, UITableViewDelegate { + + var usernames: [String] = ["Marin"] + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let username = usernames[indexPath.row] + showDetail(withUsername: username) + } + + private func showDetail(withUsername username: String) { + let detail = UserDetailViewController.instance() + detail.username = username + navigationController?.pushViewController(detail, animated: true) + } + + } + +This code is much safer, more readable and more concise. + +**All properties are set in code** + +Leave all storyboard values at their defaults. If a label needs to have a different text, or a view needs to have a background color, those things are done in code. This relates especially to all the little checkmarks in the property inspector. + +![](https://cdn-images-1.medium.com/max/800/1*QQ6_kcvyx1Z1vHdUYsc77A.png) + +The reason is that you don’t want to hard-code fonts, colors and texts. You can have constants for those, and a single place where they are kept, so you have a single place to change when you need to make a design change. + +Also, scanning the code for view properties is easier than trying to find which checkmarks are checked in the storyboard. + +This means you can build auto layout and views in the storyboard, but [style them in code](https://medium.cobeisfresh.com/composable-type-safe-uiview-styling-with-swift-functions-8be417da947f), which gives you complete freedom to create reusable code and a human-readable change history. + +#### What storyboards are for me #### + +You might be reading this article and thinking “This guy says storyboards are great, and then says he doesn’t use half of the features!”, and you’re right! + +Storyboards do have problems, and these are the ways I avoid those problems. I find storyboards very useful for what I want to do with them: create the view hierarchy and constraints. Nothing more, nothing less. + +My point is to not disregard a whole technology because you don’t like one aspect of it. You are free to pick and choose which parts you want to use. **It’s not all or nothing.** + +So for those of you who want the benefits or storyboards, but want to minimize the downsides, this is our approach that has worked very well so far. If you have any comments, feel free to leave a response or hit me up on @marinbenc on Twitter. + +*If you liked this one, check out some some other articles from my team:* + +[![](http://ww3.sinaimg.cn/large/006tNbRwgy1ff10iqm6lxj315a0ai76b.jpg)](https://medium.cobeisfresh.com/how-to-win-a-hackathon-tips-tricks-8cd391e18705) + +[![](http://ww4.sinaimg.cn/large/006tNbRwgy1ff10jmsrsej314c0aa0ud.jpg)](https://medium.cobeisfresh.com/accessing-types-from-extensions-in-swift-32ca87ec5190) + + + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 diff --git a/TODO/a-day-without-javascript.md b/TODO/a-day-without-javascript.md new file mode 100644 index 00000000000..cbe8e0925c8 --- /dev/null +++ b/TODO/a-day-without-javascript.md @@ -0,0 +1,175 @@ +> * 原文地址:[A day without Javascript](https://sonniesedge.co.uk/blog/a-day-without-javascript) +> * 原文作者:[A day without Javascript](https://sonniesedge.co.uk/about/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +# A day without Javascript + +As I write this it’s raining outside, and I’m trying to avoid having to go out into the murk and watch the Germans conduct their annual [diversity maneuvers](http://www.karneval-berlin.de/en/). I’ve therefore decided to pass my time by doing the one thing that counts as a religious crime in web dev land: I’m going to turn off javascript in my browser and see what sites work and what sites don’t. + +I know, I know, my life is simply too exciting. + +Now, I know that because I write a lot about the universal web and progressive enhancement, people assume that I must hate javascript. + +This would be an incorrect assumption. + +I simply hate people relying on brittle client-side javascript when there are other alternatives. In the same way as I wouldn’t rely on some unknown minicab firm as the sole way of getting me to the airport for a wedding flight, I don’t like relying on a non-guaranteed technology as the sole way of delivering a web app. + +For me it’s a matter of elegance and simplicity over unnecessary complexity. + +## Too many tabs + +So, for my dreary grey day experiment I restricted myself to just the things open in my browser tabs. For normal people this might be two or three sites. + +Not for me. I have approximately 17 shitmillion tabs open, because I Have a Problem With Tabs. + +No seriously. I can never just close a tab. I’ve tried things like [One Tab](https://www.one-tab.com/) but I just can’t get down to less than 30 in any one window (“I’ll just save that tab for later” I think, each and ever time). Let’s just agree that I need some kind of therapy, and we’ll all be able to move on. + +Anyway, there’s nothing fancy to this experiment. It was a case of turning off javascript in the browser and reloading a site, nothing more. To quickly disable the browser’s JS with one click I used Chrome and the [Toggle Javascript](https://chrome.google.com/webstore/detail/toggle-javascript/cidlcjdalomndpeagkjpnefhljffbnlo) extension - available, ironically enough, via the javascript-only Chrome web store. + +Oh, and for you, sweet reader, I opened these tabs in new windows, so you don’t have to see the pain of 50 tabs open at once. + +## First impressions + +So how was it? Well, with just a few minutes of sans-javascript life under my belt, my first impression was “Holy shit, things are *fast* without javascript”. There’s no ads. There’s no video loading at random times. There’s no sudden interrupts by “DO YOU WANT TO FUCKING SUBSCRIBE?” modals. + +If this were the only manifestation of turning off javascript, I’d do this for the rest of time. However, a lot of things don’t work. Navigation is the most common failure mode. Hamburger menus fail to internally link to a nav section (come on, that’s an easy one kids). Forms die when javascript is taken away (point the form to an endpoint that accepts GET/POST queries ffs). Above the fold *images* fail to load (you do know they’re streaming by default, yes?). + +## The sites + +Let’s get to it. I think I’ve got a pretty representative list of sites in my open tabs (perhaps due to the aforementioned Tab Problem). Howl at me on Twitter if you feel I missed anything particularly important. + +### Feedly + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/feedly.png) + +My very first attempt at sans-JS and I get nothing but a blank white page. Fuck you feedly. + +*sighs, runs hands over face, shouts after Feedly* + +Wait no, Feedly, I’m sorry. I didn’t mean that. It was the coffee talking. Can we talk this over? I like using you to keep up with blog posts. + +But why do you work like this, Feedly? Your devs *could* offer the site in basic HTML and use advanced features such as, er, anchor links, to move to other articles. Then when JS is available, new content can be loaded via JS. + +*Verdict:* Relationship counselling. + +### Twitter + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/twitter.png) + +Twitter shows the normal website (with full content) for a brief moment, then redirects to [mobile.twitter.com](https://mobile.twitter.com) (the old one, not the spanky new React one, of course). This is really frustrating, as the full site would still be great to load without javascript. It could use the same navigation method as the mobile site, where it sets a query parameter to the URL “?max_id=871333359884148737” that dictates what is the latest tweet in your timeline to show. Simple and elegant. + +*Verdict:* Could try harder. + +### Google Chrome + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/chrome_download.png) + +The Google Chrome download page just fails completely, with no notice, only a blank white page. + +*Sigh*. + +*Verdict:* No Chrome for you, you dirty javascriptophobe! + +### Youtube + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/youtube.png) + +Youtube really really wants to load. Really, reallllllly, wants to. But then it fucks things up at the last nanosecond and farts out, showing no video, no preview icons, and no comments (that last one is perhaps a positive). + +Even if the site is doing some funky blob loading of video media, it wouldn’t be hard to put a basic version on the page initially (with `preload="none"`), and then have it upgrade when JS kicks in. + +*Verdict:* Can’t watch My Drunk Kitchen or Superwoman. :( :( :( + +### 24 ways + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/24ways.png) + +I’ve had this open in my tabs for the last 6 months, meaning to read it. Look, I’M SORRY, okay? But holy fuck, this site works great without javascript. All the animations are there (because they’re CSS) and the slide in navigation works (because it internally links to the static version of the menu at the bottom of the page). + +*Verdict:* Class act. Smoooooth. Jazzz. + +### Netflix + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/netflix.png) + +I’m using netflix to try and indoctrinate my girlfriend into watching Star Trek. So far she’s not convinced, mainly because “Tasha *slept with Mr Data?* But it’d be like fucking your microwave”. + +Anyway, Netflix doesn’t work. Well, it loads the header, if you want to count that. I get why they don’t do things with HTML5 - DRMing all yo shit needs javascript. But still :(. + +*Verdict:* JavaScript-only is the New Black + +### NYtimes + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/nytimes_with_js.png) + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/nytimes_sans_js.png) + +Not sure why this was in my tab list, but tbh I’ve found rotting tabs from 2015 in there, so I’m not surprised. + +The NY Times site loads in *561ms* and 957kb without javascript. Holy crap, that’s what it should be like normally. For reference it took 12000ms (12seconds) and 4000kb (4mb) to load *with* javascript. Oh, and as a bonus, you get a screenful of adverts. + +A lot of images are lazy loaded, and so don’t work, getting replaced with funky loading icons. But hey ho, I can still read the stories. + +*Verdict:* Failing… to *not* work. Sad! + +### BBC News + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/bbc_news.png) + +It’s the day after the latest London terrorism attacks, and so I’ve got this open, just to see how the media intensifies and aids every terrorist action. The BBC is the inventor and poster-child for progressive enhancement via Cutting the Mustard, and it doesn’t disappoint. The non-CTM site works fully and while it doesn’t *look* the same as the full desktop site (it’s mobile-first and so is pretty much the mobile version), it still *works*. + +*Verdict:* Colman’s Mustard + +### Google search + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/google.png) + +Without JS, Google search still does what it’s best at: searching. + +Okay, there’s no autocomplete, the layout reverts to the early 2000s again, and image search is shockingly bad looking. But, in the best progressive enhancement manner, you can still perform your core tasks. + +*Verdict:* Solid. + +### Wikipedia + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/wikipedia.png) + +Like a good friend, Wikipedia never disappoints. The site is indistinguishable from the JS version. Keep being beautiful, Wikipedia. + +*Verdict:* BFFs. + +### Amazon + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/amazon.png) + +The site looks a little… *off* without JS (the myriad accordions vomit their content over the page when JS isn’t there to keep them under control). But the entire site works! You can still search, you still get recommendations. You can still add items to your basket, and you can still proceed to the checkout. + +*Verdict:* Amazonian warrior. + +### Google Maps + +![](https://sonniesedge.co.uk/images/posts/a-day-without-javascript/google_maps.png) + +Discounting Gmail, Google Maps is perhaps one of the most heavily used Single Page Applications out there. As such I expected some kind of fallback, like Gmail provides, even if it wasn’t true progressive enhancement. Maybe some kind of Streetmap style tile-by-tile navigation fallback? + +But it failed completely. + +*Verdict:* Cartography catastrophe. + +## Overall verdict + +This has made me appreciate the number of large sites that make the effort to build robust sites that work for everybody. But even on those sites that are progressively enhanced, it’s a sad indictment of things that they can be so slow on the multi-core hyperpowerful Mac that I use every day, but immediately become fast when JavaScript is disabled. + +It’s even sadder when using a typical site and you realise how much Javascript it downloads. I now know why my 1GB mobile data allowance keeps burning out at least… + +I maintain that it’s perfectly possible to use the web without javascript, especially on those sites that are considerate to the diversity of devices and users out there. And if I want to browse the web without javascript, well fuck, that’s my choice as a user. This is the web, not the Javascript App Store, and we should be making sure that things work on even the most basic device. + +I think I’m going to be turning off javascript more, just on principle. + +Haters, please tweet at me as you feel fit. + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 diff --git a/TODO/a-first-walk-into-kotlin-coroutines-on-android.md b/TODO/a-first-walk-into-kotlin-coroutines-on-android.md new file mode 100644 index 00000000000..781e976330a --- /dev/null +++ b/TODO/a-first-walk-into-kotlin-coroutines-on-android.md @@ -0,0 +1,199 @@ +> * 原文地址:[A first walk into Kotlin coroutines on Android](https://android.jlelse.eu/a-first-walk-into-kotlin-coroutines-on-android-fe4a6e25f46a) +> * 原文作者:[Antonio Leiva](https://android.jlelse.eu/@antoniolg) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者:[Feximin](https://github.com/Feximin) +> * 校对者:[wilsonandusa](https://github.com/wilsonandusa) 、[atuooo](https://github.com/atuooo) + +--- + +# 第一次走进 Android 中的 Kotlin 协程 + +![](https://cdn-images-1.medium.com/max/800/1*QU1_XFQvVRS5at9EHTqBkw.jpeg) + +> 本文提取并改编自最近更新的 [Kotlin for Android Developers](https://antonioleiva.com/book) 一书。 + + +协程是 Kotlin 1.1 引入的最牛逼的功能。他们确实很棒,不但很强大,而且社区仍然在挖掘如何使他们得到更加充分的利用。 + +简单来说,协程是一种按序写异步代码的方式。**你可以一行一行地写代码,而不是到处都有乱七八糟的回调**。有的还将会有暂停执行然后等待结果返回的能力。 + +如果你以前是 C# 程序员,async/await 是最接近的概念。但是 Kotlin 中的协程功能更强大,因为他们不是一个特定想法的实现,而是**一个语言级别的功能,可以有多种实现去解决各种问题**。 + +你可以编写自己的实现,或者使用一个 Kotlin 团队和其他独立开发者已经构建好的实现。 + +你要明白**协程在 Kotlin 1.1 中是一个实验性的功能**。这意味着当前实现在将来可能会改变,尽管旧的实现仍将被支持,但你有可能想迁移到新的定义上。如我们稍后将见,你需要去选择开启这个特性,否则在使用的时候会有警告。 + +这也意味着你应该将本文视为一个(协程)可以做些什么的示例而不是一个经验法则。未来几个月可能会有很大变动。 + +--- + +### 理解协程如何工作 + +本文旨在让你了解一些基本概念,会用一个现有的库,而不是去自己去实现一个。但我认为重要的是了解一些内部原理,这样你就不会盲目使用了。 + +协程基于**暂停函数**的想法:那些函数被调用之后**可以终止(程序)执行**,一旦完成他们自己的任务之后又可以让他(程序)继续执行。 + +暂停函数用保留关键字 `suspend` 来标记,而且只能在其他暂停函数或协程内部被调用。 + +这意味着你不能随便调用一个暂停函数。需要有一个包裹函数来构建协程并提供所需的上下文。类似这样的: + + fun async(block: suspend () -> T) + +我并不是在解释如何实现上述方法。那是一个复杂的过程,不在本文范围内,并且大多情况下已经有多种实现好的方法了。 + +如果你确实有兴趣实现自己的,你可以读一下 [**coroutines Github**](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md) 中所写的规范。你仅需要知道的是:方法名字可以随意取,至少有一个暂停块做为参数。 + +然后你可以实现一个暂停函数并在块中调用: + + suspend fun mySuspendingFun(x: Int) : Result { + … + } + + async { + val res = mySuspendingFun(20) + print(res) + } + +协程是线程吗?不完全是。他们的工作方式相似,但是(协程)更轻量、更有效。你可以有数以百万的协程运行在少量的几个线程中,这打开了一个充满可能性的世界。 + +使用协程功能有三种方式: + +- **原始实现**:意思是创建你自己的方式去使用协程。这非常复杂并且通常不是必要的。 +- **底层实现**: Kotlin 提供了一套库,解决了一些最难的部分并提供了不同场景下的具体实现,你可以在 [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) 仓库中找到这些库,比如说: [one for Android](https://github.com/Kotlin/kotlinx.coroutines/tree/master/ui/kotlinx-coroutines-android) 。 +- **高级实现**:如果你只是想要**一个可以提供一切你所需的解决方案**来开始马上使用协程的话,有几个库可以使用,他们为你做了所有复杂的工作,并且(库的)数量在持续增长。我推荐 [Anko](https://github.com/Kotlin/anko),他提供了一个可以很好的工作在 Android 上的方案,有可能你已经很熟悉了。 + +--- + +### 使用 Anko 实现协程 + +自从 0.10 版本以来,Anko 提供了两种方法以在 Android 上使用协程。 + +第一种与我们在上面的例子中看到的非常相似,和其他的库所做的也类似。 + +首先,你需要创建**一个可以调用暂停函数的异步块**: + + async(UI) { + … + } + +UI参数是 `async` 块的执行上下文。 + +然后你可以创建**在后台线程中执行的块**,将结果返回给UI线程。那些块以 `bg` 方法定义: + + async(UI) { + val r1: Deferred = bg { fetchResult1() } + val r2: Deferred = bg { fetchResult2() } + updateUI(r1.await(), r2.await()) + } + +`bg` 返回一个 `Deferred` 对象,这个对象**在 `await()` 方法被调用后会暂停协程**,直到有结果返回。我们将在下面的例子中采用这种方案。 + +正如你可能知道的,由于 Kotlin 编译器能够[推导出变量类型](https://antonioleiva.com/variables-kotlin/),因此可以更加简单: + + async(UI) { + val r1 = bg { fetchResult1() } + val r2 = bg { fetchResult2() } + updateUI(r1.await(), r2.await()) + } + +第二种方法是利用与特定子库中提供的监听器的集成,这取决于你打算使用哪个监听器。 + +例如,在 `anko-sdk15-coroutines` 中有一个 `onClick` 监听器,他的 lambda 实际上是一个协程。这样你就可以在监听器代码块上立即使用暂停函数: + + textView.onClick { + val r1 = bg { fetchResult1() } + val r2 = bg { fetchResult2() } + updateUI(r1.await(), r2.await()) + } + +如你所见,结果与之前的很相似。只是少了一些代码。 + +为了使用他,你需要添加一些依赖,这取决于你想要使用哪些监听器: + + compile “org.jetbrains.anko:anko-sdk15-coroutines:$anko_version” + compile “org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version” + compile “org.jetbrains.anko:anko-design-coroutines:$anko_version” + +--- + +### 在示例中使用协程 + +在[这本书](https://antonioleiva.com/book)所解释的例子(你可以在[这里](https://github.com/antoniolg/Kotlin-for-Android-Developers)找到)中,我们创建了一个简单的天气应用。 + +为了使用 Anko 协程,我们首先需要添加这个新的依赖: + + compile “org.jetbrains.anko:anko-coroutines:$anko_version” + +接下来,如果你还记得,我曾经告诉过你需要选择使用这个功能,否则就会出现警告。要做到这一点(使用协程功能),只需要简单地在根文件夹下的 `gradle.properties` 文件(如果不存在就创建)中添加这一行: + + kotlin.coroutines=enable + +现在,你已经准备好开始使用协程了。让我们首先进入详情 activity 中。他只是使用一个特定的命令调用了数据库(用来缓存每周的天气预报数据)。 + +这是生成的代码: + + async(UI) { + val id = intent.getLongExtra(ID, -1) + val result = bg { RequestDayForecastCommand(id) + .execute() } + bindForecast(result.await()) + } + +太棒了!天气预报数据是在一个后台线程中请求的,这多亏了 `bg` 方法,这个方法返回了一个延迟结果。那个延迟结果在可以返回前会一直在 `bindForecast` 调用中等待。 + +但并不是一切都好。发生了什么?协程有一个问题:**他们持有一个 `DetailActivity` 的引用,如果这个请求永不结束就会内存泄露**。 + +别担心,因为 Anko 有一个解决方案。你可以为你的 activity 创建一个弱引用,然后使用那个弱引用来代替: + + val ref = asReference() + val id = intent.getLongExtra(ID, -1) + + async(UI) { + val result = bg { RequestDayForecastCommand(id).execute() } + ref().bindForecast(result.await()) + } + +在 activity 可用时,弱引用允许访问 activity,当 activity 被杀死,协程将会取消。需要仔细确保的是所有对 activity 中的方法或属性的调用都要经过这个 `ref` 对象。 + +但是如果协程多次和 activity 交互的话会有点复杂。例如,在 `MainActivity` 使用这个方案将变得更加复杂。 + +这个 activity 将基于一个 zipCode 来调用一个端点来请求一周的天气预报数据: + + private fun loadForecast() { + + val ref = asReference() + val localZipCode = zipCode + + async(UI) { + val result = bg { RequestForecastCommand(localZipCode).execute() } + val weekForecast = result.await() + ref().updateUI(weekForecast) + } + } + +你不能在 `bg` 块中使用 `ref()` ,因为在那个块中的代码不是一个暂停上下文,因此你需要将 `zipCode` 保存在另一个本地变量中。 + +老实说,我认为泄露 activity 对象 1-2 秒没那么糟糕,不过有可能不能成为样板代码。因此如果你能确保你的后台处理不会永远不结束(比如,为你的服务器请求设置一个超时)的话,不使用 `asReference()` 也是安全的。 + +这样的话,`MainActivity` 将变得更加简单: + + private fun loadForecast() = async(UI) { + val result = bg { RequestForecastCommand(zipCode).execute() } + updateUI(result.await()) + } + +综上,你已经可以一种非常简单的同步方式来写你的异步代码。 + +这些代码非常简单,但是想象一下复杂的情况:后台操作的结果被下一个后台操作使用,或者当你需要遍历列表并为每一项都执行请求的时候。 + +所有一切都可以写成常规的同步代码,写起来、维护起来将更加容易。 + +--- + +关于如何充分利用协程还有很多需要学习。如果你有更多相关的经验,请评论以让我们更加了解协程。 + +如果你刚刚开始学习 Kotlin ,你可以看看[我的博客](https://antonioleiva.com/kotlin),[这本书](https://antonioleiva.com/book),或者关注我的 [Twitter](https://twitter.com/lime_cl), [LinkedIn](https://www.linkedin.com/in/antoniolg/) 或者 [Github](https://github.com/antoniolg/) 。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 diff --git a/TODO/a-follow-up-on-how-to-store-tokens-securely-in-android.md b/TODO/a-follow-up-on-how-to-store-tokens-securely-in-android.md new file mode 100644 index 00000000000..94a04ff40e7 --- /dev/null +++ b/TODO/a-follow-up-on-how-to-store-tokens-securely-in-android.md @@ -0,0 +1,178 @@ +> * 原文地址:[A follow-up on how to store tokens securely in Android](https://medium.com/@enriquelopezmanas/a-follow-up-on-how-to-store-tokens-securely-in-android-e84ac5f15f17) +> * 原文作者:[Enrique López Mañas](https://medium.com/@enriquelopezmanas) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: [lovexiaov](https://github.com/lovexiaov) +> * 校对者:[luoqiuyu](https://github.com/luoqiuyu) [hackerkevin](https://github.com/hackerkevin) + +![](https://cdn-images-1.medium.com/max/2000/1*nWjJ7GKUSVEQMC0srKK-9Q.jpeg) + +# 再谈如何安全地在 Android 中存储令牌 # + +作为本文的序言,我想对读者做一个简短的声明。下面的引言对本文的后续内容而言十分重要。 + +> 没有绝对的安全。所谓的安全是指利用一系列措施的堆积和组合,来试图延缓必然发生的事情。 + +大约 3 年前,我写了[一篇文章](http://codetalk.de/?p=86),给出了几种方法来防止潜在攻击者反编译我们 Android 应用窃取字符串令牌。为了便于回忆,也为了防止不可避免的网络瘫痪,我将会在此重新列出一些章节。 + +客户端应用与服务端的交互是最常见的场景之一。数据交换时的敏感度差别很大,并且登录请求、用户数据更改请求等之间交换的数据类型也变化多样。 + +首先要提到并应用的技术是使用 [SSL](http://info.ssl.com/article.aspx?id=10241)(安全套接层)链接客户端与服务端。再看一下文章开头的引言。尽管这样做是一个良好的开端,但这并不能确保绝对的隐私和安全。 + +当你使用 SSL 连接时(也就是当你看到浏览器上有一个小锁时),这意味着你与服务器之间的连接被加密了。理论上讲,没有什么能够访问到你请求里的信息(*) + +(*)我说过绝对的安全不存在吧?SSL 连接仍然可以被攻破。本文不打算提供所有可能的攻击手段列表,只想让你了解几种攻击的可能性。比如,可以伪造 SSL 证书,或者进行中间人攻击。 + +我们继续。假设客户端正在通过加密的 SSL 通道与后台链接,它们在愉快的交换有用的数据,执行业务逻辑。但是我们还想提供一个额外的安全层。 + +接下来要采取的措施是在通信中使用授权令牌或 API 密钥。当后台收到一个请求时,我们如何判断该请求是来自认证的客户端而不是任意一个想要获取我们 API 数据的家伙?后台会检查该客户端是否提供了一个有效的 API 密钥。如果密钥有效,则执行请求操作,否则拒绝该请求并根据业务需求采取一些措施(当出现此情况时,我一般会纪录他们的 IP 地址和客户端 ID,看一下他们的访问频率。如果频率高于我的忍受范围,我会考虑禁止并观察一下这个无礼的家伙想要得到什么)。 + +让我们从头开始构建我们的城堡吧。在我们的应用中,添加一个叫做 API_KEY 的变量,该变量会自动注入到每次的请求(如果是 Android 应用,可能会是你的 Retrofit 客户端)中。 + +```java +private final static String API_KEY = “67a5af7f89ah3katf7m20fdj202” +``` + +很好,这样可以帮助我们鉴定客户端。但问题在于它本身并没有提供一个十分有效的安全保证。 + +如果你使用 [apktool](https://ibotpeaches.github.io/Apktool/) 反编译该应用,然后搜索该字符串,你会在其中一个 .smali 文件中发现: + +```smali +const-string v1, “67a5af7f89ah3katf7m20fdj202” +``` + +是的,我知道。这并不能保证是一个有效的令牌,所以我们仍然需要通过一个精确的验证来决定如何找到那个字符串,和它是否可以用来通过验证。但是你知道我要表达什么:这通常只是时间和资源的问题。 + +Proguard 是否会能我们保证该字符串的安全呢?并不能。Proguard 在[常见问题](http://proguard.sourceforge.net/FAQ.html#encrypt)中提到了字符串的加密是完全不可能的。 + +那将字符串保存到 Android 提供的其他存储机制中呢,比如说 SharedPreferences?这并不是一个好方法。在模拟器或者 root 过的设备中可以轻易的访问到 SharedPreferences。几年前,一个叫 [Srinivas](http://resources.infosecinstitute.com/android-hacking-security-part-9-insecure-local-storage-shared-preferences/) 的伙计向我们证明了如何更改一个视频游戏中的得分。跑题了! + +#### 原生开发工具包 (NDK) #### + +我将会更新我提出的初始模型,不断迭代它,以提供更安全的替代方案。我们假设有两个函数分别负责加密和解密数据: + +```java + private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception { + SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec); + byte[] encrypted = cipher.doFinal(clear); + return encrypted; + } + + private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception { + SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.DECRYPT_MODE, skeySpec); + byte[] decrypted = cipher.doFinal(encrypted); + return decrypted; + } +``` + +代码没啥好说的。这两个函数会使用一个密钥值和一个被用来编/解码的字符串作为入参。它们会返回相应的加密或解密过的字符串。我们会用如下方式调用它们: + +```java +ByteArrayOutputStream baos = new ByteArrayOutputStream(); +bm.compress(Bitmap.CompressFormat.PNG, 100, baos); +byte[] b = baos.toByteArray(); + +byte[] keyStart = "encryption key".getBytes(); +KeyGenerator kgen = KeyGenerator.getInstance("AES"); +SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); +sr.setSeed(keyStart); +kgen.init(128, sr); +SecretKey skey = kgen.generateKey(); +byte[] key = skey.getEncoded(); + +// encrypt +byte[] encryptedData = encrypt(key,b); +// decrypt +byte[] decryptedData = decrypt(key,encryptedData); +``` + +猜到为什么要这么做了吗?是的,我们可以根据需求来加/解密令牌。这就为我们提供了一个额外的安全层:当代码混淆后,寻找令牌不再像执行字符串搜索和检查字符串周围的环境那样简单了。但是,你能指出还有一个需要解决的问题吗? + +找到了吗? + +如果还没找到就多花点时间。 + +是的。我们仍然有一个加密密钥以字符串的形式存储。虽然这种隐晦的做法增加了更多的安全层,但不管这个令牌是用于加密或它本身就是一个令牌,我们仍然有一个以明文形式存在的令牌。 + +现在,我们将使用 NDK 来继续迭代我们的安全机制。 + +NDK 允许我们在 Android 代码中访问 C++ 代码库。首先我们来想一下要做什么。我们可以在一个 C++ 函数中存放 API 密钥或者敏感数据。该函数可以在之后的代码中调用,避免了在 Java 文件中存储字符串。这就提供了一个自动的保护机制来防止反编译技术。 + +C++ 函数如下: + +```cpp +Java_com_example_exampleApp_ExampleClass_getSecretKey( JNIEnv* env, + jobject thiz ) +{ + return (*env)->NewStringUTF(env, "mySecretKey"."); +} +``` + +在 Java 代码中调用它也很简单: + +```java +static { + System.loadLibrary("library-name"); + } + +public native String getSecretKey(); +``` + +在加/解密函数中会这样调用: + +```java +byte[] keyStart = getSecretKey().getBytes(); +``` + +此时我们生成 APK,混淆它,然后反编译并尝试在原生函数 getSecretKey() 中查找该字符串,无法找到!胜利了吗? + +并没有!NDK 代码其实也可以被反汇编和检查。只是难度较高,需要更高级的工具和技术。虽然这样可以摆脱掉 95% 的脚本小子,但一个有充足资源和动机的团队让然可以拿到令牌。还记得这句话吗? + +> 没有绝对的安全。所谓的安全是指利用一系列措施的堆积和组合,来试图延缓必然发生的事情。 + +![](https://cdn-images-1.medium.com/max/800/1*JPErsmBbKjKbFoQYJAoUkg.png) + +你仍然可以在反汇编代码中找到该字符串字面值。[Hex Rays](https://www.hex-rays.com/products/decompiler/) 在反编译原生文件方面就做的很好。我很确信有一大堆的工具可以解构 Android 生成的任意原生代码(我跟 Hex Rays 并没有关系,也没有从他们那里拿到任何形式的资金酬劳)。 + +那么,我们要使用哪种方案来避免后台与客户端的通信被标记呢? + +**在设备上实时生成密钥。** + +你的设备不需要存储任何形式的密钥并处理各种保护字符串字面值的麻烦!这是在服务中用到的非常古老的技术,比如远程密钥验证。 + +1. 客户端知道有个函数会返回一个密钥。 +2. 后台知道在客户端中实现的那个函数。 +3. 客户端通过该函数生成一个密钥,并发送到服务器上。 +4. 服务器验证密钥,并根据请求执行相应的操作。 + +抓到重点了吗?为什么不使用返回三个随机素数( 1~100 之间)之和的函数来代替返回一个字符串(很容易被识别)的原生函数呢?或者拿到当天的 UNIX 时间,然后给每一位数字加 1?通过设备的一些上下文相关信息(如正在使用的内存量)来提供一个更高程度的熵值? + +上面这段包含了一些想法,希望读者们已经得到重点了。 + +### **总结** ### + +1. 绝对的安全是不存在的。 +2. 多种保护手段的结合是达到高安全度的关键。 +3. 不要在代码中存储字符串明文。 +4. 使用 NDK 来创建自生成的密钥。 + +还记得开头的那段话吧? + +> 没有绝对的安全。所谓的安全是指利用一系列措施的堆积和组合,来试图延缓必然发生的事情。 + +我想再强调一次,你的目标是尽可能的保护你的代码,同时不要忘记 100% 的安全是不可能的。但是,如果你能保证解密你代码中任意的敏感信息都需要耗费大量的资源,你就能安心睡觉啦。 + +### 一个小小的免责声明 ### + +我知道,读到此处,纵观整文,你会纳闷“这家伙怎么讲了所有麻烦的方法而没有提到 [Dexguard](https://www.guardsquare.com/en/dexguard) 呢?”。是的 Dexguard 可以混淆字符串,他们在这方面做的很好。然而 Dexguard 的售价[让人望而却步](http://thinkdiff.net/mobile/dexguard-480-eur-to-10313-eur-the-worst-software-do-not-use/)。我在之前的公司的关键安全系统中使用过 Dexguard,但这也许并不是一个适合所有人的选择。再说了,像生活一样,在软件开发中选择越多世界越丰富多彩。 + +愉快的编码吧! + +我会在 [Twitter](https://twitter.com/eenriquelopez) 上写一些关于软件工程和生活点滴的思考。如果你喜欢此文,或者它能帮到你,请随意分享,点赞或者留言。这是业余作者写作的动力。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 diff --git a/TODO/a-functional-programmers-introduction-to-javascript-composing-software.md b/TODO/a-functional-programmers-introduction-to-javascript-composing-software.md new file mode 100644 index 00000000000..761d5e5c400 --- /dev/null +++ b/TODO/a-functional-programmers-introduction-to-javascript-composing-software.md @@ -0,0 +1,496 @@ +> * 原文地址:[A Functional Programmer’s Introduction to JavaScript (Composing Software)(part 3)](https://medium.com/javascript-scene/a-functional-programmers-introduction-to-javascript-composing-software-d670d14ede30) +> * 原文作者:[Eric Elliott](https://medium.com/@_ericelliott?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者:[sunui](http://suncafe.cc) +> * 校对者:[Aladdin-ADD](https://github.com/Aladdin-ADD)、[avocadowang](https://github.com/avocadowang) + +# 函数式程序员的 JavaScript 简介 (软件编写) # + + + +烟雾艺术魔方 — MattysFlicks — (CC BY 2.0) + +> 注意:这是“软件编写”系列文章的第三部分,该系列主要阐述如何在 JavaScript ES6+ 中从零开始学习函数式编程和组合化软件(compositional software)技术(译注:关于软件可组合性的概念,参见维基百科 [Composability](https://en.wikipedia.org/wiki/Composability))。后续还有更多精彩内容,敬请期待! +> [< 上一篇](https://github.com/gy134340/gold-miner/blob/69bca85e75f4b99b33c193c21f577db93622ee8b/TODO/why-learn-functional-programming-in-javascript-composing-software.md) | [<<第一篇](https://github.com/xitu/gold-miner/blob/master/TODO/the-rise-and-fall-and-rise-of-functional-programming-composable-software.md) | [下一篇 >](https://github.com/xitu/gold-miner/blob/master/TODO/higher-order-functions-composing-software.md) + +对于不熟悉 JavaScript 或 ES6+ 的同学,这里做一个简短的介绍。无论你是 JavaScript 开发新手还是有经验的老兵,你都可能学到一些新东西。以下内容仅是浅尝辄止,吊吊大家的兴致。如果想知道更多,还需深入学习。敬请期待吧。 + + +学习编程最好的方法就是动手编程。我建议您使用交互式 JavaScript 编程环境(如 [CodePen](https://codepen.io/) 或 [Babel REPL](https://babeljs.io/repl/))。 + +或者,您也可以使用 Node 或浏览器控制台 REPL。 + +### 表达式和值 ### + +表达式是可以求得数据值的代码块。 + +下面这些都是 JavaScript 中合法的表达式: + +``` +7; + +7 + 1; // 8 + +7 * 2; // 14 + +'Hello'; // Hello +``` + +表达式的值可以被赋予一个名称。执行此操作时,表达式首先被计算,取得的结果值被赋值给该名称。对于这一点我们将使用 `const` 关键字。这不是唯一的方式,但这将是你使用最多的,所以目前我们就可以坚持使用 `const`。 + +``` +const hello = 'Hello'; +hello; // Hello +``` + +### var、let 和 const ### + +JavaScript 支持另外两种变量声明关键字:`var`,还有 `let`。我喜欢根据选择的顺序来考虑它们。默认情况下,我选择最严格的声明方式:`const`。用 `const` 关键字声明的变量不能被重新赋值。最终值必须在声明时分配。这可能听起来很严格,但限制是一件好事。这是个标识在提醒你“赋给这个名称的值将不会改变”。它可以帮你全面了解这个名称的意义,而无需阅读整个函数或块级作用域。 + +有时,给变量重新赋值很有用。比如,如果你正在写一个手动的强制性迭代,而不是一个更具功能性的方法,你可以迭代一个用 `let` 赋值的计数器。 + +因为 `var` 能告诉你很少关于这个变量的信息,所以它是最无力的声明标识。自从开始用 ES6,我就再也没在实际软件项目中有意使用 `var` 作声明了。 + +注意一下,一个变量一旦用 `let` 或 `const` 声明,任何再次声明的尝试都将导致报错。如果你在 REPL(读取-求值-输出循环)环境中更喜欢多一些实验性和灵活性,那么建议你使用 `var` 声明变量,与 `let` 和 `const` 不同,使用 `var` 重新声明变量是合法的。 + +本文将使用 const 来让您习惯于为实际程序中用 `const`,而出于试验的目的自由切换回 `var`。 + +### 数据类型 ### + +目前为止我们见到了两种数据类型:数字和字符串。JavaScript 也有布尔值(`true` 或 `false`)、数组、对象等。稍后我们再看其他类型。 + +数组是一系列值的有序列表。可以把它比作一个能够装很多元素的容器。这是一个数组字面量: + +``` +[1, 2, 3]; +``` + +当然,它也是一个可被赋予名称的表达式: + +``` +const arr = [1, 2, 3]; +``` + +在 JavaScript 中,对象是一系列键值对的集合。它也有字面量: + +``` +{ + key: 'value' +} +``` + +当然,你也可以给对象赋予名称: + +``` +const foo = { + bar: 'bar' +} +``` + +如果你想将现有变量赋值给同名的对象属性,这有个捷径。你可以仅输入变量名,而不用同时提供一个键和一个值: + +``` +const a = 'a'; +const oldA = { a: a }; // 长而冗余的写法 +const oA = { a }; // 短小精悍! +``` + +为了好玩而已,让我们再来一次: + +``` +const b = 'b'; +const oB = { b }; +``` + +对象可以轻松合并到新的对象中: + +``` +const c = {...oA, ...oB}; // { a: 'a', b: 'b' } +``` + +这些点是对象扩展运算符。它迭代 `oA` 的属性并分配到新的对象中,`oB` 也是一样,在新对象中已经存在的键都会被重写。在撰写本文时,对象扩展是一个新的试验特性,可能还没有被所有主流浏览器支持,但如果你那不能用,还可以用 `Object.assign()` 替代: + +``` +const d = Object.assign({}, oA, oB); // { a: 'a', b: 'b' } +``` + +这个 `Object.assign()` 的例子代码很少,如果你想合并很多对象,它甚至可以节省一些打字。注意当你使用 `Object.assign()` 时,你必须传一个目标对象作为第一个参数。它就是那个源对象的属性将被复制过去的对象。如果你忘了传,第一个参数传递的对象将被改变。 + +以我的经验,改变一个已经存在的对象而不创建一个新的对象常常引发 bug。至少至少,它很容易出错。要小心使用 `Object.assign()`。 + +### 解构 ### + +对象和数组都支持解构,这意味着你可以从中提取值分配给命过名的变量: + +``` +const [t, u] = ['a', 'b']; +t; // 'a' +u; // 'b' + +const blep = { + blop: 'blop' +}; + +// 下面等同于: +// const blop = blep.blop; +const { blop } = blep; +blop; // 'blop' +``` + +和上面数组的例子类似,你可以一次解构多次分配。下面这行你在大量的 Redux 项目中都能见到。 + +``` +const { type, payload } = action; +``` + +下面是它在一个 reducer(后面的话题再详细说) 的上下文中的使用方法。 + +``` +const myReducer = (state = {}, action = {}) => { + const { type, payload } = action; + switch (type) { + case 'FOO': return Object.assign({}, state, payload); + default: return state; + } +}; +``` + +如果不想为新绑定使用不同的名称,你可以分配一个新名称: + +``` +const { blop: bloop } = blep; +bloop; // 'blop' +``` + +读作:把 `blep.blop` 分配给 `bloop`。 + +### 比较运算符和三元表达式 ### + +你可以用严格的相等操作符(有时称为“三等于”)来比较数据值: + +``` +3 + 1 === 4; // true +``` + +还有另外一种宽松的相等操作符。它正式地被称为“等于”运算符。非正式地可以叫“双等于”。双等于有一两个有效的用例,但大多数时候默认使用 `===` 操作符是更好的选择。 + + +其它比较操作符有: + +- `>` 大于 +- `<` 小于 +- `>=` 大于或等于 +- `<=` 小于或等于 +- `!=` 不等于 +- `!==` 严格不等于 +- `&&` 逻辑与 +- `||` 逻辑或 + +三元表达式是一个可以让你使用一个比较器来问问题的表达式,运算出的不同答案取决于表达式是否为真: + +``` +14 - 7 === 7 ? 'Yep!' : 'Nope.'; // Yep! +``` + +### 函数 ### + +JavaScript 支持函数表达式,函数可以这样分配名称: + +``` +const double = x => x * 2; +``` + +这和数学表达式 `f(x) = 2x` 是一个意思。大声说出来,这个函数读作 `x` 的 `f` 等于 `2x`。这个函数只有当你用一个具体的 `x` 的值应用它的时候才有意思。在其它方程式里面你写 `f(2)`,就等同于 `4`。 + +换种说话就是 `f(2) = 4`。您可以将数学函数视为从输入到输出的映射。这个例子里 `f(x)` 是输入数值 `x` 到相应的输出数值的映射,等于输入数值和 `2` 的乘积。 + +在 JavaScript 中,函数表达式的值是函数本身: + +``` +double; // [Function: double] +``` + +你可以使用 `.toString()` 方法看到这个函数的定义。 + +``` +double.toString(); // 'x => x * 2' +``` + +如果要将函数应用于某些参数,则必须使用函数调用来调用它。函数调用会接收参数并且计算一个返回值。 + +你可以使用 `(argument1, argument2, ...rest)` 调用一个函数。比如调用我们的 double 函数,就加一对括号并传进去一个值: + +``` +double(2); // 4 +``` + +和一些函数式语言不同,这对括号是有意义的。没有它们,函数将不会被调用。 + +``` +double 4; // SyntaxError: Unexpected number +``` + +### 签名 ### + +函数的签名可以包含以下内容: + +1. 一个 **可选的** 函数名。 +2. 在括号里的一组参数。 参数的命名是可选的。 +3. 返回值的类型。 + +JavaScript 的签名无需指定类型。JavaScript 引擎将会在运行时断定类型。如果你提供足够的线索,签名信息也可以通过开发工具推断出来,比如一些 IDE(集成开发环境)和使用数据流分析的 [Tern.js](http://ternjs.net/)。 + +JavaScript 缺少它自己的函数签名语法,所以有几个竞争标准:JSDoc 在历史上非常流行,但它太过笨拙臃肿,没有人会不厌其烦地维护更新文档与代码同步,所以很多 JS 开发者都弃坑了。 + +TypeScript 和 Flow 是目前的大竞争者。这二者都不能让我确定地知道怎么表达我需要的一切,所以我使用 [Rtype](https://github.com/ericelliott/rtype),仅仅用于写文档。一些人倒退回 Haskell 的 curry-only [Hindley–Milner 类型系统](http://web.cs.wpi.edu/~cs4536/c12/milner-damas_principal_types.pdf)。如果仅用于文档,我很乐意看到 JavaScript 能有一个好的标记系统标准,但目前为止,我觉得当前的解决方案没有能胜任这个任务的。现在,怪异的类型标记即使和你在用的不尽相同,也就将就先用着吧。 + +``` +functionName(param1: Type, param2: Type) => Type +``` + +double 函数的签名是: + +``` +double(x: n) => n +``` + +尽管事实上 JavaScript 不需要注释签名,知道何为签名和它意味着什么依然很重要,它有助于你高效地交流函数是如何使用和如何构建的。大多数可重复使用的函数构建工具都需要你传入同样类型签名的函数。 + +### 默认参数值 ### + +JavaScript 支持默认参数值。下面这个函数类似一个恒等函数(以你传入参数为返回值的函数),一旦你用 `undefined` 调用它,或者根本不传入参数——它就会返回 0,来替代: + +``` +const orZero = (n = 0) => n; +``` + +如上,若想设置默认值,只需在传入参数时带上 `=` 操作符,比如 `n = 0`。当你用这种方式传入默认值,像 [Tern.js](http://ternjs.net/)、Flow、或者 TypeScript 这些类型检测工具可以自行推断函数的类型签名,甚至你不需要刻意声明类型注解。 + +结果就是这样,在你的编辑器或者 IDE 中安装正确的插件,在你输入函数调用时,你可以看见内联显示的函数签名。依据它的调用签名,函数的使用方法也一目了然。无论起不起作用,使用默认值可以让你写出更具可读性的代码。 + +> 注意: 使用默认值的参数不会增加函数的 `.length` 属性,比如使用依赖 `.length` 值的自动柯里化会抛出不可用异常。如果你碰上它,一些柯里化工具(比如 `lodash/curry`)允许你传入自定义参数来绕开这个限制。 + +### 命名参数 ### + +JavaScript 函数可以传入对象字面量作为参数,并且使用对象解构来分配参数标识,这样做可以达到命名参数的同样效果。注意,你也可以使用默认参数特性传入默认值。 + +``` +const createUser = ({ + name = 'Anonymous', + avatarThumbnail = '/avatars/anonymous.png' +}) => ({ + name, + avatarThumbnail +}); + +const george = createUser({ + name: 'George', + avatarThumbnail: 'avatars/shades-emoji.png' +}); + +george; +/* +{ + name: 'George', + avatarThumbnail: 'avatars/shades-emoji.png' +} +*/ +``` + +### 剩余和展开 ### + +JavaScript 中函数共有的一个特性是可以在函数参数中使用剩余操作符 `...` 来将一组剩余的参数聚集到一起。 + +例如下面这个函数简单地丢弃第一个参数,返回其余的参数: + +``` +const aTail = (head, ...tail) => tail; +aTail(1, 2, 3); // [2, 3] +``` + +剩余参数将各个元素组成一个数组。而展开操作恰恰相反:它将一个数组中的元素扩展为独立元素。研究一下这个: + +``` +const shiftToLast = (head, ...tail) => [...tail, head]; +shiftToLast(1, 2, 3); // [2, 3, 1] +``` + +JavaScript 数组在使用扩展操作符的时候会调用一个迭代器,对于数组中的每一个元素,迭代器都会传递一个值。在 `[...tail, head]` 表达式中,迭代器按顺序从 `tail` 数组中拷贝到一个刚刚创建的新的数组。之前 head 已经是一个独立元素了,我们只需把它放到数组的末端,就完成了。 + +### 柯里化 ### + +可以通过返回另一个函数来实现柯里化(Curry)和偏应用(partial application): + +``` +const highpass = cutoff => n => n >= cutoff; +const gt4 = highpass(4); // highpass() 返回了一个新函数 +``` + +你可以不使用箭头函数。JavaScript 也有一个 `function` 关键字。我们使用箭头函数是因为 `function` 关键字需要打更多的字。 +这种写法和上面的 `highPass()` 定义是一样的: + +``` +const highpass = function highpass(cutoff) { + return function (n) { + return n >= cutoff; + }; +}; +``` + +JavaScript 中箭头的大致意义就是“函数”。使用不同种的方式声明,函数行为会有一些重要的不同点(`=>` 缺少了它自己的 `this` ,不能作为构造函数),但当我们遇见那就知道不同之处了。现在,当你看见 `x => x`,想到的是 “一个携带 `x` 并且返回 `x` 的函数”。所以 `const highpass = cutoff => n => n >= cutoff;` 可以这样读: + +“`highpass` 是一个携带 `cutoff` 返回一个携带 `n` 并返回结果 `n >= cutoff` 的函数的函数” + +既然 `highpass()` 返回一个函数,你可以使用它创建一个更独特的函数: + +``` +const gt4 = highpass(4); + +gt4(6); // true +gt4(3); // false +``` + +自动柯里化函数,有利于获得最大的灵活性。比如你有一个函数 `add3()`: + +``` +const add3 = curry((a, b, c) => a + b + c); +``` + +使用自动柯里化,你可以有很多种不同方法使用它,它将根据你传入多少个参数返回正确结果: + +``` +add3(1, 2, 3); // 6 +add3(1, 2)(3); // 6 +add3(1)(2, 3); // 6 +add3(1)(2)(3); // 6 +``` + +令 Haskell 粉遗憾的是,JavaScript 没有内置自动柯里化机制,但你可以从 Lodash 引入: + +``` +$ npm install --save lodash +``` + +然后在你的模块里: + +``` +import curry from 'lodash/curry'; +``` + +或者你可以使用下面这个魔性写法: + +``` +// 精简的递归自动柯里化 +const curry = ( + f, arr = [] +) => (...args) => ( + a => a.length === f.length ? + f(...a) : + curry(f, a) +)([...arr, ...args]); +``` + +### 函数组合 ### + +当然你能够开始组合函数了。组合函数是传入一个函数的返回值作为参数给另一个函数的过程。用数学符号标识: + +``` +f . g +``` + +翻译成 JavaScript: + +``` +f(g(x)) +``` + +这是从内到外地求值: + +1. `x` 是被求数值 +2. `g()` 应用给 `x` +3. `f()` 应用给 `g(x)` 的返回值 + +例如: + +``` +const inc = n => n + 1; +inc(double(2)); // 5 +``` + +数值 `2` 被传入 `double()`,求得 `4`。 `4` 被传入 `inc()` 求得 `5`。 + +你可以给函数传入任何表达式作为参数。表达式在函数应用之前被计算: + +``` +inc(double(2) * double(2)); // 17 +``` + +既然 `double(2)` 求得 `4`,你可以读作 `inc(4 * 4)`,然后计算得 `inc(16)`,然后求得 `17`。 + +函数组合是函数式编程的核心。我们后面还会介绍很多。 + +### 数组 ### + +数组有一些内置方法。方法是指对象关联的函数,通常是这个对象的属性: + +``` +const arr = [1, 2, 3]; +arr.map(double); // [2, 4, 6] +``` + +这个例子里,`arr` 是对象,`.map()` 是一个以函数为值的对象属性。当你调用它,这个函数会被应用给参数,和一个特别的参数叫做 `this`,`this` 在方法被调用之时自动设置。这个 `this` 的存在使 `.map()` 能够访问数组的内容。 + +注意我们传递给 `map` 的是 `double` 函数而不是直接调用。因为 `map` 携带一个函数作为参数并将函数应用给数组的每一个元素。它返回一个包含了 `double()` 返回值的新的数组。 + +注意原始的 `arr` 值没有改变: + +``` +arr; // [1, 2, 3] +``` + +### 方法链 ### + +你也可以链式调用方法。方法链是指在函数返回值上直接调用方法的过程,在此期间不需要给返回值命名: + +``` +const arr = [1, 2, 3]; +arr.map(double).map(double); // [4, 8, 12] +``` + +返回布尔值(`true` 或 `false`)的函数叫做 **断言**(predicate)。`.filter()` 方法携带断言并返回一个新的数组,新数组中只包含传入断言函数(返回 `true`)的元素: + +``` +[2, 4, 6].filter(gt4); // [4, 6] +``` + +你常常会想要从一个列表选择一些元素,然后把这些元素序列化到一个新列表中: + +``` +[2, 4, 6].filter(gt4).map(double); [8, 12] +``` + +注意:后面的文章你将看到使用叫做 **transducer** 东西更高效地同时选择元素并序列化,不过这之前还有一些其他东西要了解。 + +### 总结 ### + +如果你现在有点发懵,不必担心。我们仅仅概览了一下很多事情的表面,它们尚需大量的解释和总结。很快我们就会回过头来,深入探讨其中的一些话题。 + +[**继续阅读 “高阶函数”…**](https://github.com/xitu/gold-miner/blob/master/TODO/higher-order-functions-composing-software.md) + +### 接下来 ### + +想要学习更多 JavaScript 函数式编程知识? + +[和 Eric Elliott 一起学习 JavaScript](http://ericelliottjs.com/product/lifetime-access-pass/)。 如果你不是其中一员,千万别错过! + +[ +](https://ericelliottjs.com/product/lifetime-access-pass/) + + +***Eric Elliott*** 是 [*“JavaScript 应用程序设计”*](http://pjabook.com) (O’Reilly) 以及 [*“和 Eric Elliott 一起学习 JavaScript”*](http://ericelliottjs.com/product/lifetime-access-pass/) 的作者。 曾就职于 **Adobe Systems、Zumba Fitness、The Wall Street Journal、ESPN、BBC and top recording artists including Usher、Frank Ocean、Metallica** 等公司,具有丰富的软件实践经验。 + +**他大多数时间在 San Francisco By Area ,和世界上最美丽的姑娘在一起。** + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 diff --git a/TODO/a-mindful-design-process.md b/TODO/a-mindful-design-process.md new file mode 100644 index 00000000000..855cd1e73ec --- /dev/null +++ b/TODO/a-mindful-design-process.md @@ -0,0 +1,182 @@ +> * 原文地址:[A Mindful Design Process](https://headspace.design/a-mindful-design-process-f4a4641ee88f) +> * 原文作者:[François Chartrand](https://headspace.design/@frankchartrand) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者: +> * 校对者: + +# A Mindful Design Process # + +How integrating mindfulness techniques will help you become a stronger designer and a more engaged team member. + +![](https://cdn-images-1.medium.com/max/1000/1*zvpIsZO5ZGUBZPBD-2tdKA.jpeg) + +We all have a process we follow to get us from concept to execution. Sometimes we’ll have access to a great brief, data, insights, and an inspirational mood board but still, we get stuck: we procrastinate, get hung up, or struggle with artist’s block. Sound familiar? Read on… + +I’ve outlined some easy-to-implement mindfulness techniques that you can start building into your process today. It’s about creating your best work in a meaningful and intentional way, and having fun while you’re at it. + +> “Mindfulness is awareness that arises through paying attention, on purpose, in the present moment, non-judgmentally.” + +> — Jon Kabat-Zinn + +### **Before you begin** ### + +**Just listen** +This one is so easy: just listen and pay attention. Put your phone away, close your laptop, and open your ears. + +Whether you’re meeting with clients, stakeholders, or having a 1-on-1 with a colleague, practice the art of listening. Ask more questions. Talk it through. Projects get much more interesting when you feel like you have skin in the game and actually care about the outcome. Here’s a short TED Talk by [Julian Treasure](https://medium.com/@juliantreasure) on ["5 ways to listen better.”](https://www.ted.com/talks/julian_treasure_5_ways_to_listen_better) + +*Pro tip:* Using simple [body language techniques](https://blog.udemy.com/positive-body-language/) can be an invaluable tool in your life and career. I know it sounds lame, but it actually works—and others will feel more comfortable around you. [Mitch Joel](https://medium.com/@mitchjoel) discusses some winning body language tips on his podcast—[ check it out](https://overcast.fm/+JjXi7iC4). + +![](https://cdn-images-1.medium.com/max/600/1*_YryF85J5Yju7KeanuhXgA.png) + +**Set realistic timelines and expectations** + +Ensure you have enough time to get the task completed. + +Rushing the process leads to uninspired work, so make sure you ask for enough time (or budget) to be successful. Even if you can’t get it, it’s always worth asking for what you truly believe the project requires. + +Many designers accept deadlines without asking why and burn out. You’ll leave a more positive impression on your peers when you set expectations early. + +**Design with intent** + +Is there something about this project that excites you? Is this your specialization, or are you up for a new challenge? + +Try asking yourself, is this a step in the right direction to improve my design skills? Will this help with my ideal career path? Your career is the sum of all of your work, and knowing you’re growing in the right direction can be motivating. + +### Do your research ### + +**Get context and re-frame the project** + +Before you start, do some research. Without context, you won’t be able to do your best work. Even seemingly boring projects can be exciting if you re-frame them. Check out what the best-in-class are doing—there’s a lot you can learn from their successes and slip-ups. + +[Paul Woods](https://medium.com/@paulthedesigner) wrote a great article on [turning an uninspiring brief into an awesome portfolio piece](https://medium.com/@paulthedesigner/turning-an-uninspiring-brief-into-an-awesome-portfolio-project-31b2aa871bb7#.7dclqp3w) that may be helpful for you. + +Time spent researching is never time wasted. Research will help guide you toward the next step of the process with some key insights. Plus, with solid research, it’s easier to sell your ideas internally or to clients. + +![](https://cdn-images-1.medium.com/max/1000/1*B--DG0OODgsuNTprMK8vgA.png) + +**Visualize with a mood board** + +Use [InVision Boards](https://support.invisionapp.com/hc/en-us/articles/205249269-Introduction-to-Boards) or [Pinterest](http://www.pinterest.com) and share early on with your team. Mood boards can act as visualizations of the success you want the project to achieve. They can also help align others on the project and help, or serve as a reflection when your creative juices are running low. + +**Get the tools for the task** + +Look into what tools and licenses you’ll need and set yourself up with the proper stack of tools and software. + +- Stickies/markers +- Whiteboard +- [Paper](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwjI-sT67ffSAhXFvLwKHQOtDtUQFggcMAA&url=https%3A%2F%2Fwww.fiftythree.com%2F&usg=AFQjCNGyxIjM39EcNUzww6FXtJw96xwKfA&sig2=ik8RiXeUi5YCI-7Ydyrx2Q) (flows, wireframes) +- [Sketch](https://www.sketchapp.com/) (app/web design) +- [Zeplin](http://www.zeplin.io) (spec’ing) +- [InVision](https://www.invisionapp.com/) (lofi prototyping) +- [Framer](https://framer.com/) (hifi prototyping) +- [Skitch](https://evernote.com/skitch/)(QA annotation) + +![](https://cdn-images-1.medium.com/max/1000/1*0fwiF6oe8FJ0VkEcY5KMsg.jpeg) + +### Set yourself up for success ### + +The biggest hurdle in any creative process is *beginning*. If you’re having trouble getting started, sit quietly and take few deep breaths. If that isn’t helping, go for a walk, stretch, meditate, make yourself coffee, put on an album you enjoy—whatever you need to clear your mind. + +> “Begin anywhere.” + +> — John Cage + +**Check your posture** + +I’m no scientist, but I know that posture makes a big difference in my focus and concentration. Settle in, feel the weight of your body and ensure you’re comfortable, not slouching. Standing desks are also a great option that allow you to stretch your legs and feel weight on your toes, giving you extra mobility. + +**Keep your focus** + +Try [Focus](https://heyfocus.com/) (app) to block websites you find yourself visiting often—or fully disconnect from WiFi. Set your phone on Airplane mode and put it in another room. + +**Stop multitasking** + +Successful people know that being [fully present and committed to one task](http://www.zerotoskill.com/proven-steps-to-the-most-productive-day-youll-ever-have/) is indispensable. + +![](https://cdn-images-1.medium.com/max/800/1*4MyOs2PvfYDOQ5iXPhy1zw.jpeg) + +### Unlocking creativity ### + +**Sketch. Iterate. Simplify.** + +Great designers know that your best ideas are often your last. When sketching, try not to dwell on a concept or idea for too long. Sketch out the idea or allow the thought to float away. Consider different scenarios in which people might observe this design in, whether on a mobile phone or piece of branding. Embrace and consider each thought or sensation without judging it as good or bad. Ideas will come in abundance… If your mind wanders elsewhere, notice where it has gone and gently redirect it to the present. + +**Reduce, simplify, observe,** and you’ll eventually get to an iteration you can move forward with confidently. + +![](https://cdn-images-1.medium.com/max/600/1*K6gRA8kRrQvn6CCYoz4oDA.jpeg) + +![](https://cdn-images-1.medium.com/max/600/1*z98ajqpYLq3zvhlbHpwP7A.jpeg) + +Photos by Ian Whittlesea + +*Want more? Try some breathing exercises…* + +If you’re looking for more of a creative boost, you might want to consider the breathing exercises found in *Hazdaznan Health & Breath Culture*, a book exploring the relationship between Mazdaznan, Johannes Itten and the Vorkurs (Preliminary or Foundation Course) at the Bauhaus. + +**Remember that design is for people** + +No matter what, design is for an end-user or consumer and we should do our best to understand their needs and desires—whether that’s a subscriber, a client, or a client’s client. Be respectful and considerate of everything they’re going through when engaging with your design. + +### Communication matters ### + +**Share often and communicate clearly** + +Keep your team in the loop often and set expectations early. Meet your deadlines, and if you’re about to miss a deadline, give your manager or client a heads up and let them know how you’ll avoid this in the future. + +**Communicating with your team** + +When it comes to communicating with your team, be aware of communication styles. Some people appreciate a note on Slack before you swing by their desk. Others prefer to meet in private. Everyone’s different and [increasing your self-awareness](http://www.tanveernaseer.com/increasing-self-awareness-to-improve-how-we-communicate-scott-schwertly/) will improve the way others react to your feedback. + +### **Take notes** ### + +**You can never take too many notes** + +It’s better to take too many notes than not enough. If it helps, you can even record meetings on your phone to listen to later when you’re working on the project. This way you can be totally sure you’re checking all the boxes your manager or client shared. [Helen Tran](https://medium.com/@tranhelen) of Shopify wrote about her [bullet-journal system](http://helentran.com/two-habits)and how a system like this is a habit that changed her career. + +Before completing a project, take time to jot down what went well and what didn’t so you can learn from any successes or shortcomings and be better prepared in the future. If you’re not being mindful of the process and how you felt under certain pressures, you’re bound to keep repeating the same mistakes. [Journaling](http://stronginsideout.com/journaling-techniques/)is handy as a general life practice and helps you track the progress you’re making in your life. + +**Don’t take revisions or feedback personally** + +Even if you think you’ve found the best solution it’s not a solution if it doesn’t work for your user or clients. Reach a good balance between your knowledge, expertise, and your client’s. + +While criticism isn’t personal, you should always stand up for your decisions if you think it’ll help the end-user. You should be proud of your work, but design is, in the end, for users and customers. Sometimes people give ego-driven feedback because they feel like they have to prove their own worth (even you!), and being aware of this is helpful. Practicing deep breathing or a short meditation before meetings can help keep the ego in check. + +[Julie Zhuo](https://medium.com/@joulee) wrote a post on [taking feedback impersonally](https://medium.com/the-year-of-the-looking-glass/taking-feedback-impersonally-7c0f3a8199d9) that I highly recommend. + +*Pro tip:* People fear feedback because it’s usually negative. If you’re able to, try and mix it up and give more positive feedback too. + +### Other thoughts ### + +![](https://cdn-images-1.medium.com/max/800/1*2MdbVkVcG59XyYG5AihtJQ.png) + +**Remember *why* you design** + +Are you designing to build up your portfolio? To empower users? To put a roof over your head? Try keeping a memento around that reminds you why you’re doing this—whether that’s a photo on your desk or a note in your wallet. Something small that will motivate you when you’re losing steam. + +**Get some rest** + +Don’t forget to take a break once in a while. We often celebrate the tireless designers who hustle 24/7, and I’m not saying you shouldn’t — that’s up to you — but if you’re burning the midnight oil often, you might consider using apps like [f.lux](https://justgetflux.com/) to help with your sleep. (makes your computer screen look like the room you’re in based on time of day). + +**Be humble. Give credit where it’s due.** + +It takes teamwork to make the dream work. Give credit to your teammates and share that big launch or award with others—even Buzz Aldrin had hundreds of people behind him when he first stepped onto the surface of the moon. + +**Keep your desk clean** + +NASA astronauts clean up their stations after every use; more designers should do this, especially when it comes to meeting rooms and communal spaces. Keep your desk clean, keep your mind clean (and ready for those bursts of creativity). + +**Be thankful** + +We’re lucky to do what we do for a living. Design is still in its infancy, and there’s so much exciting work to do. Happy designing! 🌞 + + +Thanks to [Alex Pompliano](https://twitter.com/alexpompliano) for editing. 🙏 + +*Frank is a Product Designer at [*Headspace*](http://www.headspace.com). He was previously at [*Edenspiekermann*](http://www.edenspiekermann.com) and co-founded [*Bureau*](http://www.bureau.ca).* + +*You can follow him on [*Twitter*](http://www.twitter.com/frankchartrand).* + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。 diff --git a/TODO/a-unified-styling-language.md b/TODO/a-unified-styling-language.md new file mode 100644 index 00000000000..5bef138f648 --- /dev/null +++ b/TODO/a-unified-styling-language.md @@ -0,0 +1,565 @@ +> * 原文地址:[A Unified Styling Language](https://medium.com/seek-blog/a-unified-styling-language-d0c208de2660) +> * 原文作者:本文已获原作者 [Mark Dalgleish](https://medium.com/@markdalgleish) 授权 +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 译者:[ZhangFe](https://github.com/ZhangFe) +> * 校对者:[JackGit](https://github.com/JackGit),[yifili09](https://github.com/yifili09) + +# 统一样式语言 + +在过去几年中,我们见证了 [CSS-in-JS](https://github.com/MicheleBertoli/css-in-js) 的兴起,尤其是在 [React](https://facebook.github.io/react) 社区。当然,它也饱含争议。很多人,尤其是那些已经对 CSS 非常熟悉的人都表示难以置信。 + +> "为什么有人要在 JS 中写 CSS? + +> 这简直是一个可怕的想法! + +> 但愿他们学过 CSS !" + +如果这就是你的反应,那么请继续读下去。我们来看看到底为什么在 JavaScript 中编写样式并不是一个可怕的想法,并且为什么我认为你应该关注这个快速发展的领域。 + +![](https://cdn-images-1.medium.com/max/2000/1*Ipu5Grtzr21suPiTfvGXaw.png) + +### 对社区的误解 + +React 社区经常被 CSS 社区误解,反之亦然。对我来说这很有趣,因为我经常混迹于这两个社区。 + +我从九十年代后期开始学习 HTML,并且从基于表格布局的黑暗时代以来就一直使用 CSS。受 [CSS Zen Garden](http://www.csszengarden.com) 启发,我是最早一批将现有代码向 [语义化标签](https://en.wikipedia.org/wiki/Semantic_HTML) 和级联样式表迁移的人。不久之后,我开始专注于前后端分离,使用[非侵入式 JavaScript](https://www.w3.org/wiki/The_principles_of_unobtrusive_JavaScript) 和客户端的交互来装饰服务端渲染的 HTML。围绕这些做法有一个小型且充满活力的社区,并且我们成为第一代尝试给浏览器平台应有尊重的前端开发者。 + +伴随这样的背景,你可能会认为我会强烈反对 React 的 [HTML-in-JS](https://facebook.github.io/react/docs/jsx-in-depth.html) 模式,它似乎违背了我们所坚持的原则,但实际上恰恰相反。根据我的经验,React 的组件模型加上服务端渲染的能力,最终给我们提供了一种构建大规模复杂单页应用的方式,从而使我们能够将快速,可访问,逐渐增强的应用推送给我们的用户。在 [SEEK](https://www.seek.com.au) 上我们就利用了这种能力,这是我们的旗舰产品,它是 React 单页应用程序,当 JavaScript 被禁用时我们的核心搜索流程依然可用,因为我们通过在服务器端运行同构的 JavaScript 代码来完成到传统的网站优雅降级。 + +所以,也请考虑一下我抛出的从一个社区到另一个社区的橄榄枝。让我们一起尝试理解这个转变。它可能不完美,它可能不是你计划在产品中使用的东西,它可能对你不是很有说服力,但是至少值得你尝试思考一下。 + +### 为什么使用 CSS-in-JS? + +如果你熟悉我最近做的与 React 以及 [CSS 模块](https://github.com/css-modules/css-modules)相关的工作,当看到我维护 CSS-in-JS 时你可能会很惊讶。 + +![](https://cdn-images-1.medium.com/max/1600/1*RtAMWbxdwW2ujyrurU9plw.png) + +毕竟,通常那些希望有局部样式但是又不希望在 JS 中写 CSS 的开发者会选择使用 CSS 模块。事实上,甚至我自己在工作中也还没用到 CSS-in-JS。 + +尽管如此,我任然对 CSS-in-JS 社区保持浓厚的兴趣,对他们不断提出的创意保持密切关注。不仅如此,**我认为更广泛的 CSS 社区对它也应该很感兴趣** + +原因是什么呢? + +为了更清楚地了解为什么人们选择在 JavaScript 中编写他们的样式,我们将重点关注采用这种方式时带来的实际好处。 + +我把它分为五个方面: + +1. 局部样式 +2. 关键 CSS +3. 智能优化 +4. 打包管理 +5. 在非浏览器环境下的样式 + + +让我们进一步的细分并且仔细看看 CSS-in-JS 提供的每一点优势。 + +### 1. + +#### **局部样式** + +想要在大规模项目中高效的构建 CSS 是非常困难的。当加入一个现有的长期运行的项目时,通常我们会发现 CSS 是系统中最复杂的部分。 + +为了解决这个问题,CSS 社区已经投入了巨大的努力,通过 [Nicole Sullivan](https://twitter.com/stubbornella) 的 [OOCSS](https://github.com/stubbornella/oocss/wiki) 和 [Jonathan Snook](https://twitter.com/snookca) 的 [SMACSS](https://smacss.com/) 都可以使我们的样式更好维护,不过 [Yandex](https://github.com/yandex) 开发的 [BEM](http://getbem.com) 更流行一些。 + +根本上来说,BEM (纯粹用于 CSS)只是一个命名规范,它要求样式的类名要遵守 `.Block__element--modifier` 的模式。在任何使用 BEM 风格的代码库中,开发人员必须始终遵守 BEM 的规则。当严格遵守时,BEM 的效果很好,但是为什么这些如同作用域一般基础的功能,却只使用单纯的**命名规范**来限制呢? + +无论是否有明确表示,大多数 CSS-in-JS 类库都遵循 BEM 的思路,它们尝试将样式定位到单个 UI 组件,只不过用了完全不同的实现方式。 + +那么在实际编写代码时什么样的呢?当使用 [Sunil Pai](https://twitter.com/threepointone) 开发的 [glamor](https://github.com/threepointone/glamor) 时,代码看起来像下面这样: + +``` +import { css } from 'glamor' +const title = css({ + fontSize: '1.8em', + fontFamily: 'Comic Sans MS', + color: 'blue' +}) +console.log(title) +// → 'css-1pyvz' +``` + +你可能会注意到**代码中没有 CSS 的类名**。因为它不再是一个指向定义在项目其他位置 class 的硬编码,而是由我们的库自动生成的。我们不必再担心全局作用域内的选择器冲突,这也意味着我们不再需要替他们添加前缀了。 + +这个选择器的作用域与周围代码的作用域一致。如果你希望在你应用的其他地方使用这个规则,你就需要将它改写成一个 JavaScript 模块并且在需要使用的地方引用它。就保持代码库的可维护性而言,它是非常强大的,**它确保了任何给定的样式都可以像其他代码一样容易追踪来源**。 + +**通过从仅仅是命名约定到默认强制局部样式的转变,我们已经提高了我们样式质量的基准线。BEM 已经烙在里面了,而不再是一个可选项。** + +— + +在继续之前,我要说明至关重要的一点。 + +**它生成的是真正的级联样式,而不是内联样式** + +大多数早期的 CSS-in-JS 库都是将样式直接添加到每个元素上,但是这个模式有个严重的缺点,'styles' 属性并不能做到 CSS 可以做到的每件事。大多数新的库不再关注于**动态样式表**,而是在运行时在全局样式中插入和删除规则。 + +举个例子,让我们看看 [Oleg Slobodskoi](https://twitter.com/oleg008) 开发的 [JSS](https://github.com/cssinjs/jss),这是最早的 CSS-in-JS 库之一,并且它生成的是**真实的 CSS**。 + +![](https://cdn-images-1.medium.com/max/1600/1*ltBvwbyvBt8OMdGZQOdMDA.png) + + +当使用 JSS 时,你可以使用标准的 CSS 特性,比如 hover 和媒体查询,它们会映射到相应的 CSS 规则。 + +``` +const styles = { + button: { + padding: '10px', + '&:hover': { + background: 'blue' + } + }, + '@media (min-width: 1024px)': { + button: { + padding: '20px' + } + } +} +``` + +一旦你将这些样式插入到文档中,你就可以使用那些自动生成的类名。 + +``` +const { classes } = jss.createStyleSheet(styles).attach() +``` + +无论你是使用一个完整的框架,还是简单的使用 **innerHTML**,你都可以在 JavaScript 中使用这些生成的 class,而不是硬编码你的 class 字符串。 + +``` +document.body.innerHTML = ` +

Hello World!

+` +``` + +单独使用这种方式管理样式并没有多大的优势,它通常和一些组件库搭配使用。因此,通常可以找到用于最流行库的绑定方案。例如,JSS 可以通过 [react-jss](https://github.com/cssinjs/react-jss) 的帮助轻松地绑定到 React 组件上,在管理生命周期的同时,它可以帮你给每个组件插入一小段样式。 + +``` +import injectSheet from 'react-jss' +const Button = ({ classes, children }) => ( + +) +export default injectSheet(styles)(Button) +``` + +通过将我们的样式集中到组件上,可以将他们和代码跟紧密的结合,这不就是 BEM 的思想么?所以 CSS-in-JS 社区的很多人觉得在所有的样式绑定样板中,提取、命名和复用组件的重要性都丢失了。 + +随着 [Glen Maddern](https://twitter.com/glenmaddern) 和 [Max Stoiber](https://twitter.com/mxstbr) 提出了 [styled-components](https://github.com/styled-components/styled-components) 的概念,出现了一个新的思考这个问题的方式。 + +![](https://cdn-images-1.medium.com/max/1600/1*l4nfMFKxfT4yNTWUK2Vsdg.png) + + +我们直接创建组件,而不是创建样式然后手动地将他们绑定到组件上。 + +``` +import styled from 'styled-components' + +const Title = styled.h1` + font-family: Comic Sans MS; + color: blue; +` +``` + +使用这些样式时,我们不会将 class 添加到存在的元素上,而是简单地渲染这些被生成的组件。 + +``` +Hello World! +``` + +虽然 styled-components 通过模板字面量的方式使用了传统的 CSS 语法,但是有人更喜欢使用数据结构。来自 [PayPal](https://github.com/paypal) 的 [Kent C. Dodds](https://twitter.com/kentcdodds) 开发的 [Glamorous](https://github.com/paypal/glamorous) 是一个值得关注的替代方案。 + +![](https://cdn-images-1.medium.com/max/1600/1*Ere9GQTIJeNac2ONfZlfdw.png) + +Glamorous 和 styled-components 一样提供了组件优先的 API,但是他的方案是使用**对象**而不是**字符串**,这样就无需在库中引入一个 CSS 解析器,可以降低库的大小并提高性能。 + +``` +import glamorous from 'glamorous' + +const Title = glamorous.h1({ + fontFamily: 'Comic Sans MS', + color: 'blue' +}) +``` + +无论你使用什么语法描述你的样式,他们不再仅仅是组件的**一部分**,他们和组件已经无法分离。当使用一个像 React 这样的库时,组件是基本构建块,并且现在我们的样式也成了构建这个架构的核心部分。**如果我们将我们应用程序中的所有内容都描述为组件,那么为什么我们的样式不行呢?** + +— + +考虑到我们之前介绍的对系统做出改变的意义,对于那些有丰富 BEM 开发经验的工程师来说,这一切看起来似乎是一个很小的提升。事实上,CSS 模块让你在不用放弃 CSS 工具生态系统的同时获得了这些提升。很多项目坚持使用 CSS 模块还有一个原因,他们发现 CSS 模块充分解决了编写大规模 CSS 的问题,并且可以保持编写常规 CSS 时的习惯。 + +然而,当我们开始在这些基本概念上构建时,事情开始变得更有趣。 + +### 2. + +#### 关键 CSS + +在文档头部内联关键样式已经成为一种比较新的最佳实践,通过仅提供当前页面所需的样式可以提高首屏时间。这与我们常用的样式加载方式形成了鲜明对比,之前我们通常会强制浏览器在渲染之前为应用下载所有的样式。 + +虽然有工具可以用于提取和内联关键 CSS,比如 [Addy Osmani](https://twitter.com/addyosmani) 的 [critical](https://github.com/addyosmani/critical),但是他们无法从根本上改变关键 CSS 难以维护和难以自动化的事实。这是一个棘手的、可选的性能优化,所以大部分项目似乎放弃了这一步。 + +CSS-in-JS 则是一个完全不同的故事。 + +当你的应用使用服务端渲染时,提取关键 CSS 不仅仅是一个优化,从根本上来说,服务器端的 CSS-in-JS 是**使用**关键 CSS 的首要工作。 + +举个例子,当使用 [Khan Academy](https://github.com/Khan) 开发的 [Aphrodite](https://github.com/Khan/aphrodite) 给元素添加 class 时,可以通过内联调用它的 `css` 函数来跟踪在这次渲染过程中使用的样式。 + +``` +import { StyleSheet, css } from 'aphrodite' +const styles = StyleSheet.create({ + title: { ... } +}) +const Heading = ({ children }) => ( +

{ children }

+) +``` + +即便你所有的样式都是在 JavaScript 中定义的,你也可以很轻松的从当前页面中提取所有的样式并生成一个 CSS 字符串,在执行服务端渲染时将它们插入文档的头部。 + +``` +import { StyleSheetServer } from 'aphrodite'; + +const { html, css } = StyleSheetServer.renderStatic(() => { + return ReactDOMServer.renderToString(); +}); +``` + +你可以像这样渲染你的关键 CSS: + +``` +const criticalCSS = ` + +`; +``` + +如果你看过 React 的服务端渲染模型,你可能会发现这个模式非常熟悉。在 React 中,你的组件在 JavaScript 中定义他们的标记,但可以在服务器端渲染成常规的 HTML 字符串。 + +**如果你使用渐进增强的方式构建你的应用,尽管全部使用 JavaScript 编写,但也有可能在客户端根本就不需要 JavaScript** + +无论用哪种方式,客户端 JavaScript bundle 都要包含启动单页应用所需的代码,它能让页面瞬间活起来,并与此同时开始浏览器中进行渲染。 + +由于在服务器上渲染 HTML 和 CSS 是同时进行的,就像前面的例子所示, Aphrodite 这样的库通常会以一个函数调用的方式帮助我们流式生成关键 CSS 和服务端渲染的 HTML。现在,我们可以用类似的方式将我们的 React 组件渲染成静态 HTML。 + +``` +const appHtml = ` +
+ ${html} +
+` +``` + +通过在服务器端使用 CSS-in-JS,我们的单页应用不仅可以脱离 JavaScript 工作,**它甚至可以加载的更快**。 + +**与我们选择器的作用域一样,渲染关键 CSS 的最佳实践如今已经烙在里面了,而不是一个可选项**。 + +### 3. + +#### 更智能的优化 + +我们最近看到了构建 CSS 的新方式的兴起,比如 [Yahoo](https://github.com/yahoo) 的 [Atomic CSS](https://acss.io/) 和 [Adam Morse](https://twitter.com/mrmrs_) 的 [Tachyons](http://tachyons.io/),它们更喜欢短小的,单一用途的类名,而不是语义化的类名。举个例子,当使用 Atomic CSS 时,你将使用类似于函数调用的语法去作为类名,并且这些类名会在之后被用来生成合适的样式表。 + +``` +
+ Atomic CSS +
+``` + +通过最大程度地提高类的复用性以及用内联样式的方式一样有效处理类名,可以达到尽可能地精简 CSS 的目的。虽然文件大小的减少很容易体现,但对于你的代码库和团队成员的影响确实是微乎其微的。这些优化会从底层引起你的 CSS 和标记语言的变化,使他们成为构建工作中更重要的部分。 + +正如我们已经介绍过的,当使用 CSS-in-JS 或者 CSS 模块时,你不再需要在 HTML 中硬编码你的类名,而是动态引用由库或者构建工具自动生成的 JavaScript 变量。 + +我们这样写样式: + +``` +