ECMAScript 6 中的 Class(类)概念,实际上可以把它看作 ECMAScript 5 对象原型写法的语法糖。
利用 ES5 实现对象原型:
function Point(x, y) {this.x = x;this.y = y;}Point.prototype.toString = function () {return '(' + this.x + ',' + this.y + ')';};var p = new Point(1, 2);
利用 ES6 实现对象原型:
class Point {constructor(x, y) {this.x = x;this.y = y;}toString() {return '(' + this.x + ',' + this.y + ')';}}
类有两种表现形式:声明式和表达式。
与 ES5 相同的是,类也是通过 new
关键字创建对象实例。
与 ES5 不同的是,在 ES6 中,我们将原型的实现写在了类中,但本质上还是一样的,都是需要新建一个类名,然后实现构造函数,再实现原型方法。
class Foo(){// 构造函数constructor(name = 'Tom'){this.name = name}// 定义一个方法并且赋值给构造函数的原型sayName(){console.log(this.name)}}const foo = new Foo()foo.sayName()// 'Tom'
类声明和函数声明的区别和特点:
let
声明类似)new
关键字来声明类,声明类会调用类的构造函数[[construct]]
的方法,该方法就是构造函数prototype
属性上function
关键字,只需直接添加到类中,
),加了会报错Object.definedProperty()
手工指定不可枚举属性name
属性总是返回紧跟 class
的关键字后的类名this
的指向默认指向 类的实例// 表达式// 这个类的名称为 Baz2 而不是 Baz1// Baz1 只有在类内部代码可用 指代当前类const Baz2 = class Baz1 {constructor() {}getClassName() {return Baz1.name;}};
构造函数(constructor
方法)是类的默认方法,通过 new
关键字生成对象实例时,自动调用该方法。若没有显式定义,一个空的构造函数会被默认添加。
⚠️ 注意:类必须使用
new
关键字调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new
关键字也能执行。
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在 this
对象上),否则都是定义在 原型对象 上(即定义在 class
上)。
class Point {constructor(x, y) {this.x = x;this.y = y;}toString() {return `(${this.x}, ${this.y})`;}}const point = new Point(2, 3);// 实例化类后,挂载在 this 上的属性会在挂载到实例对象上point.toString();// (2, 3)point.hasOwnProperty('x');// truepoint.hasOwnProperty('y');// true// 而挂载在类上的方法则挂载在原型链上point.hasOwnProperty('toString');// falsepoint.__proto__.hasOwnProperty('toString');// true
x
和 y
都是实例对象 point
自身的属性(因为定义在 this
变量上)toString
是原型对象的属性(因为定义在 Point
类上)与 ES5 一样,类的所有实例共享一个原型对象。这也意味着,可以通过实例的 __proto__
属性为类添加方法。
__proto__
并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JavaScript 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用
Object.getPrototypeOf
方法来获取实例对象的原型,然后再来为原型添加方法/属性。
与 ES5 一样,在类的内部也可以使用 get
和 set
关键字,对某个属性设置 存值函数 和 取值函数,拦截该属性的存取行为。
尽管应该在类的构造函数中创建自己的属性,但是类也支持直接在原型上定义访问器属性。
class Student () {constructor () {// ...}get run () {return 'get'}set run (value) {console.log(`set: ${value}`)}}let inst = new Student()Student.run = 'abc'// set: abcStudent.run// get
类的属性名,可以采用表达式。
const methodName = 'getArea'class Square(){constructor(length){// ...}[methodName](){// ...}}
如果某个方法之前加上星号(*
),就表示该方法是一个生成器方法(Generator 函数)。
class Foo {constructor(...args) {this.args = args;}*[Symbol.iterator]() {for (let arg of this.args) {yield arg;}}}for (let x of new Foo('hello', 'world')) {console.log(x);}// hello// world
类的方法内部如果含有 this
,它默认指向 类的实例。
但是,如果将类方法内部的方法提取出来单独使用,this
会指向该方法 运行时所在的环境,因为找不到相对应的方法而导致报错。
因此,需要 在构造函数中绑定 this
,这样就不会找不到相对应的方法。
class Student {constructor() {this.sayName = this.sayName.bind(this);}}
另一种解决方法是使用 箭头函数。
class Car {constructor() {this.sayName = (name = 'BOT') => {this.sayName(`My name is ${name}`);};}}
还有一种解决方法是使用 Proxy
,获取方法的时候,自动绑定 this
。
参考资料: