Skip to content

mvvm

https://juejin.im/post/5b1fa77451882513ea5cc2ca?utm_source=gold_browser_extension

js
class Vue {
  constructor(opt) {
    this.opt = opt
    this.observe(opt.data)
    let root = document.querySelector(opt.el)
    this.compile(root)
  }
  // 为响应式对象data里的每一个key绑定一个观察者对象
  observe() {
    Object.keys(data).forEach(key => {
      let obv = new Observer()
      data['_' + key] = data[key]
      Object.defineProperty(data, key, {
        get() {
          Observer.target && obv.addSubNode(Observer.target)
          return data['_' + key]
        },
        set(newVal) {
          obv.update(newVal)
          data['_' + key] = newVal
        }
      })
    })
  }
  // 初始化页面,遍历DOM,收集每一个key的变化,随之调整位置,以观察者方法存放起来
  compile(node) {
    [].forEach.call(node.childNodes, child => {
      if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {
        let key = RegExp.$1.trim()
        child.innerHTML = child.innerHTML.replace(new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'gm'), this.opt.data[key])
        Observer.target = child
        this.opt.data[key]
        Observer.target = null
      } else if (child.firstElementChild) {
        this.compile(child)
      }
    })
  }
}
// 常规观察者类
class Observer {
  constructor() {
    this.subNode = []
  }
  addSubNode(node) {
    this.subNode.push(node)
  }
  update(newVal) {
    this.subNode.forEach(node => {
      node.innerHTML = newVal
    })
  }
}

重点分析,梳理一下这段代码的思路,顺便了解下MVVM闭包的艺术

  • observe函数 首先我们会对需要响应式的data对象进行for循环遍历,为data的每一个key映射一个观察者对象

    • 在ES6中,for循环每次执行,都可以形成闭包,因此这个观察者对象就存放在闭包中
    • 闭包形成的本质是内层作用域中堆地址的暴露,这里我们巧妙地用getter/setter函数暴露了for循环的观察者
  • compile函数 我们从根节点向下遍历DOM,遇到mustache形式的文本,则映射成data.key对应的值,同时记录到观察者中

    • 当遍历到{{xxx}}形式的文本时,我们正则匹配出其中的变量,将它替换成data中的值
    • 为了满足后续响应式的更新,将该节点存储在key对应的观察者对象中,我们用getter函数巧妙地操作了闭包
    • 在页面初次渲染后,后续的eventLoop中,如果修改了key的值,实际会通过setter触发观察者的update函数,完成响应式更新

共 20 个模块,1301 篇 Markdown 文档。