乐趣区

关于vue.js:你可能需要的vue相关考点汇总

组件中写 name 属性的益处

能够标识组件的具体名称不便调试和查找对应属性

// 源码地位 src/core/global-api/extend.js

// enable recursive self-lookup
if (name) {Sub.options.components[name] = Sub // 记录本人 在组件中递归本人  -> jsx
}

Vuex 中 actions 和 mutations 有什么区别

题目剖析

  • mutationsactionsvuex带来的两个独特的概念。老手程序员容易混同,所以面试官喜爱问。
  • 咱们只需记住批改状态只能是 mutationsactions 只能通过提交 mutation 批改状态即可

答复范例

  1. 更改 Vuexstore 中的状态的惟一办法是提交 mutationmutation 十分相似于事件:每个 mutation 都有一个字符串的类型 (type)和一个 回调函数 (handler)。Action 相似于 mutation,不同在于:Action能够蕴含任意异步操作,但它不能批改状态,须要提交 mutation 能力变更状态
  2. 开发时,蕴含异步操作或者简单业务组合时应用 action;须要间接批改状态则提交mutation。但因为dispatchcommit是两个 API,容易引起混同,实际中也会采纳对立应用dispatch action 的形式。调用 dispatchcommit两个 API 时简直齐全一样,然而定义两者时却不甚雷同,mutation的回调函数接管参数是 state 对象。action则是与 Store 实例具备雷同办法和属性的上下文 context 对象,因而个别会解构它为 {commit, dispatch, state},从而不便编码。另外dispatch 会返回 Promise 实例便于解决外部异步后果
  3. 实现上 commit(type) 办法相当于调用 options.mutations[type](state)dispatch(type) 办法相当于调用options.actions[type](store),这样就很容易了解两者应用上的不同了

实现

咱们能够像上面这样简略实现 commitdispatch,从而分别两者不同

class Store {constructor(options) {this.state = reactive(options.state)
        this.options = options
    }
    commit(type, payload) {
        // 传入上下文和参数 1 都是 state 对象
        this.options.mutations[type].call(this.state, this.state, payload)
    }
    dispatch(type, payload) {
        // 传入上下文和参数 1 都是 store 自身
        this.options.actions[type].call(this, this, payload)
    }
}

异步组件是什么?应用场景有哪些?

剖析

因为异步路由的存在,咱们应用异步组件的次数比拟少,因而还是有必要两者的不同。

体验

大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们

import {defineAsyncComponent} from 'vue'
// defineAsyncComponent 定义异步组件,返回一个包装组件。包装组件依据加载器的状态决定渲染什么内容
const AsyncComp = defineAsyncComponent(() => {
  // 加载函数返回 Promise
  return new Promise((resolve, reject) => {
    // ... 能够从服务器加载组件
    resolve(/* loaded component */)
  })
})
// 借助打包工具实现 ES 模块动静导入
const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

答复范例

  1. 在大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们。
  2. 咱们不仅能够在路由切换时懒加载组件,还能够在页面组件中持续应用异步组件,从而实现更细的宰割粒度。
  3. 应用异步组件最简略的形式是间接给 defineAsyncComponent 指定一个 loader 函数,联合 ES 模块动静导入函数 import 能够疾速实现。咱们甚至能够指定 loadingComponenterrorComponent选项从而给用户一个很好的加载反馈。另外 Vue3 中还能够联合 Suspense 组件应用异步组件。
  4. 异步组件容易和路由懒加载混同,实际上不是一个货色。异步组件不能被用于定义懒加载路由上,解决它的是 vue 框架,解决路由组件加载的是vue-router。然而能够在懒加载的路由组件中应用异步组件

Vue 路由的钩子函数

首页能够管制导航跳转,beforeEachafterEach等,个别用于页面 title 的批改。一些须要登录能力调整页面的重定向性能。

  • beforeEach次要有 3 个参数tofromnext
  • toroute行将进入的指标路由对象。
  • fromroute以后导航正要来到的路由。
  • nextfunction肯定要调用该办法 resolve 这个钩子。执行成果依赖 n ext办法的调用参数。能够管制网页的跳转

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

Vue3 的设计指标是什么?做了哪些优化

1、设计指标

不以解决理论业务痛点的更新都是耍流氓,上面咱们来列举一下 Vue3 之前咱们或者会面临的问题

  • 随着性能的增长,简单组件的代码变得越来越难以保护
  • 短少一种比拟「洁净」的在多个组件之间提取和复用逻辑的机制
  • 类型推断不够敌对
  • bundle的工夫太久了

Vue3 通过长达两三年工夫的筹备,做了哪些事件?

咱们从后果反推

  • 更小
  • 更快
  • TypeScript 反对
  • API 设计一致性
  • 进步本身可维护性
  • 凋谢更多底层性能

一句话概述,就是更小更快更敌对了

更小

  • Vue3移除一些不罕用的 API
  • 引入tree-shaking,能够将无用模块“剪辑”,仅打包须要的,使打包的整体体积变小了

更快

次要体现在编译方面:

  • diff算法优化
  • 动态晋升
  • 事件监听缓存
  • SSR优化

更敌对

vue3在兼顾 vue2options API的同时还推出了composition API,大大增加了代码的逻辑组织和代码复用能力

这里代码简略演示下:

存在一个获取鼠标地位的函数

import {toRefs, reactive} from 'vue';
function useMouse(){const state = reactive({x:0,y:0});
    const update = e=>{
        state.x = e.pageX;
        state.y = e.pageY;
    }
    onMounted(()=>{window.addEventListener('mousemove',update);
    })
    onUnmounted(()=>{window.removeEventListener('mousemove',update);
    })

    return toRefs(state);
}

咱们只须要调用这个函数,即可获取 xy 的坐标,齐全不必关注实现过程

试想一下,如果很多相似的第三方库,咱们只须要调用即可,不用关注实现过程,开发效率大大提高

同时,VUE3是基于 typescipt 编写的,能够享受到主动的类型定义提醒

2、优化计划

vue3从很多层面都做了优化,能够分成三个方面:

  • 源码
  • 性能
  • 语法 API

源码

源码能够从两个层面开展:

  • 源码治理
  • TypeScript

源码治理

vue3整个源码是通过 monorepo的形式保护的,依据性能将不同的模块拆分到 packages 目录上面不同的子目录中

这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易浏览、了解和更改所有模块源码,进步代码的可维护性

另外一些 package(比方 reactivity 响应式库)是能够独立于 Vue 应用的,这样用户如果只想应用 Vue3的响应式能力,能够独自依赖这个响应式库而不必去依赖整个 Vue

TypeScript

Vue3是基于 typeScript 编写的,提供了更好的类型查看,能反对简单的类型推导

性能

vue3是从什么哪些方面对性能进行进一步优化呢?

  • 体积优化
  • 编译优化
  • 数据劫持优化

这里讲述数据劫持:

vue2 中,数据劫持是通过Object.defineProperty,这个 API 有一些缺点,并不能检测对象属性的增加和删除

Object.defineProperty(data, 'a',{get(){// track},
  set(){// trigger}
})

只管 Vue 为了解决这个问题提供了 setdelete 实例办法,然而对于用户来说,还是减少了肯定的心智累赘

同时在面对嵌套层级比拟深的状况下,就存在性能问题

default {
  data: {
    a: {
      b: {
          c: {d: 1}
      }
    }
  }
}

相比之下,vue3是通过 proxy 监听整个对象,那么对于删除还是监听当然也能监听到

同时Proxy 并不能监听到外部深层次的对象变动,而 Vue3 的解决形式是在getter 中去递归响应式,这样的益处是真正拜访到的外部对象才会变成响应式,而不是无脑递归

语法 API

这里当然说的就是composition API,其两大显著的优化:

  • 优化逻辑组织
  • 优化逻辑复用

逻辑组织

一张图,咱们能够很直观地感触到 Composition API在逻辑组织方面的劣势

雷同性能的代码编写在一块,而不像 options API 那样,各个性能的代码混成一块

逻辑复用

vue2 中,咱们是通过 mixin 实现性能混合,如果多个 mixin 混合,会存在两个非常明显的问题:命名抵触和数据起源不清晰

而通过 composition 这种模式,能够将一些复用的代码抽离进去作为一个函数,只有的应用的中央间接进行调用即可

同样是上文的获取鼠标地位的例子

import {toRefs, reactive, onUnmounted, onMounted} from 'vue';
function useMouse(){const state = reactive({x:0,y:0});
    const update = e=>{
        state.x = e.pageX;
        state.y = e.pageY;
    }
    onMounted(()=>{window.addEventListener('mousemove',update);
    })
    onUnmounted(()=>{window.removeEventListener('mousemove',update);
    })

    return toRefs(state);
}

组件应用

import useMousePosition from './mouse'
export default {setup() {const { x, y} = useMousePosition()
        return {x, y}
    }
}

能够看到,整个数据起源清晰了,即便去编写更多的 hook 函数,也不会呈现命名抵触的问题

v-if 和 v -show 区别

  • v-show暗藏则是为该元素增加 css--display:nonedom 元素仍旧还在。v-if显示暗藏是将 dom 元素整个增加或删除
  • 编译过程:v-if切换有一个部分编译 / 卸载的过程,切换过程中适合地销毁和重建外部的事件监听和子组件;v-show只是简略的基于 css 切换
  • 编译条件:v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染
  • v-showfalse 变为 true 的时候不会触发组件的生命周期
  • v-iffalse 变为 true 的时候,触发组件的 beforeCreatecreatebeforeMountmounted 钩子,由 true 变为 false 的时候触发组件的 beforeDestorydestoryed 办法
  • 性能耗费:v-if有更高的切换耗费;v-show有更高的初始渲染耗费

v-show 与 v -if 的应用场景

  • v-ifv-show 都能管制 dom 元素在页面的显示
  • v-if 相比 v-show 开销更大的(间接操作 dom 节 点减少与删除)
  • 如果须要十分频繁地切换,则应用 v-show 较好
  • 如果在运行时条件很少扭转,则应用 v-if 较好

v-show 与 v -if 原理剖析

  1. v-show原理

不论初始条件是什么,元素总是会被渲染

咱们看一下在 vue 中是如何实现的

代码很好了解,有 transition 就执行 transition,没有就间接设置display 属性

// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vShow.ts
export const vShow: ObjectDirective<VShowElement> = {beforeMount(el, { value}, {transition}) {
    el._vod = el.style.display === 'none' ? '' : el.style.display
    if (transition && value) {transition.beforeEnter(el)
    } else {setDisplay(el, value)
    }
  },
  mounted(el, { value}, {transition}) {if (transition && value) {transition.enter(el)
    }
  },
  updated(el, { value, oldValue}, {transition}) {// ...},
  beforeUnmount(el, { value}) {setDisplay(el, value)
  }
}
  1. v-if原理

v-if在实现上比 v-show 要简单的多,因为还有else else-if 等条件须要解决,这里咱们也只摘抄源码中解决 v-if 的一小部分

返回一个 node 节点,render函数通过表达式的值来决定是否生成DOM

// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.ts
export const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/,
  (node, dir, context) => {return processIf(node, dir, context, (ifNode, branch, isRoot) => {
      // ...
      return () => {if (isRoot) {
          ifNode.codegenNode = createCodegenNodeForBranch(
            branch,
            key,
            context
          ) as IfConditionalExpression
        } else {
          // attach this branch's codegen node to the v-if root.
          const parentCondition = getParentCondition(ifNode.codegenNode!)
          parentCondition.alternate = createCodegenNodeForBranch(
            branch,
            key + ifNode.branches.length - 1,
            context
          )
        }
      }
    })
  }
)

Vue 的事件绑定原理

原生事件绑定是通过 addEventListener 绑定给实在元素的,组件事件绑定是通过 Vue 自定义的$on 实现的。如果要在组件上应用原生事件,须要加.native 修饰符,这样就相当于在父组件中把子组件当做一般 html 标签,而后加上原生事件。

$on$emit 是基于公布订阅模式的,保护一个事件核心,on 的时候将事件按名称存在事件中心里,称之为订阅者,而后 emit 将对应的事件进行公布,去执行事件中心里的对应的监听器

EventEmitter(公布订阅模式 – 简略版)

// 手写公布订阅模式 EventEmitter
class EventEmitter {constructor() {this.events = {};
  }
  // 实现订阅
  on(type, callBack) {if (!this.events) this.events = Object.create(null);

    if (!this.events[type]) {this.events[type] = [callBack];
    } else {this.events[type].push(callBack);
    }
  }
  // 删除订阅
  off(type, callBack) {if (!this.events[type]) return;
    this.events[type] = this.events[type].filter(item => {return item !== callBack;});
  }
  // 只执行一次订阅事件
  once(type, callBack) {function fn() {callBack();
      this.off(type, fn);
    }
    this.on(type, fn);
  }
  // 触发事件
  emit(type, ...rest) {this.events[type] && this.events[type].forEach(fn => fn.apply(this, rest));
  }
}


// 应用如下
const event = new EventEmitter();

const handle = (...rest) => {console.log(rest);
};

event.on("click", handle);

event.emit("click", 1, 2, 3, 4);

event.off("click", handle);

event.emit("click", 1, 2);

event.once("dbClick", () => {console.log(123456);
});
event.emit("dbClick");
event.emit("dbClick");

源码剖析

  1. 原生 dom 的绑定
  2. Vue 在创立真是 dom 时会调用 createElm , 默认会调用 invokeCreateHooks
  3. 会遍历以后平台下绝对的属性解决代码, 其中就有 updateDOMListeners 办法, 外部会传入 add 办法
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {return}
    const on = vnode.data.on || {} 
    const oldOn = oldVnode.data.on || {} 
    target = vnode.elm normalizeEvents(on) 
    updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context) 
    target = undefined 
}
function add (name: string, handler: Function, capture: boolean, passive: boolean) {
    target.addEventListener( // 给以后的 dom 增加事件 
        name, 
        handler, 
        supportsPassive ? {capture, passive} : capture 
    ) 
}

vue 中绑定事件是间接绑定给实在 dom 元素的

  1. 组件中绑定事件
export function updateComponentListeners (vm: Component, listeners: Object, oldListeners: ?Object) {target = vm updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
    target = undefined 
}
function add (event, fn) {target.$on(event, fn) 
}

组件绑定事件是通过 vue 中自定义的 $on 办法来实现的

Vue 中组件和插件有什么区别

1. 组件是什么

组件就是把图形、非图形的各种逻辑均形象为一个对立的概念(组件)来实现开发的模式,在 Vue 中每一个.vue 文件都能够视为一个组件

组件的劣势

  • 升高整个零碎的耦合度,在放弃接口不变的状况下,咱们能够替换不同的组件疾速实现需要,例如输入框,能够替换为日历、工夫、范畴等组件作具体的实现
  • 调试不便,因为整个零碎是通过组件组合起来的,在呈现问题的时候,能够用排除法间接移除组件,或者依据报错的组件疾速定位问题,之所以可能疾速定位,是因为每个组件之间低耦合,职责繁多,所以逻辑会比剖析整个零碎要简略
  • 进步可维护性,因为每个组件的职责繁多,并且组件在零碎中是被复用的,所以对代码进行优化可取得零碎的整体降级

2. 插件是什么

插件通常用来为 Vue 增加全局性能。插件的性能范畴没有严格的限度——个别有上面几种:

  • 增加全局办法或者属性。如: vue-custom-element
  • 增加全局资源:指令 / 过滤器 / 过渡等。如 vue-touch
  • 通过全局混入来增加一些组件选项。如vue-router
  • 增加 Vue 实例办法,通过把它们增加到 Vue.prototype 上实现。
  • 一个库,提供本人的 API,同时提供下面提到的一个或多个性能。如vue-router

3. 两者的区别

两者的区别次要体现在以下几个方面:

  • 编写模式
  • 注册模式
  • 应用场景

3.1 编写模式

编写组件

编写一个组件,能够有很多形式,咱们最常见的就是 vue 单文件的这种格局,每一个 .vue 文件咱们都能够看成是一个组件

vue 文件规范格局

<template>
</template>
<script>
export default{...}
</script>
<style>
</style>

咱们还能够通过 template 属性来编写一个组件,如果组件内容多,咱们能够在内部定义 template 组件内容,如果组件内容并不多,咱们可间接写在 template 属性上

<template id="testComponent">     // 组件显示的内容
    <div>component!</div>   
</template>

Vue.component('componentA',{ 
    template: '#testComponent'  
    template: `<div>component</div>`  // 组件内容少能够通过这种模式
})

编写插件

vue插件的实现应该裸露一个 install 办法。这个办法的第一个参数是 Vue 结构器,第二个参数是一个可选的选项对象

MyPlugin.install = function (Vue, options) {
  // 1. 增加全局办法或 property
  Vue.myGlobalMethod = function () {// 逻辑...}

  // 2. 增加全局资源
  Vue.directive('my-directive', {bind (el, binding, vnode, oldVnode) {// 逻辑...}
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({created: function () {// 逻辑...}
    ...
  })

  // 4. 增加实例办法
  Vue.prototype.$myMethod = function (methodOptions) {// 逻辑...}
}

3.2 注册模式

组件注册

vue 组件注册次要分为 全局注册 部分注册

全局注册通过 Vue.component 办法,第一个参数为组件的名称,第二个参数为传入的配置项

Vue.component('my-component-name', { /* ... */})

部分注册只需在用到的中央通过 components 属性注册一个组件

const component1 = {...} // 定义一个组件

export default {
    components:{component1   // 部分注册}
}

插件注册

插件的注册通过 Vue.use() 的形式进行注册(装置),第一个参数为插件的名字,第二个参数是可抉择的配置项

Vue.use(插件名字,{ /* ... */} )

留神的是:

注册插件的时候,须要在调用 new Vue() 启动利用之前实现

Vue.use会主动阻止屡次注册雷同插件,只会注册一次

4. 应用场景

  • 组件 (Component) 是用来形成你的 App 的业务模块,它的指标是 App.vue
  • 插件 (Plugin) 是用来加强你的技术栈的功能模块,它的指标是 Vue 自身

简略来说,插件就是指对 Vue 的性能的加强或补充

Watch 中的 deep:true 是如何实现的

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

源码相干

get () {pushTarget(this) // 先将以后依赖放到 Dep.target 上 
    let value 
    const vm = this.vm 
    try {value = this.getter.call(vm, vm) 
    } catch (e) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`) 
        } else {throw e} 
    } finally {if (this.deep) { // 如果须要深度监控 
        traverse(value) // 会对对象中的每一项取值, 取值时会执行对应的 get 办法 
    }popTarget()}

$route$router 的区别

  • $route是“路由信息对象”,包含 pathparamshashqueryfullPathmatchedname 等路由信息参数。
  • $router 是“路由实例”对象包含了路由的跳转办法,钩子函数等

Vue-router 跳转和 location.href 有什么区别

  • 应用 location.href= /url 来跳转,简略不便,然而刷新了页面;
  • 应用 history.pushState(/url),无刷新页面,动态跳转;
  • 引进 router,而后应用 router.push(/url) 来跳转,应用了 diff 算法,实现了按需加载,缩小了 dom 的耗费。其实应用 router 跳转和应用 history.pushState() 没什么差异的,因为 vue-router 就是用了 history.pushState(),尤其是在 history 模式下。

Vue.extend 作用和原理

官网解释:Vue.extend 应用根底 Vue 结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。

其实就是一个子类结构器 是 Vue 组件的外围 api 实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并

  • extend是结构一个组件的语法器。而后这个组件你能够作用到 Vue.component 这个全局注册办法里还能够在任意 vue 模板里应用组件。也能够作用到 vue 实例或者某个组件中的 components 属性中并在外部应用 apple 组件。
  • Vue.component你能够创立,也能够取组件。

相干代码如下

export default function initExtend(Vue) {
  let cid = 0; // 组件的惟一标识
  // 创立子类继承 Vue 父类 便于属性扩大
  Vue.extend = function (extendOptions) {
    // 创立子类的构造函数 并且调用初始化办法
    const Sub = function VueComponent(options) {this._init(options); // 调用 Vue 初始化办法
    };
    Sub.cid = cid++;
    Sub.prototype = Object.create(this.prototype); // 子类原型指向父类
    Sub.prototype.constructor = Sub; //constructor 指向本人
    Sub.options = mergeOptions(this.options, extendOptions); // 合并本人的 options 和父类的 options
    return Sub;
  };
}

Class 与 Style 如何动静绑定

Class 能够通过对象语法和数组语法进行动静绑定

对象语法:

<div v-bind:class="{active: isActive,'text-danger': hasError}"></div>

data: {
  isActive: true,
  hasError: false
}

数组语法:

<div v-bind:class="[isActive ? activeClass :'', errorClass]"></div>

data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}

Style 也能够通过对象语法和数组语法进行动静绑定

对象语法:

<div v-bind:style="{color: activeColor, fontSize: fontSize +'px'}"></div>

data: {
  activeColor: 'red',
  fontSize: 30
}

数组语法:

<div v-bind:style="[styleColor, styleSize]"></div>

data: {
  styleColor: {color: 'red'},
  styleSize:{fontSize:'23px'}
}

vue-router 中如何爱护路由

剖析

路由爱护在利用开发过程中十分重要,简直每个利用都要做各种路由权限治理,因而相当考查使用者基本功。

体验

全局守卫:

const router = createRouter({...})
​
router.beforeEach((to, from) => {
  // ...
  // 返回 false 以勾销导航
  return false
})

路由独享守卫:

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

组件内的守卫:

const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {// 在渲染该组件的对应路由被验证前调用},
  beforeRouteUpdate(to, from) {// 在以后路由扭转,然而该组件被复用时调用},
  beforeRouteLeave(to, from) {// 在导航来到渲染该组件的对应路由时调用},
}

答复

  • vue-router中爱护路由的办法叫做路由守卫,次要用来通过跳转或勾销的形式守卫导航。
  • 路由守卫有三个级别:全局 路由独享 组件级。影响范畴由大到小,例如全局的router.beforeEach(),能够注册一个全局前置守卫,每次路由导航都会通过这个守卫,因而在其外部能够退出管制逻辑决定用户是否能够导航到指标路由;在路由注册的时候能够退出单路由独享的守卫,例如beforeEnter,守卫只在进入路由时触发,因而只会影响这个路由,管制更准确;咱们还能够为路由组件增加守卫配置,例如beforeRouteEnter,会在渲染该组件的对应路由被验证前调用,管制的范畴更准确了。
  • 用户的任何导航行为都会走 navigate 办法,外部有个 guards 队列按程序执行用户注册的守卫钩子函数,如果没有通过验证逻辑则会勾销原有的导航。

原理

runGuardQueue(guards)链式的执行用户在各级别注册的守卫钩子函数,通过则持续下一个级别的守卫,不通过进入 catch 流程勾销本来导航

// 源码
runGuardQueue(guards)
  .then(() => {
    // check global guards beforeEach
    guards = []
    for (const guard of beforeGuards.list()) {guards.push(guardToPromiseFn(guard, to, from))
    }
    guards.push(canceledNavigationCheck)

    return runGuardQueue(guards)
  })
  .then(() => {
    // check in components beforeRouteUpdate
    guards = extractComponentsGuards(
      updatingRecords,
      'beforeRouteUpdate',
      to,
      from
    )

    for (const record of updatingRecords) {
      record.updateGuards.forEach(guard => {guards.push(guardToPromiseFn(guard, to, from))
      })
    }
    guards.push(canceledNavigationCheck)

    // run the queue of per route beforeEnter guards
    return runGuardQueue(guards)
  })
  .then(() => {
    // check the route beforeEnter
    guards = []
    for (const record of to.matched) {
      // do not trigger beforeEnter on reused views
      if (record.beforeEnter && !from.matched.includes(record)) {if (isArray(record.beforeEnter)) {for (const beforeEnter of record.beforeEnter)
            guards.push(guardToPromiseFn(beforeEnter, to, from))
        } else {guards.push(guardToPromiseFn(record.beforeEnter, to, from))
        }
      }
    }
    guards.push(canceledNavigationCheck)

    // run the queue of per route beforeEnter guards
    return runGuardQueue(guards)
  })
  .then(() => {// NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>

    // clear existing enterCallbacks, these are added by extractComponentsGuards
    to.matched.forEach(record => (record.enterCallbacks = {}))

    // check in-component beforeRouteEnter
    guards = extractComponentsGuards(
      enteringRecords,
      'beforeRouteEnter',
      to,
      from
    )
    guards.push(canceledNavigationCheck)

    // run the queue of per route beforeEnter guards
    return runGuardQueue(guards)
  })
  .then(() => {
    // check global guards beforeResolve
    guards = []
    for (const guard of beforeResolveGuards.list()) {guards.push(guardToPromiseFn(guard, to, from))
    }
    guards.push(canceledNavigationCheck)

    return runGuardQueue(guards)
  })
  // catch any navigation canceled
  .catch(err =>
    isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED)
      ? err
      : Promise.reject(err)
  )

源码地位(opens new window)

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}
  ]
})

Vue 为什么没有相似于 React 中 shouldComponentUpdate 的生命周期

  • 考点: Vue的变动侦测原理
  • 前置常识: 依赖收集、虚构DOM、响应式零碎

根本原因是 VueReact的变动侦测形式有所不同

  • 当 React 晓得发生变化后,会应用 Virtual Dom Diff 进行差别检测,然而很多组件实际上是必定不会发生变化的,这个时候须要 shouldComponentUpdate 进行手动操作来缩小diff,从而进步程序整体的性能
  • Vue在一开始就晓得那个组件产生了变动,不须要手动管制 diff,而组件外部采纳的diff 形式实际上是能够引入相似于 shouldComponentUpdate 相干生命周期的,然而通常正当大小的组件不会有适量的 diff,手动优化的价值无限,因而目前 Vue 并没有思考引入 shouldComponentUpdate 这种手动优化的生命周期

Vue 中的过滤器理解吗?过滤器的利用场景有哪些?

过滤器本质不扭转原始数据,只是对数据进行加工解决后返回过滤后的数据再进行调用解决,咱们也能够了解其为一个纯函数

Vue 容许你自定义过滤器,可被用于一些常见的文本格式化

ps: Vue3中已废除filter

如何用

vue 中的过滤器能够用在两个中央:双花括号插值和 v-bind 表达式,过滤器应该被增加在 JavaScript 表达式的尾部,由“管道”符号批示:

<!-- 在双花括号中 -->
{message | capitalize}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

定义 filter

在组件的选项中定义本地的过滤器

filters: {capitalize: function (value) {if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

定义全局过滤器:

Vue.filter('capitalize', function (value) {if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({// ...})

留神:当全局过滤器和部分过滤器重名时,会采纳部分过滤器

过滤器函数总接管表达式的值 (之前的操作链的后果) 作为第一个参数。在上述例子中,capitalize 过滤器函数将会收到 message 的值作为第一个参数

过滤器能够串联:

{message | filterA | filterB}

在这个例子中,filterA 被定义为接管单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。而后持续调用同样被定义为接管单个参数的过滤器函数 filterB,将 filterA 的后果传递到 filterB 中。

过滤器是 JavaScript函数,因而能够接管参数:

{{message | filterA('arg1', arg2) }}

这里,filterA 被定义为接管三个参数的过滤器函数。

其中 message 的值作为第一个参数,一般字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数

举个例子:

<div id="app">
  <p>{{msg | msgFormat('疯狂','--')}}</p>
</div>

<script>
    // 定义一个 Vue 全局的过滤器,名字叫做  msgFormat
    Vue.filter('msgFormat', function(msg, arg, arg2) {
        // 字符串的  replace 办法,第一个参数,除了可写一个 字符串之外,还能够定义一个正则
        return msg.replace(/ 单纯 /g, arg+arg2)
    })
</script>

小结:

  • 部过滤器优先于全局过滤器被调用
  • 一个表达式能够应用多个过滤器。过滤器之间须要用管道符“|”隔开。其执行程序从左往右

利用场景

平时开发中,须要用到过滤器的中央有很多,比方 单位转换 数字打点 文本格式化 工夫格式化 之类的等

比方咱们要实现将30000 => 30,000,这时候咱们就须要应用过滤器

Vue.filter('toThousandFilter', function (value) {if (!value) return ''
  value = value.toString()
  return .replace(str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g, '$1,')
})

原理剖析

应用过滤器

{{message | capitalize}}

在模板编译阶段过滤器表达式将会被编译为过滤器函数,次要是用过parseFilters,咱们放到最初讲

_s(_f('filterFormat')(message))

首先剖析一下_f

_f 函数全名是:resolveFilter,这个函数的作用是从 this.$options.filters 中找出注册的过滤器并返回

// 变为
this.$options.filters['filterFormat'](message) // message 为参数

对于resolveFilter

import {indentity,resolveAsset} from 'core/util/index' 

export function resolveFilter(id){return resolveAsset(this.$options,'filters',id,true) || identity
}

外部间接调用 resolveAsset,将option 对象,类型,过滤器id,以及一个触发正告的标记作为参数传递,如果找到,则返回过滤器;

resolveAsset的代码如下:

export function resolveAsset(options,type,id,warnMissing){ // 因为咱们找的是过滤器,所以在 resolveFilter 函数中调用时 type 的值间接给的 'filters', 理论这个函数还能够拿到其余很多货色
  if(typeof id !== 'string'){ // 判断传递的过滤器 id 是不是字符串,不是则间接返回
      return 
  }
  const assets = options[type]  // 将咱们注册的所有过滤器保留在变量中
  // 接下来的逻辑便是判断 id 是否在 assets 中存在,即进行匹配
  if(hasOwn(assets,id)) return assets[id] // 如找到,间接返回过滤器
  // 没有找到,代码继续执行
  const camelizedId  = camelize(id) // 万一你是驼峰的呢
  if(hasOwn(assets,camelizedId)) return assets[camelizedId]
  // 没找到,继续执行
  const PascalCaseId = capitalize(camelizedId) // 万一你是首字母大写的驼峰呢
  if(hasOwn(assets,PascalCaseId)) return assets[PascalCaseId]
  // 如果还是没找到,则查看原型链(即拜访属性)
  const result = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  // 如果仍然没找到,则在非生产环境的控制台打印正告
  if(process.env.NODE_ENV !== 'production' && warnMissing && !result){warn('Failed to resolve' + type.slice(0,-1) + ':' + id, options)
  }
  // 无论是否找到,都返回查找后果
  return result
}

上面再来剖析一下_s

_s 函数的全称是 toString, 过滤器解决后的后果会当作参数传递给 toString函数,最终 toString函数执行后的后果会保留到 Vnode 中的 text 属性中,渲染到视图中

function toString(value){
  return value == null
  ? '': typeof value ==='object'
    ? JSON.stringify(value,null,2)// JSON.stringify()第三个参数可用来管制字符串外面的间距
    : String(value)
}

最初,在剖析下parseFilters,在模板编译阶段应用该函数阶段将模板过滤器解析为过滤器函数调用表达式

function parseFilters (filter) {let filters = filter.split('|')
    let expression = filters.shift().trim() // shift()删除数组第一个元素并将其返回,该办法会更改原数组
    let i
    if (filters) {for(i = 0;i < filters.length;i++){experssion = warpFilter(expression,filters[i].trim()) // 这里传进去的 expression 实际上是管道符号后面的字符串,即过滤器的第一个参数
        }
    }
    return expression
}
// warpFilter 函数实现
function warpFilter(exp,filter){
    // 首先判断过滤器是否有其余参数
    const i = filter.indexof('(')
    if(i<0){ // 不含其余参数,间接进行过滤器表达式字符串的拼接
        return `_f("${filter}")(${exp})`
    }else{const name = filter.slice(0,i) // 过滤器名称
        const args = filter.slice(i+1) // 参数,但还多了‘)’return `_f('${name}')(${exp},${args}` // 留神这一步少给了一个 ')'
    }
}

小结:

  • 在编译阶段通过 parseFilters 将过滤器编译成函数调用(串联过滤器则是一个嵌套的函数调用,前一个过滤器执行的后果是后一个过滤器函数的参数)
  • 编译后通过调用 resolveFilter 函数找到对应过滤器并返回后果
  • 执行后果作为参数传递给 toString 函数,而 toString 执行后,其后果会保留在 Vnodetext属性中,渲染到视图

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 内置指令

什么是作用域插槽

插槽

  • 创立组件虚构节点时,会将组件儿子的虚构节点保存起来。当初始化组件时,通过插槽属性将儿子进行分类{a:[vnode],b[vnode]}
  • 渲染组件时会拿对应的 slot 属性的节点进行替换操作。(插槽的作用域为父组件)
<app>
    <div slot="a">xxxx</div>
    <div slot="b">xxxx</div>
</app> 

slot name="a" 
slot name="b"

作用域插槽

  • 作用域插槽在解析的时候不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此函数进行渲染。(插槽的作用域为子组件)
  • 一般插槽渲染的作用域是父组件,作用域插槽的渲染作用域是以后子组件。
// 插槽

const VueTemplateCompiler = require('vue-template-compiler'); 
let ele = VueTemplateCompiler.compile(` 
    <my-component> 
        <div slot="header">node</div> 
        <div>react</div> 
        <div slot="footer">vue</div> 
    </my-component> `
)

// with(this) { 
//     return _c('my-component', [_c('div', {//         attrs: { "slot": "header"},
//         slot: "header" 
//     }, [_v("node")] // _文本及诶点 )
//     , _v(" "), 
//     _c('div', [_v("react")]), _v(""), _c('div', {//         attrs: { "slot": "footer"},
//         slot: "footer" }, [_v("vue")])]) 
// }

const VueTemplateCompiler = require('vue-template-compiler');
let ele = VueTemplateCompiler.compile(` 
    <div>
        <slot name="header"></slot> 
        <slot name="footer"></slot> 
        <slot></slot> 
    </div> `
);

with(this) {return _c('div', [_v("node"), _v(""), _t(_v("vue")])]), _v(" "), _t("default")], 2) 
}
//  _t 定义在 core/instance/render-helpers/index.js
// 作用域插槽:
let ele = VueTemplateCompiler.compile(` <app>
        <div slot-scope="msg" slot="footer">{{msg.a}}</div> 
    </app> `
);

// with(this) { 
//     return _c('app', { scopedSlots: _u([{ 
//         // 作用域插槽的内容会被渲染成一个函数 
//         key: "footer", 
//         fn: function (msg) {//             return _c('div', {}, [_v(_s(msg.a))]) } }]) 
//         })
//     } 
// }

const VueTemplateCompiler = require('vue-template-compiler');
VueTemplateCompiler.compile(` <div><slot name="footer" a="1" b="2"></slot> </div> `);

// with(this) {return _c('div', [_t("footer", null, { "a": "1", "b": "2"})], 2) }
退出移动版