正如我们在 数据类型 一章中所了解的,JavaScript 中有八种数据类型。其中七种被称为“基本类型”,因为它们的值只包含一个内容(无论是字符串、数字还是其他内容)。
相比之下,对象用于存储各种数据的键控集合和更复杂的实体。在 JavaScript 中,对象几乎渗透到了语言的各个方面。因此,在深入了解其他任何内容之前,我们必须首先了解它们。
可以使用花括号 {…}
创建一个对象,其中包含一个可选的属性列表。属性是一个“键:值”对,其中键
是一个字符串(也称为“属性名称”),而值
可以是任何内容。
我们可以将对象想象成一个带签名的文件柜。每条数据都按键存储在它的文件中。通过名称查找文件或添加/删除文件非常容易。
可以使用以下两种语法之一创建空对象(“空文件柜”)
let user = new Object(); // "object constructor" syntax
let user = {}; // "object literal" syntax
通常使用花括号 {...}
。该声明称为对象字面量。
字面量和属性
我们可以立即将一些属性放入 {...}
中作为“键:值”对
let user = { // an object
name: "John", // by key "name" store value "John"
age: 30 // by key "age" store value 30
};
一个属性在冒号 ":"
之前有一个键(也称为“名称”或“标识符”),在其右侧有一个值。
在 user
对象中,有两个属性
- 第一个属性的名称为
"name"
,值为"John"
。 - 第二个属性的名称为
"age"
,值为30
。
由此产生的 user
对象可以想象为一个带有两个签名文件(标记为“name”和“age”)的橱柜。
我们可以随时添加、删除和读取其中的文件。
可以使用点表示法访问属性值
// get property values of the object:
alert( user.name ); // John
alert( user.age ); // 30
值可以是任何类型。让我们添加一个布尔值
user.isAdmin = true;
要删除属性,我们可以使用 delete
运算符
delete user.age;
我们还可以使用多字属性名称,但必须用引号引起来
let user = {
name: "John",
age: 30,
"likes birds": true // multiword property name must be quoted
};
列表中的最后一个属性可以用逗号结尾
let user = {
name: "John",
age: 30,
}
这称为“尾随”或“悬挂”逗号。这样可以更轻松地添加/删除/移动属性,因为所有行都变得相似。
方括号
对于多字属性,点访问不起作用
// this would give a syntax error
user.likes birds = true
JavaScript 不理解这一点。它认为我们寻址 user.likes
,然后在遇到意外的 birds
时给出语法错误。
点要求键是有效的变量标识符。这意味着:不包含空格,不以数字开头,不包含特殊字符(允许 $
和 _
)。
有一种替代的“方括号表示法”,它可以与任何字符串一起使用
let user = {};
// set
user["likes birds"] = true;
// get
alert(user["likes birds"]); // true
// delete
delete user["likes birds"];
现在一切都很好。请注意,括号内的字符串被正确引用(任何类型的引号都可以)。
方括号还提供了一种方法,可以将属性名称作为任何表达式的结果(而不是一个文字字符串)获得——就像从变量中获得以下结果一样
let key = "likes birds";
// same as user["likes birds"] = true;
user[key] = true;
在此,变量 key
可以在运行时计算或取决于用户输入。然后我们用它来访问该属性。这给了我们很大的灵活性。
例如
let user = {
name: "John",
age: 30
};
let key = prompt("What do you want to know about the user?", "name");
// access by variable
alert( user[key] ); // John (if enter "name")
点表示法不能以类似的方式使用
let user = {
name: "John",
age: 30
};
let key = "name";
alert( user.key ) // undefined
计算属性
在创建对象时,我们可以在对象字面量中使用方括号。这称为计算属性。
例如
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {
[fruit]: 5, // the name of the property is taken from the variable fruit
};
alert( bag.apple ); // 5 if fruit="apple"
计算属性的含义很简单:[fruit]
表示属性名称应该从 fruit
中获取。
因此,如果访问者输入 "apple"
,bag
将变为 {apple: 5}
。
从本质上讲,这与以下内容相同
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};
// take property name from the fruit variable
bag[fruit] = 5;
…但看起来更漂亮。
我们可以在方括号内使用更复杂的表达式
let fruit = 'apple';
let bag = {
[fruit + 'Computers']: 5 // bag.appleComputers = 5
};
方括号比点表示法强大得多。它们允许使用任何属性名称和变量。但它们也更难写。
因此,在大多数情况下,当属性名称已知且简单时,将使用点。如果我们需要更复杂的内容,则切换到方括号。
属性值简写
在实际代码中,我们经常将现有变量用作属性名称的值。
例如
function makeUser(name, age) {
return {
name: name,
age: age,
// ...other properties
};
}
let user = makeUser("John", 30);
alert(user.name); // John
在上面的示例中,属性与变量具有相同的名称。从变量创建属性的用例非常常见,因此有一个特殊的属性值简写可以缩短它。
我们可以像这样只写 name
,而不是 name:name
function makeUser(name, age) {
return {
name, // same as name: name
age, // same as age: age
// ...
};
}
我们可以在同一个对象中同时使用普通属性和简写
let user = {
name, // same as name:name
age: 30
};
属性名称限制
正如我们已经知道的那样,变量不能具有与语言保留字(如“for”、“let”、“return”等)相等的名称。
但对于对象属性,没有这样的限制
// these properties are all right
let obj = {
for: 1,
let: 2,
return: 3
};
alert( obj.for + obj.let + obj.return ); // 6
简而言之,属性名称没有限制。它们可以是任何字符串或符号(一种特殊的标识符类型,将在后面介绍)。
其他类型会自动转换为字符串。
例如,数字 0
在用作属性键时会变成字符串 "0"
let obj = {
0: "test" // same as "0": "test"
};
// both alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)
有一个名为 __proto__
的特殊属性有一个小问题。我们不能将它设置为非对象值
let obj = {};
obj.__proto__ = 5; // assign a number
alert(obj.__proto__); // [object Object] - the value is an object, didn't work as intended
从代码中可以看到,对基本类型 5
的赋值被忽略了。
我们将在 后续章节 中介绍 __proto__
的特殊性质,并建议 修复 此类行为的方法。
属性存在性测试,“in” 运算符
与许多其他语言相比,JavaScript 中对象的显着特点是它可以访问任何属性。如果属性不存在,不会出现错误!
读取不存在的属性只会返回 undefined
。因此,我们可以轻松测试属性是否存在
let user = {};
alert( user.noSuchProperty === undefined ); // true means "no such property"
为此,还有一个特殊运算符 "in"
。
语法是
"key" in object
例如
let user = { name: "John", age: 30 };
alert( "age" in user ); // true, user.age exists
alert( "blabla" in user ); // false, user.blabla doesn't exist
请注意,in
的左侧必须是属性名。这通常是一个带引号的字符串。
如果我们省略引号,这意味着变量应包含要测试的实际名称。例如
let user = { age: 30 };
let key = "age";
alert( key in user ); // true, property "age" exists
in
运算符为什么存在?与 undefined
进行比较难道不够吗?
嗯,大多数情况下,与 undefined
的比较都能正常工作。但有一种特殊情况会失败,而 "in"
则能正确工作。
这是当对象属性存在,但存储 undefined
时
let obj = {
test: undefined
};
alert( obj.test ); // it's undefined, so - no such property?
alert( "test" in obj ); // true, the property does exist!
在上面的代码中,属性 obj.test
在技术上存在。因此,in
运算符可以正常工作。
这种情况很少发生,因为 undefined
不应显式分配。我们主要使用 null
表示“未知”或“空”值。因此,in
运算符是代码中的稀客。
“for..in” 循环
要遍历对象的全部键,有一种特殊的循环形式:for..in
。这与我们之前学习的 for(;;)
结构完全不同。
语法
for (key in object) {
// executes the body for each key among object properties
}
例如,我们输出 user
的所有属性
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// keys
alert( key ); // name, age, isAdmin
// values for the keys
alert( user[key] ); // John, 30, true
}
请注意,所有“for”结构都允许我们在循环内声明循环变量,如这里的 let key
。
此外,我们还可以在这里使用 key
以外的另一个变量名。例如,"for (let prop in obj)"
也被广泛使用。
按对象排序
对象是有序的吗?换句话说,如果我们遍历一个对象,我们是否会按照添加的顺序获得所有属性?我们可以依赖这个吗?
简短的回答是:“以特殊方式排序”:整数属性已排序,其他属性按创建顺序显示。详情如下。
例如,我们考虑一个带有电话区号的对象
let codes = {
"49": "Germany",
"41": "Switzerland",
"44": "Great Britain",
// ..,
"1": "USA"
};
for (let code in codes) {
alert(code); // 1, 41, 44, 49
}
该对象可用于向用户建议选项列表。如果我们主要为德国受众制作网站,那么我们可能希望 49
是第一个。
但是,如果我们运行代码,我们会看到完全不同的画面
- 美国 (1) 排在第一位
- 然后是瑞士 (41),依此类推。
电话区号按升序排列,因为它们是整数。因此,我们看到 1, 41, 44, 49
。
这里的“整数属性”一词是指可以不改变地转换为整数和从整数转换的字符串。
因此,"49"
是一个整数属性名,因为当它转换为整数并返回时,它仍然相同。但 "+49"
和 "1.2"
不是
// Number(...) explicitly converts to a number
// Math.trunc is a built-in function that removes the decimal part
alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property
alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property
alert( String(Math.trunc(Number("1.2"))) ); // "1", not same "1.2" ⇒ not integer property
…另一方面,如果键是非整数,则按创建顺序列出,例如
let user = {
name: "John",
surname: "Smith"
};
user.age = 25; // add one more
// non-integer properties are listed in the creation order
for (let prop in user) {
alert( prop ); // name, surname, age
}
因此,要解决电话代码的问题,我们可以通过将代码设为非整数来“作弊”。在每个代码前添加加号 "+"
就足够了。
像这样
let codes = {
"+49": "Germany",
"+41": "Switzerland",
"+44": "Great Britain",
// ..,
"+1": "USA"
};
for (let code in codes) {
alert( +code ); // 49, 41, 44, 1
}
现在按预期工作。
总结
对象是具有多个特殊功能的关联数组。
它们存储属性(键值对),其中
- 属性键必须是字符串或符号(通常是字符串)。
- 值可以是任何类型。
要访问属性,我们可以使用
- 点表示法:
obj.property
。 - 方括号表示法
obj["property"]
。方括号允许从变量中获取键,例如obj[varWithKey]
。
其他运算符
- 要删除属性:
delete obj.prop
。 - 要检查是否存在具有给定键的属性:
"key" in obj
。 - 要遍历对象:
for (let key in obj)
循环。
我们在本章中学习的内容称为“普通对象”,或简称 Object
。
JavaScript 中还有许多其他种类的对象
Array
用于存储有序数据集合,Date
用于存储有关日期和时间的信息,Error
用于存储有关错误的信息。- …等等。
它们有我们稍后会学习的特殊功能。有时人们会说“Array 类型”或“Date 类型”,但从形式上讲它们不是它们自己的类型,而是属于单个“object”数据类型。并且它们以各种方式对其进行扩展。
JavaScript 中的对象非常强大。在这里,我们只是粗略地了解了一个非常庞大的主题。我们将在本教程的后续部分与对象密切合作,并了解更多有关它们的信息。
评论
<code>
标记,对于多行 - 将它们包装在<pre>
标记中,对于 10 行以上 - 使用沙箱 (plnkr、jsbin、codepen…)