JavaScript/面向对象编程
“面向对象编程”(OOP) 是一种软件设计范式,最早出现于20世纪60年代,并在90年代流行起来。它致力于模块化(modularization)、可重用性(reusability)、状态(数据)和行为(函数)的封装和隐藏,泛化、继承等层次结构的设计。
它允许组件尽可能模块化。特别是,当创建新的对象类型时,期望把它放置在不同的环境或新的编程项目中时应该能够正常工作。这种方法的好处是更短的开发时间和更容易的调试,因为您正在重复使用已经经过验证的程序代码。这种“黑匣子”方法意味着数据进入对象,其他数据从对象中出来,但内部发生的事情并不是您需要关心的。
随着时间的推移,已经开发出不同的技术来实现 OOP。最流行的是基于类和基于原型的方法。
基于类的面向对象编程
[编辑]类是一个蓝图,定义了一组结构上相同的对象的所有方面(状态和行为)。蓝图称为类,类的实例称为对象。 C系列语言的流行成员,尤其是Java、C++和C#,使用这种基于类的方法来实现OOP。
基于原型的面向对象编程
[编辑]在基于原型的方法中,每个对象都存储其状态和行为。此外,它还有一个原型(如果它位于层次结构的顶部,则为 null)。 这样的原型是指向另一个更通用对象的指针。被引用对象的所有属性也可在引用对象中使用。基于原型的方法中不存在类。
JavaScript中的面向对象编程:“一人穿两件夹克”
[编辑]JavaScript的基石之一是根据基于原型的面向对象编程规则提供对象。对象由作为保存数据的键/值对的属性以及方法组成。 这些属性之一始终是原型属性“__proto__”。它指向“父”对象,并由此实现关系。
// relationships are realized by the property '__proto__'
let parent = [];
let child = {
name: "Joel",
__proto__: parent,
};
console.log(Object.getPrototypeOf(child)); // Array []
如果任何对象上缺少请求的属性,JavaScript引擎会在“父”对象、“祖父”对象等祖先对象中搜索该属性。这称为原型链(prototype chain)。
所有这些都以相同的方式适用于用户定义的对象和系统定义的对象(例如Arrays
或Date
)。
自EcmaScript 2015 (ES6)以来,语法提供了class
或extends
等关键字,这些关键字用于基于类的面向对象编程。尽管引入了这样的关键字,JavaScript的基础并没有改变:这些关键字以与以前相同的方式生成原型。它们是语法糖,并按照传统的原型技术进行编译。
总之,JavaScript 的语法提供了两种方式来表达面向对象的功能,例如源代码中的继承:“经典”风格和“类”风格。 尽管语法不同,但实现技术仅略有不同。
经典语法
[编辑]从诞生之日起,JavaScript就用“原型”机制定义了对象的父/子关系。如果在源代码中没有明确表示,这将自动发生。经典语法很好地展示了它。
要明确定义两个对象的父/子关系,应使用setPrototypeOf
方法将对象的原型设置为其他专门对象。方法运行后,父对象的所有属性(包括函数)都为子对象所知。
<!DOCTYPE html>
<html>
<head>
<script>
function go() {
"use strict";
const adult = {
familyName: "McAlister",
showFamily: function() {return "The family name is: " + this.familyName;}
};
const child = {
firstName: "Joel",
kindergarten: "House of Dwars"
};
// 'familyName' and 'showFamily()' are undefined here!
alert(child.firstName + " " + child.familyName);
// set the intended prototype chain
Object.setPrototypeOf(child, adult);
// or: child.__proto__ = adult;
alert(child.firstName + " " + child.familyName);
alert(child.showFamily());
}
</script>
</head>
<body id="body">
<button onclick="go()">Run the demo</button>
</body>
</html>
“adult”对象包含“familyName”和一个函数“showFamily”。 在第一步中,它们在对象“child”中是未知的。 setPrototypeOf
运行后,它们是已知的,因为“child”原型不再指向默认的“对象”,而是指向“adult”。
下一个脚本演示了原型链。 它以用户定义的变量 myArray
和 theObject
开始。 myArray
是一个包含三个元素的数组。 第 6 行中的赋值操作将 theObject
设置为同一个数组。 循环显示 theObject
的原型,并将原型链的下一个更高级别分配给它。 当到达层次结构的顶层时,循环结束。 在本例中,原型为 null
。
function go() {
"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
// or: console.log(theObject.__proto__);
// switch to the next higher level
theObject = Object.getPrototypeOf(theObject);
} while (theObject);
}
如您所知,“properties”是键/值对。 因此,可以直接使用“prototype”属性的值来识别和操作原型。 有趣的是,键的名称不是“prototype”,而是“__proto__”。 这如第 11 行所示。不过,我们建议忽略此技术并使用 API 方法进行原型操作,例如 Object.getPrototypeOf
、Object.setPrototypeOf
和 Object.create
。
“类”语法
[编辑]该脚本定义了两个类“Adult”和“Child”,它们具有一些内部属性,其中一个是方法。 关键字extends
按层次结构组合了两个类。 然后,在第 21 行,使用关键字g a method. The keyword extends
combines the two classes hierarchically. Afterward, in line 21, an instance is created with the keyword 创建了一个实例。
<!DOCTYPE html>
<html>
<head>
<script>
function go() {
"use strict";
class Adult {
constructor(familyName) {
this.familyName = familyName;
}
showFamily() {return "The family name is: " + this.familyName;}
}
class Child extends Adult {
constructor(firstName, familyName, kindergarten) {
super(familyName);
this.firstName = firstName;
this.kindergarten = kindergarten;
}
}
const joel = new Child("Joel", "McAlister", "House of Dwars");
alert(joel.firstName + " " + joel.familyName);
alert(joel.showFamily());
}
</script>
</head>
<body id="body">
<button onclick="go()">Run the demo</button>
</body>
</html>
属性familyName
和方法showFamily
在“Adult”类中定义。 但他们在“Child”类别中也是。
请再次注意,JavaScript 中这种基于类的继承是在基于原型的经典方法之上实现的。