Fundebug录屏插件更新至040修复SafariWKWebView下录制失败的问题

此次更新对插件做了多方面的升级,主要包含 BUG 修复和性能优化两个方面。 BUG 修复Safari 浏览器和 WKWebView 下数据为空由于 Safari 浏览器和 WKWebView 数据编码的处理方式和其它浏览器不一样,编码后的数据中一些特殊符号被 Safari 浏览器和 WKWebView 改写,从而使得服务端解码失败。我们更新了其编码方式来修复了这个 BUG。网站的静态图片资源重复加载此前我们默认不推荐用户开启录屏功能,因为其可能干扰使用图片的方式来发送数据的统计/广告服务,导致数据偏差。动态加载录屏插件失效此前版本的录屏插件会监听 DOM 状态,只有 domReady 才会执行相关代码。通过动态加载,永远不会触发 domReady 事件,所以录屏代码没有执行。性能优化通过优化数据记录方式,进一步提升插件性能,并减少传输的数据量。我们尽可能的增大鼠标轨迹记录的采样频率,后台通过计算模拟出可能的轨迹来进行数据补全;另外,通过减少非关键数据的记录来减少计算量和传输的数据量的大小。 接入方法使用 Script 方式接入 <script type="text/javascript" src="https://js.fundebug.cn/fundebug.revideo.0.4.0.min.js"/>使用 NPM 接入 require("fundebug-revideo");最后,感谢 Fundebug 用户闁鑅的反馈。 关于FundebugFundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎大家免费试用! 版权声明转载时请注明作者 Fundebug以及本文地址: https://blog.fundebug.com/2019/05/26/fundebug-release-revideo-0-4-0/

May 27, 2019 · 1 min · jiezi

谷歌浏览器应用插件下载汇总

第一种方法(适用于从能科学上网到不能科学上网插件的搬迁)打开谷歌应用市场(该环境必须可以科学上网)找到自己想要下载的插件,如下:首先找到该应用的id(链接后面的一串英文就是id了),步骤如下:或者如果已经下载该插件的情况下,右击插件 -> 管理拓展程序 -> 也能找到id重点,打开http://chrome-extension-downl…,输入查找出来的id,点击下载即可…另一种方法:https://chrome.lanfanshu.cn/(不需要科学上网)https://www.crx4chrome.com/(不需要科学上网)

March 1, 2019 · 1 min · jiezi

「每日一瞥 ???? 」1203 ~ 1221

Node.js 内存更高效的大文件处理方法React 元素的 $$typeof 属性是干嘛的?React 是怎么区分 class 和 function 的?Redux v6.0.0 released学习一下 React HookssetState 并没有做什么为不断演化而不断优化Babel 7.2.0 发布Redux 与 Context 取舍Hooks vs Lifecycle纯 News 向:state of js in 2018React.memo() 和 useMemo Hooksconsole.log 以外的骚操作Node.js 内存更高效的大文件处理方法在 Node.js 中,我们可以使用 fs.readFile、fs.writeFile 来实现一个文件的复制,但毫无疑问的是,当我们想要处理几个G甚至几十G的文件时,这种方法会导致内存问题。这时,我们可能会考虑利用 stream 和 buffer,将大文件劈成许多小的 chunks 来进行传输:监听 Readable Stream 读入的 chunks;利用 Writable stream 将监听到的 chunks 写到磁盘;跟踪整个监听过程(说白了就是 readable.on(‘data’, …))一般来说上述方案已经是较为合理的做法了,但是问题仍然存在,那就是 node 会将内存占用飚的极高,我们可怜的小 Mac 难免会嗡嗡作响。问题的根源是什么呢?读写速度不一致。因为读的速度很快,但写操作却来不及全部处理,这时就会将过量的数据先存入 RAM,写操作本身变成了瓶颈,且存入 RAM 也会间接影响性能。事实上这篇文章就是在推荐用 Node 的管道写法来处理,现在的 Node 已经支持这种写法。管道的方法遵循 Back pressure 机制,一般可能翻译成「背压」,其实这是在说一种现象:在数据流从上游生产者向下游消费者传输的过程中,上游生产速度大于下游消费速度,导致下游的 Buffer 溢出。 pipe 方法可以有效控制磁盘的读写速度,避免这一现象的发生。使用的时候,写法上很容易调整到 pipe 的写法,将 writeable.write(chunk) 换成 readabale.pipe(writeable) 即可。这种方式可以只以很低的内存占用来完成大文件操作,同时因为没有过度占用内存,还可以得到更快的速度。关于 pipe 的原理可以参考下面这个图。具体对这一机制的描述可见 Node 文档。源地址:https://medium.com/dev-bits/w…React 元素的 $$typeof 属性是干嘛的?用 React 久了,就一定知道 type、props、key、ref等属性,但有一个属性我们未必很了解,就是 $$typeof,它持有的是一个 Symbol 类型的值,如下所示:{ type: ‘marquee’, props: { bgcolor: ‘#ffa7c4’, children: ‘hi’, }, key: null, ref: null, $$typeof: Symbol.for(‘react.element’), // ???? Who dis}我们会用 React 的 JSX 写下很多 View,在模板中插入许多量。如果有的量是外部录入的,那我们就要提防 XSS 了。当然,React 会默认进行 escape 来预防这类事情,如果我们不得不强制展示 HTML,我们可以利用 dangerouslySetInnerHTML 来处理。但即使如此,安全性还是不够的,这就有了 $$typeof 发挥的地方。考虑这样一个场景:如果服务端开了个口子允许用户存储 JSON 对象,而用户需要一个字符串,这就可能存在问题。事实上我就曾经遇到过文中所说的场景-,-,例子如下:// Server could have a hole that lets user store JSONlet expectedTextButGotJSON = { type: ‘div’, props: { dangerouslySetInnerHTML: { __html: ‘/* put your exploit here */’ }, }, // …};let message = { text: expectedTextButGotJSON };// Dangerous in React 0.13<p> {message.text}</p>当然,React 从 0.14 版本就开始对每个 React 元素标记一个 Symbol 符号。这个方法是有效,原因是你不可能在 JSON 中放 Symbol,这样即使服务端返回了一个 JSON,React也不会去解析包含恶意脚本的 JSON。当然中的当然,这也不是万灵药,但确实对上述场景有用。一个题外话是,对于不支持 Symbol 的环境,React 会将 $$typeof 设置为 0xeac7,原因就是这个串长得像 「React」。源地址: https://overreacted.io/why-do...React 是怎么区分 class 和 function 的?当我们在写一个组件的时候,可以通过函数或类的方式来定义,比如下面这种:function Greeting() { return <p>Hello</p>;}// … orclass Greeting extends React.Component { render() { return <p>Hello</p>; }}当我们使用的时候,从来不会关注究竟是怎么定义的,而是简简单单的写个标签就 ok。// Class or function — whatever.<Greeting />但其实仔细想一想,这两种定义方式自然从使用上是不同的。根据 JavaScript 基础知识,如果是函数的定义方法,那么我们自然是要调用函数;如果是类的定义方法,那么我们自然是要实例化一个对象。// 1. 当使用函数定义时 Your codefunction Greeting() { return <p>Hello</p>;}// Inside Reactconst result = Greeting(props); // <p>Hello</p>// 2. 当使用类定义时 Your codeclass Greeting extends React.Component { render() { return <p>Hello</p>; }}// Inside Reactconst instance = new Greeting(props); // Greeting {}const result = instance.render(); // <p>Hello</p>因此可以看出,React 本身是要知道定义的方式然后再决定如何得到渲染的结果的。那么是怎么知道的呢?这篇文章探讨的很详细,但在这里,我们可以做个很简单的总结:在 React.Component 类的原型上挂上一个 flag 量,这个 flag 就是个空的对象;然后我们在用类定义的方式写 React 组件时,我们都会做的一件事就是 extends 继承一下,而「继承」会使得类定义的 React 组件在原型上共享到 flag;这样一来,所有通过类定义的方式都会继承 React.Component,也就得到了能够标记其定义方式的 flag 量。// Inside Reactclass Component {}Component.prototype.isReactComponent = {};// We can check it like thisclass Greeting extends Component {}console.log(Greeting.prototype.isReactComponent); // ✅ Yes源地址: https://overreacted.io/how-do...Redux v6.0.0 releasedRedux 正式发布了 6.0 版本,支持 React 16.4 中的 Context API。相应的,这次更新有一些大变化:connect 所使用的参数 withRef 变更为 forwardRef。如果 forwardRef 置为 true,当我们给被 connect 包裹的组件加一个 ref 时,会直接拿到被包裹的组件。我们不会再需要将 store 传给被 connect 的组件了,以后传的是自定义的 context。但在开发时通常会使用 react-redux,不直接使用 redux 的 store 来操作,所以没啥特别感觉。而这带来了一些影响:最明显的就是用到 store 的那些包要改写了。目前 redux 将 store 放在 context 里,对于依赖它的包仍可以访问到,只是不会再提供开放的 API 了。当然不久的将来估计就没了。还有一个是在 constructor 或 componentWillMount 里 dispatch 产生的行为可能有变化。此前的行为是:父子组件会各自独立的去读取 store,而子组件在挂载时会立刻使用更新过的状态。现在的话因为共用状态,所以就不会出现这种情形(我的理解就是子组件会用父组件还没用上的新状态)。但是目前有的应用可能会依赖前者的特性,所以会存在一些问题。学习一下 React Hooks今天就看看 React Hooks。官方文档应该算是一手的学习资源,就撸它了。下面的内容尽量保证已消化的清晰易懂。首先 React Hooks 到底是什么呢,文档有一句并没有着重强调的话可以拿来总结,但是略有些不好翻译,我的理解是:Hooks 可以让你在函数式组件里做到一些与状态、生命周期相关的事。什么意思呢,就是说 Hooks 并不应用在 class 定义的组件里,且我们不需要通过 this.state、this.setState 和那些个 cmpDM、cmpDU 生命周期来做事情了。下面的例子可以说是最基本但没啥用的例子:import { useState } from ‘react’;function Example() { // Declare a new state variable, which we’ll call “count” const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> );}useState 就是一个 Hook。useState 以参数作为状态初始值,以数组解构的形式返回需要的东西,包括当前的状态值和一个更新该状态的函数。也就是说,我们可以通过 setCount 来更新 count,不再需要 this.state 来初始化,不再需要 this.setState 来更新状态。上面这几句话对应老长的一篇文档-,-。那么在继续之前,我们要搞清楚为什么要搞这么个东西,它是要解决什么问题?其一,代码复用层面,我们可能会通过封装 HOC 等方式来复用逻辑,而这可能会带来大量的 wrapper,导致臃肿。其二,代码组织层面,一个深有体会的点是我们在使用 React 的时候简直就是面向生命周期编程。有时候你会发现接手的代码里,相关的逻辑分散在各个生命周期里;有时候你又会发现,不相关的逻辑写在一坨里。Hooks 让你把一个组件拆分成若干更小的、内部逻辑相关的函数。相比较而言会更清晰。其三,学习成本层面,这个就不说了,应该不存在的。但 Hooks 厉害的地方还不止于上面的例子,useEffect 就真的让人爽到飞起了。useEffect 可以看做 componentDidMount、componentDidUpdate、和 componentWillUnmount 的大集合,它是一个完整的处理副作用的机制。以下面例子为例,我们用 class 写的代码可能是这样的:class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } componentDidMount() { document.title = You clicked ${this.state.count} times; } componentDidUpdate() { document.title = You clicked ${this.state.count} times; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); }}但我们用 useEffect 就是这样的:import { useState, useEffect } from ‘react’;function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = You clicked ${count} times; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> );}我们不用去在意什么 document,把他当做某些容易理解的副作用即可,比如 fetch?我们只需要知道,很多时候我们会在几个生命周期方法里写上一样或有很多重合的逻辑,这使得我们不得不管杀又管埋(从而增加出错的几率),还搞得代码量无形变多。后者的写法明显比前者会少,你不再需要区分挂载后如何、更新后又如何。useEffect 做了什么呢?我们声明了一个状态 count,然后在副作用里用到了它,因此可以在副作用里读取到 count 最新的状态值。当我们的组件被渲染时,React 会替我们在 DOM 更新后触发副作用,每一次 render 都会,包括首次渲染。我们可能会觉得这样是不是太激烈了,但这是故意设计的。每次重新渲染,触发的副作用也是一个新的副作用,从某种程度上讲,副作用本身就成了渲染结果的一部分,似乎也就不那么「副」了。具体可以再看看文档,以后有更深入的体会再更。源地址:react文档。setState 并没有做什么本文提出了一个问题:当我们用 setState 更新状态并进而更新 DOM 时,究竟是 React 还是 React DOM 在做这一切呢?看上去,更新 DOM 像是 React DOM 在做的事,但我们调用的是 this.setState,React.Component 这个类也肯定是定义在 React 中的。因此上述问题就成了一个无法简单回答的问题。事实上,如果真的是 React 在负责更新 DOM,那么难道 this.setState 在浏览器、服务端、移动端上都可以跑了吗?比方说 React Native 中我们也是一样使用 this.setState 更新状态,但是 Android 和 iOS 上可没有 DOM,有的只是 View。结论其实是:React.Component 只是将状态更新的任务委托给了平台特定的代码。换句话说,我们的 this.setState 本身可能并没有做什么了不起的事情。React 从 0.14 版本就做了拆分,React 包本身只负责暴露定义组件的接口,而具体实现就交给各个渲染器(如 react-dom、 react-dom/server、 react-native、 react-test-renderer、 react-art 等)。react 包本身只负责提供 React 特性,但并不关心具体是如何实现这些特性的;渲染器包如 react-dom、 react-native 等,提供的就是 React 特性的具体实现以及平台特定的逻辑。这些渲染器包有很多一样的代码 (如 React 过去的调和算法),但这些代码也只是各个渲染器实现中的某些细节。举例来说,当 React 16.3 引入了 Context API 时,React 包暴露了 React.createContext() 方法,但这个方法本身并不真正实现 context 特性。对于 React DOM 和 React DOM Server,它们都要做不同的实现代码。createContext() 只返回一些对象:// A bit simplifiedfunction createContext(defaultValue) { let context = { _currentValue: defaultValue, Provider: null, Consumer: null }; context.Provider = { $$typeof: Symbol.for(‘react.provider’), _context: context }; context.Consumer = { $$typeof: Symbol.for(‘react.context’), _context: context, }; return context;}这样当我们使用 <MyContext.Provider> 或 <MyContext.Consumer> 时,React DOM 和 React DOM Server 会以不同的方式来处理 context。那么话又说回来了,setState 到底做了什么?答案就是,每个渲染器都会为创建的类创建特定的域,而这个域就是一个更新器,而这个东西,是 React DOM、 React DOM Server 或者 React Native 在创建实例之后纷纷要去提供具体实现的:// Inside React DOMconst inst = new YourComponent();inst.props = props;inst.updater = ReactDOMUpdater;// Inside React DOM Serverconst inst = new YourComponent();inst.props = props;inst.updater = ReactDOMServerUpdater;// Inside React Nativeconst inst = new YourComponent();inst.props = props;inst.updater = ReactNativeUpdater;而 React。Component 的 setState 的实现,就是委托给渲染器去创建组件实例:// ReactBaseClasses.jsComponent.prototype.setState = function(partialState, callback) { invariant( typeof partialState === ‘object’ || typeof partialState === ‘function’ || partialState == null, ‘setState(…): takes an object of state variables to update or a ’ + ‘function which returns an object of state variables.’, ); this.updater.enqueueSetState(this, partialState, callback, ‘setState’);};这就是为什么 this.setState() 即使它被定义在 React 包里仍可以更新 DOM 的原因.源地址:https://overreacted.io/how-do…Redux 作者更新关键词Mark Erikson 在 twitter 上问道,如果他用 Predictable、 Centralized、 Debuggable、 Flexible 来形容 Redux,那么大家会希望用什么词语来形容 React Redux,他会以这些关键词作为「卖点」放到首页上。于是有个评论说了如下的话:总之,Mark Erikson 在吸取了评论区的诸多意见后,最终在首页发出了最新的 4 个关键词:Official:由 Redux 团队专门维护,持续跟进 React 和 Redux 最新的 API;Predictable:贴合 React 组件模型,使用者只需要从 Redux 取出数据交给组件,而组件将会以 props 的形式接收;Encapsulated:封装了与 store 交互的逻辑;Optimized:做了复杂的性能优化,让使用者的组件只有在数据真的变化时再重新渲染。源地址:https://twitter.com/acemarke/…https://react-redux.js.org/Babel 7.2.0 发布Private Instance MethodsBabel 7.1.0 的版本开始支持 private static fields,而近日发布的 7.2.0 更进一步的支持了 private instance method。有了这个 feat,我们就可以写下面这种代码:class Person { #age = 19; #increaseAge() { this.#age++; } birthday() { this.#increaseAge(); alert(“Happy Birthday!”); }}issue#8654 中有人针对如下代码提了个问题:console.log 打印出的是 true 还是 false?class A { #method() {} getMethod() { return this.#method; }}// trueconsole.log(new A().getMethod() === new A().getMethod());littledan 回复说两个 new A() 创建的对象持有的私有方法是同一个。所以说,「面向对象的 JS」在 Babel 的帮助下出现了,你呢?管道操作符这个主要是说该版本支持了智能管道提案的核心部分,并支持了 # 作为占位符。比如下面的代码:// “Smart"const result = 2 |> double |> 3 + # |> toStringBase(2, #); // “111”// “Simple"const result = 2 |> double |> (x => 3 + x) |> (x => toStringBase(2, x));Smart 版本就是能支持占位符,比下面的 Simple 版本更简单一点。我之前只是听过这个提案,今天又看了下管道操作符是怎么个意思呢?管道操作符的语法是这样的:expression |> function我们拿 MDN 的例子来展示就很明了了:const double = (n) => n * 2;const increment = (n) => n + 1;// 没有用管道操作符double(increment(double(5))); // 22// 用上管道操作符之后5 |> double |> increment |> double; // 22总的来说,这玩意可以以一种更容易读的方式对函数进行链式调用,很适合单参数函数的场景。Plugin Names这个主要是说官方插件为各个版本的 Babel 提供了自己的名字,从而能做一些类似 Time Travel 这样的事。我们可以看看改版后的 Babeljs.io:源地址:https://babeljs.io/blog/2018/…源地址: https://github.com/reduxjs/re…为不断演化而不断优化Dan Abramov 在他的个人博客上分享了他对优秀甚至卓越的 API 设计的看法。我们通常会认为「可读性」、「正确性」、「高性能」是「首要标准」,无论有怎样的妥协、平衡,这一首要标准都会占据在一个好的程序员的意识中。但是总的来说,再优雅的代码也会被繁复变化的需求所打倒,因此 Dan 认为,在可读性的基础上,更应该追求「第二标准」:对用户使用这些 API 的诉求会发生演化这一事实有意识。总的来说,好的 API 设计会将变化考虑其中。源地址:https://overreacted.io/optimi…自定义 Hooks 优势盘点Dan 最近更的很频繁,今天更的文章主要是介绍 React Hooks 一些为什么如此设计的点,但在此之前他建议阅读下文档中自定义 Hooks 的部分。文中强调 Hooks 一个强大之处在于,Hooks 之间是可以相互传值的。从文档和这篇文章来看,使用自定义 Hooks,我们可以很好地抽象业务逻辑并复用,其效果甚至比 render props 和高阶组件还好。相比较过去在生命周期中写出来的分散的、重复的内容,自定义 Hooks 可以做得更好,且让使用者更少的考虑除业务逻辑以外的东西。下面一个例子就是看从列表中选中的好友是否在线这么个功能,我们可以注意到 useFriendStatus 就是一个自定义 Hooks,它抽出的逻辑明显具有复用性:const friendList = [ { id: 1, name: ‘Phoebe’ }, { id: 2, name: ‘Rachel’ }, { id: 3, name: ‘Ross’ },];function ChatRecipientPicker() { const [recipientID, setRecipientID] = useState(1); const isRecipientOnline = useFriendStatus(recipientID); return ( <> <Circle color={isRecipientOnline ? ‘green’ : ‘red’} /> <select value={recipientID} onChange={e => setRecipientID(Number(e.target.value))} > {friendList.map(friend => ( <option key={friend.id} value={friend.id}> {friend.name} </option> ))} </select> </> );}function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); const handleStatusChange = (status) => setIsOnline(status.isOnline); useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline;}这整个逻辑能够跑通的原因就在于,useFriendStatus 作为一个被抽出来的自定义 Hooks ,可以在 ChatRecipientPicker 内使用到最新的状态 recipientID。这就是指 Hooks 之间可以传值。const [recipientID, setRecipientID] = useState(1);const isRecipientOnline = useFriendStatus(recipientID);总结起来有这么几点:因为能在函数体中调用 Hooks,我们可以轻松的在他们之间传值,而不用再写多层组件来转换或者用一些方法来记忆中间计算;我们能够在副作用里轻松的引用到状态,不必担心在闭包中拿不到值;我们也不再需要有意识的匹配输入、输出,现在是一个整体的风格;我们也不再需要通过高阶组件、反向继承这些技术来抽象业务逻辑。Redux 与 Context 取舍文章开篇有些损,附首段原文如下,我主要针对后面的内容进行总结。The complaint that is often voiced by inexperienced React developers, is that Redux actually complicates their work instead of simplifying it. This is understandable – this tool was invented for managing data flow in more complex applications and seems to be an overkill for simple SPAs or websites.一、学习 Redux 存在的问题得在没有用 Redux 的大项目中工作,才能看出 Redux 是要解决哪些问题(有过痛苦才有体感),有哪些替代方案以及为什么它们更有效和方便。Redux 教程总是用一些原生 JS 也很好写的 Demo 来示范,给人一种是不是搞复杂了的感觉。二、Redux 旨在解决的问题分离「发生的」和「要做的」。这里提到一个点是,多个 reducer 重复相同的模式是一种比较差的实践,应该使用更简单的 reducer 替换并重用(添加参数)。传递 props 时要穿过一堆用不到的 component单单是这个场景的话,其实 Context 其实是够的:import React, { Component } from ‘react’;const MyContext = React.createContext();class AppProvider extends Component { state = { data: {}}; // state should be populated with data render() { return ( <MyContext.Provider value={this.state}> {this.props.children} </MyContext.Provider> ); }}class MyComponent extends Component { render() { return ( <AppProvider> <div className=“my-component”> <MyContext.Consumer> {(context) => context.data} </MyContext.Consumer> <ComponentWrapper /> </div> </AppProvider> ); }}class ComponentWrapper extends Component { render() { return ( <div className=“wrapper”> <Subcomponent /> </div> ); }}class Subcomponent extends Component { render() { return ( <div className=“subcomponent”> <MyContext.Consumer> {(context) => context.data} </MyContext.Consumer> </div> ); }}传递「handle」时穿过一堆用不到的 component与传递一般值类型的 props 不同的是,回调对数据的修改就是「自下而上」的感觉,而这和 React 的设计感觉有些相反。三、何时使用 Redux需要各种使用中间件,比如打 log,打错误日志,根据服务端的响应发其他请求等。多方数据共同影响单个组件/视图。对简化调试有着强烈的需求。分离 data 和 view,将数据请求和具体渲染分离。观察者模式。只需将组件连接到 Redux store,而不是在整个应用程序中创建多个发布者和订阅者。四、何时 Redux 会变得太重而 Context 已然足够想从每个组件访问管理的状态,这不足以成为使用 Redux 的理由,Redux 的东西还是更多一些。不需要中间件,只是用 Reducers 执行简单的可预测操作,接收数据的顺序与应用程序无关,也不需要 Redux。需要「全局数据」这样的东西。只是上面三个理由,还是 Context 吧。源地址:https://ideamotive.co/blog/re…Hooks vs Lifecycle今天决定拿 Hooks 相关文章继续做些学习。下面拿两个 case 来进行分析和总结。一个 useState 的 case如下是一个简单的计数器组件,上面的实现是 Hooks,下面走的 class 方式。注意到有四点差异:在顶部用数组解构的方式定义状态,以初始状态定义一次,并随时可以用 setStateVar 来修改。Hooks 用 setStateVar 来定义单个值,class 内用 setState 及对象来 merge 到新的状态。在函数式组件中访问状态,你只需要通过这个状态的「名字」,而在类定义组件中则需要 this.state.stateVar,相对是要繁琐了一些。需要 import { useState } from ‘react’ 来使用useState。如果我们需要使用多个状态,那么就多次使用 useState:一个 useEffect 的 case另一个重要的 Hook 是 useEffect,它试图取代生命周期,充当「一体化」生命周期方法。当组件挂载、更新或卸载时,就会触发 effects。如下是 class 写法的代码:用生命周期来管理一些逻辑的缺点如今已经无需多说,我们来看看 Hooks 的方式,一并分析:useEffect 就是 componentDidMount、componentDidUpdate 和 componentWillUnmount 的集合体。此外,我们可以多次调用 hooks,拆分特定需要考虑的逻辑,但又能够放在一起。我们来总结下 useEffect 是怎么工作的:useEffect 本身是一个函数,每次在组件渲染后的更新它都会被执行。我们提供给 useEffect 的参数也是函数,这个函数就是每次渲染发生时真正会被调用的方法。useEffect 的返回是可选的,而这个返回会在组建卸载之前被调用。因此,我们会在返回中做一些取消订阅、删除定时器等等清理工作。一些总结如果使用 Hooks 来编写组件,就可以使用 Hooks 来访问状态和生命周期。状态变量要单一赋值,而不要赋值成整个对象,遵循 const [stateVar, setStateVar] = setState(stateVarInitialValue) 的语法。useEffect 是用来存放那些在组件渲染后、在组件卸载前所做的事,如下所示:和 useState 一样,useEffect 也会在函数式组件中执行多次,且 effects 们会被串联起来。源地址:https://levelup.gitconnected….纯 News 向:state of js in 2018今天看看 stateofjs 上发的今年 JS 的情况调查(里面的可视化效果挺不错的)。下面挑一些有意思的讲讲:各国前端的平均收入貌似还是北美牛逼啊,没想到澳洲也挺好的。开发者的时间都用来干嘛了这一条真的与我国国情严重不符,看来还是调查数据涵盖了更多非中国的用户。性别分布当年决定写前端的时候,我是真以为前端至少比 Java 妹子多。语言、方言分布TS 是有点牛的,都不统计 ES5 了。下面的图可看出,ES6 和 TS 高度受欢迎、高度使用、高度满意和令人安心。前端框架从「使用并会再次使用」的占比来看,Vue 虽赶不上 React,但也是茁壮成长了,而且可以看出,两者都属于那种留得住人的。与之相对比的是 Angular,还蛮多人不乐意再用的。为防止被说拿百分比制造「统计假象」,这里附上统计数的版本:我们拿前面的四分图来看,可以看出,满意度最高的是 React 和 Vue,而且不分上下,而 Angular 属于用的多但风评不佳的那种。感觉上 React 肯定会是制霸前端,但是也不会压倒 Vue 了,这个世界就是这样…前端框架 vs 工资先看统计数版本,可以看出 Preact、Polymer、Ember好像工资挺高的,不过统计人数比较少,还是要看三板斧。从百分比的版本可以看出,还是 React 的天下,使用 Vue 的同学统计上会低一点。数据层Redux 很火,GraphQL 很火。服务端框架从使用度来看,Express 是毋庸置疑的霸主地位,Koa 没想到真不太行,而且看得出听说过它的人也没有那么多,第二居然是 Next.js。四分图可以更明显的看出 Express 的优势,Koa 难道在国外不火的吗?移动端 & 桌面主要也就是 Electron 和 RN,记得两年前 Electron 我都没听说过,那时候搜到的还是 NW.js。其他语言Python 是真的好使,PHP 果然是最好的语言。浏览器 APIPWA 在国外是很火。WebGL 和 WebVR 应该也有潜力。构建工具Webpack 一家独大,不知道 Parcel 能不能发育起来。其他不附图了。越来越多的人认为 JS 在往着正确的方向发展,越来越多的人认为构建一个 JS app 变得简单,并越来越享受开发 JS apps,越来越少的人认为 JS 生态变化的快了。最后好啦,差不多看完了。源地址:https://2018.stateofjs.com/de…React.memo() 和 useMemo Hooks晚饭的时候同事提到 memo,回来又看了一下发现 Hooks 其实还挺多的。基本的三板斧就是 useState、useEffect 和 useContext,当然前两个基本也就够了。useMemouseMemo 是一个用于优化性能的 Hook,想象一个场景,你想根据传入的 props 来做些计算,比如讲一个对象数组 map 成另一种结构,或者将一个数组的数据整合成一个值,或者做排序或过滤等等,都是 useMemo 可以施展拳脚的地方。用法如下,还是很好理解的,参数一是计算复杂的方法,参数二是输入,只有它们之中有变化时才会重新触发参数一,否则会返回记忆的结果。如果参数二没给,那就每次都算。。。const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);我们看段代码:const ExpensiveComputation = ({ data, sortComparator, filterPredicate}) => { const transformedData = useMemo( () => { return data .filter(filterPredicate) .sort(sortComparator); }, [data, sortComparator, filterPredicate] ); return <Table data={transformedData} />;};useMemo 会检查传入的参数列表,确认输入是否发生变化,以此避免不必要的「计算量大」的 rerender。React.memoReact.memo 并不是一个 hook,但它也是个用于性能优化的新 API。它有些类似 shouldComponentUpdate 和 PureComponent,但是在函数式组件里是没有这些东西的。另外一点是它只对 props 进行比较,这就与 shouldComponentUpdate 不一样了。React.memo 其实是一个 HOC,用于优化性能的 HOC。import React from ‘react’;const MyDemoComponent = React.memo(function MyComponent(props) { // only renders if props have changed!});// can also be an es6 arrow functionconst OtherDemo = React.memo(props => { return <div>my memoized component</div>;});// and even shorter with implicit returnconst ImplicitDemo = React.memo(props => ( <div>implicit memoized component</div>));源地址:https://medium.com/@vcarl/eve…https://scotch.io/tutorials/r...console.log 以外的骚操作console.log()console.log 是支持像 C 里的 sprintf 那种 format 方式的。// Syntaxconsole.log(msg, values)// Democonst a = { x: { xx: ‘123’, yy: ‘4545’ }, y: { zz: ‘6543’} };console.log(“test hhh %o”, a)占位符可以有 %s、%o(对象)、%d 等等。还有一种比较骚的是 %c,它事实上是 css 的占位符:如果是用来做点 log,其实还蛮炫的,不过用法在细节上有些 weird。console.dir()一般情况下,它的结果和 console.log 是一样的。但是当传入的是一个 DOM 时,就有如下区别:可以看出,当你想看一个 DOM 元素细节的时候,console.dir 打印出来的可能更符合我们的期待。console.warn()它和 console.log 唯一的区别就是会变黄,但也不止于此,你可以在下图所示的地方做 filter,这样 log 就会变得更清晰:console.table()这个东西是极其骚的,但是以前没关注过。如果我们有下面这种数组数据:const data = [{ id: “7cb1-e041b126-f3b8”, seller: “WAL0412”, buyer: “WAL3023”, price: 203450, time: 1539688433},{ id: “1d4c-31f8f14b-1571”, seller: “WAL0452”, buyer: “WAL3023”, price: 348299, time: 1539688433},{ id: “b12c-b3adf58f-809f”, seller: “WAL0012”, buyer: “WAL2025”, price: 59240, time: 1539688433}];console.table 和 console.log 的对比如下:还可以通过第二个参数指定打印出的列:console.table(data, [“id”, “price”])效果如下:需要指出的是,console.table 方法最多打印 1000 行。提前结束这里只给出了文中提到的四种,其他可在最后原文链接里看到。补充今天听同事做技术分享的时候才发现,JSON.stringify 还有他写的 Demo 中的那种用法,具体可见 MDN。// SyntaxJSON.stringify(value[, replacer[, space]])// Democonst a = { x: { xx: ‘123’, yy: ‘4545’ }, y: { zz: ‘6543’} };console.log(JSON.stringify(a, ‘’, 2))源地址:https://medium.com/@mattburge…「每日一瞥」是团队内部日常业界动态提炼,发布时效可能略有延后。文章可随意转载,但请保留此 原文链接。非常欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj(at)alibaba-inc.com 。 ...

December 26, 2018 · 8 min · jiezi

PWA,你需要知道的那些事

(ps: 有一段时间没发文了,忙得不可开交,之前团队分享PWA,答应大家整理出来,终于结稿了~)PWA简介PWA,英文全称是 Progressive Web App,2015年 由 Google 提出。PWA是提升 Web App的体验的一种新方法,能给用户原生应用的体验,兼具了 Web App 和 Native App 的优点。PWA 主要特性可靠 - 即使在不稳定的网络环境下,也能瞬间加载并展现;体验 - 快速响应,并且有平滑的动画响应用户的操作;粘性 - 像设备上的原生应用,具有沉浸式的用户体验,用户可以添加到桌面。特点:渐进式 - 适用于选用任何浏览器的所有用户,因为它是以渐进式增强作为核心宗旨来开发的。自适应 - 适合任何机型:桌面设备、移动设备、平板电脑或任何未来设备。 连接无关性 - 能够借助于服务工作线程在离线或低质量网络状况下工作。类似应用 - 由于是在 App Shell 模型基础上开发,因此具有应用风格的交互和导航,给用户以应用般的熟悉感。持续更新 - 在服务工作线程更新进程的作用下时刻保持最新状态。安全 - 通过 HTTPS 提供,以防止窥探和确保内容不被篡改。可发现 - W3C 清单和服务工作线程注册作用域能够让搜索引擎找到它们,从而将其识别为“应用”。可再互动 - 通过推送通知之类的功能简化了再互动。可安装 - 用户可免去使用应用商店的麻烦,直接将对其最有用的应用“保留”在主屏幕上。可链接 - 可通过网址轻松分享,无需复杂的安装。PWA 本身强调渐进式,但并不要求一次性达到安全、性能和体验上的所有要求,开发者可以通过 PWA Checklist 查看现有的特征。PWA功能丰富,相比原生应用更加轻量。我们可以把 PWA 网站添加到桌面上,不管在PC端还是移动端,都类似于一个原生应用,并且拥有媲美原生应用的体验。它也拥有原生 APP 应用一般的启动闪屏,也可以进行消息推送——不过要知道,它源自 Web,通常只有传统 APP 的体积的十分之一甚至更小。它不用等待下载安装的时间,打开网页的时候就已经「下载」并且「安装」完毕。APP ShellApp Shell 架构是构建 PWA 的一种方式,这种应用能可靠且即时地加载到您的用户屏幕上,与本机应用相似。定义App Shell 是支持用户界面所需的最小的 HTML、CSS 和 JavaScript,如果离线缓存,可确保在用户重复访问时提供即时、可靠的良好性能。这意味着并不是每次用户访问时都要从网络加载 App Shell。 只需要从网络中加载必要的内容。 看下面这张图来了解下:构建单页应用时,可以使用APP Shell,它依赖渐进式缓存 Shell(使用服务工作线程)让应用运行。App Shell 非常适合用于在没有网络的情况下将一些初始 HTML 快速加载到屏幕上。App Shell 应能完美地执行以下操作:快速加载尽可能使用较少的数据使用本机缓存中的静态资产将内容与导航分离开来检索和显示特定页面的内容(HTML、JSON 等)可选:缓存动态内容 App Shell 可保证 UI 的本地化以及从 API 动态加载内容,但同时不影响网络的可链接性和可检测性。 用户下次访问同一应用时,应用会自动显示最新版本。无需在使用前下载新版本。Service Worker与离线存储依赖Service Worker 作为现代浏览器的高级特性,依赖于 fetch 、promise 、CacheStorage、Cache API、等浏览器的基础能力, Cache 提供了 Request / Response 对象对的存储机制。CacheStorage 则提供了存储 Cache 对象的机制。功能和特性:一个独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context。一旦被 install,就永远存在,除非被手动 unregister用到的时候可以直接唤醒,不用的时候自动睡眠可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)离线内容开发者可控能向客户端推送消息不能直接操作 DOM必须在 HTTPS 环境下才能工作异步实现,内部大都是通过 Promise 实现注意:SW通过postMessage与页面之间通信,让页面自己去操作DOM是一个可编程的网络代理,允许开发者控制页面上处理的网络请求在不被使用的时候,它会自己终止,而当它再次被用到的时候,会被重新激活,所以你不能依赖于service worker的onfecth和onmessage的处理函数中的全局状态如果你想要保存一些持久化的信息,你可以在service worker里使用IndexedDB API只能在 HTTPS 环境下才能使用SW,因为SW 的权利比较大,能够直接截取和返回用户的请求Push Notification ( 消息通知 ) Push 和 Notification 是两个不同的功能,涉及到两个 API 。 Notification 是浏览器发出的通知消息。Push 和 Notification 的关系,Push:服务器端将更新的信息传递给 SW ,Notification: SW 将更新的信息推送给用户。IndexedDB与异步存储Web Storage 包括 Local Storage、Session Storage。它们使用简单的键值对来存储,方便灵活,但是它们的内存较小,当遇到大量的结构化数据时,就无法应对了。浏览器数据存储方式还有IndexedDB、Web SQL 和 Cookie。接下来就来看下IndexedDB,它能够在客户端存储大量的结构化数据。例如,在PWA应用中,我们可以用它来离线存储大量的聊天记录。关于IndexDB的介绍,引用这篇文章中的一段话:indexedDB 是 HTML5 提供的一种本地存储,一般用户保存大量用户数据并要求数据之间有搜索需要的场景,当网络断开的时候,可以做一些离线应用,它比 SQL 方便,不用去写一些特定的语句对数据进行操作,数据格式为 json。IndexDB 使用索引高效检索的API,如打开一个IndexedDB数据库,可以这样写 window.indexedDB.open(name)。需要注意的是,IndexDB的功能之一就是它有异步的API,类似于 ajax 请求。我们通过代码来了解下如代码所示,打开数据库后,有以下几个回调:onerror(err => {})onsuccess(res => {})onupgradeneeded(data => {})分别处理数据库打开失败、成功的回调,以及请求数据库版本变化的回调。IndexDB 在使用时,需要注意以下几点:过大的数据不适合存放在IndexDB,浏览器初始化分配是50M部分浏览器不支持IndexDB,使用前先用"indexedDB" in window判断下敏感数据不能存在客户端受到同源策略的限制HTTPSHTTPS,简单来讲就是 HTTP 的安全版,它是HTTP over SSL/TLS的缩写。PWA 只能在 HTTPS 协议下使用,本地开发时支持 localhost 和 127.0.0.1。https下调试可以用 github page。HTTPS 会对传输的数据进行加密,建立一个信息安全通道,来保证传输过程中的数据安全。同时,也会对网站服务器进行真实的身份认证。关于HTTPS,大家都比较熟悉,这里就不多赘述了。入门与实践关于PWA开发与调试的,题叶老师写了一篇文章PWA 入门: 写个非常简单的 PWA 页面,介绍了如何开启一起简单的PWA应用,感兴趣的童鞋可以去看看。总结PWA作为下一代 Web 应用模型,在国外非常受重视,在国内同样受到各大互联网企业的欢迎。去年饿了么也实现了一场PWA升级实践。之前看过一篇文章,说PWA在印度广受欢迎,因为当地2/3G网络覆盖比较多,PWA的优势就明显了。下面借用列出了一些站点,从最开始的 Flipcart,到目前的 Instangram、Uber、Twitter、Starbucks 等,不仅数量在增加,站点等级和质量也在不断地提升。图片来源篇幅有限,无法面面俱到,只能抛装引玉,欢迎批评指正~参数资料PWA(Progressive Web App)初探总结讲讲PWA用新版的 Chrome 把 PWA 网站添加到桌面,获得媲美原生应用的体验您的第一个 Progressive Web AppPWA博客App Shell 模型 ...

November 9, 2018 · 1 min · jiezi

【PWA学习与实践】(7)使用Notification API来进行消息提醒

《PWA学习与实践》系列文章已整理至gitbook - PWA学习手册,文字内容已同步至learning-pwa-ebook。转载请注明作者与出处。本文是《PWA学习与实践》系列的第七篇文章。PWA作为时下最火热的技术概念之一,对提升Web应用的安全、性能和体验有着很大的意义,非常值得我们去了解与学习。对PWA感兴趣的朋友欢迎关注《PWA学习与实践》系列文章。本文中的代码可以在learning-pwa的notification分支上找到(git clone后注意切换到notification分支)。1. 引言在第五篇文章《Web中进行服务端消息推送》中,我介绍了如何使用Push API进行服务端消息推送。提到Push就不得不说与其联系紧密的另一个API——Notification API。它让我们可以在“网站外”显示消息提示:即使当你切换到其他Tab,也可以通过提醒交互来快速让用户回到你的网站;甚至当用户离开当前网站,仍然可以收到系统的提醒消息,并且可以通过消息提醒快速打开你的网站。Notification的功能本身与Push并不耦合,你完全可以只使用Notification API或者Push API来构建Web App的某些功能。因此,本文会先介绍如何使用Notification API。然后,作为Notification的“黄金搭档”,本文还会介绍如何组合使用Push & Notification(消息推送与提醒)。2. 使用Notification API在这第二节里,我们先来了解如何独立使用Notification功能。相较于第五篇中的Push功能,Notification API更加简洁易懂。2.1. 获取提醒权限首先,进行调用消息提醒API需要获得用户的授权。在调用Notification相关API之前,需要先使用Notification对象上的静态方法Notification.requestPermission()来获取授权。由于Notification.requestPermission()在某些版本浏览器中会接收一个回调函数(Notification.requestPermission(callback))作为参数,而在另一些浏览器版本中会返回一个promise,因此将该方法进行包装,统一为promise调用:// index.jsfunction askPermission() { return new Promise(function (resolve, reject) { var permissionResult = Notification.requestPermission(function (result) { resolve(result); }); if (permissionResult) { permissionResult.then(resolve, reject); } }).then(function (permissionResult) { if (permissionResult !== ‘granted’) { throw new Error(‘We weren't granted permission.’); } });}registerServiceWorker(’./sw.js’).then(function (registration) { return Promise.all([ registration, askPermission() ]) })我们创建了一个askPermission()方法来统一Notification.requestPermission()的调用形式,并在Service Worker注册完成后调用该方法。调用Notification.requestPermission()获取的permissionResult可能的值为:denied:用户拒绝了通知的显示granted:用户允许了通知的显示default:因为不知道用户的选择,所以浏览器的行为与denied时相同chrome中,可以在chrome://settings/content/notifications里进行通知的设置与管理。2.2. 设置你的提醒内容获取用户授权后,我们就可以通过registration.showNotification()方法进行消息提醒了。当我们注册完Service Worker后,then方法的回调函数会接收一个registration参数,通过调用其上的showNotification()方法即可触发提醒:// index.jsregisterServiceWorker(’./sw.js’).then(function (registration) { return Promise.all([ registration, askPermission() ])}).then(function (result) { var registration = result[0]; /* ===== 添加提醒功能 ====== / document.querySelector(’#js-notification-btn’).addEventListener(‘click’, function () { var title = ‘PWA即学即用’; var options = { body: ‘邀请你一起学习’, icon: ‘/img/icons/book-128.png’, actions: [{ action: ‘show-book’, title: ‘去看看’ }, { action: ‘contact-me’, title: ‘联系我’ }], tag: ‘pwa-starter’, renotify: true }; registration.showNotification(title, options); }); / ======================= */})上面这段代码为页面上的button添加了一个click事件监听:当点击后,调用registration.showNotification()方法来显示消息提醒,该方法接收两个参数:title与option。title用来设置该提醒的主标题,option中则包含了一些其他设置。body:提醒的内容icon:提醒的图标actions:提醒可以包含一些自定义操作tag:相当于是ID,通过该ID标识可以操作特定的notificationrenotify:是否允许重复提醒,默认为false。当不允许重复提醒时,同一个tag的notification只会显示一次注意,由于不同浏览器中,对于option属性的支持情况并不相同。部分属性在一些浏览器中并不支持。2.3. 捕获用户的点击在上一部分中,我们已经为Web App添加了提醒功能。点击页面中的“提醒”按钮,系统就会弹出提醒框,并展示相关提醒消息。然而更多的时候,我们并不仅仅希望只展示有限的信息,更希望能引导用户进行交互。例如推荐一本新书,让用户点击阅读或购买。在上一部分我们设置的提醒框中,包含了“去看看”和“联系我”两个按钮选项,那么怎么做才能捕获用户的点击操作,并且知道用户点击了哪个呢?这一小节,就会告诉你如何实现。还记的上一部分里我们定义的actions么?…actions: [{ action: ‘show-book’, title: ‘去看看’ }, { action: ‘contact-me’, title: ‘联系我’}]…为了能够响应用户对于提醒框的点击事件,我们需要在Service Worker中监听notificationclick事件。在该事件的回调函数中我们可以获取点击的相关信息:// sw.jsself.addEventListener(’notificationclick’, function (e) { var action = e.action; console.log(action tag: ${e.notification.tag}, action: ${action}); switch (action) { case ‘show-book’: console.log(‘show-book’); break; case ‘contact-me’: console.log(‘contact-me’); break; default: console.log(未处理的action: ${e.action}); action = ‘default’; break; } e.notification.close();});e.action获取的值,就是我们在showNotification()中定义的actions里的action。因此,通过e.action就可以知道用户点击了哪一个操作选项。注意,当用户点击提醒本身时,也会触发notificationclick,但是不包含任何action值,所以在代码中将其置于default默认操作中。现在试一下,我们就可以捕获用户对于不同选项的点击了。点击后在Console中会有不同的输出。2.4. Service Worker与client通信到目前为止,我们已经可以顺利得给用户展示提醒,并且在用户操作提醒后准确捕获到用户的操作。然而,还缺最重要的一步——针对不同的操作,触发不同的交互。例如,点击提醒本身会弹出书籍简介;点击“看一看”会给用户展示本书的详情;点击“联系我”会向应用管理者发邮件等等。这里有个很重要的地方:我们在Service Worker中捕获用户操作,但是需要在client(这里的client是指前端页面的脚本环境)中触发相应操作(调用页面方法/进行页面跳转…)。因此,这就需要让Service Worker与client进行通信。通信包括下面两个部分:在Service Worker中使用Worker的postMessage()方法来通知client:// sw.jsself.addEventListener(’notificationclick’, function (e) { …… // 略去上一节内容 e.waitUntil( // 获取所有clients self.clients.matchAll().then(function (clients) { if (!clients || clients.length === 0) { return; } clients.forEach(function (client) { // 使用postMessage进行通信 client.postMessage(action); }); }) );});在client中监听message事件,判断data,进行不同的操作:// index.jsnavigator.serviceWorker.addEventListener(‘message’, function (e) { var action = e.data; console.log(receive post-message from sw, action is '${e.data}'); switch (action) { case ‘show-book’: location.href = ‘https://book.douban.com/subject/20515024/'; break; case ‘contact-me’: location.href = ‘mailto:someone@sample.com’; break; default: document.querySelector(’.panel’).classList.add(‘show’); break; }});当用户点击提醒后,我们在notificationclick监听中,将action通过postMessage()通信给client;然后在client中监听message事件,基于action(e.data)来进行不同的操作(跳转到图书详情页/发送邮件/显示简介面板)。至此,一个比较简单与完整的消息提醒(Notification)功能就完成了。然而目前的消息提醒还存在一定的局限性。例如,只有在用户访问网站期间才能有机会触发提醒。正如本文一开始所说,Push & Notification的结合将会帮助我们构筑一个强大推送与提醒功能。下面就来看下它们的简单结合。3. 消息推送与提醒在第五篇《Web中进行服务端消息推送》最后,我们通过监听push事件来处理服务端推送:// sw.jsself.addEventListener(‘push’, function (e) { var data = e.data; if (e.data) { data = data.json(); console.log(‘push的数据为:’, data); self.registration.showNotification(data.text); } else { console.log(‘push没有任何数据’); }});简单修改以上代码,与我们本文中的提醒功能相结合:// sw.jsself.addEventListener(‘push’, function (e) { var data = e.data; if (e.data) { data = data.json(); console.log(‘push的数据为:’, data); var title = ‘PWA即学即用’; var options = { body: data, icon: ‘/img/icons/book-128.png’, image: ‘/img/icons/book-521.png’, // no effect actions: [{ action: ‘show-book’, title: ‘去看看’ }, { action: ‘contact-me’, title: ‘联系我’ }], tag: ‘pwa-starter’, renotify: true }; self.registration.showNotification(title, options); } else { console.log(‘push没有任何数据’); }});使用Push来向用户推送信息,并在Service Worker中直接调用Notification API来展示该信息的提醒框。这样,即使是在用户关闭该Web App时,依然可以收到提醒,类似于Native中的消息推送与提醒。我们还可以将这个功能再丰富一些。由于用户在关闭该网站时仍然可以收到提醒,因此加入一些更强大功能:当用户切换到其他Tab时,点击提醒会立刻回到网站的tab;当用户未打开该网站时,点击提醒可以直接打开网站。// sw.jsself.addEventListener(’notificationclick’, function (e) { var action = e.action; console.log(action tag: ${e.notification.tag}, action: ${action}); switch (action) { case ‘show-book’: console.log(‘show-book’); break; case ‘contact-me’: console.log(‘contact-me’); break; default: console.log(未处理的action: ${e.action}); action = ‘default’; break; } e.notification.close(); e.waitUntil( // 获取所有clients self.clients.matchAll().then(function (clients) { if (!clients || clients.length === 0) { // 当不存在client时,打开该网站 self.clients.openWindow && self.clients.openWindow(‘http://127.0.0.1:8085’); return; } // 切换到该站点的tab clients[0].focus && clients[0].focus(); clients.forEach(function (client) { // 使用postMessage进行通信 client.postMessage(action); }); }) );});注意这两行代码,第一行会在网站关闭时打开该网站,第二行会在存在tab时自动切换到网站的tab。self.clients.openWindow && self.clients.openWindow(‘http://127.0.0.1:8085’);clients[0].focus && clients[0].focus();4. MacOS Safari中的Web Notification看一下Web Notification的兼容性:目前移动端浏览器普遍还不支持该特性。但是在Mac OS上的safari里面是支持该特性的,不过其调用方式与上文代码有些不太一样。在safari中使用Web Notification不是调用registration.showNotification()方法,而是需要创建一个Notification对象。// index.js……document.querySelector(’#js-notification-btn’).addEventListener(‘click’, function () { var title = ‘PWA即学即用’; var options = { body: ‘邀请你一起学习’, icon: ‘/img/icons/book-128.png’, actions: [{ action: ‘show-book’, title: ‘去看看’ }, { action: ‘contact-me’, title: ‘联系我’ }], tag: ‘pwa-starter’, renotify: true }; // registration.showNotification(title, options); // 使用Notification构造函数创建提醒框 // 而非registration.showNotification()方法 var notification = new Notification(title, options);});……Notification对象继承自EventTarget接口,因此在safari中需要通过添加click事件的监听来触发提醒框的交互操作:// index.jsnotification.addEventListener(‘click’, function (e) { document.querySelector(’.panel’).classList.add(‘show’);});该功能示例可以在learn-pwa/notify4safari中找到。5. 写在最后Web Notification是一个非常强大的API,尤其在和Push结合后,为WebApp带来了类似Native的丰富能力。本文中所有的代码示例均可以在learn-pwa/notification上找到。如果你喜欢或想要了解更多的PWA相关知识,欢迎关注我,关注《PWA学习与实践》系列文章。我会总结整理自己学习PWA过程的遇到的疑问与技术点,并通过实际代码和大家一起实践。到目前为止,我们已经学习了Manifest、离线缓存、消息推送、消息提醒、Debug等一些基础知识。在下一篇文章里,我们会继续了解与学习PWA中的一个重要功能——后台同步。《PWA学习与实践》系列第一篇:2018,开始你的PWA学习之旅第二篇:10分钟学会使用Manifest,让你的WebApp更“Native”第三篇:从今天起,让你的WebApp离线可用第四篇:TroubleShooting: 解决FireBase login验证失败问题第五篇:与你的用户保持联系: Web Push功能第六篇:How to Debug? 在chrome中调试你的PWA第七篇:增强交互:使用Notification API来进行提醒(本文)第八篇:使用Service Worker进行后台数据同步第九篇:PWA实践中的问题与解决方案第十篇:Resource Hint - 提升页面加载性能与体验第十一篇:从PWA离线工具集workbox中学习各类离线策略(写作中…)参考资料MDN: notificationMDN: ServiceWorkerRegistration.showNotification()MDN: WindowClientMDN: ClientsWWDC2013 ...

November 6, 2018 · 3 min · jiezi

2018你成长了么?一份给你的前端技术清单

2018 眼看就要过去了,今年的你相较去年技术上有怎样的收获呢?记得年初的时候我给自己制定了一个学习计划,现在回顾来看完成度还不错。但仍有些遗憾,一些技术点没有时间去好好学习。在学习中我发现,像文章这样的知识往往是碎片化的,而前端涉及到的面很多,如果不将这些知识有效梳理,则无法形成体系、相互串联。最后有一种东懂一块,西了解一点的感觉。因此,我结合工作体会抽象出了一些前端基础技术能力,并将这段时间学习或产出的一些不错的内容根据这些能力进行整理,形成了一份前端技术清单(github 地址)。不论你是正在自学前端遇到了瓶颈,还是对某些技术熟练掌握但某些还未涉足,都希望这份清单能对你有所帮助。由于个人精力有限,一些技术点的归纳可能有失偏颇,或者目前并未纳入进来,因此 github 上的清单内容 也会不断更新。目前只包含纯前端基础内容,NodeJS 、客户端泛前端、小程序、可视化等内容先留着坑吧。清单内容↓↓↓0. 年度报告2018 前端工具调查报告2018 JavaScript 调查报告1. 基础拾遗温故而知新,不知则习之,是以牢固根基。1.1. JavaScriptYou-Dont-Know-JS [英]JavaScript 基础运行机制:JS 引擎、运行时与调用栈概述 [英]V8 引擎简介 [英]内存管理与4中常见的泄漏 [英]Event Loop(面试里总会有一题 Event Loop…):从 Event Loop 规范探究 JavaScript 异步及浏览器更新渲染时机异步之 Event Loop [英]NodeJS 中的 Event Loop、Timers 与 process.nextTick() [英]Tasks、Microtasks、Queues 与Schedules [英]Web Workers 及其5个常见使用场景 [英]如何避免 async/await 地狱 [英]“回调地狱”的解决思路汇总1.2. CSSYou-Need-to-Know-CSSCSS布局指南CSS 中的各类换行处理方式 [英]:处理经典的换行问题浏览器将rem转成px时有精度误差怎么办?精准操控的滚动体验,浅谈新标准 Scroll Snap如何完美实现一个非button元素的按钮 [英]巧用 CSS Grid 来创建横向滚动容器 [英]如何处理内联元素中的空隙 [英]CSS Stacking Context 里那些鲜为人知的坑1.3. 浏览器浏览器的工作原理现代浏览器内部是如何运行的:Chrome浏览器概览浏览时发生了什么?渲染进程的内部工作原理compositor是如何来提高交互性能的?完整的页面生命周期 API 介绍 [英]四个新的观察者:Intersection / Mutation / Resize / Performance (Observer)渲染引擎工作方式及优化建议 [英]浏览器内核渲染:重建引擎跨域解决方案汇总2. 工程化与工具软件规模的扩大带来了工程化的需求,前端也不例外。随着 NodeJS 的出现,前端工程师可以使用熟悉的 JS 快速开发所需的工具。工具链生态的繁荣也是前端圈繁荣的一个写照。2.1. webpackwebpack 中的 Chunk 关系图算法 [英]webpack 进阶系列文章编译优化:如何提升大型项目中 webpack 的性能 ???? [英]运行时优化:Separating a Manifest [英]在 webpack 中使用 <link rel=”prefetch/preload”> [英]如何更好使用 webpack tree-shaking关于 webpack 编译缓存的讨论:mzgoddard’s comment[spec: webpack 5] - A module disk cache between build processes2.2. GulpGulp 4 简介 [英]基于Gulp的多页面应用实践指南2.3. LinterJS Linter 进化史为何要在项目汇总使用 ESLint [英]2.4. 静态类型(Typescript/Flow)Typescript 总体架构 [英]为什么要在 JavaScript 中进行静态类型检查:第一部分第二、三部分第四部分2.5. BabelBabel 用户手册Babel 插件手册2.6. CSS预处理与模块化CSS 进化史 [英]CSS 模块化方案系列3. 性能优化性能优化其实就是在理解浏览器的基础上“因地制宜”,因此可以配合1.3节“浏览器”部分进行理解。强烈推荐把 Google Web 上性能优化 Tab 中的文章都通读一遍,其基本涵盖了现代浏览器中性能优化的所有点,非常系统。下面也摘录了其中一些个人认为非常不错的篇幅。3.1. 加载性能PRPL 模式 [英]图片懒加载完全指南 [英]使用 Intersection Observer 来懒加载图片 [英]图片与视频懒加载的详细指南 [英]使用 Application Shell 架构来实现秒开应用 [英]3.2. 运行时性能避免大型、复杂的布局和布局抖动 [英]什么导致强制同步布局(reflow)? [英]如何诊断强制同步布局 [英]无线性能优化:Composite如何不择手段提升scroll事件的性能使用 passive event listener 来提高滚动流畅性 [英]节流和去抖(throttle & debounce):JavaScript 函数节流和函数去抖应用场景辨析underscore 函数去抖的实现requestIdleCallback - 一个强大而神器的 API:requestIdleCallback使用入门 [英]Idle Until Urgent [英]:requestIdleCallback的妙用3.3. 前端缓存Web 缓存简介:以购买牛奶的为例 [英]大话前端缓存 [英]缓存(一)—— 缓存总览:从性能优化的角度看缓存缓存(二)—— 浏览器缓存机制:强缓存、协商缓存缓存(三)—— 数据存储:cookie、Storage、indexedDB3.4. 性能调试与实践使用 Chrome DevTools 提升页面速度 [英]:Chrome DevTools实操讲解了解 DevTools 中的 Resource Timing淘宝新势力周H5性能优化实战优化打包策略来提升页面加载速度Chrome DevTools 中你可能不知道的调试技巧前端性能测量 [英]3.5. 性能指标以用户为中心的前端性能指标 [英]:前端性能指标的来龙去脉DOMContentLoaded:你不知道的 DOMContentLoadedDeciphering the Critical Rendering Path [英]FP (First Paint):Chrome的First PaintFCP (First Contentful Paint):First Contentful Paint Explained [英]First Contentful Paint [英]FMP (First Meaningful Paint):Chrome 中的 First Meaningful PaintTime to First Meaningful PaintTTI (Time to interactive):Time to Interactive Explainer衡量用户体验的新标准TTFB (Time To First Byte):TTFB,以及页面加载的时间节点FID (First Input Delay):First Input DelaySpeed Index:WebPagetest: Speed Index4. 安全很多安全风险老生常谈,但是往往到出现问题时,才会被重视或者意识到。8大前端安全问题上篇8大前端安全问题下篇概念讲解:编码、加密、哈希与混淆 [英]常见 Web 安全攻防总结4.1. XSS如何防止XSS攻击?4.2. CSRF如何防止CSRF攻击?Site Isolation [英]:Chrome的新特性4.3. CSPContent Security Policy 入门教程Content Security Policy (CSP) [英]4.4. HTTPS图文还原 HTTPS 原理浅谈有赞全站 HTTPS 推进4.5. 安全实录About rel=noopener [英]:打开一个新页面是如何带来安全隐患的一种新型的“钓鱼”方式 [英]一个媒体文件请求引发的跨站风险 [英]Mitigating Spectre [英]: Chrome 中的跨站安全问题5. 自动化测试自动化测试是软件工程的重要部分之一,但却极容易被忽视。2018 前端自动化测试综述 [英]测试你的前端代码(介绍篇)[英]5.1. 单元测试测试你的前端代码(单元测试篇)[英]Fakes、Mocks 以及 Stubs 概念明晰测试覆盖(率)到底有什么用?5.2. 端到端测试 (E2E)测试你的前端代码(E2E 测试篇)[英]什么是一个好的 E2E 测试?[英]平衡单元测试和端到端测试对过多的 E2E 测试说“不” [英]5.3. 其他测试你的前端代码(集成测试篇)[英]测试你的前端代码(可视化测试篇)[英]6. 框架与类库如果说基础知识是道,那框架与工具可能就是术;学习与理解它们,但千万不要成为它们的奴隶。6.1. ReactReact 底层揭秘 [英]你所需要知道的 React 细节React Fiber 架构React 16 Fiber 源码速览React 是怎样炼成的:React早期的进化之路从零开始实现一个React:1. JSX和虚拟DOM2. 组件和生命周期3. diff算法4. 异步的setState「react技术栈」单页应用实践快速入门6.2. Vue深入浅出 - vue变化侦测原理Vue 模板编译原理6.3. Redux重新设计 Redux [英]:Rematch如何用 GraphQL 来替代 Redux [英]解读 Redux 的设计思路与用法(Redux)应用构建的三个原则 [英]6.4. RxJSReactiveX 官网:宝石图真的非常形象易读响应式编程,是明智的选择图解RxJS [英]调试RxJS:Tooling [英]调试RxJS:Logging [英]7. 新技术/方向前端领域新技术、新方向层出不穷,这里汇总一些新技术方向;作为开发者需要多了解但是不要盲从7.1. PWAPWA 学习与实践系列Service Worker 入门简介 [英]PWA 在 iOS 平台上的特殊问题 [英]在你的 PWA 中小心使用 iOS 的 meta 标签 [英]饿了么的 PWA 升级实践离线指南Android 中的 WebAPKs [英]Pinterest 的 PWA 实践 [英]异步 HTTP Cookies API [英]:赋能Service Worker7.2. CSS Houdini认识 Houdini 与 CSS Paint API [英]用 Houdini 来拯救 CSS Polyfill [英]7.3. Web ComponentsWeb Components 基本概念和用法Web Components 指南 [英]Shadow DOM 使用简介HTMLUnknownElement 与 HTML5 自定义元素的故事7.4. 微前端(Micro Frontends)微前端主页 [英]微前端的那些事儿技术雷达之「微前端」- 将微服务理念扩展到前端开发7.5. HTTP/2HTTP/2 幕后原理全面介绍的 HTTP/2 [英]HTTP/2 主页:HTTP/2 协议 [英]HPACK: HTTP/2 Header压缩 [英]7.6. WebAssemblyWebAssembly 官网WebAssembly 现状与实战WebAssembly 系列:一、生动形象地介绍 WebAssembly二、JavaScript Just-in-time (JIT) 工作原理三、编译器如何生成汇编四、WebAssembly 工作原理五、为什么 WebAssembly 更快?六、WebAssembly 的现在与未来8. 业务相关在业务中往往还有一些与“业务无关”的场景需求,不论是什么业务几乎都会遇到;因此,在变与不变中,我们更需要去抽象出这些问题。8.1. 数据打点上报如何精确统计页面停留时长揭开JS无埋点技术的神秘面纱8.2. 前端监控前端异常监控解决方案研究监控平台前端SDK开发实践把前端监控做到极致前端监控系统探索总结8.3. A/B测试Twitter的A/B测试实践:一、为什么要测试以及测试的意义二、技术概述三、检测和避免 A/B Test中 bucket不平衡问题四、A/B Test中使用多个控制的启示Netflix A/B Test 实验平台实践 [英]指导方法实验中容易遇到的七种问题 [英]实验的七个准则 [英]小流量如何进行AB测试案例分享大众点评AB测试框架Gemini新浪新闻客户端AB测试与灰度发布天猫App A/B测试实践工具AB测试样本数量计算器AB测试结果有效性分析工具8.4. “服务端推”各类“服务器推”技术原理与实例长连接/websocket/SSE等主流服务器推送技术比较Comet:基于 HTTP 长连接的“服务器推”技术深入 WebSockets、HTTP/2 SSE [英]WebSocket 应用安全问题分析8.5. 动效动画设计的12个原则???? [英]贝塞尔曲线扫盲动画:从 AE 到 Web最全最好用的动效落地方法:基础知识落地方式9. 不归类的好文开卷有益。Recursion? We don’t need no stinking recursion!:如何将一些递归改为循环(尾递归优化)Turning your web traffic into a Super Computer:通过 Web Worker 和 WebSocket 来将全世界的电脑连接成超级计算机Designing very large (JavaScript) applications:高屋建瓴,适合阅读与思考 ...

November 5, 2018 · 3 min · jiezi

前端数据扁平化与持久化

(PS: 时间就像海绵里的水,挤到没法挤,只能挤挤睡眠时间了~ 知识点还是需要整理的,付出总会有收获,tired but fulfilled~)前言最近业务开发,从零搭建网页生成器,支持网页的可视化配置。为了满足这种需求,需要将各种页面抽象成类似地模块,再将每个模块抽象成各个可配置的组件,有些组件还包含一些小部件。这样一来,页面配置的JSON数据就会深层级地嵌套,那么修改一个小组件的配置,要怎样来更新页面树的数据?用id一层一层遍历?这样做法当然是不推荐的,不仅性能差,代码写起来也麻烦。因此,就考虑能否像数据库一样,把数据范式化,将嵌套的数据展开,每条数据对应一个id,通过id直接操作。Normalizr 就帮你做了这样一件事情。另外考虑到页面编辑,就需要支持 撤销 与 重做的功能,那么要怎样来保存每一步的数据?页面编辑的数据互相关联,对象的可变性会带来很大的隐患。虽然JS中的const(es6)、Object.freeze(es5) 可以防止数据被修改,但它们都是shallow处理,遇到嵌套多和深的结构就需要递归处理,而递归又存在性能上的问题。这时,用过React的童鞋就知道了,React借助 Immutable 来减少DOM diff的比对,它就能够很好地解决上面这两个问题。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。那么为什么在JS中,诸如对象这样的数据类型是可变的呢?我们先来了解一下JS的数据类型。JS数据类型JS的数据类型包括基本类型和引用类型。基本类型包括String、Number、 Boolean、Null、Undefined,引用类型主要是对象(包括Object、Function、Array、Data等)。基础类型的值本身无法被改变,而引用类型,如Object,是可以被改变的。本文讨论的数据不可变,就是指保持对象的状态不变。来看看下面的例子:// 基本类型var a = 1;var b = a;b = 3;console.log(a); // 1console.log(b); // 3// 引用类型var obj1 = {};obj1.arr = [2,3,4];var obj2 = obj1;obj2.arr.push(5);console.log(obj1.arr); // [2, 3, 4, 5]console.log(obj2.arr); // [2, 3, 4, 5]上面例子中,b的值改变后,a的值不会随着改变;而obj2.arr被修改后,obj1.arr的值却跟着变化了。这是因为JS对象中的赋值是“引用赋值”,即在赋值的过程中,传递的是在内存中的引用。这也是JS中对象为什么有深拷贝和浅拷贝的用法,只有深拷贝后,对新对象的修改才不会改变原来的对象。浅拷贝只会将对象的各个属性进行依次复制,并不会进行递归复制,而 JavaScript 存储对象都是存地址的。上面代码中,只是执行了浅拷贝,结果导致 obj1 和 obj2指向同一块内存地址。所以修改obj2.arr,obj1.arr的值也变了。如果是深拷贝(如Lodash的cloneDeep)则不同,它不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深拷贝的方法递归复制到新对象上,也就不会存在上面 obj1 和 obj2 中的 arr 属性指向同一个内存对象的问题。为了更清晰地理解这个问题,还是得来了解下javascript变量的存储方式。数据类型的存储程序的运行都需要内存,JS语言把数据分配到内存的栈(stack)和堆(heap)进行各种调用(注:内存中除了栈和堆,还有常量池)。JS这样分配内存,与它的垃圾回收机制有关,可以使程序运行时占用的内存最小。在JS中,每个方法被执行时,都会建立自己的内存栈,这个方法内定义的变量就会一一被放入这个栈中。等到方法执行结束,它的内存栈也自然地销毁了。因此,所有在方法中定义的变量都是放在栈内存中的。当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。总的来说,栈中存储的是基础变量以及一些对象的引用变量,基础变量的值是存储在栈中,而引用变量存储在栈中的是指向堆中的对象的地址,这就是修改引用类型总会影响到其他指向这个地址的引用变量的原因。堆是运行时动态分配内存的,存取速度较慢,栈的优势是存取速度比堆要快,并且栈内的数据可以共享,但是栈中数据的大小与生存期必须是确定的,缺乏灵活性。Normalizr与范式化范式化(Normalization)是数据库设计中的一系列原理和技术,以减少数据库中数据冗余,增进数据的一致性。直观地描述就是寻找对象之间的关系,通过某种方式将关系之间进行映射,减少数据之间的冗余,优化增删改查操作。Normalizr库本身的解释就是Normalizes nested JSON according to a schema),一种类似于关系型数据库的处理方法,通过建表建立数据关系,把深层嵌套的数据展开,更方便灵活的处理和操作数据。来看个官网的例子,理解一下:{ “id”: “123”, “author”: { “id”: “1”, “name”: “Paul” }, “title”: “My awesome blog post”, “comments”: [ { “id”: “324”, “commenter”: { “id”: “2”, “name”: “Nicole” } } ]}这是一份博客的数据,一篇文章article有一个作者author, 一个标题title, 多条评论,每条评论有一个评论者commenter,每个commenter又有自己的id和name。这样如果我们要获取深层级的数据,如commenter时,就需要层层遍历。这时候,如果使用Normalizr,就可以这样定义Schema:import { schema } from ’normalizr’; const user = new schema.Entity(‘users’);const comment = new schema.Entity(‘comments’, { commenter: user});const article = new schema.Entity(‘articles’, { author: user, comments: [comment]});然后调用一下 Normalize,就可以得到扁平化后的数据,如下:{ “entities”: { “users”: { “1”: { “id”: “1”, “name”: “Paul” }, “2”: { “id”: “2”, “name”: “Nicole” } }, “comments”: { “324”: { “id”: “324”, “commenter”: “2” } }, “articles”: { “123”: { “id”: “123”, “author”: “1”, “title”: “My awesome blog post”, “comments”: [“324”] } } }, “result”: “123”}这样每个作者、每条评论、每篇文章都有对应的id, 我们就不需要遍历,可以直接拿对应的id进行修改。再来看下我们在项目中的示例代码:分别定义element、section 和 page三张表,并指定它们之间的关系。这样范式化后,想对某个页面某个模块或者某个元素进行增删查改,就直接拿对应的id,不需要再耗性能去遍历了。Immutable与持久化Facebook工程师Lee Byron花了3年时间打造Immutable,与 React 同期出现。Immutable Data,维基百科上是这样定义的:In computing, a persistent data structure is a data structure that always preserves the previous version of itself when it is modified. Such data structures are effectively immutable, as their operations do not (visibly) update the structure in-place, but instead always yield a new updated structure.简单来说,Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享,这样就避免了深拷贝带来的性能损耗。我们通过图片来理解一下:Immutable 内部实现了一套完整的持久化数据结构,有很多易用的数据类型,如Collection、List、Map、Set、Record、Seq(Seq是借鉴了Clojure、Scala、Haskell这些函数式编程语言,引入的一个特殊结构)。它有非常全面的map、filter、groupBy、reduce、find等函数式操作方法。它的Api很强大,大家有兴趣可以去看下。这里简单列举 updateIn/getIn 来展示它带来的一些便捷操作:var obj = { a: { b: { list: [1, 2, 3] } }};var map = Immutable.fromJS(obj); // 注意 fromJS这里实现了深转换var map2 = Immutable.updateIn([‘a’, ‘b’, ’list’], (list) => { return list.push(4);});console.log(map2.getIn([‘a’, ‘b’, ’list’]))// List [ 1, 2, 3, 4 ]代码中我们要改变数组List的值,不必一层一层获取数据,而是直接传入对应的路径修改就行。这种操作在数据嵌套越深时,优势更加明显。来看下我们业务代码的示例吧。这里在多个页面的模块配置中,要更新某个页面的某个模块的数据,我们只需要在updateIn传入对应的path和value,就可以达到预想的效果。篇幅有限,更多的示例请自行查看api。熟悉React的同学也基于它结构的不可变性和共享性,用它来能够快速进行数据的比较。原本React中使用PureRenderMixin来做DOM diff比较,但只是浅比较,当数据结构比较深的时候,依然会存在多余的diff过程。这里只提个点,不深入展开了,感兴趣的同学可以自行google。与 Immutable.js 类似的,还有个seamless-immutable,它的代码库非常小,压缩后下载只有 2K。而 Immutable.js 压缩后下载有16K。大家各取所需,根据实际情况,自己斟酌下使用哪个比较适合。优缺点什么事物都有利弊,代码库也不例外。这里列举下它们的优缺点,大家权衡利弊,一起来看下:Normalizr 可以将数据扁平化处理,方便对深层嵌套的数据进行增删查改,但是文档不是很清晰,大家多查多理解,引入库文件也会增大。Immutable 有持久化数据解构,如List/Map等,并发安全。第二,它支持结构共享,比cloneDeep 性能更优,节省内存。第三,它借鉴了Clojure、Scala、Haskell这些函数式编程语言,引入了特殊结构Seq,支持Lazy operation。Undo/Redo,Copy/Paste,甚至时间旅行这些功能对它来说都是小菜一碟。缺点方面,Immutable源文件过大,压缩后有15kb。其次,侵入性强,与原生api容易混淆。第三,类型转换比较繁琐,尤其是与服务器交互频繁时,这种缺点就更加明显。总结篇幅有限,时间也比较晚了,关于前端数据的扁平化与持久化处理先讲这么多了,有兴趣的同学可以关注下,后面有时间会多整理分享。参考资料前端数据范式化Immutable详解及React中实践为什么需要Immutable.jsfacebook immutable.js 意义何在,使用场景?一些链接, 关于不可变数据 ...

September 3, 2018 · 2 min · jiezi