此为译文,原文地址:http://javascriptissexy.com/javascript-objects-in-detail/
JavaScript的核心--用的最多也是最根本的--数据类型是对象(Object)数据类型。JavaScript有一种复杂数据类型,对象数据类型,和五种简单数据类型:数字(Number),字符串(String),布尔(Boolean),未定义(Undefined),和空(Null)。注意简单(原始)数据类型是非可变的,他们不能被更改,但是对象是可变的。
什么是对象
对象是一组无序的原始数据类型(有时候是引用数据类型)序列,并且这个序列以名值对的形式存储。序列中的每一项被称为属性(函数被称为方法)并且每一个属性名必须是唯一的,这个唯一的名字可以是字符串或者数字。
这是一个简单的对象:
var myFirstObject = {firstName: "Richard", favoriteAuthor: "Conrad"};
重申:把对象想成一个含有项目的序列,并且序列中的每一项(既属性)是以名值对的形式存储的。上面的例子中,属性名是firstName和favoriteAuthor,而值分别是“Richard”和"Conrad"。
属性名可以是字符串或者数字,但如果属性名是数字,它就必须用方括号符来获取值。后面会有更多关于方括号符的内容。以下是另一个用数字作为属性名的对象的列子:
var ageGroup = {30: "Children", 100:"Very Old"};
console.log(ageGroup.30) // 这里会报错
// 这是如何获取属性30的值的方法,得到了值“Children”
console.log(ageGroup["30"]); // Children
//最好避免用数字作为属性命。
作为一名JavaScript的开发人员你会经常用到对象数据类型,常见的用途是存储数据和创建自定义的方法和函数。
引用数据类型和原始数据类型
引用数据类型和原始数据类型之间的主要区别之一在于引用数据类型的值是是以引用的形式存储的,而不是像原始数据类型那样以值的形式直接将值存储到变量里。比如:
// 原始数据类型字符串以值的形式存储
var person = "Kobe";
var anotherPerson = person; // anotherPerson = person的值
person = "Bryant"; // 更改person的值
console.log(anotherPerson); // Kobe
console.log(person); // Bryant
即使我们把person的值改为“Bryant”也没有用,变量anotherPerson还是保留了person之前存储的值。
将上面已经证明是以值存储的原始数据与以引用存储的对象做比较:
var person = {name: "Kobe"};
var anotherPerson = person;
person.name = "Bryant";
console.log(anotherPerson.name); // Bryant
console.log(person.name); // Bryant
在这个例子中,我们把对象person复制到anotherPerson中,但因为person中存储的值只是一个引用而非一个真正的值,所以当我们把属性person.name改为"Bryant"时,anotherPerson体现了这个改变,因为anotherPerson从来没有存储过person的属性本身的值的实际副本,它只是引用了person的属性的值。
(译注:person的值是一个引用,所以anotherPerson的值也是那一个引用,当person值的引用变更了,也就是anotherPerson中值的引用变更了。)
对象数据属性所具有的特性
每个数据属性(既存储数据的对象属性)不但有名值对的特性,还有其他三个特性(这三个特性在初时是被设置为true):
可配置性:指明该属性是否可以被删除或者修改。
枚举性:指明该属性是否能在for/in循环中被返回。
可写性:指明该属性是否能被修改。
注意ECMAScript 5和上面提到的数据属性一起还提出了访问属性。访问属性是一类函数(获取和设置)。我们会在一篇已经预计在2月15日发表的文章中学习更多关于ECMAScript 5的内容。
创建对象
有两种一般的创建对象的方法:
1.对象式
最普通并且确实是最简单的一种创建对象的方式就是下面这样的对象式:
// 这是一个用对象式符号初始化的空对象。
var myBooks = {};
// 这是一个含有4项,并且也是用对象式创建的对象
var mango = {
color: "yellow",
shape: "round",
sweetness: 8,
howSweetAmI: function () {
console.log("Hmm Hmm Good");
}
}
2.对象构造函数
第二种普遍采用的方法是用构造函数来创建对象。一个构造函数就是一个用来初始化新对象的函数,而你用这个新关键字来调用该构造函数。
var mango = new Object ();
mango.color = "yellow";
mango.shape= "round";
mango.sweetness = 8;
mango.howSweetAmI = function () {
console.log("Hmm Hmm Good");
}
虽然你可以用一些保留关键字,例如“for”来做对象的属性名,但是聪明的做法是避免这种混合的用法。
对象可以包含任何其他数据类型,包括数字、数组甚至是其他对象。
用实践模式创建对象
对于一些在你的应用中仅仅被用到一次的、用来存储数据的对象,上面用到的两种方法用来创建对象就足够了。
假设你有一个显示水果和每种水果细节的应用。在这个应用中的所有水果都有这些属性:颜色,形状,甜度,价格,还有一个showName函数。那么你每次想要创建一个新的水果对象是都要输入下面这些内容,这样做将会是相当枯燥并且低效的。
var mangoFruit = {
color: "yellow",
sweetness: 8,
fruitName: "Mango",
nativeToLand: ["South America", "Central America"],
showName: function () {
console.log("This is " + this.fruitName);
},
nativeTo: function () {
this.nativeToLand.forEach(function (eachCountry) {
console.log("Grown in:" + eachCountry);
});
}
}
如果你有10种水果,你不得不输入同样的代码10次。而如果你必须要修改nativeTo函数又会怎样呢?你将不得不在10个不同的地方修改这个函数。现在可以推断这种情况,当要给一个网页里的对象添加成员时,你会突然发现用我们到目前为止创建对象的方法创建出来的不是一个可以有实例的理想的对象,尤其是当开发大型应用时。
为了解决这种重复问题,软件工程师已经发明出了模式(对于重复并且常用的任务的解决方案)使得开发应用变得更加高效和简洁。
下面是两种常见的用来创建对象的模式。如果你已经完成了正确学习JavaScript的课程,你一定见过Code Academy的课上经常使用这第一种模式:
1.用构造函数模式创建对象
function Fruit (theColor, theSweetness, theFruitName, theNativeToLand) {
this.color = theColor;
this.sweetness = theSweetness;
this.fruitName = theFruitName;
this.nativeToLand = theNativeToLand;
this.showName = function () {
console.log("This is a " + this.fruitName);
}
this.nativeTo = function () {
this.nativeToLand.forEach(function (eachCountry) {
console.log("Grown in:" + eachCountry);
});
}
}
有了这个模式,创建各种水果就变得非常容易。于是:
var mangoFruit = new Fruit ("Yellow", 8, "Mango", ["South America", "Central America", "West Africa"]);
mangoFruit.showName(); // This is a Mango.
mangoFruit.nativeTo();
//Grown in:South America
// Grown in:Central America
// Grown in:West Africa
var pineappleFruit = new Fruit ("Brown", 5, "Pineapple", ["United States"]);
pineappleFruit.showName(); // This is a Pineapple.
如果你需要修改fruitName函数,你只要在一个地方改一次就可以了。这个模式通过仅仅建立一个可以继承的函数Fruit()封装了水果所有的功效和性质。
注意:
继承来的属性是为对象的原型属性定义的。例如:someObject.prototype.firstName = “rich”;自有属性直接为对象本身定义,例如:
// 让我们先来创建一个对象:
var aMango = new Fruit ();
// 现在我们直接为aMango对象定义mangoSpice属性
// 因为我们是直接为aMango对象定义mangoSpice属性的,所以它是aMango的一个自有属性,而不是一个继承来的属性。
aMango.mangoSpice = “some value”;
要获取对象的属性的话,我们用object.property,例如:
console.log(aMango.mangoSpice); // “some value”
要调用对象中的方法,我们用 object.method(),例如:
//首先,让我们添加一个方法
aMango.printStuff = function () {return “Printing”;}
//现在我们可以调用printStuff方法:
aMango.printStuff ();
2.用原型模式创建对象
function Fruit () {
}
Fruit.prototype.color = "Yellow";
Fruit.prototype.sweetness = 7;
Fruit.prototype.fruitName = "Generic Fruit";
Fruit.prototype.nativeToLand = "USA";
Fruit.prototype.showName = function () {
console.log("This is a " + this.fruitName);
}
Fruit.prototype.nativeTo = function () {
console.log("Grown in:" + this.nativeToLand);
}
而这是我们如何在这个原型模式中调用构造函数Fruit()的例子:
var mangoFruit = new Fruit ();
mangoFruit.showName(); //
mangoFruit.nativeTo();
// This is a Generic Fruit
// Grown in:USA
进阶阅读
对于有关这两种模式的完整讨论和深入解释每种模式是怎样工作的以及它们各自的缺点,请阅读Professional JavaScript for Web Developers的第六章节。你还将知道哪种模式是Zakas所推荐的最好用的一种(提示:以上两种都不是)。
如何获取对象的属性
用点号符和方括号符是两种主要的获取对象属性的方法。
1.点号符
// 到目前为止在前面的例子中我们已经用过点号符了,这里是另一个例子:
var book = {title: "Ways to Go", pages: 280, bookMark1:"Page 20"};
// 为了用点号符获取对象book的属性,你要这样做:
console.log ( book.title); // Ways to Go
console.log ( book.pages); // 280
2.方括号符
// 为了用方括号符获取对象book的属性,你要这样做:
console.log ( book["title"]); //Ways to Go
console.log ( book["pages"]); // 280
//或者,如果你把属性的名字存在变量里
var bookTitle = "title";
console.log ( book[bookTitle]); // Ways to Go
console.log (book["bookMark" + 1]); // Page 20
获取一个对象中不存在的属性的话,结果会返回undefined。
自有和继承得到的属性
对象有通过继承得到的属性和自有属性。自有属性是被定义给对象的属性,而继承得到的属性继承自对象的原型对象。
为了知道一个属性是否存在于一个对象里(无论是继承来的还是自有属性),你会用下面的操作:
// 用一个属性名schoolName创建一个新的对象school
var school = {schoolName:"MIT"};
// 打印出true,因为schoolName是对象school的一个自有属性
console.log("schoolName" in school); // true
// 打印出false,因为我们没有给对象school定义一个schoolType属性,而且该对象也没有从他的原型对象Object.prototype继承schoolType属性。
console.log("schoolType" in school); // false
// 打印出true,因为对象school从Object.prototype继承了方法toString。
console.log("toString" in school); // true
hasOwnProperty
用方法hasOwnProperty来确定一个对象是否有一个明确的属性是它的自有属性。这个方法非常有用,因为你需要不时的枚举对象并且你只想要它的自有属性,而不是继承来的属性。
// 用一个属性名schoolName创建一个新的对象school
var school = {schoolName:"MIT"};
// 打印出true,因为schoolName是对象school的一个自有属性
console.log(school.hasOwnProperty ("schoolName")); // true
// 打印出false,因为对象school是从Object.prototype继承来的toString方法,因此toString不是对象school的一个自有属性
console.log(school.hasOwnProperty ("toString")); // false
获取并枚举对象的属性
用for/loop循环或者一个通用的for循环来获取可枚举的(自有和继承来的)对象的属性。
// 用三个自有属性:schoolName,schoolAccredited,和schoolLocation创建一个新的对象school。
var school = {schoolName:"MIT", schoolAccredited: true, schoolLocation:"Massachusetts"};
//利用for/in循环去获取对象school里的属性
for (var eachItem in school) {
console.log(eachItem); // 结果是 schoolName, schoolAccredited, schoolLocation
}
访问继承来的属性
从Object.prototype继承的属性是不可以枚举的,所以for/in循环不会显示它们。然而,通过for/in循环迭代被揭示的可继承属性是可枚举的。
例如:
//利用for/in循环去获取对象school里的属性
for (var eachItem in school) {
console.log(eachItem); // 结果是 schoolName, schoolAccredited, schoolLocation
}
//创建一个对象school将会继承的新函数HigherLearning
/*附注:就像Wilson(一位聪明的读者)在下面的评论里纠正出来的那样,这里的属性educationLevel实际上不是被使用构造函数HigherLearning的对象所继承的;相反,属性educationLevel是每一个使用构造函数HigherLearning的对象都会创建的一个新属性。这个属性不是继承来的理由是因为我们使用了关键字“this”来定义该属性。
*/
function HigherLearning () {
this.educationLevel = "University";
}
// 用构造函数HigherLearning来实现继承
var school = new HigherLearning ();
school.schoolName = "MIT";
school.schoolAccredited = true;
school.schoolLocation = "Massachusetts";
//利用for/in循环去获取对象school里的属性
for (var eachItem in school) {
console.log(eachItem); // 打印出 educationLevel,schoolName,schoolAccredited,和schoolLocation
}
在上面这个例子里,注意为函数HigherLearning定义的属性educationLevel被做为school的属性之一给列了出来,即使educationLevel不是一个自有属性--它是一个继承来的属性。
对象的原型特性和原型属性
对象的原型特性和原型属性是JavaScript里需要特别重点理解的概念。更多内容请读我的这篇文章JavaScript Prototype in Plain, Detailed Language。(译注:计划下周翻译这篇。)
删除对象的属性
用delete操作把一个属性从对象中删除。你不可以删除继承来的属性,也不可以删除特性被设置为可配置的属性。你必须从原型对象(该属性被定义的地方)里删除可继承的属性。而且,你不可以删除那些用关键字var声明的全局对象的属性。
如果成功删除,那么delete操作会返回true。而出奇的是,即使需要删除的对象不存在或者该对象不可被删除(比如不可配置或者不是该对象所自有的属性),delete操作还是会返回true。
这些例子说明了这一点:
var christmasList = {mike:"Book", jason:"sweater" }
delete christmasList.mike; // 删除属性mike
for (var people in christmasList) {
console.log(people);
}
// 只打印出了jason
// 属性mike被删除了
delete christmasList.toString; // 返回true,但是toString没有被删除,因为它是继承来的方法
// 这里我们调用了方法toString而且它工作正常--未被删除
christmasList.toString(); //"[object Object]"
//如果属性是某个实例所自有的,那么你可以删除该实例的这个属性。例如,我们可以把我们属性educationLevel从我们上面创建的school的对象中删除,因为属性educationLevel是为该实例定义的:当我们声明函数HigherLearning时我们用关键字“this”定义了该属性。我们没有为函数HigherLearning的原型定义属性educationLevel。
console.log(school.hasOwnProperty("educationLevel")); true
// 因为educationLevel是school的一个自有属性,所以我们可以删除它
delete school.educationLevel; true
// 属性educationLevel已经从school实例中删除
console.log(school.educationLevel); undefined
// 但是属性educationLevel仍然存在于函数HigherLearning
var newSchool = new HigherLearning ();
console.log(newSchool.educationLevel); // University
// 如果我们为函数HigherLearning的原型定义一个属性,比如属性educationLevel2:
HigherLearning.prototype.educationLevel2 = "University 2";
// 那么在HigherLearning的实例中属性educationLevel2就不是自有属性
// 属性educationLevel2不是实例school的一个自有属性
console.log(school.hasOwnProperty("educationLevel2")); false
console.log(school.educationLevel2); // University 2
// 让我们尝试长处继承来的属性educationLevel2
delete school.educationLevel2; true (总是返回true, 就像先前提到的一样)
// 继承来的属性educationLevel2没有被删除
console.log(school.educationLevel2); University 2
对象序列化及去序列化
为了使你的对象能够通过HTTP协议传送或者为了别的什么目的把对象转换成字符串,你需要将它序列化(把它转换成字符串);你可以用函数JSON.stringify 来序列化你的对象。注意在ECMAScript 5之前,你必须用一个流行的库json2(来自Douglas Crockford)来获得函数JSON.stringify。该函数现在在 ECMAScript 5已经被标准化。
用来自同一个库json2得到函数 JSON.parse来去序列化你的对象(把它从一个字符串转换成一个对象)。这个函数同样在ECMAScript 5中被标准化。
JSON.stringify的例子:
var christmasList = {mike:"Book", jason:"sweater", chelsea:"iPad" }
JSON.stringify (christmasList);
// 打印出这个字符串:
// "{"mike":"Book","jason":"sweater","chels":"iPad"}"
// 为了打印出一个格式化的对象序列,添加“null”和“4”作为参数
JSON.stringify (christmasList, null, 4);
// "{
"mike": "Book",
"jason": "sweater",
"chels": "iPad"
}"
// JSON.parse 例子 \\
// 下面是一个JSON字符串, 所以我们不能用点号符来获取它的属性(像christmasListStr.mike这样)
var christmasListStr = '{"mike":"Book","jason":"sweater","chels":"iPad"}';
// 让我们把它转换成一个对象
var christmasListObj = JSON.parse (christmasListStr);
// 现在它是一个对象了,可以使用点号符
console.log(christmasListObj.mike); // Book
更多有关JavaScript对象的详细内容,包括ECMAScript 5新增的关于对象的处理,请阅读JavaScript: The Definitive Guide 6th Edition的第六章节。