Skip to content

Vue

定位:渐进式 framework

MVVM

Model-View-ViewModel 1643060c121f7361~tplv-t2oaga2asx-zoom-in-crop-mark:1304:0:0:0.awebp (685×362)

Vue.js中的MVVM - 掘金

  • 脏检查机制
  • 数据劫持
  • 代理

就前端而言,Model 往往来自后端接口,View 由 DOM 实现,VM 主要是 Vue/React

与 React 比较

  • 相同点:
    • VirtualDOM 性能好
    • 单向数据流
      • 单向数据流中的单向:数据从父组件到子组件这个流向叫单向。
      • 绑定的单双向:View 层与 Model 层之间的映射关系。
    • 组件化开发
  • 不同点:
    • vue 拥抱经典 Web,react 完全用 JS

    • 获知变化:vue 自动,收集依赖、精确更新,默认优化,react 显式 setState,递归更新

    • vue 双向绑定,react 单向绑定

    • 生态,RN,Weex

      单向绑定使得数据流也是单向的,对于复杂应用来说,这是实施统一的状态管理(如 redux)的前提。双向绑定在一些需要实时反应用户输入的场合 会非常方便(比如多级联动菜单)。但通常认为复杂应用中这种便利比不上引入状态管理带来的优势。注意,Vue 虽然通过 v-model 支持双向绑定, 但是如果引入了类似 redux 的 vuex,就无法同时使用 v-model。参见vuex/forms.md at master · vuejs/vuex 通常一个绑定一个数据就需要一个Watcher,一但我们的绑定细粒度过高就会产生大量的Watcher,这会带来内存以及依赖追踪的开销

对比其他框架 — Vue.js

前端:Vue和React的优点分别是什么?两者的最核心差异对比是什么? - 51CTO.COM

React 的单向数据流与 Vue 的双向绑定 - CSDN 博客

你是如何理解Vue的响应式系统的 - 来亦何哀 - 博客园

单向数据流

不允许直接修改 props,不要在子组件内部直接修改 props,而需要 emit 事件通知父组件修改

数组和对象 prop,可被子组件修改,解决方案是增加 ESLint 规则

如何避免 Vue 的漏洞破坏单向数据流 - 首席CTO笔记

模板渲染

template parse => AST optimize => render function

Vue模板渲染.jpeg

双向绑定

data -> Watcher -> compile

采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个数据属性的setter/getter,在数据变动时发布消息给订阅者,触发相应的监听回调。 具体步骤:

第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化

第二步:compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

第三步:Watcher 侦听者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是: 1、在自身实例化时往属性订阅器(dep)里面添加自己 2、自身必须有一个 update()方法 3、待属性变动 dep.notice() 通知时,能调用自身的 update() 方法,并触发 Compile 中绑定的回调,则功成身退。

第四步:MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。

深入响应式原理 — Vue.js仿 Vue 极简双向绑定Vue.js 源码解析:深入响应式原理How I built my own vanilla JS alternative to Vue and React | Go Make Things

组件通信

  • 复杂应用 => vuex

eventBus

借用 Vue 内部实现了事件模型,其实也可以用第三方库

非父子之间,用 event hub ,即创建一个全局的空 vue 线程,专门处理事件,触发,监听,卸载

js
//文件->event-bus.js

import Vue from 'vue'
export const EventBus = new Vue()

使用

js
import { EventBus } from './event-bus.js'

EventBus.$emit('i-got-clicked', this.clickCount)

EventBus.$on('i-got-cli
			 
			 
			 cked', clickCount => {
  console.log(`Oh, that's nice. It's gotten ${clickCount} clicks! :)`)
})

使用 Vue.js 创建全局事件总线(Global Event Bus )-pilishen.com,做全球最好的实战教程

cklmercer/vue-events: Simple event handling for Vue.js

key 的作用

更新标记

默认策略是就地更新(遍历、依次创建),在发生排序、增删时,不会重用节点。如果有增删、排序需求时,建议带上 key,提高性能。移动节点,而非创建节点,创建节点的开销可能很大。

识别节点的一个通用机制,提示 diff 算法跟踪节点,何时重用、修补、重新排序、重新创建

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素

列表渲染 — Vue.js

更新 id 导致 key 变化,重渲染,解决:this.$vnode.key = newId;

Patching the Vue.js Virtual DOM: The need, the explanation and the solution | by Michael Gallagher | DailyJS | Medium

diff 算法

vnode 虚拟节点(节点描述)

比较新旧 vnode 以决定如何更新真实 DOM

算法:同层比较,深度优先

  1. 节点比较
  2. 不同,替换,插入新 vnode,移除旧 vnode
  3. 相同,子节点比较 1. 子节点均是文本,更新文本 2. 同时有子节点 updateChildren
    • 首先假设头尾节点可能相同做4次比对尝试,如果没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点
    • 借助 key 通常可以非常精确找到相同节点,因此整个 patch 过程非常高效
3. 仅新 vnode 有子节点,创建
4. 仅旧 vnode 有子节点,删除

【一】2020大厂前端面试题大汇总之Vue专题 - YouTube

Vue.js VirtualDOM diff 算法_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili

[Vue][面试]你怎么理解vue中的diff算法?_你好,欢迎光临!-CSDN博客

diff 算法就是进行虚拟节点对比,并返回一个 patch 对象,用来存储两个节点不同的地方,最后用 patch 记录的消息去局部更新 Dom。

[Vue][面试]你了解哪些vue性能优化的方法_你好,欢迎光临!-CSDN博客_vue性能优化面试

[Vue][面试]你知道Vue中key的作用和工作原理吗?说说你对它的理解。_你好,欢迎光临!-CSDN博客_vue中key的作用和原理

异步更新队列

  1. 组件级 watcher
  2. 存入队列,nextTick 时执行
  3. 用户定义的 nextTick cb 放在最后

生命周期

  • beforeDestory不会在窗口 refresh 或 close 时触发,一般用在路由切换

    js
    window.onbeforeunload = function() {
      console.warn('haha') // 要执行的代码
      return '确认离开页面'
    }
  • CMUD + before

  • 子组件后销毁,先挂载

热重载

  • 编辑<template>,重新渲染,保留私有状态
  • 编辑<script>,销毁重建,created/mounted 都会执行
  • 只有在使用路由的情况,created 有可能不触发,比如这个组件复用了、没销毁,也没编辑其<script>部分

组件 data 为什么要求是函数?

因此每个实例可以维护一份被返回对象的独立的拷贝

  • 实例并不一定需要,官方示例、源码是可以写对象的(Vue 3 已不支持)
  • 组件是可复用的 Vue 实例,组件涉及数据隔离所需

watch

  • watch 原始类型比较可靠
  • array 不需要 deep
  • 可以 watch 一个 function
  • 容易滥用,watch 不应该用于改变其他状态,而只应该用于产生不触及状态的副作用
  • 开始时执行一次 immediate: true。场景:组件卸载而再次创建,数据存在 vuex 未变化
  • object 属性必须在 data 对象上存在才能让 Vue 转换它(getter/setter),这样才能让它是响应的。
  • 重新赋值可触发
  • 只 watch 需要的,否则可能有性能开销问题
  • app.$watch需要手动注销
  • 可以 watch array of objects?

Vue.js 中 watch 的高级用法 - 掘金How to Watch Deep Data Structures in Vue (Arrays and Objects) - Michael Thiessen

nextTick

  • DOM 更新后调用
  • vue next DOM update cycle 用 promise/MutationObserver 微任务,如果没有可用的微任务特性,回退为宏任务,所以 vue 的 nextTick 是在 setTimeout 前执行
  • 比 setTimeout 执行更快,在浏览器 render 之前?
  • node 一次事件循环叫 tick
process.nextTick(callback)

vue.js - What is nextTick or what does it do in VueJs - Stack OverflowVue.nextTick 的原理和用途 - SegmentFault 思否深入响应式原理 — Vue.js

js
const copies = callbacks.slice(0) // 复制一个数组,操作不会影响原数组

computed vs watch

  • 模板表达式不便于写复杂计算
  • 基于响应式依赖进行缓存,仅响应式依赖改变时才重新求值
  • 生成新值挂在 vm 上,而 watch 观测已存在的值
  • 依赖变化、且第一次访问时,计算新值,而 watch 数据变化就会执行?
  • watch 可执行异步、高性能开销、设置执行频率、设置中间状态 做面试的不倒翁:浅谈 Vue 中 computed 实现原理

自定义指令

自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。

  • 作用:对 DOM 元素进行底层操作
  • 形式:v-xxv-for是例外
  • 内置:v-model v-if v-show v-bind v-on v-html
  • 自定义:通过注册
  • 使用: v-focus
  • 钩子:bind inserted

示例:v-outside-click 给 body 点击事件绑定函数,判断是否是或被包含

parentElement.contains(childElement)

https://github.com/cyio/chatgpt-vue/blob/main/script.js#L215

[[vue-自定义指令]]

操作数组或对象后,视图没有更新

原因:有些操作,Vue 检测不到变化

变化检测:

  • 对象,需要访问来触发 getter/setter,不支持增、删操作的响应(没有机会 defineProperty 4x )
  • 数组,通过方法操作如 push 可以检测,不支持索引赋值、改长度的响应(无方法可重写)

解决:

  • Vue.set 或 $set

父组件和子组件生命周期钩子执行顺序

从外到内,再从内到外

父组件创建,子组件创建、挂载,父组件挂载

v-model 原理

父组件传 prop

v -> m
  @input
v <- m
  watch $emit

v-if 和 v-show 的共同点和区别

  • 都是用来做条件渲染,通过条件控制元素的显示与隐藏。
  • v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
  • v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
  • 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
  • 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

v-for

支持数组、类数组、对象、数字、字符串

对象,按顺序渲染 value

v-if v-for 同时应用

2.x for 优先 3.x if 优先

官方建议 1. 避免同层使用,易混淆 template ? 2. 过滤,用计算属性 https://v3.cn.vuejs.org/guide/migration/v-if-v-for.html#介绍

[Vue][面试]v-if和v-for哪个优先级更高?如果两个同时出现,应该怎么优化得到更好的性能?_你好,欢迎光临!-CSDN博客

Rewrite in Vite

双向绑定是对表单来说的,表单的双向绑定,说到底不过是 value 的单向绑定 + onChange 事件侦听的一个语法糖 单向数据流核心是在于避免组件的自身(未来可复用)状态设计,它强调把 state hoist 出来进行集中管理。

React setState 引起局部重新刷新。为了达到更好的性能,React 暴漏给开发者 shouldComponentUpdate 这个生命周期 hook,来避免不需要的重新渲染(相比之下,Vue 由于采用依赖追踪,默认就是优化状态:你动了多少数据,就触发多少更新,不多也不少,而 React 对数据变化毫无感知,它就提供 React.createElement 调用已生成 virtual dom)。

setState 修改了数据,但这个数据被哪些地方依赖,React 并不知道

react 递归更新,还有 diff 把关(JS 运行),并不一定重渲染 ODM

React JSX 过度的灵活性导致运行时可以用于优化的信息不足

简单状态管理

全局组件

全局注册组件 Vue.component('组件名1',xxx)

插件

plugin 需要写个 install 方法,内部还是用 组件注册,好处是可以支持更多处理

无渲染组件

  • 模板灵活性、逻辑共享
  • 优于 mixin

如果您发现自己正在编写一个内部包含特定逻辑的组件,但希望该组件的用户可以为其编写任何自定义 HTML 并使用此逻辑,那么无渲染组件可能是您的解决方案之一。

理解 Vue 中的无渲染组件Reactive Slot Scope - CodeSandboxVue3 Composition API如何替换Vue Mixins - 掘金

provide/inject

解决 Prop 逐级透传问题

不会造成全局污染,需要配对使用

适用场景:如全局状态管理、配置信息、国际化、用户身份验证等,提供了一种避免通过props层层传递数据的方式

jsx
// 使用提供者和自定义Hook
function MyComponent() {
  const { state, dispatch } = useMyContext();

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
    </div>
  );
}

Vue-JSX

html
<div class="modal-container">
  {this.$slots.default.length
    ? this.$slots.default[0]
    : <h1>Your content here</h1>}
</div>

在 vue 里 querySelectorAll 取不到 nodeList 用其它方法取到的是 HTMLcollection,比较特殊,不能直接当数组处理 ES6 提供的了转化方法 Array.from(HTMLcollection)

js
var inputs = window.document.getElementsByTagName('input')
var body = window.document.getElementsByTagName('body')[0]
console.log(body)
Array.from(inputs).forEach(function(item) {
  console.log(item)
})
inputs[x].addEventListener('focus', function() {
  body.style.position = 'fixed'
  console.log(body.style.position)
})
inputs[x].addEventListener('blur', function() {
  body.style.position = 'static'
  console.log(body.style.position)
})

学习参考

Vue 探索与实践 | Aotu.io「凹凸实验室」

Vue 2

  • 只有当实例被创建时 data 中存在的属性才是响应式的
  • 对 dom 没依赖的操作可放到 created 中,如数据请求

学习框架,官方的 examples 一定要 clone 下来看源码,这是对框架特性最好的学习资料

  • 要保证文档加载或更新后才执行,需 nextTick
  • transition 成组件,应用范围更广,类名变化
  • js 原生通信机制,事件@click.native

Understanding Components Communication in Vue 2.0 http://taha-sh.com/blog/understanding-components-communication-in-vue-20

过滤器 - filter

  • 2.x 起,不能在指令中使用,改用 JS 原生 filter,写成计算属性
  • 2.x 起,传参数由原来空格变成括号,即普通函数调用形式
  • 不像其它方法会转换数据,只是输出给用户看的变了
js
//global
Vue.filter('filterName', function(value) {
  return // thing to transform
});

//locally, like methods or computed
filters: {
  filterName(value) {
    return // thing to transform
  }
}
  • 可以串联
  • 可以传多个参数
js
{{ data | filterName(arg1, arg2) }}

// arguments are passed in order after the value
filters: {
  filterName(value, arg1, arg2) {
    return //thing to transform
  }
}
  • 每次更新都会执行,如果有大量数据且应该是缓存,用 computed

Edit fiddle - JSFiddle

打包

vue-cli webpack

app.js/main.js 页面入口
chunck-vender.js vue 框架、模块
chunck-common.js
manifest 异步、script 动态引入

webpack 使用 CommonsChunkPlugin 拆包心得 - 简书Splitting Bundles with Webpack 4webpack/examples/common-chunk-and-vendor-chunk at master · webpack/webpackSplitChunksPlugin 查看完整配置./node_modules/.bin/vue-cli-service inspect 偶然编译出错,尝试删除node_modules/.cache

单文件拆分

<template src="./template.pug" lang="pug"></template>
<script src="./script.js"></script>
<style src="./style.styl" lang="stylus"></style>
├── app.js
└── component
    ├── item-edit
    └── item-list
        ├── config.json
        ├── index.vue
        ├── script.js
        ├── style.styl
        └── template.pug

mixin

  • 把组件逻辑暴露在 mixin 中
  • 有些耦合度高的数据不适用
  • 可以是局部,可以是全局
  • 生命周期钩子中的代码会合并到组件对应生命周期中
js
// 设为全局
Vue.mixin({
  mounted() {
    console.log('hello from mixin!')
  }
})

new Vue({
  ...
})
// This console.log would now appear in every component
  • pure
js
const toggle = {
	  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing
    }
  }
}
js
const toggle = {
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing
    }
  }
}

Vue 文档中几个易忽视部分的剖析 - 掘金

mixin 私有属性方法,命名推荐添加$_mixinName_methodName 前缀 【译】VueJS 最佳实践 · Issue #69 · amandakelake/blog

动态样式

可以绑定计算属性 Edit fiddle - JSFiddle

设置当视口变化时,元素总是垂直居中

js
.container(:style="alignStyle")

  data() {
    return {
      cHeight: document.querySelector('body').clientHeight,
    };
  },
  computed: {
    alignStyle() {
      return {
        marginTop: Number.parseInt((this.cHeight - 576 - 56 - 44) / 2) + 'px'
      }
    },
  },
  mounted() {
    window.onresize = () => {
      this.cHeight = document.querySelector('body').clientHeight
    }
  },

函数式

无状态,可以无 script,props.itemClick 与上级通信

Vue Template - CodeSandbox

滚动记忆

scrollBehavior

每个页面都会自动在history.state对象中存储一个对应的key值,`

便利用这个特性实现了页面后退时,数据和滚动条还原,

不过目前只是实现了页面的顶级组件还原,

如果需要对顶级组件下的子组件实现数据还原,`

可以利用$options._scopeId来实现。

运行时版与完整版差异

是否包含模板编译器 template compiler

运行时版仅支持 render function,适合有构建步骤,然后产出用 runtime,减小体积

使用 vue-cli 预构建将 template 转换为 render function,生产环境用的是 runtime

vue.js - What exactly is Vue's runtime-only build and how does it differ from compiler build? - Stack Overflow

计算属性与方法

计算属性 三大特点:缓存、自动依赖追踪、模板中使用

计算属性适合在模板中使用,将复杂的计算逻辑与界面绑定分离,以及在频繁使用相同计算逻辑的情况下进行性能优化。方法则适合处理具有交互性、需要手动触发或不需要缓存的逻辑。选择使用哪种方式取决于你的具体需求和设计考虑。