vue2源码简单实现stage3

Posted 自在三水一方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2源码简单实现stage3相关的知识,希望对你有一定的参考价值。

在上一节中我们已经将代码整理了一下,这一节我们要做的是比较新旧dom,然后通过diff算法判断虚拟dom时候有变化,是否需要刷新视图。为了让代码更加清晰,我们这边还是做了简化,假设父节点下只有一个元素。并且手动调用更新方法,暂时不做数据双向绑定更新视图。

理解patch函数

不管是第一次挂载到视图还是后续的手动更新,我们都要触发构建新的dom,这时候我们可以将这个逻辑写到update的函数里面,但是 要根据不同的情况触发不同的更新逻辑,所以用到了patch函数

;(function () 

  function vnode (tag, data, children, text, elm) 
    this.tag = tag;
    this.data = data;
    this.children = children;
    this.text = text;
    this.elm = elm;
  

  function normalizeChildren (children) 
    if (typeof children === \'string\') 
      return [createTextVNode(children)]
    
    return children
  

  function createTextVNode (val) 
    return new vnode(undefined, undefined, undefined, String(val))
  

  function createElement (tag, data, children) 
    return new vnode(tag, data, normalizeChildren(children), undefined, undefined);
  

  function createElm (vnode) 
    var tag = vnode.tag;
    var data = vnode.data;
    var children = vnode.children;

    if (tag !== undefined) 
      vnode.elm = document.createElement(tag);

      if (data.attrs !== undefined) 
        var attrs = data.attrs;
        for (var key in attrs) 
          vnode.elm.setAttribute(key, attrs[key])
        
      

      if (children) 
        createChildren(vnode, children)
      
     else 
      vnode.elm = document.createTextNode(vnode.text);
    

    return vnode.elm;
  

  function createChildren (vnode, children) 
    for (var i = 0; i < children.length; ++i) 
      vnode.elm.appendChild(createElm(children[i]));
    
  

  function sameVnode (vnode1, vnode2) 
    return vnode1.tag === vnode2.tag
  

  function emptyNodeAt (elm) 
    return new vnode(elm.tagName.toLowerCase(), , [], undefined, elm)
  

  function patchVnode (oldVnode, vnode) 
    var elm = vnode.elm = oldVnode.elm;
    var oldCh = oldVnode.children;
    var ch = vnode.children;

    if (!vnode.text) 
      if (oldCh && ch) 
        updateChildren(oldCh, ch);
      
     else if (oldVnode.text !== vnode.text) 
      elm.textContent = vnode.text;
    
  

  function updateChildren (oldCh, newCh) 
    // 假设每一个元素下面只有一个子元素
    if (sameVnode(oldCh[0], newCh[0])) 
      patchVnode(oldCh[0], newCh[0])
     else 
      patch(oldCh[0], newCh[0])
    
  

  function patch (oldVnode, vnode) 
    var isRealElement = oldVnode.nodeType !== undefined; // virtual node has no `nodeType` property
    if (!isRealElement && sameVnode(oldVnode, vnode)) 
      patchVnode(oldVnode, vnode);
     else 
      if (isRealElement) 
        oldVnode = emptyNodeAt(oldVnode);
      
      var elm = oldVnode.elm;
      var parent = elm.parentNode;
      
      createElm(vnode);

      parent.insertBefore(vnode.elm, elm);
      parent.removeChild(elm);
    

    return vnode.elm
  

  function initData (vm) 
    var data = vm.$data = vm.$options.data;
    var keys = Object.keys(data);
    var i = keys.length
    // 代理之后可以直接使用this.key 而不是this.data.key
    while (i--) 
      proxy(vm, keys[i])
    
  

  function proxy (vm, key) 
    Object.defineProperty(vm, key, 
      configurable: true,
      enumerable: true,
      get: function () 
        return vm.$data[key]
      ,
      set: function (val) 
        vm.$data[key] = val
      
    )
  

  function Vue (options) 
    var vm = this;
    vm.$options = options;
    
    initData(vm);
    vm.mount(document.querySelector(options.el))
  

  Vue.prototype.mount = function (el) 
    var vm = this;
    vm.$el = el;
    vm.update(vm.render())
  

  Vue.prototype.update = function (vnode) 
    var vm = this;
    var prevVnode = vm._vnode;
    vm._vnode = vnode;
    if (!prevVnode) 
      vm.$el = vm.patch(vm.$el, vnode);
     else 
      vm.$el = vm.patch(prevVnode, vnode);
    
  

  Vue.prototype.patch = patch;

  Vue.prototype.render = function () 
    var vm = this;
    return vm.$options.render.call(vm)
  

  var vm = new Vue(
    el: \'#app\',
    data: 
      message: \'Hello world\',
      isShow: true
    ,
    render () 
      return createElement(
        \'div\',
        
          attrs: 
            \'class\': \'wrapper\'
          
        ,
        [
          this.isShow
          ? createElement(
            \'p\',
             
              attrs: 
                \'class\': \'inner\'
              
            ,
            this.message
          )
          : createElement(
            \'h1\',
             
              attrs: 
                \'class\': \'inner\'
              
            ,
            \'Hello world\'
          )
        ]
      )
    
  )

  // test
  setTimeout(function () 
    vm.message = \'Hello\';
    vm.update(vm.render())
  , 1000)

  setTimeout(function () 
    vm.isShow = false;
    vm.update(vm.render())
  , 2000)
)();

diff算法简介

diff算法一直是比较难的,想深入了解可以参考
[组件更新]https://ustbhuangyi.github.io/vue-analysis/v2/reactive/component-update.html#新旧节点相同

以上是关于vue2源码简单实现stage3的主要内容,如果未能解决你的问题,请参考以下文章

vue2.0 代码功能片段

vue2.0源码分析之理解响应式架构

Vue2仿网易云风格音乐播放器(附源码)

用Vue2.0实现简单的分页及跳转

Vue2源码解读

vue2源码-- computed 计算属性的实现