5.vue面试题

1,395 阅读37分钟

1.谈谈你对vue的理解

Vue是一个渐进式JavaScript框架,它专注于构建用户界面。Vue的核心思想是数据驱动和组件化。通过将页面拆分成独立的组件,可以更好地管理代码,提高代码的复用性和可维护性。

  1. 渐进式
  2. 数据响应式
  3. 组件化

2.vue的组件加载和渲染顺序

加载:先加载父组件,再加载子组件。当父组件被加载的时候,他会递归加载子组件。 渲染:组件由深度优先遍历决定的,先渲染最深层的子组件,再依次向上渲染父组件。

先加载父组件,然后加载子组件。当父组件给加载时,他会递归加载其所有子组件,并且按照顺序依次渲染他们。

组件的渲染,按照先渲染最深层的子组件,再依次向上渲染其父组件的方式。

Vue中组件的加载和渲染顺序是先加载父组件,再递归地加载子组件,然后按照深度优先遍历的方式渲染子组件,再依次向上渲染父组件。

3.vue的数据绑定机制是如何实现的

是通过数据劫持,发布/订阅模式实现的。当数据发生变化的时,会自动更新视图,并通过虚拟DOM对比算法提高性能。这个机制可以有效地简化开发过程,提高代码的可维护性和可读性。

4.vue2的响应式数据原理?

1.首先对于数据可以通过Observer把对象的属性都变成响应式对象,Observe是通过Object.defineProperty将对象转化为带有getter和setter的属性,它会递归遍历对象的所有属性,以完成深度的属性转换。

由于遍历时只能遍历到对象的当前属性,因此无法监测到将来动态增加或删除的属性,因此vue提供了setset和delete两个实例方法,让开发者通过这两个实例方法对已有响应式对象添加或删除属性。 而对于数组是这样处理的,首先进来判断当前是不是一个数组对象,如果是一个数组对象,则开始如下的操作,先产生一个代理对象,const proxyPrototype = Object.create(arrayPrototype) array.prototype 产生的一个对象,然后将传进来的数组对象的原型设置为这个代理对象,最后通过定义Object.defineProperty重新定义数组的七个方法,然后再七个方法里面,先执行array.prototype 的方法,然后再执行派发更新的操作。

// Observer.js
if (Array.isArray(value)) {
  Object.setPrototypeOf(value, proxyPrototype) // value.__proto__ === proxyPrototype
  this.observeArray(value)
}

// array.js
const arrayPrototype = Array.prototype // 缓存真实原型

// 需要处理的方法
const reactiveMethods = [
  'push',
  'pop',
  'unshift',
  'shift',
  'splice',
  'reverse',
  'sort'
]

// 增加代理原型 proxyPrototype.__proto__ === arrayProrotype
const proxyPrototype = Object.create(arrayPrototype)

// 定义响应式方法
reactiveMethods.forEach((method) => {
  const originalMethod = arrayPrototype[method]
  // 在代理原型上定义变异响应式方法
  Object.defineProperty(proxyPrototype, method, {
    value: function reactiveMethod(...args) {
      const result = originalMethod.apply(this, args) // 执行默认原型的方法
      // ...派发更新...
      return result
    },
    enumerable: false,
    writable: true,
    configurable: true
  })
})

由于遍历时只能遍历到对象的当前属性,因此无法监测到将来动态增加或删除的属性,因此vue提供了setset和delete两个实例方法,让开发者通过这两个实例方法对已有响应式对象添加或删除属性。因为这个原因vue3采用proxy来处理。

Vue会为响应式对象中的每个属性、对象本身、数组本身创建一个Dep实例,每个Dep实例都有能力做以下两件事:

  • 记录依赖:是谁在用我
  • 派发更新:我变了,我要通知那些用到我的人

执行该函数的更新组件render函数的时候,会创建一个watcher,watcher里面把更新组件render函数穿进去,然后在watcher里面把更新组件render函数执行一遍,执行的时候读取到响应式数据,这时就会调用前面的响应式数据的get方法,然后响应数据就把这个watcher放到Dep里面,这就是收集了依赖。 如果后面需要响应式数据改变了,这时就会触发set方法,然后来把更新dep里面的watcher去重新执行组件的更新组件render方法,由于可能会遇到一个组件的两个响应式数据都改变,如果直接执行就会执行两次。所以vue里面的处理是通过将watcher内部收到的派发更新去交给一个调度器的东西去处理。

调度器维护一个执行队列,该队列同一个watcher仅会存在一次,队列中的watcher不是立即执行,它会通过一个叫做nextTick的工具方法,把这些需要执行的watcher放入到事件循环的微队列中,nextTick的具体做法是通过Promise完成的。

由于我们是异步执行的,所以我们要使用nextTick()的回调函数去拿dom的信息,如果不这样,可能拿到的数据还是修改前的。

image.png

5.vue的computed和watch的区别?

Vue中的computed和watch都是用来监听数据变化并做出相应的操作的。 computed属性是通过计算已有的属性值得出的一个新值,这个值会被缓存,可以看成一个缓存的属性,只有在依赖数据发生变化时才会重新计算,这样可以避免重复计算和提高性能。 watch属性用于监听数据的变化,执行回调函数,回调里面可以进行异步操作或者其他复杂的处理,还有一点是watch不会缓存计算结果。

6.说下vue的keep alive

keep-alive是一个抽象组件,它可以将其包裹的组件进行缓存,从而在切换组件时可以避免重复创建和销毁组件,提高页面性能和用户体验。 keep-alive提供了两个钩子函数:activated和deactivated,用来在组件被激活或停用时执行一些逻辑,比如在组件被激活时执行一些数据初始化或者异步操作。

include和exclude用于指定需要缓存或排除的组件名称;max和min用于指定缓存的最大和最小数量。

7.vue next tick实现原理

在 Vue.js 中,当我们对数据进行修改时,Vue.js 会异步执行 DOM 更新。在某些情况下,我们需要在 DOM 更新完成后执行一些操作,这时就需要使用 Vue.nextTick() 方法。

原理: 如果浏览器支持 Promise,则会优先使用 Promise.then() 方法。如果不支持 Promise,则会使用原生的 setTimeout 方法模拟异步操作

比如在某些情况下需要获取某个元素的尺寸、位置等属性时,如果不使用 Vue.nextTick(),可能会获取到错误的结果。

8.vue diff 算法

image.png

在组件更新时,vue内部会使用render函数生成虚拟dom树,然后将新旧两树进行对比,找到差异点,最后更新到真实dom,对比差异所采用的算法叫diff算法。 vue采用深度递归、同层比较的方式进行比对。

Vue2的diff算法内部采用双指针的方式,它依次通过头头,尾尾,头尾,尾头的方式进行比较,在源码中判断两个节点是否是相同节点,是通过Key(vue里面设置的key)和tag(标签类型)来判断的,但是对于input还要看他们的type属性是不是一样的

如果是相同节点,则进入更新流程,并且把对应的指针往中间移动。更新流程主要是将对应的真实dom指向新的虚拟节点,更新真实dom的属性,处理当前节点的子节点,递归进行上面的操作。 如果经过上面的步骤,新节点中还有没处理的,则根据老节点列表生成一个key和位置的映射表,然后依次取出新节点的key,去映射表中找。如果找到了,则进入更新流程,如果新节点中在映射表找不到的,则需要新增真实dom。最后老节点里面没有用到的节点,则需要删除。

而Vue3采用的是快速地diff算法,通过双端对比的方式,先通过头头和尾尾对比找出可复用的节点。如果头尾对比结束,新节点列表中还有剩余节点,那么就建立一个数组,用来记录剩余新节点在老节点列表的位置,计算这个数组里面最长递增子序列,然后将最长递增序列中的节点位置保持不动,移动其他节点位置,来达到最少移动。 其实vue3的时间复杂度比vue2的还高,但是vue3减少dom的移动,因为dom的移动比js操作更消耗性能,所以还是利大于弊。

~~一个新旧节点位置映射表,用来记录剩余新节点在老节点列表中的位置。如果新节点在映射表中不到,可代表该新节点需要新建真实dom。而可以找到则表示可以复用,然后在根据新旧节点位置映射表中计算最长递增子序列,然后将最长递增序列中的节点位置保持不动,移动其他节点位置,来达到最少移动。 其实vue3的时间复杂度比vue2的还高,但是vue3减少dom的移动,因为dom的移动比js操作更消耗性能,所以还是利大于弊。 ~~

9.vue 在渲染列表的时候,为什么不建议用数组的下标当做列表的key值

因为要保证渲染列表的性能和正确性, 在Vue渲染列表时,每个元素需要一个唯一的key值来标识自己,这个key值会被用来判断列表中哪些元素需要更新、删除或新增。如果使用数组的下标作为key值,虽然可以满足每个元素key值唯一的需求,但是由于Vue的更新机制是基于diff算法实现的,使用数组下标作为key值会导致Vue无法正确地判断列表中元素的变化情况。

比方在删除和新增元素的时候使用key,比方说一个数组里面有【1,2,3】四个数据,现在把第2删除掉了,第一个数据没有问题,到第二个元素的时候,现在的index是1,这是会将原有数组的2 改成 3,如果是唯一的key,就不会存在这个问题。

10.谈一下对vuex的理解

跨组件通信,同时共享的数据多,代码逻辑复杂的话,这样就需要一个公共的地方存数据,取数据。vuex就是这么一个作用,然后组件之间数据共享的作用,可以说成是一个状态管理工具。

详细来说主要有下面的: state、mutations、actions和getters,modules。 其中,state是应用的状态,====>基本数据(数据源存放地)

而mutations用于修改state中的状态。=====> 主要是定义一些方法去改数据的。 而操作mutations的方法用commit 里面的方法。

store.commit('mutation的名字', payload);

actions则用于处理异步操作或批量的同步操作,最终通过mutations来改变state。 ====> 像一个装饰器,包裹mutations,使之可以异步. 调用actions里面的方法用

 this.$store.dispatch("asyncPower",payload);

modules ====> 模玦化Vuex

export default new Vuex.Store({
    modules:{
        banner,
        setting,
        about,
        project,
    },
    strict:true,
});

getters则用于对state中的数据进行计算或过滤。

  computed: {
    ...mapGetters("loginUser", ["status"]),
    ...mapState("loginUser", ["user"]),
  },

11.vue-router有哪几种导航钩子?

全局

1.全局前置守卫----router.beforeEach
const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  //  to:要去哪个页面
  //  from:从哪里来
  //  next:它是一个函数。
  //     如果直接放行 next()
  //     如果要跳到其它页 next(其它页)
})
  • to : 即将要进入的目标路由对象from :当前导航正要离开的路由
  • next():如果一切正常,则调用这个方法进入下一个钩子
  • next(false):去掉导航next('/login'):
  • 跳转到对应的路由next(error):执行-个error实例

在每次路由跳转之前执行,可以用来进行用户身份验证、路由拦截等操作

2. 全局后置钩子----router.afterEach

router.afterEach 注册一个全局后置钩子:

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

router.afterEach((to, from) => {
  // ...
})

在每次路由跳转之后执行,可以用来进行路由跳转后的操作,比如页面滚动、统计PV等操作。

3. 全局解析守卫----router.beforeResolve

在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

单个的

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

组件内的

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增) 在路由更新时执行,比如路由参数发生变化时
  • beforeRouteLeave 在离开当前路由时执行,可以用来进行页面数据的保存或弹出提示等操作。
const Foo = {
  template: `...`,
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

image.png

12.vue-router的核心原理

Vue Router是Vue.js官方提供的一款路由管理器,它通过监听URL变化,匹配路由规则,展示对应的组件内容,从而实现单页应用的路由控制。 总之,Vue Router是实现Vue.js单页应用路由控制的核心组件之一,它通过路由匹配、路由模式、路由导航、路由组件等多个方面实现了完整的路由控制逻辑,为开发者提供了强大的路由控制能力。

13.说一下vue中router和route的区别吧?

router 是用来跳转路由的,操作路由的。 route 是当前路由,可以获得当前路由的信息。

全局路由对象,用来操作路由。项目常拿来做页面跳转,比方说 this.router.push();this.router.push(); this.route 是用来获取当前激活路由的信息,比方说path,query,meta等属性。

14.Vue Router history 模式为什么刷新出现404?

原因是因为浏览器在刷新页面时会向服务器发送GET请求,但此时服务器并没有相应的资源来匹配这个请求,因为在History模式下,所有路由都是在前端路由中实现的,并没有对应的后端资源文件。

为了解决这个问题,我们需要在服务器端进行相关配置,让所有的路由都指向同一个入口文件(比如index.html),由前端路由来处理URL请求,返回对应的页面内容。具体的配置方式取决于服务器类型,常见的有Apache、Nginx等。

server {
    listen 80;
    server_name yourdomain.com;

    location / {
        root /usr/share/nginx/html;
        index index.html;
        try_files $uri $uri/ /index.html;
    }
}

这段代码会将所有请求都指向根目录下的index.html文件,让前端路由来处理URL请求。同时需要注意,在使用History模式时需要保证所有路由的访问路径都指向index.html,否则仍然会出现404错误。

15.Vue Router history 模式上线需要注意什么事项?

  1. 后端配置,防止刷新出现404问题
  2. 兼容性:History模式需要支持HTML5的history.pushState API,因此在一些较老的浏览器上可能会存在兼容性问题。需要在开发时做好相关的测试和兼容性处理。
  3. 打包发布:在使用Webpack等工具打包发布时,需要配置正确的publicPath,保证HTML中引用的资源路径正确。同时需要注意,如果项目使用了多个子路由,需要在打包时将所有的子路由都配置到publicPath中。 4.安全性问题,也需要后端配置好

16.用vue-router hash模式实现锚点?

1.使用路由,传锚点元素过来

this.$router.push({ path: '/yourpath#youranchor' })

2,使用原生API

mounted () {
  const anchor = document.getElementById('youranchor')
  if (anchor) {
    anchor.scrollIntoView()
  }
}

17.说下虚拟DOM和diff算法,key的作用?

在 Vue 中,虚拟DOM的概念被称为“VNode”,它是一个轻量级的JavaScript对象,用于描述DOM节点。当数据发生变化时,Vue 会创建一个新的VNode树,并通过diff算法来对比新旧VNode树,找到最小变更并进行渲染。这样可以避免对整个DOM树进行重绘,提高性能。 Vue中的key属性用于标识节点的唯一性,当节点需要移动时,key可以帮助Vue更准确地定位节点,避免不必要的操作。如果没有使用key,Vue会尝试通过就地复用和移动算法来尽可能减少DOM操作,但这可能会导致一些意外的行为,例如,数据不一致、输入框内容丢失等。

18.vue2和vue3有哪些区别?

vue3是vue.js的新版本,相比较vue.js 2,主要有以下区别:

  1. 性能提升:vue.js 3 在内部实现上进行大量的优化,使得渲染速度更快,内存占用更少。
  2. Composition API:Vue.js 3 引入了 Composition API,可以更好地组织和复用逻辑代码,提高代码的可维护性,对于逻辑功能复杂的组件更容易达到高内聚的效果。
  3. 更好的TypeScript支持:Vue.js 3 对 TypeScript 的支持更加友好,提供了完整的类型定义。
  4. 更好的Tree Shaking支持:Vue.js 3 支持更好的 Tree Shaking,可以更加精确地按需引入需要的模块。
  5. 更少的依赖:Vue.js 3 的核心库的依赖更少,可以减小打包体积。
  6. 更多的特性:Vue.js 3 支持更多的特性,如片段和Teleport等。

19.vue3有哪些新特性?

  • Composition API:允许开发者更灵活地组织和重用组件逻辑。它使用函数而不是选项对象来组织组件的代码,使得代码更具可读性和维护性。
  • Fragment:组件中可以存在多个根节点,应该vue内部会将根标签放在一个虚拟的fragment中去
  • Teleport Vue3中的Teleport可以帮助我们轻松地将组件放到DOM树中的任何地方。
  • Emits选项:Vue3中添加了Emits选项,它可以帮助我们更好地管理组件的事件。

20.vue2X 和vue3响应式上有什么区别?

  • 原理上来说 Vue 2.x 中,当一个对象被设置为响应式对象时,会通过Object.defineProperty() 方法把每个属性都转换成 getter 和 setter,当属性值发生变化时,会触发 setter,进而通知所有引用该属性的组件更新视图。 而在 Vue 3.x 中,通过 ES6 Proxy 对象代理实现了对对象的监听和拦截,可以更加细粒度地控制对象属性的读取和赋值行为,也提供了更好的性能表现。
  • vue2 对于新增属性和删除属性需要手动调方法才能保证响应式,而vue3是不用的。
  • 可以监听到数组的变化,无需额外对push、splice等进行重写
  • 使用了懒递归的方式。vue2使用的是强制递归的方式对嵌套中的对象进行监听。而vue3是在读取对象内部的嵌套的对象时,才会为其建立代理
  • Vue 3.x 中的响应式系统支持了 reactive() 和 readonly() 等 API,方便开发者创建只读或可变的响应式对象。而在 Vue 2.x 中则没有这些 API。

21. vue是单向数据流,为什么有双向绑定?

父组件可以向子组件传递props,但是子组件不能修改父组件传递来的props,子组件只能通过事件通知父组件进行数据更改。通过单向数据流的模型,所有状态的改变可记录、可跟踪,相比于双向数据流可加容易维护与定位问题。

单双向绑定,指的是View层和Model层之间的映射关系。 Vue则同时支持单向绑定和双向绑定

  • 单向绑定:插值形式{{data}}v-bind也是单向绑定
  • 双向绑定:表单的v-model,用户对View层的更改会直接同步到Model

22. vue2和vue3的diff算法有何不同?

  1. Patch flag(补丁标记): Vue 3 使用 Patch flag 来标记节点的变化类型,从而减少了需要比较的节点数,优化了 diff 算法的效率。
  2. 静态提升(Static Hoisting): Vue 3 支持静态提升(Static Hoisting),即将静态节点提升为常量,减少了渲染时的运算量。 综上所述,Vue 3 的 diff 算法在性能和体验方面都有了很大的提升,可以更好地满足现代 Web 应用的需求。
  3. 最小递增子序列

23. vue项目中style样式中为什么要添加 scoped?

在Vue中使用 scoped 属性可以让样式作用域仅限于当前组件中,不影响全局,避免了样式污染和样式冲突的问题。

scoped 会为每个组件的 style 标签添加一个唯一的属性作为标记,这样每个组件的样式规则只作用于当前组件的元素,不影响其他组件的样式

24.Vue2修改了数组哪些方法,为什么?

修改了下面几个方法

  1. push()

  2. pop()

  3. shift()

  4. unshift()

  5. splice()

  6. sort()

  7. reverse()

    举个例子,当我们通过 push() 方法向一个数组中添加元素时,Vue2 会检测到这个数组发生了变化,并通知 Vue 视图更新相关数据。这个操作不需要我们手动去更新视图,Vue2 会帮我们完成。 需要注意的是,如果我们使用非响应式的方式来更新数组,例如直接修改数组中某个元素的值,Vue2 就无法监听到这个变化。所以我们需要遵循 Vue2 的修改数组的规范,才能让 Vue2 正常响应式更新数据。 这时有几种处理情况: 原来的数组是 num = 【1,2,3,4】,那么我去修改只能按照上面的方法的7个方法去修改数组的内容,如果我采用num[0]= 100,则会出现数据更改,但是界面不会修改。 如果原理数组是numbers: [{num:1}, {num:2}, {num:3}, {num:4}],直接修改num[0] = {num:100},同样也是数据会修改,界面不会修改。如果我是用num[0].num = 1000,则数据和界面都会更新。

25.mounted生命周期和keep-alive中activated的优先级?

第一次被挂载时,mounted 生命周期会被触发,同时 keep-alive 中的缓存组件还没有被渲染,因此 activated 生命周期并不会被触发。只有当一个被缓存的组件被激活后(比如从其他页面返回到该组件所在的页面),activated 生命周期才会被触发。因此,优先级上 mounted 生命周期高于 activated 生命周期。

26.Vue 3.0 使用的 diff 算法相比 Vue 2.0 中的双端比对有以下优势

  1. 最长递增子序列算法:Vue 3.0 的 diff 算法采用了最长递增子序列算法,能够减少不必要的 DOM 操作,提升性能。
  2. 静态标记:Vue 3.0 中,编译器会对静态节点进行标记,在更新时可以直接跳过这些静态节点,减少 DOM 操作,提升性能。 3.动态删除操作:Vue 3.0 中,对于动态删除操作,采用了异步队列的方式进行,能够将多个删除操作合并为一个,减少 DOM 操作,提升性能。 总的来说,Vue 3.0 的 diff 算法相比 Vue 2.0 更加高效,能够减少不必要的 DOM 操作,提升应用的性能。

27.vue父子组件钩子的执行顺序是什么?

在 Vue 中,父子组件之间的生命周期钩子执行顺序如下:

  1. 加载阶段
  2. 父组件 beforeCreate 钩子
  3. 父组件 created 钩子
  4. 父组件 beforeMount 钩子
  5. 子组件 beforeCreate 钩子
  6. 子组件 created 钩子
  7. 子组件 beforeMount 钩子
  8. 子组件 mounted 钩子
  9. 父组件 mounted 钩子
  10. 更新阶段
  11. 父组件 beforeUpdate 钩子
  12. 子组件 beforeUpdate 钩子
  13. 子组件 updated 钩子
  14. 父组件 updated 钩子
  15. 销毁阶段
  16. 父组件 beforeDestroy 钩子
  17. 子组件 beforeDestroy 钩子
  18. 子组件 destroyed 钩子
  19. 父组件 destroyed 钩子 在这个过程中,子组件的生命周期钩子的执行顺序总是在父组件的生命周期钩子之后。在 keep-alive 组件中,由于缓存组件会被 keep-alive 管理,因此在组件被激活或停用时,执行的生命周期钩子会发生变化:
  20. 激活(activated)缓存中的组件时:
  21. 父组件 activated 钩子
  22. 子组件 activated 钩子
  23. 停用(deactivated)缓存中的组件时:
  24. 父组件 deactivated 钩子
  25. 子组件 deactivated 钩子 需要注意的是,在 keep-alive 组件中,子组件的 mounted 钩子只会在组件被首次渲染时执行,当组件被缓存并再次激活时,子组件的 mounted 钩子不会再次执行,而是执行 activated 钩子。同样,当组件被停用时,子组件的 destroyed 钩子不会立即执行,而是等到组件被销毁时才会执行。

28.vue Data里面如果有数组,如何检测数组的变化?

29.vue 父子组件传值有哪些方式?

Props:通过向子组件传递属性的方式实现数据传递。在父组件中通过v-bind绑定子组件的属性,子组件中通过props接收父组件传递的数据。这是一种单向数据流的方式,父组件可以向子组件传递数据,但是子组件不能直接修改传递过来的数据,需要通过触发事件的方式通知父组件进行修改。 事件:父组件通过emit方法触发子组件的自定义事件,子组件中通过emit方法触发子组件的自定义事件,子组件中通过on监听事件并接收参数,从而实现数据的传递。这也是一种单向数据流的方式,父组件通过事件向子组件传递数据,子组件可以通过触发事件的方式通知父组件进行修改。 parent/parent/children:通过访问父组件或子组件的实例属性来实现数据的传递。但是这种方式不够直观,且容易出现问题,因为父组件或子组件的实例属性可能会在不同的组件结构中发生变化。 refs:通过在父组件中使用ref属性来获取子组件的实例,从而可以直接访问子组件的属性和方法。这种方式也不够直观,且容易出现问题,因为在组件结构复杂的情况下,refs:通过在父组件中使用ref属性来获取子组件的实例,从而可以直接访问子组件的属性和方法。这种方式也不够直观,且容易出现问题,因为在组件结构复杂的情况下,refs可能会变得混乱。

虚拟Dom

30.对虚拟DOM的理解?

虚拟dom本质上就是一个普通的JS对象,用于描述视图的界面结构 在vue中,每个组件都有一个render函数,每个render函数都会返回一个虚拟dom树,这也就意味着每个组件都对应一棵虚拟DOM树

31.虚拟DOM的解析过程?

在一个组件实例首次被渲染时,它先生成虚拟dom树,这些虚拟dom其实就是一个js对象,里面保存着TagName、props 和 Children等属性,然后会将虚拟dom保存下来,再根据虚拟dom树创建真实dom,并把真实dom挂载到页面中合适的位置,此时,每个虚拟dom便会对应一个真实的dom。

如果一个组件受响应式数据变化的影响,需要重新渲染时,它仍然会重新调用render函数,创建出一个新的虚拟dom树,用新树和旧树对比,通过对比,vue会找到最小更新量,然后更新必要的虚拟dom节点,最后,这些更新过的虚拟节点,会去修改它们对应的真实dom

这样一来,就保证了对真实dom达到最小的改动。

32.为什么要用虚拟DOM?

  1. 保证性能下限,在不进行手动优化的情况下,提供过得去的性能。
  2. 跨平台的原因 因为真实dom只有浏览器环境才有的,你不能保证所有环境都有真实dom,所以他搞了一个所有环境都认识的虚拟dom,然后再根据不同的环境识别虚拟dom,渲染出界面,这样就可以实现一套代码在多端运行。

33.虚拟DOM真的比真实DOM性能好吗?

虚拟dom的更新技术的性能理论上不可能比原生操作dom的性能更高,比方说页面首次渲染,那么因为多了创建虚拟dom的步骤,肯定没有直接操作真实dom快。但是呢,有时候不恰当的dom操作,有可能不是最合适的,然后这时间如果你使用框架,用了虚拟dom,由于框架的作用性能可能会更好一些。

34.Vue中key的作用?

  1. v-if 简单来说v-if是为了不复用。 当使用 v-if 来实现元素切换的时候,如果切换前后含有相同类型的元素,那么这个元素就会被复用。如果是相同的 input 元素,那么切换前后用户的输入不会被清除掉,这样是不符合需求的。因此可以通过使用 key 来唯一的标识一个元素,这个情况下,使用 key 的元素不会被复用。

  2. v-for v-for 是为了复用,用 v-for 更新已渲染过的元素列表时,它默认使用“就地复用”的策略。如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处的每个元素。因此通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚拟 DOM。

35.为什么不建议用index作为key?

使用index 作为 key和没写基本上没区别,因为不管数组的顺序怎么颠倒,index 都是 0, 1, 2...这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作。

vue3 相关

36.vue3的通信方式?

  • props(父传子)
  • emit(子传父)
  • expose/ref :子组件通过 expose 暴露自身的方法和数据,父组件通过 ref 获取到子组件
  • attrs
  • v-model:v-model 是 Vue 的一个语法糖。在 Vue 3 中的玩法就更多
  • 插槽slot
  • provide / inject(依赖注入多层传值)
  • Vuex/pinia
  • mitt

38.ref与reactive的区别?

ref与reactive 是 Vue 3 用于创建响应式数据的API,

  1. ref 可以用于引用类型和基本数据类型,而reactive用于引用类型
  2. ref 函数创建的响应式数据,在模板中可以直接被使用,在 JS 中需要通过 .value 的形式才能使用。Reactive操作数据与读取数据:均不需要.value。
  3. ref 底层还是使用 reactive 来做,ref 是在 reactive 上在进行了封装,增强了其能力,使它支持了对原始数据类型的处理。

39. EventBus与mitt区别?

Vue 2 中我们使用 EventBus 来实现跨组件之间的一些通信,它依赖于 Vue 自带的 on/off/emit 等方法,而 Vue 3 中移除了这些相关方法,这意味着EventBus 这种方式我们使用不了, Vue 3 推荐尽可能使用 props/emits、provide/inject、vuex 等其他方式来替代。

如果 Vue 3 内部的方式无法满足你,官方建议使用一些外部的辅助库,例如:mitt。优点:

  • 非常小,压缩后仅有 200 bytes。
  • 完整 TS 支持,源码由 TS 编码。
  • 跨框架,它并不是只能用在 Vue 中,React、JQ 等框架中也可以使用。
  • 使用简单,仅有 on、emit、off 等少量实用API。

40.谈谈pinia?

Pinia 是 Vue 官方团队成员专门开发的一个全新状态管理库,并且 Vue 的官方状态管理库已经更改为了 Pinia。在 Vuex 官方仓库中也介绍说可以把 Pinia 当成是不同名称的 Vuex 5,这也意味不会再出 5 版本了。

优点:

1、更加轻量级,压缩后提交只有1.6kb。

2、完整的 TS 的支持,Pinia 源码完全由 TS 编码完成。

3、移除 mutations,只剩下 state 、 actions 、 getters 。

4、无需手动添加每个 store,它的模块默认情况下创建就自动注册。

5、支持服务端渲染(SSR)

6、更友好的代码分割机制

41.Vue 3 带来了哪些新的特性和性能方面的提升?

新特性

  • Composition API:允许开发者更灵活地组织和重用组件逻辑。它使用函数而不是选项对象来组织组件的代码,使得代码更具可读性和维护性。
  • Fragment:组件中可以存在多个根节点,应该vue内部会将根标签放在一个虚拟的fragment中去
  • Teleport Vue3中的Teleport可以帮助我们轻松地将组件放到DOM树中的任何地方。
  • Emits选项:Vue3中添加了Emits选项,它可以帮助我们更好地管理组件的事件。

性能提升

  • 响应式系统升级
  • 编译优化。重写了虚拟 DOM,提升了渲染速度。diff 时静态节点会被直接跳过。
  • 源码体积优化。移除了一些非必要的特性,如 filter,一些新增的模块也将会被按需引入,减小了打包体积。
  • 打包优化。更强的 Tree Shaking,可以过滤不使用的模块,没有使用到的组件,比如过渡(transition)组件,则打包时不会包含它。

42. Vue 3 对 diff 算法进行了哪些优化?

vue2 会对整个虚拟dom树进行递归比较,即便大部分内容是静态的,而vue3引入了静态标记,可以将模板中的静态内容和动态内容区分开来。在更新过程中,而对于静态内容,它将跳过比较的步骤,从而避免了不必要的比较,提高了性能和效率。

vue3 采用了快速diff 算法,效率更高。

43. Vue 3 有什么更新?

  1. 性能优化:Vue.js 3.0使用了Proxy替代Object.defineProperty实现响应式,并且使用了静态提升技术来提高渲染性能。新增了编译时优化,在编译时进行模板静态分析,并生成更高效的渲染函数。
  2. Composition API:Composition API是一个全新的组件逻辑复用方式,可以更好地组合和复用组件的逻辑。
  3. TypeScript支持:Vue.js 3.0完全支持TypeScript,在编写Vue应用程序时可以更方便地利用TS的类型检查和自动补全功能。
  4. 新的自定义渲染API:Vue.js 3.0的自定义渲染API允许开发者在细粒度上控制组件渲染行为,包括自定义渲染器、组件事件和生命周期等。
  5. 改进的Vue CLI:Vue.js 3.0使用了改进的Vue CLI,可以更加灵活地配置项目,同时支持Vue.js2.x项目升级到Vue.js 3.0。
  6. 移除一些API:Vue.js 3.0移除了一些不常用的API,如过渡相关API,部分修饰符等。

44. Proxy和Object.defineProperty的区别?

都可以用来实现JavaScript对象的响应式。但是有差别

  • 实现方式 Proxy是ES6新增的一种特性,使用了一种代理机制来实现响应式。而Object.defineProperty是在ES5中引入的,使用了getter和setter方法来实现。
  • Proxy可以代理整个对象,他可以拦截对象的常见操作。而Object.defineProperty只能代理对象上定义的属性
  • 监听属性:Proxy可以监听到新增属性和删除属性的操作,而Object.defineProperty只能监听到已经定义的属性的变化。
  • 性能:由于Proxy是ES6新增特性,其内部实现采用了更加高效的算法,相对于Object.defineProperty来说在性能方面有一定的优势。

具体来说,你在vue2对于嵌套对象来说你一开始递归设置使用Object.defineProperty来处理,如果层级比较深的时候,可能还是比较耗费性能的,另外一方面对于数组Object.defineProperty还处理不了,还要设置一个中间层来重写数组的七个方法,对于vue3来说使用Proxy只需要代理对象,层级比较深的属性,只有读到这个属性的时候,才需要设置里面的代理,对于数组也不需要重写数组的方法,因为数组的几个方法都会转化为基本操作。这是因为Proxy相对于Object.defineProperty拥有更优异的性能和更强大的能力。

45. Options Api与Composition Api的区别?

Options Api: 选项API,通过定义methods,computed,watch,data等属性与方法,共同处理页面逻辑。如果组件不复杂用这个没有问题,如果组件功能复杂的话,很容易导致一个功能被分割在这几个选项里面。几个功能混在一个选项中,这种不太利于代码阅读。 Composition API 中,组件根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起( 更加的 高内聚,低耦合 )。

Composition Api 相对Options Api的优点:

  1. 在逻辑组织和逻辑复用方面,Composition API是优于Options API。 组织逻辑:Composition Api将某个逻辑关注点相关的代码全都放在一个函数里,这样,当需要修改一个功能时,就不再需要在文件中跳来跳去。
  2. 因为Composition API几乎是函数,会有更好的类型推断。
  3. Composition API 对 tree-shaking 友好,代码也更容易压缩。
  4. Composition API中见不到this的使用,减少了this指向不明的情况。
  5. 如果是小型组件,可以继续使用Options API,也是十分友好的。

46.Vue 3 为什么比Vue 2 快?

  1. 响应式系统优化:Vue 3 引入了新的响应式系统,这个系统的设计让Vue 3 的渲染函数可以在编译时生成更少的代码,这也就意味着在运行时需要更少的代码来处理虚拟DOM。这个新系统的一个重要改进就是提供了一种基于Proxy实现的响应式机制,这种机制为开发人员提供更加高效的API,也减少了一些运行时代码。
  2. 编译优化:Vue 3 的编译器对代码进行了优化,包括减少了部分注释、空白符和其他非必要字符的编译,同时也对编译后的代码进行了懒加载优化。 3.更快的虚拟DOM:Vue 3 对虚拟DOM进行了优化,使用了跟React类似的Fiber算法,这样可以更加高效地更新DOM节点,提高性能。
  3. Composition API:Vue 3 引入了Composition API,这种API通过提供逻辑组合和重用的方法来提升代码的可读性和重用性。这种API不仅可以让Vue 3 应用更好地组织和维护业务逻辑,还可以让开发人员更加轻松地实现优化。

47. Vue 3 如何实现响应式?

48. Vue 3 响应式原理?

使用Proxy和Reflect API实现vue 3 响应式。

Reflect API则可以更加方便地实现对对象的监听和更新,可以用来访问、检查和修改对象的属性和方法,比如Reflect.get、Reflect.set、Reflect.has等。

Vue 3 会将响应式对象转换为一个Proxy对象,并利用Proxy对象的get和set拦截器来实现对属性的监听和更新。当访问响应式对象的属性时,get拦截器会被触发,此时会收集当前的依赖关系,并返回属性的值;当修改响应式对象的属性时,set拦截器会被触发,此时会触发更新操作,并通知相关的依赖进行更新。

深度代理,Proxy只会代理对象的第⼀层,判断Reflect.get的返回值是否为Object,如果是则再通过 reactive ⽅法做代理, 这样就实现了深度观测。

优点:可监听属性的变化、新增与删除,监听数组的变化。

49.vue 3 编译做了哪一些优化?

  1. 静态树提升: Vue 3 通过重写编译器,实现对静态节点(即不改变的节点)进行编译优化,使用HoistStatic功能将静态节点移动到 render 函数外部进行缓存,从而服务端渲染和提高前端渲染的性能。
  2. Patch Flag:在Vue 3 中,编译的生成vnode会根据节点patch的标记,只对需要重新渲染的数据进行响应式更新,不需要更新的数据不会重新渲染,从而大大提高了渲染性能。
  3. 静态属性提升:Vue 3 中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用。免去了重复的创建操作,优化内存。 没做静态提升之前,未参与更新的元素也在render函数内部,会重复创建阶段。
    做了静态提升后,未参与更新的元素,被放置在render 函数外,每次渲染的时候只要取出即可。同时该元素会被打上静态标记值为-1,特殊标志是负整数表示永远不会用于 Diff。
  4. 事件监听缓存:默认情况下绑定事件行为会被视为动态绑定(没开启事件监听器缓存),所以每次都会去追踪它的变化。开启事件侦听器缓存后,没有了静态标记。也就是说下次diff算法的时候直接使用。
  5. 优化Render function:Vue 3 的compile优化还包括:Render函数的换行和缩进、Render函数的条件折叠、Render函数的常量折叠等等。

50.请介绍Vue 3 中的Teleport组件?

Vue 3 中新增了teleport(瞬移)组件,可以将组件的 DOM 插到指定的组件层,而不是默认的父组件,可以用于在应用中创建模态框、悬浮提示框、通知框等组件。 Teleport 组件可以传递两个属性:

  • to (必填):指定组件需要挂载到的 DOM 节点的 ID,如果使用插槽的方式定义了目标容器也可以传入一个选择器字符串。
  • disabled (可选):当为true的时候在当前位置,否则为目标位置。

需要注意的是,虽然 DOM 插头被传送到另一个地方,但它的父组件仍然是当前组件,这一点必须牢记,否则会导致样式、交互等问题。 Teleport 组件不仅支持具体的 id/选择器,还可以为to属性绑定一个 Vue 组件实例。 总之,Teleport 组件是 Vue 3 中新增的一个非常有用的组件,可以方便地实现一些弹出框、提示框等组件的功能,提高了开发效率。

51.如何理解reactive、ref 、toRef 和 toRefs?

  • ref: 函数可以接收原始数据类型引用数据类型。- ref函数创建的响应式数据,在模板中可以直接被使用,在 JS 中需要通过 .value 的形式才能使用。
  • reactive: 函数只能接收引用数据类型
  • toRef:针对一个响应式对象的属性创建一个ref,使得该属性具有响应式,两者之间保持引用关系。

52.script setup 是干啥的?

scrtpt setup 是 vue 3 的语法糖,简化了组合式 API 的写法,并且运行性能更好。使用 script setup 语法糖的特点:

  • 属性和方法无需返回,可以直接使用。
  • 引入组件的时候,会自动注册,无需通过 components 手动注册。
  • 使用 defineProps 接收父组件传递的值。
  • useAttrs 获取属性,useSlots 获取插槽,defineEmits 获取自定义事件。
  • 默认不会对外暴露任何属性,如果有需要可使用 defineExpose 。

53.v-if 和 v-for 的优先级哪个高?

在 vue 2 中 v-for 的优先级更高,但是在 vue 3 中优先级改变了。v-if 的优先级更高。

54.setup中如何获得组件实例?

在 setup 函数中,你可以使用 getCurrentInstance() 方法来获取组件实例。getCurrentInstance() 方法返回一个对象,该对象包含了组件实例以及其他相关信息。

55.Vue 3 性能提升主要是通过哪几方面体现的?

  1. diff算法优化:vue 3 在diff算法中相比vue 2 增加了静态标记,其作用是为了会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较。

  2. 静态提升:Vue3中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用。免去了重复的创建操作,优化内存。

  3. 事件监听缓存:默认情况下绑定事件行为会被视为动态绑定(没开启事件监听器缓存),所以每次都会去追踪它的变化。开启事件侦听器缓存后,没有了静态标记。也就是说下次diff算法的时候直接使用。

  4. SSR优化:当静态内容大到一定量级时候,会用createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。

  5. 源码体积:相比Vue 2,Vue 3 整体体积变小了,除了移出一些不常用的API,最重要的是Tree shanking。 任何一个函数,如ref、reavtived、computed等,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小。

  6. 响应式系统:vue 2 中采用 defineProperty来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加getter和setter,实现响应式。

vue 3 采用proxy重写了响应式系统,因为proxy可以对整个对象进行监听,所以不需要深度遍历。

  • 可以监听动态属性的添加
  • 可以监听到数组的索引和数组length属性
  • 可以监听删除属性

56.vue 3 组合式API生命周期钩子函数有变化吗?

  1. beforeCreate===>setup()
  2. created===>setup()
  3. beforeMount == = > onBeforeMount
  4. mounted => onMounted
  5. beforeUpdate= == >onBeforeUpdate
  6. updated == >onUpdated
  7. beforeUnmount == >onBeforeUnmount
  8. unmounted ===>onUnmounted

57.watch 和 watchEffect 的区别?

watch的套路是:既要指明监视的属性,也要指明监视的回调。 watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  1. watchEffect 会自动追踪函数内部使用的数据变化,数据变化时重新执行该函数。 watch 需要显式地指定监听的数据,若指定的数据发生变化,重新执行该函数。
  2. watchEffect 的函数会立即执行一次,并在依赖的数据变化时再次执 行。watch 的回调函数只有在侦听的数据源发生变化时才会执行,不会立即执行。
  3. watch可以更精细的控制监听行为,如 deep、immediate,flush watchEffect更适合简单的场景,不需要额外的配置。相当于默认开启了 deep、immediate。

watch 里面的flush配置 image.png

58.说说Vue 3 中Treeshaking特性?举例说明一下?

Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术,简单来讲,就是在保持代码运行结果不变的前提下,去除无用的代码。在Vue 3 源码引入tree shaking特性,将全局 API 进行分块。如果您不使用其某些功能,它们将不会包含在您的基础包中。

Tree shaking主要是借助ES6模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量。

Tree shaking无非就是做了两件事:

1、编译阶段利用ES6 Module判断哪些模块已经加载。

2、判断哪些模块和变量未被使用或者引用,进而删除对应代码。