简易 Vue 构建--篇三

模拟真实DOM转换成虚拟DOM(省略了AST语法树过程);
VNode数据替换过程;
VNode转换为真实DOM,更新到视图过程。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root">
      <div class="c1">
        <div title="tt1" id="id">233</div>
        <div title="tt2">{{ age }}</div>
        <div title="tt3">{{ gender }}</div>
        <ul>
          <li>111{{name.firstName}}-{{age}}</li>
          <li>{{name.lastName}}</li>
          <li>3</li>
        </ul>
      </div>
    </div>
    <script>
      /**
       *  #0.* 属于重要辅助函数
       */

      // 如何得到虚拟dom
      // 即:将真实的DOM元素转换成js对象
      // 构建虚拟dom对象
      class VNode {
        constructor(tag, data, value, type) {
          this.tag = tag && tag.toLowerCase() // 标签名
          this.data = data // 属性
          this.value = value // 标签内容
          this.type = type // 节点类型
          this.children = [] // 子节点
        }
        // 添加虚拟dom子节点
        appendChild(vnode) {
          this.children.push(vnode)
        }
      }
      // TODO:#0.1 将真实dom转换成虚拟dom,这个函数当做 compiler 函数
      function getVNode(node) {
        const type = node.nodeType
        let _vnode = null
        if (type === 1) {
          // 标签节点
          const tag = node.nodeName // 标签名
          const attrs = node.attributes // attributes 真实dom里存放标签属性 数组对象
          const data = {} // 虚拟dom中 属性就只是对象
          for (let i = 0; i < attrs.length; i++) {
            data[attrs[i].nodeName] = attrs[i].nodeValue // 数组每一项
          }
          const value = undefined
          _vnode = new VNode(tag, data, value, type)
          const children = node.childNodes
          // 子节点
          for (let i = 0; i < children.length; i++) {
            _vnode.appendChild(getVNode(children[i]))
          }
        } else if (type === 3) {
          // 文本节点
          _vnode = new VNode(undefined, undefined, node.nodeValue, type)
        }
        // 将每一个虚拟节点返回
        return _vnode
      }

      // TODO:#0.2 将虚拟 DOM 转换成真正的 DOM
      function parseVNode(vnode) {
        let oDom = null
        // 标签节点
        if (vnode.type === 1) {
          oDom = document.createElement(vnode.tag)
          const data = vnode.data
          // 属性节点
          Object.keys(data).forEach((key) => {
            let attrName = key
            let attrValue = data[key]
            oDom.setAttribute(attrName, attrValue)
          })
          // 子节点
          for (let i = 0; i < vnode.children.length; i++) {
            oDom.appendChild(parseVNode(vnode.children[i]))
          }
        } else if (vnode.type === 3) {
          // 创建文本节点
          oDom = document.createTextNode(vnode.value)
        }
        return oDom
      }

      // 对象解析函数 a.b.c.d这种格式
      function getValueByKey(obj, str) {
        const arr = str.split('.')
        let prop = null
        while ((prop = arr.shift())) {
          obj = obj[prop]
        }
        return obj
      }

      const reg = /\{\{(.+?)\}\}/g
      // TODO:#0.3 将 带有 坑的 Vnode 与数据 data 结合, 得到 填充数据的 VNode: 模拟 AST -> VNode
      function combine(vnode, data) {
        let _type = vnode.type
        let _data = vnode.data
        let _value = vnode.value
        let _tag = vnode.tag
        let _children = vnode.children

        let _vnode = null

        if (_type === 3) {
          // 文本节点

          // 对文本处理
          _value = _value.replace(reg, function (_, g) {
            return getValueByKey(data, g.trim())
          })

          // 重新创建虚拟dom对象
          _vnode = new VNode(_tag, _data, _value, _type)
        } else if (_type === 1) {
          // 元素节点
          // 重新创建虚拟dom对象
          _vnode = new VNode(_tag, _data, _value, _type)
          _children.forEach((_subvnode) => _vnode.appendChild(combine(_subvnode, data)))
        }

        return _vnode
      }

      function MyVue(obj) {
        // 习惯: 内部的数据使用下划线 开头, 只读数据使用 $ 开头
        // 数据
        this._data = obj.data

        // 根元素
        let el = document.querySelector(obj.el) // vue 是字符串, 这里是 DOM
        // 根模板
        this._template = el
        // 为什么要拿父节点
        this._parent = this._template.parentNode

        // TODO:#1
        this.mount()
      }

      MyVue.prototype.mount = function () {
        // TODO:#2 需要提供一个 render 方法: 生成 虚拟 DOM
        // 这里得到了一个未执行的函数  该函数执行后的结果是合并数据后的VNode
        this.render = this.createRenderFunction()
        // 视图更新
        this.mountComponent()
      }

      MyVue.prototype.mountComponent = function () {
        let mount = () => {
          // TODO:#4
          // this.render() 会得到数据合并后的VNode
          // 更新函数执行得到真实DOM并渲染到页面
          this.update(this.render())
        }
        // TODO:#3
        mount.call(this) // 本质应该交给 watcher 来调用
      }
      // 渲染函数
      MyVue.prototype.createRenderFunction = function () {
        // #0.1  VNode 模拟 AST   获得VNode
       	// 函数柯里化 缓存 ast
        const ast = getVNode(this._template)

        return function () {
          // #0.3 将 带有 坑的 VNode 转换为 待数据的 VNode
          return combine(ast, this._data)
        }
      }

      // 更新视图
      MyVue.prototype.update = function (vnode) {
        // 转换成真实DOM
        let node = parseVNode(vnode)

        this._parent.replaceChild(node, document.querySelector('#root'))
      }

      // 创建实例
      new MyVue({
        el: '#root',
        data: {
          name: { firstName: 'wang', lastName: 'wu' },
          age: 18,
          gender: '男'
        }
      })
    </script>
  </body>
</html>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值