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) {
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>