import * as SparkMD5 from 'spark-md5';
import { cancelable } from './CancelablePromise';
import { sscanf } from '../../libs/scanf/scanf';
import { utf8ToBytes } from '../../libs/utf8-encoder';
import { LogLevelType, LayoutType } from '../const';
import { BaseUtils } from './BaseUtils';
const _defaultOptions = {};
const _emptyArray = [];
var _factories = [];
var _macros = {};
var _registeredClasses = {};
var _registeredComponents = {};
var _registeredVariables = {};
var _urlResolvers = [];
let _workPath = '';
let _globalEnvVars = {};
var _lastAttributeKeyCache = { key: "", keys: [] }
var _timer = typeof performance === 'undefined' ? Date : performance;
var _global = typeof window !== 'undefined' ? window : (typeof global !== 'undefined' ? global : null);
const _colorKeywords = {
'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF,
'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2,
'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50,
'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B,
'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B,
'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F,
'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3,
'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222,
'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700,
'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4,
'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00,
'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3,
'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA,
'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32,
'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3,
'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC,
'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD,
'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6,
'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9,
'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F,
'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE,
'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA,
'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0,
'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32
};
/**
* Parse color from hex value.
* @param {number} value The hex value.
* @param {Array} target The target array to store the result.
* @returns {Array} The target array.
* @example
* // 将十六进制颜色值解析为RGB数组(0-1范围)
* let color = [];
* _parseColorFromHex(0xFF0000, color); // 红色
* console.log(color); // [1, 0, 0]
*
* _parseColorFromHex(0x00FF00, color); // 绿色
* console.log(color); // [0, 1, 0]
*/
function _parseColorFromHex(value, target) {
let hex = Math.floor(value);
target[0] = (hex >> 16 & 255) / 255;
target[1] = (hex >> 8 & 255) / 255;
target[2] = (hex & 255) / 255;
return target;
}
/**
* Read a file in chunks.
* @param {Blob} buffer The file buffer.
* @param {Function} chunkCallback The callback function for each chunk.
* @param {Function} endCallback The callback function when reading is complete.
* @example
* // 分块读取大文件
* let file = new Blob(['大文件内容...'], { type: 'text/plain' });
*
* _readChunked(
* file,
* function(chunk, offset, fileSize) {
* // 处理每个数据块
* console.log('已读取:', Math.round(offset / fileSize * 100) + '%');
* },
* function(error) {
* if (error) {
* console.error('读取出错:', error);
* } else {
* console.log('读取完成');
* }
* }
* );
*/
function _readChunked(buffer, chunkCallback, endCallback) {
const blob = new Blob([buffer]); // 将 ArrayBuffer 转为 Blob
const chunkSize = 4 * 1024 * 1024; // 4MB
let offset = 0;
const fileSize = blob.size; // 使用 .size 属性
const reader = new FileReader();
reader.onload = function () {
if (reader.error) {
endCallback(reader.error || {});
return;
}
offset += reader.result.byteLength; // 使用 .byteLength 获取 ArrayBuffer 长度
chunkCallback(reader.result, offset, fileSize);
if (offset >= fileSize) {
endCallback(null);
return;
}
readNext();
};
reader.onerror = function (err) {
endCallback(err || {});
};
function readNext() {
const blobSlice = blob.slice(offset, offset + chunkSize);
reader.readAsArrayBuffer(blobSlice);
}
readNext();
}
// Try to ignore eval compile warning
var eval2 = eval;
var _eval = function (script) {
try {
return eval2(script);
}
catch (e) {
}
}
var _objectKeysToLowerCase = function (input, deep, filter) {
var idx, key, keys, last, output, self, type, value;
self = _objectKeysToLowerCase;
type = typeof deep;
// Convert "deep" to a number between 0 to Infinity or keep special object.
if (type === 'undefined' || deep === null || deep === 0 || deep === false) {
deep = 0; // Shallow copy
}
else if (type === 'object') {
if (!(deep instanceof self)) {
throw new TypeError('Expected "deep" to be a special object');
}
}
else if (deep === true) {
deep = Infinity; // Deep copy
}
else if (type === 'number') {
if (isNaN(deep) || deep < 0) {
throw new RangeError(
'Expected "deep" to be a positive number, got ' + deep
);
}
}
else {
throw new TypeError(
'Expected "deep" to be a boolean, number or object, got "' + type + '"'
);
}
// Check type of input, and throw if null or not an object.
if (input === null || typeof input !== 'object') {
throw new TypeError('Expected "input" to be an object');
}
// Check type of filter
type = typeof filter;
if (filter === null || type === 'undefined' || type === 'function') {
filter = filter || null;
}
else {
throw new TypeError('Expected "filter" to be a function');
}
keys = Object.keys(input); // Get own keys from object
last = keys.length - 1;
output = {}; // new object
if (deep) { // only run the deep copy if needed.
if (typeof deep === 'number') {
// Create special object to be used during deep copy
deep =
Object.seal(
Object.create(
self.prototype,
{
input: { value: [] },
output: { value: [] },
level: { value: -1, writable: true },
max: { value: deep, writable: false }
}
)
);
}
else {
// Circle detection
idx = deep.input.indexOf(input);
if (~idx) {
return deep.output[idx];
}
}
deep.level += 1;
deep.input.push(input);
deep.output.push(output);
idx = last + 1;
while (idx--) {
key = keys[last - idx]; // Using [last - idx] to preserve order.
value = input[key];
if (typeof value === 'object' && value && deep.level < deep.max) {
if (value.constructor === Object) {
value = self(value, deep, filter);
}
}
var _filter = filter || function () { return true; }
if (_filter(key, value)) {
output[key.toLowerCase()] = value;
}
else {
output[key] = value;
}
}
deep.level -= 1;
}
else {
// Simple shallow copy
idx = last + 1;
while (idx--) {
key = keys[last - idx]; // Using [last - idx] to preserve order.
var _filter = filter || function () { return true; }
if (_filter(key, value)) {
output[key.toLowerCase()] = input[key];
}
else {
output[key] = input[key];
}
}
}
return output;
}
// Thanks for sharing code: http://jsfiddle.net/cattail/NYNT5/
// classify object
var classof = function (o) {
if (o === null) { return "null" }
if (o === undefined) { return "undefined" }
// I suppose Object.prototype.toString use obj.constructor.name
// to generate string
var className = Object.prototype.toString.call(o).slice(8, -1);
return className;
};
var _references = [];
var _handlers = {
// Handle regexp and date even in shallow.
'RegExp': function (reg) {
var flags = '';
flags += reg.global ? 'g' : '';
flags += reg.multiline ? 'm' : '';
flags += reg.ignoreCase ? 'i' : '';
return new RegExp(reg.source, flags);
},
'Date': function (date) {
return new Date(+date);
},
'Array': function (arr, shallow, filterFunc, cloneFunc) {
var newArr = [], i;
for (i = 0; i < arr.length; i++) {
if (shallow) {
newArr[i] = arr[i];
}
else {
// handle circular reference
if (_references.indexOf(arr[i]) !== -1) {
continue;
}
// try to use clone function to copy value
if (cloneFunc && arr[i]) {
var clonedObject = cloneFunc(arr[i]);
if (clonedObject) {
newArr[i] = clonedObject;
continue;
}
}
var handler = _handlers[classof(arr[i])];
if (handler) {
_references.push(arr[i]);
newArr[i] = handler(arr[i], false);
}
else {
newArr[i] = arr[i];
}
}
}
return newArr;
},
'Object': function (obj, shallow, filterFunc, cloneFunc) {
var newObj = {}, prop, handler;
for (prop in obj) {
if (filterFunc && !filterFunc(prop)) {
continue;
}
// escape prototype properties
if (shallow) {
newObj[prop] = obj[prop];
}
else {
// handle circular reference
if (_references.indexOf(obj[prop]) !== -1) {
continue;
}
// try to use clone function to copy value
if (cloneFunc && obj[prop]) {
var clonedObject = cloneFunc(obj[prop], prop);
if (clonedObject) {
newObj[prop] = clonedObject;
continue;
}
}
// recursive
handler = _handlers[classof(obj[prop])];
if (handler) {
_references.push(obj[prop]);
newObj[prop] = handler(obj[prop], false, filterFunc, cloneFunc);
}
else {
newObj[prop] = obj[prop];
}
}
}
return newObj;
}
};
// Thanks for sharing code: https://gist.github.com/david-risney/af6f7912ea171b9076d3ba3ebd54a355
/**
*
* @param {object} parent 父对象
* @param {string[]} fullNamePath 属性路径
* @param {object} feedback 回调函数
* @example
* // 为对象属性设置断点
* let obj = { value: 10 };
*
* _valueBreakpoint(obj, ['value'], {
* onCompareValue: function(oldValue, newValue) {
* // 当值发生变化时触发断点
* return oldValue !== newValue;
* },
* onValueChanged: function(newValue) {
* console.log('值已更改为:', newValue);
* }
* });
*
* obj.value = 20; // 会触发断点
*/
function _valueBreakpoint(parent, fullNamePath, feedback) {
var name = fullNamePath[0];
var childNamePath = fullNamePath.slice(1);
var innerValue = parent[name];
var childBreakpoints = [];
function applyChildValueBreakpoints() {
if (childNamePath.length > 0 && innerValue) {
childBreakpoints.push(_valueBreakpoint(innerValue, childNamePath, feedback));
}
}
applyChildValueBreakpoints();
Object.defineProperty(parent, name, {
get: (function () {
return innerValue;
}).bind(this),
set: (function (newValue) {
if (!childNamePath.length) {
var valueChanged = false;
var onCompareValue = feedback.onCompareValue;
if (onCompareValue && onCompareValue(innerValue, newValue)) {
_eval('debugger'); // use eval here to prevent optimize debugger keyword away
valueChanged = true;
}
}
innerValue = newValue;
applyChildValueBreakpoints();
if (valueChanged) {
var onValueChanged = feedback.onValueChanged;
if (onValueChanged) {
onValueChanged(newValue);
}
}
}).bind(this),
enumerable: true,
configurable: true
});
}
let _originalValueOf = null;
/**
* 启用NaN捕获。
* @param {boolean} value 是否启用。
* @example
* // 启用NaN捕获
* _enableNaNCatch(true);
*
* // 这将抛出错误,因为试图将非数字对象转换为数字
* let obj = {};
* let num = obj + 1; // 将触发错误
*
* // 禁用NaN捕获
* _enableNaNCatch(false);
*/
function _enableNaNCatch(value) {
if (value && !_originalValueOf) {
_originalValueOf = Object.prototype.valueOf;
Object.prototype.valueOf = function () {
if (typeof this !== 'number') {
throw new Error('Object is not a Number');
}
return _originalValueOf.call(this);
}
}
else {
Object.prototype.valueOf = _originalValueOf;
_originalValueOf = null;
}
}
function _clearTimeoutById(id, callbacks) {
if (!id) {
return;
}
let index = Utils.binarySearch(callbacks, (timer) => {
return timer.id - id;
});
if (index === -1) {
return;
}
callbacks.splice(index, 1);
}
/**
* @class
* The timer to replace the global setTimeout() function
* @memberof THING
*/
class Timer {
static id = 0;
/**
* The number of batch timeout callback in one frame.
* @type {number}
* @private
*/
static batchNumber = 100;
static batchTimeoutCallbacks = [];
// Update callback functions
static updateCallbacks = [];
// Timeout callback functions
static timeoutCallbacks = [];
static directTimeoutCallbacks = [];
/**
* Set timer callback function in batch mode.
* @param {Function} callback The callback function.
* @private
* @example
* // 设置批量定时器回调函数
* THING.Utils.setBatchTimeout(function() {
* console.log('每帧执行一次');
* });
*/
static setBatchTimeout(callback) {
Timer.batchTimeoutCallbacks.push(callback);
}
/**
* 设置定时器回调函数。
* @param {Function} callback 回调函数。
* @param {number} milliseconds 延迟时间(毫秒)。
* @returns {number|null} 定时器ID。
* @example
* // 设置一个延时执行的函数
* let timerId = THING.Utils.setTimeout(function() {
* console.log('3秒后执行');
* }, 3000);
*
* // 可以通过返回的ID取消定时器
* // THING.Utils.clearTimeout(timerId);
*/
static setTimeout(callback, milliseconds) {
if (milliseconds === undefined) {
Timer.directTimeoutCallbacks.push(callback);
// Direct timeout would return nothing, due to we can not clear it
return null;
}
else {
var id = ++Timer.id;
Timer.timeoutCallbacks.push({ id, callback, time: milliseconds / 1000 });
return id;
}
}
/**
* Clear timeout callback function.
* @param {number} id The timer id.
* @example
* // 设置一个延时执行的函数
* let timerId = THING.Utils.setTimeout(function() {
* console.log('这条消息不会显示,因为定时器被取消了');
* }, 3000);
*
* // 取消定时器
* THING.Utils.clearTimeout(timerId);
*/
static clearTimeout(id) {
_clearTimeoutById(id, Timer.timeoutCallbacks);
}
/**
* 更新定时器。
* @param {number} deltaTime 时间差。
* @example
* // 更新定时器
* THING.Utils.update(1);
*/
static update(deltaTime) {
// Direct timeout ...
let length = Timer.directTimeoutCallbacks.length;
if (length) {
// To prevent dead loop calling in callback functions, so we copy callback functions here
let directTimeoutCallbacks = Timer.directTimeoutCallbacks.slice(0);
Timer.directTimeoutCallbacks.splice(0, length);
for (let i = 0; i < length; i++) {
directTimeoutCallbacks[i]();
}
}
// Delay timeout ...
var timeoutCallbacks = Timer.timeoutCallbacks;
for (var i = 0; i < timeoutCallbacks.length; i++) {
var info = timeoutCallbacks[i];
info.time -= deltaTime;
if (info.time > 0) {
continue;
}
timeoutCallbacks.splice(i--, 1);
info.callback();
}
// Batch timeout ...
if (Timer.batchTimeoutCallbacks.length) {
let number = Math.min(Timer.batchTimeoutCallbacks.length, Timer.batchNumber);
for (let i = 0; i < number; i++) {
Timer.batchTimeoutCallbacks[i]();
}
Timer.batchTimeoutCallbacks.splice(0, number);
}
// Update callbacks ...
if (Timer.updateCallbacks.length) {
Timer.updateCallbacks.forEach(callback => {
callback(deltaTime);
});
}
}
/**
* 添加更新回调函数。
* @param {Function} callback 回调函数。
* @example
* // 添加更新回调函数
* THING.Utils.addUpdateCallback(function(deltaTime) {
* console.log('每帧更新,时间差:', deltaTime);
* });
*/
static addUpdateCallback(callback) {
Timer.updateCallbacks.push(callback);
}
/**
* 移除更新回调函数。
* @param {Function} callback 回调函数。
* @example
* // 移除更新回调函数
* THING.Utils.removeUpdateCallback(function(deltaTime) {
* console.log('每帧更新,时间差:', deltaTime);
* });
*/
static removeUpdateCallback(callback) {
let index = Timer.updateCallbacks.indexOf(callback);
if (index == -1) {
return;
}
Timer.updateCallbacks.splice(index, 1);
}
}
/**
* @class Utils
* Useful functions.
* @memberof THING
*/
class Utils extends BaseUtils {
/**
* The log level type.
* @type {LogLevelType}
*/
static logLevelType = LogLevelType.All;
/**
* 设置批量定时器数量。
* @param {number} value 批量定时器数量。
* @example
* // 设置批量定时器数量
* THING.Utils.setBatchTimeoutNumber(100);
*/
static setBatchTimeoutNumber(value) {
Timer.batchNumber = value;
}
/**
* 获取批量定时器数量。
* @returns {number} 批量定时器数量。
* @example
* // 获取批量定时器数量
* const number = THING.Utils.getBatchTimeoutNumber();
* console.log(number); // 100
*/
static getBatchTimeoutNumber() {
return Timer.batchNumber;
}
/**
* 设置批量定时器回调函数。
* @param {Function} callback 回调函数。
* @example
* // 设置批量定时器回调函数
* THING.Utils.setBatchTimeout(function() {
* console.log('每帧执行一次');
* });
*/
static setBatchTimeout(callback) {
Timer.setBatchTimeout(callback);
}
/**
* Set timeout callback function.
* @param {Function} callback The callback function.
* @param {number} milliseconds The delay time in milliseconds.
* @returns {number} If milliseconds is undefined then return nothing and the timer can not be clear
* @example
* // 设置一个延时执行的函数
* let timerId = THING.Utils.setTimeout(function() {
* console.log('3秒后执行');
* }, 3000);
*
* // 可以通过返回的ID取消定时器
* // THING.Utils.clearTimeout(timerId);
*/
static setTimeout(callback, milliseconds) {
return Timer.setTimeout(callback, milliseconds);
}
/**
* Clear timeout callback function.
* @param {number} id The timeout ID.
* @example
* // 设置一个延时执行的函数
* let timerId = THING.Utils.setTimeout(function() {
* console.log('这条消息不会显示,因为定时器被取消了');
* }, 3000);
*
* // 取消定时器
* THING.Utils.clearTimeout(timerId);
*/
static clearTimeout(id) {
Timer.clearTimeout(id);
}
/**
* Clear all timeouts.
* @example
* // 设置多个定时器
* THING.Utils.setTimeout(function() { console.log('定时器1'); }, 1000);
* THING.Utils.setTimeout(function() { console.log('定时器2'); }, 2000);
* THING.Utils.setTimeout(function() { console.log('定时器3'); }, 3000);
*
* // 清除所有定时器
* THING.Utils.clearAllTimeout();
*/
static clearAllTimeout() {
Timer.timeoutCallbacks = [];
Timer.directTimeoutCallbacks = [];
Timer.batchTimeoutCallbacks = [];
}
/**
* Add update callback.
* @param {Function} callback The callback function.
* @example
* // 添加一个每帧更新的回调函数
* function onUpdate(deltaTime) {
* console.log('每帧更新,时间差:', deltaTime);
* }
*
* THING.Utils.addUpdateCallback(onUpdate);
*/
static addUpdateCallback(callback) {
Timer.addUpdateCallback(callback);
}
/**
* Remove update callback.
* @param {Function} callback The callback function.
* @example
* // 定义一个更新函数
* function onUpdate(deltaTime) {
* console.log('每帧更新,时间差:', deltaTime);
* }
*
* // 添加更新回调
* THING.Utils.addUpdateCallback(onUpdate);
*
* // 移除更新回调
* THING.Utils.removeUpdateCallback(onUpdate);
*/
static removeUpdateCallback(callback) {
Timer.removeUpdateCallback(callback);
}
/**
* Update the timer.
* @param {number} deltaTime The time difference.
* @example
* // 更新定时器
* THING.Utils.update(1);
*/
static update(deltaTime) {
Timer.update(deltaTime);
}
/**
* Disable/Enable console debug.
* @type {boolean}
* @example
* // 禁用console.debug输出
* THING.Utils.disableDebug = true;
* console.debug('这条消息不会显示');
*
* // 启用console.debug输出
* THING.Utils.disableDebug = false;
* console.debug('这条消息会显示');
*/
static get disableDebug() {
return console.debug != _consoleDebugFunc;
}
/**
*
*/
static set disableDebug(value) {
if (value) {
console.debug = function () { };
}
else {
console.debug = _consoleDebugFunc;
}
}
/**
* Disable/Enable console log.
* @type {boolean}
* @example
* // 禁用console.log输出
* THING.Utils.disableLog = true;
* console.log('这条消息不会显示');
*
* // 启用console.log输出
* THING.Utils.disableLog = false;
* console.log('这条消息会显示');
*/
static get disableLog() {
return console.log != _consoleLogFunc;
}
/**
*
*/
static set disableLog(value) {
if (value) {
console.log = function () { };
}
else {
console.log = _consoleLogFunc;
}
}
/**
* Disable/Enable console warn.
* @type {boolean}
* @example
* // 禁用console.warn输出
* THING.Utils.disableWarn = true;
* console.warn('这条警告不会显示');
*
* // 启用console.warn输出
* THING.Utils.disableWarn = false;
* console.warn('这条警告会显示');
*/
static get disableWarn() {
return console.warn != _consoleWarnFunc;
}
/**
*
*/
static set disableWarn(value) {
if (value) {
console.warn = function () { };
}
else {
console.warn = _consoleWarnFunc;
}
}
/**
* Disable/Enable console error.
* @type {boolean}
* @example
* // 禁用console.error输出
* THING.Utils.disableError = true;
* console.error('这条错误不会显示');
*
* // 启用console.error输出
* THING.Utils.disableError = false;
* console.error('这条错误会显示');
*/
static get disableError() {
return console.error != _consoleErrorFunc;
}
/**
*
*/
static set disableError(value) {
if (value) {
console.error = function () { };
}
else {
console.error = _consoleErrorFunc;
}
}
/**
* Disable/Enable console time.
* @type {boolean}
* @example
* // 禁用console.time相关函数
* THING.Utils.disableTime = true;
* console.time('计时器'); // 不会生效
* console.timeEnd('计时器'); // 不会生效
*
* // 启用console.time相关函数
* THING.Utils.disableTime = false;
* console.time('计时器'); // 开始计时
* // 执行一些操作...
* console.timeEnd('计时器'); // 结束计时并显示耗时
*/
static get disableTime() {
return console.time != _consoleTimeFunc;
}
/**
*
*/
static set disableTime(value) {
if (value) {
console.time = function () { };
console.timeEnd = function () { };
}
else {
console.time = _consoleTimeFunc;
console.timeEnd = _consoleTimeEndFunc;
}
}
/**
* 解析对象。
* @param {any} value 要解析的值。
* @param {any} defaultValue 默认值。
* @returns {any} 解析后的值。
* @example
* // 解析对象
* const obj = THING.Utils.parseObject({ a: 1, b: 2 });
* console.log(obj); // { a: 1, b: 2 }
*/
static parseObject(value, defaultValue = {}) {
if (Utils.isObject(value)) {
return Utils.mergeObject({}, value, true);
}
return Utils.mergeObject({}, defaultValue);
}
/**
* 解析JSON。
* @param {any} object 要解析的值。
* @returns {any} 解析后的值。
* @example
* // 解析JSON
* const obj = THING.Utils.parseJSON({ a: 1, b: 2 });
* console.log(obj); // { a: 1, b: 2 }
*/
static parseJSON(object) {
try {
if (Utils.isArrayBuffer(object)) {
return JSON.parse(Utils.arrayBufferToString(object));
}
else if (Utils.isArray(object) || Utils.isUint8Array(object)) {
return JSON.parse(Utils.bufferToString(object));
}
else {
return JSON.parse(object);
}
}
catch (ex) {
// Utils.error(ex);
return {};
}
}
/**
* 获取对象中的函数。
* @param {any} object 对象。
* @returns {Array<string>} 函数名数组。
* @example
* // 获取对象中的函数
* const functions = THING.Utils.getFunctionsFromObject({ a: 1, b: 2 });
* console.log(functions); // ['a', 'b']
*/
static getFunctionsFromObject(object) {
var funcNames = [];
var names_1 = Object.getOwnPropertyNames(Object.getPrototypeOf(object));
var names_2 = Object.getOwnPropertyNames(Object.getPrototypeOf(object.__proto__));
var names = names_1.concat(names_2);
for (let name of names) {
let method = object[name];
if (!(method instanceof Function)) {
continue;
}
if (name == 'constructor') {
continue;
}
funcNames.push(name);
}
return funcNames.sort();
}
/**
* 获取类类型中的函数。
* @param {any} classType 类类型。
* @returns {Array<string>} 函数名数组。
* @example
* // 获取类类型中的函数
* const functions = THING.Utils.getFunctionsFromClassType(Thing);
* console.log(functions); // ['a', 'b']
*/
static getFunctionsFromClassType(classType) {
return Utils.getFunctionsFromObject(new classType());
}
/**
* 获取类类型中的静态函数。
* @param {any} classType 类类型。
* @returns {Array<string>} 函数名数组。
* @example
* // 获取类类型中的静态函数
* const functions = THING.Utils.getStaticFunctionsFromClassType(Thing);
* console.log(functions); // ['a', 'b']
*/
static getStaticFunctionsFromClassType(classType) {
return Object.getOwnPropertyNames(classType).filter(funcName => {
return Utils.isFunction(classType[funcName]);
});
}
/**
* 获取类类型中的类名。
* @param {any} proto 类类型。
* @returns {string} 类名。
* @example
* // 获取类类型中的类名
* const className = THING.Utils.getClassNameFromProto(Thing);
* console.log(className); // 'Thing'
*/
static getClassNameFromProto(proto) {
return proto.constructor.className || proto.constructor.name;
}
/**
* 获取类类型中的类名。
* @param {any} proto 类类型。
* @returns {Array<string>} 类名数组。
* @example
* // 获取类类型中的类名
* const classNames = THING.Utils.getClassNamesFromProto(Thing);
* console.log(classNames); // ['Thing']
*/
static getClassNamesFromProto(proto) {
var classNames = [];
while (proto) {
var className = Utils.getClassNameFromProto(proto);
if (className != 'Object') {
if (classNames.indexOf(className) === -1) {
classNames.push(className);
}
}
proto = proto.__proto__;
}
return classNames;
}
/**
* Output debug log.
* @example
* // 输出调试信息
* THING.Utils.debug('调试信息');
*/
static debug() {
if (Utils.logLevelType & LogLevelType.Debug) {
console.debug.apply(console, arguments);
}
}
/**
* Output log.
* @example
* // 输出日志信息
* THING.Utils.log('日志信息');
*/
static log() {
if (Utils.logLevelType & LogLevelType.Log) {
console.log.apply(console, arguments);
}
}
/**
* Output warn log.
* @example
* // 输出警告信息
* THING.Utils.warn('警告信息');
*/
static warn() {
if (Utils.logLevelType & LogLevelType.Warn) {
console.warn.apply(console, arguments);
}
}
/**
* Output error log.
* @example
* // 输出错误信息
* THING.Utils.error('错误信息');
*/
static error() {
if (Utils.logLevelType & LogLevelType.Error) {
console.error.apply(console, arguments);
}
}
/**
* 获取当前时间。
* @returns {number} 当前时间(秒)。
* @example
* // 获取当前时间
* const currentTime = THING.Utils.getCurrentTime();
* console.log(currentTime); // 当前时间(秒)
*/
static getCurrentTime() {
return _timer.now() / 1000;
}
/**
* 计算从给定时间到当前时间的时间差(秒)。
* @param {number} time 给定的时间(秒)。
* @returns {number} 从给定时间到当前时间的时间差(秒)。
* @example
* // 计算从给定时间到当前时间的时间差
* const elapsedTime = THING.Utils.getElapsedTime(1609459200); // 假设给定的时间是2021年1月1日0时0分0秒
* console.log(elapsedTime); // 输出从给定时间到当前时间的时间差(秒)
*/
static getElapsedTime(time) {
return Utils.getCurrentTime() - time;
}
/**
* 获取当前时间(毫秒)。
* @returns {number} 当前时间(毫秒)。
* @example
* // 获取当前时间(毫秒)
* const currentTimeMilliseconds = THING.Utils.getCurrentTimeMilliseconds();
* console.log(currentTimeMilliseconds); // 输出当前时间(毫秒)
*/
static getCurrentTimeMilliseconds() {
return _timer.now();
}
/**
* 计算从给定时间到当前时间的时间差(毫秒)。
* @param {number} time 给定的时间(毫秒)。
* @returns {number} 从给定时间到当前时间的时间差(毫秒)。
* @example
* // 计算从给定时间到当前时间的时间差(毫秒)
* const elapsedTimeMilliseconds = THING.Utils.getElapsedTimeMilliseconds(1609459200000); // 假设给定的时间是2021年1月1日0时0分0秒
* console.log(elapsedTimeMilliseconds); // 输出从给定时间到当前时间的时间差(毫秒)
*/
static getElapsedTimeMilliseconds(time) {
return Utils.getCurrentTimeMilliseconds() - time;
}
/**
* 设置数据/值断点。
* @param {object} object 对象。
* @param {string | Array<string>} attributes 对象的属性名(或属性名数组)。
* @param {Function} callback 值比较回调函数,true表示断点,null表示清除断点。
* @returns {object} 返回一个对象,包含onValueChanged方法,用于设置值变化回调函数。
* @example
* // 设置数据/值断点
* THING.Utils.setValueBreakpoint(obj, 'value', function(oldValue, newValue) {
* return oldValue !== newValue;
* });
*
* // 设置多个属性断点
* THING.Utils.setValueBreakpoint(obj, ['value', 'name'], function(oldValue, newValue) {
* return oldValue !== newValue;
* });
*/
static setValueBreakpoint(object, attributes, callback = (a, b) => { return a != b; }) {
var feedback = {
onCompareValue: callback,
onValueChanged: function () { }
};
var reuslt = {
onValueChanged: function (callback) {
feedback.onValueChanged = callback;
}
}
if (Utils.isArray(attributes)) {
attributes.forEach(name => {
_valueBreakpoint(object, [name], feedback);
});
}
else if (Utils.isString(attributes)) {
_valueBreakpoint(object, [attributes], feedback);
}
return reuslt;
}
/**
* 启用NaN捕获。
* @param {boolean} value 是否启用NaN捕获。
* @example
* // 启用NaN捕获
* THING.Utils.enableNaNCatch(true);
*
* // 禁用NaN捕获
* THING.Utils.enableNaNCatch(false);
*/
static enableNaNCatch(value = true) {
_enableNaNCatch(value);
}
/**
* 获取两个对象的所有唯一键名。
* @param {object} obj1 第一个对象。
* @param {object} obj2 第二个对象。
* @returns {Array<string>} 返回一个包含所有唯一键名的数组。
* @example
* // 获取两个对象的所有唯一键名
* const obj1 = { name: 'John', age: 30 };
* const obj2 = { age: 25, city: 'Beijing' };
*
* const unionKeys = THING.Utils.getUnionKeys(obj1, obj2);
* console.log(unionKeys); // ['name', 'age', 'city']
*
* // 处理重复键
* const obj3 = { x: 1, y: 2 };
* const obj4 = { y: 3, z: 4 };
* const keys = THING.Utils.getUnionKeys(obj3, obj4);
* console.log(keys); // ['x', 'y', 'z']
*/
static getUnionKeys(obj1, obj2) {
var keys1 = Object.keys(obj1);
var keys2 = Object.keys(obj2);
for (var i = 0, l = keys2.length; i < l; i++) {
var key = keys2[i];
if (keys1.indexOf(key) == -1) {
keys1.push(key);
}
}
return keys1;
}
/**
* 格式化金额。
* @param {number} value 金额。
* @param {number} fixedNumber 小数位数。
* @returns {string} 格式化后的金额。
* @example
* // 格式化金额
* const formattedValue = THING.Utils.formatMoney(1234.5678, 2);
* console.log(formattedValue); // "1,234.57"
*/
static formatMoney(value, fixedNumber = 0) {
return value.toFixed(fixedNumber).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
/**
* 格式化字节大小。
* @param {number} value 字节大小。
* @param {number} fixedNumber 小数位数。
* @param {string} suffixName 后缀名。
* @returns {string} 格式化后的字节大小。
* @example
* // 格式化字节大小
* const formattedValue = THING.Utils.formatByteSize(1234567890, 2, 'B');
* console.log(formattedValue); // "1.23 GB"
*/
static formatByteSize(value, fixedNumber = 2, suffixName = 'B') {
if (value > 1000 * 1000 * 1000 * 1000) {
return Utils.formatMoney(value / (1000 * 1000 * 1000 * 1000), fixedNumber) + " T" + suffixName;
}
else if (value > 1000 * 1000 * 1000) {
return Utils.formatMoney(value / (1000 * 1000 * 1000), fixedNumber) + " G" + suffixName;
}
else if (value > 1000 * 1000) {
return Utils.formatMoney(value / (1000 * 1000), fixedNumber) + " M" + suffixName;
}
else if (value > 1000) {
return Utils.formatMoney(value / (1000), fixedNumber) + " K" + suffixName;
}
else {
if (suffixName) {
return Utils.formatMoney(value, fixedNumber) + " " + suffixName;
}
else {
return Utils.formatMoney(value, fixedNumber);
}
}
}
/**
* Allocate an array of objects of a specific class type.
* @param {Function} classType The class constructor.
* @param {number} number The number of elements to create.
* @returns {Array} An array of new instances of the class.
* @example
* // 创建指定类型的对象数组
* class MyClass {
* constructor() {
* this.value = 0;
* }
* }
*
* // 创建5个MyClass实例的数组
* const instances = THING.Utils.allocateArray(MyClass, 5);
* console.log(instances.length); // 5
* console.log(instances[0] instanceof MyClass); // true
*/
static allocateArray(classType, number) {
if (classType) {
return null;
}
var elements = [];
for (var i = 0; i < number; i++) {
elements.push(new classType());
}
return elements;
}
/**
* 获取数组长度。
* @param {Array} arr 数组。
* @returns {number} 数组长度。
* @example
* // 获取数组长度
* const arr = [1, 2, 3];
* const length = THING.Utils.getArrayLength(arr);
* console.log(length); // 3
*/
static getArrayLength(arr) {
if (!Utils.isArray(arr)) {
return 0;
}
return arr.length;
}
/**
* 检查数组是否为空。
* @param {Array} arr 数组。
* @returns {boolean} 是否为空。
* @example
* // 检查数组是否为空
* const emptyArr = [];
* const nonEmptyArr = [1, 2, 3];
*
* THING.Utils.isEmptyArray(emptyArr); // true
* THING.Utils.isEmptyArray(nonEmptyArr); // false
*/
static isEmptyArray(arr) {
return Utils.isType('Array')(arr) && arr.length == 0;
}
/**
* Push an element to array if it doesn't exist.
* @param {Array} arr The array.
* @param {*} element The element to push.
* @example
* // 将元素添加到数组中(如果数组中不存在该元素)
* const arr = [1, 2, 3];
*
* // 添加不存在的元素
* THING.Utils.pushToArray(arr, 4);
* console.log(arr); // [1, 2, 3, 4]
*
* // 添加已存在的元素(不会重复添加)
* THING.Utils.pushToArray(arr, 3);
* console.log(arr); // [1, 2, 3, 4]
*
* // 添加null或undefined(不会添加)
* THING.Utils.pushToArray(arr, null);
* console.log(arr); // [1, 2, 3, 4]
*/
static pushToArray(arr, element) {
if (!element) {
return;
}
var index = arr.indexOf(element);
if (index === -1) {
arr.push(element);
}
}
/**
* Remove an element from array.
* @param {Array} arr The array.
* @param {*} element The element to remove.
* @returns {boolean} True if the element was removed.
* @example
* // 从数组中移除元素
* const arr = [1, 2, 3, 4];
*
* // 移除存在的元素
* const removed = THING.Utils.removeFromArray(arr, 3);
* console.log(removed); // true
* console.log(arr); // [1, 2, 4]
*
* // 移除不存在的元素
* const notRemoved = THING.Utils.removeFromArray(arr, 5);
* console.log(notRemoved); // false
* console.log(arr); // [1, 2, 4]
*
* // 传入null数组
* const nullResult = THING.Utils.removeFromArray(null, 1);
* console.log(nullResult); // false
*/
static removeFromArray(arr, element) {
if (!arr) {
return false;
}
var index = arr.indexOf(element);
if (index === -1) {
return false;
}
arr._removeAt(index);
return true;
}
/**
* 获取值在数组中的索引。
* @param {*} value 值。
* @param {Array} arrs 数组。
* @returns {number} 索引。
* @example
* // 获取值在数组中的索引
* const arr = [1, 2, 3];
* const index = THING.Utils.getIndexFromArrays(2, arr);
* console.log(index); // 1
*/
static getIndexFromArrays(value, arrs) {
for (var i = 0; i < arrs.length; i++) {
var index = arrs[i].indexOf(value);
if (index !== -1) {
return index;
}
}
return -1;
}
/**
* 合并数组。
* @param {object} object 对象。
* @param {string} key 键。
* @param {Array} elements 元素。
* @example
* // 合并数组
* const obj = { items: [] };
* const elements = [4, 5, 6];
* THING.Utils.combineArray(obj, 'items', elements);
* console.log(obj.items); // [1, 2, 3, 4, 5, 6]
*/
static combineArray(object, key, elements) {
object[key] = object[key] || [];
if (Utils.isArray(elements)) {
object[key].concat(elements);
}
else {
object[key].push(elements);
}
}
/**
* 将字符串转换为小写。
* @param {string} s 字符串。
* @returns {string} 小写字符串。
* @example
* // 将字符串转换为小写
* const str = 'Hello World';
* const lowerStr = THING.Utils.toLowerCase(str);
* console.log(lowerStr); // 'hello world'
*
* // 处理空值
* THING.Utils.toLowerCase(null); // ''
* THING.Utils.toLowerCase(undefined); // ''
*/
static toLowerCase(s) {
return s ? s.toLowerCase() : '';
}
/**
* 将字符串转换为大写。
* @param {string} s 字符串。
* @returns {string} 大写字符串。
* @example
* // 将字符串转换为大写
* const str = 'Hello World';
* const upperStr = THING.Utils.toUpperCase(str);
* console.log(upperStr); // 'HELLO WORLD'
*
* // 处理空值
* THING.Utils.toUpperCase(null); // ''
* THING.Utils.toUpperCase(undefined); // ''
*/
static toUpperCase(s) {
return s ? s.toUpperCase() : '';
}
/**
* Check if a string is a base64 URL.
* @param {string} s The string to check.
* @returns {boolean} True if the string is a base64 URL.
* @example
* // 检查字符串是否为base64编码的URL
* const base64Url = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA';
* const regularUrl = 'https://example.com/image.png';
*
* THING.Utils.isBase64Url(base64Url); // true
* THING.Utils.isBase64Url(regularUrl); // false
*/
static isBase64Url(s) {
if (s._startsWith("data:")) {
if (s.indexOf("base64,") !== -1) {
return true;
}
}
return false;
}
/**
* Check if a URL is a video file URL.
* @param {string} url The URL to check.
* @returns {boolean} True if the URL is a video file URL.
* @example
* // 检查URL是否为视频文件
* const videoUrl = 'https://example.com/video.mp4';
* const imageUrl = 'https://example.com/image.png';
*
* THING.Utils.isVideoUrl(videoUrl); // true
* THING.Utils.isVideoUrl(imageUrl); // false
*/
static isVideoUrl(url) {
const videoExtensions = /\.(mp4|m4v|webm|flv|f4v|mov|avi|wmv|mpeg|mpg|mkv|ts)$/i;
return videoExtensions.test(url);
}
/**
* Check if a URL is a WebAssembly file URL.
* @param {string} url The URL to check.
* @returns {boolean} True if the URL is a WebAssembly file URL.
* @example
* // 检查URL是否为WebAssembly文件
* const wasmUrl = 'https://example.com/module.wasm';
* const jsUrl = 'https://example.com/script.js';
*
* THING.Utils.isWASMUrl(wasmUrl); // true
* THING.Utils.isWASMUrl(jsUrl); // false
*/
static isWASMUrl(url) {
const videoExtensions = /\.(wasm)$/i;
return videoExtensions.test(url);
}
/**
* Check if a URL is a Blob URL.
* @param {string} s The URL to check.
* @returns {boolean} True if the URL is a Blob URL.
* @example
* // 检查URL是否为Blob URL
* const blobUrl = 'blob:https://example.com/1234-5678-9abc-def0';
* const regularUrl = 'https://example.com/image.png';
*
* THING.Utils.isBlobUrl(blobUrl); // true
* THING.Utils.isBlobUrl(regularUrl); // false
*/
static isBlobUrl(s) {
if (s._startsWith("blob:")) {
return true;
}
return false;
}
/**
* 截断长字符串并添加省略号。
* @param {string} str 字符串。
* @param {number} len 字符串限制长度。
* @returns {string} 截断长字符串并添加省略号。
* @example
* // 截断长字符串并添加省略号
* const longText = 'This is a very long text that needs to be truncated';
* const shortText = THING.Utils.beautySub(longText, 10);
* console.log(shortText); // 'This is a ...'
*
* // 处理包含中文的字符串(中文字符占用两个长度单位)
* const mixedText = '这是一段中英文混合的text';
* const truncated = THING.Utils.beautySub(mixedText, 10);
* console.log(truncated); // '这是一段中...'
*/
static beautySub(str, len) {
var reg = /[\u4e00-\u9fa5]/g, // Match chinese
slice = str.substring(0, len),
chineseCharNum = (~~(slice.match(reg) && slice.match(reg).length)),
realen = slice.length * 2 - chineseCharNum;
return str.substr(0, realen) + (realen < str.length ? "..." : "");
}
/**
* 将对象的键名转换为小写。
* @param {object} input 对象。
* @param {boolean | number} [deep] 递归或深度级别。
* @param {Function} [filter] 键过滤回调函数。
* @returns {object} 转换后的对象。
* @example
* // 将对象的键名转换为小写
* var obj = { 'Name': 'GO', 'Info': { 'Year': 'Hello 2019','Verson':'xxxx' } };
*
* // 只转换第一层键名
* console.log(THING.Utils.objectKeysToLowerCase(obj));
* // {"name":"GO","info":{"Year":"Hello 2019","Verson":"xxxx"}}
*
* // 递归转换所有层级的键名
* console.log(THING.Utils.objectKeysToLowerCase(obj, true));
* // {"name":"GO","info":{"year":"Hello 2019","verson":"xxxx"}}
*
* // 使用过滤函数,排除特定键名
* console.log(THING.Utils.objectKeysToLowerCase(obj, true, function (key) {
* if (key == 'Year') { return false; } else { return true; }
* }));
* // {"name":"GO","info":{"Year":"Hello 2019","verson":"xxxx"}}
*/
static objectKeysToLowerCase(input, deep, filter) {
return _objectKeysToLowerCase(input, deep, filter);
}
/**
* Convert font size in points (pt) to pixel size (px).
* @param {number} pt Font size in points.
* @returns {number} Font size in pixels.
* @example
* // 将字体大小从磅值(pt)转换为像素值(px)
* const fontSizeInPt = 12; // 12pt
* const fontSizeInPx = THING.Utils.fontSizeToPixelSize(fontSizeInPt);
* console.log(fontSizeInPx); // 16px (12pt = 16px)
*/
static fontSizeToPixelSize(pt) {
return (pt / 72) * 96;
}
/**
* Convert pixel size (px) to font size in points (pt).
* @param {number} px Font size in pixels.
* @returns {number} Font size in points.
* @example
* // 将字体大小从像素值(px)转换为磅值(pt)
* const fontSizeInPx = 16; // 16px
* const fontSizeInPt = THING.Utils.pixelSizeToFontSize(fontSizeInPx);
* console.log(fontSizeInPt); // 12pt (16px = 12pt)
*/
static pixelSizeToFontSize(px) {
return (px / 96) * 72;
}
/**
* 检查对象是否为类。
* @param {object} obj 对象。
* @returns {boolean} 是否为类。
* @example
* // 检查对象是否为类
* const obj = { a: 1, b: 2 };
* const isClass = THING.Utils.isClass(obj);
* console.log(isClass); // false
*/
static isClass(obj) {
if (typeof obj != "function") return false;
var str = obj.toString();
// async function or arrow function
if (obj.prototype === undefined) return false;
// generator function or malformed definition
if (obj.prototype.constructor !== obj) return false;
// ES6 class
if (str.slice(0, 5) == "class") return true;
// has own prototype properties
if (Object.getOwnPropertyNames(obj.prototype).length >= 2) return true;
// anonymous function
if (/^function\s+\(|^function\s+anonymous\(/.test(str)) return false;
return false;
}
/**
* Check if a path is an image file path.
* @param {string} path The file path to check.
* @returns {boolean} True if the path is an image file path.
* @example
* // 检查文件路径是否为图片
* const isImage = THING.Utils.isImagePath('texture.png'); // true
* const isNotImage = THING.Utils.isImagePath('document.pdf'); // false
*/
static isImagePath(path) {
return (/\.(tga|png|gif|jpe?g|tiff?|webp|bmp)$/i).test(path);
}
/**
* Check if an object is empty.
* @param {object} obj The object to check.
* @returns {boolean} True if the object is empty.
* @example
* // 检查对象是否为空
* const emptyObj = {};
* const nonEmptyObj = { key: 'value' };
* THING.Utils.isEmptyObject(emptyObj); // true
* THING.Utils.isEmptyObject(nonEmptyObj); // false
*/
static isEmptyObject(obj) {
return !Object.keys(obj).length;
}
/**
* Compare two objects for equality.
* @param {object} o1 The first object.
* @param {object} o2 The second object.
* @returns {boolean} True if the objects are equal.
* @example
* // 比较两个对象是否相等
* const obj1 = { a: 1, b: 2 };
* const obj2 = { a: 1, b: 2 };
* const obj3 = { a: 1, b: 3 };
* THING.Utils.equalsObject(obj1, obj2); // true
* THING.Utils.equalsObject(obj1, obj3); // false
*/
static equalsObject(o1, o2) {
for (var key in o1) {
if (o1[key] != o2[key]) {
return false;
}
}
return true;
}
/**
* Clear all properties from an object.
* @param {object} obj The object to clear.
* @example
* // 清除对象中的所有属性
* const obj = { a: 1, b: 2, c: 3 };
* THING.Utils.clearObject(obj);
* console.log(obj); // {}
*/
static clearObject(obj) {
for (var key in obj) {
delete obj[key];
}
}
/**
* 克隆JS对象。
* @param {object} obj 要克隆的对象。
* @param {boolean} [shallow=true] 是否只使用引用。
* @param {Function} [filterFunc=null] 过滤属性的函数。
* @param {Function} [cloneFunc=null] 克隆属性的函数。
* @returns {object} 克隆后的对象。
* @example
* // 克隆一个对象
* const original = { name: 'object', values: [1, 2, 3], nested: { key: 'value' } };
*
* // 浅克隆 - 嵌套对象和数组是引用
* const shallowClone = THING.Utils.cloneObject(original);
* shallowClone.nested.key = 'changed';
* console.log(original.nested.key); // 'changed', 因为嵌套对象是引用
*
* // 深克隆 - 嵌套对象和数组也被克隆
* const deepClone = THING.Utils.cloneObject(original, false);
* deepClone.nested.key = 'new value';
* console.log(original.nested.key); // 'changed', 原始对象不受影响
*/
static cloneObject(obj, shallow = true, filterFunc = null, cloneFunc = null) {
// Thanks for sharing code: http://jsfiddle.net/cattail/NYNT5/
_references.length = 0;
var handler = _handlers[classof(obj)];
return handler ? handler(obj, shallow, filterFunc, cloneFunc) : obj;
}
/**
* Copy JS object in referenced mode.
* @param {object} target The target object.
* @param {object} source The source object.
* @returns {object} The referenced of target object.
* @example
* // 将源对象的属性复制到目标对象
* const target = { a: 1 };
* const source = { b: 2, c: 3 };
*
* THING.Utils.copyObject(target, source);
* console.log(target); // { a: 1, b: 2, c: 3 }
*
* // 如果源对象和目标对象有相同的属性,源对象的值会覆盖目标对象的值
* const target2 = { x: 10, y: 20 };
* const source2 = { y: 30, z: 40 };
*
* THING.Utils.copyObject(target2, source2);
* console.log(target2); // { x: 10, y: 30, z: 40 }
*/
static copyObject(target, source) {
if (target === source) {
return target;
}
if (source) {
Utils.clearObject(target);
for (var key in source) {
var value = source[key];
if (value) {
if (Utils.isObject(value)) {
target[key] = {};
Utils.copyObject(target[key], value);
}
else {
target[key] = value;
}
}
else {
target[key] = value;
}
}
}
return target;
}
/**
* 复制Uint8Array。
* @param {Uint8Array} source 源Uint8Array。
* @returns {Uint8Array} 复制后的Uint8Array。
* @example
* // 复制Uint8Array
* const source = new Uint8Array([1, 2, 3]);
* const copied = THING.Utils.copyUint8Array(source);
* console.log(copied); // Uint8Array [1, 2, 3]
*/
static copyUint8Array(source) {
var dst = new ArrayBuffer(source.byteLength);
return new Uint8Array(dst).set(new Uint8Array(source));
}
/**
* Merge object.
* @param {object} target The target object.
* @param {object} source The source object.
* @param {boolean} [overwrite=false] True indicates overwrite values.
* @returns {object} The merged object.
* @example
* // 合并两个对象
* var obj1 = { 'name': 'JS', 'year': '2018' };
* var obj2 = { 'version': 'xxx', 'year': '2019' };
*
* // 不覆盖已有属性
* var merged1 = THING.Utils.mergeObject(obj1, obj2);
* console.log(merged1); // {"name":"JS","year":"2018","version":"xxx"}
*
* // 覆盖已有属性
* var merged2 = THING.Utils.mergeObject(obj1, obj2, true);
* console.log(merged2); // {"name":"JS","year":"2019","version":"xxx"}
*/
static mergeObject(target, source, overwrite) {
if (!source) {
return Utils.cloneObject(target);
}
if (!Utils.isObject(source) || !Utils.isObject(target)) {
return overwrite ? Utils.cloneObject(source) : target;
}
for (var key in source) {
var targetProp = target[key];
var sourceProp = source[key];
// Continue to merge sub objects
if (Utils.isObject(sourceProp) && Utils.isObject(targetProp)) {
Utils.mergeObject(targetProp, sourceProp, overwrite);
}
// Overwrite attribute of target when it's not exist
else if (overwrite || !(key in target)) {
target[key] = Utils.cloneObject(source[key], true);
}
}
return target;
}
/**
* 将源对象的值赋值给目标对象。
* @param {object} target 目标对象。
* @param {object} source 源对象。
* @example
* // 将源对象的值赋值给目标对象
* const target = { a: 1 };
* const source = { b: 2, c: 3 };
*
* THING.Utils.assignValues(target, source);
* console.log(target); // { a: 1, b: 2, c: 3 }
*/
static assignValues(target, source) {
if (Utils.isArray(source)) {
source.forEach(src => {
Utils.assignValues(target, src);
});
}
else {
for (var key in source) {
if (target[key] !== undefined) {
continue;
}
target[key] = source[key];
}
}
}
/**
* 将颜色数组转换为十六进制数字。
* @param {Array<number>} value 颜色数组 [r, g, b],范围为 [0, 1]。
* @returns {number} 十六进制数字。
* @example
* let color = [1, 0, 0]; // 红色
* let hex = THING.Utils.colorToHex(color);
* // @expect(hex === 0xFF0000);
* @public
*/
static colorToHex(value) {
return (value[0] * 255) << 16 ^ (value[1] * 255) << 8 ^ (value[2] * 255) << 0;
}
/**
* 将颜色数组转换为十六进制字符串。
* @param {Array<number>} value 颜色数组 [r, g, b],范围为 [0, 1]。
* @returns {string} 十六进制字符串,如 '#FF0000'。
* @example
* let color = [1, 0, 0]; // 红色
* let hexString = THING.Utils.colorToHexString(color);
* // @expect(hexString === '#ff0000');
* @public
*/
static colorToHexString(value) {
if (!value) {
return null;
}
return '#' + ('000000' + Utils.colorToHex(value).toString(16)).slice(-6);
}
/**
* 检查两个颜色在 epsilon 范围内是否相等。
* @param {Array<number>} c1 第一个颜色数组 [r, g, b],范围为 [0, 1]。
* @param {Array<number>} c2 第二个颜色数组 [r, g, b],范围为 [0, 1]。
* @param {number} [epsilon] epsilon 值。
* @returns {boolean} 如果颜色相等则返回 true。
* @example
* let color1 = [1, 0, 0];
* let color2 = [1, 0, 0];
* let equal = THING.Utils.equalsColor(color1, color2);
* // @expect(equal === true);
* @public
*/
static equalsColor(c1, c2, epsilon = 0.0001) {
if (!Utils.isArray(c1) || !Utils.isArray(c2)) {
return false;
}
if (Math.abs(c1[0] - c2[0]) > epsilon) {
return false;
}
if (Math.abs(c1[1] - c2[1]) > epsilon) {
return false;
}
if (Math.abs(c1[2] - c2[2]) > epsilon) {
return false;
}
return true;
}
/**
* 检查值是否为有效的颜色格式。
* @param {*} value 要检查的值。
* @returns {boolean} 如果值是有效的颜色格式则返回 true。
* @example
* // 数组格式
* THING.Utils.isColorString([1, 0, 0]); // true
* // 十六进制数字
* THING.Utils.isColorString(0xFF0000); // true
* // 十六进制字符串
* THING.Utils.isColorString('#FF0000'); // true
* // RGB 字符串
* THING.Utils.isColorString('rgb(255,0,0)'); // true
* // RGBA 字符串
* THING.Utils.isColorString('rgba(255,0,0,1)'); // true
* // 颜色关键字
* THING.Utils.isColorString('red'); // true
* @public
*/
static isColorString(value) {
// [0~1, 0~1, 0~1]
if (Utils.isArray(value)) {
return true;
}
// 0x000000~0xFFFFFF
if (Utils.isNumber(value)) {
return true;
}
// 字符串格式
if (Utils.isString(value)) {
// '#FFFFFF'
if (value[0] == '#') {
return true;
}
// rgb(r, g, b)
else if (value.startsWith('rgb(')) {
return true;
}
// rgba(r, g, b, a)
else if (value.startsWith('rgba(')) {
return true;
}
}
if (_colorKeywords[value.toLowerCase()] !== undefined) {
return true;
}
return false;
}
/**
* Convert HSL color values to RGB color values
* @param {number} h Hue value (0-360)
* @param {number} s Saturation value (0-100)
* @param {number} l Lightness value (0-100)
* @returns {Array} RGB color values array [r, g, b] in range [0-1]
* @example
* let rgb = THING.Utils._hslToRgb(120, 100, 50);
* console.log(rgb); // [0, 1, 0]
* @private
*/
static _hslToRgb(h, s, l) {
s /= 100;
l /= 100;
const k = n => (n + h / 30) % 12;
const a = s * Math.min(l, 1 - l);
const f = n => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
return [f(0), f(8), f(4)];
}
/**
* 解析颜色值并返回 RGBA 数组。
* @param {Array | number | string} value 颜色值,可以是数组、十六进制数字或字符串格式。
* @param {Array} defaultValue 默认返回值,当解析失败时返回。
* @param {Array} [target] 目标数组,用于存储解析后的颜色值。
* @returns {Array} 返回 RGBA 数组,范围为 [0~1],如果解析失败则返回 defaultValue。
* @public
* @example
* // 解析颜色值
* let color = THING.Utils.parseColor('#FF0000');
* // @expect(color === [1, 0, 0]);
* // 解析颜色值
* let color = THING.Utils.parseColor('rgb(255,0,0)');
* // @expect(color === [1, 0, 0]);
* // 解析颜色值
* let color = THING.Utils.parseColor('hsl(120, 100%, 50%)');
* // @expect(color === [0, 1, 0]);
*/
static parseColor(value, defaultValue, target) {
target = target || [];
// [0~1, 0~1, 0~1, 0~1(optional)]
if (Utils.isArray(value)) {
target[0] = value[0];
target[1] = value[1];
target[2] = value[2];
if (value[3] !== undefined) {
target[3] = value[3];
}
return target;
}
// 0x000000~0xFFFFFF
else if (Utils.isNumber(value)) {
return _parseColorFromHex(value, target);
}
// String: '#FFFFFF'/'rgb(r, g, b)'/'hsl(h, s%, l%)'
else if (Utils.isString(value)) {
// '#FFFFFF'
if (value[0] == '#') {
return _parseColorFromHex(parseInt(value.substr(1), 16), target);
}
// rgb(r, g, b)
else if (value.startsWith('rgb(')) {
var colors = value.substring(4).split(',');
if (colors.length === 3) {
let r = parseInt(colors[0]);
let g = parseInt(colors[1]);
let b = parseInt(colors[2]._trimRight(' )'));
target[0] = r / 255;
target[1] = g / 255;
target[2] = b / 255;
return target;
}
}
// rgba(r, g, b, a)
else if (value.startsWith('rgba(')) {
var colors = value.substring(5).split(',');
if (colors.length === 4) {
var r = parseInt(colors[0]);
var g = parseInt(colors[1]);
var b = parseInt(colors[2]);
// Alpha range is [0, 1]
var a = parseFloat(colors[3]._trimRight(' )'));
target[0] = r / 255;
target[1] = g / 255;
target[2] = b / 255;
target[3] = a;
return target;
}
}
// hsl(h, s%, l%)
else if (value.startsWith('hsl(')) {
var colors = value.substring(4).split(',');
if (colors.length === 3) {
let h = parseFloat(colors[0]);
let s = parseFloat(colors[1]);
let l = parseFloat(colors[2]);
// Remove '%' and ')' from s and l values
s = parseFloat(s.toString().replace('%', ''));
l = parseFloat(l.toString().replace('%', '').replace(')', ''));
const [r, g, b] = Utils._hslToRgb(h, s, l);
target[0] = r;
target[1] = g;
target[2] = b;
return target;
}
}
// Other string, try to convert it
else {
if (isNaN(value)) {
var hexValue = _colorKeywords[value.toLowerCase()];
if (hexValue !== undefined) {
return _parseColorFromHex(hexValue, target);
}
}
else {
return _parseColorFromHex(parseInt(value), target);
}
}
}
return defaultValue;
}
/**
* 从多种类型中解析值。
* @param {object} types 类型映射对象。
* @param {any} value 要解析的值。
* @param {any} defaultValue 默认返回值,当解析失败时返回。
* @returns {any} 解析后的值。
* @example
* // 解析值
* const types = { 'red': 1, 'blue': 2, 'green': 3 };
* const value = 'blue';
* const result = THING.Utils.parseValueFromTypes(types, value);
* console.log(result); // 2
*/
static parseValueFromTypes(types, value, defaultValue) {
var keys = Object.keys(types);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (types[key] == value) {
return key;
}
}
if (defaultValue !== undefined) {
return defaultValue;
}
return value;
}
/**
* 将颜色值转换为十六进制字符串。
* @param {Array | number | string} value 颜色值,可以是数组、十六进制数字或字符串格式。
* @param {Array} defaultValue 默认返回值,当解析失败时返回。
* @returns {string} 十六进制字符串。
* @example
* // 将颜色值转换为十六进制字符串
* const value = [1, 0, 0];
* const result = THING.Utils.toColorHexString(value);
* console.log(result); // '#FF0000'
*/
static toColorHexString(value, defaultValue) {
var color = Utils.parseColor(value, defaultValue);
var r = Number(Math.round(color[0] * 255)).toString(16).padStart(2, '0');
var g = Number(Math.round(color[1] * 255)).toString(16).padStart(2, '0');
var b = Number(Math.round(color[2] * 255)).toString(16).padStart(2, '0');
return '#' + r + g + b;
}
/**
* 检查对象是否具有指定属性。
* @param {object} object 要检查的对象。
* @param {string} key 要检查的属性名。
* @param {string} [separator='/'] 属性分隔符。
* @returns {boolean} 如果对象具有指定属性,则返回 true,否则返回 false。
* @example
* // 检查对象是否具有指定属性
* const obj = { a: { b: { c: 'value' } } };
* const result = THING.Utils.hasAttribute(obj, 'a/b/c');
* console.log(result); // true
*/
static hasAttribute(object, key, separator = '/') {
return Utils.getAttribute(object, key, separator) !== undefined;
}
/**
* 获取对象的指定属性。
* @param {object} object 要获取属性的对象。
* @param {string} key 要获取的属性名。
* @param {string} [separator='/'] 属性分隔符。
* @returns {any} 获取的属性值。
* @example
* // 获取对象的指定属性
* const obj = { a: { b: { c: 'value' } } };
* const result = THING.Utils.getAttribute(obj, 'a/b/c');
*/
static getAttribute(object, key, separator = '/') {
if (!object || !key) {
return undefined;
}
let keys = [];
if (key === _lastAttributeKeyCache.key) {
keys = _lastAttributeKeyCache.keys;
}
else {
keys = key.split(separator);
_lastAttributeKeyCache.key = key;
_lastAttributeKeyCache.keys = keys;
}
// Try to find attribute from object
if (keys.length) {
for (let i = 0, l = keys.length; i < l; i++) {
let value = object[keys[i]];
if (value === undefined) {
return undefined;
}
object = value;
}
}
return object;
}
/**
* 设置对象的指定属性。
* @param {object} object 要设置属性的对象。
* @param {string} key 要设置的属性名。
* @param {any} value 要设置的属性值。
* @param {string} [separator='/'] 属性分隔符。
* @returns {object} 设置后的对象。
* @example
* // 设置对象的指定属性
* const obj = { a: { b: { c: 'value' } } };
* const result = THING.Utils.setAttribute(obj, 'a/b/c', 'newValue');
* console.log(result); // { a: { b: { c: 'newValue' } } }
*/
static setAttribute(object, key, value, separator = '/') {
if (!object || !key) {
return null;
}
var keys = key.split(separator);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (i == keys.length - 1) {
object[key] = value;
break;
}
if (Utils.isNull(object[key])) {
object[key] = {};
}
object = object[key];
}
return value;
}
/**
* 设置对象的多个属性。
* @param {object} object 要设置属性的对象。
* @param {object} attributes 要设置的属性对象。
* @param {boolean} [overwrite=true] 是否覆盖已存在的属性。
* @returns {object} 设置前的属性对象。
* @example
* // 设置对象的多个属性
* const obj = { a: 1, b: 2 };
* const attributes = { b: 3, c: 4 };
* const result = THING.Utils.setAttributes(obj, attributes);
* console.log(result); // { a: 1, b: 3, c: 4 }
*/
static setAttributes(object, attributes, overwrite = true) {
var prevAttributes = {};
for (var key in attributes) {
var objectValue = object[key];
if (objectValue !== undefined) {
if (!overwrite) {
continue;
}
prevAttributes[key] = objectValue;
}
object[key] = attributes[key];
}
return prevAttributes;
}
/**
* 设置对象的指定属性,如果属性不存在则不设置。
* @param {object} target 目标对象。
* @param {string} targetKey 目标属性名。
* @param {object} source 源对象。
* @param {Function} [compareFunc] 比较函数。
* @param {string} [sourceKey] 源属性名。
* @returns {void}
* @example
* // 设置对象的指定属性,如果属性不存在则不设置。
* const target = { a: 1 };
* const source = { b: 2 };
* THING.Utils.setAttributeIfExist(target, 'a', source);
* console.log(target); // { a: 1 }
*/
static setAttributeIfExist(target, targetKey, source, compareFunc = (v) => { return v !== undefined; }, sourceKey = targetKey) {
var sourceValue = source[sourceKey];
if (sourceValue === undefined) {
return;
}
if (!compareFunc(sourceValue)) {
return;
}
target[targetKey] = sourceValue;
}
/**
* 交换对象的指定属性。
* @param {object} object 要交换属性的对象。
* @param {string} key1 要交换的第一个属性名。
* @param {string} key2 要交换的第二个属性名。
* @returns {object} 交换后的对象。
* @example
* // 交换对象的指定属性
* const obj = { a: 1, b: 2 };
* const result = THING.Utils.swapObjectAttribute(obj, 'a', 'b');
* console.log(result); // { a: 2, b: 1 }
*/
static swapObjectAttribute(object, key1, key2) {
var v1 = object[key1];
object[key1] = object[key2];
object[key2] = v1;
return object;
}
/**
* 二分查找。
* @param {Array} array 要查找的数组。
* @param {any} target 要查找的值。
* @returns {number} 查找的索引。
* @example
* // 二分查找
* const array = [1, 2, 3, 4, 5];
* const result = THING.Utils.binarySearch(array, 3);
* console.log(result); // 2
*/
static binarySearch(array, target) {
var l = 0, r = array.length - 1;
if (Utils.isFunction(target)) {
while (l <= r) {
var mid = Math.floor((l + r) / 2);
if (target(array[mid]) === 0) {
return mid;
}
else if (target(array[mid]) > 0) {
r = mid - 1;
}
else {
l = mid + 1;
}
}
}
else {
while (l <= r) {
var mid = Math.floor((l + r) / 2);
if (target === array[mid]) {
return mid;
}
else if (target < array[mid]) {
r = mid - 1;
}
else {
l = mid + 1;
}
}
}
return -1;
}
/**
* 二分查找边界。
* @param {Array} array 要查找的数组。
* @param {any} target 要查找的值。
* @param {Function} compareFunc 比较函数。
* @returns {number} 查找的索引。
* @example
* // 二分查找边界
* const array = [1, 2, 3, 4, 5];
* const result = THING.Utils.binaryBound(array, 3);
* console.log(result); // 2
*/
static binaryBound(array, target, compareFunc) {
if (!array.length) {
return 0;
}
var i = 0, j = array.length - 1;
if (compareFunc) {
while (i < j - 1) {
var m = Math.floor((i + j) / 2);
var ret = compareFunc(array[m], target);
if (ret == 0) { return m; }
if (ret < 0) { i = m; }
else { j = m; }
}
if (compareFunc(array[i], target) > 0) { return i; }
else if (compareFunc(array[j], target) > 0) { return j; }
}
else {
while (i < j - 1) {
var m = Math.floor((i + j) / 2);
if (array[m] === target) {
return m;
}
if (array[m] < target) {
i = m;
}
else {
j = m;
}
}
if (array[i] > target) { return i; }
else if (array[j] > target) { return j; }
}
return -1;
}
/**
* 二分查找边界插入。
* @param {Array} array 要插入的数组。
* @param {any} target 要插入的值。
* @param {Function} compareFunc 比较函数。
* @returns {Array} 插入后的数组。
* @example
* // 二分查找边界插入
* const array = [1, 2, 3, 4, 5];
* const result = THING.Utils.insertBinaryBound(array, 3);
* console.log(result); // [1, 2, 3, 3, 4, 5]
*/
static insertBinaryBound(array, target, compareFunc) {
var index = Utils.binaryBound(array, target, compareFunc);
if (index === -1) {
array.push(target);
}
else {
array.splice(index, 0, target);
}
return array;
}
// Thanks for sharing code: https://blog.csdn.net/hupian1989/article/details/80920324
/**
* 节流。
* @param {Function} fn 要节流的函数。
* @param {number} wait 节流时间。
* @returns {Function} 节流后的函数。
* @example
* // 节流
* const throttledFn = THING.Utils.throttling(function() {
* console.log('节流执行');
* }, 1000);
* throttledFn(); // 每1秒执行一次
*/
static throttling(fn, wait) {
let timer;
let context, args;
let run = () => {
let waitTime = Utils.isFunction(wait) ? wait() : wait;
timer = setTimeout(() => {
fn.apply(context, args);
clearTimeout(timer);
timer = null;
}, waitTime);
}
return function () {
let waitTime = Utils.isFunction(wait) ? wait() : wait;
if (!waitTime) {
fn.apply(context, args);
return;
}
context = this;
args = arguments;
if (!timer) {
run();
}
}
}
/**
* 获取字符串的MD5码。
* @param {string} string 要获取MD5码的字符串。
* @returns {string} MD5码。
* @example
* // 获取字符串的MD5码
* const md5Code = THING.Utils.getMD5CodeFromString('hello');
* console.log(md5Code); // '5d41402abc4b2a76b9719d911017c592'
*/
static getMD5CodeFromString(string) {
return SparkMD5.hash(string);
}
/**
* 获取缓冲区的MD5码。
* @param {ArrayBuffer} buffer 要获取MD5码的缓冲区。
* @param {Function} progress 进度回调函数。
* @returns {Promise} 获取MD5码的Promise。
* @example
* // 获取缓冲区的MD5码
* const buffer = new ArrayBuffer(1024);
* const md5Code = await THING.Utils.getMD5CodeFromBuffer(buffer);
* console.log(md5Code); // '5d41402abc4b2a76b9719d911017c592'
*/
static getMD5CodeFromBuffer(buffer, progress) {
return new Promise((resolve, reject) => {
const spark = new SparkMD5.ArrayBuffer();
_readChunked(buffer,
// Reading
(chunk, offs, total) => {
spark.append(chunk);
if (progress) {
progress(offs / total);
}
},
// Error
err => {
if (err) {
reject(err);
}
else {
// TODO: Handle errors
var hash = spark.end();
resolve(hash);
}
}
);
});
}
/**
* 应用新的构造函数。
* @param {Function} ctor 要应用的构造函数。
* @param {Array} args 要传递的参数。
* @returns {object} 应用后的对象。
* @example
* // 应用新的构造函数
* const newObj = THING.Utils.applyNew(Object, ['foo', 'bar']);
* console.log(newObj); // { foo: 'foo', bar: 'bar' }
*/
static applyNew(ctor, args) {
if (!ctor) {
return null;
}
var applyArgs = ([{}]).concat(args || []);
var f = Function.prototype.bind.apply(ctor, applyArgs);
return new f();
}
/**
* 将数字转换为字符串。
* @param {number} i 要转换的数字。
* @returns {string} 转换后的字符串。
* @example
* // 将数字转换为字符串
* const str = THING.Utils.itoa(123);
* console.log(str); // '123'
*/
static itoa(i) {
return String.fromCharCode(i);
}
/**
* 将字符串转换为数字。
* @param {string} a 要转换的字符串。
* @returns {number} 转换后的数字。
* @example
* // 将字符串转换为数字
* const num = THING.Utils.atoi('123');
* console.log(num); // 123
*/
static atoi(a) {
return a.charCodeAt();
}
/**
* 将Base64字符串转换为Blob。
* @param {string} base64Str 要转换的Base64字符串。
* @returns {Blob} 转换后的Blob。
* @example
* // 将Base64字符串转换为Blob
* const blob = THING.Utils.base64ToBlob('base64Str');
* console.log(blob); // Blob { size: 1024, type: 'image/png' }
*/
static base64ToBlob(base64Str) {
var bString = atob(base64Str);
var len = bString.length;
var arr = new Uint8Array(len);
while (len--) {
arr[len] = bString.charCodeAt(len);
}
return arr;
}
/**
* 将Blob转换为Base64字符串。
* @param {Blob} bytes 要转换的Blob。
* @returns {string} 转换后的Base64字符串。
* @example
* // 将Blob转换为Base64字符串
* const base64Str = THING.Utils.blobToBase64(blob);
* console.log(base64Str); // 'base64Str'
*/
static blobToBase64(bytes) {
var bString = "";
for (var i = 0, len = bytes.length; i < len; ++i) {
bString += String.fromCharCode(bytes[i]);
}
return btoa(bString);
}
/**
* 将缓冲区转换为字符串。
* @param {ArrayBuffer} buffer 要转换的缓冲区。
* @returns {string} 转换后的字符串。
* @example
* // 将缓冲区转换为字符串
* const str = THING.Utils.arrayBufferToString(buffer);
* console.log(str); // 'base64Str'
*/
static arrayBufferToString(buffer) {
var string = String.fromCharCode.apply(null, new Uint8Array(buffer));
return decodeURIComponent(escape(string));
}
/**
* 将Uint8Array转换为字符串。
* @param {Uint8Array} buffer 要转换的Uint8Array。
* @returns {string} 转换后的字符串。
* @example
* // 将Uint8Array转换为字符串
* const str = THING.Utils.uint8ArrayToString(new Uint8Array([1, 2, 3]));
* console.log(str); // '123'
*/
static uint8ArrayToString(buffer) {
const CHUNK_SIZE = 128 * 1024; // Chunk size for processing
const decoder = new TextDecoder();
let offset = 0;
let result = '';
while (offset < buffer.length) {
const chunk = buffer.subarray(offset, offset + CHUNK_SIZE);
const str = decoder.decode(chunk, { stream: true });
result += str;
offset += CHUNK_SIZE;
}
// Decode any remaining bytes
result += decoder.decode();
return result;
}
/**
* 将字符串转换为缓冲区。
* @param {string} string 要转换的字符串。
* @returns {ArrayBuffer} 转换后的缓冲区。
* @example
* // 将字符串转换为缓冲区
* const buffer = THING.Utils.stringToArrayBuffer('123');
* console.log(buffer); // ArrayBuffer { byteLength: 3 }
*/
static stringToArrayBuffer(string) {
var buf = new ArrayBuffer(string.length);
var bufView = new Uint8Array(buf);
for (var i = 0, strLen = string.length; i < strLen; i++) {
bufView[i] = string.charCodeAt(i);
}
return buf;
}
/**
* 将缓冲区转换为UTF-16字符串。
* @param {ArrayBuffer} buffer 要转换的缓冲区。
* @returns {string} 转换后的UTF-16字符串。
* @example
* // 将缓冲区转换为UTF-16字符串
* const str = THING.Utils.arrayBufferToUtf16(new ArrayBuffer(3));
* console.log(str); // '123'
*/
static arrayBufferToUtf16(buffer) {
return String.fromCharCode.apply(null, new Uint16Array(buffer));
}
/**
* 将UTF-16字符串转换为缓冲区。
* @param {string} string 要转换的UTF-16字符串。
* @returns {ArrayBuffer} 转换后的缓冲区。
* @example
* // 将UTF-16字符串转换为缓冲区
* const buffer = THING.Utils.utf16ToArrayBuffer('123');
* console.log(buffer); // ArrayBuffer { byteLength: 6 }
*/
static utf16ToArrayBuffer(string) {
var buf = new ArrayBuffer(string.length * 2);
var bufView = new Uint16Array(buf);
for (var i = 0, strLen = string.length; i < strLen; i++) {
bufView[i] = string.charCodeAt(i);
}
return buf;
}
/**
* 将缓冲区转换为Blob。
* @param {ArrayBuffer} buffer 要转换的缓冲区。
* @param {object} options 选项。
* @returns {Blob} 转换后的Blob。
* @example
* // 将缓冲区转换为Blob
* const blob = THING.Utils.arrayBufferToBlob(new ArrayBuffer(3));
* console.log(blob); // Blob { size: 3, type: 'application/octet-stream' }
*/
static arrayBufferToBlob(buffer, options) {
return new Blob([new Uint8Array(buffer, 0, buffer.byteLength)], options);
}
/**
* 将Blob转换为缓冲区。
* @param {Blob} blob 要转换的Blob。
* @returns {Promise<ArrayBuffer>} 转换后的缓冲区。
* @example
* // 将Blob转换为缓冲区
* const buffer = await THING.Utils.blobToArrayBuffer(blob);
* console.log(buffer); // ArrayBuffer { byteLength: 3 }
*/
static blobToArrayBuffer(blob) {
return new Response(blob).arrayBuffer();
}
/**
* 将缓冲区转换为Blob。
* @param {ArrayBuffer} buffer 要转换的缓冲区。
* @param {object} options 选项。
* @returns {Blob} 转换后的Blob。
* @example
* // 将缓冲区转换为Blob
* const blob = THING.Utils.bufferToBlob(new ArrayBuffer(3));
* console.log(blob); // Blob { size: 3, type: 'application/octet-stream' }
*/
static bufferToBlob(buffer, options) {
return new Blob([buffer], options);
}
/**
* 将Blob转换为缓冲区。
* @param {Blob} blob 要转换的Blob。
* @returns {Promise<Uint8Array>} 转换后的缓冲区。
* @example
* // 将Blob转换为缓冲区
* const buffer = await THING.Utils.blobToBuffer(blob);
* console.log(buffer); // Uint8Array { length: 3 }
*/
static blobToBuffer(blob) {
return new Promise((resolve, reject) => {
new Response(blob).arrayBuffer().then((buffer) => {
resolve(new Uint8Array(buffer));
});
})
}
/**
* 将字符串转换为缓冲区。
* @param {string} string 要转换的字符串。
* @returns {Uint8Array} 转换后的缓冲区。
* @example
* // 将字符串转换为缓冲区
* const buffer = THING.Utils.stringToBuffer('123');
* console.log(buffer); // Uint8Array { length: 3 }
*/
static stringToBuffer(string) {
var result;
if ('undefined' === typeof Uint8Array) {
result = utf8ToBytes(string);
}
else {
result = new Uint8Array(utf8ToBytes(string));
}
return result;
}
/**
* 将缓冲区转换为字符串。
* @param {Uint8Array} buffer 要转换的缓冲区。
* @returns {string} 转换后的字符串。
* @example
* // 将缓冲区转换为字符串
* const str = THING.Utils.bufferToString(new Uint8Array([1, 2, 3]));
* console.log(str); // '123'
*/
static bufferToString(buffer) {
return Utils.uint8ArrayToString(buffer);
}
/**
* 同步选项。
* @param {object} target 目标对象。
* @param {object} options 选项。
* @param {object} values 值。
* @returns {boolean} 是否同步成功。
* @example
* // 同步选项
* const target = {};
* const options = { a: 1, b: 2 };
* const values = { a: 3, c: 4 };
* const result = THING.Utils.syncOptions(target, options, values);
*/
static syncOptions(target, options, values = {}) {
if (!options) {
return false;
}
for (var key in values) {
var value = Utils.parseValue(options[key], values[key]);
if (Utils.isNull(value)) {
continue;
}
target[key] = value;
}
return true;
}
/**
* 断点。
* @example
* // 断点
* THING.Utils._break();
*/
static _break() {
_eval('debugger')
}
/**
* 执行脚本。
* @param {string} script 要执行的脚本。
* @example
* // 执行脚本
* THING.Utils.eval('console.log("Hello, world!");');
*/
static eval(script) {
_eval(script);
}
/**
* 格式化字符串。
* @param {string} string 要格式化的字符串。
* @param {string} format 格式。
* @returns {string} 格式化后的字符串。
* @example
* // 格式化字符串
* const result = THING.Utils.scanf('Hello, world!', '%s');
* console.log(result); // 'Hello, world!'
*/
static scanf(string, format) {
return sscanf(string, format);
}
/**
* 获取宏。
* @returns {Array<object>} 宏。
* @example
* // 获取宏
* const macros = THING.Utils.getMacros();
* console.log(macros); // [{ name: 'macro1', value: 'value1' }, { name: 'macro2', value: 'value2' }]
*/
static getMacros() {
return _macros;
}
/**
* 添加工厂。
* @param {object} factory 工厂。
* @param {string} name 唯一名称,可以稍后通过名称替换工厂。
* @param {number} priority 优先级,从高到低创建对象。
* @example
* // 创建一个简单的工厂对象
* const myFactory = {
* createObject: function(type, options) {
* if (type === 'button') {
* return new Button(options);
* }
* return null;
* }
* };
*
* // 添加工厂,设置名称和优先级
* THING.Utils.addFactory(myFactory, 'buttonFactory', 1);
*
* // 添加另一个低优先级的工厂
* const defaultFactory = {
* createObject: function(type, options) {
* return new DefaultObject(options);
* }
* };
* THING.Utils.addFactory(defaultFactory, 'defaultFactory', 0);
*/
static addFactory(factory, name = '', priority = 0) {
if (!factory) {
return;
}
_factories.push({ factory, name, priority, index: _factories.length });
_factories.sort((a, b) => {
if (a.priority != b.priority) {
return a.priority > b.priority;
}
return a.index > b.index;
});
}
/**
* Add factories.
* @param {Array<object>} factories The factories.
* @example
* // 创建多个工厂对象
* const factories = [
* {
* createObject: function(type, options) {
* if (type === 'button') {
* return new Button(options);
* }
* return null;
* }
* },
* {
* createObject: function(type, options) {
* if (type === 'input') {
* return new Input(options);
* }
* return null;
* }
* }
* ];
*
* // 批量添加工厂
* THING.Utils.addFactories(factories);
*/
static addFactories(factories) {
factories.forEach(factory => {
Utils.addFactory(factory);
});
}
/**
* Get the factories.
* @type {Array<object>}
* @example
* // 获取所有已注册的工厂
* const factories = THING.Utils.getFactories();
*
* // 遍历所有工厂
* factories.forEach(factory => {
* console.log('工厂对象:', factory);
* // 可以调用工厂的createObject方法创建对象
* const options = {};
* const obj = factory.createObject('someType', options);
* });
*
* // 检查是否存在特定类型的工厂
* const hasButtonFactory = factories.some(factory => {
* return factory.createObject('button') !== null;
* });
*/
static getFactories() {
return _factories.map(info => {
return info.factory;
});
}
/**
* 设置工厂。
* @param {object} factory 工厂。
* @param {string} name 唯一名称。
* @returns {boolean} 是否设置成功。
* @example
* // 创建一个新的工厂对象
* const newButtonFactory = {
* createObject: function(type, options) {
* if (type === 'button') {
* return new ModernButton(options); // 返回新版本的按钮
* }
* return null;
* }
* };
*
* // 替换现有的按钮工厂
* const success = THING.Utils.setFactory(newButtonFactory, 'buttonFactory');
* if (success) {
* console.log('成功更新按钮工厂');
* } else {
* console.log('未找到指定名称的工厂');
* }
*/
static setFactory(factory, name) {
for (var i = 0; i < _factories.length; i++) {
if (_factories[i].name == name) {
_factories[i].factory = factory;
return true;
}
}
return false;
}
/**
* 从工厂创建对象。
* @param {string} type 类型名称。
* @param {object} options 选项。
* @returns {object} 对象。
* @example
* // 创建一个按钮对象
* const buttonOptions = {
* text: '点击我',
* style: {
* width: '100px',
* height: '30px'
* }
* };
* const button = THING.Utils.createObject('button', buttonOptions);
*
* // 创建一个输入框对象
* const inputOptions = {
* placeholder: '请输入内容',
* type: 'text'
* };
* const input = THING.Utils.createObject('input', inputOptions);
*
* // 处理创建失败的情况
* const unknownObject = THING.Utils.createObject('unknownType');
* if (!unknownObject) {
* console.error('创建对象失败:未知类型');
* }
*/
static createObject(type, options = _defaultOptions) {
for (var i = 0, l = _factories.length; i < l; i++) {
var factory = _factories[i].factory;
var object = factory.createObject(type, options);
if (object) {
return object;
}
}
Utils.error(`Create (type: '${type}') object failed`, options);
return null;
}
/**
* 获取全局对象。
* @returns {object} 全局对象 (window in browser, global in Node.js)
* @example
* // 获取全局对象
* const globalObj = THING.Utils.getGlobal();
*
* // 在浏览器环境中
* console.log(globalObj === window); // true
*
* // 使用全局对象添加全局变量
* globalObj.myGlobalVar = 'Hello World';
* console.log(window.myGlobalVar); // 'Hello World'
*
* // 获取全局函数
* console.log(globalObj.setTimeout === window.setTimeout); // true
*/
static getGlobal() {
return _global;
}
/**
* 注册类到对象。
* @param {object} object 对象。
* @param {string} clsName 类名。
* @param {*} cls 类型。
* @param {*} out 附加对象。
* @param {boolean} force 是否强制覆盖已存在的类。
* @returns {boolean} 是否注册成功。
* @example
* // 注册类到对象
* const obj = {};
* const cls = {};
* const out = {};
* const force = true;
* const result = THING.Utils.registerClassToObject(obj, 'clsName', cls, out, force);
* console.log(result); // true
*/
static registerClassToObject(object, clsName, cls, out, force = true) {
if (!clsName) {
return false;
}
if (Utils.isClass(cls)) {
if (force) {
cls.className = clsName;
object[clsName] = cls;
}
else {
if (cls.className === undefined) {
cls.className = clsName;
}
if (!object[clsName]) {
object[clsName] = cls;
}
}
}
if (out) {
if (force || !out[clsName]) {
out[clsName] = cls;
}
}
return true;
}
/**
* 注册类。
* @param {string} clsName 类名。
* @param {*} cls 类型。
* @param {*} out 附加对象。
* @param {boolean} force 是否强制覆盖已存在的类。
* @returns {boolean} 是否注册成功。
* @example
* class MyClass {};
* THING.Utils.registerClass('MyClass', MyClass);
* let cls = THING.Utils.getRegisteredClass('MyClass');
* // @expect(cls != null);
* @public
*/
static registerClass(clsName, cls, out, force = true) {
return Utils.registerClassToObject(_registeredClasses, clsName, cls, out, force);
}
/**
* 注册类。
* @param {object} classes 类。
* @param {*} out 附加对象。
* @param {boolean} force 是否强制覆盖已存在的类。
* @example
* class MyClass {};
* THING.Utils.registerClass('MyClass', MyClass);
* let cls = THING.Utils.getRegisteredClass('MyClass');
* // @expect(cls != null);
*/
static registerClasses(classes, out, force) {
for (var key in classes) {
this.registerClass(key, classes[key], out, force);
}
}
/**
* 获取已注册的类。
* @param {string} name 类名。
* @returns {*} 类。
* @example
* class MyClass {};
* THING.Utils.registerClass('MyClass', MyClass);
* let cls = THING.Utils.getRegisteredClass('MyClass');
* // @expect(cls != null);
* let cls2 = THING.Utils.getRegisteredClass('MyClass_not');
* // @expect(cls2 == null);
*/
static getRegisteredClass(name) {
let cls = _registeredClasses[name];
if (cls) {
return cls;
}
return null;
}
/**
* 获取已注册的类。
* @returns {object} 类。
* @example
* class MyClass {};
* THING.Utils.registerClass('MyClass', MyClass);
* let cls = THING.Utils.getRegisteredClass('MyClass');
* // @expect(cls != null);
* let cls2 = THING.Utils.getRegisteredClass('MyClass_not');
* // @expect(cls2 == null);
*/
static getRegisteredClasses() {
return _registeredClasses;
}
/**
* 移除已注册的类。
* @param {string} name 类名。
* @example
* class MyClass {};
* THING.Utils.registerClass('MyClass', MyClass);
* THING.Utils.unregisterClass('MyClass');
* let cls = THING.Utils.getRegisteredClass('MyClass');
* // @expect(cls == null);
*/
static unregisterClass(name) {
delete _registeredClasses[name];
}
/**
* 注册组件。
* @param {string} clsName 类名。
* @param {*} cls 类型。
* @param {object} out 附加对象。
* @param {boolean} force 是否强制覆盖已存在的组件。
* @returns {boolean} 是否注册成功。
* @public
* @example
* // 定义一个自定义组件类
* class ColorComponent {
* constructor() {
* this.type = 'color';
* this.value = '#000000';
* }
*
* setColor(color) {
* this.value = color;
* }
* }
*
* // 注册组件
* THING.Utils.registerComponent('ColorComponent', ColorComponent);
*
* // 使用注册的组件
* const ColorClass = THING.Utils.getRegisteredComponent('ColorComponent');
* const colorComp = new ColorClass();
* colorComp.setColor('#FF0000');
*/
static registerComponent(clsName, cls, out, force = true) {
var success1 = Utils.registerClass(clsName, cls, out, force);
var success2 = Utils.registerClassToObject(_registeredComponents, clsName, cls, out, force);
return success1 && success2;
}
/**
* 注销组件
* @param {string} name 组件名称
* @public
* @example
* // 注册一个测试组件
* class TestComponent {
* constructor() {
* this.name = 'TestComponent';
* }
* }
* THING.Utils.registerComponent('TestComponent', TestComponent);
*
* // 注销组件
* THING.Utils.unregisterComponent('TestComponent');
*
* // 尝试获取已注销的组件
* const comp = THING.Utils.getRegisteredComponent('TestComponent');
* console.log(comp); // undefined
*/
static unregisterComponent(name) {
Utils.unregisterClass(name);
delete _registeredComponents[name];
}
/**
* 根据名称获取已注册的组件
* @param {string} name 组件名称
* @returns {object} 组件类
* @public
* @example
* // 注册一个按钮组件
* class ButtonComponent {
* constructor() {
* this.type = 'button';
* this.text = '点击我';
* }
*
* onClick() {
* console.log('按钮被点击');
* }
* }
* THING.Utils.registerComponent('ButtonComponent', ButtonComponent);
*
* // 获取注册的组件
* const ButtonClass = THING.Utils.getRegisteredComponent('ButtonComponent');
* if (ButtonClass) {
* const button = new ButtonClass();
* button.onClick(); // 输出: 按钮被点击
* }
*/
static getRegisteredComponent(name) {
return _registeredComponents[name];
}
/**
* 获取已注册的组件
* @returns {object} 组件。
* @public
* @example
* // 获取所有已注册的组件
* const components = THING.Utils.getRegisteredComponents();
*
* // 遍历所有组件
* for (const name in components) {
* console.log('组件名称:', name);
* console.log('组件类:', components[name]);
*
* // 创建组件实例
* const instance = new components[name]();
* }
*
* // 检查特定组件是否已注册
* if (components['ButtonComponent']) {
* console.log('按钮组件已注册');
* }
*
* // 获取已注册组件的数量
* const count = Object.keys(components).length;
* console.log('已注册组件数量:', count);
*/
static getRegisteredComponents() {
return _registeredComponents;
}
/**
* Register a variable.
* @param {string} key The variable key.
* @param {*} value The variable value.
* @example
* // 注册全局变量
* THING.Utils.registerVariable('config', {
* apiUrl: 'https://api.example.com',
* timeout: 5000
* });
*
* // 注册简单变量
* THING.Utils.registerVariable('maxRetries', 3);
*
* // 注册函数
* THING.Utils.registerVariable('formatDate', function(date) {
* return date.toLocaleDateString();
* });
*/
static registerVariable(key, value) {
_registeredVariables[key] = value;
}
/**
* 注销变量。
* @param {string} key 变量键。
* @example
* // 注销变量
* THING.Utils.unregisterVariable('config');
*/
static unregisterVariable(key) {
delete _registeredVariables[key];
}
/**
* Get registered variable by key.
* @param {string} key The variable key.
* @returns {*} The registered variable value.
* @example
* // 获取之前注册的变量
* const config = THING.Utils.getRegisteredVariable('config');
* console.log(config.apiUrl); // 'https://api.example.com'
*
* const maxRetries = THING.Utils.getRegisteredVariable('maxRetries');
* console.log(maxRetries); // 3
*
* // 使用注册的函数
* const formatDate = THING.Utils.getRegisteredVariable('formatDate');
* console.log(formatDate(new Date())); // 例如: '2024/3/15'
*
* // 处理未注册的变量
* const notExist = THING.Utils.getRegisteredVariable('notExist');
* console.log(notExist); // undefined
*/
static getRegisteredVariable(key) {
return _registeredVariables[key];
}
/**
* Make a promise cancelable.
* @param {Promise} promise The promise to make cancelable.
* @returns {object} An object with the promise and a cancel function.
* @example
* // 创建一个可取消的异步操作
* const asyncOperation = new Promise(resolve => {
* setTimeout(() => {
* resolve('操作完成');
* }, 5000);
* });
*
* // 将Promise转换为可取消的
* const cancelable = THING.Utils.makeCancelablePromise(asyncOperation);
*
* // 使用Promise
* cancelable.promise
* .then(result => {
* console.log(result); // 如果没有取消,将输出: '操作完成'
* })
* .catch(error => {
* if (error.isCanceled) {
* console.log('操作被取消');
* } else {
* console.error('操作出错:', error);
* }
* });
*
* // 在某个时刻取消操作
* setTimeout(() => {
* cancelable.cancel();
* }, 2000);
*/
static makeCancelablePromise(promise) {
return cancelable(promise);
}
/**
* 判断是否为原生函数。
* @param {Function} func 函数。
* @returns {boolean} 是否为原生函数。
* @example
* // 判断是否为原生函数
* const isNative = THING.Utils.isNativeFunction(Math.random);
* console.log(isNative); // true
*/
static isNativeFunction(func) {
if (!Utils.isFunction(func)) {
return false;
}
return func.toString() === 'function toString() { [native code] }';
}
/**
* 绑定对象属性。
* @param {object} object 对象。
* @param {object} accessor 访问器。
* @param {string} name 属性名。
* @example
* // 绑定对象属性
* const obj = {};
* const accessor = { value: 10 };
* THING.Utils.bindObjectProperty(obj, accessor, 'value');
* console.log(obj.value); // 10
*/
static bindObjectProperty(object, accessor, name) {
Object.defineProperty(object, name,
{
get: function () {
return accessor[name];
},
set: function (value) {
accessor[name] = value;
}
}
);
}
/**
* 获取布局进度。
* @param {string|number} type 布局类型。
* @returns {number} 布局进度。
* @example
* // 获取布局进度
* const progress = THING.Utils.getProgressByLayout('left');
* console.log(progress); // 0
*/
static getProgressByLayout(type) {
let progress = 0;
if (Utils.isString(type)) {
switch (type) {
case LayoutType.Left:
case LayoutType.Bottom:
case LayoutType.Back:
progress = 0;
break;
case LayoutType.Right:
case LayoutType.Top:
case LayoutType.Front:
progress = 1;
break;
case LayoutType.Center:
progress = 0.5;
break;
default:
break;
}
}
else if (Utils.isNumber(type)) {
progress = type;
}
return progress;
}
/**
* Merge set.
* @param {Set|Array} a The a array or set.
* @param {Set|Array} b The b array or set.
* @returns {Set} 合并后的集合。
* @example
* var a = new Set(['one','two']);
* var b = new Set(['three','four']);
* // Set(4) {'one', 'two', 'three', 'four'}
* console.log(THING.Utils.mergeSet(a,b));
*/
static mergeSet(a, b) {
if (!Utils.isSet(a) && !Utils.isArray(a)) {
a = _emptyArray;
}
if (!Utils.isSet(b) && !Utils.isArray(b)) {
b = _emptyArray;
}
const array = [...a, ...b];
return new Set(array);
}
/**
* 解析URL。
* @param {string} url 要解析的URL。
* @returns {string} 解析后的URL。
* @example
* // 解析URL
* const url = THING.Utils.resolveURL('http://example.com/path/to/resource');
* console.log(url); // 'http://example.com/path/to/resource'
*/
static resolveURL(url) {
for (let i = 0; i < _urlResolvers.length; i++) {
const resolver = _urlResolvers[i];
let result = resolver(url);
if (result && result !== url) {
return result;
}
}
return url;
}
/**
* 添加URL解析器。
* @param {Function} resolver 解析器。
* @example
* // 添加URL解析器
* THING.Utils.addURLResolver(function (url) {
* return url;
* });
*/
static addURLResolver(resolver) {
_urlResolvers.push(resolver);
}
/**
* 移除URL解析器。
* @param {Function} target 解析器。
* @example
* // 移除URL解析器
* THING.Utils.removeURLResolver(function (url) {
* return url;
* });
*/
static removeURLResolver(target) {
for (let i = _urlResolvers.length - 1; i >= 0; i--) {
const resolver = _urlResolvers[i];
if (target === resolver) {
_urlResolvers._removeAt(i);
}
}
}
/**
* 获取工作路径。
* @returns {string} 工作路径。
* @example
* // 获取工作路径
* const workPath = THING.Utils.workPath;
* console.log(workPath); // 'http://example.com/path/to/resource'
*/
static get workPath() {
return _workPath;
}
/**
* 设置工作路径。
* @param {string} val 工作路径。
* @example
* // 设置工作路径
* THING.Utils.workPath = 'http://example.com/path/to/resource';
*/
static set workPath(val) {
_workPath = val;
}
/**
* 获取全局环境变量。
* @returns {object} 全局环境变量。
* @example
* // 获取全局环境变量
* const globalEnvVars = THING.Utils.getGlobalEnvVars();
* console.log(globalEnvVars); // { a: 1, b: 2 }
*/
static getGlobalEnvVars() {
return Utils.cloneObject(_globalEnvVars, false)
}
/**
* 设置全局环境变量。
* @param {object} vars 环境变量。
* @example
* // 设置全局环境变量
* THING.Utils.setGlobalEnvVars({ a: 1, b: 2 });
*/
static setGlobalEnvVars(vars) {
if (!vars) {
_globalEnvVars = {}
}
else {
_globalEnvVars = Utils.cloneObject(vars, false)
}
}
/**
* 获取全局环境变量。
* @param {string} key 环境变量键。
* @returns {any} 环境变量值。
* @example
* // 获取全局环境变量
* const globalEnvVar = THING.Utils.getGlobalEnvVar('a');
* console.log(globalEnvVar); // 1
*/
static getGlobalEnvVar(key) {
return _globalEnvVars[key]
}
/**
* 设置全局环境变量。
* @param {string} key 环境变量键。
* @param {any} val 环境变量值。
* @example
* // 设置全局环境变量
* THING.Utils.setGlobalEnvVar('a', 1);
*/
static setGlobalEnvVar(key, val) {
_globalEnvVars[key] = val
}
// 字符串排序,如果字符串中包含数字,则按照数字大小排序,否则按照字典顺序排序
/**
* 字符串排序。
* @param {string} a 字符串a。
* @param {string} b 字符串b。
* @returns {number} 排序结果。
* @example
* // 字符串排序
* const result = THING.Utils.stringSort('a10', 'a2');
* console.log(result); // -1
*/
static stringSort(a, b) {
let i = 0;
let lenA = a.length;
let lenB = b.length;
while (i < lenA && i < lenB) {
const charA = a[i];
const charB = b[i];
// 检查当前字符是否可能是数字的开始
const isDigitStartA = !isNaN(parseInt(charA, 10));
const isDigitStartB = !isNaN(parseInt(charB, 10));
if (isDigitStartA && isDigitStartB) {
// 找到数字部分的结束位置
let numStartA = i;
let numStartB = i;
while (numStartA < lenA && !isNaN(parseInt(a[numStartA], 10))) {
numStartA++;
}
while (numStartB < lenB && !isNaN(parseInt(b[numStartB], 10))) {
numStartB++;
}
// 提取数字部分并进行比较
const numA = parseInt(a.slice(i, numStartA), 10);
const numB = parseInt(b.slice(i, numStartB), 10);
if (numA !== numB) {
return numA - numB;
}
// 如果数字相同,则继续比较后面的字符
i = numStartA - 1; // 减1是因为循环会自增i
}
else if (isDigitStartA !== isDigitStartB) {
// 如果一个字符是数字的开始,另一个不是,则根据需求决定顺序
// 这里假设非数字部分排在数字部分前面(如果需要调整,可以交换返回值)
return isDigitStartA ? 1 : -1;
}
else {
// 如果两个字符都不是数字的开始,则按字典顺序比较
if (charA !== charB) {
return charA.localeCompare(charB);
}
}
i++;
}
// 如果到这里还没有返回,说明前面的字符(包括数字整体)都相同
// 比较剩余字符串的长度(处理例如 "Boxv" 和 "Box10vxx" 的情况)
return lenA - lenB;
}
/**
* Compare two values for equality.
* @param {*} o1 The first value.
* @param {*} o2 The second value.
* @returns {boolean} True if the objects are equal.
* @example
* // 比较两个变量是否相等
* THING.Utils.equals([0,1,2], [0,1,2]); // true
* THING.Utils.equals('a', 'a'); // true
* THING.Utils.equals(1, 1); // true
* THING.Utils.equals('1', 1); // false
* THING.Utils.equals({a:'a'}, {}); // false
*/
static equals(o1, o2) {
const o1Type = getBaseType(o1);
const o2Type = getBaseType(o2);
if (o1Type !== o2Type) {
return false
}
if (o1Type === 'array') {
if (o1.length !== o2.length) {
return false;
}
for (let i = 0; i < o1.length; i++) {
if (o1[i] !== o2[i]) {
return false
}
}
return true;
}
if (this.isPlainObject(o1) && this.isPlainObject(o2)) {
return this.equalsObject(o1, o2)
}
if (o1 !== o2) {
return false;
}
return true;
}
}
function getBaseType(value) {
if (Array.isArray(value)) {
return 'array'
}
else if (value === null) {
return 'null'
}
else if (value === undefined) {
return 'undefined'
}
else {
return typeof (value);
}
}
Utils.addURLResolver(function (url) {
if (!url._startsWith('/')) {
return url;
}
if (url._startsWith(_workPath)) {
return url;
}
if (_workPath) {
return _workPath._appendURL(url);
}
return url;
});
/**
* Resolve protocol-relative URL (starts with '//') to absolute URL using current window protocol.
* If the input does not start with "//", it will be returned unchanged.
* @param {string} url The url string.
* @returns {string} The resolved url.
* @example
* // In browser environment with https protocol
* BaseUtils.resolveURL('//example.com/img.png'); // returns 'https://example.com/img.png'
*/
Utils.addURLResolver(function (url) {
if (BaseUtils.isNull(url) || !BaseUtils.isString(url)) {
return url;
}
if (url.startsWith('//')) {
if (typeof window !== 'undefined' && window.location && window.location.protocol) {
return window.location.protocol + url;
}
// Fallback to https when window is unavailable (e.g. in node env)
return 'https:' + url;
}
return url;
});
var _consoleDebugFunc = console.debug;
var _consoleLogFunc = console.log;
var _consoleWarnFunc = console.warn;
var _consoleErrorFunc = console.error;
var _consoleTimeFunc = console.time;
var _consoleTimeEndFunc = console.timeEnd;
// Define other useful attributes.
Object.defineProperties(Utils, {
});
export { Utils };