ホームページ  >  記事  >  ウェブフロントエンド  >  アロー関数を理解する

アロー関数を理解する

hzc
hzc転載
2020-06-16 09:41:592576ブラウズ

私は以前、アロー関数についてはすでによく理解していると感じていましたが、再びだまされることはありえないと感じていました。しかし、数日前に非常に奇妙な問題に遭遇し、長い間悩んだ結果、それはアロー機能による落とし穴であることがわかりました。したがって、次の記事があります~

問題の説明

たとえば、基本クラス Animal があり、これには基本メソッドsayNameがあります。それを継承する後続の各サブクラスは、その身元を証明するために、sayName メソッドを実装する必要があります。基本クラス コードの実装は非常に単純です:

class Animal {
	sayName = () => {
		throw new Error('你应该自己实现这个方法');
  }
}

そこで、Animal 基本クラスから継承して Pig サブクラスを実装する必要があります。実装も非常に単純です:

class Pig extends Animal {
	sayName() {
		console.log('I am a Pig');
	}
}

とてもシンプルですか?落とし穴はどこにありますか?ただし、実際に実行してみると、結果が期待どおりではないことがわかります。正確に何が間違っていたのでしょうか?なぜこの短い数行のコードでエラーが報告されるのでしょうか?

問題を発見するアロー関数を理解する

何度も試行錯誤した結果、最終的にそれがアロー関数の落とし穴であることがわかりました。この問題を解決するには、Animal 基本クラスのsayName を通常の関数に変更するか、Pig サブクラスのsayName をアロー関数に変更するだけで済みます。では、アロー関数では一体何が起こっているのでしょうか?

これを書いていて、かつてこの質問について面接官からインタビューを受けたことを突然思い出しました!その際、インタビュアーは、クラスの通常の関数であるアロー関数と、クラスのコンストラクタ内のバインド関数の違いについて質問しました。そのときは答えは明白で論理的でしたが、相続となるとすべてがひっくり返りました。上記の質問に答えるために、まず面接の質問に答えましょう。

アロー関数、通常の関数、コンストラクターのバインド関数の違いは何ですか?

この問題をより直感的に理解するには、次のコードを使用できます。 babel のコンパイル結果と違いを確認したほうがよいでしょう。

最初に、簡単なコードを入力します

class A {
  	constructor() {
		this.b = this.b.bind(this);    	
    }
  
    a() {
    	console.log('a');
    }
	  b() {
    	console.log('b')
    }
    c = () => {
    	console.log('c')
    }
}

babel がコンパイルされてどのようなものになるかを見てみましょう:

"use strict";

function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }

function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

var A = /*#__PURE__*/function () {
  function A() {
    _classCallCheck(this, A);

    _defineProperty(this, "c", function () {
      console.log(&#39;c&#39;);
    });

    this.b = this.b.bind(this);
  }

  _createClass(A, [{
    key: "a",
    value: function a() {
      console.log(&#39;a&#39;);
    }
  }, {
    key: "b",
    value: function b() {
      console.log(&#39;b&#39;);
    }
  }]);

  return A;
}();

コンパイルされたコードのほとんどは補助関数であり、確認できるのは一部だけです重要なポイントの説明:

var A = /*#__PURE__*/function () {
  function A() {
    _classCallCheck(this, A);

    _defineProperty(this, "c", function () {
      console.log(&#39;c&#39;);
    });

    this.b = this.b.bind(this);
  }

  _createClass(A, [{
    key: "a",
    value: function a() {
      console.log(&#39;a&#39;);
    }
  }, {
    key: "b",
    value: function b() {
      console.log(&#39;b&#39;);
    }
  }]);

  return A;
}();

コンパイル結果から、相互の違いがわかります:

通常の関数: babel がコンパイルされた後、次の場所に配置されます。関数のプロトタイプ

  • コンストラクター内のバインド関数: コンパイル後、関数のプロトタイプに配置されるだけでなく、インスタンス化されるたびに生成されます。 . 現在のインスタンス コンテキストの変数をバインドします (this.b = this.b.bind(this))。

  • アロー関数: babel がコンパイルされた後、インスタンス化されるたびに、defineProperty が呼び出され、アロー関数のコンテンツが現在のインスタンス コンテキストにバインドされます。

  • コンパイル結果から判断すると、実際の開発ではコンテキストをバインドする必要がある場合はアロー関数を使用するのが最適です。なぜなら、bind メソッドを使用すると、プロトタイプ関数が生成されるだけでなく、インスタンス化ごとに追加の関数も生成されるからです。

  • 更新

Yu Tengjing のコメントを読んだ後、私はもっと本質的なことを学びました。

class = 記号を使用して宣言されたメソッドと変数の場合は、インスタンスの属性として使用されますが、= 記号なしで宣言された属性の場合は、プロトタイプ チェーンに配置されます。たとえば、

class A {
    a() {
        
    }
    b = 2;
    c = () => {
    }
}

このクラスの場合、インスタンス化されると、b と c がインスタンスの属性として使用され、a はプロトタイプ チェーンに配置されます。

では、なぜこのように実装されるのでしょうか?実際、これは tc39 仕様で言及されていることがわかります: フィールド宣言

等号宣言が直接書かれている場合、それは実際にはフィールド宣言の構文であり、そのようなものを直接宣言するのと同等です。インスタンス属性。

トピックに戻る

前の問題を解決したら、トピックに戻りましょう。実際のコンパイル条件でのクラスのアロー関数のコンパイル結果を理解すると、問題を理解しやすくなります。

Q: サブクラスが通常の関数を使用してsayNameを宣言すると、実行上の問題が発生するのはなぜですか?

A: サブクラスが通常の関数を使用してsayNameを宣言した場合、サブクラスによって宣言されたsayNameがコンストラクターのプロトタイプに配置されます。ただし、基本クラスのsayNameはアロー関数を使用するため、各インスタンスは直接sayName変数を持つことになります。 JavaScript 変数のアクセス規則に従って、まず変数自体が検索され、見つからない場合はプロトタイプ チェーン上で検索されます。したがって、sayName を検索する場合、基本クラスによって宣言された SayName 関数を直接検索し、プロトタイプ チェーン上では検索しないため、問題が発生します。

Q: サブクラスでは、sayName の宣言にアロー関数が使用されているのに、実行には問題がないのはなぜですか?

A: es6 クラスが初期化されると、最初に基本クラスのコンストラクターが実行され、次に独自のコンストラクターが実行されます。したがって、基底クラスが初期化された後は、サブクラスで宣言されたアロー関数sayNameが基底クラスのアロー関数をオーバーライドするため、実行には問題ありません。

要約

arrow 関数についてはよく知っていると思っていましたが、騙されました。確かに、学習に終わりはありません。しかし、クラス内のアロー関数についてもより深く理解しています。

しかし、コメント エリアの皆さんから指摘を受けて、実際にはアロー関数が問題の原因ではないことがわかりました。クラス内で = 記号を使用して宣言された変数は、フィールド宣言の構文に属します。この方法で宣言された変数の場合、プロトタイプ チェーンにマウントされるのではなく、インスタンスのプロパティに直接マウントされます。

推奨チュートリアル: 「JS チュートリアル

以上がアロー関数を理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。