Maison  >  Article  >  interface Web  >  Une explication détaillée de la chaîne de prototypes et de l'héritage de JavaScript en termes simples

Une explication détaillée de la chaîne de prototypes et de l'héritage de JavaScript en termes simples

黄舟
黄舟original
2017-03-03 15:25:561149parcourir

Le mécanisme d'héritage du langage Javascript, il n'a pas de concept de "sous-classe" et de "classe parent", et il n'y a pas de distinction entre "classe" et "instance", tout repose sur un seul A modèle de "chaîne de prototypes" très particulier pour implémenter l'héritage.

Cette partie de la connaissance est aussi l'un des points centraux de JavaScript, et c'est aussi un point difficile. J'ai organisé mes notes d'étude pour permettre à chacun d'étudier plus facilement et d'approfondir ma propre impression. Il y a de nombreux détails dans cette partie du code et nécessitent un examen répété. Alors commençons.

Table des matières de la série

  • Introduction à la fermeture de JavaScript (Closure) d'une manière simple

  • Cette explication approfondie de JavaScript

  • Introduction approfondie à la chaîne de prototypes et à l'héritage de JavaScript

Un petit essai

Exemple de chaîne de prototypes (les points clés sont écrits dans les commentaires, vous pouvez copier le code pour parcourir Testé dans l'appareil, le même ci-dessous)

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上的方法)

Nous venons d'accéder à x, y et z, et recherchés respectivement dans la chaîne prototype. Nous pouvons savoir : lorsque nous accédons à l'objet Lorsqu'un certain attribut est trouvé et qu'il n'y a pas d'attribut correspondant sur l'objet, il recherchera vers le haut dans la chaîne prototype si null n'est pas. trouvé jusqu'à ce que null soit trouvé, undefined sera renvoyé.

Héritage basé sur des prototypes

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上)

Attributs et prototypes des prototypes

Jetons un coup d'œil à quel type de structure est Foo.prototype. Lorsque nous utilisons la déclaration de fonction pour créer une fonction vide, alors cette fonction a un attribut prototype, et elle a deux attributs par défaut, constructeur Et __proto__. ,

l'attribut constructeur pointera vers lui-même Foo, __proto__ est exposé dans chrome (pas un attribut standard, sachez-le), alors le prototype de Foo.prototype pointera vers Object.prototype. Par conséquent, certaines méthodes toString et valueOf de

sur Object.prototype seront utilisées par chaque objet général.

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

Pour résumer : nous avons ici une fonction Foo. Cette fonction a un attribut d'objet prototype. Sa fonction est Lorsque vous utilisez new Foo() pour construire une instance, ce constructeur L'attribut prototype sera. servir de prototype à ces nouveaux objets.

Nous devons donc comprendre que prototype et prototype sont deux choses différentes. Le prototype est l'attribut par défaut de l'objet fonction, et le prototype est généralement l'attribut prototype du constructeur.

Implémentation d'une classe héritant d'une autre classe

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.

En combinant le diagramme, analysons le code ci-dessus à l'envers : nous créons d'abord un nouvel étudiant via un nouveau Étudiant L'instance de l'étudiant yun, le prototype de yun pointe vers l'attribut prototype du constructeur (voici Student.prototype), Student.prototype a la méthode hi et la méthode d'apprentissage, Student.prototype est construit via Object.create (Person.prototype), donc Student .prototype est ici un objet vide, et le prototype de cet objet pointe vers Person.prototype. Ensuite, nous définissons également les attributs LEGS_NUM, ARMS_NUM et les méthodes hi, walk sur Person.prototype. Ensuite, nous avons directement défini une fonction Person. Person.prototype est un objet prédéfini, qui aura lui-même son prototype. Son prototype est Object.prototype. C'est précisément pour cette raison que n'importe lequel de nos objets aura hasOwnProperty , valueOf, toString et. autres fonctions publiques, ces fonctions sont toutes dérivées de Object.prototype. De cette manière, l'héritage basé sur la chaîne de prototypes est implémenté. Alors, que se passe-t-il lorsque nous appelons les méthodes hi, walk et learn ? Par exemple, lorsque nous appelons la méthode hi, nous vérifions d'abord s'il existe une méthode hi sur l'objet yun, mais il n'y a pas de méthode hi dans ce cas, nous cherchons donc vers le haut et trouvons le prototype de yun, qui est la méthode hi. sur Student.protoype, nous l'appelons donc finalement Student.prototype.hi, et l'appel d'autres méthodes est similaire.

Changer de prototype

On sait que le prototype en JavaScript n'est pas comme la classe en Java Une fois la classe en Java écrite, il est difficile de la changer dynamiquement, mais en JavaScript. Le prototype est en fait un objet ordinaire, ce qui signifie que pendant la phase d'exécution du programme, nous pouvons également ajouter ou supprimer dynamiquement certains attributs au prototype.

Sur la base du code ci-dessus, nous avons déjà l'instance de yun, poursuivons l'expérience :

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是新的值
Donc, quand elle est modifiée dynamiquement Lors de l'utilisation du prototype, cela affectera toutes les instances créées ou nouvellement créées. Cependant, si vous modifiez l'intégralité du prototype et l'attribuez à un nouvel objet, cela n'affectera pas les instances déjà créées, mais cela affectera les instances suivantes.

实现继承的方式

实现继承有多种方式,下面我们还是以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)!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn