├── .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 | A Simple MVVM 6 | 11 | 12 | 13 |
14 | 15 |

16 | {{{html}}} 17 | 18 |
19 | 20 |

21 | {{text}} 22 | 23 |
24 |

{{a}} + {{b}} = {{c}}

25 | 遇到关联属性的时候会产生一些问题,需要自己手动绑定一个getter/setter 26 | 27 |
28 |

数组目前的长度: {{items.length}}

29 | 30 |
31 | 32 | 102 | 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lsgo-mvvm", 3 | "version": "1.0.0", 4 | "description": "\"\"\u001b[D一�机�� �参照vue1的源码实现mvvm\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D简单的实现下mvvm\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[C\u001b\u001b[C\u001b[C\u001b[2~\u001b[C\u001b\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[ \"\"\u001b[D\u001ba simple mvvm", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "gulp": "gulp" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/zp1996/A-simple-MVVM.git" 13 | }, 14 | "keywords": [ 15 | "mvvm" 16 | ], 17 | "author": "zp1996", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/zp1996/A-simple-MVVM/issues" 21 | }, 22 | "homepage": "https://github.com/zp1996/A-simple-MVVM#readme", 23 | "devDependencies": { 24 | "babel-plugin-transform-runtime": "^6.15.0", 25 | "babel-preset-es2015": "^6.14.0", 26 | "babel-runtime": "^6.11.6", 27 | "browser-sync": "^2.17.1", 28 | "gulp": "^3.9.1", 29 | "gulp-babel": "^6.1.2", 30 | "gulp-browserify": "^0.5.1", 31 | "gulp-jshint": "^2.0.1", 32 | "gulp-uglify": "^2.0.0", 33 | "jshint": "^2.9.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/js/array.js: -------------------------------------------------------------------------------- 1 | // 数组的变化不能利用简单的 === 来判断 2 | // 引用没变不能触发setter更新,解决办法就是重载数组的变化方法 3 | import { def } from './util'; 4 | 5 | const arrayProto = Array.prototype; 6 | export const arrayMethods = Object.create(arrayProto); 7 | 8 | // 注意的是sort,splice,reverse改变的全部是原数组 9 | ['push', 'pop', 'shift', 'unshift', 10 | 'splice', 'sort', 'reverse'].forEach((method) => { 11 | const original = arrayProto[method]; 12 | // 重载数组的方法 13 | def(arrayMethods, method, function() { 14 | let i = arguments.length; 15 | const args = new Array(i); 16 | while(i--) { 17 | args[i] = arguments[i]; 18 | } 19 | const result = original.apply(this, args); 20 | const ob = this.__ob__; 21 | let inserted; 22 | switch(method) { 23 | case 'push': 24 | case 'unshift': 25 | inserted = args; 26 | break; 27 | case 'splice': 28 | // 获取到加入的元素 29 | inserted = args.slice(2); 30 | break; 31 | } 32 | if (inserted) ob.observerArray(inserted); 33 | 34 | // 通知更新变化 35 | ob.dep.notify(); 36 | return result; 37 | }); 38 | }); -------------------------------------------------------------------------------- /src/js/batcher.js: -------------------------------------------------------------------------------- 1 | import { nextTick, debounce } from "./util"; 2 | 3 | const queue = []; // 更新UI队列 4 | var has = {}, // 队列中是否含有该Watcher 5 | waiting = false; 6 | function reset() { 7 | queue.length = 0; 8 | has = {}; 9 | waiting = false; 10 | } 11 | function flush() { 12 | queue.forEach(watcher => { 13 | watcher.run(); 14 | }); 15 | reset(); 16 | } 17 | function pushWatcher(watcher) { 18 | queue.push(watcher); 19 | if (!waiting) { 20 | waiting = true; 21 | nextTick(flush); 22 | } 23 | } 24 | module.exports = pushWatcher; -------------------------------------------------------------------------------- /src/js/compile.js: -------------------------------------------------------------------------------- 1 | import Watcher from "./watcher"; 2 | import { 3 | isScript, 4 | replace, 5 | getValue, 6 | setValue, 7 | nextTick, 8 | insertBefore 9 | } from "./util"; 10 | 11 | const dirRE = /^v-(.*)$/, 12 | eventRE = /^v-on:(.*)$/, 13 | tagRE = /\{\{\{(.*?)\}\}\}|\{\{(.*?)\}\}/g, 14 | htmlRE = /^\{\{\{(.*)\}\}\}$/, 15 | vhtml = "v-html", 16 | vtext = "v-text", 17 | vmodel = { 18 | INPUT: true, 19 | TEXTAREA: true 20 | }, 21 | cacheDiv = document.createElement("div"); 22 | // 更新集合 23 | const updateCollection = { 24 | text: (ele, value) => { 25 | ele.textContent = value == null ? "" : value; 26 | }, 27 | html: (ele, value) => { 28 | if (!ele.$parent) { 29 | ele.innerHTML = value; 30 | } else { 31 | // 解析插值html 32 | cacheDiv.innerHTML = value; 33 | const childs = cacheDiv.childNodes; 34 | const doms = []; 35 | var len = childs.length; 36 | var c; 37 | if (ele.$oneTime) { 38 | // 第一次更新 39 | while(len--) { 40 | c = childs[0]; 41 | ele.appendChild(c); 42 | doms.push(c); 43 | } 44 | ele.$doms = doms; 45 | ele.$oneTime = false; 46 | } else { 47 | const first = ele.$doms[0], 48 | parent = first.parentNode; 49 | while(len--) { 50 | c = childs[0]; 51 | insertBefore(c, first); 52 | doms.push(c); 53 | } 54 | ele.$doms.forEach(node => { 55 | parent.removeChild(node); 56 | }); 57 | ele.$doms = doms; 58 | } 59 | } 60 | }, 61 | model: (ele, value, vm, path) => { 62 | // 视图->模型,不用设置value值 63 | if (ele.value !== value) 64 | ele.value = value == null ? "" : value; 65 | // input事件不考虑ie9 66 | var flag = true; 67 | ele.addEventListener("compositionstart", () => { 68 | flag = false; 69 | }, false); 70 | ele.addEventListener("compositionend", () => { 71 | flag = true; 72 | }, false); 73 | ele.addEventListener("input", (e) => { 74 | if (!flag) return void 0; 75 | var newValue = e.target.value; 76 | if (value === newValue) { 77 | return void 0; 78 | } 79 | value = newValue; 80 | nextTick(() => { 81 | setValue(vm, path, newValue); 82 | }); 83 | }, false); 84 | } 85 | }; 86 | // 指定集合 87 | const dirCollection = { 88 | text: (node, vm, path) => { 89 | BaseDir(node, vm, path, "text"); 90 | }, 91 | html: (node, vm, path, parent) => { 92 | BaseDir(node, vm, path, "html", parent); 93 | }, 94 | model: (node, vm, path) => { 95 | if (vmodel[node.tagName]) { 96 | BaseDir(node, vm, path, "model"); 97 | } else { 98 | throw new Error("v-model just can use in input or textarea"); 99 | } 100 | }, 101 | eventDir: (node, type, vm, fn) => { 102 | const method = vm.$options.methods && vm.$options.methods[fn]; 103 | if (method) { 104 | node.addEventListener(type, method.bind(vm), false); 105 | } 106 | } 107 | }; 108 | 109 | function compile(el, vm) { 110 | el = el.cloneNode(true); 111 | compileNode(el, vm); 112 | return el; 113 | } 114 | function compileNode(node, vm) { 115 | const type = node.nodeType; 116 | if (type === 1 && !isScript(node)) { 117 | compileElement(node, vm); 118 | } else if (type === 3 && node.data.trim()) { 119 | compileTextNode(node, vm); 120 | } else { 121 | return null; 122 | } 123 | } 124 | function compileNodeList(nodes, vm) { 125 | for (let val of nodes) { 126 | compileNode(val, vm); 127 | } 128 | } 129 | function compileElement(node, vm) { 130 | var flag = false; 131 | const attrs = Array.prototype.slice.call(node.attributes); 132 | attrs.forEach((val) => { 133 | const name = val.name, 134 | value = val.value; 135 | if (dirRE.test(name)) { 136 | var dir; 137 | // 事件指令 138 | if ( 139 | (dir = name.match(eventRE)) && 140 | (dir = dir[1]) 141 | ) { 142 | dirCollection["eventDir"](node, dir, vm, value); 143 | } else { 144 | dir = name.match(dirRE)[1]; 145 | dirCollection[dir](node, vm, value); 146 | } 147 | // 指令中为v-html or v-text or v-model终止递归 148 | flag = flag || 149 | name === vhtml || 150 | name === vtext; 151 | node.removeAttribute(name); 152 | } 153 | }); 154 | const childs = node.childNodes; 155 | if (!flag && childs && childs.length) { 156 | compileNodeList(childs, vm); 157 | } 158 | } 159 | function compileTextNode(node, vm) { 160 | const tokens = parseText(node); 161 | if (tokens == null) return void 0; 162 | var frag = document.createDocumentFragment(); 163 | tokens.forEach(token => { 164 | var el; 165 | if (token.tag) { 166 | if (token.html) { 167 | el = document.createDocumentFragment(); 168 | el.$parent = node.parentNode; 169 | el.$oneTime = true; 170 | dirCollection["html"](el, vm, token.value); 171 | } else { 172 | el = document.createTextNode(" "); 173 | dirCollection["text"](el, vm, token.value); 174 | } 175 | } else { 176 | el = document.createTextNode(token.value); 177 | } 178 | el && frag.appendChild(el); 179 | }); 180 | return replace(node, frag); 181 | } 182 | function parseText(node) { 183 | var text = node.wholeText; 184 | if (!tagRE.test(text)) { 185 | return void 0; 186 | } 187 | const tokens = []; 188 | var lastIndex = tagRE.lastIndex = 0, 189 | match, index, html, value; 190 | while (match = tagRE.exec(text)) { 191 | index = match.index; 192 | if (index > lastIndex) { 193 | tokens.push({ 194 | value: text.slice(lastIndex, index) 195 | }) 196 | } 197 | html = htmlRE.test(match[0]); 198 | value = html ? match[1] : match[2]; 199 | tokens.push({ 200 | value: value, 201 | tag: true, 202 | html: html 203 | }); 204 | lastIndex = index + match[0].length; 205 | } 206 | if (lastIndex < text.length) { 207 | tokens.push({ 208 | value: text.slice(lastIndex) 209 | }) 210 | } 211 | return tokens; 212 | } 213 | function BaseDir(node, vm, path, dir, parent) { 214 | const fn = updateCollection[dir]; 215 | fn && fn(node, getValue(vm, path), vm, path); 216 | new Watcher(vm, path, (value) => { 217 | fn && fn(node, value, vm, path); 218 | }); 219 | } 220 | module.exports = compile; -------------------------------------------------------------------------------- /src/js/dep.js: -------------------------------------------------------------------------------- 1 | import {remove} from "./util"; 2 | 3 | var uid = 0; 4 | class Dep{ 5 | constructor() { 6 | this.id = uid++; 7 | this.subs = []; // 存储watcher 8 | } 9 | addSub(sub) { 10 | this.subs.push(sub); 11 | } 12 | removeSub(sub) { 13 | remove(this.subs, sub); 14 | } 15 | depend() { 16 | // 将相关的watcher添加到subs中 17 | Dep.target.addDep(this); 18 | } 19 | notify() { 20 | // 遍历与该数据有关的Watcher 21 | this.subs.forEach((val) => { 22 | val.update(); 23 | }); 24 | } 25 | } 26 | 27 | Dep.target = null; 28 | 29 | module.exports = Dep; -------------------------------------------------------------------------------- /src/js/mvvm.js: -------------------------------------------------------------------------------- 1 | import observer from "./observer"; 2 | import compile from "./compile"; 3 | import { 4 | isFunction, 5 | copy, 6 | replace, 7 | nextTick 8 | } from "./util"; 9 | /* 10 | * 每一个用到响应式的数据对应一个Dep,数据更改时,会触发setter 11 | * 会调用出来与这个Dep相关联的watcher来决定是否变化watcher相对应 12 | * 的dom,每一个与数据相关联的dom节点对应着一个watcher,整个的关联 13 | * 关系是在运行时决定的,也就是需要将模板html解析,从而得出关联关系 14 | */ 15 | class MVVM { 16 | constructor(opts) { 17 | this.$options = opts; 18 | this.el = opts.el; 19 | var data = this._data = isFunction(opts.data) ? 20 | opts.data() : copy(opts.data); 21 | // vm._data.x => vm.x,方便外界访问 22 | const keys = Object.keys(data); 23 | var i = keys.length; 24 | while (i--) { 25 | // Vue在此处还考虑到了是否在props是否有该属性 26 | this._proxy(keys[i]); 27 | } 28 | // 将数据全部转换为getter/setter形式 29 | observer(data); 30 | // 对模板进行编译 31 | this._compile(); 32 | } 33 | _proxy(key) { 34 | Object.defineProperty(this, key, { 35 | configurable: true, 36 | enumerable: true, 37 | get: () => { 38 | return this._data[key]; 39 | }, 40 | set: (val) => { 41 | this._data[key] = val; 42 | } 43 | }); 44 | } 45 | $nextTick(cb) { 46 | nextTick(cb, this); 47 | } 48 | _compile() { 49 | this.el = document.querySelector(this.el); 50 | replace(this.el, compile(this.el, this)); 51 | } 52 | } 53 | 54 | window.Mvvm = MVVM; -------------------------------------------------------------------------------- /src/js/observer.js: -------------------------------------------------------------------------------- 1 | import { hasOwn, def } from "./util"; 2 | import Dep from "./dep"; 3 | import { arrayMethods } from './array'; 4 | 5 | class Observer{ 6 | constructor(value) { 7 | this.value = value; 8 | def(value, "__ob__", this); 9 | this.dep = new Dep(); 10 | if (Array.isArray(value)) { 11 | const augment = value.__proto__ ? protoAugment : copyAugment; 12 | augment( 13 | value, 14 | arrayMethods, 15 | Object.keys(arrayMethods) 16 | ); 17 | this.observerArray(value); 18 | } else { 19 | this.walk(value); 20 | } 21 | } 22 | walk(obj) { 23 | const keys = Object.keys(obj); 24 | keys.forEach((key) => { 25 | defineReactive(this.value, key, obj[key]); 26 | }); 27 | } 28 | // 给新增的元素增加响应式的变化 29 | observerArray(items) { 30 | for (let i = 0, l = items.length; i < l; i++) { 31 | observer(items[i]); 32 | } 33 | } 34 | } 35 | function protoAugment(target, src) { 36 | target.__proto__ = src; 37 | } 38 | function copyAugment(target, src, keys) { 39 | for (let i = 0, l = keys.length; i < l; i++) { 40 | const key = keys[i]; 41 | def(target, key, src[key]); 42 | } 43 | } 44 | function defineReactive(obj, key, val) { 45 | const dep = new Dep(), 46 | property = Object.getOwnPropertyDescriptor(obj, key); 47 | if (property && !property.configurable) { 48 | return void 0; 49 | } 50 | // 对于数据关联计算情况下,应调用自身所写的get/set 51 | // 如: data.a = 1; data.b = data.a + 1; 52 | // 要想a,b全部为响应式的,则需要手动设置a的set 53 | var getter = property.get, 54 | setter = property.set, 55 | childOb = observer(val); 56 | Object.defineProperty(obj, key, { 57 | configurable: true, 58 | enumerable: true, 59 | get: () => { 60 | var value = getter ? getter.call(obj) : val; 61 | if (Dep.target) { 62 | // 关联数据与dom节点 63 | dep.depend(); 64 | if (childOb) { 65 | childOb.dep.depend(); 66 | } 67 | } 68 | return value; 69 | }, 70 | set: (newVal) => { 71 | var value = getter ? getter.call(obj) : val; 72 | if (newVal === value) { 73 | return void 0; 74 | } 75 | if (setter) { 76 | setter.call(obj, newVal); 77 | } else { 78 | val = newVal; 79 | } 80 | childOb = observer(val); 81 | // 调用watcher来更新dom 82 | dep.notify(); 83 | } 84 | }); 85 | } 86 | function observer(value) { 87 | if (!value || typeof value !== "object") { 88 | return void 0; 89 | } else if (hasOwn(value, "__ob__") && 90 | value["__ob__"] instanceof Observer 91 | ) { 92 | return void 0; 93 | } 94 | return new Observer(value); 95 | } 96 | 97 | module.exports = observer; -------------------------------------------------------------------------------- /src/js/util.js: -------------------------------------------------------------------------------- 1 | exports.isFunction =function(fn) { 2 | return typeof fn === "function"; 3 | }; 4 | exports.copy = function(obj) { 5 | const res = {}; 6 | for (let key in obj) { 7 | res[key] = typeof obj[key] === "object" 8 | ? JSON.parse(JSON.stringify(obj[key])) : obj[key]; 9 | } 10 | return res; 11 | }; 12 | exports.hasOwn = function(obj, key) { 13 | return Object.prototype.hasOwnProperty.call(obj, key); 14 | }; 15 | exports.def = function(obj, key, value, enumerable) { 16 | Object.defineProperty(obj, key, { 17 | value: value, 18 | writeable: true, 19 | configurable: true, 20 | enumerable: !!enumerable 21 | }); 22 | }; 23 | exports.remove = function(arr, val) { 24 | var index = arr.indexOf(val); 25 | if (~index) { 26 | arr.splice(index, 1); 27 | } 28 | }; 29 | exports.replace = function(nodeA, nodeB) { 30 | const parent = nodeA.parentNode; 31 | parent.replaceChild(nodeB, nodeA); 32 | }; 33 | exports.insertBefore = function(nodeA, nodeB) { 34 | const parent = nodeB.parentNode; 35 | parent.insertBefore(nodeA, nodeB); 36 | }; 37 | exports.isScript = function(node) { 38 | return node.tagName === "SCRIPT"; 39 | }; 40 | exports.getValue = function(vm, path) { 41 | var val = vm._data; 42 | path = path.split("."); 43 | path.forEach((key) => { 44 | val = val[key]; 45 | }); 46 | return val; 47 | }; 48 | exports.setValue = function(vm, path, value) { 49 | var val = vm._data; 50 | path = path.split("."); 51 | const len = path.length; 52 | path.forEach((k, i) => { 53 | if (i < len - 1) { 54 | val = val[k]; 55 | } else { 56 | val[k] = value; 57 | } 58 | }); 59 | }; 60 | function isNative (fn) { 61 | return /native code/.test(fn.toString()); 62 | } 63 | exports.nextTick = (function () { 64 | var callbacks = []; // 更新dom队列 65 | var pending = false; 66 | var timerFunc; 67 | // 发布事件函数 68 | function nextTickHandler() { 69 | pending = false; 70 | var copies = callbacks.slice(0); 71 | callbacks.length = 0; // 发布完成后清空队列 72 | copies.forEach(cb => { 73 | cb(); 74 | }); 75 | } 76 | if (typeof Promise !== "undefined" && isNative(Promise)) { 77 | var p = Promise.resolve(); 78 | timerFunc = function() { 79 | p.then(nextTickHandler); 80 | }; 81 | } else if ( 82 | typeof MutationObserver !== "undefined" && 83 | isNative(MutationObserver) 84 | ) { 85 | var counter = 1; 86 | var observer = new MutationObserver(nextTickHandler); 87 | var textNode = document.createTextNode(counter); 88 | observer.observe(textNode, { 89 | characterData: true 90 | }); 91 | timerFunc = function () { 92 | // 每次调用timerFunc会触发nextTickHandler 93 | counter = (counter + 1) % 2; 94 | textNode.data = counter; 95 | }; 96 | } else { 97 | timerFunc = function() { 98 | setTimeout(nextTickHandler); 99 | } 100 | } 101 | return function(cb, ctx) { 102 | var func = ctx ? function () { 103 | cb.call(ctx); 104 | } : cb; 105 | callbacks.push(func); 106 | if (!pending) { // 注册一个回调 107 | pending = true; 108 | timerFunc(); 109 | } 110 | } 111 | })(); 112 | exports.debounce = fn => { 113 | let immediate = null; 114 | return function(...items) { 115 | if (immediate) clearImmediate(immediate); 116 | immediate = setImmediate(() => { 117 | fn.apply(this, items); 118 | }); 119 | }; 120 | }; -------------------------------------------------------------------------------- /src/js/watcher.js: -------------------------------------------------------------------------------- 1 | import Dep from "./dep"; 2 | import pushWatcher from "./batcher"; 3 | import { 4 | hasOwn, 5 | getValue, 6 | nextTick, 7 | debounce 8 | } from "./util"; 9 | var id = 0; 10 | class Watcher{ 11 | constructor(vm, exp, cb) { 12 | this.cb = cb; // 更新UI函数 13 | this.vm = vm; 14 | this.exp = exp; 15 | this.depIds = {}; // 数据依赖集合 16 | this.value = this.get(); 17 | this.id = id++; 18 | this.run = debounce(this.run); 19 | } 20 | update() { 21 | const val = this.get(); 22 | if (val !== this.value) { 23 | this.value = val; 24 | nextTick(() => { 25 | pushWatcher(this); 26 | }); 27 | } 28 | } 29 | run() { 30 | this.cb.call(null, this.get()); 31 | } 32 | addDep(dep) { 33 | if (!hasOwn(this.depIds, dep.id)) { 34 | dep.addSub(this); 35 | this.depIds[dep.id] = dep; 36 | } 37 | } 38 | get() { 39 | Dep.target = this; 40 | const val = getValue(this.vm, this.exp); 41 | Dep.target = null; 42 | return val; 43 | } 44 | } 45 | 46 | module.exports = Watcher; --------------------------------------------------------------------------------