yox
Version:
A lightweight mvvm framework
1,999 lines (1,961 loc) • 148 kB
JavaScript
/**
* yox.js v1.0.0-alpha.408
* (c) 2017-2023 musicode
* Released under the MIT License.
*/
const VNODE_TYPE_TEXT = 1;
const VNODE_TYPE_COMMENT = 2;
const VNODE_TYPE_ELEMENT = 3;
const VNODE_TYPE_FRAGMENT = 5;
const VNODE_TYPE_SLOT = 7;
const DIRECTIVE_MODEL = 'model';
const DIRECTIVE_CUSTOM = 'o';
const MODEL_PROP_DEFAULT = 'value';
const HOOK_BEFORE_CREATE = 'beforeCreate';
const HOOK_AFTER_CREATE = 'afterCreate';
const HOOK_BEFORE_RENDER = 'beforeRender';
const HOOK_AFTER_RENDER = 'afterRender';
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 HOOK_BEFORE_PROPS_UPDATE = 'beforePropsUpdate';
/**
* 为了压缩,定义的常量
*/
const TRUE = true;
const FALSE = false;
const NULL = null;
const UNDEFINED = void 0;
const RAW_TRUE = 'true';
const RAW_FALSE = 'false';
const RAW_UNDEFINED = 'undefined';
const RAW_FILTER = 'filter';
const RAW_COMPONENT = 'component';
const RAW_DIRECTIVE = 'directive';
const RAW_TRANSITION = 'transition';
const RAW_FUNCTION = 'function';
const RAW_LENGTH = 'length';
const RAW_WILDCARD = '*';
const RAW_DOT = '.';
const NODE_TYPE_ELEMENT = 1;
const NODE_TYPE_TEXT = 3;
const NODE_TYPE_COMMENT = 8;
/**
* 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;
/**
* 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 = '';
/**
* 日志等级
*/
const LOG_LEVEL_DEBUG = 1;
const LOG_LEVEL_INFO = 2;
const LOG_LEVEL_WARN = 3;
const LOG_LEVEL_ERROR = 4;
const LOG_LEVEL_FATAL = 5;
/**
* 当前是否是源码调试,如果开启了代码压缩,empty function 里的注释会被干掉
* 源码模式默认选 INFO,因为 DEBUG 输出的日志太多,会导致性能急剧下降
*/
const LOG_LEVEL_DEFAULT = /yox/.test(EMPTY_FUNCTION.toString()) ? LOG_LEVEL_INFO : LOG_LEVEL_WARN;
/**
* 外部可配置的对象
*/
const PUBLIC_CONFIG = {
leftDelimiter: '{',
rightDelimiter: '}',
uglifyCompiled: FALSE,
minifyCompiled: FALSE,
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 && 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 || value === FALSE;
}
/**
* 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
? fn.call(context, args)
: args !== UNDEFINED
? fn(args)
: fn();
}
class CustomEvent {
/**
* 构造函数
*
* 可以传事件名称,也可以传原生事件对象
*/
constructor(type, originalEvent) {
// 这里不设置命名空间
// 因为有没有命名空间取决于 Emitter 的构造函数有没有传 true
// CustomEvent 自己无法决定
this.type = type;
this.phase = CustomEvent.PHASE_CURRENT;
if (originalEvent) {
this.originalEvent = originalEvent;
}
}
static is(event) {
return event instanceof CustomEvent;
}
/**
* 阻止事件的默认行为
*/
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 = -1;
/**
* 遍历数组
*
* @param array
* @param callback 返回 false 可停止遍历
* @param reversed 是否逆序遍历
*/
function each$2(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, 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) {
let result = -1;
each$2(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$1(array, target, strict) {
let result = 0;
each$2(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$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) {
let 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$1,
has: has$2,
toArray: toArray,
toObject: toObject,
join: join$1,
falsy: falsy$2
});
function toString (target, defaultValue) {
return target != NULL && target.toString
? target.toString()
: defaultValue !== UNDEFINED
? defaultValue
: EMPTY_STRING;
}
function isNative (target) {
return func(target)
&& toString(target).indexOf('[native code]') >= 0;
}
let createPureObject = function () {
const obj = Object.create(NULL);
return {
get(key) {
return obj[key];
},
set(key, value) {
obj[key] = value;
},
has(key) {
return key in obj;
},
keys() {
return Object.keys(obj);
}
};
};
/**
* 缓存一个参数的函数调用结果
*
* @param fn 需要缓存的函数
* @return 带缓存功能的函数
*/
function createOneKeyCache(fn) {
const cache = createPureObject();
return function (key) {
const hit = cache.get(key);
if (hit !== UNDEFINED) {
return hit;
}
const value = fn(key);
cache.set(key, value);
return value;
};
}
/**
* 缓存两个参数的函数调用结果
*
* @param fn 需要缓存的函数
* @return 带缓存功能的函数
*/
function createTwoKeyCache(fn) {
const cache = createPureObject();
return function (key1, key2) {
let hit1 = cache.get(key1);
if (hit1) {
const hit2 = hit1.get(key2);
if (hit2) {
return hit2;
}
}
else {
hit1 = createPureObject();
cache.set(key1, hit1);
}
const value = fn(key1, key2);
hit1.set(key2, value);
return value;
};
}
const camelizePattern = /-([a-z])/gi, hyphenatePattern = /\B([A-Z])/g, capitalizePattern = /^[a-z]/;
/**
* 连字符转成驼峰
*
* @param str
* @return 驼峰格式的字符串
*/
const camelize = createOneKeyCache(function (str) {
return str.replace(camelizePattern, function (_, $1) {
return upper($1);
});
});
/**
* 驼峰转成连字符
*
* @param str
* @return 连字符格式的字符串
*/
const hyphenate = createOneKeyCache(function (str) {
return str.replace(hyphenatePattern, function (_, $1) {
return '-' + lower($1);
});
});
/**
* 首字母大写
*
* @param str
* @return
*/
const 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 ? start : 0);
}
/**
* 获取子串的起始位置
*
* @param str
* @param part
* @param end
* @return
*/
function lastIndexOf(str, part, end) {
return str.lastIndexOf(part, end !== UNDEFINED ? 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) {
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(str, part) >= 0;
}
/**
* str 转成 value 为 true 的 map
*
* @param str
* @param separator
*/
function toMap(str, separator) {
const map = Object.create(NULL);
each$2(str.split(separator || ','), function (item) {
map[item] = TRUE;
});
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,
falsy: falsy$1
});
const dotPattern = /\./g, asteriskPattern = /\*/g, doubleAsteriskPattern = /\*\*/g;
/**
* 判断 keypath 是否以 prefix 开头,如果是,返回匹配上的前缀长度,否则返回 -1
*
* @param keypath
* @param prefix
* @return
*/
const match = createTwoKeyCache(function (keypath, prefix) {
if (keypath === prefix) {
return prefix.length;
}
prefix += RAW_DOT;
return startsWith(keypath, prefix)
? prefix.length
: -1;
});
const 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) {
const tokens = string$1(keypath) ? getKeypathTokens(keypath) : keypath;
for (let i = 0, lastIndex = tokens.length - 1; i <= lastIndex; i++) {
if (callback(tokens[i], i, lastIndex) === FALSE) {
break;
}
}
}
/**
* 遍历 keypath 的每个部分
*
* @param keypath1
* @param keypath2
*/
const join = createTwoKeyCache(function (keypath1, keypath2) {
return keypath1 && keypath2
? keypath1 + RAW_DOT + keypath2
: keypath1 || keypath2;
});
/**
* 是否模糊匹配
*
* @param keypath
*/
const isFuzzy = createOneKeyCache(function (keypath) {
return has$1(keypath, RAW_WILDCARD);
});
const getFuzzyPattern = createOneKeyCache(function (pattern) {
return new RegExp(`^${pattern
.replace(dotPattern, '\\.')
.replace(asteriskPattern, '(\\w+)')
.replace(doubleAsteriskPattern, '([\.\\w]+?)')}$`);
});
/**
* 模糊匹配 keypath
*
* @param keypath
* @param pattern
*/
const matchFuzzy = createTwoKeyCache(function (keypath, pattern) {
const result = keypath.match(getFuzzyPattern(pattern));
return result
? result[1]
: UNDEFINED;
});
/**
* 全局 value holder,避免频繁的创建临时对象
*/
const holder = {
value: UNDEFINED
};
/**
* 获取对象的 key 的数组
*
* @param object
* @return
*/
function keys(object) {
return Object.keys(object);
}
/**
* 遍历对象
*
* @param object
* @param callback 返回 false 可停止遍历
*/
function each(object, callback) {
for (let key in object) {
if (callback(object[key], key) === FALSE) {
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) {
let 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) {
let result = object;
each$1(keypath, function (key, index, lastIndex) {
if (result != NULL) {
// 先直接取值
let value = result[key],
// 紧接着判断值是否存在
// 下面会处理计算属性的值,不能在它后面设置 hasValue
hasValue = value !== UNDEFINED;
// 为什么不用 hasValue 判断呢?
// 因为这里需要处理的 value 要么是函数,要么是对象,基础类型无需加工
if (value) {
// 如果数据中没有计算属性,也可以自定义
value = (callback || getCallback)(value);
}
if (index === lastIndex) {
if (hasValue) {
holder.value = value;
result = holder;
}
else {
result = UNDEFINED;
}
}
else {
result = value;
}
}
else {
result = UNDEFINED;
return FALSE;
}
});
return result;
}
/**
* 为对象设置一个键值对
*
* @param object
* @param keypath
* @param value
* @param autofill 是否自动填充不存在的对象,默认自动填充
*/
function set(object, keypath, value, autofill) {
let 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;
}
});
}
/**
* 对象是否包含某个 key
*
* @param object
* @param key
* @return
*/
function has(object, key) {
// 不用 hasOwnProperty,性能差
return object[key] !== UNDEFINED;
}
/**
* 是否是空对象
*
* @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
});
/**
* 外部可用这些常量
*/
const DEBUG = LOG_LEVEL_DEBUG;
const INFO = LOG_LEVEL_INFO;
const WARN = LOG_LEVEL_WARN;
const ERROR = LOG_LEVEL_ERROR;
const FATAL = LOG_LEVEL_FATAL;
/**
* 是否有原生的日志特性,没有必要单独实现
*/
const nativeConsole = typeof console !== RAW_UNDEFINED ? console : NULL,
/**
* 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() {
const { logLevel } = PUBLIC_CONFIG;
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
});
class Emitter {
constructor(ns) {
this.ns = ns || FALSE;
this.listeners = {};
}
/**
* 发射事件
*
* @param type 事件名称或命名空间
* @param args 事件处理函数的参数列表
* @param filter 自定义过滤器
*/
fire(type, args, filter) {
let instance = this, event = string$1(type) ? instance.toEvent(type) : type, list = instance.listeners[event.type], isComplete = TRUE;
if (list) {
// 避免遍历过程中,数组发生变化,比如增删了
list = list.slice();
// 判断是否是发射事件
// 如果 args 的第一个参数是 CustomEvent 类型,表示发射事件
// 因为事件处理函数的参数列表是 (event, data)
const customEvent = args && CustomEvent.is(args[0])
? args[0]
: UNDEFINED;
// 这里不用 array.each,减少函数调用
for (let i = 0, length = list.length; i < length; i++) {
let options = list[i];
// 命名空间不匹配
if (!matchNamespace(event.ns, options)
// 在 fire 过程中被移除了
|| !has$2(list, options)
// 传了 filter,则用 filter 判断是否过滤此 options
|| (filter && !filter(event, args, options))) {
continue;
}
let 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) {
customEvent.prevent().stop();
}
else if (customEvent.isStoped) {
result = FALSE;
}
}
if (result === FALSE) {
isComplete = FALSE;
break;
}
}
}
return isComplete;
}
/**
* 注册监听
*
* @param type
* @param listener
*/
on(type, listener) {
const instance = this, listeners = instance.listeners, options = func(listener)
? { listener: listener }
: listener;
if (object$1(options) && func(options.listener)) {
if (!string$1(options.ns)) {
const event = instance.toEvent(type);
options.ns = event.ns;
type = event.type;
}
push(listeners[type] || (listeners[type] = []), options);
}
}
/**
* 取消监听
*
* @param type
* @param listener
*/
off(type, listener) {
const instance = this, listeners = instance.listeners;
if (type) {
const 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);
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
*/
has(type, listener) {
let instance = this, listeners = instance.listeners, filter = instance.toFilter(type, listener), result = TRUE, each$1 = function (list) {
each$2(list, function (item) {
if (matchListener(filter.listener, item) && matchNamespace(filter.ns, item)) {
return result = FALSE;
}
});
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
*/
toEvent(type) {
// 这里 ns 必须为字符串
// 用于区分 event 对象是否已完成命名空间的解析
const event = {
type,
ns: EMPTY_STRING,
};
// 是否开启命名空间
if (this.ns) {
const index = indexOf(type, RAW_DOT);
if (index >= 0) {
event.type = slice(type, 0, index);
event.ns = slice(type, index + 1);
}
}
return event;
}
toFilter(type, listener) {
let filter;
if (listener) {
filter = func(listener)
? { listener: listener }
: listener;
}
else {
filter = {};
}
if (string$1(filter.ns)) {
filter.type = type;
}
else {
const 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;
}
/**
* 判断 options 是否能匹配命名空间
*
* 如果 namespace 和 options.ns 都不为空,则需完全匹配
*
* 如果他们两个其中任何一个为空,则不判断命名空间
*
* @param namespace
* @param options
*/
function matchNamespace(namespace, options) {
const { ns } = options;
return ns && namespace
? ns === namespace
: TRUE;
}
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 {
constructor(hooks) {
const instance = this;
instance.tasks = [];
instance.hooks = hooks || EMPTY_OBJECT;
}
/**
* 全局单例
*/
static shared() {
return shared || (shared = new NextTask());
}
/**
* 在队尾添加异步任务
*/
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 instance = this, { tasks, hooks } = instance, { length } = tasks;
if (length) {
instance.tasks = [];
if (hooks.beforeTask) {
hooks.beforeTask();
}
for (let i = 0; i < length; i++) {
execute(tasks[i].fn, tasks[i].ctx);
}
if (hooks.afterTask) {
hooks.afterTask();
}
}
}
}
/**
* 节流调用
*
* @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);
}
};
}
// vnode.data 内部使用的几个字段
const VNODE = '$vnode';
const LOADING = '$loading';
const LEAVING = '$leaving';
const MODEL_CONTROL = '$model_control';
const MODEL_DESTROY = '$model_destroy';
const EVENT_DESTROY = '$event_destroy';
const DIRECTIVE_HOOKS = '$directive_hooks';
const DIRECTIVE_UPDATING = '$directive_updating';
function addEvent$1(api, element, component, data, key, lazy, event) {
let { name, listener } = event;
if (lazy) {
const value = lazy[name] || lazy[EMPTY_STRING];
if (value === TRUE) {
name = EVENT_CHANGE;
}
else if (value > 0) {
listener = debounce(listener, value,
// 避免连续多次点击,主要用于提交表单场景
// 移动端的 tap 事件可自行在业务层打补丁实现
name === EVENT_CLICK || name === EVENT_TAP);
}
}
if (component) {
if (event.isNative) {
const target = component.$el;
api.on(target, name, listener);
return function () {
api.off(target, name, listener);
};
}
// event 有 ns 和 listener 两个字段,满足 ThisListenerOptions 的要求
component.on(name, event);
data[EVENT_DESTROY + key] = function () {
component.off(name, event);
delete data[EVENT_DESTROY + key];
};
}
else {
api.on(element, name, listener);
data[EVENT_DESTROY + key] = function () {
api.off(element, key, listener);
delete data[EVENT_DESTROY + key];
};
}
}
function afterCreate$5(api, vnode) {
const { events } = vnode;
if (events) {
const element = vnode.node, component = vnode.component, lazy = vnode.lazy, data = vnode.data;
for (let key in events) {
addEvent$1(api, element, component, data, key, lazy, events[key]);
}
}
}
function afterUpdate$4(api, vnode, oldVNode) {
const newEvents = vnode.events, oldEvents = oldVNode.events;
if (newEvents !== oldEvents) {
const element = vnode.node, component = vnode.component, lazy = vnode.lazy, data = vnode.data;
if (oldEvents) {
const newValue = newEvents || EMPTY_OBJECT;
for (let key in oldEvents) {
if (!newValue[key]) {
const destroy = data[EVENT_DESTROY + key];
if (destroy) {
destroy();
}
}
}
}
if (newEvents) {
const oldValue = oldEvents || EMPTY_OBJECT;
for (let key in newEvents) {
const event = newEvents[key], oldEvent = oldValue[key];
if (!oldEvent) {
addEvent$1(api, element, component, data, key, lazy, event);
}
else if (event.value !== oldEvent.value) {
const destroy = data[EVENT_DESTROY + key];
if (destroy) {
destroy();
}
addEvent$1(api, element, component, data, key, lazy, event);
}
else if (oldEvent.runtime && event.runtime) {
oldEvent.runtime.execute = event.runtime.execute;
event.runtime = oldEvent.runtime;
}
}
}
}
}
function beforeDestroy$3(api, vnode) {
const events = vnode.events, data = vnode.data;
if (events) {
for (let key in events) {
const destroy = data[EVENT_DESTROY + key];
if (destroy) {
destroy();
}
}
}
}
var eventHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate$5,
afterUpdate: afterUpdate$4,
beforeDestroy: beforeDestroy$3
});
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);
},
}, radioControl = {
set(node, value) {
node.checked = node.value === toString(value);
},
sync(node, keypath, context) {
if (node.checked) {
context.set(keypath, node.value);
}
},
}, checkboxControl = {
set(node, value) {
node.checked = array$1(value)
? has$2(value, node.value, FALSE)
: !!value;
},
sync(node, keypath, context) {
const value = context.get(keypath);
if (array$1(value)) {
if (node.checked) {
context.append(keypath, node.value);
}
else {
context.removeAt(keypath, indexOf$1(value, node.value, FALSE));
}
}
else {
context.set(keypath, node.checked);
}
},
}, selectControl = {
set(node, value) {
const { multiple, options } = node;
for (let i = 0, length = options.length; i < length; i++) {
if (multiple) {
options[i].selected = has$2(value, options[i].value, FALSE);
}
else if (options[i].value == value) {
node.selectedIndex = i;
return;
}
}
if (!multiple) {
node.selectedIndex = -1;
}
},
sync(node, keypath, context) {
const { multiple, options } = node;
if (multiple) {
const values = [];
for (let i = 0, length = options.length; i < length; i++) {
if (options[i].selected) {
values.push(options[i].value);
}
}
context.set(keypath, values);
}
else {
context.set(keypath, options[node.selectedIndex].value);
}
},
};
function addModel(api, element, component, data, vnode) {
let { context, model, lazy, nativeAttrs } = vnode, { keypath, value } = model, lazyValue = lazy && (lazy[DIRECTIVE_MODEL] || lazy[EMPTY_STRING]);
if (component) {
let viewBinding = component.$model, viewSyncing = debounceIfNeeded(function (newValue) {
context.set(keypath, newValue);
}, lazyValue);
component.watch(viewBinding, viewSyncing);
data[MODEL_DESTROY] = function () {
component.unwatch(viewBinding, viewSyncing);
delete data[MODEL_DESTROY];
};
}
else {
let control = vnode.tag === 'select'
? selectControl
: inputControl,
// checkbox,radio,select 监听的是 change 事件
eventName = EVENT_CHANGE;
if (control === inputControl) {
const type = nativeAttrs && nativeAttrs.type;
if (type === 'radio') {
control = radioControl;
}
else if (type === 'checkbox') {
control = checkboxControl;
}
// 如果是输入框,则切换成 model 事件
// model 事件是个 yox-dom 实现的特殊事件
// 不会在输入法组合文字过程中得到触发事件
else if (lazyValue !== TRUE) {
eventName = EVENT_MODEL;
}
}
const sync = debounceIfNeeded(function () {
control.sync(element, keypath, context);
}, lazyValue);
api.on(element, eventName, sync);
control.set(element, value);
data[MODEL_CONTROL] = control;
data[MODEL_DESTROY] = function () {
api.off(element, eventName, sync);
delete data[MODEL_DESTROY];
delete data[MODEL_CONTROL];
};
}
}
function afterCreate$4(api, vnode) {
const model = vnode.model;
if (model) {
addModel(api, vnode.node, vnode.component, vnode.data, vnode);
}
}
function afterUpdate$3(api, vnode, oldVNode) {
const data = vnode.data, newModel = vnode.model, oldModel = oldVNode.model;
if (newModel) {
const element = vnode.node, component = vnode.component;
if (!oldModel) {
addModel(api, element, component, data, vnode);
}
else if (newModel.keypath !== oldModel.keypath) {
data[MODEL_DESTROY]();
addModel(api, element, component, data, vnode);
}
else {
if (component) {
component.set(component.$model, newModel.value);
}
else {
const control = data[MODEL_CONTROL];
if (control) {
control.set(element, newModel.value);
}
}
}
}
else if (oldModel) {
data[MODEL_DESTROY]();
}
}
function beforeDestroy$2(api, vnode) {
const data = vnode.data, destroy = data[MODEL_DESTROY];
if (destroy) {
destroy();
}
}
var modelHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate$4,
afterUpdate: afterUpdate$3,
beforeDestroy: beforeDestroy$2
});
function afterCreate$3(api, vnode) {
const { nativeAttrs } = vnode;
if (nativeAttrs) {
const element = vnode.node;
for (let name in nativeAttrs) {
api.setAttr(element, name, nativeAttrs[name]);
}
}
}
function afterUpdate$2(api, vnode, oldVNode) {
const newNativeAttrs = vnode.nativeAttrs, oldNativeAttrs = oldVNode.nativeAttrs;
if (newNativeAttrs !== oldNativeAttrs) {
const element = vnode.node;
if (newNativeAttrs) {
const oldValue = oldNativeAttrs || EMPTY_OBJECT;
for (let name in newNativeAttrs) {
if (oldValue[name] === UNDEFINED
|| newNativeAttrs[name] !== oldValue[name]) {
api.setAttr(element, name, newNativeAttrs[name]);
}
}
}
if (oldNativeAttrs) {
const newValue = newNativeAttrs || EMPTY_OBJECT;
for (let name in oldNativeAttrs) {
if (newValue[name] === UNDEFINED) {
api.removeAttr(element, name);
}
}
}
}
}
var nativeAttrHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate$3,
afterUpdate: afterUpdate$2
});
function afterCreate$2(api, vnode) {
const { nativeStyles } = vnode;
if (nativeStyles) {
const elementStyle = vnode.node.style;
for (let name in nativeStyles) {
api.setStyle(elementStyle, name, nativeStyles[name]);
}
}
}
function afterUpdate$1(api, vnode, oldVNode) {
const newNativeStyles = vnode.nativeStyles, oldNativeStyles = oldVNode.nativeStyles;
if (newNativeStyles !== oldNativeStyles) {
const elementStyle = vnode.node.style;
if (newNativeStyles) {
const oldValue = oldNativeStyles || EMPTY_OBJECT;
for (let name in newNativeStyles) {
if (oldValue[name] === UNDEFINED
|| newNativeStyles[name] !== oldValue[name]) {
api.setStyle(elementStyle, name, newNativeStyles[name]);
}
}
}
if (oldNativeStyles) {
const newValue = newNativeStyles || EMPTY_OBJECT;
for (let name in oldNativeStyles) {
if (newValue[name] === UNDEFINED) {
api.removeStyle(elementStyle, name);
}
}
}
}
}
var nativeStyleHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate$2,
afterUpdate: afterUpdate$1
});
function callDirectiveCreate(data, vnode, directive) {
data[DIRECTIVE_HOOKS + directive.name] = directive.create(vnode.component || vnode.node, directive, vnode);
}
function callDirectiveHook(data, vnode, directive, hookName) {
const hooks = data[DIRECTIVE_HOOKS + directive.name], hook = hooks && hooks[hookName];
if (hook) {
hook(directive, vnode);
}
}
function genetateDirectiveHook(hookName) {
return function (api, vnode) {
const { directives } = vnode;
if (directives) {
const data = vnode.data;
for (let name in directives) {
callDirectiveHook(data, vnode, directives[name], hookName);
}
}
};
}
function afterCreate$1(api, vnode) {
const { directives } = vnode;
if (directives) {
const data = vnode.data;
for (let name in directives) {
callDirectiveCreate(data, vnode, directives[name]);
}
}
}
function beforeUpdate$1(api, vnode, oldVNode) {
const newDirectives = vnode.directives, oldDirectives = oldVNode.directives, data = vnode.data;
// 先触发 beforeDestroy 比较符合直觉
if (oldDirectives) {
const newValue = newDirectives || EMPTY_OBJECT;
for (let name in oldDirectives) {
if (newValue[name] === UNDEFINED) {
callDirectiveHook(data, vnode, oldDirectives[name], 'beforeDestroy');
}
}
}
if (newDirectives) {
const oldValue = oldDirectives || EMPTY_OBJECT, updatingDirectives = [];
for (let name in newDirectives) {
const directive = newDirectives[name];
if (oldValue[name] === UNDEFINED) {
callDirectiveCreate(data, vnode, directive);
}
else if (directive.value !== oldValue[name].value) {
callDirectiveHook(data, vnode, directive, 'beforeUpdate');
updatingDirectives.push(directive);
}
}
data[DIRECTIVE_UPDATING] = updatingDirectives;
}
}
function afterUpdate(api, vnode, oldVNode) {
const { data } = vnode;
if (data) {
const directives = data[DIRECTIVE_UPDATING];
if (directives) {
for (let i = 0, length = directives.length; i < length; i++) {
callDirectiveHook(data, vnode, directives[i], 'afterUpdate');
}
data[DIRECTIVE_UPDATING] = UNDEFINED;
}
}
}
const afterMount = genetateDirectiveHook('afterMount');
const beforeDestroy$1 = genetateDirectiveHook('beforeDestroy');
var directiveHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate$1,
beforeUpdate: beforeUpdate$1,
afterUpdate: afterUpdate,
afterMount: afterMount,
beforeDestroy: beforeDestroy$1
});
function afterCreate(api, vnode) {
const ref = vnode.ref;
if (ref) {
const context = vnode.context;
let $refs = context.$refs;
if (!$refs) {
$refs = context.$refs = {};
}
$refs[ref] = vnode.component || vnode.node;
}
}
// 删除 ref 的时候,要确保是相同的节点
// 因为模板中可能出现同一个 ref 名字,出现在不同的地方,
// 这样就可能出现一种特殊情况,即前面刚创建了 ref1,后面又把这个这个新创建的 ref1 删除了
function beforeUpdate(api, vnode, oldVNode) {
const newRef = vnode.ref, oldRef = oldVNode.ref;
if (newRef || oldRef) {
const context = vnode.context, node = vnode.component || vnode.node;
let $refs = context.$refs;
if (newRef) {
if (!oldRef) {
if (!$refs) {
$refs = context.$refs = {};
}
$refs[newRef] = node;
}
else if (newRef !== oldRef) {
if ($refs) {
if ($refs[newRef] === node) {
delete $refs[newRef];
}
}
else {
$refs = context.$refs = {};
}
$refs[newRef] = node;
}
}
else if ($refs && oldRef && $refs[oldRef] === node) {
delete $refs[oldRef];
}
}
}
function beforeDestroy(api, vnode) {
const { ref } = vnode;
if (ref) {
const { $refs } = vnode.context, node = vnode.component || vnode.node;
if ($refs && $refs[ref] === node) {
delete $refs[ref];
}
}
}
var refHook = /*#__PURE__*/Object.freeze({
__proto__: null,
afterCreate: afterCreate,
beforeUpdate: beforeUpdate,
beforeDestroy: beforeDestroy
});
function getFragmentHostNode(api, vnode) {
if (vnode.type === VNODE_TYPE_FRAGMENT
|| vnode.type === VNODE_TYPE_SLOT) {
const child = vnode.children[0];
return child
? getFragmentHostNode(api, child)
: api.createComment(EMPTY_STRING);
}
return vnode.node;
}
function insertNodeNatively(api, parentNode, node, referenceNode) {
if (referenceNode) {
api.before(parentNode, node, referenceNode);
}
else {
api.append(parentNode, node);
}
}
function textVNodeUpdateOperator(api, vnode, oldVNode) {
const node = oldVNode.node;
vnode.node = node;
vnode.parentNode = oldVNode.parentNode;
if (vnode.text !== oldVNode.text) {
api.setNodeText(node, vnode.text);
}
}
function elementVNodeEnterOperator(vnode) {
if (vnode.data) {
enterVNode(vnode, vnode.node);
}
}
function elementVNodeLeaveOperator(vnode, done) {
if (vnode.data
&& leaveVNode(vnode, vnode.node, done)) {
return;
}
done();
}
function vnodeInsertOperator(api, parentNode, vnode, before) {
// 这里不调用 insertNodeNatively,避免判断两次
if (before) {
api.before(parentNode, vnode.node, before.node);
}
else {
api.append(parentNode, vnode.node);
}
}
function vnodeRemoveOperator(api, vnode) {
api.remove(vnode.parentNode, vnode.node);
}
function vnodeLeaveOperator(vnode, done) {
done();
}
function vnodeCreateChildrenOperator(api, vnode) {
const children = vnode.children;
for (let i = 0, length = children.length; i < length; i++) {
createVNode(api, children[i]);
}
}
function vnodeUpdateChildrenOperator(api, parentNode, vnode, oldVNode) {
updateChildren(api, parentNode, vnode.children, oldVNode.children);
}
function vnodeDestroyChildrenOperator(api, vnode) {
const children =