文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏
参与翻译: pauli (7), Kun (5), Render (1)

“class”构造允许用干净,漂亮的语法来定义基于原型的类。

“class” 语法

class语法是多功能的,我们将首先从一个简单的例子开始.

这是一个基于原型的User类:

function User(name) {
  this.name = name;
}

User.prototype.sayHi = function() {
  alert(this.name);
}

let user = new User("John");
user.sayHi();

...这和使用类语法是一样的:

class User {

  constructor(name) {
    this.name = name;
  }

  sayHi() {
    alert(this.name);
  }

}

let user = new User("John");
user.sayHi();
第 1 段(可获 0.5 积分)

很明显,两个种写法非常像。不过请注意,类里的方法之间是没有逗号的。新手经常会忘了这一点并类的方法后面加上逗号,这样是错误的。这是两种写法的一个区别。

那么,类到底是做什么的?如果你会认为它定义了一个新实例,那么你错了。

这里的类User做了两件事:

  1. 声明一个变量User,引用函数"constructor"。
  2. 把所有的方法都加入到User.prototype. 也就是 sayHiconstructor。
第 2 段(可获 1.24 积分)

下面一段代码挖掘class背后的机制:

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name);  }
}

// 证据: User 就是"constructor"函数
alert(User == User.prototype.constructor); // true

// 证据: "prototype"里有两个方法
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

下图展示了User类是如何创建的:

所以class只是一个把构造函数和原型方法一起声明的一个特殊语法。

当然不仅仅如此,还有一些如下一些小变化:

第 3 段(可获 0.55 积分)

构造函数要搭配new使用

与常规函数不同,class的构造函数只能和new搭配使用:

class User {
  constructor() {}
}

alert(typeof User); // function
User(); // Error: Class constructor User cannot be invoked without 'new'

不同的字符串表示

如果我们使用类似alert(User)来输出函数User,一般会看到"class User...",而函数则会是"function User...".

不过不要困惑:虽然字符串表示会有区别,但是说到底它还是个函数,JavaScript里并没有类这么个“实体”。

类方法不可枚举

类定义会把所有的类方法的enumerable置为false。这样有一个好处:我们用for...in去遍历对象时,就不会遍历到类方法了。

第 4 段(可获 1.05 积分)

类会生成默认的constructor

如果构造类时,没有实现contructor,系统会生成一个空函数作为constructor,跟我们写constructor() {}一样的效果

类自动启用strict模式

所有class下的代码都要符合strict模式的规范

Getters/setters

类还可以实现getters/setters. 下面是一个例子:

class User {

  constructor(name) {
    // invokes the setter
    this.name = name;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    if (value.length < 4) {
      alert("Name too short.");
      return;
    }
    this._name = value;
  }

}

let user = new User("John");
alert(user.name); // John

user = new User(""); // Name too short.
第 5 段(可获 0.65 积分)

在内部,getters和setters是用如下方式实现的:

Object.defineProperty(User.prototype, {
  name: {
    get() {
      return this._name
    },
    set(name) {
      // ...
    }
  }
});

只有方法

跟对象不一样,类声明里是不允许声明属性的,只会有方法和getters/setters。当然我们还是有办法突破这个限制的,只是需要一些额外的步骤。

如果确实需要非函数的值,我们可以手动调整原型prototype,如下:

第 6 段(可获 0.85 积分)
class User { }
User.prototype.test = 5;
alert( new User().test ); // 5

从技术上讲,这是可能的,但我们应该知道为什么要这样做。这些属性将在类的所有对象之间共享。

一个类内部的替代方法是使用getter:

class User {
  get test() {
    return 5;
  }
}
alert( new User().test ); // 5

至于外部代码,使用是一样的。但 getter 变量有些啰嗦。

Class 表达式

就像方法,类可以定义在另一个表达式中,传递,返回等。

下面是一个类返回函数(“类工厂”):

第 7 段(可获 0.91 积分)
function makeClass(phrase) {
  // declare a class and return it
  return class {
    sayHi() {
      alert(phrase);
    };
  };
}
let User = makeClass("Hello");
new User().sayHi(); // Hello

这很正常的,如果我们记得,类只是一个特殊的函数形式,具有原型定义。

而且,与命名函数表达式一样,此类类也可能有一个名称,该名称仅在该类内可见:

// "Named Class Expression" (alas, no such term, but that's what's going on)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // MyClass is visible only inside the class
  }
};
new User().sayHi(); // works, shows MyClass definition
alert(MyClass); // error, MyClass not visible outside of the class
第 8 段(可获 0.45 积分)

静态方法

我们可以为类函数定义方法,而不是给他的原型。这就是静态方法。

An example:

class User {
  static staticMethod() {
    alert(this == User);
  }
}

User.staticMethod(); // true

这实际上与将其赋值为函数属性一样:

function User() { }

User.staticMethod = function() {
  alert(this == User);
};

 在 User.staticMethod() 内部,this 的值就是他自己(点前面对象原则)。

通常,静态方法用于实现属于类的函数,但不适用于它的任何特定对象。

第 9 段(可获 0.85 积分)

例如,我们定义对象 Article,需要用一个函数来比较他们。自然的选择就是 Article.compare,就像

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static compare(articleA, articleB) {
    return articleA.date - articleB.date;
  }
}

// usage
let articles = [
  new Article("Mind", new Date(2016, 1, 1)),
  new Article("Body", new Date(2016, 0, 1)),
  new Article("JavaScript", new Date(2016, 11, 1))
];

articles.sort(Article.compare);

alert( articles[0].title ); // Body
第 10 段(可获 0.24 积分)

这里 Article.compare 在所有 articles 上面都有,就是比较他们的方式。不是某一个 article 自己的方法,而是他们整体共有的。

另一个例子是所谓的“工厂”方法。想象一下,我们需要一些方法来创建一个 article:

  1. 有指定参数创建(titledate等)。
  2. 用今天的日期创建一篇空的 article。

第一种方法可以通过构造函数实现。对于第二个,我们可以通过类的静态方法。

就像 Article.createTodays()

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static createTodays() {
    // remember, this = Article
    return new this("Todays digest", new Date());
  }
}

let article = Article.createTodays();

alert( article.title ); // Todays digest
第 11 段(可获 1 积分)

现在,每当我们需要创建一个今天的摘要,我们可以调用 Article.createTodays()。再一次强调,这不是一个 article 的方法,而是整个类的方法。

在数据库相关类中也使用静态方法来搜索/保存/删除数据库中的条目:

// assuming Article is a special class for managing articles
// static method to remove the article:
Article.remove({id: 12345});

总结

类的基本语法是这样的:

class MyClass {
  constructor(...) {
    // ...
  }
  method1(...) {}
  method2(...) {}
  get something(...) {}
  set something(...) {}
  static staticMethod(..) {}
  // ...
}
第 12 段(可获 0.7 积分)

MyClass 的值是一个构造函数提供的类。如果没有构造函数,就是空的函数。在任何情况下,类声明中列出的方法都成为原型的成员,除了写入函数本身的静态方法,就像这个调用 MyClass.staticMethod()。当我们需要一个绑定到类的函数时,使用静态方法,但不用于该类的任何对象。

在下一章中,我们将学习更多关于类的知识,包括继承。

第 13 段(可获 0.98 积分)

文章评论