关于vue.js:前端高频vue面试题合集

3次阅读

共计 25015 个字符,预计需要花费 63 分钟才能阅读完成。

路由的 hash 和 history 模式的区别

Vue-Router 有两种模式:hash 模式 history 模式。默认的路由模式是 hash 模式。

1. hash 模式

简介: hash 模式是开发中默认的模式,它的 URL 带着一个 #

特点:hash 值会呈现在 URL 外面,然而不会呈现在 HTTP 申请中,对后端齐全没有影响。所以扭转 hash 值,不会从新加载页面。这种模式的浏览器反对度很好,低版本的 IE 浏览器也反对这种模式。hash 路由被称为是前端路由,曾经成为 SPA(单页面利用)的标配。

原理: hash 模式的次要原理就是 onhashchange() 事件

window.onhashchange = function(event){console.log(event.oldURL, event.newURL);
    let hash = location.hash.slice(1);
}

应用 onhashchange()事件的益处就是,在页面的 hash 值发生变化时,无需向后端发动申请,window 就能够监听事件的扭转,并按规定加载相应的代码。除此之外,hash 值变动对应的 URL 都会被浏览器记录下来,这样浏览器就能实现页面的后退和后退。尽管是没有申请后端服务器,然而页面的 hash 值和对应的 URL 关联起来了。

2. history 模式

简介: history 模式的 URL 中没有 #,它应用的是传统的路由散发模式,即用户在输出一个 URL 时,服务器会接管这个申请,并解析这个 URL,而后做出相应的逻辑解决。特点: 相比 hash 模式更加难看。然而,history 模式须要后盾配置反对。如果后盾没有正确配置,拜访时会返回 404。API: history api 能够分为两大部分,切换历史状态和批改历史状态:

  • 批改历史状态:包含了 HTML5 History Interface 中新增的 pushState()replaceState() 办法,这两个办法利用于浏览器的历史记录栈,提供了对历史记录进行批改的性能。只是当他们进行批改时,尽管批改了 url,但浏览器不会立刻向后端发送申请。如果要做到扭转 url 但又不刷新页面的成果,就须要前端用上这两个 API。
  • 切换历史状态: 包含 forward()back()go() 三个办法,对应浏览器的后退,后退,跳转操作。

尽管 history 模式抛弃了俊俏的 #。然而,它也有本人的毛病,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出 404 来。

如果想要切换到 history 模式,就要进行以下配置(后端也要进行配置):

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

3. 两种模式比照

调用 history.pushState() 相比于间接批改 hash,存在以下劣势:

  • pushState() 设置的新 URL 能够是与以后 URL 同源的任意 URL;而 hash 只可批改 # 前面的局部,因而只能设置与以后 URL 同文档的 URL;
  • pushState() 设置的新 URL 能够与以后 URL 截然不同,这样也会把记录增加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录增加到栈中;
  • pushState() 通过 stateObject 参数能够增加任意类型的数据到记录中;而 hash 只可增加短字符串;
  • pushState() 可额定设置 title 属性供后续应用。
  • hash 模式下,仅 hash 符号之前的 url 会被蕴含在申请中,后端如果没有做到对路由的全笼罩,也不会返回 404 谬误;history 模式下,前端的 url 必须和理论向后端发动申请的 url 统一,如果没有对用的路由解决,将返回 404 谬误。

hash 模式和 history 模式都有各自的劣势和缺点,还是要依据理论状况选择性的应用。

vue 和 react 的区别

=> 相同点:

1. 数据驱动页面,提供响应式的试图组件
2. 都有 virtual DOM, 组件化的开发,通过 props 参数进行父子之间组件传递数据,都实现了 webComponents 标准
3. 数据流动单向,都反对服务器的渲染 SSR
4. 都有反对 native 的办法,react 有 React native,vue 有 wexx

=> 不同点:

 1. 数据绑定:Vue 实现了双向的数据绑定,react 数据流动是单向的
 2. 数据渲染:大规模的数据渲染,react 更快
 3. 应用场景:React 配合 Redux 架构适宜大规模多人合作简单我的项目,Vue 适宜小快的我的项目
 4. 开发格调:react 举荐做法 jsx + inline style 把 html 和 css 都写在 js 了
      vue 是采纳 webpack + vue-loader 单文件组件格局,html, js, css 同一个文件

虚构 DOM 实现原理?

虚构 DOM 的实现原理次要包含以下 3 局部:

  • 用 JavaScript 对象模仿实在 DOM 树,对实在 DOM 进行形象;
  • diff 算法 — 比拟两棵虚构 DOM 树的差别;
  • pach 算法 — 将两个虚构 DOM 对象的差别利用到真正的 DOM 树。

Vue.js 的 template 编译

简而言之,就是先转化成 AST 树,再失去的 render 函数返回 VNode(Vue 的虚构 DOM 节点),具体步骤如下:

首先,通过 compile 编译器把 template 编译成 AST 语法树(abstract syntax tree 即 源代码的形象语法结构的树状表现形式),compile 是 createCompiler 的返回值,createCompiler 是用以创立编译器的。另外 compile 还负责合并 option。

而后,AST 会通过 generate(将 AST 语法树转化成 render funtion 字符串的过程)失去 render 函数,render 的返回值是 VNode,VNode 是 Vue 的虚构 DOM 节点,外面有(标签名、子节点、文本等等)

Vue 是如何收集依赖的?

在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将由一般对象变成响应式对象,在这个过程中便会进行依赖收集的相干逻辑,如下所示∶

function defieneReactive (obj, key, val){const dep = new Dep();
  ...
  Object.defineProperty(obj, key, {
    ...
    get: function reactiveGetter () {if(Dep.target){dep.depend();
        ...
      }
      return val
    }
    ...
  })
}

以上只保留了要害代码,次要就是 const dep = new Dep()实例化一个 Dep 的实例,而后在 get 函数中通过 dep.depend() 进行依赖收集。(1)Dep Dep 是整个依赖收集的外围,其要害代码如下:

class Dep {
  static target;
  subs;

  constructor () {
    ...
    this.subs = [];}
  addSub (sub) {this.subs.push(sub)
  }
  removeSub (sub) {remove(this.sub, sub)
  }
  depend () {if(Dep.target){Dep.target.addDep(this)
    }
  }
  notify () {const subs = this.subds.slice();
    for(let i = 0;i < subs.length; i++){subs[i].update()}
  }
}

Dep 是一个 class,其中有一个关 键的动态属性 static,它指向了一个全局惟一 Watcher,保障了同一时间全局只有一个 watcher 被计算,另一个属性 subs 则是一个 Watcher 的数组,所以 Dep 实际上就是对 Watcher 的治理,再看看 Watcher 的相干代码∶

(2)Watcher

class Watcher {
  getter;
  ...
  constructor (vm, expression){
    ...
    this.getter = expression;
    this.get();}
  get () {pushTarget(this);
    value = this.getter.call(vm, vm)
    ...
    return value
  }
  addDep (dep){
        ...
    dep.addSub(this)
  }
  ...
}
function pushTarget (_target) {Dep.target = _target}

Watcher 是一个 class,它定义了一些办法,其中和依赖收集相干的次要有 get、addDep 等。

(3)过程

在实例化 Vue 时,依赖收集的相干过程如下∶
初 始 化 状 态 initState,这 中 间 便 会 通 过 defineReactive 将数据变成响应式对象,其中的 getter 局部便是用来依赖收集的。
初始化最终会走 mount 过程,其中会实例化 Watcher,进入 Watcher 中,便会执行 this.get() 办法,

updateComponent = () => {vm._update(vm._render())
}
new Watcher(vm, updateComponent)

get 办法中的 pushTarget 实际上就是把 Dep.target 赋值为以后的 watcher。

this.getter.call(vm,vm),这里的 getter 会执行 vm._render() 办法,在这个过程中便会触发数据对象的 getter。那么每个对象值的 getter 都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 办法,也就会执行 Dep.target.addDep(this)。方才 Dep.target 曾经被赋值为 watcher,于是便会执行 addDep 办法,而后走到 dep.addSub() 办法,便将以后的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目标是为后续数据变动时候能告诉到哪些 subs 做筹备。所以在 vm._render() 过程中,会触发所有数据的 getter,这样便曾经实现了一个依赖收集的过程。

MVVM、MVC、MVP 的区别

MVC、MVP 和 MVVM 是三种常见的软件架构设计模式,次要通过拆散关注点的形式来组织代码构造,优化开发效率。

在开发单页面利用时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文件里。页面的渲染、数据的获取,对用户事件的响应所有的应用逻辑都混合在一起,这样在开发简略我的项目时,可能看不出什么问题,如果我的项目变得复杂,那么整个文件就会变得简短、凌乱,这样对我的项目开发和前期的我的项目保护是十分不利的。

(1)MVC

MVC 通过拆散 Model、View 和 Controller 的形式来组织代码构造。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 利用了观察者模式,当 Model 层产生扭转的时候它会告诉无关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它次要负责用户与利用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来实现对 Model 的批改,而后 Model 层再去告诉 View 层更新。

(2)MVVM

MVVM 分为 Model、View、ViewModel:

  • Model 代表数据模型,数据和业务逻辑都在 Model 层中定义;
  • View 代表 UI 视图,负责数据的展现;
  • ViewModel 负责监听 Model 中数据的扭转并且管制视图的更新,解决用户交互操作;

Model 和 View 并无间接关联,而是通过 ViewModel 来进行分割的,Model 和 ViewModel 之间有着双向数据绑定的分割。因而当 Model 中的数据扭转时会触发 View 层的刷新,View 中因为用户交互操作而扭转的数据也会在 Model 中同步。

这种模式实现了 Model 和 View 的数据主动同步,因而开发者只须要专一于数据的保护操作即可,而不须要本人操作 DOM。

(3)MVP

MVP 模式与 MVC 惟一不同的在于 Presenter 和 Controller。在 MVC 模式中应用观察者模式,来实现当 Model 层数据发生变化的时候,告诉 View 层的更新。这样 View 层和 Model 层耦合在一起,当我的项目逻辑变得复杂的时候,可能会造成代码的凌乱,并且可能会对代码的复用性造成一些问题。MVP 的模式通过应用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的 Controller 只晓得 Model 的接口,因而它没有方法管制 View 层的更新,MVP 模式中,View 层的接口裸露给了 Presenter 因而能够在 Presenter 中将 Model 的变动和 View 的变动绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还蕴含了其余的响应逻辑。

vue 中应用了哪些设计模式

1. 工厂模式 – 传入参数即可创立实例

虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode

2. 单例模式 – 整个程序有且仅有一个实例

vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉

3. 公布 - 订阅模式 (vue 事件机制)

4. 观察者模式 (响应式数据原理)

5. 装璜模式: (@装璜器的用法)

6. 策略模式 策略模式指对象有某个行为, 然而在不同的场景中, 该行为有不同的实现计划 - 比方选项的合并策略

参考:前端 vue 面试题具体解答

为什么 vue 组件中 data 必须是一个函数?

对象为援用类型,当复用组件时,因为数据对象都指向同一个 data 对象,当在一个组件中批改 data 时,其余重用的组件中的 data 会同时被批改;而应用返回对象的函数,因为每次返回的都是一个新对象(Object 的实例),援用地址不同,则不会呈现这个问题。

Vue 中封装的数组办法有哪些,其如何实现页面更新

在 Vue 中,对响应式解决利用的是 Object.defineProperty 对数据进行拦挡,而这个办法并不能监听到数组外部变动,数组长度变动,数组的截取变动等,所以须要对这些操作进行 hack,让 Vue 能监听到其中的变动。那 Vue 是如何实现让这些数组办法实现元素的实时更新的呢,上面是 Vue 中对这些办法的封装:

// 缓存数组原型
const arrayProto = Array.prototype;
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto);
// 须要进行性能拓展的办法
const methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse"
];

/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function(method) {
  // 缓存原生数组办法
  const original = arrayProto[method];
  def(arrayMethods, method, function mutator(...args) {
    // 执行并缓存原生数组性能
    const result = original.apply(this, args);
    // 响应式解决
    const ob = this.__ob__;
    let inserted;
    switch (method) {
    // push、unshift 会新增索引,所以要手动 observer
      case "push":
      case "unshift":
        inserted = args;
        break;
      // splice 办法,如果传入了第三个参数,也会有索引退出,也要手动 observer。case "splice":
        inserted = args.slice(2);
        break;
    }
    // 
    if (inserted) ob.observeArray(inserted);// 获取插入的值,并设置响应式监听
    // notify change
    ob.dep.notify();// 告诉依赖更新
    // 返回原生数组办法的执行后果
    return result;
  });
});

简略来说就是,重写了数组中的那些原生办法,首先获取到这个数组的__ob__,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 持续对新的值察看变动(也就是通过 target__proto__ == arrayMethods 来扭转了数组实例的型),而后手动调用 notify,告诉渲染 watcher,执行 update。

vue-cli 工程罕用的 npm 命令有哪些

  • 下载 node_modules 资源包的命令:
npm install
  • 启动 vue-cli 开发环境的 npm 命令:
npm run dev
  • vue-cli 生成 生产环境部署资源 的 npm命令:
npm run build
  • 用于查看 vue-cli 生产环境部署资源文件大小的 npm命令:
npm run build --report

在浏览器上自动弹出一个 展现 vue-cli 工程打包后 app.jsmanifest.jsvendor.js 文件外面所蕴含代码的页面。能够具此优化 vue-cli 生产环境部署的动态资源,晋升 页面 的加载速度

简述 mixin、extends 的笼罩逻辑

(1)mixin 和 extends mixin 和 extends 均是用于合并、拓展组件的,两者均通过 mergeOptions 办法实现合并。

  • mixins 接管一个混入对象的数组,其中混入对象能够像失常的实例对象一样蕴含实例选项,这些选项会被合并到最终的选项中。Mixin 钩子依照传入程序顺次调用,并在调用组件本身的钩子之前被调用。
  • extends 次要是为了便于扩大单文件组件,接管一个对象或构造函数。

    (2)mergeOptions 的执行过程

  • 规范化选项(normalizeProps、normalizelnject、normalizeDirectives)
  • 对未合并的选项,进行判断
if (!child._base) {if (child.extends) {parent = mergeOptions(parent, child.extends, vm);
  }
  if (child.mixins) {for (let i = 0, l = child.mixins.length; i < l; i++) {parent = mergeOptions(parent, child.mixins[i], vm);
    }
  }
}
  • 合并解决。依据一个通用 Vue 实例所蕴含的选项进行分类逐个判断合并,如 props、data、methods、watch、computed、生命周期等,将合并后果存储在新定义的 options 对象里。
  • 返回合并后果 options。

Vue 我的项目本地开发实现后部署到服务器后报 404 是什么起因呢

如何部署

前后端拆散开发模式下,前后端是独立布署的,前端只须要将最初的构建物上传至指标服务器的 web 容器指定的动态目录下即可

咱们晓得 vue 我的项目在构建后,是生成一系列的动态文件

惯例布署咱们只须要将这个目录上传至指标服务器即可

web 容器跑起来,以 nginx 为例

server {
  listen  80;
  server_name  www.xxx.com;

  location / {index  /data/dist/index.html;}
}

配置实现记得重启nginx

// 查看配置是否正确
nginx -t 

// 平滑重启
nginx -s reload

操作完后就能够在浏览器输出域名进行拜访了

当然下面只是提到最简略也是最间接的一种布署形式

什么自动化,镜像,容器,流水线布署,实质也是将这套逻辑形象,隔离,用程序来代替重复性的劳动,本文不开展

404 问题

这是一个经典的问题,置信很多同学都有遇到过,那么你晓得其真正的起因吗?

咱们先还原一下场景:

  • vue我的项目在本地时运行失常,但部署到服务器中,刷新页面,呈现了 404 谬误

先定位一下,HTTP 404 谬误意味着链接指向的资源不存在

问题在于为什么不存在?且为什么只有 history 模式下会呈现这个问题?

为什么 history 模式下有问题

Vue是属于单页利用(single-page application)

SPA 是一种网络应用程序或网站的模型,所有用户交互是通过动静重写以后页面,后面咱们也看到了,不论咱们利用有多少页面,构建物都只会产出一个index.html

当初,咱们回头来看一下咱们的 nginx 配置

server {
  listen  80;
  server_name  www.xxx.com;

  location / {index  /data/dist/index.html;}
}

能够依据 nginx 配置得出,当咱们在地址栏输出 www.xxx.com 时,这时会关上咱们 dist 目录下的 index.html 文件,而后咱们在跳转路由进入到 www.xxx.com/login

要害在这里,当咱们在 website.com/login 页执行刷新操作,nginx location 是没有相干配置的,所以就会呈现 404 的状况

为什么 hash 模式下没有问题

router hash 模式咱们都晓得是用符号 #示意的,如 website.com/#/login, hash 的值为 #/login

它的特点在于:hash 尽管呈现在 URL 中,但不会被包含在 HTTP 申请中,对服务端齐全没有影响,因而扭转 hash 不会从新加载页面

hash 模式下,仅 hash 符号之前的内容会被蕴含在申请中,如 website.com/#/login 只有 website.com 会被蕴含在申请中,因而对于服务端来说,即便没有配置 location,也不会返回404 谬误

解决方案

看到这里我置信大部分同学都能想到怎么解决问题了,

产生问题的实质是因为咱们的路由是通过 JS 来执行视图切换的,

当咱们进入到子路由时刷新页面,web容器没有绝对应的页面此时会呈现404

所以咱们只须要配置将任意页面都重定向到 index.html,把路由交由前端解决

nginx 配置文件 .conf 批改,增加try_files $uri $uri/ /index.html;

server {
  listen  80;
  server_name  www.xxx.com;

  location / {
    index  /data/dist/index.html;
    try_files $uri $uri/ /index.html;
  }
}

批改完配置文件后记得配置的更新

nginx -s reload

这么做当前,你的服务器就不再返回 404 谬误页面,因为对于所有门路都会返回 index.html 文件

为了防止这种状况,你应该在 Vue 利用外面笼罩所有的路由状况,而后在给出一个 404 页面

const router = new VueRouter({
  mode: 'history',
  routes: [{ path: '*', component: NotFoundComponent}
  ]
})

父子组件生命周期调用程序(简略)

渲染程序:先父后子,实现程序:先子后父

更新程序:父更新导致子更新,子更新实现后父

销毁程序:先父后子,实现程序:先子后父

SPA 首屏加载速度慢的怎么解决

一、什么是首屏加载

首屏工夫(First Contentful Paint),指的是浏览器从响应用户输出网址地址,到首屏内容渲染实现的工夫,此时整个网页不肯定要全副渲染实现,但须要展现以后视窗须要的内容

首屏加载能够说是用户体验中 最重要 的环节

对于计算首屏工夫

利用 performance.timing 提供的数据:

通过 DOMContentLoad 或者 performance 来计算出首屏工夫

// 计划一:document.addEventListener('DOMContentLoaded', (event) => {console.log('first contentful painting');
});
// 计划二:performance.getEntriesByName("first-contentful-paint")[0].startTime

// performance.getEntriesByName("first-contentful-paint")[0]
// 会返回一个 PerformancePaintTiming 的实例,构造如下:{
  name: "first-contentful-paint",
  entryType: "paint",
  startTime: 507.80000002123415,
  duration: 0,
};

二、加载慢的起因

在页面渲染的过程,导致加载速度慢的因素可能如下:

  • 网络延时问题
  • 资源文件体积是否过大
  • 资源是否反复发送申请去加载了
  • 加载脚本的时候,渲染内容梗塞了

三、解决方案

常见的几种 SPA 首屏优化形式

  • 减小入口文件积
  • 动态资源本地缓存
  • UI 框架按需加载
  • 图片资源的压缩
  • 组件反复打包
  • 开启 GZip 压缩
  • 应用 SSR

1. 减小入口文件体积

罕用的伎俩是路由懒加载,把不同路由对应的组件宰割成不同的代码块,待路由被申请的时候会独自打包路由,使得入口文件变小,加载速度大大增加

vue-router 配置路由的时候,采纳动静加载路由的模式

routes:[ 
    path: 'Blogs',
    name: 'ShowBlogs',
    component: () => import('./components/ShowBlogs.vue')
]

以函数的模式加载路由,这样就能够把各自的路由文件别离打包,只有在解析给定的路由时,才会加载路由组件

2. 动态资源本地缓存

后端返回资源问题:

  • 采纳 HTTP 缓存,设置 Cache-ControlLast-ModifiedEtag 等响应头
  • 采纳 Service Worker 离线缓存

前端正当利用localStorage

3. UI 框架按需加载

在日常应用 UI 框架,例如 element-UI、或者antd,咱们经常性间接援用整个UI

import ElementUI from 'element-ui'
Vue.use(ElementUI)

但实际上我用到的组件只有按钮,分页,表格,输出与正告 所以咱们要按需援用

import {Button, Input, Pagination, Table, TableColumn, MessageBox} from 'element-ui';
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)

4. 组件反复打包

假如 A.js 文件是一个罕用的库,当初有多个路由应用了 A.js 文件,这就造成了反复下载

解决方案:在 webpackconfig文件中,批改 CommonsChunkPlugin 的配置

minChunks: 3

minChunks为 3 示意会把应用 3 次及以上的包抽离进去,放进公共依赖文件,防止了反复加载组件

5. 图片资源的压缩

图片资源尽管不在编码过程中,但它却是对页面性能影响最大的因素

对于所有的图片资源,咱们能够进行适当的压缩

对页面上应用到的 icon,能够应用在线字体图标,或者雪碧图,将泛滥小图标合并到同一张图上,用以加重http 申请压力。

6. 开启 GZip 压缩

拆完包之后,咱们再用 gzip 做一下压缩 装置compression-webpack-plugin

cnmp i compression-webpack-plugin -D

vue.congig.js 中引入并批改 webpack 配置

const CompressionPlugin = require('compression-webpack-plugin')

configureWebpack: (config) => {if (process.env.NODE_ENV === 'production') {
            // 为生产环境批改配置...
            config.mode = 'production'
            return {
                plugins: [new CompressionPlugin({
                    test: /\.js$|\.html$|\.css/, // 匹配文件名
                    threshold: 10240, // 对超过 10k 的数据进行压缩
                    deleteOriginalAssets: false // 是否删除原文件
                })]
            }
        }

在服务器咱们也要做相应的配置 如果发送申请的浏览器反对 gzip,就发送给它gzip 格局的文件 我的服务器是用 express 框架搭建的 只有装置一下 compression 就能应用

const compression = require('compression')
app.use(compression())  // 在其余中间件应用之前调用

7. 应用 SSR

SSR(Server side),也就是服务端渲染,组件或页面通过服务器生成 html 字符串,再发送到浏览器

从头搭建一个服务端渲染是很简单的,vue利用倡议应用 Nuxt.js 实现服务端渲染

四、小结

缩小首屏渲染工夫的办法有很多,总的来讲能够分成两大部分:资源加载优化 页面渲染优化

下图是更为全面的首屏优化的计划

大家能够依据本人我的项目的状况抉择各种形式进行首屏渲染的优化

Vue 我的项目性能优化 - 具体

Vue 框架通过数据双向绑定和虚构 DOM 技术,帮咱们解决了前端开发中最脏最累的 DOM 操作局部,咱们不再须要去思考如何操作 DOM 以及如何最高效地操作 DOM;但 Vue 我的项目中依然存在我的项目首屏优化、Webpack 编译配置优化等问题,所以咱们依然须要去关注 Vue 我的项目性能方面的优化,使我的项目具备更高效的性能、更好的用户体验

代码层面的优化

1. v-if 和 v-show 辨别应用场景

  • v-if 是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块
  • v-show 就简略得多,不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS displaynone/block 属性进行切换。
  • 所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景

2. computed 和 watch 辨别应用场景

  • computed:是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值;
  • watch:更多的是「察看」的作用,相似于某些数据的监听回调,每当监听的数据变动时都会执行回调进行后续操作

使用场景:

  • 当咱们须要进行数值计算,并且依赖于其它数据时,应该应用 computed,因为能够利用 computed 的缓存个性,防止每次获取值时,都要从新计算;
  • 当咱们须要在数据变动时执行异步或开销较大的操作时,应该应用 watch,应用 watch 选项容许咱们执行异步操作 (拜访一个 API ),限度咱们执行该操作的频率,并在咱们失去最终后果前,设置中间状态。这些都是计算属性无奈做到的

3. v-for 遍历必须为 item 增加 key,且防止同时应用 v-if

  • v-for 遍历必须为 item 增加 key

    • 在列表数据进行遍历渲染时,须要为每一项 item 设置惟一 key 值,不便 Vue.js 外部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值比照,较快地定位到 diff
  • v-for 遍历防止同时应用 v-if

    • vue2.xv-forv-if 优先级高,如果每一次都须要遍历整个数组,将会影响速度,尤其是当之须要渲染很小一部分的时候,必要状况下应该替换成 computed 属性

举荐:

<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id">
    {{user.name}}
  </li>
</ul>
computed: {activeUsers: function () {return this.users.filter(function (user) {return user.isActive})
  }
}

不举荐:

<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id">
    {{user.name}}
  </li>
</ul>

4. 长列表性能优化

Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变动,然而有些时候咱们的组件就是纯正的数据展现,不会有任何扭转,咱们就不须要 Vue 来劫持咱们的数据,在大量数据展现的状况下,这可能很显著的缩小组件初始化的工夫,那如何禁止 Vue 劫持咱们的数据呢?能够通过 Object.freeze 办法来解冻一个对象,一旦被解冻的对象就再也不能被批改了

export default {data: () => ({users: {}
  }),
  async created() {const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  }
};

5. 事件的销毁

Vue 组件销毁时,会主动清理它与其它实例的连贯,解绑它的全副指令及事件监听器,然而仅限于组件自身的事件。如果在 js 内应用 addEventListener 等形式是不会主动销毁的,咱们须要在组件销毁时手动移除这些事件的监听,免得造成内存泄露,如:

created() {addEventListener('click', this.click, false)
},
beforeDestroy() {removeEventListener('click', this.click, false)
}

6. 图片资源懒加载

对于图片过多的页面,为了减速页面加载速度,所以很多时候咱们须要将页面内未呈现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的晋升,也进步了用户体验。咱们在我的项目中应用 Vuevue-lazyload 插件

npm install vue-lazyload --save-dev

在入口文件 man.js 中引入并应用

import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload)

// 或者增加自定义选项
Vue.use(VueLazyload, {
  preLoad: 1.3,
  error: 'dist/error.png',
  loading: 'dist/loading.gif',
  attempt: 1
})

vue 文件中将 img 标签的 src 属性间接改为 v-lazy,从而将图片显示方式更改为懒加载显示

<img v-lazy="/static/img/1.png">

以上为 vue-lazyload 插件的简略应用,如果要看插件的更多参数选项,能够查看 vue-lazyload 的 github 地址(opens new window)

7. 路由懒加载

Vue 是单页面利用,可能会有很多的路由引入,这样应用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会呈现白屏的状况,不利于用户体验。如果咱们能把不同路由对应的组件宰割成不同的代码块,而后当路由被拜访的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,然而可能其余的页面的速度就会降下来

路由懒加载:

const Foo = () => import('./Foo.vue')
const router = new VueRouter({
  routes: [{ path: '/foo', component: Foo}
  ]
})

8. 第三方插件的按需引入

咱们在我的项目中常常会须要引入第三方插件,如果咱们间接引入整个插件,会导致我的项目的体积太大,咱们能够借助 babel-plugin-component,而后能够只引入须要的组件,以达到减小我的项目体积的目标。以下为我的项目中引入 element-ui 组件库为例

npm install babel-plugin-component -D

.babelrc 批改为:

{"presets": [["es2015", { "modules": false}]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

main.js 中引入局部组件:

import Vue from 'vue';
import {Button, Select} from 'element-ui';

Vue.use(Button)
Vue.use(Select)

9. 优化有限列表性能

如果你的利用存在十分长或者有限滚动的列表,那么须要采纳 虚构列表 的技术来优化性能,只须要渲染少部分区域的内容,缩小从新渲染组件和创立 dom 节点的工夫。你能够参考以下开源我的项目 vue-virtual-scroll-list (opens new window) 和 vue-virtual-scroller (opens new window)来优化这种有限列表的场景的

10. 服务端渲染 SSR or 预渲染

服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端实现,服务端造成的 html 片段间接返回给客户端这个过程就叫做服务端渲染。

  • 如果你的我的项目的 SEO首屏渲染 是评估我的项目的要害指标,那么你的我的项目就须要服务端渲染来帮忙你实现最佳的初始加载性能和 SEO
  • 如果你的 Vue 我的项目只需改善多数营销页面(例如 //about/contact 等)的 SEO,那么你可能须要预渲染,在构建时简略地生成针对特定路由的动态 HTML 文件。长处是设置预渲染更简略,并能够将你的前端作为一个齐全动态的站点,具体你能够应用 prerender-spa-plugin (opens new window) 就能够轻松地增加预渲染

Webpack 层面的优化

1. Webpack 对图片进行压缩

对小于 limit 的图片转化为 base64 格局,其余的不做操作。所以对有些较大的图片资源,在申请资源的时候,加载会很慢,咱们能够用 image-webpack-loader来压缩图片

npm install image-webpack-loader --save-dev
{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
  use:[
    {
    loader: 'url-loader',
    options: {
      limit: 10000,
      name: utils.assetsPath('img/[name].[hash:7].[ext]')
      }
    },
    {
      loader: 'image-webpack-loader',
      options: {bypassOnDebug: true,}
    }
  ]
}

2. 缩小 ES6 转为 ES5 的冗余代码

Babel 插件会在将 ES6 代码转换成 ES5 代码时会注入一些辅助函数,例如上面的 ES6 代码

class HelloWebpack extends Component{...}

这段代码再被转换成能失常运行的 ES5 代码时须要以下两个辅助函数:

babel-runtime/helpers/createClass  // 用于实现 class 语法
babel-runtime/helpers/inherits  // 用于实现 extends 语法    

在默认状况下,Babel 会在每个输入文件中内嵌这些依赖的辅助函数代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会呈现很屡次,造成代码冗余。为了不让这些辅助函数的代码反复呈现,能够在依赖它们时通过 require('babel-runtime/helpers/createClass') 的形式导入,这样就能做到只让它们呈现一次。babel-plugin-transform-runtime 插件就是用来实现这个作用的,将相干辅助函数进行替换成导入语句,从而减小 babel 编译进去的代码的文件大小

npm install babel-plugin-transform-runtime --save-dev

批改 .babelrc 配置文件为:

"plugins": ["transform-runtime"]

3. 提取公共代码

如果我的项目中没有去将每个页面的第三方库和公共模块提取进去,则我的项目会存在以下问题:

  • 雷同的资源被反复加载,节约用户的流量和服务器的老本。
  • 每个页面须要加载的资源太大,导致网页首屏加载迟缓,影响用户体验。

所以咱们须要将多个页面的公共代码抽离成独自的文件,来优化以上问题。Webpack 内置了专门用于提取多个Chunk 中的公共局部的插件 CommonsChunkPlugin,咱们在我的项目中 CommonsChunkPlugin 的配置如下:

// 所有在 package.json 外面依赖的包,都会被打包进 vendor.js 这个文件中。new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks: function(module, count) {
    return (
      module.resource &&
      /\.js$/.test(module.resource) &&
      module.resource.indexOf(path.join(__dirname, '../node_modules')
      ) === 0
    );
  }
}),
// 抽取出代码模块的映射关系
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  chunks: ['vendor']
})

4. 模板预编译

  • 当应用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。通常状况下这个过程曾经足够快了,但对性能敏感的利用还是最好防止这种用法。
  • 预编译模板最简略的形式就是应用单文件组件——相干的构建设置会主动把预编译解决好,所以构建好的代码曾经蕴含了编译进去的渲染函数而不是原始的模板字符串。
  • 如果你应用 webpack,并且喜爱拆散 JavaScript 和模板文件,你能够应用 vue-template-loader (opens new window),它也能够在构建过程中把模板文件转换成为 JavaScript 渲染函数

5. 提取组件的 CSS

当应用单文件组件时,组件内的 CSS 会以 style 标签的形式通过 JavaScript 动静注入。这有一些小小的运行时开销,如果你应用服务端渲染,这会导致一段“无款式内容闪动 (fouc)”。将所有组件的 CSS 提取到同一个文件能够防止这个问题,也会让 CSS 更好地进行压缩和缓存

6. 优化 SourceMap

咱们在我的项目进行打包后,会将开发中的多个文件代码打包到一个文件中,并且通过压缩、去掉多余的空格、babel 编译化后,最终将编译失去的代码会用于线上环境,那么这样解决后的代码和源代码会有很大的差异,当有 bug 的时候,咱们只能定位到压缩解决后的代码地位,无奈定位到开发环境中的代码,对于开发来说不好调式定位问题,因而 sourceMap 呈现了,它就是为了解决不好调式代码问题的

SourceMap 的可选值如下(+ 号越多,代表速度越快,- 号越多,代表速度越慢, o 代表中等速度)

  • 开发环境举荐:cheap-module-eval-source-map
  • 生产环境举荐:cheap-module-source-map

起因如下:

  • cheap:源代码中的列信息是没有任何作用,因而咱们打包后的文件不心愿蕴含列相干信息,只有行信息能建设打包前后的依赖关系。因而不论是开发环境或生产环境,咱们都心愿增加 cheap 的根本类型来疏忽打包前后的列信息;
  • module:不论是开发环境还是正式环境,咱们都心愿能定位到 bug 的源代码具体的地位,比如说某个 Vue 文件报错了,咱们心愿能定位到具体的 Vue 文件,因而咱们也须要 module配置;
  • soure-mapsource-map 会为每一个打包后的模块生成独立的 soucemap 文件,因而咱们须要减少source-map 属性;
  • eval-source-mapeval 打包代码的速度十分快,因为它不生成 map 文件,然而能够对 eval 组合应用 eval-source-map 应用会将 map 文件以 DataURL 的模式存在打包后的 js 文件中。在正式环境中不要应用 eval-source-map, 因为它会减少文件的大小,然而在开发环境中,能够试用下,因为他们打包的速度很快。

7. 构建后果输入剖析

Webpack 输入的代码可读性十分差而且文件十分大,让咱们十分头疼。为了更简略、直观地剖析输入后果,社区中呈现了许多可视化剖析工具。这些工具以图形的形式将后果更直观地展现进去,让咱们疾速理解问题所在。接下来解说咱们在 Vue 我的项目中用到的剖析工具:webpack-bundle-analyzer

if (config.build.bundleAnalyzerReport) {var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}

执行 $ npm run build --report 后生成剖析报告如下

根底的 Web 技术优化

1. 开启 gzip 压缩

gzipGNUzip 的缩写,最早用于 UNIX 零碎的文件压缩。HTTP 协定上的 gzip 编码是一种用来改良 web 应用程序性能的技术,web 服务器和客户端(浏览器)必须独特反对 gzip。目前支流的浏览器,Chrome,firefox,IE 等都反对该协定。常见的服务器如 Apache,Nginx,IIS 同样反对,zip 压缩效率十分高,通常能够达到 70% 的压缩率,也就是说,如果你的网页有 30K,压缩之后就变成了 9K 左右

以下咱们以服务端应用咱们相熟的 express 为例,开启 gzip 非常简单,相干步骤如下:

npm install compression --save
var compression = require('compression');
var app = express();
app.use(compression())

重启服务,察看网络面板外面的 response header,如果看到如下红圈里的字段则表明 gzip 开启胜利

Nginx 开启 gzip 压缩

# 是否启动 gzip 压缩,on 代表启动,off 代表开启
gzip  on;

#须要压缩的常见动态资源
gzip_types text/plain application/javascript   application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;

#因为 nginx 的压缩产生在浏览器端而微软的 ie6 很坑爹, 会导致压缩后图片看不见所以该选
项是禁止 ie6 产生压缩
gzip_disable "MSIE [1-6]\.";

#如果文件大于 1k 就启动压缩
gzip_min_length 1k;

#以 16k 为单位, 依照原始数据的大小以 4 倍的形式申请内存空间, 个别此项不要批改
gzip_buffers 4 16k;

#压缩的等级, 数字抉择范畴是 1 -9, 数字越小压缩的速度越快, 耗费 cpu 就越大
gzip_comp_level 2;

要想配置失效,记得重启 nginx 服务

nginx -t
nginx -s reload

2. 浏览器缓存

为了进步用户加载页面的速度,对动态资源进行缓存是十分必要的,依据是否须要从新向服务器发动申请来分类,将 HTTP 缓存规定分为两大类(强制缓存,比照缓存)

3. CDN 的应用

浏览器从服务器上下载 CSS、js 和图片等文件时都要和服务器连贯,而大部分服务器的带宽无限,如果超过限度,网页就半天反馈不过去。而 CDN 能够通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且 CDN 具备更好的可用性,更低的网络提早和丢包率

4. 应用 Chrome Performance 查找性能瓶颈

ChromePerformance 面板能够录制一段时间内的 js 执行细节及工夫。应用 Chrome 开发者工具剖析页面性能的步骤如下。

  • 关上 Chrome 开发者工具,切换到 Performance 面板
  • 点击 Record 开始录制
  • 刷新页面或开展某个节点
  • 点击 Stop 进行录制

Vue 模版编译原理晓得吗,能简略说一下吗?

简略说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经验以下阶段:

  • 生成 AST 树
  • 优化
  • codegen

首先解析模版,生成AST 语法树(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最初一步是 将优化后的 AST 树转换为可执行的代码

SPA、SSR 的区别是什么

咱们当初编写的 VueReactAngular利用大多数状况下都会在一个页面中,点击链接跳转页面通常是内容切换而非页面跳转,因为良好的用户体验逐步成为支流的开发模式。但同时也会有首屏加载工夫长,SEO不敌对的问题,因而有了SSR,这也是为什么面试中会问到两者的区别

  1. SPA(Single Page Application)即单页面利用。个别也称为 客户端渲染(Client Side Render),简称 CSRSSR(Server Side Render)即 服务端渲染。个别也称为 多页面利用(Mulpile Page Application),简称 MPA
  2. SPA利用只会首次申请 html 文件,后续只须要申请 JSON 数据即可,因而用户体验更好,节约流量,服务端压力也较小。然而首屏加载的工夫会变长,而且 SEO 不敌对。为了解决以上毛病,就有了 SSR 计划,因为 HTML 内容在服务器一次性生成进去,首屏加载快,搜索引擎也能够很不便的抓取页面信息。但同时 SSR 计划也会有性能,开发受限等问题
  3. 在抉择上,如果咱们的利用存在首屏加载优化需要,SEO需要时,就能够思考SSR
  4. 但并不是只有这一种代替计划,比方对一些不常变动的动态网站,SSR 反而浪费资源,咱们能够思考预渲染(prerender)计划。另外 nuxt.js/next.js 中给咱们提供了 SSG(Static Site Generate) 动态网站生成计划也是很好的动态站点解决方案,联合一些 CI 伎俩,能够起到很好的优化成果,且能节约服务器资源

内容生成上的区别:

SSR

SPA

部署上的区别

Vue 中常见性能优化

编码优化

  1. 应用 v-show 复用DOM:防止反复创立组件
<template>
  <div class="cell">
    <!-- 这种状况用 v -show 复用 DOM,比 v -if 成果好 -->
    <div v-show="value" class="on">
      <Heavy :n="10000"/>
    </div>
    <section v-show="!value" class="off">
      <Heavy :n="10000"/>
    </section>
  </div>
</template>
  1. 正当应用路由懒加载、异步组件,无效拆分 App 尺寸,拜访时才异步加载
const router = createRouter({
  routes: [// 借助 webpack 的 import()实现异步组件
    {path: '/foo', component: () => import('./Foo.vue') }
  ]
})
  1. keep-alive缓存页面:防止反复创立组件实例,且能保留缓存组件状态
<router-view v-slot="{Component}">
    <keep-alive>
    <component :is="Component"></component>
  </keep-alive>
</router-view>
  1. v-oncev-memo:不再变动的数据应用v-once
<!-- single element -->
<span v-once>This will never change: {{msg}}</span>
<!-- the element have children -->
<div v-once>
  <h1>comment</h1>
  <p>{{msg}}</p>
</div>
<!-- component -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` directive -->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>

按条件跳过更新时应用v-momo:上面这个列表只会更新选中状态变动项

<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
  <p>ID: {{item.id}} - selected: {{item.id === selected}}</p>
  <p>...more child nodes</p>
</div>
  1. 长列表性能优化:如果是大数据长列表,可采纳虚构滚动,只渲染少部分区域的内容
<recycle-scroller
  class="items"
  :items="items"
  :item-size="24"
>
  <template v-slot="{item}">
    <FetchItemView
      :item="item"
      @vote="voteItem(item)"
    />
  </template>
</recycle-scroller>
  1. 避免外部透露,组件销毁后把全局变量和事件销毁:Vue 组件销毁时,会主动解绑它的全副指令及事件监听器,然而仅限于组件自身的事件
export default {created() {this.timer = setInterval(this.refresh, 2000)
  },
  beforeUnmount() {clearInterval(this.timer)
  }
}
  1. 图片懒加载

对于图片过多的页面,为了减速页面加载速度,所以很多时候咱们须要将页面内未呈现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载

<!-- 参考 https://github.com/hilongjw/vue-lazyload -->
<img v-lazy="/static/img/1.png">
  1. 滚动到可视区域动静加载

https://tangbc.github.io/vue-virtual-scroll-list(opens new window)

  1. 第三方插件按需引入:(babel-plugin-component

element-plus 这样的第三方组件库能够按需引入防止体积太大

import {createApp} from 'vue';
import {Button, Select} from 'element-plus';
​
const app = createApp()
app.use(Button)
app.use(Select)
  1. 服务端渲染:SSR

如果 SPA 利用有首屏渲染慢的问题,能够思考SSR

以及上面的其余办法

  • 不要将所有的数据都放在 data 中,data中的数据都会减少 gettersetter,会收集对应的watcher
  • v-for 遍历为 item 增加 key
  • v-for 遍历防止同时应用 v-if
  • 辨别 computedwatch 的应用
  • 拆分组件(进步复用性、减少代码的可维护性, 缩小不必要的渲染)
  • 防抖、节流

用户体验

  • app-skeleton 骨架屏
  • pwa serviceworker

SEO 优化

  • 预渲染插件 prerender-spa-plugin
  • 服务端渲染 ssr

打包优化

  • Webpack 对图片进行压缩
  • 应用 cdn 的形式加载第三方模块
  • 多线程打包 happypack
  • splitChunks 抽离公共文件
  • 优化 SourceMap
  • 构建后果输入剖析,利用 webpack-bundle-analyzer 可视化剖析工具

根底的 Web 技术的优化

  • 服务端 gzip 压缩
  • 浏览器缓存
  • CDN 的应用
  • 应用 Chrome Performance 查找性能瓶颈

Vue 我的项目中你是如何解决跨域的呢

一、跨域是什么

跨域实质是浏览器基于 同源策略 的一种平安伎俩

同源策略(Sameoriginpolicy),是一种约定,它是浏览器最外围也最根本的平安性能

所谓同源(即指在同一个域)具备以下三个相同点

  • 协定雷同(protocol)
  • 主机雷同(host)
  • 端口雷同(port)

反之非同源申请,也就是协定、端口、主机其中一项不雷同的时候,这时候就会产生跨域

肯定要留神跨域是浏览器的限度,你用抓包工具抓取接口数据,是能够看到接口曾经把数据返回回来了,只是浏览器的限度,你获取不到数据。用 postman 申请接口可能申请到数据。这些再次印证了跨域是浏览器的限度。

如果让你从零开始写一个 vuex,说说你的思路

思路剖析

这个题目很有难度,首先思考 vuex 解决的问题:存储用户全局状态并提供治理状态 API。

  • vuex需要剖析
  • 如何实现这些需要

答复范例

  1. 官网说 vuex 是一个状态管理模式和库,并确保这些状态以可预期的形式变更。可见要实现一个vuex
  2. 要实现一个 Store 存储全局状态
  3. 要提供批改状态所需 API:commit(type, payload), dispatch(type, payload)
  4. 实现 Store 时,能够定义 Store 类,构造函数接管选项 options,设置属性state 对外裸露状态,提供 commitdispatch批改属性 state。这里须要设置state 为响应式对象,同时将 Store 定义为一个 Vue 插件
  5. commit(type, payload)办法中能够获取用户传入 mutations 并执行它,这样能够按用户提供的办法批改状态。dispatch(type, payload)相似,但须要留神它可能是异步的,须要返回一个 Promise 给用户以解决异步后果

实际

Store的实现:

class Store {constructor(options) {this.state = reactive(options.state)
        this.options = options
    }
    commit(type, payload) {this.options.mutations[type].call(this, this.state, payload)
    }
}

vuex 简易版

/**
 * 1 实现插件,挂载 $store
 * 2 实现 store
 */

let Vue;

class Store {constructor(options) {
    // state 响应式解决
    // 内部拜访:this.$store.state.***
    // 第一种写法
    // this.state = new Vue({
    //   data: options.state
    // })

    // 第二种写法:避免外界间接接触外部 vue 实例,避免内部强行变更
    this._vm = new Vue({
      data: {$$state: options.state}
    })

    this._mutations = options.mutations
    this._actions = options.actions
    this.getters = {}
    options.getters && this.handleGetters(options.getters)

    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)
  }

  get state () {return this._vm._data.$$state}

  set state (val) {return new Error('Please use replaceState to reset state')
  }

  handleGetters (getters) {Object.keys(getters).map(key => {
      Object.defineProperty(this.getters, key, {get: () => getters[key](this.state)
      })
    })
  }

  commit (type, payload) {let entry = this._mutations[type]
    if (!entry) {return new Error(`${type} is not defined`)
    }

    entry(this.state, payload)
  }

  dispatch (type, payload) {let entry = this._actions[type]
    if (!entry) {return new Error(`${type} is not defined`)
    }

    entry(this, payload)
  }
}

const install = (_Vue) => {
  Vue = _Vue

  Vue.mixin({beforeCreate () {if (this.$options.store) {Vue.prototype.$store = this.$options.store}
    },
  })
}


export default {Store, install}

验证形式

import Vue from 'vue'
import Vuex from './vuex'
// this.$store
Vue.use(Vuex)

export default new Vuex.Store({
  state: {counter: 0},
  mutations: {
    // state 从哪里来的
    add (state) {state.counter++}
  },
  getters: {doubleCounter (state) {return state.counter * 2}
  },
  actions: {add ({ commit}) {setTimeout(() => {commit('add')
      }, 1000)
    }
  },
  modules: {}})
正文完
 0