最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 最新的vue面试题大全含源码级回答(vue3篇)

    正文概述 掘金(Chenjunyi)   2021-04-15   523

    前言

    接上篇的面试文章,vue2面试题大全含源码级回答 ,这篇讲讲vue3常见的面试题及回答。

    1. vue3与vue2有哪些不同

    大的改动:

    • proxy代替Object.definPrototety响应式系统
    • ts代替flow类型检查
    • 重构了目录结构,将代码主要分成三个独立的模块,更利于长期维护
    • 重写vdom,优化编译性能
    • 支持tree shaking
    • 增加了composition api(setup),让代码更易于维护

    小的改动:

    • 异步组件需要 defineAsyncComponent 方法来创建
    • v-model 用法
    • v-if优先级高于v-for
    • destroyed 生命周期选项被重命名为 unmounted
    • beforeDestroy 生命周期选项被重命名为 beforeUnmount
    • render函数默认参数createElement移除改为全局引入
    • 组件事件现在需要在 emits 选项中声明

    新特性:

    • 组合式 API
    • Teleport
    • framents(组件支持多个根节点)
    • createRenderer(跨平台的自定义渲染器)

    没有列举完,推荐看官网的v3迁移指南

    2. vue3在哪些方面提升了性能

    1. 响应式系统提升

    vue2在初始化的时候,通过Object.defineProperty对data的每个属性进行访问和修改的拦截,getter进行依赖收集、setter派发更新。在属性值是对象的时候还需要递归调用defineproperty。看下大致实现的代码:

    function observe(target) {
      if (target && typeof target === "Object") {
        Object.keys(target).forEach((key) => {
          defineReactive(target, key, target[key])
        })
      }
    }
    function defineReactive(obj, key, val) {
      const dep = new Dep();
      observe(val) // 如果属性值是对象就遍历它的属性
      Object.defineProperty(obj, key, {
        get() {
          return val
        },
        set(v) {
          val = v
          dep.notify();
        }
      })
    }
    

    而如果属性是数组,还需要覆盖数组的七个方法(会改变原数组的七个方法)进行变更的通知:

    const arrayProto = Array.prototype
    const arrayMethods = Object.create(arrayProto)
    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
    methodsToPatch.forEach(function (method) {
      const original = arrayProto[method]
      def(arrayMethods, method, function mutator (...args) {
        const result = original.apply(this, args)
        const ob = this.__ob__
        ob.dep.notify()
        return result
      })
    })
    

    从这几段代码可以看出Object.defineProperty的几个缺点:

    • 初始化时需要遍历对象所有key,层级多的情况下,性能有一定影响
    • 动态新增、删除对象属性无法拦截,只能用set/delete api代替
    • 不支持新的Map、Set等数据结构
    • 无法监控到数组下标的变化(监听的性能代价太大)

    所以在vue3中用了proxy全面代替Object.defineProperty的响应式系统。proxy是比较新的浏览器特性,拦截的是整个对象而不是对象的属性,可以拦截多种方法,包括属性的访问、赋值、删除等操作,不需要初始化的时候遍历所有属性,并且是懒执行的特性,也就是在访问到的时候才会触发,当访问到对象属性的时候才会递归代理这个对象属性,所以性能比vue2有明显的优势。

    总结下proxy的优势:

    • 可以监听多种操作方法,包括动态新增的属性和删除属性、has、apply等操作
    • 可以监听数组的索引和 length 等属性
    • 懒执行,不需要初始化的时候递归遍历
    • 浏览器新标准,性能更好,并且有持续优化的可能

    看下大致实现拦截对象的方法。

    export function reactive(target: object) {
      if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
        return target
      }
      return createReactiveObject(
        target,
        false,
        mutableHandlers,
        mutableCollectionHandlers
      )
    }
    function createReactiveObject(
      target: Target,
      isReadonly: boolean,
      baseHandlers: ProxyHandler<any>,
      collectionHandlers: ProxyHandler<any>
    ) {
      const proxy = new Proxy(
        target,
        baseHandlers
      )
      proxyMap.set(target, proxy) // 用weakMap收集
      return proxy
    }
    

    2. 编译优化(虚拟dom优化)

    编译优化主要是通过重写虚拟dom。优化的点包括编译模板的静态标记静态提升事件缓存

    • 静态标记(PatchFlag)

    根据尤大直播所说,更新的性能提升1.32倍,ssr提升23倍。 在对更新的节点进行对比的时候,只会去对比带有静态标记的节点。并且 PatchFlag 枚举定义了十几种类型,用以更精确的定位需要对比节点的类型。

    看这段代码

    <div id="app">
        <p>前端好好玩</p>
        <div>{{message}}</div>
    </div>
    

    vue2编译后的渲染函数:

    function render() {
      with(this) {
        return _c('div', {
          attrs: {
            "id": "app"
          }
        }, [_c('p', [_v("前端好好玩")]), _c('div', [_v(
          _s(message))])])
      }
    }
    

    这个render函数会返回vnode,后面更新的时候vue2会调patch函数比旧vnode进行diff算法更新(在我的上篇文章有解析过),这时候对比是整个vnode,包括里面的静态节点<p>前端好好玩</p>,这样就会有一定的性能损耗。

    vue3编译后的渲染函数:

    import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
    
    export function render(_ctx, _cache) {
      return (_openBlock(), _createBlock("div", { id: "app" }, [
        _createVNode("p", null, "前端好好玩"),
        _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
      ]))
    }
    

    只有_createVNode这个函数带有第四个参数的才是非静态节点,也就是需要后续diff的节点。第四个参数是这个节点具体包含需要被diff的类型,比如是text节点,只有{{}}这种模板变量的绑定,后续只需要对比这个text即可,看下源码中定义了哪些枚举的元素类型:

      TEXT = 1,// 动态的文本节点
      CLASS = 1 << 1,  // 2,动态Class的节点
      STYLE = 1 << 2,  // 4,表示动态样式
      PROPS = 1 << 3,  // 8,动态属性
      FULL_PROPS = 1 << 4,  // 16 动态键名
      HYDRATE_EVENTS = 1 << 5,  // 32 带有事件监听器的节点
      STABLE_FRAGMENT = 1 << 6,   // 64 一个不会改变子节点顺序的
      KEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性
      UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key
      NEED_PATCH = 1 << 9,   // 512
      DYNAMIC_SLOTS = 1 << 10,  // 动态插槽
      HOISTED = -1,  // 静态提升的标记,不会被diff,下面的静态提升会提到
      BAIL = -2 //
    
    • 静态提升

    静态提升的意思就是把函数里的某些变量放到外面来,这样再次执行这个函数的时候就不会重新声明。vue3在编译阶段做了这个优化。还是上面那段代码,分别看下vue2和vue3编译后的不同

    vue2:

    function render() {
      with(this) {
        return _c('div', {
          attrs: {
            "id": "app"
          }
        }, [_c('p', [_v("前端好好玩")]), _c('div', [_v(_s(message))])])
      }
    }
    

    vue3:

    import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
    
    const _hoisted_1 = { id: "app" }
    const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "前端好好玩", -1 /* HOISTED */)
    
    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createBlock("div", _hoisted_1, [
        _hoisted_2,
        _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
      ]))
    }
    

    可以看到vue3将不变的节点声明放到了外面去执行,后面再渲染的时候直接去_hoited变量就行,而vue2每次render都需要执行_c生成新的节点。这里还有一个点,_hoisted_2的_createVNode第四个参数-1,标记这个节点永远不需要diff。

    • 事件缓存

    默认情况下事件被认为是动态变量,所以每次更新视图的时候都会追踪它的变化。但是正常情况下,我们的 @click 事件在视图渲染前和渲染后,都是同一个事件,基本上不需要去追踪它的变化,所以 Vue 3.0 对此作出了相应的优化叫事件监听缓存

    <div id="app">
        <p @click="handleClick">前端好好玩</p>
    </div>
    

    vue3编译后:

    import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
    
    const _hoisted_1 = { id: "app" }
    
    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createBlock("div", _hoisted_1, [
        _createVNode("p", {
          onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))
        }, "前端好好玩")
      ]))
    }
    

    可以看到onClick有一个_cache判断缓存赋值的操作,从而变成静态节点

    3. 源码体积的优化

    vue3通过重构全局api和内部api,支持了tree shaking,任何一个函数,如ref、reavtived、computed等,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小

    3. 介绍下composition api

    Composition API是vue3最重要的特性之一,为的是更好的逻辑复用和代码组织,解决options api在大型项目中,options api不好拆分和重用的问题。

    Composition api声明在setup函数内,setup是在创建组件之前执行,这也意味着这时候组件实例尚未被创建,因此在 setup 选项中没有 this。

    setup接受propscontext两个参数,props是父组件传递的参数,并且原本就是响应式的,context则是一个普通的对象,包含attrsslotsemit三个属性。setup的返回值可以在模板和其他选项中访问到,也可以返回渲染函数。

    vue2是将data选项的数据进行处理后成为响应式数据,而在vue3中要通过reactiveref函数来进行数据定义后才是响应式数据。这样做的一个好处就是模板绑定的数据不一定是需要响应式的,vue3通过用户自行决定需要响应式的数据来处理,而vue2中要在模板中使用变量只能通过在data里声明,这样就造成了一定的性能浪费。

    因为setup是在组件创建之前执行,需要访问组件实例或者 生命周期则要通过引入vue提供的函数,getCurrentInstanceonMounted等等,这就是函数式编程的方式,也更利于代码逻辑的拆分,再也不需要mixin来混入各种选项了。

    利用这个特性,可以将一些复用的代码抽离出来作为一个函数,只要在使用的地方直接进行调用,非常灵活,看下官方提供的例子:

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

    从源码看下setup函数的实现和调用逻辑: 创建组件的时候会调mountComponent,在mountComponent调用setupComponent,再setupStatefulComponent函数处理。

    function setupComponent(
      instance: ComponentInternalInstance,
      isSSR = false
    ) {
      isInSSRComponentSetup = isSSR
    
      const { props, children, shapeFlag } = instance.vnode
      const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
      initProps(instance, props, isStateful, isSSR)
      initSlots(instance, children)
    
      const setupResult = isStateful
        ? setupStatefulComponent(instance, isSSR)
        : undefined
      isInSSRComponentSetup = false
      return setupResult // 最终返回setup处理后的结果
    }
    function setupStatefulComponent(
      instance: ComponentInternalInstance,
      isSSR: boolean
    ) {
      const Component = instance.type as ComponentOptions
    
      if (__DEV__) {
        if (Component.name) {
          validateComponentName(Component.name, instance.appContext.config)
        }
        if (Component.components) {
          const names = Object.keys(Component.components)
          for (let i = 0; i < names.length; i++) {
            validateComponentName(names[i], instance.appContext.config)
          }
        }
        if (Component.directives) {
          const names = Object.keys(Component.directives)
          for (let i = 0; i < names.length; i++) {
            validateDirectiveName(names[i])
          }
        }
      }
      // 0. create render proxy property access cache
      instance.accessCache = Object.create(null)
      // 1. create public instance / render proxy
      // also mark it raw so it's never observed
      instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
      if (__DEV__) {
        exposePropsOnRenderContext(instance)
      }
      // 2. call setup()
      const { setup } = Component
      // 如果有setup选项就进去setup的处理
      if (setup) {
        const setupContext = (instance.setupContext =
          setup.length > 1 ? createSetupContext(instance) : null)
    
        currentInstance = instance
        pauseTracking()
        const setupResult = callWithErrorHandling(
          setup,
          instance,
          ErrorCodes.SETUP_FUNCTION,
          [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
        )
        // 暂停依赖收集
        resetTracking()
        currentInstance = null
        
      } else {
        finishComponentSetup(instance, isSSR)
      }
    }
    

    判断有setup选项就通过callWithErrorHandling开始执行setup,这个函数执行setup选项并做了错误处理机制。

    function callWithErrorHandling(
      fn: Function, // 这个fn就是setup选项
      instance: ComponentInternalInstance | null,
      type: ErrorTypes,
      args?: unknown[]
    ) {
      let res
      try {
        res = args ? fn(...args) : fn()
      } catch (err) {
        handleError(err, instance, type)
      }
      return res
    }
    

    执行完后在调handleSetupResult对setup的返回值进行判断是否合法,最终finishComponentSetup完成setup处理,看finishComponentSetup函数:

    function finishComponentSetup(
      instance: ComponentInternalInstance,
      isSSR: boolean
    ) {
      const Component = instance.type as ComponentOptions
    
      // template / render function normalization
      if (__NODE_JS__ && isSSR) {
        if (Component.render) {
          instance.render = Component.render as InternalRenderFunction
        }
      } else if (!instance.render) {
        // could be set from setup()
        if (compile && Component.template && !Component.render) {
          if (__DEV__) {
            startMeasure(instance, `compile`)
          }
          Component.render = compile(Component.template, {
            isCustomElement: instance.appContext.config.isCustomElement,
            delimiters: Component.delimiters
          })
          if (__DEV__) {
            endMeasure(instance, `compile`)
          }
        }
    
        instance.render = (Component.render || NOOP) as InternalRenderFunction
    
        if (instance.render._rc) {
          instance.withProxy = new Proxy(
            instance.ctx,
            RuntimeCompiledPublicInstanceProxyHandlers
          )
        }
      }
    
      // support for 2.x options
      if (__FEATURE_OPTIONS_API__) {
        currentInstance = instance
        applyOptions(instance, Component)
        currentInstance = null
      }
      ...
    }
    

    这个函数是将绑定render函数到当前实例 instance,然后再调applyOptions函数对setup之外的datacomputedwatch之类选项进行处理和生命周期钩子的调用。所以可以得出结论,setup里是访问不到data这些选项和其他生命周期。

    4. vue3的响应式实现

    在前面有说过,vue3的响应式是通过proxy实现的,在源码的/packages/reactivity目录下。

    整个响应式系统的流程如下:

    1、通过state = reactive(target) 来定义响应式数据(代理get、set、deleteProperty、has、ownKeys等操作)

    2、通过 effect 声明依赖响应式数据的函数cb ( 例如视图渲染函数render函数),并执行cb函数,执行过程中,会触发响应式数据 getter

    3、在响应式数据 getter中进行 track依赖收集:存储响应式数据与更新函数 cb 的映射关系,存储于targetMap

    4、当变更响应式数据时,触发trigger,根据targetMap找到关联的cb并执行

    通过源码来看下这几个关键函数的实现:

    reactive

    /packages/reactivity/reactive:

    function reactive(target: object) {
      // 如果尝试观察只读代理,则返回只读版本
      if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
        return target
      }
      return createReactiveObject(
        target,
        false,
        mutableHandlers,
        mutableCollectionHandlers,
        reactiveMap
      )
    }
    function createReactiveObject(
      target: Target,
      isReadonly: boolean,
      baseHandlers: ProxyHandler<any>,
      collectionHandlers: ProxyHandler<any>,
      proxyMap: WeakMap<Target, any>
    ) {
      // 如果不是对象,直接返回即可
      if (!isObject(target)) {
        if (__DEV__) {
          console.warn(`value cannot be made reactive: ${String(target)}`)
        }
        return target
      }
      // 代理的目标本身就是代理的proxy,直接返回自身
      if (
        target[ReactiveFlags.RAW] &&
        !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
      ) {
        return target
      }
      // 代理的目标已经被代理过了,直接返回代理对象
      const existingProxy = proxyMap.get(target)
      if (existingProxy) {
        return existingProxy
      }
      // 只能代理可以代理的白名单类型对象.
      const targetType = getTargetType(target)
      if (targetType === TargetType.INVALID) {
        return target
      }
      // 判断代理的对象类型,来根据不同的类型做不同的代理处理
      const proxy = new Proxy(
        target,
        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
      )
      // 保存在proxyMap,防止目标对象被重复代理
      proxyMap.set(target, proxy)
      return proxy
    }
    

    通过reactive调用createReactiveObject生成响应式对象,对传入的target有做不同情况的处理,proxy的handler用传入的baseHandlers,这里默认传入的是mutableHandlers,这个方法从reactivity/baseHandlers导入:

    mutableHandlers: ProxyHandler<object> = {
      get,
      set,
      deleteProperty,
      has,
      ownKeys
    }
    const get = /*#__PURE__*/ createGetter()
    const set = /*#__PURE__*/ createSetter()
    function createGetter(isReadonly = false, shallow = false) {
      return function get(target: Target, key: string | symbol, receiver: object) {
        
        ...
        
        // 对数组做特殊的读取值处理
        const targetIsArray = isArray(target)
    
        if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
          return Reflect.get(arrayInstrumentations, key, receiver)
        }
    
        const res = Reflect.get(target, key, receiver)
        
        // track 依赖收集
        if (!isReadonly) {
          track(target, TrackOpTypes.GET, key)
        }
       
        ...
        
        // 如果读取的值是对象,递归调用reactive,使之成为响应式对象
        if (isObject(res)) {
          return isReadonly ? readonly(res) : reactive(res)
        }
    
        return res
      }
    }
    function createSetter(shallow = false) {
      return function set(
        target: object,
        key: string | symbol,
        value: unknown,
        receiver: object
      ): boolean {
        let oldValue = (target as any)[key]
     
        ...
        
        // 判断是新增还是删除属性
        const hadKey =
          isArray(target) && isIntegerKey(key)
            ? Number(key) < target.length
            : hasOwn(target, key)
        const result = Reflect.set(target, key, value, receiver)
        // don't trigger if target is something up in the prototype chain of original
        if (target === toRaw(receiver)) {
          if (!hadKey) {
            // trigger更新函数
            trigger(target, TriggerOpTypes.ADD, key, value)
          } else if (hasChanged(value, oldValue)) {
            trigger(target, TriggerOpTypes.SET, key, value, oldValue)
          }
        }
        return result
      }
    }
    

    mutableHandlers对get、set、deleteProperty等属性操作做了处理,这边只分析get 和set。在get的时候会进行track依赖收集,如果get的属性值是对象还会进行递归响应式处理,set则会trigger进行更新。

    track

    function track(target: object, type: TrackOpTypes, key: unknown) {
      if (!shouldTrack || activeEffect === undefined) {
        return
      }
      // 获取target对应依赖表
      let depsMap = targetMap.get(target)
      if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
      }
      // 获取key对应的响应函数集合
      let dep = depsMap.get(key)
      if (!dep) {
        // 动态创建依赖关系
        depsMap.set(key, (dep = new Set()))
      }
      // activeEffect临时变量,getter触发依赖收集的回调函数,可能是render或者effect生成的副作用函数
      if (!dep.has(activeEffect)) {
        dep.add(activeEffect)
        activeEffect.deps.push(dep)
        if (__DEV__ && activeEffect.options.onTrack) {
          activeEffect.options.onTrack({
            effect: activeEffect,
            target,
            type,
            key
          })
        }
      }
    }
    

    track依赖收集的时候,先判断targetMap是否存在访问的这个对象,targetMap是一个weakMap的结构,格式为{target:{ key: [fn1,fn2]}},target为weakMap的key,value是一个map类型,key为访问到的target的属性,值为这个属性对应的回调函数集合。最后面有一个activeEffect的判断,这个判断依赖收集的副作用函数,这个副作用函数可能是ffect临时生成,也有可能是在render渲染函数临时生成的副作用函数。

    trigger

    function trigger(
      target: object,
      type: TriggerOpTypes,
      key?: unknown,
      newValue?: unknown,
      oldValue?: unknown,
      oldTarget?: Map<unknown, unknown> | Set<unknown>
    ) {
      // 获取触发更新的target对应的属性映射集合
      const depsMap = targetMap.get(target)
      if (!depsMap) {
        // never been tracked
        return
      }
    
      const effects = new Set<ReactiveEffect>()
      const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
        if (effectsToAdd) {
          effectsToAdd.forEach(effect => {
            if (effect !== activeEffect || effect.allowRecurse) {
              effects.add(effect)
            }
          })
        }
      }
    
      if (type === TriggerOpTypes.CLEAR) {
        // collection being cleared
        // trigger all effects for target
        depsMap.forEach(add)
      } else if (key === 'length' && isArray(target)) {
        depsMap.forEach((dep, key) => {
          if (key === 'length' || key >= (newValue as number)) {
            add(dep)
          }
        })
      } else {
        // schedule runs for SET | ADD | DELETE
        if (key !== void 0) {
          add(depsMap.get(key))
        }
    
        // also run for iteration key on ADD | DELETE | Map.SET
        // 根据触发的操作类型做不同的回调函数处理
        switch (type) {
          case TriggerOpTypes.ADD:
            if (!isArray(target)) {
              add(depsMap.get(ITERATE_KEY))
              if (isMap(target)) {
                add(depsMap.get(MAP_KEY_ITERATE_KEY))
              }
            } else if (isIntegerKey(key)) {
              // new index added to array -> length changes
              add(depsMap.get('length'))
            }
            break
          case TriggerOpTypes.DELETE:
            if (!isArray(target)) {
              add(depsMap.get(ITERATE_KEY))
              if (isMap(target)) {
                add(depsMap.get(MAP_KEY_ITERATE_KEY))
              }
            }
            break
          case TriggerOpTypes.SET:
            if (isMap(target)) {
              add(depsMap.get(ITERATE_KEY))
            }
            break
        }
      }
    
      const run = (effect: ReactiveEffect) => {
        if (__DEV__ && effect.options.onTrigger) {
          effect.options.onTrigger({
            effect,
            target,
            key,
            type,
            newValue,
            oldValue,
            oldTarget
          })
        }
        if (effect.options.scheduler) {
          effect.options.scheduler(effect)
        } else {
          effect()
        }
      }
      // 执行所有的回调函数集合
      effects.forEach(run)
    }
    

    trigger触发更新,根据targetsMap找到target对应的属性依赖集合,再根据key找到回调函数集合,然后还要根据操作类型做处理后,执行所有的回调函数集合。

    effect

    // effect栈,保存所有的effect副作用函数
    const effectStack: ReactiveEffect[] = []
    function effect<T = any>(
      fn: () => T,
      options: ReactiveEffectOptions = EMPTY_OBJ
    ): ReactiveEffect<T> {
      if (isEffect(fn)) {
        fn = fn.raw
      }
      const effect = createReactiveEffect(fn, options)
      if (!options.lazy) {
        effect()
      }
      return effect
    }
    function createReactiveEffect<T = any>(
      fn: () => T,
      options: ReactiveEffectOptions
    ): ReactiveEffect<T> {
      const effect = function reactiveEffect(): unknown {
        if (!effect.active) {
          return options.scheduler ? undefined : fn()
        }
        // effectStack是否存在当前执行的副作用函数
        if (!effectStack.includes(effect)) {
          cleanup(effect)
          try {
            enableTracking()
            effectStack.push(effect)
            activeEffect = effect
            return fn()
          } finally {
            effectStack.pop()
            resetTracking()
            activeEffect = effectStack[effectStack.length - 1]
          }
        }
      } as ReactiveEffect
      effect.id = uid++
      effect.allowRecurse = !!options.allowRecurse
      effect._isEffect = true
      effect.active = true
      effect.raw = fn
      effect.deps = []
      effect.options = options
      return effect
    }
    

    effectStack栈结构的数组,effect的时候,将副作用函数放入effectStack中,再将activeEffect临时赋值为当前执行的effect函数,用于track的时候将effect函数放入响应式数据的key的回调函数集合,effect执行完再将activeEffect赋值回原来effectStack的末位函数。

    5. vue3的hook与react的hook有什么不同

    毫无疑问,vue3的hook是借鉴了react的hook思想,vue3中自定义hook的写法与react看起来很类似,但实际使用是有些许不同,而内部实现原理更是完全不一样。

    首先说下react hook的两个限制:

    1. 只在最顶层使用 Hook不要在循环,条件或嵌套函数中调用 Hook
    2. 只在 React 函数中调用 Hook不要在普通的 JavaScript 函数中调用 Hook

    这在react官网也有专门介绍。

    只能在最顶层使用Hook,这是因为react的hook是依靠调用的顺序来确认state对应的hook,每次重新渲染都会再调用hook,所以需要确保hook的调用顺序是不会变的。

    再说下vue与react使用的不同之处:

    1. setup只执行一遍,而react每次渲染都会重新执行hook
    2. Hook需要更新值时Vue可以直接赋值,而react则需要调用hook的赋值函数
    3. 调用顺序无要求,也可以放在条件语句里

    实现原理的不同:

    vue中的hook是响应式对象,在render的时候读取到就会被依赖收集

    react中的hook本质是一个函数,每次重新渲染都需要再次调用,在声明的时候按照调用顺序通过{ value1, setValue1} -> { value2, setValue2 }的链表结构存储,所以需要严格限制 Hook 的执行顺序和禁止条件调用。

    6. vue3的dom diff与react的dom diff不同

    在前面的vue3性能提升的优化点有说过了vdom编译优化通过静态节点、静态提升和事件缓存,而在react是没有做这个实现的。

    react是通过把vdom树以链表的结构,利用浏览器的空闲时间来做diff,也就是时间切片的概念,如果超过了16ms,有动画或者用户交互的任务,就把主进程控制权还给浏览器,等空闲了继续diff。用的是requestIdleCallback这个浏览器的api实现。


    起源地下载网 » 最新的vue面试题大全含源码级回答(vue3篇)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元