WithCoderWithCoderWithCoder

Javascript原型对象之继承模型简介

    在之前的文章中,介绍了“Javascript原型对象之概念简介”,对于原型的概念有了一个初步的了解。本文在此基础上,介绍下原型对象继承实现的用法。

    首先,我们可以先看下面的例子:   

  <script>
        // 创建一个函数A
        function A() {

        }

        // 给函数A原型属性添加属性和方法
        A.prototype.name = 'A';
        A.prototype.say = function() {
            console.log('Hello ' + this.name);
        };

        // 实例化两个对象
        console.log('+++++++++++++++++++++++++++++++++++++++');
        var a = new A();
        var a1 = new A();

        // 打印实例化对象
        console.log(a);
        console.log(a1);

        console.log('+++++++++++++++++++++++++++++++++++++++');
        // 打印属性值
        console.log(a.name);
        console.log(a1.name);
        // 调用方法say
        a.say();
        a1.say();

        console.log('+++++++++++++++++++++++++++++++++++++++');
        // 实例化对象,并给实例化对象添加与原型对象同名的属性和方法
        var a2 = new A();
        a2.name = 'a2';
        a2.say = function() {
            console.log('我是a2的 say 方法');
        };
        a2.other = function() {
            console.log('other');
        };

        console.log(a2);
        console.log(a2.name);
        a2.say();
    </script>

    运行结果如下图:

    1-2004201RU2P5.png

    说明:

    1. 从上面的代码和打印结果中可以看到,创建的多个实例对象a,a1,a2等的原型属性和构造函数A的原型对象指向的是同一个对象

    2. 我们可以给构造函数的原型对象添加属性和方法,那么实例对象a,a1,a2…会共享这些在原型中添加的属性和方法

    3. 如果我们访问实例对象中的一个属性(如:name,say),如果在实例对象中找到,则直接返回。如果实例对象中没有找到,则直接去实例对象的__proto__属性指向的原型对象中查找,如果查找到则返回。(如果原型中也没有找到,则继续向上找原型的原型—原型链)。

    4. 如果通过实例对象a2添加了一个同名属性(如:name,say),则实例对象a2的属性就会屏蔽原型中对应的属性。( 也就是说,在实例对象a2中就不会访问原型的属性了)

    (以下内容参考https://blog.csdn.net/u012468376/article/details/53121081

    原型模型创建对象的缺陷

    原型中的所有的属性都是共享的。也就是说,用同一个构造函数创建的对象去访问原型中的属性的时候,大家都是访问的同一个对象,如果一个对象对原型的属性进行了修改,则会反映到所有的对象上面。

    但是在实际使用中,每个对象的属性一般是不同的。张三的姓名是张三,李四的姓名是李四。

    但是,这个共享特性对 方法(属性值是函数的属性)又是非常合适的。所有的对象共享方法是最佳状态。这种特性在c#和Java中是天生存在的。

    构造函数模型创建对象的缺陷

    在构造函数中添加的属性和方法,每个实例对象都有自己独有的一份,大家不会共享。这个特性对属性比较合适,但是对方法又不太合适。因为对所有对象来说,他们的方法应该是一份就够了,没有必要每人一份,造成内存的浪费和性能的低下。

    <script type="text/javascript">
        function Person(name) {
            this.name = name;

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

        // 实例化对象
        var a = new Person('neil');
        a.say();

        var b = new Person('jack');
        b.say();

        // 每个实例对象都会有不同的方法
        console.log(a.say == b.say); // false
    </script>

    可以使用下面的方法解决:

    <script type="text/javascript">
        function Person(name) {
            this.name = name;

            this.say = say// 每个实例对象的方法赋值的是同一个函数
        }

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

        // 实例化对象
        var a = new Person('neil');
        a.say();

        var b = new Person('jack');
        b.say();

        // 每个实例对象的方法赋值的是同一个函数,因此返回true
        console.log(a.say == b.say); // true
    </script>

    但是上面的这种解决方法具有致命的缺陷:封装性太差。使用面向对象,目的之一就是封装代码,这个时候为了性能又要把代码抽出对象之外,这是反人类的设计。

    使用组合模式(构造函数 + 原型)解决上述两种缺陷

    原型模式适合封装方法,构造函数模式适合封装属性,综合两种模式的优点就有了组合模式。

    <script type="text/javascript">
        function Person(name) {
            // 构造函数添加属性
            this.name = name;
        }

        // 给原型添加方法
        Person.prototype.say = function() {
            console.log(this.name);
        }

        // 实例化对象
        var a = new Person('neil');
        a.say();

        var b = new Person('jack');
        b.say();

        // 每个实例对象的方法使用的是原型方法,因此返回true
        console.log(a.say == b.say); // true
    </script>

    动态原型模式创建对象

    前面讲到的组合模式,也并非完美无缺,有一点也是感觉不是很完美。把构造方法和原型分开写,总让人感觉不舒服,应该想办法把构造方法和原型封装在一起,所以就有了动态原型模式。

    动态原型模式把所有的属性和方法都封装在构造方法中,而仅仅在需要的时候才去在构造方法中初始化原型,又保持了同时使用构造函数和原型的优点。

    看下面的代码:

    <script type="text/javascript">
        function Person(name) {
            // 构造函数添加属性
            this.name = name;

            /*
            判断this.say这个属性是不是function,如果不是function则证明是第一次创建对象,则把这个funcion添加到原型中。
            如果是function,则代表原型中已经有了这个方法,则不需要再添加。
            完美解决了性能和代码的封装问题。
            */
            if (typeof this.say != 'function') {
                // 给原型添加方法
                Person.prototype.say = function() {
                    console.log(this.name);
                }
            }
        }

        // 实例化对象
        var a = new Person('neil');
        a.say();

        var b = new Person('jack');
        b.say();

        // 每个实例对象的方法使用的是原型方法,因此返回true
        console.log(a.say == b.say); // true
    </script>

    说明:

    组合模式和动态原型模式是JavaScript中使用比较多的两种创建对象的方式。

    建议以后使用动态原型模式。他解决了组合模式的封装不彻底的缺点。

欢迎分享交流,转载请注明出处:WithCoder » Javascript原型对象之继承模型简介