前端必问面试题总结

关于Vue常见的面试题(大概率出现)


1、Vue双向数据绑定。(重中之重)

答:vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤:
第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

关于VUE双向数据绑定,其核心是Object.defineProperty()方法

前面我们说到了Object.defineProperty()这个方法,那么在这里我就简单的介绍一下Object.defineProperty()这个方法。

  • Object.defineProperty()可以用来修改对象的属性,也可以在对象上新创建一个属性;
  • Object.defineProperty(obj, prop, descriptor)这个语法总共有三个参数:
    1. obj:要定义其上属性的对象
    2. prop:要定义或者要修改的属性
    3. descriptor:具体的改变方法
  • 简单地说,就是用这个方法来定义一个值。当调用时我们使用了它里面的get方法,当我们给这个属性赋值时,又用到了它里面的set方法;

在这里插入图片描述

2、vue虚拟dom,diff算法

  1. 什么是虚拟dom?

所谓的Virtual dom,也就是我们常说的虚拟节点,它是通过JS的Object对象模拟DOM中的节点,然后再通过特定的render方法将其渲染成真实的DOM的节点。

  1. 为什么要使用虚拟dom?

虚拟DOM的出现也是为了减弱频繁的大面积重绘引发的性能问题!

既然知道了什么是虚拟DOM,那为什么在Vue或者React这样的框架中,会考虑采用这样的方式?

其实在我们使用JQuery这样的库的时候,我们不禁会大量操作DOM,那么DOM元素的变化自然会引起页面的回流或者重绘,页面的DOM重排自然会导致页面性能下降,那么如何尽可能的去减少DOM的操作是框架需要考虑的一个重要问题!

  1. 真实DOM和虚拟DOM的区别
    ·虚拟DOM不会进行排版与重绘操作
    ·真实DOM频繁排版与重绘的效率是相当低的
    ·虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗
    ·虚拟DOM有效降低大面积(真实DOM节点)的重绘与排版,因为最终与真实DOM比较差异,可以只渲染局部

看两端简单的代码,也许会更容易理解:

这是一段真实DOM的代码:

扫描二维码关注公众号,回复: 12607306 查看本文章
	<div>
		<p>test</p>
	</div>

这是一段虚拟DOM的伪代码:

var Vnode = {
    
    
    tag: 'div',
    children: [
        {
    
     tag: 'p', text: 'test' }
    ]
};

DOM Diff

说完了虚拟dom,我们了解到,这是一种为了尽可能减少页面频繁操作DOM的方式,那么在虚拟DOM中,通过什么方式才能做到呢,接下来便要说说DOM Diff

DOM Diff指的是通过Diff算法去比较虚拟DOM的变化

diff 算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom。

换句话说就是

diff 的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁

3、组件通信

众所周知,Vue 主要思想之一就是组件式开发。因此,在实际的项目开发中,肯定会以组件的开发模式进行。形如页面和页面之间需要通信一样,Vue 组件和组件之间肯定也需要互通有无、共享状态。接下来,我们就悉数给大家展示所有 Vue 组件之间的通信方式。

组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:
Vue组件通信

  1. 父组件传到子组件

父组件是通过props属性给子组件通信的

父组件parent.vue代码如下:

 <template>
   <div class="parent">
     <h2>{
    
    {
    
     msg }}</h2>
     <son :fa-msg="msg"></son> <!-- 子组件绑定faMsg变量,注意驼峰-->
 </div>
 </template>
 <script>
 import son from './Son' //引入子组件
 export default {
    
    
   name: 'HelloWorld',
   data () {
    
    
     return {
    
    
       msg: '父组件',
     }
   },
   components:{
    
    son},
 }
 </script>

子组件son代码如下:

子组件通过$emit触发父组件上的自定义事件,发送参数

 <template>
   <div class="son">
     <p>{
    
    {
    
     sonMsg }}</p>
     <p>子组件接收到内容:{
    
    {
    
     psMsg }}</p>
     <!--<input type="text" v-model="user" @change="setUser">-->
     <button @click="setUser">传值</button>
   </div>
 </template>
 <script>
 export default {
    
    
   name: "son",
   data(){
    
    
     return {
    
    
       sonMsg:'子组件',
       user:'子传父的内容'
     }
   },
   props:['psMsg'],
   methods:{
    
    
     setUser:function(){
    
    
       this.$emit('transfer',this.user)//触发transfer方法,this.user 为向父组件传递的数据
     }
   }
 }
 </script>

非父子传参 (事件总线)

假设你有两个Vue组件需要通信: A 和 B ,A组件按钮上面绑定了点击事件,发送一则消息,B组件接收。

初始化,全局创建$bus

直接在项目中的main.js中初始化$bus

// main.js
window.$bus=new Vue();

注意,这种方式初始化一个 全局的事件总线

发送事件
$bus.$emit("aMsg",'来自A页面的消息')

<!-- A.vue -->
<template>
   <button @click="sendMsg()">-</button>
</template>

<script> 
//import $bus from "../bus.js";
export default {
    
    
 methods: {
    
    
   sendMsg() {
    
    
     $bus.$emit("aMsg", '来自A页面的消息');
   }
 }
}; 
</script>

接下来,我们需要在 B页面 中接收这则消息

接收事件
$bus.$on("事件名",callback)

<!-- IncrementCount.vue -->
<template>
 <p>{
    
    {
    
    msg}}</p>
</template>

<script> 
//import $bus  from "../bus.js";
export default {
    
    
 data(){
    
    
   return {
    
    
     msg: ''
   }
 },
 mounted() {
    
    
   $bus.$on("aMsg", (msg) => {
    
    
     // A发送来的消息
     this.msg = msg;
   });
 }
};
</script>

4、Vuex

概念 : Vuex是一个专为Vue.js应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
在这里插入图片描述
Vuex由五部分组成:

  1. State:

state为单一状态树,在state中需要定义我们所需要管理的数组、对象、字符串等等,只有在这里定义了,在vue.js的组件中才能获取你定义的这个对象的状态。

  1. Getter:

getter有点类似vue.js的计算属性,当我们需要从store的state中派生出一些状态,那么我们就需要使用getter,getter会接收state作为第一个参数,而且getter的返回值会根据它的依赖被缓存起来,只有getter中的依赖值(state中的某个需要派生状态的值)发生改变的时候才会被重新计算。

  1. Action:

action可以提交mutation,在action中可以执行store.commit,而且action中可以有任何的异步操作。在页面中如果我们要嗲用这个action,则需要执行store.dispatch

  1. Mutation:

更改store中state状态的唯一方法就是提交mutation,就很类似事件。每个mutation都有一个字符串类型的事件类型和一个回调函数,我们需要改变state的值就要在回调函数中改变。我们要执行这个回调函数,那么我们需要执行一个相应的调用方法:store.commit

  1. Module:

module其实只是解决了当state中很复杂臃肿的时候,module可以将store分割成模块,每个模块中拥有自己的state、mutation、action和getter。

Vuex的高级语法:

  • 数据持久化
  • 模块化管理数据 (modules)
  • 辅助函数(语法糖)

数据持久化:

问题:存储在vuex中的状态,刷新页面,会丢失。 为了解决刷新页面数据丢失,才有了数据持久化

使用数据持久化最简单的方法就是使用插件:

vuex-persistedState

安装:

cnpm install vuex-persistedState -S

使用:

import createPersistedState from 'vuex-persistedstate'

const store = new Vuex.Store({
    
    
  state,
  mutations,
  actions,
  getters,
  plugins: [createPersistedState({
    
    
    storage: sessionStorage,
    key: "token"
  })]//会自动保存创建的状态。刷新还在
})

参数:

storage:存储方式。(sessionStorage,localStarage) key:定义本地存储中的key

模块化管理数据 (modules):

  1. 什么时候需要用到模块管理vuex数据

当项目庞大,数据信息量特别大的时候,我们可以考虑分模块形式管理数据,比如user模块管理用户信息数据,cart模块管理购物车数据,shop模块管理商品信息数据。

import vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex);

const state= ()=>{
    
     token:''}
const actions = {
    
    
   set_token({
    
    commit},val){
    
    
     commit("to_token",val)
  }
}
const mutations = {
    
    
   to_token(state,val){
    
    
    state.token=val;
  }
}
const getters = {
    
    }


//user模块
let user = {
    
    
  namespaced: true, //一定要开始命名空间。
  state: {
    
     userid: 1234 },
  actions: {
    
    
  },
  mutations: {
    
    
    SET_USERID(state, val) {
    
    
      state.userid = val;
    }
  },
  getters: {
    
    

  }
}

//购物车数据的模块
let cart = {
    
    
  namespaced: true,
  state: {
    
     userid: 567 },
  actions: {
    
    

  },
  mutations: {
    
    
  },
  getters: {
    
    

  }
}


const store = new Vuex.Store({
    
    
  state,
  mutations,
  actions,
  getters,
  modules: {
    
    
    user,
    cart
  },
  plugins: [createPersistedState({
    
    
    storage: sessionStorage,
    key: "token"
  })]//会自动保存创建的状态。刷新还在
})

export default store


home.vue如何使用:

获取user模块的`userid`
this.$store.state.user.userid
this.$store.commit("SET_USERID",12345)


辅助函数(语法糖)

  1. 有那几个辅助函数(4大金刚)
    mapState,mapActions,mapMutations,mapGetters

  2. 辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作`的目的

  3. 如何使用
    home.vue

<template>
 <div id="">
   {
    
    {
    
     token }}
   {
    
    {
    
     token - x }}
 </div>
</template>

<script>
import {
    
     mapActions, mapGetters, mapMutations, mapState } from 'vuex'

import {
    
    createNamespacedHelpers} from 'vuex'

const {
    
    mapState:mapStateUser,mapActions:mapActionUser,mapMutations:mapMutaionuser} = createNamespacedHelpers('user')

const {
    
    mapState:mapStateCart,mapActions:mapActionCart,mapMutations:mapMutaionCart} = createNamespacedHelpers('cart')



export default {
    
    
 name: '',
 data() {
    
    
   return {
    
    }
 },
 computed: {
    
    
   ...mapState({
    
    
     token: 'token'
   }),
   ...mapGetters(['token-x']),
   ...mapSateUser(['userid']),
   ...mapStateCart({
    
    cartid:'userid'})
 },
 //生命周期 - 创建完成(访问当前this实例)
 created() {
    
    
   this.setToken('123456')
 },
 //生命周期 - 挂载完成(访问DOM元素)
 mounted() {
    
    },
 methods: {
    
    
   ...mapActions({
    
    
     setToken: 'setToken'
   }),
   ...mapMutations(['SET_TOKEN']),
   ...mapMutaionUser({
    
    
   setId:"setToken"
  })
 }
}
</script>


5、 vue-router(路由原理,路由守卫,路由传参)

路由实现原理:

概念:
通过改变URL,在不重新请求页面的情况下,更新页面视图。
实现方式:
vue-router通过hashHistory interface两种方式实现前端路由,更新视图但不重新请求页面”是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有两种方式:
1.hash ---- 利用URL中的hash(’#’);
2.利用History interface在HTML5中的新增方法

6.vue生命周期

简单粗暴:
答: 总共分为8个阶段。创建前/后挂载前/后更新前/后销毁前/后
创建前/后: beforeCreated创建前,vue实例的挂载元素$el和数据对象data都是undefined,还未初始化。在created阶段/创建后,vue实例的数据对象data有了,$el还没有。
挂载前/后:beforeMount挂载前,vue实例的eldata都初始化了,但是还没有挂载html 到页面上。在Mounted阶段/挂载后,模板中的 HTML 渲染到 HTML 页面中,此时一般可以做一些 ajax 操作,mounted 只会执行一次。
更新前/后: 当data变化时,会触发beforeUpdateupdated方法
销毁前/后: 在destroy阶段/销毁前,对data的改变不会再触发周期函数,说明此时vue实列已经解除了事件监听和dom绑定,但是dom结构依然存在。destroyed阶段,组件销毁

在这里插入图片描述

7. 自定义指令,自定义过滤器

Vue 自定义指令如何使用

  1. 自定义指令分为全局自定义指令和局部自定义指令:
  2. 全局自定义指令:
    使用 Vue.directive()来全局注册指令
  3. 局部自定义指令:
    也可以注册局部指令,组件或 Vue 构造函数中接受一个 directives 的选项
    钩子函数
    自定义指令定义函数提供了几个钩子函数:
  4. bind
    只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作
  5. inserted
    被绑定元素插入父节点时调用( 父节点存在即可调用, 不必存在于
    document 中)
  6. update
    所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode 更新之前。指令的值可能发生了改变也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新
  7. componentUpdated
    所在组件的 VNode 及其孩子的 VNode 全部更新时调用
  8. unbind
    只调用一次,指令与元素解绑时调用

参数
钩子函数中有两个参数:

1.el : 指令所绑定的元素,可以用来直接操作 DOM
1.binding : 一个对象,包含以下属性:
name: 指令名,不包括 v- 前缀
value: 指令的绑定值
oldValue: 指令绑定的前一个值(update 和 componentUpdated 钩子中可用。无论值是否改变都可用)
expression: 绑定值的字符串形式
arg: 传给指令的参数

使用场景: 当我们需要对普通的DOM元素进行底层操作时就可以用到自定义指令
全局自定义指令:

//main.js中
// 全局自定义指令
Vue.directive(
  'upper-text',{
    
    bind: (el, binding)=> {
    
    
    	console.log(el, binding)
    	el.innerHTML = binding.value.toUpperCase() //转换大写
  	}
  })

局部自定义指令

//组件中
<template>
  <div class="wrapper">
    <br />
    <span>原数据</span>
    <p>{
    
    {
    
    message}}</p>
    <br />
    <span>全局</span>
    <p v-upper-text="message" v-color="'blue'"></p>
    <br />
    <span>局部</span>
    <p v-lower-text="message"></p>
  </div>
</template>
<script>
export default {
    
    
  data() {
    
    
    return {
    
    
      message: 'You can start the Internet!'
    }
  },
  computed: {
    
    },
  //局部自定义指令
  directives: {
    
    
    'lower-text'(el, binding) {
    
    
      //el:指令属性所在的标签对象
      //binding:包含指令相关信息数据的对象
      console.log(el, binding)
      el.textContent = binding.value.toLowerCase() //转换小写
    },
    color:{
    
    //改变颜色
        bind(el,binding){
    
    
            el.style.color = binding.value
        }
    }
  },
}
</script>


8. 自定义组件

简介
组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树
功能
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子

组件注册

组件名
组件名应该始终是多个单词的,根组件 App 除外

这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的

单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)

混用文件命名方式有的时候会导致大小写不敏感的文件系统的问题,这也是横线连接命名同样完全可取的原因

  • 使用 kebab-case

当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用kebab-case,例如<my-component-name>

Vue.component('my-component-name', {
    
     /* ... */ })

  • 使用 PascalCase

当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说
<my-component-name><MyComponentName>都是可接受的。注意,尽管如此,直接在 DOM
(即非字符串的模板) 中使用时只有 kebab-case 是有效的

Vue.component('MyComponentName', {
    
     /* ... */ })

全局注册

以上方法都属于全局注册, 也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中, 比如

  • HTML
<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

  • JS
Vue.component('component-a', {
    
     /* ... */ })
Vue.component('component-b', {
    
     /* ... */ })
Vue.component('component-c', {
    
     /* ... */ })

new Vue({
    
     el: '#app' })

在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用

局部注册

如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的 components 属性实现局部注册

9. 常用的指令,修饰符。

在vue中提供了一些对于页面 + 数据的更为方便的输出,这些操作就叫做指令,指令中封装了一些DOM行为, 结合属性作为一个暗号, 暗号有对应的值,根据不同的值,框架会进行相关DOM操作的绑定
vue中的指令有很多,我们平时做项目常用的有:
v-if:是动态的9向DOM树种添加或者删除元素;
v-else:是搭配v-if使用的,它必须紧跟在v-if或者v-else-if后面,否则不起作用
v-show : 是通过标签的CSS样式display的值是不是none,来控制显示隐藏

10. vue2和vue3的区别

11. keep-alive

概念:
是Vue的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现的父组件链中。
作用:
在组件切换的过程中,把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载事件以及性能消耗,提高用户体验性
原理:
created钩子函数调用时将需要缓存的 VNode节点保存在this.cache中/在 render(页面渲染) 时,如果VNode的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode实例进行渲染
VNode: 虚拟DOM,其实就是一个JS对象
参数:

  • include - 字符串或者正则表达式。只有匹配的组件会被缓存。
  • exclude - 字符串或正则表达式。然后匹配的组件都不会缓存。
  • max - 数字。最多可以缓存多少组件实例

对生命周期函数变化:
被包含在keep-alive中的组件,会多出来两个生命周期的钩子函数,activateddeactiveated
1.activated
keep-alive组件激活时调用
2.deactivated
keep-alive组件离开时调用

//正常的生命周期:beforeRouterEnter --> created -->mounted --> updated --> destroyed


//使用keepAlive后生命周期:
//首次进入缓存页面 :beforeRouterEnter --> created -->mounted --> updated --> destroyed
//再次进入缓存页面 :beforeRouteEnter --> activated --> deactivated
//注:
//1、这里的activated非常有用,因为页面被缓存时,created,mounted等生命周期均失效,你若想进行一些操作,那么可以在activated内完成(下面会举个栗子:列表页回到上次浏览位置)
//2、activated   keep-alive组件激活时调用,该钩子在服务器端渲染期间不被调用。 
//3、deactivated   keep-alive组件停用时调用,该钩子在服务端渲染期间不被调用

可以结合Router中的meta,来缓存部分页面

12. 多环境变量

13. 对axios封装,(url统一管理,axios请求拦截、响应拦截,函数封装)

14. element-ui,vant-ui按需引入

15. sass配置

16. rem、vw/vh设置

17. webpack配置(配置跨域,路径别名,打包分析,cdn引入,去掉console.log,单独打包第三方模块,ie兼容,eslint规范,图片压缩)。

猜你喜欢

转载自blog.csdn.net/Glory_05/article/details/112260732