Diff 算法的执行过程&&数据响应式&&模拟router&&Snabbdom

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

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

不是响应式的

let vm = new Vue({
    
    
 el: '#el'
 data: {
    
    
  o: 'object',
  dog: {
    
    
    name: ''
  }
 },
 method: {
    
    
  clickHandler () {
    
    
   this.dog.name = 'Trump'
  }
 }
})

题目中通过 this.dog.name = ‘Trump’

vm[key] setter 操作的时候会触发 data[key] 的 setter 操作,data[key] 中的 setter 操作会
walk 这个新的值(walk方法 是给 data 里的对象类型的值设置响应式),而题目中的 data 的 dog 是个空对象,没有任何属性,所以初始化 Vue 实例的时候,在给 dog 设置 proxy 的时候没有任何属性有 getter 和 setter 方法,所以再点击按钮动态的给 dog 添加 name 属性,并没设置的时候是不会触发 dog 对象下的属性 name 的 setter 方法,故不是响应式数据。给 dog 添加了 name 的初始值后,dog 对象的 name 属性就有了 getter 和 setter 方法,故可以实现响应式。

observe.js (data[key] 的 setter 操作会 walk 这个新的值,walk方法是给data里的对象类型的值设置响应式)

2、请简述 Diff 算法的执行过程

diff 算法是虚拟 dom 技术的必然产物,通过新旧虚拟 dom 作对比,将变化的地方更新在真实的
dom 上,另外也需要 diff 高效的执行对比过程,从而降低时间的复杂度
在执行 diff 算法的过程就是调用名为 patch 的函数,比较新旧节点。一边比较一边给真实的 DOM
打补丁。patch 函数接收两个参数 oldVnode 和 Vnode,他们分别代表新的节点和之前的旧节点。这个 patch函数会比较 oldVnode 和 Vnode 是否是相同的,既函数 sameVnode(oldVnode, Vnode),根据这个函数的返回结果分如下两种情况:
true: 则执行 patchVnode
false: 则用 Vnode 替换 oldVnode

patchVnode 函数做的工作
1、找到对应的真实的 Dom,称为 el
2、判断 Vmode 和 oldVnode 是否指向同一对象。
如果是,那么直接 return
如果他们都有文本节点并且不想等,那么将 el 的文本节点设置为 Vnode 的文本节点
如果 oldVnode 有子节点而 Vnode 没有,则删除 el 的子节点
如果 oldVnode 没有子节点而 Vnode 有,则将 Vnode 的子节点真实化之后添加到 el
如果两者都有子节点,则执行 updateChildren 函数比较子节点

流程

我把两张图片放在 image 下面了
在这里插入图片描述

现在分别对 oldS、oldE、S、E 两两做 sameVnode 比较,有四种比较方法,当其中两个能匹配上那么真实 dom 中的相应节点会移到 Vnode 相应的位置
一,如果 oldE 和 E 匹配上了,那么真实 dom 中的第一个节点会移到最后
二,如果 oldE 和 S 匹配上了,那么真实的 dom 中的最后一个节点会移动到最前面,匹配上的两个指针向中间移动
三,如果四种匹配都没有成功,那么遍历 oldChild, S挨个和他们匹配,匹配成功就在真实 dom 中将成功的节点移动到最前面。如果依旧没有成功的,那么将 S 对应的节点 插入到 dom 中对应 oldS 位置,oldS 和 S 指针向前移动。

在这里插入图片描述

oldS => oldStart
oldE => oldEnd
1.oldS = a, oldE = d; S = a, E = b;
oldS 和 S 匹配,则将 dom 中的 a 节点放到第一个,已经是第一个了就不管了,此时 dom 的位置为 a b d
2.oldS = b, oldE = d; S = c, E = b;
oldS 和 E 匹配,就将原本的 b 节点移动到最后, 因为 E 是最后一个节点,他们的位置要一致
这就是上面说的: “当其中两个能匹配上那么真实 dom 中的相应节点会移动到 Vnode 相应的位置”,此时 dom的位置为 a d b
3.oldS = d, oldE = d; S = c, E = d;
oldE 和 d 匹配,位置不变此时 dom 的位置为: a d b
4.oldS++; oldE–; oldS > oldE;
遍历结束,oldChild 先遍历完,所以把 VnodeChild 根据自己的 index 插入到真实的 dom 中去,此时 dom 位置为: a c d b
5.总结
在这里插入图片描述

二、编程题

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

let _Vue = null

export default class VueRouter {
    
      // 导出一个 vue 的类
  static install(Vue) {
    
      // 静态方法 install
    // 1、判断当前插件是否已经被安装
    if (VueRouter.install.installed) {
    
    
      return
    }
    VueRouter.install.installed = true
    // 2、把 Vue 构造函数记录到全局变量
    _Vue = Vue
    // 3、把创建 Vue 实例时候传入的 router 对象注入到 Vue 实例上
    _Vue.mixin({
    
    
      beforeCreate() {
    
    
        if (this.$options.router) {
    
    
          _Vue.prototype.$router = this.$options.router
        }
      }
    })
  }

  // 构造函数
  constructor(options) {
    
    
    this.options = options
    this.routeMap = {
    
    }
    this.data = _Vue.observable({
    
    
      current: '/'  // 存储地址
    })
    this.init()
  }

  init() {
    
    
    this.createRouteMap()
    this.initCompoments(_Vue)
    this.initEvent()
  }

  createRouteMap() {
    
    
    // 遍历所有路由规则,把路由规则解析成键值对的形式 存储到 routeMap 中
    this.options.routes.forEach(route => {
    
    
      this.routeMap[route.path] = route.component
    })
  }

  initCompoments(Vue) {
    
    
    Vue.component('router-link', {
    
    
      props: {
    
    
        to: String
      },
      // h 是个函数,用来渲染 Dom
      // h 有三个参数,第一个是标签,第二个是标签属性,第三个是生成元素的子元素
      render(h) {
    
      
        return h('a', {
    
    
          attrs: {
    
    
            href: this.to
          },
          on: {
    
    
            click: this.clickHandler  // 加 ()变成了调用,不加就是注册了
          }
        }, [this.$slots.default])
      },
      methods: {
    
    
        clickHandler(e) {
    
    
          history.pushState({
    
    }, '', this.to)
          this.$router.data.current = this.to
          e.preventDefault()  // 阻止默认行为
        }
      }
      // template: '<a :herf="to"><slot></slot></a>'
    })
    const self = this
    Vue.component('router-view', {
    
    
      render(h) {
    
    
        const component = self.routeMap[self.data.current]  // 获取路由地址
        return h(component)
      }
    })
  }

  initEvent() {
    
    
    window.addEventListener('popstate', () => {
    
    
      this.data.current = window.location.pathname
    })
  }
}

2、在模拟 Vue.js 响应式源码的基础上实现 v-html 指令,以及 v-on 指令。

处理 v-html

htmlUpdater(node, value, key) {
    
    
  node.innerHTML = value
  new Watcher(this.vm, key, (newValue) => {
    
    
    node.innerHTML = newValue
  })
}

处理 v-on

update(node, key, attrName) {
    
    
  let type = '';
  const index = attrName.indexOf(':')
  let updateFn = this[attrName + 'Updater']
  if(index > 1) {
    
    
    type = attrName.substr(index + 1)
    updateFn = this[attrName.substr(0, index) + 'Updater']
  }
  updateFn && updateFn.call(this, node, this.vm[key], key, type)
}
onUpdater(node, value, key, type) {
    
    
  const res = node.getAttribute(`v-on:${
      
      type}`)
  node.addEventListener(type, (e) => {
    
    
    console.log(res)
  })
}

3、参考 Snabbdom 提供的电影列表的示例,利用Snabbdom 实现类似的效果,如图:

在这里插入图片描述

import {
    
     init } from './node_modules/snabbdom/build/package/init.js'
import {
    
     classModule } from './node_modules/snabbdom/build/package/modules/class.js'
import {
    
     propsModule } from './node_modules/snabbdom/build/package/modules/props.js'
import {
    
     styleModule } from './node_modules/snabbdom/build/package/modules/style.js'
import {
    
     eventListenersModule } from './node_modules/snabbdom/build/package/modules/eventlisteners.js'
import {
    
     h } from './node_modules/snabbdom/build/package/h.js'
import {
    
     remove } from 'lodash'
import {
    
     container } from 'webpack'

var patch = init([classModule, propsModule, styleModule, eventListenersModule])

var vnode

// var nextKey = 11
// var margin = 8
var sortBy = 'rank'
var totalHeight = 0
var originalData = [
  {
    
     rank: 1, title: 'The Shawshank Redemption', desc: 'Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.', elmHeight: 0 },
  {
    
     rank: 2, title: 'The Godfather', desc: 'The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.', elmHeight: 0 },
  {
    
     rank: 3, title: 'The Godfather: Part II', desc: 'The early life and career of Vito Corleone in 1920s New York is portrayed while his son, Michael, expands and tightens his grip on his crime syndicate stretching from Lake Tahoe, Nevada to pre-revolution 1958 Cuba.', elmHeight: 0 },
  {
    
     rank: 4, title: 'The Dark Knight', desc: 'When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, the caped crusader must come to terms with one of the greatest psychological tests of his ability to fight injustice.', elmHeight: 0 },
  {
    
     rank: 5, title: 'Pulp Fiction', desc: 'The lives of two mob hit men, a boxer, a gangster\'s wife, and a pair of diner bandits intertwine in four tales of violence and redemption.', elmHeight: 0 },
  {
    
     rank: 6, title: 'Schindler\'s List', desc: 'In Poland during World War II, Oskar Schindler gradually becomes concerned for his Jewish workforce after witnessing their persecution by the Nazis.', elmHeight: 0 },
  {
    
     rank: 7, title: '12 Angry Men', desc: 'A dissenting juror in a murder trial slowly manages to convince the others that the case is not as obviously clear as it seemed in court.', elmHeight: 0 },
  {
    
     rank: 8, title: 'The Good, the Bad and the Ugly', desc: 'A bounty hunting scam joins two men in an uneasy alliance against a third in a race to find a fortune in gold buried in a remote cemetery.', elmHeight: 0 },
  {
    
     rank: 9, title: 'The Lord of the Rings: The Return of the King', desc: 'Gandalf and Aragorn lead the World of Men against Sauron\'s army to draw his gaze from Frodo and Sam as they approach Mount Doom with the One Ring.', elmHeight: 0 },
  {
    
     rank: 10, title: 'Fight Club', desc: 'An insomniac office worker looking for a way to change his life crosses paths with a devil-may-care soap maker and they form an underground fight club that evolves into something much, much more...', elmHeight: 0 },
]

var data = [
  originalData[0],
  originalData[1],
  originalData[2],
  originalData[3],
  originalData[4],
  originalData[5],
  originalData[6],
  originalData[7],
  originalData[8],
  originalData[9],
]

function view(data) {
    
    
  return h('div', [
    h('h1', 'Top 10 movies'),
    h('div', [
      h('a.btn.add', {
    
     on: {
    
     click: add } }, 'Add'),
      'Sort by',
      h('span-btn-group', [
        h('a.btn.rank', {
    
     class: {
    
     active: sortBy === 'rank' }, on: {
    
     click: function(){
    
     changeSort('rank') } } }, 'Rank'),
        h('a.btn.title', {
    
     class: {
    
     active: sortBy === 'title' }, on: {
    
     click: function(){
    
     changeSort('title') } } }, 'Title'),
        h('a.btn.desc', {
    
     class: {
    
     active: sortBy === 'desc' }, on: {
    
     click: function(){
    
     changeSort('desc') } } }, 'Description'),
      ])
    ]),
    h('div.list', {
    
     style: {
    
     height: totalHeight + 'px' } }, data.map(movieView))
  ])
}

// 添加
function add(params) {
    
    
  const n = originalData[Math.floor(Math.random() * 10)]
  data = [{
    
     rank: data.length + 1, title: n.title, desc: n.desc, elmHeight: 0 }].concat(data);
  render()
}

// 排序
function changeSort(prop) {
    
    
  sortBy = prop
  data.sort(function(a, b) {
    
    
    if (a[prop] > b[prop]) {
    
    
      return 1
    }
    if (a[prop] < b[prop]) {
    
    
      return -1
    }
    return 0
  })
  render()
}

// 单条数据
function movieView(movie) {
    
    
  return h('div.row', {
    
    
    key: movie.rank,
    style: {
    
    
      opacity: '0',
      transform: 'translate(-200px)',
      delayed: {
    
     transform: `translateY(${
      
      movie.offset}px)`, opacity: '1' },
      remove: {
    
     opacity: '0', transform: `translateY(${
      
      movie.offset}px) translateX(200px)` }
    },
    hook: {
    
     insert: (vnode) => {
    
     movie.elmHeight = vnode.elm.offsetHeight } },
  }, [
    h('div', {
    
     style: {
    
     fontWeight: 'bold' } }, movie.rank),
    h('div', movie.title),
    h('div', movie.desc),
    h('div.btn.rm-btn', {
    
     on: {
    
     click: function() {
    
     remove(movie) } } }, 'x')
  ])
}

// 删除
function remve(movie) {
    
    
  data = data.filter(function (m) {
    
    
    return m !== movie
  })
  render()
}


// 渲染
let oldVnode = patch(container, vnode)
function render() {
    
    
  oldVnode = patch(oldVnode, view(data))
}

Guess you like

Origin blog.csdn.net/weixin_46261261/article/details/121061382