1/** @type {Map<string, any>} Armazena os seletores e seus resultados. */ 2const domCache = new Map(); 3/** @type {number} Número máximo de entradas no cache antes de limpar automaticamente. */ 4const MAX_CACHE_SIZE = 500; 5/** @type {Map<string, IntersectionObserver>} Cache para armazenar observers ativos. */ 6const intersectionObserverCache = new Map(); 7var utilsSpeed = {}; 8 9 10const scopeIds = new WeakMap(); 11let scopeIdCounter = 0; 12 13utilsSpeed.getScopeKey = function (scope) { 14 if (scope === document) return 'document'; 15 if (!scopeIds.has(scope)) { 16 scopeIds.set(scope, `scope-${++scopeIdCounter}`); 17 } 18 return scopeIds.get(scope); 19} 20 21/** 22 * Busca um elemento no DOM e usa cache para evitar buscas repetidas. 23 * 24 * @param {string} selector - Seletor CSS do elemento a ser buscado. 25 * @param {ParentNode} [scope=document] - Elemento onde será feita a busca (default: document). 26 * @returns {Element|null} - Elemento encontrado ou null. 27 */ 28utilsSpeed.getCachedElement = function (selector, scope = document) { 29 const scopeKey = utilsSpeed.getScopeKey(scope); 30 const cacheKey = `${scopeKey}:${selector}`; 31 32 if (domCache.has(cacheKey)) { 33 const cachedEl = domCache.get(cacheKey); 34 35 if (!cachedEl || !document.contains(cachedEl)) { 36 domCache.delete(cacheKey); 37 } else { 38 domCache.delete(cacheKey); 39 domCache.set(cacheKey, cachedEl); 40 return cachedEl; 41 } 42 } 43 44 const foundElement = scope.querySelector(selector); 45 if (!foundElement) return null; 46 47 if (domCache.size >= MAX_CACHE_SIZE) { 48 const firstKey = domCache.keys().next().value; 49 domCache.delete(firstKey); 50 } 51 52 domCache.set(cacheKey, foundElement); 53 54 return foundElement; 55}; 56 57/** 58 * Busca vários elementos no DOM com cache para evitar buscas repetidas. 59 * 60 * @param {string} selector - Seletor CSS dos elementos a serem buscados. 61 * @param {ParentNode} [scope=document] - Elemento onde será feita a busca (default: document). 62 * @returns {NodeListOf<Element>} - Lista de elementos encontrados. 63 */ 64utilsSpeed.getCachedElements = function (selector, scope = document) { 65 const scopeKey = utilsSpeed.getScopeKey(scope); 66 const cacheKey = `all:${scopeKey}:${selector}`; 67 68 if (domCache.has(cacheKey)) { 69 const nodeList = domCache.get(cacheKey); 70 71 if (nodeList && Array.from(nodeList).every(el => document.contains(el))) { 72 domCache.delete(cacheKey); 73 domCache.set(cacheKey, nodeList); 74 return nodeList; 75 } else { 76 domCache.delete(cacheKey); 77 } 78 } 79 80 const foundElements = scope.querySelectorAll(selector); 81 if (!foundElements.length) return []; 82 83 if (domCache.size >= MAX_CACHE_SIZE) { 84 const firstKey = domCache.keys().next().value; 85 domCache.delete(firstKey); 86 } 87 88 domCache.set(cacheKey, foundElements); 89 90 return foundElements; 91}; 92 93 94// Função para verificar se elemento existe antes de usar 95utilsSpeed.safeQuerySelector = function (parent, selector) { 96 return parent ? parent.querySelector(selector) : null; 97} 98 99// Função para verificar se elementos existem antes de usar 100utilsSpeed.safeQuerySelectorAll = function (parent, selector) { 101 return parent ? parent.querySelectorAll(selector) : []; 102} 103 104/** 105 * Executa a função apenas após um tempo de inatividade (útil para resize, input). 106 * @param {Function} fn - Função a ser executada. 107 * @param {number} wait - Tempo de espera em ms. 108 * @returns {Function} 109 */ 110utilsSpeed.safeDebounce = function (fn, wait = 100) { 111 let timeout; 112 return function (...args) { 113 clearTimeout(timeout); 114 timeout = setTimeout(() => fn.apply(this, args), wait); 115 }; 116} 117 118/** 119 * Executa a função no máximo 1 vez por intervalo definido (útil para scroll, resize). 120 * @param {Function} fn - Função a ser executada. 121 * @param {number} limit - Intervalo mÃnimo entre execuções em ms. 122 * @returns {Function} 123 */ 124utilsSpeed.safeThrottle = function (fn, limit = 100) { 125 let inThrottle = false; 126 return function (...args) { 127 if (!inThrottle) { 128 fn.apply(this, args); 129 inThrottle = true; 130 setTimeout(() => (inThrottle = false), limit); 131 } 132 }; 133} 134 135/** 136 * Cria um IntersectionObserver com opções padrão otimizadas. 137 * Ideal para lazy loading e ativações condicionais. 138 * 139 * @param {IntersectionObserverCallback} callback - Função executada quando o alvo entra/saà da viewport. 140 * @param {IntersectionObserverInit} [options={}] - Configurações do observer. 141 * @returns {IntersectionObserver} 142 */ 143utilsSpeed.createIntersectionObserver = function (callback, options = {}) { 144 const defaultOptions = { 145 root: null, 146 rootMargin: '50px', 147 threshold: 0.1 148 }; 149 const finalOptions = { ...defaultOptions, ...options }; 150 return new IntersectionObserver(callback, finalOptions); 151} 152 153/** 154 * Limpa e desconecta todos os observers salvos no cache. 155 * Ideal para uso em componentes que são destruÃdos dinamicamente. 156 */ 157utilsSpeed.cleanupIntersectionObservers = function () { 158 intersectionObserverCache.forEach(observer => { 159 if (observer?.disconnect) { 160 observer.disconnect(); 161 } 162 }); 163 intersectionObserverCache.clear(); 164} 165 166/** 167 * Adiciona um listener de scroll otimizado com throttle e listener passivo. 168 * Ajusta o throttle dinamicamente para dispositivos de baixa performance. 169 * 170 * @param {Element} element - Elemento DOM que receberá o listener de scroll. 171 * @param {Function} callback - Função a ser executada no evento scroll. 172 * @param {Object} [options] - Opções para customização. 173 * @param {boolean} [options.passive=true] - Define se o listener será passivo. 174 * @param {number} [options.throttle] - Intervalo em ms para throttle; padrão baseado em dispositivo. 175 * 176 * @returns {Function} Função para remover o listener de scroll adicionado. 177 */ 178utilsSpeed.optimizeScrollEvent = function (element, callback, options = {}) { 179 const defaultOptions = { 180 passive: true, 181 throttle: utilsSpeed.isLowEndDevice() ? 150 : 100, 182 }; 183 const finalOptions = { ...defaultOptions, ...options }; 184 const throttledCallback = utilsSpeed.safeThrottle(callback, finalOptions.throttle); 185 186 element.addEventListener('scroll', throttledCallback, { passive: finalOptions.passive }); 187 188 return () => { 189 element.removeEventListener('scroll', throttledCallback); 190 }; 191} 192 193// Sistema de fila para atualizações de estilo para evitar reflows forçados 194const styleUpdateQueue = new Map(); 195let styleUpdateScheduled = false;
196 197utilsSpeed.queueStyleUpdate = function (element, property, value) { 198 if (!element || !element.isConnected) { 199 return; 200 } 201 202 if (!styleUpdateQueue.has(element)) { 203 styleUpdateQueue.set(element, new Map()); 204 } 205 206 const styles = styleUpdateQueue.get(element); 207 styles.set(property, value); 208 209 if (!styleUpdateScheduled) { 210 styleUpdateScheduled = true; 211 212 requestAnimationFrame(() => { 213 styleUpdateQueue.forEach((properties, element) => { 214 if (element && element.isConnected) { 215 properties.forEach((value, property) => { 216 element.style[property] = value; 217 }); 218 } 219 }); 220 221 styleUpdateQueue.clear(); 222 styleUpdateScheduled = false; 223 }); 224 } 225}; 226 227 228/** 229 * Detecta se o dispositivo é considerado de baixa performance com base em critérios configuráveis. 230 * 231 * Ãtil para condicionar a execução de scripts pesados, ajustar tamanhos de chunks, desabilitar autoplay, etc. 232 * 233 * @param {Object} [thresholds] - Configurações personalizadas dos critérios. 234 * @param {number} [thresholds.maxCpuCores=2] - Número máximo de núcleos de CPU para considerar como fraco. 235 * @param {number} [thresholds.maxMemory=2] - Quantidade máxima de memória (em GB) para considerar como fraco. 236 * @param {boolean} [thresholds.requireMobile=true] - Se `true`, o dispositivo deve ser mobile para ser considerado fraco. 237 * @param {number} [thresholds.maxScreenWidth=480] - Largura máxima da tela (em pixels) para considerar como fraco. 238 * @param {number} [thresholds.minConditionsMet=3] - Número mÃnimo de critérios que precisam ser atendidos para considerar o dispositivo fraco. 239 * @returns {boolean} `true` se o dispositivo for considerado de baixa performance, `false` caso contrário. 240 */ 241let _cachedLowEnd; 242utilsSpeed.isLowEndDevice = function (thresholds = {}) { 243 if (typeof _cachedLowEnd !== 'undefined') return _cachedLowEnd; 244 245 const DEFAULTS = { 246 maxCpuCores: 2, 247 maxMemory: 2, 248 requireMobile: true, 249 maxScreenWidth: 480, 250 minConditionsMet: 3 251 }; 252 const config = { ...DEFAULTS, ...thresholds }; 253 254 try { 255 const deviceSpecs = { 256 cpuCores: navigator.hardwareConcurrency || 2, 257 memory: navigator.deviceMemory || 1, 258 isMobile: /android|iphone|ipad|ipod/.test(navigator.userAgent.toLowerCase()), 259 screenWidth: window.screen.width || 0 260 }; 261 262 const metConditions = [ 263 deviceSpecs.cpuCores <= config.maxCpuCores, 264 deviceSpecs.memory <= config.maxMemory, 265 !config.requireMobile || deviceSpecs.isMobile, 266 deviceSpecs.screenWidth <= config.maxScreenWidth 267 ].filter(Boolean).length; 268 269 _cachedLowEnd = metConditions >= config.minConditionsMet; 270 return _cachedLowEnd; 271 } catch { 272 _cachedLowEnd = true; 273 return true; 274 } 275} 276 277// Função para otimizar batch size baseado no dispositivo 278utilsSpeed.getOptimizedBatchSize = function () { 279 return utilsSpeed.isLowEndDevice() ? 1 : 2; 280} 281 282// Função para otimizar delay baseado no dispositivo 283utilsSpeed.getOptimizedDelay = function () { 284 return utilsSpeed.isLowEndDevice() ? 50 : 30; 285} 286 287/** 288 * Executa uma lista de tarefas em lotes para evitar long tasks e travamentos na UI. 289 * Usa requestIdleCallback para aproveitar tempo ocioso e requestAnimationFrame como fallback para sincronizar com repaint. 290 * Adapta o tamanho do lote em dispositivos de baixa performance. 291 * 292 * @param {Iterable} items - Elementos ou dados a serem processados (Array, NodeList, Set, etc). 293 * @param {Function} taskFn - Função a ser executada para cada item (recebe item, Ãndice). 294 * @param {number} [batchSize=1] - Número máximo de itens processados por lote. 295 * @param {number} [delay=20] - Tempo de espera usado somente se nenhuma API para agendamento avançado estiver disponÃvel. 296 * @param {boolean} [adaptive=true] - Reduz batchSize automaticamente em dispositivos fracos. 297 */ 298utilsSpeed.runTasksInChunks = function (items, taskFn, batchSize = 1, delay = 20, adaptive = true) { 299 const entries = Array.from(items); 300 let index = 0; 301 302 const effectiveBatchSize = 303 adaptive && typeof utilsSpeed.isLowEndDevice === "function" && utilsSpeed.isLowEndDevice() 304 ? Math.min(batchSize, 2) 305 : batchSize; 306 307 function processBatch(deadline) { 308 let count = 0; 309 310 while ( 311 index < entries.length && 312 count < effectiveBatchSize && 313 (!deadline || deadline.timeRemaining() > 0) 314 ) { 315 const value = entries[index]; 316 taskFn(value, index); 317 index++; 318 count++; 319 } 320 321 if (index < entries.length) { 322 if ("requestIdleCallback" in window) { 323 requestIdleCallback(processBatch, { timeout: 100 }); 324 } else if ("requestAnimationFrame" in window) { 325 requestAnimationFrame(() => processBatch()); 326 } else { 327 setTimeout(() => processBatch(), delay); 328 } 329 } 330 } 331 332 processBatch(); 333} 334 335/** 336 * Reconstrói o conteúdo de um container DOM de forma eficiente, 337 * evitando reflows e reprocessamento do layout. 338 * 339 * @param {Element} container - Elemento onde o conteúdo será refeito. 340 * @param {Iterable<string|Element>} contentList - Lista de strings HTML ou elementos DOM. 341 * @param {Object} [options] 342 * @param {boolean} [options.clear=true] - Se deve limpar o container antes. 343 * 344 * @example 345 * const items = document.querySelectorAll('.item'); 346 * const htmlList = Array.from(items).map(i => `<div>${i.outerHTML}</div>`); 347 * replaceContentEfficiently(container, htmlList); 348 */ 349utilsSpeed.replaceContentEfficiently = function (container, contentList, options = {}) { 350 if (!container || typeof container.appendChild !== 'function') { 351 return; 352 } 353 354 const { clear = true, compare = false } = options; 355 356 const fragment = document.createDocumentFragment(); 357 358 for (const content of contentList) { 359 if (typeof content === 'string') { 360 const template = document.createElement('template'); 361 template.innerHTML = content.trim(); 362 fragment.appendChild(template.content.cloneNode(true)); 363 } else if (content instanceof Element) { 364 fragment.appendChild(content); 365 } 366 } 367 368 if (clear) { 369 if (compare && container.innerHTML.trim() === fragment.textContent.trim()) { 370 return; 371 } 372 container.innerHTML = ''; 373 } 374 375 container.appendChild(fragment); 376}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.