Vue 高频面试题

Vue2 相关问题

下面是当简历项目为vue2 时面试遇到过得问题

1、为什么data 返回值是个函数

为了确保每个组件实例都有独立的、隔离的数据作用域;
假如data是对象,所有组件实例会共享同一个数据对象,如果某个组件实例中修改了这个对象会影响其他的组件实例。
而写成函数的形式,每次创建一个新的组件实例,这个函数都会被调用从而返回一个新的独立的数据对象,每个组件实例都有自己的数据对象,互不干扰。

export default {
  data() {
    return {
      message: 'Hello, Vue!'
    };
  }

2、nextTick 是什么

在vue中,我们变更了数据DOM也会更新,但不是同步生效的,vue会记录所有的更改并在下一次事件循环中批量更新DOM,以避免不必要的DOM更新。但在开发中,我们可能需要数据变更后等DOM 也变更后再执行某种操作。nextTick 方法允许你延迟执行代码,直到 Vue 完成下一个 DOM 更新周期。在该方法的回调中我们可以访问到更新后的DOM

import { nextTick } from 'vue'
function increment() {
  state.count++
  nextTick(() => {
    // 访问更新后的 DOM
  })
}

通常使用情况:
1、需要计算DOM最新的大小或位置
2、确保元素有了样式后进行过渡或动画
3、确保DOM数据展示是最新的后再执行某种操作

3、什么是计算属性

模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。使用计算属性来描述依赖响应式状态的复杂逻辑,实现逻辑和结构的分离,同时可以实现复用;计算属性有getter 和 setter ,返回值为一个新的派生数据,计算属性会自动追踪响应式依赖,当依赖变化时重新计算;

<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

4、computed 和 wacth 的区别

概念上的区别,computed 是计算属性,返回一个派生的计算结果;watch 是监听器,可以监听指定数据源并监听到变化后触发指定的回调函数;
不同点:
1、computed 支持缓存,只有依赖数据发生改变,才会重新进行计算; watch不支持缓存,数据变,直接会触发相应的操作;
2、computed第一次加载时就监听;watch默认第一次加载时不监听(需要设置immediate:true)
3、不支持异步,当computed内有异步操作时无效,无法监听数据的变化;watch支持异步;
4、computed 有getter 和 setter 方法,watch 可一设置deep:true, immediate:true,flush: ‘post’

5、wacth 的flush: 'post’有什么作用

默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。

如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: ‘post’ 选项:

watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'post'
})

6、watch 和watchEffect() 的区别

1、回调会立即执行,不需要指定 immediate: true
2、不再需要明确传递数据源,会自动追踪依赖项
以下是代码示例:

const todoId = ref(1)
const data = ref(null)

watch(
  todoId,
  async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
    )
    data.value = await response.json()
  },
  { immediate: true }
)
watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})

7、计算属性和方法的区别

1.计算属性是一个属性 必须要有返回值 methods不一定
2.计算属性在页面渲染时 不需要加括号 methods必须要加
3.计算属性有缓存,一个计算属性仅会在其响应式依赖更新时才重新计算。 methods没有缓存 从性能上来讲 计算属性更具有优势

8、v-if 和 v-show 的区别

v-if 是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多, 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 display 属性进行切换。
所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。

9、v-if 和 v-for 一起使用优先级如何,如何解决

在vue2中,v-for的优先级是高于v-if的,如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能;另外需要注意的是在vue3则完全相反,v-if的优先级高于v-for,所以v-if执行时,它调用的变量还不存在,就会导致异常。
解决办法:
1、将原来使用v-if判断的项目使用计算属性过滤,只要遍历过滤后的列表
2、在外层包裹template,在外层循环,内层判断

<!--
 这会抛出一个错误,因为属性 todo 此时
 没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>
在外新包装一层 <template> 再在其上使用 v-for 可以解决这个问题 (这也更加明显易读):

template
<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

10、为什么v-for 需要加key

Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute。

11、v-on 常见修饰符

.once - 只触发一次回调。
.prevent - 调用 event.preventDefault()阻止默认行为
.stop - 调用 event.stopPropagation()阻止冒泡
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.capture - 添加事件侦听器时使用 capture 模式,默认情况下是事件冒泡, 如果想变成事件捕获, 那么就需要使用.capture修饰符

12、vue常见指令

1、v-if:根据表达式的值的真假条件渲染元素。在切换时元素及它的数据绑定 / 组件被销毁并重建。
2、v-show:根据表达式之真假值,切换元素的 display CSS 属性。
3、v-for:循环指令,基于一个数组或者对象渲染一个列表,vue 2.0以上必须需配合 key值 使用。
4、v-bind:动态地绑定一个或多个特性,或一个组件 prop 到表达式。
5、v-on:用于监听指定元素的DOM事件,比如点击事件。绑定事件监听器。
6、v-model:实现表单输入和应用状态之间的双向绑定
7、v-pre:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
8、v-once:只渲染元素和组件一次。随后的重新渲染,元3 素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能

13、keep-alive 是什么

keep-alive 组件是 vue 的内置组件,用于缓存内部组件实例。这样做的目的在于,keep-alive 内部的组件切回时,不用重新创建组件实例,而直接使用缓存中的实例,一方面能够避免创建组件带来的开销,另一方面可以保留组件的状态。

14、keep-alive的常用属性有哪些?

keep-alive 具有 include 和 exclude 属性,传入正则表达式或字符串根据名称匹配的组件,通过它们可以控制哪些组件进入缓存。另外它还提供了 max 属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue 会移除最久没有使用的组件缓存

15、与keep-alive相关的生命周期函数是什么,什么场景下会进行使用

受keep-alive的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是 activated 和 deactivated,它们分别在组件激活和失活时触发。第一次 activated 触发是在 mounted 之后

<script setup>
import { onActivated, onDeactivated } from 'vue'

onActivated(() => {
  // 调用时机为首次挂载
  // 以及每次从缓存中被重新插入时
})

onDeactivated(() => {
  // 在从 DOM 上移除、进入缓存
  // 以及组件卸载时调用
})
</script>

16、keep-alive的实现原理是什么?

keep-alive的实现原理是什么?

// keep-alive 内部的声明周期函数

created () {

    this.cache = Object.create(null)

    this.keys = []

}

key 数组记录目前缓存的组件 key 值,如果组件没有指定 key 值,则会为其自动生成一个唯一的 key 值
cache 对象以 key 值为键,vnode 为值,用于缓存组件对应的虚拟 DOM
在 keep-alive 的渲染函数中,其基本逻辑是判断当前渲染的 vnode 是否有对应的缓存,如果有,从缓存中读取到对应的组件实例;如果没有则将其缓存。
当缓存数量超过 max 数值时,keep-alive 会移除掉 key 数组的第一个元素。

参考文章

17、全局组件和局部组件的区别

1、全局组件注册在全局,应用中的任意模块都可以使用;局部组件在vue2中需要再使用的组件中注册,vue3中在<script setup> 的单文件组件中直接导入即可使用无需注册。但是局部组件仅在导入的组件中可以使用;
2、全局组件没被使用过的组件在生产打包中不会被自动移除(tree-shaking);
3、全局组件在大型项目中会让依赖关系变得不明确,如果使用过多不利于维护,局部组件显示导入,依赖关系明确。

18、vue中数据流是怎么样的,实际场景中需要修改props怎么办

所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
导致你想要更改一个 prop 的需求通常来源于以下两种场景:
prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:

export default {
  props: ['initialCounter'],
  data() {
    return {
      // 计数器只是将 this.initialCounter 作为初始值
      // 像下面这样做就使 prop 和后续更新无关了
      counter: this.initialCounter
    }
  }
}

需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:

export default {
  props: ['size'],
  computed: {
    // 该 prop 变更时计算属性也会自动更新
    normalizedSize() {
      return this.size.trim().toLowerCase()
    }
  }
}

19、属性透传是什么

透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id
在vue2中,可以通过 $attrs 这个实例属性来访问组件的所有透传 attribute

export default {
  created() {
    console.log(this.$attrs)
  }
}

在vue3中,你可以在 <script setup> 中使用 useAttrs() API 来访问一个组件的所有透传 attribute:

<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

如果没有使用 <script setup>,attrs 会作为 setup() 上下文对象的一个属性暴露:

export default {
  setup(props, ctx) {
    // 透传 attribute 被暴露为 ctx.attrs
    console.log(ctx.attrs)
  }
}

20、插槽是什么

插槽是子组件中提供给父组件使用的一个占位符,用slot标签表示,父组件可以在这个占位符中填充任何模板代码,比如HTML、组件等,填充的内容会替换掉子组件的标签(替换占位符)。
Vue中的插槽大致可以分为默认插槽、具名插槽和作用域插槽三种。
如果只有一个占位符通常会使用默认插槽,而如果有多个slot 则会使用具名插槽就是给slot 添加name属性,父组件插入的内容同样要加上v-slot:插槽名称。作用域插槽是指插槽的内容可以访问父组件的状态,但无法访问子组件的状态;如果想要访问子组件的数据就需要使用作用域插槽,在子组件传入,插槽的内容可以通过prop接收;
传入:

<slot name="header" message="hello"></slot>

接收:

<template #header="headerProps">
    {{ headerProps }}
  </template>

21、组件通信的方式

1、父子组件通过prop、emit 接收和传递
在vue2中的用法:
父组件:

<template>
  <div class="section">
    <com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
    <p>{{currentIndex}}</p>
  </div>
</template>

子组件:

<template>
  <div>
    <div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">{{item}}</div>
  </div>
</template>

<script>
export default {
  props: ['articles'],
  methods: {
    emitIndex(index) {
      this.$emit('onEmitIndex', index) // 触发父组件的方法,并传递参数index
    }
  }
}
</script>

在vue3中的用法:
在使用 <script setup> 的单文件组件中,props 可以使用 defineProps() 宏来声明:

<script setup>
const props = defineProps(['foo'])
const emit = defineEmits(['inFocus', 'submit'])

function buttonClick() {
  emit('submit')
}

console.log(props.foo)
</script>

在没有使用 <script setup> 的组件中,prop 可以使用 props 选项来声明:

export default {
  props: ['foo'],
   emits: ['inFocus', 'submit'],
  setup(props,ctx) {
    // setup() 接收 props 作为第一个参数
    console.log(props.foo)
    ctx.emit('submit')
  }
}

二、事件总线eventBus,在vue3中已被删除,因为在大型项目中使用Event Bus可能会变得难以维护和调试,同时也可能会影响应用程序的性能。
(1)创建一个新的Vue实例作为事件总线

// event-bus.js
import Vue from 'vue';
export const EventBus = new Vue();

(2)在需要发送事件的组件中,可以触发事件

// 发送事件的组件
import { EventBus } from './event-bus.js';

export default {
  methods: {
    sendMessage() {
      EventBus.$emit('message', 'Hello from Component A!');
    }
  }
}

(3)在需要接收事件的组件中,可以监听事件

// 接收事件的组件
import { EventBus } from './event-bus.js';

export default {
  mounted() {
    EventBus.$on('message', (data) => {
      console.log(data); // 输出 "Hello from Component A!"
    });
  }
}

在上述代码中,我们创建了一个名为EventBus的Vue实例作为事件总线。在发送事件的组件中,我们使用EventBus.$emit方法触发名为message的事件,并传递一个消息字符串。在接收事件的组件中,我们在mounted钩子函数中使用EventBus.$on方法监听message事件,并在回调函数中处理接收到的数据。
虽然看起来比较简单,但是这种方法也有不变之处,如果项目过大,使用这种方式进行通信,后期维护起来会很困难。
三、ref / $refs
在vue2中,将ref 用于组件,被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权。通过expose 选项可以用于限制对子组件实例的访问

export default {
  expose: ['publicData', 'publicMethod'],
  data() {
    return {
      publicData: 'foo',
      privateData: 'bar'
    }
  },
  methods: {
    publicMethod() {
      /* ... */
    },
    privateMethod() {
      /* ... */
    }
  }
}

在上面这个例子中,父组件通过模板引用访问到子组件实例后,仅能访问 publicData 和 publicMethod。
在vue3中,使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
  a,
  b
})
</script>

四、provde 和 inject
Provide和Inject可以解决多层次嵌套通信问题,通常在祖先组件中通过provide提供依赖,在子孙组件中注入依赖。
在vue2中的使用:

export default {
  provide: {
    message: 'hello!'
  }
}
export default {
  inject: ['message'],
  created() {
    console.log(this.message) // injected value
  }
}

在vue3中的使用:

<script setup>
import { provide } from 'vue'

provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
import { inject } from 'vue'

const message = inject('message')
</script>

五、全局状态管理
vue2时推荐使用vuex,vue3 推荐使用pinia 做状态管理。相比于 Vuex,Pinia 提供了更简洁直接的 API,并提供了组合式风格的 API,最重要的是,在使用 TypeScript 时它提供了更完善的类型推导。

22、组件生命周期

什么是 vue 生命周期?
对于 vue 来讲,生命周期就是一个 vue 实例从创建到销毁的过程
vue 生命周期的作用是什么?
给予了开发者在不同的生命周期阶段添加业务代码的能力
vue 2生命周期有几个阶段?
1、beforeCreate(创建前):在组件实例初始化完成之后立即调用,在这个阶段数据和方法都不能被访问
2、created(创建后):组件实例处理完了与状态相关的选项后调用,但还未挂载。通常在这个生命周期发起数据请求
3、 beforeMount(载入前):当前阶段虚拟 DOM 已经创建完成,即将开始渲染
4、mounted(载入后):在挂载完成后发生,在当前阶段,真实的 DOM 挂载完毕,数据完成双向绑定,可以访问到 DOM 节点
5、beforeUpdate(更新前):发生在更新之前,也就是响应式数据发生更新,虚拟 DOM 重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
6、updated(更新后):发生在更新完成之后,当前阶段组件 DOM 已完成更新。
7、beforeDestroy(销毁前):发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器
8、destroyed(销毁后):发生在实例销毁之后,这个时候只剩下了 DOM 空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
vue3 生命周期有几个阶段?
1、setup() : 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 method
2、onBeforeMount() : 组件挂载到节点上之前执行的函数;
3、onMounted() : 组件挂载完成后执行的函数;
4、onBeforeUpdate(): 组件更新之前执行的函数;
5、onUpdated(): 组件更新完成之后执行的函数;
6、onBeforeUnmount(): 组件卸载之前执行的函数;
7、onUnmounted(): 组件卸载完成后执行的函数;
keep-alive 独有的生命周期
另外还有 keep-alive 独有的生命周期,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数;在vue3中对应的分别是 onActivated 和 onDeactivated

23、父子组件的生命周期执行顺序

我们可以发现父子组件在加载的时候,执行的先后顺序为父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。

24、对vue的理解

定义:
vue是尤雨溪设计开发的一套用于构建用户界面的渐进式框架,vue 的核心库只关注视图层。
vue具有以下优点:
1、简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
2、渐进式就是框架分层,可以一部分嵌入,你可以只用核心的视图渲染功能来开发,慢慢的过渡。也可以使用全家桶
3、轻量级,核心库只有几十kb
4、组件化,把一个单页应用中的各种模块拆分到一个一个单独的组件提高开发效率
5、基于mvvm模型实现双向数据绑定,开发者通常无需手动操作DOM。只需要关注数据逻辑
6、使用虚拟DOM和diff算法批量更新DOM,提高渲染速度和页面性能

这里针对mvvm。双向数据绑定、虚拟dom展开描述

25、vue的缺点?

缺点:
1、不够稳定,变更频繁,有时候项目中会引入一些不兼容的变化或者废弃一些特性
2、和其他框架一样不利于 SEO,不适合做需要浏览器抓取信息的电商网站,比较适合做后台管理系统。
3、SPA的特性,会在首页的时候将所有的js、css数据全部加载,当项目过于庞大时,这个白屏时间就会比较明显。

26、怎么理解vue的渐进式

渐进式就是框架分层,可以一部分嵌入,你可以只用核心的视图渲染功能来开发,也可以使用全家桶,慢慢的过渡
从里向外分别是:视图层渲染–组件机制–路由机制–状态管理–构建工具

27、对mvvm的理解

mvvm一种设计思想和架构模式,模型(Model)指的是数据模型,视图(View)指的是用户页面,ViewModel 视图模型,作为模型和视图的中间桥梁,他从模型中获取数据并转换格式,以便视图能够展示。当视图的状态发生变化时,它会通知模型进行相应的数据更新。
视图可以直接通过视图模型中绑定的属性和命令操作数据而不需要直接和模型进行交互,让我们的应用程序在UI和逻辑之间清晰的分离,提高了代码的可维护性和拓展性。
基于mvvm设计的框架有VueJS、 AngularJS

29、实现双向数据绑定的原理是什么

vue实现视图和数据双向绑定的核心就是使用Object.defineProperty()来劫持data中所有的属性,并且结合发布-订阅模式,创建一个dep类来收集依赖,并给元素添加wather监听器,当属性被修改,set方法会被调用,此时调用的dep的notify方法他会触发所有订阅者watcher的update方法来更新视图。
在vue的源码中,vue的构造函数主要做了两件事:
一、将传入的options的data都传入observe方法劫持数据
二、将模版内容传入nodeToFragment方法 进行编译后再插入到根节点中

   // 创建一个vue构造函数
        function Vue(options) {
            this.data = options.data;
            observe(this.data,this);
            var id = options.el;
            var dom = nodeToFragment(document.getElementById('app'),this);
            // 编译完成后将dom返回app中
            document.getElementById(id).appendChild(dom);
        };

observe方法中使用Object.keys和forEach方法将对象的key都使用Object.defineProperty进行劫持
,在劫持之前需要创建一个dep实例,这个dep就是一个是一个收集依赖的对象,我们需要在dep的构造函数的原型中添加两个方法,第一个是addSub 添加依赖,在getter中如果元素添加了watcher监听器后就会将这个watcher加入到sublist数组中,第二个是notify方法,在setter函数中如果数据变更就要调用dep的notify方法,这个方法中他会遍历所有收集的的订阅者也就watcher,并调用watcher中的update方法更新视图;

  function Dep () {
    this.subs = [];
  };
  Dep.prototype = {
    addSub: function (sub) {
     this.subs.push(sub);
    },
   notify: function () {
     console.log(this.subs,'this.subs')
     this.subs.forEach(function (sub) {
     sub.update();
     });
   }
};

  function defineReactive(data,key,val) {
            var dep = new Dep();
             // 响应式的数据绑定
            Object.defineProperty(data,key,{
                get:function() {
                    console.log('有人在读取数据',val);
                    console.log(Dep.target,'Dep.target')
                    if (Dep.target) {
                        dep.addSub(Dep.target);
                    }
                    return val;
                },
                set:function (newVal) {
                    console.log('有人在修改数据',newVal);
                    if(newVal === val) return;
                    val= newVal;
                    dep.notify(); // 作为发布者发出通知
                }
            })
        }
        // 定义一个监听器,劫持data中所有数据的读取和更改
        function  observe(data,vm) {
            Object.keys(data).forEach(function(key) {
                 defineReactive(data,key,data[key]);
            })
        }

在nodeToFragment方法中,vue 使用while循环将node节点一个个传入complie方法编译然后添加到DocumentFragment文档碎片中,最后返回这个文档碎片并将其插入到真实的DOM中;
这个complie编译方法会根据节点的类型判断,如果节点类型是元素获取到元素的属性列表并遍历如果存在v-model 则获取该属性对应的名称并取出vue实例data中的数据复制然后删除v-modle属性;这个步骤只实现了数据和视图的同步,要实现视图修改同步数据还需要使用addEventListener 监听节点的输入等事件并赋值给实例的data,此时会触发属性的set方法,set方法中会通知订阅并触发watcher更新视图;
如果节点类型为文本,则通过正则表达式检查是否存在插值语法如果存在将节点的文本内容替换为data属性中的数据,并且为了实现数据变更文本节点视图同步修改,需要在给每个文本节点添加watcher监听器,这样属性修改触发了set方法时会调用wathcer的update方法更新视图。

  function Watcher(vm, node, name) {
            Dep.target = this;
            this.name = name;
            this.node = node;
            this.vm = vm;
            this.update();
            Dep.target = null;
        };
        Watcher.prototype = {
            update: function () {
                this.get();
                this.node.nodeValue = this.value;
            },

            // 获取data中的属性值
            get: function () {
                this.value = this.vm.data[this.name]; // 触发相应属性的get
            }
        };
        // 编译,实现数据在dom的展示
        function complle(node,vm)  {
            var reg = /{{(.*)}}/;
            // 节点类型为元素
            if(node.nodeType === 1) {
               var attr = node.attributes;
               // 遍历解析属性
               for(var i = 0;i <attr.length;i++) {
                if(attr[i].nodeName == 'v-model') {
                    var name = attr[i].nodeValue;// 获取v-model绑定的属性名
                   
                    node.addEventListener('input',function(e){
                        // 给相应的data属性赋值,进而触发属性的set方法
                        vm.data[name] = e.target.value;
                    });
                     node.value = vm.data[name]; // 将data的值赋值给该node、
                    node.removeAttribute('v-model');
                }
               }
            };
        
            // 节点类型为text
            if(node.nodeType ===3 ) {
                console.log('我修改的时候会执行这个吗')
                if(reg.test(node.nodeValue)){
                    var name = RegExp.$1.trim();; // 获取匹配到的字符串
                    node.nodeValue = vm.data[name]; // 将data的值赋值给该node
                    new Watcher(vm, node, name);
                }
            }
        }
        // 将挂载的节点劫持处理后再返回目标中
        function nodeToFragment(node,vm) {
          
            var flag = document.createDocumentFragment();
            var child;
            // while 条件为只要有child就将它添加,并且默认值为node的第一个子节点
            while (child = node.firstChild) {
                complle(child,vm);
                flag.appendChild(child); // 将子节点劫持到文档片段中,添加的子元素会在原节点被删除
                  
            }
            return flag;
        };

30、vue2用 Object.defineProperty() 实现的问题

问题:在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染
原因: Object.defineProperty 不能拦截到这些操作。Object.defineProperty,没有对对象的新属性和数组进行属性劫持;更精确的来说,对于数组而言,大部分操作都是拦截不到的。
对策:
对于数组Vue 内部通过重写函数的方式解决了这个问题。
包括七个方法:push,pop,shift,unshift,sort,reverse,splice(Vue 将 data 中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组 api 时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化)
解决方法:
1、使用$delete、$set对象新属性无法更新视图,删除属性无法更新视图

this.$set(this.obj, 'b', 'obj.b') 
this.$delete(obj, key)

set()方法相当于手动的去把obj.b处理成一个响应式的属性,此时视图也会跟着改变了

2、使用$set 或者重写的数组方法解决([index] = xxx无法更新视图

arr.splice(index, 1, item) 
Vue.$set(arr, index, value)

31、vue3双向数据绑定原理

Vue 3 中则使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref

32、proxy和defineproperty区别

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等);
target要使用 Proxy 包装的目标对象,handler定义了在执行各种操作时代理 p 的行为

const p = new Proxy(target, handler)

Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象;

Object.defineProperty(obj, prop, descriptor)

1、Object.defineProperty只能对单个属性进行控制,需要遍历对象的每个属性来定义拦截器函数。而Proxy可以对整个对象进行控制不需要遍历更加高效
2、proxy支持的拦截操作比Object.defineProperty多,比如get、set、has、deleteProperty 等
3、proxy 可以实现数组、对象属性的监听解决了Object.defineProperty监听不到的弊端

33、虚拟DOM及作用

虚拟DOM 是一种编程概念,将UI通过数据结构虚拟的表示出来,保存在内存中,其实就是一个JS对象,代表一个虚拟节点;

const vnode = {
  type: 'div',
  props: {
    id: 'hello'
  },
  children: [
    /* 更多 vnode */
  ]
}

vue模版编译时就是将其构建为虚拟DOM树,运行时渲染器调用渲染函数然后遍历虚拟DOM树并创建实际的DOM节点,当一个依赖发生变化的时候会创建一个更新后的虚拟DOM树,运行时渲染器使用diff算法遍历对比新旧树,生成补丁然后将补丁应用到真实的DOM上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能。

34、axios的作用

axios的作用是用于向后台发起请求,并在请求中做更多可控功能。
axios是一个基于promise的HTTP库,可以用于浏览器和node.js。它支持promiseAPI、拦截请求和响应、转化请求数据和响应数据、取消请求、自动转换json数据、客户端支持防御XSRF等特性。并且,axios兼容大部分浏览器,ie8及以上版本都支持。

Vue3相关问题

2023年12月31日起,vue2 不再维护,所以猜测现在问的vue3会更多,以下是针对vue3 的问题整理

1、什么是 mixins 混入

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
// 定义一个混入对象

var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定义一个使用混入对象的组件
var Component = Vue.extend({
  mixins: [myMixin]
})

var component = new Component() // => "hello from mixin!"

当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
混入对象的钩子将在组件自身钩子之前调用。

2、什么是组合式函数

组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。组合式函数约定用驼峰命名法命名,并以“use”作为开头。
如果在每个需要获取数据的组件中都要重复这种模式,那就太繁琐了。让我们把它抽取成一个组合式函数:

// fetch.js
import { ref } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  fetch(url)
    .then((res) => res.json())
    .then((json) => (data.value = json))
    .catch((err) => (error.value = err))

  return { data, error }
}

现在我们在组件里只需要:

<script setup>
import { useFetch } from './fetch.js'
const { data, 
error } = useFetch('...')
</script>

3、组合式函数和混合的区别

Vue 2 的用户可能会对 mixins 选项比较熟悉。它也让我们能够把组件逻辑提取到可复用的单元里。然而 mixins 有三个主要的短板:

1、不清晰的数据来源:当使用了多个 mixin 时,实例上的数据属性来自哪个 mixin 变得不清晰,这使追溯实现和理解组件行为变得困难。这也是我们推荐在组合式函数中使用 ref + 解构模式的理由:让属性的来源在消费组件时一目了然。
2、命名空间冲突:多个来自不同作者的 mixin 可能会注册相同的属性名,造成命名冲突。若使用组合式函数,你可以通过在解构变量时对变量进行重命名来避免相同的键名。

3、隐式的跨 mixin 交流:多个 mixin 需要依赖共享的属性名来进行相互作用,这使得它们隐性地耦合在一起。而一个组合式函数的返回值可以作为另一个组合式函数的参数被传入,像普通函数那样。

基于上述理由,我们不再推荐在 Vue 3 中继续使用 mixin。保留该功能只是为了项目迁移的需求和照顾熟悉它的用户

4、什么是组合式API

是一系列API 的集合,使我们可以使用函数而不是声明选项的方式书写 Vue 组件。
包括:
响应式API ref() 和 reactive()
生命周期狗子 onMounted() 和 onUnmounted()
依赖注入 provide() 和 inject()

5、组合式API 和选项式 API 的区别

1、更好的逻辑复用
组合式 API 最基本的优势是它使我们能够通过组合函数来实现更加简洁高效的逻辑复用。在选项式 API 中我们主要的逻辑复用机制是 mixins,而组合式 API 解决了 mixins 的所有缺陷。
2、更灵活的代码组织
选项式处理相同逻辑关注点的代码被强制拆分在了不同的选项中,位于文件的不同部分。如果我们想要将一个逻辑关注点抽取重构到一个可复用的工具函数中,需要从文件的多个不同部分找到所需的正确片段。组合式同一个逻辑关注点相关的代码被归为了一组,不再需要为了抽象而重新组织代码,大大降低了重构成本。
3、更好的类型推导
Vue 3 默认使用 TypeScript 进行开发,用组合式 API 重写的代码可以享受到完整的类型推导,不需要书写太多类型标注。
4、更小的生产包体积
由于 <script setup> 形式书写的组件模板被编译为了一个内联函数,和 <script setup> 中的代码位于同一作用域,被编译的模板可以直接访问 <script setup> 中定义的变量,无需从实例中代理。不像选项式 API 需要依赖 this 上下文对象访问属性。这对代码压缩更友好,因为本地变量的名字可以被压缩,但对象的属性名则不能。

6、vue3 推荐使用pinia 做状态管理,为什么

现有用户可能对 Vuex 更熟悉,它是 Vue 之前的官方状态管理库。由于 Pinia 在生态系统中能够承担相同的职责且能做得更好,因此 Vuex 现在处于维护模式。它仍然可以工作,但不再接受新的功能。对于新的应用,建议使用 Pinia。事实上,Pinia 最初正是为了探索 Vuex 的下一个版本而开发的,因此整合了核心团队关于 Vuex 5 的许多想法。
相比于 Vuex,Pinia 提供了更简洁直接的 API,并提供了组合式风格的 API,最重要的是,在使用 TypeScript 时它提供了更完善的类型推导。

7、从 Vue CLI 迁移到 Vite

Vue CLI 是官方提供的基于 Webpack 的 Vue 工具链,它现在处于维护模式。我们建议使用 Vite 开始新的项目,除非你依赖特定的 Webpack 的特性。在大多数情况下,Vite 将提供更优秀的开发体验。Vite 是一个轻量级的、速度极快的构建工具,对 Vue SFC 提供第一优先级支持。作者是尤雨溪,同时也是 Vue 的作者!

要使用 Vite 来创建一个 Vue 项目,非常简单:

$ npm create vue@latest

8、vue2 和 3 响应式原理的区别

在 JavaScript 中有两种劫持 property 访问的方式:getter / setters 和 Proxies。Vue 2 使用 getter / setters 完全是出于支持旧版本浏览器的限制。而在 Vue 3 中则使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref。

9、ref 和 reactive 的区别

1、返回的对象不同,ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回,reactive() 返回的是一个原始对象的 Proxy;
2、数据类型不同,ref() 方法允许我们创建可以使用任何值类型的响应式 ref,reactive() 只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)
3、访问方式不同,ref:使用 .value 属性来访问和修改值。reactive:可以直接访问和修改对象或数组的属性或元素,而无需使用 .value。

10、reactive 的缺陷

1、有限的值类型:它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型。

2、不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失:

let state = reactive({ count: 0 })

// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })

3、对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:

const state = reactive({ count: 0 })

// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++

// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)

由于这些限制,我们建议使用 ref() 作为声明响应式状态的主要 API。

11、vue3新特性

一、Vue3 没有提供单独的 onBeforeCreate 和 onCreated 方法,因为 setup 本身是在这两个生命周期之前执行的,Vue3 建议我们直接在 setup 中编写这两个生命周期中的代码。用setup代替了 beforeCreate 和 created 这两个生命周期
二、响应式原理的变化,Vue 3 使用了新的响应式系统,将 Proxy 作为其核心,以取代 Vue 2.x 中使用的Object.defineProperty。这样可以更快地追踪变化并更新视图。
三、使用Composition API 管理和组织代码代替选项式
四、不再要求必须有一个根节点,在vue2.x中,要求每个模板必须有一个根节点
五、v-for和v-if优先级,在vue2.x中,在一个元素上同时使用v-for和v-if,v-for有更高的优先级,因此在vue2.x中做性能优化,有一个重要的点就是v-for和v-if不能放在同一个元素上。而在vue3中,v-if比v-for有更高的优先级。
六、Vue 3 使用 TypeScript 重新编写了核心代码,提供了更好的类型支持和开发体验。
七、在代码复用方面退出使用组合式函数代替mixin混入
八、引入了tree-shaking,所有API通过es6模块化的方式引入,这样打包工具在打包的时候会对未使用的模块进行剔除从而减小包体积

12、vue中为什么style上要加scoped

解决多个组件间同类名样式冲突的问题,在vue组件中加了scoped后组件的css样式就只能作用于当前组件,这样组件直接的样式就不会互相覆盖和冲突,他的原理是为组件实例生成一个唯一标识,给组件中的每个dom元素添加上data-v-开头的一串随机 hash 值,项目编译运行后每个选择器都带上了data-x-xx
当项目中引入了第三方UI组件库,需要再组件中局部修改第三方组件的样式,不想去除scoped造成样式污染可以使用样式穿透

13、如何使用深度选择器

方式一:在css中使用/deep/

/deep/ .child-component {
  color: blue;

方式二:在css中使用>>>

.search-input >>>.el-input__inner{
	border: 0 !important;
	text-indent: 20px;
}

方式三:在预处理器中使用:deep

.search :deep(.el-input__wrapper) {
    width: 700px;
    background-color: red;
}

Vuex 相关问题

1、vuex是什么

Vuex是一个专为vue.js应用程序开发的状态管理模式。当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
1、多个视图依赖于同一状态(传参的方法对于多层嵌套的组件将会非常繁琐)
2、来自不同视图的行为需要变更同一状态(通常通过父子组件传递事件,复杂时会产生难以维护的代码)

vuex将共享状态抽取出来,以一个全局单例模式管理,通过一个应用使用一个store实例用来存储应用层级的状态;
如果其他组件需要访问某个全局状态通过store.state.属性名即可访问,若组件需要修改某个全局状态时只能通过调用store.commit(‘increment’) 来提交mutation更改数据;但是mutation 必须是同步函数,如果想要异步的修改需要调用store.dispatch(‘increment’)分发action,再由Action 提交的是 mutation,而不是Action直接变更状态。最后,根据State的变化,渲染到视图上。

2、Vuex核心概念有哪些

1、state => 基本数据(数据源存放地)
2、getters => 相当于store 中的计算属性(如果没有当我们需要根据state的数据派生时每个地方都得使用函数处理一下)
3、mutations => 提交更改数据的方法,同步
4、actions => 像一个装饰器,包裹mutations,使之可以异步。
5、modules => 将一个store实例分割模块

3、Vuex 为什么要分模块

模块:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。

4、Mutation 和 Action 的区别

mutation是唯一修改state的途径,每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler),这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
而Action 可以包含任意异步操作,但不是直接修改state,action内部需要提交mutation来修改状态,action函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此可以调用 context.commit 提交一个 mutation。在视图更新时,是先触发actions在触发mutation。

store.commit('increment')

Action 提交的是 mutation,而不是直接变更状态。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

5、为什么 Vuex 的 mutation 中不能做异步操作?

每个mutation执行完成后都会对应到一个新的状态变更,如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

6、Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?

一、如果请求来的数据不要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex 的state里。
二、如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用,并包装成promise返回。

7、 Vuex 和 localStorage 的区别

1、应用场景不同,vuex是为vuejs程序开发的,解决组件直接数据共享问题。而 localStorage是本地存储技术,通过用来保存需要跨页面传递的数据
2、vuex 是响应式的,而 localStorage不是
3、vuex的数据是存储在内存中的刷新页面数据会丢失,而localstorage不会
4、vuex可以存储任意类型的数据,而localStorage只能存储字符串类型的数据,存储对象需要JSON的stringify和parse方法进行处理。

8、Vuex和单纯的全局对象有什么区别?

1、Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
2、不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用

9、mapState 和 mapGetters 是干啥的

使用 mapState 辅助函数帮助我们生成计算属性

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

VueRouter 相关问题

1、路由守卫

vue-router的路由守卫主要用来通过取消或跳转的方式守卫导航。
包括三种:
1、全局前置/钩子:beforeEach、beforeResolve、afterEach
2、路由独享的守卫:beforeEnter
3、组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

全局的守卫:
路由前置守卫 beforeEach 在路由跳转前触发,以取消或更改路由的跳转;比如说用于登录验证;
路由解析守卫 beforeResolve 在所有组件内守卫和异步路由组件被解析之后调用;
后置守卫 afterEach 是在路由完成跳转后触发,比如修改页面标题;

const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
});
router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})
router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath)
})

beforeEnter 路由独享守卫,在独享的路由进入之前调用,仅改变参数和hash的时候不会触发,只有从一个路由切换到这个独享路由的时候才触发

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

组件内守卫:
beforeRouteEnter:在渲染该组件的对应路由被验证前调用,不能获取组件实例 this !因为当守卫执行时,组件实例还没被创建!
beforeRouteUpdate:在当前路由改变,但是该组件被复用时调用,举例来说,对于一个带有动态参数的路径 /users/:id,在 /users/1/users/2 之间跳转的时候,由于会渲染同样的 UserDetails 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 this
beforeRouteLeave:在导航离开渲染该组件的对应路由时调用,与 beforeRouteUpdate 一样,它可以访问组件实例 this

const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    
  },
  beforeRouteUpdate(to, from) {
    
  },
  beforeRouteLeave(to, from) {
   
  },
}

2、讲一下完整的导航守卫流程?

1、导航被触发。
2、在失活的组件里调用离开守卫beforeRouteLeave(to,from,next)。
3、调用全局的beforeEach( (to,from,next) =>{} )守卫。
4、在重用的组件里调用 beforeRouteUpdate(to,from,next) 守卫。
5、在路由配置里调用beforeEnter(to,from,next)路由独享的守卫。
6、解析异步路由组件。
7、在被激活的组件里调用beforeRouteEnter(to,from,next)。
8、在所有组件内守卫和异步路由组件被解析之后调用全局的beforeResolve( (to,from,next) =>{} )解析守卫。
9、导航被确认。
10、调用全局的afterEach( (to,from) =>{} )钩子。
11、触发 DOM 更新。

3、query和params的区别是什么

1、query用path编写传参地址,而params用name编写传参地址;
2、query传的参数会显示在url地址栏中,而params传参不会显示在地址栏中。
3、query刷新页面时参数不会消失,而params刷新页面时参数会消失;

4、router和route的区别

router 是通过Vue.use(VueRouter)构造函数得到全局路由的实例,他包含了所有的路由包含了许多关键的对象和属性。
它身上有beforeEach、beforeResolve、afterEach、push、replace、go、back等方法;

const router = new VueRouter({
  routes: [
    // 下面的对象就是路由记录
    {
      path: '/foo',
      component: Foo,
      children: [
        // 这也是个路由记录
        { path: 'bar', component: Bar }
      ]
    }
  ]
})

而route表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息,他的属性有path、query、hash、params、meta

5、前端路由和后端路由的区别

1、后端路由通常对应的是服务端渲染,也就是用户输入一个url,浏览器发起请求服务器返回一个已经拼接好数据和模版的html返回给前端,浏览器直接渲染。使用后端路由有利于SEO,并且安全性更高。但是后端路由的问题是每次切换页面都会发起请求重新加载整个页面,可能导致网页的白屏。
2、前端路由就是将url和组件的映射关系交给前端来控制,通常前端切换url 更换页面不需要发起请求不会导致网页的刷新。
前端路由又有hash模式和history模式两种,hash模式是通过修改url中#后的片段使用onhashchange事件监听来动态的匹配组件,改变#后的内容不会发起请求;而history模式主要是利用h5的history API ,使用history.pushState()可以向浏览器地址栏push一个URL,并且会发送请求;并且手动根据当前url信息匹配路由。使用前端路由减少了网络传输减轻了服务器的压力,但是数据动态的获取不利于SEO。
前后端分离的模式下,通常会使用前端路由由前端组织页面的映射关系,后端只需要专注于api的提供和数据库的维护。

6、hash和history模式的区别

由于vue是单页面应用,在项目构建过程中只有一个html文件。切换页面的时候既要让url发生变化又不能触发html的重新加载,因此不能使用普通超链接的方式跳转。
通过路由系统将项目的组件和可以访问url路径进行绑定,提供了两种页面跳转和加载模式:
1、hash模式
灵活运用了 html的瞄点功能,改变 # 后的路径本质上是更换了当前页面的瞄点,不会导致浏览器向服务器发出请求,所以不会刷新页面。并且浏览器的 window.onhashchange()事件可以监听到#后url的变化,从而查找对应的路由规则。
优点是兼容性强,可以兼容一众老式浏览器
缺点是,页面URL中一直挂着一个难看的#
2、history模式
原理是利用H5的 history中新增的两个API :pushState()和 replaceState() 来重写url。history的back、forward、go方法时触发时可以通过onpopstate监听URL变化,但pushState()和 replaceState()不会触发 popstate,因此我们需要手动的去根据当路由信息匹配组件;
不过一旦刷新网页如果当前的url服务器未配置路径转发规则会出现404
参考文章

  • 22
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值