Vue 面试题

发表于:2023-03-16
字数统计:31.8k 字
阅读时长:1.3 小时
阅读量:148

什么是MVVM,MVVM和MVC以及MVP有什么区别?

MVVM 即 Model-View-ViewModel 的简写,即模型-视图-视图模型。模型(Model)指的是后端传递的数据。视图(View)指的是所看到的页面。视图模型(ViewModel)是 MVVM 模式的核心,它是连接 View 和 Model 的桥梁。

视图模型有两个方向的作用:

1.将模型(Model)转化成视图(View),即将后端传递的数据转化成所看到的页面,实现的方式是:数据绑定。

2.将视图(View)转化成模型(Model),即将所看到的页面转化成后端的数据,实现的方式是:DOM 事件监听。当这两个方向的数据转换都实现时,我们称之为数据的双向绑定。

MVVM模型图解:

MVC 是 Model-View-Controller 的简写,即模型-视图-控制器。M 和 V 指的意思和 MVVM 中的 M 和 V 意思一样。C 即 Controller 指的是页面业务逻辑。使用 MVC 的目的就是将 M 和 V 的代码分离。MVC 是单向通信,也就是 View 跟 Model ,必须通过 Controller 来承上启下。

MVC模型图解:

MVVM 与 MVC 最大的区别就是:MVVM 实现了 View 和 Model 的自动同步,也就是当 Model 的属性改变时,我们不用再自己手动操作 Dom 元素来改变 View 的显示,而是改变属性后该属性对应 View 层显示会自动改变(双向绑定)。

说一下 Vue 的优点?

1. 体积小

压缩后只有33K;

2. 更高的运行效率

基于虚拟DOM, 一种可以预选通过JavaScript进行各种计算, 把最终的DOM操作计算出来并优化的技术,由于这个DOM操作属于预处理操作, 并没有真正地操作DOM, 所以叫虚拟DOM。

3. 双向数据绑定

让开发者不用再去操作DOM对象, 把更多的精力投入到业务逻辑上。

4. 生态丰富、学习成本低

市场上拥有大量成熟、稳重的基于Vue.js的UI框架、常用组件!拿来即用实现快速开发! 对初学者友好、入门容易、学习资料多。

什么是组件以及如何使用组件?

组件就是把一个很大的界面拆分为多个小的界面, 每一个小的界面就是一个组件

将大界面拆分成小界面就是组件化

组件化的好处是可以简化Vue实例的代码,可以提高代码复用性

Vue的组件注册分为全局注册和局部注册

全局注册是在全局Vue对象的 component 属性中绑定定义好的模板

局部注册是在 Vue 实例的 components 属性中绑定定义好的模板

如果是.vue文件的组件则需要通过 import 导入到需要的组件中。

Vue组件间是如何通信的?

方法一、props/$emit

在父组件中通过v-bind传递数据,在子组件中通过props接收数据

在父组件中通过 v-on 传递方法,在子组件的自定义方法中通过 this.$emit('自定义接收名称'); 触发传递过来的方法

方法二、$emit/$on

这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。

定义一个空的Vue实例

var Event = new Vue();

传递数据

Event.$emit(事件名,数据);

获取数据

Event.$on(事件名,data => {});

方法三、vuex

vuex 是 Vue 配套的 公共数据管理工具,我们可以将共享的数据保存到 vuex 中, 方便整个程序中的任何组件都可以获取和修改vuex中保存的公共数据

创建Vuex对象

const store = new Vuex.Store({
    // 这里的state就相当于组件中的data, 就是专门用于保存共享数据的
    state: {
        msg: "共享数据"
    }
});

在祖先组件中添加 store 的 key 保存Vuex对象

只要祖先组件中保存了Vuex对象 , 那么祖先组件和所有的后代组件就可以使用Vuex中保存的共享数据了

store: store,

在使用Vuex中保存的共享数据的时候, 必须通过如下的格式来使用

<p>{{this.$store.state.msg}}</p>

方法四:作用域插槽

作用域插槽就是带数据的插槽, 就是让父组件在填充子组件插槽内容时也能使用子组件的数据

应用场景在于: 子组件提供数据, 父组件决定如何渲染

  • slot中通过 v-bind:数据名称="数据名称" 方式暴露数据
  • 在父组件中通过 <template slot-scope="作用域名称"> 接收数据
  • 在父组件的 <template></template> 中通过 作用域名称.数据名称 方式使用数据

在 Vue2.6.版本中,为作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。

它取代了slot-scope

也就是说我们可以通过v-slot指令告诉Vue如何接收作用域插槽暴露的数据

格式: v-slot:插槽名称="作用域名称"

简写: #插槽名称="作用域名称"

Vue是如何实现双向数据绑定的(Vue双向数据绑定原理)?

采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

Vue主要通过以下4个步骤实现数据双向绑定:

1、实现一个数据监听器 Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者

2、实现一个指令解析器 Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

3、实现一个订阅者 Watcher,作为连接 Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图

4、最后实现 MVVM 作为数据绑定的入口,整合监听器、解析器和订阅者

为什么需要使用Key(Vue 中 key 值的作用?)?

需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点,找到正确的位置区插入新的节点 所以简单的说,key 的作用主要是为了高效的更新虚拟 DOM 比如渲染一堆input框,中间删除一个就会产生问题,下面的input会被顶上来, vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留,从而产生一系列的bug

第一次页面加载会触发哪几个钩子?

beforeCreate, created, beforeMount, mounted

created 和 mounted 的区别?

created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。

mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

如:数据初始化一般放到created里面,这样可以及早发请求获取数据,

如果有依赖dom必须存在的情况,就放到mounted(){this.$nextTick(() => { /* code */ })}里面

vue.nexttick?

定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

所以就衍生出了这个获取更新后的DOM的Vue方法。所以放在Vue.nextTick()回调函数中的执行的应该是会对DOM进行操作的 js代码;

理解:nextTick(),是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:当数据更新了,在dom中渲染后,自动执行该函数,

计算属性 computed?

支持缓存,只有依赖数据发生改变,才会重新进行计算

不支持异步,当computed内有异步操作时无效,无法监听数据的变化

如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed

如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。

<h2>{{newCount}}</h2>
<input v-model="nweDouble"/>
<h2>{{nweDouble}}</h2>

js

export default {
  computed: {
    newCount() {
      return this.count + 1
    },
    nweDouble: {
      get() {
        return this.number * 2
      },
      set(value) {
        this.number = value
      }
    }
  },
  data() {
    return {
      count: 0,
      number: 0
    }
  }
}

侦听器 watch?

不支持缓存,数据变,直接会触发相应的操作

watch支持异步

监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值

监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数

immediate:组件加载立即触发回调函数执行

deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。

<input v-model="name"/>
<input v-model="number"/>
<input v-model="info.name"/>

js

export default {
  watch: {
    name(newName, oldName) {
      console.log(newName, oldName)
    },
    number: {
      handler(newName, oldName) {
        console.log(newName, oldName)
      },
      immediate: true
    },
    info: {
      handler(newName, oldName) {
        console.log(newName, oldName)
      },
      deep: true
    }
  },
  data() {
    return {
      name: '',
      number: 0,
      info: {
        name: ''
      }
    }
  },
}

mvvm 框架是什么?

MVVM的定义

M:Model(服务器上的业务逻辑操作)

V:View(页面)

VM:ViewModel(Model与View之间核心枢纽,比如Vue.js)

vue-router 是什么?它有哪些组件?

路由是由多个URL组成的,使用不同的URL可以相应的导航到不同的位置。

浏览器中对页面的访问是无状态的,在切换不同的页面时都会重新进行请求。vue和vue-router在切换页面时是没有重新进行请求的,使用起来就好像页面是有状态的:这其实是借助了浏览器的History API来实现的,这样可以使得页面跳转而不刷新,页面的状态就被维持在浏览器中了。

vue-router中默认使用的是hash模式,URL中带有#号。

Hash模式

vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。 hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说 #是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中也不会不包括#;同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置;所以说Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据

History模式

由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: 'history'",这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

<router-link :to='' class='active-class'>   //路由声明式跳转 ,active-class是标签被点击时的样式
<router-view>                  //渲染路由的容器
<keep-alive>                    //缓存组件

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

前置守卫

router.beforeEach((to, from, next) => {
    if (to.matched.some(item => item.meta.may)) {
        let id = window.localStorage.getItem("id")
        if (id) {
            next()
        } else {
            next({ name: "login" })
        }
    } else {
        next()
    }
})

后置钩子

router.afterEach((to,from) => {
	if(to.meta && to.meta.title){
		document.title = to.meta.title
	}else{
		document.title = "666"
	}
})

单独路由独享钩子

{
    path: '/home',
    name: 'home',
    component: Home,
    beforeEnter(to, from, next) {
        if (window.localStorage.getItem("id")) {
            next()
        } else {
            next({ name: "login" })
        }
    }
}

组件内的钩子

beforeRouteEnter(to, from, next) {
  // do someting
  // 在渲染该组件的对应路由被 confirm 前调用
},
beforeRouteUpdate(to, from, next) {
  // do someting
  // 在当前路由改变,但是依然渲染该组件是调用
},
beforeRouteLeave(to, from ,next) {
  // do someting
  // 导航离开该组件的对应路由时被调用
}

route 和 router 的区别?

route

是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等

router

是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性。

说说你对 vue 的理解?

概述

Vue.js是一款流行的JavaScript框架,用于构建用户界面。以下是我对Vue.js的理解:

Vue.js是一种基于组件化的前端开发框架,它采用了MVVM(Model-View-ViewModel)的架构模式。Vue.js的核心思想是通过将用户界面抽象成可复用的组件来构建应用程序。每个组件都有自己的逻辑和状态,并可以通过组合和嵌套来构建复杂的界面。


特点

  1. 响应式数据绑定:Vue.js使用双向数据绑定,通过将数据模型和视图进行关联,实现了数据的自动更新。当数据发生变化时,视图会自动更新,反之亦然。
  2. 组件化开发:Vue.js鼓励将界面拆分为独立的组件,每个组件都有自己的模板、逻辑和样式。这样可以提高代码的可维护性和复用性,并使开发过程更加模块化和可扩展。
  3. 指令系统:Vue.js提供了丰富的指令系统,用于扩展HTML的功能。指令可以用于绑定事件、操作DOM、条件渲染、循环等。开发者可以自定义指令,根据需求扩展Vue.js的功能。
  4. 虚拟DOM:Vue.js使用虚拟DOM来提高页面的渲染性能。通过在JavaScript中使用虚拟DOM来操作和更新页面,最后再将虚拟DOM转换为真实的DOM进行渲染,减少了直接操作真实DOM的开销。
  5. 生态系统:Vue.js拥有庞大的生态系统,包括官方提供的插件、工具和配套库,以及社区贡献的第三方扩展。这些资源丰富了Vue.js的功能和开发工具,使开发者可以更高效地构建应用程序。

综上所述,Vue.js是一个灵活、高效且易于学习的前端框架,它通过组件化、响应式数据绑定和虚拟DOM等特性,提供了一种便捷的方式来构建现代化的Web应用程序。


核心特性

数据驱动(MVVM)

MVVM表示的是 Model-View-ViewModel

  • Model:模型层,负责处理业务逻辑以及和服务器端进行交互
  • View:视图层:负责将数据模型转化为UI展示出来,可以简单的理解为HTML页面
  • ViewModel:视图模型层,用来连接Model和View,是Model和View之间的通信桥梁

说说你对SPA(单页应用)的理解?

什么是 SPA

SPA(single-page application),翻译过来就是单页应用SPA是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换打断用户体验在单页应用中,所有必要的代码(HTML、JavaScript和CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面页面在任何时间点都不会重新加载,也不会将控制转移到其他页面举个例子来讲就是一个杯子,早上装的牛奶,中午装的是开水,晚上装的是茶,我们发现,变的始终是杯子里的内容,而杯子始终是那个杯子结构如下图

我们熟知的JS框架如react,vue,angular,ember都属于SPA

SPA 和 MPA 的区别

上面大家已经对单页面有所了解了,下面来讲讲多页应用MPA(MultiPage-page application),翻译过来就是多页应用在MPA中,每个页面都是一个主页面,都是独立的当我们在访问另一个页面的时候,都需要重新加载html、css、js文件,公共文件则根据需求按需加载如下图

单页应用与多页应用的区别

单页应用优缺点

优点:

  • 具有桌面应用的即时性、网站的可移植性和可访问性
  • 用户体验好、快,内容的改变不需要重新加载整个页面
  • 良好的前后端分离,分工更明确

缺点:

  • 不利于搜索引擎的抓取
  • 首次渲染速度相对较慢

Vue 中的 v-show 和 v-if 怎么理解?

我们都知道在 vue 中 v-show 与 v-if 的作用效果是相同的(不含v-else),都能控制元素在页面是否显示

在用法上也是相同的

<Model v-show="isShow" />
<Model v-if="isShow" />
  • 当表达式为true的时候,都会占据页面的位置
  • 当表达式都为false时,都不会占据页面位置

区别

  • 控制手段不同
  • 编译过程不同
  • 编译条件不同

控制手段:v-show 隐藏则是为该元素添加 css--display:none,dom 元素依旧还在。v-if 显示隐藏是将dom元素整个添加或删除

编译过程:v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换

编译条件:v-if 是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染

  • v-show 由false变为 true 的时候不会触发组件的生命周期
  • v-if 由 false 变为 true 的时候,触发组件的 beforeCreate、create、beforeMount、mounted 钩子,由 true变为false的时候触发组件的 beforeDestory、destoryed 方法

性能消耗:v-if 有更高的切换消耗;v-show 有更高的初始渲染消耗;


使用场景

如果需要非常频繁地切换,则使用 v-show 较好,否则使用 v-if

Vue 实例挂载的过程中发生了什么?

Vue实例挂载的过程涉及以下步骤:

  1. 创建Vue实例:首先,Vue会实例化一个Vue对象,通过调用Vue构造函数创建一个新的Vue实例。在这个过程中,Vue会进行一些初始化工作,包括设置实例的选项、响应式数据的初始化等。
  2. 编译模板:接下来,Vue会对模板进行编译。模板可以是HTML代码或者是Vue的单文件组件(.vue文件)。编译过程将模板转换为渲染函数,这个渲染函数将用于渲染Vue实例的视图。
  3. 挂载实例:在挂载阶段,Vue将创建一个真实的DOM元素,并将其替换为在模板中指定的挂载点。这个过程中,Vue会执行一系列的操作,包括生成虚拟DOM、将虚拟DOM渲染为真实DOM等。
  4. 数据初始化和依赖收集:在挂载过程中,Vue会初始化数据,并建立数据与视图之间的响应式关系。这包括将数据与模板中的指令、表达式进行绑定,并在数据变化时更新视图。
  5. 视图渲染:一旦实例被挂载到DOM上,Vue将开始渲染视图。它会根据初始的数据状态,执行渲染函数,生成一棵虚拟DOM树。然后,Vue会通过比对新旧虚拟DOM树的差异,只更新需要更新的部分,从而尽量减少DOM操作,提高性能。
  6. 生命周期钩子:在挂载过程中,Vue提供了一系列的生命周期钩子函数,允许开发者在特定阶段插入自定义逻辑。这些钩子函数包括beforeCreate、created、beforeMount和mounted等,开发者可以在这些钩子函数中执行需要的操作。

通过以上步骤,Vue实例的挂载过程完成后,它将与DOM元素建立起联系,并开始响应用户的操作和数据变化,实现动态的视图更新和交互。

Vue生命周期的理解?

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。通俗说就是 Vue 实例从创建到销毁的过程,就是生命周期。

Vue生命周期可以分为三个阶段:初始化阶段、更新阶段、销毁阶段

初始化阶段

beforeCreate()//实例创建前:模板和数据均未获取到
created()//实例创建后:最早可以获得data数据,但模板还未获取到
beforfeMount()//数据挂载前:模板已经获取到,但是数据未挂载到模板上
mounted()//数据挂载后:数据已挂载到模板中

更新阶段

beforeUpdate()//模板更新前:data改变后,更新数据模板前调用
updated()//模板更新后:将data渲染到数据模板中

销毁阶段

beforeDestroy()//实例销毁前
destroyed()//实例销毁后

为什么 Vue 中的 v-if 和 v-for 不建议一起用?

在Vue中,v-if和v-for的确可以一起使用,但它们的组合使用可能会导致一些性能问题。以下是不建议同时使用v-if和v-for的原因:

  1. 重复渲染:当v-if与v-for一起使用时,Vue在每次重新渲染时都会重新创建和销毁DOM元素。这是因为v-if是用于条件渲染的指令,而v-for是用于循环渲染的指令。这样会导致额外的开销,影响性能。
  2. 计算复杂性:当v-if与v-for结合使用时,Vue需要为每个循环迭代都进行条件判断。如果列表中的项较多或条件判断较复杂,会导致计算复杂性增加,进而影响应用程序的性能。
  3. 可读性和维护性:同时使用v-if和v-for可能会增加模板的复杂性,降低代码的可读性和维护性。在模板中同时存在条件判断和循环逻辑时,理解和调试代码可能会更加困难。

如果确实需要在循环中进行条件渲染,可以考虑使用计算属性或方法来处理。通过在组件中计算出满足条件的项,然后在模板中使用v-for来循环渲染,可以避免在每次渲染时进行条件判断。

总的来说,尽量避免在同一元素上同时使用v-if和v-for,除非确实有必要。在实际开发中,根据具体情况选择最合适的指令和技术来实现所需的逻辑和效果,以提高性能和代码的可维护性。

SPA(单页应用)首屏加载速度慢怎么解决?

什么是首屏加载

首屏时间(First Contentful Paint),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容

首屏加载可以说是用户体验中最重要的环节

关于计算首屏时间

利用 performance.timing 提供的数据:

// 方案一:
document.addEventListener('DOMContentLoaded', (event) => {
    console.log('first contentful painting');
});
// 方案二:
performance.getEntriesByName("first-contentful-paint")[0].startTime

// performance.getEntriesByName("first-contentful-paint")[0]
// 会返回一个 PerformancePaintTiming的实例,结构如下:
{
  name: "first-contentful-paint",
  entryType: "paint",
  startTime: 507.80000002123415,
  duration: 0,
};

加载慢的原因

在页面渲染的过程,导致加载速度慢的因素可能如下:

  • 网络延时问题
  • 资源文件体积是否过大
  • 资源是否重复发送请求去加载了
  • 加载脚本的时候,渲染内容堵塞了

解决方案

常见的几种SPA首屏优化方式

  • 减小入口文件积
  • 静态资源本地缓存
  • UI框架按需加载
  • 组件重复打包
  • 开启GZip压缩
  • 使用SSR
  • 代码拆分和按需加载
  • 路由懒加载
  • 首屏资源优化
  • 预渲染/服务器端渲染
  • 图片优化
  • CDN加速

详细方案

  1. 代码拆分和按需加载:将应用的代码拆分为多个模块或路由,并使用按需加载的方式加载这些模块。这样可以减少首次加载所需的资源量,只加载当前页面所需的代码,提高首屏加载速度。可以使用工具如Webpack的代码分割功能或动态导入(dynamic import)来实现代码拆分和按需加载。
  2. 路由懒加载:对于大型SPA,可以将路由进行懒加载。即在用户访问到某个路由时再加载对应的组件和资源。这样可以减少初始加载时所需的资源量,提高首屏加载速度。Vue Router提供了路由懒加载的支持,可以在定义路由时使用import语法来实现懒加载。
  3. 首屏资源优化:分析首屏所需的资源(如CSS、JavaScript、图片等),对其进行压缩、合并、缓存等优化。可以使用工具如Webpack和Gulp来进行资源的优化处理,减少加载时间和带宽消耗。
  4. 预渲染/服务器端渲染:对于需要SEO或提高首屏加载速度的场景,可以考虑使用预渲染或服务器端渲染(SSR)。预渲染是在构建阶段生成静态HTML文件,直接返回给浏览器,提供快速的首屏加载体验。SSR是将Vue应用在服务器上进行渲染,返回已渲染好的HTML给浏览器。这两种方式都可以减少客户端的加载和渲染时间,提高首屏加载速度。
  5. 图片优化:优化图片的大小和格式,使用适当的压缩算法来减少图片的文件大小。可以使用工具如ImageOptim或在线压缩工具来进行图片优化。
  6. CDN加速:使用内容分发网络(CDN)来加速静态资源的加载。将静态资源部署到CDN上,可以使这些资源离用户更近,减少网络延迟,提高加载速度。
  7. 数据量优化:如果首屏加载速度慢与数据量过大有关,可以考虑减少首屏所需数据的量或使用分页加载等方式来提高加载速度。

综合运用上述技术和策略,可以有效地解决SPA首屏加载速度慢的问题,提升用户体验和整体性能。

为什么 data 属性是一个函数而不是一个对象?

在Vue组件中,为什么data属性是一个函数而不是一个对象的原因是为了实现数据的独立性和复用性。

当data属性被定义为一个函数时,每个组件实例都会调用该函数来返回一个数据对象的副本。这样每个组件实例都会有自己独立的数据副本,避免了组件之间数据共享带来的潜在问题。

如果data属性是一个对象,那么不同的组件实例将共享相同的数据引用。这样在一个组件中修改数据,会影响到其他组件中的相同数据,导致数据的不一致和意外的行为。通过将data属性定义为函数,每个组件实例都可以调用该函数获取独立的数据对象,确保了数据的封装性和隔离性。

另外,将data属性定义为函数还能提供更好的数据复用性。在Vue中,组件可以被多次实例化和复用。如果data属性是一个对象,那么每个组件实例都会共享相同的数据对象,无法实现数据的独立性和复用性。而通过使用函数形式的data属性,每次组件实例化时都会调用函数返回一个新的数据对象,实现了数据的独立复用。

总结起来,将data属性定义为函数而不是对象,能够确保组件实例之间数据的独立性,避免数据共享带来的问题,并提供更好的数据复用性。

Vue 中给对象添加新属性界面不刷新?

在Vue中,如果给一个已经存在的对象添加新属性,界面不会自动刷新。这是因为Vue的响应式系统在实例化时会对data属性中的对象进行递归地进行拦截和劫持,使得Vue能够追踪对象的属性变化并更新界面。然而,对于已经存在的对象,Vue无法在运行时自动检测到新添加的属性。

为了让Vue能够追踪新添加属性的变化并更新界面,你有以下两个选项:

  1. 使用Vue提供的Vue.set方法或全局的this.$set方法来添加新的属性。这些方法会触发Vue的响应式系统,使得添加的属性能够被追踪并更新界面。例如:
Vue.set(obj, 'newProperty', value);
// 或者在组件中使用
this.$set(this.obj, 'newProperty', value);
  1. 使用Object.assign或扩展运算符(...)创建一个新的对象,将原有的对象和新属性一起合并。这样创建的新对象会被Vue追踪并进行响应式更新。例如:
this.obj = Object.assign({}, this.obj, { newProperty: value });
// 或者使用扩展运算符
this.obj = { ...this.obj, newProperty: value };

通过以上两种方式,Vue能够检测到新属性的变化并更新界面。请注意,在使用Vue.set方法或Object.assign时,需要将对象重新赋值给原来的属性或变量,否则Vue无法追踪到对象的变化。

需要注意的是,如果你需要在组件初始化时就添加新属性,推荐在created钩子函数中进行添加。这样可以确保在组件实例化时就添加了新属性,避免后续的计算属性或方法中对新属性的引用出现问题。

Vue 中组件和插件有什么区别?

在Vue中,组件(Component)和插件(Plugin)是两个不同的概念,它们有以下区别:

组件(Component):

  • 组件是Vue应用中的基本构建块,用于封装可复用的UI元素。
  • 组件可以包含HTML模板、CSS样式和JavaScript逻辑,用于定义特定的UI功能和交互。
  • 组件可以在应用中被多次实例化和复用,使得开发者可以将UI拆分为独立的组件,提高代码的可维护性和复用性。
  • 组件可以通过props属性接受父组件传递的数据,通过事件机制与父组件进行通信。
  • 组件可以有自己的状态(data属性),以及计算属性、方法、生命周期钩子等。

插件(Plugin):

  • 插件是一种扩展Vue功能的方式,用于为Vue应用添加全局功能或工具。
  • 插件通常是一个具有install方法的对象或函数,通过在Vue实例上安装插件来注册和启用插件的功能。
  • 插件可以添加全局指令、全局过滤器、全局混入等,以扩展Vue的核心功能。
  • 插件可以提供一些服务、工具或第三方库的集成,以便在应用中方便地使用。

总的来说,组件是用于封装UI元素和实现特定功能的可复用模块,而插件是用于扩展Vue功能和提供全局功能的一种机制。组件是局部的,用于构建应用的一部分,而插件是全局的,用于增强整个Vue应用的功能。

Vue 组件间通信方式都有哪些?

概念

组件间通信是指不同组件之间进行数据传递和交互的方式

8种常规的通信方案

  1. 通过 props 传递
  2. 通过 $emit 触发自定义事件
  3. 使用 ref
  4. EventBus
  5. $parent 或$root
  6. attrs 与 listeners
  7. Provide 与 Inject
  8. Vuex

说说你对双向绑定的理解?

双向绑定是一种数据绑定的概念,它在前端开发中常用于将数据模型与用户界面之间建立起双向的关联。在双向绑定中,当数据模型发生变化时,界面会自动更新;反过来,当用户在界面上进行操作时,数据模型也会相应地进行更新。

双向绑定的实现通常涉及以下几个要素:

  1. 数据模型(Model):数据模型是应用程序中存储数据的源头。它可以是一个对象、数组或其他数据结构。数据模型可以被绑定到界面的表单元素、组件属性等。
  2. 视图(View):视图是用户界面的呈现部分,它可以是HTML模板、组件或其他UI元素。视图通过绑定到数据模型来显示和展示数据。
  3. 绑定器(Binder):绑定器是连接数据模型和视图的桥梁。它负责监测数据模型的变化并更新视图,同时也监听用户界面上的操作并将变化反映回数据模型。

通过双向绑定,当数据模型发生变化时,绑定器会自动更新视图,从而保持界面与数据的同步。同时,当用户在界面上进行操作(如输入表单内容)时,绑定器会将变化反映回数据模型,实现数据的双向同步。

在Vue中,双向绑定是通过指令(如v-model)来实现的。v-model指令可以在表单元素上建立双向绑定,实现数据的自动同步。当用户在表单元素上输入内容时,数据模型会自动更新;反过来,当数据模型发生变化时,表单元素也会自动更新。

总的来说,双向绑定是一种方便的数据绑定方式,它能够简化开发者对数据和界面的管理,提高开发效率。通过双向绑定,数据的变化能够自动反映到界面上,用户操作的变化也能够自动同步到数据模型中,实现了数据与界面之间的实时双向通信。

说说你对 nexttick 的理解?

nextTick 是 Vue 提供的一个异步方法,用于在下次 DOM 更新循环结束之后执行回调函数。它的作用是在当前代码块中对数据的更改生效后,立即执行一些操作。

在 Vue 中,当修改数据后,Vue 并不会立即更新 DOM。而是将 DOM 更新操作放入队列中,然后在下一个事件循环中执行更新,这样可以提高性能和效率。但有时候我们需要在 DOM 更新后立即执行一些操作,比如获取某个元素的尺寸、操作更新后的 DOM 等,这时就可以使用 nextTick 方法。

nextTick 的使用方式有两种:

  1. 通过实例方法调用:在 Vue 实例上调用 $nextTick(callback) 方法,传入一个回调函数。该回调函数会在 DOM 更新循环结束后执行。
this.$nextTick(() => {
  // 执行 DOM 更新后的操作
});
  1. 通过全局方法调用:在 Vue 对象上调用 Vue.nextTick(callback) 方法,传入一个回调函数。该回调函数会在 DOM 更新循环结束后执行。
Vue.nextTick(() => {
  // 执行 DOM 更新后的操作
});

nextTick 的主要作用是确保在 DOM 更新后执行的回调能够获取到最新的 DOM,从而避免操作过时或无效的 DOM。例如,在更新数据后立即操作更新后的 DOM 元素,或者在更新后获取某个元素的尺寸等。

需要注意的是,nextTick 是异步执行的,因此回调函数不会立即执行,而是在下一个事件循环中执行。这意味着如果在 nextTick 后面有同步代码,那么同步代码会先执行,然后才会执行 nextTick 的回调函数。

总结来说,nextTick 提供了一种在 DOM 更新循环结束后执行代码的机制,确保在操作更新后的 DOM 或获取最新的 DOM 时能够获得正确的结果。它在 Vue 的开发中经常被用于处理 DOM 相关的操作和回调。

说说你对 vue 的 mixin 的理解,有什么应用场景?

在Vue中,mixin是一种可复用的对象,它可以包含组件选项(如数据、生命周期钩子、方法等),并将这些选项混入到多个组件中。通过使用mixin,我们可以在不同的组件之间共享相同的逻辑和功能。

使用mixin可以带来以下几个好处:

  1. 代码复用:mixin允许我们将一些通用的逻辑和功能封装到一个对象中,并在多个组件中重复使用。这样可以减少重复编写相同代码的工作量。
  2. 组件扩展:可以通过mixin为组件添加额外的选项,例如添加一些生命周期钩子、混入一些计算属性或方法等。这使得我们可以在多个组件中统一扩展相同的功能。
  3. 解耦逻辑:将一些复杂的逻辑分离到mixin中,可以使组件的代码更加清晰和简洁。不同的逻辑可以分别放置在不同的mixin中,从而实现逻辑的解耦和复用。
  4. 动态修改:mixin的选项可以在组件中进行动态修改或覆盖。这样我们可以根据具体的需求在不同的组件中定制相同mixin的行为。

使用mixin的应用场景包括但不限于:

  1. 共享方法和计算属性:如果有一些通用的方法或计算属性需要在多个组件中使用,可以将它们封装到一个mixin中,并在需要的组件中混入。
  2. 共享生命周期钩子:当多个组件需要相同的生命周期逻辑时,可以使用mixin将这些逻辑提取出来,并在组件中混入。
  3. 共享样式和样式处理:如果有一些共享的样式,或者需要在多个组件中处理相同的样式逻辑,可以使用mixin来统一管理和应用。

需要注意的是,mixin的使用应该谨慎,避免滥用。过多或不当使用mixin可能会导致代码的复杂性增加,难以维护和理解。在使用mixin时,应确保混入的选项不会产生命名冲突或意外的行为,并且在组件中清晰地表示出所使用的mixin。

总结来说,mixin是一种在Vue中实现代码复用和逻辑扩展的机制,可以在多个组件中共享通用的选项和功能。它提供了一种灵活的方式来组织和管理组件的代码,并可以在不同的应用场景中应用。

说说你对 slot 的理解?slot 使用场景有哪些?

在 Vue 中,slot(插槽)是一种用于组件之间内容分发的机制。它允许组件的使用者在组件模板中插入内容,实现对组件内部结构的定制和扩展。

通过使用 slot,我们可以在组件模板中定义一个或多个插槽,然后在使用组件时,使用者可以向这些插槽中插入具体的内容。这样可以实现组件的灵活性和可复用性,允许使用者根据需要自定义组件的一部分或全部内容。

以下是一些常见的使用场景:

  1. 默认插槽:通过在组件模板中使用 <slot> 元素,可以定义一个默认插槽。当使用组件时,如果没有在组件标签中插入内容,那么默认插槽中的内容将会被显示。这样可以为组件提供一个默认的内容,同时也允许使用者根据需要进行替换。
  2. 具名插槽:除了默认插槽,还可以定义具名插槽。通过给 <slot> 元素添加 name 属性,可以为组件定义多个具名插槽。使用者在组件标签中使用 <template> 元素,并为其添加 slot 属性来指定具体插入的插槽。这样可以实现对组件不同部分的内容进行定制,使使用者可以根据需要替换或扩展组件的特定部分。
  3. 作用域插槽:作用域插槽(scoped slot)是一种特殊的插槽,它允许组件将数据传递给插槽中的内容。通过在插槽中使用 <template> 元素,并在元素上绑定数据,可以将数据传递给插槽的内容。这样可以实现在组件内部进行数据处理或渲染的需求。
  4. 动态插槽:Vue 还支持动态选择插槽的内容。使用者可以根据组件的状态或属性来选择插槽。通过在组件模板中使用 <slot> 元素,并使用 v-bind 绑定插槽的名称,可以动态选择插槽的内容进行渲染。

总结来说,slot 是 Vue 中用于组件内容分发的机制,它提供了一种灵活的方式,使组件的使用者可以自定义组件的一部分或全部内容。slot 的使用场景包括提供默认内容、分发具名内容、作用域插槽和动态插槽等。通过合理使用 slot,可以提高组件的可复用性和灵活性,同时降低组件之间的耦合度。

Vue.observable 你有了解过吗?说说看

在 Vue 中,通常我们使用 Vue 实例来管理状态和数据的响应性。但是有时候,我们可能需要在非组件的地方使用响应式对象,比如在普通的 JavaScript 对象或函数中。这时,Vue 提供了 Vue.observable 方法来创建一个可观察的对象。

使用 Vue.observable,我们可以将一个普通的 JavaScript 对象转换为可观察的对象。这样,当这个对象的属性发生变化时,相关的组件或函数可以自动更新。

下面是使用 Vue.observable 的示例:

import { reactive, readonly, ref } from 'vue';

const state = Vue.observable({
  count: 0,
  message: 'Hello',
});

// 访问和修改属性
console.log(state.count); // 输出:0
state.count++; // 自动触发更新

// 创建只读的响应式对象
const readonlyState = Vue.readonly(state);

// 创建响应式的引用
const countRef = Vue.ref(state.count);
console.log(countRef.value); // 输出:1
countRef.value++; // 自动触发更新

通过使用 Vue.observable,我们可以方便地在非组件的地方创建可观察的对象,实现响应式的数据管理。这在一些辅助函数、工具函数或独立的逻辑中非常有用。但需要注意的是,Vue.observable 只能用于创建可观察的对象,而不能直接用于创建可观察的数组。如果需要对数组进行响应式处理,可以使用 Vue 提供的其他方法,如 reactive 或 ref。

你知道vue中 key 的原理吗?说说你对它的理解?

在 Vue 中,key 是用于在虚拟 DOM(Virtual DOM)渲染过程中的一种优化手段。每个 Vue 中的节点(组件)都可以通过 key 属性来指定一个唯一的标识符,以便 Vue 能够跟踪和管理这些节点的状态。

key 的主要作用是帮助 Vue 识别节点的身份,以便在进行 diff 算法比对时能够准确地找到对应的节点,从而实现高效的更新和渲染。

下面是一些 key 的原理和使用场景的理解:

  1. 节点身份标识:key 的主要作用是标识节点的身份。当 Vue 进行 diff 算法比对时,通过 key 可以追踪每个节点的变化情况,从而判断是否需要更新、复用或删除节点。如果没有 key,Vue 只能通过遍历整个节点树来进行比对,效率会受到影响。
  2. 提高渲染性能:通过使用 key,Vue 可以在进行列表渲染时,准确地判断出哪些元素是新增的、哪些元素是被删除的,从而只进行必要的更新操作,避免不必要的 DOM 操作和重新渲染,提高渲染性能。
  3. 列表重排和动画效果:在列表渲染中,如果没有指定 key,当列表项的顺序发生变化时,Vue 可能会错误地判断为删除和新增操作,导致列表项的重新渲染。通过使用 key,可以显式地声明列表项的顺序和身份,避免不必要的重新渲染,同时也为实现过渡动画效果提供了基础。

需要注意的是,key 必须是唯一且稳定的标识符。唯一性是确保每个节点都有一个独立的标识,稳定性是确保在节点重新排序时,每个节点都能保持自己的身份不变。如果 key 不唯一或不稳定,可能会导致渲染错误或性能问题。

总结来说,key 是 Vue 中用于标识节点身份的属性,它在虚拟 DOM 渲染过程中起着关键的作用。通过正确使用 key,可以提高渲染性能、避免不必要的重新渲染,并为列表重排和动画效果提供支持。

怎么缓存当前的组件?缓存后怎么更新?说说你对keep-alive的理解是什么?

在 Vue 中,可以使用 <keep-alive> 组件来缓存当前的组件。<keep-alive> 是 Vue 提供的一个抽象组件,用于缓存动态组件或组件的一部分,以便在组件被切换时保留其状态。

使用 <keep-alive> 组件的步骤如下:

  1. 将需要缓存的组件包裹在 <keep-alive> 标签内,例如:
<keep-alive><your-component />
</keep-alive>
  1. 当组件被包裹在 <keep-alive> 中时,组件实例将被缓存,而不会被销毁。

当组件被缓存后,如果需要更新缓存的组件,可以使用 <keep-alive> 提供的特殊属性 include 和 exclude。这两个属性用于控制哪些组件需要被缓存和哪些组件需要被排除在缓存之外。

  • include:用于指定需要缓存的组件的名称,可以是组件的名称或组件的 name 属性的值,多个组件之间用逗号分隔。
  • exclude:用于指定不需要被缓存的组件的名称。

下面是一个使用 include 属性的示例:

<keep-alive include="YourComponent">
  <router-view />
</keep-alive>

在上面的示例中,只有名称为 "YourComponent" 的组件会被缓存,其他组件不会被缓存。

通过使用 <keep-alive> 组件,可以在组件切换时保留其状态,避免重复的创建和销毁,提高性能和用户体验。但需要注意,缓存的组件可能会占用一定的内存,因此在使用 <keep-alive> 时需要根据实际情况进行权衡和调整。

总结来说,<keep-alive> 是 Vue 提供的一个用于缓存组件的抽象组件。它可以帮助我们在组件切换时保留其状态,并提供 include 和 exclude 属性来控制缓存的组件。通过合理使用 <keep-alive>,我们可以提高应用的性能和用户体验。

Vue常用的修饰符有哪些?有什么应用场景?

在 Vue 中,常用的修饰符有以下几种:

  1. .prevent:阻止默认事件的触发。常用于表单提交时阻止页面刷新。
  2. .stop:阻止事件冒泡。当一个元素上绑定了多个事件处理程序时,可以使用 .stop 修饰符阻止事件冒泡到更上层的元素。
  3. .capture:使用事件捕获模式而非冒泡模式。默认情况下,事件是在冒泡阶段处理的,使用 .capture 修饰符可以将事件处理转换为捕获阶段处理。
  4. .self:只当事件在目标元素自身触发时才调用事件处理程序,而不是在子元素触发时也调用。常用于父子组件嵌套的场景,只在特定的元素上触发事件处理。
  5. .once:只触发一次事件处理程序。当事件处理程序只需要执行一次时,可以使用 .once 修饰符。
  6. .passive:指示浏览器不需要等待事件处理程序完成,可以立即执行滚动等默认行为。对于滚动、触摸等频繁触发的事件,使用 .passive 修饰符可以提升性能。

这些修饰符可以在事件绑定时使用,通过在事件名称后加上修饰符来修饰事件的行为。

修饰符的应用场景如下:

  • .prevent 修饰符常用于表单提交时,阻止默认的页面刷新行为,以便在提交前进行一些自定义逻辑处理。
  • .stop 修饰符常用于阻止事件冒泡,避免事件的触发传递到更上层的元素,从而在复杂的组件结构中准确定位事件的处理。
  • .self 修饰符常用于父子组件嵌套的情况下,限制事件只在目标元素自身触发,避免子元素的事件也触发处理。
  • .once 修饰符常用于只需要执行一次的事件处理,例如点击按钮后只执行一次的操作。
  • .passive 修饰符常用于提升性能,特别是在频繁触发的事件上,通过指示浏览器可以立即执行默认行为,加快事件的处理速度。

修饰符能够提供更精确的事件处理控制和性能优化,根据具体的场景选择合适的修饰符可以提升代码的可读性和性能效果。

你有写过自定义指令吗?自定义指令的应用场景有哪些?

在 Vue 中,自定义指令是一种用于扩展 Vue 模板语法的功能。通过自定义指令,你可以直接操作 DOM,添加事件监听器、样式、动画等,以及封装可重用的行为。

自定义指令的应用场景有很多,以下是一些常见的应用场景:

  1. 操作 DOM:自定义指令可以直接操作 DOM 元素。例如,你可以创建一个自定义指令来聚焦输入框,或者在特定条件下显示/隐藏元素。
  2. 事件处理:自定义指令可以用于添加特定的事件处理逻辑。例如,你可以创建一个自定义指令,在元素上添加点击事件,并在点击时执行特定的操作。
  3. 样式和动画:自定义指令可以用于管理元素的样式和动画。例如,你可以创建一个自定义指令,在特定条件下添加或移除 CSS 类,从而改变元素的样式或触发动画效果。
  4. 表单验证:自定义指令可以用于实现表单验证逻辑。例如,你可以创建一个自定义指令,在输入框失去焦点时验证输入内容,并根据验证结果显示错误提示信息。
  5. 第三方库集成:自定义指令可以用于与第三方库进行集成。例如,你可以创建一个自定义指令,实现与图表库、日期选择器等插件的集成。

自定义指令的优势在于它们可以封装特定的行为,使代码更具可复用性和可维护性。通过自定义指令,你可以将复杂的操作封装为一个指令,然后在需要的地方直接调用,提高代码的可读性和开发效率。

要创建一个自定义指令,你可以使用 Vue.directive 方法。指令定义对象包含了指令的生命周期钩子函数和相关的方法。你可以在 bind 钩子函数中进行初始化逻辑,在 update 钩子函数中响应数据的变化,在 unbind 钩子函数中清理资源。

例如,下面是一个简单的自定义指令,用于在元素上添加背景颜色:

Vue.directive('highlight', {
  bind(el, binding) {
    el.style.backgroundColor = binding.value;
  }
});

然后,你可以在模板中使用这个自定义指令:

<div v-highlight="'red'">这是一个自定义指令</div>

在上面的例子中,v-highlight="'red'" 将会触发自定义指令,并将元素的背景颜色设置为红色。

总结来说,自定义指令是 Vue 中用于扩展模板语法的功能。它可以操作 DOM、处理事件、管理样式和动画、实现表单验证等。通过自定义指令,你可以在模板中直接调用封装好的行为,提高代码的可复用性和可维护性。

Vue中的过滤器了解吗?过滤器的应用场景有哪些?

过滤器是一种用于对数据进行格式化处理的功能。

过滤器的应用场景有以下几种:

  1. 文本格式化:过滤器可以用于格式化文本数据的展示方式。例如,你可以创建一个过滤器将文本转换为大写或小写,或者对日期进行格式化。
  2. 数据处理:过滤器可以对数据进行处理和计算。例如,你可以创建一个过滤器用于计算商品价格的折扣价或税后价。
  3. 数据筛选:过滤器可以用于筛选数据集合中的特定项。例如,你可以创建一个过滤器用于过滤出满足特定条件的数据项。
  4. 数据排序:过滤器可以用于对数据集合进行排序。例如,你可以创建一个过滤器将数组按照特定的属性进行排序。
  5. 数据转换:过滤器可以将数据从一种类型转换为另一种类型。例如,你可以创建一个过滤器将数字转换为货币格式,或将字符串转换为 URL 编码格式。

在 Vue 中,你可以使用 Vue.filter 方法来注册过滤器。过滤器可以在模板中通过 {{ value | filterName }} 的语法来使用,其中 value 是要过滤的数据,filterName 是过滤器的名称。

以下是一个示例,展示了如何创建一个简单的过滤器来格式化日期:

Vue.filter('formatDate', function(value) {
  if (value) {
    const date = new Date(value);
    return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
  }
});

然后,在模板中可以这样使用过滤器:

<p>{{ dateValue | formatDate }}</p>

在上面的例子中,dateValue 是要格式化的日期数据,formatDate 是注册的过滤器名称。

总结来说,过滤器是一种用于对数据进行格式化处理的功能。它可以用于文本格式化、数据处理、数据筛选、数据排序和数据转换等场景。通过注册过滤器并在模板中使用,可以方便地对数据进行格式化展示和处理。

什么是虚拟 DOM?如何实现一个虚拟 DOM?说说你的思路

虚拟 DOM(Virtual DOM)是一种用于在内存中表示真实 DOM 结构的轻量级 JavaScript 对象。它是为了提高前端框架(如 Vue 和 React)的性能而引入的概念。

实现一个简单的虚拟 DOM 可以按照以下思路:

  1. 创建虚拟 DOM 对象:虚拟 DOM 是一个 JavaScript 对象,它包含了节点的标签名、属性、子节点等信息。可以使用一个类或者纯对象来表示虚拟 DOM。
  2. 渲染虚拟 DOM:在页面初始化时,将虚拟 DOM 渲染为真实的 DOM 结构。可以使用递归的方式遍历虚拟 DOM 树,根据节点信息创建对应的真实 DOM 节点,并将其添加到文档中。
  3. 更新虚拟 DOM:当数据发生变化时,需要更新虚拟 DOM。可以使用 diff 算法比较新旧虚拟 DOM 的差异,找出需要更新的部分。
  4. 生成更新补丁:根据差异,生成一系列更新操作的描述,也可以称之为补丁(Patch)。补丁可以表示节点的新增、删除、替换、属性修改等操作。
  5. 应用更新补丁:将生成的补丁应用到真实的 DOM 上,更新页面显示。可以使用递归的方式遍历补丁列表,根据补丁类型执行相应的操作,如添加节点、删除节点、修改属性等。

通过上述步骤,我们可以实现一个简单的虚拟 DOM。

然而,需要注意的是,实现一个完整高效的虚拟 DOM 是一个复杂的任务,涉及到 DOM diff 算法、事件处理、批量更新等方面的优化。在实际开发中,通常会使用成熟的前端框架(如 Vue、React)来处理虚拟 DOM,它们已经内置了高效的虚拟 DOM 实现,并且提供了丰富的生态系统和工具来简化开发过程。

总结来说,虚拟 DOM 是一种用于在内存中表示真实 DOM 结构的 JavaScript 对象。实现一个简单的虚拟 DOM 可以通过创建虚拟 DOM 对象、渲染虚拟 DOM、更新虚拟 DOM、生成更新补丁和应用更新补丁等步骤来实现。然而,实际开发中通常会使用现有的前端框架来处理虚拟 DOM,以获得更好的性能和开发体验。

你了解 vue 的 diff 算法吗?说说看

Vue 使用了一种基于虚拟 DOM 的 diff 算法来高效地更新真实 DOM。

Vue 的 diff 算法主要包括以下几个步骤:

  1. 创建虚拟 DOM:在每次更新前,Vue 会先根据模板或组件的渲染函数生成新的虚拟 DOM 树,表示期望的 DOM 结构。
  2. 比较新旧虚拟 DOM:Vue 会对新旧虚拟 DOM 进行深度优先遍历,比较节点的类型、标签名和 key 值等信息,找出差异。
  3. 生成差异补丁:在比较过程中,Vue 会生成一系列差异补丁(Patch),用于描述需要对真实 DOM 进行的具体操作,如新增节点、删除节点、修改属性等。
  4. 应用差异补丁:Vue 会将生成的差异补丁应用到真实 DOM 上,实现页面的更新。Vue 采用深度优先遍历补丁列表的方式,依次执行补丁操作,从而完成 DOM 的更新。

在比较虚拟 DOM 的过程中,Vue 使用了一些优化策略,以减少比较的时间复杂度和提高性能,例如:

  1. 同级比较:Vue 只会在同级进行节点比较,不会跨级比较。这样可以避免了复杂度为 O(n^3) 的情况。
  2. key 值比较:Vue 会根据节点的 key 值来优化比较过程。通过 key 值的比较,Vue 可以判断出节点的移动、新增和删除,从而避免不必要的操作。
  3. 组件级别比较:对于组件,Vue 会比较组件的根节点,而不会递归比较组件内部的子节点。这样可以减少比较的复杂度。

通过这些优化策略,Vue 的 diff 算法可以高效地找出变化的部分,并最小化对真实 DOM 的操作,从而提高页面的性能和响应速度。

需要注意的是,虽然 Vue 的 diff 算法可以减少不必要的 DOM 操作,但仍然需要谨慎使用,特别是在处理大量数据或复杂视图结构时。在一些特殊情况下,手动优化 DOM 操作可能会比 diff 算法更高效。因此,了解 diff 算法的原理和适用场景对于开发者来说是有益的。

Vue 项目中有封装过 axios 吗?主要是封装哪方面的?

在 Vue 项目中,通常会对 axios 进行封装以方便统一处理 HTTP 请求和响应,并提供一些额外的功能和配置。

主要方面的封装包括:

  1. 创建实例:封装 axios 可以创建一个单独的实例,用于发送 HTTP 请求。这样可以在不同的模块或组件中使用相同的配置和拦截器。
  2. 请求拦截器:封装 axios 可以添加请求拦截器,用于在发送请求前进行一些处理,如添加认证信息、设置请求头等。
  3. 响应拦截器:封装 axios 可以添加响应拦截器,用于在接收响应后进行一些处理,如错误处理、数据转换等。
  4. 错误处理:封装 axios 可以统一处理请求错误,例如网络错误、超时错误等。可以通过响应拦截器来捕获错误并进行统一的处理,如显示错误提示、重新发送请求等。
  5. 统一配置:封装 axios 可以提供一些统一的配置选项,如设置基础 URL、设置默认请求头、设置超时时间等。这样可以避免在每个请求中重复设置相同的配置。
  6. 取消请求:封装 axios 可以提供取消请求的功能,以避免发送不必要的请求。可以通过创建一个 CancelToken 实例,并在需要取消请求的地方调用取消方法。
  7. 处理响应数据:封装 axios 可以对响应数据进行处理,如解析 JSON 数据、处理文件下载、处理错误码等。
  8. 封装常用请求方法:封装 axios 可以提供一些常用的请求方法,如 GET、POST、PUT、DELETE 等。这样可以简化发送请求的代码,并提供更加语义化的接口。

以上是封装 axios 的一些常见方面,实际项目中可能根据需求和团队的规范进行更多的封装和定制化。封装 axios 可以提高开发效率、简化代码、统一处理请求和响应,并提供一些额外的功能和配置。

你了解 axios 的原理吗?

Axios 是一个基于 Promise 的 HTTP 客户端库,用于在浏览器和 Node.js 中发送 HTTP 请求。它提供了简洁的 API,使得发送异步请求变得更加容易和灵活。

Axios 的主要特性和工作原理如下:

  1. 底层使用 XMLHttpRequest 对象或者是 Node.js 的 http 模块来发送请求,并处理响应。
  2. 支持 Promise API,可以使用 async/await 或者是 Promise 的链式调用来处理异步请求。
  3. 提供了丰富的配置选项,如请求的 URL、请求方法、请求头、超时时间等。
  4. 支持请求拦截器和响应拦截器。请求拦截器可以在发送请求之前对请求进行预处理,响应拦截器可以在接收到响应后对响应进行处理。
  5. 支持取消请求。可以通过创建 CancelToken 实例,并在需要取消请求的地方调用取消方法。
  6. 自动转换请求和响应数据。根据请求头中的 Content-Type 自动转换请求数据为合适的格式(如 JSON、URL 编码等),并根据响应头中的 Content-Type 自动解析响应数据。
  7. 提供了一些常用的请求方法,如 GET、POST、PUT、DELETE 等。这些方法可以方便地发送特定类型的请求,并自动处理请求参数的序列化和拼接。
  8. 支持并发请求。可以使用 axios.all 或者 axios.spread 方法来处理多个并发请求的结果。

SSR 解决了什么问题?有做过SSR吗?你是怎么做的?

SSR(服务器端渲染)解决了一些传统的单页面应用(SPA)在首次加载和 SEO 方面存在的问题。

传统的 SPA 在客户端使用 JavaScript 来渲染页面,这意味着在首次加载时,浏览器需要下载并执行 JavaScript 代码,然后再进行页面渲染。这种方式可能导致以下问题:

  1. 首次加载慢:由于需要下载和执行 JavaScript 代码,首次加载速度可能较慢,尤其是对于复杂的应用程序。
  2. SEO 不友好:搜索引擎爬虫在抓取页面时通常只会执行基本的 JavaScript,无法获取完整渲染后的页面内容。这可能导致搜索引擎无法正确解析和索引页面的内容,影响页面的搜索引擎优化(SEO)。

SSR 的目标是将页面的渲染从客户端转移到服务器端,以解决上述问题。在 SSR 中,服务器会在响应请求之前先进行页面的渲染,并将渲染后的 HTML 响应给客户端。客户端接收到完整的 HTML 后,可以立即显示页面内容,无需等待 JavaScript 的下载和执行。

SSR 的优势包括:

  1. 加载速度更快:由于服务器端已经进行了页面渲染,客户端只需接收和显示已渲染的 HTML,因此首次加载速度更快,用户体验更好。
  2. 更好的 SEO:由于搜索引擎爬虫可以直接获取到完整渲染后的 HTML 内容,SSR 有助于提高页面在搜索引擎中的可索引性和可访问性,从而改善 SEO。
  3. 更好的首次渲染性能:由于首次渲染在服务器端完成,减轻了客户端设备的负担,尤其是对于性能较低的设备或网络条件较差的用户,可以提供更好的首次渲染性能。

vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?

在Vue中实现权限管理,可以按以下步骤进行:

  1. 定义权限:首先,需要定义不同的权限级别或角色,例如管理员、普通用户等。对于按钮级别的权限,可以给每个按钮分配一个相应的权限标识。
  2. 获取权限信息:在用户登录时,从后端获取用户的权限信息,并保存在前端,例如存储在 Vuex 的状态管理中或使用 Vue 的全局变量。
  3. 控制路由访问权限:根据用户的权限信息,动态生成路由配置。在路由配置中,可以使用路由守卫(如 beforeEnter)或导航守卫(如 beforeEach)来检查用户的权限,并决定是否允许访问该路由。
  4. 控制组件显示权限:在组件级别,可以使用 v-if 或 v-show 指令根据用户权限来控制组件的显示与隐藏。通过判断用户的权限标识,决定是否渲染该组件或显示特定内容。
  5. 控制按钮级别权限:对于按钮级别的权限控制,可以通过指令或自定义指令来实现。根据用户的权限信息,决定按钮是否可见或可点击。可以在按钮的点击事件中检查用户的权限,如果权限不足,则阻止执行相应的操作。
  6. 权限管理界面:开发一个权限管理界面,用于管理用户的权限信息。在该界面中,可以进行权限的分配和管理,例如给用户分配角色、分配按钮权限等。

Vue项目中你是如何解决跨域的呢?

在Vue项目中解决跨域问题可以采用以下几种方法:

  1. 代理服务器:在开发环境中,可以配置一个代理服务器来转发请求,绕过浏览器的同源策略。通过配置 vue.config.js 文件中的 devServer.proxy 字段,将要请求的 API 代理到同一域名下的指定路径。例如:
// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://api.example.com',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
};

上述配置将以 /api 开头的请求代理到 http://api.example.com,并将 /api 从请求路径中移除。

  1. CORS(跨域资源共享):在后端接口中设置合适的 CORS 头部信息,允许特定的域名或所有域名访问接口。在服务器的响应头中添加 Access-Control-Allow-Origin 字段,指定允许访问的域名。例如,在 Express 框架中可以通过设置中间件来实现:
app.use(function(req, res, next) {
  res.header('Access-Control-Allow-Origin', 'http://localhost:8080'); // 允许访问的域名
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); // 允许的请求方法
  res.header('Access-Control-Allow-Headers', 'Content-Type'); // 允许的请求头
  next();
});

上述代码将允许 http://localhost:8080 域名下的请求访问接口,并允许常见的请求方法和请求头。

  1. JSONP(仅限 GET 请求):对于只支持 GET 请求的接口,可以使用 JSONP 来实现跨域。JSONP 利用 <script> 标签的跨域特性来获取数据。可以通过在前端动态创建 <script> 标签,并将接口地址作为 src 属性值,后端返回的数据要包裹在回调函数中。例如:
function handleResponse(data) {
  // 处理返回的数据
}

var script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);

上述代码将通过 <script> 标签访问 http://api.example.com/data 接口,并将返回的数据传递给名为 handleResponse 的回调函数。

这些是常见的解决跨域问题的方法。根据具体的情况选择合适的方法来解决跨域问题。需要注意的是,在生产环境下,应该确保后端接口和前端应用部署在同一个域名下,以避免跨域问题。

vue项目本地开发完成后部署到服务器后报404是什么原因呢?

你是怎么处理vue项目中的错误的?

vue3有了解过吗?能说说跟vue2的区别吗?

  1. 性能提升:Vue 3在内部进行了重写和优化,提供了更好的性能。通过使用Proxy代理和重写响应式系统,Vue 3实现了更高效的依赖追踪和渲染机制,减少了不必要的触发和更新,从而提高了应用的性能。
  2. Composition API:Vue 3引入了Composition API,这是一个用于组织和复用逻辑的新的API风格。相较于Vue 2的Options API,Composition API更加灵活和可组合,允许开发者根据功能而非组件进行逻辑组织,提高了代码的可维护性和重用性。
  3. 更好的TypeScript支持:Vue 3对TypeScript的支持更加完善。通过使用TypeScript来开发Vue 3应用,可以获得更好的类型推断和类型检查,提供了更好的开发体验和代码健壮性。
  4. Teleport(传送门)组件:Vue 3引入了Teleport组件,提供了一种方便的方式来在DOM中的任意位置渲染组件。Teleport允许将组件的内容渲染到DOM中的指定位置,而不受组件嵌套结构的限制。
  5. Fragment(片段)语法:Vue 3引入了Fragment语法,允许在不添加额外标签的情况下,渲染多个根级别的元素。这对于需要返回多个相邻元素的组件非常有用,避免了使用包裹元素的需求。
  6. 更小的包体积:Vue 3通过使用Tree-shaking和优化打包算法,减少了打包后的包体积。这意味着在Vue 3中使用的应用程序会有更快的加载速度和更小的初始下载大小。

需要注意的是,由于Vue 3在内部进行了重写,与Vue 2存在一些不兼容的变化。因此,如果你打算从Vue 2迁移到Vue 3,可能需要进行一些代码调整和迁移工作。Vue官方提供了迁移指南,可以帮助你更好地了解和处理这些变化。

总体而言,Vue 3带来了许多改进和新特性,提供了更好的性能、更灵活的API和更好的开发体验,使得Vue成为一个更强大和现代化的前端框架。

手写双向数据绑定(v-model)

1、使用 Object.defineProperty 的 set 方法监听数据的变化

2、保存 Dom、使用正则去除 {{}}

3、给 input 添加 keyup事件,获取对应的 key,改变 data

4、随着数据的改变 set 方法执行,将原先保存的 dom 的 innerHTML 替换为 input 输入的值

html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>v-model</title>
</head>

<!-- 
  数据双向绑定
  逻辑 -> 数据 <-> 视图
  Vue -> v-model
-->

<body><div id="app"><div><input type="text" v-model="name" placeholder="姓名" /><input type="text" v-model="age" placeholder="年龄" /><input type="text" v-model="email" placeholder="邮箱" /><input type="text" v-model="tel" placeholder="电话" /></div><div><p>姓名:<span>{{ name }}</span></p><p>年龄:<span>{{ age }}</span></p><p>邮箱:<span>{{ email }}</span></p><p>电话:<span>{{ tel }}</span></p></div></div>

  <script src="./mvvm.js"></script><script>const app = new MVVM('#app', {
      name: '',
      age: '',
      email: '',
      tel: ''
    })
  </script>
</body>
</html>

js

class MVVM {
  constructor(el, data) {
    this.el = document.querySelector(el);
    this._data = data;
    this.domPool = {};
    this.init();
  }

  init() {
    this.initData();
    this.bindDom(this.el);
    this.bindInput(this.el);
  }

  initData() {
    let _this = this;
    this.data = {};

    for(let key in this._data) {
      Object.defineProperty(this.data, key, {
        set(newValue) {
          _this.domPool[key].innerHTML = newValue;
          _this._data[key] = newValue;
        }
      })
    }
  }

  bindDom(el) {
    const childNodes = el.childNodes;

    childNodes.forEach(item => {
      if(item.nodeType === 3) {
        const _value = item.nodeValue;


        if(_value.trim().length) {
          let reg = /{{(.+?)}}/;
          let isValid = reg.test(_value);
          if(isValid) {
            const _key = _value.match(reg)[1].trim();
            this.domPool[_key] = item.parentNode;
            item.parentNode.innerText = this._data[_key] || '';
          }
        }
      }

      item.childNodes && this.bindDom(item);
    })
  }

  bindInput(el) {
    const _allInputs = el.querySelectorAll('input');

    _allInputs.forEach(input => {
      const _vModel = input.getAttribute('v-model');
      if(_vModel) {
        input.addEventListener('keyup', this.handleInput.bind(this, _vModel, input), false);
      }
    })
  }

  handleInput(key, input) {
    this.data[key] = input.value;
  }
}

keep-alive 中的生命周期有哪些?

keep-alive 是 Vue 提供的一个内置组件,用来对组件进行缓存,在组件切换过程中将状态保留在内存中,防止重复渲染 DOM。 如果为一个组件包裹了 keep-alive,那么它会多出两个生命周期:deactivated、activated。同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。 当组件被换掉时,会被缓存到内存中、触发 deactivated 生命周期; 当组件被切回来时,再去缓存里找这个组件、触发 activated 钩子函数。

1/0