实现一个类 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>

固然实现一个完整的东西仍是有不少路要走的,但愿你们都能越走越远,也能越走越近~

相关文章
相关标签/搜索