ECMAScript 6——Class的基本语法

1.简介

1)类的由来

ES5中通过构造函数生成类

 1 function Point(x, y) {
 2   this.x = x;
 3   this.y = y;
 4 }
 5 
 6 Point.prototype.toString = function () {
 7   return '(' + this.x + ', ' + this.y + ')';
 8 };
 9 
10 var p = new Point(1, 2);

ES6引入了 Class(类)这个概念

1 class Point {
2   constructor(x, y) {
3     this.x = x;
4     this.y = y;
5   }
6   toString() {
7     return '(' + this.x + ', ' + this.y + ')';
8   }
9 }

ES5 的构造函数Point,对应 ES6 的Point类的constructor方法

this关键字则代表实例对象

定义“类”的方法的时候,前面不需要加上function这个关键字

另外,方法之间不需要逗号分隔,加了会报错

ES6 的类,完全可以看作构造函数的另一种写法

1 class Point {
2   // ...
3 }
4 
5 typeof Point // "function"
6 Point === Point.prototype.constructor // true

类的所有方法都定义在类的prototype属性上面

 1 class Point {
 2   constructor() {
 3     // ...
 4   }
 5   toString() {
 6     // ...
 7   }
 8   toValue() {
 9     // ...
10   }
11 }
12 
13 // 等同于
14 Point.prototype = {
15   constructor() {},
16   toString() {},
17   toValue() {},
18 };

在类的实例上面调用方法,其实就是调用原型上的方法

1 class B {}
2 let b = new B();
3 
4 b.constructor === B.prototype.constructor // true

Object.assign方法可以很方便地一次向类添加多个方法

 1 class Point {
 2   constructor(){
 3     // ...
 4   }
 5 }
 6 
 7 Object.assign(Point.prototype, {
 8   toString(){},
 9   toValue(){}
10 });

prototype对象的constructor属性,直接指向“类”的本身

Point.prototype.constructor === Point // true

另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)

 1 class Point {
 2   constructor(x, y) {
 3     // ...
 4   }
 5   toString() {
 6     // ...
 7   }
 8 }
 9 
10 Object.keys(Point.prototype)
11 // []
12 Object.getOwnPropertyNames(Point.prototype)
13 // ["constructor","toString"]

下面代码采用 ES5 的写法,toString方法就是可枚举的

 1 var Point = function (x, y) {
 2   // ...
 3 };
 4 
 5 Point.prototype.toString = function() {
 6   // ...
 7 };
 8 
 9 Object.keys(Point.prototype)
10 // ["toString"]
11 Object.getOwnPropertyNames(Point.prototype)
12 // ["constructor","toString"]

2)constructor 方法

constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象

1 class Foo {
2   constructor() {
3     return Object.create(null);
4   }
5 }
6 
7 new Foo() instanceof Foo// false

3)类的实例

与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)

 1 //定义类
 2 class Point {
 3   constructor(x, y) {
 4     this.x = x;
 5     this.y = y;
 6   }
 7   toString() {
 8     return '(' + this.x + ', ' + this.y + ')';
 9   }
10 }
11 
12 var point = new Point(2, 3);
13 
14 point.toString() // (2, 3)
15 
16 point.hasOwnProperty('x') // true
17 point.hasOwnProperty('y') // true
18 point.hasOwnProperty('toString') // false
19 point.__proto__.hasOwnProperty('toString') // true

上面代码中,x和y都是实例对象point自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回true,而toString是原型对象的属性(因为定义在Point类上),所以hasOwnProperty方法返回false

与 ES5 一样,类的所有实例共享一个原型对象

1 var p1 = new Point(2,3);
2 var p2 = new Point(3,2);
3 
4 p1.__proto__ === p2.__proto__//true

4)属性表达式

类的属性名,可以采用表达式

 1 let methodName = 'getArea';
 2 
 3 class Square {
 4   constructor(length) {
 5     // ...
 6   }
 7   [methodName]() {
 8     // ...
 9   }
10 }

5)Class 表达式

类也可以使用表达式的形式定义

1 const MyClass = class Me {
2   getClassName() {
3     return Me.name;
4   }
5 };

需要注意的是,这个类的名字是Me,但是Me只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass引用

1 let inst = new MyClass();
2 inst.getClassName() // Me
3 Me.name // ReferenceError: Me is not defined

上面代码表示,Me只在 Class 内部有定义

如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式

const MyClass = class { /* ... */ };

采用 Class 表达式,可以写出立即执行的 Class

 1 let person = new class {
 2   constructor(name) {
 3     this.name = name;
 4   }
 5   sayName() {
 6     console.log(this.name);
 7   }
 8 }('张三');
 9 
10 person.sayName(); // "张三"

上面代码中,person是一个立即执行的类的实例

6)name属性

name属性总是返回紧跟在class关键字后面的类名

1 class Point {}
2 Point.name // "Point"

2.静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承

注意,如果静态方法包含this关键字,这个this指的是类,而不是实例

 1 class Foo {
 2   static bar() {
 3     this.baz();
 4   }
 5   static baz() {
 6     console.log('hello');
 7   }
 8   baz() {
 9     console.log('world');
10   }
11 }
12 
13 Foo.bar() // hello

另外,从这个例子还可以看出,静态方法可以与非静态方法重名

父类的静态方法,可以被子类继承

静态方法也是可以从super对象上调用的

 1 class Foo {
 2   static classMethod() {
 3     return 'hello';
 4   }
 5 }
 6 
 7 class Bar extends Foo {
 8   static classMethod() {
 9     return super.classMethod() + ', too';
10   }
11 }
12 
13 Bar.classMethod() // "hello, too"

3.私有方法和私有属性

一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的

 1 class Widget {
 2   foo (baz) {
 3     bar.call(this, baz);
 4   }
 5   // ...
 6 }
 7 
 8 function bar(baz) {
 9   return this.snaf = baz;
10 }

上面代码中,foo是公开方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法

还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值

 1 const bar = Symbol('bar');
 2 const snaf = Symbol('snaf');
 3 
 4 export default class myClass{
 5   // 公有方法
 6   foo(baz) {
 7     this[bar](baz);
 8   }
 9   // 私有方法
10   [bar](baz) {
11     return this[snaf] = baz;
12   }
13 };

上面代码中,bar和snaf都是Symbol值,一般情况下无法获取到它们,因此达到了私有方法和私有属性的效果。但是也不是绝对不行,Reflect.ownKeys()依然可以拿到它们

目前,有一个提案,为class加了私有属性。方法是在属性名之前,使用#表示

4.new.target 属性

ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的

需要注意的是,子类继承父类时,new.target会返回子类

利用这个特点,可以写出不能独立使用、必须继承后才能使用的类

 1 class Shape {
 2   constructor() {
 3     if (new.target === Shape) {
 4       throw new Error('本类不能实例化');
 5     }
 6   }
 7 }
 8 
 9 class Rectangle extends Shape {
10   constructor(length, width) {
11     super();
12   }
13 }
14 
15 var x = new Shape();  // 报错
16 var y = new Rectangle(3, 4);  // 正确

上面代码中,Shape类不能被实例化,只能用于继承

注意,在函数外部,使用new.target会报错

猜你喜欢

转载自www.cnblogs.com/LayaBox/p/11444279.html
今日推荐