hasOwnProperty()方法检测属性是否存在实例中,in可以判断属性是否存在实例中或原型中,那么结合这两种方法就可以判断属性是否存在原型中;
function isProperty(objest, property){
return !object.hasOwnProperty(property) && (property in object);
}
var box = new Box();
alert(isProperty(box, 'name'));
为了让属性和方法更好的体现封装效果,并且减少不必要的输入,原型创建可以用字面量的方式:
function Box(){};
Box.prototype = {
name:'Wu',
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
alert(box.constructor == Object);//true
//如果想让字面量方式的constructor指向实例对象,可以这么做
Box.prototype = {
constructor:Box,
};
字面量方式为什么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(){
return 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() {};
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()方法发现不存在,然后初始化原型。当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会再初始化了。这样及得到了封装,又实现了原型方法共享,并且属性都保持独立。