js实现继承,什么是原型、构造函数、实例和原型的关系,原型链继承的优缺点,组合式继承,寄生式继承。继承的最优解决方案

1 篇文章 0 订阅
1 篇文章 0 订阅

js实现继承的主要方式是原型链,想要理解原型链,首先需要理解原型

下面我们讲解构造函数、实例、原型之间的关系,最后来理解原型链实现继承

  • 原型:
    即原型对象,当我们创建一个函数,该函数都会生成一个prototype属性,这个属性是一个指针,指向原型对象;实例可以共享原型对象上定义的属性和方法;原型对象里也有一个指针constructor指向构造函数;

  • 构造函数:
    构造函数和普通函数的区别:
    1、命名上,构造函数一般命名时候约定首字母大写;
    2、 调用方式上,构造函数使用new操作符调用;

  • 实例
    当我们使用new操作符去调用构造函数会生成一个新的对象,这个新的对象我们称之为构造函数的实例,那么使用new操作符去调用构造函数发生了什么呢;
    1、在内存中创建一个新的对象
    2、这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性
    3、把构造函数中的this的作用域赋值给实例;即this指向这个新创建的实例对象
    4、执行构造函数中的代码,给新的实例对象添加属性
    5、如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象

// 先创建一个构造函数:
const Person = function(name, age){
// 一些构造函数内部的属性:
this.name=name;
this.age =age;
// 定义在原型对象上公共的方法:
Person.prototype.sayname = function() {
    console.log(this.name)
}
}

//调用构造函数创建实例对象:
const p1 = new Person('张三', 22);
const p2 = new Person('李四', 32);

p1.sayname() // 张三
p2.sayname() //李四

以上总结:现在我们知道,实例对象中的Prototype属性被赋值为构造函数的Prototype属性,所以我们所有的实例对象可以直接访问我们的原型对象,那么我们的公共的方法可以定义在我们的原型对象上,节省了内存

  • 回归正题,什么是原型链,如何实现继承
    现在我们新创建了一个构造函数Person2,Person2想要继承Person中的属性和方法如何实现呢,我们知道实例对象中有一个指针prototype可以访问到原型对象。通过这一特性,我们把这个Person的实例赋值给Person2的原型对象,Person2的原型对象中就拥有了Person实例对象中的指针,从而可以访问到Person的原型和Penson构造函数中的属性;
const Person = function(name, age){
this.name=name;
this.age =age;
Person.prototype.sayname = function() {
    console.log(this.name)
}

const Person2 = function(name, age) {
    this.remarks = '我是Person2,我想要继承Person的属性和方法'
}

// 原型链继承:
// Person2的原型对象被赋值为Person的实例;
Person2.prototype = new Person();
// 所以现在Person2的原型对象有了name和age属性还有sayname方法;

const p2 = new Person2('王五', 33)
p2.sayname(); // 王五

}

总结:以上就是原型链实现继承,但是目前原型链继承并不是最优的解决方案,因为有缺陷,我们把Person的所有属性和方法都继承在原型上,原型是供所有实例共享的,如果我们的原型上有引用类型的数据,那么就会导致其中一个实例对引用类型数据做了更改,其他实例在去访问访问到的就是更改后的数据了

const Person = function(name, age){
this.name=name;
this.age =age;
this.friends = ['张强']
Person.prototype.sayname = function() {
    console.log(this.name)
}

const Person2 = function(name, age) {
    this.remarks = '我是Person2,我想要继承Person的属性和方法'
}

// 继承:
Person2.prototype = new Person();
// 继承后对引用类型数据做更改;
p2.friends.push('李丽');

const p1 = new Person();
console.log(p1.friends) // ['张强', '李丽'];
// 此时拿到了p2更改后的数据

}
  • 构造函数和原型链组合实现继承:
    由于上边说的原型中引用数据类型存在的问题,所以我们一般使用组合继承方式;基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。
const Person = function(name, age){
this.name=name;
this.age =age;
this.friends = ['张强']
Person.prototype.sayname = function() {
    console.log(this.name)
}

const Person2 = function(name, age) {
    this.remarks = '我是Person2,我想要继承Person的属性和方法'
    // 重点:盗用构造函数,实现既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性
    Person.call(this, name, age);
}
// 圆形链继承:
Person2.prototype = new Person();
// 继承后对引用类型数据做更改;
p2.friends.push('李丽');

const p1 = new Person();
console.log(p1.friends) // ['张强'];

}

总结:以上我们最终实现了一个还算完美的继承,即继承了公共的方法,又拥有自己的私有属性,但是还不是最完美的解决方案,因为我们其实调用了两次Person构造函数,而且我们的Person2原型上拥有了多余的我们不想要的一些属性,这些属性我们只想在构造函数中去独立继承,其实我们的原型的继承可以优化一下,我们只是想拷贝一个Person原型的副本赋值给我们的Person2,但是现在通过实例赋值的方法,会把Person中构造函数中私有的属性也都赋值给我们的Person2原型中,这是我们不想要的

  • 继承式继承
/* 寄生式组合继承的核心逻辑。这个函数接收两个参数:
 子类构造函数和父类构造函数。在这个函数内部,第一步是创建父类原型的一个副本。
然后给返回的 prototype 对象设置 constructor 属性,解决由于重写原型导致默认 constructor 丢失的问题。最后将新创建的对象赋值给子类型的原型
 */
function inheritPrototype(subType, superType) {
let prototype = object(superType.prototype); // 创建对象 
prototype.constructor = subType; // 增强对象 
subType.prototype = prototype; // 赋值对象
}

const Person = function(name, age){
this.name=name;
this.age =age;
Person.prototype.sayname = function() {
    console.log(this.name)
}

const Person2 = function(name, age) {
    Person.call(this, name, age);
}
inheritPrototype(Person2, Person);

Person2.prototype.sayAge = function() {
  console.log(this.age);
};


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值