使用对象

JavaScript 基于简单的基于对象的范式设计。对象是 属性 的集合,属性是名称(或)和值之间的关联。属性的值可以是函数,在这种情况下,该属性称为 方法

JavaScript 中的对象,就像许多其他编程语言一样,可以与现实生活中的对象进行比较。在 JavaScript 中,对象是一个独立的实体,具有属性和类型。例如,将其与杯子进行比较。杯子是一个对象,具有属性。杯子有颜色、图案、重量、材质等。同样,JavaScript 对象可以具有属性,这些属性定义了它们的特征。

除了浏览器中预定义的对象之外,您还可以定义自己的对象。本章介绍如何使用对象、属性和方法,以及如何创建自己的对象。

创建新对象

您可以使用 对象初始化器 创建对象。或者,您可以先创建一个构造函数,然后通过使用 new 运算符调用该函数来实例化一个对象。

使用对象初始化器

对象初始化器也称为对象字面量。“对象初始化器”与 C++ 中使用的术语一致。

使用对象初始化器的对象的语法为

js
const obj = {
  property1: value1, // property name may be an identifier
  2: value2, // or a number
  "property n": value3, // or a string
};

冒号之前的每个属性名称都是一个标识符(名称、数字或字符串字面量),并且每个 valueN 都是一个表达式,其值为分配给属性名称。属性名称也可以是表达式;计算的键需要用方括号括起来。 对象初始化器 参考包含对语法的更详细解释。

在此示例中,新创建的对象被分配给变量 obj——这是可选的。如果您不需要在其他地方引用此对象,则不需要将其分配给变量。(请注意,如果对象出现在期望语句的位置,则可能需要将对象字面量括在括号中,以避免将字面量与块语句混淆。)

对象初始化器是表达式,并且每当执行包含它的语句时,每个对象初始化器都会导致创建一个新对象。相同的对象初始化器会创建不同的对象,这些对象彼此之间不比较相等。

以下语句仅当表达式 cond 为真时才创建对象并将其分配给变量 x

js
let x;
if (cond) {
  x = { greeting: "hi there" };
}

以下示例创建了具有三个属性的 myHonda。请注意,engine 属性也是一个具有自身属性的对象。

js
const myHonda = {
  color: "red",
  wheels: 4,
  engine: { cylinders: 4, size: 2.2 },
};

使用初始化器创建的对象称为普通对象,因为它们是 Object 的实例,而不是任何其他对象类型。某些对象类型具有特殊的初始化器语法——例如, 数组初始化器正则表达式字面量

使用构造函数

或者,您可以通过以下两个步骤创建对象

  1. 通过编写构造函数来定义对象类型。出于充分的理由,有一个强有力的约定是使用大写初始字母。
  2. 使用 new 创建对象的实例。

要定义对象类型,请为对象类型创建一个函数,该函数指定其名称、属性和方法。例如,假设您要为汽车创建对象类型。您希望这种类型的对象称为 Car,并且希望它具有用于制造商、型号和年份的属性。为此,您将编写以下函数

js
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

请注意使用 this 根据传递给函数的值为对象的属性赋值。

现在您可以如下创建名为 myCar 的对象

js
const myCar = new Car("Eagle", "Talon TSi", 1993);

此语句创建 myCar 并为其属性分配指定的值。然后 myCar.make 的值为字符串 "Eagle"myCar.model 为字符串 "Talon TSi"myCar.year 为整数 1993,依此类推。参数和参数的顺序应该相同。

您可以通过对 new 的调用创建任意数量的 Car 对象。例如,

js
const kenscar = new Car("Nissan", "300ZX", 1992);
const vpgscar = new Car("Mazda", "Miata", 1990);

对象可以具有本身是另一个对象的属性。例如,假设您按如下方式定义一个名为 Person 的对象

js
function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}

然后按如下方式实例化两个新的 Person 对象

js
const rand = new Person("Rand McKinnon", 33, "M");
const ken = new Person("Ken Jones", 39, "M");

然后,您可以重写 Car 的定义以包含一个采用 Person 对象的 owner 属性,如下所示

js
function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
}

要实例化新对象,您将使用以下内容

js
const car1 = new Car("Eagle", "Talon TSi", 1993, rand);
const car2 = new Car("Nissan", "300ZX", 1992, ken);

请注意,在创建新对象时,以上语句不是传递文字字符串或整数值,而是将对象 randken 作为所有者的参数传递。然后,如果您想了解 car2 所有者的姓名,您可以访问以下属性

js
car2.owner.name;

您始终可以向先前定义的对象添加属性。例如,语句

js
car1.color = "black";

car1 添加一个属性 color,并将其值设置为 "black"。但是,这不会影响任何其他对象。要将新属性添加到所有相同类型的对象,您必须将该属性添加到 Car 对象类型的定义中。

您还可以使用 class 语法而不是 function 语法来定义构造函数。有关更多信息,请参阅 类指南

使用 Object.create() 方法

还可以使用 Object.create() 方法创建对象。此方法非常有用,因为它允许您为要创建的对象选择 原型 对象,而无需定义构造函数。

js
// 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 animal1
const animal1 = Object.create(Animal);
animal1.displayType(); // Logs: Invertebrates

// Create new animal type called fish
const fish = Object.create(Animal);
fish.type = "Fishes";
fish.displayType(); // Logs: Fishes

对象和属性

JavaScript 对象与其关联的属性。对象属性基本上与变量相同,只是它们与对象关联,而不是与 作用域 关联。对象的属性定义了对象的特征。

例如,此示例创建了一个名为 myCar 的对象,其属性名为 makemodelyear,其值分别设置为 "Ford""Mustang"1969

js
const myCar = {
  make: "Ford",
  model: "Mustang",
  year: 1969,
};

与 JavaScript 变量一样,属性名称区分大小写。属性名称只能是字符串或符号——所有键都会 转换为字符串,除非它们是符号。 数组索引 实际上是具有包含整数的字符串键的属性。

访问属性

您可以通过其属性名称访问对象的属性。 属性访问器 有两种语法:点表示法方括号表示法。例如,您可以按如下方式访问 myCar 对象的属性

js
// Dot notation
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;

// Bracket notation
myCar["make"] = "Ford";
myCar["model"] = "Mustang";
myCar["year"] = 1969;

对象属性名可以是任何 JavaScript 字符串或符号,包括空字符串。但是,如果属性名不是有效的 JavaScript 标识符,则无法使用点表示法访问该属性。例如,属性名中包含空格或连字符、以数字开头或存储在变量中的属性,只能使用方括号表示法访问。当属性名需要动态确定时,即在运行时才能确定时,这种表示法也非常有用。示例如下

js
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 会调用anotherObjtoString()方法,并使用结果字符串作为新的键。

您还可以访问存储在变量中的字符串值属性。该变量必须以方括号表示法传递。在上面的示例中,变量str保存了"myString",并且"myString"就是属性名。因此,myObj.str将返回未定义。

js
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'

这允许访问在运行时确定的任何属性

js
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)。

js
myCar.nonexistentProperty; // undefined

枚举属性

有三种原生方法可以列出/遍历对象属性

  • for...in循环。此方法遍历对象的全部可枚举字符串属性及其原型链。
  • Object.keys()。此方法返回一个数组,其中仅包含对象myObj中可枚举的自身字符串属性名(“键”),而不包括原型链中的键。
  • Object.getOwnPropertyNames()。此方法返回一个数组,其中包含对象myObj中所有自身字符串属性名,无论它们是否可枚举。

您可以将方括号表示法与for...in一起使用来迭代对象的全部可枚举属性。为了说明其工作原理,以下函数在您将对象和对象的名称作为参数传递给该函数时显示对象的属性

js
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

以上等价于

js
function showProps(obj, objName) {
  let result = "";
  Object.keys(obj).forEach((i) => {
    result += `${objName}.${i} = ${obj[i]}\n`;
  });
  console.log(result);
}

没有原生方法可以列出继承的不可枚举属性。但是,可以使用以下函数实现此目的

js
function listAllProperties(myObj) {
  let objectToInspect = myObj;
  let result = [];

  while (objectToInspect !== null) {
    result = result.concat(Object.getOwnPropertyNames(objectToInspect));
    objectToInspect = Object.getPrototypeOf(objectToInspect);
  }

  return result;
}

有关更多信息,请参阅属性的可枚举性和所有权

删除属性

您可以使用delete运算符删除非继承属性。以下代码演示了如何删除属性。

js
// Creates a new object, myobj, with two properties, a and b.
const myobj = new Object();
myobj.a = 5;
myobj.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属性为通过某个构造函数创建的所有对象添加属性。这定义了一个由指定类型的所有对象共享的属性,而不是仅由对象的一个实例共享的属性。以下代码为Car类型的所有对象添加了一个color属性,然后从实例car1读取该属性的值。

js
Car.prototype.color = "red";
console.log(car1.color); // "red"

定义方法

方法是与对象关联的函数,或者换句话说,方法是对象的属性,该属性是一个函数。方法的定义方式与普通函数的定义方式相同,只是它们必须作为对象的属性分配。有关更多详细信息,另请参阅方法定义。例如

js
objectName.methodName = functionName;

const myObj = {
  myMethod: function (params) {
    // do something
  },

  // this works too!
  myOtherMethod(params) {
    // do something else
  },
};

其中objectName是现有对象,methodName是您分配给方法的名称,functionName是函数的名称。

然后,您可以按如下方式在对象的上下文中调用该方法

js
objectName.methodName(params);

方法通常在构造函数的prototype对象上定义,以便相同类型的全部对象共享相同的方法。例如,您可以定义一个函数来格式化和显示先前定义的Car对象的属性。

js
Car.prototype.displayCar = function () {
  const result = `A Beautiful ${this.year} ${this.make} ${this.model}`;
  console.log(result);
};

请注意使用this来引用方法所属的对象。然后,您可以按如下方式为每个对象调用displayCar方法

js
car1.displayCar();
car2.displayCar();

使用this作为对象引用

JavaScript 有一个特殊的关键字this,您可以在方法中使用它来引用当前对象。例如,假设您有两个对象,ManagerIntern。每个对象都有自己的nameagejob。在函数sayHi()中,请注意this.name的使用。当添加到这两个对象时,相同的函数将使用其附加到的相应对象的名称打印消息。

js
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()中,thisManager对象,因为Manager位于函数sayHi()之前。如果您从另一个对象访问相同的函数,this也将发生更改。如果您使用其他方法调用函数,例如Function.prototype.call()Reflect.apply(),则可以显式地将this的值作为参数传递。

定义 getter 和 setter

获取器是与获取特定属性值的属性关联的函数。设置器是与设置特定属性值的属性关联的函数。它们一起可以间接表示属性的值。

获取器和设置器可以是

对象初始化程序中,获取器和设置器的定义方式与常规方法相同,但以关键字getset为前缀。获取器方法不得期望参数,而设置器方法恰好期望一个参数(要设置的新值)。例如

js
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 - 一个返回myObj.a加1的获取器
  • myObj.c - 一个将myObj.a的值设置为myObj.c被设置为的值的一半的设置器

获取器和设置器也可以在创建后随时添加到对象中,方法是使用Object.defineProperties()方法。此方法的第一个参数是要在其上定义获取器或设置器的对象。第二个参数是一个对象,其属性名是获取器或设置器名称,其属性值是用于定义获取器或设置器函数的对象。以下示例定义了与上一个示例中使用的相同的获取器和设置器

js
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

选择哪种形式取决于您的编程风格和手头的任务。如果您可以更改原始对象的定义,则可能会通过原始初始化程序定义获取器和设置器。这种形式更紧凑且更自然。但是,如果您需要稍后添加获取器和设置器——也许是因为您没有编写特定的对象——那么第二种形式是唯一可能的。第二种形式更好地体现了 JavaScript 的动态特性,但它可能会使代码难以阅读和理解。

比较对象

在 JavaScript 中,对象是引用类型。两个不同的对象永远不相等,即使它们具有相同的属性。只有将相同的对象引用与其自身进行比较才会产生真值。

js
// Two variables, two distinct objects with the same properties
const fruit = { name: "apple" };
const fruitbear = { name: "apple" };

fruit == fruitbear; // return false
fruit === fruitbear; // return false
js
// Two variables, a single object
const fruit = { name: "apple" };
const fruitbear = fruit; // Assign fruit object reference to fruitbear

// Here fruit and fruitbear are pointing to same object
fruit == fruitbear; // return true
fruit === fruitbear; // return true

fruit.name = "grape";
console.log(fruitbear); // { name: "grape" }; not { name: "apple" }

有关比较运算符的更多信息,请参阅相等运算符

另请参阅