系统学习大前端(11)---vue-router实现、模拟vue、VDOM实现原理练习

文章内容输出来源:拉勾教育大前端高薪训练营
一、简答题
1、当我们点击按钮的时候动态给 data 增加的成员是否是响应式数据,如果不是的话,如果把新增成员设置成响应式数据,它的内部原理是什么。

let vm = new Vue({
 el: '#el'
 data: {
  o: 'object',
  dog: {}
 },
 method: {
  clickHandler () {
   // 该 name 属性是否是响应式的
   this.dog.name = 'Trump'
  }
 }
})
  • 不是响应式的
  • 解决
    • data定义的时候给定义属性name
    data:{
        dog:{
            name:''
        }
    }
    
    原理: vue的Observer方法里面对data做了递归的响应式处理。
    • 通过Vue.set()方法实现
     handleClick () {
      this.$set(this.dog, 'name', 'xxxx')
      setTimeout(() => {
        this.dog.name = 'oo444ooo'
      }, 2000)
    }
    
    原理:vue源码里面set方法通过调用defineReactive$$1方法给这个属性变成响应式的,并通过dep.notify()方法通知依赖更新。
    • 通过Object.assign()
    handleClick () {
      this.dog.name = 'xxxxx'
      this.dog = Object.assign({}, this.dog)
      setTimeout(() => {
        this.dog.name = 'oo444ooo'
      }, 2000)
    }
    
    原理:通过Object.assign() 复制增加属性后的对象,重新赋值了对象的引用,使得对象变成响应式了。方式一的处理方法原理类似。

2、请简述 Diff 算法的执行过程
diff算法是通过同层的树节点进行比较。

  • 1、老节点不存在,直接添加新节点到父元素
  • 2、新节点不存,从父元素删除老节点。
  • 3、新老节点都存在
    • 3.1 判断是否是相同节点(根据key、tag、isComment、data同时定义或不定义)
      • 相同直接返回
      • 不是相同节点
        • 如果新老节点都是静态的,且key相同。从老节点拿过来,跳过比对的过程。
        • 如果新节点是文本节点,设置节点的text
        • 新节点不是文本节点
          • 新老节点子节点都存在且不同,使用updateChildren函数来更新子节点
          • 只有新节点字节点存在,如果老节点子节点是文本节点,删除老节点的文本,将新节点子节点插入
          • 只有老节点存在子节点,删除老节点的子节点
    • 3.2 updateChildren
      • 给新老节点定义开始、结束索引
      • 循环比对新节点开始VS老节点开始、新节点结束VS老节点结束、新节点开始VS老节点结束、新节点结束VS老节点开始并移动对应的索引,向中间靠拢
      • 根据新节点的key在老节点中查找,没有找到则创建新节点。
      • 循环结束后,如果老节点有多的,则删除。如果新节点有多的,则添加。

二、编程题

1、模拟 VueRouter 的 hash 模式的实现,实现思路和 History 模式类似,把 URL 中的 # 后面的内容作为路由的地址,可以通过 hashchange 事件监听路由地址的变化。
这里跟history模式不同的地方在于事件的处理

    clickHandle (e) {
        location.hash = this.to
        this.$router.data.current = this.to
        e.preventDefault()
    }
    initEvent () {
        window.addEventListener('hashchange', () => {
            this.data.current = location.hash.slice(1)
        })
    }

hash完整版实现

let _Vue = null
export default class VueRouter {
  constructor (options) {
    this.options = options
    this.routerMap = {}
    this.data = _Vue.observable({
      current: '/'
    })
  }

  static install (Vue) {
    if (VueRouter.install.installed) return
    VueRouter.install.installed = true
    _Vue = Vue
    _Vue.mixin({
      beforeCreate () {
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }

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

  createRouteMap () {
    this.options.routes.forEach(route => {
      this.routerMap[route.path] = route.component
    })
  }

  initComponents (Vue) {
    Vue.component('router-link', {
      props: {
        to: String
      },
      render (h) {
        return h('a', {
          attrs: {
            href: this.to
          },
          on: {
            click: this.clickHandle
          }
        }, [this.$slots.default])
      },
      methods: {
        clickHandle (e) {
          location.hash = this.to
          this.$router.data.current = this.to
          e.preventDefault()
        }
      }
    })
    const _this = this
    Vue.component('router-view', {
      render (h) {
        const component = _this.routerMap[_this.data.current]
        return h(component)
      }
    })
  }

  initEvent () {
    window.addEventListener('hashchange', () => {
      this.data.current = location.hash.slice(1)
    })
  }
}

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 指令
    • 处理节点的时候对属性做一下判断 含有’:'认为是v-on:click这种
    • 然后取出事件类型、事件名称
    • 通过addEventListener在node节点上做事件监听
    • 根据事件名称在vm里面的$options.methods里找到方法执行
   compileElement(node){
       // 遍历属性节点
       Array.from(node.attributes).forEach(attr=>{
           let attrName = attr.name;
           if(this.isDirective(attrName)){
               attrName = attrName.substr(2)
               let key = attr.value;
               if (attrName.indexOf(':')!==-1) {
                   let eventType = attrName.split(':')[1]
                   this.handleEvent(this,node,eventType,key)
               }
               this.update(node, key, attrName)
           }
       })
   }
    handleEvent(vm,node,eventType,eventName){
       node.addEventListener(eventType,()=>{
           vm.vm.$options.methods[eventName]()
       })
   }

3、参考 Snabbdom 提供的电影列表的示例,实现类似的效果。
这个作业,我去掉了动画的效果。

import { h, init } from 'snabbdom'
import style from 'snabbdom/modules/style'
import eventlisteners from 'snabbdom/modules/eventlisteners'
import { originalData } from './originData'

let patch = init([style,eventlisteners])

let data = [...originalData]
const container = document.querySelector('#container')

var sortBy = 'rank';
let vnode = view(data);

// 初次渲染
let oldVnode = patch(container, vnode)


// 渲染
function render() {
    oldVnode = patch(oldVnode, view(data));
}
// 生成新的VDOM
function view(data) {
    return h('div#container',
        [
            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: [changeSort, 'rank'] }
                                }, 'Rank'),
                            h('a.btn.title',
                                {
                                    'class': { active: sortBy === 'title' },
                                    on: { click: [changeSort, 'title'] }
                                }, 'Title'),
                            h('a.btn.desc',
                                {
                                    'class': { active: sortBy === 'desc' },
                                    on: { click: [changeSort, 'desc'] }
                                }, 'Description')
                        ])
                ]),
            h('div.list', data.map(movieView))
        ]);
}

// 添加一条数据 放在最上面
function add() {
    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: {
            display: 'none', 
            delayed: { transform: 'translateY(' + movie.offset + 'px)', display: 'block' },
            remove: { display: 'none', transform: 'translateY(' + movie.offset + 'px) translateX(200px)' }
        },
        hook: {
            insert: function 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: [remove, movie]}}, 'x')]);
}
// 删除数据
function remove(movie) {
    data = data.filter(function (m) {
        return m !== movie;
    });
    render()
}

snabbdom 版本为 0.7.4,新版本用法改了好多,可以参考官网。
答案仅供参考

你可能感兴趣的:(笔记)