首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

JavaScript 中的类-2

JavaScript 中的类-2

原型链从最初开始,JavaScript 就保留着从一个对象到另一个对象的原型链。您可能认为,原型链类似于 Java 或 C++/C#        中的继承,但两种技术之间只有一个真正的相似点:当 JavaScript 需要解析一个没有直接包含在对象上的符号时,它会沿原型链查找可能的匹配值。
这不太容易理解,所以我要再说明一下。想象您使用旧式 JavaScript 样式定义了一个非常简单的对象:
清单 7. 旧式 JavaScript        对象
var obj = {};




现在,您需要获取该对象的字符串表示。通常,toString() 方法会为您完成这项工作,但 obj        上没有定义该函数,事实上,它之上什么都没有定义。该代码不仅能运行,还会返回结果:
清单 8. 结果字符串
var obj = {};
console.log(obj.toString()); // prints "[object Object]"




当解释器寻找 toString 作为 obj 对象上的名称时,它没有找到匹配值。它没有立即找到该对象的原型对象,所以它在原型中搜索          toString。如果仍然没有找到匹配值,那么它会查找原型的原型,依此类推。在这种特定情况下,obj          的原型(Object 对象)上定义了一个 toString。
现在让我们返回到 Person 类。您应该很清楚具体的情形:对象 ted 有一个对对象 Person          的原型引用,Person 拥有方法对 firstName、lastName 和          age,它们被定义为 getter 和 setter。当使用一个 getter 或 setter 时,该语言会尊重原型,代表          ted 实例本身来执行它。
Person 类上定义的所有方法均如此,您在我们添加新方法时就会看到:
清单 9. 将一个方法添加到        Person
class Person
{
  // ... as before

  getOlder() {
    return ++this.age;
  }
}




新方法允许以 Person 为原型的实例优雅地老化,如下所示:
清单 10. 沿原型链查找
ted.getOlder();
console.log(ted);
// prints Person { _firstName: 'Ted', _lastName: 'Neward', _age: 46 }




getOlder 方法是在 Person 对象上定义的,所以在调用 ted.getOlder()        时,解释器会沿原型链从 ted 查找到 Person。然后它会找到该方法并执行它。
对于大多数 Java 或 C++/C# 开发人员,可能需要一段时间才能习惯类实际上是对象的概念。对于 Smalltalk        开发人员,始终会遇到这种情况,所以他们想知道是什么耽误了我们其余人这么长时间。如果有助于您更快地解释该概念,可以尝试将 ECMAScript        中的类视为类型对象:为提供类型定义的外观而存在的对象实例。
原型继承作为一种模式,“跟随原型链” 使 ECMAScript 6 的继承规则非常容易理解。如果您创建一个扩展另一个类的类,很容易想到在派生类上调用该实例方法时发生的情况。
清单 11. 调用实例方法
class Author extends Person
{
  constructor(firstName, lastName, age, subject)
  {
    super(firstName, lastName, age);
    this.subject = subject;
  }

  get subject() { return this._subject; }
  set subject(value) { this._subject = value; }

  writeArticle() {
    console.log(this.firstName,"just wrote an article on",this.subject);
  }
}
let mark = new Author("Mark", "Richards", 55, "Architecture");
mark.writeArticle();




实例本身首先会处理调用。如果失败,那么它会检查类型对象(在本例中为 Author)。接下来,将会检查类型对象的 “扩展” 对象          (Person),依此类推,直到返回到最初的类型对象,该对象始终是 Object。
此外,从清单 11 中的 Author 构造函数可以看到,关键字 super        显然会在原型链中向上调用给定方法的原型版本。在本例中,调用了构造函数,让 Person        构造函数有机会执行发挥自己的作用。如果仅跟随原型链,那么原理很简单。
我对原型委托使用得越多,就越欣赏此解决方案的优雅之处。所有方面都遵循一个概念,“旧规则” 仍在发挥其作用。如果希望以元对象方式继续使用 ECMAScript        对象,在对象本身上添加和删除方法,您仍然可以这么做:
清单 12. 旧式对象委托
mark.favoriteLanguage = function() {
  return "Java";
}
mark.favoriteBeverage = function() {
  return "Scotch";
}
console.log(mark.firstName,"prefers writing", mark.subject,
  "using",mark.favoriteLanguage(),"and",mark.favoriteBeverage());




在我看来,新的基于类的语法很容易掌握;在本例中,会让您使用 Java 并一直使用同一种语言。
静态属性和字段如果不考虑回避          对面向对象的讨论,任何面向对象的讨论都是不完整的。当开始在代码中使用类时,知道如何处理全局变量和/或函数至关重要。在大多数语言中,这些变量和函数被认为是静态的(或整体式的),如果您喜欢使用概模式。
ECMAScript 6 没有隐式配备静态属性或字段,但根据我们上面的讨论和对 ECMAScript 对象的工作原理的一些了解,不难想象可以如何实现静态值:
清单 13. 引入静态值
class Person
{
  constructor(firstName, lastName, age)
  {
    console.log(arguments);

    // Just introduce a new field on Person itself
    // if it doesn't already exist; otherwise, just
    // reference the one that's there
    if (typeof(Person.population === 'undefined'))
      Person.population = 0;
    Person.population++;

    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  // ... as before
}




因为 Person 类实际上是一个对象,所以 ECMAScript 中的静态字段实质上是 Person          类型对象上的字段。因此,尽管没有显式的语法来定义静态字段,但可以直接在类型对象上引用字段。在上面的示例中,Person 构造函数首先检查          Person 是否已有一个 population 字段。如果没有,它会将 population 设置为        0,隐式地创建该字段。如果有一个 population 字段,那么它会递增该值。
因此,沿原型链一直到 Person 的任何实例都可以引用 population 字段,无论是直接引用还是按名称引用          Person 类(或类型对象),后者是首选方法:
清单 14. 引用类
console.log(Person.population);
console.log(ted.population);




定义字段很容易,但 ECMAScript 6 规范使定义静态方法变得有点复杂。要定义静态方法,需要在类声明中使用 static 关键字来定义函数:
清单 15. 定义静态方法
class Person
{
  // ... as before

  static haveBaby() {
    return Person.population++;
  }
}




同样地,可以通过实例或通过类本身来调用静态方法。您可能会发现,如果始终通过类名称调用静态方法,很容易跟踪在何处定义了什么对象。
结束语ECMAScript 技术委员会在其发展过程中遇到了一些严峻的挑战,但这些挑战都没有向 JavaScript        引入类那么艰难。目前,似乎新语法获得了成功,满足了大多数面向对象的开发人员的期望,而且从整体上讲没有丢弃 ECMAScript 的基础原则。
该委员会没有集成 TypeScript        等语言中提供的稳健的静态类型检查,但这从来都不是他们考虑的目标。值得称赞的是,该委员会没有试图强迫这么做,至少在这一轮改进中没有这么做。
请关注本系列的最后一期文章!我们将探索 ECMAScript 6 库的一些增强,包括显式声明和使用模块的新语法。
返回列表