├── .gitignore ├── README.md ├── build └── js │ ├── array.js │ ├── batcher.js │ ├── compile.js │ ├── dep.js │ ├── mvvm.js │ ├── observer.js │ ├── util.js │ └── watcher.js ├── gulpfile.js ├── index.html ├── package.json └── src └── js ├── array.js ├── batcher.js ├── compile.js ├── dep.js ├── mvvm.js ├── observer.js ├── util.js └── watcher.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A-simple-MVVM 2 | -------------------------------------------------------------------------------- /build/js/array.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.arrayMethods = undefined; 7 | 8 | var _create = require('babel-runtime/core-js/object/create'); 9 | 10 | var _create2 = _interopRequireDefault(_create); 11 | 12 | var _util = require('./util'); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 15 | 16 | var arrayProto = Array.prototype; // 数组的变化不能利用简单的 === 来判断 17 | // 引用没变不能触发setter更新,解决办法就是重载数组的变化方法 18 | var arrayMethods = exports.arrayMethods = (0, _create2.default)(arrayProto); 19 | 20 | // 注意的是sort,splice,reverse改变的全部是原数组 21 | ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) { 22 | var original = arrayProto[method]; 23 | // 重载数组的方法 24 | (0, _util.def)(arrayMethods, method, function () { 25 | var i = arguments.length; 26 | var args = new Array(i); 27 | while (i--) { 28 | args[i] = arguments[i]; 29 | } 30 | var result = original.apply(this, args); 31 | var ob = this.__ob__; 32 | var inserted = void 0; 33 | switch (method) { 34 | case 'push': 35 | case 'unshift': 36 | inserted = args; 37 | break; 38 | case 'splice': 39 | // 获取到加入的元素 40 | inserted = args.slice(2); 41 | break; 42 | } 43 | if (inserted) ob.observerArray(inserted); 44 | 45 | // 通知更新变化 46 | ob.dep.notify(); 47 | return result; 48 | }); 49 | }); -------------------------------------------------------------------------------- /build/js/batcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _util = require("./util"); 4 | 5 | var queue = []; // 更新UI队列 6 | var has = {}, 7 | // 队列中是否含有该Watcher 8 | waiting = false; 9 | function reset() { 10 | queue.length = 0; 11 | has = {}; 12 | waiting = false; 13 | } 14 | function flush() { 15 | queue.forEach(function (watcher) { 16 | watcher.run(); 17 | }); 18 | reset(); 19 | } 20 | function pushWatcher(watcher) { 21 | queue.push(watcher); 22 | if (!waiting) { 23 | waiting = true; 24 | (0, _util.nextTick)(flush); 25 | } 26 | } 27 | module.exports = pushWatcher; -------------------------------------------------------------------------------- /build/js/compile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _getIterator2 = require("babel-runtime/core-js/get-iterator"); 4 | 5 | var _getIterator3 = _interopRequireDefault(_getIterator2); 6 | 7 | var _watcher = require("./watcher"); 8 | 9 | var _watcher2 = _interopRequireDefault(_watcher); 10 | 11 | var _util = require("./util"); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | var dirRE = /^v-(.*)$/, 16 | eventRE = /^v-on:(.*)$/, 17 | tagRE = /\{\{\{(.*?)\}\}\}|\{\{(.*?)\}\}/g, 18 | htmlRE = /^\{\{\{(.*)\}\}\}$/, 19 | vhtml = "v-html", 20 | vtext = "v-text", 21 | vmodel = { 22 | INPUT: true, 23 | TEXTAREA: true 24 | }, 25 | cacheDiv = document.createElement("div"); 26 | // 更新集合 27 | var updateCollection = { 28 | text: function text(ele, value) { 29 | ele.textContent = value == null ? "" : value; 30 | }, 31 | html: function html(ele, value) { 32 | if (!ele.$parent) { 33 | ele.innerHTML = value; 34 | } else { 35 | // 解析插值html 36 | cacheDiv.innerHTML = value; 37 | var childs = cacheDiv.childNodes; 38 | var doms = []; 39 | var len = childs.length; 40 | var c; 41 | if (ele.$oneTime) { 42 | // 第一次更新 43 | while (len--) { 44 | c = childs[0]; 45 | ele.appendChild(c); 46 | doms.push(c); 47 | } 48 | ele.$doms = doms; 49 | ele.$oneTime = false; 50 | } else { 51 | var first = ele.$doms[0], 52 | parent = first.parentNode; 53 | while (len--) { 54 | c = childs[0]; 55 | (0, _util.insertBefore)(c, first); 56 | doms.push(c); 57 | } 58 | ele.$doms.forEach(function (node) { 59 | parent.removeChild(node); 60 | }); 61 | ele.$doms = doms; 62 | } 63 | } 64 | }, 65 | model: function model(ele, value, vm, path) { 66 | // 视图->模型,不用设置value值 67 | if (ele.value !== value) ele.value = value == null ? "" : value; 68 | // input事件不考虑ie9 69 | var flag = true; 70 | ele.addEventListener("compositionstart", function () { 71 | flag = false; 72 | }, false); 73 | ele.addEventListener("compositionend", function () { 74 | flag = true; 75 | }, false); 76 | ele.addEventListener("input", function (e) { 77 | if (!flag) return void 0; 78 | var newValue = e.target.value; 79 | if (value === newValue) { 80 | return void 0; 81 | } 82 | value = newValue; 83 | (0, _util.nextTick)(function () { 84 | (0, _util.setValue)(vm, path, newValue); 85 | }); 86 | }, false); 87 | } 88 | }; 89 | // 指定集合 90 | var dirCollection = { 91 | text: function text(node, vm, path) { 92 | BaseDir(node, vm, path, "text"); 93 | }, 94 | html: function html(node, vm, path, parent) { 95 | BaseDir(node, vm, path, "html", parent); 96 | }, 97 | model: function model(node, vm, path) { 98 | if (vmodel[node.tagName]) { 99 | BaseDir(node, vm, path, "model"); 100 | } else { 101 | throw new Error("v-model just can use in input or textarea"); 102 | } 103 | }, 104 | eventDir: function eventDir(node, type, vm, fn) { 105 | var method = vm.$options.methods && vm.$options.methods[fn]; 106 | if (method) { 107 | node.addEventListener(type, method.bind(vm), false); 108 | } 109 | } 110 | }; 111 | 112 | function compile(el, vm) { 113 | el = el.cloneNode(true); 114 | compileNode(el, vm); 115 | return el; 116 | } 117 | function compileNode(node, vm) { 118 | var type = node.nodeType; 119 | if (type === 1 && !(0, _util.isScript)(node)) { 120 | compileElement(node, vm); 121 | } else if (type === 3 && node.data.trim()) { 122 | compileTextNode(node, vm); 123 | } else { 124 | return null; 125 | } 126 | } 127 | function compileNodeList(nodes, vm) { 128 | var _iteratorNormalCompletion = true; 129 | var _didIteratorError = false; 130 | var _iteratorError = undefined; 131 | 132 | try { 133 | for (var _iterator = (0, _getIterator3.default)(nodes), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 134 | var val = _step.value; 135 | 136 | compileNode(val, vm); 137 | } 138 | } catch (err) { 139 | _didIteratorError = true; 140 | _iteratorError = err; 141 | } finally { 142 | try { 143 | if (!_iteratorNormalCompletion && _iterator.return) { 144 | _iterator.return(); 145 | } 146 | } finally { 147 | if (_didIteratorError) { 148 | throw _iteratorError; 149 | } 150 | } 151 | } 152 | } 153 | function compileElement(node, vm) { 154 | var flag = false; 155 | var attrs = Array.prototype.slice.call(node.attributes); 156 | attrs.forEach(function (val) { 157 | var name = val.name, 158 | value = val.value; 159 | if (dirRE.test(name)) { 160 | var dir; 161 | // 事件指令 162 | if ((dir = name.match(eventRE)) && (dir = dir[1])) { 163 | dirCollection["eventDir"](node, dir, vm, value); 164 | } else { 165 | dir = name.match(dirRE)[1]; 166 | dirCollection[dir](node, vm, value); 167 | } 168 | // 指令中为v-html or v-text or v-model终止递归 169 | flag = flag || name === vhtml || name === vtext; 170 | node.removeAttribute(name); 171 | } 172 | }); 173 | var childs = node.childNodes; 174 | if (!flag && childs && childs.length) { 175 | compileNodeList(childs, vm); 176 | } 177 | } 178 | function compileTextNode(node, vm) { 179 | var tokens = parseText(node); 180 | if (tokens == null) return void 0; 181 | var frag = document.createDocumentFragment(); 182 | tokens.forEach(function (token) { 183 | var el; 184 | if (token.tag) { 185 | if (token.html) { 186 | el = document.createDocumentFragment(); 187 | el.$parent = node.parentNode; 188 | el.$oneTime = true; 189 | dirCollection["html"](el, vm, token.value); 190 | } else { 191 | el = document.createTextNode(" "); 192 | dirCollection["text"](el, vm, token.value); 193 | } 194 | } else { 195 | el = document.createTextNode(token.value); 196 | } 197 | el && frag.appendChild(el); 198 | }); 199 | return (0, _util.replace)(node, frag); 200 | } 201 | function parseText(node) { 202 | var text = node.wholeText; 203 | if (!tagRE.test(text)) { 204 | return void 0; 205 | } 206 | var tokens = []; 207 | var lastIndex = tagRE.lastIndex = 0, 208 | match, 209 | index, 210 | html, 211 | value; 212 | while (match = tagRE.exec(text)) { 213 | index = match.index; 214 | if (index > lastIndex) { 215 | tokens.push({ 216 | value: text.slice(lastIndex, index) 217 | }); 218 | } 219 | html = htmlRE.test(match[0]); 220 | value = html ? match[1] : match[2]; 221 | tokens.push({ 222 | value: value, 223 | tag: true, 224 | html: html 225 | }); 226 | lastIndex = index + match[0].length; 227 | } 228 | if (lastIndex < text.length) { 229 | tokens.push({ 230 | value: text.slice(lastIndex) 231 | }); 232 | } 233 | return tokens; 234 | } 235 | function BaseDir(node, vm, path, dir, parent) { 236 | var fn = updateCollection[dir]; 237 | fn && fn(node, (0, _util.getValue)(vm, path), vm, path); 238 | new _watcher2.default(vm, path, function (value) { 239 | fn && fn(node, value, vm, path); 240 | }); 241 | } 242 | module.exports = compile; -------------------------------------------------------------------------------- /build/js/dep.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 4 | 5 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 6 | 7 | var _createClass2 = require("babel-runtime/helpers/createClass"); 8 | 9 | var _createClass3 = _interopRequireDefault(_createClass2); 10 | 11 | var _util = require("./util"); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | var uid = 0; 16 | 17 | var Dep = function () { 18 | function Dep() { 19 | (0, _classCallCheck3.default)(this, Dep); 20 | 21 | this.id = uid++; 22 | this.subs = []; // 存储watcher 23 | } 24 | 25 | (0, _createClass3.default)(Dep, [{ 26 | key: "addSub", 27 | value: function addSub(sub) { 28 | this.subs.push(sub); 29 | } 30 | }, { 31 | key: "removeSub", 32 | value: function removeSub(sub) { 33 | (0, _util.remove)(this.subs, sub); 34 | } 35 | }, { 36 | key: "depend", 37 | value: function depend() { 38 | // 将相关的watcher添加到subs中 39 | Dep.target.addDep(this); 40 | } 41 | }, { 42 | key: "notify", 43 | value: function notify() { 44 | // 遍历与该数据有关的Watcher 45 | this.subs.forEach(function (val) { 46 | val.update(); 47 | }); 48 | } 49 | }]); 50 | return Dep; 51 | }(); 52 | 53 | Dep.target = null; 54 | 55 | module.exports = Dep; -------------------------------------------------------------------------------- /build/js/observer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _typeof2 = require("babel-runtime/helpers/typeof"); 4 | 5 | var _typeof3 = _interopRequireDefault(_typeof2); 6 | 7 | var _defineProperty = require("babel-runtime/core-js/object/define-property"); 8 | 9 | var _defineProperty2 = _interopRequireDefault(_defineProperty); 10 | 11 | var _getOwnPropertyDescriptor = require("babel-runtime/core-js/object/get-own-property-descriptor"); 12 | 13 | var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); 14 | 15 | var _keys = require("babel-runtime/core-js/object/keys"); 16 | 17 | var _keys2 = _interopRequireDefault(_keys); 18 | 19 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 20 | 21 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 22 | 23 | var _createClass2 = require("babel-runtime/helpers/createClass"); 24 | 25 | var _createClass3 = _interopRequireDefault(_createClass2); 26 | 27 | var _util = require("./util"); 28 | 29 | var _dep = require("./dep"); 30 | 31 | var _dep2 = _interopRequireDefault(_dep); 32 | 33 | var _array = require("./array"); 34 | 35 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 36 | 37 | var Observer = function () { 38 | function Observer(value) { 39 | (0, _classCallCheck3.default)(this, Observer); 40 | 41 | this.value = value; 42 | (0, _util.def)(value, "__ob__", this); 43 | this.dep = new _dep2.default(); 44 | if (Array.isArray(value)) { 45 | var augment = value.__proto__ ? protoAugment : copyAugment; 46 | augment(value, _array.arrayMethods, (0, _keys2.default)(_array.arrayMethods)); 47 | this.observerArray(value); 48 | } else { 49 | this.walk(value); 50 | } 51 | } 52 | 53 | (0, _createClass3.default)(Observer, [{ 54 | key: "walk", 55 | value: function walk(obj) { 56 | var _this = this; 57 | 58 | var keys = (0, _keys2.default)(obj); 59 | keys.forEach(function (key) { 60 | defineReactive(_this.value, key, obj[key]); 61 | }); 62 | } 63 | // 给新增的元素增加响应式的变化 64 | 65 | }, { 66 | key: "observerArray", 67 | value: function observerArray(items) { 68 | for (var i = 0, l = items.length; i < l; i++) { 69 | observer(items[i]); 70 | } 71 | } 72 | }]); 73 | return Observer; 74 | }(); 75 | 76 | function protoAugment(target, src) { 77 | target.__proto__ = src; 78 | } 79 | function copyAugment(target, src, keys) { 80 | for (var i = 0, l = keys.length; i < l; i++) { 81 | var key = keys[i]; 82 | (0, _util.def)(target, key, src[key]); 83 | } 84 | } 85 | function defineReactive(obj, key, val) { 86 | var dep = new _dep2.default(), 87 | property = (0, _getOwnPropertyDescriptor2.default)(obj, key); 88 | if (property && !property.configurable) { 89 | return void 0; 90 | } 91 | // 对于数据关联计算情况下,应调用自身所写的get/set 92 | // 如: data.a = 1; data.b = data.a + 1; 93 | // 要想a,b全部为响应式的,则需要手动设置a的set 94 | var getter = property.get, 95 | setter = property.set, 96 | childOb = observer(val); 97 | (0, _defineProperty2.default)(obj, key, { 98 | configurable: true, 99 | enumerable: true, 100 | get: function get() { 101 | var value = getter ? getter.call(obj) : val; 102 | if (_dep2.default.target) { 103 | // 关联数据与dom节点 104 | dep.depend(); 105 | if (childOb) { 106 | childOb.dep.depend(); 107 | } 108 | } 109 | return value; 110 | }, 111 | set: function set(newVal) { 112 | var value = getter ? getter.call(obj) : val; 113 | if (newVal === value) { 114 | return void 0; 115 | } 116 | if (setter) { 117 | setter.call(obj, newVal); 118 | } else { 119 | val = newVal; 120 | } 121 | childOb = observer(val); 122 | // 调用watcher来更新dom 123 | dep.notify(); 124 | } 125 | }); 126 | } 127 | function observer(value) { 128 | if (!value || (typeof value === "undefined" ? "undefined" : (0, _typeof3.default)(value)) !== "object") { 129 | return void 0; 130 | } else if ((0, _util.hasOwn)(value, "__ob__") && value["__ob__"] instanceof Observer) { 131 | return void 0; 132 | } 133 | return new Observer(value); 134 | } 135 | 136 | module.exports = observer; -------------------------------------------------------------------------------- /build/js/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _setImmediate2 = require("babel-runtime/core-js/set-immediate"); 4 | 5 | var _setImmediate3 = _interopRequireDefault(_setImmediate2); 6 | 7 | var _clearImmediate2 = require("babel-runtime/core-js/clear-immediate"); 8 | 9 | var _clearImmediate3 = _interopRequireDefault(_clearImmediate2); 10 | 11 | var _promise = require("babel-runtime/core-js/promise"); 12 | 13 | var _promise2 = _interopRequireDefault(_promise); 14 | 15 | var _defineProperty = require("babel-runtime/core-js/object/define-property"); 16 | 17 | var _defineProperty2 = _interopRequireDefault(_defineProperty); 18 | 19 | var _stringify = require("babel-runtime/core-js/json/stringify"); 20 | 21 | var _stringify2 = _interopRequireDefault(_stringify); 22 | 23 | var _typeof2 = require("babel-runtime/helpers/typeof"); 24 | 25 | var _typeof3 = _interopRequireDefault(_typeof2); 26 | 27 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 28 | 29 | exports.isFunction = function (fn) { 30 | return typeof fn === "function"; 31 | }; 32 | exports.copy = function (obj) { 33 | var res = {}; 34 | for (var key in obj) { 35 | res[key] = (0, _typeof3.default)(obj[key]) === "object" ? JSON.parse((0, _stringify2.default)(obj[key])) : obj[key]; 36 | } 37 | return res; 38 | }; 39 | exports.hasOwn = function (obj, key) { 40 | return Object.prototype.hasOwnProperty.call(obj, key); 41 | }; 42 | exports.def = function (obj, key, value, enumerable) { 43 | (0, _defineProperty2.default)(obj, key, { 44 | value: value, 45 | writeable: true, 46 | configurable: true, 47 | enumerable: !!enumerable 48 | }); 49 | }; 50 | exports.remove = function (arr, val) { 51 | var index = arr.indexOf(val); 52 | if (~index) { 53 | arr.splice(index, 1); 54 | } 55 | }; 56 | exports.replace = function (nodeA, nodeB) { 57 | var parent = nodeA.parentNode; 58 | parent.replaceChild(nodeB, nodeA); 59 | }; 60 | exports.insertBefore = function (nodeA, nodeB) { 61 | var parent = nodeB.parentNode; 62 | parent.insertBefore(nodeA, nodeB); 63 | }; 64 | exports.isScript = function (node) { 65 | return node.tagName === "SCRIPT"; 66 | }; 67 | exports.getValue = function (vm, path) { 68 | var val = vm._data; 69 | path = path.split("."); 70 | path.forEach(function (key) { 71 | val = val[key]; 72 | }); 73 | return val; 74 | }; 75 | exports.setValue = function (vm, path, value) { 76 | var val = vm._data; 77 | path = path.split("."); 78 | var len = path.length; 79 | path.forEach(function (k, i) { 80 | if (i < len - 1) { 81 | val = val[k]; 82 | } else { 83 | val[k] = value; 84 | } 85 | }); 86 | }; 87 | function isNative(fn) { 88 | return (/native code/.test(fn.toString()) 89 | ); 90 | } 91 | exports.nextTick = function () { 92 | var callbacks = []; // 更新dom队列 93 | var pending = false; 94 | var timerFunc; 95 | // 发布事件函数 96 | function nextTickHandler() { 97 | pending = false; 98 | var copies = callbacks.slice(0); 99 | callbacks.length = 0; // 发布完成后清空队列 100 | copies.forEach(function (cb) { 101 | cb(); 102 | }); 103 | } 104 | if (typeof _promise2.default !== "undefined" && isNative(_promise2.default)) { 105 | var p = _promise2.default.resolve(); 106 | timerFunc = function timerFunc() { 107 | p.then(nextTickHandler); 108 | }; 109 | } else if (typeof MutationObserver !== "undefined" && isNative(MutationObserver)) { 110 | var counter = 1; 111 | var observer = new MutationObserver(nextTickHandler); 112 | var textNode = document.createTextNode(counter); 113 | observer.observe(textNode, { 114 | characterData: true 115 | }); 116 | timerFunc = function timerFunc() { 117 | // 每次调用timerFunc会触发nextTickHandler 118 | counter = (counter + 1) % 2; 119 | textNode.data = counter; 120 | }; 121 | } else { 122 | timerFunc = function timerFunc() { 123 | setTimeout(nextTickHandler); 124 | }; 125 | } 126 | return function (cb, ctx) { 127 | var func = ctx ? function () { 128 | cb.call(ctx); 129 | } : cb; 130 | callbacks.push(func); 131 | if (!pending) { 132 | // 注册一个回调 133 | pending = true; 134 | timerFunc(); 135 | } 136 | }; 137 | }(); 138 | exports.debounce = function (fn) { 139 | var immediate = null; 140 | return function () { 141 | var _this = this; 142 | 143 | for (var _len = arguments.length, items = Array(_len), _key = 0; _key < _len; _key++) { 144 | items[_key] = arguments[_key]; 145 | } 146 | 147 | if (immediate) (0, _clearImmediate3.default)(immediate); 148 | immediate = (0, _setImmediate3.default)(function () { 149 | fn.apply(_this, items); 150 | }); 151 | }; 152 | }; -------------------------------------------------------------------------------- /build/js/watcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 4 | 5 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 6 | 7 | var _createClass2 = require("babel-runtime/helpers/createClass"); 8 | 9 | var _createClass3 = _interopRequireDefault(_createClass2); 10 | 11 | var _dep = require("./dep"); 12 | 13 | var _dep2 = _interopRequireDefault(_dep); 14 | 15 | var _batcher = require("./batcher"); 16 | 17 | var _batcher2 = _interopRequireDefault(_batcher); 18 | 19 | var _util = require("./util"); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | var id = 0; 24 | 25 | var Watcher = function () { 26 | function Watcher(vm, exp, cb) { 27 | (0, _classCallCheck3.default)(this, Watcher); 28 | 29 | this.cb = cb; // 更新UI函数 30 | this.vm = vm; 31 | this.exp = exp; 32 | this.depIds = {}; // 数据依赖集合 33 | this.value = this.get(); 34 | this.id = id++; 35 | this.run = (0, _util.debounce)(this.run); 36 | } 37 | 38 | (0, _createClass3.default)(Watcher, [{ 39 | key: "update", 40 | value: function update() { 41 | var _this = this; 42 | 43 | var val = this.get(); 44 | if (val !== this.value) { 45 | this.value = val; 46 | (0, _util.nextTick)(function () { 47 | (0, _batcher2.default)(_this); 48 | }); 49 | } 50 | } 51 | }, { 52 | key: "run", 53 | value: function run() { 54 | this.cb.call(null, this.get()); 55 | } 56 | }, { 57 | key: "addDep", 58 | value: function addDep(dep) { 59 | if (!(0, _util.hasOwn)(this.depIds, dep.id)) { 60 | dep.addSub(this); 61 | this.depIds[dep.id] = dep; 62 | } 63 | } 64 | }, { 65 | key: "get", 66 | value: function get() { 67 | _dep2.default.target = this; 68 | var val = (0, _util.getValue)(this.vm, this.exp); 69 | _dep2.default.target = null; 70 | return val; 71 | } 72 | }]); 73 | return Watcher; 74 | }(); 75 | 76 | module.exports = Watcher; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require("gulp"), 2 | babel = require("gulp-babel"), 3 | jshint = require("gulp-jshint"), 4 | uglify = require("gulp-uglify"), 5 | browserSync = require("browser-sync"), 6 | browserify = require('gulp-browserify'), 7 | filePath = ["./src/js/**/*.js"], 8 | tasks = ["js"]; 9 | 10 | gulp.task("es2015", () => { 11 | return gulp.src(filePath[0]) 12 | .pipe(babel({ 13 | presets: ['es2015'], 14 | plugins: ['transform-runtime'] 15 | })) 16 | .pipe(gulp.dest("./build/js")); 17 | }); 18 | 19 | gulp.task("js", ["es2015"], () => { 20 | return gulp.src("./build/js/mvvm.js") 21 | .pipe(browserify({ 22 | insertGlobals : true, 23 | debug : !gulp.env.production 24 | })) 25 | .pipe(jshint()) 26 | // .pipe(uglify()) // 上线后打开 27 | .pipe(gulp.dest("./build/js")) 28 | .pipe(browserSync.reload({stream: true})); 29 | }); 30 | 31 | gulp.task("server", tasks, () => { 32 | browserSync.init({ 33 | server: { 34 | baseDir: "./" 35 | } 36 | }); 37 | }); 38 | 39 | gulp.task("watch", () => { 40 | gulp.watch(filePath, tasks); 41 | }); 42 | 43 | gulp.task("default", ["server", "watch"]); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |