跳转至内容

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);
... 在另一个页面提供(点击此处)。

另请参阅

[编辑 | 编辑源代码]
华夏公益教科书