【大前端03-01】手写 Vue Router、手写响应式实现、虚拟 DOM 和 Diff 算法

本文探讨了Vue中动态添加数据的响应式转换,详细解释了Diff算法的执行过程,并提供了模拟Vue Router hash模式、响应式(v-html/v-on)及Snabbdom电影列表实现的编程题目。
摘要由CSDN通过智能技术生成

【简答题】一、当我们点击按钮的时候动态给 data 增加的成员是否是响应式数据,如果不是的话,如何把新增成员设置成响应式数据,它的内部原理是什么。

let vm = new Vue({
   
 el: '#el'
 data: {
   
  o: 'object',
  dog: {
   }
 },
 method: {
   
  clickHandler () {
   
   // 该 name 属性是否是响应式的
   this.dog.name = 'Trump'
  }
 }
})

不是想响应式数据

当创建好 Vue 实例后,新增一个成员,此时 data 并没有定义该成员,data 中的成员是在创建 Vue 对象的时候 new Observer 来将其设置成响应式数据,当 Vue 实例化完成之后,再添加一个成员,此时仅仅是给 vm 上增加了一个js属性而已,因此并不是响应式的

Vue 文档中给出了解决方案 当新增一个属性时,如何将其转化为响应式数据

对于已经创建的实例,Vue不允许动态添加根级别的响应式属性。但是可以使用 Vue.set(object, propertyName, value)方法向嵌套对象添加响应式属性。您还可以使用vm.$set实例方法,这也是全局Vue.set方法的别名。

// this.$set()的源码
vue.property.$set = set

Vue.set 内部原理:

源码位置: vue/src/core/observer/index.js

export function set (target: Array<any> | Object, key: any, val: any): any {
   
  ...
  // 判断当前target是不是数组,并且key的值是有效的数组索引
  // 这块代码意思是在修改数组时调用set方法时让我们能够触发响应的代码
  if (Array.isArray(target) && isValidArrayIndex(key)) {
   
    // 类似$vm.set(vm.$data.arr, 0, 3)
    // 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
    target.length = Math.max(target.length, key)
    // 利用数组的splice变异方法触发响应式
    target.splice(key, 1, val)
    return val
  }
  // target为对象, key在target或者target.prototype上。
  // 并且key不是Object原型上的属性
  // 说明这个key本来就在对象上面已经定义过了的,直接修改值就可以了,可以自动触发响应
  if (key in target && !(key in Object.prototype)) {
   
    target[key] = val
    return val
  }
  // 以上都不成立, 即开始给target创建一个全新的属性
  // vue给响应式对象都加了一个__ob__属性,如果一个对象有这个__ob__属性,
  // 那么就说明这个对象是响应式对象,我们修改对象已有属性的时候就会触发页面渲染
  // 获取Observer实例
  const ob = (target: any).__ob__
  // Vue 实例对象拥有 _isVue 属性, 即不允许给 Vue 实例对象添加属性
  // 也不允许Vue.set/$set 函数为根数据对象(vm.$data)添加属性
  // 即 当前的target对象是vue实例对象或者是根数据对象,那么就会抛出错误警告
  if (target._isVue || (ob && ob.vmCount)) {
   
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // target本身就不是响应式数据, 不需要响应,那么直接赋值返回即可
  if (!ob) {
   
    target[key] = val
    return val
  }
  // 进行响应式处理
  // 给新加的属性添加依赖,以后再直接修改这个新的属性的时候就会触发页面渲染
  defineReactive(ob.value, key, val)
  // 触发当前的依赖(这里的依赖依然可以理解成渲染函数),所以页面就会进行重新渲染
  ob.dep.notify()
  return val
}

【简答题】二、请简述 Diff 算法的执行过程

  • 执行过程:
    • 在进行同级别节点比较的时候,首先会对新老节点数组的开始和结尾节点设置标记索引,遍历的过程中移动索引
    • 在对开始和结束节点比较的时候,总共有四种情况
      • oldStartVnode / newStartVnode (旧开始节点 / 新开始节点)
      • oldEndVnode / newEndVnode (旧结束节点 / 新结束节点)
      • oldStartVnode / oldEndVnode (旧开始节点 / 新结束节点)
      • oldEndVnode / newStartVnode (旧结束节点 / 新开始节点)
        在这里插入图片描述
    • 开始节点和结束节点比较,这两种情况类似
      • oldStartVnode / newStartVnode (旧开始节点 / 新开始节点)
      • oldEndVnode / newEndVnode (旧结束节点 / 新结束节点)
    • 如果 oldStartVnode 和 newStartVnode 是 sameVnode (key 和 sel 相同)
      • 调用 patchVnode() 对比和更新节点
      • 把旧开始和新开始索引往后移动 oldStartIdx++ / oldEndIdx++
        在这里插入图片描述
    • oldStartVnode / newEndVnode (旧开始节点 / 新结束节点) 相同
      • 调用 patchVnode() 对比和更新节点
      • 把 oldStartVnode 对应的 DOM 元素,移动到右边
      • 更新索引
        在这里插入图片描述
    • oldEndVnode / newStartVnode (旧结束节点 / 新开始节点) 相同
      • 调用 patchVnode() 对比和更新节点
      • 把 oldEndVnode 对应的 DOM 元素,移动到左边
      • 更新索引
        在这里插入图片描述
    • 如果不是以上四种情况
      • 遍历新节点,使用 newStartNode 的 key 在老节点数组中找相同节点
      • 如果没有找到,说明 newStartNode 是新节点
      • 创建新节点对应的 DOM 元素,插入到 DOM 树中
      • 如果找到了
      • 判断新节点和找到的老节点的 sel 选择器是否相同
      • 如果不相同,说明节点被修改了
      • 重新创建对应的 DOM 元素,插入到 DOM 树中
      • 如果相同,把 elmToMove 对应的 DOM 元素,移动到左边
        在这里插入图片描述
    • 循环结束
      • 当老节点的所有子节点先遍历完 (oldStartIdx > oldEndIdx),循环结束
      • 新节点的所有子节点先遍历完 (newStartIdx > newEndIdx),循环结束
    • 如果老节点的数组先遍历完(oldStartIdx > oldEndIdx),说明新节点有剩余,把剩余节点批量插入到右边
      在这里插入图片描述
    • 如果新节点的数组先遍历完(newStartIdx > newEndIdx),说明老节点有剩余,把剩余节点批量删除
      在这里插入图片描述

【编程题】一、模拟 VueRouter 的 hash 模式的实现,实现思路和 History 模式类似,把 URL 中的 # 后面的内容作为路由的地址,可以通过 hashchange 事件监听路由地址的变化。

let _Vue = null

export default class VueRouter {
   
  static install (Vue) {
   
    // 1.判断当前插件是否已经被安装
    // 如果插件已经安装直接返回
    if (VueRouter.install.installed && _Vue === Vue) return
    VueRouter.install.installed = true
    // 2.把 Vue 构造函数记录到全局变量
    _Vue = Vue
    // 3.把创建 Vue 实例时候传入的 router 对象注入到 Vue 实例上
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值