简易版 Vue 2.0.2, 增加功能

增加 $mount(el) ,作用:

  • 获取宿主
  • updateComponet
  • 创建 Watcher

Watcher 改造

  • 能够传入 updateComponet
  • dep 对应关系 1:n

测试代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app"></div>
<script src="./kvue.js"></script>
<script>
  const app = new Kvue({
    el: '#app',
    data: {
      counter: 1,
    },
    render(h) {
      // const d1 = document.createElement('div')
      // d1.id = 'app'
      // const p1 = document.createElement('p')
      // p1.textContent = this.counter
      // d1.appendChild(p1)
      // return d1

      // 使用 h 函数
      return h('div', {id: 'app', title: this.counter}, [
        h('p', null, this.counter + '')
      ])
    }
  })
  setInterval(() => {
    app.counter++
  }, 1000)
</script>
</body>
</html>

升级版的简易版 Vue 2.0

/**
 增加 `$mount(el)` ,作用:

 - 获取宿主
 - `updateComponet`
 - 创建 `Watcher`

 `Watcher` 改造

 - 能够传入 `updateComponet`
 - 和 `dep` 对应关系 `1:n`
 */


/**
 * 1、对 data 选项进行响应式处理
 * 2、编辑模板
 * 3、指令和事件
 * 4、依赖收集
 */
class Kvue {
  constructor(options) {
    this.$options = options
    this.$data = options.data
    // 响应式
    observe(this.$data)

    // 代理下
    proxy(this)

    // compile 编辑模板
    // new Compile(options.el, this)

    if (options.el) {
      this.$mount(options.el)
    }
  }

  $mount(el) {
    // 获取宿主
    this.$el = document.querySelector(el)

    // 创建 updateComponent
    const updateComponent = () => {
      // 获取渲染函数
      const {render} = this.$options
      const vnode = render.call(this, this.$createElement)
      // vnode 变成 dom
      this._update(vnode)

      // const parent = this.$el.parentElement
      // parent.insertBefore(el, this.$el.nextSibling)
      // parent.removeChild(this.$el)
      // this.$el = el
    }

    // 创建根组件对应的 Watcher
    new Watcher(this, updateComponent)
  }

  // h 函数
  $createElement(tag, props, children) {
    return {tag, props, children}
  }

  _update(vnode) {
    const prevVnode = this._vnode

    if (!prevVnode) {
      // init
      this.__patch__(this.$el, vnode)

    } else {
      // update
      this.__patch__(prevVnode, vnode)
    }
  }

  __patch__(oldVnode, vnode) {
    // dom
    if (oldVnode.nodeType) {
      const parent = oldVnode.parentElement
      const refElm = oldVnode.nextSibling
      const el = this.createElm(vnode)
      parent.insertBefore(el, refElm)
      parent.removeChild(oldVnode)

      // 保存当前 vnode
      this._vnode = vnode
    } else {
      // update
      // 获取 el
      const el = vnode.el = oldVnode.el
      //相同节点
      if (oldVnode.tag === vnode.tag) {
        // props 新旧对比
        const oldProps = oldVnode.props || {}
        const newProps = vnode.props || {}

        // 属性更新
        for (const key in oldProps) {
          const oldValue = oldProps[key]
          const newValue = newProps[key]
          if (oldValue !== newValue) {
            el.setAttribute(key, newValue)
          }
        }

        // 属性删除
        for (const key in oldProps) {
          if (!(key in newProps)) {
            el.removeAttribute(key)
          }
        }

        // children
        const oldCh = oldVnode.children
        const newCh = vnode.children
        if (typeof newCh === 'string') {
          if (typeof oldCh === 'string') {
            // 新旧文本都存在且不同
            if (oldCh !== newCh) {
              el.textContent = newCh
            }
          } else {
            // 旧的没有文本
            el.textContent = newCh
          }
        }else{
          // children
          if (typeof oldCh === 'string') {
            el.innerHTML = ''
            newCh.forEach(child => this.createElm(child))
          } else {
            // updateChildren
            this.updateChildren(el, oldCh, newCh)
          }
        }
      }
    }
  }

  createElm(vnode) {
    const el = document.createElement(vnode.tag)
    // props
    if (vnode.props) {
      for (const key in vnode.props) {
        const value = vnode.props[key]
        el.setAttribute(key, value)
      }
    }

    // children
    if (vnode.children) {
      if (typeof vnode.children === 'string') {
        // text
        el.textContent = vnode.children
      } else {
        // 递归
        vnode.children.forEach(n => {
          const child = this.createElm(n)
          el.appendChild(child)
        })
      }
    }

    vnode.el = el
    return el
  }

  updateChildren(parentElm, oldCh, newCh) {
    const len = Math.min(oldCh.length, newCh.length)
    for (let i = 0; i < len; i++) {
      this.__patch__(oldCh[i], newCh[i])
    }

    if (newCh.length > oldCh.length) {
      // add
      newCh.slice(len).forEach(child => {
        const el = this.createElm(child)
        parentElm.appendChild(el)
      })
    } else if (newCh.length < oldCh.length) {
      // remove
      oldCh.slice(len).forEach(child => {
        const el = this.createElm(child)
        parentElm.removeChild(el)
      })
    }
  }
}


// 数据响应式
function defineReactive(obj, key, val) {
  // 递归下,兼容 obj[key] = {a:10}
  observe(obj[key])

  // 创建 Key、Dep、Watcher 的依赖
  const dep = new Dep()

  Object.defineProperty(obj, key, {
    get() {
      console.log('get', key)
      // 依赖收集
      Dep.target && dep.addDep(Dep.target)
      return val
    },
    set(newVal) {
      if (newVal !== val) {
        console.log('set', key)
        // 如果 newVal 是对象,做响应式处理,比如 obj.key = {a:10}
        observe(newVal)
        val = newVal

        // watcher 更新
        // watchers.forEach(w => w.update())
        dep.notify()
      }
    }
  })
}

// 遍历 obj, 对所有属性做响应式
function observe(obj) {
  if (typeof obj !== 'object' || obj == null) {
    return
  }

  new Observer(obj)
}


// 代理,实现的目的 vm.$data.counter 可以通过 vm.counter 拿到
function proxy(vm) {
  Object.keys(vm.$data).forEach(key => {
    Object.defineProperty(vm, key, {
      get() {
        return vm.$data[key]
      },
      set(v) {
        vm.$data[key] = v
      }
    })
  })
}


// 根据不同类型做响应式处理
class Observer {
  constructor(value) {
    this.value = value

    if (Array.isArray(value)) {
      // todo
    } else {
      this.walk(value)
    }
  }

  walk(obj) {
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key, obj[key])
    })
  }
}

// 解析模板,1、处理插值,2、处理指令和事件,3、以上两者的初始化和更新

class Compile {
  constructor(el, vm) {
    this.$vm = vm
    this.$el = document.querySelector(el)

    if (this.$el) {
      this.compile(this.$el)
    }
  }

  compile(el) {
    // 遍历 el 子节点,判断类型做相应的处理
    const childNodes = el.childNodes

    childNodes.forEach(node => {
      if (node.nodeType === 1) {
        // 元素
        // console.log('元素', node.nodeName)
        // 处理 指令和事件
        const attrs = node.attributes
        Array.from(attrs).forEach(attr => {
          // k-xxx="abc"
          const attrName = attr.name
          const exp = attr.value
          if (attrName.startsWith('k-')) {
            const dir = attrName.substring(2)
            this[dir] && this[dir](node, exp)
          }
        })
      } else if (this.isInter(node)) {
        // 文本 插值表达式
        // console.log('插值', node.textContent)
        this.compileText(node)
      }

      // 递归下
      if (node.childNodes) {
        this.compile(node)
      }
    })
  }

  // 高级函数执行 dir
  update(node, exp, dir) {
    // 1、初始化
    const fn = this[dir + 'Updater']
    fn && fn(node, this.$vm[exp])

    // 2、更新操作
    new Watcher(this.$vm, exp, function (val) {
      fn && fn(node, val)
    })
  }

  // 对应的 k-text 的指令
  text(node, exp) {
    // node.textContent = this.$vm[exp]
    this.update(node, exp, 'text')
  }

  textUpdater(node, value) {
    node.textContent = value
  }

  // 编译 {{xxx}}文本
  compileText(node) {
    // node.textContent = this.$vm[RegExp.$1]
    this.update(node, RegExp.$1, 'text')
  }

  // 对应的 k-html 的指令
  html(node, exp) {
    // node.innerHTML = this.$vm[exp]
    this.update(node, exp, 'html')
  }

  htmlUpdater(node, value) {
    node.innerHTML = value
  }

  // 是否插值表达式
  isInter(node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }
}

// 监督器:负责依赖更新,有多少个变量就有多少个 watcher
// const watchers = []

class Watcher {
  constructor(vm, fn) {
    this.vm = vm
    // this.key = key
    // this.updateFn = updateFn
    this.getter = fn

    // watchers.push(this)
    // Dep.target = this
    // 相当于调用了一次 key 的 get 方法进行依赖收齐
    // this.vm[this.key]
    // Dep.target = null

    this.get()
  }

  get() {
    Dep.target = this
    this.getter.call(this.vm)
    Dep.target = null
  }

  // 被 Dep 调用的
  update() {
    // 执行实际的更新操作
    // this.updateFn.call(this.vm, this.vm[this.key])
    this.get()
  }
}

// 实现 Dep,有多少个 key 就有多少个 dep
class Dep {
  constructor() {
    // this.deps = []
    this.deps = new Set()
  }

  addDep(dep) {
    // 这里的 dep 其实就是 watcher
    // this.deps.push(dep)
    this.deps.add(dep)
  }

  notify() {
    // 这里的 dep 其实就是 watcher
    this.deps.forEach(dep => dep.update())
  }
}