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

所有这些都以相同的方式适用于用户定义的对象和系统定义的对象(例如ArraysDate)。

自EcmaScript 2015 (ES6)以来,语法提供了classextends等关键字,这些关键字用于基于类的面向对象编程。尽管引入了这样的关键字,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”。

下一个脚本演示了原型链。 它以用户定义的变量 myArraytheObject 开始。 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.getPrototypeOfObject.setPrototypeOfObject.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 中这种基于类的继承是在基于原型的经典方法之上实现的。

参见[编辑]