# 原型与继承

作者: 多叉树;转载须注明出处

我们在认识对象一文中介绍了类和对象实例的关系

这篇我们主要关注 JS是将类和对象之间关联起来的

即使我们没有学过Java、C++等 语言,我们也一定听说过 Java、C++ 等是面向对象的(Object-Oriented)的编程语言。JavaScript 也是一种面向对象的编程语言, 由于实现方式的怪异, 很多时候,我们也会听到很多人说它是基于对象(Object-Based)的编程语言。我们这里不过多的讨论这些概念,本文更多的关注 JavaScript 的面向对象是如何实现的, 刚兴趣的小伙伴可以自行搜索

# 两个特征

在JS世界中,实现对象的两个必要条件

  1. 构造函数 - 定义不同事物之间的边界, 前面文章有介绍过
  2. 原型链

# 原型链

原型链的一大作用就是用来实现 面向对象概念中的一个特别重要的概念: 继承

继承简单的理解 就是为了代码的复用, 比如作为一个程序员,再设计一款程序的时候,我希望某些属性和方法能够复用, 对于其他语言来讲, 有很多方式,比如定义静态(static)类型、或者定义父类。

但是JavaScript 没有区分 这些, JavaScript 用原型实现了继承,或者说叫代码复用。

原型链我们可以拆开理解

  1. 原型

每个函数都有一个 prototype 的引用, 这个引用指向原型对象。

对于原型对象来说, 它有一个 consturctor 属性指向 函数。

原型对象的出现为多个实例共享属性和方法提供了可能。

Untitled

<script>
    function People(name, sex) {
        this.name = name;
        this.sex = sex;
    }
    console.log(People.prototype);
    console.log(typeof People.prototype)
		// true
    console.log(People.prototype.constructor === People)
  </script>

# 原型链的继承

我们都知道,对象是可以通过 new 构造函数() 来创建, 那么创建出的对象也会带有一个 proto 的属性指向原型对象

Untitled

<script>
    function People(name, age, sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    var xiaoLuo = new People('小罗', 12, '男');

    // true
    console.log(xiaoLuo.__proto__ == People.prototype);

       
</script>

一些公用的属性和方法就可以放在 原型对象当中,虽然在 原型链中定义了属性,但是 子类同样也能方位这些属性和方法。

子类既能继承原型对象中的方法和属性,又能重写、新增自己的属性,这就叫做继承

Untitled

<script>
    function People(name, age, sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    People.prototype.nation = '中国';

    People.prototype.nationFun = function() {
        console.log(this.nation);
    }
    var xiaoLuo = new People('小罗', 22, '男');

    var xiaoHong = new People('小红', 22, '女');

    // 子类可以调用原有的属性
    console.log(xiaoLuo.nation);
    xiaoLuo.nationFun();

    // 子类可以调用原有的属性
    console.log(xiaoHong.nation);
    xiaoHong.nationFun();

    // true
    console.log(xiaoLuo.nationFun == xiaoHong.nationFun);

</script>

我们在有些场景下需要区分某些属性是属于对象,还是属于上层的原型对象

站在 对象的角度来说, 在有一些场景下需要区分 是否是定义在 自己 这里的属性或方法 ,而不是定义在公共原型对象上, 这里就需要一个方法 hasOwnProperty, 返回true 表示是自己的属性,否则为false

xiaoLuo.hasOwnProperty('name'); // true
xiaoLuo.hasOwnProperty('age'); // true
xiaoLuo.hasOwnProperty('sex'); // true
xiaoLuo.hasOwnProperty('nation'); // false

而在另一些场合中, 可以使用 in 关键子 提前知道 某个对象 的方法或者属性是否可以被访问,这里不再区 该属性或方法是否属于对象本身

'name' in xiaoLuo ; // true
'age' in xiaoLuo; // true
'sex' in xiaoLuo; // true
'nation' in xiaoLuo; // true

# 多重继承

有了原型链,我们就可以实现更多层级的继承, 比如 小罗作为人的同时有事一个球员。这里我们就可以抽象出两个类

People(人)和Player(球员) 两个类, 然后将 People 的原型对象指向 Player, 就实现了多重继承

<script>
    //父类 人类
    function People(name, age , sex) {
        this.name = age;
        this.age = age;
        this.sex = sex;
    }

    People.prototype.hello = function() {
        console.log('你好,我是:' , this.name);
    }

    
    //子类 , 球员
    function Player(name, age ,sex , jerseyNumber) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.jerseyNumber = jerseyNumber;
    }

    // 关键语句实现继承 player 的原型对象指向 People
    Player.prototype = new People();

    Player.prototype.playing = function() {
        console.log(this.name , '正在踢足球');
    }

    // 重写父类方法
    Player.prototype.hello = function() {
        console.log('你好,我是:' , this.name, "我是一名球员, 我的球员号码是:", this.jerseyNumber);
    }

    // 实例化
    var xiaoLuo = new Player('小罗', 22, '男', 10);
    var xiaoMei = new Player('小美', 23, '女', 12);

    xiaoLuo.playing();
    xiaoLuo.hello();

    xiaoMei.hello();
    xiaoMei.playing();
</script>

# 原型链的终点

我们前面创造了一个People 的构造函数知道它的 原型对象 是 People.prototype , 那么原型对象的原型对象是什么呢。

其实原型链也是有尽头的, 这个尽头就在 Object.prototype , Object的原型对象的原型对象就指向null了

Untitled

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    

    <script>
        function People(name ,age, sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }

        var xiaoLuo = new People('小罗', 22, '男');

				// 输出true
        console.log(xiaoLuo.__proto__.__proto__ === Object.prototype);
				// 输出null
        console.log(Object.prototype.__proto__);

    </script>
</body>
</html>

# 总结

JS中的继承是通过原型链来实现的, 继承的优点很多, 其中子类可以继承父类的全部方法和属性,而且还可以扩展这些属性和方法

正式这种复用性, 是的我们可以在实际开发过程中少写很多代码

参考资料: https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Object-oriented_programming (opens new window)