yox
Version:
A lightweight mvvm framework
2,032 lines (2,000 loc) • 230 kB
JavaScript
/**
* yox.js v1.0.0-alpha.408
* (c) 2017-2023 musicode
* Released under the MIT License.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Yox = factory());
}(this, (function () { 'use strict';
var SYNTAX_IF = '#if';
var SYNTAX_ELSE = 'else';
var SYNTAX_ELSE_IF = 'else if';
var SYNTAX_EACH = '#each';
var SYNTAX_IMPORT = '>';
var SYNTAX_SPREAD = '...';
var SYNTAX_COMMENT = /^!(?:\s|--)/;
var TAG_SLOT = 'slot';
var TAG_PORTAL = 'portal';
var TAG_FRAGMENT = 'fragment';
var TAG_TEMPLATE = 'template';
var ATTR_TO = 'to';
var ATTR_KEY = 'key';
var ATTR_REF = 'ref';
var ATTR_SLOT = 'slot';
var ATTR_NAME = 'name';
var SLOT_DATA_PREFIX = '$slot_';
var SLOT_NAME_DEFAULT = 'children';
var VNODE_TYPE_TEXT = 1;
var VNODE_TYPE_COMMENT = 2;
var VNODE_TYPE_ELEMENT = 3;
var VNODE_TYPE_COMPONENT = 4;
var VNODE_TYPE_FRAGMENT = 5;
var VNODE_TYPE_PORTAL = 6;
var VNODE_TYPE_SLOT = 7;
var DIRECTIVE_ON = 'on';
var DIRECTIVE_LAZY = 'lazy';
var DIRECTIVE_MODEL = 'model';
var DIRECTIVE_EVENT = 'event';
var DIRECTIVE_TRANSITION = 'transition';
var DIRECTIVE_CUSTOM = 'o';
var MODIFER_NATIVE = 'native';
var MAGIC_VAR_SCOPE = '$scope';
var MAGIC_VAR_KEYPATH = '$keypath';
var MAGIC_VAR_LENGTH = '$length';
var MAGIC_VAR_EVENT = '$event';
var MAGIC_VAR_DATA = '$data';
/**
* 为了压缩,定义的常量
*/
var TRUE$1 = true;
var FALSE$1 = false;
var NULL$1 = null;
var UNDEFINED$1 = void 0;
var RAW_TRUE = 'true';
var RAW_FALSE = 'false';
var RAW_NULL = 'null';
var RAW_UNDEFINED = 'undefined';
var RAW_THIS = 'this';
var RAW_FUNCTION = 'function';
var RAW_LENGTH = 'length';
var RAW_WILDCARD = '*';
var RAW_DOT = '.';
var RAW_SLASH = '/';
var RAW_DOLLAR = '$';
var KEYPATH_ROOT = '~';
var KEYPATH_PARENT = '..';
var KEYPATH_CURRENT = RAW_THIS;
/**
* Single instance for window in browser
*/
var WINDOW = typeof window !== RAW_UNDEFINED ? window : UNDEFINED$1;
/**
* Single instance for noop function
*/
var EMPTY_FUNCTION = function () {
/** yox */
};
/**
* 空对象,很多地方会用到,比如 `a || EMPTY_OBJECT` 确保是个对象
*/
var EMPTY_OBJECT = Object.freeze({});
/**
* 空数组
*/
var EMPTY_ARRAY = Object.freeze([]);
/**
* 空字符串
*/
var EMPTY_STRING = '';
/**
* 日志等级
*/
var LOG_LEVEL_DEBUG = 1;
var LOG_LEVEL_INFO = 2;
var LOG_LEVEL_WARN = 3;
var LOG_LEVEL_ERROR = 4;
var LOG_LEVEL_FATAL = 5;
/**
* 当前是否是源码调试,如果开启了代码压缩,empty function 里的注释会被干掉
* 源码模式默认选 INFO,因为 DEBUG 输出的日志太多,会导致性能急剧下降
*/
var LOG_LEVEL_DEFAULT = /yox/.test(EMPTY_FUNCTION.toString()) ? LOG_LEVEL_INFO : LOG_LEVEL_WARN;
/**
* 外部可配置的对象
*/
var PUBLIC_CONFIG = {
leftDelimiter: '{',
rightDelimiter: '}',
uglifyCompiled: FALSE$1,
minifyCompiled: FALSE$1,
logLevel: LOG_LEVEL_DEFAULT,
};
/**
* 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$1(value) {
return Array.isArray(value);
}
/**
* Check if value is an object.
*
* @param value
* @return
*/
function object$1(value) {
// 低版本 IE 会把 null 当作 object
return value !== NULL$1 && typeof value === 'object';
}
/**
* Check if value is a string.
*
* @param value
* @return
*/
function string$1(value) {
return typeof value === 'string';
}
/**
* Check if value is a number.
*
* @param value
* @return
*/
function number(value) {
return typeof value === 'number' && !isNaN(value);
}
/**
* Check if value is boolean.
*
* @param value
* @return
*/
function boolean(value) {
return value === TRUE$1 || value === FALSE$1;
}
/**
* Check if value is numeric.
*
* @param value
* @return
*/
function numeric(value) {
return !isNaN(value - parseFloat(value));
}
var is = /*#__PURE__*/Object.freeze({
__proto__: null,
func: func,
array: array$1,
object: object$1,
string: string$1,
number: number,
boolean: boolean,
numeric: numeric
});
/**
* 任性地执行一个函数,不管它有没有、是不是
*
* @param fn 调用的函数
* @param context 执行函数时的 this 指向
* @param args 调用函数的参数,多参数时传入数组
* @return 调用函数的返回值
*/
function execute (fn, context, args) {
return array$1(args)
? fn.apply(context, args)
: context !== UNDEFINED$1
? fn.call(context, args)
: args !== UNDEFINED$1
? fn(args)
: fn();
}
var CustomEvent = function(type, originalEvent) {
// 这里不设置命名空间
// 因为有没有命名空间取决于 Emitter 的构造函数有没有传 true
// CustomEvent 自己无法决定
this.type = type;
this.phase = CustomEvent.PHASE_CURRENT;
if (originalEvent) {
this.originalEvent = originalEvent;
}
};
CustomEvent.is = function (event) {
return event instanceof CustomEvent;
};
/**
* 阻止事件的默认行为
*/
CustomEvent.prototype.preventDefault = function () {
var instance = this;
if (!instance.isPrevented) {
var originalEvent = instance.originalEvent;
if (originalEvent) {
originalEvent.preventDefault();
}
instance.isPrevented = TRUE$1;
}
return instance;
};
/**
* 停止事件广播
*/
CustomEvent.prototype.stopPropagation = function () {
var instance = this;
if (!instance.isStoped) {
var originalEvent = instance.originalEvent;
if (originalEvent) {
originalEvent.stopPropagation();
}
instance.isStoped = TRUE$1;
}
return instance;
};
CustomEvent.prototype.prevent = function () {
return this.preventDefault();
};
CustomEvent.prototype.stop = function () {
return this.stopPropagation();
};
CustomEvent.PHASE_CURRENT = 0;
CustomEvent.PHASE_UPWARD = 1;
CustomEvent.PHASE_DOWNWARD = -1;
/**
* 遍历数组
*
* @param array
* @param callback 返回 false 可停止遍历
* @param reversed 是否逆序遍历
*/
function each$2(array, callback, reversed) {
var length = array.length;
if (length) {
if (reversed) {
for (var i = length - 1; i >= 0; i--) {
if (callback(array[i], i) === FALSE$1) {
break;
}
}
}
else {
for (var i$1 = 0; i$1 < length; i$1++) {
if (callback(array[i$1], i$1) === FALSE$1) {
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, value, action) {
if (array$1(value)) {
each$2(value, function (item) {
action(array, item);
});
}
else {
action(array, 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$1(array, target, strict) {
var result = -1;
each$2(array, function (item, index) {
if (strict === FALSE$1 ? item == target : item === target) {
result = index;
return FALSE$1;
}
});
return result;
}
/**
* 获取数组最后一项
*
* @param array 数组
* @return
*/
function last(array) {
var length = array.length;
if (length > 0) {
return array[length - 1];
}
}
/**
* 弹出数组最后一项
*
* 项目里用的太多,仅用于节省字符...
*
* @param array 数组
* @return 弹出的数组项
*/
function pop(array) {
var length = array.length;
if (length > 0) {
return array.pop();
}
}
/**
* 删除数组项
*
* @param array 数组
* @param item 待删除项
* @param strict 是否全等判断,默认是全等
* @return 删除的数量
*/
function remove(array, target, strict) {
var result = 0;
each$2(array, function (item, index) {
if (strict === FALSE$1 ? item == target : item === target) {
array.splice(index, 1);
result++;
}
}, TRUE$1);
return result;
}
/**
* 数组是否包含 item
*
* @param array 数组
* @param target 可能包含的数组项
* @param strict 是否全等判断,默认是全等
* @return
*/
function has$2(array, target, strict) {
return indexOf$1(array, target, strict) >= 0;
}
/**
* 把类数组转成数组
*
* @param array 类数组
* @return
*/
function toArray(array) {
return array$1(array)
? array
: execute(EMPTY_ARRAY.slice, array);
}
/**
* 把数组转成对象
*
* @param array 数组
* @param key 数组项包含的字段名称,如果数组项是基本类型,可不传
* @param value
* @return
*/
function toObject(array, key, value) {
var result = {};
each$2(array, function (item) {
result[key ? item[key] : item] = value || item;
});
return result;
}
/**
* 把数组合并成字符串
*
* @param array
* @param separator
* @return
*/
function join$1(array, separator) {
return array.join(separator);
}
/**
* 用于判断长度大于 0 的数组
*
* @param array
* @return
*/
function falsy$2(array) {
return !array$1(array) || !array.length;
}
var array = /*#__PURE__*/Object.freeze({
__proto__: null,
each: each$2,
push: push,
unshift: unshift,
indexOf: indexOf$1,
last: last,
pop: pop,
remove: remove,
has: has$2,
toArray: toArray,
toObject: toObject,
join: join$1,
falsy: falsy$2
});
function toString (target, defaultValue) {
return target != NULL$1 && target.toString
? target.toString()
: defaultValue !== UNDEFINED$1
? defaultValue
: EMPTY_STRING;
}
function isNative (target) {
return func(target)
&& toString(target).indexOf('[native code]') >= 0;
}
var createPureObject = function () {
var obj = Object.create(NULL$1);
return {
get: function(key) {
return obj[key];
},
set: function(key, value) {
obj[key] = value;
},
has: function(key) {
return key in obj;
},
keys: function() {
return Object.keys(obj);
}
};
};
// IE9+ 都已实现 Object.create
{
if (!isNative(Object.create)) {
createPureObject = function () {
var obj = {};
return {
get: function(key) {
return obj.hasOwnProperty(key)
? obj[key]
: UNDEFINED$1;
},
set: function(key, value) {
obj[key] = value;
},
has: function(key) {
return obj.hasOwnProperty(key);
},
keys: function() {
return Object.keys(obj);
}
};
};
}
}
/**
* 创建纯净对象
*
* @return 纯净对象
*/
var createPureObject$1 = createPureObject;
/**
* 缓存一个参数的函数调用结果
*
* @param fn 需要缓存的函数
* @return 带缓存功能的函数
*/
function createOneKeyCache(fn) {
var cache = createPureObject$1();
return function (key) {
var hit = cache.get(key);
if (hit !== UNDEFINED$1) {
return hit;
}
var value = fn(key);
cache.set(key, value);
return value;
};
}
/**
* 缓存两个参数的函数调用结果
*
* @param fn 需要缓存的函数
* @return 带缓存功能的函数
*/
function createTwoKeyCache(fn) {
var cache = createPureObject$1();
return function (key1, key2) {
var hit1 = cache.get(key1);
if (hit1) {
var hit2 = hit1.get(key2);
if (hit2) {
return hit2;
}
}
else {
hit1 = createPureObject$1();
cache.set(key1, hit1);
}
var value = fn(key1, key2);
hit1.set(key2, value);
return value;
};
}
var camelizePattern = /-([a-z])/gi, hyphenatePattern = /\B([A-Z])/g, capitalizePattern = /^[a-z]/;
/**
* 连字符转成驼峰
*
* @param str
* @return 驼峰格式的字符串
*/
var camelize = createOneKeyCache(function (str) {
return str.replace(camelizePattern, function (_, $1) {
return upper($1);
});
});
/**
* 驼峰转成连字符
*
* @param str
* @return 连字符格式的字符串
*/
var hyphenate = createOneKeyCache(function (str) {
return str.replace(hyphenatePattern, function (_, $1) {
return '-' + lower($1);
});
});
/**
* 首字母大写
*
* @param str
* @return
*/
var capitalize = createOneKeyCache(function (str) {
return str.replace(capitalizePattern, upper);
});
/**
* 重复字符串
*
* @param str
* @param count 重复次数
* @return
*/
function repeat(str, count) {
return join$1(new Array(count + 1), 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(str, part, start) {
return str.indexOf(part, start !== UNDEFINED$1 ? start : 0);
}
/**
* 获取子串的起始位置
*
* @param str
* @param part
* @param end
* @return
*/
function lastIndexOf(str, part, end) {
return str.lastIndexOf(part, end !== UNDEFINED$1 ? end : str.length);
}
/**
* str 是否以 part 开头
*
* @param str
* @param part
* @return
*/
function startsWith(str, part) {
return indexOf(str, part) === 0;
}
/**
* str 是否以 part 结束
*
* @param str
* @param part
* @return
*/
function endsWith(str, part) {
var 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(str, part) >= 0;
}
/**
* str 转成 value 为 true 的 map
*
* @param str
* @param separator
*/
function toMap$1(str, separator) {
var map = Object.create(NULL$1);
each$2(str.split(separator || ','), function (item) {
map[item] = TRUE$1;
});
return map;
}
/**
* 判断长度大于 0 的字符串
*
* @param str
* @return
*/
function falsy$1(str) {
return !string$1(str) || !str.length;
}
var string = /*#__PURE__*/Object.freeze({
__proto__: null,
camelize: camelize,
hyphenate: hyphenate,
capitalize: capitalize,
repeat: repeat,
trim: trim,
slice: slice,
indexOf: indexOf,
lastIndexOf: lastIndexOf,
startsWith: startsWith,
endsWith: endsWith,
charAt: charAt,
codeAt: codeAt,
upper: upper,
lower: lower,
has: has$1,
toMap: toMap$1,
falsy: falsy$1
});
var dotPattern = /\./g, asteriskPattern = /\*/g, doubleAsteriskPattern = /\*\*/g;
/**
* 判断 keypath 是否以 prefix 开头,如果是,返回匹配上的前缀长度,否则返回 -1
*
* @param keypath
* @param prefix
* @return
*/
var match = createTwoKeyCache(function (keypath, prefix) {
if (keypath === prefix) {
return prefix.length;
}
prefix += RAW_DOT;
return startsWith(keypath, prefix)
? prefix.length
: -1;
});
var getKeypathTokens = createOneKeyCache(function (keypath) {
return indexOf(keypath, RAW_DOT) < 0
? [keypath]
: keypath.split(RAW_DOT);
});
/**
* 遍历 keypath 的每个部分
*
* @param keypath
* @param callback 返回 false 可中断遍历
*/
function each$1(keypath, callback) {
var tokens = string$1(keypath) ? getKeypathTokens(keypath) : keypath;
for (var i = 0, lastIndex = tokens.length - 1; i <= lastIndex; i++) {
if (callback(tokens[i], i, lastIndex) === FALSE$1) {
break;
}
}
}
/**
* 遍历 keypath 的每个部分
*
* @param keypath1
* @param keypath2
*/
var join = createTwoKeyCache(function (keypath1, keypath2) {
return keypath1 && keypath2
? keypath1 + RAW_DOT + keypath2
: keypath1 || keypath2;
});
/**
* 是否模糊匹配
*
* @param keypath
*/
var isFuzzy = createOneKeyCache(function (keypath) {
return has$1(keypath, RAW_WILDCARD);
});
var getFuzzyPattern = createOneKeyCache(function (pattern) {
return new RegExp(("^" + (pattern
.replace(dotPattern, '\\.')
.replace(asteriskPattern, '(\\w+)')
.replace(doubleAsteriskPattern, '([\.\\w]+?)')) + "$"));
});
/**
* 模糊匹配 keypath
*
* @param keypath
* @param pattern
*/
var matchFuzzy = createTwoKeyCache(function (keypath, pattern) {
var result = keypath.match(getFuzzyPattern(pattern));
return result
? result[1]
: UNDEFINED$1;
});
/**
* 全局 value holder,避免频繁的创建临时对象
*/
var holder = {
value: UNDEFINED$1
};
/**
* 获取对象的 key 的数组
*
* @param object
* @return
*/
function keys(object) {
return Object.keys(object);
}
/**
* 遍历对象
*
* @param object
* @param callback 返回 false 可停止遍历
*/
function each(object, callback) {
for (var key in object) {
if (callback(object[key], key) === FALSE$1) {
break;
}
}
}
/**
* 扩展对象
*
* @return
*/
function extend(original, object) {
each(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, deep) {
var result = object;
if (array$1(object)) {
if (deep) {
result = [];
each$2(object, function (item, index) {
result[index] = copy(item, deep);
});
}
else {
result = object.slice();
}
}
else if (object$1(object)) {
result = {};
each(object, function (value, key) {
result[key] = deep ? copy(value, deep) : value;
});
}
return result;
}
function getCallback(value) {
// 如果是计算属性,取计算属性的值
return func(value.get)
? value.get()
: value;
}
/**
* 从对象中查找一个 keypath
*
* 返回值是空时,表示没找到值
*
* @param object
* @param keypath
* @return
*/
function get(object, keypath, callback) {
var result = object;
each$1(keypath, function (key, index, lastIndex) {
if (result != NULL$1) {
// 先直接取值
var value = result[key],
// 紧接着判断值是否存在
// 下面会处理计算属性的值,不能在它后面设置 hasValue
hasValue = value !== UNDEFINED$1;
// 为什么不用 hasValue 判断呢?
// 因为这里需要处理的 value 要么是函数,要么是对象,基础类型无需加工
if (value) {
// 如果数据中没有计算属性,也可以自定义
value = (callback || getCallback)(value);
}
if (index === lastIndex) {
if (hasValue) {
holder.value = value;
result = holder;
}
else {
result = UNDEFINED$1;
}
}
else {
result = value;
}
}
else {
result = UNDEFINED$1;
return FALSE$1;
}
});
return result;
}
/**
* 为对象设置一个键值对
*
* @param object
* @param keypath
* @param value
* @param autofill 是否自动填充不存在的对象,默认自动填充
*/
function set(object, keypath, value, autofill) {
var next = object;
each$1(keypath, function (key, index, lastIndex) {
if (index === lastIndex) {
next[key] = value;
}
else if (next[key]) {
next = next[key];
}
else if (autofill) {
next = next[key] = {};
}
else {
return FALSE$1;
}
});
}
/**
* 对象是否包含某个 key
*
* @param object
* @param key
* @return
*/
function has(object, key) {
// 不用 hasOwnProperty,性能差
return object[key] !== UNDEFINED$1;
}
/**
* 是否是空对象
*
* @param object
* @return
*/
function falsy(object) {
return !object$1(object)
|| array$1(object)
|| !keys(object).length;
}
var object = /*#__PURE__*/Object.freeze({
__proto__: null,
keys: keys,
each: each,
extend: extend,
merge: merge,
copy: copy,
get: get,
set: set,
has: has,
falsy: falsy
});
/**
* 外部可用这些常量
*/
var DEBUG = LOG_LEVEL_DEBUG;
var INFO = LOG_LEVEL_INFO;
var WARN = LOG_LEVEL_WARN;
var ERROR = LOG_LEVEL_ERROR;
var FATAL = LOG_LEVEL_FATAL;
/**
* 是否有原生的日志特性,没有必要单独实现
*/
var nativeConsole = typeof console !== RAW_UNDEFINED ? console : NULL$1,
/**
* 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() {
var ref = PUBLIC_CONFIG;
var logLevel = ref.logLevel;
if (logLevel >= DEBUG && logLevel <= FATAL) {
return logLevel;
}
return LOG_LEVEL_DEFAULT;
}
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({
__proto__: null,
DEBUG: DEBUG,
INFO: INFO,
WARN: WARN,
ERROR: ERROR,
FATAL: FATAL,
debug: debug,
info: info,
warn: warn,
error: error,
fatal: fatal
});
var Emitter = function(ns) {
this.ns = ns || FALSE$1;
this.listeners = {};
};
/**
* 发射事件
*
* @param type 事件名称或命名空间
* @param args 事件处理函数的参数列表
* @param filter 自定义过滤器
*/
Emitter.prototype.fire = function (type, args, filter) {
var instance = this, event = string$1(type) ? instance.toEvent(type) : type, list = instance.listeners[event.type], isComplete = TRUE$1;
if (list) {
// 避免遍历过程中,数组发生变化,比如增删了
list = list.slice();
// 判断是否是发射事件
// 如果 args 的第一个参数是 CustomEvent 类型,表示发射事件
// 因为事件处理函数的参数列表是 (event, data)
var customEvent = args && CustomEvent.is(args[0])
? args[0]
: UNDEFINED$1;
// 这里不用 array.each,减少函数调用
for (var i = 0, length = list.length; i < length; i++) {
var options = list[i];
// 命名空间不匹配
if (!matchNamespace(event.ns, options)
// 在 fire 过程中被移除了
|| !has$2(list, options)
// 传了 filter,则用 filter 判断是否过滤此 options
|| (filter && !filter(event, args, options))) {
continue;
}
var result = execute(options.listener, options.ctx, args);
// 执行次数
options.num = options.num ? (options.num + 1) : 1;
// 注册的 listener 可以指定最大执行次数
if (options.num === options.max) {
instance.off(event.type, {
ns: event.ns,
listener: options.listener,
});
}
// 如果没有返回 false,而是调用了 customEvent.stop 也算是返回 false
if (customEvent) {
if (result === FALSE$1) {
customEvent.prevent().stop();
}
else if (customEvent.isStoped) {
result = FALSE$1;
}
}
if (result === FALSE$1) {
isComplete = FALSE$1;
break;
}
}
}
return isComplete;
};
/**
* 注册监听
*
* @param type
* @param listener
*/
Emitter.prototype.on = function (type, listener) {
var instance = this, listeners = instance.listeners, options = func(listener)
? { listener: listener }
: listener;
if (object$1(options) && func(options.listener)) {
if (!string$1(options.ns)) {
var event = instance.toEvent(type);
options.ns = event.ns;
type = event.type;
}
push(listeners[type] || (listeners[type] = []), options);
}
};
/**
* 取消监听
*
* @param type
* @param listener
*/
Emitter.prototype.off = function (type, listener) {
var instance = this, listeners = instance.listeners;
if (type) {
var filter = instance.toFilter(type, listener), each$1 = function (list, name) {
each$2(list, function (item, index) {
if (matchListener(filter.listener, item) && matchNamespace(filter.ns, item)) {
list.splice(index, 1);
}
}, TRUE$1);
if (!list.length) {
delete listeners[name];
}
};
if (filter.type) {
if (listeners[filter.type]) {
each$1(listeners[filter.type], filter.type);
}
}
// 按命名空间过滤,如 type 传入 .ns
else if (filter.ns) {
each(listeners, each$1);
}
}
else {
// 清空
instance.listeners = {};
}
};
/**
* 是否已监听某个事件
*
* @param type
* @param listener
*/
Emitter.prototype.has = function (type, listener) {
var instance = this, listeners = instance.listeners, filter = instance.toFilter(type, listener), result = TRUE$1, each$1 = function (list) {
each$2(list, function (item) {
if (matchListener(filter.listener, item) && matchNamespace(filter.ns, item)) {
return result = FALSE$1;
}
});
return result;
};
if (filter.type) {
if (listeners[filter.type]) {
each$1(listeners[filter.type]);
}
}
else if (filter.ns) {
each(listeners, each$1);
}
return !result;
};
/**
* 把事件类型解析成命名空间格式
*
* @param type
*/
Emitter.prototype.toEvent = function (type) {
// 这里 ns 必须为字符串
// 用于区分 event 对象是否已完成命名空间的解析
var event = {
type: type,
ns: EMPTY_STRING,
};
// 是否开启命名空间
if (this.ns) {
var index = indexOf(type, RAW_DOT);
if (index >= 0) {
event.type = slice(type, 0, index);
event.ns = slice(type, index + 1);
}
}
return event;
};
Emitter.prototype.toFilter = function (type, listener) {
var filter;
if (listener) {
filter = func(listener)
? { listener: listener }
: listener;
}
else {
filter = {};
}
if (string$1(filter.ns)) {
filter.type = type;
}
else {
var event = this.toEvent(type);
filter.type = event.type;
filter.ns = event.ns;
}
return filter;
};
/**
* 判断 options 是否能匹配 listener
*
* @param listener
* @param options
*/
function matchListener(listener, options) {
return listener
? listener === options.listener
: TRUE$1;
}
/**
* 判断 options 是否能匹配命名空间
*
* 如果 namespace 和 options.ns 都不为空,则需完全匹配
*
* 如果他们两个其中任何一个为空,则不判断命名空间
*
* @param namespace
* @param options
*/
function matchNamespace(namespace, options) {
var ns = options.ns;
return ns && namespace
? ns === namespace
: TRUE$1;
}
var 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) {
var channel = new MessageChannel();
channel.port1.onmessage = fn;
channel.port2.postMessage(1);
};
}
else {
nextTick = setTimeout;
}
var nextTick$1 = nextTick;
var shared;
var NextTask = function(hooks) {
var instance = this;
instance.tasks = [];
instance.hooks = hooks || EMPTY_OBJECT;
};
/**
* 全局单例
*/
NextTask.shared = function () {
return shared || (shared = new NextTask());
};
/**
* 在队尾添加异步任务
*/
NextTask.prototype.append = function (func, context) {
var instance = this;
var tasks = instance.tasks;
push(tasks, {
fn: func,
ctx: context
});
if (tasks.length === 1) {
nextTick$1(function () {
instance.run();
});
}
};
/**
* 在队首添加异步任务
*/
NextTask.prototype.prepend = function (func, context) {
var instance = this;
var tasks = instance.tasks;
unshift(tasks, {
fn: func,
ctx: context
});
if (tasks.length === 1) {
nextTick$1(function () {
instance.run();
});
}
};
/**
* 清空异步队列
*/
NextTask.prototype.clear = function () {
this.tasks.length = 0;
};
/**
* 立即执行异步任务,并清空队列
*/
NextTask.prototype.run = function () {
var instance = this;
var tasks = instance.tasks;
var hooks = instance.hooks;
var length = tasks.length;
if (length) {
instance.tasks = [];
if (hooks.beforeTask) {
hooks.beforeTask();
}
for (var i = 0; i < length; i++) {
execute(tasks[i].fn, tasks[i].ctx);
}
if (hooks.afterTask) {
hooks.afterTask();
}
}
};
/**
* 元素 节点
*/
var ELEMENT = 1;
/**
* 属性 节点
*/
var ATTRIBUTE = 2;
/**
* 指令 节点
*/
var DIRECTIVE = 3;
/**
* 样式 节点
*/
var STYLE = 4;
/**
* 文本 节点
*/
var TEXT = 5;
/**
* if 节点
*/
var IF = 6;
/**
* else if 节点
*/
var ELSE_IF = 7;
/**
* else 节点
*/
var ELSE = 8;
/**
* each 节点
*/
var EACH = 9;
/**
* import 节点
*/
var IMPORT = 10;
/**
* 表达式 节点
*/
var EXPRESSION = 11;
/**
* 延展操作 节点
*/
var SPREAD = 12;
// 特殊标签
var specialTags = {};
// 特殊属性
var specialAttrs = {};
// 名称 -> 类型的映射
var name2Type = {};
// 标签名 -> vnode 类型的映射
var specialTag2VNodeType = {};
specialTags[TAG_SLOT] =
specialTags[TAG_PORTAL] =
specialTags[TAG_FRAGMENT] =
specialTags[TAG_TEMPLATE] =
specialAttrs[ATTR_KEY] =
specialAttrs[ATTR_REF] =
specialAttrs[TAG_SLOT] = TRUE$1;
name2Type['if'] = IF;
name2Type['each'] = EACH;
specialTag2VNodeType[TAG_FRAGMENT] = VNODE_TYPE_FRAGMENT;
specialTag2VNodeType[TAG_PORTAL] = VNODE_TYPE_PORTAL;
specialTag2VNodeType[TAG_SLOT] = VNODE_TYPE_SLOT;
function isSpecialAttr(element, attr) {
return specialAttrs[attr.name]
|| element.tag === TAG_PORTAL && attr.name === ATTR_TO
|| element.tag === TAG_SLOT && attr.name === ATTR_NAME;
}
function parseStyleString(source, callback) {
var parts = source.split(';');
for (var i = 0, len = parts.length; i < len; i++) {
var item = parts[i];
var index = item.indexOf(':');
if (index > 0) {
var key = trim(item.substring(0, index));
var value = trim(item.substring(index + 1));
if (key && value) {
callback(camelize(key), value);
}
}
}
}
function createAttribute$1(name, ns) {
return {
type: ATTRIBUTE,
isStatic: TRUE$1,
name: name,
ns: ns,
};
}
function createDirective(name, ns, modifier) {
return {
type: DIRECTIVE,
ns: ns,
name: name,
modifier: modifier,
};
}
function createStyle() {
return {
type: STYLE,
isStatic: TRUE$1,
};
}
function createEach(from, to, equal, index) {
return {
type: EACH,
from: from,
to: to,
equal: equal || UNDEFINED$1,
index: index,
isVirtual: TRUE$1,
};
}
function createElement$1(tag, dynamicTag, isSvg, isStyle, isComponent) {
var isSlot = tag === TAG_SLOT, isVirtual = !isComponent && !isSlot && !!specialTags[tag], isNative = !isComponent && !isSlot && !isVirtual;
return {
type: ELEMENT,
tag: tag,
dynamicTag: dynamicTag,
isSvg: isSvg,
isStyle: isStyle,
isStatic: isNative,
isNative: isNative,
isVirtual: isVirtual,
isComponent: isComponent,
};
}
function createElse() {
return {
type: ELSE,
isVirtual: TRUE$1,
};
}
function createElseIf(expr) {
return {
type: ELSE_IF,
expr: expr,
isVirtual: TRUE$1,
};
}
function createExpression(expr, safe) {
return {
type: EXPRESSION,
expr: expr,
safe: safe,
isLeaf: TRUE$1,
isStatic: expr.isStatic,
};
}
function createIf(expr) {
return {
type: IF,
expr: expr,
isVirtual: TRUE$1,
};
}
function createImport(expr) {
return {
type: IMPORT,
expr: expr,
isLeaf: TRUE$1,
};
}
function createSpread(expr) {
return {
type: SPREAD,
expr: expr,
isLeaf: TRUE$1,
};
}
function createText(text) {
return {
type: TEXT,
text: text,
isStatic: TRUE$1,
isLeaf: TRUE$1,
};
}
var // 首字母大写,或中间包含 -
componentNamePattern = /^[A-Z]|-/,
// HTML 实体(中间最多 6 位,没见过更长的)
htmlEntityPattern = /&[#\w\d]{2,6};/,
// 常见的自闭合标签
selfClosingTagNames = EMPTY_OBJECT,
// 常见的 svg 标签
svgTagNames = EMPTY_OBJECT,
// 常见的数字类型的属性(width,height,cellpadding,cellspacing 支持百分比,因此不计入数字类型)
numberAttributeNames = EMPTY_OBJECT,
// 常见的布尔类型的属性
booleanAttributeNames = EMPTY_OBJECT;
function isSelfClosing(tagName) {
return selfClosingTagNames[tagName] !== UNDEFINED$1;
}
function createAttribute(element, name, ns) {
// 组件用驼峰格式
if (element.isComponent) {
return createAttribute$1(camelize(name), ns);
}
// 原生 dom 属性
if (name === 'style') {
return createStyle();
}
var attribute = createAttribute$1(name, ns);
if (isBooleanNativeAttribute(name)) {
// 默认为 true 的布尔属性只有以下两种情况
attribute.defaultValue = name === 'spellcheck'
|| (element.tag === 'img' && name === 'draggable');
}
return attribute;
}
function getAttributeDefaultValue(element, name, defaultValue) {
// 比如 <Dog isLive>
if (element.isComponent) {
return TRUE$1;
}
// 无视 <input min> 无效写法
if (isNumberNativeAttribute(name)) {
return UNDEFINED$1;
}
// 布尔类型取决于 defaultValue
if (isBooleanNativeAttribute(name)) {
return formatBooleanNativeAttributeValue(name, TRUE$1, defaultValue);
}
// 字符串类型返回空字符串
return EMPTY_STRING;
}
function formatNativeAttributeValue(name, value, defaultValue) {
if (isNumberNativeAttribute(name)) {
return formatNumberNativeAttributeValue(name, value);
}
else if (isBooleanNativeAttribute(name)) {
return formatBooleanNativeAttributeValue(name, value, defaultValue);
}
// 字符串类型的属性,保持原样即可
return value;
}
function isNumberNativeAttribute(name) {
return numberAttributeNames[name];
}
function isBooleanNativeAttribute(name) {
return booleanAttributeNames[name];
}
function formatNumberNativeAttributeValue(name, value) {
return toString(value);
}
function formatBooleanNativeAttributeValue(name, value, defaultValue) {
// 布尔类型的属性,只有值为 true 或 属性名 才表示 true
var isTrue = value === TRUE$1 || value === RAW_TRUE || value === name;
return isTrue === defaultValue
? UNDEFINED$1
: (isTrue ? RAW_TRUE : RAW_FALSE);
}
function createElement(staticTag, dynamicTag) {
var isSvg = FALSE$1, isStyle = FALSE$1, isComponent = FALSE$1;
if (dynamicTag) {
isComponent = TRUE$1;
}
else {
isSvg = svgTagNames[staticTag] !== UNDEFINED$1;
// 是 svg 就不可能是组件
// 加这个判断的原因是,svg 某些标签含有 连字符 和 大写字母,比较蛋疼
if (!isSvg && componentNamePattern.test(staticTag)) {
isComponent = TRUE$1;
}
else if (staticTag === 'style') {
isStyle = TRUE$1;
}
}
return createElement$1(staticTag, dynamicTag, isSvg, isStyle, isComponent);
}
function compatElement(element) {
var attrs = element.attrs;
var hasType = FALSE$1;
if (attrs) {
each$2(attrs, function (attr) {
var name = attr.type === ATTRIBUTE
? attr.name
: UNDEFINED$1;
if (name === 'type') {
hasType = TRUE$1;
}
});
}
// 补全 style 标签的 type
// style 如果没有 type 则加一个 type="text/css"
// 因为低版本 IE 没这个属性,没法正常渲染样式
if (element.isStyle && !hasType) {
var attr = createAttribute$1('type');
attr.value = 'text/css';
push(element.attrs || (element.attrs = []), attr);
}
}
function setElementText(element, text) {
if (string$1(text)) {
if (htmlEntityPattern.test(text)) {
element.html = text;
}
else {
element.text = text;
}
}
else {
element.text = text;
}
return TRUE$1;
}
function setElementHtml(element, expr) {
element.html = expr;
return TRUE$1;
}
function isDef (target) {
return target !== UNDEFINED$1;
}
/**
* 字面量
*/
var LITERAL = 1;
/**
* 标识符
*/
var IDENTIFIER = 2;
/**
* 对象属性或数组下标
*/
var MEMBER = 3;
/**
* 一元表达式,如 - a
*/
var UNARY = 4;
/**
* 二元表达式,如 a + b
*/
var BINARY = 5;
/**
* 三元表达式,如 a ? b : c
*/
var TERNARY = 6;
/**
* 数组表达式,如 [ 1, 2, 3 ]
*/
var ARRAY = 7;
/**
* 对象表达式,如 { name: 'yox' }
*/
var OBJECT = 8;
/**
* 函数调用表达式,如 a()
*/
var CALL = 9;
var CODE_EOF = 0; //
var CODE_DOT = 46; // .
var CODE_COMMA = 44; // ,
var CODE_SLASH = 47; // /
var CODE_BACKSLASH = 92; // \
var CODE_SQUOTE = 39; // '
var CODE_DQUOTE = 34; // "
var CODE_OPAREN = 40; // (
var CODE_CPAREN = 41; // )
var CODE_OBRACK = 91; // [
var CODE_CBRACK = 93; // ]
var CODE_OBRACE = 123; // {
var CODE_CBRACE = 125; // }
var CODE_QUESTION = 63; // ?
var CODE_COLON = 58; // :
var CODE_PLUS = 43; // +
var CODE_MINUS = 45; // -
var CODE_MULTIPLY = 42; // *
var CODE_DIVIDE = 47; // /
var CODE_MODULO = 37; // %
var CODE_WAVE = 126; // ~
var CODE_AND = 38; // &
var CODE_OR = 124; // |
var CODE_XOR = 94; // ^
var CODE_NOT = 33; // !
var CODE_LESS = 60; // <
var CODE_EQUAL = 61; // =
var CODE_GREAT = 62; // >
var CODE_AT = 64; // @
/**
* 区分关键字和普通变量
* 举个例子:a === true
* 从解析器的角度来说,a 和 true 是一样的 token
*/
var keywordLiterals = {};
keywordLiterals[RAW_TRUE] = TRUE$1;
keywordLiterals[RAW_FALSE] = FALSE$1;
keywordLiterals[RAW_NULL] = NULL$1;
keywordLiterals[RAW_UNDEFINED] = UNDEFINED$1;
/**
* 是否是空白符,用下面的代码在浏览器测试一下
*
* ```
* for (var i = 0; i < 200; i++) {
* console.log(i, String.fromCharCode(i))
* }
* ```
*
* 从 0 到 32 全是空白符,100 往上分布比较散且较少用,唯一需要注意的是 160
*
* 160 表示 non-breaking space
* http://www.adamkoch.com/2009/07/25/white-space-and-character-160/
*/
function isWhitespace(code) {
return (code > 0 && code < 33) || code === 160;
}
/**
* 是否是数字
*/
function isDigit(code) {
return code > 47 && code < 58; // 0...9
}
/**
* 是否是数字
*/
function isNumber(code) {
return isDigit(code) || code === CODE_DOT;
}
/**
* 是否是插槽变量,@name 表示引用 name 所指定的插槽
*/
function isSlotIdentifierStart(code) {
return code === CODE_AT;
}
/**
* 变量开始字符必须是 字母、下划线、$
*/
function isIdentifierStart(code) {
return code === 36 // $
|| code === 95 // _
|| (code > 96 && code < 123) // a...z
|| (code > 64 && code < 91); // A...Z
}
/**
* 变量剩余的字符必须是 字母、下划线、$、数字
*/
function isIdentifierPart(code) {
return isIdentifierStart(code) || isDigit(code);
}
function createArray(nodes, raw) {
return {
type: ARRAY,
raw: raw,
nodes: nodes,
isStatic: isStaticNodes(nodes),
};
}
function createBinary(left, operator, right, raw) {
return {
type: BINARY,
raw: raw,
left: left,
operator: operator,
right: right,
isStatic: isStaticNodes([left, right]),
};
}
function createCall(name, args, raw) {
return {
type: CALL,
raw: raw,
name: name,
args: args,
};
}
function createIdentifier(raw, name, isProp) {
var root = FALSE$1, lookup = TRUE$1, offset = 0;
if (name === KEYPATH_CURRENT) {
name = EMPTY_STRING;
lookup = FALSE$1;
}
else if (name === KEYPATH_PARENT) {
name = EMPTY_STRING;
lookup = FALSE$1;
offset = 1;
}
else if (name === KEYPATH_ROOT) {
name = EMPTY_STRING;
root = TRUE$1;
lookup = FALSE$1;
}
else {
name = replaceSlotIdentifierIfNeeded(name);
}
// 对象属性需要区分 a.b 和 a[b]
// 如果不借用 Literal 无法实现这个判断
// 同理,如果用了这种方式,就无法区分 a.b 和 a['b'],但是无所谓,这两种表示法本就一个意思
return isProp
? createLiteral(name, raw)
: createIdentifierInner(raw, name, root, lookup, offset);
}
function createLiteral(value, raw) {
return {
type: LITERAL,
raw: raw,
value: value,
isStatic: TRUE$1,
};
}
function createObject(keys, values, raw) {
return {
type: OBJECT,
raw: raw,
keys: keys,
values: values,
isStatic: isStaticNodes(values),
};
}
function createTernary(test, yes, no, raw) {
return {
type: TERNARY,
raw: raw,
test: test,
yes: yes,
no: no,
isStatic: isStaticNodes([test, yes, no]),
};
}
function createUnary(operator, node, raw) {
return {
type: UNARY,
raw: raw,
operator: operator,
node: node,
isStatic: node.isStatic,
};
}
/**
* 通过判断 nodes 来决定是否需要创建 Member
*
* 创建 Member 至少需要 nodes 有两个节点
*/
function createMemberIfNeeded(raw, nodes) {
// 第一个节点要特殊处理
var firstNode = nodes.shift(),
// 是否直接从顶层查找
root = FALSE$1,
// 是否向上查找
lookup = TRUE$1,
// 偏移量,默认从当前 context 开始查找
offset = 0;
// 表示传入的 nodes 至少有两个节点(弹出了一个)
if (nodes.length > 0) {
// 处理剩下的 nodes
// 这里要做两手准备:
// 1. 如果全是 literal 节点,则编译时 join
// 2. 如果不全是 literal 节点,则运行时 join
// 是否全是 Literal 节点
var isLiteral = TRUE$1,
// 静态节点
staticNodes = [],
// 对于 this.a.b[c] 这样的
// 要还原静态部分 this.a.b 的 raw
// 虽然 raw 没什么大用吧,谁让我是洁癖呢
staticRaw = EMPTY_STRING,
// 动态节点
dynamicNodes = [];
each$2(nodes, function (node) {
if (isLiteral) {
if (node.type === LITERAL) {
var literalNode = node;
if (literalNode.raw === KEYPATH_PARENT) {
offset += 1;
staticRaw = staticRaw
? staticRaw + RAW_SLASH + KEYPATH_PARENT
: KEYPATH_PARENT;
return;
}
if (literalNode.raw !== KEYPATH_CURRENT) {
var value = toString(literalNode.value);
push(staticNodes, value);
if (staticRaw) {
staticRaw += endsWith(staticRaw, KEYPATH_PARENT)
? RAW_SLASH
: RAW_DOT;
}
staticRaw += value;
}
}
else {
isLiteral = FALSE$1;
}
}
if (!isLiteral) {
push(dynamicNodes, node);
}
});
// lookup 要求第一位元素是 Identifier,且它的 lookup 是 true 才为 true
// 其他情况都为 false,如 "