面向对象与原型(二)
判断属性是在构造函数还是在原型里:
1.hasOwnProperty(属性名)---->>>此方法判断属性是否在构造函数里。是,则返回true,
否,则返回false
2.in 操作符的判断范围在构造与原型中,只要在两个地方之一或都有该属性,则返回true
3.没有直接的方法来判断属性是否在原型中。但从以上两个方法中,可以推导一个方法出
来,用于判断属性是否在原型中,如下:
function isInPrototype(object,property){
return !object.hasOwnProperty(property) && (property in object)
}
若该方法返回true,则表示该属性在原型中,而且只在。
用字面量方式创建原型:
function Box(){};
Box.prototype={
name:'Lee',
age:100,
run:function(){
return this.name+this.age+'运行中...';
}
};
字面量创建的方式使用constructor属性不会指向实例, 而会指向Object, 构造函数创建
的方式则相反。如果想让字面量方式的constructor指向实例对象,那么可以这么做:
function Box(){};
Box.prototype={
constructor:Box,
name:'Lee',
age:100,
run:function(){
return this.name+this.age+'运行中...';
}
};
字面量方式为什么constructor会指向Object?因为Box.prototype={};这种写法其实就是创
建了一个新对象。而每创建一个函数,就会同时创建它prototype,这个对象也会自动获取
constructor属性。 所以, 新对象的constructor重写了Box原来的constructor, 因此会指向新对
象,那个新对象没有指定构造函数,那么就默认为Object。
原型对象不仅仅可以在自定义对象的情况下使用, 而ECMAScript内置的引用类型都可以使用
这种方式,并且内置的引用类型本身也使用了原型。
alert(Array.prototype.sort); //sort就是Array类型的原型方法
alert(String.prototype.substring); //substring就是String类型的原型方法
String.prototype.addstring=function(){ //给String类型添加一个方法
return this+',被添加了! '; //this代表调用的字符串
};
alert('Lee'.addstring()); //使用这个方法。
//尽管给原生的内置引用类型添加方法使用起来特别方便,但我们不推荐使用这种
//方法。因为它可能会导致命名冲突,不利于代码维护。
原型的最大优点是共享,同时这也是它最大的缺点。原型中所有属性是被很多实例共享的, 共享
对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:
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()); //共享带来的麻烦,也有'哥哥'了
了解决构造传参和共享问题,可以组合构造函数+原型模式:
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;
}
};
这种混合模式很好的解决了传参和引用共享的大难题。 是创建对象比较好的方法。
原型模式, 不管你是否调用了原型中的共享方法, 它都会初始化原型中的方法, 并且在声明
一个对象时, 构造函数+原型部分让人感觉又很怪异, 最好就是把构造函数和原型封装到一起。为
了解决这个问题,我们可以使用动态原型模式。
动态原型模式:
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()方法发现不存在,然后初始化原型。当第二次调用,就不
会初始化, 并且第二次创建新对象, 原型也不会再初始化了。 这样及得到了封装, 又实现了原
型方法共享,并且属性都保持独立。
使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和
新原型之间的联系。
以上讲解了各种方式对象创建的方法, 一般地情形都能满足。
如果这几种方式都不能满足需求, 可以使用一开始那种模式:
寄生构造函数(工厂模式+构造函数模式)。
function Box(name,age){
var obj=new Object();
obj.name=name;
obj.age=age;
obj.run=function(){
return this.name+this.age+'运行中...';
};
return obj;
}
这种模式比较通用, 但不能确定对象关系,所以,在可以使用之前所说的模式时,不建
议使用此模式。
在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。
由于之前说明不建议直接String.prototype.addstring,可以通过寄生构造的方式添加。
function myString(string){
var str=new String(string);
str.addstring=function(){
return this+',被添加了! ';
};
return str;
}
var box=new myString('Lee'); //比直接在引用原型添加要繁琐好多
alert(box.addstring());
在一些安全的环境中, 比如禁止使用this和new, 这里的this是构造函数里不使用this,
这里的new是在外部实例化构造函数时不使用new。这种创建方式叫做稳妥构造函数。
function Box(name,age){
var obj=new Object();
obj.run=function(){
return name+age+'运行中...'; //直接打印参数即可
};
returnobj;
}
var box=Box('Lee',100); //直接调用函数
alert(box.run());
稳妥构造函数和寄生类似。