vue源码面试题

1. 请说一下Vue2响应式数据的理解

  1. 先知道基本的问题在哪
  2. 源码的角度回答
  3. 你用的时候有哪些问题

可以监控一个数据的修改和获取操作。针对对象格式会给每个对象的属性进行劫持 Object.defineProperty

  • 源码层面 initData -> Observer -> defineReactive 方法(内部对所有的属性进行了重写 性能问题),递归值为对象的属性,增加 getter 和 setter 方法

  • 优化:我们使用 Vue 的时候,层级过深,如果数据不是响应式的就不要放在 data 中了。我们的属性取值的时候尽量避免多次取值,多次取值会多次调用 getter 方法,可以在取值时缓存结果。如果有些对象时放到 data 中的,但是不是响应式的可以考虑采用 Object.freeze()来冻结对象,源码中如果有 property.configurable === false 则不调用 getter 和 setter 方法

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

Vue2 中检测数组的变化并没有采用 defineProperty,数组长度不确定,修改索引的情况不多(如果直接使用 defineProperty 会浪费大量性能)。采用重写数组的变异方法来实现(函数劫持 push pop unshift shift splice reverse sort)

initData -> observe -> 对我们传入的数组进行原型链修改,后续调用的方法都是重写后的方法 -> 对数组中的每个对象也再次进行代理

修改数组索引值和修改数组长度是无法监测的。如 arr[1] = 100 arr.length = 200 不会触发视图更新

3. Vue中如何进行依赖收集

  • 所谓的依赖收集就是观察者模式 被观察者指的是数据,观察者(watcher)有三种:渲染watcher、计算属性watcher和用户watcher
  • 一个watcher中可能对应着多个数据(多个dep),watcher中还需要保存dep(依赖收集器,一个属性数据对应一个dep),重新渲染的时候可以让属性重新记录watcher,计算属性watcher也会用到这个dep
  • 页面重新渲染时,就会重新进行依赖收集,调用defineProperty内的get方法会调用cleanupDeps来对watcher和dep的关系进行清理,将watcher中没有对应的属性dep删除

多对多的关系 一个dep对应多个watcher,一个watcher对应多个dep。默认渲染的时候会进行依赖收集(会触发get方法)

![image-20230223205244375](/Users/mac/Library/Application Support/typora-user-images/image-20230223205244375.png)

![image-20230223205623664](/Users/mac/Library/Application Support/typora-user-images/image-20230223205623664.png)

4. 如何理解Vue中模板编译原理

我们传递的是template属性,我们需要将这个template编译成render函数

  • template -> ast语法树
  • 对语法树进行标记(标记的是静态节点)
  • 将ast语法树生成render函数

最终每次渲染都可以调用render函数返回对应的虚拟节点(递归是先子后父)

5. Vue生命周期钩子是如何实现的

利用发布订阅模式,将用户写的钩子维护成一个数组,后续一次调用callHook。主要靠的是mergeOptions

6. 组件渲染的顺序

父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted -> 父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

7. Vue的生命周期方法有哪些?一般在哪一步发送请求及原因

  • beforeCreate 没有实现响应式数据 vue3中废弃

    • initLifecycle 初始化生命周期 组件的父子关系 $parent $children
    • initEvents 初始化事件 $on $off $emit
    • initRender 初始化渲染函数
  • created 拿到的是响应式的属性(不涉及dom渲染)这个api可以在服务端渲染中使用,在Vue3中废弃

    • initInjections 在data/props之前简析inject
    • initState 初始化data/props
    • initProvide 在data/props之后简析provide
  • beforeMount

    • 将模板编译成render函数
  • mounted 替换正式节点并渲染,可以获取$el

  • beforeUpdate 数据更新前

  • updated 数据更新后

  • beforeDestroy 在调用$destroyed后立即触发,此时wathchers、子组件和事件监听器都还没被销毁

  • destroyed 调用完$destroyed后触发

    • wathchers销毁 vm._watcher.teardown()
    • 子组件销毁
    • 事件监听器销毁 vm.$0ff()
  • errorCaptured 捕获错误

一般多在mounted中(created不是比mounted早吗?代码是同步执行的,请求是异步的)服务端渲染不是都在created中吗?真正使用服务端渲染的时候也不会使用created(服务端没有dom,也没有mounted)

因为生命周期是顺序调用的(同步的),请求是异步的,所以最终获取大数据肯定是在mounted之后的

8. Vue组件data为什么必须是一个函数

针对根实例而言,只能用new Vue(),但组件是通过同一个构造函数Sub多次创建实例,如果是同一个对象的话,那么数据就会被互相影响。因为每个组件的数据源都是独立的,所以每次都调用data函数都会返回一个新的对象

9. nextTick在哪里使用?原理是?

nextTick内部采用了异步任务进行了包装(多个nextTick调用 会被合并成一次 内部合并回调)最后在异步任务中批处理,主要应用场景就是异步更新(默认调度的时候 就会添加一个nextTick任务)用户为了获取最终的渲染结果需要在内部任务执行之后再执行用户逻辑,这时候用户需要将对应的逻辑放到nextTick中

每次调用nextTick,并不会让函数立即执行,而是放在callbacks队列中,最后依次调用

当改变data中数据,也就是依赖改变时,也会将其放入callbacks队列

10. computedwatch的区别

computed和watch的相同点:底层都会创建一个watcher(区别在于computed定义的属性可以在模板中使用,watch不能在视图中使用)

  • computed 默认不会立即执行,只有取值的时候才会执行,内部会维护一个dirty属性,来控制依赖的值是否发生变化
  • watch 默认用户提供一个回调函数,数据变化了就调用这个回调。我们可以监控某个数据的变化,数据变化了执行某些操作

11. Vue.set方法是如何实现的

Vue.set方法是Vue中的一个补丁方法(正常添加属性时不会触发更新的,数组无法监控到索引和长度)

如何实现的:1. 响应式对象添加属性;2. 将添加属性后的对象重新实现响应式 3. 手动更新

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 如果是数组,且修改的是索引值,使用数组的splice方法
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  // 已经有的属性直接修改即可,已有的属性已经是响应式的了
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 如果不是响应式的(没有__ob__属性则不是响应式的),则赋值后再使用defineReactive重新将其响应式化,最后使用ob.dep.notify来通知更新
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

12. Vue为什么需要虚拟dom

  • 虚拟dom的好处是什么?我们写的代码可能针对不同的平台来使用(weex,web,小程序),而虚拟dom可以跨平台,不需要考虑平台问题
  • 不用关心兼容性问题,可以在上层将对应的渲染方法传递给我,我来通过虚拟dom渲染即可
  • diff算法针对更新的时候,有了虚拟dom之后我们可以通过diff算法来找到最后的差异进行修改真实dom(类似于在真实dom之间做了一个缓存)

13. Vuediff算法原理

diff算法的特点就是平级比较,内部采用了双指针方式进行了优化,优化了常见的操作。采用了递归比较的方式

  • 针对一个节点的diff算法

    • 先拿出根节点来进行比较,如果是用一个节点则比较属性,如果不是同一个节点则直接换成最新的即可
    • 同一个节点比较属性后,复用老节点
  • 比较儿子

    • 一方有儿子,一方没儿子(删除,添加)
    • 两方都有儿子
      • 优化比较 头头 尾尾 交叉比对
      • 都比较完后,做一个映射表,用新的去映射表中查找此元素是否存在,存在则移动,不存在则插入,最后删除多余的

14. 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要进行虚拟DOM进行diff检测差异

  • 如果给每个属性都去增加watcher,而且粒度大小也不好控制,降低watcher的数量(每一个组件都有一个watcher)可以动过diff算法优化渲染过程。通过diff算法和响应式原理这种处理一下

15. 请说明Vue中key的作用和原理,谈谈你对它的理解

sameVnode方法中会根据key来判断两个元素是否是同一个元素,key不相同说明不是同一个元素(key在动态列表中不要使用索引),我们使用key尽量要保证key的唯一性(这样可以优化diff算法)

16. 谈谈对Vue组件化的理解

  • 组件的优点:组件的复用可以根据数据渲染对应的组件,把组件相关的内容放在一起(方便复用)合理规划组件,可以做到更新的时候是组件级更新(组建的特性:属性,事件,插槽)

  • Vue中怎样处理组件:

    1. 根据用户的传入的对象生成一个组建的构造函数

    2. 根据组件产生对应的虚拟节点 data:

    3. 做组件初始化,将虚拟节点转化成真实节点(组建的init方法)new Sub().$mount()

function sameVnode (a, b) {
  return (
    a.key === b.key &&
    a.asyncFactory === b.asyncFactory && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

17. Vue组件渲染流程 (init)

  1. 定义组件 vm.$options.components['my'] = {my: 模板}
  2. 创建组建的虚拟节点 createCoponent {tag: 'my', data:{hook: {init}}, componentOptions: {Ctor: Vue.extend({my: 模板})}}
  3. 创建真实节点 createCoponent init -> new 组件().$mount() -> vm.componentInstance
  4. vm.$el 插入到父元素中

18. Vue组件的更新流程 (prepatch)

  • 组件更新会触发组件的prepatch方法,会复用组件,并且比较组件的属性、事件、插槽
  • 父组件给子组件传递的属性(props)是响应式的,在模板中使用会做依赖收集,收集自己的组件watcher
  • 稍后组件更新了,会重新给props赋值,赋值完成后会触发watcher重新更新

19. Vue中异步组件原理

Vue中异步组件的写法有很多,主要用来对大的组件进行异步加载,如Markdown组件,editor组件。就是先渲染一个注释标签,等组件加载完毕后,租后再重新渲染forceUpdate(类似于图片懒加载);使用异步组件会配合webpack(webpack可以支持code-splitting,把文件进行单独的分割,再进行加载)

原理:异步组件默认不会调用Vue.extend方法,所有Ctor上没有cid属性,没有cid属性就是异步组件。会先渲染一个占位符,但是如果有loading会先渲染loadingComponent,第一轮就结束了。如果用户调用了resolve,会将结果赋值给factory.resolved上面,强制重新渲染。重新渲染的时候再次进入resolveAsyncComponent中,会直接拿到factory.resolved的结果来渲染

const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        template: '<div>async component</div>'
      })
    }, 2000)
  }),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})

export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
): Class<Component> | void {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }

  if (isDef(factory.resolved)) {
    return factory.resolved
  }

  const owner = currentRenderingInstance
  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
    // already pending
    factory.owners.push(owner)
  }

  if (isTrue(factory.loading) && isDef(factory.loadingComp)) { // 如果factory.loading存在就直接渲染loading组件
    return factory.loadingComp
  }

  if (owner && !isDef(factory.owners)) {
    const owners = factory.owners = [owner]
    let sync = true
    let timerLoading = null
    let timerTimeout = null

    ;(owner: any).$on('hook:destroyed', () => remove(owners, owner))

    const forceRender = (renderCompleted: boolean) => {
      for (let i = 0, l = owners.length; i < l; i++) {
        (owners[i]: any).$forceUpdate()
      }

      if (renderCompleted) {
        owners.length = 0
        if (timerLoading !== null) {
          clearTimeout(timerLoading)
          timerLoading = null
        }
        if (timerTimeout !== null) {
          clearTimeout(timerTimeout)
          timerTimeout = null
        }
      }
    }

    const resolve = once((res: Object | Class<Component>) => {
      // cache resolved
      factory.resolved = ensureCtor(res, baseCtor)
      // invoke callbacks only if this is not a synchronous resolve
      // (async resolves are shimmed as synchronous during SSR)
      if (!sync) {
        forceRender(true)
      } else {
        owners.length = 0
      }
    })

    const reject = once(reason => {
      process.env.NODE_ENV !== 'production' && warn(
        `Failed to resolve async component: ${String(factory)}` +
        (reason ? `\nReason: ${reason}` : '')
      )
      if (isDef(factory.errorComp)) {
        factory.error = true
        forceRender(true)
      }
    })

    const res = factory(resolve, reject)

    if (isObject(res)) {
      if (isPromise(res)) {
        // () => Promise
        if (isUndef(factory.resolved)) {
          res.then(resolve, reject)
        }
      } else if (isPromise(res.component)) {
        res.component.then(resolve, reject)

        if (isDef(res.error)) {
          factory.errorComp = ensureCtor(res.error, baseCtor)
        }

        if (isDef(res.loading)) {
          factory.loadingComp = ensureCtor(res.loading, baseCtor)
          if (res.delay === 0) {
            factory.loading = true
          } else {
            timerLoading = setTimeout(() => {
              timerLoading = null
              if (isUndef(factory.resolved) && isUndef(factory.error)) {
                factory.loading = true
                forceRender(false)
              }
            }, res.delay || 200)
          }
        }

        if (isDef(res.timeout)) {
          timerTimeout = setTimeout(() => {
            timerTimeout = null
            if (isUndef(factory.resolved)) {
              reject(
                process.env.NODE_ENV !== 'production'
                  ? `timeout (${res.timeout}ms)`
                  : null
              )
            }
          }, res.timeout)
        }
      }
    }

    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}

20. 函数式组件的优势及原理

React 中也区分两种组件,一种叫类组件,一种叫函数式组件。类组件有this;函数组件没有类就没有this,也没有所谓的状态,没有生命周期,好处就是性能好,不需要创建watcher了。函数式组件就是调用render拿到返回结果来渲染,所以性能高

21. Vue组件间传值的方式及区别

  • props 父传递数据给儿子 props的原理就是把解析后的props,验证后就会将属性定义在当前的实例上,vm._props(这个对象上的属性都是通过defineReactive来定义的(都是响应式的)组件在渲染的过程中会去vm上取值,_props属性会被代理到vm上)

  • Emit 儿子触发组件更新,在创建虚拟节点的时候将所有的事件,绑定到了listeners,通过$on方法绑定事件,通过$emit方法来触发事件(发布订阅模式)

  • eventBus 原理就是 发布订阅模式 $bus = new Vue() 简单的通信可以采用这种方式

  • $parent $children 就是在创造子组件的时候,会将父组件的实例传入。在组件本身初始化的时候会构建组件间的父子关系,$parent获取父组件的实例,通过$children可以获取所有的子组件的实例

  • ref 可以获取dom元素和组件实例(虚拟dom没有处理ref,这里无法拿到实例,也无法获取组件)创建dom的时候如何处理ref的,会将用户所有的dom操作及属性都维护到一个cbs属性中,cbs(create update insert destroy ...)一次调用cbs中create等方法。这里就包含ref相关的操作,会操作ref并且赋值

  • provide (在父组件中将属性暴露出来)inject 在后代组件中通过inject注入属性 在父组件中提供数据,在子组件中递归向上查找

  • $attrs(所有的dom上的属性,不包含props中的属性,都是响应式的 ) $listeners(组件上所有的事件)

  • Vue.observable 可以创建一个全局的对象用于通信

  • Vuex

22. v-if和v-for哪个优先级更高?

<div>
 <span v-if="flag" v-for="i in 3"></span>
</div>

function render() {
  with(this) {
    return _c('div', _l((3), function (i) {
      return (flag) ? _c('span') : _e()
    }), 0)
  }
}

从上模板解析结果可知,v-for优先级更高,v-for渲染成函数,v-if会变成三元表达式。v-if 和 v-for 不要在一起使用

![image-20230225164514337](/Users/mac/Library/Application Support/typora-user-images/image-20230225164514337.png)

23. v-if和v-show的区别

  • v-if 控制是否渲染

  • v-show 控制的是样式(display: none)

  • v-if在编译的时候会变成三元表达式,而v-show会编译成一个指令

    <div>
     <span v-show="true"></span>
    </div>
    
    function render() {
      with(this) {
        return _c('div', [_c('span', {
          directives: [{
            name: "show",
            rawName: "v-show",
            value: (true),
            expression: "true"
          }]
        })])
     ¸ }
    }
    

24. V-if, v-model, v-for的实现原理

  • v-if会被编译成三元表达式

  • v-for会被编译成 _l函数(renderList函数)

  • v-model 放在表单元素上可以实现双向绑定,放在组件上就不一样了

    • v-model放在不同的元素上会编译出不同的结果,针对文本来说会处理文本(会被编译成value + input + 指令处理)value和input实现双向绑定阻止中文的触发,指令的作用就是处理中文输入完毕后,手动触发更新($event.target.composing);input的type为checkbox时则绑定change事件

      if (el.component) {
        genComponentModel(el, value, modifiers)
        // component v-model doesn't need extra runtime
        return false
      } else if (tag === 'select') {
        genSelect(el, value, modifiers)
      } else if (tag === 'input' && type === 'checkbox') {
        genCheckboxModel(el, value, modifiers)
      } else if (tag === 'input' && type === 'radio') {
        genRadioModel(el, value, modifiers)
      } else if (tag === 'input' || tag === 'textarea') {
        genDefaultModel(el, value, modifiers)
      } else if (!config.isReservedTag(tag)) {
        genComponentModel(el, value, modifiers)
        // component v-model doesn't need extra runtime
        return false
      }
      
    • v-model绑定到组件上时,会编译一个model对象,组件在创建虚拟节点的时候会有这个对象。看一下里面是否有自定义的prop和event,如果没有则会被简析成value + input语法

      // transform component v-model info (value and callback) into
      // prop and event handler respectively.
      function transformModel (options, data: any) {
        const prop = (options.model && options.model.prop) || 'value'
        const event = (options.model && options.model.event) || 'input'
        ;(data.attrs || (data.attrs = {}))[prop] = data.model.value
        const on = data.on || (data.on = {})
        const existing = on[event]
        const callback = data.model.callback
        if (isDef(existing)) {
          if (
            Array.isArray(existing)
              ? existing.indexOf(callback) === -1
              : existing !== callback
          ) {
            on[event] = [callback].concat(existing)
          }
        } else {
          on[event] = callback
        }
      }
      
      <div id="app">
        <my-button v-model="msg"></my-button>
      </div>
      
      Vue.component('my-button'{
      	props: {
      		msg: {}	
      	},
      	model: {
      		prop: 'msg',
      		event: 'changeMsg'
      	},
      template: `<div>{{msg}}<button @click="$emit('changeMsg', 'newMsg')"></button></div>`
      })
      
      const vm = new Vue({
      	el: "#app",
      	data() {
      		return { msg: 'hello' }
      	}
      })
      

25. Vue中.sync修饰符的作用,用法及实现原理

和v-model一样,这个api是为了实现状态同步的,这个东西在vue3中被移除了

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

<!-- 当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用:-->

<text-document v-bind:title.sync="doc.title"></text-document>

26. Vue.use是干什么的?原理是什么?

  • Vue.use方法,目的就是将Vue的构造函数传递到插件中,让所有的插件依赖的是同一个版V
  • 如果插件是函数,默认调用插件,如果插件是对象,则调用对象的install方法

    为什么需要有一个install方法?原因就是如果用户导出了一个类,在内上写一个install方法,会优先调用,否则会被当成函数来执行

  • vue-router和vuex插件中的Vue是通过参数传进去的
Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 如果插件已经安装过,则会直接返回Vue
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    const args = toArray(arguments, 1)
    // 将Vue插入到第一个参数处
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      // 将包含Vue的参数传给plugin,并将this指向plugin
      // 即 plugin.install(Vue)
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
}

const plugin1 = function(Vue, options) {}
Vue.use(plugin1)

const plugin2 = {
	install(Vue, options) {

	}
}
Vue.use(plugin2)
// 当 install 方法被同一个插件多次调用,插件将只会被安装一次。

27. 组件中写name选项有哪些好处及作用?

  • 在vue中有name属性的组件可以被递归调用(在写模板语法的时候,可以通过name属性递归调用自己)

  • 在声明组件的时候Sub.options.components[name] = Sub

  • 用来标记组件,通过name来找到对应的组件

Vue.component('cmp1', {
	name: 'cmp1',
	template: '<div>cmp1</div>'
})

Vue.component('cmp2', {
	name: 'cmp2',
	template: '<div><cmp1></cmp1></div>'
})

28. Vue中slot是如何实现的?什么时候使用它?

  • 普通插槽(普通插槽渲染作用于在父组件中)

    1. 在解析组件的时候会将组件的children放到componentOptions上作为虚拟节点的属性
    2. 将children取出来放到组件的vm.$options._renderChildren中
    3. 做出一个映射表放到vm.$slots上 -> 将结果放到vm.$scopedSlots上 vm.$scopedSlots =
    4. 组件渲染的时候UI调用_t方法,此时会去vm.$scopedSlots找到对应的函数来渲染内容
  • 具名插槽

    相对于普通插槽增加了个名字,相对于普通插槽的default

  • 作用域插槽(作用域插槽渲染的作用域是子组件的)

    1. 作用域插槽渲染的时候不会作为children,而是将作用于插槽做成了一个属性scopedSlots

    2. 制作一个映射关系 $scopedSlots = {default:fn:function({msg}){return _c('div', {},[_v(_s(msg))])}}

    3. 渲染组件的模板的时候,会通过name找到对应的函数,将数据传入到函数中,此时才渲染虚拟节点,用这个虚拟节点替换_t('default')

29. Keep-alive平时在哪里使用?原理是?

  1. keep-alive在路由中使用
  2. 在component:is中使用(缓存)
  • keep-alive的原理就是默认缓存加载过的组件对应的实例,内部采用了LRU算法

    LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。

  • 下次组件加载的时候,会找到对应的缓存的节点来进行初始化,但是会复用上次缓存$el来触发(不用再做将虚拟节点转换成真实节点了) 直接从init -> prepatch

  • 更新和销毁会触发actived和deactived

30. 如何理解自定义指令

  • 自定义指令就是用户定义好对应的钩子,当元素在不同的状态时会调用对应的钩子(所有的钩子会被合并到cbs对应的方法上,到时候依次调用)

  • 一般用于操作dom时 使用

31. Vue事件修饰符有哪些?其实现原理是什么?

  • 主要有以下修饰符

    1. stop:阻止事件冒泡,顺序是执行顺序是div>body>document,js默认开启事件冒泡。e.stopPropagation()、e.stopImmediatePropagation() 阻止改dom所有该类型事件的冒泡。
    2. self:阻止事件冒泡&&阻止事件捕获,div>body>document,document>body>div,默认是冒泡。
    3. capture: 开启事件捕获,写在需要捕获的元素上,他会捕获内部元素的同类型事件 document>body>div // 默认关闭事件捕获,开启事件冒泡。addEventListener("click", this.Parent, {capture:true}); capture,true开启,反之关闭
    4. once: 事件只触发一次就会被移除。addEventListener("click", this.Parent, {once:true}); once,true开启,反之关闭
    5. prevent: 阻止事件的默认行为 e.preventDefault() e.defaultPrevented 查看默认阻止的状态,true:已阻止。addEventListener("click", this.Parent, {passive:true}); passive,true:阻止preventDefault函数调用,反之不阻止
    6. native: 事件直接绑定到组件的原生节点上,<el-input @click.native/> click事件直接绑定在input元素上。
    7. lazy: vue v-model 修饰符。修改语法糖的input事件为change。
    8. number: vue 修饰符。把值转换为number类型。
    9. trim: vue 修饰符。去除数值前后空格。
    10. passive: 事件的默认行为立即执行,无需等待事件回调执行完毕
  • 实现树要靠的是模板编译原理

    • 编译的时候直接编译到事件内部,如stop,prevent

      <div @click.stop></div>
      
      // 上述代码会编译成如下代码
      function render() {
        with(this) {
          return _c('div', {
            on: {
              "click": function ($event) {
                $event.stopPropagation(); // 当检测到修饰符为stop时,会在对应的事件上添加
              }
            }
          })
        }
      }
      
    • 编译的时候增加标识(!: capture ~: once &: passive)

      // check capture modifier
      if (modifiers.capture) {
        delete modifiers.capture
        name = prependModifierMarker('!', name, dynamic)
      }
      if (modifiers.once) {
        delete modifiers.once
        name = prependModifierMarker('~', name, dynamic)
      }
      /* istanbul ignore if */
      if (modifiers.passive) {
        delete modifiers.passive
        name = prependModifierMarker('&', name, dynamic)
      }
      
      let events
      if (modifiers.native) {
        delete modifiers.native
        events = el.nativeEvents || (el.nativeEvents = {})
      } else {
        events = el.events || (el.events = {})
      }
      
    • 键盘事件

      <div @keyup.enter></div>
      
      const keyCodes: { [key: string]: number | Array<number> } = {
        esc: 27,
        tab: 9,
        enter: 13,
        space: 32,
        up: 38,
        left: 37,
        right: 39,
        down: 40,
        'delete': [8, 46]
      }
        
      // #4868: modifiers that prevent the execution of the listener
      // need to explicitly return null so that we can determine whether to remove
      // the listener for .once
      const genGuard = condition => `if(${condition})return null;`
      
      const modifierCode: { [key: string]: string } = {
        stop: '$event.stopPropagation();',
        prevent: '$event.preventDefault();',
        self: genGuard(`$event.target !== $event.currentTarget`),
        ctrl: genGuard(`!$event.ctrlKey`),
        shift: genGuard(`!$event.shiftKey`),
        alt: genGuard(`!$event.altKey`),
        meta: genGuard(`!$event.metaKey`),
        left: genGuard(`'button' in $event && $event.button !== 0`),
        middle: genGuard(`'button' in $event && $event.button !== 1`),
        right: genGuard(`'button' in $event && $event.button !== 2`)
      }
      
posted @   忆无痕  阅读(337)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示