PageSourceSearch

https://www.ttsdpb.com/r/cms/www/qtxg/js/avalon2.js

js ttsdpb.com collected 2026-10-03 18:38:49 UTC 280,721 bytes, 8,046 lines download raw bytes

1/*!
2built in 2017-5-9:11:38 version 2.2.6 by 司徒正美
3https://github.com/RubyLouvre/avalon/tree/2.2.4
4
5修正IE下 orderBy BUG
6更改下载Promise的提示
7修复avalon.modern 在Proxy 模式下使用ms-for 循环对象时出错的BUG
8修复effect内部传参 BUG
9重构ms-validate的绑定事件的机制
10
11*/(function (global, factory) {
12    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.avalon = factory();
13})(this, function () {
14    'use strict';
15
16    var win = typeof window === 'object' ? window : typeof global === 'object' ? global : {};
17
18    var inBrowser = !!win.location && win.navigator;
19    /* istanbul ignore if  */
20
21    var document$1 = inBrowser ? win.document : {
22        createElement: Object,
23        createElementNS: Object,
24        documentElement: 'xx',
25        contains: Boolean
26    };
27    var root = inBrowser ? document$1.documentElement : {
28        outerHTML: 'x'
29    };
30
31    var versions = {
32        objectobject: 7, //IE7-8
33        objectundefined: 6, //IE6
34        undefinedfunction: NaN, // other modern browsers
35        undefinedobject: NaN };
36    /* istanbul ignore next  */
37    var msie = document$1.documentMode || versions[typeof document$1.all + typeof XMLHttpRequest];
38
39    var modern = /NaN|undefined/.test(msie) || msie > 8;
40
41    /*
42     https://github.com/rsms/js-lru
43     entry             entry             entry             entry        
44     ______            ______            ______            ______       
45     | head |.newer => |      |.newer => |      |.newer => | tail |      
46     |  A   |          |  B   |          |  C   |          |  D   |      
47     |______| <= older.|______| <= older.|______| <= older.|______|      
48     
49     removed  <--  <--  <--  <--  <--  <--  <--  <--  <--  <--  <--  added 
50     */
51    function Cache(maxLength) {
52        // 标识当前缓存数组的大小
53        this.size = 0;
54        // 标识缓存数组能达到的最大长度
55        this.limit = maxLength;
56        //  head(最不常用的项),tail(最常用的项)全部初始化为undefined
57
58        this.head = this.tail = void 0;
59        this._keymap = {};
60    }
61
62    Cache.prototype = {
63        put: function put(key, value) {
64            var entry = {
65                key: key,
66                value: value
67            };
68            this._keymap[key] = entry;
69            if (this.tail) {
70                // 如果存在tail(缓存数组的长度不为0),将tail指向新的 entry
71                this.tail.newer = entry;
72                entry.older = this.tail;
73            } else {
74                // 如果缓存数组的长度为0,将head指向新的entry
75                this.head = entry;
76            }
77            this.tail = entry;
78            // 如果缓存数组达到上限,则先删除 head 指向的缓存对象
79            /* istanbul ignore if */
80            if (this.size === this.limit) {
81                this.shift();
82            } else {
83                this.size++;
84            }
85            return value;
86        },
87        shift: function shift() {
88            /* istanbul ignore next */
89            var entry = this.head;
90            /* istanbul ignore if */
91            if (entry) {
92                // 删除 head ,并改变指向
93                this.head = this.head.newer;
94                // 同步更新 _keymap 里面的属性值
95                this.head.older = entry.newer = entry.older = this._keymap[entry.key] = void 0;
96                delete this._keymap[entry.key]; //#1029
97                // 同步更新 缓存数组的长度
98                this.size--;
99            }
100        },
101        get: function get(key) {
102            var entry = this._keymap[key];
103            // 如果查找不到含有`key`这个属性的缓存对象
104            if (entry === void 0) return;
105            // 如果查找到的缓存对象已经是 tail (最近使用过的)
106            /* istanbul ignore if */
107            if (entry === this.tail) {
108                return entry.value;
109            }
110            // HEAD--------------TAIL
111            //   <.older   .newer>
112            //  <--- add direction --
113            //   A  B  C  <D>  E
114            if (entry.newer) {
115                // 处理 newer 指向
116                if (entry === this.head) {
117                    // 如果查找到的缓存对象是 head (最近最少使用过的)
118                    // 则将 head 指向原 head 的 newer 所指向的缓存对象
119                    this.head = entry.newer;
120                }
121                // 将所查找的缓存对象的下一级的 older 指向所查找的缓存对象的older所指向的值
122                // 例如:A B C D E
123                // 如果查找到的是D,那么将E指向C,不再指向D
124                entry.newer.older = entry.older; // C <-- E.
125            }
126            if (entry.older) {
127                // 处理 older 指向
128                // 如果查找到的是D,那么C指向E,不再指向D
129                entry.older.newer = entry.newer; // C. --> E
130            }
131            // 处理所查找到的对象的 newer 以及 older 指向
132            entry.newer = void 0; // D --x
133            // older指向之前使用过的变量,即D指向E
134            entry.older = this.tail; // D. --> E
135            if (this.tail) {
136                // 将E的newer指向D
137                this.tail.newer = entry; // E. <-- D
138            }
139            // 改变 tail 为D 
140            this.tail = entry;
141            return entry.value;
142        }
143    };
144
145    var delayCompile = {};
146
147    var directives = {};
148
149    function directive(name, opts) {
150        if (directives[name]) {
151            avalon.warn(name, 'directive have defined! ');
152        }
153        directives[name] = opts;
154        if (!opts.update) {
155            opts.update = function () {};
156        }
157        if (opts.delay) {
158            delayCompile[name] = 1;
159        }
160        return opts;
161    }
162
163    function delayCompileNodes(dirs) {
164        for (var i in delayCompile) {
165            if ('ms-' + i in dirs) {
166                return true;
167            }
168        }
169    }
170
171    var window$1 = win;
172    function avalon(el) {
173        return new avalon.init(el);
174    }
175
176    avalon.init = function (el) {
177        this[0] = this.element = el;
178    };
179
180    avalon.fn = avalon.prototype = avalon.init.prototype;
181
182    function shadowCopy(destination, source) {
183        for (var property in source) {
184            destination[property] = source[property];
185        }
186        return destination;
187    }
188    var rword = /[^, ]+/g;
189    var rnowhite = /\S+/g; //存在非空字符
190    var platform = {}; //用于放置平台差异的方法与属性
191
192
193    function oneObject(array, val) {
194        if (typeof array === 'string') {
195            array = array.match(rword) || [];
196        }
197        var result = {},
198            value = val !== void 0 ? val : 1;
199        for (var i = 0, n = array.length; i < n; i++) {
200            result[array[i]] = value;
201        }
202        return result;
203    }
204
205    var op = Object.prototype;
206    function quote(str) {
207        return avalon._quote(str);
208    }
209    var inspect = op.toString;
210    var ohasOwn = op.hasOwnProperty;
211    var ap = Array.prototype;
212
213    var hasConsole = typeof console === 'object';
214    avalon.config = { debug: true };
215    function log() {
216        if (hasConsole && avalon.config.debug) {
217            Function.apply.call(console.log, console, arguments);
218        }
219    }
220    function warn() {
221        if (hasConsole && avalon.config.debug) {
222            var method = console.warn || console.log;
223            // http://qiang106.iteye.com/blog/1721425
224            Function.apply.call(method, console, arguments);
225        }
226    }
227    function error(str, e) {
228        throw (e || Error)(str);
229    }
230    function noop() {}
231    function isObject(a) {
232        return a !== null && typeof a === 'object';
233    }
234
235    function range(start, end, step) {
236        // 用于生成整数数组
237        step || (step = 1);
238        if (end == null) {
239            end = start || 0;
240            start = 0;
241        }
242        var index = -1,
243            length = Math.max(0, Math.ceil((end - start) / step)),
244            result = new Array(length);
245        while (++index < length) {
246            result[index] = start;
247            start += step;
248        }
249        return result;
250    }
251
252    var rhyphen = /([a-z\d])([A-Z]+)/g;
253    function hyphen(target) {
254        //转换为连字符线风格
255        return target.replace(rhyphen, '$1-$2').toLowerCase();
256    }
257
258    var rcamelize = /[-_][^-_]/g;
259    function camelize(target) {
260        //提前判断,提高getStyle等的效率
261        if (!target || target.indexOf('-') < 0 && target.indexOf('_') < 0) {
262            return target;
263        }
264        //转换为驼峰风格
265        return target.replace(rcamelize, function (match) {
266            return match.charAt(1).toUpperCase();
267        });
268    }
269
270    var _slice = ap.slice;
271    function slice(nodes, start, end) {
272        return _slice.call(nodes, start, end);
273    }
274
275    var rhashcode = /\d\.\d{4}/;
276    //生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
277    function makeHashCode(prefix) {
278        /* istanbul ignore next*/
279        prefix = prefix || 'avalon';
280        /* istanbul ignore next*/
281        return String(Math.random() + Math.random()).replace(rhashcode, prefix);
282    }
283    //生成事件回调的UUID(用户通过ms-on指令)
284    function getLongID(fn) {
285        /* istanbul ignore next */
286        return fn.uuid || (fn.uuid = makeHashCode('e'));
287    }
288    var UUID = 1;
289    //生成事件回调的UUID(用户通过avalon.bind)
290    function getShortID(fn) {
291        /* istanbul ignore next */
292        return fn.uuid || (fn.uuid = '_' + ++UUID);
293    }
294
295    var rescape = /[-.*+?^${}()|[\]\/\\]/g;
296    function escapeRegExp(target) {
297        //http://stevenlevithan.com/regex/xregexp/
298        //将字符串安全格式化为正则表达式的源码
299        return (target + '').replace(rescape, '\\$&');
300    }
301
302    var eventHooks = {};
303    var eventListeners = {};
304    var validators = {};
305    var cssHooks = {};
306
307    window$1.avalon = avalon;
308
309    function createFragment() {
310        /* istanbul ignore next  */
311        return document$1.createDocumentFragment();
312    }
313
314    var rentities = /&[a-z0-9#]{2,10};/;
315    var temp = document$1.createElement('div');
316    shadowCopy(avalon, {
317        Array: {
318            merge: function merge(target, other) {
319                //合并两个数组 avalon2新增
320                target.push.apply(target, other);
321            },
322            ensure: function ensure(target, item) {
323                //只有当前数组不存在此元素时只添加它
324                if (target.indexOf(item) === -1) {
325                    return target.push(item);
326                }
327            },
328            removeAt: function removeAt(target, index) {
329                //移除数组中指定位置的元素,返回布尔表示成功与否
330                return !!target.splice(index, 1).length;
331            },
332            remove: function remove(target, item) {
333                //移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否
334                var index = target.indexOf(item);
335                if (~index) return avalon.Array.removeAt(target, index);
336                return false;
337            }
338        },
339        evaluatorPool: new Cache(888),
340        parsers: {
341            number: function number(a) {
342                return a === '' ? '' : parseFloat(a) || 0;
343            },
344            string: function string(a) {
345                return a === null || a === void 0 ? '' : a + '';
346            },
347            "boolean": function boolean(a) {
348                if (a === '') return a;
349                return a === 'true' || a === '1';
350            }
351        },
352        _decode: function _decode(str) {
353            if (rentities.test(str)) {
354                temp.innerHTML = str;
355                return temp.innerText || temp.textContent;
356            }
357            return str;
358        }
359    });
360
361    //============== config ============
362    function config(settings) {
363        for (var p in settings) {
364            var val = settings[p];
365            if (typeof config.plugins[p] === 'function') {
366                config.plugins[p](val);
367            } else {
368                config[p] = val;
369            }
370        }
371        return this;
372    }
373
374    var plugins = {
375        interpolate: function interpolate(array) {
376            var openTag = array[0];
377            var closeTag = array[1];
378            if (openTag === closeTag) {
379                throw new SyntaxError('interpolate openTag cannot equal to closeTag');
380            }
381            var str = openTag + 'test' + closeTag;
382
383            if (/[<>]/.test(str)) {
384                throw new SyntaxError('interpolate cannot contains "<" or ">"');
385            }
386
387            config.openTag = openTag;
388            config.closeTag = closeTag;
389            var o = escapeRegExp(openTag);
390            var c = escapeRegExp(closeTag);
391
392            config.rtext = new RegExp(o + '(.+?)' + c, 'g');
393            config.rexpr = new RegExp(o + '([\\s\\S]*)' + c);
394        }
395    };
396    function createAnchor(nodeValue) {
397        return document$1.createComment(nodeValue);
398    }
399    config.plugins = plugins;
400    config({
401        interpolate: ['{{', '}}'],
402        debug: true
403    });
404    //============  config ============
405
406    shadowCopy(avalon, {
407        shadowCopy: shadowCopy,
408
409        oneObject: oneObject,
410        inspect: inspect,
411        ohasOwn: ohasOwn,
412        rword: rword,
413        version: "2.2.6",
414        vmodels: {},
415
416        directives: directives,
417        directive: directive,
418
419        eventHooks: eventHooks,
420        eventListeners: eventListeners,
421        validators: validators,
422        cssHooks: cssHooks,
423
424        log: log,
425        noop: noop,
426        warn: warn,
427        error: error,
428        config: config,
429
430        modern: modern,
431        msie: msie,
432        root: root,
433        document: document$1,
434        window: window$1,
435        inBrowser: inBrowser,
436
437        isObject: isObject,
438        range: range,
439        slice: slice,
440        hyphen: hyphen,
441        camelize: camelize,
442        escapeRegExp: escapeRegExp,
443        quote: quote,
444
445        makeHashCode: makeHashCode
446
447    });
448
449    /**
450     * 此模块用于修复语言的底层缺陷
451     */
452    function isNative(fn) {
453        return (/\[native code\]/.test(fn)
454        );
455    }
456
457    /* istanbul ignore if*/
458    if (!isNative('司徒正美'.trim)) {
459        var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
460        String.prototype.trim = function () {
461            return this.replace(rtrim, '');
462        };
463    }
464    if (!Object.create) {
465        Object.create = function () {
466            function F() {}
467
468            return function (o) {
469                if (arguments.length != 1) {
470                    throw new Error('Object.create implementation only accepts one parameter.');
471                }
472                F.prototype = o;
473                return new F();
474            };
475        }();
476    }
477    var hasDontEnumBug = !{
478        'toString': null
479    }.propertyIsEnumerable('toString');
480    var hasProtoEnumBug = function () {}.propertyIsEnumerable('prototype');
481    var dontEnums = ['toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor'];
482    var dontEnumsLength = dontEnums.length;
483    /* istanbul ignore if*/
484    if (!isNative(Object.keys)) {
485        Object.keys = function (object) {
486            //ecma262v5 15.2.3.14
487            var theKeys = [];
488            var skipProto = hasProtoEnumBug && typeof object === 'function';
489            if (typeof object === 'string' || object && object.callee) {
490                for (var i = 0; i < object.length; ++i) {
491                    theKeys.push(String(i));
492                }
493            } else {
494                for (var name in object) {
495                    if (!(skipProto && name === 'prototype') && ohasOwn.call(object, name)) {
496                        theKeys.push(String(name));
497                    }
498                }
499            }
500
501            if (hasDontEnumBug) {
502                var ctor = object.constructor,
503                    skipConstructor = ctor && ctor.prototype === object;
504                for (var j = 0; j < dontEnumsLength; j++) {
505                    var dontEnum = dontEnums[j];
506                    if (!(skipConstructor && dontEnum === 'constructor') && ohasOwn.call(object, dontEnum)) {
507                        theKeys.push(dontEnum);
508                    }
509                }
510            }
511            return theKeys;
512        };
513    }
514    /* istanbul ignore if*/
515    if (!isNative(Array.isArray)) {
516        Array.isArray = function (a) {
517            return Object.prototype.toString.call(a) === '[object Array]';
518        };
519    }
520
521    /* istanbul ignore if*/
522    if (!isNative(isNative.bind)) {
523        /* istanbul ignore next*/
524        Function.prototype.bind = function (scope) {
525            if (arguments.length < 2 && scope === void 0) return this;
526            var fn = this,
527                argv = arguments;
528            return function () {
529                var args = [],
530                    i;
531                for (i = 1; i < argv.length; i++) {
532                    args.push(argv[i]);
533                }for (i = 0; i < arguments.length; i++) {
534                    args.push(arguments[i]);
535                }return fn.apply(scope, args);
536            };
537        };
538    }
539    //https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
540    /**
541     * Shim for "fixing" IE's lack of support (IE < 9) for applying slice
542     * on host objects like NamedNodeMap, NodeList, and HTMLCollection
543     * (technically, since host objects have been implementation-dependent,
544     * at least before ES6, IE hasn't needed to work this way).
545     * Also works on strings, fixes IE < 9 to allow an explicit undefined
546     * for the 2nd argument (as in Firefox), and prevents errors when
547     * called on other DOM objects.
548     */
549
550    try {
551        // Can't be used with DOM elements in IE < 9
552        _slice.call(avalon.document.documentElement);
553    } catch (e) {
554        // Fails in IE < 9
555        // This will work for genuine arrays, array-like objects,
556        // NamedNodeMap (attributes, entities, notations),
557        // NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
558        // and will not fail on other DOM objects (as do DOM elements in IE < 9)
559        /* istanbul ignore next*/
560        ap.slice = function (begin, end) {
561            // IE < 9 gets unhappy with an undefined end argument
562            end = typeof end !== 'undefined' ? end : this.length;
563
564            // For native Array objects, we use the native slice function
565            if (Array.isArray(this)) {
566                return _slice.call(this, begin, end);
567            }
568
569            // For array like object we handle it ourselves.
570            var i,
571                cloned = [],
572                size,
573                len = this.length;
574
575            // Handle negative value for "begin"
576            var start = begin || 0;
577            start = start >= 0 ? start : len + start;
578
579            // Handle negative value for "end"
580            var upTo = end ? end : len;
581            if (end < 0) {
582                upTo = len + end;
583            }
584
585            // Actual expected size of the slice
586            size = upTo - start;
587
588            if (size > 0) {
589                cloned = new Array(size);
590                if (this.charAt) {
591                    for (i = 0; i < size; i++) {
592                        cloned[i] = this.charAt(start + i);
593                    }
594                } else {
595                    for (i = 0; i < size; i++) {
596                        cloned[i] = this[start + i];
597                    }
598                }
599            }
600
601            return cloned;
602        };
603    }
604    /* istanbul ignore next*/
605    function iterator(vars, body, ret) {
606        var fun = 'for(var ' + vars + 'i=0,n = this.length; i < n; i++){' + body.replace('_', '((i in this) && fn.call(scope,this[i],i,this))') + '}' + ret;
607        /* jshint ignore:start */
608        return Function('fn,scope', fun);
609        /* jshint ignore:end */
610    }
611    /* istanbul ignore if*/
612    if (!isNative(ap.map)) {
613        avalon.shadowCopy(ap, {
614            //定位操作,返回数组中第一个等于给定参数的元素的索引值。
615            indexOf: function indexOf(item, index) {
616                var n = this.length,
617                    i = ~~index;
618                if (i < 0) i += n;
619                for (; i < n; i++) {
620                    if (this[i] === item) return i;
621                }return -1;
622            },
623            //定位操作,同上,不过是从后遍历。
624            lastIndexOf: function lastIndexOf(item, index) {
625                var n = this.length,
626                    i = index == null ? n - 1 : index;
627                if (i < 0) i = Math.max(0, n + i);
628                for (; i >= 0; i--) {
629                    if (this[i] === item) return i;
630                }return -1;
631            },
632            //迭代操作,将数组的元素挨个儿传入一个函数中执行。Prototype.js的对应名字为each。
633            forEach: iterator('', '_', ''),
634            //迭代类 在数组中的每个项上运行一个函数,如果此函数的值为真,则此元素作为新数组的元素收集起来,并返回新数组
635            filter: iterator('r=[],j=0,', 'if(_)r[j++]=this[i]', 'return r'),
636            //收集操作,将数组的元素挨个儿传入一个函数中执行,然后把它们的返回值组成一个新数组返回。Prototype.js的对应名字为collect。
637            map: iterator('r=[],', 'r[i]=_', 'return r'),
638            //只要数组中有一个元素满足条件(放进给定函数返回true),那么它就返回true。Prototype.js的对应名字为any。
639            some: iterator('', 'if(_)return true', 'return false'),
640            //只有数组中的元素都满足条件(放进给定函数返回true),它才返回true。Prototype.js的对应名字为all。
641            every: iterator('', 'if(!_)return false', 'return true')
642        });
643    }
644
645    //这里放置存在异议的方法
646    var compaceQuote = function () {
647        //https://github.com/bestiejs/json3/blob/master/lib/json3.js
648        var Escapes = {
649            92: "\\\\",
650            34: '\\"',
651            8: "\\b",
652            12: "\\f",
653            10: "\\n",
654            13: "\\r",
655            9: "\\t"
656        };
657
658        var leadingZeroes = '000000';
659        var toPaddedString = function toPaddedString(width, value) {
660            return (leadingZeroes + (value || 0)).slice(-width);
661        };
662        var unicodePrefix = '\\u00';
663        var escapeChar = function escapeChar(character) {
664            var charCode = character.charCodeAt(0),
665                escaped = Escapes[charCode];
666            if (escaped) {
667                return escaped;
668            }
669            return unicodePrefix + toPaddedString(2, charCode.toString(16));
670        };
671        var reEscape = /[\x00-\x1f\x22\x5c]/g;
672        return function (value) {
673            /* istanbul ignore next */
674            reEscape.lastIndex = 0;
675            /* istanbul ignore next */
676            return '"' + (reEscape.test(value) ? String(value).replace(reEscape, escapeChar) : value) + '"';
677        };
678    }();
679    try {
680        avalon._quote = JSON.stringify;
681    } catch (e) {
682        /* istanbul ignore next  */
683        avalon._quote = compaceQuote;
684    }
685
686    var class2type = {};
687    'Boolean Number String Function Array Date RegExp Object Error'.replace(avalon.rword, function (name) {
688        class2type['[object ' + name + ']'] = name.toLowerCase();
689    });
690
691    avalon.type = function (obj) {
692        //取得目标的类型
693        if (obj == null) {
694            return String(obj);
695        }
696        // 早期的webkit内核浏览器实现了已废弃的ecma262v4标准,可以将正则字面量当作函数使用,因此typeof在判定正则时会返回function
697        return typeof obj === 'object' || typeof obj === 'function' ? class2type[inspect.call(obj)] || 'object' : typeof obj;
698    };
699
700    var rfunction = /^\s*\bfunction\b/;
701
702    avalon.isFunction = /* istanbul ignore if */typeof alert === 'object' ? function (fn) {
703        /* istanbul ignore next */
704        try {
705            /* istanbul ignore next */
706            return rfunction.test(fn + '');
707        } catch (e) {
708            /* istanbul ignore next */
709            return false;
710        }
711    } : function (fn) {
712        return inspect.call(fn) === '[object Function]';
713    };
714
715    // 利用IE678 window == document为true,document == window竟然为false的神奇特性
716    // 标准浏览器及IE9,IE10等使用 正则检测
717    /* istanbul ignore next */
718    function isWindowCompact(obj) {
719        if (!obj) {
720            return false;
721        }
722        return obj == obj.document && obj.document != obj; //jshint ignore:line
723    }
724
725    var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/;
726
727    function isWindowModern(obj) {
728        return rwindow.test(inspect.call(obj));
729    }
730
731    avalon.isWindow = isWindowModern(avalon.window) ? isWindowModern : isWindowCompact;
732
733    var enu;
734    var enumerateBUG;
735    for (enu in avalon({})) {
736        break;
737    }
738
739    enumerateBUG = enu !== '0'; //IE6下为true, 其他为false
740
741    /*判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自å
741®šä¹‰ç±»çš„实例*/
742    /* istanbul ignore next */
743    function isPlainObjectCompact(obj, key) {
744        if (!obj || avalon.type(obj) !== 'object' || obj.nodeType || avalon.isWindow(obj)) {
745            return false;
746        }
747        try {
748            //IE内置对象没有constructor
749            if (obj.constructor && !ohasOwn.call(obj, 'constructor') && !ohasOwn.call(obj.constructor.prototype, 'isPrototypeOf')) {
750                return false;
751            }
752            var isVBscript = obj.$vbthis;
753        } catch (e) {
754            //IE8 9会在这里抛错
755            return false;
756        }
757        /* istanbul ignore if */
758        if (enumerateBUG) {
759            for (key in obj) {
760                return ohasOwn.call(obj, key);
761            }
762        }
763        for (key in obj) {}
764        return key === undefined$1 || ohasOwn.call(obj, key);
765    }
766
767    /* istanbul ignore next */
768    function isPlainObjectModern(obj) {
769        // 简单的 typeof obj === 'object'检测,会致使用isPlainObject(window)在opera下通不过
770        return inspect.call(obj) === '[object Object]' && Object.getPrototypeOf(obj) === Object.prototype;
771    }
772    /* istanbul ignore next */
773    avalon.isPlainObject = /\[native code\]/.test(Object.getPrototypeOf) ? isPlainObjectModern : isPlainObjectCompact;
774
775    var rcanMix = /object|function/;
776
777    //与jQuery.extend方法,可用于浅拷贝,深拷贝
778    /* istanbul ignore next */
779    avalon.mix = avalon.fn.mix = function () {
780        var n = arguments.length,
781            isDeep = false,
782            i = 0,
783            array = [];
784        if (arguments[0] === true) {
785            isDeep = true;
786            i = 1;
787        }
788        //将所有非空对象变成空对象
789        for (; i < n; i++) {
790            var el = arguments[i];
791            el = el && rcanMix.test(typeof el) ? el : {};
792            array.push(el);
793        }
794        if (array.length === 1) {
795            array.unshift(this);
796        }
797        return innerExtend(isDeep, array);
798    };
799    var undefined$1;
800
801    function innerExtend(isDeep, array) {
802        var target = array[0],
803            copyIsArray,
804            clone,
805            name;
806        for (var i = 1, length = array.length; i < length; i++) {
807            //只处理非空参数
808            var options = array[i];
809            var noCloneArrayMethod = Array.isArray(options);
810            for (name in options) {
811                if (noCloneArrayMethod && !options.hasOwnProperty(name)) {
812                    continue;
813                }
814                try {
815                    var src = target[name];
816                    var copy = options[name]; //当options为VBS对象时报错
817                } catch (e) {
818                    continue;
819                }
820
821                // 防止环引用
822                if (target === copy) {
823                    continue;
824                }
825                if (isDeep && copy && (avalon.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
826
827                    if (copyIsArray) {
828                        copyIsArray = false;
829                        clone = src && Array.isArray(src) ? src : [];
830                    } else {
831                        clone = src && avalon.isPlainObject(src) ? src : {};
832                    }
833
834                    target[name] = innerExtend(isDeep, [clone, copy]);
835                } else if (copy !== undefined$1) {
836                    target[name] = copy;
837                }
838            }
839        }
840        return target;
841    }
842
843    var rarraylike = /(Array|List|Collection|Map|Arguments)\]$/;
844    /*判定是否类数组,如节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象*/
845    /* istanbul ignore next */
846    function isArrayLike(obj) {
847        if (!obj) return false;
848        var n = obj.length;
849        if (n === n >>> 0) {
850            //检测length属性是否为非负整数
851            var type = inspect.call(obj);
852            if (rarraylike.test(type)) return true;
853            if (type !== '[object Object]') return false;
854            try {
855                if ({}.propertyIsEnumerable.call(obj, 'length') === false) {
856                    //如果是原生对象
857                    return rfunction.test(obj.item || obj.callee);
858                }
859                return true;
860            } catch (e) {
861                //IE的NodeList直接抛错
862                return !obj.window; //IE6-8 window
863            }
864        }
865        return false;
866    }
867
868    avalon.each = function (obj, fn) {
869        if (obj) {
870            //排除null, undefined
871            var i = 0;
872            if (isArrayLike(obj)) {
873                for (var n = obj.length; i < n; i++) {
874                    if (fn(i, obj[i]) === false) break;
875                }
876            } else {
877                for (i in obj) {
878                    if (obj.hasOwnProperty(i) && fn(i, obj[i]) === false) {
879                        break;
880                    }
881                }
882            }
883        }
884    };
885    (function () {
886        var welcomeIntro = ["%cavalon.js %c" + avalon.version + " %cin debug mode, %cmore...", "color: rgb(114, 157, 52); font-weight: normal;", "color: rgb(85, 85, 85); font-weight: normal;", "color: rgb(85, 85, 85); font-weight: normal;", "color: rgb(82, 140, 224); font-weight: normal; text-decoration: underline;"];
887        var welcomeMessage = "You're running avalon in debug mode - messages will be printed to the console to help you fix problems and optimise your application.\n\n" + 'To disable debug mode, add this line at the start of your app:\n\n  avalon.config({debug: false});\n\n' + 'Debug mode also automatically shut down amicably when your app is minified.\n\n' + "Get help and support:\n  https://segmentfault.com/t/avalon\n  http://avalonjs.coding.me/\n  http://www.baidu-x.com/?q=avalonjs\n  http://www.avalon.org.cn/\n\nFound a bug? Raise an issue:\n  https://github.com/RubyLouvre/avalon/issues\n\n";
888        if (typeof console === 'object') {
889            var con = console;
890            var method = con.groupCollapsed || con.log;
891            Function.apply.call(method, con, welcomeIntro);
892            con.log(welcomeMessage);
893            if (method !== console.log) {
894                con.groupEnd(welcomeIntro);
895            }
896        }
897    })();
898
899    function toFixedFix(n, prec) {
900        var k = Math.pow(10, prec);
901        return '' + (Math.round(n * k) / k).toFixed(prec);
902    }
903    function numberFilter(number, decimals, point, thousands) {
904        //https://github.com/txgruppi/number_format
905        //form http://phpjs.org/functions/number_format/
906        //number 必需,要格式化的数字
907        //decimals 可选,规定多少个小数位。
908        //point 可选,规定用作小数点的字符串(默认为 . )。
909        //thousands 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。
910        number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
911        var n = !isFinite(+number) ? 0 : +number,
912            prec = !isFinite(+decimals) ? 3 : Math.abs(decimals),
913            sep = typeof thousands === 'string' ? thousands : ",",
914            dec = point || ".",
915            s = '';
916
917        // Fix for IE parseFloat(0.55).toFixed(0) = 0;
918        s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
919        if (s[0].length > 3) {
920            s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
921        }
922        /** //好像没有用
923         var s1 = s[1] || ''
924        
925          if (s1.length < prec) {
926                  s1 += new Array(prec - s[1].length + 1).join('0')
927                  s[1] = s1
928          }
929          **/
930        return s.join(dec);
931    }
932
933    var rscripts = /<script[^>]*>([\S\s]*?)<\/script\s*>/gim;
934    var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g;
935    var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig;
936    var rsanitize = {
937        a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig,
938        img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig,
939        form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig
940    };
941
942    //https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
943    //    <a href="javasc&NewLine;ript&colon;alert('XSS')">chrome</a> 
944    //    <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">chrome</a>
945    //    <a href="jav	ascript:alert('XSS');">IE67chrome</a>
946    //    <a href="jav&#x09;ascript:alert('XSS');">IE67chrome</a>
947    //    <a href="jav&#x0A;ascript:alert('XSS');">IE67chrome</a>
948    function sanitizeFilter(str) {
949        return str.replace(rscripts, "").replace(ropen, function (a, b) {
950            var match = a.toLowerCase().match(/<(\w+)\s/);
951            if (match) {
952                //处理a标签的href属性,img标签的src属性,form标签的action属性
953                var reg = rsanitize[match[1]];
954                if (reg) {
955                    a = a.replace(reg, function (s, name, value) {
956                        var quote = value.charAt(0);
957                        return name + "=" + quote + "javascript:void(0)" + quote; // jshint ignore:line
958                    });
959                }
960            }
961            return a.replace(ron, " ").replace(/\s+/g, " "); //移除onXXX事件
962        });
963    }
964
965    /*
966     'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
967     'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
968     'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
969     'MMMM': Month in year (January-December)
970     'MMM': Month in year (Jan-Dec)
971     'MM': Month in year, padded (01-12)
972     'M': Month in year (1-12)
973     'dd': Day in month, padded (01-31)
974     'd': Day in month (1-31)
975     'EEEE': Day in Week,(Sunday-Saturday)
976     'EEE': Day in Week, (Sun-Sat)
977     'HH': Hour in day, padded (00-23)
978     'H': Hour in day (0-23)
979     'hh': Hour in am/pm, padded (01-12)
980     'h': Hour in am/pm, (1-12)
981     'mm': Minute in hour, padded (00-59)
982     'm': Minute in hour (0-59)
983     'ss': Second in minute, padded (00-59)
984     's': Second in minute (0-59)
985     'a': am/pm marker
986     'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)
987     format string can also be one of the following predefined localizable formats:
988     
989     'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)
990     'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)
991     'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)
992     'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010
993     'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)
994     'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)
995     'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)
996     'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)
997     */
998
999    function toInt(str) {
1000        return parseInt(str, 10) || 0;
1001    }
1002
1003    function padNumber(num, digits, trim) {
1004        var neg = '';
1005        /* istanbul ignore if*/
1006        if (num < 0) {
1007            neg = '-';
1008            num = -num;
1009        }
1010        num = '' + num;
1011        while (num.length < digits) {
1012            num = '0' + num;
1013        }if (trim) num = num.substr(num.length - digits);
1014        return neg + num;
1015    }
1016
1017    function dateGetter(name, size, offset, trim) {
1018        return function (date) {
1019            var value = date["get" + name]();
1020            if (offset > 0 || value > -offset) value += offset;
1021            if (value === 0 && offset === -12) {
1022                /* istanbul ignore next*/
1023                value = 12;
1024            }
1025            return padNumber(value, size, trim);
1026        };
1027    }
1028
1029    function dateStrGetter(name, shortForm) {
1030        return function (date, formats) {
1031            var value = date["get" + name]();
1032            var get = (shortForm ? "SHORT" + name : name).toUpperCase();
1033            return formats[get][value];
1034        };
1035    }
1036
1037    function timeZoneGetter(date) {
1038        var zone = -1 * date.getTimezoneOffset();
1039        var paddedZone = zone >= 0 ? "+" : "";
1040        paddedZone += padNumber(Math[zone > 0 ? "floor" : "ceil"](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2);
1041        return paddedZone;
1042    }
1043    //取得上午下午
1044    function ampmGetter(date, formats) {
1045        return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
1046    }
1047    var DATE_FORMATS = {
1048        yyyy: dateGetter("FullYear", 4),
1049        yy: dateGetter("FullYear", 2, 0, true),
1050        y: dateGetter("FullYear", 1),
1051        MMMM: dateStrGetter("Month"),
1052        MMM: dateStrGetter("Month", true),
1053        MM: dateGetter("Month", 2, 1),
1054        M: dateGetter("Month", 1, 1),
1055        dd: dateGetter("Date", 2),
1056        d: dateGetter("Date", 1),
1057        HH: dateGetter("Hours", 2),
1058        H: dateGetter("Hours", 1),
1059        hh: dateGetter("Hours", 2, -12),
1060        h: dateGetter("Hours", 1, -12),
1061        mm: dateGetter("Minutes", 2),
1062        m: dateGetter("Minutes", 1),
1063        ss: dateGetter("Seconds", 2),
1064        s: dateGetter("Seconds", 1),
1065        sss: dateGetter("Milliseconds", 3),
1066        EEEE: dateStrGetter("Day"),
1067        EEE: dateStrGetter("Day", true),
1068        a: ampmGetter,
1069        Z: timeZoneGetter
1070    };
1071    var rdateFormat = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/;
1072    var raspnetjson = /^\/Date\((\d+)\)\/$/;
1073    function dateFilter(date, format) {
1074        var locate = dateFilter.locate,
1075            text = "",
1076            parts = [],
1077            fn,
1078            match;
1079        format = format || "mediumDate";
1080        format = locate[format] || format;
1081        if (typeof date === "string") {
1082            if (/^\d+$/.test(date)) {
1083                date = toInt(date);
1084            } else if (raspnetjson.test(date)) {
1085                date = +RegExp.$1;
1086            } else {
1087                var trimDate = date.trim();
1088                var dateArray = [0, 0, 0, 0, 0, 0, 0];
1089                var oDate = new Date(0);
1090                //取得年月日
1091                trimDate = trimDate.replace(/^(\d+)\D(\d+)\D(\d+)/, function (_, a, b, c) {
1092                    var array = c.length === 4 ? [c, a, b] : [a, b, c];
1093                    dateArray[0] = toInt(array[0]); //å¹´
1094                    dateArray[1] = toInt(array[1]) - 1; //月
1095                    dateArray[2] = toInt(array[2]); //日
1096                    return "";
1097                });
1098                var dateSetter = oDate.setFullYear;
1099                var timeSetter = oDate.setHours;
1100                trimDate = trimDate.replace(/[T\s](\d+):(\d+):?(\d+)?\.?(\d)?/, function (_, a, b, c, d) {
1101                    dateArray[3] = toInt(a); //小时
1102                    dateArray[4] = toInt(b); //分钟
1103                    dateArray[5] = toInt(c); //秒
1104                    if (d) {
1105                        //毫秒
1106                        dateArray[6] = Math.round(parseFloat("0." + d) * 1000);
1107                    }
1108                    return "";
1109                });
1110                var tzHour = 0;
1111                var tzMin = 0;
1112                trimDate = trimDate.replace(/Z|([+-])(\d\d):?(\d\d)/, function (z, symbol, c, d) {
1113                    dateSetter = oDate.setUTCFullYear;
1114                    timeSetter = oDate.setUTCHours;
1115                    if (symbol) {
1116                        tzHour = toInt(symbol + c);
1117                        tzMin = toInt(symbol + d);
1118                    }
1119                    return '';
1120                });
1121
1122                dateArray[3] -= tzHour;
1123                dateArray[4] -= tzMin;
1124                dateSetter.apply(oDate, dateArray.slice(0, 3));
1125                timeSetter.apply(oDate, dateArray.slice(3));
1126                date = oDate;
1127            }
1128        }
1129        if (typeof date === 'number') {
1130            date = new Date(date);
1131        }
1132
1133        while (format) {
1134            match = rdateFormat.exec(format);
1135            /* istanbul ignore else */
1136            if (match) {
1137                parts = parts.concat(match.slice(1));
1138                format = parts.pop();
1139            } else {
1140                parts.push(format);
1141                format = null;
1142            }
1143        }
1144        parts.forEach(function (value) {
1145            fn = DATE_FORMATS[value];
1146            text += fn ? fn(date, locate) : value.replace(/(^'|'$)/g, "").replace(/''/g, "'");
1147        });
1148        return text;
1149    }
1150
1151    var locate = {
1152        AMPMS: {
1153            0: '上午',
1154            1: '下午'
1155        },
1156        DAY: {
1157            0: '星期日',
1158            1: '星期一',
1159            2: '星期二',
1160            3: '星期三',
1161            4: '星期四',
1162            5: '星期五',
1163            6: '星期六'
1164        },
1165        MONTH: {
1166            0: '1月',
1167            1: '2月',
1168            2: '3月',
1169            3: '4月',
1170            4: '5月',
1171            5: '6月',
1172            6: '7月',
1173            7: '8月',
1174            8: '9月',
1175            9: '10月',
1176            10: '11月',
1177            11: '12月'
1178        },
1179        SHORTDAY: {
1180            '0': '周日',
1181            '1': '周一',
1182            '2': '周二',
1183            '3': '周三',
1184            '4': '周四',
1185            '5': '周五',
1186            '6': '周六'
1187        },
1188        fullDate: 'y年M月d日EEEE',
1189        longDate: 'y年M月d日',
1190        medium: 'yyyy-M-d H:mm:ss',
1191        mediumDate: 'yyyy-M-d',
1192        mediumTime: 'H:mm:ss',
1193        'short': 'yy-M-d ah:mm',
1194        shortDate: 'yy-M-d',
1195        shortTime: 'ah:mm'
1196    };
1197    locate.SHORTMONTH = locate.MONTH;
1198    dateFilter.locate = locate;
1199
1200    /**
1201    $$skipArray:是系统级通用的不可监听属性
1202    $skipArray: 是当前对象特有的不可监听属性
1203    
1204     不同点是
1205     $$skipArray被hasOwnProperty后返回false
1206     $skipArray被hasOwnProperty后返回true
1207     */
1208    var falsy;
1209    var $$skipArray = {
1210        $id: falsy,
1211        $render: falsy,
1212        $track: falsy,
1213        $element: falsy,
1214        $computed: falsy,
1215        $watch: falsy,
1216        $fire: falsy,
1217        $events: falsy,
1218        $accessors: falsy,
1219        $hashcode: falsy,
1220        $mutations: falsy,
1221        $vbthis: falsy,
1222        $vbsetter: falsy
1223    };
1224
1225    /*
1226    https://github.com/hufyhang/orderBy/blob/master/index.js
1227    */
1228
1229    function orderBy(array, by, decend) {
1230        var type = avalon.type(array);
1231        if (type !== 'array' && type !== 'object') throw 'orderBy只能处理对象或数组';
1232        var criteria = typeof by == 'string' ? function (el) {
1233            return el && el[by];
1234        } : typeof by === 'function' ? by : function (el) {
1235            return el;
1236        };
1237        var mapping = {};
1238        var temp = [];
1239        __repeat(array, Array.isArray(array), function (key) {
1240            var val = array[key];
1241            var k = criteria(val, key);
1242            if (k in mapping) {
1243                mapping[k].push(key);
1244            } else {
1245                mapping[k] = [key];
1246            }
1247            temp.push(k);
1248        });
1249
1250        temp.sort();
1251        if (decend < 0) {
1252            temp.reverse();
1253        }
1254        var _array = type === 'array';
1255        var target = _array ? [] : {};
1256        return recovery(target, temp, function (k) {
1257            var key = mapping[k].shift();
1258            if (_array) {
1259                target.push(array[key]);
1260            } else {
1261                target[key] = array[key];
1262            }
1263        });
1264    }
1265
1266    function __repeat(array, isArray$$1, cb) {
1267        if (isArray$$1) {
1268            array.forEach(function (val, index) {
1269                cb(index);
1270            });
1271        } else if (typeof array.$track === 'string') {
1272            array.$track.replace(/[^☥]+/g, function (k) {
1273                cb(k);
1274            });
1275        } else {
1276            for (var i in array) {
1277                if (array.hasOwnProperty(i)) {
1278                    cb(i);
1279                }
1280            }
1281        }
1282    }
1283    function filterBy(array, search) {
1284        var type = avalon.type(array);
1285        if (type !== 'array' && type !== 'object') throw 'filterBy只能处理对象或数组';
1286        var args = avalon.slice(arguments, 2);
1287        var stype = avalon.type(search);
1288        if (stype === 'function') {
1289            var criteria = search._orig || search;
1290        } else if (stype === 'string' || stype === 'number') {
1291            if (search === '') {
1292                return array;
1293            } else {
1294                var reg = new RegExp(avalon.escapeRegExp(search), 'i');
1295                criteria = function criteria(el) {
1296                    return reg.test(el);
1297                };
1298            }
1299        } else {
1300            return array;
1301        }
1302        var isArray$$1 = type === 'array';
1303        var target = isArray$$1 ? [] : {};
1304        __repeat(array, isArray$$1, function (key) {
1305            var val = array[key];
1306            if (criteria.apply({
1307                key: key
1308            }, [val, key].concat(args))) {
1309                if (isArray$$1) {
1310                    target.push(val);
1311                } else {
1312                    target[key] = val;
1313                }
1314            }
1315        });
1316        return target;
1317    }
1318
1319    function selectBy(data, array, defaults) {
1320        if (avalon.isObject(data) && !Array.isArray(data)) {
1321            var target = [];
1322            return recovery(target, array, function (name) {
1323                target.push(data.hasOwnProperty(name) ? data[name] : defaults ? defaults[name] : '');
1324            });
1325        } else {
1326            return data;
1327        }
1328    }
1329
1330    function limitBy(input, limit, begin) {
1331        var type = avalon.type(input);
1332        if (type !== 'array' && type !== 'object') throw 'limitBy只能处理对象或数组';
1333        //必须是数值
1334        if (typeof limit !== 'number') {
1335            return input;
1336        }
1337        //不能为NaN
1338        if (limit !== limit) {
1339            return input;
1340        }
1341        //将目标转换为数组
1342        if (type === 'object') {
1343            input = convertArray(input, false);
1344        }
1345        var n = input.length;
1346        limit = Math.floor(Math.min(n, limit));
1347        begin = typeof begin === 'number' ? begin : 0;
1348        if (begin < 0) {
1349            begin = Math.max(0, n + begin);
1350        }
1351        var data = [];
1352        for (var i = begin; i < n; i++) {
1353            if (data.length === limit) {
1354                break;
1355            }
1356            data.push(input[i]);
1357        }
1358        var isArray$$1 = type === 'array';
1359        if (isArray$$1) {
1360            return data;
1361        }
1362        var target = {};
1363        return recovery(target, data, function (el) {
1364            target[el.key] = el.value;
1365        });
1366    }
1367
1368    function recovery(ret, array, callback) {
1369        for (var i = 0, n = array.length; i < n; i++) {
1370            callback(array[i]);
1371        }
1372        return ret;
1373    }
1374
1375    //Chrome谷歌浏览器中js代码Array.sort排序的bug乱序解决办法
1376    //http://www.cnblogs.com/yzeng/p/3949182.html
1377    function convertArray(array, isArray$$1) {
1378        var ret = [],
1379            i = 0;
1380        __repeat(array, isArray$$1, function (key) {
1381            ret[i] = {
1382                oldIndex: i,
1383                value: array[key],
1384                key: key
1385            };
1386            i++;
1387        });
1388        return ret;
1389    }
1390
1391    var eventFilters = {
1392        stop: function stop(e) {
1393            e.stopPropagation();
1394            return e;
1395        },
1396        prevent: function prevent(e) {
1397            e.preventDefault();
1398            return e;
1399        }
1400    };
1401    var keys = {
1402        esc: 27,
1403        tab: 9,
1404        enter: 13,
1405        space: 32,
1406        del: 46,
1407        up: 38,
1408        left: 37,
1409        right: 39,
1410        down: 40
1411    };
1412    for (var name$1 in keys) {
1413        (function (filter, key) {
1414            eventFilters[filter] = function (e) {
1415                if (e.which !== key) {
1416                    e.$return = true;
1417                }
1418                return e;
1419            };
1420        })(name$1, keys[name$1]);
1421    }
1422
1423    //https://github.com/teppeis/htmlspecialchars
1424    function escapeFilter(str) {
1425        if (str == null) return '';
1426
1427        return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
1428    }
1429
1430    var filters = avalon.filters = {};
1431
1432    avalon.composeFilters = function () {
1433        var args = arguments;
1434        return function (value) {
1435            for (var i = 0, arr; arr = args[i++];) {
1436                var name = arr[0];
1437                var filter = avalon.filters[name];
1438                if (typeof filter === 'function') {
1439                    arr[0] = value;
1440                    try {
1441                        value = filter.apply(0, arr);
1442                    } catch (e) {}
1443                }
1444            }
1445            return value;
1446        };
1447    };
1448
1449    avalon.escapeHtml = escapeFilter;
1450
1451    avalon.mix(filters, {
1452        uppercase: function uppercase(str) {
1453            return String(str).toUpperCase();
1454        },
1455        lowercase: function lowercase(str) {
1456            return String(str).toLowerCase();
1457        },
1458        truncate: function truncate(str, length, end) {
1459            //length,新字符串长度,truncation,新字符串的结尾的字段,返回新字符串
1460            if (!str) {
1461                return '';
1462            }
1463            str = String(str);
1464            if (isNaN(length)) {
1465                length = 30;
1466            }
1467            end = typeof end === "string" ? end : "...";
1468            return str.length > length ? str.slice(0, length - end.length) + end : /* istanbul ignore else*/
1469            str;
1470        },
1471
1472        camelize: avalon.camelize,
1473        date: dateFilter,
1474        escape: escapeFilter,
1475        sanitize: sanitizeFilter,
1476        number: numberFilter,
1477        currency: function currency(amount, symbol, fractionSize) {
1478            return (symbol || '\xA5') + numberFilter(amount, isFinite(fractionSize) ? /* istanbul ignore else*/fractionSize : 2);
1479        }
1480    }, { filterBy: filterBy, orderBy: orderBy, selectBy: selectBy, limitBy: limitBy }, eventFilters);
1481
1482    var rcheckedType = /^(?:checkbox|radio)$/;
1483
1484    /* istanbul ignore next */
1485    function fixElement(dest, src) {
1486        if (dest.nodeType !== 1) {
1487            return;
1488        }
1489        var nodeName = dest.nodeName.toLowerCase();
1490
1491        if (nodeName === "script") {
1492            if (dest.text !== src.text) {
1493                dest.type = "noexec";
1494                dest.text = src.text;
1495                dest.type = src.type || "";
1496            }
1497        } else if (nodeName === 'object') {
1498            var params = src.childNodes;
1499            if (dest.childNodes.length !== params.length) {
1500                avalon.clearHTML(dest);
1501                for (var i = 0, el; el = params[i++];) {
1502                    dest.appendChild(el.cloneNode(true));
1503                }
1504            }
1505        } else if (nodeName === 'input' && rcheckedType.test(src.nodeName)) {
1506
1507            dest.defaultChecked = dest.checked = src.checked;
1508            if (dest.value !== src.value) {
1509                dest.value = src.value;
1510            }
1511        } else if (nodeName === 'option') {
1512            dest.defaultSelected = dest.selected = src.defaultSelected;
1513        } else if (nodeName === 'input' || nodeName === 'textarea') {
1514            dest.defaultValue = src.defaultValue;
1515        }
1516    }
1517
1518    /* istanbul ignore next */
1519    function getAll(context) {
1520        return typeof context.getElementsByTagName !== 'undefined' ? context.getElementsByTagName('*') : typeof context.querySelectorAll !== 'undefined' ? context.querySelectorAll('*') : [];
1521    }
1522
1523    /* istanbul ignore next */
1524    function fixClone(src) {
1525        var target = src.cloneNode(true);
1526        //http://www.myexception.cn/web/665613.html
1527        // target.expando = null
1528        var t = getAll(target);
1529        var s = getAll(src);
1530        for (var i = 0; i < s.length; i++) {
1531            fixElement(t[i], s[i]);
1532        }
1533        return target;
1534    }
1535
1536    /* istanbul ignore next */
1537    function fixContains(root, el) {
1538        try {
1539            //IE6-8,游离于DOM树外的文本节点,访问parentNode有时会抛错
1540            while (el = el.parentNode) {
1541                if (el === root) return true;
1542            }
1543        } catch (e) {}
1544        return false;
1545    }
1546
1547    avalon.contains = fixContains;
1548
1549    avalon.cloneNode = function (a) {
1550        return a.cloneNode(true);
1551    };
1552
1553    //IE6-11的文档对象没有contains
1554    /* istanbul ignore next */
1555    function shimHack() {
1556        if (msie < 10) {
1557            avalon.cloneNode = fixClone;
1558        }
1559        if (!document$1.contains) {
1560            document$1.contains = function (b) {
1561                return fixContains(document$1, b);
1562            };
1563        }
1564        if (avalon.modern) {
1565            if (!document$1.createTextNode('x').contains) {
1566                Node.prototype.contains = function (child) {
1567                    //IE6-8没有Node对象
1568                    return fixContains(this, child);
1569                };
1570            }
1571        }
1572        //firefox 到11时才有outerHTML
1573        function fixFF(prop, cb) {
1574            if (!(prop in root) && HTMLElement.prototype.__defineGetter__) {
1575                HTMLElement.prototype.__defineGetter__(prop, cb);
1576            }
1577        }
1578        fixFF('outerHTML', function () {
1579            var div = document$1.createElement('div');
1580            div.appendChild(this);
1581            return div.innerHTML;
1582        });
1583        fixFF('children', function () {
1584            var children = [];
1585            for (var i = 0, el; el = this.childNodes[i++];) {
1586                if (el.nodeType === 1) {
1587                    children.push(el);
1588                }
1589            }
1590            return children;
1591        });
1592        fixFF('innerText', function () {
1593            //firefox45+, chrome4+ http://caniuse.com/#feat=innertext
1594            return this.textContent;
1595        });
1596    }
1597
1598    if (inBrowser) {
1599        shimHack();
1600    }
1601
1602    function ClassList(node) {
1603        this.node = node;
1604    }
1605
1606    ClassList.prototype = {
1607        toString: function toString() {
1608            var node = this.node;
1609            var cls = node.className;
1610            var str = typeof cls === 'string' ? cls : cls.baseVal;
1611            var match = str.match(rnowhite);
1612            return match ? match.join(' ') : '';
1613        },
1614        contains: function contains(cls) {
1615            return (' ' + this + ' ').indexOf(' ' + cls + ' ') > -1;
1616        },
1617        add: function add(cls) {
1618            if (!this.contains(cls)) {
1619                this.set(this + ' ' + cls);
1620            }
1621        },
1622        remove: function remove(cls) {
1623            this.set((' ' + this + ' ').replace(' ' + cls + ' ', ' '));
1624        },
1625        set: function set(cls) {
1626            cls = cls.trim();
1627            var node = this.node;
1628            if (typeof node.className === 'object') {
1629                //SVG元素的className是一个对象 SVGAnimatedString { baseVal='', animVal=''},只能通过set/getAttribute操作
1630                node.setAttribute('class', cls);
1631            } else {
1632                node.className = cls;
1633            }
1634            if (!cls) {
1635                node.removeAttribute('class');
1636            }
1637            //toggle存在版本差异,因此不使用它
1638        }
1639    };
1640
1641    function classListFactory(node) {
1642        if (!('classList' in node)) {
1643            node.classList = new ClassList(node);
1644        }
1645        return node.classList;
1646    }
1647
1648    'add,remove'.replace(rword, function (method) {
1649        avalon.fn[method + 'Class'] = function (cls) {
1650            var el = this[0] || {};
1651            //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
1652            if (cls && typeof cls === 'string' && el.nodeType === 1) {
1653                cls.replace(rnowhite, function (c) {
1654                    classListFactory(el)[method](c);
1655                });
1656            }
1657            return this;
1658        };
1659    });
1660
1661    avalon.shadowCopy(avalon.fn, {
1662        hasClass: function hasClass(cls) {
1663            var el = this[0] || {};
1664            return el.nodeType === 1 && classListFactory(el).contains(cls);
1665        },
1666        toggleClass: function toggleClass(value, stateVal) {
1667            var isBool = typeof stateVal === 'boolean';
1668            var me = this;
1669            String(value).replace(rnowhite, function (c) {
1670                var state = isBool ? stateVal : !me.hasClass(c);
1671                me[state ? 'addClass' : 'removeClass'](c);
1672            });
1673            return this;
1674        }
1675    });
1676
1677    var propMap = {}; //不规则的属性名映射
1678
1679
1680    //防止压缩时出错
1681    'accept-charset,acceptCharset|char,ch|charoff,chOff|class,className|for,htmlFor|http-equiv,httpEquiv'.replace(/[^\|]+/g, function (a) {
1682        var k = a.split(',');
1683        propMap[k[0]] = k[1];
1684    });
1685    /*
1686    contenteditable不是布尔属性
1687    http://www.zhangxinxu.com/wordpress/2016/01/contenteditable-plaintext-only/
1688    contenteditable=''
1689    contenteditable='events'
1690    contenteditable='caret'
1691    contenteditable='plaintext-only'
1692    contenteditable='true'
1693    contenteditable='false'
1694     */
1695    var bools = ['autofocus,autoplay,async,allowTransparency,checked,controls', 'declare,disabled,defer,defaultChecked,defaultSelected,', 'isMap,loop,multiple,noHref,noResize,noShade', 'open,readOnly,selected'].join(',');
1696
1697    bools.replace(/\w+/g, function (name) {
1698        propMap[name.toLowerCase()] = name;
1699    });
1700
1701    var anomaly = ['accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan', 'dateTime,defaultValue,contentEditable,frameBorder,longDesc,maxLength,' + 'marginWidth,marginHeight,rowSpan,tabIndex,useMap,vSpace,valueType,vAlign'].join(',');
1702
1703    anomaly.replace(/\w+/g, function (name) {
1704        propMap[name.toLowerCase()] = name;
1705    });
1706
1707    //module.exports = propMap
1708
1709    function isVML(src) {
1710        var nodeName = src.nodeName;
1711        return nodeName.toLowerCase() === nodeName && !!src.scopeName && src.outerText === '';
1712    }
1713
1714    var rvalidchars = /^[\],:{}\s]*$/;
1715    var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g;
1716    var rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g;
1717    var rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g;
1718
1719    function compactParseJSON(data) {
1720        if (typeof data === 'string') {
1721            data = data.trim();
1722            if (data) {
1723                if (rvalidchars.test(data.replace(rvalidescape, '@').replace(rvalidtokens, ']').replace(rvalidbraces, ''))) {
1724                    return new Function('return ' + data)(); // jshint ignore:line
1725                }
1726            }
1727            throw TypeError('Invalid JSON: [' + data + ']');
1728        }
1729        return data;
1730    }
1731
1732    var rsvg = /^\[object SVG\w*Element\]$/;
1733    var ramp = /&amp;/g;
1734    function updateAttrs(node, attrs) {
1735        for (var attrName in attrs) {
1736            try {
1737                var val = attrs[attrName];
1738                // 处理路径属性
1739                /* istanbul ignore if*/
1740
1741                //处理HTML5 data-*属性 SVG
1742                if (attrName.indexOf('data-') === 0 || rsvg.test(node)) {
1743                    node.setAttribute(attrName, val);
1744                } else {
1745                    var propName = propMap[attrName] || attrName;
1746                    /* istanbul ignore if */
1747                    if (typeof node[propName] === 'boolean') {
1748                        if (propName === 'checked') {
1749                            node.defaultChecked = !!val;
1750                        }
1751                        node[propName] = !!val;
1752                        //布尔属性必须使用el.xxx = true|false方式设值
1753                        //如果为false, IE全系列下相当于setAttribute(xxx,''),
1754                        //会影响到样式,需要进一步处理
1755                    }
1756
1757                    if (val === false) {
1758                        //移除属性
1759                        node.removeAttribute(propName);
1760                        continue;
1761                    }
1762                    //IE6中classNamme, htmlFor等无法检测它们为内建属性 
1763                    if (avalon.msie < 8 && /[A-Z]/.test(propName)) {
1764                        node[propName] = val + '';
1765                        continue;
1766                    }
1767                    //SVG只能使用setAttribute(xxx, yyy), VML只能使用node.xxx = yyy ,
1768                    //HTML的固有属性必须node.xxx = yyy
1769                    /* istanbul ignore next */
1770                    var isInnate = !avalon.modern && isVML(node) ? true : isInnateProps(node.nodeName, attrName);
1771                    if (isInnate) {
1772                        if (attrName === 'href' || attrName === 'src') {
1773                            /* istanbul ignore if */
1774                            if (avalon.msie < 8) {
1775                                val = String(val).replace(ramp, '&'); //处理IE67自动转义的问题
1776                            }
1777                        }
1778                        node[propName] = val + '';
1779                    } else {
1780                        node.setAttribute(attrName, val);
1781                    }
1782                }
1783            } catch (e) {
1784                // 对象不支持此属性或方法 src https://github.com/ecomfe/zrender 
1785                // 未知名称。\/n
1786                // e.message大概这样,需要trim
1787                //IE6-8,元素节点不支持其他元素节点的内置属性,如src, href, for
1788                /* istanbul ignore next */
1789                avalon.log(String(e.message).trim(), attrName, val);
1790            }
1791        }
1792    }
1793    var innateMap = {};
1794
1795    function isInnateProps(nodeName, attrName) {
1796        var key = nodeName + ":" + attrName;
1797        if (key in innateMap) {
1798            return innateMap[key];
1799        }
1800        return innateMap[key] = attrName in document$1.createElement(nodeName);
1801    }
1802    try {
1803        avalon.parseJSON = JSON.parse;
1804    } catch (e) {
1805        /* istanbul ignore next */
1806        avalon.parseJSON = compactParseJSON;
1807    }
1808
1809    avalon.fn.attr = function (name, value) {
1810        if (arguments.length === 2) {
1811            this[0].setAttribute(name, value);
1812            return this;
1813        } else {
1814            return this[0].getAttribute(name);
1815        }
1816    };
1817
1818    var cssMap = oneObject('float', 'cssFloat');
1819    avalon.cssNumber = oneObject('animationIterationCount,columnCount,order,flex,flexGrow,flexShrink,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom');
1820    var prefixes = ['', '-webkit-', '-o-', '-moz-', '-ms-'];
1821    /* istanbul ignore next */
1822    avalon.cssName = function (name, host, camelCase) {
1823        if (cssMap[name]) {
1824            return cssMap[name];
1825        }
1826        host = host || avalon.root.style || {};
1827        for (var i = 0, n = prefixes.length; i < n; i++) {
1828            camelCase = avalon.camelize(prefixes[i] + name);
1829            if (camelCase in host) {
1830                return cssMap[name] = camelCase;
1831            }
1832        }
1833        return null;
1834    };
1835    /* istanbul ignore next */
1836    avalon.css = function (node, name, value, fn) {
1837        //读写删除元素节点的样式
1838        if (node instanceof avalon) {
1839            node = node[0];
1840        }
1841        if (node.nodeType !== 1) {
1842            return;
1843        }
1844        var prop = avalon.camelize(name);
1845        name = avalon.cssName(prop) || /* istanbul ignore next*/prop;
1846        if (value === void 0 || typeof value === 'boolean') {
1847            //获取样式
1848            fn = cssHooks[prop + ':get'] || cssHooks['@:get'];
1849            if (name === 'background') {
1850                name = 'backgroundColor';
1851            }
1852            var val = fn(node, name);
1853            return value === true ? parseFloat(val) || 0 : val;
1854        } else if (value === '') {
1855            //请除样式
1856            node.style[name] = '';
1857        } else {
1858            //设置样式
1859            if (value == null || value !== value) {
1860                return;
1861            }
1862            if (isFinite(value) && !avalon.cssNumber[prop]) {
1863                value += 'px';
1864            }
1865            fn = cssHooks[prop + ':set'] || cssHooks['@:set'];
1866            fn(node, name, value);
1867        }
1868    };
1869    /* istanbul ignore next */
1870    avalon.fn.css = function (name, value) {
1871        if (avalon.isPlainObject(name)) {
1872            for (var i in name) {
1873                avalon.css(this, i, name[i]);
1874            }
1875        } else {
1876            var ret = avalon.css(this, name, value);
1877        }
1878        return ret !== void 0 ? ret : this;
1879    };
1880    /* istanbul ignore next */
1881    avalon.fn.position = function () {
1882        var offsetParent,
1883            offset,
1884            elem = this[0],
1885            parentOffset = {
1886            top: 0,
1887            left: 0
1888        };
1889        if (!elem) {
1890            return parentOffset;
1891        }
1892        if (this.css('position') === 'fixed') {
1893            offset = elem.getBoundingClientRect();
1894        } else {
1895            offsetParent = this.offsetParent(); //得到真正的offsetParent
1896            offset = this.offset(); // 得到正确的offsetParent
1897            if (offsetParent[0].tagName !== 'HTML') {
1898                parentOffset = offsetParent.offset();
1899            }
1900            parentOffset.top += avalon.css(offsetParent[0], 'borderTopWidth', true);
1901            parentOffset.left += avalon.css(offsetParent[0], 'borderLeftWidth', true);
1902
1903            // Subtract offsetParent scroll positions
1904            parentOffset.top -= offsetParent.scrollTop();
1905            parentOffset.left -= offsetParent.scrollLeft();
1906        }
1907        return {
1908            top: offset.top - parentOffset.top - avalon.css(elem, 'marginTop', true),
1909            left: offset.left - parentOffset.left - avalon.css(elem, 'marginLeft', true)
1910        };
1911    };
1912    /* istanbul ignore next */
1913    avalon.fn.offsetParent = function () {
1914        var offsetParent = this[0].offsetParent;
1915        while (offsetParent && avalon.css(offsetParent, 'position') === 'static') {
1916            offsetParent = offsetParent.offsetParent;
1917        }
1918        return avalon(offsetParent || avalon.root);
1919    };
1920
1921    /* istanbul ignore next */
1922    cssHooks['@:set'] = function (node, name, value) {
1923        try {
1924            //node.style.width = NaN;node.style.width = 'xxxxxxx';
1925            //node.style.width = undefine 在旧式IE下会抛异常
1926            node.style[name] = value;
1927        } catch (e) {}
1928    };
1929    /* istanbul ignore next */
1930    cssHooks['@:get'] = function (node, name) {
1931        if (!node || !node.style) {
1932            throw new Error('getComputedStyle要求传入一个节点 ' + node);
1933        }
1934        var ret,
1935        
1935    styles = window$1.getComputedStyle(node, null);
1936        if (styles) {
1937            ret = name === 'filter' ? styles.getPropertyValue(name) : styles[name];
1938            if (ret === '') {
1939                ret = node.style[name]; //其他浏览器需要我们手动取内联样式
1940            }
1941        }
1942        return ret;
1943    };
1944
1945    cssHooks['opacity:get'] = function (node) {
1946        var ret = cssHooks['@:get'](node, 'opacity');
1947        return ret === '' ? '1' : ret;
1948    };
1949
1950    'top,left'.replace(avalon.rword, function (name) {
1951        cssHooks[name + ':get'] = function (node) {
1952            var computed = cssHooks['@:get'](node, name);
1953            return (/px$/.test(computed) ? computed : avalon(node).position()[name] + 'px'
1954            );
1955        };
1956    });
1957
1958    var cssShow = {
1959        position: 'absolute',
1960        visibility: 'hidden',
1961        display: 'block'
1962    };
1963
1964    var rdisplayswap = /^(none|table(?!-c[ea]).+)/;
1965    /* istanbul ignore next */
1966    function showHidden(node, array) {
1967        //http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
1968        if (node.offsetWidth <= 0) {
1969            //opera.offsetWidth可能小于0
1970            if (rdisplayswap.test(cssHooks['@:get'](node, 'display'))) {
1971                var obj = {
1972                    node: node
1973                };
1974                for (var name in cssShow) {
1975                    obj[name] = node.style[name];
1976                    node.style[name] = cssShow[name];
1977                }
1978                array.push(obj);
1979            }
1980            var parent = node.parentNode;
1981            if (parent && parent.nodeType === 1) {
1982                showHidden(parent, array);
1983            }
1984        }
1985    }
1986    /* istanbul ignore next*/
1987    avalon.each({
1988        Width: 'width',
1989        Height: 'height'
1990    }, function (name, method) {
1991        var clientProp = 'client' + name,
1992            scrollProp = 'scroll' + name,
1993            offsetProp = 'offset' + name;
1994        cssHooks[method + ':get'] = function (node, which, override) {
1995            var boxSizing = -4;
1996            if (typeof override === 'number') {
1997                boxSizing = override;
1998            }
1999            which = name === 'Width' ? ['Left', 'Right'] : ['Top', 'Bottom'];
2000            var ret = node[offsetProp]; // border-box 0
2001            if (boxSizing === 2) {
2002                // margin-box 2
2003                return ret + avalon.css(node, 'margin' + which[0], true) + avalon.css(node, 'margin' + which[1], true);
2004            }
2005            if (boxSizing < 0) {
2006                // padding-box  -2
2007                ret = ret - avalon.css(node, 'border' + which[0] + 'Width', true) - avalon.css(node, 'border' + which[1] + 'Width', true);
2008            }
2009            if (boxSizing === -4) {
2010                // content-box -4
2011                ret = ret - avalon.css(node, 'padding' + which[0], true) - avalon.css(node, 'padding' + which[1], true);
2012            }
2013            return ret;
2014        };
2015        cssHooks[method + '&get'] = function (node) {
2016            var hidden = [];
2017            showHidden(node, hidden);
2018            var val = cssHooks[method + ':get'](node);
2019            for (var i = 0, obj; obj = hidden[i++];) {
2020                node = obj.node;
2021                for (var n in obj) {
2022                    if (typeof obj[n] === 'string') {
2023                        node.style[n] = obj[n];
2024                    }
2025                }
2026            }
2027            return val;
2028        };
2029        avalon.fn[method] = function (value) {
2030            //会忽视其display
2031            var node = this[0];
2032            if (arguments.length === 0) {
2033                if (node.setTimeout) {
2034                    //取得窗口尺寸
2035                    return node['inner' + name] || node.document.documentElement[clientProp] || node.document.body[clientProp]; //IE6下前两个分别为undefined,0
2036                }
2037                if (node.nodeType === 9) {
2038                    //取得页面尺寸
2039                    var doc = node.documentElement;
2040                    //FF chrome    html.scrollHeight< body.scrollHeight
2041                    //IE 标准模式 : html.scrollHeight> body.scrollHeight
2042                    //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点?
2043                    return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp]);
2044                }
2045                return cssHooks[method + '&get'](node);
2046            } else {
2047                return this.css(method, value);
2048            }
2049        };
2050        avalon.fn['inner' + name] = function () {
2051            return cssHooks[method + ':get'](this[0], void 0, -2);
2052        };
2053        avalon.fn['outer' + name] = function (includeMargin) {
2054            return cssHooks[method + ':get'](this[0], void 0, includeMargin === true ? 2 : 0);
2055        };
2056    });
2057
2058    function getWindow(node) {
2059        return node.window || node.defaultView || node.parentWindow || false;
2060    }
2061
2062    /* istanbul ignore if */
2063    if (msie < 9) {
2064        avalon.shadowCopy(cssMap, oneObject('float', 'styleFloat'));
2065        var rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i;
2066        var rposition = /^(top|right|bottom|left)$/;
2067        var ralpha = /alpha\([^)]+\)/i;
2068        var ropactiy = /(opacity|\d(\d|\.)*)/g;
2069        var ie8 = msie === 8;
2070        var salpha = 'DXImageTransform.Microsoft.Alpha';
2071        var border = {
2072            thin: ie8 ? '1px' : '2px',
2073            medium: ie8 ? '3px' : '4px',
2074            thick: ie8 ? '5px' : '6px'
2075        };
2076        cssHooks['@:get'] = function (node, name) {
2077            //取得精确值,不过它有可能是带em,pc,mm,pt,%等单位
2078            var currentStyle = node.currentStyle;
2079            var ret = currentStyle[name];
2080            if (rnumnonpx.test(ret) && !rposition.test(ret)) {
2081                //①,保存原有的style.left, runtimeStyle.left,
2082                var style = node.style,
2083                    left = style.left,
2084                    rsLeft = node.runtimeStyle.left;
2085                //②由于③处的style.left = xxx会影响到currentStyle.left,
2086                //因此把它currentStyle.left放到runtimeStyle.left,
2087                //runtimeStyle.left拥有最高优先级,不会style.left影响
2088                node.runtimeStyle.left = currentStyle.left;
2089                //③将精确值赋给到style.left,然后通过IE的另一个私有属性 style.pixelLeft
2090                //得到单位为px的结果;fontSize的分支见http://bugs.jquery.com/ticket/760
2091                style.left = name === 'fontSize' ? '1em' : ret || 0;
2092                ret = style.pixelLeft + 'px';
2093                //④还原 style.left,runtimeStyle.left
2094                style.left = left;
2095                node.runtimeStyle.left = rsLeft;
2096            }
2097            if (ret === 'medium') {
2098                name = name.replace('Width', 'Style');
2099                //border width 默认值为medium,即使其为0'
2100                if (currentStyle[name] === 'none') {
2101                    ret = '0px';
2102                }
2103            }
2104            return ret === '' ? 'auto' : border[ret] || ret;
2105        };
2106        cssHooks['opacity:set'] = function (node, name, value) {
2107            var style = node.style;
2108
2109            var opacity = Number(value) <= 1 ? 'alpha(opacity=' + value * 100 + ')' : '';
2110            var filter = style.filter || '';
2111            style.zoom = 1;
2112            //不能使用以下方式设置透明度
2113            //node.filters.alpha.opacity = value * 100
2114            style.filter = (ralpha.test(filter) ? filter.replace(ralpha, opacity) : filter + ' ' + opacity).trim();
2115
2116            if (!style.filter) {
2117                style.removeAttribute('filter');
2118            }
2119        };
2120        cssHooks['opacity:get'] = function (node) {
2121            var match = node.style.filter.match(ropactiy) || [];
2122            var ret = false;
2123            for (var i = 0, el; el = match[i++];) {
2124                if (el === 'opacity') {
2125                    ret = true;
2126                } else if (ret) {
2127                    return el / 100 + '';
2128                }
2129            }
2130            return '1'; //确保返回的是字符串
2131        };
2132    }
2133
2134    /* istanbul ignore next */
2135    avalon.fn.offset = function () {
2136        //取得距离页面左右角的坐标
2137        var node = this[0],
2138            box = {
2139            left: 0,
2140            top: 0
2141        };
2142        if (!node || !node.tagName || !node.ownerDocument) {
2143            return box;
2144        }
2145        var doc = node.ownerDocument;
2146        var body = doc.body;
2147        var root$$1 = doc.documentElement;
2148        var win = doc.defaultView || doc.parentWindow;
2149        if (!avalon.contains(root$$1, node)) {
2150            return box;
2151        }
2152        //http://hkom.blog1.fc2.com/?mode=m&no=750 body的偏移量是不包含margin的
2153        //我们可以通过getBoundingClientRect来获得元素相对于client的rect.
2154        //http://msdn.microsoft.com/en-us/library/ms536433.aspx
2155        if (node.getBoundingClientRect) {
2156            box = node.getBoundingClientRect(); // BlackBerry 5, iOS 3 (original iPhone)
2157        }
2158        //chrome/IE6: body.scrollTop, firefox/other: root.scrollTop
2159        var clientTop = root$$1.clientTop || body.clientTop,
2160            clientLeft = root$$1.clientLeft || body.clientLeft,
2161            scrollTop = Math.max(win.pageYOffset || 0, root$$1.scrollTop, body.scrollTop),
2162            scrollLeft = Math.max(win.pageXOffset || 0, root$$1.scrollLeft, body.scrollLeft);
2163        // 把滚动距离加到left,top中去。
2164        // IE一些版本中会自动为HTML元素加上2px的border,我们需要去掉它
2165        // http://msdn.microsoft.com/en-us/library/ms533564(VS.85).aspx
2166        return {
2167            top: box.top + scrollTop - clientTop,
2168            left: box.left + scrollLeft - clientLeft
2169        };
2170    };
2171
2172    //生成avalon.fn.scrollLeft, avalon.fn.scrollTop方法
2173    /* istanbul ignore next */
2174    avalon.each({
2175        scrollLeft: 'pageXOffset',
2176        scrollTop: 'pageYOffset'
2177    }, function (method, prop) {
2178        avalon.fn[method] = function (val) {
2179            var node = this[0] || {};
2180            var win = getWindow(node);
2181            var root$$1 = avalon.root;
2182            var top = method === 'scrollTop';
2183            if (!arguments.length) {
2184                return win ? prop in win ? win[prop] : root$$1[method] : node[method];
2185            } else {
2186                if (win) {
2187                    win.scrollTo(!top ? val : avalon(win).scrollLeft(), top ? val : avalon(win).scrollTop());
2188                } else {
2189                    node[method] = val;
2190                }
2191            }
2192        };
2193    });
2194
2195    function getDuplexType(elem) {
2196        var ret = elem.tagName.toLowerCase();
2197        if (ret === 'input') {
2198            return rcheckedType.test(elem.type) ? 'checked' : elem.type;
2199        }
2200        return ret;
2201    }
2202
2203    /**
2204     * IE6/7/8中,如果option没有value值,那么将返回空字符串。
2205     * IE9/Firefox/Safari/Chrome/Opera 中先取option的value值,如果没有value属性,则取option的innerText值。
2206     * IE11及W3C,如果没有指定value,那么node.value默认为node.text(存在trim作),但IE9-10则是取innerHTML(没trim操作)
2207     */
2208
2209    function getOption(node) {
2210        if (node.hasAttribute && node.hasAttribute('value')) {
2211            return node.getAttribute('value');
2212        }
2213        var attr = node.getAttributeNode('value');
2214        if (attr && attr.specified) {
2215            return attr.value;
2216        }
2217        return node.innerHTML.trim();
2218    }
2219
2220    var valHooks = {
2221        'option:get': msie ? getOption : function (node) {
2222            return node.value;
2223        },
2224        'select:get': function selectGet(node, value) {
2225            var option,
2226                options = node.options,
2227                index = node.selectedIndex,
2228                getter = valHooks['option:get'],
2229                one = node.type === 'select-one' || index < 0,
2230                values = one ? null : [],
2231                max = one ? index + 1 : options.length,
2232                i = index < 0 ? max : one ? index : 0;
2233            for (; i < max; i++) {
2234                option = options[i];
2235                //IE6-9在reset后不会改变selected,需要改用i === index判定
2236                //我们过滤所有disabled的option元素,但在safari5下,
2237                //如果设置optgroup为disable,那么其所有孩子都disable
2238                //因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况
2239                if ((option.selected || i === index) && !option.disabled && (!option.parentNode.disabled || option.parentNode.tagName !== 'OPTGROUP')) {
2240                    value = getter(option);
2241                    if (one) {
2242                        return value;
2243                    }
2244                    //收集所有selected值组成数组返回
2245                    values.push(value);
2246                }
2247            }
2248            return values;
2249        },
2250        'select:set': function selectSet(node, values, optionSet) {
2251            values = [].concat(values); //强制转换为数组
2252            var getter = valHooks['option:get'];
2253            for (var i = 0, el; el = node.options[i++];) {
2254                if (el.selected = values.indexOf(getter(el)) > -1) {
2255                    optionSet = true;
2256                }
2257            }
2258            if (!optionSet) {
2259                node.selectedIndex = -1;
2260            }
2261        }
2262    };
2263
2264    avalon.fn.val = function (value) {
2265        var node = this[0];
2266        if (node && node.nodeType === 1) {
2267            var get = arguments.length === 0;
2268            var access = get ? ':get' : ':set';
2269            var fn = valHooks[getDuplexType(node) + access];
2270            if (fn) {
2271                var val = fn(node, value);
2272            } else if (get) {
2273                return (node.value || '').replace(/\r/g, '');
2274            } else {
2275                node.value = value;
2276            }
2277        }
2278        return get ? val : this;
2279    };
2280
2281    var voidTag = {
2282        area: 1,
2283        base: 1,
2284        basefont: 1,
2285        bgsound: 1,
2286        br: 1,
2287        col: 1,
2288        command: 1,
2289        embed: 1,
2290        frame: 1,
2291        hr: 1,
2292        img: 1,
2293        input: 1,
2294        keygen: 1,
2295        link: 1,
2296        meta: 1,
2297        param: 1,
2298        source: 1,
2299        track: 1,
2300        wbr: 1
2301    };
2302
2303    function makeObject(str) {
2304        return oneObject(str + ',template,#document-fragment,#comment');
2305    }
2306    var pNestChild = oneObject('div,ul,ol,dl,table,h1,h2,h3,h4,h5,h6,form,fieldset');
2307    var tNestChild = makeObject('tr,style,script');
2308    var nestObject = {
2309        p: pNestChild,
2310        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect
2311        select: makeObject('option,optgroup,#text'),
2312        optgroup: makeObject('option,#text'),
2313        option: makeObject('#text'),
2314        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd
2315        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption
2316        // No special behavior since these rules fall back to "in body" mode for
2317        // all except special table nodes which cause bad parsing behavior anyway.
2318
2319        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr
2320        tr: makeObject('th,td,style,script'),
2321
2322        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody
2323        tbody: tNestChild,
2324        tfoot: tNestChild,
2325        thead: tNestChild,
2326        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup
2327        colgroup: makeObject('col'),
2328        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable
2329        // table: oneObject('caption,colgroup,tbody,thead,tfoot,style,script,template,#document-fragment'),
2330        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead
2331        head: makeObject('base,basefont,bgsound,link,style,script,meta,title,noscript,noframes'),
2332        // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element
2333        html: oneObject('head,body')
2334    };
2335
2336    /**
2337     * ------------------------------------------------------------
2338     * avalon2.2.6的新式lexer
2339     * 将字符串变成一个虚拟DOM树,方便以后进一步变成模板函数
2340     * 此阶段只会生成VElement,VText,VComment
2341     * ------------------------------------------------------------
2342     */
2343    var specalTag = { xmp: 1, style: 1, script: 1, noscript: 1, textarea: 1, '#comment': 1, template: 1 };
2344    var hiddenTag = { style: 1, script: 1, noscript: 1, template: 1 };
2345    var rcontent = /\S/; //判定里面有没有内容
2346    var rsp = /\s/;
2347    function fromString(str) {
2348        return from(str);
2349    }
2350    avalon.lexer = fromString;
2351
2352    var strCache = new Cache(100);
2353
2354    function from(str) {
2355        var cacheKey = str;
2356        var cached = strCache.get(cacheKey);
2357        if (cached) {
2358            return avalon.mix(true, [], cached);
2359        }
2360
2361        var ret = parse(str, false);
2362        strCache.put(cacheKey, avalon.mix(true, [], ret));
2363        return ret;
2364    }
2365
2366    /**
2367     * 
2368     * 
2369     * @param {any} string 
2370     * @param {any} getOne 只返回一个节点
2371     * @returns 
2372     */
2373    function parse(string, getOne) {
2374        getOne = getOne === void 666 || getOne === true;
2375        var ret = lexer(string, getOne);
2376        if (getOne) {
2377            return typeof ret[0] === 'string' ? ret[1] : ret[0];
2378        }
2379        return ret;
2380    }
2381
2382    function lexer(string, getOne) {
2383        var tokens = [];
2384        var breakIndex = 9990;
2385        var stack = [];
2386        var origString = string;
2387        var origLength = string.length;
2388
2389        stack.last = function () {
2390            return stack[stack.length - 1];
2391        };
2392        var ret = [];
2393
2394        function addNode(node) {
2395            var p = stack.last();
2396            if (p && p.children) {
2397                p.children.push(node);
2398            } else {
2399                ret.push(node);
2400            }
2401        }
2402
2403        var lastNode;
2404        do {
2405            if (--breakIndex === 0) {
2406                break;
2407            }
2408            var arr = getCloseTag(string);
2409
2410            if (arr) {
2411                //处理关闭标签
2412                string = string.replace(arr[0], '');
2413                var _node = stack.pop();
2414                if (!_node) {
2415                    throw '是不是有属性值没有用引号括起';
2416                }
2417                //处理下面两种特殊情况:
2418                //1. option会自动移除元素节点,将它们的nodeValue组成新的文本节点
2419                //2. table会将没有被thead, tbody, tfoot包起来的tr或文本节点,收集到一个新的tbody元素中
2420
2421                if (_node.nodeName === 'option') {
2422                    _node.children = [{
2423                        nodeName: '#text',
2424                        nodeValue: getText(_node)
2425                    }];
2426                } else if (_node.nodeName === 'table') {
2427                    insertTbody(_node.children);
2428                }
2429                lastNode = null;
2430                if (getOne && ret.length === 1 && !stack.length) {
2431                    return [origString.slice(0, origLength - string.length), ret[0]];
2432                }
2433                continue;
2434            }
2435
2436            var arr = getOpenTag(string);
2437            if (arr) {
2438                string = string.replace(arr[0], '');
2439                var node = arr[1];
2440                addNode(node);
2441                var selfClose = !!(node.isVoidTag || specalTag[node.nodeName]);
2442                if (!selfClose) {
2443                    //放到这里可以添加孩子
2444                    stack.push(node);
2445                }
2446                if (getOne && selfClose && !stack.length) {
2447                    return [origString.slice(0, origLength - string.length), node];
2448                }
2449                lastNode = node;
2450                continue;
2451            }
2452
2453            var text = '';
2454            do {
2455                //处理<div><<<<<<div>的情况
2456                var _index = string.indexOf('<');
2457                if (_index === 0) {
2458                    text += string.slice(0, 1);
2459                    string = string.slice(1);
2460                } else {
2461                    break;
2462                }
2463            } while (string.length);
2464
2465            //处理<div>{aaa}</div>,<div>xxx{aaa}xxx</div>,<div>xxx</div>{aaa}sss的情况
2466            var index = string.indexOf('<'); //判定它后面是否存在标签
2467            if (index === -1) {
2468                text = string;
2469                string = '';
2470            } else {
2471                var openIndex = string.indexOf(config.openTag);
2472
2473                if (openIndex !== -1 && openIndex < index) {
2474                    if (openIndex !== 0) {
2475                        text += string.slice(0, openIndex);
2476                    }
2477                    var dirString = string.slice(openIndex);
2478                    var textDir = parseTextDir(dirString);
2479                    text += textDir;
2480                    string = dirString.slice(textDir.length);
2481                } else {
2482                    text += string.slice(0, index);
2483                    string = string.slice(index);
2484                }
2485            }
2486            var mayNode = addText(lastNode, text, addNode);
2487            if (mayNode) {
2488                lastNode = mayNode;
2489            }
2490        } while (string.length);
2491        return ret;
2492    }
2493
2494    function addText(lastNode, text, addNode) {
2495        if (rcontent.test(text)) {
2496            if (lastNode && lastNode.nodeName === '#text') {
2497                lastNode.nodeValue += text;
2498                return lastNode;
2499            } else {
2500                lastNode = {
2501                    nodeName: '#text',
2502                    nodeValue: text
2503                };
2504                addNode(lastNode);
2505                return lastNode;
2506            }
2507        }
2508    }
2509
2510    function parseTextDir(string) {
2511        var closeTag = config.closeTag;
2512        var openTag = config.openTag;
2513        var closeTagFirst = closeTag.charAt(0);
2514        var closeTagLength = closeTag.length;
2515        var state = 'code',
2516            quote$$1,
2517            escape;
2518        for (var i = openTag.length, n = string.length; i < n; i++) {
2519
2520            var c = string.charAt(i);
2521            switch (state) {
2522                case 'code':
2523                    if (c === '"' || c === "'") {
2524                        state = 'string';
2525                        quote$$1 = c;
2526                    } else if (c === closeTagFirst) {
2527                        //如果遇到}
2528                        if (string.substr(i, closeTagLength) === closeTag) {
2529                            return string.slice(0, i + closeTagLength);
2530                        }
2531                    }
2532                    break;
2533                case 'string':
2534                    if (c === '\\' && /"'/.test(string.charAt(i + 1))) {
2535                        escape = !escape;
2536                    }
2537                    if (c === quote$$1 && !escape) {
2538                        state = 'code';
2539                    }
2540                    break;
2541            }
2542        }
2543        throw '找不到界定符' + closeTag;
2544    }
2545
2546    var rtbody = /^(tbody|thead|tfoot)$/;
2547
2548    function insertTbody(nodes) {
2549        var tbody = false;
2550        for (var i = 0, n = nodes.length; i < n; i++) {
2551            var node = nodes[i];
2552            if (rtbody.test(node.nodeName)) {
2553                tbody = false;
2554                continue;
2555            }
2556
2557            if (node.nodeName === 'tr') {
2558                if (tbody) {
2559                    nodes.splice(i, 1);
2560                    tbody.children.push(node);
2561                    n--;
2562                    i--;
2563                } else {
2564                    tbody = {
2565                        nodeName: 'tbody',
2566                        props: {},
2567                        children: [node]
2568                    };
2569                    nodes.splice(i, 1, tbody);
2570                }
2571            } else {
2572                if (tbody) {
2573                    nodes.splice(i, 1);
2574                    tbody.children.push(node);
2575                    n--;
2576                    i--;
2577                }
2578            }
2579        }
2580    }
2581
2582    //<div>{{<div/>}}</div>
2583    function getCloseTag(string) {
2584        if (string.indexOf("</") === 0) {
2585            var match = string.match(/\<\/(\w+[^\s\/\>]*)>/);
2586            if (match) {
2587                var tag = match[1];
2588                string = string.slice(3 + tag.length);
2589                return [match[0], {
2590                    nodeName: tag
2591                }];
2592            }
2593        }
2594        return null;
2595    }
2596    var ropenTag = /\<(\w[^\s\/\>]*)/;
2597
2598    function getOpenTag(string) {
2599        if (string.indexOf("<") === 0) {
2600            var i = string.indexOf('<!--'); //处理注释节点
2601            if (i === 0) {
2602                var l = string.indexOf('-->');
2603                if (l === -1) {
2604                    thow('注释节点没有闭合 ' + string.slice(0, 100));
2605                }
2606                var node = {
2607                    nodeName: '#comment',
2608                    nodeValue: string.slice(4, l)
2609                };
2610                return [string.slice(0, l + 3), node];
2611            }
2612            var match = string.match(ropenTag); //处理元素节点
2613            if (match) {
2614                var leftContent = match[0],
2615                    tag = match[1];
2616                var node = {
2617                    nodeName: tag,
2618                    props: {},
2619                    children: []
2620                };
2621
2622                string = string.replace(leftContent, ''); //去掉标签名(rightContent)
2623                try {
2624                    var arr = getAttrs(string); //处理属性
2625                } catch (e) {}
2626                if (arr) {
2627                    node.props = arr[1];
2628                    string = string.replace(arr[0], '');
2629                    leftContent += arr[0];
2630                }
2631
2632                if (string.charAt(0) === '>') {
2633                    //处理开标签的边界符
2634                    leftContent += '>';
2635                    string = string.slice(1);
2636                    if (voidTag[node.nodeName]) {
2637                        node.isVoidTag = true;
2638                    }
2639                } else if (string.slice(0, 2) === '/>') {
2640                    //处理开标签的边界符
2641                    leftContent += '/>';
2642                    string = string.slice(2);
2643                    node.isVoidTag = true;
2644                }
2645
2646                if (!node.isVoidTag && specalTag[tag]) {
2647                    //如果是script, style, xmp等元素
2648                    var closeTag = '</' + tag + '>';
2649                    var j = string.indexOf(closeTag);
2650                    var nodeValue = string.slice(0, j);
2651                    leftContent += nodeValue + closeTag;
2652                    node.children.push({
2653                        nodeName: '#text',
2654                        nodeValue: nodeValue
2655                    });
2656                    if (tag === 'textarea') {
2657                        node.props.type = tag;
2658                        node.props.value = nodeValue;
2659                    }
2660                }
2661                return [leftContent, node];
2662            }
2663        }
2664    }
2665
2666    function getText(node) {
2667        var ret = '';
2668        node.children.forEach(function (el) {
2669            if (el.nodeName === '#text') {
2670                ret += el.nodeValue;
2671            } else if (el.children && !hiddenTag[el.nodeName]) {
2672                ret += getText(el);
2673            }
2674        });
2675        return ret;
2676    }
2677
2678    function getAttrs(string) {
2679        var state = 'AttrName',
2680            attrName = '',
2681            attrValue = '',
2682            quote$$1,
2683            escape,
2684            props = {};
2685        for (var i = 0, n = string.length; i < n; i++) {
2686            var c = string.charAt(i);
2687            switch (state) {
2688                case 'AttrName':
2689                    if (c === '/' && string.charAt(i + 1) === '>' || c === '>') {
2690                        if (attrName) props[attrName] = attrName;
2691                        return [string.slice(0, i), props];
2692                    }
2693                    if (rsp.test(c)) {
2694                        if (attrName) {
2695                            state = 'AttrEqual';
2696                        }
2697                    } else if (c === '=') {
2698                        if (!attrName) {
2699                            throw '必须指定属性名';
2700                        }
2701                        state = 'AttrQuote';
2702                    } else {
2703                        attrName += c;
2704                    }
2705                    break;
2706                case 'AttrEqual':
2707                    if (c === '=') {
2708                        state = 'AttrQuote';
2709                    } else if (rcontent.test(c)) {
2710                        props[attrName] = attrName;
2711                        attrName = c;
2712                        state = 'AttrName';
2713                    }
2714                    break;
2715                case 'AttrQuote':
2716                    if (c === '"' || c === "'") {
2717                        quote$$1 = c;
2718                        state = 'AttrValue';
2719                        escape = false;
2720                    }
2721                    break;
2722                case 'AttrValue':
2723                    if (c === '\\' && /"'/.test(string.charAt(i + 1))) {
2724                        escape = !escape;
2725                    }
2726                    if (c === '\n') {
2727                        break;
2728                    }
2729                    if (c !== quote$$1) {
2730                        attrValue += c;
2731                    } else if (c === quote$$1 && !escape) {
2732                        props[attrName] = attrValue;
2733                        attrName = attrValue = '';
2734                        state = 'AttrName';
2735                    }
2736                    break;
2737            }
2738        }
2739        throw '必须关闭标签';
2740    }
2741
2742    var rhtml = /<|&#?\w+;/;
2743    var htmlCache = new Cache(128);
2744    var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig;
2745
2746    avalon.parseHTML = function (html) {
2747        var fragment = createFragment();
2748        //处理非字符串
2749        if (typeof html !== 'string') {
2750            return fragment;
2751        }
2752        //处理非HTML字符串
2753        if (!rhtml.test(html)) {
2754            return document$1.createTextNode(html);
2755        }
2756
2757        html = html.replace(rxhtml, '<$1></$2>').trim();
2758        var hasCache = htmlCache.get(html);
2759        if (hasCache) {
2760            return avalon.cloneNode(hasCache);
2761        }
2762        var vnodes = fromString(html);
2763        for (var i = 0, el; el = vnodes[i++];) {
2764            var child = avalon.vdom(el, 'toDOM');
2765            fragment.appendChild(child);
2766        }
2767        if (html.length < 1024) {
2768            htmlCache.put(html, fragment);
2769        }
2770        return fragment;
2771    };
2772
2773    avalon.innerHTML = function (node, html) {
2774        var parsed = avalon.parseHTML(html);
2775        this.clearHTML(node);
2776        node.appendChild(parsed);
2777    };
2778
2779    //https://github.com/karloespiritu/escapehtmlent/blob/master/index.js
2780    avalon.unescapeHTML = function (html) {
2781        return String(html).replace(/&quot;/g, '"').replace(/&#39;/g, '\'').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
2782    };
2783
2784    avalon.clearHTML = function (node) {
2785        /* istanbul ignore next */
2786        while (node.lastChild) {
2787            node.removeChild(node.lastChild);
2788        }
2789        return node;
2790    };
2791
2792    
2793    //http://segmentfault.com/q/1010000000687977/a-1020000000688757
2794    var canBubbleUp = {
2795        click: true,
2796        dblclick: true,
2797        keydown: true,
2798        keypress: true,
2799        keyup: true,
2800        mousedown: true,
2801        mousemove: true,
2802        mouseup: true,
2803        mouseover: true,
2804        mouseout: true,
2805        wheel: true,
2806        mousewheel: true,
2807        input: true,
2808        change: true,
2809        beforeinput: true,
2810        compositionstart: true,
2811        compositionupdate: true,
2812        compositionend: true,
2813        select: true,
2814        //http://blog.csdn.net/lee_magnum/article/details/17761441
2815        cut: true,
2816        copy: true,
2817        paste: true,
2818        beforecut: true,
2819        beforecopy: true,
2820        beforepaste: true,
2821        focusin: true,
2822        focusout: true,
2823        DOMFocusIn: true,
2824        DOMFocusOut: true,
2825        DOMActivate: true,
2826        dragend: true,
2827        datasetchanged: true
2828    };
2829
2830    /* istanbul ignore if */
2831    var hackSafari = avalon.modern && document$1.ontouchstart;
2832
2833    //添加fn.bind, fn.unbind, bind, unbind
2834    avalon.fn.bind = function (type, fn, phase) {
2835        if (this[0]) {
2836            //此方法不会链
2837            return avalon.bind(this[0], type, fn, phase);
2838        }
2839    };
2840
2841    avalon.fn.unbind = function (type, fn, phase) {
2842        if (this[0]) {
2843            var args = _slice.call(arguments);
2844            args.unshift(this[0]);
2845            avalon.unbind.apply(0, args);
2846        }
2847        return this;
2848    };
2849
2850    /*绑定事件*/
2851    avalon.bind = function (elem, type, fn) {
2852        if (elem.nodeType === 1) {
2853            var value = elem.getAttribute('avalon-events') || '';
2854            //如果是使用ms-on-*绑定的回调,其uuid格式为e12122324,
2855            //如果是使用bind方法绑定的回调,其uuid格式为_12
2856            var uuid = getShortID(fn);
2857            var hook = eventHooks[type];
2858            /* istanbul ignore if */
2859            if (type === 'click' && hackSafari) {
2860                elem.addEventListener('click', avalon.noop);
2861            }
2862            /* istanbul ignore if */
2863            if (hook) {
2864                type = hook.type || type;
2865                if (hook.fix) {
2866                    fn = hook.fix(elem, fn);
2867                    fn.uuid = uuid;
2868                }
2869            }
2870            var key = type + ':' + uuid;
2871            avalon.eventListeners[fn.uuid] = fn;
2872            /* istanbul ignore if */
2873            if (value.indexOf(type + ':') === -1) {
2874                //同一种事件只绑定一次
2875                if (canBubbleUp[type] || avalon.modern && focusBlur[type]) {
2876                    delegateEvent(type);
2877                } else {
2878                    avalon._nativeBind(elem, type, dispatch);
2879                }
2880            }
2881            var keys = value.split(',');
2882            /* istanbul ignore if */
2883            if (keys[0] === '') {
2884                keys.shift();
2885            }
2886            if (keys.indexOf(key) === -1) {
2887                keys.push(key);
2888                setEventId(elem, keys.join(','));
2889                //将令牌放进avalon-events属性中
2890            }
2891            return fn;
2892        } else {
2893            /* istanbul ignore next */
2894            var cb = function cb(e) {
2895                fn.call(elem, new avEvent(e));
2896            };
2897
2898            avalon._nativeBind(elem, type, cb);
2899            return cb;
2900        }
2901    };
2902
2903    function setEventId(node, value) {
2904        node.setAttribute('avalon-events', value);
2905    }
2906    /* istanbul ignore next */
2907    avalon.unbind = function (elem, type, fn) {
2908        if (elem.nodeType === 1) {
2909            var value = elem.getAttribute('avalon-events') || '';
2910            switch (arguments.length) {
2911                case 1:
2912                    avalon._nativeUnBind(elem, type, dispatch);
2913                    elem.removeAttribute('avalon-events');
2914                    break;
2915                case 2:
2916                    value = value.split(',').filter(function (str) {
2917                        return str.indexOf(type + ':') === -1;
2918                    }).join(',');
2919                    setEventId(elem, value);
2920                    break;
2921                default:
2922                    var search = type + ':' + fn.uuid;
2923                    value = value.split(',').filter(function (str) {
2924                        return str !== search;
2925                    }).join(',');
2926                    setEventId(elem, value);
2927                    delete avalon.eventListeners[fn.uuid];
2928                    break;
2929            }
2930        } else {
2931            avalon._nativeUnBind(elem, type, fn);
2932        }
2933    };
2934
2935    var typeRegExp = {};
2936
2937    function collectHandlers(elem, type, handlers) {
2938        var value = elem.getAttribute('avalon-events');
2939        if (value && (elem.disabled !== true || type !== 'click')) {
2940            var uuids = [];
2941            var reg = typeRegExp[type] || (typeRegExp[type] = new RegExp("\\b" + type + '\\:([^,\\s]+)', 'g'));
2942            value.replace(reg, function (a, b) {
2943                uuids.push(b);
2944                return a;
2945            });
2946            if (uuids.length) {
2947                handlers.push({
2948                    elem: elem,
2949                    uuids: uuids
2950                });
2951            }
2952        }
2953        elem = elem.parentNode;
2954        var g = avalon.gestureEvents || {};
2955        if (elem && elem.getAttribute && (canBubbleUp[type] || g[type])) {
2956            collectHandlers(elem, type, handlers);
2957        }
2958    }
2959
2960    var rhandleHasVm = /^e/;
2961
2962    function dispatch(event) {
2963        event = new avEvent(event);
2964        var type = event.type;
2965        var elem = event.target;
2966        var handlers = [];
2967        collectHandlers(elem, type, handlers);
2968        var i = 0,
2969            j,
2970            uuid,
2971            handler;
2972        while ((handler = handlers[i++]) && !event.cancelBubble) {
2973            var host = event.currentTarget = handler.elem;
2974            j = 0;
2975            while (uuid = handler.uuids[j++]) {
2976                if (event.stopImmediate) {
2977                    break;
2978                }
2979                var fn = avalon.eventListeners[uuid];
2980                if (fn) {
2981                    var vm = rhandleHasVm.test(uuid) ? handler.elem._ms_context_ : 0;
2982                    if (vm && vm.$hashcode === false) {
2983                        return avalon.unbind(elem, type, fn);
2984                    }
2985                    var ret = fn.call(vm || elem, event);
2986
2987                    if (ret === false) {
2988                        event.preventDefault();
2989                        event.stopPropagation();
2990                    }
2991                }
2992            }
2993        }
2994    }
2995
2996    var focusBlur = {
2997        focus: true,
2998        blur: true
2999    };
3000
3001    function delegateEvent(type) {
3002        var value = root.getAttribute('delegate-events') || '';
3003        if (value.indexOf(type) === -1) {
3004            //IE6-8会多次绑定同种类型的同一个函数,其他游览器不会
3005            var arr = value.match(avalon.rword) || [];
3006            arr.push(type);
3007            root.setAttribute('delegate-events', arr.join(','));
3008            avalon._nativeBind(root, type, dispatch, !!focusBlur[type]);
3009        }
3010    }
3011
3012    var eventProto = {
3013        webkitMovementY: 1,
3014        webkitMovementX: 1,
3015        keyLocation: 1,
3016        fixEvent: function fixEvent() {},
3017        preventDefault: function preventDefault() {
3018            var e = this.originalEvent || {};
3019            e.returnValue = this.returnValue = false;
3020            if (modern && e.preventDefault) {
3021                e.preventDefault();
3022            }
3023        },
3024        stopPropagation: function stopPropagation() {
3025            var e = this.originalEvent || {};
3026            e.cancelBubble = this.cancelBubble = true;
3027            if (modern && e.stopPropagation) {
3028                e.stopPropagation();
3029            }
3030        },
3031        stopImmediatePropagation: function stopImmediatePropagation() {
3032            this.stopPropagation();
3033            this.stopImmediate = true;
3034        },
3035        toString: function toString() {
3036            return '[object Event]'; //#1619
3037        }
3038    };
3039
3040    function avEvent(event) {
3041        if (event.originalEvent) {
3042            return event;
3043        }
3044        for (var i in event) {
3045            if (!eventProto[i]) {
3046                this[i] = event[i];
3047            }
3048        }
3049        if (!this.target) {
3050            this.target = event.srcElement;
3051        }
3052        var target = this.target;
3053        this.fixEvent();
3054        this.timeStamp = new Date() - 0;
3055        this.originalEvent = event;
3056    }
3057    avEvent.prototype = eventProto;
3058    //针对firefox, chrome修正mouseenter, mouseleave
3059    /* istanbul ignore if */
3060    if (!('onmouseenter' in root)) {
3061        avalon.each({
3062            mouseenter: 'mouseover',
3063            mouseleave: 'mouseout'
3064        }, function (origType, fixType) {
3065            eventHooks[origType] = {
3066                type: fixType,
3067                fix: function fix(elem, fn) {
3068                    return function (e) {
3069                        var t = e.relatedTarget;
3070                        if (!t || t !== elem && !(elem.compareDocumentPosition(t) & 16)) {
3071                            delete e.type;
3072                            e.type = origType;
3073                            return fn.apply(this, arguments);
3074                        }
3075                    };
3076                }
3077            };
3078        });
3079    }
3080    //针对IE9+, w3c修正animationend
3081    avalon.each({
3082        AnimationEvent: 'animationend',
3083        WebKitAnimationEvent: 'webkitAnimationEnd'
3084    }, function (construct, fixType) {
3085        if (window$1[construct] && !eventHooks.animationend) {
3086            eventHooks.animationend = {
3087                type: fixType
3088            };
3089        }
3090    });
3091
3092    /* istanbul ignore if */
3093    if (!("onmousewheel" in document$1)) {
3094        /* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
3095         firefox DOMMouseScroll detail 下3 上-3
3096         firefox wheel detlaY 下3 上-3
3097         IE9-11 wheel deltaY 下40 上-40
3098         chrome wheel deltaY 下100 上-100 */
3099        var fixWheelType = document$1.onwheel !== void 0 ? 'wheel' : 'DOMMouseScroll';
3100        var fixWheelDelta = fixWheelType === 'wheel' ? 'deltaY' : 'detail';
3101        eventHooks.mousewheel = {
3102            type: fixWheelType,
3103            fix: function fix(elem, fn) {
3104                return function (e) {
3105                    var delta = e[fixWheelDelta] > 0 ? -120 : 120;
3106                    e.wheelDelta = ~~elem._ms_wheel_ + delta;
3107                    elem._ms_wheel_ = e.wheelDeltaY = e.wheelDelta;
3108                    e.wheelDeltaX = 0;
3109                    if (Object.defineProperty) {
3110                        Object.defineProperty(e, 'type', {
3111                            value: 'mousewheel'
3112                        });
3113                    }
3114                    return fn.apply(this, arguments);
3115                };
3116            }
3117        };
3118    }
3119
3120    /* istanbul ignore if */
3121    if (!modern) {
3122        delete canBubbleUp.change;
3123        delete canBubbleUp.select;
3124    }
3125    /* istanbul ignore next */
3126    avalon._nativeBind = modern ? function (el, type, fn, capture) {
3127        el.addEventListener(type, fn, !!capture);
3128    } : function (el, type, fn) {
3129        el.attachEvent('on' + type, fn);
3130    };
3131    /* istanbul ignore next */
3132    avalon._nativeUnBind = modern ? function (el, type, fn, a) {
3133        el.removeEventListener(type, fn, !!a);
3134    } : function (el, type, fn) {
3135        el.detachEvent('on' + type, fn);
3136    };
3137    /* istanbul ignore next */
3138    avalon.fireDom = function (elem, type, opts) {
3139        if (document$1.createEvent) {
3140            var hackEvent = document$1.createEvent('Events');
3141            hackEvent.initEvent(type, true, true, opts);
3142            avalon.shadowCopy(hackEvent, opts);
3143            elem.dispatchEvent(hackEvent);
3144        } else if (root.contains(elem)) {
3145            //IE6-8触发事件必须保证在DOM树中,否则报'SCRIPT16389: 未指明的错误'
3146            hackEvent = document$1.createEventObject();
3147            if (opts) avalon.shadowCopy(hackEvent, opts);
3148            try {
3149                elem.fireEvent('on' + type, hackEvent);
3150            } catch (e) {
3151                avalon.log('fireDom', type, 'args error');
3152            }
3153        }
3154    };
3155
3156    var rmouseEvent = /^(?:mouse|contextmenu|drag)|click/;
3157    /* istanbul ignore next */
3158    avEvent.prototype.fixEvent = function () {
3159        var event = this;
3160        if (event.which == null && event.type.indexOf('key') === 0) {
3161            event.which = event.charCode != null ? event.charCode : event.keyCode;
3162        }
3163        if (rmouseEvent.test(event.type) && !('pageX' in event)) {
3164            var DOC = event.target.ownerDocument || document$1;
3165            var box = DOC.compatMode === 'BackCompat' ? DOC.body : DOC.documentElement;
3166            event.pageX = event.clientX + (box.scrollLeft >> 0) - (box.clientLeft >> 0);
3167            event.pageY = event.clientY + (box.scrollTop >> 0) - (box.clientTop >> 0);
3168            event.wheelDeltaY = ~~event.wheelDelta;
3169            event.wheelDeltaX = 0;
3170        }
3171    };
3172
3173    //针对IE6-8修正input
3174    /* istanbul ignore if */
3175    if (!('oninput' in document$1.createElement('input'))) {
3176        eventHooks.input = {
3177            type: 'propertychange',
3178            fix: function fix(elem, fn) {
3179                return function (e) {
3180                    if (e.propertyName === 'value') {
3181                        e.type = 'input';
3182                        return fn.apply(this, arguments);
3183                    }
3184                };
3185            }
3186        };
3187    }
3188
3189    var readyList = [];
3190
3191    function fireReady(fn) {
3192        avalon.isReady = true;
3193        while (fn = readyList.shift()) {
3194            fn(avalon);
3195        }
3196    }
3197
3198    avalon.ready = function (fn) {
3199        readyList.push(fn);
3200        if (avalon.isReady) {
3201            fireReady();
3202        }
3203    };
3204
3205    avalon.ready(function () {
3206        avalon.scan && avalon.scan(document$1.body);
3207    });
3208
3209    /* istanbul ignore next */
3210    function bootstrap() {
3211        function doScrollCheck() {
3212            try {
3213                //IE下通过doScrollCheck检测DOM树是否建完
3214                root.doScroll('left');
3215                fireReady();
3216            } catch (e) {
3217                setTimeout(doScrollCheck);
3218            }
3219        }
3220        if (document$1.readyState === 'complete') {
3221            setTimeout(fireReady); //如果在domReady之外加载
3222        } else if (document$1.addEventListener) {
3223            document$1.addEventListener('DOMContentLoaded', fireReady, false);
3224        } else if (document$1.attachEvent) {
3225            //必须传入三个参数,否则在firefox4-26中报错
3226            //caught exception: [Exception... "Not enough arguments"  nsresult: "0x
3227            document$1.attachEvent('onreadystatechange', function () {
3228                if (document$1.readyState === 'complete') {
3229                    fireReady();
3230                }
3231            });
3232            try {
3233                var isTop = window$1.frameElement === null;
3234            } catch (e) {}
3235            if (root.doScroll && isTop && window$1.external) {
3236                //fix IE iframe BUG
3237                doScrollCheck();
3238            }
3239        }
3240
3241        avalon.bind(window$1, 'load', fireReady);
3242    }
3243    if (inBrowser) {
3244        bootstrap();
3245    }
3246
3247    /**
3248     * ------------------------------------------------------------
3249     *                          DOM Api
3250     * shim,class,data,css,val,html,event,ready  
3251     * ------------------------------------------------------------
3252     */
3253
3254    var orphanTag = {
3255        script: 1,
3256        style: 1,
3257        textarea: 1,
3258        xmp: 1,
3259        noscript: 1,
3260        template: 1
3261    };
3262
3263    /* 
3264     *  此模块只用于文本转虚拟DOM, 
3265     *  因为在真实浏览器会对我们的HTML做更多处理,
3266     *  如, 添加额外属性, 改变结构
3267     *  此模块就是用于模拟这些行为
3268     */
3269    function makeOrphan(node, nodeName, innerHTML) {
3270        switch (nodeName) {
3271            case 'style':
3272            case 'script':
3273            case 'noscript':
3274            case 'template':
3275            case 'xmp':
3276                node.children = [{
3277                    nodeName: '#text',
3278                    nodeValue: innerHTML
3279                }];
3280                break;
3281            case 'textarea':
3282                var props = node.props;
3283                props.type = nodeName;
3284                props.value = innerHTML;
3285                node.children = [{
3286                    nodeName: '#text',
3287                    nodeValue: innerHTML
3288                }];
3289                break;
3290            case 'option':
3291                node.children = [{
3292                    nodeName: '#text',
3293                    nodeValue: trimHTML(innerHTML)
3294                }];
3295                break;
3296        }
3297    }
3298
3299    //专门用于处理option标签里面的标签
3300    var rtrimHTML = /<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi;
3301    function trimHTML(v) {
3302        return String(v).replace(rtrimHTML, '').trim();
3303    }
3304
3305    //widget rule duplex validate
3306
3307    function fromDOM(dom) {
3308        return [from$1(dom)];
3309    }
3310
3311    function from$1(node) {
3312        var type = node.nodeName.toLowerCase();
3313        switch (type) {
3314            case '#text':
3315            case '#comment':
3316                return {
3317                    nodeName: type,
3318                    dom: node,
3319                    nodeValue: node.nodeValue
3320                };
3321            default:
3322                var props = markProps(node, node.attributes || []);
3323                var vnode = {
3324                    nodeName: type,
3325                    dom: node,
3326                    isVoidTag: !!voidTag[type],
3327                    props: props
3328                };
3329                if (type === 'option') {
3330                    //即便你设置了option.selected = true,
3331                    //option.attributes也找不到selected属性
3332                    props.selected = node.selected;
3333                }
3334                if (orphanTag[type] || type === 'option') {
3335                    makeOrphan(vnode, type, node.text || node.innerHTML);
3336                    if (node.childNodes.length === 1) {
3337                        vnode.children[0].dom = node.firstChild;
3338                    }
3339                } else if (!vnode.isVoidTag) {
3340                    vnode.children = [];
3341                    for (var i = 0, el; el = node.childNodes[i++];) {
3342                        var child = from$1(el);
3343                        if (/\S/.test(child.nodeValue)) {
3344                            vnode.children.push(child);
3345                        }
3346                    }
3347                }
3348                return vnode;
3349        }
3350    }
3351
3352    var rformElement = /input|textarea|select/i;
3353
3354    function markProps(node, attrs) {
3355        var ret = {};
3356        for (var i = 0, n = attrs.length; i < n; i++) {
3357            var attr = attrs[i];
3358            if (attr.specified) {
3359                //IE6-9不会将属性名变小写,比如它会将用户的contenteditable变成contentEditable
3360                ret[attr.name.toLowerCase()] = attr.value;
3361            }
3362        }
3363        if (rformElement.test(node.nodeName)) {
3364            ret.type = node.type;
3365            var a = node.getAttributeNode('value');
3366            if (a && /\S/.test(a.value)) {
3367                //IE6,7中无法取得checkbox,radio的value
3368                ret.value = a.value;
3369            }
3370        }
3371        var style = node.style.cssText;
3372        if (style) {
3373            ret.style = style;
3374        }
3375        //类名 = 去重(静态类名+动态类名+ hover类名? + active类名)
3376        if (ret.type === 'select-one') {
3377            ret.selectedIndex = node.selectedIndex;
3378        }
3379        return ret;
3380    }
3381
3382    function VText(text) {
3383        this.nodeName = '#text';
3384        this.nodeValue = text;
3385    }
3386
3387    VText.prototype = {
3388        constructor: VText,
3389        toDOM: function toDOM() {
3390            /* istanbul ignore if*/
3391            if (this.dom) return this.dom;
3392            var v = avalon._decode(this.nodeValue);
3393            return this.dom = document$1.createTextNode(v);
3394        },
3395        toHTML: function toHTML() {
3396            return this.nodeValue;
3397        }
3398    };
3399
3400    function VComment(text) {
3401        this.nodeName = '#comment';
3402        this.nodeValue = text;
3403    }
3404    VComment.prototype = {
3405        constructor: VComment,
3406        toDOM: function toDOM() {
3407            if (this.dom) return this.dom;
3408            return this.dom = document$1.createComment(this.nodeValue);
3409        },
3410        toHTML: function toHTML() {
3411            return '<!--' + this.nodeValue + '-->';
3412        }
3413    };
3414
3415    function VElement(type, props, children, isVoidTag) {
3416        this.nodeName = type;
3417        this.props = props;
3418        this.children = children;
3419        this.isVoidTag = isVoidTag;
3420    }
3421    VElement.prototype = {
3422        constructor: VElement,
3423        toDOM: function toDOM() {
3424            if (this.dom) return this.dom;
3425            var dom,
3426                tagName = this.nodeName;
3427            if (avalon.modern && svgTags[tagName]) {
3428                dom = createSVG(tagName);
3429                /* istanbul ignore next*/
3430            } else if (!avalon.modern && (VMLTags[tagName] || rvml.test(tagName))) {
3431                dom = createVML(tagName);
3432            } else {
3433                dom = document$1.createElement(tagName);
3434            }
3435
3436            var props = this.props || {};
3437
3438            for (var i in props) {
3439                var val = props[i];
3440                if (skipFalseAndFunction(val)) {
3441                    /* istanbul ignore if*/
3442                    if (specalAttrs[i] && avalon.msie < 8) {
3443                        specalAttrs[i](dom, val);
3444                    } else {
3445                        dom.setAttribute(i, val + '');
3446                    }
3447                }
3448            }
3449            var c = this.children || [];
3450            var template = c[0] ? c[0].nodeValue : '';
3451            switch (this.nodeName) {
3452                case 'script':
3453                    dom.type = 'noexec';
3454                    dom.text = template;
3455                    try {
3456                        dom.innerHTML = template;
3457                    } catch (e) {}
3458                    dom.type = props.type || '';
3459                    break;
3460                case 'noscript':
3461                    dom.textContent = template;
3462                case 'style':
3463                case 'xmp':
3464                case 'template':
3465                    try {
3466                        dom.innerHTML = template;
3467                    } catch (e) {
3468                        /* istanbul ignore next*/
3469                        hackIE(dom, this.nodeName, template);
3470                    }
3471                    break;
3472                case 'option':
3473                    //IE6-8,为option添加文本子节点,不会同步到text属性中
3474                    /* istanbul ignore next */
3475                    if (msie < 9) dom.text = template;
3476                default:
3477                    /* istanbul ignore next */
3478                    if (!this.isVoidTag && this.children) {
3479                        this.children.forEach(function (el) {
3480                            return c && dom.appendChild(avalon.vdom(c, 'toDOM'));
3481                        });
3482                    }
3483                    break;
3484            }
3485            return this.dom = dom;
3486        },
3487
3488        /* istanbul ignore next */
3489
3490        toHTML: function toHTML() {
3491            var arr = [];
3492            var props = this.props || {};
3493            for (var i in props) {
3494                var val = props[i];
3495                if (skipFalseAndFunction(val)) {
3496                    arr.push(i + '=' + avalon.quote(props[i] + ''));
3497                }
3498            }
3499            arr = arr.length ? ' ' + arr.join(' ') : '';
3500            var str = '<' + this.nodeName + arr;
3501            if (this.isVoidTag) {
3502                return str + '/>';
3503            }
3504            str += '>';
3505            if (this.children) {
3506                str += this.children.map(function (el) {
3507                    return el ? avalon.vdom(el, 'toHTML') : '';
3508                }).join('');
3509            }
3510            return str + '</' + this.nodeName + '>';
3511        }
3512    };
3513    function hackIE(dom, nodeName, template) {
3514        switch (nodeName) {
3515            case 'style':
3516                dom.setAttribute('type', 'text/css');
3517                dom.styleSheet.cssText = template;
3518                break;
3519            case 'xmp': //IE6-8,XMP元素里面只能有文本节点,不能使用innerHTML
3520            case 'noscript':
3521                dom.textContent = template;
3522                break;
3523        }
3524    }
3525    function skipFalseAndFunction(a) {
3526        return a !== false && Object(a) !== a;
3527    }
3528    /* istanbul ignore next */
3529    var specalAttrs = {
3530        "class": function _class(dom, val) {
3531            dom.className = val;
3532        },
3533        style: function style(dom, val) {
3534            dom.style.cssText = val;
3535        },
3536        type: function type(dom, val) {
3537            try {
3538                //textarea,button 元素在IE6,7设置 type 属性会抛错
3539                dom.type = val;
3540            } catch (e) {}
3541        },
3542        'for': function _for(dom, val) {
3543            dom.setAttribute('for', val);
3544            dom.htmlFor = val;
3545        }
3546    };
3547
3548    function createSVG(type) {
3549        return document$1.createElementNS('http://www.w3.org/2000/svg', type);
3550    }
3551    var svgTags = avalon.oneObject('circle,defs,ellipse,image,line,' + 'path,polygon,polyline,rect,symbol,text,use,g,svg');
3552
3553    var rvml = /^\w+\:\w+/;
3554    /* istanbul ignore next*/
3555    function createVML(type) {
3556        if (document$1.styleSheets.length < 31) {
3557            document$1.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
3558        } else {
3559            // no more room, add to the existing one
3560            // http://msdn.microsoft.com/en-us/library/ms531194%28VS.85%29.aspx
3561            document$1.styleSheets[0].addRule(".rvml", "behavior:url(#default#VML)");
3562        }
3563        var arr = type.split(':');
3564        if (arr.length === 1) {
3565            arr.unshift('v');
3566        }
3567        var tag = arr[1];
3568        var ns = arr[0];
3569        if (!document$1.namespaces[ns]) {
3570            document$1.namespaces.add(ns, "urn:schemas-microsoft-com:vml");
3571        }
3572        return document$1.createElement('<' + ns + ':' + tag + ' class="rvml">');
3573    }
3574
3575    var VMLTags = avalon.oneObject('shape,line,polyline,rect,roundrect,oval,arc,' + 'curve,background,image,shapetype,group,fill,' + 'stroke,shadow, extrusion, textbox, imagedata, textpath');
3576
3577    function VFragment(children, key, val, index) {
3578        this.nodeName = '#document-fragment';
3579        this.children = children;
3580        this.key = key;
3581        this.val = val;
3582        this.index = index;
3583        this.props = {};
3584    }
3585    VFragment.prototype = {
3586        constructor: VFragment,
3587        toDOM: function toDOM() {
3588            if (this.dom) return this.dom;
3589            var f = this.toFragment();
3590            //IE6-11 docment-fragment都没有children属性 
3591            this.split = f.lastChild;
3592            return this.dom = f;
3593        },
3594        dispose: function dispose() {
3595            this.toFragment();
3596            this.innerRender && this.innerRender.dispose();
3597            for (var i in this) {
3598                this[i] = null;
3599            }
3600        },
3601        toFragment: function toFragment() {
3602            var f = createFragment();
3603            this.children.forEach(function (el) {
3604                return f.appendChild(avalon.vdom(el, 'toDOM'));
3605            });
3606            return f;
3607        },
3608        toHTML: function toHTML() {
3609            var c = this.children;
3610            return c.map(function (el) {
3611                return avalon.vdom(el, 'toHTML');
3612            }).join('');
3613        }
3614    };
3615
3616    /**
3617     * 虚拟DOM的4大构造器
3618     */
3619    avalon.mix(avalon, {
3620        VText: VText,
3621        VComment: VComment,
3622        VElement: VElement,
3623        VFragment: VFragment
3624    });
3625
3626    var constNameMap = {
3627        '#text': 'VText',
3628        '#document-fragment': 'VFragment',
3629        '#comment': 'VComment'
3630    };
3631
3632    var vdom = avalon.vdomAdaptor = avalon.vdom = function (obj, method) {
3633        if (!obj) {
3634            //obj在ms-for循环里面可能是null
3635            return method === "toHTML" ? '' : createFragment();
3636        }
3637        var nodeName = obj.nodeName;
3638        if (!nodeName) {
3639            return new avalon.VFragment(obj)[method]();
3640        }
3641        var constName = constNameMap[nodeName] || 'VElement';
3642        return avalon[constName].prototype[method].call(obj);
3643    };
3644
3645    avalon.domize = function (a) {
3646        return avalon.vdom(a, 'toDOM');
3647    };
3648
3649    avalon.pendingActions = [];
3650    avalon.uniqActions = {};
3651    avalon.inTransaction = 0;
3652    config.trackDeps = false;
3653    avalon.track = function () {
3654        if (config.trackDeps) {
3655            avalon.log.apply(avalon, arguments);
3656        }
3657    };
3658
3659    /**
3660     * Batch is a pseudotransaction, just for purposes of memoizing ComputedValues when nothing else does.
3661     * During a batch `onBecomeUnobserved` will be called at most once per observable.
3662     * Avoids unnecessary recalculations.
3663     */
3664
3665    function runActions() {
3666        if (avalon.isRunningActions === true || avalon.inTransaction > 0) return;
3667        avalon.isRunningActions = true;
3668        var tasks = avalon.pendingActions.splice(0, avalon.pendingActions.length);
3669        for (var i = 0, task; task = tasks[i++];) {
3670            task.update();
3671            delete avalon.uniqActions[task.uuid];
3672        }
3673        avalon.isRunningActions = false;
3674    }
3675
3676    function propagateChanged(target) {
3677        var list = target.observers;
3678        for (var i = 0, el; el = list[i++];) {
3679            el.schedule(); //通知action, computed做它们该做的事
3680        }
3681    }
3682
3683    //将自己抛到市场上卖
3684    function reportObserved(target) {
3685        var action = avalon.trackingAction || null;
3686        if (action !== null) {
3687
3688            avalon.track('征收到', target.expr);
3689            action.mapIDs[target.uuid] = target;
3690        }
3691    }
3692
3693    var targetStack = [];
3694
3695    function collectDeps(action, getter) {
3696        if (!action.observers) return;
3697        var preAction = avalon.trackingAction;
3698        if (preAction) {
3699            targetStack.push(preAction);
3700        }
3701        avalon.trackingAction = action;
3702        avalon.track('【action】', action.type, action.expr, '开始征收依赖项');
3703        //多个observe持有同一个action
3704        action.mapIDs = {}; //重新收集依赖
3705        var hasError = true,
3706            result;
3707        try {
3708            result = getter.call(action);
3709            hasError = false;
3710        } finally {
3711            if (hasError) {
3712                avalon.warn('collectDeps fail', getter + '');
3713                action.mapIDs = {};
3714                avalon.trackingAction = preAction;
3715            } else {
3716                // 确保它总是为null
3717                avalon.trackingAction = targetStack.pop();
3718                try {
3719                    resetDeps(action);
3720                } catch (e) {
3721                    avalon.warn(e);
3722                }
3723            }
3724            return result;
3725        }
3726    }
3727
3728    function resetDeps(action) {
3729        var prev = action.observers,
3730            curr = [],
3731            checked = {},
3732            ids = [];
3733        for (var i in action.mapIDs) {
3734            var dep = action.mapIDs[i];
3735            if (!dep.isAction) {
3736                if (!dep.observers) {
3737                    //如果它已经被销毁
3738                    delete action.mapIDs[i];
3739                    continue;
3740                }
3741                ids.push(dep.uuid);
3742                curr.push(dep);
3743                checked[dep.uuid] = 1;
3744                if (dep.lastAccessedBy === action.uuid) {
3745                    continue;
3746                }
3747                dep.lastAccessedBy = action.uuid;
3748                avalon.Array.ensure(dep.observers, action);
3749            }
3750        }
3751        var ids = ids.sort().join(',');
3752        if (ids === action.ids) {
3753            return;
3754        }
3755        action.ids = ids;
3756        if (!action.isComputed) {
3757            action.observers = curr;
3758        } else {
3759            action.depsCount = curr.length;
3760            action.deps = avalon.mix({}, action.mapIDs);
3761            action.depsVersion = {};
3762            for (var _i in action.mapIDs) {
3763                var _dep = action.mapIDs[_i];
3764                action.depsVersion[_dep.uuid] = _dep.version;
3765            }
3766        }
3767
3768        for (var _i2 = 0, _dep2; _dep2 = prev[_i2++];) {
3769            if (!checked[_dep2.uuid]) {
3770                avalon.Array.remove(_dep2.observers, action);
3771            }
3772        }
3773    }
3774
3775    function transaction(action, thisArg, args) {
3776        args = args || [];
3777        var name = 'transaction ' + (action.name || action.displayName || 'noop');
3778        transactionStart(name);
3779        var res = action.apply(thisArg, args);
3780        transactionEnd(name);
3781        return res;
3782    }
3783    avalon.transaction = transaction;
3784
3785    function transactionStart(name) {
3786        avalon.inTransaction += 1;
3787    }
3788
3789    function transactionEnd(name) {
3790        if (--avalon.inTransaction === 0) {
3791            avalon.isRunningActions = false;
3792            runActions();
3793        }
3794    }
3795
3796    /* 
3797     * 将要检测的字符串的字符串替换成??123这样的格式
3798     */
3799    var stringNum = 0;
3800    var stringPool = {
3801        map: {}
3802    };
3803    var rfill = /\?\?\d+/g;
3804    function dig(a) {
3805        var key = '??' + stringNum++;
3806        stringPool.map[key] = a;
3807        return key + ' ';
3808    }
3809    function fill(a) {
3810        var val = stringPool.map[a];
3811        return val;
3812    }
3813    function clearString(str) {
3814        var array = readString(str);
3815        for (var i = 0, n = array.length; i < n; i++) {
3816            str = str.replace(array[i], dig);
3817        }
3818        return str;
3819    }
3820    //https://github.com/RubyLouvre/avalon/issues/1944
3821    function readString(str, i, ret) {
3822        var end = false,
3823            s = 0,
3824            i = i || 0;
3825        ret = ret || [];
3826        for (var n = str.length; i < n; i++) {
3827            var c = str.charAt(i);
3828            if (!end) {
3829                if (c === "'") {
3830                    end = "'";
3831                    s = i;
3832                } else if (c === '"') {
3833                    end = '"';
3834                    s = i;
3835                }
3836            } else {
3837                if (c === end) {
3838                    ret.push(str.slice(s, i + 1));
3839                    end = false;
3840                }
3841            }
3842        }
3843        if (end !== false) {
3844            return readString(str, s + 1, ret);
3845        }
3846        return ret;
3847    }
3848
3849    var keyMap = avalon.oneObject("break,case,catch,continue,debugger,default,delete,do,else,false," + "finally,for,function,if,in,instanceof,new,null,return,switch,this," + "throw,true,try,typeof,var,void,while,with," + /* 关键字*/
3850    "abstract,boolean,byte,char,class,const,double,enum,export,extends," + "final,float,goto,implements,import,int,interface,long,native," + "package,private,protected,public,short,static,super,synchronized," + "throws,transient,volatile,arguments");
3851
3852    var skipMap = avalon.mix({
3853        Math: 1,
3854        Date: 1,
3855        $event: 1,
3856        window: 1,
3857        __vmodel__: 1,
3858        avalon: 1
3859    }, keyMap);
3860
3861    var rvmKey = /(^|[^\w\u00c0-\uFFFF_])(@|##)(?=[$\w])/g;
3862    var ruselessSp = /\s*(\.|\|)\s*/g;
3863    var rshortCircuit = /\|\|/g;
3864    var brackets = /\(([^)]*)\)/;
3865    var rpipeline = /\|(?=\?\?)/;
3866    var rregexp = /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/g;
3867    var robjectProp = /\.[\w\.\$]+/g; //对象的属性 el.xxx 中的xxx
3868    var robjectKey = /(\{|\,)\s*([\$\w]+)\s*:/g; //对象的键名与冒号 {xxx:1,yyy: 2}中的xxx, yyy
3869    var rfilterName = /\|(\w+)/g;
3870    var rlocalVar = /[$a-zA-Z_][$a-zA-Z0-9_]*/g;
3871
3872    var exprCache = new Cache(300);
3873
3874    function addScopeForLocal(str) {
3875        return str.replace(robjectProp, dig).replace(rlocalVar, function (el) {
3876            if (!skipMap[el]) {
3877                return "__vmodel__." + el;
3878            }
3879            return el;
3880        });
3881    }
3882
3883    function addScope(expr, type) {
3884        var cacheKey = expr + ':' + type;
3885        var cache = exprCache.get(cacheKey);
3886        if (cache) {
3887            return cache.slice(0);
3888        }
3889
3890        stringPool.map = {};
3891        //https://github.com/RubyLouvre/avalon/issues/1849
3892        var input = expr.replace(rregexp, function (a, b) {
3893            return b + dig(a.slice(b.length));
3894        }); //移除所有正则
3895        input = clearString(input); //移除所有字符串
3896        input = input.replace(rshortCircuit, dig). //移除所有短路运算符
3897        replace(ruselessSp, '$1'). //移除.|两端空白
3898
3899        replace(robjectKey, function (_, a, b) {
3900            //移除所有键名
3901            return a + dig(b) + ':'; //比如 ms-widget="[{is:'ms-address-wrap', $id:'address'}]"这样极端的情况 
3902        }).replace(rvmKey, '$1__vmodel__.'). //转换@与##为__vmodel__
3903        replace(rfilterName, function (a, b) {
3904            //移除所有过滤器的名字
3905            return '|' + dig(b);
3906        });
3907        input = addScopeForLocal(input); //在本地变量前添加__vmodel__
3908
3909        var filters = input.split(rpipeline); //根据管道符切割表达式
3910        var body = filters.shift().replace(rfill, fill).trim();
3911        if (/\?\?\d/.test(body)) {
3912            body = body.replace(rfill, fill);
3913        }
3914        if (filters.length) {
3915            filters = filters.map(function (filter) {
3916                var bracketArgs = '';
3917                filter = filter.replace(brackets, function (a, b) {
3918                    if (/\S/.test(b)) {
3919                        bracketArgs += ',' + b; //还原字符串,正则,短路运算符
3920                    }
3921                    return '';
3922                });
3923                var arg = '[' + avalon.quote(filter.trim()) + bracketArgs + ']';
3924                return arg;
3925            });
3926            filters = 'avalon.composeFilters(' + filters + ')(__value__)';
3927            filters = filters.replace(rfill, fill);
3928        } else {
3929            filters = '';
3930        }
3931        return exprCache.put(cacheKey, [body, filters]);
3932    }
3933    var rhandleName = /^__vmodel__\.[$\w\.]+$/;
3934    var rfixIE678 = /__vmodel__\.([^(]+)\(([^)]*)\)/;
3935    function makeHandle(body) {
3936        if (rhandleName.test(body)) {
3937            body = body + '($event)';
3938        }
3939        /* istanbul ignore if */
3940        if (msie < 9) {
3941            body = body.replace(rfixIE678, function (a, b, c) {
3942                return '__vmodel__.' + b + '.call(__vmodel__' + (/\S/.test(c) ? ',' + c : '') + ')';
3943            });
3944        }
3945        return body;
3946    }
3947    function createGetter(expr, type) {
3948        var arr = addScope(expr, type),
3949            body;
3950        if (!arr[1]) {
3951            body = arr[0];
3952        } else {
3953            body = arr[1].replace(/__value__\)$/, arr[0] + ')');
3954        }
3955        try {
3956            return new Function('__vmodel__', 'return ' + body + ';');
3957            /* istanbul ignore next */
3958        } catch (e) {
3959            avalon.log('parse getter: [', expr, body, ']error');
3960            return avalon.noop;
3961        }
3962    }
3963
3964    /**
3965     * 生成表达式设值函数
3966     * @param  {String}  expr
3967     */
3968    function createSetter(expr, type) {
3969        var arr = addScope(expr, type);
3970        var body = 'try{ ' + arr[0] + ' = __value__}catch(e){avalon.log(e, "in on dir")}';
3971        try {
3972            return new Function('__vmodel__', '__value__', body + ';');
3973            /* istanbul ignore next */
3974        } catch (e) {
3975            avalon.log('parse setter: ', expr, ' error');
3976            return avalon.noop;
3977        }
3978    }
3979
3980    var actionUUID = 1;
3981    //需要重构
3982    function Action(vm, options, callback) {
3983        for (var i in options) {
3984            if (protectedMenbers[i] !== 1) {
3985                this[i] = options[i];
3986            }
3987        }
3988
3989        this.vm = vm;
3990        this.observers = [];
3991        this.callback = callback;
3992        this.uuid = ++actionUUID;
3993        this.ids = '';
3994        this.mapIDs = {}; //这个用于去重
3995        this.isAction = true;
3996        var expr = this.expr;
3997        // 缓存取值函数
3998        if (typeof this.getter !== 'function') {
3999            this.getter = createGetter(expr, this.type);
4000        }
4001        // 缓存设值函数(双向数据绑定)
4002        if (this.type === 'duplex') {
4003            this.setter = createSetter(expr, this.type);
4004        }
4005        // 缓存表达式旧值
4006        this.value = NaN;
4007        // 表达式初始值 & 提取依赖
4008        if (!this.node) {
4009            this.value = this.get();
4010        }
4011    }
4012
4013    Action.prototype = {
4014        getValue: function getValue() {
4015            var scope = this.vm;
4016            try {
4017                return this.getter.call(scope, scope);
4018            } catch (e) {
4019                avalon.log(this.getter + ' exec error');
4020            }
4021        },
4022        setValue: function setValue(value) {
4023            var scope = this.vm;
4024            if (this.setter) {
4025                this.setter.call(scope, scope, value);
4026            }
4027        },
4028
4029
4030        // get --> getValue --> getter
4031        get: function get(fn) {
4032            var name = 'action track ' + this.type;
4033
4034            if (this.deep) {
4035                avalon.deepCollect = true;
4036            }
4037
4038            var value = collectDeps(this, this.getValue);
4039            if (this.deep && avalon.deepCollect) {
4040                avalon.deepCollect = false;
4041            }
4042
4043            return value;
4044        },
4045
4046
4047        /**
4048         * 在更新视图前保存原有的value
4049         */
4050        beforeUpdate: function beforeUpdate() {
4051            return this.oldValue = getPlainObject(this.value);
4052        },
4053        update: function update(args, uuid) {
4054            var oldVal = this.beforeUpdate();
4055            var newVal = this.value = this.get();
4056            var callback = this.callback;
4057            if (callback && this.diff(newVal, oldVal, args)) {
4058                callback.call(this.vm, this.value, oldVal, this.expr);
4059            }
4060            this._isScheduled = false;
4061        },
4062        schedule: function schedule() {
4063            if (!this._isScheduled) {
4064                this._isScheduled = true;
4065                if (!avalon.uniqActions[this.uuid]) {
4066                    avalon.uniqActions[this.uuid] = 1;
4067                    avalon.pendingActions.push(this);
4068                }
4069
4070                runActions(); //这里会还原_isScheduled
4071
4072            }
4073        },
4074        removeDepends: function removeDepends() {
4075            var self = this;
4076            this.observers.forEach(function (depend) {
4077                avalon.Array.remove(depend.observers, self);
4078            });
4079        },
4080
4081
4082        /**
4083         * 比较两个计算值是否,一致,在for, class等能复杂数据类型的指令中,它们会重写diff复法
4084         */
4085        diff: function diff(a, b) {
4086            return a !== b;
4087        },
4088
4089
4090        /**
4091         * 销毁指令
4092         */
4093        dispose: function dispose() {
4094            this.value = null;
4095            this.removeDepends();
4096            if (this.beforeDispose) {
4097                this.beforeDispose();
4098            }
4099            for (var i in this) {
4100                delete this[i];
4101            }
4102        }
4103    };
4104
4105    function getPlainObject(v) {
4106        if (v && typeof v === 'object') {
4107            if (v && v.$events) {
4108                return v.$model;
4109            } else if (Array.isArray(v)) {
4110                var ret = [];
4111                for (var i = 0, n = v.length; i < n; i++) {
4112                    ret.push(getPlainObject(v[i]));
4113                }
4114                return ret;
4115            } else {
4116                var _ret = {};
4117                for (var _i3 in v) {
4118                    _ret[_i3] = getPlainObject(v[_i3]);
4119                }
4120                return _ret;
4121            }
4122        } else {
4123            return v;
4124        }
4125    }
4126
4127    var protectedMenbers = {
4128        vm: 1,
4129        callback: 1,
4130
4131        observers: 1,
4132        oldValue: 1,
4133        value: 1,
4134        getValue: 1,
4135        setValue: 1,
4136        get: 1,
4137
4138        removeDepends: 1,
4139        beforeUpdate: 1,
4140        update: 1,
4141        //diff
4142        //getter
4143        //setter
4144        //expr
4145        //vdom
4146        //type: "for"
4147        //name: "ms-for"
4148        //attrName: ":for"
4149        //param: "click"
4150        //beforeDispose
4151        dispose: 1
4152    };
4153
4154    /**
4155    * 
4156     与Computed等共享UUID
4157    */
4158    var obid = 1;
4159    function Mutation(expr, value, vm) {
4160        //构造函数
4161        this.expr = expr;
4162        if (value) {
4163            var childVm = platform.createProxy(value, this);
4164            if (childVm) {
4165                value = childVm;
4166            }
4167        }
4168        this.value = value;
4169        this.vm = vm;
4170        try {
4171            vm.$mutations[expr] = this;
4172        } catch (ignoreIE) {}
4173        this.uuid = ++obid;
4174        this.updateVersion();
4175        this.mapIDs = {};
4176        this.observers = [];
4177    }
4178
4179    Mutation.prototype = {
4180        get: function get() {
4181            if (avalon.trackingAction) {
4182                this.collect(); //被收集
4183                var childOb = this.value;
4184                if (childOb && childOb.$events) {
4185                    if (Array.isArray(childOb)) {
4186                        childOb.forEach(function (item) {
4187                            if (item && item.$events) {
4188                                item.$events.__dep__.collect();
4189                            }
4190                        });
4191                    } else if (avalon.deepCollect) {
4192                        for (var key in childOb) {
4193                            if (childOb.hasOwnProperty(key)) {
4194                                var collectIt = childOb[key];
4195                            }
4196                        }
4197                    }
4198                }
4199            }
4200            return this.value;
4201        },
4202        collect: function collect() {
4203            avalon.track(name, '被收集');
4204            reportObserved(this);
4205        },
4206        updateVersion: function updateVersion() {
4207            this.version = Math.random() + Math.random();
4208        },
4209        notify: function notify() {
4210            transactionStart();
4211            propagateChanged(this);
4212            transactionEnd();
4213        },
4214        set: function set(newValue) {
4215            var oldValue = this.value;
4216            if (newValue !== oldValue) {
4217                if (avalon.isObject(newValue)) {
4218                    var hash = oldValue && oldValue.$hashcode;
4219                    var childVM = platform.createProxy(newValue, this);
4220                    if (childVM) {
4221                        if (hash) {
4222                            childVM.$hashcode = hash;
4223                        }
4224                        newValue = childVM;
4225                    }
4226                }
4227                this.value = newValue;
4228                this.updateVersion();
4229                this.notify();
4230            }
4231        }
4232    };
4233
4234    function getBody(fn) {
4235        var entire = fn.toString();
4236        return entire.substring(entire.indexOf('{}') + 1, entire.lastIndexOf('}'));
4237    }
4238    //如果不存在三目,if,方法
4239    var instability = /(\?|if\b|\(.+\))/;
4240
4241    function __create(o) {
4242        var __ = function __() {};
4243        __.prototype = o;
4244        return new __();
4245    }
4246
4247    function __extends(child, parent) {
4248        if (typeof parent === 'function') {
4249            var proto = child.prototype = __create(parent.prototype);
4250            proto.constructor = child;
4251        }
4252    }
4253    var Computed = function (_super) {
4254        __extends(Computed, _super);
4255
4256        function Computed(name, options, vm) {
4257            //构造函数
4258            _super.call(this, name, undefined, vm);
4259            delete options.get;
4260            delete options.set;
4261
4262            avalon.mix(this, options);
4263            this.deps = {};
4264            this.type = 'computed';
4265            this.depsVersion = {};
4266            this.isComputed = true;
4267            this.trackAndCompute();
4268            if (!('isStable' in this)) {
4269                this.isStable = !instability.test(getBody(this.getter));
4270            }
4271        }
4272        var cp = Computed.prototype;
4273        cp.trackAndCompute = function () {
4274            if (this.isStable && this.depsCount > 0) {
4275                this.getValue();
4276            } else {
4277                collectDeps(this, this.getValue.bind(this));
4278            }
4279        };
4280
4281        cp.getValue = function () {
4282            return this.value = this.getter.call(this.vm);
4283        };
4284
4285        cp.schedule = function () {
4286            var observers = this.observers;
4287            var i = observers.length;
4288            while (i--) {
4289                var d = observers[i];
4290                if (d.schedule) {
4291                    d.schedule();
4292                }
4293            }
4294        };
4295
4296        cp.shouldCompute = function () {
4297            if (this.isStable) {
4298                //如果变动因子确定,那么只比较变动因子的版本
4299                var toComputed = false;
4300                for (var i in this.deps) {
4301                    if (this.deps[i].version !== this.depsVersion[i]) {
4302                        toComputed = true;
4303                        this.deps[i].version = this.depsVersion[i];
4304                    }
4305                }
4306                return toComputed;
4307            }
4308            return true;
4309        };
4310        cp.set = function () {
4311            if (this.setter) {
4312                avalon.transaction(this.setter, this.vm, arguments);
4313            }
4314        };
4315        cp.get = function () {
4316
4317            //当被设置了就不稳定,当它被访问了一次就是稳定
4318            this.collect();
4319
4320            if (this.shouldCompute()) {
4321                this.trackAndCompute();
4322                // console.log('computed 2 分支')
4323                this.updateVersion();
4324                //  this.reportChanged()
4325            }
4326
4327            //下面这一行好像没用
4328            return this.value;
4329        };
4330        return Computed;
4331    }(Mutation);
4332
4333    /**
4334     * 这里放置ViewModel模块的共用方法
4335     * avalon.define: 全框架最重要的方法,生成用户VM
4336     * IProxy, 基本用户数据产生的一个数据对象,基于$model与vmodel之间的形态
4337     * modelFactory: 生成用户VM
4338     * canHijack: 判定此属性是否该被劫持,加入数据监听与分发的的逻辑
4339     * createProxy: listFactory与modelFactory的封装
4340     * createAccessor: 实现数据监听与分发的重要对象
4341     * itemFactory: ms-for循环中产生的代理VM的生成工厂
4342     * fuseFactory: 两个ms-controller间产生的代理VM的生成工厂
4343     */
4344
4345    avalon.define = function (definition) {
4346        var $id = definition.$id;
4347        if (!$id) {
4348            avalon.error('vm.$id must be specified');
4349        }
4350        if (avalon.vmodels[$id]) {
4351            avalon.warn('error:[' + $id + '] had defined!');
4352        }
4353        var vm = platform.modelFactory(definition);
4354        return avalon.vmodels[$id] = vm;
4355    };
4356
4357    /**
4358     * 在未来的版本,avalon改用Proxy来创建VM,因此
4359     */
4360
4361    function IProxy(definition, dd) {
4362        avalon.mix(this, definition);
4363        avalon.mix(this, $$skipArray);
4364        this.$hashcode = avalon.makeHashCode('$');
4365        this.$id = this.$id || this.$hashcode;
4366        this.$events = {
4367            __dep__: dd || new Mutation(this.$id)
4368        };
4369        if (avalon.config.inProxyMode) {
4370            delete this.$mutations;
4371            this.$accessors = {};
4372            this.$computed = {};
4373            this.$track = '';
4374        } else {
4375            this.$accessors = {
4376                $model: modelAccessor
4377            };
4378        }
4379        if (dd === void 0) {
4380            this.$watch = platform.watchFactory(this.$events);
4381            this.$fire = platform.fireFactory(this.$events);
4382        } else {
4383            delete this.$watch;
4384            delete this.$fire;
4385        }
4386    }
4387
4388    platform.modelFactory = function modelFactory(definition, dd) {
4389        var $computed = definition.$computed || {};
4390        delete definition.$computed;
4391        var core = new IProxy(definition, dd);
4392        var $accessors = core.$accessors;
4393        var keys = [];
4394
4395        platform.hideProperty(core, '$mutations', {});
4396
4397        for (var key in definition) {
4398            if (key in $$skipArray) continue;
4399            var val = definition[key];
4400            keys.push(key);
4401            if (canHijack(key, val)) {
4402                $accessors[key] = createAccessor(key, val);
4403            }
4404        }
4405        for (var _key in $computed) {
4406            if (_key in $$skipArray) continue;
4407            var val = $computed[_key];
4408            if (typeof val === 'function') {
4409                val = {
4410                    get: val
4411                };
4412            }
4413            if (val && val.get) {
4414                val.getter = val.get;
4415                val.setter = val.set;
4416                avalon.Array.ensure(keys, _key);
4417                $accessors[_key] = createAccessor(_key, val, true);
4418            }
4419        }
4420        //将系统API以unenumerable形式加入vm,
4421        //添加用户的其他不可监听属性或方法
4422        //重写$track
4423        //并在IE6-8中增添加不存在的hasOwnPropert方法
4424        var vm = platform.createViewModel(core, $accessors, core);
4425        platform.afterCreate(vm, core, keys, !dd);
4426        return vm;
4427    };
4428    var $proxyItemBackdoorMap = {};
4429
4430    function canHijack(key, val, $proxyItemBackdoor) {
4431        if (key in $$skipArray) return false;
4432        if (key.charAt(0) === '$') {
4433            if ($proxyItemBackdoor) {
4434                if (!$proxyItemBackdoorMap[key]) {
4435                    $proxyItemBackdoorMap[key] = 1;
4436                    avalon.warn('ms-for\u4E2D\u7684\u53D8\u91CF' + key + '\u4E0D\u518D\u5EFA\u8BAE\u4EE5$\u4E3A\u524D\u7F00');
4437                }
4438                return true;
4439            }
4440            return false;
4441        }
4442        if (val == null) {
4443            avalon.warn('定义vmodel时' + key + '的属性值不能为null undefine');
4444            return true;
4445        }
4446        if (/error|date|function|regexp/.test(avalon.type(val))) {
4447            return false;
4448        }
4449        return !(val && val.nodeName && val.nodeType);
4450    }
4451
4452    function createProxy(target, dd) {
4453        if (target && target.$events) {
4454            return target;
4455        }
4456        var vm;
4457        if (Array.isArray(target)) {
4458            vm = platform.listFactory(target, false, dd);
4459        } else if (isObject(target)) {
4460            vm = platform.modelFactory(target, dd);
4461        }
4462        return vm;
4463    }
4464
4465    platform.createProxy = createProxy;
4466
4467    platform.itemFactory = function itemFactory(before, after) {
4468        var keyMap = before.$model;
4469        var core = new IProxy(keyMap);
4470        var state = avalon.shadowCopy(core.$accessors, before.$accessors); //防止互相污染
4471        var data = after.data;
4472        //core是包含系统属性的对象
4473        //keyMap是不包含系统属性的对象, keys
4474        for (var key in data) {
4475            var val = keyMap[key] = core[key] = data[key];
4476            state[key] = createAccessor(key, val);
4477        }
4478        var keys = Object.keys(keyMap);
4479        var vm = platform.createViewModel(core, state, core);
4480        platform.afterCreate(vm, core, keys);
4481        return vm;
4482    };
4483
4484    function createAccessor(key, val, isComputed) {
4485        var mutation = null;
4486        var Accessor = isComputed ? Computed : Mutation;
4487        return {
4488            get: function Getter() {
4489                if (!mutation) {
4490                    mutation = new Accessor(key, val, this);
4491                }
4492                return mutation.get();
4493            },
4494            set: function Setter(newValue) {
4495                if (!mutation) {
4496                    mutation = new Accessor(key, val, this);
4497                }
4498                mutation.set(newValue);
4499            },
4500            enumerable: true,
4501            configurable: true
4502        };
4503    }
4504
4505    platform.fuseFactory = function fuseFactory(before, after) {
4506        var keyMap = avalon.mix(before.$model, after.$model);
4507        var core = new IProxy(avalon.mix(keyMap, {
4508            $id: before.$id + after.$id
4509        }));
4510        var state = avalon.mix(core.$accessors, before.$accessors, after.$accessors); //防止互相污染
4511
4512        var keys = Object.keys(keyMap);
4513        //将系统API以unenumerable形式加入vm,并在IE6-8中添加hasOwnPropert方法
4514        var vm = platform.createViewModel(core, state, core);
4515        platform.afterCreate(vm, core, keys, false);
4516        return vm;
4517    };
4518
4519    function toJson(val) {
4520        var xtype = avalon.type(val);
4521        if (xtype === 'array') {
4522            var array = [];
4523            for (var i = 0; i < val.length; i++) {
4524                array[i] = toJson(val[i]);
4525            }
4526            return array;
4527        } else if (xtype === 'object') {
4528            if (typeof val.$track === 'string') {
4529                var obj = {};
4530                var arr = val.$track.match(/[^☥]+/g) || [];
4531                arr.forEach(function (i) {
4532                    var value = val[i];
4533                    obj[i] = value && value.$events ? toJson(value) : value;
4534                });
4535                return obj;
4536            }
4537        }
4538        return val;
4539    }
4540
4541    var modelAccessor = {
4542        get: function get() {
4543            return toJson(this);
4544        },
4545        set: avalon.noop,
4546        enumerable: false,
4547        configurable: true
4548    };
4549
4550    platform.toJson = toJson;
4551    platform.modelAccessor = modelAccessor;
4552
4553    var _splice = ap.splice;
4554    var __array__ = {
4555        set: function set(index, val) {
4556            if (index >>> 0 === index && this[index] !== val) {
4557                if (index > this.length) {
4558                    throw Error(index + 'set方法的第一个参数不能大于原数组长度');
4559                }
4560                this.splice(index, 1, val);
4561            }
4562        },
4563        toJSON: function toJSON() {
4564            //为了解决IE6-8的解决,通过此方法显式地求取数组的$model
4565            return this.$model = platform.toJson(this);
4566        },
4567        contains: function contains(el) {
4568            //判定是否包含
4569            return this.indexOf(el) !== -1;
4570        },
4571        ensure: function ensure(el) {
4572            if (!this.contains(el)) {
4573                //只有不存在才push
4574                this.push(el);
4575                return true;
4576            }
4577            return false;
4578        },
4579        pushArray: function pushArray(arr) {
4580            return this.push.apply(this, arr);
4581        },
4582        remove: function remove(el) {
4583            //移除第一个等于给定值的元素
4584            return this.removeAt(this.indexOf(el));
4585        },
4586        removeAt: function removeAt(index) {
4587            //移除指定索引上的元素
4588            if (index >>> 0 === index) {
4589                return this.splice(index, 1);
4590            }
4591            return [];
4592        },
4593        clear: function clear() {
4594            this.removeAll();
4595            return this;
4596        },
4597        removeAll: function removeAll(all) {
4598            //移除N个元素
4599            var size = this.length;
4600            var eliminate = Array.isArray(all) ? function (el) {
4601                return all.indexOf(el) !== -1;
4602            } : typeof all === 'function' ? all : false;
4603
4604            if (eliminate) {
4605                for (var i = this.length - 1; i >= 0; i--) {
4606                    if (eliminate(this[i], i)) {
4607                        _splice.call(this, i, 1);
4608                    }
4609                }
4610            } else {
4611                _splice.call(this, 0, this.length);
4612            }
4613            this.toJSON();
4614            this.$events.__dep__.notify();
4615        }
4616    };
4617    function hijackMethods(array) {
4618        for (var i in __array__) {
4619            platform.hideProperty(array, i, __array__[i]);
4620        }
4621    }
4622    var __method__ = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
4623
4624    __method__.forEach(function (method) {
4625        var original = ap[method];
4626        __array__[method] = function () {
4627            // 继续尝试劫持数组元素的属性
4628            var core = this.$events;
4629
4630            var args = platform.listFactory(arguments, true, core.__dep__);
4631            var result = original.apply(this, args);
4632
4633            this.toJSON();
4634            core.__dep__.notify(method);
4635            return result;
4636        };
4637    });
4638
4639    function listFactory(array, stop, dd) {
4640        if (!stop) {
4641            hijackMethods(array);
4642            if (modern) {
4643                Object.defineProperty(array, '$model', platform.modelAccessor);
4644            }
4645            platform.hideProperty(array, '$hashcode', avalon.makeHashCode('$'));
4646            platform.hideProperty(array, '$events', { __dep__: dd || new Mutation() });
4647        }
4648        var _dd = array.$events && array.$events.__dep__;
4649        for (var i = 0, n = array.length; i < n; i++) {
4650            var item = array[i];
4651            if (isObject(item)) {
4652                array[i] = platform.createProxy(item, _dd);
4653            }
4654        }
4655        return array;
4656    }
4657
4658    platform.listFactory = listFactory;
4659
4660    //如果浏览器不支持ecma262v5的Object.defineProperties或者存在BUG,比如IE8
4661    //标准浏览器使用__defineGetter__, __defineSetter__实现
4662    var canHideProperty = true;
4663    try {
4664        Object.defineProperty({}, '_', {
4665            value: 'x'
4666        });
4667        delete $$skipArray.$vbsetter;
4668        delete $$skipArray.$vbthis;
4669    } catch (e) {
4670        /* istanbul ignore next*/
4671        canHideProperty = false;
4672    }
4673
4674    var protectedVB = { $vbthis: 1, $vbsetter: 1 };
4675    /* istanbul ignore next */
4676    function hideProperty(host, name, value) {
4677        if (canHideProperty) {
4678            Object.defineProperty(host, name, {
4679                value: value,
4680                writable: true,
4681                enumerable: false,
4682                configurable: true
4683            });
4684        } else if (!protectedVB[name]) {
4685            /* istanbul ignore next */
4686            host[name] = value;
4687        }
4688    }
4689
4690    function watchFactory(core) {
4691        return function $watch(expr, callback, deep) {
4692            var w = new Action(core.__proxy__, {
4693                deep: deep,
4694                type: 'user',
4695                expr: expr
4696            }, callback);
4697            if (!core[expr]) {
4698                core[expr] = [w];
4699            } else {
4700                core[expr].push(w);
4701            }
4702
4703            return function () {
4704                w.dispose();
4705                avalon.Array.remove(core[expr], w);
4706                if (core[expr].length === 0) {
4707                    delete core[expr];
4708                }
4709            };
4710        };
4711    }
4712
4713    function fireFactory(core) {
4714        return function $fire(expr, a) {
4715            var list = core[expr];
4716            if (Array.isArray(list)) {
4717                for (var i = 0, w; w = list[i++];) {
4718                    w.callback.call(w.vm, a, w.value, w.expr);
4719                }
4720            }
4721        };
4722    }
4723
4724    function wrapIt(str) {
4725        return '☥' + str + '☥';
4726    }
4727
4728    function afterCreate(vm, core, keys, bindThis) {
4729        var ac = vm.$accessors;
4730        //隐藏系统属性
4731        for (var key in $$skipArray) {
4732            if (avalon.msie < 9 && core[key] === void 0) continue;
4733            hideProperty(vm, key, core[key]);
4734        }
4735        //为不可监听的属性或方法赋值
4736        for (var i = 0; i < keys.length; i++) {
4737            var _key2 = keys[i];
4738            if (!(_key2 in ac)) {
4739                var val = core[_key2];
4740                if (bindThis && typeof val === 'function') {
4741                    vm[_key2] = val.bind(vm);
4742                    vm[_key2]._orig = val;
4743                    continue;
4744                }
4745                vm[_key2] = val;
4746            }
4747        }
4748        vm.$track = keys.join('☥');
4749
4750        function hasOwnKey(key) {
4751            return wrapIt(vm.$track).indexOf(wrapIt(key)) > -1;
4752        }
4753        if (avalon.msie < 9) {
4754            vm.hasOwnProperty = hasOwnKey;
4755        }
4756        vm.$events.__proxy__ = vm;
4757    }
4758
4759    platform.hideProperty = hideProperty;
4760    platform.fireFactory = fireFactory;
4761    platform.watchFactory = watchFactory;
4762    platform.afterCreate = afterCreate;
4763
4764    var createViewModel = Object.defineProperties;
4765    var defineProperty;
4766
4767    var timeBucket = new Date() - 0;
4768    /* istanbul ignore if*/
4769    if (!canHideProperty) {
4770        if ('__defineGetter__' in avalon) {
4771            defineProperty = function defineProperty(obj, prop, desc) {
4772                if ('value' in desc) {
4773                    obj[prop] = desc.value;
4774                }
4775                if ('get' in desc) {
4776                    obj.__defineGetter__(prop, desc.get);
4777                }
4778                if ('set' in desc) {
4779                    obj.__defineSetter__(prop, desc.set);
4780                }
4781                return obj;
4782            };
4783            createViewModel = function createViewModel(obj, descs) {
4784                for (var prop in descs) {
4785                    if (descs.hasOwnProperty(prop)) {
4786                        defineProperty(obj, prop, descs[prop]);
4787                    }
4788                }
4789                return obj;
4790            };
4791        }
4792        /* istanbul ignore if*/
4793        if (msie < 9) {
4794            var VBClassPool = {};
4795            window.execScript([// jshint ignore:line
4796            'Function parseVB(code)', '\tExecuteGlobal(code)', 'End Function' //转换一段文本为VB代码
4797            ].join('\n'), 'VBScript');
4798
4799            var VBMediator = function VBMediator(instance, accessors, name, value) {
4800                // jshint ignore:line
4801                var accessor = accessors[name];
4802                if (arguments.length === 4) {
4803                    accessor.set.call(instance, value);
4804                } else {
4805                    return accessor.get.call(instance);
4806                }
4807            };
4808            createViewModel = function createViewModel(name, accessors, properties) {
4809                // jshint ignore:line
4810                var buffer = [];
4811                buffer.push('\tPrivate [$vbsetter]', '\tPublic  [$accessors]', '\tPublic Default Function [$vbthis](ac' + timeBucket + ', s' + timeBucket + ')', '\t\tSet  [$accessors] = ac' + timeBucket + ': set [$vbsetter] = s' + timeBucket, '\t\tSet  [$vbthis]    = Me', //链式调用
4812                '\tEnd Function');
4813                //添加普通属性,因为VBScript对象不能像JS那样随意增删属性,必须在这里预先定义好
4814                var uniq = {
4815                    $vbthis: true,
4816                    $vbsetter: true,
4817                    $accessors: true
4818                };
4819                for (name in $$skipArray) {
4820                    if (!uniq[name]) {
4821                        buffer.push('\tPublic [' + name + ']');
4822                        uniq[name] = true;
4823                    }
4824                }
4825                //添加访问器属性 
4826                for (name in accessors) {
4827                    if (uniq[name]) {
4828                        continue;
4829                    }
4830                    uniq[name] = true;
4831                    buffer.push(
4832                    //由于不知对方会传入什么,因此set, let都用上
4833                    '\tPublic Property Let [' + name + '](val' + timeBucket + ')', //setter
4834                    '\t\tCall [$vbsetter](Me, [$accessors], "' + name + '", val' + timeBucket + ')', '\tEnd Property', '\tPublic Property Set [' + name + '](val' + timeBucket + ')', //setter
4835                    '\t\tCall [$vbsetter](Me, [$accessors], "' + name + '", val' + timeBucket + ')', '\tEnd Property', '\tPublic Property Get [' + name + ']', //getter
4836                    '\tOn Error Resume Next', //必须优先使用set语句,否则它会误将数组当字符串返回
4837                    '\t\tSet[' + name + '] = [$vbsetter](Me, [$accessors],"' + name + '")', '\tIf Err.Number <> 0 Then', '\t\t[' + name + '] = [$vbsetter](Me, [$accessors],"' + name + '")', '\tEnd If', '\tOn Error Goto 0', '\tEnd Property');
4838                }
4839
4840                for (name in properties) {
4841                    if (!uniq[name]) {
4842                        uniq[name] = true;
4843                        buffer.push('\tPublic [' + name + ']');
4844                    }
4845                }
4846
4847                buffer.push('\tPublic [hasOwnProperty]');
4848                buffer.push('End Class');
4849                var body = buffer.join('\r\n');
4850                var className = VBClassPool[body];
4851                if (!className) {
4852                    className = avalon.makeHashCode('VBClass');
4853                    window.parseVB('Class ' + className + body);
4854                    window.parseVB(['Function ' + className + 'Factory(acc, vbm)', //创建实例并传入两个关键的参数
4855                    '\tDim o', '\tSet o = (New ' + className + ')(acc, vbm)', '\tSet ' + className + 'Factory = o', 'End Function'].join('\r\n'));
4856                    VBClassPool[body] = className;
4857                }
4858                var ret = window[className + 'Factory'](accessors, VBMediator); //得到其产品
4859                return ret; //得到其产品
4860            };
4861        }
4862    }
4863
4864    platform.createViewModel = createViewModel;
4865
4866    var impDir = avalon.directive('important', {
4867        priority: 1,
4868        getScope: function getScope(name, scope) {
4869            var v = avalon.vmodels[name];
4870            if (v) return v;
4871            throw 'error! no vmodel called ' + name;
4872        },
4873        update: function update(node, attrName, $id) {
4874            if (!avalon.inBrowser) return;
4875            var dom = avalon.vdom(node, 'toDOM');
4876            if (dom.nodeType === 1) {
4877                dom.removeAttribute(attrName);
4878                avalon(dom).removeClass('ms-controller');
4879            }
4880            var vm = avalon.vmodels[$id];
4881            if (vm) {
4882                vm.$element = dom;
4883                vm.$render = this;
4884                vm.$fire('onReady');
4885                delete vm.$events.onReady;
4886            }
4887        }
4888    });
4889
4890    var impCb = impDir.update;
4891
4892    avalon.directive('controller', {
4893        priority: 2,
4894        getScope: function getScope(name, scope) {
4895            var v = avalon.vmodels[name];
4896            if (v) {
4897                v.$render = this;
4898                if (scope && scope !== v) {
4899                    return platform.fuseFactory(scope, v);
4900                }
4901                return v;
4902            }
4903            return scope;
4904        },
4905        update: impCb
4906    });
4907
4908    avalon.directive('skip', {
4909        delay: true
4910    });
4911
4912    var arrayWarn = {};
4913    var cssDir = avalon.directive('css', {
4914        diff: function diff(newVal, oldVal) {
4915            if (Object(newVal) === newVal) {
4916                newVal = platform.toJson(newVal); //安全的遍历VBscript
4917                if (Array.isArray(newVal)) {
4918                    //转换成对象
4919                    var b = {};
4920                    newVal.forEach(function (el) {
4921                        el && avalon.shadowCopy(b, el);
4922                    });
4923                    newVal = b;
4924                    if (!arrayWarn[this.type]) {
4925                        avalon.warn('ms-' + this.type + '指令的值不建议使用数组形式了!');
4926                        arrayWarn[this.type] = 1;
4927                    }
4928                }
4929
4930                var hasChange = false;
4931                var patch = {};
4932                if (!oldVal) {
4933                    //如果一开始为空
4934                    patch = newVal;
4935                    hasChange = true;
4936                } else {
4937                    if (this.deep) {
4938                        var deep = typeof this.deep === 'number' ? this.deep : 6;
4939                        for (var i in newVal) {
4940                            //diff差异点  
4941                            if (!deepEquals(newVal[i], oldVal[i], 4)) {
4942                                this.value = newVal;
4943                                return true;
4944                            }
4945                            patch[i] = newVal[i];
4946                        }
4947                    } else {
4948                        for (var _i4 in newVal) {
4949                            //diff差异点
4950                            if (newVal[_i4] !== oldVal[_i4]) {
4951                                hasChange = true;
4952                            }
4953                            patch[_i4] = newVal[_i4];
4954                        }
4955                    }
4956
4957                    for (var _i5 in oldVal) {
4958                        if (!(_i5 in patch)) {
4959                            hasChange = true;
4960                            patch[_i5] = '';
4961                        }
4962                    }
4963                }
4964                if (hasChange) {
4965                    this.value = patch;
4966                    return true;
4967                }
4968            }
4969            return false;
4970        },
4971        update: function update(vdom, value) {
4972
4973            var dom = vdom.dom;
4974            if (dom && dom.nodeType === 1) {
4975                var wrap = avalon(dom);
4976                for (var name in value) {
4977                    wrap.css(name, value[name]);
4978                }
4979            }
4980        }
4981    });
4982
4983    var cssDiff = cssDir.diff;
4984
4985    function getEnumerableKeys(obj) {
4986        var res = [];
4987        for (var key in obj) {
4988            res.push(key);
4989        }return res;
4990    }
4991
4992    function deepEquals(a, b, level) {
4993        if (level === 0) return a === b;
4994        if (a === null && b === null) return true;
4995        if (a === undefined && b === undefined) return true;
4996        var aIsArray = Array.isArray(a);
4997        if (aIsArray !== Array.isArray(b)) {
4998            return false;
4999        }
5000        if (aIsArray) {
5001            return equalArray(a, b, level);
5002        } else if (typeof a === "object" && typeof b === "object") {
5003            return equalObject(a, b, level);
5004        }
5005        return a === b;
5006    }
5007
5008    function equalArray(a, b, level) {
5009        if (a.length !== b.length) {
5010            return false;
5011        }
5012        for (var i = a.length - 1; i >= 0; i--) {
5013            try {
5014                if (!deepEquals(a[i], b[i], level - 1)) {
5015                    return false;
5016                }
5017            } catch (noThisPropError) {
5018                return false;
5019            }
5020        }
5021        return true;
5022    }
5023
5024    function equalObject(a, b, level) {
5025        if (a === null || b === null) return false;
5026        if (getEnumerableKeys(a).length !== getEnumerableKeys(b).length) return false;
5027        for (var prop in a) {
5028            if (!(prop in b)) return false;
5029            try {
5030                if (!deepEquals(a[prop], b[prop], level - 1)) {
5031                    return false;
5032                }
5033            } catch (noThisPropError) {
5034                return false;
5035            }
5036        }
5037        return true;
5038    }
5039
5040    /**
5041     * ------------------------------------------------------------
5042     * 检测浏览器对CSS动画的支持与API名
5043     * ------------------------------------------------------------
5044     */
5045
5046    var checker = {
5047        TransitionEvent: 'transitionend',
5048        WebKitTransitionEvent: 'webkitTransitionEnd',
5049        OTransitionEvent: 'oTransitionEnd',
5050        otransitionEvent: 'otransitionEnd'
5051    };
5052    var css3 = void 0;
5053    var tran = void 0;
5054    var ani = void 0;
5055    var name$2 = void 0;
5056    var animationEndEvent = void 0;
5057    var transitionEndEvent = void 0;
5058    var transition = false;
5059    var animation = false;
5060    //有的浏览器同时支持私有实现与标准写法,比如webkit支持前两种,Opera支持1、3、4
5061    for (name$2 in checker) {
5062        if (window$1[name$2]) {
5063            tran = checker[name$2];
5064            break;
5065        }
5066        /* istanbul ignore next */
5067        try {
5068            var a = document.createEvent(name$2);
5069            tran = checker[name$2];
5070            break;
5071        } catch (e) {}
5072    }
5073    if (typeof tran === 'string') {
5074        transition = css3 = true;
5075        transitionEndEvent = tran;
5076    }
5077
5078    //animationend有两个可用形态
5079    //IE10+, Firefox 16+ & Opera 12.1+: animationend
5080    //Chrome/Safari: webkitAnimationEnd
5081    //http://blogs.msdn.com/b/davrous/archive/2011/12/06/introduction-to-css3-animat ions.aspx
5082    //IE10也可以使用MSAnimationEnd监听,但是回调里的事件 type依然为animationend
5083    //  el.addEventListener('MSAnimationEnd', function(e) {
5084    //     alert(e.type)// animationend!!!
5085    // })
5086    checker = {
5087        'AnimationEvent': 'animationend',
5088        'WebKitAnimationEvent': 'webkitAnimationEnd'
5089    };
5090    for (name$2 in checker) {
5091        if (window$1[name$2]) {
5092            ani = checker[name$2];
5093            break;
5094        }
5095    }
5096    if (typeof ani === 'string') {
5097        animation = css3 = true;
5098        animationEndEvent = ani;
5099    }
5100
5101    var effectDir = avalon.directive('effect', {
5102        priority: 5,
5103        diff: function diff(effect) {
5104            var vdom = this.node;
5105            if (typeof effect === 'string') {
5106                this.value = effect = {
5107                    is: effect
5108                };
5109                avalon.warn('ms-effect的指令值不再支持字符串,必须是一个对象');
5110            }
5111            this.value = vdom.effect = effect;
5112            var ok = cssDiff.call(this, effect, this.oldValue);
5113            var me = this;
5114            if (ok) {
5115                setTimeout(function () {
5116                    vdom.animating = true;
5117                    effectDir.update.call(me, vdom, vdom.effect);
5118                });
5119                vdom.animating = false;
5120                return true;
5121            }
5122            return false;
5123        },
5124
5125        update: function update(vdom, change, opts) {
5126            var dom = vdom.dom;
5127            if (dom && dom.nodeType === 1) {
5128                //要求配置对象必须指定is属性,action必须是布尔或enter,leave,move
5129                var option = change || opts;
5130                var is = option.is;
5131
5132                var globalOption = avalon.effects[is];
5133                if (!globalOption) {
5134                    //如果没有定义特效
5135                    avalon.warn(is + ' effect is undefined');
5136                    return;
5137                }
5138                var finalOption = {};
5139                var action = actionMaps[option.action];
5140                if (typeof Effect.prototype[action] !== 'function') {
5141                    avalon.warn('action is undefined');
5142                    return;
5143                }
5144                //必须预定义特效
5145
5146                var effect = new avalon.Effect(dom);
5147                avalon.mix(finalOption, globalOption, option, { action: action });
5148
5149                if (finalOption.queue) {
5150                    animationQueue.push(function () {
5151                        effect[action](finalOption);
5152                    });
5153                    callNextAnimation();
5154                } else {
5155
5156                    effect[action](finalOption);
5157                }
5158                return true;
5159            }
5160        }
5161    });
5162
5163    var move = 'move';
5164    var leave = 'leave';
5165    var enter = 'enter';
5166    var actionMaps = {
5167        'true': enter,
5168        'false': leave,
5169        enter: enter,
5170        leave: leave,
5171        move: move,
5172        'undefined': enter
5173    };
5174
5175    var animationQueue = [];
5176    function callNextAnimation() {
5177        var fn = animationQueue[0];
5178        if (fn) {
5179            fn();
5180        }
5181    }
5182
5183    avalon.effects = {};
5184    avalon.effect = function (name, opts) {
5185        var definition = avalon.effects[name] = opts || {};
5186        if (css3 && definition.css !== false) {
5187            patchObject(definition, 'enterClass', name + '-enter');
5188            patchObject(definition, 'enterActiveClass', definition.enterClass + '-active');
5189            patchObject(definition, 'leaveClass', name + '-leave');
5190            patchObject(definition, 'leaveActiveClass', definition.leaveClass + '-active');
5191        }
5192        return definition;
5193    };
5194
5195    function patchObject(obj, name, value) {
5196        if (!obj[name]) {
5197            obj[name] = value;
5198        }
5199    }
5200
5201    var Effect = function Effect(dom) {
5202        this.dom = dom;
5203    };
5204
5205    avalon.Effect = Effect;
5206
5207    Effect.prototype = {
5208        enter: createAction('Enter'),
5209        leave: createAction('Leave'),
5210        move: createAction('Move')
5211    };
5212
5213    function execHooks(options, name, el) {
5214        var fns = [].concat(options[name]);
5215        for (var i = 0, fn; fn = fns[i++];) {
5216            if (typeof fn === 'function') {
5217                fn(el);
5218            }
5219        }
5220    }
5221    var staggerCache = new Cache(128);
5222
5223    function createAction(action) {
5224        var lower = action.toLowerCase();
5225        return function (option) {
5226            var dom = this.dom;
5227            var elem = avalon(dom);
5228            //处理与ms-for指令相关的stagger
5229            //========BEGIN=====
5230            var staggerTime = isFinite(option.stagger) ? option.stagger * 1000 : 0;
5231            if (staggerTime) {
5232                if (option.staggerKey) {
5233                    var stagger = staggerCache.get(option.staggerKey) || staggerCache.put(option.staggerKey, {
5234                        count: 0,
5235                        items: 0
5236                    });
5237                    stagger.count++;
5238                    stagger.items++;
5239                }
5240            }
5241            var staggerIndex = stagger && stagger.count || 0;
5242            //=======END==========
5243            var stopAnimationID;
5244            var animationDone = function animationDone(e) {
5245                var isOk = e !== false;
5246                if (--dom.__ms_effect_ === 0) {
5247                    avalon.unbind(dom, transitionEndEvent);
5248                    avalon.unbind(dom, animationEndEvent);
5249                }
5250                clearTimeout(stopAnimationID);
5251                var dirWord = isOk ? 'Done' : 'Abort';
5252                execHooks(option, 'on' + action + dirWord, dom);
5253                if (stagger) {
5254                    if (--stagger.items === 0) {
5255                        stagger.count = 0;
5256                    }
5257                }
5258                if (option.queue) {
5259                    animationQueue.shift();
5260                    callNextAnimation();
5261                }
5262            };
5263            //执行开始前的钩子
5264            execHooks(option, 'onBefore' + action, dom);
5265
5266            if (option[lower]) {
5267                //使用JS方式执行动画
5268                option[lower](dom, function (ok) {
5269                    animationDone(ok !== false);
5270                });
5271            } else if (css3) {
5272                //使用CSS3方式执行动画
5273                elem.addClass(option[lower + 'Class']);
5274                elem.removeClass(getNeedRemoved(option, lower));
5275
5276                if (!dom.__ms_effect_) {
5277                    //绑定动画结束事件
5278                    elem.bind(transitionEndEvent, animationDone);
5279                    elem.bind(animationEndEvent, animationDone);
5280                    dom.__ms_effect_ = 1;
5281                } else {
5282                    dom.__ms_effect_++;
5283                }
5284                setTimeout(function () {
5285                    //用xxx-active代替xxx类名的方式 触发CSS3动画
5286                    var time = avalon.root.offsetWidth === NaN;
5287                    elem.addClass(option[lower + 'ActiveClass']);
5288                    //计算动画时长
5289                    time = getAnimationTime(dom);
5290                    if (!time === 0) {
5291                        //立即结束动画
5292                        animationDone(false);
5293                    } else if (!staggerTime) {
5294                        //如果动画超出时长还没有调用结束事件,这可能是元素被移除了
5295                        //如果强制结束动画
5296                        stopAnimationID = setTimeout(function () {
5297                            animationDone(false);
5298                        }, time + 32);
5299                    }
5300                }, 17 + staggerTime * staggerIndex); // = 1000/60
5301            }
5302        };
5303    }
5304
5305    avalon.applyEffect = function (dom, vdom, opts) {
5306        var cb = opts.cb;
5307        var curEffect = vdom.effect;
5308        if (curEffect && dom && dom.nodeType === 1) {
5309            var hook = opts.hook;
5310            var old = curEffect[hook];
5311            if (cb) {
5312                if (Array.isArray(old)) {
5313                    old.push(cb);
5314                } else if (old) {
5315                    curEffect[hook] = [old, cb];
5316                } else {
5317                    curEffect[hook] = [cb];
5318                }
5319            }
5320            getAction(opts);
5321            avalon.directives.effect.update(vdom, curEffect, avalon.shadowCopy({}, opts));
5322        } else if (cb) {
5323            cb(dom);
5324        }
5325    };
5326    /**
5327     * 获取方向
5328     */
5329    function getAction(opts) {
5330        if (!opts.action) {
5331            return opts.action = opts.hook.replace(/^on/, '').replace(/Done$/, '').toLowerCase();
5332        }
5333    }
5334    /**
5335     * 需要移除的类名
5336     */
5337    function getNeedRemoved(options, name) {
5338        var name = name === 'leave' ? 'enter' : 'leave';
5339        return Array(name + 'Class', name + 'ActiveClass').map(function (cls) {
5340            return options[cls];
5341        }).join(' ');
5342    }
5343    /**
5344     * 计算动画长度
5345     */
5346    var transitionDuration = avalon.cssName('transition-duration');
5347    var animationDuration = avalon.cssName('animation-duration');
5348    var rsecond = /\d+s$/;
5349    function toMillisecond(str) {
5350        var ratio = rsecond.test(str) ? 1000 : 1;
5351        return parseFloat(str) * ratio;
5352    }
5353
5354    function getAnimationTime(dom) {
5355        var computedStyles = window$1.getComputedStyle(dom, null);
5356        var tranDuration = computedStyles[transitionDuration];
5357        var animDuration = computedStyles[animationDuration];
5358        return toMillisecond(tranDuration) || toMillisecond(animDuration);
5359    }
5360    /**
5361     * 
5362    <!DOCTYPE html>
5363    <html>
5364        <head>
5365            <meta charset="UTF-8">
5366            <meta name="viewport" content="width=device-width, initial-scale=1.0">
5367            <script src="dist/avalon.js"></script>
5368            <script>
5369                avalon.effect('animate')
5370                var vm = avalon.define({
5371                    $id: 'ani',
5372                    a: true
5373                })
5374            </script>
5375            <style>
5376                .animate-enter, .animate-leave{
5377                    width:100px;
5378                    height:100px;
5379                    background: #29b6f6;
5380                    transition:all 2s;
5381                    -moz-transition: all 2s; 
5382                    -webkit-transition: all 2s;
5383                    -o-transition:all 2s;
5384                }  
5385                .animate-enter-active, .animate-leave{
5386                    width:300px;
5387                    height:300px;
5388                }
5389                .animate-leave-active{
5390                    width:100px;
5391                    height:100px;
5392                }
5393            </style>
5394        </head>
5395        <body>
5396            <div :controller='ani' >
5397                <p><input type='button' value='click' :click='@a =!@a'></p>
5398                <div :effect="{is:'animate',action:@a}"></div>
5399            </div>
5400    </body>
5401    </html>
5402     * 
5403     */
5404
5405    var none = 'none';
5406    function parseDisplay(elem, val) {
5407        //用于取得此类标签的默认display值
5408        var doc = elem.ownerDocument;
5409        var nodeName = elem.nodeName;
5410        var key = '_' + nodeName;
5411        if (!parseDisplay[key]) {
5412            var temp = doc.body.appendChild(doc.createElement(nodeName));
5413            val = avalon.css(temp, 'display');
5414            doc.body.removeChild(temp);
5415            if (val === none) {
5416                val = 'block';
5417            }
5418            parseDisplay[key] = val;
5419        }
5420        return parseDisplay[key];
5421    }
5422
5423    avalon.parseDisplay = parseDisplay;
5424    avalon.directive('visible', {
5425        diff: function diff(newVal, oldVal) {
5426            var n = !!newVal;
5427            if (oldVal === void 0 || n !== oldVal) {
5428                this.value = n;
5429                return true;
5430            }
5431        },
5432        ready: true,
5433        update: function update(vdom, show) {
5434            var dom = vdom.dom;
5435            if (dom && dom.nodeType === 1) {
5436                var display = dom.style.display;
5437                var value;
5438                if (show) {
5439                    if (display === none) {
5440                        value = vdom.displayValue;
5441                        if (!value) {
5442                            dom.style.display = '';
5443                            if (dom.style.cssText === '') {
5444                                dom.removeAttribute('style');
5445                            }
5446                        }
5447                    }
5448                    if (dom.style.display === '' && avalon(dom).css('display') === none &&
5449                    // fix firefox BUG,必须挂到页面上
5450                    avalon.contains(dom.ownerDocument, dom)) {
5451                        value = parseDisplay(dom);
5452                    }
5453                } else {
5454
5455                    if (display !== none) {
5456                        value = none;
5457                        vdom.displayValue = display;
5458                    }
5459                }
5460                var cb = function cb() {
5461                    if (value !== void 0) {
5462                        dom.style.display = value;
5463                    }
5464                };
5465
5466                avalon.applyEffect(dom, vdom, {
5467                    hook: show ? 'onEnterDone' : 'onLeaveDone',
5468                    cb: cb
5469                });
5470            }
5471        }
5472    });
5473
5474    avalon.directive('text', {
5475        delay: true,
5476        init: function init() {
5477
5478            var node = this.node;
5479            if (node.isVoidTag) {
5480                avalon.error('自闭合元素不能使用ms-text');
5481            }
5482            var child = { nodeName: '#text', nodeValue: this.getValue() };
5483            node.children.splice(0, node.children.length, child);
5484            if (inBrowser) {
5485                avalon.clearHTML(node.dom);
5486                node.dom.appendChild(avalon.vdom(child, 'toDOM'));
5487            }
5488            this.node = child;
5489            var type = 'expr';
5490            this.type = this.name = type;
5491            var directive$$1 = avalon.directives[type];
5492            var me = this;
5493            this.callback = function (value) {
5494                directive$$1.update.call(me, me.node, value);
5495            };
5496        }
5497    });
5498
5499    avalon.directive('expr', {
5500        update: function update(vdom, value) {
5501            value = value == null || value === '' ? '\u200B' : value;
5502            vdom.nodeValue = value;
5503            //https://github.com/RubyLouvre/avalon/issues/1834
5504            if (vdom.dom) vdom.dom.data = value;
5505        }
5506    });
5507
5508    avalon.directive('attr', {
5509        diff: cssDiff,
5510        update: function update(vdom, value) {
5511            var props = vdom.props;
5512            for (var i in value) {
5513                if (!!value[i] === false) {
5514                    delete props[i];
5515                } else {
5516                    props[i] = value[i];
5517                }
5518            }
5519            var dom = vdom.dom;
5520            if (dom && dom.nodeType === 1) {
5521                updateAttrs(dom, value);
5522            }
5523        }
5524    });
5525
5526    avalon.directive('html', {
5527
5528        update: function update(vdom, value) {
5529            this.beforeDispose();
5530
5531            this.innerRender = avalon.scan('<div class="ms-html-container">' + value + '</div>', this.vm, function () {
5532                var oldRoot = this.root;
5533                if (vdom.children) vdom.children.length = 0;
5534                vdom.children = oldRoot.children;
5535                this.root = vdom;
5536                if (vdom.dom) avalon.clearHTML(vdom.dom);
5537            });
5538        },
5539        beforeDispose: function beforeDispose() {
5540            if (this.innerRender) {
5541                this.innerRender.dispose();
5542            }
5543        },
5544        delay: true
5545    });
5546
5547    avalon.directive('if', {
5548        delay: true,
5549        priority: 5,
5550        init: function init() {
5551            this.placeholder = createAnchor('if');
5552            var props = this.node.props;
5553            delete props['ms-if'];
5554            delete props[':if'];
5555            this.fragment = avalon.vdom(this.node, 'toHTML');
5556        },
5557        diff: function diff(newVal, oldVal) {
5558            var n = !!newVal;
5559            if (oldVal === void 0 || n !== oldVal) {
5560                this.value = n;
5561                return true;
5562            }
5563        },
5564        update: function update(vdom, value) {
5565            if (this.isShow === void 0 && value) {
5566                continueScan(this, vdom);
5567                return;
5568            }
5569            this.isShow = value;
5570            var placeholder = this.placeholder;
5571
5572            if (value) {
5573                var p = placeholder.parentNode;
5574                continueScan(this, vdom);
5575                p && p.replaceChild(vdom.dom, placeholder);
5576            } else {
5577                //移除DOM
5578                this.beforeDispose();
5579                vdom.nodeValue = 'if';
5580                vdom.nodeName = '#comment';
5581                delete vdom.children;
5582                var dom = vdom.dom;
5583                var p = dom && dom.parentNode;
5584                vdom.dom = placeholder;
5585                if (p) {
5586                    p.replaceChild(placeholder, dom);
5587                }
5588            }
5589        },
5590        beforeDispose: function beforeDispose() {
5591            if (this.innerRender) {
5592                this.innerRender.dispose();
5593            }
5594        }
5595    });
5596
5597    function continueScan(instance, vdom) {
5598        var innerRender = instance.innerRender = avalon.scan(instance.fragment, instance.vm);
5599        avalon.shadowCopy(vdom, innerRender.root);
5600        delete vdom.nodeValue;
5601    }
5602
5603    avalon.directive('on', {
5604        beforeInit: function beforeInit() {
5605            this.getter = avalon.noop;
5606        },
5607        init: function init() {
5608            var vdom = this.node;
5609            var underline = this.name.replace('ms-on-', 'e').replace('-', '_');
5610            var uuid = underline + '_' + this.expr.replace(/\s/g, '').replace(/[^$a-z]/ig, function (e) {
5611                return e.charCodeAt(0);
5612            });
5613            var fn = avalon.eventListeners[uuid];
5614            if (!fn) {
5615                var arr = addScope(this.expr);
5616                var body = arr[0],
5617                    filters = arr[1];
5618                body = makeHandle(body);
5619
5620                if (filters) {
5621                    filters = filters.replace(/__value__/g, '$event');
5622                    filters += '\nif($event.$return){\n\treturn;\n}';
5623                }
5624                var ret = ['try{', '\tvar __vmodel__ = this;', '\t' + filters, '\treturn ' + body, '}catch(e){avalon.log(e, "in on dir")}'].filter(function (el) {
5625                    return (/\S/.test(el)
5626                    );
5627                });
5628                fn = new Function('$event', ret.join('\n'));
5629                fn.uuid = uuid;
5630                avalon.eventListeners[uuid] = fn;
5631            }
5632
5633            var dom = avalon.vdom(vdom, 'toDOM');
5634            dom._ms_context_ = this.vm;
5635
5636            this.eventType = this.param.replace(/\-(\d)$/, '');
5637            delete this.param;
5638            avalon(dom).bind(this.eventType, fn);
5639        },
5640
5641        beforeDispose: function beforeDispose() {
5642            avalon(this.node.dom).unbind(this.eventType);
5643        }
5644    });
5645
5646    var rforAs = /\s+as\s+([$\w]+)/;
5647    var rident = /^[$a-zA-Z_][$a-zA-Z0-9_]*$/;
5648    var rinvalid = /^(null|undefined|NaN|window|this|\$index|\$id)$/;
5649    var rargs = /[$\w_]+/g;
5650    avalon.directive('for', {
5651        delay: true,
5652        priority: 3,
5653        beforeInit: function beforeInit() {
5654            var str = this.expr,
5655                asName;
5656            str = str.replace(rforAs, function (a, b) {
5657                /* istanbul ignore if */
5658                if (!rident.test(b) || rinvalid.test(b)) {
5659                    avalon.error('alias ' + b + ' is invalid --- must be a valid JS identifier which is not a reserved name.');
5660                } else {
5661                    asName = b;
5662                }
5663                return '';
5664            });
5665
5666            var arr = str.split(' in ');
5667            var kv = arr[0].match(rargs);
5668            if (kv.length === 1) {
5669                //确保avalon._each的回调有三个参数
5670                kv.unshift('$key');
5671            }
5672            this.expr = arr[1];
5673            this.keyName = kv[0];
5674            this.valName = kv[1];
5675            this.signature = avalon.makeHashCode('for');
5676            if (asName) {
5677                this.asName = asName;
5678            }
5679
5680            delete this.param;
5681        },
5682        init: function init() {
5683            var cb = this.userCb;
5684            if (typeof cb === 'string' && cb) {
5685                var arr = addScope(cb, 'for');
5686                var body = makeHandle(arr[0]);
5687                this.userCb = new Function('$event', 'var __vmodel__ = this\nreturn ' + body);
5688            }
5689            this.node.forDir = this; //暴露给component/index.js中的resetParentChildren方法使用
5690            this.fragment = ['<div>', this.fragment, '<!--', this.signature, '--></div>'].join('');
5691            this.cache = {};
5692        },
5693        diff: function diff(newVal, oldVal) {
5694            /* istanbul ignore if */
5695            if (this.updating) {
5696                return;
5697            }
5698            this.updating = true;
5699            var traceIds = createFragments(this, newVal);
5700
5701            if (this.oldTrackIds === void 0) return true;
5702
5703            if (this.oldTrackIds !== traceIds) {
5704                this.oldTrackIds = traceIds;
5705                return true;
5706            }
5707        },
5708        update: function update() {
5709
5710            if (!this.preFragments) {
5711                this.fragments = this.fragments || [];
5712                mountList(this);
5713            } else {
5714                diffList(this);
5715                updateList(this);
5716            }
5717
5718            if (this.userCb) {
5719                var me = this;
5720                setTimeout(function () {
5721                    me.userCb.call(me.vm, {
5722                        type: 'rendered',
5723                        target: me.begin.dom,
5724                        signature: me.signature
5725                    });
5726                }, 0);
5727            }
5728            delete this.updating;
5729        },
5730        beforeDispose: function beforeDispose() {
5731            this.fragments.forEach(function (el) {
5732                el.dispose();
5733            });
5734        }
5735    });
5736
5737    function getTraceKey(item) {
5738        var type = typeof item;
5739        return item && type === 'object' ? item.$hashcode : type + ':' + item;
5740    }
5741
5742    //创建一组fragment的虚拟DOM
5743    function createFragments(instance, obj) {
5744        if (isObject(obj)) {
5745            var array = Array.isArray(obj);
5746            var ids = [];
5747            var fragments = [],
5748                i = 0;
5749
5750            instance.isArray = array;
5751            if (instance.fragments) {
5752                instance.preFragments = instance.fragments;
5753                avalon.each(obj, function (key, value) {
5754                    var k = array ? getTraceKey(value) : key;
5755
5756                    fragments.push({
5757                        key: k,
5758                        val: value,
5759                        index: i++
5760                    });
5761                    ids.push(k);
5762                });
5763                instance.fragments = fragments;
5764            } else {
5765                avalon.each(obj, function (key, value) {
5766                    if (!(key in $$skipArray)) {
5767                        var k = array ? getTraceKey(value) : key;
5768                        fragments.push(new VFragment([], k, value, i++));
5769                        ids.push(k);
5770                    }
5771                });
5772                instance.fragments = fragments;
5773            }
5774            return ids.join(';;');
5775        } else {
5776            return NaN;
5777        }
5778    }
5779
5780    function mountList(instance) {
5781        var args = instance.fragments.map(function (fragment, index) {
5782            FragmentDecorator(fragment, instance, index);
5783            saveInCache(instance.cache, fragment);
5784            return fragment;
5785        });
5786        var list = instance.parentChildren;
5787        var i = list.indexOf(instance.begin);
5788        list.splice.apply(list, [i + 1, 0].concat(args));
5789    }
5790
5791    function diffList(instance) {
5792        var cache = instance.cache;
5793        var newCache = {};
5794        var fuzzy = [];
5795        var list = instance.preFragments;
5796
5797        list.forEach(function (el) {
5798            el._dispose = true;
5799        });
5800
5801        instance.fragments.forEach(function (c, index) {
5802            var fragment = isInCache(cache, c.key);
5803            //取出之前的文档碎片
5804            if (fragment) {
5805                delete fragment._dispose;
5806                fragment.oldIndex = fragment.index;
5807                fragment.index = index; // 相当于 c.index
5808
5809                resetVM(fragment.vm, instance.keyName);
5810                fragment.vm[instance.valName] = c.val;
5811                fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key;
5812                saveInCache(newCache, fragment);
5813            } else {
5814                //如果找不到就进行模糊搜索
5815                fuzzy.push(c);
5816            }
5817        });
5818        fuzzy.forEach(function (c) {
5819            var fragment = fuzzyMatchCache(cache, c.key);
5820            if (fragment) {
5821                //重复利用
5822                fragment.oldIndex = fragment.index;
5823                fragment.key = c.key;
5824                var val = fragment.val = c.val;
5825                var index = fragment.index = c.index;
5826
5827                fragment.vm[instance.valName] = val;
5828                fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key;
5829                delete fragment._dispose;
5830            } else {
5831
5832                c = new VFragment([], c.key, c.val, c.index);
5833                fragment = FragmentDecorator(c, instance, c.index);
5834                list.push(fragment);
5835            }
5836            saveInCache(newCache, fragment);
5837        });
5838
5839        instance.fragments = list;
5840        list.sort(function (a, b) {
5841            return a.index - b.index;
5842        });
5843        instance.cache = newCache;
5844    }
5845
5846    function resetVM(vm, a, b) {
5847        if (avalon.config.inProxyMode) {
5848            vm.$accessors[a].value = NaN;
5849        } else {
5850            vm.$accessors[a].set(NaN);
5851        }
5852    }
5853
5854    function updateList(instance) {
5855        var before = instance.begin.dom;
5856        var parent = before.parentNode;
5857        var list = instance.fragments;
5858        var end = instance.end.dom;
5859        for (var i = 0, item; item = list[i]; i++) {
5860            if (item._dispose) {
5861                list.splice(i, 1);
5862                i--;
5863                item.dispose();
5864                continue;
5865            }
5866            if (item.oldIndex !== item.index) {
5867                var f = item.toFragment();
5868                var isEnd = before.nextSibling === null;
5869                parent.insertBefore(f, before.nextSibling);
5870                if (isEnd && !parent.contains(end)) {
5871                    parent.insertBefore(end, before.nextSibling);
5872                }
5873            }
5874            before = item.split;
5875        }
5876        var ch = instance.parentChildren;
5877        var startIndex = ch.indexOf(instance.begin);
5878        var endIndex = ch.indexOf(instance.end);
5879
5880        list.splice.apply(ch, [startIndex + 1, endIndex - startIndex].concat(list));
5881    }
5882
5883    /**
5884     * 
5885     * @param {type} fragment
5886     * @param {type} this
5887     * @param {type} index
5888     * @returns { key, val, index, oldIndex, this, dom, split, vm}
5889     */
5890    function FragmentDecorator(fragment, instance, index) {
5891        var data = {};
5892        data[instance.keyName] = instance.isArray ? index : fragment.key;
5893        data[instance.valName] = fragment.val;
5894        if (instance.asName) {
5895            data[instance.asName] = instance.value;
5896        }
5897        var vm = fragment.vm = platform.itemFactory(instance.vm, {
5898            data: data
5899        });
5900        if (instance.isArray) {
5901            vm.$watch(instance.valName, function (a) {
5902                if (instance.value && instance.value.set) {
5903                    instance.value.set(vm[instance.keyName], a);
5904                }
5905            });
5906        } else {
5907            vm.$watch(instance.valName, function (a) {
5908                instance.value[fragment.key] = a;
5909            });
5910        }
5911
5912        fragment.index = index;
5913        fragment.innerRender = avalon.scan(instance.fragment, vm, function () {
5914            var oldRoot = this.root;
5915            ap.push.apply(fragment.children, oldRoot.children);
5916            this.root = fragment;
5917        });
5918        return fragment;
5919    }
5920    // 新位置: 旧位置
5921    function isInCache(cache, id) {
5922        var c = cache[id];
5923        if (c) {
5924            var arr = c.arr;
5925            /* istanbul ignore if*/
5926            if (arr) {
5927                var r = arr.pop();
5928                if (!arr.length) {
5929                    c.arr = 0;
5930                }
5931                return r;
5932            }
5933            delete cache[id];
5934            return c;
5935        }
5936    }
5937    //[1,1,1] number1 number1_ number1__
5938    function saveInCache(cache, component) {
5939        var trackId = component.key;
5940        if (!cache[trackId]) {
5941            cache[trackId] = component;
5942        } else {
5943            var c = cache[trackId];
5944            var arr = c.arr || (c.arr = []);
5945            arr.push(component);
5946        }
5947    }
5948
5949    function fuzzyMatchCache(cache) {
5950        var key;
5951        for (var id in cache) {
5952            var key = id;
5953            break;
5954        }
5955        if (key) {
5956            return isInCache(cache, key);
5957        }
5958    }
5959
5960    //根据VM的属性值或表达式的值切换类名,ms-class='xxx yyy zzz:flag'
5961    //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
5962    function classNames() {
5963        var classes = [];
5964        for (var i = 0; i < arguments.length; i++) {
5965            var arg = arguments[i];
5966            var argType = typeof arg;
5967            if (argType === 'string' || argType === 'number' || arg === true) {
5968                classes.push(arg);
5969            } else if (Array.isArray(arg)) {
5970                classes.push(classNames.apply(null, arg));
5971            } else if (argType === 'object') {
5972                for (var key in arg) {
5973                    if (arg.hasOwnProperty(key) && arg[key]) {
5974                        classes.push(key);
5975                    }
5976                }
5977            }
5978        }
5979
5980        return classes.join(' ');
5981    }
5982
5983    avalon.directive('class', {
5984        diff: function diff(newVal, oldVal) {
5985            var type = this.type;
5986            var vdom = this.node;
5987            var classEvent = vdom.classEvent || {};
5988            if (type === 'hover') {
5989                //在移出移入时切换类名
5990                classEvent.mouseenter = activateClass;
5991                classEvent.mouseleave = abandonClass;
5992            } else if (type === 'active') {
5993                //在获得焦点时切换类名
5994                classEvent.tabIndex = vdom.props.tabindex || -1;
5995                classEvent.mousedown = activateClass;
5996                classEvent.mouseup = abandonClass;
5997                classEvent.mouseleave = abandonClass;
5998            }
5999            vdom.classEvent = classEvent;
6000
6001            var className = classNames(newVal);
6002
6003            if (typeof oldVal === void 0 || oldVal !== className) {
6004                this.value = className;
6005
6006                vdom['change-' + type] = className;
6007                return true;
6008            }
6009        },
6010        update: function update(vdom, value) {
6011            var dom = vdom.dom;
6012            if (dom && dom.nodeType == 1) {
6013
6014                var dirType = this.type;
6015                var change = 'change-' + dirType;
6016                var classEvent = vdom.classEvent;
6017                if (classEvent) {
6018                    for (var i in classEvent) {
6019                        if (i === 'tabIndex') {
6020                            dom[i] = classEvent[i];
6021                        } else {
6022                            avalon.bind(dom, i, classEvent[i]);
6023                        }
6024                    }
6025                    vdom.classEvent = {};
6026                }
6027                var names = ['class', 'hover', 'active'];
6028                names.forEach(function (type) {
6029                    if (dirType !== type) return;
6030                    if (type === 'class') {
6031                        dom && setClass(dom, value);
6032                    } else {
6033                        var oldClass = dom.getAttribute(change);
6034                        if (oldClass) {
6035                            avalon(dom).removeClass(oldClass);
6036                        }
6037                        var name = 'change-' + type;
6038                        dom.setAttribute(name, value);
6039                    }
6040                });
6041            }
6042        }
6043    });
6044
6045    directives.active = directives.hover = directives['class'];
6046
6047    var classMap = {
6048        mouseenter: 'change-hover',
6049        mouseleave: 'change-hover',
6050        mousedown: 'change-active',
6051        mouseup: 'change-active'
6052    };
6053
6054    function activateClass(e) {
6055        var elem = e.target;
6056        avalon(elem).addClass(elem.getAttribute(classMap[e.type]) || '');
6057    }
6058
6059    function abandonClass(e) {
6060        var elem = e.target;
6061        var name = classMap[e.type];
6062        avalon(elem).removeClass(elem.getAttribute(name) || '');
6063        if (name !== 'change-active') {
6064            avalon(elem).removeClass(elem.getAttribute('change-active') || '');
6065        }
6066    }
6067
6068    function setClass(dom, neo) {
6069        var old = dom.getAttribute('change-class');
6070        if (old !== neo) {
6071            avalon(dom).removeClass(old).addClass(neo);
6072            dom.setAttribute('change-class', neo);
6073        }
6074    }
6075
6076    getLongID(activateClass);
6077    getLongID(abandonClass);
6078
6079    function lookupOption(vdom, values) {
6080        vdom.children && vdom.children.forEach(function (el) {
6081            if (el.nodeName === 'option') {
6082                setOption(el, values);
6083            } else {
6084                lookupOption(el, values);
6085            }
6086        });
6087    }
6088
6089    function setOption(vdom, values) {
6090        var props = vdom.props;
6091        if (!('disabled' in props)) {
6092            var value = getOptionValue(vdom, props);
6093            value = String(value || '').trim();
6094            props.selected = values.indexOf(value) !== -1;
6095
6096            if (vdom.dom) {
6097                vdom.dom.selected = props.selected;
6098                var v = vdom.dom.selected; //必须加上这个,防止移出节点selected失效
6099            }
6100        }
6101    }
6102
6103    function getOptionValue(vdom, props) {
6104        if (props && 'value' in props) {
6105            return props.value + '';
6106        }
6107        var arr = [];
6108        vdom.children.forEach(function (el) {
6109            if (el.nodeName === '#text') {
6110                arr.push(el.nodeValue);
6111            } else if (el.nodeName === '#document-fragment') {
6112                arr.push(getOptionValue(el));
6113            }
6114        });
6115        return arr.join('');
6116    }
6117
6118    function getSelectedValue(vdom, arr) {
6119        vdom.children.forEach(function (el) {
6120            if (el.nodeName === 'option') {
6121                if (el.props.selected === true) arr.push(getOptionValue(el, el.props));
6122            } else if (el.children) {
6123                getSelectedValue(el, arr);
6124            }
6125        });
6126        return arr;
6127    }
6128
6129    var updateDataActions = {
6130        input: function input(prop) {
6131            //处理单个value值处理
6132            var field = this;
6133            prop = prop || 'value';
6134            var dom = field.dom;
6135            var rawValue = dom[prop];
6136            var parsedValue = field.parseValue(rawValue);
6137
6138            //有时候parse后一致,vm不会改变,但input里面的值
6139            field.value = rawValue;
6140            field.setValue(parsedValue);
6141            duplexCb(field);
6142            var pos = field.pos;
6143            /* istanbul ignore if */
6144            if (dom.caret) {
6145                field.setCaret(dom, pos);
6146            }
6147            //vm.aaa = '1234567890'
6148            //处理 <input ms-duplex='@aaa|limitBy(8)'/>{{@aaa}} 这种格式化同步不一致的情况 
6149        },
6150        radio: function radio() {
6151            var field = this;
6152            if (field.isChecked) {
6153                var val = !field.value;
6154                field.setValue(val);
6155                duplexCb(field);
6156            } else {
6157                updateDataActions.input.call(field);
6158                field.value = NaN;
6159            }
6160        },
6161        checkbox: function checkbox() {
6162            var field = this;
6163            var array = field.value;
6164            if (!Array.isArray(array)) {
6165                avalon.warn('ms-duplex应用于checkbox上要对应一个数组');
6166                array = [array];
6167            }
6168            var method = field.dom.checked ? 'ensure' : 'remove';
6169            if (array[method]) {
6170                var val = field.parseValue(field.dom.value);
6171                array[method](val);
6172                duplexCb(field);
6173            }
6174            this.__test__ = array;
6175        },
6176        select: function select() {
6177            var field = this;
6178            var val = avalon(field.dom).val(); //字符串或字符串数组
6179            if (val + '' !== this.value + '') {
6180                if (Array.isArray(val)) {
6181                    //转换布尔数组或其他
6182                    val = val.map(function (v) {
6183                        return field.parseValue(v);
6184                    });
6185                } else {
6186                    val = field.parseValue(val);
6187                }
6188                field.setValue(val);
6189                duplexCb(field);
6190            }
6191        },
6192        contenteditable: function contenteditable() {
6193            updateDataActions.input.call(this, 'innerHTML');
6194        }
6195    };
6196
6197    function duplexCb(field) {
6198        if (field.userCb) {
6199            field.userCb.call(field.vm, {
6200                type: 'changed',
6201                target: field.dom
6202            });
6203        }
6204    }
6205
6206    function updateDataHandle(event) {
6207        var elem = this;
6208        var field = elem._ms_duplex_;
6209        if (elem.composing) {
6210            //防止onpropertychange引发爆栈
6211            return;
6212        }
6213        if (elem.value === field.value) {
6214            return;
6215        }
6216        /* istanbul ignore if*/
6217        if (elem.caret) {
6218            try {
6219                var pos = field.getCaret(elem);
6220                field.pos = pos;
6221            } catch (e) {}
6222        }
6223        /* istanbul ignore if*/
6224        if (field.debounceTime > 4) {
6225            var timestamp = new Date();
6226            var left = timestamp - field.time || 0;
6227            field.time = timestamp;
6228            /* istanbul ignore if*/
6229            if (left >= field.debounceTime) {
6230                updateDataActions[field.dtype].call(field);
6231                /* istanbul ignore else*/
6232            } else {
6233                clearTimeout(field.debounceID);
6234                field.debounceID = setTimeout(function () {
6235                    updateDataActions[field.dtype].call(field);
6236                }, left);
6237            }
6238        } else if (field.isChanged) {
6239            setTimeout(function () {
6240                //https://github.com/RubyLouvre/avalon/issues/1908
6241                updateDataActions[field.dtype].call(field);
6242            }, 4);
6243        } else {
6244            updateDataActions[field.dtype].call(field);
6245        }
6246    }
6247
6248    var rchangeFilter = /\|\s*change\b/;
6249    var rdebounceFilter = /\|\s*debounce(?:\(([^)]+)\))?/;
6250    function duplexBeforeInit() {
6251        var expr = this.expr;
6252        if (rchangeFilter.test(expr)) {
6253            this.isChanged = true;
6254            expr = expr.replace(rchangeFilter, '');
6255        }
6256        var match = expr.match(rdebounceFilter);
6257        if (match) {
6258            expr = expr.replace(rdebounceFilter, '');
6259            if (!this.isChanged) {
6260                this.debounceTime = parseInt(match[1], 10) || 300;
6261            }
6262        }
6263        this.expr = expr;
6264    }
6265    function duplexInit() {
6266        var expr = this.expr;
6267        var node = this.node;
6268        var etype = node.props.type;
6269        this.parseValue = parseValue;
6270        //处理数据转换器
6271        var parsers = this.param,
6272            dtype;
6273        var isChecked = false;
6274        parsers = parsers ? parsers.split('-').map(function (a) {
6275            if (a === 'checked') {
6276                isChecked = true;
6277            }
6278            return a;
6279        }) : [];
6280        node.duplex = this;
6281        if (rcheckedType.test(etype) && isChecked) {
6282            //如果是radio, checkbox,判定用户使用了checked格式函数没有
6283            parsers = [];
6284            dtype = 'radio';
6285            this.isChecked = isChecked;
6286        }
6287        this.parsers = parsers;
6288        if (!/input|textarea|select/.test(node.nodeName)) {
6289            if ('contenteditable' in node.props) {
6290                dtype = 'contenteditable';
6291            }
6292        } else if (!dtype) {
6293            dtype = node.nodeName === 'select' ? 'select' : etype === 'checkbox' ? 'checkbox' : etype === 'radio' ? 'radio' : 'input';
6294        }
6295        this.dtype = dtype;
6296
6297        //判定是否使用了 change debounce 过滤器
6298        // this.isChecked = /boolean/.test(parsers)
6299        if (dtype !== 'input' && dtype !== 'contenteditable') {
6300            delete this.isChanged;
6301            delete this.debounceTime;
6302        } else if (!this.isChecked) {
6303            this.isString = true;
6304        }
6305
6306        var cb = node.props['data-duplex-changed'];
6307        if (cb) {
6308            var arr = addScope(cb, 'xx');
6309            var body = makeHandle(arr[0]);
6310            this.userCb = new Function('$event', 'var __vmodel__ = this\nreturn ' + body);
6311        }
6312    }
6313    function duplexDiff(newVal, oldVal) {
6314        if (Array.isArray(newVal)) {
6315            if (newVal + '' !== this.compareVal) {
6316                this.compareVal = newVal + '';
6317                return true;
6318            }
6319        } else {
6320            newVal = this.parseValue(newVal);
6321            if (!this.isChecked) {
6322                this.value = newVal += '';
6323            }
6324            if (newVal !== this.compareVal) {
6325                this.compareVal = newVal;
6326                return true;
6327            }
6328        }
6329    }
6330
6331    function duplexBind(vdom, addEvent) {
6332        var dom = vdom.dom;
6333        this.dom = dom;
6334        this.vdom = vdom;
6335        this.duplexCb = updateDataHandle;
6336        dom._ms_duplex_ = this;
6337        //绑定事件
6338        addEvent(dom, this);
6339    }
6340
6341    var valueHijack = true;
6342    try {
6343        //#272 IE9-IE11, firefox
6344        var setters = {};
6345        var aproto = HTMLInputElement.prototype;
6346        var bproto = HTMLTextAreaElement.prototype;
6347        var newSetter = function newSetter(value) {
6348            // jshint ignore:line
6349            setters[this.tagName].call(this, value);
6350            var data = this._ms_duplex_;
6351            if (!this.caret && data && data.isString) {
6352                data.duplexCb.call(this, { type: 'setter' });
6353            }
6354        };
6355        var inputProto = HTMLInputElement.prototype;
6356        Object.getOwnPropertyNames(inputProto); //故意引发IE6-8等浏览器报错
6357        setters['INPUT'] = Object.getOwnPropertyDescriptor(aproto, 'value').set;
6358
6359        Object.defineProperty(aproto, 'value', {
6360            set: newSetter
6361        });
6362        setters['TEXTAREA'] = Object.getOwnPropertyDescriptor(bproto, 'value').set;
6363        Object.defineProperty(bproto, 'value', {
6364            set: newSetter
6365        });
6366        valueHijack = false;
6367    } catch (e) {
6368        //在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了
6369        // http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype
6370        // https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1
6371    }
6372
6373    function parseValue(val) {
6374        for (var i = 0, k; k = this.parsers[i++];) {
6375            var fn = avalon.parsers[k];
6376            if (fn) {
6377                val = fn.call(this, val);
6378            }
6379        }
6380        return val;
6381    }
6382
6383    var updateView = {
6384        input: function input() {
6385            //处理单个value值处理
6386            var vdom = this.node;
6387            var value = this.value + '';
6388            vdom.dom.value = vdom.props.value = value;
6389        },
6390        updateChecked: function updateChecked(vdom, checked) {
6391            if (vdom.dom) {
6392                vdom.dom.defaultChecked = vdom.dom.checked = checked;
6393            }
6394        },
6395        radio: function radio() {
6396            //处理单个checked属性
6397            var node = this.node;
6398            var nodeValue = node.props.value;
6399            var checked;
6400            if (this.isChecked) {
6401                checked = !!this.value;
6402            } else {
6403                checked = this.value + '' === nodeValue;
6404            }
6405            node.props.checked = checked;
6406            updateView.updateChecked(node, checked);
6407        },
6408        checkbox: function checkbox() {
6409            //处理多个checked属性
6410            var node = this.node;
6411            var props = node.props;
6412            var value = props.value + '';
6413            var values = [].concat(this.value);
6414            var checked = values.some(function (el) {
6415                return el + '' === value;
6416            });
6417
6418            props.defaultChecked = props.checked = checked;
6419            updateView.updateChecked(node, checked);
6420        },
6421        select: function select() {
6422            //处理子级的selected属性
6423            var a = Array.isArray(this.value) ? this.value.map(String) : this.value + '';
6424            lookupOption(this.node, a);
6425        },
6426        contenteditable: function contenteditable() {
6427            //处理单个innerHTML 
6428
6429            var vnodes = fromString(this.value);
6430            var fragment = createFragment();
6431            for (var i = 0, el; el = vnodes[i++];) {
6432                var child = avalon.vdom(el, 'toDOM');
6433                fragment.appendChild(child);
6434            }
6435            avalon.clearHTML(this.dom).appendChild(fragment);
6436            var list = this.node.children;
6437            list.length = 0;
6438            Array.prototype.push.apply(list, vnodes);
6439
6440            this.duplexCb.call(this.dom);
6441        }
6442    };
6443
6444    /* 
6445     * 通过绑定事件同步vmodel
6446     * 总共有三种方式同步视图
6447     * 1. 各种事件 input, change, click, propertychange, keydown...
6448     * 2. value属性重写
6449     * 3. 定时器轮询
6450     */
6451
6452    function updateDataEvents(dom, data) {
6453        var events = {};
6454        //添加需要监听的事件
6455        switch (data.dtype) {
6456            case 'radio':
6457            case 'checkbox':
6458                events.click = updateDataHandle;
6459                break;
6460            case 'select':
6461                events.change = updateDataHandle;
6462                break;
6463            case 'contenteditable':
6464                /* istanbul ignore if */
6465                if (data.isChanged) {
6466                    events.blur = updateDataHandle;
6467                    /* istanbul ignore else */
6468                } else {
6469                    /* istanbul ignore if*/
6470
6471                    if (avalon.modern) {
6472                        if (window$1.webkitURL) {
6473                            // http://code.metager.de/source/xref/WebKit/LayoutTests/fast/events/
6474                            // https://bugs.webkit.org/show_bug.cgi?id=110742
6475                            events.webkitEditableContentChanged = updateDataHandle;
6476                        } else if (window$1.MutationEvent) {
6477                            events.DOMCharacterDataModified = updateDataHandle;
6478                        }
6479                        events.input = updateDataHandle;
6480                        /* istanbul ignore else */
6481                    } else {
6482                        events.keydown = updateModelKeyDown;
6483                        events.paste = updateModelDelay;
6484                        events.cut = updateModelDelay;
6485                        events.focus = closeComposition;
6486                        events.blur = openComposition;
6487                    }
6488                }
6489                break;
6490            case 'input':
6491                /* istanbul ignore if */
6492                if (data.isChanged) {
6493                    events.change = updateDataHandle;
6494                    /* istanbul ignore else */
6495                } else {
6496                    //http://www.cnblogs.com/rubylouvre/archive/2013/02/17/2914604.html
6497                    //http://www.matts411.com/post/internet-explorer-9-oninput/
6498                    if (msie < 10) {
6499                        //IE6-8的propertychange有问题,第一次用JS修改值时不会触发,而且你是全部清空value也不会触发
6500                        //IE9的propertychange不支持自动完成,退格,删除,复制,贴粘,剪切或点击右边的小X的清空操作
6501                        events.propertychange = updateModelHack;
6502                        events.paste = updateModelDelay;
6503                        events.cut = updateModelDelay;
6504                        //IE9在第一次删除字符时不会触发oninput
6505                        events.keyup = updateModelKeyDown;
6506                    } else {
6507                        events.input = updateDataHandle;
6508                        events.compositionstart = openComposition;
6509                        //微软拼音输入法的问题需要在compositionend事件中处理
6510                        events.compositionend = closeComposition;
6511                        //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
6512                        //处理低版本的标准浏览器,通过Int8Array进行区分
6513                        if (!/\[native code\]/.test(window$1.Int8Array)) {
6514                            events.keydown = updateModelKeyDown; //safari < 5 opera < 11
6515                            events.paste = updateModelDelay; //safari < 5
6516                            events.cut = updateModelDelay; //safari < 5 
6517                            if (window$1.netscape) {
6518                                // Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
6519                                events.DOMAutoComplete = updateDataHandle;
6520                            }
6521                        }
6522                    }
6523                }
6524                break;
6525        }
6526
6527        if (/password|text/.test(dom.type)) {
6528            events.focus = openCaret; //判定是否使用光标修正功能 
6529            events.blur = closeCaret;
6530            data.getCaret = getCaret;
6531            data.setCaret = setCaret;
6532        }
6533
6534        for (var name in events) {
6535            avalon.bind(dom, name, events[name]);
6536        }
6537    }
6538
6539    function updateModelHack(e) {
6540        if (e.propertyName === 'value') {
6541            updateDataHandle.call(this, e);
6542        }
6543    }
6544
6545    function updateModelDelay(e) {
6546        var elem = this;
6547        setTimeout(function () {
6548            updateDataHandle.call(elem, e);
6549        }, 0);
6550    }
6551
6552    function openCaret() {
6553        this.caret = true;
6554    }
6555    /* istanbul ignore next */
6556    function closeCaret() {
6557        this.caret = false;
6558    }
6559    /* istanbul ignore next */
6560    function openComposition() {
6561        this.composing = true;
6562    }
6563    /* istanbul ignore next */
6564    function closeComposition(e) {
6565        this.composing = false;
6566        updateModelDelay.call(this, e);
6567    }
6568    /* istanbul ignore next */
6569    function updateModelKeyDown(e) {
6570        var key = e.keyCode;
6571        // ignore
6572        //    command            modifiers                   arrows
6573        if (key === 91 || 15 < key && key < 19 || 37 <= key && key <= 40) return;
6574        updateDataHandle.call(this, e);
6575    }
6576
6577    getShortID(openCaret);
6578    getShortID(closeCaret);
6579    getShortID(openComposition);
6580    getShortID(closeComposition);
6581    getShortID(updateDataHandle);
6582    getShortID(updateModelHack);
6583    getShortID(updateModelDelay);
6584    getShortID(updateModelKeyDown);
6585
6586    //IE6-8要处理光标时需要异步
6587    var mayBeAsync = function mayBeAsync(fn) {
6588        setTimeout(fn, 0);
6589    };
6590    /* istanbul ignore next */
6591    function setCaret(target, cursorPosition) {
6592        var range$$1;
6593        if (target.createTextRange) {
6594            mayBeAsync(function () {
6595                target.focus();
6596                range$$1 = target.createTextRange();
6597                range$$1.collapse(true);
6598                range$$1.moveEnd('character', cursorPosition);
6599                range$$1.moveStart('character', cursorPosition);
6600                range$$1.select();
6601            });
6602        } else {
6603            target.focus();
6604            if (target.selectionStart !== undefined) {
6605                target.setSelectionRange(cursorPosition, cursorPosition);
6606            }
6607        }
6608    }
6609    /* istanbul ignore next*/
6610    function getCaret(target) {
6611        var start = 0;
6612        var normalizedValue;
6613        var range$$1;
6614        var textInputRange;
6615        var len;
6616        var endRange;
6617
6618        if (target.selectionStart + target.selectionEnd > -1) {
6619            start = target.selectionStart;
6620        } else {
6621            range$$1 = document$1.selection.createRange();
6622
6623            if (range$$1 && range$$1.parentElement() === target) {
6624                len = target.value.length;
6625                normalizedValue = target.value.replace(/\r\n/g, '\n');
6626
6627                textInputRange = target.createTextRange();
6628                textInputRange.moveToBookmark(range$$1.getBookmark());
6629
6630                endRange = target.createTextRange();
6631                endRange.collapse(false);
6632
6633                if (textInputRange.compareEndPoints('StartToEnd', endRange) > -1) {
6634                    start = len;
6635                } else {
6636                    start = -textInputRange.moveStart('character', -len);
6637                    start += normalizedValue.slice(0, start).split('\n').length - 1;
6638                }
6639            }
6640        }
6641
6642        return start;
6643    }
6644
6645    avalon.directive('duplex', {
6646        priority: 9999999,
6647        beforeInit: duplexBeforeInit,
6648        init: duplexInit,
6649        diff: duplexDiff,
6650        update: function update(vdom, value) {
6651            if (!this.dom) {
6652                duplexBind.call(this, vdom, updateDataEvents);
6653            }
6654            //如果不支持input.value的Object.defineProperty的属性支持,
6655            //需要通过轮询同步, chrome 42及以下版本需要这个hack
6656            pollValue.call(this, avalon.msie, valueHijack);
6657            //更新视图
6658
6659            updateView[this.dtype].call(this);
6660        }
6661    });
6662
6663    function pollValue(isIE, valueHijack$$1) {
6664        var dom = this.dom;
6665        if (this.isString && valueHijack$$1 && !isIE && !dom.valueHijack) {
6666            dom.valueHijack = updateDataHandle;
6667            var intervalID = setInterval(function () {
6668                if (!avalon.contains(avalon.root, dom)) {
6669                    clearInterval(intervalID);
6670                } else {
6671                    dom.valueHijack({ type: 'poll' });
6672                }
6673            }, 30);
6674            return intervalID;
6675        }
6676    }
6677    avalon.__pollValue = pollValue; //export to test
6678    /* istanbul ignore if */
6679    if (avalon.msie < 8) {
6680        var oldUpdate = updateView.updateChecked;
6681        updateView.updateChecked = function (vdom, checked) {
6682            var dom = vdom.dom;
6683            if (dom) {
6684                setTimeout(function () {
6685                    oldUpdate(vdom, checked);
6686                    dom.firstCheckedIt = 1;
6687                }, dom.firstCheckedIt ? 31 : 16);
6688                //IE6,7 checkbox, radio是使用defaultChecked控制选中状态,
6689                //并且要先设置defaultChecked后设置checked
6690                //并且必须设置延迟(因为必须插入DOM树才生效)
6691            }
6692        };
6693    }
6694
6695    avalon.directive('rules', {
6696        diff: function diff(rules) {
6697            if (isObject(rules)) {
6698                var vdom = this.node;
6699                vdom.rules = platform.toJson(rules);
6700                return true;
6701            }
6702        }
6703    });
6704    function isRegExp(value) {
6705        return avalon.type(value) === 'regexp';
6706    }
6707    var rmail = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/i;
6708    var rurl = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
6709    function isCorrectDate(value) {
6710        if (typeof value === "string" && value) {
6711            //是字符串但不能是空字符
6712            var arr = value.split("-"); //可以被-切成3份,并且第1个是4个字符
6713            if (arr.length === 3 && arr[0].length === 4) {
6714                var year = ~~arr[0]; //全部转换为非负整数
6715                var month = ~~arr[1] - 1;
6716                var date = ~~arr[2];
6717                var d = new Date(year, month, date);
6718                return d.getFullYear() === year && d.getMonth() === month && d.getDate() === date;
6719            }
6720        }
6721        return false;
6722    }
6723    //https://github.com/adform/validator.js/blob/master/validator.js
6724    avalon.shadowCopy(avalon.validators, {
6725        pattern: {
6726            message: '必须匹配{{pattern}}这样的格式',
6727            get: function get(value, field, next) {
6728                var elem = field.dom;
6729                var data = field.data;
6730                if (!isRegExp(data.pattern)) {
6731                    var h5pattern = elem.getAttribute("pattern");
6732                    data.pattern = new RegExp('^(?:' + h5pattern + ')$');
6733                }
6734                next(data.pattern.test(value));
6735                return value;
6736            }
6737        },
6738        digits: {
6739            message: '必须整数',
6740            get: function get(value, field, next) {
6741                //整数
6742                next(/^\-?\d+$/.test(value));
6743                return value;
6744            }
6745        },
6746        number: {
6747            message: '必须数字',
6748            get: function get(value, field, next) {
6749                //数值
6750                next(!!value && isFinite(value)); // isFinite('') --> true
6751                return value;
6752            }
6753        },
6754        norequired: {
6755            message: '',
6756            get: function get(value, field, next) {
6757                next(true);
6758                return value;
6759            }
6760        },
6761        required: {
6762            message: '必须填写',
6763            get: function get(value, field, next) {
6764                next(value !== '');
6765                return value;
6766            }
6767        },
6768        equalto: {
6769            message: '密码输入不一致',
6770            get: function get(value, field, next) {
6771                var id = String(field.data.equalto);
6772                var other = avalon(document.getElementById(id)).val() || "";
6773                next(value === other);
6774                return value;
6775            }
6776        },
6777        date: {
6778            message: '日期格式不正确',
6779            get: function get(value, field, next) {
6780                var data = field.data;
6781                if (isRegExp(data.date)) {
6782                    next(data.date.test(value));
6783                } else {
6784                    next(isCorrectDate(value));
6785                }
6786                return value;
6787            }
6788        },
6789        url: {
6790            message: 'URL格式不正确',
6791            get: function get(value, field, next) {
6792                next(rurl.test(value));
6793                return value;
6794            }
6795        },
6796        email: {
6797            message: 'email格式不正确',
6798            get: function get(value, field, next) {
6799                next(rmail.test(value));
6800                return value;
6801            }
6802        },
6803        minlength: {
6804            message: '最少输入{{minlength}}个字',
6805            get: function get(value, field, next) {
6806                var num = parseInt(field.data.minlength, 10);
6807                next(value.length >= num);
6808                return value;
6809            }
6810        },
6811        maxlength: {
6812            message: '最多输入{{maxlength}}个字',
6813            get: function get(value, field, next) {
6814                var num = parseInt(field.data.maxlength, 10);
6815                next(value.length <= num);
6816                return value;
6817            }
6818        },
6819        min: {
6820            message: '输入值不能小于{{min}}',
6821            get: function get(value, field, next) {
6822                var num = parseInt(field.data.min, 10);
6823                next(parseFloat(value) >= num);
6824                return value;
6825            }
6826        },
6827        max: {
6828            message: '输入值不能大于{{max}}',
6829            get: function get(value, field, next) {
6830                var num = parseInt(field.data.max, 10);
6831                next(parseFloat(value) <= num);
6832                return value;
6833            }
6834        },
6835        chs: {
6836            message: '必须是中文字符',
6837            get: function get(value, field, next) {
6838                next(/^[\u4e00-\u9fa5]+$/.test(value));
6839                return value;
6840            }
6841        }
6842    });
6843
6844    var valiDir = avalon.directive('validate', {
6845        diff: function diff(validator) {
6846            var vdom = this.node;
6847            if (vdom.validator) {
6848                return;
6849            }
6850            if (isObject(validator)) {
6851                //注意,这个Form标签的虚拟DOM有两个验证对象
6852                //一个是vmValidator,它是用户VM上的那个原始子对象,也是一个VM
6853                //一个是validator,它是vmValidator.$model, 这是为了防止IE6-8添加子属性时添加的hack
6854                //也可以称之为safeValidate
6855                vdom.validator = validator;
6856                validator = platform.toJson(validator);
6857                validator.vdom = vdom;
6858                validator.dom = vdom.dom;
6859
6860                for (var name in valiDir.defaults) {
6861                    if (!validator.hasOwnProperty(name)) {
6862                        validator[name] = valiDir.defaults[name];
6863                    }
6864                }
6865                validator.fields = validator.fields || [];
6866                vdom.vmValidator = validator;
6867                return true;
6868            }
6869        },
6870        update: function update(vdom) {
6871
6872            var vmValidator = vdom.vmValidator;
6873            var validator = vdom.validator;
6874            var dom = vdom.dom;
6875            dom._ms_validate_ = vmValidator;
6876
6877            collectFeild(vdom.children, vmValidator.fields, vmValidator);
6878            var type = window.netscape ? 'keypress' : 'focusin';
6879            avalon.bind(document, type, findValidator);
6880            //为了方便用户手动执行验证,我们需要为原始vmValidate上添加一个onManual方法
6881            function onManual() {
6882                var v = this;
6883                v && valiDir.validateAll.call(v, v.onValidateAll);
6884            }
6885
6886            try {
6887                var fn = vmValidator.onManual = onManual.bind(vmValidator);
6888                validator.onManual = fn;
6889            } catch (e) {
6890                avalon.warn('要想使用onManual方法,必须在validate对象预定义一个空的onManual函数');
6891            }
6892            delete vdom.vmValidator;
6893
6894            dom.setAttribute('novalidate', 'novalidate');
6895
6896            /* istanbul ignore if */
6897            if (vmValidator.validateAllInSubmit) {
6898                avalon.bind(dom, 'submit', validateAllInSubmitFn);
6899            }
6900        },
6901        validateAll: function validateAll(callback) {
6902            var validator = this;
6903            var vdom = this.vdom;
6904            var fields = validator.fields = [];
6905            collectFeild(vdom.children, fields, validator);
6906            var fn = typeof callback === 'function' ? callback : validator.onValidateAll;
6907            var promises = validator.fields.filter(function (field) {
6908                var el = field.dom;
6909                return el && !el.disabled && validator.dom.contains(el);
6910            }).map(function (field) {
6911                return valiDir.validate(field, true);
6912            });
6913            var uniq = {};
6914            return Promise.all(promises).then(function (array) {
6915                var reasons = array.concat.apply([], array);
6916                if (validator.deduplicateInValidateAll) {
6917                    reasons = reasons.filter(function (reason) {
6918                        var el = reason.element;
6919                        var uuid = el.uniqueID || (el.uniqueID = setTimeout('1'));
6920                        if (uniq[uuid]) {
6921                            return false;
6922                        } else {
6923                            return uniq[uuid] = true;
6924                        }
6925                    });
6926                }
6927                fn.call(vdom.dom, reasons); //这里只放置未通过验证的组件
6928            });
6929        },
6930
6931        validate: function validate(field, isValidateAll, event) {
6932
6933            var promises = [];
6934            var value = field.value;
6935            var elem = field.dom;
6936            /* istanbul ignore if */
6937            if (typeof Promise !== 'function') {
6938                //avalon-promise不支持phantomjs
6939                avalon.warn('浏览器不支持原生Promise,请下载并<script src=url>引入\nhttps://github.com/RubyLouvre/avalon/blob/master/test/promise.js');
6940            }
6941            /* istanbul ignore if */
6942            if (elem.disabled) return;
6943            var rules = field.vdom.rules;
6944            var ngs = [],
6945                isOk = true;
6946            if (!(rules.norequired && value === '')) {
6947                for (var ruleName in rules) {
6948                    var ruleValue = rules[ruleName];
6949                    if (ruleValue === false) continue;
6950                    var hook = avalon.validators[ruleName];
6951                    var resolve;
6952                    promises.push(new Promise(function (a, b) {
6953                        resolve = a;
6954                    }));
6955                    var next = function next(a) {
6956                        var reason = {
6957                            element: elem,
6958                            data: field.data,
6959                            message: elem.getAttribute('data-' + ruleName + '-message') || elem.getAttribute('data-message') || hook.message,
6960                            validateRule: ruleName,
6961                            getMessage: getMessage
6962                        };
6963                        if (a) {
6964                            resolve(true);
6965                        } else {
6966                            isOk = false;
6967                            ngs.push(reason);
6968                            resolve(false);
6969                        }
6970                    };
6971                    field.data = {};
6972                    field.data[ruleName] = ruleValue;
6973                    hook.get(value, field, next);
6974                }
6975            }
6976
6977            //如果promises不为空,说明经过验证拦截器
6978            return Promise.all(promises).then(function (array) {
6979                if (!isValidateAll) {
6980                    var validator = field.validator;
6981                    if (isOk) {
6982                        validator.onSuccess.call(elem, [{
6983                            data: field.data,
6984                            element: elem
6985                        }], event);
6986                    } else {
6987                        validator.onError.call(elem, ngs, event);
6988                    }
6989                    validator.onComplete.call(elem, ngs, event);
6990                }
6991                return ngs;
6992            });
6993        }
6994    });
6995
6996    //https://github.com/RubyLouvre/avalon/issues/1977
6997    function getValidate(dom) {
6998        while (dom.tagName !== 'FORM') {
6999            dom = dom.parentNode;
7000        }
7001        return dom._ms_validate_;
7002    }
7003
7004    function validateAllInSubmitFn(e) {
7005        e.preventDefault();
7006        var v = getValidate(e.target);
7007        if (v && v.onManual) {
7008            v.onManual();
7009        }
7010    }
7011
7012    function collectFeild(nodes, fields, validator) {
7013        for (var i = 0, vdom; vdom = nodes[i++];) {
7014            var duplex = vdom.rules && vdom.duplex;
7015            if (duplex) {
7016                fields.push(duplex);
7017                bindValidateEvent(duplex, validator);
7018            } else if (vdom.children) {
7019                collectFeild(vdom.children, fields, validator);
7020            } else if (Array.isArray(vdom)) {
7021                collectFeild(vdom, fields, validator);
7022            }
7023        }
7024    }
7025
7026    function findValidator(e) {
7027        var dom = e.target;
7028        var duplex = dom._ms_duplex_;
7029        var vdom = (duplex || {}).vdom;
7030        if (duplex && vdom.rules && !duplex.validator) {
7031            var msValidator = getValidate(dom);
7032            if (msValidator && avalon.Array.ensure(msValidator.fields, duplex)) {
7033                bindValidateEvent(duplex, msValidator);
7034            }
7035        }
7036    }
7037
7038    function singleValidate(e) {
7039        var dom = e.target;
7040        var duplex = dom._ms_duplex_;
7041        var msValidator = getValidate(e.target);
7042        msValidator && msValidator.validate(duplex, 0, e);
7043    }
7044
7045    function bindValidateEvent(field, validator) {
7046
7047        var node = field.dom;
7048        if (field.validator) {
7049            return;
7050        }
7051        field.validator = validator;
7052        /* istanbul ignore if */
7053        if (validator.validateInKeyup && !field.isChanged && !field.debounceTime) {
7054            avalon.bind(node, 'keyup', singleValidate);
7055        }
7056        /* istanbul ignore if */
7057        if (validator.validateInBlur) {
7058            avalon.bind(node, 'blur', singleValidate);
7059        }
7060        /* istanbul ignore if */
7061        if (validator.resetInFocus) {
7062            avalon.bind(node, 'focus', function (e) {
7063                var dom = e.target;
7064                var field = dom._ms_duplex_;
7065                var validator = getValidate(e.target);
7066                validator && validator.onReset.call(dom, e, field);
7067            });
7068        }
7069    }
7070    var rformat = /\\?{{([^{}]+)\}}/gm;
7071
7072    function getMessage() {
7073        var data = this.data || {};
7074        return this.message.replace(rformat, function (_, name) {
7075            return data[name] == null ? '' : data[name];
7076        });
7077    }
7078    valiDir.defaults = {
7079        validate: valiDir.validate,
7080        onError: avalon.noop,
7081        onSuccess: avalon.noop,
7082        onComplete: avalon.noop,
7083        onManual: avalon.noop,
7084        onReset: avalon.noop,
7085        onValidateAll: avalon.noop,
7086        validateInBlur: true, //@config {Boolean} true,在blur事件中进行验证,触发onSuccess, onError, onComplete回调
7087        validateInKeyup: true, //@config {Boolean} true,在keyup事件中进行验证,触发onSuccess, onError, onComplete回调
7088        validateAllInSubmit: true, //@config {Boolean} true,在submit事件中执行onValidateAll回调
7089        resetInFocus: true, //@config {Boolean}
7089 true,在focus事件中执行onReset回调,
7090        deduplicateInValidateAll: false //@config {Boolean} false,在validateAll回调中对reason数组根据元素节点进行去重
7091    };
7092
7093    /**
7094     * 一个directive装饰器
7095     * @returns {directive}
7096     */
7097    // DirectiveDecorator(scope, binding, vdom, this)
7098    // Decorator(vm, options, callback)
7099    function Directive(vm, binding, vdom, render) {
7100        var type = binding.type;
7101        var decorator = avalon.directives[type];
7102        if (inBrowser) {
7103            var dom = avalon.vdom(vdom, 'toDOM');
7104            if (dom.nodeType === 1) {
7105                dom.removeAttribute(binding.attrName);
7106            }
7107            vdom.dom = dom;
7108        }
7109        var callback = decorator.update ? function (value) {
7110            if (!render.mount && /css|visible|duplex/.test(type)) {
7111                render.callbacks.push(function () {
7112                    decorator.update.call(directive$$1, directive$$1.node, value);
7113                });
7114            } else {
7115                decorator.update.call(directive$$1, directive$$1.node, value);
7116            }
7117        } : avalon.noop;
7118        for (var key in decorator) {
7119            binding[key] = decorator[key];
7120        }
7121        binding.node = vdom;
7122        var directive$$1 = new Action(vm, binding, callback);
7123        if (directive$$1.init) {
7124            //这里可能会重写node, callback, type, name
7125            directive$$1.init();
7126        }
7127        directive$$1.update();
7128        return directive$$1;
7129    }
7130
7131    var eventMap = avalon.oneObject('animationend,blur,change,input,' + 'click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,' + 'mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit', 'on');
7132    function parseAttributes(dirs, tuple) {
7133        var node = tuple[0],
7134            uniq = {},
7135            bindings = [];
7136        var hasIf = false;
7137        for (var name in dirs) {
7138            var value = dirs[name];
7139            var arr = name.split('-');
7140            // ms-click
7141            if (name in node.props) {
7142                var attrName = name;
7143            } else {
7144                attrName = ':' + name.slice(3);
7145            }
7146            if (eventMap[arr[1]]) {
7147                arr.splice(1, 0, 'on');
7148            }
7149            //ms-on-click
7150            if (arr[1] === 'on') {
7151                arr[3] = parseFloat(arr[3]) || 0;
7152            }
7153
7154            var type = arr[1];
7155            if (type === 'controller' || type === 'important') continue;
7156            if (directives[type]) {
7157
7158                var binding = {
7159                    type: type,
7160                    param: arr[2],
7161                    attrName: attrName,
7162                    name: arr.join('-'),
7163                    expr: value,
7164                    priority: directives[type].priority || type.charCodeAt(0) * 100
7165                };
7166                if (type === 'if') {
7167                    hasIf = true;
7168                }
7169                if (type === 'on') {
7170                    binding.priority += arr[3];
7171                }
7172                if (!uniq[binding.name]) {
7173                    uniq[binding.name] = value;
7174                    bindings.push(binding);
7175                    if (type === 'for') {
7176                        return [avalon.mix(binding, tuple[3])];
7177                    }
7178                }
7179            }
7180        }
7181        bindings.sort(byPriority);
7182
7183        if (hasIf) {
7184            var ret = [];
7185            for (var i = 0, el; el = bindings[i++];) {
7186                ret.push(el);
7187                if (el.type === 'if') {
7188                    return ret;
7189                }
7190            }
7191        }
7192        return bindings;
7193    }
7194    function byPriority(a, b) {
7195        return a.priority - b.priority;
7196    }
7197
7198    var rimprovePriority = /[+-\?]/;
7199    var rinnerValue = /__value__\)$/;
7200    function parseInterpolate(dir) {
7201        var rlineSp = /\n\r?/g;
7202        var str = dir.nodeValue.trim().replace(rlineSp, '');
7203        var tokens = [];
7204        do {
7205            //aaa{{@bbb}}ccc
7206            var index = str.indexOf(config.openTag);
7207            index = index === -1 ? str.length : index;
7208            var value = str.slice(0, index);
7209            if (/\S/.test(value)) {
7210                tokens.push(avalon.quote(avalon._decode(value)));
7211            }
7212            str = str.slice(index + config.openTag.length);
7213            if (str) {
7214                index = str.indexOf(config.closeTag);
7215                var value = str.slice(0, index);
7216                var expr = avalon.unescapeHTML(value);
7217                if (/\|\s*\w/.test(expr)) {
7218                    //如果存在过滤器,优化干掉
7219                    var arr = addScope(expr, 'expr');
7220                    if (arr[1]) {
7221                        expr = arr[1].replace(rinnerValue, arr[0] + ')');
7222                    }
7223                }
7224                if (rimprovePriority) {
7225                    expr = '(' + expr + ')';
7226                }
7227                tokens.push(expr);
7228
7229                str = str.slice(index + config.closeTag.length);
7230            }
7231        } while (str.length);
7232        return [{
7233            expr: tokens.join('+'),
7234            name: 'expr',
7235            type: 'expr'
7236        }];
7237    }
7238
7239    function getChildren(arr) {
7240        var count = 0;
7241        for (var i = 0, el; el = arr[i++];) {
7242            if (el.nodeName === '#document-fragment') {
7243                count += getChildren(el.children);
7244            } else {
7245                count += 1;
7246            }
7247        }
7248        return count;
7249    }
7250    function groupTree(parent, children) {
7251        children && children.forEach(function (vdom) {
7252            if (!vdom) return;
7253            var vlength = vdom.children && getChildren(vdom.children);
7254            if (vdom.nodeName === '#document-fragment') {
7255                var dom = createFragment();
7256            } else {
7257                dom = avalon.vdom(vdom, 'toDOM');
7258                var domlength = dom.childNodes && dom.childNodes.length;
7259                if (domlength && vlength && domlength > vlength) {
7260                    if (!appendChildMayThrowError[dom.nodeName]) {
7261                        avalon.clearHTML(dom);
7262                    }
7263                }
7264            }
7265            if (vlength) {
7266                groupTree(dom, vdom.children);
7267                if (vdom.nodeName === 'select') {
7268                    var values = [];
7269                    getSelectedValue(vdom, values);
7270                    lookupOption(vdom, values);
7271                }
7272            }
7273            //高级版本可以尝试 querySelectorAll
7274
7275            try {
7276                if (!appendChildMayThrowError[parent.nodeName]) {
7277                    parent.appendChild(dom);
7278                }
7279            } catch (e) {}
7280        });
7281    }
7282
7283    function dumpTree(elem) {
7284        var firstChild;
7285        while (firstChild = elem.firstChild) {
7286            if (firstChild.nodeType === 1) {
7287                dumpTree(firstChild);
7288            }
7289            elem.removeChild(firstChild);
7290        }
7291    }
7292
7293    function getRange(childNodes, node) {
7294        var i = childNodes.indexOf(node) + 1;
7295        var deep = 1,
7296            nodes = [],
7297            end;
7298        nodes.start = i;
7299        while (node = childNodes[i++]) {
7300            nodes.push(node);
7301            if (node.nodeName === '#comment') {
7302                if (startWith(node.nodeValue, 'ms-for:')) {
7303                    deep++;
7304                } else if (node.nodeValue === 'ms-for-end:') {
7305                    deep--;
7306                    if (deep === 0) {
7307                        end = node;
7308                        nodes.pop();
7309                        break;
7310                    }
7311                }
7312            }
7313        }
7314        nodes.end = end;
7315        return nodes;
7316    }
7317
7318    function startWith(long, short) {
7319        return long.indexOf(short) === 0;
7320    }
7321
7322    var appendChildMayThrowError = {
7323        '#text': 1,
7324        '#comment': 1,
7325        script: 1,
7326        style: 1,
7327        noscript: 1
7328    };
7329
7330    /**
7331     * 生成一个渲染器,并作为它第一个遇到的ms-controller对应的VM的$render属性
7332     * @param {String|DOM} node
7333     * @param {ViewModel|Undefined} vm
7334     * @param {Function|Undefined} beforeReady
7335     * @returns {Render}
7336     */
7337    avalon.scan = function (node, vm, beforeReady) {
7338        return new Render(node, vm, beforeReady || avalon.noop);
7339    };
7340
7341    /**
7342     * avalon.scan 的内部实现
7343     */
7344    function Render(node, vm, beforeReady) {
7345        this.root = node; //如果传入的字符串,确保只有一个标签作为根节点
7346        this.vm = vm;
7347        this.beforeReady = beforeReady;
7348        this.bindings = []; //收集待加工的绑定属性
7349        this.callbacks = [];
7350        this.directives = [];
7351        this.init();
7352    }
7353
7354    Render.prototype = {
7355        /**
7356         * 开始扫描指定区域
7357         * 收集绑定属性
7358         * 生成指令并建立与VM的关联
7359         */
7360        init: function init() {
7361            var vnodes;
7362            if (this.root && this.root.nodeType > 0) {
7363                vnodes = fromDOM(this.root); //转换虚拟DOM
7364                //将扫描区域的每一个节点与其父节点分离,更少指令对DOM操作时,对首屏输出造成的频繁重绘
7365                dumpTree(this.root);
7366            } else if (typeof this.root === 'string') {
7367                vnodes = fromString(this.root); //转换虚拟DOM
7368            } else {
7369                return avalon.warn('avalon.scan first argument must element or HTML string');
7370            }
7371
7372            this.root = vnodes[0];
7373            this.vnodes = vnodes;
7374            this.scanChildren(vnodes, this.vm, true);
7375        },
7376        scanChildren: function scanChildren(children, scope, isRoot) {
7377            for (var i = 0; i < children.length; i++) {
7378                var vdom = children[i];
7379                switch (vdom.nodeName) {
7380                    case '#text':
7381                        scope && this.scanText(vdom, scope);
7382                        break;
7383                    case '#comment':
7384                        scope && this.scanComment(vdom, scope, children);
7385                        break;
7386                    case '#document-fragment':
7387                        this.scanChildren(vdom.children, scope, false);
7388                        break;
7389                    default:
7390                        this.scanTag(vdom, scope, children, false);
7391                        break;
7392                }
7393            }
7394            if (isRoot) {
7395                this.complete();
7396            }
7397        },
7398
7399
7400        /**
7401         * 从文本节点获取指令
7402         * @param {type} vdom 
7403         * @param {type} scope
7404         * @returns {undefined}
7405         */
7406        scanText: function scanText(vdom, scope) {
7407            if (config.rexpr.test(vdom.nodeValue)) {
7408                this.bindings.push([vdom, scope, {
7409                    nodeValue: vdom.nodeValue
7410                }]);
7411            }
7412        },
7413
7414
7415        /**
7416         * 从注释节点获取指令
7417         * @param {type} vdom 
7418         * @param {type} scope
7419         * @param {type} parentChildren
7420         * @returns {undefined}
7421         */
7422        scanComment: function scanComment(vdom, scope, parentChildren) {
7423            if (startWith(vdom.nodeValue, 'ms-for:')) {
7424                this.getForBinding(vdom, scope, parentChildren);
7425            }
7426        },
7427
7428
7429        /**
7430         * 从元素节点的nodeName与属性中获取指令
7431         * @param {type} vdom 
7432         * @param {type} scope
7433         * @param {type} parentChildren
7434         * @param {type} isRoot 用于执行complete方法
7435         * @returns {undefined}
7436         */
7437        scanTag: function scanTag(vdom, scope, parentChildren, isRoot) {
7438            var dirs = {},
7439                attrs = vdom.props,
7440                hasDir,
7441                hasFor;
7442            for (var attr in attrs) {
7443                var value = attrs[attr];
7444                var oldName = attr;
7445                if (attr.charAt(0) === ':') {
7446                    attr = 'ms-' + attr.slice(1);
7447                }
7448                if (startWith(attr, 'ms-')) {
7449                    dirs[attr] = value;
7450                    var type = attr.match(/\w+/g)[1];
7451                    type = eventMap[type] || type;
7452                    if (!directives[type]) {
7453                        avalon.warn(attr + ' has not registered!');
7454                    }
7455                    hasDir = true;
7456                }
7457                if (attr === 'ms-for') {
7458                    hasFor = value;
7459                    delete attrs[oldName];
7460                }
7461            }
7462            var $id = dirs['ms-important'] || dirs['ms-controller'];
7463            if ($id) {
7464                /**
7465                 * 后端渲染
7466                 * serverTemplates后端给avalon添加的对象,里面都是模板,
7467                 * 将原来后端渲染好的区域再还原成原始样子,再被扫描
7468                 */
7469                var templateCaches = avalon.serverTemplates;
7470                var temp = templateCaches && templateCaches[$id];
7471                if (temp) {
7472                    avalon.log('前端再次渲染后端传过来的模板');
7473                    var node = fromString(temp)[0];
7474                    for (var i in node) {
7475                        vdom[i] = node[i];
7476                    }
7477                    delete templateCaches[$id];
7478                    this.scanTag(vdom, scope, parentChildren, isRoot);
7479                    return;
7480                }
7481                //推算出指令类型
7482                var type = dirs['ms-important'] === $id ? 'important' : 'controller';
7483                //推算出用户定义时属性名,是使用ms-属性还是:属性
7484                var attrName = 'ms-' + type in attrs ? 'ms-' + type : ':' + type;
7485
7486                if (inBrowser) {
7487                    delete attrs[attrName];
7488                }
7489                var dir = directives[type];
7490                scope = dir.getScope.call(this, $id, scope);
7491                if (!scope) {
7492                    return;
7493                } else {
7494                    var clazz = attrs['class'];
7495                    if (clazz) {
7496                        attrs['class'] = (' ' + clazz + ' ').replace(' ms-controller ', '').trim();
7497                    }
7498                }
7499                var render = this;
7500                scope.$render = render;
7501                this.callbacks.push(function () {
7502                    //用于删除ms-controller
7503                    dir.update.call(render, vdom, attrName, $id);
7504                });
7505            }
7506            if (hasFor) {
7507                if (vdom.dom) {
7508                    vdom.dom.removeAttribute(oldName);
7509                }
7510                return this.getForBindingByElement(vdom, scope, parentChildren, hasFor);
7511            }
7512
7513            if (/^ms\-/.test(vdom.nodeName)) {
7514                attrs.is = vdom.nodeName;
7515            }
7516
7517            if (attrs['is']) {
7518                if (!dirs['ms-widget']) {
7519                    dirs['ms-widget'] = '{}';
7520                }
7521                hasDir = true;
7522            }
7523            if (hasDir) {
7524                this.bindings.push([vdom, scope, dirs]);
7525            }
7526            var children = vdom.children;
7527            //如果存在子节点,并且不是容器元素(script, stype, textarea, xmp...)
7528            if (!orphanTag[vdom.nodeName] && children && children.length && !delayCompileNodes(dirs)) {
7529                this.scanChildren(children, scope, false);
7530            }
7531        },
7532
7533
7534        /**
7535         * 将绑定属性转换为指令
7536         * 执行各种回调与优化指令
7537         * @returns {undefined}
7538         */
7539        complete: function complete() {
7540            this.yieldDirectives();
7541            this.beforeReady();
7542            if (inBrowser) {
7543                var root$$1 = this.root;
7544                if (inBrowser) {
7545                    var rootDom = avalon.vdom(root$$1, 'toDOM');
7546                    groupTree(rootDom, root$$1.children);
7547                }
7548            }
7549
7550            this.mount = true;
7551            var fn;
7552            while (fn = this.callbacks.pop()) {
7553                fn();
7554            }
7555            this.optimizeDirectives();
7556        },
7557
7558
7559        /**
7560         * 将收集到的绑定属性进行深加工,最后转换指令
7561         * @returns {Array<tuple>}
7562         */
7563        yieldDirectives: function yieldDirectives() {
7564            var tuple;
7565            while (tuple = this.bindings.shift()) {
7566                var vdom = tuple[0],
7567                    scope = tuple[1],
7568                    dirs = tuple[2],
7569                    bindings = [];
7570                if ('nodeValue' in dirs) {
7571                    bindings = parseInterpolate(dirs);
7572                } else if (!('ms-skip' in dirs)) {
7573                    bindings = parseAttributes(dirs, tuple);
7574                }
7575                for (var i = 0, binding; binding = bindings[i++];) {
7576                    var dir = directives[binding.type];
7577                    if (!inBrowser && /on|duplex|active|hover/.test(binding.type)) {
7578                        continue;
7579                    }
7580                    if (dir.beforeInit) {
7581                        dir.beforeInit.call(binding);
7582                    }
7583
7584                    var directive$$1 = new Directive(scope, binding, vdom, this);
7585                    this.directives.push(directive$$1);
7586                }
7587            }
7588        },
7589
7590
7591        /**
7592         * 修改指令的update与callback方法,让它们以后执行时更加高效
7593         * @returns {undefined}
7594         */
7595        optimizeDirectives: function optimizeDirectives() {
7596            for (var i = 0, el; el = this.directives[i++];) {
7597                el.callback = directives[el.type].update;
7598                el.update = newUpdate;
7599                el._isScheduled = false;
7600            }
7601        },
7602
7603        update: function update() {
7604            for (var i = 0, el; el = this.directives[i++];) {
7605                el.update();
7606            }
7607        },
7608
7609        /**
7610         * 销毁所有指令
7611         * @returns {undefined}
7612         */
7613        dispose: function dispose() {
7614            var list = this.directives || [];
7615            for (var i = 0, el; el = list[i++];) {
7616                el.dispose();
7617            }
7618            //防止其他地方的this.innerRender && this.innerRender.dispose报错
7619            for (var _i6 in this) {
7620                if (_i6 !== 'dispose') delete this[_i6];
7621            }
7622        },
7623
7624
7625        /**
7626         * 将循环区域转换为for指令
7627         * @param {type} begin 注释节点
7628         * @param {type} scope
7629         * @param {type} parentChildren
7630         * @param {type} userCb 循环结束回调
7631         * @returns {undefined}
7632         */
7633        getForBinding: function getForBinding(begin, scope, parentChildren, userCb) {
7634            var expr = begin.nodeValue.replace('ms-for:', '').trim();
7635            begin.nodeValue = 'ms-for:' + expr;
7636            var nodes = getRange(parentChildren, begin);
7637            var end = nodes.end;
7638            var fragment = avalon.vdom(nodes, 'toHTML');
7639            parentChildren.splice(nodes.start, nodes.length);
7640            begin.props = {};
7641            this.bindings.push([begin, scope, {
7642                'ms-for': expr
7643            }, {
7644                begin: begin,
7645                end: end,
7646                expr: expr,
7647                userCb: userCb,
7648                fragment: fragment,
7649                parentChildren: parentChildren
7650            }]);
7651        },
7652
7653
7654        /**
7655         * 在带ms-for元素节点旁添加两个注释节点,组成循环区域
7656         * @param {type} vdom
7657         * @param {type} scope
7658         * @param {type} parentChildren
7659         * @param {type} expr
7660         * @returns {undefined}
7661         */
7662        getForBindingByElement: function getForBindingByElement(vdom, scope, parentChildren, expr) {
7663            var index = parentChildren.indexOf(vdom); //原来带ms-for的元素节点
7664            var props = vdom.props;
7665            var begin = {
7666                nodeName: '#comment',
7667                nodeValue: 'ms-for:' + expr
7668            };
7669            if (props.slot) {
7670                begin.slot = props.slot;
7671                delete props.slot;
7672            }
7673            var end = {
7674                nodeName: '#comment',
7675                nodeValue: 'ms-for-end:'
7676            };
7677            parentChildren.splice(index, 1, begin, vdom, end);
7678            this.getForBinding(begin, scope, parentChildren, props['data-for-rendered']);
7679        }
7680    };
7681    var viewID;
7682
7683    function newUpdate() {
7684        var oldVal = this.beforeUpdate();
7685        var newVal = this.value = this.get();
7686        if (this.callback && this.diff(newVal, oldVal)) {
7687            this.callback(this.node, this.value);
7688            var vm = this.vm;
7689            var $render = vm.$render;
7690            var list = vm.$events['onViewChange'];
7691            /* istanbul ignore if */
7692            if (list && $render && $render.root && !avalon.viewChanging) {
7693                if (viewID) {
7694                    clearTimeout(viewID);
7695                    viewID = null;
7696                }
7697                viewID = setTimeout(function () {
7698                    list.forEach(function (el) {
7699                        el.callback.call(vm, {
7700                            type: 'viewchange',
7701                            target: $render.root,
7702                            vmodel: vm
7703                        });
7704                    });
7705                });
7706            }
7707        }
7708        this._isScheduled = false;
7709    }
7710
7711    var events = 'onInit,onReady,onViewChange,onDispose,onEnter,onLeave';
7712    var componentEvents = avalon.oneObject(events);
7713
7714    function toObject(value) {
7715        var value = platform.toJson(value);
7716        if (Array.isArray(value)) {
7717            var v = {};
7718            value.forEach(function (el) {
7719                el && avalon.shadowCopy(v, el);
7720            });
7721            return v;
7722        }
7723        return value;
7724    }
7725    var componentQueue = [];
7726    avalon.directive('widget', {
7727        delay: true,
7728        priority: 4,
7729        deep: true,
7730        init: function init() {
7731            //cached属性必须定义在组件容器里面,不是template中
7732            var vdom = this.node;
7733            this.cacheVm = !!vdom.props.cached;
7734            if (vdom.dom && vdom.nodeName === '#comment') {
7735                var comment = vdom.dom;
7736            }
7737            var oldValue = this.getValue();
7738            var value = toObject(oldValue);
7739            //外部VM与内部VM
7740            // ===创建组件的VM==BEGIN===
7741            var is = vdom.props.is || value.is;
7742            this.is = is;
7743            var component = avalon.components[is];
7744            //外部传入的总大于内部
7745            if (!('fragment' in this)) {
7746                if (!vdom.isVoidTag) {
7747                    //提取组件容器内部的东西作为模板
7748                    var text = vdom.children[0];
7749                    if (text && text.nodeValue) {
7750                        this.fragment = text.nodeValue;
7751                    } else {
7752                        this.fragment = avalon.vdom(vdom.children, 'toHTML');
7753                    }
7754                } else {
7755                    this.fragment = false;
7756                }
7757            }
7758            //如果组件还没有注册,那么将原元素变成一个占位用的注释节点
7759            if (!component) {
7760                this.readyState = 0;
7761                vdom.nodeName = '#comment';
7762                vdom.nodeValue = 'unresolved component placeholder';
7763                delete vdom.dom;
7764                avalon.Array.ensure(componentQueue, this);
7765                return;
7766            }
7767
7768            //如果是非空元素,比如说xmp, ms-*, template
7769            var id = value.id || value.$id;
7770            var hasCache = avalon.vmodels[id];
7771            var fromCache = false;
7772            // this.readyState = 1
7773            if (hasCache) {
7774                comVm = hasCache;
7775                this.comVm = comVm;
7776                replaceRoot(this, comVm.$render);
7777                fromCache = true;
7778            } else {
7779                if (typeof component === 'function') {
7780                    component = new component(value);
7781                }
7782                var comVm = createComponentVm(component, value, is);
7783                this.readyState = 1;
7784                fireComponentHook(comVm, vdom, 'Init');
7785                this.comVm = comVm;
7786
7787                // ===创建组件的VM==END===
7788                var innerRender = avalon.scan(component.template, comVm);
7789                comVm.$render = innerRender;
7790                replaceRoot(this, innerRender);
7791                var nodesWithSlot = [];
7792                var directives$$1 = [];
7793                if (this.fragment || component.soleSlot) {
7794                    var curVM = this.fragment ? this.vm : comVm;
7795                    var curText = this.fragment || '{{##' + component.soleSlot + '}}';
7796                    var childBoss = avalon.scan('<div>' + curText + '</div>', curVM, function () {
7797                        nodesWithSlot = this.root.children;
7798                    });
7799                    directives$$1 = childBoss.directives;
7800                    this.childBoss = childBoss;
7801                    for (var i in childBoss) {
7802                        delete childBoss[i];
7803                    }
7804                }
7805                Array.prototype.push.apply(innerRender.directives, directives$$1);
7806
7807                var arraySlot = [],
7808                    objectSlot = {};
7809                //从用户写的元素内部 收集要移动到 新创建的组件内部的元素
7810                if (component.soleSlot) {
7811                    arraySlot = nodesWithSlot;
7812                } else {
7813                    nodesWithSlot.forEach(function (el, i) {
7814                        //要求带slot属性
7815                        if (el.slot) {
7816                            var nodes = getRange(nodesWithSlot, el);
7817                            nodes.push(nodes.end);
7818                            nodes.unshift(el);
7819                            objectSlot[el.slot] = nodes;
7820                        } else if (el.props) {
7821                            var name = el.props.slot;
7822                            if (name) {
7823                                delete el.props.slot;
7824                                if (Array.isArray(objectSlot[name])) {
7825                                    objectSlot[name].push(el);
7826                                } else {
7827                                    objectSlot[name] = [el];
7828                                }
7829                            }
7830                        }
7831                    });
7832                }
7833                //将原来元素的所有孩子,全部移动新的元素的第一个slot的位置上
7834                if (component.soleSlot) {
7835                    insertArraySlot(innerRender.vnodes, arraySlot);
7836                } else {
7837                    insertObjectSlot(innerRender.vnodes, objectSlot);
7838                }
7839            }
7840
7841            if (comment) {
7842                var dom = avalon.vdom(vdom, 'toDOM');
7843                comment.parentNode.replaceChild(dom, comment);
7844                comVm.$element = innerRender.root.dom = dom;
7845                delete this.reInit;
7846            }
7847
7848            //处理DOM节点
7849
7850            dumpTree(vdom.dom);
7851            comVm.$element = vdom.dom;
7852            groupTree(vdom.dom, vdom.children);
7853            if (fromCache) {
7854                fireComponentHook(comVm, vdom, 'Enter');
7855            } else {
7856                fireComponentHook(comVm, vdom, 'Ready');
7857            }
7858        },
7859        diff: function diff(newVal, oldVal) {
7860            if (cssDiff.call(this, newVal, oldVal)) {
7861                return true;
7862            }
7863        },
7864
7865        update: function update(vdom, value) {
7866            //this.oldValue = value //★★防止递归
7867
7868            switch (this.readyState) {
7869                case 0:
7870                    if (this.reInit) {
7871                        this.init();
7872                        this.readyState++;
7873                    }
7874                    break;
7875                case 1:
7876                    this.readyState++;
7877                    break;
7878                default:
7879                    this.readyState++;
7880                    var comVm = this.comVm;
7881                    avalon.viewChanging = true;
7882                    avalon.transaction(function () {
7883                        for (var i in value) {
7884                            if (comVm.hasOwnProperty(i)) {
7885                                if (Array.isArray(value[i])) {
7886                                    comVm[i] = value[i].concat();
7887                                } else {
7888                                    comVm[i] = value[i];
7889                                }
7890                            }
7891                        }
7892                    });
7893
7894                    //要保证要先触发孩子的ViewChange 然后再到它自己的ViewChange
7895                    fireComponentHook(comVm, vdom, 'ViewChange');
7896                    delete avalon.viewChanging;
7897                    break;
7898            }
7899            this.value = avalon.mix(true, {}, value);
7900        },
7901        beforeDispose: function beforeDispose() {
7902            var comVm = this.comVm;
7903            if (!this.cacheVm) {
7904                fireComponentHook(comVm, this.node, 'Dispose');
7905                comVm.$hashcode = false;
7906                delete avalon.vmodels[comVm.$id];
7907                this.innerRender && this.innerRender.dispose();
7908            } else {
7909                fireComponentHook(comVm, this.node, 'Leave');
7910            }
7911        }
7912    });
7913
7914    function replaceRoot(instance, innerRender) {
7915        instance.innerRender = innerRender;
7916        var root$$1 = innerRender.root;
7917        var vdom = instance.node;
7918        var slot = vdom.props.slot;
7919        for (var i in root$$1) {
7920            vdom[i] = root$$1[i];
7921        }
7922        if (vdom.props && slot) {
7923            vdom.props.slot = slot;
7924        }
7925        innerRender.root = vdom;
7926        innerRender.vnodes[0] = vdom;
7927    }
7928
7929    function fireComponentHook(vm, vdom, name) {
7930        var list = vm.$events['on' + name];
7931        if (list) {
7932            list.forEach(function (el) {
7933                setTimeout(function () {
7934                    el.callback.call(vm, {
7935                        type: name.toLowerCase(),
7936                        target: vdom.dom,
7937                        vmodel: vm
7938                    });
7939                }, 0);
7940            });
7941        }
7942    }
7943
7944    function createComponentVm(component, value, is) {
7945        var hooks = [];
7946        var defaults = component.defaults;
7947        collectHooks(defaults, hooks);
7948        collectHooks(value, hooks);
7949        var obj = {};
7950        for (var i in defaults) {
7951            var val = value[i];
7952            if (val == null) {
7953                obj[i] = defaults[i];
7954            } else {
7955                obj[i] = val;
7956            }
7957        }
7958        obj.$id = value.id || value.$id || avalon.makeHashCode(is);
7959        delete obj.id;
7960        var def = avalon.mix(true, {}, obj);
7961        var vm = avalon.define(def);
7962        hooks.forEach(function (el) {
7963            vm.$watch(el.type, el.cb);
7964        });
7965        return vm;
7966    }
7967
7968    function collectHooks(a, list) {
7969        for (var i in a) {
7970            if (componentEvents[i]) {
7971                if (typeof a[i] === 'function' && i.indexOf('on') === 0) {
7972                    list.unshift({
7973                        type: i,
7974                        cb: a[i]
7975                    });
7976                }
7977                //delete a[i] 这里不能删除,会导致再次切换时没有onReady
7978            }
7979        }
7980    }
7981
7982    function resetParentChildren(nodes, arr) {
7983        var dir = arr && arr[0] && arr[0].forDir;
7984        if (dir) {
7985            dir.parentChildren = nodes;
7986        }
7987    }
7988
7989    function insertArraySlot(nodes, arr) {
7990        for (var i = 0, el; el = nodes[i]; i++) {
7991            if (el.nodeName === 'slot') {
7992                resetParentChildren(nodes, arr);
7993                nodes.splice.apply(nodes, [i, 1].concat(arr));
7994                break;
7995            } else if (el.children) {
7996                insertArraySlot(el.children, arr);
7997            }
7998        }
7999    }
8000
8001    function insertObjectSlot(nodes, obj) {
8002        for (var i = 0, el; el = nodes[i]; i++) {
8003            if (el.nodeName === 'slot') {
8004                var name = el.props.name;
8005                resetParentChildren(nodes, obj[name]);
8006                nodes.splice.apply(nodes, [i, 1].concat(obj[name]));
8007                continue;
8008            } else if (el.children) {
8009                insertObjectSlot(el.children, obj);
8010            }
8011        }
8012    }
8013
8014    avalon.components = {};
8015    avalon.component = function (name, component) {
8016
8017        component.extend = componentExtend;
8018        return addToQueue(name, component);
8019    };
8020    function addToQueue(name, component) {
8021        avalon.components[name] = component;
8022        for (var el, i = 0; el = componentQueue[i]; i++) {
8023            if (el.is === name) {
8024                componentQueue.splice(i, 1);
8025                el.reInit = true;
8026                delete el.value;
8027                el.update();
8028                i--;
8029            }
8030        }
8031        return component;
8032    }
8033
8034    function componentExtend(child) {
8035        var name = child.displayName;
8036        delete child.displayName;
8037        var obj = { defaults: avalon.mix(true, {}, this.defaults, child.defaults) };
8038        if (child.soleSlot) {
8039            obj.soleSlot = child.soleSlot;
8040        }
8041        obj.template = child.template || this.template;
8042        return avalon.component(name, obj);
8043    }
8044
8045    return avalon;
8046});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.