创建对象的五种方式
第一种
var person = new Object();
person.name = "Leon";
person.age = "22";
第二种 json
var person = {
name:"Leon",
age:22
};
第三种 工厂
function createPerson(name,age){
var o = new Object();
o.name = name;
o.age = age;
return o;
}
第四种 构造函数
function Person(name,age){
this.name = name;
this.age = age;
}
第一、二种方式存在的缺点是不可重用,第三种方式无法判断数据类型,第四种方式每个对象的方法占用不同的内存,创建大量对象会占用较多的内存,解决方法是把方法变成全局的,但是它变成了window的方法。
第五种 基于原型
function Person(){}
Person.prototype.name = "Leon";
Person.prototype.age = 22;
Person.prototype.say = function(){
alert(this.name+","+this.age);
}
第一种状态:function Person(){},Person函数中有一个prototype属性指向Person的原型对象,在原型对象中有一个constructor的属性指向了Person函数,所以可以通过new Person()创建对象。
第二种状态:通过Person.prototype.name为原型设置值之后,这些属性和方法都是设置在Person的原型中的。
第三种状态:当使用Person创建对象之后,会在对象中有一个_prop_属性(这个属性是不能被访问的)指向了原型,当使用对象调用原型的时候,首先会在对象内部找到是否有这个属性,如果没有会通过_prop_去原型中找属性,所以当调用p1.say(),在自己的空间中不存在这个方法,就会去原型中找,找到之后就会完成say的调用。
Person.prototype.isPrototypeOf(p1);
检测p1是否有_prop_指向Person的原型。
第四种状态:当创建了一个新的p2之后依然会有一个_prop_属性指向Person原型,此时如果通过p2.name设置了属性之后,会在对象自己的内存空间中存储name的值,当调用say方法的时候,在寻找name时,在自己的空间中找到之后,就不会去原型中查找了。(特别注意:原型中的值不会被替换,仅仅只是在查找时被覆盖)
检测某个对象是否是某个函数的原型
alert(Person.prototype.isPrototypeOf(p2));
检测某个对象的constructor
alert(p1.constructor == Person);
检测某个属性是否是自己的属性
alert(p1.hasOwnProperty("name"));
可以通过delete删除空间中的属性
检测某个对象在原型或者自己中是否包含有某个属性,通过in检测(原型和自己的空间都没有为false),可以通过如下方法检测某个属性是否在原型中存在。
function hasPrototypeProperty(obj,prop){
return ((!obj.hasOwnProperty(prop))&&(prop in obj));
}
重写原型
Person.prototype = {
name:"Leon",
age:23,
say:function(){
alert(this.name+","+this.age);
}
}
var p1 = new Person();
Person.prototype.sayHi = function(){
alert(this.name+":Hi");
}
重写放在new Person()之后。
基于原型的创建虽然可以有效的完成封装,但是依然存在一些问题
1.无法通过构造函数来设置属性值。
2.当属性中有引用类型变量时,可能存在变量值重复。
function Person(){}
Person.prototype = {
constructor:Person,
name:"Leon",
age:30,
friends:["Ada","Chris"],
say:function(){
alert(this.name+"["+this.friends+"]");
}
}
var p1 = new Person();
p1.say();
p1.friends.push("Mike");
var p2 = new Person();
p2.say();//p2也有Mike
为了解决原型所带来的问题,此处需要通过组合构造函数和原型来实现对象的创建,将属性在构造函数中定义,将方法在原型中定义,有效结合两者的优点是比较常用的方式。为了让定义的方式更加符合Java的需求,把定义方法的原型代码放置到Person这个构造函数中(判断,防止重复定义if(!Person.prototype.say))。
继承
function Parent(){
this.pv = "parent";
}
Parent.prototype.pp = "ok";
Parent.prototype.showParentValue = function(){
alert(this.pv);
}
function Child(){
this.cv = "child";
}
Child.prototype = new Parent();
child.prototype.showChildValue = function(){
alert(this.cv);
}
var c = new Child();
c.showParentValue();
c.showChildValue();
在使用原型链进行继承一定要注意以下问题:
Child.prototype = {
showChildValue:function(){
alert(this.cv);
}
}
原型又重写了,不存在任何的继承关系了。
Child.prototype.showParentValue = function(){
alert("over parent");
}
同样在原型链设定之后执行这里覆盖后的方法。
基于函数伪造的方式实现继承
function Parent(){ this.color = ["red","blue"]; this.name = "Leon"; } function Child(){ Parent.call(this); Parent(); } /* 在child中的this指向child对象,当调用Parent方法的时候,而且this又是指向child,此时就等于在这里完成this.color=["red","blue"];也就等于在Child中有了this.color属性,这样也就变相地完成了继承,Parent()仅仅完成了函数的调用,无法实现继承。 */
var c1 = new Child(); c1.color.push("green"); alert(c1.color);//red,blue,green var c2 = new Child(); alert(c2.color);//red,blue
使用伪造的方式可以把子类的构造函数参数传递到父类中。
function Parent(name){
this.color = ["red","blue"];
this.name = name;
}
function child(name,age){
this.age = age;
Parent.call(this,name);
}
var c1 = new Child("Leon",12);
var c2 = new Child("Ada",22);
alert(c1.name+","+c1.age);//Leon,12
alert(c2.name+","+c2.age);//Ada,22
原型的方式写方法不会继承,解决方案是写在构造函数中,但是又会占用太多内存。
基于组合的方式实现继承
组合的实现方式是属性通过伪造的方式实现,方法通过原型链的方式实现。function Parent(name){
this.color = ["red","blue"];
this.name = name;
}
Parent.prototype.ps = function(){
alert(this.name+"["+this.color+"]");
}
function Child(name,age){
Parent.call(this,name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.say = function(){
alert(this.name+""+this.age+"["+this.color+"]");
}
var c1 = new Child("Leon",22);
c1.color.push("green");
c1.say();
c1.ps();
var c2 = new Child("Ada",23);
c2.say();
c2.ps();