WebSurfer's Home

Filter by APML

JavaScript の console.log()

by WebSurfer 31. January 2024 20:15

JavaScript の console.log() で開発者ツールの Console に JavaScript オブジェクトや html 要素 (DOM) を出力すると、Console を開いた時点での内容 (下の (1) の時点での内容ではなくて) が表示されるという話を書きます。

開発者ツールの Console 出力

Qiita の質問「javascriptとブラウザのコンソールについて」で調べたことですが、忘れそうなので自分のブログに備忘録として残しておこうと思った次第です。

上の画像の JavaScript のコードの (1) を見てください。obj = { prop: 123 } という JavaScript オブジェクトを作成してから即 console.log(obj) とし、その後で prop の値を 456 に書き換えています。しかしながら、コンソールを開いて見ると prop が書き換えられた後の 456 という結果が表示されています。

これは、MDN のドキュメント console.log() に書いてあるように、"Chrome や Firefox の比較的新しいバージョンを使っているなら注意が必要です。これらのブラウザーで記録されるのはオブジェクトへの参照です。そのため、 console.log() を呼び出した時点でのオブジェクトの「値」が表示されるのではなく、内容を見るために開いた時点での値が表示されます" ということによります。

コード上で console.log() とした時点での JavaScript オブジェクトのログを取りたい場合は、MDN のドキュメントに書いてあるように "console.log(obj) を使わず、 console.log(JSON.parse(JSON.stringify(obj))) を使用してください" とするのが良さそうです。それが上の画像の JavaScript のコード (2) です。

Console を開いた時点での内容が表示されるというのは、JavaScript オブジェクトだけでなく、 html 要素 (DOM) を JavaScript で書き換えても同じことが起こります。

上の画像の (4) を見てください。JavaScript で html の p 要素を生成して id と textContent を設定してから即 console.log(p) し、その後で id と textContent を書き換えています。しかし、Console には書き換えた後の結果が表示されています。

コード上で console.log() とした時点での p 要素のログを取りたい場合は、p.outerHTML を出力するのが良さそうです。それが上の画像の (5) です。

JavaScript はブラウザ依存ですが、Windows 10 PC の Edge 121.0.2277.83, Chrome 121.0.6167.140, Firefox 122.0, Opera 106.0.4998.66 で試して同じ結果となることは確認しました。

Tags: , ,

JavaScript

fetch API の mode:"no-cors"

by WebSurfer 28. November 2023 17:14

JavaScript の fetch() グローバル関数 のリクエストオプションに mode:"no-cors" を設定するとどうなるかという話を書きます。

mode:'no-cors' に設定した結果

上の画像がその結果です。先の記事「Web API に CORS 実装 (CORE)」で紹介した CORS を実装した Web API に対し、リクエストオプションを mode:"no-cors" に設定した fetch 関数を使って GET 要求を出し、fetch 関数の戻り値の Response オブジェクトを Chrome 119.0.6045.160 のディベロッパーツールで開いて表示したものです。

Fetch Living Standard というドキュメントが fetch の仕様書ですが、このドキュメントの mode の説明に mode:"no-cors" に設定した場合どういう結果になるかが書いてあります。以下の通りです。

"Restricts requests to using CORS-safelisted methods and CORS-safelisted request-headers. Upon success, fetch will return an opaque filtered response."

上の前半は、HTTP 要求メソッドが GET, POST, HEAD に、要求ヘッダが CORS セーフリストリクエストヘッダーに制限されると言っています (古い CORS 仕様書で言うシンプルなリクエストの条件を満たすものと同じ)。後半は、成功すると opaque filtered response を返すと言っています。

opaque filtered response とは何かというと、上に紹介した fetch の仕様書に以下のように書いてあります。

"An opaque filtered response is a filtered response whose type is "opaque", URL list is « », status is 0, status message is the empty byte sequence, header list is « », body is null, and body info is a new response body info."

この記事の一番上の画像に表示した response の中身を見てください。上の説明通り、type:"opaque", url:"", status:0 となっているのが分かるでしょうか。

Fiddler を使って要求・応答をキャプチャすると以下のようになっています。要求は出ており、応答に含まれる JSON 文字列(画像の赤枠部分)は期待通りです。

Fiddler で要求・応答をキャプチャ

しかしながら、response は opaque filtered されているので、response.json() で Uncaught (in promise) SyntaxEffor: Unexpected end of input というエラーになります。下の画像を見てください。

SyntaxEffor: Unexpected end of input

要するに、fetch のリクエストオプションを mode:"no-cors" とした場合、たとえサーバー側で CORS 対応がしてあったとしても、ブラウザ側では CORS 対応がされず、Web API からの情報(上の例では Web API から返された JSON 文字列)は取得できないという結果になりました。

というわけで、mode:"no-cors" とする理由は、自分が考え付くケースに限っての話ですが、どう考えてもなさそうです。fetch の仕様書にはデフォルトは mode:"no-cors" と書いてありますが、それは "highly discouraged from using it for new features. It is rather unsafe" だそうです。実際、Chrome, Edge, Firefox など主要なブラウザの実装では mode:"cors" がデフォルトになっていますし。

(MDN のドキュメント Request.mode の no-cors の説明に "ドメイン間でデータが漏れることによって生じるセキュリティとプライバシーの問題を防ぐ" と書いてあります。セキュリティとプライバシーの問題を防いだ結果、必要な情報も取れなくなるのでは意味がないと思うのですが・・・)


以上の例はシンプルなリクエストになる場合ですが、プリフライトリクエストが行われる場合はどうなるかも書いておきます。下の画像を見てください。fetch が返すのはやはり opaque filtered response で、前のケースと同じ結果になります。

プリフライトリクエストが行われる場合

ただし、上に述べたように、mode:"no-cors" に設定した場合、HTTP 要求メソッドとヘッダが CORS 仕様書で言うシンプルなリクエストの条件に制限される点が前のケースとは異なります。

下の Fiddler で要求・応答をキャプチャした画像の青枠部分を見てください。プリフライトリクエストで使われる OPTIONS 要求ではなくて、いきなり POST 要求が出ています。また、fetch のパラメータで Content-Type を application/json と指定したにもかかわらず text/plain となっています。

Fiddler で要求・応答をキャプチャ

サーバーからの応答が 415 Unsupported Media Type になっていますが、これは要求ヘッダの Content-Type が text/plain に書き換えられてしまったからです (ASP.NET Core Web API では Content-Type:text/plain はサポートされません)。結果、415 エラーとなってエラーメッセージ(赤枠部分)が返されています。

fetch が返すのは opaque filtered response なので response.json() の結果は、上のケースと同様に、Uncaught (in promise) SyntaxEffor: Unexpected end of input というエラーになります。下の画像を見てください。

SyntaxEffor: Unexpected end of input


ちなみに、mode:"no-cors" を削除(デフォルトの mode:"cors" となる)した場合はどうなるかも書いておきます。下の画像を見てください。CORS の操作には成功し、API からのデータも期待通り取得できています。

mode:'no-cors' を削除

fetch のリクエストオプションから mode を削除しても(ブラウザの実装ではデフォルトが mode:"cors" になるはず)期待通りに動くことは、自分の環境の Windows 10 の Chrome 119.0.6045.160, Edge 119.0.2151.93, Firefox 120.0, Opera 105.0.4970.21 で確認しました。


最後に、fetch の要求先が同一ドメインで mode:"no-cors" に設定するとどうなるかも書いておきます。 Chrome 119.0.6045.160 で試した結果ですが、fetch が返すのはクロスドメインの時のような opaque filtered response ではなくて、以下のようになりました。

同一ドメインの場合

type:"basic" というのは同一ドメインのレスポンスを意味します (opaque filtered response になる場合は type:"opaque" となります)。どうも、mode:"no-cors" は無視されて、CORS に関係ない通常のやり取りがされるように見えます。

Tags: , , ,

JavaScript

JavaScript の関数内での this

by WebSurfer 6. November 2023 17:41

JavaScript の関数内での this の使い方を備忘録として残しておきます。

MDN ドキュメント「this」に JavaScript の this についていろいろ書いてあります。関数内での this については「関数コンテキスト」のセクションに書いてあるように "関数の呼び出し方によって異なります" ということです。ただし、アロー関数の場合はそれは当てはまらないようです。

というわけで、関数内で this を使う場合は、常に「その this は何なのか?」ということを考えてコードを書く必要がありそうです。

どういうことか具体例を以下に書きます。

下の画像は、Visual Studio 2022 のテンプレートを使って作成した React + Web API のプロジェクトを実行し、React 側から fetch API を使って Web API から天気予報データを取得してブラウザに表示したものです。

天気予報データを取得しブラウザに表示

Visual Studio が自動生成した React 側の JavaScript のコードは以下の通りです。React の componentDidMount のタイミングでコードの下の方にある populateWeatherData メソッドが呼び出されると、fetch API を使って Web API から天気予報データを取得し、取得したデータを this.setState メソッドを使って state に設定しています。

import React, { Component } from 'react';

export class FetchData extends Component {
    static displayName = FetchData.name;

    constructor(props) {
        super(props);
        this.state = { forecasts: [], loading: true };
    }

    componentDidMount() {
        this.populateWeatherData();
    }

    static renderForecastsTable(forecasts) {
        return (
            <table className="table table-striped" aria-labelledby="tableLabel">
                <thead>
                    <tr>
                        <th>Date</th>
                        <th>Temp. (C)</th>
                        <th>Temp. (F)</th>
                        <th>Summary</th>
                    </tr>
                </thead>
                <tbody>
                    {forecasts.map(forecast =>
                        <tr key={forecast.date}>
                            <td>{forecast.date}</td>
                            <td>{forecast.temperatureC}</td>
                            <td>{forecast.temperatureF}</td>
                            <td>{forecast.summary}</td>
                        </tr>
                    )}
                </tbody>
            </table>
        );
    }

    render() {
        let contents = this.state.loading
            ? <p><em>Loading...</em></p>
            : FetchData.renderForecastsTable(this.state.forecasts);

        return (
            <div>
                <h1 id="tableLabel">Weather forecast</h1>
                <p>This component demonstrates fetching data from the server.</p>
                {contents}
            </div>
        );
    }

    async populateWeatherData() {
        const response = await fetch('weatherforecast');
        const data = await response.json();
        this.setState({ forecasts: data, loading: false });
    }
}

上のコードの一番最後の行の this.setState メソッドの this が、関数の書き方によってどう変わってくるかを以下に書きます。

上のコード例で populateWeatherData メソッドは FetchData クラスのメソッドなので、MDN のドキュメント this の Class context のセクションに "the this value is the object that the method was accessed on." と書いてあるように、this には FetchData オブジェクトが設定されます。

this は FetchData オブジェクト

結果、Web API から取得したデータは this.setSate メソッドによって FetchData オブジェクトの state に設定され、この記事の一番上の画像のように天気予報データがブラウザに表示されます。

では、以下のように async/await は使わないで then() の中でコールバックを呼ぶように変更し、コールバックを function () { ... } という形にして、その function の中で this を使うとどうなるでしょう?

this は undefined

上の画像の通り this は undefined となります。結果、Web API から取得したデータの state への設定は失敗しますので、初期画面で Loading... と表示された状態で止まってしまいます。

MDN のドキュメント「関数コンテキスト」に書いてありますが、(1) strict モードでは、実行コンテキストに入るときに this 値が設定されていないと、undefined のままになり、(2) strict モードでない場合は、this は既定でグローバルオブジェクトすなわち window となるそうです。then() の中で非同期に実行される場合は「実行コンテキストに入るときに this 値が設定されていない」ということになるようで、上の画像の例では (1) という結果になっています。

次に、上の画像の例のコールバック function () { ... } を以下のようにアロー関数に代えて、その中で this を使うとどうなるでしょうか?

this は FetchData オブジェクト

上の画像の通り this は FetchData オブジェクトとなります。結果、Web API から取得したデータは FetchData オブジェクトの state に設定され、期待通り天気予報データがブラウザに表示されます。

MDN のドキュメント「アロー関数」によると "アロー関数では、this はそれを囲む構文上のコンテキストの this の値が設定されます" ということで、this には FetchData オブジェクトが設定されたということのようです。

アロー関数というのは従来の function() { ... } の簡潔な代替構文に過ぎないと思っていたのですが、MDN のドキュメント「アロー関数式」に書いてあるように意図的な使用上の制限があるそうです。その中の一つが this で、アロー関数自身では this の結び付けを行わず、包含する構文上のコンテキストの this の値を保持するのだそうです。

Tags: , , ,

JavaScript

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。ブログ2はそれ以外の日々の出来事などのトピックスになっています。

Calendar

<<  December 2025  >>
MoTuWeThFrSaSu
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar