关于前端优化:高级前端进阶必修自主打造高扩展的业务组件库含有笔记分享

download:高级前端进阶必修:自主打造高扩大的业务组件库含有笔记分享独身模式只会更懒。序 说到设计模式,十有八九,singleton模式在面试中排名第一,这肯定是大多数人从入门到面试都无奈回避的基础知识。然而,singleton模式中并不只有懈怠模式和饥饿模式,大多数都是最根本的模式。如果你读过spring之类的出名框架源代码,你会发现他们的singleton模式编写和你所晓得的齐全不一样。本文将为您带来singleton pattern的根本->最优->附加举荐法,帮忙您在面试中取得疯狂分。 又懒又饿1.饥饿的中国模式 饿模式简略的了解就是提前创建对象。长处:编写简略,没有线程同步的问题。毛病:因为对象要提前创立,所以不论用不必,总是占用内存。倡议:如果对象小而简略,应用饿中文模式。 公共最终类Singleton {//创立良好的实例公有动态Singleton实例= new Singleton();//构造函数公有Singleton() {}//获取实例公共动态Singleton getInstance() {返回实例;}}复制代码 2.懈怠模式 对懈怠模式的简略了解就是在须要的时候创建对象。长处:懒加载模式性能更高。毛病:思考多线程的同步。举荐:只有不合乎下面举荐的饿人条件,就用懒人模式。 公共最终类Singleton {公有动态单例实例= null//构造函数公有Singleton() {}//获取实例公共动态Singleton getInstance() {//仅在对象为空时实例化该对象if (null == instance) {instance = new Singleton();}返回实例;}}复制代码 同步锁 懒人的毛病就是多线程同步的问题,你能够马上想到用同步锁来解决这个问题。这里应用synchronized关键字,通过代码块升高锁的粒度,最大水平的保障了性能开销。其实从java8开始,synchronized的性能曾经有了很大的晋升。 公共最终类Singleton {公有动态单例实例= null//构造函数公有Singleton() {}//获取实例公共动态Singleton getInstance() {//获取对象时增加同步锁if (null == instance) {synchronized (Singleton.class) {instance = new Singleton();}}返回实例;}}复制代码 双重查看锁 尽管下面用了同步锁代码块,勉强解决了线程同步的问题,最大水平优化了性能开销,但实际上多线程环境下还是存在线程平安问题的。当依然有多个线程进入if判断时,这个线程平安问题依然存在。尽管这种状况不肯定会产生,但极其状况下产生的概率很大。这时候你就须要用到DCL了,就是你在面试中喜爱问的对于设计模式的双检锁模式。听起来很高大上,其实又多了一层判断。说白了就是进入同步锁前后都查看,大大减少了线程平安的问题。 公共最终类Singleton {公有动态单例实例= null//构造函数公有Singleton() {}//获取实例公共动态Singleton getInstance() {//第一个判断,当instance为null时,实例化对象。if(null == instance) {synchronized (Singleton.class) {//第二次判断,放入同步锁,实例为空时实例化对象if(null == instance) {instance = new Singleton();}}}返回实例;}}复制代码 最佳双重查看锁 双检锁模式是单懒模式解决多线程下平安问题的最佳计划之一,但仍不是最好的写法。这里是指令重排的概念,它在java内存模型中。我用最简略的形式帮你了解。在Java中,一个对象在内存中执行指令的失常程序是:调配->创立->援用,而在多线程环境中,因为语句的优化,JVM可能会重新排列程序:调配->援用->创立。如果呈现这种状况,下面的双重查看锁定办法依然不能解决线程平安问题。解决办法很简略,只需增加一个volatile关键字。volatile关键字的作用:保障可见性和有序性。 公共最终类Singleton {//增加volatile关键字private volatile static Singleton实例= null//构造函数公有Singleton() {}//获取实例公共动态Singleton getInstance() {//第一个判断,当instance为null时,实例化对象。if(null == instance) {synchronized (Singleton.class) {//第二次判断,放入同步锁,实例为空时实例化对象if(null == instance) {instance = new Singleton();}}}返回实例;}}复制代码 ...

September 5, 2022 · 1 min · jiezi

关于前端优化:极客大学前端进阶训练营2020最新版内置文档资料

极客大学-前端进阶训练营【2020最新版内置文档资料】准确的路由菜单或树形构造的JSON数据递归获取实现总结在理论开发中,对树形JSON数据处理的业务场景十分常见;而且这些数据必然是多层级的,那么很重要的一点就是要做到递归。特地是在动静路由筛选和树形构造数据筛选中。正所谓,“工欲善其事,必先利其器”。所以本文总结几种用于解决失去咱们业务中所需的真正数据的办法。二.实现办法 “splice”办法 (不举荐): 阐明:这种办法只适应于唯一性的条件(比方:过滤唯一性id)的场景。如果是用在筛选树形数据或者路由菜单(比方:过滤hidden为true)的场景,会因为其实正向删除index值,故导致第一次除外的每次遍历递归的index值不精确,所以删除的值也是不对的,故不举荐。 代码实现: //只实用于唯一性的id的递归过滤function filterData(arr, id) { arr.forEach((item, index) => { if (item.id === id) { arr.splice(index, 1) } if (item.children && item.children.length) { filterData(item.children, id) } })}

August 7, 2022 · 1 min · jiezi

DailyENJS第12期V8引擎和JavaScript优化技巧

DailyENJS 致力于翻译优秀的前端英文技术文章,为技术同学带来更好的技术视野。 V8是Google用来编译JavaScript的引擎。Firefox拥有自己的名为SpiderMonkey的引擎,与V8十分相似,但有所不同。我们将在本文中讨论V8引擎。有关V8引擎的一些事实: 用C ++编写,并在Chrome和Node.js(以及Microsoft Edge的最新版本)中使用实现 ECMA-262 标准那么,当我们发送要由V8引擎解析的JavaScript时(在将其缩小,丑化之后以及您对JavaScript代码进行的其他疯狂处理之后),到底发生了什么? 我画了下面这个图,图里显示了所有步骤,然后我们将详细讨论每个步骤: 在本文中,我们将讨论JavaScript代码是如何解析的,以及如何尽可能地让你的 JavaScript 走到 Optimising Compiler。Optimizing Compiler(又名Turbofan)将我们的JavaScript代码转换为高性能机器代码,因此,我们可以给它提供的代码越多,我们的应用程序就会越快。附带说明一下,Chrome中的解释器称为 Ignition。 解析JavaScript因此,我们的JavaScript代码第一步是对其进行解析。让我们讨论一下到底是什么解析。 解析分为两种方式: 非惰性(完全解析)-这会立即解析每行懒惰(预先解析)-进行最少的工作,解析我们需要的内容,其余部分留到以后哪个更好? 让我们看一些代码。 // 立即解析const a = 1;const b = 2;// 惰性解析,因为暂时用不到function add(a, b) { return a + b;}// 因为使用到了,所以返回去 解析add(a, b);在这里变量声明将被立即解析了,但是我们的函数将被延迟解析。在我们添加 add(a,b) 之前,这是很棒的,但是因为我们需要使用这个函数,因此立即解析 add 会更快。 为了立即解析 add 函数,我们可以执行以下操作: // 立即解析const a = 1;const b = 2;// 立即解析var add = (function(a, b) { return a + b;})();// 当我们使用到这个函数的时候已经被解析了add(a, b);这就是你使用的大多数 module 的创建方式。那么,有可以解析出性能最佳的JavaScript应用程序的最佳方法吗? ...

October 17, 2019 · 3 min · jiezi

前端代码质量圈复杂度原理和实践

写程序时时刻记着,这个将来要维护你写的程序的人是一个有严重暴力倾向,并且知道你住在哪里的精神变态者。1. 导读你们是否也有过下面的想法? 重构一个项目还不如新开发一个项目...这代码是谁写的,我真想...你们的项目中是否也存在下面的问题? 单个项目也越来越庞大,团队成员代码风格不一致,无法对整体的代码质量做全面的掌控没有一个准确的标准去衡量代 码结构复杂的程度,无法量化一个项目的代码质量重构代码后无法立即量化重构后代码质量是否提升针对上面的问题,本文的主角 圈复杂度 重磅登场,本文将从圈复杂度原理出发,介绍圈复杂度的计算方法、如何降低代码的圈复杂度,如何获取圈复杂度,以及圈复杂度在公司项目的实践应用。 2. 圈复杂度2.1 定义圈复杂度 (Cyclomatic complexity) 是一种代码复杂度的衡量标准,也称为条件复杂度或循环复杂度,它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。简称 CC 。其符号为 VG 或是 M 。 圈复杂度 在 1976 年由 Thomas J. McCabe, Sr. 提出。圈复杂度大说明程序代码的判断逻辑复杂,可能质量低且难于测试和维护。程序的可能错误和高的圈复杂度有着很大关系。 2.2 衡量标准代码复杂度低,代码不一定好,但代码复杂度高,代码一定不好。圈复杂度代码状况可测性维护成本1 - 10清晰、结构化高低10 - 20复杂中中20 - 30非常复杂低高>30不可读不可测非常高3. 计算方法3.1 控制流程图控制流程图,是一个过程或程序的抽象表现,是用在编译器中的一个抽象数据结构,由编译器在内部维护,代表了一个程序执行过程中会遍历到的所有路径。它用图的形式表示一个过程内所有基本块执行的可能流向, 也能反映一个过程的实时执行过程。 下面是一些常见的控制流程: 3.2 节点判定法有一个简单的计算方法,圈复杂度实际上就是等于判定节点的数量再加上1。向上面提到的:if else 、switch case 、 for循环、三元运算符等等,都属于一个判定节点,例如下面的代码: function testComplexity(*param*) { let result = 1; if (param > 0) { result--; } for (let i = 0; i < 10; i++) { result += Math.random(); } switch (parseInt(result)) { case 1: result += 20; break; case 2: result += 30; break; default: result += 10; break; } return result > 20 ? result : result;}上面的代码中一共有1个if语句,一个for循环,两个case语句,一个三元运算符,所以代码复杂度为 4+1+1=6。另外,需要注意的是 || 和 && 语句也会被算作一个判定节点,例如下面代码的代码复杂为3: ...

October 14, 2019 · 4 min · jiezi

WebP-大战-JPEG谁才是真正的王者

目前在互联网上,图片流量仍占据较大的一部分。因此,在保证图片质量不变的情况下,节省流量带宽是大家一直需要去解决的问题。传统的图片格式,如 JPEG,PNG,GIF 等格式图片已经没有太多的优化空间。因此 Google 于 2010 年提出了一种新的图片压缩格式 — WebP,给图片的优化提供了新的可能。 WebP,JPEG 的升级版WebP 就像 JPEG 的升级版。它是 Google 推出的图片文件格式,它的目的就是为 Web 上的图片资源提供卓越的有损、无损压缩。在与其他格式同等质量指数下提供更小,更丰富的图片资源,以便资源在 Web 上访问传输。 WebP 图片格式来源于 VP8 视频编解码器,也就是 WebM 视频容器,是 WebM 视频格式地单个压缩框架。VP8 编解码器的一个强大功能就是能够进行帧内压缩,或者更确切地说,能将视频的每个帧都被压缩,再压缩帧与帧之间的差异。 WebP 特性 有损压缩:有损压缩基于 VP8 关键编码。VP8 是 On2 Technologies 创建的视频格式,是 VP6 和 VP7 格式的后续版本。无损压缩:采用预测变换,颜色变换,减去绿色,LZ77 反响参考等技术进行压缩。透明度:8位 Alpha 通道对图形图像很有用。Alpha 通道可以于有损 RGB 一起使用。与其它格式图片所不支持的 WebP 特有的功能。动画:它支持真彩色动画图像,即可以支持动态图( 类Gif 图)元数据:它可能具有 EXIF 和 XMP 元数据颜色配置文件:它可能具有嵌入式 ICC 配置文件。关于压缩 为什么可以对图片进行有损压缩,因为使用有损压缩的一个关键原则是:人类的感知能力并没有计算机那么精确。科学证明,人的眼睛只能区分 1000 万种不同的颜色,并且人眼对亮度比色度更敏感,这意味着我们会忽略较大的色度变化,而不影响我们对图片的敏感度。这就是为什么“黑蓝白金裙”事件能引起那么大的讨论,它一定上也是受到我们人类的视觉敏感度所影响。 有损压缩 WebP 的压缩是使用与 VP8 相同的方式来预测帧。VP8 基于块预测与任何基于块的编解码器一样,VP8 将帧划分为称为宏块(MarcoBlocking)的小块。在每个宏块内,编码器可以基于先前处理的块来预测冗余运动和颜色信息。图像帧是“关键”,意思是它仅使用已经在每个宏块的直接空间邻域中解码的像素。并试图对它们的未知部分进行赋值。这就称为与预测编码。然后可以从块中减去冗余数据,进而有效压缩。 ...

June 27, 2019 · 1 min · jiezi

link标签的几个用法帮助提高页面性能

写在前面本文首发于公众号:符合预期的CoyPanHTML 中<link>元素规定了外部资源与当前文档的关系。最常见的用法,是用来链接一个外部的样式表,比如: <link href="main.css" rel="stylesheet">link标签还能做一些其他的事情,来帮助我们提高页面性能。 link标签的使用来看一下link标签除了链接外部样式表之外的一些使用场景。 DNS PrefetchDNS预解析。 这个大多数人都知道,用法也很简单: <link rel="dns-prefetch" href="//example.com">DNS解析,简单来说就是把域名转化为ip地址。我们在网页里使用域名请求其他资源的时候,都会先被转化为ip地址,再发起链接。dns-prefeth使得转化工作提前进行了,缩短了请求资源的耗时。 什么时候使用呢?当我们页面中使用了其他域名的资源时,比如我们的静态资源都放在cdn上,那么我们可以对cdn的域名进行预解析。浏览器的支持情况也不错。 Preconnect预链接。 使用方法如下: <link rel="preconnect" href="//example.com"><link rel="preconnect" href="//cdn.example.com" crossorigin>我们访问一个站点时,简单来说,都会经过以下的步骤: DNS解析TCP握手如果为Https站点,会进行TLS握手使用preconnect后,浏览器会针对特定的域名,提前初始化链接(执行上述三个步骤),节省了我们访问第三方资源的耗时。需要注意的是,我们一定要确保preconnect的站点是网页必需的,否则会浪费浏览器、网络资源。 浏览器的支持情况也不错: Prefetch预拉取。 使用方法如下: <link rel="prefetch" href="//example.com/next-page.html" as="document" crossorigin="use-credentials"><link rel="prefetch" href="/library.js" as="script">link标签里的as参数可以有以下取值: audio: 音频文件video: 视频文件 Track: 网络视频文本轨道 script: javascript文件style: css样式文件font: 字体文件 image: 图片 fetch: XHR、Fetch请求worker: Web workersembed: 多媒体<embed>请求 object: 多媒体<object>请求document: 网页预拉取用于标识从当前网站跳转到下一个网站可能需要的资源,以及本网站应该获取的资源。这样可以在将来浏览器请求资源时提供更快的响应。 如果正确使用了预拉取,那么用户在从当前页面前往下一个页面时,可以很快得到响应。但是如果错误地使用了预拉取,那么浏览器就会下载额外不需要的资源,影响页面性能,并且造成网络资源浪费。 这里需要注意的是,使用了prefetch,资源仅仅被提前下载,下载后不会有任何操作,比如解析资源。 Prerender预渲染。 <link rel="prerender" href="//example.com/next-page.html">prerender比prefetch更进一步。不仅仅会下载对应的资源,还会对资源进行解析。解析过程中,如果需要其他的资源,可能会直接下载这些资源。这样,用户在从当前页面跳转到目标页面时,浏览器可以更快的响应。 浏览器的支持情况如下: Resource Hints上面的四种用法,其实就是:Resource Hints。 Resource Hints ,翻译过来是【资源提示】。w3c的概括为: This specification defines the dns-prefetch, preconnect, prefetch, and prerender relationships of the HTML Link Element (<link>). These primitives enable the developer, and the server generating or delivering the resources, to assist the user agent in the decision process of which origins it should connect to, and which resources it should fetch and preprocess to improve page performance.此规范定义HTML链接元素(<link>)的DNS预取、预连接、预取和预渲染关系。这些原语使开发人员和生成或传递资源的服务器能够帮助用户代理决定应该连接到哪个源,以及应该获取哪些资源,并进行预处理以提高页面性能。 ...

June 3, 2019 · 1 min · jiezi

如何从请求传输渲染3个方面提升Web前端性能

什么是WEB前端呢?就是用户电脑的浏览器所做的一切事情。我们来看看用户访问网站,浏览器都做了哪些事情: 输入网址 –> 解析域名 -> 请求页面 -> 解析页面并发送页面中的资源请求 -> 渲染资源 -> 输出页面 -> 监听用户操作 -> 重新渲染。 通过上面的路径可以看出浏览器分为请求、传输、渲染三部分来实现用户的访问,本文就从这三个部分来浅析如何提升WEB前端性能。 一、请求浏览器为了减少请求传输,实现了自己的缓存机制。浏览器缓存就是把一个已经请求过的Web资源拷贝一份副本存储在浏览器中,当再次请求相同的URL时,先去查看缓存,如果有本地缓存,浏览器缓存机制会根据验证机制(Etag)和过期机制(Last-Modified)进行判断是使用缓存,还是从服务器传输资源文件。具体流程如下图所示: 浏览器的请求有些是并发的,有些是阻塞的,比如:图片、CSS、接口的请求是并发;JS文件是阻塞的。请求JS的时候,浏览器会中断渲染进程,等待JS文件加载解析完毕,再重新渲染。所以要把JS文件放在页面的最后。 JS也可以通过两种方式由阻塞改成并行:一种是通过创建script标签,插入DOM中;另一种是在Script标签中增加async属性。 每种浏览器对同一域名并发的数量有限制,IE6/7是2,IE9是10,其他常见的浏览器是6,所以减少资源请求数量和使用多域名配置资源文件,能大大提高网站性能。 减少资源请求数量的方法,大致有以下几种: 1、通过打包工具,合并资源,减少资源数量。就是开发版本是很多个资源文件,部署的时候,按类合并成几个文件来输出。在实现模块管理的同时,实现统一输出。 2、CSS中,使用css sprite减少图片请求数量。 3、通过延迟加载技术,在用户无感知的情况下请求资源。 4、通过服务器配置,实现一次请求,返回多个资源文件,如淘宝CDN那样。 除了减少请求数量,也可以使用CDN镜像,来减少网络节点,实现快速响应。使用了CDN的请求,会根据用户所处的地理位置,找寻最近的CDN节点,如果请求是新的,则从资源服务器拷贝到节点,然后再返回给客户端。如果请求已经存在,则直接从节点返回客户端。 通过上面我们了解的缓存机制,如果我们部署上线的时候,是需要刷新缓存的。普通缓存通过强刷就能改过来,而CDN缓存则需要通过改变URL来实现。同时我们不可能要求用户按着Ctrl来刷新,所以通过打包工具,在部署的时候,统一更改URL是最有效的方式。而不常变更的库文件,比如echart、jquery,则不建议更改。 二、传输从服务器往客户端传输,可以开启gzip压缩来提高传输效率。 Gzip有从1-10的十个等级。越高压缩的越小,但压缩使用的服务器硬件资源就越多。根据实践,等级为5的时候最均衡,此时压缩效果是100k可以压缩成20k。 三、渲染浏览器在加载了html后,就会一边解析,一边根据解析出来的结果进行资源请求,并生成DOM树。而加载完毕的CSS,则被渲染引擎根据生成好的DOM树,来生成渲染树。等所有资源解析完毕计算好layout后,向浏览器界面绘制。随着用户操作,JS会修改DOM节点或样式,重新绘制和重新排列。重新绘制指的是绘制DOM节点对应的渲染节点,重新排列是指重新计算这些节点在浏览器界面的位置。很显然,重排是非常耗性能的。我们要做的是减少重排的次数。 生成DOM树的时候,我们可以通过减少DOM节点来优化性能。最初都是用table布局,节点深度和数量相当复杂,性能很差。同样CSS作为层叠样式表,层级也不可太深,不然遍历的成本很高。另外CSS的expression属性相当耗性能,能不用则不用。动画效果能用CSS写的就不用JS写,渲染引擎不一样,性能损耗也不一样。 上面说的是解析渲染的过程,我们再接着说说用户交互操作的过程。用户操作就会导致重绘和重排,重排一定会引起重绘,而重绘不一定会引起重排。到底怎样会引起重排呢?简单的定义,DOM结构的变化,以及DOM样式中几何属性的变化,就会导致重排。几何属性顾名思义,就是宽、高、边框、外补丁、内补丁等俗称盒模型的属性。同时还有offset之类的边距属性。 重排是最耗能的,减少重排的方法有: 1、如果需要多次改变DOM,则先在内存中改变,最后一次性的插入到DOM中。 2、同上一条,如果多次改变样式,合成一条,再插入DOM中。 3、由于position的值为absoute和fixed时候,是脱离文档流的,操作此类DOM节点,不会引起整页重排。所以动画元素设置position使其脱离文档流。 4、当DOM节点的display等于none的时候,是不会存在于渲染树的,所以如果有比较复杂的操作,先使其display等于none,等待所有操作完毕后,再将display设成block,这样就只重排两次。 5、获取会导致重排的属性值时,存入变量,再次使用时就不会再次重排。获取这些属性会导致重排:offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 以上就是浏览器如何把资源变成肉眼所见的页面的,除了上述根据浏览器流程而总结出来的性能优化,我们还需要看看javascript作为程序,需要的优化。先来看看javascript的垃圾回收机制。 Javascript的引擎会在固定的时间间隔,将不再使用的局部变量注销掉,释放其所占的内存。而闭包的存在,将使引用一直存在,无法被释放掉。全局变量的生命周期直至浏览器卸载页面才会结束。所以一般来讲,内存溢出就是由于全局变量的不释放和闭包引起。为了防止内存溢出,我们可以做的方法有: 1、业务代码放在匿名立即执行函数里面,执行完毕会立即释放掉。 2、少用全局变量,同时用完的变量手动注销掉。 3、使用回调来代替闭包访问内部属性 4、当不可避免使用闭包时,慎重的对待其中的细节。不用的时候注销掉。 5、通过浏览器自带的工具profiles,来检查内存活动情况。如果是波浪型的,说明正常。如果是倾斜式渐进上涨的,说明有内存不会被释放,需要检查相应的函数。 最后再说一点,函数里返回异步取的值,经常有人这么: Var getList = function(){ $.ajax().then(function(data){ Return data;}) };Var users = getList();毫无疑问,由于函数内的返回是异步的,所以返回只能是undefined,而不是想要的data。于是为了实现返回data,就把ajax的async属性设置成了false,由异步改为同步,来获取到data。然而最大的问题来了,同步是会中断渲染进程的,也就是请求返回的等待中,整个页面是卡死的,用户操作也不会有响应。这个问题真正的解决方案是返回promise对象,而不是把异步改成同步。 作者:马宗泽 拓展阅读:数据中台:宜信敏捷数据中台建设实践|分享实录

May 30, 2019 · 1 min · jiezi

前端优化

链接:前端优化前端优化浏览器发送HTTP请求,服务器收到请求全文后,返回HTTP响应,在浏览器接收之后结束这个过程。浏览器和服务器只有一次互动的机会,浏览器主动发起,而服务器被动地根据收到的请求内容返回结果。一个完整的请求都需要经过DNS寻址、与服务器建立连接、发送数据、等待服务器响应、接收数据的过程。 前端优化的途径页面级别的优化,例如HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等;代码级别的优化,例如JavaScript中的DOM操作优化、CSS选择符优化、图片优化以及HTML结构优化等。页面级优化1. 减少HTTP请求数减少HTTP请求数的主要途径1、从设计实现层面简化页面 保持页面简洁、减少资源的使用是最直接的。能使用CSS替代效果就尽量少使用图片。 2、合理设置HTTP缓存 恰当地缓存设置可以大大减少HTTP请求。被缓存资源的请求服务器是304响应,只有Header没有body,没有节省带宽。对于多个页面都可能使用到的代码,尽量拆分到同一个文件中。如果是嵌入页面换来的是增大了页面的体积,而且无法利用浏览器缓存。 3、资源合并和压缩 如果可以,尽可能将外部的脚本、样式进行合并,多个合为一个。另外,CSS、JavaScript、image都可以用相应的工具进行压缩。 4、CSS Sprites 合并CSS图片,减少请求数的有一个好办法。 5、lazy load image 这个策略实际上并不一定能减少HTTP请求数,但是却能在某些条件下或者页面刚加载时减少HTTP请求数。对于图片而言,在页面刚加载时可以只加载第一屏,当用户继续往后滚屏时才加载后续的图片。以前的做法是在加载时把第一屏之后的图片地址缓存在textarea标签中,待用户往下滚屏时才惰性加载。百度图片和花瓣网也是用这种流行的瀑布流加载图片。 2. 将外部脚本置底外链脚本在加载时会阻塞其他资源,例如在脚本加载完成之前,它后面的图片、样式以及其他脚本都处于阻塞状态,直到脚本加载完成后才会开始加载。如果把脚本放在比较靠前的位置,则会影响整个页面的加载速度从而影响用户体验。最简单可依赖的方法是将脚本尽可能往后挪,减少对并发下载的影响。如果时效性允许的话,可以考虑在DOMLoaded事件触发时加载,或者用setTimeout方式来灵活控制加载的时机。 3. 异步执行inline脚本inline脚本对性能的影响比外部脚本大很多。首先,与外部脚本一样,inline脚本在执行时也会阻塞并发请求,除此之外,由于浏览器在页面处理方面时单线程的,当inline脚本在页面渲染之前执行时,页面的渲染工作则会被推迟。简而言之,inline脚本在执行时页面处于空白状态。 鉴于以上两点,建议将执行时间较长的inline脚本异步执行。异步执行的方式有很多种,例如使用script元素的defer属性、使用setTimeout,此外,在HTML5中引入了web workers的机制,恰恰可以解决此类问题。 4. lazy load JavaScript目前的做法大概有两种,一种是为流量特别大的页面专门定制一个专用的mini版框架,另一种则是lazy load,最初只加载核心模块,其他模块可以等到需要使用的时候才加载,类似于java的swing,引入需要的组件库文件。 5. 将CSS放在head中6. 减少不必要的HTTP跳转对于以目录形式访问的HTTP链接,很多人都会忽略链接最后是否带/,假如服务器对此区别对待,那么其中很可能隐藏了301跳转,增加了多余请求。 代码级优化1. JavaScript1、DOM DOM操作应该是脚本中最耗性能的一类操作,例如增、删、查、改DOM元素或者对DOM集合进行操作。如果脚本中包含了大量的DOM操作则需要注意html collection。 在脚本中document.images、document.forms、getElementsByTagName()返回的都是HTMLCollection类型的集合,在平时使用的时候大多将它作为数组来使用,因为它有length属性,也可以使用索引访问每一个元素。不过在访问性能上则比数组要差很多,原因这个集合并不是一个静态的结果,它表示的仅仅是一个特定的查询,每次访问该集合时都会重新执行这个查询从而更新查询结果。所谓的访问集合包括读取集合的length属性、访问集合中的元素。 因此,当你需要遍历HTML collection时,尽量将它转为数组后再访问,以提高性能。即使不转换为数组,也请尽可能少地访问它,例如在遍历的时候可以将length属性、成员保存到局部变量后再使用局部变量。 2、慎用with with(obj){p=1};代码块的行为实际上是修改了代码块中的执行环境,将obj放在了其作用于的最前端,在with代码块中访问非局部变量都是先从obj上开始查找,如果没有再依次按作用域链向上查找,因此使用with相当于增加了作用域链长度。而每次查找作用域链都是要消耗时间的,过长的作用域链会导致查找性能下降。 因此,除非你能肯定在with代码中脂肪纹obj中的属性,否则慎用with,替代的可以使用局部变量缓存需要访问的属性。 3、避免使用eval和Function 每次eval或Function构造函数作用于字符串表示的源代码时,脚本引擎都需要将源代码转换为可执行代码。这是很消耗资源的操作——通常比简单的函数调用慢100倍以上。 eval函数效率特别低,由于事先无法知晓传给eval的字符串中的内容,eval在其上下文中解析要处理的代码,也就是说编译器无法优化上下文,因此只能有浏览器在运行时解析代码,这对性能影响很大。 Function构造函数比eval略好,因为使用此代码不会影响周围代码,但其速度仍很慢。 此外,使用eval和Function不利于JavaScript压缩工具执行压缩。 4、减少作用域链查找 作用域链查找问题,这一点在循环中尤其需要注意。如果在循环中需要访问非本作用域下的变量时请在遍历之前用局部变量缓存该变量,并在遍历结束后再重复那个变量,这一点对全局变量尤其重要,因为全局变量处于作用域链的最顶端,访问时的查找次数是最多的。 此外,要减少作用域链查找还应该减少闭包的使用。闭包的变量可能保存到内存中,内存消耗很大,解决方法是在退出函数前,将不使用的局部变量删除。 5、数据访问 JavaScript中的数据访问包括直接量(字符串、正则表达式)、变量、对象属性以及数组,其中对直接量和局部变量的访问是最快的,对对象属性以及数组的访问需要更大的开销。当出现以下情况时,建议将数据放入局部变量: 对任何对象属性的访问超过1次对任何数组成员的访问次数超过1次另外,还应当尽可能的减少对对象以及数组深度查找。 6、字符串拼接 在JavaScript中使用+号来拼接字符串效率是比较低的,因为每次运行都会开辟新的内存并生成新的字符串变量,然后拼接结果赋值给新变量。之前使用jQuery+Ajax交互页面,很多时候都是将后台传输过来的数据和前端HTML结构拼接成字符串,然后呈现在页面HTML容器里。 与之相比更为高效的做法是使用数组的join方法,即将需要拼接的字符串放在数组中最后调用其join方法得到结果。不过由于使用数组也有一定的开销,因此当需要拼接的字符串较多时可以考虑使用此方法。 2. CSS选择符在大多数人的观念中,都觉得浏览器对CSS选择符的解析是从左往右进行的。 如果是从右往左解析则效率会很高,因为第一个ID选择基本上就把查找的范围限定了,但实际上浏览器对选择符的解析是从右往左进行的。#tag A {color: "#ccc";},浏览器必须遍历查找每一个A标签的祖先节点,效率并不像之前想象的那么高。根据浏览器的这一行为特点,在写选择符的时候需要注意很多事项。

May 7, 2019 · 1 min · jiezi

????JavaScript设计模式实践:18份笔记、例子和源码????

背景介绍之前在阅读《JavaScript设计模式和开发实践》这本书的时候,收货颇丰,学习了设计模式在很多场景下的应用。但也是因为书上场景过多,所以当记不清某一种设计模式的时候,翻书温习复杂案例的成本是相对较高的。有时候,只需要一段经典、简洁的demo就可以迅速回顾起精髓,在快速业务开发中,这是个比较经济的做法。除此之外,当主要工作语言发生变化的时候(例如:js -> python),简洁的demo更能帮助开发者快速回忆某种设计模式的精髓和实现思路,方便开发者根据语言特性再做实现。因此,对于比较重要的18种设计模式,我都挑选了它的一种经典应用,并且尽量使用ES6的语法和编程习惯来进行实现。 前10个设计模式还提供了Python3的实现版本(后来比较忙,遂放弃)。文章地址一共记录了18个设计模式,部分文章发到了掘金,由于精力有限,后面几篇文章就直接放在了Github仓库 / 个人博客单例模式:https://godbmw.com/passages/2018-10-23-singleton-pattern/策略模式: https://godbmw.com/passages/2018-10-25-stragegy-pattern/代理模式: https://godbmw.com/passages/2018-11-01-proxy-pattern/迭代器模式: https://godbmw.com/passages/2018-11-06-iter-pattern/订阅-发布模式: https://godbmw.com/passages/2018-11-18-publish-subscribe-pattern/命令模式: https://godbmw.com/passages/2018-11-25-command-pattern/组合模式: https://godbmw.com/passages/2018-12-12-composite-pattern/享元模式:https://godbmw.com/passages/2018-12-16-flyweight-pattern/责任链模式: https://godbmw.com/passages/2019-01-07-chain-of-responsibility-pattern/装饰者模式: https://godbmw.com/passages/2019-01-12-decorator-pattern/状态模式: https://godbmw.com/passages/2019-01-16-state-pattern/适配器模式: https://godbmw.com/passages/2019-01-17-adapter-pattern/桥接模式: https://godbmw.com/passages/2019-01-19-bridge-pattern/解释器模式: https://godbmw.com/passages/2019-01-25-interpreter-pattern/备忘录模式: https://godbmw.com/passages/2019-01-26-memento-pattern/模板模式: https://godbmw.com/passages/2019-01-31-template-pattern/工厂模式: https://godbmw.com/passages/2019-03-31-factory-pattern/抽象工厂模式: https://godbmw.com/passages/2019-04-01-abstract-factory-pattern/放在最后其实整理这些的原因还有一个,就是为了准备今年春招的面试。然后过了腾讯的校招和阿里的前三面发现,竟然没有专门问到设计模式相关知识!但回首看,系统地学习、理智地使用设计模式(不是为了用而用),确实能提升代码的可读性,实现业务解耦。而在写这些文章的过程中,每种设计模式自己也是会找很多的实现(包括不限于python、java、c++)来参考,探索式学习还是蛮有趣的。尽管如此,有2篇文章的瑕疵还是很多,希望您抱着交流的心态来阅读,如有不当,欢迎指出、共同提升。

April 11, 2019 · 1 min · jiezi

【前端优化】前端常见性能优化

前言:web前端本质上是一种GUI软件(图形用户界面—采用图形方式显示的计算机操作用戶介面)性能time=下载资源time+服务响应time(api)+浏览器渲染time网络静态 — 50% 首屏性能-首屏渲染时间要在 1s之内优化点1:资源请求深入理解http请求的过程是前端性能优化的核心浏览器的一个请求从发送到返回都经历了什么http://www.zyy1217.com/2017/0…浏览器根据请求的URL交给DNS域名解析,找到真实IP,向服务器发起请求;服务器交给后台处理完成后返回数据,浏览器接收文件(HTML、JS、CSS、图象等)浏览器对加载到的资源(HTML、JS、CSS等)进行语法解析,建立相应的内部数据结构(如HTML的DOM)载入解析到的资源文件,渲染页面,完成1.dns是否可以通过缓存减少dns查询时间 — dns缓存/浏览器缓存查询/本地host查询/服务来查询DNS查找过程——20ms通过缓存,来减少这种查找功能将网址转化为计算机理解的IP地址—通过IP来查找2.cdn服务(内容分发网络)网络请求的过程走最近的网络环境什么是CDN—解决网络拥堵—需要添加服务器–靠money解决3. 相同的静态资源是否可以缓存前端:本地 — 添加Expire/Cache-Control头使用特殊的字符串—来标识某个请求资源版本请求资源—浏览器tag和服务器tag一样—返回304—信息没有被修改–可以直接使用本地缓存配置etag可以减轻服务器的压力能否减少请求http请求大小? — 合并压缩4.启用Gzip压缩 —在服务器端配置Gzip–思想:先把文件放在服务器上进行压缩,浏览器会自动的解压缩这些文本文件都应被压缩:减少http请求— 合并文件服务端渲染 — Vue前端:减少HTTP请求,减少HTTP请求大小合并压缩常见:html/css/js/图片html压缩 — goole 在HTML中不显示的字符,包括空格,制表符,换行符等,还有一些其他意义的字符,如HTML注释也可以被压缩大厂有意义google的流量,占到整个互联网的40%预计2016年全球网络流量将会达到1.3ZB(1ZB = 10^9TB)那么google在2016年的流量就是1.3ZB * 40%如果google每1MB请求减少一个字节每年可以节省流量近500TBWebapack — html-webpack-plugincss压缩无效代码删除+css语义合并(相同样式代码)Webpack css-loaderJs压缩混乱 — 安全性无效字符的删除剔除注释代码语义的缩减和优化 var a = 1, var a=2, var a=3,代码保护Webpack — UglifyJSPlugin文件合并 — 文件与文件之间有插入的上行请求,增加了N-1个网络延迟 文件合并弊端:首屏渲染问题 合并文件太大,造成慢缓存失效问题 标记 md5戳 只要有一个变动 则失效 a,b,c三个js合并压缩规则:公共库合并 业务代码不合并 公共库合并不同页面的各自合并 异步加载组件,不同页面单独打包,监听路由变化,自动下载Webpack — require.ensure([], callback)http://www.cnblogs.com/rubylo…Webpack — 指定entry就是一个打包的入口entry:{ index:["./src/index.js","./src/main.js"]}图片压缩主要: 降低色彩点数 + 肉眼不可见Png8/png24/png32png8 —— 256色 + 支持透明png24 —— 2^24色 + 不支持透明png32 —— 2^24色 + 支持透明不同图片使用不同的业务场景 — 手淘jpg有损压缩,压缩率高,不支持透明大部分不需要透明图片的业务场景Png支持透明,浏览器兼容好, 大部分需要透明图片的业务场景webp压缩程度更好,在ios webview有兼容性问题,安卓全部/safari no/更优的图像数据压缩算法svg矢量图代码内嵌相对较小,图片样式相对简单的场, 图片样式相对简单的业务场景图片优化方案Css雪碧图— backgroudPosition/图片大小很大 丢包 fb/需求页面Image inline base64 webpackmodule: { loaders: [ { test: /.(png|jpg)$/, loader: ‘url-loader?limit=8192&name=images/[hash:8].[name].[ext]’ } ]}矢量图svg内嵌标签(dom渲染,无请求)/iconfont(svg/png)svg标签 内置属性方法 只能花颜色单一,曲线不复杂https://developer.mozilla.org…tinypng.com优化点2:浏览器渲染http://taligarsiel.com/Projec… how bowser work浏览器拿到资源后怎么工作?拿到是一个html文件— 里面内嵌了很多css.js.静态资源链接Html渲染特点:顺序执行、并发加载(同一域名加载限制)并发加载: result: 10 in IE 8, 6 in Firefox 3+ and Chrome 5+浏览器会解析三个东西:(1) HTML/SVG/XHTML,解析这三种文件会产生一个 DOM Tree。(2) CSS,解析 CSS 会产生 CSS 规则树。(3) Javascript脚本,v8引擎,主要是通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree. 载入后马上执行,执行时会阻塞页面后续的内容(包括页面的渲染、其它资源的下载 — 视浏览器内核为定)Dom解析:最先请求 字节 字符 词法分析 dom树自上而下 边下载边渲染 不断重绘回流DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点css解析 #nav li 去找所有的li,然后再去确定它的父元素是不是#navRender tree结构不等同于dom树display:none 的东西就没必要放在渲染树中/<head>标签网页中有哪些节点、各个节点的CSS定义以及他们的从属关系Layout计算出每个节点在屏幕中的位置绘制 遍历render树,并使用UI后端层绘制每个节点解析完一部分内容就显示一部分内容,解析、渲染、http请求并行名词解释:com.atlassian.confluence.content.render.xhtml.XhtmlException: Missing required attribute: {http://atlassian.com/resource…}value回流必将引起重绘而重绘不一定会引起回流Css/js的位置 为什么这么重要,链接放在那里无所谓吗?css阻塞Css — head — 阻塞页面渲染(首屏 — 合并文件)— 白屏,闪屏Css — 阻塞js执行/不阻塞js加载 Html-preload-scanner — 查查 边下边渲染原因:浏览器线程 — GUI渲染线程与javascript线程互斥javascript执行依赖dom和css 动态修改结论:css一般放在头部Js阻塞直接引入js阻塞页面渲染 script js不阻塞资源加载, — 顺序执行 阻塞后续js执行 按文档结构Webkit Html-preload-scanner 预加载执行前先暂停,去扫描有没有其他资源需要加载 解决方法defer — IE独有IE并行下载js文件、整个DOM装载完毕执行、多个defer的<script>在执行时也会按照其出现的顺序js一般放在页面最尾部浏览器内核常驻线程:Q;1.多个js的加载顺序顺序执行加载后立即执行并阻塞渲染2.cache-control的参数缓存请求指令:Cache-Control: max-age=<seconds>Cache-Control: max-stale[=<seconds>]Cache-Control: min-fresh=<seconds>Cache-control: no-cacheCache-control: no-storeCache-control: no-transformCache-control: only-if-cached缓存响应指令Cache-control: must-revalidateCache-control: no-cacheCache-control: no-storeCache-control: no-transformCache-control: publicCache-control: privateCache-control: proxy-revalidateCache-Control: max-age=<seconds>Cache-control: s-maxage=<seconds>3.chrome独有 Html-preload-scanner 预加载在WebKit中,预加载扫描器指的是一个副解析器,当HTML主解析器被一个同步的script标签阻塞时,预加载扫描器就会启动.然后,它会马上找出接下来即将需要获取的资源(比如样式表,脚本,图片等资源)的URL,然后尽可能早的发起网络请求,而不用等到主解析器恢复运行,从而提高了整体的加载时间 ...

March 1, 2019 · 1 min · jiezi

你与弄懂promise之间可能只差这篇文章(二)

点我看看~话不多说,上源码:// 核心方法!核心方法!核心方法!也是最难懂的!—-STARTconst resolvePromise = (promise2, x, resolve, reject) => { if (promise2 === x) { return reject(new TypeError(“A promise cannot be resolved with itself”)) } if (x && (typeof x === “object” || typeof x === “function”)) { try { let then = x.then; if (typeof then === “function”) { then.call(x, y => { resolve(y); resolvePromise(promise2, y, resolve, reject); }, r => { reject(r); }) } else { resolve(x); } } catch (e) { reject(e) } } else { resolve(x) }};// 核心方法!核心方法!核心方法!也是最难懂的!—-END// Commitment就是class Commitment { constructor (executor) { this.status = “PENDING” this.value = void(0); this.reason = void(0); this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.status === “PENDING”) { this.status = “RESOLVED”; this.value = value; this.onResolvedCallbacks.forEach(cb => cb()) } } const reject = (reason) => { if (this.status === “PENDING”) { this.status = “REJECTED”; this.reason = reason; this.onRejectedCallbacks.forEach(cb => cb()) } } try { executor(resolve, reject) } catch (e) { reject(e) } } then (onResolvedFn, onRejectedFn) { let promise2, x; promise2 = new Promise((resolve, reject) => { if (this.status === “RESOLVED”) { setTimeout(() => { try { x = onResolvedFn(this.value); resolvePromise(promise2, x, resolve, reject); } catch(e) { reject(e) } }, 0) } if (this.status === “REJECTED”) { setTimeout(() => { try { x = onRejectedFn(this.reason); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } if (this.status === “PENDING”) { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { x = onResolvedFn(this.value); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { x = onRejectedFn(this.reason); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }); } }); return promise2 } catch (onRejectedFn) { return this.then(null, onRejectedFn) } // 静态方法 static resolve (value) { return new Commitment((resolve, reject) => { resolve(value) }) } static reject (err) { return new Commitment((resolve, reject) => { reject(err) }) } static all () { let result = []; return new Commitment((resolve, reject) => { if (arguments.length === 0) { return reject(new TypeError(“undefined is not iterable (cannot read property Symbol(Symbol.iterator))”)) } else { let args = […arguments][0], result = []; let count = args.length, num = 0; for (let i = 0; i < count; i++) { let item = args[i]; try { let then = item.then; if (typeof then === “function”) { then.call(item, y => { num += 1; result[i] = y; if (num === count) { resolve(result) } }, r => { reject(r) }) } else { result[i] = item; if (num === count) { resolve(result) } } } catch (e) { reject(e) } } } }) } static race () { // 未完待续 }} ...

February 28, 2019 · 3 min · jiezi

阿里云ARMS重磅推出小程序监控,助力小程序稳定运行

2018年是小程序蓬勃发展的一年,各大公司如腾讯、阿里、百度、头条等都陆续推出了自己的小程序,小程序已成为一个未来必然的趋势、移动互联网的新风口。据数据统计,目前已上线的微信小程序已超过100万,支付宝小程序、钉钉E应用、百度智能小程序、头条小程序等也在不断发力。由于小程序具有:无需下载、触手可及、用完即走、无需卸载的特性,所以用户体验对于小程序来说非常重要,一旦出现小程序卡顿,缓慢和错误,需要及时发现并修复,否则用户流失的概率会非常大。小程序与H5的不同小程序和H5都属于移动端场景下的技术选择方案,那么这里介绍一下小程序与H5的不同。运行环境的不同传统的H5的运行环境是浏览器,包括webview,其中浏览器提供window、document等BOM对象小程序的逻辑层和渲染层是分开的,逻辑层运行在JSCore中,并没有一个完整的浏览器对象,所以缺少相关的DOM API和BOM API开发成本的不同H5的开发,涉及到开发工具、前端框架、模块管理工具、任务管理工具、UI库的选择、接口调用工具及浏览器兼容性等小程序的开发,指定环境的小程序会提供开发者工具、API及规范的开发标准。由于小程序是跑在指定的环境下的,同时API是指定环境下提供的,所以不用考虑浏览器的兼容性。在H5开发中,前端常用的HTML/CSS在不同的小程序中都有指定的文件标准。例如:在微信小程序中使用WXML/WXSS在支付宝小程序、钉钉E应用中使用AXML/ACSS在百度智能小程序中使用SWAN/CSS…开发规范在指定的官方文档中都会有明确的使用介绍,使用方法与原来H5的开发大同小异,所以上手开发相对容易。使用体验的不同H5页面需要在浏览器中渲染,在复杂的业务逻辑或者丰富的页面交互时会有卡顿情况小程序除首次使用略慢,页面切换及跳转等非常顺滑,接近Native通过以上几点小程序和H5的不同的介绍,我们可以发现原来针对H5页面的监控无法直接监控小程序;同时由于小程序封闭性较强,不同的小程序在标准上也略有不同,如微信小程序、支付宝小程序及钉钉E应用等等小程序在使用标准及开放的API方面也会有一些差异,所以针对小程序的监控与针对Web应用的监控会有所不同。小程序监控的现状现在针对小程序监控的大概分为以下几类:小程序的数据统计分析,助力小程序运营相关产品: 微信小程序助手、阿拉丁小程序统计平台等特点:大部分是针对微信小程序提供相应的数据统计分析能力,从多维度分析小程序相关用户数据,适用于小程序运营,但缺乏对于用户体验,小程序性能的监控小程序错误监控相关产品: FunDebug等特点:监控小程序使用户出现的错误,帮助开发者发现并解决小程序错误,但缺乏对于小程序全局性能的监控,对于缓慢请求,缓慢页面没法监测小程序性能监控相关产品: FrontJS、听云小程序监控等特点:主要提供性能相关数据,包括JS错误、网络请求响应情况等。但是只支持微信小程序,而且没有办法把小程序的性能与后台应用的性能关联起来,没法形成端到端的监控通过上面对现有的小程序监控产品分析,存在以下问题:无法支持所有的小程序监控,主要支持微信小程序支持多类小程序监控的产品,提供的小程序相关数据较少,主要集中在错误监控没有后台应用服务的性能监控,无法从小程序上的性能问题追溯到后台应用代码和数据库,无法形成端到端的监控基于以上情况,阿里云ARMS前端监控重磅推出小程序监控,旨在帮助端到端的快速定位小程序问题,提升小程序的用户体验。提供的能力阿里云ARMS前端监控此次重点推出的小程序监控有以下特点:1. 覆盖各类符合标准规范的小程序首先解释一下这里所说的"标准规范的小程序",即包含App和Page两层:App用来描述整体程序,包含: onError事件Page用来描述各个页面,包含: onShow、onHide、onUnload事件小程序的运行环境依赖于对应的客户端,各类小程序的DSL设计看起来很像,但细节上的差别还是比较多,并且已有了分化的趋势。在这种情况下,阿里云ARMS前端监控为了更好的支持小程序的监控诉求,提供以下小程序监控的场景:微信小程序支付宝小程序钉钉E应用其他类别小程序由于小程序发展迅速,现在无法针对各类小程序都提供对应的监控SDK,所以不属于微信小程序、支付宝小程序和钉钉E应用的小程序可选择其他类别小程序的场景接入进行监控,但要满足上面说的"标准规范的小程序"前提,同时支持npm包。2. 完善的性能监控指标基础业务指标,帮助了解小程序应用的使用情况应用总PV/UV页面维度的PV/UV小程序各维度指标手机型号操作系统版本微信/支付宝等相应的APP版本网络等JS错误分析JS错误率、错误聚类、JS错误堆栈及错误定位等API请求追踪API请求成功率、API请求耗时及API请求的链路追踪自定义事件统计支持业务上自定义事件sum/avg统计3. 可通过配置选择上报方式由于业务方使用监控的诉求不同,我们不仅支持优雅的静默数据上报,也支持使用开放的统计能力进行自定义上报。具体可查看官网的前端监控接入概述中的小程序场景相关文档。总结小程序作为各大互联网公司重磅加持的方向,未来小程序的应用数量会越来越多,那么对于用户体验方面的关注与提升诉求也会不断增加,阿里云ARMS前端监控提供的小程序监控可帮助客户实时监控发现质量问题,为企业的小程序的稳定运行提供坚实的保障。附录:业务实时监控服务ARMS业务实时监控服务ARMS前端监控小程序监控接入文档

February 20, 2019 · 1 min · jiezi

前端性能优化常用总结

CSS优化1.把 CSS 资源引用放到 HTML 文件顶部一般推荐将所有 CSS 资源尽早指定在 HTML 文档 <head> 中,这样浏览器可以优先下载 CSS 并尽早完成页面渲染。2.CSS 动画使用 translate、scale 代替 top、height尽量使用 CSS3 的 translate、scale 属性代替 top、left 和 height、width,避免大量的重排计算图片优化1.图片压缩处理在移动端,通常要保证页面中一切用到的图片都是经过压缩优化处理的,而不是以原图的形式直接使用的,因为那样很消耗流量,而且加载时间更长。2.使用较小的图片,合理使用 base64 内嵌图片在页面使用的背景图片不多且较小的情况下,可以将图片转化成 base64 编码嵌入到 HTML 页面或 CSS 文件中,这样可以减少页面的 HTTP 请求数。需要注意的是,要保证图片较小,一般图片大小超过 2KB 就不推荐使用 base64 嵌入显示了。3.尽量预先设定图片等大小在加载大量的图片元素时,尽量预先限定图片的尺寸大小,否则在图片加载过程中会更新图片的排版信息,产生大量的重排4.雪碧图优化原理来于web优化中的减少http请求数量,通过减少页面图片的数量来实现。5.图片延时加载脚本优化1.JavaScript 资源引用放到 HTML 文件底部JavaScript 资源放到 HTML 文档底部可以防止 JavaScript 的加载和解析执行对页面渲染造成阻塞。由于 JavaScript 资源默认是解析阻塞的,除非被标记为异步或者通过其他的异步方式加载,否则会阻塞 HTML DOM 解析和 CSS 渲染的过程。2.DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。3.压缩JavaScript

January 31, 2019 · 1 min · jiezi

webpack4系列教程(五):处理项目中的资源文件(二)

在项目中使用 less 在 src/assets/ 下新建 common.less :body{ background: #fafafa; padding: 20px;}在 main.js 中引入 common.less :import ‘./assets/style/common.less’安装 less-loader:npm i less-loader -D添加 rules: { test: /.less$/, use: [ ‘style-loader’, ‘css-loader’, ’less-loader’ ] }打包之后,在浏览器打开 dist/index.html,less文件中的样式已经通过 style 标签载入了: 2. 使用MiniCssExtractPlugin我们之前的样式代码都是通过 style 标签载入的,那么如何通过 link 引入CSS文件的方式实现呢?这就需要使用一个插件,在webpack3中通常使用ExtractTextWebpackPlugin,但是在webpack4中已经不再支持ExtractTextWebpackPlugin的正式版,而测试版本又不够稳定,因此我们使用MiniCssExtractPlugin替代。首先安装:npm install –save-dev mini-css-extract-plugin在webpack.config.js 中引入并添加 plugins :const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’)new MiniCssExtractPlugin({ filename: “[name].css” }), 修改 CSS 和 less 的 rules:{ test: /.css$/, use: [ // ‘style-loader’, { loader: MiniCssExtractPlugin.loader }, ‘css-loader’ ] }, { test: /.less$/, use: [ // ‘style-loader’, { loader: MiniCssExtractPlugin.loader }, ‘css-loader’, ’less-loader’ ] }npm run build 之后,可见head中引入了一个 main.css 文件: 也正是我们在 common.less 和 modal.css 中的代码 3. postcss-loaderpostcss-loader 可以帮助我们处理CSS,如自动添加浏览器前缀。npm i -D postcss-loader autoprefixer在根目录下创建 postcss.config.js:const autoprefixer = require(‘autoprefixer’)module.exports = { plugins: [ autoprefixer({ browsers: [’last 5 version’] }) ]}修改 css 和 less 的 rules:{ test: /.css$/, use: [ // ‘style-loader’, { loader: MiniCssExtractPlugin.loader }, { loader: ‘css-loader’, options: { importLoaders: 1 } }, ‘postcss-loader’ ] }, { test: /.less$/, use: [ // ‘style-loader’, { loader: MiniCssExtractPlugin.loader }, ‘css-loader’, ‘postcss-loader’, ’less-loader’ ] }在 modal.css中加入:.flex{ display: flex;}打包之后打开 main.css,可见浏览器前缀已经加上了: 本人才疏学浅,不当之处欢迎批评指正

January 14, 2019 · 1 min · jiezi

2019年如何成为一个成熟的前端开发者?

2019年一眨眼就到了,很多想入行前端开发的小白朋友们,不知道在新的一年实现了没有呢?最近有个小伙伴来问,问我怎么样才能成为一名成熟的前端开发?是的,成熟的专业开发工程师现在是越来越吃香了,零基础小白就业越来越难了。如何成为一名成熟前端开发?首先,成为一名成熟的开发者,首先必是一位有扎实的基础开发。所以最基本的一些HTML、CSS、JavaScript这些基础的语法,就不详细说了。最基本的网页布局还是要简单掌握的。除了基础的语法,还有很多需要注重去理解的地方。扎实的JavaScript基础在前端开发里,最讲究的还是在JavaScript里,基础的语法,能运用很简单。但是说成熟开发,那必然是对JavaScript是熟练掌握和有深入的理解了。说到要熟练掌握JavaScript,那必然是要掌握闭包,ES678,原型链,这一系列的了。除了扎实的JS基础,还有就是要掌握符合目前市场需求的技术点了。多终端开发除了基础的PC端,目前移动端可以说是非常火的了。比PC端单一的网页不同,在移动端的技术可以应用到WEB-APP,小程序,Hybrid-App等等。WEBapp,也就是我们常见的浏览器(以及内置浏览器,比如微信)打开的大型移动端网页。比如我们常见的电商网站,功能性网站,管理网站,营销网页等等,在布局和功能上都有APP的效果。做好WEB-APP开发,最基本的很多人肯定会说响应式布局,但是前端开发是又5种布局法的,除了出名的响应式,还有在移动端最重要的弹性布局法,也就是很多人头疼的rem布局。除了布局方法之外,在H5新特性和触屏事件和设备兼容性问题也是需要信手拈来。小程序,这个也不用多说,现在可以说是非常火的,各大平台都有在做自己的小程序,各种砍价,抢票,电商,游戏都有运用。这个以微信小程序为例,主要是微信团队基于前端基础来做的封装语法,主要的还是ES语法。小程序目前很多公司都是招聘前端开发,目前还没有独立的小程序开发工程师,所以小程序可以说是前端工程师高薪就业的加分技能,换句话说就是成熟开发必备了。Hybrid-App,又称混合式APP,可能听说的人很少,但是18年是越来越多人去开发了,这种是能直接产出下载到终端的APP的,在体验感上可以说已经具备传统APP的大部分功能了。这种开发门槛较低,也就是前端开发就可以完成。多数都是大包平台就能做,有很好的前端基础就可以做了。前端主流技术框架前面的都是基础东西,现在去就业前端开发,不是只靠个基础东西,搞个移动端页面就可以算成熟了。前端3大框架,VUE、Angular、React这3个可以说是现在非常火热的了。基础语法都可以写的前端,为什么还要框架?很多小白朋友是不懂什么是框架的,只听过jQuery这些东西,以为就是框架。或者认为框架就是加速开发,觉得这些库、插件就能完成框架的工作了。其实框架的出现,是改变前端地位的重要标志。最重要的表现,就是前后端分离,在前后端分离之前,很多后端开发都是又当爹又当妈的,效果不好效率也不高,我就是在后端出身,深知痛苦。现在的前端项目,比以前是更加复杂化、多样化了。项目复杂了,问题也多了。那框架到底解决了什么问题?解决重复引用外部js,以用jQuery开发为例,很多时候都是不能单一完成一个项目的,还需要引用很多的第三方插件和库,导致会一个项目引入很多外部JS文件。这样不仅让代码变得杂乱,而且很影响打开速度。但是用框架呢,以VUE为例,一般会和构建工具配合,然后就是一个入口文件就可以完成了,在运行时候就在入口引入一次,一劳永逸。使用组件化开发,组件是前端框架里非常强大的功能之一,它可以扩展你的HTML,封装可以重用的代码块,比如你的轮播图、tab切换、页面头部、页面底部等等。这种独立的组件具有了结构(html),表现(css)和行为(js)完整的功能,很大程度的节省了代码量,提高了代码的复用性。特别是团队合作的时候,可以很好的提高使用效率。减少开发周期,如果你觉得jQuery可以减少开发周期了,那其实框架可以比库更快。比如说使用jQuery开发的时候,很多时候是需要频繁去操作DOM,每次效果都要去查找DOM,这样就显得很繁琐了。使用框架的时候,很多功能都得到了封装,比如说很多指令都有数据绑定,数据格式化这些功能。这样更多时候,我们开发的时候只需要关注数据的逻辑就行了。没有真正的成熟你会提出成熟,是因为想给自己一个标准,但是做技术工作,最重要还是市场的标准。前端技术是日新月异的,基本每年都是会有新的概念,新的架构,新的应用产品,新的交互体检。这些都是有不确定性的。做技术没有真正的成熟可言,更多的还是不断学习,持续进步。把技术不断做到专,做到精,才能在当前时代成为“成熟”。送书啦!免费送书!马上扫码来参加吧!

January 9, 2019 · 1 min · jiezi

quicklink解析

简介quicklink是一个js库,可以预加载出现在视口的网页链接,提高用户体验。它的加载过程如下:1.检测网页中的链接是否出现在视口中,等待链接出现在视口,执行步骤2。2.等待浏览器空闲后执行3。3.判断当前的网络连接是否是2G,如果是则停止执行,如果不是2G网络,执行步骤4。4.预加载链接指向资源。使用方式参考链接https://github.com/GoogleChro…quicklink源码解析quicklink的入口函数接受传入的配置参数,通过Object.assign函数和默认的配置选项合并。接着执行timeoutFn异步方法,该方法接收一个回调函数,在回调中主要逻辑如下:如果传入的options参数中有urls属性,则直接执行预加载,否则通过document.querySelectorAll方法获取所有a标签元素的NodeList,然后便利该元素节点列表,并监视该元素节点observer.observe(link);然后判断该a元素对象的href属性值所属的域名是否被允许访问,如果被允许访问,继续判断该链接是否应该被忽略,判断逻辑如下:if (!allowed.length || allowed.includes(link.hostname)) { // If there are any filters, the link must not match any of them isIgnored(link, ignores) || toPrefetch.add(link.href);}如果链接没有被忽略,则将该节点的href属性值加入到toPrefetch中const toPrefetch = new Set();toPrefetch.add(link.href);总的代码逻辑如下export default function (options) { options = Object.assign({ timeout: 2e3, priority: false, timeoutFn: requestIdleCallback, el: document, }, options); observer.priority = options.priority; const allowed = options.origins || [location.hostname]; const ignores = options.ignores || []; options.timeoutFn(() => { // If URLs are given, prefetch them. if (options.urls) { options.urls.forEach(prefetcher); } else { // If not, find all links and use IntersectionObserver. Array.from(options.el.querySelectorAll(‘a’), link => { observer.observe(link); // If the anchor matches a permitted origin // ~> A [] or true means everything is allowed if (!allowed.length || allowed.includes(link.hostname)) { // If there are any filters, the link must not match any of them isIgnored(link, ignores) || toPrefetch.add(link.href); } }); } }, {timeout: options.timeout});}检测link出现在视口上面通过observer.observe(link)监视节点元素,其中observer是IntersectionObserver对象的实例,被监听的节点对象出现在视口时,会执行new操作时传入的回调函数,并将出现在视口的节点对象通过数组的形式传给该回调。然后在回调中便利传入的数组,如果数组中的元素包含在toPrefetch对象中,则取消对该元素的监视,并对该a标签元素所对应的资源进行预加载。const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { const link = entry.target; if (toPrefetch.has(link.href)) { observer.unobserve(link); prefetcher(link.href); } } });});异步函数实现如果浏览器支持requestIdleCallback,则使用原生的函数,如果不支持,则使用setTimeout函数做ployfill。const requestIdleCallback = requestIdleCallback || function (cb) { const start = Date.now(); return setTimeout(function () { cb({ didTimeout: false, timeRemaining: function () { return Math.max(0, 50 - (Date.now() - start)); }, }); }, 1); };export default requestIdleCallback;资源请求函数实现预加载策略主要有三种1.<link> prefetchfunction linkPrefetchStrategy(url) { return new Promise((resolve, reject) => { const link = document.createElement(link); link.rel = prefetch; link.href = url; link.onload = resolve; link.onerror = reject; document.head.appendChild(link); });};2.ajax加载function xhrPrefetchStrategy(url) { return new Promise((resolve, reject) => { const req = new XMLHttpRequest(); req.open(GET, url, req.withCredentials=true); req.onload = () => { (req.status === 200) ? resolve() : reject(); }; req.send(); });}3.Fetch请求加载function highPriFetchStrategy(url) { // TODO: Investigate using preload for high-priority // fetches. May have to sniff file-extension to provide // valid ‘as’ values. In the future, we may be able to // use Priority Hints here. // // As of 2018, fetch() is high-priority in Chrome // and medium-priority in Safari. return self.fetch == null ? xhrPrefetchStrategy(url) : fetch(url, {credentials: include});}网络类型判断if (conn = navigator.connection) { // Don’t prefetch if the user is on 2G. or if Save-Data is enabled.. if ((conn.effectiveType || ‘’).includes(‘2g’) || conn.saveData) return; }将上面三种预加载方法封装成函数,暴露给外部使用const supportedPrefetchStrategy = support(‘prefetch’) ? linkPrefetchStrategy : xhrPrefetchStrategy;function prefetcher(url, isPriority, conn) { if (preFetched[url]) { return; } if (conn = navigator.connection) { // Don’t prefetch if the user is on 2G. or if Save-Data is enabled.. if ((conn.effectiveType || ‘’).includes(‘2g’) || conn.saveData) return; } // Wanna do something on catch()? return (isPriority ? highPriFetchStrategy : supportedPrefetchStrategy)(url).then(() => { preFetched[url] = true; });};export default prefetcher; ...

January 6, 2019 · 2 min · jiezi

深入解析ES6之数据结构的用法

本文介绍了深入理解ES6之数据解构的用法,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下。如有不足之处,欢迎批评指正。一 对象解构对象解构语法在赋值语句的左侧使用了对象字面量let node = { type: true, name: false} //既声明又赋值let { type, name} = node; //或者先声明再赋值let type, name({type,name} = node);console.log(type);//trueconsole.log(name);//falsetype与name标识符既声明了本地变量,也读取了对象的相应属性值。解构赋值表达式的值为表达式右侧的值。当解构表达式的右侧的计算结果为null或者undefined时,会抛出错误。默认值当你使用解构赋值语句时,如果所指定的本地变量在对象中没有找到同名属性,那么该变量会被赋值为undefinedlet node = { type: true, name: false}, type, name, value;({type,value,name} = node); console.log(type);//trueconsole.log(name);//falseconsole.log(value);//undefined//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860你可以选择性地定义一个默认值,以便在指定属性不存在时使用该值。let node = { type: true, name: false }, type, name, value;({ type, value = true, name} = node); console.log(type);//trueconsole.log(name);//falseconsole.log(value);//true//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860赋值给不同的本地变量名let node = { type: true, name: false, value: “dd”}let { type: localType, name: localName, value: localValue = “cc”} = node;console.log(localType);console.log(localName);console.log(localValue);type:localType这种语法表示要读取名为type的属性,并把它的值存储在变量localType上。该语法与传统对象字面量的语法相反嵌套的对象结构let node = {type: “Identifier”,name: “foo”,loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 4 }//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860}} let {loc: localL,loc: { start: localS, end: localE}} = node; console.log(localL);// start: {line: 1,column: 1},end: {line: 1,column: 4}console.log(localS);//{line: 1,column: 1}console.log(localE);//{line: 1,column: 4}//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860当冒号右侧存在花括号时,表示目标被嵌套在对象的更深一层中(loc: {start: localS,end: localE})二 数据解构数组解构的语法看起来跟对象解构非常相似,只是将对象字面量换成了数组字面量。let colors = [“red”, “blue”, “green”];let [firstC, secondC, thirdC, thursC = “yellow”] = colors;console.log(firstC//redconsole.log(secondC);//blueconsole.log(thirdC);//greenconsole.log(thursC);//yellow//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860你也可以在解构模式中忽略一些项,并只给感兴趣的项提供变量名。let colors = [“red”,“green”,“blue”]; let [,,thirdC] = colors;console.log(thirdC);//blue//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860thirdC之前的逗号是为数组前面的项提供的占位符。使用这种方法,你就可以轻易从数组任意位置取出值,而无需给其他项提供名称。解构赋值let colors = [“red”,“green”,“blue”], firstColor = “black”, secondColor = “purple”;[firstColor,secondColor] = colors;console.log(firstColor);//redconsole.log(secondColor);//green//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860数组解构有一个非常独特的用例,能轻易的互换两个变量的值let a =1,b =2;[a,b] = [b,a];console.log(a);//2console.log(b);//1//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860嵌套的解构let colors = [“red”, [“green”, “blue”], “yellow”];let [firstC, [, ssc]] = colors;console.log(ssc);//blue剩余项let colors = [“red”, “green”, “blue”];let [firstC, …restC] = colors;console.log(firstC);console.log(…restC);console.log(restC[0]);//greenconsole.log(restC[1]);//blue使用剩余项可以进行数组克隆let colors = [“red”, “green”, “blue”];let […restC] = colors;console.log(restC);//[“red”, “green”,“blue”]三 混合解构let node = {type: “Identifier”,name: ‘foo’,loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 4 }//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860},range: [0, 3]} let {type,name: localName,loc: { start: { line: ll }, end: { column: col }},range: [, second]} = node; console.log(type);//Identifierconsole.log(localName);//fooconsole.log(ll);//1console.log(col);//4console.log(second);//3结语感谢您的观看,如有不足之处,欢迎批评指正。 ...

December 17, 2018 · 2 min · jiezi

示例Express中路由规则及获取请求参数

本次给大家分享一篇基于express中路由规则及获取请求参数的方法,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下。如有不足之处,欢迎批评指正。express中常见的路由规则主要使用的路由规则是get和post两种,即var express = require(’express’);var app = express();app.get(); // get和post两种请求方式app.post();//欢迎加入前端全栈开发交流圈一起学习交流:864305860app.get()和app.post()的第一个参数为请求路径,第二个参数为处理请求的回调函数;回调函数有两个参数,分别为req和res,代表请求信息和响应信息。获取请求路径和请求体中的各种参数路径请求及对应获取请求路径的形式有以下几种:(1)req.query (查询get请求中的参数)GET /shoes?order=desc&shoe[type]=converse&shoe[color]=bluereq.query.order// =>‘desc’req,query.shoe.type// =>‘converse’(2)req.body (查询请求体)// POST user[name]=dby&user[email]=bing@163.comreq.body.user.name// =>‘dby’(3)req.params// GET /file/javascript/jquery.jsreq.params[0]// => ‘javascript/jquery.js’(4)req.params(name)// ?name=tobireq.params(name)// => ’tobi’// POST name=tobireq.param(’name’)// => ’tobi’//欢迎加入前端全栈开发交流圈一起学习交流:864305860由上述代码可以很明显的看出各种获取路径的含义:req.query: 处理get请求,获取get请求的请求参数req.params: 处理/:xxx形式的get或者post请求,获取请求参数req.body: 处理post请求,获取post了请求的请求体req.param(): 处理get和post请求,但查找优先级由高到低为req.params->req.body->req.query注:路径规则支持正则表达式。结语感谢您的观看,如有不足之处,欢迎批评指正。

December 17, 2018 · 1 min · jiezi

深入总结Javascript原型及原型链

本篇文章给大家详细分析了javascript原型及原型链的相关知识点以及用法分享,具有一定的参考价值,对此有需要的朋友可以参考学习下。如有不足之处,欢迎批评指正。我们创建的每个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个原型对象,而这个原型对象中拥有的属性和方法可以被所以实例共享function Person(){}Person.prototype.name = “Nicholas”;Person.prototype.age = 29;Person.prototype.sayName = function(){alert(this.name);};var person1 = new Person();person1.sayName(); //“Nicholas"var person2 = new Person();person2.sayName(); //“Nicholas"alert(person1.sayName == person2.sayName); //true//欢迎加入前端全栈开发交流圈一起学习交流:864305860一、理解原型对象无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262 第 5 版中管这个指针叫 [[Prototype]] 。虽然在脚本中没有标准的方式访问 [[Prototype]] ,但 Firefox、Safari 和 Chrome 在每个对象上都支持一个属性__proto__ ;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。以前面使用 Person 构造函数和 Person.prototype 创建实例的代码为例,图 6-1 展示了各个对象之间的关系。在此, Person.prototype 指向了原型对象,而 Person.prototype.constructor 又指回了 Person 。person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype ;换句话说,它们与构造函数没有直接的关系。可以调用 person1.sayName() 。这是通过查找对象属性的过程来实现的。(会先在实例上搜索,如果搜索不到就会继续搜索原型。)用isPrototypeOf()方法判断实例与原型对象之间的关系<br>alert(Person.prototype.isPrototypeOf(person1)); //truealert(Person.prototype.isPrototypeOf(person2)) //true<br><br>用Object.getPrototypeOf() 方法返回实例的原型对象<br>alert(Object.getPrototypeOf(person1) == Person.prototype); //true<br><br>使用 hasOwnProperty() 方法可以检测一个属性是存在于实例中,还是存在于原型中。<br>alert(person1.hasOwnProperty(“name”)); //false 来着原型<br>person1.name = “Greg”;<br>alert(person1.name); //“Greg”——来自实例<br>alert(person1.hasOwnProperty(“name”)); /true<br>//欢迎加入前端全栈开发交流圈一起学习交流:864305860二、更简单的原型语法前面例子中每添加一个属性和方法就要敲一遍 Person.prototype 。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。function Person(){}Person.prototype = { name : “Nicholas”, age : 29, job: “Software Engineer”, sayName : function () { alert(this.name); }//欢迎加入前端全栈开发交流圈一起学习交流:864305860};在上面的代码中,我们将 Person.prototype 设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外: constructor 属性不再指向 Person 了。前面曾经介绍过,每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。var friend = new Person();alert(friend instanceof Object); //truealert(friend instanceof Person); //truealert(friend.constructor == Person); //falsealert(friend.constructor == Object); //true//欢迎加入前端全栈开发交流圈一起学习交流:864305860在此,用 instanceof 操作符测试 Object 和 Person 仍然返回 true ,但 constructor 属性则等于 Object 而不等于 Person 了。如果 constructor 的值真的很重要,可以像下面这样特意将它设置回适当的值。function Person(){}Person.prototype = { constructor : Person, name : “Nicholas”, age : 29, job: “Software Engineer”, sayName : function () { alert(this.name); }//欢迎加入前端全栈开发交流圈一起学习交流:864305860};三、原生对象的原型所有原生引用类型( Object 、 Array 、 String ,等等)都在其构造函数的原型上定义了方法。例如,在 Array.prototype 中可以找到 sort() 方法,而在 String.prototype 中可以找到substring() 方法。尽管可以这样做,但不推荐修改原生对象的原型。四、原型对象的问题原型模式的最大问题是由其共享的本性所导致的。 修改其中的一个,另一个也会受影响。function Person(){}Person.prototype = {constructor: Person,name : “Nicholas”,age : 29,job : “Software Engineer”,friends : [“Shelby”, “Court”],sayName : function () {alert(this.name);}//欢迎加入前端全栈开发交流圈一起学习交流:864305860};var person1 = new Person();var person2 = new Person();person1.friends.push(“Van”);alert(person1.friends); //“Shelby,Court,Van"alert(person2.friends); //“Shelby,Court,Van"alert(person1.friends === person2.friends); //true五、原型链其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。然后层层递进,就构成了实例与原型的链条,这就是所谓原型链的基本概念。function SuperType(){ this.property = true;}//欢迎加入前端全栈开发交流圈一起学习交流:864305860SuperType.prototype.getSuperValue = function(){ return this.property;};function SubType(){ this.subproperty = false;}//欢迎加入前端全栈开发交流圈一起学习交流:864305860//继承了 SuperTypeSubType.prototype = new SuperType();SubType.prototype.getSubValue = function (){ return this.subproperty;};//欢迎加入前端全栈开发交流圈一起学习交流:864305860var instance = new SubType();alert(instance.getSuperValue()); //trueproperty 则位于 SubType.prototype 中。这是因为 property 是一个实例属性,而 getSuperValue() 则是一个原型方法。既然 SubType.prototype 现在是 SuperType的实例,那么 property 当然就位于该实例中了结语感谢您的观看,如有不足之处,欢迎批评指正。 ...

December 13, 2018 · 2 min · jiezi

判断DOM元素是否出现再浏览器窗口中

几乎所有的项目都要解决这样一个问题:判断一个元素是否出现在浏览器窗口中?因为通过它我们可以极大的优化项目的性能,进而提升用户的的体验。使用场景及技术分析所涉及的业务实现,比较常见的就是电商平台或者是图片展示类的网站。电商网站,如:淘宝、京东等;图片展示类,如:花瓣,pinterest。涉及的技术,如:lazyload技术动态的加载图片(元素),无限加载技术,包括基于骨架屏技术加载静态资源。懒加载(lazyload):它目的是按需加载,而很大一部分项目的前端实现是通过判断一个元素是否出现在浏览器窗口中,如果出现则将img元素标签内的src属性中的图片地址替换成自定data-src属性的地址,但这里不一定是data-src属性,也可能是srcset,pinterest中就是这样做的,当然你也可以定义成任何你喜欢的,这只是其中的一种方式;另一种是判断元素是否出现在浏览器窗口中之后,然后加载一个HTML代码块,小米商城官网就是这样实现的,当然也有其他一些也是这样做的,这里就不一一做介绍了。无限加载(infinte scroll):它是通过底部的 加载更多 这个代码块是否出现在浏览器窗口中,如果在,就向容器代码块中追加一定数量的相关代码块,这时 加载更多 这个代码块就会被挤压出到浏览器窗口之外。当然有些无限加载技术也使用lazyload。骨架屏技术(skelton screen):这个技术在前一波广受关注,它的原理就是在请求一个页面时,先不显示页面的内容,先显示页面的布局,像文字、图片、视频等静态资源都不显示,而显示的是与页面布局相关的css样式。其实通俗一点来说,就是网页做一个CT,这样是不是理解起来更方便?像淘宝的pc官网,youtube都是这样实现的。这些业务和技术的目的都是为了为了解决低网速情况下使用web应用,如果页面内内容没有加载,那么就会造成低用户体验。这些业务和使用的技术基本上都用了 判断页面的元素是否出现在浏览器窗口中。如何判断一个元素是否出现在窗口中呢?现在通用的是基于浏览器的窗口的判断那么何为基于浏览器窗口的判断呢?这要通过DOM的API .getBoundingClientRect() ,获取目标元素距离浏览器窗口的位置坐标(top, left) 或者(x, y)坐标,所以说是基于浏览器窗口的。我们可以拖动浏览器的滚动条来使目标元素从浏览器的顶端进入浏览器窗口(这可以判断上边界),也可以从浏览器的底部进入浏览器窗口(这可以判断下边界),而这正好是判断目标元素进入浏览器窗口的边界。上边界:目标元素的底边刚好和浏览器的顶部重合,当滚动条向下滚动,目标元素从底部开始一点点的出现,直到目标元素整个出现在浏览器窗口,反之,则逐渐远离。如果目标元素高度大于浏览器窗口的高度,那么浏览器窗口内就不不出现整个目标元素,而只会出现部分,基于这种情况衍生了一种目标元素背景图片的滚动动画。可参考苹果官网。下边界:目标元素的顶部刚好和浏览器的底部重合,当滚动条向上滚动,目标元素从顶部部开始一点点的出现,直到目标元素整个出现在浏览器窗口,反之,则逐渐远离。我们可以浏览器的滚动条向下滚动(也可以按照向下滚动,答案类似)写出如下的代码:var clienRect = el.getBoundingClientRect();if (clientRect.top > -clientRect.width && clientRect.top < window.innerHeight) {}// 或者if (clientRect.bottom > 0 && clientRect.top =< window.innerHeight) {}有的同学可能会问,是不是窗口的高度小于目标元素的高度,或者窗口的高度大于目标元素的高度都这样都一样呢?我想说一样,因为这里是根据两个边界得出的结论。上面的判断包括目标元素的部分出现在浏览器窗口,那么如何判断整个目标元素出现在浏览器的窗口中呢?可参考如下的代码:var clienRect = el.getBoundingClientRect();if (clientRect.top > 0 && clientRect.top < window.innerHeight - clientRect.width) {}// 或者if (clientRect.top >= 0 && clientRect.bottom > window.innerHeight) {}兼容性可参考can i use基于document文档的顶部来判断这里要使用的是DOM元素的 .offsetTop 来计算目标元素的顶部边界到document文档的顶部边界的距离,使用window的 .pageYoffset 来计算当浏览器滚动条滚动时document文档卷起的高度,通过比较这两个高度,我们就可以轻松的判断目标元素是否出现在浏览器窗口内。当目标元素的上边界和浏览器窗口的下边界重合,目标元素的 offsetTop 是个定值,所以此时document文档卷起的高度和目标元素的 offsetTop 之间存在这样一个等式:window.innerHeight + window.pageYoffset = el.offsetTop。若继续向下滚动浏览器的滚动条,目标元素将出现在浏览器当中,我们都知道 window.pageYoffset 的值将继续变大,由于 el.offsetTop 和 window.innerHeight 都是个定值,所以我们可以得到这样一个边界条件,当 window.pageYoffset > el.offsetTop - window.innerHeight ,目标元素从浏览器窗口的底部逐渐出现。当浏览器的滚动条继续向上滚动会出现目标元素的下边界和浏览器窗口的上边界重合的情况,当继续向下滚动滚动条元素将从浏览器窗口消失,此时存在这样一个等式:window.pageYoffset + el.offsetHeight = el.offsetTop ,所以很明显,如果 window.pageYoffset 的值继续增大,目标元素将消失与浏览器窗口,因此,很明显,只有 window.pageYoffset < el.offsetTop - el.offsetHeight 目标元素才会出现在浏览器窗口内。参考代码如下:if (window.pageYoffset > el.offsetTop - window.innerHeight && window.pageYoffset < el.offsetTop - el.offsetHeight) {}当然,上面的是目标元素部分或整体出现在浏览器窗口的判断条件,如果是整个目标元素,方法同上,可参考下面的代码:if (window.pageYoffset > el.offsetTop - window.innerHeight - el.offsetHeight && window.pageYoffset < el.offsetTop) {}虽然这种方法也可以,但它的浏览器兼容性不如第一种。参考资料How to tell if a DOM element is visible in the current viewport?Lazy Loading Imagesjquery lazyload 插件infinite-scroll 插件scroll-magic 插件Building Skeleton Screens with CSS Custom Properties ...

December 7, 2018 · 1 min · jiezi

React props和state属性的具体使用方法

本篇文章主要介绍了React props和state属性的具体使用方法,具有一定的参考价值,对此有需要的朋友可以参考学习下。如有不足之处,欢迎批评指正。props不知道大家还记不记得xml标签中的属性,就像这样:<class id=“1”> <student id=“1”>John Kindem</student> <student id=“2”>Alick Ice</student></class>这样一个xml文件表达的意思是1班有两个学生,学号为1的学生名字为John Kindem,学号为2的学生名字为Alick Ice,其中id就是属性,你可以把它看做一个常量,它是只读的。html继承自xml,而JSX从莫种意义上又是html和js的扩展,属性的概念自然得到了传承。在React中,我们使用props这一概念向React组件传递只读的值,就像这样:// 假设我们已经自定义了一个叫Hello的组件ReactDom.render( <Hello firstName={‘John’} lastName={‘Kindem’}/>, document.getElementById(‘root’));在调用React组件的时候,我们可以像上面一样向组件传递一些常量,以便组件在内部调用。而调用的方法,就像下面这样:class Hello extends React.Component { constructor(props) { super(props); } render() { return ( <div> <h1>Hello, {this.props.firstName + ’ ’ + this.props.lastName}</h1> </div> );//欢迎加入前端全栈开发交流圈一起学习交流:864305860 }//面向1-3年前端人员}//帮助突破技术瓶颈,提升思维能力 ReactDom.render( <Hello firstName={‘John’} lastName={‘Kindem’}/>, document.getElementById(‘root’));在组件内部获取传递过来的props,只需要使用this.props对象即可,但是在使用之前,记得复写组件的构造函数,并且接受props的值以调用父类构造。当然,props也能够设置默认值,向下面这样:class Hello extends React.Component { constructor(props) { super(props); } static defaultProps = { firstName: ‘John’, lastName: ‘Kindem’ }; render() { return ( <div> <h1>Hello, {this.props.firstName + ’ ’ + this.props.lastName}</h1> </div> );//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860 }//面向1-3年前端人员}//帮助突破技术瓶颈,提升思维能力 ReactDom.render( <Hello/>, document.getElementById(‘root’));只需在ES6类中声明一个static的props默认值即可,运行效果和上面一样。props没有多复杂,稍微练习即可习得。state、组件生命周期你可能回想,如果我想在React组件中添加动态效果怎么办?这一问题需要使用React组件的state来解决,state即状态的意思,在React中,所有会变化的控制变量都应该放入state,每当state中的内容变化时,页面的相应组件将会被重新渲染,另外,state完全是组件内部的东西,外部无法向内部传递state,也无法直接改变state的值。先来举一个例子:import React from ‘react’;import ReactDom from ‘react-dom’; class Time extends React.Component { constructor(props) { super(props); // 初始化state this.state = { hour: 0, minute: 0, second: 0 } } //欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860 componentDidMount() { this.interval = setInterval(() => this.tick(), 1000); } componentWillUnmount() { clearInterval(this.interval); } tick() { // 计算新时间 let newSecond, newMinute, newHour; let carryMinute = 0, carryHour = 0; newSecond = this.state.second + 1; if (newSecond > 59) { carryMinute = 1; newSecond -= 60; } newMinute = this.state.minute + carryMinute; if (newMinute > 59) { carryHour = 1; newMinute -= 60; }//欢迎加入前端全栈开发交流圈一起学习交流:864305860 newHour = this.state.hour + carryHour; if (newHour > 59) newHour -= 60; // 设置新状态 this.setState({ hour: newHour, minute: newMinute, second: newSecond }); } render() { return ( <div> <h1>current time: {this.state.hour + ‘:’ + this.state.minute + ‘:’ + this.state.second}</h1> </div> );//欢迎加入前端全栈开发交流圈一起学习交流:864305860 }//面向1-3年前端人员}//帮助突破技术瓶颈,提升思维能力 ReactDom.render( <Time/>, document.getElementById(‘root’));这样就完成了一个计数器,数值一秒钟变化一次,来讲解一下代码:首先,state的初始化是在构造函数中,像这样:constructor(props) { super(props); // 在这初始化state this.state = { … }}//欢迎加入前端全栈开发交流圈一起学习交流:864305860而改变state是使用React组件基类中的一个自带函数:this.setState({ …});使用这个函数之前一定要注意this的作用域,箭头函数中的this指向外部this,而普通函数中的this指向函数本身。另外,这里使用到了两个React组件的生命周期回调:`componentDidMount() { // React组件被加载到dom中的时候被调用 …} //欢迎加入前端全栈开发交流圈一起学习交流:864305860componentWillUnmount() { // React组件从dom中卸载的时候被调用 …}所以这样一下上面的计时器代码应该就不是什么难事了,在React组件被加载到dom中的时候设置一个计时器,每秒钟更新一次state,state更新的同时页面中的组件将会被重新渲染,而当组件被卸载的时候,则需要清除定时器,就那么简单。不过React对于state的更新频率,有一个最大的限度,超过这个限度则会导致页面渲染的性能下降,大家需要注意不要在高频函数中使用setState。结语感谢您的观看,如有不足之处,欢迎批评指正。 ...

December 5, 2018 · 2 min · jiezi

前端性能优化之图像优化原理

前端性能优化中,图像的优化是非常重要的一环,为什么要说图像的优化呢,而不是我们常见的图片优化?因为这里的图像包括矢量图和位图,我们常说的图片优化是指位图的优化。这篇文章转载至奇舞周刊,大佬总结的非常好,大家直接看原文吧!图像优化原理–转载奇舞周刊

November 29, 2018 · 1 min · jiezi

前端如何高效的与后端协作开发

前端如何高效的与后端协作开发1. 前后端分离前端与后端的分离,能使前端的开发脱离后端的开发模式,拥有更大的自由度,以此便可做前端工程化、组件化、单页面应用等。可以参考:前后端分离、web与static服务器分离2. 尽量避免后端模板渲染web 应用的渲染方式分为服务器端渲染和客户端渲染,当下比较推荐的方式是客户端渲染,数据使用全 ajax 的方式进行交互。除非在一些不得不使用服务器端渲染的情况下(如门户、电商等),应当尽量使用客户端渲染,因为客户端渲染更能使前后端分离(项目分离、代码解耦、协作分离、职责分离等),也能更好的做本地接口模拟开发,提升开发效率。即使用服务器端渲染,在技术支持的条件下,可以使用 node 中间层(由前端人员开发),代替传统的后端模板渲染,这样可以使后端与前端完全解耦,后端与前端只有数据上的往来。可以参考:细说后端模板渲染、客户端渲染、node 中间层、服务器端渲染(ssr)3. 尽量避免大量的线上调试做好本地接口模拟开发(包括后端模板渲染),避免大量的线上调试,因为线上调试很不方便,也很费事,并且每次更新代码,都需要重新构建,然后同步到服务器。所以做好本地接口模拟开发,只要程序在本地运行是没问题的,一般线上就不会有太大的问题,这样就能大幅降低调试工作量,提升开发效率。4. 本地接口模拟开发本地接口模拟就是在本地模拟一个与服务器差不多的环境,能够提供数据所需的接口,进行错误模拟处理等等。本地接口模拟开发的意义就在于能够在本地完成几乎所有的开发与调试,尽量减少线上的调试,提高开发效率。一些常用库:browser-sync: 能让浏览器实时、快速响应文件更改(html、js、css、sass、less 等)并自动刷新页面,并且可以同时在PC、平板、手机等设备下进行调试。webpack-dev-middleware: A development middleware for webpackwebpack-hot-middleware: 热更新本地开发浏览器服务另外,本地接口模拟开发需要后端开发人员有规范的接口文档。可以参考:本地化接口模拟、前后端并行开发5. 规范的接口文档前端与后端协作提升开发效率的一个很重要的方法就是减少沟通:能够形成纸质的文档就不要口头沟通、能够把接口文档写清楚也不要口头沟通(参数、数据结构、字段含义等),特别是线上协作的时候,面对面交流是很困难的。一个良好的接口文档应当有以下的几点要求与信息:格式简洁清晰:推荐用 API Blueprint分组:当接口很多的时候,分组就很必要了接口名、接口描述、接口地址http 方法、参数、headers、是否序列化http 状态码、响应数据接口文档可以用一些文档服务(如 leanote)来管理文档,也可以用 git 来管理;书写方式可以用 markdown,也可以 YAML、JSON 等。推荐使用 markdown 方式写文档,用 git 管理文档。可以参考:本地化接口模拟、前后端并行开发API Blueprint6. 去缓存前端需要做好去客户端缓存的功能,保证用户始终都是使用的最新资源,不会因为因为缓存的问题而出现 bug。传统的去缓存是在静态资源 url 上加上版本号或者时间戳,不过因为构建工具的出现以及一些浏览器已经不支持这种方式了的缘故,这种方式已经是过去时了。现在去缓存是将文件 hash 化命名,只要文件变动,文件名就会不一样,以此才能彻底的去缓存。如果使用 webpack 进行打包,会自动将所有文件进行 hash 化命名。 可以参考:webpack output-filename7. 做好错误处理前端与后端都需要各自做好错误处理,以便发生错误能够有友好的提示,也能在用户反馈时快速准确定位错误来源和原因。一般前端的错误分为:脚本运行错误:js 脚本错误,找到堆栈信息,然后解决接口错误:服务器报错、数据返回不对、没有响应数据、超时等而接口错误分为:状态码错误(状态码非 2XX):服务器报错、超时等数据错误:没有响应数据、数据格式不对、数据内容不对可以参考:HTTP状态码8. 运行时捕捉 js 脚本错误当用户在用线上的程序时,怎么知道有没有出 bug;如果出 bug 了,报的是什么错;如果是 js 报错,怎么知道是那一行运行出了错?所以,在程序运行时捕捉 js 脚本错误,并上报到服务器,是非常有必要的。这里就要用到 window.onerror 了:window.onerror = (errorMessage, scriptURI, lineNumber, columnNumber, errorObj) => { const data = { title: document.getElementsByTagName(’title’)[0].innerText, errorMessage, scriptURI, lineNumber, columnNumber, detailMessage: (errorObj && errorObj.message) || ‘’, stack: (errorObj && errorObj.stack) || ‘’, userAgent: window.navigator.userAgent, locationHref: window.location.href, cookie: window.document.cookie, }; post(‘url’, data); // 上报到服务器};线上的 js 脚本都是压缩过的,需要用 sourcemap 文件与 source-map 查看原始的报错堆栈信息。可以参考:webpack - devtoolsource-map9. 移动端远程调试、vConsole、TBS Studio因为移动端的开发无法像 pc 端开发一样使用 Chrome 的开发者调试工具,所以调试移动端需要一些额外的技巧。移动端应用一般都运行在微信浏览器中、webview 中、手机浏览器中。远程调试(Remote Debugging)远程调试就是通过 USB 连接、端口转发、搭建代理等方式,将一个设备的 web 页面映射到另一个设备上,比如将手机的 webview 映射到 pc 上,达到调试的目的。移动端 web 应用调试难题从一开始就有,不过后来浏览器厂商基本都推出自己的远程调试工具来解决这个问题,包括 Opera Mobile、iOS Safari、Chrome for Android、UC 浏览器等,另外还有一些第三方开发的远程调试工具,比如 weinre 等。以 Android 为例,可以将 webview、Chrome for Android 中的页面映射到 pc 端的 Chrome DevTools,然后就可以在 pc 端调试移动端的页面了。可以参考:移动端Web开发调试之Chrome远程调试(Remote Debugging)vConsole一个轻量、可拓展、针对手机网页的前端开发者调试面板(chrome 开发者工具的便利实现)。这个是内嵌的页面当中的便捷调试器,基本上能够满足一般的需要远程调试的页面。github: https://github.com/Tencent/vConsoledemo: https://wechatfe.github.io/vconsole/demo.htmlTBS Studio因为微信浏览器是定制的浏览器,一般的远程调试方式都不可用,需要配合特定的工具,如微信开发者工具。TBS Studio 是另一个可以像 Chrome 一样调试远程微信浏览器页面的强大工具。可以参考:tbs studio - 腾讯浏览服务-调试工具TBS Studio10. 前端后并行开发正常情况下,前端的开发在完成 UI 或者组件开发之后,就需要等后端给出接口文档才能继续进行,如果能做到前后端并行开发,也能提升开发效率。前后端并行开发,就是说前端的开发不需要等后端给出接口文档就可以进行开发,等后端给出接口之后,再对接好后就基本上可以上线了。在本地化接口模拟的实现下,就可以做到前后端并行开发,只是在代码层面需要对 ajax 进行封装。可以参考:本地化接口模拟、前后端并行开发11. 友好的沟通不管工具多么厉害,很多时候都免不了要当面沟通,友好、心平气和的沟通也是很重要的哩!后续更多博客,查看 https://github.com/senntyou/blogs作者:深予之 (@senntyou)版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证) ...

October 30, 2018 · 1 min · jiezi

优秀前端必知的话题:我们应该做些力所能及的优化

在 Web 应用开发过程中,我们经常谈及到的就是优化,而优化往往又是既简单而又复杂的过程,优化这个命题很广,最终体现出来的都是用户体验问题,我们一切优化都是为了用户体验。为什么说简单?在现代 Web 开发生态中,有非常优秀的工具链帮助我们做一些很实际的优化工作,例如 webpack 。这些工具可以很好的帮助我们解决包之间的依赖、减小包大小、提取公共模块等等问题。为什么说复杂?优化这个话题我们谈了很多年,只要有用户群,优化问题就会一直存在。而优化工作涉及的领域特别广,包含的因素又特别多,有时候需要针对特定的场景做特殊的优化工作,所以说又很复杂。不管是简单还是复杂,作为程序员,我们应当做一些我们力所能及的优化工作,本文属于探讨性话题,希望广大网友能够在留言区留下您的一些思考。Code Splitting这里不探讨如何书写高性能的代码,而是探讨下我们书写的代码该如何被构建。这里以 webpack 为构建工具(版本为4.19),来阐述下在 webpack 我们该做的优化工作。webpack 从 v4 版本开始,做了很多的优化工作,详情请看这里 。我们就拿 Code Splitting 说起,Code Splitting 是 webpack 一项重要的编译特性,能够帮助我们将代码进行拆包,抽取出公共代码。利用这项特性我们可以做更多的优化工作,减少加载时间,例如可以做按需加载。而在 webpack 中开启 Code Splitting 也很简单,它是一项开箱即用的插件,示例代码如下:module.export = {// …optimization: {splitChunks: {chunks: ‘all’}}}上面的 chunks 配置建议大家配置为 all ,详细配置请参考:splitChunks.chunks这里给出个参考结果:分别为配置前和配置后这里明显多出了几个包含 vendors~ 字样的文件,而且你会发现 app 这个文件被提取出了 vendorsappreact_vendor 和 vendors_app_redux_vendor 这两个文件。至于最大的文件为什么还有1.02M,我们可以使用 analyze 来分析下包的结构,这里是由于包含了 antd 的文件。在实际开发过程中,路由也是需要进行 Code Splitting ,在过去我们经常使用 bundle-loader ,来帮助我们进行代码分割,它是基于 require.ensure 接口进行实现。既然我们可以对路由进行代码分割,那么路由页面中的组件我们是否可以按需加载,实现代码分割呢?答案是显然的。这种业务场景也是非常的多,这里我举一个例子,就是一个登录页面,登录有多种方式,其中最常见的就是账号登录和扫码登录,默认为扫码登录。当用户没有选择账号登录,那么按道理这部分代码我们可以不进行加载,从而减少加载时间,优化用户体验。我们建议能进行组件级分割就分割,最大化减小页面大小。在 React 中虽然也可以使用 bundle-loader 来实现组件级代码分割,但是也会有一些问题。在后来,React Router 官方也推荐使用 react-loadable 来进行代码分割。强烈建议 React 使用者使用此库,该库的功能很强大,是基于 import() 实现。它可以实现预加载、重新加载等等强大功能。Tree Shaking如果你对自己编写的代码很了解,你可以通过在 package.json 中添加 sideEffects 来启用 Tree Shaking ,即摇树优化,帮助我们删掉一些不用的代码。这里不再赘述,详情可以点击Tree Shaking。Dynamic import在谈到 Code Spliting 时,我们不得不想到 dynamic import ,在之前版本的 webpack 中,我们想实现动态加载使用的是 require.ensure ,而在新版本中,取而代之的 import() ,这是TC39关于使用 import()的提案,而目前 import()兼容性如下:import() 返回一个 Promise ,如果你想使用它请确保支持 Promise 或者使用 Polyfill ,在想使用 import() 前,我们还得使用预处理器,我们可以使用 @babel/plugin-syntax-dynamic-import 插件来帮助webpack解析。webpack 官方给了我们一个 dynamic import 的示例 ,这里我就不做举例。使用 import() 我们可以很方便的实现 preload 预加载、懒加载以及上面谈到的 Code Splitting。PolyfillPolyfill 现在对于大家来说应该并不陌生,他可以帮助我们使用一些浏览器目前并不支持的特性,例如 Promise 。在Babel中,官方建议使用 babel-preset-env 配合 .browserslistrc ,开发人员可以无需关心目标环境,提升开发体验。尤其在 Polyfill 方面,只要我们配置好 .browserslistrc ,Babel 就可以智能的根据我们配置的浏览器列表来帮助我们自注入 Polyfill ,比如:.babelrc{“presets”: [["@babel/preset-env",{“useBuiltIns”: “entry”}]]}useBuiltIns 告诉 babel-preset-env 如何配置 Polyfill ,这里我配置为:entry ,然后在 webpack 入口文件中引入 import ‘@babel/polyfill’ 即可,这里注意不能多次引入 import ‘@babel/polyfill’ ,否则会报错。.browserslistrc> 1%Last 2 versions这样就完成了自动根据 .browserslistrc注入 Polyfill ,但是这样有一个问题,就是所有的浏览器都会有 Polyfill 的并集。每个浏览器之间的特性具有很大的差异,为了尽可能的减小包的大小,我们可以为每个主流浏览器单独生成 Polyfill ,不同的浏览器加载不同的 Polyfill 。首屏文件SPA 程序打包出来的html文件一般都是很小的,也就2kb左右,似乎我们还可以利用下这个大小做个优化,有了解初始拥塞窗口 的同学应该知道,通常是14.6KB,也就意味着这我们还能利用剩下的12KB左右的大小去干点什么,这了我建议内联一些首屏关键的css文件(可以使用 criticalCSS ),或者将css初始化文件内联进去,当然你也可以放其他东西,这里只是充分利用下初始拥塞窗口 特性。这里顺便讲下css初始化,css初始化有很多种选择,其中有三种比较出名的,分别是:normalize.css 、sanitize.css 和 reset.css 。关于这三种的区别我就直接引用了。normalize.css and sanitize.css correct browser bugs while carefully testing and documenting changes. normalize.css styles adhere to css specifications. sanitize.css styles adhere to common developer expectations and preferences. reset.css unstyles all elements. Both sanitize.css and normalize.css are maintained in sync.缓存在利用 webpack 打包完之后,我们有些文件几乎不会变更,比如我这里列举的react、redux、polyfill相关的文件。entry: {react_vendor: [‘react’, ‘react-dom’, ‘react-router-dom’],redux_vendor: [‘react-redux’,‘redux’, ‘redux-immutable’,‘redux-saga’, ‘immutable’],polyfill: ‘@babel/polyfill’,app: path.join(process.cwd(),‘app/app.js’)}这些不变的文件我们就可以好好的利用下,常见(http 1.1)的就是设置 Etag ,Last-Modified 和 Cache-Control 。前面两种属于对比缓存,还是需要和服务器通信一次,只有当服务器返回 304 ,浏览器才会去读取缓存文件。而 Cache-Control 属于强制缓存,服务器设定 max-age 当过了设定的时间后才会向服务器发起请求。这里打包再配上 chunk-hash 几乎可以完美的配置缓存。当然还可以利用 localStorage 来做缓存,这里提出一种思路,是我以前在效仿百度首页缓存机制想的。我们可以在把js文件版本号弄成一个配置,同时存储在服务端和客户端,比如:{“react_version”: 16.4,“redux_version”: 5.0.6,“web_version”: 1.0}客户端将该版本号存储在 cookie 或其他存储引擎中,这里推荐 localForage 来做存储。服务端将最新版本号渲染到html文件中,然后通过js脚本对比版本号,如若版本号不同,则进行加载对应的js文件,加载成功后再存储到本地存储中。如果相同,则直接取本地存储文件。还有一种缓存的场景,就是有一些api服务端更新的进度很慢,比如一天之内访问的数据都是一样的,这样就可以对客户端进行请求缓存并拦截请求,从而优化速度,减小服务器压力。其他还有其他很多可以优化的地方,比如减少http请求、图片懒加载等等,就不一一列举了,大家可以看雅虎34条军规:尽量减少 HTTP 请求个数——须权衡使用 CDN(内容分发网络)为文件头指定 Expires 或 Cache-Control ,使内容具有缓存性。避免空的 src 和 href使用 gzip 压缩内容把 CSS 放到顶部把 JS 放到底部避免使用 CSS 表达式将 CSS 和 JS 放到外部文件中减少 DNS 查找次数精简 CSS 和 JS避免跳转剔除重复的 JS 和 CSS配置 ETags使 AJAX 可缓存尽早刷新输出缓冲使用 GET 来完成 AJAX 请求延迟加载预加载减少 DOM 元素个数根据域名划分页面内容尽量减少 iframe 的个数避免 404减少 Cookie 的大小使用无 cookie 的域减少 DOM 访问开发智能事件处理程序用 <link> 代替 @import避免使用滤镜优化图像优化 CSS Spirite不要在 HTML 中缩放图像——须权衡favicon.ico要小而且可缓存保持单个内容小于25K打包组件成复合文本写在最后关于优化的文章网上太多太多,这篇文章并不是告诉大家如何优化,而是在平时写代码时能够培养一种习惯、一种意识,就是做我们力所能及的优化以及要知其所以然。文 / GoDotDotDotLESS is MORE编 / 荧声本文已由作者授权发布,版权属于创宇前端。欢迎注明出处转载本文。本文链接:欢迎点赞、收藏、留言评论、转发分享和打赏支持我们。打赏将被完全转交给文章作者。感谢您的阅读。 ...

September 30, 2018 · 2 min · jiezi