Open RainZhai opened 7 years ago
对一个现有demo进行修改和注释,方便阅读
<!DOCTYPE html> <html> <head> </head> <body> <!--多维数组--> <div id="root"> <div v-model="user"></div> <ul v-list="todos"> <h1 v-model="title"></h1> <div> <li v-list-item="todos"> <p v-class="todos:done" v-model="todos:creator"></p> <p v-model="todos:content"></p> <ul v-list="todos:members"> <li v-list-item="todos:members"> <span v-model="todos:members:name"></span> </li> </ul> </li> </div> </ul> </div> <!--双向数据绑定--> <div id="bind"> <h1 v-model="title"></h1> <input v-model="content" v-event="input" type="text" /> <p v-model="content"></p> <p v-model="content"></p> </div> <!--数组深度嵌套--> <div id="arr"> <ul v-list="todos:words:arr"> <li v-list-item="todos:words:arr"> <div> <span v-model="todos:words:arr"></span> </div> </li> </ul> </div> </body> <script> class mvvm { constructor(mvvm) { if (!mvvm.el || !mvvm.data) { throw new Error('mvvm need an object to observe.'); } //数据 this.$data = mvvm.data; //观察器 this.$watcher = new Watcher(); //查询dom对象 this.$el = document.querySelector(mvvm.el); //事件列表 this.$eventList = mvvm.eventList; //dom片段 this.$fragment = this.nodeToFragment(this.$el); //解析dom片段 this.parser(this.$fragment); //dom追加到页面 this.$el.appendChild(this.$fragment); //开始观察 this.$watcher.build(); } /** 获取节点并将节点下子内容追加到一个DocumentFragment */ nodeToFragment(el) { const fragment = document.createDocumentFragment(); let child = el.firstChild; while (child) { fragment.appendChild(child); child = el.firstChild; } return fragment; } /** 扫描并解析html节点 */ parser(node) { if (node === this.$fragment || !node.getAttribute('v-list')) { let childs = []; //将子节点转为数组 if (node.children) { childs = [...node.children]; } else { [...node.childNodes].forEach((child) => { if (child.nodeType === 1) { childs.push(child); } }); } console.log(childs); childs.forEach((child) => { //设置path if (node.path) { child.path = node.path; } this.parseEvent(child); this.parseClass(child); this.parseModel(child); if (child.children.length) { //解析子节点 this.parser(child); } }); } else { this.parseList(node); } } /** 解析数据 * param 属性值{string} * param 节点对象{HTMLElement} * return 对象{path:属性解析为数组['todos',':','creator'], data:数据对象对应的值} */ parseData(str, node) { const list = str.split(':'); let data; let nowPath; let arrayCounter = 1; const path = []; list.forEach((key, index) => { if (index === 0) { data = this.$data[key]; } else if (node.path) { nowPath = node.path[arrayCounter]; arrayCounter += 1; if (nowPath === key) { data = data[key]; } else { path.push(nowPath); data = data[nowPath][key]; arrayCounter += 1; } } else { data = data[key]; } path.push(key); }); if (node.path && node.path.length > path.length) { const lastPath = node.path[node.path.length - 1]; //if (typeof lastPath === 'number') { data = data[lastPath]; path.push(lastPath); //} } //if (!node.path || node.path !== path) { node.path = path; //} return { path, data }; } parseEvent(node) { if (node.getAttribute('v-event')) { const eventName = node.getAttribute('v-event'); const type = this.$eventList[eventName].type; const fn = this.$eventList[eventName].fn.bind(node); if (type === 'input') { let cmp = false; node.addEventListener('compositionstart', () => { cmp = true; }); node.addEventListener('compositionend', () => { cmp = false; node.dispatchEvent(new Event('input')); }); node.addEventListener('input', function input() { if (!cmp) { const start = this.selectionStart; const end = this.selectionEnd; fn(); this.setSelectionRange(start, end); } }); } else { node.addEventListener(type, fn); } } } /* * 解析自定class属性 并添加class * param html节点{HTMLElement} * */ parseClass(node) { if (node.getAttribute('v-class')) { const className = node.getAttribute('v-class'); const dataObj = this.parseData(className, node); if (!node.classList.contains(dataObj.data)) { node.classList.add(dataObj.data); } this.$watcher.regist(this.$data, dataObj.path, (old, now) => { node.classList.remove(old); node.classList.add(now); }); } } parseModel(node) { if (node.getAttribute('v-model')) { const modelName = node.getAttribute('v-model'); const dataObj = this.parseData(modelName, node); if (node.tagName === 'INPUT') { node.value = dataObj.data; } else { node.innerText = dataObj.data; } this.$watcher.regist(this.$data, dataObj.path, (old, now) => { if (node.tagName === 'INPUT') { node.value = now; } else { node.innerText = now; } }); } } parseList(node) { console.log('NODE') console.log(node.path) const parsedItem = this.parseListItem(node); const itemEl = parsedItem.itemEl; const parentEl = parsedItem.parentEl; const list = node.getAttribute('v-list'); const listData = this.parseData(list, node); listData.data.forEach((_dataItem, index) => { const copyItem = itemEl.cloneNode(true); copyItem.$data = _dataItem; //if (node.path) { copyItem.path = node.path.slice(); //} copyItem.path.push(index); this.parseEvent(copyItem); this.parseClass(copyItem); this.parseModel(copyItem); this.parser(copyItem); parentEl.insertBefore(copyItem, itemEl); }); parentEl.removeChild(itemEl); this.$watcher.regist(this.$data, listData.path, () => { while (parentEl.firstChild) { parentEl.removeChild(parentEl.firstChild); } const thisListData = this.parseData(list, node); parentEl.appendChild(itemEl); thisListData.data.forEach((_dataItem, index) => { const copyItem = itemEl.cloneNode(true); copyItem.$data = _dataItem; if (node.path) { copyItem.path = node.path.slice(); } copyItem.path.push(index); this.parseEvent(copyItem); this.parseClass(copyItem); this.parseModel(copyItem); this.parser(copyItem); parentEl.insertBefore(copyItem, itemEl); }); parentEl.removeChild(itemEl); }); } parseListItem(node) { const me = this; let target; (function getItem(nodeToScan) { [...nodeToScan.children].forEach((thisNode) => { if (nodeToScan.path) { thisNode.path = nodeToScan.path.slice(); } if (thisNode.getAttribute('v-list-item')) { target = { itemEl: thisNode, parentEl: nodeToScan } } else { me.parseEvent(thisNode); me.parseClass(thisNode); me.parseModel(thisNode); getItem(thisNode); } }); }(node)); return target; } } class Watcher { constructor() { this.routes = []; } regist(obj, k, fn) { const route = this.routes.find((el) => { let result; if ((el.key === k || el.key.toString() === k.toString()) && Object.is(el.obj, obj)) { result = el; } return result; }); if (route) { route.fn.push(fn); } else { this.routes.push({ obj, key: k, fn: [fn], }); } } //对每个route的object 进行观察 build() { this.routes.forEach((route) => { observer(route.obj, route.key, route.fn); }); } } /** 观察数据变化 * param data 数据对象{Object} * param path 节点属性数组或对象{Array || Object} * param 回调函数{Function} */ function observer(obj, k, callback) { if (!obj || (!k && k !== 0)) { throw new Error('Please pass an object to the observer.'); } if (Object.prototype.toString.call(k) === '[object Array]') { observePath(obj, k, callback); } else { let old = obj[k]; if (!old) { throw new Error('The key to observe is undefined.'); } if (Object.prototype.toString.call(old) === '[object Array]') { observeArray(old, callback); } else if (old.toString() === '[object Object]') { observeAllKey(old, callback); } else { /** 当数据发生变化时会自动调用set方法, 获取数据时会自动调用get方法 */ Object.defineProperty(obj, k, { enumerable: true, configurable: true, get: () => old, set: (now) => { if (now !== old) { callback.forEach((fn) => { fn(old, now); }); } old = now; }, }); } } } /* * 对path数组进行处理后再调用observer * param data 数据对象{Object} * param path 节点属性数组{Array} * param 回调函数{Function} */ function observePath(obj, paths, callback) { let nowPath = obj; let key; paths.forEach((path, index) => { const path2Num = parseInt(path, 10); let pathArr = path; if (path2Num === pathArr) { pathArr = path2Num; } if (index < paths.length - 1) { nowPath = nowPath[pathArr]; } else { key = pathArr; } }); observer(nowPath, key, callback); } /* * 观察对数组对象操作的处理 * param Array 数组对象{Array} * param 回调函数{Function} */ function observeArray(arr, callback) { const oam = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']; const arrayProto = Array.prototype; const hackProto = Object.create(Array.prototype); oam.forEach((method) => { Object.defineProperty(hackProto, method, { writable: true, enumerable: true, configurable: true, value: function operateArray(...arg) { const old = arr.slice(); const now = arrayProto[method].call(this, ...arg); callback.forEach((fn) => { fn(old, this, ...arg); }); return now; }, }); }); Object.setPrototypeOf(arr, hackProto); } function observeAllKey(obj, callback) { Object.keys(obj).forEach((key) => { observer(obj, key, callback); }); } let data = { title: 'todo', user: 'mirone', todos: [{ creator: 'keal', done: 'undone', content: 'writeMVVM', members: [{ name: 'kaito' }] }] } new mvvm({ el: '#root', data }) data.todos[0].done = 'done' data.todos[0].members[0].name = 'eeeee' data.todos[0].members.push({ name: 'hitoriqqqq' }) data.title = 'todo list!' data.todos.push({ creator: 'k', done: 'undone', content: 'get mvvm', members: [{ name: 'hito' }] }) data.todos[1].members.push({ name: 'asuna' }) //============ let bind = { title: 'data bind', content: 'qaq' } let e = { 'input': { type: 'input', fn: function() { bind.content = this.value } } } new mvvm({ el: '#bind', data: bind, eventList: e }) //=============== let arr = { todos: { words: { arr: ['It\'s been a long day', 'with out you', 'my friend'] } } } new mvvm({ el: '#arr', data: arr, }) arr.todos.words.arr.push('hello') </script> </html>
对一个现有demo进行修改和注释,方便阅读