vue实现(一)- 数据结构实现

vue原理解析(一)

最近对Vue的原理进行学习,并对其部分原理进行模拟。通过学习,我发现Vue在html的使用中,一共由三个部分组成:M - V - VM,和原始的MVC框架的不同,VUE把controller层转换成ViewModel-连接Model和View,“桥”。我在学习的过程中,模拟了VUE的构建结构,希望通过我的分析,可以让我们在后面对VUE源码的解析更加的通俗易懂。

vue代码结构分析

我们在HTML中引入框架,通过JS引入vue.js

<script type="text/javascript" src="../assets/js/vue.js"></script>

在HTML页面代码中,我们使用Vue的API

<div id="root">
        <div>
            <div>
                <p>{{name}} - {{message}}</p>
            </div>
        </div>
</div>

再在JS中,创建VUE实例

var outData={
     name: 'hello',
     message: 'world’
}
var app=new Vue({
     el:'#root',
     data:outData,
})

实现了如下效果
在这里插入图片描述

vue结构模拟

在模拟过程中,我们不需要引入JS,html代码也是和原来一致的只需要通过自己手写的方法,来模拟其结构。我们可以发现在VUE中,我们原始创建的DOM被生成的DOM替换了

在这里插入图片描述
我们可以发现,在浏览器中输出节点的时候,我们在调用Vue之前的节点,被替换了,生成后的节点被加载在了页面上。因此,我们需要模拟这种替换的过程
html

<div id="root">
        <div>
            <div>
                <p>{{name}} - {{message}}</p>
            </div>
        </div>
    </div>

js

<script>
   // 打印root
    console.log(root)
    // 获取元素的dom
    const dom = document.querySelector( '#root' )
    // 设置data 
    let data = {
        name: 'hello',
        message: 'world'
    }
    //对存在的Dom进行替换
    let complier = (template, data) => {
        // 用于替换{{}}中数据的正则
        const ruleForSupport = /\{\{(.+?)\}\}/g
        let children = template.childNodes
        for(let i = 0; i< children.length; i++) {
            let type = children[i].nodeType
            //对文本标签中的{{}}中的数据替换
            if(type == 3) {
                let value = children[i].nodeValue
                value = value.replace(ruleForSupport, (_, g) => {
                    console.log(1)
                    let value = data[g.trim()]
                    return value
                })
                console.log(value)
                children[i].nodeValue = value
                
            }else {
                // 对于非文本标签进行遍历
                complier(children[i], data)
            }
        }
    }
    // 通过copy节点保留原始节点,方便后面刷新操作的替换
    let copyedDom = dom.cloneNode(true)
    // 调用方法 对标签进行渲染
    complier(copyedDom, data)
    // 对root标签进行替换
    root.parentNode.replaceChild(copyedDom, root)
    // 打印修改后的root
    console.log(root)
</script>

效果如下
在这里插入图片描述
可以发现 我们已经简单实现了{{}}中信息的展示,但是还有一定的问题

  • vue 是通过虚拟dom来实现的

  • 我们只考虑了 如{{name}}的简单数据模式,并未考虑如{{person.name}}的对象数据类型

  • 未对代码进行整合

所以我们先对对象数据类型进行分析,现在我们希望通过自己写的类来实现类似

let app  = new pfVue({
    el: '#root',
    data: {
    name: 'ppppffff',
    message: 'ok'
    }
})

所以我们在页面中使用

 <div id="root">
        <div>
            <div>
                <p>{{name.firstName}} - {{message}} 
                --{{text.title.first}}</p>
            </div>
        </div>
    </div>

通过创建pfVue来模拟实现

<script>
  console.log(root)
  
  //对存在的Dom进行替换
  let complier = (template, data) => {
      // 用于替换{{}}中数据的正则
      const ruleForSupport = /\{\{(.+?)\}\}/g
      let children = template.childNodes
      for(let i = 0; i< children.length; i++) {
          let type = children[i].nodeType
          //对文本标签中的{{}}中的数据替换
          if(type == 3) {
              let value = children[i].nodeValue
              value = value.replace(ruleForSupport, (_, g) => {
                  let arr  = g.trim().split('.')
                  let getValueByKeli = createGetValueByPath(arr)
                  let arrValue = getValueByKeli(data)
                  console.log(arrValue)
                  return arrValue
              })
              children[i].nodeValue = value
          }else {
              // 对于非文本标签进行遍历
              complier(children[i], data)
          }
      }
  }
  // 利用递归获取当前参数的层级
  function getValue(value, array, index) {
      value = value[array[index]]
      if(index + 1 < array.length) {
          return getValue(value, array, index + 1) // 递归函数中需要返回值需要逐级返回 ***重点
      }else {
          console.log(value)
          return value
      }
  }
  // 利用循环获取当前参数的层级
  function getValue2( value, array) {
      let res = value
      for(let i = 0 ; i < array.length ; i ++) {
          res = res[array[i]]
      }
      return res
  }
  // 利用函数的柯里化
  function createGetValueByPath(path) {
      let res = path
      return function(value) {
          let prop 
          while(prop = res.shift()) {
              value = value[prop]
          }
          return value
      }
  }
  function pfVue(obj) {
      // 内部数据 _开头,只读通过$开头 类似Vue源码
      this._data = obj.data
      this._el = obj.el
      console.log(this._el)
      this._templaterDom =  document.querySelector( this._el )
      this.$parent = this._templaterDom.parentNode
      this.render()
  }
  // 渲染
  pfVue.prototype.render = function() {
      this.complier()
  }
  pfVue.prototype.complier = function() {
  //通过clone来的DOM进行操作 留下样本
      let _copyedDom = this._templaterDom.cloneNode(true)
      complier(_copyedDom, this._data)
      this.update(_copyedDom)
  }
  // 替换标签
  pfVue.prototype.update = function(real) {
      this.$parent.replaceChild(real, this._templaterDom)
  }
  let app  = new pfVue({
      el: '#root',
      data: {
          name: {
              firstName: 'aaaa'
          },
          message: 'ok',
          text: {
              title: {
                  first: '111'
              }
          }
      }
  })
  console.log(root)
</script>

到了这里我们已经解决了对象数据的问题,我们下面主要需要解决两个问题

  • 把页面中的DOM转换成虚拟DOM
  • 把虚拟节点加载到页面当中

虚拟DOM的实现

我们需要把页面中的DOM,全部转换成虚拟DOM数据,方便我们后面加载到页面中,所以我们需要先创建一个虚拟的DOM类,VNode

// 创建VNode类
// nodeName data content nodeType  children
class VNode  {
   constructor(nodeName, data, content, nodeType) {
       this.nodeName = nodeName && nodeName.toLowerCase();
       this.data = data;
       this.content = content;
       this.nodeType = nodeType;
       this.childNodes = [];
   }
   appendChildren(vnode) {
       this.childNodes.push(vnode)
   }
}

然后只需要把页面中的对象全部读取出来

/**
* 把节点转换成虚拟节点
*/
function readNode(node) {
   console.dir(node)
   let comNode ;
   const _nodeType = node.nodeType
   if(_nodeType === 1) {
       // 元素节点
       const _nodeAttrs = node.attributes
       const _nodeName = node.nodeName
       let data = {}
       // 对节点中的属性进行读取
       for( let i = 0; i < _nodeAttrs.length; i++ ) {
           data[_nodeAttrs[i].nodeName] = _nodeAttrs[i].nodeValue
       }
       console.log(data)
       comNode = new VNode( _nodeName, data, undefined, _nodeType )

       let childNodes = node.childNodes
       for(let i = 0; i < childNodes.length; i++) {
           comNode.appendChildren( ( readNode(childNodes[i]) ) )
       }
   }else if(_nodeType === 3) {
       // 文本节点
       comNode = new VNode( undefined, undefined, node.nodeValue, _nodeType )
   }
   return comNode 
}

通过上面的步骤我们已经实现了虚拟DOM的转换,接下来只需要对DOM进行挂载就可以了。

/**
* 对虚拟DOM进行渲染
*/
function complierNode(v) {
    console.dir(v)
    let $nodeType = v.nodeType
    let node = {}
    if($nodeType === 1) {
        node = document.createElement(v.nodeName)
        node.nodeType = $nodeType
        for(let item in v.data) {
            node.setAttribute( item, v.data[item] )                 
        }
        if(v.childNodes.length > 0) {
            let _children = v.childNodes //这里曾少写过一个Let导致递归错误 
            for( let i = 0; i < _children.length; i++ ) {
                let child = complierNode(_children[i])
                node.appendChild(child)
            }
        }
    }
    else if( $nodeType === 3 ) {
        node = document.createTextNode(v.content)
    }
    return node 
}
// 挂载DOM 
function mount() {
    let $DOM = document.querySelector( '#root' );
    let $v = readNode($DOM)
    let $compliedDOM =  complierNode($v)
    $DOM.parentNode.replaceChild( $compliedDOM, $DOM )
}

然后我们只需要调用mount方法,就可以实现虚拟DOM的转换了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值