String对象与方法
String对象与方法
JavaScript的String对象提供了丰富的字符串处理方法,如查找、替换、截取、分割等。本文将详细介绍String对象的常用方法及其使用技巧。
String对象概述
在JavaScript中,字符串可以通过字符串字面量或String对象创建。String对象是字符串的包装对象,提供了许多处理字符串的方法。
// 字符串字面量
const str1 = "Hello";
// String对象
const str2 = new String("Hello");
console.log(typeof str1); // "string"
console.log(typeof str2); // "object"
// 自动装箱:当对字符串字面量调用方法时,JavaScript会自动将其转换为临时String对象
console.log("Hello".length); // 5
console.log("Hello".toUpperCase()); // "HELLO"
字符串属性
length
返回字符串的长度(字符数量)。
const str = "Hello, World!";
console.log(str.length); // 13
// 空字符串长度为0
console.log("".length); // 0
// 包含Unicode字符的字符串
console.log("😊".length); // 2(注意:某些Unicode字符占用2个代码单元)
访问字符
使用索引访问
可以使用方括号表示法或charAt()方法访问字符串中的单个字符。
const str = "Hello";
// 使用方括号(ES5及以上)
console.log(str[0]); // "H"
console.log(str[1]); // "e"
// 使用charAt()方法
console.log(str.charAt(0)); // "H"
console.log(str.charAt(1)); // "e"
// 超出范围的索引
console.log(str[10]); // undefined
console.log(str.charAt(10)); // ""(空字符串)
// 字符串是不可变的,不能通过索引修改字符
str[0] = "J"; // 无效操作
console.log(str); // 仍然是 "Hello"
charCodeAt() 和 codePointAt()
获取字符的Unicode编码。
const str = "Hello 😊";
// charCodeAt() 返回指定位置的字符的UTF-16编码
console.log(str.charCodeAt(0)); // 72("H"的Unicode值)
console.log(str.charCodeAt(6)); // 55357(表情符号的第一个代码单元)
// codePointAt() 返回指定位置的Unicode码点(ES6)
console.log(str.codePointAt(0)); // 72("H"的Unicode值)
console.log(str.codePointAt(6)); // 128522(完整的表情符号码点)
字符串查找方法
indexOf() 和 lastIndexOf()
查找子字符串,返回首次出现的位置索引,如果未找到则返回-1。
const str = "Hello, World! Hello, JavaScript!";
// indexOf() 从前向后查找
console.log(str.indexOf("Hello")); // 0
console.log(str.indexOf("Hello", 1)); // 14(从索引1开始查找)
console.log(str.indexOf("Python")); // -1(未找到)
// lastIndexOf() 从后向前查找
console.log(str.lastIndexOf("Hello")); // 14
console.log(str.lastIndexOf("Hello", 13)); // 0(从索引13开始向前查找)
console.log(str.lastIndexOf("Python")); // -1(未找到)
includes(), startsWith() 和 endsWith()
检查字符串是否包含特定子字符串,或是否以特定子字符串开头或结尾。
const str = "Hello, World!";
// includes() 检查是否包含子字符串(ES6)
console.log(str.includes("Hello")); // true
console.log(str.includes("World")); // true
console.log(str.includes("Python")); // false
console.log(str.includes("Hello", 1)); // false(从索引1开始查找)
// startsWith() 检查是否以子字符串开头(ES6)
console.log(str.startsWith("Hello")); // true
console.log(str.startsWith("World")); // false
console.log(str.startsWith("World", 7)); // true(从索引7开始检查)
// endsWith() 检查是否以子字符串结尾(ES6)
console.log(str.endsWith("World!")); // true
console.log(str.endsWith("Hello")); // false
console.log(str.endsWith("Hello", 5)); // true(检查前5个字符)
search()
使用正则表达式搜索字符串,返回匹配的第一个位置索引,如果未找到则返回-1。
const str = "Hello, World! The year is 2023.";
// 搜索字母
console.log(str.search(/World/)); // 7
// 搜索数字
console.log(str.search(/\d+/)); // 25
// 使用不区分大小写的标志
console.log(str.search(/world/i)); // 7
// 未找到匹配
console.log(str.search(/JavaScript/)); // -1
match() 和 matchAll()
使用正则表达式匹配字符串,返回匹配结果。
const str = "Hello, World! Hello, JavaScript!";
// match() 返回匹配结果数组
const matches = str.match(/Hello/g);
console.log(matches); // ["Hello", "Hello"]
// 不使用全局标志,返回更详细的信息
const match = str.match(/Hello/);
console.log(match[0]); // "Hello"
console.log(match.index); // 0
console.log(match.input); // 整个原始字符串
// matchAll() 返回迭代器(ES2020)
const matchIterator = str.matchAll(/Hello/g);
for (const match of matchIterator) {
console.log(match[0], match.index);
}
// "Hello" 0
// "Hello" 14
字符串修改方法
大小写转换
const str = "Hello, World!";
// 转换为大写
console.log(str.toUpperCase()); // "HELLO, WORLD!"
// 转换为小写
console.log(str.toLowerCase()); // "hello, world!"
// 本地化大小写转换
console.log("i".toLocaleUpperCase("tr")); // "İ"(土耳其语)
console.log("I".toLocaleLowerCase("tr")); // "ı"(土耳其语)
trim(), trimStart() 和 trimEnd()
去除字符串开头和结尾的空白字符。
const str = " Hello, World! ";
// 去除两端空白
console.log(str.trim()); // "Hello, World!"
// 去除开头空白(ES2019)
console.log(str.trimStart()); // "Hello, World! "
console.log(str.trimLeft()); // "Hello, World! "(trimStart的别名)
// 去除结尾空白(ES2019)
console.log(str.trimEnd()); // " Hello, World!"
console.log(str.trimRight()); // " Hello, World!"(trimEnd的别名)
padStart() 和 padEnd()
用指定字符填充字符串到指定长度。
const str = "Hello";
// 在开头填充字符(ES2017)
console.log(str.padStart(10)); // " Hello"(默认用空格填充)
console.log(str.padStart(10, "*")); // "*****Hello"
console.log(str.padStart(3)); // "Hello"(如果指定长度小于原字符串长度,则返回原字符串)
// 在结尾填充字符(ES2017)
console.log(str.padEnd(10)); // "Hello "
console.log(str.padEnd(10, "*")); // "Hello*****"
console.log(str.padEnd(3)); // "Hello"
repeat()
重复字符串指定次数。
const str = "Hello";
// 重复字符串(ES6)
console.log(str.repeat(3)); // "HelloHelloHello"
console.log(str.repeat(0)); // ""(空字符串)
console.log(str.repeat(1)); // "Hello"
// 小数会被转换为整数
console.log(str.repeat(2.5)); // "HelloHello"(2.5被转换为2)
// 负数或Infinity会抛出错误
// console.log(str.repeat(-1)); // RangeError
// console.log(str.repeat(Infinity)); // RangeError
replace() 和 replaceAll()
替换字符串中的子字符串。
const str = "Hello, World! Hello, JavaScript!";
// replace() 替换第一个匹配项
console.log(str.replace("Hello", "Hi")); // "Hi, World! Hello, JavaScript!"
// 使用正则表达式替换所有匹配项
console.log(str.replace(/Hello/g, "Hi")); // "Hi, World! Hi, JavaScript!"
// 使用函数作为替换值
console.log(str.replace(/Hello/g, match => match.toUpperCase())); // "HELLO, World! HELLO, JavaScript!"
// replaceAll() 替换所有匹配项(ES2021)
console.log(str.replaceAll("Hello", "Hi")); // "Hi, World! Hi, JavaScript!"
console.log(str.replaceAll(/Hello/g, "Hi")); // "Hi, World! Hi, JavaScript!"
字符串提取方法
slice(), substring() 和 substr()
提取字符串的一部分。
const str = "Hello, World!";
// slice(start, end) 提取从start到end(不包括end)的部分
console.log(str.slice(0, 5)); // "Hello"
console.log(str.slice(7)); // "World!"
console.log(str.slice(-6)); // "World!"(负索引从字符串末尾开始计数)
console.log(str.slice(-6, -1)); // "World"
// substring(start, end) 类似于slice,但不接受负索引
console.log(str.substring(0, 5)); // "Hello"
console.log(str.substring(7)); // "World!"
console.log(str.substring(7, 2)); // "llo, "(如果start > end,则交换参数)
// substr(start, length) 从start开始提取指定长度的字符串(不推荐使用)
console.log(str.substr(0, 5)); // "Hello"
console.log(str.substr(7)); // "World!"
console.log(str.substr(-6, 5)); // "World"(负索引从字符串末尾开始计数)
split()
将字符串分割成数组。
const str = "Hello, World! Hello, JavaScript!";
// 按空格分割
console.log(str.split(" ")); // ["Hello,", "World!", "Hello,", "JavaScript!"]
// 按逗号和空格分割
console.log(str.split(", ")); // ["Hello", "World! Hello", "JavaScript!"]
// 按正则表达式分割
console.log(str.split(/[\s,]+/)); // ["Hello", "World!", "Hello", "JavaScript!"]
// 限制结果数组的长度
console.log(str.split(" ", 2)); // ["Hello,", "World!"]
// 分割成字符数组
console.log("Hello".split("")); // ["H", "e", "l", "l", "o"]
// 不分割,返回整个字符串作为数组的唯一元素
console.log(str.split()); // [str]
字符串连接方法
concat()
连接两个或多个字符串。
const str1 = "Hello";
const str2 = "World";
// 连接字符串
console.log(str1.concat(", ", str2, "!")); // "Hello, World!"
// 通常使用+运算符更简洁
console.log(str1 + ", " + str2 + "!"); // "Hello, World!"
// 或使用模板字符串(ES6)
console.log(`${str1}, ${str2}!`); // "Hello, World!"
ES6及以后的新方法
String.raw()
返回模板字符串的原始字符串形式,不处理转义序列。
// 普通字符串中的转义序列会被处理
console.log("Hello\nWorld"); // 输出两行:"Hello"和"World"
// String.raw不处理转义序列
console.log(String.raw`Hello\nWorld`); // "Hello\nWorld"(输出原始字符串,不处理\n)
// 在模板字符串中使用变量
const name = "JavaScript";
console.log(String.raw`Hello ${name}!`); // "Hello JavaScript!"(变量替换仍然有效)
fromCharCode() 和 fromCodePoint()
从Unicode编码创建字符串。
// String.fromCharCode() 从UTF-16编码创建字符串
console.log(String.fromCharCode(72, 101, 108, 108, 111)); // "Hello"
// String.fromCodePoint() 从Unicode码点创建字符串(ES6)
console.log(String.fromCodePoint(72, 101, 108, 108, 111)); // "Hello"
// 处理高位Unicode字符
console.log(String.fromCharCode(55357, 56842)); // "😊"(使用两个代码单元)
console.log(String.fromCodePoint(128522)); // "😊"(直接使用码点)
字符串模板字面量(ES6)
ES6引入了模板字面量,提供了更强大的字符串创建和操作能力。
const name = "World";
const greeting = "Hello";
// 基本用法
console.log(`${greeting}, ${name}!`); // "Hello, World!"
// 多行字符串
const multiline = `This is a
multiline
string.`;
console.log(multiline);
// 输出:
// This is a
// multiline
// string.
// 表达式插值
console.log(`2 + 3 = ${2 + 3}`); // "2 + 3 = 5"
// 嵌套模板
const nested = `${`Nested ${greeting}`}, ${name}!`;
console.log(nested); // "Nested Hello, World!"
// 带标签的模板字面量
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] || '';
return `${result}${str}<strong>${value}</strong>`;
}, '');
}
const highlighted = highlight`${greeting}, ${name}!`;
console.log(highlighted); // "<strong>Hello</strong>, <strong>World</strong>!"
实际应用示例
1. 字符串格式化
// 格式化数字为货币
function formatCurrency(amount, currency = 'CNY', locale = 'zh-CN') {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(amount);
}
console.log(formatCurrency(1234.56)); // "¥1,234.56"
console.log(formatCurrency(1234.56, 'USD', 'en-US')); // "$1,234.56"
// 格式化日期
function formatDate(date, format = 'long', locale = 'zh-CN') {
const options = format === 'long'
? { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }
: { year: 'numeric', month: '2-digit', day: '2-digit' };
return new Intl.DateTimeFormat(locale, options).format(date);
}
const today = new Date();
console.log(formatDate(today)); // 例如:"2023年5月15日星期一"
console.log(formatDate(today, 'short', 'en-US')); // 例如:"05/15/2023"
2. 字符串验证
// 验证电子邮件地址
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
console.log(isValidEmail("user@example.com")); // true
console.log(isValidEmail("invalid-email")); // false
// 验证密码强度
function checkPasswordStrength(password) {
const hasLowerCase = /[a-z]/.test(password);
const hasUpperCase = /[A-Z]/.test(password);
const hasDigit = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
const isLongEnough = password.length >= 8;
const strength = [hasLowerCase, hasUpperCase, hasDigit, hasSpecialChar, isLongEnough]
.filter(Boolean).length;
return {
score: strength,
isStrong: strength >= 4,
feedback: [
isLongEnough ? null : "密码应至少包含8个字符",
hasLowerCase ? null : "密码应包含小写字母",
hasUpperCase ? null : "密码应包含大写字母",
hasDigit ? null : "密码应包含数字",
hasSpecialChar ? null : "密码应包含特殊字符"
].filter(Boolean)
};
}
console.log(checkPasswordStrength("Passw0rd!"));
// { score: 5, isStrong: true, feedback: [] }
console.log(checkPasswordStrength("password"));
// { score: 1, isStrong: false, feedback: ["密码应包含大写字母", "密码应包含数字", "密码应包含特殊字符"] }
3. 文本处理
// 截断文本并添加省略号
function truncateText(text, maxLength) {
if (text.length <= maxLength) {
return text;
}
return text.slice(0, maxLength - 3) + '...';
}
const longText = "这是一段很长的文本,需要被截断以适应显示区域。";
console.log(truncateText(longText, 15)); // "这是一段很长的文本..."
// 高亮搜索关键词
function highlightKeywords(text, keywords) {
let result = text;
keywords.forEach(keyword => {
const regex = new RegExp(`(${keyword})`, 'gi');
result = result.replace(regex, '<mark>$1</mark>');
});
return result;
}
const article = "JavaScript是一种广泛使用的编程语言,常用于Web开发。";
console.log(highlightKeywords(article, ["JavaScript", "Web"]));
// 将驼峰命名转换为短横线命名
function camelToKebab(str) {
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
}
console.log(camelToKebab('backgroundColor')); // "background-color"
console.log(camelToKebab('userProfileData')); // "user-profile-data"
4. 国际化和本地化
// 字符串本地化比较
function localCompare(strings, locale = 'zh-CN') {
return [...strings].sort((a, b) => a.localeCompare(b, locale));
}
// 中文排序
console.log(localCompare(['张三', '李四', '王五'])); // ["李四", "王五", "张三"]
// 德语排序(处理特殊字符)
console.log(localCompare(['Österreich', 'Andorra', 'Zypern'], 'de-DE'));
// ["Andorra", "Österreich", "Zypern"]
// 字符串本地化格式化
function formatWithPluralRules(count, forms, locale = 'zh-CN') {
const pluralRules = new Intl.PluralRules(locale);
const rule = pluralRules.select(count);
return forms[rule] || forms.other;
}
// 英语复数规则
const enForms = {
one: 'You have 1 message.',
other: 'You have # messages.'
};
console.log(formatWithPluralRules(1, enForms, 'en-US')); // "You have 1 message."
console.log(formatWithPluralRules(5, enForms, 'en-US').replace('#', 5)); // "You have 5 messages."
// 中文数量表达
const zhForms = {
other: '你有 # 条消息。'
};
console.log(formatWithPluralRules(1, zhForms).replace('#', 1)); // "你有 1 条消息。"
console.log(formatWithPluralRules(5, zhForms).replace('#', 5)); // "你有 5 条消息。"
5. URL处理
// 解析URL参数
function parseURLParams(url) {
const params = {};
const queryString = url.split('?')[1];
if (!queryString) return params;
queryString.split('&').forEach(param => {
const [key, value] = param.split('=');
params[decodeURIComponent(key)] = decodeURIComponent(value || '');
});
return params;
}
const url = 'https://example.com/search?query=JavaScript&page=1&limit=10';
console.log(parseURLParams(url));
// { query: "JavaScript", page: "1", limit: "10" }
// 构建URL参数
function buildURLParams(params) {
return Object.entries(params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
}
const searchParams = {
query: 'JavaScript 教程',
page: '1',
tags: 'beginner,tutorial'
};
console.log(buildURLParams(searchParams));
// "query=JavaScript%20%E6%95%99%E7%A8%8B&page=1&tags=beginner%2Ctutorial"
字符串性能优化
1. 字符串拼接
// 不推荐:在循环中使用+运算符拼接字符串
function slowConcatenation(n) {
let result = '';
for (let i = 0; i < n; i++) {
result += i; // 每次迭代都创建新字符串
}
return result;
}
// 推荐:使用数组和join方法
function fastConcatenation(n) {
const parts = [];
for (let i = 0; i < n; i++) {
parts.push(i);
}
return parts.join('');
}
// 或使用Array.from
function fastConcatenation2(n) {
return Array.from({ length: n }, (_, i) => i).join('');
}
// 性能测试
console.time('slow');
slowConcatenation(10000);
console.timeEnd('slow');
console.time('fast');
fastConcatenation(10000);
console.timeEnd('fast');
2. 正则表达式优化
// 不推荐:在循环中重复创建正则表达式
function slowRegex(strings) {
return strings.map(str => {
return str.replace(/[aeiou]/gi, '*'); // 每次迭代都创建新正则表达式
});
}
// 推荐:在循环外创建正则表达式
function fastRegex(strings) {
const regex = /[aeiou]/gi;
return strings.map(str => {
return str.replace(regex, '*');
});
}
// 使用正则表达式的y标志(粘性匹配)提高性能
function countOccurrences(text, pattern) {
const regex = new RegExp(pattern, 'gy');
let count = 0;
let match;
while (match = regex.exec(text)) {
count++;
}
return count;
}
console.log(countOccurrences('banana', 'a')); // 3
常见错误和最佳实践
常见错误
忽略字符串的不可变性:
// 错误 let str = "Hello"; str[0] = "J"; // 无效操作,字符串是不可变的 console.log(str); // 仍然是 "Hello" // 正确 let str = "Hello"; str = "J" + str.slice(1); // 创建新字符串 console.log(str); // "Jello"
使用==比较字符串:
// 不推荐 if ("10" == 10) { // true,因为会进行类型转换 // ... } // 推荐 if ("10" === 10) { // false,严格比较不会进行类型转换 // ... }
忽略Unicode字符的特殊处理:
// 错误 const emoji = "😊"; console.log(emoji.length); // 2(而不是1) console.log(emoji[0]); // 不是完整的表情符号 // 正确 const emoji = "😊"; console.log([...emoji].length); // 1 console.log(emoji.codePointAt(0)); // 128522
最佳实践
使用模板字符串提高可读性:
// 不推荐 const message = "Hello, " + name + "! You have " + count + " new messages."; // 推荐 const message = `Hello, ${name}! You have ${count} new messages.`;
使用适当的字符串方法:
// 不推荐 const hasPrefix = str.indexOf("prefix") === 0; // 推荐 const hasPrefix = str.startsWith("prefix");
处理国际化和本地化:
// 不推荐 const sorted = names.sort(); // 可能不符合特定语言的排序规则 // 推荐 const sorted = names.sort((a, b) => a.localeCompare(b, 'zh-CN'));
使用String.raw处理特殊字符串:
// 不推荐 const regex = "\\d+\\s\\w+"; // 需要双反斜杠 // 推荐 const regex = String.raw`\d+\s\w+`; // 更清晰
避免不必要的字符串转换:
// 不推荐 const num = 42; const str = String(num); // 不必要的转换 console.log("Value: " + str); // 推荐 const num = 42; console.log(`Value: ${num}`); // 模板字符串会自动处理转换
总结
JavaScript的String对象提供了丰富的方法来处理字符串:
- 访问和检查:charAt(), charCodeAt(), codePointAt(), length
- 查找和匹配:indexOf(), lastIndexOf(), includes(), startsWith(), endsWith(), search(), match(), matchAll()
- 修改和转换:toUpperCase(), toLowerCase(), trim(), padStart(), padEnd(), repeat(), replace(), replaceAll()
- 提取和分割:slice(), substring(), substr(), split()
- 连接:concat()
- ES6及以后的新方法:String.raw(), fromCodePoint(), 模板字符串
在实际应用中,这些方法可以用于字符串格式化、验证、文本处理、国际化和URL处理等多种场景。
处理字符串时,需要注意字符串的不可变性、Unicode字符的特殊处理以及性能优化策略。
练习
编写一个函数,将驼峰命名法转换为下划线命名法(例如:
userName
→user_name
)。实现一个模板引擎函数,可以替换字符串中的占位符(例如:
"Hello, {name}!"
→"Hello, John!"
)。创建一个函数,可以检测回文字符串(正读和反读都相同的字符串,如"level")。
编写一个函数,从URL中提取域名部分。
实现一个函数,可以对字符串中的敏感信息(如电话号码、邮箱)进行脱敏处理。