JS | JS中原型链的常见继承模式

目录

什么是原型链 ?

原型链的七种常见继承模式 

一、原型链继承

二、构造函数继承

三、组合继承

四、原型式继承

五、寄生式继承

六、寄生组合式继承(最理想)

◆ 寄生组合式继承(改进)

七、克隆原型链继承


什么是原型链 ?

每个对象拥有一个原型对象,对象以其原型为模板,从原型继承属性和方法。当然 原型对象 也可能拥有原型,并从中继承属性和方法,依此类推,这种关系被称为原型链。

准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。

参考:【JS高级】JS面向对象三大特性之继承-优快云博客

原型链的七种常见继承模式 

一、原型链继承

利用原型链来实现继承,父类的一个实例作为子类的原型

function child(){
  this.xx = 'xx'; //子类自己的定义
}
child.prototype = new parent();
//这里new parent()父类对象并没有constructor属性,需要后面加上
child.prototype.constructor = child

原理: 子类的原型对象指向父类的实例, 当子类实例找不到属性和方法时,会沿着原型链往上找。

优点:简单,既是子类实例也是父类实例,父类新加原型方法或者属性,子类都能访问到

缺点:

① 不能实现多继承,所有子类的实例的原型都共享一个父类实例属性和方法

② 不能传参 

例子1:

function Show() {
  this.name = "run";
}

function Run() {
  this.age = "20"; //Run继承了Show,通过原型,形成链条
}
Run.prototype = new Show();
var show = new Run();
alert(show.name); //结果:run

例子2: 

function SuperType() {
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.Fun = function () {};
function SubType() {}
//继承了SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"

优点:能通过 instanceOfisPrototypeOf 的检测

注意:给原型添加方法的语句一定要放在原型替换SubType.prototype = new SuperType();之后

缺点:

(1) SuperType中的属性(不是方法)也变成了SubType的prototype中的公用属性,如上面例子中的color属性,可以同时被instance1和instance2修改。

(2) 创建子类型的时候,不能向父类型的构造函数中传递参数。

二、构造函数继承

通过使用call、apply方法可以在新创建的对象上执行构造函数,用父类的构造函数来增加子类的实例。

// 创建子类、添加子类属性。
function arrange(name) {
  Person.call(this, name); // 执行父构造,将This指向本身,拉取父私有属性;
}

原理: 子类的构造函数中执行父类的构造函数,并且改变子类的this绑定。

优点:简单,直接父类的属性和方法

缺点:无法继承原型链上的属性方法 

构造函数继承(对象冒充继承):为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。

function Box(age) {
  this.name = ["Lee", "Jack", "Hello"];
  this.age = age;
}
function Desk(age) {
  Box.call(this, age); //对象冒充,给超类型传参
}
var desk = new Desk(200);
alert(desk.age); //200
alert(desk.name); //['Lee','Jack','Hello']
desk.name.push("AAA"); //添加的新数据,只给 desk
alert(desk.name); //['Lee','Jack','Hello','AAA']

例子:

function SuperType() {
  this.colors = ["red", "blue", "green"];
}
function SubType() {
  //继承了SuperType
  SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
function SuperType(name) {
  this.name = name;
}
function SubType() {
  //继承了SuperType,同时还传递了参数
  SuperType.call(this, "Nicholas"); //实例属性
  this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29

原理:在子类型构造函数的内部调用超类型构造函数

优点:解决了superType中的私有属性变公有的问题,可以传递参数

缺点:方法在函数中定义,无法得到复用

三、组合继承

组合继承(原型链继承+构造函数继承):借用构造函数虽然解决了刚才两种问题, 但没有原型, 复用则无从谈起。 所以,我们需要原型链+借用构造函数的模式,这种模式成为组合继承。

function Box(age) {
  this.name = ["Lee", "Jack", "Hello"];
  this.age = age;
}
Box.prototype.run = function () {
  return this.name + this.age;
};
function Desk(age) {
  Box.call(this, age); //对象冒充
}
Desk.prototype = new Box(); //原型链继承
var desk = new Desk(100);
alert(desk.run());

例子: 

function SuperType(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
  alert(this.name);
};
function SubType(name, age) {
  SuperType.call(this, name); //借用构造函数继承属性,二次调用
  this.age = age;
}
SubType.prototype = new SuperType(); //借用原型链继承方法,一次调用
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
  alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

优点:继承前两者的优点,能通过 instanceOf 和 isPrototypeOf 的检测

缺点:两次调用父构造器函数,浪费内存。

四、原型式继承

这种继承借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型。

function obj(o) {
  //传递一个字面量函数
  function F() {} //创建一个构造函数
  F.prototype = o; //把字面量函数赋值给构造函数的原型
  return new F(); //最终返回出实例化的构造函数
}
var box = {
  //字面量对象
  name: "Lee",
  arr: ["哥哥", "妹妹", "姐姐"],
};
var box1 = obj(box); //传递
alert(box1.name);
box1.name = "Jack";
alert(box1.name);
alert(box1.arr);
box1.arr.push("父母");
alert(box1.arr);
var box2 = obj(box); //传递
alert(box2.name);
alert(box2.arr); //引用类型共享了

例子: 

function object(o){
  function F(){}
  F.prototype=0;
  retrurn new F();
}

使用场合:没必要构建构造函数,仅仅是想模拟一个对象的时候

五、寄生式继承

创建一个仅仅用于封装继承过程的函数,然后在内部以某种方式增强对象,最后返回对象。

function child(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}
function createSubObj(superInstance) {
  var clone = child(superInstance);
  return clone;
}

优点:原型式继承的一种拓展。

缺点:依旧没有类的概念。

例子:

function createAnother(original) {
  var clone = object(original); //通过调用函数创建一个新对象
  clone.sayHi = function () {
    //以某种方式来增强这个对象
    alert("hi");
  };
  return clone; //返回这个对象
}
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"],
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

缺点:方法在函数中定义,无法得到复用

六、寄生组合式继承(最理想)

将父类原型对象直接赋值给子类的构造函数,再将空属性的构造函数实例赋值给子类原型对象

function Parent(name){
  this.name = name
}
Parent.prototype.getName = function(){
  return this.name
}
// 创建子类、添加子类属性。
function Child(name){
  Parent.call(this, name)// 执行父构造,将This指向本身,拉取父私有属性;
}

 // 子类的原型对象指向父类的原型对象
Child.prototype = Parent.prototype
 // 将constructor指向本身,保证原型链不断。
Child.prototype.constructor = Child

优点:完美实现继承,解决了组合式继承带两份属性的问题; new Child的时候不用每次都 new Parent

缺点:子类的prototype添加方法会影响 父类的prototype;

例子1:

结合寄生式继承和组合式继承,完美实现不带两份超类属性的继承方式

function inheritPrototype(Super,Sub){
  var superProtoClone = Object.Create(Super.prototype)
  superProtoClone.constructor = Sub
  Sub.prototype = Super
}
function Sub(){
  Super.call()
  Sub.property = 'Sub Property'
}
inheritPrototype(Super,Sub)

例子2:

寄生组合式继承解决了两次调用的问题,组合式继承就会有两次调用的情况。

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

function inheritPrototype(subType, superType) {
  var prototype = object(superType.prototype); //创建对象
  prototype.constructor = subType; //增强对象
  subType.prototype = prototype; //指定对象
}

优点:完美实现继承,解决了组合式继承带两份属性的问题。

缺点:过于繁琐,故不如组合继承。

◆ 寄生组合式继承(改进)

将父类原型对象直接赋值给子类的构造函数,再将空属性的构造函数实例赋值给子类原型对象

function Parent(name){
  this.name = name
}
Parent.prototype.getName = function(){
  return this.name
}
// 创建子类、添加子类属性。
function Child(name){
  Parent.call(this, name)// 执行父构造,将This指向本身,拉取父私有属性;
}

 // 子类的原型对象指向父类的原型对象
// 浅拷贝 解决问题
Child.prototype = Object.create(Parent.prototype)
 // 将constructor指向本身,保证原型链不断。
Child.prototype.constructor = Child

优点:完美实现继承,解决了组合式继承带两份属性的问题; new Child的时候不用每次都 new Parent 

例子:

function inheritPrototype(subType, superType) {
  var prototype = object(superType.prototype); //创建对象
  prototype.constructor = subType; //增强对象
  subType.prototype = prototype; //指定对象
}
function SuperType(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
  alert(this.name);
};
function SubType(name, age) {
  SuperType.call(this, name);
  this.age = age;
}
inheritPrototype(SubType, SuperType); //实现继承
SubType.prototype.sayAge = function () {
  alert(this.age);
};

七、克隆原型链继承

将等待继承的原型对象克隆,再赋值给继承的原型对象

// 创建子类、添加子类属性。
function arrange(name) {
  this.name = name;
  this.goShop = function (food) {
    console.log(name + "叫你去买" + food);
  };
}
// 创建克隆类型
function Clone(obj) {
  for (var key in obj) {
    this[key] = typeof obj[key] == "object" ? new Clone(obj[key]) : obj[key];
  }
}
// 使用Clone构造函数继承原型
arrange.prototype = new Clone(Person.prototype);
// 将constructor指向本身,保证原型链不断。
arrange.prototype.constructor = arrange;

//创建arrange新实例,也是Clone实例,却不在是Person实例;
var newObj = new arrange("李四");
console.log(newObj instanceof Person); //false
console.log(newObj instanceof arrange); //true
console.log(newObj instanceof Clone); //true
// 克隆成功,可以访问克隆对象的原型对象;
console.log(newObj);
console.log(newObj.name); // 李四
newObj.eat("苹果"); // 李四正在吃:苹果
newObj.goShop("香蕉"); // 李四叫你去买香蕉

优点:直接通过对象生成一个继承该对象的对象。

缺点:不是类式继承,而是原型式基础,缺少了类的概念。


参考资料

JS原型链及继承的几种方式 - 编程之家 | js原型继承的几种方式 - 全网最菜的鸟 - 博客园

原型的继承,几种常用的继承方法-优快云博客 | 常见原型继承的四种方式-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

儒雅的烤地瓜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值