原型与继承
- 原型
每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法
- 对象创建方式1
function Box() {}//声明一个构造函数
Box.prototype.name = 'Lee';//在原型里添加属性
Box.prototype.age = 100;
Box.prototype.run = function () {//在原型里添加方法
return this.name + this.age + '运行中...';
};
复制代码
原型内的方法地址一致,例如:
var box1 = new Box();
var box2 = new Box();
alert(box1.run == box2.run);//true,方法的引用地址保持一致
复制代码
在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。proto
属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性 constructor。
判断一个对象是否指向了该构造函数的原型对象,可以使用 isPrototypeOf()方法来测试。
alert(Box.prototype.isPrototypeOf(box));//只要实例化对象,即都会指向
虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原
型中的值。
var box1 = new Box();
alert(box1.name);//Lee,原型里的
box1.name = 'Jack';
alert(box.1name);//Jack,就近原则
var box2 = new Box();
alert(box2.name);//Lee,原型里的值,没有被 box1 修
复制代码
如何判断属性是在构造函数的实例里,还是在原型里?可以使用 hasOwnProperty()函数
来验证:
alert(box1.hasOwnProperty('name')); //实例里有返回 true,否则返回 false
in 操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中。
alert('name' in box1);//true,存在实例中或原型
结合hasOwnProperty()和in可以判断原型中是否存在属性。
function isProperty(object, property) {//判断原型中是否存在属性
return !object.hasOwnProperty(property) && (property in object);
}
var box = new Box();
alert(isProperty(box, 'name'))//true,如果原型有
复制代码
- 对象创建方式2
为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使
用字面量的方式:
function Box() {};
Box.prototype = {//使用字面量的方式
name : 'Lee',
age : 100,
run : function () {
return this.name + this.age + '运行中...';
}
};
复制代码
使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区
别,字面量创建的方式使用 constructor 属性不会指向实例,而会指向 Object,构造函数创建
的方式则相反。
var box = new Box();
alert(box instanceof Box);
alert(box instanceof Object);
alert(box.constructor == Box); //字面量方式,返回 false,否则,true
alert(box.constructor == Object); //字面量方式,返回 true,否则,false
复制代码
如果想让字面量方式的 constructor 指向实例对象,那么可以这么做:
Box.prototype = { constructor : Box,//直接强制指向即可 };
原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺
点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
function Box() {};
Box.prototype = {
constructor : Box,
name : 'Lee',
age : 100,
family : ['父亲', '母亲', '妹妹'], //添加了一个数组属性
run : function () {
return this.name + this.age + this.family;
}
};
var box1 = new Box();
box1.family.push('哥哥');//在实例中添加'哥哥
alert(box1.run());
var box2 = new Box();
alert(box2.run());//共享带来的麻烦,也有'哥哥'
复制代码
- 对象创建方式3
为了解决构造传参和共享问题,可以组合构造函数+原型模式:
function Box(name, age) {//不共享的使用构造函数
this.name = name;
this.age = age;
this. family = ['父亲', '母亲', '妹妹'];
};
Box.prototype = { //共享的使用原型模式
constructor : Box,
run : function () {
return this.name + this.age + this.family;
}
};
复制代码
- 对象创建方式4
构造函数和原型封装到一起(动态原型模式)
function Box(name ,age) {//将所有信息封装到函数体内
this.name = name;
this.age = age;
if (typeof this.run != 'function') {//仅在第一次调用的初始化
Box.prototype.run = function () {
return this.name + this.age + '运行中...';
};
}
}
var box = new Box('Lee', 100);
alert(box.run());
复制代码
当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用,就
不会初始化,并且第二次创建新对象,原型也不会再初始化了。这样及得到了封装,又实现
了原型方法共享,并且属性都保持独立。
PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会
切断实例和新原型之间的联系。
- 对象创建方式5
寄生构造函数(工厂模式创建对象)
function Box(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return this.name + this.age + '运行中...';
};
return obj;
}
var box1 = new Box('Lee', 100);//第一个实例
var box2 = new Box('Jack', 200);//第二个实例
alert(box1.run());
alert(box2.run());//保持独立
复制代码
2.继承
继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而 ECMAScript 只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。
function Box() {//Box 构造
this.name = 'Lee';
}
function Desk() {//Desk 构造
this.age = 100;
}
Desk.prototype = new Box();//Desc 继承了 Box,通过原型,形成链条
var desk = new Desk();
alert(desk.age);
alert(desk.name);//得到被继承的属性
function Table() {//Table 构造
this.level = 'AAAAA';
}
Table.prototype = new Desk(); //继续原型链继承
var table = new Table();
alert(table.name);//继承了 Box 和 Des
复制代码
PS:以上原型链继承还缺少一环,那就是 Obejct,所有的构造函数都继承自 Obejct。而继承 Object 是自动完成的,并不需要程序员手动继承。
经过继承后的实例,他们的从属关系会怎样呢?
alert(table instanceof Object);//true
alert(desk instanceof Table);//false,desk 是 table 的超类
alert(table instanceof Desk);//true
alert(table instanceof Box);//true
复制代码
- 继承方式1(构造函数)
为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。
function Box(age) {
this.name = ['Lee', 'Jack', 'Hello']
this.age = age;
}
function Desk(age) {
Box.call(this, age);//对象冒充,给超类型传参
}
var desk = new Desk(200);
alert(desk.age);
alert(desk.name);
desk.name.push('AAA'); //添加的新数据,只给 desk
alert(desk.name);
复制代码
- 继承方式2 组合继承(原型链 + 构造函数)
function Box(age) {
this.name = ['Lee', 'Jack', 'Hello']
this.age = age;
}
Box.prototype.run = function () {
return this.name + this.age;
};
function Desk(age) {
Box.call(this, age);//对象冒充
}
Desk.prototype = new Box();//原型链继承
var desk = new Desk(100);
alert(desk.run());
复制代码
- 继承方式3 原型式继承;这种继承借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型。
function obj(o) {//传递一个字面量函数
function F() {}//创建一个构造函数
F.prototype = o;//把字面量函数赋值给构造函数的原型
return new F();//最终返回出实例化的构造函数
}
var box = {//字面量对象
name : 'Lee',
arr : ['哥哥','妹妹','姐姐']
};
var box1 = obj(box); //传递
alert(box1.name);
box1.name = 'Jack';
alert(box1.name);
alert(box1.arr);
box1.arr.push('父母');
alert(box1.arr);
var box2 = obj(box);//传递
alert(box2.name);
alert(box2.arr);//引用类型共享了
复制代码
- 继承方式4 寄生式继承(原型式 + 工厂模式),目的是为了封装创建对象的过程。
function create(o) {//封装创建过程
var f= obj(o);
f.run = function () {
return this.arr;//同样,会共享引用
};
return f;
}
复制代码
组合式继承是 JavaScript 最常用的继承模式;但,组合式继承也有一点小问题,就是超类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部。
function Box(name) {
this.name = name;
this.arr = ['哥哥','妹妹','父母'];
}
Box.prototype.run = function () {
return this.name;
};
function Desk(name, age) {
Box.call(this, name);//第二次调用 Box
this.age = age;
}
Desk.prototype = new Box();//第一次调用 Box
复制代码
- 继承方式5 寄生组合继承,解决两次调用的问题。
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) {
this.name = name;
this.arr = ['哥哥','妹妹','父母'];
}
Box.prototype.run = function () {
return this.name;
};
function Desk(name, age) {
Box.call(this, name);
this.age = age;
}
create(Box, Desk);//通过这里实现继承
var desk = new Desk('Lee',100);
desk.arr.push('姐姐');
alert(desk.arr);
alert(desk.run()); //只共享了方法
var desk2 = new Desk('Jack', 200);
alert(desk2.arr); //引用问题解决
复制代码
- 继承方式6 原型链继承