1. 字符串截取的基础认知
字符串操作是编程中最基础也最频繁使用的功能之一。作为前端开发者,我每天都要处理各种字符串截取需求——从用户输入校验到动态内容展示,再到数据格式化输出。substr()方法就像是一把精准的瑞士军刀,能帮我们快速提取字符串的任意部分。
这个方法最早出现在JavaScript 1.0中,至今仍是ECMAScript标准的一部分。虽然ES6引入了更现代的substring()和slice(),但substr()凭借其直观的参数设计,在遗留代码和特定场景中依然保持着旺盛的生命力。特别是在需要从指定位置向后截取固定长度字符时,substr()的语义化表达往往更胜一筹。
注意:虽然现代浏览器都支持substr(),但MDN文档已将其标记为"遗留功能"。新项目建议优先考虑substring()或slice(),但理解substr()对维护老代码至关重要。
2. 方法参数深度解析
2.1 核心参数说明
substr()方法接收两个参数:
javascript复制str.substr(start[, length])
-
start:截取起始位置(必需)
- 正数:从字符串开头计算的索引(0-based)
- 负数:从字符串末尾计算的索引(-1表示最后一个字符)
- 超出范围:自动规整到有效范围(大于长度视为length,小于-length视为0)
-
length:截取字符数(可选)
- 省略时截取到字符串末尾
- 为0或负数时返回空字符串
- 超过可截取长度时自动截断
2.2 参数组合效果实测
通过几个典型例子展示不同参数组合的效果:
javascript复制const sample = "JavaScript";
console.log(sample.substr(4)); // "Script"(从索引4到末尾)
console.log(sample.substr(4, 3)); // "Scr"(从索引4开始3个字符)
console.log(sample.substr(-6)); // "Script"(倒数第6个到末尾)
console.log(sample.substr(-6, 2)); // "Sc"(倒数第6个开始的2个字符)
console.log(sample.substr(20)); // ""(起始位置超出范围)
console.log(sample.substr(4, 20)); // "Script"(长度超出自动截断)
3. 与同类方法的横向对比
3.1 substring()方法差异
javascript复制// 参数语义不同
"JavaScript".substr(4, 3); // "Scr"(从4开始3个字符)
"JavaScript".substring(4, 7); // "Scr"(从4到7的区间)
// 负值处理不同
"JavaScript".substr(-3); // "ipt"(支持负索引)
"JavaScript".substring(-3); // "JavaScript"(负值视为0)
// 参数交换逻辑
"JavaScript".substring(7, 4); // "Scr"(自动交换参数)
3.2 slice()方法差异
javascript复制// 负索引行为一致
"JavaScript".slice(-3); // "ipt"
"JavaScript".substr(-3); // "ipt"
// 参数语义差异
"JavaScript".slice(4, 7); // "Scr"(从4到7的区间)
"JavaScript".substr(4, 3); // "Scr"(从4开始3个字符)
实用建议:需要区间选择时用slice/substring,需要指定长度时用substr更直观。
4. 实战应用场景解析
4.1 文件扩展名提取
javascript复制function getExtension(filename) {
// 处理无点号情况
const lastDot = filename.lastIndexOf('.');
return lastDot === -1 ? '' : filename.substr(lastDot + 1);
}
console.log(getExtension("archive.tar.gz")); // "gz"
console.log(getExtension("config")); // ""
4.2 手机号中间四位脱敏
javascript复制function maskPhone(phone) {
return phone.substr(0, 3) + '****' + phone.substr(7);
}
console.log(maskPhone("13812345678")); // "138****5678"
4.3 长文本预览生成
javascript复制function generatePreview(text, maxLength = 100) {
if (text.length <= maxLength) return text;
return text.substr(0, maxLength - 3) + '...';
}
const longText = "这是一段超过指定长度的文本内容,需要被截断显示...";
console.log(generatePreview(longText, 10)); // "这是一段超..."
5. 性能优化与边缘情况
5.1 性能实测对比
通过百万次操作测试(Node.js v16):
javascript复制const str = "a".repeat(1000);
console.time('substr');
for(let i=0; i<1e6; i++) str.substr(500, 100);
console.timeEnd('substr'); // ~120ms
console.time('slice');
for(let i=0; i<1e6; i++) str.slice(500, 600);
console.timeEnd('slice'); // ~80ms
结果显示slice性能略优,但对于大多数应用场景差异可忽略。
5.2 多字节字符处理
javascript复制const emojiText = "你好👍世界";
console.log(emojiText.substr(2, 2)); // "👍"(正确)
console.log(emojiText.substring(2,4)); // "��"(乱码)
重要提示:处理含emoji或多字节字符时,substr按码元截取可能更符合预期,但最佳实践是使用Array.from(str).slice()。
6. 现代JavaScript的替代方案
6.1 扩展运算符+数组方法
javascript复制const str = "JavaScript";
[...str].slice(4, 7).join(''); // "Scr"
6.2 String.prototype.at()
javascript复制"JavaScript".at(4); // "S"(替代charAt)
6.3 正则表达式提取
javascript复制"ID:12345".match(/\d+/)[0]; // "12345"
7. 类型安全与错误处理
7.1 参数类型转换规则
javascript复制"012345".substr(true, "2"); // "12"(true→1, "2"→2)
"012345".substr(NaN, 2); // "01"(NaN→0)
7.2 防御性编程实践
javascript复制function safeSubstr(str, start, length) {
if (typeof str !== 'string') {
str = String(str ?? '');
}
start = Math.trunc(Number(start)) || 0;
length = length === undefined ? undefined : Math.trunc(Number(length));
return str.substr(start, length);
}
console.log(safeSubstr(null, 2, 3)); // "ull"
console.log(safeSubstr(12345, '1', 2)); // "23"
8. 浏览器兼容性现状
虽然substr()仍被所有现代浏览器支持,但需要注意:
- IE8及以下版本对负start参数的处理有偏差
- 移动端浏览器无特殊差异
- TypeScript默认包含类型定义
- ESLint可能提示弃用警告(可通过规则配置关闭)
9. 代码可读性优化技巧
9.1 语义化变量命名
javascript复制// 不良实践
const s = str.substr(5, 3);
// 良好实践
const countryCode = phoneNumber.substr(0, 3);
9.2 配合解构使用
javascript复制const [year, month] = "2023-07".split('-');
const monthAbbr = month.substr(0, 3).toUpperCase(); // "JUL"
9.3 链式调用优化
javascript复制// 传统写法
const result = str.trim().substr(0, 50).toLowerCase();
// 可读性更好的写法
const trimmed = str.trim();
const shortened = trimmed.substr(0, 50);
const result = shortened.toLowerCase();
10. 单元测试要点
编写测试用例时应覆盖:
javascript复制describe('substr操作', () => {
test('正常截取', () => {
expect("abcde".substr(1, 3)).toBe("bcd");
});
test('负起始位置', () => {
expect("abcde".substr(-2)).toBe("de");
});
test('长度超限', () => {
expect("abc".substr(1, 10)).toBe("bc");
});
test('空字符串处理', () => {
expect("".substr(0)).toBe("");
});
});
