回答編集履歴

5

`String#startsWith` の Polyfill 追加。「実装」「結論」節の追加。

2016/03/29 09:32

投稿

think49
think49

スコア18156

test CHANGED
@@ -248,6 +248,124 @@
248
248
 
249
249
 
250
250
 
251
+ # 実装
252
+
253
+
254
+
255
+ 「何を選択するか」はコードの設計指針によりますが、私自身は次の事に気をつけています。
256
+
257
+
258
+
259
+ - アルゴリズムに無駄が無い事
260
+
261
+ - 汎用性が高い事
262
+
263
+
264
+
265
+ 例えば、`String#indexOf` は対象文字列の先頭から検索してHITした時点で `index` 値を返す関数です。その為、検索でマッチしないと最後まで検索し続ける事になります。先頭1文字だけ探せば良いところを最後まで探し続けるのは無駄です。
266
+
267
+ `String#indexOf` が候補から外れます。
268
+
269
+
270
+
271
+ 汎用性とは今回の要件だけでなく、広範囲の要件を網羅できる機能を指します。
272
+
273
+ 今回は先頭の1文字だけを検索すればすみますが、先頭の2文字を削除したい場合も対応できる方が汎用性が高いといえます。
274
+
275
+ `string[0]`, `String#charAt` は先頭1文字だけが対象なので汎用性が低いといえます。
276
+
277
+
278
+
279
+ ```JavaScript
280
+
281
+ function removeStartsWith1 (targetString, searchString) {
282
+
283
+ if (targetString.startsWith(searchString)) {
284
+
285
+ targetString = targetString.slice(searchString.length);
286
+
287
+ }
288
+
289
+
290
+
291
+ return targetString;
292
+
293
+ }
294
+
295
+
296
+
297
+ function removeStartsWith2 (targetString, searchString) {
298
+
299
+ return targetString.replace(new RegExp('^' + searchString.replace(/(\W)/g, '\u005C$1'), 'g'), '');
300
+
301
+ }
302
+
303
+
304
+
305
+ function removeStartsWith3 (textNode, searchString) {
306
+
307
+ if (textNode.data.startsWith(searchString)) {
308
+
309
+ textNode.deleteData(0, searchString.length);
310
+
311
+ }
312
+
313
+
314
+
315
+ return textNode;
316
+
317
+ }
318
+
319
+
320
+
321
+ console.log(removeStartsWith1('※aaa', '※')); // "aaa"
322
+
323
+ console.log(removeStartsWith1('aaa', '※')); // "aaa"
324
+
325
+ console.log(removeStartsWith2('※aaa', '※')); // "aaa"
326
+
327
+ console.log(removeStartsWith2('aaa', '※')); // "aaa"
328
+
329
+ console.log(removeStartsWith3(document.createTextNode('※aaa'), '※')); // "aaa"
330
+
331
+ console.log(removeStartsWith3(document.createTextNode('aaa'), '※')); // "aaa"
332
+
333
+ ```
334
+
335
+
336
+
337
+ `String#startsWith` は IE11- に対応する為に Polyfill が必要なので後述します。
338
+
339
+ 先頭文字列削除に特化するなら `String#replace` の実装がお手軽だと思います。
340
+
341
+ 対象がテキストノードなら `CharacterData.prototype.deleteData` でテキストノード自身を操作できるのでお勧めです。
342
+
343
+
344
+
345
+ # String.prototype.startsWith の Polyfill
346
+
347
+
348
+
349
+ `String.prototype.startsWith` は ES6 規定の為、IE11- で使用できませんが、Polyfill を使うことで対応できます。
350
+
351
+
352
+
353
+ - [es6-string-prototype-startswith.js: String.prototype.startsWith の Polyfill (ES6 規定)](https://gist.github.com/think49/908b8d5f08c9945beea7)
354
+
355
+
356
+
357
+ # 結論
358
+
359
+
360
+
361
+ いろいろ書きましたが、個人的には `String#replace` と `CharacterData#deleteData` の二択ですね。
362
+
363
+ テキストノードをゴリゴリ操作するなら `CharacterData#deleteData`、文字列操作なら `String#replace` というところでしょうか。
364
+
365
+ テキストノードも `data` プロパティから `String#replace` を使用してもいいのですが、画面の再描画コストが `CharacterData#deleteData` の方が低そうな気がします(未検証)。
366
+
367
+
368
+
251
369
  # 参考リンク
252
370
 
253
371
 
@@ -262,20 +380,6 @@
262
380
 
263
381
 
264
382
 
265
- # 所感
266
-
267
-
268
-
269
- 最近は正規表現エンジンも随分と速くなってきましたが、速度は最適化次第でいくらでも変わるものです。
270
-
271
- 個人的には Polyfill を適用して汎用性の高い `String#startWith` を使うか、シンプルな `string[0]` の二択ですね。
272
-
273
- IE8- を考慮しなくて良くなったのは選択肢の幅が増えて嬉しいところです。
274
-
275
-
276
-
277
-
278
-
279
383
  **(更新履歴)**
280
384
 
281
385
 
@@ -284,17 +388,7 @@
284
388
 
285
389
  - 2016/03/28 10:21 `String#lastIndexOf` のコードが追加できていなかったのを修正。jsperfが削除されていたので比較検証用コードを作成してjsfiddleにUP
286
390
 
287
-
288
-
289
- **(備考)**
290
-
291
-
292
-
293
- 基本的には速度よりも以下に自分目的にマッチしているかを重視すべきだと思います
391
+ - 2016/03/29 18:30 `String#startsWith` Polyfill 追加「実装」「結論」節の追加。
294
-
295
- polyfill作成は少し遅くなるかもしれません。
296
-
297
- 詳しくは3/29深夜までに追記します。
298
392
 
299
393
 
300
394
 

4

検証結果の記述が足りてなかったのを修正

2016/03/29 09:32

投稿

think49
think49

スコア18156

test CHANGED
@@ -196,35 +196,51 @@
196
196
 
197
197
  ```
198
198
 
199
- RegExp#test (matched): 10.239ms
199
+ RegExp#test (matched): 6.640ms
200
-
200
+
201
- true
201
+ true
202
-
202
+
203
- RegExp#test (no match): 7.670ms
203
+ RegExp#test (no match): 3.348ms
204
-
204
+
205
- false
205
+ false
206
-
206
+
207
- propety access on strings (matched): 4.714ms
207
+ propety access on strings (matched): 4.925ms
208
-
208
+
209
- true
209
+ true
210
-
210
+
211
- propety access on strings (no match): 1.399ms
211
+ propety access on strings (no match): 1.420ms
212
-
212
+
213
- false
213
+ false
214
-
214
+
215
- String#charAt (matched): 4.143ms
215
+ String#charAt (matched): 5.177ms
216
-
216
+
217
- true
217
+ true
218
-
218
+
219
- String#charAt (no match): 1.958ms
219
+ String#charAt (no match): 2.596ms
220
-
220
+
221
- false
221
+ false
222
-
222
+
223
- String#indexOf (matched): 9.909ms
223
+ String#indexOf (matched): 5.453ms
224
-
224
+
225
- true
225
+ true
226
-
226
+
227
- String#indexOf (no match): 8.867ms
227
+ String#indexOf (no match): 5.050ms
228
+
229
+ false
230
+
231
+ String#lastIndexOf (matched): 5.788ms
232
+
233
+ true
234
+
235
+ String#lastIndexOf (no match): 3.901ms
236
+
237
+ false
238
+
239
+ String#startsWith (matched): 13.675ms
240
+
241
+ true
242
+
243
+ String#startsWith (no match): 4.301ms
228
244
 
229
245
  false
230
246
 

3

検証結果の不要な文字列を削除

2016/03/28 01:29

投稿

think49
think49

スコア18156

test CHANGED
@@ -196,37 +196,37 @@
196
196
 
197
197
  ```
198
198
 
199
- test2.html:129 RegExp#test (matched): 10.239ms
199
+ RegExp#test (matched): 10.239ms
200
-
200
+
201
- test2.html:130 true
201
+ true
202
-
202
+
203
- test2.html:129 RegExp#test (no match): 7.670ms
203
+ RegExp#test (no match): 7.670ms
204
-
204
+
205
- test2.html:130 false
205
+ false
206
-
206
+
207
- test2.html:129 propety access on strings (matched): 4.714ms
207
+ propety access on strings (matched): 4.714ms
208
-
208
+
209
- test2.html:130 true
209
+ true
210
-
210
+
211
- test2.html:129 propety access on strings (no match): 1.399ms
211
+ propety access on strings (no match): 1.399ms
212
-
212
+
213
- test2.html:130 false
213
+ false
214
-
214
+
215
- test2.html:129 String#charAt (matched): 4.143ms
215
+ String#charAt (matched): 4.143ms
216
-
216
+
217
- test2.html:130 true
217
+ true
218
-
218
+
219
- test2.html:129 String#charAt (no match): 1.958ms
219
+ String#charAt (no match): 1.958ms
220
-
220
+
221
- test2.html:130 false
221
+ false
222
-
222
+
223
- test2.html:129 String#indexOf (matched): 9.909ms
223
+ String#indexOf (matched): 9.909ms
224
-
224
+
225
- test2.html:130 true
225
+ true
226
-
226
+
227
- test2.html:129 String#indexOf (no match): 8.867ms
227
+ String#indexOf (no match): 8.867ms
228
-
228
+
229
- test2.html:130 false
229
+ false
230
230
 
231
231
  ```
232
232
 

2

String#lastIndexOf のコードが追加できていなかったのを修正。jsperfが削除されていたので比較検証用コードを作成してjsfiddleにUP

2016/03/28 01:24

投稿

think49
think49

スコア18156

test CHANGED
@@ -2,61 +2,251 @@
2
2
 
3
3
 
4
4
 
5
+ # 比較検証用コード
6
+
7
+
8
+
5
- - [string startwith · jsPerf](http://jsperf.com/string-startwith)
9
+ - [前方一致検索の速度比較 - JSFiddle](https://jsfiddle.net/Ldyjr88a/)
10
+
11
+
12
+
13
+ ```JavaScript
14
+
15
+ 'use strict';
16
+
17
+ function test (target, search) {
18
+
19
+ var i = 50000,
20
+
21
+ regExp = new RegExp('^' + search.replace(/(\W)/g, '\u005C$1'));
22
+
23
+
24
+
25
+ while (i--) {
26
+
27
+ regExp.test(target);
28
+
29
+ }
30
+
31
+
32
+
33
+ return regExp.test(target);
34
+
35
+ }
36
+
37
+
38
+
39
+ function propertyAccess (target, search) {
40
+
41
+ var i = 50000;
42
+
43
+
44
+
45
+ while (i--) {
46
+
47
+ target[0] === search;
48
+
49
+ }
50
+
51
+
52
+
53
+ return target[0] === search;
54
+
55
+ }
56
+
57
+
58
+
59
+ function charAt (target, search) {
60
+
61
+ var i = 50000;
62
+
63
+
64
+
65
+ while (i--) {
66
+
67
+ target.charAt(0) === search;
68
+
69
+ }
70
+
71
+
72
+
73
+ return target.charAt(0) === search;
74
+
75
+ }
76
+
77
+
78
+
79
+ function indexOf (target, search) {
80
+
81
+ var i = 50000;
82
+
83
+
84
+
85
+ while (i--) {
86
+
87
+ target.indexOf(search) === 0;
88
+
89
+ }
90
+
91
+
92
+
93
+ return target.indexOf(search) === 0;
94
+
95
+ }
96
+
97
+
98
+
99
+ function lastIndexOf (target, search) {
100
+
101
+ var i = 50000;
102
+
103
+
104
+
105
+ while (i--) {
106
+
107
+ target.lastIndexOf(search, 0) === 0;
108
+
109
+ }
110
+
111
+
112
+
113
+ return target.lastIndexOf(search, 0) === 0;
114
+
115
+ }
116
+
117
+
118
+
119
+ function startsWith (target, search) {
120
+
121
+ var i = 50000;
122
+
123
+
124
+
125
+ while (i--) {
126
+
127
+ target.startsWith(search);
128
+
129
+ }
130
+
131
+
132
+
133
+ return target.startsWith(search);
134
+
135
+ }
136
+
137
+
138
+
139
+
140
+
141
+ function benchmark (fn, name, target, search) {
142
+
143
+ console.time(name);
144
+
145
+ var result = fn(target, search);
146
+
147
+ console.timeEnd(name);
148
+
149
+ console.log(result);
150
+
151
+ }
152
+
153
+
154
+
155
+ var matchedString = '※' + Array(50000).join('a'),
156
+
157
+ noMatchString = Array(50001).join('a');
158
+
159
+
160
+
161
+ benchmark(test, 'RegExp#test (matched)', matchedString, '※');
162
+
163
+ benchmark(test, 'RegExp#test (no match)', noMatchString, '※');
164
+
165
+ benchmark(propertyAccess, 'propety access on strings (matched)', matchedString, '※');
166
+
167
+ benchmark(propertyAccess, 'propety access on strings (no match)', noMatchString, '※');
168
+
169
+ benchmark(charAt, 'String#charAt (matched)', matchedString, '※');
170
+
171
+ benchmark(charAt, 'String#charAt (no match)', noMatchString, '※');
172
+
173
+ benchmark(indexOf, 'String#indexOf (matched)', matchedString, '※');
174
+
175
+ benchmark(indexOf, 'String#indexOf (no match)', noMatchString, '※');
176
+
177
+ benchmark(lastIndexOf, 'String#lastIndexOf (matched)', matchedString, '※');
178
+
179
+ benchmark(lastIndexOf, 'String#lastIndexOf (no match)', noMatchString, '※');
180
+
181
+ benchmark(startsWith, 'String#startsWith (matched)', matchedString, '※');
182
+
183
+ benchmark(startsWith, 'String#startsWith (no match)', noMatchString, '※');
184
+
185
+ ```
186
+
187
+
188
+
189
+ # 検証結果
190
+
191
+
192
+
193
+ Google Chrome 49.0.2623.87 m
194
+
195
+
196
+
197
+ ```
198
+
199
+ test2.html:129 RegExp#test (matched): 10.239ms
200
+
201
+ test2.html:130 true
202
+
203
+ test2.html:129 RegExp#test (no match): 7.670ms
204
+
205
+ test2.html:130 false
206
+
207
+ test2.html:129 propety access on strings (matched): 4.714ms
208
+
209
+ test2.html:130 true
210
+
211
+ test2.html:129 propety access on strings (no match): 1.399ms
212
+
213
+ test2.html:130 false
214
+
215
+ test2.html:129 String#charAt (matched): 4.143ms
216
+
217
+ test2.html:130 true
218
+
219
+ test2.html:129 String#charAt (no match): 1.958ms
220
+
221
+ test2.html:130 false
222
+
223
+ test2.html:129 String#indexOf (matched): 9.909ms
224
+
225
+ test2.html:130 true
226
+
227
+ test2.html:129 String#indexOf (no match): 8.867ms
228
+
229
+ test2.html:130 false
230
+
231
+ ```
232
+
233
+
234
+
235
+ # 参考リンク
236
+
237
+
6
238
 
7
239
  - [ECMAScript 5 compatibility table](http://kangax.github.io/compat-table/es5/)
8
240
 
9
241
  - [ECMAScript 6 compatibility table](http://kangax.github.io/compat-table/es6/)
10
242
 
11
-
12
-
13
- ```JavaScript
14
-
15
- 'use strict';
16
-
17
- var array = ['※abc', 'def', 'ghi※'];
243
+ - [String.prototype.startsWith() - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)
18
-
19
-
20
-
21
- for (var i = 0, l = array.length; i < l; ++i) {
244
+
22
-
23
- console.log('regexp', /^※/.test(array[i]));
24
-
25
- }
26
-
27
-
28
-
29
- for (var i = 0, l = array.length; i < l; ++i) {
30
-
31
- console.log('propety access on strings', array[i][0] === '※');
245
+ - [1.1.3.18 String.prototype.startsWith – ECMA-262 6th Edition](http://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.startswith)
32
-
33
- }
246
+
34
-
35
-
36
-
37
- for (var i = 0, l = array.length; i < l; ++i) {
247
+
38
-
39
- console.log('charAt', array[i].charAt(0) === '※');
248
+
40
-
41
- }
42
-
43
-
44
-
45
- for (var i = 0, l = array.length; i < l; ++i) {
46
-
47
- console.log('indexOf', array[i].indexOf('※') === 0);
48
-
49
- }
50
-
51
-
52
-
53
- for (var i = 0, l = array.length; i < l; ++i) {
54
-
55
- console.log('startsWith', array[i].startsWith('※'));
56
-
57
- }
58
-
59
- ```
249
+ # 所感
60
250
 
61
251
 
62
252
 
@@ -76,6 +266,8 @@
76
266
 
77
267
  - 2016/03/27 11:55 `String#lastIndexOf` のコード追加
78
268
 
269
+ - 2016/03/28 10:21 `String#lastIndexOf` のコードが追加できていなかったのを修正。jsperfが削除されていたので比較検証用コードを作成してjsfiddleにUP
270
+
79
271
 
80
272
 
81
273
  **(備考)**
@@ -84,7 +276,9 @@
84
276
 
85
277
  基本的には速度よりも以下に自分の目的にマッチしているかを重視すべきだと思います。
86
278
 
279
+ polyfill作成は少し遅くなるかもしれません。
280
+
87
- 詳しくは3/28までに追記します。
281
+ 詳しくは3/29深夜までに追記します。
88
282
 
89
283
 
90
284
 

1

String#lastIndexOf のコード追加

2016/03/28 01:21

投稿

think49
think49

スコア18156

test CHANGED
@@ -68,4 +68,24 @@
68
68
 
69
69
 
70
70
 
71
+
72
+
73
+ **(更新履歴)**
74
+
75
+
76
+
77
+ - 2016/03/27 11:55 `String#lastIndexOf` のコード追加
78
+
79
+
80
+
81
+ **(備考)**
82
+
83
+
84
+
85
+ 基本的には速度よりも以下に自分の目的にマッチしているかを重視すべきだと思います。
86
+
87
+ 詳しくは3/28までに追記します。
88
+
89
+
90
+
71
91
  Re: re97 さん