From 075e89b957e515a1acfbb150d342a2db0c4fd843 Mon Sep 17 00:00:00 2001 From: Radoslav Karaivanov Date: Mon, 22 Apr 2024 17:13:29 +0300 Subject: [PATCH] refactor(mask): Parser perf improvements (#1164) * Removed extra calls to `parseMaskLiterals` around the codebase. * Improved `apply` memory allocations by modifying an array instead of re-creating the masked string for each character. --- src/components/mask-input/mask-parser.ts | 74 ++++++++++++++---------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/src/components/mask-input/mask-parser.ts b/src/components/mask-input/mask-parser.ts index 304d2094b..bdec72e54 100644 --- a/src/components/mask-input/mask-parser.ts +++ b/src/components/mask-input/mask-parser.ts @@ -43,18 +43,17 @@ export class MaskParser { options: MaskOptions = { format: 'CCCCCCCCCC', promptCharacter: '_' } ) { this.options = options; + this.parseMaskLiterals(); } protected literals = new Map(); protected _escapedMask = ''; public get literalPositions() { - this.getMaskLiterals(); return Array.from(this.literals.keys()); } public get escapedMask() { - this.getMaskLiterals(); return this._escapedMask; } @@ -64,7 +63,7 @@ export class MaskParser { public set mask(value: string) { this.options.format = value || this.options.format; - this.getMaskLiterals(); + this.parseMaskLiterals(); } public get prompt() { @@ -77,7 +76,7 @@ export class MaskParser { : this.options.promptCharacter; } - protected getMaskLiterals() { + protected parseMaskLiterals() { this.literals.clear(); this._escapedMask = this.mask; @@ -111,18 +110,30 @@ export class MaskParser { protected getNonLiteralPositions(mask = '') { const positions = this.literalPositions; - return Array.from(mask) - .map((_, pos) => (!positions.includes(pos) ? pos : -1)) - .filter((pos) => pos > -1); + const result = []; + const iter = Array.from(mask).entries(); + + for (const [pos, _] of iter) { + if (!positions.includes(pos)) { + result.push(pos); + } + } + + return result; } protected getRequiredNonLiteralPositions(mask: string) { const positions = this.literalPositions; - return Array.from(mask) - .map((char, pos) => - REQUIRED.has(char) && !positions.includes(pos) ? pos : -1 - ) - .filter((pos) => pos > -1); + const result = []; + const iter = Array.from(mask).entries(); + + for (const [pos, char] of iter) { + if (REQUIRED.has(char) && !positions.includes(pos)) { + result.push(pos); + } + } + + return result; } public getPreviousNonLiteralPosition(start: number) { @@ -176,13 +187,17 @@ export class MaskParser { } public parse(masked = '') { - return Array.from(masked).reduce((prev, char, pos) => { - return `${prev}${ - !this.literalPositions.includes(pos) && !this.isPromptChar(char) - ? char - : '' - }`; - }, ''); + const positions = this.literalPositions; + const result = []; + const iter = Array.from(masked).entries(); + + for (const [pos, char] of iter) { + !positions.includes(pos) && !this.isPromptChar(char) + ? result.push(char) + : result.push(''); + } + + return result.join(''); } public isValidString(input = '') { @@ -191,10 +206,10 @@ export class MaskParser { if (required.length > this.parse(input).length) { return false; } + return required.every((pos) => { const char = input.charAt(pos); return ( - char !== undefined && this.validate(char, this._escapedMask.charAt(pos)) && !this.isPromptChar(char) ); @@ -202,17 +217,17 @@ export class MaskParser { } public apply(input = '') { - const nonLiteralPositions = this.getNonLiteralPositions(this._escapedMask); - let output = new Array(this._escapedMask.length).fill(this.prompt).join(''); + const output = new Array(this._escapedMask.length).fill(this.prompt); - this.literals.forEach((char, pos) => { - output = this.replaceCharAt(output, pos, char); - }); + for (const [pos, char] of this.literals.entries()) { + output[pos] = char; + } if (!input) { - return output; + return output.join(''); } + const nonLiteralPositions = this.getNonLiteralPositions(this._escapedMask); const values = nonLiteralPositions.map((pos, index) => { const char = input.charAt(index); return !this.validate(char, this._escapedMask.charAt(pos)) && @@ -225,11 +240,10 @@ export class MaskParser { values.splice(nonLiteralPositions.length); } - let pos = 0; - for (const each of values) { - output = this.replaceCharAt(output, nonLiteralPositions[pos++], each); + for (const [position, char] of values.entries()) { + output[nonLiteralPositions[position]] = char; } - return output; + return output.join(''); } }