vue

4/22/2024 vue

原博客:

# 什么是MVVM

Model-View-ViewModel

  • Model 代表数据模型
  • View 代表UI视图,它负责将数据模型转化成 UI 展现出来
  • ViewModel 监听 Model 中数据的改变和控制 View 层的展现
  • View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互

# Vue2.0兼容IE哪个版本以上?

不支持ie8及以下,部分兼容ie9 ,完全兼容10以上,因为vue的响应式原理是基于es5的Object.defineProperty(),而这个方法不支持ie8及以下。

# 请描述下vue的生命周期是什么?

  • beforeCreate:实例创建前被调用;
  • created:实例创建后被调用,完成数据观测,属性和方法的运算,watch/event事件回调,模板渲染成html前(vm.$el未定义)故数据初始化最好在这阶段完成;
    • 在created中,页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态,DOM节点没出来,无法操作DOM节点。在mounted不会这样,比较好。
  • beforeMount:在 $el挂载前被调用,相关的 render 函数首次被调用,期间将模块渲染成html,此时vm.$el还是未定义;
  • mounted:在$el挂载后被调用,此时vm.$el可以调用,不能保证所有的子组件都挂载,要等视图全部更新完毕用vm.$nextTick();
  • beforeUpdate:数据更新时调用;
  • updated:数据更新后调用;
  • activated:<keep-alive></keep-alive>包裹的组件激活时调用;
  • deactivated:<keep-alive></keep-alive> 包裹的组件离开时调用;
  • beforeDestroy:实例销毁之前调用,此时实例仍然完全可用;
  • destroyed:实例销毁之后调用,此时实例的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

# $nextTick() 是干什么的?

nextTick 接收一个回调函数作为参数,它的作用是将回调延迟到下一次 DOM 更新之后执行

  • 更新完数据(状态)之后,DOM更新这个动作并不是同步进行的,而是异步的。Vue.js中有一个队列,每当需要渲染时,会将Watcher推送到这个队列中,等下一次事件循环中再让Watcher触发渲染流程。(对于频繁的数据变化,短时间内只进行一次渲染,节省资源)

# watch的属性使用箭头函数定义可以吗?(methods的方法同理)

Vue2不可以。this会是 undefind,因为箭头函数中的this指向的是定义时的this,而不是执行时的this,所以不会指向Vue实例的上下文。

  watch: {
    paramA: ()=>{} // error,this不是vue实例的上下文
  }

vue3反而建议使用箭头函数

const state = reactive({ count })
watch(
  stare, // 1. 监听整个对象,无论是子属性还是孙子属性变化都会触发
  (oldVal, newVal) => {}
)
watch(
  stare.count, // 2.
  (oldVal, newVal) => {}
)
watch(
  ()=>stare, // 3.
  (oldVal, newVal) => {},
  { immediate: true }
)
watch(
  ()=>stare.count, // 4.
  (oldVal, newVal) => {}
)

深度理解vue的响应式原理 - 源码解析

# 怎么在watch监听开始之后立即被调用?

在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调。

# is这个特性你有用过吗?主要用在哪些方面?

  • 动态组件
<component :is="componentName"></component>

componentName可以是在本页面已经注册的局部组件名和全局组件名,也可以是一个组件的选项对象。当控制componentName改变时就可以动态切换选择组件。

  • is的用法 有些HTML元素,诸如 <ul><ol><table><select>,对于哪些元素可以出现在其内部是有严格限制的。 而有些HTML元素,诸如 <li><tr><option>,只能出现在其它某些特定的元素内部。
<ul>
    <card-list></card-list>
</ul>

上面的 <card-list></card-list> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。应该这么写:

<ul>
    <li is="cardList"></li>
</ul>

# 如何访问子组件的实力或者数据、方法?

通过 ref 特性

<!-- 父组件生命子组件的 ref 名称 -->
<SubComponent ref='sub_componnet'></SubComponent>

<script>
// 父组件调用子组件的数据和方法
this.$refs.sub_componnet.data;
this.$refs.sub_componnet.handleClick();
</script>

注意,重复声明会覆盖 ref 对象。

# 常见指令

  • v-if 基于表达式值的真假性,来条件性地渲染元素或者模板片段。会销毁、重建元素。
  • v-show 基于表达式值的真假性,来改变元素的可见性。适用于频繁显示隐藏的元素。
  • v-for 基于原始数据多次渲染元素或模板块。
  • v-on 绑定事件,建议用缩写 @handleClick='xxx'
  • v-bind 绑定属性,建议用缩写 :value="aaa"
  • v-text 更新元素的文本内容。将覆盖元素中所有现有的内容。
<span v-text="msg"></span> 等同于 <span>{{msg}}</span>
  • v-html 更新元素的 innerHTML。容易导致xss攻击,不建议使用。
  • v-model 绑定属性但是是双向绑定。
  • v-slot 用于声明具名插槽或是期望接收 props 的作用域插槽,缩写是 #,如 <template #header></template>
  • v-pre 跳过该元素及其所有子元素的编译。显示原始双大括号标签及内容。
<span v-pre>{{ this will not be compiled }}</span>
  • v-once 仅渲染元素和组件一次,并跳过之后的更新。

# 使用v-for遍历对象时,是按什么顺序遍历的?如何保证顺序?

按 Object.keys() 的顺序的遍历,转成数组保证顺序。

# 在v-for中使用key,会提升性能吗,为什么?

如果渲染是一个简单的列表,如不依赖子组件状态或临时DOM状态(例如:表单输入值)的列表渲染输出,不用key性能会更好,因为不用key采用的是“就地更新”的策略。如果数据项的顺序被改变, Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素。

<template>
    <div>
        <span v-for="item in lists">{{item}}</span>
    </div>
</template>
<script>
export default {
    data() {
        return {
            lists: [1, 2, 3, 4, 5]
        }
    },
}
</script>

以上的例子,v-for的内容会生成以下的DOM节点数组,我们给每一个节点标记一个身份id,以辨别节点的位置:

[
    '<span>1</span>', // id: A
    '<span>2</span>', // id:  B
    '<span>3</span>', // id:  C
    '<span>4</span>', // id:  D
    '<span>5</span>'  // id:  E
]

将lists中的数据进行位置调换,变成[2,4,3,1,5],在没有key的情景下,节点位置不变,但是节点的内容更新了,这就是“就地更新”

[
    '<span>2</span>', // id: A
    '<span>4</span>', // id:  B
    '<span>3</span>', // id:  C
    '<span>1</span>', // id:  D
    '<span>5</span>'  // id:  E
]

但是在有key的情景下,节点位置进行了交换,但是内容没有更新

[
    '<span>2</span>', // id: B
    '<span>4</span>', // id:  D
    '<span>3</span>', // id:  C
    '<span>1</span>', // id:  A
    '<span>5</span>'  // id:  E
]

如果渲染不是一个简单的列表,用key性能会更好一点,因为vue是采用diff算法来对比新旧虚拟节点来更新节点,在diff算法中,当新节点跟旧节点头尾交叉对比没有结果时,先处理旧节点生成一个健为key,值为节点下标index的map映射,如果新节点有key,会通过map映射找到对应的旧节点,如果新节点没有key,会采用遍历查找的方式去找到对应的旧节点,一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。

深入理解v-for的key以及diff算法

# 使用key要注意什么?

  • 不要使用对象或数组之类的非基本类型值作为key,请用字符串或数值类型的值;

  • 不要使用数组的index作为key值,因为在删除数组某一项,index也会随之变化,导致key变化,渲染会出错。 例:在渲染[a,b,c]用 index 作为 key,那么在删除第二项的时候,index 就会从 0 1 2 变成 0 1(而不是 0 2),随之第三项的key变成1了,就会误把第三项删除了。(这里我测试过,不一定,如果某个属性展示了,那么还是会正常变化的)

# 为什么组件中data必须用函数返回一个对象?

对象为引用类型,当重用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题。

# 怎样使scope只在当前vue文件生效?原理是什么?

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

原理:在DOM结构以及css样式上加上唯一的标记data-v-xxxxxx,保证唯一,达到样式私有化,不污染全局的作用。

注意:如果是公共组件,需要加上 :deep(),因为已经到了公共组件里面,走出了当前组件。

# 自定义指令

Vue.direction('colordir', {
  inserted: function (el, bingding) {
    // el 是元素,bingding是绑定的值
    el.style.color = binding.value;
  }
})

<h1 v-colordir="'red'">自定义指令</h1> // 'red' 作为 binding.value 传了过去

inserted 是钩子函数,一共有五个

  • bind:只调用一次,在指令第一次绑定到元素时调用,可以在这个钩子函数中进行初始化设置;
  • inserted:被绑定元素插入父节点时调用,在bind后面调用;
  • update:所在绑定的组件的VNode更新时调用
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用;
  • unbind:只调用一次,指令与元素解绑时调用。

# keep-alive 的理解

keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

# <template></template> 有什么用?

当做一个不可见的包裹元素,减少不必要的DOM元素,整个结构会更加清晰。

# SPA单页面是什么,有什么优点和缺点?

将单个页面加载到服务器之中的web应用程序。服务器会返回一个index.html文件,它所需的js,css等会在显示时统一加载,部分页面按需加载。url地址变化时不会向服务器在请求页面,通过路由才实现页面切换。

优点:

  • 良好的交互体验,用户不需要重新刷新页面,获取数据也是通过Ajax异步获取,页面显示流畅;
  • 良好的前后端工作分离模式。

缺点:

  • SEO难度较高,由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势。
  • 首屏加载过慢(初次加载耗时多)

# Proxy是什么?

proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

let obj = {
  a: 1,
  b: 2
}
let newObj = new Proxy(obj,{
  get: function (target, property) {
    // 获取时,如果不存在这个属性,就返回0
    return property in target ? target[property] : 0
  },
  // 设置某属性时不生效,固定设为 6
  set: function (target, property, value) {
    target[property] = 6;
  },
  has: function (target, prop){
    if(prop == 'b'){
      target[prop] = 6;
    }
    return prop in target;
  },
})

console.log(newObj.a);        // 1
console.log(newObj.c);        // 0

newObj.a = 3;
console.log(newObj.a)         // 6

if('b' in newObj){
  console.log(newObj)       // Proxy {a: 6, b: 6}
}

深度理解vue的响应式原理 - 源码解析

# Object.defineProperty和Proxy的区别

Object.defineProperty

  • 不能监听到数组length属性的变化;
  • 不能监听对象的添加;
  • 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。

Proxy

  • 可以监听数组length属性的变化;
  • 可以监听对象的添加;
  • 可代理整个对象,不需要对对象进行遍历,极大提高性能;
  • 多达13种的拦截远超Object.defineProperty只有get和set两种拦截。

# Vue的核心是什么?

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统。

# React采用单向数据流,Vue采用双向数据流,区别和原因?

单向数据流是指数据只能从父级向子级传递数据,子级不能改变父级向子级传递的数据。

双向数据流是指数据从父级向子级传递数据,子级可以通过一些手段改变父级向子级传递的数据。

因此 React 更加稳定,因为数据只会从父节点改变,然后流向子节点,而 Vue 更加灵活,方便开发规模较小的程序。

# 什么是虚拟DOM?

虚拟DOM是将状态映射成视图的众多解决方案中的一种,其是通过状态生成一个虚拟节点树,然后使用虚拟节点树进行渲染生成真实DOM,在渲染之前,会使用新生成的虚拟节点树和上一次虚拟节点树进行对比,只渲染不同的部分。

# 虚拟DOM的实现思路

首先要构建一个VNode的类,DOM元素上的所有属性在VNode类实例化出来的对象上都存在对应的属性。例如tag表示一个元素节点的名称,text表示一个文本节点的文本,chlidren表示子节点等。将VNode类实例化出来的对象进行分类,例如注释节点、文本节点、元素节点、组件节点、函数式节点、克隆节点。 然后通过编译将模板转成渲染函数render,执行渲染函数render,在其中创建不同类型的VNode类,最后整合就可以得到一个虚拟DOM(vnode)。 最后通过patch将vnode和oldVnode进行比较后,生成真实DOM。

# Vue为什么要求组件模板只能有一个根元素?

当前的virtualDOM差异和diff算法在很大程度上依赖于每个子组件总是只有一个根元素。

# PC端用vue做后台管理系统的时候,一般路由是动态生成的,前端的文件与路由是一一对应的,假如不小心删了一个文件,这个时候就会跳404页面,会有不好的用户体验,怎么做才能比较好的防止跳去404页面?

???文件不存在,router声明里面就会报错文件找不到啊???

增加路由守卫 router.beforeEach((to, form, next))

如果 router.getMatchedComponents(to).pop() === 'undefined' 报错,禁止跳转。