アロー関数とthisの振る舞い – callback関数を書き直す

「thisの振る舞いがいまいち分からない…」

「アロー関数は使ってるけど、従来の関数と何が違うんだろう?」

「bindって結局いつ使えばいいの?」

前回はモジュールシステムについて学びました。

今回は、コールバック関数でよく使用するアロー関数と、混乱しがちなthisの振る舞いについて、実践的に解説していきます。

従来の関数定義とthisの問題

まず、従来の関数定義での問題を見てみましょう:

JS
// jQuery時代によくあるコード
var Counter = {
    count: 0,
    button: $('#button'),
    
    setup: function() {
        // this を変数に保存
        var self = this;
        
        this.button.click(function() {
            self.count++;  // 直接thisは使えない
            console.log(self.count);
        });
    }
};

この書き方の問題点:

  • selfthat などの変数が必要
  • thisの参照が分かりにくい
  • コードが冗長になる

アロー関数による解決

JS
// モダンな書き方
const Counter = {
    count: 0,
    button: document.querySelector('#button'),
    
    setup() {
        this.button.addEventListener('click', () => {
            this.count++;  // thisが自動的に保持される
            console.log(this.count);
        });
    }
};

アロー関数の特徴:

  • レキシカルスコープでthisを継承
  • 自身のthisを持たない
  • より簡潔な構文

従来の関数とアロー関数の違い

1. thisの束縛

JS
// 従来の関数
function Traditional() {
    this.value = 42;
    
    this.getValue = function() {
        return this.value;
    };
    
    this.delayed = function() {
        setTimeout(function() {
            console.log(this.value); // undefined
        }, 1000);
    };
}

// アロー関数
function Modern() {
    this.value = 42;
    
    this.getValue = () => {
        return this.value;  // 42
    };
    
    this.delayed = function() {
        setTimeout(() => {
            console.log(this.value);  // 42
        }, 1000);
    };
}

ポイント:

  • 従来の関数は呼び出し方によってthisが変わる
  • アロー関数は定義時のスコープのthisを保持

2. メソッド定義での注意点

JS
const obj = {
    name: 'Example',
    // 従来の方法(推奨)
    normalMethod() {
        return this.name;
    },
    // アロー関数(非推奨)
    arrowMethod: () => {
        return this.name;  // undefined
    }
};

使い分けのポイント:

  • オブジェクトのメソッドは通常の関数で定義
  • コールバックはアロー関数を使用

実践的なリファクタリング例

イベントハンドラ

JS
// Before: jQuery
class UserList {
    constructor() {
        this.users = [];
        var self = this;
        
        $('#addUser').click(function() {
            var name = $('#name').val();
            self.addUser(name);
        });
    }
    
    addUser(name) {
        this.users.push(name);
        this.render();
    }
}

// After: Modern JavaScript
class UserList {
    constructor() {
        this.users = [];
        
        document.querySelector('#addUser')
            .addEventListener('click', () => {
                const name = document.querySelector('#name').value;
                this.addUser(name);
            });
    }
    
    addUser(name) {
        this.users.push(name);
        this.render();
    }
}

配列操作のコールバック

JS
// Before
const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(function(num) {
    return num * 2;
});

const filtered = numbers.filter(function(num) {
    return num > 2;
});

// After
const doubled = numbers.map(num => num * 2);
const filtered = numbers.filter(num => num > 2);

非同期処理

JS
// Before
function loadData() {
    var self = this;
    fetch('/api/data')
        .then(function(response) {
            return response.json();
        })
        .then(function(data) {
            self.processData(data);
        })
        .catch(function(error) {
            self.handleError(error);
        });
}

// After
function loadData() {
    fetch('/api/data')
        .then(response => response.json())
        .then(data => this.processData(data))
        .catch(error => this.handleError(error));
}

高度な使い分け

クラスメソッドでの利用

JS
class DataHandler {
    constructor(data) {
        this.data = data;
    }
    
    // インスタンスメソッド: 通常の関数
    process() {
        return this.data;
    }
    
    // イベントハンドラ: アロー関数
    handleClick = () => {
        this.process();
    }
    
    // 通常のメソッド + bind
    handleSubmit() {
        this.process();
    }
}

const handler = new DataHandler([1, 2, 3]);
button.addEventListener('click', handler.handleClick);  // OK
form.addEventListener('submit', handler.handleSubmit.bind(handler));  // OK

即時実行関数での使用

JS
// Before: IIFE
(function() {
    var privateVar = 'secret';
    // ...
})();

// After: アロー関数
(() => {
    const privateVar = 'secret';
    // ...
})();

まとめ

アロー関数を適切に使用することで:

  • thisの管理が簡単に
  • コードがより簡潔に
  • コールバックの意図が明確に
  • メンテナンス性が向上

次回は「テンプレートリテラルでHTML生成を最適化」について解説し、より洗練されたテンプレート処理を学んでいきましょう。