MVC

MVC 的思想:一句话描述就是 Controller 负责将 Model 的数据用 View 显示出来,换句话说就是在 Controller 里面把 Model 的数据赋值给 View。

MVVM

MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的自动同步,也就是当 Model 的属性改变时,我们不用再自己手动操作 Dom 元素,来改变 View 的显示,而是改变属性后该属性对应 View 层显示会自动改变(对应Vue数据驱动的思想)

注意:Vue 并没有完全遵循 MVVM 的思想 这一点官网自己也有说明

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。

严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了$refs 这个属性,让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM。

2.x

生命周期

Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载 Dom → 渲染、更新 → 渲染、销毁等一系列过程,称之为 Vue 的生命周期

生命周期 3.x 描述
beforeCreate setup 组件实例被创建之初,组件的属性生效之前,如 data 属性
created 组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用
beforeMount onBeforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用
mounted onMounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
beforeUpdate onBeforeUpdate 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前
updated onUpdated 组件数据更新之后
beforeDestory onBeforeUnmount 组件销毁前调用
destoryed onUnmounted 组件销毁后调用
activited keep-alive 专属,组件被激活时调用
deadctivated keep-alive 专属,组件被销毁时调用

组件生命周期调用顺序

  • 组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
  • 组件的销毁操作是先父后子,销毁完成的顺序是先子后父。 ```html
    {{ a }}
  1. <a name="93644237"></a>
  2. ### 生命周期示意图
  3. <br />![Vue-生命周期.png](https://cdn.nlark.com/yuque/0/2020/png/1544252/1594043201114-26c7bdce-ccc7-46ed-b6e1-d800c8e1cb06.png#crop=0&crop=0&crop=1&crop=1&height=760&id=NIrmX&margin=%5Bobject%20Object%5D&name=Vue-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.png&originHeight=3039&originWidth=1200&originalType=binary&ratio=1&rotation=0&showTitle=false&size=77677&status=done&style=none&title=&width=300)
  4. <a name="170e705c"></a>
  5. ### 通信
  6. | 方法 | 描述 |
  7. | --- | --- |
  8. | $parent/$children | 获取父子组件实例 |
  9. | props/$emit | 父组件通过 props 的方式向子组件传递数据,而通过$emit 子组件可以向父组件通信 |
  10. | eventBus | 通过 EventBus 进行信息的发布与订阅 |
  11. | Vuex | 是全局数据管理库,可以通过 vuex 管理全局的数据流 |
  12. | $attrs/$listeners | 跨级的组件通信 |
  13. | provide/inject | 以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效,这成为了跨组件通信的基础(封装组件库时很常用) |
  14. <a name="ILJEd"></a>
  15. ### 组件中的data是一个函数?
  16. - 一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。
  17. - 如果 `data` 是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间 `data` 不冲突,`data` 必须是一个函数。
  18. <a name="e79J4"></a>
  19. ### 子组件为什么不可以修改父组件传递的Prop?/怎么理解vue的单向数据流?
  20. - `Vue` 提倡单向数据流,即父级 `props` 的更新会流向子组件,但是反过来则不行。
  21. - 这是为了防止意外的改变父组件状态,使得应用的数据流变得难以理解。
  22. - 如果破坏了单向数据流,当应用复杂时,`debug` 的成本会非常高。
  23. <a name="VjmUP"></a>
  24. ### 状态 data vs 属性 props
  25. - 状态是组件自身的数据
  26. - 属性是来自父组件的数据
  27. - 状态的改变未必会触发更新
  28. - 属性的改变未必会触发更新
  29. <a name="8fWtG"></a>
  30. ### computed vs watch
  31. 很多时候页面会出现 `watch` 的滥用而导致一系列问题的产生,而通常更好的办法是使用 `computed` 属性,首先需要区别它们有什么区别:
  32. - `watch`:当监测的属性变化时会自动执行对应的回调函数
  33. - `computed`:计算的属性只有在它的相关依赖发生改变时才会重新求值
  34. ![](https://cdn.nlark.com/yuque/0/2021/jpeg/1544252/1624948824636-08d7be3c-9c76-43f2-80db-c020acb3955f.jpeg)<br />`computed` 监测的是依赖值,依赖值不变的情况下其会直接读取缓存进行复用,变化的情况下才会重新计算;而 `watch` 监测的是属性值, 只要属性值发生变化,其都会触发执行回调函数来执行一系列操作。
  35. `computed` 能做的,`watch` 都能做,反之则不行;能用 `computed` 的尽量用 `computed`
  36. <a name="s707x"></a>
  37. ### functional
  38. ```html
  39. <template>
  40. <div>
  41. {{ name }}
  42. <br />
  43. <button @click="handleChange">change name</button>
  44. <br />
  45. <!-- {{ slotDefault }} -->
  46. <VNodes :vnodes="slotDefault" />
  47. <br />
  48. <VNodes :vnodes="slotTitle" />
  49. <br />
  50. <VNodes :vnodes="slotScopeItem({ value: 'vue' })" />
  51. </div>
  52. </template>
  53. <script>
  54. export default {
  55. name: "BigProps",
  56. components: {
  57. VNodes: {
  58. functional: true,
  59. render: (h, ctx) => ctx.props.vnodes
  60. }
  61. },
  62. props: {
  63. name: String,
  64. onChange: {
  65. type: Function,
  66. default: () => {}
  67. },
  68. slotDefault: Array,
  69. slotTitle: Array,
  70. slotScopeItem: {
  71. type: Function,
  72. default: () => {}
  73. }
  74. },
  75. methods: {
  76. handleChange() {
  77. this.onChange("Hello vue!");
  78. }
  79. }
  80. };
  81. </script>

错误日志收集

可收集报错日志 vuex存放,上报到监控平台

  1. function isPromise(ret) {
  2. return ret && typeof ret.then === 'function' && typeof ret.catch === 'function';
  3. }
  4. const errorHandler = (error, vm, info) => {
  5. console.error('抛出全局异常');
  6. console.error(vm);
  7. console.error(error);
  8. console.error(info);
  9. };
  10. function registerActionHandle(actions) {
  11. Object.keys(actions).forEach(key => {
  12. let fn = actions[key];
  13. actions[key] = function(...args) {
  14. let ret = fn.apply(this, args);
  15. if (isPromise(ret)) {
  16. return ret.catch(errorHandler);
  17. } else {
  18. // 默认错误处理
  19. return ret;
  20. }
  21. };
  22. });
  23. }
  24. const registerVuex = instance => {
  25. if (instance.$options.store) {
  26. let actions = instance.$options.store._actions || {};
  27. if (actions) {
  28. let tempActions = {};
  29. Object.keys(actions).forEach(key => {
  30. tempActions[key] = actions[key][0];
  31. });
  32. registerActionHandle(tempActions);
  33. }
  34. }
  35. };
  36. const registerVue = instance => {
  37. if (instance.$options.methods) {
  38. let actions = instance.$options.methods || {};
  39. if (actions) {
  40. registerActionHandle(actions);
  41. }
  42. }
  43. };
  44. let VueError = {
  45. install: (Vue, options) => {
  46. /**
  47. * 全局异常处理
  48. * @param {*} error
  49. * @param {*} vm
  50. */
  51. console.log('VueErrorInstallSuc');
  52. Vue.config.errorHandler = errorHandler;
  53. Vue.mixin({
  54. beforeCreate() {
  55. registerVue(this);
  56. registerVuex(this);
  57. }
  58. });
  59. Vue.prototype.$throw = errorHandler;
  60. }
  61. };
  62. export default VueError;
  63. // TODO: use
  64. import ErrorPlugin from '@/utils/error';
  65. Vue.use(ErrorPlugin);

scoped css

<style> 标签存在 scoped 属性时,它的 CSS 只作用与当前组件中的元素

  1. <style scoped>
  2. .red {
  3. color: red;
  4. }
  5. </style>

实现原理则是通过 POSTCSS 来实现转换

  1. <template>
  2. <div class="red" data-v-0013a924>Hello</div>
  3. </template>
  4. <style>
  5. .red[data-v-0013a924] {
  6. color: red;
  7. }
  8. </style>

深度作用选择器

使用 >>> 操作符可以使 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件

  1. <style scoped>
  2. .red >>> a {
  3. color: red;
  4. }
  5. </style>

Sass 之类的预处理器无法正确解析 >>> 。这种情况下你可以使用 /deep/::v-deep 操作符取代

  1. <style lang="scss" scoped>
  2. .red {
  3. color: red;
  4. /deep/ a {
  5. color: blue;
  6. }
  7. ::v-deep a {
  8. color: yellow;
  9. }
  10. }
  11. </style>

module css

  1. <template>
  2. <div>
  3. <!-- 模板中通过 $style.xxx 访问 -->
  4. <span :class="$style.red">test</span>
  5. <span :class="{ [$style.red]: isRed }">test</span>
  6. <span :class="[$style.red, $style.bold]">test</span>
  7. </div>
  8. </template>
  9. <script>
  10. export default {
  11. data() {
  12. return {
  13. isRed: true,
  14. }
  15. },
  16. created() {
  17. // js 中访问
  18. console.log(this.$style.red)
  19. },
  20. }
  21. </script>
  22. <style lang="scss" module>
  23. .red {
  24. color: red;
  25. }
  26. .bold {
  27. font-weight: bold;
  28. }
  29. </style>

3.x

代码仓库:https://github.com/WuChenDi/Front-End/blob/master/05-Vue/vite-vue-ts

createApp

  1. import { createApp } from "vue";
  2. import App from "./App";
  3. import router from "./router";
  4. import { setupStore } from "./store";
  5. import VueHighcharts from "./directive/highcharts";
  6. async function bootstrap() {
  7. const app = createApp(App);
  8. app.use(router);
  9. setupStore(app);
  10. app.use(VueHighcharts);
  11. app.mount("#app", true);
  12. }
  13. void bootstrap();

emits 属性

  1. <template>
  2. <div>
  3. <p>{{ text }}</p>
  4. <button v-on:click="$emit('accepted')">OK</button>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. props: ['text'],
  10. emits: ['accepted']
  11. }
  12. </script>

多事件处理

  1. <!-- 这两个 one() 和 two() 将执行按钮点击事件 -->
  2. <button @click="one($event), two($event)"> Submit </button>

Fragment

之前组建的节点必须有一个根元素,Vue3 可以有多个根元素,也可以有把文本作为根元素

尽管 Fragment 看起来像一个普通的 DOM 元素,但它是虚拟的,根本不会在 DOM 树中呈现。这样我们可以将组件功能绑定到一个单一的元素中,而不需要创建一个多余的 DOM 节点。

目前你可以在 Vue2 中使用 vue-fragments 库来使用 Fragments,而在 Vue3 中,你将会在开箱即用!

  1. <template>
  2. <h1>{{ msg }}</h1>
  3. <ul>
  4. <li v-for="product in products" :key="product.id">{{ product.title }}</li>
  5. </ul>
  6. </template>

render(JSX/TSX)

可以使用空标签替代

  1. import { defineComponent, ref } from 'vue'
  2. import HelloWorldTSX from './components/HelloWorldTSX'
  3. import logo from './assets/logo.png'
  4. export default defineComponent({
  5. name: 'App',
  6. setup() {
  7. const menu = ref([
  8. { path: '/', name: 'index' },
  9. { path: '/LifeCycles', name: 'LifeCycles' },
  10. { path: '/Ref', name: 'Ref' },
  11. { path: '/RefTemplate', name: 'RefTemplate' },
  12. { path: '/ToRef', name: 'ToRef' },
  13. { path: '/ToRefs', name: 'ToRefs' },
  14. { path: '/watch', name: 'watch' },
  15. { path: '/watchEffect', name: 'watchEffect' },
  16. { path: '/chart', name: 'ChartDemo' }
  17. ])
  18. return () => (
  19. <>
  20. <img alt='Vue logo' src={logo} />
  21. <HelloWorldTSX msg='Hello Vue 3' onChange={e => console.log(e)} />
  22. <ul>
  23. {menu.value.map(i => (
  24. <li key={i.path}>
  25. <router-link to={i.path}>{i.name}</router-link>
  26. </li>
  27. ))}
  28. </ul>
  29. <router-view />
  30. </>
  31. )
  32. }
  33. })

移除 .sync 改为 v-model 参数

  1. <!-- vue 2.x -->
  2. <MyCompontent v-bind:title.sync="title" />
  3. <!-- vue 3.x -->
  4. <MyCompontent v-model:title="title" />

异步组件的引用方式

创建一个只有在需要时才会加载的异步组件

defineAsyncComponent

移除 filter

teleport

React 有个 Portalshttps://zh-hans.reactjs.org/docs/portals.html) 按照我的理解,这两个其实是类似的

Vue2 可以通过 portal-vue 库来实现(https://github.com/LinusBorg/portal-vue

Suspense

来自 React 生态的一个 idea(应该在 v16.6.x 就已发布使用),运用到 Vue3 中(试验性Suspense 会暂停你的组件渲染,并重现一个回落组件,直到满足一个条件 个人使用之后证明,Suspense 将只是一个具有插槽的组件

  1. <template>
  2. Home组件
  3. <Suspense>
  4. <template #default>
  5. <HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
  6. </template>
  7. <template #fallback>
  8. <div>Loading...</div>
  9. </template>
  10. </Suspense>
  11. </template>

Composition API

reactive

ref/toRef/toRefs

为何需要toRef 和 toRefs

初衷:不丢失响应式的情况,吧对象数据 分解/扩散 前提:针对是响应式对象(reactive 封装的)非普通对象 注意:不创造响应式,而是延续响应式

readonly

computed

watch/watchEffect

  • 两者都可监听 data 属性变化
  • watch 需要明确监听哪个属性
  • watchEffect 会根据其中的属性,自动监听其变化
  • watchEffect 初始化时,一定会执行一次, 主要是为了收集需要监听数据

    钩子函数声明周期

    编译优化的点(面试常问)

    https://vue-next-template-explorer.netlify.app

  • PatchFlag 静态标记

    • 编译模板时,动态节点做标记
    • 标记,分为不同的类型,如 TEXT/CLASS/PROPS
    • diff 算法时,可区分静态节点,以及不同类型的动态节点
  • hoistStatic 静态提升
    • 将静态节点的定义,提升到父作用域,缓存起来
    • 多个相邻的静态节点,会被合并起来
    • 典型的拿空间换时间的优化策略
  • cache Handler 缓存事件
  • SSR 优化
    • 静态节点直接输出,绕过 vdom
    • 动态节点动态渲染(以之前一致)
  • Tree-shaking 优化

    Composition API 与 React Hooks 区别对比(面试常问)

  • 前者 **setup** 只会调用一次,而后者函数会被多次调用

  • 前者不需要缓存数据(因为 setup 只会调用一次),后者需要手动调用函数进行缓存(useMemouseCallback
  • 前者不需要考虑调用顺序,而后者需要保证 hooks 执行的顺序
  • 前者 reactive + ref ,后者 useState 更难理解

    Vue Router

    vue路由hash模式和history模式实现原理分别是什么,他们的区别是什么?

  • hash 模式:

    • 后面 hash 值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面

    • 通过监听 **hashchange** 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。
  • history 模式:
    • history 模式的实现,主要是 HTML5 标准发布的两个 API,**pushState****replaceState**,这两个 API 可以在改变 url,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作
  • 区别

    • URL 展示上,hash 模式有“#”,history 模式没有
    • 刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由
    • 兼容性,hash 可以支持低版本浏览器和 IE。

      路由懒加载是什么意思?如何实现路由懒加载?

  • 路由懒加载的含义:把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件

  • 实现:结合 Vue 的异步组件和 Webpack 的代码分割功能 ```javascript // 1.可以将异步组件定义为返回一个 Promise 的工厂函数(该函数返回的 Promise 应该 resolve 组件本身) const Foo = () => Promise.resolve({ / 组件定义对象 / })

// 2.在 Webpack 中,我们可以使用动态 import语法来定义代码分块点 (split point) import(‘./Foo.vue’) // 返回 Promise

// 结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件

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

// 使用命名 chunk,和webpack中的魔法注释就可以把某个路由下的所有组件都打包在同个异步块 (chunk) 中

chunkconst Foo = () => import(/ webpackChunkName: “group-foo” / ‘./Foo.vue’)

  1. <a name="LERQo"></a>
  2. ### Vue-router 导航守卫有哪些
  3. - 全局前置/钩子:beforeEach、beforeResolve、afterEach
  4. - 路由独享的守卫:beforeEnter
  5. - 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
  6. ```javascript
  7. <script src="https://unpkg.com/vue/dist/vue.js"></script>
  8. <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  9. <div id="app">
  10. <h1>Hello App!</h1>
  11. <p>
  12. <!-- 使用 router-link 组件来导航. -->
  13. <!-- 通过传入 `to` 属性指定链接. -->
  14. <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
  15. <router-link to="/foo">Go to Foo</router-link>
  16. <router-link to="/bar">Go to Bar</router-link>
  17. </p>
  18. <!-- 路由出口 -->
  19. <!-- 路由匹配到的组件将渲染在这里 -->
  20. <router-view></router-view>
  21. </div>
  22. <script>
  23. // 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
  24. // 1. 定义 (路由) 组件。
  25. // 可以从其他文件 import 进来
  26. const Foo = { template: "<div>foo</div>" };
  27. const Bar = { template: "<div>bar</div>" };
  28. // 2. 定义路由
  29. // 每个路由应该映射一个组件。 其中"component" 可以是
  30. // 通过 Vue.extend() 创建的组件构造器,
  31. // 或者,只是一个组件配置对象。
  32. // 我们晚点再讨论嵌套路由。
  33. const routes = [
  34. { path: "/foo", component: Foo },
  35. { path: "/bar", component: Bar },
  36. ];
  37. // 3. 创建 router 实例,然后传 `routes` 配置
  38. // 你还可以传别的配置参数, 不过先这么简单着吧。
  39. const router = new VueRouter({
  40. routes,
  41. });
  42. // 4. 创建和挂载根实例。
  43. // 记得要通过 router 配置参数注入路由,
  44. // 从而让整个应用都有路由功能
  45. const app = new Vue({ router }).$mount("#app");
  46. </script>

动态路由匹配

  1. <script src="https://unpkg.com/vue/dist/vue.js"></script>
  2. <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  3. <div id="app">
  4. <p>
  5. <router-link to="/user/foo">/user/foo</router-link>
  6. <router-link to="/user/bar">/user/bar</router-link>
  7. </p>
  8. <router-view></router-view>
  9. </div>
  10. <script>
  11. const User = {
  12. template: `<div>User {{ $route.params.id }}</div>`,
  13. };
  14. const router = new VueRouter({
  15. routes: [{ path: "/user/:id", component: User }],
  16. });
  17. const app = new Vue({ router }).$mount("#app");
  18. </script>

嵌套路由

  1. <script src="https://unpkg.com/vue/dist/vue.js"></script>
  2. <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  3. <div id="app">
  4. <p>
  5. <router-link to="/user/foo">/user/foo</router-link>
  6. <router-link to="/user/foo/profile">/user/foo/profile</router-link>
  7. <router-link to="/user/foo/posts">/user/foo/posts</router-link>
  8. </p>
  9. <router-view></router-view>
  10. </div>
  11. <script>
  12. const User = {
  13. template: `
  14. <div class="user">
  15. <h2>User {{ $route.params.id }}</h2>
  16. <router-view></router-view>
  17. </div>
  18. `,
  19. };
  20. const UserHome = { template: "<div>Home</div>" };
  21. const UserProfile = { template: "<div>Profile</div>" };
  22. const UserPosts = { template: "<div>Posts</div>" };
  23. const router = new VueRouter({
  24. routes: [
  25. {
  26. path: "/user/:id",
  27. component: User,
  28. children: [
  29. // 当 /user/:id 匹配成功,
  30. // UserHome 会被渲染在 User 的 <router-view> 中
  31. { path: "", component: UserHome },
  32. // 当 /user/:id/profile 匹配成功,
  33. // UserProfile 会被渲染在 User 的 <router-view> 中
  34. { path: "profile", component: UserProfile },
  35. // 当 /user/:id/posts 匹配成功,
  36. // UserPosts 会被渲染在 User 的 <router-view> 中
  37. { path: "posts", component: UserPosts },
  38. ],
  39. },
  40. ],
  41. });
  42. const app = new Vue({ router }).$mount("#app");
  43. </script>

Vuex

Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。——Vuex官方文档

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。 | State | this.$store.state.xxx | mapState | 提供一个响应式数据 | 定义了应用状态的数据结构,可以在这里设置默认的初始状态。 | | —- | —- | —- | —- | —- | | Getter | this.$store.getters.xxx | mapGetters | 借助 Vue 的计算属性 computed 来实现缓存 | 允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。 | | Mutation | this.$store.commit(‘xxx’) | mapMutations | 更改 state 方法 | 是唯一更改 store 中状态的方法,且必须是同步函数。 | | Action | this.$stroe.dispatch(‘xxx’) | mapActions | 触发 mutation 方法 | 用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。 | | Module | | | Vue.set 动态添加 state 到响应式数据中 | 允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。 |

什么情况下使用 Vuex

不要为了用 vuex 而用 vuex

  • 如果应用够简单,最好不要使用 Vuex,一个简单的 store 模式即可
  • 需要构建一个中大型单页应用时,使用 Vuex 能更好地在组件外部管理状态

    Vuex和单纯的全局对象有什么区别?

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

  • 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

    为什么 Vuex 的 mutation 中不能做异步操作?

  • Vuex 中所有的状态更新的唯一途径都是 mutation,异步操作通过 Action 来提交 mutation 实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

  • 每个 mutation 执行完成后都会对应到一个新的状态变更,这样 devtools 就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation 支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

    Vuex 中的 action返回值

    一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

  • store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise

  • Action 通常是异步的,要知道 action 什么时候结束或者组合多个 action 以处理更加复杂的异步流程,可以通过定义 action 时返回一个promise 对象,就可以在派发 action 的时候就可以通过处理返回的 Promise 处理异步流程

    Vuex 日志

    ```javascript import Vue from ‘vue’; import Vuex from ‘vuex’; import createLogger from ‘vuex/dist/logger’;

const debug = process.env.NODE_ENV === ‘development’; Vue.use(Vuex);

const modulesFiles = require.context(‘./modules’, true, /.js$/); const modules = modulesFiles.keys().reduce((modules, modulePath) => { const moduleName = modulePath.replace(/^.\/(.*).\w+$/, ‘$1’); const value = modulesFiles(modulePath); modules[moduleName] = value.default; return modules; }, {});

export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules, plugins: debug ? [createLogger()] : [] });

  1. <a name="oMZHQ"></a>
  2. ### 持久化可以使用
  3. > vuex-persistedstate
  4. <a name="yOwx0"></a>
  5. ## 源码学习
  6. <a name="hyzjk"></a>
  7. ### Vue响应式数据/双向绑定原理
  8. > **2.x(**`**Object.defineProperty**`**),**缺点如下:
  9. > - 深度监听需要一次性递归
  10. > - 无法监听新增属性/删除属性(`Vue.set` / `Vue.delete`)
  11. > - 无法原生监听数组,需要特殊处理
  12. > - 可兼容到 `IE9`
  13. >
  14. **3.x(**`**Proxy**`**)**
  15. - `Vue` 数据双向绑定主要是指:**数据变化更新视图,视图变化更新数据**。其中,`View` 变化更新 `Data`,可以通过事件监听的方式来实现,所以 `Vue` 数据双向绑定的工作主要是如何**根据 **`**Data**`** 变化更新**`**View**`。
  16. - 默认 Vue 在初始化数据时,会给 `data` 中的属性使用 `Object.defineProperty` 重新定义所有属性,当页面取到对应属性时。会进行依赖收集(收集当前组件的 `watcher` ) 如果属性发生变化会通知相关依赖进行更新操作。
  17. - ![640.webp](https://cdn.nlark.com/yuque/0/2020/webp/1544252/1604715906583-bf59d19b-3e81-4a71-9686-40e8a2a716f8.webp#crop=0&crop=0&crop=1&crop=1&height=400&id=AzJ9A&margin=%5Bobject%20Object%5D&name=640.webp&originHeight=400&originWidth=640&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8924&status=done&style=none&title=&width=640)
  18. - 深入理解
  19. - **监听器 Observer**:对数据对象进行遍历,包括子属性对象的属性,利用 `Object.defineProperty()` 对属性都加上 `setter` 和 `getter`。这样的话,给这个对象的某个值赋值,就会触发 `setter`,那么就能监听到了数据变化。
  20. - **解析器 Compile**:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
  21. - **订阅者 Watcher**:Watcher 订阅者是 `Observer` 和 `Compile` 之间通信的桥梁 ,主要的任务是订阅 `Observer` 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 `Compile` 中对应的更新函数。每个组件实例都有相应的 `watcher` 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 `setter` 被调用时,会通知 `watcher` 重新计算,从而致使它关联的组件得以更新——这是一个典型的观察者模式
  22. - **订阅器 Dep**:订阅器采用 发布-订阅 设计模式,用来收集订阅者 `Watcher`,对监听器 `Observer` 和 订阅者 `Watcher` 进行统一管理。
  23. <a name="XzWQy"></a>
  24. #### Proxy替代Object.defineProperty
  25. - `Proxy` 只会代理对象的第一层,`Vue3` 是怎样处理这个问题的呢?
  26. - 判断当前 `Reflect.get` 的返回值是否为 `Object`,如果是则再通过 `reactive` 方法做代理, 这样就实现了深度观测。
  27. - 监测数组的时候可能触发多次 `get/set`,那么如何防止触发多次呢?我们可以判断 `key` 是否为当前被代理对象 `target` 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 `trigger`。
  28. - 优势
  29. - 直接监听对象而非属性;
  30. - 直接监听数组的变化
  31. - `Proxy` 有多达 13 种拦截方法,不限于 `apply`、`ownKeys`、`deleteProperty`、`has` 等等是 `Object.defineProperty` 不具备的;
  32. - `Proxy` 返回的是一个新对象,我们可以只操作新的对象达到目的,而 `Object.defineProperty` 只能遍历对象属性直接修改;
  33. - `Proxy` 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
  34. ```javascript
  35. {
  36. {
  37. // 2.0(Object.defineProperty)
  38. let definedObj = {};
  39. let age;
  40. Object.defineProperty(definedObj, "age", {
  41. get: function () {
  42. console.log("For age");
  43. return age;
  44. },
  45. set: function (newVal) {
  46. console.log("Set the age");
  47. age = newVal;
  48. },
  49. });
  50. definedObj.age = 24;
  51. console.log(definedObj.age);
  52. }
  53. {
  54. // 3.0(Proxy)
  55. let obj = { a: 1 };
  56. let proxyObj = new Proxy(obj, {
  57. get: function (target, prop) {
  58. return prop in target ? target[prop] : 0;
  59. },
  60. set: function (target, prop, value) {
  61. target[prop] = 0530;
  62. },
  63. });
  64. console.log(proxyObj.a); // 1
  65. console.log(proxyObj.b); // 0
  66. proxyObj.a = 0353;
  67. console.log(proxyObj.a); // 0530
  68. }
  69. }

检测数组

  • 使用函数劫持的方式,重写了数组的方法
  • Vue 将 data 中的数组,进行了原型链重写。指向了自己定义的数组原型方法,这样当调用数组 api 时,可以通知依赖更新.如果数组中包含着引用类型。会对数组中的引用类型再次进行监控。 ```javascript var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto);

var methodsToPatch = [ “push”, “pop”, “shift”, “unshift”, “splice”, “sort”, “reverse”, ];

// 重写原型方法 methodsToPatch.forEach(function (method) { // 调用原数组的方法 var original = arrayProto[method]; def(arrayMethods, method, function mutator() { var args = [], len = arguments.length; while (len—) args[len] = arguments[len];

  1. var result = original.apply(this, args);
  2. var ob = this.__ob__;
  3. var inserted;
  4. switch (method) {
  5. case "push":
  6. case "unshift":
  7. inserted = args;
  8. break;
  9. case "splice":
  10. inserted = args.slice(2);
  11. break;
  12. }
  13. if (inserted) {
  14. ob.observeArray(inserted);
  15. }
  16. // notify change
  17. ob.dep.notify(); // 当调用数组方法后,手动通知视图更新
  18. return result;
  19. });

});

// 进行深度监听 this.observeArray(value);

  1. <a name="u2wG2"></a>
  2. ### Vue**异步渲染**
  3. 因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染。<br />所以为了性能考虑,Vue会在本轮数据更新后,再去异步更新视图。
  4. ```javascript
  5. function update() {
  6. /* istanbul ignore else */
  7. if (this.lazy) {
  8. this.dirty = true;
  9. } else if (this.sync) {
  10. this.run();
  11. } else {
  12. // 当数据发生变化时会将watcher放到一个队列中批量更新
  13. queueWatcher(this);
  14. }
  15. }
  16. export function queueWatcher(watcher) {
  17. // 会对相同的watcher进行过滤
  18. var id = watcher.id;
  19. if (has[id] == null) {
  20. has[id] = true;
  21. if (!flushing) {
  22. queue.push(watcher);
  23. } else {
  24. var i = queue.length - 1;
  25. while (i > index && queue[i].id > watcher.id) {
  26. i--;
  27. }
  28. queue.splice(i + 1, 0, watcher);
  29. }
  30. // queue the flush
  31. if (!waiting) {
  32. waiting = true;
  33. if (!config.async) {
  34. flushSchedulerQueue();
  35. return;
  36. }
  37. // 调用nextTick方法 批量的进行更新
  38. nextTick(flushSchedulerQueue);
  39. }
  40. }
  41. }

nextTick实现原理

nextTick 方法主要是使用了宏任务微任务,定义了一个异步方法。多次调用 nextTick 会将方法存入
队列中,通过这个异步方法清空当前队列。 所以这个 nextTick 方法就是异步方法

  1. var timerFunc;
  2. // promise
  3. if (typeof Promise !== "undefined" && isNative(Promise)) {
  4. var p = Promise.resolve();
  5. timerFunc = function () {
  6. p.then(flushCallbacks);
  7. if (isIOS) {
  8. setTimeout(noop);
  9. }
  10. };
  11. isUsingMicroTask = true;
  12. } else if (
  13. !isIE &&
  14. typeof MutationObserver !== "undefined" &&
  15. (isNative(MutationObserver) ||
  16. MutationObserver.toString() === "[object MutationObserverConstructor]")
  17. ) {
  18. var counter = 1;
  19. var observer = new MutationObserver(flushCallbacks);
  20. var textNode = document.createTextNode(String(counter));
  21. observer.observe(textNode, {
  22. characterData: true,
  23. });
  24. timerFunc = function () {
  25. counter = (counter + 1) % 2;
  26. textNode.data = String(counter);
  27. };
  28. isUsingMicroTask = true;
  29. } else if (typeof setImmediate !== "undefined" && isNative(setImmediate)) {
  30. timerFunc = function () {
  31. setImmediate(flushCallbacks);
  32. };
  33. } else {
  34. timerFunc = function () {
  35. setTimeout(flushCallbacks, 0);
  36. };
  37. }
  38. // nextTick实现
  39. export function nextTick(cb?: Function, ctx?: Object) {
  40. let _resolve;
  41. callbacks.push(() => {
  42. if (cb) {
  43. try {
  44. cb.call(ctx);
  45. } catch (e) {
  46. handleError(e, ctx, "nextTick");
  47. }
  48. } else if (_resolve) {
  49. _resolve(ctx);
  50. }
  51. });
  52. if (!pending) {
  53. pending = true;
  54. timerFunc();
  55. }
  56. }

Computed特点

默认 computed 也是一个 watcher 是具备缓存的,只要当依赖的属性发生变化时才会更新视图

  1. function initComputed(vm: Component, computed: Object) {
  2. var watchers = (vm._computedWatchers = Object.create(null));
  3. var isSSR = isServerRendering();
  4. for (var key in computed) {
  5. var userDef = computed[key];
  6. var getter = typeof userDef === "function" ? userDef : userDef.get;
  7. if (getter == null) {
  8. warn('Getter is missing for computed property "' + key + '".', vm);
  9. }
  10. if (!isSSR) {
  11. // create internal watcher for the computed property.
  12. watchers[key] = new Watcher(
  13. vm,
  14. getter || noop,
  15. noop,
  16. computedWatcherOptions
  17. );
  18. }
  19. // component-defined computed properties are already defined on the
  20. // component prototype. We only need to define computed properties defined
  21. // at instantiation here.
  22. if (!(key in vm)) {
  23. defineComputed(vm, key, userDef);
  24. } else {
  25. if (key in vm.$data) {
  26. warn('The computed property "' + key + '" is already defined in data.', vm);
  27. } else if (vm.$options.props && key in vm.$options.props) {
  28. warn('The computed property "' + key + '" is already defined as a prop.', vm);
  29. }
  30. }
  31. }
  32. }
  33. function createComputedGetter(key) {
  34. return function computedGetter() {
  35. var watcher = this._computedWatchers && this._computedWatchers[key];
  36. if (watcher) {
  37. // 如果依赖的值没发生变化,就不会重新求值
  38. if (watcher.dirty) {
  39. watcher.evaluate();
  40. }
  41. if (Dep.target) {
  42. watcher.depend();
  43. }
  44. return watcher.value;
  45. }
  46. };
  47. }

watch 中 deep:true 实现

当用户指定了 watch 中的deep属性为 true 时,如果当前监控的值是数组类型。会对对象中的每
一项进行求值,此时会将当前 watcher 存入到对应属性的依赖中,这样数组中对象发生变化时也
会通知数据更新。

Vue源码-发现函数

数据类型判断

Object.prototype.toString.call() 返回的数据格式为 [object Object] 类型,然后用slice截取第8位到倒一位,得到结果为 Object

  1. var _toString = Object.prototype.toString;
  2. function toRawType(value) {
  3. return _toString.call(value).slice(8, -1)
  4. }
  5. console.log(toRawType({})); // Object
  6. console.log(toRawType([])); // Array
  7. console.log(toRawType(true)); // Boolean
  8. console.log(toRawType(undefined)); // Undefined
  9. console.log(toRawType(null)); // Null
  10. console.log(toRawType(() => { })); // Function

利用闭包构造map缓存数据

vue中判断我们写的组件名是不是html内置标签的时候,如果用数组类遍历那么将要循环很多次获取结果,如果把数组转为对象,把标签名设置为对象的key,那么不用依次遍历查找,只需要查找一次就能获取结果,提高了查找效率。

  1. function makeMap(str, expectsLowerCase) {
  2. var map = Object.create(null);
  3. var list = str.split(",");
  4. for (var i = 0; i < list.length; i++) {
  5. map[list[i]] = true;
  6. }
  7. return expectsLowerCase
  8. ? function (val) { return map[val.toLowerCase()]; }
  9. : function (val) { return map[val]; }
  10. }
  11. // 利用闭包,每次判断是否是内置标签只需调用isHTMLTag
  12. var isHTMLTag = makeMap('html,body,base,head,link,meta,style,title')
  13. console.log('res', isHTMLTag('body')) // true

二维数组扁平化

vue中_createElement格式化传入的children的时候用到了simpleNormalizeChildren函数,原来是为了拍平数组,使二维数组扁平化,类似lodash中的flatten方法。

  1. // lodash flatten
  2. console.log(_.flatten([1, [2, [3, [4]], 5]])); // [1, 2, [3, [4]], 5]
  3. // vue中
  4. function simpleNormalizeChildren(children) {
  5. for (var i = 0; i < children.length; i++) {
  6. if (Array.isArray(children[i])) {
  7. return Array.prototype.concat.apply([], children);
  8. }
  9. }
  10. return children;
  11. }
  12. // es6
  13. function simpleNormalizeChildren(children) {
  14. return [].concat(...children);
  15. }

方法拦截

vue中利用Object.defineProperty收集依赖,从而触发更新视图,但是数组却无法监测到数据的变化,但是为什么数组在使用push pop等方法的时候可以触发页面更新呢,那是因为vue内部拦截了这些方法。

  1. // 重写数组方法,然后再把原型指回原方法
  2. var methodsToPatch = [
  3. "push",
  4. "pop",
  5. "shift",
  6. "unshift",
  7. "reverse",
  8. "sort",
  9. "splice",
  10. ];
  11. var arrayMethods = Object.create(Array.prototype);
  12. methodsToPatch.forEach((method) => {
  13. arrayMethods[method] = function () {
  14. // 拦截方法
  15. console.log(`调用的是拦截的 ${method} 方法,进行依赖收集`);
  16. return Array.prototype[method].apply(this, arguments);
  17. };
  18. });
  19. var arr = [1,2,3];
  20. arr.__proto__ = arrayMethods;
  21. arr.push(4); // 调用的是拦截的 push 方法,进行依赖收集

继承的实现

vue中调用Vue.extend实例化组件,Vue.extend就是VueComponent构造函数,而VueComponent利用Object.create继承Vue,所以在平常开发中Vue 和 Vue.extend区别不是很大 es5原生方法实现继承的,es6中 class类直接用extends继承

  1. // ...

执行一次

闭包

  1. function once(fn) {
  2. var called = false;
  3. return function () {
  4. if (!called) {
  5. called = true;
  6. fn.apply(this, arguments);
  7. }
  8. };
  9. }

浅拷贝

简单的深拷贝我们可以用 JSON.stringify() 来实现。 vue源码中的looseEqual 浅拷贝思路,先类型判断再递归调用

  1. function isObject (obj) {
  2. return obj !== null && typeof obj === "object";
  3. }
  4. function looseEqual(a, b) {
  5. if (a === b) {
  6. return true;
  7. }
  8. var isObjectA = isObject(a);
  9. var isObjectB = isObject(b);
  10. if (isObjectA && isObjectB) {
  11. try {
  12. var isArrayA = Array.isArray(a);
  13. var isArrayB = Array.isArray(b);
  14. if (isArrayA && isArrayB) {
  15. return a.length === b.length && a.every(function (e, i) {
  16. return looseEqual(e, b[i]);
  17. })
  18. } else if (a instanceof Date && b instanceof Date) {
  19. return a.getTime() === b.getTime();
  20. } else if (!isArrayA && !isArrayB) {
  21. var keysA = Object.keys(a);
  22. var keysB = Object.keys(b);
  23. return keysA.length === keysB.length && keysA.every(function (key) {
  24. return looseEqual(a[key], b[key]);
  25. })
  26. } else {
  27. return false;
  28. }
  29. } catch (e) {
  30. return false;
  31. }
  32. } else if (!isObjectA && !isObjectB) {
  33. return String(a) === String(b);
  34. } else {
  35. return false;
  36. }
  37. }

Vue的性能优化

编码阶段

SEO优化

打包优化

用户体验

Vue CLI

使用cdn优化打包

vue.config.js

  1. const CompressionWebpackPlugin = require("compression-webpack-plugin");
  2. const productionGzipExtensions = ["js", "css"];
  3. const isProd = process.env.NODE_ENV === "production";
  4. const assetsCDN = {
  5. // webpack build externals
  6. externals: {
  7. vue: "Vue",
  8. "vue-router": "VueRouter",
  9. vuex: "Vuex",
  10. axios: "axios",
  11. nprogress: "NProgress",
  12. clipboard: "ClipboardJS",
  13. "js-cookie": "Cookies",
  14. },
  15. css: [
  16. "//cdn.jsdelivr.net/npm/ant-design-vue@1.6.5/dist/antd.css",
  17. "//cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.css"
  18. ],
  19. js: [
  20. "//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js",
  21. "//cdn.jsdelivr.net/npm/ant-design-vue@1.6.5/dist/antd.min.js",
  22. "//cdn.jsdelivr.net/npm/vue-router@3.3.4/dist/vue-router.min.js",
  23. "//cdn.jsdelivr.net/npm/vuex@3.5.1/dist/vuex.min.js",
  24. "//cdn.jsdelivr.net/npm/axios@0.20.0/dist/axios.min.js",
  25. "//cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js",
  26. "//cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js",
  27. "//cdn.jsdelivr.net/npm/js-cookie@2.2.1/src/js.cookie.min.js",
  28. "//cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.js",
  29. ],
  30. };
  31. module.exports = {
  32. lintOnSave: false, // 是否开启eslint保存检测
  33. productionSourceMap: isProd, // 是否生成sourcemap文件,生成环境不生成以加速生产环境构建
  34. assetsDir: "static",
  35. publicPath: isProd ? "/dd/" : "/",
  36. outputDir: "dist",
  37. configureWebpack: (config) => {
  38. // 生产环境下将资源压缩成gzip格式
  39. if (isProd) {
  40. // add `CompressionWebpack` plugin to webpack plugins
  41. config.plugins.push(
  42. new CompressionWebpackPlugin({
  43. algorithm: "gzip",
  44. test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"),
  45. threshold: 10240,
  46. minRatio: 0.8,
  47. })
  48. );
  49. }
  50. // if prod, add externals
  51. if (isProd) {
  52. config.externals = assetsCDN.externals;
  53. // delete console
  54. config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true;
  55. // delete console.log
  56. // config.optimization.minimizer[0].options.terserOptions.compress.pure_funcs = ["console.log"];
  57. }
  58. },
  59. chainWebpack: (config) => {
  60. // 生产环境下使用CDN
  61. if (isProd) {
  62. config.plugin("html").tap((args) => {
  63. args[0].cdn = assetsCDN;
  64. return args;
  65. });
  66. }
  67. },
  68. };

index.html

  1. <!DOCTYPE html>
  2. <html lang="en" class="beauty-scroll">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width,initial-scale=1.0">
  7. <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  8. <title><%= process.env.VUE_APP_NAME %></title>
  9. <!-- require cdn assets css -->
  10. <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
  11. <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
  12. <% } %>
  13. </head>
  14. <body class="beauty-scroll">
  15. <noscript>
  16. <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
  17. </noscript>
  18. <div id="app"></div>
  19. <!-- require cdn assets js -->
  20. <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
  21. <script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
  22. <% } %>
  23. <!-- built files will be auto injected -->
  24. </body>
  25. </html>

开启 Gzip 压缩

  1. /* vue.config.js */
  2. const isProd = process.env.NODE_ENV === "production";
  3. module.exports = {
  4. // ...
  5. configureWebpack: config => {
  6. if (isProd) {
  7. config.plugins.push(
  8. new CompressionWebpackPlugin({
  9. // 目标文件名称。[path] 被替换为原始文件的路径和 [query] 查询
  10. asset: "[path].gz[query]",
  11. algorithm: "gzip",
  12. // 处理与此正则相匹配的所有文件
  13. test: new RegExp("\\.(js|css)$"),
  14. // 只处理大于此大小的文件
  15. threshold: 10240,
  16. // 最小压缩比达到 0.8 时才会被压缩
  17. minRatio: 0.8,
  18. })
  19. )
  20. }
  21. }
  22. }

去掉debugger和console

  1. /* vue.config.js */
  2. const isProd = process.env.NODE_ENV === "production";
  3. module.exports = {
  4. // ...
  5. configureWebpack: config => {
  6. if (isProd) {
  7. config.optimization.minimizer[0].options.terserOptions.compress.warnings = false;
  8. config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true;
  9. config.optimization.minimizer[0].options.terserOptions.compress.drop_debugger = true;
  10. config.optimization.minimizer[0].options.terserOptions.compress.pure_funcs = ["console.log"];
  11. }
  12. }
  13. }

limit (244 KiB)

  1. /* vue.config.js */
  2. module.exports = {
  3. // ...
  4. configureWebpack: config => {
  5. // TODO: Webpack - WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB)
  6. config.performance = {
  7. // maxEntrypointSize: 1024 * 400,
  8. maxAssetSize: 1024 * 400,
  9. assetFilter: function(assetFilename) {
  10. return assetFilename.endsWith('.js');
  11. }
  12. };
  13. }
  14. }

vue项目构建调整内存大小

参考地址:https://stackoverflow.com/questions/55258355/vue-clis-type-checking-service-ignores-memory-limits 尝试过这种:https://support.snyk.io/hc/en-us/articles/360002046418-JavaScript-heap-out-of-memory,但是没有效果,永远都是 2048MB, 应该程序有覆盖这个值的情况出现

背景

项目过大遇到打包栈异常情况
image.png

默认情况

内存为 2048 MB
image.png

调整后

设置 12288 MB(可根据机器自行配置)
image.png

coding

  1. /* vue.config.js */
  2. const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
  3. module.exports = {
  4. // ...
  5. configureWebpack: config => {
  6. const existingForkTsChecker = config.plugins.filter(p => p instanceof ForkTsCheckerWebpackPlugin)[0];
  7. // remove the existing ForkTsCheckerWebpackPlugin
  8. // so that we can replace it with our modified version
  9. config.plugins = config.plugins.filter(p => !(p instanceof ForkTsCheckerWebpackPlugin));
  10. // copy the options from the original ForkTsCheckerWebpackPlugin
  11. // instance and add the memoryLimit property
  12. const forkTsCheckerOptions = existingForkTsChecker.options;
  13. forkTsCheckerOptions.memoryLimit = 12288;
  14. config.plugins.push(new ForkTsCheckerWebpackPlugin(forkTsCheckerOptions));
  15. }
  16. }

按照模块大小自动分割第三方库

  1. /* vue.config.js */
  2. const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
  3. module.exports = {
  4. // ...
  5. configureWebpack: config => {
  6. // 按照模块大小自动分割第三方库
  7. config.optimization.splitChunks = {
  8. maxInitialRequests: Infinity,
  9. minSize: 300 * 1024,
  10. /**
  11. * initial 入口 chunk, 对于异步导入的文件不处理
  12. * async 异步 chunk, 只对异步导入的文件处理
  13. * all 全部 chunk
  14. */
  15. chunks: 'all',
  16. // 缓存分组
  17. cacheGroups: {
  18. vendor: {
  19. test: /[\\/]node_modules[\\/]/,
  20. name(module) {
  21. const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
  22. return `npm.${packageName.replace('@', '')}`;
  23. }
  24. }
  25. }
  26. };
  27. }
  28. }

Vite

ES6 module

为什么快?

开发环境使用 ES6 Module,无需打包,非常快
生产环境使用 rollup,并不会快很多