
本文介绍了 ES6 的基础概念和特性。 完整的 slide 在 这里。
在处理诸如 resize、scroll、mousemove 和 keydown/keyup/keypress 等事件的时候,通常我们不希望这些事件太过频繁地触发,尤其是监听程序中涉及到大量的计算或者有非常耗费资源的操作。 有多频繁呢?以 mousemove 为例,根据 DOM Level 3 的规定,「如果鼠标连续移动,那么浏览器就应该触发多个连续的 mousemove 事件」,这意味着浏览器会在其内部计时器允许的情况下,根据用户移动鼠标的速度来触发 mousemove 事件。(当然了,如果移动鼠标的速度足够快,比如“刷”一下扫过去,浏览器是不会触发这个事件的)。resize、scroll 和 key* 等事件与此类似。 可以参看这个 Demo 体会下。 Debounce DOM 事件里的 debounce 概念其实是从机械开关和继电器的“去弹跳”(debounce)衍生 出来的,基本思路就是把多个信号合并为一个信号。这篇文章 解释得非常清楚,感兴趣的可以一读。 在 JavaScript 中,debounce 函数所做的事情就是,强制一个函数在某个连续时间段内只执行一次,哪怕它本来会被调用多次。我们希望在用户停止某个操作一段时间之后才执行相应的监听函数,而不是在用户操作的过程当中,浏览器触发多少次事件,就执行多少次监听函数。 比如,在某个 3s 的时间段内连续地移动了鼠标,浏览器可能会触发几十(甚至几百)个 mousemove 事件,不使用 debounce 的话,监听函数就要执行这么多次;如果对监听函数使用 100ms 的“去弹跳”,那么浏览器只会执行一次这个监听函数,而且是在第 3.1s 的时候执行的。 现在,我们就来实现一个 debounce 函数。 实现 我们这个 debounce 函数接收两个参数,第一个是要“去弹跳”的回调函数 fn,第二个是延迟的时间 delay。 实际上,大部分的完整 debounce 实现还有第三个参数 immediate ,表明回调函数是在一个时间区间的最开始执行(immediate 为 true)还是最后执行(immediate 为 false),比如 underscore 的 _.debounce。本文不考虑这个参数,只考虑最后执行的情况,感兴趣的可以自行研究。 /** * * @param fn {Function} 实际要执行的函数 * @param delay {Number} 延迟时间,也就是阈值,单位是毫秒(ms) * * @return {Function} 返回一个“去弹跳”了的函数 */ function debounce(fn, delay) { // 定时器,用来 setTimeout var timer // 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 fn 函数 return function () { // 保存函数调用时的上下文和参数,传递给 fn var context = this var args = arguments // 每次这个返回的函数被调用,就清除定时器,以保证不执行 fn clearTimeout(timer) // 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作), // 再过 delay 毫秒就执行 fn timer = setTimeout(function () { fn.apply(context, args) }, delay) } } 其实思路很简单,debounce 返回了一个闭包,这个闭包依然会被连续频繁地调用,但是在闭包内部,却限制了原始函数 fn 的执行,强制 fn 只在连续操作停止后只执行一次。 debounce 的使用方式如下: $(document).on('mouvemove', debounce(function(e) { // 代码 }, 250)) 用例 还是以 mousemove 为例,为其绑定一个“去弹跳”的监听器,效果是怎样的?请看这个 Demo。 再来考虑另外一个场景:根据用户的输入实时向服务器发 ajax 请求获取数据。我们知道,浏览器触发 key* 事件也是非常快的,即便是正常人的正常打字速度,key* 事件被触发的频率也是很高的。以这种频率发送请求,一是我们并没有拿到用户的完整输入发送给服务器,二是这种频繁的无用请求实在没有必要。 更合理的处理方式是,在用户“停止”输入一小段时间以后,再发送请求。那么 debounce 就派上用场了: $('input').on('keyup', debounce(function(e) { // 发送 ajax 请求 }, 300)) 可以查看这个 Demo 看看效果。 Throttle throttle 的概念理解起来更容易,就是固定函数执行的速率,即所谓的“节流”。正常情况下,mousemove 的监听函数可能会每 20ms(假设)执行一次,如果设置 200ms 的“节流”,那么它就会每 200ms 执行一次。比如在 1s 的时间段内,正常的监听函数可能会执行 50(1000/20) 次,“节流” 200ms 后则会执行 5(1000/200) 次。 我们先来看 Demo。可以看到,不管鼠标移动的速度是慢是快,“节流”后的监听函数都会“匀速”地每 250ms 执行一次。 实现 与 debounce 类似,我们这个 throttle 也接收两个参数,一个实际要执行的函数 fn,一个执行间隔阈值 threshhold。 同样的,throttle 的更完整实现可以参看 underscore 的 _.throttle。 /** * * @param fn {Function} 实际要执行的函数 * @param delay {Number} 执行间隔,单位是毫秒(ms) * * @return {Function} 返回一个“节流”函数 */ function throttle(fn, threshhold) { // 记录上次执行的时间 var last // 定时器 var timer // 默认间隔为 250ms threshhold || (threshhold = 250) // 返回的函数,每过 threshhold 毫秒就执行一次 fn 函数 return function () { // 保存函数调用时的上下文和参数,传递给 fn var context = this var args = arguments var now = +new Date() // 如果距离上次执行 fn 函数的时间小于 threshhold,那么就放弃 // 执行 fn,并重新计时 if (last && now < last + threshhold) { clearTimeout(timer) // 保证在当前时间区间结束后,再执行一次 fn timer = setTimeout(function () { last = now fn.apply(context, args) }, threshhold) // 在时间区间的最开始和到达指定间隔的时候执行一次 fn } else { last = now fn.apply(context, args) } } } 原理也不复杂,相比 debounce,无非是多了一个时间间隔的判断,其他的逻辑基本一致。throttle 的使用方式如下: $(document).on('mouvemove', throttle(function(e) { // 代码 }, 250)) 用例 throttle 常用的场景是限制 resize 和 scroll 的触发频率。以 scroll 为例,查看这个 Demo 感受下。 可视化解释 如果还是不能完全体会 debounce 和 throttle 的差异,可以到 这个页面 看一下两者可视化的比较。 总结 debounce 强制函数在某段时间内只执行一次,throttle 强制函数以固定的速率执行。在处理一些高频率触发的 DOM 事件的时候,它们都能极大提高用户体验。 参考 Debouncing Javascript Methods The Difference Between Throttling and Debouncing Throttling function calls
本次 QCon 共有两个前端专场,分别是「前端工程实践」和「前端架构思想」;还有一个移动开发专场「移动开发实践」。我主要参加了这三个专场,每个专场分别有三至六篇演讲;另外也听了其他专场几个感兴趣的演讲,比如「编程语言」专场的 Clojure 和 JavaScript 两个议题。 以下是我参加的议题及心得总结: 持续集成之 Why、What & How 这是大会致辞后的第一场演讲,演讲者 Kohsuke Kawaguchi 是 Jenkins 项目创始人和主要开发者。正如题目所示,本场演讲解释了持续集成的作用和必要性,并在后半段给 Jenkins 打了不少时间的广告。 QQ 空间萌宠之舞——HTML5 骨骼动画实践 原理上,骨骼动画是利用建立好的骨架套用到一张或多张图片使之动作的动画技术,比起传统的逐格动画一张一张绘出动作省了很多时间与精力,而且能更生动地动作。一般都会借助一些第三方工具来创作骨骼动画,QQ 空间萌宠之舞也是如此。 这篇演讲还讲述了他们在实践过程中遇到的问题,以及解决方案,还提供了不少性能优化的经验。 Basement——蚂蚁 Web 研发流程和基础服务实践 这篇演讲是对蚂蚁前端的 Web 应用研发流程和基础服务平台 Basement 的介绍。讲述了在 Basement 上,如何做到将一次产品迭代缩减为四步的,即:开发 -> 测试 -> 预发 -> 发布;同时还介绍了 Basement 的技术架构及实现。 除此之外,演讲者还分享了很多思考和总结,比如“一切不以上生产环境为目的的造轮子都是耍流氓”,“记录比管控更重要”,等等。 微信 H5 视频播放器在海量业务下的实践 主要讲了两方面的内容: 微信公众号文章视频播放的技术方案和实现,如何保证系统可控; ⽇常运营如何保障系统的稳定性; 关于第一点,他们实现的 H5 视频播放还是很有意思的:在客户端本地起一个代理服务器拉取视频资源,同时将广告等功能作为“插件”来实现,然后统一由“调度器”来管理视频播放;稳定性方面,包括实时监控客户端的异常信息,以及开发了一款能够远端调试移动端页面的工具。 WebGL 在前端可视化中的实践 本篇讲的是 echarts 在使用 WebGL 当中的一些经验。涉及到不少计算机图形学的知识,听起来有些费解,不过图表效果还是非常炫目的,尤其是最后一张 3D 的星空图。 如何打造自己的 PWA 应用 关于 PWA,本篇演讲的作者不但作出了简单明了的解释,而且给出了具体的实践。 PWA 最大的特点是快速和离线,其技术核心是 serviceWorker。对于一款离线 App,如何上传数据,如何对数据进行预存,如何清理过期缓存,如何进一步优化性能等等问题,这篇演讲都给出了方案和代码实现。 除此之外,PWA 还有一个鲜明特点是可以添加到主屏桌面,那么如何添加?如何像原生应用那样通知?也有非常清晰的说明。 探究 Node.js 的服务端之路 这篇演讲作者根据他们自己团队的实践,主要讲述了 v8 引擎的内存结构,内存释放以及内存泄漏的各项情况。并讨论将 Node.js 引入服务端带来的优势与常见的问题及其解决方案,以及目前 Node.js 的意义。 内容非常丰富,但演讲者在演讲过程中对内存泄漏、fork 等几个问题讲得太琐碎细致,而且用时过长,导致后面的分布式、维护相关内容没有完全发挥,有些遗憾。 应用开发的未来 甲骨文公司产品副总裁 Boris Scholl 的演讲,主要是应用开发模式上的一些看法,发展方向和挑战等。这里他提到了一个很有意思的概念:FaaS,即 Function as a Service。同时还涉及到 Serverless,微服务等等,总之是概念非常多、非常宽泛的一场演讲。 网易乐得“无埋点”数据采集实践之路 这是一篇移动开发相关的演讲。所谓的“无埋点”,意思是不必手动给页面中一些元素加“埋点”来收集用户行为,而是通过用户操作的页面元素的 xPath 及其他一些特征来自动生成一个表示用户行为的“埋点”。这一思路对 PC 端的 Web 开发或许也有一定的启发。 美团 crash 监控分析系统优化之路:crash 率从千分位到万分位 也是比较空泛的一篇演讲,老生常谈的一些内容。主要从研发流程和机制的角度来入手,讲述了如何通过一系列的非技术手段来降低 App 的 crash 率。 属兔的处子——喜欢 Clojure,但怕动态语言太灵活怎么办 Clojure 是我最近打算去学习研究的一门语言,它本身是一种 Lisp 方言。虽然对 Clojure 的语法还不甚熟悉,但这篇演讲却听得津津有味。主要讲了 Clojure 里两种类型系统 core.typed 和 core.spec 的优劣。 另外,似乎所有的动态类型语言,都有人试图去给它们加上静态的特性,比如 TypeScript 之于 JavaScript。 编程语言如何演化——以 JS 的 private 为例 这是大名鼎鼎的 @hax 的一篇演讲,PPT 相当别具一格。讲了 JavaScript 类的 private 属性/方法 的历史。 hax 的风格就是,在讲 JS 的时候,可以顺便把其他 100 种语言都黑了。 单页应用的数据流方案探索 @民工精髓 的演讲,同时还有一篇更为详细的文章在这里。这篇主要是在讲 FRP(Functional Reactive Programming)模式的特点及优势。以 RxJS/xstream 为代表的数据流库,能够很好地跟 Redux 等状态库就行结合,解决后者常见的一些问题。 ui-model,更纯粹的前端 ui-model 的思路是,将一个组件的“内容层”、“样式层”和“逻辑层”分离开来,而它只提供“逻辑层”。想法的确有独到之处,因为毕竟在实际的业务场景中,几乎绝大部分的第三方 UI 组件都是需要“定制”的。而只提供了交互逻辑的 ui-model 的组件,的确有可能省去上述的“定制”——因为样式是要有使用者自己实现的。 不过,对于这种模式,组件所需要的数据如何维护?组件间的交互如何实现?这都还是问题。 百度搜索前端架构的演进 这篇演讲也是比较空泛,更多是业务上的数据,涉及到的技术细节很少。
动机 我们热爱 React 和 Redux。但是,Redux 中有太多的样板文件,需要很多的重复劳动,这一点令人沮丧;更别提在实际的 React 应用中,还要集成 react-router 的路由功能了。 一个典型的 React/Redux 应用看起来像下面这样: actions.js export const ADD_TODO = 'todos/add' export const COMPLETE_TODO = 'todos/complete' export function addTodo(text) { return { type: ADD_TODO, text } } export function completeTodo(id) { return { type: COMPLETE_TODO, id } } reducers.js import { ADD_TODO, COMPLETE_TODO } from './actions' let nextId = 0 export default function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [...state, {text: action.text, id: nextId++}] case COMPLETE_TODO: return state.map(todo => { if (todo.id === action.id) todo.completed = true return todo }) default: return state } } Todos.js import { addTodo, completeTodo } from './actions' // ... // 在某个事件处理函数中 dispatch(addTodo('a new todo')) // 在另一个事件处理函数中 dispatch(completeTodo(42)) 看起来是不是有点繁冗?这还是没考虑 异步 action 的情况呢。如果要处理异步 action,还需要引入 middleware(比如 redux-thunk 或者 redux-saga),那么代码就更繁琐了。 在一个接口中定义 action/reducer? Todos.js import mirror, { actions } from 'mirrorx' let nextId = 0 mirror.model({ name: 'todos', initialState: [], reducers: { add(state, text) { return [...state, {text, id: nextId++}] }, complete(state, id) { return state.map(todo => { if (todo.id === id) todo.completed = true return todo }) } } }) // ... // 在某个事件处理函数中 actions.todos.add('a new todo') // 在另一个事件处理函数中 actions.todos.complete(42) 是不是就简单很多了?只需一个方法,即可定义所有的 action 和 reducer(以及 异步 action)。 而且,这行代码: actions.todos.add('a new todo') 完全等同于这行代码: dispatch({ type: 'todos/add', text: 'a new todo' }) 完全不用关心具体的 action type,不用写大量的重复代码。简洁,高效。 异步 action 上述代码示例仅仅针对同步 action。那 异步 action 怎么处理呢? mirror.model({ // 省略前述代码 effects: { async addAsync(data, getState) { const res = await Promise.resolve(data) // 调用 `actions` 上的方法 dispatch 一个同步 action actions.todos.add(res) } } }) 没错,这样就定义了一个异步 action。上述代码的效果等同于如下代码: actions.todos.addSync = (data, getState) => { return dispatch({ type: 'todos/addAsync', data }) } 调用 actions.todos.addSync 方法,则会 dispatch 一个 type 为 todos/addAsync 的 action。 当然,处理这样的 action,必须要借助于 middleware。不过实现这样一个 middleware 也非常简单,开发者只管定义 action/reducer,然后简单地调用一个函数就行了。 总结 既然是对现有开发模式做封装和简化,那么要秉承的一个原则应该是,在尽可能地避免发明新的概念,并保持现有开发模式的前提下,减少重复劳动,提高开发效率。 只提供极少数的新 API,其余的都借用 React/Redux/react-router 已有的接口,针对其做封装和强化。 也就是说,不去“颠覆” React/Redux 开发流,只是简化了接口调用,省去样板代码: 针对上面描述的思路,初步完成了一个“框架”,Mirror。
将截图红框中的正则修改为 /^[a-zA-Z\d_]{3,20}$/
ali-oss
模块是否已经本地安装了?即在 node_modules 目录中是否已经存在?
如果没有,建议 rm -rf node_modules; npm install
重新安装所有依赖,然后编译构建。
iOS 11 及以上才开始支持 WebAssembly。题主测试的环境,是否 iOS 版本较低?
如下图:
最简单的就是使用 localStorage
。
当然前提是页面刷新或者使用 location.href
指定的新地址,与当前页面同域。如果跨域了,那么 localStorage
也是不行的。
可以查看这个类库:https://github.com/sdecima/javascript-detect-element-resize
原理很简单:在目标元素上新增一个不可见的子元素,给这个子元素增加一些动画效果,然后监听子元素的 animationstart
事件。当此事件触发时,则目标元素的宽高必定发生了变化。
Bootstrap 的 grid 系统,需要 Bootstrap v3.3 支持偏移,具体看文档:https://getbootstrap.com/docs/3.3/css/#grid-offsetting
文档里指明了,v3.3 版本支持你这种偏移的语法。所以你要确认自己引用的是 3.3 版本的呢。