はじめに
JavaScriptにおける文字列の切り出しをまとめました。
substr
この機能は非推奨とされているので新たにこの機能を採用するのは避けてください。
Stringオブジェクトに組み込まれたメソッドの一つで、指定した位置から指定した文字数だけ文字列切り出してくれる関数です。
typescriptで定義された型は以下のようになっています。
String.substr(from: number, length?: number): string
文字列オブジェクトから呼び出されるメソッドで、文字列をfrom
で指定した場所からlength
で指定した分を切り出して返却する関数です。
length
は任意で渡すことができ、何も渡さなかった場合はfrom
で指定した以降の文字列を全て取得することができます。負の値、NaN
は0として扱われます。
from
が正の値の場合は文字列の先頭から数えた位置から切り出します。String.length + 1
(文字列の長さ+1)を超える値を指定した場合は空の文字列を返します。負の値の場合は文字列の末尾から数えた位置から切り出します。String.length
(文字列の長さ)以下の場合は最初の文字列から切り出します。NaN
であった場合も同様に最初の文字から切り出されます。
例
from
とlength
が捻りなく与えられた場合
'qiita'.substr(2, 3) // 'ita'
from
だけが与えられた場合
'qiita'.substr(2) // 'ita'
from
に負の値を与えた場合
'qiita'.slice(-2) // 'ta'
length
にNaNを与えた場合
'qiita'.substr(2, NaN) // ''
length
に文字列の長さを超える値を与えた場合
'qiita'.substr(2, 1000) // 'ita'
length
に負の値を与えた場合
'qiita'.substr(2, -100) // ''
from
に文字列の長さを超える値を与えた場合
'qiita'.substr(100) // ''
from
に絶対値が文字列の長さを超える負の値を与えた場合
'qiita'.substr(-100) // 'qiita'
from
にNaNを与えた場合
'qiita'.substr(NaN) // 'qiita'
参考
substring
この機能もStringオブジェクトに組み込まれたメソッドの一つです。指定した位置から指定した位置まで文字列切り出してくれる関数です。
typescriptで定義された型は以下のようになっています。
String.substring(start: number, end?: number): string
substrと比べると、命名を除けば同じ型となっています。比べてみるとfrom
がstart
にlength
がend
になっています。引数名からも察しがつきますが、start
は切り出し始めの位置、end
は終了位置の直後を表す値となっています(end
の位置の文字列は切り出されません)。
end
を指定しなかった場合はstart
以降の文字列が切り出されます。また、負の値やNaNは0として扱われます。
start
がend
よりも小さい場合はこれらの値が交換された結果が返ってきます。つまり、start
がend
として、end
がstart
として扱われることもあるということです。おそらくこのことからstart
でも負の値やNaNは0として扱われます。
例
start
とend
が捻りなく与えられた場合
'qiita'.substr(2, 3) // 'i'
start
だけが与えられた場合
'qiita'.substr(2) // 'ita'
start
に負の値を与えた場合
'qiita'.slice(-2) // 'qiita'
end
にNaNを与えた場合
'qiita'.substr(2, NaN) // 'qi'
end
に文字列の長さを超える値を与えた場合
'qiita'.substr(2, 1000) // 'ita'
end
に負の値を与えた場合
'qiita'.substr(2, -100)// 'qi'
start
に文字列の長さを超える値を与えた場合
'qiita'.substr(100) // ''
start
に絶対値が文字列の長さを超える負の値を与えた場合
'qiita'.substr(-100) // 'qiita'
start
にNaNを与えた場合
'qiita'.substr(NaN) // 'qiita'
参考
slice
この機能もStringオブジェクトに組み込まれたメソッドの一つです。指定した位置から指定した位置まで文字列切り出してくれる関数です。
String.slice(start?: number, end?: number): string;
これまでの説明はstartが任意である点を除けばsubstringと全く同じです。しかし、この二つのメソッドの違いはstartが任意になっただけではありません。
start
からend
の手前までの文字列が切り出されること、NaNであった場合は0として扱われることは同じですが、負の数であった場合の挙動は異なります。負の値であったときは末尾から絶対値分遡った位置から文字列が切り出されるようになります。数式で書くとmax(String.length + start, 0)
になります。0から文字列の長さの幅になります。
また、start
がend
よりも小さい場合でも交換されることはなく空文字列が返ってきます(負の値の場合はsubstringでは0とするのに対して、sliceでは末尾から遡った値とするので非自明な挙動になることが交換されない理由だと思います)。
start
もend
も指定しなかった場合は文字列をそのまま返します。Stringはイミュータブルですので、start
とend
を指定せずに使うことはないと考えられます。余談ですがArrayにも同様にsliceメソッドがあります。Arrayはミュータブルであり通常の代入(const a = b
)では参照元が同じになってしまいます。それを防ぐためにシャローコピーをしたい場合は引数なしでsliceを使って行うことができます。ただ、意味論的にsliceを用いるよりはスプレット構文を用いるべきだと考えています。
例
start
とend
が捻りなく与えられた場合
'qiita'.slice(2, 3) // 'i'
start
だけが与えられた場合
'qiita'.slice(2) // 'ita'
start
に負の値を与えた場合
'qiita'.slice(-2) // 'ta'
end
にNaNを与えた場合
'qiita'.slice(2, NaN) // 'qi'
end
に文字列の長さを超える値を与えた場合
'qiita'.slice(2, 1000) // 'ita'
end
に負の値を与えた場合
'qiita'.slice(2, -100) // ''
start
に文字列の長さを超える値を与えた場合
'qiita'.slice(100) // ''
start
に絶対値が文字列の長さを超える負の値を与えた場合
'qiita'.slice(-100) // 'qiita'
start
にNaNを与えた場合
'qiita'.slice(NaN) // 'qiita'
参考
charAt
Stringオブジェクトに組み込まれたメソッドの一つです。指定した位置の文字を取得する関数です。
String.charAt(pos: number): string;
pos
が0からString.length-1
(文字列の長さ-1)でない数値の場合はから文字列を取得します。一方pos
がNaNの場合は最初の文字列を取得します(pos=0として扱われます)。
例
pos
が捻りなく与えられた場合
'qiita'.charAt(3) // 't'
pos
に文字列の長さを与えられた場合
'qiita'.charAt('qiita'.length) // ''
pos
に負の値を与えられた場合
'qiita'.charAt(-1) // ''
pos
にNaNを与えられた場合
'qiita'.charAt(NaN) // 'q'
参考
全体を通して注意
これらのメソッドにおいて指定した数値は文字列の位置の指定や取得範囲の指定を行なっています。位置や範囲はString.length
に基づいて指定されることに注意をする必要があります。例えば𠮷野家の𠮷は'𠮷'.length
のようにすると2を得らます。そのため、'𠮷野家'の野屋を取得するために直感に従って'𠮷野家'.slice(1, 3)
としても'\uDFB7野'
が返されます。𠮷の長さが2であることが原因なので'𠮷野家'.slice(2, 4)
とする必要があるのです。これが起きる原因はlength
メソッドが文字列内のコード単位の数を返しているからです。𠮷は\uD842
と\uDFB7
の2つのコード単位を持つのでこのようになっています。他にも竈門禰󠄀豆子の禰󠄀や👍など身の回りの文字でも起こる現象となっているので注意したいです。今のところこれを解決するには泥臭い方法しかわからず、スマートな切り出し方法がわからないのでご存じであればコメントいただけると幸いです。