对象的创建:
创建自定义对象最简单的方式是创建一个object的实例,然后再为他添加属性和方法.
数据属性:
[[Configurable]]:表示能否通过delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。false的话表示不能删除属性.并且一旦设置为false,就不能再变回true.
[Enumerable]]:表示能否通过for-in 循环返回属性
[[Writable]]:表示能否修改属性的值
[[Value]]:包含这个属性的数据值
访问器属性:
[[Configurable]]:表示能否通过delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为true。
[[Enumerable]]:表示能否通过for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为true。
[[Get]]:在读取属性时调用的函数。默认值为undefined。
[[Set]]:在写入属性时调用的函数。默认值为undefined。
访问器的常用方式是设置一个属性的值会导致其他属性发生变化,例如:
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 和setter。只指定getter意味着属性是不能写, 只指定setter 函数的属性也不能读
读取属性的特性:
使用javascript5的object.Object.getOwnPropertyDescriptor()方法,可以取出给定属性的描述符,有两个参数:属性所在的对象和描述符的名称,返回值是对象,如果是属性访问器,这个对象有configurable、enumerable、get 和set;如果是数据属性,这个对象的属性有configurable、enumerable、writable 和value.例如:
varbook = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
lue: 1
},
year: {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
alert(descriptor.value); //2004
alert(descriptor.configurable); //false
alert(typeof descriptor.get); //"undefined"
var descriptor = Object.getOwnPropertyDescriptor(book,"year");
alert(descriptor.value); //undefined
alert(descriptor.enumerable); //false
alert(typeof descriptor.get); //"function"
创建对象:
1工厂模式:
虽然object构造函数或者变量都可以用来创建单个对象,但是使用一个接口来创建对象,会产生很多重复的代码,光想想就懒得写,于是为了解决这个问题开始引入工厂模式:用函数来封装以特定接口创建对象细节,举个栗子:
functioncreatePerson(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("Nicholas", 29, "Software Engineer");
var person2 =createPerson("Greg", 27, "Doctor");
这样咱们可以调用这个函数好多好多次,就不用每次都写那么多重复的函数了.但是,这个产生了一个问题: 怎样知道一个对象的类型,于是人们开始使用构造函数模式
2 构造函数:
例如:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = newPerson("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg",27, "Doctor");
在前面例子的最后,person1和person2 分别保存着Person 的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person,如下所示。
alert(person1.constructor == Person);//true
alert(person2.constructor == Person);//true 这样就解决了对象识别问题.
但是!!!
构造函数虽然有用,但也是有缺点的:同名的函数其实是不相等的,但是创建两个同样功能的函数完全是没必要的.所以,咱们可以将函数的定义挪到外面去:
functionPerson(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
functionsayName(){
alert(this.name);
}
var person1 =new Person("Nicholas", 29, "Software Engineer");
var person2 =new Person("Greg", 27, "Doctor");
可是这样一来函数就没有良好的包装性了,这该咋办呢?我们可以通过原型模式来解决这个问题.
3原型模式:
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法,使用原型函数的好处就是: 不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中.举个栗子:
functionPerson(){
}
Person.prototype.name ="Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "SoftwareEngineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
在这里, person1 和person2访问的都是同一组属性和同一个sayName()函数。
原型与in操作符:
有两种方式使用in 操作符:单独使用和在for-in 循环中使用。在单独使用时,in 操作符会在通过对象能够访问给定属性时返回true
由于in 操作符只要通过对象能够访问到属性就返回true,hasOwnProperty()只在属性存在于实例中时才返回true,因此只要in 操作符返回true而hasOwnProperty()返回false,就可以确定属性是原型中的属性。
原型函数中所有的属性是被很多实例共享的,这种共享对于函数来说刚刚好,也是我们所需要的,但是对于引用类型值来说就很糟糕了,来看一个栗子:
functionPerson(){
}
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
在此,Person.prototype对象有一个名为friends 的属性,该属性包含一个字符串数组。然后,创建了Person 的两个实例。接着,修改了person1.friends 引用的数组,向数组中添加了一个字符串。由于friends 数组存在于Person.prototype 而非person1 中,所以刚刚提到的修改也会通过person2.friends(与person1.friends 指向同一个数组)反映出来。
那该怎么办呢,还记得构造函数的缺点吗?对,就是函数方法不共享,要是我们能把两个方法结合起来问题不就解决了?
4 组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长
来看下面一个栗子:
functionPerson(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby","Court"];
}
Person.prototype= {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 =new Person("Nicholas", 29, "Software Engineer");
var person2 =new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends);//"Shelby,Count,Van"
alert(person2.friends);//"Shelby,Count"
alert(person1.friends=== person2.friends); //false
alert(person1.sayName=== person2.sayName); //true
在这个例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor和方法sayName()则是在原型中定义的。而修改了person1.friends(向其中添加一个新字符串),并不会影响到person2.friends,因为它们分别引用了不同的数组。