teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

5

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

2016/03/29 09:32

投稿

think49
think49

スコア18194

answer CHANGED
@@ -123,6 +123,65 @@
123
123
  false
124
124
  ```
125
125
 
126
+ # 実装
127
+
128
+ 「何を選択するか」はコードの設計指針によりますが、私自身は次の事に気をつけています。
129
+
130
+ - アルゴリズムに無駄が無い事
131
+ - 汎用性が高い事
132
+
133
+ 例えば、`String#indexOf` は対象文字列の先頭から検索してHITした時点で `index` 値を返す関数です。その為、検索でマッチしないと最後まで検索し続ける事になります。先頭1文字だけ探せば良いところを最後まで探し続けるのは無駄です。
134
+ `String#indexOf` が候補から外れます。
135
+
136
+ 汎用性とは今回の要件だけでなく、広範囲の要件を網羅できる機能を指します。
137
+ 今回は先頭の1文字だけを検索すればすみますが、先頭の2文字を削除したい場合も対応できる方が汎用性が高いといえます。
138
+ `string[0]`, `String#charAt` は先頭1文字だけが対象なので汎用性が低いといえます。
139
+
140
+ ```JavaScript
141
+ function removeStartsWith1 (targetString, searchString) {
142
+ if (targetString.startsWith(searchString)) {
143
+ targetString = targetString.slice(searchString.length);
144
+ }
145
+
146
+ return targetString;
147
+ }
148
+
149
+ function removeStartsWith2 (targetString, searchString) {
150
+ return targetString.replace(new RegExp('^' + searchString.replace(/(\W)/g, '\u005C$1'), 'g'), '');
151
+ }
152
+
153
+ function removeStartsWith3 (textNode, searchString) {
154
+ if (textNode.data.startsWith(searchString)) {
155
+ textNode.deleteData(0, searchString.length);
156
+ }
157
+
158
+ return textNode;
159
+ }
160
+
161
+ console.log(removeStartsWith1('※aaa', '※')); // "aaa"
162
+ console.log(removeStartsWith1('aaa', '※')); // "aaa"
163
+ console.log(removeStartsWith2('※aaa', '※')); // "aaa"
164
+ console.log(removeStartsWith2('aaa', '※')); // "aaa"
165
+ console.log(removeStartsWith3(document.createTextNode('※aaa'), '※')); // "aaa"
166
+ console.log(removeStartsWith3(document.createTextNode('aaa'), '※')); // "aaa"
167
+ ```
168
+
169
+ `String#startsWith` は IE11- に対応する為に Polyfill が必要なので後述します。
170
+ 先頭文字列削除に特化するなら `String#replace` の実装がお手軽だと思います。
171
+ 対象がテキストノードなら `CharacterData.prototype.deleteData` でテキストノード自身を操作できるのでお勧めです。
172
+
173
+ # String.prototype.startsWith の Polyfill
174
+
175
+ `String.prototype.startsWith` は ES6 規定の為、IE11- で使用できませんが、Polyfill を使うことで対応できます。
176
+
177
+ - [es6-string-prototype-startswith.js: String.prototype.startsWith の Polyfill (ES6 規定)](https://gist.github.com/think49/908b8d5f08c9945beea7)
178
+
179
+ # 結論
180
+
181
+ いろいろ書きましたが、個人的には `String#replace` と `CharacterData#deleteData` の二択ですね。
182
+ テキストノードをゴリゴリ操作するなら `CharacterData#deleteData`、文字列操作なら `String#replace` というところでしょうか。
183
+ テキストノードも `data` プロパティから `String#replace` を使用してもいいのですが、画面の再描画コストが `CharacterData#deleteData` の方が低そうな気がします(未検証)。
184
+
126
185
  # 参考リンク
127
186
 
128
187
  - [ECMAScript 5 compatibility table](http://kangax.github.io/compat-table/es5/)
@@ -130,22 +189,10 @@
130
189
  - [String.prototype.startsWith() - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)
131
190
  - [1.1.3.18 String.prototype.startsWith – ECMA-262 6th Edition](http://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.startswith)
132
191
 
133
- # 所感
134
-
135
- 最近は正規表現エンジンも随分と速くなってきましたが、速度は最適化次第でいくらでも変わるものです。
136
- 個人的には Polyfill を適用して汎用性の高い `String#startWith` を使うか、シンプルな `string[0]` の二択ですね。
137
- IE8- を考慮しなくて良くなったのは選択肢の幅が増えて嬉しいところです。
138
-
139
-
140
192
  **(更新履歴)**
141
193
 
142
194
  - 2016/03/27 11:55 `String#lastIndexOf` のコード追加
143
195
  - 2016/03/28 10:21 `String#lastIndexOf` のコードが追加できていなかったのを修正。jsperfが削除されていたので比較検証用コードを作成してjsfiddleにUP
196
+ - 2016/03/29 18:30 `String#startsWith` の Polyfill 追加。「実装」「結論」節の追加。
144
197
 
145
- **(備考)**
146
-
147
- 基本的には速度よりも以下に自分の目的にマッチしているかを重視すべきだと思います。
148
- polyfill作成は少し遅くなるかもしれません。
149
- 詳しくは3/29深夜までに追記します。
150
-
151
198
  Re: re97 さん

4

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

2016/03/29 09:32

投稿

think49
think49

スコア18194

answer CHANGED
@@ -97,22 +97,30 @@
97
97
  Google Chrome 49.0.2623.87 m
98
98
 
99
99
  ```
100
- RegExp#test (matched): 10.239ms
100
+ RegExp#test (matched): 6.640ms
101
101
  true
102
- RegExp#test (no match): 7.670ms
102
+ RegExp#test (no match): 3.348ms
103
103
  false
104
- propety access on strings (matched): 4.714ms
104
+ propety access on strings (matched): 4.925ms
105
105
  true
106
- propety access on strings (no match): 1.399ms
106
+ propety access on strings (no match): 1.420ms
107
107
  false
108
- String#charAt (matched): 4.143ms
108
+ String#charAt (matched): 5.177ms
109
109
  true
110
- String#charAt (no match): 1.958ms
110
+ String#charAt (no match): 2.596ms
111
111
  false
112
- String#indexOf (matched): 9.909ms
112
+ String#indexOf (matched): 5.453ms
113
113
  true
114
- String#indexOf (no match): 8.867ms
114
+ String#indexOf (no match): 5.050ms
115
115
  false
116
+ String#lastIndexOf (matched): 5.788ms
117
+ true
118
+ String#lastIndexOf (no match): 3.901ms
119
+ false
120
+ String#startsWith (matched): 13.675ms
121
+ true
122
+ String#startsWith (no match): 4.301ms
123
+ false
116
124
  ```
117
125
 
118
126
  # 参考リンク

3

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

2016/03/28 01:29

投稿

think49
think49

スコア18194

answer CHANGED
@@ -97,22 +97,22 @@
97
97
  Google Chrome 49.0.2623.87 m
98
98
 
99
99
  ```
100
- test2.html:129 RegExp#test (matched): 10.239ms
100
+ RegExp#test (matched): 10.239ms
101
- test2.html:130 true
101
+ true
102
- test2.html:129 RegExp#test (no match): 7.670ms
102
+ RegExp#test (no match): 7.670ms
103
- test2.html:130 false
103
+ false
104
- test2.html:129 propety access on strings (matched): 4.714ms
104
+ propety access on strings (matched): 4.714ms
105
- test2.html:130 true
105
+ true
106
- test2.html:129 propety access on strings (no match): 1.399ms
106
+ propety access on strings (no match): 1.399ms
107
- test2.html:130 false
107
+ false
108
- test2.html:129 String#charAt (matched): 4.143ms
108
+ String#charAt (matched): 4.143ms
109
- test2.html:130 true
109
+ true
110
- test2.html:129 String#charAt (no match): 1.958ms
110
+ String#charAt (no match): 1.958ms
111
- test2.html:130 false
111
+ false
112
- test2.html:129 String#indexOf (matched): 9.909ms
112
+ String#indexOf (matched): 9.909ms
113
- test2.html:130 true
113
+ true
114
- test2.html:129 String#indexOf (no match): 8.867ms
114
+ String#indexOf (no match): 8.867ms
115
- test2.html:130 false
115
+ false
116
116
  ```
117
117
 
118
118
  # 参考リンク

2

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

2016/03/28 01:24

投稿

think49
think49

スコア18194

answer CHANGED
@@ -1,34 +1,129 @@
1
1
  比較するなら `RegExp#test` にしないと不公平な気はしますね。
2
2
 
3
- - [string startwith · jsPerf](http://jsperf.com/string-startwith)
3
+ # 比較検証用コード
4
- - [ECMAScript 5 compatibility table](http://kangax.github.io/compat-table/es5/)
5
- - [ECMAScript 6 compatibility table](http://kangax.github.io/compat-table/es6/)
6
4
 
5
+ - [前方一致検索の速度比較 - JSFiddle](https://jsfiddle.net/Ldyjr88a/)
6
+
7
7
  ```JavaScript
8
8
  'use strict';
9
+ function test (target, search) {
10
+ var i = 50000,
9
- var array = ['※abc', 'def', 'ghi※'];
11
+ regExp = new RegExp('^' + search.replace(/(\W)/g, '\u005C$1'));
10
12
 
13
+ while (i--) {
11
- for (var i = 0, l = array.length; i < l; ++i) {
14
+ regExp.test(target);
15
+ }
16
+
12
- console.log('regexp', /^※/.test(array[i]));
17
+ return regExp.test(target);
13
18
  }
14
19
 
15
- for (var i = 0, l = array.length; i < l; ++i) {
20
+ function propertyAccess (target, search) {
21
+ var i = 50000;
22
+
23
+ while (i--) {
24
+ target[0] === search;
25
+ }
26
+
16
- console.log('propety access on strings', array[i][0] === '※');
27
+ return target[0] === search;
17
28
  }
18
29
 
19
- for (var i = 0, l = array.length; i < l; ++i) {
30
+ function charAt (target, search) {
31
+ var i = 50000;
32
+
33
+ while (i--) {
34
+ target.charAt(0) === search;
35
+ }
36
+
20
- console.log('charAt', array[i].charAt(0) === '※');
37
+ return target.charAt(0) === search;
21
38
  }
22
39
 
23
- for (var i = 0, l = array.length; i < l; ++i) {
40
+ function indexOf (target, search) {
41
+ var i = 50000;
42
+
43
+ while (i--) {
44
+ target.indexOf(search) === 0;
45
+ }
46
+
24
- console.log('indexOf', array[i].indexOf('※') === 0);
47
+ return target.indexOf(search) === 0;
25
48
  }
26
49
 
27
- for (var i = 0, l = array.length; i < l; ++i) {
50
+ function lastIndexOf (target, search) {
51
+ var i = 50000;
52
+
53
+ while (i--) {
54
+ target.lastIndexOf(search, 0) === 0;
55
+ }
56
+
28
- console.log('startsWith', array[i].startsWith('※'));
57
+ return target.lastIndexOf(search, 0) === 0;
29
58
  }
59
+
60
+ function startsWith (target, search) {
61
+ var i = 50000;
62
+
63
+ while (i--) {
64
+ target.startsWith(search);
65
+ }
66
+
67
+ return target.startsWith(search);
68
+ }
69
+
70
+
71
+ function benchmark (fn, name, target, search) {
72
+ console.time(name);
73
+ var result = fn(target, search);
74
+ console.timeEnd(name);
75
+ console.log(result);
76
+ }
77
+
78
+ var matchedString = '※' + Array(50000).join('a'),
79
+ noMatchString = Array(50001).join('a');
80
+
81
+ benchmark(test, 'RegExp#test (matched)', matchedString, '※');
82
+ benchmark(test, 'RegExp#test (no match)', noMatchString, '※');
83
+ benchmark(propertyAccess, 'propety access on strings (matched)', matchedString, '※');
84
+ benchmark(propertyAccess, 'propety access on strings (no match)', noMatchString, '※');
85
+ benchmark(charAt, 'String#charAt (matched)', matchedString, '※');
86
+ benchmark(charAt, 'String#charAt (no match)', noMatchString, '※');
87
+ benchmark(indexOf, 'String#indexOf (matched)', matchedString, '※');
88
+ benchmark(indexOf, 'String#indexOf (no match)', noMatchString, '※');
89
+ benchmark(lastIndexOf, 'String#lastIndexOf (matched)', matchedString, '※');
90
+ benchmark(lastIndexOf, 'String#lastIndexOf (no match)', noMatchString, '※');
91
+ benchmark(startsWith, 'String#startsWith (matched)', matchedString, '※');
92
+ benchmark(startsWith, 'String#startsWith (no match)', noMatchString, '※');
30
93
  ```
31
94
 
95
+ # 検証結果
96
+
97
+ Google Chrome 49.0.2623.87 m
98
+
99
+ ```
100
+ test2.html:129 RegExp#test (matched): 10.239ms
101
+ test2.html:130 true
102
+ test2.html:129 RegExp#test (no match): 7.670ms
103
+ test2.html:130 false
104
+ test2.html:129 propety access on strings (matched): 4.714ms
105
+ test2.html:130 true
106
+ test2.html:129 propety access on strings (no match): 1.399ms
107
+ test2.html:130 false
108
+ test2.html:129 String#charAt (matched): 4.143ms
109
+ test2.html:130 true
110
+ test2.html:129 String#charAt (no match): 1.958ms
111
+ test2.html:130 false
112
+ test2.html:129 String#indexOf (matched): 9.909ms
113
+ test2.html:130 true
114
+ test2.html:129 String#indexOf (no match): 8.867ms
115
+ test2.html:130 false
116
+ ```
117
+
118
+ # 参考リンク
119
+
120
+ - [ECMAScript 5 compatibility table](http://kangax.github.io/compat-table/es5/)
121
+ - [ECMAScript 6 compatibility table](http://kangax.github.io/compat-table/es6/)
122
+ - [String.prototype.startsWith() - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)
123
+ - [1.1.3.18 String.prototype.startsWith – ECMA-262 6th Edition](http://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.startswith)
124
+
125
+ # 所感
126
+
32
127
  最近は正規表現エンジンも随分と速くなってきましたが、速度は最適化次第でいくらでも変わるものです。
33
128
  個人的には Polyfill を適用して汎用性の高い `String#startWith` を使うか、シンプルな `string[0]` の二択ですね。
34
129
  IE8- を考慮しなくて良くなったのは選択肢の幅が増えて嬉しいところです。
@@ -37,10 +132,12 @@
37
132
  **(更新履歴)**
38
133
 
39
134
  - 2016/03/27 11:55 `String#lastIndexOf` のコード追加
135
+ - 2016/03/28 10:21 `String#lastIndexOf` のコードが追加できていなかったのを修正。jsperfが削除されていたので比較検証用コードを作成してjsfiddleにUP
40
136
 
41
137
  **(備考)**
42
138
 
43
139
  基本的には速度よりも以下に自分の目的にマッチしているかを重視すべきだと思います。
140
+ polyfill作成は少し遅くなるかもしれません。
44
- 詳しくは3/28までに追記します。
141
+ 詳しくは3/29深夜までに追記します。
45
142
 
46
143
  Re: re97 さん

1

String#lastIndexOf のコード追加

2016/03/28 01:21

投稿

think49
think49

スコア18194

answer CHANGED
@@ -33,4 +33,14 @@
33
33
  個人的には Polyfill を適用して汎用性の高い `String#startWith` を使うか、シンプルな `string[0]` の二択ですね。
34
34
  IE8- を考慮しなくて良くなったのは選択肢の幅が増えて嬉しいところです。
35
35
 
36
+
37
+ **(更新履歴)**
38
+
39
+ - 2016/03/27 11:55 `String#lastIndexOf` のコード追加
40
+
41
+ **(備考)**
42
+
43
+ 基本的には速度よりも以下に自分の目的にマッチしているかを重視すべきだと思います。
44
+ 詳しくは3/28までに追記します。
45
+
36
46
  Re: re97 さん