UNPKG

yox

Version:

A lightweight mvvm framework

1,906 lines (1,870 loc) 73.8 kB
/** * 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 TRUE = true; var FALSE = false; var NULL = null; var UNDEFINED = void 0; var RAW_UNDEFINED = 'undefined'; var RAW_FUNCTION = 'function'; var RAW_LENGTH = 'length'; var RAW_WILDCARD = '*'; var RAW_DOT = '.'; /** * Single instance for window in browser */ var WINDOW = typeof window !== RAW_UNDEFINED ? window : UNDEFINED; /** * 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, 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(); } 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; } return instance; }; /** * 停止事件广播 */ CustomEvent.prototype.stopPropagation = function () { var instance = this; if (!instance.isStoped) { var originalEvent = instance.originalEvent; if (originalEvent) { originalEvent.stopPropagation(); } instance.isStoped = TRUE; } 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) { break; } } } else { for (var i$1 = 0; i$1 < length; i$1++) { if (callback(array[i$1], i$1) === 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) { var 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) { 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 ? 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) { 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 && target.toString ? target.toString() : defaultValue !== UNDEFINED ? defaultValue : EMPTY_STRING; } function isNative (target) { return func(target) && toString(target).indexOf('[native code]') >= 0; } var createPureObject = function () { var obj = Object.create(NULL); 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); } }; }; /** * 缓存一个参数的函数调用结果 * * @param fn 需要缓存的函数 * @return 带缓存功能的函数 */ function createOneKeyCache(fn) { var cache = createPureObject(); return function (key) { var hit = cache.get(key); if (hit !== UNDEFINED) { return hit; } var value = fn(key); cache.set(key, value); return value; }; } /** * 缓存两个参数的函数调用结果 * * @param fn 需要缓存的函数 * @return 带缓存功能的函数 */ function createTwoKeyCache(fn) { var cache = createPureObject(); return function (key1, key2) { var hit1 = cache.get(key1); if (hit1) { var hit2 = hit1.get(key2); if (hit2) { return hit2; } } else { hit1 = createPureObject(); 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 ? 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) { 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(str, separator) { var 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 }); 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) { 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; }); /** * 全局 value holder,避免频繁的创建临时对象 */ var holder = { value: UNDEFINED }; /** * 获取对象的 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) { 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) { // 先直接取值 var 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) { 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; } }); } /** * 对象是否包含某个 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 }); /** * 外部可用这些常量 */ 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, /** * 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; 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; if (list) { // 避免遍历过程中,数组发生变化,比如增删了 list = list.slice(); // 判断是否是发射事件 // 如果 args 的第一个参数是 CustomEvent 类型,表示发射事件 // 因为事件处理函数的参数列表是 (event, data) var customEvent = args && CustomEvent.is(args[0]) ? args[0] : UNDEFINED; // 这里不用 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) { customEvent.prevent().stop(); } else if (customEvent.isStoped) { result = FALSE; } } if (result === FALSE) { isComplete = FALSE; 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); 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, 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 */ 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; } /** * 判断 options 是否能匹配命名空间 * * 如果 namespace 和 options.ns 都不为空,则需完全匹配 * * 如果他们两个其中任何一个为空,则不判断命名空间 * * @param namespace * @param options */ function matchNamespace(namespace, options) { var ns = options.ns; return ns && namespace ? ns === namespace : TRUE; } 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(); } } }; // 下面这些值需要根据外部配置才能确定 // 保留字,避免 IE 出现 { class: 'xx' } 报错 toMap('abstract,goto,native,static,enum,implements,package,super,byte,export,import,private,protected,public,synchronized,char,extends,int,throws,class,final,interface,transient,yield,let,const,float,double,boolean,long,short,volatile,default'); var STATUS_INIT = 1; var STATUS_FRESH = 2; var STATUS_DIRTY = 3; function runGetter(instance) { var input = instance.input; var getter = instance.getter; instance.value = input ? getter.apply(UNDEFINED, input) : getter(); } function runOutput(instance) { var value = instance.value; var output = instance.output; return output ? output(value) : value; } var Deps = function() { this.map = {}; this.list = []; }; Deps.prototype.add = function (observer, dep) { var deps = this.map[observer.id] || (this.map[observer.id] = {}); if (!deps[dep]) { deps[dep] = observer; this.list.push([ observer, dep ]); } }; Deps.prototype.watch = function (watcher) { var ref = this; var list = ref.list; if (list) { for (var i = 0, length = list.length; i < length; i++) { list[i][0].watch(list[i][1], watcher); } } }; Deps.prototype.unwatch = function (watcher) { var ref = this; var list = ref.list; if (list) { for (var i = 0, length = list.length; i < length; i++) { list[i][0].unwatch(list[i][1], watcher); } } }; /** * 计算属性 * * 可配置 cache、deps, get、set 等 */ var Computed = function(keypath, cache, sync, input, output, getter, setter, onChange) { var instance = this; instance.status = STATUS_INIT; instance.keypath = keypath; instance.cache = cache; instance.input = input; instance.output = output; instance.setter = setter; instance.getter = getter; instance.onChange = onChange; instance.watcherOptions = { sync: sync, watcher: function() { instance.refresh(); } }; }; /** * 读取计算属性的值 */ Computed.prototype.get = function () { var instance = this; var status = instance.status; var watcherOptions = instance.watcherOptions; // 禁用缓存 if (!instance.cache) { runGetter(instance); } // 减少取值频率,尤其是处理复杂的计算规则 else if (status !== STATUS_FRESH) { // 如果写死了依赖,则不需要收集依赖 if (instance.staticDeps) { runGetter(instance); } // 自动收集依赖 else { var dynamicDeps = instance.dynamicDeps; // 清空上次收集的依赖 if (dynamicDeps) { dynamicDeps.unwatch(watcherOptions.watcher); } instance.dynamicDeps = UNDEFINED; var lastComputed = Computed.current; // 开始收集新的依赖 Computed.current = instance; runGetter(instance); // 取值完成,恢复原值 Computed.current = lastComputed; dynamicDeps = instance.dynamicDeps; if (dynamicDeps) { dynamicDeps.watch(watcherOptions); } } } if (status !== STATUS_FRESH) { instance.status = STATUS_FRESH; } return runOutput(instance); }; Computed.prototype.set = function (value) { var ref = this; var setter = ref.setter; if (setter) { setter(value); } else if (func(value)) { this.getter = value; this.refresh(); } }; Computed.prototype.refresh = function () { var oldValue = this.value; this.status = STATUS_DIRTY; var newValue = this.get(); if (newValue !== oldValue) { this.onChange(this.keypath, newValue, oldValue); } }; Computed.prototype.addStaticDeps = function (observer, deps) { var staticDeps = this.staticDeps || (this.staticDeps = new Deps()); for (var i = 0, length = deps.length; i < length; i++) { staticDeps.add(observer, deps[i]); } staticDeps.watch(this.watcherOptions); }; Computed.prototype.addDynamicDep = function (observer, dep) { // 动态依赖不能在这直接 watch // 只有当计算属性的依赖全部收集完了,才能监听该计算属性的所有依赖 // 这样可保证依赖最少的计算属性最先执行 watch,当依赖变化时,它也会最早触发 refresh var deps = this.dynamicDeps || (this.dynamicDeps = new Deps()); deps.add(observer, dep); }; function toNumber (target, defaultValue) { return numeric(target) ? +target : defaultValue !== UNDEFINED ? defaultValue : 0; } function readValue (source, keypath) { if (source == NULL || keypath === EMPTY_STRING) { return source; } var result = get(source, keypath); if (result) { return result.value; } } /** * 对比新旧字符串 * * @param newValue * @param oldValue * @param callback */ function diffString (newValue, oldValue, callback) { var newIsString = string$1(newValue), oldIsString = string$1(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) { var newIsArray = array$1(newValue), oldIsArray = array$1(oldValue); if (newIsArray || oldIsArray) { var newLength = newIsArray ? newValue.length : UNDEFINED, oldLength = oldIsArray ? oldValue.length : UNDEFINED; callback(RAW_LENGTH, newLength, oldLength); for (var i = 0, length = Math.max(newLength || 0, oldLength || 0); i < length; i++) { callback( // 把 number 转成 string EMPTY_STRING + i, newIsArray ? newValue[i] : UNDEFINED, oldIsArray ? oldValue[i] : UNDEFINED); } return TRUE; } } /** * 对比新旧对象 * * @param newValue * @param oldValue * @param callback */ function diffObject (newValue, oldValue, callback) { var newIsObject = object$1(newValue), oldIsObject = object$1(oldValue); if (newIsObject || oldIsObject) { var diffed = createPureObject(), newObject = newIsObject ? newValue : EMPTY_OBJECT, oldObject = oldIsObject ? oldValue : EMPTY_OBJECT; if (newIsObject) { for (var key in newObject) { var value = newObject[key]; if (value !== oldObject[key]) { // 保证遍历 oldObject 时不会再次触发 diffed.set(key, TRUE); callback(key, value, oldObject[key]); } } } if (oldIsObject) { for (var key$1 in oldObject) { var value$1 = oldObject[key$1]; if (diffed.get(key$1) === UNDEFINED && value$1 !== newObject[key$1]) { callback(key$1, newObject[key$1], value$1); } } } } } /** * 递归对比 */ function diffRecursion(keypath, newValue, oldValue, fuzzyKeypaths, fuzzyKeypathLength, callback) { var diff = function (subKey, subNewValue, subOldValue) { if (subNewValue !== subOldValue) { var newKeypath = join(keypath, subKey); for (var i = 0; i < fuzzyKeypathLength; i++) { var fuzzyKeypath = fuzzyKeypaths[i]; if (matchFuzzy(newKeypath, fuzzyKeypath) !== UNDEFINED) { callback(fuzzyKeypath, newKeypath, subNewValue, subOldValue); } } diffRecursion(newKeypath, subNewValue, subOldValue, fuzzyKeypaths, fuzzyKeypathLength, callback); } }; diffString(newValue, oldValue, diff) || diffArray(newValue, oldValue, diff) || diffObject(newValue, oldValue, diff); } function diffWatcher (keypath, newValue, oldValue, watcher, isRecursive, callback) { var fuzzyKeypaths; // 遍历监听的 keypath,如果未被监听,则无需触发任何事件 for (var watchKeypath in watcher) { // 模糊监听,如 users.*.name if (isFuzzy(watchKeypath)) { // 如果当前修改的是 users.0 整个对象 // users.0 和 users.*.name 无法匹配 // 此时要知道设置 users.0 到底会不会改变 users.*.name 需要靠递归了 // 如果匹配,则无需递归 if (matchFuzzy(keypath, watchKeypath) !== UNDEFINED) { callback(watchKeypath, keypath, newValue, oldValue); } else if (isRecursive) { if (fuzzyKeypaths) { fuzzyKeypaths.push(watchKeypath); } else { fuzzyKeypaths = [watchKeypath]; } } } // 不是模糊匹配,直接通过前缀匹配 else { // 比如监听的是 users.0.name,此时修改 users.0,则直接读出子属性值,判断是否相等 var length = match(watchKeypath, keypath); if (length >= 0) { var 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, fuzzyKeypaths.length, callback); } } // 避免频繁创建对象 var optionsHolder = { watcher: EMPTY_FUNCTION, }; /** * 格式化 watch options * * @param options */ function formatWatcherOptions (options, immediate) { var isWatcher = func(options); if (isWatcher) { optionsHolder.watcher = options; optionsHolder.immediate = immediate === TRUE; return optionsHolder; } return options; } var guid = 0; /** * 观察者有两种观察模式: * * 1. 同步监听 * 2. 异步监听 * * 对于`计算属性`这种需要实时变化的对象,即它的依赖变了,它需要立即跟着变,否则会出现不一致的问题 * 这种属于同步监听 * * 对于外部调用 observer.watch('keypath', listener),属于异步监听,它只关心是否变了,而不关心是否是立即触发的 */ var Observer = function(data, context, nextTask) { var instance = this; instance.id = guid++; instance.data = data || {}; instance.context = context || instance; instance.nextTask = nextTask || new NextTask(); instance.syncEmitter = new Emitter(); instance.asyncEmitter = new Emitter(); instance.asyncOldValues = {}; instance.asyncKeypaths = {}; instance.onComputedChange = function (keypath, newValue, oldValue) { instance.diff(keypath, newValue, oldValue); }; }; /** * 获取数据 * * @param keypath * @param defaultValue * @param depIgnore * @return */ Observer.prototype.get = function (keypath, defaultValue, depIgnore) { var instance = this; var data = instance.data; var currentComputed = Computed.current; // 传入 '' 获取整个 data if (keypath === EMPTY_STRING) { return data; } // 调用 get 时,外面想要获取依赖必须设置是谁在收集依赖 // 如果没设置,则跳过依赖收集 if (currentComputed && !depIgnore) { currentComputed.addDynamicDep(instance, keypath); } var result = get(data, keypath); return result ? result.value : defaultValue; }; /** * 更新数据 * * @param keypath * @param value */ Observer.prototype.set = function (keypath, value) { var instance = this; var data = instance.data; var setValue = function (keypath, newValue) { var oldValue = instance.get(keypath); if (newValue === oldValue) { return; } var next; each$1(keypath, function (key, index, lastIndex) { if (index === 0) { var item = data[key]; if (item && item instanceof Computed) { if (lastIndex === 0) { item.set(newValue); } else { // 这里 next 可能为空 next = item.get(); } } else { if (lastIndex === 0) { data[key] = newValue; } else { next = data[key] || (data[key] = {}); } } return; } if (next) { if (index === lastIndex) { next[key] = newValue; } else { next = next[key] || (next[key] = {}); } } }); instance.diff(keypath, newValue, oldValue); }; if (string$1(keypath)) { setValue(keypath, value); } else if (object$1(keypath)) { for (var key in keypath) { setValue(key, keypath[key]); } } }; /** * 同步调用的 diff,用于触发 syncEmitter,以及唤醒 asyncEmitter * * @param keypath * @param newValue * @param oldValue */ Observer.prototype.diff = function (keypath, newValue, oldValue) { var instance = this; var syncEmitter = instance.syncEmitter; var asyncEmitter = instance.asyncEmitter; var asyncOldValues = instance.asyncOldValues; var asyncKeypaths = instance.asyncKeypaths; var isRecursive = codeAt(keypath) !== 36; diffWatcher(keypath, newValue, oldValue, syncEmitter.listeners, isRecursive, function (watchKeypath, keypath, newValue, oldValue) { syncEmitter.fire({ type: watchKeypath, ns: EMPTY_STRING, }, [ 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) { // 这里是为了解决上面说的坑 var options = asyncEmitter.listeners[watchKeypath]; for (var i = 0, length = options.length; i < length; i++) { options[i].count++; } if (!asyncKeypaths[keypath]) { asyncOldValues[keypath] = oldValue; asyncKeypaths[keypath] = {}; } asyncKeypaths[keypath][watchKeypath] = TRUE; if (!instance.pending) { instance.pending = TRUE; instance.nextTask.append(function () { if (instance.pending) { instance.diffAsync(); } }); } }); }; /** * 异步触发的 diff */ Observer.prototype.diffAsync = function () { var instance = this; var asyncEmitter = instance.asyncEmitter; var asyncOldValues = instance.asyncOldValues; var asyncKeypaths = instance.asyncKeypaths; instance.pending = UNDEFINED; instance.asyncOldValues = {}; instance.asyncKeypaths = {}; var loop = function ( keypath ) { var args = [ instance.get(keypath), asyncOldValues[keypath], keypath ], keypaths = asyncKeypaths[keypath], hasChange = args[0] !== args[1], filterWatcher = function (event, args, options) { // 前面递增了 count // 这里要递减 count // count > 0 表示前面标记了该监听器需要响应此次变化 if (options.count) { // 采用计数器的原因是,同一个 options 可能执行多次 // 比如监听 user.*,如果同批次修改了 user.name 和 user.age // 这个监听器会调用多次,如果第一次执行就把 count 干掉了,第二次就无法执行了 options.count--; // 新旧值不相等才能触发监听器 return hasChange; } }; for (var watchKeypath in keypaths) { asyncEmitter.fire({ type: watchKeypath, ns: EMPTY_STRING, }, args, filterWatcher); } }; for (var keypath in asyncOldValues) loop( keypath ); }; /** * 添加计算属性 * * @param keypath * @param options */ Observer.prototype.addComputed = function (keypath, options) { var instance = this, context = instance.context, cache = TRUE, sync = TRUE, deps, input, getter, setter, output; // 这里用 bind 方法转换一下调用的 this // 还有一个好处,它比 call(context) 速度稍快一些 if (func(options)) { getter = options.bind(context); } else if (object$1(options)) { var computedOptions = options; if (boolean(computedOptions.cache)) { cache = computedOptions.cache; } if (boolean(computedOptions.sync)) { sync = computedOptions.sync; } if (array$1(computedOptions.deps)) { deps = computedOptions.deps; } // 参数列表必须是长度大于 0 的数组 if (!falsy$2(computedOptions.input)) { input = computedOptions.input; } if (func(computedOptions.output)) { output = computedOptions.output; }