javascript面向对象
面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可
以创建任意多个具有相同属性和方法的对象。
理解对象
创建自定义对象的最简单方式就是创建一个
Object
的实例,然后再为它添加属性和方法。
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
alert(this.name);
};
其中,sayName()方法用于显示 this.name(将被解析为 person.name)的值。
// 字面量
var person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function(){
alert(this.name);
}
};
-
属性类型
ECMAScript
中有两种属性:数据属性和访问器属性。
1.
数据属性
[[Configurable]]
:表示能否通过
delete
删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
[[Enumerable]]
:表示能否通过
for-in
循环返回属性。
[[Writable]]
:表示能否修改属性的值。
[[Value]]
:包含这个属性的数据值。
要修改属性默认的特性,必须使用
ECMAScript 5
的
Object.defineProperty()
方法。
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
类似的规则也适用于不可配置的属性。
var person = {};
Object.defineProperty(person, "name", {
configurable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
delete person.name;
alert(person.name); //"Nicholas"
2.
访问器属性
访问器属性不包含数据值;它们包含一对儿
getter
和
setter
函数(不过,这两个函数都不是必需的)。
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
-
定义多个属性
定义了一个 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;
}
}
}
});
-
读取属性的特性
Object.getOwnPropertyDescriptor()可以取得给定属性的描述符。
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;
}
}
}
});
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"
创建对象
-
工厂模式
考虑到在
ECMAScript
中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节。
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("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
弊端:工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)
-
构造函数模式
ECMAScript
中的构造函数可用来创建特定类型的对象。
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("Greg", 27, "Doctor");
与工厂函数不同的是:
- 没有显式地创建对象;
- 直接将属性和方法赋给了 this 对象;
- 没有 return 语句。
注意:构造函数始终都应该以一个大写字母开头,
因为构造函数本身也是函数,只不过可以用来创建对象而已。
要创建
Person
的新实例,必须使用
new
操作符。以这种方式调用构造函数实际上会经历以下
4个步骤:
(1)
创建一个新对象;
(2)
将构造函数的作用域赋给新对象(因此
this
就指向了这个新对象);
(3)
执行构造函数中的代码(为这个新对象添加属性);
(4)
返回新对象。
在前面例子的最后,
person1
和
person2
分别保存着
Person
的一个不同的实例。这两个对象都
有一个
constructor
(构造函数)属性,该属性指向
Person
,
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
我们在这个例子中创建的所有对象既是
Object
的实例,同时也是
Person 的实例,这一点通过 instanceof
操作符可以得到验证。
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型;而这正是构造函数模式 胜过工厂模式的地方。在这个例子中,person1 和 person2 之所以同时是 Object 的实例,是因为所有对象均继承自 Object。
弊端:使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。在前面的例子中,person1 和 person2 都有一个名为 sayName()的方法,但那两个方法不是同一个 Function 的实例。
-
原型模式
我们创建的每个函数都有一个
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
-
组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实 例属性,而原型模式用于定义方法和共享的属性。
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("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
-
动态原型模式
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("Nicholas", 29, "Software Engineer");
friend.sayName();
注意构造函数代码中加粗的部分。这里只在
sayName()
方法不存在的情况下,才会将它添加到原
型中。