【直接收藏】前端 VUE 高阶面试题(一)

1.说说vue动态权限绑定渲染列表(权限列表渲染)

  1. 首先请求服务器,获取当前用户的权限数据,比如请求 this.$http.get("rights/list");

  2. 获取到权限数据之后,在列表中使用v-if v-if-else的组合来展示不同的内容

<template>
 <div>
   <!-- 面包屑导航区 -->
   <el-breadcrumb separator-class="el-icon-arrow-right">
     <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
     <el-breadcrumb-item>权限管理</el-breadcrumb-item>
     <el-breadcrumb-item>权限列表</el-breadcrumb-item>
   </el-breadcrumb>
   <!-- 卡片视图 -->
   <el-card>
     <el-table :data="rightsList" border stripe>
       <el-table-column type="index" label="#"></el-table-column>
       <el-table-column label="权限名称" prop="authName"></el-table-column>
       <el-table-column label="路径" prop="path"></el-table-column>
       <el-table-column label="权限等级" prop="level">
         <template slot-scope="scope">
           <el-tag v-if="scope.row.level === '0'">一级</el-tag>
           <el-tag type="success" v-else-if="scope.row.level === '1'">二级</el-tag>
           <el-tag type="danger" v-else>三级</el-tag>
         </template>
       </el-table-column>
     </el-table>
   </el-card>
 </div>
</template>

<script>
export default {
 data() {
   return {
     // 权限列表
     rightsList: []
  };
},
 created() {
   this.getRightsList();
},
 methods: {
   async getRightsList() {
     //获取权限列表数据
     const { data: res } = await this.$http.get("rights/list");
     if (res.meta.status !== 200) {
       return this.$message.error("获取权限列表失败!");
    }
     this.rightsList = res.data;
  }
}
};
</script>

<style lang='less' scoped>
</style>

2.Vue用的哪种设计模式

属于发布订阅模式,在vue中使用observer和definereactive两个方法的结合对数据进行递归劫持,然后通过watch这个类来对属性进行订阅,Dep类用于解耦合,当数据变更的时候先触发数据的set方法,然后调用Dep.notiify通知视图更新

3.说说vue操作真实dom性能瓶颈

vue性能瓶颈的几种情况

  1. 一次渲染大量的数据的时候,存在大量数据并且都是复杂类型的时候,会导致vue对数据的劫持时间和渲染时间变长, js 连续执行时间过长,会导致页面长时间无法交互,而且渲染时间太慢,用户一次交互反馈的时间过长。

    优化方案:可以使用requestAnimation这个方法,将数据进行分割,分批次渲染,减少了 js 的连续运行时间,并且加快了渲染时间,利用加长总运行时间换取了渲染时间,用户既能快速得到反馈,而且不会因为过长时间的 js 运行而无法与页面交互。

  2. 当页面中存在大量数据,只是修改了一小部分导致页面也会导致页面卡顿,因为vue的更新以组件为粒度进行更新的,只要修改了当前组件中所使用的数据,组件就会整个去进行更新,造成大量的时间浪费

    优化方案:将不同的模块划分成不同的组件,这样有效降低虚拟dom的diff运算时间过长的问题,比如将大量数据的模块单独放一个组件,其它放一个组件,由于vue是以组件为粒度更新,修改其它组件的情况下不会导致table的重新diff,提升页面响应速度高达几百倍

  3. 动态插槽作用域或者静态插槽的更新

    使用插槽作用域来替换这两种操作方式,一样能提升性能,因为使用插槽作用域之后,插槽内容会被封装到一个函数中,被子组件渲染,而不是在父组件

4.Vue中如何获取dom、操作dom、更新dom

如何获取dom?在Vue中提供了一种特别的方式来获取dom,即给dom加上个ref属性,那么就可以通过this.$refs.名字来获取到该dom元素。

如何操作dom、更新dom?通过refs.名字就可以拿到对应的真实dom,然后就可以用原生JS进行操作和更新。当然vue框架本身就是不需要dom操作的,通过修改相应的数据并再配合指令、模板语法就可以轻松的操作和更新dom。

5.Vue 的双向数据绑定原理是什么

在Vue2.x中,双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变。核心:关于VUE双向数据绑定,其核心是 Object.defineProperty()方法。

Vue3.x则是用ES6的语法Proxy对象来实现的。

Object.defineProperty()的缺点:

  1. 只能监听对象(Object),不能监听数组的变化,无法触发push, pop, shift, unshift,splice, sort, reverse。

  2. 必须遍历对象的每个属性

  3. 只能劫持当前对象属性,如果想深度劫持,必须深层遍历嵌套的对象。

 Proxy的优点:

  1. Proxy 可以直接监听对象而非属性。

  2. Proxy 可以直接监听数组的变化。

  3. Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的。

  4. Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改。

  5. Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利。

 let arr = [];
 let proxy = new Proxy(arr, {
   get: function(obj, prop){
     return obj[prop];
  },
   set: function(obj, prop, value){
     obj[prop] = value;   //可以被监听到变化
     return true;
  }
});
 setTimeout(()=>{
   proxy.push(1);
}, 2000)

6.mvvm框架是什么

MVVM是Model-View-ViewModel的简写。它本质上就是MVC(Model-View-Controller)的改进版。在开发过程中,由于需求的变更或添加,项目的复杂度越来越高,代码量越来越大,此时我们会发现MVC维护起来有些吃力,尤其Controller控制层非常的厚重,非常的庞大,难以维护。

所以有人想到把Controller的数据和逻辑处理部分从中抽离出来,用一个专门的对象去管理,这个对象就是ViewModel。ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。

由于实现了双向绑定,ViewModel 的内容会实时展现在 View 层,这是激动人心的,因为前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新,真正实现数据驱动开发。

7.谈谈Vue的token存储

在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下:

1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码

2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token

3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面

4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面

5、每次调后端接口,都要在请求头中加token

6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401

7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面

8.知道nextTick的作用吗,谈谈对它的理解,是什么,怎么用

当你设置 vm.message = 'new message',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。例如:

<div id="example">{
   
   {message}}</div>
var vm = new Vue({
 el: '#example',
 data: {
   message: 'old message'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent  // 'old message'
Vue.nextTick(function () {
 vm.$el.textContent  // 'new message'
})

9.nextTick和setTimeout区别

 首先Vue 在更新 DOM 时是异步执行的,也就是说数据变了,DOM不会立即改变,那么我们是如何知道DOM什么时候会改变呢?也就是说如何知道异步后的触发时机呢?

 可以通过nextTick方法,这个方法在源码内,先监听是否具备Promise.then,利用promise来监听,如果当前环境不支持promise,那么就降级采用MutationObserver,如果MutationObserver不支持的话,那么就降级采用setImmediate,如果setImmediate不支持的话,那么就使用setTimeout(fn, 0)。

 所以说nextTick和setTimeout区别总结就是:nextTick会先尝试使用promise、MutationObserver、setImmediate这些技术去监听,如果都不支持才会采用setTimeout

10.vue中为什么用虚拟dom而不操作真实dom

起初我们在使用JS/JQuery时,不可避免的会大量操作DOM,而DOM的变化又会引发回流或重绘,从而降低页面渲染性能。那么怎样来减少对DOM的操作呢?此时虚拟DOM应用而生,所以虚拟DOM出现的主要目的就是为了减少频繁操作DOM而引起回流重绘所引发的性能问题的!

虚拟DOM(Virtual Dom),起始本质上就是一个JS对象,当数据发生变化时,我们不直接操作真实DOM,因为很昂贵,我们去操作这个JS对象,就不会触发大量回流重绘操作,再加上diff算法,可以找到两次虚拟DOM之间改变的部分,从而最小量的去一次性更新真实DOM,而不是频繁操作DOM,性能得到了大大的提升。

图片

虚拟DOM还有一个好处,可以渲染到 DOM 以外的平台,实现 SSR、同构渲染这些高级特性,Weex 等框架应用的就是这一特性。

11.Vue如何进行组件传值

父向子组件传值,可以利用prop方式。

子向父组件传值,可以利用自定义事件$emit方式。

图片

多层级组件传值,可以使用provide/inject

图片

无关系的组件传值,利用vuex状态管理

图片

12.说说vue里面的父子通信

父 -> 子: 通过 Prop 向子组件传递数据,子组件通过props属性来接收。

<blog-post title="My journey with Vue"></blog-post>
Vue.component('blog-post', {
 props: ['title'],
 template: '<h3>{
  
  { title }}</h3>'  //获取父组件的值
})

子 -> 父: 父组件自定义事件,子组件利用$emit来完成。

<!--拿到子组件传递的数据$event即0.1-->
<blog-post v-on:enlarge-text="postFontSize += $event"></blog-post>
Vue.component('blog-post', {
props: ['title'],
template: '<h3 v-on:click="$emit('enlarge-text', 0.1)">{
  
  { title }}</h3>'
})

图片

图片

13.谈谈如何实现vue组件通信和传值方式 (两个问题为同一个答案问法不一样)

这类问题 首先分类 表明了解的比较多   具体就没说完 或者漏了 面试官也不会计较很多
组件通信的四大类   父与子   子与父   子与子     跨层级  
在细说各种方式 加入自己的理解
1、props和$emit
父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件

2、$attrs和$listeners

3、中央事件总线 bus

上面两种方式处理的都是父子组件之间的数据传递,而如果两个组件不是父子关系呢?这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus.$on监听触发的事件。

4、provide和inject

父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。

5、v-model

父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值

6、$parent和$children

7、boradcast和dispatch

8、vuex处理组件之间的数据交互 如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。

14.说说vue中Key值的作用

关于这个可以的key的作用 首先表明 key 不是一定要有的 不写可以代码也可以跑 但是建议加上
然后指出可以用的地方 key在v-for循环可以用用   在表单元素中也可以用key 减少缓存
一般说key 只要说配合v-for的使用

key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速
diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异能讲清楚diff算法就继续讲
diff程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.

准确: 如果不加key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug. 快速: key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度O(n),Map的时间复杂度仅仅为O(1)
讲完以后 还要补充一点自己的看法
建议使用主键比如id  

15.说说vue中的虚拟dom和diff算法

Virtual DOM 其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实的DOM上

下面就是一个真实DOM映射到虚拟DOM的例子:
<ul id='list'>
        <li class='item'>Item 1</li>
        <li class='item'>Item 2</li>
        <li class='item'>Item 3</li>
      </ul>
       
var element = {
      tagName: 'ul', // 节点标签名
      props: { // DOM的属性,用一个对象存储键值对
          id: 'list'
      },
      children: [ // 该节点的子节点
        {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
        {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
        {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
      ]
  }
   
在补充点虚拟DOM的好处
具备跨平台的优势
由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、Weex、Node 等。

操作原生DOM慢,js运行效率高。我们可以将DOM对比操作放在JS层,提高效率。
因为DOM操作的执行速度远不如Javascript的运算速度快,因此,把大量的DOM操作搬运到Javascript中,运用patching算法来计算出真正需要更新的节点,最大限度地减少DOM操作,从而显著提高性能。

Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)

提升渲染性能
Virtual DOM的优势不在于单次的操作,而是在大量、频繁的数据更新下,能够对视图进行合理、高效的更新。

diff算法
vdom因为是纯粹的JS对象,所以操作它会很高效,但是vdom的变更最终会转换成DOM操作,为了实现高效的DOM操作,一套高效的虚拟DOM diff算法显得很有必要

diff算法包括一下几个步骤:

用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文
档当中

当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较(diff),记录两棵树差异
把2所记录的差异应用到步骤1所构建的真正的DOM树上(patch),视图就更新了
diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法

实现虚拟DOM的过程

16.vue3.0有了解过吗,你觉得vue3.0好吗,好在哪

这种问题 是开放的   多说就是都是对的    可以讲差异  也可以讲新增 的知识点  

比如说 常用的api特别好用

  1. ref、toRefs、toRef、isRef

  • ref 用于定义响应式变量、快捷DOM访问。

  • 基本语法:const a = ref(1) // {value:1}

  • 基础使用: 一般用于定义 String、Number、Boolean 这种基于数据类型,外在表现统一使用 .value 访问。

  • 补充:ref 还可以访问 DOM对象或者组件实例对象,可做DOM操作。

  • toRef、toRefs 用于把一个object的变量,变成响应式的变量。

  • 基本语法:const msg = toRef(obj, key)// 把obj[key]变成响应式的

  • 基本语法:const { msg } = toRefs(obj) // 把整个obj都变成响应式的

  • unref 返回一个变量的值

  • 基本语法:const x = unref(x) // 如果x是ref变量则返回x.value,如果x不是ref变量则直接返回x。

  • isRef 用于判断一个变量是不是ref响应式变量

  • 基本语法:const bol = isRef(x)

function useUpdBoxStyle() {
 const el = ref(null)
 const updateStyle = color => {
   el.value.style.color = color
}
 return [el, updateStyle]
}
  1. shallowRef、triggerRef

  • shallowRef 用于性能优化,只对对象的第一层进行proxy

  • 基本语法:const obj = shallowRef({a:1,b:{c:{d:{e:2}}}})

  • triggerRef 用于手动触发那些shallowRef的变量进行更新视图

  • 基本语法:triggerRef(obj) // 当obj.value.b.c.d发生变化,triggerRef(obj) 强制更新视图。

  • customRef 自定义ref,把ref变量拆成get/set的写法

  • 基本语法:customRef((track, trigger) =>({get,set})

function useObj() {
 const obj = { a: 1, b: { c: { d: 2 }}}
 const obj1 = ref(obj)
 const obj2 = shallowRef(obj)
 // console.log('obj1', obj1)
 // console.log('obj2', obj2)
 const changeObj = (obj, newD) => {
   // obj1.value.b.c.d = 100
   obj.value.b.c.d = newD
   triggerRef(obj)
}
 return [[obj1, obj2], changeObj]
}
  1. reactive、readonly

  • reactive 用于定义响应式变量(引用数据类型)

  • 基本语法:const arr = reactive([]) // {value: []}

  • ref和reactive是什么关系呢?ref背后是使用reactive来实现的。

  • shallowReactive 用于性能优化,只对对象的第一层进行proxy

  • 基本语法:const c = shallowReactive({a:{b:{c:100}}}) // 只对这个对象的第一层进行proxy

  • readonly 把响应式变量变成“只读的”,如果修改就报警告。

  • 基本语法:const user = readonly({name:1,age:2})

  • isReadonly 用于判断一个变量是否是readonly的,返回布尔值

  • 基本语法:const bol = isReadonly(x)

  • isProxy 用于判断一个变量是否是响应式的,返回布尔值

  • 基本语法:const bol = isProxy(x)

  • isReactive 用于判断一个变量是否是reactive响应式变量,返回布尔值

  • 基本语法:const bol = isReactive(x)

function useUser() {
 const user = readonly(reactive({name:'list',age:30}))
 console.log('user', user)
 // setTimeout(()=>user.age=40, 2000)
 const x = 1
 const y = readonly({a:1,b:{c:3}})
 console.log('是否被proxy拦截过', isProxy(user), isProxy(x), isProxy(y.b))
 return user
}
  1. toRaw、markRaw

  • toRaw 用于返回一个响应式变量的原始值

  • 基本语法:const a3 = toRow(reactive(a1))// a1===a3是true

  • markRaw 用于把一个普通变量标记成“不可proxy”的

  • 基本语法:const b2 = markRaw(b1)// b2是无法被reactive的

function useRaw() {
 const a1 = { title: 100 }
 const a2 = reactive(a1)
 const a3 = toRaw(a2)
 console.log('toRow(a2)===a1', a3===a1)
 console.log('a2===a1', a2===a1)
 return [a1,a2,a3]
}
  1. computed、watch、watchEffect

  • computed 用于对响应式变量进行二次计算,当它依赖的响应式变量发生变化时会重新计算

  • 基本语法:const c = computed(()=>c1.value*c2.value)// 只读

  • 基本语法:const c = computed({get:()=>c1.value*c2.value,set:(newVal)=>c1.value=newVal}) // 可写可读

  • watch 用于监听响应式变量的变化,组件初始化它不执行

  • 基本语法:const stop = watch(x, (new,old)=>{}) // 调用stop()可以停止监听

  • 基本语法:const stop = watch([x,y], ([newX,newY],[oldX,oldY])=>{})

  • watchEffect 用于监听响应式变量的变化,组件初始化会执行

  • 基本语法:const stop = watchEffect(()=>ajax({cate,page,size}))

export default function useWatchComputed() {
const c1 = ref(10)
const c2 = ref(20)
const c3 = computed(()=>c1.value*c2.value) // 只读
// 可读也可写
const c4 = computed({
  get: ()=>c1.value*c2.value,
  set: (newVal)=>{
    c1.value = parseInt(newVal) / c2.value
  }
})
const stop1 = watch(c4, (newC4, oldC4)=>console.log('c4变了', newC4, oldC4))
const stop2 = watch([c1,c2], ([newC1,newC2],[oldC1,oldC2])=>{
  console.log('[c1,c2] 新值:', [newC1, newC2])
  console.log('[c1,c2] 旧值:', [oldC1, oldC2])
})
const stop3 = watchEffect(()=>{console.log('watch effect', c1.value, c2.value)})
const update = (c,v) => c.value = v
return [[c1,c2,c3,c4],[stop1,stop2,stop3,update]]
}

2:也可以说亮点

  1. 性能比vue2.x快1.2~2倍

  2. 支持tree-shaking,按需编译,体积比vue2.x更小

  3. 支持组合API

  4. 更好的支持TS

  5. 更先进的组

3.更可以说性能

1.diff算法更快

vue2.0是需要全局去比较每个节点的,若发现有节点发生变化后,就去更新该节点

vue3.0是在创建虚拟dom中,会根据DOM的的内容会不会发生内容变化,添加静态标记, 谁有flag!比较谁。

2、静态提升

vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染 vue3中对于不参与更新的元素,会做静态提升,只被创建一次,在渲染时直接复用即可

3、事件侦听缓存

默认情况下,onclick为动态绑定,所以每次都会追踪它的变化,但是因为是同一函数,没有必要追踪变化,直接缓存复用即可

在之前会添加静态标记8 会把点击事件当做动态属性 会进行diff算法比较, 但是在事件监听缓存之后就没有静态标记了,就会进行缓存复用

17.v-model有了解过吗,原理是什么

这种原理性问题  不要直接说不清楚 不了解  

先讲下使用

v-model本质上是一个语法糖,可以看成是value + input 方法的语法糖。可以通过model的prop属性和event事件来进行自定义。

2、v-model是vue的双向绑定的指令,能将页面上控件输入的值同步更新到相关绑定的data属性, 也会在更新data绑定属性时候,更新页面上输入控件的值。

然后再来讲细节

vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情

我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发生变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:

1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

2.实现一个订阅者Watcher,每一个Watcher都绑定一个更新函数,watcher可以收到属性的变化通知并执行相应的函数,从而更新视图。

3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)

3:最后补一下 vue2.0里面用Object.defineProperty  3.0里面用new  Proxy     一个监听每个属性 一个监听整个对象

18.VUE组件如何与iframe通信问题

像这种问题其实问的不是特别详情  面试者可能不懂题目的意思   但是我们要学会揣摩 面试官的问题

如果不知道 你就直说vue的组件通信 在讲iframe的页面获取v

vue组件内嵌一个iframe,现在想要在iframe内获取父组件内信息,采用的是H5新特性PostMessage来解决跨域问题

采用postMessage内涵两个API:

onMessage:消息监听

postMessage:消息发送

代码和例子

<div class="mapbox">
    <iframe name="map" src="http://localhost:8083/setposition.html?add='add'"></iframe>
</div>
clearMap(){
    let map = document.getElementsByName("map")[0].contentWindow
    map.postMessage("clearMap","*")
  }
iframe内:

window.addEventListener('message', function (evt) {
 
  if (evt.data == 'clearMap'){
      clearMap()
  }
  //event.data获取传过来的数据
});

19.用过VUE 的自定义指令吗?自定义指令的方法有哪些

这种问题一样的  先回答经常用的一些指定 比如 v-for  v-if  v-model   v-show等等之类的  指令分为全局和局部的

然后在回答自定义指令

通过directive来自定义指令,自定义指令分为全局指令和局部指令,自定义指令也有几个的钩子函数,常用的有bind和update,当 bind 和 update 时触发相同行为,而不关心其它的钩子时可以简写。一个表达式可以使用多个过滤器。过滤器之间需要用管道符“|”隔开。其执行顺序从左往右。  

20.当修改data时Vue的组件重渲染是异步还是同步

这个问题很有意思  因为平时我们一般问题异步和同步指的是 数据请求 同步和异步问题

这里加上了组件  还有修改data     这里给大家写个例子

<body>
  <div id="app">
      <div id="main">{
   
   {num}}</div>
      <button @click="add">更新</button>
  </div>
</body>
<script>
  new Vue({
      el:"#app",
      data:{
          num:10
      },
      methods:{
          add(){
              this.num++;
              console.log(this.num)//11
              console.log(document.getElementById("main").innerHTML);//10
         
          }
      }
  })
</script>

以此可以说明  

数据更新是同步的  但是视图更新是异步的

解决这个问题需要使用 $nextTick 解决视图异步更新的问题

21. .sync修饰器的作用是

首先看到 .sync  我们需要知道这是个修饰器   类似修饰器还有  .stop  .prevent  之类

其实这个修饰符就是vue封装了 子组件要修改父组件传过来的动态值的语法糖,省去了父组件需要写的方法,但是子组件emit时要加上update

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源

代码解释

// 这里父组件,要给子组件传一个title的值
<template>
  <div>  
      <t-title :title.sync="fatherTitle"></t-title>
  </div>
</template>
<script>
import tTitle from './blocks/list';
export default {
  name: 'test1',
  components: { tTitle },
  data() {
      return {
          fatherTitle: '父组件给的标题'
      };
  },
}
</script>
// 子组件
<template>
  <div>
      <h3>{
   
   { title }}</h3>
      <button @click="changeTitle">改变</button>
  </div>
</template>
<script>
export default {
  props:{
      title: {type: String, default: '默认值11'}
  },
  methods: {
      changeTitle() {
          this.$emit("update:title", "子组件要改自己的标题");
      }
  }
};
</script>

这里关键就是emit里的参数要写成'update'+ ':' +'要修改的props'

以前是用的this.$emit("自定义方法")

22.vue多组件嵌套通信方式

这个问题其实也是属于组件通信   常见的组件通信有 父传子  子传父  子传子  以及跨层级

这个多组件嵌套通信其实就是跨层级的另一种问法

多组件通信

方法一:props  一层 一层的传递

方法二:依赖注入  provide 声明  inject接收

方法三:利用公共的bus = new  Vue()   bus.$on  声明  bus.$emit()   调用

方法四:使用vuex 全局的状态管理

23.vue如何让css只在当前组件生效

当前组件<style>写成<style scoped>   加上scoped就可以了

这个style中一般还有lang   lang可以是less scss stylus 等等
不加scoped就是全局的样式

24.Vue 的 keep-live 用过吗?作用是什么?

  1. 没有用过

  2. 用过,它的作用是可以在组件切换时,保存其包裹的组件的状态,使其不被销毁,防止多次渲染。

25.keepalive,添加这个会比平常多生命周期吗? keepalive 已经缓存了,但是想跳回去的时候添加新的属性在哪个生命周期里实现

 会比平常的组件多两个生命周期钩子函数,分别是:activated 和 deactivated。使用keep-alive包裹的组件在切换时不会被销毁,而是缓存到内存中并执行 deactivated 钩子函数,再次渲染后会执行 activated 钩子函数。如果再一次跳回显示组件的时候可以在 activated 中做处理

26.说一下 keep-alive 的关联生命周期

 会比平常的组件多两个生命周期钩子函数,分别是:activated 和 deactivated。使用keep-alive包裹的组件在切换时不会被销毁,而是缓存到内存中并执行 deactivated 钩子函数,再次渲染后会执行 activated 钩子函数。

27.Vue 创建项目的指令是什么

  1. 使用的官方 cli 脚手架,如果是低于 3.0 的版本,使用npm init

  2. cli 的版本大于 3.0 的使用vue create

  3. 可以使用 vite 直接搭建项目,命令为npm init vite@latest,根据提示,一步一步操作就好

28.vue 中如何使用 ref 绑定

 通过为组件或者标签添加 ref 属性,可以在 js 代码中使用全局 api$refs获取 dom 元素或者组件,其上的方法或者属性可以直接进行操作。

29.vue 导航守卫与 jq 导航拦截器的介绍

 vue 的导航守卫一般指的是路由导航守卫,作用是在页面跳转的时候可以执行一个钩子函数。

 导航守卫使用最多的是全局守卫 router.beforeEach 主要是用来验证用户的登陆状态。它接收三个参数 to, from, next

  • to: 即将要进入的路由对象

  • from: 当前导航要离开的路由

  • next: 一个回调函数, 一定要调用这个方法,不然路由不会继续往下

    jq 导航拦截器没有听过,一般在 jQuery 的作用就是对 dom 元素做操作,jQuery 的核心功能是元素选择器。至于提到的导航器可能是一类第三方 jQuery 插件?或者网络请求拦截,如果是网络请求拦截,那么 jQuery 发起请求的话,可以封装一个全局 ajax 请求插件,通过设置 ajaxSetup 实现

 // 参考这个 https://www.runoob.com/jquery/ajax-ajaxsetup.html
 $.ajaxSetup({
   // url: 'demo_ajax_load.txt',
   beforeSend() {
     // 发起请求之前执行
  },
   complete() {
     // 所有的请求成功之后执行
  },
});

30.vue 常用哪些命令

  1. v-model 指令,用于表单输入。

  2. v-on 指令,用于事件绑定。

  3. v-bind 指令,用于动态绑定一个值和传入变量。

  4. v-once 指令,事件只能用一次,无论点击几次,执行一次之后都不会再执行。

  5. v-html 指令,会将 span 的内容替换成 rawHtml 的属性值,直接作为 HTML 代码解析。

  6. v-for 指令,与 HTML 标签结合使用,用于数据的遍历摆放。

  7. v-if 指令,用来进行条件判断的,直接操作 dom。

  8. v-else 指令,用来进行条件判断的,与 v-if 指令连用,意义为条件不成立时执行。

  9. v-show 指令,根据真假切换元素的显示状态。

31.vue 中插槽共有几种,及插槽的作用

 三种:默认插槽、具名插槽、作用域插槽

  • 默认插槽

    默认插槽就是指<slot>,<slot>的作用类似于占符。

    //定义一个全局子组件
    Vue.component('child', {
     template: '<div><slot></slot></div>',
    });
    
    var vm = new Vue({
     el: '#root',
    });
    <!--引用child组件-->
    <div id="root">
     <child>
       <span>我是占位符</span>
     </child>
    </div>

    上述的子组件 child 里定义的 slot 被 span 标签给代替了,如果子组件里没有定义 slot,则 span 标签会被直接忽略,且一个子组件里只能定义一个单个插槽。

  • 具名插槽

    可以通过设置 name 属性,指定显示的位置

    定义一个<base-layout> 组件:

    <div class="container">
     <header>
       <slot name="header"></slot>
     </header>
     <main>
       <slot></slot>
     </main>
     <footer>
       <slot name="footer"></slot>
     </footer>
    </div>
    <base-layout>
     <template v-slot:header>
       <h1>Here might be a page title</h1>
     </template>
    
     <p>A paragraph for the main content.</p>
     <p>And another one.</p>
    
     <template v-slot:footer>
       <p>Here's some contact info</p>
     </template>
    </base-layout>
  • 作用域插槽

    父组件替换插槽标签,但是内容由子组件来提供

    <body>
     <div id="root">
       <child>
         <!--定义一个插槽,该插槽必须放在template标签内-->
         <template slot-scope="props">
           <li>{
         
         {props.value}}</li>
         </template>
       </child>
       <!--!--定义不同的渲染方式-->
       <child>
         <!--slot-scope="props"作用是使props指向子组件中定义的<slot>-->
         <template slot-scope="props">
           <h1>{
         
         {props.value}}</h1>
         </template>
       </child>
     </div>
    </body>
    <script>
     //定义一个全局子组件
     Vue.component('child', {
       data: function () {
         return {
           list: [1, 2, 3, 4],
        };
      },
       template:
         '<div><ul><slot v-for="value in list" :value=value></slot></ul></div>',
    });
    
     var vm = new Vue({
       el: '#root',
    });
    </script>

32.vue 如何使用插件

 直接安装,引入就能使用。vue 还是 js,只要是 js 那么所有的插件使用都是相同的方式,引入绑定到对应的节点或者操作对应的节点就好。

33.Vue 组件懒加载,图片懒加载

组件懒加载

  1. 结合路由插件使用的时候使用 import 方式实现

// 第一步注释import导入的文件
// import About from '../components/About.vue';
// 第二步将引入组件的方式以箭头函数的方式异步引入
const routes = [
{
   path: '/about',
   component: () => import( /* webpackChunkName: 'about' */ '../components/About.vue' )
}
]
````
2. 引入组件的时候使用回调函数的方式引入,比如

```js
// 组件懒加载
const IconList = () => import('components/base/icon-list');

export default {
 components: {
   IconList,
 },
};
````

图片懒加载

就是在加载页面的时候,如果页面中的图片过多,可以使用占位符的方式替换没有在可是区域内的图片,只加载当前需要现实的图片。监听滚动条的位置,当图片标签出现在可视区域的时候,重置图片的路径为真是路径,然后展示图片地址。一般在实际开发的时候都直接使用图片懒加载插件实现。还有一种解决方案就是使用页面骨架屏效果,也是类似占位显示,当数据加载完成之后替换掉占位显示的内容

34.使用Vue封装过组件吗?有哪些?讲一下他们是怎么实现的

比如做后台管理中,很多模块经常会复用,比如侧边导航组件、项目中常用的 echarts图表的封装(比如折线图、柱状图等)

封装组件需要考虑复用性:

  • 预留插槽slot, 多次调用如果 子组件视图结构不一样那么就要 在 子组件template预留好 插槽(单个插槽、具名插槽,作用域插槽)

  • 考虑到数据传递,定义props 组件接收父组件传递的数据,同时需要注意单向数据流,props不能直接修改,$emit自定义事件,父组件修改

  • 业务逻辑不要在子组件中处理,子组件在不同父组件中调用时,业务处理代码不同,切记不要直接在子组件中处理业务,应该子组件 $emit自定义事件,将数据传递给父组件,父组件处理业务。

35.说说vuex的管理操作或理解

vuex是 vue的一个状态管理插件,采用集中式管理方式,来管理项目中多个组件的公共状态。

vuex有一个仓库概念,将组件公共的state存储在仓库的state属性中,state是只读的,组件只能使用,不能直接修改,修改需要通过 仓库中的mutations模块来修改,这样的好处是 当数据修改便于溯源,且不会因为 多个组件 直接修改数据,导致 组件间数据的互相影响, 同时 当我们仓库中有一个state 数据需要请求 数据接口才能获取时,vuex 设计了一个action模块,在action模块中发送异步请求,得到数据后,提交mutation来修改state。当state发生改变后组件自动刷新,在组件中可以commit mutation或者dispatch action来修改state。

具体工作流程如下图

图片

36.说说Vuex的工作流程

vuex的仓库有5个模块,分别是 state,mutations, actions, getters, modules

我们将组件的公共状态定义在 vuex仓库的state中,state是只读的,无法直接修改,必须调动仓库中的某个mutation才能修改状态,getters可以理解为vuex中的计算属性,当我们在某个组件中使用vuex中的某个state时,不是直接使用原值,而是需要派生出一个新的值,就可以定义getters,可以在组件中获取。当依赖的state发生改变,此时getters会重新计算得到新值,同时 action中可以发送异步请求,得到数据后,commit mutation来给state赋值

具体代码如下:

仓库代码

const store = new Vuex.Store({
   state: {
       items: [] // 定义一个公共的购物车数据
  },
   getters: {
       // 可以基于已有的state 派生新的状态
       selectedItems (state) {
           // 过滤购物车中未选中的商品
           return state.items.filter(item => item.selected)
      }
  },
   mutations: {
       // 定义mutation来修改state
       INIT_ITEMS(state, items){
           state.items = items
      }
  },
   actions: {
       // action可以发送异步请求,得到数据后commit mutation将请求结果传入
       FETCH_ITEMS({commit}, params = {}){
           // 调用封装好的 接口函数
           fetchItem(params).then(res => {
               if(res.data.code === 200) {
                   commit('INIT_ITEMS', res.data.data)
              }
          })
      }
  }
})

组件中使用 使用vuex

// 获取state
this.$store.state.items // 直接获取
{
   computed: {
...mapState(['items']) // 助手函数获取
  }
}
// 获取getters
this.$store.getters.selectedItems // 直接获取
{
   computed: {
       ...mapGetters(['selectedItems']) // 助手函数获取
  }
}
// 组件中提交action
this.$store.dispatch('FETCH_ITEMS', {token: 'xxx'})
{
   methods: {
       ...mapActions(['FETCH_ITEMS']) // 助手函数 直接调用this.FETCH_ITEMS(params)触发
  }
}
// 组件中也可以直接commit mutation
this.$store.commit('INIT_ITEMS'[,参数])
{
   methods:{
       ...mapMutations(['INIT_ITEMS']) // 助手函数 直接调用this.INIT_ITEMS(参数)
  }
}

37.vuex项目中怎么使用?工作原理是什么?

原则:

中小型项目中,如果组件的公共状态不多的情况下,不建议使用vuex,反而会增加代码复杂度,想要组件通信,直接通过event bus即可,中大型项目中,多个组件公共状态较多情况下,建议使用vuex

vuex的具体工作流程如下:

在仓库state中定义公共状态,action中发送异步请求,得到数据后调用mutation 赋值给state,组件中使用state,也可以在组件中 dispatch action和触发mutation来修改state,视图刷新

具体代码如下:

仓库代码

const store = new Vuex.Store({
   state: {
       items: [] // 定义一个公共的购物车数据
  },
   getters: {
       // 可以基于已有的state 派生新的状态
       selectedItems (state) {
           // 过滤购物车中未选中的商品
           return state.items.filter(item => item.selected)
      }
  },
   mutations: {
       // 定义mutation来修改state
       INIT_ITEMS(state, items){
           state.items = items
      }
  },
   actions: {
       // action可以发送异步请求,得到数据后commit mutation将请求结果传入
       FETCH_ITEMS({commit}, params = {}){
           // 调用封装好的 接口函数
           fetchItem(params).then(res => {
               if(res.data.code === 200) {
                   commit('INIT_ITEMS', res.data.data)
              }
          })
      }
  }
})

组件中使用 使用vuex

// 获取state
this.$store.state.items // 直接获取
{
   computed: {
...mapState(['items']) // 助手函数获取
  }
}
// 获取getters
this.$store.getters.selectedItems // 直接获取
{
   computed: {
       ...mapGetters(['selectedItems']) // 助手函数获取
  }
}
// 组件中提交action
this.$store.dispatch('FETCH_ITEMS', {token: 'xxx'})
{
   methods: {
       ...mapActions(['FETCH_ITEMS']) // 助手函数 直接调用this.FETCH_ITEMS(params)触发
  }
}
// 组件中也可以直接commit mutation
this.$store.commit('INIT_ITEMS'[,参数])
{
   methods:{
       ...mapMutations(['INIT_ITEMS']) // 助手函数 直接调用this.INIT_ITEMS(参数)
  }
}

38.Vuex中处理异步需要在什么地方写

异步处理需要在 仓库的actions中定义

Action 类似于 mutation,不同在于:

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

  • Action 可以包含任意异步操作。

我们可以在action中发送异步请求,成功后触发mutation 将结果传入,在mutation赋值给state

const store = new Vuex.Store({
   state: {
       items: [] // 定义一个公共的购物车数据
  },
   mutations: {
       // 定义mutation来修改state
       INIT_ITEMS(state, items){
           state.items = items
      }
  },
   actions: {
       // action可以发送异步请求,得到数据后commit mutation将请求结果传入
       FETCH_ITEMS({commit}, params = {}){
           // 调用封装好的 接口函数
           fetchItem(params).then(res => {
               if(res.data.code === 200) {
                   commit('INIT_ITEMS', res.data.data)
              }
          })
      }
  }
})

39.请你谈谈你对vuex的理解

vuex是专为vue设计的状态管理工具,可用于父子组件和非父子组件的全局组件通信。应用的状态集中放在store中,改变状态必须要经过commit,同步改变状态是提交mutations,异步是先通过actions再通过mutations。

一共有5大模块- state存放状态- getters就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。可以对state中的数据做一些处理- mutations更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,通过store.commit提交到mutations模块- actionsactions是异步的改变state中状态的方法,通过store.dispatch来提交到mutations模块,再通过提交commit来更改state中的状态- modulesVuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

40.vuex—个模块中改变state中数据,其他模块如何获取

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

vuex模块化后,需要另一个模块的state变化,可以在这个模块中定义getters 获取,具体代码如下

// 模块a
const modulea = {
   namespaced: true,
   state: {
       num: 10
  },
   mutations: {
       ADD_NUM(state, n) {
           state.num += n
      }
  }
}
// 模块b
const moduleb = {
   namespaced: true,
   state: {
       num: 10
  },
   getters: {
       // 在这里拿到 模块a numstate
     moduleaNum (state, getters, rootState, rootGetters) {
         // 模块下的getter有四个参数分别是当前模块的state,当前模块的getters,以及根state个根getters可以通过rootState获取其他模块的state
         return rootState.modulea.num
         
    }  
  },
   mutations: {
       ADD_NUM(state, n) {
           state.num += n
      }
  }
}

41.vuex的状态是怎样的,怎么改变状态

vuex的状态储存在仓库的state属性中,state是只读的,无法直接修改必须调用mutation才能修改

const store = new Vuex.Store({
   state: {
       num: 10
  },
   mutations: {
       ADD_NUM (state, n) {
           state.num += n
      }
  }
})
// 在组件中直接出发mutaion
this.$store.commit('ADD_NUM', 10)
// 或者助手函数 提交 mutation
{
   methods: {
       ...mapMutations(['ADD_NUM'])
  }
}
// 直接调用即可
this.ADD_NUM(10)

42.你在项目中哪里使用vuex,vuex的应用场景

原则:

中小型项目中,如果组件的公共状态不多的情况下,不建议使用vuex,反而会增加代码复杂度,想要组件通信,直接通过event bus即可,中大型项目中,多个组件公共状态较多情况下,建议使用vuex

在项目中,多个组件的公共状态可以存储的vuex中,比如电商网站的购物车数据,可以存储在vuex中。后台管理角色鉴权中的 不同角色的侧边栏数据,以及 不同角色可以访问的路由数据可以存储的vuex中,拿到数据储存。

猜你喜欢

转载自blog.csdn.net/shi15926lei/article/details/132220284