zoukankan      html  css  js  c++  java
  • js继承的实现(原型/链、函数伪装)

    一、原型继承父类的实例

            //父类及其原型属性/方法
            function SuperType () {
                this.name = ['zc','ls','ww'];
            }
            SuperType.prototype.getSuperName = function() {
                return this.name;
            };
    
    
            //子类及其原型属性/方法
            function SubType() {
                this.test = ['a','b','c','d'];
            }
            //子类型的原型指向父类型的实例(即子类的原型复制了父类的构造器以及父类原型属性/方法)
            SubType.prototype = new SuperType();
            //为子类原型添加原型拓展属性/方法
            SubType.prototype.getSubTest = function() {
                return this.test;
            }
    
            var instance1 = new SubType();
            instance1.name.push('yzy');//name属性是原型继承自父类实例
            instance1.test.push('e');//test属性是源于子类本身的构造器
            console.log(instance1.name,instance1.test)
    
            var instance2 = new SubType();
            console.log(instance2.name,instance2.test)

    控制台输出:

    标注:

    注意这里的子类原型指向一个父类的实例(引用传递),那么这块的父类实例就是内存中的一块地址,以后所有的子类实例都会有一个原型属性指向这块地址,并且子类A对这块地址中数据更改也会影响到子类B。

    图示:

     

    所以你可以看到,instance1.name是从父类实例来的,这个属性实际存在于这个单例,访问的时候都是引用传递,由于这个单例是共享的,instance1 push了一个数据,那么就算instance2没有任何动作,instance2读的时候数据也会是变化后的数据;

    而对于test属性,是子类自身的,所以这个属性值存在于子类实例自身,相互之间互不影响,所以虽然instance1.test push了一个数据,但是instance2访问的时候丝毫不受影响。

    缺点:继承自父类实例的原型属性会被所有实例所共享。

    二、构造函数伪装(call()、apply())

            //父类及其原型属性/方法
            function SuperType(name) {
                this.name = name;
                this.color = ['green','red'];
            }
            SuperType.prototype.getSuperName = function() {
                return this.name;
            }
    
            //子类及其原型属性/方法
            function SubType(name) {
                SuperType.call(this, name);this.test = ['a','b','c','d'];
            }
            SubType.prototype.getSubTest = function() {
                return this.test;
            }
    
            var instance1 = new SubType('Jack');
            console.log(instance1.name,instance1.getSubTest());
            console.log('------------------------');
            console.log(instance1.getSuperName())

    控制台输出:

    标注:

    call()方法实际上就是在当前作用域拷贝了一下函数执行者的构造函数/方法,所以上述call()方法实际上做了如下的事情

            //子类及其原型属性/方法
            function SubType(name) {
                //SuperType.call(this, name);
                this.name = name;
                this.color = ['green','red'];
                
                this.test = ['a','b','c','d'];
            }

    注意的是,call()函数伪装并不会在当前作用域执行 SuperType 原型下的方法/属性

    所以,因为 getSuperName() 是父类原型下的方法,所以call() 方法自然不会复制该方法给 SubType 构造器,因此控制台报错也就是理所当然的咯

    缺点:函数伪装不会继承父类原型下的属性/方法。

    三、组合继承(函数伪装 + 原型继承)

            //父类及其原型属性/方法
            function SuperType(name) {
                this.name = name;
            }
            SuperType.prototype.getSuperName = function () {
                return this.name;
            }
    
            // 子类1及其原型属性/方法
            function SubType1(name) {
                SuperType.call(this, name);
                this.test = ['h1', 'h2', 'h3', 'h4'];
            }
            SubType1.prototype = SuperType.prototype;
            SubType1.prototype.getSubTest = function () {
                return this.test;
            }
    
    
            // 子类2及其原型属性/方法
            function SubType2(name) {
                SuperType.call(this, name);
                this.age = 18;
            }
            SubType2.prototype = SuperType.prototype;
            SubType2.prototype.getSubAge = function () {
                return this.age;
            }
    
            var instance1 = new SubType1('Jack');
            var instance2 = new SubType2('Tom');
            console.log(instance1,instance2);

    控制台输出:

    标注:

    ①这里子类原型继承自父类原型,然后子类为原型添加了原型拓展,这里的原型继承是引用传递,所以添加拓展的操作都是基于同一块内存地址的。

    图示:

    所以,无论是父类的原型属性还是子类继承的原型(父类原型),实际上都是引用传递,都指向内存中的同一块地址,因此,上述的代码,虽然子类2虽然没有原型方法 getSubTest,但是实际上子类1已经在他们指向的共同内存地址添加了该方法,同理子类1也是。

    缺点:子类型的原型属性共享。

    四、寄生组合式继承

            function object(o) {
                function F() { };
                F.prototype = o;
                return new F();
            }
            //寄生组合式继承 
            function inheritPrototype(subType, superType) {
                var prototype = object(superType.prototype);
                subType.prototype = prototype;
            }
    
            //父类及其原型属性/方法
            function SuperType(name) {
                this.name = name;
            }
            SuperType.prototype.getSuerperName = function () {
                return this.name;
            }
    
            //子类1及其原型属性/方法
            function SubType(name) {
                SuperType.call(this, name);
                this.test = ['h1', 'h2', 'h3', 'h4'];
            }
            inheritPrototype(SubType, SuperType);
            SubType.prototype.getSubTest = function () {
                return this.test;
            };
    
            //子类2及其原型属性/方法
            function SubType2(name) {
                SuperType.call(this, name);
                this.test2 = ['s1', 's2', 's3', 's4'];
            }
            inheritPrototype(SubType2, SuperType);
            SubType2.prototype.getSubTest2 = function () {
                return this.test2;
            };
    
            /* 以下为测试代码示例 */
            var instance1 = new SubType(['yyy', 'Jack', 'Nick']);
            var instance2 = new SubType2(['yyy2', 'Jack2', 'Nick2']);
            console.log(instance1,instance2)

    控制台输出:

    标注:

    我们看这个寄生组合式继承的处理方式,传进来一个子类和父类,子类的原型 = 新对象(新对象的原型 = 父类的原型),所以就是子类原型下的原型 = 父类的原型

    这就是我们所看到的上面控制台输出的结果了,父类的原型挂在子类原型下的原型下,这样为各个子类添加原型的时候就不会影响挂在上面的父类原型了。

    但是,由于依旧是引用传递,所以这个子类原型下原型(继承自父类的原型)依旧是共享的

    图示:

    为达上述目的,我这边直接将父类实例挂在子类原型上,也是可以的:

            //寄生组合式继承
            function inheritPrototype(subType, superType) {
                subType.prototype =new superType();
            }
    
            //父类及其原型属性/方法
            function SuperType(name) {
                if(name){
                    this.name = name;
                }
            }
            SuperType.prototype.getSuerperName = function () {
                return this.name;
            }
            
            //子类1及其原型属性/方法
            function SubType(name) {
                SuperType.call(this, name);
                this.test = ['h1', 'h2', 'h3', 'h4'];
            }
            inheritPrototype(SubType, SuperType);
            SubType.prototype.getSubTest = function () {
                return this.test;
            };
    
            //子类2及其原型属性/方法
            function SubType2(name) {
                SuperType.call(this, name);
                this.test2 = ['s1', 's2', 's3', 's4'];
            }
            inheritPrototype(SubType2, SuperType);
            SubType2.prototype.getSubTest2 = function () {
                return this.test2;
            };
    
            /* 以下为测试代码示例 */
            var instance1 = new SubType(['yyy', 'Jack', 'Nick']);
            var instance2 = new SubType2(['yyy2', 'Jack2', 'Nick2']);
            console.log(instance1,instance2)

    标注:

    这里挂载在子类原型下的原型的是一个父类的实例,值得注意的是,实例化一个父类实例是会自动调用父类构造器的,所以会将父类构造器以及父类原型一同挂载到子类原型下的原型下,不妨让我们把上述例子中的父类构造器if判断去掉看看控制台输出结果:

    讲到这里你是不是觉得已经结束了???当然~~~没有!

    上面说过:这个子类原型下原型(继承自父类的原型)依旧是共享的!

    那么我后来做了个实验:

    Ⅰ.父类原型属性值是基本数据类型

            //寄生组合式继承
            function inheritPrototype(subType, superType) {
                subType.prototype =new superType();
            }
    
            //父类及其原型属性/方法
            function SuperType(name) {
                if(name){
                     this.name = name;
                }
            }
            SuperType.prototype.getSuerperName = function () {
                return this.name;
            }
    
    
    
            SuperType.prototype.age = 12
            SuperType.prototype.console = function(){
                this.age += 1;
                console.log(this.age)
            };

    //子类1及其原型属性/方法
            function SubType(name) {
                SuperType.call(this, name);
                this.test = ['h1', 'h2', 'h3', 'h4'];
            }
            inheritPrototype(SubType, SuperType);
            SubType.prototype.getSubTest = function () {
                return this.test;
            };
    
            //子类2及其原型属性/方法
            function SubType2(name) {
                SuperType.call(this, name);
                this.test2 = ['s1', 's2', 's3', 's4'];
            }
            inheritPrototype(SubType2, SuperType);
            SubType2.prototype.getSubTest2 = function () {
                return this.test2;
            };
    
            /* 以下为测试代码示例 */
            var instance1 = new SubType(['yyy', 'Jack', 'Nick']);
            var instance2 = new SubType2(['yyy2', 'Jack2', 'Nick2']);
            instance1.console();
            instance1.console();
            instance1.console();
            instance1.console();
            instance1.console();
            instance2.console();

    控制台输出:

    结果说明:父类原型下的age属性没有共享!

    Ⅱ.父类原型属性值是非基本数据类型(例如:对象):

            //寄生组合式继承
            function inheritPrototype(subType, superType) {
                subType.prototype =new superType();
            }
    
            //父类及其原型属性/方法
            function SuperType(name) {
                if(name){
                     this.name = name;
                }
            }
            SuperType.prototype.getSuerperName = function () {
                return this.name;
            }

    
            SuperType.prototype.age = {
                age:12
            }
            SuperType.prototype.console = function(){
                this.age.age += 1;
                console.log(this.age.age)
            };

    //子类1及其原型属性/方法
            function SubType(name) {
                SuperType.call(this, name);
                this.test = ['h1', 'h2', 'h3', 'h4'];
            }
            inheritPrototype(SubType, SuperType);
            SubType.prototype.getSubTest = function () {
                return this.test;
            };
    
            //子类2及其原型属性/方法
            function SubType2(name) {
                SuperType.call(this, name);
                this.test2 = ['s1', 's2', 's3', 's4'];
            }
            inheritPrototype(SubType2, SuperType);
            SubType2.prototype.getSubTest2 = function () {
                return this.test2;
            };
    
            /* 以下为测试代码示例 */
            var instance1 = new SubType(['yyy', 'Jack', 'Nick']);
            var instance2 = new SubType2(['yyy2', 'Jack2', 'Nick2']);
            instance1.console();
            instance1.console();
            instance1.console();
            instance1.console();
            instance1.console();
            instance2.console();

    控制台输出:

    结果说明:父类原型下的age属性共享!

    综上所述:

      原型上的基本数据类型属性是值传递(内存地址不共享);

      原型上的非基本数据类型属性是引用传递(内存地址共享)。

  • 相关阅读:
    【Linux编程基础】构建Linux 库文件
    【Linux调试技术】查看数据
    【C++学习】复制构造函数和赋值运算符根本的不同
    【C++学习】显式构造函数
    【C++学习】函数对象和Lambda表达式
    【C++学习】类初始化列表的分析总结
    【Linux开发基础】Linux守护服务进程(Daemon service)编程
    【编程小结】C++和Java 的缺省初始化问题
    SQL查询金额去掉小数点后面的零
    SQL自定义函数split 将数组(分隔字符串)返回阵列(表)
  • 原文地址:https://www.cnblogs.com/eco-just/p/11106079.html
Copyright © 2011-2022 走看看