社招前端经典vue面试题(附答案)

本文详述了Vue.js的面试题目,包括Vuex的状态持久化、Vue.$set的原理、Vue没有shouldComponentUpdate的原因、组件通信方式、nextTick的使用、性能优化策略、mixin的使用场景和原理、Vue路由的hash模式与history模式的区别,以及虚拟DOM的优势与实现原理。文章深入浅出地探讨了Vue.js的关键概念和技术细节。
摘要由CSDN通过智能技术生成

Vuex 页面刷新数据丢失怎么解决

体验

可以从localStorage中获取作为状态初始值:

const store = createStore({
   
  state () {
   
    return {
   
      count: localStorage.getItem('count')
    }
  }
})

业务代码中,提交修改状态同时保存最新值:虽说实现了,但是每次还要手动刷新localStorage不太优雅

store.commit('increment')
localStorage.setItem('count', store.state.count)

回答范例

  1. vuex只是在内存保存状态,刷新之后就会丢失,如果要持久化就要存起来
  2. localStorage就很合适,提交mutation的时候同时存入localStoragestore中把值取出作为state的初始值即可。
  3. 这里有两个问题,不是所有状态都需要持久化;如果需要保存的状态很多,编写的代码就不够优雅,每个提交的地方都要单独做保存处理。这里就可以利用vuex提供的subscribe方法做一个统一的处理。甚至可以封装一个vuex插件以便复用。
  4. 类似的插件有vuex-persistvuex-persistedstate,内部的实现就是通过订阅mutation变化做统一处理,通过插件的选项控制哪些需要持久化

原理

可以看一下vuex-persist (opens new window)内部确实是利用subscribe实现的

Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?

1)Vue为什么要用vm.$set() 解决对象新增属性不能响应的问题

  1. Vue使用了Object.defineProperty实现双向数据绑定
  2. 在初始化实例时对属性执行 getter/setter 转化
  3. 属性必须在data对象上存在才能让Vue将它转换为响应式的(这也就造成了Vue无法检测到对象属性的添加或删除)

所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)

2)接下来我们看看框架本身是如何实现的呢?

Vue 源码位置:vue/src/core/instance/index.js

export function set (target: Array<any> | Object, key: any, val: any): any {
   
  // target 为数组  
  if (Array.isArray(target) && isValidArrayIndex(key)) {
   
    // 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
    target.length = Math.max(target.length, key)
    // 利用数组的splice变异方法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // key 已经存在,直接修改属性值  
  if (key in target && !(key in Object.prototype)) {
   
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // target 本身就不是响应式数据, 直接赋值
  if (!ob) {
   
    target[key] = val
    return val
  }
  // 对属性进行响应式处理
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

我们阅读以上源码可知,vm.$set 的实现原理是:

  1. 如果目标是数组,直接使用数组的 splice 方法触发相应式;
  2. 如果目标是对象,会先判读属性是否存在、对象是否是响应式,
  3. 最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理

defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法

Vue为什么没有类似于React中shouldComponentUpdate的生命周期?

考点: Vue的变化侦测原理

前置知识: 依赖收集、虚拟DOM、响应式系统

根本原因是Vue与React的变化侦测方式有所不同

React是pull的方式侦测变化,当React知道发生变化后,会使用Virtual Dom Diff进行差异检测,但是很多组件实际上是肯定不会发生变化的,这个时候需要用shouldComponentUpdate进行手动操作来减少diff,从而提高程序整体的性能.

Vue是pull+push的方式侦测变化的,在一开始就知道那个组件发生了变化,因此在push的阶段并不需要手动控制diff,而组件内部采用的diff方式实际上是可以引入类似于shouldComponentUpdate相关生命周期的,但是通常合理大小的组件不会有过量的diff,手动优化的价值有限,因此目前Vue并没有考虑引入shouldComponentUpdate这种手动优化的生命周期.

vue 中使用了哪些设计模式

1.工厂模式 - 传入参数即可创建实例

虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode

2.单例模式 - 整个程序有且仅有一个实例

vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉

3.发布-订阅模式 (vue 事件机制)

4.观察者模式 (响应式数据原理)

5.装饰模式: (@装饰器的用法)

6.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略

组件通信

组件通信的方式如下:

(1) props  /   $emit

父组件通过props向子组件传递数据,子组件通过$emit和父组件通信

1. 父组件向子组件传值
  • props只能是父组件向子组件进行传值,props使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。
  • props 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。
  • props属性名规则:若在props中使用驼峰形式,模板中需要使用短横线的形式
// 父组件
<template>
  <div id="father">
    <son :msg="msgData" :fn="myFunction"></son>
  </div>
</template>

<script>
import son from "./son.vue";
export default {
   
  name: father,
  data() {
   
    msgData: "父组件数据";
  },
  methods: {
   
    myFunction() {
   
      console.log("vue");
    },
  },
  components: {
    son },
};
</script>

// 子组件
<template>
  <div id="son">
    <p>{
   {
    msg }}</p>
    <button @click="fn">按钮</button>
  </div>
</template>
<script>
export default {
    name: "son", props: ["msg", "fn"] };
</script>

2. 子组件向父组件传值
  • $emit绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过v-on监听并接收参数。
// 父组件
<template>
  <div class="section">
    <com-article
      :articles="articleList"
      @onEmitIndex="onEmitIndex"
    ></com-article>
    <p>{
   {
    currentIndex }}</p>
  </div>
</template>

<script>
import comArticle from "./test/article.vue";
export default {
   
  name: "comArticle",
  components: {
    comArticle },
  data() {
   
    return {
    currentIndex: -1, articleList: ["红楼梦", "西游记", "三国演义"] };
  },
  methods: {
   
    onEmitIndex(idx) {
   
      this.currentIndex = idx;
    },
  },
};
</script>

//子组件
<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>

(2)eventBus事件总线($emit / $on

eventBus事件总线适用于父子组件非父子组件等之间的通信,使用步骤如下: (1)创建事件中心管理组件之间的通信

// event-bus.js

import Vue from 'vue'
export const EventBus = new Vue()

(2)发送事件 假设有两个兄弟组件firstComsecondCom

<template>
  <div>
    <first-com></first-com>
    <second-com></second-com>
  </div>
</template>

<script>
import firstCom from "./firstCom.vue";
import secondCom from "./secondCom.vue";
export default {
    components: {
    firstCom, secondCom } };
</script>

firstCom组件中发送事件:

<template>
  <div>
    <button @click="add">加法</button>
  </div>
</template>

<script>
import {
    EventBus } from "./event-bus.js"; // 引入事件中心

export default {
   
  data() {
   
    return {
    num: 0 };
  },
  methods: {
   
    add() {
   
      EventBus.$emit("addition", {
    num: this.num++ });
    },
  },
};
</script>

(3)接收事件secondCom组件中发送事件:

<template>
  <div>求和: {
   {
    count }}</div>
</template>

<script>
import {
    EventBus } from "./event-bus.js";
export default {
   
  data() {
   
    return {
    count: 0 };
  },
  mounted() {
   
    EventBus.$on("addition", (param) => {
   
      this.count = this.count + param.num;
    });
  },
};
</script>

在上述代码中,这就相当于将num值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不用组件通过它来通信。

虽然看起来比较简单,但是这种方法也有不变之处,如果项目过大,使用这种方式进行通信,后期维护起来会很困难。

(3)依赖注入(provide / inject)

这种方式就是Vue中的依赖注入,该方法用于父子组件之间的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。

provide / inject是Vue提供的两个钩子,和datamethods是同级的。并且provide的书写形式和data一样。

  • provide 钩子用来发送数据或方法
  • inject钩子用来接收数据或方法

在父组件中:

provide() {
    
    return {
        
        num: this.num  
    };
}

在子组件中:

inject: ['num']

还可以这样写,这样写就可以访问父组件中的所有属性:

provide() {
   
 return {
   
    app: this
  };
}
data() {
   
 return {
   
    num: 1
  };
}

inject: ['app']
console.log(this.app.num)

注意: 依赖注入所提供的属性是非响应式的。

(3)ref / $refs

这种方式也是实现父子组件之间的通信。

ref: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。

在子组件中:

export default {
   
  data () {
   
    return {
   
      name: 'JavaScript'
    }
  },
  methods: {
   
    sayHello () {
   
      console.log('hello')
    }
  }
}

在父组件中:

<template>
  <child ref="child"></component-a>
</template>
<script>
import child from "./child.vue";
export default {
   
  components: {
    child },
  mounted() {
   
    console.log(this.$refs.child.name); // JavaScript
    this.$refs.child.sayHello(); // hello
  },
};
</script>

(4)$parent / $children

  • 使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)
  • 使用$children可以让组件访问子组件的实例,但是,$children并不能保证顺序,并且访问的数据也不是响应式的。

在子组件中:

<template>
  <div>
    <span>{
   {
    message }}</span>
    <p>获取父组件的值为: {
   {
    parentVal }}</p>
  </div>
</template>

<script>
export default {
   
  data() {
   
    return {
    message: "Vue" };
  },
  computed: {
   
    parentVal
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值