小宋爱睡觉 小宋爱睡觉
首页
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • 计算机网络
  • 浏览器原理
  • 性能优化
  • 设计模式
手写系列
  • 字符串
  • 数组
  • 链表
  • 树
  • 动态规划
  • 排序算法
  • GitHub (opens new window)
  • JueJin (opens new window)
首页
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • 计算机网络
  • 浏览器原理
  • 性能优化
  • 设计模式
手写系列
  • 字符串
  • 数组
  • 链表
  • 树
  • 动态规划
  • 排序算法
  • GitHub (opens new window)
  • JueJin (opens new window)
  • Vue基础
    • 介绍一下Vue原理
    • 介绍一下Vue双向绑定的原理
    • 介绍一下什么是MVVM、MVC、MVP
    • 说一下computed和watch的区别
    • 说一下slot都有哪些用法 原理是什么
    • 介绍一下保存页面状态的方法
    • v-if 和 v-show的区别
    • v-if原理和v-show原理
    • data为什么是一个函数而不是对象?
    • 为什么访问data属性不需要带data?
    • v-model的原理
    • 对keep-alive的理解 如何实现 具体是如何缓存的?
    • $nextTick原理
    • Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?
    • Vue模板渲染的原理是什么?
    • 子组件可以直接改变父组件的数据吗
    • Vue和React的理解和异同
    • Vue的优点
    • assets和static的区别
    • Vue如何监听对象和数组的某个属性改变
    • 对ssr的理解
    • Vue的性能优化
    • 对SPA的理解 优缺点是什么
    • template和jsx区别
    • Vue初始化页面为什么会闪动
    • mixin和mixins区别
    • MVVM优缺点
    • vue中使用了哪些设计模式?
  • Vue生命周期
  • 组件通信
  • Vue-Router
  • VueX
  • Vue3
  • VirtualDOM
  • Vue
Crucials
2021-11-28

Vue基础

# Vue基础

# 介绍一下Vue原理特别重要

创建实例 -> 遍历data的属性 -> 用(object.defineProperty/proxy)转化为getter/setter)-> 在实例中的watcher收集data的属性 -> 当data的属性改变触发setter的时候通知watcher -> watch会将关联的组件都更新一遍

img

# 介绍一下Vue双向绑定的原理特别重要

采用了数据劫持和发布订阅的方式

数据劫持的笼统意思

observe的数据对象进行递归遍历,然后添加上getter和setter,当值改变的时候会触发setter,就能监听到数据的变化了

发布订阅的意思

compile解析模板数据,然后将模板上面的变量替换成数据,然后初始化渲染视图,将每个指令对应的节点绑定更新函数 添加监听数据的订阅者 ,数据变动 -> 收到通知 -> 更新视图

而watcher订阅者就是连接observe和compile的桥梁

  1. 在属性订阅器中(dep) 添加实例化的watcher

  2. watcher的实例自身有个update方法

  3. 属性变动的时候调用dep.notify()

  4. 调用节点绑定的更新函数update()

  5. 触发compile绑定的函数

img

总结:

  • Vue

    • 记录传入的选项,设置 $data/$el
    • 把 data 的成员注入到 Vue 实例
    • 负责调用 Observer 实现数据响应式处理(数据劫持)
    • 负责调用 Compiler 编译指令/插值表达式等
  • Observer

    • 数据劫持
      • 负责把 data 中的成员转换成 getter/setter
      • 负责把多层属性转换成 getter/setter
      • 如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
    • 添加 Dep 和 Watcher 的依赖关系
    • 数据变化发送通知
  • Compiler

    • 负责编译模板,解析指令/插值表达式
    • 负责页面的首次渲染过程
    • 当数据变化后重新渲染
  • Dep

    • 收集依赖,添加订阅者(watcher)
    • 通知所有订阅者
  • Watcher

    • 自身实例化的时候往dep对象中添加自己
    • 当数据变化dep通知所有的 Watcher 实例更新视图

# 介绍一下什么是MVVM、MVC、MVP 特别重要

mvvm是 model view viewModel

  • model层代表数据模型 数据和业务逻辑都在model层

  • view代表ui视图 负责数据的展示

  • viewModel 监听model数据的改变并控制视图更新,处理交互

model和view无直接连接 而是通过viewModel进行联系,model和viewModel数据双向绑定,view层交互改变的数据 model层也会改变,model层数据改变也会触发view层的更新,这样程序猿们就可以专心在viewModel层编写业务逻辑,不用关心数据的传递

但是Vue 并没有完全遵循 MVVM 思想呢?

  • 严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了 $refs 这个属性,让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM。

mvc是model view controller

  • view代表ui视图

  • model储存页面的数据

  • controller层负责用户与应用的响应操作

view和model层应用了观察者模式,model层数据变化会通知view进行更新,用户操作 controller触发,调用model修改 然后再通知view进行更新

mvp跟mvc大致相同

  • mvc中的model和view层都耦合在一起,当逻辑复杂时候会造成代码混乱,通过mvp的presenter层,将model和view绑定在一起,实现同步更新,实现对view和model层的解耦

# 说一下computed和watch的区别特别重要

computed计算属性:依赖其他属性值,不支持异步,并且有缓存,只有当依赖的属性值发生改变且有属性值引用它时才会重新计算computed的值(例如购物车的价格就可以用computed)

watch监听器:只是充当观察作用,支持异步,无缓存,类似某种数据监听回调,当监听的数据变化执行回调进行后续操作(适用于当数据变化需要执行异步方法或开销比较大的操作)

# 说一下slot都有哪些用法 原理是什么

默认插槽:没有指定name的时候 会指定默认插槽,一个组件只能有一个

具名插槽:指定了name属性的slot,一个组件可以有多个具名插槽

作用域插槽:可以是匿名插槽也可以是作用域插槽,可以将子组件内部的数据传递给父组件

原理:当子组件实例化时,获取父组件传入slot标签的内容,存在vm.$slot,默认插槽为vm.$slot.default 具名插槽为vm.$slot.xxx,当组件执行渲染函数的时候,遇到slot,使用$slot内容替换,此时可以为插槽传递数据,则叫作用域插槽

# 介绍一下保存页面状态的方法

前组件会被卸载

  • 将当前组件的state通过JSON.stringify()储存在LocalStorage / SessionStorage

    • 优点:兼容性好,不需要额外使用库和工具

    • 缺点:相当于JSON.stringify()的缺点(Date对象Reg对象会出错) 组件回退或者前进会出bug

  • 通过react-router 的 Link 组件的 prop —— to 可以实现路由间传递参数 通过history.location.state拿到状态进行传参

  • 优点 简单快捷 不污染LocalStorage / SessionStorage 解决JSON.stringify()的缺点

  • 缺点 当前组件可以跳转至多个组件 在跳转组件要多次写相同逻辑

前组件不会被卸载

  • 单页面渲染 要写换的组件作为子组件全屏渲染

    • 优点 代码少 状态传递不出错

    • 缺点 需要传递额外prop到子组件 无法利用路由定位

利用 keep-alive 切换时的actived deactivate被执行

<keep-alive>
	<router-view v-if="$route.meta.keepAlive"></router-view>
</kepp-alive>

// router.js
{
  path: '/',
  name: 'xxx',
  component: ()=>import('../src/views/xxx.vue'),
  meta:{
    keepAlive: true // 需要被缓存
  }
},
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 缺点很明显,当页面刷新的时候Vue中的数据会丢失

# v-if 和 v-show的区别特别重要

  • 手段:v-if是动态向dom树中添加或者删除dom v-show是设置display来隐藏
  • 编译过程:v-if有一个局部编译和卸载的过程,切换过程合适地销毁和重建内部的事件监听和子组件 v-show只是基于css切换
  • 编译条件:v-if是惰性的 初始条件为假则什么都不做,只有第一次条件为真的时候才局部编译 v-show一开始就编译然后被缓存
  • 性能消耗:v-if更高的切换消耗 v-show有更高的初始渲染消耗
  • 使用场景:v-if适合运营条件不太改变的场景 v-show适合频繁切换

# v-if原理和v-show原理

v-show

不管初始条件是什么,元素总是会被渲染

我们看一下在vue中是如何实现的

代码很好理解,有transition就执行transition,没有就直接设置display属性

// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vShow.ts
export const vShow: ObjectDirective<VShowElement> = {
  beforeMount(el, { value }, { transition }) {
    el._vod = el.style.display === 'none' ? '' : el.style.display
    if (transition && value) {
      transition.beforeEnter(el)
    } else {
      setDisplay(el, value)
    }
  },
  mounted(el, { value }, { transition }) {
    if (transition && value) {
      transition.enter(el)
    }
  },
  updated(el, { value, oldValue }, { transition }) {
    // ...
  },
  beforeUnmount(el, { value }) {
    setDisplay(el, value)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

v-if

v-if在实现上比v-show要复杂的多,因为还有else else-if 等条件需要处理,这里我们也只摘抄源码中处理 v-if 的一小部分

返回一个node节点,render函数通过表达式的值来决定是否生成DOM

// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.ts
export const transformIf = createStructuralDirectiveTransform(
  /^(if|else|else-if)$/,
  (node, dir, context) => {
    return processIf(node, dir, context, (ifNode, branch, isRoot) => {
      // ...
      return () => {
        if (isRoot) {
          ifNode.codegenNode = createCodegenNodeForBranch(
            branch,
            key,
            context
          ) as IfConditionalExpression
        } else {
          // attach this branch's codegen node to the v-if root.
          const parentCondition = getParentCondition(ifNode.codegenNode!)
          parentCondition.alternate = createCodegenNodeForBranch(
            branch,
            key + ifNode.branches.length - 1,
            context
          )
        }
      }
    })
  }
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# data为什么是一个函数而不是对象?特别重要

如果data是对象时,当组件多次被实例化时,引用的是同一个data对象,会导致不同实例操作数据,会相互影响,所以要写成函数形式,数据以返回值的形式定义,每次复用组件时,会返回新的data,每个组件都有自己私有数据空间,各自维护数据,不干扰其他组件正常运行

# 为什么访问data属性不需要带data?

vue中访问属性代理 this.data.xxx 转换 this.xxx 的实现

 /** 将 某一个对象的属性 访问 映射到 对象的某一个属性成员上 */
function proxy( target, prop, key ) {
  Object.defineProperty( target, key, {
    enumerable: true,
    configurable: true,
    get () {
      return target[ prop ][ key ];
    },
    set ( newVal ) {
      target[ prop ][ key ] = newVal;
    }
  } );
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# v-model的原理重要

我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

  • text 和 textarea 元素使用 value 属性和 input 事件;
  • checkbox 和 radio 使用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例:

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

<input v-bind:value="something" v-on:input="something = $event.target.value">
1
2
3
4
5

# 对keep-alive的理解 如何实现 具体是如何缓存的?

理解

当组件需要切换是,保存一些组件 防止多次渲染,用keep-alive包裹

有三个属性:

  • include 字符串或正则表达式 只有名称匹配的组件会被匹配

  • exclude 字符串或正则表达式,任何名称匹配的组件都不会被缓存

  • max数字 最多可以缓存多少组件实例

流程:

  1. 判断组件的name 不在include或在exclude中,直接返回vNode不缓存

  2. 获取组件实例key

    • key生成规则 cid + "::" tag 仅靠cid不够,因为相同构造函数都可以注册为不同本地组件
  3. 如果组件已缓存 就从缓存对象中获取组件实例给vNode,不存在则添加进缓存对象里面

  4. 超过最大缓存数量,就用LRU清除数组内第一个组件

keep-alive中的LRU算法(least recently used)

通过cache数组存放所有组件的vNode 当cache中原有的组件被使用的时候,会将该组件的key从keys数组中删去,然后push到keys数组最后,以便超出max后删除最不常用的组件

# $nextTick原理特别重要

原理

利用JS原生的Promise MutationObserver setImmediate setTimeout的原生JS模拟对应的微/宏任务,为了利用js这种异步回调任务队列来实现Vue中自己的异步回调队列

Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中1次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用

执行时机:

在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM

引入原因:

  • 如果是同步更新 那么会频繁触发UI/DOM的渲染,可以减少无用渲染
  • 由于VirtualDOM的引入 每一次状态改变 信号都会发给DOM进行更新 每次更新状态都会需要更多的计算 这种无用功会浪费更多性能
  • Vue采用MVVM,也就是开发者只需modelView层进行业务逻辑,利用数据驱动视图的方式,但是避免不了还是要操作dom,有时候DOM1数据发生变化,此时DOM2需要从DOM1中获取数据,却发现DOM2视图并没有更新,这时候就需要用到nextTick
  • 还有的情况是在Vue生命周期里面,created钩子进行DOM的操作要放在nextTick中,因为执行created钩子时,DOM还未渲染 无法操作DOM

# Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?

不会立即同步执行渲染 而是按照一定的策略进行DOM异步更新,当侦听到数据变化的时候,Vue会开启一个队列,并缓冲同一事件循环中发生的所有数据变更,如果同一个watcher被多次触发,只会被推入队列中一次,这时候去除重复数据对避免不必要的计算和DOM操作,在下一个时间循环Tick中刷新队列并执行(已去重)的工作

# Vue模板渲染的原理是什么?重要

vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。

模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。

  • parse阶段:使用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为抽象语法树 AST。
  • optimize阶段:遍历 AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行 diff 比较时,直接跳过这一些静态节点,优化 runtime 的性能。
  • generate阶段:将最终的 AST 转化为 render 函数字符串。

# 子组件可以直接改变父组件的数据吗

子组件不可以改变父组件的数据

主要是为了维护父子组件的单向数据流,为了意外的改变父组件的状态,使得应用的数据流变得难得理解,只能通过$emit派发一个自定义事件,父组件接收到后,由父组件修改

# Vue和React的理解和异同特别重要

相似:

  • 都将路由和全局状态管理交给相关库

  • 都用了Virtual Dom提高重绘性能

  • 都有props 允许组件数据传递

  • 都提倡组件化

不同:

  • 数据流:Vue默认支持数据双向绑定,React提倡单向数据流

  • Virtual DOM:Vue会跟踪每一个组件的依赖关系,不需要重新渲染整棵树 而React每当应用改变 全部子组件都会重新渲染,可以通过PureComponent/shouldComponentUpdate来控制

  • 模板编写:Vue鼓励常规的HTML模板编写 React推荐用JS的语法扩展 -- JSX

  • 监听数据变化的原理:Vue通过getter/setter以及函数劫持,精确知道数据的变化 React通过比较引用的方式进行的,不优化的话会导致大量不必要的vDOM重新渲染

  • 高阶组件:Vue通过mixins扩展 React通过HOC来扩展

# Vue的优点特别重要

  • 轻量级框架:只关注modelView层

  • 双向数据绑定: 在数据操作方面更为简单

  • 组件化:实现了html形式的封装和重用

  • model modelView view层分离 是开发者只需要操作数据就能完成操作

  • virtualDOM 不用原生的DOM操作节点

# assets和static的区别

相同点:都用来存放静态资源

不同点: static里面的资源文件不经过压缩直接进入打包的目录,而asset里面的文件则经过压缩同static一起上传到服务器上,所以static里面的文件相对assets大一些,我们可以将项目中的js文件放在assets中,项目的iconfont等第三方资源文件放在static中,因为他们已经被处理过了

# Vue如何监听对象和数组的某个属性改变重要

  1. this.$set(target, key, value)

  2. splice()、 push()、pop()、shift()、unshift()、sort()、reverse()

Vue.$set原理

  • 如果是修改数组索引,直接用数组的splice方法触发

  • 如果是对象,判断属性是否存在 是否为响应式 ,通过调用defineReactive(就是Vue初始化对象,给对象采用(Object.defineProperty动态添加getter/setter)

# 对ssr的理解重要

ssr是服务端渲染,也就是Vue在客户端将标签渲染成HTML放在服务端,服务端直接返回给客户端

ssr优势:更好的SEO、首屏速度变快

ssr缺点:服务端渲染只支持beforeCreate和Created两个钩子 更多的服务端负载

# Vue的性能优化特别重要

编码阶段

  1. 减少data中的数据,因为data的数据会增加getter和setter 会收集相应的watcher

  2. v-for里面不能嵌套v-if(因为v-for优先级比v-if高,所以哪怕渲染一小部分的用户的元素,也得在每次重渲染的时候遍历整个列表)

  3. spa页面采用keep-alive缓存组件

  4. key保证唯一

  5. 使用路由懒加载 异步组件

  6. 防抖节流

  7. 第三方模块按需导入

  8. 懒加载

SEO优化

  1. 预渲染
  2. ssr

打包优化

  1. 压缩uglify代码

  2. tree shaking/ scope Hoisting

  3. cdn加载第三方模块

  4. splitChunk抽离公共文件

  5. sourceMap优化

用户体验

  1. 骨架屏

  2. PWA

  3. 服务端开启gzip压缩

# 对SPA的理解 优缺点是什么

概念

SPA是single-page application是仅在web应用初始化的时候加载相应的html css和js ,一旦页面加载完成SPA不会因为用户的操作而进行页面的重新加载或跳转,取而代之的是html内容的变换,ui和用户的交互

优点:

  1. 用户体验好 内容改变不会重新加载页面,避免了不必要的跳转和重复渲染 对服务器压力小

缺点:

  1. 初次加载耗时长,需要在加载页面的时候将js和css统一加载 部分页面按需加载

  2. 前进后退路由管理:不能使用浏览器的前进后退功能

  3. SEO难度大,所有内容都在一个页面中动态替换

# template和jsx区别

对于runtime来说只要组件存在render函数即可

  • vue中利用vue-loader编译.vue文件 内部依赖的vue-template-compiler 将template预编译成render函数

  • react利用babel-plugin-transform-vue-jsx这个jsx语法糖解析器就可以了

所以两者都是render 的表现形式 但是jsx更具有灵活性在复杂组件中有优势 而template在代码结构上符合视图和逻辑分离,更简单 更好维护

# Vue初始化页面为什么会闪动

在vue初始化之前,由于div是不归vue管的 所以在还没解析的情况可能会出现{{message}}的情况

解决方法

  • 在根元素标签上加上v-cloak ,添加css样式 [v-cloak] {display: none;}

  • 在根元素加上style="display: none;" :style="{display: 'block'}"

# mixin和mixins区别重要

mixin用于全局混入,会影响到每个组件实例

Vue.mixin({    
  beforeCreate() {        
    // ...逻辑        
    // 这种方式会影响到每个组件的 beforeCreate 钩子函数    
}})
1
2
3
4
5

可以全局混入封装好的axios等

mixins用于扩展组件的方式,如果多个组件有相同业务逻辑,可以将逻辑剥离开,通过mixins混入代码,比如上拉下拉加载数据等,另外mixins混入函数会先于组件的钩子函数,当重名的时候,组件的同名函数会覆盖mixins函数

# MVVM优缺点特别重要

优点:

  1. 低耦合。视图(View)可以独立于Model变化和修改,一个Model可以绑定到不同的View上,当View变化的时候Model可以不变化,当Model变化的时候View也可以不变;
  2. 可重用性。你可以把一些视图逻辑放在一个Model里面,让很多View重用这段视图逻辑。
  3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计

缺点:

  • bug难以调试,使用双向绑定 当页面异常是,可能是view层也可能是model层的代码问题,另外数据绑定的声明是写在view的模板中,没办法断点debug

  • 一个大模块的model层也很大,当长期持有,不释放内存会花费更多内存

  • 对于大型的图形应用程序,视图较多,viewmodel的构建和维护成本较高

# vue中使用了哪些设计模式?特别重要

  • 单例模式:new多次,只有一个实例

image.png

  • 工厂模式:传入参数就可以创建实例(虚拟节点的创建)
  • 发布订阅模式:eventBus
  • 观察者模式:watch和dep
  • 代理模式:_data属性、proxy、防抖、节流
  • 中介者模式:vuex
  • 策略模式
  • 外观模式
上次更新: 2022/03/20, 19:40:28
Vue生命周期

Vue生命周期→

Copyright © 2021-2025 粤ICP备2021165371号
  • 跟随系统
  • 浅色模式
  • 深色模式