前: JavaScript中級者になろう 15章を読んだ (Web Performance) - t_hazawaの日記
経緯
- JS力をつけてる
- ついに、大きくJSが変わった ES2015 (重要で知らないことはだいたいこのエディションっぽい) に到達
- 22回あるので前後編
# 学習
十六章第一回 WeakMapとWeakSet
https://uhyohyo.net/javascript/16_1.html
- 全然しらない
- 今までに説明したes5
今どきはこれくらい分かっていないと中級者とは言えないでしょう
- 昔は ES6 とよばれてた
es2015というのはとても大規模なバージョンアップでしたが、es2016,es2017はそれに比べると小規模です。なので、実質ほとんどes2015の機能であると思っていただいて構いません。
ie11は例外です。ieはマイクロソフトが開発していたinternet explorerの最終バージョンで、これはes2015にほとんど対応していません 実際の製品でes2015を使いたい場合はこのことに注意しましょう。
mapというのはなにかというと、keyに対して値を保存しておけるものです。
今回紹介するweakmapに近いのは、ただのオブジェクトです
es2015でシンボルが追加された
weakmapでは文字列などではなく、オブジェクトをキーとして使います オブジェクトをキーとして使えるということは、weakmapを使ってオブジェクトに対して別の値を対応付けることができる
- 2021/12/16 23:42 29m 上まで
- 2021/12/17 22:22 1m
var wm = new WeakMap();
作ったら、setメソッドで新しいキーと値を保存できます。
- これって実際に使われてるのかな??
wm.set(key1, 100); //key1に100を関連付ける
- wm.get(key1); は 100 をとれるが、 wm.get({}); だと、別のオブジェクトだからだめよ
上の例では、key1に入っているまさにそのオブジェクトでないと100を取り出すことはできません
- 使いにくそう
なお、weakmapでkeyにできるのはオブジェクトのみです。プリミティブをkeyにしようとするとエラーになります。
- 使いにくそう。(使われてなさそう)
- 使いみち編
- weakmap だと、 GC対象になるのでちゃんといなくなってくれるということらしい
- 確かにweakだ
- なるほど。keyにするobjを拘束しないわけね
- .get(key, default) と第二引数があるよ
weakmapが今までに比べて革新的なところは、やはり任意のオブジェクトに対して別の値を関連付けられるということです 配列をはじめとするjavascriptの言語仕様に存在するオブジェクトや、ノードのようなdomオブジェクトが組み込みオブジェクトに該当します。これらに追加情報を関連付けたいときもweakmapは活躍します
weakmapはどんなオブジェクトでもkeyとして利用できるので、関数やregexp(正規表現オブジェクト)もokです。keyとなるオブジェクトを汚さずに情報を付加できる
weakmapならば、それを持っている自分しか値を読んだり書き換えたりすることができず、安全
もちろん、WeakMapの中にオブジェクトAをkeyとして保存されていたデータも捨てられます。
mapが「キーとなっているオブジェクトの一覧」を返すメソッドなどを持っているため、map内部にしかオブジェクトへの参照が無くなってもそれを外部から取得する手段がある
- 普通のmapは強いな
- WeakMap と名前がにてる WeakSet
- 集合に含まれるかの判定だけできる (getできない)
読み終わりました 2021/12/17 22:41 18m
十六章第二回 イテレータ
https://uhyohyo.net/javascript/16_2.html
- 使いそう
- 配列も文字列もイテレータの仲間らしい 正確には iterable
for-of文。。for-of文はes2015で新たに追加された構文で、イテレータの中身を1つずつ取り出して処理する文です for文やfor-in文と似た使い方ができます。
- よくみる
var arr=[0,1,2,3,4];。for(var value of arr){。console.log(value);。}
- 簡単
for-of文でもcontinue文やbreak文が使用可能
for-of文にiterableが渡されると、それに対応するイテレータが作られ、そのイテレータにより値が順番に取り出される
- .next()を持つのがイテレータ
nextメソッドを備えたオブジェクトならイテレータになります
- 無限イテレータというのもある
- イテレータを取り出す方法をもつものが iterable (iterable もユーザが作れる)
@@iteratorメソッドなるものを持つオブジェクトがiterableなオブジェクト Symbol.iteratorをプロパティ名とするようなメソッドを作ればよい
iterableのfor-of文以外の活用法
Array.fromというメソッドがあります。これは、配列ではないものから配列を作るメソッド Object.createみたいに、コンストラクタに直接くっついているメソッド
console.log(Array.from("foobar"));。。結果は以下のような配列になります。。。["f","o","o","b","a","r"] 第二引数として関数を指定することができます map操作
Array.from("foobar",function(char){ return char.toUpperCase(); }));
Array.formはiterableだけでなく、array-likeなオブジェクト(NodeListとか)からも配列を作ることができます。
- 配列のように利用できるものが array-like
Array.from({ "0":"foo", "1":"bar", "2":"baz", length:3 })
配列や文字列などの組み込みのiterableから得られるイテレータはそれ自体iterableになる
- .entries() でイテレータを得られるらしい
このように、iterableから得られるちゃんとしたイテレータは、それ自体がiterableとなっているのが望ましい
読み終 2021/12/17 23:53 35m
十六章第三回 代入
https://uhyohyo.net/javascript/16_3.html
- 残り 28 書い
代入時にオブジェクトや配列を展開するということが可能になりました
var [a, b] = ["foo", "bar"];
- よくみる
- destructuring assignment というらしい 分割代入
代入の左辺に配列のように変数を並べたものを置いてやる 余った変数にはundefinedが入ります 右辺の配列のほうが長い場合、余ったところは代入されずに捨てられます。
- フレキシブル
配列を用いて関数が擬似的に複数の値を返すとか、そういうことも可能ですね
オブジェクトっぽい感じの代入も可能です。 var {c, d} = { c:"foo", d:"bar" };
- オブジェクトっぽいだけで、普通の代入 * 2
var {c:foo, d} = { c:"foo", d:"bar" };
- cでなく fooに入る
- varがなくても [ は動作する
- { はブロック分解釈されるので var とか ( がいる
- ネスト
次の例のように、同じ形のオブジェクトを渡せばそのとおりに値が入ります。。。var[a,b,[c,d]]=[0,1,[2,3]];
- オブジェクトもできるらしい
- iterable なら右辺に使えるらしい
[a,b,c,d,e]="foobar"; も可能
変数名を書かないことでその部分を飛ばすことができます [a,,b]= [1,2,3];
console.log([1,,,2]);
- としてみれば分かりますが、これは[1,undefined,undefined,2]という配列と同等
[a,b,...c] = [1,2,3,4,5]; ...のちからで cは[3,4,5] になる(残り全部) 何も残ってなければ空の配列
- の応用から次回 2021/12/18 0:00 43m
- 2021/12/20 22:32 土日は忙しかった 0m
for(var [i,val] of arr.entries()){ console.log(i+": "+val); }
- Array.entries() は なんか、https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/entries entries() メソッドは、配列内の各要素に対するキー/値のペアを含む新しい Array イテレーターオブジェクトを返します。 とのこと
console.log(iterator1.next().value); // expected output: Array [0, "a"]
- 配列で [0, "a"] と順にかえしてくれるのね
function foo({a, b, c}){
foo({ a: 3, b: "bar", c: false });
- みたいなのもつくれる
このように、関数宣言の引数に配列っぽいものやオブジェクトっぽいものを入れてやると、その部分に渡された引数が展開されて入ります。
...の記法を引数にも使うことができます。 引数の最後をこの記法にすると、残りの引数が配列として得られます。 function bar(a,b,...c){
- console.log(hensuu1, hennsuu2) は変数をスペース区切り文字列連結して出るのね
- arguments は使われなくなったらしい
- eS2015では引数の初期値も設定できるようになったぞ
なお、引数としてundefinedを渡した場合は引数が渡されなかった扱いになり初期値が適用されます。nullの場合はされません
function quux({a,b=3,c} ={}){
- みたいなのもできる
var {a,b=3,c} = obj;
- もできる(分割代入じのデフォルト)
var文は、そのスコープは関数内ということになっています。つまり、関数内ならどこにvar文を書いても関数内全体で通用します(そして、関数の外には影響を与えません)
- var はどこに書いても、カンすうの最初で初期化されるらしい
- let はブロック内
厳密には関数宣言の{ }はブロックではありませんが、let宣言の場合はこれも同様に扱います
- letの場合、明示的に関数の最初というか外にあるブロックのところで宣言しましょうね
基本的に、変数のスコープがブロックに制限されていても困ることはありません。広いスコープが欲しければ、それ相応の位置で宣言すればいいのです。あなたは今let宣言を知ってしまったので、今後varを使う必要はもはや無いでしょう。
for文におけるletは特別で、スコープはそのfor文の中になります。すなわち、let i=1;とi<=10とi++というforを構成する各式及び{ console.log(i); }という本体部分にわたってこのiが使用可能です
- varだと、 for のくりかえし条件で宣言したものをfor分の後でも使えた
if文などがなくてもブロックを作ることができます。
- 何もともなわずに { } とかける
本来if文やfor文などは処理部分を1文しか書くことができません。その1文にブロックを当てはめることで複数の文を処理させているのです。
- constもスコープはlet とおなじ 。 ブロックスコープ
const宣言にはある変数が定数であると分かりやすくする効果があります。
- strictもーどでない場合は、代入しても何も起きない
オブジェクトをconstで宣言してもオブジェクトの中身は変わる可能性がある constはあくまで変数への代入(obj = なんとか)を制限するのみで、変数に対するその他の操作は一切制限されないのです。
- constは宣言時に必ず代入がいる
必要がないのに再代入可能なのはバグの元なので、再代入する気がない変数はconstで宣言しましょう。そうすると、ものにもよりますが、プログラムの変数宣言の9割くらいはconstになります
変数の再宣言ができないということです。。。実は、varでは同じ変数を何回も宣言することができました
- 2回目以降のvarは無視される。単なる代入になる感じ
読み終 2021/12/20 23:01 22m
十六章第四回 シンボル
https://uhyohyo.net/javascript/16_4.html
- 新しいプリミティブであるシンボル
シンボルは既存のプリミティブとは違い、リテラルによる表現を持ちません シンボルを作るには、Symbolという組み込み関数を呼び出します。 - - let s = Symbol();
シンボルは、オブジェクトのプロパティのキー(プロパティ名)にすることができます。今までオブジェクトのプロパティ名は必ず文字列でした。
String関数は渡されたものを文字列に変換する関数
なお、複数回symbol()を呼び出すと、毎回異なるシンボルが返されます
- かならず違うキーをシンボルでつくれるのだ
- Object.keysやObject.getOwnPropertyNames は文字列キーしか列挙しないので、シンボルキーは見つからない
- Object.getOwnPropertySymbols でみつけてね
javascriptをバージョンアップするにあたって既存の機能を変更・追加したときに既存のes5プログラムが壊れたら困るので、es5プログラムからは見えないシンボルを使って機能拡張を行った
- well-known symbols
Symbolのプロパティとして参照可能 主に 組み込み関数の動作をカスタマイズするために使う
- 2021/12/20 23:10 言っっんおわり 30m
- 次はイテレータから 16_4
イテレータの話で@@iteratorメソッドなるものを紹介しましたが、実はその実体はこのwell-known symbolです @@iteratorというのはSymbol.iteratorのことです。なんということはありません。プロパティ名がwell-known symbolときにいちいちSymbol.iteratorと書くのは大変なので代わりに@@iteratorと書いている
- Synbol. を @@ とかけるらしい
つまり結局のところ、iterableなメソッドは、Symbol.iteratorをキーとして参照できるメソッドを持てばいいのです
- iterable を作るには Synbol.iterator にいい感じのfunctionを入れればいいらしい
本来は、データはiterableの中にあって、イテレータはそのデータを参照して順番に返す仕事をするだけでなければなりません。この例はiterableではなくイテレータがデータを持っているからだめですね。
[ ]で囲むことで、オブジェクトリテラル中のプロパティ名を式にできます
- シンボルキーのオブジェクトプロパティをtukunnのに便利
また、実はSymbolには第1引数として文字列を渡すことができます。これはそのシンボルの名前、あるいは説明を指定するものです。
var a=Symbol.for("foo");
- なんか、文字列に対応したSymbolが作れるらい Symbol.keyFor
yomiowa 2021/12/21 22:34 17m
十六章第五回 Promise
https://uhyohyo.net/javascript/16_5.html
- お待ちかね
- 11000文字
- ES2015(ES6)からだったのね
Promiseは、簡単にいうと非同期処理を抽象化したオブジェクト
- いままではイベントに登録して非同期処理をしていたのだ
このサイトで多く紹介してきたdomがもともとイベントをベースにした設計になっており、多くの非同期処理的apiがその延長上のものとして定められてきた
非同期処理の他の方式としては、コールバックを渡すというものがあります
- node.js の fsモジュールでもコールバックを使うらしい
fs.readFile("ファイル名",{encoding:"utf8"},function(err,data){ console.log(data); });
非同期処理には複数のパターンがある
- JSの難しいところ(複数の書き方がある)
Promiseにより、非同期処理に関する処理を書きやすくなります promiseを使った非同期処理を行う関数は、コールバックを受け取ったりする代わりにpromiseオブジェクトを返します。
const p = readFile("ファイル名"); p.then(function(data){ // ... });
- みたいにかけるようになる 確かにネストが減ってそう
promiseオブジェクトを得たら、やはりコールバックを登録する必要があります。そこで、promiseはそのためのthenメソッドを持ちます。thenメソッドに対してコールバック関数を渡すと、非同期処理が完了したらその関数が呼ばれます。
promiseの結果はコールバック関数の引数として受け取ることができます。
実は、Promiseが表す非同期処理が終了する場合には2種類あります。成功(fulfilled)と失敗(rejected)です。上の例のようのthenにコールバック関数を1つ渡した場合、成功時の処理を登録したことになります。
失敗時の処理も登録するには、thenメソッドに引数を2つ渡します。1つ目の関数が成功時の処理、2つ目の関数が失敗時の処理になります。
- なんか success: function() {},
- error: function() {} って書くのはどこだっけ
p.then(function(result){ / ここで処理終了時の動作 / },function(err){ / ここでエラー時の動作 / });
- 引数はいっこっぽい
なお、失敗時の処理のみを登録するには、thenの代わりにcatchメソッドを呼び出します。
promiseのインスタンスが持つメソッドはthenとcatchの2つです - 思ってたよりシンプル
- Promise返すメソッドの作り方
これもそんなに難しくありません。newでpromiseのインスタンスを作ればよいのです。そのとき、引数として関数を渡します。
var p = new Promise(function(fulfill,reject){ });
var p = new Promise(function(fulfill,reject){ setTimeout(fulfill,5000); });
- Promiseの引数のfunctionの中身は即座に実行される
- fulfill を呼び出すルートが成功ルート
- Promiseを返す関数に非同期処理を書くのね
複数のpromiseを組み合わせて新しいpromiseを作るためのメソッドがあらかじめ用意されています 「全ての処理が終わるまで待つ」
Promise.allメソッド 引数としてpromiseの配列を渡すと、それらが全て成功したら成功となるような新しいpromiseを作って返してくれます。そのときの結果は、各promiseの結果を配列でまとめたもの
- Promise.race から 2021/12/21 23:19 44m
- Promise.race()
こちらは渡された全てのpromiseが解決するまで待つのではなく、どれか1つが解決した時点で終了するpromiseを返します。なお、解決(resolve)というのはpromiseが成功または失敗することを指します。。。最も早く解決したpromiseが成功した場合、その結果を伴って新しいpromiseも成功します 最も早く解決したpromiseが失敗した場合は新しいpromiseも失敗で終わります
他のpromiseの結果は無視されます(結果が無視されるだけで、非同期処理自体が停止するようなことはありません)
次に、promise.resolveメソッドを紹介します。これは、即座に(ただし非同期的に)成功するpromiseを作って返します そのとき結果の値は、promise.resolveメソッドに渡した値になります
- なるほど。よく見る気がする
Promise.resolve(4)
new Promise(function(fulfill,resolve){ fulfill(4); })
promise.rejectメソッドもあり、こちらは常に失敗するpromiseを作って返すものです 結果の値はpromise.rejectメソッドに渡した値です
このようなpromiseを作る必要があるときに簡単に、また分かりやすく書くことができるので有用です
- 2021/12/21 23:50 11m 例えばpromiseを返すメソッドを から
例えばPromiseを返すメソッドを作るときに、引数がおかしいのでエラーを出したい場合などは、必ず失敗するPromiseを返すことができます。
ちなみに、promise.resolveの引数として別のpromiseを渡した場合は、そのpromiseをそのまま返します。これは、promise.resolveが「値をpromiseに変換する」という役割を持っていると見なせば理解できます
実は、promiseのメソッドthenやcatchは、新しいpromiseを返します。
実はp2は、p1.thenに渡された関数の返り値を結果として成功するようなPromiseになっています
- メソッドチェーン登場
- Promise 以外だと、メソッドがなくて難しそうチェーンするのは
- コールバック地獄の深いネストから開放
function doublePromise(value){ return new Promise(
- みたいにすると、Promise を返す関数がつくれる
.then(function(result){ return doublePromise(result); })
- を
- .then(doublePromise)
- と書ける
失敗が発生したときにpromiseチェーンがどんな挙動をするのか見ておきましょう
実は、thenのコールバック関数の処理中に例外が発生した場合、そのthenが返したpromiseは失敗します。
- .then() の引数のfunction は普通の値も Promise も返せるんだなあ
p1が失敗したときの振る舞いは指定されていません。この場合はデフォルトの動作をされます。失敗時のデフォルトの動作は以下です。
- function(err){ throw err; }
Promiseチェーンの中で失敗が発生しても、catchすれば成功に戻して続けることができる
- finally みたいなのもあったっけ?
- then の第荷引奇数か
thenを使うことで、成功と失敗を両方扱うことが可能です。
成功時のデフォルト動作は以下です。
- function(value){ return value; }
- 素通りになるるのね
- 失敗でもデフォルトだと素通り
p.then(function(){ / 成功時の処理 / }).catch(function(){ / 失敗時の処理 / });
- が多いとのこと
- then() の↓に .catch() もあると、 then() で失敗してもcatchできてうれしい
非同期処理をする関数やライブラリを作るときは、Promiseを返してみるのが今風ということです。
よみおわ 2021/12/22 22:50 18m
十六章第六回 ジェネレータ
https://uhyohyo.net/javascript/16_6.html
- 2021/12/23 0:17 19m 再開
- 私の全然知らないことだ
途中で抜けたりまた入ったりできる関数
- function* で定義して、 yield; で一時停止して、 next();で次に行く感じ
- g = kansuu(10); みたいにしてオブジェクトにする
- yield で関数から一旦抜ける
- なんで generator って名前なの?
- Generatorオブジェクト
一方、ジェネレータ関数は関数から出たり入ったりできるという特徴があります。
- yield でも返りねが出せるよ
{ value: 10, done: false, }
- の形式で出てくる
- next(10) にわたして const y = yield; で受け取る感じ
ジェネレータオブジェクトはイテレータとしても使える generatorオブジェクトは自身をイテレータとするiterableでもあります 自分自身を返すsymbol.iteratorプロパティを持つ
- イテレーターとして使ったら、次々にnextするたびに返りね?が生まれるからジェネレーターなのかな
読みおき 2021/12/23 0:28 30m
十六章第七回 ジェネレータ2
https://uhyohyo.net/javascript/16_7.html
- Generator.return(); がある(強制終了) 帰ってくるvalueもreturnの引数
yieldは式中で使えるのに対しreturnは式中に書けない
- Generator.throw(); は例外をおこす
console.log(g.throw(new Error('Hey!'))); // エラー
- function* gen() { } の中で try catch すればキャッチできる
- yield*式 渡されたiterable を順繰りにかえしていく
- 普通に関数式のときも ジェネレーター無名欠かすうを作れる
- var gen = function(){ yield [1, 2, 3]; };
- メソッドのときは this もジェネレーター関数で使える
- ジェネレーター関数はnewで呼べないらしい
function.sentはメタプロパティの一種です。メタプロパティとは、プロパティのような構文だけどプロパティではないもの function.sentはジェネレータ関数の中で使えるメタプロパティです。これは、直近のnextメソッドに引数として渡された値
よみおわ 2021/12/23 0:54 44m
十六章第八回 オブジェクトリテラル
https://uhyohyo.net/javascript/16_8.html
- よく見ると知ってる言葉だった
ES2015にはオブジェクトリテラルにも便利な記法が追加されました
一番最後にもこのようにカンマを付けることができます。
かなり昔のJavaScriptではできなかったので後方互換性を気にして最後のカンマを付けない人もいましたが、最近はそのような心配をする必要はほぼ無くなりましたので遠慮なく最後のカンマを付けましょう。
数値リテラルを書いた場合はその数値を文字列に変換したものがプロパティ名になります。次のような例に注意してください。 1e5
- Computed Property Name
[式]: 値のようにプロパティ名を[ ]で囲い、その中に式を書きます。式の結果がプロパティ名となります。 [str1 + str2]: 123, プロパティ名を文字列ではなくシンボルにしたい場合にこの記法は必須
- 変数 foo の中身をもつ 'foo' というキーのプロパティは {foo,} とかけばできる
- kansuu(foo, bar){}, とすると、 kansuu というキーの関数プロパティができる こうして作られた関数はコンストラクタに利用できない
- *kansuu(foo, bar){}, ならジェネレーター関数
- kansuu のところは ''でも1e3でも[Symbol.iterator] でもいける 普通にプロパティのキー
関数オブジェクトはnameというプロパティを持ちます。その名の通り関数の名前 関数式で作った関数は名前がない場合(俗にいう無名関数ですね)もあります
シンボルをプロパティ名にした場合は名前無しになります。にもかかわらず、well-known symbolの場合は特殊な名前が付くこともあります。 "[Symbol.iterator]" など
恐ろしいことに、コードポイント記法はプロパティ名や変数名に直に書くことができます。 var foo\u{1d40d}bar
識別子(identifier)はプログラム中に直に書いた変数名などを指す用語です
yomiowa2021/12/24 1:19 11m
十六章第九回 テンプレート文字列
https://uhyohyo.net/javascript/16_9.html
- 短い 2000時
新しい文字列リテラル
console.log(
こんにちは、${name}さん
); // "こんにちは、山田太郎さん"- バッククォートだと ${hensuumei} 展開できるようだね
リテラル中で改行してもよいという点です。この改行は文字列中にlf(\n相当)として現れます
- タグ付きテンプレート文字列
関数
文字列……
- こうすると、その関数により文字列を加工することができます。
- ${} 以外の部分が文字列として 配列で第一引数に入る ${} は第似引数以降に入る
- なんて特殊な形式なんだ
タグ付きテンプレート文字列の結果もその通りとなり、文字列でない場合があります。 要するにほとんどただの関数呼び出し
もともと用意されているタグ用の関数としてString.rawがあります。これはエスケープシーケンスを処理せずにそのままにした文字列を返す
console.log(String.raw
foo\nbar こんに\u28ffちは
); // foo\nbar こんに\u28ffちは- タグ関数の第一引数には.raw があるラしい
- ${} は便利とのこと
2021/12/24 1:28 yomiowa 20m
十六章第十回 アロー関数
https://uhyohyo.net/javascript/16_10.html
- 区切り畳、ここでブログにしたい所存
- アロー関数式
- (x, y, z)=>{ ... }
- function(x,y,z) {} がこう書ける
コンストラクタとして使用できない thisを引き継ぐ 関数の外)のthisと同じ コールバック関数を作るときにthisをそのままにしたいときに便利
- bind が要らなくなる
引数がひとつのときは引数リストの括弧を省略してx=>{ ... }とできます 引数が0個のときは括弧を省略できません。()=>{ ... }とする必要があります。
- 書き方が色々増えまくった
関数の本体部分がreturn 式;という形のとき、何かの計算をしてその値を返すだけの関数の場合はこれを(...)=> 式と省略できます。
var func2 = (x, y)=> x+y; この省略をする場合{}は必ず省略する必要があります
console.log(arr.map(x=> x*2)); // [2, 4, 6, 8, 10]
- 確かにこういう時はとても便利
オブジェクトリテラルで作ったオブジェクトを返す関数で上記の省略を使う場合、このように丸括弧で囲まないとうまくいきません。
この記法ではfunction式のときのように関数に名前をつけられません。
- 明確に知らなかったことを知れてよかった
2021/12/24 1:35 27m
感想
- 最近?でてきたよくわかってない記法が、(js中級者を読み返せば)分かるようになってよかった
- JSは書き方が複数あって難しいと思う
時間まとめ
- 2021/12/16-24 (9日間)
- 10 + 44 + 160 + 20 (ブログ) = 234分
- 4時間でES2015のことが半分分かったぞ (全部で8時間くらいか?)
- 累計: 1885分 (=31.4時間)