资讯专栏INFORMATION COLUMN

《javascript高级程序设计》 继承实现方式

cppprimer / 1746人阅读

摘要:寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部已某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。

这篇本来应该是作为写JS 面向对象的前奏,只是作为《javascript高级程序设计》继承一章的笔记

原型链

code 实现

function SuperType() {
  this.colors = ["red","blue", "green"];
}
function SubType() {
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // ["red","blue", "green","black"]

var instance2 = new SubType();
console.log(instance2.colors); // ["red","blue", "green","black"]

var instance = new SuperType();
console.log(instance.colors); // ["red","blue", "green"]

使用原型链来实现继承,原型实际上会变成另一个类型的实例,于是,原先的实例属性,会变成现在的原型属性了

在创建子类的实例时,不能向父类的构造函数中传递参数

借用构造函数

code 实现继承

function SuperType() {
  this.colors = ["red","blue","green"];
}
function SubType() {
  SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // ["red", "blue", "green", "black"]

var instance2 = new SubType();
console.log(instance2.colors); // ["red", "blue", "green"]

var instance = new SuperType();
console.log(instance.colors); // ["red","blue", "green"]

同样也可以实现参数的传递

function SuperType(name) {
  this.name = name;
}
function SubType(){
  SuperType.call(this, "jack");
  this.age = 29;
}

var instance = new SubType();
console.log(instance.name); // jack
console.log(instance.age); // 29

如果仅仅是借用构造函数,那么将无法避免构造函数模式存在的问题--方法都在构造函数中定义,因此,函数复用也就无从谈起了。而且,在超类型的原型中定义的方法,对子类而言也是不可见的,结果所有类型都只能使用构造函数模式。

组合继承

将原型链和借用构造函数的技术组合到一块,从而发回二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,即通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

code 实现

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

SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
  console.log(this.age);
};

var instance1 = new SubType("jack", 29);
instance1.colors.push("black");
console.log(instance1.colors); //["red", "blue", "green", "black"]
instance1.sayName(); // jack
instance1.sayAge(); // 29

var instance2 = new SubType("allen", 23);
console.log(instance2.colors); // ["red", "blue", "green"]
instance2.sayName(); //allen
instance2.sayAge(); // 23

instanceOfisPrototypeOf 也能够用于识别基于组合继承创建的对象

原型式继承

没有严格意义上的构造函数,通过借助原型,可以基于已有的对象创建新对象,同时还不必因此创建自定义类型

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

object() 函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。从本质上将,object() 对传入其中的对象执行了一次浅复制

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

var person = {
    name:"jack",
    friends:["allen","lucy","van"]
}

var anotherPerson = object(person);
anotherPerson.name = "bob";
anotherPerson.friends.push("steve");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "linda";
yetAnotherPerson.friends.push("shelly")

console.log(person.friends); //["allen", "lucy", "van", "steve", "shelly"]

这种原型式继承,要求你必须有一个对象可以作为另一个对象的基础。如果有这么一个对象的话,可以把他传递给object() 函数,然后再根据具体需求对得到的对象加以修饰即可。

ECMAScript5 通过Object.create() 方法规范花了原型式继承。这个方法接受两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object,create()object() 函数方法的行为相同

在没有必要创建构造函数,而只是想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过,包含引用类型值的属性始终都会共享响应的值,就像使用原型模式一样

寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部已某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。

function createAnother(original){
  var clone = object(original); // 通过调用 object() 函数创建一个新对象
  clone.sayHi = function(){  // 以某种方式来增强对象
    alert("hi");
  };
  return clone; // 返回这个对象
}

在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。前面示范继承模式时使用的object() 函数不是必需的;任何能够返回新对象的函数都使用与此模式。

寄生组合式继承

组合继承是JavaScript 最常用的继承模式;不过也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次父类的构造函数:一次是在创建子类原型的时候,另一次是在子类构造函数内部。

子类最终会包含父类对象的全部实例属性,但我们不得不在调用子类构造函数时重写这些属性。

function SuperType(name) {
    this.name = name;
    this.colors = ["red","blue","green"];
}

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

function SubType(name, age) {
    SuperType.call(this, name); // 第二次调用 SuperType()
    this.age = age;
}

SubType.prototype = new SuperType(); // 第一次调用 SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    console.log(this.age);
};

第一次调用SuperType 构造函数时,SubType.prototype会得到两个属性:namecolors,他们都是SuperType 的实例属性,只不过现在位于SubType 的原型中。当调用SubType 构造函数时,又会调用一次SuperType 构造函数,这一次又在新对象上创建了实例属性namecolors。 于是,这两个属性就屏蔽了原型中的两个同名属性。

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类的原型而调用父类的构造函数,我们所需要的无非就是父类原型的一个副本而已。本质上,就是使用寄生式继承来继承父类型的原型,然后再将结果指定给子类的原型。寄生组合式继承的基本模式如下

function inheritPrototype(subType, superType) {
  var prototype = object(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}

第一步是创建父类原型的一个副本,第二步是为创建的副本添加constructor 属性,从而弥补因重写原型而失去的默认的constructor 属性。第三步是将新创建的对象(即副本)赋值给子类的原型。

function SuperType(name) {
    this.name = name;
    this.colors = ["red","blue","green"];
}

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

function SubType(name, age) {
    SuperType.call(this, name); // 第二次调用 SuperType()
    this.age = age;
}

inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function() {
    console.log(this.age);
};

这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceofisPrototypeOf() 。开发人员普遍认为寄生组合继承是引用类型最理想的继承范式。

优缺点

原型链会修改父类的属性,在创建子类的实例时,不能向父类的构造函数中传递参数

借用构造函数,则没有继承

组合继承(原型继承+借用构造函数) 组合继承最大的问题就是无论什么情况下,都会调用两次父类的构造函数:一次是在创建子类原型的时候,另一次是在子类构造函数内部。

原型式继承,要求你必须有一个对象可以作为另一个对象的基础,
包含引用类型值的属性始终都会共享响应的值,就像使用原型模式一样

寄生式继承
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式

寄生组合式继承 (这是最成熟的方法,也是现在库实现的方法)
第一步是创建父类原型的一个副本,第二步是为创建的副本添加constructor 属性,从而弥补因重写原型而失去的默认的constructor 属性。第三步是将新创建的对象(即副本)赋值给子类的原型。

JS面向对象系列

prototype.js 是如何实现JS的类以及类的相关属性和作用

Mootools.js 是如何实现类,以及类的相关属性和作用

klass 是如何实现JS的类以及类的相关属性和作用

总结:prototype.js,Mootools.js和klass.js 实现类的方法的异同与优劣

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/84547.html

相关文章

  • javascript高级程序设计》笔记:继承

    摘要:继承和前面两篇文章中的知识非常相关,如果对函数创建原理和原型链不熟悉,请猛戳高级程序设计笔记创建对象高级程序设计笔记原型图解继承,通俗的说,就是将自身不存在的属性或方法,通过某种方式为自己所用文章分别介绍原型链继承继承借用构造函数继承组合继 继承和前面两篇文章中的知识非常相关,如果对函数创建原理和原型链不熟悉,请猛戳:《javascript高级程序设计》笔记:创建对象《javascri...

    JerryC 评论0 收藏0
  • 《你不知道的javascript》笔记_对象&原型

    摘要:上一篇你不知道的笔记写在前面这是年第一篇博客,回顾去年年初列的学习清单,发现仅有部分完成了。当然,这并不影响年是向上的一年在新的城市稳定连续坚持健身三个月早睡早起游戏时间大大缩减,学会生活。 上一篇:《你不知道的javascript》笔记_this 写在前面 这是2019年第一篇博客,回顾去年年初列的学习清单,发现仅有部分完成了。当然,这并不影响2018年是向上的一年:在新的城市稳定、...

    seasonley 评论0 收藏0
  • 【10】JavaScript 面向对象高级——继承模式

    摘要:面向对象高级继承模式一原型链继承方式原型链继承流程定义父类型构造函数。缺点无法避免构造函数模式存在的问题方法都在构造函数中定义,无法函数复用。六寄生组合式继承在这里重复一下组合继承的代码组合继承最大的缺点是会调用两次父构造函数。 JavaScript 面向对象高级——继承模式 一、原型链继承 方式1: 原型链继承 (1)流程: ​ 1、定义父类型构造函数。 ​ ...

    0xE7A38A 评论0 收藏0
  • Javascript继承浅析

    摘要:推荐高级程序设计,对类继承有详细介绍。书中涉及继承方式多达数种,意味着继承的灵活性。假设类和类不同公司有不同的公司信息,而同一公司内的员工则需要继承相同的公司信息。组合继承组合继承可以认为是以上两种组合实现。 前言 高级语言基本上都有类的概念,而javascript因为各种原因相对比较特别,并没有明确的class类声明方式(ES6暂不涉及),而是通过构造函数变相实现。推荐《javas...

    Jochen 评论0 收藏0
  • javascript高级程序设计》第六章 读书笔记 之 javascript继承的6种方法

    摘要:继承的是超类型中构造函数中的属性,如上继承了属性,但没有继承原型中的方法。上述造成的结果是子类型实例中有两组超类型的构造函数中定义的属性,一组在子类型的实例中,一组在子类型实例的原型中。 ECMAScript只支持实现继承,主要依靠原型链来实现。与实现继承对应的是接口继承,由于script中函数没有签名,所以无法实现接口继承。 一、原型链 基本思想:利用原型让一个引用类型继承另一个引用...

    孙吉亮 评论0 收藏0

发表评论

0条评论

cppprimer

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<