text: /*!
  built in 2016-12-13:18:37 version 2.2.3 by 司徒正美
  https://github.com/RubyLouvre/avalon/tree/2.2.2
  fix ms-controller BUG, 上下VM相同时,不会进行合并
  为监听数组添加toJSON方法
  IE7的checked属性应该使用defaultChecked来设置
  对旧版firefox的children进行polyfill
  修正ms-if,ms-text同在一个元素时出BUG的情况 
  修正ms-visible,ms-effect同在一个元素时出BUG的情况
  修正selected属性同步问题
  重构Proxy形态的vm
  
  */;(function (global, factory) {
      typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.avalon = factory()
  })(this, function () {
      'use strict'
  
      var win = typeof window === 'object' ? window : typeof global === 'object' ? global : {}
  
      var inBrowser = !!win.location && win.navigator
      /* istanbul ignore if  */
  
      var document$1 = inBrowser ? win.document : {
          createElement: Object,
          createElementNS: Object,
          documentElement: 'xx',
          contains: Boolean
      }
      var root = inBrowser ? document$1.documentElement : {
          outerHTML: 'x'
      }
  
      var versions = {
          objectobject: 7, //IE7-8
          objectundefined: 6, //IE6
          undefinedfunction: NaN, // other modern browsers
          undefinedobject: NaN }
      /* istanbul ignore next  */
      var msie = document$1.documentMode || versions[typeof document$1.all + typeof XMLHttpRequest]
  
      var modern = /NaN|undefined/.test(msie) || msie > 8
  
      /*
       https://github.com/rsms/js-lru
       entry             entry             entry             entry        
       ______            ______            ______            ______       
       | head |.newer => |      |.newer => |      |.newer => | tail |      
       |  A   |          |  B   |          |  C   |          |  D   |      
       |______| <= older.|______| <= older.|______| <= older.|______|      
       
       removed  <--  <--  <--  <--  <--  <--  <--  <--  <--  <--  <--  added 
       */
      function Cache(maxLength) {
          // 标识当前缓存数组的大小
          this.size = 0
          // 标识缓存数组能达到的最大长度
          this.limit = maxLength
          //  head（最不常用的项），tail（最常用的项）全部初始化为undefined
  
          this.head = this.tail = void 0
          this._keymap = {}
      }
  
      Cache.prototype = {
          put: function put(key, value) {
              var entry = {
                  key: key,
                  value: value
              }
              this._keymap[key] = entry
              if (this.tail) {
                  // 如果存在tail（缓存数组的长度不为0），将tail指向新的 entry
                  this.tail.newer = entry
                  entry.older = this.tail
              } else {
                  // 如果缓存数组的长度为0，将head指向新的entry
                  this.head = entry
              }
              this.tail = entry
              // 如果缓存数组达到上限，则先删除 head 指向的缓存对象
              /* istanbul ignore if */
              if (this.size === this.limit) {
                  this.shift()
              } else {
                  this.size++
              }
              return value
          },
          shift: function shift() {
              /* istanbul ignore next */
              var entry = this.head
              /* istanbul ignore if */
              if (entry) {
                  // 删除 head ，并改变指向
                  this.head = this.head.newer
                  // 同步更新 _keymap 里面的属性值
                  this.head.older = entry.newer = entry.older = this._keymap[entry.key] = void 0
                  delete this._keymap[entry.key] //#1029
                  // 同步更新 缓存数组的长度
                  this.size--
              }
          },
          get: function get(key) {
              var entry = this._keymap[key]
              // 如果查找不到含有`key`这个属性的缓存对象
              if (entry === void 0) return
              // 如果查找到的缓存对象已经是 tail (最近使用过的)
              /* istanbul ignore if */
              if (entry === this.tail) {
                  return entry.value
              }
              // HEAD--------------TAIL
              //   <.older   .newer>
              //  <--- add direction --
              //   A  B  C  <D>  E
              if (entry.newer) {
                  // 处理 newer 指向
                  if (entry === this.head) {
                      // 如果查找到的缓存对象是 head (最近最少使用过的)
                      // 则将 head 指向原 head 的 newer 所指向的缓存对象
                      this.head = entry.newer
                  }
                  // 将所查找的缓存对象的下一级的 older 指向所查找的缓存对象的older所指向的值
                  // 例如：A B C D E
                  // 如果查找到的是D，那么将E指向C，不再指向D
                  entry.newer.older = entry.older // C <-- E.
              }
              if (entry.older) {
                  // 处理 older 指向
                  // 如果查找到的是D，那么C指向E，不再指向D
                  entry.older.newer = entry.newer // C. --> E
              }
              // 处理所查找到的对象的 newer 以及 older 指向
              entry.newer = void 0 // D --x
              // older指向之前使用过的变量，即D指向E
              entry.older = this.tail // D. --> E
              if (this.tail) {
                  // 将E的newer指向D
                  this.tail.newer = entry // E. <-- D
              }
              // 改变 tail 为D 
              this.tail = entry
              return entry.value
          }
      }
  
      var delayCompile = {}
  
      var directives = {}
  
      function directive(name, opts) {
          if (directives[name]) {
              avalon.warn(name, 'directive have defined! ')
          }
          directives[name] = opts
          if (!opts.update) {
              opts.update = function () {}
          }
          if (opts.delay) {
              delayCompile[name] = 1
          }
          return opts
      }
  
      function delayCompileNodes(dirs) {
          for (var i in delayCompile) {
              if ('ms-' + i in dirs) {
                  return true
              }
          }
      }
  
      var window$1 = win
      function avalon$2(el) {
          return new avalon$2.init(el)
      }
  
      avalon$2.init = function (el) {
          this[0] = this.element = el
      }
  
      avalon$2.fn = avalon$2.prototype = avalon$2.init.prototype
  
      function shadowCopy(destination, source) {
          for (var property in source) {
              destination[property] = source[property]
          }
          return destination
      }
      var rword = /[^, ]+/g
      var rnowhite = /\S+/g //存在非空字符
      var platform = {} //用于放置平台差异的方法与属性
  
  
      function oneObject(array, val) {
          if (typeof array === 'string') {
              array = array.match(rword) || []
          }
          var result = {},
              value = val !== void 0 ? val : 1
          for (var i = 0, n = array.length; i < n; i++) {
              result[array[i]] = value
          }
          return result
      }
  
      var op = Object.prototype
      function quote(str) {
          return avalon$2._quote(str)
      }
      var inspect = op.toString
      var ohasOwn = op.hasOwnProperty
      var ap = Array.prototype
  
      var hasConsole = typeof console === 'object'
      avalon$2.config = { debug: true }
      function log() {
          if (hasConsole && avalon$2.config.debug) {
              Function.apply.call(console.log, console, arguments)
          }
      }
      function warn() {
          if (hasConsole && avalon$2.config.debug) {
              var method = console.warn || console.log
              // http://qiang106.iteye.com/blog/1721425
              Function.apply.call(method, console, arguments)
          }
      }
      function error(e, str) {
          throw (e || Error)(str)
      }
      function noop() {}
      function isObject(a) {
          return a !== null && typeof a === 'object'
      }
  
      function range(start, end, step) {
          // 用于生成整数数组
          step || (step = 1)
          if (end == null) {
              end = start || 0
              start = 0
          }
          var index = -1,
              length = Math.max(0, Math.ceil((end - start) / step)),
              result = new Array(length)
          while (++index < length) {
              result[index] = start
              start += step
          }
          return result
      }
  
      var rhyphen = /([a-z\d])([A-Z]+)/g
      function hyphen(target) {
          //转换为连字符线风格
          return target.replace(rhyphen, '$1-$2').toLowerCase()
      }
  
      var rcamelize = /[-_][^-_]/g
      function camelize(target) {
          //提前判断，提高getStyle等的效率
          if (!target || target.indexOf('-') < 0 && target.indexOf('_') < 0) {
              return target
          }
          //转换为驼峰风格
          return target.replace(rcamelize, function (match) {
              return match.charAt(1).toUpperCase()
          })
      }
  
      var _slice = ap.slice
      function slice(nodes, start, end) {
          return _slice.call(nodes, start, end)
      }
  
      var rhashcode = /\d\.\d{4}/
      //生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
      function makeHashCode(prefix) {
          /* istanbul ignore next*/
          prefix = prefix || 'avalon'
          /* istanbul ignore next*/
          return String(Math.random() + Math.random()).replace(rhashcode, prefix)
      }
      //生成事件回调的UUID(用户通过ms-on指令)
      function getLongID(fn) {
          /* istanbul ignore next */
          return fn.uuid || (fn.uuid = makeHashCode('e'))
      }
      var UUID = 1
      //生成事件回调的UUID(用户通过avalon.bind)
      function getShortID(fn) {
          /* istanbul ignore next */
          return fn.uuid || (fn.uuid = '_' + ++UUID)
      }
  
      var rescape = /[-.*+?^${}()|[\]\/\\]/g
      function escapeRegExp(target) {
          //http://stevenlevithan.com/regex/xregexp/
          //将字符串安全格式化为正则表达式的源码
          return (target + '').replace(rescape, '\\$&')
      }
  
      var eventHooks = {}
      var eventListeners = {}
      var validators = {}
      var cssHooks = {}
  
      window$1.avalon = avalon$2
  
      function createFragment() {
          /* istanbul ignore next  */
          return document$1.createDocumentFragment()
      }
  
      var rentities = /&[a-z0-9#]{2,10};/
      var temp = document$1.createElement('div')
      shadowCopy(avalon$2, {
          Array: {
              merge: function merge(target, other) {
                  //合并两个数组 avalon2新增
                  target.push.apply(target, other)
              },
              ensure: function ensure(target, item) {
                  //只有当前数组不存在此元素时只添加它
                  if (target.indexOf(item) === -1) {
                      return target.push(item)
                  }
              },
              removeAt: function removeAt(target, index) {
                  //移除数组中指定位置的元素，返回布尔表示成功与否
                  return !!target.splice(index, 1).length
              },
              remove: function remove(target, item) {
                  //移除数组中第一个匹配传参的那个元素，返回布尔表示成功与否
                  var index = target.indexOf(item)
                  if (~index) return avalon$2.Array.removeAt(target, index)
                  return false
              }
          },
          evaluatorPool: new Cache(888),
          parsers: {
              number: function number(a) {
                  return a === '' ? '' : parseFloat(a) || 0
              },
              string: function string(a) {
                  return a === null || a === void 0 ? '' : a + ''
              },
              boolean: function boolean(a) {
                  if (a === '') return a
                  return a === 'true' || a === '1'
              }
          },
          _decode: function _decode(str) {
              if (rentities.test(str)) {
                  temp.innerHTML = str
                  return temp.innerText || temp.textContent
              }
              return str
          }
      })
  
      //============== config ============
      function config(settings) {
          for (var p in settings) {
              var val = settings[p]
              if (typeof config.plugins[p] === 'function') {
                  config.plugins[p](val)
              } else {
                  config[p] = val
              }
          }
          return this
      }
  
      var plugins = {
          interpolate: function interpolate(array) {
              var openTag = array[0]
              var closeTag = array[1]
              if (openTag === closeTag) {
                  throw new SyntaxError('interpolate openTag cannot equal to closeTag')
              }
              var str = openTag + 'test' + closeTag
  
              if (/[<>]/.test(str)) {
                  throw new SyntaxError('interpolate cannot contains "<" or ">"')
              }
  
              config.openTag = openTag
              config.closeTag = closeTag
              var o = escapeRegExp(openTag)
              var c = escapeRegExp(closeTag)
  
              config.rtext = new RegExp(o + '(.+?)' + c, 'g')
              config.rexpr = new RegExp(o + '([\\s\\S]*)' + c)
          }
      }
      function createAnchor(nodeValue) {
          return document$1.createComment(nodeValue)
      }
      config.plugins = plugins
      config({
          interpolate: ['{{', '}}'],
          debug: true
      })
      //============  config ============
  
      shadowCopy(avalon$2, {
          shadowCopy: shadowCopy,
  
          oneObject: oneObject,
          inspect: inspect,
          ohasOwn: ohasOwn,
          rword: rword,
          version: "2.2.3",
          vmodels: {},
  
          directives: directives,
          directive: directive,
  
          eventHooks: eventHooks,
          eventListeners: eventListeners,
          validators: validators,
          cssHooks: cssHooks,
  
          log: log,
          noop: noop,
          warn: warn,
          error: error,
          config: config,
  
          modern: modern,
          msie: msie,
          root: root,
          document: document$1,
          window: window$1,
          inBrowser: inBrowser,
  
          isObject: isObject,
          range: range,
          slice: slice,
          hyphen: hyphen,
          camelize: camelize,
          escapeRegExp: escapeRegExp,
          quote: quote,
  
          makeHashCode: makeHashCode
  
      })
  
      //这里放置存在异议的方法
      var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/
      var rarraylike = /(Array|List|Collection|Map|Arguments)\]$/
  
      // avalon.type
      var class2type = {}
      'Boolean Number String Function Array Date RegExp Object Error'.replace(avalon$2.rword, function (name) {
          class2type['[object ' + name + ']'] = name.toLowerCase()
      })
  
      avalon$2.type = function (obj) {
          //取得目标的类型
          if (obj == null) {
              return String(obj)
          }
          // 早期的webkit内核浏览器实现了已废弃的ecma262v4标准，可以将正则字面量当作函数使用，因此typeof在判定正则时会返回function
          return typeof obj === 'object' || typeof obj === 'function' ? class2type[inspect.call(obj)] || 'object' : typeof obj
      }
  
      avalon$2._quote = JSON.stringify
  
      avalon$2.isFunction = function (fn) {
          return typeof fn === 'function'
      }
  
      avalon$2.isWindow = function (obj) {
          return rwindow.test(inspect.call(obj))
      }
  
      /*判定是否是一个朴素的javascript对象（Object），不是DOM对象，不是BOM对象，不是自定义类的实例*/
      avalon$2.isPlainObject = function (obj) {
          // 简单的 typeof obj === 'object'检测，会致使用isPlainObject(window)在opera下通不过
          return inspect.call(obj) === '[object Object]' && Object.getPrototypeOf(obj) === Object.prototype
      }
  
      //与jQuery.extend方法，可用于浅拷贝，深拷贝
      avalon$2.mix = avalon$2.fn.mix = function () {
          var options,
              name,
              src,
              copy,
              copyIsArray,
              clone,
              target = arguments[0] || {},
              i = 1,
              length = arguments.length,
              deep = false
  
          // 如果第一个参数为布尔,判定是否深拷贝
          if (typeof target === 'boolean') {
              deep = target
              target = arguments[1] || {}
              i++
          }
  
          //确保接受方为一个复杂的数据类型
          if (typeof target !== 'object' && typeof target !== 'function') {
              target = {}
          }
  
          //如果只有一个参数，那么新成员添加于mix所在的对象上
          if (i === length) {
              target = this
              i--
          }
  
          for (; i < length; i++) {
              //只处理非空参数
              if ((options = arguments[i]) != null) {
                  for (name in options) {
                      src = target[name]
  
                      copy = options[name]
  
                      // 防止环引用
                      if (target === copy) {
                          continue
                      }
                      if (deep && copy && (avalon$2.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
  
                          if (copyIsArray) {
                              copyIsArray = false
                              clone = src && Array.isArray(src) ? src : []
                          } else {
                              clone = src && avalon$2.isPlainObject(src) ? src : {}
                          }
  
                          target[name] = avalon$2.mix(deep, clone, copy)
                      } else if (copy !== void 0) {
                          target[name] = copy
                      }
                  }
              }
          }
          return target
      }
  
      /*判定是否类数组，如节点集合，纯数组，arguments与拥有非负整数的length属性的纯JS对象*/
      function isArrayLike(obj) {
          /* istanbul ignore if*/
          if (obj && typeof obj === 'object') {
              var n = obj.length,
                  str = inspect.call(obj)
              if (rarraylike.test(str)) {
                  return true
              } else if (str === '[object Object]' && n === n >>> 0) {
                  return true //由于ecma262v5能修改对象属性的enumerable，因此不能用propertyIsEnumerable来判定了
              }
          }
          return false
      }
  
      avalon$2.each = function (obj, fn) {
          if (obj) {
              //排除null, undefined
              var i = 0
              if (isArrayLike(obj)) {
                  for (var n = obj.length; i < n; i++) {
                      if (fn(i, obj[i]) === false) break
                  }
              } else {
                  for (i in obj) {
                      if (obj.hasOwnProperty(i) && fn(i, obj[i]) === false) {
                          break
                      }
                  }
              }
          }
      }
  
      new function welcome() {
          var welcomeIntro = ["%cavalon.js %c" + avalon$2.version + " %cin debug mode, %cmore...", "color: rgb(114, 157, 52); font-weight: normal;", "color: rgb(85, 85, 85); font-weight: normal;", "color: rgb(85, 85, 85); font-weight: normal;", "color: rgb(82, 140, 224); font-weight: normal; text-decoration: underline;"]
          var welcomeMessage = "You're running avalon in debug mode - messages will be printed to the console to help you fix problems and optimise your application.\n\n" + 'To disable debug mode, add this line at the start of your app:\n\n  avalon.config({debug: false});\n\n' + 'Debug mode also automatically shut down amicably when your app is minified.\n\n' + "Get help and support:\n  https://segmentfault.com/t/avalon\n  http://avalonjs.coding.me/\n  http://www.baidu-x.com/?q=avalonjs\n http://www.avalon.org.cn/\n\nFound a bug? Raise an issue:\n  https://github.com/RubyLouvre/avalon/issues\n\n"
  
          var hasGroup = !!console.groupCollapsed
          console[hasGroup ? 'groupCollapsed' : 'log'].apply(console, welcomeIntro)
          console.log(welcomeMessage)
          if (hasGroup) {
              console.groupEnd(welcomeIntro)
          }
      }()
  
      function toFixedFix(n, prec) {
          var k = Math.pow(10, prec)
          return '' + (Math.round(n * k) / k).toFixed(prec)
      }
      function numberFilter(number, decimals, point, thousands) {
          //https://github.com/txgruppi/number_format
          //form http://phpjs.org/functions/number_format/
          //number 必需，要格式化的数字
          //decimals 可选，规定多少个小数位。
          //point 可选，规定用作小数点的字符串（默认为 . ）。
          //thousands 可选，规定用作千位分隔符的字符串（默认为 , ），如果设置了该参数，那么所有其他参数都是必需的。
          number = (number + '').replace(/[^0-9+\-Ee.]/g, '')
          var n = !isFinite(+number) ? 0 : +number,
              prec = !isFinite(+decimals) ? 3 : Math.abs(decimals),
              sep = thousands || ",",
              dec = point || ".",
              s = ''
  
          // Fix for IE parseFloat(0.55).toFixed(0) = 0;
          s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.')
          if (s[0].length > 3) {
              s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep)
          }
          /** //好像没有用
           var s1 = s[1] || ''
          
            if (s1.length < prec) {
                    s1 += new Array(prec - s[1].length + 1).join('0')
                    s[1] = s1
            }
            **/
          return s.join(dec)
      }
  
      var rscripts = /<script[^>]*>([\S\s]*?)<\/script\s*>/gim
      var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g
      var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig
      var rsanitize = {
          a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig,
          img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig,
          form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig
      }
  
      //https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
      //    <a href="javasc&NewLine;ript&colon;alert('XSS')">chrome</a> 
      //    <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">chrome</a>
      //    <a href="jav	ascript:alert('XSS');">IE67chrome</a>
      //    <a href="jav&#x09;ascript:alert('XSS');">IE67chrome</a>
      //    <a href="jav&#x0A;ascript:alert('XSS');">IE67chrome</a>
      function sanitizeFilter(str) {
          return str.replace(rscripts, "").replace(ropen, function (a, b) {
              var match = a.toLowerCase().match(/<(\w+)\s/)
              if (match) {
                  //处理a标签的href属性，img标签的src属性，form标签的action属性
                  var reg = rsanitize[match[1]]
                  if (reg) {
                      a = a.replace(reg, function (s, name, value) {
                          var quote = value.charAt(0)
                          return name + "=" + quote + "javascript:void(0)" + quote // jshint ignore:line
                      })
                  }
              }
              return a.replace(ron, " ").replace(/\s+/g, " ") //移除onXXX事件
          })
      }
  
      /*
       'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
       'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
       'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
       'MMMM': Month in year (January-December)
       'MMM': Month in year (Jan-Dec)
       'MM': Month in year, padded (01-12)
       'M': Month in year (1-12)
       'dd': Day in month, padded (01-31)
       'd': Day in month (1-31)
       'EEEE': Day in Week,(Sunday-Saturday)
       'EEE': Day in Week, (Sun-Sat)
       'HH': Hour in day, padded (00-23)
       'H': Hour in day (0-23)
       'hh': Hour in am/pm, padded (01-12)
       'h': Hour in am/pm, (1-12)
       'mm': Minute in hour, padded (00-59)
       'm': Minute in hour (0-59)
       'ss': Second in minute, padded (00-59)
       's': Second in minute (0-59)
       'a': am/pm marker
       'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)
       format string can also be one of the following predefined localizable formats:
       
       'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)
       'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)
       'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)
       'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010
       'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)
       'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)
       'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)
       'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)
       */
  
      function toInt(str) {
          return parseInt(str, 10) || 0
      }
  
      function padNumber(num, digits, trim) {
          var neg = ''
          /* istanbul ignore if*/
          if (num < 0) {
              neg = '-'
              num = -num
          }
          num = '' + num
          while (num.length < digits) {
              num = '0' + num
          }if (trim) num = num.substr(num.length - digits)
          return neg + num
      }
  
      function dateGetter(name, size, offset, trim) {
          return function (date) {
              var value = date["get" + name]()
              if (offset > 0 || value > -offset) value += offset
              if (value === 0 && offset === -12) {
                  /* istanbul ignore next*/
                  value = 12
              }
              return padNumber(value, size, trim)
          }
      }
  
      function dateStrGetter(name, shortForm) {
          return function (date, formats) {
              var value = date["get" + name]()
              var get = (shortForm ? "SHORT" + name : name).toUpperCase()
              return formats[get][value]
          }
      }
  
      function timeZoneGetter(date) {
          var zone = -1 * date.getTimezoneOffset()
          var paddedZone = zone >= 0 ? "+" : ""
          paddedZone += padNumber(Math[zone > 0 ? "floor" : "ceil"](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2)
          return paddedZone
      }
      //取得上午下午
      function ampmGetter(date, formats) {
          return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]
      }
      var DATE_FORMATS = {
          yyyy: dateGetter("FullYear", 4),
          yy: dateGetter("FullYear", 2, 0, true),
          y: dateGetter("FullYear", 1),
          MMMM: dateStrGetter("Month"),
          MMM: dateStrGetter("Month", true),
          MM: dateGetter("Month", 2, 1),
          M: dateGetter("Month", 1, 1),
          dd: dateGetter("Date", 2),
          d: dateGetter("Date", 1),
          HH: dateGetter("Hours", 2),
          H: dateGetter("Hours", 1),
          hh: dateGetter("Hours", 2, -12),
          h: dateGetter("Hours", 1, -12),
          mm: dateGetter("Minutes", 2),
          m: dateGetter("Minutes", 1),
          ss: dateGetter("Seconds", 2),
          s: dateGetter("Seconds", 1),
          sss: dateGetter("Milliseconds", 3),
          EEEE: dateStrGetter("Day"),
          EEE: dateStrGetter("Day", true),
          a: ampmGetter,
          Z: timeZoneGetter
      }
      var rdateFormat = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/
      var raspnetjson = /^\/Date\((\d+)\)\/$/
      function dateFilter(date, format) {
          var locate = dateFilter.locate,
              text = "",
              parts = [],
              fn,
              match
          format = format || "mediumDate"
          format = locate[format] || format
          if (typeof date === "string") {
              if (/^\d+$/.test(date)) {
                  date = toInt(date)
              } else if (raspnetjson.test(date)) {
                  date = +RegExp.$1
              } else {
                  var trimDate = date.trim()
                  var dateArray = [0, 0, 0, 0, 0, 0, 0]
                  var oDate = new Date(0)
                  //取得年月日
                  trimDate = trimDate.replace(/^(\d+)\D(\d+)\D(\d+)/, function (_, a, b, c) {
                      var array = c.length === 4 ? [c, a, b] : [a, b, c]
                      dateArray[0] = toInt(array[0]) //年
                      dateArray[1] = toInt(array[1]) - 1 //月
                      dateArray[2] = toInt(array[2]) //日
                      return ""
                  })
                  var dateSetter = oDate.setFullYear
                  var timeSetter = oDate.setHours
                  trimDate = trimDate.replace(/[T\s](\d+):(\d+):?(\d+)?\.?(\d)?/, function (_, a, b, c, d) {
                      dateArray[3] = toInt(a) //小时
                      dateArray[4] = toInt(b) //分钟
                      dateArray[5] = toInt(c) //秒
                      if (d) {
                          //毫秒
                          dateArray[6] = Math.round(parseFloat("0." + d) * 1000)
                      }
                      return ""
                  })
                  var tzHour = 0
                  var tzMin = 0
                  trimDate = trimDate.replace(/Z|([+-])(\d\d):?(\d\d)/, function (z, symbol, c, d) {
                      dateSetter = oDate.setUTCFullYear
                      timeSetter = oDate.setUTCHours
                      if (symbol) {
                          tzHour = toInt(symbol + c)
                          tzMin = toInt(symbol + d)
                      }
                      return ''
                  })
  
                  dateArray[3] -= tzHour
                  dateArray[4] -= tzMin
                  dateSetter.apply(oDate, dateArray.slice(0, 3))
                  timeSetter.apply(oDate, dateArray.slice(3))
                  date = oDate
              }
          }
          if (typeof date === 'number') {
              date = new Date(date)
          }
  
          while (format) {
              match = rdateFormat.exec(format)
              /* istanbul ignore else */
              if (match) {
                  parts = parts.concat(match.slice(1))
                  format = parts.pop()
              } else {
                  parts.push(format)
                  format = null
              }
          }
          parts.forEach(function (value) {
              fn = DATE_FORMATS[value]
              text += fn ? fn(date, locate) : value.replace(/(^'|'$)/g, "").replace(/''/g, "'")
          })
          return text
      }
  
      var locate = {
          AMPMS: {
              0: '上午',
              1: '下午'
          },
          DAY: {
              0: '星期日',
              1: '星期一',
              2: '星期二',
              3: '星期三',
              4: '星期四',
              5: '星期五',
              6: '星期六'
          },
          MONTH: {
              0: '1月',
              1: '2月',
              2: '3月',
              3: '4月',
              4: '5月',
              5: '6月',
              6: '7月',
              7: '8月',
              8: '9月',
              9: '10月',
              10: '11月',
              11: '12月'
          },
          SHORTDAY: {
              '0': '周日',
              '1': '周一',
              '2': '周二',
              '3': '周三',
              '4': '周四',
              '5': '周五',
              '6': '周六'
          },
          fullDate: 'y年M月d日EEEE',
          longDate: 'y年M月d日',
          medium: 'yyyy-M-d H:mm:ss',
          mediumDate: 'yyyy-M-d',
          mediumTime: 'H:mm:ss',
          'short': 'yy-M-d ah:mm',
          shortDate: 'yy-M-d',
          shortTime: 'ah:mm'
      }
      locate.SHORTMONTH = locate.MONTH
      dateFilter.locate = locate
  
      /*
      https://github.com/hufyhang/orderBy/blob/master/index.js
      */
  
      function orderBy(array, by, decend) {
          var type = avalon$2.type(array)
          if (type !== 'array' && type !== 'object') throw 'orderBy只能处理对象或数组'
          var criteria = typeof by == 'string' ? function (el) {
              return el && el[by]
          } : typeof by === 'function' ? by : function (el) {
              return el
          }
          var mapping = {}
          var temp = []
          var index = 0
          for (var key in array) {
              if (array.hasOwnProperty(key)) {
                  var val = array[key]
                  var k = criteria(val, key)
                  if (k in mapping) {
                      mapping[k].push(key)
                  } else {
                      mapping[k] = [key]
                  }
  
                  temp.push(k)
              }
          }
  
          temp.sort()
          if (decend < 0) {
              temp.reverse()
          }
          var _array = type === 'array'
          var target = _array ? [] : {}
          return recovery(target, temp, function (k) {
              var key = mapping[k].shift()
              if (_array) {
                  target.push(array[key])
              } else {
                  target[key] = array[key]
              }
          })
      }
      function filterBy(array, search) {
          var type = avalon$2.type(array)
          if (type !== 'array' && type !== 'object') throw 'filterBy只能处理对象或数组'
          var args = avalon$2.slice(arguments, 2)
          var stype = avalon$2.type(search)
          if (stype === 'function') {
              var criteria = search
          } else if (stype === 'string' || stype === 'number') {
              if (search === '') {
                  return array
              } else {
                  var reg = new RegExp(avalon$2.escapeRegExp(search), 'i')
                  criteria = function criteria(el) {
                      return reg.test(el)
                  }
              }
          } else {
              return array
          }
  
          array = convertArray(array).filter(function (el, i) {
              return !!criteria.apply(el, [el.value, i].concat(args))
          })
  
          var isArray$$1 = type === 'array'
          var target = isArray$$1 ? [] : {}
          return recovery(target, array, function (el) {
              if (isArray$$1) {
                  target.push(el.value)
              } else {
                  target[el.key] = el.value
              }
          })
      }
  
      function selectBy(data, array, defaults) {
          if (avalon$2.isObject(data) && !Array.isArray(data)) {
              var target = []
              return recovery(target, array, function (name) {
                  target.push(data.hasOwnProperty(name) ? data[name] : defaults ? defaults[name] : '')
              })
          } else {
              return data
          }
      }
  
      function limitBy(input, limit, begin) {
          var type = avalon$2.type(input)
          if (type !== 'array' && type !== 'object') throw 'limitBy只能处理对象或数组'
          //必须是数值
          if (typeof limit !== 'number') {
              return input
          }
          //不能为NaN
          if (limit !== limit) {
              return input
          }
          //将目标转换为数组
          if (type === 'object') {
              input = convertArray(input)
          }
          var n = input.length
          limit = Math.floor(Math.min(n, limit))
          begin = typeof begin === 'number' ? begin : 0
          if (begin < 0) {
              begin = Math.max(0, n + begin)
          }
          var data = []
          for (var i = begin; i < n; i++) {
              if (data.length === limit) {
                  break
              }
              data.push(input[i])
          }
          var isArray$$1 = type === 'array'
          if (isArray$$1) {
              return data
          }
          var target = {}
          return recovery(target, data, function (el) {
              target[el.key] = el.value
          })
      }
  
      function recovery(ret, array, callback) {
          for (var i = 0, n = array.length; i < n; i++) {
              callback(array[i])
          }
          return ret
      }
  
      //Chrome谷歌浏览器中js代码Array.sort排序的bug乱序解决办法
      //http://www.cnblogs.com/yzeng/p/3949182.html
      function convertArray(array) {
          var ret = [],
              i = 0
          for (var key in array) {
              if (array.hasOwnProperty(key)) {
                  ret[i] = {
                      oldIndex: i,
                      value: array[key],
                      key: key
                  }
                  i++
              }
          }
          return ret
      }
  
      var eventFilters = {
          stop: function stop(e) {
              e.stopPropagation()
              return e
          },
          prevent: function prevent(e) {
              e.preventDefault()
              return e
          }
      }
      var keys = {
          esc: 27,
          tab: 9,
          enter: 13,
          space: 32,
          del: 46,
          up: 38,
          left: 37,
          right: 39,
          down: 40
      }
      for (var name$1 in keys) {
          ;(function (filter, key) {
              eventFilters[filter] = function (e) {
                  if (e.which !== key) {
                      e.$return = true
                  }
                  return e
              }
          })(name$1, keys[name$1])
      }
  
      //https://github.com/teppeis/htmlspecialchars
      function escapeFilter(str) {
          if (str == null) return ''
  
          return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;')
      }
  
      var filters = avalon$2.filters = {}
  
      avalon$2.composeFilters = function () {
          var args = arguments
          return function (value) {
              for (var i = 0, arr; arr = args[i++];) {
                  var name = arr[0]
                  var filter = avalon$2.filters[name]
                  if (typeof filter === 'function') {
                      arr[0] = value
                      try {
                          value = filter.apply(0, arr)
                      } catch (e) {}
                  }
              }
              return value
          }
      }
  
      avalon$2.escapeHtml = escapeFilter
  
      avalon$2.mix(filters, {
          uppercase: function uppercase(str) {
              return String(str).toUpperCase()
          },
          lowercase: function lowercase(str) {
              return String(str).toLowerCase()
          },
          truncate: function truncate(str, length, end) {
              //length，新字符串长度，truncation，新字符串的结尾的字段,返回新字符串
              if (!str) {
                  return ''
              }
              str = String(str)
              if (isNaN(length)) {
                  length = 30
              }
              end = typeof end === "string" ? end : "..."
              return str.length > length ? str.slice(0, length - end.length) + end : /* istanbul ignore else*/
              str
          },
  
          camelize: avalon$2.camelize,
          date: dateFilter,
          escape: escapeFilter,
          sanitize: sanitizeFilter,
          number: numberFilter,
          currency: function currency(amount, symbol, fractionSize) {
              return (symbol || '\xA5') + numberFilter(amount, isFinite(fractionSize) ? /* istanbul ignore else*/fractionSize : 2)
          }
      }, { filterBy: filterBy, orderBy: orderBy, selectBy: selectBy, limitBy: limitBy }, eventFilters)
  
      /* istanbul ignore next */
      function fixContains(root, el) {
          try {
              //IE6-8,游离于DOM树外的文本节点，访问parentNode有时会抛错
              while (el = el.parentNode) {
                  if (el === root) return true
              }
          } catch (e) {}
          return false
      }
  
      //safari5+是把contains方法放在Element.prototype上而不是Node.prototype
      avalon$2.contains = fixContains
  
      avalon$2.cloneNode = function (a) {
          return a.cloneNode(true)
      }
  
      if (avalon$2.modern) {
          var fixFF = function fixFF(prop, cb) {
              //firefox12 http://caniuse.com/#search=outerHTML
              if (!(prop in root)) {
                  HTMLElement.prototype.__defineGetter__(prop, cb)
              }
          }
  
          if (!document$1.contains) {
              Node.prototype.contains = function (child) {
                  //IE6-8没有Node对象
                  return fixContains(this, child)
              }
          }
  
          fixFF('outerHTML', function () {
              //https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/children
              var div = document$1.createElement('div')
              div.appendChild(this)
              return div.innerHTML
          })
          fixFF('children', function () {
              var children = []
              for (var i = 0, el; el = this.childNodes[i++];) {
                  if (el.nodeType === 1) {
                      children.push(el)
                  }
              }
              return children
          })
          fixFF('innerText', function () {
              return this.textContent
          })
      }
  
      'add,remove'.replace(rword, function (method) {
          avalon$2.fn[method + 'Class'] = function (cls) {
              var el = this[0] || {}
              //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
              if (cls && typeof cls === 'string' && el.nodeType === 1) {
                  cls.replace(rnowhite, function (c) {
                      el.classList[method](c)
                  })
              }
              return this
          }
      })
  
      avalon$2.shadowCopy(avalon$2.fn, {
          hasClass: function hasClass(cls) {
              var el = this[0] || {}
              //IE10+, chrome8+, firefox3.6+, safari5.1+,opera11.5+支持classList,
              //chrome24+,firefox26+支持classList2.0
              return el.nodeType === 1 && el.classList.contains(cls)
          },
          toggleClass: function toggleClass(value, stateVal) {
              var isBool = typeof stateVal === 'boolean'
              var me = this
              String(value).replace(rnowhite, function (c) {
                  var state = isBool ? stateVal : !me.hasClass(c)
                  me[state ? 'addClass' : 'removeClass'](c)
              })
              return this
          }
      })
  
      var propMap = { //不规则的属性名映射
          'accept-charset': 'acceptCharset',
          'char': 'ch',
          charoff: 'chOff',
          'class': 'className',
          'for': 'htmlFor',
          'http-equiv': 'httpEquiv'
      }
      /*
      contenteditable不是布尔属性
      http://www.zhangxinxu.com/wordpress/2016/01/contenteditable-plaintext-only/
      contenteditable=''
      contenteditable='events'
      contenteditable='caret'
      contenteditable='plaintext-only'
      contenteditable='true'
      contenteditable='false'
       */
      var bools = ['autofocus,autoplay,async,allowTransparency,checked,controls', 'declare,disabled,defer,defaultChecked,defaultSelected,', 'isMap,loop,multiple,noHref,noResize,noShade', 'open,readOnly,selected'].join(',')
  
      bools.replace(/\w+/g, function (name) {
          propMap[name.toLowerCase()] = name
      })
  
      var anomaly = ['accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan', 'dateTime,defaultValue,contentEditable,frameBorder,longDesc,maxLength,' + 'marginWidth,marginHeight,rowSpan,tabIndex,useMap,vSpace,valueType,vAlign'].join(',')
  
      anomaly.replace(/\w+/g, function (name) {
          propMap[name.toLowerCase()] = name
      })
  
      //module.exports = propMap
  
      var rsvg = /^\[object SVG\w*Element\]$/
      function updateAttrs(node, attrs) {
          for (var attrName in attrs) {
              var val = attrs[attrName]
              /* istanbul ignore if*/
              if (attrName.indexOf('data-') === 0 || rsvg.test(node)) {
                  node.setAttribute(attrName, val)
              } else {
                  var propName = propMap[attrName] || attrName
                  if (typeof node[propName] === 'boolean') {
                      //布尔属性必须使用el.xxx = true|false方式设值
                      //如果为false, IE全系列下相当于setAttribute(xxx,''),
                      //会影响到样式,需要进一步处理
                      node[propName] = !!val
                  }
                  if (val === false) {
                      node.removeAttribute(attrName)
                      continue
                  }
  
                  //SVG只能使用setAttribute(xxx, yyy), VML只能使用node.xxx = yyy ,
                  //HTML的固有属性必须node.xxx = yyy
                  var isInnate = attrName in node.cloneNode(false)
                  if (isInnate) {
                      node[propName] = val + ''
                  } else {
                      node.setAttribute(attrName, val)
                  }
              }
          }
      }
  
      avalon$2.parseJSON = JSON.parse
  
      avalon$2.fn.attr = function (name, value) {
          if (arguments.length === 2) {
              this[0].setAttribute(name, value)
              return this
          } else {
              return this[0].getAttribute(name)
          }
      }
  
      var cssMap = {
          'float': 'cssFloat'
      }
      avalon$2.cssNumber = oneObject('animationIterationCount,columnCount,order,flex,flexGrow,flexShrink,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom')
      var prefixes = ['', '-webkit-', '-o-', '-moz-', '-ms-']
      /* istanbul ignore next */
      avalon$2.cssName = function (name, host, camelCase) {
          if (cssMap[name]) {
              return cssMap[name]
          }
          host = host || avalon$2.root.style || {}
          for (var i = 0, n = prefixes.length; i < n; i++) {
              camelCase = avalon$2.camelize(prefixes[i] + name)
              if (camelCase in host) {
                  return cssMap[name] = camelCase
              }
          }
          return null
      }
      /* istanbul ignore next */
      avalon$2.css = function (node, name, value, fn) {
          //读写删除元素节点的样式
          if (node instanceof avalon$2) {
              node = node[0]
          }
          if (node.nodeType !== 1) {
              return
          }
          var prop = avalon$2.camelize(name)
          name = avalon$2.cssName(prop) || /* istanbul ignore next*/prop
          if (value === void 0 || typeof value === 'boolean') {
              //获取样式
              fn = cssHooks[prop + ':get'] || cssHooks['@:get']
              if (name === 'background') {
                  name = 'backgroundColor'
              }
              var val = fn(node, name)
              return value === true ? parseFloat(val) || 0 : val
          } else if (value === '') {
              //请除样式
              node.style[name] = ''
          } else {
              //设置样式
              if (value == null || value !== value) {
                  return
              }
              if (isFinite(value) && !avalon$2.cssNumber[prop]) {
                  value += 'px'
              }
              fn = cssHooks[prop + ':set'] || cssHooks['@:set']
              fn(node, name, value)
          }
      }
      /* istanbul ignore next */
      avalon$2.fn.css = function (name, value) {
          if (avalon$2.isPlainObject(name)) {
              for (var i in name) {
                  avalon$2.css(this, i, name[i])
              }
          } else {
              var ret = avalon$2.css(this, name, value)
          }
          return ret !== void 0 ? ret : this
      }
      /* istanbul ignore next */
      avalon$2.fn.position = function () {
          var offsetParent,
              offset,
              elem = this[0],
              parentOffset = {
              top: 0,
              left: 0
          }
          if (!elem) {
              return parentOffset
          }
          if (this.css('position') === 'fixed') {
              offset = elem.getBoundingClientRect()
          } else {
              offsetParent = this.offsetParent() //得到真正的offsetParent
              offset = this.offset() // 得到正确的offsetParent
              if (offsetParent[0].tagName !== 'HTML') {
                  parentOffset = offsetParent.offset()
              }
              parentOffset.top += avalon$2.css(offsetParent[0], 'borderTopWidth', true)
              parentOffset.left += avalon$2.css(offsetParent[0], 'borderLeftWidth', true)
  
              // Subtract offsetParent scroll positions
              parentOffset.top -= offsetParent.scrollTop()
              parentOffset.left -= offsetParent.scrollLeft()
          }
          return {
              top: offset.top - parentOffset.top - avalon$2.css(elem, 'marginTop', true),
              left: offset.left - parentOffset.left - avalon$2.css(elem, 'marginLeft', true)
          }
      }
      /* istanbul ignore next */
      avalon$2.fn.offsetParent = function () {
          var offsetParent = this[0].offsetParent
          while (offsetParent && avalon$2.css(offsetParent, 'position') === 'static') {
              offsetParent = offsetParent.offsetParent
          }
          return avalon$2(offsetParent || avalon$2.root)
      }
  
      /* istanbul ignore next */
      cssHooks['@:set'] = function (node, name, value) {
          try {
              //node.style.width = NaN;node.style.width = 'xxxxxxx';
              //node.style.width = undefine 在旧式IE下会抛异常
              node.style[name] = value
          } catch (e) {}
      }
      /* istanbul ignore next */
      cssHooks['@:get'] = function (node, name) {
          if (!node || !node.style) {
              throw new Error('getComputedStyle要求传入一个节点 ' + node)
          }
          var ret,
              styles = window$1.getComputedStyle(node, null)
          if (styles) {
              ret = name === 'filter' ? styles.getPropertyValue(name) : styles[name]
              if (ret === '') {
                  ret = node.style[name] //其他浏览器需要我们手动取内联样式
              }
          }
          return ret
      }
  
      cssHooks['opacity:get'] = function (node) {
          var ret = cssHooks['@:get'](node, 'opacity')
          return ret === '' ? '1' : ret
      }
  
      'top,left'.replace(avalon$2.rword, function (name) {
          cssHooks[name + ':get'] = function (node) {
              var computed = cssHooks['@:get'](node, name)
              return (/px$/.test(computed) ? computed : avalon$2(node).position()[name] + 'px'
              )
          }
      })
  
      var cssShow = {
          position: 'absolute',
          visibility: 'hidden',
          display: 'block'
      }
  
      var rdisplayswap = /^(none|table(?!-c[ea]).+)/
      /* istanbul ignore next */
      function showHidden(node, array) {
          //http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
          if (node.offsetWidth <= 0) {
              //opera.offsetWidth可能小于0
              if (rdisplayswap.test(cssHooks['@:get'](node, 'display'))) {
                  var obj = {
                      node: node
                  }
                  for (var name in cssShow) {
                      obj[name] = node.style[name]
                      node.style[name] = cssShow[name]
                  }
                  array.push(obj)
              }
              var parent = node.parentNode
              if (parent && parent.nodeType === 1) {
                  showHidden(parent, array)
              }
          }
      }
      /* istanbul ignore next*/
      avalon$2.each({
          Width: 'width',
          Height: 'height'
      }, function (name, method) {
          var clientProp = 'client' + name,
              scrollProp = 'scroll' + name,
              offsetProp = 'offset' + name
          cssHooks[method + ':get'] = function (node, which, override) {
              var boxSizing = -4
              if (typeof override === 'number') {
                  boxSizing = override
              }
              which = name === 'Width' ? ['Left', 'Right'] : ['Top', 'Bottom']
              var ret = node[offsetProp] // border-box 0
              if (boxSizing === 2) {
                  // margin-box 2
                  return ret + avalon$2.css(node, 'margin' + which[0], true) + avalon$2.css(node, 'margin' + which[1], true)
              }
              if (boxSizing < 0) {
                  // padding-box  -2
                  ret = ret - avalon$2.css(node, 'border' + which[0] + 'Width', true) - avalon$2.css(node, 'border' + which[1] + 'Width', true)
              }
              if (boxSizing === -4) {
                  // content-box -4
                  ret = ret - avalon$2.css(node, 'padding' + which[0], true) - avalon$2.css(node, 'padding' + which[1], true)
              }
              return ret
          }
          cssHooks[method + '&get'] = function (node) {
              var hidden = []
              showHidden(node, hidden)
              var val = cssHooks[method + ':get'](node)
              for (var i = 0, obj; obj = hidden[i++];) {
                  node = obj.node
                  for (var n in obj) {
                      if (typeof obj[n] === 'string') {
                          node.style[n] = obj[n]
                      }
                  }
              }
              return val
          }
          avalon$2.fn[method] = function (value) {
              //会忽视其display
              var node = this[0]
              if (arguments.length === 0) {
                  if (node.setTimeout) {
                      //取得窗口尺寸
                      return node['inner' + name] || node.document.documentElement[clientProp] || node.document.body[clientProp] //IE6下前两个分别为undefined,0
                  }
                  if (node.nodeType === 9) {
                      //取得页面尺寸
                      var doc = node.documentElement
                      //FF chrome    html.scrollHeight< body.scrollHeight
                      //IE 标准模式 : html.scrollHeight> body.scrollHeight
                      //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点？
                      return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp])
                  }
                  return cssHooks[method + '&get'](node)
              } else {
                  return this.css(method, value)
              }
          }
          avalon$2.fn['inner' + name] = function () {
              return cssHooks[method + ':get'](this[0], void 0, -2)
          }
          avalon$2.fn['outer' + name] = function (includeMargin) {
              return cssHooks[method + ':get'](this[0], void 0, includeMargin === true ? 2 : 0)
          }
      })
  
      avalon$2.fn.offset = function () {
          //取得距离页面左右角的坐标
          var node = this[0]
          try {
              var rect = node.getBoundingClientRect()
              // Make sure element is not hidden (display: none) or disconnected
              // https://github.com/jquery/jquery/pull/2043/files#r23981494
              if (rect.width || rect.height || node.getClientRects().length) {
                  var doc = node.ownerDocument
                  var root$$1 = doc.documentElement
                  var win = doc.defaultView
                  return {
                      top: rect.top + win.pageYOffset - root$$1.clientTop,
                      left: rect.left + win.pageXOffset - root$$1.clientLeft
                  }
              }
          } catch (e) {
              return {
                  left: 0,
                  top: 0
              }
          }
      }
  
      avalon$2.each({
          scrollLeft: "pageXOffset",
          scrollTop: "pageYOffset"
      }, function (method, prop) {
          avalon$2.fn[method] = function (val) {
              var node = this[0] || {}
              var win = getWindow$$1(node)
              var top = method === "scrollTop"
              if (!arguments.length) {
                  return win ? win[prop] : node[method]
              } else {
                  if (win) {
                      win.scrollTo(!top ? val : win[prop], top ? val : win[prop])
                  } else {
                      node[method] = val
                  }
              }
          }
      })
  
      function getWindow$$1(node) {
          return node.window || node.defaultView || false
      }
  
      var rcheckedType = /^(?:checkbox|radio)$/
  
      function getDuplexType(elem) {
          var ret = elem.tagName.toLowerCase()
          if (ret === 'input') {
              return rcheckedType.test(elem.type) ? 'checked' : elem.type
          }
          return ret
      }
  
      var valHooks = {
          'select:get': function self(node, ret, index, singleton) {
              var nodes = node.children,
                  value,
                  index = ret ? index : node.selectedIndex
              singleton = ret ? singleton : node.type === 'select-one' || index < 0
              ret = ret || []
              for (var i = 0, el; el = nodes[i++];) {
                  if (!el.disabled) {
                      switch (el.nodeName.toLowerCase()) {
                          case 'option':
                              if (el.selected || el.index === index) {
                                  value = el.value
                                  if (singleton) {
                                      return value
                                  } else {
                                      ret.push(value)
                                  }
                              }
                              break
                          case 'optgroup':
                              value = self(el, ret, index, singleton)
                              if (typeof value === 'string') {
                                  return value
                              }
                              break
                      }
                  }
              }
              return singleton ? null : ret
          },
          'select:set': function selectSet(node, values, optionSet) {
              values = [].concat(values) //强制转换为数组
              for (var i = 0, el; el = node.options[i++];) {
                  if (el.selected = values.indexOf(el.value) > -1) {
                      optionSet = true
                  }
              }
              if (!optionSet) {
                  node.selectedIndex = -1
              }
          }
      }
  
      avalon$2.fn.val = function (value) {
          var node = this[0]
          if (node && node.nodeType === 1) {
              var get = arguments.length === 0
              var access = get ? ':get' : ':set'
              var fn = valHooks[getDuplexType(node) + access]
              if (fn) {
                  var val = fn(node, value)
              } else if (get) {
                  return (node.value || '').replace(/\r/g, '')
              } else {
                  node.value = value
              }
          }
          return get ? val : this
      }
  
      /* 
       * 将要检测的字符串的字符串替换成??123这样的格式
       */
      var stringNum = 0
      var stringPool = {
          map: {}
      }
      var rfill = /\?\?\d+/g
      function dig(a) {
          var key = '??' + stringNum++
          stringPool.map[key] = a
          return key + ' '
      }
      function fill(a) {
          var val = stringPool.map[a]
          return val
      }
      function clearString(str) {
          var array = readString(str)
          for (var i = 0, n = array.length; i < n; i++) {
              str = str.replace(array[i], dig)
          }
          return str
      }
  
      function readString(str) {
          var end,
              s = 0
          var ret = []
          for (var i = 0, n = str.length; i < n; i++) {
              var c = str.charAt(i)
              if (!end) {
                  if (c === "'") {
                      end = "'"
                      s = i
                  } else if (c === '"') {
                      end = '"'
                      s = i
                  }
              } else {
                  if (c === end) {
                      ret.push(str.slice(s, i + 1))
                      end = false
                  }
              }
          }
          return ret
      }
  
      var voidTag = {
          area: 1,
          base: 1,
          basefont: 1,
          bgsound: 1,
          br: 1,
          col: 1,
          command: 1,
          embed: 1,
          frame: 1,
          hr: 1,
          img: 1,
          input: 1,
          keygen: 1,
          link: 1,
          meta: 1,
          param: 1,
          source: 1,
          track: 1,
          wbr: 1
      }
  
      var orphanTag = {
          script: 1,
          style: 1,
          textarea: 1,
          xmp: 1,
          noscript: 1,
          template: 1
      }
  
      /* 
       *  此模块只用于文本转虚拟DOM, 
       *  因为在真实浏览器会对我们的HTML做更多处理,
       *  如, 添加额外属性, 改变结构
       *  此模块就是用于模拟这些行为
       */
      function makeOrphan(node, nodeName, innerHTML) {
          switch (nodeName) {
              case 'style':
              case 'script':
              case 'noscript':
              case 'template':
              case 'xmp':
                  node.children = [{
                      nodeName: '#text',
                      nodeValue: innerHTML
                  }]
                  break
              case 'textarea':
                  var props = node.props
                  props.type = nodeName
                  props.value = innerHTML
                  node.children = [{
                      nodeName: '#text',
                      nodeValue: innerHTML
                  }]
                  break
              case 'option':
                  node.children = [{
                      nodeName: '#text',
                      nodeValue: trimHTML(innerHTML)
                  }]
                  break
          }
      }
  
      //专门用于处理option标签里面的标签
      var rtrimHTML = /<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi
      function trimHTML(v) {
          return String(v).replace(rtrimHTML, '').trim()
      }
  
      //widget rule duplex validate
  
      //如果直接将tr元素写table下面,那么浏览器将将它们(相邻的那几个),放到一个动态创建的tbody底下
      function makeTbody(nodes) {
          var tbody,
              needAddTbody = false,
              count = 0,
              start = 0,
              n = nodes.length
          for (var i = 0; i < n; i++) {
              var node = nodes[i]
              if (!tbody) {
                  if (node.nodeName === 'tr') {
                      //收集tr及tr两旁的注释节点
                      tbody = {
                          nodeName: 'tbody',
                          props: {},
                          children: []
                      }
                      tbody.children.push(node)
                      needAddTbody = true
                      if (start === 0) start = i
                      nodes[i] = tbody
                  }
              } else {
                  if (node.nodeName !== 'tr' && node.children) {
                      tbody = false
                  } else {
                      tbody.children.push(node)
                      count++
                      nodes[i] = 0
                  }
              }
          }
  
          if (needAddTbody) {
              for (i = start; i < n; i++) {
                  if (nodes[i] === 0) {
                      nodes.splice(i, 1)
                      i--
                      count--
                      if (count === 0) {
                          break
                      }
                  }
              }
          }
      }
  
      function validateDOMNesting(parent, child) {
  
          var parentTag = parent.nodeName
          var tag = child.nodeName
          var parentChild = nestObject[parentTag]
          if (parentChild) {
              if (parentTag === 'p') {
                  if (pNestChild[tag]) {
                      avalon$2.warn('P element can not  add these childlren:\n' + Object.keys(pNestChild))
                      return false
                  }
              } else if (!parentChild[tag]) {
                  avalon$2.warn(parentTag.toUpperCase() + 'element only add these children:\n' + Object.keys(parentChild) + '\nbut you add ' + tag.toUpperCase() + ' !!')
                  return false
              }
          }
          return true
      }
  
      function makeObject(str) {
          return oneObject(str + ',template,#document-fragment,#comment')
      }
      var pNestChild = oneObject('div,ul,ol,dl,table,h1,h2,h3,h4,h5,h6,form,fieldset')
      var tNestChild = makeObject('tr,style,script')
      var nestObject = {
          p: pNestChild,
          // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect
          select: makeObject('option,optgroup,#text'),
          optgroup: makeObject('option,#text'),
          option: makeObject('#text'),
          // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd
          // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption
          // No special behavior since these rules fall back to "in body" mode for
          // all except special table nodes which cause bad parsing behavior anyway.
  
          // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr
          tr: makeObject('th,td,style,script'),
  
          // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody
          tbody: tNestChild,
          tfoot: tNestChild,
          thead: tNestChild,
          // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup
          colgroup: makeObject('col'),
          // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable
          // table: oneObject('caption,colgroup,tbody,thead,tfoot,style,script,template,#document-fragment'),
          // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead
          head: makeObject('base,basefont,bgsound,link,style,script,meta,title,noscript,noframes'),
          // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element
          html: oneObject('head,body')
      }
  
      /**
       * ------------------------------------------------------------
       * avalon2.1.1的新式lexer
       * 将字符串变成一个虚拟DOM树,方便以后进一步变成模板函数
       * 此阶段只会生成VElement,VText,VComment
       * ------------------------------------------------------------
       */
      function nomalString(str) {
          return avalon$2.unescapeHTML(str.replace(rfill, fill))
      }
      //https://github.com/rviscomi/trunk8/blob/master/trunk8.js
  
      var ropenTag = /^<([-A-Za-z0-9_]+)\s*([^>]*?)(\/?)>/
      var rendTag = /^<\/([^>]+)>/
      var rtagStart = /[\!\/a-z]/i //闭标签的第一个字符,开标签的第一个英文,注释节点的!
      var rlineSp = /\\n\s*/g
      var rattrs = /([^=\s]+)(?:\s*=\s*(\S+))?/
  
      var rcontent = /\S/ //判定里面有没有内容
      function fromString(str) {
          return from(str)
      }
      avalon$2.lexer = fromString
  
      var strCache = new Cache(100)
  
      function AST() {}
      AST.prototype = {
          init: function init(str) {
              this.ret = []
              var stack = []
              stack.last = function () {
                  return stack[stack.length - 1]
              }
              this.stack = stack
              this.str = str
          },
          gen: function gen() {
              var breakIndex = 999999
              do {
                  this.tryGenText()
                  this.tryGenComment()
                  this.tryGenOpenTag()
                  this.tryGenCloseTag()
                  var node = this.node
                  this.node = 0
                  if (!node || --breakIndex === 0) {
                      break
                  }
                  if (node.end) {
                      if (node.nodeName === 'table') {
                          makeTbody(node.children)
                      }
                      delete node.end
                  }
              } while (this.str.length)
              return this.ret
          },
  
          fixPos: function fixPos(str, i) {
              var tryCount = str.length - i
              while (tryCount--) {
                  if (!rtagStart.test(str.charAt(i + 1))) {
                      i = str.indexOf('<', i + 1)
                  } else {
                      break
                  }
              }
              if (tryCount === 0) {
                  i = str.length
              }
              return i
          },
          tryGenText: function tryGenText() {
              var str = this.str
              if (str.charAt(0) !== '<') {
                  //处理文本节点
                  var i = str.indexOf('<')
                  if (i === -1) {
                      i = str.length
                  } else if (!rtagStart.test(str.charAt(i + 1))) {
                      //处理`内容2 {{ (idx1 < < <  1 ? 'red' : 'blue' ) + a }} ` 的情况 
                      i = this.fixPos(str, i)
                  }
                  var nodeValue = str.slice(0, i).replace(rfill, fill)
                  this.str = str.slice(i)
                  this.node = {
                      nodeName: '#text',
                      nodeValue: nodeValue
                  }
                  if (rcontent.test(nodeValue)) {
                      this.tryGenChildren() //不收集空白节点
                  }
              }
          },
          tryGenComment: function tryGenComment() {
              if (!this.node) {
                  var str = this.str
                  var i = str.indexOf('<!--') //处理注释节点
                  /* istanbul ignore if*/
                  if (i === 0) {
                      var l = str.indexOf('-->')
                      if (l === -1) {
                          avalon$2.error('注释节点没有闭合' + str)
                      }
                      var nodeValue = str.slice(4, l).replace(rfill, fill)
                      this.str = str.slice(l + 3)
                      this.node = {
                          nodeName: '#comment',
                          nodeValue: nodeValue
                      }
                      this.tryGenChildren()
                  }
              }
          },
          tryGenOpenTag: function tryGenOpenTag() {
              if (!this.node) {
                  var str = this.str
                  var match = str.match(ropenTag) //处理元素节点开始部分
                  if (match) {
                      var nodeName = match[1]
                      var props = {}
                      if (/^[A-Z]/.test(nodeName) && avalon$2.components[nodeName]) {
                          props.is = nodeName
                      }
                      nodeName = nodeName.toLowerCase()
                      var isVoidTag = !!voidTag[nodeName] || match[3] === '\/'
                      var node = this.node = {
                          nodeName: nodeName,
                          props: {},
                          children: [],
                          isVoidTag: isVoidTag
                      }
                      var attrs = match[2]
                      if (attrs) {
                          this.genProps(attrs, node.props)
                      }
                      this.tryGenChildren()
                      str = str.slice(match[0].length)
                      if (isVoidTag) {
                          node.end = true
                      } else {
                          this.stack.push(node)
                          if (orphanTag[nodeName] || nodeName === 'option') {
                              var index = str.indexOf('</' + nodeName + '>')
                              var innerHTML = str.slice(0, index).trim()
                              str = str.slice(index)
                              makeOrphan(node, nodeName, nomalString(innerHTML))
                          }
                      }
                      this.str = str
                  }
              }
          },
          tryGenCloseTag: function tryGenCloseTag() {
              if (!this.node) {
                  var str = this.str
                  var match = str.match(rendTag) //处理元素节点结束部分
                  if (match) {
                      var nodeName = match[1].toLowerCase()
                      var last = this.stack.last()
                      /* istanbul ignore if*/
                      if (!last) {
                          avalon$2.error(match[0] + '前面缺少<' + nodeName + '>')
                          /* istanbul ignore else*/
                      } else if (last.nodeName !== nodeName) {
                          var errMsg = last.nodeName + '没有闭合,请注意属性的引号'
                          avalon$2.warn(errMsg)
                          avalon$2.error(errMsg)
                      }
                      var node = this.stack.pop()
                      node.end = true
                      this.node = node
                      this.str = str.slice(match[0].length)
                  }
              }
          },
          tryGenChildren: function tryGenChildren() {
              var node = this.node
              var p = this.stack.last()
              if (p) {
                  validateDOMNesting(p, node)
                  p.children.push(node)
              } else {
                  this.ret.push(node)
              }
          },
          genProps: function genProps(attrs, props) {
  
              while (attrs) {
                  var arr = rattrs.exec(attrs)
  
                  if (arr) {
                      var name = arr[1]
                      var value = arr[2] || ''
                      attrs = attrs.replace(arr[0], '')
                      if (value) {
                          //https://github.com/RubyLouvre/avalon/issues/1844
                          if (value.indexOf('??') === 0) {
                              value = nomalString(value).replace(rlineSp, '').slice(1, -1)
                          }
                      }
                      if (!(name in props)) {
                          props[name] = value
                      }
                  } else {
                      break
                  }
              }
          }
      }
  
      var vdomAst = new AST()
  
      function from(str) {
          var cacheKey = str
          var cached = strCache.get(cacheKey)
          if (cached) {
              return avalon$2.mix(true, [], cached)
          }
          stringPool.map = {}
          str = clearString(str)
  
          vdomAst.init(str)
          var ret = vdomAst.gen()
          strCache.put(cacheKey, avalon$2.mix(true, [], ret))
          return ret
      }
  
      var rhtml = /<|&#?\w+;/
      var htmlCache = new Cache(128)
      var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig
  
      avalon$2.parseHTML = function (html) {
          var fragment = createFragment()
          //处理非字符串
          if (typeof html !== 'string') {
              return fragment
          }
          //处理非HTML字符串
          if (!rhtml.test(html)) {
              return document$1.createTextNode(html)
          }
  
          html = html.replace(rxhtml, '<$1></$2>').trim()
          var hasCache = htmlCache.get(html)
          if (hasCache) {
              return avalon$2.cloneNode(hasCache)
          }
          var vnodes = fromString(html)
          for (var i = 0, el; el = vnodes[i++];) {
              var child = avalon$2.vdom(el, 'toDOM')
              fragment.appendChild(child)
          }
          if (html.length < 1024) {
              htmlCache.put(html, fragment)
          }
          return fragment
      }
  
      avalon$2.innerHTML = function (node, html) {
          var parsed = avalon$2.parseHTML(html)
          this.clearHTML(node)
          node.appendChild(parsed)
      }
  
      //https://github.com/karloespiritu/escapehtmlent/blob/master/index.js
      avalon$2.unescapeHTML = function (html) {
          return String(html).replace(/&quot;/g, '"').replace(/&#39;/g, '\'').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&')
      }
  
      avalon$2.clearHTML = function (node) {
          /* istanbul ignore next */
          while (node.lastChild) {
              node.removeChild(node.lastChild)
          }
          return node
      }
  
      //http://www.feiesoft.com/html/events.html
      //http://segmentfault.com/q/1010000000687977/a-1020000000688757
      var canBubbleUp = {
          click: true,
          dblclick: true,
          keydown: true,
          keypress: true,
          keyup: true,
          mousedown: true,
          mousemove: true,
          mouseup: true,
          mouseover: true,
          mouseout: true,
          wheel: true,
          mousewheel: true,
          input: true,
          change: true,
          beforeinput: true,
          compositionstart: true,
          compositionupdate: true,
          compositionend: true,
          select: true,
          //http://blog.csdn.net/lee_magnum/article/details/17761441
          cut: true,
          copy: true,
          paste: true,
          beforecut: true,
          beforecopy: true,
          beforepaste: true,
          focusin: true,
          focusout: true,
          DOMFocusIn: true,
          DOMFocusOut: true,
          DOMActivate: true,
          dragend: true,
          datasetchanged: true
      }
  
      /* istanbul ignore if */
      var hackSafari = avalon$2.modern && document$1.ontouchstart
  
      //添加fn.bind, fn.unbind, bind, unbind
      avalon$2.fn.bind = function (type, fn, phase) {
          if (this[0]) {
              //此方法不会链
              return avalon$2.bind(this[0], type, fn, phase)
          }
      }
  
      avalon$2.fn.unbind = function (type, fn, phase) {
          if (this[0]) {
              var args = _slice.call(arguments)
              args.unshift(this[0])
              avalon$2.unbind.apply(0, args)
          }
          return this
      }
  
      /*绑定事件*/
      avalon$2.bind = function (elem, type, fn) {
          if (elem.nodeType === 1) {
              var value = elem.getAttribute('avalon-events') || ''
              //如果是使用ms-on-*绑定的回调,其uuid格式为e12122324,
              //如果是使用bind方法绑定的回调,其uuid格式为_12
              var uuid = getShortID(fn)
              var hook = eventHooks[type]
              /* istanbul ignore if */
              if (type === 'click' && hackSafari) {
                  elem.addEventListener('click', avalon$2.noop)
              }
              /* istanbul ignore if */
              if (hook) {
                  type = hook.type || type
                  if (hook.fix) {
                      fn = hook.fix(elem, fn)
                      fn.uuid = uuid
                  }
              }
              var key = type + ':' + uuid
              avalon$2.eventListeners[fn.uuid] = fn
              /* istanbul ignore if */
              if (value.indexOf(type + ':') === -1) {
                  //同一种事件只绑定一次
                  if (canBubbleUp[type] || avalon$2.modern && focusBlur[type]) {
                      delegateEvent(type)
                  } else {
                      avalon$2._nativeBind(elem, type, dispatch)
                  }
              }
              var keys = value.split(',')
              /* istanbul ignore if */
              if (keys[0] === '') {
                  keys.shift()
              }
              if (keys.indexOf(key) === -1) {
                  keys.push(key)
                  setEventId(elem, keys.join(','))
                  //将令牌放进avalon-events属性中
              }
          } else {
              /* istanbul ignore next */
              avalon$2._nativeBind(elem, type, fn)
          }
          return fn //兼容之前的版本
      }
  
      function setEventId(node, value) {
          node.setAttribute('avalon-events', value)
      }
      /* istanbul ignore next */
      avalon$2.unbind = function (elem, type, fn) {
          if (elem.nodeType === 1) {
              var value = elem.getAttribute('avalon-events') || ''
              switch (arguments.length) {
                  case 1:
                      avalon$2._nativeUnBind(elem, type, dispatch)
                      elem.removeAttribute('avalon-events')
                      break
                  case 2:
                      value = value.split(',').filter(function (str) {
                          return str.indexOf(type + ':') === -1
                      }).join(',')
                      setEventId(elem, value)
                      break
                  default:
                      var search = type + ':' + fn.uuid
                      value = value.split(',').filter(function (str) {
                          return str !== search
                      }).join(',')
                      setEventId(elem, value)
                      delete avalon$2.eventListeners[fn.uuid]
                      break
              }
          } else {
              avalon$2._nativeUnBind(elem, type, fn)
          }
      }
  
      var typeRegExp = {}
  
      function collectHandlers(elem, type, handlers) {
          var value = elem.getAttribute('avalon-events')
          if (value && (elem.disabled !== true || type !== 'click')) {
              var uuids = []
              var reg = typeRegExp[type] || (typeRegExp[type] = new RegExp("\\b" + type + '\\:([^,\\s]+)', 'g'))
              value.replace(reg, function (a, b) {
                  uuids.push(b)
                  return a
              })
              if (uuids.length) {
                  handlers.push({
                      elem: elem,
                      uuids: uuids
                  })
              }
          }
          elem = elem.parentNode
          var g = avalon$2.gestureEvents || {}
          if (elem && elem.getAttribute && (canBubbleUp[type] || g[type])) {
              collectHandlers(elem, type, handlers)
          }
      }
  
      var rhandleHasVm = /^e/
  
      function dispatch(event) {
          event = new avEvent(event)
          var type = event.type
          var elem = event.target
          var handlers = []
          collectHandlers(elem, type, handlers)
          var i = 0,
              j,
              uuid,
              handler
          while ((handler = handlers[i++]) && !event.cancelBubble) {
              var host = event.currentTarget = handler.elem
              j = 0
              while (uuid = handler.uuids[j++]) {
                  if (event.stopImmediate) {
                      break
                  }
                  var fn = avalon$2.eventListeners[uuid]
                  if (fn) {
                      var vm = rhandleHasVm.test(uuid) ? handler.elem._ms_context_ : 0
                      if (vm && vm.$hashcode === false) {
                          return avalon$2.unbind(elem, type, fn)
                      }
                      var ret = fn.call(vm || elem, event)
  
                      if (ret === false) {
                          event.preventDefault()
                          event.stopPropagation()
                      }
                  }
              }
          }
      }
  
      var focusBlur = {
          focus: true,
          blur: true
      }
  
      function delegateEvent(type) {
          var value = root.getAttribute('delegate-events') || ''
          if (value.indexOf(type) === -1) {
              //IE6-8会多次绑定同种类型的同一个函数,其他游览器不会
              var arr = value.match(avalon$2.rword) || []
              arr.push(type)
              root.setAttribute('delegate-events', arr.join(','))
              avalon$2._nativeBind(root, type, dispatch, !!focusBlur[type])
          }
      }
  
      var eventProto = {
          webkitMovementY: 1,
          webkitMovementX: 1,
          keyLocation: 1,
          fixEvent: function fixEvent() {},
          preventDefault: function preventDefault() {
              var e = this.originalEvent || {}
              e.returnValue = this.returnValue = false
              if (modern && e.preventDefault) {
                  e.preventDefault()
              }
          },
          stopPropagation: function stopPropagation() {
              var e = this.originalEvent || {}
              e.cancelBubble = this.cancelBubble = true
              if (modern && e.stopPropagation) {
                  e.stopPropagation()
              }
          },
          stopImmediatePropagation: function stopImmediatePropagation() {
              this.stopPropagation()
              this.stopImmediate = true
          },
          toString: function toString() {
              return '[object Event]' //#1619
          }
      }
  
      function avEvent(event) {
          if (event.originalEvent) {
              return event
          }
          for (var i in event) {
              if (!eventProto[i]) {
                  this[i] = event[i]
              }
          }
          if (!this.target) {
              this.target = event.srcElement
          }
          var target = this.target
          this.fixEvent()
          this.timeStamp = new Date() - 0
          this.originalEvent = event
      }
      avEvent.prototype = eventProto
      //针对firefox, chrome修正mouseenter, mouseleave
      /* istanbul ignore if */
      if (!('onmouseenter' in root)) {
          avalon$2.each({
              mouseenter: 'mouseover',
              mouseleave: 'mouseout'
          }, function (origType, fixType) {
              eventHooks[origType] = {
                  type: fixType,
                  fix: function fix(elem, fn) {
                      return function (e) {
                          var t = e.relatedTarget
                          if (!t || t !== elem && !(elem.compareDocumentPosition(t) & 16)) {
                              delete e.type
                              e.type = origType
                              return fn.apply(this, arguments)
                          }
                      }
                  }
              }
          })
      }
      //针对IE9+, w3c修正animationend
      avalon$2.each({
          AnimationEvent: 'animationend',
          WebKitAnimationEvent: 'webkitAnimationEnd'
      }, function (construct, fixType) {
          if (window$1[construct] && !eventHooks.animationend) {
              eventHooks.animationend = {
                  type: fixType
              }
          }
      })
  
      /* istanbul ignore if */
      if (!("onmousewheel" in document$1)) {
          /* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
           firefox DOMMouseScroll detail 下3 上-3
           firefox wheel detlaY 下3 上-3
           IE9-11 wheel deltaY 下40 上-40
           chrome wheel deltaY 下100 上-100 */
          var fixWheelType = document$1.onwheel !== void 0 ? 'wheel' : 'DOMMouseScroll'
          var fixWheelDelta = fixWheelType === 'wheel' ? 'deltaY' : 'detail'
          eventHooks.mousewheel = {
              type: fixWheelType,
              fix: function fix(elem, fn) {
                  return function (e) {
                      var delta = e[fixWheelDelta] > 0 ? -120 : 120
                      e.wheelDelta = ~~elem._ms_wheel_ + delta
                      elem._ms_wheel_ = e.wheelDeltaY = e.wheelDelta
                      e.wheelDeltaX = 0
                      if (Object.defineProperty) {
                          Object.defineProperty(e, 'type', {
                              value: 'mousewheel'
                          })
                      }
                      return fn.apply(this, arguments)
                  }
              }
          }
      }
  
      /* istanbul ignore next */
      avalon$2._nativeBind = function (el, type, fn, capture) {
          el.addEventListener(type, fn, !!capture)
      }
  
      /* istanbul ignore next */
      avalon$2._nativeUnBind = function (el, type, fn, a) {
          el.removeEventListener(type, fn, !!a)
      }
  
      /* istanbul ignore next */
      avalon$2.fireDom = function (elem, type, opts) {
          /* istanbul ignore else */
          if (document$1.createEvent) {
              var hackEvent = document$1.createEvent('Events')
              hackEvent.initEvent(type, true, true, opts)
              avalon$2.shadowCopy(hackEvent, opts)
              elem.dispatchEvent(hackEvent)
          }
      }
  
      var readyList = []
  
      function fireReady(fn) {
          avalon$2.isReady = true
          while (fn = readyList.shift()) {
              fn(avalon$2)
          }
      }
  
      avalon$2.ready = function (fn) {
          readyList.push(fn)
          if (avalon$2.isReady) {
              fireReady()
          }
      }
  
      avalon$2.ready(function () {
          avalon$2.scan && avalon$2.scan(document$1.body)
      })
  
      /* istanbul ignore next */
      function bootstrap() {
          if (document$1.readyState === 'complete') {
              setTimeout(fireReady) //如果在domReady之外加载
          } else {
              //必须传入三个参数，否则在firefox4-26中报错
              //caught exception: [Exception... "Not enough arguments"  nsresult: "0x80570001 (NS_ERROR_XPC_NOT_ENOUGH_ARGS)" 
              document$1.addEventListener('DOMContentLoaded', fireReady, false)
          }
  
          avalon$2.bind(window$1, 'load', fireReady)
      }
  
      if (inBrowser) {
          bootstrap()
      }
  
      /*******************************************************************
       *                          DOM Api                                 *
       *           shim,class,data,css,val,html,event,ready               *
       ********************************************************************/
  
      function fromDOM(dom) {
          return [from$1(dom)]
      }
  
      function from$1(node) {
          var type = node.nodeName.toLowerCase()
          switch (type) {
              case '#text':
              case '#comment':
                  return {
                      nodeName: type,
                      dom: node,
                      nodeValue: node.nodeValue
                  }
              default:
                  var props = markProps(node, node.attributes || [])
                  var vnode = {
                      nodeName: type,
                      dom: node,
                      isVoidTag: !!voidTag[type],
                      props: props
                  }
                  if (type === 'option') {
                      //即便你设置了option.selected = true,
                      //option.attributes也找不到selected属性
                      props.selected = node.selected
                  }
                  if (orphanTag[type] || type === 'option') {
                      makeOrphan(vnode, type, node.text || node.innerHTML)
                      if (node.childNodes.length === 1) {
                          vnode.children[0].dom = node.firstChild
                      }
                  } else if (!vnode.isVoidTag) {
                      vnode.children = []
                      for (var i = 0, el; el = node.childNodes[i++];) {
                          var child = from$1(el)
                          if (/\S/.test(child.nodeValue)) {
                              vnode.children.push(child)
                          }
                      }
                  }
                  return vnode
          }
      }
  
      var rformElement = /input|textarea|select/i
  
      function markProps(node, attrs) {
          var ret = {}
          for (var i = 0, n = attrs.length; i < n; i++) {
              var attr = attrs[i]
              if (attr.specified) {
                  //IE6-9不会将属性名变小写,比如它会将用户的contenteditable变成contentEditable
                  ret[attr.name.toLowerCase()] = attr.value
              }
          }
          if (rformElement.test(node.nodeName)) {
              ret.type = node.type
              var a = node.getAttributeNode('value')
              if (a && /\S/.test(a.value)) {
                  //IE6,7中无法取得checkbox,radio的value
                  ret.value = a.value
              }
          }
          var style = node.style.cssText
          if (style) {
              ret.style = style
          }
          //类名 = 去重(静态类名+动态类名+ hover类名? + active类名)
          if (ret.type === 'select-one') {
              ret.selectedIndex = node.selectedIndex
          }
          return ret
      }
  
      function VText(text) {
          this.nodeName = '#text'
          this.nodeValue = text
      }
  
      VText.prototype = {
          constructor: VText,
          toDOM: function toDOM() {
              /* istanbul ignore if*/
              if (this.dom) return this.dom
              var v = avalon$2._decode(this.nodeValue)
              return this.dom = document$1.createTextNode(v)
          },
          toHTML: function toHTML() {
              return this.nodeValue
          }
      }
  
      function VComment(text) {
          this.nodeName = '#comment'
          this.nodeValue = text
      }
      VComment.prototype = {
          constructor: VComment,
          toDOM: function toDOM() {
              if (this.dom) return this.dom
              return this.dom = document$1.createComment(this.nodeValue)
          },
          toHTML: function toHTML() {
              return '<!--' + this.nodeValue + '-->'
          }
      }
  
      function VElement(type, props, children, isVoidTag) {
          this.nodeName = type
          this.props = props
          this.children = children
          this.isVoidTag = isVoidTag
      }
      VElement.prototype = {
          constructor: VElement,
          toDOM: function toDOM() {
              if (this.dom) return this.dom
              var dom,
                  tagName = this.nodeName
              if (avalon$2.modern && svgTags[tagName]) {
                  dom = createSVG(tagName)
              } else {
                  dom = document$1.createElement(tagName)
              }
              var props = this.props || {}
  
              for (var i in props) {
                  var val = props[i]
                  if (skipFalseAndFunction(val)) {
                      dom.setAttribute(i, val + '')
                  }
              }
              var c = this.children || []
              var template = c[0] ? c[0].nodeValue : ''
              switch (this.nodeName) {
                  case 'xmp':
                  case 'style':
                  case 'script':
                  case 'noscript':
                      dom.innerHTML = template
                      break
                  case 'template':
                      if (supportTemplate) {
                          dom.innerHTML = template
                      } else {
                          /* istanbul ignore next*/
                          dom.textContent = template
                      }
                      break
                  default:
                      if (!this.isVoidTag && this.children) {
                          this.children.forEach(function (el) {
                              return el && dom.appendChild(avalon$2.vdom(el, 'toDOM'))
                          })
                      }
                      break
              }
              return this.dom = dom
          },
          toHTML: function toHTML() {
              var arr = []
              var props = this.props || {}
              for (var i in props) {
                  var val = props[i]
                  if (skipFalseAndFunction(val)) {
                      arr.push(i + '=' + avalon$2.quote(props[i] + ''))
                  }
              }
              arr = arr.length ? ' ' + arr.join(' ') : ''
              var str = '<' + this.nodeName + arr
              if (this.isVoidTag) {
                  return str + '/>'
              }
              str += '>'
              if (this.children) {
                  str += this.children.map(function (el) {
                      return el ? avalon$2.vdom(el, 'toHTML') : ''
                  }).join('')
              }
              return str + '</' + this.nodeName + '>'
          }
      }
  
      function skipFalseAndFunction(a) {
          return a !== false && Object(a) !== a
      }
  
      function createSVG(type) {
          return document$1.createElementNS('http://www.w3.org/2000/svg', type)
      }
  
      var svgTags = avalon$2.oneObject('circle,defs,ellipse,image,line,' + 'path,polygon,polyline,rect,symbol,text,use,g,svg')
  
      if (avalon$2.inBrowser) {
          var supportTemplate = 'content' in document$1.createElement('template')
      }
  
      function VFragment(children, key, val, index) {
          this.nodeName = '#document-fragment'
          this.children = children
          this.key = key
          this.val = val
          this.index = index
          this.props = {}
      }
      VFragment.prototype = {
          constructor: VFragment,
          toDOM: function toDOM() {
              if (this.dom) return this.dom
              var f = this.toFragment()
              //IE6-11 docment-fragment都没有children属性 
              this.split = f.lastChild
              return this.dom = f
          },
          dispose: function dispose() {
              this.toFragment()
              this.innerRender && this.innerRender.dispose()
              for (var i in this) {
                  this[i] = null
              }
          },
          toFragment: function toFragment() {
              var f = createFragment()
              this.children.forEach(function (el) {
                  return f.appendChild(avalon$2.vdom(el, 'toDOM'))
              })
              return f
          },
          toHTML: function toHTML() {
              var c = this.children
              return c.map(function (el) {
                  return avalon$2.vdom(el, 'toHTML')
              }).join('')
          }
      }
  
      /**
       * 虚拟DOM的4大构造器
       */
      avalon$2.mix(avalon$2, {
          VText: VText,
          VComment: VComment,
          VElement: VElement,
          VFragment: VFragment
      })
  
      var constNameMap = {
          '#text': 'VText',
          '#document-fragment': 'VFragment',
          '#comment': 'VComment'
      }
  
      var vdom = avalon$2.vdomAdaptor = avalon$2.vdom = function (obj, method) {
          if (!obj) {
              //obj在ms-for循环里面可能是null
              return method === "toHTML" ? '' : createFragment()
          }
          var nodeName = obj.nodeName
          if (!nodeName) {
              return new avalon$2.VFragment(obj)[method]()
          }
          var constName = constNameMap[nodeName] || 'VElement'
          return avalon$2[constName].prototype[method].call(obj)
      }
  
      avalon$2.domize = function (a) {
          return avalon$2.vdom(a, 'toDOM')
      }
  
      /**
      $$skipArray:是系统级通用的不可监听属性
      $skipArray: 是当前对象特有的不可监听属性
      
       不同点是
       $$skipArray被hasOwnProperty后返回false
       $skipArray被hasOwnProperty后返回true
       */
      var falsy
      var $$skipArray = {
          $id: falsy,
          $render: falsy,
          $track: falsy,
          $element: falsy,
          $watch: falsy,
          $fire: falsy,
          $events: falsy,
          $accessors: falsy,
          $hashcode: falsy,
          $mutations: falsy,
          $vbthis: falsy,
          $vbsetter: falsy
      }
  
      avalon$2.pendingActions = []
      avalon$2.uniqActions = {}
      avalon$2.inTransaction = 0
      config.trackDeps = false
      avalon$2.track = function () {
          if (config.trackDeps) {
              avalon$2.log.apply(avalon$2, arguments)
          }
      }
  
      /**
       * Batch is a pseudotransaction, just for purposes of memoizing ComputedValues when nothing else does.
       * During a batch `onBecomeUnobserved` will be called at most once per observable.
       * Avoids unnecessary recalculations.
       */
  
      function runActions() {
          if (avalon$2.isRunningActions === true || avalon$2.inTransaction > 0) return
          avalon$2.isRunningActions = true
          var tasks = avalon$2.pendingActions.splice(0, avalon$2.pendingActions.length)
          for (var i = 0, task; task = tasks[i++];) {
              task.update()
              delete avalon$2.uniqActions[task.uuid]
          }
          avalon$2.isRunningActions = false
      }
  
      function propagateChanged(target) {
          var list = target.observers
          for (var i = 0, el; el = list[i++];) {
              el.schedule() //通知action, computed做它们该做的事
          }
      }
  
      //将自己抛到市场上卖
      function reportObserved(target) {
          var action = avalon$2.trackingAction || null
          if (action !== null) {
  
              avalon$2.track('征收到', target.expr)
              action.mapIDs[target.uuid] = target
          }
      }
  
      var targetStack = []
  
      function collectDeps(action, getter) {
          if (!action.observers) return
          var preAction = avalon$2.trackingAction
          if (preAction) {
              targetStack.push(preAction)
          }
          avalon$2.trackingAction = action
          avalon$2.track('【action】', action.type, action.expr, '开始征收依赖项')
          //多个observe持有同一个action
          action.mapIDs = {} //重新收集依赖
          var hasError = true,
              result
          try {
              result = getter.call(action)
              hasError = false
          } finally {
              if (hasError) {
                  avalon$2.warn('collectDeps fail', getter + '')
                  action.mapIDs = {}
                  avalon$2.trackingAction = preAction
              } else {
                  // 确保它总是为null
                  avalon$2.trackingAction = targetStack.pop()
                  try {
                      resetDeps(action)
                  } catch (e) {
                      avalon$2.warn(e)
                  }
              }
              return result
          }
      }
  
      function resetDeps(action) {
          var prev = action.observers,
              curr = [],
              checked = {},
              ids = []
          for (var i in action.mapIDs) {
              var dep = action.mapIDs[i]
              if (!dep.isAction) {
                  if (!dep.observers) {
                      //如果它已经被销毁
                      delete action.mapIDs[i]
                      continue
                  }
                  ids.push(dep.uuid)
                  curr.push(dep)
                  checked[dep.uuid] = 1
                  if (dep.lastAccessedBy === action.uuid) {
                      continue
                  }
                  dep.lastAccessedBy = action.uuid
                  avalon$2.Array.ensure(dep.observers, action)
              }
          }
          var ids = ids.sort().join(',')
          if (ids === action.ids) {
              return
          }
          action.ids = ids
          if (!action.isComputed) {
              action.observers = curr
          } else {
              action.depsCount = curr.length
              action.deps = avalon$2.mix({}, action.mapIDs)
              action.depsVersion = {}
              for (var _i in action.mapIDs) {
                  var _dep = action.mapIDs[_i]
                  action.depsVersion[_dep.uuid] = _dep.version
              }
          }
  
          for (var _i2 = 0, _dep2; _dep2 = prev[_i2++];) {
              if (!checked[_dep2.uuid]) {
                  avalon$2.Array.remove(_dep2.observers, action)
              }
          }
      }
  
      function transaction(action, thisArg, args) {
          args = args || []
          var name = 'transaction ' + (action.name || action.displayName || 'noop')
          transactionStart(name)
          var res = action.apply(thisArg, args)
          transactionEnd(name)
          return res
      }
      avalon$2.transaction = transaction
  
      function transactionStart(name) {
          avalon$2.inTransaction += 1
      }
  
      function transactionEnd(name) {
          if (--avalon$2.inTransaction === 0) {
              avalon$2.isRunningActions = false
              runActions()
          }
      }
  
      var keyMap = avalon$2.oneObject("break,case,catch,continue,debugger,default,delete,do,else,false," + "finally,for,function,if,in,instanceof,new,null,return,switch,this," + "throw,true,try,typeof,var,void,while,with," + /* 关键字*/
      "abstract,boolean,byte,char,class,const,double,enum,export,extends," + "final,float,goto,implements,import,int,interface,long,native," + "package,private,protected,public,short,static,super,synchronized," + "throws,transient,volatile")
  
      var skipMap = avalon$2.mix({
          Math: 1,
          Date: 1,
          $event: 1,
          window: 1,
          __vmodel__: 1,
          avalon: 1
      }, keyMap)
  
      var rvmKey = /(^|[^\w\u00c0-\uFFFF_])(@|##)(?=[$\w])/g
      var ruselessSp = /\s*(\.|\|)\s*/g
      var rshortCircuit = /\|\|/g
      var brackets = /\(([^)]*)\)/
      var rpipeline = /\|(?=\?\?)/
      var rregexp = /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/g
      var robjectProp = /\.[\w\.\$]+/g //对象的属性 el.xxx 中的xxx
      var robjectKey = /(\{|\,)\s*([\$\w]+)\s*:/g //对象的键名与冒号 {xxx:1,yyy: 2}中的xxx, yyy
      var rfilterName = /\|(\w+)/g
      var rlocalVar = /[$a-zA-Z_][$a-zA-Z0-9_]*/g
  
      var exprCache = new Cache(300)
  
      function addScopeForLocal(str) {
          return str.replace(robjectProp, dig).replace(rlocalVar, function (el) {
              if (!skipMap[el]) {
                  return "__vmodel__." + el
              }
              return el
          })
      }
  
      function addScope(expr, type) {
          var cacheKey = expr + ':' + type
          var cache = exprCache.get(cacheKey)
          if (cache) {
              return cache.slice(0)
          }
  
          stringPool.map = {}
          //https://github.com/RubyLouvre/avalon/issues/1849
          var input = expr.replace(rregexp, function (a, b) {
              return b + dig(a.slice(b.length))
          }) //移除所有正则
          input = clearString(input) //移除所有字符串
          input = input.replace(rshortCircuit, dig). //移除所有短路运算符
          replace(ruselessSp, '$1'). //移除.|两端空白
  
          replace(robjectKey, function (_, a, b) {
              //移除所有键名
              return a + dig(b) + ':' //比如 ms-widget="[{is:'ms-address-wrap', $id:'address'}]"这样极端的情况 
          }).replace(rvmKey, '$1__vmodel__.'). //转换@与##为__vmodel__
          replace(rfilterName, function (a, b) {
              //移除所有过滤器的名字
              return '|' + dig(b)
          })
          input = addScopeForLocal(input) //在本地变量前添加__vmodel__
  
          var filters = input.split(rpipeline) //根据管道符切割表达式
          var body = filters.shift().replace(rfill, fill).trim()
          if (/\?\?\d/.test(body)) {
              body = body.replace(rfill, fill)
          }
          if (filters.length) {
              filters = filters.map(function (filter) {
                  var bracketArgs = ''
                  filter = filter.replace(brackets, function (a, b) {
                      if (/\S/.test(b)) {
                          bracketArgs += ',' + b //还原字符串,正则,短路运算符
                      }
                      return ''
                  })
                  var arg = '[' + avalon$2.quote(filter.trim()) + bracketArgs + ']'
                  return arg
              })
              filters = 'avalon.composeFilters(' + filters + ')(__value__)'
              filters = filters.replace(rfill, fill)
          } else {
              filters = ''
          }
          return exprCache.put(cacheKey, [body, filters])
      }
      var rhandleName = /^__vmodel__\.[$\w\.]+$/
      var rfixIE678 = /__vmodel__\.([^(]+)\(([^)]*)\)/
      function makeHandle(body) {
          if (rhandleName.test(body)) {
              body = body + '($event)'
          }
          /* istanbul ignore if */
          if (msie < 9) {
              body = body.replace(rfixIE678, function (a, b, c) {
                  return '__vmodel__.' + b + '.call(__vmodel__' + (/\S/.test(c) ? ',' + c : '') + ')'
              })
          }
          return body
      }
      function createGetter(expr, type) {
          var arr = addScope(expr, type),
              body
          if (!arr[1]) {
              body = arr[0]
          } else {
              body = arr[1].replace(/__value__\)$/, arr[0] + ')')
          }
          try {
              return new Function('__vmodel__', 'return ' + body + ';')
              /* istanbul ignore next */
          } catch (e) {
              avalon$2.log('parse getter: [', expr, body, ']error')
              return avalon$2.noop
          }
      }
  
      /**
       * 生成表达式设值函数
       * @param  {String}  expr
       */
      function createSetter(expr, type) {
          var arr = addScope(expr, type)
          var body = 'try{ ' + arr[0] + ' = __value__}catch(e){}'
          try {
              return new Function('__vmodel__', '__value__', body + ';')
              /* istanbul ignore next */
          } catch (e) {
              avalon$2.log('parse setter: ', expr, ' error')
              return avalon$2.noop
          }
      }
  
      var actionUUID = 1
      //需要重构
      function Action(vm, options, callback) {
          for (var i in options) {
              if (protectedMenbers[i] !== 1) {
                  this[i] = options[i]
              }
          }
  
          this.vm = vm
          this.observers = []
          this.callback = callback
          this.uuid = ++actionUUID
          this.ids = ''
          this.mapIDs = {} //这个用于去重
          this.isAction = true
          var expr = this.expr
          // 缓存取值函数
          if (typeof this.getter !== 'function') {
              this.getter = createGetter(expr, this.type)
          }
          // 缓存设值函数（双向数据绑定）
          if (this.type === 'duplex') {
              this.setter = createSetter(expr, this.type)
          }
          // 缓存表达式旧值
          this.oldValue = null
          // 表达式初始值 & 提取依赖
          if (!this.node) {
              this.value = this.get()
          }
      }
  
      Action.prototype = {
          getValue: function getValue() {
              var scope = this.vm
              try {
                  return this.getter.call(scope, scope)
              } catch (e) {
                  avalon$2.log(this.getter + ' exec error')
              }
          },
          setValue: function setValue(value) {
              var scope = this.vm
              if (this.setter) {
                  this.setter.call(scope, scope, value)
              }
          },
  
  
          // get --> getValue --> getter
          get: function get(fn) {
              var name = 'action track ' + this.type
  
              if (this.deep) {
                  avalon$2.deepCollect = true
              }
  
              var value = collectDeps(this, this.getValue)
              if (this.deep && avalon$2.deepCollect) {
                  avalon$2.deepCollect = false
              }
  
              return value
          },
  
  
          /**
           * 在更新视图前保存原有的value
           */
          beforeUpdate: function beforeUpdate() {
              var v = this.value
              return this.oldValue = v && v.$events ? v.$model : v
          },
          update: function update(args, uuid) {
              var oldVal = this.beforeUpdate()
              var newVal = this.value = this.get()
              var callback = this.callback
              if (callback && this.diff(newVal, oldVal, args)) {
                  callback.call(this.vm, this.value, oldVal, this.expr)
              }
              this._isScheduled = false
          },
          schedule: function schedule() {
              if (!this._isScheduled) {
                  this._isScheduled = true
                  if (!avalon$2.uniqActions[this.uuid]) {
                      avalon$2.uniqActions[this.uuid] = 1
                      avalon$2.pendingActions.push(this)
                  }
  
                  runActions() //这里会还原_isScheduled
  
              }
          },
          removeDepends: function removeDepends() {
              var self = this
              this.observers.forEach(function (depend) {
                  avalon$2.Array.remove(depend.observers, self)
              })
          },
  
  
          /**
           * 比较两个计算值是否,一致,在for, class等能复杂数据类型的指令中,它们会重写diff复法
           */
          diff: function diff(a, b) {
              return a !== b
          },
  
  
          /**
           * 销毁指令
           */
          dispose: function dispose() {
              this.value = null
              this.removeDepends()
              if (this.beforeDispose) {
                  this.beforeDispose()
              }
              for (var i in this) {
                  delete this[i]
              }
          }
      }
  
      var protectedMenbers = {
          vm: 1,
          callback: 1,
  
          observers: 1,
          oldValue: 1,
          value: 1,
          getValue: 1,
          setValue: 1,
          get: 1,
  
          removeDepends: 1,
          beforeUpdate: 1,
          update: 1,
          //diff
          //getter
          //setter
          //expr
          //vdom
          //type: "for"
          //name: "ms-for"
          //attrName: ":for"
          //param: "click"
          //beforeDispose
          dispose: 1
      }
  
      /**
      * 
       与Computed等共享UUID
      */
      var obid = 1
      function Mutation(expr, value, vm) {
          //构造函数
          this.expr = expr
          if (value) {
              var childVm = platform.createProxy(value, this)
              if (childVm) {
                  value = childVm
              }
          }
          this.value = value
          this.vm = vm
          try {
              vm.$mutations[expr] = this
          } catch (ignoreIE) {}
          this.uuid = ++obid
          this.updateVersion()
          this.mapIDs = {}
          this.observers = []
      }
  
      Mutation.prototype = {
          get: function get() {
              if (avalon$2.trackingAction) {
                  this.collect() //被收集
                  var childOb = this.value
                  if (childOb && childOb.$events) {
                      if (Array.isArray(childOb)) {
                          childOb.forEach(function (item) {
                              if (item && item.$events) {
                                  item.$events.__dep__.collect()
                              }
                          })
                      } else if (avalon$2.deepCollect) {
                          for (var key in childOb) {
                              if (childOb.hasOwnProperty(key)) {
                                  var collectIt = childOb[key]
                              }
                          }
                      }
                  }
              }
              return this.value
          },
          collect: function collect() {
              avalon$2.track(name, '被收集')
              reportObserved(this)
          },
          updateVersion: function updateVersion() {
              this.version = Math.random() + Math.random()
          },
          notify: function notify() {
              transactionStart()
              propagateChanged(this)
              transactionEnd()
          },
          set: function set(newValue) {
              var oldValue = this.value
              if (newValue !== oldValue) {
                  if (avalon$2.isObject(newValue)) {
                      var hash = oldValue && oldValue.$hashcode
                      var childVM = platform.createProxy(newValue, this)
                      if (childVM) {
                          if (hash) {
                              childVM.$hashcode = hash
                          }
                          newValue = childVM
                      }
                  }
                  this.value = newValue
                  this.updateVersion()
                  this.notify()
              }
          }
      }
  
      function getBody(fn) {
          var entire = fn.toString()
          return entire.substring(entire.indexOf('{}') + 1, entire.lastIndexOf('}'))
      }
      //如果不存在三目,if,方法
      var instability = /(\?|if\b|\(.+\))/
  
      function __create(o) {
          var __ = function __() {}
          __.prototype = o
          return new __()
      }
  
      function __extends(child, parent) {
          if (typeof parent === 'function') {
              var proto = child.prototype = __create(parent.prototype)
              proto.constructor = child
          }
      }
      var Computed = function (_super) {
          __extends(Computed, _super)
  
          function Computed(name, options, vm) {
              //构造函数
              _super.call(this, name, undefined, vm)
              delete options.get
              delete options.set
  
              avalon$2.mix(this, options)
              this.deps = {}
              this.type = 'computed'
              this.depsVersion = {}
              this.isComputed = true
              this.trackAndCompute()
              if (!('isStable' in this)) {
                  this.isStable = !instability.test(getBody(this.getter))
              }
          }
          var cp = Computed.prototype
          cp.trackAndCompute = function () {
              if (this.isStable && this.depsCount > 0) {
                  this.getValue()
              } else {
                  collectDeps(this, this.getValue.bind(this))
              }
          }
  
          cp.getValue = function () {
              return this.value = this.getter.call(this.vm)
          }
  
          cp.schedule = function () {
              var observers = this.observers
              var i = observers.length
              while (i--) {
                  var d = observers[i]
                  if (d.schedule) {
                      d.schedule()
                  }
              }
          }
  
          cp.shouldCompute = function () {
              if (this.isStable) {
                  //如果变动因子确定,那么只比较变动因子的版本
                  var toComputed = false
                  for (var i in this.deps) {
                      if (this.deps[i].version !== this.depsVersion[i]) {
                          toComputed = true
                          this.deps[i].version = this.depsVersion[i]
                      }
                  }
                  return toComputed
              }
              return true
          }
          cp.set = function () {
              if (this.setter) {
                  avalon$2.transaction(this.setter, this.vm, arguments)
              }
          }
          cp.get = function () {
  
              //当被设置了就不稳定,当它被访问了一次就是稳定
              this.collect()
  
              if (this.shouldCompute()) {
                  this.trackAndCompute()
                  // console.log('computed 2 分支')
                  this.updateVersion()
                  //  this.reportChanged()
              }
  
              //下面这一行好像没用
              return this.value
          }
          return Computed
      }(Mutation)
  
      /**
       * 这里放置ViewModel模块的共用方法
       * avalon.define: 全框架最重要的方法,生成用户VM
       * IProxy, 基本用户数据产生的一个数据对象,基于$model与vmodel之间的形态
       * modelFactory: 生成用户VM
       * canHijack: 判定此属性是否该被劫持,加入数据监听与分发的的逻辑
       * createProxy: listFactory与modelFactory的封装
       * createAccessor: 实现数据监听与分发的重要对象
       * itemFactory: ms-for循环中产生的代理VM的生成工厂
       * fuseFactory: 两个ms-controller间产生的代理VM的生成工厂
       */
  
      avalon$2.define = function (definition) {
          var $id = definition.$id
          if (!$id) {
              avalon$2.error('vm.$id must be specified')
          }
          if (avalon$2.vmodels[$id]) {
              avalon$2.warn('error:[' + $id + '] had defined!')
          }
          var vm = platform.modelFactory(definition)
          return avalon$2.vmodels[$id] = vm
      }
  
      /**
       * 在末来的版本,avalon改用Proxy来创建VM,因此
       */
  
      function IProxy(definition, dd) {
          avalon$2.mix(this, definition)
          avalon$2.mix(this, $$skipArray)
          this.$hashcode = avalon$2.makeHashCode('$')
          this.$id = this.$id || this.$hashcode
          this.$events = {
              __dep__: dd || new Mutation(this.$id)
          }
          if (avalon$2.config.inProxyMode) {
              delete this.$mutations
              this.$accessors = {}
              this.$computed = {}
              this.$track = ''
          } else {
              this.$accessors = {
                  $model: modelAccessor
              }
          }
          if (dd === void 0) {
              this.$watch = platform.watchFactory(this.$events)
              this.$fire = platform.fireFactory(this.$events)
          } else {
              delete this.$watch
              delete this.$fire
          }
      }
  
      platform.modelFactory = function modelFactory(definition, dd) {
          var $computed = definition.$computed || {}
          delete definition.$computed
          var core = new IProxy(definition, dd)
          var $accessors = core.$accessors
          var keys = []
  
          platform.hideProperty(core, '$mutations', {})
  
          for (var key in definition) {
              if (key in $$skipArray) continue
              var val = definition[key]
              keys.push(key)
              if (canHijack(key, val)) {
                  $accessors[key] = createAccessor(key, val)
              }
          }
          for (var _key in $computed) {
              if (_key in $$skipArray) continue
              var val = $computed[_key]
              if (typeof val === 'function') {
                  val = {
                      get: val
                  }
              }
              if (val && val.get) {
                  val.getter = val.get
                  val.setter = val.set
                  avalon$2.Array.ensure(keys, _key)
                  $accessors[_key] = createAccessor(_key, val, true)
              }
          }
          //将系统API以unenumerable形式加入vm,
          //添加用户的其他不可监听属性或方法
          //重写$track
          //并在IE6-8中增添加不存在的hasOwnPropert方法
          var vm = platform.createViewModel(core, $accessors, core)
          platform.afterCreate(vm, core, keys, !dd)
          return vm
      }
      var $proxyItemBackdoorMap = {}
  
      function canHijack(key, val, $proxyItemBackdoor) {
          if (key in $$skipArray) return false
          if (key.charAt(0) === '$') {
              if ($proxyItemBackdoor) {
                  if (!$proxyItemBackdoorMap[key]) {
                      $proxyItemBackdoorMap[key] = 1
                      avalon$2.warn('ms-for\u4E2D\u7684\u53D8\u91CF' + key + '\u4E0D\u518D\u5EFA\u8BAE\u4EE5$\u4E3A\u524D\u7F00')
                  }
                  return true
              }
              return false
          }
          if (val == null) {
              avalon$2.warn('定义vmodel时' + key + '的属性值不能为null undefine')
              return true
          }
          if (/error|date|function|regexp/.test(avalon$2.type(val))) {
              return false
          }
          return !(val && val.nodeName && val.nodeType)
      }
  
      function createProxy(target, dd) {
          if (target && target.$events) {
              return target
          }
          var vm
          if (Array.isArray(target)) {
              vm = platform.listFactory(target, false, dd)
          } else if (isObject(target)) {
              vm = platform.modelFactory(target, dd)
          }
          return vm
      }
  
      platform.createProxy = createProxy
  
      platform.itemFactory = function itemFactory(before, after) {
          var keyMap = before.$model
          var core = new IProxy(keyMap)
          var state = avalon$2.shadowCopy(core.$accessors, before.$accessors) //防止互相污染
          var data = after.data
          //core是包含系统属性的对象
          //keyMap是不包含系统属性的对象, keys
          for (var key in data) {
              var val = keyMap[key] = core[key] = data[key]
              state[key] = createAccessor(key, val)
          }
          var keys = Object.keys(keyMap)
          var vm = platform.createViewModel(core, state, core)
          platform.afterCreate(vm, core, keys)
          return vm
      }
  
      function createAccessor(key, val, isComputed) {
          var mutation = null
          var Accessor = isComputed ? Computed : Mutation
          return {
              get: function Getter() {
                  if (!mutation) {
                      mutation = new Accessor(key, val, this)
                  }
                  return mutation.get()
              },
              set: function Setter(newValue) {
                  if (!mutation) {
                      mutation = new Accessor(key, val, this)
                  }
                  mutation.set(newValue)
              },
              enumerable: true,
              configurable: true
          }
      }
  
      platform.fuseFactory = function fuseFactory(before, after) {
          var keyMap = avalon$2.mix(before.$model, after.$model)
          var core = new IProxy(avalon$2.mix(keyMap, {
              $id: before.$id + after.$id
          }))
          var state = avalon$2.mix(core.$accessors, before.$accessors, after.$accessors) //防止互相污染
  
          var keys = Object.keys(keyMap)
          //将系统API以unenumerable形式加入vm,并在IE6-8中添加hasOwnPropert方法
          var vm = platform.createViewModel(core, state, core)
          platform.afterCreate(vm, core, keys, false)
          return vm
      }
  
      function toJson(val) {
          var xtype = avalon$2.type(val)
          if (xtype === 'array') {
              var array = []
              for (var i = 0; i < val.length; i++) {
                  array[i] = toJson(val[i])
              }
              return array
          } else if (xtype === 'object') {
              if (typeof val.$track === 'string') {
                  var obj = {}
                  var arr = val.$track.match(/[^☥]+/g) || []
                  arr.forEach(function (i) {
                      var value = val[i]
                      obj[i] = value && value.$events ? toJson(value) : value
                  })
                  return obj
              }
          }
          return val
      }
  
      var modelAccessor = {
          get: function get() {
              return toJson(this)
          },
          set: avalon$2.noop,
          enumerable: false,
          configurable: true
      }
  
      platform.toJson = toJson
      platform.modelAccessor = modelAccessor
  
      var _splice = ap.splice
      var __array__ = {
          set: function set(index, val) {
              if (index >>> 0 === index && this[index] !== val) {
                  if (index > this.length) {
                      throw Error(index + 'set方法的第一个参数不能大于原数组长度')
                  }
                  this.splice(index, 1, val)
              }
          },
          toJSON: function toJSON() {
              //为了解决IE6-8的解决,通过此方法显式地求取数组的$model
              return this.$model = platform.toJson(this)
          },
          contains: function contains(el) {
              //判定是否包含
              return this.indexOf(el) !== -1
          },
          ensure: function ensure(el) {
              if (!this.contains(el)) {
                  //只有不存在才push
                  this.push(el)
                  return true
              }
              return false
          },
          pushArray: function pushArray(arr) {
              return this.push.apply(this, arr)
          },
          remove: function remove(el) {
              //移除第一个等于给定值的元素
              return this.removeAt(this.indexOf(el))
          },
          removeAt: function removeAt(index) {
              //移除指定索引上的元素
              if (index >>> 0 === index) {
                  return this.splice(index, 1)
              }
              return []
          },
          clear: function clear() {
              this.removeAll()
              return this
          },
          removeAll: function removeAll(all) {
              //移除N个元素
              var size = this.length
              var eliminate = Array.isArray(all) ? function (el) {
                  return all.indexOf(el) !== -1
              } : typeof all === 'function' ? all : false
  
              if (eliminate) {
                  for (var i = this.length - 1; i >= 0; i--) {
                      if (eliminate(this[i], i)) {
                          _splice.call(this, i, 1)
                      }
                  }
              } else {
                  _splice.call(this, 0, this.length)
              }
              this.toJSON()
              this.$events.__dep__.notify()
          }
      }
      function hijackMethods(array) {
          for (var i in __array__) {
              platform.hideProperty(array, i, __array__[i])
          }
      }
      var __method__ = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
  
      __method__.forEach(function (method) {
          var original = ap[method]
          __array__[method] = function () {
              // 继续尝试劫持数组元素的属性
              var core = this.$events
  
              var args = platform.listFactory(arguments, true, core.__dep__)
              var result = original.apply(this, args)
  
              this.toJSON()
              core.__dep__.notify(method)
              return result
          }
      })
  
      function listFactory(array, stop, dd) {
          if (!stop) {
              hijackMethods(array)
              if (modern) {
                  Object.defineProperty(array, '$model', platform.modelAccessor)
              }
              platform.hideProperty(array, '$hashcode', avalon$2.makeHashCode('$'))
              platform.hideProperty(array, '$events', { __dep__: dd || new Mutation() })
          }
          var _dd = array.$events && array.$events.__dep__
          for (var i = 0, n = array.length; i < n; i++) {
              var item = array[i]
              if (isObject(item)) {
                  array[i] = platform.createProxy(item, _dd)
              }
          }
          return array
      }
  
      platform.listFactory = listFactory
  
      function hideProperty(host, name, value) {
          Object.defineProperty(host, name, {
              value: value,
              writable: true,
              enumerable: false,
              configurable: true
          })
      }
  
      function $fire(expr, a) {
          var list = this.$events[expr]
          if (Array.isArray(list)) {
              for (var i = 0, w; w = list[i++];) {
                  w.callback.call(w.vm, a, w.value, w.expr)
              }
          }
      }
  
      function $watch(expr, callback, deep) {
          var core = this.$events
          var w = new Action(this, {
              deep: deep,
              type: 'user',
              expr: expr
          }, callback)
          if (!core[expr]) {
              core[expr] = [w]
          } else {
              core[expr].push(w)
          }
          return function () {
              w.dispose()
              avalon$2.Array.remove(core[expr], w)
              if (core[expr].length === 0) {
                  delete core[expr]
              }
          }
      }
      function watchFactory(core) {
          return $watch
      }
  
      function fireFactory(core) {
          return $fire
      }
  
      function afterCreate(vm, core, keys, bindThis) {
          var ac = vm.$accessors
          //隐藏系统属性
          for (var key in $$skipArray) {
              hideProperty(vm, key, vm[key])
          }
          //为不可监听的属性或方法赋值
          for (var i = 0; i < keys.length; i++) {
              var _key2 = keys[i]
              if (!(_key2 in ac)) {
                  if (bindThis && typeof core[_key2] === 'function') {
                      vm[_key2] = core[_key2].bind(vm)
                      continue
                  }
                  vm[_key2] = core[_key2]
              }
          }
          vm.$track = keys.join('☥')
          vm.$events.__proxy__ = vm
      }
  
      platform.fireFactory = fireFactory
      platform.watchFactory = watchFactory
      platform.afterCreate = afterCreate
      platform.hideProperty = hideProperty
      platform.createViewModel = Object.defineProperties
  
      if (typeof Proxy === 'function') {
          var traps
  
          ;(function () {
  
              //https://developer.mozilla.org/en-US/docs/Archive/Web/Old_Proxy_API
              var toProxy = function toProxy(definition) {
                  return Proxy.create ? Proxy.create(definition, traps) : new Proxy(definition, traps)
              }
  
              var wrapIt = function wrapIt(str) {
                  return '☥' + str + '☥'
              }
  
              var updateTrack = function updateTrack(target, name) {
                  var arr = target.$track.match(/[^☥]+/g) || []
                  arr.push(name)
                  target.$track = arr.sort().join('☥')
              }
  
              avalon$2.config.inProxyMode = true
  
              platform.modelFactory = function modelFactory(definition, dd) {
                  var clone = {}
                  for (var i in definition) {
                      clone[i] = definition[i]
                      delete definition[i]
                  }
  
                  definition.$id = clone.$id
                  var proxy = new IProxy(definition, dd)
  
                  var vm = toProxy(proxy)
                  //先添加普通属性与监控属性
                  for (var _i3 in clone) {
                      vm[_i3] = clone[_i3]
                  }
                  var $computed = clone.$computed
                  //再添加计算属性
                  if ($computed) {
                      delete clone.$computed
                      for (var _i4 in $computed) {
                          var val = $computed[_i4]
                          if (typeof val === 'function') {
                              var _val = val
                              val = { get: _val }
                          }
                          if (val && val.get) {
                              val.getter = val.get
                              //在set方法中的target是IProxy，需要重写成Proxy，才能依赖收集
                              val.vm = vm
                              if (val.set) val.setter = val.set
                              $computed[_i4] = val
                              delete clone[_i4] //去掉重名的监控属性
                          } else {
                              delete $computed[_i4]
                          }
                      }
                      for (var _i5 in $computed) {
                          vm[_i5] = $computed[_i5]
                      }
                  }
  
                  return vm
              };traps = {
                  deleteProperty: function deleteProperty(target, name) {
                      if (target.hasOwnProperty(name)) {
                          //移除一个属性,分三昌:
                          //1. 移除监听器
                          //2. 移除真实对象的对应属性
                          //3. 移除$track中的键名
                          delete target.$accessors[name]
                          delete target[name]
                          target.$track = wrapIt(target.$track).replace(wrapIt(name), '').slice(1, -1)
                      }
                      return true
                  },
                  get: function get(target, name) {
                      if (name === '$model') {
                          return platform.toJson(target)
                      }
                      //收集依赖
                      var m = target.$accessors[name]
                      if (m && m.get) {
                          return m.get()
                      }
  
                      return target[name]
                  },
                  set: function set(target, name, value) {
                      if (name === '$model' || name === '$track') {
                          return true
                      }
                      if (name in $$skipArray) {
                          target[name] = value
                          return true
                      }
  
                      var ac = target.$accessors
                      var oldValue = ac[name] ? ac[name].value : target[name]
  
                      if (oldValue !== value) {
                          if (!target.hasOwnProperty(name)) {
                              updateTrack(target, name)
                          }
                          if (canHijack(name, value, target.$proxyItemBackdoor)) {
                              var $computed = target.$computed || {}
                              //如果是新属性
                              if (!ac[name]) {
                                  target[name] = value //必须设置，用于hasOwnProperty
                                  var isComputed = !!$computed[name]
                                  var Observable = isComputed ? Computed : Mutation
                                  ac[name] = new Observable(name, value, target)
                                  return true
                              }
                              var mutation = ac[name]
                              //创建子对象
                              mutation.set(value)
                              target[name] = mutation.value
                          } else {
                              target[name] = value
                          }
                      }
                      // set方法必须返回true, 告诉Proxy已经成功修改了这个值,否则会抛
                      //'set' on proxy: trap returned falsish for property xxx 错误
                      return true
                  }
                  //has 只能用于 in 操作符，没什么用删去
  
              }
  
  
              avalon$2.itemFactory = platform.itemFactory = function itemFactory(before, after) {
                  var definition = before.$model
                  definition.$proxyItemBackdoor = true
                  definition.$id = before.$hashcode + String(after.hashcode || Math.random()).slice(6)
                  definition.$accessors = avalon$2.mix({}, before.$accessors)
                  var vm = platform.modelFactory(definition)
                  vm.$track = before.$track
                  for (var i in after.data) {
                      vm[i] = after.data[i]
                  }
                  return vm
              }
  
              platform.fuseFactory = function fuseFactory(before, after) {
                  var definition = avalon$2.mix(before.$model, after.$model)
                  definition.$id = before.$hashcode + after.$hashcode
                  definition.$accessors = avalon$2.mix({}, before.$accessors, after.$accessors)
                  return platform.modelFactory(definition)
              }
          })()
      }
  
      var impDir = avalon$2.directive('important', {
          priority: 1,
          getScope: function getScope(name, scope) {
              var v = avalon$2.vmodels[name]
              if (v) return v
              throw 'error! no vmodel called ' + name
          },
          update: function update(node, attrName, $id) {
              if (!avalon$2.inBrowser) return
              var dom = avalon$2.vdom(node, 'toDOM')
              if (dom.nodeType === 1) {
                  dom.removeAttribute(attrName)
                  avalon$2(dom).removeClass('ms-controller')
              }
              var vm = avalon$2.vmodels[$id]
              if (vm) {
                  vm.$element = dom
                  vm.$render = this
                  vm.$fire('onReady')
                  delete vm.$events.onReady
              }
          }
      })
  
      var impCb = impDir.update
  
      avalon$2.directive('controller', {
          priority: 2,
          getScope: function getScope(name, scope) {
              var v = avalon$2.vmodels[name]
              if (v) {
                  v.$render = this
                  if (scope && scope !== v) {
                      return platform.fuseFactory(scope, v)
                  }
                  return v
              }
              return scope
          },
          update: impCb
      })
  
      avalon$2.directive('skip', {
          delay: true
      })
  
      var arrayWarn = {}
      var cssDir = avalon$2.directive('css', {
          diff: function diff(newVal, oldVal) {
              if (Object(newVal) === newVal) {
                  newVal = platform.toJson(newVal) //安全的遍历VBscript
                  if (Array.isArray(newVal)) {
                      //转换成对象
                      var b = {}
                      newVal.forEach(function (el) {
                          el && avalon$2.shadowCopy(b, el)
                      })
                      newVal = b
                      if (!arrayWarn[this.type]) {
                          avalon$2.warn('ms-' + this.type + '指令的值不建议使用数组形式了！')
                          arrayWarn[this.type] = 1
                      }
                  }
  
                  var hasChange = false
                  var patch = {}
                  if (!oldVal) {
                      //如果一开始为空
                      patch = newVal
                      hasChange = true
                  } else {
                      if (this.deep) {
                          var deep = typeof this.deep === 'number' ? this.deep : 6
                          for (var i in newVal) {
                              //diff差异点  
                              if (!deepEquals(newVal[i], oldVal[i], 4)) {
                                  this.value = newVal
                                  return true
                              }
                              patch[i] = newVal[i]
                          }
                      } else {
                          for (var _i6 in newVal) {
                              //diff差异点
                              if (newVal[_i6] !== oldVal[_i6]) {
                                  hasChange = true
                              }
                              patch[_i6] = newVal[_i6]
                          }
                      }
  
                      for (var _i7 in oldVal) {
                          if (!(_i7 in patch)) {
                              hasChange = true
                              patch[_i7] = ''
                          }
                      }
                  }
                  if (hasChange) {
                      this.value = patch
                      return true
                  }
              }
              return false
          },
          update: function update(vdom, value) {
  
              var dom = vdom.dom
              if (dom && dom.nodeType === 1) {
                  var wrap = avalon$2(dom)
                  for (var name in value) {
                      wrap.css(name, value[name])
                  }
              }
          }
      })
  
      var cssDiff = cssDir.diff
  
      function getEnumerableKeys(obj) {
          var res = []
          for (var key in obj) {
              res.push(key)
          }return res
      }
  
      function deepEquals(a, b, level) {
          if (level === 0) return a === b
          if (a === null && b === null) return true
          if (a === undefined && b === undefined) return true
          var aIsArray = Array.isArray(a)
          if (aIsArray !== Array.isArray(b)) {
              return false
          }
          if (aIsArray) {
              return equalArray(a, b, level)
          } else if (typeof a === "object" && typeof b === "object") {
              return equalObject(a, b, level)
          }
          return a === b
      }
  
      function equalArray(a, b, level) {
          if (a.length !== b.length) {
              return false
          }
          for (var i = a.length - 1; i >= 0; i--) {
              try {
                  if (!deepEquals(a[i], b[i], level - 1)) {
                      return false
                  }
              } catch (noThisPropError) {
                  return false
              }
          }
          return true
      }
  
      function equalObject(a, b, level) {
          if (a === null || b === null) return false
          if (getEnumerableKeys(a).length !== getEnumerableKeys(b).length) return false
          for (var prop in a) {
              if (!(prop in b)) return false
              try {
                  if (!deepEquals(a[prop], b[prop], level - 1)) {
                      return false
                  }
              } catch (noThisPropError) {
                  return false
              }
          }
          return true
      }
  
      /**
       * ------------------------------------------------------------
       * 检测浏览器对CSS动画的支持与API名
       * ------------------------------------------------------------
       */
  
      var checker = {
          TransitionEvent: 'transitionend',
          WebKitTransitionEvent: 'webkitTransitionEnd',
          OTransitionEvent: 'oTransitionEnd',
          otransitionEvent: 'otransitionEnd'
      }
      var css3 = void 0
      var tran = void 0
      var ani = void 0
      var name$2 = void 0
      var animationEndEvent = void 0
      var transitionEndEvent = void 0
      var transition = false
      var animation = false
      //有的浏览器同时支持私有实现与标准写法，比如webkit支持前两种，Opera支持1、3、4
      for (name$2 in checker) {
          if (window$1[name$2]) {
              tran = checker[name$2]
              break
          }
          /* istanbul ignore next */
          try {
              var a = document.createEvent(name$2)
              tran = checker[name$2]
              break
          } catch (e) {}
      }
      if (typeof tran === 'string') {
          transition = css3 = true
          transitionEndEvent = tran
      }
  
      //animationend有两个可用形态
      //IE10+, Firefox 16+ & Opera 12.1+: animationend
      //Chrome/Safari: webkitAnimationEnd
      //http://blogs.msdn.com/b/davrous/archive/2011/12/06/introduction-to-css3-animat ions.aspx
      //IE10也可以使用MSAnimationEnd监听，但是回调里的事件 type依然为animationend
      //  el.addEventListener('MSAnimationEnd', function(e) {
      //     alert(e.type)// animationend！！！
      // })
      checker = {
          'AnimationEvent': 'animationend',
          'WebKitAnimationEvent': 'webkitAnimationEnd'
      }
      for (name$2 in checker) {
          if (window$1[name$2]) {
              ani = checker[name$2]
              break
          }
      }
      if (typeof ani === 'string') {
          animation = css3 = true
          animationEndEvent = ani
      }
  
      var effectDir = avalon$2.directive('effect', {
          priority: 5,
          diff: function diff(effect) {
              var vdom = this.node
              if (typeof effect === 'string') {
                  this.value = effect = {
                      is: effect
                  }
                  avalon$2.warn('ms-effect的指令值不再支持字符串,必须是一个对象')
              }
              this.value = vdom.effect = effect
              var ok = cssDiff.call(this, effect, this.oldValue)
              var me = this
              if (ok) {
                  setTimeout(function () {
                      vdom.animating = true
                      effectDir.update.call(me, vdom, vdom.effect)
                  })
                  vdom.animating = false
                  return true
              }
              return false
          },
  
          update: function update(vdom, change, opts) {
              var dom = vdom.dom
              if (dom && dom.nodeType === 1) {
                  //要求配置对象必须指定is属性，action必须是布尔或enter,leave,move
                  var option = change || opts
                  var is = option.is
  
                  var globalOption = avalon$2.effects[is]
                  if (!globalOption) {
                      //如果没有定义特效
                      avalon$2.warn(is + ' effect is undefined')
                      return
                  }
                  var finalOption = {}
                  var action = actionMaps[option.action]
                  if (typeof Effect.prototype[action] !== 'function') {
                      avalon$2.warn('action is undefined')
                      return
                  }
                  //必须预定义特效
  
                  var effect = new avalon$2.Effect(dom)
                  avalon$2.mix(finalOption, globalOption, option, { action: action })
  
                  if (finalOption.queue) {
                      animationQueue.push(function () {
                          effect[action](finalOption)
                      })
                      callNextAnimation()
                  } else {
  
                      effect[action](finalOption)
                  }
                  return true
              }
          }
      })
  
      var move = 'move'
      var leave = 'leave'
      var enter = 'enter'
      var actionMaps = {
          'true': enter,
          'false': leave,
          enter: enter,
          leave: leave,
          move: move,
          'undefined': enter
      }
  
      var animationQueue = []
      function callNextAnimation() {
          var fn = animationQueue[0]
          if (fn) {
              fn()
          }
      }
  
      avalon$2.effects = {}
      avalon$2.effect = function (name, opts) {
          var definition = avalon$2.effects[name] = opts || {}
          if (css3 && definition.css !== false) {
              patchObject(definition, 'enterClass', name + '-enter')
              patchObject(definition, 'enterActiveClass', definition.enterClass + '-active')
              patchObject(definition, 'leaveClass', name + '-leave')
              patchObject(definition, 'leaveActiveClass', definition.leaveClass + '-active')
          }
          return definition
      }
  
      function patchObject(obj, name, value) {
          if (!obj[name]) {
              obj[name] = value
          }
      }
  
      var Effect = function Effect(dom) {
          this.dom = dom
      }
  
      avalon$2.Effect = Effect
  
      Effect.prototype = {
          enter: createAction('Enter'),
          leave: createAction('Leave'),
          move: createAction('Move')
      }
  
      function execHooks(options, name, el) {
          var fns = [].concat(options[name])
          for (var i = 0, fn; fn = fns[i++];) {
              if (typeof fn === 'function') {
                  fn(el)
              }
          }
      }
      var staggerCache = new Cache(128)
  
      function createAction(action) {
          var lower = action.toLowerCase()
          return function (option) {
              var dom = this.dom
              var elem = avalon$2(dom)
              //处理与ms-for指令相关的stagger
              //========BEGIN=====
              var staggerTime = isFinite(option.stagger) ? option.stagger * 1000 : 0
              if (staggerTime) {
                  if (option.staggerKey) {
                      var stagger = staggerCache.get(option.staggerKey) || staggerCache.put(option.staggerKey, {
                          count: 0,
                          items: 0
                      })
                      stagger.count++
                      stagger.items++
                  }
              }
              var staggerIndex = stagger && stagger.count || 0
              //=======END==========
              var stopAnimationID
              var animationDone = function animationDone(e) {
                  var isOk = e !== false
                  if (--dom.__ms_effect_ === 0) {
                      avalon$2.unbind(dom, transitionEndEvent)
                      avalon$2.unbind(dom, animationEndEvent)
                  }
                  clearTimeout(stopAnimationID)
                  var dirWord = isOk ? 'Done' : 'Abort'
                  execHooks(option, 'on' + action + dirWord, dom)
                  if (stagger) {
                      if (--stagger.items === 0) {
                          stagger.count = 0
                      }
                  }
                  if (option.queue) {
                      animationQueue.shift()
                      callNextAnimation()
                  }
              }
              //执行开始前的钩子
              execHooks(option, 'onBefore' + action, dom)
  
              if (option[lower]) {
                  //使用JS方式执行动画
                  option[lower](dom, function (ok) {
                      animationDone(ok !== false)
                  })
              } else if (css3) {
                  //使用CSS3方式执行动画
                  elem.addClass(option[lower + 'Class'])
                  elem.removeClass(getNeedRemoved(option, lower))
  
                  if (!dom.__ms_effect_) {
                      //绑定动画结束事件
                      elem.bind(transitionEndEvent, animationDone)
                      elem.bind(animationEndEvent, animationDone)
                      dom.__ms_effect_ = 1
                  } else {
                      dom.__ms_effect_++
                  }
                  setTimeout(function () {
                      //用xxx-active代替xxx类名的方式 触发CSS3动画
                      var time = avalon$2.root.offsetWidth === NaN
                      elem.addClass(option[lower + 'ActiveClass'])
                      //计算动画时长
                      time = getAnimationTime(dom)
                      if (!time === 0) {
                          //立即结束动画
                          animationDone(false)
                      } else if (!staggerTime) {
                          //如果动画超出时长还没有调用结束事件,这可能是元素被移除了
                          //如果强制结束动画
                          stopAnimationID = setTimeout(function () {
                              animationDone(false)
                          }, time + 32)
                      }
                  }, 17 + staggerTime * staggerIndex) // = 1000/60
              }
          }
      }
  
      avalon$2.applyEffect = function (dom, vdom, opts) {
          var cb = opts.cb
          var curEffect = vdom.effect
          if (curEffect && dom && dom.nodeType === 1) {
              var hook = opts.hook
              var old = curEffect[hook]
              if (cb) {
                  if (Array.isArray(old)) {
                      old.push(cb)
                  } else if (old) {
                      curEffect[hook] = [old, cb]
                  } else {
                      curEffect[hook] = [cb]
                  }
              }
              getAction(opts)
              avalon$2.directives.effect.update(vdom, curEffect, avalon$2.shadowCopy({}, opts))
          } else if (cb) {
              cb(dom)
          }
      }
      /**
       * 获取方向
       */
      function getAction(opts) {
          if (!opts.action) {
              return opts.action = opts.hook.replace(/^on/, '').replace(/Done$/, '').toLowerCase()
          }
      }
      /**
       * 需要移除的类名
       */
      function getNeedRemoved(options, name) {
          var name = name === 'leave' ? 'enter' : 'leave'
          return Array(name + 'Class', name + 'ActiveClass').map(function (cls) {
              return options[cls]
          }).join(' ')
      }
      /**
       * 计算动画长度
       */
      var transitionDuration = avalon$2.cssName('transition-duration')
      var animationDuration = avalon$2.cssName('animation-duration')
      var rsecond = /\d+s$/
      function toMillisecond(str) {
          var ratio = rsecond.test(str) ? 1000 : 1
          return parseFloat(str) * ratio
      }
  
      function getAnimationTime(dom) {
          var computedStyles = window$1.getComputedStyle(dom, null)
          var tranDuration = computedStyles[transitionDuration]
          var animDuration = computedStyles[animationDuration]
          return toMillisecond(tranDuration) || toMillisecond(animDuration)
      }
      /**
       * 
      <!DOCTYPE html>
      <html>
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <script src="dist/avalon.js"></script>
              <script>
                  avalon.effect('animate')
                  var vm = avalon.define({
                      $id: 'ani',
                      a: true
                  })
              </script>
              <style>
                  .animate-enter, .animate-leave{
                      width:100px;
                      height:100px;
                      background: #29b6f6;
                      transition:all 2s;
                      -moz-transition: all 2s; 
                      -webkit-transition: all 2s;
                      -o-transition:all 2s;
                  }  
                  .animate-enter-active, .animate-leave{
                      width:300px;
                      height:300px;
                  }
                  .animate-leave-active{
                      width:100px;
                      height:100px;
                  }
              </style>
          </head>
          <body>
              <div :controller='ani' >
                  <p><input type='button' value='click' :click='@a =!@a'></p>
                  <div :effect="{is:'animate',action:@a}"></div>
              </div>
      </body>
      </html>
       * 
       */
  
      var none = 'none'
      function parseDisplay(elem, val) {
          //用于取得此类标签的默认display值
          var doc = elem.ownerDocument
          var nodeName = elem.nodeName
          var key = '_' + nodeName
          if (!parseDisplay[key]) {
              var temp = doc.body.appendChild(doc.createElement(nodeName))
              val = avalon$2.css(temp, 'display')
              doc.body.removeChild(temp)
              if (val === none) {
                  val = 'block'
              }
              parseDisplay[key] = val
          }
          return parseDisplay[key]
      }
  
      avalon$2.parseDisplay = parseDisplay
      avalon$2.directive('visible', {
          diff: function diff(newVal, oldVal) {
              var n = !!newVal
              if (oldVal === void 0 || n !== oldVal) {
                  this.value = n
                  return true
              }
          },
          ready: true,
          update: function update(vdom, show) {
              var dom = vdom.dom
              if (dom && dom.nodeType === 1) {
                  var display = dom.style.display
                  var value
                  if (show) {
                      if (display === none) {
                          value = vdom.displayValue
                          if (!value) {
                              dom.style.display = ''
                              if (dom.style.cssText === '') {
                                  dom.removeAttribute('style')
                              }
                          }
                      }
                      if (dom.style.display === '' && avalon$2(dom).css('display') === none &&
                      // fix firefox BUG,必须挂到页面上
                      avalon$2.contains(dom.ownerDocument, dom)) {
                          value = parseDisplay(dom)
                      }
                  } else {
  
                      if (display !== none) {
                          value = none
                          vdom.displayValue = display
                      }
                  }
                  var cb = function cb() {
                      if (value !== void 0) {
                          dom.style.display = value
                      }
                  }
  
                  avalon$2.applyEffect(dom, vdom, {
                      hook: show ? 'onEnterDone' : 'onLeaveDone',
                      cb: cb
                  })
              }
          }
      })
  
      avalon$2.directive('text', {
          delay: true,
          init: function init() {
  
              var node = this.node
              if (node.isVoidTag) {
                  avalon$2.error('自闭合元素不能使用ms-text')
              }
              var child = { nodeName: '#text', nodeValue: this.getValue() }
              node.children.splice(0, node.children.length, child)
              if (inBrowser) {
                  avalon$2.clearHTML(node.dom)
                  node.dom.appendChild(avalon$2.vdom(child, 'toDOM'))
              }
              this.node = child
              var type = 'expr'
              this.type = this.name = type
              var directive$$1 = avalon$2.directives[type]
              var me = this
              this.callback = function (value) {
                  directive$$1.update.call(me, me.node, value)
              }
          }
      })
  
      avalon$2.directive('expr', {
          update: function update(vdom, value) {
              vdom.nodeValue = value
              //https://github.com/RubyLouvre/avalon/issues/1834
              if (vdom.dom) if (value === '') value = '\u200B'
              vdom.dom.data = value
          }
      })
  
      avalon$2.directive('attr', {
          diff: cssDiff,
          update: function update(vdom, value) {
              var props = vdom.props
              for (var i in value) {
                  if (!!value[i] === false) {
                      delete props[i]
                  } else {
                      props[i] = value[i]
                  }
              }
              var dom = vdom.dom
              if (dom && dom.nodeType === 1) {
                  updateAttrs(dom, value)
              }
          }
      })
  
      avalon$2.directive('html', {
  
          update: function update(vdom, value) {
              this.beforeDispose()
  
              this.innerRender = avalon$2.scan('<div class="ms-html-container">' + value + '</div>', this.vm, function () {
                  var oldRoot = this.root
                  if (vdom.children) vdom.children.length = 0
                  vdom.children = oldRoot.children
                  this.root = vdom
                  if (vdom.dom) avalon$2.clearHTML(vdom.dom)
              })
          },
          beforeDispose: function beforeDispose() {
              if (this.innerRender) {
                  this.innerRender.dispose()
              }
          },
          delay: true
      })
  
      avalon$2.directive('if', {
          delay: true,
          priority: 5,
          init: function init() {
              this.placeholder = createAnchor('if')
              var props = this.node.props
              delete props['ms-if']
              delete props[':if']
              this.fragment = avalon$2.vdom(this.node, 'toHTML')
          },
          diff: function diff(newVal, oldVal) {
              var n = !!newVal
              if (oldVal === void 0 || n !== oldVal) {
                  this.value = n
                  return true
              }
          },
          update: function update(vdom, value) {
              if (this.isShow === void 0 && value) {
                  continueScan(this, vdom)
                  return
              }
              this.isShow = value
              var placeholder = this.placeholder
  
              if (value) {
                  var p = placeholder.parentNode
                  continueScan(this, vdom)
                  p && p.replaceChild(vdom.dom, placeholder)
              } else {
                  //移除DOM
                  this.beforeDispose()
                  vdom.nodeValue = 'if'
                  vdom.nodeName = '#comment'
                  delete vdom.children
                  var dom = vdom.dom
                  var p = dom && dom.parentNode
                  vdom.dom = placeholder
                  if (p) {
                      p.replaceChild(placeholder, dom)
                  }
              }
          },
          beforeDispose: function beforeDispose() {
              if (this.innerRender) {
                  this.innerRender.dispose()
              }
          }
      })
  
      function continueScan(instance, vdom) {
          var innerRender = instance.innerRender = avalon$2.scan(instance.fragment, instance.vm)
          avalon$2.shadowCopy(vdom, innerRender.root)
          delete vdom.nodeValue
      }
  
      avalon$2.directive('on', {
          beforeInit: function beforeInit() {
              this.getter = avalon$2.noop
          },
          init: function init() {
              var vdom = this.node
              var underline = this.name.replace('ms-on-', 'e').replace('-', '_')
              var uuid = underline + '_' + this.expr.replace(/\s/g, '').replace(/[^$a-z]/ig, function (e) {
                  return e.charCodeAt(0)
              })
              var fn = avalon$2.eventListeners[uuid]
              if (!fn) {
                  var arr = addScope(this.expr)
                  var body = arr[0],
                      filters = arr[1]
                  body = makeHandle(body)
  
                  if (filters) {
                      filters = filters.replace(/__value__/g, '$event')
                      filters += '\nif($event.$return){\n\treturn;\n}'
                  }
                  var ret = ['try{', '\tvar __vmodel__ = this;', '\t' + filters, '\treturn ' + body, '}catch(e){avalon.log(e, "in on dir")}'].filter(function (el) {
                      return (/\S/.test(el)
                      )
                  })
                  fn = new Function('$event', ret.join('\n'))
                  fn.uuid = uuid
                  avalon$2.eventListeners[uuid] = fn
              }
  
              var dom = avalon$2.vdom(vdom, 'toDOM')
              dom._ms_context_ = this.vm
  
              this.eventType = this.param.replace(/\-(\d)$/, '')
              delete this.param
              avalon$2(dom).bind(this.eventType, fn)
          },
  
          beforeDispose: function beforeDispose() {
              avalon$2(this.node.dom).unbind(this.eventType)
          }
      })
  
      var rforAs = /\s+as\s+([$\w]+)/
      var rident = /^[$a-zA-Z_][$a-zA-Z0-9_]*$/
      var rinvalid = /^(null|undefined|NaN|window|this|\$index|\$id)$/
      var rargs = /[$\w_]+/g
      avalon$2.directive('for', {
          delay: true,
          priority: 3,
          beforeInit: function beforeInit() {
              var str = this.expr,
                  asName
              str = str.replace(rforAs, function (a, b) {
                  /* istanbul ignore if */
                  if (!rident.test(b) || rinvalid.test(b)) {
                      avalon$2.error('alias ' + b + ' is invalid --- must be a valid JS identifier which is not a reserved name.')
                  } else {
                      asName = b
                  }
                  return ''
              })
  
              var arr = str.split(' in ')
              var kv = arr[0].match(rargs)
              if (kv.length === 1) {
                  //确保avalon._each的回调有三个参数
                  kv.unshift('$key')
              }
              this.expr = arr[1]
              this.keyName = kv[0]
              this.valName = kv[1]
              this.signature = avalon$2.makeHashCode('for')
              if (asName) {
                  this.asName = asName
              }
  
              delete this.param
          },
          init: function init() {
              var cb = this.userCb
              if (typeof cb === 'string' && cb) {
                  var arr = addScope(cb, 'for')
                  var body = makeHandle(arr[0])
                  this.userCb = new Function('$event', 'var __vmodel__ = this\nreturn ' + body)
              }
              this.node.forDir = this //暴露给component/index.js中的resetParentChildren方法使用
              this.fragment = ['<div>', this.fragment, '<!--', this.signature, '--></div>'].join('')
              this.cache = {}
          },
          diff: function diff(newVal, oldVal) {
              /* istanbul ignore if */
              if (this.updating) {
                  return
              }
  
              this.updating = true
              var traceIds = createFragments(this, newVal)
  
              if (this.oldTrackIds === void 0) return true
  
              if (this.oldTrackIds !== traceIds) {
                  this.oldTrackIds = traceIds
                  return true
              }
          },
          update: function update() {
  
              if (!this.preFragments) {
                  this.fragments = this.fragments || []
                  mountList(this)
              } else {
                  diffList(this)
                  updateList(this)
              }
  
              if (this.userCb) {
                  this.userCb.call(this.vm, {
                      type: 'rendered',
                      target: this.begin.dom,
                      signature: this.signature
                  })
              }
              delete this.updating
          },
          beforeDispose: function beforeDispose() {
              this.fragments.forEach(function (el) {
                  el.dispose()
              })
          }
      })
  
      function getTraceKey(item) {
          var type = typeof item
          return item && type === 'object' ? item.$hashcode : type + ':' + item
      }
  
      //创建一组fragment的虚拟DOM
      function createFragments(instance, obj) {
          if (isObject(obj)) {
              var array = Array.isArray(obj)
              var ids = []
              var fragments = [],
                  i = 0
  
              instance.isArray = array
              if (instance.fragments) {
                  instance.preFragments = instance.fragments
                  avalon$2.each(obj, function (key, value) {
                      var k = array ? getTraceKey(value) : key
  
                      fragments.push({
                          key: k,
                          val: value,
                          index: i++
                      })
                      ids.push(k)
                  })
                  instance.fragments = fragments
              } else {
                  avalon$2.each(obj, function (key, value) {
                      var k = array ? getTraceKey(value) : key
                      fragments.push(new VFragment([], k, value, i++))
                      ids.push(k)
                  })
                  instance.fragments = fragments
              }
              return ids.join(';;')
          } else {
              return NaN
          }
      }
  
      function mountList(instance) {
          var args = instance.fragments.map(function (fragment, index) {
              FragmentDecorator(fragment, instance, index)
              saveInCache(instance.cache, fragment)
              return fragment
          })
          var list = instance.parentChildren
          var i = list.indexOf(instance.begin)
          list.splice.apply(list, [i + 1, 0].concat(args))
      }
  
      function diffList(instance) {
          var cache = instance.cache
          var newCache = {}
          var fuzzy = []
          var list = instance.preFragments
  
          list.forEach(function (el) {
              el._dispose = true
          })
  
          instance.fragments.forEach(function (c, index) {
              var fragment = isInCache(cache, c.key)
              //取出之前的文档碎片
              if (fragment) {
                  delete fragment._dispose
                  fragment.oldIndex = fragment.index
                  fragment.index = index // 相当于 c.index
                  resetVM(fragment.vm, instance.keyName)
                  fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key
                  saveInCache(newCache, fragment)
              } else {
                  //如果找不到就进行模糊搜索
                  fuzzy.push(c)
              }
          })
          fuzzy.forEach(function (c) {
              var fragment = fuzzyMatchCache(cache, c.key)
              if (fragment) {
                  //重复利用
                  fragment.oldIndex = fragment.index
                  fragment.key = c.key
                  var val = fragment.val = c.val
                  var index = fragment.index = c.index
  
                  fragment.vm[instance.valName] = val
                  fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key
                  delete fragment._dispose
              } else {
  
                  c = new VFragment([], c.key, c.val, c.index)
                  fragment = FragmentDecorator(c, instance, c.index)
                  list.push(fragment)
              }
              saveInCache(newCache, fragment)
          })
  
          instance.fragments = list
          list.sort(function (a, b) {
              return a.index - b.index
          })
          instance.cache = newCache
      }
  
      function resetVM(vm, a, b) {
          vm.$accessors[a].value = NaN
      }
  
      function updateList(instance) {
          var before = instance.begin.dom
          var parent = before.parentNode
          var list = instance.fragments
          var end = instance.end.dom
          for (var i = 0, item; item = list[i]; i++) {
              if (item._dispose) {
                  list.splice(i, 1)
                  i--
                  item.dispose()
                  continue
              }
              if (item.oldIndex !== item.index) {
                  var f = item.toFragment()
                  var isEnd = before.nextSibling === null
                  parent.insertBefore(f, before.nextSibling)
                  if (isEnd && !parent.contains(end)) {
                      parent.insertBefore(end, before.nextSibling)
                  }
              }
              before = item.split
          }
          var ch = instance.parentChildren
          var startIndex = ch.indexOf(instance.begin)
          var endIndex = ch.indexOf(instance.end)
  
          list.splice.apply(ch, [startIndex + 1, endIndex - startIndex].concat(list))
      }
  
      /**
       * 
       * @param {type} fragment
       * @param {type} this
       * @param {type} index
       * @returns { key, val, index, oldIndex, this, dom, split, vm}
       */
      function FragmentDecorator(fragment, instance, index) {
          var data = {}
          data[instance.keyName] = instance.isArray ? index : fragment.key
          data[instance.valName] = fragment.val
          if (instance.asName) {
              data[instance.asName] = instance.value
          }
          var vm = fragment.vm = platform.itemFactory(instance.vm, {
              data: data
          })
  
          if (instance.isArray) {
              vm.$watch(instance.valName, function (a) {
                  if (instance.value && instance.value.set) {
                      instance.value.set(vm[instance.keyName], a)
                  }
              })
          } else {
              vm.$watch(instance.valName, function (a) {
                  instance.value[fragment.key] = a
              })
          }
          fragment.index = index
          fragment.innerRender = avalon$2.scan(instance.fragment, vm, function () {
              var oldRoot = this.root
              ap.push.apply(fragment.children, oldRoot.children)
              this.root = fragment
          })
          return fragment
      }
      // 新位置: 旧位置
      function isInCache(cache, id) {
          var c = cache[id]
          if (c) {
              var arr = c.arr
              /* istanbul ignore if*/
              if (arr) {
                  var r = arr.pop()
                  if (!arr.length) {
                      c.arr = 0
                  }
                  return r
              }
              delete cache[id]
              return c
          }
      }
      //[1,1,1] number1 number1_ number1__
      function saveInCache(cache, component) {
          var trackId = component.key
          if (!cache[trackId]) {
              cache[trackId] = component
          } else {
              var c = cache[trackId]
              var arr = c.arr || (c.arr = [])
              arr.push(component)
          }
      }
  
      function fuzzyMatchCache(cache) {
          var key
          for (var id in cache) {
              var key = id
              break
          }
          if (key) {
              return isInCache(cache, key)
          }
      }
  
      //根据VM的属性值或表达式的值切换类名，ms-class='xxx yyy zzz:flag'
      //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
      function classNames() {
          var classes = []
          for (var i = 0; i < arguments.length; i++) {
              var arg = arguments[i]
              var argType = typeof arg
              if (argType === 'string' || argType === 'number' || arg === true) {
                  classes.push(arg)
              } else if (Array.isArray(arg)) {
                  classes.push(classNames.apply(null, arg))
              } else if (argType === 'object') {
                  for (var key in arg) {
                      if (arg.hasOwnProperty(key) && arg[key]) {
                          classes.push(key)
                      }
                  }
              }
          }
  
          return classes.join(' ')
      }
  
      avalon$2.directive('class', {
          diff: function diff(newVal, oldVal) {
              var type = this.type
              var vdom = this.node
              var classEvent = vdom.classEvent || {}
              if (type === 'hover') {
                  //在移出移入时切换类名
                  classEvent.mouseenter = activateClass
                  classEvent.mouseleave = abandonClass
              } else if (type === 'active') {
                  //在获得焦点时切换类名
                  classEvent.tabIndex = vdom.props.tabindex || -1
                  classEvent.mousedown = activateClass
                  classEvent.mouseup = abandonClass
                  classEvent.mouseleave = abandonClass
              }
              vdom.classEvent = classEvent
  
              var className = classNames(newVal)
  
              if (typeof oldVal === void 0 || oldVal !== className) {
                  this.value = className
  
                  vdom['change-' + type] = className
                  return true
              }
          },
          update: function update(vdom, value) {
              var dom = vdom.dom
              if (dom && dom.nodeType == 1) {
  
                  var dirType = this.type
                  var change = 'change-' + dirType
                  var classEvent = vdom.classEvent
                  if (classEvent) {
                      for (var i in classEvent) {
                          if (i === 'tabIndex') {
                              dom[i] = classEvent[i]
                          } else {
                              avalon$2.bind(dom, i, classEvent[i])
                          }
                      }
                      vdom.classEvent = {}
                  }
                  var names = ['class', 'hover', 'active']
                  names.forEach(function (type) {
                      if (dirType !== type) return
                      if (type === 'class') {
                          dom && setClass(dom, value)
                      } else {
                          var oldClass = dom.getAttribute(change)
                          if (oldClass) {
                              avalon$2(dom).removeClass(oldClass)
                          }
                          var name = 'change-' + type
                          dom.setAttribute(name, value)
                      }
                  })
              }
          }
      })
  
      directives.active = directives.hover = directives['class']
  
      var classMap = {
          mouseenter: 'change-hover',
          mouseleave: 'change-hover',
          mousedown: 'change-active',
          mouseup: 'change-active'
      }
  
      function activateClass(e) {
          var elem = e.target
          avalon$2(elem).addClass(elem.getAttribute(classMap[e.type]) || '')
      }
  
      function abandonClass(e) {
          var elem = e.target
          var name = classMap[e.type]
          avalon$2(elem).removeClass(elem.getAttribute(name) || '')
          if (name !== 'change-active') {
              avalon$2(elem).removeClass(elem.getAttribute('change-active') || '')
          }
      }
  
      function setClass(dom, neo) {
          var old = dom.getAttribute('change-class')
          if (old !== neo) {
              avalon$2(dom).removeClass(old).addClass(neo)
              dom.setAttribute('change-class', neo)
          }
      }
  
      getLongID(activateClass)
      getLongID(abandonClass)
  
      function lookupOption(vdom, values) {
          vdom.children && vdom.children.forEach(function (el) {
              if (el.nodeName === 'option') {
                  setOption(el, values)
              } else {
                  lookupOption(el, values)
              }
          })
      }
  
      function setOption(vdom, values) {
          var props = vdom.props
          if (!('disabled' in props)) {
              var value = getOptionValue(vdom, props).trim()
              props.selected = values.indexOf(value) !== -1
              if (vdom.dom) {
                  vdom.dom.selected = props.selected
              }
          }
      }
  
      function getOptionValue(vdom, props) {
          if (props && 'value' in props) {
              return props.value
          }
          var arr = []
          vdom.children.forEach(function (el) {
              if (el.nodeName === '#text') {
                  arr.push(el.nodeValue)
              } else if (el.nodeName === '#document-fragment') {
                  arr.push(getOptionValue(el))
              }
          })
          return arr.join('')
      }
  
      function getSelectedValue(vdom, arr) {
          vdom.children.forEach(function (el) {
              if (el.nodeName === 'option') {
                  if (el.props.selected === true) arr.push(getOptionValue(el, el.props))
              } else if (el.children) {
                  getSelectedValue(el, arr)
              }
          })
          return arr
      }
  
      var rchangeFilter = /\|\s*change\b/
      var rdebounceFilter = /\|\s*debounce(?:\(([^)]+)\))?/
      function duplexBeforeInit() {
          var expr = this.expr
          if (rchangeFilter.test(expr)) {
              this.isChanged = true
              expr = expr.replace(rchangeFilter, '')
          }
          var match = expr.match(rdebounceFilter)
          if (match) {
              expr = expr.replace(rdebounceFilter, '')
              if (!this.isChanged) {
                  this.debounceTime = parseInt(match[1], 10) || 300
              }
          }
          this.expr = expr
      }
      function duplexInit() {
          var expr = this.expr
          var node = this.node
          var etype = node.props.type
          this.parseValue = parseValue
          //处理数据转换器
          var parsers = this.param,
              dtype
          var isChecked = false
          parsers = parsers ? parsers.split('-').map(function (a) {
              if (a === 'checked') {
                  isChecked = true
              }
              return a
          }) : []
          node.duplex = this
          if (rcheckedType.test(etype) && isChecked) {
              //如果是radio, checkbox,判定用户使用了checked格式函数没有
              parsers = []
              dtype = 'radio'
              this.isChecked = isChecked
          }
          this.parsers = parsers
          if (!/input|textarea|select/.test(node.nodeName)) {
              if ('contenteditable' in node.props) {
                  dtype = 'contenteditable'
              }
          } else if (!dtype) {
              dtype = node.nodeName === 'select' ? 'select' : etype === 'checkbox' ? 'checkbox' : etype === 'radio' ? 'radio' : 'input'
          }
          this.dtype = dtype
          var isChanged = false,
              debounceTime = 0
          //判定是否使用了 change debounce 过滤器
          // this.isChecked = /boolean/.test(parsers)
          if (dtype !== 'input' && dtype !== 'contenteditable') {
              delete this.isChange
              delete this.debounceTime
          } else if (!this.isChecked) {
              this.isString = true
          }
  
          var cb = node.props['data-duplex-changed']
          if (cb) {
              var arr = addScope(cb, 'xx')
              var body = makeHandle(arr[0])
              this.userCb = new Function('$event', 'var __vmodel__ = this\nreturn ' + body)
          }
      }
      function duplexDiff(newVal, oldVal) {
          if (Array.isArray(newVal)) {
              if (newVal + '' !== this.compareVal) {
                  this.compareVal = newVal + ''
                  return true
              }
          } else {
              newVal = this.parseValue(newVal)
              if (!this.isChecked) {
                  this.value = newVal += ''
              }
              if (newVal !== this.compareVal) {
                  this.compareVal = newVal
                  return true
              }
          }
      }
  
      function duplexValidate(node, vdom) {
          //将当前虚拟DOM的duplex添加到它上面的表单元素的validate指令的fields数组中
          var field = vdom.duplex
          var rules = vdom.rules
  
          if (rules && !field.validator) {
              while (node && node.nodeType === 1) {
                  var validator = node._ms_validate_
                  if (validator) {
                      field.rules = rules
                      field.validator = validator
  
                      if (avalon$2.Array.ensure(validator.fields, field)) {
                          validator.addField(field)
                      }
                      break
                  }
                  node = node.parentNode
              }
          }
      }
  
      var valueHijack = true
      try {
          //#272 IE9-IE11, firefox
          var setters = {}
          var aproto = HTMLInputElement.prototype
          var bproto = HTMLTextAreaElement.prototype
          var newSetter = function newSetter(value) {
              // jshint ignore:line
              setters[this.tagName].call(this, value)
              var data = this._ms_duplex_
              if (!this.caret && data && data.isString) {
                  data.duplexCb.call(this, { type: 'setter' })
              }
          }
          var inputProto = HTMLInputElement.prototype
          Object.getOwnPropertyNames(inputProto) //故意引发IE6-8等浏览器报错
          setters['INPUT'] = Object.getOwnPropertyDescriptor(aproto, 'value').set
  
          Object.defineProperty(aproto, 'value', {
              set: newSetter
          })
          setters['TEXTAREA'] = Object.getOwnPropertyDescriptor(bproto, 'value').set
          Object.defineProperty(bproto, 'value', {
              set: newSetter
          })
          valueHijack = false
      } catch (e) {
          //在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了
          // http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype
          // https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1
      }
  
      function parseValue(val) {
          for (var i = 0, k; k = this.parsers[i++];) {
              var fn = avalon$2.parsers[k]
              if (fn) {
                  val = fn.call(this, val)
              }
          }
          return val
      }
  
      var updateView = {
          input: function input() {
              //处理单个value值处理
              this.node.props.value = this.value + ''
              this.dom.value = this.value
          },
          updateChecked: function updateChecked(vdom, checked) {
              if (vdom.dom) {
                  vdom.dom.defaultChecked = vdom.dom.checked = checked
              }
          },
          radio: function radio() {
              //处理单个checked属性
              var node = this.node
              var nodeValue = node.props.value
              var checked
              if (this.isChecked) {
                  checked = !!this.value
              } else {
                  checked = this.value + '' === nodeValue
              }
              node.props.checked = checked
              updateView.updateChecked(node, checked)
          },
          checkbox: function checkbox() {
              //处理多个checked属性
              var node = this.node
              var props = node.props
              var value = props.value + ''
              var values = [].concat(this.value)
              var checked = values.some(function (el) {
                  return el + '' === value
              })
  
              props.defaultChecked = props.checked = checked
              updateView.updateChecked(node, checked)
          },
          select: function select() {
              //处理子级的selected属性
              var a = Array.isArray(this.value) ? this.value.map(String) : this.value + ''
              lookupOption(this.node, a)
          },
          contenteditable: function contenteditable() {
              //处理单个innerHTML 
  
              var vnodes = fromString(this.value)
              var fragment = createFragment()
              for (var i = 0, el; el = vnodes[i++];) {
                  var child = avalon$2.vdom(el, 'toDOM')
                  fragment.appendChild(child)
              }
              avalon$2.clearHTML(this.dom).appendChild(fragment)
              var list = this.node.children
              list.length = 0
              Array.prototype.push.apply(list, vnodes)
  
              this.duplexCb.call(this.dom)
          }
      }
  
      var updateDataActions = {
          input: function input(prop) {
              //处理单个value值处理
              var field = this
              prop = prop || 'value'
              var dom = field.dom
              var rawValue = dom[prop]
              var parsedValue = field.parseValue(rawValue)
  
              //有时候parse后一致,vm不会改变,但input里面的值
              field.value = rawValue
              field.setValue(parsedValue)
              duplexCb(field)
              var pos = field.pos
              /* istanbul ignore if */
              if (dom.caret) {
                  field.setCaret(dom, pos)
              }
              //vm.aaa = '1234567890'
              //处理 <input ms-duplex='@aaa|limitBy(8)'/>{{@aaa}} 这种格式化同步不一致的情况 
          },
          radio: function radio() {
              var field = this
              if (field.isChecked) {
                  var val = !field.value
                  field.setValue(val)
                  duplexCb(field)
              } else {
                  updateDataActions.input.call(field)
                  field.value = NaN
              }
          },
          checkbox: function checkbox() {
              var field = this
              var array = field.value
              if (!Array.isArray(array)) {
                  avalon$2.warn('ms-duplex应用于checkbox上要对应一个数组')
                  array = [array]
              }
              var method = field.dom.checked ? 'ensure' : 'remove'
              if (array[method]) {
                  var val = field.parseValue(field.dom.value)
                  array[method](val)
                  duplexCb(field)
              }
              this.__test__ = array
          },
          select: function select() {
              var field = this
              var val = avalon$2(field.dom).val() //字符串或字符串数组
              if (val + '' !== this.value + '') {
                  if (Array.isArray(val)) {
                      //转换布尔数组或其他
                      val = val.map(function (v) {
                          return field.parseValue(v)
                      })
                  } else {
                      val = field.parseValue(val)
                  }
                  field.setValue(val)
                  duplexCb(field)
              }
          },
          contenteditable: function contenteditable() {
              updateDataActions.input.call(this, 'innerHTML')
          }
      }
  
      function duplexCb(field) {
          if (field.userCb) {
              field.userCb.call(field.vm, {
                  type: 'changed',
                  target: field.dom
              })
          }
      }
  
      function updateDataHandle(event) {
          var elem = this
          var field = elem._ms_duplex_
          if (elem.composing) {
              //防止onpropertychange引发爆栈
              return
          }
          if (elem.value === field.value) {
              return
          }
          /* istanbul ignore if*/
          if (elem.caret) {
              try {
                  var pos = field.getCaret(elem)
                  field.pos = pos
              } catch (e) {}
          }
          /* istanbul ignore if*/
          if (field.debounceTime > 4) {
              var timestamp = new Date()
              var left = timestamp - field.time || 0
              field.time = timestamp
              /* istanbul ignore if*/
              if (left >= field.debounceTime) {
                  updateDataActions[field.dtype].call(field)
                  /* istanbul ignore else*/
              } else {
                  clearTimeout(field.debounceID)
                  field.debounceID = setTimeout(function () {
                      updateDataActions[field.dtype].call(field)
                  }, left)
              }
          } else {
              updateDataActions[field.dtype].call(field)
          }
      }
  
      /* 
       * 通过绑定事件同步vmodel
       * 总共有三种方式同步视图
       * 1. 各种事件 input, change, click, propertychange, keydown...
       * 2. value属性重写
       * 3. 定时器轮询
       */
      function updateDataEvents(dom, data) {
          var events = {}
          //添加需要监听的事件
          switch (data.dtype) {
              case 'radio':
              case 'checkbox':
                  events.click = updateDataHandle
                  break
              case 'select':
                  events.change = updateDataHandle
                  break
              case 'contenteditable':
                  if (data.isChanged) {
                      events.blur = updateDataHandle
                  } else {
  
                      if (window$1.webkitURL) {
                          // http://code.metager.de/source/xref/WebKit/LayoutTests/fast/events/
                          // https://bugs.webkit.org/show_bug.cgi?id=110742
                          events.webkitEditableContentChanged = updateDataHandle
                      } else if (window$1.MutationEvent) {
                          events.DOMCharacterDataModified = updateDataHandle
                      }
                      events.input = updateDataHandle
                  }
                  break
              case 'input':
                  if (data.isChanged) {
                      events.change = updateDataHandle
                  } else {
                      events.input = updateDataHandle
  
                      //https://github.com/RubyLouvre/avalon/issues/1368#issuecomment-220503284
                      events.compositionstart = openComposition
                      events.compositionend = closeComposition
                      if (avalon$2.msie) {
                          events.keyup = updateModelKeyDown
                      }
                  }
                  break
          }
  
          if (/password|text/.test(dom.type)) {
              events.focus = openCaret //判定是否使用光标修正功能 
              events.blur = closeCaret
              data.getCaret = getCaret
              data.setCaret = setCaret
          }
          for (var name in events) {
              avalon$2.bind(dom, name, events[name])
          }
      }
  
      /* istanbul ignore next */
      function updateModelKeyDown(e) {
          var key = e.keyCode
          // ignore
          //    command            modifiers                   arrows
          if (key === 91 || 15 < key && key < 19 || 37 <= key && key <= 40) return
          updateDataHandle.call(this, e)
      }
      /* istanbul ignore next */
      function openCaret() {
          this.caret = true
      }
      /* istanbul ignore next */
      function closeCaret() {
          this.caret = false
      }
      function openComposition() {
          this.composing = true
      }
      /* istanbul ignore next */
      function closeComposition(e) {
          this.composing = false
          var elem = this
          setTimeout(function () {
              updateDataHandle.call(elem, e)
          }, 0)
      }
  
      getShortID(openCaret)
      getShortID(closeCaret)
      getShortID(openComposition)
      getShortID(closeComposition)
      getShortID(updateModelKeyDown)
      getShortID(updateDataHandle)
  
      /* istanbul ignore next */
      function getCaret(field) {
          var start = NaN
          if (field.setSelectionRange) {
              start = field.selectionStart
          }
          return start
      }
      /* istanbul ignore next */
      function setCaret(field, pos) {
          if (!field.value || field.readOnly) return
          field.selectionStart = pos
          field.selectionEnd = pos
      }
  
      avalon$2.directive('duplex', {
          priority: 2000,
          beforeInit: duplexBeforeInit,
          init: duplexInit,
          diff: duplexDiff,
          update: function update(vdom, value) {
              var dom = vdom.dom
              if (!this.dom) {
                  this.duplexCb = updateDataHandle
                  this.dom = dom
                  dom._ms_duplex_ = this
                  //绑定事件
                  updateDataEvents(dom, this)
                  //添加验证
                  duplexValidate(dom, vdom)
              }
              updateView[this.dtype].call(this)
          }
      })
  
      avalon$2.directive('rules', {
          diff: function diff(rules) {
              if (isObject(rules)) {
                  var vdom = this.node
                  vdom.rules = platform.toJson(rules)
                  if (vdom.duplex) {
                      vdom.duplex.rules = vdom.rules
                  }
                  return true
              }
          }
      })
      function isRegExp(value) {
          return avalon$2.type(value) === 'regexp'
      }
      var rmail = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/i
      var rurl = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/
      function isCorrectDate(value) {
          if (typeof value === "string" && value) {
              //是字符串但不能是空字符
              var arr = value.split("-") //可以被-切成3份，并且第1个是4个字符
              if (arr.length === 3 && arr[0].length === 4) {
                  var year = ~~arr[0] //全部转换为非负整数
                  var month = ~~arr[1] - 1
                  var date = ~~arr[2]
                  var d = new Date(year, month, date)
                  return d.getFullYear() === year && d.getMonth() === month && d.getDate() === date
              }
          }
          return false
      }
      //https://github.com/adform/validator.js/blob/master/validator.js
      avalon$2.shadowCopy(avalon$2.validators, {
          pattern: {
              message: '必须匹配{{pattern}}这样的格式',
              get: function get(value, field, next) {
                  var elem = field.dom
                  var data = field.data
                  if (!isRegExp(data.pattern)) {
                      var h5pattern = elem.getAttribute("pattern")
                      data.pattern = new RegExp('^(?:' + h5pattern + ')$')
                  }
                  next(data.pattern.test(value))
                  return value
              }
          },
          digits: {
              message: '必须整数',
              get: function get(value, field, next) {
                  //整数
                  next(/^\-?\d+$/.test(value))
                  return value
              }
          },
          number: {
              message: '必须数字',
              get: function get(value, field, next) {
                  //数值
                  next(!!value && isFinite(value)) // isFinite('') --> true
                  return value
              }
          },
          norequired: {
              message: '',
              get: function get(value, field, next) {
                  next(true)
                  return value
              }
          },
          required: {
              message: '必须填写',
              get: function get(value, field, next) {
                  next(value !== '')
                  return value
              }
          },
          equalto: {
              message: '密码输入不一致',
              get: function get(value, field, next) {
                  var id = String(field.data.equalto)
                  var other = avalon$2(document.getElementById(id)).val() || ""
                  next(value === other)
                  return value
              }
          },
          date: {
              message: '日期格式不正确',
              get: function get(value, field, next) {
                  var data = field.data
                  if (isRegExp(data.date)) {
                      next(data.date.test(value))
                  } else {
                      next(isCorrectDate(value))
                  }
                  return value
              }
          },
          url: {
              message: 'URL格式不正确',
              get: function get(value, field, next) {
                  next(rurl.test(value))
                  return value
              }
          },
          email: {
              message: 'email格式不正确',
              get: function get(value, field, next) {
                  next(rmail.test(value))
                  return value
              }
          },
          minlength: {
              message: '最少输入{{minlength}}个字',
              get: function get(value, field, next) {
                  var num = parseInt(field.data.minlength, 10)
                  next(value.length >= num)
                  return value
              }
          },
          maxlength: {
              message: '最多输入{{maxlength}}个字',
              get: function get(value, field, next) {
                  var num = parseInt(field.data.maxlength, 10)
                  next(value.length <= num)
                  return value
              }
          },
          min: {
              message: '输入值不能小于{{min}}',
              get: function get(value, field, next) {
                  var num = parseInt(field.data.min, 10)
                  next(parseFloat(value) >= num)
                  return value
              }
          },
          max: {
              message: '输入值不能大于{{max}}',
              get: function get(value, field, next) {
                  var num = parseInt(field.data.max, 10)
                  next(parseFloat(value) <= num)
                  return value
              }
          },
          chs: {
              message: '必须是中文字符',
              get: function get(value, field, next) {
                  next(/^[\u4e00-\u9fa5]+$/.test(value))
                  return value
              }
          }
      })
  
      var valiDir = avalon$2.directive('validate', {
          diff: function diff(validator) {
              var vdom = this.node
              if (vdom.validator) {
                  return
              }
              if (isObject(validator)) {
                  //注意，这个Form标签的虚拟DOM有两个验证对象
                  //一个是vmValidator，它是用户VM上的那个原始子对象，也是一个VM
                  //一个是validator，它是vmValidator.$model， 这是为了防止IE6－8添加子属性时添加的hack
                  //也可以称之为safeValidate
                  vdom.vmValidator = validator
                  validator = platform.toJson(validator)
  
                  vdom.validator = validator
                  for (var name in valiDir.defaults) {
                      if (!validator.hasOwnProperty(name)) {
                          validator[name] = valiDir.defaults[name]
                      }
                  }
                  validator.fields = validator.fields || []
                  return true
              }
          },
          update: function update(vdom) {
              var validator = vdom.validator
              var dom = vdom.dom
              validator.dom = dom
              dom._ms_validate_ = validator
  
              //为了方便用户手动执行验证，我们需要为原始vmValidate上添加一个onManual方法
              var v = vdom.vmValidator
              try {
                  v.onManual = onManual
              } catch (e) {}
              delete vdom.vmValidator
  
              dom.setAttribute('novalidate', 'novalidate')
              function onManual() {
                  valiDir.validateAll.call(validator, validator.onValidateAll)
              }
              /* istanbul ignore if */
              if (validator.validateAllInSubmit) {
                  avalon$2.bind(dom, 'submit', function (e) {
                      e.preventDefault()
                      onManual()
                  })
              }
              /* istanbul ignore if */
              if (typeof validator.onInit === 'function') {
                  //vmodels是不包括vmodel的
                  validator.onInit.call(dom, {
                      type: 'init',
                      target: dom,
                      validator: validator
                  })
              }
          },
          validateAll: function validateAll(callback) {
              var validator = this
              var fn = typeof callback === 'function' ? callback : validator.onValidateAll
              var promises = validator.fields.filter(function (field) {
                  var el = field.dom
                  return el && !el.disabled && validator.dom.contains(el)
              }).map(function (field) {
                  return valiDir.validate(field, true)
              })
              var uniq = {}
              return Promise.all(promises).then(function (array) {
                  var reasons = array.concat.apply([], array)
                  if (validator.deduplicateInValidateAll) {
  
                      reasons = reasons.filter(function (reason) {
                          var el = reason.element
                          var uuid = el.uniqueID || (el.uniqueID = setTimeout('1'))
  
                          if (uniq[uuid]) {
                              return false
                          } else {
                              return uniq[uuid] = true
                          }
                      })
                  }
                  fn.call(validator.dom, reasons) //这里只放置未通过验证的组件
              })
          },
          addField: function addField(field) {
              var validator = this
              var node = field.dom
              /* istanbul ignore if */
              if (validator.validateInKeyup && !field.isChanged && !field.debounceTime) {
                  avalon$2.bind(node, 'keyup', function (e) {
                      validator.validate(field, 0, e)
                  })
              }
              /* istanbul ignore if */
              if (validator.validateInBlur) {
                  avalon$2.bind(node, 'blur', function (e) {
                      validator.validate(field, 0, e)
                  })
              }
              /* istanbul ignore if */
              if (validator.resetInFocus) {
                  avalon$2.bind(node, 'focus', function (e) {
                      validator.onReset.call(node, e, field)
                  })
              }
          },
          validate: function validate(field, isValidateAll, event) {
              var promises = []
              var value = field.value
              var elem = field.dom
  
              /* istanbul ignore if */
              if (typeof Promise !== 'function') {
                  //avalon-promise不支持phantomjs
                  avalon$2.wain('please npm install es6-promise or bluebird')
              }
              /* istanbul ignore if */
              if (elem.disabled) return
              var rules = field.rules
              var ngs = [],
                  isOk = true
              if (!(rules.norequired && value === '')) {
                  for (var ruleName in rules) {
                      var ruleValue = rules[ruleName]
                      if (ruleValue === false) continue
                      var hook = avalon$2.validators[ruleName]
                      var resolve
                      promises.push(new Promise(function (a, b) {
                          resolve = a
                      }))
                      var next = function next(a) {
                          var reason = {
                              element: elem,
                              data: field.data,
                              message: elem.getAttribute('data-' + ruleName + '-message') || elem.getAttribute('data-message') || hook.message,
                              validateRule: ruleName,
                              getMessage: getMessage
                          }
                          if (a) {
                              resolve(true)
                          } else {
                              isOk = false
                              ngs.push(reason)
                              resolve(false)
                          }
                      }
                      field.data = {}
                      field.data[ruleName] = ruleValue
                      hook.get(value, field, next)
                  }
              }
  
              //如果promises不为空，说明经过验证拦截器
              return Promise.all(promises).then(function (array) {
                  if (!isValidateAll) {
                      var validator = field.validator
                      if (isOk) {
                          validator.onSuccess.call(elem, [{
                              data: field.data,
                              element: elem
                          }], event)
                      } else {
                          validator.onError.call(elem, ngs, event)
                      }
                      validator.onComplete.call(elem, ngs, event)
                  }
                  return ngs
              })
          }
      })
  
      var rformat = /\\?{{([^{}]+)\}}/gm
  
      function getMessage() {
          var data = this.data || {}
          return this.message.replace(rformat, function (_, name) {
              return data[name] == null ? '' : data[name]
          })
      }
      valiDir.defaults = {
          validate: valiDir.validate,
          addField: valiDir.addField, //供内部使用,收集此元素底下的所有ms-duplex的域对象
          onError: avalon$2.noop,
          onSuccess: avalon$2.noop,
          onComplete: avalon$2.noop,
          onManual: avalon$2.noop,
          onReset: avalon$2.noop,
          onValidateAll: avalon$2.noop,
          validateInBlur: true, //@config {Boolean} true，在blur事件中进行验证,触发onSuccess, onError, onComplete回调
          validateInKeyup: true, //@config {Boolean} true，在keyup事件中进行验证,触发onSuccess, onError, onComplete回调
          validateAllInSubmit: true, //@config {Boolean} true，在submit事件中执行onValidateAll回调
          resetInFocus: true, //@config {Boolean} true，在focus事件中执行onReset回调,
          deduplicateInValidateAll: false //@config {Boolean} false，在validateAll回调中对reason数组根据元素节点进行去重
      }
  
      /**
       * 一个directive装饰器
       * @returns {directive}
       */
      // DirectiveDecorator(scope, binding, vdom, this)
      // Decorator(vm, options, callback)
      function Directive(vm, binding, vdom, render) {
          var type = binding.type
          var decorator = avalon$2.directives[type]
          if (inBrowser) {
              var dom = avalon$2.vdom(vdom, 'toDOM')
              if (dom.nodeType === 1) {
                  dom.removeAttribute(binding.attrName)
              }
              vdom.dom = dom
          }
          var callback = decorator.update ? function (value) {
              if (!render.mount && /css|visible|duplex/.test(type)) {
                  render.callbacks.push(function () {
                      decorator.update.call(directive$$1, directive$$1.node, value)
                  })
              } else {
                  decorator.update.call(directive$$1, directive$$1.node, value)
              }
          } : avalon$2.noop
          for (var key in decorator) {
              binding[key] = decorator[key]
          }
          binding.node = vdom
          var directive$$1 = new Action(vm, binding, callback)
          if (directive$$1.init) {
              //这里可能会重写node, callback, type, name
              directive$$1.init()
          }
          directive$$1.update()
          return directive$$1
      }
  
      var eventMap = avalon$2.oneObject('animationend,blur,change,input,' + 'click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,' + 'mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit', 'on')
      function parseAttributes(dirs, tuple) {
          var node = tuple[0],
              uniq = {},
              bindings = []
          var hasIf = false
          for (var name in dirs) {
              var value = dirs[name]
              var arr = name.split('-')
              // ms-click
              if (name in node.props) {
                  var attrName = name
              } else {
                  attrName = ':' + name.slice(3)
              }
              if (eventMap[arr[1]]) {
                  arr.splice(1, 0, 'on')
              }
              //ms-on-click
              if (arr[1] === 'on') {
                  arr[3] = parseFloat(arr[3]) || 0
              }
  
              var type = arr[1]
              if (type === 'controller' || type === 'important') continue
              if (directives[type]) {
  
                  var binding = {
                      type: type,
                      param: arr[2],
                      attrName: attrName,
                      name: arr.join('-'),
                      expr: value,
                      priority: directives[type].priority || type.charCodeAt(0) * 100
                  }
                  if (type === 'if') {
                      hasIf = true
                  }
                  if (type === 'on') {
                      binding.priority += arr[3]
                  }
                  if (!uniq[binding.name]) {
                      uniq[binding.name] = value
                      bindings.push(binding)
                      if (type === 'for') {
                          return [avalon$2.mix(binding, tuple[3])]
                      }
                  }
              }
          }
          bindings.sort(byPriority)
  
          if (hasIf) {
              var ret = []
              for (var i = 0, el; el = bindings[i++];) {
                  ret.push(el)
                  if (el.type === 'if') {
                      return ret
                  }
              }
          }
          return bindings
      }
      function byPriority(a, b) {
          return a.priority - b.priority
      }
  
      var rimprovePriority = /[+-\?]/
      var rinnerValue = /__value__\)$/
      function parseInterpolate(dir) {
          var rlineSp = /\n\r?/g
          var str = dir.nodeValue.trim().replace(rlineSp, '')
          var tokens = []
          do {
              //aaa{{@bbb}}ccc
              var index = str.indexOf(config.openTag)
              index = index === -1 ? str.length : index
              var value = str.slice(0, index)
              if (/\S/.test(value)) {
                  tokens.push(avalon$2.quote(avalon$2._decode(value)))
              }
              str = str.slice(index + config.openTag.length)
              if (str) {
                  index = str.indexOf(config.closeTag)
                  var value = str.slice(0, index)
                  var expr = avalon$2.unescapeHTML(value)
                  if (/\|\s*\w/.test(expr)) {
                      //如果存在过滤器，优化干掉
                      var arr = addScope(expr, 'expr')
                      if (arr[1]) {
                          expr = arr[1].replace(rinnerValue, arr[0] + ')')
                      }
                  }
                  if (rimprovePriority) {
                      expr = '(' + expr + ')'
                  }
                  tokens.push(expr)
  
                  str = str.slice(index + config.closeTag.length)
              }
          } while (str.length)
          return [{
              expr: tokens.join('+'),
              name: 'expr',
              type: 'expr'
          }]
      }
  
      function getChildren(arr) {
          var count = 0
          for (var i = 0, el; el = arr[i++];) {
              if (el.nodeName === '#document-fragment') {
                  count += getChildren(el.children)
              } else {
                  count += 1
              }
          }
          return count
      }
      function groupTree(parent, children) {
          children && children.forEach(function (vdom) {
              if (!vdom) return
              var vlength = vdom.children && getChildren(vdom.children)
              if (vdom.nodeName === '#document-fragment') {
                  var dom = createFragment()
              } else {
                  dom = avalon$2.vdom(vdom, 'toDOM')
                  var domlength = dom.childNodes && dom.childNodes.length
                  if (domlength && vlength && domlength > vlength) {
                      if (!appendChildMayThrowError[dom.nodeName]) {
                          avalon$2.clearHTML(dom)
                      }
                  }
              }
              if (vlength) {
                  groupTree(dom, vdom.children)
                  if (vdom.nodeName === 'select') {
                      var values = []
                      getSelectedValue(vdom, values)
                      lookupOption(vdom, values)
                  }
              }
              //高级版本可以尝试 querySelectorAll
  
              try {
                  if (!appendChildMayThrowError[parent.nodeName]) {
                      parent.appendChild(dom)
                  }
              } catch (e) {}
          })
      }
  
      function dumpTree(elem) {
          var firstChild
          while (firstChild = elem.firstChild) {
              if (firstChild.nodeType === 1) {
                  dumpTree(firstChild)
              }
              elem.removeChild(firstChild)
          }
      }
  
      function getRange(childNodes, node) {
          var i = childNodes.indexOf(node) + 1
          var deep = 1,
              nodes = [],
              end
          nodes.start = i
          while (node = childNodes[i++]) {
              nodes.push(node)
              if (node.nodeName === '#comment') {
                  if (startWith(node.nodeValue, 'ms-for:')) {
                      deep++
                  } else if (node.nodeValue === 'ms-for-end:') {
                      deep--
                      if (deep === 0) {
                          end = node
                          nodes.pop()
                          break
                      }
                  }
              }
          }
          nodes.end = end
          return nodes
      }
  
      function startWith(long, short) {
          return long.indexOf(short) === 0
      }
  
      var appendChildMayThrowError = {
          '#text': 1,
          '#comment': 1,
          script: 1,
          style: 1,
          noscript: 1
      }
  
      /**
       * 生成一个渲染器,并作为它第一个遇到的ms-controller对应的VM的$render属性
       * @param {String|DOM} node
       * @param {ViewModel|Undefined} vm
       * @param {Function|Undefined} beforeReady
       * @returns {Render}
       */
      avalon$2.scan = function (node, vm, beforeReady) {
          return new Render(node, vm, beforeReady || avalon$2.noop)
      }
  
      /**
       * avalon.scan 的内部实现
       */
      function Render(node, vm, beforeReady) {
          this.root = node //如果传入的字符串,确保只有一个标签作为根节点
          this.vm = vm
          this.beforeReady = beforeReady
          this.bindings = [] //收集待加工的绑定属性
          this.callbacks = []
          this.directives = []
          this.init()
      }
  
      Render.prototype = {
          /**
           * 开始扫描指定区域
           * 收集绑定属性
           * 生成指令并建立与VM的关联
           */
          init: function init() {
              var vnodes
              if (this.root && this.root.nodeType > 0) {
                  vnodes = fromDOM(this.root) //转换虚拟DOM
                  //将扫描区域的每一个节点与其父节点分离,更少指令对DOM操作时,对首屏输出造成的频繁重绘
                  dumpTree(this.root)
              } else if (typeof this.root === 'string') {
                  vnodes = fromString(this.root) //转换虚拟DOM
              } else {
                  return avalon$2.warn('avalon.scan first argument must element or HTML string')
              }
  
              this.root = vnodes[0]
              this.vnodes = vnodes
              this.scanChildren(vnodes, this.vm, true)
          },
          scanChildren: function scanChildren(children, scope, isRoot) {
              for (var i = 0; i < children.length; i++) {
                  var vdom = children[i]
                  switch (vdom.nodeName) {
                      case '#text':
                          scope && this.scanText(vdom, scope)
                          break
                      case '#comment':
                          scope && this.scanComment(vdom, scope, children)
                          break
                      case '#document-fragment':
                          this.scanChildren(vdom.children, scope, false)
                          break
                      default:
                          this.scanTag(vdom, scope, children, false)
                          break
                  }
              }
              if (isRoot) {
                  this.complete()
              }
          },
  
  
          /**
           * 从文本节点获取指令
           * @param {type} vdom 
           * @param {type} scope
           * @returns {undefined}
           */
          scanText: function scanText(vdom, scope) {
              if (config.rexpr.test(vdom.nodeValue)) {
                  this.bindings.push([vdom, scope, {
                      nodeValue: vdom.nodeValue
                  }])
              }
          },
  
  
          /**
           * 从注释节点获取指令
           * @param {type} vdom 
           * @param {type} scope
           * @param {type} parentChildren
           * @returns {undefined}
           */
          scanComment: function scanComment(vdom, scope, parentChildren) {
              if (startWith(vdom.nodeValue, 'ms-for:')) {
                  this.getForBinding(vdom, scope, parentChildren)
              }
          },
  
  
          /**
           * 从元素节点的nodeName与属性中获取指令
           * @param {type} vdom 
           * @param {type} scope
           * @param {type} parentChildren
           * @param {type} isRoot 用于执行complete方法
           * @returns {undefined}
           */
          scanTag: function scanTag(vdom, scope, parentChildren, isRoot) {
              var dirs = {},
                  attrs = vdom.props,
                  hasDir,
                  hasFor
              for (var attr in attrs) {
                  var value = attrs[attr]
                  var oldName = attr
                  if (attr.charAt(0) === ':') {
                      attr = 'ms-' + attr.slice(1)
                  }
                  if (startWith(attr, 'ms-')) {
                      dirs[attr] = value
                      var type = attr.match(/\w+/g)[1]
                      type = eventMap[type] || type
                      if (!directives[type]) {
                          avalon$2.warn(attr + ' has not registered!')
                      }
                      hasDir = true
                  }
                  if (attr === 'ms-for') {
                      hasFor = value
                      delete attrs[oldName]
                  }
              }
              var $id = dirs['ms-important'] || dirs['ms-controller']
              if ($id) {
                  /**
                   * 后端渲染
                   * serverTemplates后端给avalon添加的对象,里面都是模板,
                   * 将原来后端渲染好的区域再还原成原始样子,再被扫描
                   */
                  var templateCaches = avalon$2.serverTemplates
                  var temp = templateCaches && templateCaches[$id]
                  if (temp) {
                      avalon$2.log('前端再次渲染后端传过来的模板')
                      var node = fromString(tmpl)[0]
                      for (var i in node) {
                          vdom[i] = node[i]
                      }
                      delete templateCaches[$id]
                      this.scanTag(vdom, scope, parentChildren, isRoot)
                      return
                  }
                  //推算出指令类型
                  var type = dirs['ms-important'] === $id ? 'important' : 'controller'
                  //推算出用户定义时属性名,是使用ms-属性还是:属性
                  var attrName = 'ms-' + type in attrs ? 'ms-' + type : ':' + type
  
                  if (inBrowser) {
                      delete attrs[attrName]
                  }
                  var dir = directives[type]
                  scope = dir.getScope.call(this, $id, scope)
                  if (!scope) {
                      return
                  } else {
                      var clazz = attrs['class']
                      if (clazz) {
                          attrs['class'] = (' ' + clazz + ' ').replace(' ms-controller ', '').trim()
                      }
                  }
                  var render = this
                  scope.$render = render
                  this.callbacks.push(function () {
                      //用于删除ms-controller
                      dir.update.call(render, vdom, attrName, $id)
                  })
              }
              if (hasFor) {
                  if (vdom.dom) {
                      vdom.dom.removeAttribute(oldName)
                  }
                  return this.getForBindingByElement(vdom, scope, parentChildren, hasFor)
              }
  
              if (/^ms\-/.test(vdom.nodeName)) {
                  attrs.is = vdom.nodeName
              }
  
              if (attrs['is']) {
                  if (!dirs['ms-widget']) {
                      dirs['ms-widget'] = '{}'
                  }
                  hasDir = true
              }
              if (hasDir) {
                  this.bindings.push([vdom, scope, dirs])
              }
              var children = vdom.children
              //如果存在子节点,并且不是容器元素(script, stype, textarea, xmp...)
              if (!orphanTag[vdom.nodeName] && children && children.length && !delayCompileNodes(dirs)) {
                  this.scanChildren(children, scope, false)
              }
          },
  
  
          /**
           * 将绑定属性转换为指令
           * 执行各种回调与优化指令
           * @returns {undefined}
           */
          complete: function complete() {
              this.yieldDirectives()
              this.beforeReady()
              if (inBrowser) {
                  var root$$1 = this.root
                  if (inBrowser) {
                      var rootDom = avalon$2.vdom(root$$1, 'toDOM')
                      groupTree(rootDom, root$$1.children)
                  }
              }
  
              this.mount = true
              var fn
              while (fn = this.callbacks.pop()) {
                  fn()
              }
              this.optimizeDirectives()
          },
  
  
          /**
           * 将收集到的绑定属性进行深加工,最后转换指令
           * @returns {Array<tuple>}
           */
          yieldDirectives: function yieldDirectives() {
              var tuple
              while (tuple = this.bindings.shift()) {
                  var vdom = tuple[0],
                      scope = tuple[1],
                      dirs = tuple[2],
                      bindings = []
                  if ('nodeValue' in dirs) {
                      bindings = parseInterpolate(dirs)
                  } else if (!('ms-skip' in dirs)) {
                      bindings = parseAttributes(dirs, tuple)
                  }
                  for (var i = 0, binding; binding = bindings[i++];) {
                      var dir = directives[binding.type]
                      if (!inBrowser && /on|duplex|active|hover/.test(binding.type)) {
                          continue
                      }
                      if (dir.beforeInit) {
                          dir.beforeInit.call(binding)
                      }
  
                      var directive$$1 = new Directive(scope, binding, vdom, this)
                      this.directives.push(directive$$1)
                  }
              }
          },
  
  
          /**
           * 修改指令的update与callback方法,让它们以后执行时更加高效
           * @returns {undefined}
           */
          optimizeDirectives: function optimizeDirectives() {
              for (var i = 0, el; el = this.directives[i++];) {
                  el.callback = directives[el.type].update
                  el.update = newUpdate
                  el._isScheduled = false
              }
          },
  
          update: function update() {
              for (var i = 0, el; el = this.directives[i++];) {
                  el.update()
              }
          },
  
          /**
           * 销毁所有指令
           * @returns {undefined}
           */
          dispose: function dispose() {
              var list = this.directives || []
              for (var i = 0, el; el = list[i++];) {
                  el.dispose()
              }
              //防止其他地方的this.innerRender && this.innerRender.dispose报错
              for (var _i8 in this) {
                  if (_i8 !== 'dispose') delete this[_i8]
              }
          },
  
  
          /**
           * 将循环区域转换为for指令
           * @param {type} begin 注释节点
           * @param {type} scope
           * @param {type} parentChildren
           * @param {type} userCb 循环结束回调
           * @returns {undefined}
           */
          getForBinding: function getForBinding(begin, scope, parentChildren, userCb) {
              var expr = begin.nodeValue.replace('ms-for:', '').trim()
              begin.nodeValue = 'ms-for:' + expr
              var nodes = getRange(parentChildren, begin)
              var end = nodes.end
              var fragment = avalon$2.vdom(nodes, 'toHTML')
              parentChildren.splice(nodes.start, nodes.length)
              begin.props = {}
              this.bindings.push([begin, scope, {
                  'ms-for': expr
              }, {
                  begin: begin,
                  end: end,
                  expr: expr,
                  userCb: userCb,
                  fragment: fragment,
                  parentChildren: parentChildren
              }])
          },
  
  
          /**
           * 在带ms-for元素节点旁添加两个注释节点,组成循环区域
           * @param {type} vdom
           * @param {type} scope
           * @param {type} parentChildren
           * @param {type} expr
           * @returns {undefined}
           */
          getForBindingByElement: function getForBindingByElement(vdom, scope, parentChildren, expr) {
              var index = parentChildren.indexOf(vdom) //原来带ms-for的元素节点
              var props = vdom.props
              var begin = {
                  nodeName: '#comment',
                  nodeValue: 'ms-for:' + expr
              }
              if (props.slot) {
                  begin.slot = props.slot
                  delete props.slot
              }
              var end = {
                  nodeName: '#comment',
                  nodeValue: 'ms-for-end:'
              }
              parentChildren.splice(index, 1, begin, vdom, end)
              this.getForBinding(begin, scope, parentChildren, props['data-for-rendered'])
          }
      }
      var viewID
  
      function newUpdate() {
          var oldVal = this.beforeUpdate()
          var newVal = this.value = this.get()
          if (this.callback && this.diff(newVal, oldVal)) {
              this.callback(this.node, this.value)
              var vm = this.vm
              var $render = vm.$render
              var list = vm.$events['onViewChange']
              /* istanbul ignore if */
              if (list && $render && $render.root && !avalon$2.viewChanging) {
                  if (viewID) {
                      clearTimeout(viewID)
                      viewID = null
                  }
                  viewID = setTimeout(function () {
                      list.forEach(function (el) {
                          el.callback.call(vm, {
                              type: 'viewchange',
                              target: $render.root,
                              vmodel: vm
                          })
                      })
                  })
              }
          }
          this._isScheduled = false
      }
  
      var events = 'onInit,onReady,onViewChange,onDispose,onEnter,onLeave'
      var componentEvents = avalon$2.oneObject(events)
  
      function toObject(value) {
          var value = platform.toJson(value)
          if (Array.isArray(value)) {
              var v = {}
              value.forEach(function (el) {
                  el && avalon$2.shadowCopy(v, el)
              })
              return v
          }
          return value
      }
      var componentQueue = []
      avalon$2.directive('widget', {
          delay: true,
          priority: 4,
          deep: true,
          init: function init() {
              //cached属性必须定义在组件容器里面,不是template中
              var vdom = this.node
              this.cacheVm = !!vdom.props.cached
              if (vdom.dom && vdom.nodeName === '#comment') {
                  var comment = vdom.dom
              }
              var oldValue = this.getValue()
              var value = toObject(oldValue)
              //外部VM与内部VM
              // ＝＝＝创建组件的VM＝＝BEGIN＝＝＝
              var is = vdom.props.is || value.is
              this.is = is
              var component = avalon$2.components[is]
              //外部传入的总大于内部
              if (!('fragment' in this)) {
                  if (!vdom.isVoidTag) {
                      //提取组件容器内部的东西作为模板
                      var text = vdom.children[0]
                      if (text && text.nodeValue) {
                          this.fragment = text.nodeValue
                      } else {
                          this.fragment = avalon$2.vdom(vdom.children, 'toHTML')
                      }
                  } else {
                      this.fragment = false
                  }
              }
              //如果组件还没有注册，那么将原元素变成一个占位用的注释节点
              if (!component) {
                  this.readyState = 0
                  vdom.nodeName = '#comment'
                  vdom.nodeValue = 'unresolved component placeholder'
                  delete vdom.dom
                  avalon$2.Array.ensure(componentQueue, this)
                  return
              }
              this.readyState = 1
              //如果是非空元素，比如说xmp, ms-*, template
              var id = value.id || value.$id
              var hasCache = avalon$2.vmodels[id]
              var fromCache = false
  
              if (hasCache) {
                  comVm = hasCache
                  this.comVm = comVm
                  replaceRoot(this, comVm.$render)
                  fromCache = true
              } else {
                  var comVm = createComponentVm(component, value, is)
                  fireComponentHook(comVm, vdom, 'Init')
                  this.comVm = comVm
  
                  // ＝＝＝创建组件的VM＝＝END＝＝＝
                  var innerRender = avalon$2.scan(component.template, comVm)
                  comVm.$render = innerRender
                  replaceRoot(this, innerRender)
                  var nodesWithSlot = []
                  var directives$$1 = []
                  if (this.fragment || component.soleSlot) {
                      var curVM = this.fragment ? this.vm : comVm
                      var curText = this.fragment || '{{##' + component.soleSlot + '}}'
                      var childBoss = avalon$2.scan('<div>' + curText + '</div>', curVM, function () {
                          nodesWithSlot = this.root.children
                      })
                      directives$$1 = childBoss.directives
                      this.childBoss = childBoss
                      for (var i in childBoss) {
                          delete childBoss[i]
                      }
                  }
                  Array.prototype.push.apply(innerRender.directives, directives$$1)
  
                  var arraySlot = [],
                      objectSlot = {}
                  //从用户写的元素内部 收集要移动到 新创建的组件内部的元素
                  if (component.soleSlot) {
                      arraySlot = nodesWithSlot
                  } else {
                      nodesWithSlot.forEach(function (el, i) {
                          //要求带slot属性
                          if (el.slot) {
                              var nodes = getRange(nodesWithSlot, el)
                              nodes.push(nodes.end)
                              nodes.unshift(el)
                              objectSlot[el.slot] = nodes
                          } else if (el.props) {
                              var name = el.props.slot
                              if (name) {
                                  delete el.props.slot
                                  if (Array.isArray(objectSlot[name])) {
                                      objectSlot[name].push(el)
                                  } else {
                                      objectSlot[name] = [el]
                                  }
                              }
                          }
                      })
                  }
                  //将原来元素的所有孩子，全部移动新的元素的第一个slot的位置上
                  if (component.soleSlot) {
                      insertArraySlot(innerRender.vnodes, arraySlot)
                  } else {
                      insertObjectSlot(innerRender.vnodes, objectSlot)
                  }
              }
  
              if (comment) {
                  var dom = avalon$2.vdom(vdom, 'toDOM')
                  comment.parentNode.replaceChild(dom, comment)
                  comVm.$element = innerRender.root.dom = dom
                  delete this.reInit
              }
  
              //处理DOM节点
  
              dumpTree(vdom.dom)
              comVm.$element = vdom.dom
              groupTree(vdom.dom, vdom.children)
              if (fromCache) {
                  fireComponentHook(comVm, vdom, 'Enter')
              } else {
                  fireComponentHook(comVm, vdom, 'Ready')
              }
          },
          diff: function diff(newVal, oldVal) {
              if (cssDiff.call(this, newVal, oldVal)) {
                  return true
              }
          },
  
          update: function update(vdom, value) {
              //this.oldValue = value //★★防止递归
  
              switch (this.readyState) {
                  case 0:
                      if (this.reInit) {
                          this.init()
                      }
                      break
                  case 1:
                      this.readyState++
                      break
                  default:
                      this.readyState++
                      var comVm = this.comVm
                      avalon$2.viewChanging = true
                      avalon$2.transaction(function () {
                          for (var i in value) {
                              if (comVm.hasOwnProperty(i)) {
                                  if (Array.isArray(value[i])) {
                                      comVm[i] = value[i].concat()
                                  } else {
                                      comVm[i] = value[i]
                                  }
                              }
                          }
                      })
  
                      //要保证要先触发孩子的ViewChange 然后再到它自己的ViewChange
                      fireComponentHook(comVm, vdom, 'ViewChange')
                      delete avalon$2.viewChanging
                      break
              }
              this.value = avalon$2.mix(true, {}, value)
          },
          beforeDispose: function beforeDispose() {
              var comVm = this.comVm
              if (!this.cacheVm) {
                  fireComponentHook(comVm, this.node, 'Dispose')
                  comVm.$hashcode = false
                  delete avalon$2.vmodels[comVm.$id]
                  this.innerRender && this.innerRender.dispose()
              } else {
                  fireComponentHook(comVm, this.node, 'Leave')
              }
          }
      })
  
      function replaceRoot(instance, innerRender) {
          instance.innerRender = innerRender
          var root$$1 = innerRender.root
          var vdom = instance.node
          var slot = vdom.props.slot
          for (var i in root$$1) {
              vdom[i] = root$$1[i]
          }
          if (vdom.props && slot) {
              vdom.props.slot = slot
          }
          innerRender.root = vdom
          innerRender.vnodes[0] = vdom
      }
  
      function fireComponentHook(vm, vdom, name) {
          var list = vm.$events['on' + name]
          if (list) {
              list.forEach(function (el) {
                  setTimeout(function () {
                      el.callback.call(vm, {
                          type: name.toLowerCase(),
                          target: vdom.dom,
                          vmodel: vm
                      })
                  }, 0)
              })
          }
      }
  
      function createComponentVm(component, value, is) {
          var hooks = []
          var defaults = component.defaults
          collectHooks(defaults, hooks)
          collectHooks(value, hooks)
          var obj = {}
          for (var i in defaults) {
              var val = value[i]
              if (val == null) {
                  obj[i] = defaults[i]
              } else {
                  obj[i] = val
              }
          }
          obj.$id = value.id || value.$id || avalon$2.makeHashCode(is)
          delete obj.id
          var def = avalon$2.mix(true, {}, obj)
          var vm = avalon$2.define(def)
          hooks.forEach(function (el) {
              vm.$watch(el.type, el.cb)
          })
          return vm
      }
  
      function collectHooks(a, list) {
          for (var i in a) {
              if (componentEvents[i]) {
                  if (typeof a[i] === 'function' && i.indexOf('on') === 0) {
                      list.unshift({
                          type: i,
                          cb: a[i]
                      })
                  }
                  //delete a[i] 这里不能删除,会导致再次切换时没有onReady
              }
          }
      }
  
      function resetParentChildren(nodes, arr) {
          var dir = arr && arr[0] && arr[0].forDir
          if (dir) {
              dir.parentChildren = nodes
          }
      }
  
      function insertArraySlot(nodes, arr) {
          for (var i = 0, el; el = nodes[i]; i++) {
              if (el.nodeName === 'slot') {
                  resetParentChildren(nodes, arr)
                  nodes.splice.apply(nodes, [i, 1].concat(arr))
                  break
              } else if (el.children) {
                  insertArraySlot(el.children, arr)
              }
          }
      }
  
      function insertObjectSlot(nodes, obj) {
          for (var i = 0, el; el = nodes[i]; i++) {
              if (el.nodeName === 'slot') {
                  var name = el.props.name
                  resetParentChildren(nodes, obj[name])
                  nodes.splice.apply(nodes, [i, 1].concat(obj[name]))
                  continue
              } else if (el.children) {
                  insertObjectSlot(el.children, obj)
              }
          }
      }
  
      avalon$2.components = {}
      avalon$2.component = function (name, component) {
          /**
           * template: string
           * defaults: object
           * soleSlot: string
           */
          avalon$2.components[name] = component
          for (var el, i = 0; el = componentQueue[i]; i++) {
              if (el.is === name) {
                  componentQueue.splice(i, 1)
                  el.reInit = true
                  delete el.value
                  el.update()
                  i--
              }
          }
      }
  
      return avalon$2
  })