Vue2

326 阅读11分钟

第一阶段

Vue的优点和缺点

优点:轻量级、组件化、虚拟DOM、渐进式、响应式、数据与视图分离

缺点:不利于SEO,首屏加载时间长、不支持IE8以下的

为什么说Vue是一个渐进式框架

通俗来说,就是想用什么就用什么,比如component、vuex、vue-router

Vue和React的异同

相同点:

  1. 都使用了虚拟DOM
  2. 都是组件化开发
  3. 都是单向数据流(父子组件之间,不建议子修改父传下来的数据)
  4. 都支持服务端渲染

不同点:

  1. 编码方面:Vue的是template,React的是JSX
  2. 数据变化方面:Vue是响应式通过Object.defineproperty,React是手动setState
  3. 数据绑定方面:Vue是双向绑定,React是单向绑定
  4. 状态管理方面:Vue的是Vuex,React的是Redux

说一下MVVM模型?和MVC模型有何区别

MVVM模型

M——模型 Model——对应data中的数据——存放数据

V——视图 View——对应页面——展示数据

VM——视图模型 viewModel——对应Vue实例对象——操作数据,实现数据双向绑定

VM主要通过DOM Listeners 和 Data Bindings实现数据双向绑定

  1. 页面监听DOM Listeners:监听页面中的数据、事件的变化,然后反馈到Model
  2. 数据绑定Data Bindings:将Model中的数据绑定到view

image.png

MVC模型

M——模型 Model——负责从数据库取数据的地方

V——视图 View——负责展示数据的地方

C——控制器 Controller——用户交互的地方、例如点击事件等

思想:controller 将 Model 中的数据展示到 View 上

Vue和JQuery的区别在哪?为什么放弃使用JQuery?

区别: JQuery是直接操作DOM的;在Vue中,数据和视图是分开的,Vue是直接操作数据,再通过虚拟DOM来操作真实DOM,从而影响视图

放弃原因

  1. 在频繁操作DOM的场景下,Vue通过虚拟DOM,可以大大提高性能
  2. Vue集成的一下库,vuex、vue-router可以大大提高开发效率

第二阶段

this 指向

  • 组件配置中:

data 函数、methods 中的函数、watch 中的函数、computed 中的函数;它们的 this 均是指向 【VueComponent 实例对象】

  • new Vue({}) 配置中

data 函数、methods 中的函数、watch 中的函数、computed 中的函数;它们的 this 均是指向 【Vue 实例对象】

使用过哪些Vue的修饰符呢?

可以看这篇文章「百毒不侵」面试官最喜欢问的13种Vue修饰符

截屏2021-07-11 下午9.56.53.png

使用过哪些Vue的内部指令呢?

image.png

为什么data要写成函数形式并且返回一个对象

为了防止数据污染,保护数据的独立性

因为一个组件可能会被多处调用比如:在浏览器同时播放两部电视剧,都是调用同一个播放组件,但是传入的数据是不一样的,那么将data写成一个函数并且返回一个对象,那就能保证每一次调用组件的时候,该组件的实例对象都有一个独立的data对象

如果写成对象形式,会怎么样

写成对象形式,如果该组件被多处调用,就意味着该组件的所有实例对象共用同一个data对象,会造成数据污染

说一下ref

通过ref="xxx"绑定DOM元素或者组件,通过this.$refs.xxx调用

  1. 在DOM元素上使用,引用指向的是DOM元素,可以调用DOM元素上的属性
  2. 在组件上使用,引用指向的是组件实例对象,可以调用其身上的属性和方法

Vue怎么实现组件通信

Vue组件通信的6中方式

简述

  1. 父传子:父组件通过v-bind:或简写:绑定传的数据,子组件使用props接受
  2. 子传父:子组件通过$emit将数据以事件的形式发送给父组件,父组件通过v-on:或简写@接受
  3. 使用ref:在父组件中,使用ref="xxx"标记子组件,然后通过this.$refs.xxx直接调用子组件中的数据和方法
  4. 使用vuex

使用场景(基于我会的这4种)

  1. 父子通信:使用 props、$emit、ref
  2. 兄弟、跨级通信:使用 vuex

怎么设置动态class、动态style

<div :class="className"></div>

<div :style="styles"></div>

动态绑定class、style

v-if 和 v-show 的区别

v-if 通过创建和删除DOM节点来实现DOM元素的显示和隐藏,适合不频繁切换的场景

v-show 通过控制DOM元素的样式来实现显示和隐藏,适合频繁切换的场景

介绍一下计算属性和监视属性

计算属性

  1. 原理:底层借助了object.defineproperty()方法getter()和setter(),计算已有属性得出新属性

  2. 优势:与 methods相比,它内部具有缓存机制,可以实现代码复用,效率更高

  3. 调用:初始化默认调用一次,当所依赖的属性发生变化,也会被调用

// 完整写法
computed: {
    fullName: {
        // 获取 fullName 属性
        get() {
            return this.firstName + '-' + this.lastName
        },
        // 修改 fullName 属性
        set(value) {
           
        }
    }
}

// 简写:当计算属性只读不写才能简写,实际开发,大多数计算属性都不能修改
computed: {
    fullName() {
        return this.firstName + '-' + this.lastName
    }
}

监视属性

  1. watch 监听的数据必须是data 或 props中的
  2. 支持异步监听,不支持缓存
  3. watch 监听的函数会接收两个参数,第一个参数是最新的值,第二个参数是变化之前的值
// 完整写法
watch: {
    isHot: {
        immediate: true,    // 是否初始执行handler函数
        deep: true,	   // 开启深度监视

        handler(newValue, oldValue) {
            // 编写相关操作
        }
    }
}

// 简写:前提是不需要写 immediate deep 配置项
watch: {
    isHot(newValue, oldValue) {
        // 编写相关操作
    }
}

computed 和 watch 的区别

computed 是依赖已有属性计算得出一个新属性,内部有缓存机制,可以实现复用,不能进行异步操作

watch 是监听某个属性的变化,从而执行对应的函数,无缓存机制,可以进行异步操作

使用场景

computed多对一,如计算购物车总价:多个商品价格影响一个总价格

watch一对多,如搜索数据:一条搜索信息,会引出多条数据

谈谈生命周期

生命周期.png

父子组件生命周期顺序

  • 渲染过程

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

  • 子组件更新过程

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

  • 父组件更新过程

    父 beforeUpdate --> 父 updated

  • 销毁过程

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

第三阶段

不需要响应式的数据怎么处理

在 Vue 中,会对死数据进行去响应式处理

死数据就是一些从始至终都不会发生变化的数据,比如:写死的下拉框信息、表头等。既然这些数据不会发生变化,那就不需要实现响应式,免得消耗性能

去响应式处理

// 方法一:将数据定义在 data() 的 return{} 外
data () {
    this.list = {xxx}
    return {
        name: 'jie'
    }
}

// 方法二:使用 Object.freeze()
data () {
    return {
        list: Object.freeze({xxx})
    }
}

对象新属性无法更新视图,删除属性无法更新视图,为什么?怎么办?

对象新属性无法更新视图

原因:object.defineproperty()方法没有对对象的新属性进行劫持

// 方法一:在浏览器终端
Vue.$set(obj, key, value)

// 方法二:在组件中
this.$set(this.obj, key, value)

// 方法三:创建新对象
this.obj = Object.assign({}, {
    ...this.obj,  // 浅拷贝
    new: 123
})

删除属性无法更新视图

原因:若在methods中使用 delete this.obj.xxx,是不会引起视图更新的,这是因为原生的delete不会被Vue检测到

// 方法一:在浏览器终端
Vue.$delete(obj, key)

// 方法二:在组件中
this.$delete(this.obj, key)

通过数组下标修改数组中的元素或者手动修改数组的长度,无法更新视图

原因:Vue中操作数组只能通过指定的 API,不能通过下标

Vue 采用数据劫持的方式重写了数组的7种方法:push、pop、shift、unshift、sort、reverse、splice

为什么不建议使用index作为key

  • 若对数据进行:逆序添加、逆序删除等破坏顺序的操作

    • 会产生没有必要的【真实 DOM】更新:界面效果没问题,但是效率低
  • 如果结构中还包含输入类的 DOM

    • 会产生错误 DOM 更新:界面会出问题

举个例子:

<div v-for="(item, index) in list" :key="index">{{item.name}}</div>

list: [
    { name: '小明', id: '123' },
    { name: '小红', id: '124' },
    { name: '小花', id: '125' }
]

渲染为
<div key="0">小明</div>
<div key="1">小红</div>
<div key="2">小花</div>

现在我执行 list.unshift({ name: '小林', id: '122' })

渲染为
<div key="0">小林</div>
<div key="1">小明</div>
<div key="2">小红</div>
<div key="3">小花</div>


新旧对比

<div key="0">小明</div>  <div key="0">小林</div>
<div key="1">小红</div>  <div key="1">小明</div>
<div key="2">小花</div>  <div key="2">小红</div>
                         <div key="3">小花</div>

可以看出,如果用index做key的话,其实是更新了原有的三项,并新增了小花,虽然达到了渲染目的,但是损耗性能

谈谈 nextTick

作用:nextTick 指定的回调函数会在下一次DOM节点更新之后,再执行

原因:因为 nextTick 指定的回调函数会被放到异步队列的最后面,而异步队列是依次链式调用,所以指定的回调函数才会在下一次DOM节点更新之后,再执行

使用场景:当某方法中的某些操作,需要在下一次DOM节点更新之后才执行的时候

某方法 () {
    this.$nextTick(function () {
        // 需要DOM节点更新之后再执行的操作
    })
}

Vue的SSR是什么?有什么好处?

  • SSR就是服务端渲染
  • 基于nodejs serve服务环境开发,所有html代码在服务端渲染
  • 数据返回给前端,然后前端进行“激活”,即可成为浏览器识别的html代码
  • SSR首次加载更快,有更好的用户体验,有更好的seo优化,因为爬虫能看到整个页面的内容,如果是vue项目,由于数据还要经过解析,这就造成爬虫并不会等待你的数据加载完成,所以其实Vue项目的seo体验并不是很好

修改props值,为什么控制台有时飘红,有时不飘

控制台飘红是分情况的

  • 如果传递的props值是基本类型,子组件直接修改,控制台肯定报错

  • 如果传递的是引用类型,子组件修改里面的属性值或某数组的元素值,控制台不会报错

    • 因为引用类型改的不是值,而是引用地址

不过,不管传递的是什么类型的数据,我们都不建议在子组件中直接修改props的值,因为这会破坏单一数据流,可能会导致数据的变化无法追踪

最终阶段

Vue的响应式是怎么实现的

整体思路是数据劫持+观察者模式

一开始通过Object.defineProperty劫持所有属性的getter()和setter(),当数据变化的时候,通知Dep依赖收集器,调用notify,通知Watcher观察者,执行对应的回调函数,从而更新视图

MVVM作为绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听model数据变化,通过Compile来解析编译模板指令,最终利用Watcher连接Observer,Compile,达到双向数据绑定的效果,实现响应式。

202206280939311.png

Vue的数据代理是怎么实现的

  1. 通过Object.defineProperty()data对象的所有属性都添加到Vue实例对象(vm)
  2. 为每一个添加到 vm 上的属性,都指定一个专属的 getter / setter
  3. 通过 Vue 实例对象(vm)来代理 data 对象中对属性的操作(读 / 写)

数据代理就是:通过一个对象来代理,另一个对象中对属性的操作

我们在new一个Vue实例时,Vue做了什么

  1. 创建入口函数,再创建一个数据观察者Observer和一个指令解析器Compile
  2. Compile解析所有DOM节点上的vue指令,提交到更新器Updater(实际上是一个对象)
  3. Updater把数据(如{{}},msg,@click)替换,完成页面初始化渲染;
  4. Observer使用Object.defineProperty劫持数据,通知依赖器Dep
  5. Dep中加入观察者Watcher,当数据发生变化时,通知Watcher更新;
  6. Watcher取到旧值和新值,在回调函数中通知Updater更新视图;
  7. Compile中每个指令都new了一个Watcher,用于触发Watcher的回调函数进行更新。

Vue为什么要采用异步更新

因为 Vue 本身是组件级别的更新,如果更新的时候,数据特别多、操作特别频繁,如果不采用异步更新,会导致每次更新之后都要重新渲染,极其消耗性能

Vue异步更新的原理

  • 修改数据的时候,会触发所有和这个数据有关的 Watcher 进行更新
  • 然后就会将相关的 Watcher 都放入异步队列中(Queue)
  • 调用 nextTick 方法,将所有回调函数添加到数组 callbacks 中,执行异步任务
  • 在异步队列中,根据进入先后,执行对应的函数,然后更新相应的 DOM

key 有什么用?

key 主要用于虚拟 DOM 算法中, 每个虚拟节点都有一个唯一标识Key,Vue 通过对比新旧节点的key来判断节点是否改变,用key就可以大大提高渲染效率

key 的内部原理

虚拟 DOM 中 key 的作用

key 是虚拟 DOM 对象的标识,当数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM】,随后,Vue 进行【新虚拟 DOM】与【旧虚拟 DOM】的差异比较,比较规则如下:

对比规则

  • 旧虚拟 DOM 中找到与新虚拟 DOM 相同的 key

    • 若虚拟 DOM 中内容没变,直接使用之前的【真实 DOM】
    • 若虚拟 DOM 中内容变了,则生成【新的虚拟 DOM】,随后替换页面之中的【真实 DOM】
  • 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key

    • 创建【新的真实 DOM】,随后渲染到页面