diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 92fcfc69442..6a38eb05765 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1 +1 @@ -译文翻译完成,resolve # +译文翻译完成,resolve #id diff --git a/.gitignore b/.gitignore index 606539410d8..7aedc0a31fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store .AppleDouble .LSOverride -.idea \ No newline at end of file +.idea +.vscode \ No newline at end of file diff --git a/AI.md b/AI.md index e81f184c17d..87d6a9a9d03 100644 --- a/AI.md +++ b/AI.md @@ -1,3 +1,16 @@ +* [用 Python 实现马尔可夫链的初学者教程](https://juejin.im/post/5bb031d06fb9a05cdb104888) ([cdpath](https://github.com/cdpath) 翻译) +* [Python 中的无监督学习算法](https://juejin.im/post/5bab10ed6fb9a05d1f2211b6) ([zhmhhu](https://github.com/zhmhhu) 翻译) +* [用 Scikit-Learn 实现 SVM 和 Kernel SVM](https://juejin.im/post/5b7fd39af265da43831fa136) ([rockyzhengwu](https://github.com/rockyzhengwu) 翻译) +* [Sklearn 中的朴素贝叶斯分类器](https://juejin.im/post/5b8510be51882542d23a1d66) ([sisibeloved](https://github.com/sisibeloved) 翻译) +* [使用 Python 进行自动化特征工程](https://juejin.im/post/5b6ea0e4e51d4519044adff0) ([mingxing47](https://github.com/mingxing47) 翻译) +* [Python 与大数据:Airflow & Jupyter Notebook with Hadoop 3, Spark & Presto](https://juejin.im/post/5b5a7fdfe51d453526175687) ([cf020031308](https://github.com/cf020031308) 翻译) +* [自然语言处理真是有趣](https://juejin.im/post/5b6d08e2f265da0f9c67cf0b) ([lihanxiang](https://github.com/lihanxiang) 翻译) +* [给人类的机器学习指南🤖👶](https://juejin.im/post/5b136f12f265da6e5415114b) ([sisibeloved](https://github.com/sisibeloved) 翻译) +* [深度学习中所需的线性代数知识](https://juejin.im/post/5b19d99ae51d4506d81a7a2f) ([maoqyhz](https://github.com/maoqyhz) 翻译) +* [可微可塑性:一种学会学习的新方法](https://juejin.im/post/5b055308f265da0ba063879d) ([luochen1992](https://github.com/luochen1992) 翻译) +* [给初学者的 Jupyter Notebook 教程](https://juejin.im/post/5af8d3776fb9a07ab7744dd0) ([SergeyChang](https://github.com/SergeyChang) 翻译) +* [如何在安卓应用中使用 TensorFlow Mobile](https://juejin.im/post/5afb8dc5518825426c690236) ([luochen1992](https://github.com/luochen1992) 翻译) +* [在浏览器里使用 TenserFlow.js 实时估计人体姿态](https://juejin.im/post/5afd833b5188254270642ff3) ([NoName4Me](https://github.com/NoName4Me) 翻译) * [Google 的 ML Kit 为 Android 和 iOS 提供了简单的机器学习 API](https://juejin.im/post/5af2942e51882567244df836) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) * [利用 Keras 深度学习库进行词性标注教程](https://juejin.im/post/5ae4613a5188256727742d7d) ([luochen1992](https://github.com/luochen1992) 翻译) * [Facebook 的 AI 万金油:StarSpace 神经网络模型简介](https://juejin.im/post/5a83af7c6fb9a0633c661404) ([noahziheng](https://github.com/noahziheng) 翻译) diff --git a/README.md b/README.md index fd5ff389869..856834e9276 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,9 @@ [掘金翻译计划](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) 上的英文分享文章。内容覆盖[人工智能](#ai--deep-learning--machine-learning)、[Android](#android)、[iOS](#ios)、[React](#react)、[前端](#前端)、[后端](#后端)、[产品](#产品)、[设计](#设计) 等领域,读者为热爱新技术的新锐开发者。 -掘金翻译计划目前翻译完成 [978](#近期文章列表) 篇文章,官方文档及手册 [10](#官方文档及手册) 个,共有 [850](https://github.com/xitu/gold-miner/wiki/%E8%AF%91%E8%80%85%E7%A7%AF%E5%88%86%E8%A1%A8) 余名译者贡献翻译。 +掘金翻译计划目前翻译完成 [1236](#近期文章列表) 篇文章,官方文档及手册 [13](#官方文档及手册) 个,共有近 [1000](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/blockchain-miner) # 官方指南 @@ -33,6 +35,7 @@ ## 官方文档及手册 * [TensorFlow 中文文档](https://github.com/xitu/tensorflow-docs) +* [The JavaScript Tutorial](https://github.com/xitu/javascript-tutorial-zh) * [ML Kit 中文文档](https://github.com/Quorafind/MLkit-CN) * [GraphQL 中文文档](https://github.com/xitu/graphql.github.io) * [Under-the-hood-ReactJS 系列教程](https://github.com/xitu/Under-the-hood-ReactJS) @@ -40,80 +43,81 @@ * [Google Interview University 面试指北](https://github.com/xitu/google-interview-university) * [前端开发者指南(2017)](https://github.com/xitu/front-end-handbook-2017) * [前端开发者指南(2018)](https://github.com/xitu/front-end-handbook-2018) +* [Awesome Flutter](https://github.com/xitu/awesome-flutter) * [macOS Security and Privacy Guide](https://github.com/xitu/macOS-Security-and-Privacy-Guide) * [State of Vue.js report 2017 中文版](https://github.com/xitu/gold-miner/blob/master/TODO/state-of-vue-report-2017.md) -* [Elasticsearch 参考手册](https://github.com/rpgmakervx/elasticsearch-reference-translation) +* [Next.js 轻量级 React 服务端渲染应用框架中文文档](http://nextjs.frontendx.cn/) ## 区块链 -* [用不到 200 行的 GO 语言编写您自己的区块链](https://juejin.im/post/5ad95b056fb9a07aa349cd41) ([Starriers](https://github.com/Starriers) 翻译) -* [以太坊钱包详解](https://juejin.im/post/5ae2942ff265da0b886d23df) ([XatMassacrE](https://github.com/XatMassacrE) 翻译) -* [用 Go 编写你自己的区块链挖矿算法!](https://juejin.im/post/5ad6d2ff51882579ef4f7cf0) ([EmilyQiRabbit](https://github.com/EmilyQiRabbit) 翻译) -* [区块链如何帮助重塑医疗保健行业](https://juejin.im/post/5ad449b56fb9a028c6762db5) ([EmilyQiRabbit](https://github.com/EmilyQiRabbit) 翻译) +* [什么是以太坊?以太坊初学者手把手教程](https://juejin.im/post/5ba850a36fb9a05d0b14369f) ([cdpath](https://github.com/cdpath) 翻译) +* [2018 年 Security Token 发展现状及未来展望](https://juejin.im/post/5bab10c16fb9a05d2e1ba2ce) ([StellaBauhinia](https://github.com/StellaBauhinia) 翻译) +* [ELI5:用简单的语言给小白解释什么是以太坊?](https://juejin.im/post/5bb070b16fb9a05ce02a8a26) ([mingxing47](https://github.com/mingxing47) 翻译) +* [EthList:众包维护的以太坊阅读清单](https://juejin.im/post/5b9bd2f6f265da0af5030fed) ([StellaBauhinia](https://github.com/StellaBauhinia) 翻译) * [所有区块链译文>>](https://github.com/xitu/gold-miner/blob/master/blockchain.md) ## 人工智能 -* [利用 Keras 深度学习库进行词性标注教程](https://juejin.im/post/5ae4613a5188256727742d7d) ([luochen1992](https://github.com/luochen1992) 翻译) -* [Facebook 的 AI 万金油:StarSpace 神经网络模型简介](https://juejin.im/post/5a83af7c6fb9a0633c661404) ([noahziheng](https://github.com/noahziheng) 翻译) -* [Facebook 开源了物体检测研究项目 Detectron](https://juejin.im/post/5a6c2ba56fb9a01cb64f0591) ([SeanW20](https://github.com/SeanW20) 翻译) -* [IBM 工程师的 TensorFlow 入门指北](https://juejin.im/post/5a3d1ecb518825256362de6a) ([JohnJiangLA](https://github.com/JohnJiangLA) 翻译) +* [用 Python 实现马尔可夫链的初学者教程](https://juejin.im/post/5bb031d06fb9a05cdb104888) ([cdpath](https://github.com/cdpath) 翻译) +* [Python 中的无监督学习算法](https://juejin.im/post/5bab10ed6fb9a05d1f2211b6) ([zhmhhu](https://github.com/zhmhhu) 翻译) +* [用 Scikit-Learn 实现 SVM 和 Kernel SVM](https://juejin.im/post/5b7fd39af265da43831fa136) ([rockyzhengwu](https://github.com/rockyzhengwu) 翻译) +* [Sklearn 中的朴素贝叶斯分类器](https://juejin.im/post/5b8510be51882542d23a1d66) ([sisibeloved](https://github.com/sisibeloved) 翻译) * [所有 AI 译文>>](https://github.com/xitu/gold-miner/blob/master/AI.md) ## Android -* [为什么 Flutter 能最好地改变移动开发](https://juejin.im/post/5add65c46fb9a07aa541e97e) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) -* [为什么你应该开始使用 Kotlin](https://juejin.im/post/5adc1826f265da0b767d0db2) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) -* [Flutter 到底有多快?我开发了秒表应用来弄清楚。](https://juejin.im/post/5ad861566fb9a045ee01b48d) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) -* [为 JavaScript 程序员准备的 Flutter 指南](https://juejin.im/post/5ac43c536fb9a028da7cbd59) ([lsvih](https://github.com/lsvih) 翻译) +* [正确实现 linkedPurchaseToken 以避免重复订阅](https://juejin.im/post/5baf9a3e6fb9a05ce2741437) ([yuwhuawang](https://github.com/yuwhuawang) 翻译) +* [React Native 对 Flutter: 哪一个对创业公司更加友好?](https://juejin.im/post/5b949e5d5188255c791ae376) ([tanglie1993](https://github.com/tanglie1993) 翻译) +* [Flutter 的英雄和流氓们 —— 为 Flutterverse 带来平衡](https://juejin.im/post/5b8e75a46fb9a019f928e678) ([DateBro](https://github.com/DateBro) 翻译) +* [如何将 Stackdriver 连接到智能家居服务器以进行错误记录](https://juejin.im/post/5b8f8df7e51d450e9f66d6f0) ([Starriers](https://github.com/Starriers) 翻译) * [所有 Android 译文>>](https://github.com/xitu/gold-miner/blob/master/android.md) ## iOS -* [使用 Swift 实现原型动画](https://juejin.im/post/5ae28a9b6fb9a07aaa10fa1e) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) -* [不使用 fastlane 实现持续交付的 5 种选项](https://juejin.im/post/5acf47cb6fb9a028c523944c) ([melon8](https://github.com/melon8) 翻译) -* [没有 Interface Builder 的生活](https://juejin.im/post/5ab88ac0518825558a069c91) ([rydensun](https://github.com/rydensun) 翻译) -* [让我们来简化 UserDefaults 的使用](https://juejin.im/post/5abde324f265da23826e1723) ([talisk](https://github.com/talisk) 翻译) +* [在 iOS 中使用 UITests 测试 Facebook 登录功能](https://juejin.im/post/5b90e3ae6fb9a05d00458ad8) ([LoneyIsError](https://github.com/LoneyIsError) 翻译) +* [构建流畅的交互界面](https://juejin.im/post/5b7e2b34e51d4538843612cc) ([rydensun](https://github.com/rydensun) 翻译) +* [2018 年 iOS 开发找工作完全指南](https://juejin.im/post/5b7eca206fb9a019ff713277) ([melon8](https://github.com/melon8) 翻译) +* [你 Ladar 中该珍藏的:iOS 布局语言](https://juejin.im/post/5b84fe97f265da437c43422f) ([pmwangyang](https://github.com/pmwangyang) 翻译) * [所有 iOS 译文>>](https://github.com/xitu/gold-miner/blob/master/ios.md) ## 前端 -* [React 中的 Immutability:可变对象并没有什么问题](https://juejin.im/post/5ad807b36fb9a045d639ad18) ([jonjia](https://github.com/jonjia) 翻译) -* [如何调试前端:优化网络资源](https://juejin.im/post/5ade828751882567137dd2da) ([stormluke](https://github.com/stormluke) 翻译) -* [优化 WEBPACK 以更快地构建 REACT](https://juejin.im/post/5ae003d86fb9a07a9e4ce25d) ([Starriers](https://github.com/Starriers) 翻译) -* [深入浅出 SVG](https://juejin.im/post/5ad84f296fb9a045e8011be1) ([Starriers](https://github.com/Starriers) 翻译) +* [理解 JavaScript 中的执行上下文和执行栈](https://juejin.im/post/5ba32171f265da0ab719a6d7) ([CoolRice](https://github.com/CoolRice) 翻译) +* [如何使用原生 JavaScript 构建简单的 Chrome 扩展程序](https://juejin.im/post/5b98a58b6fb9a05cec4d92e0) ([shery](https://github.com/shery) 翻译) +* [CSS 变量和 JavaScript 让应用支持动态主题 🎨](https://juejin.im/post/5b9528de6fb9a05cf3710e00) ([CoolRice](https://github.com/CoolRice) 翻译) +* [JavaScript 2018 中即将迎来的新功能:异步生成器及更好的正则表达式](https://juejin.im/post/5b95be51f265da0adc18ac08) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) * [所有前端译文>>](https://github.com/xitu/gold-miner/blob/master/front-end.md) ## 后端 -* [GAN 的 Keras 实现:构建图像去模糊应用](https://juejin.im/post/5ad6e358f265da237b229bb2) ([luochen1992](https://github.com/luochen1992) 翻译) -* [用 Redis 和 Python 构建一个共享单车的 app](https://juejin.im/post/5adc861a51882567161a2799) ([Starriers](https://github.com/Starriers) 翻译) -* [Node.js 能进行 HTTP/2 推送啦!](https://juejin.im/post/5ad61595f265da23a04a129c) ([Raoul1996](https://github.com/Raoul1996) 翻译) -* [用 Skater 解读预测模型:打开模型的黑箱](https://juejin.im/post/5ae1494bf265da0b7c06f835) ([radialine](https://github.com/radialine) 翻译) +* [容器,虚拟机以及 Docker 的初学者入门介绍](https://juejin.im/entry/5bada97f6fb9a05d0e2e7014/detail) ([steinliber](https://github.com/steinliber) 翻译) +* [如何在六个月或更短的时间内成为DevOps 工程师,第二部分:配置](https://juejin.im/post/5baf677df265da0a951ee8f5) ([jianboy](https://github.com/jianboy) 翻译) +* [如何在六个月或更短的时间内成为DevOps 工程师,第三部分:版本控制](https://juejin.im/post/5bb067bfe51d450e905a0aa4) ([jianboy](https://github.com/jianboy) 翻译) +* [2018 年度最佳数据库即服务解决方案](https://juejin.im/post/5ba784e3e51d450e9d64984d) ([cf020031308](https://github.com/cf020031308) 翻译) * [所有后端译文>>](https://github.com/xitu/gold-miner/blob/master/backend.md) ## 设计 -* [细数那些我离不开的 Sketch 插件](https://juejin.im/post/5ae0623ef265da0b8d419aca) ([allenlongbaobao](https://github.com/allenlongbaobao) 翻译) -* [设计师与工程师协作的 5 项准则](https://juejin.im/post/5ac9e56af265da23945fc201) ([Starriers](https://github.com/Starriers) 翻译) -* [产品设计的环状循环](https://juejin.im/post/5aa74b32f265da23a4047aef) ([rydensun](https://github.com/rydensun) 翻译) -* [如何紧跟未来的设计趋势:15 个让你永远不过时的资料](https://juejin.im/post/5a52d2226fb9a01c9525ebbe) ([kangkai124](https://github.com/kangkai124) 翻译) +* [如何创建一个设计体系来赋能团队 —— 关注人,而非像素](https://juejin.im/post/5bac2a2fe51d450e942f4853) ([pmwangyang](https://github.com/pmwangyang) 翻译) +* [另外 5 种关于视觉和认知间差异的绘画练习](https://juejin.im/post/5baa5b45f265da0ab915cb7f) ([Ruixi](https://github.com/Ruixi) 翻译) +* [设计 QA 在应用程序设计中的重要性](https://juejin.im/post/5b89421651882542da339868) ([lihanxiang](https://github.com/lihanxiang) 翻译) +* [构建未来的设计生态系统](https://juejin.im/post/5b992be8f265da0aa3591346) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) * [所有设计译文>>](https://github.com/xitu/gold-miner/blob/master/design.md) ## 产品 -* [怎样把取消订阅的用户吸引回来](https://juejin.im/post/5acc1538518825651d07fdd1) ([allenlongbaobao](https://github.com/allenlongbaobao) 翻译) -* [游戏即服务的五条建议,提升游戏变现能力](https://juejin.im/post/5aa88773f265da23a228cc49) ([pthtc](https://github.com/pthtc) 翻译) -* [如果你想让用户回来,为什么前十分钟是至关重要的?](https://juejin.im/entry/5a7fe27f5188257a6854ce6a) ([sunshine940326](https://github.com/sunshine940326) 翻译) -* [一文教你预测 APP 未来的货币化情况](https://juejin.im/post/5a7a94d36fb9a0634d2793c6) ([pthtc](https://github.com/pthtc) 翻译) +* [如果界面产品设计师设计实体产品](https://juejin.im/post/5baf9697e51d456f087ba2a8) ([ssshooter](https://github.com/ssshooter) 翻译) +* [四个「为什么」:是什么在背后驱动你的产品?](https://juejin.im/post/5bac279cf265da0adc18d31a) ([pmwangyang](https://github.com/pmwangyang) 翻译) +* [为 APP 设计通知提醒](https://juejin.im/post/5ba31ee3e51d450e4115500b) ([rydensun](https://github.com/rydensun) 翻译) +* [虚构问题,低质量软件的根源](https://juejin.im/post/5b65122be51d4517c564d54f) ([ssshooter](https://github.com/ssshooter) 翻译) * [所有产品译文>>](https://github.com/xitu/gold-miner/blob/master/product.md) ## 其他 -* [Deploy != Release(第一部分):Deploy 与 Release 的区别及为什么很重要?](https://juejin.im/post/5ad80983f265da505c3c1b3a) ([stormluke](https://github.com/stormluke) 翻译) -* [引导员手册:24 个设计冲刺技巧](https://juejin.im/post/5ae3254d6fb9a07abc29a741) ([PokerF](https://github.com/PokerF) 翻译) -* [简短而又完全精确的编程语言历史](https://juejin.im/post/5ac1b8a25188255c637b1cd5) ([Starriers](https://github.com/Starriers) 翻译) -* [让 Apache Cassandra 尾部延迟减小 10 倍(已开源)](https://juejin.im/post/5ac31083f265da239a5fff0c) ([stormluke](https://github.com/stormluke) 翻译) +* [下一代包管理工具](https://juejin.im/post/5ba9830fe51d450e4d2fe208) ([diliburong](https://github.com/diliburong) 翻译) +* [新手开发者须知](https://juejin.im/post/5bade6a76fb9a05d32515cf0) ([ssshooter](https://github.com/ssshooter) 翻译) +* [关于工程师和影响力](https://juejin.im/post/5b8f9a96f265da0ab33125b0) ([cf020031308](https://github.com/cf020031308) 翻译) +* [用 Workers 让静态网站动态化](https://juejin.im/post/5b95c5375188255c6e70422a) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) * [所有其他分类译文>>](https://github.com/xitu/gold-miner/blob/master/others.md) # Copyright diff --git a/TODO/a-crash-course-in-assembly.md b/TODO/a-crash-course-in-assembly.md index c33888da140..d21f142f4e5 100644 --- a/TODO/a-crash-course-in-assembly.md +++ b/TODO/a-crash-course-in-assembly.md @@ -32,7 +32,7 @@ 指令分解的方式是特定于当前大脑构造的。 -例如,这种结构的大脑可能总是将前六个字节传送给 ALU。ALU 根据接收到的序列中 1 和 0 的排列,就会明白需要将两个东西加在一起。 +例如,这种结构的大脑可能总是将前六位传送给 ALU。ALU 根据接收到的序列中 1 和 0 的排列,就会明白需要将两个东西加在一起。 这个字段称为操作码(opcode),它的作用是告诉 ALU 要执行的操作。 diff --git a/TODO/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks.md b/TODO/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks.md index 20ca6b8638f..48514720564 100644 --- a/TODO/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks.md +++ b/TODO/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks.md @@ -5,7 +5,7 @@ > * 译者:[曹小帅](https://github.com/caoxiaoshuai1) > * 校对者:[PCAaron](https://github.com/PCAaron) [Usey95](https://github.com/Usey95) -# JavaScript 是如何工作的:内存管理 + 处理常见的4种内存泄漏 +# JavaScript 是如何工作的:内存管理 + 处理常见的 4 种内存泄漏 几周前,我们开始了一系列旨在深入挖掘 JavaScript 及其工作原理的研究。我们的初衷是:通过了解 JavaScript 代码块的构建以及它们之间协调工作的原理,我们将能够编写更好的代码和应用程序。 diff --git a/TODO/performance-metrics-whats-this-all-about.md b/TODO/performance-metrics-whats-this-all-about.md index 37d254dfee9..8a18cccfb9f 100644 --- a/TODO/performance-metrics-whats-this-all-about.md +++ b/TODO/performance-metrics-whats-this-all-about.md @@ -10,7 +10,7 @@ ![](https://cdn-images-1.medium.com/max/1000/1*hT4ixOXHZ8KRZ3YfbpAxbg.png) -测量页面的加载性能是一项艰难的任务。因此 [Google Developers](https://medium.com/@googledevs) 正和社区一起致力于渐进式网页指标(Progressive Web Metrics,简称 PWM’s)。 +测量页面的加载性能是一项艰难的任务。因此 [Google Developers](https://medium.com/@googledevs) 正和社区一起致力于建立渐进式网页指标(Progressive Web Metrics,简称 PWM’s)。 PWM’s 都是些什么,我们为什么需要它们? @@ -18,7 +18,7 @@ PWM’s 都是些什么,我们为什么需要它们? 此前我们有两个主要的点(事件)来测量性能: -`DOMContentLoaded` — 页面加载时触发,但脚本文件刚刚开始执行。 +`DOMContentLoaded` — 页面加载完成但脚本文件刚刚开始执行时触发(译者注:这里指初始的 HTML 文档加载并解析完成,但不包括样式表、图像和子框架的加载完成,参考 [MDN DOMContentLoaded 事件](https://developer.mozilla.org/zh-CN/docs/Web/Events/DOMContentLoaded))。 `load` 事件在页面完全加载后触发,此时用户已经可以使用页面或应用。 @@ -26,25 +26,25 @@ PWM’s 都是些什么,我们为什么需要它们? ![timeline trace of reddit.com](https://cdn-images-1.medium.com/max/1000/1*hFyHeo1-iI62aMQT8P8ORw.png) -> 时至今日,我们也可以看到 `window.onload` 并没有像以前那样真实反映出用户的感知。 +> 时至今日,我们可以看到 `window.onload` 并不像以前那样能真实反映出用户的体验了。 -> 参考:[Steve Souders](https://medium.com/@souders),[Moving beyond window.onload()](https://www.stevesouders.com/blog/2013/05/13/moving-beyond-window-onload/) (2013) +> —— [Steve Souders](https://medium.com/@souders),[Moving beyond window.onload()](https://www.stevesouders.com/blog/2013/05/13/moving-beyond-window-onload/) (2013) -确实,`DOMContentLoaded` 的问题在于解析和执行 JavaScript 的时间,如果脚本文件太大,那么这个时间就会非常长。比如移动设备,在 3G 网络的限制下测量跟踪时间轴,就会发现要花费差不多十秒才能到达 `load` 点。 +确实,`DOMContentLoaded` 的问题在于不包含解析和执行 JavaScript 的时间,如果脚本文件太大,那么这个时间就会非常长。比如移动设备,在 3G 网络的限制下测量跟踪时间轴,就会发现要花费差不多十秒才能到达 `load` 点。 另一方面,`load` 事件太晚触发,就无法分析出页面的性能瓶颈。 所以我们能否依赖这些指标?它们到底给我们提供了什么信息? -而且最主要的问题是,从页面开始加载直至加载完成,**用户如何感知这个过程**? +而且最主要的问题是,从页面开始加载直至加载完成,**用户对这个过程的感知如何**? -为什么加载感知会如此重要?可以参考 [Chrome Developers](https://medium.com/@ChromiumDev) 上的一篇文章:[Leveraging the Performance Metrics that Most Affect User Experience](https://developers.google.com/web/updates/2017/06/user-centric-performance-metrics),再次强调了加载问题。 +为什么加载感知会如此重要?可以参考 [Chrome Developers](https://medium.com/@ChromiumDev) 上的一篇文章:[Leveraging the Performance Metrics that Most Affect User Experience](https://developers.google.com/web/updates/2017/06/user-centric-performance-metrics),其再次强调了 `load` 的问题。 -看一下下方柱状图,X 轴展示了加载时间,Y 轴展示了体验到加载时长在特定时间区间里的用户的相对数量,你就可以明白不是所有用户的体验加载时间都会小于两秒。 +看一下下方柱状图,其 X 轴展示了加载时长,Y 轴展示了实际加载时长在特定时间区间里的用户的相对数量,你就可以明白不是所有用户的体验到的加载时间都会小于两秒。 ![](https://cdn-images-1.medium.com/max/1000/1*gw7eB5MF4SDAk1TGHSUlkg.png) -因此在我们的试验里,17 秒左右的 `load` 时间在获取用户感知加载这方面是没有什么价值的。用户在这 17 秒里到底看到了什么?白屏?加载了一半的页面?页面假死(用户无法点击输入框或滚动)?如果这些问题有答案的话: +因此在我们的试验里,17 秒左右的 `load` 事件在了解用户加载感知方面是没有什么价值的。用户在这 17 秒里到底看到了什么?白屏?加载了一半的页面?页面假死(用户无法点击输入框或滚动)?如果这些问题有答案的话: 1. 可以改善用户体验 2. 给应用带来更多的用户 @@ -56,8 +56,7 @@ PWM’s 都是些什么,我们为什么需要它们? 1. “**它正在运行吗?**” -我的网页浏览开始了吗(服务器有回应,等等)? -Has my navigation started successfully (the server has responded, etc.)? +我的网页开始载入了吗(服务器有回应,等等)? 2. “**它有用吗?**” @@ -69,7 +68,7 @@ Has my navigation started successfully (the server has responded, etc.)? 4. “**用户体验良好吗?**” -是否没有滚动卡顿、动画卡顿、无样式内容闪烁和缓慢的 Web 字体文件加载等问题出现,让我感到惊喜? +我是否因没有出现滚动卡顿、动画卡顿、无样式内容闪烁和 Web 字体文件加载缓慢等问题而感到惊喜? * * * @@ -77,7 +76,7 @@ Has my navigation started successfully (the server has responded, etc.)? ## 渐进式网页指标(Progressive Web Metrics) -PWM’s 的指标列表目的在于帮助检测性能瓶颈。除开 `load` 和 `DOMContentLoaded`,PWM's 给开发者提供了更多更详细的关于页面加载的信息。 +PWM’s 是一组用来帮助检测性能瓶颈的指标。除开 `load` 和 `DOMContentLoaded`,PWM's 给开发者提供了页面加载过程中更多更详细的信息。 下面让我们用 reddit.com 的跟踪时间轴来探究一下 PWM’s,并尝试弄明白每个指标的意思。 @@ -89,21 +88,21 @@ PWM’s 的指标列表目的在于帮助检测性能瓶颈。除开 `load` 和 我曾经说我们只有两个指标,这其实不太准确。(Chrome)开发者工具还给我们提供了一个指标 - FP。这个指标表示页面绘制的时间点,换句话说它表示当用户第一次看到白屏的时间点(下面是 msn.com 的 FP 截屏)。可以在[规范说明](https://github.com/w3c/paint-timing)里阅读更多相关内容。 -![](https://cdn-images-1.medium.com/max/800/1*IuI-OeOiJByd_kbOnQ4T6A.png) +![First Paint of msn.com](https://cdn-images-1.medium.com/max/800/1*IuI-OeOiJByd_kbOnQ4T6A.png) -想弄明白它是如何工作的话,作为例子,我们可以看一下 Chromium 图层的底层原理。 +为了弄明白它是如何工作的,我们以 Chromium 中 Graphic Layer 的底层实现作为例子(译者注:关于GraphicsLayer,可以参考 [WEBKIT 渲染不可不知的这四棵树](https://juejin.im/entry/57f9eb9e0bd1d00058bc0a1b)或[无线性能优化:Composite](http://taobaofed.org/blog/2016/04/25/performance-composite/) 中相关内容)。 ![Simplified Chromium Graphics Layer](https://cdn-images-1.medium.com/max/800/1*w0ejDtPxaRfJsyGRgoE02A.png) -FP 事件在图层进行绘制的时候触发,而不是文本、图片或 Canvas 出现的时候,但它也在列表里给出了一些开发者尝试使用的信息。 +FP 事件在 Graphic Layer 进行绘制的时候触发,而不是文本、图片或 Canvas 绘制的时候,但它也给出了一些开发者尝试使用的信息。 -但它并不是标准指标,所以测量就变得非常棘手。因此用到了一些不同的 “取巧” 技术,比如: +然而它并不是标准指标,所以测量就变得非常棘手。因此用到了一些不同的 “取巧” 技术,比如: -* 附加 `requestAnimationFrame` 使用 +* 使用 `requestAnimationFrame` * 捕捉 CSS 资源加载 * 甚至使用 `DOMContentLoaded` 和 `load` 事件(它们的问题之前已经讲过) -但是,尽管做出了这些努力,它仍然不具有太大的价值,因为文本、图片和 Canvas 可能在 FP 事件触发没多久就会进行绘制,而这些则会被诸如页面权重、CSS 或 JavaScript 资源大小等性能瓶颈所影响。 +尽管做出了这些努力,但它实际并没有太大的价值,因为文本、图片和 Canvas 可能在 FP 事件触发一段时间后才会进行绘制,而这个时间间隔会受到诸如页面体积、CSS 或 JavaScript 资源大小等性能瓶颈所影响。 > 这个指标不属于 PWM 的一部分,但它对于理解下面将要讲到的指标很有帮助。 @@ -111,30 +110,30 @@ FP 事件在图层进行绘制的时候触发,而不是文本、图片或 Canv ### **首次内容绘制(First Contentful Paint,FCP)** -这是当用户看见一些“内容”元素被绘制在页面上的时间点。和白屏是不一样的,它可以是文本的首次出现,或者 SVG 的首次出现,或者 Canvas 的首次出现等等。 +这是当用户看见一些“内容”元素被绘制在页面上的时间点。和白屏是不一样的,它可以是文本的首次绘制,或者 SVG 的首次出现,或者 Canvas 的首次绘制等等。 因此,用户可能会产生疑问,**它正在运行吗?** 页面是否在他(她)键入 URL 并按 enter 键后开始加载了呢? ![First Paint vs First Contentful Paint of msn.com](https://cdn-images-1.medium.com/max/800/1*UduDmCWTDefC6CHubA-lTQ.png) -继续看一下 Chromium,FCP 事件在文本(正在等待字体文件加载的文本不计算在内)、图片、Canvas 等元素绘制期间就已经被触发了。因此,FP 和 FCP 的时间差异可能从几毫秒到几秒不等。这个差别甚至可以从上面的图片中看出来。这就是为什么用一个指标来表示真实的首次内容绘制是有价值的。 +继续看一下 Chromium,FCP 事件在文本(正在等待字体文件加载的文本不计算在内)、图片、Canvas 等元素绘制时被触发。结果表明,FP 和 FCP 的时间差异可能从几毫秒到几秒不等。这个差别甚至可以从上面的图片中看出来。这就是为什么用一个指标来表示真实的首次内容绘制是有价值的。 > 你可以从[这里](https://docs.google.com/document/d/1kKGZO3qlBBVOSZTf-T8BOMETzk3bY15SC-jsMJWv4IE/edit#)阅读所有的规范说明。 **FCP 指标如何对开发者产生价值?** -如果**首次内容绘制**耗时太长,那么: +如果**首次内容绘制**前耗时太长,那么: * 你的网络连接可能有性能问题 * 资源太过庞大(如 index.html),传输它们消耗太多时间 -阅读 [Ilya Grigorik](https://medium.com/@igrigorik) 写的 [High Performance Browser Networking](https://hpbn.co/) 了解更多关于网络性能的问题,消除这些因素的影响。 +阅读 [Ilya Grigorik](https://medium.com/@igrigorik) 写的 [High Performance Browser Networking](https://hpbn.co/) 了解更多关于网络性能的问题,以消除这些因素的影响。 * * * ### 首次有意义绘制(First Meaningful Paint,FMP) -这是指页面主要内容出现在屏幕上的时间点,因此,**它有用吗?** +这是指页面主要内容出现在屏幕上的时间点,因此——**它有用吗?** ![First Paint vs First Contentful Paint vs First Meaningful Paint of msn.com](https://cdn-images-1.medium.com/max/800/1*835Kq5Mzw87L8XRoXXyKIw.png) @@ -144,7 +143,7 @@ FP 事件在图层进行绘制的时候触发,而不是文本、图片或 Canv * 博客的标题和文本 * 搜索引擎的搜索文本 -* 对于电子商务产品来说重要的图片 +* 电子商务产品中重要的图片 展示的时候。 @@ -158,7 +157,7 @@ FP 事件在图层进行绘制的时候触发,而不是文本、图片或 Canv > FMP = 最大布局变化时的绘制 -基于 Chromium 的实现,这个绘制是使用 [LayoutAnalyzer](https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/layout/LayoutAnalyzer.h&sq=package:chromium&type=cs) 进行计算的,它会收集所有的布局变化,当布局发生最大变化时得出时间。而这个时间就是 FMP。 +基于 Chromium 的实现,这个绘制是使用 [LayoutAnalyzer](https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/layout/LayoutAnalyzer.h&sq=package:chromium&type=cs) 进行计算的,它会收集所有的布局变化,得到布局发生最大变化时的时间。而这个时间就是 FMP。 > 你可以从[这里](https://docs.google.com/document/d/1BR94tJdZLsin5poeet0XoTW60M0SjvOJQttKT-JK8HI/edit#)阅读所有的规范说明。 @@ -181,7 +180,7 @@ FP 事件在图层进行绘制的时候触发,而不是文本、图片或 Canv ### 视觉上准备好 -当页面看上去“接近”加载完成,但浏览器还没有执行完所有脚本文件的状态。 +当页面看上去“接近”加载完成,但浏览器还没有执行完所有脚本文件的时候。 * * * @@ -193,9 +192,9 @@ FP 事件在图层进行绘制的时候触发,而不是文本、图片或 Canv **长任务** -浏览器底层将所有用户输入打包在一个任务里(UI 任务),并在主线程中将它们放到一个队列里。除此之外,浏览器还必须在页面上解析、编译和执行 JavaScript 代码(应用任务)。如果每个应用任务要耗费很长时间的话,那么用户输入任务就可能在其他任务结束前受到阻塞。因此它就会延迟与页面的交互,页面行为就会变得卡顿有延迟。 +浏览器底层将所有用户输入打包在一个任务里(UI 任务),并将它们放到主线程的一个队列里。除此之外,浏览器还必须解析、编译并执行页面上的 JavaScript 代码(应用任务)。如果每个应用任务要耗费很长时间的话,那么用户输入任务就可能受到阻塞,直到这些应用任务执行完成。因此它就会延迟与页面的交互,页面就会表现出卡顿和延迟。 -简单来说,长任务就是指耗时大于 50 毫秒的解析、编译和执行 JavaScript 代码块。 +简单来说,长任务就是指解析、编译或执行 JavaScript 代码块的耗时大于 50 毫秒。 > 你可以从[这里](https://w3c.github.io/longtasks/)阅读所有的规范说明。 @@ -207,11 +206,11 @@ FP 事件在图层进行绘制的时候触发,而不是文本、图片或 Canv * * * -### 首次交互 +### 首次可交互 -交互 - **它可以使用了吗?** 是的,这是当用户看见视觉上准备好的页面时提出的问题,他们希望能与页面产生交互。 +可交互 - **它可以使用了吗?** 是的,这是当用户看见视觉上准备好的页面时提出的问题,他们希望能与页面产生交互。 -首次交互发生需满足以下条件: +首次可交互发生需满足以下条件: * *FMP* * && @@ -219,28 +218,28 @@ FP 事件在图层进行绘制的时候触发,而不是文本、图片或 Canv * && * 页面视觉完成度在 85% -首次交互 - 这个指标可以拆分成两个指标,首次交互的时间(Time to First Interactive,TTFI)和首次持续交互的时间(Time to First Consistently Interactive,TTCI)。 +首次可交互 - 这个指标可以拆分成两个指标,首次可交互的时间(Time to First Interactive,TTFI)和首次可持续交互的时间(Time to First Consistently Interactive,TTCI)。 拆分的原因在于: -* 当 UI 响应良好时,定义最小程度的交互,但如果响应不好也可以接受 -* 当网站有着完整且令人愉悦的交互,并严格遵循 [RAIL](https://developers.google.com/web/fundamentals/performance/rail) 的指导原则时 +* 定义最小程度的可交互,当 UI 响应良好时满足可交互,但如果响应不好也可以接受 +* 当网站完全的、令人愉悦的可交互,并严格遵循 [RAIL](https://developers.google.com/web/fundamentals/performance/rail) 的指导原则时 **TTCI** ![](https://cdn-images-1.medium.com/max/800/0*6qzJAADPmBaNSwFw.) -使用逆序分析,从追踪线的尾端开始看,发现页面加载活动保持了 5 秒的安静并且再无更多的长任务执行,得到了一段叫做**安静窗口**的时期。安静窗口之后及第一个长任务(从安静期结束后开始算)之前的时间就是**TTCI**。 +使用逆序分析,从追踪线的尾端开始看,发现页面加载活动保持了 5 秒的安静并且再无更多的长任务执行,得到了一段叫做**安静窗口**的时期。安静窗口之后的第一个长任务(从结束时间向前开始算)之前的时间点就是 **TTCI**(译者注:这里是将整个时间线反转过来看的,实际表示的是安静窗口前,最接近安静窗口的长任务的结束时间)。 **TTFI** ![](https://cdn-images-1.medium.com/max/800/0*xWGGBiXh0pLiPeuk.) -这个指标的定义和 TTCI 有一点不同。我们从头至尾来分析跟踪时间轴。在 FMP 发生后应该有 3 秒的安静窗口。这个时间已经足够说明页面对于用户来说是可交互的。但可能会有**长任务**在这个安静窗口期间或之后开始执行,它们可以被忽略。 +这个指标的定义和 TTCI 有一点不同。我们从头至尾来分析跟踪时间轴。在 FMP 发生后有一个 3 秒的安静窗口。这个时间已经足够说明页面对于用户来说是可交互的。但可能会有**独立任务**在这个安静窗口期间或之后开始执行,它们可以被忽略。 -> **长任务** - 距离 FMP 很远执行的任务,并由 250ms 的执行时间期间(信道大小)和在信道大小前后的 1 秒安静期分隔开来。这个示例任务有可能是第三方广告或者分析脚本。 +> **独立任务** - 将 250ms 中执行的多个任务视为一个任务,当一个任务距离 FMP 很远才执行,且在这个任务前后均有一个 1 秒的安静期,则其为一个“独立任务”。举例来说,这个任务可能是第三方广告或者分析脚本。 -> 有时长于 250 毫秒的“长任务”会对页面有严重的影响。 +> 有时长于 250 毫秒的“独立任务”会对页面性能有严重的影响。 > 比如检测**adblock** @@ -248,7 +247,7 @@ FP 事件在图层进行绘制的时候触发,而不是文本、图片或 Canv **TTFI 和 TTCI 指标如何对开发者产生帮助?** -当线程长时间处于**视觉上准备好**和**首次交互**中间忙碌状态时 +当线程在**视觉上准备好**和**首次可交互**之间忙碌了很长时间的时候 ![](https://cdn-images-1.medium.com/max/800/1*_uAiHAv4-bpoMFYqgbBKcQ.png) diff --git a/TODO1/5-more-drawing-exercises.md b/TODO1/5-more-drawing-exercises.md new file mode 100644 index 00000000000..70dc0595ebb --- /dev/null +++ b/TODO1/5-more-drawing-exercises.md @@ -0,0 +1,141 @@ +> * 原文地址:[5 more drawing exercises on the difference between seeing and knowing](https://medium.com/personal-growth/5-more-drawing-exercises-9c0df4645387) +> * 原文作者:[Ralph Ammer](https://medium.com/@ralphammer?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/5-more-drawing-exercises.md](https://github.com/xitu/gold-miner/blob/master/TODO1/5-more-drawing-exercises.md) +> * 译者:[Ruixi](https://github.com/Ruixi) +> * 校对者:[diliburong](https://github.com/diliburong) + +# 另外 5 种关于视觉和认知间差异的绘画练习 + +> 本文是本系列文章的第二篇,第一篇请见:[绘图技巧的快速入门 +6 个绘图练习,让你立即上手!](https://github.com/xitu/gold-miner/blob/f57f636bd20e9b3aa0d423435e98e5b556a49b71/TODO1/a-quick-beginners-guide-to-drawing.md) + +![](https://cdn-images-1.medium.com/max/800/1*8NDD5zLkYppl5BFoFfVrcg.gif) + +下面的练习方法要比《[绘图技巧的快速入门](https://medium.com/personal-growth/a-quick-beginners-guide-to-drawing-58213877715e)》中的**更完善一点**,希望你们可以感受到同样的乐趣! + +我们将着眼于各个方面的**观察**来**加强我们的视觉思维**。 + +## 练习 1:负空间——见有于无之中 + +> **在元素之间构建空间!** + +![](https://cdn-images-1.medium.com/max/800/1*BcBbHjBYf1Y0LzPcyjxW-A.gif) + +看到你的马克杯把手的那个”洞“,你手中握着的那个奇葩的图形了吗?环顾四周,找到元素间**虚的部分**。就在你手头的纸上随便画随便排,想怎么画都成。 + +![](https://cdn-images-1.medium.com/max/800/1*knCfDWbzlVcc25wIajBgBQ.gif) + +我们很容易看到并画出已为我们所知的:这是一辆汽车,这是一栋房子,这是我的猫,等等。我们对于辨认事物的侧重,使我们对它们的外轮廓视而不见。而当我们专注于物体**之间**是什么形状时,则是在欺骗自己的感知来**看到目标的轮廓**。 + + +![](https://cdn-images-1.medium.com/max/800/1*rNaa_2y4QJir89_9HG1N4w.gif) + +小贴士:字体设计师们明白负空间的重要性。多多留意在字母之间的空间,才能更加凸显字体的特点! + +## 练习 2:动态线——见动于静之中 + +> **画出静物的动态!** + +![](https://cdn-images-1.medium.com/max/800/1*rs2qFH96L-E7dsILahV80w.gif) + +这个练习中,我们可以通过速写捕捉到**物体的动态**: + +![](https://cdn-images-1.medium.com/max/800/1*Pk0GI59VC53CVyvmVBiX4Q.gif) + +左边的轮廓告诉我们“这是什么”,而右侧的**动态线**则告诉我们**它的状态**”。 + +![](https://cdn-images-1.medium.com/max/800/1*w68p3V-bV_fsaazAXZvLZg.gif) + +当你绘制人物时,就算他们静止不动,给观者以富于动感和**肢体控制力**的体验也是殊为重要的。 + +在场景中对**特征性动作**的观察与传达会使你的画作栩栩如生。他们是**图画中的旋律**。 + +小贴士:请**快速而小心**地进行动态线条的练习!它们是**短时间内的集中爆发**。 + +注:有的人喜欢把这类图画称为“示意图”。我更喜欢用“动态”来称呼它。因为它完全就是对主体动态的描述。 + +## 练习 3:近大远小——见透视于空间之中 + +> **找一个盒子,找到它的消失线并且画出来!** + +![](https://cdn-images-1.medium.com/max/800/1*1l5OWqIxR9TQS6YUNVn9gA.gif) + +在《[快速入门](https://medium.com/personal-growth/a-quick-beginners-guide-to-drawing-58213877715e)》中,我们**构建**了一个立方体。地平线定义了我们的视高。在灭点的帮助下,我们绘制出了两侧略有缩减的效果。 + +![](https://cdn-images-1.medium.com/max/800/1*bFTDdSIT5K0koXaa-c35eg.gif) + +这回我们换个方法:**我们探寻一下在立体图形中的斜线**。 + +找一个立方体物体,观察盒子形状的透视变化。**慢慢来~看!**看到线条是如何伸展到灭点的了吗? + +![](https://cdn-images-1.medium.com/max/800/1*Uvlvz7PDyTD6xcUZAMGIQw.gif) + +现在画下物体的**线框**!你不用非得去定义地平线或者灭点什么的。自己给估个差不离的位置,然后让线条适当地聚拢就可以了。再找些大小不一的立方体物体,多画画吧! + +![](https://cdn-images-1.medium.com/max/800/1*4Fu6cFFfpTQ0rltcCTcKuw.gif) + +## 练习 4:比例——见平于不平之中 + +> **画个窗子——你都看到了外面的什么!** + +![](https://cdn-images-1.medium.com/max/800/1*kleqsKsJCSfkS6bpeUqMuw.gif) + +“比例”这个词描述的是**尺寸间的关系**。它有助于在提笔之前解决我们弄不清远近不同的物体之间的尺寸比对的难题。 + +远处的物体看上去会更小。我们已经明白了在我们比对尺寸的时候需要考虑距离的因素。 + +![](https://cdn-images-1.medium.com/max/800/1*BQ3-uCbITyvhSmFZS5WbEg.gif) + +我们实际看到的大小和我们所知道的大小是有区别的。下方这些“视错觉”**演示我们的感知力**。我们的大脑从几条斜线中得到线索,构造一个虚拟空间并进行“计算”,在这个空间里,右边的那个人肯定高点: + +![](https://cdn-images-1.medium.com/max/800/1*sziDSDPlZXLktfu5VjB4ww.gif) + +这种补偿机制在我们的日常生活中非常有用。但它也让我们在绘图时很难估测需要的尺寸。为了得到正确的“纸上”大小,我们必须**忽略我们所知道的大小,并画出我们所看到的大小**。我们知道,远处的人(实际上)并不小,但我们需要在纸上画得更小一些。 + +![](https://cdn-images-1.medium.com/max/800/1*OmS1Ft0X1eXAOEdoM1L5tA.gif) + +这里有一些技巧可以克服这些令人心烦意乱的现象,实现“看平”。其中之一是将手放在与眼睛保持恒定距离的地方**并用笔测量“平面”尺寸**。 + +![](https://cdn-images-1.medium.com/max/800/1*Yife9sf_I7z39lz4ioJX3g.gif) + +另一个办法是框住视野。它有助于让我们看到自己要绘制的平面图画,**估测“纸上”的尺寸**,并**进行适当对比**。 + +![](https://cdn-images-1.medium.com/max/800/1*qE8X1Ev19ZggIlBIcHxKdA.gif) + +我建议你通过一个窗框来看世界。窗框可以作为**示例**来参考,框住的画面中的树或者房子有多高。 + +## 练习 5:深度——见秩序于重叠之中 + +> **画株植物!** + +![](https://cdn-images-1.medium.com/max/800/1*mNhkP2Tty9WtrVK0a1VT1w.gif) + +很少有能比得上在大自然中静静地画上几个小时的植物的乐趣。也很少有需要我们如此专注的的时候。我们需要全神贯注,避免陷入平庸的象征性表达。 + +![](https://cdn-images-1.medium.com/max/800/1*sLpTjiAxJNXNb9TEI0BZ7Q.png) + +当然,有时候大自然的复杂性也是要人老命。我建议你只选一个植物的小细节,从一片叶子开始,然后完成剩下的部分。从这一片,再到下一片。当你画一个叶子或梗的时候,务必要**考虑它整体的形态,即便是模糊不清的地方**。重叠会产生一种非常棒的纵深感。 + +![](https://cdn-images-1.medium.com/max/800/1*s8b6vL8d6bHL7T3V561d0A.gif) + +我们尽可能想要准确地进行描绘的同时,也要牢牢记住:**绘画是抽象的表达**。就像一个好故事必须是可信的,但并不总是准确的。所以,当图形以不明确的方式重叠时,为观者理清这些模糊的存在是个不错的办法。 + +![](https://cdn-images-1.medium.com/max/800/1*h_oa1gOAAmgDra1OWm1quw.png) + +这种使层次分明的力量可能是绘画相对照片的最大优势之一。 + +## 跳出“符号陷阱” + +这些练习的目的是**把我们的认知从辨识物体转变为观察形状**。如果你想知道为什么这项技能对创造性思维至关重要,请移步《[看 VS 读](https://medium.com/personal-growth/seeing-vs-reading-29365d4540e2)》。 + +## 有玩得开心吗? + +我很好奇你最喜欢哪种练习方式,以及为什么。你在努力提升绘画的哪个方面?在下一组练习中,我应该涵盖什么主题? **尽请在下方留言!** + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/5-tips-to-write-better-conditionals-in-javascript.md b/TODO1/5-tips-to-write-better-conditionals-in-javascript.md new file mode 100644 index 00000000000..e0ff80b76b1 --- /dev/null +++ b/TODO1/5-tips-to-write-better-conditionals-in-javascript.md @@ -0,0 +1,382 @@ +> * 原文地址:[5 Tips to Write Better Conditionals in JavaScript](https://scotch.io/tutorials/5-tips-to-write-better-conditionals-in-javascript) +> * 原文作者:[Jecelyn Yeen](https://scotch.io/@jecelyn) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/5-tips-to-write-better-conditionals-in-javascript.md](https://github.com/xitu/gold-miner/blob/master/TODO1/5-tips-to-write-better-conditionals-in-javascript.md) +> * 译者: +> * 校对者: + +# 5 Tips to Write Better Conditionals in JavaScript + +![](https://scotch-res.cloudinary.com/image/upload/dpr_1,w_1050,q_auto:good,f_auto/v1536994013/udpahiv8rqlemvz0x3wc.png) + +When working with JavaScript, we deal a lot with conditionals, here are the 5 tips for you to write better / cleaner conditionals. + +## 1. Use Array.includes for Multiple Criteria + +Let's take a look at the example below: + +``` +// condition +function test(fruit) { + if (fruit == 'apple' || fruit == 'strawberry') { + console.log('red'); + } +} +``` + +At first glance, the above example looks good. However, what if we get more red fruits, say `cherry` and `cranberries`? Are we going to extend the statement with more `||` ? + +We can rewrite the conditional above by using `Array.includes` [(Array.includes)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) + +``` +function test(fruit) { + // extract conditions to array + const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries']; + + if (redFruits.includes(fruit)) { + console.log('red'); + } +} +``` + +We extract the `red fruits` (conditions) to an array. By doing this, the code looks tidier. + +## 2. Less Nesting, Return Early + +Let's expand the previous example to include two more conditions: + +* if no fruit provided, throw error +* accept and print the fruit quantity if exceed 10. + +``` +function test(fruit, quantity) { + const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries']; + + // condition 1: fruit must has value + if (fruit) { + // condition 2: must be red + if (redFruits.includes(fruit)) { + console.log('red'); + + // condition 3: must be big quantity + if (quantity > 10) { + console.log('big quantity'); + } + } + } else { + throw new Error('No fruit!'); + } +} + +// test results +test(null); // error: No fruits +test('apple'); // print: red +test('apple', 20); // print: red, big quantity +``` + +Look at the code above, we have: + +* 1 if/else statement that filter out invalid condition +* 3 levels of nested if statement (condition 1, 2 & 3) + +A general rule I personally follow is **return early when invalid conditions** found. + +``` +/_ return early when invalid conditions found _/ + +function test(fruit, quantity) { + const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries']; + + // condition 1: throw error early + if (!fruit) throw new Error('No fruit!'); + + // condition 2: must be red + if (redFruits.includes(fruit)) { + console.log('red'); + + // condition 3: must be big quantity + if (quantity > 10) { + console.log('big quantity'); + } + } +} +``` + +By doing this, we have one less level of nested statement. This coding style is good especially when you have long if statement (imagine you need to scroll to the very bottom to know there is an else statement, not cool). + +We can further reduce the nesting if, by inverting the conditions & return early. Look at condition 2 below to see how we do it: + +``` +/_ return early when invalid conditions found _/ + +function test(fruit, quantity) { + const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries']; + + if (!fruit) throw new Error('No fruit!'); // condition 1: throw error early + if (!redFruits.includes(fruit)) return; // condition 2: stop when fruit is not red + + console.log('red'); + + // condition 3: must be big quantity + if (quantity > 10) { + console.log('big quantity'); + } +} +``` + +By inverting the conditions of condition 2, our code is now free of a nested statement. This technique is useful when we have long logic to go and we want to stop further process when a condition is not fulfilled. + +However, that's no **hard rule** for doing this. Ask yourself, is this version (without nesting) better / more readable than the previous one (condition 2 with nested)? + +For me, I would just leave it as the previous version (condition 2 with nested). It is because: + +* the code is short and straight forward, it is clearer with nested if +* inverting condition may incur more thinking process (increase cognitive load) + +Therefore, always **aims for Less Nesting and Return Early but don't overdo it**. There is an article & StackOverflow discussion that talks further on this topic if you interested: + +* [Avoid Else, Return Early](http://blog.timoxley.com/post/47041269194/avoid-else-return-early) by Tim Oxley +* [StackOverflow discussion](https://softwareengineering.stackexchange.com/questions/18454/should-i-return-from-a-function-early-or-use-an-if-statement) on if/else coding style + +## 3. Use Default Function Parameters and Destructuring + +I guess the code below might look familiar to you, we always need to check for `null` / `undefined` value and assign default value when working with JavaScript: + +``` +function test(fruit, quantity) { + if (!fruit) return; + const q = quantity || 1; // if quantity not provided, default to one + + console.log(`We have ${q} ${fruit}!`); +} + +//test results +test('banana'); // We have 1 banana! +test('apple', 2); // We have 2 apple! +``` + +In fact, we can eliminate the variable `q` by assigning default function parameters. + +``` +function test(fruit, quantity = 1) { // if quantity not provided, default to one + if (!fruit) return; + console.log(`We have ${quantity} ${fruit}!`); +} + +//test results +test('banana'); // We have 1 banana! +test('apple', 2); // We have 2 apple! +``` + +Much easier & intuitive isn't it? Please note that each parameter can has it own [default function parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters). For example, we can assign default value to `fruit` too: `function test(fruit = 'unknown', quantity = 1)`. + +What if our `fruit` is an object? Can we assign default parameter? + +``` +function test(fruit) { + // printing fruit name if value provided + if (fruit && fruit.name) { + console.log (fruit.name); + } else { + console.log('unknown'); + } +} + +//test results +test(undefined); // unknown +test({ }); // unknown +test({ name: 'apple', color: 'red' }); // apple +``` + +Look at the example above, we want to print the fruit name if it's available or we will print unknown. We can avoid the conditional `fruit && fruit.name` checking with default function parameter & destructing. + +``` +// destructing - get name property only +// assign default empty object {} +function test({name} = {}) { + console.log (name || 'unknown'); +} + +//test results +test(undefined); // unknown +test({ }); // unknown +test({ name: 'apple', color: 'red' }); // apple +``` + +Since we only need property `name` from fruit, we can destructure the parameter using `{name}`, then we can use `name` as variable in our code instead of `fruit.name`. + +We also assign empty object `{}` as default value. If we do not do so, you will get error when executing the line `test(undefined)` - `Cannot destructure property name of 'undefined' or 'null'.` because there is no `name` property in undefined. + +If you don't mind using 3rd party libraries, there are a few ways to cut down null checking: + +* use [Lodash get](https://lodash.com/docs/4.17.10#get) function +* use Facebook open source's [idx](https://github.com/facebookincubator/idx) library (with Babeljs) + +Here is an example of using Lodash: + +``` +// Include lodash library, you will get _ +function test(fruit) { + console.log(__.get(fruit, 'name', 'unknown'); // get property name, if not available, assign default value 'unknown' +} + +//test results +test(undefined); // unknown +test({ }); // unknown +test({ name: 'apple', color: 'red' }); // apple +``` + +You may run the demo code [here](http://jsbin.com/bopovajiye/edit?js,console). Besides, if you are a fan of Functional Programming (FP), you may opt to use [Lodash fp](https://github.com/lodash/lodash/wiki/FP-Guide), the functional version of Lodash (method changed to `get` or `getOr`). + +## 4. Favor Map / Object Literal than Switch Statement + +Let's look at the example below, we want to print fruits based on color: + +``` +function test(color) { + // use switch case to find fruits in color + switch (color) { + case 'red': + return ['apple', 'strawberry']; + case 'yellow': + return ['banana', 'pineapple']; + case 'purple': + return ['grape', 'plum']; + default: + return []; + } +} + +//test results +test(null); // [] +test('yellow'); // ['banana', 'pineapple'] +``` + +The above code seems nothing wrong, but I find it quite verbose. The same result can be achieve with object literal with cleaner syntax: + +``` +// use object literal to find fruits in color + const fruitColor = { + red: ['apple', 'strawberry'], + yellow: ['banana', 'pineapple'], + purple: ['grape', 'plum'] + }; + +function test(color) { + return fruitColor[color] || []; +} +``` + +Alternatively, you may use [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) to achieve the same result: + +``` +// use Map to find fruits in color + const fruitColor = new Map() + .set('red', ['apple', 'strawberry']) + .set('yellow', ['banana', 'pineapple']) + .set('purple', ['grape', 'plum']); + +function test(color) { + return fruitColor.get(color) || []; +} +``` + +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) is the object type available since ES2015, allow you to store key value pair. + +_Should we ban the usage of switch statement?_ Do not limit yourself to that. Personally, I use object literal whenever possible, but I wouldn't set hard rule to block that, use whichever make sense for your scenario. + +Todd Motto has an article that dig deeper on switch statement vs object literal, you may read [here](https://toddmotto.com/deprecating-the-switch-statement-for-object-literals/). + +### TL;DR; Refactor the syntax + +For the example above, we can actually refactor our code to achieve the same result with `Array.filter` . + +``` + const fruits = [ + { name: 'apple', color: 'red' }, + { name: 'strawberry', color: 'red' }, + { name: 'banana', color: 'yellow' }, + { name: 'pineapple', color: 'yellow' }, + { name: 'grape', color: 'purple' }, + { name: 'plum', color: 'purple' } +]; + +function test(color) { + // use Array filter to find fruits in color + + return fruits.filter(f => f.color == color); +} +``` + +There's always more than 1 way to achieve the same result. We have shown 4 with the same example. Coding is fun! + +## 5. Use Array.every & Array.some for All / Partial Criteria + +This last tip is more about utilizing new (but not so new) Javascript Array function to reduce the lines of code. Look at the code below, we want to check if all fruits are in red color: + +``` +const fruits = [ + { name: 'apple', color: 'red' }, + { name: 'banana', color: 'yellow' }, + { name: 'grape', color: 'purple' } + ]; + +function test() { + let isAllRed = true; + + // condition: all fruits must be red + for (let f of fruits) { + if (!isAllRed) break; + isAllRed = (f.color == 'red'); + } + + console.log(isAllRed); // false +} +``` + +The code is so long! We can reduce the number of lines with `Array.every`: + +``` +const fruits = [ + { name: 'apple', color: 'red' }, + { name: 'banana', color: 'yellow' }, + { name: 'grape', color: 'purple' } + ]; + +function test() { + // condition: short way, all fruits must be red + const isAllRed = fruits.every(f => f.color == 'red'); + + console.log(isAllRed); // false +} +``` + +Much cleaner now right? In a similar way, if we want to test if any of the fruit is red, we can use `Array.some` to achieve it in one line. + +``` +const fruits = [ + { name: 'apple', color: 'red' }, + { name: 'banana', color: 'yellow' }, + { name: 'grape', color: 'purple' } +]; + +function test() { + // condition: if any fruit is red + const isAnyRed = fruits.some(f => f.color == 'red'); + + console.log(isAnyRed); // true +} +``` + +## Summary + +Let's produce more readable code together. I hope you learn something new in this article. + +That's all. Happy coding! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/7-javascript-eeg-mind-reading-libraries-for-2018.md b/TODO1/7-javascript-eeg-mind-reading-libraries-for-2018.md new file mode 100644 index 00000000000..bade25dc918 --- /dev/null +++ b/TODO1/7-javascript-eeg-mind-reading-libraries-for-2018.md @@ -0,0 +1,129 @@ +> * 原文地址:[7 Javascript EEG Mind Reading Libraries for 2018](https://blog.bitsrc.io/7-javascript-eeg-mind-reading-libraries-for-2018-9a8e28544cd7) +> * 原文作者:[Gilad Shoham](https://blog.bitsrc.io/@giladshoham?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/7-javascript-eeg-mind-reading-libraries-for-2018.md](https://github.com/xitu/gold-miner/blob/master/TODO1/7-javascript-eeg-mind-reading-libraries-for-2018.md) +> * 译者:[geniusq1981](https://github.com/geniusq1981) +> * 校对者:[Park-ma](https://github.com/Park-ma)、[huangyuanzhen](https://github.com/huangyuanzhen) + +# 2018 年七个通过脑电图分析实现“读心术”的 Javascript 库 + +## 用于探索人脑信号以实现读心的 JavaScript 库。 + +![](https://cdn-images-1.medium.com/max/1600/1*TOFxZJnsy9DPK3a3ZES05w.jpeg) + +“这个头戴装置是不是很酷?” + +脑电图是一种检测人脑中生物电活动的方法。它可以用来检测人体状态,比如癫痫或者脑瘤,以此来研究脑活动与认知方面的联系,或者用来学习人脑是如何对外部刺激产生反应,比如音乐或影像。 + +尽管相比其他方法,此方法还不够成熟,但是在一些方面它的用途还是很大的 — 可以通过外部设备将大脑活动转化成行为(比如装备激光武器的机器人军队)。 + +在脑电图信号的开发领域(由类似 [openBCI](http://openbci.com/) 这样的项目所引领),MathLab、python 和 R 都是十分 [流行的语言](https://www.researchgate.net/post/What_is_the_best_open_source_software_to_analyse_EEG_signals2)。但是就像其他领域,比如 [IOT](https://blog.bitsrc.io/10-javascript-iot-libraries-to-use-in-your-next-projects-bef5f9136f83)、[ML](https://blog.bitsrc.io/11-javascript-machine-learning-libraries-to-use-in-your-app-c49772cca46c) 和其他一些研究领域那样,Javascript [也会参与其中](http://www.castillo.io/blog/2016/4/25/neurojavascript/getting-your-brainwaves-to-the-browser-with-javascript)。 + +作为在 [**Bit**](https://bitsrc.io) 工作的一部分,我们一直在努力追寻 Javascript 前沿应用。所以,在这里是我们找到的一些非常炫酷的处理脑电图的 Javascript 库和示例。欢迎你能够提供其他更多有用的项目! + +### 1. Muse-js + +![](https://cdn-images-1.medium.com/max/1600/1*gN7_qSoxnCv7y2rW8WpO2g.gif) + +从这篇文章可以找到一个示例:[https://medium.com/@urish/reactive-brain-waves-af07864bb7d4](https://medium.com/@urish/reactive-brain-waves-af07864bb7d4) + +Muse-js 是一个与 2016 Muse 脑电头盔相匹配的 Javasript 库(使用 web bluetooth)。灵感来自于 [muse-lsl](https://github.com/alexandrebarachant/muse-lsl/blob/d2b74412585f3baa852516542a0d0853faec1b4e/muse/muse.py) python 库, muse-js 由 [@UriShaked](https://twitter.com/UriShaked) 编译,它的目标是:通过人脑直接控制网页。为什么不可以呢? + +Muse - js 可以让 web 开发者通过浏览器、RxJs 和 Angular 这样的工具去连接、分析或可视化脑电图数据。除了处理“普通”的脑电信号并把它们传送到网页上,muse-js 还可以处理与眼睛移动相关的脑电信号, 这不仅仅超级炫酷,而且对于人类认知的前沿研究也非常有帮助。尝试一下。 + +* [**urish/muse-js**: muse-js — Muse 2016 脑电头盔 Javascript 库(使用 Web Bluetooth)](https://github.com/urish/muse-js) + +* [**Reactive Brain Waves**: 如何使用 RxJS、Angular 和 Web Bluetooth,配合脑电头盔,发掘你的大脑](https://medium.com/@urish/reactive-brain-waves-af07864bb7d4) + +### 2. Wits + +![](https://cdn-images-1.medium.com/max/1600/1*AlCW5rzbus1jqJBDSiIkRw.gif) + +wits 是 Brain-Bits 项目的一部分, 它是一个 Node.js 库,可以读取来自 [Emotiv](https://www.emotiv.com/) EPOC 脑电头盔的脑电图信号。它由原生 C 模块实现(基于 [openyou/emokit-c](https://github.com/openyou/emokit-c.git)),以 128Hz 采样率的速度处理 14 路电极原始的脑电图数据流,并且给终端用户提供了丰富的接口。这里有个例子,欢迎试用一下。 + +```Javascript +const mind = require('wits') +mind.open() +mind.read(console.log) +``` + +* [**dashersw/wits**:wits — 一个使用 Emotiv EPOC 脑电头盔来读心的 Node.js 库](https://github.com/dashersw/wits) + +### 3. Brain-monitor + +![](https://cdn-images-1.medium.com/max/1600/1*hDVSjp4vSjrmqt0wwvKU1Q.gif) + +Brain-monitor 实际上是一个用 Javascript 编写的可以实时显示脑电图信号的终端应用。它配合 Emotiv EPOC 脑电头盔一起工作,以 128Hz 的采样频率对 14 个电极的原生脑电信号进行分析,并能处理一些额外的信息,比如头的方向,甚至是头盔的电量。对于喜欢使用命令行的开发者,这是个不错的选择。 + +* [**dashersw/brain-monitor**: _brain-monitor — 一个用 Node.js 编写的实时显示脑电信号的终端应用](https://github.com/dashersw/brain-monitor) + +### 4. Brain-bits + +![](https://cdn-images-1.medium.com/max/1600/1*6pYMJ2_4fV8iMP2_sPwTAg.gif) + +由 wits 和 brain-monitor 的开发者创建,Brain-bits 是为 Emotiv 脑电头盔所做的一套 P300 在线拼写系统。这个项目基于 [Electron](https://electronjs.org) 应用,后端运行 Node,而前端使用 Vue.js,利用 Node.js 的原生模块以及 [brain.js](https://github.com/BrainJS/brain.js) 来处理神经网络,并使用 [d3](https://d3js.org) 来绘制脑电图。你可以在开发者在 2018 Amsterdam JS 论坛上的 [这次演讲](https://www.youtube.com/watch?v=_4nrh6mTt4E) 里面看到一个现场演示,并能了解更多内容。 + +* [**dashersw/brain-bits**: _brain-bits — 一套为 Emotiv 脑电头盔使用的 P300 在线拼写系统。使用 Node.js 编写,GUI 是……](https://github.com/dashersw/brain-bits) + +### 5. EEG-101 + +![](https://cdn-images-1.medium.com/max/1600/1*iPMqXQS3FK1lOa3sD6oolw.png) + +EEG-101 是一个使用 Muse 和 React Native 来教授脑电图和 BCI 基础知识的交互式神经学的 [教程应用](https://play.google.com/store/apps/details?id=com.eeg_project&hl=en)。内容包括信号从哪里来,设备如何工作以及如何处理数据。使用 React Native 开发了 Android 应用,项目包含了一个用于脑电图数据的通用二进制分类器,它使用 LibMuse Java API 获取来自 Muse 头盔的数据流。这是一种很好的采集和播放脑电信号的方式。 + +* [**NeuroTechX/eeg-101**: _eeg-101 — 使用 Muse 和 Reac Native 来教授脑电图和 BCI 基础知识的交互式神经学教程应用。](https://github.com/NeuroTechX/eeg-101) + +### 6. EEG pipes + +![](https://cdn-images-1.medium.com/max/1600/1*1SPDOMNKy-3ntUgiDnpeDA.png) + +这个项目提供在 Node 和浏览器环境中处理脑电图数据的可管道化的 RxJS 操作符,包括的功能比如 FFT、功率谱密度(PSD)和功率带宽、缓冲和 Epoching、IIR 滤波器等。注意需要一个关于脑电图的 Observable,可以使用 RxJS 的 `fromEvent` 将回调事件压入 Observable 流中。试用一下。 + +* [**neurosity/eeg-pipes**: _eeg-pipes — 在 Node 和浏览器中处理脑电图数据的可管道化 RxJS 操作符](https://github.com/neurosity/eeg-pipes) + +### 7. Open BCI & JS + +Open BCI 是一个提供脑机接口和低成本硬件的开源项目。由工程师、研究人员和制造商组成的开发小组创建,他们希望“分享对利用脑电信号来更深入地理解并扩展我们是谁的坚定热情”。 + +基于此,它为各种各样脑电相关软硬件实现构筑了一个基础。其中有一些非常棒的 Javascript 实现,使用从 Node.js 到 Angular 进行脑电图处理、可视化和一系列工作。这是一些例子。 + +* [**pwstegman/WebBCI**: _WebBCI — :bar_chart: 基于 JavaScript 的脑电信号处理]((https://github.com/pwstegman/WebBCI) + +* [**NeuroJS/openbci-dashboard**: _openbci-dashboard — 一个获取并可视化 OpenBCI 脑电数据的全栈 Javascript 应用](https://github.com/NeuroJS/openbci-dashboard) + +* [**neurosity/openbci-observable**: _openbci-observable — Making OpenBCI for Node Reactive_github.com](https://github.com/neurosity/openbci-observable) + +* [**alexcastillo/angular-openbci-rx**: _angular-openbci-rx — 使用 Angular 4 实现脑电时序数据可视化](https://github.com/alexcastillo/angular-openbci-rx) + +* * * + +### 还可以看看: + +* [**karan/brain2music**: _brain2music — :音符: 脑电波数据实时音乐转换(更像是噪音)](https://github.com/karan/brain2music) + +* [**NeuroJS/topogrid**: _topogrid — javascript library for interpolation of topographic EEG plots](https://github.com/NeuroJS/topogrid) + +* * * + +### 遇见 Bit + +[**Bit**](https://bitsrc.io) 可以帮助你的团队通过导入组件和模块到编译模块中来快速搭建应用,这些非常容易分享、开发并在任意地方去构建新的工程项目。用 Javascript、React 或者其他方式试用下 Bit。 + +* [**Bit — 共享和创建代价组件**: Bit 可以帮助你在项目和应用之间共享、发现并使用代码组件来创建新功能特性和其他……](https://bitsrc.io/) + +* * * + +### 更多了解 + +* [**Monorepos Made Easier with Bit and NPM**:如何利用 Bit 和 NPM 更简单地创建 Monorepos。](https://blog.bitsrc.io/monorepo-architecture-simplified-with-bit-and-npm-b1354be62870) + +* [**Write GraphQL APIs on Node with MongoDB**:如何使用 Node.js 和 MongoDB 来编写 GraphQL APIs。](https://blog.bitsrc.io/write-graphql-apis-on-node-with-mongodb-f3d0084cbbb8) + +* [**11 Javascript Utility Libraries You Should Know In 2018**:能够加快开发的 11 个有用的 Javascript 工具包。](https://blog.bitsrc.io/11-javascript-utility-libraries-you-should-know-in-2018-3646fb31ade) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md b/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md index 2df4a5ea68f..68a7facc547 100644 --- a/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md +++ b/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md @@ -2,176 +2,177 @@ > * 原文作者:[Erik D. Kennedy](https://medium.com/@erikdkennedy?source=post_header_lockup) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md](https://github.com/xitu/gold-miner/blob/master/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md) -> * 译者: -> * 校对者: +> * 译者:[wzasd](https://github.com/wzasd) +> * 校对者:[xujunjiejack](https://github.com/xujunjiejack)、[lihanxiang](https://github.com/lihanxiang) -# 7 Rules for Creating Gorgeous UI (Part 1) +# 创造华丽 UI 的 7 个规则(Part 1) -## A non-artsy primer in digital aesthetics +## 数学美学的非艺术性入门指南 -### Introduction +### 介绍 -OK, first things first. This guide is not for everyone. Who is this guide for? +好的,让我们先说重要的事。这个指南并不适用于所有人。那这本指南的目标用户是谁呢? -* **Developers** who want to be able to design their own good-looking UI in a pinch. -* **UX designers** who want their portfolio to look better than a Pentagon PowerPoint. Or UX designers who know they can sell an awesome UX better in a pretty UI package. +* **开发人员** 希望能够在一个开发中设计一个属于他们的看起来很不错的用户界面。 +* **UX 设计师** 希望他们的产品组合看起来比五角大楼幻灯片更好看。或者那些知道他们可以在一个漂亮的 UI 中更好的实现他们出色的用户体验的设计师。 -If you went to art school or consider yourself a UI designer already, you will likely find this guide some combination of a.) boring, b.) wrong, and c.) irritating. That’s fine. All your criticisms are right. Close the tab, move along. +如果你去过艺术学校或者自认为已经是一名 UI 设计师,那么你可能发现这个指南结合了 a.) 无聊、b.) 错误和 c.) 令人生气。没关系,您的所有批评一定都是对的。让我们一起关闭标签。 -Let me tell you what you’ll find in this guide. -First, I was a UX designer with no UI skills. I _love_ designing UX, but I wasn’t doing it for long before I realized there were a bunch of good reasons to learn how to make an interface look nice: +让我告诉你会在本指南中找到什么。 -* My portfolio looked like crap, reflecting poorly on my work and thought process -* My UX consulting clients would rather buy someone’s skills if their expertise extended to more than just sketching boxes and arrows -* Did I want to work for an early-stage startup at some point? Best to be a sweeper +首先,我是一名没有任何 UI 技能的 UX 设计师。我**热爱** UX 设计,很久以前,我并不关心用户界面,在我意识到有很多好的理由来学习如何使界面看起来不错后,才下定决心学习。 -I had my excuses. _I don’t know crap about aesthetics. I majored in engineering– it’s almost a badge of pride to build something that looks awful._ +* 我的作品集看起来很糟糕,没办法充分反应我的思考过程和我的工作。 +* 咨询我 UX 的客户宁愿购买其他人的技能,因为他们的专长不仅仅是绘制箱子和箭头。 +* 我是否像在某个时期进行创业工作?最好做一个清扫工。 -> _“I majored in engineering — it’s almost a badge of -> pride to build something that looks awful.”_ +我有我自己的借口。**我并不理解美学,因为我认为他们都是在说废话,我主修工程学 — 我都快以创造一些难看的东西为荣了。** -In the end, I learned the aesthetics of apps the same way I’ve learned any creative endeavor: _cold, hard analysis_. And shameless copying of what works. I’ve worked 10 hours on a UI project and billed for 1. The other 9 were the wild flailing of learning. Desperately searching Google and Pinterest and Dribbble for something to copy from. +> **“我并不理解美学,因为我认为他们都是在说废话,我主修工程学 — 这简直就是一种标签,我都快以创造一些难看的东西为荣了。”** -These “rules” are the lessons from those hours. +最后,我终于学会了到底什么是应用程序的美学,就像是我努力学习其他创作的事物一样:**冷静,理性分析**。厚颜无耻的复制了有用的东西。我已经在一个 UI 项目上工作了 10 个小时,然而实际只为了项目付出了 1 个小时。其他 9 个小时拼命搜索谷歌、Pinterest 和 Dribbble 去里面复制有用的东西! -**So word to the nerds: if I’m any good at designing UI now, it’s because I’ve _analyzed_ stuff — not because I came out the chute with an intuitive understanding of beauty and balance.** +我在这些时间里学到的教训。 -This article is not theory. This article is pure application. You won’t see anything about golden ratios. I don’t even mention color theory. Only what I’ve learned from being bad and then [deliberately practicing](http://calnewport.com/blog/2010/01/06/the-grandmaster-in-the-corner-office-what-the-study-of-chess-experts-teaches-us-about-building-a-remarkable-life/). +**对于书呆子而言:如果我现在擅长设计用户界面,那是因为我已经分析很多东西 — 而不是因为我通过直观的对美以及平衡的理解才走出误区。** -Think of it this way: Judo was developed based on centuries of Japanese martial and philosophical traditions. You take judo lessons, and in addition to fighting, you’ll hear a lot about energy and flow and harmony. That sort of stuff. +这篇文章并不是理论的阐述。只是很纯粹的应用文章。你不会看到关于黄金分割线的任何信息。我甚至不会提色彩理论。只有我从错误学到的东西,并不断将进行[刻意练习](http://calnewport.com/blog/2010/01/06/the-grandmaster-in-the-corner-office-what-the-study-of-chess-experts-teaches-us-about-building-a-remarkable-life/)。 -Krav Maga, on the other hand, was invented by some tough Jews who were fighting Nazis in the streets of 1930s Czechoslovakia. There is no _art_ to it. In Krav Maga lessons, you learn how to stab someone’s eye with a pen and book it. +用这种方式思考:柔道是根据几个世纪的日本武术和哲学传统演化而来的。你参加了柔道课程,除此之外,你还会听到关于能量、流动以及和谐等知识。 -This is the Krav Maga of screens. +另一方面,Krav Maga 则是由一些在 30 年代捷克斯洛伐克犹太人在街头与纳粹抗争时发明的。那并没有**艺术**在中间。在 Krav Maga 课程中,你将会学到如何用笔刺入某人的眼睛。 -#### The Rules +这是屏幕中的 Krav Maga。 -Here they are: +#### 规则 -1. **Light comes from the sky** -2. **Black and white first** -3. **Double your whitespace** -4. **Learn the methods of overlaying text on images** (see [Part](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96) 2) -5. **Make text pop— and un-pop** (see [Part](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96) 2) -6. **Only use good fonts** (see [Part](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96) 2) -7. **Steal like an artist** (see [Part](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96) 2) +规则在这里: -Let’s get to it. +1. **光线来自天空。** +2. **首选白色和黑色。** +3. **添加更多的空白。** +4. **学习如何在图片上添加文字。**(查看[部分](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96) 2) +5. **使文本弹出和取消弹出。**(查看[部分](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96) 2) +6. **只使用好看的文体。**(查看[部分](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96) 2) +7. **像艺术家一样借鉴。**(查看[部分](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96) 2) -### Rule 1: Light Comes From the Sky +让我们开始吧。 -_Shadows are invaluable cues in telling the human brain what user interface elements we’re looking at._ +### 规则一:光线来自天空 -This is perhaps the **most important non-obvious thing** to learn about UI design: _light comes from the sky._ Light comes from the sky so frequently and consistently that for it to come from below actually looks _freaky._ +**阴影是最有效的提示,用来告诉人类的大脑哪些是他们正在查看的用户界面的元素。** + +这也许是**容易被忽视却很重要**去学习 UI 设计的一个内容:**光线来自天空。**光不断的从天空而来,因此如果光从下而上则确实看起来非常怪异。 ![](https://cdn-images-1.medium.com/max/400/1*eFJGYuA67SIzu9pB1MZFKQ.jpeg) -WoooOOOooo. +妈呀~ -When light comes from the sky, it illuminates the tops of things and casts shadows below them. The tops of stuff are lighter, the bottoms are darker. +当光线来自于天空的时候,它照亮了物体的顶部并在其下面投下阴影。物体的顶部较为明亮,底部较暗。 -You wouldn’t _think_ of people’s lower eyelids as being particularly shaded, but shine some light on those suckers and all of a sudden it’s demon girl at your front door. +你绝不会**认为**人们的下眼皮是需要画出来眼影的,但是当一个化了下眼影的女孩突然出现在他们门前的时候确实会亮瞎那些呆子的眼睛。 -Well, the same is true for UI. Just as we have little shadows on all the undersides of all our facial features, there are shadows on the undersides of just about every UI element you can find. **Our screens are flat, but we’ve invested a great amount of art into making just about everything on them appear be 3-D**. +那么,用户界面也正是如此。正如我们在所有的面部特征的下侧都有少量的阴影,几乎每个 UI 元素的底部都有可以被发现的阴影。**我们的屏幕是平的,但是我们投入了大量的艺术创作来制作出 3D 的效果。** ![](https://cdn-images-1.medium.com/max/800/1*DTB4xeMLpg0DW6NLOYBehw.png) -My favorite part of this image is the poker finger in the lower-right. +这张图片中我最喜欢的就是右下角的手指。 -Take buttons. Even with this relatively “flat” button, there are still a handful of light-related details: +拿按钮举例。即使有了这个相对“平面”的按钮,仍然有一些与光线相关的细节: -1. The unpushed button (top) has a **dark bottom edge**. Sun don’t shine there, son. -2. The unpushed button is **slightly brighter at the top** than at the bottom. This is because it imitates a slightly curved surface. Just as how you’d need to tilt a mirror held in front of you up to see the sun in it, surfaces that are tilted up reflect a _biiiiit_ more of the sun’s light towards you. -3. The unpushed button casts a **subtle shadow**– perhaps easier to see in the magnified section. -4. The pushed button, while still darker at the bottom than at the top, is **overall darker**– this is because it’s at the plane of the screen and the sun can’t hit it as easily. One could argue that all the pushed buttons we see in real life are darker too, because our hands are blocking the light. +1. 按钮没有按下的时候具有**黑色的底部边缘**,太阳并没有照耀到的位置。 +2. 按钮没有按下的时候**顶部会亮一些**对比底部。这是因为它模仿了一个稍微弯曲的表面。就像当你需要倾斜一面在你面前的镜子来观察太阳一样,在上面的镜面会向你的身上反射多一丁丁丁丁点的阳光。 +3. 按钮没有按下的时候投射了**微妙的阴影** — 如果放大可能看的更清楚一些。 +4. 按下的按钮虽然底部比顶部暗一些,但是**整体颜色更深**,因为他们虽然位于屏幕的平面上,太阳并不容易照射到它。有人可能会说我们在现实生活中看到的所有按钮都会变暗,因为我们手挡住了光线。 -That was just a button, and yet there are these 4 little light effects present. That’s the lesson here. Now we just apply it to _everything._ +这只是一个按钮,然而这里有四个小小的灯光效果。这些灯光效果就是我们的经验。现在我们应该将它用于**所有的东西**。 ![](https://cdn-images-1.medium.com/max/800/1*4FCAIgmJa8BuildjlnsDeA.png) -iOS 6 is a little outdated, but it makes a good case study in light behavior. +虽然 iOS 6 有点过时了,但是它在光照行为方面确实是很好的研究案例。 -Here is a pair of iOS 6 settings— “Do Not Disturb” and “Notifications”. NBD, right? But look how many light effects are going on with them. +这里有一对 iOS 6 设置 — “请勿打扰”和“通知”。很简单,对吧?但是看看他们有多少灯光效果。 -* The top lip of the inset control panel casts a small shadow -* The “ON” slider track is also immediately set in a bit -* The “ON” slider track is concave and the bottom reflects more light -* The icons are set _out_ a bit. See the bright border around the top of them? This represents a surface perpendicular to the light source, hence receiving a lot of light, hence bouncing a lot of light into your eyes. -* The divider notch is shadowed where angled away from the sun and vice versa +* 插图的控制面板边缘投下了一个小阴影。 +* “ON” 滑块轨道也跟着设置了一点。 +* “ON” 滑块轨道为凹型,底部反射了更多的光线。 +* 图标**边缘**被设置了一点点。看到他们顶部的明亮边框了吗?这代表一个垂直于光源的表面。因为垂直,所以这个表面接受了大量的光线,将大量的光线反射到眼睛中。 +* 分隔的凹口在远离太阳的部分被遮盖,反之亦然。 ![](https://cdn-images-1.medium.com/max/800/1*gWuSN3QN9dSeVwSP2LZVow.png) -_A close-up of a divider notch. From an old_ [_Hubster_](http://hubster.tv/) _concept of mine._ +**分割线的凹槽的特写镜头。来自我的一个旧 [Hubster](http://hubster.tv/) 概念。** -Elements that are generally **inset**: +通常在**嵌入**的界面元素: -* Text input fields -* Pressed buttons -* Slider tracks -* Radio button (unselected) -* Checkboxes +* 编辑栏 +* 按下的按钮 +* 滑块 +* 单选按钮(未选中) +* 复选框 -Elements that are generally **outset**: +通常在**突出**的元素: -* Buttons (unpressed) -* Slider buttons -* Dropdown controls -* Cards -* The _button_ part of a selected radio button -* Popups +* 按钮(未按下) +* 滑块按钮 +* 下拉控件 +* 卡块 +* 所选单选按钮的 **button** 部分 +* 弹出窗口 -Now that you know, you’ll notice it everywhere. You’re welcome, kid. +现在你知道了,你会注意到他们到处都是。不客气,初学者。 -#### Wait, what about flat design, Erik? +#### 等等,扁平化设计怎么样呢,Erik? -iOS7 made a stir in the tech community for its “flat design”. This is to say that it is literally _flat._ There are no simulated protrusions or indentations— just lines and shapes of solid color. +iOS 7 让“半扁平化设计”在科技界引起了轰动。这就是说他的**半扁平化。** 没有模拟凸起或者凹痕 — 只是纯色的线条和形状。 ![](https://cdn-images-1.medium.com/max/800/1*YAB8zDDxCmvegvxCu7d8kw.png) -I love _clean and simple_ as much as the next guy, but I don’t think this is a long-term trend here. The subtle simulation of 3-D in our interfaces seems far too natural to give up entirely. +我虽然和大家一样喜欢**干净和简单**。但是我认为这不是一个长期的趋势。如何将我们的界面用 3D 来在细微处进行模拟的更加自然是不能完全放弃的。 -**More likely, we’ll see semi-flat UI in the near future** (and this is what I recommend you become proficient in designing). I’m going to go ahead and call it “flatty design”. Still clean, still simple, but you’ll have _some_ shadows and cues for what to tap/slide/click. +**更多的可能是,我们将会在不久的将来看到半扁平化的 UI 设计**(而且我建议你精通这种设计)。我们将会继续称之为“扁平化设计”。依旧干净,依旧简单简单,但是会有**一些**阴影和点击/滑动的提示。 ![](https://cdn-images-1.medium.com/max/800/1*gWvCSNxqNjyYaq4IF31ZhQ.png) -OS X Yosemite— flatty, not flat. +OS X Yosemite — 扁平化而不平面化。 -As I’m writing this, Google is rolling out their “Material Design” language across their products. It’s a unified visual language that– at its core– seeks to imitate the physical world. +在写这篇文章时,Google 正在他们的产品中推出他们“Material 设计”语言。这是一种统一的视觉语言,它的核心理念就是模仿现实的世界。 -An illustration from the Material Design guide shows how to convey different depths using different shadows. +Material 设计指南中的例证展示了如何使用不同的阴影来表达不同的深度。 ![](https://cdn-images-1.medium.com/max/800/1*TtuBo6cCUTyP8XIYGSrIyg.png) ![](https://cdn-images-1.medium.com/max/400/1*sHg3HCEciqqAk1xE8qMrdg.png) -This is the sort of thing I see sticking around. +我感觉这种东西是一种长期的趋势。 -It uses subtle real-world cues to convey information. **Key word, _subtle_**_._ +它使用了现实世界的微妙的线索来传达信息。**关键词,微妙。** -You can’t say it doesn’t imitate the real-world, but it’s also not the web of 2006. There are no textures, no gradients, no sheens. +我们并不能说它没有模拟现实世界,但是它又不像是 2006 年的网络。没有纹理,没有渐变,没有发光。 -Flatty is the way of the future, I think. Flat? Psh, just a thing of the past. +我认为扁平化是未来的一种方式,平面化?切,只是过去而已。 ![](https://cdn-images-1.medium.com/max/800/1*Zqcjyz-oIqZZojyYyWVl2Q.png) -That flat design looks so hot right now! +这样的平面化设计现在看起来很火! + +### 规则 2:黑色和白色优先 -### Rule 2: Black and White First +**在添加颜色之前进行灰度设计可以简化视觉设计中最复杂部分 — 并且可以使强迫使您专注于间距和布局元素。** -_Designing in grayscale before adding color simplifies the most complex element of visual design– and forces you to focus on spacing and laying out elements._ +UX 设计师现在真的是“移动优先”来进行设计。这意味着您在想象无法想象的像素的 Retina 显示器前**优先**考虑手机上的页面和互动是如何工作的。 -UX designers are really into designing “mobile-first” these days. That means you think about how pages and interactions work on a phone _before_ imagining them on your zillion-pixel Retina monitor. +**其实这种约束很好。它可以简化思想。**您从较难的问题开始(在小屏幕上可用的应用程序),然后通过同样的解决方案去解决简单的问题(大屏幕上可以使用的应用程序)。 -**That sort of constraint is great. It clarifies thinking**. You start with the harder problem (usable app on a teeny-weeny screen), then adopt the solution to the easier problem (usable app on a large screen). +那么这里就是一种类似的约束:**优先设计黑色和白色**。首先是在没有色彩的帮助下让应用变得美观并且可用。**最后添加色彩,仅此而已。** -Well here’s another similar constraint: design _black and white first_. Start with the harder problem of making the app beautiful and usable in every way, but without the aid of color. **Add color last, and even then, only with purpose**. ![](https://cdn-images-1.medium.com/max/800/1*qheNNhQhjjwxMeJ5XGocsA.png) -[Haraldur Thorleifsson](http://ueno.co/)’s grayscale wireframes look as good as lesser designer’s finished sites. +[Haraldur Thorleifsson](http://ueno.co/) 的灰度线框看起来就如同极少的设计师完成的网站设计一样好。 -This is a reliable and easy way to keep apps looking “clean” and “simple”. **Having too many colors in too many places is a really easy way to screw up clean/simple**. B&WF forces you to focus on things like spacing, sizes, and layout first. And those are the primary concerns of a clean and simple design. +这是保持应用程序“干净”和“简单”最可靠也是最简单的方法。**在过多的地方使用过多的颜色很容易搞砸设计的简单和干净**。黑和白优先这个原则强迫你首先关注诸如间距,尺寸和布局等事情。这些都是干净简单设计的首要关注点。 ![](https://cdn-images-1.medium.com/max/600/1*YxV7C-nHHir-PSbJ4-jqhQ.png) @@ -179,155 +180,156 @@ This is a reliable and easy way to keep apps looking “clean” and “simple ![](https://cdn-images-1.medium.com/max/400/1*EnbssykGOuXeXMV3AQFyjw.png) -Classy grayscale. +优雅的灰度 -There are some cases where B&WF isn’t as useful. Designs that have a strong specific attitude— “sporty”, “flashy”, “cartoony”, etc. — need a designer who can use color extremely well. But **most apps don’t have a specific attitude except _clean and simple_**. Those that do are admittedly much harder to design. +有些情况下黑白优先的原则并不是那么有用。那些需要特殊感觉的设计 — “动感”、“华丽”或“卡通”等等。需要一个能够非常好使用多种颜色搭配的设计师。但是**大多数的应用程序没有一个特别强烈的需求属性,除了“干净”和“简洁”**。那些需要特殊设计的很难设计。 ![](https://cdn-images-1.medium.com/max/600/1*OraO1vxtkxYteZyE4CXrOQ.png) ![](https://cdn-images-1.medium.com/max/600/1*JsbQFaIY6g697PMeEuMwvA.png) -Flashy and vibrant designs by [Julien Renvoye](http://www.julienrenvoye.fr/) (left) and [Cosmin Capitanu](http://radium.ro/) (right). Harder than it looks. +[Julien Renvoye](http://www.julienrenvoye.fr/) (左)和 [Cosmin Capitanu](http://radium.ro/) (右)的华丽和充满活力的设计。比看起来更难。 -For all the rest, there’s B&WF. +对于其他的设计来讲,都是黑和白优先原则。 -#### Step 2: How to add color +#### 步骤 2:怎么去添加颜色 -The simplest color to add is one color. +只加一种颜色是能添加的最简单的颜色。 ![](https://cdn-images-1.medium.com/max/800/1*YxV7C-nHHir-PSbJ4-jqhQ.png) -Adding one color to a grayscale site draws the eye simply and effectively. +添加一种颜色在灰度设计的网站可以很简单而又有效的吸引眼球。 ![](https://cdn-images-1.medium.com/max/800/1*pds21170RP-6ZIkuSxgI2Q.png) -You can also take it one step up. Grayscale + _two_ colors, or grayscale + multiple colors of a single hue. +您同样可以采取更深的一步。灰度 + **两种**颜色,或者灰度 + 单一色调的多种颜色。 -> **Color codes in practice — i.e. wait, what’s a hue?** +> **让我们用下颜色代码 — 等等,什么是色调?** -> The web by and large talks about color as RGB hex codes. It’s most useful to ignore those. RGB is not a good framework for coloring designs. Much more useful is [HSB](https://learnui.design/blog/the-hsb-color-system-practicioners-primer.html) (which is synonymous with HSV, and similar to HSL). +> 网页上大体将颜色作为 RGB 十六进制代码进行讨论。其实忽略他们才是最有用的。RGB 并不是适合着色设计的一个有用的框架。[HSB](https://learnui.design/blog/the-hsb-color-system-practicioners-primer.html)(与HSV同义,与HSL类似)更有用。 -> HSB is better than RGB because it fits with the way we think about color naturally, and you can predict how changes to the HSB values will affect the color you’re looking at. +> HSB 比 RGB 更好,因为它符合我们对颜色自然的看法,并且您可以预测 HSB 值的变化所给您看到颜色来带的影响。 -> If this is news to you, here’s [a good primer on HSB colors](https://learnui.design/blog/the-hsb-color-system-practicioners-primer.html). +> 如果这对你来说是个新的东西,这里 [HSB 颜色的优质入门文章](https://learnui.design/blog/the-hsb-color-system-practicioners-primer.html)。 ![](https://cdn-images-1.medium.com/max/800/1*tZRxO2DReDduBqOwgqd_yw.jpeg) -Single-hue gold theme from [Smashing Magazine](http://www.smashingmagazine.com/2010/02/08/color-theory-for-designer-part-3-creating-your-own-color-palettes/). +单色调金色主题来自 [Smashing Magazine](http://www.smashingmagazine.com/2010/02/08/color-theory-for-designer-part-3-creating-your-own-color-palettes/)。 ![](https://cdn-images-1.medium.com/max/800/1*-rbrbh20EHL_Ue_IDxl_0A.jpeg) -Single-hue blue theme from [Smashing Magazine](http://www.smashingmagazine.com/2010/02/08/color-theory-for-designer-part-3-creating-your-own-color-palettes/). +单色调蓝色主题来自 [Smashing Magazine](http://www.smashingmagazine.com/2010/02/08/color-theory-for-designer-part-3-creating-your-own-color-palettes/)。 -By modifying the **saturation** and **brightness** of a single hue, you can generate multiple colors— darks, lights, backgrounds, accents, eye-catchers— but it’s not overwhelming on the eye. +通过修改单色调的**饱和度**以及**亮度**,您可以生成多种颜色 — 深色、亮色、背景、重点以及各种吸引注意的效果 — 而且不会让人眼花缭乱。 -Using multiple colors from one or two base hues is the **most reliable way to accentuate and neutralize elements without making the design messy**. +使用来自一种或者两种基本色调的多种颜色是为了**在保持设计不凌乱的同时又可以强调和中和元素**的最可靠的方法。 ![](https://cdn-images-1.medium.com/max/800/1*_fM8VVYx7hMgdJ_Wy24AXg.png) -Countdown timer by [Kerem Suer](http://kerem.co/). +倒数计时器来自 [Kerem Suer](http://kerem.co/)。 -#### A few other notes on color +#### 关于颜色的其他一些说明 -Color is the most complicated area of visual design. And while a lot of stuff on color is obtuse and not practical for finishing the design in front of you, I’ve seen some really good stuff out there. +色彩是视觉设计中最复杂的领域。虽然很多关于色彩的东西在你完成设计时并不是很实用,但是我却看到了一些非常有用的东西。 -A small toolbox: +一个小工具箱: -* [**Learn UI Design**](http://learnui.design/?utm_source=medium&utm_medium=content&utm_campaign=7-rules-part-1). Shameless plug: this is a course I’ve created, and it contains 3 _hours_ of video about designing with color (and 13+ hours on other topics in UI design). Check it out at [_learnui.design_](http://learnui.design/?utm_source=medium&utm_medium=content&utm_campaign=7-rules-part-1). -* [**Color in UI Design: A (Practical) Framework**](https://medium.com/@erikdkennedy/color-in-ui-design-a-practical-framework-e18cacd97f9e). If you liked this section, but want to hear more about _color_ (as opposed to just black and white), this is your article. And guess who wrote it! -* [**Never Use Black**](http://ianstormtaylor.com/design-tip-never-use-black/) (Ian Storm Taylor). Talks about how totally flat grays almost never appear in the real-world, and how saturating your shades of gray– especially your darker shades– adds a visual richness to your designs. Plus, saturated grays more closely mimic the real-world, which is its own virtue. -* [**Adobe Color CC**](https://color.adobe.com). An awesome tool for finding, modifying, and creating color schemes. -* [**Dribbble search-by-color**](https://dribbble.com/colors/BADA55)**.** Another awesome way to find what works with a particular color. Talk about practical. If you already have one color decided, come look at what the world’s best designers are doing/matching with that color. +* [**学习 UI 设计**](http://learnui.design/?utm_source=medium&utm_medium=content&utm_campaign=7-rules-part-1)。无耻的推广:这是我创建的一门课程,它包含3个小时的关于颜色设计的视频(以及在 UI 设计中的其他主题总共13个多小时)。请看 [**learnui.design**](http://learnui.design/?utm_source=medium&utm_medium=content&utm_campaign=7-rules-part-1)。 +* [**设计色彩学:(实用)框架**](https://medium.com/@erikdkennedy/color-in-ui-design-a-practical-framework-e18cacd97f9e)。如果你喜欢这个部分,但是希望听到更多的**颜色**(而不仅仅是黑色和白色),这是属于你的文章。猜猜是谁写的! +* [**永远不要用黑色**](http://ianstormtaylor.com/design-tip-never-use-black/) (Ian Storm Taylor)。这篇文章谈论了完全平面化的灰色几乎从来没有出现在现实世界中。同时它也提到了如何饱和灰色阴影 — 尤其是深色阴影 — 为设计增添了视觉丰富性。另外,饱和的灰色其实更贴近现实世界,这是它最美的地方。 +* [**Adobe Color CC**](https://color.adobe.com)。一个很棒的工具,用于查找、修改和创建配色方案。 +* [**Dribbble 通过颜色进行搜索**](https://dribbble.com/colors/BADA55)**。** 另一种很棒的方式来查找特定颜色的作品。如果您已经确定了一种颜色,那就看看世界上最好的设计师是怎么与这种颜色搭配。 -### Rule 3: Double your whitespace +### 规则 3:多用空白 -_To make UI that looks designed, add a lot of breathing room._ +**为了让 UI 看起来设计感十足,要添加更多的呼吸空间。** -In Rule 2, I said that B&WF forces designers to think about _spacing and layout_ before considering color, and how that’s a good thing. Well, it’s time we talk about spacing and layout. +在规则 2中,我说使用白或者黑原则迫使设计者在考虑颜色之前考虑**间距**和**布局**,为什么这是件好事,那么,现在就是讨论如何进行间距和布局的构造。 -If you’ve coded HTML from scratch, you’re probably familiar with the way HTML is, by default, laid out on the page. +如果您从头开始编写 HTML 代码,你可能很熟悉HTML在页面上默认的布局方式。 ![](https://cdn-images-1.medium.com/max/800/1*fS6ixQIk88MJlEmph7PeJA.png) -Basically, everything is smashed towards the top of the screen. The fonts are small; there’s absolutely no space between lines. There’s a _biiit_ of space between paragraphs, but it isn’t much. The paragraphs just stretch on to the end of the page, whether that’s 100 px or 10,000 px. +基本上,所有的东西都拥挤在屏幕的顶部。字体很小,线条之间是绝对没有空间的。段落之间确实有一**丢丢**空白,少得可怜。段落只是延伸到页面的末尾,无论是 100 px 还是 10000 px。 -Aesthetically speaking, that’s _awful_. **If you want to make UI that looks _designed_, you need to add in a lot of breathing room**. +从审美的角度上来讲,这太**糟糕**了。**如果你想让你的 UI 看起来很有设计感,您需要在这之间添加呼吸的空间。** -Sometimes a ridiculous amount. +有时候就是一个荒谬的数值。 -> **Whitespace, HTML, and CSS** +> **HTML 和 CSS 的留白** -> If you, like me, are used to formatting with CSS, where the **default is no whitespace**, it’s time to untrain yourself of those bad habits. Start thinking of whitespace as the default— everything starts as whitespace, until you take it away by adding a page element. +> 如果你像我一样经常使用 CSS 进行格式设置,那么**默认情况下不会有留白的**,现在是时候解决这些不良的习惯了。开始考虑将空格作为默认空间 — 所有的内容都是以空格开始,直到您通过添加页面元素将其删除。 -> Sound zen? I think it’s a big reason people still sketch this stuff. +> 听起来像是禅学?我认为这是人们仍然素描出这些东西的重要原因。 -> **Starting with a blank page means starting with nothing but whitespace**. You think of margins and spacing right from the very beginning. Everything you draw is a conscious whitespace-removing decision. +> **从空白页开始意味着以空白**开头。您从一开始就会想到利润率和间距。您绘制的所有内容都是有意识的去删除空白。 -> **Starting with a bunch of unstyled HTML means starting with content**. Spacing is the afterthought. It has to be explicitly stated. +> **从一堆无格式的 HTML 开始,意味着就是以内容**开头,间距则是后来才考虑的事情。这必须明确说明。 -Here’s an illustrative music player concept by [Piotr Kwiatkowski](http://www.piotrkwiatkowski.co.uk/). +以下是 [Piotr Kwiatkowski](http://www.piotrkwiatkowski.co.uk/) 的音乐播放器概念图。 ![](https://cdn-images-1.medium.com/max/1000/1*qFwXZ_05pRv2OtiaJHIp6Q.jpeg) -Pay particular attention to the menu on the left. +要特别注意左侧的菜单。 ![](https://cdn-images-1.medium.com/max/400/1*jSC64LYfVYlMHaI_B7xfKQ.png) -Left menu. +左侧菜单 + +菜单项之间的垂直空间完全是文本本身高度的**两倍**。您注意到这是 12px 的字体,并且在上面和下面填充同样多的间距。 -The vertical space between the menu items is fully _twice_the height of the text itself. You’re looking at 12px font with just as much padding above and below it. +或者看看标题列表。**“PLAYLISTS” 和它自己的下划线之间有 15px 的间距。这比字体本身的**[高度](http://en.wikipedia.org/wiki/Cap_height)还要高。更别提每个列表之间间隔了25个像素了。 -Or take a look at the list titles. **There’s a 15px space between the word “PLAYLISTS” and its own underline. That’s more than the** [**cap height**](http://en.wikipedia.org/wiki/Cap_height) **of the font itself!** And that’s to say nothing of the 25 pixels between the lists. * * * ![](https://cdn-images-1.medium.com/max/400/1*43qoikq5esyOer2PpETX_Q.png) -More space in the top nav bar. The text “Search all music” is 20% of the height of the bar. The icons are similarly proportioned. +顶部导航栏中有更多的空间。文字 “Search all music” 是导航栏高度的 20%。图标也是相应的比例。 -The left sidebar shows generous spacing in between lines of text, and more. +左侧边栏显示了充裕的文本行间的间距,等等。 -Piotr was conscientious about putting in extra space here, and it paid off. While this is just a concept he put together for the fun of it (as far as I know), as far as aesthetics go, it’s beautiful enough to compete with the best music UIs out there. +Piotr 认真考虑在这里增加更多的空白,并且效果很好。尽管这只是他为了更有乐趣(据我所知),就美学而言,它非常漂亮,足以与最好的音乐用户界面进行竞争。 * * * -Good, generous whitespace can make some of the messiest interfaces look inviting and simple— like forums. +适当的留白可以使一些复杂的界面看起来很简单 — 就像是论坛。 ![](https://cdn-images-1.medium.com/max/800/1*g6m0YZVyMEVMuLXzO512gg.png) -Forum design concept by [Matt Sisto](http://sis.to/). +论坛的设计来自于 [Matt Sisto](http://sis.to/)。 -Or Wikipedia. +或者维基百科。 ![](https://cdn-images-1.medium.com/max/800/1*SVtl39B-dSsHo3HFI0h4FA.png) -Wikipedia design concept by [Aurélien Salomon](https://www.behance.net/aureliensalomon). +维基百科设计理念来自 [Aurélien Salomon](https://www.behance.net/aureliensalomon)。 -You can find plenty of argument that, say, the Wikipedia redesign leaves out key functionality to using the site. But you can’t say it’s not a good way to learn! +你可以找到更多的样例,比如说,维基百科的重新设计舍弃了一些关键的网站的功能。但是你不得不说这是一个很好的学习方式! -Put space between your lines. +在你的线条之间预留空间。 -Put space between your elements. +在你的元素之间预留空间。 -Put space between your groups of elements. +在你的元素组之间预留空白。 -**Analyze what works**. +**分析可行性**。 * * * -_OK, that wraps up Part 1. Thanks for sticking around!_ +**好的,第一部分已经完结,感谢你坚持看完!** -In [Part 2](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96), I’ll be talking about the last 4 rules: +在 [Part 2](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96),我会继续讨论剩下的 4 条规则: -> **4. Learn the methods of overlaying text on images** +> **4. 学习在图片上叠加文字的方法。** -> **5. Make text pop— and un-pop** +> **5. 使文本弹出或者取消弹出。** -> **6. Only use good fonts** +> **6. 只使用优秀字体。** -> **7. Steal like an artist** +> **7. 像艺术家一样复制。** -If you learned something useful, [read Part 2](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96). +如果你学到了有用的东西,[读 Part 2](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96)。 --- diff --git a/TODO1/7-rules-for-creating-gorgeous-ui-part-2.md b/TODO1/7-rules-for-creating-gorgeous-ui-part-2.md new file mode 100644 index 00000000000..74e0b263d9a --- /dev/null +++ b/TODO1/7-rules-for-creating-gorgeous-ui-part-2.md @@ -0,0 +1,410 @@ +> * 原文地址:[7 Rules for Creating Gorgeous UI (Part 2)](https://medium.com/@erikdkennedy/7-rules-for-creating-gorgeous-ui-part-2-430de537ba96) +> * 原文作者:[Erik D. Kennedy](https://medium.com/@erikdkennedy?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/7-rules-for-creating-gorgeous-ui-part-2.md](https://github.com/xitu/gold-miner/blob/master/TODO1/7-rules-for-creating-gorgeous-ui-part-2.md) +> * 译者:[xujunjiejack](https://github.com/xujunjiejack) +> * 校对者:[maoqyhz](https://github.com/maoqyhz) + +# 创造华丽 UI 的 7 个规则(Part 2) + +## 一部出自于技术宅的通往视觉审美的指南 + +这是这个指南的第二部分,在此之前,你需要阅读[第一部分](https://github.com/xitu/gold-miner/blob/master/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md)。 + +我们正在讨论可以让你不需要去上美术学院就可以设计**简洁 UI** 的规则。 + +下面是这些规则: + +1. 光线来自天空。(查看[第 1 部分](https://github.com/xitu/gold-miner/blob/master/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md) +2. 首选白色和黑色。(查看[第 1 部分](https://github.com/xitu/gold-miner/blob/master/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md) +3. 添加更多的空白。(查看[第 1 部分](https://github.com/xitu/gold-miner/blob/master/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md) +4. **学习如何在图片上添加文字**。 +5. **令文本层次分明**。 +6. **仅使用好看的字体**。 +7. **像艺术家一样借鉴**。 + +* * * + +### 规则 4:学习如何在图片上添加文字 + +现在只有几种能够可靠得将文字精美得添加在图片上的办法。以下描述了 5 种常规和 1 种额外的方法。 + +如果你想成为一个很好的 UI 设计师,你需要学习如何用吸引人的办法把文字覆盖在图片上。每个优秀的UI设计师都会在这个方面处理得很好,但平庸的设计师往往处理得很糟糕,甚至不会处理。无论你是哪一种平庸的设计师,在读了这个章节后,你会有巨大的提升。 + +#### 方法 0:直接在图片上添加文字 +我一直在犹豫是否要在这篇文章里包含这个方法,但是**严格来说**,直接在图片上添加文字并且让设计好看是**可行**的。 + +![](https://cdn-images-1.medium.com/max/800/1*cZFET5UcuL6rjVWkwqoK_A.png) + +[Otter Surfboards](http://www.ottersurfboards.co.uk/) 看着像精致的 Instagram 配图,但是就是有点难读图片上面的字。 + +以下是这个方法的缺陷和注意事项: + +1. **图片必须是黑色的**,并且没有很多高对比度。 +2. **字体必须是白的** —— 我敢打赌你找不到一个又简单又干净的反例。**真的**,只有白色这一个。 +3. 你需要**在每种显示尺寸下测试**文本是否清晰。 + +你的设计都符合这些条件了?棒。只要你之后别再改字或着这个图片,你应该就可以用你的设计了。 + +我不认为我在任何一个正规专业的项目里直接把文字覆盖到图片上。这个方法是一个需要很高技巧的方法,就是说这种方法虽然可能可以产生**非常酷炫**的效果,但使用的时候需要小心。 + +![](https://cdn-images-1.medium.com/max/800/1*aGKzy_8di06W8u1kmKcS4Q.png) + +这是 Aquatilis 的网站,非常值得一看。 + +#### 方法 1:文本覆盖整个图片 + +在整个图片上覆盖一个图层可能是最简单的办法了。如果原始的图片不够黑,那你就可以在整个图片上加一个半透光的黑色图层。 + +这是一个非常流行的带有黑色图层的网站主页。 + +![](https://cdn-images-1.medium.com/max/800/1*-9qT-d3DcjmXV4vZkyBE8g.png) + +这个 Upstart 的网站有 35% 透光度的黑色滤镜。 + +如果你打开 Firebug(译者注:Firefox 的 debug 工具),你会发现原图因为亮度和对比度都比较高,所以字体看不清楚。但是当有了一个黑色的滤镜后,这些都不是问题! + +这个方法用在缩略图和小的图片上同样好用。 + +![](https://cdn-images-1.medium.com/max/800/1*xvsvxW00oE9NuhbRUAUK2Q.png) + +[Charity:water](http://www.charitywater.org/) 网站的缩略图。 + +黑色的图层尽管是最简单,并且用处最广泛的,你当然也可以用别的颜色的图层。 + +![](https://cdn-images-1.medium.com/max/800/1*0LUET8aQnpFvVB4yNhQj1Q.png) + +#### 方法 2:把文本放框里 + +这实在是再简单不过了,但同时又很可靠。试试把一个微微透光的黑色长方形框覆盖在一些白色的文字上。如果这个图层足够透光,你依旧可以保证即使文字底下是任何图片,文字依旧清晰可见。 + +![](https://cdn-images-1.medium.com/max/800/1*J_7pHmSn6NvTuC3xFNIlqA.png) + +Modern Honolulu 的 iPhone 设计稿 [Miguel Oliva Márquez](http://miguelolivamarquez.com/)。 + +你也可以往文本框里塞不同的颜色,但是当然要保持谨慎。 + +![](https://cdn-images-1.medium.com/max/800/1*6218qUE-AoaikksiQziL7Q.png) + +现在这些是粉色的例子。作者是 [Mark Conlan](http://markconlan.com/)。 + +#### 方法 3:把图片模糊化 + +这个把底部图片模糊化来让人看得清楚上面的文本是出人意料得好用 + +![](https://cdn-images-1.medium.com/max/800/1*mC1oHWTKlRqOZ-ra3sLh-Q.png) + +Snapguide 里用了大量的背景模糊化。注意看,这些模糊的区域同时也被加深过。 + +iOS 7 的设计真的让背景模糊化变得流行起来,虽然 Windows Vista 也用模糊化达到了非常好的效果。 + +![](https://cdn-images-1.medium.com/max/600/1*FkH2ZkCQ0wmT5FxmaAMzaA.jpeg) + +![](https://cdn-images-1.medium.com/max/600/1*FyZBDM_gMoJ8bCK3zVr_Fg.png) + +你也可以用照片里虚化的背景作为模糊化的区域。但是请注意 —— 这个办法并不好使。如果你的图片做了一点改变,你就得确保这些文字一直都是在模糊化的区域里。 + +![](https://cdn-images-1.medium.com/max/800/1*mZ22i_UB57qdFBZwOqUFFA.png) + +[Teehan + Lax](http://www.teehanlax.com/) + +我的点是,**试着**读清楚下面的小标题。 + +![](https://cdn-images-1.medium.com/max/1000/1*pMpduiy5C4LGu7abzj_a6g.png) + +这[网站](https://www.google.com/wallet/send-money/)到底是怎么被通过的? + +#### 方法 4:底部逐渐变深 + +底部逐渐变深这个方法指的是你把图片里**靠近底部的地方逐渐变黑,然后接着把白字填在上面**。这是个非常巧妙的办法。我在看到 Medium 之前都没想到过。 + +![](https://cdn-images-1.medium.com/max/1000/1*_uPTqFCygpKPJoC5q0y1mg.png) + +对于一个普通人,这些 Medium 上的收集的设计仅仅是图片上覆盖了些白色的文字-但是这种想法我说是很**错误**滴!从中部(0% 透光度的黑色)到底部(20% 透光度)有个小小的渐变。 + +这个渐变很难看出来,但是一定在那,而且绝对提高了字体的辨识度。 + +> 同时你可以注意一下这些 Medium 的收集缩略图用了一点点的文字阴影来更加提高识别度。这些人真的非常棒! + +> 这个技巧的效果是 Medium 即使把任何文字放在任何图上,也可以得到能读的结果。 + +哦,还有一件事 — 为什么图片是往下变深?原因见我的[第一条规则](https://github.com/xitu/gold-miner/blob/master/TODO1/7-rules-for-creating-gorgeous-ui-part-1.md)-灯光一直是从上面照下来的。为了让眼睛看起来更舒服,图片必须要是在底下慢慢变深,就像我们看见的所有东西一样。 + +更高级的做法:如果把模糊化和底部渐变混起来...这就是底部模糊化了! + +![](https://cdn-images-1.medium.com/max/800/1*vjezz0sSxlioqbEHbov_uw.png) + +用在 SnapGuide 上的“底部模糊”。妈妈再也不用担心我在上面使用图层了! + +#### 额外的办法:纱幕化 + +这个 [Elastica blog](http://www.elastica.net/category/blog/) 是怎么可以在任何的照片下有一个可以读得出的标题?而且这些图片是: + +* 并不是特别黑的 +* 有一点高对比度 + +我们很难解释为什么这些文字可以看得这么清晰。你看一下下面这些: + +![](https://cdn-images-1.medium.com/max/600/1*UFnAScSM_SyiqI7g8e8Ajw.png) + +![](https://cdn-images-1.medium.com/max/600/1*i1T-LV5JQMk95st51J8zDA.png) + +答案是:纱幕化。 + +纱幕是一种让光变得更柔和的摄影装备。现在这也是种视觉设计的技术。这个技术通过让图片变得更柔和来让覆盖在上面的文字更加可以辨认。 + +如果我们用浏览器放大 Elastica blog 的网页,我们可以很清楚得看到发生了什么。 + +![](https://cdn-images-1.medium.com/max/800/1*BwU3s9dGxeUSpA-cIFuaHg.png) + +在这句标题“145,000 Salesforce Users Come out to Celebrate…”有一个让透光度渐变的框。你应该可以很简单的注意到高对比度的照片下这个深蓝色的背景。 + +这可能是最**微妙**的把文字可靠得覆盖在图片上的办法,并且我在别的地方并没法看到(但是这个方法**真的是**很隐蔽)。但是把这个标记下来,你可能不知道你什么时候会用到。 + +* * * + +### 规则 5:令文本层次分明 + +使得文字变好看并且符合背景的好办法经常是把文字往相反的方向 —— 比如说,变大但是更轻。 + +在我看来,创建漂亮的 UI 的最难的部分是调整文字 —— 并且这并不是因为缺少选项。如果你读过书,你大概用过所有的能让别人注意过文字的办法,或者让人不想看这些文字的办法: + +* **尺寸**(更大 或者更小) +* **颜色**(更多或者更少对比度;明亮的颜色会吸引眼球) +* **字重**(加深或者变轻) +* **大小写**(小写,大写,或者用标题的格式)(译者注:中文并没办法做到) +* **斜体** +* **字符间距**(或者 —— 用 更 高 大 上 的 术 语 —— **字距!**) +* **边距**(讲道理这并不是一个字体**本身的**性质,但是可以用来吸引别人的注意,所以它可以出现在表上) + +![](https://cdn-images-1.medium.com/max/800/1*D7QBHz4TqdxzXphdioU_gg.png) + +颜色,大小写,和字据用得不错。这是[@workjon](http://twitter.com/workjon)的孩子做的。当然,也关注下[@workjon](http://twitter.com/workjon) —— 他的文字设计很棒! + +这里有几个别的可以吸引别人注意的选项,但是并不常用同时也不是很推荐。 + +* **下划线**下划线现在基本意味着是超链接,并且要我说,下划线并不值得我们去给它负于任何意义。 +* **字体的背景颜色**并不常见,但是这个 37signals 网站把它用做超链接。 +* **删除线**你个 90 年代的 CSS 魔术师给我滚开,没错,就是指你! + +在我个人的经验里,当我发现一个我没办法找到合适的文本样式的时候,并不是因为我忘记了如何用大写或者更深的颜色 — **一般是因为最好的解决办法经常需要把一些互相冲突的性质组合在一起**。 + + +#### Up-pop 和 Down-pop + +你可以把所有的调整文字的方法分成以下两个组: + +* **那些提高可见度的样式**大,粗,大写等等。 +* **那些降低可见度的样式**小,少对比度,小边距等等。 + +我们会分别把这些叫做 "up-pop" 和 "down-pop" 的样式,以纪念 [favorite adjective](http://theoatmeal.com/comics/design_hell)。 + +![](https://cdn-images-1.medium.com/max/800/1*cT-Y5jcbdrUdO2JiBf8fZg.png) + +从 [hugeinc.com](http://hugeinc.com/case-study/material-design) 来的案例分析。 + +“材料设计”(Material Design)里有很多 up-pop 的的内容。它是**大**的;它是**高对比度**的;它是**非常****粗**的。 + +![](https://cdn-images-1.medium.com/max/800/1*yVbtuu-qvhFRXNwWKBvL-A.png) + +这些底下的东西,但是,是 "down-pop" 的。他们是**小**的,**低对比度**,并且很**细**的。 + +现在是非常重要的内容。 + +> **这个页的标题是仅有的用上了所有 up-pop 方法的文本**。 +> 对于所有别的东西,你需要 **up-pop 并且 down-pop**。 + +如果需要强调一个网站的内容元素,需要同时用上 up-pop 和 down-pop 的办法。这么做可以允许不同的内容元素看上去有不同的样子,防止你的东西被淹没。 + +![](https://cdn-images-1.medium.com/max/800/1*8YceqPbM08OB2kjJPEWzow.png) + +这是一个视觉要素的平衡。 + +这个完美设计的 Blu Homes 网站有很多大标题,但是**需要强调的字都是小写** —— 太多的强调会看上去用力过猛。 + +![](https://cdn-images-1.medium.com/max/800/1*C0snGn4IAUg_KEjwwPQN3w.png) + +Blue Homes 网站用了字的尺寸,颜色赫尔排列来吸引你眼球的数字 — 但是注意,他们并没选择用深灰色,**反而同时用了很轻的字重,低对比度的颜色**。 + +这些**在文字底下小小的标签**,然而,是灰色的,并且是即**大写**又**非常粗重**的。 + +这些都和平衡有关。 + +![](https://cdn-images-1.medium.com/max/800/1*phgw7PCxtkr78p0Td9yHcA.png) + +contentsmagazine.com + +Contents Magazine 是一个 up-pop 和 down-pop 很不错的案例分析。 + +* 这些**文章标题**基本上是**仅有的非斜体的网页要素**。在这种情况下,**缺少**斜体更加得吸引眼球(尤其是当和加粗的字重组合的时候)。 +* 在 by 的这一行里的**作者名字**是被加粗的 — 让它和平常字重的 "by" 分别了开来。 +* 这个小的,低对比度“**已经跳出来的**”字体给其他的要素让出了位置 —— 但是因为大写,很宽的字间距,和很大的边距,你可以在想看这些字的时候清楚得看到这些字。 + +#### 选取和悬浮的样式 + +调整被选择的元素和漂浮的效果是同一种文字游戏的另一个可能 —— 但是会更难。 + +变化字体的尺寸,大小写,或者字重经常会**改变文字占据多少空间**。这种变化可以限制住悬浮效果。 + +所以你还剩下哪些选项呢? + +* 字的颜色 +* 背景颜色 +* 阴影 +* 下划线 +* 轻微的动画 — 提高,降低,等等。 + +一个很可靠的选择是:尝试把白色的元素放上颜色,或者把有颜色的元素变白,但同时加深后面的背景 + +![](https://cdn-images-1.medium.com/max/800/1*_9M8qJFXvB7cEabkV-8qrw.png) + +这个选择的按钮从有颜色变成白色,但是依旧相对于背景保持高对比度。 + +我会送给你这个段话:**调整文本的样式是很难的**。 + +但是每次我在想“这个文本大概就是**不可能**看上去好看的”,我都是错的。我只需要逐渐变得更擅长。同时,去变得更擅长,我只要不断进行尝试就行了。 + +所以我提供给你个慰藉:如果这个文字看上去不好看,不要担心 —— **只要**你能变得更擅长。但是,嘿,让我们不断尝试,使自己**变得很强**! + +**嘿,顺便说一句:如果你想学更多和调整文字样式有关的东西,看看这个** [Learn UI Design](http://learnui.design/?utm_source=medium&utm_medium=content&utm_campaign=7-rules-part-2)**,我在这里讲了更多细节**。 + +* * * + +### 规则 6:仅使用好看的字体 + +**有些字体很好看。就用他们。** + +**注意:在这个部分里,没有什么需要学习的策略或别的什么。我只会列出一些好看的字体然后供你去下载,接着运用。** + +**注意 #2:由于前几年字体的选项得到了扩展,**并且**有些字体都快用烂了,今天我会特别推荐一些特别的字体组。如果你想看更多的字体,可以阅读** [Learn UI Design](http://learnui.design/?utm_source=medium&utm_medium=content&utm_campaign=7-rules-part-2),这里面有一套可以交互的完整版的字体。(译者注:这篇文章只推荐了英文字体,不一定适用于汉字) + +**特殊格调**的网站能用非常**特殊的字体**但是对于大部分的 UI 设计,你只希望一些**简单和干净**的字体。所以兄弟,没错,别用 [Wisdom Script](http://www.losttype.com/font/?name=wisdom_script)。 + +同时,我也**只推荐免费的字体**。为啥?这份学习指南是给**学习者的**。外面有超多免费的字体,所以就让我们用吧。 + +我推荐你现在就下载,然后当你开始为项目设计的时候就用。 + +![](https://cdn-images-1.medium.com/max/800/1*5Uv1DnYGFp5vG4RvW4QdXA.png) + +这个 Font Book 应用里“用户”这一栏可以很方便得帮你记住你下载了什么。 + +![](https://cdn-images-1.medium.com/max/800/1*J_5zAxGLGQxma9wq5mZAYw.png) + +Ubuntu + +**Ubuntu** (以上)。有非常多的字重。对于某些应用有点过于特殊了 — 不过对别的就很完美。可以在[Google Fonts](http://www.google.com/fonts/specimen/Ubuntu) 上找到。 + +![](https://cdn-images-1.medium.com/max/800/1*qeIEbAW5ylrBL7SYjGfq5Q.png) + +Open Sans + +**Open Sans**(以上)。一个读起来容易也很流行的字体。时候正文部分。可以在 [Google Fonts](http://www.google.com/fonts/specimen/Open+Sans) 上找到。 + +![](https://cdn-images-1.medium.com/max/800/1*YWlIwiUEZU184CBTxhS3sw.png) + +Bebas Neue. + +**Bebas Neue**(以上)。做标题很棒。全是大写的。可以在 [Fontfabric](http://fontfabric.com/bebas-neue/) - 这里面有很棒的“Bebas Neue in use”的展示。 + +![](https://cdn-images-1.medium.com/max/800/1*lXoXBsreAzsDUNakvTTcQg.png) + +Montserrat + +**Montserrat**(以上)。只有两种字重,但是足够用了。绝对是最好的 Gotham 和 Proxima 的免费替代品,但是并没有这两个好。可以在 [Google Fonts](http://www.google.com/fonts/specimen/Montserrat) 上找到。 + +![](https://cdn-images-1.medium.com/max/800/1*ffj71mDykTq41o2P2Y7XUA.png) + +Raleway + +**Raleway**(以上)。对于标题非常好;可能对于文本正文**有点** 过了(你看那些 W)。有非常好看得极细的字重(并没有照片)。可以在 [Google Fonts](http://www.google.com/fonts/specimen/Raleway) 上找到。 + +![](https://cdn-images-1.medium.com/max/800/1*1ZvAfIv56PYBxJe0cvWzqw.png) + +Cabin + +**Cabin**(以上)。可以在 [Google Fonts](http://www.google.com/fonts/specimen/Cabin) 上找到。 + +![](https://cdn-images-1.medium.com/max/800/1*u0LHdpKxw076R1MGNWemiQ.png) + +Lato + +**Lato**(以上)。可以在 [Google Fonts](http://www.google.com/fonts/specimen/Lato) 上找到。 + +![](https://cdn-images-1.medium.com/max/800/1*st1Z0NEH4ORQKnUyBojmqQ.png) + +PT Sans + +**PT Sans**(以上)。可以在 [Google Fonts](http://www.google.com/fonts/specimen/PT+Sans) 上找到。 + +![](https://cdn-images-1.medium.com/max/800/1*MntHOFiV1tpNPoPp76Wovw.png) + +Entypo Social + +**Entypo Social** (以上)。这是个图标字体。没有错,一旦你用了 Entypo,你会在**所有地方**看到它,但是这些社交网站的图标真是太棒了。不想在小小的有颜色的圈圈里重新创造一堆社交网站的 logo?没错,我也不想。在 [Entypo.com](http://www.entypo.com/) 可以找到。 + +我会在这里给你留下一些资源: + +* [**Beautiful Google web fonts**](http://hellohappy.org/beautiful-web-type/)。这个网站**非常棒**得展现出 Google Fonts 能有多好看。我从它那找了好多好多次灵感。 +* [**FontSquirrel**](http://www.fontsquirrel.com/)。一堆最棒的商业用途的字体,并且全部都是免费的。 +* [**Typekit**](https://typekit.com/)。如果你有 Adobe Creative Cloud(就是订购了 Photoshop 或者 Illustrator 等等),那么你可以有免费用到很棒的字体。没错,连 [Proxima Nova](https://typekit.com/fonts/proxima-nova) 都有! + +* * * + +### 规则7:像艺术家一样借鉴 + +当我第一次试图坐下来然后设计应用的元素的时候 —— **一个按钮,一个表格,一个图标,一个弹出框, 所有的所有** —— 也是我第一次意识到自己对于如何让一个元素好看的知识是如何匮乏的时候。 + +但是多幸运的是,我并不是一定需要创造出什么新的 UI 元素。这就意味着我可以一直看别人是如何做的然后从中间挑点好的。 + +但是我们要从哪里挑呢?这里有。 + +#### [1. Dribbble](http://dribbble.com) + +这个特邀的“给设计师展示”网站有**网络上最好质量的 UI 设计作品**。你可以在这里找到几乎最好的网站。 + +事实上,**你应该**关注我在 Dribble 上的作品[**这里**](https://dribbble.com/erikdkennedy)。这里也有一些人你可以关注: + +* [Victor Erixon](https://dribbble.com/victorerixon)。他有一个非常独特个人样式 — 并且他的作品**很棒**。漂亮,干净,扁平的设计。这货做设计师只有大概 3 年,但是他已经是做得很顶尖了。 +* [Focus Lab](https://dribbble.com/focuslab)。这些人是“Dribbble 名人”,并且他们的作品名副其实。非常多元化;一直是最顶尖的。 +* [Cosmin Capitanu](https://dribbble.com/Radium)。一个非常厉害的多面手。他做得东西未来感十足,但又不过于高调。他**非常**善于使用颜色,然而他并不十分注重 UX 的东西 — 当然这个批评也针对 Dribbble 这个网站。 + +![](https://cdn-images-1.medium.com/max/400/1*RBeNdi_ihQcqPkhDDB9Iig.png) + +![](https://cdn-images-1.medium.com/max/400/1*Ak6v-B69tzGoLQL9pjh1Yg.png) + +![](https://cdn-images-1.medium.com/max/400/1*FO0Qaq9QDSF4R7p-ZFzpLg.jpeg) + +这些分别都是 [Victor Erixon](https://dribbble.com/victorerixon),[Focus Lab](https://dribbble.com/focuslab) 和 [Cosmin Capitanu](https://dribbble.com/Radium) 的作品。 + +#### [2. Flat UI Pinboard](https://www.pinterest.com/warmarc/flat-ui-design/) + +我压根没听说过 "warmarc",但是他手机 UI 的 pinboard(译者注:pinboard 指的是 pininterest 里的专栏) 在我绞尽脑汁找好看的 UI 时候**令人震惊**得好用。 + +![](https://cdn-images-1.medium.com/max/800/1*eDgNkeU45KBKvw1Tb35WJw.png) + +#### [3. Pttrns](http://pttrns.com/) + +这里有一个列表的移动应用的截图。Pttrns 的好处是它整个网站是按照 —— 你懂得 —— UX 模式。这可以帮助你非常快速得搜索各种界面要素,无论你在做什么,管它是登录界面,用户信息,搜索结果,等等。 + +![](https://cdn-images-1.medium.com/max/1000/1*Cacg0SgS2Mm7n-qaZyj6TQ.png) + +* * * + +我是那句**直到善于能模仿最好的作品之前,所有艺术家都应该是只鹦鹉**的坚信者。之后你就可以你自己的风格;开发出新的潮流。 + +在这之间,让我们像小偷一样作图。 + +这个章节的想法中,“像艺术家一样借鉴”是从这本书[eponymous book](http://www.amazon.com/gp/product/0761169253/ref=as_li_tl?ie=UTF8&camp=1789&creative=390957&creativeASIN=0761169253&linkCode=as2&tag=e03fd7-20&linkId=EOZRG5UP4D6JMFIR)中借鉴出来的。我并没有读那本书,主要原因是这个标题很好的概括了这本书里想表达的想法。 + +* * * + +### 总结 + +我写这篇文章是因为我希望自己在以前可以读到这篇。我希望这篇可以帮助你。如果你是个 **UX 设计师**,在你素描出个大框架后做一个好看的 mockup。如果你是个**开发者**,接手下一个自己的项目然后让它变得很**好看**。我不希望需要去上几年艺术学校才能做好的 UI。只要**观察**,**模仿**,并且**告诉你的朋友哪些可以用**。 + +无论怎么样,这是迄今为止我学到的所有东西,同时我也一直会是个初学者。 + + +—- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/7-steps-to-get-more-clients-as-a-freelance-developer.md b/TODO1/7-steps-to-get-more-clients-as-a-freelance-developer.md new file mode 100644 index 00000000000..82ec47b97b6 --- /dev/null +++ b/TODO1/7-steps-to-get-more-clients-as-a-freelance-developer.md @@ -0,0 +1,192 @@ +> * 原文地址:[How to get more clients as a freelance developer](https://medium.freecodecamp.org/7-steps-to-get-more-clients-as-a-freelance-developer-ee00342f9260) +> * 原文作者:[Jad Joubran](https://medium.freecodecamp.org/@JoubranJad?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/7-steps-to-get-more-clients-as-a-freelance-developer.md](https://github.com/xitu/gold-miner/blob/master/TODO1/7-steps-to-get-more-clients-as-a-freelance-developer.md) +> * 译者: +> * 校对者: + +# How to get more clients as a freelance developer + +## Practical tips I wish I knew a few years ago + +![](https://cdn-images-1.medium.com/max/1000/1*GjnIq3MYSQy3GP_nsCSFxA.jpeg) + +Whenever a conversation about freelancing kicks off with fellow developer friends, we’re always discussing the same concerns: + +* How can I get more clients as a freelance developer? +* I finished a coding bootcamp and I want to get started as a freelancer. How can I start? +* How do you deal with cheap competition? +* How much should I charge? + +### “How do I get more clients?” + +When I got started as a freelancer a few years ago, I made a lot of mistakes when trying to get more clients. + +I thought of it like slices of pizza. Charging less (cutting smaller slices), would yield more projects (more slices). Right? + +![](https://cdn-images-1.medium.com/max/800/1*Yjyurn2b2xh2zISUFwRUPQ.jpeg) + +I also thought that I need to explicitly tell people that I’m a freelancer and post about it on Social media, or else, how will they ever find me? + +I made these mistakes for a few years until I finally realized something. + +#### Charging low gives a cheap impression to clients + +This also drives them to ask me to do more work for the same price. I also realized that you immediately lose your value if you sell yourself directly. + +Soon after I realized that I was doing it wrong, I was able to get high quality projects with higher budgets and better working conditions. The amount of effort I was putting in was minimal compared to before. + +In the year that followed, my career started to pick up significantly. I started speaking at conferences around the world, presenting workshops for companies and banks and teaching online courses. Later, I became a [Google Developer Expert in Web Technologies](https://developers.google.com/experts/people/jad-joubran). + +Now that we got those misconceptions out of the way. Here’s some good news: + +**You don’t need 10 years of experience to get more clients. ⚡️** + +It’s not about the years of experience, it’s about the service that you’re offering. It’s about the whole experience, from the time your client needs the service, all the way until the end. + +Here are 7 steps that will help you get there and get you more clients. + +### 1. Define who you are 👨‍🎨 + +Before you start getting more clients, first you need to define how you want to appear to others — what’s your image? How do you want people to see you? + +If potential clients hear about you, they often need to know **who you are**. The first thing they would do is simply Google your name. + +Try this: Open a new incognito tab and Google your full name. What is your first impression of yourself? Is is aligned with who you are and what you do? + +**You can influence what people think about you.** + +If it’s not completely aligned, the best way to change that image is to create a personal website where you show who you are and what you’re good at. Explain what you do in the first 2 seconds of them landing on your website. + +> “Make it simple. Make it memorable. Make it inviting to look at. Make it fun to read.” — Gary Vaynerchuk + +This will motivate your visitors to scroll further down where you will prove to them that you’ve got the experience you claim. More on this in a bit. + +### 2. Don’t just be a developer ⚡️ + +When you’re working on technical projects, it’s easy to get carried away and focus on the little technical details and forget about the big picture. + +But if you focus only on the task you’re asked to do, then you’re going to be producing _average_ results. **What you need to focus on is _quality_ work**. + +![](https://cdn-images-1.medium.com/max/800/1*V0UjgCuX9HzTGK6nbAXQIQ.jpeg) + +For you to focus on quality, you have to work on skills that are around your area of expertise. For example, if you’re a front-end developer, you should definitely know the basics of User Experience and Performance. This will help you deliver outstanding results. + +The same applies to soft skills that can help your relationship with clients. For example, communication skills or business strategy. **Understanding the business behind the project will often transform you from a freelancer into a consultant.** + +These skills will prove that you’re not just a developer, you’re a professional driven by quality. That’s what can make you stand out. + +### 3. Show ’em, don’t tell ’em 👀 + +So how do you prove to people that you are who you claim to be? It’s not enough to say you’re good at what you do. You need to prove to them that you truly are experienced in that skill. + +You just have to show them what you’ve done. If you have previous projects to show, then that’s easy, you just display the ones you’re most proud of. But in some other cases (like when you have confidential projects), it might be tricky. This is a great opportunity to show your value without actually saying it. + +Here’s an example: when you go to [my website](https://jadjoubran.io), I’m claiming to be a tech speaker and web consultant. + +How do you know this is legit? You can see a background video of me giving talks in many settings (workshops, conferences). From this, visitors immediately lose any doubt and are convinced that I’m a tech speaker indeed. + +![](https://cdn-images-1.medium.com/max/800/1*fNAPmAOrYporA01IzS0FuQ.jpeg) + +Explain what you do above the fold + +It doesn’t have to be a video. You can prove your value in many different ways, like displaying logos of companies you’ve worked with, and showing blog articles you’ve written. + +### 4. Use indirect promotion 🎯 + +When you think about how to get more clients, a lot of people might think: ok, how about I start by posting on social media that I’m looking for freelance work? + +I’ve seen countless posts on Facebook, LinkedIn, Twitter & Slack of developers and designers announcing to the whole world that they are looking for freelance opportunities. + +It turns out, that’s exactly what you shouldn’t do. You immediately lose your value when you sell yourself directly. + +![](https://cdn-images-1.medium.com/max/800/1*ATY0-7Lb5lJx_0wfzmRtWg.jpeg) + +Will Code HTML for Food + +Look at it this way. Remember how you felt when someone **called you** on the phone to sell you a certain service? How spammy was that? Wouldn’t you most likely ignore and hang up as soon as possible? + +**One of the greatest tricks that I’ve learned when it comes to getting clients is to never approach clients.** + +It sounds contadictory, but it’s true. Instead, you apply the concept of indirect promotion. + +How? Simply share on your social networks the activities and projects that you’re working on without mentioning the fact that you’re looking for clients. After you share your activities a few times, people will start to know you for what you do and they’ll immediately recommend you for their friends and relatives whenever there’s an opportunity. + +That’s how I’ve gotten 100% of all my projects in the last 6 years. It works! + +If you’ve never had a freelance project before, then build a sample project instead of going and looking for work. Make it look very appealing. + +Here’s an example of a tweet I posted. + +![](https://i.loli.net/2018/10/01/5bb207440840b.png) + +### 5. Don’t aim for a steady stream of projects ⏳ + +You might think that being a successful freelancer means having a steady stream of projects and being 100% occupied with freelance work. + +But it doesn’t have to be this way, and in fact, it shouldn’t. + +If you’re occupied with freelance work all the time, then you’re not leaving time for you to be creative, learn new things, and work on your personal presence. + +**I leave 50% of my time for personal research.** During that time, I watch online conferences, read technical articles, and try out the latest technologies. + +![](https://cdn-images-1.medium.com/max/800/1*LG1PJ4OxCjvE4noc24iydA.jpeg) + +People often ask me, how did you learn XYZ.. and the answer is always the same: I built a sample app for it. + +So it’s really important that you don’t keep on looking for freelance projects all the time, but take the time to learn new technologies which will in fact get you better projects and new opportunities over time. + +### 6. Come up with your own process 🔖 + +It’s hard for us freelancers to be organized, since we’ve got plenty of responsibilities on our plate. To make sure you have a smooth workflow with each client, create a process that you can follow for most of your projects. + +This way, you’ll have a plan ready to be launched whenever a potential project comes up. You don’t have to worry about the little things. + +![](https://cdn-images-1.medium.com/max/800/1*2elFS_hJ1y47D8sXpO_tNw.jpeg) + +Here’s an example of my project kickoff workflow: + +1. Send proposal PDF +2. Send contract +3. Sign and receive counter-signed contract +4. Send downpayment invoice +5. Receive downpayment +6. Carry out freelance task +7. Send final invoice +8. Send feedback form + +When you have a personalized workflow, you will be comfortable working which will make your clients enjoy working with you and trust you. They will most likely refer you to other companies in the future. + +Read more about [creating a process](https://www.proposify.com/definitive-guide-to-going-freelance-chapter-6). + +### 7. Charge higher 💰 + +If you’re just getting started, it’s okay to charge cheap for the first project or two, but after that you have to start charging higher. + +You might think you don’t deserve to charge higher yet, but going through these steps will let you charge higher, because you have value that’s worth charging more for. + +You may not know this, but **charging low makes your client feel that they’re getting low quality results.** + +If you already have a lot of freelance work because you’re charging cheap, it’s more likely that charging higher will make you lose some clients. But that’s actually a good thing, as you’ll end up earning a bit more while having more free time for research. It’s a risk that you shouldn’t be afraid to take as it’s totally worth it. + +Also, you won’t be working with over-demanding clients, you’ll be working with clients that will appreciate your services. + +When coming up with a price, you have to be comfortable charging that price — and then increase it by 10 to 25%. + +Let’s say for example you’re already working on a certain project for an hourly rate of €80/hour. Since you dedicated a few hours of your time over the past few months to learn more about User Experience & Web Performance, you should now increase your hourly rate by around 15% as you’re bringing more value to the project. + +Following these steps completely changed my life and I hope it will change yours too. + +I can’t stress enough the importance of experimentation with new technologies. Don’t wait for opportunities to knock on your door. Create those opportunities instead. + +These were just a few tips from my free email course [Become an expert developer](https://learn.jadjoubran.io/). If you like these tips, then you will definitely love the course as we go in more details on how to grow in your expertise, get more quality clients, and more. + +* [Freelancing](https://medium.freecodecamp.org/tagged/freelancing?source=post) +* [Self Impr](https://medium.freecodecamp.org/tagged/self-improvement?source=post) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/a-beginner-friendly-introduction-to-containers-vms-and-docker.md b/TODO1/a-beginner-friendly-introduction-to-containers-vms-and-docker.md new file mode 100644 index 00000000000..0389b5161fc --- /dev/null +++ b/TODO1/a-beginner-friendly-introduction-to-containers-vms-and-docker.md @@ -0,0 +1,309 @@ +> * 原文地址:[A Beginner-Friendly Introduction to Containers, VMs and Docker](https://medium.freecodecamp.org/a-beginner-friendly-introduction-to-containers-vms-and-docker-79a9e3e119b) +> * 原文作者:[Preethi Kasireddy](https://medium.freecodecamp.org/@preethikasireddy?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-beginner-friendly-introduction-to-containers-vms-and-docker.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-beginner-friendly-introduction-to-containers-vms-and-docker.md) +> * 译者:[steinliber](https://github.com/steinliber) +> * 校对者:[7Ethan](https://github.com/7Ethan), [jianboy](https://github.com/jianboy) + +# 容器,虚拟机以及 Docker 的初学者入门介绍 + +![](https://cdn-images-1.medium.com/max/2000/1*k8n7Jx9UaLRAxum9HMp8nQ.png) + +来自: [https://flipboard.com/topic/container](https://flipboard.com/topic/container) + +如果你是一个开发者或者技术人员,那你肯定或多或少听说过 Docker:它是一个用于打包,传输并且在“容器”中运行应用的工具。它是如此不容忽视,以至于现在无论开发还是运维相关人员都在关注这个工具。甚至像谷歌,VMware 和亚马逊这样的大型公司也正在构建支持它的服务。 + +无论对你来说 Docker 是否能马上用得到,我都认为理解 “容器”的一些基础概念以及它和虚拟机(VM)之间的差异是很重要的。虽然互联网上已经有了大量优秀的 Docker 使用指南,但我没看到很多适合初学者的概念指南,特别是和容器的构成相关的。所以希望这篇文章可以帮助解决这个问题 :) + +让我们先来理解虚拟机和容器到底是什么。 + +### 什么是“容器”和“虚拟机” + +容器和虚拟机它们的目的很相似:即将应用程序和它的依赖放到一个可以在任何环境运行的自足单元中。 + +此外,容器和虚拟机消除了对物理硬件的需求,从而在能源消耗和成本效益方面能让我们更有效地使用计算资源, + +容器和虚拟机的主要区别在于它们的架构方式。让我们继续深入了解。 + +### 虚拟机 + +虚拟机在本质上是对现实中计算机的仿真,它会像真实的计算机一样执行程序。使用 __“hypervisor”__ 可以将虚拟机运行于物理机上。hypervisor 可以在主机运行,也可以在“裸机”上运行。 + +让我们来揭开这些术语的面纱: + +**hypervisor**(之后都以虚拟机管理程序称呼)是能让虚拟机在其上运行的软件,固件或者硬件。虚拟机管理程序本身会在物理计算机上运行,称为**“主机”**。主机为虚拟机提供资源,包括 RAM 和 CPU。这些资源在虚拟机之间被划分并且可以根据需要进行分配。所以如果一个虚拟机上运行了资源占用更大的应用程序,相较于其它运行在同一个主机的虚拟机你可以给其分配更多的资源。 + +运行在主机上的虚拟机(再次说明,通过使用虚拟机管理程序)通常也被叫做“访客机”。访客机包含了应用以及运行这个应用所需要的全部依赖(比如:系统二进制文件和库)。它还带有一个自己的完整虚拟化硬件栈,包括虚拟化的网络适配器,储存和 CPU-这意味着它还拥有自己成熟的整个访客操作系统。从虚拟机内部来看,访客机的操作都认为其使用的都是自己的专用资源。从外部来看,我们知道它是一个虚拟机-和其它虚拟机一起共享主机提供的资源。 + +就像前面所提到的,访客机既可以运行在**托管的虚拟机管理程序**上,也可以运行在**裸机虚拟机管理程序**上。它们之间存在一些重要的差别。 + +首先,托管的虚拟化管理程序是在主机的操作系统上运行。比如说,可以在一台运行 OSX 操作系统的计算机的系统上安装虚拟机(例如:VirtualBox 或者 VMware Workstation 8)。虚拟机无法直接访问硬件,因此必须通过主机上运行的操作系统访问(在我们的例子中,也就是 Mac 的 OSX 操作系统)。 + +托管虚拟机管理程序的好处是底层硬件并不那么重要。主机的操作系统会负责硬件的驱动而不需要管理程序参与。因此这种方式被认为具备更好的“硬件兼容性”。在另一方面,在硬件和管理程序之间这个额外的附加层会产生更多的资源开销,这会降低虚拟机的性能。 + +裸机虚拟机管理程序通过直接在主机硬件上安装和运行来解决这个性能问题。因为它直接面对底层的硬件,所以并不需要运行在主机的操作系统之上。在这种情况下,安装在主机上第一个作为操作系统运行的就是这个裸机虚拟机管理程序。与托管虚拟机管理程序不同,它有自己的设备驱动直接与每个组件交互,以执行任何 I/O,处理或特定于操作系统的任务。这样可以获得更好的性能,可伸缩性和稳定性。这里的权衡在于其对硬件的兼容性有限,因为裸机虚拟机管理程序内置的设备驱动只有那么多。 + +在讨论了虚拟机管理程序之后,你可能想知道为什么我们需要在虚拟机和主机之间这个额外的“虚拟机管理程序”层。 + +好吧,虚拟机管理程序在其中确实发挥了重要的作用,由于虚拟机拥有自己的虚拟操作系统,管理程序为虚拟机管理和执行访客操作系统提供了一个平台。它允许主机与作为客户端运行的虚拟机之间共享其资源。 + +![](https://cdn-images-1.medium.com/max/800/1*RKPXdVaqHRzmQ5RPBH_d-g.png) + +虚拟机图示 + +正如你可以在图示中所看到的,VMS 会为每个新的虚拟机打包虚拟硬件,一个内核(即操作系统)和用户空间。 + +### 容器 + +与提供硬件虚拟化的虚拟机不同,容器通过抽象“用户空间”来提供操作系统级别的虚拟化。当我们详解容器这个术语的时候你就会明白我的意思。 + +从所有的意图和目的来看,容器看起来就像一个虚拟机。比如说,它们有执行进程的私有空间,可以使用 root 权限执行命令,具有专有的网络接口和 IP 地址,允许自定义路由和 iptable 规则,可以挂载文件系统等。 + +容器和虚拟机之间的一个重要区别在于容器和其它容器共享主机系统的内核。 + +![](https://cdn-images-1.medium.com/max/800/1*V5N9gJdnToIrgAgVJTtl_w.png) + +容器图示 + +这图表明容器只会打包用户空间,而不是像虚拟机那样打包内核或虚拟硬件。每个容器都有自己独立的用户空间从而可以让多个容器在单个主机上运行。我们可以看到所有操作系统级别的体系架构是所有容器共享的。要从头开始创建的部分只有 bins 和 libs 目录。这就是容器如此轻巧的原因。 + +### Docker 是从哪来的? + +Docker 是基于 Linux 容器技术的开源项目。它使用 Luinux 的内核功能(如命名空间和控制组)在操作系统上创建容器。 + +容器已经远远不是一个新技术:Google 已经使用他们自己的容器技术好多年了。其它的容器技术包括 Solaris Zones、BSD jails 和 LXC 也已经存在好多年。 + +那么为啥 Docker 会突然取得成功呢? + +1. **使用简单**:Docker 使得任何人(开发人员,运维,架构师和其他人)都可以更轻松的利用容器的优势来快速构建和测试可移植的应用程序。它可以让任何人在他们的笔记本电脑上打包应用程序,不需要任何修改就可以让应用运行在公有云,私有云甚至裸机上。Docker 的口头禅是:“一次构建,处处运行”。 + +2. **速度**:Docker 容器非常轻量级和快速。因为容器只是运行在内核上的沙盒环境,因此它们占用的资源更少。与可能需要更多时间来创建的虚拟机相比,你可以在几秒钟内创建一个 Docker 容器,因为虚拟机每次都必须启动一个完整的操作系统。 + +3. **Docker Hub**:Docker 用户也可以从日益丰富的 Docker Hub 生态中受益,你可以把 Docker Hub 看作是 “Docker 镜像的应用商店”。Docker Hub 拥有数万个由社区构建的公共镜像,这些镜像都是随时可用的。在其中搜索符合你需求的镜像非常容易,你只需要准备拉取镜像而且几乎不需要任何修改。 + +4. **模块化和可扩展性**:Docker 可以让你轻松地把应用程序按功能拆分为单个独立的容器。比如说,你的 Postgre 数据库可以运行在一个容器中,Redis 服务运行在另一个容器中,而 Node.js 应用运行在另一个容器中。使用 Docker,将这个容器链接在一起以创建你的应用程序将会变得更简单,同时在将来可以很轻松地扩展和更新单独的组件。 + +最后但并不重要的是,有谁不喜欢 Docker 的鲸鱼(Docker 的标志)呢?:) + +![](https://cdn-images-1.medium.com/max/800/1*sGHbxxLdm87_n7tKQS3EUg.png) + +来自: [https://www.docker.com/docker-birthday](https://www.docker.com/docker-birthday) + +### 基础的 Docker 概念 + +现在我们已经大致了解了 Docker,让我们依次讲下 Docker 的基础部分: + +![](https://cdn-images-1.medium.com/max/800/1*K7p9dzD9zHuKEMgAcbSLPQ.png) + +#### Docker Engine + +Docker Engine 是 Docker 运行的底层。它是一个轻量级的运行时和工具,可以用于管理容器,镜像,构建等等。它在 Linux 本机上运行,由以下部分组成: + +1. 在主机上运行的 Docker 守护进程。 +2. Docker 客户端,用于和 Docker 守护进程通信来执行命令。 +3. 用于远程和 Docker 守护进程交互的 REST API。 + +#### Docker 客户端 + +Docker 客户端是用来和你( Docker 的终端用户)交互的。可以把它想象成 Docker 的 UI。例如: + +你是在和 Docker 客户端进行交互,然后 Docker 客户端会把你的指令传递给 Docker 守护进程。 + +``` +docker build iampeekay/someImage . +``` + +#### Docker 守护进程 + +实际上发送到 Docker 客户端的命令是由 Docker 守护进程执行(比如像构建,运行和分发容器)。Docker 守护进程在主机上运行,但作为用户,你并不能直接和守护进程交互。Docker 客户端也可以在主机上运行,但这并不是必需的。它可以运行在不同的机器上并且与运行在主机上的 Docker 守护进程通信。 + +#### Dockerfile + +你可以在 Dockerfile 中编写构建 Docker 镜像的指令。这些指令可以是: + +* **RUN apt-get y install some-package**:安装软件包 +* **EXPOSE 8000**:暴露一个端口 +* **ENV ANT_HOME /usr/local/apache-ant**:传递环境变量 + +更进一步。一旦配置好的你的 Dockfile,就可以使用 **docker build** 命令从中构建镜像。这里是 Dockerfile 的一个例子: + +简单的 Dockerfile: + +``` +# 构建基于 ubuntu 14.04 镜像 +FROM ubuntu:14.04 + +MAINTAINER preethi kasireddy iam.preethi.k@gmail.com + +# 用于 SSH 登陆和端口重定向 +ENV ROOTPASSWORD sample + +# 在安装包的过程中关闭提示 +ENV DEBIAN_FRONTEND noninteractive +RUN echo "debconf shared/accepted-oracle-license-v1-1 select true" | debconf-set-selections +RUN echo "debconf shared/accepted-oracle-license-v1-1 seen true" | debconf-set-selections + +# 更新包 +RUN apt-get -y update + +# 安装系统工具/库 +RUN apt-get -y install python3-software-properties \ + software-properties-common \ + bzip2 \ + ssh \ + net-tools \ + vim \ + curl \ + expect \ + git \ + nano \ + wget \ + build-essential \ + dialog \ + make \ + build-essential \ + checkinstall \ + bridge-utils \ + virt-viewer \ + python-pip \ + python-setuptools \ + python-dev + +# 安装 Node,npm +RUN curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash - +RUN apt-get install -y nodejs + +# 把 oracle-jdk7 添加到 Ubuntu 包仓库 +RUN add-apt-repository ppa:webupd8team/java + +# 确保包仓库是最新的 +RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list + +# 更新 apt +RUN apt-get -y update + +# 安装 oracle-jdk7 +RUN apt-get -y install oracle-java7-installer + +# 导出 JAVA_HOME 环境变量 +ENV JAVA_HOME /usr/lib/jvm/java-7-oracle + +# 执行 sshd +RUN apt-get install -y openssh-server +RUN mkdir /var/run/sshd +RUN echo "root:$ROOTPASSWORD" | chpasswd +RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config + +# SSH 登陆修复。否则用户将在登陆后被踢出 +RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd + +# 暴露 Node.js 应用端口 +EXPOSE 8000 + +# 创建 tap-to-android 应用目录 +RUN mkdir -p /usr/src/my-app +WORKDIR /usr/src/my-app + +# 安装应用依赖 +COPY . /usr/src/my-app +RUN npm install + +# 添加 entrypoint 执行入口点 +ADD entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] + +CMD ["npm", "start"] +``` + +#### Docker 镜像 + +通过你 Dockerfile 中指令构建的镜像是一个只读的模版。镜像不仅定义了你希望打包的应用程序和其依赖,还有启动时要运行的进程。 + +Docker 镜像是使用 Dockerfile 构建的。Dockerfile 中的每个指令都会为镜像添加一个新的“镜像层”,镜像层表示的是镜像文件系统中的一部分,可以添加或者替换位于它下面的镜像层内容。镜像层是 Docker 轻巧且强大结构的关键。Docker 使用 Union 文件系统来实现它: + +#### Union 文件系统 + +Docker 使用 Union 文件系统来构建一个镜像。你可以把 Union 文件系统看作是可堆叠文件系统,这意味着不同文件系统(也被认为是分支)中的文件和目录可以透明的构成一个文件系统。 + +在重叠分支内拥有相同路径目录的内容会被视为单个合并的目录,这避免了需要为每一层创建单独副本。相反,它们都被赋予了指向同一个资源的指针;当某些镜像层需要被更改时,它就会创建一个副本并且修改本地的副本,而原来的镜像层保持不变。这种方式使得在外部看起来文件系统是可写的而实际上内部却并不可写。(换句话说,就是“写时复制”系统。) + +层级系统主要提供了两个优点: + +1. **无复制**:镜像层有助于避免每次你使用镜像创建或者运行容器时复制整套文件,这使 docker 容器实例化非常快速和廉价。 +2. **镜像层隔离**:当你更改一个镜像时会更快,Docker 更新只会传播到已改变的镜像层。 + +#### 卷 + +卷是容器的“数据部分”,它会在容器创建的时候初始化。卷允许你持久化并且共享容器中的数据。数据卷与镜像中默认的 Union 文件系统是分离的,并作为主机文件系统上的普通目录和文件存在。所以,即使你销毁,更新或者重新构建你的容器,数据卷也将保持不变。如果想更新数据卷,你也可以直接对其进行更改。(这功能额外的好处在于,数据卷可以在多个容器之间共享和重用,如此简洁优雅。) + +#### Docker 容器 + +如上所述,Docker 容器将应用程序的软件及其运行所需的全部东西打包到了不可见的沙箱中。这包括操作系统,应用代码,运行时,系统库等等。Docker 容器是基于 Docker 镜像构建的。因为镜像是只读的,所以 Docker 在镜像的只读文件系统上添加了一个读写文件系统来创建容器。 + + +![](https://cdn-images-1.medium.com/max/800/1*hZgRPWerZVbaGT8jJiJZVQ.png) + +来自:Docker + +此外,Docker 创建容器还有很多步,它会创建一个网络接口以便容器和本地主机可以通信,再把可用的 IP 地址附加到容器上,并运行定义镜像时你所指定运行应用程序的进程。 + +成功创建了容器之后,你可以在任何环境中运行它而无需任何更改。 + +### 双击“容器” + +唷!已经讲了好多部分了。有一件事总是让我感到好奇,那就是实际上容器是如何实现的,特别是容器相关并没有任何的抽象基础设施边界可以参照。经过大量地阅读之后,这一切都是值得的,所以下面是我尝试向你们解释它!:) + +“容器”其实只是一个抽象的概念,用于描述不同的功能如何协同从而得到一个可视化的“容器”。让我们快速浏览这些功能: + +#### 1) 命名空间 + +命名空间为容器提供了它们自己的底层 Linux 视图,限制了容器可以查看和访问的内容。当你运行一个容器的时候,Docker 会创建这个特定容器将会使用的命名空间。 + +Docker 使用了内核中提供的几种不同类型的命名空间,比如说: + +a. **NET**:为容器提供了只有其自己可见的系统网络堆栈(例如,其自己的网络设备、IP 地址、IP 路由表、/proc/net 目录和端口号等)。 +b. **PID**:PID 表示进程 ID。如果你曾在命令行中运行 **ps aux** 来检测系统上正在运行的进程,你将会看到有一列名叫 “PID”。PID 命名空间为容器提供了只有它们自己范围内可见和交互的进程视图。包括独立的 init 进程(PID 1),这个进程是容器内所有进程的“祖先”。 +c. **MNT**:给容器一个自己的[系统“挂载”](http://www.linfo.org/mounting.html)视图。因此,在不同挂载命名空间的进程具有文件层级结构的不同视图。 +d. **UTS**:UTS 代表 UNIX 分时系统。它允许进程识别系统标识符(即主机名,域名等)。UTS 让容器可以有自己的主机名和 NIS 域名,独立于其它容器和主机系统。 +e. **IPC**:IPC 表示进程间通信。IPC 命名空间负责隔离每个容器中运行进程之间的 IPC 资源。 +f. **USER**:这个命名空间用于隔离每个容器中的用户。相较于主机系统,它的功能是让容器具有 uid(用户 ID)和 gid(组 ID)范围的不同视图。因此,进程在用户命名空间内部的 uid 和 gid 可以和外部主机不同,这就允许在进程在容器外部的 uid 是非特权用户,而不会牺牲在容器内部进程 uid 的 root 权限。 + +Docker 将这些命名空间一起使用来隔离并开始创建容器。下面的功能叫做控制组。 + +#### 2) **控制组** + +控制组(也叫做 cgroups)是一种 Linux 内核功能,用于隔离,确定优先级和统计一组进程的资源使用情况(CPU、内存、磁盘 I/O 和网络等 )。从这个意义上来说,控制组确保 Docker 容器只使用它们需要的资源-如果需要,还可以设置容器可以使用的资源限制。控制组还确保单个容器不会耗尽其中的资源从而导致系统奔溃。 + +最后,Union 文件系统是 Docker 使用的另一个功能: + +#### 3) **隔离的 Union 文件系统:** + +这个已经在上面 Docker 镜像部分描述过了:) + +这就是 Docker 容器的全部内容(当然,魔鬼在实现细节中-比如如何管理不同组件之间的交互)。 + +### Docker 的未来:Docker 将于虚拟机共存 + +虽然 Docker 确实获得了很多支持,但我并不认为它会成为虚拟机真正的威胁。容器将继续发挥作用,但有很多情况下更适合使用虚拟机。 + +比如说,如果你需要在多个服务器上运行多个应用,则使用虚拟机可能是有意义的。另一方面,如果你需要运行单个应用的多个副本,Docker 则能提供一些引人注目的优点。 + +此外,虽然容器允许你将应用拆分为更多功能独立的部分从而创建关注点分离,它也意味着需要管理的部件会越来越多,这可能会变得难以处理。 + +安全性也是 Docker 容器所关注的一个领域-由于容器之间共享内核,容器之间的隔层会更薄。一个完整的虚拟机只能向主机的虚拟机管理程序发出超级调用,但是 Docker 容器却可以向主机内核发起系统调用,这导致其被攻击的范围相比之下会更大。当安全性特别重要时,开发人员可能会选择由抽象硬件隔离的的虚拟机-这可以使不同虚拟机之间进程的互相干扰变得更加困难。 + +当然,随着容器在生产环境中更多使用和用户的进一步审核,安全和管理等问题肯定会不断发展。就目前而言,关于容器与虚拟机之间的争论对于那些每天都接触它们的人来说真的是最好的。 + +### 结论 + +我希望你现在已经掌握了了解 Docker 所需要的知识,甚至有一天会在项目中使用它。 + +像往常一样,无论我在文中有任何错误或者您有任何帮助的建议,请在评论下留言!:) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/a-complete-guide-to-getting-hired-as-an-ios-developer-in-2018.md b/TODO1/a-complete-guide-to-getting-hired-as-an-ios-developer-in-2018.md new file mode 100644 index 00000000000..57866c312dc --- /dev/null +++ b/TODO1/a-complete-guide-to-getting-hired-as-an-ios-developer-in-2018.md @@ -0,0 +1,271 @@ +> * 原文地址:[A Complete Guide to Getting Hired as an iOS Developer in 2018](https://blog.usejournal.com/a-complete-guide-to-getting-hired-as-an-ios-developer-in-2018-d7dcf50dc25) +> * 原文作者:[Rob Caraway](https://blog.usejournal.com/@robcaraway?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-complete-guide-to-getting-hired-as-an-ios-developer-in-2018.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-complete-guide-to-getting-hired-as-an-ios-developer-in-2018.md) +> * 译者:[melon8](https://github.com/melon8) +> * 校对者:[Park-ma](https://github.com/Park-ma) + +# 2018 年 iOS 开发找工作完全指南 + +## 或如何避免浪费你人生的两千个小时 + +![](https://cdn-images-1.medium.com/max/1600/1*CSEtc6xuG1-Va_JWQC7naQ.jpeg) + +我被一份耗费了我三个半月精力的工作拒绝了。 + +我做了所有的准备。那个公司的一切就是我的一切。我几乎可以告诉你所有关于那家公司创始人在网上发表的东西。 + +我大概十分天真了。 + +想象一下,我写了一篇很长的博客,里面全是实际的代码和如何改进他们应用程序的例子。因为我就是这么做的。 + +尽管我投入了所有的精力,我还是得**大声**说,得到这份工作是不可能的。我不想相信,但说出来还让我感到了一点安慰。 + +几个月后,我终于吸引了他们的注意。我和他们的 CTO 通了电话,聊得很愉快,他们邀请我参加编程挑战。 + +我花了一周的时间来做到完美,他们的团队也表示对我的代码印象深刻。我自信心高涨,感到自己很安全。 + +然后,我参加了他们的结对编程测试。 + +> 两天后,我收到一封拒绝邮件。他们告诉我,我不是很合适。超过 2000 个小时来学习换来了一小时的教训。 + +我瘫倒在沙发上。他们是对的。我并没有真的符合他们的需求 —— 我只是花了几个月的时间说服自己我做到了。 + +回想起来,很容易看出我的行为是多么荒唐和危险。我猜原来的我太害怕被拒绝,想要尽自己所能来减少被拒绝的可能性。 + +> 也许我们大多数人需要处理这种负能量 —— 把简历群发给每个可能的公司,然后石沉大海没有回应。 + +在遭到可怕的拒绝后,我醒悟过来了(好像我别无选择)。于是我重新制定了一个实际可行的策略,并最终被一家我认为非常合适的公司应聘为 iOS 开发人员。 + +### 本篇指南旨在实现的目标: + +我概述的这些策略**不需要先前的人际关系网络**,并且是那些希望获得**全职工作**的 iOS 开发者。虽然你不需要认识任何人,但知道如何沟通和推销自己还是有帮助的。 + +**你需要做大量的工作** —— 意思是以下任一条或全部: + +* 你发布的可以下载的应用程序 +* 你启动或参与的开源项目 +* 你作为该领域的意见领袖创造的内容 +* 或其他相关工作经验 + +**如果你没有任何可以推销的东西,那我也帮不了你。** + +到本指南的结尾,你就会知道在 2018 年,在一家开发面向消费者的应用的公司获得一份 iOS 开发工作需要做出什么准备工作。 + +#### 关于我的背景: + +我从 iOS4 开始就开始开发 app。我没有大学学位。我从未有过全职工作,也从未在“敏捷”开发环境或大的团队中工作过。 + +许多公司因为我没有大团队工作经验和没有大学文凭的简历拒绝了我。 + +但我也不是空手而去。我自己开发上架的应用被[下载了 100 万次](https://medium.com/@thecaraway/how-i-lean-startupd-my-way-to-240k-on-the-saturated-app-store-92862ba3c6fc)。我与人合作创办了一家(低成本的)初创公司,并以自由职业者的身份与一些很酷的客户合作。我有很好的公共项目来展示我的技能。 + +我在一个主要的科技市场(德克萨斯州奥斯丁)找到了工作,也得到了一些远程工作机会。我被聘为高级职位。我想我的经历对初级和中级程序员也有帮助。 + +**在我们跳进沉重的东西之前:** + +> **我最终被录用的第一条规则是:把所有的事情都记录下来!** + +对公司做笔记,跟踪哪些简历和求职信有用,每次面试后做笔记,这些会帮助你变得更快更好。 + +### 搞定你的 iOS 简历 + +不想重复造轮子,所以如果你有时间,请[阅读这篇编写开发人员简历指南](https://medium.freecodecamp.org/how-to-write-a-good-resume-in-2017-b8ea9dfdd3b9)。 + +如果你没有时间: + +#### 我曾经被应聘时的简历的一个稍微修改过的版本: + +![](https://cdn-images-1.medium.com/max/800/1*4xXwKJBUGdKxfn9Rrs1mVg.png) + +> 你的简历应该简单易读。以一种易于阅读的格式列出你的成就,优先列出让你看起来最好的事情。 + +**你的简历应该有:** + +* 教育背景(如获得学位或选修重要课程) +* 工作经验 +* 开源项目(提供链接) +* 你的个人应用程序(如果可能的话提供链接) +* 最相关的技术技能(保持最小篇幅) +* 其他值得注意的事情(你参加的俱乐部,你举办的开发者见面会,你赢的黑客马拉松) + +**不**要提及你是高级还是初级开发。让你的简历说明一切。 + +**对你的简历维护几个版本**。每个版本都应该尽量根据不同公司的个性调整描述细节。 + +不是让你去撒谎,而是以不同公司最看重的方面来推销自己。 + +### 获得成功的其他方法 + +#### 建立一个很棒的个人网站。 + +你的网站可以表达出简历无法表达的东西。[看看我的个人网站](http://robcaraway.com/about/)。当我走进 [InMotion Software](http://www.inmotionsoftware.com/) 的办公室时,他们打开了我的个人网站的 about me 页面。几天后他们给了我 offer。 + +[这是另一个很好的个人网站的例子](https://peterlyons.com/)。保持网站的整洁,用**你潜在雇主希望看到的方式**准确地表达你做了什么。 + +如果你不得不撒谎,你可能是在努力争取一个并不适合你的职位。没关系。调整你的期望,重新准备。 + +如果你不擅长 web 开发,请坚持在 Squarespace 或者 Wordpress 上建立你的网站。 + +**如果了解网页开发,建立你自己的网站**。我使用了 Node.js 和 Hexo。这表明,如果需要的话,我很乐意跳到其他的代码领域,这不会损害雇主的利益。 + +#### 建立强大的 LinkedIn 页面。 + +如果你认为 LinkedIn “很挫”,那你就是在和自己过不去。我就通过 LinkedIn 得到了了一些工作机会。 + +![](https://cdn-images-1.medium.com/max/600/1*cQ2mbHxy07bYePiL7I4O4Q.png) + +截至 2018 年中 + +[看看我的 LinkedIn 页面](https://www.linkedin.com/in/rob-caraway/)。你没必要成为 LinkedIn 方面的专家:我去年才开始真正地研究它。 + +要保持更新并且**有一个自己的好看的照片**。我拍了一张我满意的自拍上去。用编辑照片的 app,修修图。多练习可以让你拍出更好的照片。 + +添加特定的关键字到你的个人资料中,以助你出现在你期待的某些搜索关键词下面。 + +想象一下,如果你生活在一个不是奥斯丁这样竞争激烈的地区,你可能很快就会脱颖而出。 + +### 以聪明的方式申请工作 + +这里有一些找到 iOS 开发工作的好方法: + +* 查看 [Angel.co](https://angel.co/) 上面的工作(搜索在你的工作地和“支持远程工作”的工作) +* Google 搜索 “iOS 开发人员的工作 [首选城市]”。谷歌,Glassdoor,ZipRecruiter 和 Indeed 会弹出相关结果。 +* Google 搜索 “远程 iOS 开发” +* 检查你的 Stack Overflow 板块,做出漂亮的个人页面 +* Github 同上 +* 在 LinkedIn 上 搜索 iOS 开发者职位 +* 参加相关的技术活动 + +最后一个好地方 —— 通常城市会有一个本地的技术网站。奥斯丁有 [BuiltInAustin](https://www.builtinaustin.com/jobs)。实际上我就是通过这个板块找到了我现在工作的公司的职位。 + +> 在你的搜索中使用的关键词:移动,应用,iOS, Swift,开发者,工程师,程序员,远程,架构师,iPhone + +在你喜欢的文档应用(我用的是苹果备忘录)中记录工作列表。 + +> 记录他们的网站、他们的应用程序、他们的 glassdoor 评论以及其他的你喜欢(或不喜欢)每家公司的哪些方面。 + +找到一种你感觉可持续的申请速度。你需要足够的时间去做一些基本的准备工作。 + +我发现每周申请两到三家公司对我来说最合适,但如果你已经有了全职工作,你可能每两周甚至更慢地申请一次 —— 如果你坚持下去,那也没什么问题。 + +想想是什么让你为每个公司感到兴奋。你可能不是对他们的产品充满热情,但你喜欢他们公司的技术、文化、你可能学到的东西,或者他们帮助的人。 + +#### 写求职信 + +在做了充分研究之后,你可能会注意到一些让你喜欢上这家公司的地方。也许他们在招聘广告中特别提到的一些事情引起了你的共鸣。 + +用这些来表达为什么你是一个非常合适的人选,以及你想从他们那里得到怎样的反馈。 + +稍微放松随意一些。没有面试官愿意听让人发困的企业行话和 500 字毫无意义的独白。 + +把你对公司做笔记的时候提到的积极的方面拿出来,并提出一两件引起你注意的事。简单地用你自己的方式说一下为什么你认为自己可以胜任。 + +#### 这里有一封我用过的求职信,让我得到了一个电话面试机会: + +![](https://cdn-images-1.medium.com/max/800/1*vjgAq86vcjnwb3Wx3OkKZg.png) + +注意到它甚至有一个错字 😂 (尽管我不建议这样做) + +请注意我是如何把自己缺乏团队经验说成是一件我急于克服的事情(这是真的)。 + +像你的简历一样,记录你用过的求职信的几个版本,注意哪些有用,哪些没有用电子表格。 + +### 为编程挑战做准备 + +编程挑战是一个测试你知识和编码技能的小练习,你可以在自己的时间内(通常是在一个宽松的期限内)做。 + +编程挑战通常由一个或两个视图控制器组成,并要用到一个或两个相关技术(如网络和 core data)。 + +我不打算透露所有公司的准确的题目,但我想,即使是我申请的公司,如果有更多的应聘者做好准备,并且对公司想让应聘者知道的东西有足够的了解,公司也会很感激的。 + +#### 不说的太具体,下面是一些我遇到的几个编程挑战中所做的关键工作: + +* [AutoLayout](https://www.raywenderlich.com/125718/coding-auto-layout) 和 [Autoresizing](https://stackoverflow.com/questions/12986130/proper-autoresizingmask) 视图 +* 调整文本大小以适应不同的屏幕([Dynamic Text](https://www.raywenderlich.com/77092/text-kit-tutorial-swift)) +* 使用基本 API 进行网络请求 +* 使用 TableViews 和 CollectionViews +* 用 Core Data,UserDefaults 或存档来持久化数据 +* 知道如何使用 storyboards,也要准备好以纯代码方式编写视图和控制器 +* [Size classing](https://www.raywenderlich.com/162311/adaptive-layout-tutorial-ios-11-getting-started) +* 异步加载图片并在主线程上显示 +* 向 tableview 或 collection view 添加无限滚动 +* 将代码模块化。不要把所有东西都塞进视图控制器。了解如何构建不可变的模型和服务层对象。 + +以上这些内容也可能会出现在面试中。 + +你不可能写出完美的代码。这是可以接受的: + +> 当你写代码的时候,如果你知道代码不完美,你可以用 //TODO 或 //FIXME 来说明你将如何改进它,以向团队展示你知道你必须做的权衡。 + +别人也会看你是否有能力做出人们喜欢的产品。如果你知道如何让它超快、平滑、漂亮,即使他们没有要求(如果你也有时间),你也要去做,除非他们明确说不需要做。 + +### 如何处理结对编程挑战 + +**不是每个公司都会做这一部分,但是值得注意。** + +对于结对编程,你可能要处理你在编码挑战中创建的代码,或者处理与公司希望你编写的代码类型类似的任务。 + +不幸的是,你不能真的“伪造”这一部分。你必须相信你的直觉,因为在你不认识的人面前,你无法立即改变自己的行为。 + +> 不要紧张,在任务中要玩得开心。如果事后你觉得不太顺利,记下你能做得更好的事情。 + +如果你想练习,那就坐在你朋友旁边一起做一些项目。越多越好。 + +### 搞定面试 + +你需要准备好谈论的话题: + +* 大 O 符号。Swift/Obj-C 中的时间复杂度的例子 +* 数据结构 +* 用 Swift 创建一个 LinkedList(以防万一) +* Struct vs. Swift 的类 +* 了解 Swift 标准库数据结构是如何工作的(基本程度) +* MVC, [MVVM](https://www.raywenderlich.com/192471/design-patterns-by-tutorials-mvvm) +* 你在编程挑战中写的代码或: +* 为解决类似公司面临的问题你可能会编写的代码 +* 你的兴趣和目标与公司的目标是如何一致的 +* “你认为5年后你的职业生涯会怎样”之类的问题很可能会出现 + +对一家公司产生兴趣往往是一种“假装直到你成功”的情况。你越是研究并找出对公司有意义的贡献的方式,你的兴趣就越会“神奇地”与他们保持一致。 + +不过,不要太强迫自己 —— 那些有着糟糕的 Glassdoor 评论和零星任务的公司几乎总是你需要避开的坑。 + +我所注意到的(虽然不是绝对的规则):公司越大,面试就显得越学术。准备好应对来自大公司问题中的“陷阱”吧。 + +小公司通常会有更少的形式,因为他们不需要它。 + +#### 其他重要的准备方法: + +* 读 [Advanced Swift](https://www.objc.io/books/advanced-swift/) +* 在你的业余时间参加 [swift 在线测验](https://www.hackingwithswift.com/test) +* 阅读 [Cracking the Coding Interview](https://www.amazon.com/Cracking-Coding-Interview-Programming-Questions/dp/0984782850/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=DC92Y76B7Z8DXK6VWH9T),特别是关于数据结构和时间复杂度的部分。 + +### 最后的想法 + +找到渴望得到工作和完全不关心你得到的工作之间的平衡。 + +如果你坚持上述的过程,你会变得更好 —— 我得到的这份工作使用的简历和我刚开始找工作时投递的简历看起来完全不同。我学会了用一种更淡定的态度来处理面试。 + +让这个过程给你翅膀。每次被拒绝都会让你变得更好,所以要奖励自己的进步,而仅仅是关心你是否得到了这份工作。 + +最后,如果你住在奥斯汀地区:[InMotion Software 正在招聘](https://www.builtinaustin.com/company/inmotion-software/jobs)!:) 我和他们一起工作很开心。 + +### 学习如何制作令人惊叹的应用程序等等 + +如果你从这篇文章中有所收获,Rob Caraway 写了关于**应用程序开发、创业和建立一个伟大的开发者职业生涯的详细指南**。[在这里注册就会得到通知](http://robcaraway.com) **是他自己的想法。** + +* * * + +#### 这篇文章从哪来的 + +这个文章发表在 [Noteworthy](http://blog.usejournal.com) 上,每天都有成千上万的人来这里了解塑造我们喜爱的产品的人们和想法。 + +跟随我们的出版物去看更多的产品和设计的故事,由 [Journal](https://usejournal.com/?utm_source=usejournal.com&utm_medium=blog&utm_campaign=guest_post) 团队提供。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/a-gentle-introduction-to-react-motion.md b/TODO1/a-gentle-introduction-to-react-motion.md new file mode 100644 index 00000000000..754f82edc9b --- /dev/null +++ b/TODO1/a-gentle-introduction-to-react-motion.md @@ -0,0 +1,211 @@ +> * 原文地址:[A gentle introduction to React Motion](https://medium.com/@nashvail/a-gentle-introduction-to-react-motion-dc50dd9f2459) +> * 原文作者:[Nash Vail](https://medium.com/@nashvail?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-gentle-introduction-to-react-motion.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-gentle-introduction-to-react-motion.md) +> * 译者:[doctype233](https://github.com/doctype233) +> * 校对者:[Gavin-Gong](https://github.com/Gavin-Gong) + +# 关于 React Motion 的简要介绍 + +React 很棒,在过去的几周里,我用它玩得很开心,所以我决定尝试一下 [React Motion](https://github.com/chenglou/react-motion)。一开始 API 就让我感到有困惑和棘手,但是最终一切开始变得有意义,不过这需要时间。遗憾的是,我在网上找不到合适的 React Motion 教程,所以我决定把这篇文章写出来,不仅是作为一个开发者们的资源,也能给我自己作参考。 + +React Motion 对外暴露三个主要的组件:Motion,StaggeredMotion 和 TransitionMotion。在本教程中,我们将一起看一看 Motion 组件,之后你会发现将在这一部分花费大量时间。 + +由于这是一个 React Motion 教程,所以我将假设你有点熟悉 React 以及 ES2015。 + +我们将在使用 React Motion 重新创建[一个 Framerjs 的例子](http://framerjs.com/examples/preview/#new-tweet.framer)时探索 API。你可以在这找到代码的最终版本[在这](https://github.com/nashvail/ReactPathMenu/)。 + +![](https://cdn-images-1.medium.com/max/1600/1*kyWa60lJ2P1nGrQOSFwDag.gif) + +最终效果 + +我们首先要研究一点数学问题,但是不要担心,我会尽可能详细地解释每一个步骤。你可以直接跳过这一部分到 React.start(); 部分。 + +准备好了吗?那就开始吧... + +### Math.start(); + +我们可以把蓝色的大按钮称为——主按钮,从蓝色按钮上飞出的按钮称为——子按钮。 + +![](https://cdn-images-1.medium.com/max/2000/1*qllWMqjzSS-WNxJicsTg7A.png) + +Fig. 1 + +子按钮拥有两种位置状态,1) 子按钮均隐藏在主按钮后面的位置,2) 子按钮在主按钮周围排列成一个圆圈的位置。 + +这里就出现了数学问题,我们必须想出一种方法,在一个完美的圆中均匀地排列主按钮周围的子按钮。你可以通过试错法将这些值通过代码写死,但认真的说,谁会这么做呢?另外,一旦你找到正确的数学方法,只要你愿意你可以摆放任意多的子按钮,而他们都会自动排列自己。 + +首先让我们了解几个术语。 + +#### M_X,M_Y + +![](https://cdn-images-1.medium.com/max/1200/1*QILlqlCX5YemXpc2L301Ig.png) + +Fig. 2 + +M_X, M_Y 分别表示以主按钮为中心的 X 和 Y 坐标。(M_X, M_Y)这个点将用作计算每个子按钮的距离和方向的参考。 + +每个子按钮最初都隐藏在主按钮中心的后面,中心坐标为 M_X,M_Y。 + +#### 分离角、扇形角、飞出半径 + +![](https://cdn-images-1.medium.com/max/1600/1*H7S3us4GgfZ2-lVU7gyo2A.png) + +Fig. 3 + +飞出半径为子按钮飞出后距离主按钮的距离,其他两个词语的释义看起来不言自明。 + +还需要注意一个地方, + +> 扇形角 = (子按钮数-1) * 分离角 + +现在,我们需要设计一个函数,该函数接收子按钮(0, 1, 2, 3 …)的索引,并返回子按钮的新位置的 x 和 y 的坐标。 + +#### 基准角、索引 + +![](https://cdn-images-1.medium.com/max/1200/1*HM9Pysix_eOJjbPQ_YNPxQ.png) + +Fig. 4 + +由于通常来说三角学中的角度是从 x 轴的正方向测量的,我们将从相反的方向(右到左)开始给我们的子按钮编号。这样,以后我们就不必在每次需要子按钮的最后位置时乘以负一。 + +当我们看到它时,请注意(参见 Fig. 3) + +> 基准角 = (180 — 扇形角)/2 + +(一定程度上)。 + +#### 角 + +![](https://cdn-images-1.medium.com/max/1200/1*HV4NgkZc3HRsvSLVyPf_iA.png) + +Fig. 5 + +每个子按钮都有它自己的角度,我称之为角。这个角是计算子按钮最终位置所需的最后一条信息。 + +请注意,(参见 Fig. 3, Fig. 4) + +> 索引为 i 的子按钮的角度 = 基准角 + (i * 分离角) + +现在,一旦我们有了每个子按钮的角, + +![](https://cdn-images-1.medium.com/max/1600/1*4WLefRuCXNDKa4Zb2g6A7A.png) + +Fig. 6 + +我们就能够为每个子按钮计算 **增量 X** 和**增量 Y**。 + +请注意(参见 Fig. 2), + +> 子按钮的最终 X 轴坐标 = M_X + 增量 X + +> 子按钮的最终 Y 轴坐标 = M_Y - 增量 Y + +(我们从 M_Y 中减去增量X,因为不同于原点在左下角的一般坐标系,浏览器的原点在左上角,所以为了方便移动,你可以降低他们 y 轴坐标的值。) + +所以,这些就是我们所需要的数学方法,现在我们有两样东西:每个子按钮的初始位置(M\_X,M\_Y)和子按钮的最终位置,剩下的魔发就交由 React 来完成吧! + +### React.start(); + +在下面的关键代码中,你将会看到发生什么,点击主按钮,我们将 isOpen 的状态变量设置为 true(第85行)。一旦 isOpen 为 true,就会传递不同的子按钮的样式(第 97 行,第 66 行,第 75 行)。 + +结果: + +![](https://cdn-images-1.medium.com/max/1600/1*feVyc2Uue0mq4h0jVGW1uw.gif) + +Fig. 7 + +好的,我们在此处完成了很多操作,我们在按钮上设置子按钮的初始位置和最终位置,现在我们需要做的就是添加 React Motion 来激活在初始位置和最终位置之间的动画。 + +### React-Motion.start(); + +获取[几个参数](https://github.com/chenglou/react-motion#motion-)每个参数是可选的,但我们不关心这里的可选参数,因为我们没有做任何与这个参数有关的事情。 + +其中 一个参数是 _style_,_style_ 将作为参数传递到回调函数中,该函数包含内建的 _interpolated values_ ,然后执行它的动画。 + +(第 8 行:因为正在React中执行迭代,所以需要将一个 key 参数传递给子组件。) + +就像这样, + +即使在这样做以后,结果也不会与图 Fig. 7 有所不同,为什么这么说?好吧,我们还需要最后一步,_spring._。 + +正如前面提到的,回调函数包含内建的值,也就是说,_spring_ 帮助函数内建的值插入样式值。 + +我们需要修改 initialChildButtonStyles 和 the finalChildButtonStyles 并注意 _top_ 和 _left_ 被 _spring_ 覆盖的值。这些是仅有的改变,现在, + +![](https://cdn-images-1.medium.com/max/1600/1*vJVGoGiTF0_WWOjF4nX5yw.gif) + +Fig. 8 + +spring 可选地接收第二个参数,这是一个包含两个数字的数组 [Stiffness, damping],默认值为[170,26],这导致了上图 Fig. 8 中呈现的结果。 + +将 Stiffness 视为动画发生的速度,这不是一个非常精确的假设,只是速度越大的值越大。Dampness 是一个晃动效果参数,不过相反的,值越小,晃动效果越明显。 + +可以看看这个 + +![](https://cdn-images-1.medium.com/max/1600/1*fmPrwf2E-gy8FJ9t-c0TvQ.gif) + +[320, 8] — Fig. 9 + +![](https://cdn-images-1.medium.com/max/1600/1*cyNkSaIKitdbfkWQ5BjZkA.gif) + +[320, 17] — Fig. 10 + +我们离最终完成很近了,但是还没有。如果我们在每次下一个子按钮开始动画前添加延迟会怎样?为了达到最终效果,这正是我们需要做的,但这样做并不那么简单,我不得不把每个运动组件以数组的形式存储到状态变量中,然后一个一个地为每个子按钮改变状态以达到期望的效果,代码就像这样 + +> this.state = { +> isOpen: false, +> childButtons: [] +> }; + +然后在 componentDidMount 方法中添加 _childButtons_ + +> componentDidMount() { +> let childButtons = []; +> range(NUM_CHILDREN).forEach(index => { +> childButtons.push(this.renderChildButton(index)); +> }); + +> this.setState({childButtons: childButtons.slice(0)}); +> } + +最终打开菜单功能得以实现: + +![](https://cdn-images-1.medium.com/max/1600/1*OAwTtEZ77MFmYc5J93UWIA.gif) + +我们在这里做了一些美学的调整,如添加图标和一些旋转效果,我们得到最终效果如下。 + +![](https://cdn-images-1.medium.com/max/1600/1*kyWa60lJ2P1nGrQOSFwDag.gif) + +方法已覆盖,你可以设置任何数量的子按钮 + +![](https://cdn-images-1.medium.com/max/1600/1*CZs6nzP2gA4wYo7-7W14RQ.gif) + +NUM_CHILDREN = 1 + +![](https://cdn-images-1.medium.com/max/1600/1*ZYBIda9cB4qswqsiARS9-g.gif) + +NUM_CHILDREN = 3 + +![](https://cdn-images-1.medium.com/max/1600/1*LAGfzXC-DrjFOYJDmWAyGg.gif) + +NUM_CHILDREN = 8 + +相当酷对吗?再说一遍,你可以在[在这](https://github.com/nashvail/ReactPathMenu/blob/staggered-motion/Components/APP.js)找到相应代码。如果你觉得这篇文章有帮助,请点击下面的推荐按钮。 + +如果有一些问题、评论、建议或仅仅是想聊个天?可以在 Twitter 上找到我 [@NashVail](http://twitter.com/NashVail) 或者给我发电子邮件 [hello@nashvail.me](mailto:hello@nashvail.me)。 + +* * * + +你可能还会喜欢 + +1. [Let’s settle ‘this’ — Part One](https://medium.com/p/lets-settle-this-part-one-ef36471c7d97) +2. [Let’s settle ‘this’ — Part Two](https://medium.com/p/lets-settle-this-part-two-2d68e6cb7dba) +3. [Designing the perfect wallpaper app](https://medium.com/@nashvail/designing-the-perfect-wallpaper-app-36b8c9c226bb) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/a-minimal-guide-to-ecmascript-decorators.md b/TODO1/a-minimal-guide-to-ecmascript-decorators.md new file mode 100644 index 00000000000..d6d3a84111a --- /dev/null +++ b/TODO1/a-minimal-guide-to-ecmascript-decorators.md @@ -0,0 +1,585 @@ +> * 原文地址:[A minimal guide to ECMAScript Decorators: A short introduction to “decorators” proposal in JavaScript with basic examples and little bit about ECMAScript](https://itnext.io/a-minimal-guide-to-ecmascript-decorators-55b70338215e) +> * 原文作者:[Uday Hiwarale](https://itnext.io/@thatisuday?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-minimal-guide-to-ecmascript-decorators.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-minimal-guide-to-ecmascript-decorators.md) +> * 译者:[jonjia](https://github.com/jonjia) +> * 校对者:[coconilu](https://github.com/coconilu) [ssshooter](https://github.com/ssshooter) + +# ECMAScript 修饰器微指南 + +## JavaScript「修饰器」提案简介,包含一些基本示例和 ECMAScript 的一些示例 + +![](https://cdn-images-1.medium.com/max/2000/1*CMwgpS7hFNgPqnz62gaBqA.png) + +为什么标题是 **ECMAScript 修饰器**,而不是 **JavaScript 修饰器**?因为,[**ECMAScript**](https://en.wikipedia.org/wiki/ECMAScript) 是编写像 **JavaScript** 这种脚本语言的标准,它不强制 JavaScript 支持所有规范内容,JavaScript 引擎(不同浏览器使用不同引擎)不一定支持 ECMAScript 引入的功能,或者支持行为不一致。 + +可以将 ECMAScript 理解为我们说的**语言**,比如**英语**。那 JavaScript 就是一种方言,类似**英国英语**。方言本身就是一种语言,但它是基于语言衍生出来的。所以,ECMAScript 是烹饪/编写 JavaScript 的烹饪书,是否遵循其中所有成分/规则完全取决于厨师/开发者。 + +理论上来说,JavaScript 使用者应该遵循语言规范中所有规则(**开发者或许会疯掉吧**),但实际上新版 JavaScript 引擎很晚才会实现这些规则,开发者要确保一切正常后(才会切换)。**TC39** 也就是 ECMA 国际技术委员会第 39 号 负责维护 ECMAScript 语言规范。该团队的成员大多来自于 ECMA 国际、浏览器厂商和对 Web 感兴趣的公司。 + +由于 ECMAScript 是开放标准,任何人都可以提出新的想法或功能并对其进行处理。因此,新功能的提议将经历 4 个主要阶段,TC39 将参与此过程,直到该功能准备好发布。 + +``` ++-------+-----------+----------------------------------------+ +| stage | name | mission | ++-------+-----------+----------------------------------------+ +| 0 | strawman | Present a new feature (proposal) | +| | | to TC39 committee. Generally presented | +| | | by TC39 member or TC39 contributor. | ++-------+-----------+----------------------------------------+ +| 1 | proposal | Define use cases for the proposal, | +| | | dependencies, challenges, demos, | +| | | polyfills etc. A champion | +| | | (TC39 member) will be | +| | | responsible for this proposal. | ++-------+-----------+----------------------------------------+ +| 2 | draft | This is the initial version of | +| | | the feature that will be | +| | | eventually added. Hence description | +| | | and syntax of feature should | +| | | be presented. A transpiler such as | +| | | Babel should support and | +| | | demonstrate implementation. | ++-------+-----------+----------------------------------------+ +| 3 | candidate | Proposal is almost ready and some | +| | | changes can be made in response to | +| | | critical issues raised by adopters | +| | | and TC39 committee. | ++-------+-----------+----------------------------------------+ +| 4 | finished | The proposal is ready to be | +| | | included in the standard. | ++-------+-----------+----------------------------------------+ +``` + +现在(2018 年 6 月),**修饰器**提案正处于**第二阶段**,我们可以使用 `babel-plugin-transform-decorators-legacy` 这个 Babel 插件来转换它。在第二阶段,由于功能的语法会发生变化,因此不建议在生产环境中使用它。无论如何,修饰器都很优美,也有助于更快地完成任务。 + +从现在开始,我们要开始研究实验性的 JavaScript 了,因此你的 node.js 版本可能不支持这个新特性。所以我们需要使用 Babel 或 TypeScript 转换器。可以使用我准备的 [**js-plugin-starter**](https://github.com/thatisuday/js-plugin-starter) 插件来设置项目,其中包括了这篇文章中用到的插件。 + +* * * + +要理解修饰器,首先需要了解 JavaScript 对象属性的**属性描述符**。 **属性描述符**是对象属性的一组规则,例如属性是**可写**还是**可枚举**。当我们创建一个简单的对象并向其添加一些属性时,每个属性都有默认的属性描述符。 + +``` +var myObj = { + myPropOne: 1, + myPropTwo: 2 +}; +``` + +`myObj`是一个简单的 JavaScript 对象,在控制台中如下所示: + +![](https://cdn-images-1.medium.com/max/800/1*Y8y_yHAuU4e5qQ98328h9A.png) + +现在,如果我们像下面那样将新值写入 `myPropOne` 属性,操作可以成功,我们可以获得更改后的值。 + +``` +myObj.myPropOne = 10; +console.log( myObj.myPropOne ); //==> 10 +``` + +为了获取属性的属性描述符,我们需要使用 `Object.getOwnPropertyDescriptor(obj, propName)` 方法。这里 **Own** 的意思是只有 `propName` 属性是 `obj` 对象自有属性而不是在原型链上查找的属性时,才会返回 `propName` 的属性描述符。 + +``` +let descriptor = Object.getOwnPropertyDescriptor( + myObj, + 'myPropOne' +); + +console.log( descriptor ); +``` + +![](https://cdn-images-1.medium.com/max/800/1*_hI_shyJTWzbDzxAZRG2cw.png) + +`Object.getOwnPropertyDescriptor` 方法返回一个对象,该对象包含描述属性权限和当前状态的键。 `value` 表示属性的当前值,`writable` 表示用户是否可以为属性赋值,`enumerable` 表示该属性是否会出现在 `for in` 循环或 `for of` 循环或 `Object.keys` 等遍历方法中。`configurable` 表示用户是否有权更改**属性描述符**并更改 `writable` 和 `enumerable`。属性描述符还有 `get` 和 `set` 键,它们是获取值或设置值的中间件函数,但这两个是可选的。 + +要在对象上创建新属性或使用自定义描述符修改现有属性,我们使用 `Object.defineProperty` 方法。让我们修改 `myPropOne` 这个现有属性,`writable` 设置为 `false`,这会**禁止**向 `myObj.myPropOne` 写入值。 + + +``` +'use strict'; + +var myObj = { + myPropOne: 1, + myPropTwo: 2 +}; + +// 修改属性描述符 +Object.defineProperty( myObj, 'myPropOne', { + writable: false +} ); + +// 打印属性描述符 +let descriptor = Object.getOwnPropertyDescriptor( + myObj, 'myPropOne' +); +console.log( descriptor ); + +// 设置新值 +myObj.myPropOne = 2; +``` + +![](https://cdn-images-1.medium.com/max/800/1*OA4CAoOYemieJ9lB5wmqCg.png) + +从上面的报错中可以看出,`myPropOne` 属性是不可写入的。因此如果用户尝试给它赋予新值,就会抛出错误。 + +> 如果使用 `Object.defineProperty` 来修改现有属性的描述符,那**原始描述符**会被新的修改**覆盖**。`Object.defineProperty` 方法会返回修改后的 `myObj` 对象。 + +让我们看看如果将 `enumerable` 描述符键设置为 `false` 会发生什么。 + +``` +var myObj = { + myPropOne: 1, + myPropTwo: 2 +}; + +// 修改描述符 +Object.defineProperty( myObj, 'myPropOne', { + enumerable: false +} ); + +// 打印描述符 +let descriptor = Object.getOwnPropertyDescriptor( + myObj, 'myPropOne' +); +console.log( descriptor ); + +// 打印遍历对象 +console.log( + Object.keys( myObj ) +); +``` + +![](https://cdn-images-1.medium.com/max/800/1*Aa-unAIvyxiw3kGjIz4Ewg.png) + +从上面的结果可以看出,我们在 `Object.keys` 枚举中看不到对象的 `myPropOne` 属性。 + +使用 `Object.defineProperty` 在对象上定义新属性并传递空 `{}` 描述符时,默认描述符如下所示: + +![](https://cdn-images-1.medium.com/max/800/1*e3FZCJKiLjbMVJnFbHcKIg.png) + +现在,让我们使用自定义描述符定义一个新属性,其中 `configurable` 键设置为 `false`。我们将 `writable` 保持为`false`、`enumerable` 为 `true`,并将 `value` 设置为 `3`。 + +``` +var myObj = { + myPropOne: 1, + myPropTwo: 2 +}; + +// 设置新属性描述符 +Object.defineProperty( myObj, 'myPropThree', { + value: 3, + writable: false, + configurable: false, + enumerable: true +} ); + +// 打印属性描述符 +let descriptor = Object.getOwnPropertyDescriptor( + myObj, 'myPropThree' +); +console.log( descriptor ); + +// 修改属性描述符 +Object.defineProperty( myObj, 'myPropThree', { + writable: true +} ); +``` + +![](https://cdn-images-1.medium.com/max/800/1*QulK_GxuflHPaJ6X4UwqAA.png) + +通过将 `configurable` 设置为 `false`,我们失去了更改 `myPropThree` 属性描述符的能力。如果不希望用户操作对象的行为,这将非常有用。 + +**get**(**getter**)和 **set**(**setter**)也可以在属性描述符中设置。但是当你定义一个 getter 时,也会带来一些牺牲。你根本不能在描述符上有**初始值**或 `value`,因为 getter 将返回该属性的值。你也不能在描述符上使用 `writable`,因为你的写操作是通过 setter 完成的,可以防止写入。看看 MDN 文档关于 [**getter**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) 和 [**setter**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set),或阅读[**这篇文章**](https://codeburst.io/javascript-object-property-attributes-ac012be317e2),这里不需要太多解释。 + +> 可以使用带有两个参数的 `Object.defineProperties` 方法一次创建/更新多个属性描述符。第一个参数是**目标对象**,在其中添加/修改属性,第二个参数是一个对象,其中 `key` 为**属性名**,`value` 是它的**属性描述符**。此函数返回**目标对象。** + +你是否尝试过使用 `Object.create` 方法来创建对象?这是创建没有原型或自定义原型对象最简单方法。它也是使用自定义属性描述符从头开始创建对象的更简单方法之一。 + +`Object.create` 方法具有以下语法: + +``` +var obj = Object.create( prototype, { property: descriptor, ... } ) +``` + +这里 `prototype` 是一个对象,它将成为 `obj` 的原型。如果 `prototype` 是 `null`,那么 `obj` 将没有任何原型。使用 `var obj = {}` 语法定义空或非空对象时,默认情况下,`obj.__proto__` 指向 `Object.prototype`,因此 `obj` 具有 `Object`类的原型。 + +这类似于用 `Object.prototype` 作为第一个参数(**正在创建对象的原型**)使用 `Object.create` 方法 。 + +``` +'use strict'; + +var o = Object.create( Object.prototype, { + a: { value: 1, writable: false }, + b: { value: 2, writable: true } +} ); + +console.log( o.__proto__ ); +console.log( + 'o.hasOwnProperty( "a" ) => ', + o.hasOwnProperty( "a" ) +); +``` + +![](https://cdn-images-1.medium.com/max/800/1*Fc2_huyI1qxhEif4E9wHRw.png) + +但当我们把 **prototype** 参数设置为 `null` 时,会出现下面的错误: + +``` +'use strict'; + +var o = Object.create( null, { + a: { value: 1, writable: false }, + b: { value: 2, writable: true } +} ); + +console.log( o.__proto__ ); +console.log( + 'o.hasOwnProperty( "a" ) => ', + o.hasOwnProperty( "a" ) +); +``` + +![](https://cdn-images-1.medium.com/max/800/1*JOvcTkY5uzgrjlOBhz0QtQ.png) + +* * * + +#### ✱ 类方法修饰器 + +现在我们已经了解了如何定义/配置对象的新属性/现有属性,让我们把注意力转移到修饰器以及为什么讨论属性描述符上。 + +修饰器是一个 JavaScript 函数(**建议是纯函数**),它用于修改类属性/方法或类本身。当你在**类属性**、**方法**或**类本身**顶部添加 `@decoratorFunction` 语法后,`decoratorFunction` 方法会以一些参数**被调用**,然后**就可以使用这些参数来修改类或类属性了**。 + +让我们创建一个简单的 `readonly `修饰器函数。但在此之前,先创建一个包含 `getFullName` 方法简单的 `User` 类,这个方法通过组合 `firstName` 和 `lastName` 返回用户的全名。 + +``` +class User { + constructor( firstname, lastName ) { + this.firstname = firstname; + this.lastName = lastName; + } + + getFullName() { + return this.firstname + ' ' + this.lastName; + } +} + +// 创建实例 +let user = new User( 'John', 'Doe' ); +console.log( user.getFullName() ); +``` + +运行上面的代码,控制台中会打印出 `John Doe`。但这样有一个问题:任何人都可以修改 `getFullName` 方法。 + +``` +User.prototype.getFullName = function() { + return 'HACKED!'; +} +``` + +经过上面的修改,就会得到以下输出: + +``` +HACKED! +``` + +为了限制修改我们任何方法的权限,需要修改 `getFullName` 方法的属性描述符,这个属性属于 `User.prototype` 对象。 + +``` +Object.defineProperty( User.prototype, 'getFullName', { + writable: false +} ); +``` + +现在,如果还有用户尝试覆盖 `getFullName` 方法,他/她就会得到下面的错误。 + +![](https://cdn-images-1.medium.com/max/800/1*UVOaz8O1FoSa7KVpIBFMxA.png) + +但如果 `User` 类有很多方法,上面这种手动修改就不太好了。这就是修饰器的用武之地了。通过在 `getFullName` 方法上添加 `@readonly` 也可以实现同样功能,如下: + +``` +function readonly( target, property, descriptor ) { + descriptor.writable = false; + return descriptor; +} + +class User { + constructor( firstname, lastName ) { + this.firstname = firstname; + this.lastName = lastName; + } + + @readonly + getFullName() { + return this.firstname + ' ' + this.lastName; + } +} + +User.prototype.getFullName = function() { + return 'HACKED!'; +} +``` + +看一下 `readonly` 函数。它接收三个参数。`property` 是属性/方法的名字,`target` 是这些属性/方法属于的对象(**就和 `User.prototype` 一样**),`descriptor` 是这个属性的描述符。在修饰器函数中,我们必须返回 `descriptor` 对象。这个修改后的 `descriptor` 会替换该属性原来的属性描述符。 + +修饰器写法还有另一种版本,类似 `@decoratorWrapperFunction( ...customArgs )` 这样。但这样写,`decoratorWrapperFunction` 函数应该返回一个 `decoratorFunction` 修饰器函数,它的使用和上面的例子相同。 + +``` +function log( logMessage ) { + // 返回修饰器函数 + return function ( target, property, descriptor ) { + // 保存属性原始值,它是一个方法(函数) + let originalMethod = descriptor.value; + // 修改方法实现 + descriptor.value = function( ...args ) { + console.log( '[LOG]', logMessage ); + // 这里,调用原始方法 + // `this` 指向调用实例 + return originalMethod.call( this, ...args ); + }; + return descriptor; + } +} +class User { + constructor( firstname, lastName ) { + this.firstname = firstname; + this.lastName = lastName; + } + @log('calling getFullName method on User class') + getFullName() { + return this.firstname + ' ' + this.lastName; + } +} +var user = new User( 'John', 'Doe' ); +console.log( user.getFullName() ); +``` + +![](https://cdn-images-1.medium.com/max/800/1*sUHsV_OSQUSehgfblsYvRg.png) + +修饰器不区分静态和非静态方法。下面的代码同样可以工作,唯一不同是你如何访问这些方法。这个结论也适用于我们下面要讨论的**类实例字段修饰器**。 + +``` +@log('calling getVersion static method of User class') +static getVersion() { + return 'v1.0.0'; +} + +console.log( User.getVersion() ); +``` + +* * * + +#### ✱ **类实例字段修饰器** + +目前为止,我们已经看到通过 `@decorator` 或 `@decorator(..args)` 语法来修改类方法的属性描述符,但如何修改 **公有/私有属性(类实例字段)**呢? + +与 `typescript` 或 `java` 不同,JavaScript 类**没有**类实例字段或者说没有类属性。这是因为任何在 `class` 里面、`constructor` 外面定义的都属于类的**原型**。但也有一个新的[**提案**](https://github.com/tc39/proposal-class-fields),它提议使用 `public` 和 `private` 访问修饰符来启用类实例字段,目前处于[第 3 阶段](https://github.com/tc39/proposals),也可以通过 [**babel transformer plugin**](https://babeljs.io/docs/plugins/transform-class-properties/) 这个插件来使用它。 + +定义一个简单的 `User` 类,但这一次,不需要在构造函数中设置 `firstName` 和 `lastName` 的默认值。 + +``` +class User { + firstName = 'default_first_name'; + lastName = 'default_last_name'; + constructor( firstName, lastName ) { + if( firstName ) this.firstName = firstName; + if( lastName ) this.lastName = lastName; + } + getFullName() { + return this.firstName + ' ' + this.lastName; + } +} +var defaultUser = new User(); +console.log( '[defaultUser] ==> ', defaultUser ); +console.log( '[defaultUser.getFullName] ==> ', defaultUser.getFullName() ); +var user = new User( 'John', 'Doe' ); +console.log( '[user] ==> ', user ); +console.log( '[user.getFullName] ==> ', user.getFullName() ); +``` + +![](https://cdn-images-1.medium.com/max/800/1*44yA-f6PZURlQ-FOf4Vrww.png) + +现在,如果查看 `User` 类的原型,你不会看到 `firstName` 和 `lastName` 这两个属性。 + +![](https://cdn-images-1.medium.com/max/800/1*pUvV2kP_Evs0JWbhYK-KFg.png) + +**类实例字段**非常有用,还是面向对象编程(**OOP**)的重要组成部分。我们提出相应的提案很好,但故事远未结束。 + +与**类方法处于类的原型上**不同,**类实例字段处于对象/实例上**。由于类实例字段既不是类的一部分也不是它原型的一部分,因此操作它的描述符有点困难。Babel 为类实例字段的属性描述符提供了 `initializer` 方法来替代 `value`。为什么要用 `initializer` 方法来替代 `value` 呢?这个问题有些争议,因为修饰器提案还处于**第二阶段**,还没有发布最终草案来说明这个问题,但你可以通过查看 [**Stack Overflow 上这个答案**](https://stackoverflow.com/questions/31433630/does-the-es7-decorator-spec-require-descriptors-to-have-an-initializer-method) 来了解背景故事。 + +也就是说,让我们修改之前示例并创建简单的 `@upperCase` 修饰器函数,它会改变类实例字段默认值的大小写。 + +``` +function upperCase( target, name, descriptor ) { + let initValue = descriptor.initializer(); + descriptor.initializer = function(){ + return initValue.toUpperCase(); + } + return descriptor; +} +class User { + + @upperCase + firstName = 'default_first_name'; + + lastName = 'default_last_name'; + constructor( firstName, lastName ) { + if( firstName ) this.firstName = firstName; + if( lastName ) this.lastName = lastName; + } + getFullName() { + return this.firstName + ' ' + this.lastName; + } +} +console.log( new User() ); +``` + +![](https://cdn-images-1.medium.com/max/800/1*5_SX5itRYtBIojyjY7-wHQ.png) + +我们也可以使用**带参数的修饰器函数**,让它更有定制性。 + +``` +function toCase( CASE = 'lower' ) { + return function ( target, name, descriptor ) { + let initValue = descriptor.initializer(); + + descriptor.initializer = function(){ + return ( CASE == 'lower' ) ? + initValue.toLowerCase() : initValue.toUpperCase(); + } + + return descriptor; + } +} +class User { + @toCase( 'upper' ) + firstName = 'default_first_name'; + lastName = 'default_last_name'; + constructor( firstName, lastName ) { + if( firstName ) this.firstName = firstName; + if( lastName ) this.lastName = lastName; + } + getFullName() { + return this.firstName + ' ' + this.lastName; + } +} +console.log( new User() ); +``` + +`descriptor.initializer` 方法由 **Babel** 内部实现对象属性描述符的 `value` 的创建。它会返回分配给类实例字段的初始值。在修饰器函数内部,我们需要返回另一个 `initializer` 方法,它会返回最终值。 + +> 类实例字段提案具有高度实验性,在到达**第 4 阶段**前,它的语法很有可能会改变。因此,将类实例字段与修饰器一起使用还不是一个好习惯。 + +* * * + +#### ✱ 类修饰器 + +现在我们已经熟悉了修饰器能做什么。它可以改变属性、类方法行为和类实例字段,使我们能灵活地通过简单的语法来实现这些。 + +**类修饰器**和我们之前看到的修饰器有些不同。之前,我们使用**属性修饰器**来修改属性或方法的实现,但类修饰器函数中,我们需要返回一个构造函数。 + +我们先来理解下什么是构造函数。在下面,一个 JavaScript 类只不过是一个函数,这个函数添加了**原型方法**、定义了一些初始值。 + +``` +function User( firstName, lastName ) { + this.firstName = firstName; + this.lastName = lastName; +} +User.prototype.getFullName = function() { + return this.firstName + ' ' + this.lastName; +} +let user = new User( 'John', 'Doe' ); +console.log( user ); +console.log( user.__proto__ ); +console.log( user.getFullName() ); +``` + +![](https://cdn-images-1.medium.com/max/800/1*8upRjd8kwXbOntVmrjvOqg.png) + +> [这篇文章](https://blog.bitsrc.io/what-is-this-in-javascript-3b03480514a7) 对理解 JavaScript 中的 `this` 很有帮助。 + +因此,当我们调用 `new User` 时,就会使用传递的参数调用 `User` 这个函数,返回结果是一个对象。所以,`User` 就是一个构造函数。顺便说一句,JavaScript 中每个函数都是一个构造函数,因为如果你查看 `function.prototype`,你会发现 `constructor` 属性。只要我们使用 `new` 关键字调用函数,都会得到一个对象。 + +>如果从构造函数返回一个有效的 JavaScript 对象,那么就会使用这个对象,而不用 `this` 赋值创建新对象了。这将打破原型链,因为修改后的对象将不具有构造函数的任何原型方法。 + +考虑到这一点,让我们看看类修饰器可以做什么。类修饰器必须位于类的顶部,就像之前我们在方法名或字段名上看到的修饰器一样。这个修饰器也是一个函数,但它应该返回构造函数或类。 + +假设我有一个简单的 `User` 类如下: + +``` +class User { + constructor( firstName, lastName ) { + this.firstName = firstName; + this.lastName = lastName; + } +} +``` + +这里的 `User` 类不包含任何方法。正如上面所说,类修饰器应该返回一个构造函数。 + +``` +function withLoginStatus( UserRef ) { + return function( firstName, lastName ) { + this.firstName = firstName; + this.lastName = lastName; + this.loggedIn = false; + } +} +@withLoginStatus +class User { + constructor( firstName, lastName ) { + this.firstName = firstName; + this.lastName = lastName; + } +} +let user = new User( 'John', 'Doe' ); +console.log( user ); +``` + +![](https://cdn-images-1.medium.com/max/800/1*rM3KBl5wFGoMNkq3DDFrgg.png) + +类修饰器函数会接收目标类 `UserRef`,在上面的示例中是 `User`(**修饰器的作用目标**)并且必须返回构造函数。这打开了使用修饰器无限可能性的大门。因此,类修饰器比方法/属性修饰器更受欢迎。 + +但是上面的例子太基础了,当我们的 `User` 类有大量的属性和原型方法时,我们不想创建一个新的构造函数。好消息是,我们在修饰器函数中可以引用类,即 `UserRef`。可以从构造函数返回新类,该类将扩展 `User` 类(`UserRef` 指向的类)。因为,类也是构造函数,所以下面的代码也是合法的。 + +``` +function withLoginStatus( UserRef ) { + return class extends UserRef { + constructor( ...args ) { + super( ...args ); + this.isLoggedIn = false; + } + setLoggedIn() { + this.isLoggedIn = true; + } + } +} +@withLoginStatus +class User { + constructor( firstName, lastName ) { + this.firstName = firstName; + this.lastName = lastName; + } +} +let user = new User( 'John', 'Doe' ); +console.log( 'Before ===> ', user ); +// 设置为已登录 +user.setLoggedIn(); +console.log( 'After ===> ', user ); +``` + +![](https://cdn-images-1.medium.com/max/800/1*uWCbna4Q89ZWCz5Xmv5Hdg.png) + +* * * + +你可以将多个修饰器放在一起,执行顺序和它们外观顺序一致。 + +* * * + +修饰器是更快地达到目的的奇特方式。在它们正式加入 ECMAScript 规范之前,我们先期待一下吧。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/a-quick-beginners-guide-to-drawing.md b/TODO1/a-quick-beginners-guide-to-drawing.md new file mode 100644 index 00000000000..1b88d99fea4 --- /dev/null +++ b/TODO1/a-quick-beginners-guide-to-drawing.md @@ -0,0 +1,163 @@ +> * 原文地址:[A quick beginner’s guide to drawing: 6 drawing exercises to get you started right now!](https://medium.com/personal-growth/a-quick-beginners-guide-to-drawing-58213877715e) +> * 原文作者:[Ralph Ammer](https://medium.com/@ralphammer?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-quick-beginners-guide-to-drawing.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-quick-beginners-guide-to-drawing.md) +> * 译者:[wzasd](https://github.com/wzasd) +> * 校对者:[LeeSniper](https://github.com/LeeSniper)、[liruochen1998](https://github.com/liruochen1998) + +# 绘图技巧的快速入门 + +6 个绘图练习,让你立即上手! + +![](https://cdn-images-1.medium.com/max/800/1*rUnTj2M6B-pWZAGEJzUXIQ.gif) + +**绘图的基本功**有两个必须掌握的东西:学习**控制你的手**和**眼睛**。 + +小贴士:针对以下的 6 个练习,我建议您**用一支笔和特定的类型的纸**(例如 A5)进行练习。 + +### 灵巧性 — 两项训练 + +前两个练习是关于如何控制您的手。我们想去**建立你的肌肉记忆**并且训练您的**眼和手的协调性**。像这样的机械训练是一个很好的开始。后期您可能会使用这种方法来**尝试新的笔**,或者**当您不知道要绘制什么的时候来进行训练**。 + +当然,它们也是放松心情的好办法。 + +#### 练习 1:画圈 — 画的越多越好 + +> **在一张纸上画各种尺寸的圆圈直到纸被填满。确保圆圈不会重叠。** + +![](https://cdn-images-1.medium.com/max/800/1*4BoggbHjC0_6xxm9Giq7jA.gif) + +绘制圆圈[并不是你想象中的那么容易](https://medium.com/personal-growth/why-perfection-is-boring-1079cb3bf5d1)。注意到没有当你画的圆越大,想要画的圆润就会越困难?尝试在两个方向绘制 - 并不断画出他们。 + +小贴士:当手开始抽筋的时候**甩手**!毕竟,这是关于我们的手的锻炼。 + +![](https://cdn-images-1.medium.com/max/800/1*Ry2NnFZaPWmrsO2QiuJP9A.gif) + +#### 练习 2:镶嵌 — 结构的乐趣 + +> **用平行线充满一张纸。** + +![](https://cdn-images-1.medium.com/max/800/1*ZyZQbd50EXj65XdE8RirNg.gif) + +对角线对我们来说是最简单的因为**它们符合我们手腕的运动**。你有没有注意到左撇子比右撇子更喜欢相反的方向?看看你最喜欢的绘图设计师(对我来说:[Leonardo](https://en.wikipedia.org/wiki/Leonardo_da_Vinci))的图纸,并猜测他们使用的哪只手! + +![](https://cdn-images-1.medium.com/max/800/1*3FcbNajSFhjajSCEluEJFg.gif) + +**现在请确保尝试绘制其他方向的线**。玩的愉快!组合各种不同的影线,欣赏纸上的明暗度。 + +小贴士:**请勿旋转画纸**。这个训练的要点是让你的手适应各个方向。 + +**现在我们已经对手进行了训练,让我们开始训练我们的眼睛!** + +### 感知力 — 学习去看 + +绘画的重点是[**看到并且理解你所看到的东西**](https://medium.com/personal-growth/stop-taking-pictures-and-start-drawing-b1642aded2b6)。人们经常认为 每个人看到的东西都是相同的,但实际看是一种可以提升的技巧。**你画的越多,看到的就越多。**所以下面的四个训练**将会使你看到更多**。 + +#### 训练 3:轮廓 — 向我展示你的手 + +> **你看到你手上所有迷人的线条轮廓吗?把它们收集在一张纸上!不要试图画出整个手,只需要挑出一些可爱的线条部位。** + +![](https://cdn-images-1.medium.com/max/800/1*cZ2zA0W-UhXNDrbrghC6ww.gif) + +无论你是画一个人、一棵植物还是你最喜欢的动物,通常来说都是定义一个身体或者物体的**轮廓**,并让其他人看出来是什么。挑战的难点不是要画出这些独特的线条,而是首先要**看到它们**; + +**即使你认为你已经知道一个物体的形状,但总是值得你去仔细观察并重新审视它**。 + +#### 练习 4:明暗关系 — 折叠光与暗 + +> **排列并且画一块布。从轮廓开始,然后 - 使用您的镶嵌技能 - 创造光与暗的相互关系。** + +![](https://cdn-images-1.medium.com/max/800/1*573JHUFPYcHCIa-Ai6_1EQ.gif) + +这个练习是给你对光和暗的感觉。我不得不承认这并不简单,也可能成为高级教程的一部分。请记住:**这一个并不是完全的“正确”。**这一块布就是一个**背景**,可以尝试你以前练过各种不同的图案,并感受如何用你手绘的光线和阴影。 + +小贴士:您可以使用**弯曲阴影**来调整形状,并使用阴影线实现与**交叉线**来模拟较暗区域。 + +![](https://cdn-images-1.medium.com/max/800/1*jiVJAU_YuHy_f4zJg47ORg.gif) + +小贴士:当你看到布的时候,请闭上眼睛。你会看的很模糊,但你也会**看到光与暗之间的强烈对比**。 + +![](https://cdn-images-1.medium.com/max/800/1*oUcRLSSkj9vtMZDxLc1Ivw.gif) + +光线的排列是对于图片内**重要内容展示**的最好方法。只要看看 [Rembrandt](https://de.wikipedia.org/wiki/Rembrandt_van_Rijn) 和 [Georges de la Tour](https://en.wikipedia.org/wiki/Georges_de_La_Tour) 的绘画。下一次你看电影时会注意到光阴带来的喜剧效果。 + +#### 练习 5:透视 — 消失的空间 + +> **让我们画一些立方体!只需要按照下面的简单步骤。** + +![](https://cdn-images-1.medium.com/max/800/1*7SGLqdcPGZUuDty_3ozYvg.gif) + +透视图基本上是** 3D 环境在 2D 表面上(您的纸)的投影**。 + +![](https://cdn-images-1.medium.com/max/800/1*dpjQq5D0nfEYIYlKsNVGYw.gif) + +构建透视图是一门科学,不能在一篇文章的关注范围内详细介绍。不过,我们可以通过一种**简单的技术**获得一些乐趣,让我们直观地感受透视图的魔力: + +**步骤 1:** 画一条水平线,这就是你照片的地平线。 + +![](https://cdn-images-1.medium.com/max/800/1*HBIymxEYZI2x3I3s_e_IDw.gif) + +**步骤 2:** 在纸张边缘附近的地平线定义两个点。这就是你的两个**消失点**。 + +![](https://cdn-images-1.medium.com/max/800/1*1uFonMBvFQ3eNL9e7BcGGw.gif) + +**步骤 3:** 在某处绘制垂直线。 + +![](https://cdn-images-1.medium.com/max/800/1*b2GHhfd_-4XHLggXP0ZxDg.gif) + +**步骤 4:** 将垂直线的端点连接到消失点。 + +![](https://cdn-images-1.medium.com/max/800/1*IogkqeVs_51JOG2El6l46A.gif) + +**步骤 5:** 像这样添加两条垂直线。 + +![](https://cdn-images-1.medium.com/max/800/1*b4uEYTxOtx91ilLdFD-vQQ.gif) + +**步骤 6:** 将他们与消失点连接起来。 + +![](https://cdn-images-1.medium.com/max/800/1*VpcqF0gbtjKS2HzA9cvReg.gif) + +**步骤 7:** 现在用一直更深颜色的铅笔或者钢笔来强调立方体。瞧! + +![](https://cdn-images-1.medium.com/max/800/1*cAuyUJ969E81XvR_p9u4qg.gif) + +**重复步骤 3-7 如果你喜欢的话。** 玩的开心!如果你喜欢挑战,你甚至可能会镶嵌到立方体的边缘。 + +![](https://cdn-images-1.medium.com/max/800/1*bP1gGRnZ4YFgbp3pRSPt_g.gif) + +小贴士:当你画出相遇的线条时,**让他们交叉在一起**通常是一个好主意。这样的形状看起来更好一些。 + +![](https://cdn-images-1.medium.com/max/800/1*GSak0juhukCviIpl9vwH_A.gif) + +掌握透视图会使你能看见深度构图的力量。但是最重要的是,**你教会你的大脑如何三维的思考**。因此,即使您选择绘制“平面”图或者混淆视角的“规则” - 我喜欢这样做 - **理解透视图仍然是您可以掌握的最珍贵的绘图技能之一**。 + +#### 练习 6: 构图 — 这个为什么要放在那? + +> **制作 5 个关于一个物体的不同绘图。每次将这个物体进行不同的排列**。 + +![](https://cdn-images-1.medium.com/max/800/1*g4mywtKL2Gvc4H5gMcfKEA.gif) + +构图是一种很棒的工具,**用来表达某些东西**,以**塑造其意义或者信息**。 + +为了理解它是如何工作的,我们必须牢记,我们的感知已经被**日常的经验所固定**。例如,水平线和垂直线对于我们来说似乎比对角线更“稳定”,对角线可能会在任何时候“倒下”。当我们在底部看到一个很大的阴影时,我们莫名认为它一定会很“沉重”。 + +![](https://cdn-images-1.medium.com/max/800/1*vl77WjyBwGQO5DDvU8X8YQ.gif) + +当你在一张纸上试着对你的主题进行不同的安排时,注意这是如何改变它们的内涵 - 它们的含义。 + +![](https://cdn-images-1.medium.com/max/800/1*iUHuYpv1cxvWUu-zICyKdQ.gif) + +#### 让我知道你的想法! + +由于这是我的第一个绘图教程,我很好奇你最喜欢哪个部分。关于绘画你还有什么想了解的?**请在下面的评论部分留下您的想法和建议!** + +**哦,这是第二部分:“[**另外 5 个绘画练习**](https://github.com/xitu/gold-miner/blob/master/TODO1/5-more-drawing-exercises.md)”!** + +**如果你喜欢这篇文章,请点击下面这些小👏(您可以多次“拍手”!)并把这篇文章分享给你的朋友,和[**订阅我的邮箱**](http://eepurl.com/cJJLR1),你可以在[这里](http://ralphammer.com/writing)找到我的文章。** + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/a-quick-introduction-to-functional-javascript.md b/TODO1/a-quick-introduction-to-functional-javascript.md new file mode 100644 index 00000000000..467d4f5f2de --- /dev/null +++ b/TODO1/a-quick-introduction-to-functional-javascript.md @@ -0,0 +1,319 @@ +> * 原文地址:[A Quick Introduction to Functional Javascript](https://hackernoon.com/a-quick-introduction-to-functional-javascript-7e6fe520e7fa) +> * 原文作者:[Angelos Chalaris](https://hackernoon.com/@chalarangelo?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-quick-introduction-to-functional-javascript.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-quick-introduction-to-functional-javascript.md) +> * 译者:[Zheng7426](https://github.com/Zheng7426) +> * 校对者:[AmyFoxFN](https://github.com/AmyFoxFN) + +# 函数式 JavaScript 快速入门 + +**函数式编程**是目前最热门的趋势之一,有很多好的论点解释了人们为什么想在代码中使用它。我并不打算在这里详细介绍所有函数式编程的概念和想法,而是会尽力给你演示在日常情况下和 **JavaScript** 打交道的时候如何用上这种编程。 + +![](https://cdn-images-1.medium.com/max/2000/1*w91eh65v6nxTs2AhLhyRNA.jpeg) + +> 函数式编程是一种编程范例,它将计算机运算视为数学上的函数计算,并且避免了状态的改变和易变的数据。 + +#### 重新定义函数 + +在深入接触 JavaScript 的函数式编程范例之前,咱们得先知道什么是**高阶函数**、它的用途以及这个定义本身究竟有什么含义。高阶函数既可以把函数当成参数来接收,也可以作为把函数作为结果输出。你需要记住 **函数其实也是一种值**,也就是说你可以像传递变量一样去传递函数。 + +所以呢,在 JavaScript 里你可以这么做: + +``` +// 创建函数 +function f(x){ + return x * x; +} +// 调用该函数 +f(5); // 25 + +// 创建匿名函数 +// 并赋值一个变量 +var g = function(x){ + return x * x; +} +// 传递函数 +var h = g; +// And use it +h(5); // 25 +``` + +把函数当成值来使用 + +一旦使用上面这个技巧,你的代码更容易被重复利用,同时功能也更加强大。咱们都经历过这样的情况:想要把一个函数传到另一个函数里去执行任务,但需要写一些额外的代码来实现这一点,对吧?使用函数式编程的话,你将不再需要写额外的代码,并且可以使你的代码变得很干净、易于理解。 + +* [**为什么函数式编程很重要**:在面试软件工程师的时候测验他们的函数式编程能力为何会对你的企业有好处](https://hackernoon.com/why-functional-programming-matters-c647f56a7691) + +有一点要注意,正确的泛函代码的特点是**没有副作用**,也就是说函数应该只依赖于它们的参数输入,并且不应以任何方式影响到外界环境。这个特点有重要的含义,举个例子:如果传递进函数的参数相同,那么输出的结果也总是相同的;如果一个被调用的函数所输出的结果并没有被用到,那么这个结果即使被删掉也不会影响别的代码。 + +* * * + +#### 使用数组原型的内置方法 + +[`Array.prototype`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype) 应该是你学习 JavaScript 函数式编程的第一步,它涵盖了很多**数组转化**的实用方法,这些方法在现代网页应用里相当的常见。 + +* [**Array.prototype**: Array.prototype 属性表示数组构造函数的原型,并允许你添加新属性……](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype) + +先来看看这个叫 [`Array.prototype.sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 的方法会很不错,因为这个转化挺直白的。顾名思义,咱可以用这个方法来**给数组排序**。`.sort()` 只接收一个参数(即一个用于比较两个元素的函数)。如果第一个元素在第二个元素的前面,结果返回的是负值。反之,则返回正值。 + +排序听起来非常简单,然而当你需要给比一般数字数组复杂得多的数组排序时,可能就不那么简单了。在下面这个例子里,我们有一个对象的数组,里面存的是以磅(**lbs**)或千克(**kg**)为单位的体重,咱们需要对这些人的体重进行升序排列。代码看起来会是这样: + +``` +// 咱们这个比较函数的定义 +var sortByWeight = function(x,y){ + var xW = x.measurement == "kg" ? x.weight : x.weight * 0.453592; + var yW = y.measurement == "kg" ? y.weight : y.weight * 0.453592; + return xW > yW ? 1 : -1; +} + +// 两组数据有细微差别 +// 要根据体重来对它们进行排序 +var firstList = [ + { name: "John", weight: 220, measurement: "lbs" }, + { name: "Kate", weight: 58, measurement: "kg" }, + { name: "Mike", weight: 137, measurement: "lbs" }, + { name: "Sophie", weight: 66, measurement: "kg" }, +]; +var secondList = [ + { name: "Margaret", weight: 161, measurement: "lbs", age: 51 }, + { name: "Bill", weight: 76, measurement: "kg", age: 62 }, + { name: "Jonathan", weight: 72, measurement: "kg", age: 43 }, + { name: "Richard", weight: 74, measurement: "kg", age: 29 }, +]; + +// 用开头定义的函数 +// 对两组数据进行排序 +firstList.sort(sortByWeight); // Kate, Mike, Sophie, John +secondList.sort(sortByWeight); // Jonathan, Margaret, Richard, Bill +``` + +用函数式编程来对两个数组进行排序的例子 + +在上面的例子里,你可以很清楚地观察到使用高阶函数带来的好处:节省了空间、时间,也让你的代码更能被读懂、更容易被重复利用。如果你不打算用 `.sort()` 来写的话,你得另外写两个循环并重复大部分的逻辑。坦率来说,那样将导致更冗长、臃肿且不易理解的代码。 + +* * * + +通常你对数组的操作也不单只是排序而已。就我的经验而言,根据属性来**过滤一个数组**很常见,而且没有什么方法比 [`Array.prototype.filter()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) 更加合适。过滤数组并不困难,因为你只需将一个函数作为参数,对于那些需要被过滤掉的元素,该函数会返回 `false`。反之,该函数会返回 `true`。很简单,不是吗?咱们来看看实例: + +``` +// 一群人的数组 +var myFriends = [ + { name: "John", gender: "male" }, + { name: "Kate", gender: "female" }, + { name: "Mike", gender: "male" }, + { name: "Sophie", gender: "female" }, + { name: "Richard", gender: "male" }, + { name: "Keith", gender: "male" } +]; + +// 基于性别的简易过滤器 +var isMale = function(x){ + return x.gender == "male"; +} + +myFriends.filter(isMale); // John, Mike, Richard, Keith +``` + +关于过滤的一个简单例子 + +虽然 `.filter()` 会返回数组中所有符合条件的元素,你也可以用 [`Array.prototype.find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) 提取数组中第一个符合条件的元素,或是用 [`Array.prototype.findIndex()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) 来提取数组中第一个匹配到的元素索引。同理,你可以使用 [`Array.prototype.some()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) 来测试是否至少有一个元素符合条件,抑或是用 [`Array.prototype.every()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) 来检查是否所有的元素都符合条件。这些方法在某些应用中可以变得相当有用,所以咱们来看一个囊括了这几种方法的例子: + +``` +// 一组关于分数的数组 +// 不是每一项都标注了人名 +var highScores = [ + {score: 237, name: "Jim"}, + {score: 108, name: "Kit"}, + {score: 91, name: "Rob"}, + {score: 0}, + {score: 0} +]; + +// 这些简单且能重复使用的函数 +// 是用来查看每一项是否有名字 +// 以及分数是否为正数 +var hasName = function(x){ + return typeof x['name'] !== 'undefined'; +} +var hasNotName = function(x){ + return !hasName(x); +} +var nonZeroHighScore = function(x){ + return x.score != 0; +} + +// 填充空白的名字,直到所有空白的名字都有“---” +while (!highScores.every(hasName)){ + var highScore = highScores.find(hasNotName); + highScore.name = "---"; + var highScoreIndex = highScores.findIndex(hasNotName); + highScores[highScoreIndex] = highScore; +} + +// 检查非零的分数是否存在 +// 并在 console 里输出 +if (highScores.some(nonZeroHighScore)) + console.log(highScores.filter(nonZeroHighScore)); +else + console.log("No non-zero high scores!"); +``` + +使用函数式编程来构造数据 + +到这一步,你应该会有些融会贯通的感觉了。上面的例子清楚地体现出高阶函数是如何使你避免了大量重复且难以理解的代码。这个例子虽然简单,但你也能看出代码的简洁之处,与你在未使用函数式编程范例时所编写的内容形成鲜明对比。 + +* * * + +先撇开上面例子里复杂的逻辑,咱们有的时候只想要**将数组转化成另一个数组**,且无需对数组里的数据做那么多的改变。这个时候 [`Array.prototype.map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) 就派上用场了,我们可以用这个方法来转化数组中的对象。`.map()`和之前例子所用到的方法并不相同,区别在于其作为参数的高阶函数会返回一个对象,可以是任何你想写的对象。让我用一个简单的例子来演示一下: + +``` +// 一个有 4 个对象的数组 +var myFriends = [ + { name: "John", surname: "Smith", age: 52}, + { name: "Sarah", surname: "Smith", age: 49}, + { name: "Michael", surname: "Jones", age: 46}, + { name: "Garry", surname: "Thomas", age: 48} +]; + +// 一个简单的函数 +// 用来把名和姓放在一起 +var fullName = function(x){ + return x.name + " " + x.surname; +} + +myFriends.map(fullName); +// 应输出 +// ["John Smith", "Sarah Smith", "Michael Jones", "Garry Thomas"] +``` + +对数组里的对象进行 mapping 操作 + +从上面这个例子可以看出,一旦对数组使用了 `.map()` 方法,很容易就能得到一个仅包含咱们所需属性的数组。在这个例子里,咱只想要对象中 `name` 和 `surname` 这两行字符串,所以才使用简单的 mapping(译者注:即使用 map 方法) 来利用原来包含很多对象的数组上创建了另一个只包含字符串的数组。Mapping 这种方式可能比你想象的还要常用,它在每个网页开发者的口袋中可以成为很强大的工具。所以说,这整篇文章里你如果别的没记住的话,没关系,但千万要记住如何使用 `.map()`。 + +* * * + +最后还有一点非常值得你注意,那就是**常规目的数组转化**中的 [`Array.prototype.reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)。`.reduce()` 与上面提到的所有方法都有所不同,因为它的参数不仅仅是一个高阶函数,还包含一个**累加器**。一开始听起来可能有些令人困惑,所以先看一个例子来帮助你理解 `.reduce()` 背后的基础概念吧: + +``` +// 关于不同公司支出的数组 +var oldExpenses = [ + { company: "BigCompany Co.", value: 1200.10}, + { company: "Pineapple Inc.", value: 3107.02}, + { company: "Office Supplies Inc.", value: 266.97} +]; +var newExpenses = [ + { company: "Office Supplies Inc.", value: 108.11}, + { company: "Megasoft Co.", value: 1208.99} +]; + +// 简单的求和函数 +var sumValues = function(sum, x){ + return sum + x.value; +} + +// 将第一个数组降为几个数值之和 +var oldExpensesSum = oldExpenses.reduce(sumValues, 0.0); +// 将第二个数组降为几个数值之和 +console.log(newExpenses.reduce(sumValues, oldExpensesSum)); // 5891.19 +``` + +将数组降为和值 + +对于任何曾经把数组中的值求和的人来说,理解上面这个例子应该不会特别困难。一开始咱们定义了一个可重复使用的高阶函数,用于把数组中的 value 都加起来。之后,咱们用这个函数来给第一个数组中的支出数值求和,并把求出来的值当成初始值,而不是从零开始地去累加第二个数组中的支出数值。所以最后得出的是两个数组的支出数值总和。 + +* [**Reduce (编写软件)**:注意:这是关于学习函数式编程和编写软件的“Composing Software”系列的一部分……](https://medium.com/javascript-scene/reduce-composing-software-fe22f0c39a1d) + +当然了,`.reduce()` 可以做的事情远不止在数组中求和而已。大多数别的方法解决不了的**复杂转化**,都可以使用 `.reduce()` 与一个数组或对象的累加器来轻松解决。一个实用的例子是转化一个有很多篇文章的数组,每一篇文章有一个标题和一些标签。原来的数组会被转化成标签的数组,每一项中有使用该标签的文章数目以及这些文章的标题构成的数组。咱们来看看代码: + +``` +// 一个带有标签的文章的数组 +var articles = [ + {title: "Introduction to Javascript Scope", tags: [ "Javascript", "Variables", "Scope"]}, + {title: "Javascript Closures", tags: [ "Javascript", "Variables", "Closures"]}, + {title: "A Guide to PWAs", tags: [ "Javascript", "PWA"]}, + {title: "Javascript Functional Programming Examples", tags: [ "Javascript", "Functional", "Function"]}, + {title: "Why Javascript Closures are Important", tags: [ "Javascript", "Variables", "Closures"]}, +]; + +// 一个能够将文章数组降为标签数组的函数 +// +var tagView = function(accumulator, x){ + // 针对文章的标签数组(原数组)里的每一个标签 + x.tags.forEach(function(currentTag){ + // 写一个函数看看标签是否匹配 + var findCurrentTag = function(y) { return y.tag == currentTag; }; + // 检查是否该标签已经出现在累积器数组 + if (accumulator.some(findCurrentTag)){ + // 找到标签并获得索引 + var existingTag = accumulator.find(findCurrentTag); + var existingTagIndex = accumulator.findIndex(findCurrentTag); + // 更新使用该标签的文章数目,以及文章标题的列表 + accumulator[existingTagIndex].count += 1; + accumulator[existingTagIndex].articles.push(x.title); + } + // 否则就在累积器数组中增添标签 + else { + accumulator.push({tag: currentTag, count: 1, articles: [x.title]}); + } + }); + // 返回累积器数组 + return accumulator; +} + +// 转化原数组 +articles.reduce(tagView,[]); +// 输出: +/* +[ + {tag: "Javascript", count: 5, articles: [ + "Introduction to Javascript Scope", + "Javascript Closures", + "A Guide to PWAs", + "Javascript Functional Programming Examples", + "Why Javascript Closures are Important" + ]}, + {tag: "Variables", count: 3, articles: [ + "Introduction to Javascript Scope", + "Javascript Closures", + "Why Javascript Closures are Important" + ]}, + {tag: "Scope", count: 1, articles: [ + "Introduction to Javascript Scope" + ]}, + {tag: "Closures", count: 2, articles: [ + "Javascript Closures", + "Why Javascript Closures are Important" + ]}, + {tag: "PWA", count: 1, articles: [ + "A Guide to PWAs" + ]}, + {tag: "Functional", count: 1, articles: [ + "Javascript Functional Programming Examples" + ]}, + {tag: "Function", count: 1, articles: [ + "Javascript Functional Programming Examples" + ]} +] +*/ +``` + +使用 reduce() 来进行一项复杂的转化 + +上面这个例子可能看起来会有些小复杂,所以需要一步一步来研究。首先呢,咱想要的最终结果是一个数组,所以累加器的初始值就成了`[]`。然后,咱想要数组中的每一个对象都包含标签名、使用该标签的文章数目以及文章标题的列表。不但如此,每一个标签在数组中只能出现一次,所以咱必须用 `.some()`、`.find()` 和 `.findIndex()` 来检查标签是否存在,之后将现有标签的对象进行转化,而不是另加一个新的对象。 + +棘手的地方在于,咱不能定义一个函数来检查每个标签是否都存在(否则需要 7 个不同的函数)。所以咱们才在当前标签的循环里定义高阶函数,这样一来就可以再次使用高阶函数,避免重写代码。对了,其实这也可以通过 Currying 来完成,但我不会在本文中解释这个技巧。 + +* [**现实中的 Currying**:当我开始学习函数式编程时,我学到了很多有趣的概念……](https://hackernoon.com/currying-in-the-real-world-b9627d74a554) + +当咱们在累加器数组中获取标签的对象之后,只需要把使用该标签的文章数目递增,并且将当前标签下的文章添加到其文章数组中就行了。最后,咱们返回累加器,大功告成。仔细阅读的话会发现代码不但非常简短,而且很容易理解。相同情况下,非函数式编程的代码将会看起来非常令人困惑,而且明显会更冗杂。 + +#### 结语 + +函数式编程作为目前最热门的趋势之一,是有其充分原因的。它使咱们在写出更清晰、更精简和更“吝啬”代码的同时,不必去担心副作用和状态的改变。JavaScript 的 `[Array.prototype](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype)` 方法在许多日常情况下非常实用,并且让咱们在对数组进行简单和复杂的转化,也不必去写太多重复的代码。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/a-real-world-comparison-of-front-end-frameworks-with-benchmarks-2018-update.md b/TODO1/a-real-world-comparison-of-front-end-frameworks-with-benchmarks-2018-update.md new file mode 100644 index 00000000000..9b28dadd176 --- /dev/null +++ b/TODO1/a-real-world-comparison-of-front-end-frameworks-with-benchmarks-2018-update.md @@ -0,0 +1,93 @@ +> * 原文地址:[A Real-World Comparison of Front-End Frameworks with Benchmarks (2018 update)](https://medium.freecodecamp.org/a-real-world-comparison-of-front-end-frameworks-with-benchmarks-2018-update-e5760fb4a962) +> * 原文作者:[Jacek Schae](https://medium.freecodecamp.org/@jacekschae?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-real-world-comparison-of-front-end-frameworks-with-benchmarks-2018-update.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-real-world-comparison-of-front-end-frameworks-with-benchmarks-2018-update.md) +> * 译者:[geniusq1981](https://github.com/geniusq1981) +> * 校对者:[Hopsken ](https://github.com/Hopsken)、[zephyrJS](https://github.com/zephyrJS) + +# 前端开发框架的实战对比(2018 年更新) + +![](https://cdn-images-1.medium.com/max/1000/1*0aM-p4OCCxRMXroYn0qPVA.png) + +本文是是对 2017 年 12 月发表的 [前端开发框架的实战对比](https://medium.freecodecamp.org/a-real-world-comparison-of-front-end-frameworks-with-benchmarks-e1cb62fd526c) 一文的更新。 + +在对比中,我们将展示不同框架之间去实现几乎相同的 [实战示例应用](https://github.com/gothinkster/realworld) 有怎样的差别。 + +[实战示例应用](https://github.com/gothinkster/realworld) 为我们提供了: + +1. **实战应用**——不只是一个 "todo" 应用。一般来说,"todo" 应用无法充分的传达构建一个真实应用所需要的知识和观点。 +2. **标准化**——符合一定开发指南的项目。提供后端 API,静态标记,样式和规格。 +3. **由专家撰写或审核**——一个实战项目,理想情况下,由技术专家创建或审核。 + +#### 上一版本的不足(2017 年 12 月) + +✅ Angular 没有用于生产环境。之前实战应用仓库列出的示例应用使用的是一个开发版本,感谢 [Jonathan Faircloth](https://medium.com/@jafaircl),它现在已经是生产版本! + +✅ Vue 没有包含在实战应用仓库,因此未包括在对比中。正如你可以想象的那样,Vue 在前端引起了很大的热度。怎么可以不考虑 Vue 呢?你到底是怎么想的?这一次我们加入了Vue.js!谢谢 [Emmanuel Vilsbol](https://medium.com/@evilsbol)。 + +#### 我们比较哪些库/框架? + +和 2017 年 12 月的文章一样,我们包含了实战应用仓库中列出的所有实现方式。不管它有没有大量的拥趸,唯一标准就是它出现在 [实战应用仓库](https://github.com/gothinkster/realworld) 页面上。 + +![](https://cdn-images-1.medium.com/max/1000/1*IJ4a_VfY1Qn3yJaIy7pjVw.png) + +前往 [https://github.com/gothinkster/realworld](https://github.com/gothinkster/realworld) (2018 年 4 月) + +### 我们看什么指标? + +1. **性能:** 应用需要多长时间能显示出页面内容并可用? +2. **大小:** 应用程序多大?我们只会比较已编译过的的 JavaScript 文件大小。 CSS 对于所有不同实现框架都是通用的,并且从 CDN (内容分发网络)下载。 HTML 也是通用的。所有技术都编译或转换成 JavaScript,因此我们只计算这些文件的大小。 +3. **代码行数:** 开发者根据开发指南需要写多少行代码来做一个实战应用?为了公平,虽然有些应用程序有一些花里胡哨的东西,但它不应该对结果产生影响。所以我们唯一量化的目录只用每个 app 中的 src/ 目录。 + +### 指标 #1:**性能** + +使用 Chrome 自带的 [Lighthouse Audit](https://developers.google.com/) 工具进行 [首次有效绘制](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint) 的测试。 + +绘制速度越快,应用的使用体验就越好。Lighthouse 也测试 [First interactive](https://developers.google.com/web/tools/lighthouse/audits/first-interactive) ,但对于大多数应用来说,这几乎是相同的,而它还处于测试阶段。 + +![](https://cdn-images-1.medium.com/max/1000/1*El9cBVFHxRG36XD8KNjA_g.png) + +首次有效绘制(毫秒)——越低越好 + +在性能方面你可能不会看到很多差异。 + +### 指标 #2:大小 + +传输大小来自 Chrome 的网络标签,包含从服务器传送的压缩的响应头和响应正文。 + +文件越小,下载速度越快(并且需要解析的数据也越少)。 + +这取决于你的框架以及你添加的依赖库的大小,还有你的编译工具的好坏也有一定影响。 + +![](https://cdn-images-1.medium.com/max/1000/1*xHuwMctzoT6aA3BE4zXA5w.png) + +传输大小(KB)——越低越好 + +您可以看到 Svelte,Dojo 2 和 AppRun 做得非常好。我不能说 Elm 也表现足够好——特别是当你看下一张图时。我也想看看 [Hyperapp](https://hyperapp.js.org/) 的表现。可能下次吧,[Jorge Bucaran](https://medium.com/@jorgebucaran) ? + +### 指标 #3:代码行数 + +通过 [cloc](https://github.com/AlDanial/cloc) 我们计算每个仓库的 src 文件夹中的代码行。空白和注释行不会包含在内。这样做的意义何在? + +>如果调试是删除软件错误的过程,那么编程就是引入错误的过程 — Edsger Dijkstra + +![](https://cdn-images-1.medium.com/max/1000/1*YTfk05JBtqNBIoK_4u2H3g.png) + +# 代码行数——越少越好 + +您拥有的代码行数越少,那么出现错误的概率就越小,而且你也只需要维护较小的代码库。 + +### 结论 + +我想说,非常感谢 [Eric Simons](https://medium.com/@er) 创建了 [实战示例应用](https://github.com/gothinkster/realworld) ,还有大量的提供不同实现的贡献者们。 + +**更新:** 感谢 [Jonathan Faircloth](https://medium.com/@jafaircl) 提供生产版本的 Angular。 + +> 如果你对这篇文章感兴趣,你可以在 [Twitter](https://twitter.com/jacekschae) 和 Medium 上加我。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/a-simple-guide-to-es6-promises.md b/TODO1/a-simple-guide-to-es6-promises.md new file mode 100644 index 00000000000..e7418b49984 --- /dev/null +++ b/TODO1/a-simple-guide-to-es6-promises.md @@ -0,0 +1,264 @@ +> * 原文地址:[A Simple Guide to ES6 Promises](https://codeburst.io/a-simple-guide-to-es6-promises-d71bacd2e13a) +> * 原文作者:[Brandon Morelli](https://codeburst.io/@bmorelli25?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-simple-guide-to-es6-promises.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-simple-guide-to-es6-promises.md) +> * 译者:[熟鱼](https://github.com/sophiayang1997) +> * 校对者:[kezhenxu94](https://github.com/kezhenxu94/) [zhmhhu](https://github.com/zhmhhu) + +# 一个简单的 ES6 Promise 指南 + +> The woods are lovely, dark and deep. But I have promises to keep, and miles to go before I sleep. — Robert Frost + +![](https://cdn-images-1.medium.com/max/1000/1*WlQlce8AlSpq2VnQNO9UfQ.jpeg) + +Promise 是 JavaScript ES6 中最令人兴奋的新增功能之一。为了支持异步编程,JavaScript 使用了回调(callbacks),[以及一些其他的技术](http://exploringjs.com/es6/ch_async.html#sec_receiving-results-asynchronously)。然而,使用回调会遇到[地狱回调](http://callbackhell.com/)/[末日金字塔](https://en.wikipedia.org/wiki/Pyramid_of_doom_%28programming%29)等问题。Promise 是一种通过使代码看起来同步并避免在回调时出现问题进而大大简化异步编程的模式。 + +在这篇文章中,我们将看到什么是 Promise,以及如何利用它给我们带来好处。 + +* [**2018 年 Web 开发者路线图**:一个提供给前端或后端开发人员的插图指南(内部提供课程链接)](https://codeburst.io/the-2018-web-developer-roadmap-826b1b806e8d) + +#### 什么是 Promise? + +ECMA 委员会将 promise 定义为 —— + +> Promise 是一个对象,是一个用作延迟(也可能是异步)计算的最终结果的占位符。 + +简单来说,**一个 promise 是一个装有未来值的容器**。如果你仔细想想,这正是你正常的日常谈话中使用**承诺**(promise)这个词的方式。比如,你预定一张去印度的机票,准备前往美丽的山岗站[大吉岭](https://en.wikipedia.org/wiki/Darjeeling)旅游。预订后,你会得到一张**机票**。这张**机票**是航空公司的一个**承诺**,意味着你在出发当天可以获得相应的座位。实质上,票证是未来值的占位符,即**座位**。 + +这还有另外一个例子 —— 你向你的朋友**承诺**,会在看完[**计算机程序设计艺术**](https://en.wikipedia.org/wiki/The_Art_of_Computer_Programming)这本书后还给他们。在这里,你的话充当占位符。值就相当于这本书。 + +你可以想想其他类似承诺(promise)的例子,这些例子涉及各种现实生活中的情况,例如在医生办公室等候,在餐厅点餐,在图书馆发放书籍等等。这些所有的情况都涉及某种形式的承诺(promise)。然而,例子只能告诉我们这么多,[Talk is cheap, so let’s see the code.](https://news.ycombinator.com/item?id=902216)。 + +#### 创建 Promise + +当某个任务的完成时间不确定或太长时,我们可以创建一个 promise 。例如 —— 根据连接速度的不同,一个网络请求可能需要 10 ms 甚至需要 200 ms 这么久。我们不想等待这个数据获取的过程。对你而言,200 ms 可能看起来很少,但对于计算机来说是一段非常漫长的时间。promise 的目的就是让这种异步(asynchrony)变得简单而轻松。让我们一起来看看基础知识。 + +使用 **Promise** 构造函数创建了一个新的 promise。像这样 —— + +``` +const myPromise = new Promise((resolve, reject) => { + if (Math.random() * 100 <= 90) { + resolve('Hello, Promises!'); + } + reject(new Error('In 10% of the cases, I fail. Miserably.')); +}); +``` + +Promise 示例 + +观察这个构造函数就可以发现其接收一个带有两个参数的函数,这个函数被称为**执行器**函数,并且它**描述了需要完成的计算**。执行器函数的参数通常被称为 **resolve** 和 **reject**,分别标记执行器函数的成功和不成功的最终完成结果。 + +`resolve` 和 `reject` 本身也是函数,它们用于将返回值返回给 promise 对象。当计算成功或未来值准备好时,我们使用 `resolve` 函数将值返回。**这时我们说这个 promise 已经被成功解决(resolve)了**。 + +如果计算失败或遇到错误,我们通过在 `reject` 函数中传递错误对象告知 promise 对象。 **这时我们说这个 promise 已经被拒绝(reject)了**。`reject` 可以接收任何类型的值。但是,建议传递一个 `Error` 对象,因为它可以通过查看堆栈跟踪来帮助调试。 + +在上面的例子中,`Math.random()` 用于生成一个随机数。有 90% 概率,这个 promise 会被成功解决(假设概率均匀分布)。其余的情况则会被拒绝。 + +#### 使用 Promise + +在上面的例子中,我们创建了一个 promise 并将其存储在 `myPromise` 中。**那我们如何才能获取通过** `resolve` **或** `reject` **函数传递过来的值呢**?所有的 `Promise` 都有一个 `.then()` 方法。这样问题就好解决了,让我们一起来看一下 —— + +``` +const myPromise = new Promise((resolve, reject) => { + if (Math.random() * 100 < 90) { + console.log('resolving the promise ...'); + resolve('Hello, Promises!'); + } + reject(new Error('In 10% of the cases, I fail. Miserably.')); +}); + +// 两个函数 +const onResolved = (resolvedValue) => console.log(resolvedValue); +const onRejected = (error) => console.log(error); + +myPromise.then(onResolved, onRejected); + +// 效果同上,代码更加简明扼要 +myPromise.then((resolvedValue) => { + console.log(resolvedValue); +}, (error) => { + console.log(error); +}); + +// 有 90% 的概率输出下面语句 + +// resolving the promise ... +// Hello, Promises! +// Hello, Promises! +``` + +使用 Promise + +`.then()` 接收两个回调函数。第一个回调在 promise 被**解决**时调用。第二个回调在 promise 被**拒绝**时调用。 + +两个函数分别在第 10 行和第 11 行定义,即 `onResolved` 和 `onRejected`。它们作为回调传递给第 13 行中的 `.then()`。你也可以使用第 16 行到第 20 行更常见的 `.then` 写作风格。它提供了与上述写法相同的功能。 + +在上面的例子中还有一些需要注意的**重要**事项。 + +我们创建了一个 promise 实例 `myPromise`。我们分别在第 13 行和第 16 行附加了两个 `.then` 的处理程序。尽管它们在功能上是相同的,但它们还是被被视为不同的处理程序。但是 —— + +* 一个 promise 只能成功(resolved)或失败(reject)一次。它不能成功或失败两次,也不能从成功切换到失败,反之亦然。 +* 如果一个 promise 在你添加成功/失败回调(即 `.then`)之前就已经成功或者失败,则 promise 还是会正确地调用回调函数,即使事件发生地比添加回调函数要早。 + +这意味着一旦 promise 达到最终状态,即使你多次附加 `.then` 处理程序,状态也不会改变(即不会再重新开始计算)。 + +为了验证这一点,你可以在第3行看到一个 `console.log` 语句。当你用 `.then` 处理程序运行上述代码时,需要输出的语句只会被打印一次。**它表明 promise 缓存了结果,并且下次也会得到相同的结果**。 + +另一个要注意的是,promise 的特点是[及早求值(evaluated eagerly)](https://en.wikipedia.org/wiki/Eager_evaluation)。**只要声明并将其绑定到变量,就立即开始执行**。没有 `.start` 或 `.begin` 方法。就像在上面的例子中那样。 + +为了确保 promise 不是立即开始而是惰性求值(evaluates lazily),**我们将它们包装在函数中**。稍后会看到一个例子。 + +#### 捕捉 Promise + +到目前为止,我们只是很方便地看到了 `resolve` 的案例。那当执行器函数发生错误的时候会发生什么呢?当发生错误时,执行 `.then()` 的第二个回调,即 `onRejected`。让我们来看一个例子 —— + +``` +const myProimse = new Promise((resolve, reject) => { + if (Math.random() * 100 < 90) { + reject(new Error('The promise was rejected by using reject function.')); + } + throw new Error('The promise was rejected by throwing an error'); +}); + +myProimse.then( + () => console.log('resolved'), + (error) => console.log(error.message) +); + +// 有 90% 的概率输出下面语句 + +// The promise was rejected by using reject function. +``` + +Promise 出错 + +这与第一个例子相同,但现在它以 90% 的概率执行 **reject** 函数,并且剩下的 10% 的情况会抛出错误。 + +在第 10 和 11 行,我们分别定义了 `onResolved` 和 `onRejected` 回调。请注意,即使发生错误,`onRejected` 也会执行。因此我们没有必要通过在 `reject` 函数中传递错误来拒绝一个 promise。也就是说,这两种情况下的 promise 都会被拒绝。 + +由于错误处理是健壮程序的必要条件,因此 promise 为这种情况提供了一条捷径。当我们想要处理一个错误时,我们可以使用 `.catch(onRejected)` 接收一个回调:`onRejected`,而不必使用 `.then(null, () => {...})`。以下代码将展示如何使用 catch 处理程序 —— + +``` +myProimse.catch( + (error) => console.log(error.message) +); +``` + +请记住 `.catch` 只是 `.then(undefined, onRejected)` 的一个[语法糖](https://en.wikipedia.org/wiki/Syntactic_sugar)。 + +#### Promise 链式调用 + +`.then()` 和 `.catch()` 方法**总是返回一个 promise**。所以你可以把多个 `.then` 链接到一起。让我们通过一个例子来理解它。 + +首先,我们创建一个返回 promise 的 `delay` 函数。返回的 promise 将在给定秒数后解析。这是它的实现 —— + +``` +const delay = (ms) => new Promise( + (resolve) => setTimeout(resolve, ms) +); +``` + +在这个例子中,我们使用一个函数来包装我们的 promise,以便它不会立即执行。该 `delay` 函数接收以毫秒为单位的时间作为参数。由于[闭包](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)的特点,该执行器函数可以访问 `ms` 参数。它还包含一个在 `ms` 毫秒后调用 `resolve` 函数的 `setTimeout` 函数,从而**有效解决 promise**。这是一个示例用法 —— + +``` +delay(5000).then(() => console.log('Resolved after 5 seconds')); +``` + +只有在 `delay(5000)` 解决后,`.then` 回调中的语句才会运行。当你运行上面的代码时,你会在 5 秒后看到 `Resolved after 5 seconds` 被打印出来。 + +以下是我们如何实现 `.then()` 的链式调用 —— + +``` +const delay = (ms) => new Promise( + (resolve) => setTimeout(resolve, ms) +); + +delay(2000) + .then(() => { + console.log('Resolved after 2 seconds') + return delay(1500); + }) + .then(() => { + console.log('Resolved after 1.5 seconds'); + return delay(3000); + }).then(() => { + console.log('Resolved after 3 seconds'); + throw new Error(); + }).catch(() => { + console.log('Caught an error.'); + }).then(() => { + console.log('Done.'); + }); + +// Resolved after 2 seconds +// Resolved after 1.5 seconds +// Resolved after 3 seconds +// Caught an error. +// Done. +``` + +Promise 链式调用 + +我们从第 5 行开始。所采取的步骤如下 —— + +* `delay(2000)` 函数返回一个在两秒之后可以得到解决的 promise。 +* 第一个 `.then()` 执行。它输出了一个句子 `Resolved after 2 seconds`。然后,它通过调用 `delay(1500)` 返回另一个 promise。如果一个 `.then()` 里面返回了一个 promise,该 promise 的**解决方案(技术上称为结算)**是转发给下一个 `.then` 去调用。 +* 链式调用持续到最后。 + +**另请注意第 15 行**。我们在 `.then` 里面抛出了一个错误。那意味着当前的 promise 被拒绝了,**并被下一个** `.catch` **处理程序捕捉**。因此,`Caught an error` 这句话被打印。然而,一个 `.catch` **本身总是被解析为 promise,并且不会被拒绝**(除非你故意抛出错误)。这就是为什么 `.then` 后面的 `.catch` 会被执行的原因。 + +这里建议使用 `.catch` 而不是带有 `onResolved` 和 `onRejected` 参数的 `.then` 去处理。下面有一个案例解释了为什么最好这样做 —— + +``` +const promiseThatResolves = () => new Promise((resolve, reject) => { + resolve(); +}); + +// 导致被拒绝的 promise 没有被处理 +promiseThatResolves().then( + () => { throw new Error }, + (err) => console.log(err), +); + +// 适当的错误处理 +promiseThatResolves() + .then(() => { + throw new Error(); + }) + .catch(err => console.log(err)); +``` + +第 1 行创建了一个始终可以解决的 promise。当你有一个带有两个回调 ,即 `onResolved` 和 `onRejected` 的 `.then` 方法时,你只能处理**执行器**函数的错误和拒绝。假设 `.then` 中的处理程序也会抛出错误。它不会导致执行 `onRejected` 回调,如第 6 - 9 行所示。 + +但如果你在 `.then` 后跟着调用 `.catch`,那么 `.catch` **既捕捉执行器函数的错误也捕捉 .then 处理程序的错误**。这是有道理的,因为 `.then` 总是返回一个 promise。如第 12 - 16 行所示。 + +* * * + +你可以执行所有的代码示例,并通过实践应用学的更多。一个好的学习方法是将 promise 通过基于回调的函数重新实现。如果你使用 Node,那么在 `fs` 和其他模块中的很多函数都是基于回调的。在 Node 中确实存在可以自动将基于回调的函数转换为 promise 的实用工具,例如 [util.promisify](https://nodejs.org/api/util.html#util_util_promisify_original) 和 [pify](https://github.com/sindresorhus/pify)。但是,**如果你还在学习阶段**,请考虑遵循 WET(Write Everything Twice)原则,并重新实现或阅读尽可能多的库/函数的代码。如果不是在学习阶段,特别是在生产环境下,请每隔一段时间就要使用 [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)(Don’t Repeat Yourself) 原则激励自己。 + +还有很多其他的 promise 相关知识我没有提及,比如 `Promise.all` 、`Promise.race` 和其他静态方法,以及如何处理 promise 中出现的错误,还有一些在创建一个promise 时应该注意的一些常见的反模式(anti-patterns)和细节。你可以参考下面的文章,以便可以更好地了解这些主题。 + +如果你希望我在另一篇文章中涵盖这些主题,请回复本文!:) + +* * * + +#### 参考 + +由 [Jake Archibald](https://medium.com/@jaffathecake) 撰写的 [ECMA Promise 规范](http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects)、[Mozilla 文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)、[Google 的 Promise 开发者指南](https://developers.google.com/web/fundamentals/primers/promises#promise-api-reference),还有 [探索 JavaScript 中的 Promise 章节](http://exploringjs.com/es6/ch_promises.html#sec_first-example-promises) 以及 [Promise 介绍](http://jamesknelson.com/grokking-es6-promises-the-four-functions-you-need-to-avoid-callback-hell/)。 + +> 我希望你能喜欢这个客串贴!本文由 [**Arfat Salmon**](https://codeburst.io/@arfatsalman) 专门为 CodeBurst.io 撰写 + +### 结束语 + +感谢阅读!如果你最终决定走上 web 开发这条不归路,请查看:[**2018 年 Web 开发人员路线图**](https://codeburst.io/the-2018-web-developer-roadmap-826b1b806e8d)。 + +如果你正在努力成为一个更好的 JavaScript 开发人员,请查看:[**提高你的 JavaScript 面试水平 ——  学习算法 + 数据结构**](https://codeburst.io/ace-your-javascript-interview-learn-algorithms-data-structures-dabb547fb385)。 + +如果你希望成为我每周一次的电子邮件列表中的一员,请考虑[**在此输入你的 email**](https://docs.google.com/forms/d/e/1FAIpQLSeQYYmBCBfJF9MXFmRJ7hnwyXvMwyCtHC5wxVDh5Cq--VT6Fg/viewform),或者在 [**Twitter**](https://twitter.com/BrandonMorelli) 上关注我。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/a-simple-guide-to-understanding-javascript-es6-generators.md b/TODO1/a-simple-guide-to-understanding-javascript-es6-generators.md new file mode 100644 index 00000000000..74d923a9abd --- /dev/null +++ b/TODO1/a-simple-guide-to-understanding-javascript-es6-generators.md @@ -0,0 +1,229 @@ +> * 原文地址:[A Simple Guide to Understanding Javascript (ES6) Generators](https://medium.com/dailyjs/a-simple-guide-to-understanding-javascript-es6-Generators-d1c350551950) +> * 原文作者:[Rajesh Babu](https://medium.com/@rajeshdavid?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-simple-guide-to-understanding-javascript-es6-generators.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-simple-guide-to-understanding-javascript-es6-generators.md) +> * 译者:[ssshooter](https://github.com/ssshooter) +> * 校对者:[Zheng7426](https://github.com/Zheng7426) [hopsken](https://hopsken.com/) + +# Javascript(ES6)Generator 入门 + +![](https://cdn-images-1.medium.com/max/800/1*4877k4Hq9dPdtmvg9hnGFA.jpeg) + +如果你在过去两到五年中一直在研究 JavaScript,那么肯定看过关于 **Generator** 和 **Iterator** 的文章。虽然 **Generator** 和 **Iterator** 本质上是相关的,但 Generator 似乎比 Iterator 更令人难以理解。 + +![](https://cdn-images-1.medium.com/max/800/1*bwQSEHpbaNHte95IW2kTCw.jpeg) + +> **Iterator** 由 **Iterable** 对象(如 map,数组和字符串等)实现,我们能够使用 next() 迭代它们。Iterator 在 Generator,Observable 和 Spread 运算符中广泛使用。 + +> 如果你刚接触 Iterator,建议先阅读 [Guide to Iterators](https://codeburst.io/a-simple-guide-to-es6-iterators-in-javascript-with-examples-189d052c3d8e)。 + +可以使用内建的 Symbol.iterator 验证对象是否符合可迭代要求: + +``` +new Map([[1, 2]])[Symbol.iterator]() // MapIterator {1 => 2} +“hi”[Symbol.iterator]() // StringIterator {} +[‘1’][Symbol.iterator]() // Array Iterator {} +new Set([1, 2])[Symbol.iterator]() // SetIterator {1, 2} +``` + +第一次亮相于 ES6 的 Generator 在后续 JavaScript 版本的发布中并没有变化,所以 Generator 有可能在将来会继续保持现在的特性及用法,我们是绕不开它的。虽然 ES7 和 ES8 有一些小更新,但是改变幅度无法与 ES5 到 ES6 相提并论,可以说 ES6 使得 JavaScript 踏出了新的一步。 + +**读完本文,我相信你一定能充分理解 Generator 的原理**。如果你是专业人士,欢迎在回复中添加评论,一起改进这篇文章。为帮助大家理解代码,代码中已包含一定注释。 + +![](https://cdn-images-1.medium.com/max/800/1*ZrJKJqBsksWd-8uKM9OvgA.png) + +### 介绍 + +众所周知,JavaScript 的函数都会一直运行到 **return 或函数结束**。但对于 Generator 函数,会一直运行到 **遇到 yield 或 return 或函数结束**。与一般函数不同,**Generator 函数**一旦被调用,就会返回一个 **Generator 对象**。这个对象拥有 **Generator Iterable**,可以使用 **next()** 方法或 **for…of** 循环迭代它。 + +> Generator 每次调用 next(),函数会一直运行到下一个 yield,然后暂停执行。 + +语法上他们的标志是一个星号 **\***,**function\* X** 和 **function \*X** 的效果相同。 + +Generator 函数返回 **Generator 对象。**要把 Generator 对象赋值到一个变量,才能方便地使用它的 **next()** 方法。** 如果没有把 Generator 分配给变量,对它调用 next() 总是只会运行到第一个 yield 表达式。** + +Generator 函数中通常含有 **yield** 表达式。Generator 函数内的每个 **yield** 都是下一个执行循环开始之前的停止点。每个执行周期都通过 Generator 的 **next()** 方法触发。 + +每次调用 **next()**,**yield** 表达式都会返回包含以下参数的对象。 + +`{ value: 10, done: false } // 假设 yield 的值是 10` + +* **Value** —— **yield** 关键字右侧的值,可以是对函数的调用、对象等几乎任何东西。对于空的 yield,返回的是 **undefined**。 +* **Done** —— 表明 Generator 的状态,是否可以继续执行。完成时返回 true,意味着函数已经运行完毕。 + +**(如果你无法理解上面说的是什么,那下面的例子可能会让你理解得更清晰……)** + +![](https://cdn-images-1.medium.com/max/800/1*YnOJNuFe-r9T7pO47mVYaw.png) + +Generator 函数基础 + +> **注意:**在上面的例子中,直接访问 **Generator 函数**总是执行到第一个 yield。因此,你需要将 Generator 分配给变量才能正确迭代它。 + +### Generator 函数的生命周期 + +在深入理解之前,让我们快速浏览一下 Generator 函数的生命周期示意图: + +![](https://cdn-images-1.medium.com/max/800/1*0pLkX6yrbV2r6_pZ10AIvQ.png) + +Generator 函数的生命周期 + +每次运行到 **_yield_**,Generator 函数都会返回一个对象,该对象包含 yield 产生的值和当前 Generator 函数的状态。类似地,运行到 **_return_**,可以得到 return 的值,并且 **_done_** 的状态为 **_true_**。当 done 的状态为 true 时,意味着 Generator 函数已经运行完毕,后面的 yield 统统无效。 + +> return 后的一切代码都会被忽略,包括 **yield** 表达式。 + +**继续阅读深入理解上图。** + +### 把 yield 赋值到一个变量 + +在的示例中,我们创建了一个带有 yield 的最基本的 Generator,并获得了预期的输出。在下面代码中,我们将整个 yield 表达式赋值到一个变量。 + +![](https://cdn-images-1.medium.com/max/800/1*zdJQlUaqIiD3eV0j0QzrZA.png) + +把 yield 赋值到一个变量 + +> 把整个 yield 表达式传到变量的结果是什么?**Undefined …** + +> 为什么会是 undefined?**从第二个 next() 开始,前一个 yield 会被替换为 next 函数的参数。因为例子中的 next 没有传入任何值,所以程序判定“前一个 yield 表达式”为 undefined**。 + +这是重点中的重点,下面的章节我们将详细介绍对 next() 传参的用法。 + +### 将参数传递给 next() 方法 + +参考上面的示意图,我们聊聊关于传参到 next 函数的事情。**这是整个 Generator 使用中最棘手的部分之一**。 + +思考以下代码,其中 yield 被赋给变量,但这次我们向 next() 传参。 + +看看控制台的输出,先思考一下,后面会有解释。 + +![](https://cdn-images-1.medium.com/max/800/1*aYCKrAkgSyfEeN9cswZzbA.png) + +将参数传递给 next() + +#### 说明: + +1. 在调用 **next(20)** 的时候,第一个 yield 前的代码都被执行。因为前面已经没有 yield,传入的 20 毫无作用。输出 yield 的 value 为 i*10,也就是 100。因为执行到第一个 yield 停止,所以 **const j** 未被赋值。 +2. 调用 **next(10)** 时,第一个 yield 的位置被替换为 10,相当于在返回第二个 yield 的 value 前,设置 **yield (i * 10) = 10**,所以 **j 为 50**。yield 的 value 为 **2 * 50 / 4 = 25**。 +3. **next(5)** 用 5 替换第二个 yield,所以 k 为 5。继续执行 return 语句,返回最后的 yield value **(x + y + z) => (10 + 50 + 5) = 65**,并且 done 为 true。 + +> **这可能对初次接触 Generator 的读者有点超纲,但是给自己 5 分钟,多读几遍,就能清楚明白。** + +### Yield 作为其他函数的参数 + +Yield 在 Generator 中还有大把的用法,我们接着看看下面的代码,这是 yield 的其中一个妙用,附带解释。 + +![](https://cdn-images-1.medium.com/max/800/1*Y6pwTwJ7stPZzAeCKBfv4Q.png) + +Yield 作为其他函数的参数 + +#### 解释 + +1. 第一个 next() yield(生成) 的 value 为 undefined,因为 yield 表达式无值。 +2. 第二个 next() 生成的 value 为被传入的 `'I am usless'`,这一步为函数调用准备了参数。 +3. 第二个 next() 以 **undefined** 为参数调用了后面的函数。next() 没有接收参数,意味着**上一个 yield 表达式的值为 undefined**,所以函数打印出 **undefined** 并终止运行。 + +### 对函数调用使用 yield + +除了返回普通的值,yield 还可以调用函数并返回他的值。看看下面的例子更好理解: + +![](https://cdn-images-1.medium.com/max/800/1*zXpsq-hlqla3z3mZGWyTJw.png) + +对函数调用使用 yield + +上述代码返回了函数返回的对象作为 yield 的 value,然后把 **const user** 赋值为 **undefined**,结束运行。 + +### 对 Promise 使用 yield + +对 promise 使用 yield 与对函数调用使用 yield 相似,它会返回一个 promise,我们以此进一步判定操作成功或失败。看看以下代码,了解它的使用方法: + +![](https://cdn-images-1.medium.com/max/800/1*100c_wLxJHmcKtjZAYwJzw.png) + +对 Promise 使用 yield + +apiCall 将 promise 作为 yield value 返回,在 2 秒后 resolved 并打印出我们需要的值。 + +### `Yield*` + +Yield 表达式的介绍就告一段落了,接着我们了解一下另一个表达式 `yield*`。`Yield*` 在 Generator 函数中使用时,会把迭代委托到下一个 Generator 函数。简单来说,会先同步完成 `Yield*` 表达式中的 Generator 函数,再继续运行外层函数。 + +让我们看看下面的代码和解释,以便更好地理解。此代码来自 MDN Web 文档。 + +![](https://cdn-images-1.medium.com/max/800/1*eMlOmBoi2XGCE3qwUIj3qA.png) + +`Yield*` 基础 + +#### 解释 + +1. 调用第一个 next(),产生的值为1。 +2. 第二个 next() 调用的是 `yield*` 表达式,这意味着我们要先完成 `yield*` 表达式指定的 Generator 函数,再继续运行当前 Generator 函数。 +3. 你可以假设上面的代码被替换为如下代码: + +``` +function* g2() { + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; +} +``` + +> Generator 会按这个顺序运行结束。不过对于 `yield*` 和 return 的同时使用,我们需要特别注意,下一节将会提到。 + +### `Yield*` 与 Return + +带 return 的 `yield*` 与一般 `yield*` 有点不同。当 `yield*` 与 return 语句一起使用时,`yield*` 被赋 return 的值,也就是整个 `yield*` function() 与其关联 Generator 函数的返回值相等。 + +让我们看看下面的代码和解释,以便更好理解。 + +![](https://cdn-images-1.medium.com/max/800/1*HxJtIuXhBnOMAK0cwVElsQ.png) + +`Yield*` 与 Return + +#### **说明** + +1. 第一个 next(),直接进入 yield 1 并返回其值。 +2. 第二个 next() 返回 2。 +3. 第三个 next(),运行 **return 'foo'** 后紧接着,yield 返回 'the end',其中 'foo' 被赋值到 **const result**。 +4. 最后一个 next() 结束运行。 + +### **对内建 Iterable 对象使用 `Yield*`** + +`yield*` 还有一个值得一提的用法,它可以遍历 iterable 对象,如 Array,String 和 Map。 + +一起看看实际运行结果。 + +![](https://cdn-images-1.medium.com/max/800/1*u6RQVCQBCqw5UsF3Kger1w.png) + +对内建 Iterable 对象使用 `Yield*` + +在代码中,`yield*` 遍历传入的每一个 iterable 对象,我觉得这段代码本身是不言自明的。 + +### 最佳实践 + +最重要的是,每个 iterator/Generator 都可以使用 **for…of** 遍历。与显式调用的 next() 类似,for…of 循环依据 **yield 关键字** 进入下一次迭代。这里是重点:它只会迭代到**最后一个 yield**,不会像 next() 那样处理 return 语句。 + +下面的代码可以验证以上描述。 + +![](https://cdn-images-1.medium.com/max/800/1*dDYt_xElLC7wjUDN7HfDJg.png) + +Yield 与 for…of + +> 最后 return 的值不会被打印,因为 for…of 循环只迭代到最后一个 yield。因此,作为最佳实践,尽量避免在 Generator 函数中使用 return 语句,原因在于当使用 for…of 语句进行迭代时,return 会影响函数的可重用性。 + +![](https://cdn-images-1.medium.com/max/800/1*4877k4Hq9dPdtmvg9hnGFA.jpeg) + +### 总结 + +我希望这涵盖了 Generator 函数的基本用法,希望这篇文章能让你更好地理解 Generator 在 JavaScript 中的工作方式。如果你喜欢本文,请点个赞吧 :)。 + +请关注我的 GitHub 账号获取更多 JavaScript 和全栈项目: + +* [**rajeshdavidbabu (Rajesh Babu)**: rajeshdavidbabu has 11 repositories available. Follow their code on GitHub.](https://github.com/rajeshdavidbabu) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 + diff --git a/TODO1/a-web-application-completely-in-rust.md b/TODO1/a-web-application-completely-in-rust.md new file mode 100644 index 00000000000..d00d0966816 --- /dev/null +++ b/TODO1/a-web-application-completely-in-rust.md @@ -0,0 +1,257 @@ +> * 原文地址:[A web application completely in Rust](https://medium.com/@saschagrunert/a-web-application-completely-in-rust-6f6bdb6c4471) +> * 原文作者:[Sascha Grunert](https://medium.com/@saschagrunert?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/a-web-application-completely-in-rust.md](https://github.com/xitu/gold-miner/blob/master/TODO1/a-web-application-completely-in-rust.md) +> * 译者: +> * 校对者: + +# A web application completely in Rust + +My latest software architectural experiment is to write a complete real-world web application in Rust with as less as boilerplate as possible. Within this post I want to share my findings with you to answer the question on [how much web](http://www.arewewebyet.org) Rust actually is. + +The related project to this post [can be found on GitHub](https://github.com/saschagrunert/webapp.rs/tree/rev1). I put both, the client-side frontend and the server-side backend, into one repository for maintainability. This means Cargo needs to compile a frontend and a backend binary of the whole application with different dependencies. + +> Please be aware that the project is currently fastly architectural evolving and everey related source code of this article can be found within the `rev1` branch. + +The Application itself is a simple authentication demonstration. It allows you to login with a chosen username and password (must be the same) and fails when they are not equal. After the successful authentication a [JSON Web Token (JWT)](https://en.wikipedia.org/wiki/JSON_Web_Token) is stored on both the client and server side. Storing the token on the server side is usually not needed but I’ve done that for demonstration purposes. It could be used to track how much users are actually logged in for example. The whole application can be configured via a single [Config.toml](https://github.com/saschagrunert/webapp.rs/blob/rev1/Config.toml), for example to set the database credentials or server host and port. + +``` +[server] +ip = "127.0.0.1" +port = "30080" +tls = false + +[log] +actix_web = "debug" +webapp = "trace" + +[postgres] +host = "127.0.0.1" +username = "username" +password = "password" +database = "database" +``` + +The default Config.toml for the webapp + +### The Frontend — Client Side + +I decided to use [yew](https://github.com/DenisKolodin/yew) for the client side of the application. Yew is a modern Rust framework inspired by Elm, Angular and ReactJS for creating multi-threaded frontend apps with [WebAssembly](https://en.wikipedia.org/wiki/WebAssembly) (Wasm). The project is under highly active development and there are not that many stable releases yet. + +The tool [cargo-web](https://github.com/koute/cargo-web) is a direct dependency of yew, which makes cross compilation to Wasm straight forward. There are actually three major Wasm targets available within the Rust compiler: + +* _asmjs-unknown-emscripten _— using [asm.js](https://en.wikipedia.org/wiki/Asm.js) via Emscripten +* _wasm32-unknown-emscripten_ — using WebAssembly via Emscripten +* _wasm32-unknown-unknown _— using WebAssembly with Rust’s native WebAssembly backend + +![](https://cdn-images-1.medium.com/max/800/1*8q4reKhsoW7H-vxSzh-KJQ.jpeg) + +I decided to use the last one which requires a nightly Rust compiler, but demonstrates Rust native Wasm possiblities as its best. + +> WebAssembly is currently one of the hottest 🔥 topics when it comes to Rust. There is a lots of ongoing work in relation to cross compiling Rust to Wasm and integrating it in the nodejs (npm packaging) world. I decided to go the direct way, without any JavaScript dependencies. + +When starting the frontend of the web application (in my project via `make frontend`), cargo-web cross compiles the application to Wasm and packages it together with some static content. Then cargo-web starts a local web server which serves the application for development purposes. + +``` +> make frontend + Compiling webapp v0.3.0 (file:///home/sascha/webapp.rs) + Finished release [optimized] target(s) in 11.86s + Garbage collecting "app.wasm"... + Processing "app.wasm"... + Finished processing of "app.wasm"! + +If you need to serve any extra files put them in the 'static' directory +in the root of your crate; they will be served alongside your application. +You can also put a 'static' directory in your 'src' directory. + +Your application is being served at '/app.js'. It will be automatically +rebuilt if you make any changes in your code. + +You can access the web server at `http://0.0.0.0:8000`. +``` + +Yew has some great features, like the reusable component architecture, which made it easy to split my application into three major components: + +* [_RootComponent_](https://github.com/saschagrunert/webapp.rs/blob/rev1/src/frontend/components/root.rs): Directly mounted on the `` tag of the website and decides which child component should be loaded next. If a JWT is found on initial entering of the page, it tries to renew the token with a backend communication. If this fails, it routes to the _LoginComponent_. +* [_LoginComponent_](https://github.com/saschagrunert/webapp.rs/blob/rev1/src/frontend/components/login.rs): A child of the _RootComponent_ and contains the login form field. It also communicates with the backend for a basic username and password authentication and saves the JWT within a cookie on successful authentication. Routes to the _ContentComponent_ on successful authentication. + +![](https://cdn-images-1.medium.com/max/800/1*0h9AZ2uIwzbdDvUTsna9Lw.png) + +The LoginComponent + +* [_ContentComponent_](https://github.com/saschagrunert/webapp.rs/blob/rev1/src/frontend/components/content.rs): Another child of the _RootComponent_ and contains the main page content (for now only a header and logout button). It can be reached via the _RootComponent_ (if a valid session token is already available) or via the _LoginComponent_ (on successful authentication). This component communicates with the backend when the user pushed the logout button. + +![](https://cdn-images-1.medium.com/max/800/1*8ryczcVc5JrfrkMkBgFcuw.png) + +The ContentComponent + +* _RouterComponent_: Holds all possible routes between the components which hold content. Also contains an initial “loading” state and an “error” state of the application. Is directly attached to the _RootComponent_. + +Services are one of the next key concepts of yew. They allow reusing the same logic between components like logging facades or [cookie handling](https://github.com/saschagrunert/webapp.rs/blob/rev1/src/frontend/services/cookie.rs). Services are stateless between components and will be created on component initialization. Beside services yew contains the concepts of Agents. They can be used for sharing data between components and provide an overall application state, like needed for a routing agent. To accomplish the routing for the demonstration application between all components [a custom routing agent and service](https://github.com/saschagrunert/webapp.rs/blob/rev1/src/frontend/services/router.rs) was implemented. Yew actually ships no stand-alone router, [but their examples](https://github.com/DenisKolodin/yew/tree/master/examples/routing) contain a reference implementation which supports all kinds of URL modifications. + +> Amazingly, yew uses the [Web Workers API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) to spawn agents in separate threads and uses a local scheduler attached to a thread for concurrent tasks. This enables high concurrency applications within the browser written in Rust. + +Every component implements its [own \`Renderable\` trait](https://github.com/saschagrunert/webapp.rs/blob/rev1/src/frontend/components/root.rs#L123) which enables us to include HTML directly within the rust source via the `[html!{}](https://github.com/DenisKolodin/yew#jsx-like-templates-with-html-macro)` macro, this is pretty great and for sure checked by the compilers internal borrow checker! + +``` +impl Renderable for LoginComponent { + fn view(&self) -> Html { + html! { +
+
+
+ {"Authentication"} +
+ +
+
+ +
+ + + {&self.error} + +
+
+
+ } + } +} +``` + +The `Renderable` implementation for the LoginComponent + +The communication from the frontend to the backend and vice versa is implemented via a [WebSocket](https://en.wikipedia.org/wiki/WebSocket) connection for every client. The WebSocket has the benefit that it is usable for binary messages and the server is able to push notifications to the client too if needed. Yew already ships a WebSocket service, but I decided to [create a custom version](https://github.com/saschagrunert/webapp.rs/blob/rev1/src/frontend/services/websocket.rs) for the demonstration application mainly reasoned by the lazy initialized connection directly within the service. If the WebSocket service would be created during component initialization I would had to track multiple socket connections. + +![](https://cdn-images-1.medium.com/max/800/1*w3kQzk007POxE3PqjECqXQ.png) + +I decided to use the binary protocol [Cap’n Proto](https://capnproto.org) as application data communication layer (instead of something like [JSON](https://www.json.org), [MessagePack](https://msgpack.org) or [CBOR](http://cbor.io)) for speed and compactness reasons. One little side note worth to mention is that I did not use the [interface RPC Protocol](https://capnproto.org/rpc.html) of Cap’n Proto, because the Rust implementation does not compile for WebAssembly (because of [tokio-rs](https://github.com/tokio-rs/tokio)’ unix dependencies). This makes it a little bit harder to distinguish between the right request and response types, but a [cleanly structured API](https://github.com/saschagrunert/webapp.rs/blob/rev1/src/protocol.capnp) could solve the problem here: + +``` +@0x998efb67a0d7453f; + +struct Request { + union { + login :union { + credentials :group { + username @0 :Text; + password @1 :Text; + } + token @2 :Text; + } + logout @3 :Text; # The session token + } +} + +struct Response { + union { + login :union { + token @0 :Text; + error @1 :Text; + } + logout: union { + success @2 :Void; + error @3 :Text; + } + } +} +``` + +Cap’n Proto protocol definition for the application + +You can see that we have two different login request variants here: One for the _LoginComponent_ (credential request with username and password) and another for the _RootComponent_ (already available token renewal request). All needed protocol related implementations are packed within a [protocol service](https://github.com/saschagrunert/webapp.rs/blob/rev1/src/frontend/services/protocol.rs), which makes it easily reusable within the whole frontend. + +![](https://cdn-images-1.medium.com/max/800/1*Ngm7Avt7AM7ITqjlPcfARw.jpeg) + +UIkit — A lightweight and modular front-end framework for developing fast and powerful web interfaces. + +The user interface of the frontend is powered by [UIkit](https://getuikit.com), where version `3.0.0` will be released in the near future. A custom [build.rs](https://github.com/saschagrunert/webapp.rs/blob/rev1/build.rs) script automatically downloads all needed UIkit dependencies and compiles the overall stylesheet. This means custom styles can be inserted within a [single style.scss file](https://github.com/saschagrunert/webapp.rs/blob/rev1/src/frontend/style.scss) and are application wide applied. Neat! + +#### Frontend testing + +Testing is a little bit a problem in my opionion: The separate services can be tested pretty easily, but yew does not provide a convenient way how to test single components or agents yet. Integration and end-to-end testing of the frontend is also not possible within plain Rust for now. It could be possible to use projects like [Cypress](https://www.cypress.io) or [Protractor](http://www.protractortest.org/#/) but this would include too much JavaScript/TypeScript boilerplate so I skipped this option. + +> But hey, maybe this is a good starting point for a new project: An end-to-end testing framework written in Rust! What do you think? + +### The Backend — Server Side + +My chosen framework for the backend is [actix-web](https://github.com/actix/actix-web): A small, pragmatic, and extremely fast Rust [actor framework](https://en.wikipedia.org/wiki/Actor_model). It supports all needed technologies like WebSockets, TLS and [HTTP/2.0](https://actix.rs/docs/http2/). Actix-web supports different handlers and resources, but within the demonstration application are just two main routes used: + +* `**/ws**`: The main websocket communication resource +* `**/**`: The main application handler which routes to the statically deployed frontend application + +By default, actix-web spawns as much workers as CPU cores are available on the local machine. This means a possible application state has to be shared safely between all threads, but this is really no problem with Rusts fearless concurrency patterns. Nevertheless, the overall backend should be stateless, because it could be deployed with multiple replicas in parallel within an cloud based (like [Kubernetes](https://kubernetes.io)) environment. So the applications state should be outside of the backend within a separate [Docker](https://www.docker.com) container instance for example. + +![](https://cdn-images-1.medium.com/max/800/1*vbIdg_EDv0Jakk7iGByH-Q.png) + +I decided to use a [PostgreSQL](https://www.postgresql.org) database as main data storage. Why? Because the awesome [Diesel project](http://diesel.rs) already supports PostgreSQL and provides a safe, extensible Object-relational mapping (ORM) and query builder for it. This is pretty great since actix-web already supports Diesel. In result, a custom idiomatic Rust domain specific language can be used to create, read, update or delete (CRUD) the sessions within the database like this: + +``` +impl Handler for DatabaseExecutor { + type Result = Result; + + fn handle(&mut self, msg: UpdateSession, _: &mut Self::Context) -> Self::Result { + // Update the session + debug!("Updating session: {}", msg.old_id); + update(sessions.filter(id.eq(&msg.old_id))) + .set(id.eq(&msg.new_id)) + .get_result::(&self.0.get()?) + .map_err(|_| ServerError::UpdateToken.into()) + } +} +``` + +UpdateSession Handler for actix-web powered by Diesel.rs + +For the connection handling between actix-web and Diesel the [r2d2](https://github.com/sfackler/r2d2) project is used. This means we have (beside the application with its workers) an shared application state which holds multiple connections to the database as a single connection pool. This makes the whole backend very easily large scaling and flexible. The whole server instantiation can be found [here](https://github.com/saschagrunert/webapp.rs/blob/master/src/backend/server.rs#L44-L82). + +#### Backend testing + +The [integration testing](https://github.com/saschagrunert/webapp.rs/blob/rev1/tests/backend.rs) of the backend is done by setting up a test instance and connecting to an already running database. Then a standard WebSocket client (I used [tungstenite](https://github.com/snapview/tungstenite-rs)) can be used to send the protocol related Cap’n Proto data to the server and evaluate the expected results. This worked pretty well! I did not use the [actix-web specific test servers](https://actix.rs/actix-web/actix_web/test/index.html) because setting up a real server was not much more work. Unit testing of the other parts of the backend worked as simple as expected and produced no real pitfalls. + +### The Deployment + +Deploying the application can be done easily via an Docker image. + +![](https://cdn-images-1.medium.com/max/800/1*d-HKujYLR5Q2QED4ybEiPw.png) + +The Makefile command `make deploy` creates a Docker image called `webapp`, which contains the statically linked backend executable, the current `Config.toml`, TLS certificates and the static content for the frontend. Building a fully statically linked executable in Rust is achieved with a modified variant of the [rust-musl-builder](https://hub.docker.com/r/ekidd/rust-musl-builder/) docker image. The resulting webapp can be tested with `make run`, which starts the container with enabled host networking. The PostgreSQL container should now run in parallel. In general, the overall deployment is not that big part of the deal and should be flexible enough for future adaptions. + +### Summary + +As a summary, the basic dependency stack of the application looks like this: + +![](https://cdn-images-1.medium.com/max/800/1*jkm-cPEWdyZeHjAyqNfHHw.png) + +The only shared component between the frontend and backend is the Cap’n Proto generated Rust source, which needs a locally installed Cap’n Proto compiler. + +#### So, are we web yet (in production)? + +That is the big question, my personal opinion on that is: + +> On the backend side I would tend to say “yes”, because Rust has beside actix-web a very mature [HTTP stack](http://www.arewewebyet.org/topics/stack/) and various different [frameworks](http://www.arewewebyet.org/topics/frameworks/) for building APIs and backend services quickly. + +> On the frontend side is also a lots of work ongoing because of the WebAssembly hype, but the projects needs to have the same matureness as the backend ones, especially when it comes to stable APIs and testing possibilities. So there is a “no” for the frontend, but we’re on a pretty good track. + +![](https://cdn-images-1.medium.com/max/800/1*BIUlQD822_EKKLv4jtElWg.png) + +> Thank you very much for reading until here. ❤ + +I will continue my work on the demonstration application to continuously find out where we are in Rust in relation to web applications. Keep on rusting! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/activity-recognitions-new-transition.md b/TODO1/activity-recognitions-new-transition.md new file mode 100644 index 00000000000..2cf05f39499 --- /dev/null +++ b/TODO1/activity-recognitions-new-transition.md @@ -0,0 +1,39 @@ +> * 原文地址:[Activity Recognition’s new Transition API makes context-aware features accessible to all developers](https://android-developers.googleblog.com/2018/03/activity-recognitions-new-transition.html) +> * 原文作者:[Marc Stogaitis, Tajinder Gadh, and Michael Cai](https://android-developers.googleblog.com) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/activity-recognitions-new-transition.md](https://github.com/xitu/gold-miner/blob/master/TODO1/activity-recognitions-new-transition.md) +> * 译者:[wzasd](github.com/wzasd) +> * 校对者:[maoqyhz](https://github.com/maoqyhz)、[LeeSniper](https://github.com/LeeSniper) + +# 带有情景感知这一新特性的活动识别 Transition API 面向全体开发者开放。 + +由 Android 活动识别团队的 Marc Stogaitis,Tajinder Gadh和Michael Cai 发布 + +人们现在携带最多的私人设备就是手机,但是到目前为止,应用程序都很难根据用户不断变化的环境以及状态来调整情景模式。我们从开发者那里了解到开发者已经花费了很多时间去结合位置以及其他传感器等各种装置的数据信号,以确定用户何时开始或者结束像是步行或者驾驶这样的情景活动。更糟的是,当应用程序不断的监测用户的当前情景活动状态时,电池的寿命会受到影响。这就是今天的目的,这就是为什么今天我们如此激动地向所有 Android 开发者提供活动识别 Transition API(不同情景活动的识别 API)— 它是一个简单的 API,当用户行为发生改变时,会处理一切事物,且告诉用户你真正关注的是什么。 + +自从去年 11 月以来,Transition API 一直在后台工作,为[驾驶模式请勿打扰](https://android-developers.googleblog.com/2017/11/making-pixel-better-for-drivers.html)提供支持,这项功能在 Pixel 2 上启动。虽然在手机传感器检查到驾驶情景时打开请勿打扰似乎很简单,但在实践中会出现很多棘手的挑战。你怎么知道车辆静止是因为用户在停车场找到了位置熄火还是因为在一个红绿灯处暂时停下来呢?你是否应该相信非驾驶情景或者暂时分析错误?借助 Transtion API,所有的 Android 开发人员都可以利用 Google 使用的相同训练的数据和算法过滤器来检测用户情景活动中的这些状态更改。 + +Intuit 与我们合作测试 Transition API,并发现它是 [QuickBooks Self-Employed](https://play.google.com/store/apps/details?id=com.intuit.qbse) 应用的理想解决方案: + +“QuickBooks Self-Employed 通过导入信息并自动跟踪汽车的行驶里程,帮助自雇员工在税务时间最大限度地减免税款。在 Transition API 之前,我们使用自己的解决方案来跟踪 GPS 以及手机其他传感器的数据,但是由于 Android 设备的多样性,我们的算法并不能 100% 保证准确性,有一些用户回馈了没有记录或者缺少数据的行驶状态。我们现在能够在几天内使用 Transition API 构建一个模型,现在已经具备了相当好的准确度,并取代了我们现有的解决方案,而且可以降低电池的消耗。Transition API 使我们能够集中精力提供减少税务的解决方案。”Intuit 的 Pranay Airan 和 Mithun Mahadevan 说。 + +[![](https://2.bp.blogspot.com/-xjpu46Q1QlM/WrALrluMqRI/AAAAAAAAFJc/G0jP4_1B5TgBGCioG5vyIFkCrSl1zD1WwCLcBGAs/s1600/image1.png)](https://2.bp.blogspot.com/-xjpu46Q1QlM/WrALrluMqRI/AAAAAAAAFJc/G0jP4_1B5TgBGCioG5vyIFkCrSl1zD1WwCLcBGAs/s1600/image1.png) + +QuickBooks Self-Employed 中的自动追踪驾驶里程 + +[Life360](https://play.google.com/store/apps/details?id=com.life360.android.safetymapd) 在其应用程序中同样实现了 Transition API,并在活动检测延迟和电池的消耗方面有重大改善: + +“Life360 拥有超过 1000 万个活跃的家庭用户,是全球最大的家庭移动应用程序,我们的使命是成为家庭的医院,可以让家人在何时何地都有安全感,现在我们通过定位分享以及全天候的安全功能(例如检测家庭成员的驾驶行为),因此,准确测量用户当前的活动状态并且尽可能减少电池的消耗非常关键。要确定用户何时启动开始驾驶或者停止驾驶,我们的应用之前依靠地理位置,结合位置 API 和活动识别 API,但这种方法有很多挑战,包括如何快速检测驾驶的启动而不会过渡消耗电池并要收集分析处理活动识别的 API 的原始数据,但在测试 Transition API 的时候,我们跟我们以前的解决方案进行对比,我们看到了更高的精度以及更少的电量消耗,而不仅仅是满足我们的需求。”Life360 的 Dylan Keil 说。 + +[![](https://3.bp.blogspot.com/-jDgcFj0bhIE/WrAL4t8LU6I/AAAAAAAAFJg/07cgXSIDGKoUO5RyY24JV7m0Wjce9XtcACLcBGAs/s1600/image2.png)](https://3.bp.blogspot.com/-jDgcFj0bhIE/WrAL4t8LU6I/AAAAAAAAFJg/07cgXSIDGKoUO5RyY24JV7m0Wjce9XtcACLcBGAs/s1600/image2.png) + +Life360 中实时分享位置信息。 + +在接下来的几个月里,我们将继续在 Transition API 中增加新的活动分类,用来在 Android 上支持更多的情景感知功能,例如区分公路和铁路上的车辆。如果您准备在您的应用中使用 Transition API,请查看我们的 API 指南](https://developer.android.com/guide/topics/location/transitions.html)。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/airflow-a-workflow-management-platform.md b/TODO1/airflow-a-workflow-management-platform.md new file mode 100644 index 00000000000..008ed982a75 --- /dev/null +++ b/TODO1/airflow-a-workflow-management-platform.md @@ -0,0 +1,127 @@ +> * 原文地址:[Airflow: a workflow management platform](https://medium.com/airbnb-engineering/airflow-a-workflow-management-platform-46318b977fd8) +> * 原文作者:[Maxime Beauchemin](https://medium.com/@maximebeauchemin) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/airflow-a-workflow-management-platform.md](https://github.com/xitu/gold-miner/blob/master/TODO1/airflow-a-workflow-management-platform.md) +> * 译者:[yqian1991](https://github.com/yqian1991) +> * 校对者:[Park-ma](https://github.com/Park-ma) [DerekDick](https://github.com/DerekDick) + +# Airflow: 一个工作流程管理平台 + +出自 [Maxime Beauchemin](https://medium.com/@maximebeauchemin) + +![](https://cdn-images-1.medium.com/max/800/0*277Imf2r7ouTXOVy.png) + +**Airbnb** 是一个快速增长的、数据启示型的公司。我们的数据团队和数据量都在快速地增长,同时我们所面临的挑战的复杂性也在同步增长。我们正在扩张的数据工程师、数据科学家和分析师团队在使用 **Airflow**,它是我们搭建的一个可以快速推进工作,保持发展优势的平台,因为我们可以自己编辑、监控和改写 **数据管道**。 + +今天,我们非常自豪地宣布我们要 **开源** 和 **共享** 我们的工作流程管理平台:**Airflow**。 + +[https://github.com/airbnb/airflow](https://github.com/apache/incubator-airflow) + +* * * + +### 有向无环图(DAGs)呈绽放之势 + +当与数据打交道的工作人员开始将他们的流程自动化,那么写批处理作业是不可避免的。这些作业必须按照一个给定的时间安排执行,它们通常依赖于一组已有的数据集,并且其它的作业也会依赖于它们。即使你让好几个数据工作节点在一起工作很短的一段时间,用于计算的批处理作业也会很快地扩大成一个复杂的图。现在,如果有一个工作节奏快、中型规模的数据团队,而且他们在几年之内要面临不断改进的数据基础设施,并且手头上还有大量复杂的计算作业网络。那这个复杂性就成为数据团队需要处理,甚至深入了解的一个重要负担。 + +这些作业网络通常就是 **有向无环图**(**DAGs**),它们具有以下属性: + +* **已排程:** 每个作业应该按计划好的时间间隔运行 +* **关键任务:** 如果一些作业没有运行,那我们就有麻烦了 +* **演进:** 随着公司和数据团队的成熟,数据处理也会变得成熟 +* **异质性:** 现代化的分析技术栈正在快速发生着改变,而且大多数公司都运行着好几个需要被粘合在一起的系统 + +### 每个公司都有一个(或者多个) + +**工作流程管理** 已经成为一个常见的需求,因为大多数公司内部有多种创建和调度作业的方式。你总是可以从古老的 cron 调度器开始,并且很多供应商的开发包都自带调度功能。下一步就是创建脚本来调用其它的脚本,这在短期时间内是可以工作的。最终,一些为了解决作业状态存储和依赖的简单框架就涌现了。 + +通常,这些解决方案都是 **被动增长** 的,它们都是为了响应特定作业调度需求的增长,而这通常也是因为现有的这种系统的变种连简单的扩展都做不到。同时也请注意,那些编写数据管道的人通常不是软件工程师,并且他们的任务和竞争力都是围绕着处理和分析数据的,而不是搭建工作流程管理系统。 + +鉴于公司内部工作流程管理系统的成长总是比公司的需求落后至少一代,作业的编辑、调度和错误排查之间的 **摩擦** 制造了大量低效且令人沮丧的事情,这使得数据工作者和他们的高产出路线背道而驰。 + +### Airflow + +在评审完开源解决方案,同时听取 Airbnb 的员工对他们过去使用的系统的见解后,我们得出的结论是市场上没有任何可以满足我们当前和未来需求的方案。我们决定搭建一个崭新的系统来正确地解决这个问题。随着这个项目的开发进展,我们意识到我们有一个极好的机会去回馈我们也极度依赖的开源社区。因此,我们决定依照 Apache 的许可开源这个项目。 + +这里是 Airbnb 的一些靠 Airflow 推动的处理工序: + +* **数据仓储:** 清洗、组织规划、数据质量检测并且将数据发布到我们持续增长的数据仓库中去 +* **增长分析:** 计算关于住客和房主参与度的指标以及增长审计 +* **试验:** 计算我们 A/B 测试试验框架的逻辑并进行合计 +* **定向电子邮件:** 对目标使用规则并且通过群发邮件来吸引用户 +* **会话(Sessionization):** 计算点击流和停留时间的数据集 +* **搜索:** 计算搜索排名相关的指标 +* **数据基础架构维护:** 数据库抓取、文件夹清理以及应用数据留存策略... + +### 架构 + +就像英语是商务活动经常使用的语言一样,Python 已经稳固地将自己树立为数据工作的语言。Airflow 从创建之初就是用 Python 编写的。代码库可扩展、文档齐全、风格一致、语法过检并且有很高的单元测试覆盖率。 + +管道的编写也是用 Python 完成的,这意味着通过配置文件或者其他元数据进行动态管道生成是与生俱来的。“**配置即代码**” 是我们为了达到这个目的而坚守的准则。虽然基于 yaml 或者 json 的作业配置方式可以让我们用任何语言来生成 Airflow 数据管道,但是我们感觉到转化过程中的一些流动性丧失了。能够内省代码(ipython!和集成开发工具)子类和元程序并且使用导入的库来帮助编写数据管道为 Airflow 增加了巨大的价值。注意,只要你能写 Python 代码来解释配置,你还是可以用任何编程语言或者标记语言来编辑作业。 + +你仅需几行命令就可以让 Airflow 运行起来,但是它的完整架构包含有下面这么多组件: + +* **作业定义**,包含在源代码控制中。 +* 一个丰富的 **命令行工具** (命令行接口) 用来测试、运行、回填、描述和清理你的有向无环图的组成部件。 +* 一个 **web 应用程序**,用来浏览有向无环图的定义、依赖项、进度、元数据和日志。web 服务器打包在 Airflow 里面并且是基于 Python web 框架 Flask 构建的。 +* 一个 **元数据仓库**,通常是一个 MySQL 或者 Postgres 数据库,Airflow 可以用它来记录任务作业状态和其它持久化的信息。 +* 一组 **工作节点**,以分布式的方式运行作业的任务实例。 +* **调度** 程序,触发准备运行的任务实例。 + +### 可扩展性 + +Airflow 自带各种与 Hive、Presto、MySQL、HDFS、Postgres 和 S3 这些常用系统交互的方法,并且允许你触发任意的脚本,基础模块也被设计得非常容易进行扩展。 + +**Hooks** 被定义成外部系统的抽象并且共享同样的接口。Hooks 使用中心化的 vault 数据库将主机/端口/登录名/密码信息进行抽象并且提供了可供调用的方法来跟这些系统进行交互。 + +**操作符** 利用 hooks 生成特定的任务,这些任务在实例化后就变成了数据流程中的节点。所有的操作符都派生自 BaseOperator 并且继承了一组丰富的属性和方法。三种主流的操作符分别是: + +* 执行 **动作** 的操作符, 或者通知其它系统去执行一个动作 +* **转移** 操作符将数据从一个系统移动到另一个系统 +* **传感器** 是一类特定的操作符,它们会一直运行直到满足了特定的条件 + +**执行器(Executors)** 实现了一个接口,它可以让 Airflow 组件(命令行接口、调度器和 web 服务器)可以远程执行作业。目前,Airflow 自带一个 SequentialExecutor(用来做测试)、一个多线程的 LocalExecutor、一个使用了 [Celery](http://www.celeryproject.org/) 的 CeleryExecutor 和一个超棒的基于分布式消息传递的异步任务队列。我们也计划在不久后开源 YarnExecutor。 + +### 一个绚丽的用户界面 + +虽然 Airflow 提供了一个丰富的[命令行接口](https://airflow.apache.org/cli.html),但是最好的工作流监控和交互办法还是使用 web 用户接口。你可以容易地图形化显示管道依赖项、查看进度、轻松获取日志、查阅相关代码、触发任务、修正 false positives/negatives 以及分析任务消耗的时间,同时你也能得到一个任务通常在每天什么时候结束的全面视图。用户界面也提供了一些管理功能:管理连接、池和暂停有向无环图的进程。 + +![](https://cdn-images-1.medium.com/max/400/1*nbwR8O-CDH67fkHrXVDvYw.png) + +![](https://cdn-images-1.medium.com/max/400/1*0Mask8UZw_aCsd_7JM2Rjw.png) + +![](https://cdn-images-1.medium.com/max/400/1*JNOJotSnC3t0TIQC8gYcsg.png) + +![](https://cdn-images-1.medium.com/max/600/1*qqOg_8bMS_MzDgWSbgdtOw.png) + +![](https://cdn-images-1.medium.com/max/400/1*rNaZuJ2168jvUYiEkdu1ww.png) + +![](https://cdn-images-1.medium.com/max/400/1*ojItdtSC6etsUWOZIK8trw.png) + +锦上添花的是,用户界面有一个 [Data Profiling](https://airflow.apache.org/profiling.html) 区,可以让用户在注册好的连接上进行 SQL 查询、浏览结果集,同时也提供了创建和分享一些简单图表的方法。这个制图应用是由 [Highcharts](http://www.highcharts.com/)、[Flask Admin](https://flask-admin.readthedocs.org/en/v1.0.9/) 的增删改查接口以及 Airflow 的 [hooks](https://airflow.apache.org/code.html#hooks) 和 [宏](https://airflow.apache.org/code.html#macros)库混搭而成的。URL 参数可以传递给你图表中使用的 SQL,Airflow 的宏是通过 [Jinja templating](http://jinja.pocoo.org/) 的方式工作的。有了这些特性和查询功能,Airflow 用户可以很容易的创建和分享结果集和图表。 + +![](https://cdn-images-1.medium.com/max/400/1*8SD5x-62kLVzZ9SSfAXKCg.png) + +![](https://cdn-images-1.medium.com/max/400/1*2L-uvEnYDvf5FG3eMuknuQ.png) + +![](https://cdn-images-1.medium.com/max/400/1*EbUXRyeS65GZTXbCPWrF7w.png) + +### 一种催化剂 + +使用 Airflow 之后,Airbnb 的员工进行数据工作的生产率和热情提高了好几倍。管道的编写也加速了,监控和错误排查所花费的时间也显著减少了。更重要的是,这个平台允许人们从一个更高级别的抽象中去创建可重用的模块、计算框架以及服务。 + +### 说得够多的了! + +我们已经通过一个启发式的教程把试用 Airflow 变得极其简单。想看到示例结果也只需要执行几个 shell 命令。看一看 [Airflow 文档](https://airflow.apache.org/) 的[快速上手](https://airflow.apache.org/start.html)和[教程](https://airflow.apache.org/tutorial.html)部分,你可以在几分钟之内就让你的 Airflow web 程序以及它自带的交互式实例跑起来! + +[https://github.com/airbnb/airflow](https://github.com/apache/incubator-airflow) + +![](https://cdn-images-1.medium.com/max/800/1*YsUOrWx3mRxZZljtc9xZyw.png) + +#### 在 [airbnb.io](http://airbnb.io) 上查看我们所有的开源项目并 在 Twitter 上关注我们:[@AirbnbEng](https://twitter.com/AirbnbEng) + [@AirbnbData](https://twitter.com/AirbnbData) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/algorithms-behind-modern-storage-systems.md b/TODO1/algorithms-behind-modern-storage-systems.md index 839c25be8bd..e4caa7e1597 100644 --- a/TODO1/algorithms-behind-modern-storage-systems.md +++ b/TODO1/algorithms-behind-modern-storage-systems.md @@ -1,226 +1,226 @@ > * 原文地址:[Algorithms Behind Modern Storage Systems](https://queue.acm.org/detail.cfm?id=3220266) -> * 原文作者:[acmqueue](https://queue.acm.org/) +> * 原文作者:[Alex Petrov](http://coffeenco.de/) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/algorithms-behind-modern-storage-systems.md](https://github.com/xitu/gold-miner/blob/master/TODO1/algorithms-behind-modern-storage-systems.md) -> * 译者: -> * 校对者: +> * 译者:[LeopPro](https://github.com/LeopPro) +> * 校对者:[zephyrJS](https://github.com/zephyrJS) [FesonX](https://github.com/FesonX) -# Algorithms Behind Modern Storage Systems +# 支撑现代存储系统的算法 -## Different uses for read-optimized B-trees and write-optimized LSM-trees +## 读优化 B-Tree 和写优化 LSM-Tree 的不同用途 -### Alex Petrov +### 作者:Alex Petrov -The amounts of data processed by applications are constantly growing. With this growth, scaling storage becomes more challenging. Every database system has its own tradeoffs. Understanding them is crucial, as it helps in selecting the right one from so many available choices. +随着应用程序处理的数据量不断增长,扩展存储变得愈发具有挑战性。每个数据库系统都有自己的方案。为了从这些方案中做出正确的选择,了解它们是至关重要的。 -Every application is different in terms of read/write workload balance, consistency requirements, latencies, and access patterns. Familiarizing yourself with database and storage internals facilitates architectural decisions, helps explain why a system behaves a certain way, helps troubleshoot problems when they arise, and fine-tunes the database for your workload. +每个应用程序在读写负载平衡、一致性、延迟和访问模式方面各不相同。熟悉数据库和底层存储能帮助你进行架构决策、解释系统行为、排除故障以及根据具体情况调优。 -It's impossible to optimize a system in all directions. In an ideal world there would be data structures guaranteeing the best read and write performance with no storage overhead but, of course, in practice that's not possible. +优化一个系统不可能做到面面俱到。我们当然希望有一个数据结构既能保证最佳的读写性能,又不需要任何存储开销,但显然,这是不存在的。 -This article takes a closer look at two storage system design approaches used in a majority of modern databases—read-optimized [B-trees](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.96.6637&rep=rep1&type=pdf)1 and write-optimized [LSM (log-structured merge)-trees](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.44.2782&rep=rep1&type=pdf)5—and describes their use cases and tradeoffs. +本文深入讨论了大多数现代数据库中使用的两种存储系统设计 —— 读优化 [B-Tree](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.96.6637&rep=rep1&type=pdf) [1] 和写优化 [LSM(log-structured merge)-Tree](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.44.2782&rep=rep1&type=pdf) [5] —— 并描述了它们的用例和优缺权衡。 -### B-Trees +### B-Tree -B-trees are a popular read-optimized indexing data structure and generalization of binary trees. They come in many variations and are used in many databases (including [MySQL InnoDB](https://dev.mysql.com/doc/refman/5.7/en/innodb-physical-structure.html)4 and [PostgreSQL](http://www.interdb.jp/pg/pgsql01.html)7) and even [file systems](https://en.wikipedia.org/wiki/HTree) (HFS+8, HTrees in ext49). The _B_ in _B_-tree stands for _Bayer_, the author of the original data structure, or _Boeing_, where he worked at that time. +B-Tree 是一种流行的读优化索引数据结构,是二叉树的泛化。它有许多变种,并且被用于多种数据库(包括 [MySQL InnoDB](https://dev.mysql.com/doc/refman/5.7/en/innodb-physical-structure.html) [4]、[PostgreSQL](http://www.interdb.jp/pg/pgsql01.html) [7])甚至[文件系统](https://en.wikipedia.org/wiki/HTree)(HFS+ [8]、HTrees ext4 [9])。B-Tree 中的 _B_ 代表原始数据结构的作者 _Bayer_,或是他当时就职的公司 _Boeing_。 -In a [binary tree](https://en.wikipedia.org/wiki/Binary_tree) every node has two children (referred as a left and a right child). Left and right subtrees hold the keys that are less than and greater than the current node key, respectively. To keep the tree depth to a minimum, a binary tree has to be balanced: when randomly ordered keys are being added to the tree, it is natural that one side of the tree will eventually get deeper than the other. +在[搜索二叉树](https://en.wikipedia.org/wiki/Binary_tree)中,每个节点都有两个孩子(称为左右孩子)。左子树的节点值小于当前节点值,右子树反之。为了保持树的深度最小,搜索二叉树必须是平衡的:当随机顺序的值被添加到树中时,如果不加调整,终会导致树的倾斜。 -One way to rebalance a binary tree is to use so-called rotation: rearrange nodes, pushing the parent node of the longer subtree down below its child and pulling this child up, effectively placing it in its parent's position. Figure 1 is an example of rotation used for balancing in a binary tree. On the left, a binary tree is unbalanced after adding node 2 to it. In order to balance the tree, node 3 is used as a pivot (the tree is rotated around it). Then node 5, previously a root node and a parent node for 3, becomes its child node. After the rotation step is done, the height of the left subtree decreases by one and the height of the right subtree increases by one. The maximum depth of the tree has decreased. +一种平衡二叉树的方法是所谓的旋转:重新排列节点,将较深子树的父节点向下推到其子节点下方,并将该子节点拉上来,将其放在原父节点的位置。图 1 是平衡二叉树中的旋转示例。在左侧添加节点 2 后,二叉树失去平衡。为了使该树平衡,将其以节点 3 为轴旋转(树围绕它旋转)。然后节点 5(旋转前是根节点和节点 3 的父节点)成为其子节点。旋转完成后,左侧子树的深度减少 1,右侧子树的深度增加 1。树的最大深度已经减小。 -![Algorithms Behind Modern Storage Systems](http://deliveryimages.acm.org/10.1145/3230000/3220266/petrov1.png) +![支撑现代存储系统的算法](https://s1.ax1x.com/2018/05/18/C6yZiF.png) -Binary trees are most useful as in-memory data structures. Because of balancing (the need to keep the depth of all subtrees to a minimum) and low fanout (a maximum of two pointers per node), they don't work well on disk. B-trees allow for storing more than two pointers per node and work well with block devices by matching the node size to the page size (e.g., 4 KB). Some implementations today use larger node sizes, spanning across multiple pages in size. +二叉树是最有用的内存数据结构。然而由于平衡(保持所有子树的深度最小)和低出度(每个节点最多两个子节点),它们在磁盘上水土不服。B-Tree 允许每个节点存储两个以上的指针,并通过将节点大小与页面大小(例如,4 KB)进行匹配来与块设备协同工作。今天的一些实现将使用更大的节点大小,跨越多个页面。 -B-trees have the following properties: +B-Tree 有以下几个性质: -• Sorted. This allows sequential scans and simplifies lookups. +• 有序。这允许顺序扫描并简化查找。 -• Self-balancing. There's no need to balance the tree during insertion and deletion: when a B-tree node is full, it is split in two, and when the occupancy of the neighboring nodes falls below a certain threshold, the nodes are merged. This also means that leaves are equally distant from the root and can be located using the same amount of steps during lookup. +• 自平衡。在插入和删除时不需要平衡树:当 B-Tree 节点已满时,它被分成两部分,并且当相邻节点的利用率低于某个阈值时,合并这两个节点。这也意味着各叶子节点与根节点的距离相等,并且在查找过程中定位的步数是相同的。 -• Guarantee of logarithmic lookup time. This makes B-trees a good choice for database indexes, where lookup times are important. +• 对数级查找时间复杂度。查找时间是非常重要的,这使得 B-Tree 成为数据库索引的理想选择。 -• Mutable. Inserts, updates, and deletes (also, subsequent splits and merges) are performed on disk in place. To make in-place updates possible, a certain amount of space overhead is required. A B-tree can be organized as a clustered index, where actual data is stored on the leaf nodes or as a heap file with an unclustered B-tree index. +• 易变。插入、更新、删除(包括因此导致的拆分和合并)过程在磁盘上进行。为了使就地更新成为可能,需要一定的空间开销。B-Tree 可以作为聚集索引,实际数据存储在叶子节点上,也可以作为非聚集索引,称为一个堆文件。 -This article discusses the B+tree,3 a modern variant of the B-tree often used for database storage. The B+tree is different from the original B-tree1 in that (a) it has an additional level of linked leaf nodes holding the values, and (b) these values cannot be stored on internal nodes. +本文讨论的 B+Tree [3] 是一种经常用于数据库存储的 B-Tree 现代变种。B+Tree 与原始 B-Tree [1] 的不同之处在于:(1)它采用额外链接的叶节点存储值;(2)值不能存储在内部节点上。 -#### Anatomy of the B-tree +#### 剖析 B-Tree -Let's first take a closer look at the B-tree building blocks, illustrated in figure 2. B-trees have several node types: root, internal, and leaf. Root (top) is the node that has no parents (i.e., it is not a child of any other node). Internal nodes (middle) have both a parent and children; they connect a root node with leaf nodes. Leaf nodes (bottom) carry the data and have no children. Figure 2 depicts a B-tree with a branching factor of four (four pointers, three keys in internal nodes, and four key/value pairs on leaves). +我们先来仔细看看 B-Tree 的结构,如图 2 所示。B-Tree 的节点有几种类型:根节点,内部节点和叶子节点。根节点(顶部)是没有双亲的节点(即,它不是任何节点的子节点)。内部节点(中间)有双亲和孩子节点;他们将根节点和叶子节点连接起来。叶子节点(底部)持有数据并且没有孩子节点。图 2 描绘了分支因子为 4(4 个指针,内部节点中有 3 个键,叶上有 4 个键/值对)的 B-Tree。 -![Algorithms Behind Modern Storage Systems](http://deliveryimages.acm.org/10.1145/3230000/3220266/petrov2.png) +![支撑现代存储系统的算法](https://s1.ax1x.com/2018/05/18/C6qgs0.png) -B-trees are characterized by the following: +B-Tree 的特性如下: -• Branching factor-the number (_N_) of pointers to the child nodes. Along with the pointers, root and internal nodes hold up to _N-1_ keys. +• 分支因子 —— 指向子节点的指针数(_N_)。除指针外,根节点和内部节点还持有 N-1 个键。 -• Occupancy-how many pointers to child items the node is currently holding, out of the maximum available. For example, if the tree-branching factor is _N_, and the node is currently holding _N/2_ pointers, occupancy is 50 percent. +• 利用率 —— 节点当前持有的指向子节点的指针数量与可用最大值之比。例如,若某树分支因子是 _N_,且其中某节点当前持有 _N/2_ 个指针,则该节点利用率为 50%。 -• Height-the number of B-tree levels, signifying how many pointers have to be followed during lookup. +• 高度 —— B-Tree 的数量级,表示在查找过程中必须经过多少指针。 -Every nonleaf node in the tree holds up to _N_ keys (index entries), separating the tree into _N+1_ subtrees that can be located by following a corresponding pointer. Pointer _i_ from an entry _Ki_ points to a subtree in which all the index entries are such that _Ki-1 <= Ksearched < Ki_ (where _K_ is a set of keys). The first and last pointers are special cases, pointing to subtrees in which all the entries are less than or equal to _K0_ in the case of the leftmost child, or greater than _KN-1_ in the case of the rightmost child. A leaf node may also hold a pointer to the previous and next nodes on the same level, forming a doubly linked list of sibling nodes. Keys in all the nodes are always sorted. +树中的每个非叶节点最多可持有 _N_ 个键(索引条目),这些键将树分为 _N+1_ 个子树,这些子树可以通过相应的指针定位。项 _Ki_ 中的指针 _i_ 指向某子树,该子树中包含所有 _Ki-1 <= K目标 < Ki_(其中 _K_ 是一组键)的索引项。首尾指针是特殊的,它们指向的子树中所有的项都小于等于最左子节点的 _K0_ 或大于最右子节点的 _KN-1_。叶子节点同时持有其同级前后节点的指针,形成兄弟节点间的双向链表。所有节点中的键总是有序的。 -#### Lookups +#### 查找 -When performing lookups, the search starts at the root node and follows internal nodes recursively down to the leaf level. On each level, the search space is reduced to the child subtree (the range of this subtree includes the searched value) by following the child pointer. Figure 3 shows a lookup in a B-tree making a single root-to-leaf pass, following the pointers “between” the two keys, one of which is greater than (or equal to) the searched term, and the other of which is less than the searched term. When a point query is performed, the search is complete after locating the leaf node. During the range scan, the keys and values of the found leaf, and then the sibling leaf's nodes, are traversed, until the end of the range is reached. +进行查找时,将从根节点开始搜索,并经过内部节点递归向下到叶子节点层。在每层中,通过指向子节点的指针将搜索范围缩小到某子树(包含搜索目标值的子树)。图 3 展示了 B-Tree 的一次从根到叶的搜索过程,指针在两个键之间,其中一个大于(或等于)搜索目标,另一个小于搜索目标。进行点查询时,搜索将在定位到叶子节点后完成。进行范围扫描时,遍历所找到的叶子节点的键和值,然后遍历范围内的兄弟叶子节点。 -![Algorithms Behind Modern Storage Systems](http://deliveryimages.acm.org/10.1145/3230000/3220266/petrov3.png) +![支撑现代存储系统的算法](https://s1.ax1x.com/2018/05/19/Cc6dRe.png) -In terms of complexity, B-trees guarantee _log(n)_ lookup, because finding a key within the node is performed using binary search, shown in figure 4. Binary search is easily explained in terms of searching for words beginning with a certain letter in the dictionary, where all words are sorted alphabetically. First you open the dictionary exactly in the middle. If the searched letter is alphabetically “less than” (appears earlier than) the one opened, you continue your search in the left half of the dictionary; otherwise, you continue in the right half. You keep reducing the remaining page range by half and picking the side to follow until you find the desired letter. Every step halves the search space, making the lookup time logarithmic. Searches in B-trees have logarithmic complexity, since on the node level keys are sorted, and the binary search is performed in order to find a match. This is also why it's important to keep the occupancy high and uniform across the tree. +在复杂度方面,B-Tree 保证查询的时间复杂度为 _log(n)_,因为查找一个节点中的键使用二分查找,如图 4 所示。二进制搜索可以通俗的解释为在字典中查找以某字母开头的单词,字典中所有单词都按字母顺序排序。首先你翻开正好在字典中间的一页。如果要查找的单词字母顺序小于(在前面)当前页,你继续在字典的左半边查找;否则就继续在右半边查找。你继续像这样将剩余的页码范围分为一半,选择一边,直到找到期望的字母。每一步都将搜索范围减半,因此查找的时间复杂度为对数级。 B-Tree 节点上的键是有序的,且使用二分查找算法进行匹配,因此 B-Tree 的搜索复杂度是对数级的。这也说明了保持树的高利用率和统一访问的重要性。 -![Algorithms Behind Modern Storage Systems](http://deliveryimages.acm.org/10.1145/3230000/3220266/petrov4.png) +![支撑现代存储系统的算法](https://s1.ax1x.com/2018/05/19/CcRZy4.png) -#### Insertions, updates, and deletions +#### 插入、更新、删除 -When performing insertions, the first step is to locate the target leaf. For that, the aforementioned search algorithm is used. After the target leaf is located, key and value are appended to it. If the leaf does not have enough free space, this situation is called overflow, and the leaf has to be split in two. This is done by allocating a new leaf, moving half the elements to it and appending a pointer to this newly allocated leaf to the parent. If the parent doesn't have free space either, a split is performed on the parent level as well. The operation continues until the root is reached. When the root overflows, its contents are split between the newly allocated nodes, and the root node itself is overwritten in order to avoid relocation. This also implies that the tree (and its height) always grows by splitting the root node. +进行插入时,第一步是定位目标叶子节点。此过程使用前序搜索算法。在定位目标叶子节点后,键和值将被添加至该节点。如果该节点没有足够的可用空间,这种情况称为溢出,则将叶子节点分割成两部分。这是通过分配一个新的叶子节点,将一半元素移动到新节点并将一个指向这个新节点的指针添加到父节点来完成的。如果父节点没有足够的空间,则也会在父节点上进行分割。操作一直持续到根节点为止。当根节点溢出时,其内容在新分配的节点之间被分割,根节点本身被覆盖以避免重定位。这也意味着树(及其高度)总是通过分裂根节点而增长。 -### LSM-Trees +### LSM-Tree -The log-structured merge-tree is an immutable disk-resident write-optimized data structure. It is most useful in systems where writes are more frequent than lookups that retrieve the records. LSM-trees have been getting more attention because they can eliminate random insertions, updates, and deletions. +结构化日志合并树是一个不可变的基于磁盘的写优化数据结构。它适用于写入比查询操作更频繁的场景。LSM-Tree 已经获得了更多的关注,因为它可以避免随机插入,更新和删除。 -#### Anatomy of the LSM-tree +#### 剖析 LSM-Tree -To allow sequential writes, LSM-trees batch writes and updates in a memory-resident table (often implemented using a data structure allowing logarithmic time lookups, such as a [binary search tree](https://en.wikipedia.org/wiki/Self-balancing_binary_search_tree) or [skip list](https://en.wikipedia.org/wiki/Skip_list)) until its size reaches a threshold, at which point it is written on disk (this operation is called a _flush_). Retrieving the data requires searching all disk-resident parts of the tree, checking the in-memory table, and merging their contents before returning the result. Figure 5 shows the structure of an LSM-tree: a memory-resident table used for writes. Whenever the memory table is large enough, its sorted contents are written on disk. Reads are served, hitting both disk- and memory-resident tables, requiring a merge process to reconcile the data. +为了允许连续写入,LSM-Tree 在内存中的表(通常使用支持查找的时间复杂度为对数级的数据结构,例如二叉搜索树或跳跃表)中批量写入和更新,当其大小达到阈值时将它写在磁盘上(这个操作称为刷新)。检索数据时需要搜索树所有磁盘中的部分,检查内存中的表,合并它们的内容,然后再返回结果。图 5 展示了 LSM-Tree 的结构:用于写入的基于内存的表。只要内存表体积达到一定程度,内存表就会被写入磁盘。进行读取时,同时读取磁盘和内存表,通过一个合并操作来整合数据。 -![Algorithms Behind Modern Storage Systems](http://deliveryimages.acm.org/10.1145/3230000/3220266/petrov5.png) +![支撑现代存储系统的算法](https://s1.ax1x.com/2018/05/20/CgQ3cQ.png) -#### Sorted String Tables +#### 有序串行表 -Many modern LSM-tree implementations (such as [RocksDB](https://en.wikipedia.org/wiki/RocksDB) and [Apache Cassandra](https://en.wikipedia.org/wiki/Apache_Cassandra)) implement disk-resident tables as SSTables (Sorted String Tables), because of their simplicity (easy to write, search, and read) and merge properties (during the merge, source SSTable scans and merged result writes are sequential). +因为 SSTable(有序串行表)的简单性(易于写入,搜索和读取)与合并性能(合并期间,扫描源 SSTable,合并结果的写入是顺序的),多数现代的 LSM-Tree 实现(例如 [RocksDB](https://en.wikipedia.org/wiki/RocksDB) 和 [Apache Cassandra](https://en.wikipedia.org/wiki/Apache_Cassandra))都选用 SSTable 作为硬盘表。 -An SSTable is a disk-resident ordered immutable data structure. Structurally, an SSTable is split into two parts: data and index blocks, as shown in figure 6. A data blocks consists of sequentially written unique key/value pairs, ordered by key. An index block contains keys mapped to data-block pointers, pointing to where the actual record is located. An index is often implemented using a format optimized for quick searches, such as a B-tree, or using a hash table for a point-query. Every value item in an SSTable has a timestamp associated with it. This specifies the write time for inserts and updates (which are often indistinguishable) and removal time for deletes. +SSTable 是一种基于硬盘的有序不可变的数据结构。从结构上来看,SSTable 可以分为两部分:数据块和索引块,如图 6 所示。数据块包含以键为顺序写入的唯一键值对。索引块包含映射到数据块指针的键,指针指向实际记录的位置。为了快速搜索,索引一般使用优化的结构实现,例如 B-Tree 或用于点查询的哈希表。SSTable 中的每一个值都有一个时间戳与之对应。时间戳记录了插入、更新(这两者一般不做区分)和删除时间。 -![Algorithms Behind Modern Storage Systems](http://deliveryimages.acm.org/10.1145/3230000/3220266/petrov6.png) +![支撑现代存储系统的算法](https://s1.ax1x.com/2018/05/22/C2vwlD.png) -SSTables have some nice properties: +SSTable 具有以下优点: -• Point-queries (i.e., finding a value by key) can be done quickly by looking up the primary index. +• 通过查询主键索引可以实现快速的点查询(例如,通过键寻找值)。 -• Scans (i.e., iterating over all key/value pairs in a specified key range) can be done efficiently simply by reading key/value pairs sequentially from the data block. +• 只需要顺序读取数据块上的键值对就可以实现扫描(例如,遍历制定范围内的键值对)。 -An SSTable represents a snapshot of all database operations over a period of time, as the SSTable is created by the _flush_ process from the memory-resident table that served as a buffer for operations against the database state for this period. +SSTable 代表一段时间内所有数据库操作的快照,因为 SSTable 是通过对内存表的**刷新**操作创建的,该表充当此时段内对数据库状态的缓冲区。 -#### Lookups +#### 查询 -Retrieving data requires searching all SSTables on disk, checking the memory-resident tables, and merging their contents before returning the result. The merge step during the read is required since the searched data can reside in multiple SSTables. +检索数据需要搜索硬盘上的所有 SSTable,检查内存表,并且合并它们的内容后返回结果。要搜索的数据可以存储在多个 SSTable 中,因此合并步骤是必须的。 -The merge step is also necessary to ensure that the deletes and updates work. Deletes in an LSM-tree insert placeholders (often called _tombstones_), specifying which key was marked for deletion. Similarly, an update is just a record with a later timestamp. During the read, the records that get shadowed by deletes are skipped and not returned to the client. A similar thing happens with the updates: out of two records with the same key, only the one with the later timestamp is returned. Figure 7 shows a merge step reconciling the data stored in separate tables for the same key: as shown here, the record for Alex was written with timestamp 100 and updated with a new phone and timestamp 200; the record for John was deleted. The other two entries are taken as is, as they're not shadowed. +合并步骤也是确保删除和更新正常工作所必需的。在 LSM-Tree 中,通过插入占位符(通常称为**墓碑**)来指定哪个键被标记为删除。同样的,更新操作只是提交一个带较晚时间戳的记录。在读取期间,被标记删除的记录被跳过,不会返回给客户端。更新操作与之类似:在具有相同键的两个记录中,只返回具有较晚时间戳的记录。图 7 展示了一次合并操作,用于对在不同表中存储的同一个键的数据进行整合:如图,Alex 记录中时间戳是 100,随后更新了新的电话,时间戳为 200;John 记录被删除。另外两项没有改变,因为它们没有被覆盖。 -![Algorithms Behind Modern Storage Systems](http://deliveryimages.acm.org/10.1145/3230000/3220266/petrov7.png) +![支撑现代存储系统的算法](https://s1.ax1x.com/2018/05/23/CRyDYV.png) -To reduce the number of searched SSTables and to avoid checking every SSTable for the searched key, many storage systems employ a data structure known as a [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter)10. This is a probabilistic data structure that can be used to test whether an element is a member of the set. It can produce false-positive matches (i.e., state that the element is a member of set, while it is not, in fact, present there) but cannot produce false negatives (i.e., if a negative match is returned, the element is guaranteed not to be a member of the set). In other words, a Bloom filter is used to tell if the key “might be in an SSTable” or “is definitely not in an SSTable.” SSTables for which a Bloom filter has returned a negative match are skipped during the query. +为了减少搜索 SSTable ,防止为了查找某个键而搜索每个 SSTable,许多存储系统采用一个被称为[布隆过滤器](https://en.wikipedia.org/wiki/Bloom_filter) [10] 的数据结构。这是一个概率数据结构,可用于测试某个元素是否属于某集合。它有可能产生错误的肯定(即,判断元素是集合的成员,但实际上并不是),但不能产生错误的否定(即,如果返回否定结果,则元素一定不是集合的成员)。换句话说,布隆过滤器用于判断键“可能在 SSTable 中”或“绝对不在 SSTable 中”。在搜索过程中,将会跳过布隆过滤器返回否定结果的 SSTable。 -#### LSM-tree maintenance +#### LSM-Tree 的维护 -Since SSTables are _immutable_, they are written sequentially and hold no reserved empty space for in-place modifications. This means insert, update, or delete operations would require rewriting the whole file. All operations modifying the database state are “batched” in the memory-resident table. Over time, the number of disk-resident tables will grow (data for the same key located in several files, multiple versions of the same record, redundant records that got shadowed by deletes), and the reads will continue getting more expensive. +由于 SSTable 是**不可变**的,因此它们会按顺序写入,并且不存在用于修改的预留空白空间。这就意味着插入、更新或删除操作将需要重写整个文件。所有修改数据库状态的操作都在内存表中“批处理”。随着时间的推移,磁盘表的数量将增加(同一个键的数据位于几个不同文件,同一记录有多个不同的版本,被删除的冗余记录),读取操作的开销将变得越来越大。 -To reduce the cost of reads, reconcile space occupied by shadowed records, and reduce the number of disk-resident tables, LSM-trees require a _compaction_ process that reads complete SSTables from disk and merges them. Because SSTables are sorted by key and compaction works like merge-sort, this operation is very efficient: records are read from several sources sequentially, and merged output can be appended to the results file right away, also sequentially. One of the advantages of merge-sort is that it can work efficiently even for merging large files that don't fit in memory. The resulting table preserves the order of the original SSTables. +为了降低读取开销,整合被删除记录占用的空间并减少磁盘表的数量,LSM-Tree 需要一个**压缩**操作,从磁盘读取完整的 SSTable 并合并它们。由于 SSTable 是以键排序的,因此其压缩工作和归并排序类似,是非常高效的操作:从多个源有序序列中读取记录,进行合并后的输出马上追加到结果文件中,则结果文件也是有序的。归并排序的一个优点是,即使合并内存吃不消的大文件,它依旧可以高效地工作。结果表保留了原始 SSTable 的顺序。 -During this process, merged SSTables are discarded and replaced with their “compacted” versions, as shown in figure 8. Compaction takes multiple SSTables and merges them into one. Some database systems logically group the tables of the same size to the same “level” and start the merge process whenever enough tables are on a particular level. After compaction, the number of SSTables that have to be addressed is reduced, making queries more efficient. +在此过程中,被合并的 SSTable 被丢弃并替换为其“压缩”后的版本,如图 8 所示。压缩多个 SSTable 并将它们合并为一个。某些数据库系统在逻辑层面上按大小把不同的表分为不同级别,分组到相同的“级别”,并在特定级别的表足够多时开始合并操作。压缩后,SSTable 的数量减少,提高查询效率。 -![Algorithms Behind Modern Storage Systems](http://deliveryimages.acm.org/10.1145/3230000/3220266/petrov8.png) +![支撑现代存储系统的算法](https://s1.ax1x.com/2018/05/27/Ch4JKI.png) -### Atomicity and Durability +### 原子性与持久性 -To reduce the number of I/O operations and make them sequential, both B-trees and LSM-trees batch operations in memory before making an actual update. This means that data integrity is not guaranteed during failure scenarios and _atomicity_ (applying a series of changes atomically, as if they were a single operation, or not applying them at all) and _durability_ (ensuring that in the face of a process crash or power loss, data has reached persistent storage) properties are not ensured. +为了减少 I/O 操作并使它们顺序执行,无论是 B-Tree 还是 LSM-Tree 都在实际更新之前,先在内存中进行批量操作。这意味着,在故障情况时,数据完整性、**原子性**(将一系列操作赋予原子性,将它们视为一个操作,要么全部执行要么全不执行)、**持久性**(当进程崩溃或电源失效时,可以确保数据已经到达持久性存储设备)得不到保证。 -To solve that problem, most modern storage systems employ WAL (write-ahead logging). The key idea behind WAL is that all the database state modifications are first durably persisted in the append-only log on disk. If the process crashes in the middle of an operation, the log is replayed, ensuring that no data is lost and all changes appear atomically. +为了解决这个问题,大多数现代存储系统采用 WAL(预写日志)。WAL 的核心思想是,所有数据库状态改变都先持久化进硬盘中的只追加日志中。如果进程在工作中崩溃,将会重映日志以确保没有数据丢失且所有更改都满足原子性。 -In B-trees, using WAL can be understood as writing changes to data files only after they have been logged. Usually log sizes for B-tree storage systems are relatively small: as soon as changes are applied to the persisted storage, they can be discarded. WAL serves as a backup for the in-flight operations: any changes that were not applied to data pages can be redone from the log records. +在 B-Tree 中,使用 WAL 可以理解为仅在写入操作被记录后才将其写入数据文件。通常,B-Tree 存储系统的日志尺寸相对较小:只要将更改应用于持久存储,它们就可以被弃用。WAL 还可以作为运行时操作的备份:任何未应用于数据页的更改都可以根据日志记录重做。 -In LSM-trees, WAL is used to persist changes that have reached the memtables but have not yet been fully flushed on disk. As soon as a memtable is fully flushed and switched so that read operations can be served from the newly created SSTable, the WAL segment holding the data for the flushed memtable can be discarded. +在 LSM-Tree 中,WAL 用于保存处于内存表但尚未完全刷新到磁盘上的更改。只要内存表被刷新完毕并置换,便可以在新创建的 SSTable 中进行读取操作,则 WAL 中从内存表刷新到硬盘上的那部分更改就可以丢弃了。 -### Summarizing +### 总结 -One of the biggest differences between the B-tree and LSM-tree data structures is what they optimize for and what implications these optimizations have. +B-Tree 和 LSM-Tree 数据结构最大的差异之一是,它们优化的目的以及优化的效果。 -Let's compare the properties of B-trees with LSM-trees. In summary, B-trees have the following properties: +我们来对比一下 B-Tree 和 LSM-Tree 之间的特性。总的来说,B-Tree 具有以下特性: -• They are mutable, which allows for in-place updates by introducing some space overhead and a more involved write path, although it does not require complete file rewrites or multisource merges. +• 它是可变的,它允许通过一些空间开销和更多的写入路径来进行就地更新,因而它不需要文件重写或多源合并。 -• They are read-optimized, meaning they do not require reading from (and subsequently merging) multiple sources, thus simplifying the read path. +• 它是读优化的,这意味着它不需要从多个源数据中读取(也不需要合并),因而简化了读取路径。 -• Writes might trigger a cascade of node splits, making some write operations more expensive. +• 写操作可能引起级联节点分裂,这使得写操作开销较高。 -• They are optimized for paged environments (block storage), where byte addressing is not possible. +• 它针对分页环境(块存储)进行了优化,杜绝了字节定位操作。 -• Fragmentation, caused by frequent updates, might require additional maintenance and block rewrites. B-trees, however, usually require less maintenance than LSM-tree storage. +• 碎片化, 由频繁更新造成的碎片化可能需要额外的维护和块重写。然而对 B-Tree 的维护一般要比 LSM-Tree 要少。 -• Concurrent access requires reader/writer isolation and involves chains of locks and latches. +• 并发访问读写隔离,这涉及锁存器与锁链。 -LSM-trees have these properties: +LSM-Tree 具有以下特性: -• They are immutable. SSTables are written on disk once and never updated. Compaction is used to reconcile space occupied by removed items and merge same-key data from multiple data files. Merged SSTables are discarded and removed after a successful merge as part of the compaction process. Another useful property coming from immutability is that flushed tables can be accessed concurrently. +• 它是不可变的。SSTable 一旦被写入硬盘就不会再改变。压缩操作被用于整合占用空间,删除条目,合并在不同数据文件中的同键数据。作为压缩操作的一部分,在成功合并后,源 SSTable 将被弃用并删除。这种不可变性给我们带来了另一个有用的特性,刷新后的表可以被并发访问。 -• They are write optimized, meaning that writes are buffered and flushed on disk sequentially, potentially allowing for spatial locality on the disk. +• 它是写优化的,这意味着写入操作将进入缓冲,随后顺序刷新到硬盘上,可能支持基于硬盘的空间局部性。 -• Reads might require accessing data from multiple sources, since data for the same key, written during different times, might land in different data files. Records have to go through the merge process before being returned to the client. +• 读取操作可能需要访问多个数据源,因为在不同时间写入的同一个键的数据有可能位于不同的数据文件中。必须经过合并过程才能将记录返回给客户端。 -• Maintenance/compaction is required, as buffered writes are flushed on disk. +• 需要维护 / 压缩,因为缓冲中的写入操作被刷新到硬盘上。 -### Evaluating Storage Systems +### 评估存储系统 -Developing storage systems always presents the same challenges and factors to consider. Deciding what to optimize for has a substantial influence on the result. You can spend more time during write in order to lay out structures for more efficient reads, reserve extra space for in-place updates, facilitate faster writes, and buffer data in memory to ensure sequential write operations. It is impossible, however, to do this all at once. An ideal storage system would have the lowest read cost, lowest write cost, and no overhead. In practice, data structures compromise among multiple factors. Understanding these compromises is important. +开发存储系统总要面对类似的挑战,考虑类似的因素。决定优化方向会对结果产生很大影响。你可以在写入过程中花费更多时间来布局结构以实现更高效的读取,为就地更新预留空间,也可以缓冲数据确保顺序写入以提高写入速度。但是,一次完成这一切是不可能的。理想中的存储系统应该具有最低的读取成本,最低的写入成本,并且没有额外开销。但实际上,数据结构只能在多个因素之间权衡。理解这些取舍是重要的。 -Researchers from Harvard's DASlab (Data System Laboratory) summarized the three key parameters database systems are optimized for: read overhead, update overhead, and memory overhead, or RUM. Understanding which of these parameters are most important for your use-case influences the choice of data structures, access methods, and even suitability for certain workloads, as the algorithms are tailored having a specific use-case in mind. +来自哈佛 DASlab(数据系统实验室)的研究人员总结了数据库系统优化方向的关键三点:读取开销、更新开销和内存开销(或简称为 RUM)。对于数据结构、访问方法、甚至适用于某些工作负载的选择应该了解哪些参数对你的用例最为重要,因为算法是针对特定用例量身定制的。 -The [RUM Conjecture](http://daslab.seas.harvard.edu/rum-conjecture/)2 states that setting an upper bound for two of the mentioned overheads also sets a lower bound for the third one. For example, B-trees are read-optimized at the cost of write overhead as well as having to reserve empty space for the (thereby resulting in memory overhead). LSM-trees have less space overhead at a cost of read overhead brought on by having to access multiple disk-resident tables during the read. These three parameters form a competing triangle, and improvement on one side may imply compromise on the other. Figure 9 illustrates the RUM Conjecture. +[RUM 假说](http://daslab.seas.harvard.edu/rum-conjecture/) [2] 为上述的两种开销设置了上限,同时为第三种设置了下限。例如,B-Tree 以提高写入开销、预留空间(同时也造成了内存开销)为代价进行读优化。LSM-Tree 以读取时必须进行多硬盘表访问的高读取开销换取低写入开销。在处于竞争三角形的三个参数中,一方面的改进可能就意味着另一方面的让步。图 9 对 RUM 假说进行了说明。 -![Algorithms Behind Modern Storage Systems](http://deliveryimages.acm.org/10.1145/3230000/3220266/petrov9.png) +![支撑现代存储系统的算法](https://s1.ax1x.com/2018/05/29/C4OA5n.png) -B-trees optimize for read performance: the index is laid out in a way that minimizes the disk accesses required to traverse the tree. Only a single index file has to be accessed to locate the data. This is achieved by keeping this index file mutable, which also increases write amplification resulting from node splits and merges, relocation, and fragmentation/imbalance-related maintenance. To amortize update costs and reduce the number of splits, B-trees reserve extra free space in nodes on all levels. This helps to postpone write amplification until the node is full. In short, B-trees trade update and memory overhead for better read performance. +B-Tree 优化读取性能:索引的布局方式可以最小化遍历树的磁盘访问需求。通过访问一个索引文件就可以定位数据。这是通过持续更新索引文件来实现的,但这也增加了由于节点拆分和合并,重定位以及碎片、不平衡相关的维护造成的额外写入开销。为了平稳更新成本并减少分割次数,B-Tree 在所有级别的节点上都预留有额外的空间。这有助于在节点饱和之前延迟写入开销的增长。简而言之,B-Tree 牺牲更新和内存性能以获得更好的读取性能。 -LSM-trees optimize for write performance. Neither updates nor deletes require locating data on disk (which B-trees do), and they guarantee sequential writes by buffering all insert, update, and delete operations in memory-resident tables. This comes at the price of higher maintenance costs and a need for compaction (which is just a way of mitigating the ever-growing price of reads and reducing the number of disk-resident tables) and more expensive reads (as the data has to be read from multiple sources and merged). At the same time, LSM-trees eliminate memory overhead by not reserving any empty space (unlike B-tree nodes, which have an average occupancy of 70 percent, an overhead required for in-place updates) and allowing block compression because of the better occupancy and immutability of the end file. In short, LSM-trees trade read performance and maintenance for better write performance and lower memory overhead. +LSM-Tree 优化写入性能。无论是更新还是删除都需要在磁盘上定位数据(B-Tree 也一样),并且它通过在内存表中缓存所有插入,更新和删除操作来保证顺序写入。这是以较高的维护成本和压缩需求(这是唯一的缓解不断增长的读取开销和减少磁盘表的数量的方式)和更高的读取成本(因为数据必须从多个源读取并合并)为代价的。同时,LSM-Tree 通过不保留任何预留空间来减少内存开销(不同于 B-Tree 节点,其平均利用率为 70%,包含就地更新所需的开销),因为更高的利用率和最终文件的不变性,LSM-Tree 支持块压缩。简而言之,LSM-Tree 牺牲读取性能,提高维护成本来获得更好的写入性能和更低的内存占用。 -There are data structures that optimize for each desired property. Using adaptive data structures allows for better read performance at the price of higher maintenance costs. Adding metadata facilitating traversals (such as [fractional cascading](https://en.wikipedia.org/wiki/Fractional_cascading)) will have an impact on write time and take space, but can improve the read time. Optimizing for memory efficiency using compression (for example, algorithms such as [Gorilla compression](http://www.vldb.org/pvldb/vol8/p1816-teller.pdf),6 [delta encoding](https://en.wikipedia.org/wiki/Delta_encoding), and many others) will add some overhead for packing the data on writes and unpacking it on reads. Sometimes, you can trade functionality for efficiency. For example, [heap files and hash indexes](https://en.wikipedia.org/wiki/Database_storage_structures) can provide great performance guarantees and smaller space overhead because of the file format simplicity, for the price of not being able to perform anything but point queries. You can also trade precision for space and efficiency by using approximate data structures, such as the Bloom filter, [HyperLogLog](https://en.wikipedia.org/wiki/HyperLogLog), [Count-Min sketch](https://en.wikipedia.org/wiki/Count%E2%80%93min_sketch), and many others. +有的数据结构可针对每个期望的属性进行优化。使用自适应数据结构可以以更高维护成本获得更好的读取性能。添加有助于遍历的元数据(如[分散层叠](https://en.wikipedia.org/wiki/Fractional_cascading))将会影响写入时间并占用更多空间,但可以提高读取性能。使用压缩优化内存使用率(例如,[Gorilla 压缩](http://www.vldb.org/pvldb/vol8/p1816-teller.pdf) [6] 、[delta 编码](https://en.wikipedia.org/wiki/Delta_encoding)等诸多算法)会增加一些开销,用于在写入时压缩数据并在读取时解压缩数据。有时候,你可以牺牲功能来提高效率。例如,[堆文件和散列索引](https://en.wikipedia.org/wiki/Database_storage_structures)由于文件格式简单,可以保证很好的性能和较小的空间开销,而作为代价,它们不支持除点查询以外的其他功能。你还可以通过使用近似数据结构(如布隆过滤器、[HyperLogLog](https://en.wikipedia.org/wiki/HyperLogLog)、[Count-Min sketch](https://en.wikipedia.org/wiki/Count%E2%80%93min_sketch) 等)来为了空间与效率牺牲精度。 -The three tunables—read, update, and memory overheads—can help you evaluate the database and gain a deeper understanding of the workloads for which it is best suited. All of them are quite intuitive, and it's often easy to sort the storage system into one of the buckets and guess how it's going to perform, then validate your hypothesis through extensive testing. +三种可变参数 —— 读取,更新和内存开销 —— 可以帮助你评估数据库并深入了解最适合的工作负载。它们都非常直观,将存储系统按其分类很容易,猜测它是如何执行的,然后通过大量测试验证你的假设。 -Of course, there are other important factors to consider when evaluating a storage system, such as maintenance overhead, operational simplicity, system requirements, suitability for frequent updates and deletes, access patterns, and so on. The RUM Conjecture is just a rule of thumb that helps to develop an intuition and provide an initial direction. Understanding your workload is the first step on the way to building a scalable back end. +当然,评估存储系统时还有一些其他重要因素需要考虑,例如维护开销,易用性,系统要求,频繁增删的适应性,访问模式等。RUM 假说只是帮助发展直观感觉并提供初始方向的一条经验法则。了解你的工作部件是构建可扩展后端的第一步。 -Some factors may vary from implementation to implementation, and even two databases that use similar storage-design principles may end up performing differently. Databases are complex systems with many moving parts and are an important and integral part of many applications. This information will help you peek under the hood of a database and, knowing the difference between the underlying data structures and their inner doings, decide what's best for you. +一些因素可能因实施而异,甚至两个使用类似存储设计原则的数据库可能会有不同表现。数据库是包含许多可插拔模块的复杂系统,是许多应用程序的重要组成部分。这些信息将帮助你窥探数据库的底层,并且了解底层数据结构和其内部行为之间的差异,从而决定哪个是最适合你的。 -#### References +#### 参考文献 -1. Comer, D. 1979. The ubiquitous B-tree. _Computing Surveys_ 11(2); 121-137; [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.96.6637](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.96.6637&rep=rep1&type=pdf). +1. Comer, D. 1979. The ubiquitous B-tree. _Computing Surveys_ 11(2); 121-137; [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.96.6637](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.96.6637&rep=rep1&type=pdf). -2. Data Systems Laboratory at Harvard. The RUM Conjecture; [http://daslab.seas.harvard.edu/rum-conjecture/](http://daslab.seas.harvard.edu/rum-conjecture/). +2. Data Systems Laboratory at Harvard. The RUM Conjecture; [http://daslab.seas.harvard.edu/rum-conjecture/](http://daslab.seas.harvard.edu/rum-conjecture/). -3. Graefe, G. 2011. Modern B-tree techniques. _Foundations and Trends in Databases_ 3(4): 203-402; [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.219.7269&rep=rep1&type=pdf](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.219.7269&rep=rep1&type=pdf). +3. Graefe, G. 2011. Modern B-tree techniques. _Foundations and Trends in Databases_ 3(4): 203-402; [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.219.7269&rep=rep1&type=pdf](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.219.7269&rep=rep1&type=pdf). -4. MySQL 5.7 Reference Manual. The physical structure of an InnoDB index; [https://dev.mysql.com/doc/refman/5.7/en/innodb-physical-structure.html](https://dev.mysql.com/doc/refman/5.7/en/innodb-physical-structure.html). +4. MySQL 5.7 Reference Manual. The physical structure of an InnoDB index; [https://dev.mysql.com/doc/refman/5.7/en/innodb-physical-structure.html](https://dev.mysql.com/doc/refman/5.7/en/innodb-physical-structure.html). -5. O'Neil, P., Cheng, E., Gawlick, D., O'Neil, E. 1996. The log-structured merge-tree. _Acta Informatica_ 33(4): 351-385; [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.44.2782](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.44.2782&rep=rep1&type=pdf). +5. O'Neil, P., Cheng, E., Gawlick, D., O'Neil, E. 1996. The log-structured merge-tree. _Acta Informatica_ 33(4): 351-385; [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.44.2782](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.44.2782&rep=rep1&type=pdf). -6. Pelkonen, T., Franklin, S., Teller, J., Cavallaro, P., Huang, Q., Meza, J., Veeraraghavan, K. 2015. Gorilla: a fast, scalable, in-memory time series database. _Proceedings of the VLDB Endowment_ 8(12): 1816-1827; [http://www.vldb.org/pvldb/vol8/p1816-teller.pdf](http://www.vldb.org/pvldb/vol8/p1816-teller.pdf). +6. Pelkonen, T., Franklin, S., Teller, J., Cavallaro, P., Huang, Q., Meza, J., Veeraraghavan, K. 2015. Gorilla: a fast, scalable, in-memory time series database. _Proceedings of the VLDB Endowment_ 8(12): 1816-1827; [http://www.vldb.org/pvldb/vol8/p1816-teller.pdf](http://www.vldb.org/pvldb/vol8/p1816-teller.pdf). -7. Suzuki, H. 2015-2018. The internals of PostreSQL; [http://www.interdb.jp/pg/pgsql01.html](http://www.interdb.jp/pg/pgsql01.html). +7. Suzuki, H. 2015-2018. The internals of PostreSQL; [http://www.interdb.jp/pg/pgsql01.html](http://www.interdb.jp/pg/pgsql01.html). -8. Apple HFS Plus Volume Format; [https://developer.apple.com/legacy/library/technotes/tn/tn1150.html#BTrees](https://developer.apple.com/legacy/library/technotes/tn/tn1150.html#BTrees) +8. Apple HFS Plus Volume Format; [https://developer.apple.com/legacy/library/technotes/tn/tn1150.html#BTrees](https://developer.apple.com/legacy/library/technotes/tn/tn1150.html#BTrees) -9. Mathur, A., Cao, M., Bhattacharya, S., Dilger, A., Tomas, A., Vivier, L. (2007). [The new ext4 filesystem: current status and future plans](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.111.798&rep=rep1&type=pdf). _Proceedings of the Linux Symposium_. Ottawa, Canada: Red Hat. +9. Mathur, A., Cao, M., Bhattacharya, S., Dilger, A., Tomas, A., Vivier, L. (2007). [The new ext4 filesystem: current status and future plans](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.111.798&rep=rep1&type=pdf). _Proceedings of the Linux Symposium_. Ottawa, Canada: Red Hat. -10. Bloom, B. H. (1970), [Space/time trade-offs in hash coding with allowable errors](https://dl.acm.org/citation.cfm?doid=362686.362692),_Communications of the ACM_, 13 (7): 422-426 +10. Bloom, B. H. (1970), [Space/time trade-offs in hash coding with allowable errors](https://dl.acm.org/citation.cfm?doid=362686.362692),_Communications of the ACM_, 13 (7): 422-426 -#### Related articles +#### 相关文章 -**[The Five-minute Rule: 20 Years Later and How Flash Memory Changes the Rules](https://queue.acm.org/detail.cfm?id=1413264)** -Goetz Graefe, Hewlett-Packard Laboratories -The old rule continues to evolve, while flash memory adds two new rules. +**[五分钟法则:20 年后闪存将如何改写游戏规则](https://queue.acm.org/detail.cfm?id=1413264)** +Goetz Graefe, Hewlett-Packard 实验室 +旧规则继续发展,而闪存增加了两条新规则。 [https://queue.acm.org/detail.cfm?id=1413264](https://queue.acm.org/detail.cfm?id=1413264) -**[Disambiguating Databases](https://queue.acm.org/detail.cfm?id=2696453)** -Rick Richardson -Use the database built for your access model. +**[Disambiguating Databases](https://queue.acm.org/detail.cfm?id=2696453)** +Rick Richardson +根据你的访问模型构建数据库。 [https://queue.acm.org/detail.cfm?id=2696453](https://queue.acm.org/detail.cfm?id=2696453) -**[You're Doing It Wrong](https://queue.acm.org/detail.cfm?id=1814327)** -Poul-Henning Kamp -Think you've mastered the art of server performance? Think again. +**[你做错了!](https://queue.acm.org/detail.cfm?id=1814327)** +Poul-Henning Kamp +你以为自己已经掌握了服务器性能的艺术了么?再想一想。 [https://queue.acm.org/detail.cfm?id=1814327](https://queue.acm.org/detail.cfm?id=1814327) -**Alex Petrov** ([http://coffeenco.de/](http://coffeenco.de/), [@ifesdjeen (GitHub)](https://github.com/ifesdjeen) [@ifesdjeen (Twitter)](https://twitter.com/ifesdjeen)) is an Apache Cassandra committer and storage-systems enthusiast. Over the past several years, he has worked on databases, building distributed systems and data-processing pipelines for various companies. +**Alex Petrov** ([http://coffeenco.de/](http://coffeenco.de/), [@ifesdjeen (GitHub)](https://github.com/ifesdjeen) [@ifesdjeen (Twitter)](https://twitter.com/ifesdjeen)),一位 Apache Cassandra 贡献者、存储系统爱好者。在过去的几年,他一直致力于数据库,为各个公司建立分布式系统和数据处理管道。 -> 本文英文原文 PDF 文件下载地址:https://dl.acm.org/ft_gateway.cfm?id=3220266&ftid=1967080&dwn=1 +> 本文英文原文 PDF 文件:[下载地址](https://dl.acm.org/ft_gateway.cfm?id=3220266&ftid=1967080&dwn=1) Copyright © 2018 held by owner/author. Publication rights licensed to ACM. diff --git a/TODO1/an-easier-path-to-functional-programming-in-java.md b/TODO1/an-easier-path-to-functional-programming-in-java.md new file mode 100644 index 00000000000..2f50e09276d --- /dev/null +++ b/TODO1/an-easier-path-to-functional-programming-in-java.md @@ -0,0 +1,158 @@ +> * 原文地址:[An easier path to functional programming in Java](https://www.ibm.com/developerworks/library/j-java8idioms1/) +> * 原文作者:[Venkat Subramaniam](https://developer.ibm.com/author/venkats/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/an-easier-path-to-functional-programming-in-java.md](https://github.com/xitu/gold-miner/blob/master/TODO1/an-easier-path-to-functional-programming-in-java.md) +> * 译者:[maoqyhz](https://github.com/maoqyhz) +> * 校对者:[satansk](https://github.com/satansk)、[lihanxiang](https://github.com/lihanxiang) + +# 通往 Java 函数式编程的捷径 + +## 以声明式的思想在你的 Java 程序中使用函数式编程技术 + +Java™ 开发人员习惯于面向命令式和面向对象的编程,因为这些特性自 Java 语言首次发布以来一直受到支持。在 Java 8 中,我们获得了一组新的强大的函数式特性和语法。函数式编程已经存在了数十年,与面向对象编程相比,函数式编程通常更加简洁和达意,不易出错,并且更易于并行化。所以有很好的理由将函数式编程特性引入到 Java 程序中。尽管如此,在使用函数式特性进行编程时,就如何设计你的代码这一点上需要进行一些改变。 + +**关于本文** + +Java 8 是 Java 语言自诞生以来最重要的更新,它包含如此多的新特性,以至于你可能想知道应该从哪开始了解它。在本系列中,身为作家和教育家的 Venkat Subramaniam 提供了一种符合 Java 语言习惯的 Java 8 学习方式。邀请你进行简短的探索后,重新思考你认为理所当然的 Java 一贯用法和规范,同时逐渐将新技术和语法集成到你的程序中去。 + +我认为,以声明式的思想而不是命令式的思想来编程,可以更加轻松地向更加函数化的编程风格过渡。在 [_Java 8 idioms_ series](http://www.ibm.com/developerworks/library/?series_title_by=Java+8+idioms) 这个系列的第一篇文章中,我解释了命令式、声明式和函数式编程风格之间的异同。然后,我将向你展示如何使用声明式的思想逐渐将函数式编程技术集成到你的 Java 程序中。 + +## 命令式风格(面向过程) + +受命令式编程风格训练的开发者习惯于告诉程序需要做什么以及如何去做。这里是一个简单的例子: + +
清单 1. 以命令式风格编写的 findNemo 方法
+ +``` +import java.util.*; + +public class FindNemo { + public static void main(String[] args) { + List names = + Arrays.asList("Dory", "Gill", "Bruce", "Nemo", "Darla", "Marlin", "Jacques"); + + findNemo(names); + } + + public static void findNemo(List names) { + boolean found = false; + for(String name : names) { + if(name.equals("Nemo")) { + found = true; + break; + } + } + + if(found) + System.out.println("Found Nemo"); + else + System.out.println("Sorry, Nemo not found"); + } +} +``` + +方法 `findNemo()` 首先初始化一个可变变量 **flag**,也称为垃圾变量(garbage variable)。开发者经常会给予某些变量一个临时性的名字,例如 `f`、`t`、`temp` 以表明它们根本不应该存在。在本例中,这些变量应该被命名为 `found`。 + +接下来,程序会循环遍历给定的 `names` 列表,每次都会判断当前遍历的值是否和待匹配值相同。在这个例子中,待匹配值为 `Nemo`,如果遍历到的值匹配,程序会将标志位设为 `true`,并执行流程控制语句 "break" 跳出循环。 + +这是对于广大 Java 开发者最熟悉的编程风格 —— 命令式风格的程序,因此你可以定义程序的每一步:你告诉程序遍历每一个元素,和待匹配值进行比较,设置标志位,以及跳出循环。命令式编程风格让你可以完全控制程序,有的时候这是一件好事。但是,换个角度来看,你做了很多机器可以独立完成的工作,这势必导致生产力下降。因此,有的时候,你可以通过少做事来提高生产力。 + +## 声明式风格 + +声明式编程意味着你仍然需要告诉程序需要做什么,但是你可以将实现细节留给底层函数库。让我们看看使用声明式编程风格重写[清单 1](#listing1) 中的 `findNemo` 方法时会发生什么: + +##### 清单 2. 以声明式风格编写的 findNemo 方法 + +``` +public static void findNemo(List names) { + if(names.contains("Nemo")) + System.out.println("Found Nemo"); + else + System.out.println("Sorry, Nemo not found"); +} +``` + +首先需要注意的是,此版本中没有任何垃圾变量。你也不需要在遍历集合中浪费精力。相反,你只需要使用内建的 `contains()` 方法来完成这项工作。你仍然要告诉程序需要做什么,集合中是否包含我们正在寻找的值,但此时你已经将细节交给底层的方法来实现了。 + +在命令式编程风格的例子中,你控制了遍历的流程,程序可以完全按照指令进行;在声明式的例子中,只要程序能够完成工作,你完全不需要关注它是如何工作的。`contains()` 方法的实现可能会有所不同,但只要结果符合你的期望,你就会对此感到满意。更少的工作能够得到相同的结果。 + +训练自己以声明式的编程风格来进行思考将更加轻松地向更加函数化的编程风格过渡。原因在于,函数式编程风格是建立在声明式风格之上的。声明式风格的思维可以让你逐渐从命令式编程转换到函数式编程。 + +## 函数式编程风格 + +虽然函数式风格的编程总是声明式的,但是简单地使用声明式风格编程并不等同与函数式编程。这是因为函数式编程时将声明式编程和高阶函数结合在了一起。图 1 显示了命令式,声明式和函数式编程风格之间的关系。 + +##### 图 1. 命令式、声明式和函数式编程风格之间的关系 + +![A logic diagram showing how the imperative, declarative, and functional programming styles differ and overlap.](https://www.ibm.com/developerworks/library/j-java8idioms1/fig1.png) + +### Java 中的高阶函数 + +在 Java 中,你可以将对象传递给方法,在方法中创建对象,也可以从方法中返回对象。同时你也可以用函数做相同的事情。也就是说,你可以将函数传递给方法,在方法中创建函数,也可以从方法中返回函数。 + +在这种情况下,**方法**是类的一部分(静态或实例),但是函数可以是方法的一部分,并且不能有意地与类或实例相关联。一个可以接收、创建、或者返回函数的方法或函数称之为**高阶函数**。 + +## 一个函数式编程的例子 + +采用新的编程风格需要改变你对程序的看法。这是一个从简单例子的练习开始,到构建更加复杂程序的过程。 + +
清单 3. 命令式编程风格下的 Map
+ +``` +import java.util.*; + +public class UseMap { + public static void main(String[] args) { + Map pageVisits = new HashMap<>(); + + String page = "https://agiledeveloper.com"; + + incrementPageVisit(pageVisits, page); + incrementPageVisit(pageVisits, page); + + System.out.println(pageVisits.get(page)); + } + + public static void incrementPageVisit(Map pageVisits, String page) { + if(!pageVisits.containsKey(page)) { + pageVisits.put(page, 0); + } + + pageVisits.put(page, pageVisits.get(page) + 1); + } +} +``` + +在[清单 3](#listing3) 中,`main()` 函数创建了一个 `HashMap` 来保存网站访问次数。同时,`incrementPageVisit()` 方法增加了每次访问给定页面的计数。我们将聚焦此方法。 + +以命令式编程风格写的 `incrementPageVisit()` 方法:它的工作是为给定页面增加一个计数,并存储在 `Map` 中。该方法不知道给定页面是否已经有计数值,所以会先检查计数值是否存在,如果不存在,会为该页面插入一个值为"0"的计数值。然后再获取该计数值,递增它,并将新的计数值存储在 `Map` 中。 + +以声明式的方式思考需要你将方法的设计从 "how" 转变到 "what"。当 `incrementPageVisit()` 方法被调用时,你需要将给定的页面计数值初始化为 1 或者计数值加 1。这就是 **what**。 + +因为你是通过声明式编程的,那么下一步就是在 JDK 库中寻找可以完成这项工作且实现了 `Map` 接口的方法。换言之,你需要找到一个知道**如何**完成你指定任务的内建方法。 + +事实证明 `merge()` 方法非常适合你的而目的。清单 4 使用新的声明式方法对[清单 3](#listing3) 中的 `incrementPageVisit()` 方法进行修改。但是,在这种情况下,你不仅仅只是选择更智能的方法来写出更具声明性风格的代码,因为 `merge()` 是一个更高阶的函数。所以说,新的代码实际上是一个体现函数式风格的很好的例子: + +
清单 4. 函数式编程风格下的 Map
+ +``` +public static void incrementPageVisit(Map pageVisits, String page) { + pageVisits.merge(page, 1, (oldValue, value) -> oldValue + value); +} +``` + +在清单 4 中,`page` 作为第一个参数传递给 `merge()`:map 中键对应的值将会被更新。第二个参数作为初始值,**如果** `Map` 中不存在指定键的值,那么该值将会赋值给 `Map` 中键对应的值(在本例中为"1")。第三个参数为一个 lambda 表达式,接受当前 `Map` 中键对应的值和该函数中第二个参数对应的值作为参数。lambda 表达式返回其参数的总和,实际上增加了计数值。(**编者注**:感谢 István Kovács 修正了代码错误) + +将[清单 4](#listing4) 的 `incrementPageVisit()` 方法中的单行代码与[清单 3](#listing3) 中的多行代码进行比较。虽然[清单 4](#listing4) 中的程序是函数式编程风格的一个例子,但通过声明性地思想去思考问题帮助能够我们实现飞跃。 + +## 总结 + +在 Java 程序中采用函数式编程技术和语法有很多好处:代码更简洁,更富有表现力,移动部分更少,实现并行化更容易,并且通常比面向对象的代码更易理解。 目前面临的挑战是,如何将你的思维从绝大多数开发人员所熟悉的命令式编程风格转变为以声明式的方式进行思考。 + +虽然函数式编程并没有那么简单或直接,但是你可以学习专注于你希望程序**做什么**而不是**如何做**这件事,来取得巨大的飞跃。通过允许底层函数库管理执行,你将逐渐直观地了解用于构建函数式编程模块的高阶函数。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/animated-transition-in-react-native.md b/TODO1/animated-transition-in-react-native.md new file mode 100644 index 00000000000..9ea8e91cdc7 --- /dev/null +++ b/TODO1/animated-transition-in-react-native.md @@ -0,0 +1,173 @@ +> * 原文地址:[Animated Transition in React Native!](https://medium.com/react-native-motion/transition-challenge-9bc9fdef56c7) +> * 原文作者:[Jiří Otáhal](https://medium.com/@xotahal?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/animated-transition-in-react-native.md](https://github.com/xitu/gold-miner/blob/master/TODO1/animated-transition-in-react-native.md) +> * 译者:[talisk](https://github.com/talisk) +> * 校对者:[foxxnuaa](https://github.com/foxxnuaa) + +# React Native 中使用转场动画! + +> 这篇文章有近 15k 的浏览量。对某些人来说,这可能没什么,但对我来说是一个很大的动力。这正是我决定构建 [Pineapple — Financial Manager](https://pineapplee.io/) 的原因。仅仅 20 天,我已经完成了 [iOS 版](https://itunes.apple.com/us/app/pineapple-financial-manager/id1369607032?ls=1&mt=8),[Android 版](https://play.google.com/store/apps/details?id=com.pineapple.android)以及[网页版](https://pineapplee.io/),花费 300 美金,并写了[几篇关于它的文章](https://medium.com/how-i-built-profitable-application-faster-than)。我无法用言语表达我多么享受这段时间。你也应该试试! + +最近我试图为下一个动画挑战寻找灵感。我们开始吧 —— [由 Ivan Parfenov 创建](https://medium.muz.li/ui-interactions-of-the-week-116-40eba84eb736)。我很好奇我是否能够用 React Native 来做这个过渡效果。[**你可以在我的 expo 帐户中查看结果**](https://expo.io/@xotahal/react-native-motion-example)!为什么我们还需要这样的动画?来读读 Pablo Stanley 写的[绝佳的 UI 动画技巧](https://uxdesign.cc/good-to-great-ui-animation-tips-7850805c12e5)。 + +![](https://cdn-images-1.medium.com/max/800/1*D35P0J6_34Yrs_n3i1hvjA.gif) + +[Ivan Parfenov](https://dribbble.com/parfenoff) 设计的 [PLΛTES](https://dribbble.com/plates)。 + +我们可以看到有几个动画。工具栏(显示/隐藏),底栏(显示/隐藏),移动所选项目,隐藏所有其他项目,显示详细信息项目甚至更多。 + +![](https://cdn-images-1.medium.com/max/800/1*HdpUrmxtI0cptj8BpxsaPw.png) + +动画时间线。 + +过渡动画的难点在于同步所有这些动画。因为我们需要等到所有动画都完成,我们无法真正移除列表页面并显示详细信息页面。此外,我对整洁的代码有所追求。代码要易于维护,如果您曾尝试为项目实现动画,则代码通常会变得混乱。到处都是辅助变量,各种计算等。这正是我想介绍 [react-native-motion](https://github.com/xotahal/react-native-motion) 的原因。 + +![](https://cdn-images-1.medium.com/max/800/1*nfm2A4bKidwuPQ-Oy4vTxQ.gif) + +### 对 react-native-motion 的一个小想法 + +你能看到工具栏标题的动画吗?你只需稍微移动标题并将不透明度设置为 0 或 1。这很简单!但正因为如此,你需要编写这样的代码,甚至在你真正开始为该组件编写 UI 之前。 + +``` +class TranslateYAndOpacity extends PureComponent { + constructor(props) { + // ... + this.state = { + opacityValue: new Animated.Value(opacityMin), + translateYValue: new Animated.Value(translateYMin), + }; + // ... + } + componentDidMount() { + // ... + this.show(this.props); + // ... + } + componentWillReceiveProps(nextProps) { + if (!this.props.isHidden && nextProps.isHidden) { + this.hide(nextProps); + } + if (this.props.isHidden && !nextProps.isHidden) { + this.show(nextProps); + } + } + show(props) { + // ... + Animated.parallel([ + Animated.timing(opacityValue, { /* ... */ }), + Animated.timing(translateYValue, { /* ... */ }), + ]).start(); + } + hide(props) { + // ... + Animated.parallel([ + Animated.timing(opacityValue, { /* ... */ }), + Animated.timing(translateYValue, { /* ... */ }), + ]).start(); + } + render() { + const { opacityValue, translateYValue } = this.state; + + const animatedStyle = { + opacity: opacityValue, + transform: [{ translateY: translateYValue }], + }; + + return ( + {this.props.children} + ); + } +} +``` + +现在让我们来看看如果用 [react-native-motion](https://github.com/xotahal/react-native-motion) 实现这个动画,要怎么做。我知道动画经常是非常具体的。我知道 React Native 提供了非常强大的动画 API。无论如何,拥有一个带有基本动画的库会很棒。 + +``` +import { TranslateYAndOpacity } from 'react-native-motion'; + +class ToolbarTitle extends PureComponent { + render() { + return ( + + + // ... + + + ); + } +} +``` + +### 共享的元素 + +这一挑战的最大问题是移动选定的列表项。列表页面和详细信息页面之间共享的项目。当元素实际上没有绝对定位时,如何将项目从 FlatList 移动到 Detail 的页面顶部?使用 react-native-motion 非常容易。 + +``` +// List items page with source of SharedElement +import { SharedElement } from 'react-native-motion'; + +class ListPage extends Component { + render() { + return ( + + {listItemNode} + + ); + } +} +``` + +我们在 ListPage 上指定了 SharedElement 的 source 元素。现在我们需要对 DetailPage 上的目标元素执行几乎相同的操作,来知道我们想要移动共享元素的位置。 + +``` +// Detail page with a destination shared element +import { SharedElement } from 'react-native-motion'; + +class DetailPage extends Component { + render() { + return ( + + {listItemNode} + + ); + } +} +``` + +### 黑科技在哪里? + +我们如何将相对定位的元素从一个页面移动到另一个页面?实际上我们做不到。SharedElement 的工作方式如下: + +* 获取源 element 的位置 +* 获取目标 element 的位置(显然,没有这一步,动画不能被初始化) +* 创建共享的 element(黑科技!) +* 在屏幕上方渲染一个新图层 +* 渲染 element 元素,将覆盖源 element(在源 element 的位置上) +* 开始移动到目标 element 位置 +* 一旦移动到目标 element 位置后,删除克隆 element + +![](https://cdn-images-1.medium.com/max/800/1*MKDiUHnLdB7WiEPR26IHdw.png) + +你可以想象在同一时刻同一个 React Node 有 3 个 element。这是因为在移动动画期间,DetailPage 会覆盖列表页面。这就是为什么我们可以看到所有 3 个元素。但是我们想要创造一种我们实际移动了原始 element 的幻觉。 + +![](https://cdn-images-1.medium.com/max/1000/1*m11vVsxY3Pa_e5lDMkOT_w.png) + +SharedElement 的时间线。 + +您可以看到 A 点和 B 点。这是移动正在执行的时间段。您还可以看到 SharedElement 触发一些有用的事件。在这种情况下,我们使用 WillStart 和 DidFinish 事件。在启动移动目标 element 时,将源 element 和目标 element 的不透明度设置为 0,并在动画完成后将目标 element 的不透明度设置为 1。 + +### 你觉得怎么样? + +社区这里一直不断在维护和更新:[react-native-motion](https://github.com/xotahal/react-native-motion)。这绝不是这个库的最终和稳定版本。但是一个好的开始 :) 我很想听听你怎么想! + +> 我一直在寻找新的挑战和机会。如果您需要帮助,请告诉我,我很乐意讨论它。 + +> [LinkedIn](https://www.linkedin.com/in/xotahal/) || [Github](https://github.com/xotahal) || [Twitter](https://twitter.com/xotahal) || [Facebook](https://www.facebook.com/jiri.otahal.96) || [500px](https://500px.com/xotahal) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/announcing-the-alexa-skills-kit-for-node-js.md b/TODO1/announcing-the-alexa-skills-kit-for-node-js.md new file mode 100644 index 00000000000..b11c12d0ee8 --- /dev/null +++ b/TODO1/announcing-the-alexa-skills-kit-for-node-js.md @@ -0,0 +1,379 @@ +> * 原文地址:[Announcing the Alexa Skills Kit for Node.js](https://developer.amazon.com/zh/blogs/post/Tx213D2XQIYH864/announcing-the-alexa-skills-kit-for-node-js) +> * 原文作者:[David Isbitski](https://developer.amazon.com/blogs/alexa/author/David+Isbitski) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/announcing-the-alexa-skills-kit-for-node-js.md](https://github.com/xitu/gold-miner/blob/master/TODO1/announcing-the-alexa-skills-kit-for-node-js.md) +> * 译者:[Yuhanlolo](https://github.com/Yuhanlolo) +> * 校对者:[yqian1991](https://github.com/yqian1991), [DateBro](https://github.com/DateBro) + +# 基于 Node.js 的 Alexa Skills Kit 发布了! + +我们今天很高兴地宣布,一个新的基于 Node.js,旨在帮助开发者们更加简单和快捷地开发 Alexa skill 的 [alexa-sdk](https://github.com/alexa/alexa-skill-sdk-for-nodejs) 发布了。通过 [Alexa Skills Kit](http://developer.amazon.com/ask)、[Node.js](https://nodejs.org/en/),和 [AWS Lambda](https://aws.amazon.com/lambda/) 开发 Alexa skill 如今已成为最受欢迎的 skill 开发方式。Node.js 事件驱动,非阻塞的特性,使它非常适合开发 Alexa skill,并且 Node.js 也是世界上最大开源系统之一。除此之外,为每个月前一百万个网络请求提供免费服务的亚马逊网络服务系统(AWS)Lambda,能够支持大部分开发者从事 skill 的开发。在使用 AWS Lambda 的同时,你不需要担心管理任何 SSL 证书的问题(因为 Alexa Skills Kit 是被 AWS 信任的触发器)。 + +在使用 AWS Lambda 创建 Alexa skill 的时候,加入 Node.js 和 Alexa Skills Kit 只是一个简单的流程,但你实际上所需要写的代码要比这复杂得多。我们已经意识到大部分开发 skill 会话(session)的属性、skill 的状态持久化,创建回复以及行为模式上面。因此,Alexa 团队着手于开发一个基于 Node.js 的 Alexa Skills Kit SDK 来帮助你避免这些常见的烦恼,从而专注于你的 skill 自身的逻辑开发而不是样板化编码。 + +## 使用基于 Node.js 的 Alexa Skills Kit(alexa-sdk)加速 Alexa Skill 的开发 + +有了 alexa-sdk,我们的目标是帮助你在能够避免不必要的复杂度的情况下,更快捷地开发 skills。今天我们要发布的这个最新版本的 SDK 具备以下几个特点: + +* 新版SDK是可托管的 NPM 安装包,简化了在任何 Node.js 环境下的开发 +* 可以通过内置事件创建 Alexa 的回复 +* 为新的 session 内置帮助事件(Helper events),并且添加了未处理事件(unhandled events)来捕捉所有异常 +* 提供了能构建基于状态机的用户意图(intent)处理的帮助函数(Helper function) +* 这让根据当前 skill 的状态定义不同的事件管理器成为现实 +* 属性持久化的配置在 Amazon DynamoDB 的帮助下变得更加简单 +* 所有输出的语音将自动封装在 SSML 下 +* Lambda 事件和和上下文对象 (context objects) 将通过 this.event 读取,并且可以通过 this.contextAbility 重写内置函数,从而让你的状态管理和回复创建更加灵活。例如,将状态属性储存到 AWS S3 上。 + +## 安装和调试基于 Node.js 的 Alexa Skills Kit (alexa-sdk) + +alexa-sdk 已经上传到了 [github](https://github.com/alexa/alexa-skill-sdk-for-nodejs),并且可以以 node 包的形式通过下面的指令在你的 Node.js 环境下安装: + +``` +npm install --save alexa-sdk +``` + +为了开始使用 alexa-sdk,你需要先导入它的库。你只需要在你的项目里简单地创建一个名为 index.js 的文件然后加入以下代码: + +``` +var Alexa = require('alexa-sdk'); + +exports.handler = function(event, context, callback){ + + var alexa = Alexa.handler(event, context); + +}; +``` + +这几行代码将会导入 alexa sdk 并且为我们创建一个 alexa 对象以便之后使用。接着,我们需要处理与 skill 交互的️ intent。幸运的是,alexa-sdk 使得在我们想要的意图(Intent)上激活一个函数变得简单。例如,创建一个为 ‘HelloWorldIntent’ 服务的事件管理器,我们只需要简单地用以下代码实现: + +``` +var handlers = { + + 'HelloWorldIntent': function () { + + this.emit(':tell', 'Hello World!'); + + } + +}; +``` + +注意上面出现的一个新语法规则 “:tell”? alexa-sdk 遵循 tell/ask 的响应方式来生成你的[语音输出回复对象](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference)。如果我们想要问用户问题的话,我们需要把以上代码改成: + +``` +this.emit(‘:ask’, ’What would you like to do?’, ’Please say that again?’); +``` + +事实上,你的 skill 生成的许多回复都遵循一样的语法规则。下面是一些常见的 skill 回复生成的例子: + +``` +var speechOutput = 'Hello world!'; + +var repromptSpeech = 'Hello again!'; + +this.emit(':tell', speechOutput); + +this.emit(':ask', speechOutput, repromptSpeech); + +var cardTitle = 'Hello World Card'; + +var cardContent = 'This text will be displayed in the companion app card.'; + +var imageObj = { + + smallImageUrl: 'https://imgs.xkcd.com/comics/standards.png', + + largeImageUrl: 'https://imgs.xkcd.com/comics/standards.png' + +}; + +this.emit(':askWithCard', speechOutput, repromptSpeech, cardTitle, cardContent, imageObj); + +this.emit(':tellWithCard', speechOutput, cardTitle, cardContent, imageObj); + +this.emit(':tellWithLinkAccountCard', speechOutput); + +this.emit(':askWithLinkAccountCard', speechOutput); + +this.emit(':responseReady'); // 在回复创建之后,返回 Alexa 服务之前被调用。Calls :saveState。 + +this.emit(':saveState', false); // 事件管理器将 this.attributes 的内容和当前管理器的状态存储到 DynamoDB,然后将之前内置的回复发送到 Alexa 服务。如果你想用别的方式处理持久化状态,可以重写它。其中的第二个属性是可选的并且可以通过将它设置为 ‘true’ 以强制储存。 + +this.emit(':saveStateError'); // 在存储状态的过程出错时被调用。如果你想自己处理异常的话,可以重写它。 +``` + +一旦我们创建好事件管理器,在新的 session(NewSession)场景下,我们需要用之前创建的 alexa 对象中的 registerHandlers 函数去注册这些管理器。 + +``` +exports.handler = function(event, context, callback){ + + var alexa = Alexa.handler(event, context); + + alexa.registerHandlers(handlers); + +}; +``` + +你也可以同时注册多个事件管理器。与其创建单个管理器对象,我们创建了一个新的 session,其中有许多处理不同事件的不同管理器,并且我们可以通下面的代码同时注册它们: + +``` + alexa.registerHandlers(handlers, handlers2, handlers3, ...); +``` + +你所定义的事件管理器可以相互调用,从而保证你的 skill 的回复是统一的。下面是 LaunchRequest 和 IntentRequest(在 HelloWorldIntent 中)都返回 “Hello World” 消息的一个例子。 + +``` +var handlers = { + + 'LaunchRequest': function () { + + this.emit('HelloWorldIntent'); + + }, + + 'HelloWorldIntent': function () { + + this.emit(':tell', 'Hello World!'); + +}; +``` + +一旦你注册了所有的意图管理器函数,你只需要简单地用 alexa 对象里的执行函数去运行 skill 的逻辑就可以了。最后一行代码是这样的: + +``` +exports.handler = function(event, context, callback){ + +    var alexa = Alexa.handler(event, context); + +    alexa.registerHandlers(handlers); + +    alexa.execute(); + +}; +``` + +你可以从 github 上下载完整的示例。我们还提供了最新的基于 Node.js 和 alexa-sdk 开发的 skill 示例:[Fact](https://github.com/alexa/skill-sample-nodejs-fact),[HelloWorld](https://github.com/alexa/skill-sample-nodejs-hello-world),[HighLow](https://github.com/alexa/skill-sample-nodejs-highlowgame),[HowTo](https://github.com/alexa/skill-sample-nodejs-howto) 和 [Trivia](https://github.com/alexa/skill-sample-nodejs-trivia)。 + +## 让 Skill 的状态管理更简单 + +alexa-sdk 会根据当前状态把即将接受的 intent 传送给正确的管理器函数。它其实只是 session 属性中一个简单的字符串,用来表示 skill 的状态。在定义 intent 管理器的时候,你也可以通过将表示状态的字符串添加到 intent 的名称后面来模仿这个内置传送的过程,但事实上 alexa-sdk 已经帮你做到了。 + +比如说,让我们根据上一个管理新的 session 事件的例子,创建一个简单的有“开始”和“猜数”两个状态的猜数字游戏。 + +``` +var states = { + GUESSMODE: '_GUESSMODE', // User is trying to guess the number. + STARTMODE: '_STARTMODE' // Prompt the user to start or restart the game. +}; + +var newSessionHandlers = { + + // 以下代码将会切断任何即将输入的 intent 或者启动请求,并且把它们都传送给这个管理器。 + + 'NewSession': function() { + + this.handler.state = states.STARTMODE; + + this.emit(':ask', 'Welcome to The Number Game. Would you like to play?.'); + + } + + }; +``` + +注意当一个新的 session 被创建时,我们简单地通过 this.handler.state 把 skill 的状态设置为 STARTMODE。此时 skill 的状态将会自动被持久化在 session 的属性中,如果你在 DynamoDB 里设置了表格的话,你可以选择将它持久化于各个 session 当中。 + +值得注意的是,NewSession 是一个很棒的捕捉各种行为的管理器,同时也是一个很好的 skill 入口,但它不是必需的。NewSession 只会在一个以它命名的函数中被唤醒。你所定义的每一个状态都可以有它们自己的 NewSession 管理器,在你使用内置留存时被唤醒。在上面的例子中,我们可以更加灵活地为 states.STARTMODE 和 states.GUESSMODE 定义不同的 NewSession 行为。 + +为了定义回复 skill 在不同状态下的 intents,我们需要使用 Alexa.CreateStateHandler 函数。任何在这里定义的 intent 管理器将只会在特定状态下工作,这让我们的开发操作更加灵活! + +例如,如果我们在上面定义的 GUESSMODE 状态下,我们想要处理用户对一个问题的回复。这可以通过 StateHandlers 实现,就像这样: + +``` +var guessModeHandlers = Alexa.CreateStateHandler(states.GUESSMODE, { + + 'NewSession': function () { + + this.handler.state = ''; + + this.emitWithState('NewSession'); // 等同于 Start Mode 下的 NewSession handler + + }, + + 'NumberGuessIntent': function() { + + var guessNum = parseInt(this.event.request.intent.slots.number.value); + + var targetNum = this.attributes["guessNumber"]; + + console.log('user guessed: ' + guessNum); + + + + if(guessNum > targetNum){ + + this.emit('TooHigh', guessNum); + + } else if( guessNum < targetNum){ + + this.emit('TooLow', guessNum); + + } else if (guessNum === targetNum){ + + // 通过一个 callback 函数,用 arrow 函数储存正确的 ‘this’ context + + this.emit('JustRight', () => { + + this.emit(':ask', guessNum.toString() + 'is correct! Would you like to play a new game?', + + 'Say yes to start a new game, or no to end the game.'); + + }) + + } else { + + this.emit('NotANum'); + + } + + }, + + 'AMAZON.HelpIntent': function() { + + this.emit(':ask', 'I am thinking of a number between zero and one hundred, try to guess and I will tell you' + + + ' if it is higher or lower.', 'Try saying a number.'); + + }, + + 'SessionEndedRequest': function () { + + console.log('session ended!'); + + this.attributes['endedSessionCount'] += 1; + + this.emit(':saveState', true); + + }, + + 'Unhandled': function() { + + this.emit(':ask', 'Sorry, I didn\'t get that. Try saying a number.', 'Try saying a number.'); + + } + +}); +``` + +另一方面,如果我们在 STARTMODE 状态下,我可以用以下方式定义 StateHandlers: + +``` +var startGameHandlers = Alexa.CreateStateHandler(states.STARTMODE, { + + 'NewSession': function () { + + this.emit('NewSession'); // 在 newSessionHandlers 使用管理器 + + }, + + 'AMAZON.HelpIntent': function() { + + var message = 'I will think of a number between zero and one hundred, try to guess and I will tell you if it' + + + ' is higher or lower. Do you want to start the game?'; + + this.emit(':ask', message, message); + + }, + + 'AMAZON.YesIntent': function() { + + this.attributes["guessNumber"] = Math.floor(Math.random() * 100); + + this.handler.state = states.GUESSMODE; + + this.emit(':ask', 'Great! ' + 'Try saying a number to start the game.', 'Try saying a number.'); + + }, + + 'AMAZON.NoIntent': function() { + + this.emit(':tell', 'Ok, see you next time!'); + + }, + + 'SessionEndedRequest': function () { + + console.log('session ended!'); + + this.attributes['endedSessionCount'] += 1; + + this.emit(':saveState', true); + + }, + + 'Unhandled': function() { + + var message = 'Say yes to continue, or no to end the game.'; + + this.emit(':ask', message, message); + + } +``` + +我们可以看到 AMAZON.YesIntent 和 AMAZON.NoIntent 在 guessModeHandlers 对象中是没有被定义的,因为对于该状态来说,“是”或者“不是”的回复是没有意义的。这样的回复将会被 ‘Unhandled’ 管理器捕捉到。 + +还有就是,注意在 NewSession 和 Unhandled 这两个状态中的不同行为。在这个游戏中,我们通过调用 newSessionHandlers 对象中的 NewSession 管理器“重置” skill 的状态。你也可以跳过这一步,然后 alexa-sdk 将会为当前状态调用 intent 管理器。你只需要记住在调用 alexa.execute() 之前去注册你的状态管理器,否则它们将不会被找到。 + +所有属性将会在你的 skill 结束 session 时自动保存,但是如果用户自己结束了当前的 session,你需要 emit ‘:saveState’ 事件(this.emit(‘:saveState’, true)来强制保存这些属性。你应该在 SessionEndedRequest 管理器中做这件事,因为 SessionEndedRequest 管理器将会在用户通过“退出”或回复超时结束当前 session 的时候被调用。你可以看看以上的代码示例。 + +我们将上面的例子写在了一个高/低猜数字游戏中,你可以点击[这里下载](https://github.com/alexa/skill-sample-nodejs-highlowgame)。 + +## 通过 Amazon DynamoDB 持久化 Skill 属性 + +很多人喜欢将 session 属性值储存到数据库中以便日后使用。alexa-sdk 直接结合了 [Amazon DynamoDB](https://aws.amazon.com/dynamodb/)(一个 NoSQL 的数据库服务)让你只需要几行代码就可以实现属性存储。 + +简单地在你调用 alexa.execute 之前为 alexa 对象中的 DynamoDB 的表格设置一个名字。 + +``` +exports.handler = function(event, context, callback) { + var alexa = Alexa.handler(event, context); + alexa.appId = appId; + alexa.dynamoDBTableName = ’YourTableName'; // That’s it! + alexa.registerHandlers(State1Handlers, State2Handlers); + alexa.execute(); +}; +``` + +之后,你只需要调用 alexa 对象的 attributes 为你的属性设置一个值。不再需要其他输入而得到单独的函数! + +``` +this.attributes[”yourAttribute"] = ’value’; +``` + +你可以提前[手动创建一个表格](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html)或者为你的 Lambda 函数的 DynamoDB 提供[创建表格权限](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html)然后一切都会自动生成。不过你要知道,在第一次唤醒 skill 的时候,创建表格可能会花费几分钟的时间。 + +尝试扩展高低猜数字游戏: + +* 让它能够储存你每次游戏中所猜的平均数 +* 加入[声音效果](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/speech-synthesis-markup-language-ssml-reference#audio) +* 给玩家有限的猜数字时间 + +想要获取更多关于学习使用 Alexa Skills Kit 开发的信息,可以看看下面的链接: + +[基于 Node.js 的 Alexa Skills Kit](https://github.com/alexa/alexa-skill-sdk-for-nodejs) +[Alexa 开发者播客](http://bit.ly/alexadevchat) +[Alexa 开发培训](https://developer.amazon.com/public/community/blog/tag/Big+Nerd+Ranch) +[关于 Alexa Skills 的介绍](https://goto.webcasts.com/starthere.jsp?ei=1087595) +[101 条语音交互设计指南](https://goto.webcasts.com/starthere.jsp?ei=1087592) +[Alexa Skills Kit (ASK)](https://developer.amazon.com/ask) +[Alexa 开发者论坛](https://forums.developer.amazon.com/forums/category.jspa?categoryID=48) + +- Dave ([@TheDaveDev](http://twitter.com/thedavedev)) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/apple-has-no-idea-whats-next-so-it-s-just-banging-on-the-same-old-drum.md b/TODO1/apple-has-no-idea-whats-next-so-it-s-just-banging-on-the-same-old-drum.md new file mode 100644 index 00000000000..5265bb250ab --- /dev/null +++ b/TODO1/apple-has-no-idea-whats-next-so-it-s-just-banging-on-the-same-old-drum.md @@ -0,0 +1,147 @@ +> * 原文地址:[Apple has no idea what’s next, so it’s just banging on the same old drum](https://medium.com/@ow/apple-has-no-idea-whats-next-so-it-s-just-banging-on-the-same-old-drum-dcfd0179cf80) +> * 原文作者:[Owen Williams](Owen Williams) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/apple-has-no-idea-whats-next-so-it-s-just-banging-on-the-same-old-drum.md](https://github.com/xitu/gold-miner/blob/master/TODO1/apple-has-no-idea-whats-next-so-it-s-just-banging-on-the-same-old-drum.md) +> * 译者: +> * 校对者: + +# Apple has no idea what’s next, so it’s just banging on the same old drum + +If you want to witness a company that’s simultaneously in its prime and losing control over its own narrative, look no further than WWDC, Apple’s second-most splashy event of the year, designed to offer a glimpse of the future. + +The annual developer event is a spectacle that I’ve watched live for almost a decade, but this year was different: it showcased a company that’s lost in the woods, playing the same old hits on repeat, in the same old format. + +Not only was it painful to watch, it demonstrated that Apple doesn’t really have a coherent plan, or understanding, of where it should take its core platform, let alone the ones it’s tried to build around it. + +It’s fine to have an off year, but what struck me was how… random it felt, and how little insight or forward thinking there was. Apple’s own platform advantages, company culture, and whatever else, seem to be pigeonholing its trajectory, driving it down a path that looks increasingly dated, and leaving me to wonder if the company is self-aware enough to see the shifting tide before it’s lost at sea. + +#### Big, slow, yearly + +![](https://cdn-images-1.medium.com/max/1000/1*tIUbwrpHZPbdNPXB569wPQ.png) + +Apple struggled throughout 2017 to ship flagship features it promised at WWDC 2017, including Airplay 2 and iCloud Messages, delivering them quietly just days before this year’s event. + +Alongside a scandal about performance throttling, a series of major security slip-ups, and hardware that shipped without long-touted features, many have loudly asked what’s causing these issues — and why a company with so many engineers is fundamentally failing to ship. + +Performance improvements are arguably the biggest focus of iOS 12. They’ll be welcome for many users, along with several additional improvements: streamlined notifications, a new ‘shortcuts’ feature for custom buttons, usage reporting, group FaceTime, AR updates and a number of other minor improvements to create a major release, iOS 12. + +The company’s other platforms received similar treatment, including macOS. Apple finished dark mode, a feature it [half-introduced](https://9to5mac.com/2014/11/08/yosemite-dark-mode/) all the way back in Yosemite, added basic functionality to Finder, threw in a new way to organize your desktop, and _boom — _there’s your major release, 10.14. + +None of these things are inherently bad — in fact, people have been complaining about the lack of improvements to things like FaceTime for years — but what’s interesting is Apple’s choice to bundle them together as a way to make them look truly meaningful, rather than just fixing many of these issues sooner, in a point release. I’m aware there’s a slew of tiny other fixes and features I haven’t listed here, but that’s my point: it’s a hodgepodge of things that have been neglected over the years after being debuted once and forgotten about. + +**Here’s the rub:** Apple could arguably ship notification improvements to iOS users tomorrow in a point release, iOS 11.5, but it won’t. Combining them provides the illusion of progress. Instead of servicing users and giving them features sooner, on a regular basis, Apple chooses to hold back simple functionality longer, for its bottom line. + +As Martin Bryant points out, [Apple may have a timing problem](https://www.getrevue.co/profile/bigrevolution/issues/big-revolution-apple-has-a-timing-problem-117182): + +> Yes, Apple needs to take the time to do ‘boring’ optimisation work on iOS, but why build iOS around these big, annual feature bumps and then disappoint people when the bumps aren’t very big? + +![](https://cdn-images-1.medium.com/max/1000/1*xyYGoFI-pve4NohGovx0Eg.png) + +Interestingly, the narrative here actually doesn’t make sense anymore, either. Every year, Apple takes the time to point out how _dire_ the state of the competition is: Nobody’s Android phones get updates! Android people don’t get any the latest features! Your phones all suck! The reality is different: Android users, regardless of manufacturer, frequently get them sooner than iOS users do, because Google divorced the operating system and core application suite from one another. + +Google’s approach to unbundling Android has, for the most part, been quietly successful — in an unexpected way. Instead of shipping monolithic feature updates, Google’s applications are now updated via the Play Store, from the clock app to the calculator and even the camera (unless you’re Samsung). + +Apple has made a yearly ritual out of jabbing competitors for poor update histories, but conveniently omits the reality that improvements to Google Assistant, the built-in web browser, or even just the OS keyboard will reach billions of users in a matter of hours without needing to update the entire phone. [Android’s support libraries](https://developer.android.com/topic/libraries/support-library/) mean developers can target older devices, with new features, regardless of whether or not they received the OS update. + +Meanwhile, if you find a bug in the iOS keyboard, or some weird security flaw in Safari’s web view, you hope it gets fixed in the next version of the operating system. Maybe next year, or the year after that. It depends how bad it is, or if Apple is actively maintaining the feature, as to when it’ll get serviced. + +Don’t get me wrong, Android has a terrible history of updates that is only now beginning to change, ten years after the fact. Google has made strides with Project Treble, which makes an end-run around the device maker itself, but it’s only in its infancy with new devices picking it up today. That’s not good enough either; but it’s gaining traction _and_ getting things into people’s hands. + +![](https://i.loli.net/2018/07/23/5b556d7e1426b.png) + +For each platform update, Apple dangles a carrot. That’s the flagship feature to convince you it’s a Big Update™ worth having immediately. On macOS this year, that’s dark mode, and on iOS, the promise of performance improvements and, _god forbid_, actually decent notification management. + +Arguably the most interesting segment out of WWDC happens at the very end of the two-hour keynote: [a peek at Project Marzipan](https://www.imore.com/marzipan), a long-term effort to unify the interface framework developers use to build apps for iOS and macOS, which is expected to ship to everyone in 2019. + +![](https://cdn-images-1.medium.com/max/2000/1*Ukm9QN-FSM6m8gjZb-bL7g.png) + +From where I sit, this is an impressive, massive project that doesn’t do much more than play defense against Electron’s continued march on Apple’s territory, threatening to kill native application development altogether. Why build anything native at all, when you can write once, and run everywhere? Anti-Electron fans will run rabid at the idea, but as the technology has become more efficient and introduced lower-level API access, it only makes even more business sense. + +Marzipan is an audacious plan to defend against that by making it easier to build cross-platform apps. It’s a genuinely fascinating play with fewer apparent benefits in the short term over just building an Electron app, which addresses an additional billion users, allows developers to use familiar web technology _and_ is truly write-once-run-everywhere. + +Over time, Marzipan may win favor with developers, but I’m not convinced it’ll stop web-based technologies swallowing native app development whole, particularly given that both Microsoft _and_ Google have now bet their entire strategies on Progressive Web Applications, and how low the barrier of entry has come as a result of Electron’s success. + +Marzipan indicates something bigger, of course, such as an impending shift away from Intel chipsets entirely to some sort of custom Apple ARM-based silicon in — _shock horror — _a productivity form factor. If anything, what will win as a result will be that control, and what it could ship in a end-to-end device: true all-day battery? Always-on LTE with desktop class apps? + +If so, the message is this: lock in with us, develop for our platforms, and we’ll reward you. Don’t, and you’ll be shut out and stuck on the outside. + +#### Hey Siri, where’s the vision? + +![](https://cdn-images-1.medium.com/max/1000/1*CRkO0VCT6Mh2CFRtbhhfmA.png) + +What’s clearly missing in all of this is a willingness to take risks, or go for the long view on what’s better than the status quo for Apple’s users. Instead of looking at how phone usage is changing and redesigning the nature of iOS, it’s another year of shoehorning new features into a decade-old shell. + +The new shortcuts feature promises to let users wire up workflows of their dreams, chaining together tasks behind a single button. Yes, this is a great improvement to iOS that addresses a problem without actually improving on the reason anyone needs this in the first place — it’s just glued onto the homescreen that’s responsible for causing the need for it in the first place. + +Apple could have offered up a way to surface the weather right there, deeply integrated with the lock screen, or calendar events at the top of your home screen along with the icons, but it didn’t. Instead, it slathered what appears to be a UX hack in the shape of a notification, and tries to guess when you want to see it. + +Google’s own developer conference, just down the street in Mountain View, was held in May and offered a clearer, if poorly highlighted, view of the future: AI is a core part of mobile devices going forward, so we’re beginning to add it everywhere. + +![](https://cdn-images-1.medium.com/max/600/1*lTYCJE8xA9-M8G61QkAKsA.gif) + +The Android alternative to Shortcuts, Slices and App Actions, surfaces the device’s best guess at your next action as a deeply integrated interface component, where you can actually see information before actually going further in, or taking an action. + +Want a button to order a Lyft? Great, here’s a button embedded within the system’s app tray, with the current estimated price of your ride, which orders it right now with a single tap. Much of this data is crunched on device, just like Apple’s audacious claims to privacy brag about as well, but instead of being a UX hack to add buttons that summon help, the information is already right there, on hand, without opening anything, even Assistant. + +Google and Apple both anticipate a future in which we use our phones less — time well spent is a core part of this driver — and as a result, it appears Google has spent a lot of time thinking about how AI can help get the right information to the user. The result is the exact button they need at the right time, with relevant information, sans the need to actually go away and do something. + +To facilitate this, Google is willing to rejig the UX of its devices, mess with the sea of icons, and has invested heavily in serendipitous computing with Google Home alongside this, so it can get you there faster regardless of if the phone is in your hand. + +![](https://cdn-images-1.medium.com/max/2000/1*eCsl8DddzfF1WJRNk4QfZA.png) + +Google’s vision of the future of smartphones, mobile operating systems, and the way we’ll interact with devices over the long haul is a coherent, well-told story: get more out of your day, get the devices out of the way. It even has a [fantastic page](https://store.google.com/us/magazine/google_cross_product_experience?hl=en-US) that showcases how its own ecosystem works better, together, than I’ve ever seen explained about Apple’s ecosystem on its own site. + +As for _why_ all of this happens, I suspect it’s a difference in strategy and approach. Apple’s strategy has long been to monetize its existing cash cows as long as it can by throwing out new stuff to see what sticks and doubling down on that, rather than creating any sort of coherent narrative of what the future actually looks like, operating in secrecy until it somehow lands upon it. + +Incremental improvement is fine, but there’s a distinct lack of forward-looking, and a whole lot of looking over the fence at what everyone else is doing to bash it instead. + +#### Apples, oranges and comparing the two + +![](https://cdn-images-1.medium.com/max/1000/1*GXShGcoP70vKsNXCqfWByQ.png) + +It’s easy to compare and contrast Google and Apple because they are very different companies, but what they’re both claiming to do is the same: invent the future, whatever that actually might be. + +Their approaches, however, are increasingly diverging: Apple’s squeezing more out of less, shipping flashy features, and focusing on privacy, while Google and others have pushed further into understanding the user and getting out of their way. + +Most of this comes down to business model. + +Apple’s focus on features by piling them together drives more sales of iPhone, which drives reliable revenue on a yearly basis. Google’s is on advertising and relevance to the user, which doesn’t depend on a particular feature or thing to tout, it just needs you to love using its tools (and not mind advertising). + +Apple’s entire strategy over the last two decades has pivoted around the exploitation of a product line until something new comes along, then rinse and repeat. This is framed around improving your life and often actually does, even if that is by proxy. I’d argue that the company’s vision of the future isn’t to enrich, or drive progress, but to squeeze as much revenue as possible out of slick, well-designed and marketed ideas. The products it builds, the cycles they’re released in and the way that Apple’s entire software cycle works reflects this. + +An example of the manifestation of this is perhaps HomePod’s requirement to have a locally available iPhone to do anything interesting, leaving it crippled without one, and Animoji’s debut only to be locked away in Messages instead of somewhere like the camera. + +![](https://cdn-images-1.medium.com/max/1000/1*qf_K81yBsB-b2yJ9explpw.png) + +Google, a latecomer in the game, has the luxury — and peril — of not depending on phone revenue, so it can risk it all and get weird, since it’s not fundamentally critical to the company’s continued trajectory. Microsoft has done the same, now finding itself the underdog, risked it all and [moved to an ‘OS-as-a-service’ model](https://docs.microsoft.com/en-us/windows/deployment/update/waas-overview) in which it ships features when they’re ready instead of waiting for flashy releases. + +Apple, on the other hand, begins and ends with the iPhone today, the rest flows from there. It can’t just rip up the foundation on which its revenue exists, and Tim Cook hasn’t shown a flair for doing so. iOS is too valuable to go away and tear down to just reimagine it for fun, so it’s the status quo, with experiments like HomePod and AirPods on the side, where it _can_ get weird and sometimes wonderful. That’s fine, because Apple has plenty of cash lying around, but it’s interesting how limiting the approach can become. + +As we hurtle toward peak smartphone, the cracks here are beginning to show because Apple don’t _have_ the next big thing yet — that we know of, naturally — and it’s taking a long time to get here. We’re essentially watching the bottom of the metaphorical tube of toothpaste being squeezed, while others are trying to figure out if maybe the tube should work completely differently. + +AR is potentially the next platform, yes, [and it’s clear that Apple is pushing forward on that](https://www.wired.com/story/apple-wwdc-augmented-reality-wearables/%5C) in a big way, so it’s easy to imagine a scenario in which it makes sense to shift precious resources there instead of focusing on iOS which may wind up unimportant in a year or two. I’m not convinced that in the short term, such as the oft-claimed 2020 launch date of an Apple VR/AR headset, that we’ll be headed there in any meaningful capacity. I mean, Magic Leap, a bajillion dollar company building the future of AR showed off its hardware yesterday on Twitch, quipping that “you better not put it in your pocket or it’ll overheat.” + +I’m happy to be wrong, and I write this knowing I’ll probably be that guy who [very publically crapped on the iPhone at launch later](http://bgr.com/2015/04/07/original-iphone-reaction-comments/). Apple’s worth a very large amount of money, which is more than enough proof that it’s good at many things, including convincing people to buy a phone every year. + +![](https://cdn-images-1.medium.com/max/1000/1*_fmWBe3iuLHDiDezd6PR9g.png) + +So, what if the next platform just doesn’t arrive any time soon? We’re reaching a plateau as computing performance and power improvements level out, and the pace of innovation the iPhone — and all smartphones — relied on to exist is drying up. The software platforms have shifted entirely, like Microsoft’s focus to almost entirely be on enterprise productivity, and Google’s on being available wherever the user is in the ecosystem. They stand poised to benefit, as they offer a growing array of capabilities across the spectrum, from the smart home to a reinvention of human-computer interaction via voice assistants, and the competition further locks down the moats. + +In a new world that’s defined by ambient, intelligent computing, that just does stuff on our behalf and our tools having the context they need about us to be useful, Apple may be out of its depth or simply unwilling to risk making a bold enough bet to go beyond the iPhone. + +I think it’s a bit of both, and it’s on full display as unlikely new underdogs emerge. None of this is to say Google, Microsoft or anyone else is any better: they all have their own disadvantages, absurd inconsistencies or weird narratives at times, but a shift certainly _feels_ like it’s happening below the surface, and there’s a window of opportunity in which Google seems to be executing extremely well so far. + +Yeah, in the end these are all just tools; a way to get things done. Some people like one thing, others like the other. + +People will always choose whatever helps them get more out of their lives, and what best fits their lifestyle. For years, that default for many has been the iPhone, but nothing is forever. I think people are starting to notice. + +* * * + +_If you enjoyed this and want more insights into the technology industry, my weekday morning briefing helps you understand what’s worth knowing and why. Use the code_ **_medium-friend_** _at checkout for 40% off the first month. ♥️_ + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/articles-website-design-mistakes.md b/TODO1/articles-website-design-mistakes.md new file mode 100644 index 00000000000..3e74b89b761 --- /dev/null +++ b/TODO1/articles-website-design-mistakes.md @@ -0,0 +1,414 @@ +> * 原文地址:[Common webpage design mistakes](http://blog-en.tilda.cc/articles-website-design-mistakes) +> * 原文作者:[tilda](http://blog-en.tilda.cc/articles-website-design-mistakes) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/articles-website-design-mistakes.md](https://github.com/xitu/gold-miner/blob/master/TODO1/articles-website-design-mistakes.md) +> * 译者:[StellaBauhinia](https://github.com/StellaBauhinia) +> * 校对者:[BillShiyaoZhang](https://github.com/BillShiyaoZhang),[Hopsken](https://github.com/Hopsken) + +# 常见网页设计错误一览 + +简单的排版和设计诀窍,助力你编写出出色的网页 + +![](https://static.tildacdn.com/tild6662-3339-4234-b635-396133626363/_08.svg) + +## 需要避免的首页常见设计错误 + +**1. 页面内容没有分割成逻辑区域** + +如果页面信息被分组形成逻辑区域,用户会更容易看懂。请把内边距(Padding)设置成 120 像素到 180 像素,并且通过背景色把不同文本区域分开。 + +![在不同组的相关信息间没有设置边距,而且这个设计稿需要用色块把](https://static.tildacdn.com/tild6338-3765-4565-b733-323464326432/-/empty/noroot_1.png) + +![](https://static.tildacdn.com/tild6662-3662-4139-b465-306238313938/-/empty/noroot_2.png) + +不同组的相关信息间没有设置边距,而且这个设计需要用色块把页面划分出逻辑区域。所以,目前这些信息让人很难看懂,而且文字的区域分组也很不清晰。 + +边距足够大,而且不同区域通过背景色分开,这让什么区域包含什么信息变得一目了然。 + +**2. 页面元素的空白间距不相等** + +请为页面的逻辑区域设置相等的空白间距。否则,你的页面会看起来很凌乱,用户不会把注意力均分到每一个区域上。 + +![](https://static.tildacdn.com/tild6335-3338-4263-b430-636365313837/-/empty/_-1.png) + +![](https://static.tildacdn.com/tild6637-3936-4132-a433-373061663564/-/empty/_-1.png) + +不同位置的空白间距不均匀,并让人产生了公司信息与标题相连的印象,尽管每个区域的结构是一样的。 + +标题和内容上下的空白边距相等,让人意识到每个逻辑区域的信息一样重要。 + +**3. 边距太小,意味着用户无法把内容分解成不同逻辑区域** + +为了避免逻辑分区混在一起,请把它们隔开并插入大段空白(至少 120 像素)。 + +![](https://static.tildacdn.com/tild6535-6264-4662-a364-333163663965/-/empty/__20170919__111314_.png) + +![](https://static.tildacdn.com/tild6135-6561-4630-a464-653162353735/-/empty/__20170919__111400.png) + +使用了窄边距,构成站点的区域彼此粘在了一起。这让页面很拥挤,而且十分费解 —— 网站访问者会以为这是一段纯文字,而不是含义不同的区域。 + +边距足够大,因此这两个区域很容易被区别开。 + +**4. 避免图上文字与底图对比度太低** + +文字和背景之间应有足够的对比度。为了让文本更显眼,在底图上放一层(增加)对比度的滤镜。黑色比较流行,但你也可以使用鲜艳的颜色混合搭配。 + +另一个选择是从开始就使用高对比图片,并将放在照片较暗部分的上面。 + +![](https://static.tildacdn.com/tild3265-3735-4231-b661-633937623564/-/empty/noroot.png) + +这张照片太亮,文字难以阅读 + +![](https://static.tildacdn.com/tild3163-3033-4337-b939-303731623563/-/empty/noroot.png) + +在照片上使用滤镜后文字易于阅读了。 + +**5. 页面上太多样式** + +单页上的排版和设计样式太多,会看起来不专业,也难以阅读。为了避免这点,降低页面视觉饱和度,请限制自己只用一个字体和两个文字样式,如普通和加粗。 + +![](https://static.tildacdn.com/tild3061-3261-4337-a133-303536616431/-/empty/ggtg.png) + +![](https://static.tildacdn.com/tild3738-6665-4133-b836-316463386665/-/empty/dbgdbg.png) + +由于使用了太多的排版样式,完全不清楚视觉重点在哪里。 + +为视觉饱和度只使用了一种字体,一种颜色和两种样式。本页排版看着很整洁。 + +**6. 色块区域太窄了** + +请不要用色块突出狭窄的页面元素。它就是很难看。例如,标题已经通过它们的文字大小、样式和间距变得很显眼了。你想突出页面上的某一点吗?请在整个区域上使用背景色,包括相关的标题和文本。 + +![](https://static.tildacdn.com/tild3164-6435-4063-a262-633165613635/-/empty/noroot.png) + +![](https://static.tildacdn.com/tild3337-3730-4631-a466-373330343238/-/empty/noroot.png) + +放置在彩色背景上的标题打破了页面的连续性,让它们看起来像分离的、独立的元素。 + +标题和相关文本使用了相同的背景。这表明它们属于同一逻辑区域。 + +**7. 在窄栏中包含了太多文字** + +当窄栏中含有大量文字时,阅读会很费劲,因为页面访问者不得不一行一行地跳转视线。另外,它就是很难看!最好减少列数,缩短文本长度,否则没人会看的。 + +![](https://static.tildacdn.com/tild6639-3039-4437-b564-303364373363/-/empty/__20170919__111314_.png) + +![](https://static.tildacdn.com/tild3462-6430-4837-b465-386132303039/-/empty/__20170919__111400_.png) + +长而集中的栏列很难阅读 + +栏列中的文本很少,所以很容易阅读 + +**8. 太多居中文字了** + +当文本很短的时候,页面居中是一种很好的方式,否则用户很难高效地浏览。同时,从 24 像素开始增加字体大小。 + +如果需要包含大量文本,请使用具有可折叠文本功能的区域(在 Tilda 中,它们是 TX12、TX16N 或者是按钮 BF703)。 + +![](https://static.tildacdn.com/tild6461-6364-4466-b037-383265636437/-/empty/noroot_3.png) + +![](https://static.tildacdn.com/tild6337-3962-4361-b861-653434643432/-/empty/noroot_4.png) + +长而集中的文字很难阅读 + +标题下的短文本(均为居中样式)在页面上看起来很好 + +**9. 文字覆盖住了图片的重要部分** + +避免使用文本覆盖图片的有意义的部分或小细节。这样做的话你既会模糊图像,也使文本难以辨认。尝试不同的文字位置,如居中,左对齐或者垂直放置。 + +![](https://static.tildacdn.com/tild3036-3030-4766-b361-376266626562/-/empty/ghtt.png) + +![](https://static.tildacdn.com/tild6431-3864-4635-a564-383039663335/-/empty/dgdfgf.png) + +这个标题挡住了女人的面容。(文字下的图片中)有大量细节,使文本难以阅读。 + +图片和文字都容易阅读,构图很好。 + +**10. 误用视觉层次结构** + +为了使页面上的信息层次清晰可见,封面上的标题字体应该大于其余标题,或者至少是相同大小。举个例子,如果标题很长,尤其要这么处理。 + +![](https://static.tildacdn.com/tild6162-6462-4735-b162-356533303736/-/empty/noroot_5_42.png) + +![](https://static.tildacdn.com/tild3638-6633-4232-a238-383133396266/-/empty/4_.png) + +封面上的标题不合比例地小于下面的标题,这很让人费解。为什么?它让第二个标题更显眼了。 + +封面上的标题比下面区域中的标题大,因此整个页面看起来协调一致。 + +同样的原理适用于逻辑区域内的视觉层次结构。标题应该是页面上最大的设计元素,其次是较小的、不太突出的子标题。接下来,内容标题应明显小于主标题,但使用相同的粗细程度。最小的字体应该用于内容描述。 + +这将有助于页面访问者区分最重要和次等重要的信息。 + +![](https://static.tildacdn.com/tild3266-3936-4132-b266-316538306438/-/empty/noroot_.png) + +![](https://static.tildacdn.com/tild3035-3736-4563-b039-346439343137/-/empty/4_1_.png) + +主标题比内容标题小,看着像从属内容,尽管它在整体内容中更重要。 + +主标题是页面上最突出的元素,虽然内容标题的字体较小,但仍然清晰可见。 + +**11. 一个逻辑区域拆成两个** + +一个跟在文本之后,占据全屏的图像或图片画廊,显得跟独立的区域很像。如果你在图片画廊周围添加边距,由于背景一致,文字和图像看起来是一个逻辑区域整体。 + +![](https://static.tildacdn.com/tild6563-3763-4638-b231-613336373531/-/empty/noroot_6.png) + +![](https://static.tildacdn.com/tild6361-3762-4139-b832-386133616137/-/empty/noroot_7.png) + +全屏的图片画廊看起来与上面的标题脱节,像一个独立的区域。 + +图片画廊和它上面的标题背景一致,这使整个构图看着紧密。 + +**12. 标题太大太长** + +对短句子来说,超大字体是完美适配的。如果标题较长,请使用小号字体。这将利于阅读,并给其它的设计元素在页面上留出足够的空间。 + +![](https://static.tildacdn.com/tild3965-3362-4531-b637-353466626339/-/empty/ddfb.png) + +![](https://static.tildacdn.com/tild3839-3862-4333-b234-623335643236/-/empty/ggb.png) + +标题太大,占据了整个封面,而别的设计元素挤在剩余空间中,标题也很难阅读。 + +这个页面被有机地整合到一起,所有的设计元素之间都是协调的,文字也利于阅读。 + +**13. 误用按钮的边框样式** + +当按钮是透明的时候,边框是必需的。为一个带颜色的按钮添加边框是没有意义的,它是另一个无意义的设计特性,会让页面变得拥挤,并难以阅读。 + +![](https://static.tildacdn.com/tild3034-6436-4436-a131-323039316636/noroot.png) + +**14. 使用太多种颜色了** + +在页面上使用太多种颜色很令人费解, 而且让人闹不清哪些部分更重要。一两种颜色足以给真正重要的东西带来突出的视觉效果。 + +![](https://static.tildacdn.com/tild3330-3362-4636-b132-313337643536/-/empty/dfgdg.png) + +![](https://static.tildacdn.com/tild6461-3831-4261-a666-396132333666/-/empty/dgdgd.png) + +页面上鲜艳的颜色太多了,这很混乱。 + +一种颜色作为基调,在此之上衍生出色彩多样性,这样就不干扰页面内容了。 + +**15. 拥挤的菜单** + +人们访问网站是要为他们的问题找到解决方法。请帮助他们!使用菜单帮助人们浏览网站,并简单快速地让他们找到需要的东西。不要堆积过多信息使他们不堪重负。5-7 个菜单项就足够了。 + +![](https://static.tildacdn.com/tild3439-6233-4136-b938-396137326564/-/empty/noroot.png) + +这个菜单包含了太多信息,让页面导航变得很困难。 + +![](https://static.tildacdn.com/tild3361-6464-4436-b664-316464393930/-/empty/noroot.png) + +一个简单的菜单让你很容易找到你需要的东西。 + +## 文章的排版设计错误 + +**1. 密密麻麻的文字长段** + +一堵墙一样的文字让阅读变得费劲并难以理解。为了舒适的浏览体验,请将其拆分为段落,或引入如关键句样式、图片作为阅读区的分隔。 + +![](https://static.tildacdn.com/tild3939-3464-4266-b864-636166643064/-/empty/noroot_6.png) + +![](https://static.tildacdn.com/tild6366-3264-4432-a162-643133313439/-/empty/noroot_7.png) + +一堵墙一样的文字,很难阅读。 + +引用句或图片这样的页面元素使阅读文本更容易。 + +**2. 标题与上下段落距离相等** + +标题不应该以相等距离挂在章节中间,因为它归属于下面的段落。标题距上方的距离应该比下方大 2-3 倍。同时,标题与下方段落的距离应该与段落间距大致相同,或者稍大一些。这样,标题会从视觉上引领着后续文本。 + +![](https://static.tildacdn.com/tild3565-3834-4165-b336-316633336633/-/empty/noroot.png) + +![](https://static.tildacdn.com/tild6637-3961-4431-a265-626230333266/-/empty/noroot.png) + +标题与上下段落之间的距离相等,不清楚它属于哪个段落。 + +由于标题使用了合适的边距,很明显标题属于下面的段落。 + +**3. 排版没有逻辑顺序** + +在版面设计中,字体大小对比是用来划分不同视觉层次的文本,并建立严谨结构的。主标题应该是网页中最突出的,子标题应该小不少,但也要清晰可见。 + +![](https://static.tildacdn.com/tild6266-3064-4632-a535-663466366432/-/empty/noroot_1_.png) + +![](https://static.tildacdn.com/tild3161-3430-4431-b031-386138373562/-/empty/noroot_2_.png) + +标题和子标题的大小大致相同,之间没有明显的层次结构。 + +页面排版逻辑显现出标题比副标题更重要。 + +**4. 区域的上下间距不等** + +如果不同区域在页面中同等重要,那它们应有相同的界面外观,并且位置间距离应该相等。 + +![](https://static.tildacdn.com/tild3638-3731-4533-b466-666236316631/-/empty/__20170919__111314.png) + +![](https://static.tildacdn.com/tild6664-6133-4364-b565-373066323731/-/empty/__20170919__111400.png) + +如果封面和作者照片之间的空白太窄,看起来作者是与封面,而不是与后面的文本有更多联系。 + +由于图像上下方边距相等,各个区域显得同等重要。 + +**5. 说明文字与图片距离太近了** + +从一方面说,图片和说明文字形成一个整体,但这是两个独立的元素,说明文字不应该干扰图片。 + +![](https://static.tildacdn.com/tild3930-6362-4536-a661-303737616233/-/empty/__20170919__111314.png) + +![](https://static.tildacdn.com/tild3538-3833-4832-b031-323165616530/-/empty/__20170919__111400.png) + +说明文字贴着图片,我们单独看它们其中一个都很别扭。 + +图片和说明文字间有很多空白,但是很明显,说明文字是附属于图片的。 + +**6. 子标题和文字间空白太少** + +子标题和它紧随其后的文本属于一个整体,但是如果文章中段落间的空白大于子标题和段落间的空白,文章看起来是不连贯的。 + +![](https://static.tildacdn.com/tild3661-3433-4166-a530-626533623166/-/empty/__20170919__111314.png) + +![](https://static.tildacdn.com/tild6237-6538-4663-a165-383231383036/-/empty/__20170919__111400.png) + +标题和段落之间的空白小于段落之间的空白。 + +标题后的空白略大于段落之间的空白。 + +**7. 视觉突出元素放得离正文太近了** + +用于强调表达的页面元素,如关键句或引用句是独立的。把它们与正文的边距设置成 75-120 像素,就可以让他们真正显眼。 + +![](https://static.tildacdn.com/tild3866-3036-4131-b130-363762346639/-/empty/__20170919__111314_1.png) + +![](https://static.tildacdn.com/tild3535-3932-4163-b237-663561666265/-/empty/__20170919__111400.png) + +正文与突出元素的空白间距太小了。 + +由于空白间距较大,引用句变得真正显眼了。 + +**8. 元素间的视觉对比差太低** + +如果你想强调某个句子,把它加粗,并且把关键句的字体调到比正文字体号大 10-15 像素。让关键句从正文的其余部分中脱颖而出。 + +![](https://static.tildacdn.com/tild6636-3037-4131-b737-626432323630/-/empty/__20170919__111314.png) + +![](https://static.tildacdn.com/tild3335-6134-4533-b731-663961653865/-/empty/__20170919__111400.png) + +关键句与剩余文字混在一起。看起来很乱,请尽量避免这样。 + +现在每个人都可以一眼看到,因为字体很大,周围有足够的空白间距。 + +**9. 为窄长的文本区域使用背景色** + +如果你想强调页面的一小部分,如作者信息,在它周围设足够的空白间距就够了,用户会对间隔产生印象。不要把这一部分放在背景色上,这样会显得不合适。 + +![](https://static.tildacdn.com/tild6536-3766-4464-b432-636566326637/-/empty/__20170919__111314.png) + +![](https://static.tildacdn.com/tild3931-3639-4261-b166-663839393132/-/empty/__20170919__111400.png) + +不要为子标题加背景色。使用更大的字体和间距就足够让它在页面上突出了。 + +![](https://static.tildacdn.com/tild6530-3336-4361-b662-393731333534/noroot_5.png) + +**10. 两个全屏图片中的空白** + +当你使用了一串的全屏图片时,请避免在它们之间留下空白。图片边框是可见的,并且不需要添加额外的元素。别再加任何东西了。 + +![](https://static.tildacdn.com/tild3435-6238-4930-b066-303637636530/-/empty/__20170919__111314.png) + +![](https://static.tildacdn.com/tild3236-6334-4632-b632-663762646261/-/empty/__20170919__111400.png) + +全屏图片之间的空白没有任何意义,看起来也不好。 + +在这个例子中,图片流是和谐一体的。 + +**11. 使用了了太多设计语言** + +设计语言(如粗体)在使用很少的时候表现良好。使用太多,就会妨碍页面阅读。 + +![](https://static.tildacdn.com/tild6630-3538-4535-b934-336431386461/-/empty/noroot_4.png) + +很多单词用粗体标记,所以一段文本好像断裂了。 + +![](https://static.tildacdn.com/tild3831-3730-4836-a633-316530323365/-/empty/noroot_3.png) + +一些有标记的词可以引起读者注意,同时并不干扰正文的其余部分。 + +**12. 太多排版样式** + +设计不应干扰可读性。排版风格越少,重要的元素就会在视觉上越明显。使用主标题子标题,和关键句样式对比就够了。 + +![](https://static.tildacdn.com/tild6231-3337-4233-b238-656363376437/-/empty/__20170919__111314.png) + +![](https://static.tildacdn.com/tild6362-3465-4136-a665-313765616238/-/empty/__20170919__111400.png) + +这段文本有太多排版元素了。他们在分散读者注意力。 + +非常少的排版样式,强调点很清晰,文本层次结构一览无余。 + +**13. 在长文本中使用居中样式** + +居中样式通常用于标题和引用句,用以把它们和其余文本区别开来。一个居中的长文本很难阅读。 + +![](https://static.tildacdn.com/tild3436-3164-4761-a162-346336326366/-/empty/noroot.png) + +![](https://static.tildacdn.com/tild6333-6465-4166-b433-303831396261/-/empty/noroot.png) + +一个居中的文本看起来很乱,而且很难阅读。 + +向左对齐的文本对视觉浏览来说是舒适的。 + +**14. 标题与图片太接近了** + +标题是一个单独的设计元素。它不应该离下面的图片太近。对于一个成功的标题图片组合区域,请设置元素的间距不小于 60 像素,并添加子标题 —— 它将展开页面内容,把正确的重点放在你需要的地方。 + +![](https://static.tildacdn.com/tild3130-3863-4835-b861-393736393364/-/empty/noroot.png) + +![](https://static.tildacdn.com/tild3139-6661-4466-b266-623637356566/-/empty/noroot.png) + +标题太贴近图片,页面快窒息了。 + +在这里,标题与用图片用子标题分开,它引领了整个区域,而不仅是图片 + +**15. 在不必要的地方使用斜体** + +斜体用来强调文本中的一个词或短语。它并不像加粗样式那样会被立即注意到,但是它确实在需要时做到了强调的效果。 + +不要通篇使用斜体字(正文,标题等)。如果在文本中使用了 sans-serif(无衬线)系字体,请不要使用斜体。 + +![](https://static.tildacdn.com/tild3462-3239-4662-b434-633263623635/-/empty/photo.png) + +由于字体大小和空白间距,这个句子已经脱颖而出,所以这里不需要斜体。 + +![](https://static.tildacdn.com/tild6365-3238-4338-a537-366232383932/-/empty/photo.png) + +斜体用在了正确的地方,文本加入了适量的强调元素。 + +**16. 相对与页面中心和其它元素来说,区域很不协调** + +如果你在调整页面(改变字体大小,对齐或缩进)的时候休息一下再回顾,你可以轻松地发现这个错误。 + +![](https://static.tildacdn.com/tild3861-6565-4264-a565-353831623536/-/empty/__20170919__111314.png) + +![](https://static.tildacdn.com/tild3232-3930-4632-a131-313865623634/-/empty/__20170919__111400.png) + +在这个例子中,标题偏左移了,文本偏右移了。 + +所有的文本元素彼此和谐。 + +* * * + +作者: Ira Smirnova, Masha Belaya, Julia Zass +页面排版设计: Julia Zass + +你觉得这篇文章有用吗?如果是,请与你的朋友分享。非常感谢! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/automated-feature-engineering-in-python.md b/TODO1/automated-feature-engineering-in-python.md new file mode 100644 index 00000000000..9a0162840b5 --- /dev/null +++ b/TODO1/automated-feature-engineering-in-python.md @@ -0,0 +1,237 @@ +> * 原文地址:[Automated Feature Engineering in Python](https://towardsdatascience.com/automated-feature-engineering-in-python-99baf11cc219) +> * 原文作者:[William Koehrsen](https://towardsdatascience.com/@williamkoehrsen?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/automated-feature-engineering-in-python.md](https://github.com/xitu/gold-miner/blob/master/TODO1/automated-feature-engineering-in-python.md) +> * 译者:[mingxing47](https://github.com/mingxing47) +> * 校对者:[yqian1991](https://github.com/yqian1991) [Park-ma](https://github.com/Park-ma) + +# Python 中的特征工程自动化 + +## 如何自动化地创建机器学习特征 + +![](https://cdn-images-1.medium.com/max/1000/1*lg3OxWVYDsJFN-snBY7M5w.jpeg) + +机器学习正在利用诸如 [H20](http://docs.h2o.ai/h2o/latest-stable/h2o-docs/automl.html)、[TPOT](https://epistasislab.github.io/tpot/) 和 [auto-sklearn](https://automl.github.io/auto-sklearn/stable/) 等工具越来越多地从手工设计模型向自动化优化管道迁移。以上这些类库,连同如 [random search](http://www.jmlr.org/papers/volume13/bergstra12a/bergstra12a.pdf) 等方法一起,目的是在不需要人工干预的情况下找到适合于数据集的最佳模型,以此来简化器学习的模型选择和调优部分。然而,特征工程,作为机器学习管道中一个[可以说是更有价值的方面](https://www.featurelabs.com/blog/secret-to-data-science-success/),几乎全部是手工活。 + +[特征工程](https://en.wikipedia.org/wiki/Feature_engineering),也称为特征创建,是从已有数据中创建出新特征并且用于训练机器学习模型的过程。这个步骤可能要比实际使用的模型更加重要,因为机器学习算法仅仅从我们提供给他的数据中进行学习,创建出与任务相关的特征是非常关键的(可以参照这篇文章 ["A Few Useful Things to Know about Machine Learning"](https://homes.cs.washington.edu/~pedrod/papers/cacm12.pdf) —— 《了解机器学习的一些有用的事》,译者注)。 + +通常来说,特征工程是一个漫长的手工过程,依赖于某个特定领域的知识、直觉、以及对数据的操作。这个过程可能会非常乏味并且最终获得的特性会被人类的主观性和花在上面的时间所限制。自动特征工程的目标是通过从数据集中创建许多候选特征来帮助数据科学家减轻工作负担,从这些创建了候选特征的数据集中,数据科学家可以选择最佳的特征并且用来训练。 + +在这篇文章中,我们将剖析一个基于 [featuretools Python library](https://docs.featuretools.com/#) 库进行自动特征工程处理的案例。我们将使用一个样例数据集来展示基本信息(请继续关注未来的使用真实数据的文章)。这篇文章最终的代码可以在 [GitHub](https://github.com/WillKoehrsen/automated-feature-engineering/blob/master/walk_through/Automated_Feature_Engineering.ipynb) 获取。 + +* * * + +### 特征工程基础 + +[特征工程](https://www.datacamp.com/community/tutorials/feature-engineering-kaggle)意味着从分布在多个相关表格中的现有数据集中构建出额外的特征。特征工程需要从数据中提取相关信息,并且将其放入一个单独的表中,然后可以用来训练机器学习模型。 + +构建特征的过程非常耗时,因为每获取一项新的特征都需要很多步骤才能构建出来,尤其是当需要从多于一张表格中获取信息时。我们可以把特征创建的操作分成两类:**转换**和**聚合**。让我们通过几个例子的实战来看看这些概念。 + +一次**转换**操作仅作用于一张表,该操作能从一个或多个现有列中创建新特征(比如说 Python 中,一张表就如同 Pandas 库中的一个 `DataFrame`)。如下面的例子所示,假如我们有如下的一张客户(clients)信息表: + +![](https://cdn-images-1.medium.com/max/800/1*FHR7tlD4FuGKt8n5UHUpqw.png) + +我们可以通过从 `joined` 列中寻找出月份或者对 `income` 列取自然对数来创建特征。这些都是转换的范畴,因为他们都是使用了单张表中的信息。 + +![](https://cdn-images-1.medium.com/max/800/1*QQGYN1PD06rNT-bJphNcBA.png) + +另一方面,**聚合** 则是跨表执行的,其使用了一对多关系进行分组观察,然后再计算统计数据。比如说,如果我们还有另外一张含有客户贷款信息的表格,这张表里可能每个客户都有多种贷款,我们就可以计算出每位客户端诸如贷款平均值、最大值、最小值等统计数据。 + +这个过程包括了根据客户进行贷款表格分组、计算聚合、然后把计算结果数据合并到客户数据中。如下代码展示了我们如何使用 Python 中的 [language of Pandas](https://pandas.pydata.org/pandas-docs/stable/index.html) 库进行计算的过程: + +```python +import pandas as pd + +# 根据客户 id (client id)进行贷款分组,并计算贷款平均值、最大值、最小值 +stats = loans.groupby('client_id')['loan_amount'].agg(['mean', 'max', 'min']) +stats.columns = ['mean_loan_amount', 'max_loan_amount', 'min_loan_amount'] + +# 和客户的 dataframe 进行合并 +stats = clients.merge(stats, left_on = 'client_id', right_index=True, how = 'left') + +stats.head(10) +``` + +![](https://cdn-images-1.medium.com/max/800/1*jHHOuEft93KDenbRpaFcnA.png) + +这些操作本身并不困难,但是如果我们有数百个变量分布在数十张表中,手工进行操作则是不可行的。理想情况下,我们希望有一种解决方案,可以在多个表格当中进行自动转换和聚合操作,最后将结果数据合并到一张表格中。尽管 Pandas 是一个很优秀的资源库,但利用 Pandas 时我们仍然需要手工操作很多的数据!(更多关于手工特征工程的信息可以查看如下这个杰出的著作 [Python Data Science Handbook](https://jakevdp.github.io/PythonDataScienceHandbook/05.04-feature-engineering.html))。 + +### Featuretools 框架 + +幸运的是, featuretools 正是我们所寻找的解决方案。这个开源的 Python 库可以自动地从一系列有关联的表格中创建出很多的特征。 Featuretools 是基于一个被称为 "[Deep feature synthesis](http://featurelabs1.wpengine.com/wp-content/uploads/2017/12/DSAA_DSM_2015-1.pdf)" (深度特征合成)的方法所创建出来的,这个方法听起来要比实际跑起来更加令人印象深刻。(这个名字是来自于多特征的叠加,并不是因为这个方法使用了深度学习!) + +深度特征合成叠加了多个转换和聚合操作(在 feautretools 中也被称为 [feature primitives (特征基元)](https://docs.featuretools.com/automated_feature_engineering/primitives.html))来从遍布很多表格中的数据中创建出特征。如同绝大多数机器学习中的想法一样,这是一种建立在简单概念基础上的复杂方法。通过一次学习一个构建模块,我们可以很好地理解这个强大的方法。 + +首先,让我们看看我们的数据。之前我们已经看到了一些数据集,完整的表集合如下所示: + +* `clients` : 客户在信用社的的基本信息。每个客户在这个 dataframe 中仅占一行 + +![](https://cdn-images-1.medium.com/max/800/1*FHR7tlD4FuGKt8n5UHUpqw.png) + +* `loans`: 给客户的贷款。每个贷款在这个 dataframe 中仅占一行,但是客户可能会有多个贷款 + +![](https://cdn-images-1.medium.com/max/1000/1*95c7QchQVM-9xUUA4ZB4XQ.png) + +* `payments`: 贷款偿还。每个付款只有一行,但是每笔贷款可以有多笔付款。 + +![](https://cdn-images-1.medium.com/max/1000/1*RbgNzspaiwq74aWU6W5LWQ.png) + +如果我们有一件机器学习任务,例如预测一个客户是否会偿还一个未来的贷款,我们将把所有关于客户的信息合并到一个表格中。这些表格是相互关联的(通过 `client_id` 和 `loan_id` 变量),我们可以使用一系列的转换和聚合操作来手工完成这一过程。然而,我们很快就将看到,我们可以使用 featuretools 来自动化这个过程。 + +### 实体和实体集 + +对于 featuretools 来说,最重要的两个概念是**实体**和**实体集**。一个实体就只是一张表(或者说一个 Pandas 中的 `DataFrame`) 。一个[实体集](https://docs.featuretools.com/loading_data/using_entitysets.html)是一系列表的集合以及这些表格之间的关系。你可以把实体集认为是 Python 中的另外一个数据结构,这个数据结构有自己的方法和参数。 + +我们可以在 featuretools 中利用下面的代码创建出一个空的实体集: + +```python +import featuretools as ft + +# 创建新实体集 +es = ft.EntitySet(id = 'clients') +``` + +现在我们必须添加一些实体。每个实体必须有一个索引,它是一个包含所有唯一元素的列。也就是说,索引中的每个值必须只出现在表中一次。`clients` dataframe 中的索引是 `client_id` ,因为每个客户在这个 dataframe 中只有一行。我们使用以下语法向实体集添加一个已经有索引的实体: + +```python +# 从客户 dataframe 中创建出一个实体 +# 这个 dataframe 已经有一个索引和一个时间索引 +es = es.entity_from_dataframe(entity_id = 'clients', dataframe = clients, + index = 'client_id', time_index = 'joined') +``` + +`loans` datafram 同样有一个唯一的索引,`loan_id` 以及向实体集添加 `loan_id` 的语法和 `clients` 一样。然而,对于 `payments` dataframe 来说,并不存在唯一的索引。当我们向实体集添加实体时,我们需要把参数 `make_index` 设置为 `True`( `make_index = True` ),同时为索引指定好名称。此外,虽然 featuretools 会自动推断实体中的每个列的数据类型,我们也可以将一个列类型的字典传递给参数 `variable_types` 来进行数据类型重写。 + +```python +# 从付款 dataframe 中创建一个实体 +# 该实体还没有一个唯一的索引 +es = es.entity_from_dataframe(entity_id = 'payments', + dataframe = payments, + variable_types = {'missed': ft.variable_types.Categorical}, + make_index = True, + index = 'payment_id', + time_index = 'payment_date') +``` + +对于这个 dataframe 来说,即使 `missed` 是一个整型数据,这不是一个[数值变量](https://socratic.org/questions/what-is-a-numerical-variable-and-what-is-a-categorical-variable),因为它只能接受两个离散值,所以我们告诉 featuretools 将它是为一个分类变量。在向实体集添加了 dataframs 之后,我们将检查其中的任何一个: + +![](https://cdn-images-1.medium.com/max/800/1*DZ44KuggN_4jWKwuhrpCaw.png) + +我们指定的修改可以正确地推断列类型。接下来,我们需要指定实体集中的表是如何进行关联的。 + +#### 表关系 + +考虑两个表之间的**关系**的最佳方式是[父亲与孩子的类比](https://stackoverflow.com/questions/7880921/what-is-par-table-and-child-table-in-database)。这是一对多的关系:每个父亲可以有多个孩子。在表领域中,父亲在每个父表中都有一行,但是子表中可能有多个行对应于同一个父亲的多个孩子。 + +例如,在我们的数据集中,`clients` dataframe 是 `loans` dataframe 的父亲。每个客户在 `clients` 中只有一行,但在 `loans` 中可能有多行。同样, `loans` 是 `payments` 的父亲,因为每笔贷款都有多个支付。父亲通过共享变量与孩子相连。当我们执行聚合时,我们将子表按父变量分组,并计算每个父表的子表的统计信息。 + +要[在 featuretools 中格式化关系](https://docs.featuretools.com/loading_data/using_entitysets.html#add-a-relationship),我们只需指定将两个表链接在一起的变量。 `clients` 和 `loans` 表通过 `loan_id` 变量链接, `loans` 和 `payments` 通过 `loan_id` 联系在一起。创建关系并将其添加到实体集的语法如下所示: + +```python +# 客户与先前贷款的关系 +r_client_previous = ft.Relationship(es['clients']['client_id'], + es['loans']['client_id']) + +# 将关系添加到实体集 +es = es.add_relationship(r_client_previous) + +# 以前的贷款和以前的付款之间的关系 +r_payments = ft.Relationship(es['loans']['loan_id'], + es['payments']['loan_id']) + +# 将关系添加到实体集 +es = es.add_relationship(r_payments) + +es +``` + +![](https://cdn-images-1.medium.com/max/800/1*W_jS8Z4Ym5zAFTdjHki1ig.png) + +实体集现在包含三个实体(或者说是表)和连接这些实体的关系。在添加实体和对关系形式化之后,我们的实体集就准备完成了,我们接下来可以准备创建特征。 + +#### 特征基元 + +在深入了解特性合成之前,我们需要了解[特征基元](https://docs.featuretools.com/automated_feature_engineering/primartives.html)。我们已经知道它们是什么了,但是我们只是用不同的名字称呼它们!这些是我们用来形成新特征的基本操作: + +* 聚合:通过父节点对子节点(一对多)关系完成的操作,并计算子节点的统计信息。一个例子是通过 `client_id` 将 `loan` 表分组,并为每个客户机找到最大的贷款金额。 +* 转换:在单个表上对一个或多个列执行的操作。举个例子,取一个表中两个列之间的差值,或者取列的绝对值。 + +新特性是在 featruetools 中创建的,使用这些特征基元本身或叠加多个特征基元。下面是 featuretools 中的一些特征基元列表(我们还可以[定义自定义特征基元](https://docs.featuretools.com/guides/advanced_custom_basics.html): + +![](https://cdn-images-1.medium.com/max/800/1*_p-HwN54IjLvmSSlkkazUQ.png) + +特征基元 + +这些基元可以自己使用或组合来创建特征。要使用指定的基元,我们使用 `ft.dfs` 函数(代表深度特征合成)。我们传入 `实体集`、`目标实体`(这两个参数是我们想要加入特征的表)以及 `trans_primitives` 参数(用于转换)和 `agg_primitives` 参数(用于聚合): + +```python +# 使用指定的基元创建新特征 +features, feature_names = ft.dfs(entityset = es, target_entity = 'clients', + agg_primitives = ['mean', 'max', 'percent_true', 'last'], + trans_primitives = ['years', 'month', 'subtract', 'divide']) +``` + +以上函数返回结果是每个客户的新特征 dataframe (因为我们把客户定义为`目标实体`)。例如,我们有每个客户加入的月份,这个月份是一个转换特性基元: + +![](https://cdn-images-1.medium.com/max/800/1*gEQkpyTDxXz21_gUPeNlMQ.png) + +我们还有一些聚合基元,比如每个客户的平均支付金额: + +![](https://cdn-images-1.medium.com/max/800/1*7aOkE5N-WCNQHJi1qBcqjQ.png) + +尽管我们只指定了很少一部分的特征基元,但是 featuretools 通过组合和叠加这些基元创建了许多新特征。 + +![](https://cdn-images-1.medium.com/max/800/1*q24CTYC4x7fHj0YFwdusoQ.png) + +完整的 dataframe 有793列新特性! + +#### 深度特征合成 + +现在,我们已经准备好了理解深度特征合成(deep feature synthesis, dfs)的所有部分。事实上,我们已经在前面的函数调用中执行了 dfs 函数!深度特性只是将多个特征基元叠加的特性,而 dfs 是生成这些特性的过程的名称。深度特征的深度是创建该特性所需的特征数量。 + +例如,`MEAN(payments.payment_amount)` 列是一个深度为 1 的特征,因为它是使用单个聚合创建的。深度为 2 的特征是 `LAST(loans(MEAN(payments.payment_amount))` ,这是通过叠加两个聚合而成的: LAST(most recent) 在均值之上。这表示每个客户最近一次贷款的平均支付金额。 + +![](https://cdn-images-1.medium.com/max/800/1*y28-ibs-ZCpCvavVPmmZAw.png) + +我们可以将特征叠加到任何我们想要的深度,但是在实践中,我从来没有超过 2 的深度。在这之后,这些特征就很难解释了,但我鼓励有兴趣的人尝试[“深入研究”](http://knowyourmeme.com/memes/we-needgo-deep)。 + +* * * + +我们不必手工指定特征基元,而是可以让 featuretools 自动为我们选择特性。为此,我们使用相同的 `ft.dfs` 函数调用,但不传递任何特征基元: + +```python +# 执行深度特征合成而不指定特征基元。 +features, feature_names = ft.dfs(entityset=es, target_entity='clients', + max_depth = 2) + +features.head() +``` + +![](https://cdn-images-1.medium.com/max/800/1*tewxbRVcXb_weoy_g6EfkA.png) + +Featuretools 已经为我们构建了许多新的特征供我们使用。虽然这个过程会自动创建新特征,但它不会取代数据科学家,因为我们仍然需要弄清楚如何处理所有这些特征。例如,如果我们的目标是预测客户是否会偿还贷款,我们可以查找与特定结果最相关的特征。此外,如果我们有特殊领域知识,我们可以使用它来选择具有候选特征的特定特征基元或[种子深度特征合成](https://docs.featuretools.com/guides/tuning_dfs.html)。 + +#### 接下来的步骤 + +自动化的特征工程解决了一个问题,但却创造了另一个问题:创造出太多的特征。虽然说在确定好一个模型之前很难说这些特征中哪些是重要的,但很可能并不是所有的特征都与我们想要训练的任务相关。而且,[拥有太多特征](https://pdfs.semanticscholar.org/a83b/ddb34618cc68f1014ca12eef7f537825d104.pdf)可能会让模型的表现下降,因为在训练的过程中一些不太有用的特征会淹没那些更为重要的特征。 + +太多特征的问题被称为[维数的诅咒](https://en.wikipedia.org/wiki/Curse_of_dimensionality#Machine_learning)。随着特征数量的增加(数据的维数增加),模型越来越难以了解特征和目标之间的映射。事实上,模型执行良好所需的数据量(与特性的数量成指数比例)(https://stats.stackexchange.com/a/65380/157316)。 + +可以化解维数诅咒的是[特征削减(也称为特征选择)](https://machinelearningmastery.com/an-introduction-to-feature-selection/):移除不相关特性的过程。这可以采取多种形式:主成分分析(PCA),使用 SelectKBest 类,使用从模型引入的特征,或者使用深度神经网络进行自动编码。当然,[特征削减](https://en.wikipedia.org/wiki/Feature_selection)则是另一篇文章的另一个主题了。现在,我们知道,我们可以使用 featuretools ,以最少的工作量从许多表中创建大量的特性! + +### 结论 + +像机器学习领域很多的话题一样,使用 feautretools 的自动特征工程是一个建立在简单想法之上的复杂概念。使用实体集、实体和关系的概念,feautretools 可以执行深度特性合成来创建新特征。深度特征合成反过来又将特征基元堆叠起来 —— 也就是**聚合**,在表格之间建立起一对多的关系,同时进行**转换**,在单表中对一列或者多列应用,通过这些方法从很多的表格中构建出新的特征出来。 + +请持续关注这篇文章,与此同时,阅读关于这个竞赛的介绍 [this introduction to get started](https://towardsdatascience.com/machine-learning-kaggle-competition-part-one-getting-started-32fb9ff47426)。我希望您现在可以使用自动化特征工程作为数据科学管道中的辅助工具。我们的模型将和我们提供的数据一样好,自动化的特征工程可以帮助使特征创建过程更有效。 + +要获取更多关于特征工具的信息,包括这些工具的高级用法,可以查阅[在线文档](https://docs.featuretools.com/)。要查看特征工具如何在实践中应用,可以参见 [Feature Labs 的工作成果](https://www.featurelabs.com/),这就是开发 featuretools 这个开源库的公司。 + +我一如既往地欢迎各位的反馈和建设性的批评,你们可以在 Twitter [@koehrsen_will](http://twitter.com/koehrsen_will) 上与我进行交流。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/beautility-my-ultimate-iphone-setup.md b/TODO1/beautility-my-ultimate-iphone-setup.md new file mode 100644 index 00000000000..665e5a3ca9e --- /dev/null +++ b/TODO1/beautility-my-ultimate-iphone-setup.md @@ -0,0 +1,119 @@ +> * 原文地址:[Beautility, My Ultimate iPhone Setup](https://betterhumans.coach.me/beautility-my-ultimate-iphone-setup-1b3dd0c588a0) +> * 原文作者:[Jason Stirman](https://betterhumans.coach.me/@stirman?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/beautility-my-ultimate-iphone-setup.md](https://github.com/xitu/gold-miner/blob/master/TODO1/beautility-my-ultimate-iphone-setup.md) +> * 译者:[94haox](https://github.com/94haox) +> * 校对者:[ssshooter](https://github.com/ssshooter) + +# 美观实用性,我的究极 iPhone 设置 + +## 一份让你的 iPhone 实用又美观的指南 + +![](https://cdn-images-1.medium.com/max/1600/1*GP6_qP2JArexoS_DQP-AWQ.jpeg) + +当我理解和意识到从数码世界断连的价值时,我人生中的许多时光已经耗费在我手机的 5.5 寸屏幕上,我已经开始对它上瘾了。尽管我是一个数码控,但是我还是讨厌持续不断的嗡嗡声,并且需要每五分钟查看下手机,所以我决定稍微修改下我的手机,让它为我服务,而不是我为它服务。 + +在开始之前,先看下我的首页。 + +![](https://cdn-images-1.medium.com/max/600/1*wwNWMc756AVs5U731rXCtQ.png) + +这就是我每次解锁时看到的。背景是一个抽象的风景画,我的主屏上大多数是……好吧,是空的,是我故意的。 + +那些耗费我精力的,带着红色角标的应用图标,已经一去不复返了。 + +底部应用托盘上方有两个不易看清的点,这已经是最精简的情况了,因为左边是放满实用小部件的页面,关于这个页面我会在后面提到。但是我故意调整了壁纸图案,让它尽量能够遮住这些点。 + +我的首页让我平静。它感觉上像是为一个具有灵感和创造力的人准备的空白画布。我之前经常设置一些多样的纯色壁纸,但是这张[图](http://www.idownloadblog.com/2016/08/21/wallpapers-of-the-week-minimalist-mountains-continued/)中的某些东西触动了我。 + +在 iOS 将点从黑色变为白色的情况下,你可能没有办法用这张背景图去掩盖指示点。幸运的是,有办法解决这个问题。出现这个问题的原因是某些只有点后面的背景是漆黑一片时才会触发的 Apple magic。如果你重新布置背景,让它只是深灰色,你会发现这个问题就不存在了。 + +![](https://cdn-images-1.medium.com/max/800/1*AtDu4cwBjqdcgBD1HReyUA.png) + +### 第一步:将所有的 APP 放入一个文件夹 + +除了常用的三个,将你所有的应用都放入一个文件夹,可以将这个文件夹命名为 “Apps”。 + +这个文件夹容纳了我的所有的 130 个应用,并且按字母排序了,这是这一步最大的变化。一个文件夹最多容纳 135 个应用,每页 9 个,一共 15 页。凑巧的是我手机上差不多有 130 个应用,因为在将它们移到文件夹的过程中,我删除了 10-15 个应用。我不知道这是多还是少。 + +我用 iTunes 管理它们,我认为它应该比较快,但是我不确定是否是因为 UI 的问题,反而比较慢。此外,按字母排序也不是必须的了,因为我用搜索来做几乎所有的事情。 + +在做出改变之前,我几乎从来不在手机上使用搜索,然而现在我用它查找和启动应用,拉出联系人来通话或者发信息,搜索网站,等等。向下滑,开始输入,然后点击你想要的结果。起初,用搜索来做事,会感到很别扭,但是几天后,我就感觉我有了一个新的超能力,我直到现在还在惊讶于我怎么能这么快找到我想要的。(备注:如果你使用 Slack 并且在手机上搜索它,那么可能表现的不是很好,试试这个[方法](https://t.co/QPXkP5VZKB),它对我有效果。还有[这个](https://medium.com/@aunder)!) + +主屏上剩下的三个应用代表我会**选择**做的事情,我是否没有回复推送通知,比如:与朋友或者家人聊天(Message),看看世界上发生了啥(Twitter),或者通过电子邮件工作(我最喜欢的邮件客户端 [Superhuman](https://superhuman.com/) 的早期私人测试版)。 + +你可以选择和我不同的应用。有很多人有类似观点:Twitter 和短信会打断你的时间。如果你也这样认为,那么你可能应该选择 Podcast 和 Kindle。 + +![](https://cdn-images-1.medium.com/max/800/1*AtDu4cwBjqdcgBD1HReyUA.png) + +### 第二步:禁止推送 + +在我将所有应用放入同一个文件夹后,通知角标就困扰着我,因为它们会出现在文件夹深处的几个页面中,如果我不去浏览那些页面,我根本不能分辨出它们的位置。 + +将所有的通知关闭对我来说并不是一个选择,但是这个设置让我意识到每天我有多少不需要马上关注的通知。我就直说吧:绝大多数。所以在接下来的几天里,每当我收到通知,我就做了下面三件事中的一个... + +1. 如果通知是重要并且是时间敏感的(比如 Slack 的提及,语音信息等等)我会将它们的推送开关打开,并且将它们移到文件夹的第一页。 +2. 如果这个通知是重要的但是时间不敏感的,并且不是经常出现的(比如,设置,Testflight 等等),我离开 APP,并且保持它的推送打开。 +3. 如果这个推送不是很重要,或者时间不敏感,比如(IG 的点赞, Medium 的掌声,等等)我会将它保持在原来的位置,并且在设置中禁止它的所有推送。 + +几天后,我将所有的重要的应用都放在了文件夹的第一页,并且将大多数的推送都禁止了。将我经常使用的应用放在了第一页,比如电话和谷歌地图,等等,尽管我已经用搜索去启动它们了。 + +这是我 Apps 文件夹的第一页。 + +![](https://cdn-images-1.medium.com/max/800/1*ZXu9WEbM2EwI-bQoCGo2bw.png) + +或者,如果你准备好一次性放弃所有通知,你可以直接进入设置,找到通知部分,然后逐个关闭。 + +![](https://cdn-images-1.medium.com/max/600/1*NbgNiVH3FdCRILy4ZF2WFA.png) + +![](https://cdn-images-1.medium.com/max/600/1*HfVbZ8givcxtGKpwsZPozQ.png) + +以下是禁用单个应用的通知之前和之后的样子。 + +![](https://cdn-images-1.medium.com/max/600/1*dif55a98c_vNFcmNIshuvg.png) + +![](https://cdn-images-1.medium.com/max/600/1*vHUMisltVdqReV5_fBxn_A.png) + +![](https://cdn-images-1.medium.com/max/800/1*AtDu4cwBjqdcgBD1HReyUA.png) + +### 第三步:小部件 + +在此之前,我从未用过小部件。“widget” 这个词带来太多 Web 2.0 的回忆。但是现在我开始有点喜欢它们了。小部件屏幕始终只需要轻扫就可以访问我需要检查的或者全天使用的事。按照顺序,我使用了下面这些部件: + +* **Google Calendar**,查看我取消的即将到来的会议 +* **Stocks**,看看我今天又亏了多少钱 +* **Dark Sky**,看看周末是啥天气 +* **Tesla**,我可以通过小部件启动我的车,就像一个真正的硅谷 2B +* **Phone Favorites**,给我的朋友和家人打电话或者发短信 +* **Find Friends**,看看我的朋友和家人在哪里忽视我(开个玩笑,我家里的每个成员都会分享自己的位置,所以我们总能看到每个人的位置。这很有用,并不仅仅能定位对方,也可以找到丢失的手机!) + +小部件页面最好用的地方在于,你可以在不解锁的情况下使用它。是的,这意味着每个人都可以看到我的日程,也可以启动我的车,当然,他们可以找到我的车,并且开着它去参加我不想参加的会议,所以,双赢! + +![](https://cdn-images-1.medium.com/max/600/1*6TQRSPMw8Ov3icJ3uMoxCQ.jpeg) + +![](https://cdn-images-1.medium.com/max/600/1*OZrisFwBJdu2StGiL_IcdA.jpeg) + +![](https://cdn-images-1.medium.com/max/800/1*AtDu4cwBjqdcgBD1HReyUA.png) + +### 结果 + +我使用手机的方式有了**很大**的改变。我仍然可以轻松的访问我想要的或需要做的事情,但是我不再对那些试图吸引我注意力的应用的永无止境的推送心存感激。我经常打开手机想打开某个应用,但是划没两下就被别的应用吸引了,浪费了好多时间。 + +**我再也不会浪费任何时间在手机上,除非我想!** + +由于我几乎不浏览我 15 页的应用文件夹,两周后,我遗忘了很多应用,比如 Reddit,ESPN 和 App Store 等等,对此,我感到非常惊讶。淡出我的视野,淡出我的脑袋,对大多数我的应用来说,这是件好事。我认为这也是一件健康的事情。 + +我同样意识到,iOS 应用在这些年中变得有多么的好。我的旧的习惯都是起始于第一代 iPhone,并且没多少改变。它们阻止我去探索新的功能,像是搜索,部件和 Siri。事实证明库比蒂诺的那群书呆子正在改善 iOS,是我顽固到不能欣赏它了,果然一个人习惯了一件事就很难改变了啊。 + +![](https://cdn-images-1.medium.com/max/800/1*AtDu4cwBjqdcgBD1HReyUA.png) + +我爱我的 iPhone 设置。我感觉并没有失去什么而获得了更多。我对其他点子,提示和技巧保持开放态度。我遗漏了什么吗?还有什么我可以做的更好的地方?我还能怎么改善我的设置呢? + + +特别感谢 [Coach Tony](https://medium.com/@coachtony?source=post_page)。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/better-stats-for-better-decisionsbetter-stats-for-better-decisions.md b/TODO1/better-stats-for-better-decisionsbetter-stats-for-better-decisions.md new file mode 100644 index 00000000000..fedf4bfe175 --- /dev/null +++ b/TODO1/better-stats-for-better-decisionsbetter-stats-for-better-decisions.md @@ -0,0 +1,155 @@ +> * 原文地址:[Better stats for better decisions](https://medium.com/googleplaydev/better-stats-for-better-decisions-3661717b4f2d) +> * 原文作者:[Google Play Apps & Games Team](https://medium.com/@googleplayteam?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/better-stats-for-better-decisionsbetter-stats-for-better-decisions.md](https://github.com/xitu/gold-miner/blob/master/TODO1/better-stats-for-better-decisions.md) +> * 译者: +> * 校对者: + +# Better stats for better decisions + +![](https://cdn-images-1.medium.com/max/2000/0*xQKF5835ivk1ZLVs) + +## The latest Google Play Console and Firebase features to help you analyze your audience + +_Authored by:_ [_Tom Grinsted_](https://medium.com/@tomgrinsted_1649) _(Product Manager for the Google Play Console) and_ [_Tamzin Taylor_](https://medium.com/@tamzint) _(Head of Apps & Games BD, Western Europe at Google Play)_ + +Every day Google Play generates over 3 billion events, such as store searches, listing page views, and app installs. Part of our job is turning all of these events — and all of the amazing data that comes with them — into metrics, insights, and the tools that you can use to make better decisions. + +When you look at what you do in your business on a daily basis, you’ll find that you are making decisions, lots of decisions: about your business, your acquisitions, about your development, and about your product roadmaps. And, good decisions are founded on good insights and good data. + +In this post, we are going to discuss some of the key tools you can use to drive discovery, acquisition, engagement, and monetization. We will also introduce the user lifecycle model that guides the development of benchmarks, insights, and tools to help with decision-making. + +![](https://cdn-images-1.medium.com/max/1600/0*VRurbfuiI5T9EsYU) + +### The user lifecycle + +As with any great journey, you need a place to start: a framework to guide thinking about the benchmarks, insights, and tools you need, as developers, to grow your app and build your business. This framework is the user lifecycle. + +![](https://cdn-images-1.medium.com/max/1600/0*BzrCmXezIGvetz2N) + +The lifecycle starts with discovery and acquisition. This phase is about getting in front of people, showing them that you’ve got a really interesting app or game, and persuading them to install it. + +After the install comes engagement and monetization. You now have people coming back to your product every day: opening it and (hopefully) loving it. They’re also buying your in-app products and subscriptions, so you are getting paid as well. + +It would be great if this were the end of the story, if everybody you acquire continues to use your app. But unfortunately, it’s not. Some people are — for whatever reason — going to uninstall your app or game, and become a lost user. + +That doesn’t have to be the end of the story. Increasingly, the user lifecycle continues by persuading people to come back, to give you a second or a third chance, by telling them about the wonderful new features your app has and convincing them that they’ll want to try it again. + +And it’s within this framework that we and our colleagues at Google Play think about the challenges in front of us. + +![](https://cdn-images-1.medium.com/max/1600/0*V39kFGek9nwQy46O) + +### Tools for discovery and acquisition + +Before we look at some of the tools to help with decision-making, there are three Google Play Store features which are worth mentioning: early access, pre-registration, and Google Play Instant. + +With the **early access program** you don’t wait until your app is in production to start discovering users. When you put your app or game into an open release track in the Google Play Console, you make it available to one of the over 230 million people who have opted in to open testing, with 2.5 million more people signing up each week. + +Then there is **pre-registration**. Using this feature you can put your app or game into the Play Store, but, instead of the install button, people find a Pre-Registration button. Once someone has opted in, you can tell them when your app or game goes into production. + +> Ville Heijari, CMO at Rovio Entertainment Corporation, commented: “pre-registration was instrumental to get our fans excited about the upcoming game, and to notify them of the availability of the game at launch.” + +The third feature is **Google Play Instant**. This is an extension of our Instant Apps, which provides people with a low friction way to experience your app by enabling people to try an app or game without installing it. + +> Hothead Games implemented Google Play Instant, and director of marketing Oliver Birch has noted: “we’ve almost doubled the overall click-through rate on our Store listing. New user acquisitions are up 19%+. [As a result] we are planning to expand instant throughout our portfolio of games.” + +However, you can only manage what you can measure, which is why, to support the launch of Google Play Instant and to help you understand the contribution it’s making to your bottom line, there are new stats in the Play Console. + +You can now see the number of Instant App launches and conversions, the number of times someone who launched your Instant App goes on to download the full version. And, because the stats are in the Play Console, you can slice and dice this information with your other key metrics, such as installs and revenue. The latest addition to these stats is the ability to track which products — browsers, Search, and the Play Store — are driving most of your Instant App success. + +![](https://cdn-images-1.medium.com/max/1600/0*Bk80wPWQh-4yY5rU) + +Now, you probably care about acquiring valuable users. The buyer acquisition report has always done a good job showing you how you convert play store visitors into repeat buyers, and now it tells you about Average Revenue Per User (ARPU) at each of those stages. + +![](https://cdn-images-1.medium.com/max/1600/0*yRFpGCBHKQlGJy_z) + +With this change, you see exactly how much average revenue per user you’re getting from different marketing channels, including organic traffic. Whether you’re running a classic CPM model, cost-per-Install model, or you’re driving value further down the funnel, this information will help you evaluate your strategies and make better decisions. + +And finally, for the discussion about discovery and acquisition, there are new benchmarks. + +![](https://cdn-images-1.medium.com/max/1600/0*ezYvfaP8ns_N4Ag1) + +Benchmarks are great, because they help you focus your investment on the items that will give you the best return. The retained installers benchmarks in the user acquisition funnel, which also include all your organic traffic, let you see exactly where the there are opportunities to improve and where your efforts have paid off. + +> Brandon Ross of strategic partnerships at Intuit Inc. comments: “Organic Benchmarks have helped us gauge conversion performance vs. peers and helped us optimize our discovery and conversion.” + +![](https://cdn-images-1.medium.com/max/1600/0*-hQDAzHzWMpsOWN7) + +### Tools for engagement and monetization + +Let’s broaden our horizons and talk about Firebase tools, in addition to those in the Google Play Console. + +But first, don’t forget the Google Play Console **events timeline**. + +![](https://cdn-images-1.medium.com/max/1600/0*bPh3Rcmatd1DIMp0) + +This new report provides you with contextual information at the bottom of charts in the statistics page, Android vitals dashboard, subscription dashboard, and in other charts on the Play Console. The report shows your metrics in relation to events that affect your app, such as the rolling out of a new release. For example, you can see whether a change + +in average rating correlates with the rollout of a new release, or a price change is increasing or decreasing ARPU. + +When it comes to discovering how people are engaging with your app, the tools offered by Firebase now provide even more help. In particular, **Google Analytics for Firebase** is enabled just by linking the analytics SDK to your app and, of course, signing up for the service. + +Out-of-the-box, Google Analytics for Firebase gives you meaningful metrics about engagement and retention. But, you can also instrument your code to track the activities that matter the most to your app or game. + +![](https://cdn-images-1.medium.com/max/1600/0*wKhlf4ypBdPgQpWk) + +Interpreting all the information you get back from Google Analytics for Firebase can sometimes be a challenge, however, this can now be made a lot easier by using **Firebase Predictions**. + +Firebase Predictions uses analytics data combined with machine-learning and other tools to give you predictions about how people are going to behave in your app. By default, you get predictions about people’s spend and churn. Again, you can also instrument your app to get predictions about the features and behaviors that matter most to you. + +Moving on to the monetization phase, there have been several enhancements to the information about subscriptions. The **subscription dashboard** which launched last year and is used on a regular basis by the majority of top-earning subscription businesses. This is why we’ve continued to enhance the dashboard, including improvements to the retention and cancellation reports. + +Keep a look out for upcoming updates to the **subscription retention and cancellation reports**, which will make it easier to compare cohorts and evaluate key features, such as free trials and account hold. You will also be able to easily track more of the data that is important to you, such as renewals. + +![](https://cdn-images-1.medium.com/max/1600/0*SKEE_M66uRfVJKbg) + +With the new **cohort selector** you can select groups of users, selecting them by SKUs, date, and country. Use this feature to concentrate on a group of subscribers and analyze their performance. For example, you can pick an SKU with a free trial and compare it with an SKU with an introductory price to see which one earns you more revenue. + +When it comes to reducing your subscription churn, updates to the **cancellation report** will help you get more information about why people unsubscribe. + +![](https://cdn-images-1.medium.com/max/1600/0*WJzoyAnXXde_D6Ef) + +When someone cancels a subscription, they are given the opportunity to complete a survey, so that they can explain why they’re canceling. And the results of these surveys are available on the subscription dashboard. + +The dashboard now also reports on win-back features, such as **account hold** and **grace period**. + +![](https://cdn-images-1.medium.com/max/1600/0*-rQoM5mDb6yXpKmm) + +### Win-backs and re-installs + +The Play Console provides reports about uninstalls, for example, daily uninstall metrics or the uninstall events. Also, on the retained installers acquisition report, you can find details such as how long people keep your app before uninstalling. + +We have heard from many developers that they want more information, and we understand why. Later this year, you will see features such as the ability to analyze how many people are uninstalling your app and how many are installing your app again. So, stay tuned for more updates. + +![](https://cdn-images-1.medium.com/max/1600/0*8iSi6BGBiDcP8t7N) + +### App dashboard + +All of this new information creates a challenge. As a developer, you’re already really busy. You’ve got loads of tools, both from Google and others, and many different places to visit to get all the information you need. What you need is an easy way to see what the Play Console has to offer and the information that’s important to you. + +There is a solution: the **app dashboard** in the Google Play Console. + +![](https://cdn-images-1.medium.com/max/1600/0*yd-BCQ5XxJKRmBlh) + +The app dashboard is the landing page you arrive at after selecting an app in the Google Play Console. It starts by providing trending information: such as installs, revenue, ratings, and crashes. In the sections that follow you’ll find groups of complementary data, such as installs and uninstalls, and revenue and RPU. + +The dashboard is customizable; every section can be expanded or collapsed. So, if you’re interested in your revenue you can have that section open, but less interested in preregistration you can collapse that section. The dashboard then remembers your preferences and stays exactly as you leave it. + +### Final word + +User lifecycle has become an important way in which new features and updates to the Google Play Console are driven. As a result, these changes are designed to help you optimize every stage, from Google Play Instant and pre-registration for discovery and acquisition through to new subscription reports, enhanced acquisition reports, the new events timeline, and the uninstall stats. This information — and other details, such as your technical performance — are wrapped together in the app dashboard. + +All of these tools will help drive your success, by better understanding your audience. If you want to learn more [check out more acquisition best practices](https://developer.android.com/distribute/best-practices/grow/user-aquisition), or hear more from us about the launches in our session from I/O 2018 below. + +* YouTube 视频链接:https://youtu.be/oib_gHJA_-0?list=PLWz5rJ2EKKc9Gq6FEnSXClhYkWAStbwlC + +### What do you think? + +Do you have thoughts on analyzing your app acquisition and engagement data? Let us know in the comments below or tweet using **#AskPlayDev** and we’ll reply from [@GooglePlayDev](http://twitter.com/googleplaydev), where we regularly share news and tips on how to be successful on Google Play. + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/boost-your-website-performance-with-phpfastcache.md b/TODO1/boost-your-website-performance-with-phpfastcache.md new file mode 100644 index 00000000000..2f51d3e049d --- /dev/null +++ b/TODO1/boost-your-website-performance-with-phpfastcache.md @@ -0,0 +1,283 @@ +> * 原文地址:[Boost Your Website Performance With PhpFastCache](https://code.tutsplus.com/tutorials/boost-your-website-performance-with-phpfastcache--cms-31031) +> * 原文作者:[Sajal Soni](https://tutsplus.com/authors/sajal-soni?_ga=2.222559131.1693151914.1529137386-2093006918.1525313549) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/boost-your-website-performance-with-phpfastcache.md](https://github.com/xitu/gold-miner/blob/master/TODO1/boost-your-website-performance-with-phpfastcache.md) +> * 译者:[lsvih](https://github.com/lsvih) +> * 校对者:[吃土小2叉](https://github.com/xunge0613) + +# 使用 PhpFastCache 提升网站性能 + +本文将与你一同探索 PhpFastCache 库,来为你的 PHP 应用实现缓存功能。通过缓存功能,能够提升网站的整体性能与页面加载速度。 + +## 什么是 PhpFastCache? + +PhpFastCache 是一个能让你轻松在 PHP 应用中实现缓存功能的库。它的功能强大,且简单易用,提供了一些 API 以无痛实现缓存策略。 + +PhpFastCache 不是一个纯粹的传统文件系统式缓存。它支持各种各样的文件适配器(Files Adapter),可以让你选择 Memcache、Redis、MongoDB、CouchDB 等高性能的后端服务。 + +让我们先总览一遍最流行的适配器: + +* 文件系统 +* Memcache、Redis 和 APC +* CouchDB 和 MongoDB +* Zend Disk Cache 和 Zend Memory Cache + +如果你用的文件适配器不在上面的列表中,也可以简单地开发一个自定义驱动,插入到系统中,同样也能高效地运行。 + +除了基本功能外,PhpFastCache 还提供了事件机制,可以让你对预定义好的事件进行响应。例如,当某个事物从缓存中被删除时,你可以接收到这个事件,并去刷新或删除相关的数据。 + +在下面的章节中,我们将通过一些示例来了解如何安装及配置 PhpFastCache。 + +## 安装与配置 + +在本节中,我们将了解如何安装及配置 PhpFastCache。下面是几种将它集成进项目的方法。 + +如果你嫌麻烦,仅准备下载这个库的 **.zip** 或者 **.tar.gz** 文件,可以去[官方网站](https://www.phpfastcache.com/)直接下载。 + +或者你也可以用 Composer 包的方式来安装它。这种方式更好,因为在之后的维护和升级时会更方便。如果你还没有安装 Composer,需要先去安装它。 + +当你安装好 Composer 之后,可以用以下命令下载 PhpFastCache: + +```bash +$composer require phpfastcache/phpfastcache +``` + +命令完成后,你会得到一个 vendor 目录,在此目录中包括了全部 PhpFastCache 所需的文件。另外,如果你缺失了 PhpFastCache 依赖的库或插件,Composer 会提醒你先去安装依赖。 + +你需要找到 `composer.json` 文件,它类似于下面这样: + +```json +{ + "require": { + "phpfastcache/phpfastcache": "^6.1" + } +} +``` + +无论你通过什么方式来安装的 PhpFastCache,都要在应用中 include **autoload.php** 文件。 + +如果你用的是基于 Composer 的工作流,**autoload.php** 文件会在 **vendor** 目录中。 + +```php +// Include composer autoloader +require '{YOUR_APP_PATH}/vendor/autoload.php'; +``` + +另外,如果你是直接下载的 **.zip** 和 **.tar.gz**,**autoload.php** 的路径会在 **src/autoload.php**。 + +```php +// Include autoloader +require '{YOUR_APP_PATH}/src/autoload.php'; +``` + +只要完成上面的操作,就能开始进行缓存,享受 PhpFastCache 带来的好处了。在下一章节中,我们将以一个简单的示例来介绍如何在你的应用中使用 PhpFastCache。 + +## 示例 + +前面我提到过,PhpFastCache 支持多种文件适配器进行缓存。在本节中,我会以文件系统和 Redis 这两种文件适配器为例进行介绍。 + +### 使用文件适配器进行缓存 + +创建 **file_cache_example.php** 文件并写入下面的代码。在此我假设你使用的是 Composer workflow,因此 **vendor** 目录会与 **file_cache_example.php** 文件同级。如果你是手动安装的 PhpFastCache,需要根据实际情况修改文件结构。 + +```php + __DIR__ . "/cache" +]); + +// Get instance of files cache +$objFilesCache = CacheManager::getInstance('files'); + +$key = "welcome_message"; + +// Try to fetch cached item with "welcome_message" key +$CachedString = $objFilesCache->getItem($key); + +if (is_null($CachedString->get())) +{ + // The cached entry doesn't exist + $numberOfSeconds = 60; + $CachedString->set("This website uses PhpFastCache!")->expiresAfter($numberOfSeconds); + $objFilesCache->save($CachedString); + + echo "Not in cache yet, we set it in cache and try to get it from cache!
"; + echo "The value of welcome_message:" . $CachedString->get(); +} +else +{ + // The cached entry exists + echo "Already in cache!
"; + echo "The value of welcome_message:" . $CachedString->get(); +} +``` + +现在,我们一块一块地来理解代码。首先看到的是将 **autoload.php** 文件引入,然后导入要用到的 namespace: + +```php +// Include composer autoloader +require __DIR__ . '/vendor/autoload.php'; + +use phpFastCache\CacheManager; +``` + +当你使用文件缓存的时候,最好提供一个目录路径来存放缓存系统生成的文件。下面的代码就是做的这件事: + +```php +// Init default configuration for "files" adapter +CacheManager::setDefaultConfig([ + "path" => __DIR__ . "/cache" +]); +``` + +当然,你需要确保 **cache** 目录存在且 web server 有写入权限。 + +接下来,我们将缓存对象实例化,用 **welcome_message** 加载对应的缓存对象。 + +```php +// Get instance of files cache +$objFilesCache = CacheManager::getInstance('files'); + +$key = "welcome_message"; + +// Try to fetch cached item with "welcome_message" key +$CachedString = $objFilesCache->getItem($key); +``` + +如果缓存中不存在此对象,就将它以 60s 过期时间加入缓存,并从缓存中读取与展示它。如果它存在于缓存中,则直接获取: + +```php +if (is_null($CachedString->get())) +{ + // The cached entry doesn't exist + $numberOfSeconds = 60; + $CachedString->set("This website uses PhpFastCache!")->expiresAfter($numberOfSeconds); + $objFilesCache->save($CachedString); + + echo "Not in cache yet, we set it in cache and try to get it from cache!
"; + echo "The value of welcome_message:" . $CachedString->get(); +} +else +{ + // The cached entry exists + echo "Already in cache!
"; + echo "The value of welcome_message:" . $CachedString->get(); +} +``` + +非常容易上手对吧!你可以试着自己去运行一下这个程序来查看结果。 + +当你第一次运行这个程序时,应该会看到以下输出: + +``` +Not in cache yet, we set it in cache and try to get it from cache! +The value of welcome_message: This website uses PhpFastCache! +``` + +之后再运行的时候,输出会是这样: + +``` +Already in cache! +The value of welcome_message: This website uses PhpFastCache! +``` + +现在就能随手实现文件系统缓存了。在下一章节中,我们将模仿这个例子来使用 Redis Adapter 实现缓存。 + +### 使用 Redis Adapter 进行缓存 + +假定你在阅读本节前已经安装好了 Redis 服务,并让它运行在 6379 默认端口上。 + +下面进行配置。创建 **redis_cache_example.php** 文件并写入以下代码: + +```php + '127.0.0.1', + "port" => 6379 +]); + +// Get instance of files cache +$objRedisCache = CacheManager::getInstance('redis'); + +$key = "welcome_message"; + +// Try to fetch cached item with "welcome_message" key +$CachedString = $objRedisCache->getItem($key); + +if (is_null($CachedString->get())) +{ + // The cached entry doesn't exist + $numberOfSeconds = 60; + $CachedString->set("This website uses PhpFastCache!")->expiresAfter($numberOfSeconds); + $objRedisCache->save($CachedString); + + echo "Not in cache yet, we set it in cache and try to get it from cache!
"; + echo "The value of welcome_message:" . $CachedString->get(); +} +else +{ + // The cached entry exists + echo "Already in cache!
"; + echo "The value of welcome_message:" . $CachedString->get(); +} +``` + +如你所见,除了初始化 Redis 适配器的配置一段之外,这个文件与之前基本一样。 + +```php +// Init default configuration for "redis" adapter +CacheManager::setDefaultConfig([ + "host" => '127.0.0.1', + "port" => 6379 +]); +``` + +当然如果你要在非本机运行 Redis 服务,需要根据需求修改 host 与 port 的设置。 + +运行 **redis_cache_example.php** 文件来查看它的工作原理。你也可以在 Redis CLI 中查看输出。 + +``` +127.0.0.1:6379> KEYS * +1) "welcome_message" +``` + +以上就是使用 Redis 适配器的全部内容。你可以去多试试其它不同的适配器和配置! + +## 总结 + +本文简单介绍了 PhpFastCache 这个 PHP 中非常热门的库。在文章前半部分,我们讨论了它的基本知识以及安装和配置。在文章后半部分,我们通过几个例子来详细演示了前面提到的概念。 + +希望你喜欢这篇文章,并将 PhpFastCache 集成到你即将开发的项目中。随时欢迎提问和讨论! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/build-a-state-management-system-with-vanilla-javascript.md b/TODO1/build-a-state-management-system-with-vanilla-javascript.md new file mode 100644 index 00000000000..9ccc96449eb --- /dev/null +++ b/TODO1/build-a-state-management-system-with-vanilla-javascript.md @@ -0,0 +1,578 @@ +> * 原文地址:[Build a state management system with vanilla JavaScript](https://css-tricks.com/build-a-state-management-system-with-vanilla-javascript/) +> * 原文作者:[ANDY BELL](https://css-tricks.com/author/andybell/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/build-a-state-management-system-with-vanilla-javascript.md](https://github.com/xitu/gold-miner/blob/master/TODO1/build-a-state-management-system-with-vanilla-javascript.md) +> * 译者:[Shery](https://github.com/shery) +> * 校对者:[IridescentMia](https://github.com/IridescentMia) [coconilu](https://github.com/coconilu) + +# 使用原生 JavaScript 构建状态管理系统 + +状态管理在软件方面并不新鲜,但在 JavaScript 构建的应用中仍然相对较新。习惯上,我们会直接将状态保持在 DOM 上,甚至将其分配给 window 中的全局对象。但是现在,我们已经有了许多选择,这些库和框架可以帮助我们管理状态。像 Redux,MobX 和 Vuex 这样的库可以轻松管理跨组件状态。它大大提升了应用程序的扩展性,并且它对于状态优先的响应式框架(如 React 或 Vue)非常有用。 + +这些库是如何运作的?我们自己写个状态管理会怎么样?事实证明,它非常简单,并且有机会学习一些非常常见的设计模式,同时了解一些既有用又能用的现代 API。 + +在我们开始之前,请确保你已掌握中级 JavaScript 的知识。你应该了解数据类型,理想情况下,你应该掌握一些更现代的 ES6+ 语法特性。如果没有,[这可以帮到你](https://css-tricks.com/learning-gutenberg-4-modern-javascript-syntax/)。值得注意的是,我并不是说你应该用这个代替 Redux 或 MobX。我们正在一起开发一个小项目来提升技能,嘿,如果你在乎的是 JavaScript 文件规模的大小,那么它确实可以应付一个小型应用。 + +### 入门 + +在我们深入研究代码之前,先看一下我们正在开发什么。它是一个汇总了你今天所取得成就的“完成清单”。它将在不依赖框架的情况下像魔术般更新 UI 中的各种元素。但这并不是真正的魔术。在幕后,我们已经有了一个小小的状态系统,它等待着指令,并以一种可预测的方式维护单一来源的数据。 + +[查看演示](https://vanilla-js-state-management.hankchizljaw.io) + +[查看仓库](http://github.com/hankchizljaw/vanilla-js-state-management) + +很酷,对吗?我们先做一些配置工作。我已经整理了一些模版,以便我们可以让这个教程简洁有趣。你需要做的第一件事情是 [从 GitHub 上克隆它](https://github.com/hankchizljaw/vanilla-js-state-management-boilerplate),或者 [下载并解压它的 ZIP 文件](https://github.com/hankchizljaw/vanilla-js-state-management-boilerplate/archive/master.zip)。 + +当你下载好了模版,你需要在本地 Web 服务器上运行它。我喜欢使用一个名为 [http-server](https://www.npmjs.com/package/http-server) 的包来做这些事情,但你也可以使用你想用的任何东西。当你在本地运行它时,你会看到如下所示: + +![](https://cdn.css-tricks.com/wp-content/uploads/2018/07/state-js-1.png) + +我们模版的初始状态。 + +#### 建立项目结构 + +用你喜欢的文本编辑器打开根目录。这次对我来说,根目录是: + +``` +~/Documents/Projects/vanilla-js-state-management-boilerplate/ +``` + +你应该可以看到类似这样的结构: + +``` +/src +├── .eslintrc +├── .gitignore +├── LICENSE +└── README.md +``` + +### 发布/订阅 + +接下来,打开 `src` 文件夹,然后进入里面的 `js` 文件夹。创建一个名为 `lib` 的新文件夹。在里面,创建一个名为 `pubsub.js` 的新文件。 + +你的 `js` 目录结构应该是这样的: + +``` +/js +├── lib +└── pubsub.js +``` + +因为我们准备要创建一个小型的 [Pub/Sub 模式(发布/订阅模式)](https://msdn.microsoft.com/en-us/magazine/hh201955.aspx),所以请打开 `pubsub.js`。我们正在创建允许应用程序的其他部分订阅具名事件的功能。然后,应用程序的另一部分可以发布这些事件,通常还会携带一些相关的载荷。 + +Pub/Sub 有时很难掌握,那举个例子呢?假设你在一家餐馆工作,你的顾客点了一个前菜和主菜。如果你曾经在厨房工作过,你会知道当侍者清理前菜时,他们让厨师知道哪张桌子的前菜已经清理了。这是该给那张桌子上主菜的提示。在一个大厨房里,有一些厨师可能在准备不同的菜肴。他们都**订阅**了侍者发出的顾客已经吃完前菜的提示,因此他们自己知道要**准备主菜**。所以,你有多个厨师订阅了同一个提示(具名事件),收到提示后做不同的事(回调)。 + +![](https://cdn.css-tricks.com/wp-content/uploads/2018/07/state-management-restaurant.jpg) + +希望这样想有助于理解。让我们继续! + +PubSub 模式遍历所有订阅,并触发其回调,同时传入相关的载荷。这是为你的应用程序创建一个非常优雅的响应式流程的好方法,我们只需几行代码即可完成。 + +将以下内容添加到 `pubsub.js`: + +``` +export default class PubSub { + constructor() { + this.events = {}; + } +} +``` + +我们得到了一个全新的类,我们将 `this.events` 默认设置为空对象。`this.events` 对象将保存我们的具名事件。 + +在 constructor 函数的结束括号之后,添加以下内容: + +``` +subscribe(event, callback) { + + let self = this; + + if(!self.events.hasOwnProperty(event)) { + self.events[event] = []; + } + + return self.events[event].push(callback); +} +``` + +这是我们的订阅方法。你传递一个唯一的字符串 `event` 作为事件名,以及该事件的回调函数。如果我们的 `events` 集合中还没有匹配的事件,那么我们使用一个空数组创建它,这样我们不必在以后对它进行类型检查。然后,我们将回调添加到该集合中。如果它已经存在,就直接将回调添加到该集合中。我们返回事件集合的长度,这对于想要知道存在多少事件的人来说会方便些。 + +现在我们已经有了订阅方法,猜猜看接下来我们要做什么?你知道的:`publish` 方法。在你的订阅方法之后添加以下内容: + +``` +publish(event, data = {}) { + + let self = this; + + if(!self.events.hasOwnProperty(event)) { + return []; + } + + return self.events[event].map(callback => callback(data)); +} +``` + +该方法首先检查我们的事件集合中是否存在传入的事件。如果没有,我们返回一个空数组。没有悬念。如果有事件,我们遍历每个存储的回调并将数据传递给它。如果没有回调(这种情况不应该出现),也没事,因为我们在 `subscribe` 方法中使用空数组创建了该事件。 + +这就是 PubSub 模式。让我们继续下一部分! + +### Store 对象(核心) + +我们现在已经有了 Pub/Sub 模块,我们这个小应用程序的核心模块 Store 类有了它的唯一依赖。现在我们开始完善它。 + +让我们先来概述一下这是做什么的。 + +Store 是我们的核心对象。每当你看到 `@import store from'../lib/store.js` 时,你就会引入我们要编写的对象。它将包含一个 `state` 对象,该对象又包含我们的应用程序状态,一个 `commit` 方法,它将调用我们的 **>mutations**,最后一个 `dispatch` 函数将调用我们的 **actions**。在这个应用和 `Store` 对象的核心之间,将有一个基于代理的系统,它将使用我们的 `PubSub` 模块监视和广播状态变化。 + +首先在 `js` 目录中创建一个名为 `store` 的新目录。在那里,创建一个名为 `store.js` 的新文件。现在你的 `js` 目录应该如下所示: + +``` +/js +└── lib + └── pubsub.js +└──store + └── store.js +``` + +打开 `store.js` 并导入我们的 Pub/Sub 模块。为此,请在文件顶部添加以下内容: + +``` +import PubSub from '../lib/pubsub.js'; +``` + +对于那些经常使用 ES6 的人来说,这将是非常熟悉的。但是,在没有打包工具的情况下运行这种代码可能不太容易被浏览器识别。对于这种方法,已经获得了很多[浏览器支持](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Browser_compatibility)! + +接下来,让我们开始构建我们的对象。在导入文件后,直接将以下内容添加到 `store.js`: + +``` +export default class Store { + constructor(params) { + let self = this; + } +} +``` + +这一切都一目了然,所以让我们添加下一项。我们将为 `state`,`actions` 和 `mutations` 添加默认对象。我们还添加了一个 `status` 属性,我们将用它来确定对象在任意给定时间正在做什么。这是在 `let self = this;` 后面的: + +``` +self.actions = {}; +self.mutations = {}; +self.state = {}; +self.status = 'resting'; +``` + +之后,我们将创建一个新的 `PubSub` 实例,它将作为 `store` 的 `events` 属性的值: + +``` +self.events = new PubSub(); +``` + +接下来,我们将搜索传入的 `params` 对象以查看是否传入了任何 `actions` 或 `mutation`。当实例化 `Store` 对象时,我们可以传入一个数据对象。其中包括 `actions` 和 `mutation` 的集合,它们控制着我们 store 中的数据流。在你添加的最后一行代码后面添加以下代码: + +``` +if(params.hasOwnProperty('actions')) { + self.actions = params.actions; +} + +if(params.hasOwnProperty('mutations')) { + self.mutations = params.mutations; +} +``` + +这就是我们所有的默认设置和几乎所有潜在的参数设置。让我们来看看我们的 `Store` 对象如何跟踪所有的变化。我们将使用 [Proxy(代理)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)来完成此操作。Proxy(代理)所做的工作主要是代理 state 对象。如果我们添加一个 `get` 拦截方法,我们可以在每次询问对象数据时进行监控。与 `set` 拦截方法类似,我们可以密切关注对象所做的更改。这是我们今天感兴趣的主要部分。在你添加的最后一行代码之后添加以下内容,我们将讨论它正在做什么: + +``` +self.state = new Proxy((params.state || {}), { + set: function(state, key, value) { + + state[key] = value; + + console.log(`stateChange: ${key}: ${value}`); + + self.events.publish('stateChange', self.state); + + if(self.status !== 'mutation') { + console.warn(`You should use a mutation to set ${key}`); + } + + self.status = 'resting'; + + return true; + } +}); +``` + +这部分代码说的是我们正在捕获状态对象 `set` 操作。这意味着当 mutation 运行类似于 `state.name ='Foo'` 时,这个拦截器会在它被设置之前捕获它,并为我们提供了一个机会来处理更改甚至完全拒绝它。但在我们的上下文中,我们将会设置变更,然后将其记录到控制台。然后我们用 `PubSub` 模块发布一个 `stateChange` 事件。任何订阅了该事件的回调将被调用。最后,我们检查 `Store` 的状态。如果它当前不是一个 `mutation`,则可能意味着状态是手动更新的。我们在控制台中添加了一点警告,以便给开发人员一些提示。 + +这里做了很多事,但我希望你们开始看到这一切是如何结合在一起的,重要的是,我们如何能够集中维护状态,这要归功于 Proxy(代理)和 Pub/Sub。 + +#### Dispatch 和 commit + +现在我们已经添加了 `Store` 的核心部分,让我们添加两个方法。一个是将调用我们 `actions` 的 `dispatch`,另一个是将调用我们 `mutation` 的 `commit`。让我们从 `dispatch` 开始,在 `store.js` 中的 `constructor` 之后添加这个方法: + +``` +dispatch(actionKey, payload) { + + let self = this; + + if(typeof self.actions[actionKey] !== 'function') { + console.error(`Action "${actionKey} doesn't exist.`); + return false; + } + + console.groupCollapsed(`ACTION: ${actionKey}`); + + self.status = 'action'; + + self.actions[actionKey](self, payload); + + console.groupEnd(); + + return true; +} +``` + +此处的过程是:查找 action,如果存在,则设置状态并调用 action,同时创建日志记录组以使我们的所有日志保持良好和整洁。记录的任何内容(如 mutation 或 Proxy(代理)日志)都将保留在我们定义的组中。如果未设置任何 action,它将记录错误并返回 false。这非常简单,而且 `commit` 方法更加直截了当。 + +在 `dispatch` 方法之后添加: + +``` +commit(mutationKey, payload) { + let self = this; + + if(typeof self.mutations[mutationKey] !== 'function') { + console.log(`Mutation "${mutationKey}" doesn't exist`); + return false; + } + + self.status = 'mutation'; + + let newState = self.mutations[mutationKey](self.state, payload); + + self.state = Object.assign(self.state, newState); + + return true; +} +``` + +这种方法非常相似,但无论如何我们都要自己了解这个过程。如果可以找到 mutation,我们运行它并从其返回值获得新状态。然后我们将新状态与现有状态合并,以创建我们最新版本的 state。 + +添加了这些方法后,我们的 `Store` 对象基本完成了。如果你愿意,你现在可以模块化这个应用程序,因为我们已经添加了我们需要的大部分功能。你还可以添加一些测试来检查所有内容是否按预期运行。我不会就这样结束这篇文章的。让我们实现我们打算去做的事情,并继续完善我们的小应用程序! + +### 创建基础组件 + +为了与我们的 store 通信,我们有三个主要区域,根据存储在其中的内容进行独立更新。我们将列出已提交的项目,这些项目的可视化计数,以及另一个在视觉上隐藏着为屏幕阅读器提供更准确的信息。这些都做着不同的事情,但他们都会从共享的东西中受益,以控制他们的本地状态。我们要做一个基础组件类! + +首先,让我们创建一个文件。在 `lib` 目录中,继续创建一个名为 `component.js` 的文件。我的文件路径是: + +``` +~/Documents/Projects/vanilla-js-state-management-boilerplate/src/js/lib/component.js +``` + +创建该文件后,打开它并添加以下内容: + +``` +import Store from '../store/store.js'; + +export default class Component { + constructor(props = {}) { + let self = this; + + this.render = this.render || function() {}; + + if(props.store instanceof Store) { + props.store.events.subscribe('stateChange', () => self.render()); + } + + if(props.hasOwnProperty('element')) { + this.element = props.element; + } + } +} +``` + +让我们来谈谈这段代码吧。首先,我们要导入 `Store` **类**。这不是因为我们想要它的实例,而是更多用于检查 `constructor` 中的一个属性。说到这个,在 `constructor` 中我们要看看我们是否有一个 render 方法。如果这个 `Component` 类是另一个类的父类,那么它可能会为 `render` 设置自己的方法。如果没有设置方法,我们创建一个空方法来防止事情出错。 + +在此之后,我们像上面提到的那样对 `Store` 类进行检查。我们这样做是为了确保 `store` 属性是一个 `Store` 类实例,这样我们就可以放心地使用它的方法和属性。说到这一点,我们订阅了全局 `stateChange` 事件,所以我们的对象可以做到**响应式**。每次状态改变时都会调用 `render` 函数。 + +这就是我们需要为该类所要写的全部内容。它将被用作其他组件类 `extend` 的父类。让我们一起来吧! + +### 创建我们的组件 + +就像我之前说过的那样,我们要完成三个组件,它们都通过 `extend` 关键字,继承了基类 `Component`。让我们从最大的一个组件开始开始:项目清单! + +在你的 `js` 目录中,创建一个名为 `components` 的新文件夹,然后创建一个名为 `list.js` 的新文件。我的文件路径是: + +``` +~/Documents/Projects/vanilla-js-state-management-boilerplate/src/js/components/list.js +``` + +打开该文件并将这整段代码粘贴到其中: + +``` +import Component from '../lib/component.js'; +import store from '../store/index.js'; + +export default class List extends Component { + + constructor() { + super({ + store, + element: document.querySelector('.js-items') + }); + } + + render() { + let self = this; + + if(store.state.items.length === 0) { + self.element.innerHTML = `

You've done nothing yet 😢

`; + return; + } + + self.element.innerHTML = ` +
    + ${store.state.items.map(item => { + return ` +
  • ${item}
  • + ` + }).join('')} +
+ `; + + self.element.querySelectorAll('button').forEach((button, index) => { + button.addEventListener('click', () => { + store.dispatch('clearItem', { index }); + }); + }); + } +}; +``` + +我希望有了前面教程,这段代码的含义对你来说是不言而喻的,但是无论如何我们还是要说下它。我们先将 `Store` 实例传递给我们继承的 `Component` 父类。就是我们刚刚编写的 `Component` 类。 + +在那之后,我们声明了 render 方法,每次触发 Pub/Sub 的 `stateChange` 事件时都会调用的这个 render 方法。在这个 `render` 方法中,我们会生成一个项目列表,或者是没有项目时的通知。你还会注意到每个按钮都附有一个事件,并且它们会触发一个 action,然后由我们的 store 处理 action。这个 action 还不存在,但我们很快就会添加它。 + +接下来,再创建两个文件。虽然是两个新组件,但它们很小 —— 所以我们只是向其中粘贴一些代码即可,然后继续完成其他部分。 + +首先,在你的 `component` 目录中创建 `count.js`,并将以下内容粘贴进去: + +``` +import Component from '../lib/component.js'; +import store from '../store/index.js'; + +export default class Count extends Component { + constructor() { + super({ + store, + element: document.querySelector('.js-count') + }); + } + + render() { + let suffix = store.state.items.length !== 1 ? 's' : ''; + let emoji = store.state.items.length > 0 ? '🙌' : '😢'; + + this.element.innerHTML = ` + You've done + ${store.state.items.length} + thing${suffix} today ${emoji} + `; + } +} +``` + +看起来跟 list 组件很相似吧?这里没有任何我们尚未涉及的内容,所以让我们添加另一个文件。在相同的 `components` 目录中添加 `status.js` 文件并将以下内容粘贴进去: + +``` +import Component from '../lib/component.js'; +import store from '../store/index.js'; + +export default class Status extends Component { + constructor() { + super({ + store, + element: document.querySelector('.js-status') + }); + } + + render() { + let self = this; + let suffix = store.state.items.length !== 1 ? 's' : ''; + + self.element.innerHTML = `${store.state.items.length} item${suffix}`; + } +} +``` + +与之前一样,这里没有任何我们尚未涉及的内容,但是你可以看到有一个基类 `Component` 是多么方便,对吧?这是[面向对象编程](https://en.wikipedia.org/wiki/Object-oriented_programming)众多优点之一,也是本教程的大部分内容的基础。 + +最后,让我们来检查一下 `js` 目录是否正确。这是我们目前所处位置的结构: + +``` +/src +├── js +│ ├── components +│ │ ├── count.js +│ │ ├── list.js +│ │ └── status.js +│ ├──lib +│ │ ├──component.js +│ │ └──pubsub.js +└───── store + └──store.js + └──main.js +``` + +### 让我们把它连起来 + +现在我们已经有了前端组件和主要的 `Store`,我们所要做的就是将它全部连接起来。 + +我们已经让 store 系统和组件通过数据来渲染和交互。现在让我们把应用程序的两个独立部分联系起来,让整个项目一起协同工作。我们需要添加一个初始状态,一些 `actions` 和一些 `mutations`。在 `store` 目录中,添加一个名为 `state.js` 的新文件。我的文件路径是: + +``` +~/Documents/Projects/vanilla-js-state-management-boilerplate/src/js/store/state.js +``` + +打开该文件并添加以下内容: + +``` +export default { + items: [ + 'I made this', + 'Another thing' + ] +}; +``` + +这段代码的含义不言而喻。我们正在添加一组默认项目,以便在第一次加载时,我们的小程序将是可完全交互的。让我们继续添加一些 `actions`。在你的 `store` 目录中,创建一个名为 `actions.js` 的新文件,并将以下内容添加进去: + +``` +export default { + addItem(context, payload) { + context.commit('addItem', payload); + }, + clearItem(context, payload) { + context.commit('clearItem', payload); + } +}; +``` + +这个应用程序中的 actions 非常少。本质上,每个 action 都会将 payload(关联数据)传递给 mutation,而 mutation 又将数据提交到 store。正如我们之前所了解的那样,`context` 是 `Store` 类的实例,`payload` 是触发 action 时传入的。说到 mutations,让我们来添加一些。在同一目录中添加一个名为 `mutation.js` 的新文件。打开它并添加以下内容: + +``` +export default { + addItem(state, payload) { + state.items.push(payload); + + return state; + }, + clearItem(state, payload) { + state.items.splice(payload.index, 1); + + return state; + } +}; +``` + +与 actions 一样,这些 mutations 很少。在我看来,你的 mutations 应该保持简单,因为他们有一个工作:改变 store 的 state。因此,这些例子就像它们最初一样简单。任何适当的逻辑都应该发生在你的 `actions` 中。正如你在这个系统中看到的那样,我们返回新版本的 state,以便 `Store` 的 `commit` 方法可以发挥其魔力并更新所有内容。有了这个,store 系统的主要模块就位。让我们通过 index 文件将它们结合到一起。 + +在同一目录中,创建一个名为 `index.js` 的新文件。打开它并添加以下内容: + +``` +import actions from './actions.js'; +import mutations from './mutations.js'; +import state from './state.js'; +import Store from './store.js'; + +export default new Store({ + actions, + mutations, + state +}); +``` + +这个文件把我们所有的 store 模块导入进来,并将它们结合在一起作为一个简洁的 `Store` 实例。任务完成! + +### 最后一块拼图 + +我们需要做的最后一件事是添加本教程开头的 _waaaay_ 页面 `index.html` 中包含的 `main.js` 文件。一旦我们整理好了这些,我们就能够启动浏览器并享受我们的辛勤工作!在 `js` 目录的根目录下创建一个名为 `main.js` 的新文件。这是我的文件路径: + +``` +~/Documents/Projects/vanilla-js-state-management-boilerplate/src/js/main.js +``` + +打开它并添加以下内容: + +``` +import store from './store/index.js'; + +import Count from './components/count.js'; +import List from './components/list.js'; +import Status from './components/status.js'; + +const formElement = document.querySelector('.js-form'); +const inputElement = document.querySelector('#new-item-field'); +``` + +到目前为止,我们做的就是获取我们需要的依赖项。我们拿到了 `Store`,我们的前端组件和几个 DOM 元素。我们紧接着添加以下代码使表单可以直接交互: + +``` +formElement.addEventListener('submit', evt => { + evt.preventDefault(); + + let value = inputElement.value.trim(); + + if(value.length) { + store.dispatch('addItem', value); + inputElement.value = ''; + inputElement.focus(); + } +}); +``` + +我们在这里做的是向表单添加一个事件监听器并阻止它提交。然后我们获取文本框的值并修剪它两端的空格。我们这样做是因为我们想检查下一步是否会有任何内容传递给 store。最后,如果有内容,我们将使用该内容作为 payload(关联数据)触发我们的 `addItem` action,并且让我们闪亮的新 `store` 为我们处理它。 + +让我们在 `main.js` 中再添加一些代码。在事件监听器下,添加以下内容: + +``` +const countInstance = new Count(); +const listInstance = new List(); +const statusInstance = new Status(); + +countInstance.render(); +listInstance.render(); +statusInstance.render(); +``` + +我们在这里所做的就是创建组件的新实例并调用它们的每个 `render` 方法,以便我们在页面上获得初始状态。 + +随着最后的添加,我们完成了! + +打开你的浏览器,刷新并沉浸在新状态管理应用程序的荣耀中。来吧,添加一些类似于**“完成这个令人敬畏的教程”**的条目。很整洁,是吧? + +### 下一步 + +你可以借助我们一起整合的小系统来做很多事情。以下是你自己进一步探索的一些想法: + +* 你可以实现一些本地存储,以保持状态,即使当你重新加载时 +* 你可以分离出前端模块,只为你的项目提供一个小型状态系统 +* 你可以继续开发此应用程序的前端模块并使其看起来很棒。(我真的很想看到你的作品,所以请分享!) +* 你可以使用一些远程数据,甚至可以使用 API +* 你可以整理你所学到的关于 `Proxy` 和 Pub/Sub 模式的知识,并进一步学习那些可用于不同工作的技能 + +### 总结 + +感谢你同我一起学习状态系统是如何工作的。那些大型的主流状态管理库比我们所做的事情要复杂,智能得多 —— 但了解这些系统如何运作并揭开它们背后的神秘面纱仍然有用。无论如何,了解 JavaScript 在不使用框架下的强大能力也很有用。 + +如果你想要这个小系统的完成版本,请查看这个 [GitHub 仓库](https://github.com/hankchizljaw/vanilla-js-state-management)。你还可以在[此处](https://vanilla-js-state-management.hankchizljaw.io)查看演示。 + +如果你在此基础上进一步开发,我很乐意看到它,所以如果你这样做,请在[推特](https://twitter.com/hankchizljaw)上跟我联络或发表在下面的评论中! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/build-secure-rest-api-with-node.md b/TODO1/build-secure-rest-api-with-node.md new file mode 100644 index 00000000000..999955d042a --- /dev/null +++ b/TODO1/build-secure-rest-api-with-node.md @@ -0,0 +1,732 @@ +> * 原文地址:[Build a Simple REST API with Node and OAuth 2.0](https://developer.okta.com/blog/2018/08/21/build-secure-rest-api-with-node) +> * 原文作者:[Braden Kelley](https://developer.okta.com/blog/2018/08/21/build-secure-rest-api-with-node) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/build-secure-rest-api-with-node.md](https://github.com/xitu/gold-miner/blob/master/TODO1/build-secure-rest-api-with-node.md) +> * 译者:[Starriers](https://github.com/Starriers) +> * 校对者:[jianboy](https://github.com/jianboy) + +# 使用 Node 和 OAuth 2.0 构建一个简单的 REST API + +JavaScript 在 web 是随处可见 —— 几乎每个 web 页面都会或多或少的包含一些 JavaScript,即使没有 JavaScript,你的浏览器也可能存在某种扩展类型向页面中注入一些 JavaScript 代码。直到如今,这些事情都不可避免。 + +JavaScript 也可以用于浏览器的上下文之外的任何事情,从托管 web 服务器来控制 RC 汽车或运行成熟的操作系统。有时你想要几个一组无论是在本地网络还是在互联网上都可以相互交流的服务器。 + +今天,我会向你演示如何使用 Node.js 创建一个 REST API,并使用 OAuth 2.0 保证它的安全性,以此来阻止不必要的请求。REST API 在 web 上比比皆是,但如果没有合适的工具,就需要大量的样板代码。我会向你演示如何使用可以轻松实现客户端认证流的令人惊讶的一些工具,它可以在没有用户上下文的情况下将两台机器安全地连接。 + +## 构建你的 Node 服务器 + +使用 [Express JavaScript 库](https://expressjs.com/) 在 Node 中设置 web 服务器非常简单。创建一个包含服务器的新文件夹。 + +``` +$ mkdir rest-api +``` + +Node 使用 `package.json` 来管理依赖并定义你的项目。我们使用 `npm init` 来新建该文件。该命令会在帮助你初始化项目时询问你一些问题。现在你可以使用[标准 JS](https://standardjs.com/) 来强制执行编码标准,并将其用作测试。 + +``` +$ cd rest-api + +$ npm init +这个实用工具将引导你创建 package.json 文件。 +它只涵盖最常见的项目,并试图猜测合理的默认值。 + +请参阅 `npm help json` 来获取关于这些字段的确切文档以及它们所做的事情。 + +使用 `npm install ` 命令来安装一个 npm 依赖,并将其保存在 package.json 文件中。 + +Press ^C at any time to quit. +package name: (rest-api) +version: (1.0.0) +description: A parts catalog +entry point: (index.js) +test command: standard +git repository: +keywords: +author: +license: (ISC) +About to write to /Users/Braden/code/rest-api/package.json: + +{ + "name": "rest-api", + "version": "1.0.0", + "description": "A parts catalog", + "main": "index.js", + "scripts": { + "test": "standard" + }, + "author": "", + "license": "ISC" +} + + +Is this OK? (yes) +``` + +默认的入口端点是 `index.js`,因此,你应当创建一个 `index.js` 文件。下面的代码将为你提供一个出了默认监听 3000 端口以外什么也不做的非常基本的服务器。 + +**index.js** + +``` +const express = require('express') +const bodyParser = require('body-parser') +const { promisify } = require('util') + +const app = express() +app.use(bodyParser.json()) + +const startServer = async () => { + const port = process.env.SERVER_PORT || 3000 + await promisify(app.listen).bind(app)(port) + console.log(`Listening on port ${port}`) +} + +startServer() +``` + +`util` 的 `promisify` 函数允许你接受一个期望回调的函数,然后返回一个 promise,这是处理异步代码的新标准。这还允许我们使用相对较新的 `async`/`await` 语法,并使我们的代码看起来漂亮得多。 + +为了让它运行,你需要下载你在文件头部导入的 `require` 依赖。使用 `npm install` 来安装他们。这会将一些元数据自动保存到你的 `package.json` 文件中,并将它们下载到本地的 `node_modules` 文件中。 + +**注意**:你永远都不应该向源代码提交 `node_modules`,因为对于源代码的管理,往往会很快就变得臃肿,而 `package-lock.json` 文件将跟踪你使用的确切版本,如果你将其安装在另一台计算机上,它们将得到相同的代码。 + +``` +$ npm install express@4.16.3 util@0.11.0 +``` + +对于一些快速 linting,请安装 `standard` 作为 dev 依赖,然后运行它以确保你的代码达到标准。 + +``` +$ npm install --save-dev standard@11.0.1 +$ npm test + +> rest-api@1.0.0 test /Users/bmk/code/okta/apps/rest-api +> standard +``` + +如果一切顺利,在 `> standard` 线后,你不应该看到任何输出。如果有错误,可能如下所示: + +``` +$ npm test + +> rest-api@1.0.0 test /Users/bmk/code/okta/apps/rest-api +> standard + +standard: Use JavaScript Standard Style (https://standardjs.com) +standard: Run `standard --fix` to automatically fix some problems. + /Users/Braden/code/rest-api/index.js:3:7: Expected consistent spacing + /Users/Braden/code/rest-api/index.js:3:18: Unexpected trailing comma. + /Users/Braden/code/rest-api/index.js:3:18: A space is required after ','. + /Users/Braden/code/rest-api/index.js:3:38: Extra semicolon. +npm ERR! Test failed. See above for more details. +``` + +现在,你的代码已经准备好了,也下载了所需的依赖,你可以用 `node .` 运行服务器了。(`.` 表示查看前目录,然后检查你的 `package.json` 文件,以确定该目录中使用的主文件是 `index.js`): + +``` +$ node . + +Listening on port 3000 +``` + +为了测试它的工作状态,你可以使用 `curl` 命令。没有终结点,所以 Express 将返回一个错误: + +``` +$ curl localhost:3000 -i +HTTP/1.1 404 Not Found +X-Powered-By: Express +Content-Security-Policy: default-src 'self' +X-Content-Type-Options: nosniff +Content-Type: text/html; charset=utf-8 +Content-Length: 139 +Date: Thu, 16 Aug 2018 01:34:53 GMT +Connection: keep-alive + + + + + +Error + + +
Cannot GET /
+ + +``` + +即使它报错,那也是非常好的情况。你还没有设置任何端点,因此 Express 唯一要返回的是 404 错误。如果你的服务器根本没有运行,你将得到如下错误: + +``` +$ curl localhost:3000 -i +curl: (7) Failed to connect to localhost port 3000: Connection refused +``` + +## 用 Express、Sequelize 和 Epilogue 构建你的 REST API + +你现在有了一台正在运行的 Express 服务器,你可以添加一个 REST API。这实际上比你想象中的简单的多。我看过的最简单的方法是使用 [Sequelize](http://docs.sequelizejs.com/) 来定义数据库字段,[Epilogue](https://github.com/dchester/epilogue) 创建带有接近零样板的 REST API 端点。 + +你需要将这些依赖加入到你的项目中。Sequelize 也需要知道如何与数据库进行通信。现在,使用 SQLite 是因为它能帮助我们快速地启动和运行。 + +``` +npm install sequelize@4.38.0 epilogue@0.7.1 sqlite3@4.0.2 +``` + +新建一个包含以下代码的文件 `database.js`。我会在下面详细解释每一部分。 + +**database.js** + +``` +const Sequelize = require('sequelize') +const epilogue = require('epilogue') + +const database = new Sequelize({ + dialect: 'sqlite', + storage: './test.sqlite', + operatorsAliases: false +}) + +const Part = database.define('parts', { + partNumber: Sequelize.STRING, + modelNumber: Sequelize.STRING, + name: Sequelize.STRING, + description: Sequelize.TEXT +}) + +const initializeDatabase = async (app) => { + epilogue.initialize({ app, sequelize: database }) + + epilogue.resource({ + model: Part, + endpoints: ['/parts', '/parts/:id'] + }) + + await database.sync() +} + +module.exports = initializeDatabase +``` + +你现在只需要将那些文件导入主应用程序并运行初始化函数即可。在你的 `index.js` 文件中添加以下内容。 + +**index.js** + +``` +@@ -2,10 +2,14 @@ const express = require('express') + const bodyParser = require('body-parser') + const { promisify } = require('util') + ++const initializeDatabase = require('./database') ++ + const app = express() + app.use(bodyParser.json()) + + const startServer = async () => { ++ await initializeDatabase(app) ++ + const port = process.env.SERVER_PORT || 3000 + await promisify(app.listen).bind(app)(port) + console.log(`Listening on port ${port}`) +``` + +你现在可以测试语法错误,如果一切 看上去都正常了,就可以启动应用程序了: + +``` +$ npm test && node . + +> rest-api@1.0.0 test /Users/bmk/code/okta/apps/rest-api +> standard + +Executing (default): CREATE TABLE IF NOT EXISTS `parts` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `partNumber` VARCHAR(255), `modelNu +mber` VARCHAR(255), `name` VARCHAR(255), `description` TEXT, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL); +Executing (default): PRAGMA INDEX_LIST(`parts`) +Listening on port 3000 +``` + +在另一个终端,你可以测试它是否实际上已经在工作了(我使用 [json CLI](https://github.com/trentm/json) 来格式化 JSON 响应,使用 `npm install --global json` 进行全局安装): + +``` +$ curl localhost:3000/parts +[] + +$ curl localhost:3000/parts -X POST -d '{ + "partNumber": "abc-123", + "modelNumber": "xyz-789", + "name": "Alphabet Soup", + "description": "Soup with letters and numbers in it" +}' -H 'content-type: application/json' -s0 | json +{ + "id": 1, + "partNumber": "abc-123", + "modelNumber": "xyz-789", + "name": "Alphabet Soup", + "description": "Soup with letters and numbers in it", + "updatedAt": "2018-08-16T02:22:09.446Z", + "createdAt": "2018-08-16T02:22:09.446Z" +} + +$ curl localhost:3000/parts -s0 | json +[ + { + "id": 1, + "partNumber": "abc-123", + "modelNumber": "xyz-789", + "name": "Alphabet Soup", + "description": "Soup with letters and numbers in it", + "createdAt": "2018-08-16T02:22:09.446Z", + "updatedAt": "2018-08-16T02:22:09.446Z" + } +] +``` + +### 这发生了什么? + +如果你之前一直是按照我们的步骤来的,那么是可以跳过这部分的,因为这部分是我之前承诺过要给出的解释。 + +`Sequelize` 函数创建了一个数据库。这是配置详细信息的地方,例如要使用 SQL 语句。现在,使用 SQLite 来快速启动和运行。 + +``` +const database = new Sequelize({ + dialect: 'sqlite', + storage: './test.sqlite', + operatorsAliases: false +}) +``` + +一旦创建了数据库,你就可以为每个表使用 `database.define` 来定义它的表。用一些有用的字段创建叫做 `parts` 的表来进跟踪 parts。默认情况下,Sequelize 还会在创建和更新时自动创建和更新 `id`、`createdAt` 和 `updatedAt` 字段。 + +``` +const Part = database.define('parts', { + partNumber: Sequelize.STRING, + modelNumber: Sequelize.STRING, + name: Sequelize.STRING, + description: Sequelize.TEXT +}) +``` + +结语为了添加端点会请求获取你的 Express `app` 访问权限。但 `app` 被定义在另一个文件中。处理这个问题的一个方法就是导出一个函数,该函数接受应用程序并对其进行一些操作。当我们在另一个文件中导入这个脚本时,你可以像运行 `initializeDatabase(app)` 一样运行它。 + +结语需要同时使用 `app` 和 `database` 来初始化。软化定义你需要使用的 REST 端点。`resource` 函数会包括 `GET`、`POST`、`PUT` 和 `DELETE` 动词的端点,这些动词大多数是自动化的。 + +想真正创建数据库,你需要运行返回一个 promise 的 `database.sync()`。在你启动服务器之前,你需要等待它执行结束。 + +`module.exports` 意思是 `initializeDatabase` 函数可以从另一个函数中导入。 + +``` +const initializeDatabase = async (app) => { + epilogue.initialize({ app, sequelize: database }) + + epilogue.resource({ + model: Part, + endpoints: ['/parts', '/parts/:id'] + }) + + await database.sync() +} + +module.exports = initializeDatabase +``` + +## 用 OAuth 2.0 保护你的 Node + Express REST API + +现在你已经启动并运行了 REST API,想象你希望一个特定的应用程序从远程位置使用这个 API。如果你把它按照原样存放在互联网上,那么任何人都可以随意添加、修改或删除部位。 + +为了避免这个情况,你可以使用 OAuth 2.0 客户端凭证。这是一种不需要上下文就可以让两个服务器相互通信的方式。这两个服务器必须事先同意使用第三方授权服务器。假设有两个服务器,A 和 B,以及一个接权服务器。服务器 A 托管 REST API,服务器 B 希望访问该 API。 + +* 服务器 B 向授权服务器发送一个私钥来证明自己的身份,并申请一个临时令牌。 +* 服务器 B 会向往常一样使用 REST API,但会将令牌与请求一起发送。 +* 服务器 A 向授权服务器请求一些元数据,这些元数据可用于验证令牌。 +* 服务器 A 验证服务器 B 的请求。 + * 如果它是有效的,一个成功的响应将被发送并且服务器 B 正常运行。 + * 如果令牌无效,则将发送错误消息,并且不会泄露敏感信息。 + +### 创建授权服务器 + +这就是 OKta 发挥作用的地方。OKta 可以扮演允许你保护数据的服务器的角色。你可能会问自己“为什么是 OKta?”好的,对于构建 REST 应用程序来说,它非常的酷,但是构建一个**安全**的应用程序会更酷。为了实现这个目标,你需要添加身份验证,以便用户在查看/修改组之前必须要登录才可以。在 Okta 中,我们的目标是确保[身份管理](https://developer.okta.com/product/user-management/)比你过去使用的要更容易、更安全、更可扩展。Okta 是一种云服务,它允许开发者创建、编辑和安全存储用户账户以及用户账户数据,并将它们与一个或多个应用程序连接。我们的 API 允许你: + +* [验证](https://developer.okta.com/product/authentication/)并[授权](https://developer.okta.com/product/authorization/)你的用户 +* 存储关于用户的数据 +* 允许基于密码和[社交的登录方式](https://developer.okta.com/authentication-guide/social-login/) +* 使用[多个代理身份验证](https://developer.okta.com/use_cases/mfa/)来保护你的应用程序 +* 还有更多!查看我们的[产品文档](https://developer.okta.com/documentation/) + +如果你还没有账户,[可以注册一个永久免费的开发者账号](https://developer.okta.com/signup/),让我们开始吧! + +创建账户后,登录到开发者控制台,导航到 **API**,然后导航到 **Authorization Servers** 选项卡。单击 `default` 服务器的链接。 + +从这个 **Settings** 选项卡中,复制 `Issuer` 字段。你需要把它保存在你的 Node 应用程序可以阅读的地方。在你的项目中,创建一个名为 `.env` 的文件,如下所示: + +**.env** + +``` +ISSUER=https://{yourOktaDomain}/oauth2/default +``` + +`ISSUER` 的值应该是设置页面的 `Issuer URI` 字段的值。 + +![高亮 issuer URL。](https://developer.okta.com/assets/blog/rest-api-node/issuer-afa0da4b4f632196092a4da8f243f3bec37615602dc5b62e8e34546fd1018333.png) + +**注意**:一般规则是,你不应该将 `.env` 文件存储在源代码管理中。这允许多个项目同时使用相同的源代码,而不是需要单独的分支。它确保你的安全信息不会被公开(特备是如果你将代码作为开源发布时)。 + +接下来,导航到 **Scopes** 菜单。单击 **Add Scope** 按钮,然后为 REST API 创建一个作用域。你需要起一个名称(例如,`parts_manager`),如果你愿意,还可以给它一个描述。 + +![添加范围的截图](https://developer.okta.com/assets/blog/rest-api-node/adding-scope-f3ecb3b4eec06d616a130400245843c0de2dd52a54b2fdcff7449a10a2ce75ed.png) + +你还应该将作用域添加到你的 `.env` 文件中,以便你的代码可以访问到它。 + +**.env** + +``` +ISSUER=https://{yourOktaDomain}/oauth2/default +SCOPE=parts_manager +``` + +你现在需要创建一个客户端。导航到 **Applications**,然后单击 **Add Application**。选择 **Service**,然后单击 **Next**。输入服务名(例如 `Parts Manager`)然后单击 **Done**。 + +这将带你到具体的客户凭据的页面。这些是服务器 B(将消耗 REST API 的服务器)为了进行身份验证所需要的凭据。在本例中,客户端和服务器代码位于同一存储库中,因此继续将这些数据添加到你的 `.env` 文件中。请确保将 `{yourClientId}` 和 `{yourClientSecret}` 替换为此页面中的值。 + +``` +CLIENT_ID={yourClientId} +CLIENT_SECRET={yourClientSecret} +``` + +### 创建中间件来验证 Express 中的令牌 + +在 Express 中,你可以添加将在每个端点之前运行的中间件。然后可以添加元数据,设置报头,记录一些信息,甚至可以提前取消请求并发送错误消息。在本例中,你需要创建一些中间件来验证客户端发送的令牌。如果令牌是有效的,它会被送达至 REST API 并返回适当的响应。如果令牌无效,它将使用错误消息进行响应,这样只有授权的机器才能访问。 + +想要验证令牌,你尅使用 OKta 的中间件。你还需要一个叫做 [dotenv](https://github.com/motdotla/dotenv) 的工具来加载环境变量: + +``` +npm install dotenv@6.0.0 @okta/jwt-verifier@0.0.12 +``` + +现在创建一个叫做 `auth.js` 的文件,它可以导出中间件: + +**auth.js** + +``` +const OktaJwtVerifier = require('@okta/jwt-verifier') + +const oktaJwtVerifier = new OktaJwtVerifier({ issuer: process.env.ISSUER }) + +module.exports = async (req, res, next) => { + try { + const { authorization } = req.headers + if (!authorization) throw new Error('You must send an Authorization header') + + const [authType, token] = authorization.trim().split(' ') + if (authType !== 'Bearer') throw new Error('Expected a Bearer token') + + const { claims } = await oktaJwtVerifier.verifyAccessToken(token) + if (!claims.scp.includes(process.env.SCOPE)) { + throw new Error('Could not verify the proper scope') + } + next() + } catch (error) { + next(error.message) + } +} +``` + +函数首先会检查 `authorization` 报头是否在该请求中,然后抛出一个错误。如果存在,它应该类似于 `Bearer {token}`,其中 `{token}` 是一个 [JWT](https://www.jsonwebtoken.io/) 字符串。如果报头不是以 `Bearer` 开头,则会引发另一个错误。然后我们将令牌发送到 [Okta 的 JWT 验证器](https://github.com/okta/okta-oidc-js/tree/master/packages/jwt-verifier) 来验证令牌。如果令牌无效,JWT 验证器将抛出一个错误,否则,它将返回一个带有一些信息的对象。然后你可以验证要求是否包含你期望的范围。 + +如果一切顺利,它就会以无参的形式调用 `next()` 函数,这将告诉 Express 可以转到链中的下一个函数(另一个中间件或最终端点)。如果将字符串传递给 `next` 函数,那么 Express 将其视为将被传回客户端的错误,并且不会在链中继续。 + +你仍然需要导入这个函数并将其作为中间件添加到应用程序中。你还需要在索引文件的顶部加载 `dotenv`,以确保 `.env` 中的环境变量加载到你的应用程序中。对 `index.js` 作以下更改: + +**index.js** + +``` +@@ -1,11 +1,14 @@ ++require('dotenv').config() + const express = require('express') + const bodyParser = require('body-parser') + const { promisify } = require('util') + ++const authMiddleware = require('./auth') + const initializeDatabase = require('./database') + + const app = express() + app.use(bodyParser.json()) ++app.use(authMiddleware) + + const startServer = async () => { + await initializeDatabase(app) +``` + +如果测试请求是否被正确阻止,请尝试再次运行... + +``` +$ npm test && node . +``` + +然后在另一个终端上运行一些 `curl` 命令来进行检测: + +1. 授权报头是否在请求之中 + +``` +$ curl localhost:3000/parts + + + + +Error + + +
You must send an Authorization header
+ + +``` + +2. 在授权请求的报头中是否有 Bearer 令牌 + +``` +$ curl localhost:3000/parts -H 'Authorization: Basic asdf:1234' + + + + +Error + + +
Expected a Bearer token
+ + +``` + +3. Bearer 令牌是否有效 + +``` +$ curl localhost:3000/parts -H 'Authorization: Bearer asdf' + + + + +Error + + +
Jwt cannot be parsed
+ + +``` + +### 在 Node 中创建一个测试客户端 + +你现在已经禁止没有有效令牌的人访问应用程序,但如何获取令牌并使用它呢?我会向你演示如何在 Node 中编写一个简单的客户端,这也将帮助你测试一个有效令牌的工作。 + +``` +npm install btoa@1.2.1 request-promise@4.2.2 +``` + +**client.js** + +``` +require('dotenv').config() +const request = require('request-promise') +const btoa = require('btoa') + +const { ISSUER, CLIENT_ID, CLIENT_SECRET, SCOPE } = process.env + +const [,, uri, method, body] = process.argv +if (!uri) { + console.log('Usage: node client {url} [{method}] [{jsonData}]') + process.exit(1) +} + +const sendAPIRequest = async () => { + const token = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`) + try { + const auth = await request({ + uri: `${ISSUER}/v1/token`, + json: true, + method: 'POST', + headers: { + authorization: `Basic ${token}` + }, + form: { + grant_type: 'client_credentials', + scope: SCOPE + } + }) + + const response = await request({ + uri, + method, + body, + headers: { + authorization: `${auth.token_type} ${auth.access_token}` + } + }) + + console.log(response) + } catch (error) { + console.log(`Error: ${error.message}`) + } +} + +sendAPIRequest() +``` + +这里,代码将 `.env` 中的变量加载到环境中,然后从 Node 中获取它们。节点将环境变量存储在 `process.env`(`process` 是一个具有大量有用变量和函数的全局变量)。 + +``` +require('dotenv').config() +// ... +const { ISSUER, CLIENT_ID, CLIENT_SECRET, SCOPE } = process.env +// ... +``` + +接下来,由于这将从命令行运行,所以你可以再次使用 `process` 来获取与 `process.argv` 一起传入的参数。这将为你提供一个数组,其中包含传入的所有参数。前两个逗号前面没有任何变量名称,因为在本例中前两个不重要;他们只是通向 `node` 的路径,以及脚本的名称(`client` 或者 `client.js`)。 + +URL 是必须的,它包括端点,但是方法和 JSON 数据是可选的。默认的方法是 `GET`,因此如果你只是获取数据,就可以忽略它。在这种情况下,你也不需要任何有效负载。如果参数看起来不正确,那么这将使用错误消息和退出代码 `1` 退出程序,这表示错误。 + +``` +const [,, uri, method, body] = process.argv +if (!uri) { + console.log('Usage: node client {url} [{method}] [{jsonData}]') + process.exit(1) +} +``` + +Node 当前不允许在主线程中使用 `await`,因此要使用更干净的 `async`/`await` 语法,你必须创建一个函数,然后调用它。 + +如果在任何一个 `await` 函数中发生错误,那么屏幕上就会打印出 `try`/`catch`。 + +``` +const sendAPIRequest = async () => { + try { + // ... + } catch (error) { + console.error(`Error: ${error.message}`) + } +} + +sendAPIRequest() +``` + +这是客户端向授权服务器发送令牌请求的地方。对于授权服务器本身的授权,你需要使用 Basic Auth。当你得到一个内置弹出要求用户名和密码时,基本认证是浏览器使用相同的行为。假设你的用户名是 `AzureDiamond` 并且你的密码是 `hunter2`。你的浏览器就会将它们用(`:`)连起来,然后 base64(这就是 `btoa` 函数的功能)对它们进行编码,来获取 `QXp1cmVEaWFtb25kOmh1bnRlcjI=`。然后它发送一个授权报头 `Basic QXp1cmVEaWFtb25kOmh1bnRlcjI=`。服务器可以用 base64 对令牌进行解码,以获取用户名和密码。 + +基础授权本身并不安全,因为它很容易被破解,这就是为什么 `https` 对于中间人攻击很重要。在这里,客户端 ID 和客户端密钥分别是用户名和密码。这也是为什么必须保证 `CLIENT_ID` 和 `CLIENT_SECRET` 是私有的原因。 + +对于 OAuth 2.0,你还需要制定授权类型,在本例中为 `client_credentials`,因为你计划在两台机器之间进行对话。你还要指定作用域。还有需要其他选项需要在这里进行添加,但这是我们这个示例所需要的所有选项。 + +``` +const token = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`) +const auth = await request({ + uri: `${ISSUER}/v1/token`, + json: true, + method: 'POST', + headers: { + authorization: `Basic ${token}` + }, + form: { + grant_type: 'client_credentials', + scope: SCOPE + } +}) +``` + +一旦你通过验证,你就会得到一个访问令牌,你可以将其发送到 REST API,改令牌应该类似于 `Bearer eyJra...HboUg`(实际令牌要长的多 —— 可能在 800 个字符左右)。令牌包含 REST API 需要的所有信息,可以验证令牌的失效时间以及各种其他信息,像请求作用域、发出者和用于令牌的客户端 ID。 + +来自 REST API 的响应就会打印在屏幕上。 + +``` +const response = await request({ + uri, + method, + body, + headers: { + authorization: `${auth.token_type} ${auth.access_token}` + } +}) + +console.log(response) +``` + +现在就尝试一下。同样,用 `npm test && node .` 启动你的应用程序,然后尝试一些像下面的命令: + +``` +$ node client http://localhost:3000/parts | json +[ + { + "id": 1, + "partNumber": "abc-123", + "modelNumber": "xyz-789", + "name": "Alphabet Soup", + "description": "Soup with letters and numbers in it", + "createdAt": "2018-08-16T02:22:09.446Z", + "updatedAt": "2018-08-16T02:22:09.446Z" + } +] + +$ node client http://localhost:3000/parts post '{ + "partNumber": "ban-bd", + "modelNumber": 1, + "name": "Banana Bread", + "description": "Bread made from bananas" +}' | json +{ + "id": 2, + "partNumber": "ban-bd", + "modelNumber": "1", + "name": "Banana Bread", + "description": "Bread made from bananas", + "updatedAt": "2018-08-17T00:23:23.341Z", + "createdAt": "2018-08-17T00:23:23.341Z" +} + +$ node client http://localhost:3000/parts | json +[ + { + "id": 1, + "partNumber": "abc-123", + "modelNumber": "xyz-789", + "name": "Alphabet Soup", + "description": "Soup with letters and numbers in it", + "createdAt": "2018-08-16T02:22:09.446Z", + "updatedAt": "2018-08-16T02:22:09.446Z" + }, + { + "id": 2, + "partNumber": "ban-bd", + "modelNumber": "1", + "name": "Banana Bread", + "description": "Bread made from bananas", + "createdAt": "2018-08-17T00:23:23.341Z", + "updatedAt": "2018-08-17T00:23:23.341Z" + } +] + +$ node client http://localhost:3000/parts/1 delete | json +{} + +$ node client http://localhost:3000/parts | json +[ + { + "id": 2, + "partNumber": "ban-bd", + "modelNumber": "1", + "name": "Banana Bread", + "description": "Bread made from bananas", + "createdAt": "2018-08-17T00:23:23.341Z", + "updatedAt": "2018-08-17T00:23:23.341Z" + } +] +``` + +## 了解更多关于 Okta 的 Node 和 OAuth 2.0 客户端凭据的更多信息 + +希望你已经看到了在 Node 中创建 REST API 并对未经授权的用户进行安全保护是多么容易的。现在,你已经有机会创建自己的示例项目了,请查看有关 Node、OAuth 2.0 和 Okta 的其他一些优秀资源。你还可以浏览 Okta 开发者博客,以获取其他优秀文章。 + +* [客户端证书流的实现](https://developer.okta.com/authentication-guide/implementing-authentication/client-creds) +* [验证访问令牌](https://developer.okta.com/authentication-guide/tokens/validating-access-tokens) +* [自定义授权服务器](https://developer.okta.com/authentication-guide/implementing-authentication/set-up-authz-server) +* [教程:用 Node.js 构建一个基本的 CRUD App](/blog/2018/06/28/tutorial-build-a-basic-crud-app-with-node) +* [使用 OAuth 2.0 客户端证书保护 Node API](/blog/2018/06/06/node-api-oauth-client-credentials) + +和以前一样,你可以在下面的评论中或在 Twitter [@oktadev](https://twitter.com/OktaDev) 给我们提供反馈或者提问,我们期待收到你的来信! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/build-time-travel-debugging-in-redux-from-scratch.md b/TODO1/build-time-travel-debugging-in-redux-from-scratch.md index f4350e3a1e6..42eba0d46fc 100644 --- a/TODO1/build-time-travel-debugging-in-redux-from-scratch.md +++ b/TODO1/build-time-travel-debugging-in-redux-from-scratch.md @@ -2,44 +2,44 @@ > * 原文作者:[Trey Huffine](https://levelup.gitconnected.com/@treyhuffine?source=post_header_lockup) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/build-time-travel-debugging-in-redux-from-scratch.md](https://github.com/xitu/gold-miner/blob/master/TODO1/build-time-travel-debugging-in-redux-from-scratch.md) -> * 译者: -> * 校对者: +> * 译者:[老教授](https://juejin.im/user/58ff449a61ff4b00667a745c/posts) +> * 校对者:[DM.Zhong](https://github.com/zhongdeming428)、[da](https://github.com/sunhaokk) -# Build time travel debugging in Redux from scratch +# 从零开始,在 Redux 中构建时间旅行式调试 ![](https://cdn-images-1.medium.com/max/2000/1*WoJnfVeCnfT2cGzlsgAjJw.jpeg) -In this tutorial we will go step-by-step through building time travel debugging from scratch. We will begin by covering the core principles of Redux and how they enable building such a powerful feature. We will then build the Redux core library and time travel debugging in pure JavaScript and connect it to a simple HTML application, without using any React. +在这篇教程中,我们将从零开始一步步构建时间旅行式调试。我们会先介绍 Redux 的核心特性,及这些特性怎么让时间旅行式调试这种强大功能成为可能。接着我们会用原生 JavaScript 来构建一个 Redux 核心库以及实现时间旅行式调试,并将它应用到一个简单的不含 React 的 HTML 应用里面去。 ![](https://cdn-images-1.medium.com/max/800/1*cRt1u7SCt376nCWu_Ae7mA.gif) -### The fundamentals of time traveling with Redux +### 使用 Redux 进行时间旅行的基础 -Time travel debugging refers to the ability step forward and backward through the state of you application, empowering the developer understand exactly what is happening at any point in the app’s lifecycle. +时间旅行式调试指的是让你的应用程序状态(state)向前走和向后退的能力,这就使得开发者可以确切地了解应用在其生命周期的每一点上发生了什么。 -Redux is an extension of the flux pattern which enforces unidirectional data flow. In addition, Redux adds 3 principles to the flux philosophy. +Redux 是使用单向数据流的 flux 模式的一个拓展。Redux 在 flux 的思路体系上额外加入了 3 条准则。 -1. **A single source of truth for the state**. The entire state of the application is stored in a single JavaScript object. -2. **State is read-only**. This is the concept of immutability. The state is never altered, but every actions generates a brand new state object, replacing the old one. -3. **Changes are made with pure functions**. This means that any time a new state is generated, it happens without triggering any other side effects. +1. **唯一的状态来源**。应用程序的全部状态都存储在一个 JavaScript 对象里面。 +2. **状态是只读的**。这就是不可变的概念了。状态是永远不能被修改的,不过每一个动作(action)都会产生一个全新的状态对象,然后用它来替换掉旧的(状态对象)。 +3. **由纯函数来产生修改**。这意味着任何时候生成一个新的状态,都不会产生其他的副作用。 -By utilizing the fundamental concept that the state of a Redux application is generated in this linear and predictable timeline, time travel debugging extends the concept by storing a copy of the state tree generated by every action that is triggered. +Redux 应用程序的状态是在一个线性的可预测的时间线上生成的,借助这个概念,时间旅行式调试进一步拓展,将触发的每一个动作(action)所产生的状态树都做了一个副本保存下来。 -The UI can be thought of as a pure function of the Redux state. Time traveling allows us to set the state of the application to a desired value and generate the exact UI that will be created under those conditions. This kind of visibility and transparency around an application is an incredibly powerful tool for a developer to fully understand what’s happening inside their application and significantly reduce the effort required to debug it. +UI 界面可以被当做是 Redux 状态的一个纯函数(译者注:纯函数意味着输入确定的 Redux 状态肯定产生确定的 UI 界面)。时间旅行允许我们给应用程序状态设置一个特定的值,从而在那些条件下产生一个准确的 UI 界面。这种应用程序的可视化和透明化的能力对开发者来说是极为有用的,可以帮他们透彻地理解应用程序里面发生了什么,并显著地减少调试程序耗费的精力。 -### Building a simple application with Redux and time travel debugging +### 使用 Redux 和时间旅行式调试搭建一个简单的应用 -We are going to build a simple HTML application that generates a random background color when clicked and uses Redux to store the RGB values. We will also create a time travel extension that will allow us to replay every state of the application and visually see the background changing at each step. +我们接下来会搭建一个简单的 HTML 应用,它会在每次点击的时候产生一个随机的背景颜色并使用 Redux 将颜色的 RGB 值存下来。我们还会建立一个时间旅行拓展,它可以帮我们回放应用程序的每一个状态,并让我们可视化地看到每一步的背景色变化。 -#### Building the Redux core library +#### 搭建 Redux 核心库 -If you are interested in building time travel debugging, I am also going to assume you have familiarity with Redux. If you are new to Redux or need a refresher on the store and reducer, refer to [this article](https://levelup.gitconnected.com/learn-redux-by-building-redux-from-scratch-dcbcbd31b0d0?source=user_profile---------8----------------) before continuing for a detailed explanation. In this tutorial, you will walk through building `createStore` and reducers step-by-step. +如果你对搭建时间旅行式调试感兴趣,那我将默认你已熟练掌握 Redux。如果你是 Redux 的新手或者需要对 store 和 reducer 这些概念重温一下,那建议在接下去的详细讲解前阅读下[这篇文章](https://levelup.gitconnected.com/learn-redux-by-building-redux-from-scratch-dcbcbd31b0d0?source=user_profile---------8----------------)。在这部分教程中,你将一步步搭建 `createStore` 和 reducer。 -The Redux core library is the `createStore` function. The Redux store manages the state object which represents the entire state of the application and exposes the methods necessary to read from and update the state. Invoking `createStore` initializes the state and returns an object containing the `getState()`, `subscribe()`, and `dispatch()` methods. +Redux 核心库就是这个 `createStore` 函数。Redux 的 store 管理着状态对象(这个状态对象代表着应用的全局状态)并暴露出必要的接口供读取和更新状态。调用 `createStore` 会初始化状态并返回一个包含 `getState()`、`subscribe()` 和 `dispatch()` 等方法的对象。 -The `createStore` function takes one required argument which is the reducer function and can optionally receives an `initialState` argument. The entirety of `createStore` is the following (crazy how small it is, right?): +`createStore` 函数接受一个 reducer 函数作为必要参数,并接受一个 `initialState` 作为可选参数。整个 `createStore` 如下文所示(不可思议的简短,对吧?): -``` +```js const createStore = (reducer, initialState) => { const store = {}; store.state = initialState; @@ -60,13 +60,13 @@ const createStore = (reducer, initialState) => { }; ``` -#### Implementing time travel debugging +#### 实现时间旅行式调试 -We will implement time traveling by subscribing a new listener to the Redux store and also extending the store’s functionality. Each change in state will be added to an array, giving us a synchronous representation of every change in state of the application. We will print the list of states to the DOM for clarity. +我们将对 Redux 的 store 实现一个新的监听,并拓展 store 的能力,从而实现时间旅行功能。状态的每一次改变都将被添加到一个数组里,对于应用状态的每次改变都会给我们一个同步表现。为了清晰起见,我们将把这个状态的列表打印到 DOM 节点里面。 -First we initialize the timeline and index of the active state in the history (line 1–2). We also create a `saveTimeline` function that adds the current state to the timeline array, prints the state to the DOM, and increments the index of the given state tree that is being rendered by the app. To ensure we capture every state change, we subscribe the `saveTimeline` function as a listener to the Redux store. +首先,我们会对时间轴和历史中处于活动态的状态索引进行初始化(第1、2行)。我们还会创建一个 `savetimeline` 函数,它会将当前状态添加到时间轴数组,将状态打印到 DOM 节点上,并对程序用来渲染的指定状态树的索引进行递增。为了确保我们捕捉到每一次状态变化,我们将 `saveTimeline` 函数作为 Redux store 的一个监听者实施订阅。 -``` +```js const timeline = []; let activeItem = 0; @@ -81,25 +81,25 @@ const saveTimeline = () => { store.subscribe(saveTimeline); ``` -Next add a new function to the store — `setState`. This will allow us to inject any state into the Redux store. It will be called as we time travel between states using buttons on the DOM that we will create in the next section. The following is the implementation of the `setState` function on the store. +接着我们在 store 中添加一个新的函数 —— `setState`。它允许我们向 Redux 的 store 中注入任何状态值。当我们要通过一个 DOM 上的按钮(下一节创建)在不同的状态间进行穿梭时,这个函数就会被调用。下面就是 store 里面这个 `setState` 函数的实现: -``` -// FOR DEBUGGING PURPOSES ONLY +```js +// 仅供调试 store.setState = desiredState => { store.state = desiredState; -// Assume the debugger is injected last. We don't want to update -// the saved states as we are debugging, so we slice it off. + // 假设调试器(译者注:上文的 saveTimeline )是最后被注入(到 store.listeners )的, + // 我们并不想在调试时更新 timeline 中已存储的状态,所以我们把它排除掉。 const applicationListeners = store.listeners.slice(0, -1); applicationListeners.forEach(listener => listener()); }; ``` -> Keep in mind that this is for educational purposes only. You should extend the Redux store or set the state in this manner. +> 谨记,我们这么做仅为了方便学习。仅在此场景下你可以直接拓展 Redux 的 store 或直接设置状态。 -When we build the full application in the following section, we will also build out the DOM. For now, all you need to know is that there will be a “previous” and a “next” button to enable time traveling. These buttons will update the active index for the timeline state being shown, allowing us to easily move forward and backward through the state changes. The follow shows how we register the event listeners to navigate the timeline: +当我们在下一节建立好整个应用,我们也就同时把 DOM 节点给建立好了。现在,你只要知道将会有一个“向前走”和一个“向后走”的按钮来用来进行时间旅行。这两个按钮将更新状态时间轴的活动索引(从而改变用来展示的活动状态),允许我们在不同的状态变化间轻松地前进和后退。下面代码将告诉你怎么注册事件监听来穿梭时间轴: -``` +```js const previous = document.getElementById('previous'); const next = document.getElementById('next'); @@ -129,9 +129,9 @@ next.addEventListener('click', e => { }); ``` -Putting this all together yields the following code to create time travel debugging. +综合起来,可以得到下面的代码来创建时间旅行式调试。 -``` +```js const timeline = []; let activeItem = 0; @@ -145,18 +145,18 @@ const saveTimeline = () => { store.subscribe(saveTimeline); -// FOR DEBUGGING & EDUCATIONAL PURPOSES ONLY -// The store should not be extended like this. +// 仅供调试 +// store 不应该像这样进行拓展。 store.setState = desiredState => { store.state = desiredState; - // Assume the debugger is injected last. We don't want to update - // the saved states as we're debugging. + // 假设调试器(译者注:上文的 saveTimeline )是最后被注入(到 store.listeners )的, + // 我们并不想在调试时更新 timeline 中已存储的状态,所以我们把它排除掉。 const applicationListeners = store.listeners.slice(0, -1); applicationListeners.forEach(listener => listener()); }; -// This assumes there are previous/next buttons with the give IDs to control the time travel +// 这里假定通过这两个 ID 就可以拿到向前走、向后走两个按钮,用以控制时间旅行 const previous = document.getElementById('previous'); const next = document.getElementById('next'); @@ -185,13 +185,13 @@ next.addEventListener('click', e => { }); ``` -#### Building an application with time travel debugging +#### 搭建一个含时间旅行式调试的应用程序 -We will now create a visual representation to understand time travel debugging. We add an event listener to the document body that will generate three random numbers between 0–255 and save those as `r`, `g`, and `b` in the Redux store. A function subscribed to the store that will update the background color as well as display the current RGB value on the screen. In addition, our time travel debugger will be subscribed to the state changes and add each change to the timeline. +现在我们开始创建视觉上的效果来理解时间旅行式调试。我们在 document 的 body 上添加事件监听,事件触发时会创建三个 0-255 间的随机数,并分别作为 RGB 值存到 Redux 的 store 里面。将会有一个 store 的订阅函数来更新页面背景色并把当前 RGB 色值展现在屏幕上。另外,我们的时间旅行式调试会对状态变化进行订阅,把每个变化记录到时间轴里。 -We begin by initializing the HTML document as follows. +我们以下面的代码来初始化 HTML 文档并开始我们的工作。 -``` +```html @@ -222,17 +222,17 @@ We begin by initializing the HTML document as follows. } ``` -Notice that we also create a `
` for the debugger. There are buttons to navigate the different states and a node to list each update in the state. +注意我们还创建了一个 `
` 用于调试。里面有用于不同状态间穿梭的按钮,还有一个用来列举状态每一次变化的 DOM 节点。 -Inside the script, we begin by referencing the DOM nodes and our `createStore`. +在 JavaScript 里,我们先引用 DOM 节点,引入 `createStore`。 -``` +```js const textNode = document.getElementById('background'); const timelineNode = document.getElementById('timeline'); @@ -257,9 +257,9 @@ const createStore = (reducer, initialState) => { }; ``` -Next, we create the reducer to track the RGB values and initialize the store. The initial state will be a white background. +接着,我们创建一个用于跟踪 RGB 色值变化的 reducer 并初始化 store。初始状态将是白色背景。 -``` +```js const getInitialState = () => { return { r: 255, @@ -284,9 +284,9 @@ const reducer = (state = getInitialState(), action) => { const store = createStore(reducer); ``` -Now we can subscribe a function to the store that will set the background color and add the text RGB value to the DOM. This is cause any update to the state to be represented our UI. +现在我们对 store 添加订阅函数,用于设置页面背景色并把文本形式的 RGB 色值添加到 DOM 节点上。这会让状态的每一个变化都可以在我们的 UI 界面上表现出来。 -``` +```js const setBackgroundColor = () => { const state = store.getState(); const { r, g, b } = state; @@ -299,14 +299,14 @@ const setBackgroundColor = () => { store.subscribe(setBackgroundColor); ``` -Finally we add a function to generate a random number 0–255 and an `onClick` event listener that will dispatch a new RGB value to the store. +最后我们添加一个函数用于生成 0-255 间的随机数,并加上一个 `onClick` 的事件监听,事件触发时将新的 RGB 值派发(dispatch)到 store 里面。 -``` +```js const generateRandomColor = () => { return Math.floor(Math.random() * 255); }; -// A simple event to dispatch changes +// 一个简单的事件用于派发数据变化 document.addEventListener('click', () => { console.log('----- Previous state', store.getState()); store.dispatch({ @@ -321,13 +321,13 @@ document.addEventListener('click', () => { }); ``` -This is the entirety of our application logic. We add the time travel code from the previous section below and at the bottom of the script tag call `store.dispatch({})` to generate the initial state. +这就是我们所有的程序逻辑了。我们将上一节的时间旅行代码添加到后面,并在 script 标签的最后面调用 `store.dispatch({})` 来产生初始状态。 ![](https://cdn-images-1.medium.com/max/800/1*i3L6QvShxky5wkcloijdqA.gif) -Below is the full working code of the application. +下面是应用程序的完整代码。 -``` +```html @@ -405,7 +405,7 @@ Below is the full working code of the application. const generateRandomColor = () => { return Math.floor(Math.random() * 255); }; - // A simple event to dispatch changes + // 一个简单的事件用于派发数据变化 document.addEventListener('click', () => { console.log('----- Previous state', store.getState()); store.dispatch({ @@ -428,11 +428,11 @@ Below is the full working code of the application. activeItem = timeline.length - 1; }; store.subscribe(saveTimeline); - // FOR DEBUGGING PURPOSES ONLY + // 仅供调试 store.setState = desiredState => { store.state = desiredState; - // Assume the debugger is injected last. We don't want to update - // the saved states as we're debugging. + // 假设调试器(译者注:上文的 saveTimeline )是最后被注入(到 store.listeners )的, + // 我们并不想在调试时更新 timeline 中已存储的状态,所以我们把它排除掉。 const applicationListeners = store.listeners.slice(0, -1); applicationListeners.forEach(listener => listener()); }; @@ -456,19 +456,19 @@ Below is the full working code of the application. const desiredState = timeline[index]; store.setState(desiredState); }); - store.dispatch({}); // Sets the inital state + store.dispatch({}); // 设置初始状态 ``` -### Wrap up +### 总结 -Our educational implementation of time travel debugging depicts the core principles of Redux. We can effortlessly track the ongoing state of our application, making it easy to debug and understand fully what is happening. +我们的时间旅行式调试的教学示范实现向我们展现了 Redux 的核心准则。我们可以毫不费劲地跟踪我们应用程序中不断变化的状态,便于调试和了解正在发生的事情。 * * * -If you found this article helpful, please tap the ❤. [Follow me](https://medium.com/@treyhuffine) for more articles on blockchain, React, Node.js, JavaScript, and open source software! You can also find me on [Twitter](https://twitter.com/treyhuffine) or [gitconnected](https://gitconnected.com/treyhuffine). +如果你觉得本文有用,请点击 ❤。[订阅我](https://medium.com/@treyhuffine) 可以看到更多关于 blockchain、React、Node.js、JavaScript 和开源软件的文章!你也可以在 [Twitter](https://twitter.com/treyhuffine) 或 [gitconnected](https://gitconnected.com/treyhuffine) 上找到我。 --- diff --git a/TODO1/building-a-cross-platform-mobile-team.md b/TODO1/building-a-cross-platform-mobile-team.md new file mode 100644 index 00000000000..65c811a2732 --- /dev/null +++ b/TODO1/building-a-cross-platform-mobile-team.md @@ -0,0 +1,88 @@ +> * 原文地址:[Building a Cross-Platform Mobile Team: Adapting mobile for a world with React Native](https://medium.com/airbnb-engineering/building-a-cross-platform-mobile-team-3e1837b40a88) +> * 原文作者:[Gabriel Peal](https://medium.com/@gpeal?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/building-a-cross-platform-mobile-team.md](https://github.com/xitu/gold-miner/blob/master/TODO1/building-a-cross-platform-mobile-team.md) +> * 译者:[ALVINYEH](https://github.com/ALVINYEH) +> * 校对者:[DateBro](https://github.com/DateBro) + +# 建立一个跨平台的移动端团队 + +## 使用 React Native 适应移动端 + +![](https://cdn-images-1.medium.com/max/2000/1*3WNSZyXGOWKJyPT9r8VY8Q.jpeg) + +**这是[系列博客文章](https://juejin.im/post/5b2c924ff265da59a401f050)中的第三篇,本文将会概述使用 React Native 的经验,以及 Airbnb 移动端接下来要做的事情。** + +除了 React Native 数不清的技术优势和缺陷之外,我们还了解到 React Native 对于一个工程组织意味着什么。采用它比在现有平台添加新库或模式要复杂得多。这同时也带来了一些组织上的挑战。与通常可以有效解决的技术挑战不同,组织上的挑战更难以发现,纠正和恢复。不过值得庆幸的是,我们的手机文化是健康的,但在考虑 React Native 时有很多事情需要注意。 + +#### React Native 呈现出两极化 + +根据我们的经验,工程师们在刚接触 React Native 时候,提出了截然不同的观点,从赞扬它将会成为集 Android、iOS 和 Web 的银弹,到完全反对在团队中 React Native 的任何使用。在正式投入使用了之后也是这样的情况。一些团队有着令人难以置信的经历,而其他团队则为此后悔不已,并重回原生的怀抱。 + +#### 根本原因 + +在使用 React Native 时,存在一些不可避免的错误,改进和性能问题。但是,有很多动人的东西: + +1. React Native 本身迭代速度很快。 +2. 我们可以同时进行基础架构和功能的开发。 +3. 工程师们可以一起学习 React Native,这门语言对每个人相对来说都是比较新的。 +4. 我们在开发和正式生产环境中进行调试的文档和指南有时不一致,可能会造成混淆。 + +因此,通常很难找到问题的根本原因。有时候,不清楚问题出在哪个团队,或者这个问题是不是 React Native 固有的问题。 + +#### React Native 仍然需要原生 + +一个常见的误解是,React Native 允许你完全不用编写原生代码。然而,事情并不是这样的。React Native 的原生基础有时还会继续向前发展。例如,每个平台上的文本渲染略有不同,键盘处理方式不同,并且默认情况下在 Android 上旋转时重新创建 Activity。一个高质量的 React Native 体验,需要仔细地保持不同平台的平衡。再加上,开发者难以精通三种平台上的专业知识,因此在开发中始终难以实现优质体验。 + +#### 跨平台调试 + +大多数工程师都能精通一或两个平台。很少有人能同时精通 Android、iOS 和 React。尽管成熟的 React Native 环境中,绝大多数工作都是通过 JavaScript 和 React 完成的,但有时构建或调试某些东西需要钻研原生的东西。这些情况可能导致工程师在他们从未使用过的平台上调试时,陷入专业知识之外的困境。更糟糕的是,由于根本原因难以确定,工程师甚至不确定往什么方向定位问题。 + +#### 持续招聘 + +尽管我们投入使用 React Native,但我们移动端的野心和团队仍在同步扩大。然而,通过社区口碑,许多人开始将 Airbnb 与 React Native 联系起来,甚至有人认为我们的应用是 100% React Native。尽管事实远非如此,但许多 Android 和 iOS 工程师也因此不愿意申请来 Airbnb。以防你是其中之一,[我们还在招人呢](https://www.airbnb.com/careers/departments/engineering)! + +#### 混合应用很难实现 + +100% 原生或 100% React Native 的路相对简单。但是,一旦你在代码库中混合使用了,就会出现许多新问题。你如何分配你的团队?团队如何协作?如何在你的应用中共享状态?如何确保代码得到测试?工程师如何在三个平台上进行有效调试?如何决定使用哪个平台来实现新功能?如何在整个组织中聘用和分配资源?这些都是非常重要的需要解决方案的问题,如果你沿着这条路一直走下去,就不可避免地会出现这些问题。 + +#### 三种开发环境 + +为了成为一名高效率的 React Native 工程师,拥有稳定且最新的 React Native、Android 和 iOS 环境非常重要。对于像 Airbnb 这样大的组织来说,每个平台都需要大量时间来配置,学习并保持最新状态。短短几周后,常常意味着要花费几个小时,才能使所有东西都恢复到最新状态。 + +#### Balancing Native vs React Native + +在许多情况下,问题的最佳解决方案需要跨越原生和 React Native。例如,我们导航的实现大量使用 Activity 和 ViewController,其大部分代码都是原生的,适用于每个平台。但很多时候,不清楚代码是否应该用原生或 React Native 编写。当然,工程师通常会选择他们更舒适的平台,但是这可能导致代码不太理想。 + +#### 跨平台测试 + +我们发现,由于方便或舒适,工程师主要在一个平台上进行开发。通常,他们会假设如果它在他们测试的平台上正常工作,那么它在全平台同理也能完美运行。大多数情况下,这证明了 React Native 优势。然而,这往往不是真实的情况,它最终会导致在 QA 周期的后期或生产环境中频繁发生问题。 + +#### 拆分团队 + +在原生以及 React Native 工作的团队,经常面临技术和沟通方面的挑战。一旦代码库在原生和 React Native 之间拆分,代码就会变得支离破碎。共享业务逻辑、模型、状态等变成更具挑战性的难题,工程师不再具备在整个流程中工作的专业知识。我们知道,这个问题从一开始就存在,但认为可能会通过与 Web 的更多合作来平衡。一些团队确实开始通过 Web 和移动设备共享资源和代码,但是大多数团队没有意识到这一潜在的好处。 + +#### 感知迭代速度 + +我们使用 React Native 的质量目标之一,就是提高开发速度。通常,React Native 的功能是由单个工程师编写的,而不是针对每个平台编写的。从 React Native 工程师的角度来看,如果他们比在 Android 或 iOS 上花费的时间多 50%,即使总体的花费的时间更少,他们也会觉得花费的时间更长。 + +#### 公共资源和文件 + +Android 和 iOS 已有十年的时间了,有数百万的工程师为学习资源、开源和在线帮助都贡献了不少力量。我们利用 [CodePath](https://codepath.com/androidbootcamp) 等许多资源来帮助人们学习 Android 和 iOS。尽管 React Native 拥有最大的跨平台社区之一,并且可以利用 React 资源,但它相比 Android 和 iOS 还小得多。再加上我们必须在内部建设大部分基础架构,这一事实意味着,相对于原生资源,我们有限的 React Native 资源在教育和培训上会投入过大。 + +* * * + +这是系列博客文章的第三部分,重点讲述了我们使用 React Native 的经验,以及 Airbnb 移动端接下来要做的事情。 + +* [第一部分:Airbnb 中的 React Native](https://juejin.im/post/5b2c924ff265da59a401f050) +* [第二部分:技术细节](https://juejin.im/post/5b3b40a26fb9a04fab44e797) +* [**第三部分:构建跨平台的移动端团队**](https://github.com/xitu/gold-miner/blob/master/TODO1/sunsetting-react-native.md) +* [第四部分:在 React Native 上作出的决策](https://github.com/xitu/gold-miner/blob/master/TODO1/sunsetting-react-native.md) +* [第五部分:移动端接下来的事情](https://github.com/xitu/gold-miner/blob/master/TODO1/whats-next-for-mobile-at-airbnb.md) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/building-a-custom-slider-in-flutter-with-gesturedetector.md b/TODO1/building-a-custom-slider-in-flutter-with-gesturedetector.md new file mode 100644 index 00000000000..b2b550423b6 --- /dev/null +++ b/TODO1/building-a-custom-slider-in-flutter-with-gesturedetector.md @@ -0,0 +1,63 @@ +> * 原文地址:[Building a Custom Slider in Flutter with GestureDetector](https://medium.com/@rjstech/building-a-custom-slider-in-flutter-with-gesturedetector-fcdd76224acd) +> * 原文作者:[RJS Tech](https://medium.com/@rjstech/building-a-custom-slider-in-flutter-with-gesturedetector-fcdd76224acd) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/building-a-custom-slider-in-flutter-with-gesturedetector.md](https://github.com/xitu/gold-miner/blob/master/TODO1/building-a-custom-slider-in-flutter-with-gesturedetector.md) +> * 译者:[ALVINYEH](https://github.com/ALVINYEH) +> * 校对者: + +# 使用 Flutter 的 GestureDetector 构建自定义滑块 + +![](https://cdn-images-1.medium.com/max/1600/1*jIONll1unU_jcNHgv0C5qg.png) + +Flutter 的一大优点是,可以轻松创建自定义 UI。在本教程中,我们将看到这一点。 + +首先,我们先停下来思考一下,需要构建什么内容。我们应该有一个滑块,并在其顶部显示填充的百分比。 + +在此之前,很明显我们需要维护一个窗口小控件,它显示一个已填充的给定百分比的进度条。在构建 UI 时,最好考虑一下这些控件,它们不具有任何状态,但会显示父级控件所提供的内容。 + +所以,让我们开始声明小控件 + +![](https://cdn-images-1.medium.com/max/1600/1*9QyxospGGYvnt0b_OLpE_A.png) + +这个小控件非常简单,我们接收完成的百分比值,以及正面和背面部分的颜色。主 `Container` 将背面颜色作为背景,我们将绘制正面部分去覆盖它。它的子节点是 `Row`,虽然它只包含一个子节点,但我保留了它,方便你添加另一个 `Container`,它可以显示背面的部分或其中的一些信息(例如,剩余的百分比)。通过从 `Container` 的总宽度中取相同的百分比,计算并显示已完成百分比的 `Container` 的 `width`。 + +接下来,我们从主要的 App 类开始。 + +![](https://cdn-images-1.medium.com/max/1600/1*XCxELZi86mQkd8RxK6yMAQ.png) + +显然,现在我们必须声明 `MyHomePage` 类,现在这个类应该能够使用我们上面编写的 `CustomSlider` 控件,并处理手势检测部分,其中用户可以拖动来增加和减少滑块显示的百分比。 + +![](https://cdn-images-1.medium.com/max/1600/1*pjjpL-46CNHxQaur3jOp4A.png) + +这个控件必须是有状态的,因为要追踪其百分比。在这里,我们声明了控件的颜色,并将初始百分比保持为 0.0。另外还要注意,现在我们有一个显示舍入百分比的 `Text`,它与 `CustomSlider` 一起在屏幕上居中。 + +现在,请注意我们用 `GestureDetector` 控件包围住了 `CustomSlider` 控件。我们接下来的工作就是,给控件注入活力,使用 `GestureDetector` 控件来捕获用户的拖动事件。 + +让我们看看实现这部分的代码。 + +![](https://cdn-images-1.medium.com/max/1600/1*pNfLsEImWg3IT2Y8YZtQIw.png) + +这是添加了拖动部分的完整代码。`GestureDetector` 控件加入了 `onPanStart`、`onPanUpdate` 和 `onPanEnd` 属性来处理拖动的手势。我希望这些命名,能表明各自的用途。 + +为了知道用户拖动了多少,我们存储了拖动开始的位置,每次用户移动他/她的手指时,都会在 `onPanUpdate` 方法中计算距离。接着将距离除以滑块的宽度 200。然后我们简单地将计算完的距离添加到百分比的位置,设置值为 0.0 到 100.0 之间。该值不会超过滑动块的边界,这对于百分比的值来说是自然而然的事情。 + +这里只给出一个我们自定义的滑块……请用这个来展示一下你做了什么改变吧。 + +[点击这里](https://pastebin.com/C2ZuRdM8) 获得不同可以复制/粘贴的代码版本。 + +* [JavaScript](https://medium.com/tag/javascript?source=post) +* [Flutter](https://medium.com/tag/flutter?source=post) +* [Gesturedetector](https://medium.com/tag/gesturedetector?source=post) +* [Apps](https://medium.com/tag/apps?source=post) +* [Dart](https://medium.com/tag/dart?source=post) + +喜欢你读的东西吗?给 RJS Tech 一点掌声。 + +从为之欢呼到起立鼓掌,拍手表示你是多么喜欢这篇文章。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/building-a-successful-app-or-game-business-in-southeast-asia.md b/TODO1/building-a-successful-app-or-game-business-in-southeast-asia.md new file mode 100644 index 00000000000..e52f42f5049 --- /dev/null +++ b/TODO1/building-a-successful-app-or-game-business-in-southeast-asia.md @@ -0,0 +1,90 @@ +> * 原文地址:[How to grow your app business in Southeast Asia](https://medium.com/googleplaydev/building-a-successful-app-or-game-business-in-southeast-asia-29e6eea0defb) +> * 原文作者:[Guy Charusadhirakul](https://medium.com/@guycharusa?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/building-a-successful-app-or-game-business-in-southeast-asia.md](https://github.com/xitu/gold-miner/blob/master/TODO1/building-a-successful-app-or-game-business-in-southeast-asia.md) +> * 译者: +> * 校对者: + +# How to grow your app business in Southeast Asia + +## Four key strategies for localization and growing to new markets with Android (Go edition) + +![](https://cdn-images-1.medium.com/max/1600/1*mNb91X17FSyOL7CKXh6E-A.png) + +Southeast Asia is a large and diverse region spanning 10 countries, with a [population of over 630 million](https://aseanup.com/asean-infographics-population-market-economy/). With over [330 million internet users](https://www.thinkwithgoogle.com/intl/en-apac/trends-and-insights/e-conomy-sea-unlocking-200b-digital-opportunity/) — already more internet users than the US — the region is ripe for an explosive digital and mobile revolution. [Research by Google and Temasek](https://www.thinkwithgoogle.com/intl/en-apac/trends-and-insights/e-conomy-sea-unlocking-200b-digital-opportunity/) highlights that the Southeast Asia (SEA) digital economy will be worth over US$200 billion by 2025. + +Unlike regions with more developed internet infrastructure, people in SEA rely on smartphones to access information, share content on social media, and consume entertainment. In fact, people in Southeast Asia [spend 3.6 hours on the mobile internet every day](https://www.blog.google/around-the-globe/google-asia/sea-internet-economy/), more time than anywhere else in the world. + +With fast-growing disposable income and rapid growth in smartphone ownership, Southeast Asia represents an opportunity for app and game developers to expand their user base and business. + +However, Southeast Asia presents unique challenges for global app and game developers. While countries in the region share many cultural and economic characteristics, they speak different languages and have unique consumer preferences. Many consumers in the region are still getting used to making purchases on their smartphones and, at the same time, becoming familiar with new payment methods. + +Based on my experience, as a Southeast Asian native and from my work across the region, I have synthesized 4 key strategies for app and game developers to help grow their business in Southeast Asia. + +![](https://cdn-images-1.medium.com/max/1600/0*SP1YjLo_uniUb49G) + +### Strategy 1: Localize your content + +Localization is key. I recommend translating app and game contents along with their Google Play store listing into the local languages. This is critical for markets such as Thailand, Indonesia, and Vietnam where English is not widely used. Developers that have translated have seen increased growth in app installs, users, and spending. + +> _For example, based on comparing in-app spending in the 3 months before localization to the 3 months after, Legacy of Discord-FuriousWings by GTarcade reported that they saw 150% growth in spending when the game was localized into Thai and 40% growth from their Bahasa Indonesia version. Similarly, Supercell data shows 40% increase in spending after localizing_ [_Hay Day_](https://play.google.com/store/apps/details?id=com.supercell.hayday) _into Thai._ + +Where you’re unfamiliar with the new market, use [store listing experiments](https://developer.android.com/distribute/best-practices/grow/store-listing-experiments) to test versions of the store listing in the languages you’re targeting. + +In addition to translating the content, you should consider localizing in-app or game content to fit the local cultural norms. Creating a cultural fit makes apps and games feel relevant to people. + +> _For example,_ [_Smule_](https://play.google.com/store/apps/developer?id=Smule) _works with artists to offer relevant songs to listeners in Indonesia and Malaysia. Smule is consistently one of the top grossing apps on the Google Play Store in these countries._ + +![](https://cdn-images-1.medium.com/max/1600/0*2BmnPD79f2EoGRII) + +### Strategy 2: Localize price and think about local payments + +Consumers in Southeast Asia have lower disposable incomes relative to developed markets. GDP per capita for 2016 is estimated at [US$ 4,034](https://www.aseanstats.org/wp-content/uploads/2018/01/ASYB_2017-rev.pdf). Therefore, consider pricing in-app purchases or subscriptions to match consumers income. + +> _Smule price their monthly subscription in Indonesia at IDR 12,000, which is $US0.83. This compares with US$4.99/month in the US. By setting the price of in-game currencies 30–40% lower than the North Asia version,_ [_Dragon Nest M_](https://play.google.com/store/apps/details?id=com.playfungame.ggplay.lzgsea)_, a popular action game in SEA, reported net positive revenue._ + +Google Play also provides for in-app purchases to be priced below $US0.99 for all markets in Southeast Asia, except Singapore. + +In addition to localizing pricing items to local users’ ability to pay, direct carrier billing and gift cards are popular payment methods in the region. These are good alternatives, as credit card ownership is not widespread in Southeast Asia. Google Play has partnerships with 24 carriers in Southeast Asia to make it easy for consumers to make purchases in your apps. + +![](https://cdn-images-1.medium.com/max/1600/0*cBlieEiL3XU7Gu3b) + +### Strategy 3: Optimize your apps and games for those in emerging markets, such as SEA + +Southeast Asian consumers use a wide variety of devices — from high-end smartphones to entry-level Android phones. To ensure the best user experience on entry-level devices, many developers optimize their apps by reducing APK size and optimizing memory use. This is in line with the result of [Google’s survey of Android user](https://medium.com/googleplaydev/shrinking-apks-growing-installs-5d3fcba23ce2): ~70% of people in emerging markets consider the size of an app before downloading it out of concerns for data cost and phone storage space. + +> _The developer of_ [_Garena Free Fire_](https://play.google.com/store/apps/details?id=com.dts.freefireth) _optimized this game for emerging markets by reducing APK size through audio, image, and data compression. The developer also optimized memory use by using different texture resolution depending on the graphics quality. As a result, Free Fire has been one of the top-downloaded games in Southeast Asia._ + +You can further optimize your apps for [Android Oreo (Go Edition)](https://www.android.com/versions/oreo-8-0/go-edition/). Do this by reducing APK size, optimizing memory use, and reducing app start-up time. [Viki](https://play.google.com/store/apps/details?id=com.viki.android), [Shopback](https://play.google.com/store/apps/details?id=com.shopback.app), [Tokopedia](https://play.google.com/store/search?q=Tokopedia&c=apps&sticky_source_country=ID), and [Picmix](https://play.google.com/store/apps/details?id=com.picmix.mobile) are examples of apps popular in Southeast Asia which have been optimized for Android Oreo (Go edition) to better serve people in the region. + +You should also pay attention to [Android vitals](https://developer.android.com/topic/performance/vitals/), which measures app health signals such as crash rate, app-not-responding, and battery draining wake locks. These are very relevant to users and devices in emerging markets, such as SEA. You can monitor Android vitals in the Google Play Console. + +However, if your app or game needs a higher spec device to provide a good experience, take advantage of [device catalog](https://support.google.com/googleplay/android-developer/answer/7353455?hl=en). This Google Play Console feature enables you to filter devices to ensure that only consumers with suitable phones can install your app or game. + +![](https://cdn-images-1.medium.com/max/1600/0*_D796bdhi6hvwiNy) + +### Strategy 4: Build local communities and engage with local users + +Southeast Asian users are highly social: online and offline. Successful developers have harnessed the power of community to acquire users, educate people about their apps and games, and keep users engaged and coming back. Here are some tips that can help build a strong community in Southeast Asia: + +* **Communicate with people in their language:** To build a strong community you need to communicate with people in their language. This means having native speakers responding to user reviews on the Play Store and providing communications through other channels too. When Netmarble launched their popular game [Lineage2 Revolution](https://play.google.com/store/apps/details?id=com.netmarble.revolutionthm) in Indonesia, they responded to user reviews on Google Play in Bahasa Indonesian — the local language. Lineage2 Revolution has consistently been among the top 3 grossing titles in Indonesia on Google Play since it’s launch. +* **Build a local social media presence:** Successful developers use popular social media in local markets to regularly communicate relevant news and content to their community. These social channels are also popular ways for people to relay customer service issues to you. For example, [IGG.COM](https://play.google.com/store/apps/dev?id=8895734616362643252) estimates that more than 50% of their customer service issues in SEA come through social channels. +* **Consider working with content creators:** YouTube is extremely popular in Southeast Asia. In fact, Thailand and Indonesia have the [highest proportion of people who watch YouTube on mobile](https://www.thinkwithgoogle.com/intl/en-apac/trends-and-insights/beyond-numbers-youtube-shapes-lives-thailand-indonesia/). As a result, many game developers work with creators to stream gameplay that helps educate and re-engage players. +* **Don’t underestimate offline:** Offline events are an important element of building community in Southeast Asia. Developers such as [Com2uS](https://play.google.com/store/apps/dev?id=6850516909323484758) and [Siamgame](https://play.google.com/store/apps/dev?id=6476992165808510390) regularly hold offline events for their most avid fans, to foster a strong sense of community and increase re-engagement. eSport is becoming popular in SEA and it is even being included in this year’s Asian Games, hosted in Jakarta and Palembang, Indonesia. [Hero Games](https://play.google.com/store/apps/dev?id=9060101706093336387) and [Garena](https://play.google.com/store/apps/details?id=com.dts.freefireth) have credited eSport as being a major factor in driving engagement with the gamer community in Southeast Asia. + +### Final word + +The Southeast Asia market offers a tremendous opportunity to find new users and grow revenue as the economies in this dynamic region continue to grow. The key to success is to tailor your business to the market you are targeting — localize your content, set pricing to local incomes, optimize for Android Oreo (Go Edition), and build communities. If you want more guidance, check out the [Build for billions](https://developer.android.com/docs/quality-guidelines/building-for-billions/) page for Android developers. + +* * * + +### What do you think? + +Do you have thoughts on building app and games businesses in SEA? Let us know in the comments below or tweet using **#AskPlayDev** and we’ll reply from [@GooglePlayDev](http://twitter.com/googleplaydev), where we regularly share news and tips on how to be successful on Google Play. + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/building-a-text-editor-for-a-digital-first-newsroom.md b/TODO1/building-a-text-editor-for-a-digital-first-newsroom.md new file mode 100644 index 00000000000..b0794898a87 --- /dev/null +++ b/TODO1/building-a-text-editor-for-a-digital-first-newsroom.md @@ -0,0 +1,145 @@ +> * 原文地址:[Building a Text Editor for a Digital-First Newsroom](https://open.nytimes.com/building-a-text-editor-for-a-digital-first-newsroom-f1cb8367fc21) +> * 原文作者:[Sophia Ciocca](https://open.nytimes.com/@sophiaciocca?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/building-a-text-editor-for-a-digital-first-newsroom.md](https://github.com/xitu/gold-miner/blob/master/TODO1/building-a-text-editor-for-a-digital-first-newsroom.md) +> * 译者: +> * 校对者: + +# Building a Text Editor for a Digital-First Newsroom + +## An inside look at the inner workings of a technology you may take for granted + +![](https://cdn-images-1.medium.com/max/800/1*LnJwoZLOuEZ1v1eAN-UWZg.gif) + +Illustration by Aaron Krolik/The New York Times + +If you’re like most people in America, you use a text editor nearly every day. Whether it’s your basic Apple Notes, or something more advanced like Google Docs, Microsoft Word or Medium, our text editors allow us to record and render our important thoughts and information, enabling us to tell stories in the most engaging ways. + +But you might not have ever thought about how those text editors work under the hood. Every time you press a key, hundreds of lines of code may be executing to render your desired character on the page. Actions that seem small — such as dragging a selection over a few words of text or turning text into a heading — actually trigger lots of changes in the system underlying the program. + +While you may not think about the code powering these complicated text-editing maneuvers, my team here at The New York Times thinks about it constantly. Our primary task is to create an ultra-customized story editor for the newsroom. Beyond the basics of being able to type and render content, this new story editor needs to combine the advanced features of Google Docs with the intuitive design focus of Medium, then add lots of features unique to the newsroom’s workflow. + +For a number of years, The Times’s newsroom has used a legacy homegrown text editor that hasn’t quite served its many needs. While our older editor is intensely tailored to the newsroom’s production workflow, its UI leaves much to be desired: It heavily compartmentalizes that workflow, separating different parts of a story (e.g. text, photos, social media and copy-editing) into completely different parts of the app. Producing an article in this older editor therefore requires navigating through a lengthy series of unintuitive and visually unappealing tabs. + +In addition to promoting a fragmented workflow for users, the legacy editor also causes a lot of pain on the engineering side. It relies on direct DOM manipulation to render everything in the editor, adding various HTML tags to signify the difference between deleted text, new text and comments. This means engineers on other teams then have to put the article through heavy tag cleanup before it can be published and rendered to the website, a process that is time-consuming and prone to mistakes. + +As the newsroom evolves, we envisioned a new story editor that would visually bring the different components of stories _inline,_ so that reporters and editors alike could see exactly what a story would look like before it publishes. Additionally, the new approach would ideally be more intuitive and flexible in its code implementation, avoiding many of the problems caused by the older editor. + +With these two goals in mind, my team set out to build this new text editor, which we named Oak. After much research and months of prototyping, we opted to build it on the foundation of [ProseMirror](http://prosemirror.net/), a robust open-source JavaScript toolkit for building rich-text editors. ProseMirror takes a completely different approach than our old text editor did, representing the document using its own non-HTML [tree-shaped data structure](https://en.wikipedia.org/wiki/Tree_%28data_structure%29) that describes the structure of the text in terms of paragraphs, headings, lists, links and more. + +Unlike the output of our old editor, the output of a text editor built on ProseMirror can ultimately be rendered as a DOM tree, Markdown text or any other number of other formats that can express the concepts it encodes, making it very versatile and solving many of the problems we run into with our legacy text editor. + +So how does ProseMirror work, exactly? Let’s jump into the technology behind it. + +### Everything is a Node + +ProseMirror structures its main elements — paragraphs, headings, lists, images, etc. — as _nodes_. Many nodes can have child nodes — e.g., a `heading_basic` node can have child nodes including a `heading1` node, a `byline` node, a `timestamp` node and `image` nodes. This leads to the tree-like structure I mentioned above. + +![](https://cdn-images-1.medium.com/max/1000/1*Ek78_oxd_hD-fn_dx-YvFg.png) + +The interesting exception to this tree-like structure lies in the way paragraph nodes codify their text. Consider a paragraph consisting of the sentence, “This is **strong text with _emphasis_**”. + +The DOM would codify that sentence as a tree, like this: + +![](https://cdn-images-1.medium.com/max/800/0*oGZfDS1Rlm4MzAQu.) + +_Traditional DOM representation of a sentence — its tags work in a nested, tree-like fashion. Source:_ [_ProseMirror_](https://prosemirror.net/docs/guide/) + +In ProseMirror, however, the content of a paragraph is represented as a flat sequence of inline elements, each with its own set of styles: + +![](https://cdn-images-1.medium.com/max/800/0*BKjocnJ6-DyNj-tK.) + +_How ProseMirror would structure the same sentence. Source:_ [_ProseMirror_](https://prosemirror.net/docs/guide/) + +There’s an advantage to this flat paragraph structure: ProseMirror keeps track of every node in terms of its numerical position. Because ProseMirror recognizes the italicized and bolded word “emphasis” in the example above as its own standalone node, it can represent the node’s position as simple character offsets rather than thinking about it as a location in a tree. The text editor can know, for example, that the word “emphasis” begins at position 63 in the document, which makes it easy to select, find and work with. + +All of these nodes — paragraph nodes, heading nodes, image nodes, etc. — have certain features associated with them, including sizes, placeholders and draggability. In the case of some specific nodes like images or videos, they also must contain an ID so that media files can be found in the larger CMS environment. How does Oak know about all of these node features? + +To tell Oak what a particular node is like, we create it with a “node spec,” a class that defines those custom behaviors or methods that the text editor needs to understand and properly work with the node. We then define a schema of all the nodes that exist in our editor and where each node is allowed to be placed in the overall document. (We wouldn’t, for example, want users placing embedded tweets inside of the header, so we disallow it in the schema.) In the schema, we list all the nodes that exist in the Oak environment and how they relate to each other. + +``` +export function nytBodySchemaSpec() { + const schemaSpec = { + nodes: { + doc: new DocSpec({ content: 'block+', marks: '_' }), + paragraph: new ParagraphSpec({ content: 'inline*', group: 'block', marks: '_' }), + heading1: new Heading1Spec({ content: 'inline*', group: 'block', marks: 'comment' }), + blockquote: new BlockquoteSpec({ content: 'inline*', group: 'block', marks: '_' }), + summary: new SummarySpec({ content: 'inline*', group: 'block', marks: 'comment' }), + header_timestamp: new HeaderTimestampSpec({ group: 'header-child-block', marks: 'comment' }), + ... + }, + marks: + link: new LinkSpec(), + em: new EmSpec(), + strong: new StrongSpec(), + comment: new CommentMarkSpec(), + }, + }; +} +``` + +Using this list of all the nodes that exist in the Oak environment and how they relate to each other, ProseMirror creates a model of the document at any given time. This model is an object, very similar to the JSON shown next to the example Oak article in the topmost illustration. As the user edits the article, this object is constantly being replaced with a new object that includes the edits, which ensures ProseMirror always knows what the document includes and therefore what to render on the page. + +Speaking of which: Once ProseMirror knows how nodes fit together in a document tree, how does it know what those nodes look like or how to actually display them on the page? To map the ProseMirror state to the DOM, every node has a simple `toDOM()` method out of the box that converts the node to a basic DOM tag — for example, a Paragraph node’s `toDOM()` method would convert it to a `

` tag, while an Image node’s `toDOM()` method would convert it to an `` tag. But because Oak needs customized nodes that do very specific things, our team leverages ProseMirror’s NodeView function to design a custom React component that renders the nodes in specific ways. + +(Note: ProseMirror is framework-agnostic, and NodeViews can be created using any front-end framework or none at all; our team has just chosen to use React.) + +### Keeping track of text styling + +If a node is created with a specific visual appearance that ProseMirror gets from its NodeView, how do additional user-added stylings like bold or italics work? That’s what _marks_ are for. You might have noticed them up in the schema code block above. + +Following the block where we declare all the nodes in the schema, we declare the types of marks each node is allowed to have. In Oak, we support certain marks for some nodes, and not for others — for instance, we allow italics and hyperlinks in small heading nodes, but neither in large heading nodes. Marks for a given node are then kept in that node’s object in ProseMirror’s state of the current document. We also use marks for our custom comment feature, which I’ll get to a little later in this post. + +### How do edits work under the hood? + +In order to render an accurate version of the document at any given time and also track a version history, it’s critically important that we record virtually everything the user does to change the document — for example, pressing the letter “s” or the enter key, or inserting an image. ProseMirror calls each of these micro-changes a _step_. + +To ensure that all parts of the app are in sync and showing the most recent data, the state of the document is immutable, meaning that updates to the state don’t happen by simply editing the existing data object. Instead, ProseMirror takes the old object, combines it with this new step object and arrives at a brand new state. (For those of you familiar with Flux concepts, this probably feels familiar.) + +This flow both encourages cleaner code and also leaves a trail of updates, enabling some of the editor’s most important features, including version comparison. We track these steps and their order in our Redux store, making it easy for the user to roll back or roll forward changes to switch between versions and see the edits that different users have made: + +![](https://cdn-images-1.medium.com/max/800/1*tSuAfd7GowO1oQoLRPQt5A.gif) + +_Our version comparison feature relies on keeping careful track of each transaction in an immutable Redux state._ + +### Some of the Cool Features We’ve Built + +The ProseMirror library is intentionally modular and extensible, which means it requires heavy customization to do anything at all. This was perfect for us because our goal was to build a text editor to fit the newsroom’s specific requirements. Some of the most interesting features our team has built include: + +#### Track Changes + +Our “track changes” feature, shown above, is arguably Oak’s most advanced and important. With newsroom articles involving a complex flow between reporters and their various editors, it’s important to be able to track what changes different users have made to the document and when. This feature relies heavily on the careful tracking of each transaction, storing each one in a database and then rendering them in the document as green text for additions and red strikeout text for deletions. + +#### Custom Headers + +Part of Oak’s purpose is to be a design-focused text editor, giving reporters and editors the ability to present visual journalism in the way that best fits any given story. To this aim, we’ve created custom header nodes including horizontal and vertical full-bleed images. These headers in Oak are each nodes with their own unique NodeViews and schemas that allow them to include bylines, timestamps, images and other nested nodes. For users, they mirror the headers that published articles can have on the reader-facing site, giving reporters and editors as close as possible a representation to what the article will look like when it’s published for the public on the actual New York Times website. + +![](https://cdn-images-1.medium.com/max/400/1*_cgjmva3RSguksfzzMsfhA.png) + +![](https://cdn-images-1.medium.com/max/400/1*tQYcbXpRjU4zkUwgSurr8Q.png) + +![](https://cdn-images-1.medium.com/max/400/1*gcFYFMW2K07mmG_q488f1Q.png) + +A few of Oak’s header options. From left to right: Basic header, Horizontal full-bleed header, Vertical full-bleed header. + +#### Comments + +Comments are an important part of the newsroom workflow — editors need to converse with reporters, asking questions and giving suggestions. In our legacy editor, users were forced to put their comments directly into the document alongside the article text, which often made the article look busy and were easy to miss. For Oak, our team created an intricate ProseMirror plugin that renders comments off to the right. Believe it or not, comments are actually a type of _mark_ under the hood. It’s an annotation on text, like bold, italics or hyperlinks; the difference is just the display style. + +![](https://cdn-images-1.medium.com/max/800/1*4t-fGEwAmWDBdhHjTVoswA.gif) + +In Oak, comments are a type of mark, but they’re displayed on the right side of the relevant text or node. + +* * * + +Oak has come a long way since its conception, and we’re excited to continue building new features for the many newsroom desks that are beginning to make the switch from our legacy editor. We’re planning to begin work soon on a collaborative editing feature that would allow more than one user to edit an article at the same time, which will radically improve the way reporters and editors work together. + +Text editors are much more complex than many know. I consider it a privilege to be part of the Oak team, building a tool that, as a writer, I find fascinating and also so important for the functioning of one of the world’s largest and most influential newsrooms. Thank you to my managers, Tessa Ann Taylor and Joe Hart, and my team that’s been working on Oak since well before I arrived: Thomas Rhiel, Jeff Sisson, Will Dunning, Matthew Stake, Matthew Berkowitz, Dylan Nelson, Shilpa Kumar, Shayni Sood and Robinson Deckert. I am lucky to have such amazing teammates in making the Oak magic happen. Thank you. + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/building-fluid-interfaces-ios-swift.md b/TODO1/building-fluid-interfaces-ios-swift.md new file mode 100644 index 00000000000..e6783a29cc4 --- /dev/null +++ b/TODO1/building-fluid-interfaces-ios-swift.md @@ -0,0 +1,594 @@ +> * 原文地址:[Building Fluid Interfaces: How to create natural gestures and animations on iOS](https://medium.com/@nathangitter/building-fluid-interfaces-ios-swift-9732bb934bf5) +> * 原文作者:[Nathan Gitter](https://medium.com/@nathangitter?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/building-fluid-interfaces-ios-swift.md](https://github.com/xitu/gold-miner/blob/master/TODO1/building-fluid-interfaces-ios-swift.md) +> * 译者:[RydenSun](https://juejin.im/user/585b9407da2f6000657a5c0c) +> * 校对者:[atuooo](https://github.com/atuooo) + +# 构建流畅的交互界面 + +## 如何在 iOS 上创建自然的交互手势及动画 + +在 WWDC 2018 上,苹果设计师进行了一次题为 [“设计流畅的交互界面”](https://developer.apple.com/videos/play/wwdc2018/803/) 的演讲,解释了 iPhone X 手势交互体系背后的设计理念。 + +![](https://cdn-images-1.medium.com/max/1600/1*EZJGlfbTCPSEq7Exwjla1Q.png) + +苹果 WWDC18 演讲 “设计流畅的交互界面” + +这是我最喜欢的 WWDC 分享 —— 我十分推荐它 + +这次分享提供了一些技术性指导,这对一个设计演讲来说是很特殊的,但它只是一些伪代码,留下了太多的未知。 + +![](https://cdn-images-1.medium.com/max/1600/1*m_arQ47qnUvIFPNCHRxt7Q.png) + +演讲中一些看起来像 Swift 的代码。 + +如果你想尝试实现这些想法,你可能会发现想法和实现是有差距的。 + +我的目的就是通过提供每个主要话题的可行的代码例子,来减少差距。 + +![](https://cdn-images-1.medium.com/max/1600/1*zvcJzQnHtJRrDhvfV9XaYw.gif) + +我们会创建 8 个界面。 按钮,弹簧动画,自定义界面和更多! + +这是我们今天会讲到的内容概览: + +1. “设计流畅的交互界面”演讲的概要。 +2. 8 个流畅的交互界面,背后的设计理念和构建的代码。 +3. 设计师和开发者的实际应用 + +### 什么是流畅的交互界面? + +一个流畅交互界面也可以被描述为“快”,“顺滑”,“自然”或是“奇妙”。它是一种光滑的,无摩擦的体验,让你只会感觉到它是对的。 + +WWDC 演讲认为流畅的交互界面是“你思想的延伸”或是“自然世界的延伸”。当一个界面是按照人们的想法做事,而不是按照机器的想法时,他就是流畅的。 + +### 是什么让它们流畅? + +流畅的交互界面是响应式的,可中断的,并且是可重定向的。这是一个 iPhone X 滑动返回首页的手势案例: + +![](https://cdn-images-1.medium.com/max/1600/1*XxdPbsgL9qeY4QXr1pztfw.gif) + +应用在启动动画中是可以被关闭的。 + +交互界面即时响应用户的输入,可以在任何进程中停止,甚至可以中途改变动画方向。 + +### 我们为什么关注流畅的交互界面? + +1. 流畅的交互界面提升了用户体验,让用户感觉每一个交互都是快的,轻量和有意义的。 +2. 它们给予用户一种掌控感,这为你的应用与品牌建立了信任感。 +3. 它们很难被构建。一个流畅的交互界面是很难被仿造,这是一个有力的竞争优势。 + +### 交互界面 + +这篇文章剩下的部分,我会为你们展示怎样来构建 WWDC 演讲中提到的 8 个主要的界面。 + +![](https://cdn-images-1.medium.com/max/1600/1*989Lsw_y9JcZsJrAVyxEEQ.png) + +图标代表了我们要构建的 8 个交互界面。 + +![](https://cdn-images-1.medium.com/max/2000/1*slFD9J80nOOOjm9dsn6aGQ.png) + +### 交互界面 #1:计算器按钮 + +这个按钮模仿了 iOS 计算器应用中按钮的表现行为。 + +![](https://cdn-images-1.medium.com/max/1600/1*h-Y4Y6K8uxu1mZ6NYst4MA.gif) + +#### 核心功能 + +1. 被点击时马上高亮。 +2. 即便处于动画中也可以被立即点击。 +3. 用户可以在按住手势结束时或手指脱离按钮时取消点击。 +4. 用户可以在按住手势结束时,手指脱离按钮和手指重回按钮来确认点击。 + +#### 设计理念 + +我们希望按钮感觉是即时响应的,让用户知道它们是有功能的。 另外,我们希望操作是可以被取消的,如果用户在按下按钮时决定撤销操作。这允许用户更快的做决定,因为他们可以在考虑的同时进行操作。 + +![](https://cdn-images-1.medium.com/max/1600/1*ccdkb04pc02QvnfYJtum8g.png) + +WWDC 演讲上的幻灯片,展示了手势是如何与想法同时进行的,以此让操作更迅速。 + +#### 关键代码 + +第一步是创建一个按钮,继承自 `UIControl`,不是继承自 `UIButton`。`UIButton` 也可以正常工作,但我们既然要自定义交互,那我们就不需要它的任何功能了。 + +``` +CalculatorButton: UIControl { + public var value: Int = 0 { + didSet { label.text = “\(value)” } + } + private lazy var label: UILabel = { ... }() +} +``` + +下一步,我们会使用 `UIControlEvents` 来为各种点击交互事件分配响应的功能。 + +``` +addTarget(self, action: #selector(touchDown), for: [.touchDown, .touchDragEnter]) +addTarget(self, action: #selector(touchUp), for: [.touchUpInside, .touchDragExit, .touchCancel]) +``` + +我们将 `touchDown` 和 `touchDragEnter` 组合到一个单独的事件,叫做 `touchDown`,并且我们将 `touchUpInside`,`touchDragExit` 和 `touchCancel` 组合一个单独的事件,叫做 `touchUp`。 + +(查看 [这个文档](https://developer.apple.com/documentation/uikit/uicontrolevents?language=objc) 来获取所有可用的 `UIControlEvents` 的描述。) + +这让我们有两个方法来处理动画。 + +``` +private var animator = UIViewPropertyAnimator() +@objc private func touchDown() { + animator.stopAnimation(true) + backgroundColor = highlightedColor +} +@objc private func touchUp() { + animator = UIViewPropertyAnimator(duration: 0.5, curve: .easeOut, animations: { + self.backgroundColor = self.normalColor + }) + animator.startAnimation() +} +``` + +在 `touchDown`,我们根据需要取消存在的动画,然后马上将颜色设置成高亮颜色(在这里是浅灰色)。 + +在 `touchUp`,我们创建了一个新的 animator 并且将动画启动。使用 `UIViewPropertyAnimator`,可以轻松地取消高亮动画。 + +(幻灯片笔记:这不是严谨的 iOS 计算器应用中按钮的表现,它允许手势从别的按钮移动到这个按钮来启动点击事件。大多数情况下,我在这里创建的按钮就是 iOS 按钮的预期行为) + +### 交互界面 #2:弹簧动画 + +这个交互展示了弹簧动画是如何可以通过指定一个“阻尼”(反弹)和“响应”(速度)来创建的。 + +![](https://cdn-images-1.medium.com/max/1600/1*S0s0LiggTJm1U44lC4kcfg.gif) + +#### 核心功能 + +1. 使用“对设计友好”的参数。 +2. 对动画持续时间无概念。 +3. 可轻易中断。 + +#### 设计理念 + +弹簧是一个很好的动画模型,因为它的速度和自然的外观表现。一个弹簧动画可以及其迅速的开始,用其大多数的时间来慢慢接近最终状态。 这对创建一个响应式的交互界面来说是完美的。 + +设计弹簧动画时的几个额外的提醒: + +1. 弹簧动画不需要有弹性。使用数值为 1 的阻尼会构建一个动画,它慢慢的向剩下部分靠近,但没有任何反弹。大多数动画应该使用值为 1 的阻尼。 +2. 尝试着避免考虑时长。理论上,一个弹簧动画从来不会完全靠近其余的部分,如果强加上时长限制,会造成动画的不自然。相反,要不断调整阻尼和响应值,直到它感觉对。 +3. 可中断性是很关键的。因为弹簧动画消耗了它们绝大部分的时间来接近最终值,用户可能会认为动画已经完成并且会尝试再与它交互。 + +#### 关键代码 + +在 UIKit 中,我们可以用 `UIViewPropertyAnimator` 和一个 `UISpringTimingParameters` 对象来构建一个弹簧动画。不幸的是,它没有一个只接受“阻尼”和“响应”的初始化构造器。我们能得到的最接近的初始化构造器是 `UISpringTimingParameters`,它需要质量,硬度,阻尼和初始加速度这几个参数。 + +``` +UISpringTimingParameters(mass: CGFloat, stiffness: CGFloat, damping: CGFloat, initialVelocity: CGVector) +``` + +我们希望创建一个简便的初始化构造器,只使用阻尼和响应这两个参数,并且将它们映射至需要的质量,硬度和阻尼。 + +使用一点物理知识,我们可以导出我们需要的公示: + +![](https://cdn-images-1.medium.com/max/1600/1*G_83X45IJ6J8Cedkvue_WA.png) + +弹簧动画的常量和阻尼系数的解决方案。 + +有了这个结果,我们正好可以使用我们想要的参数来创建我们自己的 `UISpringTimingParameters`。 + +``` +extension UISpringTimingParameters { + convenience init(damping: CGFloat, response: CGFloat, initialVelocity: CGVector = .zero) { + let stiffness = pow(2 * .pi / response, 2) + let damp = 4 * .pi * damping / response + self.init(mass: 1, stiffness: stiffness, damping: damp, initialVelocity: initialVelocity) + } +} +``` + +这就是我们如何可以指定弹簧动画到所有其他的交互界面。 + +#### 弹簧动画背后的物理学 + +想深入研究弹簧动画?看看 Christian Schnorr 发的这篇极好的文章:[Demystifying UIKit Spring Animations](https://medium.com/ios-os-x-development/demystifying-uikit-spring-animations-2bb868446773)。 + +![](https://cdn-images-1.medium.com/max/1600/1*NPFOJlbdIyjPXLYU4nJxUQ.png) + +读了他的文章之后,我最终理解了弹簧动画。对 Christian 大大的致敬,因为它帮助我理解了这些动画背后的数学理论,而且教我如何解二阶微分方程。 + +### 交互界面 #3:手电筒按钮 + +又是一个按钮,但又不同的表现形式。它模仿了 iPhone X 锁屏上的手电筒按钮。 + +![](https://cdn-images-1.medium.com/max/1600/1*nrzZVlSrZ7hhrxRe_Sl_bA.gif) + +#### 核心功能 + +1. 需要一个使用 3D Touch 的强力手势。 +2. 对手势有反弹提示。 +3. 对确认启动有震动反馈。 + +#### 设计理念 + +苹果希望创建一个按钮,它可以轻易地并且快速地被接触到,但是并不会被不小心触发。需要强压来启动手电筒是一个很棒的选择,但是缺少了功能的可见性和反馈性。 + +为了解决这个问题,这个按钮是有弹性的,并且会随着用户按压的力度来变大。除此之外,有两个单独的触觉震动反馈:一个是在达到要求的力度按压时,另一个是按压结束按钮被触发时。这些触觉模拟了物理按钮的表现形式。 + +#### 关键代码 + +为了衡量按压按钮的力度,我们可以使用 touch 事件提供的 `UITouch` 对象。 + +``` +override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesMoved(touches, with: event) + guard let touch = touches.first else { return } + let force = touch.force / touch.maximumPossibleForce + let scale = 1 + (maxWidth / minWidth - 1) * force + transform = CGAffineTransform(scaleX: scale, y: scale) +} +``` + +我们基于用户按压力度计算了缩放比例,这样可以让按钮随着用户按压力度变大。 + +既然按钮可以被按压但不会启动,我们需要持续追踪按钮的实时状态。 + +``` +enum ForceState { + case reset, activated, confirmed +} + +private let resetForce: CGFloat = 0.4 +private let activationForce: CGFloat = 0.5 +private let confirmationForce: CGFloat = 0.49 +``` + +通过将确认压力设置到稍小于启动压力,防止用户通过快速的超过压力阈值来频繁的启动和取消启动按钮。 + +对于触觉反馈,我们可以使用 `UIKit` 的反馈生成器。 + +``` +private let activationFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) + +private let confirmationFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium) +``` + +最后,对于反弹动画,我们可以使用 `UIViewPropertyAnimator` 并且配合我们前面构建的 `UISpringTimingParameters` 初始化构造器。 + +``` +let params = UISpringTimingParameters(damping: 0.4, response: 0.2) +let animator = UIViewPropertyAnimator(duration: 0, timingParameters: params) +animator.addAnimations { + self.transform = CGAffineTransform(scaleX: 1, y: 1) + self.backgroundColor = self.isOn ? self.onColor : self.offColor +} +animator.startAnimation() +``` + +### 交互界面 #4:橡皮筋动画 + +橡皮筋动画发生在视图抗拒移动时。一个例子就是当滚动视图滑到最底部时。 + +![](https://cdn-images-1.medium.com/max/1600/1*y0jRo2TeJ9VtCZPmQxyRLw.gif) + +#### 核心功能 + +1. 交互界面永远是可响应的,即使当操作是无效的。 +2. 不同步的触摸追踪,代表了边界。 +3. 随着远离边界,移动距离变小。 + +#### 设计理念 + +橡皮筋动画是一种很好的方式来沟通无效的操作,它仍然会给用户一种掌控感。它温柔的告诉你这是一个边界,将它们拉回到有效的状态。 + +#### 关键代码 + +幸运的是,橡皮筋动画实现起来很直接。 + +``` +offset = pow(offset, 0.7) +``` + +通过使用 0 到 1 之间的一个指数,视图会随着远离原始位置,移动越来越少。要移动的少就用一个大的指数,移动的多就使用一个小的指数。 + +再详细一点,这段代码一般是在触摸移动时,在 `UIPanGestureRecognizer` 回调中实现的。 + +``` +var offset = touchPoint.y - originalTouchPoint.y +offset = offset > 0 ? pow(offset, 0.7) : -pow(-offset, 0.7) +view.transform = CGAffineTransform(translationX: 0, y: offset) +``` + +注意:这并不是苹果如何使用像 scroll view 这些元素来实现橡皮筋动画。我喜欢这个方法,是因为它简单,但对不同的表现,还有很多更复杂的方法。 + +### 交互界面 #5:加速中止 + +为了看 iPhone X 上的应用切换,用户需要从屏幕底部向上滑,并且在中途停止。这个交互界面就是为了创建这个表现形式。 + +![](https://cdn-images-1.medium.com/max/1600/1*GMqctAhbjqpmWmAtsKVeDg.gif) + +#### 核心功能 + +1. 中止是基于手势加速度来计算的。 +2. 越快的停止导致越快的响应。 +3. 没有计时器。 + +#### 设计理念 + +流畅的交互界面应该是快速的。计时器产生的延迟,即便很短,也会让界面感到卡顿。 + +这个交互十分酷,因为它的反应时间是根据用户手势运动的。如果他们很快停止,界面会很快响应。如果他们慢慢停止,界面就慢慢响应。 + +#### 关键代码 + +为了衡量加速度,我们可以追踪最新的拖拽手势的速度值。 + +``` +private var velocities = [CGFloat]() +private func track(velocity: CGFloat) { + if velocities.count < numberOfVelocities { + velocities.append(velocity) + } else { + velocities = Array(velocities.dropFirst()) + velocities.append(velocity) + } +} +``` + +这段代码更新了 `velocities` 数组,这样可以一直持有最新的 7 个速度值,这些可以被用来计算加速度值。 + +为了判断加速度是否足够大,我们可以计算数组中第一个速度值和目前速度值的差。 + +``` +if abs(velocity) > 100 || abs(offset) < 50 { return } +let ratio = abs(firstRecordedVelocity - velocity) / abs(firstRecordedVelocity) +if ratio > 0.9 { + pauseLabel.alpha = 1 + feedbackGenerator.impactOccurred() + hasPaused = true +} +``` + +我们也要确保手势移动有一个最小位移和速度。如果手势已经慢下来超过 90%,我们会考虑将它停止。 + +我的实现并不完美。在我的测试里,它看起来工作的不错,但还有机会深入探索加速度的计算方法。 + +### 交互界面 #6:奖励有自我动量的动画一些反弹效果 + +一个抽屉动画,有打开和关闭状态,他们会根据手势的速度有一些反弹。 + +![](https://cdn-images-1.medium.com/max/1600/1*Wwh583M_4qLWg8Pb16mNeA.gif) + +#### 核心功能 + +1. 点击抽屉动画,没有反弹。 +2. 轻弹出抽屉,有反弹。 +3. 可交互,可中断并且可逆。 + +#### 设计理念 + +抽屉动画展示了这个交互界面的理念。当用户有一定速度的滑动某个视图,将动画附带一些反弹会更令人满意。这样交互界面感觉像活得,也更有趣。 + +当抽屉被点击时,它的动画是没有反弹的,这感觉起来是对的,因为点击时没有任何明确方向动量的。 + +当设计自定义的交互界面时,要谨记界面对于不同的交互是有不同的动画的。 + +#### 关键代码 + +为了简化点击与拖拽手势的逻辑,我们可以使用一个自定义的手势识别器的子类,在点击的一瞬间进入 `began` 状态。 + +``` +class InstantPanGestureRecognizer: UIPanGestureRecognizer { + override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + self.state = .began + } +} +``` + +这可以让用户在抽屉运动时,点击抽屉来停止它,这就像点击一个正在滚动的滚动视图。为了处理这些点击,我们可以检查当手势停止时,速度是否为 0 并继续动画。 + +``` +if yVelocity == 0 { + animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) +} +``` + +为了处理带有速度的手势,我们首先需要计算它相对于剩下的总距离的速度。 + +``` +let fractionRemaining = 1 - animator.fractionComplete +let distanceRemaining = fractionRemaining * closedTransform.ty +if distanceRemaining == 0 { + animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) + break +} +let relativeVelocity = abs(yVelocity) / distanceRemaining +``` + +当我们可以使用这个相对速度时,配合计时变量来继续这个包含一点反弹的动画。 + +``` +let timingParameters = UISpringTimingParameters(damping: 0.8, response: 0.3, initialVelocity: CGVector(dx: relativeVelocity, dy: relativeVelocity)) + +let newDuration = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters).duration + +let durationFactor = CGFloat(newDuration / animator.duration) + +animator.continueAnimation(withTimingParameters: timingParameters, durationFactor: durationFactor) +``` + +这里我们创建有一个新的 `UIViewPropertyAnimator` 来计算动画需要的时间,这样我们可以在继续动画时提供正确的 `durationFactor`。 + +关于动画的回转,会更复杂,我这里就不介绍了。如果你想知道的哦更多,我写了一个关于这部分的完整的教程:[构建更好的 iOS APP 动画](http://www.swiftkickmobile.com/building-better-app-animations-swift-uiviewpropertyanimator/)。 + +### 交互动画 #7: FaceTime PiP + +重新创造 iOS FaceTime 应用中的 picture-in-picture(下文中简称 Pip)UI。 + +![](https://cdn-images-1.medium.com/max/1600/1*zHlr_QAPv7YpEF5wb6YZAQ.gif) + +#### 核心功能 + +1. 轻量,轻快的交互 +2. 投影位置是基于 `UIScrollView` 的减速速率。 +3. 有遵循手势最初速度的持续动画。 + +#### 关键代码 + +我们最终的目的是写一些这样的代码。 + +``` +let params = UISpringTimingParameters(damping: 1, response: 0.4, initialVelocity: relativeInitialVelocity) + +let animator = UIViewPropertyAnimator(duration: 0, timingParameters: params) + +animator.addAnimations { + self.pipView.center = nearestCornerPosition +} + +animator.startAnimation() +``` + +我们希望创建一个带有初始速度的动画,并且与拖拽手势的速度相匹配。并且进行 pip 动画到最近的角落。 + +首先,我们需要计算初始速度。 + +为了能做到这个,我们需要计算基于目前速度,目前为止和目标为止的相对速度。 + +``` +let relativeInitialVelocity = CGVector( + dx: relativeVelocity(forVelocity: velocity.x, from: pipView.center.x, to: nearestCornerPosition.x), + dy: relativeVelocity(forVelocity: velocity.y, from: pipView.center.y, to: nearestCornerPosition.y) +) + +func relativeVelocity(forVelocity velocity: CGFloat, from currentValue: CGFloat, to targetValue: CGFloat) -> CGFloat { + guard currentValue - targetValue != 0 else { return 0 } + return velocity / (targetValue - currentValue) +} +``` + +我们可以将速度分解为 x 和 y 两部分,并且决定它们各自的相对速度。 + +下一步,我们为 PiP 动画计算各个角落。 + +为了让我们的交互界面感觉自然并且轻量,我们要基于它现在的移动来投影 PiP 的最终位置。如果 PiP 可以滑动并且停止,它最终停在哪里? + +``` +let decelerationRate = UIScrollView.DecelerationRate.normal.rawValue +let velocity = recognizer.velocity(in: view) +let projectedPosition = CGPoint( + x: pipView.center.x + project(initialVelocity: velocity.x, decelerationRate: decelerationRate), + y: pipView.center.y + project(initialVelocity: velocity.y, decelerationRate: decelerationRate) +) +let nearestCornerPosition = nearestCorner(to: projectedPosition) +``` + +我们可以使用 `UIScrollView` 的减速速率来计算剩下的位置。这很重要,因为它与用户滑动的肌肉记忆相关。如果一个用户知道一个视图需要滚动多远,他们可以使用之前的知识直觉地猜测 PiP 到最终目标需要多大力。 + +这个减速速率也是很宽泛的,让交互感到轻量——只需要一个小小的推动就可以送 PiP 飞到屏幕的另一端。 + +我们可以使用“设计流畅的交互界面”演讲中的投影方法来计算最终的投影位置。 + +``` +/// Distance traveled after decelerating to zero velocity at a constant rate. +func project(initialVelocity: CGFloat, decelerationRate: CGFloat) -> CGFloat { + return (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate) +} +``` + +最后缺失的一块就是基于投影位置找到最近的角落的逻辑。我们可以循环所有角落的位置并且找到一个和投影位置距离最小的角落。 + +``` +func nearestCorner(to point: CGPoint) -> CGPoint { + var minDistance = CGFloat.greatestFiniteMagnitude + var closestPosition = CGPoint.zero + for position in pipPositions { + let distance = point.distance(to: position) + if distance < minDistance { + closestPosition = position + minDistance = distance + } + } + return closestPosition +} +``` + +总结最终的实现:我们使用了 `UIScrollView` 的减速速率来投影 pip 的运动到它最终的位置,并且计算了相对速度传入了 `UISpringTimingParameters`。 + +### 交互界面 #8: 旋转 + +将 PiP 的原理应用到一个旋转动画。 + +![](https://cdn-images-1.medium.com/max/1600/1*jL07YlwI-5skQGkc4W8OeQ.gif) + +#### 核心功能 + +1. 使用投影来遵循手势的速度。 +2. 永远停在一个有效的方向。 + +#### 关键代码 + +这里的代码和前面的 PiP 很像。 我们会使用同样的构造回调,除了将 `nearestCorner` 方法换成 `closestAngle`。 + +``` +func project(...) { ... } + +func relativeVelocity(...) { ... } + +func closestAngle(...) { ... } +``` + +当最终是时候创建一个 `UISpringTimingParameters`,针对初始速度,我们是需要使用一个 `CGVector`,即使我们的旋转只有一个维度。任何情况下,如果动画属性只有一个维度,将 `dx` 值设为期望的速度,将 `dy` 值设为 0。 + +``` +let timingParameters = UISpringTimingParameters( + damping: 0.8, + response: 0.4, + initialVelocity: CGVector(dx: relativeInitialVelocity, dy: 0) +) +``` + +在内部,动画会忽略 `dy` 的值而使用 `dx` 的值来创建时间曲线。 + +### 自己试一试! + +这些交互在真机上更有趣。要自己玩一下这些交互的,这个是 demo 应用,可以在 [GitHub](https://github.com/nathangitter/fluid-interfaces) 上获取到。 + +![](https://cdn-images-1.medium.com/max/2000/1*7gS4SLe571r7RZvpps3X9A.png) + +流畅的交互界面 demo 应用,可以在 GitHub 上获取! + +### 实际应用 + +#### 对于设计师 + +1. 把交互界面考虑成流程的表达中介,而不是一些固定元素的组合。 +2. 在设计流程早期就考虑动画和手势。Sketch 这些排版工具是很好用的,但是并不会像设备一样提供完整的表现。 +3. 跟开发工程师进行原型展示。让有设计思维的开发工程师帮助你开发原型的动画,手势和触觉反馈。 + +#### 对于开发工程师 + +1. 将这些建议应用到你自己开发的自定义交互组件上。考虑如何更有趣的将它们结合到一起。 +2. 告诉你的设计师关于这些新的可能。许多设计师没有意识到 3D touch,触觉反馈,手势和弹簧动画的真正力量。 +3. 与设计师一起演示原型。帮助他们在真机上查看自己的设计,并且创建一些工具帮助他们,来让设计更加的有效率。 + +* * * + +如果你喜欢这篇文章,请留下一些鼓掌。 👏👏👏 + +**你可以点击鼓掌 50 次!** 所以赶快点啊! 😉 + +请将这篇文章在社交媒体上分享给你的 iOS 设计师/iOS 开发工程师朋友。 + +如果你喜欢这种内容,你应该在 Twitter 上关注我。我只发高质量的内容。[twitter.com/nathangitter](https://twitter.com/nathangitter) + +感谢 [David Okun](https://twitter.com/dokun24) 校对。 + +感谢 [Christian Schnorr](https://medium.com/@jenoxx?source=post_page) 和 [David Okun](https://medium.com/@davidokun?source=post_page)。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/building-the-design-ecosystem-of-the-future.md b/TODO1/building-the-design-ecosystem-of-the-future.md new file mode 100644 index 00000000000..e9c6343f80e --- /dev/null +++ b/TODO1/building-the-design-ecosystem-of-the-future.md @@ -0,0 +1,85 @@ +> * 原文地址:[Building the Design Ecosystem of the Future](https://medium.com/@pablostanley/building-the-design-ecosystem-of-the-future-d22b663fed1f) +> * 原文作者:[Pablo Stanley](https://medium.com/@pablostanley?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/building-the-design-ecosystem-of-the-future.md](https://github.com/xitu/gold-miner/blob/master/TODO1/building-the-design-ecosystem-of-the-future.md) +> * 译者:[MeFelixWang](https://github.com/MeFelixWang) +> * 校对者:[StellaBauhinia](https://github.com/StellaBauhinia) + +# 构建未来的设计生态系统 + +## 我为什么要加入 InVision + +很高兴告诉大家我加入了 InVision 团队,在 [Studio](https://www.invisionapp.com/studio) 平台工作。我们的目标是建立起连接最紧密的设计生态系统,使团队能够安然度过设计过程中从构思到开发的每个阶段。 + +* * * + +![](https://cdn-images-1.medium.com/max/800/1*YueL6vogJTWvNi-03_h2Vw.gif) + +### 设计工具的演变 + +在过去几年中,现代设计师的角色以惊人的速度演变着。对此我深有体会。曾经被认为是创造了漂亮像素的人已经转变为应用研究成果、创造浏览体验、增加交互设计以及通过为公司用户发声而加入领导者行列的人。 + +由于这种快速转变,一系列新的责任转移到了设计师身上。技术的进步要求设计人员针对各种内容、设备和语言进行设计。曾经的一个静态像素,现在变成了一个集接收、增长、缩小和动画于一体的动态单元。我们现在将想法转化为概念、草图、线框、屏幕、流程、原型、动画、代码,最后转化为产品。 + +在这种演变的同时,设计工具已经发展到能够支持这些特定需求了。这包括从专门为创建屏幕设计和交互式原型而构建的工具到实现文档响应式布局,实现设计传递以及支持实时协作的插件。 + +令人兴奋的是看到如此多样化的工具集出现,但这也最终导致设计师的不确定性,并且在许多情况下,设计工作流程被破坏了。💔 + +* * * + +![](https://cdn-images-1.medium.com/max/800/1*MkogGsVeqrb3HpzbF4u0Dg.gif) + +### 学习成为一名设计师 + +四年前,我从平面设计师转变为“产品设计师”。老实说,当时我并不知道产品设计师究竟是什么。但它似乎很简单。就是绘制大量的盒子,添加文本,并将所有内容对齐到左边,对吧? + +哦,我错了。产品设计涉及的东西不止于此。有要遵循的流程,有实施的方法,有合作伙伴以及使用的工具。那时候,没有太多的学习资源,我们把设计领导者写的稀缺的 Medium 文章视为福音。🙏🏽 + +在此演变的过程中,越来越多的资源开始出现,使得成为产品设计师的道路变得容易了一些。[Meng To](https://medium.com/@mengto) 的 [Design+Code](https://designcode.io/) 教会了我们如何使用必要的工具,[uxdesign.cc](https://medium.com/@uxdesigncc) 让我们深入了解了什么是用户体验。我决定加入他们并与其他设计师分享我在学习过程中学到的东西。我开始在湾区举办[设计研讨会]((https://www.meetup.com/)),并创建了一个由教程组成的[ YouTube 频道]((https://www.youtube.com/c/sketchtogethertv))。 + +为了学习如何成为更好的产品设计师,我决定教授别人如何成为一名产品设计师。听起来违反直觉,对吧?但这有点道理。当你试图展示某些东西时会迫使自己去深刻理解它,这样你就可以将其分解然后加以解释。 + +分享我所学到的东西让我置身于一个梦幻般的创造者社区。人们渴望知识,由一种令人难以置信的创造力所驱动,他们会公开分享他们学到的东西。我很幸运能成为这个令人难以置信的团队的一员。 + +我的下一步计划是从教导人们如何使用设计工具转变为加入一个为他们创建设计工具的团队。 + +* * * + +![](https://cdn-images-1.medium.com/max/800/1*SenVAHnqgG4uTWEm971hXQ.gif) + +### 为什么选择 InVision + +根据 [Jim Collins](https://www.jimcollins.com/article_topics/articles/good-to-great.html) 的说法,伟大公司的发展征程并不始于他们的目标或是他们的途径,而是开始于他们的团队。他们从让那些愿意付出几倍努力的人上车开始发迹。没有比 InVision 更好的例子了。 + +在过去的几年里,我一直与 InVision 的人保持着联系,在插件、资源方面进行合作,只是对设计和工具感到不安。虽然他们的专业性和沟通能力一直很优秀,但我注意到的最震撼的是他们的人。我觉得遇到的每个人都与他们目前的岗位完美适配,而事实也的确如此。 + +该团队让 InVision 快速成长并保持稳定。他们创造了一些最受欢迎的设计工具,如 [Craft](https://www.invisionapp.com/craft)、[Design System Manager](https://www.invisionapp.com/blog/announcing-invision-design-system-manager/),[Design Better](https://www.designbetter.co/)、[Freehand](https://www.invisionapp.com/feature/freehand)、[Inspect](https://www.invisionapp.com/feature/inspect),以及现在的 [Studio](https://www.invisionapp.com/studio) —— 一个面向现代设计师的屏幕设计工具。加入这样一支优秀的团队将是一次学习经历。我迫不及待地想与一些我最钦佩的最有才华的设计师和聪明的产品人并肩工作。🙌🏽 + +* * * + +![](https://cdn-images-1.medium.com/max/800/1*8Z9ciGXuvpoAPDv_N8zNew.gif) + +### 适合所有人的设计工具 + +如果你用来创建令人惊叹的体验所需的一切都在同一个设计生态系中会怎样?听起来像圣杯,对吧?随着 Studio 平台的推出,这很快就会实现。我正在加入一个与第三方合作的团队,帮助他们提供高质量的体验。我们将帮助为顶级应用程序提供设计资产和资源。我们将与社区联系,了解他们的需求并帮助他们加入我们的平台。 + +我们正在为该平台建立一个由工程师、设计师和营销人员组成的支持团队。此外,我们正在为信仰充值 —— [Design Forward Fund](https://www.invisionapp.com/design-forward-fund) 正在向为 InVision Studio 创建世界级应用和附加组件的人和公司提供 500 万美元的赠款和股权投资。我想像着一个由创造者社区建立和推动的健康生态系统。如果你对可以改进 Studio 内设计工作流程的应用程序有好的想法,我们很希望听到!📣 + +我很高兴能加入 InVision 成为平台的设计主管。我们邀请你、创造者、开发者和企业家加入我们,让梦想成真 —— 构建未来的设计生态系统。 + +* * * + +顺便说一句,此文章中的所有动画都是使用 [Studio](https://www.invisionapp.com/studio) 制作的。你可以[在这里下载](https://www.dropbox.com/sh/nsq4kd2w9v7801h/AAAbNsPy5vLbKOiPIQgFDTDoa?dl=0)。 + +* * * + +感谢 [Courtney M. Sawyer](https://medium.com/@courtneymsawyer)、[Edgar Chaparro](https://medium.com/@Echaparro) 和 [Lindsey Scott](https://medium.com/@lindseylinds) 帮助我解决语法上的恐怖问题。 + +感谢 [Courtney Sawyer](https://medium.com/@csawyer?source=post_page)、[Lindsey Scott](https://medium.com/@lindseylinds?source=post_page) 和 [Edgar Chaparro](https://medium.com/@Echaparro?source=post_page). [No rights reserved](http://creativecommons.org/publicdomain/zero/1.0/)。作者没有[保留任何权利]([No rights reserved](http://creativecommons.org/publicdomain/zero/1.0/))。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/camera-enumeration-on-android.md b/TODO1/camera-enumeration-on-android.md new file mode 100644 index 00000000000..d7e1da7337c --- /dev/null +++ b/TODO1/camera-enumeration-on-android.md @@ -0,0 +1,157 @@ +> * 原文地址:[Camera Enumeration on Android](https://medium.com/androiddevelopers/camera-enumeration-on-android-9a053b910cb5) +> * 原文作者:[Oscar Wahltinez](https://medium.com/@owahltinez?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/camera-enumeration-on-android.md](https://github.com/xitu/gold-miner/blob/master/TODO1/camera-enumeration-on-android.md) +> * 译者:[luoqiuyu](https://github.com/luoqiuyu) +> * 校对者:[hanliuxin5](https://github.com/hanliuxin5) + +# Android 的多摄像头支持 + +从 Android P 开始,添加了对逻辑多摄像头和 USB 摄像头的支持。这对 Android 开发者来说意味着什么? + +### 多摄像头 + +一台设备有多个摄像头没什么新鲜的,但是直到现在,Android 设备仍然最多只有前后两个摄像头。如果你想要打开第一个摄像头,需要进行以下操作: + +``` +val cameraDevice = Camera.open(0) +``` + +但是这些是比较简单的操作。如今多摄像头意味着前置或者后置有两个及两个以上的摄像头。有很多镜头可供选择! + +### Camera2 API + +由于兼容性问题,尽管旧的 Camera API 已经被废弃很长时间,上述的代码仍然有效。但是随着生态系统的发展,需要更先进的相机功能。因此,Android 5.0(Lollipop)引进了 Camera2,适用于 API 21 及以上。用 Camera2 API 来打开第一个存在的摄像头代码如下所示: + +``` +val cameraManager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager +val cameraId = cameraManager.cameraIdList[0] +cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() { + override fun onOpened(device: CameraDevice) { + // Do something with `device` + } + override fun onDisconnected(device: CameraDevice) { + device.close() + } + override fun onError(device: CameraDevice, error: Int) { + onDisconnected(device) + } +}, null) +``` + +### 第一个并不是最好的选择 + +上述代码目前看起来没什么问题。如果我们所需要的只是一个能够打开第一个存在的摄像头的应用程序,那么它在大部分的 Android 手机上都有效。但是考虑到以下场景: + +* 如果设备没有摄像头,那么应用程序会崩溃。这看起来似乎不太可能,但是要知道 Android 运用在各种设备上,包括 Android Things、Android Wear 和 Android TV 等这些有数百万用户的设备。 +* 如果设备至少有一个后置摄像头,它将会映射到列表中的第一个摄像头。但是当应用程序运行在没有后置摄像头的设备上,比如 PixelBooks 或者其他一些 ChromeOS 的笔记本电脑,将会打开唯一一个前置摄像头。 + +那么我们应该怎么做?检查摄像头列表和摄像头特性: + +``` + +val cameraIdList = cameraManager.cameraIdList // may be empty +val characteristics = cameraManager.getCameraCharacteristics(cameraId) +val cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING) +``` + +变量 `cameraLensFacing` 有以下取值: + +* [CameraMetadata.LENS_FACING_FRONT](https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#LENS_FACING_FRONT) +* [CameraMetadata.LENS_FACING_BACK](https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#LENS_FACING_BACK) +* [CameraMetadata.LENS_FACING_EXTERNAL](https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#LENS_FACING_EXTERNAL) + +更多有关摄像头配置的信息,请查看[文档](https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#LENS_FACING). + +### 合理的默认设置 + +根据应用程序的使用情况,我们希望默认打开特定的相机镜头配置(如果可以提供这样的功能)。比如,自拍应用程序很可能想要打开前置摄像头,而一款增强现实类的应用程序应该希望打开后置摄像头。我们可以将这样的一个逻辑包装成一个函数,它可以正确地处理上面提到的情况: + +``` +fun getFirstCameraIdFacing(cameraManager: CameraManager, + facing: Int = CameraMetadata.LENS_FACING_BACK): String? { + val cameraIds = cameraManager.cameraIdList + // Iterate over the list of cameras and return the first one matching desired + // lens-facing configuration + cameraIds.forEach { + val characteristics = cameraManager.getCameraCharacteristics(it) + if (characteristics.get(CameraCharacteristics.LENS_FACING) == facing) { + return it + } + } + // If no camera matched desired orientation, return the first one from the list + return cameraIds.firstOrNull() +} +``` + +### 切换摄像头 + +目前为止,我们讨论了如何基于应用程序的用途选择默认摄像头。很多相机应用程序还为用户提供切换摄像头的功能: + +![](https://cdn-images-1.medium.com/max/800/0*bv1q93VR4XIoazVZ) + +Google 相机应用中切换摄像头按钮 + +要实现这个功能,尝试从[CameraManager.getCameraIdList()](https://developer.android.com/reference/android/hardware/camera2/CameraManager#getCameraIdList%28%29)提供的列表中选择下一个摄像头,但是这并不是个好的方式。因为从 Android P 开始,我们将会看到在同样的情况下更多的设备有多个摄像头,甚至有通过 USB 连接的外部摄像头。如果我们想要提供给用户切换不同摄像头的 UI,建议([按照文档](https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA))是为每个可能的镜头配置选择第一个可用的摄像头。 + +尽管没有一个通用的逻辑可以用来选择下一个摄像头,但是下述代码适用于大部分情况: + +``` +fun filterCameraIdsFacing(cameraIds: Array, cameraManager: CameraManager, + facing: Int): List { + return cameraIds.filter { + val characteristics = cameraManager.getCameraCharacteristics(it) + characteristics.get(CameraCharacteristics.LENS_FACING) == facing + } +} + +fun getNextCameraId(cameraManager: CameraManager, currCameraId: String? = null): String? { + // Get all front, back and external cameras in 3 separate lists + val cameraIds = cameraManager.cameraIdList + val backCameras = filterCameraIdsFacing( + cameraIds, cameraManager, CameraMetadata.LENS_FACING_BACK) + val frontCameras = filterCameraIdsFacing( + cameraIds, cameraManager, CameraMetadata.LENS_FACING_FRONT) + val externalCameras = filterCameraIdsFacing( + cameraIds, cameraManager, CameraMetadata.LENS_FACING_EXTERNAL) + + // The recommended order of iteration is: all external, first back, first front + val allCameras = (externalCameras + listOf( + backCameras.firstOrNull(), frontCameras.firstOrNull())).filterNotNull() + + // Get the index of the currently selected camera in the list + val cameraIndex = allCameras.indexOf(currCameraId) + + // The selected camera may not be on the list, for example it could be an + // external camera that has been removed by the user + return if (cameraIndex == -1) { + // Return the first camera from the list + allCameras.getOrNull(0) + } else { + // Return the next camera from the list, wrap around if necessary + allCameras.getOrNull((cameraIndex + 1) % allCameras.size) + } +} +``` + +这看起来可能有点复杂,但是我们需要考虑到大量的有不同配置的设备。 + +### 兼容性行为 + +对于那些仍然在使用已经废弃的 Camera API 的应用程序,通过 [Camera.getNumberOfCameras()](https://developer.android.com/reference/android/hardware/Camera#getNumberOfCameras%28%29) 得到的摄像头的数量取决于 OEM 的实现。文档上是这样描述的: + +> 如果系统中有逻辑多摄像头,为了保持应用程序的向后兼容性,这个方法仅为每个逻辑摄像头和底层的物理摄像头组公开一个摄像头。使用 camera2 API 去查看所有摄像头。 + +请仔细阅读 [其余文档](https://developer.android.com/reference/android/hardware/Camera.CameraInfo.html#orientation) 获得更多信息。通常来说,类似的建议适用于:使用 [Camera.getCameraInfo()](https://developer.android.com/reference/android/hardware/Camera#getCameraInfo%28int,%20android.hardware.Camera.CameraInfo%29) API 查询所有的摄像头[方向](https://developer.android.com/reference/android/hardware/Camera.CameraInfo.html#orientation), 在用户切换摄像头时,仅仅只为每个可用的方向提供一个摄像头。 + +### 最佳实践 + +Android 运行在许多不同的设备上。你不应该假设你的应用程序总是在有一两个摄像头的传统的手持设备上运行,而是应该为你的应用程序选择最适合的摄像头。如果你不需要特定的摄像头,选择有所需默认配置的第一个摄像头。如果设备连接了外部摄像头,则可以合理的假设用户希望首先看到这些外部摄像头中的第一个。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 + diff --git a/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-1.md b/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-1.md new file mode 100644 index 00000000000..7024befeef3 --- /dev/null +++ b/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-1.md @@ -0,0 +1,88 @@ +> * 原文地址:[(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 1](https://citizenlab.ca/2018/08/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments/) +> * 原文作者:[Jeffrey Knockel](https://citizenlab.ca/author/jknockel/), [Lotus Ruan](https://citizenlab.ca/author/lotus/), [Masashi Crete-Nishihata](https://citizenlab.ca/author/masashi/), and [Ron Deibert](https://citizenlab.ca/author/profd/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-1.md](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-1.md) +> * 译者: +> * 校对者: + +# (CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 1 + +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 1](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-1.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 2](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 3](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 4](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-4.md) + +## Key findings + +* WeChat (the most popular chat app in China) uses two different algorithms to filter images in Moments: an OCR-based one that filters images containing sensitive text and a visual-based one that filters images that are visually similar to those on an image blacklist +* We discovered that the OCR-based algorithm has implementation details common to many OCR algorithms in that it converts images to grayscale and uses blob merging to consolidate characters +* We found that the visual-based algorithm is not based on any machine learning approach that uses high level classification of an image to determine whether it is sensitive or not; however, we found that the algorithm does possess other surprising properties +* For both the OCR- and visual-based algorithms, we uncovered multiple implementation details that informed techniques to evade the filter +* By analyzing and understanding how both the OCR- and visual-based filtering algorithms operate, we are able to discover weaknesses in both algorithms that allow one to upload images perceptually similar to those prohibited but that evade filtering + +## Introduction + +WeChat, (_Weixin_ 微信 in Chinese), is the dominant chat application in China and fourth largest in the world. In February 2018, WeChat reportedly [hit](http://www.scmp.com/tech/apps-gaming/article/2135690/tencents-wechat-hits-1-billion-milestone-lunar-new-year-boosts) one billion monthly active users during the Chinese Lunar New Year. The application is owned and operated by Tencent, one of China’s largest technology companies. In the past two years, WeChat has transformed beyond a commercial social media platform and become part of China’s e-governance initiatives. China’s Ministry of Public Security has been [collaborating](http://www.g.com.cn/tech/21472402/) with Tencent to implement the country’s national identification card system on WeChat. + +Chinese users spend [a third](http://www.economist.com/news/business/21703428-chinas-wechat-shows-way-social-medias-future-wechats-world) of their mobile online time on WeChat and typically return to the app ten times a day or more. Among WeChat’s many functions, the [most frequently used](http://tech.qq.com/a/20160321/030364.htm) feature is [WeChat Moments](http://blog.wechat.com/2015/06/12/tech-tip-your-guide-to-wechat-moments/) (朋友圈), which resembles Facebook’s Timeline and allows users to share images, videos, and articles. Moments has a relatively high level of intimacy, because a user’s updates on Moments can only be seen by friends who have been verified or selected by the user, and a user can only see interactions of people who are already on their WeChat contact list. Because of such perceived privacy, users [reported](https://t.qianzhan.com/caijing/detail/170424-8f9569e1.html) that they frequently share details of their daily life and express personal opinions on Moments. + +Operating a chat application in China requires following laws and regulations on content control and monitoring. Previous Citizen Lab research [uncovered](https://citizenlab.ca/2016/11/wechat-china-censorship-one-app-two-systems/) that WeChat censors content–both text and images–and [demonstrates](https://citizenlab.ca/2017/11/managing-message-censorship-19th-national-communist-party-congress-wechat/) that censorship is heightened around sensitive events. Our previous work [found](https://citizenlab.ca/2017/04/we-cant-chat-709-crackdown-discussions-blocked-on-weibo-and-wechat/) that WeChat uses a hash-based system to filter images in one-to-one and group chats. However, image censorship on Moments is more complex: an image is filtered according to its content in a way that is tolerant to some modifications to the image. + +In this report, we present our findings studying the implementation of image filtering on WeChat Moments. We found two different algorithms that WeChat Moments uses to filter images: an OCR-based one that filters images containing sensitive text and a visual-based one that filters images that are visually similar to those on an image blacklist. We found that the OCR-based algorithm has similarities to many common OCR algorithms in that it converts images to grayscale and uses blob merging to consolidate characters. We also found that the visual-based algorithm is not based on any machine learning approach that uses high level classification of an image to determine whether it is sensitive or not; however, we found that the algorithm does possess other surprising properties. By understanding how both the OCR- and visual-based algorithms work, we are able to discover weaknesses in both algorithms that allow one to upload images perceptually similar to those blacklisted but that evade filtering. + +Through our findings we provide a better understanding of how image filtering is implemented on an application with over one billion users. We hope that our methods can be used as a road map for future research studying image filtering on other platforms. + +## Regulatory Environment in China + +WeChat thrives on the huge user base it has amassed in China, but the Chinese market carries unique challenges. As Chinese social media applications continue to gain popularity, authorities have introduced tighter content controls. + +Any Internet company operating in China is subject to laws and regulations that hold companies legally responsible for content on their platforms. Companies are expected to invest in staff and filtering technologies to moderate content and stay in compliance with government regulations. [Failure to comply](http://www.wsj.com/articles/china-threatens-sina-corp-over-insufficient-censorship-1428743575) can lead to fines or revocation of operating licenses. This environment creates a system of “intermediary liability” where responsibility of content control is pushed down to companies. + +In 2010, China’s State Council Information Office (SCIO) published a major government-issued document on its Internet policy. It includes [a list of prohibited topics](http://www.humanrights.cn/cn/rqlt/rqwj/rqbps/t20100608_605154_1.htm) that are vaguely defined, including “disrupting social order and stability” and “damaging state honor and interests.” Control over the Internet in China has tightened since 2012 following the establishment of the Cyberspace Administration of China (CAC). The CAC has [become](http://www.xinhuanet.com/english/2017-05/03/c_136251798.htm) the new regulator of online news services replacing the SCIO. Chinese President Xi Jinping directly heads the CAC, which signals a dramatic change of the leadership’s attitudes towards Internet management: It is [a matter of national security](http://www.cac.gov.cn/2014-02/27/c_133148354.htm) and that the (CPC) [must](https://news.qq.com/a/20160321/020121.htm) control the Internet just as how it controls traditional media. + +Recent regulations push content control liability down to the user level. In 2014, the CAC introduced regulations informally referred to as the “[WeChat Ten Doctrines](http://paper.people.com.cn/xaq/html/2014-11/01/content_1522983.htm)”, which emphasizes the implementation of a real-name registration system and a prohibition against activities that violate the “seven baselines” of observing laws and regulations, the Socialist system, the national interest, citizens’ lawful rights and interests, public order, social morality, and truthfulness of information. In 2017, the CAC released four major regulations on Internet management, ranging from strengthening real-name registration requirements on [Internet forums](http://www.cac.gov.cn/2017-08/25/c_1121541921.htm) and [online comments](http://www.cac.gov.cn/2017-08/25/c_1121541842.htm) to making individuals who host public accounts and moderate chat groups [liable](http://www.cac.gov.cn/2017-09/07/c_1121624269.htm) for content on the platforms. + +Under the CAC, WeChat, along with other Chinese social media platforms, face much higher penalties than fines if they fail to moderate content. On April 9, 2018, the CAC [ordered](http://www.bjnews.com.cn/finance/2018/04/09/482425.html) all Chinese app stores to remove the four most popular news aggregation applications for weeks because they failed to “maintain the lawful order of information sharing.” A day later, authorities [demanded](http://www.xinhuanet.com/english/2018-04/10/c_137100672.htm) Toutiao, China’s top news aggregation website, and WeChat permanently shut down an account that featured parody and jokes due to “publishing vulgar and improper content.” In the same month, Tencent [suspended](https://www.thepaper.cn/newsDetail_forward_2070142) all video playing functions on WeChat and QQ if the URL of the video was an external link. + +To handle increased government pressures, companies are investing more heavily in filtering technologies and human resources to moderate content. Global Times, a Chinese state media outlet affiliated with People’s Daily, [reported](http://www.globaltimes.cn/content/1098173.shtml) that tech companies are expanding their human censor team and developing artificial intelligence tools to review “trillions of posts, voice messages, photos and videos every day” to make sure their content is in line with laws and regulations. However, authorities still think that “these platforms are not fully performing their duties.” + +In September 2016, Chinese authorities issued [new regulations](http://news.sohu.com/20160920/n468794222.shtml) that explicitly state that messages and comments on social media products like WeChat Moments can be collected and used as “electronic data” in legal proceedings. Martin Lau Chi-ping, a senior manager at Tencent, [said](http://www.scmp.com/tech/social-gadgets/article/2138249/tencent-profit-doubles-strong-smartphone-games-business) the following: + +> “We are very concerned about user data security. It is top of our concerns… In a law enforcement situation, of course, any company has to comply with the regulations and laws within the country.” + +Recently, WeChat users have been arrested for “[insulting police](http://d.youth.cn/sk/201702/t20170224_9165131.htm)” or “[threatening to blow up a government building](http://www.kejilie.com/ifeng/article/veqIby.html)” on Moments, which indicates that the feature may be subject to monitoring by the authorities or the company. + +## Previous Examples of WeChat Image Filtering + +In [previous](https://citizenlab.ca/2017/04/we-cant-chat-709-crackdown-discussions-blocked-on-weibo-and-wechat/) [Citizen Lab](https://citizenlab.ca/2017/04/we-cant-chat-709-crackdown-discussions-blocked-on-weibo-and-wechat/) [research](https://citizenlab.ca/2017/04/we-cant-chat-709-crackdown-discussions-blocked-on-weibo-and-wechat/), we showed that image censorship occurs in both WeChat’s chat function and WeChat Moments. Similar to keyword-based text filtering, censorship of images is only enabled for users with accounts registered to mainland China phone numbers. The filtering is also non-transparent in that no notice is given to a user if the image they have sent is blocked. Censorship of an image is concealed from the user who posted the censored image. + +Figure 1 shows a user with an international account successfully posting a censored image: the image is visible to users with international accounts, but the post is hidden from users with China accounts. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f1-1024x603.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f1.png) + +Figure 1: Image censorship on WeChat moments. A user with a China account (on the left) attempts to send an [image](https://www.chrlawyers.hk/sites/default/files/33.png) related to the 709 Crackdown and is hidden from other China account’s Moments feed (on the right). The image is visible in the user’s own feed as well as to an international account (in the middle). + +In January 2017, we [discovered](https://citizenlab.ca/2017/04/we-cant-chat-709-crackdown-discussions-blocked-on-weibo-and-wechat/) that a number of images related to the “[709 Crackdown](https://chinachange.org/tag/709-arrest-of-lawyers/)” (referring to a crackdown on human rights lawyers and their families in China) are blocked in group chat when using an account registered to a mainland China phone number. The censorship was found when we were performing keyword testing of news articles. When we copied and pasted the image accompanying certain news articles about the 709 Crackdown, the image itself was filtered. In subsequent sample testing, we found 58 images related to the event censored on Moments, most of which are infographics related to the 709 Crackdown, profile sketches of the affected lawyers and their relatives, or images of people holding the slogan “oppose torture, pay attention to Xie Yang” (“反对酷刑,关注谢阳”). See Figure 2 for an example of the image filtering. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f2-1024x906.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f2.png) + +Figure 2: Image censorship in a WeChat group Chat. A user with a China account (on the left) attempts to send an image of the cover of a report on the 709 Crackdown and is blocked. + +We [documented](https://citizenlab.ca/2017/07/analyzing-censorship-of-the-death-of-liu-xiaobo-on-wechat-and-weibo/) similar instances of image censorship on WeChat following the death of Liu Xiaobo in July 2017. The scope of censorship was wider and more intensive compared to the case of the 709 Crackdown. Not only were images censored on WeChat’s group chat and Moments, but we also documented image filtering on WeChat’s one-to-one chat function for the first time (see Figure 3). + +In the wake of Liu Xiaobo’s death, we again found that images blocked in one-to-one chat messages were also blocked on group chat and WeChat Moments. Images blocked in chat functions were always blocked on WeChat Moments. The greater attention to WeChat Moments and group chat may be due to the semi-public nature of the two features. Messages in these functions can reach a larger audience than one-to-one chat, potentially making these features subject to a higher level of scrutiny. However, the blocking of images on one-to-one chat shows an effort to restrict content across semi-public and private chat functions, demonstrating the sensitivity of Liu Xiaobo’s death. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f3-1024x877.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f3.png) + +Figure 3: Image censorship in a WeChat one-to-one chat. A user with an international account (on the left) attempts to send an image of a cartoon of an empty chair symbolizing Nobel Laureate Liu Xiaobo to a China account. The image is not received by the China account. + +In both cases, our tests showed that an image on Moments is filtered according to that image’s content in a way that is tolerant to some modifications to the image; however, until this study it was unclear the algorithms used by WeChat to filter and which kinds of image modifications evaded filtering and which did not. In this report, we conduct a systematic analysis of WeChat’s filtering mechanisms to understand how WeChat implements image filtering. This understanding informs weaknesses in WeChat’s algorithms and techniques for evading WeChat’s image filtering. + +> * 下一篇:[(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 2](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md b/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md new file mode 100644 index 00000000000..30bbaf0e0bb --- /dev/null +++ b/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md @@ -0,0 +1,257 @@ +> * 原文地址:[(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 2](https://citizenlab.ca/2018/08/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments/) +> * 原文作者:[Jeffrey Knockel](https://citizenlab.ca/author/jknockel/), [Lotus Ruan](https://citizenlab.ca/author/lotus/), [Masashi Crete-Nishihata](https://citizenlab.ca/author/masashi/), and [Ron Deibert](https://citizenlab.ca/author/profd/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md) +> * 译者: +> * 校对者: + +# (CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 2 + +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 1](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-1.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 2](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 3](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 4](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-4.md) + +## Analyzing Image Filtering on WeChat + +We measured whether an image is automatically filtered on WeChat Moments by posting the image using an international account and measuring whether it was visible to an account registered to a mainland China phone number after 60 seconds, as we found that images automatically filtered were typically removed in 5 to 30 seconds. To determine how WeChat filters images, we performed modifications to images that were otherwise filtered and measured which modifications evaded filtering. The results of this method revealed multiple implementation details of WeChat’s filtering algorithms, and since our methods understand how WeChat’s filtering algorithm is implemented by analyzing which image modifications evade filtering, they naturally inform strategies to evade the filter. + +We found that WeChat uses two different filtering mechanisms to filter images: an Optical Character Recognition (OCR)-based approach that searches images for sensitive text and a visual-based approach that visually compares an uploaded image against a list of blacklisted images. In this section we describe how testing for and understanding implementation details of both of these filtering methods led to effective evasion techniques. + +### OCR-based filtering + +We found that one approach that Tencent uses to filter sensitive images is to use OCR technology. An OCR algorithm is an algorithm that automatically reads and extracts text from images. OCR technology is commonly used to perform tasks such as automatically converting a scanned document into editable text or to read characters off of a license plate. In this section, we describe how WeChat uses OCR technology to detect sensitive words in images. + +OCR algorithms are complicated to implement and the subject of active research. While reading text comes naturally to most people, computer algorithms have to be specifically programmed and trained in how to do this. OCR algorithms have become increasingly sophisticated over the past decades to be able to effectively read text in an increasingly diverse amount of real-world cases. + +We did not systematically measure how much time WeChat’s OCR algorithm required, but we found that OCR images were not filtered in real time and that after uploading an image containing sensitive text, it would typically be visible to other users between 5 and 30 seconds before it was filtered and removed from others’ views of the Moments feed. + +#### Grayscale conversion + +OCR algorithms may use different strategies to recognize text. However, at a high level, we found that WeChat’s OCR algorithm shares implementation details with other OCR algorithms. As most OCR algorithms do not operate directly on colour images, the first step they take is to convert a colour image to _grayscale_ so that it only consists of black, white, and intermediate shades of gray, as this largely simplifies text recognition since the algorithms only need to operate on one channel. + +| Algorithm | Result | +| --------- | ------ | +| Original | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f4-1.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f4-1.jpeg) | +| Average | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f4-2.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f4-2.png) | +| Lightness | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f4-3.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f4-3.png) | +| Luminosity | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f4-4.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f4-4.png) | + +Table 1: An image with green text and a background colour of gray with the same shade as the text according to the luminosity formula for grayscale and how the text would appear to an OCR algorithm according to three different grayscale algorithms. If the OCR algorithm uses the same grayscale algorithm that we used to determine the intensity of the gray background, then the text effectively disappears to the algorithm. + +To test if WeChat’s OCR filtering algorithm performed a grayscale conversion of colour images, we designed test images that would evade filtering if the OCR algorithm converted uploaded images to grayscale. We designed the images to contain text hidden in the hue of an image in such a way that it is easily legible by a person reading it in colour but such that once it is converted to grayscale, the text disappears and is invisible to the OCR algorithm. If the image evaded censorship, then the OCR algorithm must have converted the image to grayscale (see Table 1 for an illustration). + +As we did not know which formula the OCR algorithm used to convert colour images to grayscale, we evaluated multiple possibilities. Coloured raster images are typically represented digitally as a two-dimensional array of pixels, each pixel having three colour channels (red, green, and blue) of variable intensity. These intensities correspond to the intensities of the red, green, and blue outputs on most electronic displays, one for each cone of the human eye. + +In principle, the gray intensity of a colour pixel could be calculated according to any function of its red, green, and blue intensities. We evaluated three common algorithms: + +1. [average](https://docs.gimp.org/en/gimp-tool-desaturate.html)(_r_, _g_, _b_) = (_r_ + _g_ + _b_) / 3 + +2. [lightness](https://docs.opencv.org/3.4.2/de/d25/imgproc_color_conversions.html)(_r_, _g_, _b_) = (max(_r_, _g_, _b_) + min(_r_, _g_, _b_)) / 2 + +3. [luminosity](https://docs.opencv.org/3.4.2/de/d25/imgproc_color_conversions.html)(_r_, _g_, _b_) = 0.299 _r_ + 0.587 _g_ + 0.114 _b_ + + +To use as comparisons and to validate our technique, in addition to WeChat’s algorithm, we also performed this same analysis on two other OCR algorithms: the [one provided by Tencent’s Cloud API](https://youtu.qq.com/#/char-general), an online API programmers can license from Tencent to perform OCR, and [Tesseract.js](https://tesseract.projectnaptha.com/), a browser-based Javascript implementation of the open source [Tesseract](https://github.com/tesseract-ocr/tesseract) OCR engine. We chose Tencent’s Cloud OCR because we suspected it may share common implementation details with the OCR algorithm WeChat uses for filtering, and we chose Tesseract.js since it was popular and open source. + +Since Tesseract.js was open source, we analyzed it first as it allowed us to look at the source code and directly observe the algorithm used for grayscale to use as a ground truth. To our surprise, the exact algorithm used was not any of the algorithms that we had initially presumed but rather a close approximation of one. Namely, it used a fixed-point approximation of the YCbCr luminosity formula equivalent to the following Javascript expression: + +> (255 * (77 * _r_ + 151 * g + 28 * _b_) + 32768) >> 16 + +where “_a_ >> _b_” denotes shifting _a_ to the right by _b_ bits, an operation mathematically equivalent to ⌊_a_ / 2_b_⌋. Multiplied out, this is approximately equivalent to 0.300 _r_ + 0.590 _g_ + 0.109 _b_. + +Knowing this, we created images containing filtered text in six different colours: red, (1.0, 0, 0); yellow, (1.0, 1.0, 0); green, (0, 1.0, 0); cyan, (0, 1.0, 1.0); blue, (0, 1.0, 1.0); and magenta, (1.0, 0, 1.0); where (_r_, _g_, _b_) is the colour in RGB colourspace and 1.0 is the highest intensity of each channel (see Table 2). These six colours were chosen because they have maximum saturation in the [HSL](https://en.wikipedia.org/wiki/HSL_and_HSV) colourspace and a simple representation in the RGB colourspace. For each colour (_r_, _g_, _b_), we created an image whose text was colour (_r_, _g_, _b_) and whose background was the gray colour (_Y_, _Y_, _Y_), such that _Y_ was equal to the value of the above Javascript expression evaluated as a function of _r_, _g_, and _b_. + +We tested the images on Tesseract.js, and any text we tried putting into the image was completely invisible to the algorithm. We found that no other grayscale algorithm consistently evaded detection on all colours, including the original luminosity formula. While very similar to the formula Tesseract.js used, it, for example, failed for the colours with a blue component, as the coefficient for the blue channel is where the formulas most disagreed. Even this small difference produced text that was detectable. Generalizing from this, we concluded that evading WeChat’s OCR filtering algorithm may prove difficult, as we may have to know the exact grayscale formula used, but once we correctly identified it, we would be able to consistently evade WeChat’s filtering algorithm with any colour of text. + +| | | | | | +| -- | -- | -- | -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f5-1-215x300.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f5-1.jpeg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f5-2-215x300.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f5-2.jpeg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f5-3-215x300.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f5-3.jpeg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f5-4-215x300.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f5-4.jpeg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f5-5-215x300.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f5-5.jpeg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f5-6-215x300.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f5-6.jpeg) | + +> Table 2: Each of the six colours of text tested. Here the background colour of each of the above six images was chosen according to the luminosity of the colour of that image’s text. + +Knowing that our methodology was feasible as long we knew the exact algorithfigurm that WeChat used to convert images to grayscale, we turned our attention to WeChat’s OCR algorithm. Using the same technique as before, we tested each of the three candidate grayscale conversion algorithms to determine if any would consistently evade the filter. We used the same six colours as before. For each test image, we used 25 keyword combinations randomly selected from a set that we already knew to be filtered via OCR filtering (see the Section “Filtered text content analysis” for how we created this set). For each colour (_r_, _g_, _b_), we used the grayscale algorithm being tested _f_ to determine (_Y_, _Y_, _Y_), the colour of the gray background, where _Y_ = _f_(_r_, _g_, _b_). + +After performing this initial test, we found that only when choosing the intensity of the gray background colour as given by the luminosity formula could we consistently evade filtering for every tested colour. The other two algorithms did not evade censorship when testing most colours (see Table 3). + +![](https://i.loli.net/2018/08/15/5b73fb70eac5a.png) + +> Table 3: Results choosing the intensity of the gray background colour according to three different grayscale conversion algorithms for six different colours of text. For the average and lightness algorithms, most of the images were filtered. For the luminosity algorithm, none of them were. + +We repeated this same experiment for Tencent’s online OCR platform. Unlike WeChat’s OCR filtering implementation, where we could only observe whether WeChat’s filter found sensitive text in the image, Tencent’s platform provided us with more information, including whether the OCR algorithm detected any text at all and the exact text detected. Repeating the same procedure as with WeChat, we found that again only choosing gray backgrounds according to each colour’s luminosity would consistently hide all text from Tencent’s online OCR platform. This suggested that Tencent’s OCR platform may share implementation details with WeChat, as both appear to perform grayscale conversion the same way. + +To confirm that using the luminosity formula to choose the text’s background colour consistently evaded WeChat’s OCR filtering, we performed a more extensive test targeting only that algorithm. We selected five lists of 25 randomly chosen keywords we knew to be blocked. We also selected five lists of 10, 5, 2, and 1 keyword(s) chosen at random. For each of these lists, we created six images, one for each of the same six colours we used in the previous experiment. Our results were that all 150 images evaded filtering. These results show that we can consistently evade WeChat’s filtering by hiding coloured text on a gray background chosen by the luminosity of the text and that WeChat’s OCR algorithm uses the same or similar formula for grayscale conversion. + +### Image thresholding + +After converting a coloured image to grayscale, another step in most OCR algorithms is to apply a _thresholding_ algorithm to the grayscale image to convert each pixel, which may be some shade of gray, to either completely black or completely white such that there are no shades of gray in between. This step is often called “binarization” as it creates a binary image where each pixel is either 0 (black) or 1 (white). Like converting an image to grayscale, thresholding further simplifies the image data making it easier to process. + +There are two common approaches to thresholding. One is to apply _global thresholding_. In this approach, a single threshold value is chosen for every pixel in the image, and if a gray pixel is less than that threshold, it is turned black, and if it is at least that threshold, it is turned white. This threshold can be a value fixed in advance, such as 0.5, a value between 0.0 (black) and 1.0 (white), but it is often determined dynamically according to the image’s contents using [Otsu’s method](https://en.wikipedia.org/wiki/Otsu%27s_method), which determines the value of the threshold depending on the distribution of gray values in the image. + +Instead of using the same global threshold for the entire message, another approach is to apply _adaptive thresholding_. Adaptive thresholding is a more sophisticated approach that calculates a separate threshold value for each pixel depending on the values of its nearby pixels. + +To test if WeChat used a global thresholding algorithm such as Otsu’s method, we created a grayscale image with 25 random keyword combinations discovered censored via WeChat’s OCR filtering. The text was light gray (intensity 0.75) on a white (intensity 1.0) background, and the right-hand side was entirely black (intensity 0.0) (see Table 4). This image was designed so that an algorithm such as Otsu’s would pick a threshold such that all of the text would be turned entirely white. + +| | | | +| -- | -- | -- | +| ![](https://citizenlab.ca/wp-content/uploads/2018/08/f6-1-1-300x290.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f6-2-300x290.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f6-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f6-3-300x290.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f6-3.png) | + +> Table 4: Left, the original image. Centre, what the image would look like to an OCR filter performing thresholding using Otsu’s method. Right, what the image might look like to an OCR filter performing thresholding using an adaptive thresholding technique. + +We first tested the image against Tesseract.js and found that this made the text invisible to the OCR algorithm. This suggested that it used a global thresholding algorithm. Upon inspecting the source code, we found that it did use a global thresholding algorithm and that it determined the global threshold using Otsu’s method. This suggests that our technique would successfully evade OCR detection on other platforms using a global thresholding algorithm. + +We then uploaded the image to WeChat and found that the image was filtered and that our strategy did not evade detection. We also uploaded it to Tencent’s Cloud OCR and found that the text was detected there as well. This suggests that these two platforms do not use global thresholding, possibly using either adaptive thresholding or no thresholding at all. + +### Blob merging + +After thresholding, many OCR algorithms perform a step called _blob merging_. After the image has been thresholded, it is now binary, _i.e_., entirely black or white, with no intermediate shades of gray. In order to recognize each character, many OCR algorithms try to determine which blobs in an image correspond to each character. Many characters such as the English letter “i” are made up of unconnected components. In languages such as Chinese, individual characters can be made up of many unconnected components (e.g., 診). OCR algorithms use a variety of algorithms to try to combine these blobs into characters and to evaluate which combinations produce the most recognizable characters. + +| | | +| -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f7-1.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f7-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f7-2.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f7-2.png) | + +> Table 5: Left, the square tiling. Right, the letter tiling. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f8-1-300x96.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f8-1.png) + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f8-2-300x96.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f8-2.png) + +> Figure 4: 法轮功 (Falun Gong) patterned using squares and letters. + +To test whether WeChat’s OCR filtering performed blob merging, we experimented with uploading an image that could be easily read by a person but that would be difficult to read by an algorithm piecing together blobs combinatorially. To do this, we experimented with using two different patterns to fill text instead of using solid colours. Specifically, we used a tiled square pattern and a tiled letter pattern (see Table 5 and and Figure 4), both black on white. Using these patterns causes most characters to be made up of a large amount of disconnected blobs in a way that is easily readable by most people but that is difficult for OCR algorithms performing blob merging. The second pattern that tiles English letters was designed to especially confuse an OCR algorithm by tricking it into finding the letters in the tiles as opposed to the larger characters that they compose. + +To test if blobs of this type affected the OCR algorithm, we created a series of test images. We selected five lists of 25 randomly chosen keyword combinations we knew to be blocked. Randomly sampling from blocked keyword combinations, we also created five lists for each of four additional list lengths, 10, 5, 2, and 1. For each of these lists, we created two images: one with the text patterned in squares and another patterned in letters. + +For images with a large number of keywords, we decrease the font size to ensure that the generated images fit within a 1000×1000 pixel image. This is to ensure that images did not become too large and to ensure that they would not be downscaled, as we had previously experienced some images that were larger than 1000×1000 downscaled by WeChat, although we did not confirm that 1000×1000 was the exact cutoff. We did this to control for any effects that downscaling the images could have on our experiment such as by blurring the text. + +![](https://i.loli.net/2018/08/15/5b743caeac96d.png) + +> Table 6: The number of images that evaded filtering for each test. Letter-patterned text evaded all tests, but square-patterned did not evade two of the tests with the largest number of sensitive keywords. + +Our results showed that square-patterned text evaded filtering in 92% of our tests, and letter-patterned text evaded filtering in 100% of our tests (see Table 6 for a breakdown). The reason for the two failures of squares in the 25 keyword case is not clear, but there are two possibilities. One is that the higher number of keywords per image increased the probability that at least one of those keywords would not evade filtering. The second is that images with a larger number of keywords used a smaller font size, and so there were fewer blobs per character, reducing the effectiveness of the evasion strategy. Letters were more effective in evading filtering and were perfect in our testing. This may be because of the previously suggested hypothesis that the OCR filter would be distracted by the letters in the pattern and thus miss the characters of which they collectively form, but it may also be because the letters are less dense insofar as they have fewer black pixels per white. Overall, these results suggest that WeChat’s OCR filtering algorithm considers blobs when performing text recognition and that splitting characters into blobs is an effective evasion strategy. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f9.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f9.png) + +> Figure 5: Output of Tencent Cloud OCR when uploading the Falun Gong image from Figure 4. The filter finds the constituent letters making up the characters, as well as other erroneous symbols, but not the characters 法轮功 themselves. + +For comparison, we also tested these same images on Tesseract.js and Tencent’s Cloud OCR. For the former, the images always evaded detection, whereas in the latter the patterns often failed to evade detection, especially in larger images. We suspect that blobs are also important to Tencent’s Cloud OCR, but, as we found that these patterns did not evade detection only in larger images, we suspect that this is due to some processing such as downscaling that is being performed by Tencent’s Cloud OCR only on larger images. We predict that by increasing the distance between the blobs in larger images, we could once again evade filtering on Tencent’s Cloud OCR. + +### Character classification + +Most OCR algorithms ultimately determine which characters exist in an image by performing character classification based on different features extracted from the original image such as blobs, edges, lines, or pixels. The classification is often done using machine learning methods. For instance, Tencent’s Cloud OCR [advertises](https://cloud.tencent.com/product/ocr) that it uses deep learning, and Tesseract [also uses machine learning methods](https://github.com/tesseract-ocr/docs/blob/master/tesseracticdar2007.pdf) to classify each individual character. + +In cases where deep neural networks are used to classify images, researchers have developed ways of adversarially creating images that appear to people as one thing but that trick the neural networks into classifying the image under an unrelated category; however, this work does not typically focus on OCR-related networks. [One recent work](https://arxiv.org/abs/1802.05385) was able to trick Tesseract into misreading text; however, it unfortunately required full _white-box_ assumptions (i.e., it was done with the knowledge of all Tesseract source code and its trained machine learning models) and so their methods could not be used to create adversarial inputs for a _black-box_ OCR filter such as WeChat’s where we do not have access to its source code or its trained machine learning models. + +Outside of the context of OCR, researchers have developed black-box methods to estimate gradients of neural networks when one does not have direct access to them. This allows one to still trigger a misclassification by the neural network by uploading an adversarial image that appears to people as one thing but is classified as another unrelated thing by the neural network. While this would seem like an additional way to circumvent OCR filtering, the threat models assumed by even these black-box methods are often unrealistic. [A recent work capable of working under the most restrictive assumptions](https://arxiv.org/abs/1804.08598) assumes that an attacker has access to not only the network’s classifications, but the top _n_ classifications and their corresponding scores. This is unfortunately still too restrictive for WeChat’s OCR, as our only signal from WeChat’s filtering is a single bit–whether the image was filtered or not. Even Tencent’s Cloud OCR, which may share implementation details with WeChat’s OCR filtering, provides a classification score for the top result but does not provide any other scores for any other potential classifications, and so the threat model is still too restrictive. + +### Filtered text content analysis + +In this section we look at the nature of the text content triggering WeChat’s OCR-based filtering. Our previous research [found](https://citizenlab.ca/2016/11/wechat-china-censorship-one-app-two-systems/) that WeChat filters text chat using blacklisted keyword combinations consisting of one (_e.g.,_ “刘晓波”) or more (_e.g.,_ “六四 [+] 学生 [+] 民主运动”) keyword components, where if a message contains all components of any blacklisted keyword combination then it is filtered. We found that to implement its OCR-based image filtering WeChat also maintains a blacklist of sensitive keyword combinations but that this blacklist is different from the one used to filter text chat. Only if an image contains all components of any keyword combination blacklisted from images will it be filtered. + +To help understand the scope and target of OCR-based image filtering on WeChat, in April 2018, we tested images containing keyword combinations from a sample list. This sample list was created using keyword combinations [previously found blocked](https://citizenlab.ca/2017/11/managing-message-censorship-19th-national-communist-party-congress-wechat/) in WeChat’s group text chat between September 22, 2017 and March 16, 2018, excluding any keywords that were no longer blocked in group text chat at the time of our testing. These results provide a general overview of the overlap between text chat censorship and OCR-based image censorship on WeChat. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f10-MK-1024x648.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f10-MK.png) + +> Figure 6: The percentage of tested keywords blocked and not blocked on WeChat’s OCR-based image censorship by category. + +Out of the 876 keyword combinations tested, we found 309 trigger OCR-based image censorship on WeChat Moments. In [previous research](https://citizenlab.ca/2016/11/wechat-china-censorship-one-app-two-systems/), we performed content analysis of keywords by manually grouping them into content categories based on contextual information. Using similar methods, we present content analysis to provide a high-level description of our keyword and image samples. Figure 6 shows the percentage of tested keyword combinations blocked and not blocked on WeChat’s OCR-based image censorship by category. + +**Government Criticism** + +We found that 59 out of 194 tested keyword combinations thematically related to government criticism triggered OCR-based image filtering. These include keyword combinations criticizing government officials and policy (see Table 7). + +![](https://i.loli.net/2018/08/15/5b743d1ecd146.png) + +> Table 7: Examples of keyword combination related to government criticism that triggered OCR-based image filtering. + +The first two examples make references to China’s censorship policies: Lu Wei, the former head of the Cyberspace Administration of China (the country’s top-level Internet management office), is often [described](https://www.straitstimes.com/asia/east-asia/chinas-former-internet-czar-lu-wei-charged-with-taking-bribes) as China’s “Internet czar”; and in 2017, Freedom House [ranked](https://freedomhouse.org/report/freedom-net/2017/china) China as “the world’s worst abuser of Internet freedom” for the third year in a row. The keyword ( “盗国贼” kleptocrat) is an derogatory reference to [Wang Qishan](https://www.scmp.com/news/china/diplomacy-defence/article/2146263/chinese-vice-president-wang-qishan-given-key-foreign), the current Vice President of China whose family [has](https://www.bbc.com/zhongwen/simp/chinese-news-40345328) allegedly benefited from ties to Chinese conglomerate HNA group. + +| | | +| -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f11-1-169x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f11-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f11-2-169x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f11-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f11-3-169x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f11-3.png) | + +> Table 8: An example of OCR-based image filtering in WeChat Moments. A user with an international account (on the right) posts an image containing the text “互联网自由 全世界 报告 最差” (Internet freedom [+] globally [+] report [+] the worst), which is hidden from the Moments’ feed of user with China account (in the middle). The image is visible in the user’s own feed as well as to another international account (on the left). + +**Party Policies and Ideology** + +In [previous work](https://citizenlab.ca/2017/11/managing-message-censorship-19th-national-communist-party-congress-wechat/), we found that even keyword combinations that were non-critical and that only made neutral references to CPC ideologies and central policy were blocked on WeChat. Eighteen out of 84 tested keywords made neutral references to CPC policies and triggered OCR-based image filtering (see Table 9). + +![](https://i.loli.net/2018/08/15/5b743d8cd4930.png) + +> Table 9: Examples of keyword combinations related to Party policies and ideology that triggered OCR-based image filtering. + +**Social Activism** + +Forty-eight keyword combinations in our sample set include references to protest, petition, or activist groups. We found that 21 of them triggered OCR-based image filtering (see Table 10). + +![](https://i.loli.net/2018/08/15/5b743f064f251.png) + +> Table 10: Examples of keyword combinations related to social activism that triggered OCR-based image filtering. + +**Leadership** + +[Past work](https://www.usenix.org/system/files/conference/foci17/foci17-paper-knockel.pdf)[ shows](https://citizenlab.ca/2016/11/wechat-china-censorship-one-app-two-systems/) that direct or indirect references to the name of a Party or government leader often trigger censorship. We found that among the 113 tested keyword combinations that made general references to government leadership, 21 triggered OCR-based image filtering (see Table 11). For example, we found that both the simplified and traditional Chinese version of “Premier Wang Qishan” triggered OCR-based image filtering. Around the 19th National Communist Party Congress in late 2017, there was widespread [speculation](https://www.bbc.com/zhongwen/simp/press-review-40715318) centering on whether Wang Qishan, a close ally of Xi, would assume the role of Chinese premier. + +![](https://i.loli.net/2018/08/15/5b743f42a9b5f.png) + +> Table 11: Examples of keyword combination related to leadership that triggered OCR-based image filtering. + +**Xi Jinping** + +Censorship related to President Xi Jinping has [increased](https://citizenlab.ca/2017/11/managing-message-censorship-19th-national-communist-party-congress-wechat/) in recent years on Chinese social media. The focus of censorship related to Xi warrants testing it as a single category. Among the 258 Xi Jinping-related keyword tested, 101 triggered OCR-based image censorship (see Table 12). Keywords included memes that subtly reference Xi (such as likening his appearance to Winnie the Pooh), and derogatory homonyms (吸精瓶, which literally means Semen sucking bottle). + +![](https://i.loli.net/2018/08/15/5b743f74587f8.png) + +> Table 12: Examples of keyword combinations related to Xi Jinping that triggered OCR-based image filtering. + +**Power Struggle** + +Content in this category is thematically linked to power struggles or personnel transition within the CPC. Smooth power transition has been a challenge through the CPC’s history. Rather than institutionalizing the process, personnel transitions are often influenced by [patronage networks](https://www.brookings.edu/articles/the-powerful-factions-among-chinas-rulers/) based on family ties, personal contacts, and where individuals work. We found that 40 of the 64 tested keywords in this content category triggered OCR-based image filtering (see Table 13). + +![](https://i.loli.net/2018/08/15/5b743f9b719b2.png) + +> Table 13: Examples of keyword combinations related to power struggle that triggered OCR-based image filtering. + +**International Relations** + +Forty-four keywords in our sample set include references to China’s relations with other countries. We found 18 of them triggered OCR-based image filtering (see Table 14). + +![](https://i.loli.net/2018/08/15/5b743fc6d53ee.png) + +> Table 14: Examples of keyword combinations related to international relations that triggered OCR-based image filtering. + +**Ethnic Groups and Disputed Territories** + +Content in this category includes references to Hong Kong, Taiwan, or ethnic groups such as Tibetans and Uyghurs. These issues have long been contested and are [frequently censored](https://netalert.me/harmonized-histories.html) [topics](https://citizenlab.ca/2017/01/tibetans-blocked-from-kalachakra-at-borders-and-on-wechat/) in mainland China. We found 15 out of 47 keywords tested in this category triggered OCR-based image censorship (see Table 15). + +![](https://i.loli.net/2018/08/15/5b743ffa6ce96.png) + +> Table 15: Examples of keyword combinations related to ethnic groups and disputed territories that triggered OCR-based image filtering. + +**Events** + +Content in this category references specific events such as the June 4, 1989 Tiananmen Square protest. We found that 14 of the 17 tested event-related keywords triggered OCR-based image filtering (see Table 16). Thirteen of the keywords were related to the Tiananmen Square protests. We also found references to more obscure events censored such as the [suicide](http://www.chinadaily.com.cn/china/2017-09/12/content_31905967.htm) of WePhone app founder Sun Xiangmao, who said his ex-wife Zhai Xinxin had blackmailed him into paying her 10 million RMB. Although the event attracted wide public attention and online [debates](https://www.whatsonweibo.com/questions-surrounding-tragic-suicide-wephone-founder-su-xiangmo/), it is unclear why the keyword was blocked. + +![](https://i.loli.net/2018/08/15/5b74406888ce5.png) + +> Table 16: Examples of keyword combinations related to events that triggered OCR-based image filtering. + +**Foreign Media** + +The Chinese government maintains tight control over news media, especially those owned and operated by [foreign organizations](https://foreignpolicy.com/2016/03/04/china-won-war-western-media-censorship-propaganda-communist-party/?wp_login_redirect=0). We found one out of three blocked text-based images that include names of news organizations that operate outside of China and publish critical reports on political issues (see Table 17). + +![](https://i.loli.net/2018/08/15/5b7440923c6b3.png) + +> Table 17: Example of keyword combinations related to foreign media that triggered OCR-based image filtering. + +> * 上一篇:[(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 1](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-1.md) +> +> * 下一篇:[(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 3](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md) + + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md b/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md new file mode 100644 index 00000000000..039254d1513 --- /dev/null +++ b/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md @@ -0,0 +1,284 @@ +> * 原文地址:[(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 3](https://citizenlab.ca/2018/08/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments/) +> * 原文作者:[Jeffrey Knockel](https://citizenlab.ca/author/jknockel/), [Lotus Ruan](https://citizenlab.ca/author/lotus/), [Masashi Crete-Nishihata](https://citizenlab.ca/author/masashi/), and [Ron Deibert](https://citizenlab.ca/author/profd/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md) +> * 译者: +> * 校对者: + +# (CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 3 + +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 1](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-1.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 2](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 3](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 4](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-4.md) + +## Visual-based filtering + +In the previous section we analyzed how WeChat filters images containing sensitive text. In this section we analyze the other mechanism we found that WeChat uses to filter images: a visual-based algorithm that can filter images that do not necessarily contain text. This algorithm works by comparing an image’s similarity to those on a list of blacklisted images. To test different hypotheses concerning how the filter operated, we performed modifications to sensitive images that were normally censored and observed which types of modifications evaded the filtering and which did not, allowing us to evaluate whether our hypotheses were consistent with our observed filtering. + +Like with WeChat’s OCR-based filtering, we did not systematically measure how much time WeChat’s visual-based filtering required. However, we found that after uploading a filtered image that does not contain sensitive text, it would typically be visible to other users for only up to 10 seconds before it was filtered and removed from others’ views of the feed. This may be because they were either filtered before they were made visible or after they were visible but before we could refresh the feed to view them. Since this algorithm typically takes less time than the OCR-based one, this algorithm would appear to be less computationally expensive than the one used for OCR filtering. + +### Grayscale conversion + +We performed an analysis of their grayscale conversion algorithm similar to the one we performed when evaluating WeChat’s OCR filtering to determine which grayscale conversion algorithm, if any, the blacklist-based image filtering was using. Like when testing the OCR filtering algorithm, we designed experiments such that if the blacklisted image filtering algorithm uses the same grayscale algorithm that we used to determine the intensity of gray in the image, then the image effectively disappears to the algorithm and evades filtering. + +| | | +| -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f12-1-255x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f12-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f12-2-255x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f12-2.png) | + +> Table 18: Left, the original sensitive image. Right, the image thresholded to black and white, which is still filtered. + +We chose an image originally containing a small number of colours and that we verified would still be filtered after converting to black and white (see Table 18). We used the black and white image as a basis for our grayscale conversion tests, where for each image we would replace white pixels with the colour to test and black with that colour’s shade of gray according to the grayscale conversion algorithm we are testing (see Table 19). As before, we tested three different grayscale conversion algorithms: Average, Lightness, and Luminosity (see the section on OCR filtering for their definitions). + +| | | | | | | +| -- | -- | -- | -- | -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f13-1-150x150.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f13-1.jpeg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f13-2-150x150.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f13-2.jpeg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f13-3-150x150.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f13-3.jpeg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f13-4-150x150.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f13-4.jpeg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f13-5-150x150.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f13-5.jpeg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f13-6-150x150.jpeg)](https://citizenlab.ca/wp-content/uploads/2018/08/f13-6.jpeg) | + +> Table 19: Each of the six colours tested. Here the intensity of the gray background of each image was chosen according to the luminosity of the foreground colour. + +![](https://i.loli.net/2018/08/15/5b744160327c4.png) + +> Table 20: Results choosing the intensity of the gray background according to three different grayscale conversion algorithms for six different colours of text. Only when using the luminosity algorithm were no images were filtered. + +We found that the results are largely consistent with those previously found when testing the OCR algorithm suggesting that both the OCR-based and the visual-based algorithms use the same grayscale conversion algorithm. In both cases, most images created according to the Average and Lightness algorithms were filtered, whereas all images created according to the Luminosity algorithms evaded filtering (see Table 20). This suggests that WeChat’s blacklisted image filtering, like their OCR-based image filter, converts images to grayscale and does so using the Luminosity formula. + +### Cryptographic hashes + +A simple way to compare whether two images are the same is by either hashing their encoded file contents or the values of their pixels using a cryptographic hash such as [MD5](https://en.wikipedia.org/wiki/MD5). While this makes image comparison very efficient, this method is not tolerant of even small changes in values to pixels, as cryptographic hashes are designed such that small changes to the hashed content result in large changes to the hash. This inflexibility is incompatible with the kinds of image modifications that we found the filter tolerant of throughout this report. + +### Machine learning classification + +We discussed in the OCR section about how machine learning methods, including neural networks and deep learning, can be used to identify the text in an image. In this case, the machine learning algorithms classify each character into a category, where the different categories might be _a_, _b_, _c_, …, _1_, _2_, _3_, …, as well as Chinese characters, punctuation, etc. However, machine learning can also be used to classify more general purposes images into high level categories based on their content such as “cat” or “dog.” For purposes of image filtering, many social media platforms use machine learning [to classify whether content is pornography](https://yahooeng.tumblr.com/post/151148689421/open-sourcing-a-deep-learning-solution-for). + +If Tencent chose to use a machine learning classification approach, they could attempt to train a network to recognize whether an image may lead to government reprimands. However, training a network against such a nebulous and nuanced category would be rather difficult considering the vagueness and fluidity of Chinese content regulations. Instead, they might identify certain more well-defined categories of images that would be potentially sensitive, such as images of Falun Gong practitioners or of deceased Chinese dissident Liu Xiaobo, and then classify whether images belong to these sensitive categories. + +| | | | | +| -- | -- | -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f14-1-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f14-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f14-2-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f14-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f14-3-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f14-3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f14-4-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f14-4.png) | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f14-5-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f14-5.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f14-6-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f14-6.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f14-7-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f14-7.png) | + +> Table 21: Tencent’s sample images for each of the seven categories its classifier detects. + +Tencent advertises _YouTu_, an API developed by the company’s machine learning research team, for providing “artificial intelligence-backed [solution](https://youtu.qq.com/#/solution-safe) to online content review.” Tencent claims that the API is equipped with image recognition functions, including OCR and facial recognition technologies, and is able to detect user generated images that contain “pornographic, terrorist, political, and spammy content”. In the case of [terrorism-related images](https://youtu.qq.com/#/img-terror-identity), YouTu provides specific categories of what it considers sensitive: militants (武装分子), controlled knives and tools (管制刀具), guns (枪支), bloody scenes (血腥), fire (火灾), public congregations (人群聚集), and extremism and religious symbols or flags (极端主义和宗教标志、旗帜) (see Table 21). To test if WeChat uses this system, we tested the sample images that Tencent provided on their website advertising the API (see Table 22). We found none of them to be censored. + +| | | +| -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f15-1.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f15-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f15-2.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f15-2.png) | + +> Table 22: Left, the original image of knives, which Tencent’s Cloud API classifies as “knives” with 98% confidence. Right, the mirrored image of knives, which the Cloud API classifies as “knives” with 99% confidence. Mirroring the image had virtually no effect on the Tencent classifier’s confidence of the image’s category and no effect on the ultimate classification. + +After these results, we wanted to test more broadly as to whether they may be using any sort of machine learning classification system at all or whether they were maintaining a blacklist of specific sensitive images. To do this, we performed a test that would modify images that we knew to be filtered in a way that semantically preserved their content while nevertheless largely moving around their pixels. We decided to test _mirroring_ (i.e., horizontally flipping) images. First, as a control case, we submitted the mirrored images of each of the seven categories from Table 21. We found that, as we expected, mirroring the images did not affect what they were classified as by Tencent’s Cloud API (see Table 22 for an example). + +| | | | | | | | +| -- | -- | -- | -- | -- | -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-1-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-2-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-3-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-4-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-4.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-5-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-5.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-6-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-6.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-7-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-7.png)| +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-8-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-8.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-9-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-9.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-10-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-10.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-11-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-11.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-12-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-12.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-13-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-13.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f16-14-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f16-14.png) | + +> Table 23: The first 14 of the 15 images we tested mirroring. + +| | | +| -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f17-225x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f17.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f17-mirrored-225x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f17-mirrored.png) | + +> Table 24: Left, the 15th image we tested, an image of Liu Xiaobo. Right, the mirrored image. Although the images technically have pixels in different positions, they both show a depiction of the deceased Liu Xiaobo, but only the original image on the left is filtered. + +Next, we mirrored 15 images that we found to be filtered using data from [previous](https://citizenlab.ca/2017/04/we-cant-chat-709-crackdown-discussions-blocked-on-weibo-and-wechat/) [reports](https://citizenlab.ca/2017/07/analyzing-censorship-of-the-death-of-liu-xiaobo-on-wechat-and-weibo/) and other contextual knowledge. Upon uploading them to WeChat, we found that none of them were filtered after mirroring. Other semantic-preserving operations such as cropping the whitespace from the image in Table 24 also allowed the image to evade filtering. These results, as well as additional results described further below, strongly suggest that no sort of high level machine learning classification system is being used to trigger the observed filtering on WeChat. Rather, these results suggest that there is a specific blacklist of images being maintained by Tencent that each image uploaded is somehow being compared against using some kind of similarity metric. This type of approach may be desirable as it easily allows Tencent to censor specific sensitive images that may be trending or that they are otherwise asked to censor by a government official regardless of the topic or category of the image. Note that this does not rule out the use of machine learning methods all together. Rather, this rules out any sort of filtering based on high level image classification. + +### Invariant features + +There are different ways of describing images in a way that are invariant to certain transformations such as translation (_e.g._, moving the position of an image on a blank canvas), scale (_e.g_., downscaling or upscaling an image preserving its aspect ratio), and rotation (e.g., turning an image 90 degrees onto its side). For instance, [Hu moments](https://en.wikipedia.org/wiki/Image_moment#Rotation_invariants) are a way of describing an image using seven numbers that are invariant to translation, scale, and rotation. For example, you could rotate an image and make it twice as large, and if you calculated the resulting image’s Hu moments they would be the nearly the same as those of the original (for infinite resolution images they are exactly the same, but for discrete images with finite resolution, the numbers would be approximately equal). [Zernike moments](https://ieeexplore.ieee.org/document/55109/) are similar to Hu moments except that they are designed such that one can calculate an arbitrarily high number of them in order to generate an increasingly detailed description of the image. + +Hu and Zernike moments are called global features because each moment describes an entire image; however, there also exist local feature descriptors to only describe a specific point in an image. By using local feature descriptors to describe an image, one can more precisely describe the relevant parts of an image. The locations of these descriptors are often chosen through a separate keypoint-finding process designed to find the most “interesting” points in the image. Popular descriptors such as [SIFT](https://en.wikipedia.org/wiki/Scale-invariant_feature_transform) descriptors are, like Hu and Zernike moments, invariant to scale and rotation. + +To test if WeChat uses global or local features with these invariance properties to compare similarity between images, we tested if the WeChat image filter is invariant to the same properties as these features. We trivially knew that WeChat’s algorithm was invariant to scale as we had quickly found that any size of an image not trivially small would be filtered (in fact there was no reason to think that we were ever uploading an image the same size as the one on the blacklist). To test rotation, we rotated each image by 90 degrees counterclockwise. After testing these rotations on the same 15 sensitive images we tested in the previous section, we found that all consistently evaded filtering. This suggests that whatever similarity metric WeChat uses to compare uploaded images to those in a blacklist is not invariant to rotation. + +### Intensity-based similarity + +Another way to compare similarity between two images is to treat each as a one-dimensional array of pixel intensities and then compare these arrays using a similarity metric. Here we investigate three intensity-based similarity metrics: the mean absolute difference, statistical correlation, and mutual information. + +#### Mean absolute difference + +One intensity-based method to compare similarity between two images is to compare the mean absolute difference of their pixel intensities. This is to say, for each pixel, subtract from its intensity the intensity of the corresponding pixel in the other image and then take its absolute value. The average of all of these absolute differences is the images’ mean absolute difference. Thus, values close to zero represent images that are very similar. + +To determine if this was the similarity metric that WeChat used to compare images, we performed the following experiment. We _inverted_, or took the negative, of each of our 15 images and measured whether the inverted image was still filtered. This transformation was chosen since it would produce images that visually resemble the original image while producing a mean absolute difference similar to that of an unrelated image. + +| | | | | +| -- | -- | -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f18-1-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f18-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f18-2-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f18-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f18-3-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f18-3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f18-4-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f18-4.png) | + +> Table 25: Out of 15 images, these four evaded filtering. Compared to their non-inverted selves, they had mean absolute differences of 0.87, 0.66, 0.85, and 0.67, respectively. + +We found that out of the 15 images we tested, only four of their inverted forms evaded filtering (see Table 25). The evaded images had an average mean absolute difference of 0.76, whereas the filtered ones had an average of 0.72. Among the inverted images that evaded filtering, the lowest mean absolute difference was 0.55, whereas among the inverted images that were filtered, the highest mean absolute difference was 0.97. This suggests that image modifications with low mean absolute differences can still evade filtering, whereas images with high mean absolute differences can still be filtered. Thus it would seem that mean absolute difference is not the similarity metric being used. + +#### Statistical correlation + +Another intensity-based approach to comparing images is to calculate their statistical correlation. The correlation between both images is the statistical correlation between each of their pixel intensities. The result is a value between -1 and 1, where a value close to 1 signifies that the brighter pixels in one image tend to be the brighter pixels in the other, and a value close to 0 signifies little correlation. Values close to -1 signify that the brighter pixels in one image tend to be the darker pixels in the other (and vice versa), such as if one image is the other with its colours inverted. As this would suggest that the images are related, images with a correlation close to both -1 and 1 can be considered similar. + +| | | +| -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f19-1.jpg)](https://citizenlab.ca/wp-content/uploads/2018/08/f19-1.jpg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f19-2.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f19-2.png) | + +> Figure 7: Left, the original image. Right, the same image with the colours on the left half on the image inverted. When converted to grayscale, these images have a nearly zero correlation of -0.02, yet the image on the right is still filtered. + +To test whether image correlation is used to compare images, we created a specially crafted image by inverting the colours of the left half on the image while leaving the colours in the right half unchanged (see Figure 7). By doing this, the left halves would have strong negative correlation, and the right halves would have strong positive correlation, and so the total correlation would be around zero. We found that our image created this way had a near zero correlation of -0.02, yet it was still filtered. This suggests that statistical correlation between images is not used to determine their similarity. + +#### Mutual information + +A third intensity-based approach to compare images is to calculate their mutual information. A measurement of mutual information called normalized mutual information (NMI) may be used to constrain the result to be between 0 and 1. Intuitively, mutual information between two images is the amount of information that knowing the colour of a pixel in one image gives you about knowing the colour of a pixel in the same position in other image (or vice versa). + +Similar to when we were testing image correlation, we wanted to produce an image that has near-zero NMI but is still filtered. We found that the image that is half-inverted unfortunately still has a NMI of 0.89, a very high number. This is because knowing the colour of a pixel in the original image still gives us a lot of information about what colour it will be in the modified one, as in this case it will be either the original colour or its inverse. In this metric, the distance between colours is never considered, and so there is no longer a cancelling out effect. + +| | | +| -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f20-1-255x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f20-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f20-2-255x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f20-2.png) | + +> Figure 8: Left, the original image in grayscale. Right, the image converted to black and white and inverted to the right of the flag transition. Although these images have a near-zero NMI of 0.03, the image on the right is still filtered. + +To create an image with low NMI compared to its original, we took a sensitive image that used colours fairly symmetrically and converted it to black and white. We then inverted nearly half of the image (see Figure 8). Since the modified image is in black and white, knowing the colour of a pixel in the original now gives you little information about the colour in the other image, as most colours appear equally on both sides of the image; it is just as likely to be either its colour or its inverse, and since there are only two colours used in the modified image, knowing that does not provide any information. + +Note that in our modified image, we did not split the original image with its inverse image exactly down the middle as we did with the Tank Man photo in Figure 7. We had originally tried this but found that it was not filtered. However, when we split it along the natural border between the Hong Kong and Chinese flags in the image, then it was filtered. This result suggested that edges may be an important feature in WeChat’s algorithm. + +#### Histogram similarity + +Another general method of comparing images is according to normalized histograms of their pixel intensities. Binning would typically be used to allow for similar colours to fall into the same bins. A simple way of comparing images is to take a histogram over each image in its entirety. This similarity metric would reveal if two images use similar colours, but since it does not consider the locations of any of the pixels, it lacks descriptive power when used as a similarity metric. As such, it would be invariant to rotation. However, as we saw in the earlier section, Tencent’s filter is not. This algorithm would also be invariant to non-uniform changes in scale, (i.e., changes in aspect ratio). To test if WeChat’s filtering algorithm is, we changed the aspect ratio of the same 15 sensitive images we tested in the previous section, in one set reducing each image’s height to 70%, and in another reducing each image’s width to 70%. We found that in all but one case (an image that had its width reduced by 70%) the new images evaded filtering. This further suggests that WeChat is not using a simple histogram approach. + +A histogram-based approach would also be sensitive to changes in image brightness and inverting an image’s colours. However, we experimented with increasing images brightness by 0.4 (i.e., increasing each of the R, G, and B values for each pixel by 0.4) or inverting their colours. In each case, the image was still filtered. Since a histogram-based metric would be sensitive to these transformations, this is unlikely to be Tencent’s similarity metric. + +One enhancement to the histogram approach is to use a _spatial histogram_, where an image is divided into regions and a separate histogram is counted for each region. This would allow the histogram to account for the spatial qualities of each image. We found reference to Tencent using such an algorithm in a June 2016 document on Intel’s website describing optimizations made to Tencent’s image censorship system. The document is titled “[Tencent Optimizes an Illegal Image Filtering System](https://software.intel.com/en-us/blogs/2016/06/27/tencent-optimizes-an-illegal-image-filtering-system).” The document describes how Intel worked with Tencent to use [SIMD](https://en.wikipedia.org/wiki/SIMD) technology, namely Intel’s [SSE](https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions) instruction set to improve the performance of Tencent’s filtering algorithm used on WeChat and other platforms to censor images. The document does not reveal the exact algorithm used. However, it does include a high level outline and some code samples that reveal characteristics of the algorithm. + +The high level view of the algorithm described in the document is as follows. + +1. First, an uploaded image is decoded. +2. Next, it is then smoothed and resized to a fixed size. +3. A _fingerprint_ of the image is calculated. +4. The fingerprint is compared to the fingerprint of each illegal image. If the image is determined to be illegal, then it is filtered. + +The details of the fingerprinting step are never explicitly described in the document, but from what we can infer from code samples, we suspect that they were using the following fingerprinting algorithm: + +1. The image is converted to grayscale. +2. The image is then divided into a 3×3 grid of equally sized rectangle regions. +3. For each of the 9 regions, a 4-bin histogram is generated by counting and binning based on the intensity of each pixel in that region. The fingerprint is a vector of these 9 × 4 = 36 counts. + +| | | +| -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f21-1-300x200.jpg)](https://citizenlab.ca/wp-content/uploads/2018/08/f21-1.jpg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f21-2-300x200.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f21-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f21-3-300x200.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f21-3.png) | + +> Table 26: Left, the original filtered image. Centre, the original image with the contrast drastically decreased. Right, the original image with the colours inverted. Both the low contrast and inverted images are filtered. + +It is never explicitly stated in the report, but file names referenced in the report (simhash.cpp, simhash11.cpp) would suggest that the [SimHash](https://en.wikipedia.org/wiki/SimHash) algorithm may be used to reduce the size of the final fingerprint vector and to binary-encode it. This spatial-histogram-based fingerprinting algorithm is, however, also inconsistent with our observations. While this approach would explain why the metric is not invariant to mirroring or rotation, it still would be sensitive to changes in contrast or to inverting the colours of the image, which we found WeChat’s algorithm to be largely robust to (see Table 26 for an example of decreased contrast and inverted colours). + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f22-300x169.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f22.png) + +> Figure 9: Original image. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f23-300x169.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f23.png) + +> Figure 10: Image modified such that each of the nine regions has been vertically flipped. + +In light of finding Intel’s report, we decided to perform one additional test. We took a censored image and divided it into nine equally sized regions as done in the fingerprinting algorithm referenced in Intel’s report. Next, we independently vertically flipped the contents of each region (see Figures 9 and 10). The contents of each region should still have the same distribution of pixel intensities, thus matching the fingerprint; however, we found that the modified image evaded filtering. It is possible that Tencent has changed their implementation since Intel’s report. + +#### Edge detection + +Edges are often used to compare image similarity. Intuitively, edges represent the boundaries of objects and of other features in images. + +| | | +| -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f24-1-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f24-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f24-2-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f24-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f24-3-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f24-3.png) | + +> Table 27: Two kinds of filtering. Left, the original image. Centre, the image with a Sobel filter applied. Right, the Canny edge detection algorithm. + +There are generally two approaches to edge detection. The first approach involves taking the differences between adjacent pixels. The [Sobel filter](https://en.wikipedia.org/wiki/Sobel_operator) is a common example of this (see Table 27). One weakness of this approach is that by signifying small differences as low intensity and larger differences as high intensity, it still does not specify in a 0-or-1 sense which are the “real” edges and which are not. A different approach is to use a technique like [Canny edge detection](https://en.wikipedia.org/wiki/Canny_edge_detector) which uses a number of filtering and heuristic techniques to reduce each pixel of a Sobel-filtered image to either black (no edge) or white (an edge is present). As this reduces each pixel to one bit, it is more computationally efficient to use as an image feature. + +There is some reason to think that WeChat’s filtering may incorporate edge detection. When we searched online patents for references to how Tencent may have implemented their image filtering, we found that in June 2008 Tencent filed a patent in China called [图片检测系统及方法](https://encrypted.google.com/patents/CN101303734A) (System and method for detecting a picture). In it they describe the following real-time system for detecting blacklisted images after being uploaded. + +1. First, an uploaded image is resized to a preset size in a way that preserves aspect ratio. +2. Next, the [Canny edge detection](https://en.wikipedia.org/wiki/Canny_edge_detector) algorithm is then used to find the edges in the uploaded image. +3. A fingerprint of the image is calculated. + 1. First, the moment invariants of the result of the Canny edge detection algorithm are calculated. It is unclear what kind of moment invariants are calculated. + 2. In a way that is not clearly specified by the patent, the moment invariants are through some process binary-encoded. + 3. Finally, the resulting binary-encoded values are [MD5](https://en.wikipedia.org/wiki/MD5) hashed. This resulting hash is the image fingerprint. +4. fingerprint of the uploaded image is then compared to the fingerprint of each illegal image. If the fingerprint matches any of those in the image blacklist, then it is filtered. + +Steps 1 and 4 are generally consistent with our observations in this report thus far. Uploaded images are resized to a preset size in a way that preserves aspect ratio, and the calculated fingerprint of an image is compared to those in a blacklist. + +In step 3, the possibility of using Canny edge detection is thus far compatible with all of our findings in this report (although it is far from being the only possibility). The use of moment invariants is not strongly supported, as WeChat’s filtering algorithm is very sensitive to rotation. Moreover, encoding the invariants into an MD5 hash through any imaginable means would seem inconsistent with our observations thus far, as MD5, being a cryptographic hash, has the property that the smallest of changes to the hashed content have, in expectation, an equal size of effect on the value of the hash as that of the largest changes. However, we might imagine that they use an alternative hash such as [SimHash](https://en.wikipedia.org/wiki/SimHash), which can hash vectors of real-valued numbers such that two hashes can be compared for similarity in a way that approximates the original [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity) between the hashed vectors. + +We found that designing experiments to test for the use of Canny edge detection difficult. The algorithm is highly parameterized, and the parameters are often determined dynamically using heuristics based on the contents of an image. Moreover, unlike many image transformations such as grayscale conversion, Canny edge detection is not idempotent, (i.e., the canny edge detection of a canny edge detection is not the same as the original canny edge detection). This means that we cannot simply upload an edge-detected image and see if it gets filtered. Instead, we created test images by removing as many potentially relevant features of an image as possible while preserving the edges of an image. + +| | | +| -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f21-1-300x200.jpg)](https://citizenlab.ca/wp-content/uploads/2018/08/f21-1.jpg) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f25-2-300x200.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f25-2.png) | + +> Table 28: Left, the original filtered image. Right, the image thresholded according to Otsu’s method, which is also filtered. + +To do this, we again returned to thresholding, which we originally explored when analyzing the WeChat filter’s ability to perform OCR. By using thresholding, we reduced all pixels to either black or white, eliminating any gray or gradients from the image, while hopefully largely preserving the edges in the image (see Table 28). + +In our experiment, we wanted to know what effects performing thresholding would have on images that we knew were filtered. To do this, on our usual 15 images we applied global thresholding according to four different thresholds: the image’s median grayscale pixel value, the image’s mean grayscale pixel value, a fixed value of 0.5, and a threshold value chosen using Otsu’s method (see Table 29). + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/Table_14.png)](https://citizenlab.ca/wp-content/uploads/2018/08/Table_14.png) + +> Table 29: Results performing four different thresholding algorithms on 15 images. All but one image was filtered after being thresholded by at least one of the four algorithms. + +We found that all but one image was still filtered after being thresholded by at least one of the four algorithms. + +| | | | | +| -- | -- | -- | -- | +| (a) | (b) | (c) | (d) | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f26-1-300x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f26-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f26-2-300x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f26-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f26-3.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f26-3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f26-4.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f26-4.png) | + +> Table 30: (a), Liu Xiaobo’s empty chair, when thresholded according to Otsu’s method, its stripes are lost. (b), Liu Xiaobo and his wife thresholded according to Otsu’s method with the algorithm performing poorly on the gradient background. (c) and (d), an artificially created gradient background and its thresholded counterpart. In (d), an edge has been created where perceptually one would not be thought to exist. + +All but two of the images were still filtered after being thresholded using a threshold determined via Otsu’s method. Among the two images that were not filtered, one was the image of Liu Xiaobo’s empty chair. This may be because the threshold chosen by Otsu’s method did not distinguish the stripes on the empty chair. The other was the photograph of Liu Xiaobo and his wife clanging coffee cups. This may be because thresholding does not preserve edges well with backgrounds with gradients, as the thresholding will create an erroneous edge where none actually exists (see Table 30). + +As an additional test, we took the 15 images thresholded using Otsu’s method and inverted them. This would preserve the location of all edges while radically altering the intensity of many pixels. + +| | | | | +| -- | -- | -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f27-1-225x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f27-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f27-2-255x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f27-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f27-3-300x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f27-3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f27-4-220x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f27-4.png) | + +> Table 31: The four images filtered after thresholding according to Otsu’s method and then inverted. + +We found that among the 13 images that were filtered after applying Otsu’s method, only four were filtered after they were additionally inverted (see Table 31). The two images that were not filtered before were also not filtered after being inverted. This suggests that, if edge detection is used, it is either in addition to other features of the image, or the edge detection algorithm is not one such as the Canny edge detection algorithm which only tracks edges not their “sign” (i.e., whether the edge is going from lighter to darker versus darker to lighter). + +Between the Intel report and the Tencent patent, we have seen external evidence that WeChat is using either spatial histograms or Canny edge detection to fingerprint sensitive images. Since neither seems to be used by itself, is it possible that they are building a fingerprint using both? To test this, we took the 13 filtered images thresholded using Otsu’s method and tested to see how light we could lighten the black channel such that the thresholded image is still filtered. + +| | | | | | | | +| -- | -- | -- | -- | -- | -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-1-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-2-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-3-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-4-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-4.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-5-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-5.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-6-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-6.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-7-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-7.png) | +| 20 | 215 | 56 | 11 | 12 | 19 | 15 | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-8-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-8.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-9-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-9.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-10-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-10.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-11-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-11.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-12-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-12.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f28-13-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f28-13.png) | +| 18 | 28 | 55 | 255 | 21 | 18 | + +> Table 32: The lightest image still filtered, and, below each image, the difference in intensities between white and the darkest value (out of 255). + +Our results show that sometimes the difference in intensities can be small for an image to still be filtered and that sometimes it must be large, with one case allowing no lightening of the black pixels at all (see Table 32). The images with small differences are generally the non-photographic ones with well-defined edges. These are images where the thresholding algorithm would have been most likely to preserve features of the image such as the edges in the first place. Thus, they are more likely to preserve these features when they have been lightened up, especially after possible filtering such as downscaling has been applied. + +Nevertheless, this result shows that the original image need not have a similar spatial histogram. Six of the seven images in Table 32 have an intensity difference of less than 64, which, in the 4-binned spatial histogram algorithm referenced in the Intel report, would put every pixel into the same bin. When we repeat this experiment with the inverted thresholded images and lightening them such that every pixel fit into the same bin, we could not get any additional inverted images to be filtered, despite these images preserving the locations of the edges and having the same spatial histograms as images that we knew to be filtered. All together this suggests that spatial histograms are not an important feature of these images. + +So far our approach has been to eliminate as many of an image’s features as possible except for edges and test to see if it still gets filtered. We also decided to take the opposite approach, eliminating edges by blurring them while keeping other features untouched. We proportionally resized each image such that its smallest dimension(s) is/are 200 pixels (see the “Resizing” section for why we resized this way). Then we applied a normalized box filter to blur the image, increasing the kernel size until the image is sufficiently blurred to evade filtering. + +| | | | | | +| -- | -- | -- | -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-1-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-2-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-3-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-4-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-4.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-5-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-5.png) | +| 5 px | 3 px | 4 px | 4 px | 2 px | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-6-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-6.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-7-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-7.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-8-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-8.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-9-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-9.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-10-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-10.png) | +| 3 px | 1 px | 6 px | 4 px | 5 px | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-11-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-11.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-12-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-12.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-13-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-13.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-14-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-14.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f29-15-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f29-15.png) | +| 4 px | 6 px | 7 px | 5 px | 6 px | + +> Table 33: The largest normalized box filter kernel size that can be applied to each image while still being filtered. + +In general, we saw that WeChat’s filter was not robust to blurring (see Table 33). Non-photographic images were generally the easiest to evade filtering by blurring, possibly because they generally have sharper and more well-defined edges. + +In this section we have demonstrated evidence that edges are important image features in WeChat’s filtering algorithm. Nevertheless, it remains unclear exactly how WeChat builds image fingerprints. Some possibilities are that it specifically uses filtering methods such as Sobel filtering, although a detection algorithm such as Canny edge detection seems unlikely as it does not preserve the sign of the edges. Another possibility is that it fingerprints images in the [frequency domain](https://en.wikipedia.org/wiki/Fourier_transform), where small changes to distinct edges can often have effect the values of a large number of multiple frequencies, and where large changes to the overall brightness of an image can significantly affect the values of only a small number of frequencies. + +> * 上一篇:[(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 2](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md) +> +> * 下一篇:[(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 4](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-4.md) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-4.md b/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-4.md new file mode 100644 index 00000000000..2de5a64dec9 --- /dev/null +++ b/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-4.md @@ -0,0 +1,170 @@ +> * 原文地址:[(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 4](https://citizenlab.ca/2018/08/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments/) +> * 原文作者:[Jeffrey Knockel](https://citizenlab.ca/author/jknockel/), [Lotus Ruan](https://citizenlab.ca/author/lotus/), [Masashi Crete-Nishihata](https://citizenlab.ca/author/masashi/), and [Ron Deibert](https://citizenlab.ca/author/profd/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-4.md](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-4.md) +> * 译者: +> * 校对者: + +# (CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 4 + +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 1](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-1.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 2](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-2.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 3](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md) +> * [(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 4](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-4.md) + +#### Resizing + +Up until this point, we have been mostly concerned with experimenting with images that have the same aspect ratios. In this section we test how changing images’ sizes affected WeChat’s ability to recognize them. How does WeChat’s filter compare images that might be an upscaled or downscaled version of that on the blacklist? For instance, does WeChat normalize the dimensions of uploaded images to a canonical size? (See Table 34) + +![](https://i.loli.net/2018/08/16/5b74e3805b458.png) + +> Table 34: Examples of how two images would be resized according to five different hypotheses. + +To answer these questions, we decided to test five different hypotheses: + +1. Images are proportionally resized such that their **width** is some value such as 100. +2. Images are proportionally resized such that their **height** is some value such as 100. +3. Images are proportionally resized such that their **largest** dimension is some value such as 100. +4. Images are proportionally resized such that their **smallest** dimension is some value such as 100. +5. **Both** dimensions are resized according to two parameters to some fixed size and proportion such as 100×100. + +If the last hypothesis is correct, then we would expect WeChat’s filter to be invariant to non-uniform changes in scale, i.e., it should be tolerant of modifications to a sensitive image’s aspect ratio. This is because the aspect ratio of the uploaded image would be erased when the image is resized to a preset aspect ratio. To test this, we performed an experiment on our usual set of 15 images. We created a _shorter_ image by stretching each image 30% shorter. We also created a _thinner_ image by stretching each image 30% thinner. Each of the shorter images evaded filtering. Moreover, all but one of the thinner images, the graphic of Liu Xiaobo with his wife, evaded filtering. As modifying the aspect ratio of blacklisted images easily evades filtering, this would suggest that the last hypothesis is not true. + +To test hypotheses 1 through 4, we made the following corresponding predictions: + +1. If images are proportionally resized based on their **width**, then adding extra space to their width would evade filtering but adding it to their height would not. +2. If images are proportionally resized based on their **height**, then adding extra space to their height would evade filtering. +3. If images are proportionally resized based on their **largest** dimension, then adding extra space to that dimension would evade filtering. +4. If images are proportionally resized based on their **smallest** dimension, then adding extra space to that dimension would evade filtering. + +| | | | | | +| -- | -- | -- | -- | -- | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f30-w1-150x141.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f30-w1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f30-w2-150x125.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f30-w2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f30-w3-150x141.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f30-w3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f30-w4-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f30-w4.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f30-w5-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f30-w5.png) | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f30-h1-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f30-h1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f30-h2-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f30-h2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f30-h3-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f30-h3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f30-h4-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f30-h4.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f30-h5-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f30-h5.png) | + +> Table 35: The five wide and the five tall images we tested. + +| | **Resized to same height** | **Resized to same width** | +| -- | -------------------------- | ------------------------- | +| | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f31-1.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f31-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f31-2.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f31-2.png) | +| | (the original) | (the original) | +| + | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f31-3.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f31-3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f31-4-1.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f31-4-1.png) | +| | (space added to width, resized to same height) | (space added to width, resized to same width) | +| = | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f31-5-150x100.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f31-5.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f31-6.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f31-6.png) | + +> Table 36: Two different ways of resizing an image after extra space is added to its width. If resizing by its height (hypothesis 2) or by its shortest dimension (hypothesis 4), the scale of the image’s contents are unchanged with respect to the original and there is complete overlap (white). If resizing by its width (hypothesis 1) or by its largest dimension (hypothesis 3), the image’s original contents become smaller and do not overlap well with the original. + +To test these predictions, we chose ten filtered images, five such that their height is no more than ⅔ of their width, which we call the _wide_ images, and five such that their width is no more than ⅔ of their height, which we call the _tall_ images (see Table 35). We then modified each of the images by adding black space the size of 50% of their width to their left and right sides (see Table 36 for an example) and again by adding black space the size of 50% of their height to their top and bottom sides. We repeated these again except by using 200% of the respective dimensions. + +![](https://i.loli.net/2018/08/16/5b74f84ec10a0.png) + +> Table 37: The number of wide and tall images that evaded filtering after adding different amounts of extra space to either their width or height. + +We found that wide images with space added to their width and tall images with space added to their height were always filtered. This is consistent with hypothesis 4, that WeChat resizes based on an uploaded image’s shortest dimension, as this hypothesis predicts that adding space in this matter will not change the scale of the original image contents after the image is resized. We also found that wide images with space added to their height and tall images with space added to their width usually evaded filtering, suggesting that this caused the uploaded image to be further downscaled compared to the corresponding one on the blacklist. + +The results between adding 50% and 200% extra space were fairly consistent, with only one additional image being filtered in the 200% case. This consistency is to be expected, since according to the shortest dimension hypothesis, adding extra space past when the image has already become square will not affect its scaling. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f32.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f32.png) + +> Figure 11: A screenshot of visually similar images to the Tank Man photo with extra space on their top and bottom found via a reverse Google Image search. + +It is not clear why some images–two tall images with extra width and one wide image with extra height–were still filtered. It is possible that WeChat’s filtering algorithm has some tolerance of changes in scale. However, it is also possible that variants of these images with that extra space or some other border or content added in these areas are also on the blacklist. For example, the only wide image with extra height to still be filtered is the famous and highly reproduced Tank Man photo. A reverse Google Image search finds that there are many images with similar spacing added to them already in circulation on the Internet (see Figure 11). + +#### Translational invariance + +In the previous section, when we tested how the filter resizes uploaded images, we did so by adding blank black space to the edges of uploaded images and observing which are filtered. We found that images with a large amount of extra space added to their largest dimensions were still filtered. We tested this by keeping the sensitive image in the centre and adding an equal amount of extra space to both sides of the largest dimension. We wanted to know if WeChat’s filtering algorithm can only find sensitive images in the centre of such an image, or if it can find them anywhere in an image. Formally, we wanted to test whether WeChat’s filtering algorithm is [translationally invariant](https://en.wikipedia.org/wiki/Translation_invariance). + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/F33_1-300x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/F33_1.png)[![](https://citizenlab.ca/wp-content/uploads/2018/08/F33_2-300x300.png)](https://citizenlab.ca/wp-content/uploads/2018/08/F33_2.png) + +> Figure 12: The three images on the left are filtered demonstrating the WeChat filter’s translational invariance. However, the three images on the right are not filtered because they make the tall image wider, affecting its smallest dimension. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/F34_1-MK-1.png)](https://citizenlab.ca/wp-content/uploads/2018/08/F34_1-MK-1.png) + +> Figure 13: Examples of images filtered due to the WeChat filter’s translational invariance. + +We took images from the previous experiment and experimented with increasing their canvas size and moving the image proper around inside of a larger, blank canvas (see Figures 12 and 13). We found that so long as we did not change the size of the image’s smallest dimension, the image proper could be moved anywhere inside of the extended canvas and still not be censored. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/F35_1-MK.png)](https://citizenlab.ca/wp-content/uploads/2018/08/F35_1-MK.png) + +> Figure 14: For a square image where its width is equal to its height, adding space to either dimension will not evade filtering regardless of where the image is located. + +Note that in a tall or wide image, we can only add space to one of its dimensions for it to still be filtered. For a square image, we can add space to either side, but only in one dimension at a time (see Figure 14). However, if we add extra space to both, its scale will be modified after it is resized by WeChat’s filtering algorithm. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/F36_1-MK.png)](https://citizenlab.ca/wp-content/uploads/2018/08/F36_1-MK.png) + +> Figure 15: (a), an image encoded to JPEG by WeChat that evaded filtering, and (b), that image with the extended canvas cropped off that also evaded filtering. (c), an image encoded to PNG by WeChat that was filtered, and (d), that image with the extended canvas cropped off that was also filtered. At a glance, both (b) and (d) look surprisingly similar, but (b) has more JPEG compression artifacts. + +We came across some apparent exceptions to the WeChat algorithm’s translational invariance that we found could ultimately be explained by WeChat’s compression algorithm. Some images where we extended the canvas with blank space evaded filtering (see Figure 15). However, we found that this may actually be due to the WeChat server’s compression of the images. We found that, after posing these images, when we downloaded them using another WeChat user and examined them, they were encoded in JPEG, a lossy compression algorithm that decreases the size needed to represent the image by partially reducing the quality of that image. We found that when we took this image as it was encoded by WeChat’s servers and cropped off the extended canvas and posted it onto WeChat, it still evaded filtering, suggesting that it is WeChat’s compression and not the extension of the canvas per se that caused the image to evade filtering. We found that WeChat increased its compression of images for larger images, likely to try to keep larger images from taking up more space. Thus, by extending the size of the canvas, we increased the compression of the original image, causing it to evade filtering. + +We found that not all images were JPEG compressed when downloaded by another client. Rarely, images would be downloaded in PNG, a lossless compression algorithm that reduces image size by accounting for redundancies in an image but never by reducing the image’s quality. In this case, we found that the PNG image another WeChat client downloaded was pixel-for-pixel identical to the one that we had originally uploaded. We found that such images were always filtered, further suggesting that WeChat’s own compression was affecting its filtering behavior in other images. Unfortunately, we were unable to determine why WeChat would compress a posted image as JPEG or PNG, as this behavior was both rare and nondeterministic. That is, often even if we uploaded an image that had previously been observed to have been encoded to PNG, it would often be encoded to JPEG in subsequent uploads. This nondeterministic compression behavior would also explain why we would occasionally observe nondeterministic filtering behavior on WeChat. + +| | | | | | +| -- | -- | -- | -- | -- | +| (a) | (b) | (c) | (d) | (e) | +| [![](https://citizenlab.ca/wp-content/uploads/2018/08/f37-1-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f37-1.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f37-2-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f37-2.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f37-3-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f37-3.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f37-4-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f37-4.png) | [![](https://citizenlab.ca/wp-content/uploads/2018/08/f37-5-150x150.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f37-5.png) | + +> Table 38: (a), the original image. (b), that image thresholded and pixelated to 16×16 blocks. (c), (d), and (e) show compression artifacts by colouring pure black as orange, pure white as green, leaving the off-white and off-black colours unchanged. (c) is the result of WeChat’s compression. (d) is the result of WebP compression at quality 42, visually similar to (c). (e) is the result of either PNG or JPEG compression, as PNG is losslessly compressed and JPEG uses no interblock compression. + +We found that the images downloaded in JPEG appeared to have been also previously compressed using a different compression algorithm. We determined this by creating an image made entirely of 16×16 pixel blocks of purely black or white (see Table 38 (a) and (b)). Even though JPEG is a lossy algorithm, it independently compresses 8×8 pixel blocks (i.e., there is no inter-block compression between blocks). However, we observed the images having been compressed by a 16×16 block compression algorithm that utilizes information from surrounding blocks. The new [WebP](https://en.wikipedia.org/wiki/WebP) image compression algorithm from Google is consistent with these findings, as were the compression artifacts (see Table 38 (c), (d), and (e)). Moreover, we found that WeChat supported posting WebP images, which further suggests that they may be using it to encode uploaded images as well. + +Despite having some initial difficulty controlling for the effects of WeChat’s image compression on posted images, we generally found that WeChat’s filtering algorithm is invariant to translation. There are a number of different methods that could account for finding an image inside of another, especially since the algorithm is not invariant to changes in scale. WeChat’s filtering algorithm could be centering images according to their centre of mass before comparing them. The filter could be using [cross-correlation](https://en.wikipedia.org/wiki/Cross-correlation) or [phase correlation](https://en.wikipedia.org/wiki/Phase_correlation) to compare the images accounting for differences in their alignments (i.e., their translational difference). WeChat’s filtering algorithm could also be using a sliding window approach such as with [template matching](https://en.wikipedia.org/wiki/Template_matching), or it may be using a [convolutional neural network](https://en.wikipedia.org/wiki/Convolutional_neural_network), a neural network that does not require a sliding window to implement but that has similar functionality. We initially found the translational invariance of WeChat’s algorithm surprising given that it was not invariant to other transformations such as mirroring or rotation, but the use of any one of the methods enumerated in this paragraph would provide translational invariance without necessarily providing invariance to mirroring or rotation. In the next section, we will try to eliminate some of these methods as possibilities by testing what happens if we replace the blank canvas space that we have been using to extend image with complex patterns. + +#### Sliding window + +In the previous section, we tested for translational invariance by extending the canvas with blank space. What if the added content is not blank? In this section we are concerned with whether the algorithm is not simply translationally invariant but whether it can find an image inside of another image regardless of the surrounding contents. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f38-1.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f38-1.png) + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/f38-2.png)](https://citizenlab.ca/wp-content/uploads/2018/08/f38-2.png) + +> Figure 16: Above, an image extended with i = 2 blank canvases. Below, an image extended with i = 2 duplicates of itself. + +Given our findings about WeChat’s compression affecting filtering results in the previous section, we carefully designed our experiment. Taking our five wide and five tall images, we extended their canvas in their largest dimension on both sides by _i_ ⋅ _n_, for a total of 2 ⋅ _i_ ⋅ _n_ overall, for each _i_ in {1, 2, …, 5}, where _n_ is the size of the largest dimension. We then created equivalently sized images that were not blank. Since many image operations such as thresholding and edge detection are sensitive to the distribution of pixel intensities in an image, and others such as moment invariants are sensitive to changes in centre of mass, to control for these variables, we filled each blank area by a duplicate copy of the image itself so that these variables are not affected (see Figure 16). To account for WeChat’s compression, for any image we generated, if it evades filtering, we download the image and analyze it. If the centre image inside of it (the only one in the case of a blank extended canvas or the one in the middle in the case of where the image is duplicated) are no longer filtered when uploaded on their own, then we discard all results from any images derived from that original wide or tall image. + +[![](https://citizenlab.ca/wp-content/uploads/2018/08/Table_17.png)](https://citizenlab.ca/wp-content/uploads/2018/08/Table_17.png) + +> Table 39: After testing a wide or tall image by either extending it by i-many blank canvas or i-many image duplicates, was it still filtered? Y = Yes, N = No, C = No due to compression artifacts. With one exception, all images extended with blankness were either filtered or evaded filtering due to compression artifacts, whereas when extending an image with duplicates of itself, none of the filtering evasion can be explained by compression artifacts. + +Our results are given in Table 39. We found that images extended with their own duplicates evaded filtering after a sufficiently large number of images were added, and none of these evasions could be explained by image compression. Conversely, in all but one test, images extended with blank canvases were either filtered or their evasion could be explained by image compression. + +These results suggest that, even when we add additional contents to an uploaded image such that neither the distribution of intensities of the image nor its centre of mass change, these contents affect the ability of WeChat to recognize the uploaded image as sensitive. This suggests that WeChat may not use a sliding window approach that ignores contents outside of that window to compare images. Instead, the images appear to be compared as a whole and that adding complex patterns outside of a blacklisted image can evade filtering. + +#### Perceptual hashing + +Unlike cryptographic hashing, where small changes are designed to produce large changes to the hash, perceptual hashing is a technique to reduce an image to a hash such that similar images have either [equal](https://ieeexplore.ieee.org/abstract/document/1421855/) or [similar](http://phash.org/) hashes to facilitate efficient comparison. It is [used by many social media companies such as Facebook, Microsoft, Twitter and YouTube](https://newsroom.fb.com/news/2016/12/partnering-to-help-curb-spread-of-online-terrorist-content/) to filter illegal content. + +As we suggested in the section on edge detection, a frequency-based approach would explain the visual-based filter’s sensitivity to edges; however, such an approach can also be used to achieve a hash exhibiting translational invariance. The popular open source implementation [pHash](http://phash.org/) computes a hash using the discrete cosine transform, which is not translationally invariant. However, an alternative spectral computation that would exhibit translational invariance would be to calculate the image’s amplitude spectrum by computing the absolute magnitude of the discrete [Fourier transform](https://en.wikipedia.org/wiki/Fourier_transform) of the image, as [translation only affects the phase, not the magnitude](https://en.wikipedia.org/wiki/Fourier_transform#Basic_properties), of the image’s frequencies. The use of a hash based on this computation would be consistent with our findings, but more work is needed to test if this technique is used. + +## Conclusion + +In analyzing both the OCR-based and visual-based filtering techniques implemented by WeChat, we discovered both strengths in the filter as well as weaknesses. An effective evasion strategy against an image filter modifies a sensitive image so that it (1) no longer resembles a blacklisted image to the filter but (2) still resembles a blacklisted image to people reading it. + +The OCR-based algorithm was generally able to read text of varying legibility and in a variety of environments. However, due to the way it was implemented, we found two ways to evade filtering: + +* By hiding text in the hue of an image, since the OCR filter converts images to grayscale. +* By hiding text using a large amount of blobs, since the OCR filter performs blob merging. + +Similarly, the visual-based algorithm was able to match sensitive images to those on a blacklist under a variety of conditions. The algorithm had translational invariance. Moreover, it detected images even after their brightness or contrast had been altered, after their colours had been inverted, and after they had been thresholded to only two colours. However, due to the way it was implemented, we found multiple ways to evade filtering: + +* By mirroring or rotating the image, since the filter has no high level semantic understanding of uploaded images. However, many images lose meaning when mirrored or rotated, particularly images that contain text which may be rendered illegible. +* By changing the aspect ratio of an image, such as by stretching the image wider or taller. However, this may make objects in images look out of proportion. +* By blurring the photo, since edges appear important to the filter. However, while edges are important to WeChat’s filter, they are often perceptually important for people too. +* By adding a sufficiently large border to the smallest dimensions of an image, or to both the smallest and largest dimensions, particularly if both dimensions are of equal or similar size. +* By adding a large border to the largest dimensions of an image and adding a sufficiently complex pattern to it. + +In this work we present experiments uncovering implementation details of WeChat’s image filter that inform multiple effective evasion strategies. While the focus of this work has been WeChat, due to common implementation details between image filtering implementations, we hope that our methods will serve as a road map for future research studying image censorship on other platforms. + +## Acknowledgments + +We would like to thank Lex Gill for research assistance. We would also like to extend thanks to Jakub Dalek, Adam Senft, and Miles Kenyon for peer review. This research was supported by the Open Society Foundations. + +Image testing data and source code is available [here](https://github.com/citizenlab/chat-censorship/tree/master/wechat/image-filtering). + +> * 上一篇:[(CAN’T) PICTURE THIS: An Analysis of Image Filtering on WeChat Moments — Part 3](https://github.com/xitu/gold-miner/blob/master/TODO1/cant-picture-this-an-analysis-of-image-filtering-on-wechat-moments-3.md) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/clean-architecture-in-go.md b/TODO1/clean-architecture-in-go.md new file mode 100644 index 00000000000..acf3a432404 --- /dev/null +++ b/TODO1/clean-architecture-in-go.md @@ -0,0 +1,455 @@ +> * 原文地址:[Clean Architecture in Go: An example of clean architecture in Go using gRPC](https://medium.com/@hatajoe/clean-architecture-in-go-4030f11ec1b1) +> * 原文作者:[Yusuke Hatanaka](https://medium.com/@hatajoe?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/clean-architecture-in-go.md](https://github.com/xitu/gold-miner/blob/master/TODO1/clean-architecture-in-go.md) +> * 译者: +> * 校对者: + +# Clean Architecture in Go + +## An example of clean architecture in Go using gRPC + +### What I want to tell you + +Clean architecture is well known architecture these days. However, we may not know about details of the implementation very well. +So I tried to make one example that is conscious of clean architecture in Go using gRPC. + +* [hatajoe/8am: Contribute to hatajoe/8am development by creating an account on GitHub.](https://github.com/hatajoe/8am "https://github.com/hatajoe/8am") + +This small project represents user registration example. Please feel free to respond anything. + +### The Structure + +8am is based on clean architecture, the project structure is like below. + +``` +% tree +. +├── Makefile +├── README.md +├── app +│ ├── domain +│ │ ├── model +│ │ ├── repository +│ │ └── service +│ ├── interface +│ │ ├── persistence +│ │ └── rpc +│ ├── registry +│ └── usecase +├── cmd +│ └── 8am +│ └── main.go +└── vendor + ├── vendor packages + |... +``` + +The top directory contains three directories: + +* app: application package root directory +* cmd: main package directory +* vendor: several vendor packages directory + +Clean architecture has some conceptual layer like below: + +![](https://cdn-images-1.medium.com/max/800/1*B7LkQDyDqLN3rRSrNYkETA.jpeg) + +There are 4 layers, blue, green, red and yellow layers there in order from the outside. I represented these layers except blue to the app directory: + +* interface: the green layer +* usecase: the red layer +* domain: the yellow layer + +The most important thing about clean architecture is to make interfaces through each layer. + +### Entities — the yellow layer + +IMO, the Entities layer looks like a domain layer of the layered architecture. +So I named this layer to app/domain due to that is confused with the entity of DDD. + +app/domain has three packages: + +* model: has aggregate, entity and value object +* repository: has repository interfaces of aggregate +* service: has application services that depend on several models + +I explain what detail of implementation for each package. + +#### model + +model has user aggregate like below: + +> This is not actually aggregate, but I hope you will be on a premise that various entity and value object will be added in the future. + +``` +package model + +type User struct { + id string + email string +} + +func NewUser(id, email string) *User { + return &User{ + id: id, + email: email, + } +} + +func (u *User) GetID() string { + return u.id +} + +func (u *User) GetEmail() string { + return u.email +} +``` + +The aggregate is the boundary of a transaction in order to keep their consistency of business rules. Thus there is one repository against the one aggregate. + +#### repository + +In this layer, the repository is just an interface because of that should not know about the detail of persistence implementation. But also persistence is an important essence for this layer. + +implementation of the user aggregate repository is: + +``` +package repository + +import "github.com/hatajoe/8am/app/domain/model" + +type UserRepository interface { + FindAll() ([]*model.User, error) + FindByEmail(email string) (*model.User, error) + Save(*model.User) error +} +``` + +FindAll fetches all users who are persisted in the system. And Save persists user to the system. I say it again, this layer should not know what where is the object is saved or serialized. + +#### service + +The service layer is gathered business logic that should not be included in the model. For example, this application don’t allow existing email address registration. If model has this validation, we will get to feel something wrong like below: + +``` +func (u *User) Duplicated(email string) bool { + // Find user by email from persistence layer... +} +``` + +`Duplicated function` is not related to `User` model. +For solving this, we can add the service layer like below: + +``` +type UserService struct { + repo repository.UserRepository +} + +func (s *UserService) Duplicated(email string) error { + user, err := s.repo.FindByEmail(email) + if user != nil { + return fmt.Errorf("%s already exists", email) + } + if err != nil { + return err + } + return nil +} +``` + +* * * + +Entities contain business logic and interface through the other layers. +Business logic should be included in the model and service, and should not be depended any other layers. If we need to access any other layer, we should through the layers using repository interface. By inverting the dependencies like this, may packages to be isolated, more testable and maintainable. + +### Use Cases — the red layer + +Use cases are unit of the one operation for application. +In 8am, listing user and user registration are defined as use case. +Those use cases are represented this interface below: + +``` +type UserUsecase interface { + ListUser() ([]*User, error) + RegisterUser(email string) error +} +``` + +Why is it an interface? This is because use case is used from interface layer — the green layer. We should always define interface if we are going to through between layers. + +_UserUsecase_ implementation is simple like below: + +``` +type userUsecase struct { + repo repository.UserRepository + service *service.UserService +} + +func NewUserUsecase(repo repository.UserRepository, service *service.UserService) *userUsecase { + return &userUsecase { + repo: repo, + service: service, + } +} + +func (u *userUsecase) ListUser() ([]*User, error) { + users, err := u.repo.FindAll() + if err != nil { + return nil, err + } + return toUser(users), nil +} + +func (u *userUsecase) RegisterUser(email string) error { + uid, err := uuid.NewRandom() + if err != nil { + return err + } + if err := u.service.Duplicated(email); err != nil { + return err + } + user := model.NewUser(uid.String(), email) + if err := u.repo.Save(user); err != nil { + return err + } + return nil +} +``` + +_userUsercase_ depend two packages _repository.UserRepository_ interface and _*service.UserService_ struct. These two packages are must injected when use case initialize by use case user. Those dependencies are solved by DI container in normally, this will be wrote later in this entry. + +ListUser use case fetch all registered users and RegisterUser use case register a user to the system if it is not registered same email address. + +One point, the _User_ is not _model.User. model.User_ may has many business knowledges, but other layers should not better to know about that. So I defined DAO for use case users due to encapsulate the knowledges. + +``` +type User struct { + ID string + Email string +} + +func toUser(users []*model.User) []*User { + res := make([]*User, len(users)) + for i, user := range users { + res[i] = &User{ + ID: user.GetID(), + Email: user.GetEmail(), + } + } + return res +} +``` + +* * * + +So, why do you think about this service is used as concrete implementation instead of using interface? This is because that this service depends no other layers. Conversely, the repository through layers and the implementation depends detail of devices that should not known from other layer, thus that was defined an interface. I think this is a most important thing in this architecture. + +### Interface — the green layer + +This layer is placed the concrete object like handler of the API endpoint, repository of the RDB or other boundaries for interfaces. In this case, I added two concrete objects that memory storage accessor and gRPC service. + +#### Memory storage accessor + +I added concrete user repository as memory storage accessor. + +``` +type userRepository struct { + mu *sync.Mutex + users map[string]*User +} + +func NewUserRepository() *userRepository { + return &userRepository{ + mu: &sync.Mutex{}, + users: map[string]*User{}, + } +} + +func (r *userRepository) FindAll() ([]*model.User, error) { + r.mu.Lock() + defer r.mu.Unlock() + + users := make([]*model.User, len(r.users)) + i := 0 + for _, user := range r.users { + users[i] = model.NewUser(user.ID, user.Email) + i++ + } + return users, nil +} + +func (r *userRepository) FindByEmail(email string) (*model.User, error) { + r.mu.Lock() + defer r.mu.Unlock() + + for _, user := range r.users { + if user.Email == email { + return model.NewUser(user.ID, user.Email), nil + } + } + return nil, nil +} + +func (r *userRepository) Save(user *model.User) error { + r.mu.Lock() + defer r.mu.Unlock() + + r.users[user.GetID()] = &User{ + ID: user.GetID(), + Email: user.GetEmail(), + } + return nil +} +``` + +This is concrete implementation of repository. We’ll need to another implementation if we need to persist the user to the RDB or other. But even in such case, we don’t need to change the model layer. The model layer is depending for only repository interface, and not interest for this implementation detail. This is amazing. + +This _User_ is defined for only in this package. This also for solving about decapsulating of knowledge through between the layers. + +``` +type User struct { + ID string + Email string +} +``` + +#### gRPC service + +I think gRPC service is also included the interface layer. +These are defined `app/interface/rpc` directory like below: + +``` +% tree +. +├── rpc.go +└── v1.0 + ├── protocol + │ ├── user_service.pb.go + │ └── user_service.proto + ├── user_service.go + └── v1.go +``` + +`protocol` directory contains protocol buffers DSL file(user_service.proto) and generated RPC service code(user_service.pb.go). + +`user_service.go` is the wrapper of gRPC endpoint handler: + +``` +type userService struct { + userUsecase usecase.UserUsecase +} + +func NewUserService(userUsecase usecase.UserUsecase) *userService { + return &userService{ + userUsecase: userUsecase, + } +} + +func (s *userService) ListUser(ctx context.Context, in *protocol.ListUserRequestType) (*protocol.ListUserResponseType, error) { + users, err := s.userUsecase.ListUser() + if err != nil { + return nil, err + } + + res := &protocol.ListUserResponseType{ + Users: toUser(users), + } + return res, nil +} + +func (s *userService) RegisterUser(ctx context.Context, in *protocol.RegisterUserRequestType) (*protocol.RegisterUserResponseType, error) { + if err := s.userUsecase.RegisterUser(in.GetEmail()); err != nil { + return &protocol.RegisterUserResponseType{}, err + } + return &protocol.RegisterUserResponseType{}, nil +} + +func toUser(users []*usecase.User) []*protocol.User { + res := make([]*protocol.User, len(users)) + for i, user := range users { + res[i] = &protocol.User{ + Id: user.ID, + Email: user.Email, + } + } + return res +} +``` + +_userService_ depends for only use case interface. +If you want to use use case from another layer (e.g, CUI), you can implement in this interface layer as you like. + +`v1.go` is resolver of object dependencies using DI container: + +``` +func Apply(server *grpc.Server, ctn *registry.Container) { + protocol.RegisterUserServiceServer(server, NewUserService(ctn.Resolve("user-usecase").(usecase.UserUsecase))) +} +``` + +`v1.go` apply package that was retrieved from _*registry.Container_ to gRPC service. + +At the last, let’s take a look about DI container implementation. + +#### registry + +The registry is DI container that resolve dependency of object. +I have been used github.com/sarulabs/di as DI container. + +[sarulabs/di: Dependency injection container in go (golang). Contribute to sarulabs/di development by creating an account on GitHub.](https://github.com/sarulabs/di "https://github.com/sarulabs/di") + +github.com/surulabs/di can be used easily: + +``` +type Container struct { + ctn di.Container +} + +func NewContainer() (*Container, error) { + builder, err := di.NewBuilder() + if err != nil { + return nil, err + } + + if err := builder.Add([]di.Def{ + { + Name: "user-usecase", + Build: buildUserUsecase, + }, + }...); err != nil { + return nil, err + } + + return &Container{ + ctn: builder.Build(), + }, nil +} + +func (c *Container) Resolve(name string) interface{} { + return c.ctn.Get(name) +} + +func (c *Container) Clean() error { + return c.ctn.Clean() +} + +func buildUserUsecase(ctn di.Container) (interface{}, error) { + repo := memory.NewUserRepository() + service := service.NewUserService(repo) + return usecase.NewUserUsecase(repo, service), nil +} +``` + +For example in above, I associate `user-usecase` string with concrete use case implementation by using `buildUserUsecase` function. Thus we can replace any concrete implementation of use case in one place registry. + +* * * + +Thank you for reading this entry. Feedback is welcome. You have any ideas and improvements feel free to respond me! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/conditional-rendering-in-react.md b/TODO1/conditional-rendering-in-react.md new file mode 100644 index 00000000000..f03cf27c913 --- /dev/null +++ b/TODO1/conditional-rendering-in-react.md @@ -0,0 +1,1607 @@ +> * 原文地址:[8 React conditional rendering methods](https://blog.logrocket.com/conditional-rendering-in-react-c6b0e5af381e) +> * 原文作者:[Esteban Herrera](https://blog.logrocket.com/@eh3rrera?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/conditional-rendering-in-react.md](https://github.com/xitu/gold-miner/blob/master/TODO1/conditional-rendering-in-react.md) +> * 译者:[Dong Han](https://github.com/IveHD) +> * 校对者:[Jessica Shao](https://github.com/tutaizi),[doctype233](https://github.com/tutaizi + +# 8 React 实现条件渲染的多种方式和性能考量 + +![](https://cdn-images-1.medium.com/max/800/1*iePG8qczEBX1ICAMR5U-JQ.png) + +[JSX](https://facebook.github.io/jsx/) 是对 JavaScript 强大的扩展,允许我们来定义 UI 组件。但是它不直接支持循环和条件表达式(尽管添加 [条件表达式已经被讨论过了](https://github.com/reactjs/react-future/issues/35))。 + +如果你想要遍历一个列表来渲染多个组件或者实现一些条件逻辑,你不得不使用纯 Javascript,你也并没有很多的选择来处理循环。更多的时候,`map` 将会满足你的需要。 + +但是条件表达式呢? + +那就是另外一回事了。 + +### 有几种方案可供你选择 + +在 React 中有多种使用条件语句的方式。并且,和编程中的大多数事情一样,依赖于你所要解决的实际问题,有些方式是更适合的。 + +本教程介绍了最流行的条件渲染方法: + +* If/Else +* 避免使用 `null` 渲染 +* 元素变量 +* 三元运算符 +* 与运算 (&&) +* 立即调用函数(IIFE) +* 子组件 +* 高阶组件(HOCs) + +作为所有这些方法如何工作的示例,接下来将实现具有查看/编辑功能的组件: + +![](https://cdn-images-1.medium.com/max/800/0*vS8AU_xnc4VHcHrK.) + +你可以在 [JSFiddle](https://jsfiddle.net/) 中尝试和拷贝(fork)所有例子。 + +让我们从使用 if/else 这种最原始的实现开始并在这里构建它。 + +### If/else + +让我们使用如下状态来构建一个组件: + +``` +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + } +} +``` + +你将使用一个属性来保存文本,并且使用另外一个属性存储正在被编辑的文本。第三个属性将用来表示你是在 `edit` 还是 `view` 模式下。 + +接下来,添加一些方法来处理输入文本、保存和输入事件: + +``` +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } +} +``` + +现在,对于渲染方法,除了保存的文本之外,还要检查模式状态属性,以显示编辑按钮或文本输入框和保存按钮: + +``` +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } +} +``` + +下面是完整的代码,可以在 fiddle 中尝试执行它: + +Babel + JSX: + +``` +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } + + render () { + if(this.state.mode === 'view') { + return ( +

+

Text: {this.state.text}

+ +
+ ); + } else { + return ( +
+

Text: {this.state.text}

+ + +
+ ); + } + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +if/else 是最简单的方式来解决这个问题,但是我确定你知道这并不是一种好的实现方式。 + +它适用于简单的用例,每个程序员都知道它是如何工作的。但是有很多重复,`render` 方法看起来并不简洁。 + +所以让我们通过将所有条件逻辑提取到两个渲染方法来简化它,一个来渲染文本框,另一个来渲染按钮: + +``` +class App extends React.Component { + // … + + renderInputField() { + if(this.state.mode === 'view') { + return
; + } else { + return ( +

+ +

+ ); + } + } + + renderButton() { + if(this.state.mode === 'view') { + return ( + + ); + } else { + return ( + + ); + } + } + + render () { + return ( +
+

Text: {this.state.text}

+ {this.renderInputField()} + {this.renderButton()} +
+ ); + } +} +``` + +下面是完整的代码,可以在 fiddle 中尝试执行它: + +Babel + JSX: + +``` +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } + + renderInputField() { + if(this.state.mode === 'view') { + return
; + } else { + return ( +

+ +

+ ); + } + } + + renderButton() { + if(this.state.mode === 'view') { + return ( + + ); + } else { + return ( + + ); + } + } + + render () { + return ( +
+

Text: {this.state.text}

+ {this.renderInputField()} + {this.renderButton()} +
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +需要注意的是当组件在预览模式下时,方法 `renderInputField` 返回了一个空的 `div` 元素。 + +然而这并不是必要的。 + +### 避免渲染空元素 + +如果你想要**隐藏**一个组件,你可以让它的渲染方法返回 `null`,因为没必要渲染一个空的(和不同的)元素来占位。 + +需要注意的重要一点是当返回 `null` 时,即使组件并不会被看见,但是生命周期方法仍然被触发了。 + +举个例子,下面的代码实现了两个组件之间的计数器: + +Babel + JSX: + +``` +class Number extends React.Component { + constructor(props) { + super(props); + } + + componentDidUpdate() { + console.log('componentDidUpdate'); + } + + render() { + if(this.props.number % 2 == 0) { + return ( +
+

{this.props.number}

+
+ ); + } else { + return null; + } + } +} + +class App extends React.Component { + constructor(props) { + super(props); + this.state = { count: 0 } + } + + onClick(e) { + this.setState(prevState => ({ + count: prevState.count + 1 + })); + } + + render() { + return ( +
+ + +
+ ) + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +`Number` 组件只有在父组件传递偶数时渲染父组件传递的值,否则,将返回 `null`。然后,当观察控制台输出时,将会发现不管 `render` 返回什么, `componentDidUpdate` 总是会被调用。 + +回头来看我们的例子,像这样来改变 `renderInputField` 方法: + +``` + renderInputField() { + if(this.state.mode === 'view') { + return null; + } else { + return ( +

+ +

+ ); + } + } +``` + +下面是完整的代码: + +Babel + JSX: + +``` +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } + + renderInputField() { + if(this.state.mode === 'view') { + return null; + } else { + return ( +

+ +

+ ); + } + } + + renderButton() { + if(this.state.mode === 'view') { + return ( + + ); + } else { + return ( + + ); + } + } + + render () { + return ( +
+

Text: {this.state.text}

+ {this.renderInputField()} + {this.renderButton()} +
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +返回 `null` 来替代一个空元素的优势在于这将会对组建的性能有一些改善,因为 React 不必要解绑组件来替换它。 + +例如,当执行返回空 `div` 元素的代码时,打开检阅页面元素,将会看到在跟元素下的 `div` 元素是如何被刷新的: + +![](https://cdn-images-1.medium.com/max/800/0*1f--Ics8DXB3UFp_.) + +对比这个例子,当返回 `null` 来隐藏组件时,`Edit` 按钮被点击时 `div` 元素是不更新的: + +![](https://cdn-images-1.medium.com/max/800/0*7SzdmNMiVje-msFz.) + +[这里](https://reactjs.org/docs/reconciliation.html),你将明白更多关于 React 是如何更新 DOM 元素的和“对比”算法是如何运行的。 + +可能在这个简单的例子中,性能的改善是微不足道的,但是当在一个需要频繁更新的组件中时,情况将是不一样的。 + +稍后会详细讨论条件渲染的性能影响。现在,让我们继续改进这个例子。 + +### 元素变量 + +我不喜欢的一件事是在一个方法中有不止一个 `return` 声明。 + +所以我将会使用一个变量来存储 JSX 元素并且只有当条件判断为 `true` 的时候才初始化它: + +``` +renderInputField() { + let input; + + if(this.state.mode !== 'view') { + input = +

+ +

; + } + + return input; + } + + renderButton() { + let button; + + if(this.state.mode === 'view') { + button = + ; + } else { + button = + ; + } + + return button; + } +``` + +这样做是等同于那些返回 `null` 的方法的。 + +以下是优化后的完整代码: + +Babel + JSX: + +``` +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } + + renderInputField() { + let input; + + if(this.state.mode !== 'view') { + input = +

+ +

; + } + + return input; + } + + renderButton() { + let button; + + if(this.state.mode === 'view') { + button = + ; + } else { + button = + ; + } + + return button; + } + + render () { + return ( +
+

Text: {this.state.text}

+ {this.renderInputField()} + {this.renderButton()} +
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +使用这种方式使主 `render` 方法更有可读性,但是可能并没有必要使用 if/else 判断(或者像 `switch` 这样的语句)和辅助的渲染方法。 + +让我们尝试一种更简单的方法。 + +### 三元运算符 + +我们可以使用 [三元运算符](https://en.wikipedia.org/wiki/%3F:) 来代替 if/else 语句: + +``` +condition ? expr_if_true : expr_if_false +``` + +该运算符用大括号包裹,表达式可以包含JSX,可选择将其包含在圆括号中以提高可读性。 + +它可以应用于组件的不同部分。让我们将它应用到示例中,以便您可以看到这个实例。 + +我将在 `render` 方法中删除 `renderInputField` 和 `renderButton`,并添加一个变量用来表示组件是在 `view` 还是 `edit` 模式: + +``` +render () { + const view = this.state.mode === 'view'; + + return ( +
+
+ ); +} +``` + +现在,你可以使用三元运算符,当组件被设置为 `view` 模式时返回 `null`,否则返回输入框: + +``` + // ... + + return ( +
+

Text: {this.state.text}

+ + { + view + ? null + : ( +

+ +

+ ) + } + +
+ ); +``` + +使用三元运算符,你可以通过改变组件的事件处理函数和现实的标签文字来动态的声明它的按钮是保存还是编辑: + +``` + // ... + + return ( +
+

Text: {this.state.text}

+ + { + ... + } + + + +
+ ); +``` + +正如前面所说,三元运算符可以应用在组件的不同位置。 + +可以在 fiddle 中运行查看效果: + +[https://jsfiddle.net/eh3rrera/y6yff8rv/](https://jsfiddle.net/eh3rrera/y6yff8rv/) + +### 与运算符 + +在某种特殊情况下,三元运算符是可以简化的。 + +当你想要一种条件下渲染元素,另一种条件下不渲染元素时,你可以使用 `&&` 运算符。 + +不同于 `&` 运算符,当左侧的表达式可以决定最终结果时,`&&` 是不会再执行右侧表达式的判断的。 + +例如,如果第一个表达式被判定为 false(`false && …`),就没有必要再执行判断下一个表达式了,因为结果将永远是 `false`。 + +在 React 中,你可以使用像下面这样的表达式: + +``` +return ( +
+ { showHeader &&
} +
+); +``` + +如果 `showHeader` 被判定为 `true`,`
` 组件将会被这个表达式返回。 + +如果 `showHeader` 被判定为 `false`,`
` 组件将会被忽略并且一个空的 `div` 将会被返回。 + +使用这种方式,下面的表达方式: + +``` +{ + view + ? null + : ( +

+ +

+ ) +} +``` + +可以被改写为: + +``` +!view && ( +

+ +

+) +``` + +下面是可在 fiddle 中执行的完整代码: + +Banel + JSX: + +``` +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } + + render () { + const view = this.state.mode === 'view'; + + return ( +
+

Text: {this.state.text}

+ + { + !view && ( +

+ +

+ ) + } + + +
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +看起来更好了,不是吗? + +然而,三元表达式不总是看起来这么好。 + +考虑一组复杂的嵌套条件: + +``` +return ( +
+ { condition1 + ? + : ( condition2 + ? + : ( condition3 + ? + : + ) + ) + } +
+); +``` + +这可能会很快变得混乱。 + +出于这个原因,有时您可能想要使用其他技术,例如立即执行函数。 + +### 立即执行函数表达式 (IIFE) + +顾名思义,立即执行函数就是在定义之后被立即调用的函数,他们不需要被显式地调用。 + +通常情况下,你一般会这样定义并执行(定义后执行)一个函数: + +``` +function myFunction() { + +// ... + +} + +myFunction(); +``` + +但是如果你想要在定义后立即执行一个函数,你必须使用一对括号来包裹这个函数(把它转换成一个表达式)并且通过添加另外两个括号来执行它(括号里面可以传递函数需要的任何参数)。 + +就像这样: + +``` +( function myFunction(/* arguments */) { + // ... +}(/* arguments */) ); +``` + +或者这样: + +``` +( function myFunction(/* arguments */) { + // ... +} ) (/* arguments */); +``` + +因为这个函数不会在其他任何地方被调用,所以你可以省略函数名: + +``` +( function (/* arguments */) { + // ... +} ) (/* arguments */); +``` + +或者你也可以使用箭头函数: + +``` +( (/* arguments */) => { + // ... +} ) (/* arguments */); +``` + +在 React 中,你可以使用大括号来包裹立即执行函数,在函数内写所有你想要的逻辑(if/else、switch、三元运算符等等),然后返回任何你想要渲染的东西。 + +例如, 下面的立即执行函数中就是如何判断渲染保存还是编辑按钮的逻辑: + +``` +{ + (() => { + const handler = view + ? this.handleEdit + : this.handleSave; + const label = view ? 'Edit' : 'Save'; + + return ( + + ); + })() +} +``` + +下面是可以在 fiddle 中执行的完整代码: + +Babel + JSX: + +``` +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } + + render () { + const view = this.state.mode === 'view'; + + return ( +
+

Text: {this.state.text}

+ + { + !view && ( +

+ +

+ ) + } + + { + (() => { + const handler = view + ? this.handleEdit + : this.handleSave; + const label = view ? 'Edit' : 'Save'; + + return ( + + ); + })() + } +
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +``` +
+``` + +### 子组件 + +很多时候,立即执行函数看起来可能是一种不那么优雅的解决方案。 + +毕竟,我们在使用 React,React 推荐使用的方案是将你的应用逻辑分解为尽可能多的组件,并且推荐使用函数式编程而非命令式编程。 + +所以修改条件渲染逻辑为一个子组件,这个子组件会依据父组件传递的 props 来决定在不同情况下的渲染,这将会是一个更好的方案。 + +但在这里,我将做一些有点不同的事情,向您展示如何从一个命令式的解决方案转向更多的声明式和函数式解决方案。 + +我将从创建一个 `SaveComponent` 组件开始: + +``` +const SaveComponent = (props) => { + return ( +
+

+ +

+ +
+ ); +}; +``` + +正如函数式编程的属性,`SaveComponent` 的功能逻辑都来自于它接收的参数所指定的。同样的方式定义另一个组件 `EditComponent`: + +``` +const EditComponent = (props) => { + return ( + + ); +}; +``` + +现在 `render` 方法就会变成这样: + +``` +render () { + const view = this.state.mode === 'view'; + + return ( +
+

Text: {this.state.text}

+ + { + view + ? + : ( + + ) + } +
+ ); +} +``` + +下面是可以在 fiddle 中执行的完整代码: + +Babel + JSX: + +``` +const SaveComponent = (props) => { + return ( +
+

+ +

+ +
+ ); +}; + +const EditComponent = (props) => { + return ( + + ); +}; + +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } + + render () { + const view = this.state.mode === 'view'; + + return ( +
+

Text: {this.state.text}

+ + { + view + ? + : ( + + ) + } +
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +### If 组件 + +有像 [jsx-control-statements](https://github.com/AlexGilleran/jsx-control-statements) 这样的库可以扩展JSX来添加如下条件语句: + +``` + + + Hi! + + +``` + +这些库提供更高级的组件,但是如果我们需要简单的 if/else,我们可以参考 [Michael J. Ryan](https://github.com/tracker1) 在 [issue](https://github.com/facebook/jsx/issues/65) 下的 [评论](https://github.com/facebook/jsx/issues/65#issuecomment-255484351): + +``` +const If = (props) => { + const condition = props.condition || false; + const positive = props.then || null; + const negative = props.else || null; + + return condition ? positive : negative; +}; + +// … + +render () { + const view = this.state.mode === 'view'; + const editComponent = ; + const saveComponent = ; + + return ( +
+

Text: {this.state.text}

+ +
+ ); +} +``` + +下面是可以在 fiddle 中执行的完整代码: + +Babel + JSX: + +``` +const SaveComponent = (props) => { + return ( +
+

+ +

+ +
+ ); +}; + +const EditComponent = (props) => { + return ( + + ); +}; + +const If = (props) => { + const condition = props.condition || false; + const positive = props.then || null; + const negative = props.else || null; + + return condition ? positive : negative; +}; + +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } + + render () { + const view = this.state.mode === 'view'; + const editComponent = ; + const saveComponent = ; + + return ( +
+

Text: {this.state.text}

+ +
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +### 高阶组件 + +[高阶组件](https://reactjs.org/docs/higher-order-components.html)(HOC)是一个函数,它接收一个已经存在的组件并且基于这个组件返回一个新的带有更多附加功能的组件: + +``` +const EnhancedComponent = higherOrderComponent(component); +``` + +应用于条件渲染时,一个组件被传递给一个高阶组件,高阶组件可以依据一些条件返回一个不同于原组件的组件: + +``` +function higherOrderComponent(Component) { + return function EnhancedComponent(props) { + if (condition) { + return ; + } + + return ; + }; +} +``` + +这里有一篇 [Robin Wieruch](https://www.robinwieruch.de/about/) 写的 [关于高阶组件的精彩好文](https://www.robinwieruch.de/gentle-introduction-higher-order-components/),这篇文章深入讨论了高阶组件在条件渲染中的应用。 + +在我们这篇文章中,我将会借鉴一些 `EitherComponent` 的概念。 + +在函数式编程中,`Either` 这一类方法的实现通常是作为一个包装,来返回两个不同的值。 + +所以让我们从定义一个接收两个参数的函数开始,另一个函数返回一个布尔值(判断条件的结果),如果这个布尔值为 `true` 则返回组件: + +``` +function withEither(conditionalRenderingFn, EitherComponent) { + +} +``` + +通常高阶组件的函数名都以 `with` 开头。 + +这个函数将会返回另一个函数,这个被返回的函数接收一个原组件并返回一个新的组件: + +``` +function withEither(conditionalRenderingFn, EitherComponent) { + return function buildNewComponent(Component) { + + } +} +``` + +这个被一个内部函数返回的组件(函数)就是你将在你的应用中使用的,所以它接收一个对象,这个对象具有它运行所需的所有属性: + +``` +function withEither(conditionalRenderingFn, EitherComponent) { + return function buildNewComponent(Component) { + return function FinalComponent(props) { + + } + } +} +``` + +内部函数可以访问到外部函数的参数,因此,根据函数 `conditionalRenderingFn` 的返回值,你可以判断返回 `EitherComponent` 或者原 `Component`: + +``` +function withEither(conditionalRenderingFn, EitherComponent) { + return function buildNewComponent(Component) { + return function FinalComponent(props) { + return conditionalRenderingFn(props) + ? + : ; + } + } +} +``` + +或者使用箭头函数: + +``` +const withEither = (conditionalRenderingFn, EitherComponent) => (Component) => (props) => + conditionalRenderingFn(props) + ? + : ; +``` + +通过这个方式,使用原来定义的 `SaveComponent` 和 `EditComponent`,你可以创建一个 `withEditConditionalRendering` 高阶组件,并且通过它可以创建一个 `EditSaveWithConditionalRendering` 组件: + +``` +const isViewConditionFn = (props) => props.mode === 'view'; + +const withEditContionalRendering = withEither(isViewConditionFn, EditComponent); +const EditSaveWithConditionalRendering = withEditContionalRendering(SaveComponent); +``` + +这样一来你就只需在render方法中使用该组件,并向它传递所有需要用到的属性: + +``` +render () { + return ( +
+

Text: {this.state.text}

+ +
+ ); +} +``` + +下面是可以在 fiddle 中执行的完整代码: + +Babel + JSX: + +``` +const SaveComponent = (props) => { + return ( +
+

+ +

+ +
+ ); +}; + +const EditComponent = (props) => { + return ( + + ); +}; + +const withEither = (conditionalRenderingFn, EitherComponent) => (Component) => (props) => + conditionalRenderingFn(props) + ? + : ; + +const isViewConditionFn = (props) => props.mode === 'view'; + +const withEditContionalRendering = withEither(isViewConditionFn, EditComponent); +const EditSaveWithConditionalRendering = withEditContionalRendering(SaveComponent); + +class App extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', inputText: '', mode:'view'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSave = this.handleSave.bind(this); + this.handleEdit = this.handleEdit.bind(this); + } + + handleChange(e) { + this.setState({ inputText: e.target.value }); + } + + handleSave() { + this.setState({text: this.state.inputText, mode: 'view'}); + } + + handleEdit() { + this.setState({mode: 'edit'}); + } + + render () { + return ( +
+

Text: {this.state.text}

+ +
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +### 性能考量 + +条件渲染可能是复杂的。就像前面我所展示的那样,每种方式的性能也可能是不同的。 + +然而,在大多数时候这种差别是不成问题的。但当它确实造成问题时,你将需要深入理解 React 的虚拟 DOM 的工作原理,并且使用一些技巧来[优化性能](https://reactjs.org/docs/optimizing-performance.html)。 + +这里有一篇关于很好的文章,关于 [优化React的条件渲染](https://medium.com/@cowi4030/optimizing-conditional-rendering-in-react-3fee6b197a20),我非常推荐你读一下。 + +基本的思想是条件渲染导致改变组件的位置将会引起回流,从而导致应用内组件的解绑/绑定。 + +基于这篇文章的例子,我写了两个例子: + +第一个例子使用 if/else 来控制 `SubHeader` 组件的显示/隐藏: + +Babel + JSX: + +``` +const Header = (props) => { + return

Header

; +} + +const Subheader = (props) => { + return

Subheader

; +} + +const Content = (props) => { + return

Content

; +} + +class App extends React.Component { + constructor(props) { + super(props); + this.state = {isToggleOn: true}; + + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + this.setState(prevState => ({ + isToggleOn: !prevState.isToggleOn + })); + } + + render() { + if(this.state.isToggleOn) { + return ( +
+
+ + + +
+ ); + } else { + return ( +
+
+ + +
+ ); + } + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +第二个例子使用与运算(`&&`)做同样的事情: + +Babel + JSX: + +``` +const Header = (props) => { + return

Header

; +} + +const Subheader = (props) => { + return

Subheader

; +} + +const Content = (props) => { + return

Content

; +} + +class App extends React.Component { + constructor(props) { + super(props); + this.state = {isToggleOn: true}; + + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + this.setState(prevState => ({ + isToggleOn: !prevState.isToggleOn + })); + } + + render() { + return ( +
+
+ { this.state.isToggleOn && } + + +
+ ); + } +} + +ReactDOM.render( + , + document.getElementById('root') +); +``` + +打开元素检查并且点击几次按钮。 + +你将看到在每一种实现中 `Content` 是被如何处理的。 + +### 结论 + +就像编程中的很多事情一样,在 React 中有很多种方式实现条件渲染。 + +我会说除了第一种方式(有多种返回的if/else),你可以任选你喜欢的方式。 + +基于下面的原则,你可以决定哪一种方式在你的实际情况中是最好的: + +* 你的编程风格 +* 条件逻辑的复杂程度 +* 使用 JavaScript、JSX和高级的 React 概念(比如高阶组件)的舒适度。 + +如果所有的事情都是相当的,那么就追求简明度和可读性。 + +* * * + +### Plug: LogRocket, a DVR for web apps + +[![](https://cdn-images-1.medium.com/max/1000/1*s_rMyo6NbrAsP-XtvBaXFg.png)](http://logrocket.com) + +[LogRocket](https://logrocket.com) 是一款前端日志工具,能够在你自己的浏览器上复现问题。而不是去猜为什么发生错误或者向用户要截图和日志,LogRocket 帮助你复现场景来快速理解发生了什么错误。 它适用于任何应用程序,且和框架无关,并且具有从Redux,Vuex和@ngrx/store记录其他上下文的插件。 + +除了记录Redux动作和状态之外,LogRocket 还记录控制台日志,JavaScript 错误,堆栈跟踪,带有头信息+主体的网络请求/响应,浏览器元数据和自定义日志。它还可以检测 DOM 来记录页面上的 HTML 和 CSS,即使是最复杂的单页面应用,也能还原出像素级的视频。 + +免费试用。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/crafting-beautiful-ux-with-api-requests.md b/TODO1/crafting-beautiful-ux-with-api-requests.md new file mode 100644 index 00000000000..e41018efe27 --- /dev/null +++ b/TODO1/crafting-beautiful-ux-with-api-requests.md @@ -0,0 +1,137 @@ +> * 原文地址:[Crafting beautiful UX with API requests](https://uxdesign.cc/crafting-beautiful-ux-with-api-requests-56e7dcc2f58e) +> * 原文作者:[Ryan Baker](https://uxdesign.cc/@ryan.da.baker?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/crafting-beautiful-ux-with-api-requests.md](https://github.com/xitu/gold-miner/blob/master/TODO1/crafting-beautiful-ux-with-api-requests.md) +> * 译者:[MeFelixWang](https://github.com/MeFelixWang) +> * 校对者:[sunhaokk](https://github.com/sunhaokk) + +# 用 API 请求制作赏心悦目的 UX + +## 在构建 Web 应用时,首先要创建一个优雅且响应迅速的体验。 + +试图控制超出 Web 应用程序范围的体验通常是事后的想法。工程师忘记了处理从 API 请求数据时可能会遇到的所有麻烦事情。在本文中,我将为你提供三种模式(包括代码片段),以使你的应用程序能弹性应对不可预测的情形。 + +![](https://cdn-images-1.medium.com/max/1000/1*lEMi48f7LTbhCpaFKQVM6A.jpeg) + +让你的用户和这个愚蠢的人类一样快乐 + +### 模式 1:超时 + +超时是一种简单的模式。简而言之,就是:“如果你的反应比我想要的慢,请取消我的请求”。 + +#### 什么时候用 + +你应该使用超时来设置你希望请求耗用的时长**上限**。有什么可能会使你的 API 响应时间比预期的长?这取决于你的 API,但以下是一些现实场景的示例: + +你的服务器与数据库进行通信。数据库宕机了,但服务器的连接超时为 30 秒。服务器将花费完整的 30 秒来确定它无法与数据库通信。这意味着你的用户将等待 30 秒! + +你使用了 AWS 负载均衡器,其背后的服务器已宕机(无论出于何种原因)。你将负载均衡器超时保留为[默认值 60 秒](https://aws.amazon.com/blogs/aws/elb-idle-timeout-control/),并且在失败之前一直尝试连接服务器。 + +#### 什么时候不用 + +如果你的 API 已知响应时间具有可变性,则不应使用超时。一个很好的例子可能是返回报告数据的 API。请求一天的数据是快速的(可能是亚秒响应时间),但请求八个月的数据大约需要 12 秒。 + +**如果你无法确定对于请求应该花多长时间的可靠上限,则不要使用超时。** + +#### 如何使用 + +假设你的应用程序中有一个方法可以做到这一点: + +![](https://cdn-images-1.medium.com/max/800/1*VrWx5PPIf84n8PKfaxCi8g.png) + +示例方法可能存在于 React 组件内部 + +你知道你的 API 在 99% 的时间里会在 3 秒内响应。假设你使用 [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 从 API 获取数据,你可以这样做: + +![](https://cdn-images-1.medium.com/max/800/1*n4ONmQQn8dwfd674LIPfLw.png) + +为你的 API 调用配置超时 + +**注意:你可能用于进行 API 调用的大多数库都具有超时配置。请使用你工具的内置功能,而不是自己编写** + +### 模式 2:最短等待时间 + +最短等待时间也是一种简单的模式。它与超时相反:它可以保护你的应用免受 API **快速**响应的影响。 + +#### 什么时候用 + +如果要向用户显示加载状态,则最短等待时间是一种非常好的模式,但 API 可能会快速响应。结果就是用户会看到加载状态,接着数据“弹出”进入视图,然后其才能专注于想做的事。 + +这不是一个良好的体验。如果你显示加载状态,你是在告诉用户“稍等,我们正在处理些事儿,我们会马上回来”。这让用户可以喘口气,也许查看一下他们的手机 —— 如果用户看到加载状态,那么用户**希望等待**。如果你获取太快,那就太突兀了。你打断了她的休息,让她变得紧张。 + +#### **什么时候不用** + +当你拥有响应速度始终非常快的 API 时,最好避免使用最短等待模式。**不要**为了添加加载状态而添加,如果不需要,就**不要**让用户等待。 + +#### 如何使用 + +使用上面的示例,你可以编写代码“在这两件事完成之前不做任何事”,如下所示: + +![](https://cdn-images-1.medium.com/max/800/1*-eXymmc8GfkuGTG4XrBMfw.png) + +强制请求的最短等待时间 + +### 模式 3:重试 + +重试模式是我将要介绍的最复杂的模式。基本的想法是,如果得到错误的响应,我们想要重试几次请求。这是一个非常简单的想法,但在使用它时需要记住一些注意事项。 + +#### 什么时候用 + +当你向可能发生间歇性故障的 API 发出请求时,你会希望使用此方法。当知道请求会**不时**因为无法控制的问题而失败时我们几乎都希望重试。 + +就我而言,当我知道我正在发出使用特定数据库的请求时,我会经常使用它。访问该数据库时,有时它会失败。是的,这很糟糕。是的,这是我们应该解决的问题。作为应用程序开发人员,当被告知“暂时处理它”时,我们可能没有能力修复底层基础架构问题。这就是你想要重试的时候。 + +#### 什么时候不用 + +如果我们拥有可靠的且始终如一的响应式 API,则无需重试。如果响应失败并且重试后依然不能成功响应,那我们也就不需要重试了。 + +大多数 API 都是一致的。这就是为什么你需要小心这个模式: + +#### 如何使用 + +我们希望确保在发出请求时,不会对服务器造成冲击。想象一下因为负载过重造成服务器宕机的情形吧。重试将把一个已死的服务器再埋到六英尺深的地下。出于这个原因,我们在进行后续请求时需要所谓的**退避策略**。我们不希望在服务器宕机的情况下仍然立即一个接一个地发出 5 个请求。我们应该错开它们以减少 API 服务器上的负载。 + +大多数情况下,我们使用**指数退避**来确定在发送下一个请求之前我们应该等待多长时间。我们通常只想重试 3 次,所以这里有一个使用不同函数的等待时间示例: + +![](https://cdn-images-1.medium.com/max/600/1*SrIVlW-y7ihWboBqzM6O9A.png) + +立即发送第一个请求。它失败了。接下来,我们需要确定在发送第一次重试之前使用退避策略等待多长时间。让我们看一下这些曲线,其中 X 等于我们已经发送的重试次数。 + +使用我们的二次(y = x²)函数和线性(y = x)函数,在第一个等待时间内我们得到 0,即应该立即发送下一个请求。 + +所以可以在运行时消除这两个函数了。 + +使用指数(y = 2^x)函数和常数(y = 1)函数,我们得到 1 秒的等待时间。 + +常数函数使我们无法灵活处理已经发送的重试次数,从而改变我们应该等待的时间。 + +这就只剩下指数函数了。让我们编写一个函数,来告诉我们根据已经发送的重试次数确定等待多少秒: + +![](https://cdn-images-1.medium.com/max/800/1*3D0xaSIUBz-M5-h1ccbZuA.png) + +简单的 y = 2^x 函数 + +在编写重试函数之前,我们想要一种方法来确定请求是否错误。假设状态码大于或等于 500 时,请求是错误的。这个就是我们可以为此编写的函数了: + +![](https://cdn-images-1.medium.com/max/800/1*y2ir3VPSLIbr1aWi_WcERg.png) + +如果响应错误,我们的函数会抛出自定义错误 + +请记住,你可能有不同的标准来确定请求是否失败。最后,我们可以使用指数退避策略编写重试函数: + +![](https://cdn-images-1.medium.com/max/1000/1*kcvzvrQ58jm8GaCRmAKYvA.png) + +我们使用指数退避策略重试 + +你会注意到我创建了一个我没有导出的函数(`_retryWithBackoff`)。使用我们的重试函数时,调用代码不能在迭代中显式传递。 + +### 总结 + +有很多很好的防御模式可以提供良好的用户体验。这三个你今天就可以使用!如果你有兴趣了解更多,我建议阅读 [**Release It**](https://www.amazon.com/Release-Design-Deploy-Production-Ready-Software/dp/1680502395/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=BNBXXWPWRX7DEQ4CWMKB)!一本关于如何在构建可扩展软件时解决这些确切问题的书。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/creating-with-a-design-system-in-sketch-part-one-tutorial.md b/TODO1/creating-with-a-design-system-in-sketch-part-one-tutorial.md new file mode 100644 index 00000000000..ee3c7f0f1ea --- /dev/null +++ b/TODO1/creating-with-a-design-system-in-sketch-part-one-tutorial.md @@ -0,0 +1,219 @@ +> * 原文地址:[Creating with a Design System in Sketch: Part One [Tutorial]](https://medium.com/sketch-app-sources/creating-with-a-design-system-in-sketch-part-one-tutorial-5116e36213f9) +> * 原文作者:[Marc Andrew](https://medium.com/@marcandrew?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-one-tutorial.md](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-one-tutorial.md) +> * 译者:[pmwangyang](https://github.com/pmwangyang) +> * 校对者:[Zheng7426](https://github.com/Zheng7426) + +# 在 Sketch 中使用一个设计体系创作:第一部分[教程] + +## 在 Sketch 中建立一个设计体系并使用它工作 + +![](https://cdn-images-1.medium.com/max/1000/1*jwTJroljaX-67eahDjPGKw.png) + +### 🎁 想用我的优质 Sketch 设计体系大幅优化你的工作流程吗?你可以点击[这里](https://kissmyui.com/cabana)获取 Cabana。 + +使用推广码 **MEDIUM25** 购买可享 **75 折**优惠。 + +![](https://cdn-images-1.medium.com/max/800/1*aEcIFESUCKiFVRpssVQTOA.jpeg) + +* * * + +**我看到过许多介绍建立 Sketch 设计体系元素的教程,但是很少有教程会实际上教你在练习中创建新的、特别好的设计体系。** + +这就是我这一系列教程想要做的 —— 不仅仅是教你创建设计体系的元素,还有如何用你创建的体系设计一个适配多个设备的 iOS App,并且告诉你我如何构建自己的体系以及背后的思考过程和决策。 + +### 系列导航 + +* **第一部分** +* [第二部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-two-tutorial.md) +* [第三部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-three-tutorial.md) +* [第四部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-four-tutorial.md) +* [第五部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-five-tutorial.md) +* [第六部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-six-tutorial.md) +* [第七部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-seven-tutorial.md) +* [第八部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-eight-tutorial.md) +* [第九部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-nine-tutorial.md) + +* * * + +![](https://cdn-images-1.medium.com/max/1000/1*a-kVsheThBDPzeyMSGDLjQ.jpeg) + +### 设计体系总览 + +好,在我们埋头开始设计我们这非常华丽且风格类似 Medium 的 iOS 应用之前(谁说山寨来着?),我们对一会教程中将要用到的设计体系(基于 Cabana-Lite)的 Sketch 文件进行一个快速概览。 + +在设计版式(开始)文件中有三个页面…… + +* _Design System (Setup)_ **设计体系(设置)** +* _Symbols_ **组件** +* _Format_ **格式** + +让我们按顺序说…… + +#### 设计体系(设置) + +![](https://cdn-images-1.medium.com/max/800/1*5K_jofmNF-5emgDSd7PejA.jpeg) + +这就是见证奇迹的地方!这里可以管理你的项目中至少 90% 的样式。 + +设置这些元素为基准颜色或文字样例,这样你调整它们的时候,你的所有设计都会自动适应变化。 + +你在这里的所有改动会映射到组件页面(一会我们会涉及),当然,也会自动适应到你当前的画板上。 + +在这个页面上有 2 个画板…… + +* _Colors + Overlays + Duotone_ (译者注:画板名比较小,注意左上角>_<) +* _Typography_ (我们会在第二部分涉及到这个画板) + +#### Colors + Overlays + Duotone (颜色 + 覆盖色 + 双色调) + +![](https://cdn-images-1.medium.com/max/800/1*9DjFvdT281n_nZb2sLgejA.jpeg) + +通过这个画板你可以看到,我将所有的颜色资源组织到了一起。如基准色(Base Colors),叠加色(Color Overlays)和图片效果(在这个例子里是双色调效果 “Duotone Image”)。 + +其实在我个人的 Cabana 设计体系里我做了一点分割,将基准色、叠加色添加到了 Colors 画板,像双色调图之类的添加到另一个名为 Various 的画板,这个画板还包含渐变、边框阴影等。但我想让你感觉这个教程更紧凑些,所以采取这样的布局方式,还可以吧? + +#### Base Colors(基准颜色) + +![](https://cdn-images-1.medium.com/max/800/1*EEaKR_Kq0sLD54eRgFbLJQ.jpeg) + +在这个系列教程里,设计我们的 iOS App 只需要 4 种基准色。如果你创建你自己的体系,需要在一个大型项目中覆盖所有的基准色,创建像下面这些基准色是一个明智的选择(当然这只是建议)…… + +* _Primary_ **(原色,译者注:或者可以称为“主题色”)** +* _Secondary_ **(二次色)** +* _Tertiary_ **(第三色)** +* _Black_ **(黑色)** +* _Grey_ **(灰色)** +* _Light Grey_ **(淡灰色)** +* _Success_ **(成功色)** +* _Warning_ **(警告色)** +* _Error_ **(错误色)** + +你可以把上面的列表替换成自己想要的内容,比如移除第三色、添加另一种深度的灰色,以获得一些自定义元素,来完成适合自己设计体系的一些项目。 + +好,让我们回头看看这些基准色,我给你一些在我自己的设计体系中设置基础颜色的秘诀 —— 使用 **图层样式**。 + +我们首先设置一下原色描边,创建一个 **200x200** 的矩形(快捷键“R”),移除填充色,用我选定的十六进制颜色设置 **1px** 的描边,并设置圆角半径为 **4**。 + +![](https://cdn-images-1.medium.com/max/800/1*Vn_ITS4EHqh7sxlvtjujRA.jpeg) + +然后创建一个新的图层样式(在 Prototyping 栏中选择 Create new Layer Style)…… + +![](https://cdn-images-1.medium.com/max/800/1*mK2HsJYdyNqsEJ6rgaytgg.jpeg) + +并把它命名为 _Border/Primary_ **(描边/原色)**…… + +![](https://cdn-images-1.medium.com/max/800/1*rz6lSqepDeLmbYjPreTwEQ.jpeg) + +再设置一个原色填充矩形,创建一个**200x200** 的矩形(快捷键“R”),选择我选定的十六进制颜色,并设置圆角半径为 **4**。 + +![](https://cdn-images-1.medium.com/max/800/1*Q0JRENrjTqBCSwpQHOrvqQ.jpeg) + +然后创建一个新的图层样式并命名为 _Fill/Primary_ **(填充/原色)**。 + +![](https://cdn-images-1.medium.com/max/800/1*Xs9Crw81EXCXdh4MCwHiVA.jpeg) + +然后我将这两个矩形重叠,你可能要问,为什么这么做? + +这允许我们使用这样的设计体系时,仅仅选择一次就能很容易的修改图层样式,从而改变描边和填充色。 + +这样占据的屏幕更小,并且最重要的是,比这儿放一个 **A 元素**那儿放一个 **B 元素**改动起来快多了。 + +接下来,我在合适的位置设置好所有的基准色和对应的图层样式后,给它们设置好名称(比如 Primary、Black、Grey 等等)。 + +![](https://cdn-images-1.medium.com/max/800/1*zleRk-jDNjwSQM0rnyZXhw.jpeg) + +现在我有了方便的参考点,并且鼠标点几下就能调整。比如,如果需要改变原色,选中它,再选择图层样式,就全部搞定了不是吗?不需要任何多余操作,也不用忍受“不不不,我不是要选中这个元素”这种令人抓狂的事。 + +接下来重复这个过程,将我上文提到过的所有其他基准色(黑色、灰色等等)设置好图层样式,命名为和 _Border/Primary_ 和 _Fill/Primary_ 同样的格式。 + +#### Color Overlays (颜色叠加层) + +![](https://cdn-images-1.medium.com/max/800/1*_NEQy-MOpVB6kRL4PtdjIA.jpeg) + +在这个教程里,我只在叠加颜色中建立了一个名为 Black(黑色)的叠加层。 + +把 Black 层叠加到图片上来调整对比度很容易,它的十六进制色统一地取自基准色 _Black_**(黑色)**。 + +就像我提到的基准色一样,举一反三,在你的设计体系中,实际上只要让叠加层来匹配以下几个基准色…… + +* _Primary_ **原色** +* _Secondary_ **二次色** +* _Black_**(刚刚这个例子中使用的)** + +我来给你一些指引,告诉你如何创建颜色叠加层,当然,在我的设计体系里,还是使用图层样式。 + +现在我主要讲解下面教程里将要用到的黑色叠加层。 + +创建一个 **432x248** (这个尺寸是我随便选的,你可以设置其他尺寸)的矩形(快捷键“R”)并设置圆角半径为 **4**(个人喜好,这样看起来更漂亮一些),粘贴之前创建的 Black 基准色的十六进制色值,然后设置不透明度为 60%。 + +![](https://cdn-images-1.medium.com/max/800/1*OCNWm39eED210ruevgB85w.jpeg) + +然后创建一个新的名为 _Overlay/Black_**(叠加层/黑色)**的图层样式。 + +![](https://cdn-images-1.medium.com/max/800/1*kVA7DcMOm0NF1oaRrcno-A.jpeg) + +这就完成了,但是考虑到这个叠加层 99% 的情况是覆盖在一个图片上,我想现在最明智的事,是在新的叠加图层样式旁边添加一个小小的预览。就像我刚刚提到的,它在我的设计中位于图片的顶部,这意味着我可以更好的预览叠加层的效果,并且允许我调整它的不透明度,直到我对结果满意为止。 + +让我来教你怎么做…… + +首先创建一个和前面创建过的颜色叠加层尺寸一致的矩形(R),并且用图片填充它。 + +![](https://cdn-images-1.medium.com/max/800/1*U8AQvkA5u9n8KCw5loa8gQ.jpeg) + +接下来创建一个同样尺寸的矩形(R),覆盖在图片上,然后套用刚刚创建的 _Overlay/Black_**(叠加层/黑色)**图层样式。 + +![](https://cdn-images-1.medium.com/max/800/1*khyh4RrFpHT1aH4jYjCC_w.jpeg) + +就像我刚才说的一样,现在我有一个实时的预览,可以观察叠加层添加到图像时的外观,并可以相应地调整,直到我对结果满意为止。 + +#### Duotone (双色调图) + +最后,让我们来学习双色调图片,我们在教程中只展示了一种双色调图片样式,但是在 Cabana 设计体系中我创建了 9 种之多。 + +是的,像双色调图片或者渐变的存在只是为了好看,并不是你自己设计体系里像基准色和阴影(译者注:也可译为“图层阴影”)一样的必要元素。但我为什么提到它们呢?因为你永远不会知道你的项目中会包含什么玩意儿。 + +好,在我们完成这部分教程之前,让我告诉你如何用我自己的体设计系和设计版式(开始)文件快速创建双色调图片,我们可以称之为“奖励关卡” ^_^ + +像我刚刚做叠加层图像预览一样,创建一个矩形(R),用图像填充它。 + +![](https://cdn-images-1.medium.com/max/800/1*BYB-1sB80cuCUX2ASs6u-g.jpeg) + +然后只需要在元素中添加几个额外的填充颜色,并调整混合模式,直到有一些颜色可以透过来,就像文件中包含的示例那样,这就叫“双色调”(当当当当!过关~)…… + +* _#041674_ & _Lighten_ **光照** +* _#1EDE81_ & _Multiply_ **正片叠底** + +![](https://cdn-images-1.medium.com/max/800/1*H_XjH44nZrhzyKyCVev12Q.jpeg) + +![](https://cdn-images-1.medium.com/max/800/1*N-Tpy9zVquh_XpAhoL7rew.jpeg) + +我们来优化一下,在检查器中拖拽来重新排列填充样式,直到如下图所示 + +![](https://cdn-images-1.medium.com/max/800/1*dhaaEb1gIlKKkNXTcwMFvA.jpeg) + +现在给这个预览起个酷炫狂拽吊炸天的名字(比如:哥布林),机智如我! + +* * * + +好了,这一系列教程的第一部分就圆满结束了,记得回来和我一起学习第二部分哦。第二部分会涉及设计体系中的文字排版,还有我如何整合这一部分到设计体系中的重要的提示和建议。 + +**跳转到第二部分点击[这里](https://medium.com/sketch-app-sources/creating-with-a-design-system-in-sketch-part-two-tutorial-445e0264556a)…** + +### 🎁 想用我的优质 Sketch 设计体系大幅优化你的工作流程吗?你可以点击[这里](https://kissmyui.com/cabana)获取 Cabana。 + +使用推广码 **MEDIUM25** 购买可享 **75 折**优惠。 + +**感谢阅读** + +**马克** + +**设计师、作家、父亲以及哈什·布朗斯的爱人** + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/creating-with-a-design-system-in-sketch-part-two-tutoria.md b/TODO1/creating-with-a-design-system-in-sketch-part-two-tutoria.md new file mode 100644 index 00000000000..db801e34d79 --- /dev/null +++ b/TODO1/creating-with-a-design-system-in-sketch-part-two-tutoria.md @@ -0,0 +1,136 @@ +> * 原文地址:[Creating with a Design System in Sketch: Part Two [Tutorial]](https://medium.com/sketch-app-sources/creating-with-a-design-system-in-sketch-part-two-tutorial-445e0264556a) +> * 原文作者:[Marc Andrew](https://medium.com/@marcandrew?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-two-tutoria.md](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-two-tutoria.md) +> * 译者:[Zheng7426](https://github.com/Zheng7426) +> * 校对者:[pmwangyang](https://github.com/pmwangyang) + +# 在 Sketch 中使用一个设计体系创作: 第二部分 [教程] + +## 创建和玩转一个 Sketch 的设计体系 + +* * * + +### 🎁 想用我这针对 Sketch 的优质设计体系来飞速提升你的工作流程吗?你可以从[这里](https://kissmyui.com/cabana)获取一份 Cabana。 + +输入这个促销码 **MEDIUM25** 就能得到 **七五折的优惠**。 + +![](https://cdn-images-1.medium.com/max/800/1*aEcIFESUCKiFVRpssVQTOA.jpeg) + +* * * + +我在这个全面的系列教程里会给你提供关于如何构建你自己设计体系有价值的干货(以及我如何构建自己的体系),之后在为一个叫 **format** 的 App (风格类似 Medium 网页)构建设计时咱们把这些学过的元素都整合在一起并实践出来。 + +### 本系列教程目录 + +* [第一部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-one-tutorial.md) +* **第二讲** +* [第三部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-three-tutorial.md) +* [第四部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-four-tutorial.md) +* [第五部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-five-tutorial.md) +* [第六部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-six-tutorial.md) +* [第七部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-seven-tutorial.md) +* [第八部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-eight-tutorial.md) +* [第九部分](https://github.com/xitu/gold-miner/blob/master/TODO1/creating-with-a-design-system-in-sketch-part-nine-tutorial.md) + + +* * * + +### 文字设计 + +![](https://cdn-images-1.medium.com/max/800/1*HkYiqCoiWKrqrD_k-FLLQw.jpeg) + +嘿嘿。 接下来我们来讲设计体系的文字设计 (文字风格)。为了更好地达到教学目的,在新手包裹(咱们开始设计 iOS App 时会接触到)的文字风格是我在 Cabana 里所使用的文字风格的精简版。 + +在我逐步构建出 Cabana 设计体系的过程中,最耗时的元素莫过于文字设计了。创建文字的风格是件苦差事,然而当我开始将他们付诸实践时就能看到其优点所在。不过不管怎么说,要把他们都一一整合绝非易事。 + +![](https://cdn-images-1.medium.com/max/800/1*AJ1Kize1DQ0RLs3cLSiPQA.jpeg) + +像我之前提到过的,在本系列教程中我只囊括了文字风格精简之后的4种色彩样式: +- 黑 +- 灰 +- 白 +- 原色 + +当然啦,我在第一讲中也提到过,如果你打算创建一个十分丰满的设计体系的话,那么你可以创建有着以下几种色彩选择的文字风格: + +- 黑 +- 灰 +- 浅灰 +- 白 +- 原色 +- 红 +- 绿 +- …或者任何其他颜色用来作为你的底色 + +以上呢就是我为自己的设计体系所做的选择,其实和我之前所创建的底色的选择差不了多少。 + +### 为何如此麻烦? + +有一天有人问我为啥子我得为两种字体家族(Font Family 1 与 Font Family 2)创建这么多不同的字重和字型大小 —— 这样不是自找麻烦吗? + +我见过有些设计体系,能够只为了一个标题专门构建一个字体家族,然后另建一个内容主体的字体家族,再来一个专门为导引的… + +我个人觉得这样做的话才是真的麻烦,而且在之后的过程中容易出岔子。 + +回到我的做法,在整个设计体系的创建阶段确实会更累人(花了不少时间吧?)。不过呢,一旦你手头上有了两种字体家族中所有不同的自重和字型大小,你就能很自在地说 “我在这个项目中全程只用到 Proxima Nova(属于字体家族1号),并且我有H1、H2、Body(内容主体)和 Lead(导引)以及其他所有内容分类,而不是项目做到一半了才发现我第一个字体家族中没有 Body,而且字体家族 2 号里没有 H1,然后得回头重新完善现有的体系,真是令人感受到淡淡的忧伤!” + +### 为何我文字设计的选项命名如此奇怪? + +还有人提到为啥子我文字设计的选项叫做… + +- Font Family #1 (字体家族 1 号) +- Font Family #2 (字体家族 2 号) + +同样的,我见过有些设计体系是直接用字体家族原本的名称来标注文字风格的,比如说 — _Lato_, _Open Sans_, _Proxima Nova_ 等等… + +然后你会看到以下的画面: + +**_H1 > Proxima Nova > Left > Black_** + +先声明一下我并不是完全不赞同这样的方案,如果你能适应的话那你很棒棒。然而我个人觉着吧,比如说当你决定地把 _Proxima Nova_ 换成 _Helvetica_ 的时候,这便成了会使整个过程变慢的另一个因素。虽然说当你想要切换成不同的文字风格的时候,有 Sketch的插件可以做到这一点,但既然可以避免淌这趟浑水你又何必多此一举呢,对吧? + +如果你习惯于 90% 的情况下在,标题上用字体家族 1 号,而在内容主体、导引段落等地方使用字体家族 2 号。。。那么看起来这就是你的老习惯,所以……当你决定把字体从 Proxima Nova 换成 Comic Sans ,而不得不更改文字样式名称的时候,千万别对插件火冒三丈啊。 + +### 先看这儿,朋友!! + +**如果你对于我如何在自己的设计体系里构建文字设计的元素还想要有更深的了解的话,可以阅读我之前写过的** [**文章**](https://medium.com/sketch-app-sources/how-to-create-a-design-system-in-sketch-part-one-fd450dccab10) **(直接跳到文字设计的部分), 完事之后记得回到这里哈。** + +你看完这篇 [**文章**](https://medium.com/sketch-app-sources/how-to-create-a-design-system-in-sketch-part-one-fd450dccab10)了吗?”酷,我们现在在一个节奏上,很稳! + +就像在第一讲中我曾经对基准色元素所做的那样,当我完成了两套字体家族的调试之后,给他们加上相对应的标题(比如 字体家族 #1 (黑), 字体家族 #2 (灰)等等。然后把他们放在一块儿并且锁定。 + +我对字体家族1号和字体家族 2 号(白色)做了类似的设置,为了有明显的对比色我把背景设置成黑色,然后也给锁定了。 +现在我可以简单地选到这个部分,拖拽光标去选择一大块文字了… + +![](https://cdn-images-1.medium.com/max/800/1*RTccjxnSeMvzpOFHk0UxwQ.jpeg) + +…用 Inspector 来更新字体,不用担心一不小心改变了参照的标题或者把背景层拖到了屏幕里。 + +![](https://cdn-images-1.medium.com/max/800/1*72TdwduU1t-2nIrLbO9SMQ.jpeg) + +**当你重复这么做20次的时候会不会非常头疼?** + +希望藉着本篇丰富的干货以及这篇之前提到的[好文](https://medium.com/sketch-app-sources/how-to-create-a-design-system-in-sketch-part-one-fd450dccab10),你现在对于创建出你自己设计体系最棒的文字设计有了更加专业的想法。 + +* * * + +好了,本系列教程的第二讲到此为止。请继续阅读第三讲,在第三讲中我将会提到设计体系中用到的 Symbols 以及更多的内容,以及一些实用且绝妙的诀窍和提示,还有我如何将其融入到我的设计体系中的想法。 +**想前往第三部分就点[这里](https://medium.com/sketch-app-sources/creating-with-a-design-system-in-sketch-part-three-tutorial-105b12a0944a)…** + +### 🎁 想用我这针对 Sketch 的优质设计体系来飞速提升你的工作流程吗?你可以从[这里](https://kissmyui.com/cabana)拷贝一份 Cabana。 + +输入这个促销码 **MEDIUM25** 就能得到**七五折的优惠**哦。 + +**谢谢阅读本文** + +**Marc** + +**设计师, 作者, 父亲,以及一两个奇葩渐变色的爱好者** + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/css-variables-dynamic-app-themes.md b/TODO1/css-variables-dynamic-app-themes.md new file mode 100644 index 00000000000..887f046f1e0 --- /dev/null +++ b/TODO1/css-variables-dynamic-app-themes.md @@ -0,0 +1,107 @@ +> * 原文地址:[Dynamic App Themes with CSS Variables and JavaScript 🎨](https://itnext.io/css-variables-dynamic-app-themes-86c0db61cbbb) +> * 原文作者:[Mike Wilcox](https://itnext.io/@mjw56?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/css-variables-dynamic-app-themes.md](https://github.com/xitu/gold-miner/blob/master/TODO1/css-variables-dynamic-app-themes.md) +> * 译者:[CoolRice](https://github.com/CoolRice) +> * 校对者:[Yifan Xiang](https://github.com/diliburong), [CoderMing](https://github.com/CoderMing) + +# CSS 变量和 JavaScript 让应用支持动态主题 🎨 + +![](https://cdn-images-1.medium.com/max/1000/1*tZ4wAfvhrQpuzvM-pZkkmg.jpeg) + +大家好!在这篇文章中我准备讲一讲我在 Web 应用中创建动态主题加载器的方法。我会讲一点关于 React、Create-React-App、Portals、Sass、CSS 变量还有其它有意思的东西。如果你对此感兴趣,请继续阅读! + +我正在开发的应用是一个音乐应用程序,它是 Spotify 的迷你克隆版。前端代码[基于 Create-React-App](https://reactjs.org/docs/create-a-new-react-app.html#create-react-app)。添加了 [node-sass-chokidar](https://github.com/michaelwayman/node-sass-chokidar) 使得 CRA 支持 Sass。 + +![](https://cdn-images-1.medium.com/max/800/1*eONilVt2-KF6bpIu9OxhzQ.png) + +集成 Sass + +给 CRA 添加 Sass 并不困难。我仅仅需要安装 `node-sass-chokidar` 然后在 package.json 文件添加一些脚本,这些脚本告诉 `node-sass-chokidar` 怎样去编译 Sass 文件并且在开发时能够监视文件变化以再次编译。`include-path` 标志让 `node-sass-chokidar` 知道去哪寻找通过 `@import` 引入的 Sass 文件。[这里](https://github.com/michaelwayman/node-sass-chokidar#options)有一份完整的选项清单。 + +集成 Sass 之后,我接下来要做的是定义一个颜色列表,它会成为应用程序的基本模板。这个列表用不着非常详细,只需要有基本模板所需最少的颜色就行。接下来,我定义那些使用颜色的部分,并为它们提供了描述性的名字。有了这些变量,它们就可以应用于应用程序的各种组件,这些组件会明确应用的主题基调。 + +![](https://cdn-images-1.medium.com/max/800/1*4J5_zY1pkslb8GWLgpVdmA.png) + +Sass 颜色变量 + +![](https://cdn-images-1.medium.com/max/800/1*bBXgZI-3qWHiW2k8IeoJhA.png) + +Sass 主题变量 + +在这里,可以看到我已经定义了一组基本颜色变量,并将它们应用于默认的 Sass 主题变量。这些主题变量会贯穿整个代码库的样式表,以将调色板应用到程序并赋予它生命! + +下面,我需要一种简单的方法来动态更新这些变量。这个方法就是使用 [CSS 变量](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables)。 + +![](https://cdn-images-1.medium.com/max/800/1*SgLF0GFzpFXgPZZrZkbgQg.png) + +CSS 变量的浏览器支持 + +CSS 变量是一个较新的浏览器规范并且几乎 100% 浏览器支持。考虑到我正在构建的应用是一个原型程序,所以我没有过多考虑支持旧浏览器。话虽如此,还是有些人推出了一些[IE 垫片](https://github.com/luwes/css-var-shim)。 + +就我的用例来说,我需要将 Sass 变量同步到 CSS 变量。为此,我选择了使用 [css-vars](https://github.com/malyw/css-vars) 包。 + +![](https://cdn-images-1.medium.com/max/800/1*--j_jmZ8p1-2awwqDQleVw.png) + +css-vars + +按照上面 `README` 中描述的那样,我大致上对我的应用做了类似的更改…… + +![](https://cdn-images-1.medium.com/max/800/1*IzkhVzxv991uNSMBBYK1Yg.png) + +用 Sass 添加 CSS 变量支持 + +准备到位后,我可以在我的样式表中使用 CSS 变量,而不是使用 Sass 变量。上面的重要一行是 `$css-vars-use-native: true;`,它告诉 css-vars 包编译的 CSS 应该编译为真正的 CSS 变量。这对于以后需要动态更新它们非常重要。 + +下一步要在应用中添加一个 “主题选择器”。对此,我希望能有多一点乐趣并选择添加了一个隐藏的菜单。这个隐藏的菜单有一点复活节彩蛋的感觉并且更加有趣。我并不太担心正确的用户体验 — 将来我可能会把这个菜单可视化。不过现在,让我们为应用程序添加一个秘密菜单,当用户按下键盘上的某个组合键时会显示这个菜单。 + +![](https://cdn-images-1.medium.com/max/800/1*0z13r6yik2WcRMiNoWHl8g.png) + +Modal 容器 + +此容器将监听 `CTRL + E` 组合键,当它监听到事件时,显示隐藏的菜单。这个 `Modal` 组件其实是一个 React Portal…… + +![](https://cdn-images-1.medium.com/max/800/1*D3xwDmwtLh7xtP1hRyldGw.png) + +Modal Portal + +模式 Portal 可以附着和脱离 `modal-root` 元素。有了它,我就可以创建 `Theme` 组件,这个组件拥有可以选择不同主题的菜单。 + +![](https://cdn-images-1.medium.com/max/800/1*eozcDZ0mLiymtSeRlsxDLQ.png) + +主题组件 + +这里,我引入了一个拥有和之前定义的变量相匹配的调色板列表。列表在选择后会全局更新应用的状态,然后调用 `updateThemeForStyle` 使用来 JavaScript 更新 CSS 变量。 + +![](https://cdn-images-1.medium.com/max/800/1*DZ7v0KtJ41HtF7dvhEz0fQ.png) + +更新 CSS 变量 + +这个函数使用所选主题的名字在 `themeOptions` 中找到选中的主题调色板,然后遍历对应调色板中的颜色属性并更新到 `html` 元素的 `style` 属性上。 + +主题选项只是一个选项列表,它具有与 CSS 变量定义的变量相同的变量. + +![](https://cdn-images-1.medium.com/max/800/1*-FaRopFYzpFdf7bjX7Xv8g.png) + +主题选项 + +有了所有的这些更改,主题选择器现在可以动态更新! + +![](https://cdn-images-1.medium.com/max/800/1*crV1ujG7TsYXjB3LRbgGdw.gif) + +主题选择 + +这是动态更新主题的效果! + +这是我添加功能的[提交](https://github.com/mjw56/wavves/commit/7fd2210c69617c33c4244d4755f1d33770d3c57d),完整的代码库请看[这里](https://github.com/mjw56/wavves)。 + +你可以[在此](https://wavves-amcsxyspgk.now.sh/)尝试一下这个应用的工作版。(需要 Spotify 的高级会员)。对,如果你在应用中按下 `CTRL + e`,隐藏的主题选择模式就会显示!😄 + +感谢阅读,祝你玩得愉快! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/data-science-for-startups-introduction.md b/TODO1/data-science-for-startups-introduction.md new file mode 100644 index 00000000000..916ce0c8aa7 --- /dev/null +++ b/TODO1/data-science-for-startups-introduction.md @@ -0,0 +1,64 @@ +> * 原文地址:[Data Science for Startups: Introduction](https://towardsdatascience.com/data-science-for-startups-introduction-80d022a18aec) +> * 原文作者:[Ben Weber](https://towardsdatascience.com/@bgweber?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/data-science-for-startups-introduction.md](https://github.com/xitu/gold-miner/blob/master/TODO1/data-science-for-startups-introduction.md) +> * 译者: +> * 校对者: + +# Data Science for Startups: Introduction + +![](https://cdn-images-1.medium.com/max/1600/1*z0AJeiYe_9qltVgp2g7zkw.jpeg) + +Source: rawpixel at pixabay.com + +I recently changed industries and joined a startup company where I’m responsible for building up a data science discipline. While we already had a solid data pipeline in place when I joined, we didn’t have processes in place for reproducible analysis, scaling up models, and performing experiments. The goal of this series of blog posts is to provide an overview of how to build a data science platform from scratch for a startup, providing real examples using Google Cloud Platform (GCP) that readers can try out themselves. + +This series is intended for data scientists and analysts that want to move beyond the model training stage, and build data pipelines and data products that can be impactful for an organization. However, it could also be useful for other disciplines that want a better understanding of how to work with data scientists to run experiments and build data products. It is intended for readers with programming experience, and will include code examples primarily in R and Java. + +#### Why Data Science? + +One of the first questions to ask when hiring a data scientist for your startup is +_how will data science improve our product_? At [Windfall Data](https://angel.co/windfall-data), our product is data, and therefore the goal of data science aligns well with the goal of the company, to build the most accurate model for estimating net worth. At other organizations, such as a mobile gaming company, the answer may not be so direct, and data science may be more useful for understanding how to run the business rather than improve products. However, in these early stages it’s usually beneficial to start collecting data about customer behavior, so that you can improve products in the future. + +Some of the benefits of using data science at a start up are: + +1. Identifying key business metrics to track and forecast +2. Building predictive models of customer behavior +3. Running experiments to test product changes +4. Building data products that enable new product features + +Many organizations get stuck on the first two or three steps, and do not utilize the full potential of data science. The goal of this series of blog posts is to show how managed services can be used for small teams to move beyond data pipelines for just calculating run-the-business metrics, and transition to an organization where data science provides key input for product development. + +#### Series Overview + +Here are the topics I am planning to cover for this blog series. As I write new sections, I may add or move around sections. Please provide comments at the end of this posts if there are other topics that you feel should be covered. + +1. **Introduction (this post):** Provides motivation for using data science at a startup and provides an overview of the content covered in this series of posts. Similar posts include [functions of data science](https://towardsdatascience.com/functions-of-data-science-4afd5341a659), [scaling data science](https://medium.com/windfalldata/scaling-data-science-at-windfall-55f5f23698e1) and [my FinTech journey](https://towardsdatascience.com/from-games-to-fintech-my-ds-journey-b7169f08b6ad). +2. [**Tracking Data:**](https://towardsdatascience.com/data-science-for-startups-tracking-data-4087b66952a1) Discusses the motivation for capturing data from applications and web pages, proposes different methods for collecting tracking data, introduces concerns such as privacy and fraud, and presents an example with Google PubSub. +3. [**Data pipelines:**](https://medium.com/@bgweber/data-science-for-startups-data-pipelines-786f6746a59a) Presents different approaches for collecting data for use by an analytics and data science team, discusses approaches with flat files, databases, and data lakes, and presents an implementation using PubSub, DataFlow, and BigQuery. Similar posts include [a scalable analytics pipeline](https://towardsdatascience.com/a-simple-and-scalable-analytics-pipeline-53720b1dbd35) and [the evolution of game analytics platforms](https://towardsdatascience.com/evolution-of-game-analytics-platforms-4b9efcb4a093). +4. [**Business Intelligence:**](https://towardsdatascience.com/data-science-for-startups-business-intelligence-f4a2ba728e75) Identifies common practices for ETLs, automated reports/dashboards and calculating run-the-business metrics and KPIs. Presents an example with R Shiny and Data Studio. +5. [**Exploratory Analysis**](https://towardsdatascience.com/data-science-for-startups-exploratory-data-analysis-70ac1815ddec)**:** Covers common analyses used for digging into data such as building histograms and cumulative distribution functions, correlation analysis, and feature importance for linear models. Presents an example analysis with the [Natality](https://cloud.google.com/bigquery/sample-tables) public data set. Similar posts include [clustering the top 1%](https://medium.freecodecamp.org/clustering-the-top-1-asset-analysis-in-r-6c529b382b42) and [10 years of data science visualizations](https://towardsdatascience.com/10-years-of-data-science-visualizations-af1dd8e443a7). +6. [**Predictive Modeling**](https://medium.com/@bgweber/data-science-for-startups-predictive-modeling-ec88ba8350e9)**:** Discusses approaches for supervised and unsupervised learning, and presents churn and cross-promotion predictive models, and methods for evaluating offline model performance. +7. [**Model Production**](https://medium.com/@bgweber/data-science-for-startups-model-production-b14a29b2f920)**:** Shows how to scale up offline models to score millions of records, and discusses batch and online approaches for model deployment. Similar posts include [Productizing Data Science at Twitch](https://blog.twitch.tv/productizing-data-science-at-twitch-67a643fd8c44), and [Producizting Models with DataFlow](https://towardsdatascience.com/productizing-ml-models-with-dataflow-99a224ce9f19). +8. **Experimentation:** Provides an introduction to A/B testing for products, discusses how to set up an experimentation framework for running experiments, and presents an example analysis with R and bootstrapping. Similar posts include [A/B testing with staged rollouts](https://blog.twitch.tv/a-b-testing-using-googles-staged-rollouts-ea860727f8b2). +9. **Recommendation Systems:** Introduces the basics of recommendation systems and provides an example of scaling up a recommender for a production system. Similar posts include [prototyping a recommender](https://towardsdatascience.com/prototyping-a-recommendation-system-8e4dd4a50675). +10. **Deep Learning:** Provides a light introduction to data science problems that are best addressed with deep learning, such as flagging chat messages as offensive. Provides examples of prototyping models with the R interface to [Keras](https://keras.rstudio.com/), and productizing with the R interface to [CloudML](https://tensorflow.rstudio.com/tools/cloudml/articles/getting_started.html). + +The series is also available as a book in [web](https://bgweber.github.io/) and [print](https://www.amazon.com/dp/1983057975) formats. + +#### Tooling + +Throughout the series, I’ll be presenting code examples built on Google Cloud Platform. I choose this cloud option, because GCP provides a number of managed services that make it possible for small teams to build data pipelines, productize predictive models, and utilize deep learning. It’s also possible to sign up for a free trial with GCP and get $300 in credits. This should cover most of the topics presented in this series, but it will quickly expire if your goal is to dive into deep learning on the cloud. + +For programming languages, I’ll be using R for scripting and Java for production, as well as SQL for working with data in BigQuery. I’ll also present other tools such as Shiny. Some experience with R and Java is recommended, since I won’t be covering the basics of these languages. + +* * * + +[Ben Weber](https://www.linkedin.com/in/ben-weber-3b87482/) is a data scientist in the gaming industry with experience at Electronic Arts, Microsoft Studios, Daybreak Games, and Twitch. He also worked as the first data scientist at a FinTech startup. + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/databook-turning-big-data-into-knowledge-with-metadata-at-uber.md b/TODO1/databook-turning-big-data-into-knowledge-with-metadata-at-uber.md new file mode 100644 index 00000000000..ad97354f9fd --- /dev/null +++ b/TODO1/databook-turning-big-data-into-knowledge-with-metadata-at-uber.md @@ -0,0 +1,156 @@ +> * 原文地址:[Databook: Turning Big Data into Knowledge with Metadata at Uber](https://eng.uber.com/databook/) +> * 原文作者:[Luyao Li, Kaan Onuk and Lauren Tindal](https://eng.uber.com/databook/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/databook-turning-big-data-into-knowledge-with-metadata-at-uber.md](https://github.com/xitu/gold-miner/blob/master/TODO1/databook-turning-big-data-into-knowledge-with-metadata-at-uber.md) +> * 译者:[cf020031308](https://github.com/cf020031308) +> * 校对者:[yqian1991](https://github.com/yqian1991) + +# Databook:通过元数据,Uber 将大数据转化为知识 + +![](https://i.loli.net/2018/08/16/5b751c9f4474b.png) + +从司机与乘客的位置和目的地,到餐馆的订单和支付交易,Uber 的运输平台上的每次互动都是由数据驱动的。数据赋能了 Uber 的全球市场,使我们面向全球乘客、司机和食客的产品得以具备更可靠、更无缝的用户体验,并使我们自己的员工能够更有效地完成工作。 + +凭借系统的复杂性和数据的广泛性,Uber 将数据驱动提升到了一个新的水平:每天处理万亿级的 Kafka 消息,横跨多个数据中心的 [HDFS](https://eng.uber.com/scaling-hdfs/) 中存储着数百 PB 的数据,并支持每周上百万的分析查询。 + +然而大数据本身并不足以形成见解。Uber 规模的数据要想被有效且高效地运用,还需要结合背景信息来做业务决策,这才能形成见解。为此我们创建了 Uber 的内部平台 Databook,该平台用于展示和管理具体数据集的有关内部位置和所有者的元数据,从而使我们能够将数据转化为知识。 + +### 业务(和数据)的指数增长 + +自 2016 年以来,Uber 的平台上已增加了多项新业务,包括 [Uber Eats](https://www.ubereats.com/)、[Uber Freight](https://freight.uber.com/),以及 [Jump Bikes](https://jumpbikes.com/)。如今,我们每天至少完成 1500 万次行程,每月活跃有超过 7500 万乘客。在过去的八年中,Uber 已从一家小型创业公司发展到在全球拥有 18,000 名员工。 + +随着这种增长,数据系统和工程架构的复杂性也增加了。例如有数万张表分散在多个在役的分析引擎中,包括 [Hive](https://hive.apache.org/)、[Presto](https://eng.uber.com/presto/) 和 [Vertica](https://www.vertica.com/)。这种分散导致我们急需了解信息的全貌,特别是我们还在不断添加新业务和新员工。 2015 年的时候,Uber 开始手动维护一些静态 HTML 文件来对表进行索引。 + +随着公司发展,要更新的表和相关元数据的量也在增长。为确保我们的数据分析能跟上,我们需要一种更加简便快捷的方式来做更新。在这种规模和增长速度下,有个能发现所有数据集及其相关元数据的强大系统简直不要太赞:它绝对是让 Uber 数据能利用起来的必备品。 + +[![](https://eng.uber.com/wp-content/uploads/2018/08/image6.png)](http://eng.uber.com/wp-content/uploads/2018/08/image6.png) + +图 1:Databook 是 Uber 的内部平台,可以展示和管理数据有关内部位置和所有者的元数据。 + +为使数据集的发现和探索更容易,我们创建了 Databook。 Databook 平台管理和展示着 Uber 中丰富的数据集元数据,使我们员工得以探索、发现和有效利用 Uber 的数据。 Databook 确保数据的背景信息 —— 它的意义、质量等 —— 能被成千上万试图分析它的人接触到。简而言之,Databook 的元数据帮助 Uber 的工程师、数据科学家和运营团队将此前只能干看的原始数据转变为可用的知识。 + +通过 Databook,我们摒弃了手动更新,转为使用一种先进的自动化元数据库来采集各种经常刷新的元数据。Databook 具有以下特性: + +* **拓展性:** 易于添加新的元数据、存储和记录。 +* **访问性:** 所有元数据可被服务以程序方式获取。 +* **扩展性:** 支持高通量读取。 +* **功能性:** 跨数据中心读写。 + +Databook 提供了各种各样的元数据,这些元数据来自 Hive、Vertica、[MySQL](https://eng.uber.com/mysql-migration/)、[Postgres](https://www.postgresql.org/)、[Cassandra](http://cassandra.apache.org/) 和其他几个内部存储系统,包括: + +* 表模式 +* 表/列的说明 +* 样本数据 +* 统计数据 +* 上下游关系 +* 表的新鲜度、SLA 和所有者 +* 个人数据分类 + +所有的元数据都可以通过一个中心化的 UI 和 [RESTful API](https://restfulapi.net/) 来访问到。 UI 使用户可以轻松访问到元数据,而 API 则使 Databook 中的元数据能被 Uber 的其他服务和用例使用。 + +虽说当时已经有了像 LinkedIn 的 [WhereHows](https://github.com/linkedin/WhereHows/wiki) 这种开源解决方案,但在 Databook 的开发期间,Uber 还没有采用 [Play 框架](https://www.playframework.com/)和 [Gradle](https://gradle.org/)(译者注:两者为 WhereHows 的依赖)。 且 WhereHows 缺乏跨数据中心读写的支持,而这对满足我们的性能需求至关重要。因此,利用 Java 本身强大的功能和成熟的生态系统,我们创建了自己内部的解决方案。 + +接下来,我们将向您介绍我们创建 Databook 的过程以及在此过程中我们遇到的挑战。 + +### Databook 的架构 + +Databook 的架构可以分为三个部分:采集元数据、存储元数据以及展示元数据。下面图 2 描绘的是该工具的整体架构: + +[![](https://eng.uber.com/wp-content/uploads/2018/08/image4.png)](http://eng.uber.com/wp-content/uploads/2018/08/image4.png) + +图 2:Databook 架构:元数据从 Vertica、Hive 和其他存储系统中获取,存储到后端数据库,通过 RESTful API 输出。 + +Databook 引入多个数据源作为输入,存储相关元数据并通过 RESTful API 输出(Databook 的 UI 会使用这些 API)。 + +在初次设计 Databook 时,我们就必须做出一个重大的决定,是事先采集元数据存起来,还是等到要用时现去获取?我们的服务需要支持高通量和低延迟的读取,如果我们将此需求托付给元数据源,则所有元数据源都得支持高通量和低延迟的读取,这会带来复杂性和风险。比如,获取表模式的 Vertica 查询通常要处理好几秒,这并不适合用来做可视化。同样,我们的 Hive metastore 管理着所有 Hive 的元数据,令其支持高通量读取请求会有风险。既然 Databook 支持许多不同的元数据源,我们就决定将元数据存储在 Databook 自身的架构中。此外,大多数用例虽然需要新鲜的元数据,但并不要求实时看到元数据的更改,因此定期爬取是可行的。 + +我们还将请求服务层与数据采集层分开,以使两者能运行在独立的进程中,如下面的图 3 所示: + +[![](https://eng.uber.com/wp-content/uploads/2018/08/image11.png)](http://eng.uber.com/wp-content/uploads/2018/08/image11.png) + +图 3:Databook 由两个不同的应用层组成:数据采集爬虫和请求服务层。 + +两层隔离开可减少附带影响。例如,数据采集爬虫作业可能占用较多的系统资源,没隔离就会影响到请求服务层上 API 的 SLA。另外,与 Databook 的请求服务层相比,数据采集层对中断不太敏感,如果数据采集层挂掉,可确保仍有之前的元数据能提供,从而最大限度地减少对用户的影响。 + +### 事件驱动采集 vs 定时采集 + +我们的下一个挑战是确定如何最且成效且最高效地从多种不同数据源采集元数据。我们考虑过多种方案,包括创建一个分布式的容错框架,利用基于事件的数据流来近乎实时地检测和调试问题。 + +我们先创建了爬虫来定期采集各种数据源和微服务生成的有关数据集的元数据信息,例如表的使用数据统计,它由我们用于解析和分析 SQL 的强大开源工具 [Queryparser](https://eng.uber.com/queryparser/) 生成。**(顺带一提:Queryparser 也由我们的“数据知识平台”团队创建)。** + +我们需要以可扩展的方式频繁采集元数据信息,还不能阻塞到其他的爬虫任务。为此,我们将爬虫部署到了不同的机器,这就要求分布式的爬虫之间能进行有效协调。我们考虑配置 [Quartz](http://www.quartz-scheduler.org/) 的集群模式(由 MySQL 支持)来做分布式调度。但是,却又面临两个实现上的障碍:首先,在多台机器上以集群模式运行 Quartz 需要石英钟的定期[同步](http://www.quartz-scheduler.org/documentation/quartz-2.2.x/configuration/ConfigJDBCJobStoreClustering.html),这增加了外部依赖,其次,在启动调度程序后我们的 MySQL 连接就一直不稳定。最后,我们排除了运行 Quartz 集群的方案。 + +但是,我们仍然决定使用 Quartz,以利用其强大的内存中调度功能来更轻松、更高效地向我们的任务队列发布任务。对于 Databook 的任务队列,我们用的是 Uber 的开源任务执行框架 [Cherami](https://eng.uber.com/cherami/)。这个开源工具让我们能在分布式系统中将消费程序解耦,使其能跨多个消费者群组进行异步通信。有了 Cherami,我们将 Docker 容器中的爬虫部署到了不同的主机和多个数据中心。使用 Cherami 使得从多个不同来源采集各种元数据时不会阻塞任何任务,同时让 CPU 和内存的消耗保持在理想水平并限制在单个主机中。 + +尽管我们的爬虫适用于大多数元数据类型,但有一些元数据还需要近乎实时地获取,所以我们决定过渡到基于 Kafka 的事件驱动架构。有了这个,我们就能及时检测和调试数据中断。我们的系统还可以捕获元数据的重大变动,例如数据集上下游关系和新鲜度,如下面的图 4 所示: + +[![](https://eng.uber.com/wp-content/uploads/2018/08/image5.png)](http://eng.uber.com/wp-content/uploads/2018/08/image5.png) + +图 4:在 Databook 中,对每个表采集上下游关系/新鲜度元数据。 + +这种架构使我们的系统能够以程序方式触发其他微服务并近乎实时地向数据用户发送信息。但我们仍需使用我们的爬虫执行诸如采集/刷新样本数据的任务,以控制对目标资源的请求频率,而对于在事件发生时不一定需要采集的元数据(比如数据集使用情况统计)则自动触发其他系统。 + +除了近乎实时地轮询和采集元数据之外,Databook UI 还从使用者和生产者处采集数据集的说明、语义,例如表和列的描述。 + +### 我们如何存储元数据 + +在 Uber,我们的大多数数据管道都运行在多个集群中,以实现故障转移。因此,同一张表的某些类型的元数据的值(比如延迟和使用率)可能因集群的不同而不同,这种数据被定义为集群相关。相反,从用户处采集的说明元数据与集群无关:描述和所有权信息适用于所有集群中的同一张表。 为了正确关联这两种类型的元数据,例如将列描述与所有集群中的表列相关联,可以采用两种方案:写时关联或读时关联。 + +##### 写时关联 + +将集群相关的元数据与集群无关的元数据相关联时,最直接的策略是在写入期间将元数据关联在一起。例如,当用户给某个表列添加描述时,我们就将信息保存到所有集群的表中,如下面的图 5 所示: + +[![](https://eng.uber.com/wp-content/uploads/2018/08/image3.png)](http://eng.uber.com/wp-content/uploads/2018/08/image3.png) + +图 5:Databook 将集群无关的元数据持久化保存到所有表中。 + +这方案可确保持久化数据保持整洁。例如在图 5 中,如果“列 1”不存在,该集群就会拒绝该请求。但这存在一个重要的问题:在写入时将集群无关的元数据关联到集群相关的元数据,所有集群相关的元数据必须已经存在,这在时间上只有一次机会。例如,当在图 5 中改动表列描述时,还只有集群 1 有此“列 1”,则集群 2 的写入失败。之后,集群 2 中同一个表的模式被更新,但已错失机会,除非我们定期重试写入,否则此描述将永远不可用,这导致系统复杂化。下面图 6 描述了这种情况: + +[![](https://eng.uber.com/wp-content/uploads/2018/08/image9.png)](http://eng.uber.com/wp-content/uploads/2018/08/image9.png) + +图 6:Databook 将集群无关的元数据持久保存到所有表中。 + +##### 读时关联 + +实现目标的另一种方案是在读取时关联集群无关和集群相关的元数据。由于这两种元数据是在读取时尝试关联,无所谓集群相关的元数据一时是否存在,因此这方案能解决写时关联中丢失元数据的问题。当表模式更新后显示“列 1”时,其描述将在用户读取时被合并,如下面图 7 所示: + +[![](https://eng.uber.com/wp-content/uploads/2018/08/image10.png)](http://eng.uber.com/wp-content/uploads/2018/08/image10.png) + +图 7:Databook 在读取时关联集群相关和集群无关的元数据。 + +##### 存储选择 + +Databook 后端最初是使用 MySQL,因为它开发速度快,可以通过 Uber 的基础设施自动配置。但是,当涉及多数据中心支持时,共享 MySQL 集群并不理想,原因有三: + +* 单个主节点:首先,Uber 仅支持单个主节点,导致其他数据中心的写入时间较慢(我们这情况每次写入增加约 70ms)。 +* 手动提权:其次,当时不支持自动提权。因此,如果主节点挂掉,要花数小时才能提升一个新的主节点。 +* 数据量:我们弃用 MySQL 的另一个原因是 Uber 所产生的大量数据。我们打算保留所有历史变更,并希望我们的系统支持未来扩展,而无需在集群维护上花费太多时间。 + +出于这些原因,我们选择 Cassandra 来取代 MySQL,因为它具有强大的 XDC 复制支持,允许我们从多个数据中心写入数据而不会增加延迟。而且由于 Cassandra 具有线性可扩展性,我们不再需要担心适应 Uber 不断增长的数据量。 + +### 我们如何展示数据 + +Databook 提供了两种访问元数据的主要方法:RESTful API 和可视化 UI。Databook 的 RESTful API 用 [Dropwizard](https://www.dropwizard.io/)(一个用于高性能 RESTful Web 服务的 Java 框架)开发,并部署在多台计算机上,由 Uber 的内部请求转发服务做负载平衡。 + +在 Uber,Databook 主要用于其他服务以程序方式访问数据。例如,我们的内部查询解析/重写服务依赖于 Databook 中的表模式信息。API 可以支持高通量读取,并且可以水平扩展,当前的每秒查询峰值约为 1,500。可视化 UI 由 React.js 和 Redux 以及 D3.js 编写,主要服务于整个公司的工程师、数据科学家、数据分析师和运营团队,用以分流数据质量问题并识别和探索相关数据集。 + +##### 搜索 + +搜索是 Databook UI 的一项重要功能,它使用户能够轻松访问和导航表元数据。我们使用 Elasticsearch 作为我们的全索引搜索引擎,它从 Cassandra 同步数据。如下面图 8 所示,使用 Databook,用户可结合多个维度搜索,例如名称、所有者、列和嵌套列,从而实现更及时、更准确的数据分析: + +[![](https://eng.uber.com/wp-content/uploads/2018/08/image1.png)](http://eng.uber.com/wp-content/uploads/2018/08/image1.png) + +图 8:Databook 允许用户按不同的维度进行搜索,包括名称、所有者和列。 + +### Databook 的新篇章 + +通过 Databook,Uber 现在的元数据比以往更具可操作性和实用性,但我们仍在努力通过建造新的、更强大的功能来扩展我们的影响力。我们希望为 Databook 开发的一些功能包括利用机器学习模型生成数据见解的能力,以及创建高级的问题检测、预防和缓解机制。 + +如果结合内部和开源解决方案建立可扩展的智能服务并开发有创意的复杂技术对您来说有吸引力,请联系 Zoe Abrams([za@uber.com](mailto:za@uber.com))或申请我们团队的[职位](https://www.uber.com/careers/list/29589/)! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/defining-component-apis-in-react.md b/TODO1/defining-component-apis-in-react.md new file mode 100644 index 00000000000..6d7aa773826 --- /dev/null +++ b/TODO1/defining-component-apis-in-react.md @@ -0,0 +1,222 @@ +> * 原文地址:[Defining Component APIs in React](http://jxnblk.com/writing/posts/defining-component-apis-in-react/) +> * 原文作者:[Jxnblk](http://jxnblk.com) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/defining-component-apis-in-react.md](https://github.com/xitu/gold-miner/blob/master/TODO1/defining-component-apis-in-react.md) +> * 译者:[Gavin-Gong](https://github.com/Gavin-Gong) +> * 校对者:[xunge0613](https://github.com/xunge0613) [sunui](https://github.com/sunui) + +# 设计 React 组件 API + +多年来,我致力于一系列处理组件 API 和构建应用程序、库的模式。以下是一系列如何设计组件 API 的想法、观点和建议,这会让组件更灵活、更具有组合性、更容易理解。这些规则都不是硬性的,但它们帮助我想明白了如何组织和创建组件。 + +## 提供最少的 API + +正如 React 库本身的目标是 [最少化 API](https://www.youtube.com/watch?v=4anAwXYqLG8) 一样,我建议在设计组件 API 时采用类似的观点。需要学习的新内容越少,其他人就越容易知道如何使用你创建的组件,从而使它们更容易被重用。如果有人不理解你的组件 API,那么他们重复你的工作的可能性就会增加。这是我如何创建组件的核心理念,我发现在我工作中牢记它很有帮助。 + +## 让你的代码更容易被找到 + +从扁平目录结构开始,不要过早地组织代码库。人类喜欢整理东西,但我们在这方面做得很糟糕。命名已经足够困难了,为组件库创建目录结构,您可能会做更多的工作,最终使其他人更难找到你所写的代码。 + +一个单独存放组件的目录在变得难以管理之前会变得相当大。如果所有的组件都在一个文件夹中,在大多数文件系统工具中会自动按照字母进行排序,这有助于为其他人提供更完整的代码库概览。 + +## 避免 renderXXX 方法 + +如果您在组件中定义了以 render 开头的自定义方法,那么它很可能应该被定义为自有组件。正如 [Chris Biscardi](https://mobile.twitter.com/chrisbiscardi/status/1004559213320814592) 所说,**“高效意味着有有足够的复杂度值得被分解”**。React 能明智地决定是否渲染的时机,因此,将这些组件拆分为自有组件,可以帮助 React 更好地运行。 + +```jsx +// 不要这样写 +class Items extends React.Component { + renderItems ({ items }) { + return items.map(item => ( +
  • + {renderItem(item)} +
  • + )) + } + + renderItem (item) { + return ( +
    + {item.name} +
    + ) + } + + render () { + return ( +
      + {renderItems(this.props) +
    + ) + } +} +``` + +```jsx +// 这样写 +const ItemList = ({ items }) => +
      + {items.map(item => ( +
    • + +
    • + )} +
    + +const Item = ({ name }) => +
    {item.name}
    + +class Items extends React.Component { + render () { + const { items } = this.props + return + } +} +``` + +## 在数据界限上分割组件 + +通常,组件应该由数据的形状来定义 + +> 既然你经常向用户展示 JSON 数据模型,你会发现,如果你的模型构建正确,你的 UI(以及你的组件结构)会被很好地映射。 + +* [React 理念](https://facebook.github.io/react/docs/thinking-in-react.html) + +我经常看到 React 新手尝试复制我所说的 "[Bootstrap](https://getbootstrap.com)" 组件,即具有视觉边界,但与任何数据结构都没有直接联系的 UI 组件。React 组件和 BEM 风格、基于 CSS 的组件有着不同的关注点。不应该创建一个需要定制 props 的通用 Card 组件来显示图像、标题和链接,而是为你需要展示的数据创建组件。也许通用的 Card 组件应该是一个接受来自数据库的 product 对象的 ProductCard 组件。 + +```jsx +// 不要这样写 + + +// 这样写 + +``` + +很可能,你需要的 ProductCard 的特定样式并不都是可重用的,而且你可能只在代码库中的一个地方定义了这个样式。在这种情况下,你可以遵循 [三次法则]()。如果你已经在代码库中复制了三次 Card 组件结构,那么将其抽象出自有组件可能是值得的。 + +## 避免繁多的 props + +正如 [Jenn Creighton](https://twitter.com/gurlcode) 所说,避免 [繁多的 props]((https://speakerdeck.com/jenncreighton/flexible-architecture-for-react-components?slide=10))。不要害怕创建一个新的组件,而不是向组件添加一些任意的 props 和附加的逻辑。例如,Button 组件可以接受不同颜色、大小和形状的 props,但并不总是需要这么多的 props。 + +```jsx +// 不要这要写 + + ; 0 ; + + `; + + // 我们可以通过影子根节点查询内部节点 + // 就比如这里的按钮 + this.incrementButton = this.shadowRoot.querySelector('#counter-increment'); + this.decrementButton = this.shadowRoot.querySelector('#counter-decrement'); + this.counterValue = this.shadowRoot.querySelector('#counter-value'); + + // 我们可以绑定事件,用类方法来响应 + this.incrementButton.addEventListener("click", this.decrement.bind(this)); + this.decrementButton.addEventListener("click", this.increment.bind(this)); + + } + + increment() { + this.counter++ + this.invalidate(); + } + + decrement() { + this.counter-- + this.invalidate(); + } + + // 当计数器的值发生变化时调用 + invalidate() { + this.counterValue.innerHTML = this.counter; + } +} + +// 这里定义了可以在 DOM 树上直接使用的真实节点 +customElements.define('counter-element', CounterElement); +``` + +特别注意最后一行,那里注册了可以用在 DOM 里面的 Custom Element。 + +#### Custom Element 的种类 + +上面代码展示了如何从 `HTMLElement` 接口做拓展,然而我们还可以从更具体的节点上拓展,比如 `HTMLButtonElement`。Web Component 规范提供了一个[完整的可供继承的接口列表](https://html.spec.whatwg.org/multipage/indices.html#element-interfaces)。 + +Custom Element 可分为两种主要类型:**独立自定义元素(Autonomous custom elements)** 和 **内置自定义元素(Customized built-in elements)**。独立自定义元素和那些早已定义且不继承自特定接口的节点类似(译者注:就是我们平常使用的 DOM 节点)。一个独立自定义元素只要在页面一定义上,就可以像常规 HTML 节点那样使用。举个例子,上面定义的计数节点,既可以在 HTML 中通过 `` 定义,也可以在 JavaScript 中用 `document.createElement('counter-element')` 来创建。 + +内置自定义元素在使用上略有不同,当 HTML 定义节点时可以传一个 `is` 属性到标准节点上(比如 ` +``` + +即使不看样式代码,你也可以一眼就看出这段代码会创建一个既大又橙的价格按钮。无论你是否喜欢带有连字符和下划线的这种风格,拥有严格的命名约定是模块化 CSS 向前迈出的一大步,这让代码带有自文档的效果! + +#### 不嵌套 CSS + +就像 OOCSS 建议使用 class 而不使用 ID 一样,BEM 也为代码风格作了一些限制。最值得注意的是,他们认为不应该嵌套 CSS 选择器。嵌套选择器扰乱了优先度,使得重用代码变得更加困难。例如,只需使用 `.btn__price` **而不是** `.btn .btn__price`。 + +**注意:这里的嵌套指实践中在 Sass 或 Less 嵌套选择器的做法,但即使你没有使用预处理器也适用,因为这关乎选择器优先度问题。** + +这个原则不出问题是因为严格的命名约定。我们曾经使用嵌套选择器将它们隔离在命名空间的上下文中。而 BEM 的命名约定本身就提供了命名空间,因此我们不再需要嵌套。即使 CSS 的根级别的所有内容都是单个类,但这些名称的具体程度足以避免冲突。 + +一般来说,选择器可以在没有嵌套的情况下生效,就不要嵌套它。 BEM 允许此规则的唯一例外是基于块状态或其修饰符的样式元素。例如,可以使用 `.btn__text` 然后用 `.btn--orange .btn__text` 来覆盖应用了修饰符按钮的文本颜色。 + +### SMACSS + +我们最后要讨论的框架是 [SMACSS](https://smacss.com/),含义是 CSS 的可扩展性和模块化架构(Scalable & Modular Architecture)。[Jonathan Snook](https://twitter.com/snookca) 于 2011 年提出了 SMACSS,当时他在雅虎工作,为 Yahoo Mail 编写 CSS。 + +![“At the very core of SMACSS is categorization. By categorizing CSS rules, we begin to see patterns and can define better practices around each of these patterns.” —Jonathan Snook](https://spaceninja.ghost.io/content/images/2016/11/snookca.jpg) + +来源:[Jonathan Snook](https://smacss.com/book/categorizing),图:[Elida Arrizza](https://www.flickr.com/photos/elidr/10864268273/) + +他在 OOCSS 和 BEM 的基础上添加的关键概念是,不同**类别**的组件需要以不同的方式处理。 + +#### 类别(Categories) + +以下是他为 CSS 系统可能包含的规则定义的类别: + +1. **基础(Base)** 规则是HTML元素的默认样式,如链接,段落和标题。 +2. **布局(Layout)** 规则将页面分成几个部分,并将一个或多个模块组合在一起。它们只定义布局,而不管颜色或排版。 +3. **模块(Module)**(又名“对象”或“块”)是可重用的,设计中的一个模块。例如,按钮,媒体对象,产品列表等。 +4. **状态(State)** 规则描述了模块或布局在特定状态下的外观。通常使用 JavaScript 应用或删除。例如,隐藏,扩展,激活等。 +5. **主题(Theme)** 规则描述了模块或布局在主题应用时的外观,例如,在 Yahoo Mail 中,可以使用用户主题,这会影响页面上的每个模块。(这非常适用于像雅虎这样的应用程序,但大多数网站都不会使用此类别。) + +#### 命名约定前缀 + +下一个原则是使用前缀来区分类别,他喜欢 BEM 明确的命名约定,但他还希望能够一目了然地看出模块的类型。 + +* `l-` 用作布局规则的前缀:`l-inline` +* `m-` 用作模块规则的前缀:`m-callout` +* `is-` 用作状态规则的前缀:`is-collapsed` + +(基础规则没有前缀,因为它们直接应用于 HTML 元素而不使用类。) + +## 共享模块化原则 + +这些框架的相同之处远胜于其不同之处。我看到从 OOCSS 到 BEM 再到 SMACSS 的明确发展。它们的发展代表了我们行业在性能和大规模 CSS 领域不断增长的经验。 + +你不必选择其中一个框架,相反,我们可以尝试定义模块化 CSS 的通用规则。让我们看看这些框架共用和保留的最佳部分。 + +### 模块化元素 + +模块化系统由以下元素组成: + +* **模块(Module):**(又名对象,块或组件)一种可复用且自成一体的模式。如媒体对象,导航和页眉。 +* **子元素(Child Element):** 一个不能独立存在的小块,属于模块的一部分。如媒体对象中的图像,导航选项卡和页眉 logo。 +* **模块修改器(Module Modifier):**(又名皮肤或主题)改变模块的视觉外观。如左/右对齐的媒体对象,垂直/水平导航。 + +### 模块化类别 + +模块化系统中的样式可以分为以下几类: + +* **基础(Base)** 规则是 HTML 元素的默认样式,如:`a`、`li` 和 `h1` +* **布局(Layout)** 规则控制模块的布局方式,但不控制视觉外观,如:`.l-centered`、`.l-grid` 和 `.l-fixed-top` +* **模块(Modules)** 是可复用的,独立的 UI 组件视觉样式,如:`.m-profile`、`.m-card` 和 `.m-modal` +* **状态(State)** 规则由 JavaScript 添加,如:`.is-hidden`、`.is-collapsed` 和 `.is-active` +* **助手(Helper)**(又名功能)规则适用范围小,独立于模块,如:`.h-uppercase`、`.h-nowrap` 和 `.h-muted` + +### 模块化规则 + +在模块化系统中编写样式时,请遵循以下规则: + +* 不要使用 ID +* CSS 嵌套不要超过一层 +* 为子元素添加类名 +* 遵循命名约定 +* 为类名添加前缀 + +## FAQ + +### 这么做 HTML 不会有很多类吗? + +模块化 CSS 最常见的反对意见就是,它会在 HTML 中产生许多类。我认为这是因为长期以来 CSS 的最佳实践都认为应该避免大量 class 使用。早在 2011 年,Nicole Sullivan 就写了一篇很棒的博文 [Our (CSS) Best Practices are Killing Us](http://www.stubbornella.org/content/2011/04/28/our-best-practices-are-killing-us/),明确驳斥了这个想法。 + +我看到一些开发人员提倡使用预处理器的 `extend` 函数将多个样式连接成一个类名。我建议不要这样做,因为它会使你的代码不那么灵活。他们不能让其他开发者以新的方式组合你的乐高积木,而是固定了你定义的几种组合。 + +#### BEM 的类名又长又丑! + +不要因为类名太长而害怕,他们是自文档的!当我看到 BEM 风格的类名(或任何其他模块化命名约定)时,我会觉得很愉悦,因为只要看一眼就能知道这些类的含义。你可以在 HTML 中清晰理解它们。 + +### 孙元素的命名如何约定? + +长话短说:没这回事。 + +模块化 CSS 初学者可以快速掌握子元素的概念:`minifig__arm` 是 `minifig` 的一部分。然而,有时候他们处理 CSS 中的 DOM 结构时,会疑问如何作深层嵌套,比如 `minifig__arm__hand`。 + +没有必要这样做。请记住,这个思路是要将样式与标记分离。无论 `hand` 是 `minifig` 的直接子元素还是嵌套了多少层,都无关紧要。CSS 关心的只有 `hand` 是 `minifig` 的孩子。 + +``` +.minifig {} + .minifig__arm {} + .minifig__arm__hand {} /* don't do this */ + .minifig__hand {} /* do this instead */ +``` + +### 模块冲突怎么办? + +模块化 CSS 初学者比较关注的另一件事是模块之间的冲突。例如,如果我将 `l-card` 模块和 `m-author-profile` 模块同时应用于同一个元素,是否会导致问题? + +答案是:理想情况下,模块不应该重叠太多。在这个例子中,`l-card` 模块关注布局,而 `m-author-profile` 模块关注样式,你可能会看到 `l-card` 设置宽度和边距,而 `m-author-profile` 设置背景颜色和字体。 + +![](https://i.loli.net/2018/09/29/5baf81580aa8a.png) + +测试模块是否冲突的一种方法是以随机顺序加载它们。你可以将项目构建配置中设定为在构建时随机交换样式位置。如果看到bug,就证明你的 CSS 需要以特定顺序加载。 + +如果你发现需要将两个模块应用于同一个元素并且它们存在冲突,请考虑它们是否真的是两个独立的模块。也许它们可以用一个修饰符组合成一个模块? + +该规则的最后一个例外是“helper”或“utility”类可能会发生冲突,在这些情况下,你可以安全地考虑使用 `!important`。我知道,你曾被告知 `!important` 不是什么好东西,永远不应该被使用,但我们的做法有细微的差别:主动使用它来确保 helper 类总是优先还是不错的。 ([Harry Roberts has more to say on this topic in the CSS Guidelines](https://cssguidelin.es/#important)。) + +

    总结,模块化 CSS 太美妙啦

    + +我们来简要回顾一下,还记得这段代码吗? + +``` +
    + +

    ...

    +
    +``` + +> `box` 和 `profile` 有什么关系?`profile` 和 `avatar` 有什么关系?或者他们之间有关系吗?你应该在 `bio` 旁边添加 `pro-user` 吗?`image` 和 `profile` 写在同一部分 CSS 吗?可以在其他地方使用 `avatar` 吗? + +现在我们知道如何解决这些问题了。通过编写模块化 CSS 并使用适当的命名约定,我们可以编写自文档的代码: + +``` +
    + +

    ...

    +
    +``` + +我们可以看到哪些类彼此相关,哪些类彼此不相关,以及如何相关。我们知道在这个组件的范围之外我们不能使用哪些类,当然,我们还知道哪些类可以在其他地方复用。 + +模块化 CSS 简化了代码并推进了重构,产出自文档的代码,这样的代码不影响外部作用域且可复用。 + +或者换句话说,**模块化 CSS 是可预测的,可维护的并且是高性能的。** + +现在我们可以重温那个老笑话,结局发生了变化: + +![Two CSS properties walk into a bar. Everything is fine, thanks to modular code and proper namespacing.](https://spaceninja.ghost.io/content/images/2016/11/updated-tweet.png) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 + diff --git a/TODO1/what-it-was-like-to-write-a-full-blown-flutter-app.md b/TODO1/what-it-was-like-to-write-a-full-blown-flutter-app.md new file mode 100644 index 00000000000..f914555aa70 --- /dev/null +++ b/TODO1/what-it-was-like-to-write-a-full-blown-flutter-app.md @@ -0,0 +1,99 @@ +> * 原文地址:[What It Was Like to Write a Full Blown Flutter App](https://hackernoon.com/what-it-was-like-to-write-a-full-blown-flutter-app-330d8202825b) +> * 原文作者:[Nick Manning](https://hackernoon.com/@seenickcode?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/what-it-was-like-to-write-a-full-blown-flutter-app.md](https://github.com/xitu/gold-miner/blob/master/TODO1/what-it-was-like-to-write-a-full-blown-flutter-app.md) +> * 译者:[tanglie1993](https://github.com/tanglie1993) +> * 校对者:[Park-ma](https://github.com/Park-ma), [atuooo](https://github.com/atuooo) + +# 写一个完整的 Flutter App 是什么感觉 + +![](https://cdn-images-1.medium.com/max/800/1*SZK7j8dPQuaecmaeJoWxwA.jpeg) + +**更新**:我将会发布一个新的名为 Practical Flutter 的 Flutter 课程。它将在 18 年七月底开始。如果你想收到通知,[点击这里](https://mailchi.mp/5a27b9f78aee/practical-flutter)。 🚀 + +今天早上我吃了两顿早饭,因为我需要发动所有的写博客用的脑力。从[我的上一个帖子](https://codeburst.io/why-flutter-will-take-off-in-2018-bbd75f8741b0)以后,我有了很多想说的话,所以我们开始吧。 + +我非常激动,因为我可以正式继续写关于 Flutter 的文章了,因为我即将把我的第一个 Flutter app 投放到 iOS 和 Android 商店——只有一两周了!因为我在空闲时间里一直在写这个 app,所以在过去几个月我一直拒绝被打扰。 + +**自从 Ruby on Rails 或 Go 发布以来,我从没有因为一个技术而这么激动过。** 在花了好几年深入学习 iOS app 开发之后,我因为对 Android 非常不熟悉而感到不爽。而且,以前的那些跨平台开发框架都很难吸引我。 + +比如在两年前,前往跨平台 app 开发的聚会,我会觉得那些东西都很不正规、不稳定、开发者体验糟糕、难以使用,或者最近一两年都没法用。 + +我刚刚完成第一个 Flutter app,并感到我可以长期安全地向这个框架投入更多的时间。写一个 app 是对一个框架最后的检验,而 Flutter 通过了这个检验。能够熟练地开发 iOS 和 Android app 令我感到惊喜。我也很喜欢服务端的开发与扩容,而[我的妻子 Irina](https://www.behance.net/irinamanning) 是一名用户体验设计师,所以这是一个强大的组合。 + +**这篇博文将会很长,因为它包括很多内容:** + +1. **我关于把 iOS app 迁移到 Flutter 的经验** +2. **目前为止关于 Flutter 的想法** +3. **对 Google 团队的建议** + +我决定尽快写下我的想法,以便继续写教程(以及更多的 app!)。 + +### 1. 把 iOS 应用迁移到 Flutter + +自从我的上一个 [关于 Flutter 的帖子](https://codeburst.io/why-flutter-will-take-off-in-2018-bbd75f8741b0) 以来,我感觉合乎逻辑的下一步是真正深入地学习 Flutter。我非常喜欢久经考验、由端到端的实例的教程 (think Digital Ocean 或者甚至 Auth0 教程)。端到端,细致的,高质量的例子一直是新技术吸引我的方式,因为我可以看到基本能够正式上线的代码,并且确信我在使用正确的方式实现功能。我也想做同样的事,所以我决定写 Flutter 的教程。 + +有了这些目标之后,我决定最适合我的 app 是重写一个我已经发布到 App store 上的 iOS app。Steady Calendar([homepage](https://www.steadycalendar.com),[Product Hunt](https://www.producthunt.com/posts/steady-calendar)),是一款[我的妻子 Irina](https://www.behance.net/irinamanning) 和我设计和开发的习惯养成器。我们是在几年前生活在柏林时开发的。从那时候以来,这个产品使我们为设计、实现和发布帮助他人养成健康习惯的产品而着迷。 + +把这个 iOS app 迁移到 Flutter 花了我一到两个月的空闲时间。这使我可以毫无压力地写出优秀的 Flutter 教程。 + +很酷的是我可以把以下内容包括在我的教程中,因为我在 app 中实现了它们: + +* 登录之前的介绍。 +* Facebook/email 注册与登录。 +* 展示日历的网格 view ,用户可以在完成一个目标之后高亮某一天。 +* iOS 与 Android 用户都熟悉的跨平台表单。 +* 使用 [Scoped Model](https://pub.dartlang.org/packages/scoped_model) 的 Redux 风格的状态管理。 +* 具有栈、定位元素、图像和按钮的自定义 UI。 +* 列表 view。 +* 简单、多语言、国际化的 UI。 +* 跨平台导航栏,同样是 iOS 和 Android 用户都很熟悉的。 +* 具有全局样式的控件。 +* 集成测试。 +* 把 app 提交到 Apple 应用商店。 +* 把 app 提交到 Google Play 商店。 + +### 2. 目前为止关于 Flutter 的想法 + +我已经在后端和 webapp 开发方面有了 17 年以上的经验,其中的 4 年我重度参与了 iOS 开发,并且在上一年,我需要花很多的工作时间在 React Native 上 (去年也发布了一些 React 项目)。 + +**以下是在学习 Flutter 时出现的想法:** + +1. **开发者体验**,开发者的团体精神和给我的支持十分惊人。从 Stack Overflow,Google Groups 到博文的所有东西质量都很高,因为人们对 Flutter 非常有热情。 Google 工程师在日常工作之外,还愿意花很多时间在 Google Groups 上回答问题,这就形成了一个了不起的社区。他们在和各种背景的工程师合作时表现得非常礼貌、非常专业,而其他很多公司就不见得是这样。开发者社区非常热闹,成员们非常积极,并提供深思熟虑的答案。文档也非常出色。库非常稳定,Flutter 是基于 Dart 的,而这个语言已经存在了多年,易于学习,并且久经考验。总而言之,开发者体验很棒。 +2. 如我所预期的,**使用 Dart 的第三方库还相对稀少**。但这些并不说明 Dart 不适合使用,至少在我的经验中不是这样。**我们需要的特性中 95% 已经可以使用了**,仅有的例外是第三方的分析工具,但是对 HTTP 简单封装一层即可完成这个功能。 +3. **Material Design 控件**,Flutter 框架包含了大量的这些东西。它适合迅速开发简单的 app,但对于专业的、跨平台的 app,它会使 iOS 用户感到陌生。我不能把 Material Design 控件呈现给我的 iOS 用户,因为这将使他们对我的 app 感到陌生。Flutter 当然提供一系列的 iOS 控件,但这些东西都还不够完整。幸好,我开发的 Steady app 中的大多数控件已经是自定义的了。对于表单之类的东西而言,这还是很有挑战的。所以最后,文档、示例和整个 Flutter SDK 都很依赖于 Material Design。这很好,但对于像我这样的人而言,还需要更多的平衡。 +4. **在 Flutter 中开发自定义 UI 非常顺畅**。在被 CocoaTouch / iOS 宠坏之后,我有了非常高的标准。在接触了大量 Flutter 代码并比较了开发自定义 UI 的经验后,**Google 团队确实做得很好**。当然,有一些控件让我觉得过于复杂,会使学习曲线过于陡峭,但这不是很大的问题。在写完一个真正的 app 之后,人们将很快能够察觉最关键、最常用的控件有什么特征(嘿,我将在将来的教程中包括这部分内容)。 +5. 作为一个 iOS 用户,我花了几个月的时间开发最初的 iOS app Steady Calendar,**我将永远不会忘记第一次在实体的安卓设备上运行它时的激动之情**。我猜这是因为我总是特别不喜欢其它跨平台移动框架。如果你花了数月的空闲时间,辛辛苦苦开发了一些东西,发现你可以在两个主要平台上运行它,你将会迷上它。这对于很多人来说可能并不是有帮助的反馈,但我反正需要分享我的看法! +6. **开发跨平台 app 将会让你遇到更多的设计挑战** 但这和 Flutter 本身真的没有太多关系,这主要是关于跨平台开发的。当你计划开发一个 Flutter app 时,要确保你有一个好的设计师和好的自定义 UI,否则你就要准备好根据情况判断你的 app 是该使用 Material Design 还是 Cupertino 控件了。在前一种情况下,这和 Flutter 的关系较小,而和开发跨平台 app 本身的挑战关系更大。你需要确定 UI 对于 Android 和 iOS 的用户而言都很好看,而且他们都能习惯它。 +7. **学习和使用 Dart 非常愉快**。我喜欢它和 TypeScript 或 Flow 相比的稳定性和可靠性。说得具体一点,我有一些 React 方面的背景,并在过去几个月的日常工作中大量(非常大量)学习 React Native。我也有多年使用 Objective-C 然后是 Swift 的经验。Dart 是一口新鲜的空气,因为 **并不试图变得过于复杂,并且有可靠的核心库和包**。说真的,我认为哪怕是高中新生也可以使用 Dart 完成基本的编程。我听见很多人抱怨说他们需要学习一种新的语言,但对于 Dart 而言只需要一两个小时,最多一天就够了。 +8. **Flutter很棒。** 它并不是完美的,但按照我个人的观点,它的学习曲线,易用性,可用的工具使它成为了比我以前用过的所有框架都更好的移动开发框架。 + +### Google 应该做什么 + +1. Google 的团队成员和朋友们应该在 Google Groups 中**继续提供有内涵、友好和即时的支持**。这是一个很大的加分项,也是使得该框架在易用性和支持方面如此出色的原因。支持和培育开发者社区的团队**心态良好、令人喜爱,而且这是很重要的**。 +2. 调查开发者社区的成员,以确定哪些控件可能不太有用。**对于那些不太有用的控件,只要把它们从文档和教程中移除**或反对使用它们即可。比如,‘Center’ 控件很适合 Hello, World 容器,但我从没有理解过它。为什么更常用的 “Container” 并没有一个实现同样功能的属性?这是一个非常低级的例子,但我认为这是 Go 之所以这么成功的原因之一,因为它的核心库长期保持简明。 +3. **更专注于 iOS 用户。** Material Design 很适合快速开发针对安卓用户的东西。但我从不把它用于 iOS app。我觉得相比于 Swift,Flutter 更加简便,不需要学习大量的库。我觉得很多 iOS 用户会很喜欢学习 Flutter,如果它有更多 iOS 风格控件的话。 +4. 实现更加真实的特性和屏幕的**更多教程**。我希望看见更多像这样的教程:[https://flutter.io/get-started/codelab/](https://flutter.io/get-started/codelab/) 以及更多“端到端”的教程,其中包括后端的集成。 +5. **App 主题**应当**更少专注于 Material Design**。再说一次,我在开发 iOS 时不希望使用 “MaterialApp” 控件。主题看起来和它很紧地耦合,但主题应该是更加普适的。 +6. **文档中更少使用 Firebase** 或不要这样经常地推动用户使用。我发现 Firebase 很适合快速开发,并且它对于新用户来说非常易用。但是很大数量的人已经有一个后端,或者永远不会考虑使用 Firebase。所以我觉得更侧重终于如何使用 JSON 和简单的 web 服务将很有帮助。我必须阅读很多第三方教程,因为我觉得文档不够现实。我将在将来的博文中深入说明这一点。 + +目前为止,Flutter 使我感到非常愉快。 + +接下来,我将会考虑重写另一个我在 iOS 应用商店中的 app [www.brewswap.co](http://www.brewswap.co),它更复杂(Tinder 风格的照片滑动,实时聊天,等等)。 + +目前为止,这些是我能想到的主要缺点。像所有其它框架一样,它有很多奇怪的问题,学习曲线也不完全合理,但总的来说,**Flutter 使我感到可以投入大量时间,而且更重要的是,享受使用它**。 + +做好收到最初几期 Flutter 教程的准备,并且我希望我可以为准备投入 Flutter 的人提供一些有用的见解——我说,赶快开始吧! + +如果任何人有任何问题,最好[**在 Twitter 上 ping 我**](https://twitter.com/seenickcode) **@seenickcode**。 + +**更新**:[注册](https://mailchi.mp/5a27b9f78aee/practical-flutter)后可以在 Practical Flutter 课程上线时收到通知。🚀 + +使用 Flutter 愉快。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/what-we-learned-migrating-off-cron-to-airflow.md b/TODO1/what-we-learned-migrating-off-cron-to-airflow.md new file mode 100644 index 00000000000..081215de555 --- /dev/null +++ b/TODO1/what-we-learned-migrating-off-cron-to-airflow.md @@ -0,0 +1,68 @@ +> * 原文地址:[What we learned migrating off Cron to Airflow](https://medium.com/videoamp/what-we-learned-migrating-off-cron-to-airflow-b391841a0da4) +> * 原文作者:[Katie Macias](https://medium.com/@katiemacias?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/what-we-learned-migrating-off-cron-to-airflow.md](https://github.com/xitu/gold-miner/blob/master/TODO1/what-we-learned-migrating-off-cron-to-airflow.md) +> * 译者:[cf020031308](https://github.com/cf020031308) +> * 校对者:[yqian1991](https://github.com/yqian1991) + +# 从 Cron 到 Airflow 的迁移中我们学到了什么 + +![](https://cdn-images-1.medium.com/max/800/1*nK7DJiewFjF4E6F8BdrSMA.png) + +去年秋天,VideoAmp 数据工程部门正在进行重大变革。当时的团队由三名数据工程师和一名与我们密切合作的系统工程师组成。我们集体确定了如何优化公司的技术债。 + +当时,数据团队是所有批处理作业的唯一所有者,这些作业从我们的实时竞价仓库获取反馈,提供给 Postgres 数据库,由 Postgres 数据库向 UI 填充。如果这些作业失败,UI 就会过时,我们的内部交易员和外部客户将只有陈旧数据可供依据。因此,满足这些作业的服务等级协议对于平台的成功至关重要。大多数这些作业都是用 Scala 构建的,并且使用了 Spark。这些批处理作业通过 Cron(一种内置于 Linux 的调度工具)被精心地编排。 + +**Cron 的优点** + +我们发现 Cron 造成的一些主要的痛点甚至盖过了其带来的好处。 Cron 内置于 Linux 中,无需安装。此外,Cron 相当可靠,这使它成为一个吸引人的选择。因此,Cron 是项目概念证明的绝佳选择。但成规模使用时效果并不好。 + +![](https://cdn-images-1.medium.com/max/800/0*eMxK4MoEOhgJTlQJ.) + +Crontab 文件:以前如何安排应用程序 + +**Cron 的缺点** + +Cron 的第一个问题是对 crontab 文件的更改不容易追踪。 crontab 文件包含了在该计算机上运行的作业的调度计划,这些计划是跨项目的,但不会在源码管理中跟踪,也不会集成到单个项目的部署过程中。相反,工程师会随需要进行编辑,但不记录时间或项目依赖。 + +第二个问题是作业效果不透明。 Cron 的日志是输出到运行作业的服务器上 —— 而不是集中到某处一起。开发人员如何知道作业是成功还是失败?不论开发人员是自己浏览,还是需要暴露给下游工程团队使用,解析日志获取这些细节都是昂贵的。 + +最后,重新运行失败的作业是权宜且困难的。默认情况下,Cron 只设置了一些环境变量。新手开发人员经常会惊讶地发现存储在 crontab 文件中的 bash 命令不会产生与其终端相同的输出,因为他们的 bash 配置文件中的设置并不存在于 Cron 环境中。这要求开发人员构建出执行命令的用户环境的所有依赖关系。 + +很明显,需要在 Cron 之上构建许多工具才能获得成功。虽然我们略微解决了一些,但我们知道有许多功能更强大的开源选项可用。我们整个团队使用过的编排工具统共有 Luigi、Oozie 和其他定制解决方案等,但最终这些经历仍让我们觉得不满。而 AirBnB 的 Airflow 能入选 Apache 孵化器,这正意味着它众望所归。 + +**设置和迁移** + +以典型的黑客方式,我们秘密地从现有技术栈中获取资源,通过设置 Airflow metadb 和主机来证明我们的想法。 + +此 metadb 包含了重要的信息,如单向无环图(DAG)和任务清单、作业的效果和结果,以及用于向其他任务发送信号的变量。 Airflow metadb 可以构建在诸如 PostgreSQL 或 SQLite 之类的关系数据库之上。通过这种依赖,Airflow 可以扩展到单个实例之外。我们就挪用了 Amazon RDS 平台上我们另一个团队用于开发的 PostgreSQL 虚拟机(他们的开发冲刺结束了,他们不再使用这个实例)。 + +Airflow 主机安装在我们的 spark 开发虚拟机上,是我们 spark 集群的一部分。该主机上配置了 LocalExecutor,并运行着调度程序和用于 Airflow 的 UI。安装在我们的 spark 集群中的一个实例上后,我们的作业具有了执行所需要的 spark 依赖项的权限。这是成功迁移的关键,也是之前尝试失败的原因。 + +从 Cron 迁移到 Airflow 带来了特殊的挑战。由于 Airflow 自带任务调度,我们不得不修改我们的应用程序以获取新格式的输入。幸运的是,Airflow 通过变量为调度脚本提供必要的元信息。我们还去除了我们在其中构建的大部分工具的应用程序,例如推送警报和信号。最后,我们最终将许多应用程序拆分为较小的任务,以遵循 DAG 范例。 + +![](https://cdn-images-1.medium.com/max/800/0*tktxAKxxE2x4ZGA-.) + +Airflow UI:开发人员可以清楚地从 UI 中辨别出哪些 Dags 是稳定的,哪些不那么健壮,需要强化。 + +**得到的教训** + +1. **应用程序臃肿** 使用 Cron 时,调度逻辑必须与应用程序紧密耦合。每个应用程序都必须完成 DAG 的整个工作。这个额外的逻辑掩盖了作为应用程序目的核心的工作单元。这使得它既难以调试又难以并行开发。自从迁移到 Airflow 以来,将任务放在 DAG 中的想法使团队能够开发出专注于该工作单元的强大脚本,同时最大限度地减少了我们的工作量。 +2. **优化了批处理作业的效果呈现** 通过采用 Airflow UI,数据团队的批处理作业效果对团队和依赖我们数据的其他工程部门都是透明的。 +3. **具有不同技能组合的数据工程师可以共同构建一个管道** 我们的数据工程团队利用 Scala 和 Python 来执行 Spark 作业。 Airflow 为我们的团队提供了一个熟悉的中间层,可以在 Python 和 Scala 应用程序之间建立协议 —— 允许我们支持这两种技能的工程师。 +4. **我们的批处理作业调度的扩展路径很明显** 使用 Cron 时,我们的调度仅限于一台机器。扩展应用程序需要我们构建一个协调层。 Airflow 正为我们提供了开箱即用的扩展功能。使用 Airflow,从 LocalExecutor 到 CeleryExecutor,从单个机器上的 CeleryExecutor 到多个 Airflow worker,路径清晰可见。 +5. **重新运行作业变得简单容易** 使用 Cron,我们需要获取执行的 Bash 命令,并希望我们的用户环境与 Cron 环境足够相似,以便能为调试而重现问题。如今,通过 Airflow,任何数据工程师都可以直接查看日志,了解错误并重新运行失败的任务。 +6. **合适的警报级别** 在 Airflow 之前,批处理作业的所有告警都会发送到我们的流式数据应用程序的告警邮箱中。通过 Airflow,该团队构建了一个 Slack 操作符,可被所有 DAG 统一调用来推送通知。这使我们能够将来自我们的实时竞价堆栈的紧急失败通知与来自我们的批处理作业的重要但不紧急的通知分开。 +7. **一个表现不佳的 DAG 可能会导致整个机器崩溃** 为您的数据团队设立规范,来监控其作业的外部依赖性,以免影响其他作业的服务等级协议。 +8. **滚动覆盖您的 Airflow 日志** 这应该不言而喻,但 Airflow 会存储所有它所调用的应用程序的日志。请务必适当地对日志进行滚动覆盖,以防止因磁盘空间不足而导致整个机器停机。 + +五个月后,VideoAmp 的数据工程团队规模几乎增加了两倍。我们管理着 36 个 DAG,并且数量还在增加! Airflow 经过扩展可让我们所有的工程师为我们的批处理流程做出贡献和支持。该工具的简单性使得新工程师上手相对无痛。该团队正在快速开发改进,例如对我们的 slack 频道进行统一的推送告警、升级到 Python3 Airflow、转移到 CeleryExecutor,以及利用 Airflow 提供的强大功能。 + +有任何疑问或意见可在这里直接询问,或在下面分享你的经验。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/whats-coming-up-in-javascript-2018-async-generators-better-regex.md b/TODO1/whats-coming-up-in-javascript-2018-async-generators-better-regex.md new file mode 100644 index 00000000000..80c066893ba --- /dev/null +++ b/TODO1/whats-coming-up-in-javascript-2018-async-generators-better-regex.md @@ -0,0 +1,71 @@ +> * 原文地址:[What’s Coming Up in JavaScript 2018: Async Generators, Better Regex](https://thenewstack.io/whats-coming-up-in-javascript-2018-async-generators-better-regex/) +> * 原文作者:[Mary Branscombe](https://thenewstack.io/author/marybranscombe/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/whats-coming-up-in-javascript-2018-async-generators-better-regex.md](https://github.com/xitu/gold-miner/blob/master/TODO1/whats-coming-up-in-javascript-2018-async-generators-better-regex.md) +> * 译者:[MeFelixWang](https://github.com/MeFelixWang) +> * 校对者:[CoderMing](https://github.com/CoderMing) + +# JavaScript 2018 中即将迎来的新功能:异步生成器及更好的正则表达式 + +![](https://cdn.thenewstack.io/media/2018/08/ba3bc5a9-res-3615421_1920-1024x681.jpg) + +2018 年 6 月发布的最新年度 [ECMAScript 更新](http://www.ecma-international.org/ecma-262/9.0/index.html),尽管在常见功能的积压上仍然远远小于 ECMAScript 6,但依然是迄今为止最大的年度版本。 + +身为 ECMAScript 编辑及微软在 [ECMA TC39 委员会](https://github.com/tc39)代表的 [Brian Terlson](https://github.com/bterlson) 告诉 The New Stack:这个版本中两个最大的开发者功能是异步生成器和一些期待已久的正则表达式改进,以及 rest/spread 属性。 + +“异步生成器和迭代器是将异步函数和迭代器结合起来的结果,所以它就像你可以在其中等待的异步生成器或你可以从中得到返回值的异步函数,”他解释道。以前,ECMAScript 允许你编写一个可以输入或等待但不能同时进行两者操作的函数。“这对于在 Web 平台占比越来越大的消费流来说非常方便,尤其是在 Fetch 对象公开流的情况下。” + +异步迭代器类似于 Observable 模式,但更灵活。“Observable 是推模型; 一旦你订阅了它,无论你是否准备好,你都会被爆炸式的事件和通知冲击,所以你必须实施缓冲或采样策略来处理干扰,”Terlson 解释道。异步迭代器是一种推拉模型 —— 你请求一个值后发送给你 —— 这对于诸如网络 IO 原语之类的东西更有效。 + +[Promise.prototype.finally](https://github.com/tc39/proposal-promise-finally) 对异步编程也很有帮助,在一个 promise 状态变为 fulfilled 或 rejected 后,指定一个最终方法来进行清理。 + +## 更多常规正则表达式功能 + +Terlson 对正则表达式的改进感到特别兴奋(其中大部分工作都是由 V8 团队完成的,他们已经完成了这四个主要功能的早期实现),因为这是此语言落后的领域。 + +“自从 JavaScript 诞生之日起,ECMAScript 正则表达式就没有过显著进步;几乎所有其他编程语言的正则表达式库都比 ECMAScript 的功能高级。“ECMAScript 6 包含了[一些小的更新](http://2ality.com/2015/07/regexp-es6.html),但他将 ECMAScript 2018 视为“第一次明显改变你如何编写正则表达式的更新”。 + +[dotAll 标志](https://github.com/tc39/proposal-regexp-dotall-flag)使点字符匹配所有字符,而不再会对匹配一些换行符(比如 n 或 f )无效。“你不能使用点字符,除非你处于多行模式并且你不关心每行的结束,”他指出。这方面的变通办法创造了一些不必要的复杂的正则表达式,Terlson 期望“每个人都能在正则表达式中使用该模式”。 + +[命名捕获组](https://github.com/tc39/proposal-regexp-named-groups)与许多其他语言中的命名组类似,你可以在命名正则表达式匹配的字符串中的不同部分,并将其视为对象。“这几乎等同于在你的正则表达式中添加注释,通过赋予它一个名字来解释该组试图捕捉的内容,”他解释道。“这个模式的一部分是月份,这是出生日期......这对于未来其他人维护你的模式真的很有帮助。” + +还有其他关于空字符的提案,即告诉正则表达式引擎忽略模式匹配中的空格、换行符以及注释,允许在空格后的行尾添加注释,这种特性可能包含在 ECMAScript 的未来版本中并将进一步提高可维护性。 + +以前 ECMAScript 有先行断言但没有后行断言。“人们使用了一些技巧,比如反转字符串,然后进行匹配,或一些其他 hacks,”Terlson 指出。这对于查找和替换的正则表达式特别有用。“你看到的并没有成为你匹配的一部分,所以如果你要替换前后任何一边有美元符号的数字,你就可以做到这一点而无需做额外的工作将美元符号重新放回去。”ECMAScript [后行断言](https://github.com/tc39/proposal-regexp-lookbehind)允许像 C# 中那样的可变长度的后行断言,而不仅仅是 Perl 中的固定长度模式。 + +特别是对于需要支持国际用户的开发人员,允许在正则表达式中使用 [Unicode 属性转义](https://github.com/tc39/proposal-regexp-unicode-property-escapes#ecmascript-proposal-unicode-property-escapes-in-regular-expressions) `\\p{…}` 和 `\\P{…}` 将使创建 Unicode 可识别的正则表达式变得更加容易。目前,这对开发人员来说是件很麻烦的事。 + +“Unicode 定义了数字,但这些数字不仅包括基本拉丁语 ASCII 0 到 9,还包括数学数字,粗体数字,大纲数字,花哨的演示数字,表格数字。如果要匹配 Unicode 中的任何数字,则 Unicode 可识别的应用程序必须具有可用的整个 Unicode 数据表。通过添加此功能,你可以将这些全部委托给 Unicode,”他说。如果你想以严格的方式匹配 Unicode 字符,比如说进行表单验证,并且你想做正确的事情而不是告诉人们他们的名称是无效的,这在很多情况下很难做到,但是使用 Unicode 字符类你可以明确指出名称所需的字符范围。已经有了不同语言和脚本的类,所以如果你只想处理希腊语或汉字,完全可以做到。Emoji 正变得越来越普遍。 + +还有一些新的国际化 API,用于本地化的[日期和时间格式](https://github.com/tc39/proposal-intl-formatToParts),欧元货币格式和[复数形式](https://github.com/tc39/proposal-intl-plural-rules),这样可以更轻松地执行本地化标签和按钮等操作。 + +ECMAScript 2018 扩展了[对象](https://github.com/tc39/proposal-object-rest-spread)和数组对 rest 和 spread 模式的支持(在 React 生态系统中很常见,许多开发人员都没有意识到它还没有完全标准化),Terlson 称之为有超大影响的小功能。rest 和 spread 对于复制和克隆对象很有用,例如,如果你有一个不可变的结构,而你要更改除一个属性之外的所有内容,或者你想复制一个对象但添加一个额外的属性。Terlson 指出,这种模式经常用于为选项记录分配默认值。“对于你一直在做的事情来说,这是一个非常好的语法模式。” + +Babel 和 TypeScript 等转换器已经支持 ECMAScript 2018 的许多功能。浏览器支持也将随着时间的推移实现,并且所有新功能都已经在 Chrome 的发布版本中(要获得完整的支持矩阵图表,请查看 [ECMAScript 兼容性表](http://kangax.github.io/compat-table/es2016plus/))。 + +[![](https://cdn.thenewstack.io/media/2018/08/cf694974-ecmascript.png)](https://cdn.thenewstack.io/media/2018/08/cf694974-ecmascript.png) + +ECMAScript 兼容性表检测到的浏览器支持情况。 + +## 未来发展;ECMAScript 2019 + +一些有趣的提案尚未达到成为 ECMAScript 标准的一部分所必需的第四阶段,包括对私有字段和方法的声明略有争议的想法,其中包括许多备选提案。 + +当在 ECMAScript 6 中引入类时,它们是“极小的”,Terlson 解释为“故意在很小[范围],因为我们将在以后继续处理它们。”私有字段允许开发人员声明可以在类的内部通过名称进行引用的字段,但不能从类的外部访问,”他说。这不只是提供了更好的性能,因为当在类构造函数中声明所有字段时,运行时可以更好地优化对象的处理,但也是语言强制实现隐私,而 TypeScript 中的私有字段则不是这样。与 symbols 不同,你可以使用 get 属性列出对象上的所有 symbols,私有字段将不允许反射。 + +“库作者正在寻求一种拥有私人状态的方式,以便开发者不能依赖它,”Terlson 解释道。“即使做了他们不应该做的事情,库也不喜欢打断用户。”例如,类中的私有属性将允许库作者避免暴露内部实现细节,如果他们将来可能会修改的话。 + +BigInt 提案也处于第三阶段。目前,ECMAScript 只有 64 位浮点数类型,但许多平台和 web API 使用 64 位整数 — 包括 [Twitter 用作推文 ID](https://dev.twitter.com/overview/api/twitter-ids-json-and-snowflake) 的 64 位整数。“你不能再将 JavaScript 中的推文 ID 表示为数字,”Terlson 解释道。“它们必须表示为一个字符串。” BigInt 是一个更通用的提案,用于添加任意精度的整数,而不只是添加 64 位整数。加密 API 和高精度计时器也将利用这一点,Terlson 预计 JIT JavaScript 引擎可能会使用原生 64 位字段来提供大整数以提升性能。 + +两项提案已经进入第四阶段;让 catch 绑定成为可选项(如果你不需要实际使用变量,就不必再将变量传递给 catch 块),以及进行[小的语法更改](https://github.com/tc39/proposal-json-superset)以处理 JSON 和 ECMAScript 字符串格式之间的不匹配。这些将与其他在未来几个月内取得进展的提案一起进入 ECMAScript 2019。 + +微软是 The New Stack 的赞助商。 + +首图[来自](https://pixabay.com/en/res-the-wind-pbx-current-3615421/) Pixabay。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/whats-next-for-mobile-at-airbnb.md b/TODO1/whats-next-for-mobile-at-airbnb.md new file mode 100644 index 00000000000..61ead949f93 --- /dev/null +++ b/TODO1/whats-next-for-mobile-at-airbnb.md @@ -0,0 +1,217 @@ +> * 原文地址:[What’s Next for Mobile at Airbnb:: Bringing the best back to native](https://medium.com/airbnb-engineering/whats-next-for-mobile-at-airbnb-5e71618576ab) +> * 原文作者:[]() +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/whats-next-for-mobile-at-airbnb.md](https://github.com/xitu/gold-miner/blob/master/TODO1/whats-next-for-mobile-at-airbnb.md) +> * 译者:[ALVINYEH](https://github.com/ALVINYEH) +> * 校对者:[DateBro](https://github.com/DateBro) + +# Airbnb 移动端路在何方? + +## 发挥原生最大的潜力 + +![](https://cdn-images-1.medium.com/max/2000/1*_N3sz8fhNFU5tB5YTVfGHg.jpeg) + +**这是[系列博客文章](https://juejin.im/post/5b2c924ff265da59a401f050)中的第五篇,本文将会概述使用 React Native 的经验,以及 Airbnb 移动端接下来要做的事情。** + +### 激动人心的时刻即将来临 + +即使当初在尝试使用 React Native 时,我们也同时加快了原生的开发。今天,我们在生产环境或正在进行中的项目方面,有许多令人激动的计划。其中一些项目的灵感,来自我们使用 React Native 的最佳部分和经验。 + +#### 服务器驱动渲染 + +即使我们已不再使用 React Native,但也看到了只编写一次产品代码的价值。我们仍然非常依赖通用设计语言系统([DLS](https://airbnb.design/building-a-visual-language/)),因为许多页面在 Android 和 iOS 上几乎一模一样。 + +几个团队已经尝试开始在强大的服务器驱动的渲染框架上达成一致。使用这些框架,服务器将数据发送到设备,描述需要渲染的组件,页面配置以及可能发生的操作。然后,每个移动平台都会对这些数据进行解析,并使用 DLS 组件渲染原生页面,甚至是整个流程。 + +服务器驱动的大规模渲染还有很多难题。下面是我们正在解决的几个问题: + +* 在保持向下兼容性的同时,需要安全地更新组件定义。 +* 跨平台共享组件的类型定义。 +* 在运行时响应事件,如按钮点击或用户输入。 +* 在保留内部状态的同时,在多个 JSON 驱动的屏幕之间进行过渡。 +* 在构建时渲染完全没有现有实现的自定义组件。我们正在试验 [Lona](https://github.com/airbnb/Lona/) 格式。 + +服务器驱动的渲染框架已经提供了巨大的价值,我们可以即时实验和更新功能。 + +#### Epoxy 组件 + +2016 年,我们开源了 Android 的 [Epoxy](https://github.com/airbnb/epoxy)。Epoxy 是一个框架,可以实现简单的异构 RecyclerView、UICollectionView 和 UITableView。今天,大多数新页面都采用了 Epoxy。这可以让我们将每个页面拆分为独立的组件,实现延迟渲染。现今,我们在 Android 和 iOS 上都有用 Epoxy。 + +在 iOS 上大概长这个样子: + +``` +BasicRow.epoxyModel( + content: BasicRow.Content( + titleText: "Settings", + subtitleText: "Optional subtitle"), + style: .standard, + dataID: "settings", + selectionHandler: { [weak self] _, _, _ in + self?.navigate(to: .settings) + }) +``` + +在 Android 上,我们利用使用 [Kotlin 编写 DSL](https://kotlinlang.org/docs/reference/type-safe-builders.html),使编写组件更加简单和类型安全: + +``` +basicRow { + id("settings") + title(R.string.settings) + subtitleText(R.string.settings_subtitle) + onClickListener { navigateTo(SETTINGS) } +} +``` + +#### Epoxy Diffing + +在 React 中,利用 [render](https://reactjs.org/tutorial/tutorial.html#what-is-react) 可返回一个组件列表。React 性能的关键在于,这些组件只表示你要渲染的实际视图/HTML 的数据模型。然后对组件树进行扩展,只渲染更改的部分。我们为 Epoxy 建立了一个类似的概念。在 Epoxy 中,你可以在 [buildModel](https://reactjs.org/tutorial/tutorial.html#what-is-react) 中为整个页面声明模型。与优雅的 Kotlin 和 DSL 搭配使用,在概念上与 React 非常相似,看起来像这样: + +``` +override fun EpoxyController.buildModels() { + header { + id("marquee") + title(R.string.edit_profile) + } + inputRow { + id("first name") + title(R.string.first_name) + text(firstName) + onChange { + firstName = it + requestModelBuild() + } + } + // Put the rest of your models here... +} +``` + +每当数据发生变化时,你都要调用 `requestModelBuild()`,这个方法会重新渲染你的页面,并调用最佳的 RecyclerView。 + +在 iOS 上大概长这个样子: + +``` +override func itemModel(forDataID dataID: DemoDataID) -> EpoxyableModel? { + switch dataID { + case .header: + return DocumentMarquee.epoxyModel( + content: DocumentMarquee.Content(titleText: "Edit Profile"), + style: .standard, + dataID: DemoDataID.header) + case .inputRow: + return InputRow.epoxyModel( + content: InputRow.Content( + titleText: "First name", + inputText: firstName) + style: .standard, + dataID: DemoDataID.inputRow, + behaviorSetter: { [weak self] view, content, dataID in + view.textDidChangeBlock = { _, inputText in + self?.firstName = inputText + self?.rebuildItemModel(forDataID: .inputRow) + } + }) + } +} +``` + +#### 一个新的 Android 产品架构(MvRx) + +最近令人非常激动的进展之一是,我们正在开发新架构,内部称之为 MvRx。 MvRx 结合了 Epoxy、[Jetpack](https://developer.android.com/jetpack/)、[RxJava](https://github.com/ReactiveX/RxJava) 的优点,以及 Kotlin 与 React 的许多原理,构建出的新页面比以往任何时候都更容易、更流畅。它是一个固执己见而又灵活的框架,通过采用我们观察到的共同开发模式以及 React 的最佳部分而开发出来的。同时它也是线程安全的,几乎所有事情都从主线程运行,这使得滚动和动画都能变得非常流畅。 + +到目前为止,它已经在各种页面上正常工作了,并且几乎不用去处理生命周期。我们目前正在针对一系列 Android 产品进行试用,如果它能继续取得成功,我们会计划开源。这是创建发出网络请求的功能页面所需的完整代码: + +``` +data class SimpleDemoState(val listing: Async = Uninitialized) + +class SimpleDemoViewModel(override val initialState: SimpleDemoState) : MvRxViewModel() { + init { + fetchListing() + } + + private fun fetchListing() { + // This automatically fires off a request and maps its response to Async+ // which is a sealed class and can be: Unitialized, Loading, Success, and Fail. + // No need for separate success and failure handlers! + // This request is also lifecycle-aware. It will survive configuration changes and + // will never be delivered after onStop. + ListingRequest.forListingId(12345L).execute { copy(listing = it) } + } +} + +class SimpleDemoFragment : MvRxFragment() { + // This will automatically subscribe to the ViewModel state and rebuild the epoxy models + // any time anything changes. Similar to how React's render method runs for every change of + // props or state. + private val viewModel by fragmentViewModel(SimpleDemoViewModel::class) + + override fun EpoxyController.buildModels() { + val (state) = withState(viewModel) + if (state.listing is Loading) { + loader() + return + } + // These Epoxy models are not the views themself so calling buildModels is cheap. RecyclerView + // diffing will be automaticaly done and only the models that changed will re-render. + documentMarquee { + title(state.listing().name) + } + // Put the rest of your Epoxy models here... + } + + override fun EpoxyController.buildFooter() = fixedActionFooter { + val (state) = withState(viewModel) + buttonLoading(state is Loading) + buttonText(state.listing().price) + buttonOnClickListener { _ -> } + } +} +``` + +MvRx 的架构比较简单,主要用于处理 Fragment 参数,跨进程重启的 savedInstanceState 持久性,TTI 跟踪以及其他一些功能。 + +我们还在开发一个类似的 iOS 框架,该框架正在进行早期测试。 + +预计很快会听到更多这方面的消息,我们对迄今取得的进展感到兴奋。 + +#### 迭代速度 + +当从 React Native 切换回原生时,马上显现出来的问题就是迭代速度。从一个在一或两秒就能可靠地测试更改部分的平台,到一个可能需要等待 15 分钟的平台,根本无法接受。幸好,我们也找到了一些补救措施。 + +我们在 Android 和 iOS 上构建了基础架构,可以只编译包含启动器的应用中的一部分,并且可以依赖于特定的功能模块。 + +在 Android 上,这里使用了 [gradle product flavors](https://developer.android.com/studio/build/build-variants#product-flavors)。我们的 gradle 模块看起来像这样: + +![](https://cdn-images-1.medium.com/max/1600/1*KVrbsdwESyfbtKFeh2acXg.png) + +这种新的间接层,使得工程师们能够在应用的一小部分上进行构建和开发。与 [IntelliJ 的卸载模块](https://blog.jetbrains.com/idea/2017/06/intellij-idea-2017-2-eap-introduces-unloaded-modules/)配合使用,大大提高了 MacBook Pro 上的构建时间和 IDE 性能。 + +我们编写了脚本来创建新的测试 flavor,在短短几个月内,我们已经创建了 20 多个。使用这些新的 flavor 开发版本平均要快 2.5 倍,花费 5 分钟以上的构建时间百分比下降了 15 倍。 + +作为参考,这是 [gradle 代码段](https://gist.github.com/gpeal/d68e4fc1357ef9d126f25afd9ab4eee2),可用于动态生成具有根依赖性模块的 product flavor。 + +同样,在 iOS 上,我们的模块如下所示: + +![](https://cdn-images-1.medium.com/max/1600/1*AVB7em_JCmj-JmjTCkLdQw.png) + +相同系统的构建速度可提高 3-8 倍 + +### 结论 + +很高兴能够成为一家不怕尝试新技术,同时又努力保持高质量、高速度和良好开发体验的公司。最后,React Native 是一个发行新功能的重要工具,它为我们提供了新的移动开发思路。如果你想参与其中,[请告诉我们](https://www.airbnb.com/careers/departments/engineering)! + +* * * + +这是系列博客文章的第五部分,重点讲述了我们使用 React Native 的经验,以及 Airbnb 移动端接下来要做的事情。 + +* [第一部分:Airbnb 中的 React Native](https://juejin.im/post/5b2c924ff265da59a401f050) +* [第二部分:技术细节](https://juejin.im/post/5b3b40a26fb9a04fab44e797) +* [第三部分:构建跨平台的移动端团队](https://juejin.im/post/5b446177f265da0f7c4faec8) +* [第四部分:在 React Native 上作出的决策](https://juejin.im/post/5b447b1e6fb9a04fd3437dad) +* [**第五部分:移动端接下来的事情**](https://github.com/xitu/gold-miner/blob/master/TODO1/whats-next-for-mobile-at-airbnb.md) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/whats-the-difference-between-dogs-html-and-dogs-html.md b/TODO1/whats-the-difference-between-dogs-html-and-dogs-html.md new file mode 100644 index 00000000000..c2fc8d73d46 --- /dev/null +++ b/TODO1/whats-the-difference-between-dogs-html-and-dogs-html.md @@ -0,0 +1,108 @@ +> * 原文地址:[What’s the difference between ./dogs.html and /dogs.html?](https://css-tricks.com/whats-the-difference-between-dogs-html-and-dogs-html/) +> * 原文作者:[CHRIS COYIER](https://css-tricks.com/author/chriscoyier/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/whats-the-difference-between-dogs-html-and-dogs-html.md](https://github.com/xitu/gold-miner/blob/master/TODO1/whats-the-difference-between-dogs-html-and-dogs-html.md) +> * 译者:[Shery](https://github.com/shery) +> * 校对者:[Kasheem Lew](https://github.com/kasheemlew) [Cherry](https://github.com/sunshine940326) + +# ./dogs.html 和 /dogs.html 间有什么区别? + +它们**都是** **URL 路径**。但是他们名字不同。 + +```html + +Dogs + + +Dogs +``` + +还有完整 URL 路径,如下所示: + +```html + +Dogs +``` + +全限定 URL 的功能再明显不过 —— 它会指向一个确切的页面。所以,让我们再来看看前两个例子。 + +假设你的网站上有这样的目录结构: + +``` +public/ +├── index.html +└── animals/ + ├── cats.html + └── dogs.html +``` + +如果你在 `cats.html` 上放置了一个链接到 `/dogs.html`(一个“绝对”路径)的超链接,那么它将指向 404 页面 —— 这个网站的根目录那一层没有 `dogs.html` 文件!在路径开头的 `/` 意味着__“从**最底层**开始,然后再往上”__(`public/` 是最底层到目录)。 + +那个在 `cats.html` 上的链接需要写成 `./dogs.html`(从当前文件所在目录开始)或 `/animals/dogs.html`(明确说明要从哪个目录开始)。 + +当然,目录结构越复杂,绝对 URL 越长。 + +``` +public/ +├── animals/ + └── pets/ + ├── c/ + | └── cats.html + └── d/ + └── dogs.html +``` + +在这样的结构下,就想要从 `dogs.html` 链接到 `cats.html` 而言,URL 肯定是其中之一... + +```html + +cats + + +cats +``` + +在这种情况下值得注意的是,如果 `animals/` 被重命名为 `animal/`,就会使得绝对链接失效,但是相对链接仍会有效。这可能是使用绝对链接的缺点。当你使用绝对链接时,改变路径将会影响你的链接。 + +我们只研究了 HTML 文件中链接到 HTML 文件的情形,但基本上这个思路对于网页(和计算机)是通用的。例如,在 CSS 文件中,你可能有下面这样的代码: + +```css +body { + /* 当前文件所在目录下的 /images 目录里的图片 */ + background-image: url(./images/pattern.png); +} +``` + +...在这种情况下是正确的: + +``` +public/ +├── images/ +| └── pattern.png +├──index.html +└── style.css +``` + +但是如果你移动了 CSS 文件... + +``` +public/ +├── images/ +| └── pattern.png +├── css/ +| └── style.css +└── index.html +``` + +...紧接着就会出问题,是因为你的 CSS 文件现在嵌套在另一个目录中,引用路径变得更深。你需要使用两个点再次回到当前文件所在目录的上一级目录,例如 `../images/pattern.png`。 + +并不是哪种 URL 格式比另一种格式好 —— 它只取决于你认为当时怎样更有用、更直观。 + +对我来说,我在思考哪些东西最不可能改变。对于类似图像资源的东西,我发现我不太可能移动它,因此使用绝对 URL 路径(例如 `/images/pattern.png`)链接它似乎是最安全的。但是为了链接到恰好位于同一目录中的所有文档,使用相对链接的方式似乎更安全。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/why-designers-hate-politics-and-what-to-do-about-it.md b/TODO1/why-designers-hate-politics-and-what-to-do-about-it.md new file mode 100644 index 00000000000..e486426da98 --- /dev/null +++ b/TODO1/why-designers-hate-politics-and-what-to-do-about-it.md @@ -0,0 +1,60 @@ +> * 原文地址:[Why Designers Hate Politics (And What To Do About It)](https://medium.com/@berkun/why-designers-hate-politics-and-what-to-do-about-it-54d170a298cc) +> * 原文作者:[Scott Berkun](https://medium.com/@berkun?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/why-designers-hate-politics-and-what-to-do-about-it.md](https://github.com/xitu/gold-miner/blob/master/TODO1/why-designers-hate-politics-and-what-to-do-about-it.md) +> * 译者:[Starriers](https://github.com/Starriers) + +# 为什么设计师讨厌政治(如何解决) + +为生活而设计产品的人拥有乐观主义。为了做好他们自己的工作,无论是设计网站还是汽车,他们都坚信自己拥有能创造出比目前世界上已有产品更好的产品的能力。问题是,这种乐观主义,一旦掺杂不成熟的思想,就会对组织的工作产生肤浅的想法。设计师往往会带有充分理由的声明,他们比任何人都了解人类的行为,但他们对政治的厌恶态度表明,他们不了解群体中最自然的行为之一。那些讨厌,不理解政治的设计师背叛了他们自己的理念,因为他们没有认识到政治是如何定义他们必须工作的环境的。 + +困扰的主要原因之一是,政治一词有两种截然不同的用法: + +* Politics (n): 自私自利以及善于玩弄权术的人做的事情。 +* Politics (n): 适用于某一群体成员的决策过程。 + +当有人说“我讨厌在 Stan 组织的工作,它太政治化了”时,他们用的是第一定义,他们明确地指明滥用权力来为某人自己的利益服务或创造一种W恐惧和功能失衡的文化。这些无疑都是坏事,但政治一词经常被那些根本无法理解为什么他们的想法被否决,或为什么他们没有得到应有权力的人宽泛地使用。相比于审视他们所身处的文化(谁促使了繁荣?为什么他们那么做了,而我没有?我需要的是新技能还是新工作?),他们更愿意去指着政治理念。 + +许多设计师都拒绝接受的教训是**人类的天性就是政治性的**(上述第二定义)**。**社会学、人类学和心理学领域主要是探讨人类尝试彼此(和自己)相处的复杂挑战。简而言之,当你组织热门去做某件事时,不管是举办聚会还是创建公司,每个人对正确做法都有自己的观点。他们喜欢和谁一起工作,喜欢做什么工作。这意味着,无论一个项目负责人多么有才华,有些人永远也不会得到他们想要的一切。这会激励人们去影响那些有权势的人,或视图为自己争取权利(如果加薪、升职或者信誉危在旦夕,人们往往会忘记自己的目标)。当然,表达自己雄心壮志的方式有很多,有些会更健康,更透明,但政治无处不在。 + +> **“每一种管理行为都是一种政治行为,在某种程度上重新分配或增强权利” —— Richard Farson** + +抽象地说,指责“政治”是避免对解决问题负责的一种便捷方式。这同样适用于指责“管理”、“工程”或“营销”,并说他们有多愚蠢(参阅:[他们得不到的错误](http://scottberkun.com/2010/the-fallacy-of-they-dont-get-it/))。指指点点并不能能让他们变得愚蠢或无效,也不会转移个人的责任,学会如何与他们合作(这可能是问题的一部分),让他们更有说服力、更有协作精神或更有思想。谁知道,如果仔细观察,可能只有你认为别人无能的东西只是一个聪明的人,受到和你一样困难的政治因素的约束。当然,有些工作场所真的不好,但这主要是为了给自己找个新地工作场所,当一个天生乐观的设计师,对他们为谁工作和与谁一起工作而感到悲观时,是时候向前看了。 + +设计师喜欢讨论他们解决问题的技巧,但**政治只是另一种解决问题的方法:人性问题**。如果你用与设计或工程问题相同的乐观、纪律和创造力来来解决组织性问题,你会发现其他方式来探索和使用它们,以便做出更多的决策。这是对设计师抱怨政治的最大讽刺:设计师应该善于将解决问题理解人性结合在一起,然而他们常常无法摆脱他们对已存在问题的失望。仅仅因为你的到来而期望有一个完整的组织或个体之间突然以不同的方式进行工作是不成熟的,然而设计师往往就是那么做的。扭曲的笑话是,每个人都想要更多的权力,他们认为自己值得拥有,然而(设计师)却没发现最让他们沮丧的人是在以与自己拥有着相似的情绪做事。 + +在你组织的政治经验中,最重要的因素是你的 boss。一个好的管理者会把你从组织性的戏剧中拉回来,然后[助你成功](http://scottberkun.com/2018/set-up-to-succeed-or-fail/),然而一个糟糕的管理者却会放大组织中存在的最严重的问题。对于设计师来说,这意味着最沉重的政治复旦落在他们组织中最高级的设计师身上。他们的工作是为所有为他们工作的人铺平到了,与组织中其他有权势的人建立关系。但可悲的是设计作为一种职业,遭受了来自 [Peter Principle](https://en.wikipedia.org/wiki/Peter_principle) 的影响。过度升职的问题在工作中比比皆是,但设计是一个非常专业的领域,成为设计总监或主管的人往往比指导、领导或管理的人更擅长设计。在最好的情况下,他们直到自己的主要工作是担任 CEO 和其他高管的设计大使,建立合作伙伴关系,调整目标,并获得影响力,这些影响可以转移到自己的组织中。但是,即使作为一个没有从上面得到很多支持的个人设计师,你依然可以做很多事情。 + +即使是在最公平的组织中,前进的方法 —— 政治,也是基于你的声誉。在同一个组织中,你完成工作情况的优劣(或者你一点信誉没有)会决定你在组织中的待遇好坏。这意味着获取信任与培养来自同辈与上级的尊重是必由之路。这远比允许你在[让人们了解你的主要](http://scottberkun.com/2009/how-to-keep-your-mouth-shut/)会议上听到的“政治”挫折要高效的多。就像设计师学习用户一样,他们也可以学习他们同事和上级。通过询问简单的问题,可以提高提示更多健康的政治的可能性: + +* 我的 boss 的价值所在?她在尝试解决什么问题?我的才能对解决问题有帮助么? +* 她的 boss 尝试解决的问题是什么?他们是如何保证平衡的?(这是 boss 和越级管理者之间的真实问题么?) +* 组织中谁最让他们失望?为什么?(有权势的人也有自己的政治要处理) +* 同一阶层中谁让这里更繁荣?为什么?(如果没有人这里繁荣,那么原因是什么) +* 我可以从他们那里学到什么? +* 谁最让我失望?我的目标和他们的一致么?为什么?谁给他们设定了目标?他们与设置我的目标的人关系好么?谁才是最终的 boss,为什么他们还没有解决这个问题? +* 我的工作优先级很低,我所见到“政治”实际上只是优先级次序决定的么? +* 比我更具影响力的人让我相信他可以帮助我听到更多人的意见? +* 他们认为我对这里的文化有什么现实的期望? +* 我们薄弱的政治技能是什么?我如何才能变成一个更好的引导者?谈判专家?说服者? +* 是否有一个我可以说出自己想法并用它的名声帮助自己成长的声誉良好的人? +* 这里是否有更能配合我工作的经理? +* 或许是我需要换个新的工作环境? + +*** + +参考资源: + +* 本文改编自畅销书[让事情顺利发生](http://www.amazon.com/dp/B0026OR3AS/tag=scottberkunco-20/)的第 16 章 +* [如何让事情顺利发生](http://scottberkun.com/2012/how-to-make-things-happen/) +* [处理组织的政治](https://jarango.com/2018/04/09/dealing-with-organizational-politics/), By Jorge Arango (his essay inspired me to write this one) +* [不要毁坏文化的批判](http://scottberkun.com/2014/critique-dont-fuck-up-culture/) + +![](https://cdn-images-1.medium.com/max/800/1*akPxfOQMFk-096sGzWIIUA.jpeg) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/why-every-android-developer-should-try-out-flutter.md b/TODO1/why-every-android-developer-should-try-out-flutter.md new file mode 100644 index 00000000000..4edd2b0eaf4 --- /dev/null +++ b/TODO1/why-every-android-developer-should-try-out-flutter.md @@ -0,0 +1,60 @@ +> * 原文地址:[Why every Android Developer should try out Flutter](https://proandroiddev.com/why-every-android-developer-should-try-out-flutter-319ae710e97f) +> * 原文作者:[Aaron Oertel](https://proandroiddev.com/@aaronoe?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/why-every-android-developer-should-try-out-flutter.md](https://github.com/xitu/gold-miner/blob/master/TODO1/why-every-android-developer-should-try-out-flutter.md) +> * 译者:[ALVINYEH](https://github.com/ALVINYEH) +> * 校对者:[DateBro](https://github.com/DateBro) + +# 为什么每个 Android 开发者都应该尝试 Flutter + +几个月前,我写过一篇题为“[为什么 Flutter 能最好地改变移动开发](https://juejin.im/post/5add65c46fb9a07aa541e97e)”的文章。虽然已经过去了一段时间,但是我对 Flutter 的热爱依然非常强烈;事实上,当我继续使用它时,我意识到了我之前忽略了 Flutter 独特方面的重要性。不要误会我的意思 —— 我仍然认为 Flutter 最强大的一点就是如何解决跨平台开发的许多问题。但最近我开始关注移动开发发展的更多领域,特别是声明性用户界面的概念。 + +![](https://cdn-images-1.medium.com/max/800/0*pV87QzKfowqgkEkd) + +摄影者:来自 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 的 [Chris Charles](https://unsplash.com/@licole?utm_source=medium&utm_medium=referral)。 + +我相信你已经听过一系列关于为什么 Android 开发者应该关注 Flutter 的若干论据(如果你还没有看过,请让我谦逊地建议你瞧瞧[这个](https://proandroiddev.com/why-flutter-will-change-mobile-development-for-the-best-c249f71fa63c)),但是我想指出一个我还没有真正解决的大问题,那就是 Flutter 可以让你对 App 开发有完全不同的看法。首先,你的应用本身将采用不同的方式构建 —— 但更重要的是,实际的 UI 开发通过将其合并到你的 Dart 代码(而不是 XML)中而被推到前台,因此使它成为了“一等公民”。一旦你的 UI 代码突然出现在一种非标记语言中,你就会意识到你突然有了构建应用的可能性。说实话,在使用 Flutter 之后,我开始讨厌在 Android 上编写 UI 代码;因为在 Android 中步骤更加繁琐,虽然你仍然可以使用数据绑定等工具构建响应式应用,但它实际上比 Flutter 中要花费更多的时间。 + +当你考虑在 Android 中整合动画和其他动态数据时,使用 Flutter 的论点变得更加有力。整合动画可能会不太方便,有时你可能不得不拒绝设计师的要求,因为要实现他们的需求太难了。谢天谢地,Flutter 改变了这一切。如果你一直在关注 Flutter,你可能已经从 [Fluttery](https://medium.com/fluttery) 听说过 **Flutter 挑战**。这些挑战展示了构建具有大量自定义组件和精美设计(包括动画)的复杂 UI 的快速和直观性。在 Android 上实现这样的东西会变得非常困难 —— 特别是因为与 Flutter 不同,Android 的视图基于继承而非组合,这使得构建视图变得更加复杂。 + +下面,让我们切入正题:使用 Flutter **构建声明性 UI**,这改变了 UI 开发的一切。现在也许你在想,**Android 布局不也是以声明方式构建的吗?**答案是肯定的,但事实不是。使用 XML 来定义布局让我们有了以声明方式定义布局的感觉,但如果你的视图是完全静态的,并且所有数据都是以 XML 格式设置的,那么这种感觉才真正成立。不幸的是,这种情况几乎从未发生过;一旦添加动态数据和类似列表之类的东西,你自然必须使用一些 Java / Kotlin 代码将数据绑定到视图。然后我们最终得到某种 ViewModel,它将数据设置为视图。想象一下,这就像在 Android 上调用 `textView.text =“Hello Medium!”` 一样。在 Flutter 上,这是完全不同的:你创建了一个包含某个状态的窗口组件类,然后根据该状态以声明方式定义你的布局。每当状态改变时,我们调用 `setState()` 来重新渲染我们改变的组件树的部分。让我们看一下如何在 Flutter 中使用 API,并使用结果渲染一个 List: + +``` +@override +Widget build(BuildContext context) { + return new FutureBuilder( + future: apiClient.getUserRepositoriesFuture(username), + builder: (BuildContext context, + AsyncSnapshot snapshot) { + if (snapshot.hasError) + return new Center(child: new Text("Network error")); + if (!snapshot.hasData) + return new Center( + child: new CircularProgressIndicator(), + ); + return new ListView.builder( + itemCount: snapshot.data.nodes.length, + itemBuilder: (BuildContext context, int index) => + new RepoPreviewTile( + repository: snapshot.data.nodes[index], + ), + ); + }, + ); +} +``` + +在这里,我们使用了 `FutureBuilder` 来等待网络调用(Future)的完成。一旦网络调用完成,出现结果或错误,`FutureBuilder` 组件会在内部调用 `setState` 来调用所提供的 `builder` 方法来重新渲染。正如你在这个例子中看到的,一切都是**声明式的**。在 Android 上做同样的事情通常需要一个被动的 XML 布局,然后需要一些其他类来手动设置状态,比如 Adapter 和视图模型。这种方法的问题在于,状态可能与屏幕上渲染的状态不同。这就是为什么我们希望拥有像 Flutter 为我们提供的那样的声明性布局。我们最终编写的代码要少得多,同时将状态绑定到要在屏幕上显示的内容。 + +有了这些声明性布局,我们也开始对架构进行了不同的思考。突然间,**reactive** 这个词出现了,我们谈论了更多的是关于状态管理的内容,而不是架构。有了 Flutter,像 MVP 和 MVVM 这样的架构已经没有多大有意义了;我们不再使用它们了,而是考虑状态如何流经我们的应用。状态突然成为讨论的一个重要部分,我们将投入越来越多精力去思考构建应用的新方法上。这对我们所有人来说都是一次新的旅程,有许多事情可以解决,但最重要的是,这是我们开阔视野的机会。 + +坦白地说,Flutter 也不只有阳光和彩虹。我目前正在与 Flutter 合作开展一个更大的项目来了解它的弱点,迄今为止我遇到的最大缺陷是缺乏基础设施。当我尝试使用 Graphql-API 时,这个问题就非常明显;虽然有库确实会这样做,但它们并没有接近 Android 与 Apollo 的关系。不过,好消息是,Flutter 迎头赶上只是时间的问题,在此期间扩展现有的库,甚至建立自己的库并不困难。请注意,你可能需要花一些时间投入在应用程序的基础设施中,而对于 Android 和 iOS 来说,情况通常并非如此 —— 毕竟,天下没有免费的午餐。 + +最后,我最近从使用 Flutter 中得到的最大启示之一就是,体验这种构建 UI 的声明方式以及它对状态管理的影响是非常有用的。我觉得 Flutter 太棒了;不过,我告诫你不要把它当作解决你所有问题的银弹,而应该是作为一种创新的工具,它可以比在 Android 上更快地构建漂亮的自定义 UI。更重要的是,它展示了强大的声明性布局功能,并让你将应用视为渲染状态,而不是非连贯性 Activity,视图和视图模型 —— 仅此而言,我强烈建议你尝试一下 Flutter。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/why-is-front-end-development-so-unstable.md b/TODO1/why-is-front-end-development-so-unstable.md new file mode 100644 index 00000000000..d873f1432ee --- /dev/null +++ b/TODO1/why-is-front-end-development-so-unstable.md @@ -0,0 +1,118 @@ +> * 原文地址:[Why is Front-End Development So Unstable?](http://www.breck-mckye.com/blog/2018/05/why-is-front-end-development-so-unstable/) +> * 原文作者:[Jimmy Breck-McKye](http://www.breck-mckye.com) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/why-is-front-end-development-so-unstable.md](https://github.com/xitu/gold-miner/blob/master/TODO1/why-is-front-end-development-so-unstable.md) +> * 译者:[Colafornia](https://github.com/Colafornia) +> * 校对者:[geniusq1981](https://github.com/geniusq1981) [sunhaokk](https://github.com/sunhaokk) + +# 为何前端开发如此不稳定 + +我们都知道这样一个笑话:在你学会一项前端技术的时候,另外三项新技术已经发布了。不仅如此,你刚学会的那个也已经被弃用了。 + +我们却不常看到有解释为什么会这样。 + +典型的解释(来源于 reddit 的 `r/programming` 频道)这似乎与前端开发者天生不耐烦,追逐流行与能力有限相关,这种解释构成了一个更普遍的谬论:假设你所不理解的行为是由整个群体的愚蠢,糟糕或贪婪造成的 (而你自己的不明智行为完全是由你无法控制的因素造成的)。 + +无论它是不是谬论,我们确实有这个问题,对吗? + +### 量化问题 + +在跑偏之前,我们有必要确定这个问题是否真的有现实依据。前端技术真的变化很快吗? + +从主流受关注(也可能不是)的技术来看,细想一下这个 [Github](https://github.com/collections/front-end-javascript-frameworks) 上“高星” JavaScript 前端技术排行: + +``` ++------------------------------------------------------------+ +| 库 | Star 数 | 发布时间 | 年龄 | +|------------------------------------------------------------+ +| React | 96986 | 2015 年 3 月 | 3 年 | +| Vue | 95727 | 2015 年 10 月 | 2.5 年 | +| Angular (1) | 58531 | 2010 年 10 月 | 7.5 年 | +| jQuery | 49061 | 2006 年 8 月 | 11 年 | +| Angular (2+) | 36665 | 2015 年 12 月 | 2.5 年 | +| Backbone | 27194 | 2010 年 10 月 | 7.5 年 | +| Polymer | 19668 | 2015 年 5 月 | 3 年 | +| Ember | 19003 | 2011 年 12 月 | 6.5 年 | +| Aurelia | 10506 | 2016 年 6 月 | 2 年 | +| Knockout | 8894 | 2010 年 7 月 | 8 年 | ++------------------------------------------------------------+ +``` + +最年轻的项目已经两岁半了,虽然并不是**那么**老,例如,它都不到你的桌面操作系统维护周期的一半长,但是也不是像那个笑话说的那样。所以是什么导致人们对前端有了这种更迭快速、甚至不可持续变化的感觉呢? + +### React 与它的朋友们 + +可能是 React 造成的。作为一个强力工具,它需要一系列的辅助模块和支持库来支撑,这就是问题所在。React 生态中我称之为“微型库(microlib)架构”的内容非常庞大,其应用是由无数离散的,单一用途的 JavaScript 库组成,以对 [Unix 哲学](https://homepage.cs.uri.edu/~thenry/resources/unix_art/ch01s06.html) 致敬。 + +这一架构的优点是,当新的实践出现,你可以快速适应,这在像几年前那种快速革新阶段非常有用。缺点在于它使你需要经常进行大型迭代,同时也要求你在众多(往往太多)所谓的微型库(microlib)中审查挑选。 + +这也是我论点的主旨:问题不在于 JavaScript 语言本身 [1],Web 或是任何特定的技术,而是糟糕的“做选择式架构”使得开发者不得不追逐流行趋势。 + +### NPM 问题 + +NPM 是现代 JavaScript 的最大资产,也是最大负债。它提供了丰富的模块,几乎可以满足任意特定需求,但很难对于这些模块进行过滤和管理。哪些模块是真正得到支持的?哪些模块真的有正确的功能?哪些模块不只是恶意软件的载体?JavaScript 开发者真正使用的选择方法是受欢迎程度——下载次数与 Github 上的 Star 数量,这无疑加剧了追逐流行的风气。 + +当然还有其它方法去甄别一个库:你可以浏览这个库的 Github issue 列表,在 StackOverflow 上搜索问题。你也可以做一些测试甚至自己检查源代码(在大多数情况下)。但这些方法都耗费时间,在选择类似日期解析模块这种小玩意儿时,没有必要做这些耗费时间的事。 + +我承认这是 JavaScript 开发者的一个文化缺陷。作为面试官,我经常问面试者是如何进行技术选型的,答案令人沮丧,受欢迎程度总是他们唯一知道的指标。软件工程至少在一定程度上是一项研究工作,我们需要培训初级工程师这些研究技能,但是即使我们做了培训,工程师们也未必能做出正确的选择。 + +### 想象自己是一名初级工程师 + +站在初级、中级 JavaScript 开发人员的角度,第一次编写新的应用程序。 + +起初你很天真。你的项目非常干净并希望让事情一直简单,你是一个虔诚的 Agilist(敏捷开发倡导者)而且 YAGNI(You aren't gonna need it,意为 “你不需要它”)是你的口号。因此你从一个“简单的框架”入手。这感觉挺好,对吧?(即使感觉并不好,这也经常是你唯一的选择)。 + +作为一个基本框架它能做的事很少,因此担子落在你的肩上,需要选择一些辅助库。如果你负责前端工作,可能是 Redux 的用于表单和 API 请求的辅助库。如果是后端,则可能是 Express 中间件[2]。 + +如果你用 Google 搜索一下,会发现一个强烈推荐 **X.js** 的 Medium 文章。后来发现这篇文章就是 **X.js** 的作者写的,尽管她从未声明过利益冲突(但是她提供了一个 GitTip 的 jar 包)。并不是说所有的 Medium 文章看起来都一样,因此你不能依赖某个“品牌”来识别有信誉的资料。 + +你错过了一些指出 **X.js** 致命缺陷的评论,因为 Medium 有意压下了这些评论,然后你去继续寻找一个 **Y**。 + +这次你在 Twitter 上找到了一个有一百多个红心的链接!你猜这是个好信号,因为 Twitter 是一个比你懂得更多的社区“策划”的。你在感激之情中也点了红心(像之前那一百多个人一样)然后按照链接到了 Github。 + +事情进展的没那么快。那个链接过时了——这个库已经被废弃了。你会发现这一点是因为页面上到处都是 `DEPRECATED` 这个单词,,就像史努比主题公园里的 `CONDEMNED` 标志(译者注:史努比系列电影中的一个主题)。 + +你发现 **Y.js** 是“面向对象”的。你模糊记起计算机专业大一时学到的面向对象程序设计语言和通信的内容,觉得这是一个好东西。但显然这很糟糕。 + +Medium 上的另一篇文章试图解释为什么,然而他的论证不仅含糊不清,还有一堆密密麻麻你不认识的术语。后来你发现这些术语就是文章作者自己发明的,正如他所引用的看似中立的外部博客文章一样,他引用了自己的论点。 + +情况变得更糟了。文章声称在 JavaScript 面试中提到 OOP(面向对象)也会导致你拿不到 offer!你现在已经完全懵逼了。谢天谢地,手头就有解决方案——文章作者的售价 50 刀的 JavaScript 开发课程。你记下了课程链接,感觉三生有幸才能找到这个课程,为了表示感激又给了一个 clap(此文章的第一万九千零一个 clap)(译者注:clap 是 Medium 上类似于点赞的一个东西)。 + +于是你继续找到了 Github 上的高星项目 **Z.js**,虽然它的文档看起来没什么用。文档只是列出了一堆方法,实际该怎么使用 **Z.js** 呢?至少看到 **Z.js** 使用了叫 “Standard JS” 的东西,你觉得这与 ECMA 标准委员会有关,精神一振。然而它们之间并没有什么关系。 + +作为一名初级工程师,怎么才能做得更好呢?谁可以引导你?高级工程师也同样在边学边做。我们也困在其中,只能疲于跟上潮流,维持工作。 + +所以你放弃抵抗:选择了 Gihub 上 Star 数最多,投票最多的项目。**这就是为什么 JavaScript 是由潮流和炒作驱动的**。 + +### 应该做些什么? + +和那些天生爱抱怨的人一样,我更擅长抱怨问题,而不是**解决**问题。但我有一些想法: + +### 谨慎对待 Medium + +Medium 鼓励了一些标题党,使得我们很难辨别权威内容。传统博客允许优秀作者创建独特的博客主题,有助于读者识别之前有过帮助的内容源。 + +### 谨慎对待 “自我提升” + +过去几年里看到了 JavaScript 领域中更积极的自我推销,这可能与在线付费内容的增加与作为 Github “网红” 进行就业/咨询的优势有关。对于那些为优秀内容付费并感到满意的人来说这没有任何问题,但是越来越多的人遇到了虚假策略:自我引证,发明专有术语(所以搜索引擎会将你带回作者的文章)以及名称冒用(如 “Standard.js”)。 + +### 考虑无微型库(non-microlib)架构 + +尝试用提供丰富功能特性并且无需其它插件来提升效率的框架来启动项目,这将立即减少模块变动和意想不到的变更。这也是我对 [Vue.js](https://vuejs.org/) 感兴趣的原因之一。你也可以像 [Next](https://github.com/zeit/next.js/) 一样使用 React 作为初学者工具箱或更大型框架的一部分。 + +### 不要过分焦虑就业问题 + +唯一需要在报到当天就全盘了解公司内外技术栈的是外包人员,他们可以在公司外完成项目,获得可观的薪酬。除此之外,大多数老板并不在意你不了解最新 React 辅助库的来龙去脉。因此,不必理会那些需要学习一切内容的呼吁,其中大多数都是噪音。 + +### 注释 + +[1] 这一想法有很多很多错误。 + +[2] 你敢信 Express 需要一个中间件才能解析 JSON 格式的 POST 请求?抱歉,Express 就是这么为所欲为。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/why-machine-learning-matters.md b/TODO1/why-machine-learning-matters.md index b7cf7bafc25..924d8833e11 100644 --- a/TODO1/why-machine-learning-matters.md +++ b/TODO1/why-machine-learning-matters.md @@ -2,195 +2,187 @@ > * 原文作者:[Vishal Maini](https://medium.com/@v_maini?source=post_header_lockup) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/why-machine-learning-matters.md](https://github.com/xitu/gold-miner/blob/master/TODO1/why-machine-learning-matters.md) -> * 译者: -> * 校对者: +> * 译者:[sisibeloved](https://github.com/sisibeloved) +> * 校对者:[DAA233](https://github.com/DAA233)、[whuzxq](https://github.com/whuzxq) -# Machine Learning for Humans🤖👶 +# 给人类的机器学习指南🤖👶 -## Simple, plain-English explanations accompanied by math, code, and real-world examples. +## 简单易懂的英文解释加上数学、代码和真实案例。 ![](https://cdn-images-1.medium.com/max/800/1*Vkf6A8Mb0wBoL3Fw1u0paA.jpeg) -``` -**[Update 9/1/17]** This series is now available as a full-length e-book! [Download here](https://www.dropbox.com/s/e38nil1dnl7481q/machine_learning.pdf?dl=0). -``` +**[更新于 9/1/17]** 这个系列已经有完整的电子书了![下载地址](https://www.dropbox.com/s/e38nil1dnl7481q/machine_learning.pdf?dl=0)。 -### Roadmap +### 导览 -[**Part 1: Why Machine Learning Matters**](https://medium.com/machine-learning-for-humans/why-machine-learning-matters-6164faf1df12)**.** The big picture of artificial intelligence and machine learning — past, present, and future. +**[章节 1:论机器学习的重要性](https://medium.com/machine-learning-for-humans/why-machine-learning-matters-6164faf1df12)。** 人工智能和机器学习的广阔画卷 —— 过去、现在和未来。 -[**Part 2.1: Supervised Learning**](https://medium.com/@v_maini/supervised-learning-740383a2feab)**.** Learning with an answer key. Introducing linear regression, loss functions, overfitting, and gradient descent. +**[章节 2.1:监督学习](https://medium.com/@v_maini/supervised-learning-740383a2feab)。** 学习解决方案。介绍线性回归、损失函数、过拟合和梯度下降。 -[**Part 2.2: Supervised Learning II**](https://medium.com/@v_maini/supervised-learning-2-5c1c23f3560d)**.** Two methods of classification: logistic regression and SVMs. +**[章节 2.2:监督学习 II](https://medium.com/@v_maini/supervised-learning-2-5c1c23f3560d)。** 两种分类方法:逻辑斯蒂回归和支持向量机(SVM)。 -[**Part 2.3: Supervised Learning III**](https://medium.com/@v_maini/supervised-learning-3-b1551b9c4930)**.** Non-parametric learners: k-nearest neighbors, decision trees, random forests. Introducing cross-validation, hyperparameter tuning, and ensemble models. +**[章节 2.3:监督学习 III](https://medium.com/@v_maini/supervised-learning-3-b1551b9c4930)。** 无参学习器:k-近邻算法、决策树、随机森林。介绍交叉验证、超参调整和集成模型。 -[**Part 3: Unsupervised Learning**](https://medium.com/@v_maini/unsupervised-learning-f45587588294)**.** Clustering: k-means, hierarchical. Dimensionality reduction: principal components analysis (PCA), singular value decomposition (SVD). +**[章节 3:无监督学习](https://medium.com/@v_maini/unsupervised-learning-f45587588294)。** 聚类:K-均值方法、分级。降维:主分量分析(PCA)、奇异值分解(SVD)。 -[**Part 4: Neural Networks & Deep Learning**](https://medium.com/@v_maini/neural-networks-deep-learning-cdad8aeae49b)**.** Why, where, and how deep learning works. Drawing inspiration from the brain. Convolutional neural networks (CNNs), recurrent neural networks (RNNs). Real-world applications. +**[章节 4:神经网络和深度学习](https://medium.com/@v_maini/neural-networks-deep-learning-cdad8aeae49b)。** 深度学习的工作原理。从人类的大脑中汲取灵感。卷积神经网络(CNNs)、循环神经网络(RNNs)。实际应用。 -[**Part 5: Reinforcement Learning**](https://medium.com/@v_maini/reinforcement-learning-6eacf258b265)**.** Exploration and exploitation. Markov decision processes. Q-learning, policy learning, and deep reinforcement learning. The value learning problem. +**[章节 5:强化学习](https://medium.com/@v_maini/reinforcement-learning-6eacf258b265)。** 探索和开发。马尔可夫决策过程。Q-学习、政策学习和深度强化学习。价值学习问题。 -[**Appendix: The Best Machine Learning Resources**](https://medium.com/@v_maini/how-to-learn-machine-learning-24d53bb64aa1)**.** A curated list of resources for creating your machine learning curriculum. +**[附录:机器学习的最佳资源](https://medium.com/@v_maini/how-to-learn-machine-learning-24d53bb64aa1)。** 精选的资源列表,用于创建您自己的机器学习课程。 -### Who should read this? +### 面向的读者有哪些? -* Technical people who want to get up to speed on machine learning quickly -* Non-technical people who want a primer on machine learning and are willing to engage with technical concepts -* Anyone who is curious about how machines think +* 想要快速熟悉机器学习的技术人群 +* 想要入门机器学习并愿意接受技术概念的非技术人群 +* 对于机器是如何思考感兴趣的读者 -This guide is intended to be accessible to anyone. Basic concepts in probability, statistics, programming, linear algebra, and calculus will be discussed, but it isn’t necessary to have prior knowledge of them to gain value from this series. +这份指南老少皆宜。我们将会讨论概率、统计、编程、线性代数和微积分的基本概念,但就算没有基础知识亦能有所收获。 -``` -This series is a guide for getting up-to-speed on high-level machine learning concepts in ~2-3 hours. +这个系列是一份在约 2~3 小时内快速熟悉高水平的机器学习概念的指南。 -If you're more interested in figuring out which courses to take, textbooks to read, projects to attempt, etc., take a look at our recommendations in the [Appendix: The Best Machine Learning Resources](https://medium.com/machine-learning-for-humans/how-to-learn-machine-learning-24d53bb64aa1). -``` +如果你想知道哪些课程值得学习、哪些书值得阅读、哪些项目值得尝试等等,看看我们在[附录:机器学习的最佳资源](https://medium.com/machine-learning-for-humans/how-to-learn-machine-learning-24d53bb64aa1)里的推荐吧。 -### Why machine learning matters +### 论机器学习的重要性 -Artificial intelligence will shape our future more powerfully than any other innovation this century. Anyone who does not understand it will soon find themselves feeling left behind, waking up in a world full of technology that feels more and more like magic. +与本世纪的其他创新相比,人工智能比更具前景。任何不了解它的人都会在一个充满梦幻般高科技的世界中幡然悔悟,发现自己的落伍。 -The rate of acceleration is already astounding. After a couple of [AI winters and periods of false hope](https://en.wikipedia.org/wiki/History_of_artificial_intelligence#The_first_AI_winter_1974.E2.80.931980) over the past four decades, rapid advances in data storage and computer processing power have dramatically changed the game in recent years. +人工智能的进步已然异常惊人。在过去四十年的一系列 [AI 寒冬和不切实际的希望](https://en.wikipedia.org/wiki/History_of_artificial_intelligence#The_first_AI_winter_1974.E2.80.931980) 之后,近些年数据存储和计算机运算性能上的飞速进步急剧地改变了游戏规则。 -In 2015, Google trained a conversational agent (AI) that could not only convincingly interact with humans as a tech support helpdesk, but also discuss morality, express opinions, and answer general facts-based questions. +在 2015 年,Google 训练了一个对话机器人(AI),不仅能够作为一个称职的技术支持顾问与人类进行交流,还能讨论道德、发表意见并回答一些基于现实的问题。 ![](https://cdn-images-1.medium.com/max/800/1*P1H87bkqILoGBVVT7g0T0A.png) -([Vinyals & Le, 2017](https://arxiv.org/abs/1506.05869)) +([Vinyals 和 Le,2017](https://arxiv.org/abs/1506.05869)) -The same year, DeepMind developed an [agent](https://storage.googleapis.com/deepmind-media/dqn/DQNNaturePaper.pdf) that surpassed human-level performance at 49 Atari games, receiving only the pixels and game score as inputs. Soon after, in 2016, DeepMind obsoleted their own achievement by releasing a new [state-of-the-art gameplay method](https://arxiv.org/pdf/1602.01783.pdf) called A3C. +同年,DeepMind 开发了一个[智能体](https://storage.googleapis.com/deepmind-media/dqn/DQNNaturePaper.pdf),仅接收像素和游戏分数作为输入,并在 49 个 Atari 游戏中超越了人类的表现。不久之后,在 2016 年,DeepMind 发布了一种全新的名为 A3C 的适用于人工智能[进行游戏的方法](https://arxiv.org/pdf/1602.01783.pdf),从而超越了先前的成就。 -Meanwhile, [AlphaGo](https://deepmind.com/research/publications/mastering-game-go-deep-neural-networks-tree-search/) defeated one of the best human players at Go — an extraordinary achievement in a game dominated by humans for two decades after machines first conquered chess. Many masters could not fathom how it would be possible for a machine to grasp the full nuance and complexity of this ancient Chinese war strategy game, with its 10¹⁷⁰ possible board positions (there are only [10⁸⁰atoms in the universe](http://www.slate.com/articles/technology/technology/2016/03/google_s_alphago_defeated_go_champion_lee_sedol_ken_jennings_explains_what.html)). +同一时期,[AlphaGo](https://deepmind.com/research/publications/mastering-game-go-deep-neural-networks-tree-search/) 战胜了一位顶尖的围棋高手 —— 距机器首次在国际象棋上战胜人类已经过去了二十年,围棋这个领域一直被人类统治着,这可谓是一次惊人的胜利。许多高手无法领会机器怎么可能了解这个古老的中国战争艺术游戏的细节和复杂度,毕竟它有着 10¹⁷⁰ 种可能的对局([宇宙中只有 10⁸⁰ 个原子](http://www.slate.com/articles/technology/technology/2016/03/google_s_alphago_defeated_go_champion_lee_sedol_ken_jennings_explains_what.html))。 ![](https://cdn-images-1.medium.com/max/800/1*2pYq0Qc3oMDYoVoEA9-6cg.png) -Professional Go player Lee Sedol reviewing his match with AlphaGo after defeat. Photo via [The Atlantic](https://www.theatlantic.com/technology/archive/2016/03/the-invisible-opponent/475611/). +职业围棋选手李世石在与 AlphaGo 对战落败后复盘。[The Atlantic](https://www.theatlantic.com/technology/archive/2016/03/the-invisible-opponent/475611/) 摄。 -In March 2017, OpenAI created agents that [invented their own language](https://blog.openai.com/learning-to-communicate/) to cooperate and more effectively achieve their goal. Soon after, Facebook reportedly successfully training agents to [negotiate](https://code.facebook.com/posts/1686672014972296/deal-or-no-deal-training-ai-bots-to-negotiate/) and even [lie](https://www.theregister.co.uk/2017/06/15/facebook_to_teach_chatbots_negotiation/). +在 2017 年 3 月,OpenAI 创造了能够[发明自己的语言](https://blog.openai.com/learning-to-communicate/)来合作并更加有效地达成目标的机器人。不久后,Facebook 宣布正在训练能够[谈判](https://code.facebook.com/posts/1686672014972296/deal-or-no-deal-training-ai-bots-to-negotiate/)甚至[撒谎](https://www.theregister.co.uk/2017/06/15/facebook_to_teach_chatbots_negotiation/)的机器人。 -Just a few days ago (as of this writing), on August 11, 2017, OpenAI reached yet another incredible milestone by defeating the world’s top professionals in 1v1 matches of the online multiplayer game Dota 2. +就在(本文完成的)几天前,在 2017 年 8 月 11 日,OpenAI 在在线多人对战游戏 Dota 2 中,1v1 战胜了世界顶尖的职业选手,完成了另一个令人难以置信的里程碑。 ![](https://cdn-images-1.medium.com/max/800/1*eWnzwOQX5QgkQ4FRU_I6sg.png) -See the full match at The International 2017, with Dendi (human) vs. OpenAI (bot), on [YouTube](https://www.youtube.com/watch?v=wiOopO9jTZw). +在 [YouTube](https://www.youtube.com/watch?v=wiOopO9jTZw) 上观看这场国际邀请赛 2017(Ti 7)中 Dendi(人类)对阵 OpenAI(机器人)的完整比赛。 -Much of our day-to-day technology is powered by artificial intelligence. Point your camera at the menu during your next trip to Taiwan and the restaurant’s selections will magically appear in English via the Google Translate app. +许多生活中常见的技术都离不开人工智能。在你下一次前往台湾的旅程中,使用 Google Translate 应用,将相机对准菜单扫一扫,对应的菜单项会神奇地变成英文。 ![](https://cdn-images-1.medium.com/max/800/1*x8IgnfzPL7iLZHa9Uhy50g.png) -Google Translate overlaying English translations on a drink menu in real time using convolutional neural networks. +Google Translate 通过卷积神经网络,实时地将英文翻译覆盖到饮品菜单上面。 -Today AI is used to design [evidence-based treatment plans](https://www.ibm.com/watson/health/oncology-and-genomics/oncology/) for cancer patients, instantly analyze results from medical tests to [escalate to the appropriate specialist](https://deepmind.com/applied/deepmind-health/) immediately, and conduct [scientific research](http://benevolent.ai/) for drug discovery. +如今 AI 被用来为癌症患者制定[基于病情的治疗计划](https://www.ibm.com/watson/health/oncology-and-genomics/oncology/)、从药物测试中即时分析结果以便快速[分配合适的专家](https://deepmind.com/applied/deepmind-health/),和开展发现药物的[科学研究](http://benevolent.ai/)。 ![](https://cdn-images-1.medium.com/max/800/1*GEo3QHtN3gcWt0b2k08q7w.png) -A bold proclamation by London-based BenevolentAI (screenshot from [About Us](http://benevolent.ai/about-us/) page, August 2017). +位于伦敦的 BenevolentAI 的大胆宣言(截图自[关于我们](http://benevolent.ai/about-us/)页面,2017 年 8 月)。 -In everyday life, it’s increasingly commonplace to discover machines in roles traditionally occupied by humans. Really, don’t be surprised if a little housekeeping delivery bot shows up instead of a human next time you call the hotel desk to send up some toothpaste. +在日常生活中,机器取代传统意义上人类扮演的角色变得越来越普遍。真的,如果下次你打电话给酒店前台让他们送一些牙膏,出现在你面前的是一个小小的家务运输机器人,而不是一个真人时,请不要惊讶。 ![](https://i.loli.net/2018/05/16/5afb8c9f2b861.png) -In this series, we’ll explore the core machine learning concepts behind these technologies. By the end, you should be able to describe how they work at a conceptual level and be equipped with the tools to start building similar applications yourself. +在本系列中,我们将探讨这些技术背后的核心机器学习概念。在完成整个系列之后,你应该不仅能从概念上描述它们运作的原理,并且可以熟练运用工具来构建类似的你自己的应用程序。 -### The semantic tree: artificial intelligence and machine learning +### 语义树:人工智能和机器学习 -> One bit of advice: it is important to view knowledge as sort of a **semantic tree** — make sure you understand the fundamental principles, ie the trunk and big branches, before you get into the leaves/details or there is nothing for them to hang on to. — Elon Musk, [Reddit AMA](https://www.reddit.com/r/IAmA/comments/2rgsan/i_am_elon_musk_ceocto_of_a_rocket_company_ama/cnfre0a/) +> 一点小建议:将知识看成一种**语义树** — 确保你理解了基本原理(主干和分支),然后再去看树叶/细节,否则它们会无处可栖。 — Elon Musk,[Reddit 有问必答](https://www.reddit.com/r/IAmA/comments/2rgsan/i_am_elon_musk_ceocto_of_a_rocket_company_ama/cnfre0a/) ![](https://cdn-images-1.medium.com/max/800/1*QJG2nMIqWHmLp2j4c0GVuQ.png) -Machine learning is one of many subfields of artificial intelligence, concerning the ways that computers learn from experience to improve their ability to think, plan, decide, and act. +机器学习是人工智能的众多子领域之一,关注如何让计算机学习经验和提升思考、计划、决定和行动的能力。 -**Artificial intelligence is the study of agents that perceive the world around them, form plans, and make decisions to achieve their goals.** Its foundations include mathematics, logic, philosophy, probability, linguistics, neuroscience, and decision theory. Many fields fall under the umbrella of AI, such as computer vision, robotics, machine learning, and natural language processing. +**人工智能是对智能体的研究,他们感知媒介周围的世界,形成计划并做出决定以实现其目标。** 它的基础包括数学、逻辑学、哲学、概率学、语言学、神经科学和决策论。许多领域属于人工智能的范畴,例如计算机视觉、机器人科学、机器学习和自然语言处理。 -**Machine learning is a subfield of artificial intelligence**. Its goal is to enable computers to learn on their own. A machine’s learning algorithm enables it to identify patterns in observed data, build models that explain the world, and predict things without having explicit pre-programmed rules and models. +**机器学习是人工智能的子领域。** 它的目标是让计算机自行学习。一个机器学习算法使它能够识别观测数据中的模式,构建能够解释世界的模型,并在没有确切的预编程规则和模型的情况下预测事物的发展。 -``` -**The AI effect: what actually qualifies as “artificial intelligence”?** +**人工智能效应:什么才是“人工智能”?** -The exact standard for technology that qualifies as “AI” is a bit fuzzy, and interpretations change over time. The AI label tends to describe machines doing tasks traditionally in the domain of humans. Interestingly, once computers figure out how to do one of these tasks, humans have a tendency to say it wasn’t _really_ intelligence. This is known as the [**AI effect**](https://en.wikipedia.org/wiki/AI_effect). +“人工智能”的技术标准有点模糊,并且随着时间推移而不断改变。AI 这个标签通常用来形容能在传统领域取代人类的机器。有趣的是,一旦计算机知道如何完成这些任务,人们通常会说它不是**真正的**智能。这被称为 [**AI 效应**](https://en.wikipedia.org/wiki/AI_effect)。 -For example, when IBM’s Deep Blue defeated world chess champion [Garry Kasparov](https://medium.com/@GarryKasparov) in 1997, people complained that it was using "brute force" methods and it wasn’t “real” intelligence at all. As Pamela McCorduck wrote, _“It’s part of the history of the field of artificial intelligence that every time somebody figured out how to make a computer do something — play good checkers, solve simple but relatively informal problems — there was chorus of critics to say, ‘that’s not thinking’”_([McCorduck, 2004](http://www.pamelamc.com/html/machines_who_think.html)). +例如,IBM 的深蓝在 1997 年击败了世界国际象棋冠军 [Garry Kasparov](https://medium.com/@GarryKasparov) 时,人们抱怨说它使用的是『蛮力』的方法,根本不是『真正』的智力。正如 Pamela McCorduck 所写的,**『人工智能领域的历史的一部分,就是每当有人想出如何让计算机做某事时 —— 成为一个出色的棋手,解决简单但相对没那么正式的问题 —— 就有一群评论家跳出来说,「那不叫思考!」』** ([McCorduck,2004](http://www.pamelamc.com/html/machines_who_think.html))。 -Perhaps there is a certain _je ne sais quoi_ inherent to what people will reliably accept as “artificial intelligence”: +可能人类对于毫无保留地接收所谓的『人工智能』有种**难以言述**的抗拒吧: -"AI is whatever hasn't been done yet." - Douglas Hofstadter +『人工智能永远不可能实现。』 —— 侯世达 -So does a calculator count as AI? Maybe by some interpretation. What about a self-driving car? Today, yes. In the future, perhaps not. Your cool new chatbot startup that automates a flow chart? Sure… why not. -``` +那么计算器能算 AI 吗?在某些解释中也许能算。那一辆自动驾驶汽车呢?在今天是的,而在未来或许算不上。新型的能够自动完成流程图的很酷的聊天机器人项目呢?当然……为什么不呢。 -### Strong AI will change our world forever; to understand how, studying machine learning is a good place to start +### 强大的人工智能将永远改变我们的世界;想了解这个过程,学习机器学习是一个很好的入口。 -The technologies discussed above are examples of **artificial narrow intelligence (ANI)**, which can effectively perform a narrowly defined task. +上面讨论的技术是 **狭义人工智能(ANI)** 的例子,它可以有效地执行一个狭义上的任务。 -Meanwhile, we’re continuing to make foundational advances towards human-level **artificial general intelligence (AGI),** also known as [**strong AI**](https://en.wikipedia.org/wiki/Artificial_general_intelligence). The definition of an AGI is an artificial intelligence that can successfully perform _any intellectual task that a human being can_, including learning, planning and decision-making under uncertainty, communicating in natural language, making jokes, manipulating people, trading stocks, or… reprogramming itself. +同时,我们还在继续向制造类人级别的**广义人工智能(AGI)**,也被称为[**强人工智能**](https://en.wikipedia.org/wiki/Artificial_general_intelligence)努力。AGI 的定义是一种人工智能,它能成功地完成**人类所能从事的任何智力活动**,包括学习、计划和不确定情况下的决策、用自然语言交流、开玩笑、操纵人、买卖股票或……对自身进行重编程。 -And this last one is a big deal. Once we create an AI that can improve itself, it will unlock a cycle of recursive self-improvement that could lead to an **intelligence explosion** over some unknown time period, ranging from many decades to a single day. +而这最后一项是个大问题。一旦我们创建了一个可以改进自身的 AI,它将开启一个自我完善的循环,这可能导致在某一时期发生**智力爆炸**,从几十年到一天都有可能。 -> Let an ultraintelligent machine be defined as a machine that can far surpass all the intellectual activities of any man however clever. Since the design of machines is one of these intellectual activities, an ultraintelligent machine could design even better machines; there would then unquestionably be an ‘intelligence explosion,’ and the intelligence of man would be left far behind. Thus the first ultraintelligent machine is the last invention that man need ever make, provided that the machine is docile enough to tell us how to keep it under control. — I.J. Good, 1965 +> 定义超级智能机器为一台机器,它可以在智力活动中远超任何聪明的人。因为设计机器是这些智力活动中的一种,超级智能机器可以设计出更好的机器;毫无疑问,这将是一个『智力爆炸』,而人类的智力将远远落在后面。因此,第一台超级智能机器是人类需要进行的最后一项发明,只要机器足够温顺地告诉我们如何控制它。 —— I.J. Good,1965 -You may have heard this point referred to as the **singularity**. The term is borrowed from the gravitational singularity that occurs at the center of a black hole, an infinitely dense one-dimensional point where the laws of physics as we understand them start to break down. +你可能听说过这一点被称为**奇点**。这个术语源自黑洞中心的引力奇点,黑洞是一个无限密度的一维点,在那里,我们了解的物理定律开始不复存在。 ![](https://cdn-images-1.medium.com/max/800/1*rR4Hp7-pfgGBDqyPdcnh8g.png) -We have zero visibility into what happens beyond the event horizon of a black hole because no light can escape. Similarly, **after we unlock AI’s ability to recursively improve itself, it’s impossible to predict what will happen, just as** **mice who intentionally designed a human might have trouble predicting what the human would do to their world.** Would it keep helping them get more cheese, as they originally intended? (Image via [WIRED](http://www.wired.co.uk/article/what-black-holes-explained)) +我们对黑洞边界之内的事情一无所知,因为没有光可以从黑洞的捕捉中逃逸。同样地,**当我们解锁了 AI 循环改进自身的能力之后,也没有人能够预测将会发生什么,就像创造出一个人类的老鼠可能无法预测人类会对他们的世界做什么一样。** 他会继续帮它们获取更多奶酪,就像它们预期的那样吗?(图片来自 [WIRED](http://www.wired.co.uk/article/what-black-holes-explained)) -A recent report by the Future of Humanity Institute surveyed a panel of AI researchers on timelines forAGI, and found that **“researchers believe there is a 50% chance of AI outperforming humans in all tasks in 45 years”** ([Grace et al, 2017](https://arxiv.org/pdf/1705.08807.pdf))**.** We’ve personally spoken with a number of sane and reasonable AI practitioners who predict much longer timelines (the upper limit being “never”), and others whose timelines are alarmingly short — as little as a few years. +人类未来研究学院最近发布了一份报告,对人工智能领域的研究者进行了 AGI 的时限调查,发现『**研究人员认为,人工智能有 50% 的几率在 45 年内在任何领域中胜过人类**』([Grace 等人,2017](https://arxiv.org/pdf/1705.08807.pdf))。我们曾与一些理智的人工智能实践者私下交谈过,他们预测的时限更长(上限是『永远』),而其他人给出的时限惊人地短 —— 仅仅只有几年。 ![](https://cdn-images-1.medium.com/max/800/0*2TpuuqUKnhdnr5eK.) -Image from Kurzweil’s The Singularity Is Near, published in 2005. Now, in 2017, only a couple of these posters could justifiably remain on the wall. +来自 Kurzweil 的《奇点临近》,发表于 2005。现在,在 2017,只有几张海报能够名正言顺地留在墙上了。 -The advent of greater-than-human-level **artificial superintelligence (ASI)** could be one of the best or worst things to happen to our species.It carries with it the immense challenge of specifying what AIs will _want_ in a way that is friendly to humans. +比人类级别更高的 **超级人工智能(ASI)** 的出现对人类来说可能是最好或最坏的事情之一,它带来了一个巨大的挑战,即用有利于人类的方式确定 AI **想要**什么。 -While it’s impossible to say what the future holds, one thing is certain: **2017 is a good time to start understanding how machines think.** To go beyond the abstractions of a philosopher in an armchair and intelligently shape our roadmaps and policies with respect to AI, we must engage with the details of how machines see the world — what they “want”, their potential biases and failure modes, their temperamental quirks — just as we study psychology and neuroscience to understand how humans learn, decide, act, and feel. +虽然说不好未来会发生什么,但有一点是肯定的:**2017 年是开始理解机器如何思考的好时机。** 不仅仅是像坐在扶手椅上的哲学家,带着对人工智能的尊重睿智地制定我们的路线图和政策这样抽象的理解,我们必须接触机器如何看待世界的细节 —— 他们“想要”什么,他们潜藏的偏见和失效模式,他们的性格怪癖 —— 就像我们研究心理学和神经科学,以了解人类如何学习、决定、行动和感觉。 -``` -There are complex, high-stakes questions about AI that will require our careful attention in the coming years. +关于人工智能存在着复杂的、高风险的问题,这些问题需要我们在未来几年的认真关注。 -How can we combat AI’s propensity to [further entrench systemic biases](https://www.google.com/intl/en/about/gender-balance-diversity-important-to-machine-learning/) evident in existing data sets? What should we make of fundamental [disagreements among the world’s most powerful technologists](http://fortune.com/2017/07/26/mark-zuckerberg-argues-against-elon-musks-view-of-artificial-intelligence-again/) about the potential risks and benefits of artificial intelligence? What will happen to humans' sense of purpose in a world without work? -``` +我们该如何抑制人工智能[进一步控制现有的数据集中明显的系统偏差](https://www.google.com/intl/en/about/gender-balance-diversity-important-to-machine-learning/)的倾向?我们应该如何看待世界上最好的技术专家之间关于人工智能潜在的风险和收益的[分歧](http://fortune.com/2017/07/26/mark-zuckerberg-argues-against-elon-musks-view-of-artificial-intelligence-again/)?在一个没有工作的世界里,人类的追求会发生什么变化? -Machine learning is at the core of our journey towards artificial general intelligence, and in the meantime, it will change every industry and have a massive impact on our day-to-day lives. That’s why we believe it’s worth understanding machine learning, at least at a conceptual level — and we designed this series to be the best place to start. +机器学习是我们实现广义人工智能的核心,同时,它将改变每一个行业,并对我们的日常生活产生巨大的影响。这就是为什么我们认为机器学习值得了解,至少在概念层面上是这样的 —— 因此我们推出了这个系列,作为最佳的入门读物。 -### How to read this series +### 怎样阅读这个系列 -You don’t necessarily need to read the series cover-to-cover to get value out of it. Here are three suggestions on how to approach it, depending on your interests and how much time you have: +你可以不必按部就班地阅读这个系列。根据你自己的兴趣和空余时间,有三种方法推荐给你: -1. **T-shaped approach.** Read from beginning to end. Summarize each section in your own words as you go (see: [Feynman technique](https://mattyford.com/blog/2014/1/23/the-feynman-technique-model)); this encourages active reading & stronger retention. Go deeper into areas that are most relevant to your interests or work. We’ll include resources for further exploration at the end of each section. -2. **Focused approach.** Jump straight to the sections you’re most curious about and focus your mental energy there. -3. [**80/20 approach**](https://www.thebalance.com/pareto-s-principle-the-80-20-rule-2275148)**.** Skim everything in one go, make a few notes on interesting high-level concepts, and call it a night. 😉 +1. **T 形阅读法。** 从头读到尾。用你自己的语言概括一下每一个章节的内容(见:[Feynman technique](https://mattyford.com/blog/2014/1/23/the-feynman-technique-model));这样能够提升阅读的积极性并加深记忆。然后在你最感兴趣或与工作关联最为紧密的地方深入钻研。我们将会在每一章的结尾介绍一些拓展资源。 +2. **专注阅读法。** 跳到你最感兴趣的地方并把你的精力花在那儿。 +3. **[80/20 法](https://www.thebalance.com/pareto-s-principle-the-80-20-rule-2275148)。** 先通读全文,标记一些有趣的高级概念,然后花一晚时间好好钻研。😉 -### About the authors +### 关于作者 ![](https://cdn-images-1.medium.com/max/800/1*UWNsFVQBaDW5dq1HnW9k4w.png) -“Ok, we have to be done with gradient descent by the time we finish this ale.” @ [The Boozy Cow](https://medium.com/@TheBoozyCow) in Edinburgh +『读完这篇短文,我们就能理解什么是梯度下降了。』 来自爱丁堡的@ [The Boozy Cow](https://medium.com/@TheBoozyCow) -[Vishal](https://www.linkedin.com/in/vishalmaini/) most recently led growth at [Upstart](https://www.upstart.com/about#future-of-credit-2), a lending platform that utilizes machine learning to price credit, automate the borrowing process, and acquire users. He spends his time thinking about startups, applied cognitive science, moral philosophy, and the ethics of artificial intelligence. +[Vishal](https://www.linkedin.com/in/vishalmaini/) 最近创办了 [Upstart](https://www.upstart.com/about#future-of-credit-2),一个利用机器学习来定价、自动化借贷过程并获取用户的借贷平台。他热衷于应用认知科学、道德哲学和人工智能伦理学来创业。 -[Samer](https://www.linkedin.com/in/samer-sabri-8995a717/) is a Master’s student in Computer Science and Engineering at UCSD and co-founder of [Conigo Labs](http://www.conigolabs.com/). Prior to grad school, he founded TableScribe, a business intelligence tool for SMBs, and spent two years advising Fortune 100 companies at McKinsey. Samer previously studied Computer Science and Ethics, Politics, and Economics at Yale. +[Samer](https://www.linkedin.com/in/samer-sabri-8995a717/) 是 UCSD 一位正在攻读计算机科学与工程的硕士生,并且是 [Conigo Labs](http://www.conigolabs.com/) 的创始人之一。在毕业之前,他创建了 TableScribe,一个面向中小企业的商业智能工具,并在麦肯锡公司待了两年,为财富 100 强公司提供咨询服务。Samer 之前在耶鲁学习了计算机科学、伦理学、政治学和经济学。 -Most of this series was written during a 10-day trip to the United Kingdom in a frantic blur of trains, planes, cafes, pubs and wherever else we could find a dry place to sit. Our aim was to solidify our own understanding of artificial intelligence, machine learning, and how the methods therein fit together — and hopefully create something worth sharing in the process. +这个系列的大部分内容都是在为期 10 天的英国之行中写下的,经历了火车、飞机、咖啡馆、酒吧以及种种浮光掠影。我们的目标是巩固我们对人工智能、机器学习、以及其中的方法如何结合在一起的理解 —— 并在这个过程中创造出值得分享的东西。 -And now, without further ado, let’s dive into machine learning with [**Part 2.1: Supervised Learning**](https://medium.com/@v_maini/supervised-learning-740383a2feab)! +现在,不要迟疑,让我们进入[**章节 2.1:监督学习**](https://medium.com/@v_maini/supervised-learning-740383a2feab),开始探索机器学习的世界吧! * * * -More from** Machine Learning for Humans** 🤖👶 +更多**给人类的机器学习指南**🤖👶系列: -* **Part 1: Why Machine Learning Matters ✅** -* [Part 2.1: Supervised Learning](https://medium.com/@v_maini/supervised-learning-740383a2feab) -* [Part 2.2: Supervised Learning II](https://medium.com/@v_maini/supervised-learning-2-5c1c23f3560d) -* [Part 2.3: Supervised Learning III](https://medium.com/@v_maini/supervised-learning-3-b1551b9c4930) -* [Part 3: Unsupervised Learning](https://medium.com/@v_maini/unsupervised-learning-f45587588294) -* [Part 4: Neural Networks & Deep Learning](https://medium.com/@v_maini/neural-networks-deep-learning-cdad8aeae49b) -* [Part 5: Reinforcement Learning](https://medium.com/@v_maini/reinforcement-learning-6eacf258b265) -* [Appendix: The Best Machine Learning Resources](https://medium.com/@v_maini/how-to-learn-machine-learning-24d53bb64aa1) +* **章节 1:论机器学习的重要性 ✅** +* [章节 2.1:监督学习](https://medium.com/@v_maini/supervised-learning-740383a2feab) +* [章节 2.2:监督学习 II](https://medium.com/@v_maini/supervised-learning-2-5c1c23f3560d) +* [章节 2.3:监督学习 III](https://medium.com/@v_maini/supervised-learning-3-b1551b9c4930) +* [章节 3:无监督学习](https://medium.com/@v_maini/unsupervised-learning-f45587588294) +* [章节 4:神经网络和深度学习](https://medium.com/@v_maini/neural-networks-deep-learning-cdad8aeae49b) +* [章节 5:强化学习](https://medium.com/@v_maini/reinforcement-learning-6eacf258b265) +* [附录:机器学习的最佳资源](https://medium.com/@v_maini/how-to-learn-machine-learning-24d53bb64aa1) -#### Contact: [ml4humans@gmail.com](mailto:ml4humans@gmail.com) +#### 联系人:[ml4humans@gmail.com](mailto:ml4humans@gmail.com) -A special thanks to [_Jonathan Eng_](https://www.linkedin.com/in/jonathaneng1/)_,_ [_Edoardo Conti_](https://www.linkedin.com/in/edoardoconti/)_,_ [_Grant Schneider_](https://www.linkedin.com/in/grantwschneider/)_,_ [_Sunny Kumar_](https://www.linkedin.com/in/sunnykumar1/)_,_ [_Stephanie He_](https://www.linkedin.com/in/stephanieyhe/)_,_ [_Tarun Wadhwa_](https://www.linkedin.com/in/tarunw/)_, and_ [_Sachin Maini_](https://www.linkedin.com/in/sachinmaini/) (series editor) for their significant contributions and feedback. +特别感谢 [_Jonathan Eng_](https://www.linkedin.com/in/jonathaneng1/)、[_Edoardo Conti_](https://www.linkedin.com/in/edoardoconti/)、[_Grant Schneider_](https://www.linkedin.com/in/grantwschneider/)、[_Sunny Kumar_](https://www.linkedin.com/in/sunnykumar1/)、[_Stephanie He_](https://www.linkedin.com/in/stephanieyhe/)、[_Tarun Wadhwa_](https://www.linkedin.com/in/tarunw/) 和 [_Sachin Maini_](https://www.linkedin.com/in/sachinmaini/)(系列编辑)的不可或缺的贡献和反馈。 --- diff --git a/TODO1/why-robinhood-uses-airflow.md b/TODO1/why-robinhood-uses-airflow.md new file mode 100644 index 00000000000..14379840450 --- /dev/null +++ b/TODO1/why-robinhood-uses-airflow.md @@ -0,0 +1,97 @@ +> * 原文地址:[Why Robinhood uses Airflow](https://robinhood.engineering/why-robinhood-uses-airflow-aed13a9a90c8) +> * 原文作者:[Vineet Goel](https://robinhood.engineering/@vineetgoel?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/why-robinhood-uses-airflow.md](https://github.com/xitu/gold-miner/blob/master/TODO1/why-robinhood-uses-airflow.md) +> * 译者:[cf020031308](https://github.com/cf020031308) +> * 校对者:[yqian1991](https://github.com/yqian1991) + +# Robinhood 为什么使用 Airflow + +Robinhood 通过定时作业批处理大量任务。这些作业涵盖了从数据分析和指标汇总到经纪业务如股息支付的范围。我们起初使用 cron 来调度这些工作,但随着它们的数量和复杂性的增加,这越来越具有挑战性: + +* **依赖管理**难。使用 cron,我们得用上游作业的最坏预期时长来安排下游作业。随着这些作业的复杂度及其依赖关系的成规模增加,这越来越难。 +* **失败处理**和警报必须由作业管理。在存在依赖关系的情况下,如果作业不能处理重试和上游故障,就只能靠工程师随叫随到。 +* **回溯**难。我们得筛查日志或警报来检查作业在过去某一天的表现。 + +为了满足调度需求,我们决定放弃 cron,将其替换为能解决上述问题的东西。我们调研了一些开源替代品,如 [Pinball](https://github.com/pinterest/pinball), [Azkaban](https://azkaban.github.io/) 以及 [Luigi](https://github.com/spotify/luigi),最终决定用 [Airflow](http://pythonhosted.org/airflow/index.html)。 + +#### Pinball + +Pinball 由 Pinterest 开发,具有分布式、可水平扩展的工作流管理和调度系统的许多功能。它解决了上面提到的很多问题,但文档很少,社区相对较小。 + +#### Azkaban + +由 LinkedIn 开发的 Azkaban 可能是我们考虑过的替代品中最古老的。它使用属性文件来定义工作流,而大多数新的替代方案使用代码。这使得定义复杂工作流程变得更加困难。 + +#### Luigi + +由 Spotify 开发的 Luigi 拥有一个活跃的社区,可能在我们的调研中最接近 Airflow。它使用 Python 来定义工作流,并带有一个简单的 UI。但是 Luigi 没有调度程序,用户仍然需要依赖 cron 来安排作业。 + +### 你好,Airflow! + +由 Airbnb 开发的 Airflow 拥有一个持续增长的社区,似乎是最适合我们目的的。它是一个可水平扩展的分布式工作流管理系统,允许我们使用 Python 代码指定复杂的工作流。 + +#### 依赖管理 + +Airflow 使用[操作符](https://airflow.incubator.apache.org/concepts.html#operators)作为定义任务的基本抽象单元,并使用 [DAG](https://airflow.incubator.apache.org/concepts.html#dags)(有向无环图)通过一组操作符定义工作流。操作符是可扩展的,这使得自定义工作流变得容易。操作符分为3种类型: + +* **动作**执行某些操作的操作符,例如执行 Python 函数或提交 Spark Job。 +* **转移**在系统之间移动数据的操作符,例如从 Hive 到 Mysql 或从 S3 到 Hive。 +* **传感器**在满足特定条件时触发依赖网中的下游任务,例如在下游使用之前检查 S3 上的某个文件是否可用。传感器是 Airflow 的强大功能,使我们能够创建复杂的工作流程并轻松管理其前提条件。 + +下面是一个示例,说明不同类型的传感器如何用于典型的 ETL([数据提取转换与加载](https://en.wikipedia.org/wiki/Extract,_transform,_load))工作流程。该示例使用传感器操作符等待数据可用,并使用转移操作符将数据移动到所需位置。然后将动作操作符用于转换阶段,然后使用转移操作符加载结果。最后,我们使用传感器操作符来验证结果是否已正确存储。 + +![](https://cdn-images-1.medium.com/max/800/1*CcxrRbffqn45YwGglCyexw.png) + +``` +| 传感器 -> 转移 -> | 动作 | -> 转移 -> 传感器 | +| 提取 | 转换 | 加载 | +``` + +使用不同类型的 Airflow 操作符的 ETL 工作流程 + +#### 故障处理和监控 + +Airflow 允许我们为单个任务配置重试策略配置,并可设置在出现故障、重试以及运行的任务[长于预期](https://airflow.incubator.apache.org/concepts.html#slas)的情况下告警。Airflow 有直观的 UI,带有一些用于监控和管理作业的强大工具。它提供了作业的历史视图和控制作业状态的工具 —— 例如,终止正在运行的作业或手动重新运行作业。 Airflow 的一个独特功能是能够使用作业数据创建图表。这使我们能够构建自定义可视化以紧密监视作业,并在排查作业和调度问题时充当一个很好的调试工具。 + +#### 可扩展 + +Airflow 操作符是使用 Python 类定义的。这使得通过扩展现有操作符来定义自定义、可重用的工作流非常容易。我们在内部构建了一大套自定义操作符,其中一些值得注意的例子是 OpsGenieOperator,DjangoCommandOperator 和 KafkaLagSensor。 + +#### 更智能的 Cron + +Airflow DAG 是使用 Python 代码定义的。这使我们能够定义比 cron 更复杂的调度。例如,我们的一些 DAG 只需在交易日运行。而如果用简陋的 cron,我们得设置在所有的工作日运行,然后在应用程序中处理市场假期的情况。 + +我们还使用 Airflow 传感器在市场收盘后立即开始作业,即使当天只有半天开盘。以下示例通过为需要复杂的调度的工作流自定义操作符,来在给定日期根据市场时间动态更新。 + +![](https://cdn-images-1.medium.com/max/800/1*avVioxXl1jTrnC0rj0oEYA.png) + +在给定日期根据市场时间动态调度的工作流 + +#### 回填 + +我们使用 Airflow 进行指标聚合和批量处理数据。随着需求的不断变化,我们有时需要回头更改我们汇总某些指标或添加新指标的方式。这需要能往过去任意时间段回填数据。 Airflow 提供了一个命令行工具,让我们能使用单个命令跨任意时间段进行回填,也可以从 UI 触发回填。我们使用 Celery(由我们的 [Ask Solem](https://medium.com/@asksol) 制作)往 worker box 中分发这些任务。 Celery 的分发能力使我们能够在运行回填时使用更多 worker box,从而使回填变得快捷方便。 + +#### 常见的陷阱和弱点 + +我们目前使用的是 Airflow 1.7.1.3,它在生产中运行良好,但有自己的[弱点和陷阱](https://cwiki.apache.org/confluence/display/AIRFLOW/Common+Pitfalls)。 + +* **时区问题** —— Airflow 依赖系统时区(而不是 UTC)进行调度。这要求整个 Airflow 设置在同一时区运行。 +* **调度程序**分开运行预定作业和回填作业。这可能会导致奇怪的结果,例如回填不符合 DAG 的 max_active_runs 配置。 +* Airflow 主要用于数据批处理,因而其设计师决定总是**先等待一个间隔后再开始作业**。因此,对一个计划在每天午夜运行的作业,其上下文中传入的执行时间为“2016-12-31 00:00:00”,但实际却在“2017-01-01 00:00:00”才真正运行。这可能会让人感到困惑,尤其是在不定期运行的作业中。 +* **意外的回填** —— 默认情况下,Airflow 会在 DAG 从暂停中恢复时或在添加一个 start_date 为过去时间的新 DAG 时尝试回填错过的任务。虽然这种行为是可预料的,但终究没有办法绕过,如果一个作业不应该回填,这就会导致问题。Airflow 1.8 引入了[最近操作符](https://github.com/apache/incubator-airflow/blob/master/airflow/operators/latest_only_operator.py) 来解决这个问题。 + +* * * + +### 总结 + +Airflow 迅速发展成了我们 Robinhood 基础设施的重要组成部分。使用 Python 代码和可扩展 API 定义 DAG 的能力使 Airflow 成为可配置且功能强大的工具。希望这篇文章对于任何探索调度和工作流管理工具以满足其自身需求的人都很有用。我们很乐意回答任何问题。如果这种东西对你很有意思,考虑下我们的[招聘](https://boards.greenhouse.io/robinhood#.WQqFh1PyvUI)! + +感谢 [Arpan Shah](https://medium.com/@arpanshah29?source=post_page),[Aravind Gottipati](https://medium.com/@aravindg?source=post_page),和 [Jack Randall](https://medium.com/@thejgr?source=post_page)。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/why-we-need-web-3-0.md b/TODO1/why-we-need-web-3-0.md new file mode 100644 index 00000000000..5b1fd057288 --- /dev/null +++ b/TODO1/why-we-need-web-3-0.md @@ -0,0 +1,65 @@ +> * 原文地址:[Why We Need Web 3.0](https://medium.com/s/the-crypto-collection/why-we-need-web-3-0-5da4f2bf95ab) +> * 原文作者:[Gav Would](https://medium.com/@gavofyork) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/why-we-need-web-3-0.md](https://github.com/xitu/gold-miner/blob/master/TODO1/why-we-need-web-3-0.md) +> * 译者: +> * 校对者: + +# Why We Need Web 3.0 + +> Ethereum co-founder Gavin Wood on why today’s internet is broken — and how we can do better next time around + +It was over four years ago that I coined the term “Web 3.0.” Back then, it was clear to me: Ethereum — the platform I cofounded — would allow people to interact in mutually beneficial ways without anyone needing to trust each other. With technologies for message passing and data publication, we hoped to construct a peer-to-peer web that lets you do everything you can now, except there would no servers and no authorities to manage the flow of information. + +These days, with key components still missing or dysfunctional, with scalability still wanting, and many projects suffering from compatibility problems, I don’t always find it easy to see the light at the end of the tunnel, or how we will get there. But the important points are unchanged from before: Centralization is not socially tenable long-term, and government is too clumsy to fix things. + +What precisely is wrong with the web today? In short, it’s a big baby. It has grown old without growing up. While connecting the far corners of the globe with a packet-switching network and hypertext platform is an incredible achievement, the web has become corrupted from its own success. + +> The internet today is broken by design. + +Rewind to 1990s and the internet was a very different place. Google was still a .org domain, open-source software was being described as “cancer” by one of the [fiercest monopolists of all time](https://www.zdnet.com/article/ballmer-i-may-have-called-linux-a-cancer-but-now-i-love-it/), and “information superhighway” and “internet addict” were gaining traction as terms for the newly anointed. People (well, teenagers like me) still ran their own websites and email servers, and “net neutrality” was something fishermen argued about when buying trawlers. The fabric of the internet was yet to become warped by the shape of society. It was still rather barebones and empowering, reflecting its academic and enthusiast roots. + +In the 20 years following, the “World Wide Web” would change the nature of society — this we know. Critically, though, the basic technical architecture of the internet provided no backstop against changes happening in the other direction. Society was bound to imprint itself back on the web. + +Technology often mirrors its past. It acts in line with the previous paradigm, only faster, harder, better, or stronger than before. As the global economy went online, we replicated the same social structures that we had before. Partly, we have the web to thank for the modern divides between rich and poor, between powerful and impotent, and between enlightened and under-informed. + +The internet today is broken by design. We see wealth, power and influence placed in the hands of the greedy, the megalomaniacs, or the plain malicious. Markets, institutions, and trust relationships have been transposed to this new platform, with the density, power and incumbents changed, but with the same old dynamics. + +Take how we pay for things online. On Web 2.0, you are not empowered to make payments per se. In reality, you must contact your financial institution to do it on your behalf. You are not trusted to do something as innocuous as pay your water bill. You are treated like a child appealing to a parent. If you wish to contact your friend online, then likely you will need to appeal to Facebook to relay your message. + +> Technology often mirrors its past… As the global economy went online, we replicated the same social structures that we had before. + +The goliaths that run these services — often critical to our lives and jobs — have no (obvious) evil intent, but nor do they act with benevolence or principle. They make money from our fealty, feeding us our information, and cutting us off when inconvenient. + +Most of us don’t fear government or corporate intrusion on our lives, but there are well-documented case where their interests are not aligned with our own. Look at Wikileaks. In 2010, a broadly respected set of journalists that publishes information generally in the public interest was targeted and cut off by major financial institutions like PayPal and Visa without any legal grounds. If you wanted to give a perfectly legal charitable donation to Wikileaks, you effectively couldn’t. + +With so much of the world’s data channelled through so few cables, the inconvenient truth is that unless we put in place open software protocols, our increasingly digital society will continue to be at risk from malicious “authorities” both within society and (as in the case Russian tampering of our elections) from outside. Those who wish to protect the peaceful, liberal post-war world order need to realize: Our present digital architecture will magnify society’s maladies, not limit them. + +Web 3.0 is an inclusive set of protocols to provide building blocks for application makers. These building blocks take the place of traditional web technologies like HTTP, AJAX and MySQL, but present a whole new way of creating applications. These technologies give the user strong and verifiable guarantees about the information they are receiving, what information they are giving away, and what they are paying and what they are receiving in return. By empowering users to act for themselves within low-barrier markets, we can ensure censorship and monopolization have fewer places to hide. Consider Web 3.0 to be an executable _Magna Carta_ — “the foundation of the freedom of the individual against the arbitrary authority of the despot.” + +If society does not adopt the principles of Web 3.0 for its digital platform, it runs the risk of continued corruption and eventual failure, just as medieval feudal systems and Soviet-style communism proved untenable in a world of modern democracies. + +The adoption of Web 3.0 will be neither fast nor clean. With entrenched interests controlling much of our digital lifestyles, and interests often aligned between lawmakers, government and technology monopolists (consider how the NSA’s Prism program enlisted the help of Facebook and Google), some jurisdictions may even attempt to make components of the new web illegal. Already Russia has outlawed bitcoin and the U.K. has expressed a (ridiculous) desire to outlaw strong cryptography. + +> Web 3.0 is an inclusive set of protocols to provide building blocks for application makers. These building blocks take the place of traditional web technologies… but present a whole new way of creating applications. + +If society does not adopt the principles of Web 3.0 for its digital platform, it runs the risk of continued corruption and eventual failure, just as medieval feudal systems and Soviet-style communism proved untenable in a world of modern democracies. Aspects of this new system, including bitcoin or the InterPlanetary File System, will gain traction first, probably in niche areas, much as Linux found traction “under the radar” in server backrooms. As technology matures, and traditional firms inevitably slow their innovation and treat their products as cash-cows (see Microsoft), the advantages of Web 3.0 will grow. It will be no more possible to outlaw Web 3.0 than it was for cities and countries to ban Uber, Airbnb, Grindr, and Wikipedia. + +From a user’s point of view, Web 3.0 will look barely different from Web 2.0, at least initially. We’ll see the same display technologies: HTML5, CSS, and so on. On the back-end, technologies like Polkadot — Parity’s inter-chain blockchain protocol — will connect different technological threads into a single economy and “movement.” + +We’ll use web browsers, but they might be called “wallets” or “key stores.” Browsers (and components like hardware wallets) will represent a person’s assets and identity online, allowing us to pay for something, or prove who we are, without needing to appeal to a bank or identity service. There will still be room for trusted parties, insurance outfits, backup-services and so forth. But their tasks will be commoditized and their activity verifiable. As these service providers are forced to compete in a global, open and transparent market, web users will be relieved of price-gouging and rent-seeking. + +Web 3.0 will engender a new global digital economy, creating new business models and markets to go with them, busting platform monopolies like Google and Facebook, and giving rise to vast levels of bottom-up innovation. Cheap government attacks on our privacy and liberty like widespread data trawling, censorship and propaganda, will become more difficult. + +To be sure, we can’t predict the first successful use-cases of this new platform and when they might appear. As with the development of the internet before it, the timeline could be measured in decades rather than months. But when Web 3.0 emerges, it will bring a whole new meaning to the phrase “the Digital Age.” + +* * * + +_From groundbreakers to lawbreakers, BREAKER is a new online magazine reporting on blockchain’s most intriguing innovators. For more stories, visit_ [_breakermag.com_](https://breakermag.com/?utm_source=Medium&utm_medium=referral&utm_campaign=relaunch) _or follow BREAKER_ [_@breakermag.com_](https://twitter.com/breakermag). + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/why-you-should-give-flutter-some-of-your-attention.md b/TODO1/why-you-should-give-flutter-some-of-your-attention.md new file mode 100644 index 00000000000..1fcca97c827 --- /dev/null +++ b/TODO1/why-you-should-give-flutter-some-of-your-attention.md @@ -0,0 +1,131 @@ +> * 原文地址:[Why You Should Give Flutter Some of Your Attention](https://medium.com/the-andela-way/why-you-should-give-flutter-some-of-your-attention-22dd7e5cae42) +> * 原文作者:[Bruce Bigirwenkya](https://medium.com/@bruce.bigirwenkya?source=post_header_lockup) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/why-you-should-give-flutter-some-of-your-attention.md](https://github.com/xitu/gold-miner/blob/master/TODO1/why-you-should-give-flutter-some-of-your-attention.md) +> * 译者:[DateBro](https://github.com/DateBro) +> * 校对者:[Swants](https://github.com/swants) + +# 为什么你需要关注一下 Flutter + +![](https://cdn-images-1.medium.com/max/1000/1*ksS2oqmcv5ol9nCaMkraIw.jpeg) + +跨平台移动应用开发新方式 + +### 什么是 Flutter + +Flutter 是由 Google 开发,完全开源的一款帮助开发者短时间内在 iOS 和 Android 上开发高质量原生界面的移动应用 SDK。 + +Flutter 刚刚发布了 [发布预览 1](https://medium.com/flutter-io/flutter-release-preview-1-943a9b6ee65a) + +### 为什么选择 Flutter 呢? + +了解为什么选择 Flutter 需要明白其用途和各个平台中的开发历史。 + +#### 谁是 Flutter 的目标用户 + +* 希望打造高性能用户界面的开发者。 +* 不想学习各种原生平台语言但希望进入移动应用程序开发层的 Web 应用开发者。 +* 希望通过一次开发吸引更多用户的公司。 +* 希望应用程序设计与他们愿景一致的设计师。 + +#### 历史 + +原生应用程序开发与跨平台开发始终有明显区别,有着各种优点和缺点。 + +跨平台应用的确非常有吸引力。尽管如此,它仍然在不断变化,最终填补了原生应用程序开发空间的空白。一般来说,移动开发也比较年轻(不到十年)。 + +[这篇文章](https://hackernoon.com/whats-revolutionary-about-flutter-946915b09514) 详细介绍了移动开发中使用的视图技术的历史。 + +第一个跨平台框架使用了 Web 技术并显示 Web 视图 + +在 Apple 发布 iOS SDK 之前,他们鼓励第三方开发人员为 iPhone 搭建 web 应用,因此用 Web 技术搭建跨平台应用是一个明显的阶段。 + +[响应式编程](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) 是一种强调异步数据流与事件数据流的编程范式。在动画和其他渲染要求方面,它已经越来越多地被用于用户界面开发。 + +像 ReactJS 这样的响应式 Web 框架使用响应式编程技术来简化 Web 视图的构建。 + +根据编译机制和视图类型进行技术分类 + +![](https://cdn-images-1.medium.com/max/800/1*pxh6w9ALI-bAYHg33zZQ2g.png) + +#### “桥” + +传统上,构建跨平台应用会因为在不同领域运行而面临性能损失。应用程序是用 JavaScript 开发但界面是完全原生的。不同领域的变量不能互相访问。不同领域的变量和数据交互都必须通过“桥”来完成。 + +例如,如果你在 Chrome 中调试 React Native 应用,这就意味着程序将在两个不同的领域运行(桌面和移动)。而这些领域通过 WebSocket 连接起来。 + +React Native 的优化尝试在运行时通过“桥”将数据交换保持在最低值。最终,每个环境下的运行都很流畅但跨桥的交换的延迟会增大。 + +Flutter 依赖 Dart 语言的静态编译解决这个问题。这意味着在运行时不再需要“桥”,因为程序会被编译成原生代码。 + +#### 组件 + +窗口组件是控制和影响应用程序的视图和界面的元素。Flutter 中万物皆组件。这使它更加自包含,可重用和可扩展。 + +#### 布局 + +传统的布局依靠的是不同 CSS 文件中定义的多项样式规则。这些规则适用于标记,因此能够为所应用的规则创造多种可能的交互和矛盾。CSS3 有大约 375 项规则。不考虑规则中可能存在的矛盾,布局的可能性通常为 N 阶平方。 + +Flutter 重新设计的布局更高效也更直观。布局信息由组件在建模时单独指定。这不仅使查看代码的开发者更容易理解正在发生的事,而且还意味着窗口组件没有处理可能不适用于它的规则的开销。 + +Flutter 团队提供了很多他们觉得用起来不错的布局组件。Flutter 也有很多围绕布局的优化,像为了只在有必要生成大体积组件时的缓存。 + +#### Dart + +Flutter 团队使用 Dart 有以下几个原因: + +* **静态编译** + Dart 是静态编译的。Dart VM 可以为你正在开发的平台构建本机 ARM 代码。这意味着与使用即时编译器,在程序执行时编译的应用程序相比,程序要快得多。 +* **动态编译:** Dart 也可以即时编译。Flutter 利用这种开发能力来缩短开发周期。像热重载这样的功能是可行的,因为应用程序可以轻松编译更新,从而更容易测试和迭代产品。 +* **强类型:** Dart 是一种 [强类型](https://en.wikipedia.org/wiki/Strong_and_weak_typing) 语言。如果你用过 Java 或 C#,有几个原因使得过渡到 Dart 非常理想,其中一个就是是,Dart 看起来比较熟悉,以及它的类型安全,所以你不必牺牲程序的完整性。 +* **服务端的潜力:** Dart 非常适合很多事情,包括在服务器上运行。服务器端的 Dart 越来越受人们关注。考虑到统一代码库的可能性,在 Flutter 中使用它就是一个很好的例子。 + +你可以从 [这里](https://hackernoon.com/why-flutter-uses-dart-dd635a054ebf) 了解更多关于 Flutter 上使用的 Dart 语言。 + +#### Flutter 的结构 + +![](https://cdn-images-1.medium.com/max/800/1*okW6pQoMLLmlAhPnGL95PA.png) + +Flutter 应用的结构 + +#### 在 Fuchsia 上的潜力 + +能够在 Fuchsia 上使用。[Fuchsia](https://fuchsia.googlesource.com/) 是一个新的 [开源](https://fuchsia.googlesource.com/) 操作系统,现在由谷歌开发,这在科技爱好者圈子引起了不少的关注。不像 Android 和其他流行的操作系统,它基于一个叫做“Zircon”的微内核。Flutter 已经和 Armadillo 一起用来测试 [Fuchsia 用户界面](https://9to5google.com/2018/03/02/fuchsia-friday-first-fuchsia-app/) 的开发了。 + +#### 目前的不足之处 + +现在能感觉出的缺点是基于过去的开发者经验。这部分只用于突出 Flutter 项目的重要性和解决方案。证明了 Flutter 团队的发展速度。 + +你可以查看本文,以了解原生移动开发者在测试驾驶 Flutter 时面临的挑战。他们强调的一些问题在我看来非常有争议,比如缺乏 OpenGL 支持,允许 Flutter 使用 Skia,支持 OpenGL 作为其后端之一。 + +由于 Flutter 处于刚发布不久,其他你可能面临的可能的挑战包括: + +* 动画的低级实现。这在过去对于一些需要降低水平以创作想要的动画的人来说就是一种挑战。 +* 仍然缺少数据操作库。Flutter 一开始专注视觉渲染。如果你是一个非常依赖现成的社区模块和组件来实现的人,那么你可能会因为稀缺的数据操作库而艰难前行。然而,我个人觉得这个领域正在稳步成熟,Flutter 提供了很多关于构建应用程序的建议,并为人们构建库以使其产品与最佳约定保持一致铺平了道路。 + +#### Flutter 入门 + +前往 [Flutter.io](http://flutter.io/) 了解怎样入门 Flutter 。你还也查看底部参考资料部分中的内容。 + +总之,Flutter 入门将涉及下载 SDK 并配置使用它的路径。在你使用的编辑器中安装必要的插件是必要的下一步。 + +* * * + +你可能在 Mac OS 上遇到[依赖缺失问题](https://github.com/flutter/flutter/issues/16428) ,这可以通过运行 **pip install six** 来解决。 + +当你尝试运行 flutter upgrade 时,你可能遇到的另一个问题是合并冲突。如果你已经测试了与 SDK 捆绑在一起的 Flutter 示例,就会出现这种情况。在这种情况下,在运行 Flutter 升级命令之前进入 Flutter SDK 文件夹,存储已更改的文件(git add.| git stash)非常有用。 + +#### 一些资源 + +* Rohan Taneja 在这篇[文章](https://medium.freecodecamp.org/learn-flutter-best-resources-18f88346ed0f) 中提供了珍贵资源的一套相当详细的链接。 +* [Fluttery](https://medium.com/fluttery) 是一系列为想入门 Flutter 的人准备的教程,挑战和模式集合。 +* [Flutter studio](https://flutterstudio.app/) 也是为想简化开发过程的人准备的优秀资源。 + +**我很高兴尝试它。如果你也是,请告诉我!** ❤️🚀 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/why-you-should-replace-foreach.md b/TODO1/why-you-should-replace-foreach.md new file mode 100644 index 00000000000..f14a5b2b567 --- /dev/null +++ b/TODO1/why-you-should-replace-foreach.md @@ -0,0 +1,72 @@ +> * 原文地址:[Why you should replace forEach with map and filter in JavaScript](https://gofore.com/en/why-you-should-replace-foreach/) +> * 原文作者:[Roope Hakulinen](https://disqus.com/by/roopehakulinen/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/why-you-should-replace-foreach.md](https://github.com/xitu/gold-miner/blob/master/TODO1/why-you-should-replace-foreach.md) +> * 译者:[zhmhhu](https://github.com/zhmhhu) +> * 校对者:[CoderMing](https://github.com/CoderMing), [diliburong](https://github.com/diliburong) + +# 在 JavaScript 中 为什么你应当使用 map 和 filter 来替代 forEach + +> 当你需要将一个数组或一部分数组复制到一个新数组时,首选 `map` 和 `filter`,而不是 `forEach`。 + +咨询工作对我来说最大的好处之一是我可以看到无数的项目。这些项目有大有小,使用的编程语言和开发人员能力差异很大。尽管我认为有很多模式都应该放弃使用,但 JavaScript 语言中的这种模式尤其要弃用:使用 forEach 创建新数组。该模式实际上非常简单,看起来像这样: + +``` +const kids = []; +people.forEach(person => { + if (person.age < 15) { + kids.push({ id: person.id, name: person.name }); + } +}); +``` + +这段代码的意思是,处理一个包含所有人的数组,以找出每个年龄小于 15 岁的人。然后选择 person 对象中的其中几个字段作为 'kids' 对象,并将其复制到 kids 数组中。 + +虽然这是有效的,但这是非常必要的(参见[编程范例](https://en.wikipedia.org/wiki/Programming_paradigm))编码方式。你可能有所怀疑,这有什么不对?要理解这一点,让我们首先熟悉两个朋友 `map` 和 `filter`。 + +## `map` 和 `filter` + + 在 2015 年,`map` 和 `filter` 作为 ES6 特性集的一部分被引入 JavaScript。它们是数组的方法,允许在 JavaScript 中进行更多函数式编程。像在函数式编程世界中一样,这两种方法都没有改变原始数组。相反,它们都返回一个新数组。它们都接受函数类型的单个参数。然后在原始数组中的每一项上调用此函数以生成结果数组。让我们看看这些方法的作用: + +* `map`:每项调用函数处理后的值存放到返回的新数组中。 +* `filter`:每项调用函数处理后的值决定该项是否应该放在方法返回的新数组中。 + +在同一个团体中他们也有第三个朋友,但较少使用。这位朋友的名字是 [`reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)。 + +以下是可以查看实际操作结果的简单示例: + +``` +const numbers = [1, 2, 3, 4, 5]; +const doubled = numbers.map(number => number * 2); // [2, 4, 6, 8, 10] +const even = numbers.filter(number => number % 2 === 0); // [2, 4] +``` + +既然我们知道 `map` 和 `filter` 会做什么,让我们接下来看一个我希望前面的示例应当如何编写的例子: + +``` +const kids = people + .filter(person => person.age < 15) + .map(person => ({ id: person.id, name: person.name })); +``` + +如果您想知道 `map` 中使用的 lambda 的语法,请参阅此 [Stack Overflow answer](https://stackoverflow.com/a/28770578/1744702) 以获取解释。 + +那么这个实现究竟有什么好处: + +* 关注点分离:过滤和更改数据格式是两个独立的问题,使用单独的方法可以分离关注点。 +* 可测试性:为实现这两个目的,一个简单的、[纯函数](https://en.wikipedia.org/wiki/Pure_function)的方法可以轻松地针对各种行为进行单元测试。值得注意的是,初始实现并不像它依赖于其范围 (`kids` 数组)之外的某些状态那样纯粹。 +* 可读性:由于这些方法具有过滤数据或更改数据格式的明确目的,因此很容易看出正在进行何种操作。特别是因为有那些同类功能的函数,如 `reduce`。 +* 异步编程:`forEach` 和 `async`/`await` 不能很好地协同工作。另一方面,`map` 提供了一个能够结合 promises 和 `async`/`await` 的有效模式。在下一篇博文中有关于此问题的更多信息。 + +另一个值得注意的地方是,当你想引起副作用时(例如更改全局状态),不应当使用 `map`。特别是在不使用或存储 `map` 方法的返回值的情况下。 + +## 结论 + +`map` 和 `filter` 的使用提供了许多好处,例如关注点分离、可测试性、可读性和对异步编程的支持。因此,对我来说这是一个明智的选择。然而,我经常遇到使用 `forEach` 的开发人员。虽然函数式编程可能令人害怕,这些方法有也具有来自该世界的某些特征,对它们并不用害怕。在[响应式编程](https://en.wikipedia.org/wiki/Reactive_programming)中,`map` 和 `filter` 也被大量使用,感谢 [RxJS](http://reactivex.io/rxjs/) 的贡献,响应式编程现在越来越多地在 JavaScript 世界中使用。因此,下次你要写一个 `forEach` 时,首先要考虑其他方法。但要注意,它们可能会永久改变您的编码方式。 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/writing-cleaner-view-code-by-overriding-loadview.md b/TODO1/writing-cleaner-view-code-by-overriding-loadview.md new file mode 100644 index 00000000000..01a380d793b --- /dev/null +++ b/TODO1/writing-cleaner-view-code-by-overriding-loadview.md @@ -0,0 +1,189 @@ +> * 原文地址:[Writing Cleaner View Code in Swift By Overriding loadView()](https://swiftrocks.com/writing-cleaner-view-code-by-overriding-loadview.html) +> * 原文作者:[Bruno Rocha](https://bit.ly/2IY5F4Y) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/writing-cleaner-view-code-by-overriding-loadview.md](https://github.com/xitu/gold-miner/blob/master/TODO1/writing-cleaner-view-code-by-overriding-loadview.md) +> * 译者:[RickeyBoy](https://github.com/RickeyBoy) +> * 校对者:[徐键](https://github.com/foxxnuaa) + +# 重写 loadView() 方法使 Swift 视图代码更加简洁 + +究竟选择使用 Storyboards 还是纯代码书写 view 是非常主观的事情。在对两种方式都进行了尝试之后,我个人支持使用纯代码书写 view 来完成项目,这样能够允许多人编辑相同的类而不产生讨厌的冲突,也更方便进行代码审查。 + +在最开始练习纯代码写 view 的时候,人们普遍遇到的一个问题是最开始不知道将代码放在**哪里**。如果你采用普通 storyboard 的方式,将所有相关代码都放进你的 ViewController 之中,这样很容易会最终产生一个巨大的上帝类: + +``` +final class MyViewController: UIViewController { + private let myButton: UIButton = { + // + }() + + private let myView: UIView = { + // + }() + + // 其他 10 个左右的 view + + override func viewDidLoad() { + super.viewDidLoad() + setupViews() + } + + private func setupViews() { + setupMyButton() + setupMyView() + // 设置其他的 view + } + + private func setupMyButton() { + view.addSubview(myButton) + // 十行约束代码 + } + + private func setupMyView() { + view.addSubview(myView) + // 十行约束代码 + } + + // 所有其他的设置 + + // 所有 ViewModel 的逻辑 + + // 所有 Button 的点击逻辑等东西... +} +``` + +你可以通过把 view 移动到不同的文件并添加引用到原来的 ViewController 之中来改善这样的情况,但是你仍然需要用本不应该在 ViewController 中的内容填满 ViewController,就比如约束代码和其他设置 view 的代码 — 更不用说你现在有两个 view 属性(`myView` 和原生 `view`)在 ViewController 之中,而这没有任何好处。 + +``` +final class MyViewController: UIViewController { + + let myView = MyView() + + override func viewDidLoad() { + super.viewDidLoad() + setupMyView() + } + + private func setupMyView() { + view.addSubview(myView) + // 10 行左右的约束代码 + myView.delegate = self + // 现在我们同时有了 view 和 MyView... + } +} +``` + +臃肿的 ViewController 以及逻辑**过多**的 ViewController 都非常难以管理和维护。在像 MVVM 这样的架构下,ViewController 应该主要作为自身的 View 以及 ViewModel 之间的路由器 -- 设置并且约束 View 并不是它们的职责,ViewController 只应该起到前后传递信息的**路由作用**。 + +在一个大部分代码都是关于自身 View 的视图代码项目中,能够清晰地拆分你的架构中各部分的职责,对于一个便于维护的项目来说非常重要。你要让你真正构建视图部分的代码完全和你的 ViewController 分离 -- 幸运的是有一个简单的方法,就是重写 `UIViewController` 中原生的 `View` 属性。这样做允许你在分离的文件中管理你的多个 View,同时也仍能保证你的 ViewController 不用去设置任何 View。 + +## loadView() + +`loadView()` 是 `UIViewController` 中并不常见的一个方法,但它是 ViewController 的生命周期中非常重要的一部分,因为它承担着最开始加载出 `view` 属性的责任。当使用 Storyboard 的时候,它会加载出 nib 并将其附加给 `view`,但当手动初始化 ViewController 时,这个方法所做的一切就是创建出一个空的 `UIView`。你可以重写这个方法并改变它的行为,并且在 ViewController 的 `view` 上添加任何类型的 view。 + +``` +final class MyViewController: UIViewController { + override func loadView() { + let myView = MyView() + myView.delegate = self + view = myView + } + + override func viewDidLoad() { + super.viewDidLoad() + print(view) // 一个 MyView 的实例 + } +} +``` + +注意 `view` 会自动的约束自己到 ViewController 的边界,所以并不需要为 `myView` 设置外部约束! + +现在,`view` 成为了我自定义的 view(在本例中为 `MyView`)的一个引用。你可以在这个 view 独立的文件内部构建其所有功能,并且 ViewController 对此毫无权限。太棒了! + +为了获取 `MyView` 中的内容,你可以将 `View` 强制转换为你自己的类型: + +``` +var myView: MyView { + return view as! MyView +} +``` + +这样看起来有点奇怪,但这是因为 `view` 将仍然被定义为 `UIView` 类型,而不是你为它定义的类型。 + +为了避免我的 ViewController 中重复出现这样的代码,我喜欢创建一个 `CustomView` 协议,并在其中定义包含关联类型的行为: + +``` +/// HasCustomView 协议为 UIViewController 定义了一个 customView 属性,它是为了去代替普通的 view 属性。 +/// 为了实现这些,你必须在 loadView() 方法时为你的 UIViewController 提供一个自定义的 View。 +public protocol HasCustomView { + associatedtype CustomView: UIView +} + +extension HasCustomView where Self: UIViewController { + /// UIViewController 的自定义 view。 + public var customView: CustomView { + guard let customView = view as? CustomView else { + fatalError("Expected view to be of type \(CustomView.self) but got \(type(of: view)) instead") + } + return customView + } +} +``` + +最终会: + +``` +final class MyViewController: UIViewController, HasCustomView { + typealias CustomView = MyView + + override func loadView() { + let customView = CustomView() + customView.delegate = self + view = customView + } + + override func viewDidLoad() { + super.viewDidLoad() + customView.render() // 一些 MyView 的方法 + } +} +``` + +如果每次都定义这个 `CustomView` 类型别名会让你有点烦,那么你可以进一步在泛型类中定义这些行为: + +``` +class CustomViewController: UIViewController { + var customView: CustomView { + return view as! CustomView // 因为我们正在重写 view,所以永远不会解析失败。 + } + + override func loadView() { + view = CustomView() + } +} + +final class MyViewController: CustomViewController { + override func loadView() { + super.loadView() + customView.delegate = self + } +} +``` + +我个人不太喜欢泛型的方式,因为编译器并不允许泛型类具有的 `@objc` 方法的扩展,这会禁止你在扩展中拥有 `UITableViewDataSource` 之类的协议。但是,除非你需要做一些特殊的事情(比如设置委托),它会允许你跳过重写 `loadView()` 这一步,从而能保持 ViewController 的整洁。 + +## 结论 + +重写 `loadView()` 是一个让你的视图代码项目更加易于理解、易于维护的好方法,并且我已经使用 `HasCustomView` 方法获得了非常良好的效果,特别是在最近几个项目中。编写视图部分的代码也许不是你的选择,但是它带来了很多显而易见的好处。尝试一下吧,看看它是不是更适合你。 + +如果你有更好的定义 view 并且不需要 storyboard 的方法,或者你可能有一些疑问、意见或者反馈,请让我知道。 + +## 参考文献和推荐阅读 + +[苹果官方文档:loadView()](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621454-loadview) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/wwdc-2018-rumors-ios-12-new-macbook-air-ipad-pro-homepod.md b/TODO1/wwdc-2018-rumors-ios-12-new-macbook-air-ipad-pro-homepod.md new file mode 100644 index 00000000000..4d138248ce7 --- /dev/null +++ b/TODO1/wwdc-2018-rumors-ios-12-new-macbook-air-ipad-pro-homepod.md @@ -0,0 +1,113 @@ +> * 原文地址:[WWDC 2018: All the rumors on iOS 12, iPad Pro, new MacBooks and more](https://www.cnet.com/news/wwdc-2018-rumors-ios-12-new-macbook-air-ipad-pro-homepod/) +> * 原文作者:[JOHN FALCONE](https://www.cnet.com/profiles/jpfalcone/), [JUSTIN JAFFE](https://www.cnet.com/profiles/justin+jaffe/) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/wwdc-2018-rumors-ios-12-new-macbook-air-ipad-pro-homepod.md](https://github.com/xitu/gold-miner/blob/master/TODO1/wwdc-2018-rumors-ios-12-new-macbook-air-ipad-pro-homepod.md) +> * 译者:[Dandy Xu](https://github.com/dandyxu) +> * 校对者:[shisaq](https://github.com/shisaq) + +# WWDC 2018:关于 iOS 12、iPad Pro、新 MacBook 及更多产品的所有预测 + +## 当蒂姆库克(Tim Cook)在 6 月 4 日走进圣何塞(San Jose)的发布会现场时将会发生什么事情?(译者注:圣何塞(San Jose)是美国加州的第三大城市) + +我们距离 [苹果公司](https://www.cnet.com/apple/) 的下一个正式发布会只有2周的时间了,我们通常可以在发布会上一窥该公司的未来。 + +苹果公司一年一度的 [世界开发者大会](https://www.cnet.com/wwdc) 提供了关于下一代操作系统和软件应用的线索和方向,它们为该公司的 [iPhone](https://www.cnet.com/products/apple-iphone-x/review/)、[iPad](https://www.cnet.com/products/apple-ipad-2018-9-7-inch/review/)、Macs、手表、[HomePod](https://www.cnet.com/products/apple-homepod/review/) 音响和 [苹果电视](https://www.cnet.com/products/apple-tv-4k/review/) 提供动力。今年的发布会于 6 月 4 日至 8 日,在美国加州圣何塞市的 McEnery Convention Center 举行,同时也给第三方开发者们提供了一个和苹果管理层、工程师以及产品设计人员沟通交流的机会。 + +[WWDC](https://www.cnet.com/wwdc/) 中极有可能揭露将在今年内实现的针对操作系统、应用、服务以及软件框架的更新升级。但是,苹果有时也会用来发布一些新的硬件。在我们进行更深层次的软件预计分析之前,我们先来探讨一下可能会发布哪些新的硬件。 + +## 可能会发布新的 iPad Pro、MacBook 及更多的硬件 + +当苹果公司 CEO 蒂姆库克 (Tim Cook) 在 6 月 4 日站在发布会舞台上时,不要期望能够看到新的高端 iPhone 或者新版本的 Mac Pro。[iPhone X](https://www.cnet.com/products/apple-iphone-x/review/)、[iPhone 8](https://www.cnet.com/products/apple-iphone-8-review/) 和[iPhone 8 Plus](https://www.cnet.com/products/apple-iphone-8-plus/review/) 已经如期在去年 9 月份发布了,同时苹果也已经确认了 [新的Mac Pro将于2019年发布](https://www.cnet.com/news/the-new-mac-pro-is-coming-in-2019/)。同样的,[HomePod](https://www.cnet.com/products/apple-homepod/review/) 音响和 [入门级别的iPad](https://www.cnet.com/products/apple-ipad-2018-9-7-inch/review/) 也分别于2月和3月刚刚全新发布。 + +但这里有 4 个产品类别,极有可能在 6 月份进行发布: + +**MacBooks and iMacs:** 在去年的WWDC中,苹果几乎将整条笔记本和台式机的产品线进行了更新。现在,一年过去了,将全部产品升级到最新的 [第8代Intel处理器](https://www.cnet.com/news/kaby-lake-coffee-lake-and-beyond-what-we-know-about-the-next-intel-chips/),尽管有些枯燥,但确实是最简单的升级做法。但是否需要更大规模的设计改革 - 例如重新思考 [蝴蝶键盘的问题](https://www.cnet.com/news/apple-macbook-keyboard-issue-prompts-lawsuit/) 和在 [MacBook Pro笔记本](https://www.cnet.com/products/apple-macbook-pro-with-touch-bar-15-inch-2017/review/) 上 [仍然存在争议的Touch Bar功能](https://www.cnet.com/news/apple-macbook-pro-touch-problems-commentary/),仍然有待观察。同样的问题也适用于 MacBook Air,一直有预测说该系列将会迎来新的型号。 + +阅读:[2018 MacBook Air:所有关于规格、价格以及发布时间的预测](https://www.cnet.com/news/2018-macbook-air-all-the-rumors-on-specs-price-release-date/) + +![ipad-pro-12-34](https://cnet1.cbsistatic.com/img/pLk32huKrWmXsXUmt-XHiJupsbM=/970x0/2017/07/17/33e09a24-eba2-4e4b-890e-0f6faa1da6e8/ipad-pro-12-34.jpg) + +Sarah Tew/CNET + +**iPad Pros:** 苹果刚刚在 3 月给 [iPad Pro](https://www.cnet.com/products/apple-ipad-pro-10-5-inch-2017/review/) 产品线引入了一个重要的功能,就是让新的 [入门级iPad](https://www.cnet.com/products/apple-ipad-2018-9-7-inch/review/) 兼容手写笔。人们认为这种举措将使价格更高的 iPad Pro 可以向 iPhone X 的风格迈进:去掉 home 键,同时添加 Face ID。 + +阅读:[iPad Pro 2018:所有关于规格、价格以及发布时间的预测](https://www.cnet.com/news/ipad-pro-2018-all-the-rumors-on-specs-price-release-date/) + +**iPhone SE 2:** 如我们之前说过的,iPhone 的大升级通常都在 9 月份。但一直有预测说 [iPhone SE](https://www.cnet.com/products/apple-iphone-se-review/) - 即首次在 2016 年 3 月亮相的入门级 iPhone,或多或少是该升级了。是否采用全屏幕的 iPhone X 设计(看起来不太可能)或者只是基于现有模块的规格升级(更有可能),这些都是未知的。 + +阅读:[iPhone SE 2:预测的规格、价格、发布时间](https://www.cnet.com/news/iphone-se-2-launch-date-specs-price-release-date-rumors-notch/) + +**Apple AirPower:** 苹果公司的全球市场高级副总裁 Phil Schiller 在 2017 年 9 月介绍这个和 iPhone X 搭配使用的,多设备的充电设备时,他说这将在“明年”推出。所以,直到 2019 年的 1 月 1 日,你都不能说它推出“晚”了,有一种假设是苹果公司希望在这个产品发布一周年之前,将它放上货架出售。WWDC 将会是一个绝佳的机会去宣布它的价格和发售日期 - 也许我们还能看到和 AirPower 兼容的 AirPods 收纳盒。 + +阅读:[AirPower: 我们所知道的苹果无线充电板](https://www.cnet.com/news/apple-airpower-wireless-charger-everything-we-know/) + +![airpower-charging](https://cnet3.cbsistatic.com/img/ncNN2P2MqPIxuYdY7jRaGHHUkzM=/970x0/2017/09/12/02937a84-4f0a-4611-8af2-4a5764b95055/screen-shot-2017-09-12-at-2-44-50-pm.png) + +AirPower 早在 2017 年的 9 月份就发布了,但到现在还没有发售。 + +截图来自Alexandra Able/CNET + +**其他我们不用指望在6月4日出现的产品:** 对于其他的苹果硬件,恐怕它们都需要一个长期的过程。不要期望会看到新的苹果手表,苹果电视盒,[第二代的AirPods](https://www.cnet.com/news/apple-airpods-2-wireless-charging-things-we-expect/) 或者 [小型HomePod音响](https://www.cnet.com/news/homepod-slow-sales-homepod-mini-homepod-price-cut/)。也许苹果会花一些时间去安利 [AR Kit](https://www.cnet.com/pictures/best-arkit-apps/) — 它是一个软件工具集,将增强现实带进iOS — 同时 [Mac里的虚拟现实](https://www.cnet.com/news/apple-bringing-vr-external-graphics-and-game-engines-to-mac/),但绝对不用期待任何关于 [苹果AR/VR头戴式设备](https://www.cnet.com/news/apple-is-working-on-an-ar-augmented-reality-vr-virtual-reality-headset-powered-by-a-wireless-wigig-hub/),苹果公司显然在暗地里进行研发 - 即使真的要期待一下,这些都需要直到 2020 年才能知晓。 + +你将看到的,将是针对上文提到的所有硬件而更新的大量软件以及操作系统。以下是可以期待的内容。 + +## iOS 12:质量高于数量 + +在经历了一系列 [持续的](https://www.cnet.com/news/how-to-fix-the-ios-11-1-autocorrect-bug/) [缺陷](https://www.cnet.com/news/apple-updates-operating-systems-to-fix-app-crashing-bug/) [和](https://www.cnet.com/how-to/why-you-should-avoid-your-iphones-calculator/) [小故障](https://www.cnet.com/news/apple-slows-down-older-iphone-battery-issues/) 后,这些问题都导致iOS11口碑不佳 — 包括 [关于故意降低 iPhones 性能的有争议功能](https://www.cnet.com/news/apple-slows-down-older-iphone-battery-issues/) — 苹果 [称将会把质量放在比创新更重要的位置](https://www.bloomberg.com/news/articles/2018-01-30/apple-is-said-to-push-back-some-key-iphone-software-features) 在它即将到来的移动操作系统中,也就是所谓的 iOS 12。 + +这也许将推迟一些计划中的升级,包括重新设计的主屏,摄像头的增强以及针对AR游戏的多媒体播放器的支持,该消息来自 [彭博社](https://www.bloomberg.com/news/articles/2018-01-30/apple-is-said-to-push-back-some-key-iphone-software-features),它也是目前为止所有这些预测的主要来源。 + +[![iphone-8-rendering-armend.png](https://cnet2.cbsistatic.com/img/H0LiIaasuxLf5kdAoMYgFrmTEZA=/724x407/2018/02/28/909a855f-937f-4d63-920b-83e3fa456607/iphone-x2.jpg)](https://www.cnet.com/pictures/iphone-2018-most-wanted-features/) + +iPhone 2018:最需要的功能 + +这不代表着 iOS 12 不会发布一些新的功能。日本博客 [Mac Otakara](http://www.macotakara.jp/blog/rumor/entry-34952.html) 报道说一些更新将着重于改进 Face ID,包括能够于横屏模式解锁设备的能力。同时,彭博社也报道过苹果计划扩展它的 [动态表情库](https://www.cnet.com/news/getting-started-with-the-iphone-x-animoji-apple/)。同时,当然,如果动态表情(和苹果的 [景深](https://www.cnet.com/news/apple-face-id-truedepth-how-it-works/) 摄像头技术)正在引入未来版本的 iPad 中,这些支持也将需要加入到 iOS 软件的平板系统中。 + +## MacOS: 加倍关注安全 + +存在安全问题的不仅仅是 iOS 系统。在 2017 年 11 月,[研究人员已经发现了一个巨大的安全漏洞](https://www.cnet.com/news/apple-flaw-allows-macos-high-sierra-logins-without-passwords/),它位于苹果的 Mac 操作系统,允许用户能够在任何 Mac 笔记本或者台式机上虚拟登陆,并且不需要密码。([如果你近期没有更新MacOS,这里是如何防止此类访问的方法](https://support.apple.com/en-us/HT204012)) 诸如此类,我们期待苹果能够投入更多的时间和精力付诸行动改善安全问题,而不仅仅只在今年的WWDC中简单一提。 + +![apple-watch-faces.jpg](https://cnet4.cbsistatic.com/img/JsfogWRVDuQhU5AF9zZFW3caio0=/970x0/2014/11/27/2a3d4179-935d-459a-9c19-25d6a3eee158/apple-watch-faces.jpg) + +现在到了给苹果手表添加更多表盘的时间? + +截图来自 César Salza/CNET + +## WatchOS:扩展健康和健身功能 + +现在只有很少的一些信息关于苹果如何对待它的可穿戴操作系统,但 [近期预测的苹果全新的圆形表盘设计专利](https://www.cnet.com/news/round-apple-watch-patent-granted/) 预示了一种新的可能性。同时也有可能出现 [一个更大规模的表盘商店](https://9to5mac.com/2018/04/14/watchos-4-3-1-suggests-future-support-for-third-party-watch-faces/),或者更多的表盘定制化。否则,我们将看到一些关于继续扩展苹果手表的健康和健身功能的发布(通过 HealthKit 软件集)。 + +## TV OS、Audio OS 和 Siri:让苹果电视和 HomePod 更加智能 + +我们已经知道 [tvOS 11.4 的 beta 版本](https://www.cnet.com/news/airplay-2-multiroom-audio-apple-tv-homepod-ios-11-4-hands-on/) 将会更加紧密地结合苹果电视,集成到 Home 应用中,同时提供和首次于 2017 年 WWDC 发布的 AirPlay 2 多房间音响功能的兼容性支持。这也许意味着更多和 HomeKit 的集成,苹果的 [智能家居](https://www.cnet.com/smart-home/) 平台,将在今年的 WWDC 中准备就绪。也许我们也将听到更多关于游戏和电视应用的消息。 + +![homepod-product-photos-12](https://cnet4.cbsistatic.com/img/KaRXiRMCYAkcqVm3q0radFmPjHg=/970x0/2018/02/04/d5f53fb7-49e8-4f7d-84e0-574c3992572c/homepod-product-photos-12.jpg) + +Tyler Lizenby/CNET + +iOS 11.4 也将最终带来已经承诺过的多房间语音和立体声配对功能到 HomePod 中。但当一个产品的硬件是顶尖的,它的软件却严重缺乏。我们希望苹果能够借 HomePod 发布一周年的时机,宣布针对语音操作系统的具体改进。从而使音响能够减少对于 iPhone 的依赖,对 [亚马逊的 Echo](https://www.cnet.com/news/amazon-echo-alexa-devices-everything-you-need-to-know/) 和 [谷歌的家庭音响系统](https://www.cnet.com/news/everything-you-need-to-know-about-google-home/) 构成明显的挑战。 + +在如今情况下:如果苹果还希望有机会和 Alexa 以及 Google Assistant 继续竞争的话,毫无疑问,Siri 需要一次彻彻底底的重构。 + +## 关于'Marzipan'? + +苹果开发者在最近几个月中最大的困扰之一就是关于“Marzipan”。这应该是初始版本的开发代号,首次由 [彭博社](https://www.bloomberg.com/news/articles/2017-12-20/apple-is-said-to-have-plan-to-combine-iphone-ipad-and-mac-apps) 在 1 月份报道,旨在为用户带来一种“使用一组能够在 iPhone、iPad 和 Mac 都运行良好的应用程序”的方式。 + +虽然人们都说,即使未来 iOS 和 MacOS 没有整合成一个操作系统,那最终 [ iOS 系统的应用也可以在 MacOS 上运行](https://www.cnet.com/news/apple-may-offer-combined-ios-and-mac-apps-next-year/)。然而真相可能没有那么激动人心。根据 [苹果权威专家 John Gruber ](https://daringfireball.net/2018/04/scuttlebutt_regarding_ui_project),他对于 Marzipan 的名字一无所知。但他称苹果目前显然正在研究一种共享工具集,它能够将 iOS 和 Mac 的应用在一个通用的环境中同时开发,至少在一定程度上。相较而言,目前的情况是开发者必须要为每一个平台设计、开发和部署不同的版本,这导致了很多重复的工作。 + +Gruber 的说法揭露了一种更少野心,但同时更加现实的为 iOS 和 MacOS 服务的协作方式 - 虽然是一种听起来对开发者更实际的方式,但也在技术上更具可能性。但是,他还指出“这将是 2019 年的事情” - 意味着你应当不会在今年的 WWDC 中听到任何公开的披露。 + +## 让我们 6 月 4 日拭目以待 + +CNET 将全程报道 WWDC,包括预览,圣何塞的发布会现场直播以及许多后续的更近报道。保持关注。 + +[iPhone 2018](https://www.cnet.com/news/iphone-2018-iphone-x-plus-launch-date-specs-price-release-date-rumors/):所有关于规格、功能和发布时间的预测 + +[Google I/O 2018](https://www.cnet.com/news/duplex-android-p-and-assistant-everything-important-from-google-io/):所有谷歌宣布的重要事宜 + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + + +--- + +> [掘金翻译计划](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)、[前端](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#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/android.md b/android.md index 4c0052389b0..1959e515efa 100644 --- a/android.md +++ b/android.md @@ -1,3 +1,50 @@ +* [正确实现 linkedPurchaseToken 以避免重复订阅](https://juejin.im/post/5baf9a3e6fb9a05ce2741437) ([yuwhuawang](https://github.com/yuwhuawang) 翻译) +* [Android 的多摄像头支持](https://juejin.im/post/5b98fcf4f265da0ad13b66e0) ([luoqiuyu](https://github.com/luoqiuyu) 翻译) +* [React Native 对 Flutter: 哪一个对创业公司更加友好?](https://juejin.im/post/5b949e5d5188255c791ae376) ([tanglie1993](https://github.com/tanglie1993) 翻译) +* [Flutter 的英雄和流氓们 —— 为 Flutterverse 带来平衡](https://juejin.im/post/5b8e75a46fb9a019f928e678) ([DateBro](https://github.com/DateBro) 翻译) +* [如何将 Stackdriver 连接到智能家居服务器以进行错误记录](https://juejin.im/post/5b8f8df7e51d450e9f66d6f0) ([Starriers](https://github.com/Starriers) 翻译) +* [写一个完整的 Flutter App 是什么感觉](https://juejin.im/post/5b7e18c4e51d4538cf53d1f8) ([tanglie1993](https://github.com/tanglie1993) 翻译) +* [深入 Flutter 之手势](https://juejin.im/post/5b70eee8e51d456682516d36) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) +* [挑战 Flutter 之 YouTube](https://juejin.im/post/5b6e4108e51d451963502877) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) +* [挑战 Flutter 之 Twitter](https://juejin.im/post/5b6f9220f265da2816595999) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) +* [测试原生,Flutter 和 React Native 移动开发之间的性能差异。](https://juejin.im/post/5b62ccef6fb9a04fc564c11b) ([LeeSniper](https://github.com/LeeSniper) 翻译) +* [如何用 Flutter 来创建一个带有底部导航栏的应用程序](https://juejin.im/post/5b4862f56fb9a04f9c43b218) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [Slidable:一个 Flutter 的故事](https://juejin.im/post/5b5e84b86fb9a04fa91bfeb6) ([YueYongDev](https://github.com/YueYongDev) 翻译) +* [在 Flutter 中实现微光闪烁效果](https://juejin.im/post/5b552d516fb9a04fc9374978) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [为什么每个 Android 开发者都应该尝试 Flutter](https://juejin.im/post/5b5e70ffe51d4518e311b63d) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [移动技术在改善财务健康方面的作用](https://juejin.im/post/5b5485c0e51d45355d51ca1b) ([DateBro](https://github.com/DateBro) 翻译) +* [在 Flutter 中解析复杂的 JSON](https://juejin.im/post/5b5d782ae51d45191c7e7fb3) ([DateBro](https://github.com/DateBro) 翻译) +* [Kotlin 揭秘:理解速记 Lambda 语法](https://juejin.im/post/5b59c1945188251b3950ea6f) ([androidxiao](https://github.com/androidxiao) 翻译) +* [深入了解 Flutter](https://juejin.im/post/5b5c3736e51d4519634fe799) ([DateBro](https://github.com/DateBro) 翻译) +* [使用 Flutter 制作 3D 翻转动画](https://juejin.im/post/5b5534c951882562b9248294) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [Flutter 实用指南:给初学者的 6 个小帖士](https://juejin.im/post/5b5534126fb9a04fcc449f4c) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [用 Flutter 写一个待办事项应用](https://juejin.im/post/5b4f52275188251b11096a28) ([DateBro](https://github.com/DateBro) 翻译) +* [Flutter 系列入门教程五:网格](https://juejin.im/post/5b6915636fb9a04fd73a6ecf) ([YueYongDev](https://github.com/YueYongDev) 翻译) +* [Flutter 中的原生应用程序状态](https://juejin.im/post/5b651edc6fb9a04fbc221435) ([Starriers](https://github.com/Starriers) 翻译) +* [实用 ProGuard 规则示例](https://juejin.im/post/5b72b49a5188256137188578) ([DerekDick](https://github.com/DerekDick) 翻译) +* [Flutter 挑战之 WhatsApp](https://juejin.im/post/5b70f42851882560ec4b190d) ([YueYongDev](https://github.com/YueYongDev) 翻译) +* [为什么你需要关注一下 Flutter](https://juejin.im/post/5b508c7cf265da0f955ccb09) ([DateBro](https://github.com/DateBro) 翻译) +* [如何在 Flutter 中设计 LinearLayout?](https://juejin.im/post/5b3edeb16fb9a04fe820ccbc) ([androidxiao](https://github.com/androidxiao) 翻译) +* [使用 Flutter 的 GestureDetector 构建自定义滑块](https://juejin.im/post/5b431bff5188251b166ee0c1) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [Flutter 组件到底是什么?](https://juejin.im/post/5b46f836f265da0f8f2025f7) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [海量视频时代下的内容发现之旅](https://juejin.im/post/5b441c895188251aad20f644) ([Yuhanlolo](https://github.com/Yuhanlolo) 翻译) +* [React Native:回顾 Udacity 移动工程团队的使用历程](https://juejin.im/post/5b421b606fb9a04fd15ff802) ([pkuwwt](https://github.com/pkuwwt) 翻译) +* [如何避免拍脑袋想出的产品优先策略](https://juejin.im/post/5b37a0156fb9a00e78666072) ([bobmayuze](https://github.com/bobmayuze) 翻译) +* [Android 应用和游戏的无障碍开发介绍](https://juejin.im/post/5b31ea42e51d455882380119) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [如何用 Android vitals 解决应用程序的质量问题](https://juejin.im/post/5b3229a7f265da598c09dd4a) ([LeeSniper](https://github.com/LeeSniper) 翻译) +* [在 SnackBar,Navigation 和其他事件中使用 LiveData(SingleLiveEvent 案例)](https://juejin.im/post/5b2b1b2cf265da5952314b63) ([wzasd](https://github.com/wzasd) 翻译) +* [PWA 再进化,可以生成一个安卓原生的 WebAPK 了](https://juejin.im/post/5b2fb7f2e51d4558b46667be) ([Yuhanlolo](https://github.com/Yuhanlolo) 翻译) +* [如何针对 Android 优化您的应用(Go 版)](https://juejin.im/post/5b21cd246fb9a01e754642f3) ([androidxiao](https://github.com/androidxiao) 翻译) +* [带你领略 ConstraintLayout 1.1 的新功能](https://juejin.im/post/5b013e6f51882542c760dc7b) ([Moosphan](https://github.com/Moosphan) 翻译) +* [在 Android P 中使用默认的 TLS 来保护你的用户](https://juejin.im/post/5b29030551882574b4094a6f) ([hanliuxin5](https://github.com/hanliuxin5) 翻译) +* [介绍 Google Play 上新的优质 Android 应用与游戏](https://juejin.im/post/5ae978d151882567370633ca) ([sisibeloved](https://github.com/sisibeloved) 翻译) +* [情景活动感知识别的 Transition API 新特性面向全体开发者开放](https://juejin.im/post/5b21cfe16fb9a01e561a4b26) ([wzasd](https://github.com/wzasd) 翻译) +* [通过安全浏览保护 WebView](https://juejin.im/post/5b0fb8a06fb9a00a25192a3f) ([androidxiao](https://github.com/androidxiao) 翻译) +* [使用 Span 来修改文本样式的优质体验](https://juejin.im/post/5b24c20851882574ea3a0d86) ([wzasd](https://github.com/wzasd) 翻译) +* [Windows Insets 和 Fragment 过渡动画](https://juejin.im/post/5b05206f6fb9a07ac162c9af) ([LeeSniper](https://github.com/LeeSniper) 翻译) +* [Android 支持库 21.1.0 中的 Loaders](https://juejin.im/post/5b06139a6fb9a07aa43c966f) ([dreamhb](https://github.com/dreamhb) 翻译) +* [Android Studio 切换至 D8 dexer](https://juejin.im/post/5b0a84f0518825388e725ec8) ([Starriers](https://github.com/Starriers) 翻译) +* [移动游戏发行的新时代](https://juejin.im/post/5af9acf851882542877345ef) ([IllllllIIl](https://github.com/IllllllIIl) 翻译) * [在 Google I/O 2018 观看 Flutter 的正确姿势](https://juejin.im/post/5aebd7166fb9a07ab4587b3f) ([wzasd](https://github.com/wzasd) 翻译) * [更为详细的地图、导航和助手功能 —— Google I/O 2018 的 Android 应用更新](https://juejin.im/post/5aee74626fb9a07abb237f89) ([sisibeloved](https://github.com/sisibeloved) 翻译) * [论 Android 中 Span 的正确打开方式](https://juejin.im/entry/5af401bb518825671776537d/) ([tanglie1993](https://github.com/tanglie1993) 翻译) diff --git a/backend.md b/backend.md index 318e11dda29..03fd1a2682f 100644 --- a/backend.md +++ b/backend.md @@ -1,3 +1,52 @@ +* [使用 Node 和 OAuth 2.0 构建一个简单的 REST API](https://juejin.im/post/5bb1d3f95188255c6a044d9a) ([Starriers](https://github.com/Starriers) 翻译) +* [容器,虚拟机以及 Docker 的初学者入门介绍](https://juejin.im/entry/5bada97f6fb9a05d0e2e7014/detail) ([steinliber](https://github.com/steinliber) 翻译) +* [如何在六个月或更短的时间内成为DevOps 工程师,第2部分:配置](https://juejin.im/post/5baf677df265da0a951ee8f5) ([jianboy](https://github.com/jianboy) 翻译) +* [如何在六个月或更短的时间内成为DevOps 工程师,第三部分:版本控制](https://juejin.im/post/5bb067bfe51d450e905a0aa4) ([jianboy](https://github.com/jianboy) 翻译) +* [2018 年度最佳数据库即服务解决方案](https://juejin.im/post/5ba784e3e51d450e9d64984d) ([cf020031308](https://github.com/cf020031308) 翻译) +* [SmartyStreets 的 Go 测试探索之路](https://juejin.im/post/5ba83f2ff265da0a867c3818) ([kasheemlew](https://github.com/kasheemlew) 翻译) +* [使用 Go 编写微服务及其 GraphQL 网关](https://juejin.im/post/5b94cf476fb9a05d26593f07) ([changkun](https://github.com/changkun) 翻译) +* [GopherCon 2018:揭秘二叉查找树算法](https://juejin.im/post/5b94de9c5188255c5047076c) ([changkun](https://github.com/changkun) 翻译) +* [使用 Nexmo 和微软语音翻译 API 构建 Babel 鱼](https://juejin.im/post/5b8a78a3e51d4538a515cbda) ([Starriers](https://github.com/Starriers) 翻译) +* [如何优化企业级规模的 Node.js 应用程序](https://juejin.im/post/5b83c639f265da436d7e4c5e) ([Starriers](https://github.com/Starriers) 翻译) +* [Databook:通过元数据,Uber 将大数据转化为知识](https://juejin.im/post/5b800032e51d45389d3b4950) ([cf020031308](https://github.com/cf020031308) 翻译) +* [Python 的多线程与多进程](https://juejin.im/post/5b84f3086fb9a01a1a27cedb) ([lsvih](https://github.com/lsvih) 翻译) +* [如何在数据科学中写出生产层面的代码?](https://juejin.im/post/5b7adb7751882542d63b2805) ([sisibeloved](https://github.com/sisibeloved) 翻译) +* [Apache Airflow 的关键概念](https://juejin.im/post/5b7ba247e51d4538d42ab6a0) ([Starriers](https://github.com/Starriers) 翻译) +* [我是如何使用 Python 在 Medium 上找到并关注有趣的人](https://juejin.im/post/5b72c61851882561311fccce) ([Park-ma](https://github.com/Park-ma) 翻译) +* [如何使用 Python 和 BeautifulSoup 抓取网站内容](https://juejin.im/post/5b74fcec51882561446fb97f) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [我如何使用 Node.js 来实现工作自动化](https://juejin.im/post/5b4fe75ef265da0f54052138) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [在 UNIX 中,一切皆文件](https://juejin.im/post/5b652d346fb9a04fc03129e6) ([pmwangyang](https://github.com/pmwangyang) 翻译) +* [使用 PhpFastCache 提升网站性能](https://juejin.im/post/5b54d01be51d4517c5649965) ([lsvih](https://github.com/lsvih) 翻译) +* [我们是如何高效实现一致性哈希的](https://juejin.im/post/5b5488a96fb9a04fad3a181a) ([yqian1991](https://github.com/yqian1991) 翻译) +* [正则表达式要跑 5 天,所以我做了个工具将其缩短至 15 分钟。](https://juejin.im/post/5b6d426f6fb9a04fd1604341) ([cf020031308](https://github.com/cf020031308) 翻译) +* [如何使用 Pandas 重写你的 SQL 查询以及其他操作](https://juejin.im/post/5b5e5b2ee51d4517df1510c7) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [Robinhood 为什么使用 Airflow](https://juejin.im/post/5b4808f751882519ec07eaba) ([cf020031308](https://github.com/cf020031308) 翻译) +* [Web 应用架构基础课](https://juejin.im/post/5b69a8eef265da0f926baa56) ([horizon13th](https://github.com/horizon13th) 翻译) +* [Airflow: 一个工作流程管理平台](https://juejin.im/post/5b5bd2b6f265da0f60131d0c) ([yqian1991](https://github.com/yqian1991) 翻译) +* [从 Cron 到 Airflow 的迁移中我们学到了什么](https://juejin.im/post/5b4c3575f265da0f7334bbc9) ([cf020031308](https://github.com/cf020031308) 翻译) +* [[字幕翻译] Andrew Godwin — Django 异步 — PyCon 2018](https://www.bilibili.com/video/av24571596/) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [我是如何从零开始建立一个网络爬虫来实现我的求职自动化的](https://juejin.im/post/5b3b40896fb9a04f9e22e927) ([Starriers](https://github.com/Starriers) 翻译) +* [使用多重赋值与元组解包提升 Python 代码的可读性](https://juejin.im/post/5b3c2cf1e51d451925625b94) ([lsvih](https://github.com/lsvih) 翻译) +* [通过 SSH 远程使用 Python 解释器来运行 Flask](https://juejin.im/post/5b3e25a8e51d45191716d187) ([Starriers](https://github.com/Starriers) 翻译) +* [从 Java EE 8 Security API 开始 — 第二部分](https://juejin.im/post/5b3b49d66fb9a04f8a2160a9) ([Starriers](https://github.com/Starriers) 翻译) +* [基于 Node.js 的 Alexa Skills Kit 发布了!](https://juejin.im/post/5b4790b6f265da0f5d4cbec5) ([Yuhanlolo](https://github.com/Yuhanlolo) 翻译) +* [使用 Python 实现接缝裁剪算法](https://juejin.im/post/5b46ee8e6fb9a04fc436ad60) ([caoyi0905](https://github.com/caoyi0905) 翻译) +* [Erlang 之禅第二部分](https://juejin.im/post/5b3ea3ed6fb9a04fb8772a2c) ([7Ethan](https://github.com/7Ethan) 翻译) +* [从 Java EE 8 Security API 开始 — 第一部分](https://juejin.im/post/5b35a8d4f265da599423598b) ([Starriers](https://github.com/Starriers) 翻译) +* [通过插图学习 Go 的并发](https://juejin.im/post/5b2cd078e51d4558b70ca399) ([elliott-zhao](https://github.com/elliott-zhao) 翻译) +* [Kubernetes 分布式应用部署和人脸识别 app 实例](https://juejin.im/post/5b2db0576fb9a00e50313904) ([maoqyhz](https://github.com/maoqyhz) 翻译) +* [[字幕翻译] Graham Dumpleton — Secrets of a WSGI master. — PyCon 2018](https://github.com/xitu/gold-miner/blob/master/TODO1/secrets-of-a-wsgi-master-pycon-2018.md) ([vuuihc](https://github.com/vuuihc) 翻译) +* [Erlang 之禅第一部分](https://juejin.im/post/5b2a16cce51d4558d726e8c9) ([steinliber](https://github.com/steinliber) 翻译) +* [[字幕翻译] James Bennett — 理解 Python 字节码 — PyCon 2018](https://github.com/xitu/gold-miner/issues/3990) ([cdpath](https://github.com/cdpath) 翻译) +* [如何通过树莓派的深度学习轻松检测对象](https://juejin.im/post/5b1ba938518825137661af46) ([Starriers](https://github.com/Starriers) 翻译) +* [[字幕翻译] 玛利亚塔·维加亚 — 什么是 Python 核心开发者?— PyCon 2018](https://juejin.im/post/5b152c825188257d8a48d4a8) ([elliott-zhao](https://github.com/elliott-zhao) 翻译) +* [一种更简单的途径在 Java 中进行函数式编程](https://juejin.im/post/5b24aa5f6fb9a00e4d53e0c9) ([maoqyhz](https://github.com/maoqyhz) 翻译) +* [在 Laravel 应用程序之间共享数据库](https://juejin.im/post/5b17407fe51d4506a14db524) ([elliott-zhao](https://github.com/elliott-zhao) 翻译) +* [支撑现代存储系统的算法](https://juejin.im/post/5b10f80b5188257d92206851) ([LeopPro](https://github.com/LeopPro) 翻译) +* [使用 Go 语言的流模式来解析 DrugBank 的 XML(或者任何大的 XML 文件)](https://juejin.im/entry/5b06322a6fb9a07aab2a3d44) ([steinliber](https://github.com/steinliber) 翻译) +* [那些我们不需要的 HTTP 头信息](https://juejin.im/post/5b06c89df265da0db35022d8) ([SergeyChang](https://github.com/SergeyChang) 翻译) +* [由 Node.js 发送 Web 推送通知](https://juejin.im/post/5afc32146fb9a07acf5657e7) ([lsvih](https://github.com/lsvih) 翻译) +* [30 分钟 Python 爬虫教程](https://juejin.im/post/5afab181f265da0b7452514a) ([kezhenxu94](https://github.com/kezhenxu94) 翻译) * [Python 中的键值(具名)参数:如何使用它们](https://juejin.im/post/5ae97546f265da0b8d41bcc7) ([sisibeloved](https://github.com/sisibeloved) 翻译) * [使用 python 和 keras 实现卷积神经网络](https://juejin.im/post/5aefb0f351882567336aa3c7) ([JohnJiangLA](https://github.com/JohnJiangLA) 翻译) * [使用 Go 和 AWS Lambda 构建无服务 API](https://juejin.im/post/5af4082f518825672a02f262) ([sisibeloved](https://github.com/sisibeloved) 翻译) diff --git a/blockchain.md b/blockchain.md index daac6ead06d..2972e70135c 100644 --- a/blockchain.md +++ b/blockchain.md @@ -1,3 +1,6 @@ +* [能源行业聚焦基于区块链技术的加密货币](https://juejin.im/post/5b47f4a9e51d45198a2eb75b) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [Envion 通过区块链采矿来支持可再生能源的发展](https://juejin.im/post/5b47f6c2e51d45191b611b09) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [BigQuery 中的比特币:使用公共数据分析区块链](https://juejin.im/post/5afc17b16fb9a07aaf35673a) ([LeopPro](https://github.com/LeopPro) 翻译) * [用不到 200 行的 GO 语言编写您自己的区块链](https://juejin.im/post/5ad95b056fb9a07aa349cd41) ([Starriers](https://github.com/Starriers) 翻译) * [用 Java 代码实现区块链](https://juejin.im/post/5ae57d9e6fb9a07ab83dcc03) ([Starriers](https://github.com/Starriers) 翻译) * [以太坊钱包详解](https://juejin.im/post/5ae2942ff265da0b886d23df) ([XatMassacrE](https://github.com/XatMassacrE) 翻译) diff --git a/design.md b/design.md index bd7231a203c..dab5fa5e13e 100644 --- a/design.md +++ b/design.md @@ -1,3 +1,16 @@ +* [如何创建一个设计体系来赋能团队 —— 关注人,而非像素](https://juejin.im/post/5bac2a2fe51d450e942f4853) ([pmwangyang](https://github.com/pmwangyang) 翻译) +* [另外 5 种关于视觉和认知间差异的绘画练习](https://juejin.im/post/5baa5b45f265da0ab915cb7f) ([Ruixi](https://github.com/Ruixi) 翻译) +* [构建未来的设计生态系统](https://juejin.im/post/5b992be8f265da0aa3591346) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) +* [设计 QA 在应用程序设计中的重要性](https://juejin.im/post/5b89421651882542da339868) ([lihanxiang](https://github.com/lihanxiang) 翻译) +* [为什么设计师讨厌政治](https://juejin.im/post/5b89599f51882542b03e6d5b) ([Starriers](https://github.com/Starriers) 翻译) +* [常见网页设计错误一览](https://juejin.im/post/5b7984265188254312414c1f) ([StellaBauhinia](https://github.com/StellaBauhinia) 翻译) +* [如何设计移动应用的搜索功能?](https://juejin.im/post/5b692ca251882562b924a6c7) ([xunge0613](https://github.com/xunge0613) 翻译) +* [在 Sketch 中使用一个设计体系创作:第一部分 [教程]](https://juejin.im/post/5b591a655188257bca290b24) ([pmwangyang](https://github.com/pmwangyang) 翻译) +* [在 Sketch 中使用一个设计体系创作: 第二部分 [教程]](https://juejin.im/post/5b5d2a456fb9a04fc80b8f4b) ([Zheng7426](https://github.com/Zheng7426) 翻译) +* [绘图技巧的快速入门之:6 个绘图练习,让你立即上手!](https://juejin.im/post/5b39823fe51d4558ca674cff) ([wzasd](https://github.com/wzasd) 翻译) +* [为什么 UX,UI,CX,IA,IxD 和其他类型的设计都是愚蠢的](https://juejin.im/post/5b3588f2e51d4558dd69a44c) ([zhmhhu](https://github.com/zhmhhu) 翻译) +* [创造华丽 UI 的 7 个规则(Part 2)](https://juejin.im/post/5b2a1554e51d4558cc35b3be) ([xujunjiejack](https://github.com/xujunjiejack) 翻译) +* [创造华丽 UI 的 7 个规则(Part 1)](https://juejin.im/post/5b1dcc7d5188257d7d71946a) ([wzasd](https://github.com/wzasd) 翻译) * [用户体验中的稀缺性:成为常态的心理偏见](https://juejin.im/post/5aef0943518825672a02d2ca) ([Starriers](https://github.com/Starriers) 翻译) * [细数那些我离不开的 Sketch 插件](https://juejin.im/post/5ae0623ef265da0b8d419aca) ([allenlongbaobao](https://github.com/allenlongbaobao) 翻译) * [设计师与工程师协作的 5 项准则](https://juejin.im/post/5ac9e56af265da23945fc201) ([Starriers](https://github.com/Starriers) 翻译) diff --git a/front-end.md b/front-end.md index 862d02303de..e465818a75c 100644 --- a/front-end.md +++ b/front-end.md @@ -1,3 +1,72 @@ +* [以申请大学流程来解释 JavaScript 的 filter](https://juejin.im/post/5b9f09685188255c5e66d60c) ([calpa](https://github.com/calpa) 翻译) +* [探索 SMACSS:可扩展的模块化 CSS 框架](https://juejin.im/post/5ba234c85188255c38535a47) ([Park-ma](https://github.com/Park-ma) 翻译) +* [this 到底指向什么 — 理解 JavaScript 中的 this、call、apply 和 bind](https://juejin.im/post/5b9f176b6fb9a05d3827d03f) ([CoolRice](https://github.com/CoolRice) 翻译) +* [理解 JavaScript 中的执行上下文和执行栈](https://juejin.im/post/5ba32171f265da0ab719a6d7) ([CoolRice](https://github.com/CoolRice) 翻译) +* [React Profiler 介绍](https://juejin.im/post/5ba0f8e4f265da0ab915bcf2) ([CoderMing](https://github.com/CoderMing) 翻译) +* [./dogs.html 和 /dogs.html 间有什么区别?](https://juejin.im/post/5ba7a5dfe51d450e4a1bc136) ([shery](https://github.com/shery) 翻译) +* [现代浏览器内部揭秘(第一部分)](https://juejin.im/post/5b9b0932e51d450e9059c16a) ([Colafornia](https://github.com/Colafornia) 翻译) +* [JAVASCRIPT 日期权威指南](https://juejin.im/post/5b9b4b7bf265da0af6099ed8) ([CoderMing](https://github.com/CoderMing) 翻译) +* [用 API 请求制作赏心悦目的 UX](https://juejin.im/post/5b992d13e51d450e894de541) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) +* [如何使用原生 JavaScript 构建简单的 Chrome 扩展程序](https://juejin.im/post/5b98a58b6fb9a05cec4d92e0) ([shery](https://github.com/shery) 翻译) +* [CSS 变量和 JavaScript 让应用支持动态主题 🎨](https://juejin.im/post/5b9528de6fb9a05cf3710e00) ([CoolRice](https://github.com/CoolRice) 翻译) +* [JavaScript 2018 中即将迎来的新功能:异步生成器及更好的正则表达式](https://juejin.im/post/5b95be51f265da0adc18ac08) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) +* [在 JavaScript 中 为什么你应当使用 map 和 filter 来替代 forEach](https://juejin.im/post/5b95c4fe5188255c402ae6fb) ([zhmhhu](https://github.com/zhmhhu) 翻译) +* [函数式 JavaScript 编程的简短介绍](https://juejin.im/post/5b8f97135188255c4a70f999) ([Zheng7426](https://github.com/Zheng7426) 翻译) +* [简单的响应式现代 css 网格布局](https://juejin.im/post/5b8b4ddef265da436d7e594d) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) +* [使用原生 JavaScript 构建状态管理系统](https://juejin.im/post/5b763528e51d45559e3a5b64) ([shery](https://github.com/shery) 翻译) +* [布局的下一次革新会是怎样的](https://juejin.im/post/5b85586ce51d4538c77a9cc1) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) +* [如何使用纯函数式 JavaScript 处理脏副作用](https://juejin.im/post/5b82bdb351882542e241ed32) ([Gavin-Gong](https://github.com/Gavin-Gong) 翻译) +* [2018 年七个通过脑电图分析实现“读心术”的 Javascript 库](https://juejin.im/post/5b7f63e651882542c20f22f0) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [一行 JavaScript 代码竟然让 FT.com 网站慢了十倍](https://juejin.im/post/5b7bb6dfe51d4538bf55aa5f) ([IridescentMia](https://github.com/IridescentMia) 翻译) +* [SpaceAce 了解一下,一个新的前端状态管理库](https://juejin.im/post/5b7653c96fb9a009c72cb7af) ([noahziheng](https://github.com/noahziheng) 翻译) +* [2018 来谈谈 Web 组件](https://juejin.im/post/5b780a98e51d4538980bf5cf) ([weberpan](https://github.com/weberpan) 翻译) +* [如何提升设计到开发的协作效率](https://juejin.im/post/5b83609de51d4538c210a957) ([meterscao](https://github.com/meterscao) 翻译) +* [优化 MP4 视频以获得更快的流传输速度](https://juejin.im/post/5b72bed8e51d45661e00f693) ([HaoChuan9421](https://github.com/HaoChuan9421) 翻译) +* [如何向带有插槽的 React 组件传递多个 Children](https://juejin.im/post/5b72935a6fb9a009724b3e4e) ([Zheng7426](https://github.com/Zheng7426) 翻译) +* [用 React 和 Vue 创建了两个完全相同的应用后,发现了这些差异](https://juejin.im/post/5b63f50a5188253128337110) ([jonjia](https://github.com/jonjia) 翻译) +* [让我们一起解决“this”难题  —  第二部分](https://juejin.im/post/5b6915cce51d4519962f0ca7) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [让我们一起解决“this”难题 — 第一部分](https://juejin.im/post/5b680d8b518825196730a00f) ([doctype233](https://github.com/doctype233) 翻译) +* [设计 React 组件 API](https://juejin.im/post/5b545f0b6fb9a04fc93748ba) ([](https://github.com/) 翻译) +* [关于 React Motion 的简要介绍](https://juejin.im/post/5b48061551882519790c77f3) ([doctype233](https://github.com/doctype233) 翻译) +* [无头渲染组件](https://juejin.im/post/5b5e919f51882519d3467f41) ([](https://github.com/) 翻译) +* [用 30 分钟建立一个网站的方式来学习 Bootstrap 4](https://juejin.im/post/5b5eb7b2e51d451989055d9d) ([Zheng7426](https://github.com/Zheng7426) 翻译) +* [使用 Web Beacon API 记录活动](https://juejin.im/post/5b694b5de51d4519700fa56a) ([elliott-zhao](https://github.com/elliott-zhao) 翻译) +* [ECMAScript 修饰器微指南](https://juejin.im/post/5b543d8af265da0f4a4e711f) ([jonjia](https://github.com/jonjia) 翻译) +* [由 CSS 网格系统的创造者们所讲述的故事](https://juejin.im/post/5b503d9ef265da0f504a518e) ([Tivcrmn](https://github.com/Tivcrmn) 翻译) +* [Javascript(ES6)Generator 入门](https://juejin.im/post/5b4c22aa6fb9a04faf479be1) ([ssshooter](https://github.com/ssshooter) 翻译) +* [论 Rust 和 WebAssembly 对源码地址索引的极限优化](https://juejin.im/post/5b51948a5188251ac771c064) ([D-kylin](https://github.com/D-kylin) 翻译) + + +* [单元素组件模式简介](https://juejin.im/post/5b445d79e51d4519596b5f87) ([jonjia](https://github.com/jonjia) 翻译) +* [React 实现条件渲染的多种方式和性能考量](https://juejin.im/post/5b3e34905188251b1f223ad3) ([IveHD](https://github.com/IveHD) 翻译) +* [2018 年,如何选择最好的静态站点生成器](https://juejin.im/post/5b47079bf265da0faa3655be) ([ssshooter](https://github.com/ssshooter) 翻译) +* [将项目迁移到 Yarn 然后又迁回到 npm](https://juejin.im/post/5b3b5735f265da0f894b443d) ([zhongdeming428](https://github.com/zhongdeming428) 翻译) +* [或许你并不需要 Rust 和 WASM 来提升 JS 的执行效率 — 第二部分](https://juejin.im/post/5b357c20f265da595f0d3f91) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [怎样使用简单的三角函数来创建更好的加载动画](https://juejin.im/post/5b33055f518825748871c590) ([zhongdeming428](https://github.com/zhongdeming428) 翻译) +* [WOFF文件格式 1.0](https://github.com/xitu/gold-miner/blob/master/TODO1/recommendation-woff-2012-12-13.md) ([zhmhhu](https://github.com/zhmhhu) 翻译) +* [Vue.js 还是 React?你会选择哪一个?为什么?](https://juejin.im/post/5b25b3a16fb9a00e70686180) ([allenlongbaobao](https://github.com/allenlongbaobao) 翻译) +* [或许你并不需要 Rust 和 WASM 来提升 JS 的执行效率 — 第一部分](https://juejin.im/post/5b24cf7e51882574c2652f61) ([shery15](https://github.com/shery15) 翻译) +* [从零开始,在 Redux 中构建时间旅行式调试](https://juejin.im/post/5b24c0bce51d4558ba1a5584) ([weberpan](https://github.com/weberpan) 翻译) +* [为何前端开发如此不稳定](https://juejin.im/post/5b1f2f1ae51d4506894983ae) ([Colafornia](https://github.com/Colafornia) 翻译) +* [前端开发框架实战对比(2018年更新)](https://juejin.im/post/5b20a5996fb9a01e6b2c21ec) ([geniusq1981](https://github.com/geniusq1981) 翻译) +* [我们距离 Babel 7.0 发布很近了。这里是所有我们一直在做的很酷的事情。](https://juejin.im/post/5b174f3e518825139b18e1f0) ([xueshuai](https://github.com/xueshuai) 翻译) +* [新的 CSS 特性正在改变网页设计](https://juejin.im/post/5b0cae8c6fb9a009de14c833) ([sophiayang1997](https://github.com/sophiayang1997) 翻译) +* [一个简单的 ES6 Promises 指南](https://juejin.im/post/5b0eb3b1f265da08f31e770a) ([sophiayang1997](https://github.com/sophiayang1997) 翻译) +* [JavaScript 是如何工作的:Service Worker 的生命周期与使用场景](https://juejin.im/post/5b14c1d86fb9a01e700ff2f2) ([talisk](https://github.com/talisk) 翻译) +* [使用 styled-components 的 React 服务器端渲染极简指南](https://juejin.im/post/5b0f9a4c51882515791502d0) ([elliott-zhao](https://github.com/elliott-zhao) 翻译) +* [什么是 JavaScript 生成器?如何使用生成器?](https://juejin.im/post/5b14faf2f265da6e4d5af3b9) ([lsvih](https://github.com/lsvih) 翻译) +* [为什么 VueX 是前端与 API 之间的完美接口](https://juejin.im/post/5b14d1bd6fb9a01e4508bfc1) ([zhmhhu](https://github.com/zhmhhu) 翻译) +* [Font-size:一个出乎意料复杂的 CSS 属性](https://juejin.im/post/5b1292355188251377116617) ([zephyrJS](https://github.com/zephyrJS) 翻译) +* [JavaScript 是如何工作的:深入网络层 + 如何优化性能和安全](https://juejin.im/post/5b02ae48518825429d1f9aff) ([Hopsken](https://github.com/Hopsken) 翻译) +* [JavaScript 是如何工作的:对比 WebAssembly + 为什么在某些场景下它比 JavaScript 更合适](https://juejin.im/post/5b0526e751882507c36d0167) ([stormluke](https://github.com/stormluke) 翻译) +* [怎样更好地使用 Vue:我在新工作中遇到的一些问题清单](https://juejin.im/post/5b0a36366fb9a07a9c04aca2) ([sophiayang1997](https://github.com/sophiayang1997) 翻译) +* [如何在 5 分钟之内写出一个不错的 loading 界面](https://juejin.im/post/5af54bbb518825426d2d2e63) ([whuzxq](https://github.com/whuzxq) 翻译) +* [使用 Puppeteer 和 Jest 测试你的 React 应用](https://juejin.im/post/5af90988518825426a1fcc2e) ([jonjia](https://github.com/jonjia) 翻译) +* [这就是为什么我们需要在 React 的类组件中为事件处理程序绑定 this](https://juejin.im/post/5afa6e2f6fb9a07aa2137f51) ([whuzxq](https://github.com/whuzxq) 翻译) +* [JavaScript 是如何工作的:CSS 和 JS 动画背后的原理 + 如何优化性能](https://juejin.im/post/5afaea6b6fb9a07aa34a6a74) ([NoName4Me](https://github.com/NoName4Me) 翻译) +* [⚛ React 状态管理工具博物馆](https://juejin.im/post/5afd18cf6fb9a07acb3d13ac) ([jonjia](https://github.com/jonjia) 翻译) +* [可用但最不常见的 HTML5 标签](https://juejin.im/post/5b0026e8f265da0b8c253c2a) ([lsvih](https://github.com/lsvih) 翻译) +* [JavaScript 是如何工作的:Web 推送通知的机制](https://juejin.im/post/5b002766518825429d1f90bc) ([Starriers](https://github.com/Starriers) 翻译) * [漫画深入浅出 ES 模块](https://juejin.im/post/5aea6ae7f265da0ba4699e0a) ([stormluke](https://github.com/stormluke) 翻译) * [JavaScript 是如何运作的:用 MutationObserver 追踪 DOM 的变化](https://juejin.im/post/5aee720df265da0b8f627173) ([EmilyQiRabbit](https://github.com/EmilyQiRabbit) 翻译) * [深入浅出 JavaScript 关键词 — this](https://juejin.im/post/5aefe76e6fb9a07abc29d4a1) ([weberpan](https://github.com/weberpan) 翻译) diff --git a/integrals.md b/integrals.md index eedb40de7a4..cd94dc0c3af 100644 --- a/integrals.md +++ b/integrals.md @@ -77,7 +77,7 @@ |[CSS writing-mode 的特别技巧](http://gold.xitu.io/entry/57b08227165abd005426657b)|翻译|4| |[如何理解 JavaScript 中的 Promise 机制](http://gold.xitu.io/entry/57890b881532bc0061d5ac52)|翻译|7| |[Google Cloud Functions 文档](https://github.com/xitu/gold-miner/issues/104)|翻译|7| -|[ Building a Kotlin project 1/2](https://github.com/xitu/gold-miner/blob/master/TODO/building-a-kotlin-project.md)|校对|1| +|[Building a Kotlin project 1/2](https://github.com/xitu/gold-miner/blob/master/TODO/building-a-kotlin-project.md)|校对|1| |[创建一个基于 Kotlin 的 Android 项目(下集)](http://gold.xitu.io/entry/570f52981ea493006b5cc9ff)|校对|1| |[你真的懂病毒式营销吗](http://gold.xitu.io/entry/5715d39cdf0eea005c930d80)|翻译|6| |[用工厂流水线的方式来理解 RxJava 的概念](http://gold.xitu.io/entry/571f2cc971cfe40057351e95)|校对|1| @@ -92,7 +92,7 @@ |------|-------|-------| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|2| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|7| -|[TensorFlow 官方文档翻译校对]()|翻译|8| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|8| |[并发编程](https://juejin.im/post/59ecb058f265da43346f10e5)|校对|1.5| |[什么是蒙特卡洛树搜索](https://juejin.im/post/59f16e8c5188250385371302)|校对|1| |[探索 Python 3 加密技术](http://gold.xitu.io/entry/575fae92df0eea0062c5a1dc)|校对|1| @@ -112,10 +112,11 @@ |[编写高性能的 Swift 代码](http://gold.xitu.io/entry/56b60c97816dfa005ae0c0d4)|翻译|15| |[第一次翻译计划文章已兑换Octocat小]()|减去积分|15| -## 译者:[l9m](https://github.com/L9m) 历史贡献积分:82.5 当前积分:47.5 年度积分:12.5 +## 译者:[l9m](https://github.com/L9m) 历史贡献积分:97.5 当前积分:62.5 年度积分:27.5 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|15| |[用行为经济学来传达付费应用订阅的价值](https://juejin.im/post/5ad3ffd0f265da23906c785f)|校对|1| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6| |[简短而又完全精确的编程语言历史](https://juejin.im/post/5ac1b8a25188255c637b1cd5)|校对|1| @@ -172,10 +173,12 @@ |[Starlight - 在网页中运行 Lua](http://gold.xitu.io/entry/5719907eebcb7d005cc6acca)|校对|1| |[React.js 新手村教程](http://gold.xitu.io/entry/5719b6acebcb7d006a007d9b)|翻译|8| -## 译者:[sqrthree](https://github.com/sqrthree) 历史贡献积分:100 当前积分:61 年度积分:48 +## 译者:[sqrthree](https://github.com/sqrthree) 历史贡献积分:107 当前积分:68 年度积分:55 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|44| |[不要害怕 Rebase](https://juejin.im/post/5ab1bdbe518825556e5df5f8)|翻译|4| |[深度学习系列1:设置 AWS & 图像识别](https://juejin.im/post/5987f5885188256dcf65d01e)|校对|1| @@ -208,10 +211,11 @@ |[JavaScript 开发者年度调查报告](http://gold.xitu.io/entry/56ca985071cfe40054d98994)|翻译|15| |[第一次翻译计划文章已兑换Octocat小]()|减去积分|15| -## 译者:[edvardhua](https://github.com/edvardHua) 历史贡献积分:117 当前积分:57 年度积分:10 +## 译者:[edvardhua](https://github.com/edvardHua) 历史贡献积分:124 当前积分:64 年度积分:17 |文章|类型|积分| |------|-------|-------| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|7| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|10| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|10| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|14| @@ -246,7 +250,7 @@ |[Swift + 闭包初始化](http://gold.xitu.io/entry/57bd72a3a633bd005d49fa32/)|翻译|5| |[等不及集成 iOS 10 新特性?如何在应用维护与新特性集成之间找到平衡点](http://gold.xitu.io/entry/57ad9e0e7db2a200540fe491)|翻译|6| |[如何运用最新的技术提升网页速度和性能](http://gold.xitu.io/entry/57b3f7928d2a3b0069605c2c)|校对|1| -|[8 月份兑换章鱼猫两小只]()|减去积分|15*2| +|[8 月份兑换章鱼猫两小只]()|减去积分|30| |[利用 Shoryuken and SQS 快速处理 API 请求](http://gold.xitu.io/entry/57a14ac879bc44005497b433)|翻译|6| |[用户界面中的字体](http://gold.xitu.io/entry/57859bb9d342d300578b1ebf)|翻译|8| |[较为完整的 React.js / Vue.js 的性能比较 Part 1](https://gold.xitu.io/entry/577bacc92e958a00549106dc)|翻译|7| @@ -263,10 +267,12 @@ |[使用 webP 减少图片的大小](http://gold.xitu.io/entry/57383657c4c97100601212d5)|翻译|4| |[Vector For All (slight return)](http://gold.xitu.io/entry/5756697ea341310063dd532c)|校对|1| -## 译者:[cdpath](https://github.com/cdpath) 历史贡献积分:106 当前积分:61 +## 译者:[cdpath](https://github.com/cdpath) 历史贡献积分:126 当前积分:81 年度积分:20 |文章|类型|积分| |------|-------|-------| +|[用 Python 实现马尔可夫链的初学者教程](https://juejin.im/post/5bb031d06fb9a05cdb104888)|翻译|7| +|[[字幕翻译] James Bennett — 理解 Python 字节码 — PyCon 2018](https://github.com/xitu/gold-miner/issues/3990)|翻译|13| |[从零到一用 Python 写一个区块链](https://juejin.im/entry/59faa0ed51882576ea3507de/detail)|翻译|5| |[以排印为本,从内容出发](https://juejin.im/entry/5965c5b26fb9a06ba025074c/detail)|翻译|9| |[用动效创建的可用性:动效中的用户体验宣言](https://juejin.im/post/595c77a66fb9a06bcb7f92ff)|校对|2| @@ -419,10 +425,11 @@ |[使用重构件(Codemod)加速 JavaScript 开发和重构](http://gold.xitu.io/entry/574e49662e958a005e00f543)|翻译|7| |[8月份兑换小章鱼猫一只]()|减去积分|15| -## 译者:[zheaoli](https://github.com/Zheaoli) 历史贡献积分:87 当前积分:-5 年度积分:4 +## 译者:[zheaoli](https://github.com/Zheaoli) 历史贡献积分:87。5 当前积分:-4.5 年度积分:4.5 |文章|类型|积分| |------|-------|-------| +|[使用多重赋值与元组解包提升 Python 代码的可读性](https://juejin.im/post/5b3c2cf1e51d451925625b94)|翻译|0.5| |[带你了解以太坊第 2 层扩容方案:State Channels、Plasma 和 Truebit](https://juejin.im/post/5aa1f63c518825558804f85b)|校对|4| |[Django 基于 Postgres 的全文搜索](https://juejin.im/entry/593a069b61ff4b006c70ba82/detail)|校对|1| |[在 Swift 中使用闭包实现懒加载](https://juejin.im/post/590a9eeab123db00549776ee)|校对|1| @@ -598,10 +605,11 @@ |[使用 BEM 来模块化你的 CSS 代码](http://gold.xitu.io/entry/5757ce7b7db2a200540d51eb)|翻译|7| |[关于 Swift 编译时性能优化的一些思考](http://gold.xitu.io/entry/5757f29279bc440061f5822c)|翻译|4| -## 译者:[bobmayuze](https://github.com/bobmayuze) 历史贡献积分:48 当前积分:33 +## 译者:[bobmayuze](https://github.com/bobmayuze) 历史贡献积分:54.5 当前积分:39.5 年度积分:6.5 |文章|类型|积分| |------|-------|-------| +|[如何避免拍脑袋想出的产品优先策略](https://juejin.im/post/5b37a0156fb9a00e78666072)|翻译|6.5| |[TensorFlow 官方文档翻译校对](https://github.com/xitu/tensorflow-docs)|翻译|10| |[建立更好的代码审查制度](https://juejin.im/entry/5934bafb2f301e006b055f57/detail)|翻译|10| |[某些2017年的 UX 趋势啊,扎心了](https://juejin.im/post/58cf5dc22f301e007e52fb2b)|校对|1| @@ -761,10 +769,11 @@ |[选择使用正确的 Markdown Parser](https://github.com/xitu/gold-miner/blob/master/TODO/choosing-right-markdown-parser.md)|翻译|4| |[Sketch的过去现在和未来](http://gold.xitu.io/entry/5721e59771cfe40057521079)|翻译|6| -## 译者:[ruixi](https://github.com/Ruixi) 历史贡献积分:59 当前积分:24 +## 译者:[ruixi](https://github.com/Ruixi) 历史贡献积分:64 当前积分:29 年度积分:5 |文章|类型|积分| |------|-------|-------| +|[另外 5 种关于视觉和认知间差异的绘画练习](https://juejin.im/post/5baa5b45f265da0ab915cb7f)|翻译|5| |[为企业应用设计更好的表格](https://juejin.im/post/5976ecb65188250c855facc2)|校对|1| |[用动效创建的可用性:动效中的用户体验宣言](https://juejin.im/post/595c77a66fb9a06bcb7f92ff)|翻译|12| |[从形式到功能,设计思维的改变](https://juejin.im/post/58fedca744d9040069f720e4)|翻译|5| @@ -793,10 +802,11 @@ |[在网站 Logo 上右击时提示下载网站的 Logo 素材下载](https://github.com/rainyear/gold-miner/blob/fabff8780118b24c61c57b13577cf971757c84d5/TODO/right-click-logo-show-logo-download-options.md)|校对|1| |[给产品经理的简易优先级法则](http://gold.xitu.io/entry/572ad1cc1532bc0065d5e36b)|翻译|7| -## 译者:[loneyiserror](https://github.com/LoneyIsError) 历史贡献积分:9 当前积分:9 +## 译者:[loneyiserror](https://github.com/LoneyIsError) 历史贡献积分:12 当前积分:12 年度积分:3 |文章|类型|积分| |------|-------|-------| +|[在 iOS 中使用 UITests 测试 Facebook 登录功能](https://juejin.im/post/5b90e3ae6fb9a05d00458ad8)|翻译|3| |[使用 Xcode 的 Scheme 来跑不同的测试集合](http://gold.xitu.io/entry/5723223f71cfe400575f4528)|校对|1| |[给 iOS 开发者的 GCD 用户手册](http://gold.xitu.io/entry/574e544971cfe4006bffa552)|翻译|7| |[如何检测 iPhone 处于低电量模式](http://gold.xitu.io/entry/574ff27d7db2a20055c7aec3)|校对|1| @@ -808,8 +818,8 @@ |[区块链中的共识机制 DBFT](https://juejin.im/entry/59cba85ef265da06507542a4/detail)|校对|2| |[漂亮的字体组合的秘密](https://gold.xitu.io/entry/58a3b99aac502e0068b0e8fa/)|校对|1| |[使用 WebSocket 和 CSS3 创造魔法](https://gold.xitu.io/entry/5894612a2f301e006900cfcc)|校对|1| -|[[[译]GitHub 是如何阻止网络暴力的](https://gold.xitu.io/entry/58652f6661ff4b00583ba924)|翻译|6| -|[[译]JavaScript 测试︰ 单元 vs 功能 vs 集成测试](https://gold.xitu.io/entry/584ab2dc128fe1006c7cdc11)|翻译|6| +|[GitHub 是如何阻止网络暴力的](https://gold.xitu.io/entry/58652f6661ff4b00583ba924)|翻译|6| +|[JavaScript 测试︰ 单元 vs 功能 vs 集成测试](https://gold.xitu.io/entry/584ab2dc128fe1006c7cdc11)|翻译|6| |[推荐文章『Introduction to Node & Express』入选]()|奖励|1| |[使用 React.js 的渐进式 Web 应用程序:第 3 部分 - 离线支持和网络恢复能力](http://gold.xitu.io/entry/58350983a22b9d006bbb90d3/)|校对|2| |[JavaScript 包管理器工作原理简介](http://gold.xitu.io/entry/5835a2c0c59e0d005772a62f/)|校对|1| @@ -817,7 +827,7 @@ |[设计,其实是一种产生共鸣的过程](http://gold.xitu.io/entry/57a41ffca341310063262054)|校对|1| |[移动应用设计新趋势](http://gold.xitu.io/entry/5796ee065bbb500063ef3535)|校对|1| |[字体加载策略全面指南](https://gold.xitu.io/entry/5790d1aa5bbb500063b8a747)|校对|1| -|[[译]移动开发中用 1x 视觉稿设计的好处](http://gold.xitu.io/entry/578840f9d342d30058a29968)|翻译|4| +|[移动开发中用 1x 视觉稿设计的好处](http://gold.xitu.io/entry/578840f9d342d30058a29968)|翻译|4| |[用户界面中的字体](http://gold.xitu.io/entry/57859bb9d342d300578b1ebf)|校对|2| |[UI 的黑暗面!暗色背景的优势](https://gold.xitu.io/entry/577c9385a633bd005be7fe7a)|校对|3| |[假如 Mac 上也有 iOS 应用?](http://gold.xitu.io/entry/577b87d27db2a20054e47ecc)|翻译|8| @@ -1403,10 +1413,13 @@ |[好的与坏的,Swift 语言面面观(一)](http://gold.xitu.io/entry/578c647a6be3ff006ce49e91)|校对|1| |[Android 中美腻的下划线](http://gold.xitu.io/entry/578705faa34131005b46e9c2)|校对|1| -## 译者:[tanglie1993](https://github.com/tanglie1993/) 历史贡献积分:75 当前积分:75 年度积分:11 +## 译者:[tanglie1993](https://github.com/tanglie1993/) 历史贡献积分:85 当前积分:85 年度积分:21 |文章|类型|积分| |------|-------|-------| +|[React Native 对 Flutter: 哪一个对创业公司更加友好?](https://juejin.im/post/5b949e5d5188255c791ae376)|翻译|3.5| +|[关于工程师和影响力](https://juejin.im/post/5b8f9a96f265da0ab33125b0)|校对|1.5| +|[写一个完整的 Flutter App 是什么感觉](https://juejin.im/post/5b7e18c4e51d4538cf53d1f8)|翻译|5| |[论 Android 中 Span 的正确打开方式](https://juejin.im/entry/5af401bb518825671776537d/)|翻译|2| |[使用 leanback 的 DiffCallback:和 DiffUtil 回调之间的区别](https://juejin.im/post/5a9218ee5188257a5c60892a)|校对|0.5| |[从 SQLite 逐步迁移到 Room](https://juejin.im/post/5a8c3a2cf265da4e761fd721)|校对|1| @@ -1698,10 +1711,17 @@ |[用 Swift 开发我的第一个 iOS 应用前,我想要知道这些内容](http://gold.xitu.io/entry/57c66667c4c9710061a57b3f/)|校对|2| |[你的设计应该「所见即所得」](http://gold.xitu.io/entry/57c5978f128fe1005fdf4858/)|校对|1| -## 译者:[steinliber](https://github.com/steinliber) 历史贡献积分:60 当前积分:15 年度积分:8 +## 译者:[steinliber](https://github.com/steinliber) 历史贡献积分:100 当前积分:40 年度积分:48 |文章|类型|积分| |------|-------|-------| +|[容器,虚拟机以及 Docker 的初学者入门介绍](https://juejin.im/entry/5bada97f6fb9a05d0e2e7014/detail)|翻译|8| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|7| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|13| +|[自然语言处理真是有趣](https://juejin.im/post/5b6d08e2f265da0f9c67cf0b)|校对|3| +|2018 年 7 月兑掘金桌垫和 GitHub 贴纸各 1 个|减去积分|15| +|[Erlang 之禅第一部分](https://juejin.im/post/5b2a16cce51d4558d726e8c9)|翻译|6| +|[使用 Go 语言的流模式来解析 DrugBank 的 XML(或者任何大的 XML 文件)](https://juejin.im/entry/5b06322a6fb9a07aab2a3d44)|翻译|3| |[为 Python Web 应用编写 Dockerfiles](https://juejin.im/post/5aaf8038518825557e783285)|校对|1| |[将项目迁移到 Python 3](https://juejin.im/post/5a9e3ff06fb9a028d2077434)|校对|2| |[标准化的包布局](https://juejin.im/post/5a8cf9dc6fb9a063475f8c15)|翻译|5| @@ -1835,10 +1855,11 @@ |[Swift 中的面向协议编程是如何点亮我的人生的](http://gold.xitu.io/entry/58044fc5a22b9d005b4f56b2)|翻译|4| |[Swift 3,这些陷阱在等你](http://gold.xitu.io/entry/57f6fa03816dfa0056a4d782/)|校对|1| -## 译者:[tina92](https://github.com/Tina92) 历史贡献积分:62 当前积分:12 年度积分:6 +## 译者:[tina92](https://github.com/Tina92) 历史贡献积分:62 当前积分:2 年度积分:6 |文章|类型|积分| |------|-------|-------| +|[2018 年 5 月兑 T 恤 L 号 1 个](#)|减去积分|10| |[2018 年 4 月兑 套头衫 M 号 1 个](#)|减去积分|20| |[不要害怕 Rebase](https://juejin.im/post/5ab1bdbe518825556e5df5f8)|校对|1| |[为 Django Framework 贡献你的力量并没有想象中的那么难](https://juejin.im/post/5a77cd6e5188257a79247fe1)|校对|1| @@ -2153,10 +2174,12 @@ |[我在手撕 SVG 条形图时踩过的定位坑](http://gold.xitu.io/entry/58306b428ac2470061b60ede/)|校对|1| |[构建 Android APP 一定要绕过的 30 个坑](http://gold.xitu.io/entry/58217b84570c350060bc40f8/)|校对|1| -## 译者:[aceleewinnie](https://github.com/AceLeeWinnie) 历史贡献积分:83 当前积分:38 年度积分:1.5 +## 译者:[aceleewinnie](https://github.com/AceLeeWinnie) 历史贡献积分:91 当前积分:46 年度积分:9.5 |文章|类型|积分| |------|-------|-------| +|[如何在数据科学中写出生产层面的代码?](https://juejin.im/post/5b7adb7751882542d63b2805)|校对|3| +|[如何使用纯函数式 JavaScript 处理脏副作用](https://juejin.im/post/5b82bdb351882542e241ed32)|校对|5| |[GraphQL API 设计最佳实践](https://juejin.im/post/5a3f494d6fb9a0450a678f8d)|翻译|1.5| |[使用 CSS Grid:以兼容不支持栅格化布局的浏览器](https://juejin.im/post/5a3f494d6fb9a0450a678f8d)|校对|2| |[找出可能影响性能的代码模式](https://juejin.im/post/59e87b89f265da433226b0f3)|校对|1.5| @@ -2403,10 +2426,11 @@ |[如何搭建安卓开发持续化集成环境(Ubuntu + Jenkins + SonarQube)](https://gold.xitu.io/entry/589d1c251b69e60059ba04b5)|翻译|7| |[SOLID 原则:权威指南](https://gold.xitu.io/entry/587f1c331b69e6005853ecfa)|校对|1| -## 译者:[fghpdf](https://github.com/fghpdf) 历史贡献积分:26.5 当前积分:26.5 +## 译者:[fghpdf](https://github.com/fghpdf) 历史贡献积分:26.5 当前积分:16.5 年度积分:0 |文章|类型|积分| |------|-------|-------| +|2018 年 7 月兑掘金桌垫 1 个|减去积分|10| |[fghpdf 翻译开源库 Python 100 个](#)|翻译|10| |[搭建个人深度学习平台](https://juejin.im/post/59be8e2b5188252c24746e9c)|校对|1.5| |[在你沉迷于包的海洋之前,还是了解一下运行时 Node.js 的本身](https://juejin.im/post/58cf4a3144d90400690b7be7/)|翻译|6| @@ -2416,10 +2440,12 @@ |[防守式编程的艺术](https://gold.xitu.io/entry/58980dbc1b69e6005997f069)|校对|1| |[为何我抵制使用缓存?](https://gold.xitu.io/entry/5884184f1b69e60058dc7fc6)|校对|1| -## 译者:[vuuihc](https://github.com/vuuihc) 历史贡献积分:36.5 当前积分:36.5 年度积分:3.5 +## 译者:[vuuihc](https://github.com/vuuihc) 历史贡献积分:45.5 当前积分:25.5 年度积分:12.5 |文章|类型|积分| |------|-------|-------| +|2018 年 9 月兑套头衫 M 号 1 个|减去积分|20| +|[[字幕翻译] Graham Dumpleton — Secrets of a WSGI master. — PyCon 2018](https://github.com/xitu/gold-miner/blob/master/TODO1/secrets-of-a-wsgi-master-pycon-2018.md) |翻译|9| |[用 Python 做一个 H5 游戏机器人](https://juejin.im/post/5aa9e7645188257bf550c745)|校对|1.5| |[2018 年要学习的优秀 JavaScript 库与知识](https://juejin.im/post/5a4e23f0f265da3e377bce4f)|校对|2| |[如何在 JavaScript 中使用 Generator?](https://juejin.im/post/5a44968df265da4335630fb9)|校对|1| @@ -2472,10 +2498,11 @@ |[ConstraintLayout (这到底是什么)](https://gold.xitu.io/entry/589dc5382f301e0069c3791a)|校对|1| |[Model-View-Intent 模式下的响应式应用 - 第一部分 - Model(模型)](https://gold.xitu.io/entry/589c91935c497d0056211a19)|校对|2| -## 译者:[chendongnan](https://github.com/ChenDongnan) 历史贡献积分:4 当前积分:4 +## 译者:[chendongnan](https://github.com/ChenDongnan) 历史贡献积分:5 当前积分:5 年度积分:1 |文章|类型|积分| |------|-------|-------| +|[Airbnb 中的 React Native](https://juejin.im/post/5b2c924ff265da59a401f050)|校对|1| |[结构体指针](https://juejin.im/post/59a60cc1f265da249a201ae9)|校对|1| |[ConstraintLayout ( 这到底是什么 ) (小贴士及小技巧) 第二部分](https://gold.xitu.io/entry/58aaedd5ac502e006974e868)|校对|1| |[iOS 开发者一定要知道的 14 个知识点](https://gold.xitu.io/entry/58a5727261ff4b006c432a7f/)|校对|2| @@ -2520,10 +2547,12 @@ |[Android 中的定时任务调度](https://juejin.im/post/595c9061f265da6c22119084)|校对|1| |[RxJava 中的 Subscriptions 是怎样泄露内存的](https://gold.xitu.io/entry/58b43c908ac2475ccb5d8e39)|校对|1| -## 译者:[zhangfe](https://github.com/ZhangFe) 历史贡献积分:40 当前积分:10 +## 译者:[zhangfe](https://github.com/ZhangFe) 历史贡献积分:55 当前积分:25 年度积分:15 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|9| |[React 应用性能调优](https://juejin.im/post/5a289cc6f265da43346fcc8d)|翻译|6| |[原子设计:如何设计组件系统](https://juejin.im/post/59780066f265da6c3433872f)|校对|2| |[兑换大猫一个]()|减去|30| @@ -2615,10 +2644,12 @@ |[再谈 CSS 中的代码味道](https://juejin.im/post/58c2034dac502e0062cf8e2a/)|校对|1| |[Google 是如何构建 web 框架的](https://gold.xitu.io/entry/58bcdda4128fe1007e5b44db/)|校对|1| -## 译者:[atuooo](https://github.com/atuooo) 历史贡献积分:47.5 当前积分:32.5 年度积分:7 +## 译者:[atuooo](https://github.com/atuooo) 历史贡献积分:51.5 当前积分:36.5 年度积分:11 |文章|类型|积分| |------|-------|-------| +|[构建流畅的交互界面](https://juejin.im/post/5b7e2b34e51d4538843612cc)|校对|3| +|[写一个完整的 Flutter App 是什么感觉](https://juejin.im/post/5b7e18c4e51d4538cf53d1f8)|校对|1| |[工作量证明 vs 权益证明:基本挖矿指南](https://juejin.im/post/5a9ded346fb9a028b617065a)|校对|2| |[Service workers:Progressive Web Apps 背后的小英雄](https://juejin.im/post/5a9c8c87f265da238c3a23e4)|校对|1| |[为什么你的 APP 在 Sketch 上看起来更好:探索 Sketch 和 iOS 的渲染差异](https://juejin.im/post/5a9572575188257a61326630)|校对|1| @@ -2664,10 +2695,14 @@ |[创建和使用 WebAssembly 组件](https://juejin.im/post/58d0bbe144d90400684c0f3a)|翻译|5| |[Web 分享 API 赋予浏览器原生分享能力](https://gold.xitu.io/entry/58c174ec44d9040069777f17/)|校对|1| -## 译者:[iridescentmia](https://github.com/IridescentMia) 历史贡献积分:32 当前积分:17 +## 译者:[Iridescentmia](https://github.com/IridescentMia) 历史贡献积分:40 当前积分:25 年度积分:10 |文章|类型|积分| |------|-------|-------| +|[JAVASCRIPT 日期权威指南](https://juejin.im/post/5b9b4b7bf265da0af6099ed8)|校对|2| +|[一行 JavaScript 代码竟然让 FT.com 网站慢了十倍](https://juejin.im/post/5b7bb6dfe51d4538bf55aa5f)|翻译|3| +|[布局的下一次革新会是怎样的](https://juejin.im/post/5b85586ce51d4538c77a9cc1)|校对|1.5| +|[使用原生 JavaScript 构建状态管理系统](https://juejin.im/post/5b763528e51d45559e3a5b64)|校对|1.5| |[Object Composition 中的宝藏](https://juejin.im/post/5a648ed0518825733201b818)|校对|2| |[2018 年 1 月兑换小猫一个]()|减去积分|15| |[模拟是一种代码异味(软件编写)(第十二部分)](https://juejin.im/post/5a2d363e6fb9a0450b6652c8)|校对|3| @@ -2744,10 +2779,12 @@ |[如何在 ChromeOS 下用 Go 搭建 Web 服务](https://juejin.im/post/58d9e1711b69e6006bc38b1a)|翻译|8| |[Pull request review 的十大错误](https://juejin.im/post/58ce3b3e61ff4b006c988f63)|校对|1| -## 译者:[sunui](https://github.com/sunui) 历史贡献积分:100.2 当前积分:10.2 +## 译者:[sunui](https://github.com/sunui) 历史贡献积分:103.7 当前积分:13.7 年度积分:3.5 |文章|类型|积分| |------|-------|-------| +|[设计 React 组件 API](https://juejin.im/post/5b545f0b6fb9a04fc93748ba)|校对|1| +|[为什么 VueX 是前端与 API 之间的完美接口](https://juejin.im/post/5b14d1bd6fb9a01e4508bfc1)|校对|2.5| |[2017 年 10 月兑 树莓派套餐 1 个](#)|减去积分|45| |[2017 年 10 月 翻译 JavaScript 97 个](#)|翻译|9.7| |[React 16 带来了什么以及对 Fiber 架构的解释](https://juejin.im/post/59de1b2a51882578c70c0833)|校对|1| @@ -2792,10 +2829,11 @@ |------|-------|-------| |[编写整洁 CSS 代码的黄金法则](https://juejin.im/post/58d34bed128fe1006caf8b6b)|校对|1| -## 译者:[bambooom](https://github.com/bambooom) 历史贡献积分:56.5 当前积分:56.5 年度积分:14.5 +## 译者:[bambooom](https://github.com/bambooom) 历史贡献积分:60 当前积分:60 年度积分:18 |文章|类型|积分| |------|-------|-------| +|[Font-size:一个出乎意料复杂的 CSS 属性](https://juejin.im/post/5b1292355188251377116617)|校对|3.5| |[使用 NumPy 和 Pandas 进行 Python 式数据清理](https://juejin.im/post/5ad57db3f265da239c7bd9fb)|翻译|8| |[为 JavaScript 程序员准备的 Flutter 指南](https://juejin.im/post/5ac43c536fb9a028da7cbd59)|校对|1.5| |[前端开发者指南 2018](https://github.com/xitu/front-end-handbook-2018)|翻译校对|3| @@ -2849,10 +2887,17 @@ |[你正在阅读的用户体验文章是不是在向你进行推销?](https://juejin.im/post/58d4c501a22b9d00645544d9)|校对|2| |[震惊,还可以用这种姿势学习编程](https://juejin.im/post/58d3ebd4128fe1006cb43722)|翻译|3| -## 译者:[lsvih](https://github.com/lsvih) 历史贡献积分:254 当前积分:213 年度积分:46.5 +## 译者:[lsvih](https://github.com/lsvih) 历史贡献积分:277.5 当前积分:211.5 年度积分:70 |文章|类型|积分| |------|-------|-------| +|[Python 的多线程与多进程](https://juejin.im/post/5b84f3086fb9a01a1a27cedb)|翻译|3.5| +|[使用 PhpFastCache 提升网站性能](https://juejin.im/post/5b54d01be51d4517c5649965)|翻译|5| +|[使用多重赋值与元组解包提升 Python 代码的可读性](https://juejin.im/post/5b3c2cf1e51d451925625b94)|翻译|5.5| +|2018 年 6 月兑服务器 1 个|减去积分|25| +|[什么是 JavaScript 生成器?如何使用生成器?](https://juejin.im/post/5b14faf2f265da6e4d5af3b9)|翻译|4| +|[可用但最不常见的 HTML5 标签](https://juejin.im/post/5b0026e8f265da0b8c253c2a)|翻译|2| +|[由 Node.js 发送 Web 推送通知](https://juejin.im/post/5afc32146fb9a07acf5657e7)|翻译|3.5| |[为 JavaScript 程序员准备的 Flutter 指南](https://juejin.im/post/5ac43c536fb9a028da7cbd59)|翻译|5| |[为 Python Web 应用编写 Dockerfiles](https://juejin.im/post/5aaf8038518825557e783285)|翻译|3| |[用 Python 做一个 H5 游戏机器人](https://juejin.im/post/5aa9e7645188257bf550c745)|翻译|4.5| @@ -3032,10 +3077,15 @@ |[Functor 与 Category (软件编写)(第六部分)](https://juejin.im/post/58f58d5da0bb9f006aac3e8d)|翻译|4| |[Reduce(软件编写)(第五部分)](https://juejin.im/post/58f44082da2f60005d3a3710)|翻译|4| -## 译者:[xunge0613](https://github.com/xunge0613) 历史贡献积分:49.5 当前积分:49.5 年度积分:9.5 +## 译者:[xunge0613](https://github.com/xunge0613) 历史贡献积分:59 当前积分:59 年度积分:19 |文章|类型|积分| |------|-------|-------| +|[无头渲染组件](https://juejin.im/post/5b5e919f51882519d3467f41)|校对|2| +|[设计 React 组件 API](https://juejin.im/post/5b545f0b6fb9a04fc93748ba)|校对|1| +|[使用 PhpFastCache 提升网站性能](https://juejin.im/post/5b54d01be51d4517c5649965)|校对|1| +|[如何设计移动应用的搜索功能?](https://juejin.im/post/5b692ca251882562b924a6c7)|翻译|3| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2.5| |[前端开发者指南 2018](https://github.com/xitu/front-end-handbook-2018)|翻译校对|8| |[HTML 5.2 有哪些新内容?](https://juejin.im/post/5a5dab05518825734107edda)|校对|1.5| |[Web 设计准则](https://juejin.im/post/59c9c6f66fb9a00a4d53eec7)|翻译|4| @@ -3053,10 +3103,11 @@ |[光速 React](https://juejin.im/post/591ad6b7128fe1005ce1123a)|校对|1| |[如何使用 HTTP Headers 来保护你的 Web 应用](https://juejin.im/post/58f5d3718d6d810057c18f75)|校对|2| -## 译者:[luoqiuyu](https://github.com/luoqiuyu) 历史贡献积分:3 当前积分:3 +## 译者:[luoqiuyu](https://github.com/luoqiuyu) 历史贡献积分:5.5 当前积分:5.5 年度积分:2.5 |文章|类型|积分| |------|-------|-------| +|[Android 的多摄像头支持](https://juejin.im/post/5b98fcf4f265da0ad13b66e0)|翻译|2.5| |[再谈如何安全地在 Android 中存储令牌](https://juejin.im/post/5938f81e5c497d006b6187ea)|校对|1| |[如何创建 BubblePicker – Android 多彩菜单动画](https://juejin.im/post/591e734d2f301e006bea5243)|校对|1| |[真正行动之前 你将一无所成](https://juejin.im/entry/58f6136861ff4b00580aef28)|校对|1| @@ -3178,10 +3229,16 @@ |------|-------|-------| |[开发者(也就是我)与Rx Observable 类的对话 [ Android RxJava2 ] ( 这到底是什么?) 第五部分](https://juejin.im/post/590ab4f7128fe10058f35119)|校对|1| -## 译者:[leviding](https://github.com/leviding) 历史贡献积分:145 当前积分:100 年度积分:66 +## 译者:[leviding](https://github.com/leviding) 历史贡献积分:185 当前积分:140 年度积分:106 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|20| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|8| +|[WWDC 2018:关于iOS 12、iPad Pro、新MacBooks或者更多产品的所有预测](https://juejin.im/post/5b056d485188256710601ecc)|校对|2| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|7| +|[30 分钟 Python 爬虫教程](https://juejin.im/post/5afab181f265da0b7452514a)|校对|1| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|2| |[更为详细的地图、导航和助手功能 —— Google I/O 2018 的 Android 应用更新](https://juejin.im/post/5aee74626fb9a07abb237f89)|校对|1| |[以太坊钱包详解](https://juejin.im/post/5ae2942ff265da0b886d23df)|校对|4| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|20| @@ -3193,7 +3250,7 @@ |[2017 年 JavaScript 发展状况回顾](https://juejin.im/entry/5a44a500f265da43271887f0/)|翻译|8| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|11| |[全新 Android 注入器 : Dagger 2(二)](https://juejin.im/post/5a3a1883f265da4321542fc1)|校对|1| -|[2017 年 11 月兑换 套头衫和各一个]()|减去积分|30| +|[2017 年 11 月兑换 套头衫和 T 恤各一个]()|减去积分|30| |[学习 JavaScript:9个常见错误阻碍你进步](https://juejin.im/post/59bb4a7c6fb9a00a53274cd7)|翻译|3| |[为什么 context.Value 重要,如何改进](https://juejin.im/post/59b20d6ff265da249517c14a)|校对|2| |[2017 年 9 月 6 日兑换小猫一个]()|减去积分|15| @@ -3232,10 +3289,13 @@ |[前端调试技巧与诀窍](https://juejin.im/post/5901e8d6a0bb9f0065e64f63)|校对|2| |[别让你的偏爱拖了后腿:快拥抱箭头函数吧!](https://juejin.im/post/59158c92a0bb9f005fd58fd7)|校对|2| -## 译者:[changkun](https://github.com/changkun) 历史贡献积分:138.5 当前积分:138.7 年度积分:39.5 +## 译者:[changkun](https://github.com/changkun) 历史贡献积分:154 当前积分:154 年度积分:55 |文章|类型|积分| |------|-------|-------| +|[GopherCon 2018:揭秘二叉查找树算法](https://juejin.im/post/5b94de9c5188255c5047076c)|翻译|6| +|[使用 Go 编写微服务及其 GraphQL 网关](https://juejin.im/post/5b94cf476fb9a05d26593f07)|翻译|4.5| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|5| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|30.5| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|9| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|6| @@ -3349,10 +3409,11 @@ |[用 Go 语言理解 Tensorflow](https://juejin.im/post/59420951128fe1006a1960f8)|校对|1| |[间复杂度 O(log n) 意味着什么?](https://juejin.im/entry/593f56528d6d810058a355f4/detail)|校对|1| -## 译者:[horizon13th](https://github.com/horizon13th) 历史贡献积分:49.6 当前积分:4.6 年度积分:10 +## 译者:[horizon13th](https://github.com/horizon13th) 历史贡献积分:56 当前积分:11 年度积分:16.4 |文章|类型|积分| |------|-------|-------| +|[Web 应用架构基础课](https://juejin.im/post/5b69a8eef265da0f926baa56)|翻译|6.4| |[2018 年 4 月兑树莓派套餐 1 个](#)|减去积分|45| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|10| |[网站设计综合指南](https://juejin.im/post/5a5abb97518825733f6df3d9)|翻译|12| @@ -3372,18 +3433,20 @@ |[统一样式语言](https://juejin.im/post/595311fa6fb9a06b9d5b5f08)|校对|2| |[理解 NPM 5 中的 lock 文件](https://juejin.im/post/5943849aac502e006b84ce07)|校对|1| -## 译者:[osirism](https://github.com/osirism) 历史贡献积分:5 当前积分:5 +## 译者:[osirism](https://github.com/osirism) 历史贡献积分:6 当前积分:6 年度积分:1 |文章|类型|积分| |------|-------|-------| +|[为什么 UX,UI,CX,IA,IxD 和其他类型的设计都是愚蠢的](https://juejin.im/post/5b3588f2e51d4558dd69a44c)|校对|1| |[以排印为本,从内容出发](https://juejin.im/entry/5965c5b26fb9a06ba025074c/detail)|校对|2| |[用动效创建的可用性:动效中的用户体验宣言](https://juejin.im/post/595c77a66fb9a06bcb7f92ff)|校对|2| |[为复杂产品制定设计规范](https://juejin.im/post/5944b8e55c497d006bdc261a)|校对|1| -## 译者:[cacppuccino](https://github.com/CACppuccino) 历史贡献积分:77.5 当前积分:32.5 年度积分:23 +## 译者:[cacppuccino](https://github.com/CACppuccino) 历史贡献积分:79.5 当前积分:34.5 年度积分:25 |文章|类型|积分| |------|-------|-------| +|[通过插图学习 Go 的并发](https://juejin.im/post/5b2cd078e51d4558b70ca399)|校对|2| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|5| |[第三方 SDK 的信任问题](https://juejin.im/post/5aa72655f265da238b7daa74)|翻译|8| |[前端开发者指南 2018](https://github.com/xitu/front-end-handbook-2018)|翻译校对|10| @@ -3450,10 +3513,18 @@ |------|-------|-------| |[如何在无损的情况下让图片变的更小](https://juejin.im/post/5959fbe0f265da6c2518d740)|校对|2| -## 译者:[swants](https://github.com/swants) 历史贡献积分:52.5 当前积分:32.5 年度积分:13.5 +## 译者:[swants](https://github.com/swants) 历史贡献积分:62 当前积分:2 年度积分:23 |文章|类型|积分| |------|-------|-------| +|2018 年 9 月兑套头衫(秋冬款)1 个|减去积分|20| +|[为什么你需要关注一下 Flutter](https://juejin.im/post/5b508c7cf265da0f955ccb09)|校对|1.5| +|[Flutter 实用指南:给初学者的 6 个小帖士](https://juejin.im/post/5b5534126fb9a04fcc449f4c)|校对|1.5| +|2018 年 8 月兑掘金桌垫 1 个|减去积分|10| +|2018 年 6 月兑掘金桌垫 1 个|减去积分|10| +|[Swift 中的内存泄漏](https://juejin.im/post/5b07a1c251882538914a5d3e)|校对|1.5| +|[Swift 中的 Playground 驱动开发](https://juejin.im/post/5b07665c6fb9a07ac5608d8a)|校对|3| +|[关于你的编程生涯的一些告诫](https://juejin.im/post/5b0256e36fb9a07aa767f5b4)|校对|2| |[轻松管理 Swift 项目中的不同环境](https://juejin.im/post/5af264a4f265da0b967233ff)|校对|1.5| |[Flutter 到底有多快?我开发了秒表应用来弄清楚。](https://juejin.im/post/5ad861566fb9a045ee01b48d)|校对|1.5| |[为什么你的 APP 在 Sketch 上看起来更好:探索 Sketch 和 iOS 的渲染差异](https://juejin.im/post/5a9572575188257a61326630)|校对|1| @@ -3567,10 +3638,11 @@ |------|-------|-------| |[深度学习的未来](https://juejin.im/post/597843506fb9a06ba4747db5)|校对|2| -## 译者:[sunshine940326](https://github.com/sunshine940326) 历史贡献积分:104.8 当前积分:64.8 年度积分:22 +## 译者:[sunshine940326](https://github.com/sunshine940326) 历史贡献积分:105.8 当前积分:65.8 年度积分:23 |文章|类型|积分| |------|-------|-------| +|[./dogs.html 和 /dogs.html 间有什么区别?](https://juejin.im/post/5ba7a5dfe51d450e4a1bc136)|校对|1| |[前端开发者指南 2018](https://github.com/xitu/front-end-handbook-2018)|翻译校对|5| |[使用 SVG 符号和 CSS 变量实现多彩图标](https://juejin.im/post/5a8409e06fb9a063342672b6)|校对|1.5| |[如果你想让用户回来,为什么前十分钟是至关重要的?](https://juejin.im/entry/5a7fe27f5188257a6854ce6a)|翻译|4| @@ -3609,10 +3681,11 @@ |[REST API 已死,GraphQL 长存](https://juejin.im/post/5991667b518825485d28dfb1)|翻译|12| |[所有你需要知道的关于完全理解 Node.js 事件循环及其度量](https://juejin.im/post/5984816a518825265674c8f6)|校对|1| -## 译者:[zyziyun](https://github.com/zyziyun) 历史贡献积分:25 当前积分:25 +## 译者:[zyziyun](https://github.com/zyziyun) 历史贡献积分:27 当前积分:27 年度积分:2 |文章|类型|积分| |------|-------|-------| +|[2018 来谈谈 Web 组件](https://juejin.im/post/5b780a98e51d4538980bf5cf)|校对|2| |[SQL 指引:如何写出更好的查询](https://juejin.im/post/59c1d402518825396f4f6321)|翻译|15| |[自定义 Babel 和 ESLint 插件是如何提高生产率与用户体验的](https://juejin.im/post/599519e06fb9a02499759f71)|校对|1| |[理解 Service Worker](https://juejin.im/post/599163316fb9a03c3c14d751)|翻译|8| @@ -3640,10 +3713,15 @@ |[深度学习系列4: 为什么你需要使用嵌入层](https://juejin.im/post/599183c6f265da3e2e5717d2)|校对|1| |[深度学习系列3 - CNNs 以及应对过拟合的详细探讨](https://juejin.im/post/598f25b15188257d8643173d)|翻译|4| -## 译者:[jasonxia23](https://github.com/jasonxia23) 历史贡献积分:45 当前积分:45 年度积分:29 +## 译者:[jasonxia23](https://github.com/jasonxia23) 历史贡献积分:50.5 当前积分:5.5 年度积分:34.5 |文章|类型|积分| |------|-------|-------| +|[通过安全浏览保护 WebView](https://juejin.im/post/5b0fb8a06fb9a00a25192a3f)|校对|0.5| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|2| +|[部署!=发布(第二部分)](https://juejin.im/post/5b00d2fa6fb9a07a9a1120e9)|校对|1| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|2| +|[2018 年 5 月兑 树莓派套餐 1 个]()|减去积分|45| |[Web 应用的未来:Heroku vs Docker](https://juejin.im/post/5ae7dae5518825670f7ba367)|校对|1| |[那些好玩却没有被 ECMAScript 2017 采纳的提案](https://juejin.im/post/5ae920fd51882567127852e7)|校对|2| |[使用 React, Redux, and SVG 开发游戏 - 第3部分](https://juejin.im/post/5af2a4d06fb9a07aa114302b)|校对|3| @@ -3691,10 +3769,11 @@ |[AI 能解决你的 UX 设计问题吗?](https://juejin.im/post/5992aa306fb9a03c445df727)|校对|1| |[REST API 已死,GraphQL 长存](https://juejin.im/post/5991667b518825485d28dfb1)|校对|2| -## 译者:[calpa](https://github.com/calpa) 历史贡献积分:5 当前积分:5 +## 译者:[calpa](https://github.com/calpa) 历史贡献积分:7.5 当前积分:7.5 年度积分:2.5 |文章|类型|积分| |------|-------|-------| +|[以申请大学流程来解释 JavaScript 的 filter](https://juejin.im/post/5b9f09685188255c5e66d60c)|翻译|2.5| |[原生 JavaScript 值得学习吗?答案是肯定的](https://juejin.im/post/59a535bd6fb9a0249975cb9b)|校对|1| |[Redux 有多棒?](https://juejin.im/entry/599cff2551882524397f95c1/detail)|校对|2| |[理解 Service Worker](https://juejin.im/post/599163316fb9a03c3c14d751)|校对|2| @@ -4008,10 +4087,13 @@ |[cnJason 翻译开源库 Java 215 个](#)|翻译|21.5| |[cnJason 翻译开源库 Java 100 个](#)|翻译|10| -## 译者:[RickeyBoy](https://github.com/RickeyBoy) 历史贡献积分:14.5 当前积分:14.5 年度积分:4 +## 译者:[RickeyBoy](https://github.com/RickeyBoy) 历史贡献积分:26 当前积分:26 年度积分:15.5 |文章|类型|积分| |------|-------|-------| +|[你 Ladar 中该珍藏的:iOS 布局语言](https://juejin.im/post/5b84fe97f265da437c43422f)|校对|1.5| +|[重写 loadView() 方法使 Swift 视图代码更加简洁](https://juejin.im/post/5b68fe5b6fb9a04fd16039c0)|翻译|3| +|[Swift 中的内存泄漏](https://juejin.im/post/5b07a1c251882538914a5d3e)|翻译|7| |[避免 Swift 单元测试中的强制解析](https://juejin.im/post/5a9ce4ac6fb9a028b547602e)|翻译|4| |[Swift 上的高性能数组](https://juejin.im/post/59e84129f265da43128005ed)|校对|0.5| |[翻译开源库 iOS 100 个](#)|翻译|10| @@ -4212,10 +4294,12 @@ |[JavaScript 如何工作:在 V8 引擎里 5 个优化代码的技巧](https://juejin.im/post/5a102e656fb9a044fd1158c6)|校对|2| |[Vue Report 2017](https://juejin.im/post/5a138fae5188254d28732899)|翻译|4| -## 译者:[caoyi0905](https://github.com/caoyi0905) 历史贡献积分:25.5 当前积分:15.5 +## 译者:[caoyi0905](https://github.com/caoyi0905) 历史贡献积分:30.5 当前积分:20.5 年度积分:5 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2| +|[使用 Python 实现接缝裁剪算法](https://juejin.im/post/5b46ee8e6fb9a04fc436ad60)|翻译|3| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|8| |[TensorFlow 官方文档翻译校对](https://github.com/xitu/tensorflow-docs)|翻译|10| |[ES6 中的元编程: 第三部分 - 代理(Proxies)](https://juejin.im/post/5a0f05496fb9a04508093ac4)|校对|1.5| @@ -4322,10 +4406,12 @@ |[JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧](https://juejin.im/post/5a221d35f265da43356291cc)|翻译|10| |[JavaScript 如何工作:在 V8 引擎里 5 个优化代码的技巧](https://juejin.im/post/5a102e656fb9a044fd1158c6)|翻译|7| -## 译者:[tvChan](https://github.com/tvChan) 历史贡献积分:36.5 当前积分:2.5 年度积分:18.5 +## 译者:[tvChan](https://github.com/tvChan) 历史贡献积分:37.5 当前积分:-1.5 年度积分:19.5 |文章|类型|积分| |------|-------|-------| +|[下一代包管理工具](https://juejin.im/post/5ba9830fe51d450e4d2fe208)|校对|1| +|2018 年 9 月兑掘金水杯 1 个|减去积分|5| |[2018 年 3 月兑小猫 1 个、GitHub 贴纸 pack3 一个、掘金笔记本 1 个和比特币 3 个]()|减去积分|34| |[保持 webpack 快速运行的诀窍:一本提高构建性能的现场指导手册](https://juejin.im/post/5a6adc6d5188257350517033)|校对|3| |[2018 前端性能优化清单——第一部分](https://juejin.im/post/5a67e49df265da3e377c2d59)|翻译|8| @@ -4340,19 +4426,30 @@ |[你想学 React.js 吗?](https://juejin.im/post/5a2f8ea5f265da43305e6f6b)|翻译|3| |[JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧](https://juejin.im/post/5a221d35f265da43356291cc)|校对|4| -## 译者:[athena0304](https://github.com/athena0304) 历史贡献积分:11 当前积分:11 +## 译者:[athena0304](https://github.com/athena0304) 历史贡献积分:63.5 当前积分:33.5 年度积分:55.5 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|8| +|[The JavaScript Tutorial 审校](https://github.com/xitu/javascript-tutorial-en)|奖励|10| +|2018 年 8 月兑 Octoplush 1 个|减去积分|30| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|4| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|18.5| |[JavaScript 是如何工作的:深入 WebSockets 和使用了 SSE+ 的 HTTP/2,如何在二者当中做出选择](https://juejin.im/post/5a522647518825732d7f6cbb)|校对|3| |[模拟是一种代码异味(软件编写)(第十二部分)](https://juejin.im/post/5a2d363e6fb9a0450b6652c8)|校对|3| |[如何禁用链接](https://juejin.im/post/5a240aa66fb9a0452577f06a)|校对|1| |[JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧](https://juejin.im/post/5a221d35f265da43356291cc)|校对|4| -## 译者:[sakila1012](https://github.com/sakila1012) 历史贡献积分:29.5 当前积分:29.5 年度积分:27.5 +## 译者:[sakila1012](https://github.com/sakila1012) 历史贡献积分:36.5 当前积分:20.5 年度积分:34.5 |文章|类型|积分| |------|-------|-------| +|[现代浏览器内部揭秘(第一部分)](https://juejin.im/post/5b9b0932e51d450e9059c16a)|校对|1.5| +|[自然语言处理真是有趣](https://juejin.im/post/5b6d08e2f265da0f9c67cf0b)|校对|2| +|2018 年 6 月兑掘金笔记本 2 个|减去积分|16| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3.5| |[使用 Web3 和 Vue.js 来创建你的第一个以太坊 dAPP(第三部分)](https://juejin.im/post/5ac36e1f518825556a729c3f)|翻译|4| |[20 年后比特币将会变成什么样 - 第 3 部分](https://juejin.im/post/5a9ce3715188255585070586)|翻译|6| |[教你使用 CSS 计数器](https://juejin.im/post/5a811c6251882528b63ff8d7)|翻译|2.5| @@ -4389,10 +4486,11 @@ |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|15| |[TensorFlow 官方文档翻译校对](https://github.com/xitu/tensorflow-docs)|翻译|6| -## 译者:[charsdavy](https://github.com/charsdavy) 历史贡献积分:26.5 当前积分:26.5 +## 译者:[charsdavy](https://github.com/charsdavy) 历史贡献积分:27.5 当前积分:27.5 年度积分:1 |文章|类型|积分| |------|-------|-------| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|1| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|12| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|6| |[Xcode 环境配置最佳实践](https://juejin.im/post/5a4f120cf265da3e3d48f1f6)|校对|1.5| @@ -4417,10 +4515,15 @@ |------|-------|-------| |[TensorFlow 官方文档翻译校对](https://github.com/xitu/tensorflow-docs)|翻译|6| -## 译者:[jonjia](https://github.com/jonjia) 历史贡献积分:46.5 当前积分:46.5 年度积分:31 +## 译者:[jonjia](https://github.com/jonjia) 历史贡献积分:79.5 当前积分:79.5 年度积分:64 |文章|类型|积分| |------|-------|-------| +|[ECMAScript 修饰器微指南](https://juejin.im/post/5b543d8af265da0f4a4e711f)|翻译|7| +|[用 React 和 Vue 创建了两个完全相同的应用后,发现了这些差异](https://juejin.im/post/5b63f50a5188253128337110)|翻译|6| +|[单元素组件模式简介](https://juejin.im/post/5b445d79e51d4519596b5f87)|翻译|5.5| +|[⚛ React 状态管理工具博物馆](https://juejin.im/post/5afd18cf6fb9a07acb3d13ac)|翻译|7.5| +|[使用 Puppeteer 和 Jest 测试你的 React 应用](https://juejin.im/post/5af90988518825426a1fcc2e)|翻译|7| |[如何写出更好的 React 代码](https://juejin.im/post/5ae975d26fb9a07aa92588b7)|翻译|5| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| |[React 中的 Immutability:可变对象并没有什么问题](https://juejin.im/post/5ad807b36fb9a045d639ad18)|翻译|4.5| @@ -4460,10 +4563,13 @@ |------|-------|-------| |[JavaScript 工作原理:内存管理 + 处理常见的 4 种内存泄漏](https://juejin.im/post/5a2559ae6fb9a044fe4634ba)|翻译|8| -## 译者:[LeopPro](https://github.com/LeopPro) 历史贡献积分:37 当前积分:37 年度积分:24 +## 译者:[LeopPro](https://github.com/LeopPro) 历史贡献积分:52 当前积分:44 年度积分:39 |文章|类型|积分| |------|-------|-------| +|2018 年 7 月兑集智优盘 1 个|减去积分|8| +|[支撑现代存储系统的算法](https://juejin.im/post/5b10f80b5188257d92206851)|翻译|12| +|[BigQuery 中的比特币:使用公共数据分析区块链](https://juejin.im/post/5afc17b16fb9a07aaf35673a)|翻译|3| |[使用 Rust 开发一个简单的 Web 应用,第 4 部分 —— CLI 选项解析](https://juejin.im/post/5a8d6aad6fb9a0634514c6c6)|翻译|8| |[使用 Rust 开发一个简单的 Web 应用,第 3 部分 —— 整合](https://juejin.im/post/5a8d6a436fb9a0635172803f)|翻译|5| |[使用 Rust 开发一个简单的 Web 应用,第 2b 部分](https://juejin.im/post/5a8196716fb9a0635c0478d7)|翻译|6| @@ -4517,10 +4623,19 @@ |[如何取消你的 Promise?](https://juejin.im/post/5a32705a6fb9a045117127fa)|校对|1| |[你想学 React.js 吗?](https://juejin.im/post/5a2f8ea5f265da43305e6f6b)|校对|2| -## 译者:[Colafornia](https://github.com/Colafornia) 历史贡献积分:65 当前积分:44 年度积分:56.5 +## 译者:[Colafornia](https://github.com/Colafornia) 历史贡献积分:88 当前积分:22 年度积分:79.5 |文章|类型|积分| |------|-------|-------| +|[现代浏览器内部揭秘(第一部分)](https://juejin.im/post/5b9b0932e51d450e9059c16a)|翻译|6| +|2018 年 9 月兑 Google DIY 纸板音箱 1 个|减去积分|45| +|[我们距离 Babel 7.0 发布很近了。这里是所有我们一直在做的很酷的事情。](https://juejin.im/post/5b174f3e518825139b18e1f0)|校对|2.5| +|[为何前端开发如此不稳定](https://juejin.im/post/5b1f2f1ae51d4506894983ae)|翻译|5| +|[怎样(以及为什么要)保持你的 Git 提交记录的整洁](https://juejin.im/post/5b29060ee51d4558cd2adac0)|校对|1.5| +|[Font-size:一个出乎意料复杂的 CSS 属性](https://juejin.im/post/5b1292355188251377116617)|校对|1.5| +|[JavaScript 是如何工作的:对比 WebAssembly + 为什么在某些场景下它比 JavaScript 更合适](https://juejin.im/post/5b0526e751882507c36d0167)|校对|2| +|[⚛ React 状态管理工具博物馆](https://juejin.im/post/5afd18cf6fb9a07acb3d13ac)|校对|2.5| +|[JavaScript 是如何工作的:CSS 和 JS 动画背后的原理 + 如何优化性能](https://juejin.im/post/5afaea6b6fb9a07aa34a6a74)|校对|2| |[那些好玩却没有被 ECMAScript 2017 采纳的提案](https://juejin.im/post/5ae920fd51882567127852e7)|翻译|6| |[如何逃离 async/await 地狱](https://juejin.im/post/5aefbb48f265da0b9b073c40)|翻译|3| |[React 中的 Immutability:可变对象并没有什么问题](https://juejin.im/post/5ad807b36fb9a045d639ad18)|校对|2| @@ -4550,10 +4665,12 @@ |------|-------|-------| |[从 Gzip 压缩 SVG 说起 — 论如何减小资源文件的大小](https://juejin.im/post/5a30a7fdf265da4309452517)|校对|1| -## 译者:[noahziheng](https://github.com/noahziheng) 历史贡献积分:22 当前积分:22 年度积分:18.5 +## 译者:[noahziheng](https://github.com/noahziheng) 历史贡献积分:26.5 当前积分:26.5 年度积分:23 |文章|类型|积分| |------|-------|-------| +|[SpaceAce 了解一下,一个新的前端状态管理库](https://juejin.im/post/5b7653c96fb9a009c72cb7af)|翻译|3.5| +|[如何向带有插槽的 React 组件传递多个 Children](https://juejin.im/post/5b72935a6fb9a009724b3e4e)|校对|1| |[Facebook 的 AI 万金油:StarSpace 神经网络模型简介](https://juejin.im/post/5a83af7c6fb9a0633c661404)|翻译|5| |[JavaScript 中的私有变量](https://juejin.im/post/5a8e9b6d5188257a5f1ed826)|翻译|4| |[你前所未知的 JavaScript 调试器](https://juejin.im/post/5a7e94a66fb9a0633b2105ed)|校对|1| @@ -4680,10 +4797,12 @@ |[做好准备:新的 V8 即将到来,Node.js 的性能正在改变。](https://juejin.im/post/5aaf755851882555627d16e5)|校对|3| |[论原子 CSS 的日益普及](https://juejin.im/post/5a4387af6fb9a045186b04d1)|校对|1.5| -## 译者:[lcx-seima](https://github.com/lcx-seima) 历史贡献积分:28 当前积分:28 年度积分:23 +## 译者:[lcx-seima](https://github.com/lcx-seima) 历史贡献积分:37.5 当前积分:37.5 年度积分:32.5 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|4.5| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3.5| |[React & Redux 顶级开发伴侣](https://juejin.im/post/5acae8dc6fb9a028c06b1c4c)|翻译|4| @@ -4737,10 +4856,14 @@ |[Face ID 对易用性意味着什么](https://juejin.im/post/5a41d01cf265da432c241943)|校对|1| |[自动化持续集成/持续分发,以节省更多时间编写代码](https://juejin.im/post/5a44aab86fb9a044ff31c418)|翻译|3| -## 译者:[hanliuxin5](https://github.com/hanliuxin5) 历史贡献积分:52.5 当前积分:52.5 年度积分:52.5 +## 译者:[hanliuxin5](https://github.com/hanliuxin5) 历史贡献积分:57.5 当前积分:47.5 年度积分:57.5 |文章|类型|积分| |------|-------|-------| +|[Android 的多摄像头支持](https://juejin.im/post/5b98fcf4f265da0ad13b66e0)|校对|1| +|2018 年 9 月兑掘金桌垫 1 个|减去积分|10| +|[在 Android P 中使用默认的 TLS 来保护你的用户](https://juejin.im/post/5b29030551882574b4094a6f)|翻译|3| +|[预测你的游戏的货币化未来](https://juejin.im/post/5ad1d3b6f265da238f12fafa)|校对|1| |[利用 Android 构建 TV 的未来](https://juejin.im/post/5a964061f265da4e914b8f88)|校对|1| |[使用MVI构建响应式 APP — 第七部分 — TIMING (SINGLELIVEEVENT 问题)](https://juejin.im/post/5ac47f0bf265da23793c60be)|校对|1| |[Google Play 控制台指南](https://juejin.im/post/5ac1c3546fb9a028d444bc2b)|校对|4| @@ -4765,10 +4888,12 @@ |[函数式 Java 到函数式 Kotlin 的转换](https://juejin.im/post/5a52fac96fb9a01c9657fa4e)|校对|1| |[Android MVP 架构必要知识:第三部分(Dialog,ViewPager,RecyclerView 以及 Adapters)](https://juejin.im/post/5a45fe846fb9a0450c49bba0)|校对|1| -## 译者:[pot-code](https://github.com/pot-code) 历史贡献积分:51 当前积分:51 年度积分:46 +## 译者:[pot-code](https://github.com/pot-code) 历史贡献积分:63.5 当前积分:63.5 年度积分:58.5 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|4| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|8.5| |[用户账户、授权和密码管理的 12 个最佳方法](https://juejin.im/post/5a9e4db151882555677e0dc1)|校对|2| |[前端开发者指南 2018](https://github.com/xitu/front-end-handbook-2018)|翻译校对|1| |[CSS Grid 之列宽自适应:`auto-fill` vs `auto-fit`](https://juejin.im/post/5a8c25d66fb9a0634d27b73b)|翻译|6| @@ -4783,20 +4908,24 @@ |[JavaScript 自动化爬虫入门指北(Chrome + Puppeteer + Node JS)](https://juejin.im/post/5a4e1038f265da3e591e1247)|翻译|7| |[智对订阅难点:教你如何应对工作中 10 种常见订阅问题](https://juejin.im/post/5a406644f265da430d583cb7)|翻译|5| -## 译者:[Wangalan30](https://github.com/Wangalan30) 历史贡献积分:12 当前积分:12 年度积分:10.5 +## 译者:[Wangalan30](https://github.com/Wangalan30) 历史贡献积分:17.5 当前积分:17.5 年度积分:16 |文章|类型|积分| |------|-------|-------| +|[在 iOS 中使用 UITests 测试 Facebook 登录功能](https://juejin.im/post/5b90e3ae6fb9a05d00458ad8)|校对|2| +|[如何设计移动应用的搜索功能?](https://juejin.im/post/5b692ca251882562b924a6c7)|校对|1.5| +|[苹果公司如何修复 3D Touch](https://juejin.im/post/5b35e5886fb9a00e3642724f)|翻译|2| |[建立一个像科幻小说一样的虚拟世界:设计一个全球性的虚拟世界](https://juejin.im/post/5ad19dcdf265da2389262784)|校对|1.5| |[用户账户、授权和密码管理的 12 个最佳方法](https://juejin.im/post/5a9e4db151882555677e0dc1)|翻译|6| |[一文教你预测 APP 未来的货币化情况](https://juejin.im/post/5a7a94d36fb9a0634d2793c6)|校对|2| |[如何紧跟未来的设计趋势:15 个让你永远不过时的资料](https://juejin.im/post/5a52d2226fb9a01c9525ebbe)|校对|1| |[智对订阅难点:教你如何应对工作中 10 种常见订阅问题](https://juejin.im/post/5a406644f265da430d583cb7)|校对|1.5| -## 译者:[IllllllIIl](https://github.com/IllllllIIl) 历史贡献积分:25 当前积分:0 年度积分:23.5 +## 译者:[IllllllIIl](https://github.com/IllllllIIl) 历史贡献积分:30 当前积分:5 年度积分:28.5 |文章|类型|积分| |------|-------|-------| +|[移动游戏发行的新时代](https://juejin.im/post/5af9acf851882542877345ef)|翻译|5| |[2018 年 5 月兑 Google Play 开发者账号 1 个]()|减去积分|25| |[如何用 Python 写一个 Discord 机器人](https://juejin.im/post/5ac1b9796fb9a028c42e5a61)|校对|0.5| |[建立一个像科幻小说一样的虚拟世界:设计一个全球性的虚拟世界](https://juejin.im/post/5ad19dcdf265da2389262784)|校对|1.5| @@ -4808,10 +4937,13 @@ |[开发者也是用户 - 简介](https://juejin.im/post/5a6536e6518825734216ff06)|校对|1| |[智对订阅难点:教你如何应对工作中 10 种常见订阅问题](https://juejin.im/post/5a406644f265da430d583cb7)|校对|1.5| -## 译者:[pkuwwt](https://github.com/pkuwwt) 历史贡献积分:97 当前积分:52 年度积分:90 +## 译者:[pkuwwt](https://github.com/pkuwwt) 历史贡献积分:100.5 当前积分:10.5 年度积分:93.5 |文章|类型|积分| |------|-------|-------| +|[为 APP 设计通知提醒](https://juejin.im/post/5ba31ee3e51d450e4115500b)|校对|3.5| +|2018 年 9 月兑 Google DIY 纸板音箱 1 个|减去积分|45| +|[React Native:回顾 Udacity 移动工程团队的使用历程](https://juejin.im/post/5b421b606fb9a04fd15ff802)|翻译|礼物| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|5| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译|15| |[2018 年 2 月兑 树莓派套餐 1 个]()|减去积分|45| @@ -4970,10 +5102,13 @@ |[我未曾见过的 JS 特性](https://juejin.im/post/5a723216f265da3e2e62d0a5)|校对|1| |[通过后台数据预获取技术实现性能提升](https://juejin.im/post/5a71a9c3f265da3e2f01459b)|校对|2| -## 译者:[foxxnuaa](https://github.com/foxxnuaa) 历史贡献积分:25.5 当前积分:25.5 年度积分:25.5 +## 译者:[foxxnuaa](https://github.com/foxxnuaa) 历史贡献积分:28.5 当前积分:28.5 年度积分:28.5 |文章|类型|积分| |------|-------|-------| +|[Tab Bar 就是新的汉堡菜单](https://juejin.im/post/5b61684fe51d451986517e31)|校对|1.5| +|[React Native 中使用转场动画!](https://juejin.im/post/5b69467e5188251b3c3b4e4e)|校对|0.5| +|[重写 loadView() 方法使 Swift 视图代码更加简洁](https://juejin.im/post/5b68fe5b6fb9a04fd16039c0)|校对|1| |[这可能是 2018 年最好的一篇 PHP 性能测评(包含 5.6 到 7.2,以及HHVM)](https://juejin.im/post/5ab4bac9518825557b4caec6)|校对|3| |[带你了解以太坊第 2 层扩容方案:State Channels、Plasma 和 Truebit](https://juejin.im/post/5aa1f63c518825558804f85b)|校对|4| |[使用 Web3 和 Vue.js 来创建你的第一个以太坊 dAPP(第一部分)](https://juejin.im/post/5aa7a8d2518825558805128d)|翻译|3.5| @@ -5044,10 +5179,20 @@ |[为 Django Framework 贡献你的力量并没有想象中的那么难](https://juejin.im/post/5a77cd6e5188257a79247fe1)|翻译|3| |[大话(Summer vs winter Observable)之我与 Rx Observable[Android RxJava2](这是什么鬼)第六话](https://juejin.im/post/5a7cfdaaf265da4e7f35901a)|校对|3| -## 译者:[LeeSniper](https://github.com/LeeSniper) 历史贡献积分:13.5 当前积分:13.5 年度积分:13.5 +## 译者:[LeeSniper](https://github.com/LeeSniper) 历史贡献积分:41 当前积分:41 年度积分:41 |文章|类型|积分| |------|-------|-------| +|[测试原生,Flutter 和 React Native 移动开发之间的性能差异。](https://juejin.im/post/5b62ccef6fb9a04fc564c11b)|翻译|3| +|[Airbnb 在 React Native 上下的赌注(二):技术细节](https://juejin.im/post/5b3b40a26fb9a04fab44e797)|校对|1.5| +|[绘图技巧的快速入门之:6 个绘图练习,让你立即上手!](https://juejin.im/post/5b39823fe51d4558ca674cff)|校对|2.5| +|[在 SnackBar,Navigation 和其他事件中使用 LiveData(SingleLiveEvent 案例)](https://juejin.im/post/5b2b1b2cf265da5952314b63)|校对|2| +|[如何用 Android vitals 解决应用程序的质量问题](https://juejin.im/post/5b3229a7f265da598c09dd4a)|翻译|7| +|[带你领略 ConstraintLayout 1.1 的新功能](https://juejin.im/post/5b013e6f51882542c760dc7b)|校对|1.5| +|[情景活动感知识别的 Transition API 新特性面向全体开发者开放](https://juejin.im/post/5b21cfe16fb9a01e561a4b26)|校对|0.5| +|[Awesome Flutter 翻译](https://github.com/xitu/awesome-flutter)|翻译|6| +|[Windows Insets 和 Fragment 过渡动画](https://juejin.im/post/5b05206f6fb9a07ac162c9af)|翻译|2| +|[如何在安卓应用中使用 TensorFlow Mobile](https://juejin.im/post/5afb8dc5518825426c690236)|校对|1.5| |[建立一个像科幻小说一样的虚拟世界:设计一个全球性的虚拟世界](https://juejin.im/post/5ad19dcdf265da2389262784)|翻译|5| |[利用 Android 构建 TV 的未来](https://juejin.im/post/5a964061f265da4e914b8f88)|校对|1| |[使用 leanback 的 DiffCallback:和 DiffUtil 回调之间的区别](https://juejin.im/post/5a9218ee5188257a5c60892a)|翻译|1| @@ -5076,10 +5221,11 @@ |------|-------|-------| |[低估中国区块链,对你是个好消息](https://juejin.im/post/5a7943e2f265da4e9d221119)|翻译|12| -## 译者:[dreamhb](https://github.com/dreamhb) 历史贡献积分:1 当前积分:1 +## 译者:[dreamhb](https://github.com/dreamhb) 历史贡献积分:2.5 当前积分:2.5 年度积分:2.5 |文章|类型|积分| |------|-------|-------| +|[Android 支持库 21.1.0 中的 Loaders](https://juejin.im/post/5b06139a6fb9a07aa43c966f)|翻译|1.5| |[新兴技术领域中以用户为中心的设计的应用](https://juejin.im/post/5a682282f265da3e283a2cca)|校对|1| ## 译者:[baileilei](https://github.com/baileilei) 历史贡献积分:1 当前积分:1 @@ -5117,10 +5263,17 @@ |[json — JavaScript 对象表示法](https://juejin.im/post/5a9432ae5188257a5c6092b0)|翻译|2.5| |[教你使用 CSS 计数器](https://juejin.im/post/5a811c6251882528b63ff8d7)|校对|0.5| -## 译者:[weberpan](https://github.com/weberpan) 历史贡献积分:15 当前积分:15 年度积分:15 +## 译者:[weberpan](https://github.com/weberpan) 历史贡献积分:39 当前积分:39 年度积分:39 |文章|类型|积分| |------|-------|-------| +|[探索 SMACSS:可扩展的模块化 CSS 框架](https://juejin.im/post/5ba234c85188255c38535a47)|校对|5| +|[2018 来谈谈 Web 组件](https://juejin.im/post/5b780a98e51d4538980bf5cf)|翻译|6| +|[从零开始,在 Redux 中构建时间旅行式调试](https://juejin.im/post/5b24c0bce51d4558ba1a5584)|翻译|4| +|[Vue.js 还是 React?你会选择哪一个?为什么?](https://juejin.im/post/5b25b3a16fb9a00e70686180)|校对|2.5| +|[什么是 JavaScript 生成器?如何使用生成器?](https://juejin.im/post/5b14faf2f265da6e4d5af3b9)|校对|1.5| +|[JavaScript 是如何工作的:Web 推送通知的机制](https://juejin.im/post/5b002766518825429d1f90bc)|校对|3| +|[使用 Puppeteer 和 Jest 测试你的 React 应用](https://juejin.im/post/5af90988518825426a1fcc2e)|校对|2| |[如何写出更好的 React 代码](https://juejin.im/post/5ae975d26fb9a07aa92588b7)|校对|2| |[深入浅出 JavaScript 关键词 — this](https://juejin.im/post/5aefe76e6fb9a07abc29d4a1)|翻译|10| |[React 应用中 “神奇” 的多态—性能隐患](https://juejin.im/post/5aa5ebe16fb9a028d04314b0)|校对|1| @@ -5136,10 +5289,11 @@ |[前端开发者指南 2018](https://github.com/xitu/front-end-handbook-2018)|翻译校对|7| |[用 Java 创造你的第一个区块链 — 第一部分](https://juejin.im/post/5a8ed1d75188257a836c4218)|校对|1.5| -## 译者:[94haox](https://github.com/94haox) 历史贡献积分:6.5 当前积分:6.5 年度积分:6.5 +## 译者:[94haox](https://github.com/94haox) 历史贡献积分:9.5 当前积分:9.5 年度积分:9.5 |文章|类型|积分| |------|-------|-------| +|[一份在你的 iPhone 上平衡实用和美观的指南](https://juejin.im/post/5b4c0d0ce51d4519503b1e67)|翻译|3| |[iOS App 上一种灵活的路由方式](https://juejin.im/post/5aafc278f265da23805973e3)|校对|1| |[为你的 React 应用制作 SVG 图标库](https://juejin.im/post/5a9e40fe518825558a061cfd)|校对|0.5| |[通过 Quick 和 Nimble 在 Swift 中进行测试驱动开发](https://juejin.im/post/5a93635d5188257a7924c5d5)|翻译|3.5| @@ -5166,10 +5320,41 @@ |[使用 MVI 开发响应式 APP - 第三部分 - 状态减少(state reducer)](https://juejin.im/post/5a955c50f265da4e853d856a)|翻译|4| |[二十年后比特币会变成什么样?- 第二部分](https://juejin.im/post/5a955721f265da4e826377b6)|翻译|6| -## 译者:[Starriers](https://github.com/Starriers) 历史贡献积分:176.5 当前积分:146.5 年度积分:176.5 +## 译者:[Starriers](https://github.com/Starriers) 历史贡献积分:347 当前积分:317 年度积分:347 |文章|类型|积分| |------|-------|-------| +|[使用 Node 和 OAuth 2.0 构建一个简单的 REST API](https://juejin.im/post/5bb1d3f95188255c6a044d9a)|翻译|5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|12| +|[使用 Nexmo 和微软语音翻译 API 构建 Babel 鱼](https://juejin.im/post/5b8a78a3e51d4538a515cbda)|翻译|8| +|[如何将 Stackdriver 连接到智能家居服务器以进行错误记录](https://juejin.im/post/5b8f8df7e51d450e9f66d6f0)|翻译|3| +|[为什么设计师讨厌政治](https://juejin.im/post/5b89599f51882542b03e6d5b)|翻译|4| +|[Apache Airflow 的关键概念](https://juejin.im/post/5b7ba247e51d4538d42ab6a0)|翻译|6| +|[如何优化企业级规模的 Node.js 应用程序](https://juejin.im/post/5b83c639f265da436d7e4c5e)|翻译|3| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|10| +|[Flutter 中的原生应用程序状态](https://juejin.im/post/5b651edc6fb9a04fbc221435)|翻译|3| +|[无头渲染组件](https://juejin.im/post/5b5e919f51882519d3467f41)|翻译|4.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|11.5| +|[我们是如何高效实现一致性哈希的](https://juejin.im/post/5b5488a96fb9a04fad3a181a)|校对|2| +|[我如何使用 Node.js 来实现工作自动化](https://juejin.im/post/5b4fe75ef265da0f54052138)|校对|1| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|28| +|[TensorFlow 官方文档修订](https://github.com/xitu/tensorflow-docs)|修订|8| +|[从 Java EE 8 Security API 开始 — 第二部分](https://juejin.im/post/5b3b49d66fb9a04f8a2160a9)|翻译|4.5| +|[通过 SSH 远程使用 Python 解释器来运行 Flask](https://juejin.im/post/5b3e25a8e51d45191716d187)|翻译|4| +|[我是如何从零开始建立一个网络爬虫来实现我的求职自动化的](https://juejin.im/post/5b3b40896fb9a04f9e22e927)|翻译|4| +|[将项目迁移到 Yarn 然后又迁回到 npm](https://juejin.im/post/5b3b5735f265da0f894b443d)|校对|1.5| +|[如何在 Flutter 中设计 LinearLayout?](https://juejin.im/post/5b3edeb16fb9a04fe820ccbc)|校对|0.5| +|[从 Java EE 8 Security API 开始 — 第一部分](https://juejin.im/post/5b35a8d4f265da599423598b)|翻译|5| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|10| +|[如何通过树莓派的深度学习轻松检测对象](https://juejin.im/post/5b1ba938518825137661af46)|翻译|7| +|[介绍 Google Play 上新的优质 Android 应用与游戏](https://juejin.im/post/5ae978d151882567370633ca)|校对|0.5| +|[Android Studio 切换至 D8 dexer](https://juejin.im/post/5b0a84f0518825388e725ec8)|翻译|2| +|[Android 支持库 21.1.0 中的 Loaders](https://juejin.im/post/5b06139a6fb9a07aa43c966f)|校对|1| +|[Windows Insets 和 Fragment 过渡动画](https://juejin.im/post/5b05206f6fb9a07ac162c9af)|校对|0.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|12| +|[JavaScript 是如何工作的:Web 推送通知的机制](https://juejin.im/post/5b002766518825429d1f90bc)|翻译|5| +|[部署!=发布(第二部分)](https://juejin.im/post/5b00d2fa6fb9a07a9a1120e9)|翻译|3| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1| |[Web 应用的未来:Heroku vs Docker](https://juejin.im/post/5ae7dae5518825670f7ba367)|校对|1| |[设计大型 JavaScript 应用程序](https://juejin.im/post/5aedd93b51882567244ddfc0)|校对|3| |[漫画深入浅出 ES 模块](https://juejin.im/post/5aea6ae7f265da0ba4699e0a)|校对|1.5| @@ -5225,20 +5410,38 @@ |[json — JavaScript 对象表示法](https://juejin.im/post/5a9432ae5188257a5c6092b0)|校对|1| |[嵌套三元表达式棒极了(软件编写)(第十四部分)](https://juejin.im/post/5a7d6769f265da4e7e10ad82)|校对|1| -## 译者:[zhmhhu](https://github.com/zhmhhu) 历史贡献积分:42.5 当前积分:42.5 年度积分:42.5 +## 译者:[zhmhhu](https://github.com/zhmhhu) 历史贡献积分:86 当前积分:41 年度积分:86 |文章|类型|积分| |------|-------|-------| +|[Python 中的无监督学习算法](https://juejin.im/post/5bab10ed6fb9a05d1f2211b6)|翻译|5| +|[在 JavaScript 中 为什么你应当使用 map 和 filter 来替代 forEach](https://juejin.im/post/5b95c4fe5188255c402ae6fb)|翻译|2.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|8| +|2018 年 8 月兑树莓派套餐 1 个|减去积分|45| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| +|[为什么 UX,UI,CX,IA,IxD 和其他类型的设计都是愚蠢的](https://juejin.im/post/5b3588f2e51d4558dd69a44c)|翻译|4| +|推荐多篇优秀英文文章|奖励|3| +|[WOFF文件格式 1.0](https://github.com/xitu/gold-miner/blob/master/TODO1/recommendation-woff-2012-12-13.md)|翻译|10| +|[为什么 VueX 是前端与 API 之间的完美接口](https://juejin.im/post/5b14d1bd6fb9a01e4508bfc1)|翻译|5| +|[一个简单的 ES6 Promises 指南](https://juejin.im/post/5b0eb3b1f265da08f31e770a)|校对|1| |[用 Python 编程进行糖尿病相关的机器学习](https://juejin.im/post/5ace62e96fb9a028b92d8267)|校对|1| |[设计研究的 9 条规则](https://juejin.im/post/5aa6958a5188255587233477)|翻译|5.5| |[前端开发者指南 2018](https://github.com/xitu/front-end-handbook-2018)|翻译校对|34| |[在 V8 引擎中设置原型(prototypes)](https://juejin.im/post/5a9921e76fb9a028bd4bc3c4)|校对|1| |[json — JavaScript 对象表示法](https://juejin.im/post/5a9432ae5188257a5c6092b0)|校对|1| -## 译者:[rydensun](https://github.com/rydensun) 历史贡献积分:26 当前积分:26 年度积分:26 +## 译者:[rydensun](https://github.com/rydensun) 历史贡献积分:53 当前积分:43 年度积分:53 |文章|类型|积分| |------|-------|-------| +|[如何创建一个设计体系来赋能团队 —— 关注人,而非像素](https://juejin.im/post/5bac2a2fe51d450e942f4853)|校对|3.5| +|[为 APP 设计通知提醒](https://juejin.im/post/5ba31ee3e51d450e4115500b)|翻译|5| +|[以申请大学流程来解释 JavaScript 的 filter](https://juejin.im/post/5b9f09685188255c5e66d60c)|校对|1.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| +|[构建流畅的交互界面](https://juejin.im/post/5b7e2b34e51d4538843612cc)|翻译|8| +|[Tab Bar 就是新的汉堡菜单](https://juejin.im/post/5b61684fe51d451986517e31)|翻译|3| +|[如何设计移动应用的搜索功能?](https://juejin.im/post/5b692ca251882562b924a6c7)|校对|1| +|2018 年 6 月兑掘金桌垫 1 个|减去积分|10| |[Swift 写网络层:用面向协议的方式](https://juejin.im/post/5af432385188256737066c04)|校对|2| |[构建、测试、分发!运用 Fastlane 与 Jenkins,完整的 iOS 持交付指南。](https://juejin.im/post/5af430f0f265da0b8262d1ea)|校对|1| |[用户体验中的稀缺性:成为常态的心理偏见](https://juejin.im/post/5aef0943518825672a02d2ca)|校对|1.5| @@ -5258,20 +5461,22 @@ |------|-------|-------| |[前端开发者指南 2018](https://github.com/xitu/front-end-handbook-2018)|翻译校对|2| -## 译者:[xueshuai](https://github.com/xueshuai) 历史贡献积分:24 当前积分:24 年度积分:24 +## 译者:[xueshuai](https://github.com/xueshuai) 历史贡献积分:31 当前积分:31 年度积分:31 |文章|类型|积分| |------|-------|-------| +|[我们距离 Babel 7.0 发布很近了。这里是所有我们一直在做的很酷的事情。](https://juejin.im/post/5b174f3e518825139b18e1f0)|翻译|7| |[使用 React, Redux, and SVG 开发游戏 - 第3部分](https://juejin.im/post/5af2a4d06fb9a07aa114302b)|翻译|9| |[写给前端开发者的 GraphQL 指南](https://juejin.im/post/5ac09072518825558c479215)|校对|1| |[关于 CSS 变量,你需要了解的一切](https://juejin.im/post/5ab835225188255572085af4)|校对|3.5| |[Typescript : 类 vs 接口](https://juejin.im/post/5aa79a106fb9a028db585eae)|翻译|2.5| |[前端开发者指南 2018](https://github.com/xitu/front-end-handbook-2018)|翻译校对|8| -## 译者:[AlenQi](https://github.com/AlenQi) 历史贡献积分:0.5 当前积分:0.5 年度积分:0.5 +## 译者:[AlenQi](https://github.com/AlenQi) 历史贡献积分:1.5 当前积分:1.5 年度积分:1.5 |文章|类型|积分| |------|-------|-------| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|1| |[为你的 React 应用制作 SVG 图标库](https://juejin.im/post/5a9e40fe518825558a061cfd)|校对|0.5| ## 译者:[rpgmakervx](https://github.com/rpgmakervx) 历史贡献积分:4.5 当前积分:4.5 年度积分:4.5 @@ -5282,10 +5487,13 @@ |[Elasticsearch Reference Getting Start](https://juejin.im/post/5aa0b13af265da238b7d92f7)|翻译|1.5| |[Typescript : 类 vs 接口](https://juejin.im/post/5aa79a106fb9a028db585eae)|校对|1| -## 译者:[zephyrJS](https://github.com/zephyrJS) 历史贡献积分:29 当前积分:29 年度积分:29 +## 译者:[zephyrJS](https://github.com/zephyrJS) 历史贡献积分:38.5 当前积分:38.5 年度积分:38.5 |文章|类型|积分| |------|-------|-------| +|[支撑现代存储系统的算法](https://juejin.im/post/5b10f80b5188257d92206851)|校对|1.5| +|[前端开发框架实战对比(2018年更新)](https://juejin.im/post/5b20a5996fb9a01e6b2c21ec)|校对|0.5| +|[Font-size:一个出乎意料复杂的 CSS 属性](https://juejin.im/post/5b1292355188251377116617)|翻译|7.5| |[漫画深入浅出 ES 模块](https://juejin.im/post/5aea6ae7f265da0ba4699e0a)|校对|1.5| |[使用 React、Redux 和 SVG 开发游戏 - Part 2](https://juejin.im/post/5ad373fef265da2395316f27)|翻译|10| |[拖放库中 React 性能的优化](https://juejin.im/post/5ac31b096fb9a028bc2dedfc)|校对|3| @@ -5294,10 +5502,11 @@ |[斐波那契数列中的偶数(Python vs. JavaScript)](https://juejin.im/post/5aaa61f8f265da237b21d258)|翻译|2| |[测试 React & Redux 应用真实引导](https://juejin.im/post/5a9cc812518825556f54e3b6)|校对|1.5| -## 译者:[razertory](https://github.com/razertory) 历史贡献积分:0.5 当前积分:0.5 年度积分:0.5 - +## 译者:[razertory](https://github.com/razertory) 历史贡献积分:4 当前积分:4 年度积分:4 |文章|类型|积分| |------|-------|-------| +|[GopherCon 2018:揭秘二叉查找树算法](https://juejin.im/post/5b94de9c5188255c5047076c)|校对|2| +|[使用 Go 编写微服务及其 GraphQL 网关](https://juejin.im/post/5b94cf476fb9a05d26593f07)|校对|1.5| |[Elasticsearch Reference Getting Start](https://juejin.im/post/5aa0b13af265da238b7d92f7)|校对|0.5| ## 译者:[YinTokey](https://github.com/YinTokey) 历史贡献积分:4.5 当前积分:4.5 年度积分:4.5 @@ -5320,10 +5529,17 @@ |[如何修改域名来提高国际增长率](https://juejin.im/post/5aaf0542f265da239530c653)|校对|1| |[开始设计动画的九个步骤](https://juejin.im/post/5aa1f965f265da23994e1e1f)|校对|1| -## 译者:[talisk](https://github.com/talisk) 历史贡献积分:24.5 当前积分:24.5 年度积分:24.5 +## 译者:[talisk](https://github.com/talisk) 历史贡献积分:42 当前积分:42 年度积分:42 |文章|类型|积分| |------|-------|-------| +|[React Native 中使用转场动画!](https://juejin.im/post/5b69467e5188251b3c3b4e4e)|翻译|3| +|[watchOS 5 愿望清单:Apple Watch Podcasts、open Siri face 和更新 Control Center 等](https://juejin.im/post/5b15045bf265da6e6039372b)|翻译|4| +|[JavaScript 是如何工作的:Service Worker 的生命周期与使用场景](https://juejin.im/post/5b14c1d86fb9a01e700ff2f2)|翻译|5| +|[Swift 中的内存泄漏](https://juejin.im/post/5b07a1c251882538914a5d3e)|校对|1.5| +|[Swift 中的 Playground 驱动开发](https://juejin.im/post/5b07665c6fb9a07ac5608d8a)|校对|2| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1| +|[由 Node.js 发送 Web 推送通知](https://juejin.im/post/5afc32146fb9a07acf5657e7)|校对|1| |[Swift 写网络层:用面向协议的方式](https://juejin.im/post/5af432385188256737066c04)|翻译|6| |[构建、测试、分发!运用 Fastlane 与 Jenkins,完整的 iOS 持交付指南。](https://juejin.im/post/5af430f0f265da0b8262d1ea)|翻译|6| |[Flutter 到底有多快?我开发了秒表应用来弄清楚。](https://juejin.im/post/5ad861566fb9a045ee01b48d)|校对|1.5| @@ -5335,10 +5551,11 @@ |[没有 Interface Builder 的生活](https://juejin.im/post/5ab88ac0518825558a069c91)|校对|1| |[使用 Travis CI 自动发布 npm](https://juejin.im/post/5ab39fedf265da23a04979cb)|校对|1| -## 译者:[liang-kai](https://github.com/liang-kai) 历史贡献积分:1 当前积分:1 年度积分:1 +## 译者:[liang-kai](https://github.com/liang-kai) 历史贡献积分:4 当前积分:4 年度积分:4 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3| |[使用 Travis CI 自动发布 npm](https://juejin.im/post/5ab39fedf265da23a04979cb)|校对|1| ## 译者:[anxsec](https://github.com/anxsec) 历史贡献积分:8.5 当前积分:8.5 年度积分:8.5 @@ -5358,10 +5575,16 @@ |[写给前端开发者的 GraphQL 指南w](https://juejin.im/post/5ac09072518825558c479215)|翻译|4| |[iOS App 上一种灵活的路由方式](https://juejin.im/post/5aafc278f265da23805973e3)|校对|1| -## 译者:[allenlongbaobao](https://github.com/allenlongbaobao) 历史贡献积分:70.5 当前积分:60.5 年度积分:70.5 +## 译者:[allenlongbaobao](https://github.com/allenlongbaobao) 历史贡献积分:92 当前积分:82 年度积分:91 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3| +|[Vue.js 还是 React?你会选择哪一个?为什么?](https://juejin.im/post/5b25b3a16fb9a00e70686180)|翻译|3.5| +|[JavaScript 是如何工作的:Service Worker 的生命周期与使用场景](https://juejin.im/post/5b14c1d86fb9a01e700ff2f2)|校对|1| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6| +|[JavaScript 是如何工作的:Web 推送通知的机制](https://juejin.im/post/5b002766518825429d1f90bc)|校对|2| |[深入浅出 JavaScript 关键词 — this](https://juejin.im/post/5aefe76e6fb9a07abc29d4a1)|校对|3| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|8| |[JavaScript 是如何工作的:渲染引擎和性能优化技巧](https://juejin.im/post/5ad5986bf265da23994f06ab)|校对|2| @@ -5392,10 +5615,11 @@ |[没有 Interface Builder 的生活](https://juejin.im/post/5ab88ac0518825558a069c91)|校对|1| |[使用 MVI 编写响应式 APP—第六部分—状态恢复](https://juejin.im/post/5ab4c028518825557e7853a1)|校对|1| -## 译者:[stormluke](https://github.com/stormluke) 历史贡献积分:32 当前积分:32 年度积分:32 +## 译者:[stormluke](https://github.com/stormluke) 历史贡献积分:37 当前积分:37 年度积分:37 |文章|类型|积分| |------|-------|-------| +|[JavaScript 是如何工作的:对比 WebAssembly + 为什么在某些场景下它比 JavaScript 更合适](https://juejin.im/post/5b0526e751882507c36d0167)|翻译|5| |[漫画深入浅出 ES 模块](https://juejin.im/post/5aea6ae7f265da0ba4699e0a)|翻译|8| |[JavaScript 是如何工作的:渲染引擎和性能优化技巧](https://juejin.im/post/5ad5986bf265da23994f06ab)|翻译|6| |[如何调试前端:优化网络资源](https://juejin.im/post/5ade828751882567137dd2da)|翻译|4| @@ -5406,10 +5630,12 @@ |[让 Apache Cassandra 尾部延迟减小 10 倍(已开源)](https://juejin.im/post/5ac31083f265da239a5fff0c)|翻译|4| |[让我们来简化 UserDefaults 的使用](https://juejin.im/post/5abde324f265da23826e1723)|校对|0.5| -## 译者:[EmilyQiRabbit](https://github.com/EmilyQiRabbit) 历史贡献积分:37.5 当前积分:37.5 年度积分:37.5 +## 译者:[EmilyQiRabbit](https://github.com/EmilyQiRabbit) 历史贡献积分:55.5 当前积分:55.5 年度积分:55.5 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|13| |[JavaScript 是如何运作的:用 MutationObserver 追踪 DOM 的变化](https://juejin.im/post/5aee720df265da0b8f627173)|翻译|3.5| |[区块链如何帮助重塑医疗保健行业](https://juejin.im/post/5ad449b56fb9a028c6762db5)|翻译|6| |[用 Go 编写你自己的区块链挖矿算法!](https://juejin.im/post/5ad6d2ff51882579ef4f7cf0)|翻译|6| @@ -5420,12 +5646,17 @@ |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|5| |[使用 Web3 和 Vue.js 来创建你的第一个以太坊 dAPP(第二部分)](https://juejin.im/post/5aba0870f265da23a2292245)|校对|1| -## 译者:[dandyxu](https://github.com/dandyxu) 历史贡献积分:14.5 当前积分:14.5 年度积分:14.5 +## 译者:[dandyxu](https://github.com/dandyxu) 历史贡献积分:29 当前积分:29 年度积分:29 |文章|类型|积分| |------|-------|-------| +|[2018 年,如何选择最好的静态站点生成器](https://juejin.im/post/5b47079bf265da0faa3655be)|校对|1| +|[关于 HomePod WWDC 的愿望清单: 测试版程序、新的语音资源、Echo 等功能](https://juejin.im/post/5b14ff08f265da6e1a602e26)|翻译|4| +|[WWDC 2018:关于iOS 12、iPad Pro、新MacBooks或者更多产品的所有预测](https://juejin.im/post/5b056d485188256710601ecc)|翻译|6| +|[⚛ React 状态管理工具博物馆](https://juejin.im/post/5afd18cf6fb9a07acb3d13ac)|校对|2.5| +|[这就是为什么我们需要在 React 的类组件中为事件处理程序绑定 this](https://juejin.im/post/5afa6e2f6fb9a07aa2137f51)|校对|1| |[论 Android 中 Span 的正确打开方式](https://juejin.im/entry/5af401bb518825671776537d/)|校对|1| -|[如何逃离 async/await 地狱](https://juejin.im/post/5aefbb48f265da0b9b073c40)|校对|4| +|[我在编程初级阶段常犯的错误](https://juejin.im/post/5ae97af6f265da0ba062f797)|校对|4| |[深入浅出 SVG](https://juejin.im/post/5ad84f296fb9a045e8011be1)|校对|1.5| |[提高 10 倍性能:优化静态网站](https://juejin.im/post/5ac9e430f265da2392369ec0)|校对|1.5| |[单向用户界面架构问题研究](https://juejin.im/post/5aca38c5518825482e392c00)|校对|1.5| @@ -5445,10 +5676,22 @@ |------|-------|-------| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|4| -## 译者:[sunhaokk](https://github.com/sunhaokk) 历史贡献积分:32.5 当前积分:32.5 年度积分:32.5 +## 译者:[sunhaokk](https://github.com/sunhaokk) 历史贡献积分:70 当前积分:45 年度积分:70 |文章|类型|积分| |------|-------|-------| +|2018 年 9 月兑掘金帽衫和水杯各一个|减去积分|25| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2| +|[用 API 请求制作赏心悦目的 UX](https://juejin.im/post/5b992d13e51d450e894de541)|校对|1.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|15| +|[为何前端开发如此不稳定](https://juejin.im/post/5b1f2f1ae51d4506894983ae)|校对|0.5| +|[从零开始,在 Redux 中构建时间旅行式调试](https://juejin.im/post/5b24c0bce51d4558ba1a5584)|校对|0.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|4.5| +|[这就是为什么我们需要在 React 的类组件中为事件处理程序绑定 this](https://juejin.im/post/5afa6e2f6fb9a07aa2137f51)|校对|1| +|[使用 Puppeteer 和 Jest 测试你的 React 应用](https://juejin.im/post/5af90988518825426a1fcc2e)|校对|1| +|[给初学者的 Jupyter Notebook 教程](https://juejin.im/post/5af8d3776fb9a07ab7744dd0)|校对|3.5| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|14| |[[1] + [2] - [3] === 9!? 类型转换深入研究](https://juejin.im/post/5ad5af7251882555894a5054)|翻译|2| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| @@ -5465,20 +5708,36 @@ |[如何用 Python 写一个 Discord 机器人](https://juejin.im/post/5ac1b9796fb9a028c42e5a61)|校对|0.5| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|6| -## 译者:[melon8](https://github.com/melon8) 历史贡献积分:17 当前积分:17 年度积分:17 +## 译者:[melon8](https://github.com/melon8) 历史贡献积分:30 当前积分:30 年度积分:30 |文章|类型|积分| |------|-------|-------| +|[2018 年 iOS 开发找工作完全指南](https://juejin.im/post/5b7eca206fb9a019ff713277)|翻译|6| +|[不越狱探索 App](https://juejin.im/post/5b0e7eec518825155a048dc4)|翻译|7| |[Story 中 Type Mode 在 iOS 和 Android 上的实现](https://juejin.im/post/5ae7e1596fb9a07ab9794332)|翻译|5| |[轻松管理 Swift 项目中的不同环境](https://juejin.im/post/5af264a4f265da0b967233ff)|翻译|4| |[使用 iPhone X 与 Maya 实现快速面部捕捉](https://juejin.im/post/5af40eba6fb9a07ac76ed7d1)|校对|1| |[使用 Swift 实现原型动画](https://juejin.im/post/5ae28a9b6fb9a07aaa10fa1e)|校对|2| |[不使用 fastlane 实现持续交付的 5 种选项](https://juejin.im/post/5acf47cb6fb9a028c523944c)|翻译|5| -## 译者:[luochen1992](https://github.com/luochen1992) 历史贡献积分:28.5 当前积分:28.5 年度积分:28.5 +## 译者:[luochen1992](https://github.com/luochen1992) 历史贡献积分:55 当前积分:10 年度积分:55 |文章|类型|积分| |------|-------|-------| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|1| +|[如何通过树莓派的深度学习轻松检测对象](https://juejin.im/post/5b1ba938518825137661af46)|校对|2| +|[使用 Span 来修改文本样式的优质体验](https://juejin.im/post/5b24c20851882574ea3a0d86)|校对|1| +|[深度学习中所需的线性代数知识](https://juejin.im/post/5b19d99ae51d4506d81a7a2f)|校对|2.5| +|[2018 年 5 月兑树莓派套餐 1 套]()|减去积分|45| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|0.5| +|[JavaScript 是如何工作的:深入网络层 + 如何优化性能和安全](https://juejin.im/post/5b02ae48518825429d1f9aff)|校对|1.5| +|[可微可塑性:一种学会学习的新方法](https://juejin.im/post/5b055308f265da0ba063879d)|翻译|3| +|[关于你的编程生涯的一些告诫](https://juejin.im/post/5b0256e36fb9a07aa767f5b4)|校对|2| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|4| +|[如何在 5 分钟之内写出一个不错的 loading 界面](https://juejin.im/post/5af54bbb518825426d2d2e63)|校对|1| +|[30 分钟 Python 爬虫教程](https://juejin.im/post/5afab181f265da0b7452514a)|校对|1| +|[在浏览器里使用 TenserFlow.js 实时估计人体姿态](https://juejin.im/post/5afd833b5188254270642ff3)|校对|2| +|[如何在安卓应用中使用 TensorFlow Mobile](https://juejin.im/post/5afb8dc5518825426c690236)|翻译|5| |[Java 桥接方法详解](https://juejin.im/post/5af28ca0518825672565da74)|校对|0.5| |[使用 Go 和 AWS Lambda 构建无服务 API](https://juejin.im/post/5af4082f518825672a02f262)|校对|1.5| |[使用 iPhone X 与 Maya 实现快速面部捕捉](https://juejin.im/post/5af40eba6fb9a07ac76ed7d1)|校对|1| @@ -5503,10 +5762,15 @@ |[引导员手册:24 个设计冲刺技巧](https://juejin.im/post/5ae3254d6fb9a07abc29a741)|翻译|5| |[设计师与工程师协作的 5 项准则](https://juejin.im/post/5ac9e56af265da23945fc201)|校对|1| -## 译者:[NoName4Me](https://github.com/NoName4Me) 历史贡献积分:9 当前积分:9 年度积分:9 +## 译者:[NoName4Me](https://github.com/NoName4Me) 历史贡献积分:40.5 当前积分:40.5 年度积分:40.5 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|12| +|[JavaScript 是如何工作的:CSS 和 JS 动画背后的原理 + 如何优化性能](https://juejin.im/post/5afaea6b6fb9a07aa34a6a74)|翻译|5| +|[在浏览器里使用 TenserFlow.js 实时估计人体姿态](https://juejin.im/post/5afd833b5188254270642ff3)|翻译|7| +|[预测你的游戏的货币化未来](https://juejin.im/post/5ad1d3b6f265da238f12fafa)|翻译|3.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|4| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6.5| |[关于 SPA 你需要掌握的 4 层](https://juejin.im/post/5ac9ae935188255caf067974)|校对|1.5| |[设计师与工程师协作的 5 项准则](https://juejin.im/post/5ac9e56af265da23945fc201)|校对|1| @@ -5519,10 +5783,18 @@ |[React & Redux 顶级开发伴侣](https://juejin.im/post/5acae8dc6fb9a028c06b1c4c)|校对|1| |[拖放库中 React 性能的优化](https://juejin.im/post/5ac31b096fb9a028bc2dedfc)|校对|3| -## 译者:[Hopsken](https://github.com/Hopsken) 历史贡献积分:9 当前积分:9 年度积分:9 +## 译者:[Hopsken](https://github.com/Hopsken) 历史贡献积分:31.5 当前积分:26.5 年度积分:31.5 |文章|类型|积分| |------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|10| +|2018 年 7 月兑掘金马克杯 1 个(赠集智杯子一个),减 5 积分|减去积分|5| +|[常见网页设计错误一览](https://juejin.im/post/5b7984265188254312414c1f)|校对|2| +|[SpaceAce 了解一下,一个新的前端状态管理库](https://juejin.im/post/5b7653c96fb9a009c72cb7af)|校对|1.5| +|[Javascript(ES6)Generator 入门](https://juejin.im/post/5b4c22aa6fb9a04faf479be1)|校对|1.5| +|[单元素组件模式简介](https://juejin.im/post/5b445d79e51d4519596b5f87)|翻译|1.5| +|[前端开发框架实战对比(2018年更新)](https://juejin.im/post/5b20a5996fb9a01e6b2c21ec)|校对|1| +|[JavaScript 是如何工作的:深入网络层 + 如何优化性能和安全](https://juejin.im/post/5b02ae48518825429d1f9aff)|翻译|5| |[Web 应用的未来:Heroku vs Docker](https://juejin.im/post/5ae7dae5518825670f7ba367)|翻译|3| |[使用 NumPy 和 Pandas 进行 Python 式数据清理](https://juejin.im/post/5ad57db3f265da239c7bd9fb)|校对|3| |[提高 10 倍性能:优化静态网站](https://juejin.im/post/5ac9e430f265da2392369ec0)|校对|1.5| @@ -5536,10 +5808,11 @@ |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1| |[Redux-Saga 为你打 call:管理你的异步 action (上)](https://juejin.im/post/5ac1cb9d6fb9a028cf32a046)|校对|1| -## 译者:[meloalright](https://github.com/meloalright) 历史贡献积分:5 当前积分:5 年度积分:5 +## 译者:[meloalright](https://github.com/meloalright) 历史贡献积分:14 当前积分:14 年度积分:14 |文章|类型|积分| |------|-------|-------| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|9| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|2| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3| @@ -5562,17 +5835,26 @@ |------|-------|-------| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1| -## 译者:[wavezhang](https://github.com/wavezhang) 历史贡献积分:3.5 当前积分:3.5 年度积分:3.5 +## 译者:[wavezhang](https://github.com/wavezhang) 历史贡献积分:4 当前积分:4 年度积分:4 |文章|类型|积分| |------|-------|-------| +|[Android Studio 切换至 D8 dexer](https://juejin.im/post/5b0a84f0518825388e725ec8)|校对|0.5| |[用户体验中的稀缺性:成为常态的心理偏见](https://juejin.im/post/5aef0943518825672a02d2ca)|校对|1.5| |[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2| -## 译者:[sisibeloved](https://github.com/sisibeloved) 历史贡献积分:31 当前积分:31 年度积分:31 +## 译者:[sisibeloved](https://github.com/sisibeloved) 历史贡献积分:64 当前积分:64 年度积分:64 |文章|类型|积分| |------|-------|-------| +|[Sklearn 中的朴素贝叶斯分类器](https://juejin.im/post/5b8510be51882542d23a1d66)|翻译|4| +|[如何在数据科学中写出生产层面的代码?](https://juejin.im/post/5b7adb7751882542d63b2805)|翻译|7| +|[Web 应用架构基础课](https://juejin.im/post/5b69a8eef265da0f926baa56)|校对||2| +|[在 UNIX 中,一切皆文件](https://juejin.im/post/5b652d346fb9a04fc03129e6)|校对|2.5| +|[介绍 Google Play 上新的优质 Android 应用与游戏](https://juejin.im/post/5ae978d151882567370633ca)|翻译|2| +|[给人类的机器学习指南🤖👶](https://juejin.im/post/5b136f12f265da6e5415114b)|翻译|7.5| +|[给初学者的 Jupyter Notebook 教程](https://juejin.im/post/5af8d3776fb9a07ab7744dd0)|校对|5| +|[移动游戏发行的新时代](https://juejin.im/post/5af9acf851882542877345ef)|校对|3| |[使用 Go 和 AWS Lambda 构建无服务 API](https://juejin.im/post/5af4082f518825672a02f262)|翻译|8| |[Python 中的键值(具名)参数:如何使用它们](https://juejin.im/post/5ae97546f265da0b8d41bcc7)|翻译|5| |[更为详细的地图、导航和助手功能 —— Google I/O 2018 的 Android 应用更新](https://juejin.im/post/5aee74626fb9a07abb237f89)|翻译|2| @@ -5580,18 +5862,29 @@ |[用 Java 代码实现区块链](https://juejin.im/post/5ae57d9e6fb9a07ab83dcc03)|校对|4| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|6| -## 译者:[mingxing47](https://github.com/mingxing47) 历史贡献积分:4.5 当前积分:4.5 年度积分:4.5 +## 译者:[mingxing47](https://github.com/mingxing47) 历史贡献积分:27.5 当前积分:27.5 年度积分:27.5 |文章|类型|积分| |------|-------|-------| +|[如何像程序员般思考 —— 蕴含在问题解决中的经验](https://juejin.im/post/5b76839ae51d4566491c24bb)|翻译|5| +|[我是如何使用 Python 在 Medium 上找到并关注有趣的人](https://juejin.im/post/5b72c61851882561311fccce)|校对|1.5| +|[使用 Python 进行自动化特征工程](https://juejin.im/post/5b6ea0e4e51d4519044adff0)|翻译|7| +|[[字幕翻译] Andrew Godwin — Django 异步 — PyCon 2018](https://www.bilibili.com/video/av24571596/)|校对|3.5| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|5| +|[预测你的游戏的货币化未来](https://juejin.im/post/5ad1d3b6f265da238f12fafa)|校对|1| |[用 Go 编写你自己的区块链挖矿算法!](https://juejin.im/post/5ad6d2ff51882579ef4f7cf0)|校对|2.5| |[利用 Keras 深度学习库进行词性标注教程](https://juejin.im/post/5ae4613a5188256727742d7d)|校对|1| |[GAN 的 Keras 实现:构建图像去模糊应用](https://juejin.im/post/5ad6e358f265da237b229bb2)|校对|1| -## 译者:[SergeyChang](https://github.com/SergeyChang) 历史贡献积分:7.5 当前积分:7.5 年度积分:7.5 +## 译者:[SergeyChang](https://github.com/SergeyChang) 历史贡献积分:25 当前积分:25 年度积分:25 |文章|类型|积分| |------|-------|-------| +|[那些我们不需要的 HTTP 头信息](https://juejin.im/post/5b06c89df265da0db35022d8)|翻译|4.5| +|[使用 Go 语言的流模式来解析 DrugBank 的 XML(或者任何大的 XML 文件)](https://juejin.im/entry/5b06322a6fb9a07aab2a3d44)|校对|1.5| +|[可微可塑性:一种学会学习的新方法](https://juejin.im/post/5b055308f265da0ba063879d)|校对|0.5| +|[给初学者的 Jupyter Notebook 教程](https://juejin.im/post/5af8d3776fb9a07ab7744dd0)|翻译|10| +|[BigQuery 中的比特币:使用公共数据分析区块链](https://juejin.im/post/5afc17b16fb9a07aaf35673a)|校对|1| |[使用 Go 和 AWS Lambda 构建无服务 API](https://juejin.im/post/5af4082f518825672a02f262)|校对|1.5| |[为什么你应该开始使用 Kotlin](https://juejin.im/post/5adc1826f265da0b767d0db2)|校对|1| |[区块链如何帮助重塑医疗保健行业](https://juejin.im/post/5ad449b56fb9a028c6762db5)|校对|2| @@ -5604,10 +5897,27 @@ |------|-------|-------| |[用 Skater 解读预测模型:打开模型的黑箱](https://juejin.im/post/5ae1494bf265da0b7c06f835)|翻译|6| -## 译者:[ALVINYEH](https://github.com/ALVINYEH) 历史贡献积分:42 当前积分:42 年度积分:42 - -|文章|类型|积分| -|------|-------|-------| +## 译者:[ALVINYEH](https://github.com/ALVINYEH) 历史贡献积分:94 当前积分:94 年度积分:94 + +|文章|类型|积分| +|------|-------|-------| +|[Flutter 实用指南:给初学者的 6 个小帖士](https://juejin.im/post/5b5534126fb9a04fcc449f4c)|翻译|4| +|[使用 Flutter 制作 3D 翻转动画](https://juejin.im/post/5b5534c951882562b9248294)|翻译|3| +|[为什么每个 Android 开发者都应该尝试 Flutter](https://juejin.im/post/5b5e70ffe51d4518e311b63d)|翻译|3.5| +|[Flutter 组件到底是什么?](https://juejin.im/post/5b46f836f265da0f8f2025f7)|翻译|4| +|[使用 Flutter 的 GestureDetector 构建自定义滑块](https://juejin.im/post/5b431bff5188251b166ee0c1)|翻译|2| +|[Airbnb 中的 React Native(五 — 完):Airbnb 移动端路在何方?](https://juejin.im/post/5b46f92de51d45198e721cd7)|翻译|4| +|[Airbnb 中的 React Native(四):React Native 退役](https://juejin.im/post/5b447b1e6fb9a04fd3437dad)|翻译|3.5| +|[Airbnb 中的 React Native (三):建立一个跨平台的移动端团队](https://juejin.im/post/5b446177f265da0f7c4faec8)|翻译|4| +|[Airbnb 在 React Native 上下的赌注(二):技术细节](https://juejin.im/post/5b3b40a26fb9a04fab44e797)|翻译|6| +|[Airbnb 在 React Native 上下的赌注(一):概述](https://juejin.im/post/5b2c924ff265da59a401f050)|翻译|2.5| +|[不越狱探索 App](https://juejin.im/post/5b0e7eec518825155a048dc4)|校对|2.5| +|[Swift 中的 Playground 驱动开发](https://juejin.im/post/5b07665c6fb9a07ac5608d8a)|翻译|7| +|[JavaScript 是如何工作的:CSS 和 JS 动画背后的原理 + 如何优化性能](https://juejin.im/post/5afaea6b6fb9a07aa34a6a74)|校对|2| +|[如何在 5 分钟之内写出一个不错的 loading 界面](https://juejin.im/post/5af54bbb518825426d2d2e63)|校对|1| +|[如何在安卓应用中使用 TensorFlow Mobile](https://juejin.im/post/5afb8dc5518825426c690236)|校对|0.5| +|[BigQuery 中的比特币:使用公共数据分析区块链](https://juejin.im/post/5afc17b16fb9a07aaf35673a)|校对|1| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1.5| |[Python 中的键值(具名)参数:如何使用它们](https://juejin.im/post/5ae97546f265da0b8d41bcc7)|校对|1| |[论 Android 中 Span 的正确打开方式](https://juejin.im/entry/5af401bb518825671776537d/)|校对|2| |[Swift 写网络层:用面向协议的方式](https://juejin.im/post/5af432385188256737066c04)|校对|2| @@ -5626,34 +5936,56 @@ |[细数那些我离不开的 Sketch 插件](https://juejin.im/post/5ae0623ef265da0b8d419aca)|校对|1| |[用 Skater 解读预测模型:打开模型的黑箱](https://juejin.im/post/5ae1494bf265da0b7c06f835)|校对|2.5| -## 译者:[wzasd](https://github.com/wzasd) 历史贡献积分:8.5 当前积分:8.5 年度积分:8.5 +## 译者:[wzasd](https://github.com/wzasd) 历史贡献积分:36 当前积分:36 年度积分:36 |文章|类型|积分| |------|-------|-------| +|[绘图技巧的快速入门之:6 个绘图练习,让你立即上手!](https://juejin.im/post/5b39823fe51d4558ca674cff)|翻译|3.5| +|[在 SnackBar,Navigation 和其他事件中使用 LiveData(SingleLiveEvent 案例)](https://juejin.im/post/5b2b1b2cf265da5952314b63)|翻译|4| +|[使用 Span 来修改文本样式的优质体验](https://juejin.im/post/5b24c20851882574ea3a0d86)|翻译|6| +|[情景活动感知识别的 Transition API 新特性面向全体开发者开放](https://juejin.im/post/5b21cfe16fb9a01e561a4b26)|翻译|2.5| +|[创造华丽 UI 的 7 个规则(Part 1)](https://juejin.im/post/5b1dcc7d5188257d7d71946a)|翻译|6.5| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|5| |[在 Google I/O 2018 观看 Flutter 的正确姿势](https://juejin.im/post/5aebd7166fb9a07ab4587b3f)|翻译|1.5| |[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|7| -## 译者:[kezhenxu94](https://github.com/kezhenxu94) 历史贡献积分:20 当前积分:20 年度积分:20 +## 译者:[kezhenxu94](https://github.com/kezhenxu94) 历史贡献积分:43 当前积分:43 年度积分:43 |文章|类型|积分| |------|-------|-------| +|[深度学习中所需的线性代数知识](https://juejin.im/post/5b19d99ae51d4506d81a7a2f)|校对|1.5| +|[一个简单的 ES6 Promises 指南](https://juejin.im/post/5b0eb3b1f265da08f31e770a)|校对|1| +|[新的 CSS 特性正在改变网页设计](https://juejin.im/post/5b0cae8c6fb9a009de14c833)|校对|2.5| +|[怎样更好地使用 Vue:我在新工作中遇到的一些问题清单](https://juejin.im/post/5b0a36366fb9a07a9c04aca2)|校对|1| +|[关于你的编程生涯的一些告诫](https://juejin.im/post/5b0256e36fb9a07aa767f5b4)|翻译|6| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|8| +|[我在编程初级阶段常犯的错误](https://juejin.im/post/5ae97af6f265da0ba062f797)|翻译|12| +|[30 分钟 Python 爬虫教程](https://juejin.im/post/5afab181f265da0b7452514a)|翻译|3| |[那些好玩却没有被 ECMAScript 2017 采纳的提案](https://juejin.im/post/5ae920fd51882567127852e7)|校对|2| |[我是如何修复 Python 3.7 中一个非常古老的 GIL 竞态条件 bug 的](https://juejin.im/post/5aeba1075188256712786039)|翻译|3.5| |[Java 桥接方法详解](https://juejin.im/post/5af28ca0518825672565da74)|翻译|2| -|[如何逃离 async/await 地狱](https://juejin.im/post/5aefbb48f265da0b9b073c40)|翻译|12| |[Google 的 ML Kit 为 Android 和 iOS 提供了简单的机器学习 API](https://juejin.im/post/5af2942e51882567244df836)|校对|0.5| -## 译者:[whuzxq](https://github.com/whuzxq) 历史贡献积分:1 当前积分:1 年度积分:1 +## 译者:[whuzxq](https://github.com/whuzxq) 历史贡献积分:10.5 当前积分:10.5 年度积分:10.5 |文章|类型|积分| |------|-------|-------| +|[给人类的机器学习指南🤖👶](https://juejin.im/post/5b136f12f265da6e5415114b)|校对|1.5| +|[这就是为什么我们需要在 React 的类组件中为事件处理程序绑定 this](https://juejin.im/post/5afa6e2f6fb9a07aa2137f51)|翻译|4| +|[如何在 5 分钟之内写出一个不错的 loading 界面](https://juejin.im/post/5af54bbb518825426d2d2e63)|翻译|3| +|[移动游戏发行的新时代](https://juejin.im/post/5af9acf851882542877345ef)|校对|1| |[如何逃离 async/await 地狱](https://juejin.im/post/5aefbb48f265da0b9b073c40)|校对|1| -## 译者:[DAA233](https://github.com/DAA233) 历史贡献积分:4 当前积分:4 年度积分:4 +## 译者:[DAA233](https://github.com/DAA233) 历史贡献积分:23 当前积分:23 年度积分:23 |文章|类型|积分| |------|-------|-------| -|[如何逃离 async/await 地狱](https://juejin.im/post/5aefbb48f265da0b9b073c40)|校对|4| +|[如何使用 Pandas 重写你的 SQL 查询以及其他操作](https://juejin.im/post/5b5e5b2ee51d4517df1510c7)|校对|1.5| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|4| +|[介绍 Google Play 上新的优质 Android 应用与游戏](https://juejin.im/post/5ae978d151882567370633ca)|校对|0.5| +|[给人类的机器学习指南🤖👶](https://juejin.im/post/5b136f12f265da6e5415114b)|校对|3| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|10| +|[我在编程初级阶段常犯的错误](https://juejin.im/post/5ae97af6f265da0ba062f797)|校对|4| ## 译者:[Gladysgong](https://github.com/Gladysgong) 历史贡献积分:1 当前积分:1 年度积分:1 @@ -5661,8 +5993,791 @@ |------|-------|-------| |[使用 python 和 keras 实现卷积神经网络](https://juejin.im/post/5aefb0f351882567336aa3c7)|校对|1| -## 译者:[shery15](https://github.com/shery15) 历史贡献积分:10 当前积分:10 年度积分:10 +## 译者:[shery](https://github.com/shery) 历史贡献积分:33 当前积分:3 年度积分:33 |文章|类型|积分| |------|-------|-------| +|推荐优秀英文文章|奖励|1| +|[./dogs.html 和 /dogs.html 间有什么区别?](https://juejin.im/post/5ba7a5dfe51d450e4a1bc136)|翻译|2| +|2018 年 9 月兑 Octoplush 1 个|减去积分|30| +|推荐多篇优秀英文文章|奖励|2| +|[如何使用原生 JavaScript 构建简单的 Chrome 扩展程序](https://juejin.im/post/5b98a58b6fb9a05cec4d92e0)|翻译|6| +|推荐优秀英文文章两篇|奖励|1| +|[使用原生 JavaScript 构建状态管理系统](https://juejin.im/post/5b763528e51d45559e3a5b64)|翻译|6.5| +|[或许你并不需要 Rust 和 WASM 来提升 JS 的执行效率 — 第一部分](https://juejin.im/post/5b24cf7e51882574c2652f61)|翻译|4.5| |[设计大型 JavaScript 应用程序](https://juejin.im/post/5aedd93b51882567244ddfc0)|翻译|10| + +## 译者:[isiyin](https://github.com/isiyin) 历史贡献积分:2 当前积分:2 年度积分:2 + +|文章|类型|积分| +|------|-------|-------| +|[在浏览器里使用 TenserFlow.js 实时估计人体姿态](https://juejin.im/post/5afd833b5188254270642ff3)|校对|2| + +## 译者:[sophiayang1997](https://github.com/sophiayang1997) 历史贡献积分:20.5 当前积分:1.5 年度积分:20.5 + +|文章|类型|积分| +|------|-------|-------| +|2018 年 9 月兑 掘金笔记本一个,比特币两个|减去积分|12| +|[为 GitHub 项目做出贡献的初学者指南](https://juejin.im/entry/5b2e58ba6fb9a00e4966ee4b)|翻译|2| +|[一个简单的 ES6 Promises 指南](https://juejin.im/post/5b0eb3b1f265da08f31e770a)|翻译|6| +|[新的 CSS 特性正在改变网页设计](https://juejin.im/post/5b0cae8c6fb9a009de14c833)|翻译|7| +|2018 年 6 月兑掘金笔记本 1 个|减去积分|8| +|[怎样更好地使用 Vue:我在新工作中遇到的一些问题清单](https://juejin.im/post/5b0a36366fb9a07a9c04aca2)|翻译|3| +|[JavaScript 是如何工作的:深入网络层 + 如何优化性能和安全](https://juejin.im/post/5b02ae48518825429d1f9aff)|校对|1.5| +|[可用但最不常见的 HTML5 标签](https://juejin.im/post/5b0026e8f265da0b8c253c2a)|校对|1| + +## 译者:[SpielbergGao](https://github.com/SpielbergGao) 历史贡献积分:2 当前积分:2 年度积分:2 + +|文章|类型|积分| +|------|-------|-------| +|[Awesome Flutter 校对](https://github.com/xitu/awesome-flutter)|校对|1| +|[TensorFlow 官方文档翻译](https://github.com/xitu/tensorflow-docs)|翻译校对|1| + +## 译者:[lihanxiang](https://github.com/lihanxiang) 历史贡献积分:38 当前积分:20 年度积分:38 + +|文章|类型|积分| +|------|-------|-------| +|[如何在六个月或更短的时间内成为DevOps 工程师,第三部分:版本控制](https://juejin.im/post/5bb067bfe51d450e905a0aa4)|校对|2| +|[如何在六个月或更短的时间内成为DevOps 工程师,第2部分:配置](https://juejin.im/post/5baf677df265da0a951ee8f5)|校对|3| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|8| +|[设计 QA 在应用程序设计中的重要性](https://juejin.im/post/5b89421651882542da339868)|翻译|3| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3| +|2018 年 8 月兑掘金桌垫 1 个|减去积分|10| +|[自然语言处理真是有趣](https://juejin.im/post/5b6d08e2f265da0f9c67cf0b)|翻译|9| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1| +|[2018 年,如何选择最好的静态站点生成器](https://juejin.im/post/5b47079bf265da0faa3655be)|校对|0.5| +|2018 年 7 月兑纪念币 4 个|减去积分|8| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6| +|[一种更简单的途径在 Java 中进行函数式编程](https://juejin.im/post/5b24aa5f6fb9a00e4d53e0c9)|校对|0.5| +|[创造华丽 UI 的 7 个规则(Part 1)](https://juejin.im/post/5b1dcc7d5188257d7d71946a)|校对|0.5| +|[那些我们不需要的 HTTP 头信息](https://juejin.im/post/5b06c89df265da0db35022d8)|校对|1| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|0.5| + +## 译者:[IveHD](https://github.com/IveHD) 历史贡献积分:17 当前积分:17 年度积分:17 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|4| +|[React 实现条件渲染的多种方式和性能考量](https://juejin.im/post/5b3e34905188251b1f223ad3)|翻译|5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2| + +## 译者:[menq](https://github.com/menq) 历史贡献积分:12 当前积分:12 年度积分:12 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2| + +## 译者:[lwjcjmx123](https://github.com/lwjcjmx123) 历史贡献积分:16 当前积分:16 年度积分:16 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|7| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6| +|[部署!=发布(第二部分)](https://juejin.im/post/5b03d4ac6fb9a07a9d70ab5f)|翻译|3| + +## 译者:[shisaq](https://github.com/shisaq) 历史贡献积分:2.5 当前积分:2.5 年度积分:2.5 + +|文章|类型|积分| +|------|-------|-------| +|[WWDC 2018:关于iOS 12、iPad Pro、新MacBooks或者更多产品的所有预测](https://juejin.im/post/5b056d485188256710601ecc)|校对|2.5| + +## 译者:[xxholly32](https://github.com/xxholly32) 历史贡献积分:2.5 当前积分:2.5 年度积分:2.5 + +|文章|类型|积分| +|------|-------|-------| +|[怎样更好地使用 Vue:我在新工作中遇到的一些问题清单](https://juejin.im/post/5b0a36366fb9a07a9c04aca2)|校对1| +|[可微可塑性:一种学会学习的新方法](https://juejin.im/post/5b055308f265da0ba063879d)|校对|1.5| + +## 译者:[zhongdeming428](https://github.com/zhongdeming428) 历史贡献积分:22 当前积分:7 年度积分:22 + +|文章|类型|积分| +|------|-------|-------| +|2018 年 8 月兑掘金桌垫和集智马克杯各一个|减去积分|15| +|[将项目迁移到 Yarn 然后又迁回到 npm](https://juejin.im/post/5b3b5735f265da0f894b443d)|翻译|3| +|[怎样使用简单的三角函数来创建更好的加载动画](https://juejin.im/post/5b33055f518825748871c590)|翻译|6| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6| +|[从零开始,在 Redux 中构建时间旅行式调试](https://juejin.im/post/5b24c0bce51d4558ba1a5584)|校对|1| +|[怎样(以及为什么要)保持你的 Git 提交记录的整洁](https://juejin.im/post/5b29060ee51d4558cd2adac0)|翻译|4| +|[什么是 JavaScript 生成器?如何使用生成器?](https://juejin.im/post/5b14faf2f265da6e4d5af3b9)|校对|0.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1.5| + +## 译者:[androidxiao](https://github.com/androidxiao) 历史贡献积分:20 当前积分:10 年度积分:20 + +|文章|类型|积分| +|------|-------|-------| +|[Kotlin 揭秘:理解速记 Lambda 语法](https://juejin.im/post/5b59c1945188251b3950ea6f)|翻译|3.5| +|[如何在 Flutter 中设计 LinearLayout?](https://juejin.im/post/5b3edeb16fb9a04fe820ccbc)|翻译|5| +|[绘图技巧的快速入门之:6 个绘图练习,让你立即上手!](https://juejin.im/post/5b39823fe51d4558ca674cff)|校对|0.5| +|2018 年 6 月兑集智纪念水杯 2 个|减去积分|10| +|[带你领略 ConstraintLayout 1.1 的新功能](https://juejin.im/post/5b013e6f51882542c760dc7b)|校对|0.5| +|[如何针对 Android 优化您的应用(Go 版)](https://juejin.im/post/5b21cd246fb9a01e754642f3)|翻译|6.5| +|[通过安全浏览保护 WebView](https://juejin.im/post/5b0fb8a06fb9a00a25192a3f)|翻译|1.5| +|[在 Android P 中使用默认的 TLS 来保护你的用户](https://juejin.im/post/5b29030551882574b4094a6f)|校对|1| +|[Awesome Flutter 校对](https://github.com/xitu/awesome-flutter)|校对|1.5| + +## 译者:[hannahGu](https://github.com/hannahGu) 历史贡献积分:2 当前积分:2 年度积分:2 + +|文章|类型|积分| +|------|-------|-------| +|[使用 styled-components 的 React 服务器端渲染极简指南](https://juejin.im/post/5b0f9a4c51882515791502d0)|校对|0.5| +|[新的 CSS 特性正在改变网页设计](https://juejin.im/post/5b0cae8c6fb9a009de14c833)|校对|1.5| + +## 译者:[elliott-zhao](https://github.com/elliott-zhao) 历史贡献积分:35 当前积分:35 年度积分:35 + +|文章|类型|积分| +|------|-------|-------| +|[使用 Web Beacon API 记录活动](https://juejin.im/post/5b694b5de51d4519700fa56a)|翻译|5| +|[通过插图学习 Go 的并发](https://juejin.im/post/5b2cd078e51d4558b70ca399)|翻译|4.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3.5| +|[在 Laravel 应用程序之间共享数据库](https://juejin.im/post/5b17407fe51d4506a14db524)|翻译|5| +|[[字幕翻译] 玛利亚塔·维加亚 — 什么是 Python 核心开发者?— PyCon 2018](https://juejin.im/post/5b152c825188257d8a48d4a8)|翻译|12| +|[使用 styled-components 的 React 服务器端渲染极简指南](https://juejin.im/post/5b0f9a4c51882515791502d0)|翻译|4| +|[JavaScript 是如何工作的:Service Worker 的生命周期与使用场景](https://juejin.im/post/5b14c1d86fb9a01e700ff2f2)|校对|1| + +## 译者:[lance10030](https://github.com/lance10030) 历史贡献积分:10.5 当前积分:10.5 年度积分:10.5 + +|文章|类型|积分| +|------|-------|-------| +|[让我们一起解决“this”难题  —  第二部分](https://juejin.im/post/5b6915cce51d4519962f0ca7)|校对|1| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|8| +|[使用 styled-components 的 React 服务器端渲染极简指南](https://juejin.im/post/5b0f9a4c51882515791502d0)|校对|1.5| + +## 译者:[Teiyanana](https://github.com/Teiyanana) 历史贡献积分:0.5 当前积分:0.5 年度积分:0.5 + +|文章|类型|积分| +|------|-------|-------| +|[为什么 VueX 是前端与 API 之间的完美接口](https://juejin.im/post/5b14d1bd6fb9a01e4508bfc1)|校对|0.5| + +## 译者:[maoqyhz](https://github.com/maoqyhz) 历史贡献积分:22 当前积分:22 年度积分:22 + +|文章|类型|积分| +|------|-------|-------| +|[为 GitHub 项目做出贡献的初学者指南](https://juejin.im/entry/5b2e58ba6fb9a00e4966ee4b)|校对|1.5| +|[Kubernetes 分布式应用部署和人脸识别 app 实例](https://juejin.im/post/5b2db0576fb9a00e50313904)|翻译|10| +|[从 Java EE 8 Security API 开始 — 第一部分](https://juejin.im/post/5b35a8d4f265da599423598b)|校对|0.5| +|[创造华丽 UI 的 7 个规则(Part 2)](https://juejin.im/post/5b2a1554e51d4558cc35b3be)|校对|2| +|[一种更简单的途径在 Java 中进行函数式编程](https://juejin.im/post/5b24aa5f6fb9a00e4d53e0c9)|翻译|4| +|[情景活动感知识别的 Transition API 新特性面向全体开发者开放](https://juejin.im/post/5b21cfe16fb9a01e561a4b26)|校对|0.5| +|[深度学习中所需的线性代数知识](https://juejin.im/post/5b19d99ae51d4506d81a7a2f)|翻译|3.5| + +## 译者:[zmkoo000](https://github.com/zmkoo000) 历史贡献积分:1.5 当前积分:1.5 年度积分:1.5 + +|文章|类型|积分| +|------|-------|-------| +|[怎样(以及为什么要)保持你的 Git 提交记录的整洁](https://juejin.im/post/5b29060ee51d4558cd2adac0)|校对|1.5| + +## 译者:[xujunjiejack](https://github.com/xujunjiejack) 历史贡献积分:13 当前积分:13 年度积分:13 + +|文章|类型|积分| +|------|-------|-------| +|[为什么 UX,UI,CX,IA,IxD 和其他类型的设计都是愚蠢的](https://juejin.im/post/5b3588f2e51d4558dd69a44c)|校对|1.5| +|[创造华丽 UI 的 7 个规则(Part 2)](https://juejin.im/post/5b2a1554e51d4558cc35b3be)|翻译|9| +|[创造华丽 UI 的 7 个规则(Part 1)](https://juejin.im/post/5b1dcc7d5188257d7d71946a)|校对|2.5| + +## 译者:[KyleLan329](https://github.com/KyleLan329) 历史贡献积分:0.5 当前积分:0.5 年度积分:0.5 + +|文章|类型|积分| +|------|-------|-------| +|[Vue.js 还是 React?你会选择哪一个?为什么?](https://juejin.im/post/5b25b3a16fb9a00e70686180)|校对|0.5| + +## 译者:[geniusq1981](https://github.com/geniusq1981) 历史贡献积分:69.5 当前积分:24.5 年度积分:69.5 + +|文章|类型|积分| +|------|-------|-------| +|2018 年 9 月兑树莓派套餐 1 套|减区积分|45| +|[如何优化企业级规模的 Node.js 应用程序](https://juejin.im/post/5b83c639f265da436d7e4c5e)|校对|1.5| +|[2018 年七个通过脑电图分析实现“读心术”的 Javascript 库](https://juejin.im/post/5b7f63e651882542c20f22f0)|翻译|4| +|[用 Flutter 写一个待办事项应用](https://juejin.im/post/5b4f52275188251b11096a28)|校对|2| +|[使用 Flutter 制作 3D 翻转动画](https://juejin.im/post/5b5534c951882562b9248294)|校对|1| +|[在 Flutter 中实现微光闪烁效果](https://juejin.im/post/5b552d516fb9a04fc9374978)|翻译|3.5| +|[如何用 Flutter 来创建一个带有底部导航栏的应用程序](https://juejin.im/post/5b4862f56fb9a04f9c43b218)|翻译|5| +|[让我们一起解决“this”难题  —  第二部分](https://juejin.im/post/5b6915cce51d4519962f0ca7)|翻译|6| +|[如何使用 Python 和 BeautifulSoup 抓取网站内容](https://juejin.im/post/5b74fcec51882561446fb97f)|翻译|3.5| +|[如何使用 Pandas 重写你的 SQL 查询以及其他操作](https://juejin.im/post/5b5e5b2ee51d4517df1510c7)|翻译|6| +|[我如何使用 Node.js 来实现工作自动化](https://juejin.im/post/5b4fe75ef265da0f54052138)|翻译|3| +|[[字幕翻译] Andrew Godwin — Django 异步 — PyCon 2018](https://www.bilibili.com/video/av24571596/)|翻译|12| +|[Envion 通过区块链采矿来支持可再生能源的发展](https://juejin.im/post/5b47f6c2e51d45191b611b09)|翻译|3| +|[能源行业聚焦基于区块链技术的加密货币](https://juejin.im/post/5b47f4a9e51d45198a2eb75b)|翻译|3.5| +|[或许你并不需要 Rust 和 WASM 来提升 JS 的执行效率 — 第二部分](https://juejin.im/post/5b357c20f265da595f0d3f91)|翻译|4| +|[Android 应用和游戏的无障碍开发介绍](https://juejin.im/post/5b31ea42e51d455882380119)|翻译|4| +|[如何避免拍脑袋想出的产品优先策略](https://juejin.im/post/5b37a0156fb9a00e78666072)|校对|1.5| +|[前端开发框架实战对比(2018年更新)](https://juejin.im/post/5b20a5996fb9a01e6b2c21ec)|翻译|2.5| +|[为何前端开发如此不稳定](https://juejin.im/post/5b1f2f1ae51d4506894983ae)|校对|2.5| +|[或许你并不需要 Rust 和 WASM 来提升 JS 的执行效率 — 第一部分](https://juejin.im/post/5b24cf7e51882574c2652f61)|校对|1| + +## 译者:[HCMY](https://github.com/HCMY) 历史贡献积分:2 当前积分:2 年度积分:2 + +|文章|类型|积分| +|------|-------|-------| +|[Flutter 挑战之 WhatsApp](https://juejin.im/post/5b70f42851882560ec4b190d)|校对|1| +|[Kubernetes 分布式应用部署和人脸识别 app 实例](https://juejin.im/post/5b2db0576fb9a00e50313904)|校对|0.5| +|[[字幕翻译] James Bennett — 理解 Python 字节码 — PyCon 2018](https://github.com/xitu/gold-miner/issues/3990)|校对|0.5| + +## 译者:[kasheemlew](https://github.com/kasheemlew) 历史贡献积分:14.5 当前积分:14.5 年度积分:14.5 + +|文章|类型|积分| +|------|-------|-------| +|[用 Python 实现马尔可夫链的初学者教程](https://juejin.im/post/5bb031d06fb9a05cdb104888)|校对|2| +|[SmartyStreets 的 Go 测试探索之路](https://juejin.im/post/5ba83f2ff265da0a867c3818)|翻译|4.5| +|[./dogs.html 和 /dogs.html 间有什么区别?](https://juejin.im/post/5ba7a5dfe51d450e4a1bc136)|校对|1| +|[Erlang 之禅第二部分](https://juejin.im/post/5b3ea3ed6fb9a04fb8772a2c)|校对|2.5| +|[Erlang 之禅第一部分](https://juejin.im/post/5b2a16cce51d4558d726e8c9)|校对|2| +|[[字幕翻译] 玛利亚塔·维加亚 — 什么是 Python 核心开发者?— PyCon 2018](https://juejin.im/post/5b152c825188257d8a48d4a8)|校对|2.5| + +## 译者:[satansk](https://github.com/satansk) 历史贡献积分:2 当前积分:2 年度积分:2 + +|文章|类型|积分| +|------|-------|-------| +|[函数式 JavaScript 编程的简短介绍](https://juejin.im/post/5b8f97135188255c4a70f999)|校对|0.5| +|[Erlang 之禅第二部分](https://juejin.im/post/5b3ea3ed6fb9a04fb8772a2c)|校对|0.5| +|[一种更简单的途径在 Java 中进行函数式编程](https://juejin.im/post/5b24aa5f6fb9a00e4d53e0c9)|校对|1| + +## 译者:[FesonX](https://github.com/FesonX) 历史贡献积分:4.5 当前积分:4.5 年度积分:4.5 + +|文章|类型|积分| +|------|-------|-------| +|[自然语言处理真是有趣](https://juejin.im/post/5b6d08e2f265da0f9c67cf0b)|校对|3| +|[支撑现代存储系统的算法](https://juejin.im/post/5b10f80b5188257d92206851)|校对|1.5| + +## 译者:[7Ethan](https://github.com/7Ethan) 历史贡献积分:10.5 当前积分:10.5 年度积分:10.5 + +|文章|类型|积分| +|------|-------|-------| +|[Python 中的无监督学习算法](https://juejin.im/post/5bab10ed6fb9a05d1f2211b6)|校对|1.5| +|[容器,虚拟机以及 Docker 的初学者入门介绍](https://juejin.im/entry/5bada97f6fb9a05d0e2e7014/detail)|校对|2| +|[Erlang 之禅第二部分](https://juejin.im/post/5b3ea3ed6fb9a04fb8772a2c)|翻译|5.5| +|[Erlang 之禅第一部分](https://juejin.im/post/5b2a16cce51d4558d726e8c9)|校对|1.5| + +## 译者:[Moosphan](https://github.com/Moosphan) 历史贡献积分:3 当前积分:3 年度积分:3 + +|文章|类型|积分| +|------|-------|-------| +|[带你领略 ConstraintLayout 1.1 的新功能](https://juejin.im/post/5b013e6f51882542c760dc7b)|翻译|3| + +## 译者:[yingye](https://github.com/yingye) 历史贡献积分:0.5 当前积分:0.5 年度积分:0.5 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|0.5| + +## 译者:[MeFelixWang](https://github.com/MeFelixWang) 历史贡献积分:41 当前积分:41 年度积分:41 + +|文章|类型|积分| +|------|-------|-------| +|[用 Workers 让静态网站动态化](https://juejin.im/post/5b95c5375188255c6e70422a)|翻译|5| +|[构建未来的设计生态系统](https://juejin.im/post/5b992be8f265da0aa3591346)|翻译|4| +|[JavaScript 2018 中即将迎来的新功能:异步生成器及更好的正则表达式](https://juejin.im/post/5b95be51f265da0adc18ac08)|翻译|6| +|[用 API 请求制作赏心悦目的 UX](https://juejin.im/post/5b992d13e51d450e894de541)|翻译|5| +|[简单的响应式现代 css 网格布局](https://juejin.im/post/5b8b4ddef265da436d7e594d)|翻译|4| +|[布局的下一次革新会是怎样的](https://juejin.im/post/5b85586ce51d4538c77a9cc1)|翻译|4| +|[挑战 Flutter 之 Twitter](https://juejin.im/post/5b6f9220f265da2816595999)|翻译|2| +|[挑战 Flutter 之 YouTube](https://juejin.im/post/5b6e4108e51d451963502877)|翻译|3| +|[深入 Flutter 之手势](https://juejin.im/post/5b70eee8e51d456682516d36)|翻译|5.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2.5| + +## 译者:[STLighter](https://github.com/STLighter) 历史贡献积分:3 当前积分:3 年度积分:3 + +|文章|类型|积分| +|------|-------|-------| +|修订文章性能指标都是些什么鬼?|修订|3| + +## 译者:[Yuhanlolo](https://github.com/Yuhanlolo) 历史贡献积分:14.5 当前积分:14.5 年度积分:14.5 + +|文章|类型|积分| +|------|-------|-------| +|[基于 Node.js 的 Alexa Skills Kit 发布了!](https://juejin.im/post/5b4790b6f265da0f5d4cbec5)|翻译|5| +|[海量视频时代下的内容发现之旅](https://juejin.im/post/5b441c895188251aad20f644)|翻译|4.5| +|[PWA 再进化,可以生成一个安卓原生的 WebAPK 了](https://juejin.im/post/5b2fb7f2e51d4558b46667be)|翻译|3| +|[如何避免拍脑袋想出的产品优先策略](https://juejin.im/post/5b37a0156fb9a00e78666072)|校对|2| + +## 译者:[DateBro](https://github.com/DateBro) 历史贡献积分:41 当前积分:-4 年度积分:41 + +|文章|类型|积分| +|------|-------|-------| +|2018 年 9 月兑树莓派 1 套|减去积分|45| +|[Flutter 的英雄和流氓们 —— 为 Flutterverse 带来平衡](https://juejin.im/post/5b8e75a46fb9a019f928e678)|翻译|5| +|[为什么你需要关注一下 Flutter](https://juejin.im/post/5b508c7cf265da0f955ccb09)|翻译|4| +|[用 Flutter 写一个待办事项应用](https://juejin.im/post/5b4f52275188251b11096a28)|翻译|6| +|[深入了解 Flutter](https://juejin.im/post/5b5c3736e51d4519634fe799)|翻译|4| +|[在 Flutter 中解析复杂的 JSON](https://juejin.im/post/5b5d782ae51d45191c7e7fb3)|翻译|5| +|[移动技术在改善财务健康方面的作用](https://juejin.im/post/5b5485c0e51d45355d51ca1b)|翻译|4| +|[为什么每个 Android 开发者都应该尝试 Flutter](https://juejin.im/post/5b5e70ffe51d4518e311b63d)|翻译|1.5| +|[如何用 Flutter 来创建一个带有底部导航栏的应用程序](https://juejin.im/post/5b4862f56fb9a04f9c43b218)|校对|1| +|[基于 Node.js 的 Alexa Skills Kit 发布了!](https://juejin.im/post/5b4790b6f265da0f5d4cbec5)|校对|0.5| +|[React Native:回顾 Udacity 移动工程团队的使用历程](https://juejin.im/post/5b421b606fb9a04fd15ff802)|校对|1.5| +|[海量视频时代下的内容发现之旅](https://juejin.im/post/5b441c895188251aad20f644)|校对|1.5| +|[Flutter 组件到底是什么?](https://juejin.im/post/5b46f836f265da0f8f2025f7)|校对|1| +|[Airbnb 中的 React Native(五 — 完):Airbnb 移动端路在何方?](https://juejin.im/post/5b46f92de51d45198e721cd7)|校对|1.5| +|[Airbnb 中的 React Native(四):React Native 退役](https://juejin.im/post/5b447b1e6fb9a04fd3437dad)|翻译|1| +|[Airbnb 中的 React Native (三):建立一个跨平台的移动端团队](https://juejin.im/post/5b446177f265da0f7c4faec8)|校对|1.5| +|[如何用 Android vitals 解决应用程序的质量问题](https://juejin.im/post/5b3229a7f265da598c09dd4a)|校对|1| +|[Android 应用和游戏的无障碍开发介绍](https://juejin.im/post/5b31ea42e51d455882380119)|校对|1| + +## 译者:[D-kylin](https://github.com/D-kylin) 历史贡献积分:18 当前积分:18 年度积分:18 + +|文章|类型|积分| +|------|-------|-------| +|[论 Rust 和 WebAssembly 对源码地址索引的极限优化](https://juejin.im/post/5b51948a5188251ac771c064)|翻译|12.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|4| +|[或许你并不需要 Rust 和 WASM 来提升 JS 的执行效率 — 第二部分](https://juejin.im/post/5b357c20f265da595f0d3f91)|校对|1.5| + +## 译者:[liruochen1998](https://github.com/liruochen1998) 历史贡献积分:3.5 当前积分:3.5 年度积分:3.5 + +|文章|类型|积分| +|------|-------|-------| +|[我是如何从零开始建立一个网络爬虫来实现我的求职自动化的](https://juejin.im/post/5b3b40896fb9a04f9e22e927)|校对|1.5| +|[绘图技巧的快速入门之:6 个绘图练习,让你立即上手!](https://juejin.im/post/5b39823fe51d4558ca674cff)|校对|0.5| +|[苹果公司如何修复 3D Touch](https://juejin.im/post/5b35e5886fb9a00e3642724f)|校对|0.5| +|[怎样使用简单的三角函数来创建更好的加载动画](https://juejin.im/post/5b33055f518825748871c590)|校对|1| + +## 译者:[JackEggie](https://github.com/JackEggie) 历史贡献积分:3 当前积分:3 年度积分:3 + +|文章|类型|积分| +|------|-------|-------| +|[从 Java EE 8 Security API 开始 — 第一部分](https://juejin.im/post/5b35a8d4f265da599423598b)|校对|3| + +## 译者:[cf020031308](https://github.com/cf020031308) 历史贡献积分:50 当前积分:50 年度积分:50 + +|文章|类型|积分| +|------|-------|-------| +|[2018 年度最佳数据库即服务解决方案](https://juejin.im/post/5ba784e3e51d450e9d64984d)|翻译|7| +|[关于工程师和影响力](https://juejin.im/post/5b8f9a96f265da0ab33125b0)|翻译|5.5| +|[Databook:通过元数据,Uber 将大数据转化为知识](https://juejin.im/post/5b800032e51d45389d3b4950)|翻译|7| +|[从 Cron 到 Airflow 的迁移中我们学到了什么](https://juejin.im/post/5b4c3575f265da0f7334bbc9)|翻译|4.5| +|[Robinhood 为什么使用 Airflow](https://juejin.im/post/5b4808f751882519ec07eaba)|翻译|5| +|[正则表达式要跑 5 天,所以我做了个工具将其缩短至 15 分钟。](https://juejin.im/post/5b6d426f6fb9a04fd1604341)|翻译|5| +|[编程语言和平台:一条被评论的推文串](https://juejin.im/post/5b4c2b75e51d45195b336d57)|翻译|8| +|[Python 与大数据:Airflow & Jupyter Notebook with Hadoop 3, Spark & Presto](https://juejin.im/post/5b5a7fdfe51d453526175687)|翻译|5| +|[Kubernetes 分布式应用部署和人脸识别 app 实例](https://juejin.im/post/5b2db0576fb9a00e50313904)|校对|3| + +## 译者:[zhangshiqin](https://github.com/zhangshiqin) 历史贡献积分:1.5 当前积分:1.5 年度积分:1.5 + +|文章|类型|积分| +|------|-------|-------| +|[为 GitHub 项目做出贡献的初学者指南](https://juejin.im/entry/5b2e58ba6fb9a00e4966ee4b)|校对|1.5| + +## 译者:[upupming](https://github.com/upupming) 历史贡献积分:8 当前积分:8 年度积分:8 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3.5| +|[修订 The JavaScript Tutorial](https://github.com/xitu/javascript-tutorial-zh/pull/151)|修订|1| + +## 译者:[ssshooter](https://github.com/ssshooter) 历史贡献积分:32 当前积分:32 年度积分:32 + +|文章|类型|积分| +|------|-------|-------| +|[新手开发者须知](https://juejin.im/post/5bade6a76fb9a05d32515cf0)|翻译|3| +|[如果界面产品设计师设计实体产品](https://juejin.im/post/5baf9697e51d456f087ba2a8)|翻译|2.5| +|[Javascript(ES6)Generator 入门](https://juejin.im/post/5b4c22aa6fb9a04faf479be1)|翻译|6| +|[由 CSS 网格系统的创造者们所讲述的故事](https://juejin.im/post/5b503d9ef265da0f504a518e)|校对|1.5| +|[ECMAScript 修饰器微指南](https://juejin.im/post/5b543d8af265da0f4a4e711f)|校对|2| +|[用 React 和 Vue 创建了两个完全相同的应用后,发现了这些差异](https://juejin.im/post/5b63f50a5188253128337110)|校对|1.5| +|[一份在你的 iPhone 上平衡实用和美观的指南](https://juejin.im/post/5b4c0d0ce51d4519503b1e67)|校对|1.5| +|[虚构问题,低质量软件的根源](https://juejin.im/post/5b65122be51d4517c564d54f)|翻译|5.5| +|[2018 年,如何选择最好的静态站点生成器](https://juejin.im/post/5b47079bf265da0faa3655be)|翻译|7| +|[Airbnb 在 React Native 上下的赌注(二):技术细节](https://juejin.im/post/5b3b40a26fb9a04fab44e797)|校对|1.5| + +## 译者:[tutaizi](https://github.com/tutaizi) 历史贡献积分:2.5 当前积分:2.5 年度积分:2.5 + +|文章|类型|积分| +|------|-------|-------| +|[React 实现条件渲染的多种方式和性能考量](https://juejin.im/post/5b3e34905188251b1f223ad3)|翻译|2.5| + +## 译者:[wyh888](https://github.com/wyh888) 历史贡献积分:1.5 当前积分:1.5 年度积分:1.5 + +|文章|类型|积分| +|------|-------|-------| +|[将项目迁移到 Yarn 然后又迁回到 npm](https://juejin.im/post/5b3b5735f265da0f894b443d)|校对|1.5| + +## 译者:[shisaq](https://github.com/shisaq) 历史贡献积分:1.5 当前积分:1.5 年度积分:1.5 + +|文章|类型|积分| +|------|-------|-------| +|[通过 SSH 远程使用 Python 解释器来运行 Flask](https://juejin.im/post/5b3e25a8e51d45191716d187)|校对|1.5| + +## 译者:[yqian1991](https://github.com/yqian1991) 历史贡献积分:37.5 当前积分:37.5 年度积分:37.5 + +|文章|类型|积分| +|------|-------|-------| +|[Apache Airflow 的关键概念](https://juejin.im/post/5b7ba247e51d4538d42ab6a0)|校对|2| +|[如何在数据科学中写出生产层面的代码?](https://juejin.im/post/5b7adb7751882542d63b2805)|校对|1.5| +|[Python 的多线程与多进程](https://juejin.im/post/5b84f3086fb9a01a1a27cedb)|校对|1.5| +|[Databook:通过元数据,Uber 将大数据转化为知识](https://juejin.im/post/5b800032e51d45389d3b4950)|校对|2| +|[使用 Python 进行自动化特征工程](https://juejin.im/post/5b6ea0e4e51d4519044adff0)|校对|2.5| +|[从 Cron 到 Airflow 的迁移中我们学到了什么](https://juejin.im/post/5b4c3575f265da0f7334bbc9)|校对|2| +|[Airflow: 一个工作流程管理平台](https://juejin.im/post/5b5bd2b6f265da0f60131d0c)|翻译|4| +|[Web 应用架构基础课](https://juejin.im/post/5b69a8eef265da0f926baa56)|校对||2.5| +|[Robinhood 为什么使用 Airflow](https://juejin.im/post/5b4808f751882519ec07eaba)|校对|1.5| +|[我们是如何高效实现一致性哈希的](https://juejin.im/post/5b5488a96fb9a04fad3a181a)|翻译|8| +|[在 UNIX 中,一切皆文件](https://juejin.im/post/5b652d346fb9a04fc03129e6)|校对|2| +|[Python 与大数据:Airflow & Jupyter Notebook with Hadoop 3, Spark & Presto](https://juejin.im/post/5b5a7fdfe51d453526175687)|校对|2| +|推荐多篇优秀英文文章|奖励|3| +|[使用 Python 实现接缝裁剪算法](https://juejin.im/post/5b46ee8e6fb9a04fc436ad60)|校对|0.5| +|[基于 Node.js 的 Alexa Skills Kit 发布了!](https://juejin.im/post/5b4790b6f265da0f5d4cbec5)|校对|2.5| + +## 译者:[trexguo](https://github.com/trexguo) 历史贡献积分:3.5 当前积分:3.5 年度积分:3.5 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1.5| + +## 译者:[Tivcrmn](https://github.com/Tivcrmn) 历史贡献积分:10 当前积分:0 年度积分:10 + +|文章|类型|积分| +|------|-------|-------| +|2018 年 9 月兑 GitHub 贴纸 1 个和集智水杯 1 个|减去积分|10| +|[由 CSS 网格系统的创造者们所讲述的故事](https://juejin.im/post/5b503d9ef265da0f504a518e)|翻译|6| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1| + +## 译者:[Moonliujk](https://github.com/Moonliujk) 历史贡献积分:9 当前积分:9 年度积分:9 + +|文章|类型|积分| +|------|-------|-------| +|[用 30 分钟建立一个网站的方式来学习 Bootstrap 4](https://juejin.im/post/5b5eb7b2e51d451989055d9d)|校对|3.5| +|[无头渲染组件](https://juejin.im/post/5b5e919f51882519d3467f41)|校对|1| +|[让我们一起解决“this”难题  —  第二部分](https://juejin.im/post/5b6915cce51d4519962f0ca7)|校对|2| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2.5| + +## 译者:[xutaogit ](https://github.com/xutaogit ) 历史贡献积分:10 当前积分:10 年度积分:10 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| +|[2018 来谈谈 Web 组件](https://juejin.im/post/5b780a98e51d4538980bf5cf)|校对|2| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3| + +## 译者:[doctype233](https://github.com/doctype233) 历史贡献积分:12 当前积分:12 年度积分:12 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|7| +|[关于 React Motion 的简要介绍](https://juejin.im/post/5b48061551882519790c77f3)|翻译|4| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1| + +## 译者:[pmwangyang](https://github.com/pmwangyang) 历史贡献积分:26 当前积分:9 年度积分:26 + +|文章|类型|积分| +|------|-------|-------| +|[如何创建一个设计体系来赋能团队 —— 关注人,而非像素](https://juejin.im/post/5bac2a2fe51d450e942f4853)|翻译|7| +|[四个「为什么」:是什么在背后驱动你的产品?](https://juejin.im/post/5bac279cf265da0adc18d31a)|翻译|3| +|2018 年 9 月兑掘金桌垫 x 1,水杯 x 1,比特币 x 1|减去积分|17| +|[你 Ladar 中该珍藏的:iOS 布局语言](https://juejin.im/post/5b84fe97f265da437c43422f)|翻译|5| +|[在 UNIX 中,一切皆文件](https://juejin.im/post/5b652d346fb9a04fc03129e6)|翻译|4| +|[编程语言和平台:一条被评论的推文串](https://juejin.im/post/5b4c2b75e51d45195b336d57)|校对|2| +|[在 Sketch 中使用一个设计体系创作: 第二部分 [教程]](https://juejin.im/post/5b5d2a456fb9a04fc80b8f4b)|校对|0.5| +|[在 Sketch 中使用一个设计体系创作:第一部分 [教程]](https://juejin.im/post/5b591a655188257bca290b24)|翻译|4.5| + +## 译者:[Zheng7426](https://github.com/Zheng7426) 历史贡献积分:16 当前积分:16 年度积分:16 + +|文章|类型|积分| +|------|-------|-------| +|[函数式 JavaScript 编程的简短介绍](https://juejin.im/post/5b8f97135188255c4a70f999)|翻译|4| +|[Javascript(ES6)Generator 入门](https://juejin.im/post/5b4c22aa6fb9a04faf479be1)|校对|1| +|[由 CSS 网格系统的创造者们所讲述的故事](https://juejin.im/post/5b503d9ef265da0f504a518e)|校对|1| +|[用 30 分钟建立一个网站的方式来学习 Bootstrap 4](https://juejin.im/post/5b5eb7b2e51d451989055d9d)|翻译|4| +|[如何向带有插槽的 React 组件传递多个 Children](https://juejin.im/post/5b72935a6fb9a009724b3e4e)|翻译|3| +|[在 Sketch 中使用一个设计体系创作: 第二部分 [教程]](https://juejin.im/post/5b5d2a456fb9a04fc80b8f4b)|翻译|2.5| +|[在 Sketch 中使用一个设计体系创作:第一部分 [教程]](https://juejin.im/post/5b591a655188257bca290b24)|校对|0.5| + +## 译者:[Park-ma](https://github.com/Park-ma) 历史贡献积分:28.5 当前积分:28.5 年度积分:28.5 + +|文章|类型|积分| +|------|-------|-------| +|[如果界面产品设计师设计实体产品](https://juejin.im/post/5baf9697e51d456f087ba2a8)|校对|0.5| +|[探索 SMACSS:可扩展的模块化 CSS 框架](https://juejin.im/post/5ba234c85188255c38535a47)|翻译|6.5| +|[用 Workers 让静态网站动态化](https://juejin.im/post/5b95c5375188255c6e70422a)|校对|1.5| +|[如何使用原生 JavaScript 构建简单的 Chrome 扩展程序](https://juejin.im/post/5b98a58b6fb9a05cec4d92e0)|校对|1| +|[2018 年 iOS 开发找工作完全指南](https://juejin.im/post/5b7eca206fb9a019ff713277)|校对|2| +|[写一个完整的 Flutter App 是什么感觉](https://juejin.im/post/5b7e18c4e51d4538cf53d1f8)|校对|1.5| +|[如何像程序员般思考 —— 蕴含在问题解决中的经验](https://juejin.im/post/5b76839ae51d4566491c24bb)|校对|0.5| +|[如何提升设计到开发的协作效率](https://juejin.im/post/5b83609de51d4538c210a957)|校对|2| +|[一行 JavaScript 代码竟然让 FT.com 网站慢了十倍](https://juejin.im/post/5b7bb6dfe51d4538bf55aa5f)|校对|1.5| +|[2018 年七个通过脑电图分析实现“读心术”的 Javascript 库](https://juejin.im/post/5b7f63e651882542c20f22f0)|校对|2| +|[用 30 分钟建立一个网站的方式来学习 Bootstrap 4](https://juejin.im/post/5b5eb7b2e51d451989055d9d)|校对|0.5| +|[如何使用 Python 和 BeautifulSoup 抓取网站内容](https://juejin.im/post/5b74fcec51882561446fb97f)|翻译|1| +|[我是如何使用 Python 在 Medium 上找到并关注有趣的人](https://juejin.im/post/5b72c61851882561311fccce)|翻译|4| +|[使用 Python 进行自动化特征工程](https://juejin.im/post/5b6ea0e4e51d4519044adff0)|校对|1| +|[Airflow: 一个工作流程管理平台](https://juejin.im/post/5b5bd2b6f265da0f60131d0c)|校对|1| +|[正则表达式要跑 5 天,所以我做了个工具将其缩短至 15 分钟。](https://juejin.im/post/5b6d426f6fb9a04fd1604341)|校对|0.5| +|[Tab Bar 就是新的汉堡菜单](https://juejin.im/post/5b61684fe51d451986517e31)|校对|1.5| + +## 译者:[StellaBauhinia](https://github.com/StellaBauhinia) 历史贡献积分:15 当前积分:15 年度积分:15 + +|文章|类型|积分| +|------|-------|-------| +|[SmartyStreets 的 Go 测试探索之路](https://juejin.im/post/5ba83f2ff265da0a867c3818)|校对|2| +|[2018 年度最佳数据库即服务解决方案](https://juejin.im/post/5ba784e3e51d450e9d64984d)|校对|2| +|[构建未来的设计生态系统](https://juejin.im/post/5b992be8f265da0aa3591346)|校对|2| +|[常见网页设计错误一览](https://juejin.im/post/5b7984265188254312414c1f)|翻译|5| +|[使用 Web Beacon API 记录活动](https://juejin.im/post/5b694b5de51d4519700fa56a)|校对|2.5| +|[正则表达式要跑 5 天,所以我做了个工具将其缩短至 15 分钟。](https://juejin.im/post/5b6d426f6fb9a04fd1604341)|校对|1.5| + +## 译者:[DerekDick](https://github.com/DerekDick) 历史贡献积分:7 当前积分:7 年度积分:7 + +|文章|类型|积分| +|------|-------|-------| +|[实用 ProGuard 规则示例](https://juejin.im/post/5b72b49a5188256137188578)|翻译|6| +|[Airflow: 一个工作流程管理平台](https://juejin.im/post/5b5bd2b6f265da0f60131d0c)|校对|1| + +## 译者:[coolseaman](https://github.com/coolseaman) 历史贡献积分:4.5 当前积分:4.5 年度积分:4.5 + +|文章|类型|积分| +|------|-------|-------| +|[优化 MP4 视频以获得更快的流传输速度](https://juejin.im/post/5b72bed8e51d45661e00f693)|校对|1.5| +|[如何使用 Python 和 BeautifulSoup 抓取网站内容](https://juejin.im/post/5b74fcec51882561446fb97f)|校对|3| + +## 译者:[HaoChuan9421](https://github.com/HaoChuan9421) 历史贡献积分:12 当前积分:12 年度积分:12 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|6.5| +|[深入 Flutter 之手势](https://juejin.im/post/5b70eee8e51d456682516d36)|翻译|1.5| +|[优化 MP4 视频以获得更快的流传输速度](https://juejin.im/post/5b72bed8e51d45661e00f693)|翻译|4| + +## 译者:[hrt96](https://github.com/hrt96) 历史贡献积分:0.5 当前积分:0.5 年度积分:0.5 + +|文章|类型|积分| +|------|-------|-------| +|[优化 MP4 视频以获得更快的流传输速度](https://juejin.im/post/5b72bed8e51d45661e00f693)|校对|0.5| + +## 译者:[BillShiyaoZhang](https://github.com/BillShiyaoZhang) 历史贡献积分:4 当前积分:4 年度积分:4 + +|文章|类型|积分| +|------|-------|-------| +|[常见网页设计错误一览](https://juejin.im/post/5b7984265188254312414c1f)|校对|1.5| +|[实用 ProGuard 规则示例](https://juejin.im/post/5b72b49a5188256137188578)|校对|2| +|[如何向带有插槽的 React 组件传递多个 Children](https://juejin.im/post/5b72935a6fb9a009724b3e4e)|校对|0.5| + +## 译者:[huangyuanzhen](https://github.com/huangyuanzhen) 历史贡献积分:5.5 当前积分:5.5 年度积分:5.5 + +|文章|类型|积分| +|------|-------|-------| +|[探索 SMACSS:可扩展的模块化 CSS 框架](https://juejin.im/post/5ba234c85188255c38535a47)|校对|2| +|[2018 年七个通过脑电图分析实现“读心术”的 Javascript 库](https://juejin.im/post/5b7f63e651882542c20f22f0)|校对|1| +|[如何使用纯函数式 JavaScript 处理脏副作用](https://juejin.im/post/5b82bdb351882542e241ed32)|校对|1| +|[用 React 和 Vue 创建了两个完全相同的应用后,发现了这些差异](https://juejin.im/post/5b63f50a5188253128337110)|校对|1.5| + +## 译者:[coconilu](https://github.com/coconilu) 历史贡献积分:6 当前积分:6 年度积分:6 + +|文章|类型|积分| +|------|-------|-------| +|[使用原生 JavaScript 构建状态管理系统](https://juejin.im/post/5b763528e51d45559e3a5b64)|校对|3| +|[ECMAScript 修饰器微指南](https://juejin.im/post/5b543d8af265da0f4a4e711f)|校对|2| +|[让我们一起解决“this”难题  —  第二部分](https://juejin.im/post/5b6915cce51d4519962f0ca7)|校对|1| + +## 译者:[Gavin-Gong](https://github.com/Gavin-Gong) 历史贡献积分:13 当前积分:13 年度积分:13 + +|文章|类型|积分| +|------|-------|-------| +|[如何使用纯函数式 JavaScript 处理脏副作用](https://juejin.im/post/5b82bdb351882542e241ed32)|翻译|8.5| +|[关于 React Motion 的简要介绍](https://juejin.im/post/5b48061551882519790c77f3)|校对|1.5| +|[设计 React 组件 API](https://juejin.im/post/5b545f0b6fb9a04fc93748ba)|翻译|3| + +## 译者:[Eternaldeath](https://github.com/Eternaldeath) 历史贡献积分:2 当前积分:2 年度积分:2 + +|文章|类型|积分| +|------|-------|-------| +|[一行 JavaScript 代码竟然让 FT.com 网站慢了十倍](https://juejin.im/post/5b7bb6dfe51d4538bf55aa5f)|校对|1| +|[使用 Web Beacon API 记录活动](https://juejin.im/post/5b694b5de51d4519700fa56a)|校对|1| + +## 译者:[YueYongDev](https://github.com/YueYongDev) 历史贡献积分:9 当前积分:9 年度积分:9 + +|文章|类型|积分| +|------|-------|-------| +|[Flutter 挑战之 WhatsApp](https://juejin.im/post/5b70f42851882560ec4b190d)|翻译|2.5| +|[Flutter 系列入门教程五:网格](https://juejin.im/post/5b6915636fb9a04fd73a6ecf)|翻译|3.5| +|[Slidable:一个 Flutter 的故事](https://juejin.im/post/5b5e84b86fb9a04fa91bfeb6)|翻译|3| + +## 译者:[dayixinsheng](https://github.com/dayixinsheng) 历史贡献积分:1.5 当前积分:1.5 年度积分:1.5 + +|文章|类型|积分| +|------|-------|-------| +|[Slidable:一个 Flutter 的故事](https://juejin.im/post/5b5e84b86fb9a04fa91bfeb6)|校对|1.5| + +## 译者:[CoolRice](https://github.com/CoolRice) 历史贡献积分:27 当前积分:27 年度积分:27 + +|文章|类型|积分| +|------|-------|-------| +|[React Profiler 介绍](https://juejin.im/post/5ba0f8e4f265da0ab915bcf2)|翻译|4| +|[理解 JavaScript 中的执行上下文和执行栈](https://juejin.im/post/5ba32171f265da0ab719a6d7)|翻译|5| +|[this 到底指向什么 — 理解 JavaScript 中的 this、call、apply 和 bind](https://juejin.im/post/5b9f176b6fb9a05d3827d03f)|翻译|6| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| +|[CSS 变量和 JavaScript 让应用支持动态主题 🎨](https://juejin.im/post/5b9528de6fb9a05cf3710e00)|翻译|3| +|[简单的响应式现代 css 网格布局](https://juejin.im/post/5b8b4ddef265da436d7e594d)|校对|1| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|2| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|1| + +## 译者:[meterscao](https://github.com/meterscao) 历史贡献积分:9 当前积分:9 年度积分:9 + +|文章|类型|积分| +|------|-------|-------| +|[如何提升设计到开发的协作效率](https://juejin.im/post/5b83609de51d4538c210a957)|翻译|9| + +## 译者:[rockyzhengwu](https://github.com/rockyzhengwu) 历史贡献积分:12 当前积分:12 年度积分:12 + +|文章|类型|积分| +|------|-------|-------| +|[如何像程序员般思考 —— 蕴含在问题解决中的经验](https://juejin.im/post/5b76839ae51d4566491c24bb)|校对|2| +|[Sklearn 中的朴素贝叶斯分类器](https://juejin.im/post/5b8510be51882542d23a1d66)|校对|2| +|[用 Scikit-Learn 实现 SVM 和 Kernel SVM](https://juejin.im/post/5b7fd39af265da43831fa136)|翻译|6| +|[如何提升设计到开发的协作效率](https://juejin.im/post/5b83609de51d4538c210a957)|校对|2| + +## 译者:[zhusimaji](https://github.com/zhusimaji) 历史贡献积分:2 当前积分:2 年度积分:2 + +|文章|类型|积分| +|------|-------|-------| +|[用 Scikit-Learn 实现 SVM 和 Kernel SVM](https://juejin.im/post/5b7fd39af265da43831fa136)|校对|2| + +## 译者:[TrWestdoor](https://github.com/TrWestdoor) 历史贡献积分:2 当前积分:2 年度积分:2 + +|文章|类型|积分| +|------|-------|-------| +|[用 Scikit-Learn 实现 SVM 和 Kernel SVM](https://juejin.im/post/5b7fd39af265da43831fa136)|校对|2| + +## 译者:[unimp](https://github.com/unimp) 历史贡献积分:0.5 当前积分:0.5 年度积分:0.5 + +|文章|类型|积分| +|------|-------|-------| +|推荐优秀英文文章两篇|奖励|0.5| + +## 译者:[ShiqinHuo](https://github.com/ShiqinHuo) 历史贡献积分:2 当前积分:2 年度积分:2 + +|文章|类型|积分| +|------|-------|-------| +|[关于工程师和影响力](https://juejin.im/post/5b8f9a96f265da0ab33125b0)|校对|2| + +## 译者:[davelet](https://github.com/davelet) 历史贡献积分:3 当前积分:3 年度积分:3 + +|文章|类型|积分| +|------|-------|-------| +|[React Native 对 Flutter: 哪一个对创业公司更加友好?](https://juejin.im/post/5b949e5d5188255c791ae376)|校对|3| + +## 译者:[AmyFoxFN](https://github.com/AmyFoxFN) 历史贡献积分:1.5 当前积分:1.5 年度积分:1.5 + +|文章|类型|积分| +|------|-------|-------| +|[函数式 JavaScript 编程的简短介绍](https://juejin.im/post/5b8f97135188255c4a70f999)|校对|1.5| + +## 译者:[haoyuez](https://github.com/haoyuez) 历史贡献积分:3 当前积分:3 年度积分:3 + +|文章|类型|积分| +|------|-------|-------| +|[使用 Nexmo 和微软语音翻译 API 构建 Babel 鱼](https://juejin.im/post/5b8a78a3e51d4538a515cbda)|校对|3| + +## 译者:[QC-L](https://github.com/QC-L) 历史贡献积分:4 当前积分:4 年度积分:4 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|4| + +## 译者:[BriFuture](https://github.com/BriFuture) 历史贡献积分:17.5 当前积分:17.5 年度积分:17.5 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3| +|[this 到底指向什么 — 理解 JavaScript 中的 this、call、apply 和 bind](https://juejin.im/post/5b9f176b6fb9a05d3827d03f)|校对|3.5| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|7| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|4| + +## 译者:[YvongYang](https://github.com/YvongYang) 历史贡献积分:5 当前积分:5 年度积分:5 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|5| + +## 译者:[CoderMing](https://github.com/CoderMing) 历史贡献积分:16 当前积分:16 年度积分:16 + +|文章|类型|积分| +|------|-------|-------| +|[下一代包管理工具](https://juejin.im/post/5ba9830fe51d450e4d2fe208)|校对|1| +|[React Profiler 介绍](https://juejin.im/post/5ba0f8e4f265da0ab915bcf2)|翻译|4.5| +|[理解 JavaScript 中的执行上下文和执行栈](https://juejin.im/post/5ba32171f265da0ab719a6d7)|校对|1| +|[在 JavaScript 中 为什么你应当使用 map 和 filter 来替代 forEach](https://juejin.im/post/5b95c4fe5188255c402ae6fb)|校对|0.5| +|[JavaScript 2018 中即将迎来的新功能:异步生成器及更好的正则表达式](https://juejin.im/post/5b95be51f265da0adc18ac08)|校对|1.5| +|[CSS 变量和 JavaScript 让应用支持动态主题 🎨](https://juejin.im/post/5b9528de6fb9a05cf3710e00)|校对|1| +|[如何使用原生 JavaScript 构建简单的 Chrome 扩展程序](https://juejin.im/post/5b98a58b6fb9a05cec4d92e0)|校对|1| +|[JAVASCRIPT 日期权威指南](https://juejin.im/post/5b9b4b7bf265da0af6099ed8)|翻译|4.5| +|[现代浏览器内部揭秘(第一部分)](https://juejin.im/post/5b9b0932e51d450e9059c16a)|校对|1| + +## 译者:[c6n](https://github.com/c6n) 历史贡献积分:0.5 当前积分:0.5 年度积分:0.5 + +|文章|类型|积分| +|------|-------|-------| +|[JAVASCRIPT 日期权威指南](https://juejin.im/post/5b9b4b7bf265da0af6099ed8)|校对|0.5| + +## 译者:[diliburong](https://github.com/diliburong) 历史贡献积分:6 当前积分:6 年度积分:6 + +|文章|类型|积分| +|------|-------|-------| +|[下一代包管理工具](https://juejin.im/post/5ba9830fe51d450e4d2fe208)|翻译|2| +|[另外 5 种关于视觉和认知间差异的绘画练习](https://juejin.im/post/5baa5b45f265da0ab915cb7f)|校对|1.5| +|[在 JavaScript 中 为什么你应当使用 map 和 filter 来替代 forEach](https://juejin.im/post/5b95c4fe5188255c402ae6fb)|校对|0.5| +|[CSS 变量和 JavaScript 让应用支持动态主题 🎨](https://juejin.im/post/5b9528de6fb9a05cf3710e00)|校对|2| + +## 译者:[MagicGary](https://github.com/MagicGary) 历史贡献积分:3 当前积分:3 年度积分:3 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3| + +## 译者:[orange727](https://github.com/orange727) 历史贡献积分:3 当前积分:3 年度积分:3 + +|文章|类型|积分| +|------|-------|-------| +|[The JavaScript Tutorial 翻译](https://github.com/xitu/javascript-tutorial-en)|翻译校对|3| + +## 译者:[DerekTso](https://github.com/DerekTso) 历史贡献积分:3 当前积分:3 年度积分:3 + +|文章|类型|积分| +|------|-------|-------| +|推荐多篇优秀英文文章|奖励|3| + +## 译者:[samt42](https://github.com/samt42) 历史贡献积分:1 当前积分:1 年度积分:1 + +|文章|类型|积分| +|------|-------|-------| +|推荐一篇优秀英文文章|奖励|1| + +## 译者:[linxuesia](https://github.com/linxuesia) 历史贡献积分:4 当前积分:4 年度积分:4 + +|文章|类型|积分| +|------|-------|-------| +|[理解 JavaScript 中的执行上下文和执行栈](https://juejin.im/post/5ba32171f265da0ab719a6d7)|校对|2.5| +|[以申请大学流程来解释 JavaScript 的 filter](https://juejin.im/post/5b9f09685188255c5e66d60c)|校对|1.5| + +## 译者:[jianboy](https://github.com/jianboy) 历史贡献积分:16.5 当前积分:16.5 年度积分:16.5 + +|文章|类型|积分| +|------|-------|-------| +|[Python 中的无监督学习算法](https://juejin.im/post/5bab10ed6fb9a05d1f2211b6)|校对|1.5| +|[用 Python 实现马尔可夫链的初学者教程](https://juejin.im/post/5bb031d06fb9a05cdb104888)|校对|2| +|[如何在六个月或更短的时间内成为DevOps 工程师,第三部分:版本控制](https://juejin.im/post/5bb067bfe51d450e905a0aa4)|翻译|5| +|[如何在六个月或更短的时间内成为DevOps 工程师,第2部分:配置](https://juejin.im/post/5baf677df265da0a951ee8f5)|翻译|4| +|[容器,虚拟机以及 Docker 的初学者入门介绍](https://juejin.im/entry/5bada97f6fb9a05d0e2e7014/detail)|校对|2.5| +|[使用 Node 和 OAuth 2.0 构建一个简单的 REST API](https://juejin.im/post/5bb1d3f95188255c6a044d9a)|校对|1.5| + +## 译者:[yuwhuawang](https://github.com/yuwhuawang) 历史贡献积分:8.5 当前积分:8.5 年度积分:8.5 + +|文章|类型|积分| +|------|-------|-------| +|[正确实现 linkedPurchaseToken 以避免重复订阅](https://juejin.im/post/5baf9a3e6fb9a05ce2741437)|翻译|5| +|[新手开发者须知](https://juejin.im/post/5bade6a76fb9a05d32515cf0)|校对|1.5| +|[2018 年度最佳数据库即服务解决方案](https://juejin.im/post/5ba784e3e51d450e9d64984d)|校对|2| + +## 译者:[sanfran1068](https://github.com/sanfran1068) 历史贡献积分:1.5 当前积分:1.5 年度积分:1.5 + +|文章|类型|积分| +|------|-------|-------| +|[为 APP 设计通知提醒](https://juejin.im/post/5ba31ee3e51d450e4115500b)|校对|1.5| + +## 译者:[zx-Zhu](https://github.com/zx-Zhu) 历史贡献积分:2.5 当前积分:2.5 年度积分:2.5 + +|文章|类型|积分| +|------|-------|-------| +|[正确实现 linkedPurchaseToken 以避免重复订阅](https://juejin.im/post/5baf9a3e6fb9a05ce2741437)|校对|1.5| +|[新手开发者须知](https://juejin.im/post/5bade6a76fb9a05d32515cf0)|校对|1| diff --git a/ios.md b/ios.md index 751029cc6a0..654df7ae3b4 100644 --- a/ios.md +++ b/ios.md @@ -1,3 +1,20 @@ +* [在 iOS 中使用 UITests 测试 Facebook 登录功能](https://juejin.im/post/5b90e3ae6fb9a05d00458ad8) ([LoneyIsError](https://github.com/LoneyIsError) 翻译) +* [构建流畅的交互界面](https://juejin.im/post/5b7e2b34e51d4538843612cc) ([rydensun](https://github.com/rydensun) 翻译) +* [2018 年 iOS 开发找工作完全指南](https://juejin.im/post/5b7eca206fb9a019ff713277) ([melon8](https://github.com/melon8) 翻译) +* [你 Ladar 中该珍藏的:iOS 布局语言](https://juejin.im/post/5b84fe97f265da437c43422f) ([pmwangyang](https://github.com/pmwangyang) 翻译) +* [重写 loadView() 方法使 Swift 视图代码更加简洁](https://juejin.im/post/5b68fe5b6fb9a04fd16039c0) ([RickeyBoy](https://github.com/RickeyBoy) 翻译) +* [React Native 中使用转场动画!](https://juejin.im/post/5b69467e5188251b3c3b4e4e) ([talisk](https://github.com/talisk) 翻译) +* [一份在你的 iPhone 上平衡实用和美观的指南](https://juejin.im/post/5b4c0d0ce51d4519503b1e67) ([94haox](https://github.com/94haox) 翻译) +* [Tab Bar 就是新的汉堡菜单](https://juejin.im/post/5b61684fe51d451986517e31) ([rydensun](https://github.com/rydensun) 翻译) +* [Airbnb 中的 React Native 上下的赌注(五 — 完):Airbnb 移动端路在何方?](https://juejin.im/post/5b46f92de51d45198e721cd7) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [Airbnb 中的 React Native 上下的赌注(四):React Native 退役](https://juejin.im/post/5b447b1e6fb9a04fd3437dad) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [Airbnb 中的 React Native 上下的赌注(三):建立一个跨平台的移动端团队](https://juejin.im/post/5b446177f265da0f7c4faec8) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [Airbnb 在 React Native 上下的赌注(二):技术细节](https://juejin.im/post/5b3b40a26fb9a04fab44e797) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [Airbnb 在 React Native 上下的赌注(一):概述](https://juejin.im/post/5b2c924ff265da59a401f050) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [苹果公司如何修复 3D Touch](https://juejin.im/post/5b35e5886fb9a00e3642724f) ([Wangalan30](https://github.com/Wangalan30) 翻译) +* [不越狱探索 App](https://juejin.im/post/5b0e7eec518825155a048dc4) ([melon8](https://github.com/melon8) 翻译) +* [Swift 中的 Playground 驱动开发](https://juejin.im/post/5b07665c6fb9a07ac5608d8a) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) +* [Swift 中的内存泄漏](https://juejin.im/post/5b07a1c251882538914a5d3e) ([RickeyBoy](https://github.com/RickeyBoy) 翻译) * [使用 iPhone X 与 Maya 实现快速面部捕捉](https://juejin.im/post/5af40eba6fb9a07ac76ed7d1) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) * [轻松管理 Swift 项目中的不同环境](https://juejin.im/post/5af264a4f265da0b967233ff) ([melon8](https://github.com/melon8) 翻译) * [构建、测试、分发!运用 Fastlane 与 Jenkins,完整的 iOS 持交付指南。](https://juejin.im/post/5af430f0f265da0b8262d1ea) ([talisk](https://github.com/talisk) 翻译) diff --git a/others.md b/others.md index 29f0775e786..485ce1ad5b7 100644 --- a/others.md +++ b/others.md @@ -1,3 +1,13 @@ +* [下一代包管理工具](https://juejin.im/post/5ba9830fe51d450e4d2fe208) ([diliburong](https://github.com/diliburong) 翻译) +* [新手开发者须知](https://juejin.im/post/5bade6a76fb9a05d32515cf0) ([ssshooter](https://github.com/ssshooter) 翻译) +* [用 Workers 让静态网站动态化](https://juejin.im/post/5b95c5375188255c6e70422a) ([MeFelixWang](https://github.com/MeFelixWang) 翻译) +* [关于工程师和影响力](https://juejin.im/post/5b8f9a96f265da0ab33125b0) ([cf020031308](https://github.com/cf020031308) 翻译) +* [如何像程序员般思考 —— 蕴含在问题解决中的经验](https://juejin.im/post/5b76839ae51d4566491c24bb) ([mingxing47](https://github.com/mingxing47) 翻译) +* [编程语言和平台:一条被评论的推文串](https://juejin.im/post/5b4c2b75e51d45195b336d57) ([cf020031308](https://github.com/cf020031308) 翻译) +* [为 GitHub 项目做出贡献的初学者指南](https://juejin.im/entry/5b2e58ba6fb9a00e4966ee4b) ([sophiayang1997](https://github.com/sophiayang1997) 翻译) +* [怎样(以及为什么要)保持你的 Git 提交记录的整洁](https://juejin.im/post/5b29060ee51d4558cd2adac0) ([zhongdeming428](https://github.com/zhongdeming428) 翻译) +* [关于你的编程生涯的一些告诫](https://juejin.im/post/5b0256e36fb9a07aa767f5b4) ([kezhenxu94](https://github.com/kezhenxu94) 翻译) +* [部署!=发布(第二部分)](https://juejin.im/post/5b00d2fa6fb9a07a9a1120e9) ([Starriers](https://github.com/Starriers) 翻译) * [如何逃离 async/await 地狱](https://juejin.im/post/5aefbb48f265da0b9b073c40) ([Colafornia](https://github.com/Colafornia) 翻译) * [我在编程初级阶段常犯的错误](https://juejin.im/post/5ae97af6f265da0ba062f797) ([kezhenxu94](https://github.com/kezhenxu94) 翻译) * [Deploy != Release(第一部分):Deploy 与 Release 的区别及为什么很重要?](https://juejin.im/post/5ad80983f265da505c3c1b3a) ([stormluke](https://github.com/stormluke) 翻译) diff --git a/product.md b/product.md index c1edc34b87e..81b016209c4 100644 --- a/product.md +++ b/product.md @@ -1,3 +1,11 @@ +* [如果界面产品设计师设计实体产品](https://juejin.im/post/5baf9697e51d456f087ba2a8) ([ssshooter](https://github.com/ssshooter) 翻译) +* [四个「为什么」:是什么在背后驱动你的产品?](https://juejin.im/post/5bac279cf265da0adc18d31a) ([pmwangyang](https://github.com/pmwangyang) 翻译) +* [为 APP 设计通知提醒](https://juejin.im/post/5ba31ee3e51d450e4115500b) ([rydensun](https://github.com/rydensun) 翻译) +* [虚构问题,低质量软件的根源](https://juejin.im/post/5b65122be51d4517c564d54f) ([ssshooter](https://github.com/ssshooter) 翻译) +* [关于 HomePod WWDC 的愿望清单: 测试版程序、新的语音资源、Echo 等功能](https://juejin.im/post/5b14ff08f265da6e1a602e26) ([dandyxu](https://github.com/dandyxu) 翻译) +* [watchOS 5 愿望清单:Apple Watch Podcasts、open Siri face 和更新 Control Center 等](https://juejin.im/post/5b15045bf265da6e6039372b) ([talisk](https://github.com/talisk) 翻译) +* [WWDC 2018:关于iOS 12、iPad Pro、新MacBooks或者更多产品的所有预测](https://juejin.im/post/5b056d485188256710601ecc) ([dandyxu](https://github.com/dandyxu) 翻译) +* [预测你的游戏的货币化未来](https://juejin.im/post/5ad1d3b6f265da238f12fafa) ([NoName4Me](https://github.com/NoName4Me) 翻译) * [用行为经济学来传达付费应用订阅的价值](https://juejin.im/post/5ad3ffd0f265da23906c785f) ([ALVINYEH](https://github.com/ALVINYEH) 翻译) * [怎样把取消订阅的用户吸引回来](https://juejin.im/post/5acc1538518825651d07fdd1) ([allenlongbaobao](https://github.com/allenlongbaobao) 翻译) * [游戏即服务的五条建议,提升游戏变现能力](https://juejin.im/post/5aa88773f265da23a228cc49) ([pthtc](https://github.com/pthtc) 翻译)