JS专题之继承

栏目: JavaScript · 发布时间: 5年前

内容简介:众所周知,JavaScript 中,没有 JAVA 等主流语言“类”的概念,更没有“父子类继承”的概念,而是通过原型对象和原型链的方式实现继承。于是,我们这一篇讲一讲 JS 中的继承(委托)。JavaScript 是面向对象编程的语言,里面全是对象,而如果不通过继承的机制将对象联系起来,势必会造成程序代码的冗余,不方便书写。

前言

众所周知,JavaScript 中,没有 JAVA 等主流语言“类”的概念,更没有“父子类继承”的概念,而是通过原型对象和原型链的方式实现继承。

于是,我们这一篇讲一讲 JS 中的继承(委托)。

一、为什么要有继承?

JavaScript 是面向对象编程的语言,里面全是对象,而如果不通过继承的机制将对象联系起来,势必会造成程序代码的冗余,不方便书写。

二、为什么又是原型链继承?

好,既然是 OO 语言,那么就加继承属性吧。但是 JS 创造者并不打算引用 class,不然 JS 就是一个完整的 OOP 语言了,而创造者 JS 更容易让新手开发。

后来,JS 创造者就将 new 关键字创建对象后面不接 class,改成构造函数,又考虑到继承,于是在构造函数上加一个原型对象,最后让所有通过 new 构造函数 创建出来的对象,就继承构造函函数的原型对象的属性。

function Person() {
    // 构造函数
    this.name = "jay";
}

Person.prototype = {
    sex: "male"
}

var person1 = new Person();
console.log(person1.name);  // jay
console.log(person1.sex);  // male

所以,就有了 JavaScript 畸形的继承方式:原型链继承~

三、原型链继承

function Parent() {
    this.names = ["aa", "bb", "cc"];
    this.age = 18;
}

function Child() {
    // ...
}

Child.prototype = new Parent();  // 改变构造函数的原型对象

var child1 = new Child();

// 继承了 names 属性
console.log(child1.names);  // ["aa", "bb", "cc"]
console.log(child1.age);   // 18
child1.names.push("dd");
child1.age = 20;
var child2 = new Child();
console.log(child2.names);  // ["aa", "bb", "cc", "dd"]
console.log(child2.age);  // 18

以上例子中,暴露出原型链继承的两个问题:

  1. 包含引用类型数据的原型属性,会被所有实例共享,基本数据类型则不会。
  2. 在创建子类型实例时,无法向父类型的构造函数中传递参数。

四、call 或 apply 继承

function Parent(age) {
    this.names = ["aa", "bb", "cc"]
    this.age = age;
}
function Child() {
    Parent.call(this, 18);
}

var child1 = new Child();

// 继承了 names 属性
console.log(child1.names);  // ["aa", "bb", "cc"]
child1.names.push("dd");
console.log(child1.age);  // 18

var child2 = new Child();
console.log(child2.names);  // ["aa", "bb", "cc"]
console.log(child2.age);  // 18

call 或 apply 的原理是在子类型的构造函数中,“借调”父类型的构造函数,最终实现子类型中拥有父类型中属性的副本了。

call 或 apply 这种继承方式在《JavaScript 高级程序设计》中叫作“借用构造函数(constructor stealing)”,解决了原型链继承中,引用数据类型被所有子实例共享的问题,也能够实现传递参数到构造函数中,但唯一的问题在于业务代码也写在了构造函数中,函数得不到复用。

五、组合继承

组合继承(combination inheritance)也叫作伪经典继承,指的是,前面两种方法:原型链继承和 call 或 apply 继承 组合起来,保证了实例都有自己的属性,同时也能够实现函数复用:

function Parent(age) {
    this.names = ["aa", "bb", "cc"]
    this.age = age;
}

Parent.prototype.sayName = function () {
    console.log(this.names);
}

function Child() {
    Parent.call(this, 18);  // 第一次调用
}

Child.prototype = new Parent();  // 第二次调用:通过原型链继承 sayName 方法
Child.prototype.constructor = Child;  // 改变 constructor 为子类型构造函数

var child1 = new Child();
child1.sayName();   // ["aa", "bb", "cc"]
child1.names.push("dd");
console.log(child1.age);  // 18

var child2 = new Child();
console.log(child2.names);  // ["aa", "bb", "cc"]
console.log(child2.age); 
child2.sayName();  // ["aa", "bb", "cc"]

组合继承将继承分为两步,一次是创建子类型关联父类型原型对象的时候,另一次是在子类型构造函数的内部。是 JS 最常用的继承方式。

六、原型式继承

原型式继承说白了,就是将父类型作为一个对象,直接变成子类型的原型对象。

function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}
 
var parent = {
    age: 18,
    names: ["aa", "bb", "cc"]
};
 

var child1 = object(parent);

// 继承了 names 属性
console.log(child1.names);  // ["aa", "bb", "cc"]
child1.names.push("dd");
console.log(child1.age);  // 18

var child2 = object(parent);
console.log(child2.names);  // ["aa", "bb", "cc", "dd"]
console.log(child2.age); // 18

原型式继承其实就是对原型链继承的一种封装,它要求你有一个已有的对象作为基础,但是原型式继承也有共享父类引用属性,无法传递参数的缺点。

这个方法后来有了正式的 API: Object.create({...})

所以当有一个对象,想让子实例继承的时候,可以直接用 Object.create() 方法。

七、寄生式继承

寄生式继承是把原型式 + 工厂模式结合起来,目的是为了封装创建的过程。

function createAnother(original){ 
    var clone= object(original);    //通过调用函数创建一个新对象
    clone.sayHi = function(){      //以某种方式来增强这个对象
        console.log("hi");
    };
    return clone;                  //返回这个对象
}
 
var person = {
    age: 18,
    names: ["aa", "bb", "cc"]
};
 
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // "hi"

八、 寄生组合式继承

刚才说到组合继承有一个会两次调用父类的构造函数造成浪费的缺点,寄生组合继承就可以解决这个问题。

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); // 创建了父类原型的浅复制
    prototype.constructor = subType;             // 修正原型的构造函数
    subType.prototype = prototype;               // 将子类的原型替换为这个原型
}
 
function SuperType(age){
    this.age = age;
    this.names = ["aa", "bb", "cc"];
}
 
SuperType.prototype.sayName = function(){
    console.log(this.names);
};
 
function SubType(age){
    SuperType.call(this, age);
    this.age = age;
}
// 核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费
inheritPrototype(SubType, SuperType);

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

var child1 = new SubType(22)
child1.sayAge()  // 22
child1.sayName()  // ["aa", "bb", "cc"]

九、ES6 class extends

class Parent {
    constructor(name) {
    this.name = name;
    }
    doSomething() {
            console.log('parent do something!');
    }
    sayName() {
        console.log('parent name:', this.name);
    }
}

class Child extends Parent {
    constructor(name, parentName) {
    super(parentName);
    this.name = name;
    }
    sayName() {
         console.log('child name:', this.name);
    }
}

const child = new Child('son', 'father');
child.sayName();            // child name: son
child.doSomething();        // parent do something!

const parent = new Parent('father');
parent.sayName();   // parent name: father

ES6 的 class extends 本质上是 ES5 的语法糖。

ES6实现继承的具体原理:

class Parent {
}
 
class Child {
}
 
Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}
 
// B 的实例继承 A 的实例
Object.setPrototypeOf(Child.prototype, parent.prototype);
 
// B 继承 A 的静态属性
Object.setPrototypeOf(Child, Parent);

总结

javascript 由于历史发展原因,继承方式实际上是通过原型链属性查找的方式,但正规的叫法不叫继承而叫“委托”,ES6 的 class extends 关键字也不过是 ES5 的语法糖。所以,了解 JS 的原型和原型链非常重要,详情请翻看我之前的文章《JavaScript原型与原型链》

参考:

《JavaScript 高级程序设计》

2019/02/10 @Starbucks

欢迎关注我的个人公众号“谢南波”,专注分享原创文章。

JS专题之继承


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

众声喧哗

众声喧哗

胡泳 / 广西师范大学出版社 / 2008-9 / 35.00元

本书触及了网络政治学中的一个重大话题——网络空间中的私域与公域。随着科技的进步,在信息时代的开端,公与私的含义和边界都出现了不容忽视的游移。《众声喧哗》主要探讨,经由新的共有媒体的作用,传统的公私两分如何在社会和政治的双重压力下产生消长和易位。在这里,公域与私域不能看做结构性的东西,而必须视之为一种流和一种过程。在网络时代,我们既要追求生机勃勃的公共生活,又要保证私人领域一定的自主性。共有媒体也许......一起来看看 《众声喧哗》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器