Maison  >  Article  >  interface Web  >  Analyse complète des principes de l'héritage JavaScript

Analyse complète des principes de l'héritage JavaScript

不言
不言original
2018-09-03 10:13:391223parcourir

Cet article vous apporte une analyse complète des principes de l'héritage JavaScript. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Héritage

Nous savons que JS est de la programmation OO, et naturellement les fonctionnalités de la programmation OO sont indispensables Après avoir appris le prototype, frappons pendant que le fer est chaud et parlons de l'un des trois. Principales fonctionnalités de la programmation OO - héritage.

Le mot héritage devrait être plus facile à comprendre. Nous connaissons l'héritage de biens, l'héritage d'une entreprise familiale, etc. Leur principe est qu'il y a un héritier, et alors vous êtes l'héritier, donc il y a. héritage. C'est vrai, l'héritage en JS, comme vous le comprenez, apparaît également par paires.
L'héritage consiste à copier les propriétés d'un objet vers l'objet qui doit être hérité.

Le langage OO prend en charge deux méthodes d'héritage : l'héritage d'interface et l'héritage d'implémentation, dont l'héritage d'interface est seules les signatures de méthode sont héritées, tandis que l'héritage d'implémentation hérite de la méthode réelle . Étant donné que les fonctions dans ECMAScript n'ont pas de signature, l'héritage d'interface ne peut pas être implémenté et seul l'héritage d'implémentation est pris en charge. La principale méthode d'héritage passe par la chaîne de prototypes Pour comprendre la chaîne de prototypes, vous devez d'abord savoir ce qu'est une chaîne de prototypes. Le prototype est. Pour ceux qui ne comprennent pas, vous pouvez lire cet article Qu'est-ce que le prototype JavaScript ? Explication détaillée du prototype javascript

En fait, l'héritage signifie simplement que
① il doit avoir un parent
② et il obtient toutes les instances et méthodes de ce parent

Un petit concept est popularisé ici. La pas de signature mentionnée ci-dessus n'était pas très claire lorsque je l'ai vue pour la première fois. Après l'avoir recherché, j'ai trouvé que cette déclaration était tout à fait acceptable.

Aucune signature
Nous savons que JS est un langage faiblement typé et que ses paramètres peuvent être représentés par un tableau de 0 ou plus de valeurs. Nous pouvons nommer les paramètres des fonctions JS. C'est juste pour plus de commodité. mais ce n'est pas nécessaire, c'est-à-dire Il n'y a pas de lien nécessaire entre le fait de spécifier des paramètres sans nom et le fait de transmettre des paramètres. Nous pouvons nommer les paramètres mais ne pas les transmettre (la valeur par défaut n'est pas définie pour le moment), ou. on ne peut pas nommer des paramètres mais passer des paramètres, cette façon d'écrire est légale en JS Au contraire, les langages fortement typés ont des exigences très strictes à ce sujet Une fois plusieurs paramètres définis, plusieurs paramètres doivent être passés. Les paramètres nommés doivent nécessiter la création préalable d'une signature de fonction, et les futurs appels doivent être cohérents avec cette signature. (C'est-à-dire que si vous définissez plusieurs paramètres, vous devez transmettre plusieurs paramètres)**, mais js n'a pas ces règles et réglementations. L'analyseur ne vérifiera pas les paramètres nommés, donc js n'a pas de signature. .

Donnez-moi un exemple

function JSNoSignature () {  
  console.log("first params" + arguments[0] + "," + "second params" + arguments[1]);
}
JSNoSignature ("hello", "world");

Cet exemple est évident. Le paramètre nommé est vide, mais nous pouvons toujours transmettre des paramètres et appeler la méthode. JS ne se soucie pas du soi-disant type de paramètre, du nombre de paramètres, de la position du paramètre, des paramètres entrants et sortants. Toutes ses valeurs sont placées dans des arguments. Si vous devez renvoyer une valeur, renvoyez-la simplement sans déclaration. C'est ce qu'on appelle JS sans signature.

Chaîne prototype

Qu'est-ce qu'une chaîne prototype ? Le sens littéral est facile à comprendre, c'est-à-dire que le fait d'enchaîner tous les prototypes ensemble s'appelle une chaîne de prototypes. Bien sûr, cette explication est juste pour faciliter la compréhension. La chaîne de prototypes est la principale méthode pour implémenter l'héritage. L'idée de base est d'utiliser des prototypes pour permettre à un type de référence d'hériter des propriétés et des méthodes d'un autre type de référence . Nous savons que chaque constructeur a un objet prototype, que l'objet prototype contient un pointeur vers le constructeur et que l'instance contient un pointeur interne vers le prototype. À ce stade, si nous rendons l'objet prototype égal à une instance d'un autre type, alors l'objet prototype contiendra à ce moment un pointeur vers un autre prototype. En conséquence, l'autre prototype contient également un pointeur vers un autre constructeur. constitue la chaîne des instances et des prototypes, qui est la chaîne des prototypes. Pour parler franchement, c'est instance → prototype → instance → prototype → instance... La connexion est la chaîne de prototypes.

Je pense que l'héritage est une forme de chaîne de prototypes

.

Après avoir connu la chaîne de prototypes, nous devons savoir comment l'utiliser. ECMA fournit un ensemble de modes de base de la chaîne de prototypes. Les modes de base sont les suivants

Le mode de base de. la chaîne de prototypes

Lors de l'appel de instance.getFatherValue(), elle passera par trois étapes de recherche

①Rechercher par instance
// 创建一个父类
function FatherType(){
    this.fatherName = '命名最头痛';
}

FatherType.prototype.getFatherValue = function() {
    return this.fatherName;
}

function ChildType(){
    this.childName = 'George';
}

// 继承了FatherType,即将一个实例赋值给函数原型,我们就说这个原型继承了另一个函数实例
// 将子类的原型指向这个父类的实例
ChildType.prototype = new FatherType();

ChildType.prototype.getChildValue = function() {
    return this.childName;
}

let instance = new ChildType();
console.log(instance.getFatherValue()); // 命名最头痛
②Rechercher ChildType.prototype

③Rechercher FatherType.prototype. A ce stade, la méthode est trouvée dans cette étape, mais elle est introuvable. Dans le cas de propriétés ou de méthodes, le processus de recherche avance toujours une par une jusqu'à la fin de la chaîne de prototypes avant de s'arrêter.

La chaîne de prototypes à ce moment est instance → ChildType.prototype → FatherType.prototype
Après avoir exécuté instance.getFatherValue(), ceci dans getFatherValue est ChildType. À ce moment, ChildType trouvera l'attribut FatherName en fonction. à la chaîne prototype, finalement trouvée dans FatherType.

À ce moment-là, instance.constructor pointe vers FatherType

默认的原型

所有的引用类型默认都继承了Object,而这个继承也是通过原型链实现的,因此,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object。prototype,这也就是所有自定义类型都会继承toString(),valueOf()等默认方法的根本原因。
Array类型也是继承了Object类型的。
因此,我们可以总结一下,在原型链的最顶端就是Object类型,所有的函数默认都继承了Object中的属性。

原型和实例关系的确认

isPrototypeOf方法

javascript原型是什么?javascript原型的详细解说中我们有提到过isPrototypeOf方法可以用于判断这个实例的指针是否指向这个原型,这一章我们学习了原型链,这里做个补充,按照原型链的先后顺序,isPrototypeOf方法可以用于判断这个实例是否属于这个原型的。

依旧用上面那个例子
// 注意,这里用的是原型,Object.prototype,FatherType.prototype,ChildType.prototype
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(FatherType.prototype.isPrototypeOf(instance)); // true
console.log(ChildType.prototype.isPrototypeOf(instance)); // true

下面再介绍另一种方法,通过instanceof操作符,也可以确定原型和实例之间的关系

instanceof操作符

instanceof操作符是用来测试原型链中的构造函数是否有这个实例

function FatherType(){
    this.fatherName = '命名最头痛';
}

FatherType.prototype.getFatherValue = function() {
    return this.fatherName;
}

function ChildType(){
    this.childName = 'George';
}

// 继承了FatherType
ChildType.prototype = new FatherType();

// 创建实例
let instance = new ChildType();

// 为ChildType原型上添加新方法,要放在继承FatherType之后,这是因为new FatherType()会将ChildType原型上添加的新方法全部覆盖掉

ChildType.prototype.getChildValue = function() {
    return this.childName;
}

// 此时getFatherValue被重写了
ChildType.prototype.getFatherValue = function() {
    return true
}

console.log(instance.getFatherValue()); // true

②通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链。这部分的例子和解释在javascript原型是什么?javascript原型的详细解说中已经表述过了。一样的道理,只不过把原型换成了原型链罢了。

原型链的bug

原型链虽然强大,可以用它来实现继承,但是也是存在bug的,它最大的bug来自包含引用类型值的原型。也就是说原型链上面定义的原型属性会被所有的实例共享。
它还有另外一个bug,即在创建子类型的实例时,不能向父类型(超类型)的构造函数中传递参数。或者说没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
基于以上这两个原因,实践过程中很少会单独使用原型链

借用构造函数

其设计思想就是在子类型构造函数的内部调用父类(超类)构造函数。
由于函数只不过是在特定环境中执行代码的对象,因此通过apply()和call()方法也可以在(将来)新创建的对象上执行构造函数。

function FatherType() {
  this.name = 'George';
} 

function ChildType() {
  //通过call方法改变this的指向,此时FatherType中的this指的是ChildType,相当于在构造函数中定义自己的属性。
  FatherType.call(this);
}

let instance1 = new ChildType(); 
instance1.name = '命名最头痛';
console.log(instance1.name); // '命名最头痛'

let instance2 = new ChildType();
console.log(instance2.name); // George

通过上述方法很好解决了原型属性共享问题,此外,既然是一个函数,它也能传相应的参数,因此也能实现在子类型构造函数中向超类型构造函数传递参数。

function FatherType(name){
  this.name = name
}
function ChildType(){
  FatherType.call(this, "George");
  this.age = 18
}
let instance = new ChildType();
console.log(instance.name);  // George
console.log(instance.age);   // 18

借用构造函数的问题
借用构造函数,方法都在构造函数中定义,那么函数的复用就无从谈起,而且在父类(超类型)的原型定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

组合继承

组合继承也叫伪经典继承,其设计思想是将原型链和借用构造函数的技术组合到一块,发挥二者之长的一种继承模式,其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,这样既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

function FatherType(name){
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

FatherType.prototype.sayName = function() {
  console.log(this.name)
}

// 借用构造函数实现对实例的继承
function ChildType(name, age){
  // 使用call方法继承FatherType中的属性
  FatherType.call(this, name);
  this.age = age
}

// 利用原型链实现对原型属性和方法的继承
ChildType.prototype = new FatherType(); //将FatherType的实例赋值给ChildType原型
ChildType.prototype.constructor = ChildType; // 让ChildType的原型指向ChildType函数
ChildType.prototype.sayAge = function(){
  console.log(this.age)
}
let instance1 = new ChildType('命名最头痛', 18);
instance1.colors.push('black');
console.log(instance1.colors);         // 'red, blue, green, black'
instance1.sayName();
instance1.sayAge();

var instance2 = new ChildType('命名最头痛', 18);
console.log(instance2.colors);         // 'red, blue, green'
instance2.sayName();                   // '命名最头痛'
instance2.sayAge();                    // 18

组合继承方式避免了原型链和借用构造函数的缺陷,是JS中常用的继承方式。

原型链继承

原型链继承没有使用严格意义上的构造函数,其思想是基于已有的对象创建新对象

// 此object函数返回一个实例, 实际上object()对传入其中的对象执行了一次浅复制.
function object(o) {
  function F() {}  // 创建一个临时构造函数
  F.prototype = o; // 将传入的对象作为构造函数的原型
  return new F();  // 返回这个临时构造函数的新实例
}

let demo = {
  name: 'George',
  like: ['apple', 'dog']
}

let demo1 = object(demo);
demo1.name = '命名';     // 基本类型
demo1.like.push('cat'); // 引用类型共用一个内存地址

let demo2 = object(demo);
demo2.name = '头痛';    // 基本类型
demo2.like.push('chicken') // 引用类型共用一个内存地址
console.log(demo.name) // George
console.log(demo.like) // ["apple", "dog", "cat", "chicken"]

原型链继承的前提是必须要有一个对象可以作为另一个对象的基础。通过object()函数生成新对象后,再根据需求对新对象进行修改即可。 由于新对象(demo1, demo2)是将传入对象(demo)作为原型的,因此当涉及到引用类型时,他们会共用一个内存地址,引用类型会被所有实例所共享,实际上相当于创建了demo对象的两个副本。

Object.create()方法

ECMA5中新增Object.create()方法规范化了原型式继承。该方法接收两个参数
①基础对象,这个参数的实际作用是定义了模板对象中有的属性,就像上面例子中的demo,只有一个参数情况下,Object.create()与上例子中的object相同
②这个是可选参数,一个为基础对象定义额外属性的对象, 该对象的书写格式与Object.defineProperties()方法的第二个参数格式相同,每个属性都是通过自己的描述符定义的,以这种方式指定的任何属性都会覆盖原型对象上的同名属性。

// 只有一个参数
var demoObj = {
  name: 'George',
  like: ['apple', 'dog', 'cat']
}
let demo1Obj = Object.create(demoObj);
demo1Obj.name = '命名';
demo1Obj.like.push('banana');

let demo2Obj = Object.create(demoObj);
demo2Obj.name = '头痛';
demo2Obj.like.push('walk');

console.log(demoObj.like) //["apple", "dog", "cat", "banana", "walk"]

// 两个参数
var demoObj = {
  name: 'George',
  like: ['apple', 'dog', 'cat']
}

let demo1Obj = Object.create(demoObj, {
  name: {
    value:'命名'
  },
  like:{
    value: ['monkey']
  },
  new_val: {
    value: 'new_val'
  }
});
console.log(demoObj.name) // George
console.log(demo1Obj.name) // 命名
console.log(demo1Obj.like) // ["monkey"]
console.log(demo1Obj.new_val) // new_val
console.log(Object.getOwnPropertyDescriptor(demo1Obj,'new_val')) // {value: "new_val", writable: false, enumerable: false, configurable: false}

如果只想让一个对象与另一个对象保持类型的情况下,原型式继承是完全可以胜任的,不过要注意的是,引用类型值的属性始终都会共享相应的值。

寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路,其设计思想与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后返回一个对象。

// 这个函数所返回的对象,既有original的所有属性和方法,也有自己的sayHello方法
function createAnother(original) {
  let clone = Object.create(original);
  clone.sayHello = function(){            
    console.log('HELLO WORLD')
  }
  return clone;
}

let person = {
  name: 'George',
  foods: ['apple', 'banana']
}

let anotherPerson = createAnother(person);
anotherPerson.sayHello();  // HELLO WORLD

使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。

寄生组合式继承

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混合形式来继承方法。其背后思想:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。说白了就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

function inheritPrototype(childType, fatherType){
  let fatherObj = Object.create(fatherType.prototype);  // 创建对象
  fatherObj.constructor = childType;   // 弥补重写原型而失去的默认constructor属性
  childType.prototype = fatherObj;     // 指定对象
}

上例是寄生组合式继承最简单的形式,这个函数接受两个参数:子类型构造函数和超类型构造函数,在函数内部,①创建了父类型原型的一个副本,②为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性。③将新创建的对象(即副本)赋值给子类型的原型。

function FatherType(name){
  this.name = name;
  this.foods = ['apple', 'banana'];
}
FatherType.prototype.sayName = function(){
  console.log(this.name)
}
function ChildType(name, age){
  FatherType.call(this, name);
  this.age = age;
}
inheritPrototype(ChildType, FatherType);
ChildType.prototype.sayAge = function(){
  console.log(this.age)
}

总结

JS继承的主要方式是通过原型链实现的

实例-原型-实例-原型...无限链接下去就是原型链

所有引用类型的默认原型都是Object

instanceof操作符和isPrototypeOf方法都可以用于判断实例与原型的关系,其区别是,前者用的是原型,后者用的是构造函数

给原型添加方法的代码一定要放在继承之后,这是因为,在继承的时候被继承者会覆盖掉继承者原型上的所有方法

Object.create()方法用于创建一个新对象,其属性会放置在该对象的原型上

继承有6种方式,分别是原型链,借用构造函数,组合继承,原型式继承,寄生式继承和寄生组合式继承

相关推荐:

JavaScript中的继承之类继承_javascript技巧

JavaScript原型和继承

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

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