笔记:Vue 常见面试题汇总及解析

844 阅读11分钟

1. 对 MVVM、MVC 的理解

1-1. MVC

主要体现在服务端, 用户操作会请求服务端路由,路由会调用对应的控制器来处理,控制器会获取数据。将结果返回给前端,页面重新渲染

  • M Model 数据模型
  • V View 视图
  • C Constructor 控制器

MVC

实际项目

1-2. MVVM

将数据绑定到 viewModel 层上,会自动将数据渲染到页面中,视图变化会通知viewModel层 更新数据,数据更新会通知viweModel更新数据,ViewModel 就是我们 MVVM 模式中的桥梁

  • M Model 数据模型
  • V View 视图
  • VM ViewModel 视图模型

MVVM

Vue 就是典型的 MVVM

2. 响应式数据的原理

Vue 的响应式主要是通过 Object.definePrototype 来实现的,

  • observe(data, true) 先判断当前数据是否已经是响应式的
  • new Observer(value) 实例化 Observer 类,并添加 __ob__ 响应式标致
  • this.walk(value) 对顶层对象的处理
  • defineReactive(obj, keys[i]) 对属性的处理,这里有一个递归操作(当属性为对象类型的时候,排除null
  • Object.definePrototype 实例化 Dep 类,给属性添加 get、set
    • get 中进行依赖收集 dep.depend()
    • set 中进行派发更新 dep.notify()

源码解析

3. Vue 中是如何检测数组变化?

首先分两种情况:

    1. 支持 __proto__
    • Array.prototype 中实例化出一个对象(arrayMethods),用作中间桥梁,避免原型链的污染
    • arrayMethods 原型链上方法进行重写(需要重写的方法都列举在 methodsToPatch 数组中,主要包含 ['push','pop','shift','unshift','splice','sort','reverse']
    • 将当前需要响应式的数组的原型对象指向 arrayMethods target.__proto__ = src
    • 调用 observeArray 方法对数组的每一项进行响应式处理 ob.observeArray(inserted)
    • 最后精心派发更新 ob.dep.notify()
      数组方法通过 __proto__ 的方式拦截-源码解析
    1. 不支持 __proto__
    • Array.prototype 中实例化出一个对象(arrayMethods),用作中间桥梁,避免原型链的污染
    • arrayMethods 原型链上方法进行重写(需要重写的方法都列举在 methodsToPatch 数组中,主要包含 ['push','pop','shift','unshift','splice','sort','reverse']
    • arrayMethods 上可枚举与不可枚举的属性添加到当前需要响应式的数组上

数组方法通过赋值属性的方式拦截-源码解析

4. Vue 为什么是异步更新?

因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染.所以为了性能考虑。 Vue 会在本轮数据更新后,再去异步更新视图!

update() {
    /* istanbul ignore else */
    if (this.lazy) {
        this.dirty = true
    } else if (this.sync) {
        this.run()
    } else {
        queueWatcher(this); // 当数据发生变化时会将watcher放到一个队列中批量更新
    }
}
export function queueWatcher(watcher: Watcher) {
    const id = watcher.id // 会对相同的watcher进行过滤
    if (has[id] == null) {
        has[id] = true
        if (!flushing) {
            queue.push(watcher)
        } else {
            let i = queue.length - 1
            while (i > index && queue[i].id > watcher.id) {
                i--
            }
            queue.splice(i + 1, 0, watcher)
        }
        // queue the flush
        if (!waiting) {
            waiting = true
            if (process.env.NODE_ENV !== 'production' && !config.async) {
                flushSchedulerQueue()
                return
            }
            nextTick(flushSchedulerQueue) // 调用nextTick方法 批量的进行更新
        }
    }
}

5. nextTick 实现原理?

  • 创建回调函数队列 const callbacks = []
  • 将回调函数添加到队列中
  • 通过一个异步函数(timerFunc)来执行 flushCallbacks 函数(flushCallbacks 函数的作用就是依次执行回调函数队列中的函数)
  • 如果没有提供回调函数,则返回一个 Promise

nextTick实现原理

疑问: UI 渲染都是在所有的微任务执行之后才进行的,为什么 $nextTick 还是用微任务呢

6. Vue 中的 computed 的特点

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

function initComputed(vm: Component, computed: Object) {
    const watchers = vm._computedWatchers = Object.create(null)
    const isSSR = isServerRendering()
    for (const key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        if (!isSSR) {
            // create internal watcher for the computed property.
            watchers[key] = new Watcher(
                vm,
                getter || noop,
                noop,
                computedWatcherOptions
            )
        }
        // component-defined computed properties are already defined on the
        // component prototype. We only need to define computed properties defined

        // at instantiation here.
        if (!(key in vm)) {
            defineComputed(vm, key, userDef)
        } else if (process.env.NODE_ENV !== 'production') {
            if (key in vm.$data) {
                warn(`The computed property "${key}" is already defined in data.`, vm)
            } else if (vm.$options.props && key in vm.$options.props) {
                warn(`The computed property "${key}" is already defined as a prop.`, vm)
            }
        }
    }
}
function createComputedGetter(key) {
    return function computedGetter() {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
            if (watcher.dirty) { // 如果依赖的值没发生变化,就不会重新求值
                watcher.evaluate()
            }
            if (Dep.target) {
                watcher.depend()
            }
            return watcher.value
        }
    }
}

7. 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()
    }
    return value
}
function _traverse(val: any, seen: SimpleSet) {
    let i, keys
    const isA = Array.isArray(val)
    if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
        return
    }
    if (val.__ob__) {
        const depId = val.__ob__.dep.id
        if (seen.has(depId)) {
            return
        }
        seen.add(depId)
    }
    if (isA) {
        i = val.length
        while (i--) _traverse(val[i], seen)
    } else {
        keys = Object.keys(val)
        i = keys.length
        while (i--) _traverse(val[keys[i]], seen)
    }
}

8. Vue 组件的生命周期

每个生命周期什么时候被调用:

  • beforeCreate 在实例初始化之后,数据观测(data observer) 之前被调用。
  • created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data
  • observer),属性和方法的运算, watch/event 事件回调。这里没有$el
  • beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。
  • mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
  • beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
  • updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
  • beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed Vue 实例销毁后调用。调用后, Vue 实例指示的所有东西都会解绑定,所有的事件 监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。

每个生命周期内部可以做什么事:

  • created 实例已经创建完成,因为它是最早触发的原因可以进行一些数据,资源的请求。
  • mounted 实例已经挂载完成,可以进行一些DOM操作
  • beforeUpdate 可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  • updated 可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态, 因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。
  • beforeDestroy 可以执行一些优化操作,清空定时器,解除绑定事件

9. ajax 请求放在哪个生命周期中

created、mounted 中都可以, 但是在接口完成之后需要操作 dom 的情况下 ,最好放在 mounted 中并且 mounted 中还不一定保证所有的子组件都挂载成功,这是可以使用 Vm.$nextTick

mounted 钩子在服务器端渲染期间不被调用

10. beforeDestroy 的作用

  • 清楚定时器
  • 清楚自定义事件 $on
  • 移除手动给 dom 绑定的事件

11. Vue 中模板编译原理

12. Vue 中 v-if 和 v-show 的区别

13. 为什么 V-for 和 v-if 不能连用

14. 用 vnode 来描述一个 DOM 结构

15. v-for 中为什么要用 key

16. 描述组件渲染和更新过程

渲染组件时,会通过 Vue.extend 方法构建子组件的构造函数,并进行实例化。最终手动调用 $mount() 进行挂载。更新组件时会进行 patchVnode 流程.核心就是diff算法

17. 组件中的 data 为什么是一个函数?

function VueComponent(){}
    VueComponent.prototype.$options = {
    data:{name:'zf'}
}
let vc1 = new VueComponent();
vc1.$options.data = 'jw';
let vc2 = new VueComponent();
console.log(vc2.$options.data);

同一个组件被复用多次,会创建多个实例。这些实例用的是同一个构造函数,如果 data 是一个对象的 话。那么所有组件都共享了同一个对象。为了保证组件的数据独立性要求每个组件必须通过 data 函数 返回一个对象作为组件的状态。

Sub.options = mergeOptions(
    Super.options,
    extendOptions
)
function mergeOptions() {
    function mergeField(key) {
        const strat = strats[key] || defaultStrat
        options[key] = strat(parent[key], child[key], vm, key)
    }
}
strats.data = function (
    parentVal: any,
    childVal: any,
    vm?: Component
): ?Function {
    if (!vm) { // 合并是会判断子类的data必须是一个函数
        if (childVal && typeof childVal !== 'function') {
            process.env.NODE_ENV !== 'production' && warn(
                'The "data" option should be a function ' +
                'that returns a per-instance value in component ' +
                'definitions.',
                vm
            )
            return parentVal
        }
        return mergeDataOrFn(parentVal, childVal)
    }
    return mergeDataOrFn(parentVal, childVal, vm)
}

一个组件被使用多次,用的都是同一个构造函数。为了保证组件的不同的实例data不冲突,要求 data必须是一个函数,这样组件间不会相互影响

18. Vue 中事件绑定的原理

Vue 的事件绑定分为两种一种是原生的事件绑定,还有一种是组件的事件绑定,

  • 1.原生 dom 事件的绑定,采用的是 addEventListener 实现
  • 2.组件绑定事件采用的是 $on 方法

19. v-model 中的实现原理及如何自定义 v-model

v-model 可以看成是 value+input方法 的语法糖 input v-model checkbox v-model select v-model 组件的v-model 就是value+input的语法糖

20. Vue 中 v-html 会导致哪些问题?

  • 可能会导致 xss 攻击
  • v-html 会替换掉标签内部的子元素
let template = require('vue-template-compiler');
let r = template.compile(`<div v-html="'<span>hello</span>'"></div>`)
// with(this){return _c('div',{domProps:
{ "innerHTML": _s('<span>hello</span>') }})}
console.log(r.render);
// _c 定义在core/instance/render.js
// _s 定义在core/instance/render-helpers/index,js
if (key === 'textContent' || key === 'innerHTML') {
    if (vnode.children) vnode.children.length = 0
    if (cur === oldProps[key]) continue
    // #6601 work around Chrome version <= 55 bug where single textNode
    // replaced by innerHTML/textContent retains its parentNode property
    if (elm.childNodes.length === 1) {
        elm.removeChild(elm.childNodes[0])
    }
}

21. Vue 父子组件生命周期调用顺序

  • 加载渲染过程
    • 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount-子mounted->父mounted
  • 子组件更新过程
    • 父beforeUpdate->子beforeUpdate->子updated->父updated
  • 父组件更新过程
    • 父beforeUpdate->父updated
  • 销毁过程
    • 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

组件的调用顺序都是先父后子,渲染完成的顺序肯定是先子后父 组件的销毁操作是先父后子,销毁完成的顺序是先子后父

function patch(oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) {
        if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
        return
    }
    let isInitialPatch = false
    const insertedVnodeQueue = [] // 定义收集所有组件的insert hook方法的数组
    // somthing ...
    createElm(
        vnode,
        insertedVnodeQueue,
        oldElm._leaveCb ? null : parentElm,
        nodeOps.nextSibling(oldElm)
    )
    // somthing...
    // 最终会依次调用收集的insert hook
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
    return vnode.elm
}
function createElm(
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
) {
    // createChildren会递归创建儿子组件
    createChildren(vnode, children, insertedVnodeQueue)
    // something...
}
// 将组件的vnode插入到数组中
function invokeCreateHooks(vnode, insertedVnodeQueue) {
    for (let i = 0; i < cbs.create.length; ++i) {
        cbs.create[i](emptyNode, vnode)
    }
    i = vnode.data.hook // Reuse variable
    if (isDef(i)) {
        if (isDef(i.create)) i.create(emptyNode, vnode)
        if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
    }
}
// insert方法中会依次调用mounted方法
insert(vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
        componentInstance._isMounted = true
        callHook(componentInstance, 'mounted')
    }
}
function invokeInsertHook(vnode, queue, initial) {
    // delay insert hooks for component root nodes, invoke them after the
    // element is really inserted
    if (isTrue(initial) && isDef(vnode.parent)) {
        vnode.parent.data.pendingInsert = queue
    } else {
        for (let i = 0; i < queue.length; ++i) {
            queue[i].data.hook.insert(queue[i]); // 调用insert方法
        }
    }
}
Vue.prototype.$destroy = function () {
    callHook(vm, 'beforeDestroy') //
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null) // 先销毁儿子
    // fire destroyed hook
    callHook(vm, 'destroyed')
}

21. Vue 组件如何通信? 数据流是单向还是双向?

  • 父子间通信 父->子通过 props 、子-> 父 on、emit (发布订阅)
  • 获取父子组件实例的方式 parent、children
  • 在父组件中提供数据子组件进行消费 Provide、inject 插件
  • attrslisteners
  • Ref 获取实例的方式调用组件的属性或者方法
  • Event Bus 实现跨组件通信 Vue.prototype.$bus = new Vue
  • Vuex 状态管理实现通信

22. Vue 中相同逻辑如何抽离

  • Vue.mixin 用法 给组件每个生命周期,函数等都混入一些公共逻辑
Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin); // 将当前定义的属性合并到每个
    组件中
    return this
}
export function mergeOptions(
    parent: Object,
    child: Object,
    vm?: Component
): Object {
    if (!child._base) {
        if (child.extends) { // 递归合并extends
            parent = mergeOptions(parent, child.extends, vm)
        }
        if (child.mixins) { // 递归合并mixin
            for (let i = 0, l = child.mixins.length; i < l; i++) {
                parent = mergeOptions(parent, child.mixins[i], vm)
            }
        }
    }
    const options = {} // 属性及生命周期的合并
    let key
    for (key in parent) {
        mergeField(key)
    }
    for (key in child) {
        if (!hasOwn(parent, key)) {
            mergeField(key)
        }
    }
    function mergeField(key) {
        const strat = strats[key] || defaultStrat
        // 调用不同属性合并策略进行合并
        options[key] = strat(parent[key], child[key], vm, key)
    }
    return options
}

23. 为什么要使用异步组件

如果组件功能多打包出的结果会变大,我可以采用异步的方式来加载组件。主要依赖 import() 这 个语法,可以实现文件的分割加载。

components:{
AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") //
require([])
}
export function (
    Ctor: Class<Component> | Function | Object | void,
    data: ?VNodeData,
    context: Component,
    children: ?Array<VNode>,
    tag?: string
): VNode | Array<VNode> | void {
    // async component
    let asyncFactory
    if (isUndef(Ctor.cid)) {
        asyncFactory = Ctor
        Ctor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回
        undefiend
        // 第二次渲染时Ctor不为undefined
        if (Ctor === undefined) {
            return createAsyncPlaceholder( // 渲染占位符 空虚拟节点
                asyncFactory,
                data,
                context,
                children,
                tag
            )
        }
    }
}
function resolveAsyncComponent(
    factory: Function,
    baseCtor: Class<Component>
): Class<Component> | void {
    if (isDef(factory.resolved)) { // 3.在次渲染时可以拿到获取的最新组件
        return factory.resolved
    }
    const resolve = once((res: Object | Class<Component>) => {
        factory.resolved = ensureCtor(res, baseCtor)
        if (!sync) {
            forceRender(true) //2. 强制更新视图重新渲染
        } else {
            owners.length = 0
        }
    })
    const reject = once(reason => {
        if (isDef(factory.errorComp)) {
            factory.error = true
            forceRender(true)
        }
    })
    const res = factory(resolve, reject)// 1.将resolve方法和reject方法传入,用户调用
    resolve方法后
    sync = false
    return factory.resolved
}

24. 什么是作用域插槽

  • 1.插槽:
    • 创建组件虚拟节点时,会将组件的儿子的虚拟节点保存起来。当初始化组件时,通过插槽属性将儿 子进行分类 {a:[vnode],b[vnode]}
    • 渲染组件时会拿对应的slot属性的节点进行替换操作。(插槽的作用域为父组件)
<app><div slot="a">xxxx</div><div slot="b">xxxx</div></app>
slot name="a"
slot name="b"
  • 2.作用域插槽:
    • 作用域插槽在解析的时候,不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此 函数进行渲染。(插槽的作用域为子组件)

1.插槽:

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

2 作用域插槽:

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)
}
**/

25. 谈谈你对 keep-alive 的了解

keep-alive 可以实现组件的缓存,当组件切换时不会对当前组件进行卸载,常用的2个属性 include / exclude ,2个生命周期 activated , deactivated LRU算法

export default {
    name: 'keep-alive',
    abstract: true, // 抽象组件
    props: {
        include: patternTypes,
        exclude: patternTypes,
        max: [String, Number]
    },
    created() {
        this.cache = Object.create(null) // 创建缓存列表
        this.keys = [] // 创建缓存组件的key列表
    },
    destroyed() { // keep-alive销毁时 会清空所有的缓存和key
        for (const key in this.cache) { // 循环销毁
            pruneCacheEntry(this.cache, key, this.keys)
        }
    },
    mounted() { // 会监控include 和 include属性 进行组件的缓存处理
        this.$watch('include', val => {
            pruneCache(this, name => matches(val, name))
        })
        this.$watch('exclude', val => {
            pruneCache(this, name => !matches(val, name))
        })
    },
    render() {
        const slot = this.$slots.default // 会默认拿插槽
        const vnode: VNode = getFirstComponentChild(slot) // 只缓存第一个组件
        const componentOptions: ? VNodeComponentOptions = vnode &&
            vnode.componentOptions
        if (componentOptions) {
            // check pattern
            const name: ? string = getComponentName(componentOptions) // 取出组件的名字
            const {
                include,
                exclude
            } = this
            if ( // 判断是否缓存
                // not included
                (include && (!name || !matches(include, name))) ||
                // excluded
                (exclude && name && matches(exclude, name))
            ) {
                return vnode
            }
            const {
                cache,
                keys
            } = this
            const key: ? string = vnode.key == null
                // same constructor may get registered as different local components
                // so cid alone is not enough (#3269)
                ?
                componentOptions.Ctor.cid + (componentOptions.tag ?
                    `::${componentOptions.tag}` : '') :
                vnode.key // 如果组件没key 就自己通过 组件的标签和key和cid 拼接一个key
            if (cache[key]) {
                vnode.componentInstance = cache[key].componentInstance // 直接拿到组件实// make current key freshest
                remove(keys, key) // 删除当前的 [b,c,d,e,a] // LRU 最近最久未使用法
                keys.push(key) // 并将key放到后面[b,a]
            } else {
                cache[key] = vnode // 缓存vnode
                keys.push(key) // 将key 存入
                // prune oldest entry
                if (this.max && keys.length > parseInt(this.max)) { // 缓存的太多超过了max
                    就需要删除掉
                    pruneCacheEntry(cache, keys[0], keys, this._vnode) // 要删除第0个 但是现
                    在渲染的就是第0个
                }
            }
            vnode.data.keepAlive = true // 并且标准keep-alive下的组件是一个缓存组件
        }
        return vnode || (slot && slot[0]) // 返回当前的虚拟节点
    }
}

26. Vue 中常见性能优化

    1. 编码优化:
      1. 不要将所有的数据都放在data中,data中的数据都会增加getter和setter,会收集对应的 watcher
      1. vue 在 v-for 时给每项元素绑定事件需要用事件代理
      1. SPA 页面采用keep-alive缓存组件
      1. 拆分组件( 提高复用性、增加代码的可维护性,减少不必要的渲染 )
      1. v-if 当值为false时内部指令不会执行,具有阻断功能,很多情况下使用v-if替代v-show
      1. key 保证唯一性 ( 默认 vue 会采用就地复用策略 )
      1. Object.freeze 冻结数据
    • 8.合理使用路由懒加载、异步组件
    • 9.尽量采用runtime运行时版本
    • 10.数据持久化的问题 (防抖、节流)
    1. Vue 加载性能优化:
    1. 用户体验:
    • app-skeleton 骨架屏
    • app-shell app壳
    • pwa serviceworker
    1. SEO 优化:
    • 预渲染插件 prerender-spa-plugin
    • 服务端渲染 ssr
    1. 打包优化:
    • 使用 cdn 的方式加载第三方模块
    • 多线程打包 happypack splitChunks 抽离公共文件
    • sourceMap 生成
    1. 缓存,压缩
    • 客户端缓存、服务端缓存
    • 服务端 gzip 压缩

27. Vue3.0 你知道有哪些改进

  • Vue3 采用了TS来编写
  • 支持 Composition API
  • Vue3 中响应式数据原理改成 proxy vdom 的对比算法更新,只更新 vdom 的绑定了动态数据的部分

28. 实现 hash 路由和 history 路由

29. Vue-Router 中导航守卫有哪些

    1. 导航被触发。
    1. 在失活的组件里调用离开守卫。
    1. 调用全局的 beforeEach 守卫。
    1. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
    1. 在路由配置里调用 beforeEnter 。
    1. 解析异步路由组件。
    1. 在被激活的组件里调用 beforeRouteEnter 。
    1. 调用全局的 beforeResolve 守卫 (2.5+)。
    1. 导航被确认。
    1. 调用全局的 afterEach 钩子。
    1. 触发 DOM 更新。
    1. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

30. action 和 mutation 区别

  • mutation 是同步更新数据(内部会进行是否为异步方式更新数据的检测) $watch 严格模式下会报错
  • action 异步操作,可以获取数据后调佣 mutation 提交最终数据

31. 简述 Vuex 工作原理

单向数据流结构

  • 将 State 里面的数据,绑定到 View 中
  • View 中通过 dispatch 触发 Actions 来做异步操作
  • Actions 中通过 commit 触发 Mutations 来改变 State

【笔记不易,如对您有帮助,请点赞,谢谢】