• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

7.8 Classes(类)

JS 教程 tiantian 386次浏览 0个评论 扫描二维码

那个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

class User {

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

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

}

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

可以发现,两个样例代码非常地相似。请注意,位于class中的方法之间并没有使用逗号进行分割。很多新手开发者可能会忘记这个特性,而在方法之间放置逗号,然后就导致莫名其妙的错误。那不是一个字面量对象,而是一个class语法。

所以,class的真正功能是什么?我们可能认为它定义了一个新的语言级别的实体,但是这是错误的。

那个class User {...}实际上完成了两件事情:

  1. 定义一个名为User的变量,其引用名为constructor的函数。
  2. 将列出的方法放入到User.prototype中。此处,它包含了sayHiconstructor函数。

下面是验证的代码:

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

// proof: User is the "constructor" function
alert(User === User.prototype.constructor); // true

// proof: there are two methods in its "prototype"
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

下面以图的形式来演示上述的class User的内容:

7.8 Classes(类)

所以class是一个特殊的语法,用来定义其构造器和原型方法。

但是,class的功能不止于此。存在某些细微的调整:

构造器需要new

不像一个普通的函数,一个类的constructor,无法在不使用new的情况下调用。

class User {
  constructor() {}
}

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

不同的字符串输出

如果我们使用普通的输出方式alert(User),某些引擎会显示class User...,其他的引擎会显示function User...

但是,请注意:那个字符串表示形式可能发生变化,但是它仍然是一个函数,在JavaScript 语言中不存在单独的名为class的实体。

类方法是不可枚举

class 定义会将所有放入prototype的方法都设置它们的enumerable标记为false。这个特性很好,因为我们在使用for..in迭代对象时,我们通常不希望列出它的类方法。

类具有一个默认的 constructor() {}

如果在 class构造器中没有定义明确的constructor函数,那么就会自动创建一个空白函数,就像我们创建的空白函数constructor() {}

类永远使用严格模式

位于 class 构造器内的所有代码都自动应用严格模式。

Getters/setters

类可能也具有 getters/setters。下面是user.name的一个实现样例:

class User {

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

  get name() {
    return this._name;
  }

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

}

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

user = new User(""); // Name too short.

在内部,getters 和 setters 同样是创建在User原型的内部,好比:

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

只有方法

不像对象字面量,在class中不允许使用property:value赋值操作。class的内部只允许存在方法和 getters/setters。最新的规范正在尝试解决这个限制,但是目前还没有最终结论。

如果我们真的希望将一个非函数值放入到原型中,那么我们就可以手工修改prototype,比如:

class User { }

User.prototype.test = 5;

alert( new User().test ); // 5

所以,技术上,那是可行的,但是我们应该明白这样做的后果。那样的属性将在该类的所有对象中进行共享。

一个位于class的变通方式是使用 getter:

class User {
  get test() {
    return 5;
  }
}

alert( new User().test ); // 5

从外部代码看来,这个用法的功能是等效的。但是这个 getter 版本更加慢一些。

类表达式

就像函数一样,类也可以定义在另一个表达式内部,被当作变量传递,被返回等等。

下面是一个返回类定义的函数(类工厂):

function makeClass(phrase) {
  // declare a class and return it
  return class {
    sayHi() {
      alert(phrase);
    };
  };
}

let User = makeClass("Hello");

new User().sayHi(); // Hello

如果我们将那个class看作是一个特殊形式的函数,该函数具有原型定义。那么上述示例代码就非常容易理解。

而且,就像命名的函数表达式,这样的类同样可以具有一个名称,它也只在该类内部可见:

// "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

静态方法

我们也可以将方法赋值给类的函数,而不是给它的原型。这样的方法被称为 static(静态方法)。

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

User.staticMethod(); // true

实际上,这等价于将该方法当作函数的属性:

function User() { }

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

位于User.staticMethod()方法内部的那个this的值等价于这个类构造器User本身(还是那个“位于点运算符之前”规则)。

一般地,静态方法用来实现属于类的函数,不属于任何的特定对象。

举例,我们具有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

此处的Article.compare有些超过了本章节的范围,可以认为是比较对象的一个方式。它不是某一个article对象的方法,而是整个article类的比较方式。

另一个例子可能是所谓的“工厂”方法。假设,我们需要多种创建article对象的的方法:

  1. 通过给定的参数来创建(title, date).
  2. 通过今天的日期来创建一个空白article

第一种实现方式可以是构造器。第二种方式就是使用一个该类的静态方法。

比如此处的Article.createTodays()

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

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

let article = Article.createTodays();

alert( article.title ); // Todays digest

现在,假设我们需要创建今天的article,我们可以直接调用Article.createTodays()。再一次,那不是某一个article的方法,而是整个article类的方法。

静态类被广泛应用于与数据库相关的类中,用来搜索/保存/移除数据库中的实体。比如:

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

总结

基本的 class 语法如下:

class MyClass {
  constructor(...) {
    // ...
  }
  method1(...) {}
  method2(...) {}
  get something(...) {}
  set something(...) {}
  static staticMethod(..) {}
  // ...
}

那个MyClass的值是一个函数,其值由constructor提供。如果不存在constructor,就有一个默认的空白函数。

在任意情况下,位于类定义中的方法都成为了其原型prototype的一部分,除了静态方法,它们使用static进行标记,它们位于该函数本身,而且通过MyClass.staticMethod()的方式可调用。当我们需要的方法是绑定于某个类,而不是该类的某个特定对象时,我们就可以使用静态函数。

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

任务


重写类

重写那个Clock类,不使用prototype语法,而使用class语法。


天天编码 , 版权所有丨本文标题:7.8 Classes(类)
转载请保留页面地址:http://www.tiantianbianma.com/classes.html/
喜欢 (1)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址