import { Anot, createFragment, platform, isObject, ap, makeHashCode } from '../seed/core' import { VFragment } from '../vdom/VFragment' import { $$skipArray } from '../vmodel/reserved' import { addScope, makeHandle } from '../parser/index' import { updateView } from './duplex/share' var rforAs = /\s+as\s+([$\w]+)/ var rident = /^[$a-zA-Z_][$a-zA-Z0-9_]*$/ var rinvalid = /^(null|undefined|NaN|window|this|\$index|\$id)$/ var rargs = /[$\w_]+/g Anot.directive('for', { delay: true, priority: 3, beforeInit: function() { var str = this.expr, asName str = str.replace(rforAs, function(a, b) { /* istanbul ignore if */ if (!rident.test(b) || rinvalid.test(b)) { console.error( 'alias ' + b + ' is invalid --- must be a valid JS identifier which is not a reserved name.' ) } else { asName = b } return '' }) var arr = str.split(' in ') var kv = arr[0].match(rargs) if (kv.length === 1) { //确保Anot._each的回调有三个参数 kv.unshift('$key') } this.expr = arr[1] this.keyName = kv[0] this.valName = kv[1] this.signature = makeHashCode('for') if (asName) { this.asName = asName } delete this.param }, init: function() { var cb = this.userCb if (typeof cb === 'string' && cb) { var arr = addScope(cb, 'for') var body = makeHandle(arr[0]) this.userCb = new Function( '$event', 'var __vmodel__ = this\nreturn ' + body ) } this.node.forDir = this //暴露给component/index.js中的resetParentChildren方法使用 this.fragment = [ '
', this.fragment, '
' ].join('') this.cache = {} }, diff: function(newVal, oldVal) { /* istanbul ignore if */ if (this.updating) { return } this.updating = true var traceIds = createFragments(this, newVal) if (this.oldTrackIds === void 0) return true if (this.oldTrackIds !== traceIds) { this.oldTrackIds = traceIds return true } }, update: function() { if (!this.preFragments) { this.fragments = this.fragments || [] mountList(this) } else { diffList(this) updateList(this) } if (this.userCb) { var me = this setTimeout(function() { me.userCb.call(me.vm, { type: 'rendered', target: me.begin.dom, signature: me.signature }) }, 0) } delete this.updating }, beforeDispose: function() { this.fragments.forEach(function(el) { el.dispose() }) } }) function getTraceKey(item) { var type = typeof item return item && type === 'object' ? item.$hashcode : type + ':' + item } //创建一组fragment的虚拟DOM function createFragments(instance, obj) { if (isObject(obj)) { var array = Array.isArray(obj) var ids = [] var fragments = [], i = 0 instance.isArray = array if (instance.fragments) { instance.preFragments = instance.fragments Anot.each(obj, function(key, value) { var k = array ? getTraceKey(value) : key fragments.push({ key: k, val: value, index: i++ }) ids.push(k) }) instance.fragments = fragments } else { Anot.each(obj, function(key, value) { if (!(key in $$skipArray)) { var k = array ? getTraceKey(value) : key fragments.push(new VFragment([], k, value, i++)) ids.push(k) } }) instance.fragments = fragments } return ids.join(';;') } else { return NaN } } function mountList(instance) { var args = instance.fragments.map(function(fragment, index) { FragmentDecorator(fragment, instance, index) saveInCache(instance.cache, fragment) return fragment }) var list = instance.parentChildren var i = list.indexOf(instance.begin) list.splice.apply(list, [i + 1, 0].concat(args)) } function diffList(instance) { var cache = instance.cache var newCache = {} var fuzzy = [] var list = instance.preFragments list.forEach(function(el) { el._dispose = true }) instance.fragments.forEach(function(c, index) { var fragment = isInCache(cache, c.key) //取出之前的文档碎片 if (fragment) { delete fragment._dispose fragment.oldIndex = fragment.index fragment.index = index // 相当于 c.index resetVM(fragment.vm, instance.keyName) fragment.vm[instance.valName] = c.val fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key saveInCache(newCache, fragment) } else { //如果找不到就进行模糊搜索 fuzzy.push(c) } }) fuzzy.forEach(function(c) { var fragment = fuzzyMatchCache(cache, c.key) if (fragment) { //重复利用 fragment.oldIndex = fragment.index fragment.key = c.key var val = (fragment.val = c.val) var index = (fragment.index = c.index) fragment.vm[instance.valName] = val fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key delete fragment._dispose } else { c = new VFragment([], c.key, c.val, c.index) fragment = FragmentDecorator(c, instance, c.index) list.push(fragment) } saveInCache(newCache, fragment) }) instance.fragments = list list.sort(function(a, b) { return a.index - b.index }) instance.cache = newCache } function updateItemVm(vm, top) { for (var i in top) { if (top.hasOwnProperty(i)) { vm[i] = top[i] } } } function resetVM(vm, a, b) { vm.$accessors[a].value = NaN } function updateList(instance) { var before = instance.begin.dom var parent = before.parentNode var list = instance.fragments var end = instance.end.dom for (var i = 0, item; (item = list[i]); i++) { if (item._dispose) { list.splice(i, 1) i-- item.dispose() continue } if (item.oldIndex !== item.index) { var f = item.toFragment() var isEnd = before.nextSibling === null parent.insertBefore(f, before.nextSibling) if (isEnd && !parent.contains(end)) { parent.insertBefore(end, before.nextSibling) } } before = item.split } var ch = instance.parentChildren var startIndex = ch.indexOf(instance.begin) var endIndex = ch.indexOf(instance.end) list.splice.apply(ch, [startIndex + 1, endIndex - startIndex].concat(list)) if (parent.nodeName === 'SELECT' && parent._ms_duplex_) { updateView['select'].call(parent._ms_duplex_) } } /** * * @param {type} fragment * @param {type} this * @param {type} index * @returns { key, val, index, oldIndex, this, dom, split, vm} */ function FragmentDecorator(fragment, instance, index) { var data = {} data[instance.keyName] = instance.isArray ? index : fragment.key data[instance.valName] = fragment.val if (instance.asName) { data[instance.asName] = instance.value } var vm = (fragment.vm = platform.itemFactory(instance.vm, { data: data })) if (instance.isArray) { vm.$watch(instance.valName, function(a) { if (instance.value && instance.value.set) { instance.value.set(vm[instance.keyName], a) } }) } else { vm.$watch(instance.valName, function(a) { instance.value[fragment.key] = a }) } fragment.index = index fragment.innerRender = Anot.scan(instance.fragment, vm, function() { var oldRoot = this.root ap.push.apply(fragment.children, oldRoot.children) this.root = fragment }) return fragment } // 新位置: 旧位置 function isInCache(cache, id) { var c = cache[id] if (c) { var arr = c.arr /* istanbul ignore if*/ if (arr) { var r = arr.pop() if (!arr.length) { c.arr = 0 } return r } delete cache[id] return c } } //[1,1,1] number1 number1_ number1__ function saveInCache(cache, component) { var trackId = component.key if (!cache[trackId]) { cache[trackId] = component } else { var c = cache[trackId] var arr = c.arr || (c.arr = []) arr.push(component) } } var rfuzzy = /^(string|number|boolean)/ var rkfuzzy = /^_*(string|number|boolean)/ function fuzzyMatchCache(cache) { var key for (var id in cache) { var key = id break } if (key) { return isInCache(cache, key) } }