20200921のJavaScriptに関する記事は30件です。

Twitterクローンもどき作成までの過程

PC版のTwitterのようなサイトを作ろうと思って調べたことをまとめていきます

要素をきっちり3分割

target{
 width : calc(100% / 3) ;
}

参考:【CSSで3等分】要素をきっちり三分割するスタイルシートの書き方

画面サイズ取得

画面取得するには以下を使用する
この取得したサイズに応じて表示する内容を変更したりする
どちらもスクロールサイズを除くサイズ

縦サイズ

document.documentElement.clientWidth

横サイズ

document.documentElement.clientWidth

画面サイズ変更の監視

window.addEventListener('resize',()=>{
  //処理
}

参考:要素のリサイズに応じてイベントを発動する方法

入力ボックス

入力ボックスはHTML5の contentEditable 属性を使う
この属性をtrueにすると編集できるようになる
inputを使うよりもシンプルな入力画面になる

<div contentEditable="true">入力ボックス</div>

aの下線部を消す

text-decoration: none;

Twitterのようなタイムラインの形のテンプレート

<body style="background-color:rgb(54, 3, 3)">
    <div style="background-color: rgb(107, 107, 2);width: 500px;display: inline-flex;">
        <!-- ユーザ情報 -->
        <div style="background-color: rgb(104, 101, 101);">
            <img src="image/bell.png" alt="" style="width: 50px;">
        </div>
        <!-- 投稿内容 -->
        <div style="display: block;">
            <div style="background-color: rgb(109, 65, 65);">あああ</div>
            <div style="background-color: rgb(69, 65, 109);">いいい</div>
            <div style="background-color: rgb(65, 109, 88);">ううう</div>
            <!-- <div contenteditable="true" style="background-color: rgb(65, 109, 88);">ううう</div> -->
        </div>

    </div>

</body>

スクリーンショット 2020-09-19 23.45.22.png

block要素を上下左中央に配置

.outer{
  position: relative;
}
.inner{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translateY(-50%) translateX(-50%);
  -webkit- transform: translateY(-50%) translateX(-50%);
}

引用:CSSで要素を上下や左右から中央寄せする7つの方法

フォーカスインとフォーカスアウト

addEventListenerでコントロールするものいいが特定のタグであれば
onfocusonblur属性がいい仕事をしてくれる

属性 説明
onfocus フォーカスしたとき
onblur フォーカスアウトしたとき
<input placeholder="キーワード検索" type="text" onblur="focusOut(this)" onfocus="focus(this)">

thisでその要素自体を取得できる

気になったcssプロパティなど

flex-basis

コンテンツボックスの寸法を定義

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 周りの知識を整理したい

かれこれTypeScript+Jest案件で1週間以上ハマっている。それはおそらく各技術をちゃんと理解せず、雰囲気だけで使っているから。
なので私が今良くわかっていない以下の6つの技術に関して、自分なりに整理してみようと思います。

  • JavaScript
  • 以下予定
    • TypeScript
    • webpack
    • Node.js
    • Jest
    • babel

JavaScript(ECMAScript)

私のよく知ってるJavascriptは、ブラウザで動くやつです。index.htmlに直接書いたり、外部ファイルに記述してscriptタグで読み込んだりするやつです。これについて調べてみます。
もともとは1995年に"make web pages alive"というスローガンのもとに作られ、その目論見は見事成功しているように思えます。もともとはLiveScriptという名前だったようです。それが、当時流行っていたJavaに乗っかる形でJavaScriptと改名したとのことで、Javaと全く無関係ではないようです。しかし1997年にはECMAScriptという名前になり汎用言語としての規格が制定され、JavaScriptはその実装の一つとして独自の進化を遂げ、Javaとは何ら関係なくなりました。

ECMAScriptのバージョンについて

2015年以降は、ECMAScript2015 など、西暦に基づいた名前になっています。以下、主なECMAScriptのバージョンについて書いてみます。

ECMAScript1 (1997)

最初のエディションです。

ECMAScript3 (1999)

  • 正規表現
  • try/catch

が追加されました。ES3は全てのブラウザがサポートする最も高いバージョンです。 トランスパイラのターゲット言語として指定されているのを見たことがありますが、そういうことだったんですね。

ECMAScript5 (2009)

いわゆるES5です。ES5から追加された機能としては、

  • "use strict"
  • Array.isArrayArray.forEach などの配列周りの関数
  • JSON.parse
  • Date.now()
  • getterとsetter
  • 配列の最後の要素の後の余分なカンマ(←地味に嬉しい)

などがあるようです。だいぶモダンみが増してきました。ES5は全てのモダンなブラウザがサポートする最も高いバージョンです。というのは、IE9が"use strict"に対応していない、というのがあるみたいです。

ECMAScript6 (2015)

いわゆるES6ですね。このバージョンから、バージョン番号は西暦の下1桁+1になっててややこしいです。

  • letconst
  • Promise
  • Arrow Functions (() => {}みたいなやつ)
  • Class
  • デフォルトパラメータ

などが追加され、より関数型言語を意識した形になっています。個人的にはES5->ES6の変化が最も大きく感じます。IE以外は全てサポートしているようです。

ECMAScript7 (2016)

  • ** (exponential operator)

など。ES7をフルサポートするのは Chrome と Opera のみのようです。

ECMAScript8 (2017)

  • async/await

構文糖衣ではありますが、これがないと生きていけない人もいるのではないでしょうか。

ECMAScript9 (2018)

  • rest / spread properties
    • 多値をシンプルに受け渡しできる構文。便利。

環境

ECMAScriptそのものは、I/OやDOMについての規定はないようです。そのへんはJavaScriptにおいて規定されているという認識です。また、JavaScriptといってもブラウザとNode.jsでは環境が異なるはずです。そのへんはまた後で詳しく調べる。

JavaScript Engine

一口にブラウザのJavaScriptといっても、その実装はブラウザによってまちまちのようです。

  • Chorome: V8
  • FireFox: SpiderMonkey
  • IE: Chakra

これらが共通でフルサポートしているのがES5で、後述するトランスパイラはより上位のECMAScriptのバージョンをES5まで落とすことでマルチブラウザ対応を可能にします。

Transpiling

先述したES6以降に備わっている素敵な機能は、全ての環境、ブラウザで使えるわけではありません。では単なる絵に描いた餅かというとそうではなく、ES2015以降で頻繁に行われるようになったトランスパイラなるものを使い、ES3などのどのブラウザでもサポートされているようなバージョンでも動くソースに変換します。
コンパイルは高級な言語からより低級な言語に変換する処理と認識していますが、トランスパイルは、同レベルの別の言語、または同一言語のより下位のバージョンに変換する処理といったところでしょうか。コンパイラはないと困るが、トランスパイラはより幸せになるためのもので、必要性はコンパイラほどではないイメージです。機械語を直で書く人もいるようですが…。
バージョン間の差異を埋めるのにPolyfillという手法もあるようですが、これは実行時にAPIなどの機能を補完するもので、根本的な文法の違いを吸収することはできません。いっそソースを書き換えてしまえというのがTranspilingの考え方かと思います。
例えばトランスパイラは、以下のものをES5やES3に変換します

  • TypeScript
  • CoffeeScript
  • ES2015

疲れたので今日はここまで。
TODO: 続きを書く。

参考

https://www.w3schools.com/js/js_versions.asp
https://javascript.info/intro
https://en.wikipedia.org/wiki/ECMAScript

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】JavaScriptの基本①

JavaScriptとは

Webページの中身を書き換えたり、動かしたりするために利用されている。Webブラウザ上で動くWebアプリケーションなどの多くが、このJavaScriptで作られている。

変数(variable)とは

変数は、箱のようなもの。処理途中にデータを一時的に保存する。この変数には、数値や文字列などを入れることができる。

変数の宣言

変数を利用するにあたっては、予め変数を宣言しないといけない。変数の宣言=値を格納するための領域をメモリ上に確保すること。初期値を設定しなかった場合、デフォルトで未定義undefinedという特別な値を変数に割り当てる。

var命令

再代入、再宣言は可能。

sample.js
// score という名前の変数を宣言
var score;

// 変数 scoreを宣言し、1を代入
var score = 1;
.
.
.
// 変数 scoreを再宣言、10を代入
var score = 10;

let命令

再代入は可能、再宣言は不可。

sample.js
let score = 1;
// 再代入
score = 10;
// 再宣言でエラーになる
let score = 10;

const命令

再代入、再宣言は不可。
例えば税率など今後値が変更されたりすることがある場合は、同じ値がコードにたくさんあると、修正作業が大変になる。修正漏れやバグを防ぐためにもconstを使うべき場合がある。

sample.js
const score = 1;
// 再代入でエラー
score = 10;
// 再宣言でエラー
const score = 10;

関数(function)とは

何度も行う処理を関数にすることで、1度だけ書いてプログラムを処理する書き方。

関数の定義

sample.js
// 関数 getScoreを定義
function getScore() {
// ここに処理をかく
}

引数や戻り値を返す場合

sample.js
// 関数 getScoreを定義し、引数をnumberを渡す
function getScore(number) {
// numberを2乗した値を、戻り値として返す
return number * number;

関数リテラル

宣言した時点では、名前を持たない無名関数。

sample.js
// function(number1...){}と名前のない関数を定義した上で変数 getScoreに格納している
var getScore = function(number1, number2) {
return number1 * number2;
}

アロー関数

アロー関数は関数リテラルのfunction()を省略して代わりに=>で記述する方法。文の戻り値がそのまま戻り値となるので、returnも省略可能。

sample.js
// 通常の関数
var getScore(number) {
// ここに処理を書く
}

// アロー関数
var getScore = (number) => {
// ここに処理を書く
}

処理が1行の場合は、{}で囲わなくてもいい。

sample.js
var getScore = (number) => ここに処理を書く;

引数がない場合

sample.js
// 空の()を書く
var getScore = () => ここに処理を書く;

引数(parameter)とは

引数は、関数の挙動を決めるためのパレメータ。内部処理を行う際に、外部から値を受け取るための変数。引数を書く括弧の中には、変数名を書く。引数が必要ないのなら、何も書かなくていい。複数使いたい場合はカンマで区切って並べて書く。宣言した『引数』は、『関数』内でのみ有効です。

sample.js
function getScore(引数1, 引数2, 引数3) {
// 処理
}

戻り値(return)とは

関数処理の結果、最終的に呼び出し元に返すための値(返り値ともいう)。関数の途中でreturnを記述すると、それ以降処理が実行されない。戻り値がない=呼び出し元に値を返さない関数では、returnは省略可、デフォルトのundefinedを返す。

sample.js
// デフォルトのundefinedを返す
return;

今回は以上です。

フロントエンド育成所のもりけん塾に参加しています。

もりけん塾を運営している森田賢二さんのブログ
https://kenjimorita.jp/
@terrace_techのTwitter
https://twitter.com/terrace_tech

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(´ω`) わいが清掃のお仕事してた頃に作ったアプリってのがある

ホテル清掃の経験ある人フォロワーになってくれないか。
3つ目の記事になるがQiitaからフォローしてくれた人は0だ。来てくれたら歓迎するよ。

何をしてたか

ホテル清掃をしていた頃、全ての作業は分担されておりベッドの人はベッドを何部屋もやる。バスルームも同じだ。おかげで某ビジネスホテルだがシングルのベッドなら3分で出来るようになったしバスルームでも7、8分で行えるようになった。
つまり、みんながキーボード触っている時期にわいは便所に手を突っ込んで掃除しながらたまに便器からはみ出た?をキレイにしてた。それから3時間56台の勢いでベッドをひたすら組んでいた。とはいっても毎日じゃない。

何をしたいか

ただし、部屋数が増えてくればだれてくるし汚れている部屋であれば多少なりとも予定作業時間を過ぎてしまう。といったことが起こる。当たり前の話だ。そういったときにこのアプリが活かせる。自分が遅れているのか早く進んでいるか把握できるようになる。

何を作ったか

タイマーだ。部屋数と時刻を2か所入力する(作業開始時刻と作業時間)。あとは?ボタンを押すごとに時間が記録され予定完了時刻より早ければ緑、遅ければ赤に行色が変わる。

image.png

何を使ったか

・画面は Fomantic-UI
・時刻入力は CleaveJS
・現在時刻取得は MomentJS
・部屋毎の作業時間計算は MomentDuration
・また比較演算子を関数で行いたかったから Predicate

image.png

どう作ったか

index.html
<!doctype html>
 <html>
  <head>

   <title></title>

   <meta charset='utf-8'>
   <meta content='' name='author'>
   <meta content='' name='description'>
   <meta content='' name='application-name'>
   <meta content='telephone=no,address=no,email=no,date=no,url=no' name='format-detection'>
   <meta content='noimageindex,notranslate,nosnippet,noarchive,nofollow,noindex' name='robots'>
   <meta content='width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no' name='viewport'>

   <link href='css/fomantic-ui/2.7.8.min.css' rel='stylesheet' media='screen'>
   <link href='asset/progressive-web.json' rel='manifest'>
   <link href='asset/favicon.ico' rel='icon'>

   <style name='font'>
     @font-face{
       font-family:'M+2VM+IAPG circle';
       src:url('asset/m+2vm+ipag-circle.ttf');
     }
     @font-face{
       font-family:'gidole';
       src:url('asset/gidole.ttf');
     }
  </style>

   <style>
     /*
      * 視認
      */
     html,
     body{
       font-size:14px;
     }
     .ui.table td,
     .ui.table input{
       font-family:'gidole';
     }
     .ui.table>thead>tr>th,
     .ui.table>tbody>tr>td{
       padding:0;
     }
     /*
      * 入力
      */
     .ui.table input[type='number']{
       -moz-appearance:textfield;
     }
     .ui.table input::-webkit-outer-spin-button,
     .ui.table input::-webkit-inner-spin-button{
       -webkit-appearance: none;
     }
     .ui.table input{
       background:transparent;
       text-align:center;
       appearance:none;
       outline:none;
       border:none;
       width:100%;
     }
     /*
      * 見栄
      */
     .ui.table thead tr:first-child>th:first-child{
       width:3em;
     }
     .ui.card,
     .ui.label,
     .ui.table,
     .ui.buttons{
       border-bottom:2px solid rgba(34,36,38,.15) !important;
       box-shadow:rgba(16, 36, 94, 0.4) 0 2px 6px 0 !important;
     }
     .ui.card .description p{
       font:12px 'M+2VM+IPAG circle';
       line-height:1.2;
       color:black;
     }
     /*
      * アイコン
      */
     i.icon.thumbs-up:before {
       content: '\f164';
     }
  </style>

 </head>
  <body>
   <main>
     <div class='ui grid padded'>
       <div class='row'>
         <div class='column'>
           <div class='ui card fluid'>
             <div class='image'>
               <img src='asset/businesshotel-9956-400x400.png'>
            </div>
             <div class='content'>
               <div class='description'>
                 <p>ホテル清掃の進捗を管理するアプリです。予定通り進んでいるか緑と赤のバランスでわかります。上のテーブルを入力して作業を開始しましょう。</p>
              </div>
            </div>
          </div>
        </div>
      </div>
       <div class='row'>
         <div class='column'>

           <table class='ui table unstackable celled fixed center aligned blue'>
             <thead>
               <tr>
                 <th></th>
                 <th>開始時刻</th>
                 <th>作業時間</th>
              </tr>
            </thead>
             <tbody>
               <tr>
                 <td><input type='number' v-model.number='rooms'></td>
                 <td><input type='tel' v-model='time.base' ref='v1'></td>
                 <td><input type='tel' v-model='time.task' ref='v2'></td>
              </tr>
            </tbody>
          </table>

           <table class='ui table unstackable celled fixed center aligned blue'>
             <thead>
               <th>No.</th>
               <th>予定時刻</th>
               <th>完了時刻</th>
            </thead>
             <tbody>
               <tr v-for='(no,i) in rooms' :class="[lt(i)]">
                 <td>{{no}}</td>
                 <td>{{estimate[i] | HHmmss }}</td>
                 <td>{{complete[i] | HHmmss }}</td>
              </tr>
            </tbody> 
          </table>

           <div class='ui buttons two blue'>
             <button class='ui button icon' @click='reset'>
               <i class='icon file'></i>
            </button>
             <button class='ui button icon' @click='stamp'>
               <i class='icon thumbs-up'></i>
            </button>
          </div>

        </div>
      </div>
    </div>
  </main>
 </body>

  <!-- native -->
  <script src='js/native/moment-2.24.0.min.js'></script>
  <script src='js/native/moment-duration-format-2.3.2.min.js'></script>
  <script src='js/native/predicate-1.2.0.min.js'></script>
  <script src='js/native/cleave-1.5.3.min.js'></script>

  <!-- vue -->
  <script src='js/vue/2.6.10.js'></script>
  <script>
    new Vue({
      el:'main',
      data:{
        /*
         * 入力用のデータ
         */
        time:{
          base:null,    /* 作業開始時刻        */
          task:null,    /* 作業平均時間(作業者)*/
        },
        rooms:10,       /* 作業予定個数(部屋数)*/

        /*
         * 進捗用のデータ
         */
        estimate:[      /* 作業完了予定時刻     */
        ],
        complete:[      /* 作業完了実際時刻     */
        ],

        /*
         * 入力用のバリデーション(時刻と時間)
         */
        validation:{
          time:true,
          timePattern:['h','m','s']
        }
      },
      filters: {
        HHmmss:function(ts) {
          if(ts !== undefined){
            /* 何時:何分:何秒 */
            return moment(ts).format('HH:mm:ss')
          }
        }
      },
      computed:{
        timeChanged:function(){
          /* 部屋数と時刻が修正されたことを監視 */
          return this.rooms && this.time
        },
      },
      watch:{
        timeChanged:{
          deep:true,
          handler:function(){
            /* 再計算 */
            this.calc()
          }
        }
      },
      methods:{
        lt:function(i){
          /* 予定より遅い or 早い */
          return (i < this.complete.length) ? 
            (predicate.lt(this.estimate[i],this.complete[i]) ? 'negative' : 'positive') : null
        },
        stamp:function(){
          /* 作業完了 */
          if(this.complete.length < this.rooms) {
            this.complete.push(moment().valueOf())
          }
        },
        reset:function(){
          /* 初期化 */
          this.complete.splice(0,this.complete.length)
        },
        calc:function(){
          /* 計算 */
          var base = moment(this.time.base,'HH:mm:ss')
          var task = moment.duration(this.time.task)

          for(var i=0;i<this.rooms;i++){
            var val = base.add(task).valueOf()
            if(val){
              this.estimate[i] = val
            }
          }
        },
        init:function(){
          /* HH:MM:SSによる入力検証 */
          new Cleave(this.$refs.v1,this.validation)
          new Cleave(this.$refs.v2,this.validation)
        }
      },
      mounted:function(){
        this.time.base = '11:30:00' /* 作業開始 */
        this.time.task = '00:09:00' /* 作業時間 */
        this.init()       /* NN:NN:NNの入力検証 */
        this.calc()       /* 計算(作業完了時刻) */
      }
    }) 
 </script>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chromeで回転させる拡張機能作ってみた

はじめに

動画を見ていて横向きの動画を縦長に表示された経験はありませんか?例えばこんな感じです。

こんな感じで(上の画像はあたりさわりのない画像をとってきてるので縦横もくそもありませんが…)横に黒い空白ができてうざいという悩みを解決する機能を、拡張機能として実装していきます。

やること

筆者はGoogle Chromeを使っているのでChrome用の拡張機能を作っていきます。ちなみにブラウザはChromeがおススメです。なぜなら拡張機能を入れるのがすごく簡単だからです(後述)。

概要としてはctrl+矢印キーで操作をしたいと思います。右を押したら右回りに90度回転、左を押したら左に90度回転した状態になるようにする、そして上を押したら回転を戻すように書いていきます。

設定

まずはjsonファイルを書いていきます。これは拡張機能の全般的な設定をするところです。
名前やバージョン、実行の仕方及びファイルを書いていきます。ここでは自分用にローカルで実行するだけなので、最低限書くべきものを書きます。

manifest.json

{
    "manifest_version": 2,
    "name": "rotate",
    "version": "1.0",

    "browser_action": {
        "default_title": "rotate"
    },
    "content_scripts": [
        {
          "matches": ["http://*/*", "https://*/*"],
          "css": ["rotate.css"],
          "js": ["rotate.js"]
        }
    ]
}

"content_scripts"というところにファイルを記述すると、ウェブページのhtmlに対して操作を行うcss,jsファイルを指定できます。ページはすべてのページに対して行うように設定しています。

CSS

CSSでは回転したときに指定するクラスを書いていきます。回転角は右回りで指定します。

rotate.css
.rotate-right{
    transform: rotate(90deg);
}

.rotate-left{
    transform: rotate(270deg);
}

JavaScript

JSではキーが押されたときにCSSで設定したクラスを付与するようにします。キーの同時押しを認識しなければなりませんが、ctrlキーに関してはevent.ctrlKeyでT/Fが返ってくるのでそれを使います。

rotate.js
var video = document.querySelector('video');

document.addEventListener('keydown', function(event) {
    if (event.ctrlKey){
        if (event.keyCode == 37){ //左
            video.classList.add("rotate-left")
            video.classList.remove("rotate-right")
        } else if (event.keyCode == 39){ //右
            video.classList.add("rotate-right")
            video.classList.remove("rotate-left")
        } else if (event.keyCode == 38){ //上
            video.classList.remove("rotate-left")
            video.classList.remove("rotate-right")
        }
    }
});

丁寧に書くならremoveの前にcontainsで有無を確かめるべきかもしれませんが、これで動くので十分でしょう。

Chromeの設定

まずは右上の拡張機能のアイコン(?)を押して一番下の拡張機能を管理を押します。
image.png

次に右上のデベロッパーモードを有効にしてパッケージ化していない拡張機能を読み込むを押します。するとフォルダを選択できるので、manifest.jsonがあるフォルダを指定すると読み込まれます。簡単でしょ
image.png

拡張機能を更新したときはこのページで読み込みボタンを押してページをリロードすれば適用されます。

表示はこのようになりました。大きさについては複数のページに対応させたい関係上指定していませんし、回転中心も中心のままなので周囲に枠ができちゃっていますが、許容範囲でしょう。もしこのページだけに使いたいみたいなことであれば、ピッタリになるように設定してもよさそうです。
image.png

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Nuxt.js】非同期するなら、$axiosでしょ

標準搭載のaxiosと、Nuxtモジュールのaxiosの比較

モジュール axiosの表記 リクエストメソッドの表記 レスポンス値の取得
axios axios get res.data
@nuxtjs/axios $axios $get res

セットアップ

$ yarn add @nuxtjs/axios
nuxt.config.js
modules: [
  '@nuxtjs/axios',
],
axios: {
},

Usage getリクエスト

asyncData関数

script
export default {
  async asyncData({ $axios }) {
    const res = await $axios.$get('URL')
    return {
      data: res
    }
  }
}

そのほかでの関数内

script
export default {
  methods: {
    async function () {
      const res = await this.$axios.$get('URL')
      this.data = res
    }
  }
}

Usage postリクエスト

script
export default {
  data () {
    return {
      user: {
        name: '',
        email: '',
        password: '',
      }
    }
  },
  methods: {
    submit: async function () {
      const res = await this.$axios.$post('URL', this.user)
      console.log(res)
    }
  }
}

ルーティング内のid取得に関する比較

関数 /_idの取得
async asyncData (context) context.params.id
その他 this.$route.params.id

番外編:ES6のDestructuring(分割代入)を利用した記述法

関数の引数の省略

script
async asyncData(context) {
  const posts = await context.$axios.$get('/posts')
  return { posts }
},

script
async asyncData ({ $axios }) {
  const posts = await $axios.$get('/posts')
}

関数の引数 & レスポンス値の定数の省略

script
async asyncData (context) {
  const res = await axios.get('/posts/' + context.params.id)
  return { post: res.data }
}

script
async asyncData ({ params }) {
  const { data } = await axios.get('/posts/' + params.id)
  return { post: data }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

2020年に最も人気があるプログラミング言語 10選

元記事: https://jp.scrapestorm.com/tutorial/10-most-popular-programming-languages-in-2020/

プログラミングの初心者について、より良い見通しがある言語を選択するは非常に重要です。本文は十種類の2020年に最も人気があるプログラミング言語を紹介します。

まずTIOBEランキングを見てみます。非常に権威のあるランキングです。次の14つは長く生きているので、SQLがGOに置き換えられたことを除いて、他の9つのプログラミング言語がすべて存在し、長い間リストを支配していることがわかります。これから一つずつ紹介しましょう。
無題.png

1.Java

Javaは実際にはC ++の代替品です。Sunは当初、C ++よりも簡単なオブジェクト指向プログラミング言語を開発したいと考えていました。時間の経過とともに、Javaは学習とクロスプラットフォーム化が容易になるため、Javaの人気はC ++の人気をはるかに超えています。
Java仮想マシンの助けを借りて、JavaはLinux、Windows、Mac-OSなどのさまざまなオペレーティングシステムで自由に使用できるため、エンタープライズレベルの開発で非常に人気があります。

2.C++

名前からわかるように、C ++はC言語の拡張機能であり、C言語のオブジェクト指向関数の作成を目的としています。
C ++はすべてのプラットフォームで実行でき、あらゆるタイプのハードウェアを効果的に利用できるため、リソースが限られたプラットフォームで最高のパフォーマンスを発揮できます。

3.C

C言語は1960年に誕生し、作成者チームはC言語に対して1つの要件しか持っていませんでした。それは、普遍的であり、システムリソースを効果的に使用できる必要があります。 当時、メモリのすべてのバイトが高価だったからです。C言語は非常に早い時期に誕生しましたが、依然として最も一般的に使用されているプログラミング言語の1つです。
C ++と同様に、Cもメモリに直接アクセスしてハードウェアを制御できます。 Cはオペレーティングシステムと密接に関連しており、プログラマーはメモリ割り当ての詳細を処理する必要があるため、把握するのが困難です。

4.Python

学習コストは非常に低いため、すべてのプログラマーはPythonを好みますが、現在の非常に深い人工知能、機械学習、データ分析などのアプリケーションレベルは非常に高くなっています。
Pythonの構文はシンプルでエレガントで、コミュニティも非常に活発です。 しかし、言うべきことの1つは、Pythonの職位には高い学歴が必要であることです。

5.JavaScript

JavaScriptは高級なダイナミンクプログラミング言語です。非常に人気のあるフロントエンドフレームワークVue.jsはjsJavaScriptで作成しました。フロントエンド開発に従事したい場合は、JavaScriptは必須であると言えます。

6.C

当初、C#はJavaのコピーと見なされていましたが、それらには著しい類似点があり、Javaとほぼ同じ構文であり、コンパイルして実行する必要があります。 時間の開発とMicrosoftによる多大な努力により、C#は豊富なクラスライブラリとフレームワークを蓄積し、開発者はこれに基づいて.NETプラットフォームに基づくさまざまなアプリケーションをすばやく作成できます。

7.Swift

Swiftは、Appleによって作成された強力で直感的なプログラミング言語であり、iOS、Mac、Apple TV、およびApple Watch用のアプリの開発に使用できます。 開発者に完全な自由を提供することを目的としています。 Swiftは使いやすくオープンソースであり、アイデアさえあれば、誰でも素晴らしいものを作成できます。

8.Go

Go言語のデザインは非常に洗練されており、使用方法も非常に簡単で、開発と拡張を解決する能力も非常に優れています。 重要なのは、学習が非常に簡単であり、これらの利点がGo言語の急速な成長に貢献していることです。
Google、AWS、Cloudflare、CoreOSなどはすべて、クラウドコンピューティング関連製品の開発にGolangを大規模に使用し始めています。 未来はとても明るいと言えます。

9.PHP

PHPは35年以上にわたってWebアプリケーションの開発に使用されてきました2010年頃、PHPは常にWeb開発の王様でした。特に、WordPressなどのコンテンツ管理プラットフォームの人気とFacebook(PHPが開発した)の支持が相まって、業界でのPHPの地位が強化されました。

10.Ruby

Rubyはもともとオブジェクト向けのスクリプトプログラミング言語でしたが、時間が経つにつれて、徐々に解釈された高水準の汎用プログラミング言語に発展しました。 開発者の生産性を向上させるのに非常に役立ちます。シリコンバレーでは、Rubyは非常に人気があり、クラウドコンピューティング時代のWebプログラミング言語として知られています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

この画像ファイルは正しいJavaScriptファイルです

以下はSebastian Stamm ( Twitter / GitHub / Webサイト )による記事、This Image Is Also a Valid Javascript Fileの日本語訳です。

01.gif

This Image Is Also a Valid Javascript File

画像は普通はバイナリファイルであり、Javascriptファイルは基本的にテキストファイルです。
いずれも固有のルールに従わなければなりません。
画像ファイルは、データをエンコードするための具体的なフォーマット形式が決まっています。
Javascriptファイルは、実行するためには特定の構文に従わなければなりません。
ところでふと気になりました。
Javascriptとして実行可能な有効な構文を持っている画像ファイルを作成することはできるでしょうか?

ここより先を見る前に、実験の結果を以下のサンドボックスで確認してみることをお勧めします。

https://codesandbox.io/s/executable-gif-8yq0j?file=/index.html

自身で画像をダウンロードして確認してみたければ、こちらからダウンロードできます。

https://executable-gif.glitch.me/image.gif

Choosing the Right Image Type

残念ながら画像には大量のバイナリデータが含まれているため、そのままJavascriptとして解釈しようとするとエラーになります。
そこで私がまず考えたのは、次のようなものでした。
以下のように、全ての画像データをコメントに入れたらよいのではないかということです。

/*ALL OF THE BINARY IMAGE DATA*/

これは有効なJavascriptファイルになります。
しかし、画像ファイルは特定のバイト列で開始される必要があります。
たとえばPNGファイルの先頭は常に89 50 4E 47 0D 0A 1A 0Aです。
最初が/*で始まっていたら、それは有効な画像ファイルではありません。

このヘッダを見ていて次のアイデアを思いつきました。
バイト列を変数名にして、バイナリは文字列として代入できればよいのではないかということです。

PNG=`ALL OF THE BINARY IMAGE DATA`;

バイナリデータには改行コードが含まれている可能性があり、改行コードの扱いはテンプレートリテラルのほうが優れているので、通常の"'ではなくテンプレートリテラルを用いることにしました。

残念ながら、ほとんどの画像ファイルはヘッダ部のバイト列に変数名として許されない文字を含んでいます。
しかし、それが可能な画像フォーマットをひとつ発見しました。
GIFです。
GIFファイルのヘッダは47 49 46 38 39 61で、これは文字列GIF89aをASCIIで綴ったものであり、すなわち完全に合法ということです。

Choosing the Right Image Dimensions

有効な変数名で始めることができる画像フォーマットを見つけたので、次はそこに等号=とバックティック`が必要です。
従って、ここではファイルの次の4バイトを3D 09 60 04とすることにします。

02.png

GIFフォーマットでは、ヘッダの次の4バイトは画像のサイズを表します。
この中に等号の3Dとバックティックの60を埋め込まなければなりません。
GIFはリトルエンディアンであるため、画像サイズにはそれぞれ2バイト目が大きな影響を与えます。
何万ピクセルもある画像にならないように、3D60は下位バイトに格納することにしました。

画像サイズの残ったバイトには、GIF89a= `という有効文字列を残すために空白文字を入れます。
最も画像幅を小さくできる有効な空白文字は水平タブ09であり、従って画像幅は3D 09、リトルエンディアンでは2365になります。
思ったよりは大きいですが、まだ妥当なサイズです。

画像の高さについては、ちょうどよいアスペクト比になりそうなものを選ぶことができます。
今回は04を選択したので、画像の高さは60 04、すなわち1120です。

Getting our own script in there

このJavascriptは今のところ何もしません。
グローバル変数GIF89aに文字列を代入しているだけです。
せっかくなので何か面白いことがおこるようにしたいですよね。
しかし、GIFファイル内のほとんどは画像をエンコードするためのデータなので、そこにJavascriptを入れようとすると、それは単に壊れた画像になってしまうでしょう。
ところで何故か、GIFフォーマットにはComment Extensionという機能が含まれています。
これはGIFデコーダで解釈されないメタデータを保存する場所であり、すなわちJavascriptのロジックを配置するのに最適な場所ということです。

Comment ExtensionはColor Tableのすぐ後ろに配置されています。
ここには任意の文字列を入れることができるため、変数GIF89aの文字列を閉じることも簡単です。
そこで任意のJavascriptを記述し、最後にコメント開始を入れることで、残りの画像の部分をJavascriptパーサに解釈されないようにします。

全体として、ファイルの中身は以下のようになります。

GIF89a= ` BINARY COLOR TABLE DATA ... COMMENT BLOCK:

`;alert("Javascript!");/*

REST OF THE IMAGE */

ただし、少しばかり制限があります。
Comment Extensionは複数のサブブロックで構成する必要があり、ブロックごとの最大サイズは255です。
そしてサブブロックの終わりには、次のサブブロックの長さを表すバイトを記述しなければなりません。
従って、大きなスクリプトを入れたい場合は、以下のように小さな部品に分解していく必要があります。

alert('Javascript');/*0x4A*/console.log('another subblock');/*0x1F*/...

コメント中に書かれているhexcodeは、次のサブブロックの長さを表しています。
これはJavascriptには不要ですが、GIFフォーマットとしては必要です。
従って、Javascriptの邪魔にならないようにJavascriptコメント中に書かなければなりません。
この問題を解決するため、スクリプトをチャンクに分けるための小さなスクリプトを書きました。

https://gist.github.com/SebastianStamm/c2433819cb9e2e5af84df0904aa43cb8

Cleaning up the Binary

基本的な構造が分かったので、次は画像データのバイナリがJavascript構文を壊さないようにする必要があります。
上で説明したように、ファイルには3つのセクションがあります。
ひとつめは変数GIF89aへの代入部分、ふたつめがJavascriptコード、最後は複数行コメントです。

まずは最初の変数代入部分を見てみましょう。

GIF89a= ` BINARY DATA `;

バイナリデータに`、もしくは${が入っていたら、そこでテンプレート文字列が終了したり無効な式が生成されてしまうため困ったことになります。
この修正はとても簡単です。
すなわち、バイナリを変更するだけです。
たとえばバイナリ中にASCIIコード60があったら、それを61、すなわち文字aに変更します。
ここはカラーパレットの定義部分のデータを変更することになるため、結果として一部のカラーコードが、たとえば#286048から#286148へと微妙に変化することになります。
しかし、この違いに誰かが気付く可能性は非常に低いでしょう。

Fighting the corruption

Javascriptコードの最後に、バイナリデータがJavascriptに影響しないようにコメント開始コードを書きました。

alert("Script done");/*BINARY IMAGE DATA ...

もし画像のバイナリに*/が含まれていたら、そこでコメントが終了してしまい、不正なJavascriptファイルになってしまいます。
従って、ここでも同じように2文字のうちどちらかを変更することで、コメントが終了しないようにすることにします。
しかし、ここは既にエンコードされた画像セクションであるので、単純に書き換えるだけではこのように画像が壊れてしまいます。

03.gif

極端な場合、画像が全く表示されなくなることすらありました。
しかしどの文字を変更するかを慎重に選択することで、画像の破損を最小限に抑えることができました。
幸いなことに、問題になるような*/はほんの少しだけでした。
最終的な画像は、Valid Javascript File文字列の下のほうなどに僅かな破損が見えますが、概ね満足のいく仕上がりになっています。

Ending the File

最後にしなければならない問題は、ファイルの最後にあります。
ファイルの末尾は00 3Bで終わらせなければならないため、最後までコメントで埋めることはできません。
ファイルの終わりであるため、バイナリデータの変更は目に見えるような画像の破損を起こしません。
従って、Javascriptが正常に動くように、コメントブロック終了後に1行コメントを追加しました。

/* BINARY DATA*/// 00 3B

Convincing the Browser to Execute an Image

ここまできて、ついにJavascriptとして有効な画像ファイルが完成しました。
しかし、最後にまたひとつ問題があります。
この画像をサーバにアップロードし、scriptタグで読み込もうとすると、次のようなエラーが発生する可能性があります。

Refused to execute script from 'http://localhost:8080/image.gif' because its MIME type ('image/gif') is not executable.

ブラウザは『こいつは画像ファイルだから実行なんてしないぞ』と拒否します。
大抵の場合、これは適切な動作と言えましょう。
しかし、私はこれを実行したいのです。
ということで、その解決策はそのファイルが画像であることをブラウザに伝えないことです。
そのためだけに、ヘッダ情報を送らず画像ファイルだけを返すサーバを作りました。

ヘッダのMIME typeがなければ、ブラウザはそのファイルの正体が何であるかわからず、コンテキストに即したものを実行します。
すなわち、<img>タグでは画像として表示し、<script>タグではJavascriptとして実行します。

But ... why?

なんのため?
それは、私がまだ解明できていないことです。

これを作るのは精神的によい挑戦でした。
もし、これが実際に役立つようなシナリオを思いついたら、ぜひ教えてください!

コメント欄

「面白かった!」
「?」
「こいつはクールだ……ぜ??」
「画像ファイルにトラッカーを仕込むとか?もうひとつ思いついたのは、単純にハッキングとか。」「画像に情報を仕込む技術は既にある。ステガノグラフィでぐぐれ。」
「セキュリティへの影響について考えてる。imgタグに入ってる画像をブラウザが勝手にJavaScriptとして実行したら最悪だ。」「imgタグはJavaSctiptと解釈されないから問題ない。」

感想

img srcscript srcどちらで読み込んでも正しく動作する、面白ファイルが出来上がりました。

手法自体はコメントにバイナリを埋め込むというもので、古来より幾度となく行われているものですが、今回はJavaScriptを動くようにしたうえで画像としても破綻無く仕上げているところが面白いですね。

この手法の実用的な例というとCutwailがありますが、これは別に正しい画像である必要はないからちょっと違うかな。

今はセキュリティ上できないようになっているはずですが、この手法を突き詰めたらいずれ、
・Webサイトにアクセスしたら、一枚の画像ファイルが落ちてきた
・画像の表示が終わったと思ったら何故かHTMLになっていた
みたいなこともできるかもしれませんね。

そんなことをやって何のメリットがあるのかさっぱりわかりませんが。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Moment.js → Day.js の移行は全置換じゃだめ。イミュータブルですので

タイトルで言いたいことがわかった人は読む必要のない話です。

サマリ

Day.js は JavaScript の軽量な日付操作ライブラリで、Moment.js のAPIと互換性があるため、Moment.jsからの移行先として候補にあがることが多いです。

Moment.js との大きな違いとして、単体で Locale の情報を含んでおらず、プラグインを追加して利用する必要があります。
これについてはこの記事では割愛します。

もうひとつ、Day.js は Moment.js と違って immutable(不変オブジェクト) であるという特徴を持ちます。
個人的にこれは Moment.js と比べて優れた点であると思いますが、移行の際は気をつけないと、発覚の遅れるバグを生む危険性があります。

日本語の紹介記事をざっとググった限り、この点にちゃんと言及している記事が見つからず、誰かが不幸になりそうな気がしたので書いておきます。

検証バージョン

  • Moment.js: 2.28.0
  • Day.js: 1.8.36

何が危険か

前述のとおり、Day.js は Moment.js となまじ互換性のある API を持つため、ソースコード内で moment と書いているところをガバっと dayjs に置換してしまえば、それでほとんど問題なく動いてしまいます。

実際そのような説明をしている日本語情報もいくらか見つかるのですが、 addsubtract といった操作関数の使い方によっては、意図しない結果を生むことがあります。

具体例

Moment.js を使い、日付に1日を加算してから文字列化するコードです。

const m = moment("2020-12-31");
m.add(1, "days");
console.log(m.format("YYYY/MM/DD"));

出力結果は「2021/01/01」となります。
これを Day.js に置き換えます。

const d = dayjs("2020-12-31");
d.add(1, "days");
console.log(d.format("YYYY/MM/DD"));

出力結果は 「2020/12/31」 となります。

解説

蛇足ながら解説しておきます。

Moment.js の add 関数は、戻り値で加算後のオブジェクトを返すと同時に、自身の値も書き換える挙動をします。

const m1 = moment("2020-12-31");
const m2 = m1.add(1, "days");
console.log(m1.format("YYYY/MM/DD"));   // 2021/01/01
console.log(m2.format("YYYY/MM/DD"));   // 2021/01/01
console.log(m1 === m2); // true(実は同じオブジェクト)

Moment.js 単体で見た場合、この挙動はしばしばバグを生む原因でもありました。
もっと凶悪なのは、 startOf という、副作用を持つことが想像しにくい関数さえも内部状態を書き換えてしまうことです。

const m1 = moment("2020-12-31");
const m2 = m1.startOf("month");
console.log(m1.format("YYYY/MM/DD"));   // 2020/12/01
console.log(m2.format("YYYY/MM/DD"));   // 2020/12/01
console.log(m1 === m2); // true

Day.js はこのような挙動を改善するため、これらの関数を内部状態を書き換えないイミュータブルな仕様に変更しています。
最初から Day.js を採用する場合は望ましい挙動ではありますが、Moment.js で動いていたものをそのまま置き換えられるわけではない点に注意が必要です。

const d1 = dayjs("2020-12-31");
const d2 = d1.startOf("month");
console.log(d1.format("YYYY/MM/DD"));   // 2020/12/31
console.log(d2.format("YYYY/MM/DD"));   // 2020/12/01
console.log(d1 === d2); // false

結び

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

配列の間に特定要素を挿入するコード(javascript)

コード

const data = [1, 2, 3];
const separater = 0;

const result = data.map((item, index)=>{
  return [index * 2, item]
}).concat(
  Array.from({length: data.length - 1})
  .map((_, index) => [index * 2 + 1, separater])
).sort()
.map(tuple=>tuple[1]);

console.log(result);
// [ 1, 0, 2, 0, 3 ]

解説

1. データに偶数インデックスを貼ります

data.map((item, index)=>{
  return [index * 2, item]
})

2. 奇数インデックスを貼った挿入データの配列を作ります

  Array.from({length: data.length - 1})
  .map((_, index) => [index * 2 + 1, separater])

3. 1-2を結合してソートをかけて値だけ抜いてきます

const result = data.map((item, index)=>{
  return [index * 2, item]
}).concat(
  Array.from({length: data.length - 1})
  .map((_, index) => [index * 2 + 1, separater])
).sort()
.map(tuple=>tuple[1]);

用途

Reactで要素の間に<hr/>要素を挟み込みたかったので作りました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

時間はないけれど、最短最速でプログラミングを身に付けたい!【基礎の基礎】

超ハイパースペシャル初心者の“ぴおにあ”でーす:v:

最初に投稿してから一か月以上たってしまった。はやっ。

さて、この約一か月間、会社員、かつ学生で時間が無いド素人の私が、最短最速独学でプログラミングを習得するために何をしていたか。

今日はそれをお教えしましょう(ΦωΦ)フフフ…

今までは本当にプログラミングのプの字もわかっていなかったので、Qiitaの書き方も全くわかりませんでした。

わろ先生に以下のページを教えていただいたものの、書いてあることは当然未知のことだらけ^^;

参照: Markdown記法 チートシート

「えっ、Qiitaって何かの言語を使って書くの?何の言語なの?私はまだHTMLの基礎の基礎しか知らないよ?」
そうなんです、言語とは何かさえよく分かっていなかった(いや今も分かっているわけではないけれど、汗)

わはは? そんな状態からは一歩脱出いたしました。というわけで、今回はこれを参考に少し見やすく書いてみましたよ。

基礎知識

HTML・CSS・Java Scriptの基礎知識

とにかく、MDN Web Docを熟読。
これは本当に良いです。超初心者におすすめ。ページは少し真面目でとっつきにくいんだけど、
無料でこんなに勉強できてしまうなんて少し感動すら覚えました。説明もなかなか丁寧。これを何度も読みました。

参照: MDN Web Doc

Qiitaやドットインストールを参考に

超初心者にドットインストールは優しい!

ドットインストールは3分以内の無料動画(2020年9月21日時点で436レッスン、6516本の動画がアップ)でプログラミングを学ぶ準備からガイドしてくれます。3分間なので要所を抑えたい時や調べものの時、このサイト内でキーワード検索するなどして使用し、大変便利です。

参照: ドットインストール 


Qiitaは知や経験が豊富に公開されている

少し新鮮な驚きなのですが、エンジニアさんたちの世界って共有が一つのキーになっているんですね。素敵な世界❤️
見知らぬ多くの人たちが、経験や知識を惜しみなく共有してくれている場がこんなにたくさんある(私が知っているのはQiita、Github、MDN、dotinstallくらいですが)ことに嬉しい驚きです。
これを存分に使いましょう。タグで検索し不明点を調べたりしています。

参照:  Qiita:わろ先生の「JavaScriptによる「順次」「繰り返し」「分岐」」ページ

動画学習

Udemy
 
動画学習にも挑戦!!! 以下参照ページのわろ先生おすすめコースの中から、ちょうどUdemyがセールをしていたので1500円のHTML・CSS・Java Script の初級コースを3つ購入。(プログラミングの勉強で初出費)
全部で9時間程度のコースをひとつまずは受講してみました。動画学習は実際にコードを書いてそれがどう表示されるのかを見ることができるので、記憶に残りやすいと感じます。
私の場合には時短のために、速度を1.5倍速に上げて聞きました。1.5倍でも講師の説明は十分聞き取れます。分からないところはメモして何度でも見る癖をつけるようにしたいです。

参照: 【保存版】Udemy 350コース突破記念 講師に全力 リスペクト企画 オススメ コース紹介【入門者向け】

Youtube

Blockchainの勉強
 
これは技術ではなく、概念を学ぶため。最初にyoshitechで事前資料として指定された以下の動画や同じIBM Japan channnel の「ブロックチェーンの仕組み」などを何度も見ています。

参照: ブロックチェーンとは何か? by IBM Japan channnel


私は目的がBlockchainを使ったエシカルな仕組み作りなので、HTML/CSS の凝った装飾テクニックなどは後回しにしてJavaScriptへ進んでいます。
今の時点で、平均1日50分間程度は学習に当てられているようですが、学校のテスト期間やレポート次第では増減するでしょう。

状況を見ながら、学習環境なども見直してゆきたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

配列の操作(Javascript)

Pushメソッド

後から配列に値を追加したい場合はpushメソッドを使用する

配列名.push(追加したい値)

forEachメソッド

配列の中を順番に取り出したいときに使用

配列名.forEach((引数名)=>{
  処理;
});

上記のように引数内に入っている関数をコールバック関数と呼ぶ

findメソッド

コールバック関数内に記述した条件式の中に当てはまる値の最初の一つの要素全てを取り出すメソッド

const 定数名=配列名.find((定数名)=>{
  return 条件式;
});

配列の要素がオブジェクトでも取り出しが可能

const 定数名=配列名.find((定数名)=>{
  return オブジェクトのプロパティ;
});

filterメソッド

findメソッドとは違い条件に合致した要素全てを取り出すことが可能

const 定数名=配列名.filter((定数名)=>{
  return 条件式;
});

find関数と同じように配列の要素がオブジェクトでも取り出しが可能

const 定数名=配列名.filter((定数名)=>{
  return オブジェクトのプロパティ;
});

map関数

配列内の要素全てに同様の処理を行い、その戻り値から新たな配列を作成するメソッド

const 定数名=配列名.map((定数名)=>{
  return 配列内の要素に対する処理;
});

find,filter関数と同じように配列の要素がオブジェクトでも取り出しが可能

const 定数名=配列名.map((定数名)=>{
  return 配列内の要素に対する処理;
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで親クラスのメソッドを呼び出す方法

前提

Javaだと親クラスのhogeメソッドを
super.hogeって感じで呼び出せるけど
Vanilla.JSはどーすんのって話。
ECMAScriptなんて知りません。
なお、Vanilla.JSにクラスの概念がないことは知ってます。

こうすればいい

main.js
function SuperClazz() {};
SuperClazz.prototype.hoge = function(param) {
    console.log("SuperClazz hoge: " + param);
};

function SubClazz() {
    SuperClazz.call(this);
};

SubClazz.prototype = Object.create(SuperClazz.prototype);
SubClazz.prototype.constructor = SubClazz;

SubClazz.prototype.hoge = function(param) {
    // ↓これ
    SuperClazz.prototype.hoge.call(this, param);
    console.log("SubClazz hoge: " + param);
};

// 実行
const subClazz = new SubClazz();
subClazz.hoge("I Love Vanilla JS");

実行

C>node main
SuperClazz hoge: I Love Vanilla JS
SubClazz hoge: I Love Vanilla JS

はいOK

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Deno v1.4.0で実装された新機能の紹介

はじめに

Deno@v1.4.0がリリースされました。
このリリースには多くの新機能が含まれています。
この記事ではそれらの新機能について紹介します。

まとめ

  • テストカバレッジの出力がサポートされた
  • deno runコマンドで--watchフラグがサポートされた
  • WebSocket APIが実装された
  • --unstableフラグを付与すると、isolatedModules及びimportsNotUsedAsValues: errorオプションが有効化されるようになった
  • deno installコマンドでインストールしたスクリプトがバンドルされるようになった
  • deno fmt及びdeno lintがチェックしたファイル数を表示するようになった
  • console.logでCSSによるスタイリングがサポートされた

テストカバレッジの出力がサポートされた

deno testコマンドにテストカバレッジのサポートが追加されました。
deno testコマンドを実行する際に、--coverage及び--unstableオプションを付与することで、テストカバレッジが出力されます。

$ deno test -A --coverage --unstable
Debugger listening on ws://127.0.0.1:9229/ws/d0d8502d-422f-4257-aa2a-db380af200e6
running 9 tests
test Application#evaluate ... ok (94ms)
test Application#exposeFunction ... ok (116ms)
test Application#onExit ... ok (78ms)
test Application#serveFolder ... ok (173ms)
test Application#serveFolder with prefix ... ok (199ms)
test custom executablePath ... ok (4ms)
test Chrome#evaluate ... ok (112ms)
test Chrome#load ... ok (119ms)
test Chrome#bind ... ok (106ms)

test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (1002ms)

test coverage:
file:///home/uki00a/ghq/github.com/uki00a/carol/deps.ts 100.000%
file:///home/uki00a/ghq/github.com/uki00a/carol/test_util.ts 82.609%
file:///home/uki00a/ghq/github.com/uki00a/carol/mod.ts 96.429%
file:///home/uki00a/ghq/github.com/uki00a/carol/chrome.ts 90.769%
file:///home/uki00a/ghq/github.com/uki00a/carol/locate.ts 53.488%
file:///home/uki00a/ghq/github.com/uki00a/carol/application.ts 88.462%
file:///home/uki00a/ghq/github.com/uki00a/carol/transport.ts 77.500%
file:///home/uki00a/ghq/github.com/uki00a/carol/logger.ts 87.500%

現時点(v1.4.0)では上記のようなテキスト形式の出力のみがサポートされます。将来的には、lcovjsonのようなフォーマットもサポートが検討されているようです。

deno runコマンドで--watchフラグがサポートされた

次のように、deno runコマンドを実行する際に--watch及び--unstableオプションを付与することで、Denoが指定されたファイルを監視します。そのファイル及び依存ファイルに変更が発生する度に、ソースコードの再コンパイル及び再実行が行われます。

$ deno run --watch --unstable sample.ts

現時点ではdeno runコマンドのみが--watchオプションをサポートしていますが、将来的にはdeno test等にも追加される見込みのようです。

WebSocket APIが実装された

Web標準に準拠したWebSocket APIが実装されました。使用する際は--allow-netフラグを指定する必要があります。

const ws = new WebSocket("ws://localhost:8080");

ws.addEventListener("open", () => {
  ws.send("Hello");
});

ws.addEventListener("error", (e) => {
  console.error(e);
});

ws.addEventListener("message", (e) => {
  console.log(e);
  ws.close();
});

ws.addEventListener("close", () => {
  console.log("closed");
});

この変更に伴い、std/wsモジュールで提供されていたWebSocketクライアント(connectWebSocket関数)は削除されました。(WebSocketサーバについては、従来どおりstd/wsモジュールで提供されます)

参考

https://developer.mozilla.org/ja/docs/Web/API/WebSockets_API

--unstableオプションを付与すると、isolatedModules及びimportsNotUsedAsValues: errorオプションが有効化されるようになった

現時点では--unstableオプションを指定したときのみ、これらのオプションが有効化されます。将来的には、--unstableオプションの有無に関わらず、デフォルトでこれらのオプションが有効化される見込みのようです。

この変更により、次のような影響があります。(--unstableオプションを指定したときのみ)

interfacetype等をimportする際はimport typeを使う必要がある

// NG?‍♂️
import { Application } from "./application.ts";

// OK?‍♂️
import type { Application } from "./application.ts";

interfacetype等を再exportする際はexport typeを使う必要がある

// NG?‍♂️
export { Hoge } from "./hoge.ts";

// OK?‍♂️
export type { Hoge } from "./hoge.ts";

参考

deno installコマンドでインストールしたスクリプトがバンドルされるようになった

追記) いくつかのスクリプトのインストールで問題が発生したため、この変更はv1.4.1でrevertされました。現在、swcを使ってdeno bundleコマンドの再実装が行われており、それが完了次第、この変更は再度denoに取り込まれる見込みのようです(#7461)

deno installでインストールしたスクリプトが、その依存関係等も含め、単一のJSファイルにbundleされるようになりました。

これにより、ユーザがスクリプトの依存モジュールを再読込した際に、意図せずしてスクリプトが壊れてしまう問題を回避できるなどの利点があるようです。

deno fmt及びdeno lintがチェックしたファイル数を表示するようになった

deno fmt及びdeno lintを実行した際に、チェックが行われたファイル数が表示されるようになりました。

これによって、ちゃんとチェックが行われたかどうか分かりやすくなっています。

$ deno fmt
Checked 13 files
# v1.4.0以前は、エラーがなければ何も表示されなかった

console.logでCSSによるスタイリングがサポートされた

%cを使うことで、CSSによるスタイリングができるようになりました。

以下のコードを実行すると、Denoという文字の色が緑に、背景色が青で表示されます。

console.log("Hello %cDeno", "color: green; background-color: blue");

スクリーンショット 2020-09-21 19-17-15.png

現時点では、以下のスタイルがサポートされています。

  • color
  • background-color
  • font-weight
  • font-style
  • text-decoration-color
  • text-decoration-line

スタイリングを無効化したいときは、環境変数NO_COLORtrueを設定します。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptの基本の書き方④

はじめに

前回までも物は以下から

Javascriptの基本の書き方①
Javascriptの基本の書き方②
Javascriptの基本の書き方③

継承

別のクラスから機能を継承して新たなクラスを作成することが可能
(継承元を親クラス,継承先を子クラスと呼ぶ)

class 今回作成するクラス名(子クラス) extends 継承元のクラス(親クラス){

}

他のファイルにクラスを継承させる方法

・他クラスへのエクスポート
クラス定義の後に記載

export default クラス名;

defaultエクスポートは1ファイルに1つだけ使用できる。

・別ファイルからのクラスのインポート

import 親クラス名 from "./親クラスが所属しているファイル名";

名前付きエクスポート,インポート

1つのファイルで複数エクスポートしたい場合export defaultではなく名前付きエクスポートを使用する

・名前付きエクスポート

export {値の名前}

・名前付きインポート

import { 値の名前 } from "./ファイル名"

,区切りで複数の値を一括でエクスポート,インポートすることも可能

パッケージの導入

パッケージをimportすることにより外部のプログラムを読み込み使用することができる

import パッケージ名 from "パッケージ名"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでTypeScriptを始めよう(TypeScriptの書き方)

はじめに

おはようございます。こんにちは。こんばんは。
Watatakuです。
今回はVue.jsでTypeScriptを始めよう(はじめかた)の続きなのですがそもそもTypeScriptの書き方が分からないのでそこからやっていきます。

この記事はの対象者はVueを勉強しているけど「TypeScriptベースでアプリを構築したい!」という方へ向けて書いております。

インストール

前回の章でVue.jsで環境を作った方は次の「データ型(アノテーション)」まで飛ばしてください。

「npm」を使ってインストールします。

$ npm install -g typescript

バージョンの確認方法は、次のとおりです。

$ tsc -v
Version 3.8.3
  1. Hello World 「Hello World!」と表示するスクリプトを作成します。

(1) 「helloworld.ts」の作成。

let str: string = 'Hello World!';
console.log(str);

(2) 「tsc 」コマンドでコンパイル。
「helloworld.js」が出力されます。

$ tsc helloworld.ts
tscのコマンドは次のとおり。

-h / --help: ヘルプの表示
--outDir <dir>: 出力先フォルダの指定
--outFile <file>: 出力先ファイルの指定
--sourceMap: ソースマップ(.map.js)の生成
-d / --declaration: 定義ファイル(.d.ts)の生成
-p / --project <dir>: tsconfig.jsonを含んだフォルダの指定
-t / --target <version>: ECMAScriptのバージョンの指定
-w / -watch: ファイル変更を監視して、自動コンパイル

(3) 「node」コマンドで実行。

$ node helloworld.js
Hello World!

データ型(アノテーション)

TypeScriptでは、変数、定数、関数、引数などの後ろに 「: 型名」を指定することで型を宣言することができます。

基本的なアノテーション

  • nunber
  • string
  • boolean

numberが数値、stringが文字列型、booleanがtrue,falseです。

let num: number;
let str: string;
let bool: boolean;

num = 1;
num = '1'; // Error

str = 'its';
str = 1; // Error

bool = true;
bool = 'true'; // Error

配列

配列の場合は後ろに 「: 型名[]」を指定する。

let Array: string[];

Array = ['its', 'hoge'];

Array[0] = 123; // Error
Array = ['its', 123]; // Error

any型

any型はすべての型と互換性があります。
any型にはどんな型でも代入でき、その変数を何にでも代入できます。

let hoge: any;

hoge = 'name'
hoge = 123

let num: number;
hoge = num;

基本的には使わないと思ってください。

void

voidは関数に戻り値がない時に使います。

let message: 'hoge';

function hoge(message: string): void {
    console.log(message);
}

object

インターフェース

インターフェースはオブジェクトがある構造に合っているかどうかの型チェックに使用されます。
インターフェースを定義することによって、変数の特定の組み合わせに名前を付けることができ、それらが常に一緒になることを確実にします。

interface User {
   name: string;
   full_name: string;
}

let name:User;
name = {
   name: 'hoge',
   full_name: 'hoge hoge';
};

name = {           // Error : `full_name` is missing
    name: 'hoge',
};
name = {           // Error : `second` is the wrong type
    name: 'hoge',
    full_name: 123;
};

union

union型は、プロパティを複数の型を定義して、そのどれかに当てはまると使用可能にできるものです。

type strOrNum = String | Number;

strOrNum = 'hoge';
strOrNum = 123;
strOrNum = true // Error

タプル型

TypeScriptはタプル型という型も用意しています。ただし、JavaScriptにはタプルという概念はありません。そこで、TypeScriptでは配列をタプルの代わりとして用いることにしています4。これは、関数から複数の値を返したい場合に配列に入れてまとめて返すみたいなユースケースを想定していると思われます。

let tuple_value: [string, number] = ["Hello", 123];

オブジェクト指向

このようにTypeScriptはオブジェクト指向で書けます。

class Person {
    name: string;
    age: number;

    // コンストラクタ
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    // 情報の表示
    show_info(): void {
        console.log(`name=${this.name}, age=${this.age}`);
    }
}

// インスタンスの生成
let taro = new Person("taro", 14);

// 情報の表示
taro.show_info();

またextendsでクラスの継承も可能です。

class NextPerson extends Person {
    constructor(name: string, age: number) {
        super(name, age);
    }
}

参考
以上。

最後に。

解説が浅いところとか、間違い等があると思いますのでその時はアドバイス等お願いします。

次回はいよいよVueファイルでのtypescriptの使い方を見ていきましょう。

最後まで読んでいただきありがとうございました。
Twitterやってます。良ければチェックして見てください。:point_up::point_up:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript エラー処理 完全ガイド

image.png

本記事は、Valentino Gagliardi 氏の "A mostly complete guide to error handling in JavaScript." を許可を頂いた上で翻訳したものです。

TOC

プログラミングにおけるエラーとは?

私たちの書くプログラムは 常にうまく動作するわけではありません。

時に、プログラムを停止させたり、ユーザーに何か問題が起こったことを知らせたい シチュエーションがあります。

例えば、以下のようなケースがあるでしょう:

  • プログラムが存在しないファイルを開こうとした
  • ネットワークの接続が不調である
  • ユーザーが無効な値を入力した

すべてのケースで、私たちがプログラマーとして、またはプログラミングエンジンを通して、 エラー を作成します。

エラーを作成することで、ユーザーに問題が起きたことをメッセージで伝えたり、プログラムの実行を停止させたりできるのです。

JavaScript におけるエラーとは?

JavaScript におけるエラーはオブジェクト です。このオブジェクトは、後にプログラムを停止するために 投げられる ものです。

JavaScript で新しくエラーを作成するには、適切な コンストラクタ関数 を呼び出します。例えば、一般的なエラーを新規に作成するには以下を実行します:

const err = new Error("Something bad happened!");

new というキーワードを省略することもできます:

const err = Error("Something bad happened!");

一度作成されると、エラーオブジェクトは3つのプロパティを提供します。

  • message: エラーメッセージを含む文字列
  • name: エラーのタイプ
  • stack: 関数実行のスタックトレース

例えば、適当なメッセージ文字列でTypeError オブジェクトを作成した場合、message は実際に渡した文字列となり、name"TypeError"となります:

const wrongType = TypeError("Wrong type given, expected number");

wrongType.message; // "Wrong type given, expected number"
wrongType.name; // "TypeError"

Firefox は上記のプロパティの他に、columnNumberfilenamelineNumberといった非標準プロパティを実装しています。

JavaScript エラー型の種類

JavaScript にはたくさんのエラー型があります。具体的には以下の通りです:

  • Error
  • EvalError
  • InternalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError

これらのエラー型は、あたらしいエラーオブジェクトを返す 本物のコンストラクタ関数 であることを忘れないでください。

あなた自身のエラーオブジェクトを作成する際、ErrorTypeError という最も一般的な 2 つのエラー型を使うことが多いでしょう。

エラーの大多数は InternalErrorSyntaxError のように、JavaScript エンジンから直接的に発現するものがほとんどです。

TypeError の一例は、const に再代入しようとした際に発生します:

const name = "Jules";
name = "Caty";

// TypeError: Assignment to constant variable.

SyntaxError の一例は、タイプミスをしたときに発生します:

va x = '33';
// SyntaxError: Unexpected identifier

または、awaitasync 関数以外で利用するなど、予約語を不適切な場所を使った場合にも発生します:

function wrong(){
    await 99;
}

wrong();

// SyntaxError: await is only valid in async function

TypeErrorの他の例としては、ページに存在しない HTML 要素を指定したときに発生します:

Uncaught TypeError: button is null

これらのよくあるエラーオブジェクトに加えて、AggregateError オブジェクトが JavaScript にもうすぐ導入される予定です。後ほど見るように、AggregateError は複数のエラーをまとめる際に便利です。

これらの組み込みエラーに加えて、ブラウザでは以下のようなもの目にすることがあります:

  • DOMException
  • DOMError (Dupulicated, 今は使われていない)

DOMException は Web APIs に関連するエラーファミリーです。ブラウザの中で、ばかげたことをしたときに投げられます。例えば以下のようなことです:

document.body.appendChild(document.cloneNode(true));

結果:

Uncaught DOMException: Node.appendChild: May not add a Document as a child

完全なリストは、MDNのこちらのページを参照してください。

例外とは?

多くのデベロッパーは、エラーと例外を同様のものとして考えています。実際には、 エラーオブジェクトが投げられたときにのみ、エラーオブジェクトが例外になる のです。

JavaScript で例外を投げるには、throwとエラーオブジェクトを用います:

const wrongType = TypeError("Wrong type given, expected number");

throw wrongType;

短縮形のほうがより一般的です。多くのコードベースで以下のようなものを目にするでしょう:

throw TypeError("Wrong type given, expected number");

または

throw new TypeError("Wrong type given, expected number");

関数や条件分岐構文の外で例外を投げることはほとんどありません。代わりに、以下の例を考えてみましょう:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

ここでは、関数の引数が文字列(string)かどうかをチェックしています。文字列でなければ、例外を投げます。

JavaScript のルール的には、エラーオブジェクトだけではなく何でも投げることができます:

throw Symbol();
throw 33;
throw "Error!";
throw null;

しかしながら、 プリミティブ型を投げることは避け、適切はエラーオブジェクトを投げるべき です。

そうすることで、コードベースにおいてエラー処理の一貫性を保つことができます。他のチームメンバーがエラーオブジェクトにおいて error.messageerror.stack にアクセスすることができます。

例外を投げると何が起きる?

例外はエレベーターが上に行くようなものです。 一度例外を投げると、どこかで止められない限りプログラムスタックの中でぶくぶくと泡立ってしまします。

以下のようなコードを考えてみましょう:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

このコードをブラウザもしくは Node.js で実行した場合、プログラムは停止し以下のようなエラーを表示します:

Uncaught TypeError: Wrong type given, expected a string
    toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9

さらに、エラーが発生した正確な行数を把握することができます。

この表示が スタックトレース であり、プログラムの問題を追跡する際に便利です。

スタックトレースは下から上に積み上がります。つまりここでは以下のようになっていました:

    toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9

ここから以下のことが言えます:

  • 9 行目にあるプログラムの何かが toUppercase を呼び出した
  • 3 行目において toUppercase で問題が発生した

ブラウザのコンソールで確認する以外にも、エラーオブジェクトの stack プロパティにアクセスすることによってスタックトレースを見ることができます。

もし例外が キャッチされなかった 場合、つまり、プログラマが例外をキャッチするために何もしなかった場合、プログラムはクラッシュします。

コードの中で、いつ、どこで例外をキャッチするかは、その時々で異なります。

例えば、 プログラムを完全にクラッシュさせるために、例外をスタックに加えて伝播させたいかもしれません。 これは、無効なデータで処理を進めるよりもプログラムを停止させたほうが安全である、といった、致命的なエラーを処理する際に起こりうることです。

さて、ここまでで基本の紹介をしたので、 JavaScript の同期処理と非同期処理における、エラーと例外処理 に話を進めましょう。

同期的エラー処理

同期処理のコードはほとんどの場合単純でわかりやすいので、エラー処理も簡単です。

通常関数のエラー処理

同期処理のコードは、書かれた通りに順番に実行されます。前述のコードをもう一度見てみましょう:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

ここで、JavaScript エンジンは toUppercase を呼び出して実行しています。すべての処理は 同期的 に行われます。このように同期関数から発生する例外を キャッチ するには、try/catch/finally を使うことができます:

try {
  toUppercase(4);
} catch (error) {
  console.error(error.message);
  // or log remotely
} finally {
  // clean up
}

通常、try はハッピーパスや、潜在的に例外を投げる可能性のある関数呼び出しに対して利用します。

catch は、 実際に例外を捉えます。 エラーオブジェクトを受け取り 、エラーの内容を検査することができます(そして本番環境ではログをリモートサーバーに送信したりします)。

一方で、finally ステートメントは、関数の実行結果に関わらず実行されます。つまり、関数が失敗したか成功したかにかかわらず finally 内に書かれたコードは実行されます。

try/catch/finally同期的な 構造であることを覚えておいて下さい。そしていま、 非同期処理のコードから発生する例外をキャッチする方法を獲得した のです。

ジェネレーター関数のエラー処理

JavaScript におけるジェネレーター関数は、関数の特殊な形式です。

この形式の関数は、関数の内側のスコープとその外側の間で 双方向のコミュニケーションチャネル を提供する以外に、 任意に停止したり再開したり することができます。

ジェネレーター関数を作成するには、function キーワードの後ろにアスタリスク * を付けます:

function* generate() {
//
}

そうすると、値を返すために関数内で yield を使用することができます:

function* generate() {
  yield 33;
  yield 99;
}

ジェネレーター関数の返り値イテレータオブジェクト です。ジェネレーターから値を取り出すためには、2つの方法があります:

  • イテレータオブジェクトの next() を呼び出す
  • for...ofイテレーション する

先程の例で、ジェネレーターから値を取り出す場合は、以下のようにできます:

function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

ここで go がイテレータオブジェクトになります。

ここから、go.next() を呼び出し、実行を進めることができます:

function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

const firstStep = go.next().value; // 33
const secondStep = go.next().value; // 99

ジェネレーターは、 呼び出し元から値や例外を受け取ることもできます。

next() に加えて、ジェネレーターから返されたイテレータオブジェクトは、throw() メソッドを持っています。

このメソッドを利用して、ジェネレーターに例外を注入することによってプログラムを停止させてみましょう:

function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

const firstStep = go.next().value; // 33

go.throw(Error("Tired of iterating!"));

const secondStep = go.next().value; // never reached

注入された例外をキャッチするには、ジェネレーター関数内の処理を try/catch 構文で囲む必要があります(必要であれば finally も利用できます):

function* generate() {
  try {
    yield 33;
    yield 99;
  } catch (error) {
    console.error(error.message);
  }
}

ジェネレーター関数は例外を関数の外に投げることもできます。この仕組みは、try/catch/finally を使って同期処理の例外をキャッチするものと同じです。

ジェネレーター関数に対して for...of 構文を利用する例は以下のとおりです:

function* generate() {
  yield 33;
  yield 99;
  throw Error("Tired of iterating!");
}

try {
  for (const value of generate()) {
    console.log(value);
  }
} catch (error) {
  console.error(error.message);
}

/* Output:
33
99
Tired of iterating!
*/

ここでは、try ブロックの中でハッピーパスを実行し、例外があれば catch でキャッチします。

非同期エラー処理

JavaScript はシングルスレッドで実行されるプログラム言語であり、原理的には同期的です。

ブラウザエンジンのようなホスト環境が JavaScript の機能を拡張させたことで、外部のシステムと通信したり、I/O 処理を行うための、たくさんの Web API が使えるようになりました。

ブラウザにおける非同期性の例は タイムアウト(timeouts)、イベント(events)、プロミス(Promise) があります。

非同期の世界におけるエラー処理 は同期の世界におけるそれとは異なります。

いくつか例を見ていきましょう。

タイマーのエラー処理

JavaScript を学び始めたばかりのとき、try/catch/finally 構文について学ぶと、あらゆるコードブロックを try/catch/finally 構文 で囲みたくなるかもしれません。

例えば以下のような関数を考えてみましょう:

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000)
}

この関数は、約 1 秒後にエラーを投げます。この例外を正しく扱うにはどうしたらよいでしょうか?

以下のようなコードは 上手く動きません :

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}

try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

前述したように、try/catch 構文は同期的です。一方で、ここでは setTiemout という、タイマー機能を持つブラウザの API を利用しています。

setTimeout に渡したコールバック関数が実行されるときには、既にtry/catch 構文の実行は 終わっている のです。上のプログラムは例外をキャッチすることができず、クラッシュしてしまいます。

2 つの異なったトラックが実行されているのです:

Track A: --> try/catch
Track B: --> setTimeout --> callback --> throw

プログラムをクラッシュさせたくなければ、try/catch 構文を、setTimeout に渡しているコールバック関数の中に移動する必要があります。

しかし、このアプローチは多くの場合意味を成しません。後で見るように、 Promises を用いた非同期エラー処理がより優れている のです。

イベントのエラー処理

Document Object Model (DOM) の HTML ノードは、EventTarget と連携しています。EventTarget は、ブラウザにおけるあらゆるイベントエミッターの共通の祖先といえる存在です。

これはつまり、ページ上の全ての HTML 要素におけるイベントを取得することができることを意味します。

(Node.js も今後のリリースで EventTarget をサポートする予定です)

DOM イベントに対するエラー処理の仕組みは、非同期 Web API における仕組みと同様 です。

以下の例を考えてみましょう:

const button = document.querySelector("button");

button.addEventListener("click", function() {
  throw Error("Can't touch this button!");
});

ここでは、ボタンがクリックされた瞬間に例外を投げています。どのようにその例外をキャッチするのでしょうか?以下のパターンは 上手く動作せず 、プログラムはクラッシュしてしまいます:

const button = document.querySelector("button");

try {
  button.addEventListener("click", function() {
    throw Error("Can't touch this button!");
  });
} catch (error) {
  console.error(error.message);
}

setTimeout の例で見たように、addEventListener に渡されるあらゆるコールバック関数は、非同期的に実行されます:

Track A: --> try/catch
Track B: --> addEventListener --> callback --> throw

プログラムをクラッシュさせたくなければ、addEventListener のコールバック関数内部に try/catch 構文を移動する必要があります。

しかしここでも、そのようにする意味がほぼありません。

setTimeout の例で見たように、非同期処理コードの実行パスにおいて投げられた例外は 外側でキャッチすることができるものではなく 、結果としてプログラムはクラッシュします。

次のセクションで、Promises と async/await がどのように非同期処理におけるエラー処理を手軽なものにするか見ていきます。

onerror はどうだろう?

HTML 要素には、onlickonmouseenteronchange など多くのイベントハンドラがあります。

そのなかには、onerror もありますが、throw やその類のものとは何も関係がありません。

onerror イベントハンドラは、<img><script> のような HTML 要素が存在しないリソースを扱ったときにトリガーされます。

以下のような例を考えてみましょう:

// omitted
<body>
<img src="nowhere-to-be-found.png" alt="So empty!">
</body>
// omitted

上記のような、存在しないリソースを参照する要素を含んだ HTML ドキュメントをブラウザで見ると、コンソールに以下のようなエラーが表示されます:

GET http://localhost:5000/nowhere-to-be-found.png
[HTTP/1.1 404 Not Found 3ms]

JavaScript では、このエラーを以下のように「キャッチ」できるかもしれません:

const image = document.querySelector("img");

image.onerror = function(event) {
  console.log(event);
};

より優れた形で書くと、以下のようになります:

const image = document.querySelector("img");

image.addEventListener("error", function(event) {
  console.log(event);
});

このパターンは、画像やスクリプトなどのリソースに欠損があった際に、代替となるリソースをローディングしたい場合 に便利です。

だたし、onerrorthrowtry/catch とは何の関係もないことを覚えておいて下さい。

Promise を用いたエラー処理

Promise によるエラー処理を説明するために、何度も登場している以下の例を「約束化(promisify)」させてみましょう。以下のコード例を編集していきます:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

単純に文字列もしくは例外を返す代わりに、成功とエラーを処理するための Promise.rejectPromise.resolve を利用してみましょう:

function toUppercase(string) {
  if (typeof string !== "string") {
    return Promise.reject(TypeError("Wrong type given, expected a string"));
  }

  const result = string.toUpperCase();

  return Promise.resolve(result);
}

(厳密には、上記コードに非同期処理を行う部分はありませんが、説明するには十分です)

いま、toUppercase 関数は「約束」され、処理結果を扱うために then を、 リジェクトされた Promise を処理するためcatch を使うことができます:

toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message));

上記のコードは、以下のようなログを吐き出します:

Wrong type given, expected a string

Promise において、catch はエラーを処理するための構成要素です。

catchthen に加え、finally もあります。この finally は、try/catch 構文における finally と似たものです。

Promise における finally も、返された Promise の結果に 関わらず 実行されます:

toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message))
  .finally(() => console.log("Run baby, run"));

then/catch/finally に渡されたコールバック関数は、Microtask キューによって非同期に処理されることを覚えておいて下さい。これらは、イベントやタイマーよりも優先される micro task です。

プロミス(Promise)、エラー(error)そしてスロー(throw)

Promise をリジェクトする際は、引数としてエラーオブジェクト渡すのが ベストプラクティス です:

Promise.reject(TypeError("Wrong type given, expected a string"));

そうすることで、エラー処理の一貫性を保つことができます。他のチームメンバーが常に error.message にアクセスすることができますし、さらに重要なことに、スタックトレースを調査することができます。

Promise.reject に加えて、例外を投げることで Promise チェーンから抜け出すことができます。

以下のコード例を考えてみます:

Promise.resolve("A string").then(value => {
  if (typeof value === "string") {
    throw TypeError("Expected a number!");
  }
});

文字列を返すとともに Promise をリゾルブし、そしてその直後に throw によって例外を投げています。

例外の伝播を食い止めるために、通常通り catch を使うことができます:

Promise.resolve("A string")
  .then(value => {
    if (typeof value === "string") {
      throw TypeError("Expected a number!");
    }
  })
  .catch(reason => console.log(reason.message));

このパターンは、fetch を使う際によく用いられます。レスポンスオブジェクトのエラーチェックを行う例は以下の通りです:

fetch("https://example-dev/api/")
  .then(response => {
    if (!response.ok) {
      throw Error(response.statusText);
    }

    return response.json();
  })
  .then(json => console.log(json));

ここでも、catch によって例外を受け取ることができます。もし例外を受け取ることに失敗した場合、あるいはあえて受け取らないことにした場合、 例外はキャッチされるまでスタックに残り続けます。

これは一概に悪いこととは言えませんが、環境によって、キャッチされていないリジェクトに対する挙動は異なります。

例えば、Node.js は将来的に、処理されていない Promise のリジェクトがあった場合は、プログラムをクラッシュさせる予定です:

DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

必ずリジェクトはキャッチしましょう!

"プロミス化"されたタイマーのエラー処理

タイマーとイベントにおいて、コールバック関数内で投げられた例外をキャッチすることは不可能ではありません。前のセクションで、以下のような例を挙げました:

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}

// DOES NOT WORK
try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

Promise によって与えられた解決策は、コードの「プロミス化」です。基本的に、Promise でタイマーを囲みます:

function failAfterOneSecond() {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(Error("Something went wrong!"));
    }, 1000);
  });
}

reject によって Promise のリジェクトをセットし、エラーオブジェクトを渡します。

この時点で、catch をつかって例外を処理することができます:

failAfterOneSecond().catch(reason => console.error(reason.message));

Tips: 成功した Promise の返り値の変数名として value、Promise のリジェクトの変数名として reason を使うことが一般的です。

Node.js は promisify と呼ばれる、古い形で書かれたコールバック API をプロミス化するユーティリティを提供しています。

Promise.all のエラー処理

Promise の static メソッドである Promise.all は Promise の配列を引数にとり、リゾルブした Promise の配列を返します。

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.resolve("All good here too!");

Promise.all([promise1, promise2]).then((results) => console.log(results));

// [ 'All good!', 'All good here too!' ]

渡した配列のどれか1つでもリジェクトされた場合、Promise.all は最初にリジェクトされた Promise のエラーとともにリジェクトします。

このような状況を扱うために、前のセクションで見たように catch が利用できます:

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.reject(Error("No good, sorry!"));
const promise3 = Promise.reject(Error("Bad day ..."));

Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message));

Promise.all の実行結果に関わらず関数を実行するには、finally を利用します:

Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));

Promise.any のエラー処理

Promise.any (Firefox > 79, Chrome > 85) は、Promise.all の反対の処理をする関数と考えることができます。

Promise.all が、渡した配列の中に 1 つでもリジェクトされるものがあった場合にエラーを返すのに対し、Promise.any はリジェクトが発生しても、リゾルブしたものが 1 つでもあればそれを返します。

Promise.any に渡した配列に含まれる すべての Promise がリジェクトされた場合、結果として得られるエラーは AggregatedError です。以下のようなコード例を考えてみましょう:

const promise1 = Promise.reject(Error("No good, sorry!"));
const promise2 = Promise.reject(Error("Bad day ..."));

Promise.any([promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));

catch を使ってエラーを処理しています。このコードの実行結果は以下の通りです:

AggregateError: No Promise in Promise.any was resolved
Always runs!

AggregatedError オブジェクトは、通常の Error オブジェクトと同様のプロパティに加えて、errors プロパティを持っています:

//
  .catch(error => console.error(error.errors))
//

このプロパティは、それぞれのリジェクトで返されたエラーの配列を格納しています:

[Error: "No good, sorry!, Error: "Bad day ..."]

Promise.race のエラー処理

Promise.race は、Promsie の配列を引数に取ります:

const promise1 = Promise.resolve("The first!");
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, promise2]).then(result => console.log(result));

// The first!

得られる返り値は、 「レース」を制した 1 番着の Promise です。

リジェクトされた場合はどうなるのでしょうか?リジェクトされる Promise が一番でなければ、Promise.race はリゾルブします:

const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, rejection, promise2]).then(result =>
  console.log(result)
);

// The first!

もし リジェクトが一番になった場合、Promise.race はリジェクトされ 、以下のようにしてリジェクトをキャッチすることができます:

const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([rejection, promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error.message));

// Ouch!

Promise.allSettled のエラー処理

Promise.allSettled は ECMAScript 2020 で追加される関数です。

この関数を使って処理するケースはそれほど多くありません。なぜなら、 Promise のリジェクトがあったとしても、返り値は常にリゾルブされた Promise になるため です。

以下のようなコード例を考えてみます:

const promise1 = Promise.resolve("Good!");
const promise2 = Promise.reject(Error("No good, sorry!"));

Promise.allSettled([promise1, promise2])
  .then(results => console.log(results))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));

上の例では、リゾルブする Promise とリジェクトされる Promise を 1 つずつ含め、配列として渡しています。

このケースでは、catch が実行されることはありません。代わりに finally が実行されます。

then 内の処理によってロギングされる結果は次の通りです:

[
  { status: 'fulfilled', value: 'Good!' },
  {
    status: 'rejected',
    reason: Error: No good, sorry!
  }
]

async/await のエラー処理

JavaScript の async/await は非同期関数を表しますが、コードを読む立場からみれば、同期関数の 可読性の高さ の恩恵を受けているといえます。

話を単純にするために、何度も登場している同期関数 toUppercase を、function キーワードの前に async を付け足すことで、非同期関数に変換します:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

async というプレフィックスを使うことで、関数に Promise を返す ように仕向けることができるようになります。これはつまり、thencatchfinally といったチェーンが使えることを意味します:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase("abc")
  .then(result => console.log(result))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));

async 関数内で例外を投げた 場合、この例外は、 裏側で機能している Promise をリジェクト させます。

どんなエラーも、catch によってキャッチすることができます。

最も重要なことは、このスタイルに加えて同期関数と同様に try/catch/finally 構文を使える、ということです。

以下の例では、toUppercase 関数を consumer という他の関数から呼び出しています。consumer 内部では、toUppercase 関数を try/catch/finally 関数で囲っています:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

async function consumer() {
  try {
    await toUppercase(98);
  } catch (error) {
    console.error(error.message);
  } finally {
    console.log("Always runs!");
  }
}

consumer(); // Returning Promise ignored

実行結果は以下の通りです:

Wrong type given, expected a string
Always runs!

同様のトピックは次の記事でも扱っています: How to Throw Errors From Async Functions in JavaScript?

非同期ジェネレーターのエラー処理

JavaScript の 非同期ジェネレーター は、通常の値の代わりに Promise を yeild することができるジェネレーター関数 です。

async とジェネレーター関数を組み合わせて使います。イテレータオブジェクトが呼び出し元に対して Promise を返すジェネレーター関数です。

非同期ジェネレーターを作るために、async でプレフィックスした、* を持つ関数を定義します:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

Promise の仕組みに基づいているため、エラー処理に対しても同様のルールが適用されます。非同期ジェネレーター関数内の throw は Promise のリジェクトに繋がり、catch でキャッチすることができます。

非同期ジェネレーター関数から Promise を取り出す には、以下の 2 つのアプローチがあります。

  • thenハンドラ
  • 非同期イテレーション

上のコード例では、最初の 2 つの値が yield されたあとに、例外が投げられます。これは以下のようにできることを意味します:

const go = asyncGenerator();

go.next().then(value => console.log(value));
go.next().then(value => console.log(value));
go.next().catch(reason => console.error(reason.message));

上記コードの実行結果は以下の通りです:

{ value: 33, done: false }
{ value: 99, done: false }
Something went wrong!

もう1つのアプローチは、 for await...of非同期イテレーション を用いる方法です。非同期イテレーションを用いるためには、呼び出し側の関数を async で囲む必要があります。

以下が完全なコード例です:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

async function consumer() {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
}

consumer();

async/await で見たように、潜在的に存在する例外は try/catch で処理することができます:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

async function consumer() {
  try {
    for await (const value of asyncGenerator()) {
      console.log(value);
    }
  } catch (error) {
    console.error(error.message);
  }
}

consumer();

実行結果は以下の通りです:

33
99
Something went wrong!

非同期ジェネレーター関数の返り値であるイテレータオブジェクトには、同期ジェネレーター関数と同様に throw() メソッドがあります。

イテレータオブジェクトにおいて throw() メソッドを呼び出すと、例外は投げず、代わりにリジェクトされた Promise を投げます:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  yield 11;
}

const go = asyncGenerator();

go.next().then(value => console.log(value));
go.next().then(value => console.log(value));

go.throw(Error("Let's reject!"));

go.next().then(value => console.log(value)); // value is undefined

この状況を処理するには、以下のようにできます:

go.throw(Error("Let's reject!")).catch(reason => console.error(reason.message));

ただし、イテレータオブジェクトの throw()ジェネレーター関数の内部 に例外を送ることを忘れないでおきましょう。これは、以下のようなパターンを適用することを意味します:

async function* asyncGenerator() {
  try {
    yield 33;
    yield 99;
    yield 11;
  } catch (error) {
    console.error(error.message);
  }
}

const go = asyncGenerator();

go.next().then(value => console.log(value));
go.next().then(value => console.log(value));

go.throw(Error("Let's reject!"));

go.next().then(value => console.log(value)); // value is undefined

Node.js のエラー処理

Node.js の同期エラー処理

Node.js における同期エラー処理は、今までみてきた内容とほとんど同じです。

同期コード には、try/catch/finally が使えます。

しかしながら、非同期の世界に目を向けてみると、面白いことが起こります。

Node.js の非同期エラー処理: コールバックパターン

非同期コードにおいては、Node.js は 2 つの書き方に依存しています:

  • コールバックパターン
  • イベントエミッター

コールバックパターン において 非同期 Node.js API は、 イベントループ を通して処理され コールスタック が空になるとすぐに実行されるという関数を引数に取ります。

以下のようなコードを考えてみましょう:

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) console.error(error);
    // do stuff with the data
  });
}

上のコードからコールバック関数を抽出すると、どのようにエラーを処理することになっているかを見ることができます:

//
function(error, data) {
    if (error) console.error(error);
    // do stuff with the data
  }
//

fs.readFile の実行過程においてエラーが発生した場合には、エラーオブジェクトを得ます。

この時点で、以下のことが可能です:

  • 今までしてきたように、単純にエラーオブジェクトのログを表示する
  • 例外を投げる
  • 他のコールバックにエラーを渡す

例外を投げる場合は、以下のようにできます:

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) throw Error(error.message);
    // do stuff with the data
  });
}

しかし、DOM におけるイベントやタイマーと同様に、この例外は プログラムをクラッシュ させます。以下のように try/catch を用いてクラッシュを阻止しようとしても、うまくいきません:

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) throw Error(error.message);
    // do stuff with the data
  });
}

try {
  readDataset("not-here.txt");
} catch (error) {
  console.error(error.message);
}

プログラムをクラッシュさせたくなければ、他のコールバックにエラーを渡すことが望ましい方法です。

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) return errorHandler(error);
    // do stuff with the data
  });
}

ここで用いている eventHandler はその名前からも分かるように、エラーを処理するシンプルな関数です:

function errorHandler(error) {
  console.error(error.message);
  // do something with the error:
  // - write to a log.
  // - send to an external logger.
}

Node.js における非同期エラー処理: イベントエミッター

Node.js で行う多くのことは、 イベント に基づいています。ほとんどの場合、 エミッターオブジェクト と、いくつかのメッセージを待ち受けているオブザーバーとやり取りを行います。

Node.js のイベント駆動なモジュール(例えば net など)はすべて EventEmitter というルートクラスを継承しています。

Node.js の EventEmitter は、2 つの基本的なメソッド持っています: onemit です。

以下のような単純な HTTP サーバーを考えてみましょう:

const net = require("net");

const server = net.createServer().listen(8081, "127.0.0.1");

server.on("listening", function () {
  console.log("Server listening!");
});

server.on("connection", function (socket) {
  console.log("Client connected!");
  socket.end("Hello client!");
});

ここで、以下の 2 つのイベントを待ち受けています: listeningconnection です。

これらのイベントに加えて、イベントエミッターはエラーが発生した際に エラー イベントも発火します。

もしこの上記コードのポート番号を 80 にして実行した場合、以下のような例外を得るでしょう:

const net = require("net");

const server = net.createServer().listen(80, "127.0.0.1");

server.on("listening", function () {
  console.log("Server listening!");
});

server.on("connection", function (socket) {
  console.log("Client connected!");
  socket.end("Hello client!");
});

実行結果:

events.js:291
      throw er; // Unhandled 'error' event
      ^

Error: listen EACCES: permission denied 127.0.0.1:80
Emitted 'error' event on Server instance at: ...

この例外をキャッチするためには、 エラー を待ち受けるイベントハンドラを登録します:

server.on("error", function(error) {
  console.error(error.message);
});

以下のような結果を得ます:

listen EACCES: permission denied 127.0.0.1:80

さらに、プログラムはクラッシュしません。

このトピックについての詳細は、"Error Handling in Node.js" を読むと良いでしょう。

まとめ

このガイドでは、シンプルな同期コードから高度な非同期な仕組みまで、JavaScript のエラー処理全般を扱いました。

JavaScript のプログラムでは、例外の発生の仕方は多岐にわたります。

同期コードの例外は最も単純に対処することができますが、 非同期コード における例外処理は 複雑になる 場合があります。

一方で、ブラウザの新しい JavaScript API はほとんどすべて Promise に向かっています。普及したこのパターンは、then/catch/finally または async/awaittry/catch を使って例外を処理することをより簡単にします。

このガイドを読んだ後は、 プログラムで起こり得るすべての状況を認識して、例外を正しくキャッチすることができる ようになっているはずです。

最後までお読み頂きありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】エラー処理 完全ガイド【保存版】

image.png

本記事は、Valentino Gagliardi 氏の "A mostly complete guide to error handling in JavaScript." を許可を頂いた上で翻訳したものです。

TOC

プログラミングにおけるエラーとは?

私たちの書くプログラムは 常にうまく動作するわけではありません。

時に、プログラムを停止させたり、ユーザーに何か問題が起こったことを知らせたい シチュエーションがあります。

例えば、以下のようなケースがあるでしょう:

  • プログラムが存在しないファイルを開こうとした
  • ネットワークの接続が不調である
  • ユーザーが無効な値を入力した

すべてのケースで、私たちがプログラマーとして、またはプログラミングエンジンを通して、 エラー を作成します。

エラーを作成することで、ユーザーに問題が起きたことをメッセージで伝えたり、プログラムの実行を停止させたりできるのです。

JavaScript におけるエラーとは?

JavaScript におけるエラーはオブジェクト です。このオブジェクトは、後にプログラムを停止するために 投げられる ものです。

JavaScript で新しくエラーを作成するには、適切な コンストラクタ関数 を呼び出します。例えば、一般的なエラーを新規に作成するには以下を実行します:

const err = new Error("Something bad happened!");

new というキーワードを省略することもできます:

const err = Error("Something bad happened!");

一度作成されると、エラーオブジェクトは3つのプロパティを提供します。

  • message: エラーメッセージを含む文字列
  • name: エラーのタイプ
  • stack: 関数実行のスタックトレース

例えば、適当なメッセージ文字列でTypeError オブジェクトを作成した場合、message は実際に渡した文字列となり、name"TypeError"となります:

const wrongType = TypeError("Wrong type given, expected number");

wrongType.message; // "Wrong type given, expected number"
wrongType.name; // "TypeError"

Firefox は上記のプロパティの他に、columnNumberfilenamelineNumberといった非標準プロパティを実装しています。

JavaScript エラー型の種類

JavaScript にはたくさんのエラー型があります。具体的には以下の通りです:

  • Error
  • EvalError
  • InternalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError

これらのエラー型は、あたらしいエラーオブジェクトを返す 本物のコンストラクタ関数 であることを忘れないでください。

あなた自身のエラーオブジェクトを作成する際、ErrorTypeError という最も一般的な 2 つのエラー型を使うことが多いでしょう。

エラーの大多数は InternalErrorSyntaxError のように、JavaScript エンジンから直接的に発現するものがほとんどです。

TypeError の一例は、const に再代入しようとした際に発生します:

const name = "Jules";
name = "Caty";

// TypeError: Assignment to constant variable.

SyntaxError の一例は、タイプミスをしたときに発生します:

va x = '33';
// SyntaxError: Unexpected identifier

または、awaitasync 関数以外で利用するなど、予約語を不適切な場所を使った場合にも発生します:

function wrong(){
    await 99;
}

wrong();

// SyntaxError: await is only valid in async function

TypeErrorの他の例としては、ページに存在しない HTML 要素を指定したときに発生します:

Uncaught TypeError: button is null

これらのよくあるエラーオブジェクトに加えて、AggregateError オブジェクトが JavaScript にもうすぐ導入される予定です。後ほど見るように、AggregateError は複数のエラーをまとめる際に便利です。

これらの組み込みエラーに加えて、ブラウザでは以下のようなもの目にすることがあります:

  • DOMException
  • DOMError (Dupulicated, 今は使われていない)

DOMException は Web APIs に関連するエラーファミリーです。ブラウザの中で、ばかげたことをしたときに投げられます。例えば以下のようなことです:

document.body.appendChild(document.cloneNode(true));

結果:

Uncaught DOMException: Node.appendChild: May not add a Document as a child

完全なリストは、MDNのこちらのページを参照してください。

例外とは?

多くのデベロッパーは、エラーと例外を同様のものとして考えています。実際には、 エラーオブジェクトが投げられたときにのみ、エラーオブジェクトが例外になる のです。

JavaScript で例外を投げるには、throwとエラーオブジェクトを用います:

const wrongType = TypeError("Wrong type given, expected number");

throw wrongType;

短縮形のほうがより一般的です。多くのコードベースで以下のようなものを目にするでしょう:

throw TypeError("Wrong type given, expected number");

または

throw new TypeError("Wrong type given, expected number");

関数や条件分岐構文の外で例外を投げることはほとんどありません。代わりに、以下の例を考えてみましょう:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

ここでは、関数の引数が文字列(string)かどうかをチェックしています。文字列でなければ、例外を投げます。

JavaScript のルール的には、エラーオブジェクトだけではなく何でも投げることができます:

throw Symbol();
throw 33;
throw "Error!";
throw null;

しかしながら、 プリミティブ型を投げることは避け、適切はエラーオブジェクトを投げるべき です。

そうすることで、コードベースにおいてエラー処理の一貫性を保つことができます。他のチームメンバーがエラーオブジェクトにおいて error.messageerror.stack にアクセスすることができます。

例外を投げると何が起きる?

例外はエレベーターが上に行くようなものです。 一度例外を投げると、どこかで止められない限りプログラムスタックの中でぶくぶくと泡立ってしまします。

以下のようなコードを考えてみましょう:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

このコードをブラウザもしくは Node.js で実行した場合、プログラムは停止し以下のようなエラーを表示します:

Uncaught TypeError: Wrong type given, expected a string
    toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9

さらに、エラーが発生した正確な行数を把握することができます。

この表示が スタックトレース であり、プログラムの問題を追跡する際に便利です。

スタックトレースは下から上に積み上がります。つまりここでは以下のようになっていました:

    toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9

ここから以下のことが言えます:

  • 9 行目にあるプログラムの何かが toUppercase を呼び出した
  • 3 行目において toUppercase で問題が発生した

ブラウザのコンソールで確認する以外にも、エラーオブジェクトの stack プロパティにアクセスすることによってスタックトレースを見ることができます。

もし例外が キャッチされなかった 場合、つまり、プログラマが例外をキャッチするために何もしなかった場合、プログラムはクラッシュします。

コードの中で、いつ、どこで例外をキャッチするかは、その時々で異なります。

例えば、 プログラムを完全にクラッシュさせるために、例外をスタックに加えて伝播させたいかもしれません。 これは、無効なデータで処理を進めるよりもプログラムを停止させたほうが安全である、といった、致命的なエラーを処理する際に起こりうることです。

さて、ここまでで基本の紹介をしたので、 JavaScript の同期処理と非同期処理における、エラーと例外処理 に話を進めましょう。

同期的エラー処理

同期処理のコードはほとんどの場合単純でわかりやすいので、エラー処理も簡単です。

通常関数のエラー処理

同期処理のコードは、書かれた通りに順番に実行されます。前述のコードをもう一度見てみましょう:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

ここで、JavaScript エンジンは toUppercase を呼び出して実行しています。すべての処理は 同期的 に行われます。このように同期関数から発生する例外を キャッチ するには、try/catch/finally を使うことができます:

try {
  toUppercase(4);
} catch (error) {
  console.error(error.message);
  // or log remotely
} finally {
  // clean up
}

通常、try はハッピーパスや、潜在的に例外を投げる可能性のある関数呼び出しに対して利用します。

catch は、 実際に例外を捉えます。 エラーオブジェクトを受け取り 、エラーの内容を検査することができます(そして本番環境ではログをリモートサーバーに送信したりします)。

一方で、finally ステートメントは、関数の実行結果に関わらず実行されます。つまり、関数が失敗したか成功したかにかかわらず finally 内に書かれたコードは実行されます。

try/catch/finally同期的な 構造であることを覚えておいて下さい。そしていま、 非同期処理のコードから発生する例外をキャッチする方法を獲得した のです。

ジェネレーター関数のエラー処理

JavaScript におけるジェネレーター関数は、関数の特殊な形式です。

この形式の関数は、関数の内側のスコープとその外側の間で 双方向のコミュニケーションチャネル を提供する以外に、 任意に停止したり再開したり することができます。

ジェネレーター関数を作成するには、function キーワードの後ろにアスタリスク * を付けます:

function* generate() {
//
}

そうすると、値を返すために関数内で yield を使用することができます:

function* generate() {
  yield 33;
  yield 99;
}

ジェネレーター関数の返り値イテレータオブジェクト です。ジェネレーターから値を取り出すためには、2つの方法があります:

  • イテレータオブジェクトの next() を呼び出す
  • for...ofイテレーション する

先程の例で、ジェネレーターから値を取り出す場合は、以下のようにできます:

function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

ここで go がイテレータオブジェクトになります。

ここから、go.next() を呼び出し、実行を進めることができます:

function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

const firstStep = go.next().value; // 33
const secondStep = go.next().value; // 99

ジェネレーターは、 呼び出し元から値や例外を受け取ることもできます。

next() に加えて、ジェネレーターから返されたイテレータオブジェクトは、throw() メソッドを持っています。

このメソッドを利用して、ジェネレーターに例外を注入することによってプログラムを停止させてみましょう:

function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

const firstStep = go.next().value; // 33

go.throw(Error("Tired of iterating!"));

const secondStep = go.next().value; // never reached

注入された例外をキャッチするには、ジェネレーター関数内の処理を try/catch 構文で囲む必要があります(必要であれば finally も利用できます):

function* generate() {
  try {
    yield 33;
    yield 99;
  } catch (error) {
    console.error(error.message);
  }
}

ジェネレーター関数は例外を関数の外に投げることもできます。この仕組みは、try/catch/finally を使って同期処理の例外をキャッチするものと同じです。

ジェネレーター関数に対して for...of 構文を利用する例は以下のとおりです:

function* generate() {
  yield 33;
  yield 99;
  throw Error("Tired of iterating!");
}

try {
  for (const value of generate()) {
    console.log(value);
  }
} catch (error) {
  console.error(error.message);
}

/* Output:
33
99
Tired of iterating!
*/

ここでは、try ブロックの中でハッピーパスを実行し、例外があれば catch でキャッチします。

非同期エラー処理

JavaScript はシングルスレッドで実行されるプログラム言語であり、原理的には同期的です。

ブラウザエンジンのようなホスト環境が JavaScript の機能を拡張させたことで、外部のシステムと通信したり、I/O 処理を行うための、たくさんの Web API が使えるようになりました。

ブラウザにおける非同期性の例は タイムアウト(timeouts)、イベント(events)、プロミス(Promise) があります。

非同期の世界におけるエラー処理 は同期の世界におけるそれとは異なります。

いくつか例を見ていきましょう。

タイマーのエラー処理

JavaScript を学び始めたばかりのとき、try/catch/finally 構文について学ぶと、あらゆるコードブロックを try/catch/finally 構文 で囲みたくなるかもしれません。

例えば以下のような関数を考えてみましょう:

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000)
}

この関数は、約 1 秒後にエラーを投げます。この例外を正しく扱うにはどうしたらよいでしょうか?

以下のようなコードは 上手く動きません :

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}

try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

前述したように、try/catch 構文は同期的です。一方で、ここでは setTiemout という、タイマー機能を持つブラウザの API を利用しています。

setTimeout に渡したコールバック関数が実行されるときには、既にtry/catch 構文の実行は 終わっている のです。上のプログラムは例外をキャッチすることができず、クラッシュしてしまいます。

2 つの異なったトラックが実行されているのです:

Track A: --> try/catch
Track B: --> setTimeout --> callback --> throw

プログラムをクラッシュさせたくなければ、try/catch 構文を、setTimeout に渡しているコールバック関数の中に移動する必要があります。

しかし、このアプローチは多くの場合意味を成しません。後で見るように、 Promises を用いた非同期エラー処理がより優れている のです。

イベントのエラー処理

Document Object Model (DOM) の HTML ノードは、EventTarget と連携しています。EventTarget は、ブラウザにおけるあらゆるイベントエミッターの共通の祖先といえる存在です。

これはつまり、ページ上の全ての HTML 要素におけるイベントを取得することができることを意味します。

(Node.js も今後のリリースで EventTarget をサポートする予定です)

DOM イベントに対するエラー処理の仕組みは、非同期 Web API における仕組みと同様 です。

以下の例を考えてみましょう:

const button = document.querySelector("button");

button.addEventListener("click", function() {
  throw Error("Can't touch this button!");
});

ここでは、ボタンがクリックされた瞬間に例外を投げています。どのようにその例外をキャッチするのでしょうか?以下のパターンは 上手く動作せず 、プログラムはクラッシュしてしまいます:

const button = document.querySelector("button");

try {
  button.addEventListener("click", function() {
    throw Error("Can't touch this button!");
  });
} catch (error) {
  console.error(error.message);
}

setTimeout の例で見たように、addEventListener に渡されるあらゆるコールバック関数は、非同期的に実行されます:

Track A: --> try/catch
Track B: --> addEventListener --> callback --> throw

プログラムをクラッシュさせたくなければ、addEventListener のコールバック関数内部に try/catch 構文を移動する必要があります。

しかしここでも、そのようにする意味がほぼありません。

setTimeout の例で見たように、非同期処理コードの実行パスにおいて投げられた例外は 外側でキャッチすることができるものではなく 、結果としてプログラムはクラッシュします。

次のセクションで、Promises と async/await がどのように非同期処理におけるエラー処理を手軽なものにするか見ていきます。

onerror はどうだろう?

HTML 要素には、onlickonmouseenteronchange など多くのイベントハンドラがあります。

そのなかには、onerror もありますが、throw やその類のものとは何も関係がありません。

onerror イベントハンドラは、<img><script> のような HTML 要素が存在しないリソースを扱ったときにトリガーされます。

以下のような例を考えてみましょう:

// omitted
<body>
<img src="nowhere-to-be-found.png" alt="So empty!">
</body>
// omitted

上記のような、存在しないリソースを参照する要素を含んだ HTML ドキュメントをブラウザで見ると、コンソールに以下のようなエラーが表示されます:

GET http://localhost:5000/nowhere-to-be-found.png
[HTTP/1.1 404 Not Found 3ms]

JavaScript では、このエラーを以下のように「キャッチ」できるかもしれません:

const image = document.querySelector("img");

image.onerror = function(event) {
  console.log(event);
};

より優れた形で書くと、以下のようになります:

const image = document.querySelector("img");

image.addEventListener("error", function(event) {
  console.log(event);
});

このパターンは、画像やスクリプトなどのリソースに欠損があった際に、代替となるリソースをローディングしたい場合 に便利です。

だたし、onerrorthrowtry/catch とは何の関係もないことを覚えておいて下さい。

Promise を用いたエラー処理

Promise によるエラー処理を説明するために、何度も登場している以下の例を「約束化(promisify)」させてみましょう。以下のコード例を編集していきます:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

単純に文字列もしくは例外を返す代わりに、成功とエラーを処理するための Promise.rejectPromise.resolve を利用してみましょう:

function toUppercase(string) {
  if (typeof string !== "string") {
    return Promise.reject(TypeError("Wrong type given, expected a string"));
  }

  const result = string.toUpperCase();

  return Promise.resolve(result);
}

(厳密には、上記コードに非同期処理を行う部分はありませんが、説明するには十分です)

いま、toUppercase 関数は「約束」され、処理結果を扱うために then を、 リジェクトされた Promise を処理するためcatch を使うことができます:

toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message));

上記のコードは、以下のようなログを吐き出します:

Wrong type given, expected a string

Promise において、catch はエラーを処理するための構成要素です。

catchthen に加え、finally もあります。この finally は、try/catch 構文における finally と似たものです。

Promise における finally も、返された Promise の結果に 関わらず 実行されます:

toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message))
  .finally(() => console.log("Run baby, run"));

then/catch/finally に渡されたコールバック関数は、Microtask キューによって非同期に処理されることを覚えておいて下さい。これらは、イベントやタイマーよりも優先される micro task です。

プロミス(Promise)、エラー(error)そしてスロー(throw)

Promise をリジェクトする際は、引数としてエラーオブジェクト渡すのが ベストプラクティス です:

Promise.reject(TypeError("Wrong type given, expected a string"));

そうすることで、エラー処理の一貫性を保つことができます。他のチームメンバーが常に error.message にアクセスすることができますし、さらに重要なことに、スタックトレースを調査することができます。

Promise.reject に加えて、例外を投げることで Promise チェーンから抜け出すことができます。

以下のコード例を考えてみます:

Promise.resolve("A string").then(value => {
  if (typeof value === "string") {
    throw TypeError("Expected a number!");
  }
});

文字列を返すとともに Promise をリゾルブし、そしてその直後に throw によって例外を投げています。

例外の伝播を食い止めるために、通常通り catch を使うことができます:

Promise.resolve("A string")
  .then(value => {
    if (typeof value === "string") {
      throw TypeError("Expected a number!");
    }
  })
  .catch(reason => console.log(reason.message));

このパターンは、fetch を使う際によく用いられます。レスポンスオブジェクトのエラーチェックを行う例は以下の通りです:

fetch("https://example-dev/api/")
  .then(response => {
    if (!response.ok) {
      throw Error(response.statusText);
    }

    return response.json();
  })
  .then(json => console.log(json));

ここでも、catch によって例外を受け取ることができます。もし例外を受け取ることに失敗した場合、あるいはあえて受け取らないことにした場合、 例外はキャッチされるまでスタックに残り続けます。

これは一概に悪いこととは言えませんが、環境によって、キャッチされていないリジェクトに対する挙動は異なります。

例えば、Node.js は将来的に、処理されていない Promise のリジェクトがあった場合は、プログラムをクラッシュさせる予定です:

DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

必ずリジェクトはキャッチしましょう!

"プロミス化"されたタイマーのエラー処理

タイマーとイベントにおいて、コールバック関数内で投げられた例外をキャッチすることは不可能ではありません。前のセクションで、以下のような例を挙げました:

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}

// DOES NOT WORK
try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

Promise によって与えられた解決策は、コードの「プロミス化」です。基本的に、Promise でタイマーを囲みます:

function failAfterOneSecond() {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(Error("Something went wrong!"));
    }, 1000);
  });
}

reject によって Promise のリジェクトをセットし、エラーオブジェクトを渡します。

この時点で、catch をつかって例外を処理することができます:

failAfterOneSecond().catch(reason => console.error(reason.message));

Tips: 成功した Promise の返り値の変数名として value、Promise のリジェクトの変数名として reason を使うことが一般的です。

Node.js は promisify と呼ばれる、古い形で書かれたコールバック API をプロミス化するユーティリティを提供しています。

Promise.all のエラー処理

Promise の static メソッドである Promise.all は Promise の配列を引数にとり、リゾルブした Promise の配列を返します。

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.resolve("All good here too!");

Promise.all([promise1, promise2]).then((results) => console.log(results));

// [ 'All good!', 'All good here too!' ]

渡した配列のどれか1つでもリジェクトされた場合、Promise.all は最初にリジェクトされた Promise のエラーとともにリジェクトします。

このような状況を扱うために、前のセクションで見たように catch が利用できます:

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.reject(Error("No good, sorry!"));
const promise3 = Promise.reject(Error("Bad day ..."));

Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message));

Promise.all の実行結果に関わらず関数を実行するには、finally を利用します:

Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));

Promise.any のエラー処理

Promise.any (Firefox > 79, Chrome > 85) は、Promise.all の反対の処理をする関数と考えることができます。

Promise.all が、渡した配列の中に 1 つでもリジェクトされるものがあった場合にエラーを返すのに対し、Promise.any はリジェクトが発生しても、リゾルブしたものが 1 つでもあればそれを返します。

Promise.any に渡した配列に含まれる すべての Promise がリジェクトされた場合、結果として得られるエラーは AggregatedError です。以下のようなコード例を考えてみましょう:

const promise1 = Promise.reject(Error("No good, sorry!"));
const promise2 = Promise.reject(Error("Bad day ..."));

Promise.any([promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));

catch を使ってエラーを処理しています。このコードの実行結果は以下の通りです:

AggregateError: No Promise in Promise.any was resolved
Always runs!

AggregatedError オブジェクトは、通常の Error オブジェクトと同様のプロパティに加えて、errors プロパティを持っています:

//
  .catch(error => console.error(error.errors))
//

このプロパティは、それぞれのリジェクトで返されたエラーの配列を格納しています:

[Error: "No good, sorry!, Error: "Bad day ..."]

Promise.race のエラー処理

Promise.race は、Promsie の配列を引数に取ります:

const promise1 = Promise.resolve("The first!");
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, promise2]).then(result => console.log(result));

// The first!

得られる返り値は、 「レース」を制した 1 番着の Promise です。

リジェクトされた場合はどうなるのでしょうか?リジェクトされる Promise が一番でなければ、Promise.race はリゾルブします:

const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, rejection, promise2]).then(result =>
  console.log(result)
);

// The first!

もし リジェクトが一番になった場合、Promise.race はリジェクトされ 、以下のようにしてリジェクトをキャッチすることができます:

const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([rejection, promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error.message));

// Ouch!

Promise.allSettled のエラー処理

Promise.allSettled は ECMAScript 2020 で追加される関数です。

この関数を使って処理するケースはそれほど多くありません。なぜなら、 Promise のリジェクトがあったとしても、返り値は常にリゾルブされた Promise になるため です。

以下のようなコード例を考えてみます:

const promise1 = Promise.resolve("Good!");
const promise2 = Promise.reject(Error("No good, sorry!"));

Promise.allSettled([promise1, promise2])
  .then(results => console.log(results))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));

上の例では、リゾルブする Promise とリジェクトされる Promise を 1 つずつ含め、配列として渡しています。

このケースでは、catch が実行されることはありません。代わりに finally が実行されます。

then 内の処理によってロギングされる結果は次の通りです:

[
  { status: 'fulfilled', value: 'Good!' },
  {
    status: 'rejected',
    reason: Error: No good, sorry!
  }
]

async/await のエラー処理

JavaScript の async/await は非同期関数を表しますが、コードを読む立場からみれば、同期関数の 可読性の高さ の恩恵を受けているといえます。

話を単純にするために、何度も登場している同期関数 toUppercase を、function キーワードの前に async を付け足すことで、非同期関数に変換します:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

async というプレフィックスを使うことで、関数に Promise を返す ように仕向けることができるようになります。これはつまり、thencatchfinally といったチェーンが使えることを意味します:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase("abc")
  .then(result => console.log(result))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));

async 関数内で例外を投げた 場合、この例外は、 裏側で機能している Promise をリジェクト させます。

どんなエラーも、catch によってキャッチすることができます。

最も重要なことは、このスタイルに加えて同期関数と同様に try/catch/finally 構文を使える、ということです。

以下の例では、toUppercase 関数を consumer という他の関数から呼び出しています。consumer 内部では、toUppercase 関数を try/catch/finally 関数で囲っています:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

async function consumer() {
  try {
    await toUppercase(98);
  } catch (error) {
    console.error(error.message);
  } finally {
    console.log("Always runs!");
  }
}

consumer(); // Returning Promise ignored

実行結果は以下の通りです:

Wrong type given, expected a string
Always runs!

同様のトピックは次の記事でも扱っています: How to Throw Errors From Async Functions in JavaScript?

非同期ジェネレーターのエラー処理

JavaScript の 非同期ジェネレーター は、通常の値の代わりに Promise を yeild することができるジェネレーター関数 です。

async とジェネレーター関数を組み合わせて使います。イテレータオブジェクトが呼び出し元に対して Promise を返すジェネレーター関数です。

非同期ジェネレーターを作るために、async でプレフィックスした、* を持つ関数を定義します:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

Promise の仕組みに基づいているため、エラー処理に対しても同様のルールが適用されます。非同期ジェネレーター関数内の throw は Promise のリジェクトに繋がり、catch でキャッチすることができます。

非同期ジェネレーター関数から Promise を取り出す には、以下の 2 つのアプローチがあります。

  • thenハンドラ
  • 非同期イテレーション

上のコード例では、最初の 2 つの値が yield されたあとに、例外が投げられます。これは以下のようにできることを意味します:

const go = asyncGenerator();

go.next().then(value => console.log(value));
go.next().then(value => console.log(value));
go.next().catch(reason => console.error(reason.message));

上記コードの実行結果は以下の通りです:

{ value: 33, done: false }
{ value: 99, done: false }
Something went wrong!

もう1つのアプローチは、 for await...of非同期イテレーション を用いる方法です。非同期イテレーションを用いるためには、呼び出し側の関数を async で囲む必要があります。

以下が完全なコード例です:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

async function consumer() {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
}

consumer();

async/await で見たように、潜在的に存在する例外は try/catch で処理することができます:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

async function consumer() {
  try {
    for await (const value of asyncGenerator()) {
      console.log(value);
    }
  } catch (error) {
    console.error(error.message);
  }
}

consumer();

実行結果は以下の通りです:

33
99
Something went wrong!

非同期ジェネレーター関数の返り値であるイテレータオブジェクトには、同期ジェネレーター関数と同様に throw() メソッドがあります。

イテレータオブジェクトにおいて throw() メソッドを呼び出すと、例外は投げず、代わりにリジェクトされた Promise を投げます:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  yield 11;
}

const go = asyncGenerator();

go.next().then(value => console.log(value));
go.next().then(value => console.log(value));

go.throw(Error("Let's reject!"));

go.next().then(value => console.log(value)); // value is undefined

この状況を処理するには、以下のようにできます:

go.throw(Error("Let's reject!")).catch(reason => console.error(reason.message));

ただし、イテレータオブジェクトの throw()ジェネレーター関数の内部 に例外を送ることを忘れないでおきましょう。これは、以下のようなパターンを適用することを意味します:

async function* asyncGenerator() {
  try {
    yield 33;
    yield 99;
    yield 11;
  } catch (error) {
    console.error(error.message);
  }
}

const go = asyncGenerator();

go.next().then(value => console.log(value));
go.next().then(value => console.log(value));

go.throw(Error("Let's reject!"));

go.next().then(value => console.log(value)); // value is undefined

Node.js のエラー処理

Node.js の同期エラー処理

Node.js における同期エラー処理は、今までみてきた内容とほとんど同じです。

同期コード には、try/catch/finally が使えます。

しかしながら、非同期の世界に目を向けてみると、面白いことが起こります。

Node.js の非同期エラー処理: コールバックパターン

非同期コードにおいては、Node.js は 2 つの書き方に依存しています:

  • コールバックパターン
  • イベントエミッター

コールバックパターン において 非同期 Node.js API は、 イベントループ を通して処理され コールスタック が空になるとすぐに実行されるという関数を引数に取ります。

以下のようなコードを考えてみましょう:

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) console.error(error);
    // do stuff with the data
  });
}

上のコードからコールバック関数を抽出すると、どのようにエラーを処理することになっているかを見ることができます:

//
function(error, data) {
    if (error) console.error(error);
    // do stuff with the data
  }
//

fs.readFile の実行過程においてエラーが発生した場合には、エラーオブジェクトを得ます。

この時点で、以下のことが可能です:

  • 今までしてきたように、単純にエラーオブジェクトのログを表示する
  • 例外を投げる
  • 他のコールバックにエラーを渡す

例外を投げる場合は、以下のようにできます:

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) throw Error(error.message);
    // do stuff with the data
  });
}

しかし、DOM におけるイベントやタイマーと同様に、この例外は プログラムをクラッシュ させます。以下のように try/catch を用いてクラッシュを阻止しようとしても、うまくいきません:

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) throw Error(error.message);
    // do stuff with the data
  });
}

try {
  readDataset("not-here.txt");
} catch (error) {
  console.error(error.message);
}

プログラムをクラッシュさせたくなければ、他のコールバックにエラーを渡すことが望ましい方法です。

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) return errorHandler(error);
    // do stuff with the data
  });
}

ここで用いている eventHandler はその名前からも分かるように、エラーを処理するシンプルな関数です:

function errorHandler(error) {
  console.error(error.message);
  // do something with the error:
  // - write to a log.
  // - send to an external logger.
}

Node.js における非同期エラー処理: イベントエミッター

Node.js で行う多くのことは、 イベント に基づいています。ほとんどの場合、 エミッターオブジェクト と、いくつかのメッセージを待ち受けているオブザーバーとやり取りを行います。

Node.js のイベント駆動なモジュール(例えば net など)はすべて EventEmitter というルートクラスを継承しています。

Node.js の EventEmitter は、2 つの基本的なメソッド持っています: onemit です。

以下のような単純な HTTP サーバーを考えてみましょう:

const net = require("net");

const server = net.createServer().listen(8081, "127.0.0.1");

server.on("listening", function () {
  console.log("Server listening!");
});

server.on("connection", function (socket) {
  console.log("Client connected!");
  socket.end("Hello client!");
});

ここで、以下の 2 つのイベントを待ち受けています: listeningconnection です。

これらのイベントに加えて、イベントエミッターはエラーが発生した際に エラー イベントも発火します。

もしこの上記コードのポート番号を 80 にして実行した場合、以下のような例外を得るでしょう:

const net = require("net");

const server = net.createServer().listen(80, "127.0.0.1");

server.on("listening", function () {
  console.log("Server listening!");
});

server.on("connection", function (socket) {
  console.log("Client connected!");
  socket.end("Hello client!");
});

実行結果:

events.js:291
      throw er; // Unhandled 'error' event
      ^

Error: listen EACCES: permission denied 127.0.0.1:80
Emitted 'error' event on Server instance at: ...

この例外をキャッチするためには、 エラー を待ち受けるイベントハンドラを登録します:

server.on("error", function(error) {
  console.error(error.message);
});

以下のような結果を得ます:

listen EACCES: permission denied 127.0.0.1:80

さらに、プログラムはクラッシュしません。

このトピックについての詳細は、"Error Handling in Node.js" を読むと良いでしょう。

まとめ

このガイドでは、シンプルな同期コードから高度な非同期な仕組みまで、JavaScript のエラー処理全般を扱いました。

JavaScript のプログラムでは、例外の発生の仕方は多岐にわたります。

同期コードの例外は最も単純に対処することができますが、 非同期コード における例外処理は 複雑になる 場合があります。

一方で、ブラウザの新しい JavaScript API はほとんどすべて Promise に向かっています。普及したこのパターンは、then/catch/finally または async/awaittry/catch を使って例外を処理することをより簡単にします。

このガイドを読んだ後は、 プログラムで起こり得るすべての状況を認識して、例外を正しくキャッチすることができる ようになっているはずです。

最後までお読み頂きありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptの基本の書き方③

はじめに

前回,前々回は以下から

Javascriptの基本の書き方①

Javascriptの基本の書き方②

関数の定義と呼び出し

・定義方法

const 定数名 = function() {
  処理;
};

・呼び出し方法

定数名();

アロー関数

ES6からは関数の定義方法を簡素化できるアロー関数が可能になった

・定義方法

const 定数名 = () => {
  処理;
};

オブジェクト内で定義、実行する方法

・定義方法

const 定数名={
  プロパティ値:() => {
    処理;
  }
}

・実行方法

定数名.プロパティ値();

クラスの作成方法

class クラス名{

}

クラスからオブジェクトを作成する方法

class クラス名 {
}

const 定数名= new クラス名();

コンストラクタ

コンストラクタとはインスタンスが生成されたときに実行する機能

class クラス名 {
  constructor(){
    処理;
  }  
}

クラス内でプロパティ値を使用する方法

・定義方法

class クラス名 {
  constructor() {
    this.プロパティ値=;
  }
}

・クラス外でプロパティ値を使用する方法

クラス名.プロパティ値

メソッド

インスタンスの動作を表す。

・定義方法

class クラス名 {
  メソッド名(){
    処理;
  }
}

・クラス内のメソッドの呼び出し方法

クラス名.メソッド名();

メソッド内でインスタンスの値を使用する方法

this.プロパティ名

メソッド内でメソッドを使用する方法

this.メソッド名();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSの無い軽量な静的サイトを生成する11ty

Netlifyのガイド&チュートリアルを読んでいたところ、以下の記事が面白かった。
(英語記事)
https://www.netlify.com/blog/2020/09/18/eleventy-and-vue-a-match-made-to-power-netlify.com/

彼らはWebサイトを次のレベルに引き上げるためのプロジェクトに着手しているようだ。
目標は、サイトを軽量にすることと、コンポーネントの再利用を容易にすることだ。
どうやらクライアント側で大量のJSを読み込むことを良しとしていない。
そのため、以下の技術が採用されている。

  • 11ty
    シンプルな静的サイトジェネレーター。Eleventyとも。
    (ただしEleventyで検索するとアパレルブランドがヒットしてしまうので、11tyを使うことをお勧めする。)

  • Vue
    コンポーネント指向のJSのフレームワーク。お馴染み。

選定にあたって様々なコンポーネント指向のフレームワークを試してみたが、それぞれクライアント側に何かしらのJSランタイムを提供する必要があったようだ。

確かに、有名な静的サイトジェネレーターであるGatsbyのデモサイトを見てみると、scriptタグで外部のJavaScriptを五個読んでいた。
これだけシンプルなサイトであってもだ。
https://gatsby-starter-blog-demo.netlify.app/
SPAという構成上、JSは避けて通れないものだ。

しかしながら、11tyは一つもJSを読み込まない。
https://eleventy-base-blog.netlify.app/
こちらのデモサイトのhtmlを見てほしいが、scriptタグは存在しない。
これこそが彼らが求めていた軽量なサイトなのだ。

ところが、11tyはコンポーネント指向ではない。
そのため、11tyにVueのプラグインを組み込む方法が記事に書かれている。
組み込むといってもあくまで開発側の話で、クライアント側にはVueランタイムが入るわけではない。
デプロイ前のビルド専用の最適化として使われるのみである。
組み込む方法は記事に任せるとして、僕としては11tyについて軽く調べてみた。

11tyは様々なテンプレートエンジンを包括したようなもので、以下のようなものが使える。
https://www.11ty.dev/docs/languages/
mdやjsも使えるので、門戸は広い。

すぐに始められるスターターも用意されていて、Lighthouseスコアが高いものがほとんどだ。
https://www.11ty.dev/docs/starter/
このパフォーマンスはさすがと言える。
(Gatsbyで作ったサイトも実は高得点だが。プリレンダリングとかのお陰かな?)

統計データとして、静的サイトジェネレーターの比較サイトの
https://www.staticgen.com/
を見てみると、直近のGithubスター数ではNext.jsの次の二位に位置している。
勢いがあり、今後メジャーになっていく可能性がある。

今はReact人気のため、GatsbyやNextのシェアが高いが、今後このVueプラグインの利便性が上がったりしてくれば、軽量を求めるサイトでの採用が増えてくると思われる。
(今はVueプラグインを使うには実験的機能を有効にしないといけないので、プロダクションへの採用は控えられるかもしれない)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ES6以降のJavaScript/TypeScriptで0...N-1の配列を作る

動機

JavaScript/TypeScriptで定数(N)で決められた数だけループしたい時、

const N = 100;
for(let i = 0; i < N; i++){
   //...なんか処理
}

こんなダサいループは書きたく無い。。。

pythonのrange(N)みたいなのがJavaScriptにもあったらなぁ。。。

解決策

英語でググってみたらStackOverflowに良い解決策が載っていたのでご紹介。

世界には頭の良い人がいるものだなぁ。

const N = 100;
[...Array(N).keys()].forEach(i => {
   //...なんか処理(iには0...N-1(=99)が格納される)
});

もし1...Nにしたければ、

const N = 100;
[...Array(N).keys()].map(n => n + 1); // [1,2,3,...,100]

こんな具合にmapしてズラしてあげればOK。

応用

ということはもしかして、FizzBuzzがワンライナーで書けるんじゃないか...?

[...Array(100).keys()].map(n => n + 1).map(n => n % 15 === 0 ? 'FizzBuzz' : n % 3 === 0 ? 'Fizz' : n % 5 === 0 ? 'Buzz' : n.toString()).forEach(s => console.log(s));

出来た。

一応解説すると、

[...Array(100).keys()] // [0,1,2, ... ,99]の配列を作る
   .map(n => n + 1)    // mapでズラして[1,2,3, ... , 100]にする
   .map(n => n % 15 === 0 ? 'FizzBuzz' : n % 3 === 0 ? 'Fizz' : n % 5 === 0 ? 'Buzz' : n.toString()) // FizzBuzzのルールを満たすように文字列に変換する
   .forEach(s => console.log(s)); //1行ずつ出力する

この.map().forEach()を繋げて書く表記に慣れていない方は、手前味噌で恐縮ですが私の過去記事をご覧ください。

(ちなみに)indexを使用しない場合

生成した配列の中身の数値を使わず、単にN回ループしたい場合は[...Array(N)]で良いらしい。

// 長さが100の0から99までのランダムな整数の配列を作成
const randomInt = [...Array(100)].map(() => Math.floor(Math.random() * 100));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

font-awesomeとbootstrap(4.5.1)の導入

各種バージョン

Ruby: 2.6.5
Rails: 6.0.0
Bootstrap: 4.5.1
Font-Awesome(Free): 5.7.2

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>boot_and_fontawesome_app</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.18.1/build/cssreset/cssreset-min.css">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    <script src="https://kit.fontawesome.com/d9fcea61b7.js" crossorigin="anonymous"></script>
  </head>

  <body>
    <%= yield %>
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  </body>
</html>

上記を貼るだけ
以下はサンプルコード。

app/views/index.html.erb
<div class="container">
  <div class="jumbotron m-3">
    <h1 class="display-4">Hello, Bootstrap!</h1>
    <hr class="my-4">
    <p>This is the template of Ruby on Rails using Bootstrap.</p>
    <button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#exampleModal">
      <i class="far fa-window-maximize"></i>
      Click here
    </button>
  </div>
  <!-- Modal -->
  <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="modal-body">
          Hello!
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>
</div>

すると以下のようなページを表示できます。
https://gyazo.com/2c476830157ce55b86000591254edc33
ブートストラップとfont-awesomeが適用できていることがわかります。

参考 https://getbootstrap.jp/docs/4.5/getting-started/introduction/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

addEventListenerにより重複している処理を防ぐ方法

動作環境
Ruby 2.6.5
Rails 6.0.3.2

以前、addEventListenerによって同じ処理が何度も発生していたことにより、エラーが起きてしまい、思うような処理ができないことがあったので、投稿してみました。

addEventListenerにより重複している処理が発生してしまう例

index.html.erb
<% @hugas.each do |huga| %>
  <div class="huga" >    
    <%= huga.content %>
  </div>
<% end %>

上記のコードは、hugaのcontentカラムを繰り返し表示させており、その表示されたcontentのclassはhugaであるということを表しています。

huga.js
function hoge() {
  const hugas = document.querySelectorAll(".huga");
  hugas.forEach(function (huga) {
    huga.addEventListener("click", () => {
    //クリックすることで発生する処理
    });
  });
};
setInterval(hoge, 1000);

function hoge()を1行目として、2行目からこのコードの解説をしていきます。
2行目でclassがhugaである要素をすべてhugasに代入しています。
3行目でhugasを1つずつに分けて、それらの名前をhugaとしています。
4行目でhugaをクリックすると5行目の処理を発生するようにしています。
最終行により、以上の動作を毎秒発生させています。

つまり、上記の2つのコードはcontentをクリックすると、huga.jsの5行目に書かれた処理が発生するということを表しています。

しかし、このままではエラーが起きてしまいます。なぜなら、huga.jsの最終行により毎秒2行目と3行目の動作が発生しているからです。それによって何が起こるのかというと、例えばそのページに遷移してから10秒後にcontentをクリックした場合、5行目の処理が10回同時に発生してしまうというようなことが起きてしまいます。

この問題を解決するために、この記事のタイトルである「addEventListenerにより重複している処理を防ぐ方法」が必要となります。

※そもそもsetIntervalではなく、window.addEventListener('load',hoge);にすれば良いのではという意見が出ると思いますが、その通りです。しかし、index.html.erbのhuga.contentにおいて非同期通信が使われている場合はsetIntervalを使う必要があります。非同期通信だとページのロードが行われないからです。

addEventListenerにより重複している処理を防ぐ方法

huga.js
function hoge() {
  const hugas = document.querySelectorAll(".huga");
  hugas.forEach(function (huga) {
    if (huga.getAttribute("baz") != null) {
      return null;
    }
    huga.setAttribute("baz", "true");
    huga.addEventListener("click", () => {
    //クリックすることで発生する処理
    });
  });
};
setInterval(hoge, 1000);

重複している処理は、上記のコードの4行目から7行目を追記することで防ぐことができる。なぜなら、この追記によって何秒経過してからcontentをクリックしても、1つのhugaには1回の処理しか行わないという意味になるからです。

追記した部分を詳しく解説していきます。
まず、1秒経過すると1回目の処理が行われ、if文によって条件分岐が起きます。hugaはbazという属性(Attribute)は持っていないのでnullとなり、条件式はfalseとなりますが、falseの場合の処理は記載されていないため、そのまま次の処理に移ります。7行目によって、hugaにbazという属性が与えられ、それはtrueとなります。つまり、1回目の処理は記載する前と変わっていません。

2秒経過した場合を見ていきます。2秒経過すると2回目の処理が行われ、再度if文によって条件分岐が起きます。1回目と違い、hugaにはbazという属性を持っているため、nullではありません。そのため、条件式はtrueとなり、trueの場合の処理が行われ、return nullが実行されます。return nullとは、処理を抜け出すという意味なので、この記載以降の処理は行われなくなります。つまり、2秒経過してから、contentをクリックしても、処理が1回しか行われないようになります。
当然ですが3秒後以降も同じであるため、重複している処理を防ぐことができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS:filterメソッド】重複した要素のある配列から、重複した要素のみを一つだけ取り出す

filterを使ってみる

filterメソッドとは?
コールバック関数を書いて、特定の配列から条件を満たす、新しい配列を作成する。

sample.js
arr1.filter(コールバック関数) {
  return // 条件 ;
}

filterは3つの引数を設定できます。

今回の、条件を満たす配列を作成するためには引数を3つ使います。

sample.js
arr1.filter(配列の要素の値, 配列要素のインデックス番号, 要素の入った配列そのもの) {}

例)数字の配列から重複する数字を取り除いた数字を取り出す

sample.js
let arr1 = [1,2,2,3,3,3,4,5,6,6];
let arr2 = arr1.filter(function(val, i, arr) {
  return arr.indexOf(val) === i;
});

console.log(arr2); // [1, 2, 3, 4, 5, 6]

arr.indexOf(val) === i;がtrueのものを順番に取り出して配列にしていく。

indexOf(val)は、配列に入っている要素で初めて出てくる要素のindex番号を返しています。

console.logで確認してみると。

sample.js
let arr1 = [1,2,2,3,3,3,4,5,6,6];
let arr2 = arr1.filter(function(val, i, arr) {
  console.log(arr.indexOf(val)); // 0 1 1 3 3 3 6 7 8 8
  console.log(arr.indexOf(val)===i); 
// true true false true false false true true true false
});

つまり、1という要素が配列に複数入っている場合、最初のindex番号の1だけ取り出すため、重複した数字の取り除いた配列が出来上がります。

【本題】重複した要素のある配列から、重複した要素のみを一つだけ取り出す

sample.js
let arr1 = [1,2,2,3,3,3,4,5,6,6];
let arr2 = arr1.filter(function(val, i, arr) {
  return arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
});

console.log(arr2); // [2, 3, 6]

i !== arr.lastIndexOf(val)
ここでも何が行われているのかわからないのでconsole.logで確認してみる。

sample.js
let arr1 = [1,2,2,3,3,3,4,5,6,6];
let arr2 = arr1.filter(function(val, i, arr) {
  console.log(arr.lastIndexOf(val)); // 0 2 2 5 5 5 6 7 9 9
  console.log(i !== arr.lastIndexOf(val));
  // false true false true true false false false true false
});

lastIndexOf(val):indexOf(val)とは違って、配列に入っている要素で最後に出てくる要素のindex番号を返しています。

i !== arr.lastIndexOf(val)
→最後のindex番号以外でtrueを満たすもの。

最後に、

arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
で、&&を使用することで、重複した要素のある配列から、重複した要素のみを一つだけ取り出します

参考

JavaScriptで配列の重複を除去する
配列の重複をはじく、もしくは重複を取り出す

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(´ω`) ... VueのTable {width,align}を一括指定するディレクティブつくりました

前回、DatGuiをBackEnd APIを検証するための登録画面に利用している。って記事を記載した。がだれからも良いねはなくLGTMも来なくて内心ビビってる。マジか。と思った。というかツイッターのフォロワーは未だ増えないままだ。この現状を見てInputMaskをぶっこんだ話なんかも展開する予定だったがやめた。そしてQiitaフォントサイズ大きいなってことで自分でブログを作ろうと思った。プロトタイプはあるし順調だ。だからその話もメモ代わりにQiitaで展開していくつもりだ。そこで動作するサンプルなんかも公開しよう。その間の記事はQiitaにメモ代わりで投稿することにした。

1.前提

趣味で何かを開発していたら、テーブルの列が後から増えてくる(登録する項目なんか)ってのはよくある話だ。少なくとも自分の間じゃそうだ。最初のプロトタイプを作るときは名前なんて姓だけで十分だしあとIDとPASSの3項目でテーブル作って社員登録機能を作る。みたいな最小限の構成で組み始めている。できた次に社員名いっとくか。メールアドレスいっとくか。みたいに項目を増やしていく。(機能が出来たら次のデータを作る。棚田だ)
image.png

2.問題

ここで起きてくる問題はCSSだ。今や<tag>の中にstyleをぶっこむなんて真似も全くしてこなかった俺だが、このような作り方の前では後から項目が増えるからID指定やCLASS指定でやろうと面倒くさくなってくる。nth-child(n)で最初は指定していたが列間の途中に項目を増やす状況になると、配列途中に値を挿入するみたいに以降の全番号を手で振りなおすという愚かさだ。面倒くさい(姓と名は左寄せのほうがいいな)

sample.css
table tr td:nth-child(1) { width:6em; text-align:center; }
table tr td.employee-name { width:6wm; text-align:center; }
table tr td#employee-name { width:6em; text-align:center; }
table tr td[data=employee_name] { width:6em; text-align:center; }

3.結局

タグの中に書き込んだ方が一番手っ取り早い。という結論に至った。周りはどうかな?どうやってるか知りたい。だが、なぜ毎回widthやtext-alignを打ち込まなくちゃならないんだ?面倒くさい。APIを検証するためのこの画面ではテーブルの項目は永遠と増え続ける。テーブル自体もだ。そのたびに書くのは面倒だ。その間の時間を省いてチロルチョコを食べた方がいい。健康的な考えだ。

sample.html
 <table>
   <tr>
     <td style='width:6em;text-align:center;'></td>
  </tr>
</table>

4.ということでディレクティブを作った

前置きが長かった。要はセルのtdのwidthとalignを{attribute:value}をvalueの一括指定でDOM作ってくれたら便利だなという話だ。ソースはこれ。

directive.vue
   Vue.directive('style',function(el,binding,vnode){
     vnode.context.$nextTick(function(){
       var arr = binding.value.split(',') 
       switch(binding.arg){
         case 'align':
           _.each(el.children,function(el,i){
             var align = arr[i] == 'l' ? 'left' : arr[i] == 'r' ? 'right' : 'center'
             el.classList.add(align)
           })
           break;
         case 'width':
           _.each(el.children,function(el,i){
             el.style.width = (arr[i] == undefined) ? 'auto' : arr[i] + 'em'
           })
           break;  
       }
     })
   })

5.使い方

ディレクティブをかますだけ。<tr>に。列の幅指定はヘッダだけで良く配置指定はボディに組み込んでいるが、確かにv-style:alignは効率的じゃないかもしれない。全セルに指定することになるからループも多い。ここだけCSSにした方がいいかも。どうやるのがベストだと思うか知りたい。ダブルクォートの間にシングルクォートを挟むのはこれ自体を文字列として評価してもらうためだ。

それからこのコードはLODASHに依存している。_.each()のとこだけだ。

sample.html
 <table>
   <thead>
     <tr v-style:width="'7,7,7,5,5,5,5,5,5,5,5,5,5,auto'">
    </tr>
  </thead>
   <tbody>
     <tr v-for='v,k,n in list' v-style:align="'c,l,l,c,c,c,c,c,c,c,c,c,c,c'">
    </tr>
  </tbody>
</table>

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jsGridでよく使うページ操作の一覧

はじめに

よく使ったページ操作の一覧をまとめました。

現在のページインデックスを取得

// 現在のページインデックスを取得
var pageIndex = $("#grid").jsGrid("option", "pageIndex");

特定のページを開く

指定したインデックスのページを開きます。
pageIndexは、開くページの1から始まるインデックスです。値は1~総ページ数の範囲である必要があります。

// 最後のページを開く
// pageCountにはデフォルトでページ総数が入ります
$("#jsGrid").jsGrid("openPage", "pageCount");

グリッドのフィルタリング

グリッドをフィルタリングして、該当のデータがあれば特定の処理を実施できる。
今回は、グリッドをフィールド「名前」を「Otto Clay」でフィルタリングしている。

// グリッドのフィルタリング
$("#grid").jsGrid("search", { Name: "Otto Clay" }).done(function() {
    console.log("Otto Clayが見つかりました!");
});

2020-09-21.png

参考

Documentation - jsGrid

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHubのリンクが同じ画面で開かれるのがしんどかったのでChromeの拡張機能作った

久々のQiita投稿?
普段はその辺でエンジニアやってる物です。

個人で使うめちゃくちゃニッチな物を作ったので、せっかくなので記事にしてみました。
そしてここから下、「cmd + クリックでええやん」禁止です?‍♂️
それすらめんどくさかったので作った所存です。

何を作ったの?

GitHub上でリンクをクリックした時に遷移先が同じ画面で開かれるのがしんどかったのでChromeの拡張機能を作った話です。
スクリーンショット 2020-09-21 11.19.17.png

作ったものはこちら。
Chromeウェブストアに公開しているので、よかったらダウンロードして使ってみてください。

https://chrome.google.com/webstore/detail/github-target-blank/bhikeadioahfjdfnckphpmfjkpmingah

ちなみに、2020年9月現在のGitHub上のクラスやIDを利用しているので、GitHub上のHTML構造が変更された場合突然動かなくなることも考えられます。
メンテを続けるかどうかはまだ決めてません。

GitHubのしんどさとは

コードレビューをGitHub上で行う時に、PR画面のConversation内にコミットハッシュを書くと自動でリンクになってくれる便利機能があります。
こんな感じですね。

スクリーンショット 2020-09-15 16.56.58.png

マウスオーバーでコミットログとかみられるんですけど、見たいのはそこじゃない、
そのcommitでどのファイルをどうやって修正しているかなんだ。

あとこんな時。
こんなものに他の方に協力仰ぐのも申し訳なかったので以下全部自演コメントです?‍♂️

スクリーンショット 2020-09-21 11.12.59.png

スクリーンショット 2020-09-21 11.13.07.png

こんなやりとり数多くあったのでGitHubユーザの方ならきっと想像できるはず。

レビュアーとしては、
「お、修正してくれたんだ〜〜修正内容どんな感じかな〜〜〜〜」って
コミットログのリンクを押して確認しようとすると、どういうわけか同じタブで画面遷移します

もちろん、戻るボタンで戻っても入力したコメントとかは消えないですよ?

でも普通に考えると、別タブで開いて修正履歴じっくり見たい時のが多く無いですか?

あと、他にもPRの概要を読みながらおもむろにFiles changedタブとか押してPR全体のファイル変更履歴とか見たくないですか?

そんな要望に応えることができるChrome拡張を作りました。
その名もgithub-target-blankです。

工夫したところ

アプローチとしてまず考えたのが、
別タブで開きたいa要素に対して target='blank'を動的に付与する
というやり方。
(rel='no-oppener no-referrer'も一緒に)

GitHubのページの特定のa要素のみtarget='blank'を付与するだけなので、そこまで大変では無いやり方ですね。

ただこのやり方だと、開かれたページがアクティブな状態になります。
そう、PRのConversationを見ているのにリンクを押すと別のタブがアクティブになってしまうわけですね。

違う、違うんだ。
僕が欲しいのはGoogle Chromeで cmd + クリックでリンクを押した時のバックグラウンドでタブが開かれるあの動きなんだ。

というわけでこのアプローチは断念。
別のアプローチをなんか無いかなーと探していたところ、こんなメソッドを発見。

https://developer.mozilla.org/ja/docs/Web/API/MouseEvent

MouseEvent インターフェイスは、ポインティングデバイス (マウスなど) によるユーザの対話によって発生したイベントを表します。
このインターフェイスを使用する一般的なイベントとして click, dblclick, mouseup, mousedown があります。

ふむふむ。あれこれなんか使えるくね?
っていうか、擬似的にa要素作って、MouseEventで擬似a要素をDispatchしちゃえばいいんじゃね?

って思って探したらありました。さすがはStackOverFlow。
https://stackoverflow.com/questions/10812628/open-a-new-tab-in-the-background

注意
ここで紹介されてるinitMouseEventはDeprecatedのメソッドのため、利用非推奨です。
代替機能として、MouseEventが推奨されてます。

おーできそう、ってことでコード書いてみました。
メインの部分はこんな感じ。
ChromeのExtensionにはjQueryも使えるっぽいけど、サイト内でバージョン競合とか起こすと嫌なので久々にVanillaJS。

hoge.addEventListener("click", function (e) {
  const evt = new MouseEvent("click", {
    metaKey: true, // Macだとcmdキーが metaKeyとして判定される学び
    ctrlKey: true, // Windowsだとバックグラウンドで開く時に使うのがctrlキーなのでこちらも設定
  });
  const a = document.createElement("a");
  a.href = e.currentTarget.url; // 遷移先のURL
  a.dispatchEvent(evt); // ここでMouseEventイベントを擬似的に作ったa要素に向けて発火
  e.preventDefault(); // 本来のa要素での移動をキャンセル
});

Macの場合cmdキーがmetaKeyになるとか知らなんだ。。。20分ぐらいハマりました。

ってなわけで、GoogleChromeでcmd(ctrl) + クリックした時にバックグラウンドでタブが開くあの動きが再現できました、わーい。

苦労したところ

ChromeExtensionの仕組みわからなすぎ問題
開発にあたり一時間ぐらいこのページとにらめっこしてました。

https://developer.chrome.com/extensions/getstarted

三つの世界の世界観がマジでわからなかったので、@sakaimoさんの記事を参考にしました。圧倒的感謝??‍♂️?
https://qiita.com/sakaimo/items/416f36db1aa982d8d00c

特定のページだけで利用したい時はContent Scripts
アイコン(Extensionインストール後に出てくる右上のあれ)クリック時に何かしたい時はBrowser Action (Page Action)
Chrome起動中ずっと動かしときたい時はEvent Pageって区分のようです。

今回はGitHub上で動けばよかったのでContent Scriptを採用してましたが、そのうち「これはバックグラウンドでこれは普通に開きたい」とかあるだろうなぁという気持ちがあったため、extensionの設定をローカルストレージに保存するようにするために、

popup.js上で設定変更

background.jsにイベント(任意のメッセージ)を送信

background.js内で設定内容をローカルストレージに保存

contents.js発火時にローカルストレージ上の設定をbackground.js経由で取得

有効な設定要素のみ、addEventListenerを実行

っていうフローを取ってます。結局全部使ってる。

pjax対応
GitHubのHTMLElementsを眺めてるとどうやらpjaxが利用されているようで、ページ遷移した時にうまく動かないバグがありました。
この辺りはMutationObserverを使ってDOMを監視し、変更されたタイミングで再度content.jsを発火するようにしています。

const target = document.getElementById(observerSettings.selector);
// 現状だと github.com がホストに含まれるときは実行するようになっているため、
// 特定ページ意外で発火したときのエラーを防ぐために、getElementByIdで取得した要素が存在する場合のみ
// MutationObserverを利用するようにしている
if (target) { 
  const observer = new MutationObserver((mutations) => {
    // ここで任意の処理を発火することができるので、メイン処理を再度実効
  });
  observer.observe(target, observerSettings.config);
}

さいごに

久々にVanillaのJSでコーディングしたり、Chromeの拡張機能の仕組みに混乱したり、
「よっしゃできたーーー」ってなってたらinitMouseEventが利用非推奨なことに気がついて全部作り替えることになったりしましたが、
ChromeのExtensionは手軽にコーディングできる良い環境だと思いました。

なんだか、GoogleChromeで急に回転するポプ子とピピ美が見たくなった気がしたので、クソアプリアドベントカレンダーにでもエントリーしようかなと思った今日この頃。

なんやかんや言ってますが楽しくコーディングできました。
皆さんも、是非ChromeのExtensionを作ってみては如何でしょうか。
コードレビュー時のあーーーーーいまのページ移動しないで欲しいのーーーも解消されたので、本日はここまでにします。

同じような悩みがある方、是非使ってみてください。
ご意見ご感想PRお待ちしております。

おまけ

・・・きっと大変なことがあったんだろうと思いましたまる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue3 でダッシュボードのスケルトンを作る。

Vue3 がリリースされました。

売りであるComposition APIはだいぶいい感じです。
Vue2に比べて学習コストがかなり低そうです。

Vue3そのものについては他所でたくさん説明されるでしょうから、この記事では実際にVue3の大きな使われ方と思われるダッシュボードのスケルトンをVue CLIを使って作る手順をご紹介します。

またComposition APIもさらっと使ってみます。

できあがるのは以下のようなものです。ナビゲーションバーのHome,Aboutでメインコンテンツが切り替わり、左上のハンバーガーボタンでナビゲーションバーの幅が変化します。

スクリーンショット 2020-09-21 4.18.52.png

準備

npm install -g @vue/cli

9/20現在インストールされるバージョンは4.5.6です。この記事もそのバージョンで確認しています。

プロジェクトを作成します。

vue create dashboard

プロジェクト名(dashboard)は任意のものでかまいません。

以下のようにオプションが表示されます。

Vue CLI v4.5.6
? Please pick a preset: 
  Default ([Vue 2] babel, eslint) 
  Default (Vue 3 Preview) ([Vue 3] babel, eslint) 
❯ Manually select features 

vue-routerを使うのでManually select featuresを選択してください。

Vue CLI v4.5.6
? Please pick a preset: Manually select features
? Check the features needed for your project: 
 ◉ Choose Vue version
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
❯◉ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

Router を選択します。

Vue CLI v4.5.6
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, Linter
? Choose a version of Vue.js that you want to start the project with 
  2.x 
❯ 3.x (Preview) 

この後の質問はデフォルトを選択しておけばいいです。

サービスを開始します。

cd dashboard
yarn serve

ブラウザでlocalhost:8080に繋ぎます。

作っていく

この時点でプロジェクトフォルダの中のpublicsrcは以下のような中身になっています。この中のpublic/index.htmlsrc/App.vueを変更していきます。

public
├── favicon.ico
└── index.html
src
├── App.vue
├── assets
│   └── logo.png
├── components
│   └── HelloWorld.vue
├── main.js
├── router
│   └── index.js
└── views
    ├── About.vue
    └── Home.vue

html, body, #app にスタイルを充てる

全画面を使うために、public/index.htmlcssを追記して高さを調整します。

<style>
html, body, #app {
;   height: 100%
;   margin: 0
}
</style>

聖杯レイアウトを作成する。

App.vueを編集して聖杯レイアウトにします。

src/App.vue
<template>
    <div id=HG>
        <header><div @click="toggleNav()"></div></header>
        <nav>
            <router-link to="/">Home</router-link><br>
            <router-link to="/about">About</router-link><br>
        </nav>
        <router-view/>
        <aside>ASIDE</aside>
        <footer>{{ shrink }}</footer>
    </div>
</template>

<script>
import { ref } from 'vue'
export default {
    setup() {
        const shrink = ref( false )
        return {
            shrink
        ,   toggleNav   : () => {
                document.querySelector( 'nav' ).style.width = shrink.value ? '200px' : '100px'
                shrink.value = !shrink.value
            }
        }
    }
}
</script>

<style>
#HG {
;   height                  : 100%
;   display                 : grid
;   grid-template-rows      : 32px 1fr 32px
;   grid-template-columns   : auto 1fr auto
}
nav, aside {
;   width                   : 200px
;   background              : lightgreen
;   transition              : all 300ms 0s ease
}
header, footer {
;   grid-column             : 1 / 4
;   background              : pink
}
</style>

Composition-API

上のソースのscriptタグの中でComposition-APIを使用しています。
setup()が返す辞書がtemplateの中で{{}}とか@clickとかから参照できるようになります。

最後に

Composition-APIVue2時代の指定方法より直感的で学習コストを下げることに成功していると思われます。積極的にVue3に乗り換えていっていいんじゃないでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm から取得した Node-RED をカスタマイズして起動する(2)

何のため?

Node-RED を Heroku で動作させたかったのだが想定外の動作をしていました。
Node-RED で発生している事象を調査するための環境構築手順を整理した記録です。

セオリーから外れているかもしれませんので、間違っていたらコメントをお願いします。

0. 準備

前の記事で node-red-sample ディレクトリ配下に Node-RED のソースがある状態の環境を構築した。
本記事では、『2.2 カスタマイズ方法(2) : settings.js を userDir に入れておく』 の方法で進めていく。

準備としては、下記コマンドを入力したところから開始となります。

$ mkdir node-red-sample
$ cd node-red-sample
$ npm -y init
$ npm install node-red
$ mkdir data
$ cp ./node_modules/node-red/settings.js ./data/

Node-RED を起動するには下記コマンドを入力する。

$ node ./node_modules/node-red/red.js  --userDir ./data

1. npm で入手した Node-RED をローカル管理に変更

前述の通り、Node-RED のコアをデバッグしたいので、ソース管理内に保持する手順を実施する。

1.1. Node-RED をローカルフォルダに移動

改変を加える Node-RED ソースは local_packages ディレクトリに置いて作業を進める事にする。

$ mkdir local_packages
$ mv node_modules/node-red ./local_packages/
$ mv node_modules/@node-red ./local_packages/
# 念のため依存パッケージからnpmjsのnode-redを取り除く
$ npm uninstall node-red
$ rm package-lock.json

1.2. package.json を書き換え

local_packages フォルダのモジュール類を dependencies 記述する。

package.json
  "dependencies": {
    "node-red": "file:local_packages/node-red",
    "@node-red/nodes": "file:local_packages/@node-red/nodes",
    "@node-red/editor-api": "file:local_packages/@node-red/editor-api",
    "@node-red/editor-client": "file:local_packages/@node-red/editor-client",
    "@node-red/registry": "file:local_packages/@node-red/registry",
    "@node-red/runtime": "file:local_packages/@node-red/runtime",
    "@node-red/util": "file:local_packages/@node-red/util"
  }

1.3. 書き換えた package.json の内容を node_modules に反映させる

$ npm install

以上で、node_modules 配下には local_packages からのハードリンクが張られる。
Node-RED Core 実体は local_packages にあるものが利用される。

→ コメント追加するなりして、自由にデバッグが出来るので HAPPY !!!

まとめ

本手順で作ったソース一式を git で管理し、herokuで好きなようにデバッグ出来るようになった。

→ その後の調査でherokuとは関係なく発生する問題であることが判明した。

続く...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

var と let/const の違い(+ 関数とホイスティングのお話)

前提

8月末から、プログラミングの学習を始めました。
学習の質を高めるため、学んだことを Qiita にアウトプットする取り組みを続けています。

現在は、HTML/CSS の学習がひと段落ついたので、JavaScript の学習に入りました。
ただ、JavaScript に入ってから、学習内容がかなり本格化してきたのを感じています。
学んだことをかみ砕くのに時間がかかるので、Qiita へのアウトプットも滞りがちになってしまいました。

これじゃいかん!
ということで、急ぎ投稿したのがこの記事です。

この記事の目的

ここ数日でようやく腑に落ちてきた「ホイスティング関連」について、自分なりにまとめるのが目的です。
アップアップしながら書いているので、思わぬ間違いがあるかもしれません。
(余裕がないあまり、本文は「ですます調」ですらありません・汗)

もしお読みくださった方が間違いに気づかれましたら、ご指摘いただけると大変助かります。

それでは以下、本文に移ります。

宣言文の前で呼び出せるか? その1

まずは、宣言文の前に console.log を置いた例で考えてみる。

var の場合

JavaScript
console.log(num);

var num=1;

この場合、console には「undefined」と出力される。

ここでの「undefined」とは、
まだ具体的な数値の入っていない「空き箱」
のようなイメージで考えればよい。

つまり、
   ① var 宣言文 によって「num の空き箱」が作られる     
   ② この「空き箱」が埋まるのは、宣言文よりも後ろ

すると、「console.log(num);」は宣言文の前なので、そこで使われる num はまだ「空き箱」。
そのため、console には「undefined」と出力された。

let の場合

JavaScript
console.log(num);

let num=1;

この場合、console はエラーを出力する。

なぜなら、let は var と異なり、空き箱すら作らない。
そのため、宣言文よりも前では、「num など存在しない」ということになる。

したがって、「console.log(num);」はエラーを返すほかない。

const の場合

JavaScript
console.log(num);

const num=1;

let と同じく、console はエラーを出力する。

その理由も、let と同じ。

ここまでのまとめ

undefined だろうがエラーだろうが、うまく動いていないのは同じじゃない?
わざわざ var を封印しなくてもいいんんじゃない?

・・・とも思える。
だが、次の例を見れば「 var が非推奨になる理由」をより明確に実感できる。

宣言文の前で呼び出せるか? その2

次は、console.log よりも前に「num=500;」が書かれている例で考えてみる。

var の場合

JavaScript
num=500;

console.log(num);

var num=1;

この場合、console には「500」と出力される。

というのも、var によって作られた「num の空き箱」は、宣言文の前後を問わず使える。
「num=500;」の位置でも、「num の空き箱」はれっきと存在しているのだ。

その結果「num=500;」が、「num の空き箱」の中に 500 を入れる。
この 500 は、そのまま「console.log(num);」によって出力されることになる。

・・・これの何がまずいのだろう?

上記の例では、「num=500;」は宣言文からとても近い場所にある。
そのため、それほど問題はないようにも思える。

しかし仮に「num=500;」が、宣言文から10,000行離れた場所にうっかり残した消し忘れだったとしたらどうだろう?
var で宣言した変数は、コード内のいかなる場所からでも、出力結果に影響を与えることができてしまうのだ。

そう考えると、var の使用がバグの温床になりかねないことを実感できる。

let の場合

JavaScript
num=500;

console.log(num);

let num=1;

この場合、console にはエラーが出力される。

なぜなら、let を用いた変数宣言では、「空き箱」が作られない。
宣言文よりも前の場所では、「num という箱じたいが存在しない」ことになる。

したがって、宣言文より先に「num=500;」と書いてあったとしても、
「num って何よ??」
ということになる。
その結果、console.log はエラーを返すほかない。

このように let は、バグの温床になりかねない場面で、明確にエラーを返してくれる。
その意味で、var よりも let を使うべきなのだ。

const の場合

JavaScript
num=500;

console.log(num);

const num=1;

let を用いた場合と同じく、console にはエラーが出力される。

この場面では、const の挙動は let とまったく同じだ。

const との関係でも、 var はやはり非推奨ということになる。

ここまでのまとめ

基本的に、
「var は使わず、let / const を使うべき」
ということがわかった。

なぜなら、var で宣言した変数には、コードのどこからでも代入できてしまうから。
変数の予期せぬ挙動を防ぐためには、let / const を使うのが安全だということになる。

ちなみに、let / const の使い分けは?

なお、let と const の違いは「再代入の可否」。
let は再代入可能、const は再代入不可能、という違いがある。

たとえば、console に「0」「1」「2」と連続して出力されるループ処理を行いたいとする。
この場合、次のようなコードを使う。

JavaScript
for(let i=0; i<3; i=i+1){
  console.log(i);
}

このfor 文の中には、「i=i+1」という「変数 i への再代入」の記述がある。
つまり、これは「再代入が必要なコード」である。

こうしたコードを書くには、let を使わなければならない。
const を使うと再代入ができなくなるからだ。

言い換えると、再代入の不要なコードで let を使うと、「再代入できてしまうことによるバグ」が発生するおそれがある。

結論としては、次のような方針が推奨される。
 ① 基本的には const を使う
 ② 再代入が必要な場合のみ let を使う

おまけ: 関数とホイスティングのお話(※宣言文との位置関係に関して)

これまで「宣言文との位置関係」を中心に、var / let / const について考えてきた。

こうした「宣言文との位置関係」の話は、ホイスティングとも呼ばれる。
ホスティングとは、宣言された変数・関数が、宣言文よりも前の位置で実行される挙動のこと。
宣言文よりも前に「巻き上げること(=hoisting)」が、ホイスティングという名前の由来。

つまりこれまでは、var / let / const を題材に、変数のホイスティングについて見てきたともいえる。
そこで最後に、関数オブジェクトのホイスティングについても触れてみる。

たとえば、次のようなシンプルな(シンプルすぎる)関数を考える。

JavaScript
function A(){
  const num=1;
  console.log(num);
}

A();

もちろん、コードの最後に書かれた「A();」の記述によって関数A が実行され、console には「1」と出力される。

では、このコードを次のように書き換えるとどうなるか。

JavaScript
A();

function A(){
  const num=1;
  console.log(num);
}

さきほどは最後に書かれていた「A();」が、今度は先頭に書かれている。
こうした記述でも、関数A は問題なく実行され、console には「1」と出力される。

このように関数オブジェクトは、関数宣言より前に実行文を置いても、なんら問題なく挙動する。
言い換えると、宣言文よりも前に巻き上げて(ホイスティングして)実行することができる。

ホイスティング可能な var が非推奨になった経緯を考えると、関数オブジェクトがホイスティング可能というのは、むしろ注意すべき点なのかもしれない。

今後も注意して、引き続き学習を進めていきたい。



・・・今回は、以上です。
最後までお読みくださり、ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む