使用ES6新特性實現簡單的MVVM(1)--數據驅動

嘗試使用es6新特性,本身來實現一個mvvm及vue的各類特性。
相關代碼放在github,會持續更新,歡迎賞個star。
本篇文章爲系列文章的第一篇,會比較容易理解,後續會持續更新後面的記錄。
文章首發於本人博客html

最簡單的watcher

從開始接觸Vue開始,咱們便對它的「數據響應」讚歎不絕,那麼咱們首先,來實現一個最簡單的watcher,來監聽數據,以進行對應的操做,相似後續會涉及的dom操做等。vue

Proxy

咱們都知道,Vue使用Object.defineProperty來進行數據監聽,監聽obj的get和set方法。在ES6中,Proxy能夠攔截某些操做的默認行爲,也就是對目標對象的訪問進行攔截,過濾和改寫。咱們能夠利用這個特性來實現對數據的監聽:git

const watcher = (obj, fn) => {
  return new Proxy(obj, {
    get (target, prop, receiver) {
      return Reflect.get(target, prop, receiver)
    },

    set (target, prop, value) {
      const oldValue = Reflect.get(target, prop, receiver)
      const result = Reflect.set(target, prop, value)

      fn(value, oldValue)

      return result
    }
  })
}

結果:es6

let obj = watcher({ a: 1 }, (val, oldVal) => {
  console.log('old =>> ', oldVal)
  console.log('new =>> ', val)
})

obj.a = 2
// old =>> , 1
// new =>> , 2

簡單的Dom操做

咱們已經能夠對簡單的數據操做進行監聽(雖然還有各類問題),接下來,咱們只要在監聽到dom後進行數據操做便可。解析模板什麼的咱們就先不作了,咱們能夠繼續利用Proxy實現一個dom輔助函數:github

const dom = new Proxy({}, {
  get (target, tagName) {
    return (attrs = {}, ...childrens) => {
      // 建立節點
      const elem = document.createElement(tagName)

      // 添加attribute
      attrs.forEach(attr => elem.addAttribute(attr, attrs[attr])

      // 添加子元素
      childrens.forEach(child => {
        const child = typeof child === 'string'
          ? document.createTextNode(child) 
          : child

        elem.appendChild(child)
      })

      return elem
    }
  }
})

也就是說,咱們爲dom的各屬性進行監聽,當訪問對應的節點時,咱們建立而且爲他添加各類屬性等:數組

dom.div(
  {class: 'wrap'}, 
  'helloworld',
  dom.a({
    href: 'https://www.360.cn'
  }, 'welcome to 360')
)

// 輸出
<div class="wrap">
  helloworld
  <a href="https://www.360.cn">welcome to 360</a>
</div>

拼接基礎框架

咱們在這裏給咱們的這個小架子起名爲'W',讓它能夠真正的運行起來。相似Vue的語法,咱們須要在進行實例化的時候,watch咱們的data,而且更新dom。相似這樣:app

const vm = new W({
  el: 'body',
  data () {
    return {
      msg: 'hello world'
    }
  },
  render () {
    return dom.div({
      class: 'wrap'
    },
      dom.a({
        href: 'http://www.360.cn'
      }, this.msg)
    )
  }
})

所以,咱們須要實現這樣一個類,來處理咱們的參數,並進行實例的初始化,監聽,以及渲染控制等。框架

export default class W {
  constructor (config) {}

  /**
   * observe data
   */
  _initData () {}

  /**
   * 渲染節點
   */
  _renderDom () {}
}

初始化數據

首先,咱們進行數據初始化,將數據置爲observable,在對其修改的時候進行監聽:dom

import watcher from './data.js'

class W {
  constructor (config) {
    const { data = () => {} } = config

    this._config = config
    this._initData(data)

    return this._vm
  }
}

_initData (data) {
  this._vm = watcher(Object.assign({}, this, data()), this._renderDom.bind(this))
}

在這裏咱們須要注意兩點:mvvm

  1. 咱們的data參數爲一個function
    這個緣由在vue官方文檔已經說過,當咱們直接使用對象的時候,不一樣的實例間會共享同一個對象,致使出現對一個組件進行修改,另外一個組件也進行修改的問題。具體能夠查看data-必須是函數

  2. 咱們返回的是this._vm而不是this
    咱們這裏作了兩步操做,首先將this與data進行合併,再將整個對象進行監聽,並賦值到_vm屬性上。

這樣,咱們經過new W()初始化的實例,則能夠訪問到咱們的data屬性及方法,而且具備數據驅動的特性了。

更新DOM

咱們已經爲watcher的回調添加了dom更新的事件,咱們只要在這裏執行render函數,並掛載到對應的el上便可:

const { render, el } = this._config
const targetEl = document.querySelector(el)
const renderDom = render()

targetEl.innerHTML = ''
targetEl.appendChild(renderDom)

綁定this

咱們會發現,咱們在config的render函數中,使用了this.msg來訪問data的msg屬性,所以咱們須要實如今各組件中,經過this能夠訪問到本實例的特性。我猜你已經想到了,咱們可使用bind,call和apply來實現它:

/**
 * 爲全部的函數綁定this
 */
bindVM () {
  const { _config } = this

  for(let key of Object.keys(_config)) {
    const val = _config[key]
    if (typeof(val) === 'function') {
      _config[key] = val.bind(this._vm)
    }
  }
}

測試

簡單的架子拼接完成,咱們來進行測試下咱們的成果,咱們須要實現兩點功能:

  1. 能夠按照咱們的render函數正常掛載,並可訪問到data上的數據

  2. 經過對實例進行修改,修改會自動更新到節點上

代碼:

const vm = new W({
  el: 'body',
  data () {
    return {
      msg: 'hello world'
    }
  },
  render () {
    return dom.div({
      class: 'wrap'
    },
      dom.a({
        href: 'http://www.360.cn'
      }, this.msg)
    )
  }
})

// 測試修改vm
setInterval(_ => {
  vm.msg = 'hello world =>>>' + new Date()
}, 1000)

結果:

結果

最基本的功能已經實現啦!

結語

本次咱們只實現了最最最簡單的數據驅動功能,後續還有不少須要進行處理,咱們也會對其一一進行梳理和實現,你們能夠持續關注下,例如:

  • 數組變更監聽

  • object深度監聽

  • 更新隊列

  • render過程當中記錄僅相關的屬性

  • 模板渲染

  • v-model

  • ...等等

相關代碼放在github,會持續更新,歡迎賞個star。

敬請期待!

相關文章
相關標籤/搜索