【JavaScript】原型、原型链与继承

1. 原型模式

每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个prototype对象就是通过调用构造函数创建的对象的原型,使用原型对象的好处是,在它上面定义的属性和方法可以被所有实例对象共享。原来在构造函数中直接赋值给对象实例的值,可以直接赋值给它们的原型,如下所示:

function Person(){
    
    }

Person.prototype.name = "CODER-V";
Person.prototype.age = 18;
Person.prototype.sayName() = function(){
    
    
	console.log(this.name);
};

let p1 = new Person();
p1.sayName()// CODER-V

let p2 = new Person();
p2.sayName()// CODER-V

这里,所有属性和方法都直接添加到了Person的prototype属性上,构造函数体中什么也没有。但这样定义之后,实例任然可以拥有相应的属性和方法,要理解这个过程就必须理解ECMAScript中方原型的本质。

2. 理解原型

无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)。默认情况下,所有原型对象都会自动获得一个名为constructor的属性,指回与之关联的构造函数。
在这里插入图片描述

在自定义构造函数时,原型对象默认只会获得constructor属性,其他方法都继承自Object。每次调用构造函数创建一个新的实例,这个实例内部的[[prototype]]指针就会被赋值为构造函数的原型对象。JavaScript中没有访问这个[[prototype]]特性的标准方式,但Firefox、Safari、Chrome会在每个对象上暴露_proto_属性,通过这个属性可以访问对象的原型。在其他实现中,这个特性完全被隐藏了。关键在于理解这一点:实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有。

在这里插入图片描述
虽然不是所有实现都对外暴露了[[prototype]],但是可以使用isPrototypeOf()方法,确定两个对象是否共享一个原型(其实这个原型就是一个隐藏类,对隐藏类不了解的可以看一下我的另一篇文章:垃圾回收与内存管理 4.3节),本质上,isPrototypeOf()会在传入参数的[[prototype]]指向调用它的对象时,返回true,即:

Persion.prototype.isPrototypeOf(Person1); //true

ECMAScript的Object类型有一个方法叫Object.getPrototypeOf(),返回参数内部特性[[prototype]]的值,即:

Persion.getPrototypeOf(Person1) == Persion.prototype; //true
Persion.getPrototypeOf(Person1).name; //CODER-V

使用Object.getPrototypeOf()可以方便的获取一个对象的原型,而这在通过与原型实现继承时显得尤为重要(本章后面会介绍)。

Object类型还有一个setPrototypeOf()方法,可以向实例的私有特性写入一个新值。这样就可以重写一个对象的原型继承关系:

let biped = {
    
     numLegs: 2 };
let person = {
    
     name: "CODER-V" };

Object.setPrototypeOf(person,biped);
console.log(person.biped);// 2,通过Object.setPrototypeOf为person的原型对象写入了新的值

但是不推荐这样做,因为修改了原型会间接修改了继承关系,这种影响是微妙且深远的,会影响所有继承了这个原型的实例对象。为了避免使用setPrototypeOf()造成的性能下降,可以通过Object.create()来创建一个新的对象,同时为其指定原型:

let biped = {
    
     numLegs: 2 };
let person = Object.create(biped);
person.name = "CODER-V";

/**
* person={
*	numLegs: 2;
*	name: "CODER-V"
* }
*/

3. 原型层级

通过对象访问属性时,会按照这个对象的属性名称开始搜索。搜索开始于对象实例本身。如果在这个实例上发现了给定的名称,则返回该名称对应的值。如果没有找到这个属性,则会沿着指针进入原型对象,然后在原型对象上找到属性后,再返回对应的值。这就是原型用于在多个对象实例间共享属性和方法的原理。

注意:前面提到的constructor属性只存在于原型对象,因此通过实例对象也是可以访问到的。

4. 原生对象原型

原型模式之所以重要,不仅体现在自定义类型上,而且还因为它也是实现所有原生引用类型的模式。所有原生引用类型的构造函数(包括Object、Array、String等)都在原型上定义了实例方法。比如,数组实例的sort()方法就是定义在Array.prototype上的,而字符串包装对象的sbustring()方法也是定义在String.prototype上的。

通过原生对象的原型可以取得所有默认方法的引用,也可以给原生类型的实例定义新的方法。可以像修改自定义对象原型一样修改原生对象原型,因此随时可以添加方法。

注意:尽管可以这么做,但开发环境中不推荐修改原生对象原型。这样做可能会引起误会和命名冲突(比如一个名称在浏览器中不存在,但在其他实现中却存在)。而且还有可能意外的重写原生的方法。推荐的做法是创建一个自定义的类,继承原生类型。

5. 继承与原型链

5.1 继承

很多面向对象语言都支持两种继承:接口继承和实现继承。前者只继承方法签名,后者继承实际的方法。接口继承在ECMAScript中是不可能的,因为函数没有签名(JavaScript没有接口和抽象方法的概念)。实现继承是JavaScript唯一的继承方式,而这主要就是通过原型链实现的。

5.2 原型链

ECMA-262把原型链定义为ECMAScript的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。重温一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应的另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本思想

实现原型链涉及如下代码模式:

// 这里是一个父类原型
function SuperType(){
    
    
	this.property = true;
};
SuperType.prototype.getSuperValue() = function(){
    
    
	return this.property;
};


// 这里是一个子类原型
function SubType(){
    
    
	this.subproperty = fales;
};
// 继承父类原型SuperType
SubType.prototype = new SuperType();//在子类原型上创建父类实例,实现继承

SubType.prototype.getSubValue = function(){
    
    
	return this.subproperty;
};

let instance = new SubType();
console.log(instacne.getSuperValue());// true

这个例子实现继承的关键,是SubType没有使用默认原型(下一节会讲到),而是将其替换成一个新的对象,这个新的对象恰好是SuperType的实例。这样一来,SubType的实例不仅能从SuperType的实例中继承属性和方法,而且还与SuperType的原型挂上了钩。能访问SuperType原型上的属性和方法。

原型链扩展前面描述的原型搜索机制。我们知道,在读取实例上的属性时,首先会在实例上搜索这个属性。如果找不到,则会继承搜索实例的原型。在通过原型链实现继承后,搜索就可以继承向上,搜索原型的原型,会一直持续到原型链的末端。

6. 默认原型

实际上原型链中还有一环。默认情况下,所有引用类型都继承自Object,这也是通过原项链实现的。任何函数的默认原型都是一个Object的实例,这意味着这个实例内部有一个指针指向Object.prototype。这也是为什么自定义类型能够继承包括toString()、valueOf()在内的所有默认方法的原因。

7. 总结

在JavaScript中,每个对象都有一个与之关联的原型(prototype),也称为原型对象。原型对象是一个普通的JavaScript对象,它包含了一些共享的属性和方法,这些属性和方法可以被该对象的所有实例所共享。

当访问一个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript会沿着该对象的原型链(prototype chain)向上查找,直到找到该属性或方法所在的原型对象为止。如果最终仍未找到该属性或方法,则返回undefined。

JavaScript中的原型链是由每个对象的原型对象所组成的链式结构。每个对象的原型对象都可以通过Object.getPrototypeOf()方法来获取。如果一个对象没有显式地指定原型对象,则其原型对象默认为Object.prototype对象。

JavaScript中的原型机制是一种基于对象的继承机制,它允许我们通过原型对象来共享属性和方法,从而避免在每个对象中都重复定义相同的属性和方法。这种机制可以帮助我们编写出更加简洁、高效的JavaScript代码。

(原型链)继承链:在子类实例的原型上创建父类实例,父类的构造器指向了上一级原型。原型也可能是一个实例,原型的原型又调用其父类构造器创建实例,其父类构造器指向了上一级原型,以此内推直到原型指向Object.prototype对象为止。

猜你喜欢

转载自blog.csdn.net/qq_45872039/article/details/129501522