资讯专栏INFORMATION COLUMN

JavaScript之深入各种继承

tomlingtm / 308人阅读

摘要:通常有这两种继承方式接口继承和实现继承。理解继承的工作是通过调用函数实现的,所以是寄生,将继承工作寄托给别人做,自己只是做增强工作。适用基于某个对象或某些信息来创建对象,而不考虑自定义类型和构造函数。

一、继承的概念

继承,是面向对象语言的一个重要概念。
通常有这两种继承方式:接口继承实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。

《JS高程》里提到:“由于函数没有签名,在‘ECMAScript’中无法实现接口继承。”

等等,函数签名是什么东西?据MDN文档定义如下:

函数签名(类型签名、方法签名)定义了函数或方法的输入与输出。

签名可包含以下内容:

参数 及参数的 类型

一个的返回值及其类型

可能会抛出或传回的异常

该方法在 面向对象程序中的可用性方面的信息(如public、static或prototype)。

看起来好复杂啊,我们换成强类型的语言的角度会不会更好理解?
譬如,C的函数签名就是我们熟悉的函数声明:

int func(double d);

此时:参数名为d,参数类型为double,返回值为func(d),返回值类型为int
再如,Java的函数签名:

public static void main(String[] args)

此时:参数名为args,参数类型为String [],返回值类型为void所以该方法没有返回值,访问修饰符public表示该方法是公有方法,static表示该方法是一个类方法而非实例方法……

现在,我们知道函数签名是怎么回事了。那么,接口继承又是什么东西?相信大家会遥想起Java中的interfaceimplements等……在此就不班门弄斧了。

我们知道,JavaScript是类型松散的语言,不像Java它们有严格的变量类型检查。所以,JS的函数才没有签名,才无法实现接口继承。

那么,JS的实现继承是怎么回事呢?

二、JS的继承

原型链的基本模式
理解:通过创建SuperType的实例,并将该实例赋给SubType.prototype,来实现SubType继承SuperTypeinstance的原型指向SubTypeSubType的原型指向SuperTypeSuperType的原型指向Object,如此构成了原型链。
缺点:对象实例共享所有继承的属性和方法,不适宜多带带适用

function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function() {
    return this.property;
};
function SubType() {
  this.subproperty = false;
}
//SubType继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
  return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());  //true

于是,有下一个招式,来解决包含引用类型值的原型属性会被所有实例共享的弱点。

借用构造函数
理解:“借用”超类型构造方法,在新的子类型对象上执行超类型函数定义的所有对象初始化代码
适用:解决超类型的引用类型值被所有子类型对象实例共享,而且子类型可向超类型传参
缺点:不能做到函数复用,从而降低效率,不适宜多带带适用

function SuperType(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"]; //引用类型值
}
function SubType() {
  SuperType.call(this, "A"); //继承SuperType “借用”超类型的构造函数 并传参
  this.age = 20;    //实例属性
}
var instance1 = new SubType();    //instance1.name: "A", instance1.age: 20
instance1.colors.push("black"); //instance1.colors: "red, blue, green, black"
var instance2 = new SubType();  //instance2.colors: "red, blue, green"

接下来,组合技能出大招!

组合继承
理解:将原型链和借用构造函数组合到一起,通过原型链来继承共享的原型属性和方法,通过借用构造函数来继承实例属性。
适用:最常用的继承模式。
缺点:调用两次超类型构造函数,在SubType上创建了多余的属性,造成超类型对象的实例属性的重写

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);   //继承属性 第二次调用超类型构造函数 新实例得到两个实例属性name,colors
  this.age = age;
}
SubType.prototype = new SuperType();  //继承方法 第一次调用超类型构造函数 SubType.prototype得到两个实例属性name,colors
SubType.prototype.sayAge = function() {
  alert(this.age);
};

var instance1 = new SubType("妹妹", 18);
instance1.colors.push("black");
alert(instance1.colors);  //"red, blue, green, black"
instance1.sayName();  //"妹妹"
instance1.sayAge();  //18

var instance2 = new SubType("弟弟", 20);
alert(instance2.colors);  //"red, blue, green"
instance2.sayName();  //"弟弟"
instance2.sayAge();  //20

这是打boss的大招,那我怎么对付小怪?

原型式继承
理解:本质是对给定对象的浅复制
适用:不必预先定义构造函数来实现继承,只想让一个对象与另一个对象保持类似
缺点:引用类型值的属性被共享,如同原型模式一样

function object(o) {    //对o进行浅复制
  function F() {}    //创建一个临时性的构造函数
  F.prototype = o;    //将传入的对象o作为F的原型
  return new F();    //返回F的新实例
}

var person = {
  name: "A",
  friends: ["B", "C", "D"]
};
var anotherPerson = object(person);    // Object.create(person)
anotherPerson.name = "E";
anotherPerson.friends.push("F");
var yetAnotherPerson = object(person);    //Object.create(person)
yetAnotherPerson.name = "G";
yetAnotherPerson.friends.push("H");
alert(person.friends);  //"B, C, D, F, H" 引用类型值的属性被共享啦

注意:Object.create()方法的第二个参数,新对象的额外属性的对象,会覆盖原型对象上的同名属性。

var anotherPerson = Object.create(person, {
  name: { value: "E" }
});
alert(anotherPerson.name);   // "E"

打了这么久,能不能让小招也升级(封装)一下啊?

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。

理解:继承的工作是通过调用函数实现的,所以是“寄生”,将继承工作寄托给别人做,自己只是做增强工作。
适用:基于某个对象或某些信息来创建对象,而不考虑自定义类型和构造函数。
缺点:不能做到函数复用,从而降低效率

 function createAnother(original) {
   var clone = object(original); //通过调用函数创建一个新对象
   clone.sayHi = function() {  //以某种方式来增强这个对象
     alert("hi");
   }
   return clone;
 }
 var person = {
   name: "A",
   friends: ["B", "C", "D"]
 };
 var anotherPerson = createAnother(person);  //不仅有person所有属性方法,还有自己的sayHi方法
 anotherPerson.sayHi();  //"hi"

经验攒足,我要把之前打boos的组合大招升到满级!

寄生组合继承
理解:通过借用构造函数来继承属性,通过原型链的混用形式来继承方法。用寄生式继承来继承超类型的原型,再将增强后的结果指定给子类型的原型。
适用:引用类型最理想的继承模式,高效,只调用了一个SuperType构造函数

function inheritPrototype(subType, superType) {
  var prototype = object(superType.prototype); //创建对象 超类型原型的副本
  prototype.constructor = subType;  //增强对象 弥补因重写原型而失去默认的constructor属性
  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);
};

三、ES6的继承
extends关键字用来创建一个普通类或者内建对象的子类。
class A {
    ...
}
class B extends A {
    ...
}

其中,

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

因为extends实现了:

Object.setPrototypeOf(B.prototype, A.prototype);    //B.prototype.__proto__ = A.prototype;

Object.setPrototypeOf(B, A);    //B.__proto__ = A

extends更具体的实现方法参见面试官问:JS的继承,在此就不班门弄斧了~

完~若有不足,请多指教,不胜感激!

以上代码借鉴于《JS高级程序设计》

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

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

相关文章

  • JavaScript深入继承的多种方式和优缺点

    摘要:深入系列第十五篇,讲解各种继承方式和优缺点。优点融合原型链继承和构造函数的优点,是中最常用的继承模式。寄生组合式继承为了方便大家阅读,在这里重复一下组合继承的代码组合继承最大的缺点是会调用两次父构造函数。 JavaScript深入系列第十五篇,讲解JavaScript各种继承方式和优缺点。 写在前面 本文讲解JavaScript各种继承方式和优缺点。 但是注意: 这篇文章更像是笔记,哎...

    JackJiang 评论0 收藏0
  • 深入理解JavaScript

    摘要:深入之继承的多种方式和优缺点深入系列第十五篇,讲解各种继承方式和优缺点。对于解释型语言例如来说,通过词法分析语法分析语法树,就可以开始解释执行了。 JavaScript深入之继承的多种方式和优缺点 JavaScript深入系列第十五篇,讲解JavaScript各种继承方式和优缺点。 写在前面 本文讲解JavaScript各种继承方式和优缺点。 但是注意: 这篇文章更像是笔记,哎,再让我...

    myeveryheart 评论0 收藏0
  • JavaScript深入从原型到原型链

    摘要:深入系列的第一篇,从原型与原型链开始讲起,如果你想知道构造函数的实例的原型,原型的原型,原型的原型的原型是什么,就来看看这篇文章吧。让我们用一张图表示构造函数和实例原型之间的关系在这张图中我们用表示实例原型。 JavaScript深入系列的第一篇,从原型与原型链开始讲起,如果你想知道构造函数的实例的原型,原型的原型,原型的原型的原型是什么,就来看看这篇文章吧。 构造函数创建对象 我们先...

    Songlcy 评论0 收藏0
  • JavaScript深入系列15篇正式完结!

    摘要:写在前面深入系列共计篇已经正式完结,这是一个旨在帮助大家,其实也是帮助自己捋顺底层知识的系列。深入系列自月日发布第一篇文章,到月日发布最后一篇,感谢各位朋友的收藏点赞,鼓励指正。 写在前面 JavaScript 深入系列共计 15 篇已经正式完结,这是一个旨在帮助大家,其实也是帮助自己捋顺 JavaScript 底层知识的系列。重点讲解了如原型、作用域、执行上下文、变量对象、this、...

    fxp 评论0 收藏0
  • JavaScript深入创建对象的多种方式以及优缺点

    摘要:深入系列第十四篇,讲解创建对象的各种方式,以及优缺点。也就是说打着构造函数的幌子挂羊头卖狗肉,你看创建的实例使用都无法指向构造函数这样方法可以在特殊情况下使用。 JavaScript深入系列第十四篇,讲解创建对象的各种方式,以及优缺点。 写在前面 这篇文章讲解创建对象的各种方式,以及优缺点。 但是注意: 这篇文章更像是笔记,因为《JavaScript高级程序设计》写得真是太好了! 1....

    Terry_Tai 评论0 收藏0

发表评论

0条评论

tomlingtm

|高级讲师

TA的文章

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