总结

197 阅读21分钟

原型与原型链

  • 所有函数都有一个特别的属性:
    • prototype : 显式原型属性
  • 所有实例对象都有一个特别的属性:
    • __proto__ : 隐式原型属性
  • 显式原型与隐式原型的关系
    • 函数的prototype: 定义函数时被自动赋值, 值默认为{}, 即用为原型对象
    • 实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
    • 原型对象即为当前实例对象的父对象
  • 原型链
    • 所有的实例对象都有__proto__属性, 它指向的就是原型对象
    • 这样通过__proto__属性就形成了一个链的结构---->原型链
    • 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
    • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作

执行上下文与执行上下文栈

  • 变量提升与函数提升
    • 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
    • 函数提升: 在函数定义语句之前, 就执行该函数
    • 先有变量提升, 再有函数提升
  • 理解
    • 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
    • 执行上下文栈: 用来管理产生的多个执行上下文
  • 分类:
    • 全局: window
    • 函数: 对程序员来说是透明的
  • 生命周期
    • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
    • 函数 : 调用函数时产生, 函数执行完时死亡
  • 包含哪些属性:
    • 全局 :
      • 用var定义的全局变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===>window
    • 函数
      • 用var定义的局部变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===> 调用函数的对象, 如果没有指定就是window
      • 形参变量 ===>对应实参值
      • arguments ===>实参列表的伪数组
  • 执行上下文创建和初始化的过程
    • 全局:
      • 在全局代码执行前最先创建一个全局执行上下文(window)
      • 收集一些全局变量, 并初始化
      • 将这些变量设置为window的属性
    • 函数:
      • 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
      • 收集一些局部变量, 并初始化
      • 将这些变量设置为执行上下文的属性

作用域与作用域链

  • 理解:
    • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
    • 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量
  • 分类:
    • 全局
    • 函数
    • js没有块作用域(在ES6之前)
  • 作用
    • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
    • 作用域链: 查找变量
  • 区别作用域与执行上下文
    • 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
    • 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
    • 联系: 执行上下文环境是在对应的作用域中的

闭包

  • 理解:
    • 当嵌套的内部函数引用了外部函数的变量时就产生了闭包
    • 通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性
  • 作用:
    • 延长局部变量的生命周期
    • 让函数外部能操作内部的局部变量
  • 写一个闭包程序
    function fn1() {
      var a = 2;
      function fn2() {
        a++;
        console.log(a);
      }
      return fn2;
    }
    var f = fn1();
    f();
    f();
    
  • 闭包应用:
    • 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
    • 循环遍历加监听
    • JS框架(jQuery)大量使用了闭包
  • 缺点:
    • 变量占用内存的时间可能会过长
    • 可能导致内存泄露
    • 解决:
      • 及时释放 : f = null; //让内部函数对象成为垃圾对象

内存溢出与内存泄露

  1. 内存溢出
  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  1. 内存泄露
  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包
    • 进程:
  • 程序的一次执行, 它占有一片独有的内存空间
  • 可以通过windows任务管理器查看进程
  • 线程:
    • 是进程内的一个独立执行单元
    • 是程序执行的一个完整流程
    • 是CPU的最小的调度单元
  • 关系
    • 一个进程至少有一个线程(主)
    • 程序是在某个进程中的某个线程执行的

浏览器内核模块组成

  • 主线程
    • js引擎模块 : 负责js程序的编译与运行
    • html,css文档解析模块 : 负责页面文本的解析
    • DOM/CSS模块 : 负责dom/css在内存中的相关处理
    • 布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)
  • 分线程
    • 定时器模块 : 负责定时器的管理
    • DOM事件模块 : 负责事件的管理
    • 网络请求模块 : 负责Ajax请求
  • 循环流程
    • 在一个事件循环中,异步事件返回结果后会被放到一个任务队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈...如此反复,进入循环。

js线程

  • js是单线程执行的(回调函数也是在主线程)
  • H5提出了实现多线程的方案: Web Workers
  • 只能是主线程更新界面

定时器问题:

  • 定时器并不真正完全定时
  • 如果在主线程执行了一个长时间的操作, 可能导致延时才处理

事件处理机制(图)

  • 代码分类
    • 初始化执行代码: 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
    • 回调执行代码: 处理回调逻辑
  • js引擎执行代码的基本流程:
    • 初始化代码===>回调代码
  • 模型的2个重要组成部分:
    • 事件管理模块
    • 回调队列
  • 模型的运转流程
    • 执行初始化代码, 将事件回调函数交给对应模块管理
    • 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
    • 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行

H5 Web Workers

  • 可以让js在分线程执行

  • Worker

    var worker = new Worker('worker.js');
    worker.onMessage = function(event){event.data} : 用来接收另一个线程发送过来的数据的回调
    worker.postMessage(data1) : 向另一个线程发送数据
    
  • 问题:

    • worker内代码不能操作DOM更新UI
    • 不是每个浏览器都支持这个新特性
    • 不能跨域加载JS
  • svn版本控制

  • svn server

HTML, CSS

  • html的语义化:
    • 用正确的标签做正确的事情,根据内容的结构化,选择合适的标签
    • 便于开发者阅读和写出优雅的代码,便于浏览器的爬虫更好的解析
    • 原因:用户体验;有利于seo,便于团队开发
  • postion的理解:
    • relative:相对于自身原来的位置定位,不脱离文档流
    • absolute: a)父元素(relative) - 相对于父元素定位,脱离文档流 b)父元素:无 - 相对于body定位,脱离文档流
    • static: 默认
    • fixed: 固定定位,相对于浏览器定位,脱离文档流
    • inherit:继承父级的positon属性
    • initial: 默认值
  • 盒模型:
    • 标准盒模型:content-box:width = div的宽度
    • 怪异盒模型: boxder-box: width = content + padding + border
    • 获取元素的宽度: dom.offsetWidth = dom.getBoundingClientRect().width;
  • 行内元素和块级元素:
    • 行内元素:不能设置宽,高,与其他行内元素位于同一行,一般不在里面嵌套块级元素
      • 例如:span, input, label, select, img, textarea, em, strong, i
    • 块级元素: 排斥其他元素同行,可设置狂傲,可容纳块,行内元素
      • 例如: h1--h6, p, div, dl, dt, dv, ol, ul, li, table, td, th
    • 区别:
      • 块级元素独占一行,行内元素多个可排成一排;
      • 块级元素可设置狂傲,行内元素不能设置宽高;
      • 块级元素可以包含块级,行内, 行内元素一般包含行内的和文本
      • 块级元素可以设置padding,margin, 行内元素只能设置padding和margin的left/right
  • BFC(块级格式化上下文)
    • 概念:页面的一个独立的渲染区域,容器里面的自元素不会在布局上影响外面的元素,决定块级盒子的浮动,布局的因素
    • 触发条件:
      • float浮动
      • 绝对定位(absolute, fixed)
      • 行内块元素(inline-block, table-cell)
      • overflo的值不为visible的块元素
      • 弹性元素,display:flex/inline-flex
    • BFC作用
      • 解决上下两个相邻元素的重叠问题(垂直margin合并的问题)
        • 正数时:两者之前取最大
        • 负数时:绝对值取较大的
        • 一正一负: 两者的和
      • 清除内部浮动问题
    • BFC的原理
      • BFC不会与float的元素重叠,BFC下的编剧也不会重叠,BFC是一个独立的容器和外面的容器互不干扰,计算BFC的高度,浮动的子元素也会参与
  • CSS的动画效果animation
    • @keyframes 'loop' {0% {}; 50% {}; 100% {}}
  • 伪类的元素: nth-child(even) / nth-child(old)
  • css的选择器的优先级:
    • 如果多重样式: 外部样式 < 内部样式 < 内联样式
    • 注意: 外部样式放在内部样式的后面,则会覆盖;
    • 选择器的优先级:
      • 内联样式 > id选择器 > 类选择器 > 元素选择器
    • 总结
      • 越具体优先级越高
      • 同样优先级写在后面的覆盖前面的
      • !important的优先级最高,但是少用
  • 清除浮动的方法:
    • overflow: hidden
    • clearfix:after { content: '', display: block, clear: both } }
    • 父元素给个固定高度

    闭包

    • 定义:
      • 函数和函数内部能访问的变量的总和,就是闭包;
      • 当嵌套的内部函数引入用了外部函数的变量
      • 本质:内部函数中的一个对象,这个对象中包含了引用的变量属性
      • 从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。
    • 作用
      • 让函数外部能操作内部的局部变量
      • 延长局部变量的生命周期
    • 闭包应用:
      • 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
      • 循环遍历加监听
      • JS框架(jQuery)大量使用了闭包
    • 缺点:
      • 变量占用内存的时间可能会过长
      • 可能导致内存泄露
      • 解决:
        • 及时释放 : f = null; //让内部函数对象成为垃圾对象

内存溢出与内存泄露

  1. 内存溢出
  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  1. 内存泄露
  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包

立即执行函数

  • 定义:声明一个函数,并立即调用这个函数
  • 作用:创建一个独立的作用域,这个作用域内的变量,外面访问不到,避免全剧污染

promise的理解

  • Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,有then、catch、finally等方法
  • 特点:
    • 对象的状态不受外界影响
    • 一旦状态改变,就不会再变,任何时候都可以得到这个结果
  • promise的其他api
    • promise.all
    • promise.race

async/await

  • 捕获异常: try ... catch
  • ().then 后面可以捕获异常

浅拷贝和深拷贝

  • 浅拷贝
    • 浅拷贝是拷贝一层,深层次的对象级别的就拷贝引用;
      • 浅拷贝的时候如果数据是基本数据类型,直接赋值,会拷贝其本身;
      • 如果除了基本数据类型之外还有一层对象,那么对于浅拷贝而言就只能拷贝其引用,对象的改变会反应到拷贝对象上;
    • 浅拷贝的方法:
      • Object.assign(target, ...sources)
        • target:目标对象, sources:任意多个源对象,返回值:目标对象会被返回
  • 深拷贝
    • 深拷贝是拷贝多层,每一级别的数据都会拷贝出来
      • 深拷贝就会拷贝多层,即使是嵌套了对象,也会都拷贝出来。
    • 方法
      • 对象只有一层的话: Object.assign()函数
      • JSON.parse(JSON.stringify(obj))
        • 缺点:会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
      • 递归拷贝
      • 使用Object.create()方法
        • 直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

js的垃圾回收机制

  1. js的内存管理是自动的,无形的,创建的原始值,对象,函数,,,都是会占用内存。
  2. js的内存管理的可达性:是以某种方式可访问的值,一定存在内存中;
  3. 可达性分类: * 固有的可达值的基本集合,明显不会被释放
    • 函数的局部变量、函数根(roots)
    • 嵌套调用时,调用连上所有的函数的变量与参数根(roots)
    • 全局变量
    • 内部的根(roots) * 一个值可以通过引用和引用连从根访问任何值,该值也是可达的
    • 全局变量中有个属性,该对象的一个属性引用了另一个对象,该对象也是可达的
      • 例子1 let user = { name: 'John'}; John = null;
        • 不可达,没有引用,不能访问,垃圾回收器会认为是垃圾数据并进行回收,释放内存
      • 例子2 let user = { name: 'John'}; let admin = user; user = null;
        • 对象可以通过admin访问,对象还存在内存中,若admin= null,对象就会被删除
  4. 垃圾会是的内部算法 ---》 定期执行垃圾回收 * 垃圾回收器找到所有的根,并标记 * 然后标记来自他们的所有引用 * 便利标记的对象,并标记他们的引用,所有遍历到的对象都会被记住,以免将来遍历到同一个对象 * 。。。直到所有可达的从根部的引用都被访问到 * 没有被标记的对象都会被删除 例子: 一个油桶,流经所有引用并标记所有可达的对象,移除没有标记的
  5. js的优化: * 分代收集: 对象分成新的,旧的。有些对象出现完成他们的工作并很快死去,他们可以被很快清理,长期存活的对象会变得老旧,被查的频率减少 * 增量收集: 一次性太耗时,垃圾收集分成几部分去完成,逐一进行处理,需要他们之间有额外的标记 * 闲时收集: 在cpu闲时尝试运行,减少对代码的执行影响
  6. 总结: * 1. 垃圾回收时自动完成的,不能强制执行或阻止执行 * 2. 当对象时可达状态时,一定存在内存中 * 3. 被引用与可访问不同,一组相互链接的对象可能整体都时不可达的 ## MVVM理解
  • Model:代表数据模型,数据和业务逻辑都在Model层中定义;
  • View:代表UI视图,负责数据的展示;
  • ViewModel:就是与界面(view)对应的Model。因为,数据库结构往往是不能直接跟界面控件一一对应上的,所以,需要再定义一个数据对象专门对应view上的控件。而ViewModel的职责就是把model对象封装成可以显示和接受输入的界面数据对象。

vue的理解

  • vue中的computed,watch,methods的区别
    • computed:
      • 缓存数据,如果属性依赖没有变化,不会重新计算
      • 一个数据手多个数据的影响
    • watch:
      • 做别的事情,上报数据
      • 一个数据影响多个数据
    • methods:
      • 看到一次计算一次

vue的深入式响应原理

  • 非侵入性的响应式系统
  • 数据模型是普通的js对象,修改时,视图回进行更新
  • 原理:
    • 把一个普通的js对象传入vue实例作为data,vue将便利此对象的所有的property,并使用Object.defineProperty把这些property全部转换为getter,setter;
    • getter,setter用户不可见,但是内部vue可以追踪依赖,在proerpty被访问和修改时通知变更。
    • 每个组件实例都对应一个watcher实例,会在组件渲染的过程中把接触过的数据property记录为依赖
    • 之后当依赖项的setter触发时,会通知watcher,从而使关联的组件重新渲染。

vue的proxy原理

  • Proxy基本语法 const obj = new Proxy(target, handler); 参数说明如下: target: 被代理对象。 handler: 是一个对象,声明了代理target的一些操作。 obj: 是被代理完成之后返回的对象。 但是当外界每次对obj进行操作时,就会执行handler对象上的一些方法。handler中常用的对象方法如下: 1. get(target, propKey, receiver) 2. set(target, propKey, value, receiver) 3. has(target, propKey) 4. construct(target, args): 5. apply(target, object, args)

vue的生命周期

  • 创建vue实例,Vue();
  • 在创建vue实例,执行init(),调用beforeCreate();
  • 同时监听data数据,初始化内部事件,进行属性,方法计算;
  • 以上干完了调用created钩子函数;
  • 模版编译
  • 编译模版完成,调用beforeMount函数
  • Mounted: el被vm.$el替换,并挂载实例上之后调用该钩子函数
  • beforeUpdate:数据更新时调用,发生在虚拟Dom重新渲染和打补丁之前,再次钩子中可以进一步更新状态,不会出发附加render过程;
  • updated:数据更新导致的虚拟Dom重新渲染,打补丁,被调用时,组件dom已更新;
  • activated: keep-alive组件激活时调用
  • deactivated: keep-alive组件停用时调用
  • beforeDestory: 实例销毁之前
  • destoryed:vue实例销毁后调用

vue的父子组件的生命周期的顺序

  • 父beforeCreate ----》父 crated ----》父beforeMount ----》子 beforeCreate -----》子 created ------》 子 beforeMount -----》 子 mounted -----》 父 mounted

keep-alive的用法

  • keep-alive是什么?
    • 抽象组件,自身不会渲染Dom,也不会出现在父组件中,包裹动态组件,缓存不活动的组件,不是销毁组件
  • 应用
    • 动态组件中 ---------》 inclued(缓存) exclued(不缓存)
    • vue-router中使用

vue的父子组件通信

  • props/emit方法
    • 父组件 A 通过 props 的方式向子组件 B 传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
  • emit/emit/on
    • 这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。
    • 方法: var Event = new Vue();
  • vuex方法
    • vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 进行,Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。
    • vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在 vuex 里数据改变的时候把数据拷贝一份保存到 localStorage 里面,刷新之后,如果 localStorage 里有保存的数据,取出来再替换 store 里的 state。
  • attrs/attrs/listeners
    • attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(classstyle除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(classstyle除外),并且可以通过vbind="attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
    • listeners:包含了父作用域中的(不含.native修饰器的)von事件监听器。它可以通过von="listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="listeners" 传入内部组件
  • provide/inject
    • 祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量
    • provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

vue的优点和缺点

  • 优点:组件化开发,单页面路由,丰富的api方法,单项数据流,易于结合第三方库;
  • 缺点: 指令太多滥用指令,

vue的路由的懒加载原理,import和require区别?

  • 原理:
    • vue的异步组件,webpack的代码分割功能实现懒加载
  • 区别:
    • require: 是AMD方式 ----》 运行时调用(赋值过程)
    • import: 是es6的语法 ----》 编译时调用(解构过程)
  • 优点
    • 更好的用户体验,首屏组件家在速度更快,解决白屏,懒加载将页面划分,需要时家在,分担首页所承担的家在压力,减少首页加载用时## webpack的常用的loader和plugin?
  1. 常用loader: * css-loader: 处理图片路径 ---》 css样式打包进js中 * style-loader: 通过把css插入dom中 * postcss-loader:兼容不同浏览器,自动加上前缀-webkit * sass-loader: 家在sass/scss文件并编译成css * vue-loader: webpack的loader,单文件写vue组件 * vue-style-loader: 基于style-loader链式家在css-loader,内置依赖 * eslint-loader: 支持eslint * eslint-freendly-formatter: 格式化 * svg-sprite-loader: svg雪碧图
  2. 常用plugin: * mini-css-extract-plugin: css提取为独立的文件,对每个包含css的js文件都会创建一个css文件,支持按需加载css,sourcemap,不支持HMR * optimize-css-assets-webpack-plugin: 优化css资源 * css-webpack-plugin: 每次构建dist前清理dist * babel-polyfill: es6的一些方法不能转换

webpack的打包原理

  • webpack会分析每个入口文件,解析包依赖关系的各个文件,每个模块都打包到bundle.js。webpack给每个模块分配一个唯一的ID并通过这个ID索引和访问模块。页面运行时,先启动entry.js,其他模块会在运行require时候执行。

webpack的loader

  1. loader: 文件加载起,能够加载资源文件,对文件进行一些处理,最终一起打包到指定的文件中;
    • 注意:
      • 处理一个文件可以使用多个loader,loader的执行顺序和配置中的顺序相反,最后一个loader最先执行;
      • 第一个执行的loader,接收资源文件内容作为参数,其他loader接收前一个执行的loader的返回值作为参数,最后执行的loader会返回此模块的js源码
  2. plugin: 在webpack中,plugin是用来拓展webpack的,是一个apply属性的js对象,属性会被compiler调用 注意: loader是在module。rules里面配置,plugin是在plugins里面单独配置

webpack中,开发用过那些提高效率的插件?

  • webpack-dashboard: 展示打包信息
  • webpack-merge: 提取公共配置,减少重复配置代码
  • speed-measure-webpack-plugin: 打包过程中的loader plugin的耗时,性能瓶颈;
  • size-plugin: 监控资源体积变化
  • HotModuleReplacePlugin: 模块热更替

SourceMap是什么?生产环境怎么用?

  • 编译打包压缩后的代码应社会源代码的过程,打包压缩后的代码不具备良好的可读性,调试源码就需要source map
  1. hidden-source-map: 借助第三方错误,监控平台entry;
  2. nosources-source-map: 只提示具体行数,以及查看源代码的错误信息
  3. sourcemap: 通过nginx设置,map文件只对白名单开放

模块打包原理

webpack实际上为每个模块创造了一个可以导出,导入的环境,本质上没有修改代码的执行逻辑,代码执行顺序与模块加载顺序完全一致

webpack打包项目体积过大?

  1. 去掉开发环境下的配置: devtool:false
  2. extractTextPlugin: 提取样式到css文件
  3. webpack-boundle-analyzer: 打包文件体积和依赖关系的可视化
  4. CommonsChunkPlugin: 提取通用模式文件
  5. 提取manifest:让提取的公共js的hash值不变
  6. 压缩js,css,图片

webpack优化构建速度性能?

  1. 保持最新版本的webpack和node.js

  2. 多进程/多实例构建: thread-loader,可以将非常小号资源的loaders转存到workpoll中

  3. 持久化缓存---cache-loader

  4. 提取页面的公共资源 * 使用html-webpack-externals-plugin * 使用splitchunksplugin进行替换 * 基础包cdn引入 * commonschunkplugin * 图片压缩,image-webpack-loader * 缩小打包的作用域 * 利用缓存进行二次构建: babel-loader/ cache-loader