【知识梳理】7.框架基础_4.25

211 阅读5分钟

Vue

基础

1 谈谈什么是SPA

仅在项目初始化的时候加载所有的HTMLCSSJavaScript,后面通过前端路由完成页面内容的切换。

  • 首页加载慢,后期页面切换快
  • 服务器压力小
  • 浏览器前进后退会有问题

2 谈谈什么是MVVM

Model–View–ViewModel软件架构设计模式。数据Model会绑定到ViewModelViewModel自动将数据渲染到View中,View变化的时候会通过ViewModel来更新Model

<!-- View层 -->
<div id="app">
  <p>{{message}}</p>
  <button v-on:click="showMessage()">Click me</button>
</div>
// ViewModel层
var app = new Vue({
  el: "#app",
  data: {
    // 用于描述视图状态
    message: "Hello Vue!",
  },
  methods: {
    // 用于描述视图行为
    showMessage() {
      let vm = this;
      alert(vm.message);
    },
  },
  created() {
    let vm = this;
    // Ajax 获取 Model 层的数据
    ajax({
      url: "/your/server/data/api",
      success(res) {
        vm.message = res;
      },
    });
  },
});
// Model层
{
  "url": "/your/server/data/api",
  "res": {
    "success": true,
    "name": "IoveC",
    "domain": "www.cnblogs.com"
  }
}

3 Class和style 动态绑定

主要通过对象和数组两种方法

对象形式:

<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>

data: {
  isActive: true,
  hasError: false
}
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

data: {
  activeColor: 'red',
  fontSize: 30
}

数组形式:

<div v-bind:class="[activeClass, errorClass]"></div>

data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
<div v-bind:style="[baseStyles, overridingStyles]"></div>

data: {
  baseStyles: {
    color: 'green'
  },
  overridingStyles: {
    width: '100px';
  }
}

4 v-show和v-if

  • v-if惰性加载,需要时才会去渲染相关模块
  • v-show总是会渲染,只是通过display属性控制显示

5 computed和watch区别

  • computed依赖其他属性,并且有缓存,只有当依赖的属性值发生变化时,才会去更新computed的值
  • watch则是对数据的监听回调

computed用于数值计算,watch用于监听数据变化时执行相关异步操作

Q1:computed的实现原理

A:Vue为computed内部实现了一个惰性的watcher和一个Dep实列,当computed对于的依赖发生改变时,先查询Dep中是否有订阅者,有的话再去比较新旧值,有变化的时候才去更新。

  • 只有当需要读取属性值时,才会去重新计算
  • 最终值改变时才会重新渲染
// 不会重新渲染
computed = a + b + c = 6
||
computed = a + e + f = 6

Q:vm.$watch原理

A:对watcher和dep的封装

6 组件中data为什么是个函数

因为组件可以被复用,如果是个对象的话组件在复用的时候,没有作用域隔离,data容易被修改。

函数的话,返回一个对象的独立拷贝。维护各自的data对象。

7 谈谈Vue的单向数据流

父组件prop的更新会向下流动到子组件中,反过来不行。避免组件间数据流难以跟踪

Q: 如果子组件必须要改父组件的prop呢

A:子组件通过$emit向上派发一个事件,父组件接受后修改

8 谈谈v-model原理

本质是语法糖,在不同的表单元素间通过使用属性和事件来实现

  • texttextarea元素使用value属性和input事件
  • checkboxradio使用checked属性和change事件
  • select字段将value作为prop并将change作为事件
<input v-model="sth">
// 等于
<input :value="sth" @input="sth = $event.targent.value">

自定义组件:

父组件:
<ModelChild v-model="message"></ModelChild>

子组件:
<div>{{value}}</div>

props: {
  value: String
},
methods: {
  test1() {
    this.$emit('input', '小红')
  },
},

9 谈谈Vue的生命周期

生命周期就是一个Vue实例,从创建到销毁的一系列过程。在每个阶段都有对应的函数(钩子)可以进行相应的操作。

beforeCreate:new Vue之后调用的第一个钩子,此时组件上的data methods等都不可用

created:初始化data属性的绑定,真实DOM还没生成

beforeMount: 挂载前,render函数第一次被调用,虚拟DOM生成

mounted:真实DOM挂载完毕,data完成双向绑定,el被$.el替代

beforeUpdate: 数据更新前,可以修改数据

updated: 真实DOM更新完毕

beforeDestroy: 组件被卸载前,可以清空定时器

destroyed: 组件被卸载,数据无

16ca74f183827f46.png

Q1:接口请求放在哪个钩子

A:created、beforeMount、mounted都可以,推荐created。主要是1.data已经初始化,更早请求更早获取;2.SSR不支持beforeMount、mounted

Q2:从哪个钩子开始可以操作DOM

A:mounted中,真实DOM已经挂载

10 父子组件钩子函数的执行顺序

渲染阶段:

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

更新阶段:

父beforeUpdate->子beforeUpdate->子Updated->父Updated

销毁阶段:

父beforeDestroy->子beforeDestroy->子Destroyed->父Destroyed

11 父组件如何监听子组件的生命周期

方法1:

// Parent.vue
<Child @mounted="doSomething"/>

// Child.vue
mounted() {
  this.$emit('mounted');
}

方法2:

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

doSomething() {
  console.log('父组件监听到mounted钩子函数 ...');
},

//  Child.vue
mounted(){
  console.log('子组件触发mounted钩子函数 ...');
},

// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...

12 组件间通信方式

  1. props和$emit

父子间通信

  1. ref和$parent

父子间通信

  1. EventBus

通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信

  1. Vuex

13 什么是SSR

将Vue在客户端把标签渲染成HTML的工作放在服务端,服务端把HTML直接返回给客户端

  • 更好的SEO,SPA中数据通过请求获取,而SSR页面和数据由服务端一起返回
  • 首屏更快
  • 只支持beforeCreate和created
  • 服务端NodeJS渲染比只提供静态文件更消耗CPU性能

进阶

14 Vue2.x响应式原理

f6d4d627b9d34347b39830acc9df07c1~tplv-k3u1fbpfcp-watermark.image.png

依赖收集:

通过defineProperty给data添加setter和getter方法;通过创建Dep,收集使用到data的Watcher

编译模板的时候会创建Watcher,并将Dep中的target指向当前的Watcher,如果使用到data就会触发getter方法,然后通过Dep.addSub将Watcher收集起来

派发更新:

数据更新的时候会触发setter方法,判断subs是否有订阅者watcher,有的话然后通过notify调用watcher的update方法更新视图

image-20200804225629695.png

15 Vue2.x如何监测数组的变化

两种特殊情况:

  • 通过下标直接修改数组内对应的值
  • 修改数组的长度

第一种:使用vm.$set()。详见下一节 第二种:数组原型拦截。重写了数组的7个方法,先获取数组的observer对象,有新值就调用便利所有值使用observe监听,手动调用notify

// 以下7个方法被重写
push、pop、shift、unshift、splice、sort、reverse

16 vm.$set()实现原理

vm.$set(target, key, value)
  1. 如果是数组,则使用Vue重新的splice方法方法实现响应式

  2. 如果是对象,会先判读属性是否存在、获取对象的ob判断是否是响应式,根据判断要么直接赋值要么就调用defineReactive来实现响应式

17 Proxy与Object.defineProperty

  • Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历
  • Proxy存在浏览器兼容性问题
  • Proxy可以直接监听对象和数组,还可以代理动态增加的属性

18 Vue中的Virtual-DOM

Virtual-DOM基本流程:

  • 用JS对象模拟真实DOM树(记录节点的类型、属性、子节点等)
  • 比较两棵Virtual-DOM树的差异(Diff算法)
  • 将两个Virtual-DOM对象的差异应用到真正的DOM树(Patch算法)

Vue中Virtual-DOM实现:

  1. Vue中用VNode这个class描述Virtual-DOM,通过_createElement方法来创建VNode
  2. 当oldVnode不存在时,直接创建新的节点。存在时会调用sameVnode进行基本属性的比较,相同才进行Diff,不同的话会删掉老Vnode创建新Vnode
  3. Diff过程,首先进行文本节点的判断, 没有文本节点的情况下,进入子节点的Diff

21 Vue中key的作用

diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,从而找到相应旧节点

20 Vue2.x和Vue3的Diff算法比较

Vue2.x:

1、先找到 不需要移动的相同节点,借助key值找到可复用的节点是,消耗最小

2、再找相同但是需要移动的节点,消耗第二小

3、最后找不到,才会去新建删除节点,保底处理

注意:对比过程不会对Vnode进行修改,而直接对真实DOM修改,Vue的patch是即时的,并不是打包所有修改最后一起操作DOM

Vue3:

最强增长序列,

V2里面会优先处理头头比对,头尾比对,尾头比对;

V3根据新老节点索引列表找到最长稳定序列,通过最长增长子序列的算法比对,找出新旧节点中不需要移动的节点,原地复用,仅对需要移动或已经patch的节点进行操作

21 Vue中渲染过程

22 Vue中的事件机制

Vue事件机制本质上就是一个发布-订阅模式的实现

23 nextTick原理

JS是单线程的,代码执行是基于事件循环。在执行完一个宏任务及其对应的微任务队列后,再执行下一个宏任务前,会进行UI渲染。

Vue在更新DOM时是异步执行的。只要监听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更

如果同一个watcher被多次触发,只会被推入到队列中一次

nextTick方法会在队列中加入一个回调函数,确保该函数在前面的DOM操作完成后才调用

总结:

  • 同一事件循环中的宏任务和对应微任务执行完成->DOM更新->执行Vue.nextTick()的回调
  • nextTick实现基于MutationObserver API,给它绑定回调,得到MO实例,这个回调会在MO实例监听到变动时触发

场景:

  • 在created中执行的DOM操作要放到Vue.nextTick回调中
  • 在修改数据之后立即使用这个方法,获取更新后的DOM
// 创建MO实例
const observer = new MutationObserver(callback)

const textNode = '想要监听的DOM'

observer.observe(textNode, {
  characterData: true // 说明监听文本内容的修改
})

24 谈谈keep-alive原理

虚拟组件,不会渲染DOM

实例会缓存对应组件的VNode,如果命中缓存,直接从缓存对象返回对应VNode

接受3个变量include、exclude、max

LRU缓存淘汰算法

25 Vue-Router路由模式及实现原理

  • hash: 使用URL的hash值来作路由。支持所有浏览器
  • history: 依赖HTML5 History API和服务器配置
  • abstract: 支持所有JavaScript运行环境,如Node.js服务器端。如果发现没有浏览器的API,路由会自动强制进入这个模式

1、hash模式

在域名之后带有一个#

  • 通过loaction.hash获取当前的URL的hash
  • 通过hashchange方法监听URL中hash的变化
  • hash值的改变会记录到浏览器历史中

push将新路由添加到浏览器访问历史的栈顶

2021-04-12_114130.png

  1. $router.push() //调用方法
  2. HashHistory.push() //根据hash模式调用,设置hash并添加到浏览器历史记录(添加到栈顶)(window.location.hash= XXX)
  3. History.transitionTo() //监测更新,更新则调用History.updateRoute()
  4. History.updateRoute() //更新路由
  5. {app._route= route} //替换当前app路由
  6. vm.render() //更新视图

replace替换掉当前的路由添加到浏览器访问历史的栈顶

2021-04-12_114153.png

2、history模式

HTML5 提供的 History API 来实现

  • 通过loaction.pathname获取当前的URL的路由地址
  • history.pushState和history.repalceState修改URL地址,只有前者记录浏览器历史
  • popState监听URL中路由的变化

Q:路由懒加载

A:把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件

// vue异步组件和webpack的动态`import`
const Foo = () => import('./Foo.vue')

26 Vue-Router导航守卫和路由钩子

全局:router.beforeEach router.beforeResolve router.afterEach

路由独享的守卫:beforeEnter

组件内的守卫:beforeRouteEnter beforeRouteUpdate beforeRouteLeave

完整的导航解析流程:

1.失活组件调用beforeRouteLeave

2.调用全局的钩子beforeEach

*3.如果是复用的组件beforeRouteUpdate

4.路由独享beforeEnter

5.激活的组件beforeRouteEnter

6.全局的beforeResolve

7.全局的afterEach

8.触发DOM更新

9.调用beforeRouteEnter中传给next的回调,对应组件作为回调的参数

注:捕获路由钩子中的错误,router.onError;afterEach只接受to、from不接受next

27 Vuex

跨组件通信、数据响应式、数据持久化

  • State:定义了应用状态的数据结构,可以在这里设置默认值。
  • Getter:允许组件从Store中获取数据,mapGetters辅助函数仅仅是将store中的getter映射到局部计算属性。
  • Mutation:是唯一更改store中状态的方法,且必须是同步函数。
  • Action:用于提交mutation,而不是直接变更状态,可以包含任意异步操作。
  • Module:允许将单一的Store拆分为多个store且同时保存在单一的状态树中。

28 Vue3.0其他新特性

待补充

29 Vue项目优化

  • Web层
  • Webpack层
  • 代码层

详细可以参考下:Vue 项目性能优化 — 实践指南

30 踩过的坑

待补充

参考