使用对象
JavaScript 基于对象范式设计。对象是属性的集合,而属性是名称(或键)与值之间的关联。属性的值可以是一个函数,在这种情况下,该属性称为方法。
JavaScript 中的对象,就像许多其他编程语言中的对象一样,可以与现实生活中的对象进行比较。在 JavaScript 中,对象是一个独立的实体,具有属性和类型。例如,将其与一个杯子进行比较。杯子是一个具有属性的对象。杯子有颜色、设计、重量、制作材料等。同样,JavaScript 对象可以有属性,这些属性定义了它们的特征。
除了浏览器中预定义的对象之外,你还可以定义自己的对象。本章描述了如何使用对象、属性和方法,以及如何创建自己的对象。
创建新对象
你可以使用对象初始化器来创建对象。或者,你可以首先创建构造函数,然后使用 new 运算符调用该函数来实例化一个对象。
使用对象初始化器
对象初始化器也称为对象字面量。“对象初始化器”与 C++ 中使用的术语一致。
使用对象初始化器创建对象的语法是
const obj = {
property1: value1, // property name may be an identifier
2: value2, // or a number
"property n": value3, // or a string
};
每个冒号前的属性名称都是一个标识符(可以是名称、数字或字符串字面量),每个 valueN 都是一个表达式,其值被赋给属性名称。属性名称也可以是表达式;计算键需要用方括号括起来。对象初始化器参考包含对语法的更详细解释。
在这个示例中,新创建的对象被分配给变量 obj —— 这是可选的。如果你不需要在其他地方引用这个对象,你就不需要将它分配给一个变量。(请注意,如果对象出现在预期语句的位置,你可能需要将对象字面量用括号括起来,以免字面量与块语句混淆。)
对象初始化器是表达式,每次执行包含对象初始化器的语句时,都会创建一个新对象。相同的对象初始化器会创建不同的对象,它们之间不相等。
以下语句仅当表达式 cond 为 true 时才创建对象并将其分配给变量 x
let x;
if (cond) {
x = { greeting: "hi there" };
}
以下示例创建了具有三个属性的 myHonda。请注意,engine 属性也是一个具有自己属性的对象。
const myHonda = {
color: "red",
wheels: 4,
engine: { cylinders: 4, size: 2.2 },
};
使用初始化器创建的对象称为普通对象,因为它们是 Object 的实例,而不是任何其他对象类型。某些对象类型具有特殊的初始化器语法——例如,数组初始化器和正则表达式字面量。
使用构造函数
或者,你可以通过以下两个步骤创建对象
- 通过编写构造函数来定义对象类型。有一个强烈的惯例,并且有充分的理由,使用大写首字母。
- 使用
new创建对象的实例。
要定义对象类型,请为对象类型创建一个函数,该函数指定其名称、属性和方法。例如,假设你想为汽车创建一种对象类型。你希望这种类型的对象称为 Car,并且你希望它具有制造厂商、型号和年份的属性。为此,你将编写以下函数
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
注意使用 this 根据传递给函数的值为对象的属性赋值。
现在你可以按如下方式创建名为 myCar 的对象
const myCar = new Car("Eagle", "Talon TSi", 1993);
此语句创建 myCar 并为其属性分配指定值。然后 myCar.make 的值是字符串 "Eagle",myCar.model 是字符串 "Talon TSi",myCar.year 是整数 1993,依此类推。参数和实参的顺序应该相同。
你可以通过调用 new 创建任意数量的 Car 对象。例如,
const randCar = new Car("Nissan", "300ZX", 1992);
const kenCar = new Car("Mazda", "Miata", 1990);
对象可以具有本身是另一个对象的属性。例如,假设你按如下方式定义了一个名为 Person 的对象
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
然后按如下方式实例化两个新的 Person 对象
const rand = new Person("Rand McKinnon", 33, "M");
const ken = new Person("Ken Jones", 39, "M");
然后,你可以重写 Car 的定义,以包含一个接受 Person 对象的 owner 属性,如下所示
function Car(make, model, year, owner) {
this.make = make;
this.model = model;
this.year = year;
this.owner = owner;
}
要实例化新对象,你将使用以下语句
const car1 = new Car("Eagle", "Talon TSi", 1993, rand);
const car2 = new Car("Nissan", "300ZX", 1992, ken);
请注意,在创建新对象时,上述语句不是传递字面量字符串或整数值,而是将对象 rand 和 ken 作为所有者的参数传递。然后,如果你想找出 car2 的所有者姓名,你可以访问以下属性
car2.owner.name;
你始终可以向先前定义的对象添加属性。例如,语句
car1.color = "black";
向 car1 添加了一个 color 属性,并将其值设置为 "black"。但是,这不会影响任何其他对象。要将新属性添加到所有相同类型的对象,你必须将该属性添加到 Car 对象类型的定义中。
使用 Object.create() 方法
对象也可以使用 Object.create() 方法创建。此方法非常有用,因为它允许你为要创建的对象选择原型对象,而无需定义构造函数。
// Animal properties and method encapsulation
const Animal = {
type: "Invertebrates", // Default value of properties
displayType() {
// Method which will display type of Animal
console.log(this.type);
},
};
// Create new animal type called `animal`
const animal = Object.create(Animal);
animal.displayType(); // Logs: Invertebrates
// Create new animal type called fish
const fish = Object.create(Animal);
fish.type = "Fishes";
fish.displayType(); // Logs: Fishes
对象和属性
JavaScript 对象具有与之关联的属性。对象属性与变量基本相同,只是它们与对象关联,而不是与作用域关联。对象的属性定义了对象的特征。
例如,此示例创建了一个名为 myCar 的对象,其属性名为 make、model 和 year,其值分别设置为 "Ford"、"Mustang" 和 1969
const myCar = {
make: "Ford",
model: "Mustang",
year: 1969,
};
与 JavaScript 变量一样,属性名称区分大小写。属性名称只能是字符串或 Symbol——所有键都会转换为字符串,除非它们是 Symbol。数组索引实际上是具有包含整数的字符串键的属性。
访问属性
你可以通过属性名称访问对象的属性。属性访问器有两种语法:点表示法和方括号表示法。例如,你可以按如下方式访问 myCar 对象的属性
// Dot notation
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;
// Bracket notation
myCar["make"] = "Ford";
myCar["model"] = "Mustang";
myCar["year"] = 1969;
对象属性名称可以是任何 JavaScript 字符串或符号,包括空字符串。但是,你不能使用点表示法访问名称不是有效 JavaScript 标识符的属性。例如,包含空格或连字符、以数字开头或存储在变量中的属性名称只能使用方括号表示法访问。当属性名称需要动态确定(即,直到运行时才能确定)时,此表示法也很有用。示例如下:
const myObj = {};
const str = "myString";
const rand = Math.random();
const anotherObj = {};
// Create additional properties on myObj
myObj.type = "Dot syntax for a key named type";
myObj["date created"] = "This key has a space";
myObj[str] = "This key is in variable str";
myObj[rand] = "A random number is the key here";
myObj[anotherObj] = "This key is object anotherObj";
myObj[""] = "This key is an empty string";
console.log(myObj);
// {
// type: 'Dot syntax for a key named type',
// 'date created': 'This key has a space',
// myString: 'This key is in variable str',
// '0.6398914448618778': 'A random number is the key here',
// '[object Object]': 'This key is object anotherObj',
// '': 'This key is an empty string'
// }
console.log(myObj.myString); // 'This key is in variable str'
在上面的代码中,键 anotherObj 是一个对象,它既不是字符串也不是符号。当它被添加到 myObj 时,JavaScript 会调用 anotherObj 的toString() 方法,并使用生成的字符串作为新键。
你还可以访问存储在变量中的具有字符串值的属性。变量必须以方括号表示法传递。在上面的示例中,变量 str 存储了 "myString",并且 "myString" 是属性名称。因此,myObj.str 将返回 undefined。
str = "myString";
myObj[str] = "This key is in variable str";
console.log(myObj.str); // undefined
console.log(myObj[str]); // 'This key is in variable str'
console.log(myObj.myString); // 'This key is in variable str'
这允许在运行时访问任何确定的属性
let propertyName = "make";
myCar[propertyName] = "Ford";
// access different properties by changing the contents of the variable
propertyName = "model";
myCar[propertyName] = "Mustang";
console.log(myCar); // { make: 'Ford', model: 'Mustang' }
但是,请注意不要使用方括号来访问名称由外部输入提供的属性。这可能会使你的代码容易受到对象注入攻击。
对象的不存在属性的值为 undefined(而不是 null)。
myCar.nonexistentProperty; // undefined
枚举属性
有三种原生方法可以列出/遍历对象属性
for...in循环。此方法遍历对象及其原型链的所有可枚举字符串属性。Object.keys()。此方法返回一个数组,其中仅包含对象myObj中可枚举的自有字符串属性名称(“键”),但不包括原型链中的属性。Object.getOwnPropertyNames()。此方法返回一个数组,其中包含对象myObj中所有自有字符串属性名称,无论它们是否可枚举。
你可以使用方括号表示法与 for...in 结合使用,以遍历对象的所有可枚举属性。为了说明其工作原理,以下函数在你将对象和对象名称作为参数传递给函数时显示对象的属性
function showProps(obj, objName) {
let result = "";
for (const i in obj) {
// Object.hasOwn() is used to exclude properties from the object's
// prototype chain and only show "own properties"
if (Object.hasOwn(obj, i)) {
result += `${objName}.${i} = ${obj[i]}\n`;
}
}
console.log(result);
}
术语“自有属性”指的是对象的属性,但不包括原型链中的属性。因此,函数调用 showProps(myCar, 'myCar') 将打印以下内容
myCar.make = Ford myCar.model = Mustang myCar.year = 1969
上述内容等同于
function showProps(obj, objName) {
let result = "";
Object.keys(obj).forEach((i) => {
result += `${objName}.${i} = ${obj[i]}\n`;
});
console.log(result);
}
没有原生方法可以列出所有继承属性,包括不可枚举的属性。但是,这可以通过以下函数实现
function listAllProperties(myObj) {
let objectToInspect = myObj;
let result = [];
while (objectToInspect !== null) {
result = result.concat(Object.getOwnPropertyNames(objectToInspect));
objectToInspect = Object.getPrototypeOf(objectToInspect);
}
return result;
}
有关更多信息,请参阅属性的可枚举性和所有权。
删除属性
你可以使用 delete 运算符删除非继承属性。以下代码显示了如何删除属性。
// Creates a new object, myObj, with two properties, a and b.
const myObj = { a: 5, b: 12 };
// Removes the a property, leaving myObj with only the b property.
delete myObj.a;
console.log("a" in myObj); // false
继承
JavaScript 中的所有对象都至少继承自另一个对象。被继承的对象称为原型,继承的属性可以在构造函数的 prototype 对象中找到。有关更多信息,请参阅继承和原型链。
为某种类型的所有对象定义属性
你可以使用 prototype 属性向通过特定构造函数创建的所有对象添加属性。这定义了一个由指定类型的所有对象共享的属性,而不是仅仅由对象的单个实例共享。以下代码将 color 属性添加到所有 Car 类型的对象,然后从实例 car1 读取属性的值。
Car.prototype.color = "red";
console.log(car1.color); // "red"
定义方法
方法是与对象关联的函数,或者换句话说,方法是对象的一个函数属性。方法的定义方式与普通函数相同,只是它们必须作为对象的属性进行赋值。另请参阅方法定义以获取更多详细信息。一个例子是
objectName.methodName = functionName;
const myObj = {
myMethod: function (params) {
// do something
},
// this works too!
myOtherMethod(params) {
// do something else
},
};
其中 objectName 是现有对象,methodName 是你分配给方法的名称,而 functionName 是函数的名称。
然后,你可以在对象的上下文中按如下方式调用该方法
objectName.methodName(params);
方法通常在构造函数的 prototype 对象上定义,以便所有相同类型的对象共享相同的方法。例如,你可以定义一个函数来格式化并显示先前定义的 Car 对象的属性。
Car.prototype.displayCar = function () {
const result = `A Beautiful ${this.year} ${this.make} ${this.model}`;
console.log(result);
};
注意使用 this 来引用方法所属的对象。然后你可以按如下方式为每个对象调用 displayCar 方法
car1.displayCar();
car2.displayCar();
使用 this 进行对象引用
JavaScript 有一个特殊关键字 this,你可以在方法中使用它来引用当前对象。例如,假设你有两个对象,Manager 和 Intern。每个对象都有自己的 name、age 和 job。在函数 sayHi() 中,请注意使用 this.name。当添加到这两个对象时,相同的函数将打印带有它所附加的相应对象的名称的消息。
const Manager = {
name: "Karina",
age: 27,
job: "Software Engineer",
};
const Intern = {
name: "Tyrone",
age: 21,
job: "Software Engineer Intern",
};
function sayHi() {
console.log(`Hello, my name is ${this.name}`);
}
// add sayHi function to both objects
Manager.sayHi = sayHi;
Intern.sayHi = sayHi;
Manager.sayHi(); // Hello, my name is Karina
Intern.sayHi(); // Hello, my name is Tyrone
this 是函数调用中“隐藏的参数”,通过在被调用的函数之前指定对象来传递。例如,在 Manager.sayHi() 中,this 是 Manager 对象,因为 Manager 在函数 sayHi() 之前。如果你从另一个对象访问相同的函数,this 也会改变。如果你使用其他方法调用函数,例如 Function.prototype.call() 或 Reflect.apply(),你可以显式地将 this 的值作为参数传递。
定义 Getter 和 Setter
Getter 是与属性关联的函数,用于获取特定属性的值。Setter 是与属性关联的函数,用于设置特定属性的值。它们共同可以间接表示属性的值。
Getter 和 setter 可以是
- 在对象初始化器中定义,或者
- 稍后添加到任何现有对象。
在对象初始化器中,getter 和 setter 的定义方式与常规方法类似,但前面带有关键字 get 或 set。getter 方法不能期望参数,而 setter 方法期望恰好一个参数(要设置的新值)。例如
const myObj = {
a: 7,
get b() {
return this.a + 1;
},
set c(x) {
this.a = x / 2;
},
};
console.log(myObj.a); // 7
console.log(myObj.b); // 8, returned from the get b() method
myObj.c = 50; // Calls the set c(x) method
console.log(myObj.a); // 25
myObj 对象的属性是
myObj.a— 一个数字myObj.b— 一个 getter,返回myObj.a加 1myObj.c— 一个 setter,将myObj.a的值设置为myObj.c被设置值的一半
Getter 和 setter 也可以在创建后的任何时间使用 Object.defineProperties() 方法添加到对象。此方法的第一个参数是要定义 getter 或 setter 的对象。第二个参数是一个对象,其属性名称是 getter 或 setter 名称,其属性值是用于定义 getter 或 setter 函数的对象。这是一个示例,定义了与前面示例中使用的相同的 getter 和 setter
const myObj = { a: 0 };
Object.defineProperties(myObj, {
b: {
get() {
return this.a + 1;
},
},
c: {
set(x) {
this.a = x / 2;
},
},
});
myObj.c = 10; // Runs the setter, which assigns 10 / 2 (5) to the 'a' property
console.log(myObj.b); // Runs the getter, which yields a + 1 or 6
选择这两种形式中的哪一种取决于你的编程风格和手头的任务。如果你可以更改原始对象的定义,你可能会通过原始初始化器定义 getter 和 setter。这种形式更紧凑和自然。但是,如果你需要在以后添加 getter 和 setter——也许是因为你没有编写特定的对象——那么第二种形式是唯一可能的形式。第二种形式更好地代表了 JavaScript 的动态特性,但它可能使代码难以阅读和理解。
比较对象
在 JavaScript 中,对象是引用类型。两个不同的对象永远不相等,即使它们具有相同的属性。只有比较相同的对象引用本身才返回 true。
// Two variables, two distinct objects with the same properties
const fruit = { name: "apple" };
const anotherFruit = { name: "apple" };
fruit == anotherFruit; // return false
fruit === anotherFruit; // return false
// Two variables, a single object
const fruit = { name: "apple" };
const anotherFruit = fruit; // Assign fruit object reference to anotherFruit
// Here fruit and anotherFruit are pointing to same object
fruit == anotherFruit; // return true
fruit === anotherFruit; // return true
fruit.name = "grape";
console.log(anotherFruit); // { name: "grape" }; not { name: "apple" }
有关比较运算符的更多信息,请参阅相等运算符。