使用JavaScript将字符串转换为标题大小写

779

有没有一种简单的方法将字符串转换为标题格式?例如,john smith 变成 John Smith。我不想要像John Resig的解决方案那样复杂的东西,只是(希望)一种一两行代码就能实现的方法。


1
有许多方法,我们有一些性能统计数据吗? - theAnubhav
1
@theAnubhav 是的,我们现在有一个基准 - Ulysse BN
2
到了2022年,浏览器仍然没有本地功能来执行此操作。 - Sơn Trần-Nguyễn
1
这种大小写格式完全取决于语言/地点/文化。 - James Moore
我希望解决方案的一个测试用例是“Comhrá i mBÁC le Seán Nguyen” - 祝你好运!基本上,计算机可以执行称为“标题大小写”的操作的想法可能是没有希望的,即使给定了大量的机器学习资源。 - James Moore
69个回答

21

没有使用正则表达式,仅供参考:

String.prototype.toProperCase = function() {
  var words = this.split(' ');
  var results = [];
  for (var i = 0; i < words.length; i++) {
    var letter = words[i].charAt(0).toUpperCase();
    results.push(letter + words[i].slice(1));
  }
  return results.join(' ');
};

console.log(
  'john smith'.toProperCase()
)


感谢您提供的无需正则表达式的解决方案! - lucifer63
这在‘džungla’上不起作用。 - user3840170

18

如果您担心那些填充词语,您可以告诉函数不要大写它们。

/**
 * @param String str The text to be converted to titleCase.
 * @param Array glue the words to leave in lowercase. 
 */
var titleCase = function(str, glue){
    glue = (glue) ? glue : ['of', 'for', 'and'];
    return str.replace(/(\w)(\w*)/g, function(_, i, r){
        var j = i.toUpperCase() + (r != null ? r : "");
        return (glue.indexOf(j.toLowerCase())<0)?j:j.toLowerCase();
    });
};

希望这对你有所帮助。

编辑

如果你想处理前导词,可以用一个额外的变量来跟踪:

var titleCase = function(str, glue){
    glue = !!glue ? glue : ['of', 'for', 'and', 'a'];
    var first = true;
    return str.replace(/(\w)(\w*)/g, function(_, i, r) {
        var j = i.toUpperCase() + (r != null ? r : '').toLowerCase();
        var result = ((glue.indexOf(j.toLowerCase()) < 0) || first) ? j : j.toLowerCase();
        first = false;
        return result;
    });
};

2
你可以将一个字符串分解成一个数组。因此,我们可以有葡萄牙语、西班牙语、意大利语和法语的介词:glue ='de|da|del|dos|do|das|des|la|della|delli'.split('|'); - Junior Mayhé
这并不保证第一个单词大写;例如,“and another thing”会变成“and Another Thing”。只需要一种优雅的方法来始终大写第一个单词。 - Brad Koch
@BradKoch - 使用空格填充,这样您就可以使用“和”、“的”等作为搜索词,然后“And Another And Another”将替换为“And Another and Another”。 - Yimin Rong
除了它会将“'”和“-”后面的文本(如H'Dy或Number-One)也大写之外,它很好。 - luky
你可以使用回退(fallback)而不是三元运算符:glue = glue || ['of', 'for', 'and', 'a']; - tm2josep
@tm2josep 是的,我回答这个问题时是按照Crockford推荐的方式编写的 :-) 今天已经过去了6年,但我仍然更喜欢显式地强制转换类型。 - fncomp

17

如果您需要一份语法正确的答案:

这个答案考虑到介词,如“of”,“from”等,会生成一个你在论文中期望看到的编辑风格标题。

toTitleCase 函数

该函数考虑到语法规则(在此列出)。该函数还合并空白并删除特殊字符(根据您的需求修改正则表达式)。

const toTitleCase = (str) => {
  const articles = ['a', 'an', 'the'];
  const conjunctions = ['for', 'and', 'nor', 'but', 'or', 'yet', 'so'];
  const prepositions = [
    'with', 'at', 'from', 'into','upon', 'of', 'to', 'in', 'for',
    'on', 'by', 'like', 'over', 'plus', 'but', 'up', 'down', 'off', 'near'
  ];

  // The list of spacial characters can be tweaked here
  const replaceCharsWithSpace = (str) => str.replace(/[^0-9a-z&/\\]/gi, ' ').replace(/(\s\s+)/gi, ' ');
  const capitalizeFirstLetter = (str) => str.charAt(0).toUpperCase() + str.substr(1);
  const normalizeStr = (str) => str.toLowerCase().trim();
  const shouldCapitalize = (word, fullWordList, posWithinStr) => {
    if ((posWithinStr == 0) || (posWithinStr == fullWordList.length - 1)) {
      return true;
    }

    return !(articles.includes(word) || conjunctions.includes(word) || prepositions.includes(word));
  }

  str = replaceCharsWithSpace(str);
  str = normalizeStr(str);

  let words = str.split(' ');
  if (words.length <= 2) { // Strings less than 3 words long should always have first words capitalized
    words = words.map(w => capitalizeFirstLetter(w));
  }
  else {
    for (let i = 0; i < words.length; i++) {
      words[i] = (shouldCapitalize(words[i], words, i) ? capitalizeFirstLetter(words[i], words, i) : words[i]);
    }
  }

  return words.join(' ');
}

单元测试确保正确性

import { expect } from 'chai';
import { toTitleCase } from '../../src/lib/stringHelper';

describe('toTitleCase', () => {
  it('Capitalizes first letter of each word irrespective of articles, conjunctions or prepositions if string is no greater than two words long', function(){
    expect(toTitleCase('the dog')).to.equal('The Dog'); // Capitalize articles when only two words long
    expect(toTitleCase('for all')).to.equal('For All'); // Capitalize conjunctions when only two words long
    expect(toTitleCase('with cats')).to.equal('With Cats'); // Capitalize prepositions when only two words long
  });

  it('Always capitalize first and last words in a string irrespective of articles, conjunctions or prepositions', function(){
    expect(toTitleCase('the beautiful dog')).to.equal('The Beautiful Dog');
    expect(toTitleCase('for all the deadly ninjas, be it so')).to.equal('For All the Deadly Ninjas Be It So');
    expect(toTitleCase('with cats and dogs we are near')).to.equal('With Cats and Dogs We Are Near');
  });

  it('Replace special characters with space', function(){
    expect(toTitleCase('[wolves & lions]: be careful')).to.equal('Wolves & Lions Be Careful');
    expect(toTitleCase('wolves & lions, be careful')).to.equal('Wolves & Lions Be Careful');
  });

  it('Trim whitespace at beginning and end', function(){
    expect(toTitleCase(' mario & Luigi superstar saga ')).to.equal('Mario & Luigi Superstar Saga');
  });

  it('articles, conjunctions and prepositions should not be capitalized in strings of 3+ words', function(){
    expect(toTitleCase('The wolf and the lion: a tale of two like animals')).to.equal('The Wolf and the Lion a Tale of Two like Animals');
    expect(toTitleCase('the  three Musketeers  And plus ')).to.equal('The Three Musketeers and Plus');
  });
});
请注意,我从所提供的字符串中删除了相当多的特殊字符。您需要调整正则表达式以满足您项目的要求。

我更喜欢这个解决方案,因为它真正考虑了标题大小写。它不是“Gone With The Wind”,而是“Gone with the Wind”。 - russellmania
1
这应该是被接受的答案。你甚至写了测试!参考:https://www.titlecase.com https://danielmiessler.com/blog/a-list-of-different-case-types/ https://github.com/gouch/to-title-case - ManInTheArena
那是打错字了吗?你传递了3个参数给capitalizeFirstLetter,但它只期望1个参数。我猜JavaScript不在意,但TypeScript会因此出问题。 - AndrewBenjamin
很好的回答,帮了很大的忙。 - Biplov Kumar
这在 'džungla' 上不起作用。 - user3840170

14

如果上面的解决方案中使用的正则表达式让你感到困惑,试试这段代码:

function titleCase(str) {
  return str.split(' ').map(function(val){ 
    return val.charAt(0).toUpperCase() + val.substr(1).toLowerCase();
  }).join(' ');
}

喜欢它!不要转换为数组。 - neelmeg
1
嗯... split确实将其转换为数组,只是因为map意味着您不必使用[]符号,所以您不会像明显地看到它。 - MalcolmOcean
3
这与2年前a8m的回答相同。 - Michael
不完全相同;这里使用旧式的 function 而不是新式的 => 语法来表示映射函数。 - AlexChaffee
1
单字符输入的中断。 - Madbreaks
1
@AlexChaffee请查看修订历史。a8m在原始答案中没有使用箭头函数。 - Mulan

13

我创建了这个函数,可以处理姓氏(所以不是标题大小写)例如“McDonald”或“MacDonald”或“O'Toole”或“D'Orazio”。但它并不能处理德语或荷兰语中通常用小写书写的“van”或“von”等姓氏... 我相信“de”也经常用小写表示,比如“Robert de Niro”。这些仍需要解决。

function toProperCase(s)
{
  return s.toLowerCase().replace( /\b((m)(a?c))?(\w)/g,
          function($1, $2, $3, $4, $5) { if($2){return $3.toUpperCase()+$4+$5.toUpperCase();} return $1.toUpperCase(); });
}

1
对名称敏感,但不能正确处理“macy”。 - brianary
这是唯一一个正确处理大小写转换并注意到像“美属维尔京群岛”这样的缩写的函数。 - Rodrigo Polo
你可以通过在其中放置负向先行断言来解决 macy 问题,因此 \b((m)(a?c))?(\w) 变成了 \b((m)(a?c))?(\w)(?!\s) - Ste

11

如果您可以在代码中使用第三方库,那么lodash为我们提供了一个助手函数。

https://lodash.com/docs/4.17.3#startCase

_.startCase('foo bar');
// => 'Foo Bar'

_.startCase('--foo-bar--');
// => 'Foo Bar'
 
_.startCase('fooBar');
// => 'Foo Bar'
 
_.startCase('__FOO_BAR__');
// => 'FOO BAR'


11

首先,通过使用空格拆分字符串将其转换为数组:

var words = str.split(' ');

然后使用array.map创建一个新数组,其中包含首字母大写的单词。

var capitalized = words.map(function(word) {
    return word.charAt(0).toUpperCase() + word.substring(1, word.length);
});

然后使用join函数将新数组按空格连接:

capitalized.join(" ");

function titleCase(str) {
  str = str.toLowerCase(); //ensure the HeLlo will become Hello at the end
  var words = str.split(" ");

  var capitalized = words.map(function(word) {
    return word.charAt(0).toUpperCase() + word.substring(1, word.length);
  });
  return capitalized.join(" ");
}

console.log(titleCase("I'm a little tea pot"));

注意:

这种方法有一个缺点,它只会将每个单词的首字母大写。在这里,“单词”指的是由空格分隔的每个字符串。

假设您有以下字符串:

str = "I'm a little/small tea pot";

使用此方法会产生:

I'm A Little/small Tea Pot

与期望的结果不符:

I'm A Little/Small Tea Pot

在这种情况下,可以使用正则表达式和.replace方法来解决问题:

使用ES6语法:

const capitalize = str => str.length
  ? str[0].toUpperCase() +
    str.slice(1).toLowerCase()
  : '';

const escape = str => str.replace(/./g, c => `\\${c}`);
const titleCase = (sentence, seps = ' _-/') => {
  let wordPattern = new RegExp(`[^${escape(seps)}]+`, 'g');
  
  return sentence.replace(wordPattern, capitalize);
};
console.log( titleCase("I'm a little/small tea pot.") );

或者不使用ES6

function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.substring(1, str.length).toLowerCase();
}

function titleCase(str) {
  return str.replace(/[^\ \/\-\_]+/g, capitalize);
}

console.log(titleCase("I'm a little/small tea pot."));


这在‘džungla’上不起作用。 - user3840170
@user3840170,你能详细说明一下吗?哪一个不起作用?‘džungla’是什么? - undefined

10

ES 6

str.split(' ')
   .map(s => s.slice(0, 1).toUpperCase() + s.slice(1).toLowerCase())
   .join(' ')

否则

str.split(' ').map(function (s) {
    return s.slice(0, 1).toUpperCase() + s.slice(1).toLowerCase();
}).join(' ')

提醒一下,如果你还想要那个单词的第一个字母大写,应该使用s.slice(0, 1).toUpperCase() - WindsofTime
@jssridhar,你还应该将代码改为ES6。 - caiosm1005
1
如果“str”是单个字符,则中断。 - Madbreaks
@jssridhar 可能更好地使用 .charAt(0).toUpperCase() - roydukkey
3
重复a8m的答案 - Mulan
这在‘džungla’上不起作用。 - user3840170

9
大多数答案似乎忽略了使用单词边界元字符(\b)的可能性。以下是 Greg Dean 的答案的缩短版本,利用了它:
function toTitleCase(str)
{
    return str.replace(/\b\w/g, function (txt) { return txt.toUpperCase(); });
}

对于像Jim-Bob这样的连字符姓名也适用。


2
这是一个优雅的部分解决方案,但无法处理带有重音或大写字母的字符串。例如,我输入"Sofía Vergara" => "SofíA Vergara" 或者 "Sofía VERGARA" => "SofíA VERGARA"。第二种情况可以通过在.replace(...)之前应用.toLowerCase()函数来解决。而第一种情况需要找到正确的正则表达式。 - Asereware
2
嗯,这似乎是正则表达式实现中的一个错误,我认为带重音符号的字符应该被视为单词字符(不过你说得对,目前它不能处理这些情况)。 - lewax00
\w 只包括 [A-Za-z0-9_] 这些字符,而不是所有字母。如果需要使用 Unicode 类别 \p{L},则需要使用 /u 修饰符(参见这里)。对于 \b,需要使用不同的解决方案,它只在 \W\w 之间起作用(参见这里)。 - cmbuckley

8

尝试一下这个,最简单的方法:

str.replace(/(^[a-z])|(\s+[a-z])/g, txt => txt.toUpperCase());

这很不错。对于我所拥有的基本用例(其中性能并不一定是优先考虑的因素),这非常完美! - KhoPhi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接