JavaScript面向对象-继承

8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

一、继承的概念

继承是面向对象软件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”也可以称“B是A的超类”。
继承的目的是重用代码,即继承的一方可以使用被继承一方的属性和方法,这样做可以提高效率和节省资源。
在继承的概念中反复的提到了类,而JS中没有类,并不是严格的面向对象的语言,在JS中的继承不是直观的A extends B这样简单,本质上它的继承是通过原型链实现的。
JS中一切皆是对象,和我们的大千世界一样,对象并不是孤立存在的,有区别也有联系,JS中的所有对象都有8888888888888888888888888888888888888888一个属性叫原型链,顺着原型链可以追溯任何一个对象的祖宗十八代,直到Object。
在这里插入图片描述
上图中,数组实例有3个自有属性(实例属性),在它的__proto__链条上的属性是继承属性(原型属性)。
自有属性可以看成是一个对象的个性,而继承属性可以看成这类对象的共性。
当实例化一个对象时,会执行其构造函数给自有属性赋值,__proto__属性去引用其构造函数属性prototype对象的属性和方法。

二、JS中实现继承

在JS中,继承的目的就是让一个对象能够使用另一个对象的方法,如下图有两个对象:
在这里插入图片描述
我们来模拟继承,使Student对象能够使用Person对象的属性和方法,可以有两种方案,使用方案1或方案2或结合两个方案,想办法规避不同方案的缺点之后形成了JS解决继承问题的各种方法。
方案1:把Person的属性和方法作为自有属性添加到子类上
方案2:把Person添加到Student的原型链上,Person的属性和方法称为其原型属性(继承属性)

思路一:构造函数继承

1、借用构造函数继承(对象冒充)

这是方案1的解决办法,把父类的属性和方法想办法绑定到子类上,思路是使用call或apply方法,调用父类构造函数来实现继承。

function Person(name,age){
  this.name=name;
  this.age=age;
};
Person.prototype.say=function () {}; 
Person.prototype.walkingMode='直立行走';
function Student(sid,name,age) {
  //使用call方法借用Person构造函数,相当于拷贝一份Person的自有属性成为Student的自有属性
  Person.call(this,name,age);
  this.sid=sid;
};
Student.prototype.sing=function(){};
console.log(new Student('1001','张三',18));

在这里插入图片描述

  • 这种方法只能继承构造函数里的自有属性,无法继承构造函数原型对象中的属性和方法,继承到的属性又称为子类实例的自有属性;
  • 可以继承多个构造函数属性;
  • 在子类实例中可向父类传参。
2、原型链继承

这是方案2的解决办法,原型链继承是把父类的添加到子类的原型链上,具体的方案是让子类的原型指向父类的一个实例,这样子类的实例就继承父类了,实际上相当于重写了子类(构造函数的)prototype的值。
假设有两个类(构造函数),Student和Person,我们想要函数Student的属性指向Person的实例。
在这里插入图片描述

function Person(name,age){
  this.name=name;
  this.age=age;
};
Person.prototype.say=function () {};
Person.prototype.walkingMode='直立行走';
function Student(sid,name,age) {
  this.sid=sid;
};
//重写Student的prototype属性这样会导致Student的原型中的constructor属性丢失
Student.prototype=new Person();
//把constructor添加回去,缺点是constructor属性成为可枚举属性
Student.prototype.constructor=Student;
Student.prototype.sing=function(){};
console.log(new Student('1001','张三',18));

在这里插入图片描述
这种方法使得子类的实例能够继承父类构造函数的所有属性(包括构造函数及其原型的属性)。
缺点是:

  1. 子类的实例无法向父类构造函数传参。
  2. 只能继承一个父类的属性(单一继承)。
3、组合继承

组合继承是组合原型链继承和借用构造函数继承来实现的,也就是分两个步骤:1、把父类的添加到子类的原型链上,2、把父类的添加到子类的原型链上。

function Person(name,age){
  this.name=name;
  this.age=age;
};
Person.prototype.say=function () {};
Person.prototype.walkingMode='直立行走';
function Student(sid,name,age) {
  //使用call方法借用Person构造函数,相当于拷贝一份Person的自有属性成为Student的自有属性
  Person.call(this,name,age);
  this.sid=sid;
};

//重写Student的prototype属性这样会导致Student的原型中的constructor属性丢失
Student.prototype=new Person();
//把constructor添加回去,缺点是constructor属性变的可枚举
Student.prototype.constructor=Student;
Student.prototype.sing=function(){};
console.log(new Student('1001','张三',18));

在这里插入图片描述
组合继承实现了既可以继承父类的所有方法和属性,又能够向父类构造函数传参,但有一个缺点是子类实例化时,会在实例对象和原型对象中重复定义父类构造函数中的属性或方法。

4、寄生组合继承

这种方式是对组合继承的一种改良,思路是利用一个空的构造函数,这个构造函数就好像父类的代理,让它的原型指向父类的原型,子类的原型指向空构造函数的实例。

function Person(name,age){
  this.name=name;
  this.age=age;
};
Person.prototype.say=function () {};
Person.prototype.walkingMode='直立行走';
function Student(sid,name,age) {
  //使用call方法借用Person构造函数,相当于拷贝一份Person的自有属性成为Student的自有属性
  Person.call(this,name,age);
  this.sid=sid;
};
//定义一个空的构造函数
var F = function () {};
F.prototype = Person.prototype;
Student.prototype = new F();
Student.prototype.constructor=Student;
Student.prototype.sing=function(){};
console.log(new Student('1001','张三',18));

在这里插入图片描述
组合继承、寄生组合继承是比较常用的继承方法, 观察对比图,不难发现寄生组合继承方式打印出的对象似乎就是我们想要的样子。
寄生组合继承的代码进一步改造,封装一个绑定继承关系的函数,打印出的子类实例是一样的。
PS:extend函数代码是在阮一峰老师那抄来的,阅读详细

function Person(name,age){
  this.name=name;
  this.age=age;
};
Person.prototype.say=function () {};
Person.prototype.walkingMode='直立行走';
function Student(sid,name,age) {
  //使用call方法借用Person构造函数,相当于拷贝一份Person的自有属性成为Student的自有属性
  Person.call(this,name,age);
  this.sid=sid;
};
//绑定继承关系的函数
function extend(Child, Parent) {
  var F = function(){};
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
  Child.uber = Parent.prototype;
}
extend(Student,Person);
console.dir(Student);
Student.prototype.sing=function(){};
console.log(new Student('1001','张三',18));

我们来看一下执行了extend函数后,Student构造函数的样子,通过uber属性与父类的原型建立了联系。
在这里插入图片描述

思路二:复制继承

前面的方法中,无一例外使用到了构造函数,用它来模拟类和继承,如果我们的对象直接是用字面量声明的的,怎么让它去继承另一个对象的属性呢?

思路上父对象的属性,全部拷贝给子对象

浅拷贝

1、Object.create() 拷贝父对象的属性使其成为子对象的继承属性

Object.create() 继承指定的对象的属性和方法去创建一个新的对象这种方法,如果修改父对象的属性,子对象也会跟着修改。

var p={
  name:'张三',
  age:18,
  say:function () {}
};
var s=Object.create(p);
s.sid='1001';
console.log(s);

在这里插入图片描述

2、Object.assign() 拷贝父对象的属性使其成为子对象的自有属性

Object.assign() 将源对象的所有可枚举复制到目标对象
Object.assign的第一个参数是目标对象,以一或多个源对象作为参数,将源对象的所有可枚举复制到目标对象。

var p={
  name:'张三',
  age:18,
  family:['mother','father'],
  say:function () {}
};
var s={
  sid:'1001'
}
var s=Object.assign(s,p);
console.log(s);

在这里插入图片描述
这种方式得到的属性是自有属性,似乎修改子对象的属性不会影响到父对象,但如果属性是引用类型时,属性有被从外部篡改的可能。
比如,我这样修改子对象的family属性,父对象的family属性也会被修改

s.family.push('sister');

深拷贝

利用JSON对象的两个方法stringify和parse,来实现深拷贝。

  • JSON.stringify方法将JS对象转换成字符串对象
  • JSON.parse方法将字符串对象转换成JS对象
var p={
  name:'张三',
  age:18,
  family:['mother','father'],
  say:function () {}
};
var s = JSON.parse(JSON.stringify(p));
s.sid='1001';
s.family.push('sister');
console.log(p);
console.log(s);

利用JSON对象的两个方法stringify和parse实现深拷贝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值