text: /**
   * yox.js v1.0.0-alpha.85
   * (c) 2017-2019 musicode
   * Released under the MIT License.
   */
  
  const SLOT_DATA_PREFIX = '$slot_';
  const HINT_BOOLEAN = 3;
  const DIRECTIVE_MODEL = 'model';
  const DIRECTIVE_EVENT = 'event';
  const DIRECTIVE_BINDING = 'binding';
  const DIRECTIVE_CUSTOM = 'o';
  const MODIFER_NATIVE = 'native';
  const MODEL_PROP_DEFAULT = 'value';
  const NAMESPACE_HOOK = '.hook';
  const HOOK_BEFORE_CREATE = 'beforeCreate';
  const HOOK_AFTER_CREATE = 'afterCreate';
  const HOOK_BEFORE_MOUNT = 'beforeMount';
  const HOOK_AFTER_MOUNT = 'afterMount';
  const HOOK_BEFORE_UPDATE = 'beforeUpdate';
  const HOOK_AFTER_UPDATE = 'afterUpdate';
  const HOOK_BEFORE_DESTROY = 'beforeDestroy';
  const HOOK_AFTER_DESTROY = 'afterDestroy';
  
  /**
   * 为了压缩，定义的常量
   */
  const TRUE = true;
  const FALSE = false;
  const NULL = null;
  const UNDEFINED = void 0;
  const MINUS_ONE = -1;
  const RAW_UNDEFINED = 'undefined';
  const RAW_FILTER = 'filter';
  const RAW_PARTIAL = 'partial';
  const RAW_COMPONENT = 'component';
  const RAW_DIRECTIVE = 'directive';
  const RAW_TRANSITION = 'transition';
  const RAW_VALUE = 'value';
  const RAW_LENGTH = 'length';
  const RAW_FUNCTION = 'function';
  const RAW_WILDCARD = '*';
  const RAW_DOT = '.';
  /**
   * Single instance for window in browser
   */
  const WINDOW = typeof window !== RAW_UNDEFINED ? window : UNDEFINED;
  /**
   * Single instance for document in browser
   */
  const DOCUMENT = typeof document !== RAW_UNDEFINED ? document : UNDEFINED;
  /**
   * Single instance for global in nodejs or browser
   */
  const GLOBAL = typeof global !== RAW_UNDEFINED ? global : WINDOW;
  /**
   * tap 事件
   *
   * 非常有用的抽象事件，比如 pc 端是 click 事件，移动端是 touchend 事件
   *
   * 这样只需 on-tap="handler" 就可以完美兼容各端
   *
   * 框架未实现此事件，通过 Yox.dom.addSpecialEvent 提供给外部扩展
   *
   */
  const EVENT_TAP = 'tap';
  /**
   * 点击事件
   */
  const EVENT_CLICK = 'click';
  /**
   * 输入事件
   */
  const EVENT_INPUT = 'input';
  /**
   * 变化事件
   */
  const EVENT_CHANGE = 'change';
  /**
   * 唯一内置的特殊事件：model
   */
  const EVENT_MODEL = 'model';
  /**
   * Single instance for noop function
   */
  const EMPTY_FUNCTION = function () {
      /** yox */
  };
  /**
   * 空对象，很多地方会用到，比如 `a || EMPTY_OBJECT` 确保是个对象
   */
  const EMPTY_OBJECT = Object.freeze({});
  /**
   * 空数组
   */
  const EMPTY_ARRAY = Object.freeze([]);
  /**
   * 空字符串
   */
  const EMPTY_STRING = '';
  
  function isDef (target) {
      return target !== UNDEFINED;
  }
  
  function isUndef (target) {
      return target === UNDEFINED;
  }
  
  /**
   * Check if value is a function.
   *
   * @param value
   * @return
   */
  function func(value) {
      return typeof value === RAW_FUNCTION;
  }
  /**
   * Check if value is an array.
   *
   * @param value
   * @return
   */
  function array(value) {
      return Array.isArray(value);
  }
  /**
   * Check if value is an object.
   *
   * @param value
   * @return
   */
  function object(value) {
      // 低版本 IE 会把 null 和 undefined 当作 object
      return value !== NULL && typeof value === 'object';
  }
  /**
   * Check if value is a string.
   *
   * @param value
   * @return
   */
  function string(value) {
      return typeof value === 'string';
  }
  /**
   * Check if value is a number.
   *
   * @param value
   * @return
   */
  function number(value) {
      return typeof value === 'number';
  }
  /**
   * Check if value is boolean.
   *
   * @param value
   * @return
   */
  function boolean(value) {
      return typeof value === 'boolean';
  }
  /**
   * Check if value is numeric.
   *
   * @param value
   * @return
   */
  function numeric(value) {
      return number(value)
          || (string(value) && !isNaN(parseFloat(value)) && isFinite(value));
  }
  
  var is = /*#__PURE__*/Object.freeze({
    func: func,
    array: array,
    object: object,
    string: string,
    number: number,
    boolean: boolean,
    numeric: numeric
  });
  
  /**
   * 任性地执行一个函数，不管它有没有、是不是
   *
   * @param fn 调用的函数
   * @param context 执行函数时的 this 指向
   * @param args 调用函数的参数，多参数时传入数组
   * @return 调用函数的返回值
   */
  function execute (fn, context, args) {
      if (func(fn)) {
          return array(args)
              ? fn.apply(context, args)
              : isDef(context)
                  ? fn.call(context, args)
                  : isDef(args)
                      ? fn(args)
                      : fn();
      }
  }
  
  class CustomEvent {
      /**
       * 构造函数
       *
       * 可以传事件名称，也可以传原生事件对象
       */
      constructor(type, originalEvent) {
          this.type = type;
          this.phase = CustomEvent.PHASE_CURRENT;
          if (originalEvent) {
              this.originalEvent = originalEvent;
          }
      }
      /**
       * 阻止事件的默认行为
       */
      preventDefault() {
          const instance = this;
          if (!instance.isPrevented) {
              const { originalEvent } = instance;
              if (originalEvent) {
                  originalEvent.preventDefault();
              }
              instance.isPrevented = TRUE;
          }
          return instance;
      }
      /**
       * 停止事件广播
       */
      stopPropagation() {
          const instance = this;
          if (!instance.isStoped) {
              const { originalEvent } = instance;
              if (originalEvent) {
                  originalEvent.stopPropagation();
              }
              instance.isStoped = TRUE;
          }
          return instance;
      }
      prevent() {
          return this.preventDefault();
      }
      stop() {
          return this.stopPropagation();
      }
  }
  CustomEvent.PHASE_CURRENT = 0;
  CustomEvent.PHASE_UPWARD = 1;
  CustomEvent.PHASE_DOWNWARD = MINUS_ONE;
  
  /**
   * 遍历数组
   *
   * @param array
   * @param callback 返回 false 可停止遍历
   * @param reversed 是否逆序遍历
   */
  function each(array, callback, reversed) {
      const { length } = array;
      if (length) {
          if (reversed) {
              for (let i = length - 1; i >= 0; i--) {
                  if (callback(array[i], i) === FALSE) {
                      break;
                  }
              }
          }
          else {
              for (let i = 0; i < length; i++) {
                  if (callback(array[i], i) === FALSE) {
                      break;
                  }
              }
          }
      }
  }
  function nativePush(array, item) {
      array[array.length] = item;
  }
  function nativeUnshift(array, item) {
      array.unshift(item);
  }
  /**
   * 添加
   *
   * @param array
   * @param value
   * @param action
   */
  function addItem(array$1, value, action) {
      if (array(value)) {
          each(value, function (item) {
              action(array$1, item);
          });
      }
      else {
          action(array$1, value);
      }
  }
  /**
   * 往后加
   *
   * @param array
   * @param target
   */
  function push(array, target) {
      addItem(array, target, nativePush);
  }
  /**
   * 往前加
   *
   * @param array
   * @param target
   */
  function unshift(array, target) {
      addItem(array, target, nativeUnshift);
  }
  /**
   * 数组项在数组中的位置
   *
   * @param array 数组
   * @param target 数组项
   * @param strict 是否全等判断，默认是全等
   * @return 如果未找到，返回 -1
   */
  function indexOf(array, target, strict) {
      let result = MINUS_ONE;
      each(array, function (item, index) {
          if (strict === FALSE ? item == target : item === target) {
              result = index;
              return FALSE;
          }
      });
      return result;
  }
  /**
   * 获取数组最后一项
   *
   * @param array 数组
   * @return
   */
  function last(array) {
      const { length } = array;
      if (length > 0) {
          return array[length - 1];
      }
  }
  /**
   * 弹出数组最后一项
   *
   * 项目里用的太多，仅用于节省字符...
   *
   * @param array 数组
   * @return 弹出的数组项
   */
  function pop(array) {
      const { length } = array;
      if (length > 0) {
          return array.pop();
      }
  }
  /**
   * 删除数组项
   *
   * @param array 数组
   * @param item 待删除项
   * @param strict 是否全等判断，默认是全等
   * @return 删除的数量
   */
  function remove(array, target, strict) {
      let result = 0;
      each(array, function (item, index) {
          if (strict === FALSE ? item == target : item === target) {
              array.splice(index, 1);
              result++;
          }
      }, TRUE);
      return result;
  }
  /**
   * 数组是否包含 item
   *
   * @param array 数组
   * @param target 可能包含的数组项
   * @param strict 是否全等判断，默认是全等
   * @return
   */
  function has(array, target, strict) {
      return indexOf(array, target, strict) >= 0;
  }
  /**
   * 把类数组转成数组
   *
   * @param array 类数组
   * @return
   */
  function toArray(array$1) {
      return array(array$1)
          ? array$1
          : execute(EMPTY_ARRAY.slice, array$1);
  }
  /**
   * 把数组转成对象
   *
   * @param array 数组
   * @param key 数组项包含的字段名称，如果数组项是基本类型，可不传
   * @param value
   * @return
   */
  function toObject(array, key, value) {
      let result = {};
      each(array, function (item) {
          result[key ? item[key] : item] = value || item;
      });
      return result;
  }
  /**
   * 把数组合并成字符串
   *
   * @param array
   * @param separator
   * @return
   */
  function join(array, separator) {
      return array.join(separator);
  }
  /**
   * 用于判断长度大于 0 的数组
   *
   * @param array
   * @return
   */
  function falsy(array$1) {
      return !array(array$1) || !array$1.length;
  }
  
  var array$1 = /*#__PURE__*/Object.freeze({
    each: each,
    push: push,
    unshift: unshift,
    indexOf: indexOf,
    last: last,
    pop: pop,
    remove: remove,
    has: has,
    toArray: toArray,
    toObject: toObject,
    join: join,
    falsy: falsy
  });
  
  const camelizePattern = /-([a-z])/gi, hyphenatePattern = /\B([A-Z])/g, capitalizePattern = /^[a-z]/, camelizeCache = {}, hyphenateCache = {}, capitalizeCache = {};
  /**
   * 连字符转成驼峰
   *
   * @param str
   * @return 驼峰格式的字符串
   */
  function camelize(str) {
      if (!camelizeCache[str]) {
          camelizeCache[str] = str.replace(camelizePattern, function ($0, $1) {
              return upper($1);
          });
      }
      return camelizeCache[str];
  }
  /**
   * 驼峰转成连字符
   *
   * @param str
   * @return 连字符格式的字符串
   */
  function hyphenate(str) {
      if (!hyphenateCache[str]) {
          hyphenateCache[str] = str.replace(hyphenatePattern, function ($0, $1) {
              return '-' + lower($1);
          });
      }
      return hyphenateCache[str];
  }
  /**
   * 首字母大写
   *
   * @param str
   * @return
   */
  function capitalize(str) {
      if (!capitalizeCache[str]) {
          capitalizeCache[str] = str.replace(capitalizePattern, upper);
      }
      return capitalizeCache[str];
  }
  /**
   * 清除两侧空白符
   *
   * @param str
   * @return 清除两侧空白符的字符串
   */
  function trim(str) {
      return falsy$1(str)
          ? EMPTY_STRING
          : str.trim();
  }
  /**
   * 截取字符串
   *
   * @param str
   * @param start
   * @param end
   * @return
   */
  function slice(str, start, end) {
      return number(end)
          ? start === end
              ? EMPTY_STRING
              : str.slice(start, end)
          : str.slice(start);
  }
  /**
   * 获取子串的起始位置
   *
   * @param str
   * @param part
   * @param start
   * @return
   */
  function indexOf$1(str, part, start) {
      return str.indexOf(part, isDef(start) ? start : 0);
  }
  /**
   * 获取子串的起始位置
   *
   * @param str
   * @param part
   * @param end
   * @return
   */
  function lastIndexOf(str, part, end) {
      return str.lastIndexOf(part, isDef(end) ? end : str.length);
  }
  /**
   * str 是否以 part 开头
   *
   * @param str
   * @param part
   * @return
   */
  function startsWith(str, part) {
      return indexOf$1(str, part) === 0;
  }
  /**
   * str 是否以 part 结束
   *
   * @param str
   * @param part
   * @return
   */
  function endsWith(str, part) {
      const offset = str.length - part.length;
      return offset >= 0 && lastIndexOf(str, part) === offset;
  }
  /**
   * 获取某个位置的字符
   */
  function charAt(str, index) {
      return str.charAt(index || 0);
  }
  /**
   * 获取某个位置的字符编码
   */
  function codeAt(str, index) {
      return str.charCodeAt(index || 0);
  }
  /**
   * 大写格式
   */
  function upper(str) {
      return str.toUpperCase();
  }
  /**
   * 小写格式
   */
  function lower(str) {
      return str.toLowerCase();
  }
  /**
   * str 是否包含 part
   *
   * @param str
   * @param part
   * @return 是否包含
   */
  function has$1(str, part) {
      return indexOf$1(str, part) >= 0;
  }
  /**
   * 判断长度大于 0 的字符串
   *
   * @param str
   * @return
   */
  function falsy$1(str) {
      return !string(str) || !str.length;
  }
  
  var string$1 = /*#__PURE__*/Object.freeze({
    camelize: camelize,
    hyphenate: hyphenate,
    capitalize: capitalize,
    trim: trim,
    slice: slice,
    indexOf: indexOf$1,
    lastIndexOf: lastIndexOf,
    startsWith: startsWith,
    endsWith: endsWith,
    charAt: charAt,
    codeAt: codeAt,
    upper: upper,
    lower: lower,
    has: has$1,
    falsy: falsy$1
  });
  
  const dotPattern = /\./g, asteriskPattern = /\*/g, doubleAsteriskPattern = /\*\*/g, splitCache = {}, patternCache = {};
  /**
   * 判断 keypath 是否以 prefix 开头，如果是，返回匹配上的前缀长度，否则返回 -1
   *
   * @param keypath
   * @param prefix
   * @return
   */
  function match(keypath, prefix) {
      if (keypath === prefix) {
          return prefix.length;
      }
      prefix += RAW_DOT;
      return startsWith(keypath, prefix)
          ? prefix.length
          : MINUS_ONE;
  }
  /**
   * 遍历 keypath 的每个部分
   *
   * @param keypath
   * @param callback 返回 false 可中断遍历
   */
  function each$1(keypath, callback) {
      // 判断字符串是因为 keypath 有可能是 toString
      // 而 splitCache.toString 是个函数
      const list = isDef(splitCache[keypath])
          ? splitCache[keypath]
          : (splitCache[keypath] = keypath.split(RAW_DOT));
      for (let i = 0, lastIndex = list.length - 1; i <= lastIndex; i++) {
          if (callback(list[i], i === lastIndex) === FALSE) {
              break;
          }
      }
  }
  /**
   * 遍历 keypath 的每个部分
   *
   * @param keypath1
   * @param keypath2
   */
  function join$1(keypath1, keypath2) {
      return keypath1 && keypath2
          ? keypath1 + RAW_DOT + keypath2
          : keypath1 || keypath2;
  }
  /**
   * 是否模糊匹配
   *
   * @param keypath
   */
  function isFuzzy(keypath) {
      return has$1(keypath, RAW_WILDCARD);
  }
  /**
   * 模糊匹配 keypath
   *
   * @param keypath
   * @param pattern
   */
  function matchFuzzy(keypath, pattern) {
      let cache = patternCache[pattern];
      if (!cache) {
          const str = pattern
              .replace(dotPattern, '\\.')
              .replace(asteriskPattern, '(\\w+)')
              .replace(doubleAsteriskPattern, '([\.\\w]+?)');
          cache = patternCache[pattern] = new RegExp(`^${str}$`);
      }
      const result = keypath.match(cache);
      if (result) {
          return result[1];
      }
  }
  
  /**
   * 全局 value holder，避免频繁的创建临时对象
   */
  const holder = {
      value: UNDEFINED
  };
  
  /**
   * 获取对象的 key 的数组
   *
   * @param object
   * @return
   */
  function keys(object) {
      return Object.keys(object);
  }
  function sortKeyByAsc(a, b) {
      return a.length - b.length;
  }
  function sortKeyByDesc(a, b) {
      return b.length - a.length;
  }
  /**
   * 排序对象的 key
   *
   * @param object
   * @param desc 是否逆序，默认从小到大排序
   * @return
   */
  function sort(object, desc) {
      return keys(object).sort(desc ? sortKeyByDesc : sortKeyByAsc);
  }
  /**
   * 遍历对象
   *
   * @param object
   * @param callback 返回 false 可停止遍历
   */
  function each$2(object, callback) {
      for (let key in object) {
          if (callback(object[key], key) === FALSE) {
              break;
          }
      }
  }
  /**
   * 清空对象所有的键值对
   *
   * @param object
   */
  function clear(object) {
      each$2(object, function (_, key) {
          delete object[key];
      });
  }
  /**
   * 扩展对象
   *
   * @return
   */
  function extend(original, object) {
      each$2(object, function (value, key) {
          original[key] = value;
      });
      return original;
  }
  /**
   * 合并对象
   *
   * @return
   */
  function merge(object1, object2) {
      return object1 && object2
          ? extend(extend({}, object1), object2)
          : object1 || object2;
  }
  /**
   * 拷贝对象
   *
   * @param object
   * @param deep 是否需要深拷贝
   * @return
   */
  function copy(object$1, deep) {
      let result = object$1;
      if (array(object$1)) {
          if (deep) {
              result = [];
              each(object$1, function (item, index) {
                  result[index] = copy(item, deep);
              });
          }
          else {
              result = object$1.slice();
          }
      }
      else if (object(object$1)) {
          result = {};
          each$2(object$1, function (value, key) {
              result[key] = deep ? copy(value, deep) : value;
          });
      }
      return result;
  }
  /**
   * 从对象中查找一个 keypath
   *
   * 返回值是空时，表示没找到值
   *
   * @param object
   * @param keypath
   * @return
   */
  function get(object, keypath) {
      each$1(keypath, function (key, isLast) {
          if (object != NULL) {
              // 先直接取值
              let value = object[key], 
              // 紧接着判断值是否存在
              // 下面会处理计算属性的值，不能在它后面设置 hasValue
              hasValue = isDef(value);
              // 如果是计算属性，取计算属性的值
              if (value && func(value.get)) {
                  value = value.get();
              }
              if (isLast) {
                  if (hasValue) {
                      holder.value = value;
                      object = holder;
                  }
                  else {
                      object = UNDEFINED;
                  }
              }
              else {
                  object = value;
              }
          }
          else {
              object = UNDEFINED;
              return FALSE;
          }
      });
      return object;
  }
  /**
   * 为对象设置一个键值对
   *
   * @param object
   * @param keypath
   * @param value
   * @param autofill 是否自动填充不存在的对象，默认自动填充
   */
  function set(object, keypath, value, autofill) {
      each$1(keypath, function (key, isLast) {
          if (isLast) {
              object[key] = value;
          }
          else if (object[key]) {
              object = object[key];
          }
          else if (autofill) {
              object = object[key] = {};
          }
          else {
              return FALSE;
          }
      });
  }
  /**
   * 对象是否包含某个 key
   *
   * @param object
   * @param key
   * @return
   */
  function has$2(object, key) {
      // 不用 hasOwnProperty，性能差
      return isDef(object[key]);
  }
  /**
   * 是否是空对象
   *
   * @param object
   * @return
   */
  function falsy$2(object$1) {
      return !object(object$1)
          || array(object$1)
          || !keys(object$1).length;
  }
  
  var object$1 = /*#__PURE__*/Object.freeze({
    keys: keys,
    sort: sort,
    each: each$2,
    clear: clear,
    extend: extend,
    merge: merge,
    copy: copy,
    get: get,
    set: set,
    has: has$2,
    falsy: falsy$2
  });
  
  function toString (target, defaultValue) {
      return target != NULL && target.toString
          ? target.toString()
          : isDef(defaultValue)
              ? defaultValue
              : EMPTY_STRING;
  }
  
  const DEBUG = 1;
  const INFO = 2;
  const WARN = 3;
  const ERROR = 4;
  const FATAL = 5;
  /**
   * 是否有原生的日志特性，没有必要单独实现
   */
  const nativeConsole = typeof console !== RAW_UNDEFINED ? console : NULL, 
  /**
   * 当前是否是源码调试，如果开启了代码压缩，empty function 里的注释会被干掉
   */
  defaultLogLevel = /yox/.test(toString(EMPTY_FUNCTION)) ? DEBUG : WARN, 
  /**
   * console 样式前缀
   * ie 和 edge 不支持 console.log 样式
   */
  stylePrefix = WINDOW && /edge|msie|trident/i.test(WINDOW.navigator.userAgent)
      ? EMPTY_STRING
      : '%c', 
  /**
   * 日志打印函数
   */
  printLog = nativeConsole
      ? stylePrefix
          ? function (tag, msg, style) {
              nativeConsole.log(stylePrefix + tag, style, msg);
          }
          : function (tag, msg) {
              nativeConsole.log(tag, msg);
          }
      : EMPTY_FUNCTION;
  /**
   * 全局调试开关
   */
  function getLogLevel() {
      if (GLOBAL) {
          const logLevel = GLOBAL['YOX_LOG_LEVEL'];
          if (logLevel >= DEBUG && logLevel <= FATAL) {
              return logLevel;
          }
      }
      return defaultLogLevel;
  }
  function getStyle(backgroundColor) {
      return `background-color:${backgroundColor};border-radius:12px;color:#fff;font-size:10px;padding:3px 6px;`;
  }
  /**
   * 打印 debug 日志
   *
   * @param msg
   */
  function debug(msg, tag) {
      if (getLogLevel() <= DEBUG) {
          printLog(tag || 'Yox debug', msg, getStyle('#999'));
      }
  }
  /**
   * 打印 info 日志
   *
   * @param msg
   */
  function info(msg, tag) {
      if (getLogLevel() <= INFO) {
          printLog(tag || 'Yox info', msg, getStyle('#2db7f5'));
      }
  }
  /**
   * 打印 warn 日志
   *
   * @param msg
   */
  function warn(msg, tag) {
      if (getLogLevel() <= WARN) {
          printLog(tag || 'Yox warn', msg, getStyle('#f90'));
      }
  }
  /**
   * 打印 error 日志
   *
   * @param msg
   */
  function error(msg, tag) {
      if (getLogLevel() <= ERROR) {
          printLog(tag || 'Yox error', msg, getStyle('#ed4014'));
      }
  }
  /**
   * 致命错误，中断程序
   *
   * @param msg
   */
  function fatal(msg, tag) {
      if (getLogLevel() <= FATAL) {
          throw new Error(`[${tag || 'Yox fatal'}]: ${msg}`);
      }
  }
  
  var logger = /*#__PURE__*/Object.freeze({
    DEBUG: DEBUG,
    INFO: INFO,
    WARN: WARN,
    ERROR: ERROR,
    FATAL: FATAL,
    debug: debug,
    info: info,
    warn: warn,
    error: error,
    fatal: fatal
  });
  
  class Emitter {
      constructor(ns) {
          this.ns = ns || FALSE;
          this.listeners = {};
      }
      /**
       * 发射事件
       *
       * @param bullet 事件或事件名称
       * @param data 事件数据
       */
      fire(type, args, filter) {
          let instance = this, namespace = string(type) ? instance.parse(type) : type, list = instance.listeners[namespace.name], isComplete = TRUE;
          if (list) {
              // 避免遍历过程中，数组发生变化，比如增删了
              list = copy(list);
              // 判断是否是发射事件
              // 如果 args 的第一个参数是 CustomEvent 类型，表示发射事件
              // 因为事件处理函数的参数列表是 (event, data)
              const event = args && args[0] instanceof CustomEvent
                  ? args[0]
                  : UNDEFINED;
              each(list, function (options) {
                  // 命名空间不匹配
                  if (!matchNamespace(namespace.ns, options)
                      // 在 fire 过程中被移除了
                      || !has(list, options)
                      // 传了 filter，则用 filter 判断是否过滤此 options
                      || (filter && !filter(namespace, args, options))) {
                      return;
                  }
                  // 为 event 对象加上当前正在处理的 listener
                  // 这样方便业务层移除事件绑定
                  // 比如 on('xx', function) 这样定义了匿名 listener
                  // 在这个 listener 里面获取不到当前 listener 的引用
                  // 为了能引用到，有时候会先定义 var listener = function,
                  // 然后再 on('xx', listener) 这样其实是没有必要的
                  if (event) {
                      event.listener = options.fn;
                  }
                  let result = execute(options.fn, options.ctx, args);
                  if (event) {
                      event.listener = UNDEFINED;
                  }
                  // 执行次数
                  options.num = options.num ? (options.num + 1) : 1;
                  // 注册的 listener 可以指定最大执行次数
                  if (options.num === options.max) {
                      instance.off(namespace.key, options.fn);
                  }
                  // 如果没有返回 false，而是调用了 event.stop 也算是返回 false
                  if (event) {
                      if (result === FALSE) {
                          event.prevent().stop();
                      }
                      else if (event.isStoped) {
                          result = FALSE;
                      }
                  }
                  if (result === FALSE) {
                      return isComplete = FALSE;
                  }
              });
          }
          return isComplete;
      }
      /**
       * 注册监听
       *
       * @param type
       * @param listener
       */
      on(type, listener) {
          const instance = this, { listeners } = instance, options = func(listener)
              ? { fn: listener }
              : listener;
          if (object(options) && func(options.fn)) {
              const { name, ns } = instance.parse(type);
              options.ns = ns;
              push(listeners[name] || (listeners[name] = []), options);
          }
      }
      /**
       * 取消监听
       *
       * @param type
       * @param listener
       */
      off(type, listener) {
          const instance = this, { listeners } = instance;
          if (type) {
              const { name, ns } = instance.parse(type), matchListener = createMatchListener(listener), each$1 = function (list, name) {
                  each(list, function (options, index) {
                      if (matchListener(options) && matchNamespace(ns, options)) {
                          list.splice(index, 1);
                      }
                  }, TRUE);
                  if (!list.length) {
                      delete listeners[name];
                  }
              };
              if (name) {
                  if (listeners[name]) {
                      each$1(listeners[name], name);
                  }
              }
              else if (ns) {
                  each$2(listeners, each$1);
              }
          }
          else {
              // 清空
              instance.listeners = {};
          }
      }
      /**
       * 是否已监听某个事件
       *
       * @param type
       * @param listener
       */
      has(type, listener) {
          let instance = this, { listeners } = instance, { name, ns } = instance.parse(type), result = TRUE, matchListener = createMatchListener(listener), each$1 = function (list) {
              each(list, function (options) {
                  if (matchListener(options) && matchNamespace(ns, options)) {
                      return result = FALSE;
                  }
              });
              return result;
          };
          if (name) {
              if (listeners[name]) {
                  each$1(listeners[name]);
              }
          }
          else if (ns) {
              each$2(listeners, each$1);
          }
          return !result;
      }
      /**
       * 把事件类型解析成命名空间格式
       *
       * @param type
       */
      parse(type) {
          const result = {
              key: type,
              name: type,
              ns: EMPTY_STRING,
          };
          if (this.ns) {
              const index = indexOf$1(type, RAW_DOT);
              if (index >= 0) {
                  result.name = slice(type, 0, index);
                  result.ns = slice(type, index + 1);
              }
          }
          return result;
      }
  }
  function matchTrue() {
      return TRUE;
  }
  /**
   * 外部会传入 Function 或 EmitterOptions 或 空
   *
   * 这里根据传入值的不同类型，创建不同的判断函数
   *
   * 如果传入的是 EmitterOptions，则全等判断
   *
   * 如果传入的是 Function，则判断函数是否全等
   *
   * 如果传入的是空，则直接返回 true
   *
   * @param listener
   */
  function createMatchListener(listener) {
      return func(listener)
          ? function (options) {
              return listener === options.fn;
          }
          : matchTrue;
  }
  /**
   * 判断 options 是否能匹配命名空间
   *
   * 如果 namespace 和 options.ns 都不为空，则需完全匹配
   *
   * 如果他们两个其中任何一个为空，则不判断命名空间
   *
   * @param namespace
   * @param options
   */
  function matchNamespace(namespace, options) {
      const { ns } = options;
      return ns && namespace
          ? ns === namespace
          : TRUE;
  }
  
  function isNative (target) {
      return func(target) && /native code/.test(toString(target));
  }
  
  let nextTick;
  // IE (10+) 和 node
  if (typeof setImmediate === RAW_FUNCTION && isNative(setImmediate)) {
      nextTick = setImmediate;
  }
  // 用 MessageChannel 去做 setImmediate 的 polyfill
  // 原理是将新的 message 事件加入到原有的 dom events 之后
  // 兼容性 IE10+ 和其他标准浏览器
  if (typeof MessageChannel === RAW_FUNCTION && isNative(MessageChannel)) {
      nextTick = function (fn) {
          const channel = new MessageChannel();
          channel.port1.onmessage = fn;
          channel.port2.postMessage(1);
      };
  }
  else {
      nextTick = setTimeout;
  }
  var nextTick$1 = nextTick;
  
  let shared;
  class NextTask {
      /**
       * 全局单例
       */
      static shared() {
          return shared || (shared = new NextTask());
      }
      constructor() {
          this.tasks = [];
      }
      /**
       * 在队尾添加异步任务
       */
      append(func, context) {
          const instance = this, { tasks } = instance;
          push(tasks, {
              fn: func,
              ctx: context
          });
          if (tasks.length === 1) {
              nextTick$1(function () {
                  instance.run();
              });
          }
      }
      /**
       * 在队首添加异步任务
       */
      prepend(func, context) {
          const instance = this, { tasks } = instance;
          unshift(tasks, {
              fn: func,
              ctx: context
          });
          if (tasks.length === 1) {
              nextTick$1(function () {
                  instance.run();
              });
          }
      }
      /**
       * 清空异步队列
       */
      clear() {
          this.tasks.length = 0;
      }
      /**
       * 立即执行异步任务，并清空队列
       */
      run() {
          const { tasks } = this;
          if (tasks.length) {
              this.tasks = [];
              each(tasks, function (task) {
                  execute(task.fn, task.ctx);
              });
          }
      }
  }
  
  let guid = 0;
  function guid$1 () {
      return ++guid;
  }
  
  // vnode.data 内部使用的几个字段
  const ID = '$id';
  const VNODE = '$vnode';
  const LOADING = '$loading';
  const COMPONENT = '$component';
  const LEAVING = '$leaving';
  
  function update(api, vnode, oldVnode) {
      const { node, nativeAttrs } = vnode, oldNativeAttrs = oldVnode && oldVnode.nativeAttrs;
      if (nativeAttrs || oldNativeAttrs) {
          const newValue = nativeAttrs || EMPTY_OBJECT, oldValue = oldNativeAttrs || EMPTY_OBJECT;
          each$2(newValue, function (attr, name) {
              if (!oldValue[name]
                  || attr.value !== oldValue[name].value) {
                  api.attr(node, name, attr.value);
              }
          });
          each$2(oldValue, function (_, name) {
              if (!newValue[name]) {
                  api.removeAttr(node, name);
              }
          });
      }
  }
  
  function update$1(api, vnode, oldVnode) {
      const { node, nativeProps } = vnode, oldNativeProps = oldVnode && oldVnode.nativeProps;
      if (nativeProps || oldNativeProps) {
          const newValue = nativeProps || EMPTY_OBJECT, oldValue = oldNativeProps || EMPTY_OBJECT;
          each$2(newValue, function (prop, name) {
              if (!oldValue[name]
                  || prop.value !== oldValue[name].value) {
                  api.prop(node, name, prop.value);
              }
          });
          each$2(oldValue, function (prop, name) {
              if (!newValue[name]) {
                  api.removeProp(node, name, prop.hint);
              }
          });
      }
  }
  
  function update$2(vnode, oldVnode) {
      const { data, directives } = vnode, oldDirectives = oldVnode && oldVnode.directives;
      if (directives || oldDirectives) {
          const node = data[COMPONENT] || vnode.node, isKeypathChange = oldVnode && vnode.keypath !== oldVnode.keypath, newValue = directives || EMPTY_OBJECT, oldValue = oldDirectives || EMPTY_OBJECT;
          each$2(newValue, function (directive, name) {
              const { once, bind, unbind } = directive.hooks;
              if (!oldValue[name]) {
                  bind(node, directive, vnode);
              }
              else if (once
                  || directive.value !== oldValue[name].value
                  || isKeypathChange) {
                  if (unbind) {
                      unbind(node, oldValue[name], oldVnode);
                  }
                  bind(node, directive, vnode);
              }
          });
          each$2(oldValue, function (directive, name) {
              if (!newValue[name]) {
                  const { unbind } = directive.hooks;
                  if (unbind) {
                      unbind(node, directive, oldVnode);
                  }
              }
          });
      }
  }
  function remove$1(vnode) {
      const { directives } = vnode;
      if (directives) {
          const node = vnode.data[COMPONENT] || vnode.node;
          each$2(directives, function (directive) {
              const { unbind } = directive.hooks;
              if (unbind) {
                  unbind(node, directive, vnode);
              }
          });
      }
  }
  
  function update$3(vnode, oldVnode) {
      let { data, ref, props, slots, directives, context } = vnode, node;
      if (vnode.isComponent) {
          node = data[COMPONENT];
          // 更新时才要 set
          // 因为初始化时，所有这些都经过构造函数完成了
          if (oldVnode) {
              const model = directives && directives[DIRECTIVE_MODEL];
              if (model) {
                  if (!props) {
                      props = {};
                  }
                  props[node.$model] = model.value;
              }
              if (props) {
                  node.checkProps(props);
              }
              const result = merge(props, slots);
              if (result) {
                  node.forceUpdate(result);
              }
          }
      }
      else {
          node = vnode.node;
      }
      if (ref) {
          const refs = context.$refs;
          if (refs) {
              refs[ref] = node;
          }
      }
  }
  
  function isPatchable(vnode, oldVnode) {
      return vnode.tag === oldVnode.tag
          && vnode.key === oldVnode.key;
  }
  function createKeyToIndex(vnodes, startIndex, endIndex) {
      let result, vnode, key;
      while (startIndex <= endIndex) {
          vnode = vnodes[startIndex];
          if (vnode && (key = vnode.key)) {
              if (!result) {
                  result = {};
              }
              result[key] = startIndex;
          }
          startIndex++;
      }
      return result || EMPTY_OBJECT;
  }
  function insertBefore(api, parentNode, node, referenceNode) {
      if (referenceNode) {
          api.before(parentNode, node, referenceNode);
      }
      else {
          api.append(parentNode, node);
      }
  }
  function createComponent(vnode, options) {
      const child = (vnode.parent || vnode.context).createComponent(options, vnode);
      vnode.data[COMPONENT] = child;
      vnode.data[LOADING] = FALSE;
      update$3(vnode);
      update$2(vnode);
      return child;
  }
  function createData() {
      const data = {};
      data[ID] = guid$1();
      return data;
  }
  function createVnode(api, vnode) {
      let { tag, node, data, isComponent, isComment, isText, isStyle, isOption, children, text, html, context } = vnode;
      if (node && data) {
          return;
      }
      data = createData();
      vnode.data = data;
      if (isText) {
          vnode.node = api.createText(text);
          return;
      }
      if (isComment) {
          vnode.node = api.createComment(text);
          return;
      }
      if (isComponent) {
          let componentOptions = UNDEFINED;
          // 动态组件，tag 可能为空
          if (tag) {
              context.loadComponent(tag, function (options) {
                  if (has$2(data, LOADING)) {
                      // 异步组件
                      if (data[LOADING]) {
                          // 尝试使用最新的 vnode
                          if (data[VNODE]) {
                              vnode = data[VNODE];
                              // 用完就删掉
                              delete data[VNODE];
                          }
                          enterVnode(vnode, createComponent(vnode, options));
                      }
                  }
                  // 同步组件
                  else {
                      componentOptions = options;
                  }
              });
          }
          // 不论是同步还是异步组件，都需要一个占位元素
          vnode.node = api.createComment(RAW_COMPONENT);
          if (componentOptions) {
              createComponent(vnode, componentOptions);
          }
          else {
              data[LOADING] = TRUE;
          }
      }
      else {
          node = vnode.node = api.createElement(vnode.tag, vnode.isSvg);
          if (children) {
              addVnodes(api, node, children);
          }
          else if (text) {
              api.text(node, text, isStyle, isOption);
          }
          else if (html) {
              api.html(node, html, isStyle, isOption);
          }
          update(api, vnode);
          update$1(api, vnode);
          update$3(vnode);
          update$2(vnode);
      }
  }
  function addVnodes(api, parentNode, vnodes, startIndex, endIndex, before) {
      let vnode, start = startIndex || 0, end = isDef(endIndex) ? endIndex : vnodes.length - 1;
      while (start <= end) {
          vnode = vnodes[start];
          createVnode(api, vnode);
          insertVnode(api, parentNode, vnode, before);
          start++;
      }
  }
  function insertVnode(api, parentNode, vnode, before) {
      const { node, data, context } = vnode, hasParent = api.parent(node);
      // 这里不调用 insertBefore，避免判断两次
      if (before) {
          api.before(parentNode, node, before.node);
      }
      else {
          api.append(parentNode, node);
      }
      // 普通元素和组件的占位节点都会走到这里
      // 但是占位节点不用 enter，而是等组件加载回来之后再调 enter
      if (!hasParent) {
          let enter = UNDEFINED;
          if (vnode.isComponent) {
              const component = data[COMPONENT];
              if (component) {
                  enter = function () {
                      enterVnode(vnode, component);
                  };
              }
          }
          else if (!vnode.isStatic && !vnode.isText && !vnode.isComment) {
              enter = function () {
                  enterVnode(vnode);
              };
          }
          if (enter) {
              // 执行到这时，组件还没有挂载到 DOM 树
              // 如果此时直接触发 enter，外部还需要做多余的工作，比如 setTimeout
              // 索性这里直接等挂载到 DOM 数之后再触发
              // 注意：YoxInterface 没有声明 $observer，因为不想让外部访问，
              // 但是这里要用一次，所以加了 as any
              context.$observer.nextTask.prepend(enter);
          }
      }
  }
  function removeVnodes(api, parentNode, vnodes, startIndex, endIndex) {
      let vnode, start = startIndex || 0, end = isDef(endIndex) ? endIndex : vnodes.length - 1;
      while (start <= end) {
          vnode = vnodes[start];
          if (vnode) {
              removeVnode(api, parentNode, vnode);
          }
          start++;
      }
  }
  function removeVnode(api, parentNode, vnode) {
      const { node } = vnode;
      if (vnode.isStatic || vnode.isText || vnode.isComment) {
          api.remove(parentNode, node);
      }
      else {
          let done = function () {
              destroyVnode(api, vnode);
              api.remove(parentNode, node);
          }, component;
          if (vnode.isComponent) {
              component = vnode.data[COMPONENT];
              // 异步组件，还没加载成功就被删除了
              if (!component) {
                  done();
                  return;
              }
          }
          leaveVnode(vnode, component, done);
      }
  }
  function destroyVnode(api, vnode) {
      /**
       * 如果一个子组件的模板是这样写的：
       *
       * <div>
       *   {{#if visible}}
       *      <slot name="children"/>
       *   {{/if}}
       * </div>
       *
       * 当 visible 从 true 变为 false 时，不能销毁 slot 导入的任何 vnode
       * 不论是组件或是元素，都不能销毁，只能简单的 remove，
       * 否则子组件下一次展现它们时，会出问题
       */
      const { data, children, parent, slot } = vnode;
      // 销毁插槽组件
      // 如果宿主组件正在销毁，$vnode 属性会在调 destroy() 之前被删除
      // 这里表示的是宿主组件还没被销毁
      // 如果宿主组件被销毁了，则它的一切都要进行销毁
      if (slot && parent && parent.$vnode) {
          // 如果更新时，父组件没有传入该 slot，则子组件需要销毁该 slot
          const slots = parent.get(slot);
          // slots 要么没有，要么是数组，不可能是别的
          if (slots && has(slots, vnode)) {
              return;
          }
      }
      if (vnode.isComponent) {
          const component = data[COMPONENT];
          if (component) {
              remove$1(vnode);
              component.destroy();
          }
          else
              [
                  data[LOADING] = FALSE
              ];
      }
      else {
          remove$1(vnode);
          if (children) {
              each(children, function (child) {
                  destroyVnode(api, child);
              });
          }
      }
  }
  /**
   * vnode 触发 enter hook 时，外部一般会做一些淡入动画
   */
  function enterVnode(vnode, component) {
      // 如果组件根元素和组件本身都写了 transition
      // 优先用外面定义的
      // 因为这明确是在覆盖配置
      let { data, transition } = vnode;
      if (component && !transition) {
          // 再看组件根元素是否有 transition
          transition = component.$vnode.transition;
      }
      execute(data[LEAVING]);
      if (transition) {
          const { enter } = transition;
          if (enter) {
              enter(vnode.node);
              return;
          }
      }
  }
  /**
   * vnode 触发 leave hook 时，外部一般会做一些淡出动画
   * 动画结束后才能移除节点，否则无法产生动画
   * 这里由外部调用 done 来通知内部动画结束
   */
  function leaveVnode(vnode, component, done) {
      // 如果组件根元素和组件本身都写了 transition
      // 优先用外面定义的
      // 因为这明确是在覆盖配置
      let { data, transition } = vnode;
      if (component && !transition) {
          // 再看组件根元素是否有 transition
          transition = component.$vnode.transition;
      }
      if (transition) {
          const { leave } = transition;
          if (leave) {
              leave(vnode.node, data[LEAVING] = function () {
                  if (data[LEAVING]) {
                      done();
                      data[LEAVING] = UNDEFINED;
                  }
              });
              return;
          }
      }
      // 如果没有淡出动画，直接结束
      done();
  }
  function updateChildren(api, parentNode, children, oldChildren) {
      let startIndex = 0, endIndex = children.length - 1, startVnode = children[startIndex], endVnode = children[endIndex], oldStartIndex = 0, oldEndIndex = oldChildren.length - 1, oldStartVnode = oldChildren[oldStartIndex], oldEndVnode = oldChildren[oldEndIndex], oldKeyToIndex, oldIndex;
      while (oldStartIndex <= oldEndIndex && startIndex <= endIndex) {
          // 下面有设为 UNDEFINED 的逻辑
          if (!startVnode) {
              startVnode = children[++startIndex];
          }
          else if (!endVnode) {
              endVnode = children[--endIndex];
          }
          else if (!oldStartVnode) {
              oldStartVnode = oldChildren[++oldStartIndex];
          }
          else if (!oldEndVnode) {
              oldEndVnode = oldChildren[--oldEndIndex];
          }
          // 从头到尾比较，位置相同且值得 patch
          else if (isPatchable(startVnode, oldStartVnode)) {
              patch(api, startVnode, oldStartVnode);
              startVnode = children[++startIndex];
              oldStartVnode = oldChildren[++oldStartIndex];
          }
          // 从尾到头比较，位置相同且值得 patch
          else if (isPatchable(endVnode, oldEndVnode)) {
              patch(api, endVnode, oldEndVnode);
              endVnode = children[--endIndex];
              oldEndVnode = oldChildren[--oldEndIndex];
          }
          // 比较完两侧的节点，剩下就是 位置发生改变的节点 和 全新的节点
          // 当 endVnode 和 oldStartVnode 值得 patch
          // 说明元素被移到右边了
          else if (isPatchable(endVnode, oldStartVnode)) {
              patch(api, endVnode, oldStartVnode);
              insertBefore(api, parentNode, oldStartVnode.node, api.next(oldEndVnode.node));
              endVnode = children[--endIndex];
              oldStartVnode = oldChildren[++oldStartIndex];
          }
          // 当 oldEndVnode 和 startVnode 值得 patch
          // 说明元素被移到左边了
          else if (isPatchable(startVnode, oldEndVnode)) {
              patch(api, startVnode, oldEndVnode);
              insertBefore(api, parentNode, oldEndVnode.node, oldStartVnode.node);
              startVnode = children[++startIndex];
              oldEndVnode = oldChildren[--oldEndIndex];
          }
          // 尝试同级元素的 key
          else {
              if (!oldKeyToIndex) {
                  oldKeyToIndex = createKeyToIndex(oldChildren, oldStartIndex, oldEndIndex);
              }
              // 新节点之前的位置
              oldIndex = startVnode.key
                  ? oldKeyToIndex[startVnode.key]
                  : UNDEFINED;
              // 移动元素
              if (isDef(oldIndex)) {
                  patch(api, startVnode, oldChildren[oldIndex]);
                  oldChildren[oldIndex] = UNDEFINED;
              }
              // 新元素
              else {
                  createVnode(api, startVnode);
              }
              insertVnode(api, parentNode, startVnode, oldStartVnode);
              startVnode = children[++startIndex];
          }
      }
      if (oldStartIndex > oldEndIndex) {
          addVnodes(api, parentNode, children, startIndex, endIndex, children[endIndex + 1]);
      }
      else if (startIndex > endIndex) {
          removeVnodes(api, parentNode, oldChildren, oldStartIndex, oldEndIndex);
      }
  }
  function patch(api, vnode, oldVnode) {
      if (vnode === oldVnode) {
          return;
      }
      const { node, data } = oldVnode;
      // 如果不能 patch，则删除重建
      if (!isPatchable(vnode, oldVnode)) {
          // 同步加载的组件，初始化时不会传入占位节点
          // 它内部会自动生成一个注释节点，当它的根 vnode 和注释节点对比时，必然无法 patch
          // 于是走进此分支，为新组件创建一个 DOM 节点，然后继续 createComponent 后面的流程
          const parentNode = api.parent(node);
          createVnode(api, vnode);
          if (parentNode) {
              insertVnode(api, parentNode, vnode, oldVnode);
              removeVnode(api, parentNode, oldVnode);
          }
          return;
      }
      vnode.node = node;
      vnode.data = data;
      // 组件正在异步加载，更新为最新的 vnode
      // 当异步加载完成时才能用上最新的 vnode
      if (oldVnode.isComponent && data[LOADING]) {
          data[VNODE] = vnode;
          return;
      }
      // 两棵静态子树就别折腾了
      if (vnode.isStatic && oldVnode.isStatic) {
          return;
      }
      update(api, vnode, oldVnode);
      update$1(api, vnode, oldVnode);
      update$3(vnode, oldVnode);
      update$2(vnode, oldVnode);
      const { text, html, children, isStyle, isOption } = vnode, oldText = oldVnode.text, oldHtml = oldVnode.html, oldChildren = oldVnode.children;
      if (string(text)) {
          if (text !== oldText) {
              api.text(node, text, isStyle, isOption);
          }
      }
      else if (string(html)) {
          if (html !== oldHtml) {
              api.html(node, html, isStyle, isOption);
          }
      }
      // 两个都有需要 diff
      else if (children && oldChildren) {
          if (children !== oldChildren) {
              updateChildren(api, node, children, oldChildren);
          }
      }
      // 有新的没旧的 - 新增节点
      else if (children) {
          if (string(oldText) || string(oldHtml)) {
              api.text(node, EMPTY_STRING, isStyle);
          }
          addVnodes(api, node, children);
      }
      // 有旧的没新的 - 删除节点
      else if (oldChildren) {
          removeVnodes(api, node, oldChildren);
      }
      // 有旧的 text 没有新的 text
      else if (string(oldText) || string(oldHtml)) {
          api.text(node, EMPTY_STRING, isStyle);
      }
  }
  function create(api, node, context, keypath) {
      return {
          tag: api.tag(node),
          data: createData(),
          node,
          context,
          keypath,
      };
  }
  function destroy(api, vnode, isRemove) {
      if (isRemove) {
          const parentNode = api.parent(vnode.node);
          if (parentNode) {
              removeVnode(api, parentNode, vnode);
          }
      }
      else {
          destroyVnode(api, vnode);
      }
  }
  
  /**
   * 元素 节点
   */
  
  function toNumber (target, defaultValue) {
      return numeric(target)
          ? +target
          : isDef(defaultValue)
              ? defaultValue
              : 0;
  }
  
  /**
   * 字面量
   */
  
  function setPair(target, name, key, value) {
      const data = target[name] || (target[name] = {});
      data[key] = value;
  }
  const KEY_DIRECTIVES = 'directives';
  function render(context, template, filters, partials, directives, transitions) {
      let $scope = { $keypath: EMPTY_STRING }, $stack = [$scope], $vnode, vnodeStack = [], localPartials = {}, findValue = function (stack, index, key, lookup, depIgnore, defaultKeypath) {
          let scope = stack[index], keypath = join$1(scope.$keypath, key), value = stack, holder$1 = holder;
          // 如果最后还是取不到值，用回最初的 keypath
          if (isUndef(defaultKeypath)) {
              defaultKeypath = keypath;
          }
          // 如果取的是 scope 上直接有的数据，如 $keypath
          if (isDef(scope[key])) {
              value = scope[key];
          }
          // 如果取的是数组项，则要更进一步
          else if (isDef(scope.$item)) {
              scope = scope.$item;
              // 到这里 scope 可能为空
              // 比如 new Array(10) 然后遍历这个数组，每一项肯定是空
              // 取 this
              if (key === EMPTY_STRING) {
                  value = scope;
              }
              // 取 this.xx
              else if (scope != NULL && isDef(scope[key])) {
                  value = scope[key];
              }
          }
          if (value === stack) {
              // 正常取数据
              value = context.get(keypath, stack, depIgnore);
              if (value === stack) {
                  if (lookup && index > 0) {
                      return findValue(stack, index - 1, key, lookup, depIgnore, defaultKeypath);
                  }
                  // 到头了，最后尝试过滤器
                  const result = get(filters, key);
                  if (result) {
                      holder$1 = result;
                      holder$1.keypath = key;
                  }
                  else {
                      holder$1.value = UNDEFINED;
                      holder$1.keypath = defaultKeypath;
                  }
                  return holder$1;
              }
          }
          holder$1.value = value;
          holder$1.keypath = keypath;
          return holder$1;
      }, createEventListener = function (type) {
          return function (event, data) {
              // 事件名称相同的情况，只可能是监听 DOM 事件，比如写一个 Button 组件
              // <button on-click="click"> 纯粹的封装了一个原生 click 事件
              if (type !== event.type) {
                  event = new CustomEvent(type, event);
              }
              context.fire(event, data);
          };
      }, createMethodListener = function (name, args, stack) {
          return function (event, data) {
              const method = context[name];
              if (event instanceof CustomEvent) {
                  let result = UNDEFINED;
                  if (args) {
                      const scope = last(stack);
                      if (scope) {
                          scope.$event = event;
                          scope.$data = data;
                          result = execute(method, context, args(stack));
                          scope.$event =
                              scope.$data = UNDEFINED;
                      }
                  }
                  else {
                      result = execute(method, context, data ? [event, data] : event);
                  }
                  return result;
              }
              else {
                  execute(method, context, args ? args(stack) : UNDEFINED);
              }
          };
      }, createGetter = function (getter, stack) {
          return function () {
              return getter(stack);
          };
      }, renderTextVnode = function (text) {
          const vnodeList = last(vnodeStack);
          if (vnodeList) {
              const lastVnode = last(vnodeList);
              if (lastVnode && lastVnode.isText) {
                  lastVnode.text += text;
              }
              else {
                  const textVnode = {
                      isText: TRUE,
                      text,
                      context,
                      keypath: $scope.$keypath,
                  };
                  push(vnodeList, textVnode);
              }
          }
      }, renderAttributeVnode = function (name, value) {
          if ($vnode.isComponent) {
              setPair($vnode, 'props', name, value);
          }
          else {
              setPair($vnode, 'nativeAttrs', name, { name, value });
          }
      }, renderPropertyVnode = function (name, hint, value) {
          setPair($vnode, 'nativeProps', name, { name, value, hint });
      }, renderLazyVnode = function (name, value) {
          setPair($vnode, 'lazy', name, value);
      }, renderTransitionVnode = function (name) {
          $vnode.transition = transitions[name];
      }, renderBindingVnode = function (name, holder, hint) {
          const key = join$1(DIRECTIVE_BINDING, name);
          setPair($vnode, KEY_DIRECTIVES, key, {
              ns: DIRECTIVE_BINDING,
              name,
              key,
              modifier: holder.keypath,
              hooks: directives[DIRECTIVE_BINDING],
              hint,
          });
          return holder.value;
      }, renderModelVnode = function (holder) {
          setPair($vnode, KEY_DIRECTIVES, DIRECTIVE_MODEL, {
              ns: DIRECTIVE_MODEL,
              name: EMPTY_STRING,
              key: DIRECTIVE_MODEL,
              value: holder.value,
              modifier: holder.keypath,
              hooks: directives[DIRECTIVE_MODEL]
          });
      }, renderEventMethodVnode = function (name, key, modifier, value, method, args) {
          setPair($vnode, KEY_DIRECTIVES, key, {
              ns: DIRECTIVE_EVENT,
              name,
              key,
              value,
              modifier,
              hooks: directives[DIRECTIVE_EVENT],
              handler: createMethodListener(method, args, $stack),
          });
      }, renderEventNameVnode = function (name, key, modifier, value, event) {
          setPair($vnode, KEY_DIRECTIVES, key, {
              ns: DIRECTIVE_EVENT,
              name,
              key,
              value,
              modifier,
              hooks: directives[DIRECTIVE_EVENT],
              handler: createEventListener(event),
          });
      }, renderDirectiveVnode = function (name, key, modifier, value, method, args, getter) {
          const hooks = directives[name];
          setPair($vnode, KEY_DIRECTIVES, key, {
              ns: DIRECTIVE_CUSTOM,
              name,
              key,
              value,
              hooks,
              modifier,
              getter: getter ? createGetter(getter, $stack) : UNDEFINED,
              handler: method ? createMethodListener(method, args, $stack) : UNDEFINED,
          });
      }, renderSpreadVnode = function (holder) {
          const { value, keypath } = holder;
          // 如果为 null 或 undefined，则不需要 warn
          if (value != NULL) {
              // 数组也算一种对象，要排除掉
              if (object(value) && !array(value)) {
                  each$2(value, function (value, key) {
                      setPair($vnode, 'props', key, value);
                  });
                  if (keypath) {
                      const key = join$1(DIRECTIVE_BINDING, keypath);
                      setPair($vnode, KEY_DIRECTIVES, key, {
                          ns: DIRECTIVE_BINDING,
                          name: EMPTY_STRING,
                          key,
                          modifier: join$1(keypath, RAW_WILDCARD),
                          hooks: directives[DIRECTIVE_BINDING],
                      });
                  }
              }
          }
      }, renderElementVnode = function (vnode, tag, attrs, childs, slots) {
          if (tag) {
              const componentName = context.get(tag);
              vnode.tag = componentName;
          }
          if (attrs) {
              $vnode = vnode;
              attrs();
              $vnode = UNDEFINED;
          }
          // childs 和 slots 不可能同时存在
          if (childs) {
              vnodeStack.push(vnode.children = []);
              childs();
              pop(vnodeStack);
          }
          else if (slots) {
              const renderSlots = {};
              each$2(slots, function (slot, name) {
                  vnodeStack.push([]);
                  slot();
                  const vnodes = pop(vnodeStack);
                  renderSlots[name] = vnodes.length ? vnodes : UNDEFINED;
              });
              vnode.slots = renderSlots;
          }
          vnode.context = context;
          vnode.keypath = $scope.$keypath;
          const vnodeList = last(vnodeStack);
          if (vnodeList) {
              push(vnodeList, vnode);
          }
          return vnode;
      }, renderExpressionIdentifier = function (name, lookup, offset, holder, depIgnore, stack) {
          const myStack = stack || $stack, result = findValue(myStack, myStack.length - ((offset || 0) + 1), name, lookup, depIgnore);
          return holder ? result : result.value;
      }, renderExpressionMemberKeypath = function (identifier, runtimeKeypath) {
          unshift(runtimeKeypath, identifier);
          return join(runtimeKeypath, RAW_DOT);
      }, renderExpressionMemberLiteral = function (value, staticKeypath, runtimeKeypath, holder$1) {
          if (isDef(runtimeKeypath)) {
              staticKeypath = join(runtimeKeypath, RAW_DOT);
          }
          const match = get(value, staticKeypath);
          holder.keypath = UNDEFINED;
          holder.value = match ? match.value : UNDEFINED;
          return holder$1 ? holder : holder.value;
      }, renderExpressionCall = function (fn, args, holder$1) {
          holder.keypath = UNDEFINED;
          // 当 holder 为 true, args 为空时，args 会传入 false
          holder.value = execute(fn, context, args || UNDEFINED);
          return holder$1 ? holder : holder.value;
      }, 
      // <slot name="xx"/>
      renderSlot = function (name, defaultRender) {
          const vnodeList = last(vnodeStack), vnodes = context.get(name);
          if (vnodeList) {
              if (vnodes) {
                  each(vnodes, function (vnode) {
                      push(vnodeList, vnode);
                      vnode.slot = name;
                      vnode.parent = context;
                  });
              }
              else if (defaultRender) {
                  defaultRender();
              }
          }
      }, 
      // {{#partial name}}
      //   xx
      // {{/partial}}
      renderPartial = function (name, render) {
          localPartials[name] = render;
      }, 
      // {{> name}}
      renderImport = function (name) {
          if (localPartials[name]) {
              localPartials[name]();
          }
          else {
              const partial = partials[name];
              if (partial) {
                  partial(renderExpressionIdentifier, renderExpressionMemberKeypath, renderExpressionMemberLiteral, renderExpressionCall, renderTextVnode, renderAttributeVnode, renderPropertyVnode, renderLazyVnode, renderTransitionVnode, renderBindingVnode, renderModelVnode, renderEventMethodVnode, renderEventNameVnode, renderDirectiveVnode, renderSpreadVnode, renderElementVnode, renderSlot, renderPartial, renderImport, renderEach, renderRange, toString);
              }
          }
      }, eachHandler = function (generate, item, key, keypath, index, length) {
          const lastScope = $scope, lastStack = $stack;
          // each 会改变 keypath
          $scope = { $keypath: keypath };
          $stack = lastStack.concat($scope);
          // 避免模板里频繁读取 list.length
          if (isDef(length)) {
              $scope.$length = length;
          }
          // 业务层是否写了 expr:index
          if (index) {
              $scope[index] = key;
          }
          // 无法通过 context.get($keypath + key) 读取到数据的场景
          // 必须把 item 写到 scope
          if (!keypath) {
              $scope.$item = item;
          }
          generate();
          $scope = lastScope;
          $stack = lastStack;
      }, renderEach = function (generate, holder, index) {
          const { keypath, value } = holder;
          if (array(value)) {
              for (let i = 0, length = value.length; i < length; i++) {
                  eachHandler(generate, value[i], i, keypath
                      ? join$1(keypath, EMPTY_STRING + i)
                      : EMPTY_STRING, index, length);
              }
          }
          else if (object(value)) {
              for (let key in value) {
                  eachHandler(generate, value[key], key, keypath
                      ? join$1(keypath, key)
                      : EMPTY_STRING, index);
              }
          }
      }, renderRange = function (generate, from, to, equal, index) {
          let count = 0;
          if (from < to) {
              if (equal) {
                  for (let i = from; i <= to; i++) {
                      eachHandler(generate, i, count++, EMPTY_STRING, index);
                  }
              }
              else {
                  for (let i = from; i < to; i++) {
                      eachHandler(generate, i, count++, EMPTY_STRING, index);
                  }
              }
          }
          else {
              if (equal) {
                  for (let i = from; i >= to; i--) {
                      eachHandler(generate, i, count++, EMPTY_STRING, index);
                  }
              }
              else {
                  for (let i = from; i > to; i--) {
                      eachHandler(generate, i, count++, EMPTY_STRING, index);
                  }
              }
          }
      };
      return template(renderExpressionIdentifier, renderExpressionMemberKeypath, renderExpressionMemberLiteral, renderExpressionCall, renderTextVnode, renderAttributeVnode, renderPropertyVnode, renderLazyVnode, renderTransitionVnode, renderBindingVnode, renderModelVnode, renderEventMethodVnode, renderEventNameVnode, renderDirectiveVnode, renderSpreadVnode, renderElementVnode, renderSlot, renderPartial, renderImport, renderEach, renderRange, toString);
  }
  
  // 这里先写 IE9 支持的接口
  let innerText = 'textContent', innerHTML = 'innerHTML', findElement = function (selector) {
      const node = DOCUMENT.querySelector(selector);
      if (node) {
          return node;
      }
  }, addEventListener = function (node, type, listener) {
      node.addEventListener(type, listener, FALSE);
  }, removeEventListener = function (node, type, listener) {
      node.removeEventListener(type, listener, FALSE);
  }, 
  // IE9 不支持 classList
  addElementClass = function (node, className) {
      node.classList.add(className);
  }, removeElementClass = function (node, className) {
      node.classList.remove(className);
  }, createEvent = function (event, node) {
      return event;
  };
  {
      if (DOCUMENT) {
          // 此时 document.body 不一定有值，比如 script 放在 head 里
          if (!DOCUMENT.documentElement.classList) {
              addElementClass = function (node, className) {
                  const classes = node.className.split(CHAR_WHITESPACE);
                  if (!has(classes, className)) {
                      push(classes, className);
                      node.className = join(classes, CHAR_WHITESPACE);
                  }
              };
              removeElementClass = function (node, className) {
                  const classes = node.className.split(CHAR_WHITESPACE);
                  if (remove(classes, className)) {
                      node.className = join(classes, CHAR_WHITESPACE);
                  }
              };
          }
      }
  }
  const CHAR_WHITESPACE = ' ', 
  /**
   * 绑定在 HTML 元素上的事件发射器
   */
  EMITTER = '$emitter', 
  /**
   * 跟输入事件配套使用的事件
   */
  COMPOSITION_START = 'compositionstart', 
  /**
   * 跟输入事件配套使用的事件
   */
  COMPOSITION_END = 'compositionend', domain = 'http://www.w3.org/', namespaces = {
      svg: domain + '2000/svg',
  }, specialEvents = {};
  specialEvents[EVENT_MODEL] = {
      on(node, listener) {
          let locked = FALSE;
          on(node, COMPOSITION_START, listener[COMPOSITION_START] = function () {
              locked = TRUE;
          });
          on(node, COMPOSITION_END, listener[COMPOSITION_END] = function (event) {
              locked = FALSE;
              listener(event);
          });
          addEventListener(node, EVENT_INPUT, listener[EVENT_INPUT] = function (event) {
              if (!locked) {
                  listener(event);
              }
          });
      },
      off(node, listener) {
          off(node, COMPOSITION_START, listener[COMPOSITION_START]);
          off(node, COMPOSITION_END, listener[COMPOSITION_END]);
          removeEventListener(node, EVENT_INPUT, listener[EVENT_INPUT]);
          listener[COMPOSITION_START] =
              listener[COMPOSITION_END] =
                  listener[EVENT_INPUT] = UNDEFINED;
      }
  };
  function createElement(tag, isSvg) {
      return isSvg
          ? DOCUMENT.createElementNS(namespaces.svg, tag)
          : DOCUMENT.createElement(tag);
  }
  function createText(text) {
      return DOCUMENT.createTextNode(text);
  }
  function createComment(text) {
      return DOCUMENT.createComment(text);
  }
  function prop(node, name, value) {
      if (isDef(value)) {
          set(node, name, value, FALSE);
      }
      else {
          const holder = get(node, name);
          if (holder) {
              return holder.value;
          }
      }
  }
  function removeProp(node, name, hint) {
      set(node, name, hint === HINT_BOOLEAN
          ? FALSE
          : EMPTY_STRING, FALSE);
  }
  function attr(node, name, value) {
      if (isDef(value)) {
          node.setAttribute(name, value);
      }
      else {
          // value 还可能是 null
          const value = node.getAttribute(name);
          if (value != NULL) {
              return value;
          }
      }
  }
  function removeAttr(node, name) {
      node.removeAttribute(name);
  }
  function before(parentNode, node, beforeNode) {
      parentNode.insertBefore(node, beforeNode);
  }
  function append(parentNode, node) {
      parentNode.appendChild(node);
  }
  function replace(parentNode, node, oldNode) {
      parentNode.replaceChild(node, oldNode);
  }
  function remove$2(parentNode, node) {
      parentNode.removeChild(node);
  }
  function parent(node) {
      const { parentNode } = node;
      if (parentNode) {
          return parentNode;
      }
  }
  function next(node) {
      const { nextSibling } = node;
      if (nextSibling) {
          return nextSibling;
      }
  }
  const find = findElement;
  function tag(node) {
      if (node.nodeType === 1) {
          return lower(node.tagName);
      }
  }
  function text(node, text, isStyle, isOption) {
      if (isDef(text)) {
          {
              node[innerText] = text;
          }
      }
      else {
          return node[innerText];
      }
  }
  function html(node, html, isStyle, isOption) {
      if (isDef(html)) {
          {
              node[innerHTML] = html;
          }
      }
      else {
          return node[innerHTML];
      }
  }
  const addClass = addElementClass;
  const removeClass = removeElementClass;
  function on(node, type, listener, context) {
      const emitter = node[EMITTER] || (node[EMITTER] = new Emitter()), nativeListeners = emitter.nativeListeners || (emitter.nativeListeners = {});
      // 一个元素，相同的事件，只注册一个 native listener
      if (!nativeListeners[type]) {
          // 特殊事件
          const special = specialEvents[type], 
          // 唯一的原生监听器
          nativeListener = function (event) {
              const customEvent = event instanceof CustomEvent
                  ? event
                  : new CustomEvent(event.type, createEvent(event, node));
              if (customEvent.type !== type) {
                  customEvent.type = type;
              }
              emitter.fire(type, [customEvent]);
          };
          nativeListeners[type] = nativeListener;
          if (special) {
              special.on(node, nativeListener);
          }
          else {
              addEventListener(node, type, nativeListener);
          }
      }
      emitter.on(type, {
          fn: listener,
          ctx: context,
      });
  }
  function off(node, type, listener) {
      const emitter = node[EMITTER], { listeners, nativeListeners } = emitter;
      // emitter 会根据 type 和 listener 参数进行适当的删除
      emitter.off(type, listener);
      // 如果注册的 type 事件都解绑了，则去掉原生监听器
      if (nativeListeners && !emitter.has(type)) {
          const special = specialEvents[type], nativeListener = nativeListeners[type];
          if (special) {
              special.off(node, nativeListener);
          }
          else {
              removeEventListener(node, type, nativeListener);
          }
          delete nativeListeners[type];
      }
      if (falsy$2(listeners)) {
          node[EMITTER] = UNDEFINED;
      }
  }
  function addSpecialEvent(type, hooks) {
      specialEvents[type] = hooks;
  }
  
  var domApi = /*#__PURE__*/Object.freeze({
    createElement: createElement,
    createText: createText,
    createComment: createComment,
    prop: prop,
    removeProp: removeProp,
    attr: attr,
    removeAttr: removeAttr,
    before: before,
    append: append,
    replace: replace,
    remove: remove$2,
    parent: parent,
    next: next,
    find: find,
    tag: tag,
    text: text,
    html: html,
    addClass: addClass,
    removeClass: removeClass,
    on: on,
    off: off,
    addSpecialEvent: addSpecialEvent
  });
  
  /**
   * 计算属性
   *
   * 可配置 cache、deps、get、set 等
   */
  class Computed {
      constructor(keypath, sync, cache, deps, observer, getter, setter) {
          const instance = this;
          instance.keypath = keypath;
          instance.cache = cache;
          instance.deps = deps;
          instance.context = observer.context;
          instance.observer = observer;
          instance.getter = getter;
          instance.setter = setter;
          instance.unique = {};
          instance.watcher = function ($0, $1, $2) {
              // 计算属性的依赖变了会走进这里
              const oldValue = instance.value, newValue = instance.get(TRUE);
              if (newValue !== oldValue) {
                  observer.diff(keypath, newValue, oldValue);
              }
          };
          instance.watcherOptions = {
              sync,
              watcher: instance.watcher
          };
          if (instance.fixed = !falsy(deps)) {
              each(deps, function (dep) {
                  observer.watch(dep, instance.watcherOptions);
              });
          }
      }
      /**
       * 读取计算属性的值
       *
       * @param force 是否强制刷新缓存
       */
      get(force) {
          const instance = this, { getter, context } = instance;
          // 禁用缓存
          if (!instance.cache) {
              instance.value = execute(getter, context);
          }
          // 减少取值频率，尤其是处理复杂的计算规则
          else if (force || !has$2(instance, RAW_VALUE)) {
              // 如果写死了依赖，则不需要收集依赖
              if (instance.fixed) {
                  instance.value = execute(getter, context);
              }
              else {
                  // 清空上次收集的依赖
                  instance.unbind();
                  // 开始收集新的依赖
                  const lastComputed = Computed.current;
                  Computed.current = instance;
                  instance.value = execute(getter, context);
                  // 绑定新的依赖
                  instance.bind();
                  Computed.current = lastComputed;
              }
          }
          return instance.value;
      }
      set(value) {
          const { setter, context } = this;
          if (setter) {
              setter.call(context, value);
          }
      }
      /**
       * 添加依赖
       *
       * 这里只是为了保证依赖唯一，最后由 bind() 实现绑定
       *
       * @param dep
       */
      add(dep) {
          this.unique[dep] = TRUE;
      }
      /**
       * 绑定依赖
       */
      bind() {
          const { unique, deps, observer, watcherOptions } = this;
          each$2(unique, function (_, dep) {
              push(deps, dep);
              observer.watch(dep, watcherOptions);
          });
          // 用完重置
          // 方便下次收集依赖
          this.unique = {};
      }
      /**
       * 解绑依赖
       */
      unbind() {
          const { deps, observer, watcher } = this;
          each(deps, function (dep) {
              observer.unwatch(dep, watcher);
          }, TRUE);
          deps.length = 0;
      }
  }
  
  /**
   * 从 keypath 数组中选择和 keypath 最匹配的那一个
   *
   * @param sorted 经过排序的 keypath 数组
   * @param keypath
   */
  function matchBest (sorted, keypath) {
      let result;
      each(sorted, function (prefix) {
          const length = match(keypath, prefix);
          if (length >= 0) {
              result = {
                  name: prefix,
                  prop: slice(keypath, length)
              };
              return FALSE;
          }
      });
      return result;
  }
  
  function readValue (source, keypath) {
      if (source == NULL || keypath === EMPTY_STRING) {
          return source;
      }
      const result = get(source, keypath);
      if (result) {
          return result.value;
      }
  }
  
  /**
   * 对比新旧数组
   *
   * @param newValue
   * @param oldValue
   * @param callback
   */
  function diffString (newValue, oldValue, callback) {
      const newIsString = string(newValue), oldIsString = string(oldValue);
      if (newIsString || oldIsString) {
          callback(RAW_LENGTH, newIsString ? newValue.length : UNDEFINED, oldIsString ? oldValue.length : UNDEFINED);
          return TRUE;
      }
  }
  
  /**
   * 对比新旧数组
   *
   * @param newValue
   * @param oldValue
   * @param callback
   */
  function diffArray (newValue, oldValue, callback) {
      const newIsArray = array(newValue), oldIsArray = array(oldValue);
      if (newIsArray || oldIsArray) {
          const newLength = newIsArray ? newValue.length : UNDEFINED, oldLength = oldIsArray ? oldValue.length : UNDEFINED;
          callback(RAW_LENGTH, newLength, oldLength);
          for (let i = 0, length = Math.max(newLength || 0, oldLength || 0); i < length; i++) {
              callback('' + i, newValue ? newValue[i] : UNDEFINED, oldValue ? oldValue[i] : UNDEFINED);
          }
          return TRUE;
      }
  }
  
  /**
   * 对比新旧对象
   *
   * @param newValue
   * @param oldValue
   * @param callback
   */
  function diffObject (newValue, oldValue, callback) {
      const newIsObject = object(newValue), oldIsObject = object(oldValue);
      if (newIsObject || oldIsObject) {
          newValue = newIsObject ? newValue : EMPTY_OBJECT;
          oldValue = oldIsObject ? oldValue : EMPTY_OBJECT;
          if (newIsObject) {
              each$2(newValue, function (value, key) {
                  if (value !== oldValue[key]) {
                      callback(key, value, oldValue[key]);
                  }
              });
          }
          if (oldIsObject) {
              each$2(oldValue, function (value, key) {
                  if (value !== newValue[key]) {
                      callback(key, newValue[key], value);
                  }
              });
          }
      }
  }
  
  function diffRecursion(keypath, newValue, oldValue, watchFuzzyKeypaths, callback) {
      const diff = function (subKeypath, subNewValue, subOldValue) {
          if (subNewValue !== subOldValue) {
              const newKeypath = join$1(keypath, subKeypath);
              each(watchFuzzyKeypaths, function (fuzzyKeypath) {
                  if (isDef(matchFuzzy(newKeypath, fuzzyKeypath))) {
                      callback(fuzzyKeypath, newKeypath, subNewValue, subOldValue);
                  }
              });
              diffRecursion(newKeypath, subNewValue, subOldValue, watchFuzzyKeypaths, callback);
          }
      };
      diffString(newValue, oldValue, diff)
          || diffArray(newValue, oldValue, diff)
          || diffObject(newValue, oldValue, diff);
  }
  
  function diffWatcher (keypath, newValue, oldValue, watcher, isRecursive, callback) {
      let fuzzyKeypaths;
      // 遍历监听的 keypath，如果未被监听，则无需触发任何事件
      each$2(watcher, function (_, watchKeypath) {
          // 模糊监听，如 users.*.name
          if (isFuzzy(watchKeypath)) {
              // 如果当前修改的是 users.0 整个对象
              // users.0 和 users.*.name 无法匹配
              // 此时要知道设置 users.0 到底会不会改变 users.*.name 需要靠递归了
              // 如果匹配，则无需递归
              if (isDef(matchFuzzy(keypath, watchKeypath))) {
                  callback(watchKeypath, keypath, newValue, oldValue);
              }
              else if (isRecursive) {
                  if (fuzzyKeypaths) {
                      push(fuzzyKeypaths, watchKeypath);
                  }
                  else {
                      fuzzyKeypaths = [watchKeypath];
                  }
              }
              return;
          }
          // 不是模糊匹配，直接靠前缀匹配
          // 比如监听的是 users.0.name，此时修改 users.0，则直接读出子属性值，判断是否相等
          const length = match(watchKeypath, keypath);
          if (length >= 0) {
              const subKeypath = slice(watchKeypath, length), subNewValue = readValue(newValue, subKeypath), subOldValue = readValue(oldValue, subKeypath);
              if (subNewValue !== subOldValue) {
                  callback(watchKeypath, watchKeypath, subNewValue, subOldValue);
              }
          }
      });
      // 存在模糊匹配的需求
      // 必须对数据进行递归
      // 性能确实会慢一些，但是很好用啊，几乎可以监听所有的数据
      if (fuzzyKeypaths) {
          diffRecursion(keypath, newValue, oldValue, fuzzyKeypaths, callback);
      }
  }
  
  /**
   * 触发异步变化时，用此函数过滤下，哪些 listener 应该执行
   *
   * @param item
   * @param data
   */
  function filterWatcher (_, args, options) {
      if (options.count && args) {
          // 采用计数器的原因是，同一个 options 可能执行多次
          // 比如监听 user.*，如果同批次修改了 user.name 和 user.age
          // 这个监听器会调用多次，如果第一次执行就把 count 干掉了，第二次就无法执行了
          options.count--;
          // 新旧值不相等
          return args[0] !== args[1];
      }
  }
  
  // 避免频繁创建对象
  const optionsHolder = {
      watcher: EMPTY_FUNCTION
  };
  /**
   * 格式化 watch options
   *
   * @param options
   */
  function formatWatcherOptions (options, immediate) {
      if (func(options)) {
          optionsHolder.watcher = options;
          optionsHolder.immediate = immediate === TRUE;
          return optionsHolder;
      }
      if (options && options.watcher) {
          return options;
      }
  }
  
  /**
   * 观察者有两种观察模式：
   *
   * 1. 同步监听
   * 2. 异步监听
   *
   * 对于`计算属性`这种需要实时变化的对象，即它的依赖变了，它需要立即跟着变，否则会出现不一致的问题
   * 这种属于同步监听
   *
   * 对于外部调用 observer.watch('keypath', listener)，属于异步监听，它只关心是否变了，而不关心是否是立即触发的
   */
  class Observer {
      constructor(data, context) {
          const instance = this;
          instance.data = data || {};
          instance.context = context || instance;
          instance.nextTask = new NextTask();
          instance.syncEmitter = new Emitter();
          instance.asyncEmitter = new Emitter();
          instance.asyncChanges = {};
      }
      /**
       * 获取数据
       *
       * @param keypath
       * @param defaultValue
       * @param depIgnore
       * @return
       */
      get(keypath, defaultValue, depIgnore) {
          const instance = this, currentComputed = Computed.current, { data, computed, reversedComputedKeys } = instance;
          // 传入 '' 获取整个 data
          if (keypath === EMPTY_STRING) {
              return data;
          }
          // 调用 get 时，外面想要获取依赖必须设置是谁在收集依赖
          // 如果没设置，则跳过依赖收集
          if (currentComputed && !depIgnore) {
              currentComputed.add(keypath);
          }
          let result, target;
          if (computed) {
              target = computed[keypath];
              if (target) {
                  return target.get();
              }
              if (reversedComputedKeys) {
                  const match = matchBest(reversedComputedKeys, keypath);
                  if (match && match.prop) {
                      result = get(computed[match.name].get(), match.prop);
                  }
              }
          }
          if (!result) {
              result = get(data, keypath);
          }
          return result ? result.value : defaultValue;
      }
      /**
       * 更新数据
       *
       * @param keypath
       * @param value
       */
      set(keypath, value) {
          const instance = this, { data, computed, reversedComputedKeys } = instance, setValue = function (newValue, keypath) {
              const oldValue = instance.get(keypath);
              if (newValue === oldValue) {
                  return;
              }
              let target;
              if (computed) {
                  target = computed[keypath];
                  if (target) {
                      target.set(newValue);
                  }
                  if (reversedComputedKeys) {
                      const match = matchBest(reversedComputedKeys, keypath);
                      if (match && match.prop) {
                          target = computed[match.name];
                          if (target) {
                              const targetValue = target.get();
                              if (object(targetValue)) {
                                  set(targetValue, match.prop, newValue);
                              }
                          }
                      }
                  }
              }
              if (!target) {
                  set(data, keypath, newValue);
              }
              instance.diff(keypath, newValue, oldValue);
          };
          if (string(keypath)) {
              setValue(value, keypath);
          }
          else if (object(keypath)) {
              each$2(keypath, setValue);
          }
      }
      /**
       * 同步调用的 diff，用于触发 syncEmitter，以及唤醒 asyncEmitter
       *
       * @param keypath
       * @param newValue
       * @param oldValue
       */
      diff(keypath, newValue, oldValue) {
          const instance = this, { syncEmitter, asyncEmitter, asyncChanges } = instance, 
          /**
           * 我们认为 $ 开头的变量是不可递归的
           * 比如浏览器中常见的 $0 表示当前选中元素
           * DOM 元素是不能递归的
           */
          isRecursive = codeAt(keypath) !== 36;
          diffWatcher(keypath, newValue, oldValue, syncEmitter.listeners, isRecursive, function (watchKeypath, keypath, newValue, oldValue) {
              syncEmitter.fire(watchKeypath, [newValue, oldValue, keypath]);
          });
          /**
           * 此处有坑，举个例子
           *
           * observer.watch('a', function () {})
           *
           * observer.set('a', 1)
           *
           * observer.watch('a', function () {})
           *
           * 这里，第一个 watcher 应该触发，但第二个不应该，因为它绑定监听时，值已经是最新的了
           */
          diffWatcher(keypath, newValue, oldValue, asyncEmitter.listeners, isRecursive, function (watchKeypath, keypath, newValue, oldValue) {
              each(asyncEmitter.listeners[watchKeypath], function (item) {
                  item.count++;
              });
              const { keypaths } = asyncChanges[keypath] || (asyncChanges[keypath] = { value: oldValue, keypaths: [] });
              if (!has(keypaths, watchKeypath)) {
                  push(keypaths, watchKeypath);
              }
              if (!instance.pending) {
                  instance.pending = TRUE;
                  instance.nextTask.append(function () {
                      if (instance.pending) {
                          instance.pending = UNDEFINED;
                          instance.diffAsync();
                      }
                  });
              }
          });
      }
      /**
       * 异步触发的 diff
       */
      diffAsync() {
          const instance = this, { asyncEmitter, asyncChanges } = instance;
          instance.asyncChanges = {};
          each$2(asyncChanges, function (change, keypath) {
              const args = [instance.get(keypath), change.value, keypath];
              // 不能在这判断新旧值是否相同，相同就不 fire
              // 因为前面标记了 count，在这中断会导致 count 无法清除
              each(change.keypaths, function (watchKeypath) {
                  asyncEmitter.fire(watchKeypath, args, filterWatcher);
              });
          });
      }
      /**
       * 添加计算属性
       *
       * @param keypath
       * @param computed
       */
      addComputed(keypath, options) {
          let cache = TRUE, sync = TRUE, deps = [], getter, setter;
          if (func(options)) {
              getter = options;
          }
          else if (object(options)) {
              const computedOptions = options;
              if (boolean(computedOptions.cache)) {
                  cache = computedOptions.cache;
              }
              if (boolean(computedOptions.sync)) {
                  sync = computedOptions.sync;
              }
              // 因为可能会修改 deps，所以这里创建一个新的 deps，避免影响外部传入的 deps
              if (array(computedOptions.deps)) {
                  deps = copy(computedOptions.deps);
              }
              if (func(computedOptions.get)) {
                  getter = computedOptions.get;
              }
              if (func(computedOptions.set)) {
                  setter = computedOptions.set;
              }
          }
          if (getter) {
              const instance = this, computed = new Computed(keypath, sync, cache, deps, instance, getter, setter);
              if (!instance.computed) {
                  instance.computed = {};
              }
              instance.computed[keypath] = computed;
              instance.reversedComputedKeys = sort(instance.computed, TRUE);
              return computed;
          }
      }
      /**
       * 移除计算属性
       *
       * @param keypath
       */
      removeComputed(keypath) {
          const instance = this, { computed } = instance;
          if (computed && has$2(computed, keypath)) {
              delete computed[keypath];
              instance.reversedComputedKeys = sort(computed, TRUE);
          }
      }
      /**
       * 监听数据变化
       *
       * @param keypath
       * @param watcher
       * @param immediate
       */
      watch(keypath, watcher, immediate) {
          const instance = this, { context, syncEmitter, asyncEmitter } = instance, bind = function (keypath, options) {
              const emitter = options.sync ? syncEmitter : asyncEmitter, 
              // formatWatcherOptions 保证了 options.watcher 一定存在
              listener = {
                  fn: options.watcher,
                  ctx: context,
                  count: 0,
              };
              if (options.once) {
                  listener.max = 1;
              }
              emitter.on(keypath, listener);
              if (options.immediate) {
                  execute(options.watcher, context, [
                      instance.get(keypath),
                      UNDEFINED,
                      keypath
                  ]);
              }
          };
          if (string(keypath)) {
              bind(keypath, formatWatcherOptions(watcher, immediate));
              return;
          }
          each$2(keypath, function (options, keypath) {
              bind(keypath, formatWatcherOptions(options));
          });
      }
      /**
       * 取消监听数据变化
       *
       * @param keypath
       * @param watcher
       */
      unwatch(keypath, watcher) {
          this.syncEmitter.off(keypath, watcher);
          this.asyncEmitter.off(keypath, watcher);
      }
      /**
       * 取反 keypath 对应的数据
       *
       * 不管 keypath 对应的数据是什么类型，操作后都是布尔型
       *
       * @param keypath
       * @return 取反后的布尔值
       */
      toggle(keypath) {
          const value = !this.get(keypath);
          this.set(keypath, value);
          return value;
      }
      /**
       * 递增 keypath 对应的数据
       *
       * 注意，最好是整型的加法，如果涉及浮点型，不保证计算正确
       *
       * @param keypath 值必须能转型成数字，如果不能，则默认从 0 开始递增
       * @param step 步进值，默认是 1
       * @param max 可以递增到的最大值，默认不限制
       */
      increase(keypath, step, max) {
          const value = toNumber(this.get(keypath), 0) + (step || 1);
          if (!number(max) || value <= max) {
              this.set(keypath, value);
              return value;
          }
      }
      /**
       * 递减 keypath 对应的数据
       *
       * 注意，最好是整型的减法，如果涉及浮点型，不保证计算正确
       *
       * @param keypath 值必须能转型成数字，如果不能，则默认从 0 开始递减
       * @param step 步进值，默认是 1
       * @param min 可以递减到的最小值，默认不限制
       */
      decrease(keypath, step, min) {
          const value = toNumber(this.get(keypath), 0) - (step || 1);
          if (!number(min) || value >= min) {
              this.set(keypath, value);
              return value;
          }
      }
      /**
       * 在数组指定位置插入元素
       *
       * @param keypath
       * @param item
       * @param index
       */
      insert(keypath, item, index) {
          let list = this.get(keypath);
          list = !array(list) ? [] : copy(list);
          const { length } = list;
          if (index === TRUE || index === length) {
              list.push(item);
          }
          else if (index === FALSE || index === 0) {
              list.unshift(item);
          }
          else if (index > 0 && index < length) {
              list.splice(index, 0, item);
          }
          else {
              return;
          }
          this.set(keypath, list);
          return TRUE;
      }
      /**
       * 在数组尾部添加元素
       *
       * @param keypath
       * @param item
       */
      append(keypath, item) {
          return this.insert(keypath, item, TRUE);
      }
      /**
       * 在数组首部添加元素
       *
       * @param keypath
       * @param item
       */
      prepend(keypath, item) {
          return this.insert(keypath, item, FALSE);
      }
      /**
       * 通过索引移除数组中的元素
       *
       * @param keypath
       * @param index
       */
      removeAt(keypath, index) {
          let list = this.get(keypath);
          if (array(list)
              && index >= 0
              && index < list.length) {
              list = copy(list);
              list.splice(index, 1);
              this.set(keypath, list);
              return TRUE;
          }
      }
      /**
       * 直接移除数组中的元素
       *
       * @param keypath
       * @param item
       */
      remove(keypath, item) {
          let list = this.get(keypath);
          if (array(list)) {
              list = copy(list);
              if (remove(list, item)) {
                  this.set(keypath, list);
                  return TRUE;
              }
          }
      }
      /**
       * 拷贝任意数据，支持深拷贝
       *
       * @param data
       * @param deep
       */
      copy(data, deep) {
          return copy(data, deep);
      }
      /**
       * 销毁
       */
      destroy() {
          const instance = this;
          instance.syncEmitter.off();
          instance.asyncEmitter.off();
          instance.nextTask.clear();
          clear(instance);
      }
  }
  
  /**
   * 节流调用
   *
   * @param fn 需要节制调用的函数
   * @param delay 调用的时间间隔，单位毫秒
   * @param immediate 是否立即触发
   * @return 节流函数
   */
  function debounce (fn, delay, immediate) {
      let timer;
      return function () {
          if (!timer) {
              const args = toArray(arguments);
              if (immediate) {
                  execute(fn, UNDEFINED, args);
              }
              timer = setTimeout(function () {
                  timer = UNDEFINED;
                  if (!immediate) {
                      execute(fn, UNDEFINED, args);
                  }
              }, delay);
          }
      };
  }
  
  function bind(node, directive, vnode) {
      let { key, name, modifier, handler } = directive, { lazy } = vnode;
      if (!handler) {
          return;
      }
      if (lazy) {
          const value = lazy[name] || lazy[EMPTY_STRING];
          if (value === TRUE) {
              name = EVENT_CHANGE;
          }
          else if (value > 0) {
              handler = debounce(handler, value, 
              // 避免连续多次点击，主要用于提交表单场景
              // 移动端的 tap 事件可自行在业务层打补丁实现
              name === EVENT_CLICK || name === EVENT_TAP);
          }
      }
      let element;
      if (vnode.isComponent) {
          const component = node;
          if (modifier === MODIFER_NATIVE) {
              element = component.$el;
              on(element, name, handler);
              vnode.data[key] = function () {
                  off(element, name, handler);
              };
          }
          else {
              // 还原命名空间
              if (modifier) {
                  name += RAW_DOT + modifier;
              }
              component.on(name, handler);
              vnode.data[key] = function () {
                  component.off(name, handler);
              };
          }
      }
      else {
          element = node;
          on(element, name, handler);
          vnode.data[key] = function () {
              off(element, name, handler);
          };
      }
  }
  function unbind(node, directive, vnode) {
      execute(vnode.data[directive.key]);
  }
  
  var event = /*#__PURE__*/Object.freeze({
    bind: bind,
    unbind: unbind
  });
  
  function debounceIfNeeded(fn, lazy) {
      // 应用 lazy
      return lazy && lazy !== TRUE
          ? debounce(fn, lazy)
          : fn;
  }
  const inputControl = {
      set(node, value) {
          node.value = toString(value);
      },
      sync(node, keypath, context) {
          context.set(keypath, node.value);
      },
      name: RAW_VALUE
  }, radioControl = {
      set(node, value) {
          node.checked = node.value === toString(value);
      },
      sync(node, keypath, context) {
          if (node.checked) {
              context.set(keypath, node.value);
          }
      },
      name: 'checked'
  }, checkboxControl = {
      set(node, value) {
          node.checked = array(value)
              ? has(value, node.value, FALSE)
              : !!value;
      },
      sync(node, keypath, context) {
          const value = context.get(keypath);
          if (array(value)) {
              if (node.checked) {
                  context.append(keypath, node.value);
              }
              else {
                  context.removeAt(keypath, indexOf(value, node.value, FALSE));
              }
          }
          else {
              context.set(keypath, node.checked);
          }
      },
      name: 'checked'
  }, selectControl = {
      set(node, value) {
          each(toArray(node.options), node.multiple
              ? function (option) {
                  option.selected = has(value, option.value, FALSE);
              }
              : function (option, index) {
                  if (option.value == value) {
                      node.selectedIndex = index;
                      return FALSE;
                  }
              });
      },
      sync(node, keypath, context) {
          const { options } = node;
          if (node.multiple) {
              const values = [];
              each(toArray(options), function (option) {
                  if (option.selected) {
                      push(values, option.value);
                  }
              });
              context.set(keypath, values);
          }
          else {
              context.set(keypath, options[node.selectedIndex].value);
          }
      },
      name: RAW_VALUE
  };
  const once = TRUE;
  function bind$1(node, directive, vnode) {
      let { context, lazy, isComponent } = vnode, dataBinding = directive.modifier, lazyValue = lazy && (lazy[DIRECTIVE_MODEL] || lazy[EMPTY_STRING]), set, unbind;
      if (isComponent) {
          let component = node, viewBinding = component.$model, viewSyncing = debounceIfNeeded(function (newValue) {
              context.set(dataBinding, newValue);
          }, lazyValue);
          set = function (newValue) {
              if (set) {
                  component.set(viewBinding, newValue);
              }
          };
          unbind = function () {
              component.unwatch(viewBinding, viewSyncing);
          };
          component.watch(viewBinding, viewSyncing);
      }
      else {
          let element = node, control = vnode.tag === 'select'
              ? selectControl
              : inputControl, 
          // checkbox,radio,select 监听的是 change 事件
          eventName = EVENT_CHANGE;
          if (control === inputControl) {
              const type = node.type;
              if (type === 'radio') {
                  control = radioControl;
              }
              else if (type === 'checkbox') {
                  control = checkboxControl;
              }
              // 如果是输入框，则切换成 model 事件
              // model 事件是个 yox-dom 实现的特殊事件
              // 不会在输入法组合文字过程中得到触发事件
              else if (lazyValue !== TRUE) {
                  eventName = EVENT_MODEL;
              }
          }
          set = function (newValue) {
              if (set) {
                  control.set(element, newValue);
              }
          };
          const sync = debounceIfNeeded(function () {
              control.sync(element, dataBinding, context);
          }, lazyValue);
          unbind = function () {
              off(element, eventName, sync);
          };
          on(element, eventName, sync);
          control.set(element, directive.value);
      }
      // 监听数据，修改界面
      context.watch(dataBinding, set);
      vnode.data[directive.key] = function () {
          context.unwatch(dataBinding, set);
          set = UNDEFINED;
          unbind();
      };
  }
  function unbind$1(node, directive, vnode) {
      execute(vnode.data[directive.key]);
  }
  
  var model = /*#__PURE__*/Object.freeze({
    once: once,
    bind: bind$1,
    unbind: unbind$1
  });
  
  const once$1 = TRUE;
  function bind$2(node, directive, vnode) {
      // binding 可能是模糊匹配
      // 比如延展属性 {{...obj}}，这里 binding 会是 `obj.*`
      let binding = directive.modifier, 
      // 提前判断好是否是模糊匹配，避免 watcher 频繁执行判断逻辑
      isFuzzy$1 = isFuzzy(binding), watcher = function (newValue, _, keypath) {
          if (watcher) {
              const name = isFuzzy$1
                  ? matchFuzzy(keypath, binding)
                  : directive.name;
              if (vnode.isComponent) {
                  const component = node;
                  component.checkProp(name, newValue);
                  component.set(name, newValue);
              }
              else {
                  const element = node;
                  if (isDef(directive.hint)) {
                      prop(element, name, newValue);
                  }
                  else {
                      attr(element, name, newValue);
                  }
              }
          }
      };
      vnode.context.watch(binding, watcher);
      vnode.data[directive.key] = function () {
          vnode.context.unwatch(binding, watcher);
          watcher = UNDEFINED;
      };
  }
  function unbind$2(node, directive, vnode) {
      execute(vnode.data[directive.key]);
  }
  
  var binding = /*#__PURE__*/Object.freeze({
    once: once$1,
    bind: bind$2,
    unbind: unbind$2
  });
  
  const globalDirectives = {}, globalTransitions = {}, globalComponents = {}, globalPartials = {}, globalFilters = {}, TEMPLATE_COMPUTED = '$$', selectorPattern = /^[#.][-\w+]+$/;
  class Yox {
      constructor(options) {
          const instance = this, $options = options || EMPTY_OBJECT;
          // 为了冒泡 HOOK_BEFORE_CREATE 事件，必须第一时间创建 emitter
          // 监听各种事件
          // 支持命名空间
          instance.$emitter = new Emitter(TRUE);
          if ($options.events) {
              instance.on($options.events);
          }
          {
              // 当前组件的直接父组件
              if ($options.parent) {
                  instance.$parent = $options.parent;
              }
              // 建立好父子连接后，立即触发钩子
              execute($options[HOOK_BEFORE_CREATE], instance, $options);
              // 冒泡 before create 事件
              instance.fire(HOOK_BEFORE_CREATE + NAMESPACE_HOOK, $options);
          }
          let { data, props, vnode, propTypes, computed, methods, watchers, extensions, } = $options;
          instance.$options = $options;
          if (extensions) {
              extend(instance, extensions);
          }
          // 数据源，默认值仅在创建组件时启用
          const source = props ? copy(props) : {};
          {
              if (propTypes) {
                  each$2(propTypes, function (rule, key) {
                      let value = source[key];
                      if (isUndef(value)) {
                          value = rule.value;
                          if (isDef(value)) {
                              source[key] = rule.type === RAW_FUNCTION
                                  ? value
                                  : func(value)
                                      ? value()
                                      : value;
                          }
                      }
                  });
              }
          }
          // 先放 props
          // 当 data 是函数时，可以通过 this.get() 获取到外部数据
          const observer = instance.$observer = new Observer(source, instance);
          if (computed) {
              each$2(computed, function (options, keypath) {
                  observer.addComputed(keypath, options);
              });
          }
          const extend$1 = func(data) ? execute(data, instance, options) : data;
          if (object(extend$1)) {
              each$2(extend$1, function (value, key) {
                  source[key] = value;
              });
          }
          if (methods) {
              each$2(methods, function (method, name) {
                  instance[name] = method;
              });
          }
          {
              let placeholder = UNDEFINED, { el, root, model, context, replace, template, transitions, components, directives, partials, filters, slots, } = $options;
              if (model) {
                  instance.$model = model;
              }
              // 把 slots 放进数据里，方便 get
              if (slots) {
                  extend(source, slots);
              }
              // 检查 template
              if (string(template)) {
                  // 传了选择器，则取对应元素的 html
                  if (selectorPattern.test(template)) {
                      placeholder = find(template);
                      if (placeholder) {
                          template = html(placeholder);
                          placeholder = UNDEFINED;
                      }
                  }
              }
              // 检查 el
              if (el) {
                  if (string(el)) {
                      const selector = el;
                      if (selectorPattern.test(selector)) {
                          placeholder = find(selector);
                      }
                  }
                  else {
                      placeholder = el;
                  }
                  if (!replace) {
                      append(placeholder, placeholder = createComment(EMPTY_STRING));
                  }
              }
              // 根组件
              if (root) {
                  instance.$root = root;
              }
              // 当前组件是被哪个组件渲染出来的
              // 因为有 slot 机制，$context 不一定等于 $parent
              if (context) {
                  instance.$context = context;
              }
              setFlexibleOptions(instance, RAW_TRANSITION, transitions);
              setFlexibleOptions(instance, RAW_COMPONENT, components);
              setFlexibleOptions(instance, RAW_DIRECTIVE, directives);
              setFlexibleOptions(instance, RAW_PARTIAL, partials);
              setFlexibleOptions(instance, RAW_FILTER, filters);
              // 当存在模板和计算属性时
              // 因为这里把模板当做一种特殊的计算属性
              // 因此模板这个计算属性的优先级应该最高
              if (template) {
                  // 拷贝一份，避免影响外部定义的 watchers
                  const newWatchers = watchers
                      ? copy(watchers)
                      : {};
                  newWatchers[TEMPLATE_COMPUTED] = {
                      // 模板一旦变化，立即刷新
                      sync: TRUE,
                      watcher: function (vnode) {
                          instance.update(vnode, instance.$vnode);
                      }
                  };
                  // 当模板的依赖变了，则重新创建 virtual dom
                  observer.addComputed(TEMPLATE_COMPUTED, {
                      // 当模板依赖变化时，异步通知模板更新
                      sync: FALSE,
                      get: function () {
                          return instance.render();
                      }
                  });
                  afterCreateHook(instance, newWatchers);
                  // 编译模板
                  // 在开发阶段，template 是原始的 html 模板
                  // 在产品阶段，template 是编译后且经过 stringify 的字符串
                  // 当然，这个需要外部自己控制传入的 template 是什么
                  // Yox.compile 会自动判断 template 是否经过编译
                  instance.$template = string(template)
                      ? Yox.compile(template)
                      : template;
                  if (!vnode) {
                      vnode = create(domApi, placeholder, instance, EMPTY_STRING);
                  }
                  instance.update(instance.get(TEMPLATE_COMPUTED), vnode);
                  return;
              }
          }
          afterCreateHook(instance, watchers);
      }
      /**
       * 安装插件
       *
       * 插件必须暴露 install 方法
       */
      static use(plugin) {
          plugin.install(Yox);
      }
      /**
       * 定义组件对象
       */
      static define(options) {
          return options;
      }
      /**
       * 因为组件采用的是异步更新机制，为了在更新之后进行一些操作，可使用 nextTick
       */
      static nextTick(task, context) {
          NextTask.shared().append(task, context);
      }
      /**
       * 编译模板，暴露出来是为了打包阶段的模板预编译
       */
      static compile(template, stringify) {
          {
              return EMPTY_STRING;
          }
      }
      /**
       * 注册全局指令
       */
      static directive(name, directive) {
          {
              if (string(name) && !directive) {
                  return getResource(globalDirectives, name);
              }
              setResource(globalDirectives, name, directive);
          }
      }
      /**
       * 注册全局过渡动画
       */
      static transition(name, transition) {
          {
              if (string(name) && !transition) {
                  return getResource(globalTransitions, name);
              }
              setResource(globalTransitions, name, transition);
          }
      }
      /**
       * 注册全局组件
       */
      static component(name, component) {
          {
              if (string(name) && !component) {
                  return getResource(globalComponents, name);
              }
              setResource(globalComponents, name, component);
          }
      }
      /**
       * 注册全局子模板
       */
      static partial(name, partial) {
          {
              if (string(name) && !partial) {
                  return getResource(globalPartials, name);
              }
              setResource(globalPartials, name, partial, Yox.compile);
          }
      }
      /**
       * 注册全局过滤器
       */
      static filter(name, filter) {
          {
              if (string(name) && !filter) {
                  return getResource(globalFilters, name);
              }
              setResource(globalFilters, name, filter);
          }
      }
      /**
       * 取值
       */
      get(keypath, defaultValue, depIgnore) {
          return this.$observer.get(keypath, defaultValue, depIgnore);
      }
      /**
       * 设值
       */
      set(keypath, value) {
          // 组件经常有各种异步改值，为了避免组件销毁后依然调用 set
          // 这里判断一下，至于其他方法的异步调用就算了，业务自己控制吧
          const { $observer } = this;
          if ($observer) {
              $observer.set(keypath, value);
          }
      }
      /**
       * 监听事件，支持链式调用
       */
      on(type, listener) {
          addEvents(this, type, listener);
          return this;
      }
      /**
       * 监听一次事件，支持链式调用
       */
      once(type, listener) {
          addEvents(this, type, listener, TRUE);
          return this;
      }
      /**
       * 取消监听事件，支持链式调用
       */
      off(type, listener) {
          this.$emitter.off(type, listener);
          return this;
      }
      /**
       * 发射事件
       */
      fire(type, data, downward) {
          // 外部为了使用方便，fire(type) 或 fire(type, data) 就行了
          // 内部为了保持格式统一
          // 需要转成 Event，这样还能知道 target 是哪个组件
          let instance = this, { $emitter, $parent, $children } = instance, event = type instanceof CustomEvent ? type : new CustomEvent(type), namespace = event.ns || (event.ns = $emitter.parse(event.type)), args = [event], isComplete;
          // 告诉外部是谁发出的事件
          if (!event.target) {
              event.target = instance;
          }
          // 比如 fire('name', true) 直接向下发事件
          if (object(data)) {
              push(args, data);
          }
          else if (data === TRUE) {
              downward = TRUE;
          }
          isComplete = $emitter.fire(namespace, args);
          if (isComplete) {
              if (downward) {
                  if ($children) {
                      event.phase = CustomEvent.PHASE_DOWNWARD;
                      each($children, function (child) {
                          return isComplete = child.fire(event, data, TRUE);
                      });
                  }
              }
              else if ($parent) {
                  event.phase = CustomEvent.PHASE_UPWARD;
                  isComplete = $parent.fire(event, data);
              }
          }
          return isComplete;
      }
      /**
       * 监听数据变化，支持链式调用
       */
      watch(keypath, watcher, immediate) {
          this.$observer.watch(keypath, watcher, immediate);
          return this;
      }
      /**
       * 取消监听数据变化，支持链式调用
       */
      unwatch(keypath, watcher) {
          this.$observer.unwatch(keypath, watcher);
          return this;
      }
      /**
       * 加载组件，组件可以是同步或异步，最后会调用 callback
       *
       * @param name 组件名称
       * @param callback 组件加载成功后的回调
       */
      loadComponent(name, callback) {
          {
              if (!loadComponent(this.$components, name, callback)) {
                  {
                      loadComponent(globalComponents, name, callback);
                  }
              }
          }
      }
      /**
       * 创建子组件
       *
       * @param options 组件配置
       * @param vnode 虚拟节点
       */
      createComponent(options, vnode) {
          {
              const instance = this;
              options = copy(options);
              options.root = instance.$root || instance;
              options.parent = instance;
              options.context = vnode.context;
              options.vnode = vnode;
              options.replace = TRUE;
              let { props, slots, directives } = vnode, model = directives && directives[DIRECTIVE_MODEL];
              if (model) {
                  if (!props) {
                      props = {};
                  }
                  const key = options.model || MODEL_PROP_DEFAULT;
                  props[key] = model.value;
                  options.model = key;
              }
              if (props) {
                  options.props = props;
              }
              if (slots) {
                  options.slots = slots;
              }
              const child = new Yox(options);
              push(instance.$children || (instance.$children = []), child);
              const node = child.$el;
              if (node) {
                  vnode.node = node;
              }
              return child;
          }
      }
      /**
       * 注册当前组件级别的指令
       */
      directive(name, directive) {
          {
              const instance = this, { $directives } = instance;
              if (string(name) && !directive) {
                  return getResource($directives, name, Yox.directive);
              }
              setResource($directives || (instance.$directives = {}), name, directive);
          }
      }
      /**
       * 注册当前组件级别的过渡动画
       */
      transition(name, transition) {
          {
              const instance = this, { $transitions } = instance;
              if (string(name) && !transition) {
                  return getResource($transitions, name, Yox.transition);
              }
              setResource($transitions || (instance.$transitions = {}), name, transition);
          }
      }
      /**
       * 注册当前组件级别的组件
       */
      component(name, component) {
          {
              const instance = this, { $components } = instance;
              if (string(name) && !component) {
                  return getResource($components, name, Yox.component);
              }
              setResource($components || (instance.$components = {}), name, component);
          }
      }
      /**
       * 注册当前组件级别的子模板
       */
      partial(name, partial) {
          {
              const instance = this, { $partials } = instance;
              if (string(name) && !partial) {
                  return getResource($partials, name, Yox.partial);
              }
              setResource($partials || (instance.$partials = {}), name, partial, Yox.compile);
          }
      }
      /**
       * 注册当前组件级别的过滤器
       */
      filter(name, filter) {
          {
              const instance = this, { $filters } = instance;
              if (string(name) && !filter) {
                  return getResource($filters, name, Yox.filter);
              }
              setResource($filters || (instance.$filters = {}), name, filter);
          }
      }
      /**
       * 对于某些特殊场景，修改了数据，但是模板的依赖中并没有这一项
       * 而你非常确定需要更新模板，强制刷新正是你需要的
       */
      forceUpdate(data) {
          {
              const instance = this, { $vnode, $observer } = instance, { computed } = $observer;
              if ($vnode && computed) {
                  const template = computed[TEMPLATE_COMPUTED], oldValue = template.get();
                  if (data) {
                      instance.set(data);
                  }
                  // 当前可能正在进行下一轮更新
                  $observer.nextTask.run();
                  // 没有更新模板，强制刷新
                  if (!data && oldValue === template.get()) {
                      instance.update(template.get(TRUE), $vnode);
                  }
              }
          }
      }
      /**
       * 把模板抽象语法树渲染成 virtual dom
       */
      render() {
          {
              const instance = this;
              return render(instance, instance.$template, merge(instance.$filters, globalFilters), merge(instance.$partials, globalPartials), merge(instance.$directives, globalDirectives), merge(instance.$transitions, globalTransitions));
          }
      }
      /**
       * 更新 virtual dom
       *
       * @param vnode
       * @param oldVnode
       */
      update(vnode, oldVnode) {
          {
              let instance = this, { $vnode, $options } = instance, afterHook;
              // 每次渲染重置 refs
              // 在渲染过程中收集最新的 ref
              // 这样可避免更新时，新的 ref，在前面创建，老的 ref 却在后面删除的情况
              instance.$refs = {};
              if ($vnode) {
                  execute($options[HOOK_BEFORE_UPDATE], instance);
                  instance.fire(HOOK_BEFORE_UPDATE + NAMESPACE_HOOK);
                  patch(domApi, vnode, oldVnode);
                  afterHook = HOOK_AFTER_UPDATE;
              }
              else {
                  execute($options[HOOK_BEFORE_MOUNT], instance);
                  instance.fire(HOOK_BEFORE_MOUNT + NAMESPACE_HOOK);
                  patch(domApi, vnode, oldVnode);
                  instance.$el = vnode.node;
                  afterHook = HOOK_AFTER_MOUNT;
              }
              instance.$vnode = vnode;
              // 跟 nextTask 保持一个节奏
              // 这样可以预留一些优化的余地
              Yox.nextTick(function () {
                  if (instance.$vnode) {
                      execute($options[afterHook], instance);
                      instance.fire(afterHook + NAMESPACE_HOOK);
                  }
              });
          }
      }
      /**
       * 校验组件参数
       *
       * @param props
       */
      checkProps(props) {
      }
      checkProp(key, value) {
      }
      /**
       * 销毁组件
       */
      destroy() {
          const instance = this, { $parent, $options, $emitter, $observer } = instance;
          {
              execute($options[HOOK_BEFORE_DESTROY], instance);
              instance.fire(HOOK_BEFORE_DESTROY + NAMESPACE_HOOK);
              const { $vnode } = instance;
              if ($parent && $parent.$children) {
                  remove($parent.$children, instance);
              }
              if ($vnode) {
                  // virtual dom 通过判断 parent.$vnode 知道宿主组件是否正在销毁
                  instance.$vnode = UNDEFINED;
                  destroy(domApi, $vnode, !$parent);
              }
          }
          $observer.destroy();
          {
              execute($options[HOOK_AFTER_DESTROY], instance);
              instance.fire(HOOK_AFTER_DESTROY + NAMESPACE_HOOK);
          }
          // 发完 after destroy 事件再解绑所有事件
          $emitter.off();
          clear(instance);
      }
      /**
       * 因为组件采用的是异步更新机制，为了在更新之后进行一些操作，可使用 nextTick
       */
      nextTick(task) {
          this.$observer.nextTask.append(task, this);
      }
      /**
       * 取反 keypath 对应的数据
       *
       * 不管 keypath 对应的数据是什么类型，操作后都是布尔型
       */
      toggle(keypath) {
          return this.$observer.toggle(keypath);
      }
      /**
       * 递增 keypath 对应的数据
       *
       * 注意，最好是整型的加法，如果涉及浮点型，不保证计算正确
       *
       * @param keypath 值必须能转型成数字，如果不能，则默认从 0 开始递增
       * @param step 步进值，默认是 1
       * @param max 可以递增到的最大值，默认不限制
       */
      increase(keypath, step, max) {
          return this.$observer.increase(keypath, step, max);
      }
      /**
       * 递减 keypath 对应的数据
       *
       * 注意，最好是整型的减法，如果涉及浮点型，不保证计算正确
       *
       * @param keypath 值必须能转型成数字，如果不能，则默认从 0 开始递减
       * @param step 步进值，默认是 1
       * @param min 可以递减到的最小值，默认不限制
       */
      decrease(keypath, step, min) {
          return this.$observer.decrease(keypath, step, min);
      }
      /**
       * 在数组指定位置插入元素
       *
       * @param keypath
       * @param item
       * @param index
       */
      insert(keypath, item, index) {
          return this.$observer.insert(keypath, item, index);
      }
      /**
       * 在数组尾部添加元素
       *
       * @param keypath
       * @param item
       */
      append(keypath, item) {
          return this.$observer.append(keypath, item);
      }
      /**
       * 在数组首部添加元素
       *
       * @param keypath
       * @param item
       */
      prepend(keypath, item) {
          return this.$observer.prepend(keypath, item);
      }
      /**
       * 通过索引移除数组中的元素
       *
       * @param keypath
       * @param index
       */
      removeAt(keypath, index) {
          return this.$observer.removeAt(keypath, index);
      }
      /**
       * 直接移除数组中的元素
       *
       * @param keypath
       * @param item
       */
      remove(keypath, item) {
          return this.$observer.remove(keypath, item);
      }
      /**
       * 拷贝任意数据，支持深拷贝
       *
       * @param data
       * @param deep
       */
      copy(data, deep) {
          return this.$observer.copy(data, deep);
      }
  }
  /**
   * core 版本
   */
  Yox.version = "1.0.0-alpha.85";
  /**
   * 方便外部共用的通用逻辑，特别是写插件，减少重复代码
   */
  Yox.is = is;
  Yox.dom = domApi;
  Yox.array = array$1;
  Yox.object = object$1;
  Yox.string = string$1;
  Yox.logger = logger;
  Yox.Event = CustomEvent;
  Yox.Emitter = Emitter;
  function afterCreateHook(instance, watchers) {
      if (watchers) {
          instance.watch(watchers);
      }
      {
          execute(instance.$options[HOOK_AFTER_CREATE], instance);
          instance.fire(HOOK_AFTER_CREATE + NAMESPACE_HOOK);
      }
  }
  function setFlexibleOptions(instance, key, value) {
      if (func(value)) {
          instance[key](execute(value, instance));
      }
      else if (object(value)) {
          instance[key](value);
      }
  }
  function addEvent(instance, type, listener, once) {
      const options = {
          fn: listener,
          ctx: instance
      };
      if (once) {
          options.max = 1;
      }
      // YoxInterface 没有声明 $emitter，因为不想让外部访问，
      // 但是这里要用一次，所以加了 as any
      instance.$emitter.on(type, options);
  }
  function addEvents(instance, type, listener, once) {
      if (string(type)) {
          addEvent(instance, type, listener, once);
      }
      else {
          each$2(type, function (value, key) {
              addEvent(instance, key, value, once);
          });
      }
  }
  function loadComponent(registry, name, callback) {
      if (registry && registry[name]) {
          const component = registry[name];
          // 注册的是异步加载函数
          if (func(component)) {
              registry[name] = [callback];
              const componentCallback = function (result) {
                  const queue = registry[name], options = result['default'] || result;
                  registry[name] = options;
                  each(queue, function (callback) {
                      callback(options);
                  });
              }, promise = component(componentCallback);
              if (promise) {
                  promise.then(componentCallback);
              }
          }
          // 正在加载中
          else if (array(component)) {
              push(component, callback);
          }
          // 不是异步加载函数，直接同步返回
          else {
              callback(component);
          }
          return TRUE;
      }
  }
  function getResource(registry, name, lookup) {
      if (registry && registry[name]) {
          return registry[name];
      }
      else if (lookup) {
          return lookup(name);
      }
  }
  function setResource(registry, name, value, formatValue) {
      if (string(name)) {
          registry[name] = formatValue ? formatValue(value) : value;
      }
      else {
          each$2(name, function (value, key) {
              registry[key] = formatValue ? formatValue(value) : value;
          });
      }
  }
  {
      // 全局注册内置指令
      Yox.directive({ event, model, binding });
      // 全局注册内置过滤器
      Yox.filter({
          hasSlot(name) {
              // 不鼓励在过滤器使用 this
              // 因此过滤器没有 this 的类型声明
              // 这个内置过滤器是不得不用 this
              return isDef(this.get(SLOT_DATA_PREFIX + name));
          }
      });
  }
  
  export default Yox;
  //# sourceMappingURL=yox.esm.js.map
  