CLIENT GUI Framework
[vnfsdk/refrepo.git] / openo-portal / portal-common / src / main / webapp / common / thirdparty / avalon / avalon.modern.js
1 /*==================================================\r
2  Copyright (c) 2013-2015 司徒正美 and other contributors\r
3  http://www.cnblogs.com/rubylouvre/\r
4  https://github.com/RubyLouvre\r
5  http://weibo.com/jslouvre/\r
6 \r
7  Released under the MIT license\r
8  avalon.modern.js 1.43 built in 2015.5.21\r
9  support IE10+ and other browsers\r
10  ==================================================*/\r
11 (function (global, factory) {\r
12 \r
13     if (typeof module === "object" && typeof module.exports === "object") {\r
14         // For CommonJS and CommonJS-like environments where a proper `window`\r
15         // is present, execute the factory and get avalon.\r
16         // For environments that do not have a `window` with a `document`\r
17         // (such as Node.js), expose a factory as module.exports.\r
18         // This accentuates the need for the creation of a real `window`.\r
19         // e.g. var avalon = require("avalon")(window);\r
20         module.exports = global.document ? factory(global, true) : function (w) {\r
21             if (!w.document) {\r
22                 throw new Error("Avalon requires a window with a document")\r
23             }\r
24             return factory(w)\r
25         }\r
26     } else {\r
27         factory(global)\r
28     }\r
29 \r
30 // Pass this if window is not defined yet\r
31 }(typeof window !== "undefined" ? window : this, function (window, noGlobal) {\r
32 \r
33     /*********************************************************************\r
34      *                    全局变量及方法                                  *\r
35      **********************************************************************/\r
36     var expose = Date.now()\r
37 //http://stackoverflow.com/questions/7290086/javascript-use-strict-and-nicks-find-global-function\r
38     var DOC = window.document\r
39     var head = DOC.head //HEAD元素\r
40     head.insertAdjacentHTML("afterBegin", '<avalon ms-skip class="avalonHide"><style id="avalonStyle">.avalonHide{ display: none!important }</style></avalon>')\r
41     var ifGroup = head.firstChild\r
42 \r
43     function log() {\r
44         if (avalon.config.debug) {\r
45 // http://stackoverflow.com/questions/8785624/how-to-safely-wrap-console-log\r
46             console.log.apply(console, arguments)\r
47         }\r
48     }\r
49 \r
50     /**\r
51      * Creates a new object without a prototype. This object is useful for lookup without having to\r
52      * guard against prototypically inherited properties via hasOwnProperty.\r
53      *\r
54      * Related micro-benchmarks:\r
55      * - http://jsperf.com/object-create2\r
56      * - http://jsperf.com/proto-map-lookup/2\r
57      * - http://jsperf.com/for-in-vs-object-keys2\r
58      */\r
59     function createMap() {\r
60         return Object.create(null)\r
61     }\r
62 \r
63     var subscribers = "$" + expose\r
64     var otherRequire = window.require\r
65     var otherDefine = window.define\r
66     var innerRequire\r
67     var stopRepeatAssign = false\r
68     var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach\r
69     var rcomplexType = /^(?:object|array)$/\r
70     var rsvg = /^\[object SVG\w*Element\]$/\r
71     var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/\r
72     var oproto = Object.prototype\r
73     var ohasOwn = oproto.hasOwnProperty\r
74     var serialize = oproto.toString\r
75     var ap = Array.prototype\r
76     var aslice = ap.slice\r
77     var Registry = {} //将函数曝光到此对象上,方便访问器收集依赖\r
78     var W3C = window.dispatchEvent\r
79     var root = DOC.documentElement\r
80     var hyperspace = DOC.createDocumentFragment()\r
81     var cinerator = DOC.createElement("div")\r
82     var class2type = {}\r
83     "Boolean Number String Function Array Date RegExp Object Error".replace(rword, function (name) {\r
84         class2type["[object " + name + "]"] = name.toLowerCase()\r
85     })\r
86 \r
87 \r
88     function noop() {\r
89     }\r
90 \r
91 \r
92     function oneObject(array, val) {\r
93         if (typeof array === "string") {\r
94             array = array.match(rword) || []\r
95         }\r
96         var result = {},\r
97             value = val !== void 0 ? val : 1\r
98         for (var i = 0, n = array.length; i < n; i++) {\r
99             result[array[i]] = value\r
100         }\r
101         return result\r
102     }\r
103 \r
104 //生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript\r
105     var generateID = function (prefix) {\r
106         prefix = prefix || "avalon"\r
107         return String(Math.random() + Math.random()).replace(/\d\.\d{4}/, prefix)\r
108     }\r
109 \r
110     function IE() {\r
111         if (window.VBArray) {\r
112             var mode = document.documentMode\r
113             return mode ? mode : window.XMLHttpRequest ? 7 : 6\r
114         } else {\r
115             return 0\r
116         }\r
117     }\r
118 \r
119     var IEVersion = IE()\r
120 \r
121     avalon = function (el) { //创建jQuery式的无new 实例化结构\r
122         return new avalon.init(el)\r
123     }\r
124 \r
125     /*视浏览器情况采用最快的异步回调*/\r
126     avalon.nextTick = new function () {// jshint ignore:line\r
127         var tickImmediate = window.setImmediate\r
128         var tickObserver = window.MutationObserver\r
129         var tickPost = W3C && window.postMessage\r
130         if (tickImmediate) {\r
131             return tickImmediate.bind(window)\r
132         }\r
133 \r
134         var queue = []\r
135 \r
136         function callback() {\r
137             var n = queue.length\r
138             for (var i = 0; i < n; i++) {\r
139                 queue[i]()\r
140             }\r
141             queue = queue.slice(n)\r
142         }\r
143 \r
144         if (tickObserver) {\r
145             var node = document.createTextNode("avalon")\r
146             new tickObserver(callback).observe(node, {characterData: true})// jshint ignore:line\r
147             return function (fn) {\r
148                 queue.push(fn)\r
149                 node.data = Math.random()\r
150             }\r
151         }\r
152 \r
153         if (tickPost) {\r
154             window.addEventListener("message", function (e) {\r
155                 var source = e.source\r
156                 if ((source === window || source === null) && e.data === "process-tick") {\r
157                     e.stopPropagation()\r
158                     callback()\r
159                 }\r
160             })\r
161 \r
162             return function (fn) {\r
163                 queue.push(fn)\r
164                 window.postMessage('process-tick', '*')\r
165             }\r
166         }\r
167 \r
168         return function (fn) {\r
169             setTimeout(fn, 0)\r
170         }\r
171     }// jshint ignore:line\r
172     /*********************************************************************\r
173      *                 avalon的静态方法定义区                              *\r
174      **********************************************************************/\r
175     avalon.init = function (el) {\r
176         this[0] = this.element = el\r
177     }\r
178     avalon.fn = avalon.prototype = avalon.init.prototype\r
179 \r
180     avalon.type = function (obj) { //取得目标的类型\r
181         if (obj == null) {\r
182             return String(obj)\r
183         }\r
184         // 早期的webkit内核浏览器实现了已废弃的ecma262v4标准,可以将正则字面量当作函数使用,因此typeof在判定正则时会返回function\r
185         return typeof obj === "object" || typeof obj === "function" ?\r
186         class2type[serialize.call(obj)] || "object" :\r
187             typeof obj\r
188     }\r
189 \r
190     var isFunction = function (fn) {\r
191         return serialize.call(fn) === "[object Function]"\r
192     }\r
193 \r
194     avalon.isFunction = isFunction\r
195 \r
196     avalon.isWindow = function (obj) {\r
197         return rwindow.test(serialize.call(obj))\r
198     }\r
199 \r
200     /*判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自定义类的实例*/\r
201 \r
202     avalon.isPlainObject = function (obj) {\r
203         // 简单的 typeof obj === "object"检测,会致使用isPlainObject(window)在opera下通不过\r
204         return serialize.call(obj) === "[object Object]" && Object.getPrototypeOf(obj) === oproto\r
205     }\r
206 \r
207 //与jQuery.extend方法,可用于浅拷贝,深拷贝\r
208     avalon.mix = avalon.fn.mix = function () {\r
209         var options, name, src, copy, copyIsArray, clone,\r
210             target = arguments[0] || {},\r
211             i = 1,\r
212             length = arguments.length,\r
213             deep = false\r
214 \r
215         // 如果第一个参数为布尔,判定是否深拷贝\r
216         if (typeof target === "boolean") {\r
217             deep = target\r
218             target = arguments[1] || {}\r
219             i++\r
220         }\r
221 \r
222         //确保接受方为一个复杂的数据类型\r
223         if (typeof target !== "object" && !isFunction(target)) {\r
224             target = {}\r
225         }\r
226 \r
227         //如果只有一个参数,那么新成员添加于mix所在的对象上\r
228         if (i === length) {\r
229             target = this\r
230             i--\r
231         }\r
232 \r
233         for (; i < length; i++) {\r
234             //只处理非空参数\r
235             if ((options = arguments[i]) != null) {\r
236                 for (name in options) {\r
237                     src = target[name]\r
238                     copy = options[name]\r
239                     // 防止环引用\r
240                     if (target === copy) {\r
241                         continue\r
242                     }\r
243                     if (deep && copy && (avalon.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {\r
244 \r
245                         if (copyIsArray) {\r
246                             copyIsArray = false\r
247                             clone = src && Array.isArray(src) ? src : []\r
248 \r
249                         } else {\r
250                             clone = src && avalon.isPlainObject(src) ? src : {}\r
251                         }\r
252 \r
253                         target[name] = avalon.mix(deep, clone, copy)\r
254                     } else if (copy !== void 0) {\r
255                         target[name] = copy\r
256                     }\r
257                 }\r
258             }\r
259         }\r
260         return target\r
261     }\r
262 \r
263     function _number(a, len) { //用于模拟slice, splice的效果\r
264         a = Math.floor(a) || 0\r
265         return a < 0 ? Math.max(len + a, 0) : Math.min(a, len);\r
266     }\r
267 \r
268     avalon.mix({\r
269         rword: rword,\r
270         subscribers: subscribers,\r
271         version: 1.43,\r
272         ui: {},\r
273         log: log,\r
274         slice: function (nodes, start, end) {\r
275             return aslice.call(nodes, start, end)\r
276         },\r
277         noop: noop,\r
278         /*如果不用Error对象封装一下,str在控制台下可能会乱码*/\r
279         error: function (str, e) {\r
280             throw new (e || Error)(str)// jshint ignore:line\r
281         },\r
282         /*将一个以空格或逗号隔开的字符串或数组,转换成一个键值都为1的对象*/\r
283         oneObject: oneObject,\r
284         /* avalon.range(10)\r
285          => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\r
286          avalon.range(1, 11)\r
287          => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\r
288          avalon.range(0, 30, 5)\r
289          => [0, 5, 10, 15, 20, 25]\r
290          avalon.range(0, -10, -1)\r
291          => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]\r
292          avalon.range(0)\r
293          => []*/\r
294         range: function (start, end, step) { // 用于生成整数数组\r
295             step || (step = 1)\r
296             if (end == null) {\r
297                 end = start || 0\r
298                 start = 0\r
299             }\r
300             var index = -1,\r
301                 length = Math.max(0, Math.ceil((end - start) / step)),\r
302                 result = new Array(length)\r
303             while (++index < length) {\r
304                 result[index] = start\r
305                 start += step\r
306             }\r
307             return result\r
308         },\r
309         eventHooks: {},\r
310         /*绑定事件*/\r
311         bind: function (el, type, fn, phase) {\r
312             var hooks = avalon.eventHooks\r
313             var hook = hooks[type]\r
314             if (typeof hook === "object") {\r
315                 type = hook.type\r
316                 if (hook.deel) {\r
317                     fn = hook.deel(el, type, fn, phase)\r
318                 }\r
319             }\r
320             if (!fn.unbind)\r
321                 el.addEventListener(type, fn, !!phase)\r
322             return fn\r
323         },\r
324         /*卸载事件*/\r
325         unbind: function (el, type, fn, phase) {\r
326             var hooks = avalon.eventHooks\r
327             var hook = hooks[type]\r
328             var callback = fn || noop\r
329             if (typeof hook === "object") {\r
330                 type = hook.type\r
331                 if (hook.deel) {\r
332                     fn = hook.deel(el, type, fn, false)\r
333                 }\r
334             }\r
335             el.removeEventListener(type, callback, !!phase)\r
336         },\r
337         /*读写删除元素节点的样式*/\r
338         css: function (node, name, value) {\r
339             if (node instanceof avalon) {\r
340                 node = node[0]\r
341             }\r
342             var prop = /[_-]/.test(name) ? camelize(name) : name, fn\r
343             name = avalon.cssName(prop) || prop\r
344             if (value === void 0 || typeof value === "boolean") { //获取样式\r
345                 fn = cssHooks[prop + ":get"] || cssHooks["@:get"]\r
346                 if (name === "background") {\r
347                     name = "backgroundColor"\r
348                 }\r
349                 var val = fn(node, name)\r
350                 return value === true ? parseFloat(val) || 0 : val\r
351             } else if (value === "") { //请除样式\r
352                 node.style[name] = ""\r
353             } else { //设置样式\r
354                 if (value == null || value !== value) {\r
355                     return\r
356                 }\r
357                 if (isFinite(value) && !avalon.cssNumber[prop]) {\r
358                     value += "px"\r
359                 }\r
360                 fn = cssHooks[prop + ":set"] || cssHooks["@:set"]\r
361                 fn(node, name, value)\r
362             }\r
363         },\r
364         /*遍历数组与对象,回调的第一个参数为索引或键名,第二个或元素或键值*/\r
365         each: function (obj, fn) {\r
366             if (obj) { //排除null, undefined\r
367                 var i = 0\r
368                 if (isArrayLike(obj)) {\r
369                     for (var n = obj.length; i < n; i++) {\r
370                         if (fn(i, obj[i]) === false)\r
371                             break\r
372                     }\r
373                 } else {\r
374                     for (i in obj) {\r
375                         if (obj.hasOwnProperty(i) && fn(i, obj[i]) === false) {\r
376                             break\r
377                         }\r
378                     }\r
379                 }\r
380             }\r
381         },\r
382         //收集元素的data-{{prefix}}-*属性,并转换为对象\r
383         getWidgetData: function (elem, prefix) {\r
384             var raw = avalon(elem).data()\r
385             var result = {}\r
386             for (var i in raw) {\r
387                 if (i.indexOf(prefix) === 0) {\r
388                     result[i.replace(prefix, "").replace(/\w/, function (a) {\r
389                         return a.toLowerCase()\r
390                     })] = raw[i]\r
391                 }\r
392             }\r
393             return result\r
394         },\r
395         Array: {\r
396             /*只有当前数组不存在此元素时只添加它*/\r
397             ensure: function (target, item) {\r
398                 if (target.indexOf(item) === -1) {\r
399                     return target.push(item)\r
400                 }\r
401             },\r
402             /*移除数组中指定位置的元素,返回布尔表示成功与否*/\r
403             removeAt: function (target, index) {\r
404                 return !!target.splice(index, 1).length\r
405             },\r
406             /*移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否*/\r
407             remove: function (target, item) {\r
408                 var index = target.indexOf(item)\r
409                 if (~index)\r
410                     return avalon.Array.removeAt(target, index)\r
411                 return false\r
412             }\r
413         }\r
414     })\r
415 \r
416     var bindingHandlers = avalon.bindingHandlers = {}\r
417     var bindingExecutors = avalon.bindingExecutors = {}\r
418 \r
419     /*判定是否类数组,如节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象*/\r
420     function isArrayLike(obj) {\r
421         if (obj && typeof obj === "object") {\r
422             var n = obj.length,\r
423                 str = serialize.call(obj)\r
424             if (/(Array|List|Collection|Map|Arguments)\]$/.test(str)) {\r
425                 return true\r
426             } else if (str === "[object Object]" && n === (n >>> 0)) {\r
427                 return true //由于ecma262v5能修改对象属性的enumerable,因此不能用propertyIsEnumerable来判定了\r
428             }\r
429         }\r
430         return false\r
431     }\r
432 \r
433 \r
434 // https://github.com/rsms/js-lru\r
435     var Cache = new function () {// jshint ignore:line\r
436         function LRU(maxLength) {\r
437             this.size = 0\r
438             this.limit = maxLength\r
439             this.head = this.tail = void 0\r
440             this._keymap = {}\r
441         }\r
442 \r
443         var p = LRU.prototype\r
444 \r
445         p.put = function (key, value) {\r
446             var entry = {\r
447                 key: key,\r
448                 value: value\r
449             }\r
450             this._keymap[key] = entry\r
451             if (this.tail) {\r
452                 this.tail.newer = entry\r
453                 entry.older = this.tail\r
454             } else {\r
455                 this.head = entry\r
456             }\r
457             this.tail = entry\r
458             if (this.size === this.limit) {\r
459                 this.shift()\r
460             } else {\r
461                 this.size++\r
462             }\r
463             return value\r
464         }\r
465 \r
466         p.shift = function () {\r
467             var entry = this.head\r
468             if (entry) {\r
469                 this.head = this.head.newer\r
470                 this.head.older =\r
471                     entry.newer =\r
472                         entry.older =\r
473                             this._keymap[entry.key] = void 0\r
474             }\r
475         }\r
476         p.get = function (key) {\r
477             var entry = this._keymap[key]\r
478             if (entry === void 0)\r
479                 return\r
480             if (entry === this.tail) {\r
481                 return entry.value\r
482             }\r
483             // HEAD--------------TAIL\r
484             //   <.older   .newer>\r
485             //  <--- add direction --\r
486             //   A  B  C  <D>  E\r
487             if (entry.newer) {\r
488                 if (entry === this.head) {\r
489                     this.head = entry.newer\r
490                 }\r
491                 entry.newer.older = entry.older // C <-- E.\r
492             }\r
493             if (entry.older) {\r
494                 entry.older.newer = entry.newer // C. --> E\r
495             }\r
496             entry.newer = void 0 // D --x\r
497             entry.older = this.tail // D. --> E\r
498             if (this.tail) {\r
499                 this.tail.newer = entry // E. <-- D\r
500             }\r
501             this.tail = entry\r
502             return entry.value\r
503         }\r
504         return LRU\r
505     }// jshint ignore:line\r
506 \r
507     /*********************************************************************\r
508      *                           DOM 底层补丁                             *\r
509      **********************************************************************/\r
510 //safari5+是把contains方法放在Element.prototype上而不是Node.prototype\r
511     if (!DOC.contains) {\r
512         Node.prototype.contains = function (arg) {\r
513             return !!(this.compareDocumentPosition(arg) & 16)\r
514         }\r
515     }\r
516     avalon.contains = function (root, el) {\r
517         try {\r
518             while ((el = el.parentNode))\r
519                 if (el === root)\r
520                     return true\r
521             return false\r
522         } catch (e) {\r
523             return false\r
524         }\r
525     }\r
526 \r
527     if (window.SVGElement) {\r
528         var svgns = "http://www.w3.org/2000/svg"\r
529         var svg = DOC.createElementNS(svgns, "svg")\r
530         svg.innerHTML = '<circle cx="50" cy="50" r="40" fill="red" />'\r
531         if (!rsvg.test(svg.firstChild)) {// #409\r
532             /* jshint ignore:start */\r
533             function enumerateNode(node, targetNode) {\r
534                 if (node && node.childNodes) {\r
535                     var nodes = node.childNodes\r
536                     for (var i = 0, el; el = nodes[i++];) {\r
537                         if (el.tagName) {\r
538                             var svg = DOC.createElementNS(svgns,\r
539                                 el.tagName.toLowerCase())\r
540                             // copy attrs\r
541                             ap.forEach.call(el.attributes, function (attr) {\r
542                                 svg.setAttribute(attr.name, attr.value)\r
543                             })\r
544                             // 递归处理子节点\r
545                             enumerateNode(el, svg)\r
546                             targetNode.appendChild(svg)\r
547                         }\r
548                     }\r
549                 }\r
550             }\r
551 \r
552             /* jshint ignore:end */\r
553             Object.defineProperties(SVGElement.prototype, {\r
554                 "outerHTML": {//IE9-11,firefox不支持SVG元素的innerHTML,outerHTML属性\r
555                     enumerable: true,\r
556                     configurable: true,\r
557                     get: function () {\r
558                         return new XMLSerializer().serializeToString(this)\r
559                     },\r
560                     set: function (html) {\r
561                         var tagName = this.tagName.toLowerCase(),\r
562                             par = this.parentNode,\r
563                             frag = avalon.parseHTML(html)\r
564                         // 操作的svg,直接插入\r
565                         if (tagName === "svg") {\r
566                             par.insertBefore(frag, this)\r
567                             // svg节点的子节点类似\r
568                         } else {\r
569                             var newFrag = DOC.createDocumentFragment()\r
570                             enumerateNode(frag, newFrag)\r
571                             par.insertBefore(newFrag, this)\r
572                         }\r
573                         par.removeChild(this)\r
574                     }\r
575                 },\r
576                 "innerHTML": {\r
577                     enumerable: true,\r
578                     configurable: true,\r
579                     get: function () {\r
580                         var s = this.outerHTML\r
581                         var ropen = new RegExp("<" + this.nodeName + '\\b(?:(["\'])[^"]*?(\\1)|[^>])*>', "i")\r
582                         var rclose = new RegExp("<\/" + this.nodeName + ">$", "i")\r
583                         return s.replace(ropen, "").replace(rclose, "")\r
584                     },\r
585                     set: function (html) {\r
586                         if (avalon.clearHTML) {\r
587                             avalon.clearHTML(this)\r
588                             var frag = avalon.parseHTML(html)\r
589                             enumerateNode(frag, this)\r
590                         }\r
591                     }\r
592                 }\r
593             })\r
594         }\r
595     }\r
596 //========================= event binding ====================\r
597     var eventHooks = avalon.eventHooks\r
598 //针对firefox, chrome修正mouseenter, mouseleave(chrome30+)\r
599     if (!("onmouseenter" in root)) {\r
600         avalon.each({\r
601             mouseenter: "mouseover",\r
602             mouseleave: "mouseout"\r
603         }, function (origType, fixType) {\r
604             eventHooks[origType] = {\r
605                 type: fixType,\r
606                 deel: function (elem, _, fn) {\r
607                     return function (e) {\r
608                         var t = e.relatedTarget\r
609                         if (!t || (t !== elem && !(elem.compareDocumentPosition(t) & 16))) {\r
610                             delete e.type\r
611                             e.type = origType\r
612                             return fn.call(elem, e)\r
613                         }\r
614                     }\r
615                 }\r
616             }\r
617         })\r
618     }\r
619 //针对IE9+, w3c修正animationend\r
620     avalon.each({\r
621         AnimationEvent: "animationend",\r
622         WebKitAnimationEvent: "webkitAnimationEnd"\r
623     }, function (construct, fixType) {\r
624         if (window[construct] && !eventHooks.animationend) {\r
625             eventHooks.animationend = {\r
626                 type: fixType\r
627             }\r
628         }\r
629     })\r
630 \r
631     if (DOC.onmousewheel === void 0) {\r
632         /* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120\r
633          firefox DOMMouseScroll detail 下3 上-3\r
634          firefox wheel detlaY 下3 上-3\r
635          IE9-11 wheel deltaY 下40 上-40\r
636          chrome wheel deltaY 下100 上-100 */\r
637         eventHooks.mousewheel = {\r
638             type: "wheel",\r
639             deel: function (elem, _, fn) {\r
640                 return function (e) {\r
641                     e.wheelDeltaY = e.wheelDelta = e.deltaY > 0 ? -120 : 120\r
642                     e.wheelDeltaX = 0\r
643                     Object.defineProperty(e, "type", {\r
644                         value: "mousewheel"\r
645                     })\r
646                     fn.call(elem, e)\r
647                 }\r
648             }\r
649         }\r
650     }\r
651 \r
652     /*********************************************************************\r
653      *                           配置系统                                 *\r
654      **********************************************************************/\r
655 \r
656     function kernel(settings) {\r
657         for (var p in settings) {\r
658             if (!ohasOwn.call(settings, p))\r
659                 continue\r
660             var val = settings[p]\r
661             if (typeof kernel.plugins[p] === "function") {\r
662                 kernel.plugins[p](val)\r
663             } else if (typeof kernel[p] === "object") {\r
664                 avalon.mix(kernel[p], val)\r
665             } else {\r
666                 kernel[p] = val\r
667             }\r
668         }\r
669         return this\r
670     }\r
671 \r
672     var openTag, closeTag, rexpr, rexprg, rbind, rregexp = /[-.*+?^${}()|[\]\/\\]/g\r
673 \r
674     function escapeRegExp(target) {\r
675         //http://stevenlevithan.com/regex/xregexp/\r
676         //将字符串安全格式化为正则表达式的源码\r
677         return (target + "").replace(rregexp, "\\$&")\r
678     }\r
679 \r
680     var plugins = {\r
681         loader: function (builtin) {\r
682             var flag = innerRequire && builtin\r
683             window.require = flag ? innerRequire : otherRequire\r
684             window.define = flag ? innerRequire.define : otherDefine\r
685         },\r
686         interpolate: function (array) {\r
687             openTag = array[0]\r
688             closeTag = array[1]\r
689             if (openTag === closeTag) {\r
690                 throw new SyntaxError("openTag!==closeTag")\r
691             } else if (array + "" === "<!--,-->") {\r
692                 kernel.commentInterpolate = true\r
693             } else {\r
694                 var test = openTag + "test" + closeTag\r
695                 cinerator.innerHTML = test\r
696                 if (cinerator.innerHTML !== test && cinerator.innerHTML.indexOf("&lt;") > -1) {\r
697                     throw new SyntaxError("此定界符不合法")\r
698                 }\r
699                 cinerator.innerHTML = ""\r
700             }\r
701             var o = escapeRegExp(openTag),\r
702                 c = escapeRegExp(closeTag)\r
703             rexpr = new RegExp(o + "(.*?)" + c)\r
704             rexprg = new RegExp(o + "(.*?)" + c, "g")\r
705             rbind = new RegExp(o + ".*?" + c + "|\\sms-")\r
706         }\r
707     }\r
708 \r
709     kernel.debug = true\r
710     kernel.plugins = plugins\r
711     kernel.plugins['interpolate'](["{{", "}}"])\r
712     kernel.paths = {}\r
713     kernel.shim = {}\r
714     kernel.maxRepeatSize = 100\r
715     avalon.config = kernel\r
716     var ravalon = /(\w+)\[(avalonctrl)="(\S+)"\]/\r
717     var findNodes = function (str) {\r
718         return DOC.querySelectorAll(str)\r
719     }\r
720     /*********************************************************************\r
721      *                            事件总线                               *\r
722      **********************************************************************/\r
723     var EventBus = {\r
724         $watch: function (type, callback) {\r
725             if (typeof callback === "function") {\r
726                 var callbacks = this.$events[type]\r
727                 if (callbacks) {\r
728                     callbacks.push(callback)\r
729                 } else {\r
730                     this.$events[type] = [callback]\r
731                 }\r
732             } else { //重新开始监听此VM的第一重简单属性的变动\r
733                 this.$events = this.$watch.backup\r
734             }\r
735             return this\r
736         },\r
737         $unwatch: function (type, callback) {\r
738             var n = arguments.length\r
739             if (n === 0) { //让此VM的所有$watch回调无效化\r
740                 this.$watch.backup = this.$events\r
741                 this.$events = {}\r
742             } else if (n === 1) {\r
743                 this.$events[type] = []\r
744             } else {\r
745                 var callbacks = this.$events[type] || []\r
746                 var i = callbacks.length\r
747                 while (~--i < 0) {\r
748                     if (callbacks[i] === callback) {\r
749                         return callbacks.splice(i, 1)\r
750                     }\r
751                 }\r
752             }\r
753             return this\r
754         },\r
755         $fire: function (type) {\r
756             var special, i, v, callback\r
757             if (/^(\w+)!(\S+)$/.test(type)) {\r
758                 special = RegExp.$1\r
759                 type = RegExp.$2\r
760             }\r
761             var events = this.$events\r
762             if (!events)\r
763                 return\r
764             var args = aslice.call(arguments, 1)\r
765             var detail = [type].concat(args)\r
766             if (special === "all") {\r
767                 for (i in avalon.vmodels) {\r
768                     v = avalon.vmodels[i]\r
769                     if (v !== this) {\r
770                         v.$fire.apply(v, detail)\r
771                     }\r
772                 }\r
773             } else if (special === "up" || special === "down") {\r
774                 var elements = events.expr ? findNodes(events.expr) : []\r
775                 if (elements.length === 0)\r
776                     return\r
777                 for (i in avalon.vmodels) {\r
778                     v = avalon.vmodels[i]\r
779                     if (v !== this) {\r
780                         if (v.$events.expr) {\r
781                             var eventNodes = findNodes(v.$events.expr)\r
782                             if (eventNodes.length === 0) {\r
783                                 continue\r
784                             }\r
785                             //循环两个vmodel中的节点,查找匹配(向上匹配或者向下匹配)的节点并设置标识\r
786                             /* jshint ignore:start */\r
787                             Array.prototype.forEach.call(eventNodes, function (node) {\r
788                                 Array.prototype.forEach.call(elements, function (element) {\r
789                                     var ok = special === "down" ? element.contains(node) : //向下捕获\r
790                                         node.contains(element) //向上冒泡\r
791                                     if (ok) {\r
792                                         node._avalon = v //符合条件的加一个标识\r
793                                     }\r
794                                 });\r
795                             })\r
796                             /* jshint ignore:end */\r
797                         }\r
798                     }\r
799                 }\r
800                 var nodes = DOC.getElementsByTagName("*") //实现节点排序\r
801                 var alls = []\r
802                 Array.prototype.forEach.call(nodes, function (el) {\r
803                     if (el._avalon) {\r
804                         alls.push(el._avalon)\r
805                         el._avalon = ""\r
806                         el.removeAttribute("_avalon")\r
807                     }\r
808                 })\r
809                 if (special === "up") {\r
810                     alls.reverse()\r
811                 }\r
812                 for (i = 0; callback = alls[i++];) {\r
813                     if (callback.$fire.apply(callback, detail) === false) {\r
814                         break\r
815                     }\r
816                 }\r
817             } else {\r
818                 var callbacks = events[type] || []\r
819                 var all = events.$all || []\r
820                 for (i = 0; callback = callbacks[i++];) {\r
821                     if (isFunction(callback))\r
822                         callback.apply(this, args)\r
823                 }\r
824                 for (i = 0; callback = all[i++];) {\r
825                     if (isFunction(callback))\r
826                         callback.apply(this, arguments)\r
827                 }\r
828             }\r
829         }\r
830     }\r
831 \r
832     /*********************************************************************\r
833      *                           modelFactory                             *\r
834      **********************************************************************/\r
835 //avalon最核心的方法的两个方法之一(另一个是avalon.scan),返回一个ViewModel(VM)\r
836     var VMODELS = avalon.vmodels = createMap() //所有vmodel都储存在这里\r
837     avalon.define = function (id, factory) {\r
838         var $id = id.$id || id\r
839         if (!$id) {\r
840             log("warning: vm必须指定$id")\r
841         }\r
842         if (VMODELS[$id]) {\r
843             log("warning: " + $id + " 已经存在于avalon.vmodels中")\r
844         }\r
845         if (typeof id === "object") {\r
846             var model = modelFactory(id)\r
847         } else {\r
848             var scope = {\r
849                 $watch: noop\r
850             }\r
851             factory(scope) //得到所有定义\r
852             model = modelFactory(scope) //偷天换日,将scope换为model\r
853             stopRepeatAssign = true\r
854             factory(model)\r
855             stopRepeatAssign = false\r
856         }\r
857         model.$id = $id\r
858         return VMODELS[$id] = model\r
859     }\r
860 \r
861 //一些不需要被监听的属性\r
862     var $$skipArray = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray").match(rword)\r
863 \r
864     function isObservable(name, value, $skipArray) {\r
865         if (isFunction(value) || value && value.nodeType) {\r
866             return false\r
867         }\r
868         if ($skipArray.indexOf(name) !== -1) {\r
869             return false\r
870         }\r
871         if ($$skipArray.indexOf(name) !== -1) {\r
872             return false\r
873         }\r
874         var $special = $skipArray.$special\r
875         if (name && name.charAt(0) === "$" && !$special[name]) {\r
876             return false\r
877         }\r
878         return true\r
879     }\r
880 \r
881 //ms-with,ms-each, ms-repeat绑定生成的代理对象储存池\r
882     var midway = createMap()\r
883 \r
884     function getNewValue(accessor, name, value, $vmodel) {\r
885         switch (accessor.type) {\r
886             case 0://计算属性\r
887                 var getter = accessor.get\r
888                 var setter = accessor.set\r
889                 if (isFunction(setter)) {\r
890                     var $events = $vmodel.$events\r
891                     var lock = $events[name]\r
892                     $events[name] = [] //清空回调,防止内部冒泡而触发多次$fire\r
893                     setter.call($vmodel, value)\r
894                     $events[name] = lock\r
895                 }\r
896                 return getter.call($vmodel) //同步$model\r
897             case 1://监控属性\r
898                 return value\r
899             case 2://对象属性(包括数组与哈希)\r
900                 if (value !== $vmodel.$model[name]) {\r
901                     var svmodel = accessor.svmodel = objectFactory($vmodel, name, value, accessor.valueType)\r
902                     value = svmodel.$model //同步$model\r
903                     var fn = midway[svmodel.$id]\r
904                     fn && fn() //同步视图\r
905                 }\r
906                 return value\r
907         }\r
908     }\r
909 \r
910     function modelFactory(source, $special, $model) {\r
911         if (Array.isArray(source)) {\r
912             var arr = source.concat()\r
913             source.length = 0\r
914             var collection = Collection(source)// jshint ignore:line\r
915             collection.pushArray(arr)\r
916             return collection\r
917         }\r
918         //0 null undefined || Node || VModel\r
919         if (!source || source.nodeType > 0 || (source.$id && source.$events)) {\r
920             return source\r
921         }\r
922         if (!Array.isArray(source.$skipArray)) {\r
923             source.$skipArray = []\r
924         }\r
925         source.$skipArray.$special = $special || createMap() //强制要监听的属性\r
926         var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤\r
927         $model = $model || {} //vmodels.$model属性\r
928         var $events = createMap() //vmodel.$events属性\r
929         var watchedProperties = createMap() //监控属性\r
930         var initCallbacks = [] //初始化才执行的函数\r
931         for (var i in source) {\r
932             (function (name, val) {\r
933                 $model[name] = val\r
934                 if (!isObservable(name, val, source.$skipArray)) {\r
935                     return //过滤所有非监控属性\r
936                 }\r
937                 //总共产生三种accessor\r
938                 $events[name] = []\r
939                 var valueType = avalon.type(val)\r
940                 var accessor = function (newValue) {\r
941                     var name = accessor._name\r
942                     var $vmodel = this\r
943                     var $model = $vmodel.$model\r
944                     var oldValue = $model[name]\r
945                     var $events = $vmodel.$events\r
946 \r
947                     if (arguments.length) {\r
948                         if (stopRepeatAssign) {\r
949                             return\r
950                         }\r
951                         //计算属性与对象属性需要重新计算newValue\r
952                         if (accessor.type !== 1) {\r
953                             newValue = getNewValue(accessor, name, newValue, $vmodel)\r
954                             if (!accessor.type)\r
955                                 return\r
956                         }\r
957                         if (!isEqual(oldValue, newValue)) {\r
958                             $model[name] = newValue\r
959                             notifySubscribers($events[name]) //同步视图\r
960                             safeFire($vmodel, name, newValue, oldValue) //触发$watch回调\r
961                         }\r
962                     } else {\r
963                         if (accessor.type === 0) { //type 0 计算属性 1 监控属性 2 对象属性\r
964                             //计算属性不需要收集视图刷新函数,都是由其他监控属性代劳\r
965                             newValue = accessor.get.call($vmodel)\r
966                             if (oldValue !== newValue) {\r
967                                 $model[name] = newValue\r
968                                 //这里不用同步视图\r
969                                 safeFire($vmodel, name, newValue, oldValue) //触发$watch回调\r
970                             }\r
971                             return newValue\r
972                         } else {\r
973                             collectSubscribers($events[name]) //收集视图函数\r
974                             return accessor.svmodel || oldValue\r
975                         }\r
976                     }\r
977                 }\r
978                 //总共产生三种accessor\r
979                 if (valueType === "object" && isFunction(val.get) && Object.keys(val).length <= 2) {\r
980                     //第1种为计算属性, 因变量,通过其他监控属性触发其改变\r
981                     accessor.set = val.set\r
982                     accessor.get = val.get\r
983                     accessor.type = 0\r
984                     initCallbacks.push(function () {\r
985                         var data = {\r
986                             evaluator: function () {\r
987                                 data.type = Math.random(),\r
988                                     data.element = null\r
989                                 $model[name] = accessor.get.call($vmodel)\r
990                             },\r
991                             element: head,\r
992                             type: Math.random(),\r
993                             handler: noop,\r
994                             args: []\r
995                         }\r
996                         Registry[expose] = data\r
997                         accessor.call($vmodel)\r
998                         delete Registry[expose]\r
999                     })\r
1000                 } else if (rcomplexType.test(valueType)) {\r
1001                     //第2种为对象属性,产生子VM与监控数组\r
1002                     accessor.type = 2\r
1003                     accessor.valueType = valueType\r
1004                     initCallbacks.push(function () {\r
1005                         var svmodel = modelFactory(val, 0, $model[name])\r
1006                         accessor.svmodel = svmodel\r
1007                         svmodel.$events[subscribers] = $events[name]\r
1008                     })\r
1009                 } else {\r
1010                     accessor.type = 1\r
1011                     //第3种为监控属性,对应简单的数据类型,自变量\r
1012                 }\r
1013                 accessor._name = name\r
1014                 watchedProperties[name] = accessor\r
1015             })(i, source[i])// jshint ignore:line\r
1016         }\r
1017 \r
1018         $$skipArray.forEach(function (name) {\r
1019             delete source[name]\r
1020             delete $model[name] //这些特殊属性不应该在$model中出现\r
1021         })\r
1022 \r
1023         $vmodel = Object.defineProperties($vmodel, descriptorFactory(watchedProperties), source) //生成一个空的ViewModel\r
1024         for (var name in source) {\r
1025             if (!watchedProperties[name]) {\r
1026                 $vmodel[name] = source[name]\r
1027             }\r
1028         }\r
1029         //添加$id, $model, $events, $watch, $unwatch, $fire\r
1030         $vmodel.$id = generateID()\r
1031         $vmodel.$model = $model\r
1032         $vmodel.$events = $events\r
1033         for (i in EventBus) {\r
1034             $vmodel[i] = EventBus[i]\r
1035         }\r
1036 \r
1037         Object.defineProperty($vmodel, "hasOwnProperty", {\r
1038             value: function (name) {\r
1039                 return name in this.$model\r
1040             },\r
1041             writable: false,\r
1042             enumerable: false,\r
1043             configurable: true\r
1044         })\r
1045 \r
1046         initCallbacks.forEach(function (cb) { //收集依赖\r
1047             cb()\r
1048         })\r
1049         return $vmodel\r
1050     }\r
1051 \r
1052 //比较两个值是否相等\r
1053     var isEqual = Object.is || function (v1, v2) {\r
1054             if (v1 === 0 && v2 === 0) {\r
1055                 return 1 / v1 === 1 / v2\r
1056             } else if (v1 !== v1) {\r
1057                 return v2 !== v2\r
1058             } else {\r
1059                 return v1 === v2\r
1060             }\r
1061         }\r
1062 \r
1063     function safeFire(a, b, c, d) {\r
1064         if (a.$events) {\r
1065             EventBus.$fire.call(a, b, c, d)\r
1066         }\r
1067     }\r
1068 \r
1069     var descriptorFactory = function (obj) {\r
1070         var descriptors = createMap()\r
1071         for (var i in obj) {\r
1072             descriptors[i] = {\r
1073                 get: obj[i],\r
1074                 set: obj[i],\r
1075                 enumerable: true,\r
1076                 configurable: true\r
1077             }\r
1078         }\r
1079         return descriptors\r
1080     }\r
1081 \r
1082 //应用于第2种accessor\r
1083     function objectFactory(parent, name, value, valueType) {\r
1084         //a为原来的VM, b为新数组或新对象\r
1085         var son = parent[name]\r
1086         if (valueType === "array") {\r
1087             if (!Array.isArray(value) || son === value) {\r
1088                 return son //fix https://github.com/RubyLouvre/avalon/issues/261\r
1089             }\r
1090             son._.$unwatch()\r
1091             son.clear()\r
1092             son._.$watch()\r
1093             son.pushArray(value.concat())\r
1094             return son\r
1095         } else {\r
1096             var iterators = parent.$events[name]\r
1097             var ret = modelFactory(value)\r
1098             ret.$events[subscribers] = iterators\r
1099             midway[ret.$id] = function (data) {\r
1100                 while (data = iterators.shift()) {\r
1101                     (function (el) {\r
1102                         avalon.nextTick(function () {\r
1103                             var type = el.type\r
1104                             if (type && bindingHandlers[type]) { //#753\r
1105                                 el.rollback && el.rollback() //还原 ms-with ms-on\r
1106                                 bindingHandlers[type](el, el.vmodels)\r
1107                             }\r
1108                         })\r
1109                     })(data)// jshint ignore:line\r
1110                 }\r
1111                 delete midway[ret.$id]\r
1112             }\r
1113             return ret\r
1114         }\r
1115     }\r
1116 \r
1117     /*********************************************************************\r
1118      *          监控数组(与ms-each, ms-repeat配合使用)                     *\r
1119      **********************************************************************/\r
1120 \r
1121     function Collection(model) {\r
1122         var array = []\r
1123         array.$id = generateID()\r
1124         array.$model = model //数据模型\r
1125         array.$events = {}\r
1126         array.$events[subscribers] = []\r
1127         array._ = modelFactory({\r
1128             length: model.length\r
1129         })\r
1130         array._.$watch("length", function (a, b) {\r
1131             array.$fire("length", a, b)\r
1132         })\r
1133         for (var i in EventBus) {\r
1134             array[i] = EventBus[i]\r
1135         }\r
1136         array.$map = {\r
1137             el: 1\r
1138         }\r
1139         array.$proxy = []\r
1140         avalon.mix(array, CollectionPrototype)\r
1141         return array\r
1142     }\r
1143 \r
1144     function mutateArray(method, pos, n, index, method2, pos2, n2) {\r
1145         var oldLen = this.length, loop = 2\r
1146         while (--loop) {\r
1147             switch (method) {\r
1148                 case "add":\r
1149                     /* jshint ignore:start */\r
1150                     var m = pos + n\r
1151                     var array = this.$model.slice(pos, m).map(function (el) {\r
1152                         if (rcomplexType.test(avalon.type(el))) {//转换为VM\r
1153                             return el.$id ? el : modelFactory(el, 0, el)\r
1154                         } else {\r
1155                             return el\r
1156                         }\r
1157                     })\r
1158                     /* jshint ignore:end */\r
1159                     for (var i = pos; i < m; i++) {//生成代理VM\r
1160                         var proxy = eachProxyAgent(i, this)\r
1161                         this.$proxy.splice(i, 0, proxy)\r
1162                     }\r
1163                     _splice.apply(this, [pos, 0].concat(array))\r
1164                     this._fire("add", pos, n)\r
1165                     break\r
1166                 case "del":\r
1167                     var ret = this._splice(pos, n)\r
1168                     var removed = this.$proxy.splice(pos, n) //回收代理VM\r
1169                     recycleProxies(removed, "each")\r
1170                     this._fire("del", pos, n)\r
1171                     break\r
1172             }\r
1173             if (method2) {\r
1174                 method = method2\r
1175                 pos = pos2\r
1176                 n = n2\r
1177                 loop = 2\r
1178                 method2 = 0\r
1179             }\r
1180         }\r
1181         resetIndex(this.$proxy, index)\r
1182         if (this.length !== oldLen) {\r
1183             this._.length = this.length\r
1184         }\r
1185         return ret\r
1186     }\r
1187 \r
1188     var _splice = ap.splice\r
1189     var CollectionPrototype = {\r
1190         _splice: _splice,\r
1191         _fire: function (method, a, b) {\r
1192             notifySubscribers(this.$events[subscribers], method, a, b)\r
1193         },\r
1194         size: function () { //取得数组长度,这个函数可以同步视图,length不能\r
1195             return this._.length\r
1196         },\r
1197         pushArray: function (array) {\r
1198             var m = array.length, n = this.length\r
1199             if (m) {\r
1200                 ap.push.apply(this.$model, array)\r
1201                 mutateArray.call(this, "add", n, m, Math.max(0, n - 1))\r
1202             }\r
1203             return m + n\r
1204         },\r
1205         push: function () {\r
1206             //http://jsperf.com/closure-with-arguments\r
1207             var array = []\r
1208             var i, n = arguments.length\r
1209             for (i = 0; i < n; i++) {\r
1210                 array[i] = arguments[i]\r
1211             }\r
1212             return this.pushArray(array)\r
1213         },\r
1214         unshift: function () {\r
1215             var m = arguments.length, n = this.length\r
1216             if (m) {\r
1217                 ap.unshift.apply(this.$model, arguments)\r
1218                 mutateArray.call(this, "add", 0, m, 0)\r
1219             }\r
1220             return m + n //IE67的unshift不会返回长度\r
1221         },\r
1222         shift: function () {\r
1223             if (this.length) {\r
1224                 var el = this.$model.shift()\r
1225                 mutateArray.call(this, "del", 0, 1, 0)\r
1226                 return el //返回被移除的元素\r
1227             }\r
1228         },\r
1229         pop: function () {\r
1230             var n = this.length\r
1231             if (n) {\r
1232                 var el = this.$model.pop()\r
1233                 mutateArray.call(this, "del", n - 1, 1, Math.max(0, n - 2))\r
1234                 return el //返回被移除的元素\r
1235             }\r
1236         },\r
1237         splice: function (start) {\r
1238             var m = arguments.length, args = [], change\r
1239             var removed = _splice.apply(this.$model, arguments)\r
1240             if (removed.length) { //如果用户删掉了元素\r
1241                 args.push("del", start, removed.length, 0)\r
1242                 change = true\r
1243             }\r
1244             if (m > 2) {  //如果用户添加了元素\r
1245                 if (change) {\r
1246                     args.splice(3, 1, 0, "add", start, m - 2)\r
1247                 } else {\r
1248                     args.push("add", start, m - 2, 0)\r
1249                 }\r
1250                 change = true\r
1251             }\r
1252             if (change) { //返回被移除的元素\r
1253                 return mutateArray.apply(this, args)\r
1254             } else {\r
1255                 return []\r
1256             }\r
1257         },\r
1258         contains: function (el) { //判定是否包含\r
1259             return this.indexOf(el) !== -1\r
1260         },\r
1261         remove: function (el) { //移除第一个等于给定值的元素\r
1262             return this.removeAt(this.indexOf(el))\r
1263         },\r
1264         removeAt: function (index) { //移除指定索引上的元素\r
1265             if (index >= 0) {\r
1266                 this.$model.splice(index, 1)\r
1267                 return mutateArray.call(this, "del", index, 1, 0)\r
1268             }\r
1269             return []\r
1270         },\r
1271         clear: function () {\r
1272             recycleProxies(this.$proxy, "each")\r
1273             this.$model.length = this.$proxy.length = this.length = this._.length = 0 //清空数组\r
1274             this._fire("clear", 0)\r
1275             return this\r
1276         },\r
1277         removeAll: function (all) { //移除N个元素\r
1278             if (Array.isArray(all)) {\r
1279                 all.forEach(function (el) {\r
1280                     this.remove(el)\r
1281                 }, this)\r
1282             } else if (typeof all === "function") {\r
1283                 for (var i = this.length - 1; i >= 0; i--) {\r
1284                     var el = this[i]\r
1285                     if (all(el, i)) {\r
1286                         this.removeAt(i)\r
1287                     }\r
1288                 }\r
1289             } else {\r
1290                 this.clear()\r
1291             }\r
1292         },\r
1293         ensure: function (el) {\r
1294             if (!this.contains(el)) { //只有不存在才push\r
1295                 this.push(el)\r
1296             }\r
1297             return this\r
1298         },\r
1299         set: function (index, val) {\r
1300             if (index >= 0) {\r
1301                 var valueType = avalon.type(val)\r
1302                 if (val && val.$model) {\r
1303                     val = val.$model\r
1304                 }\r
1305                 var target = this[index]\r
1306                 if (valueType === "object") {\r
1307                     for (var i in val) {\r
1308                         if (target.hasOwnProperty(i)) {\r
1309                             target[i] = val[i]\r
1310                         }\r
1311                     }\r
1312                 } else if (valueType === "array") {\r
1313                     target.clear().push.apply(target, val)\r
1314                 } else if (target !== val) {\r
1315                     this[index] = val\r
1316                     this.$model[index] = val\r
1317                     var proxy = this.$proxy[index]\r
1318                     if (proxy) {\r
1319                         notifySubscribers(proxy.$events.el)\r
1320                     }\r
1321                     //  this._fire("set", index, val)\r
1322                 }\r
1323             }\r
1324             return this\r
1325         }\r
1326     }\r
1327 //相当于原来bindingExecutors.repeat 的index分支\r
1328     function resetIndex(array, pos) {\r
1329         var last = array.length - 1\r
1330         for (var el; el = array[pos]; pos++) {\r
1331             el.$index = pos\r
1332             el.$first = pos === 0\r
1333             el.$last = pos === last\r
1334         }\r
1335     }\r
1336 \r
1337     function sortByIndex(array, indexes) {\r
1338         var map = {};\r
1339         for (var i = 0, n = indexes.length; i < n; i++) {\r
1340             map[i] = array[i] // preserve\r
1341             var j = indexes[i]\r
1342             if (j in map) {\r
1343                 array[i] = map[j]\r
1344                 delete map[j]\r
1345             } else {\r
1346                 array[i] = array[j]\r
1347             }\r
1348         }\r
1349     }\r
1350 \r
1351     "sort,reverse".replace(rword, function (method) {\r
1352         CollectionPrototype[method] = function () {\r
1353             var newArray = this.$model//这是要排序的新数组\r
1354             var oldArray = newArray.concat() //保持原来状态的旧数组\r
1355             var mask = Math.random()\r
1356             var indexes = []\r
1357             var hasSort\r
1358             ap[method].apply(newArray, arguments) //排序\r
1359             for (var i = 0, n = oldArray.length; i < n; i++) {\r
1360                 var neo = newArray[i]\r
1361                 var old = oldArray[i]\r
1362                 if (isEqual(neo, old)) {\r
1363                     indexes.push(i)\r
1364                 } else {\r
1365                     var index = oldArray.indexOf(neo)\r
1366                     indexes.push(index)//得到新数组的每个元素在旧数组对应的位置\r
1367                     oldArray[index] = mask    //屏蔽已经找过的元素\r
1368                     hasSort = true\r
1369                 }\r
1370             }\r
1371             if (hasSort) {\r
1372                 sortByIndex(this, indexes)\r
1373                 sortByIndex(this.$proxy, indexes)\r
1374                 this._fire("move", indexes)\r
1375                 resetIndex(this.$proxy, 0)\r
1376             }\r
1377             return this\r
1378         }\r
1379     })\r
1380 \r
1381     /*********************************************************************\r
1382      *                           依赖调度系统                             *\r
1383      **********************************************************************/\r
1384     var ronduplex = /^(duplex|on)$/\r
1385 \r
1386     avalon.injectBinding = function (data) {\r
1387         Registry[expose] = data //暴光此函数,方便collectSubscribers收集\r
1388         avalon.openComputedCollect = true\r
1389         var fn = data.evaluator\r
1390         if (fn) { //如果是求值函数\r
1391             try {\r
1392                 var c = ronduplex.test(data.type) ? data : fn.apply(0, data.args)\r
1393                 if (!data.noRefresh)\r
1394                     data.handler(c, data.element, data)\r
1395             } catch (e) {\r
1396                 //log("warning:exception throwed in [avalon.injectBinding] " + e)\r
1397                 delete data.evaluator\r
1398                 var node = data.element\r
1399                 if (node.nodeType === 3) {\r
1400                     var parent = node.parentNode\r
1401                     if (kernel.commentInterpolate) {\r
1402                         parent.replaceChild(DOC.createComment(data.value), node)\r
1403                     } else {\r
1404                         node.data = openTag + data.value + closeTag\r
1405                     }\r
1406                 }\r
1407             }\r
1408         }\r
1409         avalon.openComputedCollect = false\r
1410         delete Registry[expose]\r
1411     }\r
1412 \r
1413     function collectSubscribers(list) { //收集依赖于这个访问器的订阅者\r
1414         var data = Registry[expose]\r
1415         if (list && data && avalon.Array.ensure(list, data) && data.element) { //只有数组不存在此元素才push进去\r
1416             addSubscribers(data, list)\r
1417         }\r
1418     }\r
1419 \r
1420 \r
1421     function addSubscribers(data, list) {\r
1422         data.$uuid = data.$uuid || generateID()\r
1423         list.$uuid = list.$uuid || generateID()\r
1424         var obj = {\r
1425             data: data,\r
1426             list: list,\r
1427             $$uuid: data.$uuid + list.$uuid\r
1428         }\r
1429         if (!$$subscribers[obj.$$uuid]) {\r
1430             $$subscribers[obj.$$uuid] = 1\r
1431             $$subscribers.push(obj)\r
1432         }\r
1433     }\r
1434 \r
1435     function disposeData(data) {\r
1436         data.element = null\r
1437         data.rollback && data.rollback()\r
1438         for (var key in data) {\r
1439             data[key] = null\r
1440         }\r
1441     }\r
1442 \r
1443     function isRemove(el) {\r
1444         try {//IE下,如果文本节点脱离DOM树,访问parentNode会报错\r
1445             if (!el.parentNode) {\r
1446                 return true\r
1447             }\r
1448         } catch (e) {\r
1449             return true\r
1450         }\r
1451         return el.msRetain ? 0 : (el.nodeType === 1 ? typeof el.sourceIndex === "number" ?\r
1452         el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el))\r
1453     }\r
1454 \r
1455     var $$subscribers = avalon.$$subscribers = []\r
1456     var beginTime = new Date()\r
1457     var oldInfo = {}\r
1458 \r
1459     function removeSubscribers() {\r
1460         var i = $$subscribers.length\r
1461         var n = i\r
1462         var k = 0\r
1463         var obj\r
1464         var types = []\r
1465         var newInfo = {}\r
1466         var needTest = {}\r
1467         while (obj = $$subscribers[--i]) {\r
1468             var data = obj.data\r
1469             var type = data.type\r
1470             if (newInfo[type]) {\r
1471                 newInfo[type]++\r
1472             } else {\r
1473                 newInfo[type] = 1\r
1474                 types.push(type)\r
1475             }\r
1476         }\r
1477         var diff = false\r
1478         types.forEach(function (type) {\r
1479             if (oldInfo[type] !== newInfo[type]) {\r
1480                 needTest[type] = 1\r
1481                 diff = true\r
1482             }\r
1483         })\r
1484         i = n\r
1485         //avalon.log("需要检测的个数 " + i)\r
1486         if (diff) {\r
1487             //avalon.log("有需要移除的元素")\r
1488             while (obj = $$subscribers[--i]) {\r
1489                 data = obj.data\r
1490                 if (data.element === void 0)\r
1491                     continue\r
1492                 if (needTest[data.type] && isRemove(data.element)) { //如果它没有在DOM树\r
1493                     k++\r
1494                     $$subscribers.splice(i, 1)\r
1495                     delete $$subscribers[obj.$$uuid]\r
1496                     avalon.Array.remove(obj.list, data)\r
1497                     //log("debug: remove " + data.type)\r
1498                     disposeData(data)\r
1499                     obj.data = obj.list = null\r
1500                 }\r
1501             }\r
1502         }\r
1503         oldInfo = newInfo\r
1504         // avalon.log("已经移除的个数 " + k)\r
1505         beginTime = new Date()\r
1506     }\r
1507 \r
1508     function notifySubscribers(list) { //通知依赖于这个访问器的订阅者更新自身\r
1509         if (list && list.length) {\r
1510             if (new Date() - beginTime > 444 && typeof list[0] === "object") {\r
1511                 removeSubscribers()\r
1512             }\r
1513             var args = aslice.call(arguments, 1)\r
1514             for (var i = list.length, fn; fn = list[--i];) {\r
1515                 var el = fn.element\r
1516                 if (el && el.parentNode) {\r
1517                     if (fn.$repeat) {\r
1518                         fn.handler.apply(fn, args) //处理监控数组的方法\r
1519                     } else if (fn.type !== "on") { //事件绑定只能由用户触发,不能由程序触发\r
1520                         var fun = fn.evaluator || noop\r
1521                         fn.handler(fun.apply(0, fn.args || []), el, fn)\r
1522                     }\r
1523                 }\r
1524             }\r
1525         }\r
1526     }\r
1527 \r
1528     /************************************************************************\r
1529      *              HTML处理(parseHTML, innerHTML, clearHTML)                 *\r
1530      **************************************************************************/\r
1531 //parseHTML的辅助变量\r
1532     var tagHooks = new function () {// jshint ignore:line\r
1533         avalon.mix(this, {\r
1534             option: DOC.createElement("select"),\r
1535             thead: DOC.createElement("table"),\r
1536             td: DOC.createElement("tr"),\r
1537             area: DOC.createElement("map"),\r
1538             tr: DOC.createElement("tbody"),\r
1539             col: DOC.createElement("colgroup"),\r
1540             legend: DOC.createElement("fieldset"),\r
1541             _default: DOC.createElement("div"),\r
1542             "g": DOC.createElementNS("http://www.w3.org/2000/svg", "svg")\r
1543         })\r
1544         this.optgroup = this.option\r
1545         this.tbody = this.tfoot = this.colgroup = this.caption = this.thead\r
1546         this.th = this.td\r
1547     }// jshint ignore:line\r
1548 \r
1549     String("circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use").replace(rword, function (tag) {\r
1550         tagHooks[tag] = tagHooks.g //处理SVG\r
1551     })\r
1552     var rtagName = /<([\w:]+)/\r
1553     var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig\r
1554     var scriptTypes = oneObject(["", "text/javascript", "text/ecmascript", "application/ecmascript", "application/javascript"])\r
1555     var script = DOC.createElement("script")\r
1556     var rhtml = /<|&#?\w+;/\r
1557     avalon.parseHTML = function (html) {\r
1558         var fragment = hyperspace.cloneNode(false)\r
1559         if (typeof html !== "string") {\r
1560             return fragment\r
1561         }\r
1562         if (!rhtml.test(html)) {\r
1563             fragment.appendChild(DOC.createTextNode(html))\r
1564             return fragment\r
1565         }\r
1566         html = html.replace(rxhtml, "<$1></$2>").trim()\r
1567         var tag = (rtagName.exec(html) || ["", ""])[1].toLowerCase(),\r
1568         //取得其标签名\r
1569             wrapper = tagHooks[tag] || tagHooks._default,\r
1570             firstChild\r
1571         wrapper.innerHTML = html\r
1572         var els = wrapper.getElementsByTagName("script")\r
1573         if (els.length) { //使用innerHTML生成的script节点不会发出请求与执行text属性\r
1574             for (var i = 0, el; el = els[i++];) {\r
1575                 if (scriptTypes[el.type]) {\r
1576                     var neo = script.cloneNode(false) //FF不能省略参数\r
1577                     ap.forEach.call(el.attributes, function (attr) {\r
1578                         neo.setAttribute(attr.name, attr.value)\r
1579                     })// jshint ignore:line\r
1580                     neo.text = el.text\r
1581                     el.parentNode.replaceChild(neo, el)\r
1582                 }\r
1583             }\r
1584         }\r
1585 \r
1586         while (firstChild = wrapper.firstChild) { // 将wrapper上的节点转移到文档碎片上!\r
1587             fragment.appendChild(firstChild)\r
1588         }\r
1589         return fragment\r
1590     }\r
1591 \r
1592     avalon.innerHTML = function (node, html) {\r
1593         var a = this.parseHTML(html)\r
1594         this.clearHTML(node).appendChild(a)\r
1595     }\r
1596 \r
1597     avalon.clearHTML = function (node) {\r
1598         node.textContent = ""\r
1599         while (node.firstChild) {\r
1600             node.removeChild(node.firstChild)\r
1601         }\r
1602         return node\r
1603     }\r
1604 \r
1605     /*********************************************************************\r
1606      *                           扫描系统                                 *\r
1607      **********************************************************************/\r
1608 \r
1609     avalon.scan = function (elem, vmodel) {\r
1610         elem = elem || root\r
1611         var vmodels = vmodel ? [].concat(vmodel) : []\r
1612         scanTag(elem, vmodels)\r
1613     }\r
1614 \r
1615 //http://www.w3.org/TR/html5/syntax.html#void-elements\r
1616     var stopScan = oneObject("area,base,basefont,br,col,command,embed,hr,img,input,link,meta,param,source,track,wbr,noscript,script,style,textarea".toUpperCase())\r
1617 \r
1618     function checkScan(elem, callback, innerHTML) {\r
1619         var id = setTimeout(function () {\r
1620             var currHTML = elem.innerHTML\r
1621             clearTimeout(id)\r
1622             if (currHTML === innerHTML) {\r
1623                 callback()\r
1624             } else {\r
1625                 checkScan(elem, callback, currHTML)\r
1626             }\r
1627         })\r
1628     }\r
1629 \r
1630 \r
1631     function createSignalTower(elem, vmodel) {\r
1632         var id = elem.getAttribute("avalonctrl") || vmodel.$id\r
1633         elem.setAttribute("avalonctrl", id)\r
1634         vmodel.$events.expr = elem.tagName + '[avalonctrl="' + id + '"]'\r
1635     }\r
1636 \r
1637     var getBindingCallback = function (elem, name, vmodels) {\r
1638         var callback = elem.getAttribute(name)\r
1639         if (callback) {\r
1640             for (var i = 0, vm; vm = vmodels[i++];) {\r
1641                 if (vm.hasOwnProperty(callback) && typeof vm[callback] === "function") {\r
1642                     return vm[callback]\r
1643                 }\r
1644             }\r
1645         }\r
1646     }\r
1647 \r
1648     function executeBindings(bindings, vmodels) {\r
1649         for (var i = 0, data; data = bindings[i++];) {\r
1650             data.vmodels = vmodels\r
1651             bindingHandlers[data.type](data, vmodels)\r
1652             if (data.evaluator && data.element && data.element.nodeType === 1) { //移除数据绑定,防止被二次解析\r
1653                 //chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99\r
1654                 data.element.removeAttribute(data.name)\r
1655             }\r
1656         }\r
1657         bindings.length = 0\r
1658     }\r
1659 \r
1660 //https://github.com/RubyLouvre/avalon/issues/636\r
1661     var mergeTextNodes = IEVersion && window.MutationObserver ? function (elem) {\r
1662         var node = elem.firstChild, text\r
1663         while (node) {\r
1664             var aaa = node.nextSibling\r
1665             if (node.nodeType === 3) {\r
1666                 if (text) {\r
1667                     text.nodeValue += node.nodeValue\r
1668                     elem.removeChild(node)\r
1669                 } else {\r
1670                     text = node\r
1671                 }\r
1672             } else {\r
1673                 text = null\r
1674             }\r
1675             node = aaa\r
1676         }\r
1677     } : 0\r
1678 \r
1679     var rmsAttr = /ms-(\w+)-?(.*)/\r
1680     var priorityMap = {\r
1681         "if": 10,\r
1682         "repeat": 90,\r
1683         "data": 100,\r
1684         "widget": 110,\r
1685         "each": 1400,\r
1686         "with": 1500,\r
1687         "duplex": 2000,\r
1688         "on": 3000\r
1689     }\r
1690 \r
1691     var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit")\r
1692     var obsoleteAttrs = oneObject("value,title,alt,checked,selected,disabled,readonly,enabled")\r
1693 \r
1694     function bindingSorter(a, b) {\r
1695         return a.priority - b.priority\r
1696     }\r
1697 \r
1698     function scanAttr(elem, vmodels) {\r
1699         //防止setAttribute, removeAttribute时 attributes自动被同步,导致for循环出错\r
1700         var attributes = elem.hasAttributes() ? avalon.slice(elem.attributes) : []\r
1701         var bindings = [],\r
1702             msData = createMap(),\r
1703             match\r
1704         for (var i = 0, attr; attr = attributes[i++];) {\r
1705             if (attr.specified) {\r
1706                 if (match = attr.name.match(rmsAttr)) {\r
1707                     //如果是以指定前缀命名的\r
1708                     var type = match[1]\r
1709                     var param = match[2] || ""\r
1710                     var value = attr.value\r
1711                     var name = attr.name\r
1712                     msData[name] = value\r
1713                     if (events[type]) {\r
1714                         param = type\r
1715                         type = "on"\r
1716                     } else if (obsoleteAttrs[type]) {\r
1717                         log("warning!请改用ms-attr-" + type + "代替ms-" + type + "!")\r
1718                         if (type === "enabled") {//吃掉ms-enabled绑定,用ms-disabled代替\r
1719                             log("warning!ms-enabled或ms-attr-enabled已经被废弃")\r
1720                             type = "disabled"\r
1721                             value = "!(" + value + ")"\r
1722                         }\r
1723                         param = type\r
1724                         type = "attr"\r
1725                         elem.removeAttribute(name)\r
1726                         name = "ms-attr-" + param\r
1727                         elem.setAttribute(name, value)\r
1728                         match = [name]\r
1729                         msData[name] = value\r
1730                     }\r
1731                     if (typeof bindingHandlers[type] === "function") {\r
1732                         var binding = {\r
1733                             type: type,\r
1734                             param: param,\r
1735                             element: elem,\r
1736                             name: match[0],\r
1737                             value: value,\r
1738                             priority: type in priorityMap ? priorityMap[type] : type.charCodeAt(0) * 10 + (Number(param) || 0)\r
1739                         }\r
1740                         if (type === "html" || type === "text") {\r
1741                             var token = getToken(value)\r
1742                             avalon.mix(binding, token)\r
1743                             binding.filters = binding.filters.replace(rhasHtml, function () {\r
1744                                 binding.type = "html"\r
1745                                 binding.group = 1\r
1746                                 return ""\r
1747                             })// jshint ignore:line\r
1748                         }\r
1749                         if (name === "ms-if-loop") {\r
1750                             binding.priority += 100\r
1751                         }\r
1752                         if (vmodels.length) {\r
1753                             bindings.push(binding)\r
1754                             if (type === "widget") {\r
1755                                 elem.msData = elem.msData || msData\r
1756                             }\r
1757                         }\r
1758                     }\r
1759                 }\r
1760             }\r
1761         }\r
1762         var control = elem.type\r
1763         if (control && msData["ms-duplex"]) {\r
1764             if (msData["ms-attr-checked"] && /radio|checkbox/.test(control)) {\r
1765                 log("warning!" + control + "控件不能同时定义ms-attr-checked与ms-duplex")\r
1766             }\r
1767             if (msData["ms-attr-value"] && /text|password/.test(control)) {\r
1768                 log("warning!" + control + "控件不能同时定义ms-attr-value与ms-duplex")\r
1769             }\r
1770         }\r
1771         bindings.sort(bindingSorter)\r
1772         var scanNode = true\r
1773         for (i = 0; binding = bindings[i]; i++) {\r
1774             type = binding.type\r
1775             if (rnoscanAttrBinding.test(type)) {\r
1776                 return executeBindings(bindings.slice(0, i + 1), vmodels)\r
1777             } else if (scanNode) {\r
1778                 scanNode = !rnoscanNodeBinding.test(type)\r
1779             }\r
1780         }\r
1781         executeBindings(bindings, vmodels)\r
1782         if (scanNode && !stopScan[elem.tagName] && rbind.test(elem.innerHTML + elem.textContent)) {\r
1783             mergeTextNodes && mergeTextNodes(elem)\r
1784             scanNodeList(elem, vmodels) //扫描子孙元素\r
1785         }\r
1786     }\r
1787 \r
1788     var rnoscanAttrBinding = /^if|widget|repeat$/\r
1789     var rnoscanNodeBinding = /^each|with|html|include$/\r
1790 \r
1791     function scanNodeList(parent, vmodels) {\r
1792         var node = parent.firstChild\r
1793         while (node) {\r
1794             var nextNode = node.nextSibling\r
1795             scanNode(node, node.nodeType, vmodels)\r
1796             node = nextNode\r
1797         }\r
1798     }\r
1799 \r
1800     function scanNodeArray(nodes, vmodels) {\r
1801         for (var i = 0, node; node = nodes[i++];) {\r
1802             scanNode(node, node.nodeType, vmodels)\r
1803         }\r
1804     }\r
1805 \r
1806     function scanNode(node, nodeType, vmodels) {\r
1807         if (nodeType === 1) {\r
1808             scanTag(node, vmodels) //扫描元素节点\r
1809             if (node.msCallback) {\r
1810                 node.msCallback()\r
1811                 node.msCallback = void 0\r
1812             }\r
1813         } else if (nodeType === 3 && rexpr.test(node.data)) {\r
1814             scanText(node, vmodels) //扫描文本节点\r
1815         } else if (kernel.commentInterpolate && nodeType === 8 && !rexpr.test(node.nodeValue)) {\r
1816             scanText(node, vmodels) //扫描注释节点\r
1817         }\r
1818     }\r
1819 \r
1820     function scanTag(elem, vmodels, node) {\r
1821         //扫描顺序  ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(100) \r
1822         //--> ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)--〉ms-duplex(2000)垫后        \r
1823         var a = elem.getAttribute("ms-skip")\r
1824         var b = elem.getAttributeNode("ms-important")\r
1825         var c = elem.getAttributeNode("ms-controller")\r
1826         if (typeof a === "string") {\r
1827             return\r
1828         } else if (node = b || c) {\r
1829             var newVmodel = avalon.vmodels[node.value]\r
1830             if (!newVmodel) {\r
1831                 return\r
1832             }\r
1833             //ms-important不包含父VM,ms-controller相反\r
1834             vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels)\r
1835             elem.removeAttribute(node.name) //removeAttributeNode不会刷新[ms-controller]样式规则\r
1836             elem.classList.remove(node.name)\r
1837             createSignalTower(elem, newVmodel)\r
1838         }\r
1839         scanAttr(elem, vmodels) //扫描特性节点\r
1840     }\r
1841 \r
1842     var rhasHtml = /\|\s*html\s*/,\r
1843         r11a = /\|\|/g,\r
1844         rlt = /&lt;/g,\r
1845         rgt = /&gt;/g,\r
1846         rstringLiteral = /(['"])(\\\1|.)+?\1/g\r
1847 \r
1848     function getToken(value) {\r
1849         if (value.indexOf("|") > 0) {\r
1850             var scapegoat = value.replace(rstringLiteral, function (_) {\r
1851                 return Array(_.length + 1).join("1")// jshint ignore:line\r
1852             })\r
1853             var index = scapegoat.replace(r11a, "\u1122\u3344").indexOf("|") //干掉所有短路或\r
1854             if (index > -1) {\r
1855                 return {\r
1856                     filters: value.slice(index),\r
1857                     value: value.slice(0, index),\r
1858                     expr: true\r
1859                 }\r
1860             }\r
1861         }\r
1862         return {\r
1863             value: value,\r
1864             filters: "",\r
1865             expr: true\r
1866         }\r
1867     }\r
1868 \r
1869     function scanExpr(str) {\r
1870         var tokens = [],\r
1871             value, start = 0,\r
1872             stop\r
1873         do {\r
1874             stop = str.indexOf(openTag, start)\r
1875             if (stop === -1) {\r
1876                 break\r
1877             }\r
1878             value = str.slice(start, stop)\r
1879             if (value) { // {{ 左边的文本\r
1880                 tokens.push({\r
1881                     value: value,\r
1882                     filters: "",\r
1883                     expr: false\r
1884                 })\r
1885             }\r
1886             start = stop + openTag.length\r
1887             stop = str.indexOf(closeTag, start)\r
1888             if (stop === -1) {\r
1889                 break\r
1890             }\r
1891             value = str.slice(start, stop)\r
1892             if (value) { //处理{{ }}插值表达式\r
1893                 tokens.push(getToken(value))\r
1894             }\r
1895             start = stop + closeTag.length\r
1896         } while (1)\r
1897         value = str.slice(start)\r
1898         if (value) { //}} 右边的文本\r
1899             tokens.push({\r
1900                 value: value,\r
1901                 expr: false,\r
1902                 filters: ""\r
1903             })\r
1904         }\r
1905         return tokens\r
1906     }\r
1907 \r
1908     function scanText(textNode, vmodels) {\r
1909         var bindings = []\r
1910         if (textNode.nodeType === 8) {\r
1911             var token = getToken(textNode.nodeValue)\r
1912             var tokens = [token]\r
1913         } else {\r
1914             tokens = scanExpr(textNode.data)\r
1915         }\r
1916         if (tokens.length) {\r
1917             for (var i = 0; token = tokens[i++];) {\r
1918                 var node = DOC.createTextNode(token.value) //将文本转换为文本节点,并替换原来的文本节点\r
1919                 if (token.expr) {\r
1920                     token.type = "text"\r
1921                     token.element = node\r
1922                     token.filters = token.filters.replace(rhasHtml, function () {\r
1923                         token.type = "html"\r
1924                         token.group = 1\r
1925                         return ""\r
1926                     })// jshint ignore:line\r
1927                     bindings.push(token) //收集带有插值表达式的文本\r
1928                 }\r
1929                 hyperspace.appendChild(node)\r
1930             }\r
1931             textNode.parentNode.replaceChild(hyperspace, textNode)\r
1932             if (bindings.length)\r
1933                 executeBindings(bindings, vmodels)\r
1934         }\r
1935     }\r
1936 \r
1937     /*********************************************************************\r
1938      *                        avalon的原型方法定义区                        *\r
1939      **********************************************************************/\r
1940 \r
1941     function hyphen(target) {\r
1942         //转换为连字符线风格\r
1943         return target.replace(/([a-z\d])([A-Z]+)/g, "$1-$2").toLowerCase()\r
1944     }\r
1945 \r
1946     function camelize(target) {\r
1947         //转换为驼峰风格\r
1948         if (target.indexOf("-") < 0 && target.indexOf("_") < 0) {\r
1949             return target //提前判断,提高getStyle等的效率\r
1950         }\r
1951         return target.replace(/[-_][^-_]/g, function (match) {\r
1952             return match.charAt(1).toUpperCase()\r
1953         })\r
1954     }\r
1955 \r
1956     "add,remove".replace(rword, function (method) {\r
1957         avalon.fn[method + "Class"] = function (cls) {\r
1958             var el = this[0]\r
1959             //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26\r
1960             if (cls && typeof cls === "string" && el && el.nodeType === 1) {\r
1961                 cls.replace(/\S+/g, function (c) {\r
1962                     el.classList[method](c)\r
1963                 })\r
1964             }\r
1965             return this\r
1966         }\r
1967     })\r
1968 \r
1969     avalon.fn.mix({\r
1970         hasClass: function (cls) {\r
1971             var el = this[0] || {} //IE10+, chrome8+, firefox3.6+, safari5.1+,opera11.5+支持classList,chrome24+,firefox26+支持classList2.0\r
1972             return el.nodeType === 1 && el.classList.contains(cls)\r
1973         },\r
1974         toggleClass: function (value, stateVal) {\r
1975             var className, i = 0\r
1976             var classNames = String(value).split(/\s+/)\r
1977             var isBool = typeof stateVal === "boolean"\r
1978             while ((className = classNames[i++])) {\r
1979                 var state = isBool ? stateVal : !this.hasClass(className)\r
1980                 this[state ? "addClass" : "removeClass"](className)\r
1981             }\r
1982             return this\r
1983         },\r
1984         attr: function (name, value) {\r
1985             if (arguments.length === 2) {\r
1986                 this[0].setAttribute(name, value)\r
1987                 return this\r
1988             } else {\r
1989                 return this[0].getAttribute(name)\r
1990             }\r
1991         },\r
1992         data: function (name, value) {\r
1993             name = "data-" + hyphen(name || "")\r
1994             switch (arguments.length) {\r
1995                 case 2:\r
1996                     this.attr(name, value)\r
1997                     return this\r
1998                 case 1:\r
1999                     var val = this.attr(name)\r
2000                     return parseData(val)\r
2001                 case 0:\r
2002                     var ret = {}\r
2003                     ap.forEach.call(this[0].attributes, function (attr) {\r
2004                         if (attr) {\r
2005                             name = attr.name\r
2006                             if (!name.indexOf("data-")) {\r
2007                                 name = camelize(name.slice(5))\r
2008                                 ret[name] = parseData(attr.value)\r
2009                             }\r
2010                         }\r
2011                     })\r
2012                     return ret\r
2013             }\r
2014         },\r
2015         removeData: function (name) {\r
2016             name = "data-" + hyphen(name)\r
2017             this[0].removeAttribute(name)\r
2018             return this\r
2019         },\r
2020         css: function (name, value) {\r
2021             if (avalon.isPlainObject(name)) {\r
2022                 for (var i in name) {\r
2023                     avalon.css(this, i, name[i])\r
2024                 }\r
2025             } else {\r
2026                 var ret = avalon.css(this, name, value)\r
2027             }\r
2028             return ret !== void 0 ? ret : this\r
2029         },\r
2030         position: function () {\r
2031             var offsetParent, offset,\r
2032                 elem = this[0],\r
2033                 parentOffset = {\r
2034                     top: 0,\r
2035                     left: 0\r
2036                 };\r
2037             if (!elem) {\r
2038                 return\r
2039             }\r
2040             if (this.css("position") === "fixed") {\r
2041                 offset = elem.getBoundingClientRect()\r
2042             } else {\r
2043                 offsetParent = this.offsetParent() //得到真正的offsetParent\r
2044                 offset = this.offset() // 得到正确的offsetParent\r
2045                 if (offsetParent[0].tagName !== "HTML") {\r
2046                     parentOffset = offsetParent.offset()\r
2047                 }\r
2048                 parentOffset.top += avalon.css(offsetParent[0], "borderTopWidth", true)\r
2049                 parentOffset.left += avalon.css(offsetParent[0], "borderLeftWidth", true)\r
2050                 // Subtract offsetParent scroll positions\r
2051                 parentOffset.top -= offsetParent.scrollTop()\r
2052                 parentOffset.left -= offsetParent.scrollLeft()\r
2053             }\r
2054             return {\r
2055                 top: offset.top - parentOffset.top - avalon.css(elem, "marginTop", true),\r
2056                 left: offset.left - parentOffset.left - avalon.css(elem, "marginLeft", true)\r
2057             }\r
2058         },\r
2059         offsetParent: function () {\r
2060             var offsetParent = this[0].offsetParent\r
2061             while (offsetParent && avalon.css(offsetParent, "position") === "static") {\r
2062                 offsetParent = offsetParent.offsetParent;\r
2063             }\r
2064             return avalon(offsetParent || root)\r
2065         },\r
2066         bind: function (type, fn, phase) {\r
2067             if (this[0]) { //此方法不会链\r
2068                 return avalon.bind(this[0], type, fn, phase)\r
2069             }\r
2070         },\r
2071         unbind: function (type, fn, phase) {\r
2072             if (this[0]) {\r
2073                 avalon.unbind(this[0], type, fn, phase)\r
2074             }\r
2075             return this\r
2076         },\r
2077         val: function (value) {\r
2078             var node = this[0]\r
2079             if (node && node.nodeType === 1) {\r
2080                 var get = arguments.length === 0\r
2081                 var access = get ? ":get" : ":set"\r
2082                 var fn = valHooks[getValType(node) + access]\r
2083                 if (fn) {\r
2084                     var val = fn(node, value)\r
2085                 } else if (get) {\r
2086                     return (node.value || "").replace(/\r/g, "")\r
2087                 } else {\r
2088                     node.value = value\r
2089                 }\r
2090             }\r
2091             return get ? val : this\r
2092         }\r
2093     })\r
2094 \r
2095     if (root.dataset) {\r
2096         avalon.fn.data = function (name, val) {\r
2097             name = name && camelize(name)\r
2098             var dataset = this[0].dataset\r
2099             switch (arguments.length) {\r
2100                 case 2:\r
2101                     dataset[name] = val\r
2102                     return this\r
2103                 case 1:\r
2104                     val = dataset[name]\r
2105                     return parseData(val)\r
2106                 case 0:\r
2107                     var ret = createMap()\r
2108                     for (name in dataset) {\r
2109                         ret[name] = parseData(dataset[name])\r
2110                     }\r
2111                     return ret\r
2112             }\r
2113         }\r
2114     }\r
2115     var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/\r
2116     avalon.parseJSON = JSON.parse\r
2117 \r
2118     function parseData(data) {\r
2119         try {\r
2120             if (typeof data === "object")\r
2121                 return data\r
2122             data = data === "true" ? true :\r
2123                 data === "false" ? false :\r
2124                     data === "null" ? null : +data + "" === data ? +data : rbrace.test(data) ? JSON.parse(data) : data\r
2125         } catch (e) {\r
2126         }\r
2127         return data\r
2128     }\r
2129 \r
2130     avalon.each({\r
2131         scrollLeft: "pageXOffset",\r
2132         scrollTop: "pageYOffset"\r
2133     }, function (method, prop) {\r
2134         avalon.fn[method] = function (val) {\r
2135             var node = this[0] || {}, win = getWindow(node),\r
2136                 top = method === "scrollTop"\r
2137             if (!arguments.length) {\r
2138                 return win ? win[prop] : node[method]\r
2139             } else {\r
2140                 if (win) {\r
2141                     win.scrollTo(!top ? val : win[prop], top ? val : win[prop])\r
2142                 } else {\r
2143                     node[method] = val\r
2144                 }\r
2145             }\r
2146         }\r
2147     })\r
2148 \r
2149     function getWindow(node) {\r
2150         return node.window && node.document ? node : node.nodeType === 9 ? node.defaultView : false\r
2151     }\r
2152 \r
2153 //=============================css相关==================================\r
2154     var cssHooks = avalon.cssHooks = createMap()\r
2155     var prefixes = ["", "-webkit-", "-moz-", "-ms-"] //去掉opera-15的支持\r
2156     var cssMap = {\r
2157         "float": "cssFloat"\r
2158     }\r
2159     avalon.cssNumber = oneObject("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom")\r
2160 \r
2161     avalon.cssName = function (name, host, camelCase) {\r
2162         if (cssMap[name]) {\r
2163             return cssMap[name]\r
2164         }\r
2165         host = host || root.style\r
2166         for (var i = 0, n = prefixes.length; i < n; i++) {\r
2167             camelCase = camelize(prefixes[i] + name)\r
2168             if (camelCase in host) {\r
2169                 return (cssMap[name] = camelCase)\r
2170             }\r
2171         }\r
2172         return null\r
2173     }\r
2174     cssHooks["@:set"] = function (node, name, value) {\r
2175         node.style[name] = value\r
2176     }\r
2177 \r
2178     cssHooks["@:get"] = function (node, name) {\r
2179         if (!node || !node.style) {\r
2180             throw new Error("getComputedStyle要求传入一个节点 " + node)\r
2181         }\r
2182         var ret, computed = getComputedStyle(node)\r
2183         if (computed) {\r
2184             ret = name === "filter" ? computed.getPropertyValue(name) : computed[name]\r
2185             if (ret === "") {\r
2186                 ret = node.style[name] //其他浏览器需要我们手动取内联样式\r
2187             }\r
2188         }\r
2189         return ret\r
2190     }\r
2191     cssHooks["opacity:get"] = function (node) {\r
2192         var ret = cssHooks["@:get"](node, "opacity")\r
2193         return ret === "" ? "1" : ret\r
2194     }\r
2195 \r
2196     "top,left".replace(rword, function (name) {\r
2197         cssHooks[name + ":get"] = function (node) {\r
2198             var computed = cssHooks["@:get"](node, name)\r
2199             return /px$/.test(computed) ? computed :\r
2200             avalon(node).position()[name] + "px"\r
2201         }\r
2202     })\r
2203     var cssShow = {\r
2204         position: "absolute",\r
2205         visibility: "hidden",\r
2206         display: "block"\r
2207     }\r
2208     var rdisplayswap = /^(none|table(?!-c[ea]).+)/\r
2209 \r
2210     function showHidden(node, array) {\r
2211         //http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html\r
2212         if (node.offsetWidth <= 0) { //opera.offsetWidth可能小于0\r
2213             var styles = getComputedStyle(node, null)\r
2214             if (rdisplayswap.test(styles["display"])) {\r
2215                 var obj = {\r
2216                     node: node\r
2217                 }\r
2218                 for (var name in cssShow) {\r
2219                     obj[name] = styles[name]\r
2220                     node.style[name] = cssShow[name]\r
2221                 }\r
2222                 array.push(obj)\r
2223             }\r
2224             var parent = node.parentNode\r
2225             if (parent && parent.nodeType === 1) {\r
2226                 showHidden(parent, array)\r
2227             }\r
2228         }\r
2229     }\r
2230 \r
2231     "Width,Height".replace(rword, function (name) { //fix 481\r
2232         var method = name.toLowerCase(),\r
2233             clientProp = "client" + name,\r
2234             scrollProp = "scroll" + name,\r
2235             offsetProp = "offset" + name\r
2236         cssHooks[method + ":get"] = function (node, which, override) {\r
2237             var boxSizing = -4\r
2238             if (typeof override === "number") {\r
2239                 boxSizing = override\r
2240             }\r
2241             which = name === "Width" ? ["Left", "Right"] : ["Top", "Bottom"]\r
2242             var ret = node[offsetProp] // border-box 0\r
2243             if (boxSizing === 2) { // margin-box 2\r
2244                 return ret + avalon.css(node, "margin" + which[0], true) + avalon.css(node, "margin" + which[1], true)\r
2245             }\r
2246             if (boxSizing < 0) { // padding-box  -2\r
2247                 ret = ret - avalon.css(node, "border" + which[0] + "Width", true) - avalon.css(node, "border" + which[1] + "Width", true)\r
2248             }\r
2249             if (boxSizing === -4) { // content-box -4\r
2250                 ret = ret - avalon.css(node, "padding" + which[0], true) - avalon.css(node, "padding" + which[1], true)\r
2251             }\r
2252             return ret\r
2253         }\r
2254         cssHooks[method + "&get"] = function (node) {\r
2255             var hidden = [];\r
2256             showHidden(node, hidden);\r
2257             var val = cssHooks[method + ":get"](node)\r
2258             for (var i = 0, obj; obj = hidden[i++];) {\r
2259                 node = obj.node\r
2260                 for (var n in obj) {\r
2261                     if (typeof obj[n] === "string") {\r
2262                         node.style[n] = obj[n]\r
2263                     }\r
2264                 }\r
2265             }\r
2266             return val;\r
2267         }\r
2268         avalon.fn[method] = function (value) { //会忽视其display\r
2269             var node = this[0]\r
2270             if (arguments.length === 0) {\r
2271                 if (node.setTimeout) { //取得窗口尺寸,IE9后可以用node.innerWidth /innerHeight代替\r
2272                     return node["inner" + name]\r
2273                 }\r
2274                 if (node.nodeType === 9) { //取得页面尺寸\r
2275                     var doc = node.documentElement\r
2276                     //FF chrome    html.scrollHeight< body.scrollHeight\r
2277                     //IE 标准模式 : html.scrollHeight> body.scrollHeight\r
2278                     //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点?\r
2279                     return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp])\r
2280                 }\r
2281                 return cssHooks[method + "&get"](node)\r
2282             } else {\r
2283                 return this.css(method, value)\r
2284             }\r
2285         }\r
2286         avalon.fn["inner" + name] = function () {\r
2287             return cssHooks[method + ":get"](this[0], void 0, -2)\r
2288         }\r
2289         avalon.fn["outer" + name] = function (includeMargin) {\r
2290             return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? 2 : 0)\r
2291         }\r
2292     })\r
2293     avalon.fn.offset = function () { //取得距离页面左右角的坐标\r
2294         var node = this[0]\r
2295         try {\r
2296             var rect = node.getBoundingClientRect()\r
2297             // Make sure element is not hidden (display: none) or disconnected\r
2298             // https://github.com/jquery/jquery/pull/2043/files#r23981494\r
2299             if (rect.width || rect.height || node.getClientRects().length) {\r
2300                 var doc = node.ownerDocument\r
2301                 var root = doc.documentElement\r
2302                 var win = doc.defaultView\r
2303                 return {\r
2304                     top: rect.top + win.pageYOffset - root.clientTop,\r
2305                     left: rect.left + win.pageXOffset - root.clientLeft\r
2306                 }\r
2307             }\r
2308         } catch (e) {\r
2309             return {\r
2310                 left: 0,\r
2311                 top: 0\r
2312             }\r
2313         }\r
2314     }\r
2315     //=============================val相关=======================\r
2316 \r
2317     function getValType(elem) {\r
2318         var ret = elem.tagName.toLowerCase()\r
2319         return ret === "input" && /checkbox|radio/.test(elem.type) ? "checked" : ret\r
2320     }\r
2321 \r
2322     var valHooks = {\r
2323         "select:get": function (node, value) {\r
2324             var option, options = node.options,\r
2325                 index = node.selectedIndex,\r
2326                 one = node.type === "select-one" || index < 0,\r
2327                 values = one ? null : [],\r
2328                 max = one ? index + 1 : options.length,\r
2329                 i = index < 0 ? max : one ? index : 0\r
2330             for (; i < max; i++) {\r
2331                 option = options[i]\r
2332                 //旧式IE在reset后不会改变selected,需要改用i === index判定\r
2333                 //我们过滤所有disabled的option元素,但在safari5下,如果设置select为disable,那么其所有孩子都disable\r
2334                 //因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况\r
2335                 if ((option.selected || i === index) && !option.disabled) {\r
2336                     value = option.value\r
2337                     if (one) {\r
2338                         return value\r
2339                     }\r
2340                     //收集所有selected值组成数组返回\r
2341                     values.push(value)\r
2342                 }\r
2343             }\r
2344             return values\r
2345         },\r
2346         "select:set": function (node, values, optionSet) {\r
2347             values = [].concat(values) //强制转换为数组\r
2348             for (var i = 0, el; el = node.options[i++];) {\r
2349                 if ((el.selected = values.indexOf(el.value) > -1)) {\r
2350                     optionSet = true\r
2351                 }\r
2352             }\r
2353             if (!optionSet) {\r
2354                 node.selectedIndex = -1\r
2355             }\r
2356         }\r
2357     }\r
2358     /*********************************************************************\r
2359      *                          编译系统                                  *\r
2360      **********************************************************************/\r
2361     var quote = JSON.stringify\r
2362 \r
2363     var keywords = [\r
2364         "break,case,catch,continue,debugger,default,delete,do,else,false",\r
2365         "finally,for,function,if,in,instanceof,new,null,return,switch,this",\r
2366         "throw,true,try,typeof,var,void,while,with", /* 关键字*/\r
2367         "abstract,boolean,byte,char,class,const,double,enum,export,extends",\r
2368         "final,float,goto,implements,import,int,interface,long,native",\r
2369         "package,private,protected,public,short,static,super,synchronized",\r
2370         "throws,transient,volatile", /*保留字*/\r
2371         "arguments,let,yield,undefined" /* ECMA 5 - use strict*/].join(",")\r
2372     var rrexpstr = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g\r
2373     var rsplit = /[^\w$]+/g\r
2374     var rkeywords = new RegExp(["\\b" + keywords.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g')\r
2375     var rnumber = /\b\d[^,]*/g\r
2376     var rcomma = /^,+|,+$/g\r
2377     var cacheVars = new Cache(512)\r
2378     var getVariables = function (code) {\r
2379         var key = "," + code.trim()\r
2380         var ret = cacheVars.get(key)\r
2381         if (ret) {\r
2382             return ret\r
2383         }\r
2384         var match = code\r
2385             .replace(rrexpstr, "")\r
2386             .replace(rsplit, ",")\r
2387             .replace(rkeywords, "")\r
2388             .replace(rnumber, "")\r
2389             .replace(rcomma, "")\r
2390             .split(/^$|,+/)\r
2391         return cacheVars.put(key, uniqSet(match))\r
2392     }\r
2393     /*添加赋值语句*/\r
2394 \r
2395     function addAssign(vars, scope, name, data) {\r
2396         var ret = [],\r
2397             prefix = " = " + name + "."\r
2398         var isProxy = /\$proxy\$each/.test(scope.$id)\r
2399         for (var i = vars.length, prop; prop = vars[--i];) {\r
2400             var el = isProxy && scope.$map[prop] ? "el" : prop\r
2401             if (scope.hasOwnProperty(el)) {\r
2402                 ret.push(prop + prefix + el)\r
2403                 data.vars.push(prop)\r
2404                 if (data.type === "duplex") {\r
2405                     vars.get = name + "." + el\r
2406                 }\r
2407                 vars.splice(i, 1)\r
2408             }\r
2409         }\r
2410         return ret\r
2411     }\r
2412 \r
2413     function uniqSet(array) {\r
2414         var ret = [],\r
2415             unique = {}\r
2416         for (var i = 0; i < array.length; i++) {\r
2417             var el = array[i]\r
2418             var id = el && typeof el.$id === "string" ? el.$id : el\r
2419             if (!unique[id]) {\r
2420                 unique[id] = ret.push(el)\r
2421             }\r
2422         }\r
2423         return ret\r
2424     }\r
2425 \r
2426 //缓存求值函数,以便多次利用\r
2427     var cacheExprs = new Cache(128)\r
2428 //取得求值函数及其传参\r
2429     var rduplex = /\w\[.*\]|\w\.\w/\r
2430     var rproxy = /(\$proxy\$[a-z]+)\d+$/\r
2431     var rthimRightParentheses = /\)\s*$/\r
2432     var rthimOtherParentheses = /\)\s*\|/g\r
2433     var rquoteFilterName = /\|\s*([$\w]+)/g\r
2434     var rpatchBracket = /"\s*\["/g\r
2435     var rthimLeftParentheses = /"\s*\(/g\r
2436 \r
2437     function parseFilter(val, filters) {\r
2438         filters = filters\r
2439             .replace(rthimRightParentheses, "")//处理最后的小括号\r
2440             .replace(rthimOtherParentheses, function () {//处理其他小括号\r
2441                 return "],|"\r
2442             })\r
2443             .replace(rquoteFilterName, function (a, b) { //处理|及它后面的过滤器的名字\r
2444                 return "[" + quote(b)\r
2445             })\r
2446             .replace(rpatchBracket, function () {\r
2447                 return '"],["'\r
2448             })\r
2449             .replace(rthimLeftParentheses, function () {\r
2450                 return '",'\r
2451             }) + "]"\r
2452         return "return avalon.filters.$filter(" + val + ", " + filters + ")"\r
2453     }\r
2454 \r
2455     function parseExpr(code, scopes, data) {\r
2456         var dataType = data.type\r
2457         var filters = data.filters || ""\r
2458         var exprId = scopes.map(function (el) {\r
2459                 return String(el.$id).replace(rproxy, "$1")\r
2460             }) + code + dataType + filters\r
2461         var vars = getVariables(code).concat(),\r
2462             assigns = [],\r
2463             names = [],\r
2464             args = [],\r
2465             prefix = ""\r
2466         //args 是一个对象数组, names 是将要生成的求值函数的参数\r
2467         scopes = uniqSet(scopes)\r
2468         data.vars = []\r
2469         for (var i = 0, sn = scopes.length; i < sn; i++) {\r
2470             if (vars.length) {\r
2471                 var name = "vm" + expose + "_" + i\r
2472                 names.push(name)\r
2473                 args.push(scopes[i])\r
2474                 assigns.push.apply(assigns, addAssign(vars, scopes[i], name, data))\r
2475             }\r
2476         }\r
2477         if (!assigns.length && dataType === "duplex") {\r
2478             return\r
2479         }\r
2480         if (dataType !== "duplex" && (code.indexOf("||") > -1 || code.indexOf("&&") > -1)) {\r
2481             //https://github.com/RubyLouvre/avalon/issues/583\r
2482             data.vars.forEach(function (v) {\r
2483                 var reg = new RegExp("\\b" + v + "(?:\\.\\w+|\\[\\w+\\])+", "ig")\r
2484                 code = code.replace(reg, function (_) {\r
2485                     var c = _.charAt(v.length)\r
2486                     var r = IEVersion ? code.slice(arguments[1] + _.length) : RegExp.rightContext\r
2487                     var method = /^\s*\(/.test(r)\r
2488                     if (c === "." || c === "[" || method) {//比如v为aa,我们只匹配aa.bb,aa[cc],不匹配aaa.xxx\r
2489                         var name = "var" + String(Math.random()).replace(/^0\./, "")\r
2490                         if (method) {//array.size()\r
2491                             var array = _.split(".")\r
2492                             if (array.length > 2) {\r
2493                                 var last = array.pop()\r
2494                                 assigns.push(name + " = " + array.join("."))\r
2495                                 return name + "." + last\r
2496                             } else {\r
2497                                 return _\r
2498                             }\r
2499                         }\r
2500                         assigns.push(name + " = " + _)\r
2501                         return name\r
2502                     } else {\r
2503                         return _\r
2504                     }\r
2505                 })\r
2506             })\r
2507         }\r
2508         //---------------args----------------\r
2509         data.args = args\r
2510         //---------------cache----------------\r
2511         var fn = cacheExprs.get(exprId) //直接从缓存,免得重复生成\r
2512         if (fn) {\r
2513             data.evaluator = fn\r
2514             return\r
2515         }\r
2516         prefix = assigns.join(", ")\r
2517         if (prefix) {\r
2518             prefix = "var " + prefix\r
2519         }\r
2520         if (/\S/.test(filters)) { //文本绑定,双工绑定才有过滤器\r
2521             if (!/text|html/.test(data.type)) {\r
2522                 throw Error("ms-" + data.type + "不支持过滤器")\r
2523             }\r
2524             code = "\nvar ret" + expose + " = " + code + ";\r\n"\r
2525             code += parseFilter("ret" + expose, filters)\r
2526         } else if (dataType === "duplex") { //双工绑定\r
2527             var _body = "'use strict';\nreturn function(vvv){\n\t" +\r
2528                 prefix +\r
2529                 ";\n\tif(!arguments.length){\n\t\treturn " +\r
2530                 code +\r
2531                 "\n\t}\n\t" + (!rduplex.test(code) ? vars.get : code) +\r
2532                 "= vvv;\n} "\r
2533             try {\r
2534                 fn = Function.apply(noop, names.concat(_body))\r
2535                 data.evaluator = cacheExprs.put(exprId, fn)\r
2536             } catch (e) {\r
2537                 log("debug: parse error," + e.message)\r
2538             }\r
2539             return\r
2540         } else if (dataType === "on") { //事件绑定\r
2541             if (code.indexOf("(") === -1) {\r
2542                 code += ".call(this, $event)"\r
2543             } else {\r
2544                 code = code.replace("(", ".call(this,")\r
2545             }\r
2546             names.push("$event")\r
2547             code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")\r
2548             var lastIndex = code.lastIndexOf("\nreturn")\r
2549             var header = code.slice(0, lastIndex)\r
2550             var footer = code.slice(lastIndex)\r
2551             code = header + "\n" + footer\r
2552         } else { //其他绑定\r
2553             code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")\r
2554         }\r
2555         try {\r
2556             fn = Function.apply(noop, names.concat("'use strict';\n" + prefix + code))\r
2557             data.evaluator = cacheExprs.put(exprId, fn)\r
2558         } catch (e) {\r
2559             log("debug: parse error," + e.message)\r
2560         } finally {\r
2561             vars = assigns = names = null //释放内存\r
2562         }\r
2563     }\r
2564 \r
2565 \r
2566 //parseExpr的智能引用代理\r
2567 \r
2568     function parseExprProxy(code, scopes, data, tokens, noRegister) {\r
2569         if (Array.isArray(tokens)) {\r
2570             code = tokens.map(function (el) {\r
2571                 return el.expr ? "(" + el.value + ")" : quote(el.value)\r
2572             }).join(" + ")\r
2573         }\r
2574         parseExpr(code, scopes, data)\r
2575         if (data.evaluator && !noRegister) {\r
2576             data.handler = bindingExecutors[data.handlerName || data.type]\r
2577             //方便调试\r
2578             //这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定\r
2579             //将它移出订阅者列表\r
2580             avalon.injectBinding(data)\r
2581         }\r
2582     }\r
2583 \r
2584     avalon.parseExprProxy = parseExprProxy\r
2585     var bools = ["autofocus,autoplay,async,allowTransparency,checked,controls",\r
2586         "declare,disabled,defer,defaultChecked,defaultSelected",\r
2587         "contentEditable,isMap,loop,multiple,noHref,noResize,noShade",\r
2588         "open,readOnly,selected"\r
2589     ].join(",")\r
2590     var boolMap = {}\r
2591     bools.replace(rword, function (name) {\r
2592         boolMap[name.toLowerCase()] = name\r
2593     })\r
2594 \r
2595     var propMap = { //属性名映射\r
2596         "accept-charset": "acceptCharset",\r
2597         "char": "ch",\r
2598         "charoff": "chOff",\r
2599         "class": "className",\r
2600         "for": "htmlFor",\r
2601         "http-equiv": "httpEquiv"\r
2602     }\r
2603 \r
2604     var anomaly = ["accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan",\r
2605         "dateTime,defaultValue,frameBorder,longDesc,maxLength,marginWidth,marginHeight",\r
2606         "rowSpan,tabIndex,useMap,vSpace,valueType,vAlign"\r
2607     ].join(",")\r
2608     anomaly.replace(rword, function (name) {\r
2609         propMap[name.toLowerCase()] = name\r
2610     })\r
2611 \r
2612     var rnoscripts = /<noscript.*?>(?:[\s\S]+?)<\/noscript>/img\r
2613     var rnoscriptText = /<noscript.*?>([\s\S]+?)<\/noscript>/im\r
2614 \r
2615     var getXHR = function () {\r
2616         return new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP") // jshint ignore:line\r
2617     }\r
2618 \r
2619     var cacheTmpls = avalon.templateCache = {}\r
2620 \r
2621     bindingHandlers.attr = function (data, vmodels) {\r
2622         var text = data.value.trim(),\r
2623             simple = true\r
2624         if (text.indexOf(openTag) > -1 && text.indexOf(closeTag) > 2) {\r
2625             simple = false\r
2626             if (rexpr.test(text) && RegExp.rightContext === "" && RegExp.leftContext === "") {\r
2627                 simple = true\r
2628                 text = RegExp.$1\r
2629             }\r
2630         }\r
2631         if (data.type === "include") {\r
2632             var elem = data.element\r
2633             data.includeRendered = getBindingCallback(elem, "data-include-rendered", vmodels)\r
2634             data.includeLoaded = getBindingCallback(elem, "data-include-loaded", vmodels)\r
2635             var outer = data.includeReplace = !!avalon(elem).data("includeReplace")\r
2636             if (avalon(elem).data("includeCache")) {\r
2637                 data.templateCache = {}\r
2638             }\r
2639             data.startInclude = DOC.createComment("ms-include")\r
2640             data.endInclude = DOC.createComment("ms-include-end")\r
2641             if (outer) {\r
2642                 data.element = data.startInclude\r
2643                 elem.parentNode.insertBefore(data.startInclude, elem)\r
2644                 elem.parentNode.insertBefore(data.endInclude, elem.nextSibling)\r
2645             } else {\r
2646                 elem.insertBefore(data.startInclude, elem.firstChild)\r
2647                 elem.appendChild(data.endInclude)\r
2648             }\r
2649         }\r
2650         data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况\r
2651         parseExprProxy(text, vmodels, data, (simple ? 0 : scanExpr(data.value)))\r
2652     }\r
2653 \r
2654     bindingExecutors.attr = function (val, elem, data) {\r
2655         var method = data.type,\r
2656             attrName = data.param\r
2657         if (method === "css") {\r
2658             avalon(elem).css(attrName, val)\r
2659         } else if (method === "attr") {\r
2660             // ms-attr-class="xxx" vm.xxx="aaa bbb ccc"将元素的className设置为aaa bbb ccc\r
2661             // ms-attr-class="xxx" vm.xxx=false  清空元素的所有类名\r
2662             // ms-attr-name="yyy"  vm.yyy="ooo" 为元素设置name属性\r
2663             var toRemove = (val === false) || (val === null) || (val === void 0)\r
2664 \r
2665             if (!W3C && propMap[attrName]) { //旧式IE下需要进行名字映射\r
2666                 attrName = propMap[attrName]\r
2667             }\r
2668             var bool = boolMap[attrName]\r
2669             if (typeof elem[bool] === "boolean") {\r
2670                 elem[bool] = !!val //布尔属性必须使用el.xxx = true|false方式设值\r
2671                 if (!val) { //如果为false, IE全系列下相当于setAttribute(xxx,''),会影响到样式,需要进一步处理\r
2672                     toRemove = true\r
2673                 }\r
2674             }\r
2675             if (toRemove) {\r
2676                 return elem.removeAttribute(attrName)\r
2677             }\r
2678 \r
2679             //SVG只能使用setAttribute(xxx, yyy), VML只能使用elem.xxx = yyy ,HTML的固有属性必须elem.xxx = yyy\r
2680             var isInnate = rsvg.test(elem) ? false : (DOC.namespaces && isVML(elem)) ? true : attrName in elem.cloneNode(false)\r
2681             if (isInnate) {\r
2682                 elem[attrName] = val\r
2683             } else {\r
2684                 elem.setAttribute(attrName, val)\r
2685             }\r
2686         } else if (method === "include" && val) {\r
2687             var vmodels = data.vmodels\r
2688             var rendered = data.includeRendered\r
2689             var loaded = data.includeLoaded\r
2690             var replace = data.includeReplace\r
2691             var target = replace ? elem.parentNode : elem\r
2692             var scanTemplate = function (text) {\r
2693                 if (loaded) {\r
2694                     var newText = loaded.apply(target, [text].concat(vmodels))\r
2695                     if (typeof newText === "string")\r
2696                         text = newText\r
2697                 }\r
2698                 if (rendered) {\r
2699                     checkScan(target, function () {\r
2700                         rendered.call(target)\r
2701                     }, NaN)\r
2702                 }\r
2703                 var lastID = data.includeLastID\r
2704                 if (data.templateCache && lastID && lastID !== val) {\r
2705                     var lastTemplate = data.templateCache[lastID]\r
2706                     if (!lastTemplate) {\r
2707                         lastTemplate = data.templateCache[lastID] = DOC.createElement("div")\r
2708                         ifGroup.appendChild(lastTemplate)\r
2709                     }\r
2710                 }\r
2711                 data.includeLastID = val\r
2712                 while (true) {\r
2713                     var node = data.startInclude.nextSibling\r
2714                     if (node && node !== data.endInclude) {\r
2715                         target.removeChild(node)\r
2716                         if (lastTemplate)\r
2717                             lastTemplate.appendChild(node)\r
2718                     } else {\r
2719                         break\r
2720                     }\r
2721                 }\r
2722                 var dom = getTemplateNodes(data, val, text)\r
2723                 var nodes = avalon.slice(dom.childNodes)\r
2724                 target.insertBefore(dom, data.endInclude)\r
2725                 scanNodeArray(nodes, vmodels)\r
2726             }\r
2727 \r
2728             if (data.param === "src") {\r
2729                 if (typeof cacheTmpls[val] === "string") {\r
2730                     avalon.nextTick(function () {\r
2731                         scanTemplate(cacheTmpls[val])\r
2732                     })\r
2733                 } else if (Array.isArray(cacheTmpls[val])) { //#805 防止在循环绑定中发出许多相同的请求\r
2734                     cacheTmpls[val].push(scanTemplate)\r
2735                 } else {\r
2736                     var xhr = getXHR()\r
2737                     xhr.onreadystatechange = function () {\r
2738                         if (xhr.readyState === 4) {\r
2739                             var s = xhr.status\r
2740                             if (s >= 200 && s < 300 || s === 304 || s === 1223) {\r
2741                                 var text = xhr.responseText\r
2742                                 for (var f = 0, fn; fn = cacheTmpls[val][f++];) {\r
2743                                     fn(text)\r
2744                                 }\r
2745                                 cacheTmpls[val] = text\r
2746                             }\r
2747                         }\r
2748                     }\r
2749                     cacheTmpls[val] = [scanTemplate]\r
2750                     xhr.open("GET", val, true)\r
2751                     if ("withCredentials" in xhr) {\r
2752                         xhr.withCredentials = true\r
2753                     }\r
2754                     xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")\r
2755                     xhr.send(null)\r
2756                 }\r
2757             } else {\r
2758                 //IE系列与够新的标准浏览器支持通过ID取得元素(firefox14+)\r
2759                 //http://tjvantoll.com/2012/07/19/dom-element-references-as-global-variables/\r
2760                 var el = val && val.nodeType === 1 ? val : DOC.getElementById(val)\r
2761                 if (el) {\r
2762                     if (el.tagName === "NOSCRIPT" && !(el.innerHTML || el.fixIE78)) { //IE7-8 innerText,innerHTML都无法取得其内容,IE6能取得其innerHTML\r
2763                         xhr = getXHR() //IE9-11与chrome的innerHTML会得到转义的内容,它们的innerText可以\r
2764                         xhr.open("GET", location, false) //谢谢Nodejs 乱炖群 深圳-纯属虚构\r
2765                         xhr.send(null)\r
2766                         //http://bbs.csdn.net/topics/390349046?page=1#post-393492653\r
2767                         var noscripts = DOC.getElementsByTagName("noscript")\r
2768                         var array = (xhr.responseText || "").match(rnoscripts) || []\r
2769                         var n = array.length\r
2770                         for (var i = 0; i < n; i++) {\r
2771                             var tag = noscripts[i]\r
2772                             if (tag) { //IE6-8中noscript标签的innerHTML,innerText是只读的\r
2773                                 tag.style.display = "none" //http://haslayout.net/css/noscript-Ghost-Bug\r
2774                                 tag.fixIE78 = (array[i].match(rnoscriptText) || ["", "&nbsp;"])[1]\r
2775                             }\r
2776                         }\r
2777                     }\r
2778                     avalon.nextTick(function () {\r
2779                         scanTemplate(el.fixIE78 || el.value || el.innerText || el.innerHTML)\r
2780                     })\r
2781                 }\r
2782             }\r
2783         } else {\r
2784             if (!root.hasAttribute && typeof val === "string" && (method === "src" || method === "href")) {\r
2785                 val = val.replace(/&amp;/g, "&") //处理IE67自动转义的问题\r
2786             }\r
2787             elem[method] = val\r
2788             if (window.chrome && elem.tagName === "EMBED") {\r
2789                 var parent = elem.parentNode //#525  chrome1-37下embed标签动态设置src不能发生请求\r
2790                 var comment = document.createComment("ms-src")\r
2791                 parent.replaceChild(comment, elem)\r
2792                 parent.replaceChild(elem, comment)\r
2793             }\r
2794         }\r
2795     }\r
2796 \r
2797     function getTemplateNodes(data, id, text) {\r
2798         var div = data.templateCache && data.templateCache[id]\r
2799         if (div) {\r
2800             var dom = DOC.createDocumentFragment(),\r
2801                 firstChild\r
2802             while (firstChild = div.firstChild) {\r
2803                 dom.appendChild(firstChild)\r
2804             }\r
2805             return dom\r
2806         }\r
2807         return avalon.parseHTML(text)\r
2808     }\r
2809 \r
2810 //这几个指令都可以使用插值表达式,如ms-src="aaa/{{b}}/{{c}}.html"\r
2811     "title,alt,src,value,css,include,href".replace(rword, function (name) {\r
2812         bindingHandlers[name] = bindingHandlers.attr\r
2813     })\r
2814 //根据VM的属性值或表达式的值切换类名,ms-class="xxx yyy zzz:flag" \r
2815 //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html\r
2816     bindingHandlers["class"] = function (data, vmodels) {\r
2817         var oldStyle = data.param,\r
2818             text = data.value,\r
2819             rightExpr\r
2820         data.handlerName = "class"\r
2821         if (!oldStyle || isFinite(oldStyle)) {\r
2822             data.param = "" //去掉数字\r
2823             var noExpr = text.replace(rexprg, function (a) {\r
2824                 return a.replace(/./g, "0")\r
2825                 //return Math.pow(10, a.length - 1) //将插值表达式插入10的N-1次方来占位\r
2826             })\r
2827             var colonIndex = noExpr.indexOf(":") //取得第一个冒号的位置\r
2828             if (colonIndex === -1) { // 比如 ms-class="aaa bbb ccc" 的情况\r
2829                 var className = text\r
2830             } else { // 比如 ms-class-1="ui-state-active:checked" 的情况 \r
2831                 className = text.slice(0, colonIndex)\r
2832                 rightExpr = text.slice(colonIndex + 1)\r
2833                 parseExpr(rightExpr, vmodels, data) //决定是添加还是删除\r
2834                 if (!data.evaluator) {\r
2835                     log("debug: ms-class '" + (rightExpr || "").trim() + "' 不存在于VM中")\r
2836                     return false\r
2837                 } else {\r
2838                     data._evaluator = data.evaluator\r
2839                     data._args = data.args\r
2840                 }\r
2841             }\r
2842             var hasExpr = rexpr.test(className) //比如ms-class="width{{w}}"的情况\r
2843             if (!hasExpr) {\r
2844                 data.immobileClass = className\r
2845             }\r
2846             parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : 0))\r
2847         } else {\r
2848             data.immobileClass = data.oldStyle = data.param\r
2849             parseExprProxy(text, vmodels, data)\r
2850         }\r
2851     }\r
2852 \r
2853     bindingExecutors["class"] = function (val, elem, data) {\r
2854         var $elem = avalon(elem),\r
2855             method = data.type\r
2856         if (method === "class" && data.oldStyle) { //如果是旧风格\r
2857             $elem.toggleClass(data.oldStyle, !!val)\r
2858         } else {\r
2859             //如果存在冒号就有求值函数\r
2860             data.toggleClass = data._evaluator ? !!data._evaluator.apply(elem, data._args) : true\r
2861             data.newClass = data.immobileClass || val\r
2862             if (data.oldClass && data.newClass !== data.oldClass) {\r
2863                 $elem.removeClass(data.oldClass)\r
2864             }\r
2865             data.oldClass = data.newClass\r
2866             switch (method) {\r
2867                 case "class":\r
2868                     $elem.toggleClass(data.newClass, data.toggleClass)\r
2869                     break\r
2870                 case "hover":\r
2871                 case "active":\r
2872                     if (!data.hasBindEvent) { //确保只绑定一次\r
2873                         var activate = "mouseenter" //在移出移入时切换类名\r
2874                         var abandon = "mouseleave"\r
2875                         if (method === "active") { //在聚焦失焦中切换类名\r
2876                             elem.tabIndex = elem.tabIndex || -1\r
2877                             activate = "mousedown"\r
2878                             abandon = "mouseup"\r
2879                             var fn0 = $elem.bind("mouseleave", function () {\r
2880                                 data.toggleClass && $elem.removeClass(data.newClass)\r
2881                             })\r
2882                         }\r
2883                         var fn1 = $elem.bind(activate, function () {\r
2884                             data.toggleClass && $elem.addClass(data.newClass)\r
2885                         })\r
2886                         var fn2 = $elem.bind(abandon, function () {\r
2887                             data.toggleClass && $elem.removeClass(data.newClass)\r
2888                         })\r
2889                         data.rollback = function () {\r
2890                             $elem.unbind("mouseleave", fn0)\r
2891                             $elem.unbind(activate, fn1)\r
2892                             $elem.unbind(abandon, fn2)\r
2893                         }\r
2894                         data.hasBindEvent = true\r
2895                     }\r
2896                     break;\r
2897             }\r
2898         }\r
2899     }\r
2900 \r
2901     "hover,active".replace(rword, function (method) {\r
2902         bindingHandlers[method] = bindingHandlers["class"]\r
2903     })\r
2904 //ms-controller绑定已经在scanTag 方法中实现\r
2905 //ms-css绑定已由ms-attr绑定实现\r
2906 \r
2907 \r
2908 // bindingHandlers.data 定义在if.js\r
2909     bindingExecutors.data = function (val, elem, data) {\r
2910         var key = "data-" + data.param\r
2911         if (val && typeof val === "object") {\r
2912             elem[key] = val\r
2913         } else {\r
2914             elem.setAttribute(key, String(val))\r
2915         }\r
2916     }\r
2917 //双工绑定\r
2918     var duplexBinding = bindingHandlers.duplex = function (data, vmodels) {\r
2919         var elem = data.element,\r
2920             hasCast\r
2921         parseExprProxy(data.value, vmodels, data, 0, 1)\r
2922 \r
2923         data.changed = getBindingCallback(elem, "data-duplex-changed", vmodels) || noop\r
2924         if (data.evaluator && data.args) {\r
2925             var params = []\r
2926             var casting = oneObject("string,number,boolean,checked")\r
2927             if (elem.type === "radio" && data.param === "") {\r
2928                 data.param = "checked"\r
2929             }\r
2930             if (elem.msData) {\r
2931                 elem.msData["ms-duplex"] = data.value\r
2932             }\r
2933             data.param.replace(/\w+/g, function (name) {\r
2934                 if (/^(checkbox|radio)$/.test(elem.type) && /^(radio|checked)$/.test(name)) {\r
2935                     if (name === "radio")\r
2936                         log("ms-duplex-radio已经更名为ms-duplex-checked")\r
2937                     name = "checked"\r
2938                     data.isChecked = true\r
2939                 }\r
2940                 if (name === "bool") {\r
2941                     name = "boolean"\r
2942                     log("ms-duplex-bool已经更名为ms-duplex-boolean")\r
2943                 } else if (name === "text") {\r
2944                     name = "string"\r
2945                     log("ms-duplex-text已经更名为ms-duplex-string")\r
2946                 }\r
2947                 if (casting[name]) {\r
2948                     hasCast = true\r
2949                 }\r
2950                 avalon.Array.ensure(params, name)\r
2951             })\r
2952             if (!hasCast) {\r
2953                 params.push("string")\r
2954             }\r
2955             data.param = params.join("-")\r
2956             data.bound = function (type, callback) {\r
2957                 if (elem.addEventListener) {\r
2958                     elem.addEventListener(type, callback, false)\r
2959                 } else {\r
2960                     elem.attachEvent("on" + type, callback)\r
2961                 }\r
2962                 var old = data.rollback\r
2963                 data.rollback = function () {\r
2964                     elem.avalonSetter = null\r
2965                     avalon.unbind(elem, type, callback)\r
2966                     old && old()\r
2967                 }\r
2968             }\r
2969             for (var i in avalon.vmodels) {\r
2970                 var v = avalon.vmodels[i]\r
2971                 v.$fire("avalon-ms-duplex-init", data)\r
2972             }\r
2973             var cpipe = data.pipe || (data.pipe = pipe)\r
2974             cpipe(null, data, "init")\r
2975             var tagName = elem.tagName\r
2976             duplexBinding[tagName] && duplexBinding[tagName](elem, data.evaluator.apply(null, data.args), data)\r
2977         }\r
2978     }\r
2979 //不存在 bindingExecutors.duplex\r
2980 \r
2981     function fixNull(val) {\r
2982         return val == null ? "" : val\r
2983     }\r
2984 \r
2985     avalon.duplexHooks = {\r
2986         checked: {\r
2987             get: function (val, data) {\r
2988                 return !data.element.oldValue\r
2989             }\r
2990         },\r
2991         string: {\r
2992             get: function (val) { //同步到VM\r
2993                 return val\r
2994             },\r
2995             set: fixNull\r
2996         },\r
2997         "boolean": {\r
2998             get: function (val) {\r
2999                 return val === "true"\r
3000             },\r
3001             set: fixNull\r
3002         },\r
3003         number: {\r
3004             get: function (val, data) {\r
3005                 var number = parseFloat(val)\r
3006                 if (-val === -number) {\r
3007                     return number\r
3008                 }\r
3009                 var arr = /strong|medium|weak/.exec(data.element.getAttribute("data-duplex-number")) || ["medium"]\r
3010                 switch (arr[0]) {\r
3011                     case "strong":\r
3012                         return 0\r
3013                     case "medium":\r
3014                         return val === "" ? "" : 0\r
3015                     case "weak":\r
3016                         return val\r
3017                 }\r
3018             },\r
3019             set: fixNull\r
3020         }\r
3021     }\r
3022 \r
3023     function pipe(val, data, action, e) {\r
3024         data.param.replace(/\w+/g, function (name) {\r
3025             var hook = avalon.duplexHooks[name]\r
3026             if (hook && typeof hook[action] === "function") {\r
3027                 val = hook[action](val, data)\r
3028             }\r
3029         })\r
3030         return val\r
3031     }\r
3032 \r
3033     var TimerID, ribbon = []\r
3034 \r
3035     avalon.tick = function (fn) {\r
3036         if (ribbon.push(fn) === 1) {\r
3037             TimerID = setInterval(ticker, 60)\r
3038         }\r
3039     }\r
3040 \r
3041     function ticker() {\r
3042         for (var n = ribbon.length - 1; n >= 0; n--) {\r
3043             var el = ribbon[n]\r
3044             if (el() === false) {\r
3045                 ribbon.splice(n, 1)\r
3046             }\r
3047         }\r
3048         if (!ribbon.length) {\r
3049             clearInterval(TimerID)\r
3050         }\r
3051     }\r
3052 \r
3053     var watchValueInTimer = noop\r
3054     var rmsinput = /text|password|hidden/\r
3055     new function () { // jshint ignore:line\r
3056         try { //#272 IE9-IE11, firefox\r
3057             var setters = {}\r
3058             var aproto = HTMLInputElement.prototype\r
3059             var bproto = HTMLTextAreaElement.prototype\r
3060 \r
3061             function newSetter(value) { // jshint ignore:line\r
3062                 if (avalon.contains(root, this)) {\r
3063                     setters[this.tagName].call(this, value)\r
3064                     if (!rmsinput.test(this.type))\r
3065                         return\r
3066                     if (!this.msFocus && this.avalonSetter) {\r
3067                         this.avalonSetter()\r
3068                     }\r
3069                 }\r
3070             }\r
3071 \r
3072             var inputProto = HTMLInputElement.prototype\r
3073             Object.getOwnPropertyNames(inputProto) //故意引发IE6-8等浏览器报错\r
3074             setters["INPUT"] = Object.getOwnPropertyDescriptor(aproto, "value").set\r
3075             Object.defineProperty(aproto, "value", {\r
3076                 set: newSetter\r
3077             })\r
3078             setters["TEXTAREA"] = Object.getOwnPropertyDescriptor(bproto, "value").set\r
3079             Object.defineProperty(bproto, "value", {\r
3080                 set: newSetter\r
3081             })\r
3082         } catch (e) {\r
3083             //在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了\r
3084             // http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype\r
3085             // https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1\r
3086             watchValueInTimer = avalon.tick\r
3087         }\r
3088     } // jshint ignore:line\r
3089 //处理radio, checkbox, text, textarea, password\r
3090     duplexBinding.INPUT = function (element, evaluator, data) {\r
3091         var $type = element.type,\r
3092             bound = data.bound,\r
3093             $elem = avalon(element),\r
3094             composing = false\r
3095 \r
3096         function callback(value) {\r
3097             data.changed.call(this, value, data)\r
3098         }\r
3099 \r
3100         function compositionStart() {\r
3101             composing = true\r
3102         }\r
3103 \r
3104         function compositionEnd() {\r
3105             composing = false\r
3106         }\r
3107 \r
3108         //当value变化时改变model的值\r
3109 \r
3110         var updateVModel = function () {\r
3111             if (composing) //处理中文输入法在minlengh下引发的BUG\r
3112                 return\r
3113             var val = element.oldValue = element.value //防止递归调用形成死循环\r
3114             var lastValue = data.pipe(val, data, "get")\r
3115             if ($elem.data("duplexObserve") !== false) {\r
3116                 evaluator(lastValue)\r
3117                 callback.call(element, lastValue)\r
3118                 if ($elem.data("duplex-focus")) {\r
3119                     avalon.nextTick(function () {\r
3120                         element.focus()\r
3121                     })\r
3122                 }\r
3123             }\r
3124         }\r
3125         //当model变化时,它就会改变value的值\r
3126         data.handler = function () {\r
3127             var val = data.pipe(evaluator(), data, "set") + ""\r
3128             if (val !== element.oldValue) {\r
3129                 element.value = val\r
3130             }\r
3131         }\r
3132         if (data.isChecked || $type === "radio") {\r
3133             updateVModel = function () {\r
3134                 if ($elem.data("duplexObserve") !== false) {\r
3135                     var lastValue = data.pipe(element.value, data, "get")\r
3136                     evaluator(lastValue)\r
3137                     callback.call(element, lastValue)\r
3138                 }\r
3139             }\r
3140             data.handler = function () {\r
3141                 var val = evaluator()\r
3142                 var checked = data.isChecked ? !!val : val + "" === element.value\r
3143                 element.checked = element.oldValue = checked\r
3144             }\r
3145             bound("click", updateVModel)\r
3146         } else if ($type === "checkbox") {\r
3147             updateVModel = function () {\r
3148                 if ($elem.data("duplexObserve") !== false) {\r
3149                     var method = element.checked ? "ensure" : "remove"\r
3150                     var array = evaluator()\r
3151                     if (!Array.isArray(array)) {\r
3152                         log("ms-duplex应用于checkbox上要对应一个数组")\r
3153                         array = [array]\r
3154                     }\r
3155                     avalon.Array[method](array, data.pipe(element.value, data, "get"))\r
3156                     callback.call(element, array)\r
3157                 }\r
3158             }\r
3159             data.handler = function () {\r
3160                 var array = [].concat(evaluator()) //强制转换为数组\r
3161                 element.checked = array.indexOf(data.pipe(element.value, data, "get")) > -1\r
3162             }\r
3163             bound("change", updateVModel)\r
3164         } else {\r
3165             var events = element.getAttribute("data-duplex-event") || "input"\r
3166             if (element.attributes["data-event"]) {\r
3167                 log("data-event指令已经废弃,请改用data-duplex-event")\r
3168             }\r
3169             events.replace(rword, function (name) {\r
3170                 switch (name) {\r
3171                     case "input":\r
3172                         bound("input", updateVModel)\r
3173                         bound("DOMAutoComplete", updateVModel)\r
3174                         if (!IEVersion) {\r
3175                             bound("compositionstart", compositionStart)\r
3176                             bound("compositionend", compositionEnd)\r
3177                         }\r
3178                         break\r
3179                     default:\r
3180                         bound(name, updateVModel)\r
3181                         break\r
3182                 }\r
3183             })\r
3184             bound("focus", function () {\r
3185                 element.msFocus = true\r
3186             })\r
3187             bound("blur", function () {\r
3188                 element.msFocus = false\r
3189             })\r
3190             if (rmsinput.test($type)) {\r
3191                 watchValueInTimer(function () {\r
3192                     if (root.contains(element)) {\r
3193                         if (!element.msFocus && element.oldValue !== element.value) {\r
3194                             updateVModel()\r
3195                         }\r
3196                     } else if (!element.msRetain) {\r
3197                         return false\r
3198                     }\r
3199                 })\r
3200             }\r
3201 \r
3202             element.avalonSetter = updateVModel\r
3203         }\r
3204 \r
3205         element.oldValue = element.value\r
3206         avalon.injectBinding(data)\r
3207         callback.call(element, element.value)\r
3208     }\r
3209     duplexBinding.TEXTAREA = duplexBinding.INPUT\r
3210     duplexBinding.SELECT = function (element, evaluator, data) {\r
3211         var $elem = avalon(element)\r
3212 \r
3213         function updateVModel() {\r
3214             if ($elem.data("duplexObserve") !== false) {\r
3215                 var val = $elem.val() //字符串或字符串数组\r
3216                 if (Array.isArray(val)) {\r
3217                     val = val.map(function (v) {\r
3218                         return data.pipe(v, data, "get")\r
3219                     })\r
3220                 } else {\r
3221                     val = data.pipe(val, data, "get")\r
3222                 }\r
3223                 if (val + "" !== element.oldValue) {\r
3224                     evaluator(val)\r
3225                 }\r
3226                 data.changed.call(element, val, data)\r
3227             }\r
3228         }\r
3229 \r
3230         data.handler = function () {\r
3231             var val = evaluator()\r
3232             val = val && val.$model || val\r
3233             if (Array.isArray(val)) {\r
3234                 if (!element.multiple) {\r
3235                     log("ms-duplex在<select multiple=true>上要求对应一个数组")\r
3236                 }\r
3237             } else {\r
3238                 if (element.multiple) {\r
3239                     log("ms-duplex在<select multiple=false>不能对应一个数组")\r
3240                 }\r
3241             }\r
3242             //必须变成字符串后才能比较\r
3243             val = Array.isArray(val) ? val.map(String) : val + ""\r
3244             if (val + "" !== element.oldValue) {\r
3245                 $elem.val(val)\r
3246                 element.oldValue = val + ""\r
3247             }\r
3248         }\r
3249         data.bound("change", updateVModel)\r
3250         element.msCallback = function () {\r
3251             avalon.injectBinding(data)\r
3252             data.changed.call(element, evaluator(), data)\r
3253         }\r
3254     }\r
3255 // bindingHandlers.html 定义在if.js\r
3256     bindingExecutors.html = function (val, elem, data) {\r
3257         val = val == null ? "" : val\r
3258         var isHtmlFilter = "group" in data\r
3259         var parent = isHtmlFilter ? elem.parentNode : elem\r
3260         if (!parent)\r
3261             return\r
3262         if (typeof val === "string") {\r
3263             var fragment = avalon.parseHTML(val)\r
3264         } else if (val.nodeType === 11) { //将val转换为文档碎片\r
3265             fragment = val\r
3266         } else if (val.nodeType === 1 || val.item) {\r
3267             var nodes = val.nodeType === 1 ? val.childNodes : val.item\r
3268             fragment = hyperspace.cloneNode(true)\r
3269             while (nodes[0]) {\r
3270                 fragment.appendChild(nodes[0])\r
3271             }\r
3272         }\r
3273         if (!fragment.firstChild) {\r
3274             fragment.appendChild(DOC.createComment("ms-html"))\r
3275         }\r
3276         nodes = avalon.slice(fragment.childNodes)\r
3277         //插入占位符, 如果是过滤器,需要有节制地移除指定的数量,如果是html指令,直接清空\r
3278         if (isHtmlFilter) {\r
3279             var n = data.group,\r
3280                 i = 1\r
3281 \r
3282             data.group = nodes.length\r
3283             data.element = nodes[0]\r
3284 \r
3285             while (i < n) {\r
3286                 var node = elem.nextSibling\r
3287                 if (node) {\r
3288                     parent.removeChild(node)\r
3289                     i++\r
3290                 }\r
3291             }\r
3292             parent.replaceChild(fragment, elem)\r
3293         } else {\r
3294             avalon.clearHTML(parent).appendChild(fragment)\r
3295         }\r
3296         scanNodeArray(nodes, data.vmodels)\r
3297     }\r
3298     bindingHandlers["if"] =\r
3299         bindingHandlers.data =\r
3300             bindingHandlers.text =\r
3301                 bindingHandlers.html =\r
3302                     function (data, vmodels) {\r
3303                         parseExprProxy(data.value, vmodels, data)\r
3304                     }\r
3305 \r
3306     bindingExecutors["if"] = function (val, elem, data) {\r
3307         if (val) { //插回DOM树\r
3308             if (elem.nodeType === 8) {\r
3309                 elem.parentNode.replaceChild(data.template, elem)\r
3310                 elem = data.element = data.template //这时可能为null\r
3311             }\r
3312             if (elem.getAttribute(data.name)) {\r
3313                 elem.removeAttribute(data.name)\r
3314                 scanAttr(elem, data.vmodels)\r
3315             }\r
3316             data.rollback = null\r
3317         } else { //移出DOM树,并用注释节点占据原位置\r
3318             if (elem.nodeType === 1) {\r
3319                 var node = data.element = DOC.createComment("ms-if")\r
3320                 elem.parentNode.replaceChild(node, elem)\r
3321                 data.template = elem //元素节点\r
3322                 ifGroup.appendChild(elem)\r
3323                 data.rollback = function () {\r
3324                     if (elem.parentNode === ifGroup) {\r
3325                         ifGroup.removeChild(elem)\r
3326                     }\r
3327                 }\r
3328             }\r
3329         }\r
3330     }\r
3331 //ms-important绑定已经在scanTag 方法中实现\r
3332 //ms-include绑定已由ms-attr绑定实现\r
3333 \r
3334     var rdash = /\(([^)]*)\)/\r
3335     bindingHandlers.on = function (data, vmodels) {\r
3336         var value = data.value\r
3337         data.type = "on"\r
3338         var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10\r
3339         if (typeof bindingHandlers.on[eventType + "Hook"] === "function") {\r
3340             bindingHandlers.on[eventType + "Hook"](data)\r
3341         }\r
3342         if (value.indexOf("(") > 0 && value.indexOf(")") > -1) {\r
3343             var matched = (value.match(rdash) || ["", ""])[1].trim()\r
3344             if (matched === "" || matched === "$event") { // aaa() aaa($event)当成aaa处理\r
3345                 value = value.replace(rdash, "")\r
3346             }\r
3347         }\r
3348         parseExprProxy(value, vmodels, data)\r
3349     }\r
3350 \r
3351     bindingExecutors.on = function (callback, elem, data) {\r
3352         callback = function (e) {\r
3353             var fn = data.evaluator || noop\r
3354             return fn.apply(this, data.args.concat(e))\r
3355         }\r
3356         var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10\r
3357         if (eventType === "scan") {\r
3358             callback.call(elem, {\r
3359                 type: eventType\r
3360             })\r
3361         } else if (typeof data.specialBind === "function") {\r
3362             data.specialBind(elem, callback)\r
3363         } else {\r
3364             var removeFn = avalon.bind(elem, eventType, callback)\r
3365         }\r
3366         data.rollback = function () {\r
3367             if (typeof data.specialUnbind === "function") {\r
3368                 data.specialUnbind()\r
3369             } else {\r
3370                 avalon.unbind(elem, eventType, removeFn)\r
3371             }\r
3372         }\r
3373     }\r
3374     bindingHandlers.repeat = function (data, vmodels) {\r
3375         var type = data.type\r
3376         parseExprProxy(data.value, vmodels, data, 0, 1)\r
3377         var freturn = false\r
3378         try {\r
3379             var $repeat = data.$repeat = data.evaluator.apply(0, data.args || [])\r
3380             var xtype = avalon.type($repeat)\r
3381             if (xtype !== "object" && xtype !== "array") {\r
3382                 freturn = true\r
3383                 avalon.log("warning:" + data.value + "只能是对象或数组")\r
3384             }\r
3385         } catch (e) {\r
3386             freturn = true\r
3387         }\r
3388 \r
3389         var arr = data.value.split(".") || []\r
3390         if (arr.length > 1) {\r
3391             arr.pop()\r
3392             var n = arr[0]\r
3393             for (var i = 0, v; v = vmodels[i++];) {\r
3394                 if (v && v.hasOwnProperty(n)) {\r
3395                     var events = v[n].$events || {}\r
3396                     events[subscribers] = events[subscribers] || []\r
3397                     events[subscribers].push(data)\r
3398                     break\r
3399                 }\r
3400             }\r
3401         }\r
3402         var elem = data.element\r
3403         elem.removeAttribute(data.name)\r
3404 \r
3405         data.sortedCallback = getBindingCallback(elem, "data-with-sorted", vmodels)\r
3406         data.renderedCallback = getBindingCallback(elem, "data-" + type + "-rendered", vmodels)\r
3407         var signature = generateID(type)\r
3408         var comment = data.element = DOC.createComment(signature + ":end")\r
3409         data.clone = DOC.createComment(signature)\r
3410         hyperspace.appendChild(comment)\r
3411 \r
3412         if (type === "each" || type === "with") {\r
3413             data.template = elem.innerHTML.trim()\r
3414             avalon.clearHTML(elem).appendChild(comment)\r
3415         } else {\r
3416             data.template = elem.outerHTML.trim()\r
3417             elem.parentNode.replaceChild(comment, elem)\r
3418         }\r
3419         data.template = avalon.parseHTML(data.template)\r
3420         data.rollback = function () {\r
3421             var elem = data.element\r
3422             if (!elem)\r
3423                 return\r
3424             bindingExecutors.repeat.call(data, "clear")\r
3425             var parentNode = elem.parentNode\r
3426             var content = data.template\r
3427             var target = content.firstChild\r
3428             parentNode.replaceChild(content, elem)\r
3429             var start = data.$with\r
3430             start && start.parentNode && start.parentNode.removeChild(start)\r
3431             target = data.element = data.type === "repeat" ? target : parentNode\r
3432         }\r
3433         if (freturn) {\r
3434             return\r
3435         }\r
3436         data.handler = bindingExecutors.repeat\r
3437         data.$outer = {}\r
3438         var check0 = "$key"\r
3439         var check1 = "$val"\r
3440         if (Array.isArray($repeat)) {\r
3441             $repeat.$map[data.param || "el"] = 1\r
3442             check0 = "$first"\r
3443             check1 = "$last"\r
3444         }\r
3445         for (i = 0; v = vmodels[i++];) {\r
3446             if (v.hasOwnProperty(check0) && v.hasOwnProperty(check1)) {\r
3447                 data.$outer = v\r
3448                 break\r
3449             }\r
3450         }\r
3451         var $events = $repeat.$events\r
3452         var $list = ($events || {})[subscribers]\r
3453         if ($list && avalon.Array.ensure($list, data)) {\r
3454             addSubscribers(data, $list)\r
3455         }\r
3456         if (xtype === "object") {\r
3457             data.$with = true\r
3458             var pool = !$events ? {} : $events.$withProxyPool || ($events.$withProxyPool = {})\r
3459             data.handler("append", $repeat, pool)\r
3460         } else if ($repeat.length) {\r
3461             data.handler("add", 0, $repeat.length)\r
3462         }\r
3463     }\r
3464 \r
3465     bindingExecutors.repeat = function (method, pos, el) {\r
3466         if (method) {\r
3467             var data = this, start, fragment\r
3468             var end = data.element\r
3469             var comments = getComments(data)\r
3470             var parent = end.parentNode\r
3471             var transation = hyperspace.cloneNode(false)\r
3472             switch (method) {\r
3473                 case "add": //在pos位置后添加el数组(pos为插入位置,el为要插入的个数)\r
3474                     var n = pos + el\r
3475                     var fragments = []\r
3476                     var array = data.$repeat\r
3477                     for (var i = pos; i < n; i++) {\r
3478                         var proxy = array.$proxy[i]\r
3479                         proxy.$outer = data.$outer\r
3480                         shimController(data, transation, proxy, fragments)\r
3481                     }\r
3482                     parent.insertBefore(transation, comments[pos] || end)\r
3483                     for (i = 0; fragment = fragments[i++];) {\r
3484                         scanNodeArray(fragment.nodes, fragment.vmodels)\r
3485                         fragment.nodes = fragment.vmodels = null\r
3486                     }\r
3487                     break\r
3488                 case "del": //将pos后的el个元素删掉(pos, el都是数字)\r
3489                     sweepNodes(comments[pos], comments[pos + el] || end)\r
3490                     break\r
3491                 case "clear":\r
3492                     start = comments[0]\r
3493                     if (start) {\r
3494                         sweepNodes(start, end)\r
3495                     }\r
3496                     break\r
3497                 case "move":\r
3498                     start = comments[0]\r
3499                     if (start) {\r
3500                         var signature = start.nodeValue\r
3501                         var rooms = []\r
3502                         var room = [],\r
3503                             node\r
3504                         sweepNodes(start, end, function () {\r
3505                             room.unshift(this)\r
3506                             if (this.nodeValue === signature) {\r
3507                                 rooms.unshift(room)\r
3508                                 room = []\r
3509                             }\r
3510                         })\r
3511                         sortByIndex(rooms, pos)\r
3512                         while (room = rooms.shift()) {\r
3513                             while (node = room.shift()) {\r
3514                                 transation.appendChild(node)\r
3515                             }\r
3516                         }\r
3517                         parent.insertBefore(transation, end)\r
3518                     }\r
3519                     break\r
3520                 case "append": //将pos的键值对从el中取出(pos为一个普通对象,el为预先生成好的代理VM对象池)\r
3521                     var pool = el\r
3522                     var keys = []\r
3523                     fragments = []\r
3524                     for (var key in pos) { //得到所有键名\r
3525                         if (pos.hasOwnProperty(key) && key !== "hasOwnProperty") {\r
3526                             keys.push(key)\r
3527                         }\r
3528                     }\r
3529                     if (data.sortedCallback) { //如果有回调,则让它们排序\r
3530                         var keys2 = data.sortedCallback.call(parent, keys)\r
3531                         if (keys2 && Array.isArray(keys2) && keys2.length) {\r
3532                             keys = keys2\r
3533                         }\r
3534                     }\r
3535                     for (i = 0; key = keys[i++];) {\r
3536                         if (key !== "hasOwnProperty") {\r
3537                             if (!pool[key]) {\r
3538                                 pool[key] = withProxyAgent(key, data)\r
3539                             }\r
3540                             shimController(data, transation, pool[key], fragments)\r
3541                         }\r
3542                     }\r
3543                     var comment = data.$with = data.clone\r
3544                     parent.insertBefore(comment, end)\r
3545                     parent.insertBefore(transation, end)\r
3546                     for (i = 0; fragment = fragments[i++];) {\r
3547                         scanNodeArray(fragment.nodes, fragment.vmodels)\r
3548                         fragment.nodes = fragment.vmodels = null\r
3549                     }\r
3550                     break\r
3551             }\r
3552             if (method === "clear")\r
3553                 method = "del"\r
3554             var callback = data.renderedCallback || noop,\r
3555                 args = arguments\r
3556             checkScan(parent, function () {\r
3557                 callback.apply(parent, args)\r
3558                 if (parent.oldValue && parent.tagName === "SELECT") { //fix #503\r
3559                     avalon(parent).val(parent.oldValue.split(","))\r
3560                 }\r
3561             }, NaN)\r
3562         }\r
3563     }\r
3564 \r
3565     "with,each".replace(rword, function (name) {\r
3566         bindingHandlers[name] = bindingHandlers.repeat\r
3567     })\r
3568 \r
3569     function shimController(data, transation, proxy, fragments) {\r
3570         var content = data.template.cloneNode(true)\r
3571         var nodes = avalon.slice(content.childNodes)\r
3572         if (!data.$with) {\r
3573             content.insertBefore(data.clone.cloneNode(false), content.firstChild)\r
3574         }\r
3575         transation.appendChild(content)\r
3576         var nv = [proxy].concat(data.vmodels)\r
3577         var fragment = {\r
3578             nodes: nodes,\r
3579             vmodels: nv\r
3580         }\r
3581         fragments.push(fragment)\r
3582     }\r
3583 \r
3584     function getComments(data) {\r
3585         var end = data.element\r
3586         var signature = end.nodeValue.replace(":end", "")\r
3587         var node = end.previousSibling\r
3588         var array = []\r
3589         while (node) {\r
3590             if (node.nodeValue === signature) {\r
3591                 array.unshift(node)\r
3592             }\r
3593             node = node.previousSibling\r
3594         }\r
3595         return array\r
3596     }\r
3597 \r
3598 \r
3599 //移除掉start与end之间的节点(保留end)\r
3600     function sweepNodes(start, end, callback) {\r
3601         while (true) {\r
3602             var node = end.previousSibling\r
3603             if (!node)\r
3604                 break\r
3605             node.parentNode.removeChild(node)\r
3606             callback && callback.call(node)\r
3607             if (node === start) {\r
3608                 break\r
3609             }\r
3610         }\r
3611     }\r
3612 \r
3613 // 为ms-each,ms-with, ms-repeat会创建一个代理VM,\r
3614 // 通过它们保持一个下上文,让用户能调用$index,$first,$last,$remove,$key,$val,$outer等属性与方法\r
3615 // 所有代理VM的产生,消费,收集,存放通过xxxProxyFactory,xxxProxyAgent, recycleProxies,xxxProxyPool实现\r
3616     var eachProxyPool = []\r
3617     var withProxyPool = []\r
3618 \r
3619     function eachProxyFactory() {\r
3620         var source = {\r
3621             $index: 0,\r
3622             $first: false,\r
3623             $last: false,\r
3624             $map: {},\r
3625             $host: [],\r
3626             $outer: {},\r
3627             $remove: avalon.noop,\r
3628             el: {\r
3629                 get: function () {\r
3630                     var e = this.$events\r
3631                     var array = e.$index\r
3632                     e.$index = e.el //#817 通过$index为el收集依赖\r
3633                     try {\r
3634                         return this.$host[this.$index]\r
3635                     } finally {\r
3636                         e.$index = array\r
3637                     }\r
3638                 },\r
3639                 set: function (val) {\r
3640                     this.$host.set(this.$index, val)\r
3641                 }\r
3642             }\r
3643         }\r
3644 \r
3645         var second = {\r
3646             $last: 1,\r
3647             $first: 1,\r
3648             $index: 1\r
3649         }\r
3650         var proxy = modelFactory(source, second)\r
3651         proxy.$id = generateID("$proxy$each")\r
3652         return proxy\r
3653     }\r
3654 \r
3655     function eachProxyAgent(index, host) {\r
3656         var proxy = eachProxyPool.shift()\r
3657         if (!proxy) {\r
3658             proxy = eachProxyFactory()\r
3659         }\r
3660         var last = host.length - 1\r
3661         proxy.$index = index\r
3662         proxy.$first = index === 0\r
3663         proxy.$last = index === last\r
3664         proxy.$map = host.$map\r
3665         proxy.$host = host\r
3666         proxy.$remove = function () {\r
3667             return host.removeAt(proxy.$index)\r
3668         }\r
3669         return proxy\r
3670     }\r
3671 \r
3672     function withProxyFactory() {\r
3673         var proxy = modelFactory({\r
3674             $key: "",\r
3675             $outer: {},\r
3676             $host: {},\r
3677             $val: {\r
3678                 get: function () {\r
3679                     return this.$host[this.$key]\r
3680                 },\r
3681                 set: function (val) {\r
3682                     this.$host[this.$key] = val\r
3683                 }\r
3684             }\r
3685         }, {\r
3686             $val: 1\r
3687         })\r
3688         proxy.$id = generateID("$proxy$with")\r
3689         return proxy\r
3690     }\r
3691 \r
3692     function withProxyAgent(key, data) {\r
3693         var proxy = withProxyPool.pop()\r
3694         if (!proxy) {\r
3695             proxy = withProxyFactory()\r
3696         }\r
3697         var host = data.$repeat\r
3698         proxy.$key = key\r
3699         proxy.$host = host\r
3700         proxy.$outer = data.$outer\r
3701         if (host.$events) {\r
3702             proxy.$events.$val = host.$events[key]\r
3703         } else {\r
3704             proxy.$events = {}\r
3705         }\r
3706         return proxy\r
3707     }\r
3708 \r
3709     function recycleProxies(proxies, type) {\r
3710         var proxyPool = type === "each" ? eachProxyPool : withProxyPool\r
3711         avalon.each(proxies, function (key, proxy) {\r
3712             if (proxy.$events) {\r
3713                 for (var i in proxy.$events) {\r
3714                     if (Array.isArray(proxy.$events[i])) {\r
3715                         proxy.$events[i].forEach(function (data) {\r
3716                             if (typeof data === "object")\r
3717                                 disposeData(data)\r
3718                         }) // jshint ignore:line\r
3719                         proxy.$events[i].length = 0\r
3720                     }\r
3721                 }\r
3722                 proxy.$host = proxy.$outer = {}\r
3723                 if (proxyPool.unshift(proxy) > kernel.maxRepeatSize) {\r
3724                     proxyPool.pop()\r
3725                 }\r
3726             }\r
3727         })\r
3728         if (type === "each")\r
3729             proxies.length = 0\r
3730     }\r
3731 \r
3732     /*********************************************************************\r
3733      *                         各种指令                                  *\r
3734      **********************************************************************/\r
3735 //ms-skip绑定已经在scanTag 方法中实现\r
3736 // bindingHandlers.text 定义在if.js\r
3737     bindingExecutors.text = function (val, elem) {\r
3738         val = val == null ? "" : val //不在页面上显示undefined null\r
3739         if (elem.nodeType === 3) { //绑定在文本节点上\r
3740             try { //IE对游离于DOM树外的节点赋值会报错\r
3741                 elem.data = val\r
3742             } catch (e) {\r
3743             }\r
3744         } else { //绑定在特性节点上\r
3745             elem.textContent = val\r
3746         }\r
3747     }\r
3748     function parseDisplay(nodeName, val) {\r
3749         //用于取得此类标签的默认display值\r
3750         var key = "_" + nodeName\r
3751         if (!parseDisplay[key]) {\r
3752             var node = DOC.createElement(nodeName)\r
3753             root.appendChild(node)\r
3754             if (W3C) {\r
3755                 val = getComputedStyle(node, null).display\r
3756             } else {\r
3757                 val = node.currentStyle.display\r
3758             }\r
3759             root.removeChild(node)\r
3760             parseDisplay[key] = val\r
3761         }\r
3762         return parseDisplay[key]\r
3763     }\r
3764 \r
3765     avalon.parseDisplay = parseDisplay\r
3766 \r
3767     bindingHandlers.visible = function (data, vmodels) {\r
3768         var elem = avalon(data.element)\r
3769         var display = elem.css("display")\r
3770         if (display === "none") {\r
3771             var style = elem[0].style\r
3772             var has = /visibility/i.test(style.cssText)\r
3773             var visible = elem.css("visibility")\r
3774             style.display = ""\r
3775             style.visibility = "hidden"\r
3776             display = elem.css("display")\r
3777             if (display === "none") {\r
3778                 display = parseDisplay(elem[0].nodeName)\r
3779             }\r
3780             style.visibility = has ? visible : ""\r
3781         }\r
3782         data.display = display\r
3783         parseExprProxy(data.value, vmodels, data)\r
3784     }\r
3785 \r
3786     bindingExecutors.visible = function (val, elem, data) {\r
3787         elem.style.display = val ? data.display : "none"\r
3788     }\r
3789     bindingHandlers.widget = function (data, vmodels) {\r
3790         var args = data.value.match(rword)\r
3791         var elem = data.element\r
3792         var widget = args[0]\r
3793         var id = args[1]\r
3794         if (!id || id === "$") { //没有定义或为$时,取组件名+随机数\r
3795             id = generateID(widget)\r
3796         }\r
3797         var optName = args[2] || widget //没有定义,取组件名\r
3798         var constructor = avalon.ui[widget]\r
3799         if (typeof constructor === "function") { //ms-widget="tabs,tabsAAA,optname"\r
3800             vmodels = elem.vmodels || vmodels\r
3801             for (var i = 0, v; v = vmodels[i++];) {\r
3802                 if (v.hasOwnProperty(optName) && typeof v[optName] === "object") {\r
3803                     var vmOptions = v[optName]\r
3804                     vmOptions = vmOptions.$model || vmOptions\r
3805                     break\r
3806                 }\r
3807             }\r
3808             if (vmOptions) {\r
3809                 var wid = vmOptions[widget + "Id"]\r
3810                 if (typeof wid === "string") {\r
3811                     log("warning!不再支持" + widget + "Id")\r
3812                     id = wid\r
3813                 }\r
3814             }\r
3815             //抽取data-tooltip-text、data-tooltip-attr属性,组成一个配置对象\r
3816             var widgetData = avalon.getWidgetData(elem, widget)\r
3817             data.value = [widget, id, optName].join(",")\r
3818             data[widget + "Id"] = id\r
3819             data.evaluator = noop\r
3820             elem.msData["ms-widget-id"] = id\r
3821             var options = data[widget + "Options"] = avalon.mix({}, constructor.defaults, vmOptions || {}, widgetData)\r
3822             elem.removeAttribute("ms-widget")\r
3823             var vmodel = constructor(elem, data, vmodels) || {} //防止组件不返回VM\r
3824             if (vmodel.$id) {\r
3825                 avalon.vmodels[id] = vmodel\r
3826                 createSignalTower(elem, vmodel)\r
3827                 try {\r
3828                     vmodel.$init(function () {\r
3829                         avalon.scan(elem, [vmodel].concat(vmodels))\r
3830                         if (typeof options.onInit === "function") {\r
3831                             options.onInit.call(elem, vmodel, options, vmodels)\r
3832                         }\r
3833                     })\r
3834                 } catch (e) {\r
3835                 }\r
3836                 data.rollback = function () {\r
3837                     try {\r
3838                         vmodel.widgetElement = null\r
3839                         vmodel.$remove()\r
3840                     } catch (e) {\r
3841                     }\r
3842                     elem.msData = {}\r
3843                     delete avalon.vmodels[vmodel.$id]\r
3844                 }\r
3845                 addSubscribers(data, widgetList)\r
3846                 if (window.chrome) {\r
3847                     elem.addEventListener("DOMNodeRemovedFromDocument", function () {\r
3848                         setTimeout(removeSubscribers)\r
3849                     })\r
3850                 }\r
3851             } else {\r
3852                 avalon.scan(elem, vmodels)\r
3853             }\r
3854         } else if (vmodels.length) { //如果该组件还没有加载,那么保存当前的vmodels\r
3855             elem.vmodels = vmodels\r
3856         }\r
3857     }\r
3858     var widgetList = []\r
3859 //不存在 bindingExecutors.widget\r
3860     /*********************************************************************\r
3861      *                             自带过滤器                            *\r
3862      **********************************************************************/\r
3863     var rscripts = /<script[^>]*>([\S\s]*?)<\/script\s*>/gim\r
3864     var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g\r
3865     var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig\r
3866     var rsanitize = {\r
3867         a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig,\r
3868         img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig,\r
3869         form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig\r
3870     }\r
3871     var rsurrogate = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g\r
3872     var rnoalphanumeric = /([^\#-~| |!])/g;\r
3873 \r
3874     function numberFormat(number, decimals, point, thousands) {\r
3875         //form http://phpjs.org/functions/number_format/\r
3876         //number        必需,要格式化的数字\r
3877         //decimals      可选,规定多少个小数位。\r
3878         //point 可选,规定用作小数点的字符串(默认为 . )。\r
3879         //thousands     可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。\r
3880         number = (number + '')\r
3881             .replace(/[^0-9+\-Ee.]/g, '')\r
3882         var n = !isFinite(+number) ? 0 : +number,\r
3883             prec = !isFinite(+decimals) ? 3 : Math.abs(decimals),\r
3884             sep = thousands || ",",\r
3885             dec = point || ".",\r
3886             s = '',\r
3887             toFixedFix = function (n, prec) {\r
3888                 var k = Math.pow(10, prec)\r
3889                 return '' + (Math.round(n * k) / k)\r
3890                         .toFixed(prec)\r
3891             }\r
3892         // Fix for IE parseFloat(0.55).toFixed(0) = 0;\r
3893         s = (prec ? toFixedFix(n, prec) : '' + Math.round(n))\r
3894             .split('.')\r
3895         if (s[0].length > 3) {\r
3896             s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep)\r
3897         }\r
3898         if ((s[1] || '')\r
3899                 .length < prec) {\r
3900             s[1] = s[1] || ''\r
3901             s[1] += new Array(prec - s[1].length + 1)\r
3902                 .join('0')\r
3903         }\r
3904         return s.join(dec)\r
3905     }\r
3906 \r
3907 \r
3908     var filters = avalon.filters = {\r
3909         uppercase: function (str) {\r
3910             return str.toUpperCase()\r
3911         },\r
3912         lowercase: function (str) {\r
3913             return str.toLowerCase()\r
3914         },\r
3915         truncate: function (str, length, truncation) {\r
3916             //length,新字符串长度,truncation,新字符串的结尾的字段,返回新字符串\r
3917             length = length || 30\r
3918             truncation = truncation === void(0) ? "..." : truncation\r
3919             return str.length > length ? str.slice(0, length - truncation.length) + truncation : String(str)\r
3920         },\r
3921         $filter: function (val) {\r
3922             for (var i = 1, n = arguments.length; i < n; i++) {\r
3923                 var array = arguments[i]\r
3924                 var fn = avalon.filters[array.shift()]\r
3925                 if (typeof fn === "function") {\r
3926                     var arr = [val].concat(array)\r
3927                     val = fn.apply(null, arr)\r
3928                 }\r
3929             }\r
3930             return val\r
3931         },\r
3932         camelize: camelize,\r
3933         //https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet\r
3934         //    <a href="javasc&NewLine;ript&colon;alert('XSS')">chrome</a> \r
3935         //    <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">chrome</a>\r
3936         //    <a href="jav      ascript:alert('XSS');">IE67chrome</a>\r
3937         //    <a href="jav&#x09;ascript:alert('XSS');">IE67chrome</a>\r
3938         //    <a href="jav&#x0A;ascript:alert('XSS');">IE67chrome</a>\r
3939         sanitize: function (str) {\r
3940             return str.replace(rscripts, "").replace(ropen, function (a, b) {\r
3941                 var match = a.toLowerCase().match(/<(\w+)\s/)\r
3942                 if (match) { //处理a标签的href属性,img标签的src属性,form标签的action属性\r
3943                     var reg = rsanitize[match[1]]\r
3944                     if (reg) {\r
3945                         a = a.replace(reg, function (s, name, value) {\r
3946                             var quote = value.charAt(0)\r
3947                             return name + "=" + quote + "javascript:void(0)" + quote// jshint ignore:line\r
3948                         })\r
3949                     }\r
3950                 }\r
3951                 return a.replace(ron, " ").replace(/\s+/g, " ") //移除onXXX事件\r
3952             })\r
3953         },\r
3954         escape: function (str) {\r
3955             //将字符串经过 str 转义得到适合在页面中显示的内容, 例如替换 < 为 &lt \r
3956             return String(str).\r
3957                 replace(/&/g, '&amp;').\r
3958                 replace(rsurrogate, function (value) {\r
3959                     var hi = value.charCodeAt(0)\r
3960                     var low = value.charCodeAt(1)\r
3961                     return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'\r
3962                 }).\r
3963                 replace(rnoalphanumeric, function (value) {\r
3964                     return '&#' + value.charCodeAt(0) + ';'\r
3965                 }).\r
3966                 replace(/</g, '&lt;').\r
3967                 replace(/>/g, '&gt;')\r
3968         },\r
3969         currency: function (amount, symbol, fractionSize) {\r
3970             return (symbol || "\uFFE5") + numberFormat(amount, isFinite(fractionSize) ? fractionSize : 2)\r
3971         },\r
3972         number: numberFormat\r
3973     }\r
3974     /*\r
3975      'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)\r
3976      'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)\r
3977      'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)\r
3978      'MMMM': Month in year (January-December)\r
3979      'MMM': Month in year (Jan-Dec)\r
3980      'MM': Month in year, padded (01-12)\r
3981      'M': Month in year (1-12)\r
3982      'dd': Day in month, padded (01-31)\r
3983      'd': Day in month (1-31)\r
3984      'EEEE': Day in Week,(Sunday-Saturday)\r
3985      'EEE': Day in Week, (Sun-Sat)\r
3986      'HH': Hour in day, padded (00-23)\r
3987      'H': Hour in day (0-23)\r
3988      'hh': Hour in am/pm, padded (01-12)\r
3989      'h': Hour in am/pm, (1-12)\r
3990      'mm': Minute in hour, padded (00-59)\r
3991      'm': Minute in hour (0-59)\r
3992      'ss': Second in minute, padded (00-59)\r
3993      's': Second in minute (0-59)\r
3994      'a': am/pm marker\r
3995      'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)\r
3996      format string can also be one of the following predefined localizable formats:\r
3997 \r
3998      'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)\r
3999      'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)\r
4000      'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)\r
4001      'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010\r
4002      'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)\r
4003      'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)\r
4004      'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)\r
4005      'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)\r
4006      */\r
4007     new function () {// jshint ignore:line\r
4008         function toInt(str) {\r
4009             return parseInt(str, 10) || 0\r
4010         }\r
4011 \r
4012         function padNumber(num, digits, trim) {\r
4013             var neg = ""\r
4014             if (num < 0) {\r
4015                 neg = '-'\r
4016                 num = -num\r
4017             }\r
4018             num = "" + num\r
4019             while (num.length < digits)\r
4020                 num = "0" + num\r
4021             if (trim)\r
4022                 num = num.substr(num.length - digits)\r
4023             return neg + num\r
4024         }\r
4025 \r
4026         function dateGetter(name, size, offset, trim) {\r
4027             return function (date) {\r
4028                 var value = date["get" + name]()\r
4029                 if (offset > 0 || value > -offset)\r
4030                     value += offset\r
4031                 if (value === 0 && offset === -12) {\r
4032                     value = 12\r
4033                 }\r
4034                 return padNumber(value, size, trim)\r
4035             }\r
4036         }\r
4037 \r
4038         function dateStrGetter(name, shortForm) {\r
4039             return function (date, formats) {\r
4040                 var value = date["get" + name]()\r
4041                 var get = (shortForm ? ("SHORT" + name) : name).toUpperCase()\r
4042                 return formats[get][value]\r
4043             }\r
4044         }\r
4045 \r
4046         function timeZoneGetter(date) {\r
4047             var zone = -1 * date.getTimezoneOffset()\r
4048             var paddedZone = (zone >= 0) ? "+" : ""\r
4049             paddedZone += padNumber(Math[zone > 0 ? "floor" : "ceil"](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2)\r
4050             return paddedZone\r
4051         }\r
4052 \r
4053         //取得上午下午\r
4054 \r
4055         function ampmGetter(date, formats) {\r
4056             return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]\r
4057         }\r
4058 \r
4059         var DATE_FORMATS = {\r
4060             yyyy: dateGetter("FullYear", 4),\r
4061             yy: dateGetter("FullYear", 2, 0, true),\r
4062             y: dateGetter("FullYear", 1),\r
4063             MMMM: dateStrGetter("Month"),\r
4064             MMM: dateStrGetter("Month", true),\r
4065             MM: dateGetter("Month", 2, 1),\r
4066             M: dateGetter("Month", 1, 1),\r
4067             dd: dateGetter("Date", 2),\r
4068             d: dateGetter("Date", 1),\r
4069             HH: dateGetter("Hours", 2),\r
4070             H: dateGetter("Hours", 1),\r
4071             hh: dateGetter("Hours", 2, -12),\r
4072             h: dateGetter("Hours", 1, -12),\r
4073             mm: dateGetter("Minutes", 2),\r
4074             m: dateGetter("Minutes", 1),\r
4075             ss: dateGetter("Seconds", 2),\r
4076             s: dateGetter("Seconds", 1),\r
4077             sss: dateGetter("Milliseconds", 3),\r
4078             EEEE: dateStrGetter("Day"),\r
4079             EEE: dateStrGetter("Day", true),\r
4080             a: ampmGetter,\r
4081             Z: timeZoneGetter\r
4082         }\r
4083         var rdateFormat = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/\r
4084         var raspnetjson = /^\/Date\((\d+)\)\/$/\r
4085         filters.date = function (date, format) {\r
4086             var locate = filters.date.locate,\r
4087                 text = "",\r
4088                 parts = [],\r
4089                 fn, match\r
4090             format = format || "mediumDate"\r
4091             format = locate[format] || format\r
4092             if (typeof date === "string") {\r
4093                 if (/^\d+$/.test(date)) {\r
4094                     date = toInt(date)\r
4095                 } else if (raspnetjson.test(date)) {\r
4096                     date = +RegExp.$1\r
4097                 } else {\r
4098                     var trimDate = date.trim()\r
4099                     var dateArray = [0, 0, 0, 0, 0, 0, 0]\r
4100                     var oDate = new Date(0)\r
4101                     //取得年月日\r
4102                     trimDate = trimDate.replace(/^(\d+)\D(\d+)\D(\d+)/, function (_, a, b, c) {\r
4103                         var array = c.length === 4 ? [c, a, b] : [a, b, c]\r
4104                         dateArray[0] = toInt(array[0])     //年\r
4105                         dateArray[1] = toInt(array[1]) - 1 //月\r
4106                         dateArray[2] = toInt(array[2])     //日\r
4107                         return ""\r
4108                     })\r
4109                     var dateSetter = oDate.setFullYear\r
4110                     var timeSetter = oDate.setHours\r
4111                     trimDate = trimDate.replace(/[T\s](\d+):(\d+):?(\d+)?\.?(\d)?/, function (_, a, b, c, d) {\r
4112                         dateArray[3] = toInt(a) //小时\r
4113                         dateArray[4] = toInt(b) //分钟\r
4114                         dateArray[5] = toInt(c) //秒\r
4115                         if (d) {                //毫秒\r
4116                             dateArray[6] = Math.round(parseFloat("0." + d) * 1000)\r
4117                         }\r
4118                         return ""\r
4119                     })\r
4120                     var tzHour = 0\r
4121                     var tzMin = 0\r
4122                     trimDate = trimDate.replace(/Z|([+-])(\d\d):?(\d\d)/, function (z, symbol, c, d) {\r
4123                         dateSetter = oDate.setUTCFullYear\r
4124                         timeSetter = oDate.setUTCHours\r
4125                         if (symbol) {\r
4126                             tzHour = toInt(symbol + c)\r
4127                             tzMin = toInt(symbol + d)\r
4128                         }\r
4129                         return ""\r
4130                     })\r
4131 \r
4132                     dateArray[3] -= tzHour\r
4133                     dateArray[4] -= tzMin\r
4134                     dateSetter.apply(oDate, dateArray.slice(0, 3))\r
4135                     timeSetter.apply(oDate, dateArray.slice(3))\r
4136                     date = oDate\r
4137                 }\r
4138             }\r
4139             if (typeof date === "number") {\r
4140                 date = new Date(date)\r
4141             }\r
4142             if (avalon.type(date) !== "date") {\r
4143                 return\r
4144             }\r
4145             while (format) {\r
4146                 match = rdateFormat.exec(format)\r
4147                 if (match) {\r
4148                     parts = parts.concat(match.slice(1))\r
4149                     format = parts.pop()\r
4150                 } else {\r
4151                     parts.push(format)\r
4152                     format = null\r
4153                 }\r
4154             }\r
4155             parts.forEach(function (value) {\r
4156                 fn = DATE_FORMATS[value]\r
4157                 text += fn ? fn(date, locate) : value.replace(/(^'|'$)/g, "").replace(/''/g, "'")\r
4158             })\r
4159             return text\r
4160         }\r
4161         var locate = {\r
4162             AMPMS: {\r
4163                 0: "上午",\r
4164                 1: "下午"\r
4165             },\r
4166             DAY: {\r
4167                 0: "星期日",\r
4168                 1: "星期一",\r
4169                 2: "星期二",\r
4170                 3: "星期三",\r
4171                 4: "星期四",\r
4172                 5: "星期五",\r
4173                 6: "星期六"\r
4174             },\r
4175             MONTH: {\r
4176                 0: "1月",\r
4177                 1: "2月",\r
4178                 2: "3月",\r
4179                 3: "4月",\r
4180                 4: "5月",\r
4181                 5: "6月",\r
4182                 6: "7月",\r
4183                 7: "8月",\r
4184                 8: "9月",\r
4185                 9: "10月",\r
4186                 10: "11月",\r
4187                 11: "12月"\r
4188             },\r
4189             SHORTDAY: {\r
4190                 "0": "周日",\r
4191                 "1": "周一",\r
4192                 "2": "周二",\r
4193                 "3": "周三",\r
4194                 "4": "周四",\r
4195                 "5": "周五",\r
4196                 "6": "周六"\r
4197             },\r
4198             fullDate: "y年M月d日EEEE",\r
4199             longDate: "y年M月d日",\r
4200             medium: "yyyy-M-d H:mm:ss",\r
4201             mediumDate: "yyyy-M-d",\r
4202             mediumTime: "H:mm:ss",\r
4203             "short": "yy-M-d ah:mm",\r
4204             shortDate: "yy-M-d",\r
4205             shortTime: "ah:mm"\r
4206         }\r
4207         locate.SHORTMONTH = locate.MONTH\r
4208         filters.date.locate = locate\r
4209     }// jshint ignore:line\r
4210     /*********************************************************************\r
4211      *                      AMD加载器                                   *\r
4212      **********************************************************************/\r
4213 //https://www.devbridge.com/articles/understanding-amd-requirejs/\r
4214 //http://maxogden.com/nested-dependencies.html\r
4215     var modules = avalon.modules = {\r
4216         "domReady!": {\r
4217             exports: avalon,\r
4218             state: 3\r
4219         },\r
4220         "avalon": {\r
4221             exports: avalon,\r
4222             state: 4\r
4223         }\r
4224     }\r
4225 //Object(modules[id]).state拥有如下值 \r
4226 // undefined  没有定义\r
4227 // 1(send)    已经发出请求\r
4228 // 2(loading) 已经被执行但还没有执行完成,在这个阶段define方法会被执行\r
4229 // 3(loaded)  执行完毕,通过onload/onreadystatechange回调判定,在这个阶段checkDeps方法会执行\r
4230 // 4(execute)  其依赖也执行完毕, 值放到exports对象上,在这个阶段fireFactory方法会执行\r
4231     modules.exports = modules.avalon\r
4232 \r
4233     new function () {// jshint ignore:line\r
4234         var loadings = [] //正在加载中的模块列表\r
4235         var factorys = [] //放置define方法的factory函数\r
4236         var rjsext = /\.js$/i\r
4237 \r
4238         function makeRequest(name, config) {\r
4239 //1. 去掉资源前缀\r
4240             var res = "js"\r
4241             name = name.replace(/^(\w+)\!/, function (a, b) {\r
4242                 res = b\r
4243                 return ""\r
4244             })\r
4245             if (res === "ready") {\r
4246                 log("debug: ready!已经被废弃,请使用domReady!")\r
4247                 res = "domReady"\r
4248             }\r
4249 //2. 去掉querystring, hash\r
4250             var query = ""\r
4251             name = name.replace(rquery, function (a) {\r
4252                 query = a\r
4253                 return ""\r
4254             })\r
4255             //3. 去掉扩展名\r
4256             var suffix = "." + res\r
4257             var ext = /js|css/.test(suffix) ? suffix : ""\r
4258             name = name.replace(/\.[a-z0-9]+$/g, function (a) {\r
4259                 if (a === suffix) {\r
4260                     ext = a\r
4261                     return ""\r
4262                 } else {\r
4263                     return a\r
4264                 }\r
4265             })\r
4266             var req = avalon.mix({\r
4267                 query: query,\r
4268                 ext: ext,\r
4269                 res: res,\r
4270                 name: name,\r
4271                 toUrl: toUrl\r
4272             }, config)\r
4273             req.toUrl(name)\r
4274             return req\r
4275         }\r
4276 \r
4277         function fireRequest(req) {\r
4278             var name = req.name\r
4279             var res = req.res\r
4280             //1. 如果该模块已经发出请求,直接返回\r
4281             var module = modules[name]\r
4282             var urlNoQuery = name && req.urlNoQuery\r
4283             if (module && module.state >= 1) {\r
4284                 return name\r
4285             }\r
4286             module = modules[urlNoQuery]\r
4287             if (module && module.state >= 3) {\r
4288                 innerRequire(module.deps || [], module.factory, urlNoQuery)\r
4289                 return urlNoQuery\r
4290             }\r
4291             if (name && !module) {\r
4292                 module = modules[urlNoQuery] = {\r
4293                     id: urlNoQuery,\r
4294                     state: 1 //send\r
4295                 }\r
4296                 var wrap = function (obj) {\r
4297                     resources[res] = obj\r
4298                     obj.load(name, req, function (a) {\r
4299                         if (arguments.length && a !== void 0) {\r
4300                             module.exports = a\r
4301                         }\r
4302                         module.state = 4\r
4303                         checkDeps()\r
4304                     })\r
4305                 }\r
4306 \r
4307                 if (!resources[res]) {\r
4308                     innerRequire([res], wrap)\r
4309                 } else {\r
4310                     wrap(resources[res])\r
4311                 }\r
4312             }\r
4313             return name ? urlNoQuery : res + "!"\r
4314         }\r
4315 \r
4316 //核心API之一 require\r
4317         var requireQueue = []\r
4318         var isUserFirstRequire = false\r
4319         innerRequire = avalon.require = function (array, factory, parentUrl, defineConfig) {\r
4320             if (!isUserFirstRequire) {\r
4321                 requireQueue.push(avalon.slice(arguments))\r
4322                 if (arguments.length <= 2) {\r
4323                     isUserFirstRequire = true\r
4324                     var queue = requireQueue.splice(0, requireQueue.length), args\r
4325                     while (args = queue.shift()) {\r
4326                         innerRequire.apply(null, args)\r
4327                     }\r
4328                 }\r
4329                 return\r
4330             }\r
4331 \r
4332             if (!Array.isArray(array)) {\r
4333                 avalon.error("require方法的第一个参数应为数组 " + array)\r
4334             }\r
4335             var deps = [] // 放置所有依赖项的完整路径\r
4336             var uniq = createMap()\r
4337             var id = parentUrl || "callback" + setTimeout("1")// jshint ignore:line\r
4338             defineConfig = defineConfig || createMap()\r
4339             defineConfig.baseUrl = kernel.baseUrl\r
4340             var isBuilt = !!defineConfig.built\r
4341             if (parentUrl) {\r
4342                 defineConfig.parentUrl = parentUrl.substr(0, parentUrl.lastIndexOf("/"))\r
4343                 defineConfig.mapUrl = parentUrl.replace(rjsext, "")\r
4344             }\r
4345             if (isBuilt) {\r
4346                 var req = makeRequest(defineConfig.defineName, defineConfig)\r
4347                 id = req.urlNoQuery\r
4348             } else {\r
4349                 array.forEach(function (name) {\r
4350                     var req = makeRequest(name, defineConfig)\r
4351                     var url = fireRequest(req) //加载资源,并返回该资源的完整地址\r
4352                     if (url) {\r
4353                         if (!uniq[url]) {\r
4354                             deps.push(url)\r
4355                             uniq[url] = "司徒正美" //去重\r
4356                         }\r
4357                     }\r
4358                 })\r
4359             }\r
4360 \r
4361             var module = modules[id]\r
4362             if (!module || module.state !== 4) {\r
4363                 modules[id] = {\r
4364                     id: id,\r
4365                     deps: isBuilt ? array.concat() : deps,\r
4366                     factory: factory || noop,\r
4367                     state: 3\r
4368                 }\r
4369             }\r
4370             if (!module) {\r
4371                 //如果此模块是定义在另一个JS文件中, 那必须等该文件加载完毕, 才能放到检测列队中\r
4372                 loadings.push(id)\r
4373             }\r
4374             checkDeps()\r
4375         }\r
4376 \r
4377 //核心API之二 require\r
4378         innerRequire.define = function (name, deps, factory) { //模块名,依赖列表,模块本身\r
4379             if (typeof name !== "string") {\r
4380                 factory = deps\r
4381                 deps = name\r
4382                 name = "anonymous"\r
4383             }\r
4384             if (!Array.isArray(deps)) {\r
4385                 factory = deps\r
4386                 deps = []\r
4387             }\r
4388             var config = {\r
4389                 built: !isUserFirstRequire, //用r.js打包后,所有define会放到requirejs之前\r
4390                 defineName: name\r
4391             }\r
4392             var args = [deps, factory, config]\r
4393             factory.require = function (url) {\r
4394                 args.splice(2, 0, url)\r
4395                 if (modules[url]) {\r
4396                     modules[url].state = 3 //loaded\r
4397                     var isCycle = false\r
4398                     try {\r
4399                         isCycle = checkCycle(modules[url].deps, url)\r
4400                     } catch (e) {\r
4401                     }\r
4402                     if (isCycle) {\r
4403                         avalon.error(url + "模块与之前的模块存在循环依赖,请不要直接用script标签引入" + url + "模块")\r
4404                     }\r
4405                 }\r
4406                 delete factory.require //释放内存\r
4407                 innerRequire.apply(null, args) //0,1,2 --> 1,2,0\r
4408             }\r
4409 //根据标准,所有遵循W3C标准的浏览器,script标签会按标签的出现顺序执行。\r
4410 //老的浏览器中,加载也是按顺序的:一个文件下载完成后,才开始下载下一个文件。\r
4411 //较新的浏览器中(IE8+ 、FireFox3.5+ 、Chrome4+ 、Safari4+),为了减小请求时间以优化体验,\r
4412 //下载可以是并行的,但是执行顺序还是按照标签出现的顺序。\r
4413 //但如果script标签是动态插入的, 就未必按照先请求先执行的原则了,目测只有firefox遵守\r
4414 //唯一比较一致的是,IE10+及其他标准浏览器,一旦开始解析脚本, 就会一直堵在那里,直接脚本解析完毕\r
4415 //亦即,先进入loading阶段的script标签(模块)必然会先进入loaded阶段\r
4416             var url = config.built ? "unknown" : getCurrentScript()\r
4417             if (url) {\r
4418                 var module = modules[url]\r
4419                 if (module) {\r
4420                     module.state = 2\r
4421                 }\r
4422                 factory.require(url)\r
4423             } else {//合并前后的safari,合并后的IE6-9走此分支\r
4424                 factorys.push(factory)\r
4425             }\r
4426         }\r
4427 //核心API之三 require.config(settings)\r
4428         innerRequire.config = kernel\r
4429         //核心API之四 define.amd 标识其符合AMD规范\r
4430         innerRequire.define.amd = modules\r
4431 \r
4432         //==========================对用户配置项进行再加工==========================\r
4433         var allpaths = kernel["orig.paths"] = createMap()\r
4434         var allmaps = kernel["orig.map"] = createMap()\r
4435         var allpackages = kernel["packages"] = []\r
4436         var allargs = kernel["orig.args"] = createMap()\r
4437         avalon.mix(plugins, {\r
4438             paths: function (hash) {\r
4439                 avalon.mix(allpaths, hash)\r
4440                 kernel.paths = makeIndexArray(allpaths)\r
4441             },\r
4442             map: function (hash) {\r
4443                 avalon.mix(allmaps, hash)\r
4444                 var list = makeIndexArray(allmaps, 1, 1)\r
4445                 avalon.each(list, function (_, item) {\r
4446                     item.val = makeIndexArray(item.val)\r
4447                 })\r
4448                 kernel.map = list\r
4449             },\r
4450             packages: function (array) {\r
4451                 array = array.concat(allpackages)\r
4452                 var uniq = createMap()\r
4453                 var ret = []\r
4454                 for (var i = 0, pkg; pkg = array[i++];) {\r
4455                     pkg = typeof pkg === "string" ? {name: pkg} : pkg\r
4456                     var name = pkg.name\r
4457                     if (!uniq[name]) {\r
4458                         var url = joinPath(pkg.location || name, pkg.main || "main")\r
4459                         url = url.replace(rjsext, "")\r
4460                         ret.push(pkg)\r
4461                         uniq[name] = pkg.location = url\r
4462                         pkg.reg = makeMatcher(name)\r
4463                     }\r
4464                 }\r
4465                 kernel.packages = ret.sort()\r
4466             },\r
4467             urlArgs: function (hash) {\r
4468                 if (typeof hash === "string") {\r
4469                     hash = {"*": hash}\r
4470                 }\r
4471                 avalon.mix(allargs, hash)\r
4472                 kernel.urlArgs = makeIndexArray(allargs, 1)\r
4473             },\r
4474             baseUrl: function (url) {\r
4475                 if (!isAbsUrl(url)) {\r
4476                     var baseElement = head.getElementsByTagName("base")[0]\r
4477                     if (baseElement) {\r
4478                         head.removeChild(baseElement)\r
4479                     }\r
4480                     var node = DOC.createElement("a")\r
4481                     node.href = url\r
4482                     url = node.href\r
4483                     if (baseElement) {\r
4484                         head.insertBefore(baseElement, head.firstChild)\r
4485                     }\r
4486                 }\r
4487                 if (url.length > 3)\r
4488                     kernel.baseUrl = url\r
4489             },\r
4490             shim: function (obj) {\r
4491                 for (var i in obj) {\r
4492                     var value = obj[i]\r
4493                     if (Array.isArray(value)) {\r
4494                         value = obj[i] = {\r
4495                             deps: value\r
4496                         }\r
4497                     }\r
4498                     if (!value.exportsFn && (value.exports || value.init)) {\r
4499                         value.exportsFn = makeExports(value)\r
4500                     }\r
4501                 }\r
4502                 kernel.shim = obj\r
4503             }\r
4504 \r
4505         })\r
4506 \r
4507 \r
4508         //==============================内部方法=================================\r
4509         function checkCycle(deps, nick) {\r
4510             //检测是否存在循环依赖\r
4511             for (var i = 0, id; id = deps[i++];) {\r
4512                 if (modules[id].state !== 4 &&\r
4513                     (id === nick || checkCycle(modules[id].deps, nick))) {\r
4514                     return true\r
4515                 }\r
4516             }\r
4517         }\r
4518 \r
4519         function checkFail(node, onError) {\r
4520             var id = trimQuery(node.src) //检测是否死链\r
4521             node.onload = node.onerror = null\r
4522             if (onError) {\r
4523                 setTimeout(function () {\r
4524                     head.removeChild(node)\r
4525                     node = null // 处理旧式IE下的循环引用问题\r
4526                 })\r
4527                 log("debug: 加载 " + id + " 失败" + onError + " " + (!modules[id].state))\r
4528             } else {\r
4529                 return true\r
4530             }\r
4531         }\r
4532 \r
4533         function checkDeps() {\r
4534             //检测此JS模块的依赖是否都已安装完毕,是则安装自身\r
4535             loop: for (var i = loadings.length, id; id = loadings[--i];) {\r
4536                 var obj = modules[id],\r
4537                     deps = obj.deps\r
4538                 if (!deps)\r
4539                     continue\r
4540                 for (var j = 0, key; key = deps[j]; j++) {\r
4541                     if (Object(modules[key]).state !== 4) {\r
4542                         continue loop\r
4543                     }\r
4544                 }\r
4545                 //如果deps是空对象或者其依赖的模块的状态都是2\r
4546                 if (obj.state !== 4) {\r
4547                     loadings.splice(i, 1) //必须先移除再安装,防止在IE下DOM树建完后手动刷新页面,会多次执行它\r
4548                     fireFactory(obj.id, obj.deps, obj.factory)\r
4549                     checkDeps() //如果成功,则再执行一次,以防有些模块就差本模块没有安装好\r
4550                 }\r
4551             }\r
4552         }\r
4553 \r
4554         function loadJS(url, id, callback) {\r
4555             //通过script节点加载目标模块\r
4556             var node = DOC.createElement("script")\r
4557             node.className = subscribers //让getCurrentScript只处理类名为subscribers的script节点\r
4558             node.onload = function () {\r
4559                 var factory = factorys.pop()\r
4560                 factory && factory.require(id)\r
4561                 if (callback) {\r
4562                     callback()\r
4563                 }\r
4564                 log("debug: 已成功加载 " + url)\r
4565                 id && loadings.push(id)\r
4566                 checkDeps()\r
4567             }\r
4568             node.onerror = function () {\r
4569                 checkFail(node, true)\r
4570             }\r
4571 \r
4572             head.insertBefore(node, head.firstChild) //chrome下第二个参数不能为null\r
4573             node.src = url //插入到head的第一个节点前,防止IE6下head标签没闭合前使用appendChild抛错\r
4574             log("debug: 正准备加载 " + url) //更重要的是IE6下可以收窄getCurrentScript的寻找范围\r
4575         }\r
4576 \r
4577         var resources = innerRequire.plugins = {\r
4578             //三大常用资源插件 js!, css!, text!, ready!\r
4579             ready: {\r
4580                 load: noop\r
4581             },\r
4582             js: {\r
4583                 load: function (name, req, onLoad) {\r
4584                     var url = req.url\r
4585                     var id = req.urlNoQuery\r
4586                     var shim = kernel.shim[name.replace(rjsext, "")]\r
4587                     if (shim) { //shim机制\r
4588                         innerRequire(shim.deps || [], function () {\r
4589                             var args = avalon.slice(arguments)\r
4590                             loadJS(url, id, function () {\r
4591                                 onLoad(shim.exportsFn ? shim.exportsFn.apply(0, args) : void 0)\r
4592                             })\r
4593                         })\r
4594                     } else {\r
4595                         loadJS(url, id)\r
4596                     }\r
4597                 }\r
4598             },\r
4599             css: {\r
4600                 load: function (name, req, onLoad) {\r
4601                     var url = req.url\r
4602                     head.insertAdjacentHTML("afterBegin", '<link rel="stylesheet" href="' + url + '">')\r
4603                     log("debug: 已成功加载 " + url)\r
4604                     onLoad()\r
4605                 }\r
4606             },\r
4607             text: {\r
4608                 load: function (name, req, onLoad) {\r
4609                     var url = req.url\r
4610                     var xhr = getXHR()\r
4611                     xhr.onload = function () {\r
4612                         var status = xhr.status;\r
4613                         if (status > 399 && status < 600) {\r
4614                             avalon.error(url + " 对应资源不存在或没有开启 CORS")\r
4615                         } else {\r
4616                             log("debug: 已成功加载 " + url)\r
4617                             onLoad(xhr.responseText)\r
4618                         }\r
4619                     }\r
4620                     xhr.open("GET", url, true)\r
4621                     if ("withCredentials" in xhr) {//这是处理跨域\r
4622                         xhr.withCredentials = true\r
4623                     }\r
4624                     xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")//告诉后端这是AJAX请求\r
4625                     xhr.send()\r
4626                     log("debug: 正准备加载 " + url)\r
4627                 }\r
4628             }\r
4629         }\r
4630         innerRequire.checkDeps = checkDeps\r
4631 \r
4632         var rquery = /(\?[^#]*)$/\r
4633 \r
4634         function trimQuery(url) {\r
4635             return (url || "").replace(rquery, "")\r
4636         }\r
4637 \r
4638         function isAbsUrl(path) {\r
4639             //http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative\r
4640             return /^(?:[a-z]+:)?\/\//i.test(String(path))\r
4641         }\r
4642 \r
4643 \r
4644         function getCurrentScript() {\r
4645             // inspireb by https://github.com/samyk/jiagra/blob/master/jiagra.js\r
4646             var stack\r
4647             try {\r
4648                 a.b.c() //强制报错,以便捕获e.stack\r
4649             } catch (e) { //safari5的sourceURL,firefox的fileName,它们的效果与e.stack不一样\r
4650                 stack = e.stack\r
4651             }\r
4652             if (stack) {\r
4653                 /**e.stack最后一行在所有支持的浏览器大致如下:\r
4654                  *chrome23:\r
4655                  * at http://113.93.50.63/data.js:4:1\r
4656                  *firefox17:\r
4657                  *@http://113.93.50.63/query.js:4\r
4658                  *opera12:http://www.oldapps.com/opera.php?system=Windows_XP\r
4659                  *@http://113.93.50.63/data.js:4\r
4660                  *IE10:\r
4661                  *  at Global code (http://113.93.50.63/data.js:4:1)\r
4662                  *  //firefox4+ 可以用document.currentScript\r
4663                  */\r
4664                 stack = stack.split(/[@ ]/g).pop() //取得最后一行,最后一个空格或@之后的部分\r
4665                 stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉换行符\r
4666                 return trimQuery(stack.replace(/(:\d+)?:\d+$/i, "")) //去掉行号与或许存在的出错字符起始位置\r
4667             }\r
4668             var nodes = head.getElementsByTagName("script") //只在head标签中寻找\r
4669             for (var i = nodes.length, node; node = nodes[--i];) {\r
4670                 if (node.className === subscribers && node.readyState === "interactive") {\r
4671                     var url = node.src\r
4672                     return node.className = trimQuery(url)\r
4673                 }\r
4674             }\r
4675         }\r
4676 \r
4677         var rcallback = /^callback\d+$/\r
4678 \r
4679         function fireFactory(id, deps, factory) {\r
4680             var module = Object(modules[id])\r
4681             module.state = 4\r
4682             for (var i = 0, array = [], d; d = deps[i++];) {\r
4683                 if (d === "exports") {\r
4684                     var obj = module.exports || (module.exports = createMap())\r
4685                     array.push(obj)\r
4686                 } else {\r
4687                     array.push(modules[d].exports)\r
4688                 }\r
4689             }\r
4690             try {\r
4691                 var ret = factory.apply(window, array)\r
4692             } catch (e) {\r
4693                 log("执行[" + id + "]模块的factory抛错: " + e)\r
4694             }\r
4695             if (ret !== void 0) {\r
4696                 module.exports = ret\r
4697             }\r
4698             if (rcallback.test(id)) {\r
4699                 delete modules[id]\r
4700             }\r
4701             delete module.factory\r
4702             return ret\r
4703         }\r
4704 \r
4705         function toUrl(id) {\r
4706             if (id.indexOf(this.res + "!") === 0) {\r
4707                 id = id.slice(this.res.length + 1) //处理define("css!style",[], function(){})的情况\r
4708             }\r
4709             var url = id\r
4710             //1. 是否命中paths配置项\r
4711             var usePath = 0\r
4712             var baseUrl = this.baseUrl\r
4713             var rootUrl = this.parentUrl || baseUrl\r
4714             eachIndexArray(id, kernel.paths, function (value, key) {\r
4715                 url = url.replace(key, value)\r
4716                 usePath = 1\r
4717             })\r
4718             //2. 是否命中packages配置项\r
4719             if (!usePath) {\r
4720                 eachIndexArray(id, kernel.packages, function (value, key, item) {\r
4721                     url = url.replace(item.name, item.location)\r
4722                 })\r
4723             }\r
4724             //3. 是否命中map配置项\r
4725             if (this.mapUrl) {\r
4726                 eachIndexArray(this.mapUrl, kernel.map, function (array) {\r
4727                     eachIndexArray(url, array, function (mdValue, mdKey) {\r
4728                         url = url.replace(mdKey, mdValue)\r
4729                         rootUrl = baseUrl\r
4730                     })\r
4731                 })\r
4732             }\r
4733             var ext = this.ext\r
4734             if (ext && usePath && url.slice(-ext.length) === ext) {\r
4735                 url = url.slice(0, -ext.length)\r
4736             }\r
4737             //4. 转换为绝对路径\r
4738             if (!isAbsUrl(url)) {\r
4739                 rootUrl = this.built || /^\w/.test(url) ? baseUrl : rootUrl\r
4740                 url = joinPath(rootUrl, url)\r
4741             }\r
4742             //5. 还原扩展名,query\r
4743             var urlNoQuery = url + ext\r
4744             url = urlNoQuery + this.query\r
4745             //6. 处理urlArgs\r
4746             eachIndexArray(id, kernel.urlArgs, function (value) {\r
4747                 url += (url.indexOf("?") === -1 ? "?" : "&") + value;\r
4748             })\r
4749             this.url = url\r
4750             return this.urlNoQuery = urlNoQuery\r
4751         }\r
4752 \r
4753         function makeIndexArray(hash, useStar, part) {\r
4754             //创建一个经过特殊算法排好序的数组\r
4755             var index = hash2array(hash, useStar, part)\r
4756             index.sort(descSorterByName)\r
4757             return index\r
4758         }\r
4759 \r
4760         function makeMatcher(prefix) {\r
4761             return new RegExp('^' + prefix + '(/|$)')\r
4762         }\r
4763 \r
4764         function makeExports(value) {\r
4765             return function () {\r
4766                 var ret\r
4767                 if (value.init) {\r
4768                     ret = value.init.apply(window, arguments)\r
4769                 }\r
4770                 return ret || (value.exports && getGlobal(value.exports))\r
4771             }\r
4772         }\r
4773 \r
4774 \r
4775         function hash2array(hash, useStar, part) {\r
4776             var array = [];\r
4777             for (var key in hash) {\r
4778                 // if (hash.hasOwnProperty(key)) {//hash是由createMap创建没有hasOwnProperty\r
4779                 var item = {\r
4780                     name: key,\r
4781                     val: hash[key]\r
4782                 }\r
4783                 array.push(item)\r
4784                 item.reg = key === "*" && useStar ? /^/ : makeMatcher(key)\r
4785                 if (part && key !== "*") {\r
4786                     item.reg = new RegExp('\/' + key.replace(/^\//, "") + '(/|$)')\r
4787                 }\r
4788                 //   }\r
4789             }\r
4790             return array\r
4791         }\r
4792 \r
4793         function eachIndexArray(moduleID, array, matcher) {\r
4794             array = array || []\r
4795             for (var i = 0, el; el = array[i++];) {\r
4796                 if (el.reg.test(moduleID)) {\r
4797                     matcher(el.val, el.name, el)\r
4798                     return false\r
4799                 }\r
4800             }\r
4801         }\r
4802 \r
4803         // 根据元素的name项进行数组字符数逆序的排序函数\r
4804         function descSorterByName(a, b) {\r
4805             var aaa = a.name\r
4806             var bbb = b.name\r
4807             if (bbb === "*") {\r
4808                 return -1\r
4809             }\r
4810             if (aaa === "*") {\r
4811                 return 1\r
4812             }\r
4813             return bbb.length - aaa.length\r
4814         }\r
4815 \r
4816         var rdeuce = /\/\w+\/\.\./\r
4817 \r
4818         function joinPath(a, b) {\r
4819             if (a.charAt(a.length - 1) !== "/") {\r
4820                 a += "/"\r
4821             }\r
4822             if (b.slice(0, 2) === "./") { //相对于兄弟路径\r
4823                 return a + b.slice(2)\r
4824             }\r
4825             if (b.slice(0, 2) === "..") { //相对于父路径\r
4826                 a += b\r
4827                 while (rdeuce.test(a)) {\r
4828                     a = a.replace(rdeuce, "")\r
4829                 }\r
4830                 return a\r
4831             }\r
4832             if (b.slice(0, 1) === "/") {\r
4833                 return a + b.slice(1)\r
4834             }\r
4835             return a + b\r
4836         }\r
4837 \r
4838         function getGlobal(value) {\r
4839             if (!value) {\r
4840                 return value\r
4841             }\r
4842             var g = window\r
4843             value.split(".").forEach(function (part) {\r
4844                 g = g[part]\r
4845             })\r
4846             return g\r
4847         }\r
4848 \r
4849         var mainNode = DOC.scripts[DOC.scripts.length - 1]\r
4850         var dataMain = mainNode.getAttribute("data-main")\r
4851         if (dataMain) {\r
4852             plugins.baseUrl(dataMain)\r
4853             var href = kernel.baseUrl\r
4854             kernel.baseUrl = href.slice(0, href.lastIndexOf("/") + 1)\r
4855             loadJS(href.replace(rjsext, "") + ".js")\r
4856         } else {\r
4857             var loaderUrl = trimQuery(mainNode.src)\r
4858             kernel.baseUrl = loaderUrl.slice(0, loaderUrl.lastIndexOf("/") + 1)\r
4859         }\r
4860     }// jshint ignore:line\r
4861 \r
4862     /*********************************************************************\r
4863      *                    DOMReady                                         *\r
4864      **********************************************************************/\r
4865     var readyList = [], isReady\r
4866     var fireReady = function (fn) {\r
4867         isReady = true\r
4868         if (innerRequire) {\r
4869             modules["domReady!"].state = 4\r
4870             innerRequire.checkDeps()\r
4871         }\r
4872         while (fn = readyList.shift()) {\r
4873             fn(avalon)\r
4874         }\r
4875     }\r
4876 \r
4877 \r
4878     if (DOC.readyState === "complete") {\r
4879         setTimeout(fireReady) //如果在domReady之外加载\r
4880     } else {\r
4881         DOC.addEventListener("DOMContentLoaded", fireReady)\r
4882     }\r
4883     window.addEventListener("load", fireReady)\r
4884     avalon.ready = function (fn) {\r
4885         if (!isReady) {\r
4886             readyList.push(fn)\r
4887         } else {\r
4888             fn(avalon)\r
4889         }\r
4890     }\r
4891     avalon.config({\r
4892         loader: true\r
4893     })\r
4894     avalon.ready(function () {\r
4895         avalon.scan(DOC.body)\r
4896     })\r
4897 \r
4898 // Register as a named AMD module, since avalon can be concatenated with other\r
4899 // files that may use define, but not via a proper concatenation script that\r
4900 // understands anonymous AMD modules. A named AMD is safest and most robust\r
4901 // way to register. Lowercase avalon is used because AMD module names are\r
4902 // derived from file names, and Avalon is normally delivered in a lowercase\r
4903 // file name. Do this after creating the global so that if an AMD module wants\r
4904 // to call noConflict to hide this version of avalon, it will work.\r
4905 \r
4906 // Note that for maximum portability, libraries that are not avalon should\r
4907 // declare themselves as anonymous modules, and avoid setting a global if an\r
4908 // AMD loader is present. avalon is a special case. For more information, see\r
4909 // https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon\r
4910     if (typeof define === "function" && define.amd) {\r
4911         define("avalon", [], function () {\r
4912             return avalon\r
4913         })\r
4914     }\r
4915 // Map over avalon in case of overwrite\r
4916     var _avalon = window.avalon\r
4917     avalon.noConflict = function (deep) {\r
4918         if (deep && window.avalon === avalon) {\r
4919             window.avalon = _avalon\r
4920         }\r
4921         return avalon\r
4922     }\r
4923 // Expose avalon identifiers, even in AMD\r
4924 // and CommonJS for browser emulators\r
4925     if (noGlobal === void 0) {\r
4926         window.avalon = avalon\r
4927     }\r
4928 \r
4929     window._injectTer = function (code) {\r
4930         return eval(code)\r
4931     }\r
4932 \r
4933     return avalon\r
4934 \r
4935 }));