JavaScript系列 -- String 详解

451 阅读17分钟

String 概述

定义

字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。

'abc'
"abc"

单引号字符串的内部,可以使用双引号。双引号字符串的内部,可以使用单引号。

'key = "value"'
"It's a long journey"

上面两个都是合法的字符串。

如果要在单引号字符串的内部,使用单引号,就必须在内部的单引号前面加上反斜杠,用来转义。双引号字符串内部使用双引号,也是如此。

'Did she say 'Hello'?'
// "Did she say 'Hello'?"

"Did she say "Hello"?"
// "Did she say "Hello"?"

由于 HTML 语言的属性值使用双引号,所以很多项目约定 JavaScript 语言的字符串只使用单引号,本教程遵守这个约定。当然,只使用双引号也完全可以。重要的是坚持使用一种风格,不要一会使用单引号表示字符串,一会又使用双引号表示。

字符串默认只能写在一行内,分成多行将会报错。

'a
b
c'
// SyntaxError: Unexpected token ILLEGAL

上面代码将一个字符串分成三行,JavaScript 就会报错。

如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。

var longString = 'Long \
long \
long \
string';

longString
// "Long long long string"

上面代码表示,加了反斜杠以后,原来写在一行的字符串,可以分成多行书写。但是,输出的时候还是单行,效果与写在同一行完全一样。注意,反斜杠的后面必须是换行符,而不能有其他字符(比如空格),否则会报错。

连接运算符(+)可以连接多个单行字符串,将长字符串拆成多行书写,输出的时候也是单行。

var longString = 'Long '
  + 'long '
  + 'long '
  + 'string';

如果想输出多行字符串,有一种利用多行注释的变通方法。

(function () { /*
line 1
line 2
line 3
*/}).toString().split('\n').slice(1, -1).join('\n')
// "line 1
// line 2
// line 3"

上面的例子中,输出的字符串就是多行。

转义

反斜杠(\)在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符。

需要用反斜杠转义的特殊字符,主要有下面这些。

  • \0 :null(\u0000
  • \b :后退键(\u0008
  • \f :换页符(\u000C
  • \n :换行符(\u000A
  • \r :回车键(\u000D
  • \t :制表符(\u0009
  • \v :垂直制表符(\u000B
  • ' :单引号(\u0027
  • " :双引号(\u0022
  • \ :反斜杠(\u005C

上面这些字符前面加上反斜杠,都表示特殊含义。

console.log('1\n2')
// 1
// 2

上面代码中,\n表示换行,输出的时候就分成了两行。

反斜杠还有三种特殊用法。

(1)\HHH

反斜杠后面紧跟三个八进制数(000377),代表一个字符。HHH对应该字符的 Unicode 码点,比如\251表示版权符号。显然,这种方法只能输出256种字符。

(2)\xHH

\x后面紧跟两个十六进制数(00FF),代表一个字符。HH对应该字符的 Unicode 码点,比如\xA9表示版权符号。这种方法也只能输出256种字符。

(3)\uXXXX

\u后面紧跟四个十六进制数(0000FFFF),代表一个字符。XXXX对应该字符的 Unicode 码点,比如\u00A9表示版权符号。

下面是这三种字符特殊写法的例子。

'\251' // "©"
'\xA9' // "©"
'\u00A9' // "©"

'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true

如果在非特殊字符前面使用反斜杠,则反斜杠会被省略。

'\a'
// "a"

上面代码中,a是一个正常字符,前面加反斜杠没有特殊含义,反斜杠会被自动省略。

如果字符串的正常内容之中,需要包含反斜杠,则反斜杠前面需要再加一个反斜杠,用来对自身转义。

"Prev \ Next"
// "Prev \ Next"

字符串与数组

字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。

var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"

// 直接对字符串使用方括号运算符
'hello'[1] // "e"

如果方括号中的数字超过字符串的长度,或者方括号中根本不是数字,则返回undefined

'abc'[3] // undefined
'abc'[-1] // undefined
'abc'['x'] // undefined

但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。

var s = 'hello';

delete s[0];
s // "hello"

s[1] = 'a';
s // "hello"

s[5] = '!';
s // "hello"

上面代码表示,字符串内部的单个字符无法改变和增删,这些操作会默默地失败。

length 属性

length属性返回字符串的长度,该属性也是无法改变的。

var s = 'hello';
s.length // 5

s.length = 3;
s.length // 5

s.length = 7;
s.length // 5

上面代码表示字符串的length属性无法改变,但是不会报错。

字符集

JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode 表示(Unicode 对应表)。

JavaScript 不仅以 Unicode 储存字符,还允许直接在程序中使用 Unicode 码点表示字符,即将字符写成\uxxxx的形式,其中xxxx代表该字符的 Unicode 码点。比如,\u00A9代表版权符号。

var s = '\u00A9';
s // "©"

解析代码的时候,JavaScript 会自动识别一个字符是字面形式表示,还是 Unicode 形式表示。输出给用户的时候,所有字符都会转成字面形式。

var f\u006F\u006F = 'abc';
foo // "abc"

上面代码中,第一行的变量名foo是 Unicode 形式表示,第二行是字面形式表示。JavaScript 会自动识别。

我们还需要知道,每个字符在 JavaScript 内部都是以16位(即2个字节)的 UTF-16 格式储存。也就是说,JavaScript 的单位字符长度固定为16位长度,即2个字节。

但是,UTF-16 有两种长度:对于码点在U+0000U+FFFF之间的字符,长度为16位(即2个字节);对于码点在U+10000U+10FFFF之间的字符,长度为32位(即4个字节),而且前两个字节在0xD8000xDBFF之间,后两个字节在0xDC000xDFFF之间。举例来说,码点U+1D306对应的字符为𝌆,它写成 UTF-16 就是0xD834 0xDF06

JavaScript 对 UTF-16 的支持是不完整的,由于历史原因,只支持两字节的字符,不支持四字节的字符。这是因为 JavaScript 第一版发布的时候,Unicode 的码点只编到U+FFFF,因此两字节足够表示了。后来,Unicode 纳入的字符越来越多,出现了四字节的编码。但是,JavaScript 的标准此时已经定型了,统一将字符长度限制在两字节,导致无法识别四字节的字符。上一节的那个四字节字符𝌆,浏览器会正确识别这是一个字符,但是 JavaScript 无法识别,会认为这是两个字符。

'𝌆'.length // 2

上面代码中,JavaScript 认为𝌆的长度为2,而不是1。

总结一下,对于码点在U+10000U+10FFFF之间的字符,JavaScript 总是认为它们是两个字符(length属性为2)。所以处理的时候,必须把这一点考虑在内,也就是说,JavaScript 返回的字符串长度可能是不正确的。

Base64 转码

有时,文本里面包含一些不可打印的符号,比如 ASCII 码0到31的符号都无法打印出来,这时可以使用 Base64 编码,将它们转成可以打印的字符。另一个场景是,有时需要以文本格式传递二进制数据,那么也可以使用 Base64 编码。

所谓 Base64 就是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、+/这64个字符组成的可打印字符。使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理。

JavaScript 原生提供两个 Base64 相关的方法。

  • btoa():任意值转为 Base64 编码
  • atob():Base64 编码转为原来的值
var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"

注意,这两个方法不适合非 ASCII 码的字符,会报错。

btoa('你好') // 报错

要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。

function b64Encode(str) {
  return btoa(encodeURIComponent(str));
}

function b64Decode(str) {
  return decodeURIComponent(atob(str));
}

b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"

以上内容转载自 字符串 - JavaScript 教程

正则表达式

正则表达式:正则,也叫做规则,让计算机能够读懂人类的规则

使用正则表达式可以很好的 代表字符串

  • 正则表达式两端用/隔开,如:/正则表达式/
  • 在该正则表达式后面加上g表示 global, 全局匹配
  • 加上i表示 ignore,忽略字符串大小写
  • 加上m表示 mutiple ,允许多行匹配

匹配规则

字符描述例子
任何字符 x匹配这个字符/a/.test(str) 判断 str 是否含有字母 a
x l y匹配 x 或者 y/alb/.test(str) 判断 str 是否含有字母 a 或 b
[xyz]字符集合。匹配 x、y、z中任意一个/aeiou/ 匹配所有元音字母
[a-z]字符范围。匹配小写字母/[a-z]/.test(str) 判断 str 是否含有小写字母
[0-9]字符范围。匹配数字字符/[0-9]/.test(str) 判断 str 是否含有数字字母
[A-Z]字符范围。匹配大写字母/[A-Z]/.test(str) 判断 str 是否含有大写字母
[A-Za-z0-9]字符范围。匹配任何英文字符和数字字符/[A-Za-z0-9]/.test(str) 判断 str 是否含有英文字母或数字字符
[^0-9]字符范围求反。匹配指定范围以外的任何字符,^是放在[]里面的/[^a-z]/.test(str) 判断 str 是否含有数字字符以外的字符
匹配输入字符串的开始位置^ab 表示匹配以 ab 开头的字符串
$匹配输入字符串的结束位置ab$ 表示匹配以 ab 结尾的字符串
^...$匹配输入字符串的结束位置^\d$ 表示匹配一个 [0-9] 的数字
*匹配前面的子表达式零次或多次zo* 能匹配 z 或者 zoo(o..) 但不能匹配 zo
+匹配前面的子表达式一次或多次zo* 能匹配 zo 或者 zoo(o..) 但不能匹配 z
?匹配前面的子表达式零次一次zo* 能匹配 z 或者 zo 但不能匹配 zoo(o..)
\d等价于 [0-9]^\d+$ 表示匹配 多个 [0-9] 的数字
\D等价于 [^0-9]
\w等价于 [A-Za-z0-9_],其中包括下划线_
\W等价于 [^A-Za-z0-9_]

创建字符串

var str = "hello,world!"
var str = new Array("hello,world!")[0]

我们可以借助Object.getOwnPropertyNames()方法来看看StringString.prototype都有哪些属性和方法:

image.png

其中是 ES5 新增的有:

  • trim

其中是 ES6 新增的有:

  • String.fromCodePoint()、String.raw()
  • codePointAt、normalize、includes、startsWith、endsWith、repeat、padStart、padEnd、trimStart、trimEnd、matchAll、replaceAll

字符串的方法我对它们按功能进行分类:

String 的方法

访问字符串中的字符

访问字符串的某一项:

  • 直接使用 [index] 进行索引
var str = "hello,world!"
str[0] // "h"
str[5] // ","
str[12] // undefined

str.charAt( index )

  • 使用str.charAt( index )方法也可以访问字符串的指定索引的字符
var str = "hello,world!"
str.charAt(0) // "h"
str.charAt(5) // ","
str.charAt(12) // undefined

修改字符串

修改字符串的某个或某些子字符串:

  • 注意:直接使用 [index] 索引后改变是不可行
var str = "hello,world!"
str[4] = "g" // "g"
str // "hello,world!"

单个替换有以下三种方法:

str.replace( searchValue , replaceValue) 只替换首个匹配字符

  1. 直接使用 str.replace()方法,但是适用的场景是已经要替换的字符串是什么,而不是要替换处的索引
var str = "hello,world!"
str.replace("o","g") // "hellg,world!"
  1. 使用 str.split() 将字符串转化为数组后,对数组某一项进行替换,然后使用 arr.join() 将数组转回字符串,缺点是 浪费空间
var MyReplace = function(str,index,s){
    var arr = str.split("")
    arr[index] = s
    return arr.join("")
}
var str = "hello,world!"
MyReplace(str,4,"g") // "hellg,world!"
  1. 拷贝子字符串后拼接
var MyReplace = function(str,index,s){
    return str.substring(0,index) + s + str.substring(index + 1)
}
var str = "hello,world!"
MyReplace(str,4,"g") // "hellg,world!"

str.replace( /正则表达式/ , replaceValue)

var str = "hello,world!"
str.replace(/o/g,"g") // "hellg,wgrld!" 

另外,全局替换有以下两种方法:

str.replaceAll( searchValue, replaceValue) 可以实现全局替换

const p = 'The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?';
console.log(p.replaceAll('dog', 'monkey'));
// "The quick brown fox jumps over the lazy monkey. If the monkey reacted, was it really lazy?"

str.trim()

功能:会从一个字符串的两端删除空白字符

var str = '   Hello world!   ';
console.log(str); // "   Hello world!   ";
console.log(str.trim()); // "Hello world!";

字符串拼接

  • 直接使用 + 号拼接
"h" + "e" // "he"

String.raw

学习方法前我们先了解一下模板字符串是啥东西

模板字符串

image.png

以上就是一个模板字符串,为 ES6 新增内容,用 `` 符号隔开,内部用 ${ } 把一个变量囊括起来,然后返回的值把 ${ } 里的变量名替换成变量的值后加入字符串的效果,这也是String.raw的其中一个功能(所以在这一块其实也可以不写 String.raw)

${ } 里面可以写表达式,也可以写字符串

String.raw 的使用

通常不需要把它看成一个普通函数,你只需要把它放在模板字符串前面就可以了,而不是在它后面加个括号和一堆参数来调用它,引擎会替你去调用它

String.raw`Hi\n${2+3}!`; // 'Hi\n5!',Hi 后面的字符不是换行符,\ 和 n 是两个不同的字符
String.raw `Hi\u000A!`; // 任何类型的转义形式都会失效,保留原样输出
let name = "Bob";
String.raw `Hi\n${name}!`; // "Hi\nBob!",内插表达式还可以正常运行

String.raw 里面还可以传入多个参数,第一个为对象,对象里面一个字段名称为raw,字段的值为一个数组,另外的参数表示插入到数组两两元素之间

// 下面这个函数和 `foo${2 + 3}bar${'Java' + 'Script'}baz` 是相等的.
String.raw({
  raw: ['foo', 'bar', 'baz']
}, 2 + 3, 'Java' + 'Script'); // 'foo5barJavaScriptbaz'

str.concat( s1 , [s2,s3,...])

功能:将一个或多个字符串与原字符串连接合并,形成一个新的字符串并返回

注意:如果合并的某个元素非字符串类型,则会先调用 toString 方法将其转化为字符串类型

var str = 'Hello, '
console.log(str.concat('Kevin','. Have a nice day.')) // Hello, Kevin. Have a nice day.

var arr = ['Hello', ' ', 'Venkat', '!']
"".concat(...arr)  // "Hello Venkat!",使用 rest 参数作简便式写法

"".concat({})    // [object Object]
"".concat([])    // ""
"".concat(null)  // "null"

str.padEnd( targetLength , [padString] )

功能:用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充

参数:

  • 当前字符串需要填充到的目标长度
  • 填充字符串
var str1 = '200';
console.log(str1.padEnd(5));  // "200  "

var str2 = '由于空间不足,字符串部分溢出,溢出部分用省略号代替';
console.log(str2.padEnd(28, '.')); //  "由于空间不足,字符串部分溢出,溢出部分用省略号代替..."

str.padStart()

功能:用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充

var str1 = '5';
console.log(str1.padStart(2, '0')); //  "05"

var fullNumber = '2034399002125581';
var last4Digits = fullNumber.slice(-4);
var maskedNumber = last4Digits.padStart(fullNumber.length, '*');
console.log(maskedNumber); // "************5581"

检索子字符串

str.includes( "searchstr" , [start] )

功能:从索引为 start 处开始在原字符串 str 中是否存在目标字符串 searchstr ,返回值为 true 或 false

var str = "hello,world!"
str.includes("o") // true
str.includes("6") // false
str.includes('Hello'); // false,该方法区分大小写

str.indexOf( "searchstr" , [start] )

功能:indexOf 方法应该算 includes 方法的升级版。从索引为 start 处开始在原字符串 str 中目标字符串 searchstr 第一次出现时的索引(searchstr 首字符对应在 str 中的索引),若找不到则返回 -1

var str = "hello,world!"
str.indexOf("o") // 4
str.indexOf("6") // -1

str.lastIndexOf( "searchstr" , [start] )

功能:从索引为 start 处开始在原字符串 str 中目标字符串 searchstr 最后一次出现时的索引(searchstr 首字符对应在 str 中的索引),若找不到则返回 -1

var str = "hello,world!"
str.indexOf("o") // 7
str.lastIndexOf("6") // -1

正则表达式 - 语法

/正则表达式/.test( str )

备注:如果传入一个非正则表达式对象 regexp,则会使用 new RegExp(regexp) 隐式地将其转换为正则表达式对象

功能:执行正则表达式和 String 对象之间的一个检索匹配。返回正则表达式在字符串中是否出现;

// 举个例子:用于查看字符串中是否为纯数字,是否含有非数字字符
var str1 = "8642813359942"
var str2 = "36455f912h338"
/[^0-9]/g.test(str1) // false,str1 为纯数字字符组成的字符串
/[^0-9]/g.test(str2) // true,str2 含有非数字字符

str.search( /正则表达式/ )

功能:执行正则表达式和 String 对象之间的一个搜索匹配。返回正则表达式在字符串中首次匹配项的索引;否则返回 -1

// 举个例子:用于查看字符串中是否为纯数字,是否含有非数字字符
var str1 = "8642813359942"
var str2 = "36455f912h338"
str1.search(/[^0-9]/g) // -1,str1 为纯数字字符组成的字符串
str2.search(/[^0-9]/g) // 5,str2 含有非数字字符,且第一个非数字字符所在索引为 5

str.match( /正则表达式/ )

功能:检索返回一个字符串匹配正则表达式的结果,返回符合匹配结果的字符组成的数组,没有任何匹配结果则返回 null

var str = 'The quick brown fox jumps over the lazy dog. It barked.';
str.match(/[A-Z]/g); // ["T", "I"]
str.match(/[0-9]/g); // null

str.matchAll( /正则表达式/ )

功能:返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器

var str = 'test1test2';
var arr = [...str.matchAll(/t(e)(st(\d?))/g)];

image.png

字符串和数组之间的互相转化

arr = str.split()

  • 如果不传入参数,直接返回只有该字符串一个元素的数组
  • 如果传入参数为空字符串,则把该字符串一个一个字符按序拆开并依次放入数组中,然后返回该数组
  • 如果传入参数为非空字符串,但该字符串中不含有该非空字符串,则直接返回只有该字符串一个元素的数组
  • 如果传入参数为非空字符串,且该字符串中含有该非空字符串,则以该参数为分割界限,把原字符串分割成多个子字符串并放入数组中,相对顺序不变,最后返回该数组
var str = "hello,world!"
str.split() // ["hello,world!"]
str.split("") // ["h", "e", "l", "l", "o", ",", "w", "o", "r", "l", "d", "!"]
str.split("6") // ["hello,world!"]
str.split(",") // ["hello" , "world!"]

简易代码实现 API:

var MySplit = function(){
    if(arguments.length === 1) return MySplit1(arguments[0])
    else if(arguments.length === 2) return MySplit2(arguments[0],arguments[1])
    else console.log("参数输入格式错误")
}
var MySplit1 = function(str){ // 只传入一个参数
    return [str]
}
var MySplit2 = function(str,s){ // 传入了两个参数
    if(str.length === 0) return [""]
    var arr = []
    if(s === ""){
        for(let i in str) arr.push(str[i])
        return arr
    }
    var len = str.length
    var l = s.length
    var start = 0
    var end = 0
    while(true){
        // 利用 indexOf 方法辅助搜查分割字符串 s,获得索引,由于 slice 方法无法改变原字符串,
        // 所以得通过 indexOf 方法的第二个参数辅助
        end = str.indexOf(s,start) 
        if(end === -1) {
            arr.push(str.slice(start))
            break
        }
        arr.push(str.slice(start,end))
        start = end + l
    }
    return arr
}

测试:

image.png

这里只是简单实现,可能还是有不少 bug 的,以后有时间再慢慢补,感兴趣的小伙伴可以自行去实现

str = arr.join()

  • 如果不传入参数,则默认连接字符串为,逗号。即各元素以,为连接符连成字符串返回
  • 如果传入参数为空字符串,则把各个数组元素先利用toString转换为字符串后直接拼接起来,中间没有连接符
  • 如果传入参数为非空字符串,则以该非空字符串为连接字符串,把各个数组元素先利用toString转换为字符串后连接成一个字符串,相对顺序不变
var arr = ["hell", ",w", "rld!"]
arr.join() // "hell,,w,rld!"
arr.join("") // "hell,wrld!"
arr.join("o") // "hello,world!"

简易代码实现 API:

var MyJoin = function(){
    if(arguments.length === 1) return MyJoin1(arguments[0])
    else if(arguments.length === 2) return MyJoin2(arguments[0],arguments[1])
    else console.log("参数输入格式错误")
}
var MyJoin1 = function(arr){ // 只传入一个参数
    return MyJoin2(arr,",")
}
var MyJoin2 = function(arr,s){ // 传入了两个参数
    if(str.length === 0) return ""
    var res = ""
    if(s === ""){
        for(let i in arr) res += arr[i]
        return res
    }
    var len = arr.length
    for(let i = 0;i < len;i++){
        res += arr[i]
        if(i < len - 1) res += s
    }
    return res
}
MyJoin(["hell", ",w", "rld!"],"")

测试:

image.png

这里只是简单实现,可能还是有不少 bug 的,以后有时间再慢慢补,感兴趣的小伙伴可以自行去实现

截取字符串

str.substring( start , [end] )

拷贝原字符串索引为从 start 到 end 的一段子字符串,不会改变原字符串,如果省略 [end] 则 [end] 默认为 str.length

var str = 'hello,world!';
str.substring(6) // "world!"
str.substring(6,8) // "wo"

str.slice( start , [end])

拷贝原字符串索引为从 start 到 end 的一段子字符串,不会改变原字符串,如果省略 [end] 则 [end] 默认为 str.length;start 若为负数,负多少就表示倒数第几个

var str = 'hello,world!';
str.slice(6) // "world!"
str.slice(6,8) // "wo"

str.substring 和 str.slice 的区别在哪

  • 传入两个参数降序时的处理方式不同:
    • substring:第二个参数比第一个参数小,substring 会自动把那个小的当作 start
    • slice:第二个参数比第一个参数小,slice 认为这是错误地传参方式,返回空串
var str = 'hello,world!';
str.substring(8,6) // "wo"
str.slice(8,6) // ""
  • 传入负参数时的处理方式不同:
    • substring:会干脆将负参数都直接转换为 0
    • slice:负多少就是倒数第几个
var str = 'hello,world!';
str.substring(3,-4) // "hel"
str.slice(3,-4) // "lo,wo"

str.substr( start , [length] )

拷贝原字符串索引为从 start 开始截取长度为 length 的一段子字符串,不会改变原字符串,如果省略 [length] 则该方法会截取到字符串结束;start 若为负数,负多少就表示倒数第几个

var str = 'hello,world!';
str.substr(6) // "world!"
str.substr(6,4) // "worl"

大小写转换

str.toLowerCase()

str.toUpperCase()

str.toLocaleLowerCase()

str.toLocaleUpperCase()

var stringValue = "hello world";
console.log(stringValue.toLowerCase());          //"hello world"
console.log(stringValue.toUpperCase());          //"HELLO WORLD"
console.log(stringValue.toLocaleLowerCase());    //"hello world"
console.log(stringValue.toLocaleUpperCase());    //"HELLO WORLD"

toLocaleLowerCase() 和 toLocaleUpperCase() 方法是针对特定地区的实现,少数语言(如土耳其语言)会为Unicode大小写转换应用特殊的规则,这时候就必须使用针对地区的方法来保证实现正确的转换

参考文章