ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript のプロトタイプと継承についての詳細

JavaScript のプロトタイプと継承についての詳細

青灯夜游
青灯夜游転載
2019-11-26 18:11:331908ブラウズ

この記事では主に、JavaScript でプロトタイプがどのように機能するか、すべてのオブジェクトで共有される [プロトタイプ] の非表示プロパティを介してオブジェクトのプロパティとメソッドをリンクする方法、およびカスタム コンストラクターを作成する方法と、プロパティとメソッドの値を渡すためにプロトタイプの継承がどのように機能するかについて説明します。 。

JavaScript のプロトタイプと継承についての詳細

#はじめに

JavaScript はプロトタイプベースの言語です。つまり、オブジェクトのプロパティとメソッドには次の方法でアクセスできます。クローン作成および拡張機能を備えたユニバーサル オブジェクト共有を備えています。これはプロトタイプ継承と呼ばれ、クラス継承とは異なります。 JavaScript は、一般的なオブジェクト指向プログラミング言語の中でも比較的独特です。PHP、Python、Java などの他のよく知られた言語は、オブジェクトの設計図としてクラスを定義するクラスベースの言語であるためです。

[関連コースの推奨事項:

JavaScript ビデオ チュートリアル ]

この記事では、オブジェクト プロトタイプとは何か、コンストラクターを使用してプロトタイプを拡張する方法について学びます。新しいオブジェクト。継承とプロトタイプ チェーンについても学習します。

JavaScript プロトタイプ

JavaScript のすべてのオブジェクトには、[[プロトタイプ]] と呼ばれる内部プロパティがあります。新しい空のオブジェクトを作成することで、これを実証できます。

let x = {};

これが通常のオブジェクトの作成方法ですが、これを実現する別の方法はオブジェクト コンストラクターを使用することであることに注意してください。

let x = new object()

[[Prototype]] を囲む角かっこは、次のことを示します。これは内部プロパティであり、コードから直接アクセスできないこと。

この新しく作成されたオブジェクトの [[プロトタイプ]] を見つけるには、getPrototypeOf() メソッドを使用します。


Object.getPrototypeOf(x);

出力は、いくつかの組み込みプロパティとメソッドで構成されます。

出力:


{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

[[プロトタイプ]] を検索する別の方法は、__proto__ プロパティを使用することです。 __proto__ は、[[Prototype]] オブジェクトの内部プロパティを公開するプロパティです。

. _proto__ はレガシー機能であり、実稼働コードでは使用しないでください。また、最新のすべてのブラウザーに存在するわけではないことに注意してください。ただし、この記事ではデモの目的でこれを使用できます。

x.__proto__;

出力は getPrototypeOf() を使用した場合と同じになります。

出力


{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

JavaScript では 2 つ以上のオブジェクトにリンクされたメソッドが作成されるため、JavaScript のすべてのオブジェクトに [[プロトタイプ]] があることが重要です。

作成するオブジェクトは、日付や配列などの組み込みオブジェクトと同じ [[プロトタイプ]] を持ちます。この内部プロパティは、プロトタイプ属性を介してあるオブジェクトから別のオブジェクトに参照できます。これについては、チュートリアルで後ほど説明します。

#プロトタイプの継承

#オブジェクトのプロパティまたはメソッドにアクセスしようとすると、JavaScript は最初にオブジェクトを検索します。それ自体が見つからない場合は、オブジェクトの [[Prototype]] を検索します。オブジェクトとその [[Prototype]] をクエリした後に一致が見つからない場合、JavaScript はリンクされたオブジェクトのプロトタイプをチェックし、プロトタイプ チェーンの最後に到達するまで検索を続けます。


#プロトタイプ チェーンの最後は Object.prototype です。すべてのオブジェクトは、オブジェクトのプロパティとメソッドを継承します。チェーンの終わりを超えて検索すると、結果は null になります。

この例では、x は object から継承された空のオブジェクトです。 x は、オブジェクトが持つ任意のプロパティまたはメソッド (toString() など) を使用できます。

x.toString();

出力

[object Object]

このプロトタイプ チェーンの長さは 1 チェーンのみです。 x - > オブジェクト。 2 つの [[Prototype]] プロパティを連結しようとすると null になるため、これがわかります。

x.__proto__.__proto__;

出力

null

別のタイプのオブジェクトを見てみましょう。 JavaScript で配列を操作した経験がある場合は、pop() や Push() など、配列に多くの組み込みメソッドがあることをご存知でしょう。新しい配列の作成時にこれらのメソッドにアクセスできるのは、作成されたすべての配列が array.prototype のプロパティとメソッドにアクセスできるためです。

新しい配列を作成してこれをテストできます。

let y = [];

これを配列コンストラクターとして記述することもできることを思い出してください。y = new array() とします。

新しい y 配列の [[Prototype]] を見ると、x オブジェクトよりも多くのプロパティとメソッドがあることがわかります。 Array.prototype からすべてを継承します。

y.__proto__;
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …]

プロトタイプのコンストラクター属性が Array() に設定されていることがわかります。コンストラクター プロパティは、関数からオブジェクトを構築するためのメカニズムであるオブジェクトのコンストラクターを返します。

この場合、プロトタイプ チェーンが長くなるため、2 つのプロトタイプをリンクできるようになりました。 y -> Array -> Object のように見えます。

y.__proto__.__proto__;
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

このチェーンは Object.prototype を参照するようになりました。内部の [[Prototype]] をコンストラクターの Prototype 属性に対してテストして、それらが同じものを参照していることを確認できます。

y.__proto__ === Array.prototype;            // true
y.__proto__.__proto__ === Object.prototype; // true

isPrototypeOf() メソッドを使用してこれを実現することもできます。

Array.prototype.isPrototypeOf(y);      // true
Object.prototype.isPrototypeOf(Array); // true

我们可以使用instanceof操作符来测试构造函数的prototype属性是否出现在对象原型链中的任何位置。

y instanceof Array; // true

总而言之,所有JavaScript对象都具有隐藏的内部[[Prototype]]属性(可能__proto__在某些浏览器中公开)。对象可以扩展,并将继承[[Prototype]]其构造函数的属性和方法。

这些原型可以被链接,并且每个额外的对象将继承整个链中的所有内容。链以Object.prototype结束。

构造器函数

构造函数是用来构造新对象的函数。new操作符用于基于构造函数创建新实例。我们已经看到了一些内置的JavaScript构造函数,比如new Array()和new Date(),但是我们也可以创建自己的自定义模板来构建新对象。

例如,我们正在创建一个非常简单的基于文本的角色扮演游戏。用户可以选择一个角色,然后选择他们将拥有的角色类别,例如战士、治疗者、小偷等等。

由于每个字符将共享许多特征,例如具有名称、级别和生命值,因此创建构造函数作为模板是有意义的。然而,由于每个角色类可能有非常不同的能力,我们希望确保每个角色只能访问自己的能力。让我们看看如何使用原型继承和构造函数来实现这一点。

首先,构造函数只是一个普通函数。当使用new关键字的实例调用它时,它将成为一个构造函数。在JavaScript中,我们按照惯例将构造函数的第一个字母大写。

// Initialize a constructor function for a new Hero
function Hero(name, level) {
  this.name = name;  this.level = level;
}

我们创建了一个名为Hero的构造函数,它有两个参数:name和level。因为每个字符都有一个名称和一个级别,所以每个新字符都有这些属性是有意义的。this关键字将引用创建的新实例,因此将this.name设置为name参数将确保新对象具有name属性集。

现在我们可以用new创建一个新的实例。

let hero1 = new Hero('Bjorn', 1);

如果我们在控制台输出hero1,我们将看到已经创建了一个新对象,其中新属性按预期设置。

输出

Hero {name: "Bjorn", level: 1}

现在,如果我们得到hero1的[[Prototype]],我们将能够看到构造函数Hero()。(记住,它的输入与hero1相同。,但这是正确的方法。)

Object.getPrototypeOf(hero1);

输出

constructor: ƒ Hero(name, level)

您可能注意到,我们只在构造函数中定义了属性,而没有定义方法。在JavaScript中,为了提高效率和代码可读性,通常在原型上定义方法。

我们可以使用prototype向Hero添加一个方法。我们将创建一个greet()方法。

// Add greet method to the Hero prototype
Hero.prototype.greet = function () {
  return `${this.name} says hello.`;
}

因为greet()在Hero的原型中,而hero1是Hero的一个实例,所以这个方法对hero1是可用的。

hero1.greet();

输出

"Bjorn says hello."

如果检查Hero的[[Prototype]],您将看到greet()现在是一个可用选项。

这很好,但是现在我们想要为英雄创建角色类。将每个类的所有功能都放到Hero构造函数中是没有意义的,因为不同的类具有不同的功能。我们希望创建新的构造函数,但也希望它们连接到原始的Hero。

我们可以使用call()方法将属性从一个构造函数复制到另一个构造函数。让我们创建一个战士和一个治疗构造器。

// Initialize Warrior constructor
function Warrior(name, level, weapon) {
  // Chain constructor with call
  Hero.call(this, name, level);  // Add a new property
  this.weapon = weapon;
}// Initialize Healer constructor
function Healer(name, level, spell) {
  Hero.call(this, name, level);  this.spell = spell;
}

两个新的构造函数现在都具有Hero和unqiue的属性。我们将把attack()方法添加到Warrior中,而heal()方法添加到Healer中。

Warrior.prototype.attack = function () {
  return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () {
  return `${this.name} casts ${this.spell}.`;
}

此时,我们将使用两个可用的新字符类创建字符。

const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');

hero1现在被认为是拥有新属性的战士。

输出

Warrior {name: "Bjorn", level: 1, weapon: "axe"}

我们可以使用我们在战士原型上设置的新方法。

hero1.attack();
Console
"Bjorn attacks with the axe."

但是如果我们尝试使用原型链下面的方法会发生什么呢?

hero1.greet();

输出

Uncaught TypeError: hero1.greet is not a function

使用call()链接构造函数时,原型属性和方法不会自动链接。我们将使用Object.create()来链接原型,确保在创建并添加到原型的任何其他方法之前将其放置。

Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);
// All other prototype methods added below…

现在我们可以在一个战士或治疗者的实例上成功地使用Hero的原型方法。

hero1.greet();

输出

"Bjorn says hello."

这里是我们的角色创建页面的完整代码。

// Initialize constructor functions
function Hero(name, level) {
  this.name = name;
  this.level = level;
}
 
function Warrior(name, level, weapon) {
  Hero.call(this, name, level);
 
  this.weapon = weapon;
}
 
function Healer(name, level, spell) {
  Hero.call(this, name, level);
 
  this.spell = spell;
}
 
// Link prototypes and add prototype methods
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);
 
Hero.prototype.greet = function () {
  return `${this.name} says hello.`;
}
 
Warrior.prototype.attack = function () {
  return `${this.name} attacks with the ${this.weapon}.`;
}
 
Healer.prototype.heal = function () {
  return `${this.name} casts ${this.spell}.`;
}
 
// Initialize individual character instances
const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');

使用这段代码,我们已经用基本属性创建了Hero类,从原始构造函数创建了两个名为Warrior和Healer的字符类,向原型添加了方法,并创建了单独的字符实例。

この記事は js チュートリアル 列からのものです。ぜひ学習してください。

以上がJavaScript のプロトタイプと継承についての詳細の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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