一、理解对象
1.属性类型
ECMA-262第5版在定义只有内部采用的特性时,描述了属性的各种特征。
ECMAScript中有两种属性:数据属性和访问器属性。
1)数据属性
数据属性有四个描述其行为的特性。
- [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
- [[Enumerable]]:表示能否通过for-in循环返回属性。
- [[Writable]]:表示能否修改属性的值。
- [[Value]]:包含这个属性的数据值。读取属性的时候,从这个位置读;写入属性的时候,把新值保存在这个位置,默认值为undefined。
要修改属性默认的特性,必须使用ES5的Object.defineProperty()方法。
Object.defineProperty():接收三个参数(属性所在的对象、属性的名字和一个描述符对象),其中描述符对象的属性必须为configurable、enumerable、writable和value。
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
注意:一旦把属性定义为不可配置的,就不能再把它变回可配置了。
2)访问器属性
访问器属性有如下4个特性。
- [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。
- [[Enumerable]]:表示能否通过for-in循环返回属性。
- [[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
//_year前面的下划线是一种常用的记号,表示只能通过对象方法访问的属性。
2.定义多个属性
Object.defineProperties()方法可以通过描述符一次定义多个属性。
var book = {};
Object.defineProperties(book, {
_year: {
writable: true,
value: 2004
},
edition: {
writable: true,
value: 1
},
year: {
get: function(){
return this,_year;
},
set: function(newValue){
if (newValue>2004){
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
3.读取属性的特性
Object.getOwnPropertyDescriptor()方法可以取得给定属性的描述符。
接收两个参数:属性所在的对象和要读取其描述符的属性名称。
var book = {};
Object.defineProperties(book, {
_year: {
writable: true,
value: 2004
},
edition: {
writable: true,
value: 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.工厂模式
使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。
function creatPerson(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("Nicholals", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
缺点:没有解决对象识别的问题,即怎样知道一个对象的类型。这个模式后来被构造函数模式所取代。
2.构造函数模式
可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholals", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
//按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。
将构造函数当做函数
//当做构造函数使用
var person = new Person("Nicholals", 29, "Software Engineer");
person.sayName(); //"Nicholals"
//作为普通函数调用
Person("Greg", 27, "Doctor"); //添加到window
window.sayName(); //"Greg"
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"
缺点:它的每个成员都无法得到复用,包括函数。
3.原型模式
使用构造函数的prototype属性来指定那些应该共享的属性和方法。
1)理解原型对象
无论什么时候只要创建了一个新函数,就会根据一定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向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();
person1.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
下图展示了以上使用Person构造函数和Person.prototype创建实例的代码的各个对象之间的关系:
isPrototypeOf()方法:可以确定对象之间是否存在这种关系,即实例指向构造函数的原型对象。
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
Object.getPrototypeOf()方法:返回[[Prototype]]的值。
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
注意:在实例中添加一个与实例原型中同名的属性,那么该属性将会屏蔽原型中的那个属性。只有使用delete操作符完全删除实例属性,才可以重新访问原型中的属性。
hasOwnProperty()方法:可以检测一个属性是存在于实例中(返回true),还是存在于原型中(返回false)。
2)原型与in操作符
in操作符会在通过对象能够访问给定属性时返回true。
因此同时使用hasOwnProperty()方法和in操作符,可确定该属性是存在于对象中还是原型中。
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name)&&(name in object);
}
在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性。
Object.keys()方法:接收一个对象作为参数,可返回一个包含所有可枚举属性的字符串数组。
Object.getOwnPropertyNames()方法:可以获得所有实例属性,无论它是否可枚举。
3)更简单的原型语法
function Person(){
}
Person.prototype = {
name: "Nicholas",
age: 29,
job: "software Engineer",
sayName: function () {
alert(this.name);
}
};
上面的原型写法本质上完全重写了默认的prototype对象,因此constructor也就变成了新对象的constructor属性,不再指向Person函数。
可以使用下面这种方式特意将它设置回适当的值。
function Person(){
}
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "software Engineer",
sayName: function () {
alert(this.name);
}
};
但是,以这种方式重设constructor属性会导致它的[[Enumerable]]特性被设置为true,默认情况下,原生的constructor是不可枚举的。
因此使用Object.defineProperty()可解决上述问题。
function Person(){
}
Person.prototype = {
name: "Nicholas",
age: 29,
job: "software Engineer",
sayName: function () {
alert(this.name);
}
};
//重设构造函数,只适用于ES5兼容的浏览器
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
4)原型的动态性
实例中的指针仅指向原型,而不指向构造函数,因此重写原型对象时,已创建的实例仍然指向先前的原型对象。
5)原生对象的原型
所有原生引用类型(Object、Array、String等)都在其构造函数的原型上定义了方法,比如String.prototype中可以找到substring()方法,当然,你也可以通过原生对象的原型,定义新方法,不过,不推荐这么做。
6)原型对象的问题
- 不能为构造函数传递初始化参数
- 对于包含引用类型值的属性来说,实例不能拥有属于自己的全部属性
创建自定义对象的最常见方式,就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,而原型模式用于定义共享的属性和方法。
function Person(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("Nicholals", 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
5.动态原型模式
function Person(name, age, job){
//属性
this.name = name;
this.age = age;
this.job = job;
//方法
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
var friend = new Person("Nicholals", 29, "Software Engineer");friend.sayName();
6.寄生构造函数模式
这个模式与工厂构造模式是一模一样的,它可以在特殊的情况下用来为对象创建构造函数。
7.稳妥构造函数模式
稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:
- 新创建对象的实例方法不引用this;
- 不使用new操作符调用构造函数;