使用物件

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 為 true 時才建立物件並將其分配給變數 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 randCar = new Car("Nissan", "300ZX", 1992);
const kenCar = 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 `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 的物件,其屬性名為 makemodelyear,其值分別設定為 "Ford""Mustang"1969

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

與 JavaScript 變數一樣,屬性名稱區分大小寫。屬性名稱只能是字串或 Symbol——所有鍵都會轉換為字串,除非它們是 Symbol。陣列索引實際上是具有包含整數的字串鍵的屬性。

訪問屬性

你可以透過屬性名稱訪問物件的屬性。屬性訪問器有兩種語法:點表示法方括號表示法。例如,你可以按如下方式訪問 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 將返回 undefined。

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 = { 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 讀取屬性的值。

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

Getter 是與屬性關聯的函式,用於獲取特定屬性的值。Setter 是與屬性關聯的函式,用於設定特定屬性的值。它們共同可以間接表示屬性的值。

Getter 和 setter 可以是

物件初始化器中,getter 和 setter 的定義方式與常規方法類似,但前面帶有關鍵字 getset。getter 方法不能期望引數,而 setter 方法期望恰好一個引數(要設定的新值)。例如

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 — 一個 getter,返回 myObj.a 加 1
  • myObj.c — 一個 setter,將 myObj.a 的值設定為 myObj.c 被設定值的一半

Getter 和 setter 也可以在建立後的任何時間使用 Object.defineProperties() 方法新增到物件。此方法的第一個引數是要定義 getter 或 setter 的物件。第二個引數是一個物件,其屬性名稱是 getter 或 setter 名稱,其屬性值是用於定義 getter 或 setter 函式的物件。這是一個示例,定義了與前面示例中使用的相同的 getter 和 setter

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

選擇這兩種形式中的哪一種取決於你的程式設計風格和手頭的任務。如果你可以更改原始物件的定義,你可能會透過原始初始化器定義 getter 和 setter。這種形式更緊湊和自然。但是,如果你需要在以後新增 getter 和 setter——也許是因為你沒有編寫特定的物件——那麼第二種形式是唯一可能的形式。第二種形式更好地代表了 JavaScript 的動態特性,但它可能使程式碼難以閱讀和理解。

比較物件

在 JavaScript 中,物件是引用型別。兩個不同的物件永遠不相等,即使它們具有相同的屬性。只有比較相同的物件引用本身才返回 true。

js
// 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
js
// 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" }

有關比較運算子的更多資訊,請參閱相等運算子

另見