vue源码模拟02-生成虚拟dom、渲染数据、虚拟dom转真实dom

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="root">
    11
    {{name}}
    <p>11</p>
  </div>

  <script>
    let el = document.querySelector("#root")

    class Vnode {
      constructor(tag, data, value, type) {
        this.tag = tag
        this.data = data
        this.value = value
        this.type = type
        this.children = []
      }
      appendChild(vnode) {
        this.children.push(vnode)
      }
    }
    class Vue {
      constructor(options) {
        this._data = options.data
        this._el = document.querySelector(options.el)
        this._vnode = null
        this._ast = null
        this.mount()
      }
      mount() {
        this.render = this.createRenderFn()
        this.mountComponent()
      }
      //柯里化-用来缓存 AST抽象语法树的
      //返回render函数,调用render函数的话会生成vnode
      createRenderFn() {
        this._ast = this.getVnode(this._el)
        //将抽象语法树 + data 结合生成 vnode
        return (data) => {
          let _tmp = this.combine(this._ast, data)
          return _tmp
        }
      }
      mountComponent() {
        const mount = () => {
          this.update(this.render(this._data))
        }
        mount.call(this)
      }
      //更新
      update(vnode) {
        this.updated(this.vnodeToReal(vnode))
      }
      updated(realDOM) {
        this._el.parentNode.replaceChild(realDOM, this._el)
      }
      //data和 vnode结合 
      combine(vnode) {
        if (vnode.type == 1) {
          for (let i = 0; i < vnode.children.length; i++) {
            this.combine(vnode.children[i])
          }
        } else if (vnode.type == 3) {
          vnode.value = vnode.value.replace(/\{\{(.+?)\}\}/g, (_, $1) => {
            return this.getValueByPath($1.trim(), this._data)
          })
        }
        return vnode
      }
      //格式化 data 中的 数据路径
      getValueByPath(str, data) {
        let arr = str.replace(/\[/g, '.').replace(/\]/, '').split('.')
        return arr.reduce((total, item, index) => {
          return total[item]
        }, data)

      }
      //真实DOM转虚拟DOM
      getVnode(template) {
        let type = template.nodeType
        let vnode = null
        if (type == 1) {
          let tag = template.nodeName
          let attrs = template.attributes
          let attrsObj = {}
          for (let i = 0; i < attrs.length; i++) {
            attrsObj[attrs[i].nodeName] = attrs[i].nodeValue
          }
          vnode = new Vnode(tag, attrs, undefined, type)
          let childNodesList = template.childNodes
          for (let i = 0; i < childNodesList.length; i++) {
            vnode.appendChild(this.getVnode(childNodesList[i]))
          }
        } else if (type == 3) {
          let value = template.nodeValue
          vnode = new Vnode(undefined, undefined, value, type)
        }
        return vnode
      }
      //虚拟DOM转真实DOM
      vnodeToReal(vnode) {
        let el
        if (vnode.type == 1) {
          el = document.createElement(vnode.tag)
          // for (let key in vnode.data || {}) {
          //   el.setAttribute(key, vnode.data[key])
          // }
          for (let i = 0; i < vnode.children.length; i++) {
            el.appendChild(this.vnodeToReal(vnode.children[i]))
          }
        } else if (vnode.type == 3) {
          el = document.createTextNode(vnode.value)
        }
        return el
      }
      
    }


    new Vue({
      el: '#root',
      data: {
        name: 'Ren'
      }
    })
  </script>
</body>

</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RxnNing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值