Posts List

在项目中启用 ESLint 与 Stylelint 检查

多人参与的项目中,经常会多人交叉编辑多个文件。这就导致了各个文件中充斥着各种编码风格。 最最常见的就有: 使用空格还是Tab 一次缩进是 2 个空格还是 4 个空格 还是 8 个空格 字符串使用单引号还是双引号 JavaScript 一行结尾到底要不要加分号 关键字前后要不要加空格 等等等等 不同风格混杂在一起极大的影响代码的可读性与质量。因此在多人项目中维护一致的代码风格就很重要了。 本文就简单介绍一下如何通过 ESLint 和 Stylelint 为代码库配置针对 JavaScript 与 CSS(SCSS)的代码风格检查。 配置 ESLint 安装 eslint yarn add --dev eslint // or npm install --save-dev eslint 创建配置文件 ./node_modules/.bin/eslint --init 该命令会在项目目录中创建一个 .eslintrc.json 文件。默认生成的配置文件可能看起来是这个样子: { "rules": { "semi": ["error", "always"], "quotes": ["error", "double"] } } rules 里定义的就是你想要检查的规则了。例如上边示例中就配置了分号和引号的使用。 完整的配置文档可以在这里找到。 配置 npm script 配置好以后就可以通过命令

Service Worker 基础知识

本文旨在帮你快速了解 service worker 的基本知识点,让你对 service worker 有一个大致的了解。为以后开发一个简单的 PWA 应用打下基础。 关于 service worker 的几个基本知识点 它是一个可编程的网络代理,让你可以控制页面请求的处理方式。 它是一个 JavaScript Worker,因此它无法直接操作 DOM。但可以通过 postMessage 接口与页面通信。同时,service worker 中的代码不会阻塞页面响应。 它在闲置时被终止,在需要时被启动。并不是常驻内存。因此你不能在 onfetch 或是 onmessage 回调中依赖全局状态。 被设计成完全异步。因此在 service worker 中无法使用同步 API (例如同步 XHR,localStorage等)。接口重度依赖于 promise。 只能在 HTTPS 页面加载(唯一的例外:localhost/127.0.0.1,方便调试)。 Service worker 的作用域 一个 service worker 的默认作用域是这个 service worker 脚本所在的目录。例如 https://example.com/sw.js 脚本默认就是 https://example.com 下的所有页面。 你也可以在注册 service worker 时明确指定作用域: navigator.serviceWorker.register('sw.js', { scope: './abc' }); 假设以上代码在 https://example.com 页面里执行,则意味着该 service worker 的作用域就是 https://example.

[译] 在大型应用中使用 Redux 的五个技巧

Redux 是一个很棒的用于管理应用程序“状态”的工具。单向数据流以及对不可变数据的关注使得推断状态的变化变得很简单。每次状态变化都由一个 action 触发,这会导致 reducer 函数返回一个变更后的新状态。由于客户要在我们的平台上管理或发布广告资源,在 AppNexus 使用 Redux 创建的很多用户界面都需要处理大量数据以及非常复杂的交互。在开发这些界面的过程中,我们发现了一些有用的规则和技巧以维持 Redux 易于管理。以下的几点讨论应该可以帮助到任何在大型,数据密集型应用中使用 Redux 的开发者: 第一点: 在存储和访问状态时使用索引和选择器 第二点: 把数据对象,对数据对象的修改以及其它 UI 状态区分开 第三点: 在单页应用的不同页面间共享数据,以及何时不该这么做 第四点: 在状态中的不同节点复用通用的 reducer 函数 第五点: 连接 React 组件与 Redux 状态的最佳实践 1. 使用索引保存数据,使用选择器读取数据 选择正确的数据结构可以对程序的结构和性能产生很大影响。存储来自 API 的可序列化数据可以极大的受益于索引的使用。索引是指一个 JavaScript 对象,其键是我们要存储的数据对象的 id,其值则是这些数据对象自身。这种模式和使用 hashmap 存储数据非常类似,在查询效率方面也有相同的优势。这一点对于精通 Redux 的人来说不足为奇。实际上,Redux 的作者 Dan Abramov 在它的 Redux 教程中就推荐了这种数据结构。 设想你有一组从 REST API 获取的数据对象,例如来自 /users 服务的数据。假设我们决定直接将这个普通数组存储在状态中,就像在响应中那样。当我们需要获取一个特定用户对象时会怎样呢?我们需要遍历状态中的所有用户。如果用户很多,这可能会是一个代价高昂的操作。如果我们想跟踪用户的一小部分,例如选中和未选中的用户呢?我们要么需要把数据保存在两个数组中,要么就要跟踪这些选中和未选中用户在主数组中的索引(译者注:此处指的是普通意义上的数组索引)。 然而,我们决定重构代码改用索引的方式存储数据。我们可以在 reducer 中以如下的方式存储数据: { "usersById": { 123: { id: 123, name: "Jane Doe", email: "jdoe@example.

[译] Node.js 流: 你需要知道的一切

图片来源 Node.js 中的流有着难以使用,更难以理解的名声。现在我有一个好消息告诉你:事情已经不再是这样了。 很长时间以来,开发人员创造了许许多多的软件包为的就是可以更简单的使用流。但是在本文中,我会把重点放在原生的 Node.js 流 API上。 “流是 Node 中最棒的,同时也是最被误解的想法。” — Dominic Tarr 流到底是什么呢? 流是数据的集合 —— 就像数组或字符串一样。区别在于流中的数据可能不会立刻就全部可用,并且你无需一次性的把这些数据全部放入内存。这使得流在操作大量数据或是数据从外部来源逐段发送过来的时候变得非常有用。 然而,流的作用并不仅限于操作大量数据。它还带给我们组合代码的能力。就像我们可以通过管道连接几个简单的 Linux 命令以组合出强大的功能一样,我们可以利用流在 Node 中做同样的事。 Linux 命令的组合性 const grep = ... // 一个 grep 命令输出的 stream const wc = ... // 一个 wc 命令输入的 stream grep.pipe(wc) Node 中许多内建的模块都实现了流接口: 截屏来自于我的 Pluralsight 课程 —— 高级 Node.js 上边的列表中有一些 Node.js 原生的对象,这些对象也是可以读写的流。这些对象中的一部分是既可读、又可写的流,例如 TCP sockets,zlib 以及 crypto。 需要注意的是这些对象是紧密关联的。虽然一个 HTTP 响应在客户端是一个可读流,但在服务器端它却是一个可写流。这是因为在 HTTP 的情况中,我们基本上是从一个对象(http.IncomingMessage)读取数据,向另一个对象写入数据(http.ServerResponse)。 还需要注意的是 stdio 流(stdin,stdout,stderr)在子进程中有着与父进程中相反的类型。这使得在子进程中从父进程的 stdio 流中读取或写入数据变得非常简单。

正则表达式中的悲观回溯

前几天有小伙伴来求救说页面上有一个 input 框,随着用户不断输入内容,页面响应会越来越慢直到完全失去响应。 简单沟通过后得知具体场景是这样的: input 框中允许用户输入一连串逗号分隔的商品id 在用户输入的过程中实时检测用户输入的内容是否符合规则,若不符合则给出提示信息 小伙伴的解决方案也很直接: 给 input 框绑定 keyup 事件。 在 keyup 事件回调函数中通过正则表达式判断是否符合规则,决定是否展示提示信息。 经过反复验证得到如下规律: 用户在输入商品 id 的过程中(连续输入多个数字)不会卡顿 当用户输入逗号时,出现卡顿。随着输入商品 id 的数量增加,卡顿越来越明显,直至浏览器失去响应。 于是打开 Chrome 开发者工具,选择 Performance (原 Timeline) 标签页。将整个过程记录下来,得到如下时间线: 其中黄色宽条表示 JavaScript 主线程的执行情况。连续的黄条越长,表示单次 JavaScript 运行的时间越长。也就意味着 UI 失去响应的时间越长。这一点从截图中的蓝色框中也可以得到印证。蓝色框中的红色长条表示浏览器一帧(一次渲染)所需要的时间。 那么到底是 JavaScript 中的哪些代码占中了这么长 CPU 时间呢?我们在底部的选项卡中选中 Bottom-Up ,按 Total Time 降序排列。得到如下结果: 可以看出,72.% 的 CPU 时间用在了一条正则表达式上。你肯定想到了,这就是小伙伴用来检查用户输入是否合法的正则表达式。 完整的正则表达式是这样的: /^\s*((\d+(\,|,)\d+)*|(\d+))\s*$/ 接着去 regex101 上测试一下,测试数据如下,由 10 个商品 ID 组成的字符串: 123456789,123456789,123456789,123456789,123456789,123456789,123456789,123456789,123456789,123456789 执行结果如下:

[译] 6个Async/Await完胜Promise的原因

原文地址:https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9 友情提醒:NodeJS自从7.6版开始已经内置了对async/await的支持。如果你还没用过该特性,那么接下来我会给出一系列的原因解释为何你应该立即开始使用它并且会结合示例代码说明。 async/await快速入门 为了让还没听说过这个特性的小伙伴们有一个大致了解,以下是一些关于该特性的简要介绍: async/await是一种编写异步代码的新方法。在这之前编写异步代码使用的是回调函数和promise。 async/await实际是建立在promise之上的。因此你不能把它和回调函数搭配使用。 async/await和promise一样,是非阻塞的。 async/await可以使异步代码在形式上更接近于同步代码。这就是它最大的价值。 语法 假设有一个getJSON方法,它返回一个promise,该promise会被resolve为一个JSON对象。我们想要调用该方法,输出得到的JSON对象,最后返回"done"。 以下是使用promise的实现方式: const makeRequest = () => getJSON() .then(data => { console.log(data) return "done" }) makeRequest() 使用async/await则是这样的: const makeRequest = async () => { console.log(await getJSON()) return "done" } makeRequest() 使用async/await时有以下几个区别: 在定义函数时我们使用了async关键字。await关键字只能在使用async定义的函数的内部使用。所有async函数都会返回一个promise,该promise最终resolve的值就是你在函数中return的内容。 由于第一点中的原因,你不能在顶级作用域中await一个函数。因为顶级作用域不是一个async方法。 // this will not work in top level // await makeRequest() // this will work makeRequest().then((result) => { // do something }) await getJSON()意味着直到getJSON()返回的promise在resolve之后,console.

Webpack2中的NamedModulesPlugin与HashedModuleIdsPlugin

要讨论Webpack 2中新增的这两个plugin的功能,还要先从使用Webpack打包的项目的前端资源缓存方案说起。 通常在使用了Webpack的项目中我们会使用CommonsChunkPlugin来将所有依赖的第三方包打包到一个名为vender的chunk中。与此同时,为了避免每次更改项目代码时导致vender chunk的chunkHash改变,我们还会单独生成一个manifest chunk。 举个例子,假设我们有一个项目,项目中入口文件为index.js。其内容如下: import add from './src/add'; import leftPad from 'left-pad'; import jsonp from 'jsonp'; add(1, 2); 通常我们的webpack.config.js文件就会有类似如下的配置: const path = require('path'); const webpack = require('webpack'); module.exports = { entry: { 'app': './index.js', 'vender': ['left-pad', 'jsonp'] }, output: { filename: '[name].[chunkHash].js', path: path.resolve(__dirname, 'build') }, resolve: { extensions: ['.js'] }, module: { ... }, plugins:[ new webpack.optimize.CommonsChunkPlugin({ name: ['vender', 'manifest'], minChunks: Infinity, }) ] }; 这时,通过Webpack打包,会生成三个文件:

在JavaScript项目中锁定npm依赖包版本

问题 最近在项目中遇到这样一个问题,webpack生成的vender包的哈希值在我和同事的电脑上不一致。由于之前已经配置过了CommonsChunkPlugin(配置如下),所以我们期望的结果是在不同环境下构建出的文件哈希值应该是一致的。 注:只给出了和本文内容相关的配置项 module.exports = { entry: { app: './src/js/app.jsx', vender: [ 'classnames', 'react', 'react-dom', 'redux', 'react-redux', 'redux-promise-middleware', 'updeep', 'axios', 'jdc-rc-list', 'react-router', 'react-router-redux' ] }, output: { path: path.resolve(__dirname, './build/js/'), filename: (isDevelopmentEnvironment ? '[name].bundle.js' : '[name].[chunkhash].js'), chunkFilename: (isDevelopmentEnvironment ? '[name].bundle.js' : '[name].[chunkhash].js') }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: ['vender', 'manifest'], minChunks: Infinity, }) ] }; 调查 经过多次验证发现,我们在各自的电脑上多次构建出来的哈希值可以保持一致,只是在两台电脑上不一致。考虑到vender中打包的都是第三方依赖,于是猜测是两台电脑上安装的依赖包不同。接着对比了两人的package.json中的dependencies,完全一致。 考虑到package.json文件中包版本中的^符号匹配的范围比较大。参考npm官方给出的解释: ^1.2.3 := >=1.2.3 <2.0.0 ^0.2.3 := >=0.2.3 <0.3.0 ^0.0.3 := >=0.

React Conf 2017视频 百度网盘地址

React Conf 2017的全部视频已经在YouTube上放出来了。为了方便在家里的电视上播放,就全部下载了一套。顺便上传了百度网盘一份。感兴趣的可以看看哦。全部720p。 下载地址: http://pan.baidu.com/s/1gfl5FzX

Redux 3.6.0 源码阅读 之 src/createStore.js

嗯,就是学习一下,读读源码。顺便把源码里的文档翻译了一下并添加了一些简单的注释。 本文对应的文件是src/createStore.js import isPlainObject from 'lodash/isPlainObject' import $$observable from 'symbol-observable' /** * ActionTypes里定义的是Redux保留的私有action。 * 对于任何未知的action,你必须返回store的当前状态。 * 如果传入的当前状态是undefined,你必须返回store的初始状态。 * 不要在应用代码中直接引用这些action。 */ export var ActionTypes = { INIT: '@@redux/INIT' } /** * createStore方法用于创建一个保存程序状态的store。 * 改变store中数据的唯一方法是调用store的`dispatch()`方法。 * * 你的应用中应该只有一个store。为了将程序状态中不同部分的变更逻辑 * 组合在一起,你可以通过`combineReducers`方法将多个reducer组合成一个reducer。 * * @param {Function} reducer 一个返回应用下一状态的函数,入参是程序的当前状态以及 * 要发送的action。 * * @param {any} [preloadedState] store的初始状态。你可以选择性的为store指定一个 * 初始状态。 * 如果你使用了`combineReducers`方法来生成最终的reducer。那么这个初始状态对象的 * 结构必须与调用`combineReducers`方法时传入的参数的结构保持对应关系。 * * @param {Function} enhancer store增强器。你可以选择性的传入一个增强函数来扩展 * store的功能,例如中间件,时间旅行,持久化等。Redux自带的唯一一个增强器是 * `applyMiddleware()`方法。 * * @returns {Store} 返回一个可以读取状态,发送action以及订阅变更通知的Redux store。 */ export default function createStore(reducer, preloadedState, enhancer) { // 如果只传入reducer和enhancer,则store的初始状态为undefined if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } // enhancer必须是一个函数 if (typeof enhancer !