UNPKG

migi

Version:

A JavaScript MVVM on JSX

871 lines (857 loc) 25.3 kB
import Event from './Event'; import Component from './Component'; import Cb from './Cb'; import util from './util'; import browser from './browser'; import range from './range'; import cachePool from './cachePool'; import type from './type'; import hash from './hash'; import matchHash from './matchHash'; import fixEvent from './fixEvent'; import delegate from './delegate'; import Obj from './Obj'; function replaceWith(elem, cns, index, vd, isText) { // insertAdjacentHTML在插入text时浏览器行为表现不一致,ff会合并相邻text,chrome则不会 // 因此DOM使用insertAdjacentHTML,text则用textNode var target; if(isText) { var s = util.stringify(vd); target = document.createTextNode(s || ''); elem.replaceChild(target, cns[index]); } else { target = vd.toString(); // textNode没有insertAdjacentHTML方法,必须使用replaceChild if(cns[index].nodeType == 1) { cns[index].insertAdjacentHTML('afterend', target); elem.removeChild(cns[index]); } else { var node = browser.getParent(vd.__name); node.innerHTML = target; elem.replaceChild(node.firstChild, cns[index]); } // 别忘了触发DOM事件 vd.emit(Event.DOM); } } function insertAt(elem, cns, index, vd, isText) { var target; if(isText) { var s = util.stringify(vd); target = document.createTextNode(s || ''); if(index >= cns.length) { elem.appendChild(target); } else { elem.insertBefore(target, cns[index]); } } else { target = vd.toString(); if(index >= cns.length) { elem.insertAdjacentHTML('beforeend', target); } else { if(cns[index].nodeType == 1) { cns[index].insertAdjacentHTML('beforebegin', target); } else { var node = browser.getParent(vd.__name); node.innerHTML = target; elem.insertBefore(node.firstChild, cns[index]); } } // 别忘了触发DOM事件 vd.emit(Event.DOM); } } function add(parent, elem, vd, record, temp, last) { if(Array.isArray(vd)) { record.index.push(0); // 防止空数组跳过的情况 for(var i = 0, len = Math.max(vd.length, 1); i < len; i++) { var item = vd[i]; record.index[record.index.length - 1] = i; add(parent, elem, item, record, temp, last && i == len - 1); } record.index.pop(); } else if(util.isDom(vd)) { vd.__parent = parent; vd.__top = parent.top; vd.style = parent.style; hash.set(vd); if(temp.prev) { if(temp.prev == type.TEXT) { record.start++; } insertAt(elem, elem.childNodes, record.start++, vd); } else { switch(record.state) { case type.DOM_TO_TEXT: case type.TEXT_TO_TEXT: // 第一次添加dom时,即使之前的text没有变化,也需记录range,但可能影响之后的t2t,也需记录 addRange(record); record.start++; break; case type.TEXT_TO_DOM: break; case type.DOM_TO_DOM: break; } insertAt(elem, elem.childNodes, record.start++, vd); } temp.prev = type.DOM; } else { if(temp.prev) { if(temp.prev == type.DOM) { insertAt(elem, elem.childNodes, record.start, vd, true); recordRange(record); } addRange(record); } else { switch(record.state) { case type.DOM_TO_TEXT: case type.TEXT_TO_TEXT: addRange(record); break; case type.TEXT_TO_DOM: insertAt(elem, elem.childNodes, record.start, vd, true); recordRange(record); addRange(record); break; case type.DOM_TO_DOM: insertAt(elem, elem.childNodes, record.start, vd, true); recordRange(record); break; } } temp.prev = type.TEXT; } // add结束后,根据之前的state和最后add的d/t假设出当前等同的状态 if(last) { switch(record.state) { case type.TEXT_TO_TEXT: if(temp.prev == type.DOM) { record.state = type.TEXT_TO_DOM; } break; case type.DOM_TO_DOM: if(temp.prev == type.TEXT) { record.state = type.DOM_TO_TEXT; } break; case type.TEXT_TO_DOM: if(temp.prev == type.TEXT) { record.state = type.TEXT_TO_TEXT; } break; case type.DOM_TO_TEXT: if(temp.prev == type.DOM) { record.state = type.DOM_TO_DOM; } break; } record.prev = temp.prev; } } function del(elem, vd, record, temp, last) { if(Array.isArray(vd)) { for(var i = 0, len = vd.length; i < len; i++) { del(elem, vd[i], record, temp, last && i == len - 1); } } else if(util.isDom(vd)) { switch(record.state) { case type.DOM_TO_TEXT: case type.TEXT_TO_TEXT: removeAt(elem, record.start + 1); break; case type.TEXT_TO_DOM: case type.DOM_TO_DOM: removeAt(elem, record.start); break; } temp.prev = type.DOM; // 缓存对象池 // 遍历孩子vd回收 util.getAllChildrenElement(vd).forEach((item) => { cachePool.add(item.__destroy()); }); cachePool.add(vd.__destroy()); } else { if(temp.prev) { if(temp.prev == type.DOM) { switch(record.state) { case type.DOM_TO_TEXT: removeAt(elem, record.start + 1); addRange(record); break; case type.TEXT_TO_TEXT: removeAt(elem, record.start + 1); addRange(record); break; case type.DOM_TO_DOM: removeAt(elem, record.start); break; case type.TEXT_TO_DOM: removeAt(elem, record.start); break; } } // 删过text,之后的text自动一并删除,可以忽略 // else {} } else { switch(record.state) { case type.DOM_TO_TEXT: removeAt(elem, record.start + 1); addRange(record); break; case type.TEXT_TO_TEXT: addRange(record); break; case type.DOM_TO_DOM: removeAt(elem, record.start); break; case type.TEXT_TO_DOM: break; } } temp.prev = type.TEXT; } if(last) { switch(record.state) { case type.TEXT_TO_TEXT: if(temp.prev == type.DOM) { record.state = type.DOM_TO_TEXT; } else { addRange(record); } break; case type.DOM_TO_DOM: if(temp.prev == type.TEXT) { record.state = type.TEXT_TO_DOM; } break; case type.TEXT_TO_DOM: if(temp.prev == type.DOM) { record.state = type.DOM_TO_DOM; } break; case type.DOM_TO_TEXT: if(temp.prev == type.TEXT) { record.state = type.TEXT_TO_TEXT; } break; } } } function removeAt(elem, start) { // 当table省略tbody直接写tr时,浏览器可能会自动生成tbody节点,diff时不在对比内会造成bug,提前判断下 if(elem.childNodes[start]) { elem.removeChild(elem.childNodes[start]); } } function equalText(a, b) { if(a === undefined || a === null) { a = ''; } if(b === undefined || b === null) { b = ''; } return a.toString() == b.toString(); } function recordRange(record) { record.history = record.index.slice(); } function addRange(record) { let start = record.start; // 连续text更新防止重复,它们的dom索引start相同 if(record.range.length && record.range[record.range.length - 1].start == start) { return; } record.range.push({ start, index: record.history.slice(), }); } function diffVd(ovd, nvd) { // 相同引用说明没发生变更,在一些使用常量、变量未变的情况下会如此 if(ovd == nvd) { return; } // 特殊的uid,以及一些引用赋给新vd var elem = ovd.element; var props = ['__uid', '__element', '__parent', '__top', '__style', '__dom', '__names']; var i = props.length - 1; for(; i >= 0; i--) { var k = props[i]; nvd[k] = ovd[k]; } // vd记录更新uid引用 hash.set(nvd); // 记录对比过的prop var temp = {}; for(i = ovd.__props.length - 1; i >= 0; i--) { var item = ovd.__props[i]; var k = item[0]; // 只检查普通属性,onXXX事件由__listener中的引用移除 if(k.indexOf('on') != 0 || k == 'on') { temp[k] = true; // 对比老属性,多余删除,相同无需更新 if(nvd.props.hasOwnProperty(k)) { var v = item[1]; var nv = nvd.props[k]; if(nv instanceof Obj) { nv = nv.v; } if(v instanceof Obj) { v = v.v; } if(nv !== v) { nvd.__updateAttr(k, nv); } nvd.__cache[k] = nv; } else { nvd.__updateAttr(k, null); delete nvd.__cache[k]; } } } // 移除__listener记录的引用 ovd.__removeListener(); // 添加新vd的属性 var len = nvd.__props.length; for(i = 0; i < len; i++) { var item = nvd.__props[i]; var k = item[0]; let v = item[1]; if(v instanceof Obj) { v = v.v; } // 事件和属性区分对待 if(k.indexOf('on') == 0 && k != 'on') { var name = k.slice(2).toLowerCase(); nvd.__addListener(name, function(e) { e = e || window.event; fixEvent(e); var target = e.target; var uid = target.getAttribute('migi-uid'); var tvd = hash.get(uid); if(v instanceof Cb) { v.cb.call(v.context, e, nvd, tvd); } else if(util.isFunction(v)) { v(e, nvd, tvd); } else if(Array.isArray(v)) { v.forEach(function(item) { var cb = item[1]; if(delegate(e, item[0], nvd)) { if(cb instanceof Cb) { cb.cb.call(cb.context, e, nvd, tvd); } else if(util.isFunction(cb)) { cb(e, nvd, tvd); } } }); } }); } else if(!temp.hasOwnProperty(k)) { nvd.__updateAttr(k, v); } } if(nvd.__style) { nvd.__updateStyle(true); } var record = { start: 0, index: [], range: [], first: true }; diffChild(nvd, elem, ovd.children, nvd.children, record); if(record.range.length) { // textarea特殊判断 if(nvd.__name == 'textarea') { nvd.__updateAttr('value', range.value(record.range[0], nvd.children)); return; } for(var i = 0, len = record.range.length; i < len; i++) { range.update(record.range[i], nvd.children, elem); } } // 缓存对象池 cachePool.add(ovd.__destroy()); } function diff(parent, elem, ov, nv, record, opt) { if(opt) { diffArray(parent, elem, ov, nv, record, opt); } else { diffChild(parent, elem, ov, nv, record); } } function diffChild(parent, elem, ovd, nvd, record) { if(ovd instanceof Obj) { ovd = ovd.v; } if(nvd instanceof Obj) { nvd = nvd.v; } // 新老值是否是数组处理方式不同 var oa = Array.isArray(ovd); var na = Array.isArray(nvd); // 都是数组时,还要检查二者长度 if(oa && na) { var ol = ovd.length; var nl = nvd.length; var os = ol ? 1 : 0; var ns = nl ? 2 : 0; record.index.push(0); switch(os | ns) { // 都是空数组 case 0: if(record.state == type.TEXT_TO_DOM) { insertAt(elem, elem.childNodes, record.start++, nvd, true); } record.state = type.TEXT_TO_TEXT; record.prev = type.TEXT; break; // 有内容的数组变为空数组 case 1: diffChild(parent, elem, ovd[0], nvd[0], record); var temp = {}; for(var i = 1; i < ol; i++) { del(elem, ovd[i], record, temp, i == ol - 1); } break; // 空数组变为有内容 case 2: diffChild(parent, elem, ovd[0], nvd[0], record); var temp = {}; for(var i = 1; i < nl; i++) { record.index[record.index.length - 1] = i; add(parent, elem, nvd[i], record, temp, i == nl - 1); } break; // 都有内容 case 3: for(var i = 0, len = Math.min(ol, nl); i < len; i++) { record.index[record.index.length - 1] = i; diffChild(parent, elem, ovd[i], nvd[i], record); } var temp = {}; // 老的多余的删除 if(i < ol) { for(; i < ol; i++) { del(elem, ovd[i], record, temp, i == ol - 1); } } // 新的多余的插入 else if(i < nl) { for(; i < nl; i++) { record.index[record.index.length - 1] = i; add(parent, elem, nvd[i], record, temp, i == nl - 1); } } break; } record.index.pop(); } // 老的是数组新的不是 else if(oa) { // 将老的第1个和新的相比,注意老的第一个可能还是个数组,递归下去 diffChild(parent, elem, ovd[0], nvd, record); // 移除剩余的老的 var temp = {}; for(var i = 1, len = ovd.length; i < len; i++) { del(elem, ovd[i], record, temp, i == len - 1); } } // 新的是数组老的不是 else if(na) { record.index.push(0); // 将新的第1个和老的相比,注意新的第一个可能还是个数组,递归下去 diffChild(parent, elem, ovd, nvd[0], record); var temp = {}; // 增加剩余的新的 for(var i = 1, len = nvd.length; i < len; i++) { record.index[record.index.length - 1] = i; add(parent, elem, nvd[i], record, temp, i == len - 1); } record.index.pop(); } // 都不是数组 else { var oe = util.isDom(ovd) ? 1 : 0; var ne = util.isDom(nvd) ? 2 : 0; var cns = elem.childNodes; switch(oe | ne) { // 都是text时,根据上个状态设置range case 0: if(record.first) { // 先尝试记录range,连续的text会去重,始终以第一个text为准,后续的防重不会被记录 // 记录后只有text发生改变时才会将这条记录状态改变 recordRange(record); if(!equalText(ovd, nvd)) { addRange(record); } } else { switch(record.state) { case type.DOM_TO_TEXT: elem.removeChild(cns[record.start + 1]); // 因之前发生的d2t变更,本次t2t无需对比直接记录 addRange(record); break; case type.TEXT_TO_DOM: insertAt(elem, cns, record.start, nvd, true); recordRange(record); // 因之前发生t2d变更,即便本次没有发生变化,但可能影响之后的t2t,也需记录 addRange(record); break; case type.DOM_TO_DOM: recordRange(record); case type.TEXT_TO_TEXT: if(!equalText(ovd, nvd)) { addRange(record); } break; } } record.state = type.TEXT_TO_TEXT; record.prev = type.TEXT; break; // DOM变TEXT case 1: ovd.__delRef(); if(record.first) { replaceWith(elem, cns, record.start, nvd, true); recordRange(record); } else { switch(record.state) { case type.DOM_TO_TEXT: case type.TEXT_TO_TEXT: addRange(record); elem.removeChild(cns[record.start + 1]); break; case type.TEXT_TO_DOM: case type.DOM_TO_DOM: replaceWith(elem, cns, record.start, nvd, true); recordRange(record); break; } } // 遍历孩子vd回收 util.getAllChildrenElement(ovd).forEach((item) => { cachePool.add(item.__destroy()); }); // 缓存对象池 cachePool.add(ovd.__destroy()); record.state = type.DOM_TO_TEXT; record.prev = type.TEXT; break; // TEXT变DOM case 2: // 这种情况下相当于add新vd,无parent和style引用 nvd.__parent = parent; nvd.__top = parent.top; nvd.style = parent.style; hash.set(nvd); if(record.first) { replaceWith(elem, cns, record.start++, nvd); } else { switch(record.state) { case type.DOM_TO_TEXT: record.start++; case type.DOM_TO_DOM: replaceWith(elem, cns, record.start++, nvd); break; case type.TEXT_TO_DOM: insertAt(elem, cns, record.start++, nvd); break; case type.TEXT_TO_TEXT: addRange(record); insertAt(elem, cns, ++record.start, nvd); record.start++; break; } } record.state = type.TEXT_TO_DOM; record.prev = type.DOM; break; // DOM变DOM case 3: switch(record.state) { // case type.DOM_TO_DOM: // case type.TEXT_TO_DOM: // break; case type.DOM_TO_TEXT: case type.TEXT_TO_TEXT: record.start++; break; } var ocp = ovd instanceof Component ? 1 : 0; var ncp = nvd instanceof Component ? 2 : 0; switch(ocp | ncp) { // DOM名没变递归diff,否则重绘 case 0: ovd.__delRef(); if(ovd.__name == nvd.__name) { ovd.__parent = parent; ovd.__top = parent.top; diffVd(ovd, nvd); nvd.__saveRef(); } else { nvd.__parent = parent; nvd.__top = parent.top; nvd.style = parent.style; elem = ovd.element; elem.insertAdjacentHTML('afterend', nvd.toString()); elem.parentNode.removeChild(elem); nvd.emit(Event.DOM); matchHash.del(ovd.__uid); hash.set(nvd); // 缓存对象池 cachePool.add(ovd.__destroy()); } break; // Component和VirtualDom变化则直接重绘 default: ovd.__delRef(); elem = ovd.element; elem.insertAdjacentHTML('afterend', nvd.toString()); elem.parentNode.removeChild(elem); nvd.__parent = parent; nvd.__top = parent.top; // match中为模拟style的:active伪类注册了window的一些事件,需检查移除 if(ocp) { matchHash.del(ovd.virtualDom.__uid); } else { matchHash.del(ovd.__uid); } nvd.style = parent.style; nvd.emit(Event.DOM); hash.set(nvd); // 遍历孩子vd回收 util.getAllChildrenElement(ocp ? ovd.__virtualDom : ovd).forEach((item) => { cachePool.add(item.__destroy()); }); // 缓存对象池 cachePool.add(ovd.__destroy()); break; } record.state = type.DOM_TO_DOM; record.prev = type.DOM; record.start++; break; } } record.first = false; } function checkText(elem, vd, record) { if(record.state == type.TEXT_TO_DOM) { insertAt(elem, elem.childNodes, record.start, vd, true); recordRange(record); addRange(record); } else if(record.state == type.DOM_TO_TEXT) { addRange(record); removeAt(elem, record.start + 1); } } function diffArray(parent, elem, ovd, nvd, record, opt) { if(ovd instanceof Obj) { ovd = ovd.v; } if(nvd instanceof Obj) { nvd = nvd.v; } var ol = ovd.length; var nl = nvd.length; var os = ol ? 1 : 0; var ns = nl ? 2 : 0; record.index.push(0); switch(os | ns) { // 都是空数组 case 0: if(record.state == type.TEXT_TO_DOM) { insertAt(elem, elem.childNodes, record.start++, nvd, true); } record.state = type.TEXT_TO_TEXT; record.prev = type.TEXT; break; // 有内容的数组变为空数组 case 1: diffChild(parent, elem, ovd[0], nvd[0], record); var temp = {}; for(var i = 1; i < ol; i++) { del(elem, ovd[i], record, temp, i == ol - 1); } break; // 空数组变为有内容 case 2: diffChild(parent, elem, ovd[0], nvd[0], record); var temp = {}; for(var i = 1; i < nl; i++) { record.index[record.index.length - 1] = i; add(parent, elem, nvd[i], record, temp, i == nl - 1); } break; // 都有内容 case 3: var oFirst = util.arrFirst(ovd); var nFirst = util.arrFirst(nvd); var ot = util.isDom(oFirst) ? 1 : 0; var nt = util.isDom(nFirst) ? 2 : 0; var temp = {}; switch(opt.method) { case 'push': if(!record.first && nt == 0) { record.index[record.index.length - 1] = 0; checkText(elem, nFirst, record); } for(var i = 0; i < ol; i++) { record.index[record.index.length - 1] = i; scan(elem, nvd[i], record); } // 可能push多个参数 for(; i < nl; i++) { record.index[record.index.length - 1] = i; add(parent, elem, nvd[i], record, temp, i == nl - 1); } break; case 'pop': if(!record.first && nt == 0) { record.index[record.index.length - 1] = 0; checkText(elem, nFirst, record); } for(var i = 0; i < nl; i++) { record.index[record.index.length - 1] = i; scan(elem, nvd[i], record); } del(elem, ovd[nl], record, {}, true); break; case 'unshift': if(record.first) { record.state = type.DOM_TO_DOM; } for(var i = 0; i < nl - ol; i++) { record.index[record.index.length - 1] = i; add(parent, elem, nvd[i], record, temp, i == nl - ol - 1); } if(ot == 0) { record.index[record.index.length - 1] = i; checkText(elem, oFirst, record); } for(; i < nl; i++) { record.index[record.index.length - 1] = i; scan(elem, nvd[i], record); } break; case 'shift': if(record.first) { record.state = type.DOM_TO_DOM; } del(elem, ovd[0], record, {}, true); if(nt == 0) { record.index[record.index.length - 1] = 0; checkText(elem, nFirst, record); } for(var i = 0; i < nl; i++) { record.index[record.index.length - 1] = i; scan(elem, nvd[i], record); } break; case 'splice': let index = opt.args[0]; let delLen = opt.args[1]; let addLen = opt.args.length - 2; for(var i = 0; i < index; i++) { record.index[record.index.length - 1] = i; scan(elem, nvd[i], record); } if(record.first) { record.state = type.DOM_TO_DOM; } for(; i < Math.min(delLen, addLen) + index; i++) { record.index[record.index.length - 1] = i; diffChild(parent, elem, ovd[i], nvd[i], record); } if(delLen > addLen) { for(var j = i; j < delLen + index; j++) { del(elem, ovd[j], record, temp, j == delLen + index - 1); } } else if(delLen < addLen) { for(; i < addLen + index; i++) { record.index[record.index.length - 1] = i; add(parent, elem, nvd[i], record, temp, i == addLen + index - 1); } } if(i < nl) { nFirst = util.arrFirst(nvd[i]); if(!util.isDom(nFirst)) { record.index[record.index.length - 1] = i; checkText(elem, nFirst, record); } } for(; i < nl; i++) { record.index[record.index.length - 1] = i; scan(elem, nvd[i], record); } break; } break; } record.index.pop(); record.first = false; } function scan(elem, vd, record) { if(vd instanceof Obj) { vd = vd.v; } if(Array.isArray(vd)) { record.index.push(0); for(var i = 0, len = vd.length; i < len; i++) { record.index[record.index.length - 1] = i; scan(elem, vd[i], record); } record.index.pop(); } else { if(util.isDom(vd)) { if(record.prev == type.TEXT) { record.start++; } record.state = type.DOM_TO_DOM; record.prev = type.DOM; record.start++; } else { if(record.first || record.prev == type.DOM) { recordRange(record); } record.state = type.TEXT_TO_TEXT; record.prev = type.TEXT; } record.first = false; } } export default { diff, checkText, recordRange, addRange, };