MochiuWiki : SUSE, EC, PCB
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
JavaScriptの基礎 - thisキーワードのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
JavaScriptの基礎 - thisキーワード
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == JavaScriptにおける <code>this</code> キーワードは、関数が「どのように呼び出されたか」によって参照先が動的に変化する特殊なキーワードである。<br> 他の多くのオブジェクト指向言語とは異なり、<code>this</code> は「定義時」ではなく「呼び出し時」に決定される。(アロー関数を除く)<br> <br> <code>this</code> は、関数に暗黙的に渡される引数と考えることができる。<br> グローバルコンテキストではウィンドウオブジェクトやグローバルオブジェクトを参照し、メソッド内ではそのメソッドが属するオブジェクトを参照し、コンストラクタ内では生成されたインスタンスを参照する。<br> <br> <code>this</code> の参照先は、<code>call</code>、<code>apply</code>、<code>bind</code> の3つのメソッドを使用して明示的に指定することが可能である。<br> これらのメソッドを活用することで、関数の再利用性を高めたり、コールバック関数内での <code>this</code> の消失問題を解決することができる。<br> <br> ES2015で導入されたアロー関数は自身の <code>this</code> を持たず、定義されたスコープの外側の <code>this</code> を参照するレキシカルthisの性質を持つ。<br> この性質により、コールバック内での <code>this</code> の消失問題を簡潔に解決できる。<br> <br> 現代のReact開発では、関数コンポーネントとHooksの普及により <code>this</code> を意識する場面は大幅に減少しているが、<br> レガシーコードの理解やクラスベースの設計を扱う時には <code>this</code> の動作を正しく理解しておくことが不可欠である。<br> <br><br> == thisの基本的な挙動 == <code>this</code> の参照先は、関数が呼び出されるコンテキストによって決まる。<br> 以下に、主要な呼び出しコンテキストと対応する <code>this</code> の参照先を示す。<br> <br> ==== グローバルコンテキスト ==== スクリプトの最上位レベル (いずれの関数にも属さない場所) で <code>this</code> を参照すると、グローバルオブジェクトを指す。<br> <br> * Webブラウザ環境 *: <code>this</code> は、<code>window</code> オブジェクトを参照する。 * Node.js環境 *: <code>this</code> は、<code>globalThis</code> オブジェクトを参照する。 <br> <syntaxhighlight lang="javascript"> // Webブラウザ環境 console.log(this === window); // true // Node.js環境 console.log(this === globalThis); // true </syntaxhighlight> <br> ==== 関数内のthis ==== 通常の関数を呼び出した場合、<code>this</code> の参照先は strictモードの有無によって異なる。<br> <br> * Non-strictモード (デフォルト) *: <code>this</code> は、グローバルオブジェクトを参照する。 * Strictモード (<code>"use strict"</code> または ESモジュール) *: <code>this</code> は、<code>undefined</code> になる。 <br> <syntaxhighlight lang="javascript"> // Non-strictモード function showThis() { console.log(this); // window (ブラウザ) または globalThis (Node.js) } showThis(); // Strictモード "use strict"; function showThisStrict() { console.log(this); // undefined } showThisStrict(); </syntaxhighlight> <br> ==== メソッド内のthis ==== オブジェクトのメソッドとして呼び出された場合、<code>this</code> はそのメソッドが属するオブジェクトを参照する。<br> <br> <syntaxhighlight lang="javascript"> const person = { name: "太郎", greet: function() { console.log("こんにちは、" + this.name); } }; person.greet(); // "こんにちは、太郎" (thisはpersonを参照する) </syntaxhighlight> <br> メソッドを変数に代入して呼び出すと、<code>this</code> が消失する点に注意が必要である。<br> これは、メソッドを変数に代入した時点でオブジェクトとの関連が切れ、通常の関数呼び出しとして扱われるためである。<br> <br> <syntaxhighlight lang="javascript"> const person = { name: "太郎", greet: function() { console.log("こんにちは、" + this.name); } }; person.greet(); // "こんにちは、太郎" const greetFn = person.greet; greetFn(); // "こんにちは、undefined" (thisが消失する) </syntaxhighlight> <br> ==== コンストラクタ内のthis ==== <code>new</code> 演算子を使用して関数を呼び出すと、<code>this</code> は新しく生成されたインスタンスを参照する。<br> <br> <syntaxhighlight lang="javascript"> function Person(name, age) { this.name = name; // 生成されるインスタンスのnameプロパティに代入される this.age = age; this.greet = function() { console.log("こんにちは、" + this.name + "です"); }; } const taro = new Person("太郎", 25); const hanako = new Person("花子", 30); taro.greet(); // "こんにちは、太郎です" hanako.greet(); // "こんにちは、花子です" </syntaxhighlight> <br><br> == bind / call / apply == <code>call</code>、<code>apply</code>、<code>bind</code> は、関数の <code>this</code> を明示的に指定するためのメソッドである。<br> これらを活用することで、<code>this</code> の消失問題を解決したり、異なるオブジェクトに対して同じ関数を再利用したりすることができる。<br> <br> ==== call ==== <code>call</code> メソッドは、<code>this</code> を束縛して関数を即座に実行する。<br> 引数は個別に指定する。<br> <br> 基本構文を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> 関数名.call(thisArg, arg1, arg2, ...); </syntaxhighlight> <br> 使用例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> function greet(greeting, punctuation) { return greeting + "、" + this.name + punctuation; } const person = { name: "太郎" }; const result = greet.call(person, "こんにちは", "!"); console.log(result); // "こんにちは、太郎!" </syntaxhighlight> <br> ==== apply ==== <code>apply</code> メソッドは、<code>this</code> を束縛して関数を即座に実行する。<br> <code>call</code> との違いは、引数を配列として指定する点である。<br> <br> 基本構文を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> 関数名.apply(thisArg, [argsArray]); </syntaxhighlight> <br> 使用例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> function greet(greeting, punctuation) { return greeting + "、" + this.name + punctuation; } const person = { name: "太郎" }; const args = ["こんにちは", "!"]; const result = greet.apply(person, args); console.log(result); // "こんにちは、太郎!" // 配列の要素を個別の引数として渡す用途にも使用される const numbers = [3, 1, 4, 1, 5, 9, 2, 6]; const max = Math.max.apply(null, numbers); console.log(max); // 9 </syntaxhighlight> <br> ==== bind ==== <code>bind</code> メソッドは、<code>this</code> を束縛した新しい関数を返す。<br> <code>call</code> や <code>apply</code> と異なり、即座に関数を実行しない。<br> <br> 基本構文を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> const boundFn = 関数名.bind(thisArg, arg1, ...); </syntaxhighlight> <br> 使用例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> function greet(greeting, punctuation) { return greeting + "、" + this.name + punctuation; } const person = { name: "太郎" }; // thisを束縛した新しい関数を生成する const boundGreet = greet.bind(person); // 後から呼び出す console.log(boundGreet("おはよう", "。")); // "おはよう、太郎。" console.log(boundGreet("こんばんは", "!")); // "こんばんは、太郎!" // 引数も部分適用できる (カリー化) const morningGreet = greet.bind(person, "おはよう"); console.log(morningGreet("。")); // "おはよう、太郎。" </syntaxhighlight> <br> <u>bindされた関数に対して再度 <code>call</code> や <code>apply</code> で <code>this</code> を変更しようとしても、bindが優先されるため変更できない。</u><br> <br> ==== call / apply / bindの比較 ==== 下表に、3つのメソッドの違いを示す。<br> <br> <center> {| class="wikitable" |+ call / apply / bind の比較 ! メソッド !! 実行時期 !! 引数の形式 !! 主な用途 |- | <code>call</code> || 即座に実行 || 個別に指定 || 単発の関数呼び出し |- | <code>apply</code> || 即座に実行 || 配列として指定 || 配列を引数として展開 |- | <code>bind</code> || 新しい関数を返す || 個別に指定 || イベントハンドラ、コールバック |} </center> <br> 3つのメソッドを使用した同一処理の比較を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> function introduce(job, city) { return this.name + "は" + job + "で、" + city + "に住んでいます"; } const person = { name: "太郎" }; // call: 即座に実行、引数を個別に渡す introduce.call(person, "エンジニア", "東京"); // apply: 即座に実行、引数を配列で渡す introduce.apply(person, ["エンジニア", "東京"]); // bind: 新しい関数を返す、後から呼び出す const boundIntroduce = introduce.bind(person); boundIntroduce("エンジニア", "東京"); </syntaxhighlight> <br><br> == アロー関数のthis == アロー関数は、通常の関数とは異なる <code>this</code> の動作を持つ。<br> アロー関数は自身の <code>this</code> バインディングを持たず、定義された場所の外側のスコープの <code>this</code> を参照する。<br> <br> ==== レキシカルthis ==== アロー関数の <code>this</code> は、定義時のスコープ (レキシカルスコープ) に基づいて決定される。<br> この性質を <u>レキシカルthis</u> と呼ぶ。<br> <br> 通常の関数とアロー関数の <code>this</code> の違いを以下に示す。<br> <br> <syntaxhighlight lang="javascript"> const person = { name: "太郎", hobbies: ["読書", "映画", "料理"], // 通常の関数: thisはコールバック内で消失する listHobbiesWithFunction: function() { this.hobbies.forEach(function(hobby) { // thisはwindow (またはundefined) を参照してしまう console.log(this.name + "は" + hobby + "が好き"); // undefinedは映画が好き }); }, // アロー関数: 外側のthis (= person) を参照する listHobbiesWithArrow: function() { this.hobbies.forEach(hobby => { // thisは外側のメソッドのthis (= person) を引き継ぐ console.log(this.name + "は" + hobby + "が好き"); // 太郎は映画が好き }); } }; person.listHobbiesWithArrow(); // "太郎は読書が好き" // "太郎は映画が好き" // "太郎は料理が好き" </syntaxhighlight> <br> アロー関数には <code>call</code>、<code>apply</code>、<code>bind</code> で <code>this</code> を変更することはできない。<br> 常に定義時のレキシカルスコープの <code>this</code> を使用する。<br> <br> <syntaxhighlight lang="javascript"> const arrowFn = () => { console.log(this); }; const obj = { name: "太郎" }; // call / apply / bind を使用しても thisは変わらない arrowFn.call(obj); // グローバルオブジェクト (thisは変わらない) arrowFn.apply(obj); // グローバルオブジェクト (thisは変わらない) arrowFn.bind(obj)(); // グローバルオブジェクト (thisは変わらない) </syntaxhighlight> <br> ==== メソッドでのアロー関数の注意点 ==== オブジェクトのメソッドとしてアロー関数を使用すると、<code>this</code> がそのオブジェクトを参照しない問題が発生する。<br> アロー関数が定義された時点の外側のスコープ (多くの場合はグローバルスコープ) の <code>this</code> を引き継ぐためである。<br> <br> <syntaxhighlight lang="javascript"> const obj = { name: "太郎", // アロー関数をメソッドとして定義した場合 (非推奨) greetArrow: () => { // thisはグローバルオブジェクトを参照する (objではない) console.log(this.name); // undefined }, // 通常の関数をメソッドとして定義した場合 (推奨) greetFunction: function() { // thisはobjを参照する console.log(this.name); // "太郎" } }; obj.greetArrow(); // undefined obj.greetFunction(); // "太郎" </syntaxhighlight> <br> <u>オブジェクトのメソッドを定義する場合は、通常の関数 (または ES2015のメソッド短縮記法) を使用することを推奨する。</u><br> アロー関数は、メソッド内のコールバックとして使用する場面に適している。<br> <br><br> == クラスにおけるthis == ES2015で導入されたクラス構文においても、<code>this</code> の挙動は通常の関数と同様である。<br> クラスメソッド内の <code>this</code> はインスタンスを参照するが、メソッドをコールバックとして渡した場合は <code>this</code> が消失する問題が発生する。<br> <br> <syntaxhighlight lang="javascript"> class Counter { constructor() { this.count = 0; } increment() { this.count++; console.log(this.count); } } const counter = new Counter(); counter.increment(); // 1 (thisはcounterを参照する) // メソッドをコールバックとして渡すとthisが消失する const fn = counter.increment; fn(); // エラー: Cannot read properties of undefined (reading 'count') </syntaxhighlight> <br> この問題を解決する方法は以下の3つがある。<br> <br> * 方法1 : コンストラクタでbindする *: コンストラクタ内で <code>this</code> を束縛した新しい関数を生成して、同名のプロパティに代入する。 *: <syntaxhighlight lang="javascript"> class Counter { constructor() { this.count = 0; // コンストラクタ内でbindする this.increment = this.increment.bind(this); } increment() { this.count++; console.log(this.count); } } const counter = new Counter(); const fn = counter.increment; fn(); // 1 (thisはcounterを参照する) </syntaxhighlight> *: <br> * 方法2 : クラスフィールドでアロー関数を使用する (推奨) *: クラスフィールドとアロー関数を組み合わせると、インスタンスごとに <code>this</code> が固定されたメソッドを定義できる。 *: <syntaxhighlight lang="javascript"> class Counter { count = 0; // クラスフィールド // アロー関数でメソッドを定義するとthisが固定される increment = () => { this.count++; console.log(this.count); }; } const counter = new Counter(); const fn = counter.increment; fn(); // 1 (thisは常にcounterを参照する) </syntaxhighlight> *: <br> * 方法3 : インラインアロー関数でラップする *: コールバックを渡す箇所でアロー関数でラップする。 *: <syntaxhighlight lang="javascript"> class Counter { count = 0; increment() { this.count++; console.log(this.count); } } const counter = new Counter(); // アロー関数でラップすることでthisを維持する document.addEventListener("click", () => counter.increment()); </syntaxhighlight> <br><br> == Reactでthisが問題にならない理由 == 現代のReact開発では、<code>this</code> に関する問題を意識する場面は大幅に減少している。<br> その背景にあるクラスコンポーネント時代の問題と、関数コンポーネントによる解決を以下に示す。<br> <br> ==== クラスコンポーネント時代のthis問題 ==== React 16.8以前では、クラスコンポーネントが主流であった。<br> クラスコンポーネントでは、イベントハンドラを <code>onClick</code> 等のPropsに渡す際に <code>this</code> が消失する問題が頻繁に発生していた。<br> <br> <syntaxhighlight lang="javascript"> class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } handleClick() { // thisが消失しているためthis.setStateはundefinedになる this.setState({ count: this.state.count + 1 }); } render() { return ( <button onClick={this.handleClick}> {/* this.handleClickをonClickに渡すとthisが消失する */} カウント: {this.state.count} </button> ); } } </syntaxhighlight> <br> この問題に対する解決策を以下に示す。<br> <br> * 解決策1 : コンストラクタでbindする *: <syntaxhighlight lang="javascript"> class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; // コンストラクタでbindする this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState({ count: this.state.count + 1 }); } render() { return ( <button onClick={this.handleClick}> カウント: {this.state.count} </button> ); } } </syntaxhighlight> *: <br> * 解決策2 : クラスフィールドでアロー関数を使用する (推奨) *: <syntaxhighlight lang="javascript"> class Counter extends React.Component { state = { count: 0 }; // クラスフィールドとアロー関数でthisを固定する handleClick = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <button onClick={this.handleClick}> カウント: {this.state.count} </button> ); } } </syntaxhighlight> <br> ==== 関数コンポーネントでの解決 ==== React 16.8で導入されたHooksにより、関数コンポーネントで状態管理が可能になった。<br> 関数コンポーネントは <code>this</code> を全く使用しないため、<code>this</code> の消失問題が根本的に解消される。<br> <br> <syntaxhighlight lang="javascript"> import React, { useState } from "react"; // 関数コンポーネント (thisが不要) function Counter() { const [count, setCount] = useState(0); // thisを使用しないため消失の問題が発生しない const handleClick = () => { setCount(count + 1); }; return ( <button onClick={handleClick}> カウント: {count} </button> ); } </syntaxhighlight> <br> 下表に、クラスコンポーネントと関数コンポーネントの <code>this</code> に関する比較を示す。<br> <br> <center> {| class="wikitable" |+ クラスコンポーネント vs 関数コンポーネント ! 項目 !! クラスコンポーネント !! 関数コンポーネント |- | thisの使用 || 必要 || 不要 |- | 状態管理 || <code>this.state</code> / <code>this.setState</code> || <code>useState</code> Hook |- | thisの消失問題 || 発生する || 発生しない |- | 解決策 || bind または クラスフィールド || Hooks (そもそも問題なし) |- | 現在の推奨 || レガシーコード || 現在のベストプラクティス |} </center> <br> 現在のReactのベストプラクティスは関数コンポーネントとHooksを使用することであるが、<br> 既存のレガシーコードを保守する場面ではクラスコンポーネントの <code>this</code> の動作を理解しておく必要がある。<br> <br><br> == 関連情報 == * [[JavaScriptの基礎 - アロー関数]] *: アロー関数の構文、暗黙のreturn、レキシカルthis * [[JavaScriptの基礎 - コールバック関数]] *: コールバック関数、高階関数、タイマ、イベントリスナー * [[JavaScriptの基礎 - オブジェクトリテラル]] *: オブジェクトの作成と操作、プロパティアクセス、メソッド <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Web]]
JavaScriptの基礎 - thisキーワード
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse