看了昨天的 es6+ 工具的介紹,可以幫助我們實現工具庫的模組化實踐,因此我們來練習寫一個字串處理的工具,並在寫好後根據昨天的 es6+ 特性來重構這個小工具。
變數命名始終是一個大哉問,例如命名流派(?)就有分 camel case
、pascal case
、kebab case
、 snake case
。可能 conventionally 前端使用一種、後端使用一種、資料庫又使用另一種,這時我們就可能需要轉換變數名稱。
userName
)UserName
)user-name
)user_name
)我們最終想要一個 functional programming 的介面,可以輸入一個字串,然後不需要重複輸入,就可以轉變成不同的 case,如下
const converter = letterCase("userName");
const camel = converter.toCamelCase();
const snake = converter.toSnakeCase();
因此,這個模組化工具的主要內容應該看起來長這樣:
function letterCase(str = ''){
const chars = parse(str)
return {
toCamelCase: () => ...,
toSnakeCase: () => ...,
toKebabCase: () => ...,
toPascalCase: () => ...,
toConstantCase: () => ...,
toDotCase: () => ...
}
}
此處的 chars
為一個個小寫字母的 array,其中用 null 當作一個 element 為分界,那在每一個轉換的 function 就變得很容易,因為只要每次遇到 null 就代表後面應該要加 -
或 .
、或改成大寫。如 user-name
會被爬成 ['u', 's', 'e', 'r', null, 'n', 'a', 'm', 'e']
。
雖然用正規表達可以快很多,但因為正規表達式很難懂,所以沒關係!我們就走邏輯派!
接著就可以繼續處理拿到的 array,根據觀察,字串分成兩種:一種是用某個東西把字接起來的(也就是用符號把目前的 null 取代),另一種是直接改變大小寫。按照這兩個邏輯,我們可以把上面 letterCase
的內容填起來:
寫完了很開心 XD 忘了這只是為了提供一個範例好讓我們可以改寫成比較 es6+ 特性的結構。
首先,可以把一些 const 變數提取出來,以更有語意的 Symbol 創建標記以避免打錯或忘記。
const SEPARATOR_SYMBOL = Symbol('separator');
/**
* 使用 Map 來映射字符碼,提升可讀性
*/
const SEPARATOR_CHARS = new Map([
[45, '-'], // hyphen
[46, '.'], // dot
[95, '_'] // underscore
]);
const CASE_TYPES = {
CAMEL: Symbol('camel'),
PASCAL: Symbol('pascal'),
SNAKE: Symbol('snake'),
KEBAB: Symbol('kebab'),
CONSTANT: Symbol('constant'),
DOT: Symbol('dot')
};
接著使用 desctruing
、 spread operator
改寫 parse()
class LetterCaseConverter {
#parsedChars = [];
constructor(str = "") {
this.#parsedChars = this.#parse(str);
}
#parse(str) {
const output = [];
// 使用 Spread Operator 將字串轉為字符陣列
const chars = [...str];
for (const [index, char] of chars.entries()) {
const charCode = char.charCodeAt(0);
const lowerChar = char.toLowerCase();
const upperChar = char.toUpperCase();
// 使用 Map 檢查是否為分隔符
if (SEPARATOR_CHARS.has(charCode)) {
output.push(SEPARATOR_SYMBOL);
continue;
}
// 檢查大寫字母(除了第一個字符)
if (char === upperChar && index !== 0) {
output.push(SEPARATOR_SYMBOL, lowerChar);
} else {
output.push(lowerChar);
}
}
return output;
}
應用同樣的技巧,一步步改寫其他的 function,並使用 getter 回傳內部定義好的方法。
#capitalizedBasedSeparation(initialCapitalization = false) {
let capitalize = initialCapitalization;
const output = [];
// 使用 Destructuring Assignment 處理字符
for (const char of this.#parsedChars) {
if (char === SEPARATOR_SYMBOL) {
capitalize = true;
} else if (capitalize) {
output.push(char.toUpperCase());
capitalize = false;
} else {
output.push(char);
}
}
return output.join("");
}
#charBasedSeparation(separator = "", transform = char => char) {
const output = [];
for (const char of this.#parsedChars) {
if (char === SEPARATOR_SYMBOL) {
output.push(separator);
} else {
output.push(transform(char));
}
}
return output.join("");
}
get camelCase() {
return this.#capitalizedBasedSeparation(false);
}
get pascalCase() {
return this.#capitalizedBasedSeparation(true);
}
get snakeCase() {
return this.#charBasedSeparation("_");
}
get kebabCase() {
return this.#charBasedSeparation("-");
}
get constantCase() {
return this.#charBasedSeparation("_", char => char.toUpperCase());
}
get dotCase() {
return this.#charBasedSeparation(".");
}
/**
* 使用 Destructuring Assignment 返回所有格式
*/
get allCases() {
return {
camelCase: this.camelCase,
pascalCase: this.pascalCase,
snakeCase: this.snakeCase,
kebabCase: this.kebabCase,
constantCase: this.constantCase,
dotCase: this.dotCase
};
}
/**
* 使用 Generator 創建迭代器,可以逐一產生不同格式
*/
*caseIterator() {
const cases = this.allCases;
// 使用 Destructuring 遍歷
for (const [type, value] of Object.entries(cases)) {
yield { type, value };
}
}
/**
* 使用 Symbol.iterator 讓物件可以被 for...of 遍歷
*/
*[Symbol.iterator]() {
yield* this.caseIterator();
}
/**
* 使用 Spread Operator 的批量轉換方法
*/
static convertMultiple(...strings) {
return strings.map(str => new LetterCaseConverter(str).allCases);
}
/**
* 使用 Rest Parameters 和 Destructuring 的工廠方法
*/
static fromMultipleSources(...sources) {
const [primary, ...alternatives] = sources;
const converter = new LetterCaseConverter(primary);
// 如果主要來源為空,嘗試替代來源
if (!primary && alternatives.length > 0) {
const validSource = alternatives.find(src => src && src.trim());
return new LetterCaseConverter(validSource || '');
}
return converter;
}
}
// 使用 ES6 Modules 導出
export default LetterCaseConverter;
// 使用 Destructuring 導出便利函數
export const {
convertMultiple,
fromMultipleSources
} = LetterCaseConverter;
// 便利的工廠函數
export const letterCase = str => new LetterCaseConverter(str);
最後,我們來看一下如何使用
1. 基本使用
const converter = letterCase("userName");
console.log('Getter API:', converter.camelCase); // Getter API:
userName
2. Destructuring Assignment
const { camelCase, snakeCase, kebabCase } = converter.allCases;
console.log('Destructuring:', { camelCase, snakeCase, kebabCase });
3. Spread Operator - 批量轉換
const results = LetterCaseConverter.convertMultiple(
"userName",
"user-name",
"user_name"
);
console.log('Spread Operator:', results);
這邊的輸出結果會得到三個 item 一樣的 array,符合我們轉換全部種類的預期。
4. Iterator & Generator - 使用 for...of 遍歷
也可以使用 iterator 得到以上的內容
for (const { type, value } of converter) {
console.log(` ${type}: ${value}`);
// Iterator:
// camelCase: userName
// pascalCase: UserName
// snakeCase: user_name
// kebabCase: user-name
// constantCase: USER_NAME
// dotCase: user.name
}
5. Destructuring with Rest - 工廠方法
const converter2 = LetterCaseConverter.fromMultipleSources(
'',
'fallback-name',
'another-option'
);
console.log('Rest Parameters:', converter2.camelCase); // Rest Parameters:
fallbackName
6. Symbol
console.log('SEPARATOR_SYMBOL:', SEPARATOR_SYMBOL.toString());
console.log('CASE_TYPES:', Object.values(CASE_TYPES).map(s => s.toString()));