跳轉至內容

JavaScript/面向物件程式設計 - 古典模式

來自 Wikibooks,開放世界的開放書籍




JavaScript 面向物件方法的核心是物件的連結串列,其中每個物件都作為其後續物件的原型。當使用經典語法時,就會顯示出來。

有不同的語法方式可以 構造物件。它們並不相同,但即使在幕後觀察,你也會發現它們語義上的細微差別。所有變體都建立了一組鍵值對,即屬性。這組屬性組成了一個物件。

"use strict";

// construction via literals
const obj_1 = {};
obj_1.property_1 = "1";
alert(obj_1.property_1);

const obj_1a = {property_1a: "1a"};
alert(obj_1a.property_1a);


// construction via 'new' operator
const obj_2 = new Object();
obj_2.property_2 = "2";
alert(obj_2.property_2);

const obj_2a = new Object({property_2a: "2a"});
alert(obj_2a.property_2a);


// construction via 'Object.create' method
const obj_3 = Object.create({});
obj_3.property_3 = "3";
alert(obj_3.property_3);

const obj_3a = Object.create({property_3a: "3a"});
alert(obj_3a.property_3a);

三種語言結構 字面量newObject.create 建立簡單或複雜的物件。此類物件可以透過向附加屬性賦值來擴充套件。

鍵值對的“值”部分不僅可以包含原始資料型別的的值。它們也可以包含函式。(當函式是屬性的值部分時,它們被稱為 方法。)

"use strict";

// pure literal syntax for 'func_1'
const obj_1 = {
  property_1: "1",
  func_1: function () {return "Message from func_1: " + this.property_1}
};
// add a second function 'func_2'
obj_1.property_2 = "2";
obj_1.func_2 = function () {return "Message from func_2: " + this.property_2};

// invoke the two functions
alert(obj_1.func_1());
alert(obj_1.func_2());

在前面的示例中,我們定義了包含一個具有值的屬性和另一個具有方法的屬性的物件。這兩個部分都可以透過常用的點表示法訪問。但它們缺少一個智慧的語法特性:無法在第一次呼叫時直接定義它們的屬性。類似於 const x = new obj_1("valueOfProperty")const mike = new Person("Mike") 這樣的語法將無法執行,因為這種語法缺少屬性的名稱。

我們更改並擴充套件了上面的示例以允許使用這種語法,即與引數結合使用的 new 運算子。為此,我們定義了函式(它們也是物件),這些函式包含和儲存變數以及(“內部”)函式/方法。

"use strict";

function Person(name, isAlive) {
  this.name = name;
  this.isAlive = isAlive;
  // a (sub-)function to realize some functionality
  this.show = function () {return "The person's name is: " + this.name};
}

// creation via 'new'
const mike = new Person("Mike", true);
const john = new Person("John", false);

alert(mike.name + " / " + mike.show());
alert(john.name + " / " + john.show());

函式 Person 接受引數,就像其他任何函式一樣。它的第一個字母大寫,但這只是一個約定,不是強制的。如果使用 new 運算子呼叫函式,則在第一步中,將建立一個新的物件 構造。在函式中,可以使用關鍵字“this”引用新物件,例如,儲存給定的引數。此外,如果函式需要以(“內部”)函式的形式提供一些功能,你可以定義它們並將對它們的引用儲存在“this”中,並在一個任意名稱下 - 在示例中,它是“show”函式。

定義函式後,可以使用 new 運算子透過它來建立單個物件(例項)。這些單個物件儲存給定的引數並提供內部定義的函式。

請注意,new Person(...) 語句與不帶 new 運算子的函式的常規呼叫不同:Person(...)new 是必需的,以指示在函式體可以執行之前必須構造物件。如果沒有 new,JavaScript 引擎不會建立物件,並且使用“this”將失敗。

預定義資料型別

[編輯 | 編輯原始碼]

許多預定義資料型別(Date、Array 等)都是以這種方式定義的。因此,可以使用 new 運算子來建立它們:const arr = new Array([0, 1, 2])。引數儲存在新建立的物件的某個地方。這裡只有一個,一個字面表達的陣列。它被分解,陣列元素被儲存起來。一些派生屬性被計算出來,例如 length,並提供許多方法,例如 pushpop。總而言之,內部結構通常與外部可見形式不同。

除了使用 new 的這種統一語法之外,還有一些針對特定資料型別而設計的語法變體。例如,對於 Array,可以使用 const arr = [0, 1, 2]。但這只是對 const arr = new Array([0, 1, 2]) 的縮寫。

本章介紹了在層次結構中排列物件的各種語法可能性。

setPrototypeOf

[編輯 | 編輯原始碼]

如果你已經定義了獨立的物件,則可以在之後將它們連結在一起,以便它們建立父子關係。關鍵函式是 setPrototypeOf。顧名思義,該函式將一個物件設定為另一個物件的原型。透過這種方式,父物件的屬性(包括函式)可以被子物件訪問。

"use strict";

// two objects which are independent of each other (in the beginning)
const parent = {property_1: "1"};
const child  = {property_2: "2"};

// alert(child.property_1);  // undefined in the beginning

// link them together
Object.setPrototypeOf(child, parent);

alert(child.property_1);    // '1'

成功執行 setPrototypeOf 後,物件“擴充套件”了 物件。它可以訪問 物件的所有屬性,如第 12 行所示。

它是如何工作的?每個物件都包含一個名為“__proto__”的屬性,即使它在原始碼中沒有任何地方提到。它的值引用了充當其“父”物件的該物件。同樣,“父”物件也包含這樣的“__proto__”屬性,依此類推。在最高級別,該值為 null,表示層次結構的結束。總而言之,它是一個物件的“連結串列”。它被稱為 原型鏈。它是 JavaScript OOP 實現的核心:“父”物件充當引用物件的“原型” - 對於所有系統物件以及所有使用者定義的物件。

無論何時搜尋任何屬性,JavaScript 引擎都會使用 原型鏈。當引擎找不到它時,它會切換到下一級並重復搜尋。

這同樣適用於搜尋函式的情況。

"use strict";

const parent = {
  property_1: "1",
  func_1: function () {return "Message from func_1: " + this.property_1}
};
const child = {
  property_2: "2",
  func_2: function () {return "Message from func_2: " + this.property_2}
};

// alert(child.func_1());  // not possible at the beginning
Object.setPrototypeOf(child, parent);

alert(child.func_1());  // '1'

在第 13 行之後,物件可以呼叫方法 func_1,儘管它是由 物件定義的。

假設你事先知道一個物件將充當另一個物件的子物件。在這種情況下,new 運算子提供了從一開始就定義依賴關係的可能性。現有物件可以作為引數傳遞給建立過程。JavaScript 引擎將透過完全相同的機制(“__proto__”屬性)將現有物件與新建立的物件組合起來。

"use strict";

const parent = {property_1: "1"}

// inheritance via 'new' operator
const child = new Object(parent);
alert(child.property_1);

Object.create

[編輯 | 編輯原始碼]

這種預先已知的層次關係也可以透過 Object.create 方法來實現。

"use strict";

const parent = {property_1: "1"}

// construction via 'Object.create'
const child = Object.create(parent);
alert(child.property_1);

與基於類的方案的區別

[編輯 | 編輯原始碼]

JavaScript 的原型式方法與基於類的方案之間存在一些區別。這裡展示了其中一個關於繼承的區別。

在使用上述方法之一建立原型層次結構和例項之後,您可以修改“父”例項以同時操作所有“子”例項。

"use strict";

// construction of a small hierarchy
const parent   = {property_1: "1"}
const child_11 = {property_11: "11"}
const child_12 = {property_12: "12"}

Object.setPrototypeOf(child_11, parent);
Object.setPrototypeOf(child_12, parent);

// show that none of the instances contains a property 'property_2'
alert(parent.property_2);    // undefined
alert(child_11.property_2);  // undefined
alert(child_12.property_2);  // undefined

// a single statement adds 'property_2' to all three instances:
parent.property_2 = "2";
alert(parent.property_2);    // 2
alert(child_11.property_2);  // 2
alert(child_12.property_2);  // 2

第 17 行的語句實際上將屬性“property_2”新增到了所有例項。當後面的語句獲取“property_2”時,JavaScript 引擎將遵循原型鏈。首先,它將在“子”例項中找不到“property_2”。但透過遵循原型鏈,它將在“父”例項中找到它。對於“子”例項而言,屬性是在它自己的空間還是在它的父空間中並沒有區別。

與基於類的方案的區別在於,不僅添加了新屬性的值,而且擴充套件了所有例項的結構:新增的屬性在第 17 行之前根本不存在。

檢查物件層次結構

[編輯 | 編輯原始碼]

有多種方法可以檢查任何變數或值的 資料型別的層次結構。

getPrototypeOf

[編輯 | 編輯原始碼]

getPrototypeOf 方法讓您有機會檢查層次結構。它返回父物件本身,而不是它的資料型別。如果您對父型別的資料型別感興趣,則必須使用其他運算子之一檢查父型別的資料型別。

"use strict";

const parent = {property_1: "1"}
const child_1 = Object.create(parent);

// use 'console.log'; it's more meaningful than 'alert'
console.log(Object.getPrototypeOf(child_1));  // {property_1: "1"}

const arr = [0, 1, 2];
const child_2 = Object.create(arr);
console.log(Object.getPrototypeOf(child_2)); // [0, 1, 2]
console.log(Object.getPrototypeOf(arr));     // []

或者,在一個靈活的迴圈中遵循原型鏈

"use strict";

// define an array with three elements
const myArray = [0, 1, 2];
let theObject = myArray;

do {
  // show the object's prototype
  console.log(Object.getPrototypeOf(theObject));  // Array[], Object{...}, null

  // switch to the next higher level
  theObject = Object.getPrototypeOf(theObject);
} while (theObject);

instanceof

[編輯 | 編輯原始碼]

instanceof 運算子測試變數的原型鏈是否包含給定的資料型別。它返回一個布林值。

"use strict";

// define an array with three elements
const myArray = [0, 1, 2];
alert (myArray instanceof Array);   // true
alert (myArray instanceof Object);  // true
alert (myArray instanceof Number);  // false

typeof 運算子返回一個字串,顯示其運算元的資料型別。但它僅限於檢測某些資料型別或其父物件。可能的返回值為:“undefined”、“object”、“boolean”、“number”、“bigint”、“string”、“symbol”、“function”。

"use strict";

// define an array with three elements
const myArray = [0, 1, 2];
let theObject = myArray;

do {
  // show the object's prototype
  console.log(typeof theObject);  // object, object, object

  // switch to the next higher level
  theObject = Object.getPrototypeOf(theObject);
} while (theObject);
... 在另一個頁面提供(點選此處)。

另請參閱

[編輯 | 編輯原始碼]
華夏公益教科書