这是@尚春实现的MVVM,使用定时器轮询,只支持{{}}与input.value的修改。
这只能算是一个玩具,真正的MVVM需要有更复杂的扫描机制,JS解析器,双向绑定链什么的。
<!DOCTYPE html>
<html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div data-component="input">
<template>
<input type="text" name="username" tb-model="username" value="" />
<span>{{ username }}</span>
</template>
</div>
<script>
var DIRECTIVE_ATTR_MODEL = 'tb-model',
regMustache = /\{\{\s*(\w+)\s*\}\}/g,
slice = Array.prototype.slice;
function boot() {
var components = slice.call(document.querySelectorAll('[data-component]'), 0);
components.forEach(function (el) {
var component = el.getAttribute('data-component');
bootComponent(el, window[component + 'Controller']);
});
}
function bootComponent(el, controller) {
var $scope = {},
elFrag = el.querySelector('template').content.cloneNode(true);
traverse(elFrag, $scope);
el.appendChild(elFrag);
controller($scope);
}
function traverse(root, $scope) {
for (var el = root.firstChild; el; el = el.nextSibling) {
parseElement(el, $scope);
if (el) {
traverse(el, $scope);
}
}
}
function parseElement(el, $scope) {
if (el.nodeType === 1) {
// element
if (el.hasAttribute(DIRECTIVE_ATTR_MODEL)) {
var model = el.getAttribute(DIRECTIVE_ATTR_MODEL);
el.removeAttribute(DIRECTIVE_ATTR_MODEL);
el.addEventListener('input', function () {
$scope[model] = this.value;
});
}
} else if (el.nodeType === 3) {
// text node
var text = el.textContent,
tpl = [],
lastIndex = 0,
match = regMustache.exec(text);
while (match) {
tpl.push(text.substring(lastIndex, regMustache.lastIndex - match[0].length));
tpl.push({
type: 'var',
content: match[1]
});
lastIndex = regMustache.lastIndex;
match = regMustache.exec(text);
}
watch($scope, function () {
text = '';
tpl.forEach(function (item) {
text += typeof item === 'string' ? item : $scope[item.content];
});
el.textContent = text;
});
}
}
function watch($scope, cb) {
var old = _.cloneDeep($scope),
timer;
timer = setInterval(function () {
if (!_.isEqual($scope, old)) {
cb($scope, old);
old = _.cloneDeep($scope);
}
}, 50);
}
function inputController($scope) {
$scope.username = 'spring';
}
boot();
</script>
</body>
</html>
<!DOCTYPE html> <html> <head> <script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script> <meta charset="utf-8"> <title>JS Bin</title> </head> <body> <div data-component="input"> <template> <input type="text" name="username" tb-model="username" value="" /> <span>{{ username }}</span> </template> </div> <script> var DIRECTIVE_ATTR_MODEL = 'tb-model', regMustache = /\{\{\s*(\w+)\s*\}\}/g, slice = Array.prototype.slice; function boot() { var components = slice.call(document.querySelectorAll('[data-component]'), 0); components.forEach(function (el) { var component = el.getAttribute('data-component'); bootComponent(el, window[component + 'Controller']); }); } function bootComponent(el, controller) { var $scope = {}, elFrag = el.querySelector('template').content.cloneNode(true); traverse(elFrag, $scope); el.appendChild(elFrag); controller($scope); } function traverse(root, $scope) { for (var el = root.firstChild; el; el = el.nextSibling) { parseElement(el, $scope); if (el) { traverse(el, $scope); } } } function parseElement(el, $scope) { if (el.nodeType === 1) { // element if (el.hasAttribute(DIRECTIVE_ATTR_MODEL)) { var model = el.getAttribute(DIRECTIVE_ATTR_MODEL); el.removeAttribute(DIRECTIVE_ATTR_MODEL); el.addEventListener('input', function () { $scope[model] = this.value; }); } } else if (el.nodeType === 3) { // text node var text = el.textContent, tpl = [], lastIndex = 0, match = regMustache.exec(text); while (match) { tpl.push(text.substring(lastIndex, regMustache.lastIndex - match[0].length)); tpl.push({ type: 'var', content: match[1] }); lastIndex = regMustache.lastIndex; match = regMustache.exec(text); } watch($scope, function () { text = ''; tpl.forEach(function (item) { text += typeof item === 'string' ? item : $scope[item.content]; }); el.textContent = text; }); } } function watch($scope, cb) { var old = _.cloneDeep($scope), timer; timer = setInterval(function () { if (!_.isEqual($scope, old)) { cb($scope, old); old = _.cloneDeep($scope); } }, 50); } function inputController($scope) { $scope.username = 'spring'; } boot(); </script> </body> </html>
运行代码