JavaScript 中有字串的 trim
方法,但卻沒有陣列的 trim
,現在就來嘗試實作一個陣列的 trim
吧。
本文的範例不考慮型別錯誤等問題,聚焦於方法本身的說明。
在實作前,我們先來了解一下 trim
這個方法的功用,在字串的 trim
中,會將前後的空白字元,包括行的結束字符從字串中去除,例如下面這字串:
` hello
world
`
使用了 trim
回傳的結果為:
大部分的情況下,由於字串前後的空白及換行字元多半是沒有意義且容易造成 bug 產生,例如比對字串是否相等時:
'hi' === 'hi ' // false
這時候 trim
就很有用處:
'hi'.trim() === 'hi '.trim() // true
除了前後都作用的 trim
之外,也有只作用一邊的方法:
' ya '.trimStart() // 'ya '
' ya '.trimEnd() // ' ya'
前面內建的 trim
很好用,但如果想要刪去的是除了空白和換行字元以外的其他字元呢?例如:底線(_
)或是小老鼠(@
)。
這是就需要自製的方法來達成,首先設計介面:
function trimChar(string, chars) {...}
string
: String
型別,原字串chars
: String
型別,要刪除的字元trim
作用過的字串也可以直接寫在
String
的prototype
上:String.prototype.trimChar = function(chars) {...}
。
接下來實作方法:
function trimChar(string, chars) {
const strArr = string.split('');
// 找到第一個不排除的字元索引
const start = strArr.findIndex(ch => !chars.includes(ch));
if(start === -1) return '';
// 找到最後一個不排除的字元索引
let reverseStart = strArr.slice().reverse().findIndex(ch => !chars.includes(ch));
const end = string.length - reverseStart;
// 去頭去尾
return strArr.slice(start, end).join('');
}
trimChar('!hello!@world@', '!@'); // "hello!@world"
findIndex
找到第一個不排除的字元索引slice
刪除前後想要排除的字元,並合併後回傳這樣 trim
特定字元的方法就建置完成了。
上面的是前後都 trim
的方法,想想如果只 trim
開頭或結尾其一的方法怎麼寫呢?
function trimCharStart(string, chars) {
const strArr = string.split('');
// 找到第一個不排除的字元索引
const start = strArr.findIndex(ch => !chars.includes(ch));
if(start === -1) return '';
// 直接取長度當作結尾索引
const end = string.length;
// 去頭去尾
return strArr.slice(start, end).join('');
}
function trimCharEnd(string, chars) {
const strArr = string.split('');
// 直接取 0 為開頭索引
const start = 0;
// 找到最後一個不排除的字元索引
let reverseStart = strArr.slice().reverse().findIndex(ch => !chars.includes(ch));
const end = string.length - reverseStart;
// 去頭去尾
return strArr.slice(start, end).join('');
}
trimCharStart('!hello!@world@', '!@'); // "hello!@world@"
trimCharEnd('!hello!@world@', '!@'); // "!hello!@world"
只要將開頭/結尾的索引設回原本的值就可以了。
看了字串的 trim
實作後,對於陣列的 trim
有沒有一點概念了呢?接著就來想一下改怎麼處理陣列的部分吧。
首先先來定義陣列 trim
介面:
function arrayTrim(array, exclude) {...}
array
: any[]
型別,原陣列exclude
: element => boolean
型別,要排除的元素傳回 true
的回呼函數trim
作用過的陣列同字串
trim
也可以寫在Array.prototype
上。
可以先試著用上面字串的思維想想要怎麼實作 arrayTrim
。
function arrayTrim(array, exclude) {
// 找到第一個不排除的字元索引
const start = array.findIndex(ch => !exclude(ch));
if(start === -1) return '';
// 找到最後一個不排除的字元索引
let reverseStart = array.slice().reverse().findIndex(ch => !exclude(ch));
const end = array.length - reverseStart;
// 去頭去尾
return array.slice(start, end);
}
可以發現跟字串的方法大同小異,這是因為在上面講解字串 trim
時刻意使用陣列的思路來說明,要不然字串可以使用正規表達式寫出更簡潔的方法,詳細可以參考 StackOverflow 上的解答。
// ex1
arrayTrim(['!', 'hello', '!', '@', 'world', '@'], element => ['!', '@'].includes(element));
// ["hello", "!", "@", "world"]
// ex2
arrayTrim([[0, '@'], [1, 'hello'], [2, '!'], [3, '@'], [4, 'world'], [5, '@']], element => ['!', '@'].includes(element[1]));
// [[1, 'hello'], [2, '!'], [3, '@'], [4, 'world']]
陣列的第二個參數會需要是回呼函數,因為陣列中可能會是複雜結構,像是第二個例子一樣。
只
trim
陣列的開頭/結尾的方法就交給各位想想嘍~
會需要這個方法是由於最近處理到時間流的資料,他的資料會像下面這樣:
const datapoints = [
[622,1450754160000],
[587,1450754220000],
[622,1450754280000],
[123,1450754340000],
[622,1450754400000],
[851,1450754460000]
];
待在某個時間點的資料有可能是 null
:
const datapoints = [
[null,1450754160000],
[587,1450754220000],
[null,1450754280000],
[123,1450754340000],
[622,1450754400000],
[null,1450754460000]
];
繪圖時前後的資訊是不需要的,因為那個時間點本來就還沒有資料或是已經沒有資料了,但中間的資料如果是 null
的話就需要將圖上的那個時間畫為 0 值,否則前後的資料會相連使人誤以為這是連續的數值。
同步發表於 Limitless Ping
小弟不太懂JS,都寫C和C#,請問
ch => !chars.includes(ch)
是什麼意思? 輸入的字元不是chars嗎? ch在includes()這裡的時候會代表什麼值?
應該是 chars => !string.includes(chars) 嗎?
ch => !chars.includes(ch)
是 arrow function
ch
: 是傳入參數!chars.includes(ch)
: 是回傳值,這裡是一個布林值這個 arrow function 是 findIndex
的回呼函數,定義可以在 MDN 找到,他是一個循覽陣列每個元素時叫用的函數,有三個參數的函數並要回傳布林值,我們只需要第一個參數 element
,因此一個參數就好( JS 不需要的參數可以省略)。
到這裡我們知道 ch => !chars.includes(ch)
是傳入 findIndex
的回呼函數,而 findIndex
是作用在 strArr
上,而 strArr
是 string 拆開的陣列,可以得到下面的程式:
// 假定傳入的 string = '!hello!@world@'
'!hello!@world@'.split('').findIndex(ch => !chars.includes(ch));
因此 ch
會分別為 '!', 'h', 'e', 'l', 'l', 'o', '!', '@', 'w', 'o', 'r', 'l', 'd', '@'
所以 ch => !chars.includes(ch)
的意思是說 string
分為陣列後的每個元素 ch
做尋覽,如果包含想排除的字元(chars
)的話回傳 false
否則回傳 true
了解了,謝謝大神!
錯在我不了解findIndex函數內放arrow function的使用方法,把ch當成類似C#的委派時的用法,才會想不出後面includes()內為什麼放ch。