Skip to content

Latest commit

 

History

History
158 lines (95 loc) · 6.18 KB

firstStrToUppercase.md

File metadata and controls

158 lines (95 loc) · 6.18 KB

问题:JavaScript 中如使字符串的第一个字母大写?

如何让一个字符串的第一个字母大写,但不改变其他字母的大小写?

示例:


- "this is a test" → "This is a test"
- "the Eiffel Tower" → "The Eiffel Tower"
- "/index.html" → "/index.html"

答案

基本解决方案是:

function capitalizeFirstLetter(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

console.log(capitalizeFirstLetter('foo')); // Foo

其他一些答案修改了 String.prototype(这个答案以前也是如此),但我建议不要这样做,因为可维护性的问题(如果其他代码添加相同名称的本地函数,可能会导致代码冲突)。

如果你想用 Unicode 代码点而不是代码单元来工作,你可以使用 String#[@iterator],在通过 toLocaleUpperCase 来获得当前正确的大写字母。

const capitalizeFirstLetter = ([ first, ...rest ], locale = navigator.language) =>
  first.toLocaleUpperCase(locale) + rest.join('')

console.log(
  capitalizeFirstLetter('foo'), // Foo
  capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉"), // "𐐎𐐲𐑌𐐼𐐲𐑉" (correct!)
  capitalizeFirstLetter("italya", 'tr') // İtalya" (correct in Turkish Latin!)
)

更多国际化方案选择,请参阅下面的其他答案。

其他答案

大多数建议的答案如下所示:

function capitalizeFirstLetter(str) {
  return str[0].toUpperCase() + str.slice(1);
}

但是,一些大小写字符不在 BMP基本多文种平面,代码点 U+0U+FFFF)之外。例如,以这个 西班牙语 文本为例:

capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉"); // "𐐶𐐲𐑌𐐼𐐲𐑉"

这里的第一个字符没有大写,因为字符串的数组索引属性不访问 characters代码单元*。他们访问 UTF-16 代码单元。切片时也是如此——索引值指向代码单元。

'𐐶𐐲𐑌𐐼𐐲𐑉'[0] // '\uD801'

碰巧 UTF-16 代码单位就是 1:1USV 代码单位在两个范围内:U+0U+D7FFU+E000U+FFFF 。大多数大小写字符都在这两个范围内,但并不是全部的。

ES2015 开始,处理这个变得更容易了。 String.prototype[@@iterator] 方法返回一个新的Iterator 对象,它遍历字符串的代码点,返回每一个代码点的字符串值。

function capitalizeFirstLetter([ first, ...rest ]) {
  console.log(first)
  return [ first.toUpperCase(), ...rest ].join('');
}

capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // 𐐶  and '𐐎𐐲𐑌𐐼𐐲𐑉'

对于较长的字符串,这可能不是非常有效的。因为我们并不真的需要对剩余部分进行迭代。我们可以使用String.prototype.codePointAt 来获得第一个(可能的)字母,但是我们仍然需要确定切片应该从哪里开始。避免迭代余数的一个方法是测试第一个代码点是否在 BMP 之外;如果不是,分片从 1 开始,如果是,分片从 2 开始。

function capitalizeFirstLetter(str) {
  const firstCP = str.codePointAt(0);
  const index = firstCP > 0xFFFF ? 2 : 1;

  return String.fromCodePoint(firstCP).toUpperCase() + str.slice(index);
}

capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"

在开始的时候,我提到了国际化的考虑。其中有些是很难解释的,因为它们不仅需要了解使用的是什么语言,而且还可能需要了解该语言中的单词的具体知识。

例如,爱尔兰的数字符号 mb 在一个词的开头大写为 mB

另一个例子,德语的 eszett,据说从来不会放在单词的开头。小写的 eszett ß,大写为 SS,但 SS 可以小写为 ßss

你需要有德语的语言知识才能知道哪个才是正确的。

这类问题最著名的例子可能是土耳其语。在土耳其拉丁语中,i 的大写形式是 İ,而 I 的小写形式是 ı

它们是两个不同的字母。幸运的是,我们确实有办法解决这个问题:

function capitalizeFirstLetter([ first, ...rest ], locale) {
  return [ first.toLocaleUpperCase(locale), ...rest ].join('');
}

capitalizeFirstLetter("italy", "en") // "Italy"
capitalizeFirstLetter("italya", "tr") // "İtalya"

在浏览器中,用户最常用的语言标签由 navigator.language 标明。

navigator.languages 中则也可以找到按优先顺序排列的语言标签列表。

navigator.language // 'zh-CN'
navigator.languages // ['zh-CN', 'zh', 'en']

ES2018 中引入的 RegExp 中支持 Unicode 属性字符类的代理中,我们可以通过直接表达我们感兴趣的字符来进一步优化代码:

function capitalizeFirstLetter(str, locale=navigator.language) {
  return str.replace(/^\p{CWU}/u, char => char.toLocaleUpperCase(locale));
}

优化过后,现在就能够比较精准地处理字符串中多个单词的大写。CWUChanges_When_Uppercased 字符属性与所有代码点相匹配,这些代码点在大写时将会发生变化。

我们可以用诸如荷兰语 ij 这样的大写数字字符来试试。

capitalizeFirstLetter('ijsselmeer'); // "IJsselmeer"

最后

在大多数情况下,问这个问题的人都不会关心 Deseret 的大写字母或国际化问题。但是,能搞提前意识到这些问题是很好的,因为你不知道什么时候就会遇到这些问题。它们并不是 边缘 案例,或者说,它们不是定义上的边缘案例,毕竟有一整个国家的大多数人都说土耳其语。

将代码单元与代码点混淆是一个相当常见的错误来源(特别是在表情符号方面),字符串和语言都是相当复杂的。

问题来源:https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript/53930826