ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptのプロトタイプチェーンと継承を分かりやすく詳しく解説

JavaScriptのプロトタイプチェーンと継承を分かりやすく詳しく解説

黄舟
黄舟オリジナル
2017-03-03 15:25:561113ブラウズ

JavaScript言語の継承メカニズムには、「サブクラス」と「親クラス」の概念がなく、「クラス」と「インスタンス」の区別もなく、すべて非常に独特な「プロトタイプ」「プロトタイプチェーン」に依存しています。継承を実装するパターン。

この部分の知識はJavaScriptの核心部分の一つでもあり、難しい点でもあります。みんなの勉強がしやすくなるように、そして私自身の感想を深めるために、勉強ノートを整理しました。コードのこの部分には多くの詳細があり、繰り返し検討する必要があります。それでは始めましょう。

シリーズ目次

  • JavaScriptのクロージャ(Closure)を簡単に説明

  • JavaScriptのこれを簡単に説明

  • JavaScriptのプロトタイプチェーンと継承を簡単に説明

ちょっと試してみる

プロトタイプチェーンの例(書き方のポイント) コメントでは、コードをブラウザにコピーしてテストすることができます、以下同様)

function foo(){}              //通过function foo(){}定义一个函数对象
foo.prototype.z = 3;          //函数默认带个prototype对象属性   (typeof foo.prototype;//"object")

var obj =new foo();           //我们通过new foo()构造器的方式构造了一个新的对象
obj.y = 2;                    //通过赋值添加两个属性给obj
obj.x = 1;                    //通过这种方式构造对象,对象的原型会指向构造函数的prototype属性,也就是foo.prototype

obj.x; // 1                 //当访问obj.x时,发现obj上有x属性,所以返回1
obj.y; // 2                 //当访问obj.y时,发现obj上有y属性,所以返回2
obj.z; // 3                 //当访问obj.z时,发现obj上没有z属性,那怎么办呢?它不会停止查找,它会查找它的原型,也就是foo.prototype,这时找到z了,所以返回3

//我们用字面量创建的对象或者函数的默认prototype对象,实际上它也是有原型的,它的原型指向Object.prototype,然后Object.prototype也是有原型的,它的原型指向null。
                                   //那这里的Object.prototype有什么作用呢?
typeof obj.toString; // ‘function'  

//我们发现typeof obj.toString是一个函数,但是不管在对象上还是对象的原型上都没有toString方法,因为在它原型链的末端null之前都有个Object.prototype方法,
//而toString正是Object.prototype上面的方法。这也解释了为什么JS基本上所有对象都有toString方法
'z' in obj; // true               //obj.z是从foo.prototype继承而来的,所以'z' in obj返回了true
obj.hasOwnProperty('z'); 
// false   
//但是obj.hasOwnProperty('z')返回了false,表示z不是obj直接对象上的,而是对象的原型链上面的属性。(hsaOwnProperty也是Object.prototype上的方法)

今、x、y、zにアクセスして、プロトタイプを検索しましたオブジェクトの特定の属性にアクセスし、そのオブジェクトに対応する属性がない場合は、プロトタイプ チェーンを上方向に検索し、それでも null が見つかった場合は、unknown が返されます。

プロトタイプベースの継承

function Foo(){
   this.y = 2;     
}

Foo.prototype.x = 1;
var obj3 = new Foo();  
//①当使用new去调用的时候,函数会作为构造器去调用②this会指向一个对象(这里是obj3),而这个对象的原型会指向构造器的prototype属性(这里是Foo.prototype)
obj3.y; //2 
obj3.x; //1    //可以看到y是对象上的,x是原型链上的原型(也就是Foo.prototype上)

プロトタイプの属性とプロトタイプ

関数宣言を使用して空の関数を作成する場合、Foo.prototypeがどのような構造であるかを見てみましょう。この関数にはプロトタイプ属性があり、デフォルトでコンストラクターと __proto__ という 2 つの属性があります。コンストラクター属性はそれ自体 Foo を指します。__proto__ は chrome で公開されます (標準属性ではありません。知っておいてください)。次に Foo のプロトタイプです。プロトタイプは Object.prototype を指します。したがって、Object.prototype の一部のメソッド toString および valueOf は、すべての一般オブジェクトで使用されます。

function Foo(){}
typeof Foo.prototype; // "object"
Foo.prototype.x = 1;
var obj3 = new Foo();

要約すると、ここには Foo 関数があります。この関数にはプロトタイプ オブジェクト属性があります。その機能は

new Foo() を使用してインスタンスを構築する場合、このコンストラクターのプロトタイプ属性が新しいプロトタイプとして使用されます。これらのオブジェクトの。

したがって、プロトタイプとプロトタイプは別のものであることを理解する必要があります。プロトタイプは関数オブジェクトのデフォルト属性であり、プロトタイプは通常、コンストラクターのプロトタイプ属性です。 あるクラスが別のクラスを継承することを実現

function Person(name, age) {
   this.name = name;    
   //直接调用的话,this指向全局对象(this知识点整理)
   this.age = age;      
   //使用new调用Peoson的话,this会指向原型为Person.prototype的空对象,通过this.name给空对象赋值,最后this作为return值
}

Person.prototype.hi = function() {   
//通过Person.prototype.hi创建所有Person实例共享的方法,(可以参考上节的左图:对象的原型会指向构造器的prototype属性,所以想让obj1,obj2,obj3共享一些方法的话,只需在原型对象上一次性地添加属性和方法就可以了);
   console.log('Hi, my name is ' + this.name + ',I am ' + this.age + ' years old now.')//这里的this是全局对象
};

Person.prototype.LEGS_NUM = 2;   //再设置一些对Person类的所有实例共享的数据
Person.prototype.ARMS_NUM = 2;
Person.prototype.walk = function() {
  console.log(this.name + ' is walking...');
};

function Student(name, age, className) {  //每个学生都属于人
  Person.call(this, name, age);  //在Student这个子类里面先调用一下父类
  this.className = className;
}

//下一步就是我们怎么去把Student的实例继承Person.prototype的一些方法

Student.prototype = Object.create(Person.prototype);    
//Object.create():创建一个空对象,并且这个对象的原型指向它的参数  
//这样子我们可以在访问Student.prototype的时候可以向上查找到Person.prototype,又可以在不影响Person的情况下,创建自己的方法
Student.prototype.constructor = Student;  
//保持一致性,不设置的话constructor会指向Person

Student.prototype.hi = function() {    
//通过Student.prototype.hi这样子的赋值可以覆盖我们基类Person.prototype.hi
  console.log('Hi, my name is ' + this.name + ',I am ' + this.age + ' years old now, and from ' + this.className + '.');
}
Student.prototype.learn = function(subject) {    
//同时,我们又有自己的learn方法
  console.log(this.name + 'is learning ' + subject + ' at' + this.className + '.');
};

//test
var yun = new Student('Yunyun', 22, 'Class 3,Grade 2');
yun.hi(); //Hi,my name is Yunyun,I'm 22 years old now,and from Class 3, Grade 2.
console.log(yun.ARMS_NUM); // 2     
//我们本身对象是没有的,对象的原型也就是Student.prototype也没有,但是我们用了继承,继续向上查找,找到了Person.prototype.ARMS_NUM,所以返回2
yun.walk(); //Yunyun is walking...
yun.learn('math'); //Yunyun is learning math at Class 3,Grade 2.

図と組み合わせて、上記のコードを逆に分析してみましょう: まず、新しい Student を介して Student インスタンス yun を作成し、yun のプロトタイプは、次のプロトタイプ属性を指します。コンストラクター (ここでは Student.prototype)。 Student.prototype には hi メソッドと learn メソッドがあります。 Student.prototype は Object.create(person.prototype) によって構築されるため、ここでの Student.prototype は空のオブジェクトです。このオブジェクトのプロトタイプは .prototype を指します。次に、Legs_NUM、ARMS_NUM 属性、および こんにちは、walk メソッドも Person.prototype に設定します。次に、 person.prototype はプリセット オブジェクトであり、そのプロトタイプも Object.prototype であるため、どのオブジェクトにも hasOwnProperty 、 valueOf 、 toString が含まれます。他のパブリック関数、これらの関数はすべて Object.prototype から派生します。このようにして、

プロトタイプチェーンに基づく継承が実装されます。 では、hi、walk、Learn メソッドを呼び出すとどうなるでしょうか?たとえば、hi メソッドを呼び出す場合、最初にオブジェクト yun に hi メソッドがあるかどうかを確認しますが、このインスタンスには hi メソッドがないため、上方向に検索して、hi メソッドである yun のプロトタイプを見つけます。 Student.protoype にあるので、最終的にそれを呼び出します。その 1 つは Student.prototype.hi であり、他のメソッドの呼び出しも同様です。

プロトタイプを変更する JavaScript のプロトタイプが Java のクラスとは異なることはわかっていますが、Java のクラスを一度作成すると、動的に変更するのは困難ですが、

JavaScript のプロトタイプは実際には通常のオブジェクトです

つまり、プログラムの実行段階で、プロトタイプにいくつかの属性を動的に追加または削除することもできます。

上記のコードに基づいて、yun のインスタンスがすでにあるので、実験を進めましょう:

Student.prototype.x = 101;        //通过Student.prototype.x把yun的原型动态地添加一个属性x
yun.x;   //101                    //那我们发现所有的实例都会受到影响
//接着我们做个有趣的实验
Student.prototype = {y:2};        //我们直接修改构造器的prototype属性,把它赋值为一个新的对象
yun.y;  //undefined               
yun.x;  //101                     //所以我们得出:当我们修改Student.prototype值的时候,并不能修改已经实例化的对象
var Tom = new Student('Tom',3,'Class LOL KengB');  
Tom.x; //undefined                //但当我们创建一个新的实例时,这一次x就不见了,
Tom.y; //2                        //并且y是新的值
したがって、プロトタイプが動的に変更されると、作成されたインスタンスまたは新しく作成されたすべてのインスタンスに影響しますが、変更した場合プロトタイプ全体を作成して新しいオブジェクトに割り当てると、作成されたインスタンスには影響しませんが、後続のインスタンスには影響します。

实现继承的方式

实现继承有多种方式,下面我们还是以Person和Student来分析

function Person() {
}

function Student() {
}

Student.prototype = Person.prototype; // 我们可不可用这种方式呢?这种方法是错误的:因为子类Student有自己的一些方法
//,如果通过这样子赋值,改变Student的同时也改变了Person。

Student.prototype = new Person(); //这种方式是可以实现的,但是调用构造函数有时候也是有问题的,比如要传进Person一个name和age
//,这里的Student是个类,还没实例化,这时候有些奇怪了,传什么都不是。

Student.prototype = Object.create(Person.prototype); //相对来说这中方式是比较理想的,这里我们创建了一个空的对象
//,并且对象的原型指向Person.prototype,这样我们既保证了继承了Person.prototype上的方法,并且Student.prototype又有自己空的对象。
//但是Object.create是ES5以后才有的

以上就是深入浅出JavaScript之原型链和继承的详解的内容,更多相关内容请关注PHP中文网(www.php.cn)!

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。