[JavaScript]thisのスコープ理解していない人がいるわけない。（私は知らなかった）

# はじめに

画像アップロード機能を実装していたときにスコープに苦しめられた話です。

FileReaderやImageオブジェクトを使って苦手な非同期処理をしていたので、ハマっている原因が整理できずに泥沼化してました。。。

# できごと

## やりたいこと

- `<input type=’file’>`で画像選択時にリアルタイムで選択した画像が表示される
- 最終的に選択した画像のbase64の値をサーバーに送信したいので、ファイルが選択されたらcanvas→blob→base64の流れでデータを変換する

## 参考にした記事

['input type=file'から'canvas'への転写]([https://qiita.com/_shimizu/items/7df9add540984eba8786](https://qiita.com/_shimizu/items/7df9add540984eba8786))

ほぼこちらのソースコードをコピーで実装

## ソースコード

upload-component.vue

```vue
<template>
    <div>
        <div>
            <input
                type="file"
                @change="uploadFile"
            >
            <!-- サーバーにリクエストする値 -->
            <input
                type="hidden"
                name="icon_base64"
                :value="iconBase64"
            >
            <canvas
                id="canvas"
            />
        </div>
    </div>
</template>

<script>
export default {
    data () {
        return {
            iconBase64: '',
        }
    },
    methods: {
        uploadFile(event) {
            const file = event.currentTarget.files[0];

            const readerForFile = new FileReader();
            readerForFile.onload = function() {
                // ファイルが読み込まれたあとの処理
                const image = new Image();
                image.onload = async function() {
                    // imageを転写するcanvasの取得、加工
                    const canvas = document.getElementById('canvas');
                    canvas.width = image.width;
                    canvas.height = image.height;
                    const context = canvas.getContext("2d");
                    // canvas(CanvasRenderingContext2D)に画像を転写
                    context.drawImage(image, 0, 0);

                    // canvas -> blobの変換
                    const blob = await this.canvasToBlob(canvas);
                    // blob -> base64の変換してプロパティに代入
                    this.iconBase64 = await this.readAsDataURL(blob);
                }
                // 画像がimageタグに読み込み->image.onloadイベント発火
                image.src = readerForFile.result;
            }
            // Fileオブジェクトを読み込む->readerForFile.onloadイベント発火
            readerForFile.readAsDataURL(file);
        },
        canvasToBlob(canvas) {
            return new Promise((resolve) => {
                canvas.toBlob(resolve, 'image/png');
            });
        },
        readAsDataURL(blob) {
            return new Promise((resolve) => {
                const reader = new FileReader();
                reader.onloadend = () => {
                    resolve(reader.result);
                };
                reader.readAsDataURL(blob);
            });
        },
    },
}
</script>
```

## 結果

以下エラーが発生

```php
TypeError: canvasToBlob is not a function...
```

エラー箇所はココ。

```php
// canvas -> blobの変換
const blob = await this.canvasToBlob(canvas);
```

でも、canvasToBlobは定義しているよ？ということでもう一度コードを読み返す。

## 結論

はい、そうです。thisのスコープでした。。。

今回以下のようにFileReaderやImageのonloadで非同期処理をしていて

```php
readerForFile.onload = function() {
  ...
  image.onload = async function() {
    ...
```

その中で

```php
// canvas -> blobの変換
const blob = await this.canvasToBlob(canvas);
```

これをやっていると。

実際にcanvasToBlobを定義しているのはVueComponent内（※）で定義しているので、そりゃ`this.canvasToBlob`でアクセスできないよねって話です。

※イメージ湧かない場合はvueのexport default内でconsole.log(this)やるとわかります。

## 対策

onload内でthis(=VueComponent)にアクセスしたい。でも、`this.canvasToBlob`でアクセスできない。

ということでthisを変数化してしまえばＯＫ

### 参考記事

[Vue.jsのdataオブジェクトを参照する際に Uncaught (in promise) TypeError: Cannot set property 'foo' of undefined]([https://qiita.com/shanonim/items/7718556c0fab54a517c2](https://qiita.com/shanonim/items/7718556c0fab54a517c2))

## ソースコード（修正後）

```php
    methods: {
        uploadFile(event) {
            const file = event.currentTarget.files[0];

			const vm = this;
            const readerForFile = new FileReader();
            readerForFile.onload = function() {
                // ファイルが読み込まれたあとの処理
                const image = new Image();
                image.onload = async function() {
										...
										...
                    // canvas -> blobの変換
                    const blob = await vm.canvasToBlob(canvas);
                    // blob -> base64の変換してプロパティに代入
                    this.iconBase64 = await vm.readAsDataURL(blob);
                }
                // 画像がimageタグに読み込み->image.onloadイベント発火
                image.src = readerForFile.result;
            }
            // Fileオブジェクトを読み込む->readerForFile.onloadイベント発火
            readerForFile.readAsDataURL(file);
        },
        canvasToBlob(canvas) {
						...
        },
				...
    },
```


`const vm = this;`でVueComponentを示すthisを変数に格納してonload内でも`canvasToBlob`が呼べるようになりました！

# おわりに

「こんだけ？」といわれるかもですが、すいません、すごくハマったのは事実なんです。。。

改めてJavaScriptのスコープの概念の理解不足が露呈した瞬間でした。。。

久しぶりにJavaScriptを触って、改めてJavaScriptに対してのアレルギーを感じることを実感したので、コツコツ言語仕様を理解しようかと思います。

最後までお読みいただきありがとうございます。