UNPKG

migi

Version:

A JavaScript MVVM on JSX

1,137 lines (1,122 loc) 31.6 kB
import Event from './Event'; import Element from './Element'; import Component from './Component'; import util from './util'; import Obj from './Obj'; import Cb from './Cb'; import range from './range'; import match from './match'; import domDiff from './domDiff'; import type from './type'; import fixEvent from './fixEvent'; import attr from './attr'; import hash from './hash'; import touch from './touch'; import delegate from './delegate'; import matchUtil from './matchUtil'; import eventCaseName from './eventCaseName'; import selfClose from './selfClose'; import dev from './dev'; const TOUCH = { 'swipe': true, 'swipeleft': true, 'swiperight':true, 'swipeup': true, 'swipedown': true, 'longtap': true, 'doubletap': true }; function convertSelector(selector) { if(selector instanceof Element) { return selector.name + '[migi-uid="' + selector.__uid + '"]'; } return selector.replace(/(^|\s|,|])([A-Z][\w$]*)\b/, '$1[migi-name="$2"]'); } function find(name, children) { return findAll(name, children, true)[0] || null; } function findAll(name, children, first) { return __findAll(name, children, [], first); } function __findAll(name, children, res, first) { for(var i = 0, len = children.length; i < len; i++) { var child = children[i]; if(child instanceof Element) { res = __findEq(name, child, res, first); } else if(child instanceof Obj) { child = child.v; if(Array.isArray(child)) { res = __findAll(name, child, res, first); } else if(child instanceof Element) { res = __findEq(name, child, res, first); } } else if(Array.isArray(child)) { res = __findAll(name, child, res, first); } if(first && res.length) { break; } } return res; } function __findEq(name, child, res, first) { // cp不递归 if(child instanceof Component) { if(child instanceof name) { res.push(child); } } // vd递归 else { if(child instanceof name) { res.push(child); if(first) { return res; } } res = res.concat(child.findAll(name, first)); } return res; } class VirtualDom extends Element { constructor(uid, name, props, children) { // 自闭合标签不能有children if(selfClose.hasOwnProperty(name) && children && children.length) { throw new Error('self-close tag can not has children: ' + name); } super(uid, name, props, children); var self = this; self.__names = null; // 从Component根节点到自己的tagName列表,以便css计算 self.__classes = null; // 同上,class列表 self.__ids = null; // 同上,id列表 // self.__hover = false; // 是否处于鼠标hover状态 // self.__active = false; // 是否处于鼠标active状态 // self.__listener = null; // 添加的event的cb引用,remove时使用 // self.__init(name, children); } // @override toString() { var self = this; var res = '<' + self.__name; // 处理属性 for(var i = 0, len = self.__props.length; i < len; i++) { var item = self.__props[i]; var s = self.__renderProp(item[0], item[1]); res += s; } // 使用jaw内联css需解析 if(self.__style) { var s = self.__match(true); if(s) { if(res.indexOf(' style="') > 1) { res = res.replace(/ style="[^"]*"/, ' style="' + s + '"'); } else { res = res + ' style="' + s + '"'; } } } res += ' migi-uid="' + self.__uid + '"'; // :input要侦听数据绑定 self.__checkListener(); // 自闭合标签特殊处理 if(self.__selfClose) { return res + '/>'; } res += '>'; // 有dangerouslySetInnerHTML直接返回 if(self.props.dangerouslySetInnerHTML) { var s = self.props.dangerouslySetInnerHTML; if(s instanceof Obj) { s = s.toSourceString(); } else if(Array.isArray(s)) { s = util.joinSourceArray(s); } else { s = util.stringify(s); } res += s; } // 渲染children else { res += self.__renderChildren(); } res +='</' + self.__name + '>'; return res; } // @override preString() { var self = this; // 处理属性 for(var i = 0, len = self.__props.length; i < len; i++) { var item = self.__props[i]; self.__renderProp(item[0], item[1]); } // 使用jaw内联css需解析 if(self.__style) { self.__match(true); } // :input要侦听数据绑定 self.__checkListener(); // 渲染children self.__renderChildren(); } // 始终以缓存的props属性为准,哪怕更改了真实DOM的属性 isFirst(children) { // 本身就是Component的唯一节点 if(this.parent instanceof Component) { return true; } children = children || this.parent.children; for(var i = 0, len = children.length; i < len; i++) { var child = children[i]; if(Array.isArray(child) && child.length) { return this.isFirst(child); } else if(child == this) { return true; } else if(child instanceof VirtualDom) { return false; } else if(child instanceof Obj) { child = child.v; if(Array.isArray(child) && child.length) { return this.isFirst(child); } } } } isLast(children) { // 本身就是Component的唯一节点 if(this.parent instanceof Component) { return true; } children = children || this.parent.children; for(var i = children.length - 1; i >= 0; i--) { var child = children[i]; if(Array.isArray(child) && child.length) { return this.isLast(child); } else if(child == this) { return true; } else if(child instanceof VirtualDom) { return false; } else if(child instanceof Obj) { child = child.v; if(Array.isArray(child) && child.length) { return this.isLast(child); } } } } isEmpty() { return childEmpty(this.children); } isEnabled() { return !this.__cache.disabled; } isDisabled() { return this.__cache.disabled; } isChecked() { return this.__cache.checked; } prev() { var res = {}; getPrev(this.parent.children, this, res, function(child) { res.prev = child; }); return res.prev; } prevAll(sel) { var res = { prev: [] }; getPrev(this.parent.children, this, res, function(child) { if(sel && !matchUtil.nci(sel, child) || !sel) { res.prev.push(child); } }); return res.prev; } next() { var res = {}; getNext(this.parent.children, this, res, function(child) { res.next = child; res.done = true; }); return res.next; } nextAll(sel) { var res = { next: [] }; getNext(this.parent.children, this, res, function(child) { if(sel && !matchUtil.nci(sel, child) || !sel) { res.next.push(child); } }); return res.next; } isOnly() { return this.siblings().length == 1; } isOnlyOfType(sel) { var self = this; var all = self.siblings(); for(var i = 0, len = all.length; i < len; i++) { var item = all[i]; if(item != self && !matchUtil.nci(sel, item)) { return false; } } return true; } isFirstOfType(sel) { var prevAll = this.prevAll(sel); for(var i = 0, len = prevAll.length; i < len; i++) { if(!matchUtil.nci(sel, prevAll[i])) { return false; } } return true; } isLastOfType(sel) { var nextAll = this.nextAll(sel); for(var i = 0, len = nextAll.length; i < len; i++) { if(!matchUtil.nci(sel, nextAll[i])) { return false; } } return true; } siblings() { var parent = this.parent; var all = allChildren(parent.children); return all; } getIdx(reverse) { var siblings = this.siblings(); var i = siblings.indexOf(this); if(i > -1) { return reverse ? siblings.length - i - 1 : i; } return -1; } getIdxOfType(sel, reverse) { var siblings = reverse ? this.nextAll(sel) : this.prevAll(sel); if(reverse) { siblings.unshift(this); } else { siblings.push(this); } var i = siblings.indexOf(this); if(i > -1) { return reverse ? siblings.length - i - 1 : i; } return -1; } closest(sel) { let cur = this; while(cur && cur != this.top) { if(matchUtil.nci(sel, cur)) { cur = cur.parent; continue; } return cur; } } __renderProp(k, v) { var self = this; var res = ''; // onxxx侦听处理 if(/^on[a-zA-Z]/.test(k)) { self.__renderPropEventDelay = self.__renderPropEventDelay || []; self.__renderPropEventDelay.push({ k: k.slice(2).toLowerCase(), v }); } // Obj类型绑定处理 else if(v instanceof Obj) { // 特殊html不转义 if(k == 'dangerouslySetInnerHTML') { return ''; } var s = v.toString(true); if(k == 'className') { k = 'class'; } else if(k == 'htmlFor') { k = 'for'; } self.__cache[k] = s; // 特殊属性根据类型输出或是在DOM后设置prop var special = attr.special(self.__name, k); switch(special) { case attr.RENDER_EXIST: if(v.v) { res = ' ' + k + '="' + s + '"'; } break; case attr.RENDER_DOM: self.once(Event.DOM, function() { self.__updateAttr(k, v.v); }); break; default: res = ' ' + k + '="' + s + '"'; break; } } else { var s = Array.isArray(v) ? util.joinSourceArray(v) : util.stringify(v); if(k == 'dangerouslySetInnerHTML') { return ''; } if(k == 'className') { k = 'class'; } else if(k == 'htmlFor') { k = 'for'; } self.__cache[k] = s; // 特殊属性根据类型输出或是在DOM后设置prop var special = attr.special(self.__name, k); switch(special) { case attr.RENDER_EXIST: if(v) { res = ' ' + k + '="' + util.encodeHtml(s, true) + '"'; } break; case attr.RENDER_DOM: self.once(Event.DOM, function() { self.__updateAttr(k, v); }); break; default: res = ' ' + k + '="' + util.encodeHtml(s, true) + '"'; break; } } // 使用jaw导入样式时不输出class和id,以migi-class和migi-id取代之 if(self.__style) { switch(k) { case 'class': case 'id': res = ' ' + 'migi-' + res.slice(1); break; } } return res; } __renderChildren() { var self = this; var res = ''; for(var i = 0, len = self.children.length; i < len; i++) { res += renderChild(self.children[i]); } return res; } __checkListener() { var self = this; if(self.__name == 'input') { if(self.props.hasOwnProperty('value')) { var item = self.props.value; if(item instanceof Obj && item.vBind) { self.once(Event.DOM, function() { var type = (self.__cache.type || '').toLowerCase(); function cb(e) { fixEvent(e); var v = e.target.value; if(type == 'number') { v = parseFloat(v); } item.vBind(v); } switch(type) { // 一些无需联动 // case 'button': // case 'hidden': // case 'image': // case 'file': // case 'reset': // case 'submit': // break; // 只需侦听change case 'checkbox': case 'radio': case 'range': case 'color': self.__addListener('change', cb); break; // 其它无需change,但input等 default: self.__addListener(['input', 'paste', 'cut', 'change'], cb); break; } }); } } else if(self.props.hasOwnProperty('checked')) { var item = self.props.checked; if(item instanceof Obj && item.vBind) { self.once(Event.DOM, function() { function cb(e) { fixEvent(e); var v = e.target.checked; item.vBind(v); } var type = self.__cache.type || ''; switch(type.toLowerCase()) { case 'checkbox': case 'radio': self.__addListener('change', cb); break; } }); } } } else if(self.__name == 'option') { if(self.props.hasOwnProperty('selected')) { var item = self.props.selected; if(item instanceof Obj && item.vBind) { self.once(Event.DOM, function() { function cb(e) { fixEvent(e); var v = e.target.selected; item.vBind(v); } self.__addListener('change', cb); }); } } } // textarea的value在标签的childNodes里,这里只处理单一child情况 // children有多个其中一个是text有歧义,忽视 else if(self.__name == 'textarea') { if(self.children.length == 1) { var child = self.children[0]; if(child instanceof Obj && child.vBind) { self.once(Event.DOM, function() { function cb(e) { fixEvent(e); var v = e.target.value; child.vBind(v); } self.__addListener(['input', 'paste', 'cut', 'change'], cb); }); } } } if(self.__renderPropEventDelay) { self.__renderPropEventDelay.forEach(function(item) { self.once(Event.DOM, function() { self.__addEvt(item.k, item.v); }); }); self.__renderPropEventDelay = null; } } __addEvt(name, v) { var self = this; self.__addListener(name, function(e) { fixEvent(e); var target = e.target; var uid = target.getAttribute('migi-uid'); var tvd = hash.get(uid); if(v instanceof Cb && util.isFunction(v.cb)) { return v.cb.call(v.context, e, self, tvd); } else if(util.isFunction(v)) { return v(e, self, tvd); } else if(Array.isArray(v)) { var ret; v.forEach(function(item, i) { var cb = item[1]; var res = delegate(e, item[0], self); if(res[0]) { if(cb instanceof Cb && util.isFunction(cb.cb)) { if(i) { cb.cb.call(cb.context, e, self, res[1], tvd); } else { ret = cb.cb.call(cb.context, e, self, res[1], tvd); } } else if(util.isFunction(cb)) { if(i) { cb(e, self, res[1], tvd); } else { ret = cb(e, self, res[1], tvd); } } } }); return ret; } }); } __addListener(name, cb) { var self = this; if(Array.isArray(name)) { for(var i = 0, len = name.length; i < len; i++) { self.__addListener(name[i], cb); } } else { // 一般没有event,也就不生成对象防止diff比对 self.__listener = self.__listener || []; if(name == 'tap') { name = 'click'; } var elem = self.element; // touch特殊对待 if(TOUCH.hasOwnProperty(name)) { touch(this, name, cb, self.__listener); return; } // 记录下来留待清除 self.__listener.push([name, cb]); elem.addEventListener(eventCaseName[name] || name, cb); // onLoad可能因为缓存不发生 if(name == 'load' && elem.complete) { var event = document.createEvent('Event'); event.initEvent('load', true, true); elem.dispatchEvent(event); } } } __removeListener() { var self = this; if(self.__listener) { var elem = self.element; for(var i = self.__listener.length - 1; i >= 0; i--) { var arr = self.__listener[i]; elem.removeEventListener(arr[0], arr[1]); } self.__listener = null; } } find(selector) { if(util.isFunction(selector)) { return find(selector, this.children); } if(this.element) { var node = this.element.querySelector(convertSelector(selector)); var uid = node.getAttribute('migi-uid'); return hash.get(uid) || null; } return null; } findAll(selector) { if(util.isFunction(selector)) { return findAll(selector, this.children); } var res = []; if(this.element) { var nodes = this.element.querySelectorAll(convertSelector(selector)); Array.from(nodes).forEach(function(node) { if(node) { var uid = node.getAttribute('migi-uid'); var vd = hash.get(uid) || null; if(vd) { res.push(vd); } } }); } return res; } // @override __onDom() { super.__onDom(); var self = this; // start标明真实DOM索引,因为相邻的文本会合并为一个text节点 var option = { start: 0, first: true }; self.__checkBlank(self.children, option); // 可能最后一个是空白text,或没有children,需特殊判断下插入 if(option.empty || option.first) { self.__insertBlank(option.start); } } __checkBlank(item, option) { var self = this; if(Array.isArray(item) && item.length) { for(var i = 0, len = item.length; i < len; i++) { self.__checkBlank(item[i], option); } } else if(util.isDom(item)) { // 前面的连续的空白节点需插入一个空TextNode if(option.empty) { self.__insertBlank(option.start); } // 递归通知DOM事件,增加start索引 option.start++; // 前方文本节点需再增1次,因为文本节点自身不涉及start索引逻辑 if(option.prev == type.TEXT) { option.start++; } option.prev = type.DOM; option.empty = false; option.first = false; item.emit(Event.DOM); } else if(item instanceof Obj) { self.__checkBlank(item.v, option); } else if(isEmptyText(item)) { // 前方如有兄弟文本节点,无需插入,否则先记录empty,等后面检查是否有非空text出现,再插入空白节点 if(option.prev == type.TEXT) { return; } option.empty = true; option.prev = type.TEXT; option.first = false; } // 一旦是个非空text,之前记录的空text将无效,因为相邻的text会合并为一个text节点 else { option.empty = false; option.prev = type.TEXT; option.first = false; } } __insertBlank(start) { var blank = document.createTextNode(''); var elem = this.element; var cns = elem.childNodes; // 可能仅一个空文本节点,或最后一个空文本节点 var length = cns.length; if(!length || start >= length) { elem.appendChild(blank); } // 插入 else { elem.insertBefore(blank, cns[start]); } } // @override __onData(k, opt) { var self = this; // 尚未添加到dom时无效 if(!self.dom) { return; } // 联动属性值 for(var i = 0, len = self.__props.length; i < len; i++) { var item = self.__props[i]; var key = item[0]; item = item[1]; if(item instanceof Obj) { var change = false; var vk = Array.isArray(k) ? 1: 0; var ok = Array.isArray(item.k) ? 2 : 0; switch(vk | ok) { case 0: change = k == item.k; break; case 1: change = k.indexOf(item.k) > -1; break; case 2: change = item.k.indexOf(k) > -1; break; case 3: var hash = {}; for(var j = k.length - 1; j >= 0; j--) { hash[k[j]] = true; } for(var temp = item.k, j = 0, len = temp.length; j < len; j++) { if(hash.hasOwnProperty(temp[j])) { change = true; break; } } break; } if(change) { var ov = item.v; if(item.update(ov)) { self.__updateAttr(key, item.v); } } } } // 利用索引更新,子节点可能为文本、Component、VirtualDom,以及数组 // 其中只有文本节点需要自己更新,记录其索引,组件和VirtualDom递归通知更新 // 由于渲染时相邻的文本变量和String文本同为一个文本节点,因此start为真实DOM的索引,history和record为vd索引 // 当文本节点时start不更新 // Obj类型的判断type和count,及为文本时是否为空 var record = { start: 0, range: [], history: [], first: true }; var children = self.children; for(var index = 0, len = children.length; index < len; index++) { var child = children[index]; record.index = [index]; self.__checkObj(k, child, record, opt); } if(record.range.length) { // textarea特殊判断 if(self.__name == 'textarea') { self.__updateAttr('value', range.value(record.range[0], self.children)); return; } for(var i = 0, len = record.range.length; i < len; i++) { range.update(record.range[i], self.children, self.element); } } } // record.first标明是否第一个,因为child为数组时会展开,当child不是第1个时其展开项都有prev __checkObj(k, child, record, opt) { var self = this; // 当Component和VirtualDom则start++,且前面是非空文本节点时再++,因为有2个节点 // 文本节点本身不会增加索引,因为可能有相邻的 if(child instanceof Obj) { // 可能Obj的关联是个列表,触发的变量name也是列表 var change = false; var vk = Array.isArray(k) ? 1 : 0; var ok = Array.isArray(child.k) ? 2 : 0; switch(vk | ok) { case 0: change = k == child.k; break; case 1: change = k.indexOf(child.k) > -1; break; case 2: change = child.k.indexOf(k) > -1; break; case 3: var hash = {}; for(var i = k.length - 1; i >= 0; i--) { hash[k[i]] = true; } for(var temp = child.k, i = 0, len = temp.length; i < len; i++) { if(hash.hasOwnProperty(temp[i])) { change = true; break; } } break; } // 当可能发生变化时才进行比对 if(change) { var ov = child.v; // 对比是否真正发生变更 if(child.update(ov)) { domDiff.diff(this, this.element, ov, child.v, record, dev.optimizeArrayDiff && child.single && opt); } else { self.__checkObj(k, child.v, record, opt); } } else { self.__checkObj(k, child.v, record, opt); } } // 递归通知,增加索引 else if(util.isDom(child)) { if(child instanceof VirtualDom) { child.__onData(k, opt); } // bindProperty #37 else { child.__notifyBindProperty(k); } record.start++; // 前面的文本再加一次索引 if(!record.first && record.prev == type.TEXT) { record.start++; } record.state = type.DOM_TO_DOM; record.prev = type.DOM; } else if(Array.isArray(child)) { if(child.length) { // 数组类型记得递归记录history索引,结束后出栈 record.index.push(0); for(var i = 0, len = child.length; i < len; i++) { var item = child[i]; record.index[record.index.length - 1] = i; // 第1个同时作为children的第1个要特殊处理 self.__checkObj(k, item, record, opt); } record.index.pop(); } // 注意空数组算text类型 else { domDiff.checkText(this.element, child, record); if(record.first || record.prev == type.DOM) { domDiff.recordRange(record); } record.state = type.TEXT_TO_TEXT; record.prev = type.TEXT; } } // 其它情况为文本节点或者undefined忽略 else { domDiff.checkText(this.element, child, record); if(record.first || record.prev == type.DOM) { domDiff.recordRange(record); } record.state = type.TEXT_TO_TEXT; record.prev = type.TEXT; } record.first = false; } // TODO: 一个神奇的现象,实体字符作为attr在初始化时作为String拼接和在setAttribute中表现不一致 // 如&nbsp;会成为charCode 160的Non-breaking space,而非32的Normal space // 但是setAttribute会保留实体字符形式 __updateAttr(k, v) { if(k == 'dangerouslySetInnerHTML') { if(v === null || v === undefined) { v = ''; } this.element.innerHTML = util.stringify(v); // 清空后创建空字符节点 this.__insertBlank(0); return; } attr.update(this.__name, this.element, k, v, this.__style); this.__cache[k] = v; // 使用了jaw内联解析css if(this.__style) { this.__updateStyle(); } } __match(first) { var inline = this.__cache.style || ''; // 预处理class和id,class分为数组形式,id判断#开头 this.__initCI(); var matches = match(this.__names, this.__classes, this.__ids, this.__style || { default:{} }, this, first); // 本身的inline最高优先级追加到末尾 return matches + inline; } __initCI() { var p = this.parent; if(p instanceof VirtualDom) { this.__classes = p.__classes.slice(); this.__ids = p.__ids.slice(); } else { this.__classes = []; this.__ids = []; } // 预处理class和id,class分为数组形式,id判断#开头 this.__classes.push(matchUtil.splitClass(this.__cache['class'])); this.__ids.push(matchUtil.preId(this.__cache.id)); } __updateStyle(first) { var s = this.__match(first); if(this.element.getAttribute('style') != s) { this.element.setAttribute('style', s); } // diff调用初始化nvd时自上而下,忽略children if(first) { return; } for(var i = this.children.length - 1; i >= 0; i--) { var child = this.children[i]; if(child instanceof VirtualDom) { child.__updateStyle(); } } } __init(name, children) { var self = this; self.__selfClose = selfClose.hasOwnProperty(name); childParent(children, self); } // @Overwrite __reset(uid, name, props = [], children = []) { super.__reset(uid, name, props, children); this.__init(name, children); this.__hasDes = false; return this; } __destroy() { if(this.__onHover || this.__outHover) { if(this.element) { this.element.removeEventListener('mouseenter', this.__onHover); this.element.removeEventListener('mouseleave', this.__outHover); } } this.__hash = {}; this.__cache = null; this.__names = null; this.__classes = null; this.__ids = null; this.__hover = false; this.__active = false; this.__listener = null; this.__parent = null; this.__top = null; this.__dom = false; this.__style = null; this.__element = null; this.__renderPropEventDelay = null; return this; } // @Overwrite clean() { super.clean(); this.__renderPropEventDelay = null; this.__removeListener(); this.__names = null; this.__classes = null; this.__ids = null; this.__hover = false; this.__active = false; } get names() { return this.__names || (this.__names = []); } get element() { return this.__element || (this.__element = document.querySelector(this.__name + '[migi-uid="' + this.__uid + '"]')); } get style() { return this.__style; } set style(v) { var self = this; self.__style = v; if(self.parent instanceof VirtualDom) { self.__names = self.parent.names.slice(); } else { self.__names = []; } self.__names.push(self.__name); for(var i = 0, len = self.children.length; i < len; i++) { childStyle(self.children[i], v); } } } // 静态文本节点,包括空、undefined、null、空数组 function isEmptyText(item) { return item === undefined || item === null || !item.toString(); } function renderChild(child) { if(child instanceof Element || child instanceof Obj) { return child.toString(); } if(Array.isArray(child)) { var res = ''; for(var i = 0, len = child.length; i < len; i++) { res += renderChild(child[i]); } return res; } return util.encodeHtml(util.stringify(child)); } function childParent(child, parent) { if(Array.isArray(child)) { for(var i = 0, len = child.length; i < len; i++) { childParent(child[i], parent); } } else if(child instanceof Element) { child.__parent = parent; } else if(child instanceof Obj) { childParent(child.v, parent); } } function childStyle(child, style) { if(Array.isArray(child)) { for(var i = 0, len = child.length; i < len; i++) { childStyle(child[i], style); } } else if(child instanceof VirtualDom) { child.style = style; } else if(child instanceof Obj) { childStyle(child.v, style); } } function childEmpty(child) { var res = true; if(Array.isArray(child)) { for(var i = 0, len = child.length; i < len; i++) { res = childEmpty(child[i]); if(!res) { break; } } } else if(child instanceof Element) { res = false; } else if(child instanceof Obj) { res = childEmpty(child.v); } else { res = isEmptyText(child); } return res; } function getPrev(child, target, res, cb) { if(Array.isArray(child)) { for(var i = 0, len = child.length; i < len; i++) { getPrev(child[i], target, res, cb); if(res.done) { break; } } } else if(child instanceof Element) { if(target == child) { res.done = true; return; } cb(child); } else if(child instanceof Obj) { getPrev(child.v, target, res, cb); } } function getNext(child, target, res, cb) { if(Array.isArray(child)) { for(var i = 0, len = child.length; i < len; i++) { getNext(child[i], target, res, cb); if(res.done) { break; } } } else if(child instanceof Element) { if(target == child) { res.start = true; } else if(res.start) { cb(child); } } else if(child instanceof Obj) { getNext(child.v, target, res, cb); } } function allChildren(child, res = []) { if(Array.isArray(child)) { for(var i = 0, len = child.length; i < len; i++) { allChildren(child[i], res); } } else if(child instanceof Element) { res.push(child); } else if(child instanceof Obj) { allChildren(child.v, res); } return res; } export default VirtualDom;