之前写过经典继承的方法,它是综合利用了构造函数和原型链继承的优点打造的经典继承,在这里简单概括一下,见如下代码:
(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赋值,不过和这个方法比起来要逊色一点,在这里不再介绍。