Coder Social home page Coder Social logo

a-log's People

Contributors

qumai152909 avatar

Watchers

 avatar

a-log's Issues

Interview

缓存
cookie
浏览器存储
BFC
h5自适应、兼容性;
react:state频繁触发,优化
截流抖动;
定位;
css3
less;
继承;
动画;
1像素问题;
shell
优化编译构建速度;
redux mobx
node
webpack eslit
npm. 外链
svg修改颜色

继承
原声js事件

React 基础

virtual DOM 如何工作

当数据发生变化,比如setState时,会引起组件重新渲染,整个UI都会以virtual dom的形式重新渲染;

然后收集差异,也就是diff新的virtual dom和老的virtual dom的差异;

最后把差异队列里的差异,比如增加节点、删除节点、移动节点更新到真实的DOM上;

setState实际上是异步

setState实际上是异步的,这是为了提升react底层的性能,是为了防止时间间隔很短的情况下-多次改变state,React会在这种情况下将几次改变state合并成一次从而提高性能。

Fiber

https://juejin.cn/post/6844903816857403405#heading-4

diff算法

1, diff算法是同级比较,假设第一层两个虚拟DOM节点不一致,就不会往下比了,就会将原始页面虚拟DOM全部删除掉,然后用新的虚拟DOM进行全部的替换,虽然这有可能有一些性能的浪费,但是由于同层比对的算法性能很高,因此又弥补了性能的损耗。

2, 比较key: 做list循环的时候有一个key值,这样比对的时候就可以相对应的比对,找出要改变的,以及不需要渲染的,这样使用key做关联,极大的提升了虚拟DOM比对的性能,这要保证在新的虚拟DOM后key值不变,这就说明了为什么做list循环的时候key的值不要是index,因为这样没有办法保证原虚拟DOM和新虚拟DOM的key值一致性,而造成性能的损耗,一般这个key对应后台数据的唯一id字段,而不是循环的index。

举例说明:

变化前数组的值是[1,2,3,4],key就是对应的下标:0,1,2,3
变化后数组的值是[4,3,2,1],key对应的下标也是:0,1,2,3

什么是 Props?

Props 是 React 中属性的简写。

它们是只读的,即不可变。

它们总是在整个应用中从父组件传递到子组件。

子组件永远不能将 prop 送回父组件。这有助于维护单向数据流,通常用于呈现动态生成的数据。

React主要优点

  1. 它提高了应用的性能
  2. 可以方便地在客户端和服务器端使用
  3. 由于 JSX,代码的可读性很好
  4. React 很容易与 Meteor,Angular 等其他框架集成
  5. 使用React,编写UI测试用例变得非常容易

上下文Context

less继承

sticky

服务器端渲染

promise.all

react setState 异步

useCallback

缓存

发生了什么

dns

跨域

history

元素与组件

组件是由元素构成的。

元素数据结构是普通对象,

而组件数据结构是类或纯函数。

元素

元素是虚拟dom。

它是 React 中最小基本单位,可以使用 JSX 语法创建一个 React 元素:

const element = <div className="element">I'm element</div>

React 元素不是真实的 DOM 元素,它仅仅是 js 的普通对象(plain objects),所以也没办法直接调用 DOM 原生的 API。上面的 JSX 转译后的对象大概是这样的:

{
    _context: Object,
    _owner: null,
    key: null,
    props: {
    className: 'element'
    children: 'I'm element'
  },
    ref: null,
    type: "div"
}

除了使用 JSX 语法,我们还可以使用 React.createElement() 和 React.cloneElement() 来构建 React 元素。

组件

React 中有三种构建组件的方式。React.createClass()、ES6 class和函数组件。

事件

https://www.jianshu.com/p/41776f2f4d8b

https://cloud.tencent.com/developer/article/1516369

浏览器事件机制

1、事件捕获阶段

当某个事件触发时,文档根节点最先接受到事件,然后根据DOM树结构向具体绑定事件的元素传递。

该阶段为父元素截获事件提供了机会。
事件传递路径为:
window —> document —> boy —> div—> text

2、目标阶段

具体元素已经捕获事件。之后事件开始向根节点冒泡。

3、事件冒泡阶段

该阶段的开始即是事件的开始,根据DOM树结构由具体触发事件的元素向根节点传递。
事件传递路径:
text—> div —> body —> document —> window

react事件

  • React将事件绑定到了document上。并没有绑定到具体的dom节点上。

  • 基于浏览器的事件冒泡机制(冒泡),所有节点的事件都会在 document 上触发。

  • 然后由统一的事件处理程序来处理(主要是dispatchEvent);

  • 所以当事件触发的时候,分发函数dispatchEvent将指定函数执行。

dispatchEvent

react利用事件的冒泡机制,将事件处理函数绑定到document上,使用一个统一的事件监听器(dispatchEvent):

  • 1.维持了映射保存所有组件内的事件监听和处理函数;

  • 2.组件挂载、卸载的时,只是在监听器(dispatchEvent)上插入或删除一些对象;

  • 3.事件触发时,由监听器(dispatch)分发事件并调用相应的方法;

优点:

  • 统一规范,解决 ie 事件兼容问题,简化事件逻辑

  • 简化了事件处理和回收机制,效率大大提升。

  • 减少内存消耗,提升性能,不需要注册那么多的事件了,一种事件类型只在 document 上注册一次

  • 然后,进行自动绑定: 自动绑定this为当前组件实例。

合成事件 vs 原生事件

合成事件是什么?

合成事件是对原生事件的封装;

并对某些原生事件进行了升级和改造,使之能兼容不同的浏览器。


  • react中,e不是原生事件对象,而是包装过的对象,可以通过e.nativeEvent访问原生事件对象e`;
  • react的e,有两种方式阻止冒泡,e.stopPropagationstopImmediatePropagation`,两者的区别是:

e.stopPropagation只能阻止向父级元素冒泡,

stopImmediatePropagation不仅可以阻止向父级元素冒泡,还能阻止自身元素剩余的其他事件的执行。

原生事件总是比合成事件先执行

禁止冒泡会怎样?

如果一个节点上同时绑定了合成和原生事件,那么禁止冒泡后执行关系是怎样的呢?

原生事件(阻止冒泡)会阻止合成事件的执行

合成事件(阻止冒泡)不会阻止原生事件的执行。


原因:

浏览器事件的执行需要经过三个阶段,捕获阶段-目标元素阶段-冒泡阶段。

节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡。

既然原生都阻止冒泡了,那合成还执行个啥嘞。

好,轮到合成的被阻止冒泡了,那原生会执行吗?当然会了。

因为原生的事件先于合成的执行,所以合成事件内阻止的只是合成的事件冒泡。

两者不同

(1)命名规范不同
React事件的属性名是采用驼峰形式的,事件处理函数是一个函数;
原生事件通过addEventListener给事件添加事件处理函数

(2)React事件只支持事件冒泡。原生事件通过配置第三个参数,true为事件捕获,false为事件冒泡;

(3)事件挂载目标不同

(4)this指向不同

错误边界(Error Boundaries)

部分 UI 中的 JavaScript 错误不应该破坏整个应用程序。

为了解决这个问题,React 16引入了一个 “错误边界(Error Boundaries)” 的新概念。

componentDidCatch(err,info) {
        this.setState({hasError: true});
}

如何在React中使用innerHTML

增加dangerouslySetInnerHTML属性,并且传入对象的属性名叫_html

function Component(props){
    return <div dangerouslySetInnerHTML={{_html:'<span>你好</span>'}}>
            </div>
}

useReducer

useReducer是 useState 的另一种替代。
它接受(state, action) => newState,并且返回了一个与当前state成对的dispatch的方法。

useContext

如果想要组件之间共享状态,可以使用useContext。

简单来说Context的作用就是对它所包含的组件树提供全局共享数据的一种技术,
类似一个全局store,useContext和useReducer可以实现redux。

useCallback

useCallback 和 useMemo基本是一样的,都是对组件进行性能优化的;
应用场景:父组件向子组件传值时,父组件render会导致子组件也会跟着render,不管传的值是否发生改变,子组件都会render,造成资源的浪费。用useCallback 和 useMemo可以监控传值情况,对其进行缓存,传值没有改变时,会阻止子组件render。

两者区别在于:useCallback是对字面量进行缓存,而useMemo是对其进行计算缓存。
简单来说就是useCallback直接缓存函数,useMemo缓存函数的返回值


(useMemo有是的作用是什么呢?是避免在每次渲染时都进行高开销的计算的优化的策略,)
🤔大伙应该明白了useCallback的作用了,配合memo用于优化子组件的渲染次数

需要传入两个参数

  • callback(仅仅是个函数),并把要做事情的函数放在callback函数体内执行
  • deps 要做事情的函数需要引入的外部参数或者是依赖参数
  const handleChildrenCallback = useCallback(() => {
    handleChildren();
  }, []);// 咱们不需要就不要传入

useCallback 返回值

返回一个 memoized 回调函数。在依赖参数不变的情况下,返回的回调函数是同一个引用地址

注意 每当依赖参数发生改变useCallback就会自动重新返回一个新的 memoized 函数(地址发生改变)

useCallback使用场景

1、作为props,传递给子组件,为避免子元素不必要的渲染,需要配合React.Memo或者useMemo使用,否则无意义:

2、作为useEffect的依赖项,需要进行比较的时候才需要加上useCallback;

useMemo

  • 优化针对于当前组件高开销的计算,具有记忆功能。
  • 其实我们不希望在不改变count值的时候去重新执行computeExpensiveValue,使用useMemo,是可以做到

和useCallback不同之处

  • useCallback 优化针对于子组件渲染
  • useMemo 优化针对于当前组件高开销的计算

Refs

refs 干嘛用的?

Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的钩子。

useRef

  • useRef传入一个参数initValue,并创建一个对象**{ current: initValue } **给函数组件使用,
  • 在整个生命周期中该对象保持不变。
  • 变更.current属性不会引发组件重新渲染。
  • current 属性可以用来保存在整个生命周期**享数据的容器。

refs ( forwardRef Refs ) 转发是什么?

转发 ref 给子组件用的,俗称 ref 转发;

是父组件用来获取子组件的dom元素的:

// 子组件
const Child = React.forwardRef((props, ref) => (
    <input ref={ref} />
));

// 父组件
import React, { useRef, useEffect } from 'react';

function Father() {
  const myRef = useRef(null);
  
  effect(() => {
    if (myRef.current) {
        console.log(myRef.current);
    }
  }, []);
  
  return <Child ref={myRef} />
}

此时父组件的this.myRef.current的值是input这个DOM元素

Ref 转发 是让某些组件(Child)可以使用它们接收的 ref 的特性,这些组件还可以进一步将其传递给子组件。

refs 和 findDOMNode() 哪个是首选项?

最好使用 refs 回调 而不是 findDOMNode() API。

因为 findDOMNode() 将来会阻止对 React 的某些改进。而且`findDOMNode使用了this,已经不适用新版本的react。

ASCII 、Unicode、UTF-8

ASCII、utf-8是编码方式

Unicode是字符集

字节

计算机内部,所有信息最终都是一个二进制值。

每一个二进制位(bit)有01两种状态,因此8个二进制位就可以组合出256(2的8次方)种状态,这被称为一个字节(byte)。

也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从0000000011111111

ASCII 码

American Standard Code for Information Interchange,美国信息交换标准代码)

英语字符与二进制位之间的关系,做了统一规定。这被称为 ASCII 码

上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为 ASCII 码,一直沿用至今。

ASCII 码::一共规定了128个字符的相应二进制编码,

比如: 大写的字母A是65(二进制01000001);a=97(0110 0001); 空格是32(二进制00100000);

常见ASCII码的大小规则:09<AZ<a~z。

这128个符号(包括32个不能打印出来的控制符号,如delete、esc),只占用了1个字节的后面7位,最前面的一位统一规定为0

缺点

限制: 只能显示26个基本英文字母、英文标点符号、阿拉伯数字,因此只能用于显示现代美国英语。

在英语中,用128个符号编码便可以表示所有,但是用来表示其他语言,128个符号是不够的。

改进

改进方法: Unicode

虽然EASCII解决了部分西欧语言的显示问题,但对更多其他语言依然无能为力。因此,现在的软件系统大多采用Unicode

汉字编码

目前的编码标准主要有 ASCII、GB2312GBKUnicode等。

ASCII 编码是最简单的西文编码方案。

GB2312、GBK、GB18030 是汉字字符编码方案的国家标准。

Unicode 是全球字符编码的国际标准

GB2312

汉字多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。

比如,中文常见的 GB2312编码,使用两个字节表示一个汉字,所以理论上最多可以表示 256 x 256 = 65536 个符号。

注意: 虽然都是用多个字节表示一个符号,但GB类的汉字编码与 Unicode 和 UTF-8 是毫无关系的。

乱码

世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号

因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。

为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。

解决方式:

那么,世界上有没有统一的一种编码,将所有符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。

这就是 Unicode,这是一种所有符号的编码。

Unicode

Unicode 可以容纳100多万个符号。

每个符号的编码都不一样。

比如,U+0041表示英语的大写字母AU+4E25表示汉字(查询网站: https://home.unicode.org/)。

需要注意的是,Unicode 只是一个符号映射集,它只规定了符号的二进制代码是什么,却没有规定这个二进制代码应该如何存储。

比如,汉字的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

分区 平面

一般Unicode的码位表示成U+XXXXXX 的形式,X 代表一个十六制数字,X的个数是 4-6 ,也就是U+0000 ~ U+10FFFF间。

至于为什么上限是10FFFF,和目前的码位划分方式有关。

为了方便码位的管理,便于码位的分配,Unicode将编码空间均分成17 个 65536 大小的分区,每个分区称为平面(plane)。**

其中,第零平面,也就是U+0000~U+FFFF的码位叫做基本多语言平面,简称BMP(Basic Multilingual Plane)。我们平常的使用的大部分语言文字,包括除生僻字以外的汉字码位都规定在这个平面中。

其余平面叫做补充平面(Supplementary Planes)

问题

这里就有两个严重的问题?

  • 1, 如何才能区别 Unicode 和 ASCII ? (计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?)
  • 2, 我们已经知道,英文字符只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

根据发展趋势,要求出现统一的编码方式。UTF-8 就是在互联网上使用最广的一种用 Unicode字符编码的方式。

UTF-8

**UTF-8 最大的一个特点,就是它是一种变长的编码方式: **它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-88-bit Unicode Transformation Format)是一种针对Unicode可变长度字符编码,也是一种前缀码

编码规则:

  • 1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。

  • 2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

    下表总结了编码规则,字母x表示可用编码的位。

    Unicode符号范围     |            UTF-8编码方式
    (十六进制)          |              (二进制)
    ----------------------+---------------------------------------------
    0000 0000-0000 007F | 0xxxxxxx
    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Unicode中前128个字符,和ASCII码相同,用单个字节进行编码,这使得原来处理ASCII字符的软件无须或只须做少部分修改,即可继续使用。

解读

跟据上表,解读 UTF-8 编码非常简单。

如果一个字节的第一位是0,则这个字节单独就是一个字符;

如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。


  • 对于UTF-8编码中的任意字节B,如果B的第一位为0,则B独立的表示一个字符(ASCII码);
  • 如果B的第一位为1,第二位为0,则B为一个多字节字符中的一个字节(非ASCII字符);
  • 如果B的前两位为1,第三位为0,则B为两个字节表示的字符中的第一个字节;
  • 如果B的前三位为1,第四位为0,则B为三个字节表示的字符中的第一个字节;
  • 如果B的前四位为1,第五位为0,则B为四个字节表示的字符中的第一个字节;

示例:

的 Unicode 是4E25100111000100101),

根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF), 因此的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx

然后,从的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0

这样就得到了,的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5

即: 的Unicode是4E25,UTF-8 编码E4B8A5

结构

  • 1, 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。

  • 2, 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、则需要两个字节编码(Unicode范围由U+0080至U+07FF)

  • 3, 其他**基本多文种平面(BMP)**中的字符(这包含了大部分常用字,如大部分的汉字)使用三个字节编码(Unicode范围由U+0800至U+FFFF)

  • 4, 其他极少使用的Unicode 辅助平面的字符使用四至六字节编码。

优点

  • ASCII是UTF-8的一个子集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。
  • UTF-8字符串可以由一个简单的算法可靠地识别出来。

缺点

不利于正则匹配;

补充:emoji表情

2010年,Unicode 开始为 Emoji 分配码点。

Unicode只是规定了 emoji 的码位和含义。并没有规定它的样式。举例来说,码点U+1F600表示一张微笑的脸,但是这张脸长什么样,则由各个系统自己实现。

因此,当我们输入这个 Emoji 的时候,并不能保证所有用户看到的都是同一张脸。如果用户的系统没有实现这个 Emoji 符号,用户就会看到一个没有内容的方框,因为系统无法渲染这个码点。

https://apps.timwhitlock.info/emoji/tables/unicode#block-6b-additional-transport-and-map-symbols

补充:中文正则

/^[\u4e00-\u9fa5]+$/.test(str)

UTF-8 (Unicode) :\u4e00-\u9fa5 (中文)

https://moelove.info/2014/07/22/%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E4%B8%AD%E6%96%87%E5%8F%8A%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81%E9%97%AE%E9%A2%98/

base64

什么是base64编码? 

图片的base64编码就是可以将一副图片数据编码成一串字符串,使用该字符串代替图像(地址)

<img src="data:image/gif;base64,R0lGODlhHAAmAKIHAKqqqsvLy0hISObm5vf394uLiwAAAP///yH5B…EoqQqJKAIBaQOVKHAXr3t7txgBjboSvB8EpLoFZywOAo3LFE5lYs/QW9LT1TRk1V7S2xYJADs=">

Base64 的内容是有 0 ~ 9,a ~ z,A ~ Z,+,/ 组成,正好 64 个字符。

base64 可以用来将二进制的字节序列数据编码成, 由64个字符构成的文本。编码时,每 6 个二进制比特为一个编码单元。所以,经过 base64 编码后体积会增加 1/3。(https://juejin.cn/post/6844904197519835150)

使用时,在传输编码方式中指定 base64。等号 = 用来作为后缀。

图片的base64编码和Data URLs

绝大多数现代浏览器都支持一种名为 Data URLs 的特性,允许使用 base64 对图片的二进制数据进行编码,将其作为文本字符串嵌入网页中。

Data URLs 由四个部分组成:

  • 1 前缀(data:

  • 2, 指示数据类型的 MIME 类型 (image/jpeg); ( 默认值为 text/plain;charset=US-ASCII)

  • 3, 如果非文本则为可选的 base64 标记;

  • 4,数据本身(编码后数据);

data:[<mediatype>][;base64],<data>

<img src="data:image/jpeg;base64,R0lGODlhHAAmAKIHAKqqqsvLy0hISObm5vf394uLiwAAAP///yH5B…EoqQqJKAIBaQOVKHAXr3t7txgBjboSvB8EpLoFZywOAo3LFE5lYs/QW9LT1TRk1V7S2xYJADs=">

注意:

但需要注意的是:如果图片较大,图片的色彩层次比较丰富,则不适合使用这种方式,因为该图片经过 base64 编码后的字符串非常大,会明显增大 HTML 页面的大小,从而影响加载速度。

图片base64编码意义

这样做有什么意义呢?我们知道,我们所看到的网页上的每一个图片,都是需要消耗一个http请求下载而来的。

不管如何,图片的下载始终都要向服务器发出请求,要是图片的下载不用向服务器发出请求,而可以随着 HTML 的下载同时下载到本地那就太好了,而base64正好能解决这个问题。

base64怎么编码的 🧚‍♀️🧚‍♀️🧚‍♀️

Base64编码本质上是一种将二进制数据转成文本数据的方案。

对于非二进制数据,是先将其转换成二进制形式,然后每连续6比特(2的6次方=64)计算其十进制值,根据该值在上面的索引表中找到对应的字符,最终得到一个文本字符串。

以每 6 个比特为一个单元,进行 base64 编码操作。Man 这个字符串的长度刚好是 3,共24比特,我们可以用 4(24/6=4) 个 base64 单元来表示。

但如果待编码的字符串长度不是 3 的整数倍时,应该如何处理呢?

如果要编码的字节数不能被 3 整除,最后会多出 1 个或 2 个字节,那么可以使用下面的方法进行处理:先使用 0 字节值在末尾补足,使其能够被 3 整除,然后再进行 base64 的编码。

以编码字符 A 为例,其所占的字节数为 1,不能被 3 整除,需要补 2 个字节。编码结果“QQ==”

假设需编码的字符串为 BC,其所占字节数为 2,不能被 3 整除,需要补 1 个字节。编码结果:“QkM=”

url-loader和图片base64

{
    test: /\.(gif|jpg)$/,
    loader: 'url-loader?limit=30000',
    options: {
        name: '[name].[ext]?[hash]'
    }
}

url-loader 的工作流程如下:

  • 1,获取 limit 参数
  • 2, 如果文件大小在 limit 之内,则直接对文件进行base64编码(怎样编码呢?),并返回编码后的内容。
  • 3, 如果文件大小超过了 limit ,则调用 file-loader

源码:

module.exports = function(content) {

    // 获取 options 配置,上面已经讲过了就不在重复
  var options =  loaderUtils.getOptions(this) || {};
  // Options `dataUrlLimit` is backward compatibility with first loader versions
    // limit 参数,只有文件大小小于这个数值的时候我们才进行base64编码,否则将直接调用 file-loader
  var limit = options.limit || (this.options && this.options.url && this.options.url.dataUrlLimit);

  if(limit) {
    limit = parseInt(limit, 10);
  }

  var mimetype = options.mimetype || options.minetype || mime.lookup(this.resourcePath);

  // No limits or limit more than content length
  if(!limit || content.length < limit) {
    if(typeof content === "string") {
      content = new Buffer(content);
    }

    // 直接返回 base64 编码的内容 (content.toString("base64")是Node的转换方法)
    return "module.exports = " + JSON.stringify("data:" + (mimetype ? mimetype + ";" : "") + "base64," + content.toString("base64"));
  }

    // 超过了文件大小限制,那么我们将直接调用 file-loader 来加载
  var fallback = options.fallback || "file-loader";
  var fallbackLoader = require(fallback);

  return fallbackLoader.call(this, content);
}

// 一定别忘了这个,因为默认情况下 webpack 会把文件内容当做UTF8字符串处理,而我们的文件是二进制的,当做UTF8会导致图片格式错误。
// 因此我们需要指定webpack用 raw-loader 来加载文件的内容,而不是当做 UTF8 字符串传给我们
// 参见: https://webpack.github.io/docs/loaders.html#raw-loader
module.exports.raw = true

https://github.com/lihongxun945/diving-into-webpack/blob/master/4-file-loader-and-url-loader.md 🐎 webpack 源码解析 四:file-loader 和 url-loader

怎样将文件进行base64编码呢?

从上面代码可以看出,进行base64编码的代码为: content.toString("base64");这是什么意思呢?

答案:Node.js Base64 Encoding

语法:

 buf.toString([encoding[, start[, end]]])

下面是如何在Node.js中将一个普通字符串encode成Base64格式的代码:

// plain-text string
const str = 'Base64 Encoding in Node.js';

const buff = Buffer.from(str, 'utf-8'); // 根据给定的字符串创建一个缓冲区

const base64 = buff.toString('base64');
// QmFzZTY0IEVuY29kaW5nIGluIE5vZGUuanM=

https://cloud.tencent.com/developer/article/1732836

btoa

atob()btoa() : 用于Base64编解码的标准JavaScript函数, 这些方法是窗口对象的一部分,仅在浏览器中可用。

const str = "JavaScript is fun!!";

// encode the string
const base64 = btoa(str);

console.log(base64); // SmF2YVNjcmlwdCBpcyBmdW4hIQ==

注意:btoa 仅支持 ASCII 编码,中文会报错,Unicode字符也会报错

默认,btoa()适用于8个字节的二进制数据,如果是超过8个字节的数据,例如Unicode字符,btoa()会报异常错误。例如:

const str = "JavaScript is fun 🎉";

// encode the string
const encodedStr = btoa(str);

console.log(encodedStr); // 报错

如何转换中文?

答案:借助 encodeURIComponentdecodeURIComponent

由于 btoa 仅支持 ASCII 字符序列,如果通过 encodeURIComponent 将中文字符编码成ASCII字符序列,再通过 btoa 进行 base64 编码。

window.btoa(encodeURIComponent('孙')); // "JUU1JUFEJTk5"

// 反之
decodeURIComponent(window.atob('JUU1JUFEJTk5')); // 孙

atob

const encodedStr = "SmF2YVNjcmlwdCBpcyBmdW4hIQ==";

// decode the string
const str = atob(encodedStr);

console.log(str); // output: JavaScript is fun!!

实例: 浏览器端图片压缩

https://juejin.cn/post/6844904197519835150#heading-4

在一些场合中,我们希望在上传本地图片时,先对图片进行一定的压缩,然后再提交到服务器,从而减少传输的数据量。在前端要实现图片压缩,我们可以利用 Canvas 对象提供的 toDataURL() 方法,该方法接收 typeencoderOptions 两个可选参数。

其中 type 表示图片格式,默认为 image/png。而 encoderOptions 用于表示图片的质量,在指定图片格式为 image/jpegimage/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92,其他参数会被忽略。

下面我们来看一下具体如何实现图片压缩:

// compress.js
const MAX_WIDTH = 800; // 图片最大宽度

function compress(base64, quality, mimeType) {
  let canvas = document.createElement("canvas");
  let img = document.createElement("img");
  img.crossOrigin = "anonymous";
  return new Promise((resolve, reject) => {
    img.src = base64;
    img.onload = () => {
      let targetWidth, targetHeight;
      if (img.width > MAX_WIDTH) {
        targetWidth = MAX_WIDTH;
        targetHeight = (img.height * MAX_WIDTH) / img.width;
      } else {
        targetWidth = img.width;
        targetHeight = img.height;
      }
      canvas.width = targetWidth;
      canvas.height = targetHeight;
      let ctx = canvas.getContext("2d");
      ctx.clearRect(0, 0, targetWidth, targetHeight); // 清除画布
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      let imageData = canvas.toDataURL(mimeType, quality / 100);
      resolve(imageData);
    };
  });
}

对于返回的 Data URL 格式的图片数据,为了进一步减少传输的数据量,我们可以把它转换为 Blob 对象:

function dataUrlToBlob(base64, mimeType) {
  let bytes = window.atob(base64.split(",")[1]);
  let ab = new ArrayBuffer(bytes.length);
  let ia = new Uint8Array(ab);
  for (let i = 0; i < bytes.length; i++) {
    ia[i] = bytes.charCodeAt(i);
  }
  return new Blob([ab], { type: mimeType });
}

在转换完成后,我们就可以压缩后的图片对应的 Blob 对象封装在 FormData 对象中,然后再通过 AJAX 提交到服务器上:

function uploadFile(url, blob) {
  let formData = new FormData();
  let request = new XMLHttpRequest();
  formData.append("image", blob);
  request.open("POST", url, true);
  request.send(formData);
}

其实 Canvas 对象除了提供 toDataURL() 方法之外,它还提供了一个 toBlob() 方法,该方法的语法如下:

canvas.toBlob(callback, mimeType, qualityArgument)

toDataURL() 方法相比,toBlob() 方法是异步的,因此多了个 callback 参数,这个 callback 回调方法默认的第一个参数就是转换好的 blob文件信息。

介绍完上述的内容,我们来看一下本地图片压缩完整的示例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>本地图片压缩</title>
  </head>
  <body>
    <input type="file" accept="image/*" onchange="loadFile(event)" />
    <script src="./compress.js"></script>
    <script>
      const loadFile = function (event) {
        const reader = new FileReader();
        reader.onload = async function () {
          let compressedDataURL = await compress(
            reader.result,
            90,
            "image/jpeg"
          );
          let compressedImageBlob = dataUrlToBlob(compressedDataURL);
          uploadFile("https://httpbin.org/post", compressedImageBlob);
        };
        reader.readAsDataURL(event.target.files[0]);
      };
    </script>
  </body>
</html>

链接:

https://cloud.tencent.com/developer/article/1730338 如何在Node.js和Express中上传文件

如何使用JavaScript漂亮地打印JSON对象 https://cloud.tencent.com/developer/article/17

https://github.com/TheAlgorithms/Javascript 印度人维护的算法

https://cloud.tencent.com/developer/article/1729516 前段项目整理

https://cloud.tencent.com/developer/article/1677084 ts入门

https://cloud.tencent.com/developer/article/1651267 Nodejs中读取文件目录中的所有文件

How to read and write JSON files in Node.js https://attacomsian.com/blog/nodejs-read-write-json-files

http://blog.xiayf.cn/2016/01/24/base64-encoding/ Base64编码原理与应用

0-h5优化

https://www.cnblogs.com/kunmomo/p/11556146.html webView是什么

https://tech.meituan.com/2017/06/09/webviewperf.html webView 详细 图片

前端优化总结

在过去PC和手机浏览器中,已经有无数的优化手段,总结起来无外乎以下几点:

  • 减少请求数量:合并资源,减少HTTP请求,懒加载,内联资源等
  • 减少请求资源大小:压缩资源,gzip,web图片,减少cookie等
  • 提高请求速度:DNS预解析,资源预加载,CDN加速等
  • 缓存:浏览器缓存,manifest缓存等
  • 渲染:css、js加载顺序,同构直出等

几乎所有的应用最先遇到的性能瓶颈都是网络请求,所以能减少请求的缓存就显得比较重要。

浏览器的缓存机制这里就不做赘述,主要有通过设置Cache-Control等HTTP请求头的方式,和PWA中利用Service Worker的方式。利用浏览器缓存机制,页面再次请求资源时,可以从缓存中获取,减少网络请求。

而数据方面的请求,可以通过localStorage进行数据的缓存,首次渲染时可以使用本地缓存数据,然后再请求更新。

通过以上手段可以使页面第二次被访问时,快速打开,但是第一次访问时,依旧存在慢的问题。

作者:Chersquwn链接:https://juejin.im/post/6844904021426176007

webview优化

webview是什么?

WebView是一个基于webkit引擎、可以解析DOM 元素,展现web页面的控件,

它和浏览器展示页面的原理是相同的,所以可以把它当做浏览器看待。

WebView是用于展示网络请求后的结果,也就是将url网络请求的结果展示在里面。

如果原生系统没有webview,则是无法渲染展示html的。

使用webview的好处?

原生APP是将页面的布局设计,以及业务代码打包,然后用户下载安装使用。而webview是通过加载html文件来进行页面的展示,当需要更新页面布局的或者业务逻辑变更时,如果是原生的APP,就需要修改前端内容后,升级打包,重新发布才可以使用最新的内容。

而通过webview方式的页面,则只需要修改html代码或者js文件(如果是从服务器端获取,只要新的文件部署完成),用户重新刷新就可以使用更新后的,无需通过下载安装的方式完成升级。

页面启动的过程

为什么H5页面的启动时间长?尤其是SPA应用?

从用户点击到页面渲染完成,大体上经历了一下过程:

初始化webview => 请求html => 解析html => 请求css和js => 渲染 => 解析和执行js => 请求数据 => 渲染 => 下载图片 => 渲染 => 页面显示可交互

  • 1,交互无反馈 (webview初始化)
  • 2,页面白屏(建立连接,接收页面,接收样式,渲染)
  • 3,页面基本框架出现,但是没有数据;页面处于loading状态 (下载解析执行脚本,接收数据,渲染)
  • 4,出现所需的数据

常规SPA应用中,通常在请求完并且执行js时,才会出现loading之类的画面,而在这之前都是白屏,用户体验就很差,尤其是网络慢的时候。那么怎么才能进行优化呢?

为什么WebView总是很慢

  • 在浏览器中,我们输入地址时(甚至在之前),浏览器就可以开始加载页面。
  • 而在客户端中,客户端需要先花费时间初始化WebView,初始化完成后,才开始加载。

而这段时间,由于WebView还不存在,所有后续的过程是完全阻塞的。

webview初始化:

webview 是移动端浏览器实例,几乎具备 PC 端浏览器的绝大多数能力,客户端在使用 webview 打开 H5 页面前,需要实例化 webview 对象,其初始化的过程在 android 系统中需要大约 500ms 以上的时间。

有一种优化手段是:使用对象复用机制,提前创建 webview 对象池,需要使用 webview 时,直接从池中获取初始化完毕的对象,这种方式,可以避免每次打开 H5 页面都要初始化 webview 实例,从而提升页面打开速度。

L P App的webview

App Webview 目前没有做预加载,点击进入H5页面时,才会初始化webview,关闭 H5 页面时会销毁 webview,再次打开时重新初始化。

目测初始化 Webview 的时间 ,**总白屏时间是几秒级的,**从 native 进入 H5 和 H5 进入 H5 页面,时间差异明显, 冷热启动的具体耗时要客户端才能debug详细数据。

总结:

  • 初始化Weview预加载,可以极大缩短白屏等待时间(和翔宇沟通过,他们也有意向做 webview 的前置)
  • 启动webview进程过程中,可以由 native 代理页面的网络请求,等启动完毕,资源也请求好了(客户端表示目前访问时不清楚要下载哪些资源,如果有列表就可以做)
  • loading时间可以忽略不计,目前 C 端 H5 页面是服务端渲染,不是前端渲染

业界已有实践的方法

各种方法,总结起来都是围绕两点:

  1. 在使用前预先初始化好WebView,从而减小耗时。
  2. 在初始化的同时,通过Native来完成一些网络请求等过程,使得WebView初始化不是完全的阻塞后续过程。

1,提前初始化全局webView

  • 当App刚启动时,就初始化全局webView并隐藏,
  • 当用户访问了webView时,直接使用这个 webView加载对应网页。

**优点:**这种方法可以比较有效的减少WebView在App中的首次打开时间。当用户访问页面时,不需要初始化WebView的时间。

**缺点:**是需要额外消耗内存。还有需要webView切换时需要清除页面痕迹,容易造成内存泄漏。

2, 设置webView缓冲池

App启动后,在webView的缓冲池,初始化多个webView。

需要加载网页时,可以去缓冲池中去取出新的webView。不需要时就可以直接销毁,同时初始化新的webView放进缓冲池中。

缺点:是相比而言需要更大的额外内存消耗。但是确保减少webView初始化消耗的时间和降低清除页面所造成的内存泄漏

3, 客户端代理数据请求

在初始化webview的同时,通过 Native 来完成一些网络请求等,然后再回传给WebView,使得 WebView初始化不是完全的阻塞后续过程。

特点:

此方法虽然不能减小WebView初始化时间,但数据请求和WebView初始化可以并行进行,总体的页面加载时间就缩短了;缩短总体的页面加载时间。

建立连接-优化

在页面请求的数据返回之前,主要有以下过程耗费时间:

  • DNS
  • connection
  • 服务器处理

DNS

DNS优化:WebView采用和客户端API相同的域名

DNS会在系统级别进行缓存,对于WebView的地址,如果使用的域名与native的API相同,则可以直接使用缓存的DNS而不用再发起请求。

以美团为例,美团的客户端请求域名主要位于api.meituan.com,然而内嵌的WebView主要位于 i.meituan.com。

当我们初次打开App时:

  • 客户端App首次打开都会请求api.meituan.com,其DNS将会被系统缓存。
  • 然而当打开WebView的时候,由于请求了不同的域名,需要重新获取i.meituan.com的IP。

根据统计,至少10%的用户打开WebView时耗费了60ms在DNS上面,如果WebView的域名与App的API域名统一,则可以让WebView的DNS时间全部达到1.3ms的量级。

dns-prefetch

DNS Prefetch 是一种DNS 预解析技术,浏览器会在加载网页时,对网页中的域名进行解析缓存,这样在你单击当前网页中的连接时就无需进行DNS的解析,而是直接利用缓存,减少用户等待时间,提高用户体验。


当浏览器从第三方服务跨域请求资源的时候,在浏览器发起请求之前,这个第三方的跨域域名需要被解析为一个IP地址,这个过程就是DNS解析DNS缓存可以用来减少这个过程的耗时,DNS解析可能会增加请求的延迟,对于那些需要请求许多第三方的资源的网站而言,DNS解析的耗时延迟可能会大大降低网页加载性能。

dns-prefetch可帮助开发人员处理DNS解析延迟问题。

HTML <link>元素通过dns-prefetch的rel属性值提供此功能。然后在href属性中指定跨域域名:

<link rel="dns-prefetch" href="https://fonts.googleapis.com/">

每当站点引用跨域域上的资源时,都应在<link>放置dns-prefetch提示


这个功能有个默认加载条件,所有的a标签的href在chrome,firefox包括高版本的IE都会自动去启用DNS Prefetching,也就是说,你网页的a标签href带的域名,是不需要在head里面加上link手动设置的。

​ 但有一点他没有提到,a标签的默认启动在HTTPS不起作用。需要如下设置开启

<meta http-equiv="x-dns-prefetch-control" content="on">

作者:zyy1688链接:https://juejin.im/post/6844903612829663245

DNS 的预解析

现代浏览器都支持 DNS 的预解析,学名:DNS Prefetching。用法也很简单,就是在html代码里加入这样的 link 标签

<link rel="dns-prefetch" href="//delai.me">  

注意

  • 首先,dns-prefetch仅对跨域上的DNS查找有效,因此请避免将其用于您当前访问的站点。

    这是因为当浏览器解析到当前Link元素内容时,您站点域背后的IP已经被解析。

  • 其次,还可以将dns-prefetch(和其他资源提示)指定为HTTP标头:

    Link: <https://fonts.googleapis.com/>; rel=dns-prefetch
  • 第三,考虑将dns-prefetch与preconnect结合

    尽管dns-prefetch仅执行DNS查找,但preconnect会建立与服务器的连接。如果站点是通过HTTPS服务的,则此过程包括DNS解析,建立TCP连接以及执行TLS握手。将两者结合起来可提供机会,进一步减少跨源请求的感知延迟。您可以安全地将它们一起使用,如下所示:

    <link rel="preconnect" href="https://fonts.googleapis.com/" crossorigin>
    <link rel="dns-prefetch" href="https://fonts.googleapis.com/">

之所以把dns-prefetch放在preconnect之后,是因为浏览器对dns-prefetch的支持比对preconnect的支持要好。不支持preconnect的浏览器仍然可以通过回退到dns-prefetch来获得更多好处。

正确的使用姿势

1.对静态资源域名,做手动dns prefetching。
2.对js里会发起的跳转、请求做手动dns prefetching。
3.不用对超链接做手动dns prefetching,因为chrome会自动做dns prefetching。
4.对重定向跳转的新域名做手动dns prefetching,比如:页面上有个A域名的链接,但访问A会重定向到B域名的链接,这么在当前页对B域名做手动dns prefetching是有意义的。

总结:

预解析的实现:

\1. 用meta信息来告知浏览器, 当前页面要做DNS预解析:

\2. 在页面header中使用link标签来强制对DNS预解析:

注:dns-prefetch需慎用,多页面重复DNS预解析会增加重复DNS查询次数。

多域名

Chrome 等现代化浏览器,都会有同域名限制并发下载数的情况,不同的浏览器及版本都不一样,简单的情况如下:一般为6个以内。

使用不同的域名,可以最大化下载线程,但注意保持在 2~4 个域名内,以避免 DNS 查询损耗(会使DNS解析负担过重)。

例如,动态内容放在 csspod.com 上,静态资源放在 static.csspod.com 上。这样还可以禁用静态资源域下的 Cookie,减少数据传输,详见 Cookie 优化。cookie隔离

浏览器对并发请求的数目限制是针对域名的,即针对同一域名(包括二级域名)在同一时间支持的并发请求数量的限制。如果请求数目超出限制,则会阻塞。因此,网站中对一些静态资源,使用不同的一级域名,可以提升浏览器并行请求的数目,加速界面资源的获取速度。

网络优化

大致方向:

  • 1, 保证接口设计的合理性,必要时合并网络请求,减少请求次数;
  • 2,网络缓存(在弱网或者是无网络的情况下,因为有缓存的支持,不至于 APP 打开一片空白,这给用户更好的体验。)
  • 3,数据压缩,如 Gzip 压缩 request 和 response,减少网络流量传输。

  • 配置浏览器缓存,主要指强缓存和协商缓存,可以大大减少网络时延,减少服务器压力。
  • 资源压缩,前端有成熟的工具可以对生成的 js、css 等产物进行压缩,若有必要可以还考虑 gzip 压缩,获得更大的压缩比。
  • 资源请求合并,过多分散的资源包会产生过多的网络请求,但也不能随意合并,最佳的方式是按照页面或者模块进行划分,并配置 async 属性来异步加载 script 脚本。

预缓存

前情提要

App应用的功能代码,通常在用户访问之前,就已经以安装包的形式,通过应用市场下载安装好了。

而网页应用的功能代码(静态资源),则是在用户实际访问时,才实时下载运行。

这一『用时下载』的特点是一把双刃剑,既带来了实时更新的灵活性,也造成了应用启动的延迟,导致网页应用启动速度远远落后于App应用,造成交互体验和用户转化短板。

预缓存概念:

由于静态资源具有确定性,因此可以先主动获取所需缓存的资源列表,并且在 Service Worker 安装阶段,就主动发起静态资源请求并缓存,这样一旦新的 Service Worker 被激活之后,缓存就直接能投入使用了。

这是一个资源预取的过程,因此静态资源的缓存方案也称为预缓存方案

  • 资源列表获取

H5 页面加速优化

有三个方向可以进行优化:

  • 页面启动白屏时间
  • H5 页面的交互体验,如响应流畅度
  • 页面渲染性能

静态资源优化

静态资源主要指 html,js 和 css 资源,对于单页应用而言主要是 js 和 css。

按需加载,对于单页应用,如果在首页,就把整个站点的资源全部下载,其实是不合理的,使用按需加载(懒加载)的方式可以有效提高首页性能。

骨架屏也是移动端首屏优化的一个重要手段,在页面数据未准备好的情况,相比与枯燥的白屏页面而言,展示骨架屏能给用户一个好的感官体验。但是如何生成质量高的骨架屏也是一个难点,需要综合考虑 ROI 来选择是否使用骨架屏。

资源加载顺序

  1. CSS的加载会在HTML解析到CSS的标签时开始,所以CSS的标签要尽量靠前。
  2. 但是,CSS链接下面不能有任何的JS标签(包括很简单的内联JS),否则会阻塞HTML的解析。
  3. 如果必须要在头部增加内联脚本,一定要放在CSS标签之前。

通常情况下,CSS不会阻塞HTML的解析,但如果CSS后面有JS,则会阻塞JS的执行直到CSS加载完成(即便JS是内联的脚本),从而间接阻塞HTML的解析。

ssr优化

还有另外一个完全不同思路来优化移动端 H5 页面打开速度,那就是服务端渲染,也称之为 SSR。

简单来讲就是,服务端将页面的 html 和数据提前组装好再传递给浏览器,浏览器只负责解析 html 和展示,因此首屏渲染较快。

但是会给服务端增加压力和复杂度,现实中需要综合考虑利弊以及 ROI 来选择是否使用 SSR 方案。

#我的优化实践

1, Webview 首屏优化

客户端建立 webview 复用池,预初始化 webview

2,静态资源预缓存

将变更不频繁的公用资源,通过map存放在App本地,直接从本地缓存中读取资源

3,公用静态资源统一存放

h5 公用的资源存放到 fe-lib-h5 项目中,多项目切换可利用缓存 。

多端公用

项目地址:http://gitcode.lietou.com/dev38/fe-global/fe-lib-h5

使用文档:http://gitcode.lietou.com/dev38/fe-global/fe-lib-h5/blob/master/README.md

fly-lib-h5:

h5公用静态资源库

  • 直接在 jsp 使用 fly-lib-h5 的项目链接即可
// 示例
<script>
  FeLoader.get(
    '//s.lietou-static.com/fe-lib-h5/v5/babel-polyfill/6.8.0/polyfill.min.js',
    '//s.lietou-static.com/fe-lib-h5/v5/react/16.8.6/react.min.js',
    '//s.lietou-static.com/fe-lib-h5/v5/react-dom/16.8.6/react-dom.min.js',
  );
</script>
  • 目录划分
- asset // 生产静态资源目录,npm run prd 编译后生成
- asset-dev // 开发静态资源目录,npm run dev 编译后生成
- src // 资源文件总目录
	- react // 资源目录(资源必须创建目录管理)
		- 16.8.6 第三方资源按照版本号创建次级目录
			- react.min.js
			- react.js
  - react-dom
  	- 16.8.6
  		- react-dom.min.js

4,CDN节点响应延时偏高

调整 CDN 部署策略,启用 s.lietou-static.com (H5资源独立域名,不兼容IE9及以下浏览器)

测量指标:

首包时间,可用性,总下载字节数,总下载时间,DNS时间(55.36%), 建立连接时间,内容下载时间,首屏时间

TTFB(waiting时间)提高明显

5,页面结构 (HTTP)

  • 1,在入口页添加 dns-prefetch meta 标签

对于会使用到的域名,添加 “dns-prefetch”,提前执行dns解析,只需要在入口页面添加

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="x-dns-prefetch-control" content="on"> // 开启dns-prefetch
  <link rel=dns-prefetch href="https://concat.lietou-static.com"> // 需要预解析的域名
  <link rel=dns-prefetch href="https://image0.lietou-static.com">
  <title>title</title>
  <%@ include file="/includes/v5/resources_header.html" %>
  • 2, 预先建立 connect 通道 :

使用 “preconnect” 建立 connect 通道,当请求对应资源时,会直接使用建立好的通道

<!-- resources_header.html -->
  <meta charset="UTF-8">
  <meta http-equiv="x-dns-prefetch-control" content="on">
  <link rel=dns-prefetch href="https://concat.lietou-static.com">
  <link rel=dns-prefetch href="https://image0.lietou-static.com">
  <link rel="preconnect" href="//s.lietou-static.com" />
  <link rel="preconnect" href="//image0.lietou-static.com" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=0">
  • 3, 使用 prefetch标签预加载下一页需要使用的资源

    判断用户访问的下一页面,如果是极大几率发生的结果,可以使用 “prefetch” 预加载下个页面的资源,浏览器普遍会在后台保持 5 分钟,切换页面也不会丢失加载

    <link rel="prefetch" href="/path/to/script.js">

6,多域名

资源多域名,提高并行资源加载速度(已启用):图片资源前端静态资源为 2 个域名

目前使用的前端资源域 concat.lietou-static.com 要兼容 IE7,8,9 用户的访问,部署方式导致目前节点偏少。

可以启用新域名(s.lietou-static.com),针对手机高版本浏览器,测试新的cdn部署方式,不需要兼容IE用户


静态资源和图片使用了不同域名:s.lietou-static.com ; concat.lietou-static.com image0.lietou-static.com (图片服务器)

🌲🌲🌲🌲🌲🌲🌲🌲🌲:https://learnku.com/docs/f2e-performance-rules/use-a-domain-name-without-cookies/6392

7,js优化

DOMContentLoaded 表明页面阻塞代码偏多

❇️维持页面基础交互外,js代码过多

  • loader.js(cdn回源)
  • femonitor.min.js(前端错误收集)
  • dlog.js(安全部门检测用户行为,页面 onLoad 之前就开始发送请求)
  • 利用document.write获取微信分享签名的代码(很多页面不需要,可以移动到api调用时,但单页面可能会有多次调用)
  • user.behavior.js(未知用途)
  • hm百度分析代码
  • gio分析代码(growingIO)
  • stat.js(页面 onLoad 之前就开始发送请求)
  • 百家号推送代码

❇️代码放置位置不规范

  • dlog.js放置到div中
  • head 标签内在外联 css 文件下面放置内联 js 代码
  • 多个请求没有合并一次调用 FeLoader

❇️common.js 代码量过大,导致解析时间平均占用接近600ms,大小 263KB

压缩 common.js 的代码,目前打包到 common.js 中的代码是多次引用的代码包及基础js,这减小了平均页面打开时间,对初次打开不友好

实践结果

本人参与的项目在 H5 页面只针对静态资源和数据请求进行了优化,完成后获得效果还是较为理想的,见下图。

绿色线是优化之后页面打开的平均白屏时间,蓝色是优化前的平均白屏时间,能看到优化成效还是相当可观的,如果能将 webview 的初始化时间也优化掉,基本上能达到页面秒开。


对于会使用到的域名,添加 “dns-prefetch”,提前执行dns解析,只需要在入口页面添加


以上是结合自己项目以及以往的经验总结的较为常规的针对移动端体验优化的思路,比较浅显,其实每一个优化思路虽然说起来简单,但是在实践中会因为各种因素(如投入产出比,前后端资源协调等)导致夭折,而且优化思路也需要分场景,需要因地制宜选用不同的方案。

参考链接:

1, https://www.nihaoshijie.com.cn/index.php/archives/795/

2, https://www.ershicimi.com/p/072bbd14a6def2c2faed2128b7f8b445 来自阿里大佬的关于移动端体验优化经验总结 🧚‍♀️🧚‍♀️🧚‍♀️🧚‍♀️🧚‍♀️🧚‍♀️

3, https://lavas-project.github.io/pwa-book/chapter05/4-precache.html 预缓存文档

4, https://zhuanlan.zhihu.com/p/77144845 知乎-预加载-预缓存

5,https://developer.aliyun.com/article/762969 数据监控平台

6, https://tech.youzan.com/dns-prefetching/ 域名预解析

7, https://cloud.tencent.com/developer/article/1046674 dns-prefetch 简介 🧚‍♀️

8, barretlee/performance-column#3 GitHub dns-prefetch 🧚‍♂️🧚‍♂️🧚‍♂️🧚‍♂️🧚‍♂️🧚‍♂️🧚‍♂️🧚‍♂️🧚‍♂️

9, https://learnku.com/docs/f2e-performance-rules/use-multiple-domain-names/6388 前端优化35个军规🐎🐎🐎🐎🐎🐎🐎🐎🐎🐎

setTimeout Promise

setTimeout和Promise的异步的区别,在浏览器和Node下的区别 ?

共同点: 都是异步任务; 都是通过任务队列进行管理/调度;

Event Loop

我们之前说过,javascript以单线程、同步的方式执行主线程,遇到异步会将异步函数放入到任务队列中,
当主线程执行完毕,会循环执行任务队列中的函数,也就是事件循环,直到任务队列为空。

JS主线程不断的循环往复的从任务队列中读取任务,执行任务,这种运行机制称为事件循环。

执行机制

1、JS是单线程语言,包括同步任务、异步任务(异步任务:又包括宏观任务和微观任务

宏任务和微任务

1.宏任务:包括整体代码script,setTimeout,setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境);

2.微任务:Promise.then(非new Promise)、MutaionObserver、process.nextTick(Node.js 环境)

注:new Promise中的代码会立即执行,then函数分发到微任务队列,process.nextTick分发到微任务队列Event Queue

**注意: setTimeOut并不是直接的把你的回掉函数放进异步队列中去,而是在定时器的时间到了之后,把回掉函数放到执行异步队列中去。**如果此时这个队列已经有很多任务了,那就排在他们的后面。这也就解释了为什么setTimeOut为什么不能精准的执行的问题了。

队列任务优先级:promise.Trick() > promise的回调 > setTimeout > setImmediate

宏、微任务区别:

  1. 在执行上下文栈的同步任务执行完后;
  2. 首先执行Microtask队列,按照队列先进先出的原则,一次执行完所有Microtask队列任务;
  3. 然后执行Macrotask/Task队列,一次执行一个,一个执行完后,检测 Microtask是否为空;
  4. 为空则执行下一个Macrotask/Task;
  5. 不为空则执行Microtask

promise、async/await

  1. 首先,new Promise是同步的任务,会被放到主进程中去立即执行。而**.then()函数是异步任务**会放到异步队列中去,那什么时候放到异步队列中去呢?当你的promise状态结束的时候,就会立即放进异步队列中去了。
  2. 带async关键字的函数会返回一个promise对象,如果里面没有await,执行起来等同于普通函数;
  3. await 关键字要在 async 关键字函数的内部,await 写在外面会报错;
  4. 就算await的不是promise对象,是一个同步函数,也会等这样操作。

示例代码1:

setTimeout(function() {
    console.log('宏任务setTimeout');  //先遇到setTimeout,将其回调函数注册后分发到宏任务Event Queue
  //如果setTimeout设置时间,那它会先把函数放到宏任务Event Table,等时间到了再放入宏任务Event Queue里面
})
new Promise(function(resolve) {
    console.log('微任务promise');  	//new Promise函数立即执行
    resolve();						//必须resolve执行才能执行then
}).then(function() {
    console.log('微任务then');  		  //then函数分发到微任务Event Queue
})
console.log('主线程console');
 
//执行顺序结果: 微任务promise、主线程console、微任务then、宏任务setTimeout

示例代码2

console.log('1主线程');	//整体script作为第一个宏任务进入主线程

setTimeout(function() {	//setTimeout,其回调函数被分发到宏任务Event Queue(执行规则:从上到下排序,先进先执行)中
    console.log('2宏任务');
    process.nextTick(function() {
        console.log('3宏任务里面的微任务');
    })
    new Promise(function(resolve) {
        console.log('4微任务');
        resolve();
    }).then(function() {
        console.log('5微任务')
    })
})

process.nextTick(function() {	// process.nextTick()其回调函数被分发到微任务Event Queue中
    console.log('6微任务');
})

new Promise(function(resolve) {		//Promise,new Promise直接执行,输出7
    console.log('7微任务');
    resolve();
}).then(function() {
    console.log('8微任务')			//then被分发到微任务Event Queue中,排在process.nextTick微任务后面。
})

setTimeout(function() {			//setTimeout,其回调函数被分发到宏任务Event Queue中,排在前面的setTimeout后面
    console.log('9宏任务');
    process.nextTick(function() {
        console.log('10宏任务里面的微任务');
    })
    new Promise(function(resolve) {
        console.log('11微任务');
        resolve();
    }).then(function() {
        console.log('12微任务')
    })
})

//执行结果: 1主线程、7微任务、6微任务、8微任务、2宏任务、4微任务、3宏任务里面的微任务、5微任务、
//          9宏任务、11微任务、10宏任务里面的微任务、12微任务

示例代码3: async

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
async1()
console.log('script end')

// 结果: async1 start –> async2 –> script end –> async1 end

async函数还是基于Promise的一些封装,而Promise是属于微任务的一种;

因此会把await async2()后面的所有代码放到Promise的then回调函数中去,相当于下面代码写法:

async function async1() {
    console.log('async1 start')
    new Promise(function(resolve){
        console.log('async2')
        resolve()
    }).then(function(){
        console.log('async1 end')
    })
}

示例代码4:

async function async1() {
	console.log('async1 start');
	await async2();
	console.log('asnyc1 end');
}

async function async2() {
	console.log('async2');
}

console.log('script start');

setTimeout(() => {
	console.log('setTimeOut');
}, 0);

async1();

new Promise(function (reslove) {
	console.log('promise1');
	reslove();
}).then(function () {
	console.log('promise2');
})

console.log('script end')

//  script start 、  async1 start  、async2、 promise1、 script end、  asnyc1 end、 promise2、 setTimeOut

示例代码5: for循环

setTimeout(function(){
  console.log("will be executed at the top of the next Event Loop")
}, 0);

var p1 = new Promise(function(resolve, reject){
    setTimeout(function(){ resolve(1) }, 0);
});

setTimeout(function(){
    console.log("will be executed at the bottom of the next Event Loop")
}, 0)

for (var i = 0; i < 100; i++) {
    (function(j){
        p1.then(function(value){
           console.log("promise then - " + j)
        });
    })(i)
}
/* 结果:
      will be executed at the top of the next Event Loop
      promise then - 0
      promise then - 1
      promise then - 2
      ...
      promise then - 99
      will be executed at the bottom of the next Event Loop
*/
  1. 首先同步执行完所有代码,其间注册了三个setTimeout异步任务,100个Promise异步任务;
  2. 没有可以执行的微任务;检查MacroTask队列,取第一个到期的MacroTask,执行输出will be executed at the top of the next Event Loop
  3. 然后检查MicroTask队列,发现没有到期的MicroTask,进入第4步;
  4. 再次检查MacroTask,执行第二个setTimeout处理函数:resolve Promise;
  5. 然后检查MicroTask队列,发现Promise已解决,其异步处理函数(then中回调)均可执行,依次执行,输出promise then - 0promise then - 99
  6. 最后再次检查MacroTask队列,执行输出will be executed at the bottom of the next Event Loop
  7. 交替往复检查两个异步任务队列,直至执行完毕;

示例代码6:

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}

async function async2() {
    console.log('async2')
}

console.log('script start')

setTimeout(function () {
    console.log('settimeout')
})

async1()

new Promise(function (resolve) {
    console.log('promise1')
    resolve()
}).then(function () {
    console.log('promise2')
})

console.log('script end')

// script start \ async1 start \ async2  \ promise1 \ script end \  async1 end \ promise2 \settimeout

链接:

https://www.kancloud.cn/freya001/interview/1235574 每天一个

https://segmentfault.com/a/1190000017961422
https://zhuanlan.zhihu.com/p/29235579

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.