SOURCE

console 命令行工具 X clear

                    
>
console
$(function() {
  function Listener() {
    this._listeners = [];
  }
  Listener.prototype.on = function(lt) {
    this._listeners.push(lt);
  }
  Listener.prototype.notify = function() {
    for (var i = 0,
    n = this._listeners.length; i < n; i++) {
      var lt = this._listeners[i];
      lt && lt.watch();
    }
  }

  Listener.watcher = null;
  Listener.watcherStack = [];
  Listener.pushWatcher = function(w) {
    if (Listener.watcher) {
      Listener.watcherStack.push(Listener.watcher)
    }
    Listener.watcher = w;
  }
  Listener.popWatcher = function() {
    Listener.watcher = Listener.watcherStack.pop();
  }

  function parsePath(path) {
    if (/[^\w.$]/.test(path)) {
      return
    }
    var segments = path.split('.');
    return function(obj) {
      for (let i = 0; i < segments.length; i++) {
        if (!obj) {
          return;
        }
        obj = obj[segments[i]]
      }
      return obj
    }
  }

  function Watcher(obj, expOrFn, watch) {
    this._watch = watch;
    this._obj = obj;
    this._func = (typeof expOrFn === 'function') ? expOrFn: parsePath(expOrFn);
    Listener.pushWatcher(this);
    try {
      this.value = this._func.call(obj, obj);
    } finally {
      Listener.popWatcher(this);
    }
  }
  Watcher.prototype.watch = function() {
    this._watch && this._watch();
  }

  function watcher(obj, expOrFn, watch) {
    return new Watcher(obj, expOrFn, watch);
  }

  function observer(value) {
    // 只介绍原理,不处理array类型等细节处理了
    if (Object.prototype.toString.call(value) !== '[object Object]' || value.__observer__) {
      return;
    }
    value.__observer__ = true;
    var keys = Object.keys(value);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(value, keys[i], value[keys[i]], listeners);
    }
    return value;
  }

  function defineReactive(obj, key, val) {
    var listeners = listeners || {};
    if (key == '__observer__') {
      return;
    }
    var property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable === false) {
      return
    }
    var listener = new Listener();
    var getter = property && property.get;
    var setter = property && property.set;
    // 子对象递归转换掉
    observer(val, listeners);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function() {
        const value = getter ? getter.call(obj) : val;
        if (Listener.watcher) { // 将观察者注册到监听器中
          listener.on(Listener.watcher);
        }
        return value
      },
      set: function(newVal) {
        const value = getter ? getter.call(obj) : val
        if (newVal === value || (newVal !== newVal && value !== value)) {
          return
        }
        if (setter) {
          setter.call(obj, newVal)
        } else {
          val = newVal
        }
        observer(newVal);
        listener.notify();
      }
    })
  }

  function formatJson(el, str, data, watcher) {
    var fn = !/\W/.test(str) ? _formatJson_cache[str] = _formatJson_cache[str] || $formatJson(document.getElementById(str).innerHTML) : new Function("obj", "watcher", "refresh", "var p=[],print=function(){p.push.apply(p,arguments);};" + "watcher(obj, function(obj) { with(obj){ p.push('" + str.replace(/[\r\t\n]/g, " ")
      .split("<%").join(String.fromCharCode(9))
      .replace(/((^|%>)[^\t]*)'/g, "$1\r")
      .replace(/\t=(.*?)%>/g, "',$1,'")
      .split(String.fromCharCode(9)).join("');")
      .split("%>").join("p.push('")
      .split("\r").join("\\'") + "');} }, refresh); return p.join('');");
    el.innerHTML = data ? fn(data, watcher||function(obj, func){
      func.call(null, data);
    }, function() {
      formatJson(el, str, data);
    }) : fn;
  }
  var tpl = function(el, str, data, helpers) {
    var nStr = str;
    nStr = nStr.replace(/{{=(.*?)}}/g, '<%= $1 %>');
    nStr = nStr.replace(/{{/g, '<%');
    nStr = nStr.replace(/}}/g, '%>');
    data._ = $.extend({}, helpers || {});
    formatJson(el, nStr, data, watcher);
  }

  function VM(opts) {
    opts = opts || {};
    var self = this;
    self.data = observer(opts.data); // 将data设置为监控对象
    self._el = document.getElementById(opts.el);
    self._tpl = self._el.innerHTML.replace('&lt;', '<').replace('&gt;', '>').replace('&quot;', '"').replace('&amp;', '&');
    self._render = function() {
      tpl(self._el, self._tpl, this.data);
    }
    self._render(); // 模版渲染
  }

  // 将vm绑定到具体视图中去
  var vm = new VM({
    el: 'main',
    data: {
      list: [],
      curDate: new Date().toLocaleString()
    }
  })

  $('#getCurDate').on('click', function() {
    var date = new Date().toLocaleString();
    vm.data.curDate = date;
    vm.data.list = vm.data.list.concat([date]); // 演示代码列表类型没有处理,靠重新设置触发一次
  })

})
<div id="main">
  {{=curDate}}
  <ul>
    {{ for(var i=0,n=list.length;n>i;i++) { }}
    <li>{{=list[i]}}</li>
    {{ } }}
    <ul>
</div>
<br/>
<button id="getCurDate">
  获取当前日期
</button>