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) {
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);
self._el = document.getElementById(opts.el);
self._tpl = self._el.innerHTML.replace('<', '<').replace('>', '>').replace('"', '"').replace('&', '&');
self._render = function() {
tpl(self._el, self._tpl, this.data);
}
self._render();
}
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>