-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 144 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 144 KB
1
[{"title":"离线友好的表单","date":"2017-09-09T16:31:43.000Z","path":"2017/09/10/离线友好的表单/","text":"原文地址:Offline-Friendly Forms 原文作者:mxbck 译文出自:掘金翻译计划 本文永久链接:https://github.com/xitu/gold-miner/blob/master/TODO/offline-friendly-forms.md 译者:sunui 校对者:yanyixin、Tina92 网络不佳时网页表单的表现通常并不理想。如果你试图在离线状态下提交表单,那就很可能丢失刚刚填好的数据。下面就看看我们是如何修复这个问题的。 太长,勿点:这里是本文的 CodePen Demo。 随着 Service Workers 的推行,现在开发者们甚至可以实现离线版的网页了。静态资源的缓存相对容易,而像表单这样需要服务器交互的情况就很难优化了。即使这样,提供一些有用的离线回退方案还是有可能的。 首先,我们为离线友好的表单创建一个新的类。接着我们保存一些 <form> 元素的属性然后绑定一个触发 submit 事件的函数: 12345678910class OfflineForm { // 配置实例。 constructor(form) { this.id = form.id; this.action = form.action; this.data = {}; form.addEventListener('submit', e => this.handleSubmit(e)); }} 在 submit 处理函数中,我们使用 navigator.onLine 属性内置一个简单的网络检查器。浏览器对它的支持很好,而且实现它也不难。 ⚠️ 但它还是有一定误报的可能,因为这个属性只能检查客户端是否连接到网络,而不能检测实际的网络连通性。另一方面,一个 false 值意味着“离线”是相对确定的。因此,比起其他方式这个判断方法是最好的。 如果一个用户当前处于离线状态,我们就暂停表单的提交,把数据存储在本地。 12345678910111213handleSubmit(e) { e.preventDefault(); // 解析表单输入,存储到对象中 this.getFormData(); if (!navigator.onLine) { // 用户离线,在设备中存储数据 this.storeData(); } else { // 用户在线,通过 ajax 发送数据 this.sendData(); }} 存储表单数据存储数据到用户设备有几种不同的方式。根据数据的不同,如果你不希望本地副本持久存储在内存中,可以使用 sessionStorage。在我们的例子中,我们可以一起使用 localStorage。 我们可以给表单数据附上时间戳,把它赋值给一个新的对象,并且使用 localStorage.setItem 保存。这个方法接受两个参数:key(表单 id)和 value(数据的 JSON 串)。 12345678910111213storeData() { // 检测 localStorage 是否可用 if (typeof Storage !== 'undefined') { const entry = { time: new Date().getTime(), data: this.data, }; // 把数据存储为 JSON 串 localStorage.setItem(this.id, JSON.stringify(entry)); return true; } return false;} 提示:你可以在 Chrome 的开发者工具 “Application” 中查看存储数据。如果不出差错,你可以看到内容如下: 通知用户发生了什么也是个好主意,这样他们会知道他们的数据不会丢失。我们可以扩展 handleSubmit 函数来显示某些反馈信息。 多么周到的表单! 检查保存的数据一旦用户联网,我们想检查一下是否有被存储的提交。我们可以监听 online 事件来捕获网络链接的改变,还有页面刷新时的 load 事件: 12345constructor(form){ ... window.addEventListener('online', () => this.checkStorage()); window.addEventListener('load', () => this.checkStorage());} 123456789101112131415161718192021checkStorage() { if (typeof Storage !== 'undefined') { // 检测我们是否在 localStorage 之中存储了数据 const item = localStorage.getItem(this.id); const entry = item && JSON.parse(item); if (entry) { // 舍弃超过一天的提交。 (可选) const now = new Date().getTime(); const day = 24 * 60 * 60 * 1000; if (now - day > entry.time) { localStorage.removeItem(this.id); return; } // 我们已经验证了表单数据,尝试提交它 this.data = entry.data; this.sendData(); } }} 一旦我们成功提交了表单,那最后一步就是移除 localStorage 中的数据,来避免重复提交。假设是一个 ajax 表单,我们可以在服务器响应成功的回调里做这件事。很简单,这里我们可以使用 storage 对象的 removeItem() 方法。 12345678910111213sendData() { // 向服务器发送 ajax 请求 axios.post(this.action, this.data) .then((response) => { if (response.status === 200) { // 成功时移除存储的数据 localStorage.removeItem(this.id); } }) .catch((error) => { console.warn(error); });} 如果你不想使用 ajax 提交,另一个方案是将存储的数据回填到表单,然后调用 form.submit() 或让用户自己点击提交按钮。 ☝️ 注意:简单起见,我在这个案例中省略了一些其他部分,比如表单验证和安全 token 验证等,这些东西在真正的生产环境是必不可少的。这里的另一个问题是处理敏感数据,就是说你不能在本地存储一些密码或者信用卡数据等私密信息。 如果你感兴趣,请查阅 CodePen 上的全部示例。 掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。","tags":[{"name":"前端","slug":"前端","permalink":"http://suncafe.cc/tags/前端/"}]},{"title":"在HTTP/2的世界里管理CSS和JS","date":"2017-08-27T14:49:50.000Z","path":"2017/08/27/在HTTP2的世界里管理CSS和JS/","text":"原文地址:Managing CSS & JS in an HTTP/2 World 原文作者:Trevor Davis 译文出自:掘金翻译计划 本文永久链接:https://github.com/xitu/gold-miner/blob/master/TODO/managing-css-js-http-2.md 译者:sunui 校对者:Usey95、alfred-zhong 使用了 HTTP/2,在网站中传输 CSS 和 JS 将变得完全不同,本文是结合我实践的一份指南。 我们已经听说 HTTP/2 很多年了。我们甚至写了一些关于它的博客。但我们的真正实践并不多。一直到现在。在一些最近的项目中,我把使用 HTTP/2 作为一个目标,并弄清楚如何更好地应用多路复用。本文并不会主要去讲你为什么应该使用 HTTP/2,而是要讨论我是如何管理 CSS 和 JS 的从而解释这一范式转变。 拆分 CSS这是我们多年来作为最佳实践的反例。但为了汲取多路复用的好处,最好的方式还是把你的 CSS 拆分成更小的文件,这样在每一页只加载必要的CSS。应该像这个例子这样: 123456789101112131415161718192021222324<html><head> <!--每一页都是用的全局样式, header/footer/etc --> <link href=\"stylesheets/global/index.css\" rel=\"stylesheet\"></head><body> <link href=\"stylesheets/modules/text-block/index.css\" rel=\"stylesheet\"> <div class=\"text-block\"> ... </div> <link href=\"stylesheets/modules/two-column-block/index.css\" rel=\"stylesheet\"> <div class=\"two-column-block\"> ... </div> <link href=\"stylesheets/modules/image-promos-block/index.css\" rel=\"stylesheet\"> <div class=\"image-promos-block\"> ... </div></body></html> 没错,<link> 标签放在了 <body> 内部,但不必惊慌,这完全合规。因此对于每一个小的标签块,都可以拥有一个独立的只包含相应 CSS 的样式。假如你正在使用模块化风格构建你的页面,这很容易设置。 管理 SCSS 文件经过一些实践,这是我整理的 SCSS 文件结构: CONFIG 文件夹 我使用这个文件夹设置一堆变量: 这里的入口文件是 _index.scss,它引入了所有其他 SCSS 文件,所以我可以访问到一些变量和 mixins。它是这样的: 12@import \"variables\";@import \"../functions/*\"; FUNCTIONS 文件夹 顾名思义,它包含了一些常见的 mixins 和函数,每一个 mixin 或函数都对应一个文件。 GLOBAL 文件夹 这个文件夹包含我每一页都使用的 CSS。特别适合放一些类似网站的 header、footer、reset、字体和其他通用样式之类的东西。 index.scss 看起来是这样的: 12345678910@import \"../config/index\";@import \"_fonts.scss\";@import \"_reset.scss\";@import \"_base.scss\";@import \"_utility.scss\";@import \"_skip-link.scss\";@import \"_header.scss\";@import \"_content.scss\";@import \"_footer.scss\";@import \"components/*\"; 最后一行引入了所有 components 的子目录,这是将额外全局样式模块化的捷径。 MODULES 文件夹 这是我们 HTTP/2 体系中最重要的文件夹。当我拆分样式到对应的模块,这个文件夹会包含非常非常多的文件。所以我从拆分每一个模块到子目录开始: 每个模块中的 index.scss 是这样的: 12345// 导入所有的全局变量和 mixin@import "../../config/index";// 导入这个模块文件夹中的所有部分@import "_*.scss"; 这样我可以访问到变量和 mixin,然后我可以把模块的 CSS 拆分为许多部分,它们组合成一个单独的 CSS 模块文件。 PAGES 文件夹 实质上这个文件夹和 modules 文件夹一样,但我为了页面特定的内容使用它”。这种更模块化的方式在我们最近做的东西里绝对罕见,但是它很好地把页面的特殊样式拆分出来了。 适配 Blendid最近所有的项目我们都是用 Blendid 来构建的 。为了实现上文描述的 SCSS 配置,我需要添加 node-sass-glob-importer。一旦装好它,我只需把它添加到 Blendid 的 task-config.js 中。 12345678910var globImporter = require('node-sass-glob-importer');module.exports = { stylesheets: { ... sass: { importer: globImporter() }, ...} duang,这样就完成了管理 SCSS 的 HTTP/2 配置。 彩蛋:Craft 宏很长一段时间以来,我们在 Viget 都主张使用 Craft,我就写了一个宏来减少这种引入样式的方式: 123{%- macro css(stylesheet) -%} <link rel="stylesheet" href="/stylesheets{{ stylesheet }}/index.css" media="not print">{%- endmacro -%} 当我想要引入一个模块的 CSS 文件,我只需这样: 1{{ macros.css('/modules/image-block') }} 如果我需要在整个网站上放置样式表引用,这就更简单了。 管理 JS就像 CSS 一样,我想要把 JS 拆分为模块,这样每一页只加载必要的 JS。一样的,使用 Blendid 配置,为了一切正常运转我只需要做一点点微调。 我使用的是 import(),而非 Webpack 的require(),。因此现在的 modules/index.js 文件需要看起来是这样的: 12345678910const moduleElements = document.querySelectorAll('[data-module]');for (var i = 0; i < moduleElements.length; i++) { const el = moduleElements[i]; const name = el.getAttribute('data-module'); import(`./${name}`).then(Module => { new Module.default(el); });} 正如 Webpack 文档中所说:”这个特性内部依赖 Promise。如果你在旧版本浏览器使用 import(),记得使用一个 polyfill 来兼容 Promise,比如 es6-promise 或者 promise-polyfill“。 因此我把 es6-promise polyfill 加入到我的入口文件 app.js 中,使其自动兼容。 1require('es6-promise/auto'); 是的,然后你就可以在 Blendid 开箱即用的模式触发模块生成对应特定的 JS。 1<div data-module="carousel"> 这很完美吗?还不,但至少可以引领你开始以合理的方式管理 HTTP/2 资源。随着我们对如何拆分代码来更好地使用 HTTP/2 的思考,我真切地希望这个配置将会越来越完善。 掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。","tags":[{"name":"前端","slug":"前端","permalink":"http://suncafe.cc/tags/前端/"}]},{"title":"使用CSS栅格和Flexbox打造Trello布局","date":"2017-08-19T11:38:09.000Z","path":"2017/08/19/使用CSS栅格和Flexbox打造Trello布局/","text":"原文地址:Building a Trello Layout with CSS Grid and Flexbox 原文作者:Giulio Mainardi 译文出自:掘金翻译计划 本文永久链接:https://github.com/xitu/gold-miner/blob/master/TODO/building-trello-layout-css-grid-flexbox.md 译者:sunui 校对者:Aladdin-ADD、ahonn 通过本教程,我将带你完成 Trello 看板 (查看示例)的基本布局。这是一个响应式的、纯 CSS 的解决方案,并且我们将只开发布局的结构特性。 这是一个 CodePen demo,可预览一下最终结果。 除了栅格布局和 Flexbox,这个方案还采用了 calc 和视图单位。我们也将利用 Sass 变量,让代码更可读和高效。 不提供向下兼容,所以请确保在支持的浏览器上运行。一切就绪,就让我们开始一步一步开发看板组件吧。 屏幕布局一个 Trello 看板由一个 app 栏、一个 board 栏和一个包含卡片列表的部分组成。我使用以下标签骨架搭建出这一结构: 123456789101112131415<div class=\"ui\"> <nav class=\"navbar app\">...</nav> <nav class=\"navbar board\">...</nav> <div class=\"lists\"> <div class=\"list\"> <header>...</header> <ul> <li>...</li> ... <li>...</li> </ul> <footer>...</footer> </div> </div></div> 这个布局将通过 CSS 栅格实现。确切地说是 3×1 栅格(就是指一列三行)。第一行用于 app 栏,第二行用于 board 栏,第三行用于 .lists 元素。 前两行各自有一个固定的高度,而第三行将撑起可变窗口高度的其余部分: 12345.ui { height: 100vh; display: grid; grid-template-rows: $appbar-height $navbar-height 1fr;} 视图单位可以确保 .ui 容器总是和浏览器的窗口高度一致。 一个栅格化的上下文被分配给容器,并且指定了上文说的行和列。确切地说,是只指定了行,因为声明单独的列是没有必要的。一对 Sass 变量指定了两个栏目的高度,使用 fr 单位指定 .lists 元素高度使其撑起可变窗口高度的其余部分,这样每行的大小就设定完成了。 卡片列表部分如上所述,屏幕栅格的第三行托管着卡片列表的容器。这是标签的轮廓: 123456789<div class=\"lists\"> <div class=\"list\"> ... </div> ... <div class=\"list\"> ... </div></div> 我用一个满屏宽的 Flexbox 单行行容器来格式化列表: 123456789101112.lists { display: flex; overflow-x: auto; > * { flex: 0 0 auto; // 'rigid' lists margin-left: $gap; } &::after { content: ''; flex: 0 0 $gap; }} 给 overflow-x 指定 auto 值,当列表不适合视口提供的宽度时,浏览器会在屏幕底部显示一个水平滚动条。 flex 简写属性用于 flex item 使列表更严格。flex-basis (简写的方式使用)的 auto 值指示布局引擎从 .list 元素的宽度属性取值,flex-grow 和 flex-shrink 的 0 值可以防止宽度的改变。 接下来我将在列表之间添加一个水平分隔。如果给列表设置右间距,当水平溢出时看板上最后一个列表之后的间距不会被渲染。为了解决这个问题,列表被一个左间距分隔并且最后一个列表和窗口右边缘的间距通过给每个 .lists 元素添加一个伪元素 ::after 来实现。默认值 flex-shrink: 1 一定要被重写,否则这个伪元素会”吸收“所有的负空间,然后消失。 注意在 Firefox < 54 的版本上要给 .lists 指定 width: 100% 以确保正确的布局渲染。 卡片列表每个卡片列表由一个 header 栏、一个卡片序列和一个 footer 栏目组成。以下 HTML 代码段实现了这一结构: 123456789<div class=\"list\"> <header>List header</header> <ul> <li>...</li> ... <li>...</li> </ul> <footer>Add a card...</footer></div> 这里的关键任务是如何管理列表的高度。header 和 footer 有固定的高度(未必相等)。然后有一些不定数量的卡片,每个卡片都有不定量的内容。因此随着卡片的添加和移除,这个列表也会增大和缩小。 但是高度不能无限增大,它需要有一个取决于 .lists 元素高度的上限。一旦突破上线,我想有一个垂直滚动条出现来允许访问溢出列表的卡片。 这听起来是 max-height 和 overflow 属性能做的。但如果根容器 .list 提供了这些属性,一旦列表达到了它的最大高度,所有的 .list 元素包括 header 和 footer 在内都会出现滚动条。下图左右两边分别显示错误的和正确的侧边条: 因此,让我们把 max-height 约束给内部的 <ul>。应该提供什么值呢?header 和 footer 的高度必须从列表父容器(.lists)的高度之中扣除: 123ul { max-height: calc(100% - #{$list-header-height} - #{$list-footer-height});} 但还有一个问题。百分比数值并不参照 .lists 而是参照 <ul> 元素的父元素 .list,并且这个元素没有定义高度,因此这个百分比不能确定。这个问题可以通过设置 .list 和 .lists 同样高度来解决: 123.list { height: 100%;} 这样,既然 .list 和 .lists 总是一样高,它的 background-color 属性不能用于列表背景色,但可以使用它的子元素(header, footer 和卡片)来实现这一目的。 最后一个 list 高度的调整很有必要,可用来计算列表底部和窗口底部的一点空间($gap)。 123.list { height: calc(100% - #{$gap} - #{$scrollbar-thickness});} 还有一个 $scrollbar-thickness 需要被减去,防止列表触及 .list 元素的水平滚动条。 事实上这个滚动条”增长“在 .lists 盒子内部。也就是说,100% 这个值是指包括滚动条在内的 .lists 的高度。 而在火狐中,这个滚动条被”附加“给 .lists 高度的外部,就是说 .lists 高度的 100% 并不包含滚动条。所以这个减法就没什么必要了。结果是当滚动条可见时,在火狐中已经触及最大高度的底部边框和滚动条的顶部之间的可视空间会稍大一些。 这是这个组件相应的 CSS 规则: 1234567891011121314151617181920212223242526272829303132.list { width: $list-width; height: calc(100% - #{$gap} - #{$scrollbar-thickness}); > * { background-color: $list-bg-color; color: #333; padding: 0 $gap; } header { line-height: $list-header-height; font-size: 16px; font-weight: bold; border-top-left-radius: $list-border-radius; border-top-right-radius: $list-border-radius; } footer { line-height: $list-footer-height; border-bottom-left-radius: $list-border-radius; border-bottom-right-radius: $list-border-radius; color: #888; } ul { list-style: none; margin: 0; max-height: calc(100% - #{$list-header-height} - #{$list-footer-height}); overflow-y: auto; }} 如上所述,列表背景色通过给每一个 .list 元素的子元素的 background-color 属性指定 $list-bg-color 值而被渲染。overflow-y 使得卡片滚动条只有按需显示。最后,给 header 和 footer 添加一些简单的样式。 完成收尾单个卡片包含的一个列表元素 HTML: 1<li>Lorem ipsum dolor sit amet, consectetur adipiscing elit</li> 卡片也有可能包含一个封面图片: 1234<li> <img src=\"...\" alt=\"...\"> Lorem ipsum dolor sit amet</li> 这是相应的样式: 12345678910111213141516171819li { background-color: #fff; padding: $gap; &:not(:last-child) { margin-bottom: $gap; } border-radius: $card-border-radius; box-shadow: 0 1px 1px rgba(0,0,0, 0.1); img { display: block; width: calc(100% + 2 * #{$gap}); margin: -$gap 0 $gap (-$gap); border-top-left-radius: $card-border-radius; border-top-right-radius: $card-border-radius; }} 设置完一个背景、填充、和底部间距就差背景图片的布局了。这个图片宽度一定是跨越整个卡片的,从左填充的边缘到右填充的边缘: 1width: calc(100% + 2 * #{$gap}); 然后,指定负边距以使图片水平和垂直对齐: 1margin: -$gap 0 $gap (-$gap); 第三个正边距的值用于指定封面图片和文字之间的空间。 最后我给占据屏幕布局第一行的两条添加了一个 flex 格式化上下文,但它们只是草图。通过扩展 demo 自由构建你自己的实现吧。 总结这只是实现这种设计的一种可行方法,如果能看见其他方式那一定很有趣。此外,如果能完成整个布局那就更好了,比如完成最后的两个栏目。 另一个潜在的改进是能够为卡片列表实现自定义的滚动条。 所以,fork 这个 demo 尽情发挥吧,记得在下面的讨论区留下你的链接哦。 掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。","tags":[{"name":"前端","slug":"前端","permalink":"http://suncafe.cc/tags/前端/"}]},{"title":"高性能React:3个新工具加速你的应用","date":"2017-08-14T15:01:30.000Z","path":"2017/08/14/高性能React:3个新工具加速你的应用/","text":"原文地址:High Performance React: 3 New Tools to Speed Up Your Apps 原文作者:Ben Edelstein 译文出自:掘金翻译计划 本文永久链接:https://github.com/xitu/gold-miner/blob/master/TODO/make-react-fast-again-tools-and-techniques-for-speeding-up-your-react-app.md 译者:sunui 校对者:yzgyyang、reid3290 通常来说 React 是相当快的,但开发者也很容易犯一些错误导致出现性能问题。组件挂载过慢、组件树过深和一些非必要的渲染周期可以迅速地联手拉低你的应用速度。 幸运的是有大量的工具,甚至有些是 React 内置的,可以帮助我们检测性能问题。本文将着重介绍一些加快 React 应用的工具和技术。每一部分都配有一个可交互而且(希望是)有趣的 demo! 工具 #1: 性能时间轴React 15.4.0 引入了一个新的性能时间轴特性,可以精确展示组件何时挂载、更新和卸载。也可以让你可视化地观察组件生命周期相互之间的关系。 注意: 目前,这一特性仅支持 Chrome、Edge 和 IE,因为它调用的 User Timing API 还没有在所有浏览器中实现。 如何使用 打开你的应用并追加一个参数:react_perf。例如, http://localhost:3000?react_perf 打开 Chrome 开发者工具 Performance 栏并点击 Record。 执行你想要分析的操作。 停止记录。 观察 User Timing 选项下的可视化视图。 理解输出结果每一个色条显示的是一个组件做“处理”的时间。由于 JavaScript 是单线程的,每当一个组件正在挂载或渲染,它都会霸占主线程,并阻塞其他代码运行。 像 [update] 这样中括号内的文字描述的是生命周期的哪一个阶段正在发生。把时间轴按照步骤分解,你可以看到依据方法的细粒度的计时,比如 [componentDidMount] [componentWillReceiveProps] [ctor] (constructor) 和 [render]。 堆叠的色条代表组件树,虽然在 React 拥有过深的组件树也比较典型,但如果你想优化一个频繁挂载的组件,减少嵌套组件的数量也是有帮助的,因为每一层都会增加少量的性能和内存消耗。 这里需要注意的是时间轴中的计时时长是针对 React 的开发环境构建的,会比生产环境慢很多。实际上性能时间轴本身也会拖慢你的应用。虽然这些时长不能代表真正的性能指标,但不同组件间的相对时间是精确的。而且一个组件是否完全被更新不取决于是否是生产环境的构建。 Demo #1出于乐趣,我故意写了一个具有严重性能问题的 TodoMVC 应用。你可以在这里尝试。 打开 Chrome 开发者工具,切换到 “Performance” 栏,点击 Record 开始记录时间轴。然后在应用中添加一些 TODO,停止记录,检查时间轴。看看你能不能找出造成性能问题的组件 :) Tool #2: why-did-you-update在 React 中最影响性能的问题之一就是非必要的渲染周期。默认情况下,一旦父组件渲染,React 组件就会跟着重新渲染,即使它们的 props 没有变化也是如此。 举个例子,如果我有一个简单的组件长这样: class DumbComponent extends Component { render() { return <div> {this.props.value} </div>; } } 它的父组件是这样: class Parent extends Component { render() { return <div> <DumbComponent value={3} /> </div>; } } 每当父组件渲染,DumbComponent 就会重新渲染,尽管它的 props 没有改变。 一般来讲,如果 render 运行,并且虚拟 DOM 没有改变,而且既然 render 应该是个纯净的没有任何副作用的方法,那么这就是一个不必要的渲染周期。在一个大型应用中检测这种事情是非常困难的,但幸运的是有一个工具可以帮得上忙。 使用 why-did-you-update why-did-you-update 是一个 React 钩子工具,用来检测潜在的非必要组件渲染。它会检测到被调用但 props 没有改变的组件 render。 安装 使用 npm 安装: npm i --save-dev why-did-you-update 在你应用中的任何地方添加下面这个片段: import React from ‘react’ if (process.env.NODE_ENV !== ‘production’) { const {whyDidYouUpdate} = require(‘why-did-you-update’) whyDidYouUpdate(React) } 注意: 这个工具在本地开发环境使用起来非常棒,但是要确保生产环境要禁用掉,因为它会拖慢你的应用。 理解输出结果why-did-you-update 在运行时监听你的应用,并用日志输出可能存在非必要更新的组件。它让你看到一个渲染周期前后的 props 对比,来决定是否可能存在非必要的更新。 Demo #2为了演示 why-did-you-update,我在 TodoMVC 中安装了这个库并放在 Code Sandbox 网站上,这是一个在线的 React 练习场。 打开浏览器控制台,并添加一些 TODO 来查看输出。 这里查看 demo。 注意这个应用中很少的组件存在非必要渲染。尝试执行上述的技术来避免非必要渲染,如果操作正确,why-did-you-update 不会在控制台输出任何内容。 Tool #3: React Developer Tools React Developer Tools 这个 Chrome 扩展有一个内置特性用来可视化组件更新。这有助于防止非必要的渲染周期。使用它,首先要确保在这里安装了这个扩展。 然后点击 Chrome 开发者工具中的 “React” 选项卡打开扩展并勾选“Highlight Updates”。 然后简单操作你的应用。和不同的组件交互并观察 DevTools 施展它的魔法。 理解输出结果React Developer Tools 在给定的时间点高亮正在重新渲染的组件。根据更新的频率,使用不同的颜色。蓝色显示罕见更新,经过绿色、黄色的过渡,一直到红色用来显示更新频繁的组件。 看到黄色或红色并不必要觉得一定是坏事。它可能发生在调整一个滑块或频繁触发更新的其他 UI 元素,这属于意料之中。但如果当你点击一个简单的按钮并且看到了红色这可能就意味着事情不对了。这个工具的目的就是识破正在发生非必要更新的组件。作为应用的开发者,你应该对给定时间内哪个组件应该被更新有一个大体的概念。 Demo #3为了演示高亮,我故意让 TodoMVC 应用更新一些非必要的组件。 这里查看 demo。 打开上面的链接,然后打开 React Developer Tools 并启用更新高亮。当你在上面的文字输入框中输入内容时,你将看到所有的 TODO 非必要地高亮。你输入得越快,你会看到颜色变化指示更新越来越频繁。 修复非必要渲染一旦你已经确定应用中非必要重新渲染的组件,有几种简单的方法来修复。 使用 PureComponent在上面的例子中,DumbComponent 是只接收属性的纯函数。这样,组件就只有当它的 props 变化的时候才重新渲染。React 有一个特殊的内置组件类型叫做 PureComponent,就是适用这种情况的用例。 与继承自 React.Component 相反,像这样使用 React.PureComponent: class DumbComponent extends PureComponent { render() { return <div> {this.props.value} </div>; } } 那么只有当这个组件的 props 实际发生变化时它才会被重新渲染了。就是这样! 注意 PureComponent 对 props 做了一个浅对比,因此如果你使用复杂的数据结构,它可能会错失一些属性变化而不会更新你的组件。 调用 shouldComponentUpdateshouldComponentUpdate 是一个在 render 之前 props 或 state 发生改变时被调用的组件方法。如果 shouldComponentUpdate 返回 true,render 将会被调用,如果返回 false 什么也不会发生。 通过执行这个方法,你可以命令 React 在 props 没有发生改变的时候避免给定组件的重新渲染。 例如,我们可以在上文中的 DumbComponent 中这样调用 shouldComponentUpdate。 class DumbComponent extends Component { shouldComponentUpdate(nextProps) { if (this.props.value !== nextProps.value) { return true; } else { return false; } } render() { return <div>foo</div>; } } 在生产环境中调试性能问题React Developer Tools 只能在你自己的机器上运行的应用中使用。如果您有兴趣了解用户在生产中看到的性能问题,试试 LogRocket。 LogRocket 就像是 web 应用的 DVR,会记录发生在你的站点上的所有的一切。你可以重现带有 bug 或性能问题的会话来快速了解问题的根源,而不用猜测问题发生的原因。 LogRocket 工具为你的应用记录性能数据、Redux actions/state、日志、带有请求头和请求体的网络请求和响应以及浏览器的元数据。它也能记录页面上的 HTML 和 CSS,甚至可以为最复杂的单页面应用重新创建完美像素的视频。 LogRocket | 为 JavaScript 应用而生的日志记录和会话回放工具LogRocket 帮助你了解用影响你用户的问题,这样你就可以回过头来构建伟大的软件了。logrocket.com 感谢阅读,希望这些工具和技术能在你的下一个 React 项目中帮到你! 掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。","tags":[{"name":"前端","slug":"前端","permalink":"http://suncafe.cc/tags/前端/"}]},{"title":"即将到来的正则表达式新特性","date":"2017-07-29T13:21:40.000Z","path":"2017/07/29/即将到来的正则表达式新特性/","text":"原文地址:Upcoming Regular Expression Features 原文作者:Jakob Gruber、Yang Guo 译文出自:掘金翻译计划 本文永久链接:https://github.com/xitu/gold-miner/blob/master/TODO/upcoming-regexp-features.md 译者:sunui 校对者:atuooo、Tina92 ES2015 给 JavaScript 语言引入了许多新特性,其中包括正则表达式语法的一些重大改进,新增了 Unicode 编码 (/u) 和粘滞位 (/y)两个修饰符。而在那之后,发展也并未停止。经过与 TC39(ECMAScript 标准委员会)的其他成员的紧密合作,V8 团队提议并共同设计了让正则表达式更强大的几个新特性。 这些新特性目前已经计划包含在 JavaScript 标准中。虽然提案还没有完全通过,但是它们已经进入 TC39 流程的候选阶段了。我们已经以试验功能(见下文)在浏览器实现了这些特性,以便在最终定稿之前提供及时的设计和实现反馈给各自的提案作者。 本文给您预览一下这个令人兴奋的未来。如果您愿意跟着体验这些即将到来的示例,可以在 chrome://flags/#enable-javascript-harmony 页面中开启实验性 JavaScript 功能。 命名捕获正则表达式可以包含所谓的捕获(或捕获组),它可以捕获一部分匹配的文本。到目前为止,开发者只能通过索引来引用这些捕获,这取决于其在正则匹配中的位置。 const pattern =/(\\d{4})-(\\d{2})-(\\d{2})/u; const result = pattern.exec('2017-07-10'); // result[0] === '2017-07-10' // result[1] === '2017' // result[2] === '07' // result[3] === '10' 但正则表达式已经因难于读、写和维护而臭名昭著,并且数字引用会使事情进一步复杂化。例如,在一个更长的表达式中判断一个独特捕获的索引是很困难的事: /(?:(.)(.(?<=[^(])(.)))/ // 最后一个捕获组的索引是? 更糟糕的是,更改一个表达式可能会潜在地转变所有已存在的捕获的索引: /(a)(b)(c)\\3\\2\\1/ // 一些简单的有序的反向引用。 /(.)(a)(b)(c)\\4\\3\\2/ // 所有都需要更新。 命名捕获是一个即将到来的特性,它允许开发者给捕获组分配名称来帮助尽可能地解决这些问题。语法类似于 Perl、Java、.Net 和 Ruby: const pattern =/(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})/u; const result = pattern.exec('2017-07-10'); // result.groups.year === '2017' // result.groups.month === '07' // result.groups.day === '10' 命名捕获组也可以被命名的反向引用来引用,并传入 String.prototype.replace: // 命名反向引用。 /(?<LowerCaseX>x)y\\k<LowerCaseX>/.test('xyx'); //true // 字符串替换。 const pattern =/(?<fst>a)(?<snd>b)/; 'ab'.replace(pattern,'$<snd>$<fst>'); // 'ba' 'ab'.replace(pattern,(m, p1, p2, o, s,{fst, snd})=> fst + snd); // 'ba' 关于这个新特性的全部详情可以在规范提案中查看。 dotAll 修饰符默认情况下,元字符 . 在正则表达式中匹配除了换行符以外的任何字符: /foo.bar/u.test('foo\\nbar'); // false 一个提案引入了 dotAll 模式,通过 /s 修饰符来开启。在 dotAll 模式中,. 也可以匹配换行符。 /foo.bar/su.test('foo\\nbar'); // true 关于这个新特性的全部详情可以在规范提案中查看。 Unicode 属性逃逸(Unicode Property Escapes)正则表达式语法已经包含了特定字符类的简写。\\d 代表数字并且只能是 [0-9];\\w 是单词字符的简写,或者写成 [A-Za-z0-9_]。 自从 ES2015 引入了 Unicode,突然间大量的字符可以被认为是数字,例如圈一:①;或者被认为是字符的,例如中文字符:雪。 它们都不会被 \\d 或 \\w 匹配。而改变这些简写的含义将会破坏已经存在的正则表达式模式。 于是,新的字串类被引入。注意它们只在使用 /u 修饰符的 Unicode-aware 正则表达式中可用。 /\\p{Number}/u.test('①'); // true /\\p{Alphabetic}/u.test('雪'); // true 排除型字符可以使用 \\P 匹配。 /\\P{Number}/u.test('①'); // false /\\P{Alphabetic}/u.test('雪'); // false 统一码联盟还定义了许多方式来分类码位,例如数学符号和日语平假名字符: /^\\p{Math}+$/u.test('∛∞∉'); // true /^\\p{Script_Extensions=Hiragana}+$/u.test('ひらがな'); // true 全部受支持的 Unicode 属性类列表可以在目前的规范提案中找到。更多示例请查看这篇内容丰富的文章。 后行断言先行断言从一开始就已经是 JavaScript 正则表达式语法的一部分。与之相对的后行断言也终于将被引入。你们中的一些人可能记得,这成为 V8 的一部分已经有一段时间了。我们甚至在底层已经用后行断言实现了 ES2015 规定的 Unicode 修饰符。 “后行断言”这个名字已经很好地描述了它的涵义。它提供一个方式来限制一个正则,只有后行组匹配通过之后才继续匹配。它提供匹配和非匹配两种选择: /(?<=\\$)\\d+/.exec('$1 is worth about ¥123'); // ['1'] /(?<!\\$)\\d+/.exec('$1 is worth about ¥123'); //['123'] 更多详细信息,查看我们之前的一篇博文,专门介绍了后行断言。相关示例可以查看V8 测试用例。 致谢本文的完成有幸得到了很多相关人士的帮助,他们的辛勤工作造就了这一切:特别是语言之王Mathias Bynens、Dan Ehrenberg、Claude Pache、Brian Terlson、Thomas Wood、Gorkem Yakin、和正则大师 Erik Corry;还有为语言规范作出努力的每一个人以及 V8 团队对这些特性的实施。 希望您能像我们一样为这些新的正则表达式特性而感到兴奋! 掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。","tags":[{"name":"前端","slug":"前端","permalink":"http://suncafe.cc/tags/前端/"}]},{"title":"JavaScript 的函数式编程是一种反模式","date":"2017-07-06T15:32:49.000Z","path":"2017/07/06/JavaScript的函数式编程是一种反模式/","text":"原文地址:Functional programming in JavaScript is an antipattern 原文作者:Alex Dixon 译文出自:掘金翻译计划 本文永久链接:https://github.com/xitu/gold-miner/blob/master/TODO/functional-programming-in-javascript-is-an-antipattern.md 译者:sunui 校对者:LeviDing、xekri 其实 Clojure 更简单些写了几个月 Clojure 之后我再次开始写 JavaScript。就在我试着写一些很普通的东西的时候,我总会想下面这些问题: “这是 ImmutableJS 变量还是 JavaScript 变量?” “我如何 map 一个对象并且返回一个对象?” “如果它是不可变的,要么使用 <这种语法> 的 <这个函数>,否则使用 <不同的语法和完全不同行为> 的 <同一个函数的另一个版本>” “一个 React 组件的 state 可以是一个不可变的 Map 吗?” “引入 lodash 了吗?” “fromJS 然后 <写代码> 然后 .toJS()?” 这些问题似乎没什么必要。但我猜想我已经思考这些问题上百万次了只是没有注意到,因为这些都是我知道的。 当使用 React、Redux、ImmutableJS、lodash、和像 lodash/fp、ramda 这样的函数式编程库的任意组合写 JavaScript 的时候,我觉得没什么方法能避免这种思考。 我需要一直把下面这些事记在脑海里: lodash 的 API、Immutable 的 API、lodash/fp 的 API、ramda 的 API、还有原生 JS 的 API 或一些组合的 API 处理 JavaScript 数据结构的可变编程技术 处理 Immutable 数据结构的不可变编程技术 使用 Redux 或 React 时,可变的 JavaScript 数据结构的不可变编程 就算我能够记住这些东西,我依然会遇到上面那一堆问题。不可变数据、可变数据和某些情况下不能改变的可变数据。一些常用函数的签名和返回值也是这样,几乎每一行代码都有不同的情况要考虑。我觉得在 JavaScript 中使用函数式编程技术很棘手。 按照惯例像 Redux 和 React 这种库需要不可变性。所以即使我不使用 ImmutableJS,我也得记得“这个地方不能改变”。在 JavaScript 中不可变的转换比它本身的使用更难。我感觉这门语言给我前进的道路下了一路坑。此外,JavaScript 没有像 Object.map 这样的基本函数。所以像上个月 4300 多万人一样,我使用 lodash,它提供大量 JavaScript 自身没有的函数。不过它的 API 也不是友好支持不可变的。一些函数返回新的数值,而另一些会更改已经存在的数据。再次强调,花时间来区分它们是很不划算的。事实大概如此,想要处理 JavaScript,我需要了解 lodash、它的函数名称、它的签名、它的返回值。更糟糕的是,它的“collection 在先, arguments 在后”的方式对函数式编程来说也并不理想。 如果我使用 ramda 或者 lodash/fp 会好一些,可以很容易地组合函数并且写出清晰整洁的代码。但是它不能和 Immutable 数据结构一起使用。我可能还是要写一些参数集合在后而其他时候在前的代码。我必须知道更多的函数名、签名、返回值,并引入更多的基本函数。 当我单独使用 ImmutableJS,一些事变得容易些了。Map.set 返回全新的值。一切都返回全新的值!这就是我想要的。不幸的是,ImmutableJS 也有一些纠结的事情。我不可避免地要处理两套不同的数据结构。所以我不得不清楚 x 是 Immutable 的还是 JavaScript 的。通过学习其 API 和整体思维方式,我可以使用 Immutable 在 2 秒内知道如何解决问题。当我使用原生 JS 时,我必须跳过该解决方案,用另一种方式来解决问题。就像 ramda 和 lodash 一样,有大量的函数需要我了解 —— 它们返回什么、它们的签名、它们的名称。我也需要把我所知的所有函数分成两类:一类用于 Immutable 的,另一类用于其它。这往往也会影响我解决问题的方式。我有时会不自主地想到柯里化和组合函数的解决方案。但不能和 ImmutableJS 一起使用。所以我跳过这个解决方案,想想其他的。 当我全部想清楚以后,我才能尝试写一些代码。然后我转移到另一个文件,做一遍同样的事情。 JavaScript 中的函数式编程。 反模式的可视化。 我已孤立无援,并且把 JavaScript 的函数式编程称为一种反模式。这是一条迷人之路却将我引入迷宫。它似乎解决了一些问题,最终却创造了更多的问题。重点是这些问题似乎没有更高层次的解决方案能避免我一次有又一次地处理问题。 这件事的长期成本是什么?我没有确切的数字,但我敢说如果不必去想“在这里我可以用什么函数?”和“我可否改变这个变量”这样的问题,我可以更高效地开发。这些问题对我想要解决的问题或者我想要增加的功能没有任何意义。它们是语言本身造成的。我能想到避免这个问题的唯一办法就是在路的起点就不要走下去 —— 不要使用 ImmutableJS 、ImmutableJS 数据结构、Redux/React 概念中的不可变数据,以及 ramda 表达式和 lodash。总之就是写 JavaScript 不要使用函数式编程技术,它看似不是什么好的解决方案。 如果你确定并同意我所说的(如果不同意,也很好),那么我认为值得花 5 分钟或一天甚至一周时间来考虑:保持在 JavaScript 路子上相比用一个不同的东西取代,耗费的长期成本是什么? 这个所谓不同的东西对于我来说就是 Clojurescript。它是一门像 ES6 一样的 “compile-to-JS” 语言。大体上说,它是一种使用不同语法的 JavaScript。它的底层是被设计成用于函数式编程的语言,操作不可变的数据结构。对我来说,它比 JavaScript 更容易,更有前途。 Clojure/Clojurescript 是什么?Clojurescript 类似 Clojure,除了它的宿主语言是 JavaScript 而不是 Java。它们的语法完全相同:如果你学 Clojurescript,其实你就在学 Clojure。这意味着如果你了解了 Clojurescript,你就可以写 JavaScript 和 Java。“30 亿的设备上运行着 Java”;我非常确定其他设备上运行着 JavaScript。 和 JavaScript 一样,Clojure 和 Clojurescript 也是动态类型的。你可以 100% 地使用 Clojurescript 语言用 Node 写服务端的全栈应用。与单独编译成 JavaScript 的语言不同,你也可以选择写一个基于 Java 的 servrer 来支持多线程。 作为一个普通的 JavaScript/Node 开发者,学习这门语言及其生态系统对我来说并不困难。 是什么使得 Clojurescript 更简单? 在编辑器中执行任意你想要执行的代码。 你可以在编辑器中一键执行任何代码。 的确如此,你可以在编辑器中输入任何你想写的代码,选中它(或者把光标放在上面)然后运行并查看结果。你可以定义函数,然后用你想用的参数调用它。你可以在应用运行的时候做这些事。所以,如果你不知道一些东西如何运作,你可以在你的编辑器的 REPL 里求值,看看会发生什么。 函数可以作用于数组和对象。 Map、reduce、filter 等对数组和对象的作用都相同。设计就是如此。我们毋须再纠结于 map 对数组和对象作用的不同之处。 不可变的数据结构。 所有 Clojurescript 数据结构都是不可变的。因此你再也不必纠结一些东西是否可变了。你也不需要切换编程范式,从可变到不可变。你完全在不可变数据结构的领地上。 一些基本函数是语言本身包含的。 像 map、filter、reduce、compose 和很多其他函数都是核心语言的一部分,不需要外界引入。因此你的脑子里不必记着 4 种不同版本的“map”了(Array.map、lodash.map、ramda.map、Immutable.map)。你只需要知道一个。 它很简洁。 相对于其他任何编程语言,它只需要短短几行的代码就能表达你的想法。(通常少得多) 函数式编程。 Clojurescript 是一门彻底的函数式编程语言 —— 支持隐式返回声明、函数是一等公民、lambda 表达式等等。 使用 JavaScript 中所需的任何内容。 你可以使用 JavaScript 的一切以及它的生态系统,从 console.log 到 npm 库都可以。 性能。 Clojurescript 使用 Google Closure 编译器来优化输出的 JavaScript。Bundle 体积小到极致。用于生产的打包过程不需要从设置优化到 :advanced 的复杂配置。 可读的库代码。 有时候了解“这个库的功能是干嘛的?”很有用。当我使用 JavaScript 中的“跳转到定义处”,我通常都会看到被压缩或错位的源代码。Clojure 和 Clojurescript 的库都直接被显示成写出来的样子,因此不需离开你的编辑器去看一些东西如何工作就很简单,因为你可以直接阅读源码。 是一种 LISP 方言。 很难列举出这方面的好处,因为太多了。我喜欢的一点是它的公式化,(有这么一种模式可以依靠)代码是用语言的数据结构来表达的。(这使得元编程很容易)。Clojure 不同于 LISP 因为它并不是 100% 的 ()。它的代码和数据结构中可以使用 [] 和 {},就像大多数编程语言那样。 元编程。 Clojurescript 允许你编写生成代码的代码。这一点有我不想掩盖的巨大内涵。其中之一是你可以高效地扩展语言本身。这是一个出自 Clojure for the Brave and True 的例子: 12345678(defmacro infix [infixed] (list (second infixed) (first infixed) (last infixed)))(infix (1 + 1))=> 2(macroexpand '(infix (1 + 1)))=> (+ 1 1); 这个宏把它传入 Clojure,Clojure 可以正确执行,因为是 Clojure 的原生语法。 为什么它并不流行?既然说它这么棒,可它怎么不上天呢?有人指出它已经很流行了,它只是不如 lodash、React、Redux 等等那么流行而已。但既然它更好,不应该和它们一样流行吗?为什么偏爱函数式编程、不可变性和 React 的 JS 开发者还没有迁移到 Clojurescript? 因为缺少工作机会吗? Clojure 可以编译成 JavaScript 和 Java。它实际上也可以编译成 C#。因此大量的 JavaScript 工作都可以当作 Clojurescript 工作。它是一种函数式语言,用于为所有编译目标完成所有的任务。先不论它的价值如何体现,2017 StackOverflow 的调查表明 Clojure 开发者的薪资水平是所有语言中全球平均最高的。 因为 JS 开发者很懒吗? 并不是。正如我在上面所展示的,我们做了大量的工作。有个词叫 JavaScript 疲劳,你可能已经听说过了。 我们很抗拒,不想学点新东西吗? 并不是。 我们已经因采用新技术而臭名昭著。 因为缺乏熟悉的框架和工具吗? 这感觉上可能是个原因,但 Javascript 中有的东西, Clojurescript 都有与之对应的: re-frame 对应 Redux、reagent 对应 React、figwheel 对应 Webpack/热加载、leiningen 对应 yarn/npm、Clojurescript 对应 Underscore/Lodash。 是因为括号的问题使得这门语言太难写了吗? 这方面也许谈的还不够多,但我们不必自己来区分圆括号方括号 。基本上,Parinfer 使得 Clojure 成为了空格语言。 因为在工作中很难使用? 可能是吧。它是一种新技术,就像 React 和 Redux 曾经那样,在某些时候也是很难推广的。即使也没什么技术限制 —— Clojurescript 集成到现有代码库和集成 React 的方式是类似的。你可以把 Clojurescript 加入到已经存在的代码库中,每次重写一个文件的旧代码,新代码依然可以和未更改的旧代码交互。 没有足够受欢迎? 很不幸,我想这就是它的原因。我使用 JavaScript 一部分原因就是它拥有庞大的社区。Clojurescript 太小众了。我使用 React 的部分原因是它是由 Facebook 维护的。而 Clojure 的维护者是花大量时间思考的留着长发的家伙。 有数量上的劣势,我认了。但“人多势众”否决了所有其他可能的因素。 假设有一条路通向 100 美元,它很不受欢迎,而另一条路通向 10 美元,它极其受欢迎,我会选择受欢迎的那条路吗? 恩,也许会的吧!那里有成功的先例。它一定比另一条路安全,因为更多的人选择了它。他们一定不会遇到什么可怕的事。而另一条路听起来美好,但我确定那一定是个陷阱。如果它像看起来那么美好,那么它就是最受欢迎的那条路了。 掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。","tags":[{"name":"前端","slug":"前端","permalink":"http://suncafe.cc/tags/前端/"}]},{"title":"如何充分利用JavaScript控制台","date":"2017-06-27T16:09:19.000Z","path":"2017/06/28/[译]如何充分利用JavaScript控制台/","text":"原文地址:How to get the most out of the JavaScript console 原文作者:Darryl Pargeter 译文出自:掘金翻译计划 译者:sunui 校对者:reid3290、Aladdin-ADD JavaScript 中最基本的调试工具之一就是 console.log()。console 还附带了一些其他好用的方法,可以添加到开发人员的调试工具包中。 你可以使用 console 执行以下任务: 输出一个计时器来协助进行简单的基准测试 输出一个表格来以易读的格式显示一个数组或对象 使用 CSS 将颜色和其他样式选项应用于输出 Console 对象console 对象允许您访问浏览器的控制台。它允许你输出有助于调试代码的字符串、数组和对象。console 是 window 对象的属性,由浏览器对象模型(BOM)提供。 我们可以通过这两种方法之一访问 console: window.console.log('This works') console.log('So does this') 第二个选项本质上是对前者的引用,所以我们使用后者以精简代码。 关于 BOM 的快速提示:它没有设定标准,所以每家浏览器都以稍微不同的方式实现。我在 Chrome 和 Firefox 测试了所有示例,但你的输出可能有所不同,这取决于你使用的浏览器。 输出文本 将文本记录到控制台console 对象最常见的元素是 console.log,对于大多数情况,使用它就可以完成任务。 输出信息到控制台的四种方式: log info warn error 他们四个工作方式相同。你唯一要做的是给选择的方法传递一个或更多的参数。控制台会显示不同的图标来指示其记录级别。下面的例子中你可以看到 info 级别的记录和 warning/error 级别的不同之处。 简单易读的输出 输出东西太多将变得难以阅读 你可能注意到了 error 日志消息 —— 它比其他消息更显眼。它显示着红色的背景和堆栈跟踪,而 info 和 warn 就不会。但是在 Chrome 中 warn 确实有一个黄色的背景。 视觉上的区分有助于你在控制台快速浏览辨别出错误或警告信息。你应该确保在准备生产的应用中移除它们,除非你打算让它们来警示其他操作你的代码的开发者。 字符串替换这个技术可以使用字符串中的占位符来替换你向方法中传入的其他参数。 输入: console.log('string %s', 'substitutions') 输出: string substitutions %s 是逗号后面第二个参数 'substitutions' 的占位符。任何的字符串、整数或数组都将被转换成字符串并替换 %s。如果你传入一个对象,它将显示为 [object Object]。 如果你想传入对象,你需要使用 %o 或者 %O,而不是 %s。 console.log('this is an object %o', { obj: { obj2: 'hello' }}) 数字字符串替换可以与整数和浮点数一起使用: 整数使用 %i 或 %d, 浮点数使用 %f。 输入: console.log('int: %d, floating-point: %f', 1, 1.5) 输出:int: 1, floating-point: 1.500000 可以使用 %.1f 来格式化浮点数,使小数点后仅显示一位小数。你可以用 %.nf 来显示小数点后 n 位小数。 如果我们使用上述例子显示小数点后一位小数来格式化浮点数值,它看起来这样: 输入: console.log('int: %d, floating-point: %.1f', 1, 1.5) 输出: int: 1, floating-point: 1.5 格式化说明符 %s | 使用字符串替换元素 %(d|i)| 使用整数替换元素 %f| 使用浮点数替换元素 %(o|O) | 元素显示为一个对象 %c | 应用提供的 CSS 字符串模板随着 ES6 的出现,模板字符串是替换或连接的替代品。他们使用反引号(``)来代替引号,变量包裹在 ${} 中: const a = 'substitutions'; console.log(`bear: ${a}`); // bear: substitutions 对象在模板字符串中显示为 [object Object],所以你将需要使用 %o 或 %O 替换以看到详情,或单独记录。 比起使用字符串连接:console.log('hello' + str + '!');,使用替换或模板可以创建更易读的代码。 美妙的彩色插曲!现在,是时候来点更有趣而多彩的东西了! 是时候用字符串替换让我们的 console 弹出丰富多彩的颜色了。 我将使用一个模仿 Ajax 的例子,给我们显示一个请求成功(用绿色)和失败(用红色)。这是输出和代码: 成功的小熊和失败的蝙蝠 const success = [ 'background: green', 'color: white', 'display: block', 'text-align: center' ].join(';'); const failure = [ 'background: red', 'color: white', 'display: block', 'text-align: center' ].join(';'); console.info('%c /dancing/bears was Successful!', success); console.log({data: { name: 'Bob', age: 'unknown' }}); // "mocked" data response console.error('%c /dancing/bats failed!', failure); console.log('/dancing/bats Does not exist'); 在字符串替换中使用 %c 占位符来应用你的样式规则。 console.error('%c /dancing/bats failed!', failure); 然后把你的 CSS 元素作为参数,你就能看到应用 CSS 的日志了。 你也可以给你的字符串添加多个 %c。 console.log('%cred %cblue %cwhite','color:red;','color:blue;', 'color: white;') 这将按照他们的代表的颜色输出字符 “red”、“blue” 和 “white”。 控制台仅仅支持少数 CSS 属性,建议你试验一下哪些支持哪些不支持。重申一下,你的输出结果可能因你的浏览器而异。 其他可用的方法还有几个其他可用的 console 方法。注意下面有几项还不是 API 标准,所以可能浏览器间互不兼容。这个例子使用的是 Firefox 51.0.1。 Assert()Assert 携带两个参数 —— 如果第一个参数计算为 false,那么它将显示第二个参数。 let isTrue = false; console.assert(isTrue, 'This will display'); isTrue = true; console.assert(isTrue, 'This will not'); 如果断言为 false,控制台将输出内容。它显示为一个上文提到的 error 级别的日志,给你显示一个红色的错误消息和堆栈跟踪。 Dir()dir 方法显示一个传入对象的可交互属性列表。 console.dir(document.body); Chrome 会显示不同的层级最终,dir 仅仅能节省一两次点击,如果你需要检查一个 API 响应返回的对象,你可以用它结构化地显示出来以节约一些时间。 Table()table 方法用一个表格显示数组或对象 console.table(['Javascript', 'PHP', 'Perl', 'C++']); 输出数组 数组的索引或对象的属性名位于左侧的索引栏,值显示在右侧列栏。 const superhero = { firstname: 'Peter', lastname: 'Parker', } console.table(superhero); 输出对象 Chrome 用户需要注意: 这是我的同事提醒我的,上述 table 方法的例子在 Chrome 中貌似不能工作。你可以通过将项目放入数组或对象数组中来解决此问题。 console.table([['Javascript', 'PHP', 'Perl', 'C++']]); const superhero = { firstname: 'Peter', lastname: 'Parker', } console.table([superhero]); Group()console.group() 由至少三个 console 调用组成,它可能是使用时需要打最多字的方法。但它也是最有用的方法之一(特别对使用 Redux Logger 的开发者)。 稍基础的调用看起来是这样的: console.group(); console.log('I will output'); console.group(); console.log('more indents') console.groupEnd(); console.log('ohh look a bear'); console.groupEnd(); 这将输出多个层级,显示效果因你的显示器而异。 Firefox 显示成缩进列表: Chrome 显示成对象的风格: 每次调用 console.group() 都将开启一个新的组,如果在一个组内会创建一个新的层级。每次调用 console.groupEnd() 都会结束当前组或层级并向上移动一个层级。 我发现 Chrome 的输出样式更易读,因为它看起来像一个可折叠的对象。 你可以给 group 传入一个 header 参数,它将被显示并替代 console.group: console.group('Header'); 如果你调用 console.groupCollapsed(),你可以从一开始就将这个组显示为折叠。据我所知,这个方法可能只有 Chrome 支持。 Time()time 方法和上文的 group 方法类似,由两部分组成。 一个用于启动计时器的方法和一个停止它的方法。 一旦计时器完成,它将以毫秒为单位输出总运行时间。 启动计时器使用 console.time('id for timer'),结束计时器使用 console.timeEnd('id for timer')。您可以同时运行多达 10,000 个定时器。 输出结果可能有点像这样: timer: 0.57ms。 当你需要做一个快速的基准测试时,它非常有用。 结论我们已经更深入地了解了 console 对象以及其中附带的其他一些方法。当我们需要调试代码时,这些方法是可用的好工具。 仍然有几种方法我没有谈论,因为他们的 API 依然在变动。具体可以阅读 MDN Web API 和 WHATWG 规范。 https://developer.mozilla.org/en/docs/Web/API/console 掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。","tags":[{"name":"前端","slug":"前端","permalink":"http://suncafe.cc/tags/前端/"}]},{"title":"JavaScript:回调是什么鬼?","date":"2017-06-22T14:56:41.000Z","path":"2017/06/22/[译]JavaScript:回调是什么鬼/","text":"原文地址:JavaScript: What the heck is a Callback? 原文作者:Brandon Morelli 译文出自:掘金翻译计划 译者:sunui 校对者:reid3290、wilsonandusa 配合简单的示例,用短短 6 分钟学习和理解回调的基本知识。 回调 —— 题图来自 unsplash 回调是什么?简单讲: 回调是指在另一个函数执行完成之后被调用的函数 —— 因此得名“回调”。 稍复杂地讲: 在 JavaScript 中,函数也是对象。因此,函数可以传入函数作为参数,也可以被其他函数返回。这样的函数称为高阶函数。被作为参数传入的函数就叫做回调函数。 ^ 这听起来有点啰唆,让我们来看一些例子来简化一下。 为什么我们需要回调?有一个非常重要的原因 —— JavaScript 是事件驱动的语言。这意味着,JavaScript 不会因为要等待一个响应而停止当前运行,而是在监听其他事件时继续执行。来看一个基本的例子: function first(){ console.log(1); } function second(){ console.log(2); } first(); second(); 正如你所料,first 函数首先被执行,随后 second 被执行 —— 控制台输出下面内容: // 1 // 2 一切都如此美好。 但如果函数 first 包含某种不能立即执行的代码会如何呢?例如我们必须发送请求然后等待响应的 API 请求?为了模拟这种状况,我们将使用 setTimeout,它是一个在一段时间之后调用函数的 JavaScript 函数。我们将函数延迟 500 毫秒来模拟一个 API 请求,新代码长这样: function first(){ // 模拟代码延迟 setTimeout( function(){ console.log(1); }, 500 ); } function second(){ console.log(2); } first(); second(); 现在理解 setTimeout() 是如何工作的并不重要,重要的是你看到了我们已经把 console.log(1); 移动到了 500 秒延迟函数内部。那么现在调用函数会发生什么呢? first(); second(); // 2 // 1 即使我们首先调用了 first() 函数,我们记录的输出结果却在 second() 函数之后。 这不是 JavaScript 没有按照我们想要的顺序执行函数的问题,而是 JavaScript 在继续向下执行 second() 之前没有等待 first() 响应的问题。 所以为什么给你看这个?因为你不能一个接一个地调用函数并希望它们按照正确的顺序执行。回调正是确保一段代码执行完毕之后再执行另一段代码的方式。 创建一个回调好了,说了这么多,让我们创建一个回调! 首先,打开你的 Chrome 开发者工具(Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J),在控制台输入下面的函数声明: function doHomework(subject) { alert(`Starting my ${subject} homework.`); } 上面,我们已经创建了 doHomework 函数。我们的函数携带一个变量,是我们正在研究的课题。在控制台输入下面内容调用你的函数: doHomework('math'); // Alerts: Starting my math homework. 现在把我们的回调加进来,我们传入 callback 作为 doHomework() 的最后一个参数。这个回调函数是我们定义在接下来要调用的 doHomework() 函数的第二个参数。 function doHomework(subject**, callback**) { alert(`Starting my ${subject} homework.`); **callback();** } doHomework('math'**, function() { alert('Finished my homework'); }**); 如你所见,如果你将上面的代码输入控制台,你将依次得到两个警告:第一个是“starting homework”,接着是“finished homework”。 但是你的回调函数并不总是必须定义在函数调用里面,它们也可以定义在你代码中的其他位置,比如这样: function doHomework(subject, callback) { alert(`Starting my ${subject} homework.`); callback(); } function alertFinished(){ alert('Finished my homework'); } **doHomework('math', alertFinished);** 这个例子的结果和之前的例子完全一致。如你所见,我们在 doHomework() 函数调用中传入了 alertFinished 函数定义作为参数! 实际应用案例上周我发表了一篇关于如何用 38 行代码构建一个 Twitter 机器人的文章。文中的代码可以实现的唯一原因就是我使用了 Twitters API。当你向一个 API 发送请求,在你操作响应内容之前你必须等待这个响应。这是回调在实际应用中的绝佳案例。请求长这样: T.get('search/tweets', params, function(err, data, response) { if(!err){ // 这里是施展魔法之处 } else { console.log(err); } }) T.get 仅仅意味着我们将要向 Twitter 发送一个 get 请求 这个请求中有三个参数:‘search/tweets’ 是请求的路径,params 是搜索参数,随后的一个匿名函数是我们的回调。 回调在这里很重要,因为在我们的代码继续运行之前我们需要等待一个来自服务端的响应。我们并不知道 API 请求会成功还是会失败,所以通过 get 向 search/tweets 发送了请求参数以后,我们要等待。一旦 Twitter 响应,我们的回调函数就被调用。Twitter 要么发送一个 err(error)对象,要么发送一个 response 对象返回给我们。在我们的回调函数中我们可以使用 if() 语句来区分请求是否成功,然后相应地处理新数据。 你做到了干得漂亮!你现在(理想状况下)已经理解了回调是什么,回调如何工作。这只是回调的冰山一角,记住学无止境啊!我每周都会更新一些文章/教程,如果你愿意接收每周一次的推送,点击这里输入你的邮箱订阅吧! 掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。","tags":[{"name":"前端","slug":"前端","permalink":"http://suncafe.cc/tags/前端/"}]},{"title":"Airbnb 的前端重构","date":"2017-06-04T13:16:07.000Z","path":"2017/06/04/[译]Airbnb的前端重构/","text":"原文地址:Rearchitecting Airbnb’s Frontend 原文作者:Adam Neary 译文出自:掘金翻译计划 译者:sunui 校对者: 概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 端的架构。本文将讨论:(1)催生一些变化的产品驱动因素,(2)我们如何一步步摆脱遗留的 Rails 解决方案,(3)一些新技术栈的关键性支柱。彩蛋:我们将讨论接下来要做的事。 Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们流量最高的页面。近十年来,工程师们一直在发展、加强、和优化 Rails 输出页面的方式。 最近,我们转移到了主页以外的垂直页面,来介绍一些体验和去处。作为 web 端新增产品的一部分,我们花时间重新思考了搜索体验本身。 用于一个广泛搜索的路由间的过渡 我们希望用户体验流畅,要去斟酌用户在浏览页面和缩小搜索范围时遇到的内容,而不是从 www.airbnb.com 着陆页导航,(1)访问一个搜索结果页,(2)访问一个单一列表页,(3)访问预订流程,(4)每个页面都由 Rails 单独传送。 设计三种浏览搜索页的状态:新用户,老用户,和营销页。 在标签页之间切换和与列表进行交互应该感到惬意而轻松。事实上,如今没有什么可以阻止我们致力于在中小屏幕上提供与本地应用相符的体验。 再标签页之间切换的未来概念,考虑异步加载内容 要开发这种类型的体验,我们需要摆脱传统的页面切换方法,最终我们兴奋地全面重构了前端代码。 Leland Richardson 最近在 React Conf 大会上发表了关于 React Native 的存在于高访问量 native 应用中的“褐色地带”。 这篇文章将会探讨如何在类似的约束下进行强制性升级,不过是在 web 端。如果你遇到类似的情况,希望对你有帮助。 从 Rails 之中解脱在我们的烧烤开火之前,因为我们的线路图上存在所有有趣的渐进式 web 应用(WPA),我们需要从 Rails 中解脱出来(或者至少在 Airbnb 用 Rails 提供单独页面的这种方式)。 不幸的是,就在几个月前,我们的搜索页还包含一些非常老旧的代码,像指环王一样,触碰它就要小心自负后果。有趣的事实:我曾尝试用一个简单的 React 组件替换一个 Rails presenter 备份过的小巧的 Handlebars 模板,突然很多完全不相关的部分都崩掉了——甚至 API 响应都除了问题。原来,presenter 改变了后备 Rails 模型,多年来即使在 UI 没有渲染的时候,它也影响着所有的下游数据。 简而言之,我们在这个项目中,像 Indiana Jone 用自己的宝物交换了一袋沙子,突然间庙宇开始崩塌,我们正在从石块中奔跑。 第 1 步: 调整 API 数据当使用 Rails 在服务器端渲染页面时,你可以用任何你喜欢的方式把数据丢给服务器端的 React 组件。Controllers、helpers 和 presenters 能生成任何形式的数据,甚至当你把部分页面迁移到 React 时,每个组件都能处理它所需的任何数据。 但一旦你想渲染客户端路由,你需要能够以预定的形式动态请求所需的数据。将来我们可能用类似 GraphQL 的东西解决这个问题,但是现在暂且把它放到一边吧,因为这件事和重构代码没太大关系。相反,我们选择在我们的 API 的 “v2” 上进行调整,我们需要我们所有的组件来开始处理规范的数据格式。 如果你发现你自己和我们情况类似并且是一个大型的应用,你可能发现我们像我们这样做,规划迁移现有的服务器端数据管道是很容易的。简单地在任何地方用 Rails 渲染一个React组件,并确保数据输入是 API 所规定的类型。你可以用客户端的 React PropTypes 来进一步验证数据类型是否与 API v2 一致。 对我们来说棘手的问题是和那些参与客户预定流程交互的团队协作:商业旅游、发展、度假租赁团队;中国和印度市场团队,灾难恢复团队…等等,我们需要重新培训所有这些人,即使在技术上可以将数据直接传递到正在呈现的组件上(“是的,我明白,这仅仅是一种实验,但是…”),所有的数据都要通过 API。 第 2 步: 非 API 数据: 配置、试验、惯用语、本地化、 国际化…有一类独特的数据和我们设想的 API 化的数据不同,包括应用配置,用户试验任务,国际化,本地化等等类似的问题。近年来,Airbnb 已经建立了一套难以置信的工具来支持这些功能,但是把这些数据传送到前端的机制就不那么令人愉快了(在革命开始之前,或许就已经很蹩脚了!)。 我们使用 Hypernova 来服务端渲染 React,但是在我们此次重构深入之前,无论服务端渲染时 React 组件中的试验交付会不会爆发或者客户端上提供的字符串转换是否都可以在服务器上可靠地使用,这些都还有点模糊。最重要的是,如果服务器和客户端输出匹配不到位,页面不仅会不断闪烁刷新 diff,还可以在加载后重新渲染整个页面,这对于性能来说很可怕。 更糟糕的是,我们有很久以前写过一些神奇的 Rails 功能,比如 add_bootstrap_data(key, value) 表面上可以在 Rails 中的任何地方调用,通过 BootstrapData.get(key) 使数据在客户端的全局可用(再次强调,对 Hypernova 来说已经不必要了)。这作为小团队的一个实用程序开始成为对大团队和应用来说不可溯源的巫术。由于每个团队拥有不同的页面或功能,因此“数据清洗”变得越来越棘手,因此每个团队都会培养出一种不同的加载配置的机制,以满足其独特需求。 显然,这已经崩溃了,所以我们融合了一个用于引导非 API 数据的规范机制,我们开始将所有应用程序和页面迁移到 Rails 和 React/Hypernova 之间的这种切换。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596import React, { PropTypes } from 'react';import { compose } from 'redux';import AirbnbUser from '[our internal user management library]';import BootstrapData from '[our internal bootstrap library]';import Experiments from '[our internal experiment library]';import KillSwitch from '[our internal kill switch library]';import L10n from '[our internal l10n library]';import ImagePaths from '[our internal CDN pipeline library]';import withPhrases from '[our internal i18n library]';import { forbidExtraProps } from '[our internal propTypes library]';const propTypes = forbidExtraProps({ behavioralUid: PropTypes.string, bootstrapData: PropTypes.object, experimentConfig: PropTypes.object, i18nInit: PropTypes.object, images: PropTypes.object, killSwitches: PropTypes.objectOf(PropTypes.bool), phrases: PropTypes.object, userAttributes: PropTypes.object,});const defaultProps = { behavioralUid: null, bootstrapData: {}, experimentConfig: {}, i18nInit: null, images: {}, killSwitches: {}, phrases: {}, userAttributes: null,};function withHypernovaBootstrap(App) { class HypernovaBootstrap extends React.Component { constructor(props) { super(props); const { behavioralUid, bootstrapData, experimentConfig, i18nInit, images, killSwitches, userAttributes, } = props; // 清除服务器上的引导数据,以避免泄露数据 if (!global.document) { BootstrapData.clear(); } BootstrapData.extend(bootstrapData); ImagePaths.extend(images); // 在测试中用空对象调用 L10n.init 是不安全的 if (i18nInit) { L10n.init(i18nInit); } if (userAttributes) { AirbnbUser.setCurrent(userAttributes); } if (userAttributes && behavioralUid) { Experiments.initializeGlobalConfiguration({ experiments: experimentConfig, userId: userAttributes.id, visitorId: behavioralUid, }); } else { Experiments.setExperiments(experimentConfig); } KillSwitches.extend(killSwitches); } render() { // 理想情况下,我们只想传输 bootstrapData // 如果你有从 redux 或 alt 数据 从服务端到 bootstrap // 你当然可以只传输一个在 bootstrapData 中的 key // 其他属性被处理但是不会传入应用 return <App bootstrapData={this.props.bootstrapData} />; } } Bootstrap.propTypes = propTypes; Bootstrap.defaultProps = defaultProps; const wrappedComponentName = App.displayName || App.name || 'Component'; Bootstrap.displayName = `withHypernovaBootstrap(${wrappedComponentName})`; return Bootstrap;}export default compose(withPhrases, withHypernovaBootstrap); 用于引导非 API 数据规范的更高阶的组件 这个更高阶的组件做了两件更重要的事情: 它接收一个引导数据作为普通的旧对象的规范形式,并且正确地初始化所有支持的工具,用于服务器渲染和客户端渲染。 它吞噬除了一切除了 bootstrapData ,它是另一个简单的对象,必要时把 <App> 组件传入 Redux 作为 children 使用。 单纯来看,我们删除了 add_bootstrap_data,并阻止工程师将任意键传递到顶级的 React 组件。秩序被重新恢复,以前我们在客户端中动态地导航到路由,并且渲染材料复杂的 content,而不需要Rails来支持它。 进击的前端服务端的重构已经有了头绪,现在我们把目光转向客户端。 懒加载的单页面应用那段日子已经过去了,朋友们,初始化时带着可怕 loading 的巨型单页面应用(SPA)已经不复存在了。当我们提出用 React Router 做客户端路由的方案时,可怕的 loading 是很多人提出拒绝的理由。 在 chrome Timeline 中 route 包的懒加载 但是,如果你看到上面的内容,你就会发现代码分割 和延迟加载 捆绑路由的影响。实质上,我们是在服务端渲染的页面并且仅仅传输最低限度的一部分用于在浏览器端交互的 Javascript 代码,然后我们利用浏览器的空余时间主动下载其余部分。 在 Rails 端,我们有一个 controller 用于通过 SPA 交付的所有路由。每一个 action 只负责:(1)出发客户端导航中的一切请求,(2)将数据和配置引导到 Hypernova。我们把每个 action (controller、helpers 和 presenters 之间)上千行的 Ruby 代码缩减到 20-30 行。实力碾压。 但这不仅仅是代码的不同… 两种方式加载东京主页的对比(4-5 倍的差距) …现在页面间的过渡像奶油般顺滑,并且这一步大幅提升了速度(约 5 倍)。而且我们我们可以实现文章开头的那张动画特性。 异步组件之前的 React ,我们需要一次渲染整个页面,我们以前的 React 都是这么做的。但现在我们使用异步组件,类似这种方式, mount 以后加载组件层次结构的部分。 12345678910111213141516171819202122232425262728293031323334353637export default class AsyncComponent extends React.Component { constructor(props) { super(props); this.state = { Component: null, }; } componentDidMount() { this.props.loader().then((Component) => { this.setState({ Component }); }); } render() { const { Component } = this.state; // `loader` 属性没有被使用。 它被提取,所以我们不会将其传递给包装的组件 // eslint-disable-next-line no-unused-vars const { renderPlaceholder, placeholderHeight, loader, ...rest } = this.props; if (Component) { return <Component {...rest} />; } return renderPlaceholder ? renderPlaceholder() : <WrappedPlaceholder height={placeholderHeight} />; }}AsyncComponent.propTypes = { // 注意 loader 是返回一个 promise 的函数。 // 这个 promise 应该处理一个可渲染的组件。 loader: PropTypes.func.isRequired, placeholderHeight: PropTypes.number, renderPlaceholder: PropTypes.func,}; 这对于最初不可见的重量级元素尤其有用,比如 Modals 和 Panels。我们的明确目标是精确地提供初始化页面可见部分所需的 所需的 JavaScript,并使其可交互,而不只一行。这也意味着如果,比方说团队想使用 D3 用于页面弹窗的一个图表,而其他部分不使用 D3,这时候他们就可以权衡一下下载仓库的代码,可以把他们的弹窗代码和其他代码隔离出来。 最重要的是,它可以简单地在任何需要的地方使用: 123456789101112131415161718192021import React from 'react';import AsyncComponent from '../../../components/AsyncComponent';import scheduleAsyncLoad from '../../../utils/scheduleAsyncLoad';function mapLoader() { return new Promise((resolve) => { if (process.env.LAZY_LOAD) { return airPORT('./Map', 'HomesSearchMap') .then(x => x.default || x); } });}export function scheduleMapLoad() { scheduleAsyncLoad(searchResultsMapLoader);}export default function MapAsync(props) { return <AsyncComponent loader={mapLoader} {...props} />;}view raw 这里我们可以简单地把我们的同步版本的地图换成异步版本,这在小断点上特别有用,用户通过点击按钮显示地图。考虑到大多数用户用手机,在担心 Google 地图之前,让他们进入互动这样会缩短加载时的焦虑感。 另外,注意 scheduleAsyncLoad() 的效率,在用户交互之前就要请求包。考虑到地图如此频繁的使用,我们不需要等待用户交互就去请求它。而是在用户进入主页和搜索页的时候就把它加入队列,如果用户在下载完成之前就请求了它,他们会看到一个 <Loader /> 直到组件可用。没毛病。 这种方法的最后一个好处是 HomesSearch_Map 成为浏览器可以缓存的命名包。当我们分解较大的基于路由的捆绑包时,应用程序中 slowly-changing 的部分在更新时保持不变,从而进一步节省了 JavaScript 下载时间。 构建无障碍的设计语言毫无疑问,它保证的是一个专有的需求,但是我们已经开始构建内部组件库,其中辅助功能被强制为一个严格的约束。在接下来的几个月中,我们将替换所有与屏幕阅读器不兼容的 UI。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182import React, { PropTypes } from 'react';import { forbidExtraProps } from 'airbnb-prop-types';import CheckBox from '../CheckBox';import FlexBar from '../FlexBar';import Label from '../Label';import HideAt from '../HideAt';import ShowAt from '../ShowAt';import Spacing from '../Spacing';import Text from '../Text';import CheckBoxOnly from '../../private/CheckBoxOnly';import toggleArrayItem from '../../utils/toggleArrayItem';import ROOM_TYPES from '../../constants/roomTypes';const propTypes = forbidExtraProps({ id: PropTypes.string.isRequired, roomTypes: PropTypes.arrayOf(PropTypes.oneOf(ROOM_TYPES.map(roomType => roomType.filterKey))), onUpdate: PropTypes.func,});const defaultProps = { roomTypes: [], onUpdate() {},};export default function RoomTypeFilter({ id, roomTypes, onUpdate }) { return ( <div> {ROOM_TYPES.map(({ id: roomTypeId, filterKey, iconClass: IconClass, title, subtitle }) => { const inputId = `${id}-${roomTypeId}-Checkbox`; const titleId = `${id}-${roomTypeId}-title`; const subtitleId = `${id}-${roomTypeId}-subtitle`; const selected = roomTypes.includes(filterKey); const checkbox = ( <Spacing top={0.5} right={1}> <CheckBoxOnly id={inputId} describedById={subtitleId} name={`${roomTypeId}-only`} checked={selected} onChange={() => onUpdate({ roomTypes: toggleArrayItem(roomTypes, filterKey) })} /> </Spacing> ); return ( <div key={roomTypeId}> <ShowAt breakpoint="mediumAndAbove"> <Label htmlFor={inputId}> <FlexBar align="top" before={checkbox} after={<IconClass size={28} />}> <Spacing right={2}> <div id={titleId}> <Text light>{title}</Text> </div> <div id={subtitleId}> <Text small light>{subtitle}</Text> </div> </Spacing> </FlexBar> </Label> </ShowAt> <HideAt breakpoint="mediumAndAbove"> <Spacing vertical={2}> <CheckBox id={roomTypeId} name={roomTypeId} checked={selected} label={title} onChange={() => onUpdate({ roomTypes: toggleArrayItem(roomTypes, filterKey) })} subtitle={subtitle} /> </Spacing> </HideAt> </div> ); })} </div> );}RoomTypeFilter.propTypes = propTypes;RoomTypeFilter.defaultProps = defaultProps; 通过我们的设计语言系统加入的无障碍设计到产品的例子 这个 UI 非常丰富,我们希望将 CheckBox 不仅与 title 相关联,还可以使用 aria-describedby 与 subtitle 关联。为了实现这一点,需要 DOM 中唯一的标识符,这意味着强制关联一个必须的 ID 作为任何调用方需要提供的属性。如果一个组件被用于生产,这些是 UI 是可以强制约束类型的,它提供内置的可访问性。 上面的代码也演示了我们的响应式实体 HideAt 和 ShowAt,它使我们能够大幅度地改变用户在不同屏幕尺寸下的体验,而无需使用 CSS 控制隐藏和显示。这造就了更精简的页面。 关于状态的“外科”和“哲学”不涉及关于如何处理应用程序状态的争论的前端文章不是完整的前端文章。 我们使用 Redux 来处理所有的 API 数据和“全局”数据比如认证状态和体验配置。个人来讲我喜欢 redux-pack 处理异步,你会发现新大陆。 然而,当遇到页面上所有的复杂性——特别是围绕搜索的——对于一些像表单元素这样低级的用户交互使用 redux 就没那么好用了。我们发现无论如何优化,Redux 循环依然会造成输入体验的卡顿。 我们的房间类型筛选器 (代码在上面) 所以对于用户的所有操作我们使用组件的本地状态,除非触发路由变化或者网络请求才是用 Redux,并且我们没再遇到什么麻烦。 同时,我喜欢 Redux container 组件的那种感觉,并且我们即使带有本地状态,我们依然可以构建可以共享的高阶组件。一个伟大的例子就是我们的筛选功能。搜索在底特律的家,你会在页面上看见几个不同的面板,每一个都可以独立操作,你可以更改你的搜索条件。在不同的断点之间,实际上有几十个组件需要知道当前应用的搜索过滤器以及如何更新它们,在用户交互期间被暂时或z正式地被用户接受。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192import React, { PropTypes } from 'react';import { connect } from 'react-redux';import SearchFiltersShape from '../../shapes/SearchFiltersShape';import { isDirty } from '../utils/SearchFiltersUtils';function mapStateToProps({ exploreTab }) { const { responseFilters, } = exploreTab; return { responseFilters, };}export const withFiltersPropTypes = { stagedFilters: SearchFiltersShape.isRequired, responseFilters: SearchFiltersShape.isRequired, updateFilters: PropTypes.func.isRequired, clearFilters: PropTypes.func.isRequired,};export const withFiltersDefaultProps = { stagedFilters: {}, responseFilters: {}, updateFilters() {}, clearFilters() {},};export default function withFilters(WrappedComponent) { class WithFiltersHOC extends React.Component { constructor(props) { super(props); this.state = { stagedFilters: props.responseFilters, }; } componentWillReceiveProps(nextProps) { if (isDirty(nextProps.responseFilters, this.props.responseFilters)) { this.setState({ stagedFilters: nextProps.responseFilters }); } } render() { const { responseFilters } = this.props; const { stagedFilters } = this.state; return ( <WrappedComponent {...this.props} stagedFilters={stagedFilters} updateFilters={({ updateObj, keysToRemove }, callback) => { const newStagedFilters = omit({ ...stagedFilters, ...updateObj }, keysToRemove); this.setState({ stagedFilters: newStagedFilters, }, () => { if (callback) { // setState callback can be called before withFilter state // propagates to child props. callback(newStagedFilters); } }); }} clearFilters={() => { this.setState({ stagedFilters: responseFilters, }); }} /> ); } } const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component'; WithFiltersHOC.WrappedComponent = WrappedComponent; WithFiltersHOC.displayName = `withFilters(${wrappedComponentName})`; if (WrappedComponent.propTypes) { WithFiltersHOC.propTypes = { ...omit(WrappedComponent.propTypes, 'stagedFilters', 'updateFilters', 'clearFilters'), responseFilters: SearchFiltersShape, }; } if (WrappedComponent.defaultProps) { WithFiltersHOC.defaultProps = { ...WrappedComponent.defaultProps }; } return connect(mapStateToProps)(WithFiltersHOC);} 这里我们有一个利落的技巧。每一个需要和筛选交互的组件只需被 HOC 包裹起来,你就能做到了。它甚至还有属性类型。每个组件都通过 Redux 连接到responseFilters(与当前显示的结果相关联的那些),并同时保有一个本地 stagedFilters 状态对象用于更改。 通过以这种方式处理状态,与我们的价格滑块进行交互对页面的其余部分没有影响,所以表现很好。而且但所有过滤器面板都具有相同的功能签名,因此开发也很简单。 未来做些什么?既然现在已经良策在手,我们可以把目光转向未来。 AMP 核心预订流程中的所有页面的 AMP 版本将会实现亚秒级(某些情况下)在手机 web 上 Google 搜索的 可交互时间,通过移动网络和桌面网络,所需的许多更改将在 P50 / P90 / P95 冷负载时间内实现显着改善。 PWA 功能将实现亚秒级(在某些情况下)返回访客的可交互时间,并将打开离线优先功能的大门,因此对于具有脆弱网络连接的用户非常关键。 将最终的锤子应用到传统的技术/框架上将会将包大小减少一半。这不是华而不实的工作,我们最终翻出 jQuery、Alt、Bootstrap、Underscore 以及所有额外的 CSS 请求(他们使渲染停滞,并且将近 97% 的规则是不会被使用!)不仅精简了我们的代码,还精简了新员工在上升时需要学习的足迹。 最后,yeoman 的手动捕捉瓶颈的工作、异步加载代码在初始渲染时不可见、避免不必要的重新渲染、并降低重新渲染的成本,这些改进正是拖拉机和顶级跑车之间的区别。 下次请收听我们将追逐的这些机会的成果。因为这么多的成果会有一些数量上的冲突,我们将尽量选择一些具体的成果在下篇文章中总结。 自然,如果你欣赏本文并觉得这是一个有趣的挑战,我们一直在寻找优秀出色的人加入团队。如果你只想做一些交流,那么随时可以点击我的 twitter @adamrneary。 最后,深切地向 Salih Abdul-Karim 和 Hugo Ahlberg 两位体验设计师致敬,他们的令人动容的动画至今让我目不转睛。许多工程师在他们的领域值得赞美,作出努力人的名单难以一一列出的,但绝对包括 Nick Sorrentino、Joe Lencioni、Michael Landau、Jack Zhang、Walker Henderson 和 Nico Moschopoulos. 掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。","tags":[{"name":"前端","slug":"前端","permalink":"http://suncafe.cc/tags/前端/"}]},{"title":"JavaScript算法笔记——初级篇","date":"2017-05-09T14:43:32.000Z","path":"2017/05/09/Javascript算法笔记——初级篇/","text":"Reverse a String 字符串反转point: Global String Object String.prototype.split() Array.prototype.reverse() Array.prototype.join() code:12345function reverseString(str) { return str.split("").reverse().join("");}reverseString("hello"); Factorialize a Number 求阶乘point: Arithmetic Operators code:1234567function factorialize(num) { if (num === 0) { return 1; } return num * factorialize(num-1);}factorialize(5); Check for Palindromes 回文point String.prototype.replace() String.prototype.toLowerCase() 1234567function palindrome(str) { var re = /[\\W_]/g; var lowRegStr = str.toLowerCase().replace(re, ''); var reverseStr = lowRegStr.split('').reverse().join(''); return reverseStr === lowRegStr;}palindrome("eye"); Find the Longest Word in a String 查找最长单词 String.prototype.split() String.length 12345678function findLongestWord(str) { return Math.max.apply(null,str.split(" ").map(function(one){ return one.length }))}findLongestWord("The quick brown fox jumped over the lazy dog"); Title Case a Sentence 句子变成标题 String.prototype.split() 1234567function titleCase(str) { return str.toLowerCase().split(' ').map(function(word) { return word.replace(word[0], word[0].toUpperCase()); }).join(' ');}titleCase("I'm a little tea pot"); Return Largest Numbers in Arrays 数组中的最大值 Comparison Operators1234567function largestOfFour(arr) { return arr.map(function(innerarr){ return Math.max.apply(null,innerarr) })}largestOfFour([[4, 5, 1, 3], [13, 27, 18, 26], [32, 35, 37, 39], [1000, 1001, 857, 1]]); Confirm the Ending 确认结尾 String.prototype.substr() String.prototype.substring() 12345function confirmEnding(str, target) { return str.substr(-target.length)===target;}confirmEnding("Bastian", "n"); Repeat a string repeat a string 重复字符串 Global String Object12345function repeatStringNumTimes(str, num) { if(num<1) return "" ; return str+repeatStringNumTimes(str, num-1);}repeatStringNumTimes("abc",8); Repeat a string repeat a string 重复字符串 Global String Object 123456789function truncateString(str, num) { if (str.length <= num) { return str; } else { return str.slice(0, num > 3 ? num - 3 : num) + '...'; }}truncateString("A-tisket a-tasket A green and yellow basket", 11); Chunky Monkey 分割数组 Array.prototype.slice() Array.prototype.splice()1234567891011121314function chunkArrayInGroups(arr, size) { var temp=[]; var newArr=[]; arr.forEach(function(item,index){ temp.push(item); if(temp.length===size||index===arr.length-1){ newArr.push(temp); temp=[]; } }); return newArr;}chunkArrayInGroups(["a", "b", "c", "d"], 2); Mutations 字符查找 String.prototype.indexOf()1234567891011function mutation(arr) { var string1=arr[0].toLowerCase(); var string2=arr[1].toLowerCase(); for(var i=0;i<string2.length;i++){ if(string1.indexOf(string2[i])===-1)return false; } return true;}mutation(["hello", "hey"]); Falsy Bouncer 过滤数组 Boolean Objects Array.prototype.filter() 12345function bouncer(arr) { return arr.filter(Boolean);}bouncer([7, "ate", "", false, 9]); Seek and Destroy 过滤数组 转换arguments Arguments object Array.prototype.filter()12345678function destroyer(arr) { var arr2=Array.prototype.slice.call(arguments); return arr.filter(function(item){ return arr2.slice(1).indexOf(item)===-1; });}destroyer([1, 2, 3, 1, 2, 3], 2, 3); Where do I belong 排序 Array.prototype.sort()1234567891011121314function getIndexToIns(arr, num) { arr.sort(function(a, b) { return a - b; }); for (var a = 0; a < arr.length; a++) { if (arr[a] >= num) return parseInt(a); } return arr.length;}getIndexToIns([40, 60], 50); Caesars Cipher 撒开密码1234567891011121314151617function rot13(str) { var rotCharArray = []; var regEx = /[A-Z]/ ; str = str.split(""); for (var x in str) { if (regEx.test(str[x])) { // A more general approach // possible because of modular arithmetic // and cyclic nature of rot13 transform rotCharArray.push((str[x].charCodeAt() - 65 + 13) % 26 + 65); } else { rotCharArray.push(str[x].charCodeAt()); } } str = String.fromCharCode.apply(String, rotCharArray); return str;}","tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://suncafe.cc/tags/JavaScript/"}]},{"title":"函数式程序员的JavaScript简介(软件编写)(第三部分)","date":"2017-04-18T15:54:12.000Z","path":"2017/04/18/[译] 函数式程序员的 JavaScript 简介 (软件编写)(第三部分)/","text":"原文地址:A Functional Programmer’s Introduction to JavaScript (Composing Software)(part 3) 原文作者:Eric Elliott 译文出自:掘金翻译计划 译者:sunui 校对者:Aladdin-ADD、avocadowang 烟雾艺术魔方 — MattysFlicks — (CC BY 2.0) 注意:这是“软件编写”系列文章的第三部分,该系列主要阐述如何在 JavaScript ES6+ 中从零开始学习函数式编程和组合化软件(compositional software)技术(译注:关于软件可组合性的概念,参见维基百科 Composability)。后续还有更多精彩内容,敬请期待!< 上一篇 | <<第一篇 | 下一篇 > 对于不熟悉 JavaScript 或 ES6+ 的同学,这里做一个简短的介绍。无论你是 JavaScript 开发新手还是有经验的老兵,你都可能学到一些新东西。以下内容仅是浅尝辄止,吊吊大家的兴致。如果想知道更多,还需深入学习。敬请期待吧。 学习编程最好的方法就是动手编程。我建议您使用交互式 JavaScript 编程环境(如 CodePen 或 Babel REPL)。 或者,您也可以使用 Node 或浏览器控制台 REPL。 表达式和值表达式是可以求得数据值的代码块。 下面这些都是 JavaScript 中合法的表达式: 12345677;7 + 1; // 87 * 2; // 14'Hello'; // Hello 表达式的值可以被赋予一个名称。执行此操作时,表达式首先被计算,取得的结果值被赋值给该名称。对于这一点我们将使用 const 关键字。这不是唯一的方式,但这将是你使用最多的,所以目前我们就可以坚持使用 const。 12const hello = 'Hello';hello; // Hello var、let 和 constJavaScript 支持另外两种变量声明关键字:var,还有 let。我喜欢根据选择的顺序来考虑它们。默认情况下,我选择最严格的声明方式:const。用 const 关键字声明的变量不能被重新赋值。最终值必须在声明时分配。这可能听起来很严格,但限制是一件好事。这是个标识在提醒你“赋给这个名称的值将不会改变”。它可以帮你全面了解这个名称的意义,而无需阅读整个函数或块级作用域。 有时,给变量重新赋值很有用。比如,如果你正在写一个手动的强制性迭代,而不是一个更具功能性的方法,你可以迭代一个用 let 赋值的计数器。 因为 var 能告诉你很少关于这个变量的信息,所以它是最无力的声明标识。自从开始用 ES6,我就再也没在实际软件项目中有意使用 var 作声明了。 注意一下,一个变量一旦用 let 或 const 声明,任何再次声明的尝试都将导致报错。如果你在 REPL(读取-求值-输出循环)环境中更喜欢多一些实验性和灵活性,那么建议你使用 var 声明变量,与 let 和 const 不同,使用 var 重新声明变量是合法的。 本文将使用 const 来让您习惯于为实际程序中用 const,而出于试验的目的自由切换回 var。 数据类型目前为止我们见到了两种数据类型:数字和字符串。JavaScript 也有布尔值(true 或 false)、数组、对象等。稍后我们再看其他类型。 数组是一系列值的有序列表。可以把它比作一个能够装很多元素的容器。这是一个数组字面量: 1[1, 2, 3]; 当然,它也是一个可被赋予名称的表达式: 1const arr = [1, 2, 3]; 在 JavaScript 中,对象是一系列键值对的集合。它也有字面量: 123{ key: 'value'} 当然,你也可以给对象赋予名称: 123const foo = { bar: 'bar'} 如果你想将现有变量赋值给同名的对象属性,这有个捷径。你可以仅输入变量名,而不用同时提供一个键和一个值: 123const a = 'a';const oldA = { a: a }; // 长而冗余的写法const oA = { a }; // 短小精悍! 为了好玩而已,让我们再来一次: 12const b = 'b';const oB = { b }; 对象可以轻松合并到新的对象中: 1const c = {...oA, ...oB}; // { a: 'a', b: 'b' } 这些点是对象扩展运算符。它迭代 oA 的属性并分配到新的对象中,oB 也是一样,在新对象中已经存在的键都会被重写。在撰写本文时,对象扩展是一个新的试验特性,可能还没有被所有主流浏览器支持,但如果你那不能用,还可以用 Object.assign() 替代: 1const d = Object.assign({}, oA, oB); // { a: 'a', b: 'b' } 这个 Object.assign() 的例子代码很少,如果你想合并很多对象,它甚至可以节省一些打字。注意当你使用 Object.assign() 时,你必须传一个目标对象作为第一个参数。它就是那个源对象的属性将被复制过去的对象。如果你忘了传,第一个参数传递的对象将被改变。 以我的经验,改变一个已经存在的对象而不创建一个新的对象常常引发 bug。至少至少,它很容易出错。要小心使用 Object.assign()。 解构对象和数组都支持解构,这意味着你可以从中提取值分配给命过名的变量: 123456789101112const [t, u] = ['a', 'b'];t; // 'a'u; // 'b'const blep = { blop: 'blop'};// 下面等同于:// const blop = blep.blop;const { blop } = blep;blop; // 'blop' 和上面数组的例子类似,你可以一次解构多次分配。下面这行你在大量的 Redux 项目中都能见到。 1const { type, payload } = action; 下面是它在一个 reducer(后面的话题再详细说) 的上下文中的使用方法。 1234567const myReducer = (state = {}, action = {}) => { const { type, payload } = action; switch (type) { case 'FOO': return Object.assign({}, state, payload); default: return state; }}; 如果不想为新绑定使用不同的名称,你可以分配一个新名称: 12const { blop: bloop } = blep;bloop; // 'blop' 读作:把 blep.blop 分配给 bloop。 比较运算符和三元表达式你可以用严格的相等操作符(有时称为“三等于”)来比较数据值: 13 + 1 === 4; // true 还有另外一种宽松的相等操作符。它正式地被称为“等于”运算符。非正式地可以叫“双等于”。双等于有一两个有效的用例,但大多数时候默认使用 === 操作符是更好的选择。 其它比较操作符有: > 大于 < 小于 >= 大于或等于 <= 小于或等于 != 不等于 !== 严格不等于 && 逻辑与 || 逻辑或 三元表达式是一个可以让你使用一个比较器来问问题的表达式,运算出的不同答案取决于表达式是否为真: 114 - 7 === 7 ? 'Yep!' : 'Nope.'; // Yep! 函数JavaScript 支持函数表达式,函数可以这样分配名称: 1const double = x => x * 2; 这和数学表达式 f(x) = 2x 是一个意思。大声说出来,这个函数读作 x 的 f 等于 2x。这个函数只有当你用一个具体的 x 的值应用它的时候才有意思。在其它方程式里面你写 f(2),就等同于 4。 换种说话就是 f(2) = 4。您可以将数学函数视为从输入到输出的映射。这个例子里 f(x) 是输入数值 x 到相应的输出数值的映射,等于输入数值和 2 的乘积。 在 JavaScript 中,函数表达式的值是函数本身: 1double; // [Function: double] 你可以使用 .toString() 方法看到这个函数的定义。 1double.toString(); // 'x => x * 2' 如果要将函数应用于某些参数,则必须使用函数调用来调用它。函数调用会接收参数并且计算一个返回值。 你可以使用 <functionName>(argument1, argument2, ...rest) 调用一个函数。比如调用我们的 double 函数,就加一对括号并传进去一个值: 1double(2); // 4 和一些函数式语言不同,这对括号是有意义的。没有它们,函数将不会被调用。 1double 4; // SyntaxError: Unexpected number 签名函数的签名可以包含以下内容: 一个 可选的 函数名。 在括号里的一组参数。 参数的命名是可选的。 返回值的类型。 JavaScript 的签名无需指定类型。JavaScript 引擎将会在运行时断定类型。如果你提供足够的线索,签名信息也可以通过开发工具推断出来,比如一些 IDE(集成开发环境)和使用数据流分析的 Tern.js。 JavaScript 缺少它自己的函数签名语法,所以有几个竞争标准:JSDoc 在历史上非常流行,但它太过笨拙臃肿,没有人会不厌其烦地维护更新文档与代码同步,所以很多 JS 开发者都弃坑了。 TypeScript 和 Flow 是目前的大竞争者。这二者都不能让我确定地知道怎么表达我需要的一切,所以我使用 Rtype,仅仅用于写文档。一些人倒退回 Haskell 的 curry-only Hindley–Milner 类型系统。如果仅用于文档,我很乐意看到 JavaScript 能有一个好的标记系统标准,但目前为止,我觉得当前的解决方案没有能胜任这个任务的。现在,怪异的类型标记即使和你在用的不尽相同,也就将就先用着吧。 1functionName(param1: Type, param2: Type) => Type double 函数的签名是: 1double(x: n) => n 尽管事实上 JavaScript 不需要注释签名,知道何为签名和它意味着什么依然很重要,它有助于你高效地交流函数是如何使用和如何构建的。大多数可重复使用的函数构建工具都需要你传入同样类型签名的函数。 默认参数值JavaScript 支持默认参数值。下面这个函数类似一个恒等函数(以你传入参数为返回值的函数),一旦你用 undefined 调用它,或者根本不传入参数——它就会返回 0,来替代: 1const orZero = (n = 0) => n; 如上,若想设置默认值,只需在传入参数时带上 = 操作符,比如 n = 0。当你用这种方式传入默认值,像 Tern.js、Flow、或者 TypeScript 这些类型检测工具可以自行推断函数的类型签名,甚至你不需要刻意声明类型注解。 结果就是这样,在你的编辑器或者 IDE 中安装正确的插件,在你输入函数调用时,你可以看见内联显示的函数签名。依据它的调用签名,函数的使用方法也一目了然。无论起不起作用,使用默认值可以让你写出更具可读性的代码。 注意: 使用默认值的参数不会增加函数的 .length 属性,比如使用依赖 .length 值的自动柯里化会抛出不可用异常。如果你碰上它,一些柯里化工具(比如 lodash/curry)允许你传入自定义参数来绕开这个限制。 命名参数JavaScript 函数可以传入对象字面量作为参数,并且使用对象解构来分配参数标识,这样做可以达到命名参数的同样效果。注意,你也可以使用默认参数特性传入默认值。 1234567891011121314151617181920const createUser = ({ name = 'Anonymous', avatarThumbnail = '/avatars/anonymous.png'}) => ({ name, avatarThumbnail});const george = createUser({ name: 'George', avatarThumbnail: 'avatars/shades-emoji.png'});george;/*{ name: 'George', avatarThumbnail: 'avatars/shades-emoji.png'}*/ 剩余和展开JavaScript 中函数共有的一个特性是可以在函数参数中使用剩余操作符 ... 来将一组剩余的参数聚集到一起。 例如下面这个函数简单地丢弃第一个参数,返回其余的参数: 12const aTail = (head, ...tail) => tail;aTail(1, 2, 3); // [2, 3] 剩余参数将各个元素组成一个数组。而展开操作恰恰相反:它将一个数组中的元素扩展为独立元素。研究一下这个: 12const shiftToLast = (head, ...tail) => [...tail, head];shiftToLast(1, 2, 3); // [2, 3, 1] JavaScript 数组在使用扩展操作符的时候会调用一个迭代器,对于数组中的每一个元素,迭代器都会传递一个值。在 [...tail, head] 表达式中,迭代器按顺序从 tail 数组中拷贝到一个刚刚创建的新的数组。之前 head 已经是一个独立元素了,我们只需把它放到数组的末端,就完成了。 柯里化可以通过返回另一个函数来实现柯里化(Curry)和偏应用(partial application): 12const highpass = cutoff => n => n >= cutoff;const gt4 = highpass(4); // highpass() 返回了一个新函数 你可以不使用箭头函数。JavaScript 也有一个 function 关键字。我们使用箭头函数是因为 function 关键字需要打更多的字。这种写法和上面的 highPass() 定义是一样的: 12345const highpass = function highpass(cutoff) { return function (n) { return n >= cutoff; };}; JavaScript 中箭头的大致意义就是“函数”。使用不同种的方式声明,函数行为会有一些重要的不同点(=> 缺少了它自己的 this ,不能作为构造函数),但当我们遇见那就知道不同之处了。现在,当你看见 x => x,想到的是 “一个携带 x 并且返回 x 的函数”。所以 const highpass = cutoff => n => n >= cutoff; 可以这样读: “highpass 是一个携带 cutoff 返回一个携带 n 并返回结果 n >= cutoff 的函数的函数” 既然 highpass() 返回一个函数,你可以使用它创建一个更独特的函数: 1234const gt4 = highpass(4);gt4(6); // truegt4(3); // false 自动柯里化函数,有利于获得最大的灵活性。比如你有一个函数 add3(): 1const add3 = curry((a, b, c) => a + b + c); 使用自动柯里化,你可以有很多种不同方法使用它,它将根据你传入多少个参数返回正确结果: 1234add3(1, 2, 3); // 6add3(1, 2)(3); // 6add3(1)(2, 3); // 6add3(1)(2)(3); // 6 令 Haskell 粉遗憾的是,JavaScript 没有内置自动柯里化机制,但你可以从 Lodash 引入: 1$ npm install --save lodash 然后在你的模块里: 1import curry from 'lodash/curry'; 或者你可以使用下面这个魔性写法: 12345678// 精简的递归自动柯里化const curry = ( f, arr = []) => (...args) => ( a => a.length === f.length ? f(...a) : curry(f, a))([...arr, ...args]); 函数组合当然你能够开始组合函数了。组合函数是传入一个函数的返回值作为参数给另一个函数的过程。用数学符号标识: 1f . g 翻译成 JavaScript: 1f(g(x)) 这是从内到外地求值: x 是被求数值 g() 应用给 x f() 应用给 g(x) 的返回值 例如: 12const inc = n => n + 1;inc(double(2)); // 5 数值 2 被传入 double(),求得 4。 4 被传入 inc() 求得 5。 你可以给函数传入任何表达式作为参数。表达式在函数应用之前被计算: 1inc(double(2) * double(2)); // 17 既然 double(2) 求得 4,你可以读作 inc(4 * 4),然后计算得 inc(16),然后求得 17。 函数组合是函数式编程的核心。我们后面还会介绍很多。 数组数组有一些内置方法。方法是指对象关联的函数,通常是这个对象的属性: 12const arr = [1, 2, 3];arr.map(double); // [2, 4, 6] 这个例子里,arr 是对象,.map() 是一个以函数为值的对象属性。当你调用它,这个函数会被应用给参数,和一个特别的参数叫做 this,this 在方法被调用之时自动设置。这个 this 的存在使 .map() 能够访问数组的内容。 注意我们传递给 map 的是 double 函数而不是直接调用。因为 map 携带一个函数作为参数并将函数应用给数组的每一个元素。它返回一个包含了 double() 返回值的新的数组。 注意原始的 arr 值没有改变: 1arr; // [1, 2, 3] 方法链你也可以链式调用方法。方法链是指在函数返回值上直接调用方法的过程,在此期间不需要给返回值命名: 12const arr = [1, 2, 3];arr.map(double).map(double); // [4, 8, 12] 返回布尔值(true 或 false)的函数叫做 断言(predicate)。.filter() 方法携带断言并返回一个新的数组,新数组中只包含传入断言函数(返回 true)的元素: 1[2, 4, 6].filter(gt4); // [4, 6] 你常常会想要从一个列表选择一些元素,然后把这些元素序列化到一个新列表中: 1[2, 4, 6].filter(gt4).map(double); [8, 12] 注意:后面的文章你将看到使用叫做 transducer 东西更高效地同时选择元素并序列化,不过这之前还有一些其他东西要了解。 总结如果你现在有点发懵,不必担心。我们仅仅概览了一下很多事情的表面,它们尚需大量的解释和总结。很快我们就会回过头来,深入探讨其中的一些话题。 继续阅读 “高阶函数”… 接下来想要学习更多 JavaScript 函数式编程知识? 和 Eric Elliott 一起学习 JavaScript。 如果你不是其中一员,千万别错过! Eric Elliott 是 “JavaScript 应用程序设计” (O’Reilly) 以及 “和 Eric Elliott 一起学习 JavaScript” 的作者。 曾就职于 Adobe Systems、Zumba Fitness、The Wall Street Journal、ESPN、BBC and top recording artists including Usher、Frank Ocean、Metallica 等公司,具有丰富的软件实践经验。 他大多数时间在 San Francisco By Area ,和世界上最美丽的姑娘在一起。","tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://suncafe.cc/tags/JavaScript/"}]},{"title":"写给“老派” Web开发者的“现代”JavaScript指南","date":"2017-04-11T13:24:32.000Z","path":"2017/04/11/[译] 写给“老派” Web 开发者的“现代” JavaScript 指南/","text":"原文地址:Modern JavaScript for Ancient Web Developers 原文作者:Gina Trapani 译文出自:掘金翻译计划 译者:suncafe 校对者:xilihuasi、 Reid 用 JavaScript 学习 JavaScript。图片来自 learnyounode。 有这样一种守旧的后端 web 开发者,他们很久以前就掌握了诸如 Perl 、Python、PHP 或 Java Server Pages 一类的东西,甚至还掌握了 Rails 或者 Django。他们使用巨大的关系型数据库构建 JSON API 服务,呃甚至是 XML。 他是个后端开发者, 因此对他而言,JavaScript 一直只是个可以添加一些前端花招,使网页上的东西变色的有趣小玩具。如果说 JavaScript 真的很有用,那也不过是给表单添加验证,以防止错误的数据进入数据库。八年前 jQuery 让这个人十分震惊。JavaScript 本身依然是可以被容忍但从未被接纳的语言。 随后 JavaScript 及其现代框架侵蚀了后端、前端和他们之间的一切,对于 JavaScript 开发者而言,2017年正是重新成为一个全新 web 开发者的时刻。 Hi.我是一个正在学习现代 JavaScript 的“老派” web 开发者。我才刚刚起步玩得也还算尽兴,当然也踩了一些坑。有一些现代 JavaScript 的概念我希望我能在开始学习之前就融会贯通。 在旧编程语言的惯性思维模式之上学习一个新的生态系统,我在心态和期望方面得做下面一些改变。 转移目标 (.jS)现代 JS 的特点就是朝气蓬勃和发展迅速,所以很容易就选择了过时的框架、模板引擎、构建工具、 教程或者已经不是最佳实践的技术。(如果真有一个被广泛接受的最佳实践的概念的话) 这种情况下,就有必要向你身边的 JavaScript 工程师朋友伸手求助了,和他们聊一聊你的技术路线。我很荣幸在 Postlight 得到了工程师朋友(特别是 Jeremy Mack)的精湛指导,感谢他们容忍我无穷无尽的问题。 我要说的是,学习现代的 JavaScript 需要人为干预。事物还在不断发展变化,各种教程尚未成熟和定型,所谓最佳实践也未形成正式规范。如果你身边没有大牛,那么至少也得检查 Medium 上文章或教程的日期,或 GitHub 仓库的最近一次提交时间。如果时间超过了一年,基本上可以确定已经过时。 新的问题,而不是已经确定的解决方案走类似这样的路线:当你在学习现代 JavaScript 时,你遇到的问题的解决方案还在渐渐得到解决,这正是一个好机会。事实上,很可能仅仅差一次 code review,你在使用这个包时就可以修复问题。 当你在使用一种像 PHP 这样的古老的语言的时候,你可以 Google 一个提问或者问题,几乎百分之百能找到一个 5 年前的 Stack Overflow 回答来解决它,或者你能在(详尽的、大量评论的、无与伦比的)文档里找到整个描述。 现代 JavaScript 就并非如此了。 我曾经徜徉在 GitHub issues 和源码的时候不止一次找到的都是一些过时的文档。剖析 GitHub 版本库是学习和使用各种包的一部分,而且对于我这样的“老派人”,差之毫厘的学习总是令人迷惑。 工具过载在 2017 年学习 JavaScript 还有另一个不一样的地方:创建程序花费的时间感觉和写应用的时间一样多。需要以“正确的方式”去做的工具、插件、软件包和依赖以及编辑器配置和构建配置所需的绝对数量足以使你在启动项目之前望而却步。 不要因此止步不前。我不得不放手去做,从起步到正确配置,允许自己的不完美甚至一些业余,只为舒适地使用自己的工具。(我不会告诉你我曾用 nodemon 做代码检查)随后我会找到更好的方法并且在每个新项目中纳入进来。 这方面 JS 还有大量的工作要做。现代 JavaScript 领域依然是不断变化的,但我一个现代 JS 工程师亲友告诉我,这份来自 Jonathan Verrecchia 的教程是目前构建一个当代 JavaScript 栈的不二之选。对,就是现在。 教程 / 项目 / 舍弃 / 重复无论学习什么语言都要经历写代码 - 舍弃 - 写更多代码这个过程。我的现代 JavaScript 学习经历已经成为一个个教程组成的阶梯,然后做一个小巧简单的项目,期间总结出现的疑问和困惑列出清单。然后和我的同事碰头以获得答案和解释,然后刷更多的教程,然后做一个稍微大一些的项目,更多的问题,再碰头,如此重复。 这是迄今为止我在这个过程中经历过的一些研讨会和教程的不完整列表。 HOW-TO-NPM —— npm 是 JavaScript 的包管理器。即使在学习这个教程之前我已经敲打过上千次 “npm install”,但是知道学完这个我才知道 npm 做的所有事情。(在很多项目中我已经转移使用 yarn,而不是 npm,但所有的概念都是相通的) npm i -g how-to-npm learnyounode——我打算专注于服务端 JavaScript,因为那有令我安逸的东西,那就是 Node.js。Learnyounode 是一个交互式教程,结构上类似 how-to-npm。 expressworks —— 和前面两个项目类似,Expressworks 是 Express.js 的介绍,一个 Node.js 的 web 框架。在 Postlight 公司 Express 没有得到广泛使用,但对于初学者,它值得学习去上手构建一个简单的 web 应用。 现在是时候做点真东西了。我发现 Tomomi Imura 的一篇教程 Creating a Slack Command Bot from Scratch with Node.js 已经可以学到足够的 Node 和 Express 的新技能来应对工作。因为我专注于后端,使用 Slack 创建一个 “/” 命令是一个很好的开始,因为没有前端演示(Slack 帮你做好了) 在构建这个命令的过程中,我不使用演练中所推荐的 ngrok 或者 Heroku,而是使用 Zeit Now,这是任何人可用的、创建快速一次性的 JS 应用的宝贵工具。 一旦开始写真正意义的代码,我也开始掉下工具无底洞了,安装 Sublime 插件,获取正确的 Node 版本,配置 ESLint,使用 Airbnb 的代码规范 (Postlight 公司的偏好) —— 这些事情拖了我的后退,但也都是有价值的初始化投资。对于这方面我还在坑里,例如 Webpack 对我来说依然美妙又神秘,不过这个视频是个很不错的介绍. 某些时候 JS 的异步执行(特别是回调地狱)开始困扰我,Promise It Won’t Hurt 是另一个教你怎样使用 Promise 书写优雅异步逻辑的教程。Promise 是用于解决异步执行的 JS 新概念。说实话 Promise 令我耳目一新,他们是巧妙的范式转变。感谢 Mariko Kosaka,现在我每次买汉堡的时候都能想起这些。 burger.resolve() — 图片来自 The Promise of a Burger Party. 我知道在这会陷入各种各样的麻烦,比如尝试使用 Jest 测试,使用 Botkit 让 Slack 机器人更有趣,使用 Serverless 真正打破函数式编程的价值。如果你不知道这些是什么意思,其实也没关系。这是一个大世界,我们都有属于自己的路要走。 “首先做,然后做对,然后做得更好.”最后这件最重要的事我一定要提起:不断去做就是学习的过程,做得很糟糕?那也是学习的过程。 这年头学习现代 JavaScript 感觉就像是在不知所以然得做无用功。当你在想有这么多时间搬搬砖不是更好吗的时候,Google 的 Addy Osmani 有个不错的建议 我鼓励人们采用这种方法来跟上 JavaScript 生态系统:首先做,然后做对,然后才是做得更好. […] 掌握任何新技能的基本要求都需要时间,实践和技巧。如果不使用每日一库或者响应式学习,容易产生挫败感。学会正确使用 Babel 和 React 花费了我数周时间,学习 Isomorphic JS,WebPack 和其他所有相关的库花了更多的时间。 简简单单地开始并且从基础做起就好. 这里感谢 NodeSchool 和 Free Code Camp,帮助初学者学习 JavaScript 的两个神奇的网站.","tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://suncafe.cc/tags/JavaScript/"}]},{"title":"搜索结果页的最佳实践","date":"2017-03-28T13:09:42.000Z","path":"2017/03/28/[译]搜索结果页的最佳实践/","text":"原文地址:Best Practices for Search Results 原文作者:Nick Babich 译文出自:掘金翻译计划 译者:suncafe 校对者:iloveivyxuan、Graning 搜索就像是用户和系统之间的一次对话:用户用一次查询来表达他们需要的信息,而系统用一组结果做为回应。搜索结果页恰恰是整个搜索体验中的一个关键部分:它提供了让用户参与对话的机会,来指导用户的信息需求。 这篇文章中,我愿意分享 10 个最佳实践,来帮助你提升搜索结果页的用户体验。 1. 点击搜索按钮后,不要清除用户的查询内容保留用户输入的原始文字。 再次查询是信息检索中关键的一步。如果用户没有找到他们想要的信息,他们可能会把一部分查询内容改为更清晰的关键词再搜索一遍。为了方便用户进行查询,在搜索框中留下初始的关键词,让用户不至于重复输入。 2. 提供准确而且相关的搜索结果搜索结果的第一页是黄金位置。 搜索结果页是一次搜索体验最核心的地方,它可以提升一个网站的转化率也可以毁掉它。通常用户可以基于一两组搜索结果就可以快速判断一个网站是否存在价值。 将准确的结果返回给用户显然非常重要,否则他们将不再相信这个搜索工具。所以你的搜索工具必须以合理的方式确定结果的优先级,并把所有重要的结果放置在第一页。 3. 使用有效的自动提示无效的自动提示会让搜索体验大打折扣。 请确保自动提示是有效的。当用户输入文字时,像识别词根、预测文本、搜索建议都是一些对用户很有帮助的功能。这些做法有助于加快搜索进度,并让用户在跳转间依旧保持工作状态。 图片来源: ThinkWithGoogle 4. 纠正拼写错误打字本来就很容易出错。 如果用户错误的输入了搜索关键词,而你可以检测到这个错误,那么可以针对系统猜测或“更正”后的关键词来显示搜索结果。这样就避免了由于没有返回结果,用户不得不再次输入关键词的尴尬。 不支持搜索重组的苹果商店上没有搜索到结果页面。 Asos 网站在用户打字错误时,很好地显示了一组代替结果来避免激怒用户。它会这样提示用户:“您的初始搜索为 ‘Overcoatt’,我们也为您搜索了‘Overcoats’的相关结果” 5. 显示搜索结果的数量显示相关搜索结果的数量,让与用户能够知道他们大概会花费多长时间来浏览这些搜索结果。 相关结果数量能够让用户更清楚如何进行再次搜索。 6. 保留用户最近的搜索记录即使用户很熟悉搜索引擎的功能,搜索这件事仍然需要用户从他们的记忆里唤起信息。为了想出一个有意义的关键词,用户需要考虑到他要查找的目标所具有的相关属性,并将它们融合到查询条件中。设计搜索体验时,你应该时刻记住基本的可用性原则: 尊重用户的努力 你的网站应该 保存所有最近的站内搜索记录, 当用户下一次创建搜索的时候把这些记录提供给他们. 保存最近搜索记录的好处是用户再次搜索同样的内容时可以节约他们的时间和精力。 提示: 提供的条目不要超过 10 个 (并且不要有滚动条) 这样不会让用户觉得信息过载。 7. 选择合适的页面布局显示搜索结果的一个挑战是不同的页面内容需要不同的布局来支撑。内容展现最基本的两种布局分别是列表视图和网格视图。一个经验法则: 列表用于详情展示,网格用于图片展示 不妨一起在产品页面中验证一下这个法则。这时产品的细节特征在就显得尤为重要了。对于类似家用电器这样的产品,诸如型号、评级和尺寸等 细节 是用户在 选择购买过程中 关注的重要因素,因此列表视图更有意义。 列表布局更适合细节导向的布局 对于一些 需要更少的产品细节信息 的产品,网格视图 是一个更好的选择。比如服装这样的产品,用户在挑选产品的过程中对文字描述信息不会太关心,而是依赖于 产品外观 做决定。对于这类产品用户更关心产品间的视觉差异,并且更愿意在一个长页面上来回滚动挑选,而不是在一个列表页和产品详情页面里反复切换。 网格布局更适合视觉导向的布局 提示: 允许用户为搜索结果选择“列表视图”或“网格视图”,让用户选择他们自己更期望的方式来浏览他们的查询结果。 允许用户通过一个功能菜单来更改布局 设计网格布局的时候,选择一个合适的图片尺寸,既要足够大到清晰识别细节,又要足够小到让用户一次尽量看到更多的条目。 8. 显示搜索进度理想状况下,搜索结果应该 立即 显示,但如果做不到,应该使用进度条来为用户提供系统的反馈。你应该给你的用户一个清晰的指示,让他们知道还要等待多久。 Aviasales 网站提示用户 搜索需要一些时间 提示: 如果搜索过于耗时,你可以使用动画. 好的动画能够分散访客的注意力,让他们忽略漫长的等待。 9. 提供排序和筛选的选项用户搜索返回的结果和关键词相关度较低或者结果太多的时候,他们往往感觉很迷茫。你应该提供给用户一些与其搜索相关的筛选选项,并且在他们应用筛选选项的时候要支持多选。 筛选选项可以帮助用户减少搜索结果并对其排序,不然会需要大量的(过多的)滚动和分页。 提示: 不要给用户过多的筛选选项这一点很重要。如果你的搜索需要大量的筛选,应该为它们设定默认值。 不要在筛选功能中隐藏排序功能,它们是不一样的。 当用户限制了搜索范围,在搜索结果页的顶部要明确说明这这个范围。 10. 不要反馈 “没有找到相关结果”把一个没有搜索结果的页面丢给用户会令他很沮丧。如果他们搜索了多次都返回这样的结果那就更过分了。 当它们的搜索没有匹配到结果时 应该避免让他们陷入死胡同 ,为他们提供有价值的替代品。(例如,网店可以从相似类别的商品中为用户推荐替代商品) 惠普网站的“没有找到相关结果”页就是一个死胡同的例子。它与在无结果页面上显示有价值的替代品的页面形成鲜明对比,例如亚马逊的页面。 结论搜索引擎是构建一个优秀网站的关键要素。用户在寻找和学习事物时期望一个流畅的体验,而且他们通常基于一两组搜索结果的质量对网站的价值做出非常快速的判断。一个优秀的搜索工具应当能够帮助用户快速而简单地查找他们想要的结果。 谢谢!","tags":[]},{"title":"我是怎么思考前端工程的","date":"2016-12-16T17:05:42.000Z","path":"2016/12/17/我是怎么思考前端工程的/","text":"这是公司技术分享会上的分享笔记 前端目标: 对现有优秀框架的了解与整合使用 根据项目的业务特点构建出合适的开发模式 设计前端测试方案保证代码质量 用工程化方案组织起团队的开发流程。 发展历程 历程 经历 WEB 1.0 官网 php asp jsp 交易平台 ajax富应用时代 交易平台后台 工程化大前端 万有 VR、可穿戴设备、智能家电… 拉仇恨定律:任何能够用JavaScript实现的应用系统,最终都必将用JavaScript实现。 —— Atwood 痛点 代码耦合,前后端协作不顺畅 前端工作流程繁琐 代码复用性差,服务器压力大,后端API不通用 首屏时间长,javascript代码增量大 状态难以管理 路由控制 组件化 依赖难以管理 新语法的兼容性(浏览器) 可维护性(全局变量) 前端专属的开发服务器 前端测试 万有前端的解决方案 面向企业 管理系统 前后端分离 webpack自动构建 SPA => webpack打包 按需加载 react react-router Redux(flux) npm babel es6 模块化 webpack-dev-server karma react 组件化使应用程序更易于开发和维护学习曲线平缓,核心 API 简洁清晰,易于学习JSX 语法不落俗套,充分发挥了 JavaScript 的能量天生适配 Flux 和 Redux社区活跃且具有创造力,奉献了诸多优秀的开发工具单向数据流比双向数据绑定的方式更适合复杂应用程序,质量更高支持服务端 webpack 通过配置可以应对各种情况支持主流的模块加载方式(AMD,CommonJS,globals)内部机制可以修复破损的模块可以处理 CSS全面的缓存系统支持热重载可以加载大多数的资源提供高效的性能优化方案 改进: jquery (fetch)react-bootstrapstate (redux)test (karma)动画","tags":[]},{"title":"my beatiful lover [Prisma]","date":"2016-12-07T15:56:02.000Z","path":"2016/12/07/相册/","text":"","tags":[{"name":"照片","slug":"照片","permalink":"http://suncafe.cc/tags/照片/"}]},{"title":"JavaScript 数据类型与数据结构","date":"2016-05-02T15:57:00.000Z","path":"2016/05/02/JavaScript 数据类型和数据结构/","text":"Javascript 是一种弱类型语言 在程序运行过程中,类型会被自动确定同一个变量,可以保存不同类型的数据 12345var a=1;var a="1";var a=true;var a={};var a=[]; 数据类型 原始类型 Boolean Null Undefined String Number Symbol //es6 Object 原始值 除 Object 以外的所有类型都是不可变的(值本身无法被改变)。如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变。我们称这些类型的值为“原始值”。 数字类型注意事项 JavaScript 中只有一种数字类型:基于 IEEE 754 标准的双精度 64 位二进制格式的值(-(263 -1) 到 263 -1)。它并没有为整数给出一种特定的类型。除了能够表示浮点数外,还有一些带符号的值:+Infinity,-Infinity 和 NaN (非数值,Not-a-Number)。 1242 / +0; // Infinity42 / -0; // -Infinity 对象 在计算机科学中, 对象是指内存中的可以被 标识符引用的一块区域. 标准对象 一个 Javascript 对象就是键和值之间的映射. 函数 函数是一个附带可被调用功能的常规对象。 日期 当你想要显示日期时,毋庸置疑,使用内建的 Date 对象。 数组 数组是一种使用整数作为键(integer-key-ed)属性和长度(length)属性之间关联的常规对象。 结构化数据: JSON ,JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,来源于 JavaScrip 同时也被多种语言所使用。 JSON 用于构建通用的数据结构。 typeof 操作符 可以查询变量的类型 Type Result Undefined “undefined” Null “object” (see below) Boolean “boolean” Number “number” String “string” Symbol “symbol” Host object (provided by the JS environment) Implementation -dependent Function object (implements [[Call]] in ECMA-262 terms) “ function” Any other object “object”","tags":[]},{"title":"JavaScript 中的相等性判断","date":"2016-05-02T15:12:00.000Z","path":"2016/05/02/JavaScript 中的相等性判断/","text":"相等性判断操作符 === 严格相等,比较时进行类型转换 == 非严格相等,如果类型不同会直接返回 false Object.is 在三等号判等的基础上特别处理了 NaN 、 -0 和 +0 ,保证 -0 和 +0 不再相同,但 Object.is(NaN, NaN) 会返回 true。 这三个运算符的原语中,没有一个会比较两个变量是否结构上概念类似。对于任意两个不同的非原始对象,即便他们有相同的结构, 以上三个运算符都会计算得到 false 123456{}=={} //false[]==[] //false[1,2,3]==[1,2,3] //falsevar obj={}obj===obj //true 严格相等的注意事项1234567+0===0 //true-0===0 //trueNaN===NaN //false NaN与其他任何值都不相等var x=NaN;x !== x //true 非严格相等的注意事项 相等操作符比较两个值是否相等,在比较前将两个被比较的值转换为相同类型。在转换后(等式的一边或两边都可能被转换),最终的比较方式等同于全等操作符 === 的比较方式。 相等操作符满足交换律。 最好有限使用全等而不是相等,全等操作符的结果更容易预测,并且因为没有隐式转换,全等比较的操作会更快。 12345678910111213141516var num = 0;var obj = new String("0");var str = "0";var b = false;console.log(num == num); // trueconsole.log(obj == obj); // trueconsole.log(str == str); // trueconsole.log(num == obj); // trueconsole.log(num == str); // trueconsole.log(obj == str); // trueconsole.log(null == undefined); // trueconsole.log(obj == null);console.log(obj == undefined); Object.is Object.is 应该被认为是有其特殊的用途,而不应说他和其他的相等更宽松或严格。 12Object.is(NaN,NaN) //trueObject.is(-0,0) //false","tags":[]},{"title":"前端基础之HTTP的相关概念","date":"2016-05-01T11:20:00.000Z","path":"2016/05/01/前端基础之HTTP的相关概念/","text":"简介HTTP 是一种传输网络资源的协议,是web上数据交换的基础。 HTTP 是一种 client-server 协议,也就是说请求通常是由像浏览器这样的接受方发起的。 客户端和服务端通过交换各自的消息(与数据流正好相反)来进行交互。 requests 由像浏览器这样的客户端发出的消息 responses 被服务端回应的消息。 基于HTTP 的组件系统Client <==> Proxy <==> Proxy <==> Server 在一个浏览器和处理请求的服务器之间,还有计算机、路由器、调制解调器等等许多实体。 HTTP是在最上层应用层中的,虽然下面的层次对分析网络问题非常重要,但是对HTTP的描述来说,这些大多数都是不相关的。 客户端:user-agentuser-agent 通常由浏览器来扮演,浏览器作为发起一个请求的实体,获取这个页面的HTML文档,再解析它并根据文档中的资源信息发送其他的请求来获取脚本信息,或者CSS来进行页面布局渲染,还有一些其它的页面资源(如图片和视频等)。 然后,它把这些资源结合到一起,展现出来一个完整的文档,也就是网页。 Web服务端web Server 它可以是许多共同分担负载(负载平衡)的一组服务器组成的计算机群,也可以是一种复杂的软件,通过向其他计算机发起请求来获取部分或全部资源的软件。 Server不再只是一个单独的机器,它可以是在同一个机器上装载的许多服务之一。 Proxies在浏览器和服务器之间,有许多传输层、网络层和物理层的计算机和其他设备参与了HTTP 的转发 ,对于HTTP的应用层来说他们大多就是透明的,而还有一部分表现在应用层上的,就叫做proxies了 功能: 缓存 过滤 负载均衡,让多个服务器服务不同的请求 对不同资源的权限控制 登陆,允许存储历史信息 HTTP的特性 简单 可读 可扩展 (通过headers)前后端协商添加功能 无状态 cookies的使用可以创建有状态的会话 http cookies 是一种headers扩展 HTTP依赖于TCP进行消息传递 TCP是可靠的面向连接的传输层协议,http是应用层,网络层使用IP协议 打开一个TCP 链接很耗时,HTTP/1.1引入了持久链接的概念,默认使用长连接,Connection:keep-alive TCP连接的建立是需要三次握手的,而释放则需要4次握手所以每个连接的建立都是需要资源消耗和时间消耗的 短连接的操作步骤是:建立连接——数据传输——关闭连接…建立连接——数据传输——关闭连接 长连接的操作步骤是:建立连接——数据传输…(保持连接)…数据传输——关闭连接 HTTP 控制的常见特性HTTP良好的扩展性控制着越来越多Web的功能。 缓存 (cookeis) 开放同源限制 (cors) 认证 (Authenticate 或者 cookies) 代理 (例如SOCKS协议的代理) 会话 cookies HTTP 流 打开一个TCP连接 发送一个http报文 读取服务端返回的报文 关闭连接或者为以后的请求重用连接。 HTTP 报文在HTTP/2中,这些报文被嵌入到了一个新的二进制结构中-帧。帧可以允许实现很多优化,如复用和报文头部的压缩。即使只有原始HTTP报文的一部分以这种HTTP/2版本的方式发送出来,每个报文的语义依旧不变 request: HTTP的method 获取资源的路径 HTTP协议的版本号。 headers body response: HTTP的版本号 状态码 状态信息 HTTP headers body","tags":[]},{"title":"谈谈工具","date":"2016-04-08T14:33:32.000Z","path":"2016/04/08/谈谈工具/","text":"前一阵子Adobe公司发布了一款新软件叫Adobe XD,传言是Sketch的劲敌。Sketch也在设计圈里火了有段日子了,不过由于只有OS版本,在国内难以达到UI标配的程度。然而显然PS在界面设计方面是有点臃肿的,UI行业火爆到开始平息的时候新软件的出现为时不晚。如果如果XD发布windows版本,那么Sketch就真的有成为过客的危险了。 高中毕业时候我在书店里买了一本Photoshop教程书,那时谈不上设计,但那时已是我接触生产力工具的启蒙,我被复杂的界面所倾倒,被变换的像素所震撼,被未知的强大所吸引。掰掰手指已是四年多,CS456到CC软件也算是伴随着我成长。如今我做前端的工作,每天面对更多的是文本编辑器和命令行工具,PS打开的时候越来越少了,这让我总有些不舍。 如今的互联网行业真的是日新月异,各类开发设计原型巴拉巴拉的工具比比皆是群芳争艳乐此不疲。这其中自然也不乏开天辟地的神作。所有的工具都凝聚了开发人员的心血与智慧,然而并不是所有工具都适合你,选择自然是个问题。1.最重要的你要做的是什么2.有什么可选择你问google3.别被学习曲线吓到也别死磕4.计算时间成本 本想总结一下前端开发工具然而发现善良的好多网友总结的比我的全的多,那么我来开启传送门。http://www.fefork.com/fetool/感谢@nieweidong的前端工具集。 另外今晚最重要的一句:真诚的向程序开发人员致敬!","tags":[{"name":"工具","slug":"工具","permalink":"http://suncafe.cc/tags/工具/"}]},{"title":"今天是个值得庆祝的好日子","date":"2016-04-07T16:38:59.000Z","path":"2016/04/08/今天是个值得庆祝的好日子/","text":"原因很简单,因为我开启了hexo新篇章,哈哈哈 还要练习使用markdown呢,希望自己坚持下去 这篇博客呢,主要是记录一下自己的前端学习笔记,以及自己想说的话 万事开头难,今天折腾了好久终于把博客搭建起来,感谢google 感谢网上所有无私奉献的人们 千里之行始于足下,滴水穿石巴拉巴拉~ Hello word!","tags":[{"name":"碎碎念","slug":"碎碎念","permalink":"http://suncafe.cc/tags/碎碎念/"}]},{"title":"Hello hexo","date":"2016-04-07T12:53:47.000Z","path":"2016/04/07/hello-world/","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment","tags":[]},{"title":"关于XMLHttpRequest","date":"2016-03-11T11:41:00.000Z","path":"2016/03/11/关于xmlhttprequest的一切/","text":"简介XMLHttpRequest 也可以简称 xhr,它是浏览器提供的 webAPI,是一个 JavaScript 对象,我们熟知的 ajax,axis 等第三方库均是对 xhr 的封装。XMLHttpRequest 虽然叫 XMLHttpRequest ,但是其支持传输所有类型的数据资源 ; 并且除了HTTP ,它还支持file 和 ftp协议。 使用方法 tips:建议学习 webAPI 的时候打开console 敲一敲增进理解,例如 输入 window 执行,可以看到 window对象的所有api window.XMLHttpRequest 是一个构造函数 ,在使用xhr的接口前要先实例化一个xhr 对象 12var xhr = new XMLHttpRequest;console.log(xhr) 我们可以看到xhr所有的属性和方法被打印出来 123456789101112131415161718//超时时间xhr.timeout = 3000;//返回的数据格式xhr.responseType = "text";//创建一个 post 请求,地址 ,异步xhr.open('POST', '/server/api', true);//注册相关事件回调处理函数xhr.onload = function(e) { if(this.status == 200||this.status == 304){ console.log(this.responseText); }};xhr.ontimeout = function(e) { ... };xhr.onerror = function(e) { ... };xhr.upload.onprogress = function(e) { ... };//发送请求xhr.send(formData); 到此为止,一个xhr请求被我们发送到服务端了","tags":[]},{"title":"一起看文档JSON.tringify","date":"2016-03-08T08:27:00.000Z","path":"2016/03/08/json-stringfy-想到的/","text":"JSON 是浏览器内置对象 共携带两个常用方法 tringify 和 parse ; JSON.stringfy是处理异步数据常用到的方法;有时候ajax请求服务端要求传输标准的JSON数据,如果我们直接传输 JavaScript 对象可能会收到后台报错 ;那么我们需要将对象转换成标准的json格式在放在data里面发送。 1data:JSON.stringify(data) 但是有很多童鞋和我一样都知道stringify接收对象作为参数却不知道其实stringify还接收另外两个参数 在用 react 写demo的时候有一个有发送表单的场景 1234567891011getInitialState(){ return { a:1, b:2, c:3, d:4 } }ajax({ data:JSON.stringify(state)}) 但是如果我们只想传递abc 而不想传输 c d 呢? 基础的初学者这样写 123456ajax({ data:JSON.stringify({ a:state.a, b:state.b })}) 代码非常的冗余 如果了解过JSON.stringify的第二个参数的话其实可以这样写 123ajax({ data:JSON.stringify(data,["a","b"])}) 有没有清爽多了呢 ,第三个参数不多说 快去翻文档吧 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify","tags":[]}]