實現一個類 Vue 的 MVVM 框架

原文地址:https://gmiam.com/post/evo.htmljavascript

Vue 一個 MVVM 框架、一個響應式的組件系統,經過把頁面抽象成一個個組件來增長複用性、下降複雜性html

主要特點就是數據操縱視圖變化,一旦數據變化自動更新全部關聯組件~vue

因此它的一大特性就是一個數據響應系統,固然有了數據還須要一個模板解析系統java

即 HTMLParse 幫咱們把數據模板生成最終的頁面,但每次數據變更都從新生成 HTML 片斷掛載到 DOM 性能確定慢的無法說node

因此還須要 Virtual DOM 把最少的變更應用到 DOM 上,以提高性能git

基本上述三項組裝到一塊兒也就出來了咱們本身的 Vue 框架 Evogithub

下面先介紹下 Virtual DOM segmentfault

所謂的 Virtual DOM 就是用 JS 來模擬 DOM 樹(由於 JS 操做比 DOM 快不少)app

每次數據變更用新生成的樹與以前的樹作比對,計算出最終的差別補丁到真正的 DOM 樹上框架

Vue 2.0 底層基於 Snabbdom 這個 Virtual DOM 作了優化與整合

具體能夠到這裏查看更多 https://github.com/snabbdom/s...

這個庫的主要特點是簡單、模塊化方便擴展與出色的性能

一個簡單例子

var snabbdom = require('snabbdom');
var patch = snabbdom.init([ // Init patch function with chosen modules
  require('snabbdom/modules/class').default, // makes it easy to toggle classes
  require('snabbdom/modules/props').default, // for setting properties on DOM elements
  require('snabbdom/modules/style').default, // handles styling on elements with support for animations
  require('snabbdom/modules/eventlisteners').default, // attaches event listeners
]);
var h = require('snabbdom/h').default; // helper function for creating vnodes

var container = document.getElementById('container');

var vnode = h('div#container.two.classes', {on: {click: someFn}}, [
  h('span', {style: {fontWeight: 'bold'}}, 'This is bold'),
  ' and this is just normal text',
  h('a', {props: {href: '/foo'}}, 'I\'ll take you places!')
]);
// Patch into empty DOM element – this modifies the DOM as a side effect
patch(container, vnode);

var newVnode = h('div#container.two.classes', {on: {click: anotherEventHandler}}, [
  h('span', {style: {fontWeight: 'normal', fontStyle: 'italic'}}, 'This is now italic type'),
  ' and this is still just normal text',
  h('a', {props: {href: '/bar'}}, 'I\'ll take you places!')
]);
// Second `patch` invocation
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state

不難看出 patch 就是一個模塊化的功能聚合,你也能夠根據核心的 Hook 機制來提供本身的功能模塊

而後經過 snabbdom/h 來建立 vnodes,最後用 patch 作更新處理

這個庫的代碼量不大,實現的很是靈活,有興趣的能夠讀讀源碼,另外也建議讀讀這篇文章 https://github.com/livoras/bl... 以更好的瞭解內部原理

不過從上面的語法能夠看出使用起來至關麻煩,因此咱們須要一種簡單的書寫方式來幫咱們解析成對應的語法規則

也就是要說的 HTMLParse

Vue 2.0 的 Parse 原型基於 John Resig 的 HTML Parser,這個 Parser 寫的很小巧,能夠到這裏瞭解 http://ejohn.org/blog/pure-ja...

基本的 HTML 解析用法

var results = "";
        
HTMLParser(html, {
  start: function( tag, attrs, unary ) {
    results += "<" + tag;

    for ( var i = 0; i < attrs.length; i++ )
      results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';

    results += (unary ? "/" : "") + ">";
  },
  end: function( tag ) {
    results += "</" + tag + ">";
  },
  chars: function( text ) {
    results += text;
  },
  comment: function( text ) {
    results += "<!--" + text + "-->";
  }
});

return results;

能夠看出它把 HTML 解析後對應的節點數據都傳入了處理函數,Vue 在它的基礎上作了升級與優化處理,在拿到對應的節點數據後作一些本身的解析處理,如 分析 v-if、v-for、v-on 等屬性作指令處理,也就出來了 Vue 的模板系統~

下面在說下響應系統

數據響應主要是依據 ES5 的 getter 與 setter 來作數據變化的鉤子處理,好比下面

Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: ()=>{
    // some handle
    return val
  },
  set: newVal => {
    if(newVal === val)
      return
    val = newVal
    //some handle
  }
})

這樣取值與賦值的過程當中均可以作一些咱們本身的處理,好比 set 的時候咱們能夠判斷值是否真的發生了變化,變化了能夠觸發咱們的從新渲染函數,作虛擬 DOM 比對處理更新界面

不過說明下並非一旦有數據變更咱們就要作從新渲染,看這個例子

new Vue({
      template: `
        <div>
          <section>
            <span>name:</span> {{name}}
          </section>
          <section>
            <span>age:</span> {{age}}
          </section>
        <div>`,
      data: {
        name: 'js',
        age: 24,
        height: 180
      }
    })

    setTimeout(function(){
      demo.height = 181
    }, 3000)

能夠看到 height 的變更與咱們的模板徹底無關,若是作重渲染會形成浪費,因此 Vue 作了一個收集依賴

Vue 在第一次渲染的時候會讀取須要的數據,因此它在 get 的時候作了手腳(依賴收集),後面只有依賴的數據變更纔會觸發重渲染

想更詳細的瞭解數據響應的能夠看看這個 https://segmentfault.com/a/11...

不過 ES5 的 setter、getter,使用與處理起來仍是有些麻煩與不便

因此數據方面我選擇了這個 https://github.com/nx-js/obse... 使用 Proxy 的庫作響應處理(畢竟如今不考慮兼容性~)

實現原理與上面的差很少,只不過更簡單,功能更強一些~

上面就是咱們主要參考的技能點,讓咱們加些代碼把它們連起來,這樣本身的框架就出來了~

最終的實現代碼在這裏 https://github.com/ygm125/evo

evo = easy + vue + o,快來幫我 star 吧~

下面來個例子,跑起來

<div id="app">
    <div :message="message">{{ message }}</div>

    <a v-for="(item,index) in list" @click="popMsg(item.text)">{{index}}、{{item.text}}</a>

    <my-component :message="message"></my-component>

    <div v-if="first">first</div>
    <div v-else>not</div>
</div>
<script src="../dist/evo.js"></script>
<script>

    var Child = {
        data: {
            text: 'component'
        },
        template: '<div>A custom {{text}} {{message}}!</div>'
    }

    var app = new Evo({
        components: {
            'my-component': Child
        },
        el: "#app",
        data: {
            first: true,
            message: "Hello Evo!",
            list: [{
                text: "Im one"
            }, {
                text: "Im two"
            }]
        },
        methods: {
            popMsg(msg) {
                alert(msg)
            }
        }
    })

    setTimeout(function(){
        app.message = 'HI'
    },1000)

</script>

固然實現一個完整的東西仍是有不少路要走的,但願你們都能越走越遠,也能越走越近~

相關文章
相關標籤/搜索