Vue2面试题

1.v-show和v-if的区别

v-show通过CSS display控制显示和隐藏,v-if是组件的渲染和销毁,

所以频繁切换显示状态用v-show,否则用v-if

2.为何在v-for中使用key?

必须使用key,且最好不用index,diff算法中通过tag和key来判断是否是相同节点,目的是减少渲染次数,提高性能

key的作用

key是Vue中vnode的唯一标记,通过这个标记可以使diff操作更高效

3.双向数据绑定v-model的实现原理

v-model是v-bind绑定value变量,和v-on绑定input事件的结合,

通过v-bind绑定value变量,每次输入内容时候触发input事件,通过事件对象参数获取内容然后赋值给value变量,value发生变化之后input输入框发生变化,,从而实现双向绑定。

<input v-model='something'>
    
相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

自定义组件的双向绑定:

父组件给子组件添加v-model,子组件通过prop的value属性获取数据,子组件内部想要修改value属性的时候通过$emit触发input事件并且携带最新的值给父组件,父组件把值同步到value变量上。

//parent
 <custom-v-model v-model="text"></custom-v-model>
 //child
  <input :value="text" @input="$emit('input', $event.target.value)" />
  props: {
      text: String
  },
  model: {
    prop: 'text',
    event: 'input'
  },
4.为什么组件的data必须是个函数?

因为.vue文件中定义的组件是一个类,每个地方使用该组件的时候都是对组件的实例化,如果data是个对象,组件复用会相互影响。

5.何时使用keepalive?

缓存组件,不需要重复渲染,

多个静态tab页的切换

优化性能

6.何时需要使用beforeDestory?

解绑自定义事件event.$off, 清除定时器,解绑自定义DOM事件,如window.scroll等

7.对vue的MVVM的理解

MVVM分为三个部分,

Model模型层,负责业务数据

View视图层, 负责视图相关的,html和css

ViewModel是View和model连接的桥梁,负责监听模型层和视图层的修改,

MVVM支持双向绑定,意思是当模型层数据进行修改时,VM层会检测到变化,并通知视图层进行更新,反之修改视图层也会通知模型层进行修改。

8.怎样理解Vue的单向数据流

prop让父子组件之间形成了一个单向绑定,父组件的prop更新单向流动到子组件,反过来则不行,这样防止子组件改变父组件的状态从而造成混乱。如果子组件想修改,只能通过$emit派发一个自定义事件通知父组件修改。

有两种常见尝试修改prop的情况:

1.prop传递一个初始值,子组件想将其作为一个本地的数据使用,可以定义一个本地变量,将prop值作为初始值。

2.prop值作为初始值传入并且需要进行转换,可以定义一个计算属性。

9.computed和watch的区别和应用场景?

computed计算属性:如果依赖的属性没有发生变化,调用函数的 时候会从缓存中取值,使用场景是当一个属性受多个属性影响时使用,比如购物车结算。

watch监听属性:每次监听的属性发生变化是执行回调,使用场景是一个属性影响多个属性时,比如搜索框。

10.直接给一个数组项赋值,Vue能检测到吗?

由于js的限制,直接给数组的索引位置赋值并不会触发Vue的更新机制

当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength

为了解决第一个问题,Vue 提供了以下操作方法:
复制代码// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

为了解决第二个问题,Vue 提供了以下操作方法:
// Array.prototype.splice
vm.items.splice(newLength)
11.Vue生命周期的理解?

指的是Vue实例从创建,初始化数据,编译模板,挂载,渲染,更新,卸载的过程。

beforeCreate:组件实例开始创建

created:组件实例已创建,属性和方法已初始化

beforeMount:在挂载之前被调用,模板编译完成,render函数首次被调用。

mounted:挂载完成,dom树已经渲染到页面上,可以进行dom操作。

beforeUpdate:组件数据更新之前调用,发生在虚拟dom打补丁之前。

updated:组件数据更新之后

activated:keepalive专用,组件被激活时调用

deactivated:组件销毁时调用

beforeDestory:组件销毁前调用

destory:组件销毁后调用

12.Vue的父组件和子组件生命周期钩子函数的执行顺序?

执行顺序分为四类:

加载渲染过程:

父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

子组件更新过程:

父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

父组件更新过程:

父 beforeUpdate -> 父 updated

销毁过程:

父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

13.在哪个生命周期中调用异步请求?

‘可以在created和beforeMount,mounted中进行,因为此时data和方法都已经创建,推荐在creatde中进行,因为这样可以减少加载事件。

14.父组件可以监听子组件的生命周期吗?
// Parent.vue
<Child @mounted="doSomething"/>
    
// Child.vue
mounted() {
  this.$emit("mounted");
}


方法二
//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},
    
//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},
15.谈谈对keep-alive的理解

keepalive可以使被包含的组件保留状态避免被重新渲染。

一般结合路由和动态组件一起使用,用于缓存组件,

有include和exclude属性,都支持字符串和正则表达式,include表示缓存的组件,exclude表示不被缓存的组件,,其中exclude优先级比include高。

对应的两个钩子函数activated和deactivated,当组件被激活时触发activated,组件被移除时触发deactivated。

16.多个组件有相同逻辑,如何抽离?(mixin, 以及mixin缺点)
17.你使用过vuex吗?

vuex是一个状态管理模式,集中存储管理所有组件的状态,每一个vuex的核心是store,就是一个容器,包含大部分状态。

Vux的存储是响应式的,放store中状态发生改变,相应的组件也会发生更新。

改变状态的唯一方法是commit mutions。

可以用于大型项目的组件之间的状态管理,小型项目推荐使用localstorage作为数据之间的传递。

vuex的核心概念有state,getter,mutation,action,module

state存放的是状态

mutations存放如何更改状态,且必须是同步函数

getters是从state中派生出状态,比如state中获取某个状态进行过滤后获取新的状态。

actions,通过commit mutations中的方法来改变状态,重点是可以进行异步操作。

module,可以把容器分成几个模块,把状态和方法分类,让代码更清晰。

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

vm.$set实现原理是:

如果目标是数组,直接使用数组的splice方法触发响应式

如果目标是对象,会先判断属性是否存在,对象是否是响应式,最后如果对属性进行响应式处理,则是通过调用defineReactive方法进行响应式处理(defineReactive方法就是Vue在初始化对象时,给对象属性采用Obejct.defineProperty动态添加getter和setter的功能所调用的方法)

19.你有对Vue项目进行哪些优化?

代码层面:

v-if和v-show

computed和watch区分使用场景

v-for必须添加key,且不能与v-if同时用

事件销毁

图片懒加载

路由懒加载

按需引入插件

webpack层面优化:

webpack对图片进行压缩

减少es6转为es5的冗余代码

提取公共代码

模板预编译

Vue项目的编译优化

基础的web技术的优化

开启gzip压缩

浏览器缓存

cdn的使用

Vue响应式
1.监听data变化的核心api是什么?

Object.defineProperty(Vue3.0后使用proxy)

接受三个参数(更新的对象,属性名,配置项)

作用是直接在一个对象上定义一个新属性,或修改一个已存在的属性

它的缺点:

1.监听不到对象新增和删除属性

2.监听不到数组下标的变化,导致通过数组下标添加元素无法实时响应

3.性能问题,需要为每个属性都添加setter和getter,当监听的数据较大时,会带来一定性能问题

 writable:    是否可重写
    value:      当前值
    get:         读取时内部调用的函数
    set:        写入时内部调用的函数
    enumerable:     是否可以遍历
    configurable:     是否可再次修改配置项
    get:当有人读取n属性时,get函数会被调用,返回n的值,
    set:当哦有人修改n属性时,set函数会被调用,会受到要修改的值,可以实现数据的联动效果。

基本使用
const data = {}
let n = 'chenrui'
Object.defineProperty(data, 'n', {
  get: function() {
    console.log('get');
    return n
  },
  set: function(newVal) {
    console.log('set');
    n = newVal
  }
})

// 测试
console.log(data.n);
data.n = 'wuwuwu'
console.log(data);  

2.深度监听data变化

// 通过Object.defineproperty深度监听数组,对象

// 触发更新视图
function updateView() {
  console.log('视图更新');
}

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
  observer(value)
  Object.defineProperty(target, key, {
    enumerable: true,
    configurable: true,
    get() {
      return value;
    },
    set(newVal) {
      
      if(value !== newVal) {
        observer(newVal)
        // 注意:value一直在闭包中,此处设置完之后,再get时也是最新的值
        value = newVal
        // 触发视图更新
        updateView()
      }
    }
  })
}

// 监听对象属性
function observer(target) {
  if(typeof target !== 'object' || target === null) {
    return target  //不是数组或对象
  }

  if(Array.isArray(target)) {
    target.__proto__= arrProto
    for (let i = 0; i < target.length; i++) {
      observer(target[i])

    }
  }

  // 重新定义各个属性(for in也可以遍历数组)
  for(let key in target) {
    defineReactive(target, key, target[key])
  }
}
// 重新定义数组原型
const oldArrayProperty = Array.prototype
// Object.create  创建新对象,原型指向oldArrayProperty,扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty)
const methodsArr = ['push', 'pop', 'shift', 'unshift', 'splice']
methodsArr.forEach((methodName) => {
  arrProto[methodName] = function() {
    updateView()
    console.log('监听数组成功', methodName);
    oldArrayProperty[methodName].call(this, ...arguments)
    
  }
})

// 准备数据
let data = {
  name: 'chenrui',
  info: {
    address: '北京'
  },
  nums: [1,2,3,65]
}
// 监听数据
observer(data)

data.name = 'rt'
data.n = 'nnnn'  //新增属性,监听不到,---Vue.set
delete data.name  //删除属性,监听不到 ---Vue.delete
data.info.address = 'shanghai ' //深度监听
data.nums.push(3232)
2.模板编译(渲染和模板编译的关系)

是将模板字符串转换为rander函数的过程。具体说是当组件的生命周期执行到created和beforemounted之间时,Vue将模板编译成rander函数,它是一个纯js函数,由with语句构成,接受一个Vue组件实例参数,render函数执行时会调用h函数,生成虚拟dom。

vdom: patch(elem, vnode) 和patch(vnode, newVnode)

3.组件渲染/更新的过程

初次渲染过程:

解析模板为render函数,(或在开发环境已完成)触发响应式,监听data属性getter setter, 执行render函数,生成vnode, 并且执行patch(elem, vnode)

更新过程:

修改data,触发setter,(此前在getter中已被监听),重新执行render函数,生成newVnode,执行patch(vnode, newVnode),diff算法更新视图

相关的三个考点:渲染和响应式的关系,渲染和模板编译的关系,渲染和vdom的关系

4.描述响应式原理(Obejct.defineProperty + 渲染更新过程)

通过Object.defineproperty遍历对象的属性,把每个属性转换成带有getter和setter的属性,读取属性时调用getter,修改属性时调用setter。

当运行render函数时,用到了data中的数据时,就会调用该数据的getter函数,然后用watcher记录下来,当数据发生改变时,就会调用setter函数,watcher也会记录下来,然后通知render函数重新运行,然后生成虚拟dom树。

5.什么是虚拟dom?

虚拟dom是一棵以js对象为基础的树,用对象来描述节点(vnode)。

如果组件内有响应的数据,数据发生改变时,render函数会生成一个新的虚拟dom,新的虚拟dom和旧的虚拟dom进行对比(这个对比过程就是diff算法),最后把差异更新到真实dom中。

虚拟dom的优缺点

优点:

保证性能的下限,虚拟dom需要适配任何api可能产生的操作,它的dom操作必须是普遍适用的,虽然性能不是最优的但比起直接操作dom的性能要好,所以是保证性能的下限。

无需手动操作dom,虚拟dom的diff和patch都是在一次更新中自动进行的

跨平台:虚拟dom本质是js对象,而dom与平台强相关, 相比之下虚拟DOM可以进行更方便地跨平台操作 。

缺点:

无法进行极致优化。

6.为什么需要虚拟dom?

虚拟dom把整个dom树抽象成一个js对象,建立了一一对应的关系,每次dom的更改,通过找到相应对象,就找到了相应的dom节点,然后对其更新,可以节省性能,并且js对象的查询比对整个dom树的查询消耗的性能要多,可以直接操作js对象,而不需要频繁操作dom。

7.简述diff算法过程

diff算法的用途是在新老虚拟dom之间找到最小更新的部分,将该部分进行dom更新,

只会在同层级进行比较

diff算法

1.通过patch判断新旧节点是否相同(通过选择器sel和key唯一标识判断),不同则删除旧节点

2.相同就通过patchVnode函数进一步比较,在这个方法内部,先判断新旧节点石头相同如果相同,直接return,如果不同,要分情况对比,对比原则是以新虚拟节点的结果为准,有以下几种情况:

1.新旧节点都有文本节点,就用新的文本节点代替旧文本节点

2.旧节点没有子节点,新节点有子节点,就直接添加新节点

3.旧节点有子节点,新节点没有子节点,直接删除旧子节点

4.新旧节点都有子节点,就执行updateChildren方法

8.异步渲染

$nexttick等dom渲染完成之后再回调,页面渲染会将data的修改做整合,一次性更新视图,目的是减少dom操作次数,提高性能

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

双向绑定指的是:数据变化更新视图,视图变化更新数据。

主要通过四个步骤实现双向绑定:

1.监听器Observer:对数据对象进行遍历,包括子属性对象的属性,利用Object.defineproperty()对属性加上setter和getter,这样的话能监听到数据变化。

2.解析器Compile:解析Vue模板指令,将模板中的变量都替换成数据,然后初始化视图,并且将每个节点都绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

3.订阅者Watcher:watcher订阅者是Observer和Compile之间通信的桥梁,主要任务是订阅Observer中属性值变化的消息,当收到属性变化的消息时,触发解析器Compile中对应的更新函数。

4.订阅器Dep:订阅器采用发布-订阅的模式,收集订阅者watcher,对监听器Observer和订阅者watcher进行统一管理。

vue router
1.路由传参的三种方式

路由传参方式可以分为params传参和query传参,而params传参又可以分为在url中显示参数和不显示参数两种方式,这就是vue路由传参的三种方式。

方式一:params传参(显示参数)

params传参(显示参数)分为声明式和编程式两种:

a.声明式router-link

//子路由配置
{
  path: '/child/:id',
  component: Child
}
//父路由组件
<router-link :to="/child/123">进入Child路由</router-link>

b.编程式this.$router.push

//子路由配置
{
  path: '/child/:id',
  component: Child
}
//父路由编程式传参(一般通过事件触发)
this.$router.push({
    path:'/child/${id}',
})

//在子路由中这样获取参数

this.$route.params.id

方法二:params传参(不显示参数)

也分为声明式和编程式,与方式一不同的是通过路由的name属性进行传值

这种params传值不显示参数的方法会导致在刷新页面时,传递的值会丢失。

a.声明式

<router-link :to="{name:'Child',params:{id:123}}">进入Child路由</router-link>

b.编程式

//子路由配置
{
  path: '/child,
  name: 'Child',
  component: Child
}
//父路由编程式传参(一般通过事件触发)
this.$router.push({
    name:'Child',
    params:{
    	id:123
    }
})

方式三:query传参(显示参数)

a.声明式

//子路由配置
{
  path: '/child',
  name: 'Child',
  component: Child
}
//父路由组件
<router-link :to="{name:'Child',query:{id:123}}">进入Child路由</router-link>

b.编程式

//子路由配置
{
  path: '/child',
  name: 'Child',
  component: Child
}
//父路由编程式传参(一般通过事件触发)
this.$router.push({
    name:'Child',
    query:{
    	id:123
    }
})
query传参,name和path都行
2.hash和history的对比:

hash特点:

通过location.hash实现

hash永远不会提交到服务端

hash变化会触发页面跳转,即浏览器的前进,后退,

hash变化不会刷新页面,spa必需的特点,

可以通过hashchange事件监听hash值的变化进行页面跳转

history特点:

h5 history通过history.pushState和window.onpopstate监听

h5 history需要后端支持

两者选择:

B端:推荐使用hash,简单易用,对url规范不敏感

C端:可以用h5 history,但需要服务端支持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值