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

回答編集履歴

1

ロジックと回答文を大リファクタリング

2018/03/19 16:38

投稿

miyabi-sun
miyabi-sun

スコア21472

answer CHANGED
@@ -1,6 +1,39 @@
1
- まず、倍数でアホにる人は関数化ししょう
1
+ まず、今回質問文の改良点的所が幾つか見受けられ
2
- 3という文字が含まれる時というのは後で対応していくので、まずは3の倍数に集中してくださいね。
3
2
 
3
+ - document.writeは非推奨なのでやめよう
4
+ - 数字がアホになるか否かの判定ロジックは関数として切り分けよう
5
+ - 数字を入れるとアホ、もしくは数字を返すロジックも関数として切り分けよう
6
+ - タイマー機能を改良しよう
7
+
8
+ ---
9
+
10
+ > document.writeは非推奨なのでやめよう
11
+
12
+ document.writeはJavaScript(以下JS)黎明期の書き方ですが、
13
+ これは様々な箇所で不具合が出るダメな子です。
14
+ 結果、どのJSの書籍でも「document.write」は使うな!と明記される程嫌われている機能なので忘れてください。
15
+
16
+ DOMツリー構造を動的に変更するやり方を覚えましょう。
17
+ jQueryというライブラリを扱うのが初心者にはオススメです。
18
+ [jQueryのDOMを挿入](https://qiita.com/nekoneko-wanwan/items/227ccad5f8cc449e91e9)を覚えてください。
19
+
20
+ この反映は質問者さんへの課題として残しておきます。
21
+
22
+ 従って、今回の回答文では`console.log`とデベロッパーツールによる簡易的な表示方法を利用していきます。
23
+ F12キーを押すと、デベロッパーツールが立ち上がります。
24
+ `console.log(123)`という風に実行すると、デベロッパーツールのconsoleタブに結果を出力してくれます。
25
+
26
+ ---
27
+
28
+ > 数字がアホになるか否かの判定ロジックは関数として切り分けよう
29
+
30
+ 3の倍数でアホになる判定ロジックは関数として外部に切り出してください。
31
+ 関数にしてしまえば、結果の確認がとても楽になりますよ。
32
+
33
+ `isナベアツ`です!
34
+ numという数値の引数を要求しており、アホになりそうだったら`true`
35
+ 普通の回答で数字を教えてくれそうなら`false`になります。
36
+
4
37
  ```JavaScript
5
38
  var isNabeatsu = function (num) {
6
39
  return num % 3 === 0;
@@ -8,209 +41,196 @@
8
41
  console.log(isNabeatsu(1)); // false
9
42
  console.log(isNabeatsu(2)); // false
10
43
  console.log(isNabeatsu(3)); // true
44
+ console.log(isNabeatsu(4)); // false
45
+ console.log(isNabeatsu(5)); // false
46
+ console.log(isNabeatsu(6)); // true
11
47
  ```
12
48
 
13
- 早速これを質問文に組み込みましう。
49
+ よしよし、うく動作ているよですね
14
- …とdocument.writeは推奨さてない書き方なので、[jQueryDOMを挿入](https://qiita.com/nekoneko-wanwan/items/227ccad5f8cc449e91e9)を覚えてくだ
50
+ ですがはまだ3倍数だけ判定するので肝心13や23等で反応しなそうです
15
51
 
16
- とりあえずはデベロッパーツールのコンソールに表示するやり方で作っていきます。
17
- コードを下記のように変えて、F12キーでデベロッパーツールを起動してみてください。
18
- (タイマー機能は一旦潰しています。これも後で復活させますからね。)
19
-
20
52
  ```JavaScript
21
53
  var isNabeatsu = function (num) {
22
54
  return num % 3 === 0;
23
55
  }
24
- var start = function () {
25
- var suuji = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
26
- for (var it of suuji) {
27
- var output = isNabeatsu(it) ? 'AHO!' : ''+it;
56
+ console.log(isNabeatsu(13)); // false
28
- console.log(output);
57
+ console.log(isNabeatsu(23)); // false
29
- }
30
- }
31
- start();
32
- // 1
33
- // 2
34
- // AHO!
58
+ console.log(isNabeatsu(31)); // false
35
- // 4
36
- // 5
37
- // AHO!
38
- // 7
39
- // 8
40
- // AHO!
41
- // 10
42
59
  ```
43
60
 
61
+ あらら、全滅です…
44
- うん上手く動いいるよですね
62
+ ではこれを改良し完成させましょ
45
- では、次章で3という文字が含まれた時アホになる対応も入れていきます。
63
+ 「数字の3が含まれている場合はfalseではなくtrueになる」が条件です。
46
64
 
47
- ---
65
+ この数字の3を探す…といった風な用途は「文字列検索」といいます。
66
+ JSには[数値型や文字列型、配列、オブジェクト…など、様々な型](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects)が存在します。
67
+ それらの型には値を加工するのに使う便利な機能(メソッド)が用意されています。
48
68
 
49
- 文字検索するには正規表現が便利です!
69
+ 先程3の倍数か否か確認する、`num % 3`と入力しましたね。
70
+ 数値型は数値計算に利用される型であり、他の型では数値計算は不可能です。
50
- すが、今回[String.prototype.split](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/split)という機能を使ってやってみしょう
71
+ も数字の3を探したい……こういう要望文字列型が得意とする仕事なので数値型から文字列型へ変換(キャスト)する必要があり
51
72
 
52
- まずJavaScriptには型によっ使えるメソッドが決まっいます。
73
+ プログラマのテクニックの一つとして数値として判断出来る値は基本的には数値とし所持しおき、
53
- 例えば文字列ならば`.split`というメソッド利用可能になっす。
74
+ 文字列検索したくなったらその時だけ、文字列型にキャストする使方が分かりやくオススメです
54
- デベロッパーツールのコンソールで下記のコマンドを入力して動作を確かめてみましょう。
55
75
 
76
+ 今回は[String.prototype.match](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/match)を利用することにしました。
77
+ 正規表現を使って文字列を探す少々高難度の手法ですが、3という文字列を探すだけの正規表現は`/3/`とわりと簡単なので採用しました。
78
+ (他にも様々な方法で解決することが可能で、10人に頼めば10人共がちょっとずつ違う判定ロジックを持ってくると思います。探してみてね!!)
79
+
56
80
  ```JavaScript
57
- // この書き方はエラー
58
- // の直後に.があると、小数点として認識しよとするがアルファベトなので構エラーとなる
81
+ // 列検索を行matchメソドは字列型限定のメソッド
82
+ var num = 123;
59
- 30.split("3")
83
+ num.match(/3/);
60
- // Uncaught SyntaxError: Invalid or unexpected token
84
+ // Uncaught TypeError: num.match is not a function
61
85
 
62
- // カッコで包めば大丈夫…でもこの書き方もエラー
63
- // 数値型にはsplitというメソッドは存在しない
64
- (30).split("3")
65
- // Uncaught TypeError: 30.split is not a function
86
+ // JSでは.toString()を使えば全ての値が文字列型にキャストされる
87
+ num.toString().match(/3/);
88
+ // ["3", index: 2, input: "123", groups: undefined]
66
89
 
90
+ // 値をカッコで包んだりしてそのまま.toString()で文字列型にして使うことも可能
91
+ (17).toString().match(/3/);
67
- // 文字列ならOK
92
+ // null
68
- "15".split("3")
69
- // ["15"]
70
93
 
71
- // 3の付く文字の場合、配列が分断され確認
94
+ // 論理型にキャストすにはBoolean使う
72
- "130".split("3")
73
- // ["1", "0"]
74
-
75
- // 数値が文字列になってくれればなぁ…
76
- // JSではどんな型にも.toString()が用意されてて、文字列に変換できる!
77
- (130).toString().split("3")
95
+ Boolean((31).toString().match(/3/));
96
+ // true
78
- // ["1", "0"]
97
+ Boolean((16).toString().match(/3/));
98
+ // false
79
99
  ```
80
100
 
101
+ これを元に`isナベアツ`関数を完成させましょう。
102
+ 「3で割り切れる数字」もしくは「数字の3がある」場合ですので、
81
- 実現きそうな気がてきしたね
103
+ 論理和あるor演算子`||`を利用しま
82
- `.split`を通った文字列は、`3`という文字列があろうがなかろうが必ず配列に変化します。
83
- そして、配列には`length`という要素数を調べるプロパティが存在します。
84
104
 
85
105
  ```JavaScript
106
+ var isNabeatsu = function (num) {
107
+ return (num % 3 === 0) || Boolean(num.toString().match(/3/));
108
+ }
109
+ console.log(isNabeatsu(1)); // false
110
+ console.log(isNabeatsu(2)); // false
86
- (15).toString().split("3").length
111
+ console.log(isNabeatsu(3)); // true
87
- // 1
88
-
89
- (130).toString().split("3").length
112
+ console.log(isNabeatsu(13)); // true
90
- // 2
113
+ console.log(isNabeatsu(23)); // true
114
+ console.log(isNabeatsu(31)); // true
115
+ console.log(isNabeatsu(25)); // false
91
116
  ```
92
117
 
93
- そうなんです、130のように途中に3がある文字列はそこでぶった切られる為に配列の要素数が1を超えるんですね。
94
- 念のため13や31でも挙を確認してしょうか
118
+ いい感じにしてますね。
119
+ isナベアツ関数完成です。
95
120
 
96
- ```JavaScript
121
+ ---
97
- (13).toString().split("3")
98
- // ["1", ""]
99
- (13).toString().split("3").length
100
- // 2
101
122
 
102
- (31).toString().split("3")
103
- // ["", "1"]
123
+ > 数字を入れるとアホ、もしくは数字を返すロジックも関数として切り分けよう
104
- (31).toString().split("3").length
105
- // 2
106
- ```
107
124
 
108
- これを`isNabeatsu`関数に反映てみましょうか。
125
+ 今度は`toナベアツ`関数を作成しましょうか。
126
+ これも切り分ける事でテストが非常にしやすくなります。
109
127
 
128
+ もうアホになるべき数字か否かを判定する関数は出来ていますね?
129
+ 今回は「特定条件でAHO!」か「そうでなければ数字をそのまま使う」ので、三項演算子を使ってみましょうか。
130
+ ついでに、`AHO!`という文字列が返ってくる可能性がある以上、もう片方の数字も文字列型にキャストして同じ文字列型に合わせておいたほうが使いやすそうですね。
131
+
110
132
  ```JavaScript
111
133
  var isNabeatsu = function (num) {
112
- if (num % 3 === 0) {
113
- return true;
114
- } else if (num.toString().split('3').length > 1) {
134
+ return (num % 3 === 0) || Boolean(num.toString().match(/3/));
115
- return true;
116
- } else {
117
- return false;
118
- }
119
135
  }
136
+ var toNabeatsu = function (num) {
120
- console.log(isNabeatsu(11)); // false
137
+ return isNabeatsu(num) ? 'AHO!' : num.toString();
138
+ }
121
- console.log(isNabeatsu(12)); // true
139
+ console.log(toNabeatsu(1)); // 1
140
+ console.log(toNabeatsu(2)); // 2
141
+ console.log(toNabeatsu(3)); // AHO!
122
- console.log(isNabeatsu(13)); // true
142
+ console.log(toNabeatsu(13)); // AHO!
143
+ console.log(toNabeatsu(23)); // AHO!
144
+ console.log(toNabeatsu(31)); // AHO!
123
145
  ```
124
146
 
147
+ はい完了。
148
+ このように同じ数字を入れると、必ず同じ値が返ってくるような処理は関数化しておくと、
125
- ん、いい感じに動作していますね。
149
+ このように動作確認が楽ですね。
126
150
 
151
+ ---
152
+
153
+ 早速これを質問文に組み込みましょう。
154
+ suuji変数に入っている配列を全て消化したら完了というロジックに変更しています。
155
+
127
156
  ```JavaScript
128
157
  var isNabeatsu = function (num) {
129
- if (num % 3 === 0) {
130
- return true;
131
- } else if (num.toString().split('3').length > 1) {
158
+ return (num % 3 === 0) || Boolean(num.toString().match(/3/));
132
- return true;
133
- } else {
134
- return false;
135
- }
136
159
  }
137
- var start = function () {
160
+ var toNabeatsu = function (num) {
161
+ return isNabeatsu(num) ? 'AHO!' : num.toString();
162
+ }
138
- var suuji = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
163
+ var suuji = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
164
+ var aaa = 0;
165
+ var bbb = setInterval(start, 1000);
166
+
167
+ function start() {
168
+ // 表示したい文字列は今回はconsole.logを使って表示
169
+ console.log(toNabeatsu(suuji[aaa]));
170
+ aaa++;
139
- for (var it of suuji) {
171
+ if (aaa >= suuji.length) {
140
- var output = isNabeatsu(it) ? 'AHO!' : ''+it;
172
+ clearInterval(bbb); // タイマー停止
141
- console.log(output);
142
173
  }
143
174
  }
144
- start();
175
+ // 1
176
+ // 2
177
+ // AHO!
178
+ // 4
179
+ // 5
180
+ // AHO!
181
+ // 7
182
+ // 8
183
+ // AHO!
184
+ // 10
145
185
  ```
146
186
 
187
+ 1秒おきにconsole.logが実行されて、表示されていく様が分かります。
188
+ 10秒後に数字の値が全て表示されて、これで完了ですね。
189
+
147
190
  ---
148
191
 
149
- 最後にタイマー機能を復活させて終わりしょうか。
192
+ 【おけ】
150
- そのまえに`suuji`の手打ちが面倒なのでさくっと数値の配列作ってくれる関数でも用意しましょうかね。
151
- これはイディオムというおまじない的なものなので、興味が出たら調べてみてください。
152
193
 
153
- ```JavaScript
154
- var range = function (start, end) {
155
- return Array(end - start + 1).fill(0).map(function (it, i) {
156
- return i + start;
194
+ > タイマー機能を改良しよう
157
- })
158
- }
159
- console.log(range(1, 3)); // [1, 2, 3]
160
- console.log(range(5, 8)); // [5, 6, 7, 8]
161
- ```
162
195
 
196
+ 見事動くようになりましたが、
197
+ 関数はともかく、この`suuji`、`aaa`、`bbb`がキモいんですよねぇ……
163
- これを使ってstart時に外から込むようします。
198
+ 何がキモいってstart関数の中から打ちで読みいってるあたりです。
164
- まずは完成形をドン!
165
199
 
200
+ こういう変数は「状態変数」といって、
201
+ 出来るだけ減らした方がかっこいいのです。
202
+
203
+ そのためのテクニックはいくつかありますが、今回は2つ加えます。
204
+
205
+ - ループの仕組みをsetTimeoutを使った再起ループ化
206
+ - start関数は引数の配列を監視して続けるかやめるか選ぶように
207
+
166
208
  ```JavaScript
167
209
  var isNabeatsu = function (num) {
168
- if (num % 3 === 0) {
169
- return true;
170
- } else if (num.toString().split('3').length > 1) {
210
+ return (num % 3 === 0) || Boolean(num.toString().match(/3/));
171
- return true;
172
- } else {
173
- return false;
174
- }
175
211
  }
176
- var range = function (start, end) {
212
+ var toNabeatsu = function (num) {
177
- return Array(end - start + 1).fill(0).map(function (it, i) {
213
+ return isNabeatsu(num) ? 'AHO!' : num.toString();
178
- return i + start;
179
- })
180
214
  }
181
215
  var start = function (numbers) {
182
- if (numbers.length < 1) return;
216
+ if (numbers[0] == null) return;
183
- var it = numbers[0];
184
- var output = isNabeatsu(it) ? 'AHO!' : ''+it;
185
- console.log(output);
217
+ console.log(toNabeatsu(numbers[0]));
186
218
  setTimeout(start.bind(null, numbers.slice(1)), 1000);
187
219
  }
188
- start(range(1, 13));
220
+ start([1, 2, 3, 4, 5, 6, 7, 8, 9, 10,]);
189
221
  // 1
190
222
  // 2
191
- // AHO
223
+ // AHO!
192
224
  // 4
193
225
  // 5
194
- // AHO
226
+ // AHO!
195
227
  // 7
196
228
  // 8
197
- // AHO
229
+ // AHO!
198
230
  // 10
199
- // 11
200
- // AHO!
201
- // AHO!
202
231
  ```
203
232
 
204
- ちゃんと1秒おきに文字がコンソールに表示されましたね。
205
- setIntervalは1秒毎に時を刻んでくれるので便利ですが、
206
- 変数のハンドリングが超大変です。
207
-
208
- 今回はstart関数を一度実行したら、
209
- 2度目以降は毎回setTimeoutを使う仕様にしました。
210
-
211
- いやいや、start関数はnumbersという数字の配列寄越せって言ってるんだから、
212
- setTimeoutやsetIntervalで使えないでしょ!どうすんの!?
213
- そんなこともあろうかと、関数には[function.prototype.bind](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)というメソッドが用意されています。
233
+ 全ての関数には[function.prototype.bind](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)というメソッドが用意されています。
214
234
  まぁ、要するに関数に値を適用しつつ、実行しないで待ってくれるという必殺技があるんです。
215
235
 
216
236
  ```JavaScript
@@ -223,12 +243,15 @@
223
243
  // 15
224
244
  ```
225
245
 
226
- また、配列には[array.prototype.slice](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)というメソッドが用意されてお
246
+ 更に配列には[array.prototype.slice](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)という配列の一部を切り出すメソッドがます。
227
- 配列切っらす事が可能です。
247
+ これ利用し金太郎アメのようにnumbers変数の関数を頭か1個ずつ削り取って利用る作りにしています。
228
248
 
229
249
  ```JavaScript
230
250
  [1, 2, 3].slice(1)
231
251
  // [2, 3]
252
+
253
+ [1, 2, 3].slice(1).slice(1)
254
+ // [3]
232
255
  ```
233
256
 
234
257
  全体的な流れとしてはこうです。