unhurried

コンピュータ関連ネタがほとんど、ときどき趣味も…

Object Oriented Programming in JavaScript

Although I'm familiar with Object Oriented Programming (OOP) as I has been using Java for a long time, I've never tried to use it in JavaScript. When I researched OOP in JavaScript, I found it a little complicated because there exist a few ways to implemnt. In this article, I sorted out and simplified them.

  • Use function as class cannot be used in JavaScript before ES5.
var ES5Class = (function() {

    // constructor
    var ES5Class = function(member) {
        // Cope with the following usage which doesn't use new operator.
        // var instance = ES5Class();
        if(!(this instanceof ES5Class)) {
            return new ES5Class(member);
        }

        // members
        // Conventionally prefix an under score to the name of private variables.
        // (Although it can be accessed from outside the class.)
        this._member = member;
    };

    // Declare a method with prototype.
    // Methods can be declared in following way, but it's not efficient
    // because functions will be created every time an instance is generated.
    //   this.printMethod = function... 
    var p = ES5Class.prototype;
    
    // methods
    p.printMember = function () {
        console.log(this._member);
    }

    return ES5Class;
})();

module.exports = ES5Class;
  • With JavaScript later than ES6, class syntax can be used to declare classes.
class ES6Class {

    // constructor
    constructor(member) {
        // memners
        // Conventionally prefix an under score to the name of private variables.
        // (Although it can be accessed from outside the class.)
        this._member = member;
    }

    printMember() {
        console.log(this._member);
    }
}

module.exports = ES6Class
  • class syntax can also define static methods but cannot define static members.
// As there is no way to declare static members in class syntax,
// define global-scope variables instead.
var _member = 0;

class ES6StaticClass {

    // static methods
    static printMember() {
        _member++;
        console.log(_member);
    }
}

module.exports = ES6StaticClass

Error Handling in JavaScript

When we handle exceptions in Java, it is common to declare classes which extend Exception class and make conditional branches with instanceof operator. As far as I researched about how to do in JavaScript, there are mainly two ways.

  1. Extend Error object
    • Declare classes which extend Error object (For before ES5: prototype of Error object) and make conditional branches with instanceof operator.
    • Using instanceof operator makes the comparison reliable, but Error classes have to be imported in the file where conditional branches exist.
  2. Change the name property of Error object.
    • Create an Error object and change the name property, then make conditional branches by strings set as the name property.
    • This method is simple, but thereis a possibility that the names of the errors may be the same as the ones defined in the dependent modules.

Sample Code

error.js

var ERROR = {};

// As the original value of the name priperty of Error object is "Error",
// change it to a name of the custom Error class.
ERROR.Error1 = class extends Error {}
ERROR.Error1.prototype.name = "Error1";

// For before ES5, implement the inheritance using prototype.
ERROR.Error2 = function (message) {
    this.message = message;
}
ERROR.Error2.prototype = new Error;
ERROR.Error2.prototype.name = "Error2";

module.exports = ERROR;

module.js

const ERROR = require('./error.js');

class MyClass {
    static myMethod(arg) {
        if (arg === 1) {
            throw new ERROR.Error1("Error1 occured.")
        } else if (arg === 2) { 
            throw new ERROR.Error2("Error2 occured.")
        } else if (arg === 3) { 
            const e = new Error("Error3 occured.")
            e.name = "Error3";
            throw e;
        }
    }
}

module.exports = MyClass;

main.js

const MyClass = require('./module.js');
const ERROR = require('./error.js');

try {
    MyClass.myMethod(1);
} catch(e) {
    if (e instanceof ERROR.Error1) {
        console.log(e.name);
        console.log(e.message);
    }
}

try {
    MyClass.myMethod(2);
} catch(e) {
    if (e instanceof ERROR.Error2) {
        console.log(e.name);
        console.log(e.message);
    }
}

try {
    MyClass.myMethod(3);
} catch(e) {
    if (e.name === "Error3") {
        console.log(e.name);
        console.log(e.message);
    }
}

Result

$ node main.js
Error1
Error1 occured.
Error2
Error2 occured.
Error3
Error3 occured.

Reference

JavaScript エラー処理(独自エラー・条件分岐)

Javaで例外処理と言えば、Exceptionクラスを継承したクラスを定義して、instanceof演算子で条件分岐するのが定番です。JavaScriptの定番について調べてみたところ、大きくは下記の2パターンがあるようです。

  1. Errorオブジェクトを継承する

    • Errorオブジェクトを継承したクラス(ES5以前の場合はprototypeを継承したオブジェクト)を定義して、instanceof演算子で条件分岐します。
    • instanceof演算子で比較するため、文字列比較と異なり確実に比較ができますが、条件分岐をするコードでも独自Errorクラス(オブジェクト)をロードする必要があります。
  2. Errorオブジェクトのnameプロパティを変更する

    • Errorオブジェクトを生成して、nameプロパティを変更して、nameプロパティに設定した文字列一致で条件分岐します。
    • 手軽に記述できる一方、文字列一致のため予期せず依存するモジュールに定義されているエラーとnameプロパティが重なってしまう可能性があります。

サンプルコード

error.js

var ERROR = {};

// Errorを継承したままではnameプロパティは「Error」のままであるため、
// nameプロパティを独自のエラークラスの名称に変更する。
ERROR.Error1 = class extends Error {}
ERROR.Error1.prototype.name = "Error1";

// ES5以前の場合はprototypeを使って継承する。
ERROR.Error2 = function (message) {
    this.message = message;
}
ERROR.Error2.prototype = new Error;
ERROR.Error2.prototype.name = "Error2";

module.exports = ERROR;

module.js

const ERROR = require('./error.js');

class MyClass {
    static myMethod(arg) {
        if (arg === 1) {
            throw new ERROR.Error1("Error1 occured.")
        } else if (arg === 2) { 
            throw new ERROR.Error2("Error2 occured.")
        } else if (arg === 3) { 
            const e = new Error("Error3 occured.")
            e.name = "Error3";
            throw e;
        }
    }
}

module.exports = MyClass;

main.js

const MyClass = require('./module.js');
const ERROR = require('./error.js');

try {
    MyClass.myMethod(1);
} catch(e) {
    if (e instanceof ERROR.Error1) {
        console.log(e.name);
        console.log(e.message);
    }
}

try {
    MyClass.myMethod(2);
} catch(e) {
    if (e instanceof ERROR.Error2) {
        console.log(e.name);
        console.log(e.message);
    }
}

try {
    MyClass.myMethod(3);
} catch(e) {
    if (e.name === "Error3") {
        console.log(e.name);
        console.log(e.message);
    }
}

実行結果

$ node main.js
Error1
Error1 occured.
Error2
Error2 occured.
Error3
Error3 occured.

参考

JavaScript オブジェクト指向

私はJavaを長く使ってきたためオブジェクト指向の考え方は馴染み深いのですが、JavaScriptではを使うときにはあまり意識していませんでした。JavaScriptはクラスを定義する方法がいくつかあってややこしいので、簡単に整理してみました。

  • ES5以前ではclass構文が利用できないためfunctionを利用します。
var ES5Class = (function() {

    // コンストラクタ
    var ES5Class = function(member) {
        // new演算子を指定せずに下記のように呼び出されたときための対策
        // var instance = ES5Class();
        if(!(this instanceof ES5Class)) {
            return new ES5Class(member);
        }

        // メンバ変数
        // プライベートにしたい変数には慣習的にアンダースコアを付ける。
        // (実際には外部からのアクセスは可能であるが、変数の用途を明示するため。)
        this._member = member;
    };

    // プロトタイプを使ってメソッドを定義する。
    // this.printMethod = function... でも定義できるが、
    // インスタンス生成の度に関数が作成されるため効率が悪い。
    var p = ES5Class.prototype;
    
    // メソッド
    p.printMember = function () {
        console.log(this._member);
    }

    return ES5Class;
})();

module.exports = ES5Class;
  • ES6以降ではclass構文を使ってクラスの定義ができます。
class ES6Class {

    // コンストラクタ
    constructor(member) {
        // メンバ変数
        // プライベートにしたい変数には慣習的にアンダースコアを付ける。
        // (実際には外部からのアクセスは可能であるが、変数の用途を明示するため。)
        this._member = member;
    }

    printMember() {
        console.log(this._member);
    }
}

module.exports = ES6Class
  • class構文では静的メソッドも定義できますが、静的メンバは定義できません。
// class構文には静的メンバを定義する方法は用意されていないため、
// 代替手段として、グローバルスコープの変数を利用する。
var _member = 0;

class ES6StaticClass {

    // 静的メソッド
    static printMember() {
        _member++;
        console.log(_member);
    }
}

module.exports = ES6StaticClass

Node.js モジュール読み込み

Node.jsのモジュール読み込みではmodule.exports、exportsが利用できますが、違いがいまいち理解できていなかったので自分なりに整理してみました。
実装上の違いといった細かいことはリンク先にとても詳しく記載されていますので、今回は使い分け方に焦点を当てています。

module.exportsとexportsの使い分け

単一のクラスや関数、オブジェクトをエクスポートする場合はmodule.exportsを使います。

class MyClass {
    ...
}
module.exports = MyClass;

複数の関数やオブジェクトを持つオブジェクトをエクスポートする場合はexportsを使います。

function myFunc1 {
    ...
}
function myFunc2 {
    ...
}
exports.myFunc1 = myFunc1;
exports.myFunc2 = myFunc2;

※ module.exportsを利用しても記述できますが、exportsの方がシンプルに記述できます。

module.exports = {
    myFunc1: myFunc1,
    myFunc2: myFunc1
}

requireを複数回実行したときの挙動

初回ロード時のみ評価され2回目以降はキャッシュされたオブジェクトが返却されます。

  • module.js
var _var = Math.random();
function func() {
    console.log(`module.js - func : _var=${_var}`)
}
exports.func = func;
  • main.js
const module1 = require('./module.js')
const module2 = require('./module.js')
module1.func()
module2.func()
  • 実行結果
module.js - func : _var=0.7381836391077983
module.js - func : _var=0.7381836391077983

参考

vim-mode-plusのキーバインドを変更する

Atomエディタプラグインvim-mode-plusには通常のVimエディタにあるvimrcを読み込む機能がないため、Atomのキーマップ設定ファイルであるkeymap.csonに設定し、複数のコマンドを割り当てたい場合はAtomの設定ファイルであるinit.coffeeに複数コマンドを1つのコマンドとして登録する必要があります。

例として、以下のvimrcファイルをinit.coffeeとkeymap.csonに変換する例をご紹介します。

変換前

vimrc

noremap j gj
noremap k gk

noremap <S-j> 10j
noremap <S-k> 10k
noremap <S-h> 10h
noremap <S-l> 10l

変換後

init.coffee

atom.commands.add 'atom-text-editor.vim-mode-plus', 'custom:move-left-10', ->
  view = atom.views.getView atom.workspace.getActiveTextEditor()
  atom.commands.dispatch view, 'vim-mode-plus:set-count-1'
  atom.commands.dispatch view, 'vim-mode-plus:set-count-0'
  atom.commands.dispatch view, 'vim-mode-plus:move-left'

atom.commands.add 'atom-text-editor.vim-mode-plus', 'custom:move-down-screen-10', ->
  view = atom.views.getView atom.workspace.getActiveTextEditor()
  atom.commands.dispatch view, 'vim-mode-plus:set-count-1'
  atom.commands.dispatch view, 'vim-mode-plus:set-count-0'
  atom.commands.dispatch view, 'vim-mode-plus:move-down-screen'

atom.commands.add 'atom-text-editor.vim-mode-plus', 'custom:move-up-screen-10', ->
  view = atom.views.getView atom.workspace.getActiveTextEditor()
  atom.commands.dispatch view, 'vim-mode-plus:set-count-1'
  atom.commands.dispatch view, 'vim-mode-plus:set-count-0'
  atom.commands.dispatch view, 'vim-mode-plus:move-up-screen'

atom.commands.add 'atom-text-editor.vim-mode-plus', 'custom:move-right-10', ->
  view = atom.views.getView atom.workspace.getActiveTextEditor()
  atom.commands.dispatch view, 'vim-mode-plus:set-count-1'
  atom.commands.dispatch view, 'vim-mode-plus:set-count-0'
  atom.commands.dispatch view, 'vim-mode-plus:move-right'

keymap.cson

'atom-text-editor.vim-mode-plus:not(.insert-mode)':
  'shift-h': 'custom:move-left-10'
  'shift-j': 'custom:move-down-screen-10'
  'shift-k': 'custom:move-up-screen-10'
  'shift-l': 'custom:move-right-10'
  'h': 'vim-mode-plus:move-left'
  'j': 'vim-mode-plus:move-down-screen'
  'k': 'vim-mode-plus:move-up-screen'
  'l': 'vim-mode-plus:move-right'

AtomにVimキーバインドを設定する

学生の頃からVimを愛用して来ましたが、用途によってはプラグインが豊富なAtomの方が便利な場合があるので、AtomVimキーバインドを設定する方法を調べてみました。

プラグインのインストー

Escで挿入モードを抜けたときにIMEをオフにする

キーマップを設定する