zoukankan      html  css  js  c++  java
  • JavaScript中的类、原型、原型链、继承(转载)

    前言

    JavaScript在ES6之前严格意义上是没有像JAVA,C#这种语言中类的概念的。ES6添加了class,但其实这个class也只是ES6以前的构造函数和原型的语法糖而已。要想真正了解JavaScript中最复杂的部分,就得从最初的构造函数和原型讲起。

    在ES6以前,我们是这样实例化一个对象的:

    首先声明一个构造函数,一般用首字母大写来区分构造函数和普通函数.

    function Animal(name, age) {
        this.name = name;
        this.age = age;
    }
    

    接下来在构造函数的prototype上挂载公共方法

    Animal.prototype.say = function () {
        console.log(this.name);
    }
    

    最后实例化一个对象

    const dog = new Animal('dog', 3);
    dog.say();  //会在控制台打印出dog
    

    当我们使用ES6的class来声明上面这个类的话,代码如下

    class Animal {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        say() {
            console.log(this.name);
        }
    }
    const dog = new Animal('dog', 3);
    dog.say();  //会在控制台打印出dog
    

    使用class的新语法明显更让我们有面向对象类的感觉,但是实际上他们的原理是一致的。

    接下来使用这个对象来分析一下JavaScript独特的原型。

    原型

    首先打印一下dog对象

    JavaScript中的类、原型、原型链、继承

     

    dog对象

    然后看一下Animal类

    JavaScript中的类、原型、原型链、继承

     

    Animal类

    在这里 dog.__proto__ 与 Animal.prototype就是所谓的原型,在这个原型对象里我们可以看到有constructor字段,它指向dog这个实例的构造函数,也就是Animal, dog.__proto__.constructor === Animal 返回的是true.所以原型、类和实例直接的关系可以这样表示:

    JavaScript中的类、原型、原型链、继承

    原型关系图

    原型是JavaScript的特色之一,虽然类、实例这些面向对象的概念和Java这些语言很像,但本质上却并不一样。在JavaScript中,由构造函数实例化出的每个对象,本身一般只包含自己的属性,但是当我们调用这个对象上没有的方法或属性时,实例化的对象就会沿着自己的原型向上找。如果原型上还没有找到,且这个原型对象还有自己的原型,那就会顺着这个原型链一直回溯到最顶级的原型Object.prototype。这也是我们一般在构造函数的原型上挂载公共方法的原因 Animal.prototype.xxx = function () {},这样可以让实例化出的每一个对象沿着自己的原型链找到这个方法,所有的实例化的对象共用同一个方法。

    原型链

    JavaScript中所有的对象都有原型,其中最顶级的原型就是Object.prototype,任何一个对象的原型链的顶端必然是Object.prototype,下面来看一下上面示例中的原型链。

    JavaScript中的类、原型、原型链、继承

     

    原型链

    上图中的原型链: dog -> Animal.prototype -> Object.prototype,最终由实例化对象追溯到了Object.prototype。我们平时用字面量定义的普通对象 { name: harlan, age: 24 },这个对象的原型就是Object.prototype。

    原型继承

    介绍完原型链之后,必须得提一下JavaScript中的继承,其实从上面的例子就可以看出,可以认为所有的对象都继承自Object,因为所有的对象的原型链的顶端都是Object.prototype。
    我们再来看一个JavaScript内置的继承关系:

            定义一个函数function a() { },
            JS中所有的函数都继承自内置的类Function, 而Function则继承自Object,
            a.__proto__ === Function.prototype // true
            a.__proto__.__proto__ = Object.prototype // true
            所以这个原型链是a -> Function.prototype -> Object.prototype
    

    现在我们要自己定义类来实现这种继承。

    首先需要在子类实例化时调用父类的构造函数

            function Dog(name, age, weight) {
                Animal.call(this, name, age);
                this.weight = weight;
            }
    

    注:有些初学者可能不是太理解这里发生了什么,我在这里详细介绍一下
    首先我们来看一个构造函数使用new调用会发生什么

    Animal(name, age) {
        const this = {};
        this.name = name;
        this.age = age;
        return this;
    }
    Animal(name, age);

    在JS中new这个运算符可以认为在要执行的函数语句前先定义了一个this对象,最后又把这个this对象返回。

    在搞清楚new的实际操作后我们再来看一下new Dog(name, age, weight)的过程

    function Dog(name, age, weight) {
        const this = {};
        Animal(this, name, age);  // 这行代码又可以理解为下面的语句  
        // this.name = name;  
        // this.age = age;  
        this.weight = weight;
        return this;
    }
    Dog(name, age, weight);
    

    在初始化属性之后,我们需要将Dog.prototype的原型设置成Animal.prototype,即Dog.prototype.__proto__ === Animal.prototype返回true,这样挂载在Animal.prototype原型上的方法就可以被Dog实例化出的对象调用,当然这里我们不能直接设置__proto__属性,这个属性其实没有在ES的规范中被提及,只是被各个浏览器实现了。在这里我们使用Object.create()这个方法。

    Dog.prototype = Object.create(Animal.prototype);
    

    Object.create会创建一个新的对象,这个对象的原型就是这个方法传入的参数,在这里我们实现了原型链的修改,此时的原型链如下

    const dog = new Dog(name, age, weight)dog -> Dog.prototype -> Animal.prototype -> Object.prototype
    

    最后,我们还必须让dog的原型的constructor属性指向构造函数

    Dog.prototype.constructor = Dog;
    

    至此,一个继承自Animal的Dog类就建立完毕了,完整代码如下

    function Dog(name, age, weight) {
        Animal.call(this, name, age);
        this.weight = weight;
    }
    Dog.prototype = Object.create(Animal.prototype);
    Dog.prototype.constructor = Dog;
    

    这种方式也被称为寄生组合继承。

    class继承

    ES6中提出了class,class的继承的本质就是上面我们提到的寄生组合继承,但是在代码量上和形式上都更加简单

    class Dog extends Animal {
        constructor(name, age, weight) {
            super(name, age); // 这一句代码一定不能缺    
            this.weight = weight;
        }
    }
    

    小结

    JavaScript的原型可以说是这门语言最核心的知识之一了,本人从接触JS一直到现在,真的是每个阶段对于这方面的内容都有不同的理解。希望通过这篇文章的分享可以让大家对于原型有更深入的理解。

    作者:Harlan_Zhang
    链接:https://www.jianshu.com/p/f7e794b30392  

      

      

      

      

      

      

      

      

      

      

      

  • 相关阅读:
    HDU 2192 MagicBuilding
    HDU 2148 Score
    HDU 1847 Good Luck in CET4 Everybody!
    往CImageList中加图标列表
    LoadIcon
    OnInitialUpdate 详解
    设备坐标(DP)、客户坐标(Client)、逻辑坐标(LP)
    Web及网络基础学习(一)
    Qt 下QMessageBox下中文乱码问题
    vs2005菜单:选项项目和解决方案
  • 原文地址:https://www.cnblogs.com/liontone/p/12296512.html
Copyright © 2011-2022 走看看