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 {
      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 || {};
    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();
      }
    })
  }

  var $main = $('#main');
  var $getDate = $('#getCurDate');
  // 设置为可监控对象
  var obj = observer({
    curDate: new Date()
  });
  // 进行监控
  watcher(obj, 'curDate', function() {
    $main.text(obj.curDate.toLocaleString());
  });

  $getDate.on('click', function() {
    obj.curDate = new Date();
  })

})
<div id="main">
</div>
<br/>
<button id="getCurDate">
  获取当前日期
</button>