经典继承的小小不足以及想到的完善方法

本文介绍了JavaScript中的一种经典继承模式,该模式结合了构造函数和原型链的优点,实现了子类对父类属性和方法的有效继承,并通过改进避免了多次调用构造函数及内存浪费的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前写过经典继承的方法,它是综合利用了构造函数和原型链继承的优点打造的经典继承,在这里简单概括一下,见如下代码:

(function () {
    function SuperTest(name, age) {
        this._name = name;//命名前加了_代表这是一个私有变量,不能在外界直接访问得到;
        this._age = age;
        this._colors = ["red", "black", "blue"];
    }

    SuperTest.prototype = {
        constructor: SuperTest,
        getName: function () {
            alert(this._name);
        },
        setName: function (name) {
            this._name = name;
        }
    };
    var supertest = new SuperTest("许志伟", 22);
    supertest.getName();//许志伟
    supertest.setName("赵超");
    supertest.getName();//赵超
    //然后开始创建一个子类并实现继承;

    function SubTest() {
        SuperTest.call(this,"李潇然",30);//<span style="color:#6666cc;">调用了父类的构造方法</span>,并初始化了两个参数,直接存储在SubTest的原型对象中了;
        this.subproperty = false;
    }

    SubTest.prototype = new SuperTest();//<span style="color:#3366ff;">在这里实例化了一次</span>,并且prototype属性指向父类的原型(原型链继承);
    SubTest.prototype.constructor = SubTest;
    SubTest.prototype.sayHello = function () {
        alert("Hello这是子类的定义方法");
    };
    var subtest = new SubTest();
    subtest.getName();//李潇然;
    subtest.sayHello();//Hello这是子类的定义方法
})();

这个例子开头就用了一个私有作用域,然后在作用域里面先定义了一个父类,包括私有属性和公有的方法,然后用了组合模式实现了继承,所谓组合模式就是既用了构造函数的特点(每个实例都有自己独立的属性,且优先级高于原型中定义的属性),又用了原型链的优点(能够调用父类原型的公有方法,节省内存)。最后的子类能拥有和父类相同的私有属性,而且也能访问到父类的原型方法(因为子类的prototype也是指向父类的prototype的)。但是这个方法仍然不够完美,细心一点会发现这里调用了两次构造函数,而且父类的私有属性也会赋值给子类的prototype中,造成内存的浪费。那么有没有更好的方式呢?

在之前的经典继承的博客中,提出的方案还是不够完美,因为子类的原型是直接指向父类的原型的,这样如果子类原型添加了新属性,那么父类原型也会发生改变,这不能满足一般意义上的继承。至少要像Java一样,子类可以重写方法,对父类是不会造成影响的。所以只要新建一个空的构造函数,让它初始化子类的原型,这样就能开辟一个新空间,同时又能指向父类的原型,而且也只调用了一次父类的构造函数,简直完美。上例子:

(function () {
    function SuperTest(name, age) {
        this._name = name;//命名前加了_代表这是一个私有变量,不能在外界直接访问得到;
        this.age = age;
        this._colors = ["red", "black", "blue"];
    }

    SuperTest.prototype = {
        constructor: SuperTest,
        getName: function () {
            alert(this._name);
        },
        setName: function (name) {
            this._name = name;
        }
    };
    var supertest = new SuperTest("许志伟", 22);
    supertest.getName();//许志伟
    supertest.setName("赵超");
    supertest.getName();//赵超
    //然后开始创建一个子类并实现继承;

    function SubTest() {
        SuperTest.call(this,"李潇然",30);//调用了父类的构造方法,并初始化了两个参数,直接存储在SubTest的原型对象中了;
        this.subproperty = false;
    }
    function inheritPrototypeMethod(superType,SubType){
        function Empty(){}//放一个匿名函数,这样在不用的时候能及时回收内存;
        Empty.prototype = superType.prototype;//借鉴了之前的方法,这里不再新建一个类,而是直接引用;
        SubType.prototype = new Empty();//这样就新建了新的存储空间,并且其prototype属性是指向父类的prototype的;
        SubType.prototype.constructor = SubType;
    }
    inheritPrototypeMethod(SuperTest,SubTest);//这里是运用了引用传递,能直接实现继承功能;
    var subtest = new SubTest();
    subtest.getName();//李潇然,这说明了实现了继承!能调用父类的原型方法了;
    //接下来给子类定义一个新的方法,看能不能影响到父类;
    SubTest.prototype.sayGoodBye = function () {
        alert("Goodbye!");
    };
    //subtest.sayGoodBye();//成功输出Goodbye!,这没什么奇怪的,接下来看父类;
    supertest.sayGoodBye();//并没有输出结果,这说明重新声明方法不会对父类造成影响了;
})();

在例子当中已经把原理解释清楚了。当然网上还有循环赋值的方法,原理很简单就是给子类的prototype赋值,不过和这个方法比起来要逊色一点,在这里不再介绍。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值