やりたいこと
個人での長時間のPC作業中に、振り返ったり頭を切り替えたりできるようにするため、15分くらいの間隔でお知らせしてくれるアラームがほしい。
方法の検討
- スマホのアプリだとPC作業に気づかなかったり、逆に集中が切れてしまったりするので、PCでこっそり動くのがいい
- でも作業用PCに余計なアプリは入れたくない
自分でJavaScriptで作って、ブラウザで開いておく方法を試してみる。
通知APIを動かす
JavaScriptを使うにしても、余計なライブラリはなるべく使わずに済ませたい。
幸い標準で通知APIなるものが用意されてるので、これを使う。
まずは単に通知を表示するスクリプトを作ってみる。
window.onload = function() {
Notification.requestPermission();
const notification = new Notification("Check!");
};
↑を読み込む適当なHTMLを作って、ブラウザで開く。
<html>
<head>
<script src="notification.js"></script>
</head>
<body>
</body>
</html>
HTMLを表示すると、よくある感じの通知の許可要求ダイアログが表示される。
(このへんWebに公開する場合はいろいろ考えなきゃいかんけど、ローカルなのであまり気にせず)
許可すると、画面右上によくある感じの通知が表示される。
これを15分ごとに表示させるようにしたい。
実装その1 シンプルに
まずは素直に実装してみる。
window.onload = function() {
Notification.requestPermission();
setInterval(checkTime, 1000);
};
let previousMinutes;
const checkTime = function() {
const currentTime = new Date();
const minutes = currentTime.getMinutes();
if (previousMinutes !== minutes && minutes % 15 === 0) {
previousMinutes = minutes;
const notification = new Notification("Check!");
}
};
setInterval(checkTime, 1000)
と書くことで、1秒間隔で関数 checkTime
を実行する。
関数 checkTime
では現在時刻をチェックして、0/15/30/45分だったら一度だけデスクトップ通知をする。
(通知の間隔を変えたければ minutes % 15
の数値を変えればよい。最初は1分間隔で試すとよい)
これでやりたいことは実現できたけど…
このコードだと、直前の「分」を保持している変数 previousMinutes
をグローバルスコープに置いてしまっている。
これを安全なスコープに閉じ込めてあげたい。
実装その2 クロージャ
このようにしてスコープを閉じられた。見た目の動作は同じ。
window.onload = function() {
Notification.requestPermission();
setInterval(checkTime, 1000);
};
const checkTime = function() {
let previousMinutes;
return function() {
const currentTime = new Date();
const minutes = currentTime.getMinutes();
if (previousMinutes !== minutes && minutes % 15 === 0) {
previousMinutes = minutes;
const notification = new Notification("Check!");
}
}
}();
いい感じのクロージャのサンプルになったので、ざっくり説明。
定数 checkTime
には、無名関数を即時実行した結果が代入される。
無名関数を即時実行して返ってくる結果は return function() ...
と記述のある通り、これまた無名関数。
この無名関数のコードは実装その1の checkTime
関数とまったく同じ。
結果として、実装その1とその2はまったく同じ関数 checkTime
を実行するように見えるけど…。
実装その1とその2で、関数 checkTime
の違いは、関数のスコープ。
実装その1は、最も外側に関数が定義されているので、関数 checkTime
のスコープはグローバルスコープ。
実装その2は、外側の無名関数内に関数が定義されているので、関数 checkTime
のスコープは外側の無名関数が作るローカルスコープ。
(このように、実行時でなく定義時に決まるスコープをレキシカルスコープと言う)
で、実装その2は同じローカルスコープに変数 previousMinutes
が定義されている。
同じスコープなので関数 checkTime
からアクセスできるけど、他のスコープにある関数からはアクセスできない。
これで、変数 previousMinutes
は必要な関数からのみアクセスされる、安全なスコープに閉じ込めることができた。
おまけ設定
通知が味気ないので、ちょっとだけオプションを設定してみる。
window.onload = function() {
Notification.requestPermission();
setInterval(checkTime, 1000);
};
const checkTime = function() {
let previousMinutes;
const options = {
body: "調子はどうだい?",
icon: "img/youkai_kappa.png"
};
return function() {
const currentTime = new Date();
const minutes = currentTime.getMinutes();
if (previousMinutes !== minutes && minutes % 15 === 0) {
previousMinutes = minutes;
const notification = new Notification("Check!", options);
}
}
}();
参考資料
Notifications API を使用する - WebAPI | MDN
WindowOrWorkerGlobalScope.setInterval() - Web API | MDN
開眼! JavaScript 第7章「スコープとクロージャ」
JavaScript の原理:クロージャの真実
おわりに
この記事は Mikatus Advent Calendar 2019 7日目として公開しました。
明日は我らがリーダー @takuya0301 さんです!
参考資料の「クロージャの真実」もぜひご覧ください(本記事の説明はざっくりなので)。