23. 理解对象
一、面向对象概念
面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但,ECMAScript 中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”
一开始,创建自定义对象的最简单方式就是创建一个 Object 的实例,然后再为它添加属性和方法,如下所示。
<xmp>
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
alert(this.name);
};
</xmp>
几年后,对象字面量成为创建这种对象的首选模式。前面的例子用对象字面量语法可以写成这样:
<xmp>
var person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function(){
alert(this.name);
}
};
</xmp>
二、属性类型
ECMAScript 中有两种属性:数据属性和访问器属性。
1. 数据属性
数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有 4 个描述其行为的特性。
属性 | 描述 |
---|---|
[[Configurable]] | 表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为 true |
[[Enumerable]] | 表示能否通过 for-in 循环返回属性。默认值为 true |
[[Writable]] | 表示能否修改属性的值。默认值为 true |
[[Value]] | 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined 。 |
<xmp>
var person = {
name: "Nicholas"
};
</xmp>
这里创建了一个名为 name 的属性,为它指定的值是 “Nicholas” 。也就是说, [[Value]] 特性将被设置为 “Nicholas” ,而对这个值的任何修改都将反映在这个位置。
要修改属性默认的特性,必须使用 ECMAScript 5 的 Object.defineProperty() 方法。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符(descriptor)对象的属性必须是: configurable 、 enumerable 、 writable 和 value 。设置其中的一或多个值,可以修改对应的特性值。例如:
<xmp>
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
</xmp>
2. 访问器属性
属性 | 描述 |
---|---|
[[Configurable]] | 表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为 true |
[[Enumerable]] | 表示能否通过 for-in 循环返回属性。默认值为 true |
[[Get]] | 在读取属性时调用的函数。默认值为 undefined |
[[Set]] | 在写入属性时调用的函数。默认值为 undefined |
访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。
<xmp>
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
</xmp>
注:在 不 支 持 Object.defineProperty() 方 法 的 浏 览 器 中 不 能 修 改 [[Configurable]] 和[[Enumerable]] 。IE8只能在 DOM 对象上使用这个方法,而且只能创建访问器属性。由于实现不彻底,建议不要在 IE8 中使用 Object.defineProperty()方法。
三、继承
1、原型链继承
ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。
<xmp>
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
</xmp>
上面代码的原型关系图如下:
SubType 继承了 SuperType ,而 SuperType 继承了 Object 。当调用 instance.toString()时,实际上调用的是保存在 Object.prototype 中的那个方法。
2、组合继承
组合继承(combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
<xmp>
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
function SubType(name, age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
</xmp>
3、寄生式继承
寄生式继承是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
<xmp>
function createAnother(original){
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
</xmp>
这个例子中的代码基于 person 返回了一个新对象—— anotherPerson 。新对象不仅具有 person的所有属性和方法,而且还有自己的 sayHi() 方法。
注:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;