乐趣区

vue总结

原文地址

vue(前端框架)解决了什么问题?

现在的前端页面元素越来越多,结构也变得越来越复杂, 当数据和视图混合在一起的时候对它们的处理会十分复杂,同时也很容易出现错误,而现代框架 使用声明式语法,描述组件对象的嵌套关系,并自动生成与 dom 对象的对应关系
参考 1

vue 生命周期

vue 生命周期 描述
beforeCreate 组件实力被创建,el 和数据对象都为 undefined,还未初始化
create 数据已经被初始化,并且初始化了 Vue 内部事件, 但是 DOM 还未生成
befroeMount 完成了模板的编译。把 data 对象里面的数据和 vue 的语法写的模板编译成了虚拟 DOM
mouted 执行了 render 函数,将渲染出来的内容挂载到了 DOM 节点上
beforeUpdate 组件更新之前: 数据发生变化时,会调用 beforeUpdate,然后经历 DOM diff
updated 组件更新后
actived keep-alive组件被激活
deactivated keep-alive移除
beforeDestroy 组件销毁前
destroyed 组件销毁后

简述 Vue 的响应式原理

可以问数据变动如何和视图联系在一起?
Vue 是采用数据劫持结合发布者 - 订阅者模式的方式, Vue 相应系统有三大核心:observe,dep,watcher; 精简版 Vue 代码参考

  • Observe:当一个 Vue 实例创建时,initData 阶段,vue 会遍历 data 选项的属性(observe),用 Object.defineProperty 将它们转为 getter/setter 并且在内部追踪相关依赖(dep),在属性被访问和修改时通知变化。
  • Compite:调用 compile 方法解析模版, 当视图中有用到 vue.data 中数据的时候,会调用实例化 watcher 方法进行依赖收集
  • Watcher:是 ObserverCompile之间通信的桥梁,当视图中遇到绑定的数据时, 在 watcher 方法中会获取这个数据,此时会触发 observe 中的 getter 方法,
  • Dep:发布订阅模式,observe中数据的 getter 被触发时会收集依赖的watcher(dep.depend 方法)
  • 当有数据被改动时会触发 observe 中数据的 setter,此时会调用dep.notify 方法给所有订阅的 watcher 发通知(通过回掉方式)进行视图更新,此时会进行 diff 流程:

vue 中 data 为什么必须要是一个函数

vue 中的 data 为对象,是引用类型,当重用组件时,一个组件对 data 做了更改,那么另一个组件也会跟着改,而使用返回一个函数返回数据,则每次返回都是一个新对象,引用地址不用,所以就不会出现问题

Virtual DOM 是什么

虚拟 DOM 是一个 JavaScript 对象,包含了当前 DOM 的基本结构和信息,它的存在是为了减少对操作无用 DOM 所带来的性能消耗,在大量的、频繁的数据更新下能够对视图进行合理的高效的更新(细粒度的精准修改),同时也抽象了原来的渲染过程,实现了跨平台的能力

简述 vue 中的 DOM DIFF 算法

精简源码;当数据发生改变时,set方法会让调用 Dep.notify 通知所有订阅者 Watcher,订阅者就会调用patch 给真实的 DOM 打补丁 (两个重要函数patchVnodeupdateChildren):

  • 先判断根结点及变化后的节点是否是sameVnode,如果不是的化,就会创建新的根结点并进行替换
  • 如果是 sameVnode,则进入patchVnode 函数,其基本判断

    1. 如果两个节点是相等 oldVnode === vnode 则直接return
    2. 如果 新节点是文本节点 ,则判断新旧文本节点是否一致,不一致(oldVnode.text !== vnode.text) 则替换
    3. 如果 新节点不是文本节点 ,则开始比较新旧节点的子节点oldChch
    4. 如果 子节点都存在 ,则进行updateChildren 计算(稍后讲)
    5. 如果 只有新子节点存在, 则如果旧节点有文本节点,则移除文本节点,然后将新子节点拆入
    6. 如果 只有旧子节点存在,则移除所有子节点
    7. 如果 均无子节点且旧节点是文本节点,则移除文本节点(此时新节点一定不是文本节点)
  • updateChildren函数做细致对比

    1. start && oldStart 对比
    2. end && oldEnd 对比
    3. start && oldEnd 对比
    4. end && oldStart 对比
    5. 生成 map 映射,(key: 旧子节点上的 key,value: 旧子节点在自己点中的位置), 根据 key 记录下老节点在新节点的位置(idxInOld
      1) 如果 找到了 idxInOld, 如果是 相同节点 则移动旧节点到新的对应的地方,否则虽然 key 相同但元素不同,当作新元素节点去创建
      2) 如果 没有找到 idxInOld,则创建节点
    6. 如果 老节点先遍历完,则新节点比老节点多, 将新节点多余的插入进去
    7. 如果 新节点先遍历完,则就节点比新节点多, 将旧节点多余的删除

vue 中 key 的作用

主要是为了复用节点,高效的更新虚拟 DOM,另外,在使用标签元素过渡效果时也会用到 key

computed 的原理

  • vue 对象初始化的同时对计算属性进行初始化initComputed,
  • computed 会对初始化的 Watcher 传入 lazy: true 就会触发 Watcher 中的watcher.dirty=true(dirty 决定了当前属性是否更新),
  • 当视图中有对 computed 引用的时候会第一次执行计算属性,并将 dirty 设置为 false, 并将结果保存在this.value 中进行缓存,
  • 如果依赖没有更改,则下次获取 computed 会这直接返回 this.value, 只有当computed 所依赖的属性发生变化时会将 dirty 设置为true,并重新计算
class Watcher{
  ……
  evaluate () {this.value = this.get()
    this.dirty = false
  }
  ……
}

class initComputed{
  …… 
  // 计算属性的 getter 获取计算属性的值时会调用
    createComputedGetter (key) {return function computedGetter () {
          // 获取到相应的 watcher
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
             //watcher.dirty 参数决定了计算属性值是否需要重新计算,默认值为 true,即第一次时会调用一次
              if (watcher.dirty) {
                  /* 每次执行之后 watcher.dirty 会设置为 false,只要依赖的 data 值改变时才会触发
                  watcher.dirty 为 true, 从而获取值时从新计算 */
                watcher.evaluate()}
              // 获取依赖
              if (Dep.target) {watcher.depend()
              }
              // 返回计算属性的值
              return watcher.value
        }
      }
    }
  ……
}

计算属性 computed 和 watch 的区别

计算属性 顾名思义就是通过其他变量计算得来的,它的值是基于其所依赖的属性来进行 缓存 的, 只有在其所依赖的属性发生变化时才会从新求值
watch是监听一个变量,当变量发生变化时,会调用对应的方法

对 $nextTick 的理解

vue 实现响应式并不是数据一更新就立刻触发 dom 变化,而是按照一定的策略对 dom 进行更新,源码位置,原理:

  • 首先会将所有的 nextTick 放到一个函数中,然后放在 callbacks 数组中,$nextTick没有传 cb 回掉,则返回一个promise
  • 接下来就是 callbacks 的执行时机

    • 首先如果浏览器是否兼容 promise,则用promise.resolve().then 来执行callbacks
    • 如果浏览器兼容 MutationObserver, 则用实例化的MutationObserver 监听文本变化来执行回掉,
    • 如果兼容 setImmediate, 则用setImmediate(cb) 来执行回掉
    • 最后降级为用 setTimeout(fn,0) 来执行
  • vue2.5.X 版本中对于像 v-on 这样的 DOM 交互事件,默认走 macroTimerFunc,也就是,跳过第一步promise 的判断,

子组件为何不可以修改父组件传递的 Prop, 是如何监控并给出错误提示的

  • 单向数据流,易于监测数据的流动,出现了错误可以更加迅速的定位到错误发生的位置
  • initProps 时,会对 props 进行 defineReactive 操作,传入的第四个参数是自定义的 set 报错判断函数,该函数会在触发 props 的 set 方法时执行
// src/core/instance/state.js 源码路径
function initProps (vm: Component, propsOptions: Object) {
  ...
  for (const key in propsOptions) {if (process.env.NODE_ENV !== 'production') {
      ...
      defineReactive(props, key, value, () => {
        // 如果不是跟元素并且不是更新子元素
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })}
    ...
  }
}
// src/core/observer/index.js
export function defineReactive (obj,key,val,customSetter,shallow) {const property = Object.getOwnPropertyDescriptor(obj, key)
  
  const getter = property && property.get
  const setter = property && property.set
  
  Object.defineProperty(obj, key, {
    ...
    set: function reactiveSetter (newVal) {const value = getter ? getter.call(obj) : val
      if (newVal === value || (newVal !== newVal && value !== value)) {return}
      if (process.env.NODE_ENV !== 'production' && customSetter) {customSetter()
      }
      if (getter && !setter) return
      if (setter) {setter.call(obj, newVal)
      } else {val = newVal}
      childOb = !shallow && observe(newVal)
      dep.notify()}
  })
}

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

加载过程:父组件 beforeCreate => 父组件 created => 父组件 beforeMount => 子组件 beforeCreate => 子组件 created => 子组件 beforeMount => 子组件 mounted => 父组件 mounted
更新过程:父组件 beforeUpdate => 子组件 beforeUpdate => 子组件 updated => 父组件 updated
销毁过程:父组件 beforeDestroy => 子组件 beforeDestroy => 子组件 destoryed => 父组件 destoryed

vue-router 的导航解析流程

官网

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用

仅代表个人见解,能力有限,如有错误会误人子弟的地方欢迎留言指出;谢谢

退出移动版