最新的Vue面试题大全含源码级回答,吊打面试官系列

}

其实nextTick就是一个把回调函数推入任务队列的方法。

6.data为什么是函数


如果组件里 data 直接写了一个对象的话,那么在模板中多次声明这个组件,组件中的 data 会指向同一个引用。

此时对 data 进行修改,会导致其他组件里的 data 也被修改。使用函数每次都重新声明一个对象,这样每个组件的data都有自己的引用,就不会出现相互污染的情况了。

7.组件通信方式


1. props和$on$emit

适合父子组件的通信,通过props传递响应式数据,父组件通过$on监听事件、子组件通过$emit发送事件。

on和emit是在组件实例初始化的时候通过initEvents初始化事件,在组件实例vm._events赋值一个空的事件对象,通过这个对象实现事件的发布订阅。下面是事件注册的几个关键函数:

// 组件初始化event对象,收集要监听的事件和对应的回调函数

function initEvents (vm: Component) {

vm._events = Object.create(null)

vm._hasHookEvent = false

// init parent attached events

const listeners = vm.$options._parentListeners

if (listeners) {

updateComponentListeners(vm, listeners)

}

}

// 注册组件监听的事件

function updateComponentListeners (

vm: Component,

listeners: Object,

oldListeners: ?Object

) {

target = vm

updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)

target = undefined

}

2. ref$parent$children,还有$root

  • ref: 在普通DOM元素上声明就是DOM元素的引用,组件就是指向组件实例。

  • $parent:访问组件的父组件实例

  • $children:访问所有的子组件集合(数组)

  • $root: 指向root实例

3. Event Bus

通常是创建一个空的Vue实例作为事件总线(事件中心),实现任何组件在这个实例上的事件触发与监听。原理就是一个发布订阅的模式,跟$on``$emit一样,在实例化一个组件的事件通过initEvents初始化一个空的event对象,再通过实例化后的这个bus(vue实例)手动的$on$emit添加监听和触发的事件,代码在src/core/instance/events:

Vue.prototype.$on = function (event: string | Array, fn: Function): Component {

const vm: Component = this

// 传入的事件如果是数组,就循环监听每个事件

if (Array.isArray(event)) {

for (let i = 0, l = event.length; i < l; i++) {

vm.$on(event[i], fn)

}

} else {

// 如果已经有这个事件,就push新的回调函数进去,没有则先赋值空数组再push

(vm._events[event] || (vm._events[event] = [])).push(fn)

// instead of a hash lookup

if (hookRE.test(event)) {

vm._hasHookEvent = true

}

}

return vm

}

Vue.prototype.$emit = function (event: string): Component {

const vm: Component = this

let cbs = vm._events[event]

// 循环调用要触发的事件的回调函数数组

if (cbs) {

cbs = cbs.length > 1 ? toArray(cbs) : cbs

const args = toArray(arguments, 1)

const info = event handler for "${event}"

for (let i = 0, l = cbs.length; i < l; i++) {

invokeWithErrorHandling(cbs[i], vm, args, vm, info)

}

}

return vm

}

4. attrs、listeners

  • $attrs: 包含了父作用域没被props声明绑定的数据,组件可以通过v-bind="$attrs"继续传给子组件

  • $listernes: 包含了父作用域中的v-on(不含 .native 修饰器的) 监听事件,可以通过v-on="$listeners"传入内部组件

5. provide、inject

父组件通过provide注入一个依赖,其所有的子孙组件可以通过inject来接收。要注意的是官网有这一段话:

提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

所以Vue不会对provide中的变量进行响应式处理。要想 inject 接受的变量是响应式的,provide 提供的变量本身就需要是响应式的。实际上在很多高级组件中都可以看到组件会将this通过provide传递给子孙组件,包括element-ui、ant-design-vue等。

6. vuex 状态管理实现通信

vuex是专为vue设计的状态管理模式。每个组件实例都有共同的store实例,并且store.state是响应式的,改变state唯一的办法就是通过在这个store实例上commit一个mutation,方便跟踪每一个状态的变化,实现原理在下面的vuex原理里有讲。

8.computed、watch、method有什么区别


computed:有缓存,有对应的watcher,watcher有个lazy为true的属性,表示只有在模板里去读取它的值后才会计算,并且这watcher在初始化的时候会赋值dirty为true,watcher只有dirty为true的时候才会重新求值,重新求值后会将dirty置为false,false会直接返回watcher的value,只有下次watcher的响应式依赖有更新的时候,会将watcher的dirty再置为false,这时候才会重新求值,这样就实现了computed的缓存。

watch:watcher的对象每次更新都会执行函数。watch 更适用于数据变化时的异步操作。如果需要在某个数据变化时做一些事情,使用watch。

method: 将方法在模板里使用,每次视图有更新都会重新执行函数,性能消耗较大。

9.生命周期


官网对生命周期的说明:

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

生命周期就是每个Vue实例完成初始化、运行、销毁的一系列动作的钩子。

基本上可以说8 个阶段创建前/后,载入前/后,更新前/后,销毁前/后。

  • 创建前/后: 在 beforeCreate 阶段,vue 实例的挂载元素 el 还没有。

  • 载入前/后:在 beforeMount 阶段,vue 实例的$el 和 data 都初始化了,但还是挂载之前为虚拟的 dom 节点,data.message 还未替换。在 mounted 阶段,vue 实例挂载完成,data.message 成功渲染。

  • 更新前/后:当 data 变化时,会触发 beforeUpdate 和 updated 方法。

  • 销毁前/后:在执行 destroy 方法后,对 data 的改变不会再触发周期函数,说明此时 vue 实例已经解除了事件监听以及和 dom 的绑定,但是 dom 结构依然存在

结合源码再理解,在源码中生命周期钩子是用callHook函数调用的。看下callHook函数:

function callHook (vm: Component, hook: string) {

pushTarget()

const handlers = vm.$options[hook]

const info = ${hook} hook

if (handlers) {

for (let i = 0, j = handlers.length; i < j; i++) {

invokeWithErrorHandling(handlers[i], vm, null, vm, info)

}

}

if (vm._hasHookEvent) {

vm.$emit(‘hook:’ + hook)

}

popTarget()

}

接收一个vm组件实例的参数和hook,取组件实例的$options传入的hook属性值,有的话会循环调用这个钩子的回调函数。在调用生命钩子的回调函数之前会临时pushTarget一个null值,也就是将Dep.target置为空来禁止在执行生命钩子的时候进行依赖收集。

vm.$emit(‘hook:’ + hook)则是用来给父组件监听该组件的回调事件。

接下来看每个生命钩子具体调用的时机。

1. beforeCreate、created:

Vue.prototype._init = function (options?: Object) {

initLifecycle(vm)

initEvents(vm)

initRender(vm)

callHook(vm, ‘beforeCreate’)

initInjections(vm) // resolve injections before data/props

initState(vm)

initProvide(vm) // resolve provide after data/props

callHook(vm, ‘created’)

if (vm.$options.el) {

vm. m o u n t ( v m . mount(vm. mount(vm.options.el)

}

}

在执行beforeCreate之前调用了 initLifecycle、initEvents、initRender函数,所以beforeCreate是在初始化生命周期、事件、渲染函数之后的生命周期。

在执行created之前调用了initInjections、initState、initProvide,这时候created初始化了data、props、watcher、provide、inject等,所以这时候就可以访问到data、props等属性。

2. beforeMount、mounted

3. beforeUpdate、updated

这两个钩子函数是在数据更新的时候进行回调的函数。在src/core/instance/lifecycle.js找到beforeUpdate调用的代码:

new Watcher(vm, updateComponent, noop, {

before () {

if (vm._isMounted && !vm._isDestroyed) {

callHook(vm, ‘beforeUpdate’)

  • 27
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值