Board logo

标题: JavaScript 中的类-1 [打印本页]

作者: look_w    时间: 2018-1-20 21:19     标题: JavaScript 中的类-1

对象简史JavaScript 最初被设想和宣传为 Java 的轻量型版本,所以它通常被认为是一种面向对象的传统语言。得益于 new        关键词,它似乎在语法上类似于过去常常在 Java 或 C++ 中看到的语法。
事实上,JavaScript 不是基于类的环境,而是一个基于对象的环境。如果您不熟悉或仅偶尔参与面向对象的开发,JavaScript        可能对您无关紧要,但理解它们的区别仍然很重要。在基于对象的环境中,不存在类。每个对象是从另一个现有对象克隆而来的,而不是来自类。当克隆一个对象时,会保持对其原型对象的隐式引用。
在基于对象的环境中工作有其优势,但在没有基于类的概念(比如属性和继承)的情况下能执行的操作上也存在局限。ECMAScript 技术委员会曾经试图将面向对象的元素集成到        JavaScript 中,而不牺牲它的独特风格。在 ECMAScript 6 中,该委员会最终找到了实现途径。
类定义从一开始就采用 class 关键字可能是最容易的实现途径。如下所示,此关键字表示一个新 ECMAScript 类的定义:
清单 1. 定义新类
class Person
{
}

let p = new Person();




空类本身不是很有趣。毕竟,每个人都有姓名和年龄,Person 类应该反映出这一点。我们可以在构造类实例时,通过引入构造函数来添加这些细节:
清单 2. 构造类实例
class Person
{
  constructor(firstName, lastName, age)
  {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
}

let ted = new Person("Ted", "Neward", 45);
console.log(ted);




构造函数是一个特殊函数,会在构造过程中被调用。任何作为 new 运算符的一部分而传递给 type          的参数都被传递给构造函数。但是不要误解:constructor 仍然是 ECMAScript 函数。您可以利用它类似 JavaScript        的灵活参数,以及隐式的 arguments 参数,就象这样:
清单 3. 灵活的参数和隐式参数
class Person
{
  constructor(firstName, lastName, age)
  {
    console.log(arguments);
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
}

let ted = new Person("Ted", "Neward", 45);
console.log(ted);
let cher = new Person("Cher");
console.log(cher);
let r2d2 = new Person("R2", "D2", 39, "Astromech Droid");
console.log(r2d2);




尽管该语言委员的目的显然是让 JavaScript 开发人员能够编写更加传统的面向类的代码,但他们会还想支持 ECMAScript        目前所具有的灵活性和开放性。理想情况下,这意味着开发人员能各取所长。
属性和封装无法公开和维护其状态的类不是一个真正的类。因此,ECMAScript 6 现在允许开发人员定义伪装为字段的属性函数。这为我们设定了 ECMAScript 中的各种封装风格。
考虑 Person 类。firstName、lastName 和 age        作为成熟的属性是合理的,我们将它们定义如下:
清单 4. 定义属性
class Person
{
  constructor(firstName, lastName, age)
  {
    console.log(arguments);
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  get firstName() { return this._firstName; }
  set firstName(value) { this._firstName = value; }
  get lastName() { return this._lastName; }
  set lastName(value) { this._lastName = value; }
  get age() { return this._age; }
  set age(value) { this._age = value; }
}




请注意 getter 和 setter(根据 ECMAScript 规范中的官方规定)是如何引用字段名称的,字段名称添加了一条下划线作为前缀。这意味着          Person 现在有 6 个函数和 3 个字段 — 每个属性有 2 个函数和 1 个字段。不同于其他语言,ECMAScript 中的          property 语法不会在创建属性时静默地引入后备存储字段。(后备存储 是存储数据的地方 —        换句话说,是实际字段本身。)
属性不需要逐个地直接反映类的内部状态。事实上,属性的封装性质很大程度上是为了部分或完整地隐藏内部状态:
清单 5. 封装隐藏状态
class Person
{
  // ... as before

  get fullName() { return this._firstName + " " + this._lastName; }
  get surname() { return this._lastName; }
  get givenName() { return this._firstName; }
}




但是,请注意,属性语法没有消除您直接获取字段的能力。您仍然可以使用熟悉的 ECMAScript 原理,枚举一个对象来获得它的内部结构:
清单 6. 枚举一个对象
for (let m in ted) {
  console.log(m,ted[m]);
    // prints
    //   "_firstName,Ted"
    //   "_lastName,Neward"
    //   "_age,45"
}




您还可以使用 Object 定义的 getAllPropertyNames() 函数来检索同一个列表。
现在有一个有趣的问题:如果对象本身上没有 firstName、lastName 和 age 的        getter 和 setter 函数,那么如何在没有严格的解释器能力的情况下解析类似 “ted.firstName” 的表达式?
答案既简单又优雅:ted(Person 的实例)保留了与它的类 Person 的原型链接。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0