[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"list-\u002Farticles\u002F6":3},{"items":4,"total":125},[5,20,31,41,52,60,69,78,86,97,107,116],{"id":6,"type":7,"slug":8,"title":9,"date":10,"category":11,"tags":12,"body_markdown":14,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":19},167,"article","10-misconceptions-about-amp","【译】澄清对AMP的十个误解","2017-08-23 10:30","web",[13],"AMP","\n## 1. AMP是一个新的渲染引擎\u002F编程语言\n\nAMP 是一套开源的 web 组件格式和类库。与其它类库或者框架相比，AMP 最大的区别在于，它采用了白名单策略，来约定你可以做什么。\n\n为什么要限制一些东西的使用呢？这是因为一些看起来很无辜的小代码很容易拖慢网站的性能。而且一段时间后回来排查这些性能问题会是一件非常困难的事情。这就像你在德国的 Autobahn 快速路上开车，却只在右道行走，不知道最左边的道路才是更快的。AMP 就是这样一种技术，强迫你走到最左边的快速道路，并且保证你前方的道路是没有障碍的。\n\nAMP 带来的并不只有限制，它还提供了很多的自定义标签，这些标签都有各自内置的功能。当你使用这些自定义标签，并遵守一些其它的规则，那么 AMP 将通过一些手段保证你的网站速度是非常快的。这些手段主要包括强制静态布局、高效率资源加载和一些其它的优化。\n\nAMP 有一份文档，规定了什么样的标签是兼容的，什么样的标签是不兼容的。它还发布了一个内置的验证工具，可以让你看到当前页面的是否符合 AMP 文档的要求。需要强调的是，从技术上来说，即使不遵守所有的规定，AMP 页面也能运行得很好，只是你的页面无法通过 AMP 验证（从性能上来说，不遵守 AMP 规定到一定程度的时候，AMP做的性能改进也会全部失效，另外如果有一些东西是要求与 AMP 页面协作的，那么你的页面可能无法正常显示 ）。但同时，这也意味着 AMP 所强调的一些特性全部没有了。\n\n\u003C!-- more -->\n\n### 还有更多解释吗？\n\n真的没有了。AMP 只是一套 web 组件生态系统而已。但是，因为可以很容易通过编程的方式来确定一个页面是合法的 AMP，就可以做更多炫酷的事情了。比如：\n\n- 合法的 AMP 可以使用免费、高速的缓存（例如[Google AMP Cache](https:\u002F\u002Fdevelopers.google.com\u002Famp\u002Fcache\u002F)）\n- 基本可以确认合法的 AMP 页面速度很快，且对用户友好\n- AMP 页面是“自包含”（self-contained）的（译注：指页面是完整、独立的），所以可以被嵌入第三方平台\n\n这也允许第三方平台做一些很炫酷的事情：\n\n- 出现在 Google 搜索的 Top Stories 轮播上\n- 从 Pinterest 上链接到 AMP 页面\n- 在 PWA 中使用 AMP 页面\n\n## 2. AMP是Google的项目\n\nAMP 最早是由出版行业和 Google 在2015年提出来的（当然，一些促使 AMP 诞生的体验问题，比如移动端 web 页面加载慢等，属于明显的行业内共性问题）。从一开始，它就是由出版行业、广告行业、技术提供者和平台提供方一起携手开发的，除了 Google 以外，参与者还包括 Twitter、Linkedin 和 Pinterest 。AMP 从提出来的第一天起就是一个通过 Github 进行开放协作的开源项目。到现在为止，AMP 接受了来自超过 200 名贡献者的 Pull Request，这些贡献者绝大部分不是 Google 的员工。\n\nGoogle 确实有一支团队在全职为 AMP 项目工作，AMP 项目的大部分贡献也来自这个团队，但这个团队也是通过和其它人一样的[Intent to implement](https:\u002F\u002Fgithub.com\u002Fampproject\u002Famphtml\u002Fblob\u002Fmaster\u002FCONTRIBUTING.md#contributing-features)流程来工作。Google 团队也会将它们的[周会纪要](https:\u002F\u002Fgithub.com\u002Fampproject\u002Famphtml\u002Fissues?utf8=%E2%9C%93&q=label%3A%22Meeting%20Notes%22%20)以及其它的文档发布出来，尽量保证外部贡献者都可以参与进来。\n\n> 10月17日更新：针对这一点，外界有一些疑问和评论。上面这两段话仍然有效，但是我补充一个更精简的结论：AMP 项目当前的核心贡献者都是 Google 员工，所以 AMP 可以称作是 Google 领导（Google-led）的项目。但是它是被当作一个独立的开源项目来看待的，我们正在邀请开发者和社区参与进来一起贡献，让他们也变成核心贡献者，使 AMP 项目更加独立。\n\n## 3. AMP需要Chrome才能运行\n\n绝对不是这样！AMP 是一个跨平台、跨浏览器的类库，支持所有流行的移动浏览器和桌面浏览器的[最新两个版本](https:\u002F\u002Fwww.ampproject.org\u002Flearn\u002Fbrowsers\u002F):\n\n![AMP可以运行的浏览器](\u002Fassets\u002Fweb\u002F2017\u002F10-misconceptions-about-amp\u002F1.png)\n\n## 4. AMP 限制了我的布局和设计\n\n你肯定会被 AMP 能做的事情惊讶到。AMP 确实限制了一些标签和对性能影响很大的 CSS 属性的使用，但是整体来看，在为站点编写样式时，受到的[限制非常小](https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Fguides\u002Fresponsive\u002Fstyle_pages)。想写一个疯狂的 5 层 flexbox 嵌套布局？那就写吧。想基于伪元素写一个疯狂的 UI ？也 OK。\n\n下面是一个我写的[AMP发展计划页面](https:\u002F\u002Fpaulbakaus.com\u002Ftutorials\u002Fcss\u002Fflexbox-freebie-auto-growing-list-for-amp-roadmap\u002F)：\n\n\u003Ciframe height='265' scrolling='no' title='Flexy Steppy List' src='\u002F\u002Fcodepen.io\u002Fpbakaus\u002Fembed\u002FezOQYa\u002F?height=265&theme-id=0&default-tab=result&embed-version=2' frameborder='no' allowtransparency='true' allowfullscreen='true' style='width: 100%;'>See the Pen \u003Ca href='https:\u002F\u002Fcodepen.io\u002Fpbakaus\u002Fpen\u002FezOQYa\u002F'>Flexy Steppy List\u003C\u002Fa> by Paul Bakaus (\u003Ca href='https:\u002F\u002Fcodepen.io\u002Fpbakaus'>@pbakaus\u003C\u002Fa>) on \u003Ca href='https:\u002F\u002Fcodepen.io'>CodePen\u003C\u002Fa>.\n\u003C\u002Fiframe>\n\n## 5. AMP只适合轻量级页面\n\n有几分道理，但也有误导性。这主要取决于你如何理解“轻量”。严格来说，AMP 的目标是静态内容。但我们所说的静态内容同样可以包含具有艺术气息的动画、侧边栏、灯箱广告、手风琴导航、轮播等等。你可以查看[AMPByExample](https:\u002F\u002Fampbyexample.com\u002F)中的一些[高级例子](https:\u002F\u002Fampbyexample.com\u002F#advanced)。\n\n## 6. AMP只适用于移动端\n\n诚然，AMP（Accelerated Mobile Pages）中的“Mobile”无助于澄清这个问题，但是这个说法还是跟事实完全不符。\n\nAMP 是一个非常强大的跨平台解决方案，它希望出版行业和开发者将工程资源从细碎的多平台兼容支持中解放出来，将焦点放到创建伟大的新产品特性上，而这些产品特性可以被任何设备上的所有用户轻易访问到。\n\nAMP本身是在[响应式设计](https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Fguides\u002Fresponsive_amp)的概念支持下被创造出来的。目前有与 AMP 集成的平台大部分是聚焦移动端的，但是在桌面端，你也可以从 AMP 中获取得很多好处。\n\n想知道如何使用 AMP 来处理不同分辨率和不同设备的话，可以看我的另一篇文章[“AMP中的'mobile'”](https:\u002F\u002Fpaulbakaus.com\u002F2016\u002F07\u002F01\u002Fabout-that-mobile-in-accelerated-mobile-pages\u002F)。\n\n## 7. 我现有的网站上无法使用AMP\n\n我们已经澄清过第 4 点，并没有什么特别的理由让你现在的网站无法使用 AMP，因为当你读完第一个问题后，就知道了 AMP 只是一个 web 组件类库而已。事实上，[AMP项目主页](https:\u002F\u002Fwww.ampproject.org\u002F)就是完全使用的 AMP：\n\n![AMP项目主页使用的AMP](\u002Fassets\u002Fweb\u002F2017\u002F10-misconceptions-about-amp\u002F2.png)\n\n当然，和其它类库一样，[AMP并不适合每一个人](https:\u002F\u002Fpaulbakaus.com\u002F2016\u002F02\u002F26\u002Flife-after-amp\u002F)。在动手前想一想在[AMP的强制限制](https:\u002F\u002Fwww.ampproject.org\u002Flearn\u002Fhow-amp-works\u002F)（同时也带来好处）下，你的网站是否能正常运行。如果答案是肯定的，那么就切换到 AMP 吧。有一个基本的原则，如果你的网站没有静态内容，并且页面并不是最深层次的页面（译注：原文 leaf pages，leaf 指树状结构中的叶子节点，对应到网站一般指最深层次的页面，例如文章页），例如入口页，也就是用户从搜索中点进来的页面，那么 AMP 可能不适合你。\n\n## 8. 如果我自己做优化，那AMP就没什么用\n\nAMP 的优化是“无脑优化”，即使你身边没有web开发大师，它也能帮助你。我们对将网站性能优化到极致这件事情感到自信和骄傲。事实上，因为 AMP 是一个通用库，它可能会漏掉一些针对你的网站特殊场景下的优化策略，这意味着你自己的手工优化工作很可能会带来更好的性能。\n\n但到今天为止，浏览器和一些大的平台例如 Google 搜索，仍然没有办法来确认你的网站是非常快速且对用户友好的。所以如果你选择自己做优化工作，你可能能得到一个非常快的网站，但是没有办法让其它人确信。而 AMP 的验证使得它对于第三方平台非常有吸引力。\n\n## 9. AMP只对出版发行行业有好处\n\n没错，如果你将你的新闻站点变成 AMP，就有机会出现在 Google 的 Top Stories 轮播上，并且 Google 会在移动端[搜索结果](https:\u002F\u002Fsearch.googleblog.com\u002F2016\u002F09\u002Fsearch-results-are-officially-ampd.html)中使用一个内联的查看器来加速 AMP 页面。但是[eBay也创建了AMP页面](http:\u002F\u002Fwww.ebaytechblog.com\u002F2016\u002F06\u002F30\u002Fbrowse-ebay-with-style-and-speed\u002F)（[示例](http:\u002F\u002Fm.ebay.com\u002Fsch\u002Famp\u002F16GB-iPhone-5s-Smartphones\u002F9355\u002Fbn_341667\u002Fi.html)），尽管它们并不是新闻网站。\n\n![AMP可以运行的浏览器](\u002Fassets\u002Fweb\u002F2017\u002F10-misconceptions-about-amp\u002F3.png)\n\n为什么 eBay 要选择AMP？[它们自己是这么说的](http:\u002F\u002Fwww.ebaytechblog.com\u002F2016\u002F06\u002F30\u002Fbrowse-ebay-with-style-and-speed\u002F)：\n\n> AMP 的好处之一在于，它是一套构建移动端 web 页面的最佳实践的集合。我们之前已经在遵守这些最佳实践中的一部分，但是这些不同的做法分散在各个团队中，而且每个团队都有自己的偏好。AMP 的出现让我们可以更好地整理加固这个最佳实践的清单，并将它们作为常规开发周期中的一部分。\n\n## 10. 我得在AMP和PWA中做出选择\n\nAMP 和 PWA 是互补的技术，它们的使用场景完全不一样。如果将它们结合在一起使用，你就能使用它们创建出我认为目前最完美的内容站点：\n\n1. 用户发现了你的内容的链接，点进来了\n2. 内容被瞬间加载完毕，并且看起来很舒服\n3. 阅读完之后，用户被邀请阅读更多内容，或者邀请用户使用一个更好体验的版本，它们由快速导航、通知推送、离线支持等技术支持\n4. 当用户接受了你的邀请后，他们将被引导到一个可以安装到桌面的版本，这个版本的使用体验就像 App 一样\n\n听起来非常棒对吧？你需要做的只是下面这些（或许有稍许变化）：\n\n- 最深层面的页面（有内容的页面，而不是概览页面）使用 AMP 发布，以获得瞬间加载的体验\n- 当用户浏览你的内容的时候，在这些AMP页面中使用[&lt;amp-install-serviceworker&gt;](https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Freference\u002Fextended\u002Famp-install-serviceworker.html)初始化缓存和 PWA 应用外壳（PWA app shell）\n- 当用户点击网站上的其它链接的时候（例如，在类似 App 的体验中，点击底部的按钮），[Service Worker](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FService_Worker_API\u002FUsing_Service_Workers)接管请求，然后加载[PWA应用外壳](https:\u002F\u002Fdevelopers.google.com\u002Fweb\u002Fupdates\u002F2015\u002F11\u002Fapp-shell?hl=en)\n- 最后，已经加载好的PWA应用外壳可以将 AMP 作为数据源嵌入到页面，这将使得你只需要为相同内容创建一个后台（既可以作为单独的AMP浏览，也可以作为 PWA 的数据源）\n\n如果同时谈到 AMP 和 PWA 的话，还有更多话题可以说，所以请期待在这个主题上的后续深度文章吧。\n\n## 总结\n\n现在你有答案了。针对 10 个误解，我们给了 10 个澄清的答案，希望能给你一个对 AMP 更大更清晰的印象，也让你想清楚 AMP 对你来说是否适合。还有问题吗？[联系我吧](https:\u002F\u002Ftwitter.com\u002Fpbakaus)！\n\n原文链接\u003Chttps:\u002F\u002Fpaulbakaus.com\u002F2016\u002F10\u002F13\u002Fdebunked-10-misconceptions-about-amp\u002F>\n\n作者：Paul Bakaus (@pbakaus)\n",null,0,"2026-08-28 04:37:17","published","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fweb\u002F2017\u002F10-misconceptions-about-amp\u002F1.png",{"id":21,"type":7,"slug":22,"title":23,"date":24,"category":11,"tags":25,"body_markdown":29,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":30},175,"passing-data-between-promise-callbacks","【译】在Promise回调之间传值的方法","2017-08-22 13:00:00",[26,27,28],"JavaScript","Promise","异步","\n在基于 Promise 编写的代码中，经常会有很多回调函数，它们都有各自的变量作用域。那么如果我们需要在这些回调函数之间共享数据，要怎么办呢？本文总结了一些方法。\n\n## 1. 问题\n\n下面的代码演示了使用 Promise 回调时经常碰到的一类问题：变量`connection`（A）在一个作用域中存在，但是需要被另一个作用域访问（B和C）：\n\n```javascript\ndb.open()\n.then(connection => { \u002F\u002F (A)\n    return connection.select({ name: 'Jane' });\n})\n.then(result => {\n    \u002F\u002F Process result\n    \u002F\u002F Use `connection` to make more queries (B)\n})\n···\n.catch(error => {\n    \u002F\u002F handle errors\n})\n.finally(() => {\n    connection.close(); \u002F\u002F (C)\n});\n```\n\n在这段代码中，我们使用了 ES 规范中的`Promise.prototype.finally()`。它提供了和`try`语句的`finally`分支类似的功能。\n\n\u003C!-- more -->\n\n## 2. 解决方法：副作用\n\n第一种解决方法是将要共享的值`connection`存入这些回调函数的上级作用域（A）：\n\n```javascript\nlet connection; \u002F\u002F (A)\ndb.open()\n.then(conn => {\n    connection = conn;\n    return connection.select({ name: 'Jane' });\n})\n.then(result => {\n    \u002F\u002F Process result\n    \u002F\u002F Use `connection` to make more queries (B)\n})\n···\n.catch(error => {\n    \u002F\u002F handle errors\n})\n.finally(() => {\n    connection.close(); \u002F\u002F (C)\n});\n```\n\n因为`connection`的定义在回调函数的外面，所以 B 和 C 都能访问它。\n\n## 3. 解决方法：嵌套作用域\n\n上面例子的同步版本，看起来是这样的：\n\n```javascript\ntry {\n    const connection = await db.open();\n    const result = await connection.select({ name: 'Jane' });\n    ···\n} catch (error) {\n    \u002F\u002F handle errors\n} finally {\n    connection.close();\n}\n```\n\n同步版本的代码中，使得`connection`在函数内部可用的方法是将声明提前到上级作用域中：\n\n```javascript\nconst connection = await db.open();\ntry {\n    const result = await connection.select({ name: 'Jane' });\n    ···\n} catch (error) {\n    \u002F\u002F handle errors\n} finally {\n    connection.close();\n}\n```\n\n> 译注：`try...catch`中，`catch`和`finally`可以共享`try`中的变量，所以此处将`connection`移到外部定义，对于同步代码来说，是非必需的。\n\n我们可以在 Promise 中做同样的事情——将 Promise 链起来：\n\n```javascript\ndb.open() \u002F\u002F (A)\n.then(connection => { \u002F\u002F (B)\n    return connection.select({ name: 'Jane' }) \u002F\u002F (C)\n    .then(result => {\n        \u002F\u002F Process result\n        \u002F\u002F Use `connection` to make more queries\n    })\n    ···\n    .catch(error => {\n        \u002F\u002F handle errors\n    })\n    .finally(() => {\n        connection.close();\n    });\n})\n```\n\n这段代码有两个 Promise 链：\n\n- 第一个开始于 A ，`connection`是`db.open()`的结果\n- 第二个被包裹在 B 处的`.then()`中，从 C 处开始，注意 C 处的`return`将两个 Promise 连接起来了\n\n你可能已经注意到了，不管是同步版本还是异步版本的代码，如果`db.open()`同步抛出一个错误，这个错误将不能被`catch`处理。有[一篇专门的关于 `Promise.try()`](http:\u002F\u002F2ality.com\u002F2017\u002F08\u002Fpromise-try.html)将演示在异步版本中如何修复这个问题。在同步版本的代码中，你可以将`db.open()`移入`try`中即可。\n\n## 4. 解决方法：返回多值\n\n下面将演示另一种在回调函数之间传值的方法。但是它不是任何时候都能工作，尤其是你不能将它用于前面演示的数据库操作中。我们来看一个它能工作的例子。\n\n我们面临一个相似的问题：在 Promise 链中，需要将`intermediate`的值从 A 处的回调传递到 B 处的回调。\n\n```javascript\nreturn asyncFunc1()\n.then(result1 => { \u002F\u002F (A)\n    const intermediate = ···;\n    return asyncFunc2();\n})\n.then(result2 => { \u002F\u002F (B)\n    console.log(intermediate);\n    ···\n});\n```\n\n我们使用`Promise.all()`从第一个回调函数中传递多个值给第二个回调函数，从而解决这个问题：\n\n```javascript\nreturn asyncFunc1()\n.then(result1 => {\n    const intermediate = ···;\n    return Promise.all([asyncFunc2(), intermediate]); \u002F\u002F (A)\n})\n.then(([result2, intermediate]) => {\n    console.log(intermediate);\n    ···\n});\n```\n\n注意，在 A 处返回一个数组是不行的，因为`.then()`会获得一个Promise，一个值。使用`Promise.all()`时，内部会使用`Promise.resolve()`来保证数组元素都是 Promise ，并且在它们全部被满足（fulfill）时，将它们的值组成一个数组传递给作为下一个回调的参数。\n\n这种方法的局限性在于你不能传值到`.catch()`或者`.finally()`回调中。\n\n最后，这种方法还可以在`Promise.all()`中传入一个对象（不仅仅可以是数组），这样每一个返回的值都有一个标签。\n\n> 译注：`Promise.all()`目前并不支持传入对象，作者应该是希望支持这样一种使用方式。\n\n## 5. 相关链接\n\n- 在 Exploring ES6 的\"[Promises for asynchronous programming](http:\u002F\u002Fexploringjs.com\u002Fes6\u002Fch_promises.html)\"章节，有关于 Promise 链的更多内容\n- [ES提案：`Promise.prototype.finally()`](http:\u002F\u002F2ality.com\u002F2017\u002F07\u002Fpromise-prototype-finally.html)\n- [ES提案：`Promise.try()`](http:\u002F\u002F2ality.com\u002F2017\u002F08\u002Fpromise-try.html)\n\n作者：Dr. Axel Rauschmayer\n\n原文链接：\u003Chttp:\u002F\u002F2ality.com\u002F2017\u002F08\u002Fpromise-callback-data-flow.html>\n","",{"id":32,"type":7,"slug":33,"title":34,"date":35,"category":36,"tags":37,"body_markdown":40,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":30},122,"why-nobody-cares-tencent-tech","【问答】为啥没人关心腾讯的前端技术栈？","2017-08-02 16:31","tech",[38,39],"腾讯","阿里","\n本文来自知乎问题[为啥没人关心腾讯的前端技术栈？](https:\u002F\u002Fwww.zhihu.com\u002Fquestion\u002F63278539\u002Fanswer\u002F207369772)\n\n业界对阿里前端的关注度的确是比腾讯的要高好多。个人以为主要原因如下：\n\n## 1. 公司宣传策略不同\n\n几年前参加过阿里的校招的宣讲会，令我十分意外的是，一场校招宣讲会，居然让章文嵩博士去做了大篇幅的演讲。（不了解其人的可自行搜索。）整场听下来，会传达一个非常重要的基调，就是“我们的技术非常好，这里非常多的牛人”。后来也有幸去参加过阿里的一些培训活动（类似百淘\u002F百支之类的），活动的主线也是让我们去采访阿里各行各业的牛人（当时叫“牛P”）。\n\n同年，也参加了腾讯的校招宣讲会，腾讯的宣讲会有非常大的篇幅在讲企业文化和公司福利待遇。公司的愿景是什么，文化氛围是什么，薪酬组成如何，奖金如何，班车夜宵如何等等。\n\n两家公司都非常有吸引力，但是策略是完全不同的。校招当然只是一个窗口，但是反映出两家公司对外宣传的一些策略确实是非常不一样的。包括现在，很多人对阿里的印象都是技术氛围好，对腾讯的印象则是文化和福利待遇好。这个印象的不同并不是天然形成的，而是公司有意营造的结果。\n\n\u003C!-- more -->\n\n## 2. 公司文化不同\n\n阿里的文化被戏称“土俗骚”，这让公司保持了相当的活力，甚至是让外人觉得有些出格的活力。同样，在技术上，也有类似的氛围，据我的了解，公司对技术氛围的营造、技术交流等活动还是非常支持的，所以大家能看到阿里在对外技术输出、开源、新技术实践等领域是非常活跃的。这也会导致大家非常关注阿里在技术上的一举一动，甚至很多时候会作为一个非常重要的风向标。\n\n腾讯的文化则偏向于比较正能量的方向，鼓励积极向上的文化。但是这个对技术氛围没有什么积极的影响。腾讯更多的是产品文化，要把产品打磨到极致，技术为产品服务。腾讯的技术人员到晋级T3以上的时候是需要答辩的，而这个答辩的时候海量产品实践、为产品做了多少事情其实是一个非常重要的考量指标。这直接导致“进取的员工”会花更多精力在产品实践上。（这里进取的员工是指看清了公司职业道路，希望积极表现的员工，并不是说只研究技术的员工不进取，只是这个在公司职业道路上的帮助相对没那么大。）而对外输出、开源等事情相对来说就没有那么重要了，公司虽然原则上不反对，但是并没有什么实际上的支持。所以我们看到腾讯的技术输出是非常少的，公司层面也不会宣传自己的技术有多好，开源氛围有多好等。\n\n这个差别导致大家的关注点非常不一样。我们知道阿里首页用了Node，知道它们在App中用了weex。但是你知道微信用了什么，手机QQ用了什么，腾讯网用的什么么？\n\n## 技术实力究竟有没有差距\n\n我觉得这个问题得两说。\n\n如果说技术实力是指对某一些技术领域的掌握，那我觉得可能还真的是有差距的，比如在Node这一块，阿里有好几位参与对Node核心代码贡献的，也有朴灵这样的大牛，自己做出了alinode这样的产品，而腾讯这一块，目前并没有太大动作。\n\n但是如果以技术支持产品的角度来说，其实没有本质差别，Node掌握没那么好是吧，行，我们用C++\u002FPHP一样支撑起来。无非是并发、延时、日志、容灾、安全这些事情嘛。\n\n同样，从前端角度来说，可以说腾讯没有出过组件库，没有什么成功的开源产品，但是面对前端这些常见的技术场景，也确实并没有什么技术难度，大家都能做得很好。\n\n## 未来会有改变吗\n\n我觉得会的。\n\n阿里某一些开源项目，或者技术栈的选用，也并非都是那么好的结果。只能说态度很积极，结果怎么样，还需要时间考验。事实上不管是过去还是现在，吐槽阿里技术的也并不少。（前端相关的比如 kissy \u002F 阿拉雷 \u002F seajs \u002F weex ，并不是没有意义，而是不够好。）随着开源这个东西的“神性”下降，大家已经可以更理性地看待开源的意义，也明白并不一定高举高打的都是好东西。\n\n我不确定阿里是否会有关于开源项目持续维护更好一些的机制，但不管怎样，我觉得这里是值得思考的。\n\n腾讯也并不一定会对技术分享、开源一直封闭。事实上微信最近已经开源了好一些作品，反响也还不错。另外云计算的战争开始打响，让大家都意识到，这个领域是一个一定要对开源、对社区友好的领域。所以我们看到阿里在做技术社区，腾讯也在做。\n\n综上：阿里新技术实践相对比较多，且因为种种原因，让业界都知晓，所以关注自然多。腾讯的新技术实践相对少，或者实践完没有让业界知道，关注自然也就少了。\n",{"id":42,"type":7,"slug":43,"title":44,"date":45,"category":46,"tags":47,"body_markdown":50,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":51},83,"timing-app","时间管理工具——Timing App","2017-07-26 08:54","life",[48,49],"工具","时间管理","\n你是否觉得每天碌碌无为？你是否每时每刻都在切换桌面上的程序，但是时常忘记自己要干嘛？你是否在每天下班的时候根本回想不起来这过去的一天到底做了什么？\n\n如果是的话，请马上拿起电话订购吧，只要 998，只要 998……\n\n对不起，跑错片场了。如果你也有以上的困惑，那么请继续往下读。\n\n## 简介\n\n今天要推荐的这款软件是一个 Mac App，名叫 Timing。但是因为 Timing 这个词实在是太通用了，所以我们用 Timing App 来指代。\n\n我在几年前用过这个软件，当时给我留下了非常深的印象，最近它又更新了一个大版本，非常好用，所以介绍给大家。\n\n\u003C!-- more -->\n\n![screenshot](\u002Fassets\u002Flife\u002F2017\u002Ftiming-app\u002F01.jpg)\n\n首先看一下软件的整体界面，左侧导航分为三个区域，分别是 Overview \u002F Review \u002F Details。上面的截图是 Overview （概览）界面。图中最有价值的是第一行右侧的柱状图，和第二行两张饼图。\n\n我们放大看一下这三张图：\n\n![stat bar chart](\u002Fassets\u002Flife\u002F2017\u002Ftiming-app\u002F02.jpg)\n\n柱状图中每根柱子是一个小时，柱子的高度代表在电脑上活动的时间。不同颜色对应不同项目。比如这个图上可以看到，下午4点到5点，杂事21分钟，沟通20分钟，码代码的时间只有6分钟。\n\n![stat pie chart](\u002Fassets\u002Flife\u002F2017\u002Ftiming-app\u002F03.jpg)\n\n第一张饼图的意义是整天的项目分布，可以看到大部分时间还是在码代码的。如果为项目指定了任务（一会说如何做），则可以展开看到详细的任务。比如图中我的杂事主要是面试，开会的部分则是在讨论一个导航问题。\n\n![stat projects](\u002Fassets\u002Flife\u002F2017\u002Ftiming-app\u002F04.jpg)\n\n第二张饼图的意义是整天在 APP 上花的时间分布图，可以看到我的大部分时间贡献给了 Chrome 和 VSCode 。\n\n可见，有了以上几张图，你就能清晰地知道一天的时间都花在哪了，每天回顾一下，就更能知道哪些时间是不该浪费的，哪些活动是可以调整的。这和记账的原理类似，看到白花花的账单再想狡辩说我没花钱，自己良心都过不去。\n\n## 原理\n\n这个软件全程都躺在后台，根本（几乎）不需要你的关注。它会默默记录你对每个App的使用情况，然后生成这样的图表，非常省心。\n\n它的核心原理，其实是利用 MacOS 的辅助功能，可以获取当前你正在用哪个 App ，当你切换 App 时，就能算出花了多少时间。然后通过定好的规则将这些 App 归类到不同的项目。例如当你在用 QQ 的时候，就算是闲聊，当你在用 VSCode 的时候，自然就是在码代码了。\n\n那如果我在用浏览器怎么办呢？你怎么知道我是在调试项目，还是在看微博，还是在看某些见不得人的东西？\n\n这就是这个 App 比较神奇的地方之一了，它不仅可以知道你在用浏览器，还可以知道你在浏览什么东西。\n\n![urls in browser](\u002Fassets\u002Flife\u002F2017\u002Ftiming-app\u002F05.jpg)\n\n当你点开 Details 的时候，就能看到每一条浏览记录。既然能分到这么细致，当然也就可以按网址来归类喽，比如看知乎和 Twitter 就被我归到了休闲中，但是看 Github 就被归类到了代码中。\n\n## 自动任务\n\nTiming App 2.0 在理论上最大的变动应该是加入了任务的概念。也就是我们前面提过的，我不光能看到我在开会，我还能看到会议的内容。这个会议内容就是手工记录的一个任务。\n\n这样的话， Timing App 的三个核心概念就呼之欲出了：\n\n1. App 的活动，比如哪几分钟在用 VSCode ，哪几分钟在用 Chrome 。\n2. App 对应的项目，例如 VSCode 对应代码，QQ对应闲聊。\n3. 任务，它相当于项目的具体说明，例如你写代码时在写哪个需求，开会时在开哪个会\n\n初看起来，这个任务跟 App 和 项目 的关系好像没那么大，而且居然需要手工记录。但是我觉得这正是 Timing App 2.0 带来的最精华的部分。\n\n![tasks](\u002Fassets\u002Flife\u002F2017\u002Ftiming-app\u002F06.jpg)\n\n看这张图，最上面一行是 App 的活动，中间一行是对应的项目，而第三行，则是任务。神奇的地方在于，Timing App 会根据前两行，推算出一段连续的第三行区间，这个时候，你只需要点一下加号，然后轻轻敲一下你在干嘛就可以了，并不需要像传统的日报工具一样自己去记录时间点或者计算时长。如果你觉得它的时间段给得不准，当然也可以通过上面的坐标轴自己拉时间点来调整。\n\n## 手动任务\n\n眼尖的朋友应该能看到，上面的图中，15:00 到 16:00 有一段，App 是空白的，那怎么会有项目和任务的数据呢？这就涉及到手动任务了。\n\nTiming App 中有一个选项，叫作“Ask for activity after being idle”，译过来就是“在空闲之后要求填写活动”，这个活动就是任务的意思。具体而言，当你的电脑一段时间不活动之后，它就会开始计时：“这个家伙从 15:00 就不在电脑前了，不知道干嘛去了”。等你回到电脑，开始活动的时候，它就会弹一个窗口：“快说，刚刚的一小时，你干嘛去了？”这时候，你只要轻轻地输入“面试”，然后选择项目是“杂事”就可以啦。\n\n![manual task](\u002Fassets\u002Flife\u002F2017\u002Ftiming-app\u002F07.jpg)\n\n听起来很麻烦，但是实际用下来这个特性非常方便，不论你因为什么原因离开电脑，它都会提醒你记录你刚刚在干嘛。这样下次就不用再抓破脑袋回想这一个小时跑哪里去啦，是被老大拉去讨论问题还是被产品抓去讨论细节还是开会等等。\n\n此外，有时候你人在电脑前，但是想记录一件连续的事情的话，你还可以手动开始一个任务，只要点一下“Start Task”即可。这个适用于带着电脑参加有计划的活动，例如项目总结会，员工大会等等。\n\n![start task](\u002Fassets\u002Flife\u002F2017\u002Ftiming-app\u002F08.png)\n\n当时间到了之后就会提醒你，时间已到，要不要继续延时，还是任务已完成。\n\n## 小结\n\n知道了每一天的时间是怎么花掉的，知道每时每刻在为什么而忙碌，会不会让你的下一天更高效呢？反正重新用回 Timing App 后，我的效率是提高了不少的。要不你也试试？\n\n对了，如果你的工作有写周报或者日报的需求，那么同样也可以使用 Timing App，只要稍微花几秒钟加一下任务，一天的周报就马上出来了。\n\n最后：这个 App 收费。它有三种授权方式，功能不一，特别提醒一下：最便宜的 29 刀，是没有上面说的手动任务的功能的。\n","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Flife\u002F2017\u002Ftiming-app\u002F01.jpg",{"id":53,"type":7,"slug":54,"title":55,"date":56,"category":11,"tags":57,"body_markdown":58,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":59},177,"the-boundary-of-mvvm","思考MVVM的边界","2017-07-11 13:27",[],"\n## 前提\n\nMVVM 的概念由来已久，一开始，伴随着非常多的争议，MVC 还是 MVP 还是 MVVM ？MVVM 中什么是 VM ？不过随着时间的推移，大家也不再纠结到底是 MV 什么鬼了，于是也有人叫它们为 MV* 。为行文方便，下文的 MVVM 并不特指 Angular.js 之类双向绑定的框架，更多是 MV* 的意义。\n\n不管它有什么争议，核心的数据绑定机制上，大家是没有太多争议的。Angular.js 带来的数据双向绑定至今让人印象深刻。而 React 带来的单向数据流\n\n![UI=f(state)](\u002Fassets\u002Fweb\u002F2017\u002Fthe-boundary-of-mvvm\u002F01.jpg)\n\n在今天已经几乎成为主流，在其它框架中都能找到它的影子。在 Vue 中也能找到它的身影，尤其是在引入 Vuex 之后，几乎就是 Flux 的翻版，概念几乎完全一样。\n\n今天要讨论的主题就是上面这个公式的边界，为避免 MVVM 名称带来的争议，下文直接使用“框架”一词。\n\n\u003C!-- more -->\n\n## 数据与UI的映射\n\n![math function](\u002Fassets\u002Fweb\u002F2017\u002Fthe-boundary-of-mvvm\u002F02.jpg)\n\n我们在数学中都学过 y = f(x) ，表示对每一个确定的 x ，都有唯一的 y 值与它对应。例如 y = x + 1，不论 x 取什么值，都能找到确定的 y 与之对应。而反过来理解，y 值的不同正是因为 x 取值不同导致的。\n\nUI = F(state)\n\n这也是一个典型的函数。它表示每一种 state 都有唯一与之对应的 UI 表现。反过来，UI 表现的不同正是因为 state 的不同导致的。\n\n正是因为这种映射的确定性，带来了极易理解的 UI 开发方法：我们只需要编写好映射关系，然后注意力就可以全部放到 state 上面了，因为 state 的任何变动都会体现到 UI 上，而 UI 是不会在 state 不变的情况下发生变更的。而因为同样的 state 对应同样的 UI ，我们甚至可以完全将用户当前的 UI 复制到另一台设备上，只要保证 state 是完全相同的即可。\n\n## 初窥边界\n\n如果世界真的这么简单，那也许就不会有前端工程师一职了。\n\n回想一个真实的案例，如果我们要将一个字符串显示在界面上，那么可能是这么写：\n\n```html\n\u003Cspan>{{text}}\u003C\u002Fspan>\n```\n\n```javascript\nthis.setState({\n  text:'hello world'\n});\n```\n\n此时 UI 完全由 state 决定。但是，如果这是一个输入框呢？\n\n```html\n\u003Cinput value=\"{{text}}\" \u002F>\n```\n\n这样写吗？那输入框输入的时候会发生什么？熟悉 React 的朋友应该知道，在 React 中，有一种 input 叫作 Controlled Input ，它需要监听输入框的事件，当内容变更的时候，实时改变 state 的值，从而再次达到 state 和 UI 一致的目的。然而这种情况下与其说是 state 控制 UI ，倒不如说是 UI 在控制 state 了。\n\n![controlled input](\u002Fassets\u002Fweb\u002F2017\u002Fthe-boundary-of-mvvm\u002F03.jpg)\n\n如果说内容变更还可以勉强通过 Controlled Input 的方式来保持 state 到 UI 的映射，那光标的控制就只能说是无能为力了。\n\n理论上来说，当我们输入的时候，光标也在同步变更，此时如果我们继续应用 state 到 UI 的映射，是有可能导致输入框被重新渲染的，而此时 input 中的光标是无法保证位置的。这样会导致非常奇怪的输入体验，或者说其实是没法用的。框架在处理输入框上下了非常多的功夫，其实是避免了 state 变更的时候重新渲染 input 的，最多只是改变它的属性（值）。\n\n至此，我们其实已经看到了，所谓 state 控制 UI 也只是在我们的理想中存在，现实中有非常多的细节是无法通过 state 到 UI 的映射关系照顾到的。\n\n## 再探边界\n\n上面的例子，为什么在设计 state 的时候，不把光标也设计进去呢？这样不就可以进行精准的 state 到 UI 的控制了么？\n\n我们在脑海中尝试一下：首先，需要在 state 中为每个输入框的值增加一个光标位置。接下来，需要在每一次值变更的时候计算光标位置，并通过光标 API 维护界面上光标的位置。然后，需要监听 focus \u002F blur 事件，重建、移除光标。需要监听点击事件重算光标位置，需要监听键盘事件，使光标位置发生跳跃。除此之外，还需要处理选区，此时是否还需要在 state 中添加一个选区呢？\n\n我们只是想控制一下光标而已……\n\n而如果我们不控制光标（也就是主流框架的现状），你会发现事情要更容易得多，我们上述种种光标的行为浏览器都有对应的处理和反应。也就是说，浏览器把这些行为都已经封装到了 input 组件中，而我们在大部分情况下，并不需要处理这些。这个封装行为，实际上就划出了一条边界：框架应该只管理封装组件对外暴露的部分，未暴露的部分不应该介入。\n\n再举一例，当我们引入一个文本编辑器组件（例如 AceEditor ）时，这个编辑器除了内容之外，还会有非常多的行为，例如是否显示 查找\u002F替换 的界面，是否显示行号、参考线，使用不同的语法进行高亮等等。以高亮这一行为来说，本质上它是由一堆带样式的`\u003Cspan>`元素堆起来的，例如`var a=123`这样一句简单的代码，它实际的结构可能是这样的：\n\n```html\n\u003Cspan class=\"keyword\">var \u003C\u002Fspan>\u003Cspan class=\"variable\">a\u003C\u002Fspan>\u003Cspan class=\"operate\">=\u003C\u002Fspan> \u003Cspan class=\"const\">123\u003C\u002Fspan>\n```\n\n此时我们需要将这个高亮的结构通过 state 来映射吗？还是在 state 中直接保存 var a=123 这样一个字符串就好了？答案不言自明。\n\n![highlight](\u002Fassets\u002Fweb\u002F2017\u002Fthe-boundary-of-mvvm\u002F04.jpg)\n\n至此，我们已经能非常明白地对 MVVM 的边界有一个认知。这条边界，就是组件的封装，面对一个封装的组件，我们不应该跨过封装的边界。\n\n但，问题又来了，如果组件的行为并不能通过 MVVM 的 state 来决定，那么，由谁决定？\n","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fweb\u002F2017\u002Fthe-boundary-of-mvvm\u002F01.jpg",{"id":61,"type":7,"slug":62,"title":63,"date":64,"category":11,"tags":65,"body_markdown":68,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":30},176,"secrets-of-wild-frontend","【闲聊】大前端揭秘","2017-06-23 11:47",[66,67],"前端","大前端","\n这是一篇标题党，真正的标题叫\n\n## GMTC 2017 一些感想和收获\n\nGMTC 2017 已经于 6 月 9 - 10 日在北京召开。这个会议的全称叫作“全球移动技术大会”，今年是第二届。第一届的内容以 App 为主，今年则在大前端的趋势下同时涵盖了 App 和 Web 的主题。\n\n我也非常荣幸地被工程化专场出品人裕波邀请作为演讲嘉宾，讲了富途在 web 前端组件化方面的一些实践经验。\n\n不过，本文的重点还是想以听会者的角度来讲一讲本次参会的一些感想和收获。\n\n\u003C!-- more -->\n\n## 大前端\n\n大前端这个概念很多年前就被提出来了，我自己的个人简介也多年来一直写的“关注大前端生态圈”。最近两年，这个概念已变得炙手可热。\n\n什么是大前端呢？这得从端这个概念讲起。端其实是来自英文单词“end”，终端的意思，通俗地理解成设备。而前端则是指用户手上的设备。具体到 web 前端而言，就是指用户访问 web 所使用的设备，那基本上就是 PC \u002F 手机等设备……上面安装的浏览器。而如果去掉 web 的概念，前端其实还可以指用户手机上的 App 。只不过在翻译的时候，我们有意识做了区别，web 的叫前端，native app 的叫终端，事实上，它们都是“end”，或者“front-end”。\n\n所以到这里大前端的概念就呼之欲出了，就是指 web 前端 + native app 这个范围内的东西。之所以这个概念会火，当然不是因为两个概念被相加这么简单，而是大家越来越发现在端上，不同的技术是有充分融合的可能性的。\n\n早些年，谈到 web 和 native 的融合，大家可能唯一能想到的方式就是 webview ，基于这个方案，也有很多成型的框架，最著名的就是Cordova。但是因为 webview 先天在体验和性能上的缺陷，这种方案一直没有成为主流。\n\n近年来，在 webview 的体验改进上，行业内的专家们都付出了相当多的努力，产生了很多具体的方案，比如使用 native UI + webview 结合，将 webivew 不好处理的像滑动、页面切换等交给 native 处理。\n\n在本次 GMTC 大会上，也有听到手Q对 webview 中加载性能的极致优化。他们通过 webview 池、接管 webview 网络、server + native + web 三者结合的增量更新机制等，非常好地提升了 webview 的性能，从现场的展示来看，优化效果非常明显。据说该解决方案将在近期全面开源，可以关注手Q团队的微信公众号“小时光茶社”。\n\n除了使用 webview 之外，还有两个方向，也是大前端的方向：native 动态化和 web native化。\n\n所谓 native 动态化，是指以 React Native 为代表的，使用 JS 驱动 Native UI 的技术。这种方案的好处是可以使用成熟和庞大的 web 生态圈资源，辅助 Native 的开发。比如 JS 的热加载、热更新，JS 框架 React \u002F Vue ，使用 CSS 来进行布局和样式编写等等。目前来看，这仍然是大前端领域一个非常大的热门主题。\n\nweb native 化则主要有几个方向：\n\n一个方向是以 AMP 为代表的轻量化 web 方案。在【重发】To A Dark Futuer 一文中，我提到了现行的 web 在移动端其实是有点格格不入的，它太繁重，不适合移动端轻量化的特征。而 AMP 则是要从 web 中挑出一个适合移动端使用的子集，然后加以修饰，让 web 更轻更快，变成移动设备上的一个更漂亮的公民。GMTC 上，来自百度的工程师也提到了他们家的 MIP 方案，和 AMP 如出一辙。特别值得关注的是现场有听众提问，以后我们是否需要准备一个 AMP 版本，再准备一个 MIP 版本，带来新的兼容性适配负担？百度的工程师表示他们会兼容 AMP 。但是现场使用百度搜索了一下一些 AMP 页面，并没有什么神奇的事情发生……\n\n另一个方向是为现有的 web 增加一些 App 才有的能力，这方面的代表技术是 PWA。Google 表示未来 PWA 将在 Android 系统上拥有和 App 同样的地位。目前 PWA 已经支持了将 web 应用放到桌面，有独立图标和独立进程管理，支持使用 Service Worker 进行离线，并具备了消息推送的能力（至于天朝……你懂的）。GMTC 的现场也有来自 Google 的工程师讲解了 PWA 的能力和基本使用方式。不过个人觉得介绍得还是比较浅。对于我更关注的 PWA 未来的计划基本上只字未提。\n\n第三个方向则是以小程序为代表的“再造一个 web 生态”。本次会议上来自小米的工程师分享小米正在研发的框架，基本上可以看作是“MIUI 上的小程序”，这是本次听会感受最强烈的一个主题，跟微信小程序有相似之处，下一篇专门来讲。\n\n## 其他\n\n1. Instagram 表示他们的 App 至今仍然是单 dex 的结构，也就是方法不超过 65535个，这个比较震惊，对一个用户量如此之大的程序仍然能如此克制，心怀敬意。\n2. 微信团队现场开源了他们的 SQLite 包装库  wcdb ，看起来还不错。\n3. 路遇图灵的同学也在现场，很贴心地送了两本书。不得不说图灵在作译者关系方面还是照顾得相当好的，赞。\n",{"id":70,"type":7,"slug":71,"title":72,"date":73,"category":11,"tags":74,"body_markdown":76,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":77},180,"webpack-3-coming","webpack 3来了","2017-06-20 09:42",[75],"webpack","\nwebpack 已经正式发布了 3.0.0 ，距离 webpack 2 （2.2.0）发布才仅仅过去了 5 个月。按理说，这是一件天大的好事，当一个构建工具在快速发布版本的时候，意味着它还有更多的可能性。但是翻一下这两天的微博、朋友圈，基本上大家的声音非常一致：\n\nwebpack 2 还没学呢，3就发布了。（这可如何是好？还好没学 2 ……）这是正式加入了刷版本号的军团吗？\n\n## webpack 干了啥\n\nwebpack之所以火起来，主要是做对了这么几件事情：\n\n1. AMD \u002F Common.js 一把抓，只是要模块化方案，全都支持\n2. 支持与 npm 无缝集成，更好地助力前端代码复用\n3. 将 CSS 和图片资源变成 JS 的附属资源，从源头上进行统一管理\n4. loader与plugin机制，带来无穷多的可能性\n\n当然，这些要具体去分析又可以写一篇长篇大论了。我个人觉得，webpack最牛的地方在于让全世界的前端都接受了一个事实：\n\n前端也是需要编译的，并且编译会带来更多可能性。\n\n正是因为编译的加入，我们看到了模块化更好地落地，看到了使用 npm 带来的更好的代码复用机制，看到了 vue 单文件组件化方案，看到了 JSX 在 React 中的平滑落地，看到了  TypeScript 正逐步被大家接受，看到了 CSS in JS 的方案正遍地开花……\n\n虽然大家都不想当配置工程师，但是上述种种还是会让人心动，所以仍然有无数人会拥抱它。在未来相当长一段时间内，这会成为前端开发工作流中一个必不可少的环节。\n\n\u003C!-- more -->\n\n## webpack 2 和 3 干了啥\n\nwebpack 2 最主要的变动就是支持了 ES Modules 。在 webpack 1 时代，如果用 ES Modules 写代码，需要先使用 babel 将代码转译成 CommonJS 模块，然后才可以使用 webpack 分析、打包、优化。而 webpack 2 直接支持了 ES Modules 的分析，不需要先依赖 babel 。\n\n看起来这是一个很小的改动，但背后意义重大。这意味着 JS 生态圈向着 ES Next 迈出了坚实的一步，为后续更多 ES 的支持打下了坚实的基础。同时，因为 ES Modules 的依赖声明是静态的，webpack 2 也支持了 tree sharking 。这意味着以后写代码的时候可以放心地将一个模块的所有功能一个点一个点进行导出，而打包的时候只会包含已经引入的那一些点，没有被使用到的代码将在打包时被抛弃。\n\n值得一提的是，webpack 2 和 webpack 1 配置文件不兼容。不过好在官方有完善的升级指引，并且现在如果配置文件不对的话，会非常详尽地告诉你，哪一个值是不对的，它的可能值是什么。\n\nwebpack 3 带来的最主要的变动……呃，好像没有。这其实意味着从 webpack 2 开始，对外暴露的 API 已经基本稳定。也意味着，从 webpack 2 升级到 webpack 3 只需要 npm i webpack@3 -D 即可。在本文行文之前，我在一个项目中进行了试验，的确如此，无痛升级。\n\n当然说一点变动都没有肯定是不对的，毕竟都跳大版本号了，最值得关注的一点是 webpack 3 带来了模块的 Scope Hoisting 特性。之前的版本中， webpack 会将模块用一个函数包裹起来，而在 webpack 3 中，如果你开启了 Scope Hoisting 的话，它会尝试将模块进行合并，减少包裹函数。\n\n![webpack 2 打包结果](\u002Fassets\u002Fweb\u002F2017\u002Fwebpack-3-coming\u002F01.jpg)\n\nwebpack 2 打包结果\n\n\n![webpack 3 打包结果](\u002Fassets\u002Fweb\u002F2017\u002Fwebpack-3-coming\u002F02.jpg)\n\nwebpack 3 打包结果\n\n## webpack 的未来\n\n在 webpack 3 的介绍文章中，还强调了一些值得关注的点，比如会更关注社区用户的需求，大家最需要哪个特性，就优先开发哪个特性。此外发布周期也会更短，不会再以年为单位进行 beta 或者 RC。\n\n在 What's Next 部分，则列出了一些可能会重点关注的点：\n\n- 构建过程中更好的缓存\n- 更快的冷启动构建和增量构建\n- 更好的 TypeScript 使用体验\n- 重构长缓存机制\n- WASM 模块支持\n- 提升用户体验\n\n整体来看，webpack 的团队还是非常积极的。大家之前所诟病的问题，像文档、配置、构建速度等等都已经在逐渐改善。按这个趋势的话，我对 webpack 的未来还是相当有信心的。\n\n怎么样，看完了还觉得惶恐吗？\n\n参考资料:\n- [webpack 3: Official Release!!](https:\u002F\u002Fmedium.com\u002Fwebpack\u002Fwebpack-3-official-release-15fd2dd8f07b)\n","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fweb\u002F2017\u002Fwebpack-3-coming\u002F01.jpg",{"id":79,"type":7,"slug":80,"title":81,"date":82,"category":46,"tags":83,"body_markdown":85,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":30},84,"what-is-your-ideal","【问答】别跟我谈理想 我的理想是不上班","2017-06-14 09:25",[84],"问答","\n“工作倦怠期，不想上班怎么办？”\n\n很好的问题，好到我好几天都不知道要怎么回答这个问题。\n\n## 引子\n\n想起一个段子，我把它写到了本文标题上，叫“别跟我谈理想，我的理想是不上班。”\n\n这个段子在网上引起了相当多的共鸣，可见不想上班并不是一个个案，而是普遍存在的心态。既然如此普遍，为什么还会有“怎么办”这样一问。按照时下流行的文风（多年以前，叫“一秒毁掉小清新”，现在不知道叫啥），这个问题的标准答案应该是“既然不想上班那就不上。”\n\n好了，全文结束，谢谢大家。\n\n## 现实\n\n咦，你还在看哦？那意思就是你对上面的答案并不满意喽？我想大部分人可能都会觉得这个答案完全就是在扯淡嘛。虽然我说不想上班，但是还是得上班，甚至还是得精神饱满地去上班：领导早，同事好，这个东东有前途，那个体验很重要！\n\n如果你觉得你应该是需要好好上班的，只是想摆脱不想上班的状态。那说明在潜意识里，你并不认可不想上班这样一种心理。这也是我们这么多年所接受的教育带来的一种意识：劳动最光荣，人应该充满激情地投入劳动中，否则就是不对的，是没有前途的，是该被否定的。\n\n\u003C!-- more -->\n\n## 理想\n\n在对工作产生倦怠的时候，一种很充满正能量的方案，就是用理想来鞭策自己。\n\n“妈的，你好烦，都说了我的理想是不上班……”\n\n对不起，不上班叫欲望，不叫理想。\n\n> 理想今年你几岁\n> 你总是诱惑着年轻的朋友\n> 你总是谢了又开 给我惊喜\n> 又让我沉入失望的生活里\n\n这个，才是理想的定义……等会，跑错片场了，我明明找的是理想的百科呀，怎么变歌词了？\n\n> 理想，是对未来事物的美好想象和希望，也比喻对某事物臻于最完善境界的观念。是人们在实践过程中形成的、有实现可能性的、对未来社会和自身发展的向往与追求，是人们的世界观、人生观和价值观在奋斗目标上的集中体现。满足眼前的物质和精神需求，又憧憬未来的生活目标，期盼满足更高的物质和精神需求。对现状永不满足、对未来不懈追求，是理想形成的动力和源泉。\n\n这就不解释了，或者通俗一些说，当你想让这个世界变得更好一点的时候，这时候的想法才能算是理想。作为一个互联网从业者，把你手上的产品做得更好一点，让用户用起来快一点点，方便一点点，这也算是一个理想。\n\n大约四五年前，曾经负责过一个项目管理的项目。一开始这个项目组很多人，大家一起去打磨产品，很开心。但是后来项目稳定之后，项目组其他成员开始逐步抽调到其他项目，最后整个项目基本上只剩下我一个人维护。但是越是到这种时候，我竟然越觉得这个项目像是自己的作品，想要去做好它的每一个方面。甚至还会专门跨几栋楼，跑到我们的资深用户那里去做现场观察和访谈，然后带回第一手的资料投入到产品的下一个迭代改进中。\n\n那时候的我，挺有理想的，小小的，却很坚定。这种状态下，你就会很想上班，很想去打磨好自己的产品。\n\n## 鸡汤\n\n理想很丰满，现实很骨感。并不是所有产品都能激起人想为之奋斗终身的使命感。如果你的产品确实太无聊了，让你没有任何欲望，怎么办？\n\n现实一点说，有时候确实没有太多办法，工作无法激起你的激情的时候，想得到一个好的工作状态并不是一件容易的事情。\n\n不过，好在我们仍然有事情可以做。\n\n所谓工作，也无非就是干活。对于码农来说，自然就是码代码了，对设计师来说，就是画稿了。当这些活没有意思的时候，就会觉得对工作产生了倦怠。\n\n不过，当你回首一天的工作的时候，会发现其实你的时间并不都只是用在码代码和画稿上。你会需要跟 leader 汇报项目现状，需要跟项目经理汇报排期和工作进度，需要跟项目组成员讨论需求细节，需要去准备各种文档，需要去和上下游进行流程上的合作，需要使用思维导图、版本管理、编辑器、画图软件等工具来辅助你写代码或者画稿……\n\n如果工作本身没有意思了，这些东西有没有可能有意思呢？跟一个你仰慕的 leader 说话会不会让你小鹿乱撞？汇报排期和工作进度是胸有成竹还是一无所知？产品细节讨论是否都能 get 到点并正确表达自己的观点？合作流程是否清楚，是否知道什么时间点该找谁要东西了？软件是否玩得转？\n\n于我而言，这些都是工作中有意思的东西，甚至吸引力并不比工作内容本身差。当工作内容千篇一律的时候，我会在工作本身之外的事情尝试使用不同的策略和工具。\n\n比如对于排期，有时候会让自己紧张一些，保持一种快速编码快速对接需求的状态，同时也是检验自己的编码速度和质量极限在哪里，有时候会多排一些时间让自己放松一些，以便留有余地处理突发情况或者偶尔玩心大发时能放纵一两个小时。\n\n比如对于工具，我会尽量尝试在每一个项目中使用不同的工具。这样虽然工作内容是一样的，但是我自己做的事情并不完全相同，而这些不同都会带来一些兴奋点。\n\n当我第一次接触到 Git 的时候，感觉它好像有那么一点意思，于是花了一些时间去学习它，顺便也考察了一下市面上的 GUI 工具。但当时我们的项目使用的是 SVN 来进行版本管理。于是我又学了一下 SVN 和 Git 的关系，它们的区别，以及怎么互相迁移，工具怎么互相使用。最后我就直接在我的项目中使用了 git-svn 。\n\n看起来这毫无意义，但是却有一种满足感，又学到了一些新东西，并且对原来学到的东西认知更深入。这种“我又学到了新东西，感觉又 level up 了”的心理，对一个人的状态提升是非常巨大的。而这些认知在我之后将团队的版本工具从 SVN 整体切到 Git 的时候起到了巨大的作用。当然，这是后话了。\n\n## 退而结网\n\n再退一步，也有可能，并非所有的公司都有这样宽松的环境让你自己去做很多决策。有可能排期是你的 leader 直接安排，没有商量余地，有可能工具是组内大牛直接选定，不允许自己玩。那不是就悲剧了吗？\n\n确实如此，公司项目中必然还是要以公司利益为重，在很多现实的考量下，有时候就没有那么宽松的环境了。\n\n但是不用太过悲观。一方面，总有一些东西还是有空间的，比如选择用 sublime text 还是 vscode ，选择 Git 命令行还是 SourceTree 。另一方面，如果公司的地盘无法做主，我们还可以有自己的地盘。也就是说，除了公司的项目外，我们仍然可以有自己的项目，也就是所谓的 side project 。\n\n关于 side project 的话题，可以在之后专门来聊一聊。这里只需要知道，一旦有了 side project ，就有了自己的一片试验田。这些项目可以很小，也可以用户量很少，但是你仍然可以像一个真实的项目一样去照顾它的方方面面，最重要的是，这个过程中，你拥有100%的自主选择权。而一旦有了自己喜欢的项目，又选择了自己心仪的各种方案，很难再出现不想把它做下去的问题。\n\n于是，人的状态又会不一样，看，这个人始终鸡血满满的，但只有你自己知道你在为什么而兴奋。\n\n好了，全文结束，谢谢大家。\n\n> 对了，公众号也是个 side project 。以及，我的理想是有一天公众号的粉丝可以自然增长，有可能实现吗？\n",{"id":87,"type":7,"slug":88,"title":89,"date":90,"category":46,"tags":91,"body_markdown":95,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":96},80,"a-travel-to-beijing","【多图】北京行流水账","2017-06-11 17:26",[92,93,94],"影像","北京","GMTC","\n![题图：北京机场天花板](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F01.jpg)\n\n最近参加了 GMTC 2017 会议，因此来了一趟北京。现在在北京机场，飞机又延误，等着回深圳。趁这个时间，先记一下这次北京行的流水账。\n\n## 凌晨4点的北京\n\n为了不耽误周四的上班时间，订了周四晚上8点的飞机，本来觉得已经够晚的了，但是没想到居然再延误了4个小时，12点多才从深圳起飞。（其实什么时候飞的我都不太记得了，当时脑子里只有一个概念，就是我要补觉，于是一上飞机就开始睡。）\n\n到达北京的时候已经是凌晨3点半了，下机之后又坐摆渡车什么的，等我快出机场的时候，发现天边已经泛出一丝金边。虽然有点不敢相信，但是还是很快意识到，天要亮了……\n\n出机场坐了个的士，一边跑一边欣赏车外的月亮。那天月亮很大很白，低低地挂在天边，跟着的士一路跑到了酒店，很美，让我想起一句歌词“去到来时的路上 \u002F 还是那躺在公路尽头的月亮”。\n\n“你见过凌晨4点的北京吗”\n\n“嗯啊，天快亮了，月亮美得很，咋了？”\n\n![凌晨4点的北京机场](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F02.jpg)\n\n![凌晨4点的北京月亮](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F03.jpg)\n\n（月亮被拍毁了，渣）\n\n\u003C!-- more -->\n\n## GMTC 2017\n\nGMTC 是一个话题非常密集的会议，不想错过，于是在酒店趟了几个小时后就起床去参加 GMTC 的分享了。第一天的主题基本上以 app 方面为主，中间也有碰到一些有意思的主题，比如小米的 hybrid 方案，也就是在 MIUI 上做一个小程序。关于听会的详细情况，后面会再详细说一说。\n\n![小米hybrid方案](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F04.jpg)\n\n我的分享被安排在第二天下午的“工程化专场”。一开始觉得不是太紧张，但是时间越临近越越来控制紧张的情绪。于是拿起酒店准备的纸笔开始瞎画，以缓存紧张的心理。（后来还被裕波拍照了……）\n\n![瞎画](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F05.jpg)\n\n分享的过程基本上还算顺利，最终超时几分钟完成，也就冲掉了 QA 的时间。会后也有挺多人对这个话题感兴趣，私下交流了很多。\n\n![演讲照片](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F06.jpg)\n\n![会后交流](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F07.jpg)\n\n## 黑科技\n\n在来北京的路上，发现以前的同事、现在大疆的solo哥也一同前往，于是前两天在听议题的时候基本上都和solo哥一起行动的。\n\n第二天中午有幸把玩了一下大疆刚出不久的 spark ，也就是传说中可以不要遥控器，直接使用手势控制的无人机。效果确实非常震撼，这台无人机的体积非常小，手势控制的准确度也非常不错，轻度玩家或者自拍控应该会很喜欢。\n\n当然，我们玩的时候也吸引了非常多人的围观，其中也有不少妹子，唯一遗憾的是，好像solo哥在交流只顾着无人机了，不知道有没有看上哪个妹子呢？\n\n![无人机](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F08.jpg)\n\n## 故友新朋\n\n在北京这几天也去会了新老朋友。\n\nGMTC 结束以后裕波和小鱼组了个局，于是我也就跟着去蹭了一顿饭。在京味斋吃，味道很不错。技术大牛们人非常 Nice ，路上也有跟小鱼交流一些团队的情况，收获不少。唯一的问题是等位略久了点，不过这也制造了更多的自由交流时间，也挺好。\n\n（此处少一张照片）\n\n第一天的会议结束后溜出去找之前大学的朋友们吃了一顿饭。虽然许久不见，大家却还是那么亲切。另外值得一提的是，我们随意选的一家，居然最后结账的时候发现是韩寒开的，叫“很高兴遇见你”。\n\n![大学同学](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F09.jpg)\n\n第三天（也就是今天），跟群里的网友们见了个面，这也是人生中为数不多的网友见面会。有点出乎意料的是发现大家比想象中更要 Nice ，非常平和亲切。Key大还专门赠送了一些礼物，感动了一把。非常感谢群友们的支持，希望未来还有机会跟你们聚一聚。当然，要特别感谢托大，非常地帅气，请完客又专程送我到机场，感激之情无以言表。\n\n![Key大给的礼物](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F10.jpg)\n\n![网友见面](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F11.jpg)\n\n## 其他\n\n今天早上去了一趟鸟巢，算是故地重游了。上一次来还是2009年，奥运后刚结束之后的鸟巢和水立方显得特别大气。而今天去的时候发现整个场馆周围都被围起来了，而且鸟巢和水立方之间还种上了绿化带，完全没有了气势磅磗的感觉，于是草草转了半圈就走了，不免有点遗憾。\n\n![鸟巢](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F12.jpg)\n\n在北京之所以能跑这么多地方，也是因为有小黄车。之前来北京只是觉得北京的单车特别多，这一次来却有了不一样的感觉。\n\n北京的小黄车大部分都已经是电子锁实心胎，数量多，摆放也整齐，更重要的是，基本上没有坏车。而相比之下，深圳的小黄车就感觉很难找了，有时候好不容易找到，却发现要么没气，要么被破坏，要么被私用了。\n\n骑起来也明显是北京感觉更好一些，因为北京有专用的自行车道，路权有充分的保障，甚至在红绿灯路口都专门有标识出自行车通道。而深圳的自行车道，只能呵呵了。\n\n![共享单车](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F13.jpg)\n\n![自行车道](\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F14.jpg)\n\n好啦，流水账就到这里，总体而言这次北京之行感觉还是相当不错的。关于 GMTC 听会的一些技术方面的感想，稍后再专门花时间聊一聊。\n","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Flife\u002F2017\u002Fa-travel-to-beijing\u002F01.jpg",{"id":98,"type":7,"slug":99,"title":100,"date":101,"category":11,"tags":102,"body_markdown":105,"permalink":106,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":30},169,"frontend-recent-safety-tech","一些比较新的前端安全相关的技术点","2017-05-18 15:06",[103,26,104],"安全","HTTP","\n> 本文为[《Web前后端漏洞分析与防御》](https:\u002F\u002Fcoding.imooc.com\u002Fclass\u002F104.html)课程的配套文章。早年在慕课社区发布，现补发在博客上。\n\n一说到安全，大家总会特别敏感，尤其是有相当部分的前端开发者并不了解安全相关的知识，颇有谈虎色变的感觉。具体到前端安全这个话题呢，又有些说不清道不明，因为大部分的防御方案，总少不了后端的参与，也有开发者慢慢觉得好像安全都应该由后端来关注了。\n\n其实不然，起码 XSS CSRF 这一类的安全问题前端是一定要了解它们的原理和防御方法的。从防御方法上来说，XSS 和 CSRF 的防御在业界都有比较成熟的方案了。本文将记录一些比较新的防御方案，可能有一些比较老的书籍或者文章中不会提及这些方法。\n\n\u003C!-- more -->\n\n## XSS\n\nXSS 全称 Cross Site Scripting ，跨站脚本攻击，因为 CSS 这名字老早就被样式表拿走了，大家都在 web 这个领域，重名又不好看，所以只好起了个名字叫 XSS 了。说实话从 XSS 干的事来讲，其实并不太理解为什么有个“跨站”在里面，如果一定要强行解释的话，大概是因为有可能会运行一个来自别人网站的脚本，把这个脚本叫“跨站”了吧。\n\n好了，不重要。\n\n重要的是它是谁，它从哪里来，要到哪里去……这样的哲学问题有点难回答。我们换一个，重要的是它是什么鬼，能干什么，怎么防。\n\n### XSS 是什么鬼\n\n玩游戏的人都知道在游戏中经常出现一些奇葩的名字，比如“星辰并亲了他一口”，看上去一脸不知道什么鬼，但是当他有点事的时候，你就觉得好玩了，比如公会老大邀请他，就会有一条消息“公会老大邀请了星辰并亲了他一口”，然后一公会的人哄堂大笑。\n\n其实这种案例用专业的术语来说，就叫 XSS 了……\n\n本来“公会老大邀请了XX”这样一个句式，只希望XX是一个不会引起误会的名字而已，结果因为有一个奇葩名字，直接改变了整句话的意思，引介出了意外的含义。\n\nXSS 也是同样的东西，比如我只想在页面上显示一个名字：\n\n```html\n\u003Cspan class=\"name\">{{name}}\u003C\u002Fspan>\n```\n\n但是，如果我的名字是长这样的：\n\n```\n星辰\u003Cscript>alert('SB')\u003C\u002Fscript>\n```\n\n这时候就好玩了：\n\n```html\n\u003Cspan class=\"name\">星辰\u003Cscript>alert('SB')\u003C\u002Fscript>\u003C\u002Fspan>\n```\n\n你看，页面中凭空多了一段脚本。这个例子还算善良的，只是弹出来骂了你一句……\n\n“难道他还能弹出来打我？”\n\n呃……当然不是啦，但是人家可以偷偷干坏事啊。你说说，你都用JS干嘛？用户登录用的JS吧，读取资料用的JS吧，点击买东西、消费用的JS吧，查用户有多少钱用的JS吧，基于 Cookies 也可以读写吧。好的，你用JS能干的事情人家都能干。\n\n没事偷你个登录态，帮用户消费两块钱，查下你用户手机号是多少……接下来的事情我就不说了（无非就是前端程序员要背锅离职呗？）\n\n### XSS 怎么防御\n\n一个经典的防御方法就是对内容进行转义和过滤，比如\n\n```javascript\nvar escapeHtml = function(str) {\n\tif(!str) return '';\n\tstr = str.replace(\u002F&\u002Fg, '&amp;');\n\tstr = str.replace(\u002F\u003C\u002Fg, '&lt;');\n\tstr = str.replace(\u002F>\u002Fg, '&gt;');\n\tstr = str.replace(\u002F\"\u002Fg, '&quto;');\n\tstr = str.replace(\u002F'\u002Fg, '&#39;');\n\t\u002F\u002F str = str.replace(\u002F \u002Fg, '&#32;');\n\treturn str;\n};\n\nvar name = escapeHtml(`\u003Cscript>alert('SB')\u003C\u002Fscript>`);\n```\n\n此时 name 会变成\n\n```\n&lt;script&gt;alert(&#39;SB&#39;)&lt;\u002Fscript&gt;\n```\n\n这样就会原样显示出来，再也无法耍流氓啦。\n\n当然，富文本还要更麻烦一些，因为要保留一部分标签和属性，要不然全变纯文本了，就不富了。这种情况一般通过黑名单进行过滤，或者白名单放行。即只允许一部分指定的标签和属性，其它的全部转义掉。\n\n### CSP 大法\n\n前面转义的方法的出发点，是让用户的输入不要变成程序，输入的什么就让它输出成什么。\n\n事实上现代浏览器为我们带来了一个全新的安全策略，叫作内容安全策略，Content Security Policy，简称CSP。CSP的思路跟转义不一样，它的着手点是，如果一段代码变成了程序，我们是否应该运行它。或者更准确一点说，它实际上是定义页面上哪一些内容是可被信任的，哪一些内容是不被信任的。\n\n因为我们自己的脚本是预先就知道并放在页面上的，所以我们可以设置好信任关系，当有 XSS 脚本出现时，它并不在我们的信任列表中，因此可以阻止它运行。\n\n它的具体使用方式是在 HTTP 头中输出 CSP 策略：\n\n```\nContent-Security-Policy: \u003Cpolicy-directive>; \u003Cpolicy-directive>\n```\n\n从语法上可以看到，一个头可以输出多个策略，每一个策略由一个指令和指令对应的值组成。指令可以理解为指定内容类型的，比如`script-src`指令用于指定脚本，`img-src`用于指定图片。值则主要是来源，比如某个指定的URL，或者`self`表示同源，或者`unsafe-inline`表示在页面上直接出现的脚本等。\n\n详细的指令和值，可以查看[MDN相关页面](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FHeaders\u002FContent-Security-Policy)。\n\n具体到上面的 XSS 例子，可以使用\n\n```\nContent-Security-Policy: script-src 'self';\n```\n\n这样除了在同一个域名下的JS文件外，其它的脚本都不可以执行了，自然之前 XSS 的内容也就失效啦。简单粗暴有没有？\n\n当然，如果你说，我就是要在页面中放点内联的脚本，不可以么？当然可以啦，CSP 设计的时候也考虑了这些情况，还是相当灵活的。你只需要指定一个 nonce 属性，或者计算一下 hash 值，即可。详细的用法看 [MDN](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FHeaders\u002FContent-Security-Policy\u002Fscript-src) 哦。\n\n说实话，用 CSP 来处理 XSS 攻击还是不如转义来得优雅，因为转义可以不影响用户输入输出，不改变内容的本质。但是 CSP 提供了足够简单而又灵活的方式来防御 XSS ，可以很好地作为我们前端 XSS 防御的最后一道防线。\n\n## CSRF\n\nCSRF 也是个望文生不到义的词，它的全称是 Cross Site Request Foggy，即跨站请求攻击。虽然也有跨站，但我觉得这个跨站还是相当可以理解的，它真的是从别的网站发起一个请求到我们的网站的。\n\n当一个用户登录我们的网站后，在 Cookies 中会存放用户的身份凭证。在大部分时候，就是一个 SessionId 。当用户下次访问我们的网站的时候，我们用这个凭证识别出用户是谁，有没有登录态。\n\n如果第三方网站的代码请求了我们的网站，会发生什么呢？比如\n\n```html\n\u003Cimg src=\"http:\u002F\u002Fwww.example.com\u002Fhaha\" \u002F>\n```\n\n虽然它是一张图片，但它确实向`www.example.com`发了一个请求，如此用户有登录态的话，其实就相当于是用户自己发了一个请求。如果这个地址是一个发表文章、发布微博甚至转账之类的链接，那用户就在不知情的情况下进行了一些操作。这也是比较严重的安全问题。\n\n当然你可能会说，现在谁还这么弱智，把这么敏感的操作用 GET 啊？没错，你可以选择用 POST ，但是这丝毫不能阻止 CSRF 攻击的发生啊。\n\n```html\n\u003Ciframe name=\"test\">\u003C\u002Fiframe>\n\u003Cform target=\"test\" method=\"post\" action=\"http:\u002F\u002Fwww.example.com\u002Fhaha\">\n    ...\n\u003C\u002Fform>\n```\n\n当这个表单提交的时候，我们就发了一个 POST 请求。华丽丽的 CSRF 。\n\n### CSRF 的常规防御\n\nCSRF 比较常规的防御方式是通过判断来源和加 token。\n\n判断来源比较简单，主要是判断`referer`这个头，如果不是自己的网站，就返回错误。\n\n加 token 即同样的随机 token，在 cookies 中放一份，在表单中再放一份。这样第三方网站就无法获取到这个 token 是什么。\n\n但是这样做也有一个比较明显的问题，就是无法保证站内用户的体验。虽然你防了站外的攻击，但是也降低了站内用户的体验。具体表现在如果同时打开多个表单，只有最后一个表单能成功提交。\n\n### same-site 的 Cookie\n\n回想 CSRF 之所以能够攻击成功，核心原因就在于用户的身份是放在 Cookies 中的，而不管你通过什么方式访问网站，都会带上这个网站的 Cookies ，从第三方来的访问自然也不能例外。\n\n但是，Chrome 在这个问题上给了我们不同的答案，可以放第三方访问时不带 Cookies 。也就是说 Cookies 只有本站能用，来自第三方的访问都不能使用。\n\n具体的使用方式，是在打 Cookie 的时候，加上一个属性：`SameSite`，它的值有两：\n\n- `strict` 任何来自第三方的请求都不能使用 Cookies ，包括通过链接点进来的\n- `lex` 只有比较敏感的操作不带 Cookies ，比如表单提交\n\n针对 CSRF ，我们可以将 Cookies 设置成`SameSite: strict`的，这样就可以有效防御 CSRF 了。不过比较可惜的是，目前只有 Chrome 才支持这一属性。希望未来所有浏览器都能跟上脚步。\n\n> 使用 SameSite 还会面临一个问题，如果用户是点击链接进来的，那么是不能使用登录态的。一般可以考虑将用户不敏感的信息不设置这个属性，点进来仍然可以显示当前用户是谁，但是在请求的时候要求一个比较敏感的 SameSite 的 Cookies。这里需要更多的实践经验来探索。\n\n## 小结\n\n本文重点讲了 XSS 和 CSRF 这两种比较常见的前端安全问题的防御思路，尤其是如何使用一些新的规范、实现来帮助我们进行防御。希望后面浏览器对这些安全相关防御办法的普及率能再高一些，让前端工程师能花更少的时间写出更安全的代码。\n","\u002Farticle\u002Ffrontend-recent-safety-tech.html",{"id":108,"type":7,"slug":109,"title":110,"date":111,"category":11,"tags":112,"body_markdown":114,"permalink":115,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":30},170,"how_nginx_processes_a_request","【译】nginx是如何处理请求的","2017-05-05 12:55",[113],"nginx","\nNginx首先需要确定由哪个`server`来处理请求。我们看一个简单的配置文件，在`*:80`端口上包含了三个虚拟主机（`server`）：\n\n```\nserver {\n    listen      80;\n    server_name example.org www.example.org;\n    ...\n}\n\nserver {\n    listen      80;\n    server_name example.net www.example.net;\n    ...\n}\n\nserver {\n    listen      80;\n    server_name example.com www.example.com;\n    ...\n}\n```\n\n在这个配置下，Nginx只通过请求的`Host`头来决定路由到哪个`server`。如果`Host`头的值跟所有的`server`的`server_name`都不匹配，或者请求中没有包含这个阔大，Nginx会使用这个端口上的默认`server`。在这个配置文件中，默认`server`是指第一个，这正是Nginx标准的默认行为。默认`server`也可以通过配置显示指定，只需要在`listen`指令值中加上`default_server`参数即可。\n\n\u003C!-- more -->\n\n```\nserver {\n    listen      80 default_server;\n    server_name example.net www.example.net;\n    ...\n}\n```\n\n> `default_server`参数在 0.8.21 版本之后可用。在更早的版本中，应该使用`default`参数。\n\n值得注意的是，默认`server`是`listen`指令的参数，而不是`server_name`的。下文会详细介绍。\n\n## 如何阻止没有指明 Host 的请求\n\n如果要禁止没有包含`Host`头的请求，可以用下面的配置让一个`server`丢弃这样的请求：\n\n```\nserver {\n    listen      80;\n    server_name \"\";\n    return      444;\n}\n```\n\n如果请求没有带`Host`头，将与`server_name`为空字符串的`server`匹配，返回非标准的私有状态码444时，Nginx会关闭连接。\n\n> 从 0.8.48 版本开始，空字符串是`server_name`的默认值，因此`server_name \"\"`可以省略。在更早的版本中，默认值是机器的主机名。\n\n## 基于 IP 和基于主机名的虚拟主机\n\n我们来看一个更复杂的例子，这个例子中有一些监听在不同地址上的`server`：\n\n```\nserver {\n    listen      192.168.1.1:80;\n    server_name example.org www.example.org;\n    ...\n}\n\nserver {\n    listen      192.168.1.1:80;\n    server_name example.net www.example.net;\n    ...\n}\n\nserver {\n    listen      192.168.1.2:80;\n    server_name example.com www.example.com;\n    ...\n}\n```\n\n在这个配置中，Nginx 首先将请求的 IP 地址和端口与`server`进行匹配，得到一些匹配的`server`。然后根据请求的`Host`头与这些`server`的`server_name`进行匹配。如果`server_name`无法匹配，则请求将由默认`server`处理。例如，在`192.168.1.1:80`上收到一个`www.example.com`的请求，这个请求将由`192.168.1.1:80`上的默认`server`进行处理，也就是由第一个`server`进行处理，因为无法在这个（IP 地址和）端口上匹配到`www.example.com`。\n\n前面已经说过，默认`server`是`listen`指令的一个参数，那么监听不同的（IP 地址和）端口就可以指定不同的默认`server`：\n\n```\nserver {\n    listen      192.168.1.1:80;\n    server_name example.org www.example.org;\n    ...\n}\n\nserver {\n    listen      192.168.1.1:80 default_server;\n    server_name example.net www.example.net;\n    ...\n}\n\nserver {\n    listen      192.168.1.2:80 default_server;\n    server_name example.com www.example.com;\n    ...\n}\n```\n\n## 一个简单的 PHP 站点配置\n\n现在我们通过一个简单的 PHP 站点来看一下 Nginx 是如何处理`location`的：\n\n```\nserver {\n    listen      80;\n    server_name example.org www.example.org;\n    root        \u002Fdata\u002Fwww;\n\n    location \u002F {\n        index   index.html index.php;\n    }\n\n    location ~* \\.(gif|jpg|png)$ {\n        expires 30d;\n    }\n\n    location ~ \\.php$ {\n        fastcgi_pass  localhost:9000;\n        fastcgi_param SCRIPT_FILENAME\n                      $document_root$fastcgi_script_name;\n        include       fastcgi_params;\n    }\n}\n```\n\nNginx 首先会通过遍历字符串的方式查找指向最具体的`location`前缀，这与配置顺便无关。在上面的楝文件中，唯一的前缀是`\u002F`，它可以匹配任意请求，将被最后使用。然后 Nginx 会按照配置书写的顺序进行正则表达式匹配。一旦匹配到第一条规则，则会停止后续查找，Nginx 将使用这个`location`。如果正则表达式匹配全部失败了，则 Nginx 会使用前面找到的前缀指向最具体的`location`。\n\n值得注意的是，所有类型的`location`都只匹配请求的`URI`部分，不包括任何参数。这是因为请求的参数可能有很多种形式，例如：\n\n```\n\u002Findex.php?user=john&page=1\n\u002Findex.php?page=1&user=john\n```\n\n此外，用户可以在参数中随便添加任何东西：\n\n```\n\u002Findex.php?page=1&something+else&user=john\n```\n\n现在我们来看看，按上面的配置文件，一个请求将如何被处理：\n\n- 请求`\u002Flogo.gif`首先被前缀`location` `\u002F`匹配到，然后被正则表达式`\\.(gif|jpg|png)$`匹配到。这样的话，它将由后者进行处理。因为指定了`root \u002Fdata\u002Fwww`，因此这个请求将被映射到`\u002Fdata\u002Fwww\u002Flogo.gif`，这个文件将被发送到客户端。\n- 请求`\u002Findex.php`也被前缀`location ` `\u002F`匹配到，然后被正则表达式`\\.(php)$`匹配到。这样的话，它将由后者进行处理。请求被转交到监听在`localhost:9000`的 FastCGI 服务进行处理。`fastcgi_param`指令会将 FastCGI 的参数`SCRIPT_FILENAME`设置为`\u002Fdata\u002Fwww\u002Findex.php`，然后 FastCGI 服务会执行这个文件。`$document_root`变量的值等于`root`指令的值，变量`$fastcgi_script_name`的值等于请求的 URI ，也就是`\u002Findex.php`。\n- 请求`\u002Fabout.html`只被前缀`location` `\u002F`匹配到，这样它将被在这个`location`中进行处理。因为指定了`root \u002Fdata\u002Fwww`，因此这个请求将被映射到`\u002Fdata\u002Fwww\u002Fabout.html`，这个文件将被发送到客户端。\n- 请求`\u002F`的处理更复杂一些。它只被前缀`location` `\u002F`匹配到，这样它将被在这个`location`中进行处理。接下来`index`指令将根据`root \u002Fdata\u002Fwww`指令的路径探测`index`参数中指定的文件是否存在。如果`\u002Fdata\u002Fwww\u002Findex.html`不存在，而`\u002Fdata\u002Fwww\u002Findex.php`存在，则该指令将内部跳转到`\u002Findex.php`，然后 Nginx 会像对待新请求一样重新匹配这个请求。如前文所述，这个请求将被 FastCGI 服务进行处理。\n\n编写：Igor Sysoev 编辑：Brian Mercer\n\n原文：\u003Chttp:\u002F\u002Fnginx.org\u002Fen\u002Fdocs\u002Fhttp\u002Frequest_processing.html>\n","\u002Farticle\u002Fhow_nginx_processes_a_request.html",{"id":117,"type":7,"slug":118,"title":119,"date":120,"category":11,"tags":121,"body_markdown":124,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"cover":30},181,"why-richtext-editor-on-web-is-terrible","为什么web富文本编辑器是天坑？","2017-03-15 10:17",[11,122,123],"富文本","编辑器","\n知乎看到一个问题，《为什么都说富文本编辑器是天坑》。里面的答案挺有意思的，各位做web前端的大牛们都有自己的切肤之痛。有兴趣的可以手工复制以下网址查看：\n\n\u003Chttps:\u002F\u002Fwww.zhihu.com\u002Fquestion\u002F38699645\u002Fanswer\u002F108376836>\n\n但是这些答案大部分还是停留在碰到的问题上面，而没有去深入总结和思考富文本编辑器背后的需求和实现思路。正好前两年在medium上看到一篇文章，叫《Why ContentEditable is Terrible》，推荐给大家。原文可点击左下角链接查看。\n\n\u003Chttps:\u002F\u002Fmedium.engineering\u002Fwhy-contenteditable-is-terrible-122d8a40e480>\n\n\u003C!-- more -->\n\n以下为简略意译版本：\n\n【一段引子，大意就是表示作者在富文本编辑器的问题上已经思考了一年了。】\n\n从数学证明的角度来说，ContentEditable在富文本的编辑这件事上是有问题的（broken，也许应该翻为烯烂？）。\n\n首先，什么叫作“所见即所得”（WYSIWYG）？当你选中一段文本按下回车的时候会发生什么？这些问题的定义并不是很清楚，而从数学证明的角度可以首先弄明白我们要研究的问题是什么。\n\n什么叫所见即所得？一个好的所见即所得编辑器应该遵守以下三条公理：\n\n- DOM内容和可见的内容的映射应该是工作良好的（well-behaved）\n- DOM选区和可见的选区的映射应该是工作良好的（well-behaved）\n- 所有可见的编辑操作应该映射到对可见内容的一个代数闭域和完整集合\n\n我将会解释这三条分别是什么意思，以及富文本编辑器为什么应该遵守这三条规则。但是首先我们要记住，公理是不需要证明的，所以它们的正确性先不作置疑。\n\n接下来，我们会看到ContentEditable无法遵守上面的任意一条公理。\n\n最后我们来看一看一些解决方案，以及Medium的富文本编辑器如何解决这个问题。\n\n。。。敷衍的分隔线。。。\n\nDOM空间是指页面的内在结构，而可视空间（WYSIWYG）是页面的渲染结果。当我们说两个页面一样时，是指他们在可视空间看起来一样。\n\n浏览器的渲染引擎做的工作就是将DOM空间映射到可视空间。即对DOM x来说，可视空间 = 渲染函数Render(x)。\n\n当我们说一个映射工作良好（well-behaved）时，是指所有的编辑操作都能在映射中反映出来。如果用E表示编辑操作，x和y表示DOM，则有\n\n- 如果Render(x) = Render(y)\n- 那么Render(E(x)) = Render(E(y))\n\n这其实是在处理“所见”之后的“所得”部分。即如果两个页面看起来一样，我们对它们做相同的编辑操作，那么操作之后的结果应该仍然看起来一样。\n\n事实上无数的富文本编辑器无法满足这个假设。看例子\n\nThe [hobbit](http:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FThe_Hobbit) was a very well-to-do hobbit, and his name was *_Baggins_*.\n\n对应的DOM大概如下\n\n```html\nThe \u003Ca href=”http:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FThe_Hobbit\">hobbit\u003C\u002Fa> was a very well-to-do hobbit, and his name was \u003Cstrong>\u003Cem>Baggins\u003C\u002Fem>\u003C\u002Fstrong>.\n```\n\n对于粗体和任何的部分，有非常多的编码方法\n\n```html\n\u003Cstrong>\u003Cem>Baggins\u003C\u002Fem>\u003C\u002Fstrong>\n\u003Cem>\u003Cstrong>Baggins\u003C\u002Fstrong>\u003C\u002Fem>\n\u003Cem>\u003Cstrong>Bagg\u003C\u002Fstrong>\u003Cstrong>ins\u003C\u002Fstrong>\u003C\u002Fem>\n\u003Cem>\u003Cstrong>Bagg\u003C\u002Fstrong>\u003C\u002Fem>\u003Cstrong>\u003Cem>ins\u003C\u002Fem>\u003C\u002Fstrong>\n```\n\n从编辑器的角度来说，它们应该是一样的，但是从DOM的角度很难识别它们是同样的东西。\n\n很多编辑器在这方面会有问题，比如有一个空的`span`可能会导致两个ContentEditable元素看起来一样，但编辑行为完全不一样，让用户觉得很难编辑，工程师也很难调试。\n\n理想情况下，我们应该可以对DOM使用可视化编辑的指令，这些指令会保证对可视空间相同的页面进行相同编辑操作后可视空间仍然是完全相同的。\n\n。。。分隔线。。。\n\nDOM空间和可视空间的映射很抓狂，但至少是多对一的，一个DOM只会有一种可视表现。\n\n选区就更糟糕了，它是多对多映射的。\n\n```html\nhis name was \u003Cstrong>\u003Cem>Baggins\u003C\u002Fem>\u003C\u002Fstrong>\n```\n\n假设这样一段内容，光标在Baggins前面，那么光标到底是在`\u003Cstrong>`前后还是`\u003Cem>`前后？你打字的时候希望是粗体的还是斜体的还是没有任何样式的？\n\n更麻烦的是，一个DOM选区会有多种可视表示。比如“well-to-do”在“to-”后面折行了，那么在光标在第一行最后和在第二行开始对应着相同的DOM位置，但可视位置却不一样。\n\n我们设计编辑器时希望选区看起来和表现起来都是一样的，但是这个映射完全乱七八糟。\n\n。。。分隔线。。。\n\n【这一段举了webkit的例子，省略，只说结论】\n\n富文本编辑器可以产出非常简洁易懂的代码，但是一旦碰到来自外部的内容就蒙逼了，比如粘贴自网页或者word的内容。\n\n一个好的富文本编辑器的内容应该是对自己的编辑行为闭域的，即内容应该只来自于正常的输入和编辑，而不应该介入来自外部的内容。\n\n。。。分隔线。。。\n\n好的富文本要怎么做？Medium的思路：\n\n1. 为文档生成数据模型，可以根据模型判断是否在可视空间相同\n2. 建立从DOM到模型的映射关系\n3. 在模型上建立编辑操作的定义\n4. 将所有的键盘和鼠标操作映射到上述编辑操作\n\n1 模型\n\nMedium编辑器的模型包含两部分：一个段落列表，一个区块列表。\n\n每个段落包括：纯文本，标记（如第1个字到第5个字是粗体），图片等数据，布局数据（位置等）\n\n区域则是包含段落的一片区域。\n\n选区使用两个点来表示，每个点由段落和索引和文本的偏移量来表示。\n\n这个模型的好处是当且仅当两个模型相同的时候，可视空间才会相同，模型的任何变更都会很好地映射到可视空间（well-defined）。\n\n2 DOM到模型的映射\n\n分为内部映射和外部映射。内部映射是DOM和模型的映射，我们希望是一对一的。外部映射是当我们拿到粘贴的内容时，需要将内容映射到我们的段落-区块模型。我们希望外部映射是有损的，首先处理纯文本，然后是粗体\u002F任何\u002F链接等标记，接下来是图片和其它的格式 。\n\n将模型映射到DOM看起来类似这样：\n\n```html\n\u003Cdiv> \u003C!-- root -->\n  \u003Csection> \u003C!-- section -->\n    \u003C!-- section-inner -->\n    \u003Cdiv class=\"section-inner layout-column\">\n      \u003Cp>  \u003C!-- paragraph -->\n        \u003Cstrong>\u003Cem>Baggins\u003C\u002Fem>\u003C\u002Fstrong> \u003C!-- text -->\n```\n\n【省略一段解释对应关系的，注释中都有写了。】\n\n映射样式的时候，我们会按顺序进行：首先是A，然后STRONG，然后EM，绝不会出现A在STRONG中的情况。\n\n3 编辑操作\n\n定义了6种操作：添加段落、移除段落、更新段落、添加选区、移除选区、更新选区。\n\n所有的编辑操作都可以由这6种操作进行组合而来。在这些操作下，所有的内容都是结构良好的。这些操作直接对应我们模型的变更，而不是DOM的变更 。\n\n4 捕获编辑动作\n\n键盘和鼠标操作被映射到这6种操作中，具体而言是从ContentEditable操作中映射而来：换行（enter\u002Fctrl-m）、删除（delete\u002Fbackspace）、输入、粘贴等。我们捕获这些事件，然后阻止它们的默认动作，然后将这些动作转换成内部的编辑操作。\n\n对其它的键盘事件，我们不阻止默认动作，而是让ContentEditor进行操作，然后在事件结束后将段落和DOM再映射回模型。\n\n如果每次模型变更都全量更新DOM，编辑体验会很差，因为内容在不停闪烁。我们监听了模型的变更，尽量让DOM的变化最小化。\n\n。。。分隔线。。。\n\n【展望未来的富文本编辑器，略，都是没影的东西。】\n",190]