JavaScript字串所有API全解密

路易斯發表於2017-05-03

關於

本文近 6k 字,讀完需 10 分鐘。

字串作為基本的資訊交流的橋樑,幾乎被所有的程式語言所實現(然而c、c++沒有提供)。多數開發者幾乎每天都在和字串打交道,語言內建的String模組,極大地提升了開發者的效率。JavaScript通過自動裝箱字串字面量為String物件,自然地繼承了String.prototype的所有方法,更加簡化了字串的使用。

截止ES6,字串共包含31個標準的API方法,其中有些方法出鏡率較高,需要摸透原理;有些方法之間相似度較高,需要仔細分辨;甚至有些方法執行效率較低,應當儘量少的使用。下面將從String構造器方法說起,逐步幫助你掌握字串。

String構造器方法

fromCharCode

fromCharCode() 方法返回使用指定的Unicode序列建立的字串,也就是說傳入Unicode序列,返回基於此建立的字串。

語法:fromCharCode(num1, num2,…),傳入的引數均為數字。

如下這是一個簡單的例子,將返回 ABC、abc、*、+、- 和 /:

String.fromCharCode(65, 66, 67); // "ABC"
String.fromCharCode(97, 98, 99); // "abc"
String.fromCharCode(42); // "*"
String.fromCharCode(43); // "+"
String.fromCharCode(45); // "-"
String.fromCharCode(47); // "/"複製程式碼

看起來fromCharCode像是滿足了需求,但實際上由於js語言設計時的先天不足(只能處理UCS-2編碼,即所有字元都是2個位元組,無法處理4位元組字元),通過該方法並不能返回一個4位元組的字元,為了彌補這個缺陷,ES6新增了fromCodePoint方法,請往下看。

fromCodePoint(ES6)

fromCodePoint() 方法基於ECMAScript 2015(ES6)規範,作用和語法同fromCharCode方法,該方法主要擴充套件了對4位元組字元的支援。

// "?" 是一個4位元組字元,我們先看下它的數字形式
"?".codePointAt(); // 119558
//呼叫fromCharCode解析之,返回亂碼
String.fromCharCode(119558); // "팆"
//呼叫fromCodePoint解析之,正常解析
String.fromCodePoint(119558); // "?"複製程式碼

除了擴充套件對4位元組的支援外,fromCodePoint還規範了錯誤處理,只要是無效的Unicode編碼,就會丟擲錯誤RangeError: Invalid code point...,這就意味著,只要不是符合Unicode字元範圍的正整數(Unicode最多可容納1114112個碼位),均會丟擲錯誤。

String.fromCodePoint('abc'); // RangeError: Invalid code point NaN
String.fromCodePoint(Infinity); // RangeError: Invalid code point Infinity
String.fromCodePoint(-1.23); // RangeError: Invalid code point -1.23複製程式碼

注:進一步瞭解Unicode編碼,推薦這篇文章:淺談文字編碼和Unicode(下)。如需在老的瀏覽器中使用該方法,請參考Polyfill

raw(ES6)

raw() 方法基於ECMAScript 2015(ES6)規範,它是一個模板字串的標籤函式,作用類似於Python的r和C#的@字串字首,都是用來獲取一個模板字串的原始字面量。

語法: String.raw(callSite, ...substitutions),callSite即模板字串的『呼叫點物件』,...substitutions表示任意個內插表示式對應的值,這裡理解起來相當拗口,下面我將通俗的講解它。

如下是python的字串字首r:

# 防止特殊字串被轉義
print r'a\nb\tc' # 列印出來依然是 "a\nb\tc"
# python中常用於正規表示式
regExp = r'(?<=123)[a-z]+'複製程式碼

如下是String.raw作為字首的用法:

// 防止特殊字串被轉義
String.raw`a\nb\tc`; // 輸出為 "a\nb\tc"
// 支援內插表示式
let name = "louis";
String.raw`Hello \n ${name}`;  // "Hello \n louis"
// 內插表示式還可以運算
String.raw`1+2=${1+2},2*3=${2*3}`; // "1+2=3,2*3=6"複製程式碼

String.raw作為函式來呼叫的場景不太多,如下是用法:

// 物件的raw屬性值為字串時,從第二個引數起,它們分別被插入到下標為0,1,2,...n的元素後面
String.raw({raw: 'abcd'}, 1, 2, 3); // "a1b2c3d"
// 物件的raw屬性值為陣列時,從第二個引數起,它們分別被插入到陣列下標為0,1,2,...n的元素後面
String.raw({raw: ['a', 'b', 'c', 'd']}, 1, 2, 3); // "a1b2c3d"複製程式碼

那麼怎麼解釋String.raw函式按照下標挨個去插入的特性呢?MDN上有段描述如下:

In most cases, String.raw() is used with template strings. The first syntax mentioned above is only rarely used, because the JavaScript engine will call this with proper arguments for you, just like with other tag functions.

這意味著,String.raw作為函式呼叫時,基本與ES6的tag標籤模板一樣。如下:

// 如下是tag函式的實現
function tag(){
  const array = arguments[0];
  return array.reduce((p, v, i) => p + (arguments[i] || '') + v);
}
// 回顧一個simple的tag標籤模板
tag`Hello ${ 2 + 3 } world ${ 2 * 3 }`; // "Hello 5 world 6"
// 其實就想當於如下呼叫
tag(['Hello ', ' world ', ''], 5, 6); // "Hello 5 world 6"複製程式碼

因此String.raw作為函式呼叫時,不論物件的raw屬性值是字串還是陣列,插槽都是天生的,下標為0,1,2,...n的元素後面都是插槽(不包括最後一個元素)。實際上,它相當於是這樣的tag函式:

function tag(){
  const array = arguments[0].raw;
  if(array === undefined || array === null){ // 這裡可簡寫成 array == undefined
    throw new TypeError('Cannot convert undefined or null to object');
  }
  return array.reduce((p, v, i) => p + (arguments[i] || '') + v);
}複製程式碼

實際上,String.raw作為函式呼叫時,若第一個引數不是一個符合標準格式的物件,執行將丟擲TypeError錯誤。

String.raw({123: 'abcd'}, 1, 2, 3); // TypeError: Cannot convert undefined or null to object複製程式碼

目前只有Chrome v41+和Firefox v34+版本瀏覽器實現了該方法。

String.prototype

和其他所有物件一樣,字串例項的所有方法均來自String.prototype。以下是它的屬性特性:

writable false
enumerable false
configurable false

可見,字串屬性不可編輯,任何試圖改變它屬性的行為都將丟擲錯誤。

屬性

String.prototype共有兩個屬性,如下:

  • String.prototype.constructor 指向構造器(String())
  • String.prototype.length 表示字串長度

方法

字串原型方法分為兩種,一種是html無關的方法,一種是html有關的方法。我們先看第一種。但是無論字串方法如何厲害,都不至於強大到可以改變原字串。

HTML無關的方法

常用的方法有,charAt、charCodeAt、concat、indexOf、lastIndexOf、localeCompare、match、replace、search、slice、split、substr、substring、toLocaleLowerCase、toLocaleUpperCase、toLowerCase、toString、toUpperCase、trim、valueof 等ES5支援的,以及 codePointAt、contains、endsWith、normalize、repeat、startsWith 等ES6支援的,還包括 quote、toSource、trimLeft、trimRight 等非標準的。

接下來我們將對各個方法分別舉例闡述其用法。若沒有特別說明,預設該方法相容所有目前主流瀏覽器。

charAt

charAt() 方法返回字串中指定位置的字元。

語法:str.charAt(index)

index 為字串索引(取值從0至length-1),如果超出該範圍,則返回空串。

console.log("Hello, World".charAt(8)); // o, 返回下標為8的字串o複製程式碼
charCodeAt

charCodeAt() 返回指定索引處字元的 Unicode 數值。

語法:str.charCodeAt(index)

index 為一個從0至length-1的整數。如果不是一個數值,則預設為 0,如果小於0或者大於字串長度,則返回 NaN。

Unicode 編碼單元(code points)的範圍從 0 到 1,114,111。開頭的 128 個 Unicode 編碼單元和 ASCII 字元編碼一樣.

charCodeAt() 總是返回一個小於 65,536 的值。因為高位編碼單元需要由一對字元來表示,為了檢視其編碼的完成字元,需要檢視 charCodeAt(i) 以及 charCodeAt(i+1) 的值。如需更多瞭解請參考 fixedCharCodeAt

console.log("Hello, World".charCodeAt(8)); // 111
console.log("前端工程師".charCodeAt(2)); // 24037, 可見也可以檢視中文Unicode編碼複製程式碼
concat

concat() 方法將一個或多個字串拼接在一起,組成新的字串並返回。

語法:str.concat(string2, string3, …)

console.log("早".concat("上","好")); // 早上好複製程式碼

但是 concat 的效能表現不佳,強烈推薦使用賦值操作符(+或+=)代替 concat。"+" 操作符大概快了 concat 幾十倍。(資料參考 效能測試)。

indexOf
lastIndexOf

indexOf() 方法用於查詢子字串在字串中首次出現的位置,沒有則返回 -1。該方法嚴格區分大小寫,並且從左往右查詢。而 lastIndexOf 則從右往左查詢,其它與前者一致。

語法:str.indexOf(searchValue [, fromIndex=0])str.lastIndexOf(searchValue [, fromIndex=0])

searchValue 表示被查詢的字串,fromIndex 表示開始查詢的位置,預設為0,如果小於0,則查詢整個字串,若超過字串長度,則該方法返回-1,除非被查詢的是空字串,此時返回字串長度。

console.log("".indexOf("",100)); // 0
console.log("IT改變世界".indexOf("世界")); // 4
console.log("IT改變世界".lastIndexOf("世界")); // 4複製程式碼
localeCompare

localeCompare() 方法用來比較字串,如果指定字串在原字串的前面則返回負數,否則返回正數或0,其中0 表示兩個字串相同。該方法實現依賴具體的本地實現,不同的語言下可能有不同的返回。

語法:str.localeCompare(str2 [, locales [, options]])

var str = "apple";
var str2 = "orange";
console.log(str.localeCompare(str2)); // -1
console.log(str.localeCompare("123")); // 1複製程式碼

目前 Safari 瀏覽器暫不支援該方法,但Chrome v24+、Firefox v29+,IE11+ 和 Opera v15+ 都已實現了它。

match

match() 方法用於測試字串是否支援指定正規表示式的規則,即使傳入的是非正規表示式物件,它也會隱式地使用new RegExp(obj)將其轉換為正規表示式物件。

語法:str.match(regexp)

該方法返回包含匹配結果的陣列,如果沒有匹配項,則返回 null。

描述

  • 若正規表示式沒有 g 標誌,則返回同 RegExp.exec(str) 相同的結果。而且返回的陣列擁有一個額外的 input 屬性,該屬性包含原始字串,另外該陣列還擁有一個 index 屬性,該屬性表示匹配字串在原字串中索引(從0開始)。
  • 若正規表示式包含 g 標誌,則該方法返回一個包含所有匹配結果的陣列,沒有匹配到則返回 null。

相關 RegExp 方法

  • 若需測試字串是否匹配正則,請參考 RegExp.test(str)。
  • 若只需第一個匹配結果,請參考 RegExp.exec(str)。
var str = "World Internet Conference";
console.log(str.match(/[a-d]/i)); // ["d", index: 4, input: "World Internet Conference"]
console.log(str.match(/[a-d]/gi)); // ["d", "C", "c"]
// RegExp 方法如下
console.log(/[a-d]/gi.test(str)); // true
console.log(/[a-d]/gi.exec(str)); // ["d", index: 4, input: "World Internet Conference"]複製程式碼

由上可知,RegExp.test(str) 方法只要匹配到了一個字元也返回true。而RegExp.exec(str) 方法無論正則中有沒有包含 g 標誌,RegExp.exec將直接返回第一個匹配結果,且該結果同 str.match(regexp) 方法不包含 g 標誌時的返回一致。

replace

該方法在之前已經講過,詳細請參考 String.prototype.replace高階技能

search

search() 方法用於測試字串物件是否包含某個正則匹配,相當於正規表示式的 test 方法,且該方法比 match() 方法更快。如果匹配成功,search() 返回正規表示式在字串中首次匹配項的索引,否則返回-1。

注意:search方法與indexOf方法作用基本一致,都是查詢到了就返回子串第一次出現的下標,否則返回-1,唯一的區別就在於search預設會將子串轉化為正規表示式形式,而indexOf不做此處理,也不能處理正則。

語法:str.search(regexp)

var str = "abcdefg";
console.log(str.search(/[d-g]/)); // 3, 匹配到子串"defg",而d在原字串中的索引為3複製程式碼

search() 方法不支援全域性匹配(正則中包含g引數),如下:

console.log(str.search(/[d-g]/g)); // 3, 與無g引數時,返回相同複製程式碼
slice

slice() 方法提取字串的一部分,並返回新的字串。該方法有些類似 Array.prototype.slice 方法。

語法:str.slice(start, end)

首先 end 引數可選,start可取正值,也可取負值。

取正值時表示從索引為start的位置擷取到end的位置(不包括end所在位置的字元,如果end省略則擷取到字串末尾)。

取負值時表示從索引為 length+start 位置擷取到end所在位置的字元。

var str = "It is our choices that show what we truly are, far more than our abilities.";
console.log(str.slice(0,-30)); // It is our choices that show what we truly are
console.log(str.slice(-30)); // , far more than our abilities.複製程式碼
split

split() 方法把原字串分割成子字串組成陣列,並返回該陣列。

語法:str.split(separator, limit)

兩個引數均是可選的,其中 separator 表示分隔符,它可以是字串也可以是正規表示式。如果忽略 separator,則返回的陣列包含一個由原字串組成的元素。如果 separator 是一個空串,則 str 將會被分割成一個由原字串中字元組成的陣列。limit 表示從返回的陣列中擷取前 limit 個元素,從而限定返回的陣列長度。

var str = "today is a sunny day";
console.log(str.split()); // ["today is a sunny day"]
console.log(str.split("")); // ["t", "o", "d", "a", "y", " ", "i", "s", " ", "a", " ", "s", "u", "n", "n", "y", " ", "d", "a", "y"]
console.log(str.split(" ")); // ["today", "is", "a", "sunny", "day"]複製程式碼

使用limit限定返回的陣列大小,如下:

console.log(str.split(" ")); // ["today"]複製程式碼

使用正則分隔符(RegExp separator), 如下:

console.log(str.split(/\s*is\s*/)); // ["today", "a sunny day"]複製程式碼

若正則分隔符裡包含捕獲括號,則括號匹配的結果將會包含在返回的陣列中。

console.log(str.split(/(\s*is\s*)/)); // ["today", " is ", "a sunny day"]複製程式碼
substr

substr() 方法返回字串指定位置開始的指定數量的字元。

語法:str.substr(start[, length])

start 表示開始擷取字元的位置,可取正值或負值。取正值時表示start位置的索引,取負值時表示 length+start位置的索引。

length 表示擷取的字元長度。

var str = "Yesterday is history. Tomorrow is mystery. But today is a gift.";
console.log(str.substr(47)); // today is a gift.
console.log(str.substr(-16)); // today is a gift.複製程式碼

目前 Microsoft's JScript 不支援 start 引數取負的索引,如需在 IE 下支援,請參考 Polyfill

substring

substring() 方法返回字串兩個索引之間的子串。

語法:str.substring(indexA[, indexB])

indexA、indexB 表示字串索引,其中 indexB 可選,如果省略,則表示返回從 indexA 到字串末尾的子串。

描述

substring 要擷取的是從 indexA 到 indexB(不包含)之間的字元,符合以下規律:

  • 若 indexA == indexB,則返回一個空字串;
  • 若 省略 indexB,則提取字元一直到字串末尾;
  • 若 任一引數小於 0 或 NaN,則被當作 0;
  • 若 任一引數大於 length,則被當作 length。

而 如果 indexA > indexB,則 substring 的執行效果就像是兩個引數調換一般。比如:str.substring(0, 1) == str.substring(1, 0)

var str = "Get outside every day. Miracles are waiting everywhere.";
console.log(str.substring(1,1)); // ""
console.log(str.substring(0)); // Get outside every day. Miracles are waiting everywhere.
console.log(str.substring(-1)); // Get outside every day. Miracles are waiting everywhere.
console.log(str.substring(0,100)); // Get outside every day. Miracles are waiting everywhere.
console.log(str.substring(22,NaN)); // Get outside every day.複製程式碼
toLocaleLowerCase
toLocaleUpperCase

toLocaleLowerCase() 方法返回撥用該方法的字串被轉換成小寫的值,轉換規則根據本地化的大小寫對映。而toLocaleUpperCase() 方法則是轉換成大寫的值。

語法:str.toLocaleLowerCase(), str.toLocaleUpperCase()

console.log('ABCDEFG'.toLocaleLowerCase()); // abcdefg
console.log('abcdefg'.toLocaleUpperCase()); // ABCDEFG複製程式碼
toLowerCase
toUpperCase

這兩個方法分別表示將字串轉換為相應的小寫,大寫形式,並返回。如下:

console.log('ABCDEFG'.toLowerCase()); // abcdefg
console.log('abcdefg'.toUpperCase()); // ABCDEFG複製程式碼
toString
valueOf

這兩個方法都是返回字串本身。

語法:str.toString(), str.valueOf()

var str = "abc";
console.log(str.toString()); // abc
console.log(str.toString()==str.valueOf()); // true複製程式碼

對於物件而言,toString和valueOf也是非常的相似,它們之間有著細微的差別,請嘗試執行以下一段程式碼:

var x = {
    toString: function () { return "test"; },
    valueOf: function () { return 123; }
};

console.log(x); // test
console.log("x=" + x); // "x=123"
console.log(x + "=x"); // "123=x"
console.log(x + "1"); // 1231
console.log(x + 1); // 124
console.log(["x=", x].join("")); // "x=test"複製程式碼

當 "+" 操作符一邊為數字時,物件x趨向於轉換為數字,表示式會優先呼叫 valueOf 方法,如果呼叫陣列的 join 方法,物件x趨向於轉換為字串,表示式會優先呼叫 toString 方法。

trim

trim() 方法清除字串首尾的空白並返回。

語法:str.trim()

console.log(" a b c ".trim()); // "a b c"複製程式碼

trim() 方法是 ECMAScript 5.1 標準加入的,它並不支援IE9以下的低版本IE瀏覽器,如需支援,請參考以下相容寫法:

if(!String.prototype.trim) {
  String.prototype.trim = function () {
    return this.replace(/^\s+|\s+$/g,'');
  };
}複製程式碼
codePointAt(ES6)

codePointAt() 方法基於ECMAScript 2015(ES6)規範,返回使用UTF-16編碼的給定位置的值的非負整數。

語法:str.codePointAt(position)

console.log("a".codePointAt(0)); // 97
console.log("\u4f60\u597d".codePointAt(0)); // 20320複製程式碼

如需在老的瀏覽器中使用該方法,請參考 Polyfill

includes(ES6)

includes() 方法基於ECMAScript 2015(ES6)規範,它用來判斷一個字串是否屬於另一個字元。如果是,則返回true,否則返回false。

語法:str.includes(subString [, position])

subString 表示要搜尋的字串,position 表示從當前字串的哪個位置開始搜尋字串,預設值為0。

var str = "Practice makes perfect.";
console.log(str.includes("perfect")); // true
console.log(str.includes("perfect",100)); // false複製程式碼

實際上,Firefox 18~39中該方法的名稱為contains,由於bug 1102219的存在,它被重新命名為includes() 。目前只有Chrome v41+和Firefox v40+版本瀏覽器實現了它,如需在其它版本瀏覽器中使用該方法,請參考 Polyfill

endsWith(ES6)

endsWith() 方法基於ECMAScript 2015(ES6)規範,它基本與 contains() 功能相同,不同的是,它用來判斷一個字串是否是原字串的結尾。若是則返回true,否則返回false。

語法:str.endsWith(substring [, position])

與contains 方法不同,position 引數的預設值為字串長度。

var str = "Learn and live.";
console.log(str.endsWith("live.")); // true
console.log(str.endsWith("Learn",5)); // true複製程式碼

同樣目前只有 Firefox v17+版本實現了該方法。其它瀏覽器請參考 Polyfill

normalize(ES6)

normalize() 方法基於ECMAScript 2015(ES6)規範,它會按照指定的 Unicode 正規形式將原字串正規化。

語法:str.normalize([form])

form 引數可省略,目前有四種 Unicode 正規形式,即 "NFC"、"NFD"、"NFKC" 以及 "NFKD",form的預設值為 "NFC"。如果form 傳入了非法的引數值,則會丟擲 RangeError 錯誤。

var str = "\u4f60\u597d";
console.log(str.normalize()); // 你好
console.log(str.normalize("NFC")); // 你好
console.log(str.normalize("NFD")); // 你好
console.log(str.normalize("NFKC")); // 你好
console.log(str.normalize("NFKD")); // 你好複製程式碼

目前只有Chrome v34+和Firefox v31+實現了它。

repeat(ES6)

repeat() 方法基於ECMAScript 2015(ES6)規範,它返回重複原字串多次的新字串。

語法:str.repeat(count)

count 引數只能取大於等於0 的數字。若該數字不為整數,將自動轉換為整數形式,若為負數或者其他值將報錯。

var str = "A still tongue makes a wise head.";
console.log(str.repeat(0)); // ""
console.log(str.repeat(1)); // A still tongue makes a wise head.
console.log(str.repeat(1.5)); // A still tongue makes a wise head.
console.log(str.repeat(-1)); // RangeError:Invalid count value複製程式碼

目前只有 Chrome v41+、Firefox v24+和Safari v9+版本瀏覽器實現了該方法。其他瀏覽器請參考 Polyfill

startsWith(ES6)

startsWith() 方法基於ECMAScript 2015(ES6)規範,它用來判斷當前字串是否是以給定字串開始的,若是則返回true,否則返回false。

語法:str.startsWith(subString [, position])

var str = "Where there is a will, there is a way.";
console.log(str.startsWith("Where")); // true
console.log(str.startsWith("there",6)); // true複製程式碼

目前以下版本瀏覽器實現了該方法,其他瀏覽器請參考 Polyfill

Chrome Firefox Edge Opera Safari
41+ 17+ ✔️ 28+ 9+

其它非標準的方法暫時不作介紹,如需瞭解請參考 String.prototype 中標註為感嘆號的方法。

HTML有關的方法

常用的方法有 anchor,link 其它方法如 big、blink、bold、fixed、fontcolor、fontsize、italics、small、strike、sub、sup均已廢除。

接下來我們將介紹 anchor 和 link 兩個方法,其他廢除方法不作介紹。

anchor

anchor() 方法建立一個錨標籤。

語法:str.anchor(name)

name 指定被建立的a標籤的name屬性,使用該方法建立的錨點,將會成為 document.anchors 陣列的元素。

var str = "this is a anchor tag";
document.body.innerHTML = document.body.innerHTML + str.anchor("anchor1"); // body末尾將會追加這些內容 <a name="anchor1">this is a anchor tag</a>複製程式碼

link() 方法同樣建立一個a標籤。

語法:str.link(url)

url 指定被建立的a標籤的href屬性,如果url中包含特殊字元,將自動進行編碼。例如 " 會被轉義為 &\quot。 使用該方法建立的a標籤,將會成為 document.links 陣列中的元素。

var str = "百度";
document.write(str.link("https://www.baidu.com")); // <a href="https://www.baidu.com">百度</a>複製程式碼

小結

部分字串方法之間存在很大的相似性,要注意區分他們的功能和使用場景。如:

  • substr 和 substring,都是兩個引數,作用基本相同,兩者第一個引數含義相同,但用法不同,前者可為負數,後者值為負數或者非整數時將隱式轉換為0。前者第二個參數列示擷取字串的長度,後者第二個參數列示擷取字串的下標;同時substring第一個引數大於第二個引數時,執行結果同位置調換後的結果。
  • search方法與indexOf方法作用基本一致,都是查詢到了就返回子串第一次出現的下標,否則返回-1,唯一的區別就在於search預設會將子串轉化為正規表示式形式,而indexOf不做此處理,也不能處理正則。

另外,還記得嗎?concat方法由於效率問題,不推薦使用。

通常,字串中,常用的方法就charAt、indexOf、lastIndexOf、match、replace、search、slice、split、substr、substring、toLowerCase、toUpperCase、trim、valueof 等這些。熟悉它們的語法規則就能熟練地駕馭字串。


本文就討論這麼多內容,大家有什麼問題或好的想法歡迎在下方參與留言和評論。

本文作者:louis

本文連結:louiszhai.github.io/2016/01/12/…

參考文章

相關文章