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

本文深入解析JavaScript中的原型机制,探讨原型链的概念,以及通过六种不同的方式实现继承的过程,包括原型链继承、借用构造函数、组合继承、原型式继承、寄生式继承和寄生组合式继承。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JavaScript 原型、原型链、继承

参考文章:

[1] https://blog.youkuaiyun.com/qq_42019025/article/details/80708446

[2] http://47.98.159.95/my_blog/blogs/javascript/js-base/005.html#_1-%E5%8E%9F%E5%9E%8B%E5%AF%B9%E8%B1%A1%E5%92%8C%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E6%9C%89%E4%BD%95%E5%85%B3%E7%B3%BB

1. 原型是什么?解释原型链

 

在JavaScript中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个prototype属性,这个属性指向函数的原型对象。这个对象的用途是包含特定类型的所有实例共享的属性和方法,即这个原型对象是用来给实例共享属性和方法的。

而每个实例内部都有一个指向原型对象的指针,也就是__proto__,它的指向和生成该实例的构造函数有关,即指向构造函数的prototype对象。

当函数经过new调用时,这个函数就成为了构造函数,返回一个全新的实例对象,这个实例对象有一个__proto__属性,指向构造函数的原型对象。

如果让原型对象等于另外一个类型的实例,该类型的实例的__proto__又指向另一个构造函数原型对象,假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条,也就是所谓的原型链,当这条链条到 object 的时候就到头了,object 的 __proto__ 指向 null。

2. JS实现继承的6种方式

推荐阅读:https://www.cnblogs.com/Grace-zyy/p/8206002.html

2.1 原型链继承

核心: 将父类的实例作为子类的原型

function Father(){
    this.colors = ['blue','yellow'];
    
}

function Son(){
    
}

// Son 继承了 Father
Son.prototype = new Father();

Son.prototype.sleep = function(){
	console.log("睡觉")
}
var instance1 = new Son();
var instance2 = new Son();

instance1.colors.push('black')
console.log(instance1.colors) // [ 'blue', 'yellow', 'black' ]
console.log(instance2.colors) // [ 'blue', 'yellow', 'black' ]

console.log(instance1 instanceof Son) // true
console.log(instance1 instanceof Father) // true

instance1.sleep() // 睡觉
instance2.sleep() // 睡觉


 优点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例。
  2. 父类新增原型方法、原型属性,子类都能访问到。
  3. 简单易用,只要修改子类的原型属性指向父类的实例就行。

缺点:

  1. 无法实现多继承。(每个构造函数只能指定一个原型对象)
  2. 来自原型对象的所有引用类型值的属性是所有实例共享的。(例如数组)
  3. 创建子类实例时,无法向父类构造函数传参。(因为在创建实例之前,就需要给子类的构造函数指定原型实例对象,此时可以传参,但是会影响将来生成的所有实例)

2.2 借用构造函数 

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Father(name){
    this.name = name;
    this.colors = ['red','yellow'];
}

function Son(name){
    // 继承了 Father,同时还传递了名字这个参数
    Father.call(this, name);
    
    // 实例属性
    this.age = 29;
}

var instance1 = new Son('Nicholas');
var instance2 = new Son('John');

console.log(instance1.name); // Nicholas
console.log(instance2.name); // John
instance1.colors.push('blue');  
console.log(instance1.colors, instance2.colors)  
// [ 'red', 'yellow', 'blue' ] [ 'red', 'yellow' ]

优点:

  1. 解决了2.1中,子类实例共享父类引用属性的问题(每个属性都会复制一份,父类的引用属性不会被共享)。
  2. 创建子类实例时,可以向父类传递参数(传递多个也可以)
  3. 可以实现多继承(call多个父类对象,就继承了多个父类对象的属性和方法)

缺点:

  1. 实例并不是父类的实例,只是子类的实例(本质上只是一种复制,实例对象保存了父类实例属性和方法的副本,子类的原型并没有发生改变)
  2. 只能继承父类的实例属性和方法,不能继承父类的原型属性/方法
  3. 无法实现函数复用(函数都是单独复制的一份),每个子类都有父类实例函数的副本,影响性能。

2.3.组合继承

核心:通过利用原型链继承和借用构造函数继承实现了组合继承。

通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Father(name){
    this.colors = ['blue','yellow'];
    this.name = name;
}

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

function Son(name, age){
    Father.call(this, name);
    this.age = age;
}


Son.prototype = new Father();
Son.prototype.constructor = Son;
Son.prototype.sayAge = function(){
	console.log(this.age);
}


var instance1 = new Son('Nicholas', 29);
instance1.colors.push('black');
console.log(instance1.colors); // [ 'blue', 'yellow', 'black' ]
instance1.sayName(); // Nicholas
instance1.sayAge(); // 29

var instance2 = new Son('John', 26); 
console.log(instance2.colors); // [ 'blue', 'yellow' ]
instance2.sayName(); // John
instance2.sayAge(); // 26

优点:

  1. 弥补了方式2.2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
  2. 既是子类的实例,也是父类的实例,可以用 instanceof 方法来检测,因为使用了原型继承。
  3. 不存在引用属性共享问题,因为 call 方法把父类属性都复制了一份,包括引用属性,实例中包含该属性,就不会到原型链上去寻找。
  4. 可向父类构造函数传参(通过call 方法)
  5. 函数可复用

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

2.4 原型式继承

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 

var father = {
	name:'Nicholas',
	colors:['blue','yellow']
}

var son1 = Object.create(father)
son1.name = "Greg"
son1.colors.push('black')

var son2 = Object.create(father)
son2.name = "Linda"
son2.colors.push('red')

console.log(son1.colors) // [ 'blue', 'yellow', 'black', 'red' ]
console.log(son2.colors) // [ 'blue', 'yellow', 'black', 'red' ]

console.log(son1.name) // Greg
console.log(son2.name) // Linda


// Object.create 内部做的事情
Object.create = function (obj) {
    function F() {}
    F.prototype = obj;

    return new F();
};

2.5 寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路

var father = {
	name:'Nicholas',
	colors:['blue','yellow']
}

// 进行了一次浅复制
function object(o){
	function F(){}
	F.prototype = o;
	return new F();
}

function createClone(original){
	var clone = object(original); // 通过调用函数来创建一个新的对象
	clone.sayHi = function(){ // 使用某种方式来增强这个对象
		console.log('hi');
	}
	return clone; // 返回这个对象
}

var clone1 = createClone(father) // 克隆了原对象的所有实例方法属性,并增加了自己的方法
clone1.sayHi() // hi

这个例子中的代码基于 father 这个原型对象返回了一个新对象,clone,这个新对象不仅有 father 的所有属性和方法,而且还有自己的sayHi方法。使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。

2.6 寄生组合式继承

使用了寄生继承和组合继承进行结合的方法。

优点:解决了组合继承中调用两次父类构造函数的问题。

缺点:构造比较复杂。

function inheritPrototype(subType, superType){

    var p = Object.create(superType.prototype)
    
    p.constructor = subType

    subType.prototype = p

}






function Father(name){
    this.name = name
    this.colors = ["red","blue","green"]
}
Father.prototype.sayName = function(){
    console.log(this.name)
}
function Son(name,age){
    Father.call(this,name)
    
    this.age = age
}

inheritPrototype(Son, Father)

Son.prototype.sayAge = function () {
    alert(this.age)
}

说白了就是让子类继承于父类的原型,避免在生成子类的实例的时候重复调用当前父类的构造函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值