总结二

358 阅读9分钟

一,vue和react的异同点

不同之处

1,数据流不同

react单向数据流,默认state数据不可变,需要setState更改数据
vue中v-model实现了双向数据绑定,state不是必须存在的
2,模板渲染方式不同
react使用jsx的方式,将js逻辑融入到页面中; vue使用html,css,和常规html模板相似; 3,虚拟dom不同
vue挂载到真实dom上,每一次更改数据时,会跟踪每一个组件的依赖关系,局部更新视图;
react虚拟dom,每一次数据发生变化时都调用render函数,更新整个dom结构,需要通过shouldComponentUpdate生命周期来控制;
diff算法不同 两者流程思维上是类似的,都是基于两个假设(算法复杂度为O(n)):

不同的组件产生不同的 DOM 结构。当type不相同时,对应DOM操作就是直接销毁老的DOM,创建新的DOM。 同一层次的一组子节点,可以通过唯一的 key 区分。

但两者源码实现上有区别: Vue基于snabbdom库,它有较好的速度以及模块机制。Vue Diff使用双向指针,边对比,边更新DOM。
React主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。

相似之处

虚拟dom提高运行效率 组件化思想
props父子组件传值 社区发展的不错

二,vue中祖孙组件传值

inject 和 provide 主要为高阶组件提供的,是2.2.0版本新增的,这一对选项需要一起使用,以允许祖先组件向其后子孙组件注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
provide选项应该是一个对象或者返回一个对象的函数
inject选项应该是一个字符串数组或一个对象


//父组件示例
<template>
  <div>
    <childCom></childCom>
  </div>
</template>
<script>
  import childCom from '../components/childCom'
  export default {
    name: "Parent",
    provide: {      //重要一步,在父组件中注入一个变量
      msg: "demo"
    },
    components:{
      childCom
    }
}
//子组件示例
//在子组件中不管嵌套多少层,都能够得到这个变量
<template>
  <div>
      {{msg}}
  </div>
</template>
<script>
  export default {
    name: "childCom",
    inject: ['msg'],//子孙组件中使用inject接住变量即可
  }
</script>
//但没有办法实现响应式更新
//需要如下操作  
//当传入一个可监听的动态对象时,可将传入的数据变成响应式的  
parent:
provide(){
  return:{
  	passprops:()=>{}//传给子组件含有动态属性的函数;
  }
}
child:
inject:['passprops']//接受来自于父组件的函数;
computed:{
	reactiveprops(){
    	this.passprops();//调用传来的函数,监听属性是否变动
    }//页面展示reactiveprops时就是获取到的响应式数据了
}

三,Z-index

在不考虑css3的情况下,z-index只有在已定位的元素上生效(position:reactive/absoulate/fixed/sticky); z-index默认情况下是auto,也可以赋值为正或为负;
z-index:0,z-indec:auto区别就是z-index:0时创建一个新的层叠上下文
如何比较两个DOM元素的显示顺序
1,如果是在相同的层叠上下文,按照层叠水平的规则来显示元素
2,如果是在不同的层叠上下文中,先找到共同的祖先层叠上下文,然后比较共同层叠上下文下这个两个元素所在的局部层叠上下文的层叠水平。
创建层叠上下文的方式

一、默认创建层叠上下文

默认创建层叠上下文,只有 HTML 根元素,这里你可以理解为 body 标签。它属于根层叠上下文元素,不需要任何 CSS 属性来触发。

二、需要配合 z-index 触发创建层叠上下文

依赖 z-index 值创建层叠上下文的情况:

position 值为 relative/absolute/fixed(部分浏览器)
flex 项(父元素 display 为 flex|inline-flex),注意是子元素,不是父元素创建层叠上下文
这两种情况下,需要设置具体的 z-index 值,不能设置 z-index 为 auto,这也就是 z-index: auto 和 z-index: 0 的一点细微差别。

前面我们提到,设置 position: relative 的时候 z-index 的值为 auto 会生效,但是这时候并没有创建层叠上下文,当设置 z-index 不为 auto,哪怕设置 z-index: 0 也会触发元素创建层叠上下文。

三、不需要配合 z-index 触发创建层叠上下文

这种情况下,基本上都是由 CSS3 中新增的属性来触发的,常见的有:

元素的透明度 opacity 小于1
元素的 mix-blend-mode 值不是 normal
元素的以下属性的值不是 none:
transform
filter
perspective
clip-path
mask / mask-image / mask-border
元素的 isolution 属性值为 isolate
元素的 -webkit-overflow-scrolling 属性为 touch
元素的 will-change 属性具备会创建层叠上下文的值

四,ajax请求中断

当发送ajax请求一段时间后还没有得到响应,可以设置ajax请求中断

xhr.open("GET","https://www.geekjc.com/fetch/post",true);
xhr.ontimeout=function(){
    console.log("请求超时")
}
xhr.timeout = 1000;
xhr.send();

//-------------------------------
setTimeout("CheckRequest()","8000");
function CheckRequest(){
   //为4时代表请求完成了    
    if(xhr.readyState!=4){
        alert('响应超时');
        //关闭请求
        xhr.abort();
    }
}

五,flat和flatmap的区别

flat默认是什么都不传,如果传入一个数字,则意味着数组最大扁平化的深度例子:[1,2,3,[2,[3]],4] arr.flat(2)=>[1,2,3,2,[3],4 flatMap默认是什么都不传默认将数组扁平化到深度为1的程度

六,JS中的定时器准确吗?

由于js是单线程的,需要先执行主线程上的同步任务,然后读取任务队列,执行对应的异步任务,所以在任务队列中存在大量任务的情况下,就会造成定时器回调任务的延迟
解决方法:
一, 动态计算时间差
二, 将定时器作为一个单独的线程执行并引入

七,common.js,AMD和UMD的区别

1,common.js

一个文件是一个单独的模块,每个模块都有单独的作用域,作为私有变量,不能被外部引用,exports暴露出一个对象,利用require将某个模块引入,模块可以多次加载,但只在第一次加载时生效,如需重新加载,需要将缓存清除,同步加载模块。只有上一个模块加载完后才会加载后续模块。

2,AMD

requireJS定义了一个函数 define,它是全局变量,  
用来定义模块  
define(id?, dependencies?, factory);
id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
dependencies:是一个当前模块依赖的模块名称数组
factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值 

在页面上使用require函数加载模块  
require异步加载模块,解决了依赖性的问题  
require([dependencies], function(){}); 
require()函数接受两个参数
=第一个参数是一个数组,表示所依赖的模块
第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块

3,UMD

同时兼容了common.js和AMD
CMD推崇就近依赖,只有在用到某个模块的时候再去require

(function (root, factory) {  
    if (typeof exports === 'object') {
        // Commonjs  
        module.exports = factory();  
          
    } else if (typeof define === 'function' && define.amd) {  
        // AMD
        define(factory);  
          
    } else {  
        //  没有使用模块加载器的方式
        window.eventUtil = factory();  
    }  
})(this, function() {  
    // module  
    return {  
        addEvent: function(el, type, handle) {  
            //...  
        },  
        removeEvent: function(el, type, handle) {  
              
        },  
    };  
});  

八,keep-alive

http持久连接,开启http keep-alive后,,能复用已有的TCP链接,当前一个hhtp请求已经响应完毕,服务器端没有立即关闭TCP链接,而是等待一段时间接收浏览器端可能发送过来的第二个请求,通常浏览器在第一个请求返回之后会立即发送第二个请求,如果某一时刻只能有一个链接,同一个TCP链接处理的请求越多,开启KeepAlive能节省的TCP建立和关闭的消耗就越多
缺点 当请求结束后,客户端接受完全部信息后,不会立即关闭tcp连接,此时服务器一直占用资源,直到到达一定的超时时间,服务器端发送FIN报文请求关闭连接。

九,instanceof和原型链

A instanceof B 旨在判断B的原型对象(B.prototype)是否在A的原型链上;

//创建一个构造函数
function Person(){
}
//创建构造函数的一个实例
let p1=new Person()  
Person.prototype=>Person的原型对象
p1.__proto__=>Person的原型对象  
所以p1 instanceof Persontrue

同时构造函相当于new Object  
所以Person.__proto__=Object.prototype  
所以所以p1 instanceof Objecttrue

instanceof的实现

function instanceOf(left,right){
	if(left.__proto__===null){
    	return false;
    }else if(left.__proto__==right.prototype){
    	return true;
    }
    left=left.__proto__;
}

十,vue组件传值的方法总结

1,vuex
2, props
3, $emit在子组件中直接调用父组件的方法
4,injec,provide祖先组件传值
5,sessionStorage等存储

十一,函数式编程

常见的编程范式:函数式编程,命令式编程等

函数式编程的特点:    
	1,函数可以像其他数据类型一样作为参数使用或赋值
    2,函数是一个表达式,总有返回值
    3,函数是一个保持独立,不修改外部变量,不依赖于外部变量

十二,DNS与CDN

递归查询通常主机向本地DNS服务器发送域名请求时使用的是递归查询
迭代查询本地DNS向各级DNS服务器发送域名解析时使用的是迭代查询

DNS的解析过程
1,首先浏览器输入一个URL,先查看本地DNS缓存中是否有对应域名解析,没有则进行下一步 2,查询服务器DNS缓存中是否有对应的域名解析,查询计算机host文件配置中是否含有该域名对应服务器的IP地址,没有则进行下一步
3,查询本地域名服务器(阿里云,京东云等)
4,如果上述都没命中,则本地DNS向根域名服务器发送递归查询(如上图操作流程所示);
CDN(内容分发网络)
CNAME
一个域名可以有多个CName记录,Cname记录指向一个A记录,A记录指向服务器IP地址
GSLB
在DNS解析域名时新增了一个全局负载均衡系统(GSLB),GSLB的主要功能是根据用户的本地DNS的IP地址判断用户的位置,筛选出距离用户较近的本地负载均衡系统(SLB),并将该SLB的IP地址作为结果返回给本地DNS。SLB主要负责判断缓存服务器集群中是否包含用户请求的资源数据,如果缓存服务器中存在请求的资源,则根据缓存服务器集群中节点的健康程度、负载量、连接数等因素筛选出最优的缓存节点,并将HTTP请求重定向到最优的缓存节点上。
1,首先浏览器输入一个URL,先查看本地DNS缓存中是否有对应域名解析,没有则进行下一步
2,查询服务器DNS缓存中是否有对应的域名解析,查询计算机host文件配置中是否含有该域名对应服务器的IP地址,没有则进行下一步
3,本地DNS层层递归查找到一条NAME字段为"join.qq.com"的CNAME记录(由服务提供者配置),该记录的Value字段为"join.qq.cdn.com";并且还找到另一条NAME字段为"join.qq.cdn.com"的A记录,该记录的Value字段为GSLB的IP地址;本地DNS向GSLB发送DNS查询报文;
4,GSLB根据本地DNS的IP地址综合考量最优的SLB的IP地址填入DNS回应报文,作为DNS查询的最终结果; 本地DNS回复客户端的DNS请求,将上一步的IP地址作为最终结果回复给客户端;
5,客户端根据IP地址向SLB发送HTTP请求,SLB综合考虑缓存服务器集群中各个节点的资源限制条件、健康度、负载情况等因素,筛选出最优的缓存节点后回应客户端的HTTP请求(状态码为302,重定向地址为最优缓存节点的IP地址); 客户端接收到SLB的HTTP回复后,重定向到该缓存节点上; 缓存节点判断请求的资源是否存在、过期,将缓存的资源直接回复给客户端,否则到源站进行数据更新再回复。

十三,vue中Object.defineProperty和proxy

Object.defineProperty能够劫持对象的属性,但是需要对对象的每一个属性进行遍历劫持;如果对象上有新增的属性,则需要对新增的属性再次进行劫持;如果属性是对象,还需要深度遍历。
Object.defineProperty(obj,prop,descriptor){
}可以为对象设置属性,修改属性,或修改对象的属性描述符  
obj指的是对象,prop,是属性,descriptor是属性描述符;  
属性描述符主要有两种形式:数据描述符和存取描述符(两者不兼容,但都可以和configrable、enumerable搭配使用)
数据描述符特有的两个属性:value和writable;
存取描述符特有的两个属性:get和set  

Object.definedProperty缺点:  

无法检测到对象属性的添加或删除
无法检测数组元素的变化,需要进行数组方法的重写
无法检测数组的长度的修改

proxy:
var proxy = new Proxy(target, handler); //target就是我们需要代理的目标对象,可以是对象或者数组;handler和Object.defineProperty中的descriptor描述符有些类似,也是一个对象,用来定制代理规则。


十四,JS中数据类型的转化

segmentfault.com/a/119000001…
!的优先级高于==/===

常见的考点:  
1,[]==[]//false,由于x和y是引用数据类型,两者在堆中的地址并不相同,所以为false
2,[]==![]//true
3,{}==!{}//false,Number({})=NAN
4,{}==![]//false
5,![]=={}//false
6,[]==!{}//true  与2相同
7,undefined==null//true
console.log(true + false);//1
console.log(12 | "5");//3
console.log("number" + 12 + 3);//number123
console.log(12 + 3 + "number");//15number
console.log([1] > null);//true
console.log('a' + +'b');//aNaN
// console.log(string == "string");//报错
console.log(null == "");//false
console.log(["x"] == "x");//true
console.log([] + null + 1);//null2
console.log({} + []);//[object object]

十五,vue中this.options.data()this.options.data()或this.options.created()

this.options可以获取到vue中任和定义的函数,this.options可以获取到vue中任和定义的函数,this.options.data()可以用来初始化页面中的data()中定义的数据;

十六,es6中的map和Object的区别

1,object对象有原型,除非创建一个null
2,object对象的键值只能是string或者symbol类型,map的键值可以是任何类型,甚至是function,set等
3,通过map的size属性,可以得到map对象的长度,map对象还有很多独有的方法

Map.prototype.set(key,value)设置键名key对应键值为value,返回整个Map实例;可以采用链式写法(连缀语法);
 Map.prototype.get(key):读取key对应的键值,找不到返回undefinedMap.prototype.has(key):返回true/false(key是否是Map实例的键);
 Map.prototype.delete(key):删除键key,返回true/fals;
 Map.prototype.clear():清除全部成员,没有返回值  
 
 set实例的2个属性:
  Set.prototype.constructor:构造函数,默认是Set();
  Set.prototype.size:返回Set实例的成员总数;

  Set实例的4个操作方法:
  Set.prototype.add(value):添加某个值,返回Set结构
  Set.prototype.delete(value):删除某个值,返回true/false(是否成功)
  Set.prototype.has(value):返回true/false(value是否是Set实例的成员)
  Set.prototype.clear():清空所有成员,没有返回值
  set还可以做集合的数学运算

十七,vue3.0中teleport

Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术

  <button @click="showToast" class="btn">打开 toast</button>
  <!-- to 属性就是目标位置,将被插入id为该属性值的位置 -->
  <teleport to="#teleport-target">
    <div v-if="visible" class="toast-wrap">
      <div class="toast-msg">我是一个 Toast 文案</div>
    </div>
  </teleport>

十八,前端性能优化

1,dns预解析
预加载和预解析 preload: Prefetch: Prefetch包括资源预加载、DNS预解析、http预连接和页面预渲染

原理:在 HTTP 建立之前,将 DNS 查询的结果缓存到系统/浏览器中,提升网页的加载效率
使用方法:
手动 解析:
<link rel="dns-prefetch" href="//seller.jd.id" />    
自动解析a标签href中的
由于浏览器的单域名最大并发限制(通常为6-10个),所以可以多添加几个域名进行dns预解析;   
由于chrome使用了八个异步线程用来处理dns预解析,所以dns预解析不是越多越好;  
使用场景:新用户第一次登陆某站点时能够明显加快访问速度;登陆页加入下一跳页面所用资源的预解析;

2,ios底部默认栏设置

padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom);

3,vue打包后dist目录

css
html
js=>
app.js是入口js
vendor则是通过提取公共模块插件来提取的代码块(webpack本身带的模块化代码部分),包含node-modules等,体积比较大,经常变动不利于缓存;
manifest则是在vendor的基础上,再抽取出要经常变动的部分,比如关于异步加载js模块部分的内容。
将公共静态组件抽离出来,利用externals,使其在生产环境下不打包进入vendor.js中,而是以直接引入的方式加载到页面中  

浏览器加载资源的默认优先级  
1,html、css、font这三种类型的资源优先级最高;
2,然后是preload资源(通过<link rel=“preload">标签预加载)、script、xhr请求;
3,接着是图片、语音、视频;
4,最低的是prefetch预读取的资源。

4,按需加载路由
得益于vue-cli3的封装,如下代码实现了按需加载路由

{
    path: '/about',
    name: 'about',
    component: () => import('@/views/about'),
    meta: { title: '关于' }
  },  

5,抽离公共组件,将需要频繁引入的组件在main.js中引入,注册作为全局组件;
6,使用vue-lazyload,防抖
7,打包时利用UglifyJsPlugin插件除去console.log等部分;
8,开启gzip压缩js,css,json,html等文件,查看responseHeader中的content-encoding:gzip即为生效;
9,利用localStorage对关键路径中的资源进行缓存,节约了发送http请求的时间,关键是LS缓存更新机制,判断是发送xhr请求更新还是直接调取缓存;

十九,面试学习

1,promise的第二个参数reject和catch的区别和应用场景(promise处理异常的方式)
reject和throw是用来抛出异常,catch是用来处理异常
reject和throw类似,thorw不能够放在异步函数中,reject可以放在异步函数中

catch相当于:
Promise.prototype.catch = function(fn){
    return this.then(null,fn);
}  
catch操作的对象实际是new Promise.then()返回的promise函数而不是new Promise();   
then的第二个参数和catch捕获错误信息的时候会就近原则  
const p = new Promise((resolve, rejected) => {
      rejected('1');
      resolve();
  }).then(res => {
      console.log(2);
      rejected();
  }, err => {
      console.log(4);
  }).catch(err => {
      console.log(3);
  })
  推荐使用catch来进行异常捕获,因为catch不仅可以捕获new Promise中的异常,还能够捕获then中的异常;

2,http,https,http1.0,http1.1,http2.0的区别
http1.0和http1.1

1,缓存处理,http1.0通过If-Modified-Since,Expires来做为缓存判断的标准http1.1增加了e-tag,If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。  
2,带宽优化和网络连接:http1.0存在浪费带宽的现象,如只需要对象的一部分,却返回一整个对象,http1.1中在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。  
3,错误通知:http1.1新增了了24个错误状态响应码;  
4,host头处理:HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。  
5,长连接:在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。  

http和https
加密: 对称加密和非对称加密
对称加密对称加密是指通信双方都持有同一个密钥;用该密钥进行加密和解密;
非对称加密非对称加密中有两把密钥,一把公钥一把私钥,用公钥加密后必须用对应的私钥解密;私钥加密后也必须用对应公钥解密。
https是用对称加密和非对称加密相结合 过程

  • 某网站服务器拥有公钥A,私钥A';
  • 浏览器向服务器发送请求,服务器收到请求后利用明文传输将公钥A发送给浏览器;
  • 浏览器随机产生一个用于对称加密的密钥X,利用公钥A加密后传输给服务器;
  • 服务器收到拿到后用私钥A'解密,得到密钥X;
  • 浏览器和服务器双方将共同使用密钥X进行加密;

中间人搞鬼

  • 在服务器将公钥A传输给浏览器的过程中,将公钥A劫持下来;
  • 中间人自己生成一个公钥B;明文传输给浏览器;
  • 浏览器收到公钥B后,随机生成一个密钥X,利用公钥B加密,然后发送给服务器;
  • 中间人劫持到密钥X,然后用公钥A加密,发送给服务器;
  • 在浏览器和服务器都不知道的情况下;每一次加密中间人都能用密钥X对内容进行解密。

ca认证安全证书

  • 网站在使用https前,需要先向“CA机构”申请颁发一份数字证书,数字证书里有证书持有者、证书持有者的公钥等信息,服务器把证书传输给浏览器,浏览器从证书里取公钥就行了,证书就如身份证一样,可以证明“该公钥对应该网站”。 如何防止数字证书传输过程中被劫持
    数字签名
  • 制作过程ca拥有非对称的公钥和私钥,用哈希函数对证书中的信息进行解析;将解析后的信息利用ca公钥进行加密生成数字签名;
  • 浏览器验证过程用ca中的私钥对数字签名进行解析,利用哈希函数对证书信息进行解析,若两者一致,则签名成功,反之,则签名失败。
    http1.1和HTTP2.0
1、 HTTP2.0采用二进制格式而非文本格式:二进制协议解析起来更加高效,错误更少;
2、 HTTP2.0是完全多路复用的,代替原来的序列和阻塞机制——只需要一个连接即可实现并行:多路复用能同时处理多个消息的请求和响应,甚至可以在传输过程中将一个消息和另外一个消息掺杂在一起,所以客户端只需要一个连接就能加载整个页面;
3、 压缩头部信息减小开销:
4、 HTTP2.0允许服务器主动推送应答到客户端的缓存中:当浏览器请求一个页面时,服务器会首先返回HTML,浏览器解析HTML和发送所有内嵌资源的请求,服务器才开始发送JS、CSS、图片等,而服务器“推送”服务通过“推送”那些它认为客户端可能需要的内容到客户端的缓存中,从此来避免往返的延迟。

3,js获取接口请求时间
方法一:获取触发方法的时间戳和返回响应数据的时间戳,两者之差就是接口请求时间
方法二:window.performance
4,json.parse(),json.stringfy的局限性
不能转化时间戳,正则,function,contructor(),Error(),undefined等
5,EventBus
EventBus 又称为事件总线。在Vue中可以使用 EventBus 来作为组件传递数据的桥梁的,就像是所有 组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,

初始化的两种方式:  
方式一:在main.js中vue.prototype.$EventBus={}
方式二:创建bus.js并将其导出  
import vue from 'vue;
export const EventBus = new Vue(); 

使用方式: 
发送和接受消息EventBus.$on,EventBus.$emit相当于把消息放到EventBus上,任意组件之间都可以通过其进行通信;    

移除方式:EventBus.$off()移除事件监听器

6,css中的百分比

宽,高的百分比  
函数宽和高的百分比,参照其包含块的宽度和高度;  
当包含块没有自定义高度,且该元素不是绝对定位的情况下,该元素设置的高度相当于auto;html标签定义的百分比总是有效的;

margin,padding的百分比  
对于marginpadding,其任意方向的百分比都是参照于其包含块的;

border-radius,line-height的百分比  
border-radius,line-height的百分比,参照于该元素自身

background-position的百分比  
bsckground-position的百分比实际上做了一个减法器,利用的是背景的宽高-图片的宽高,来定义图片初始位置;  

font_size的百分比  
font-size的百分比参照的是它的直接父元素的font-size  

vertical-align的百分比  
vertical-align的百分比,参照于自身的line-height  

position定位的bottom,top,left,right的百分比  
position定位的bottom,top,left,right的百分比,参照于其包含块的宽高  

百分比值用于继承的情况下,只有该百分比于参照物计算后的值会被继承,而不是继承百分比值

7,如何提高webpack的构建速度

1happyPack()  
进程:进程是系统进行资源分配和调度的基本单位;  
线程:线程是cpu调度和分配的基本单位;  
由于运行在 Node.js 之上的 Webpack 是单线程模型的,所以 Webpack 需要处理的任务要一个一个进行操作;因此可以引入happyPack来提高webpack的构建速度;  
配置:
安装: npm install happypack --save-dev  
webpack.config.js  
引入:const HappyPack = require('happypack');  
module.exports = {
    ...
    module: {
        rules: [
            test: /\.js$/,
            // use: ['babel-loader?cacheDirectory'] 之前是使用这种方式直接使用 loader
            // 现在用下面的方式替换成 happypack/loader,并使用 id 指定创建的 HappyPack 插件
            use: ['happypack/loader?id=babel'],
            // 排除 node_modules 目录下的文件
            exclude: /node_modules/
        ]
    }
     plugins: [
        ...,
        new HappyPack({
            /*
             * 必须配置
             */
            // id 标识符,要和 rules 中指定的 id 对应起来
            id: 'babel',
            // 需要使用的 loader,用法和 rules 中 Loader 配置一样
            // 可以直接是字符串,也可以是对象形式
            loaders: ['babel-loader?cacheDirectory']
            //使用多个loader时,这里传入多个loader;
        })
    ]
}
2,代码压缩  
webpack-paralle-uglify-plugin  
3,图片压缩  
 image-webpack-loader  
4,提取页面公共资源
使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中  
5DLL分包,避免反复编译浪费时间
使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。  
6,充分利用缓存提升二次构建速度
babel-loader 开启缓存
terser-webpack-plugin 开启缓存
使用 cache-loader 或者 hard-source-webpack-plugin