继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继
承:一个是接口实现,一个是继承。而ECMAScript 只支持继承,不支持接口实现,而实现
继承的方式依靠原型链完成。
一、原型+对象构造之间的关系结构像链条一样,称原型链。
function Box() { //Box 构造/、被继承的函数叫做超类型(父类,基类) this.name = 'Lee'; } function Desk() { //Desk 构造//继承的函数叫做子类型(子类或派生类) this.age = 100; } function Table() { this.level = 'AAAAA'; }
//通过原型链继承,超类型实例化后的对象实例,赋值给子类型的原型属性 //new Box()会将Box构造里的信息和原型里的信息都交给Desk //Desk的原型,得到的是Box的构造+原型里的信息 Desk.prototype = new Box(); Table.prototype = new Desk();
如果要实例化table,那么Desk 实例中有age=100,原型中增加相同的属性age=200,
最后结果是多少呢?
Desk.prototype.age = 200; //实例和原型中均包含age
PS:以上原型链继承还缺少一环,那就是Obejct,所有的构造函数都继承自Obejct。而
继承Object 是自动完成的,并不需要程序员手动继承。
从属关系
function Box() { //被继承的函数叫做超类型(父类,基类) this.name = 'Lee'; } Box.prototype.name = 'Jack'; function Desk() { //继承的函数叫做子类型(子类,派生类) this.age = 100; } Desk.prototype = new Box(); //通过原型链继承 var box = new Box(); var desk = new Desk(); //alert(desk.name); //就近原则,实例里有,就返回,没有就去查找原型 //子类型从属于自己或者他的超类型 //alert(desk instanceof Object); alert(desk instanceof Desk); alert(desk instanceof Box); alert(box instanceof Desk);
字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数
二、对象冒充(伪造对象、经典继承)/借用构造函数
//使用对象冒充继承 function Box(name, age) { this.name = name; this.age = age; this.family = ['哥哥','姐姐','妹妹']; //引用类型,放在构造里就不会被共享 } //Box.prototype.family = '家庭'; function Desk(name, age) { Box.call(this, name, age) //对象冒充,对象冒充只能继承构造里的信息//desk冒充Box } var desk = new Desk('Lee', 100); alert(desk.family); desk.family.push('弟弟'); alert(desk.family); var desk2 = new Desk('Lee', 100); alert(desk2.family);
三、借用构造函数虽然解决了刚才两种问题,但没有原型,复用则无从谈起。所以,我们需
要原型链+借用构造函数的模式,这种模式成为组合继承。(用的比较多)
function Box(name, age) { this.name = name; this.age = age; this.family = ['哥哥','姐姐','妹妹']; } Box.prototype.run = function () { return this.name + this.age + '运行中...'; }; //构造函数里的方法,放在构造里,每次实例化,都会分配一个内存地址,浪费,所以最好放在原型里,保证多次实例化只有一个地址 function Desk(name, age) { Box.call(this, name, age) //对象冒充 } Desk.prototype = new Box(); //原型链继承 var desk = new Desk('Lee', 100); alert(desk.run());
四、原型继承
原型式继承;这种继承借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型。
//1.原型链继承,2.借用构造函数继承(对象冒充继承) 3.组合继承(结合前两种)
//4.原型式继承
//临时中转函数 function obj(o) { //o表示将要传递进入的一个对象 function F() {} //F构造是一个临时新建的对象,用来存储传递过来的对象 F.prototype = o; //将o对象实例赋值给F构造的原型对象 return new F(); //最后返回这个得到传递过来对象的对象实例 } //F.prototype = o 其实就相当于 Desk.prototype = new Box(); //这是字面量的声明方式,相当于var box = new Box(); var box = { name : 'Lee', age : 100, family : ['哥哥','姐姐','妹妹'] }; //box1就等于new F() var box1 = obj(box); //alert(box1.name); alert(box1.family); box1.family.push('弟弟'); alert(box1.family); var box2 = obj(box); alert(box2.family); //引用类型的属性共享了
五、寄生式继承把原型式+工厂模式结合
目的是为了封装创建对象的过程。
//临时中转函数 function obj(o) { function F() {} F.prototype = o; return new F(); } //寄生函数 function create(o) { var f = obj(o); f.run = function () { return this.name + '方法'; } return f; } var box = { name : 'Lee', age : 100, family : ['哥哥','姐姐','妹妹'] }; var box1 = create(box); alert(box1.run());
六、寄生组合继承
组合式继承是JavaScript 最常用的继承模式;但,组合式继承也有一点小问题,就是超
类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的
内部。
//临时中转函数 function obj(o) { function F() {} F.prototype = o; return new F(); } //寄生函数 function create(box, desk) { var f = obj(box.prototype); f.constructor = desk; //调整原型构造指针 desk.prototype = f; } function Box(name, age) { this.name = name; this.age = age; } Box.prototype.run = function () { return this.name + this.age + '运行中...' } function Desk(name, age) { Box.call(this, name, age); //对象冒充 } //通过寄生组合继承来实现继承 create(Box, Desk); //这句话用来替代Desk.prototype = new Box(); var desk = new Desk('Lee', 100); alert(desk.run()); alert(desk.constructor);