TypeScript Deep Dive 日本語版
  • TypeScript Deep Dive 日本語版
  • TypeScript入門 & 環境構築
    • なぜTypeScriptを使うのか?
  • JavaScript
    • 等価演算子の同一性
    • リファレンス
    • nullとundefined
    • this
    • クロージャ
    • Number型
    • Truthy
  • モダンなJavaScriptの機能
    • クラス
      • Classes Emit
    • アロー関数
    • 残余引数(Restパラメータ)
    • let
    • const
    • 分割代入
    • スプレッド演算子
    • for...of
    • Iterator
    • テンプレートリテラル
    • Promise
    • ジェネレータ
    • async await
  • プロジェクトの環境設定
    • コンパイルコンテキスト
      • tsconfig.json
      • コンパイル対象ファイルの設定
    • 宣言空間
    • ファイルモジュール
      • ファイルモジュールの詳細
      • global.d.ts
    • 名前空間
    • 動的インポート
  • Node.js & TypeScriptのプロジェクト作成
  • React & TypeScriptのプロジェクト作成
  • TypeScriptの型システム
    • JavaScriptからの移行ガイド
    • @types パッケージ (DefinitelyTyped)
    • アンビエント宣言(declare)
      • 型定義ファイル
      • グローバル変数の宣言
    • インターフェース
    • Enum
    • lib.d.ts
    • 関数の型
    • 呼び出し可能オブジェクト
    • Type Assertion(型アサーション)
    • Freshness
    • 型ガード
    • リテラル型
    • Readonly
    • ジェネリック型
    • 型推論
    • 型の互換性
    • never
    • 判別可能なUnion型
    • Index signature(インデックス型)
    • 型の移動
    • 例外のハンドリング
    • ミックスイン
  • JSX
    • React
    • React以外のJSX
  • オプション
    • noImplicitAny
    • strictNullChecks
  • TypeScriptのエラー
    • エラーの理解
    • 一般的なエラー
  • NPM
  • テスト
    • Jest
    • Cypress
  • ツール
    • Prettier
    • Husky
    • Changelog
  • その他のヒント
    • String Based Enums
    • Nominal Typing
    • Stateful Functions
    • Bind is Bad
    • Currying
    • Type Instantiation
    • Lazy Object Literal Initialization
    • Classes are Useful
    • Avoid Export Default
    • Limit Property Setters
    • outFile caution
    • JQuery tips
    • static constructors
    • singleton pattern
    • Function parameters
    • Build Toggles
    • Barrel
    • Create Arrays
    • Typesafe Event Emitter
  • スタイルガイド(コーディング規約)
  • TypeScriptコンパイラの内側
    • Program
    • AST
      • TIP: Visit Children
      • TIP: SyntaxKind enum
      • Trivia
    • Scanner
    • Parser
      • Parser Functions
    • Binder
      • Binder Functions
      • Binder Declarations
      • Binder Container
      • Binder SymbolTable
      • Binder Error Reporting
    • Checker
      • Checker Diagnostics
      • Checker Error Reporting
    • Emitter
      • Emitter Functions
      • Emitter SourceMaps
    • Contributing
GitBook提供
このページ内
  • IIFE(Immediately-Invoked Function Expression): 即時実行関数式
  • __extends
  • d.prototype.__proto__ = b.prototype
  • d.prototype.__proto__ = b.prototypeの意味

役に立ちましたか?

  1. モダンなJavaScriptの機能
  2. クラス

Classes Emit

IIFE(Immediately-Invoked Function Expression): 即時実行関数式

クラスの機能を実現するためにコンパイル後に生成されるJavaScriptは、以下のようなものかもしれません:

function Point(x, y) {
    this.x = x;
    this.y = y;
}
Point.prototype.add = function (point) {
    return new Point(this.x + point.x, this.y + point.y);
};

TypeScriptが生成するクラスは、Immediately-Invoked Function Expression(IIFE)に包まれています。IIFEの例:

(function () {

    // BODY

    return Point;
})();

クラスがIIFEに包まれている理由は、継承に関係しています。TypeScriptは、IIFEによってベースクラスを変数_superとして補足(キャプチャ)できるようにしています:

var Point3D = (function (_super) {
    __extends(Point3D, _super);
    function Point3D(x, y, z) {
        _super.call(this, x, y);
        this.z = z;
    }
    Point3D.prototype.add = function (point) {
        var point2D = _super.prototype.add.call(this, point);
        return new Point3D(point2D.x, point2D.y, this.z + point.z);
    };
    return Point3D;
})(Point);

TypeScriptは、IIFEによって、簡単にベースクラス Pointを_super変数に捕捉できるようにしており、かつ、それがクラスの本体で一貫的に使用されていることに注意してください。

__extends

クラスを継承するとTypeScriptが次のような関数を生成することに気づくでしょう:

var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};

ここで dは派生クラス(derived class)を参照し、bはベースクラス(base class)を参照します。この関数は次の2つのことを行います:

  1. 親クラスの静的メンバを子クラスにコピーする:for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];

  2. 子クラス関数のプロトタイプを準備し、必要に応じて親のprotoのメンバを検索できるようにする。つまり、実質的に d.prototype.__proto__ = b.prototype です。

1を理解するのに苦労する人はほとんどいませんが、2については多くの人が理解に苦しみます。そのため、順番に説明します。

d.prototype.__proto__ = b.prototype

これについて多くの人を教えた結果、次のような説明が最もシンプルだと分かりました。まず、__extendsのコードが、単純なd.prototype.__proto__ = b.prototypeとどうして同じなのか、そしてなぜ、この行それ自体が重要であるのかを説明します。これをすべて理解するためには、これらのことを理解する必要があります:

  1. __proto__

  2. prototype

  3. newの関数の内側のthisに対する効果

  4. newのprototypeと__proto__に対する効果

JavaScriptのすべてのオブジェクトは __proto__メンバを含んでいます。このメンバは古いブラウザではアクセスできないことがよくあります(ドキュメントでは、この魔法のプロパティを [[prototype]]と呼ぶことがあります)。これは1つの目的を持っています:検索しているプロパティがオブジェクトに見つからない場合(例えば obj.property)、obj.__proto__.propertyを検索します。それでもまだ見つからなければ、 obj.__proto__.__proto__.propertyを検索します: それが見つかるか、最後の.__proto__自体がnullとなるまで続きます。これは、JavaScriptが_プロトタイプ継承_(prototypal inheritance)をサポートしているということです。次の例でこれを示します。chromeコンソールまたはNode.jsで実行できます:

var foo = {}

// foo を設定します foo.__proto__ も同様です
foo.bar = 123;
foo.__proto__.bar = 456;

console.log(foo.bar); // 123
delete foo.bar; // オブジェクトから削除します
console.log(foo.bar); // 456
delete foo.__proto__.bar; // foo.__proto__ から削除します
console.log(foo.bar); // undefined

これで、あなたは __proto__を理解できました。もう1つの便利な事実は、JavaScriptの関数(function)にはprototypeというプロパティがあり、そして、そのconstructorメンバは、逆に関数を参照しているということです。これを以下に示します:

function Foo() { }
console.log(Foo.prototype); // {} 例: これは存在しており、undefinedではありません
console.log(Foo.prototype.constructor === Foo); // `constructor` というメンバがあり、逆に関数を指しています

次に、呼び出された関数内のthisに対してnewが及ぼす影響を見てみましょう。基本的に、newを使って呼び出された関数の内側のthisは、関数から返される新しく生成されたオブジェクトを参照します。関数内でthisのプロパティを変更するのは簡単です:

function Foo() {
    this.bar = 123;
}

// new 演算子を使います
var newFoo = new Foo();
console.log(newFoo.bar); // 123

ここで理解しておくべきことは、関数に対するnewの呼び出しにより、関数のprototypeが、新しく生成されたオブジェクトの__proto__に設定されることです。次のコードを実行することによって、それを完全に理解できます:

function Foo() { }

var foo = new Foo();

console.log(foo.__proto__ === Foo.prototype); // True!

これだけのことです。今度は__extendsの抜粋を見てください。これらの行に番号を振りました:

1  function __() { this.constructor = d; }
2   __.prototype = b.prototype;
3   d.prototype = new __();

この関数を逆から見ると、3行目のd.prototype = new __()は、 d.prototype = {__proto__ : __.prototype}を意味します(prototypeと__proto__に対するnewの効果によるものです)。それを2行目(__.prototype = b.prototype;)と組み合わせると、d.prototype = {__proto__ : b.prototype}となります。

しかし、待ってください。私達は、単にd.prototype.__proto__だけが変更され、d.prototype.constructorは、それまで通り維持されることを望んでいました。そこで重要な意味があるのが、最初の行(function __() { this.constructor = d; })です。これによってd.prototype = {__proto__ : __.prototype, constructor : d}と同じことを実現できます(これは関数の内側のthisに対するnewによる効果のためです)。したがって元のd.prototype.constructorを復元しているので、ここで変更されたものは、__proto__だけです。なのでd.prototype.__proto__ = b.prototypeとなります。

d.prototype.__proto__ = b.prototypeの意味

これを行うことによって、子クラスにメンバ関数を追加しつつ、その他のメンバは基本クラスから継承することができます。次の簡単な例で説明します:

function Animal() { }
Animal.prototype.walk = function () { console.log('walk') };

function Bird() { }
Bird.prototype.__proto__ = Animal.prototype;
Bird.prototype.fly = function () { console.log('fly') };

var bird = new Bird();
bird.walk();
bird.fly();

基本的にbird.flyはbird.__proto__.fly(newはbird.__proto__の参照をBird.prototypeに設定することを思い出してください)から検索され、bird.walk(継承されたメンバ)はbird.__proto__.__proto__.walkから検索されます(bird.__proto__ == Bird.prototype、そして、bird.__proto__.__proto__ == Animal.prototypeです)。

前へクラス次へアロー関数

最終更新 2 年前

役に立ちましたか?