vuejs

总结一些 vue 相关的知识点

组件中 data 必须是一个函数?

Data 是一个函数时,每个组件实例都有自己的作用域,每个实例相互独 立,不会相互影响如果是引用类型(对象),当多个组件共用一个数据源时,一处数据改变, 所有的组件数据都会改变,所以要利用函数通过 return 返回对象的拷贝, (返回一个新数据),让每个实例都有自己的作用域,相互不影响。

watch 监听$route 变化第一次不会执行

项目中我们需要根据动态路由传递的参数来请求数据,如果只方在 created 总组件切换不会请求,放到 watch 中第一次不会请求。解决方法可以给 watch 返回一个对象

1
2
3
4
5
6
7
8
watch : {
$route: {
immediate: true, // 加载立即触发
handler() {
console.log('router变了')
}
}
}

计算属性 computed 和监听器 watch 都可以观察属性的变化从而做出响应,不同的是:

计算属性 computed 更多是作为缓存功能的观察者,它可以将一个或者多个 data 的属性进行复杂的计算生成一个新的值,提供给渲染函数使用,当依赖的属性变化时,computed 不会立即重新计算生成新的值,而是先标记为脏数据,当下次 computed 被获取时候,才会进行重新计算并返回。

而监听器 watch 并不具备缓存性,监听器 watch 提供一个监听函数,当监听的属性发生变化时,会立即执行该函数。

vue 中 key 值的作用

  1. 使用 key 来给每个节点做一个唯一标识,作用主要是为了高效的更新虚拟 DOM,其原理是 vue 在 patch 过程中通过 key 可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个 patch 过程更加高效,减少 DOM 操作量,提高性能。
  2. 另外,若不设置 key 还可能在列表更新时引发一些隐蔽的 bug
  3. vue 中在使用相同标签名元素的过渡切换时,也会使用到 key 属性,其目的也是为了让 vue 可以区分它们,否则 vue 只会替换其内部属性而不会触发过渡效果

diff 算法

  1. diff 算法是虚拟 DOM 技术的必然产物:通过新旧虚拟 DOM 作对比(即 diff),将变化的地方更新在真实 DOM 上;另外,也需要 diff 高效的执行对比过程,从而降低时间复杂度为 O(n)。
  2. vue 2.x 中为了降低 Watcher 粒度,每个组件只有一个 Watcher 与之对应,只有引入 diff 才能精确找到发生变化的地方。
  3. vue 中 diff 执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果 oldVnode 和新的渲染结果 newVnode,此过程称为 patch。
  4. diff 过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾节点可能相同做 4 次比对尝试,如果没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点;借助 key 通常可以非常精确找到相同节点,因此整个 patch 过程非常高效

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

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

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

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

mvvm(待做)

nextTick

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
源码实现延迟调用优先级是:Promise > setImmediate > MessageChannel > setTimeout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})

// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick()
.then(function () {
// DOM 更新了
})


Vue 双数据绑定过程中,这边儿数据改变了怎么通知另一边

改变数据劫持和观察者模式 Vue 数据双向绑定是通过数据劫持和观察者模式来实现的, 数据劫持,object.defineproperty 它的目的是:当给属性赋值的时候,程序可以感知到,就可以控制属性值的有效范围,可以改变其他属性的 值观察者模式它的目的是当属性发生改变的时候,使用该数据的地方也发 生改变

Vue3.0 是如何变得更快的?(底层,源码)

  1. diff 方法优化 Vue2.x 中的虚拟 dom 是进行全量的对比。

  2. Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对 比的时候,值对比带有 patch flag 的节点,并且可以通过 flag 的信息 得知当前节点要对比的具体内容化。hoistStatic 静态提升 Vue2.x : 无论元素是否参与更新,每次都会重新创建。 Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时 候被不停的复用。

  3. cacheHandlers 事件侦听器缓存 默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的 变化但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用 即可。

vue 中的嵌套组件

组件定义中一定要声明定义 name 属性

按钮级别的权限设置,可以在利用自定义指令

引用 vue 官方文档对自定义指令的说明

需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令

Vue 的响应式原理中 Object.defineProperty

为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

  • Object.defineProperty 无法监控到数组下标的变化,导致通过数组下标添 加元素,不能实时响应;
  • Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个 属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy 可以劫持整个对 象,并返回一个新的对象。
  • Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性

Vue 的父组件和子组件生命周期钩子执行顺序是什么

  1. 父组建: beforeCreate -> created -> beforeMount
  2. 子组件: -> beforeCreate -> created -> beforeMount -> mounted
  3. 父组件: -> mounted
  4. 总结:从外到内,再从内到外

React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化 到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?

三种优化来降低复杂度:

  1. 如果父节点不同,放弃对子节点的比较,直接删除旧节点然后添加新的 节点重新渲染;
  2. 如果子节点有变化,Virtual DOM 不会计算变化的是什么,而是重新渲染,
  3. 通过唯一的 key 策略