ECMAScript有两种属性:
数据属性和访问器属性。
数据属性
数据属性有4个描述其行为的特性。
Configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,能把属性修改为访问器属性。对于在对象上直接定义的属性,默认为true.
Enumerable:表示能否通过for-in循环返回属性。
Writable:表示能否修改属性的值。
Value:包含这个属性的数据值。
要修改属性默认的特性,可使用ECMAScript5的Object.defineProperty()方法。接收三个参数:属性所在的对象、属性的名字、一个描述符对象。如下:
var person = {};
Object.defineProperty(person,"name",{
writable:false;
value:"nichols";
Configurable:false;
});
alert(person.name);//"nichols"
person.name = "greg";//严格模式下会抛错.非严格模式下忽略。
delete person.name;//严格模式下会抛错
alert(person.name);//nichols
访问器属性
包含一对getter和setter函数。有四个特性
Configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,能把属性修改为访问器属性。对于在对象上直接定义的属性,默认为true.
Enumerable:表示能否通过for-in循环返回属性。
[[Set]]:在写入属性时调用的函数。默认为undefined.
访问器属性不能直接定义,必须调用Object.defineProperty()定义。
var book = {
_year:2004,
edition:1
};
Object.defineProperty(book,"year",{
get:function(){
return this._year;
},
set:function(newValue){
if(newValue>2004){
this._year = newValue;
this.edition +=newValue-2004;
}
}
});
book.year = 2005;
alert(book.edition);//2
不一定要同时指定getter和seter.只指定getter意味着不能写。
历史遗留的两个方法:
var book = {
_year:2004,
edition:1
};
book.__defineGetter__("year",function{return this._year});
book.__defineSetter__("year",function{.....});
同时定义多个属性:
Object.defineProperties():接收两个对象参数,要添加或修改属性的对象和对应的要添加或修改属性。
var book = {};
Object.defineProperties(book,{
_year:{
value:2004
},
edition:{
value:1
},
year:{
get:function(){
return this._year;
},
set:function(newValue){
if(newValue>2004){
this._year = newValue;
this.edition +=newValue-2004;
}
}
}
});
读取属性的特性
使用ECMAScript5的Object.getOwnPropertyDescriptor()方法可以取得给定对象的描述符。这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象。
var descriptor = Object.getOwnPropertyDescrptor(book,"_year");
alert(descriptor.value);//2004
alert(descriptor.configurable);//false
alert(typeof descriptor.get);//undefined
var descriptor = Object.getOwnPropertyDescrptor(book,"year");
alert(descriptor.value);//undefined
alert(descriptor.enumerable);//false
alert(typeof descriptor.get);//function
工厂模式
用函数来封装以特定接口创建对象的细节。
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("nichils",29,"softward engineer");
构造函数模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Nicholas", 29, "Software Engineer");
alert(person1.sayName == person2.sayName)//false
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person1.constructor == Person); //true
要创建person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
1. 创建一个新对象
2. 将构造函数的作用域赋给新对象。
3. 执行构造函数中的代码
4. 返回新对象。
上面的person有一个constructor属性,该属性指向Person.
总结:创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型。任何函数只要通过new操作符来来调用,那他就可以作为构造函数。而不用new就跟普通函数没什么区别。
构造函数的问题:使用构造函数的主要问题就是每个方法都要在每个实例上重新创建一遍。
可以把函数定义转移到构造函数外来解决这个问题。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
而这样会带来新的问题:如果对象需要定义很多个方法那么就要定义很多个全局函数。可以通过原型模式来解决。
原型模式
我们创建的每个函数都有一个prototype属性,这个属性是个指针,指向一个对象,这个对象包含可以由特定类型的所有实例共享的属性和方法。
使用原型对象的好处:不必在构造函数中定义对象实例的信息,可以将这些信息直接添加到原型对象中。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
原型对象的这些属性和方法是由所有实例共享的。
所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype所在函数的指针。所以
Person.prototype.constructor = Person.
当调用构造函数的新实例后,该实例的内部也会有一个指针叫[Prototype]指向构造函数的原型对象而非构造函数。但我们无法访问
[Prototype]。可以调用isPrototypeOf()方法来判断这种关系。
alert(person1.prototype.isPrototypeOf(person1));//true
或者通过Object.getPropertyOf()来得到[Prototype]的值。即这个对象的原型。
alert(Object.getPrototypeOf(person1)==Person.prototype);//true
每当读取一个对象的属性时,首先先搜索对象实例本身的属性,找到了就返回。找不到再去搜索原型对象的属性。找到了就返回。
原型最初只包含constructor属性,而该属性也是共享的。因此可以通过对象实例访问。
如果在实例中添加了一个与对象原型中的属性同名的属性,则在实例中创建该属性,并且屏蔽原型中的那个属性。
function Person(){
}
Person.prototype.name = "Nicholas";
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg" ?from instance
alert(person2.name); //"Nicholas" ?from prototype
hasOwnProerty():可以检测一个属性是存在于实例中还是存在于原型中。如果存在于对象实例中则返回true.
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
person1.name = 'ncios';
alert(person1.hasOwnProperty("name")); //true
原型与in操作符
两种使用方法:单独使用和在for-in循环中使用。单独使用时,如果通过对象访问能够给定属性,in操作符会返回true,无论该对象存在于实例中还是原型中。
alert("name" in person1);//true
person1.name = "kke";
alert("name" in person1);//true
使用for-in循环返回的是所有能够通过对象访问的、可枚举的属性。不管该属性是在实例中还是在原型中。所有开发人员定义的属性都是可枚举的。
注:IE早期版本的实现中存在一个bug,及屏蔽不可枚举属性的实例属性不会出现在for-in循环中。
var o = {
toString : function(){
return "My Object";
}
}
for (var prop in o){
if (prop == "toString"){
alert("Found toString");//ie中中不显示
}
}
可以通过ECMAScript5的Object.keys()方法来取得对象上所有可枚举的实例属性。接收一个对象参数,返回一个包含该对象所有可枚举属性的字符串数组。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = 'rob';
pa.age=13;
alert(Object.keys(p1));//name,age.
getOwnPropertyNames():得到所有的实例属性无论是否可枚举。
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"
更简单的原型语法:
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
但此时原型对象的constructor属性不再指向Person,而是指向Object构造函数。尽管通过instanceof还能返回正确的结果。
var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true
原型的动态性:对原型对象的任何修改都能够立即从实例上反映出来。
var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
};
friend.sayHi(); //hi
而如果是把原型改为另外一个对象,就等同于切断构造函数与最初原型之间的联系。而实例中的指针是指向最初原型的。因此会出错。
function Person(){
}
var friend = new Person();
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error
原型对象的问题:
对于包含引用类型的属性的原型对象,所有实例共享的都是同一个引用类型。
function Person(){
}
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
friends : ["Shelby", "Court"],
sayName : function () {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true
实例一般都会有自己全部的属性的,因此这个问题是很少有人单独使用原型模式的原因所在。