ES6对于Class类的基本语法详解(2024-04-10)

目录

1、传统ES5写法

[2、ES6 的class语法](#2、ES6 的class语法)

3、ES5与ES6行为对比

[4、类的constructor() 方法](#4、类的constructor() 方法)

[5、类的实例 new](#5、类的实例 new)

6、类的对象属性(新写法)

7、类的取值函数(getter)和存值函数(setter)

8、Class类的表达式

9、Class的static静态方法与静态属性


ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

1、传统ES5写法

JavaScript 语言中,生成实例对象的传统方法是通过构造函数。

javascript 复制代码
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可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

2、ES6 的class语法

javascript 复制代码
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}


-----------------------------------------------


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

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

// 上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
// 使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

上面代码定义了一个"类",可以看到里面有一个constructor()方法,这就是构造方法,而this关键字则代表实例对象。这种新的 Class 写法,本质上与本章开头的 ES5 的构造函数Point是一致的。

Point类除了构造方法,还定义了一个toString()方法。

注意,定义toString()方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。

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

上面代码表明,类的数据类型就是函数,类本身就指向构造函数。

使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

javascript 复制代码
class Bar {
  doStuff() {
    console.log('stuff');
  }
}

const b = new Bar();
b.doStuff() // "stuff"

构造函数的prototype属性,在 ES6 的"类"上面继续存在。

事实上,类的所有方法都定义在类的prototype属性上面。

javascript 复制代码
class Point {
  constructor() {
    // ...
  }

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}

// 等同于

Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};


------------------------------

class B {}
const b = new B();

b.constructor === B.prototype.constructor // true 等同

// b是B类的实例,它的constructor()方法就是B类原型的constructor()方法

上面代码中,constructor()toString()toValue()这三个方法,其实都是定义在Point.prototype上面。

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

由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign()方法可以很方便地一次向类添加多个方法。

javascript 复制代码
class Point {
  constructor(){
    // ...
  }
}

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

3、ES5与ES6行为对比

javascript 复制代码
1、prototype对象的constructor属性,直接指向"类"的本身,这与 ES5 的行为是一致的。

var Point = function (x, y) {
  // ...
};

class Point {
  constructor(){
    // ...
  }
}

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

------------------------------------------------------------------------------

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

// toString()方法是Point类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致
class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

Object.keys(Point.prototype)
// []  不可枚举
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]


// 上面代码采用 ES5 的写法,toString()方法就是可枚举的。
var Point = function (x, y) {
  // ...
};

Point.prototype.toString = function () {
  // ...
};

Object.keys(Point.prototype)
// ["toString"]  可枚举
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

4、类的constructor() 方法

constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。

一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。

javascript 复制代码
class Point {
}

// 等同于
class Point {
  constructor() {}
}

5、类的实例 new

生成类的实例的写法,与 ES5 完全一样,也是使用new命令。前面说过,如果忘记加上new,像函数那样调用Class(),将会报错。

javascript 复制代码
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

// 实例报错
var point = Point(2, 3);

// 实例正确
var point = new Point(2, 3);


----------------------------------------------------------------------

// 类的属性和方法,除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

// x和y都是实例对象point自身的属性(因为定义在this对象上),所以hasOwnProperty()方法返回true,而toString()是原型对象的属性(因为定义在Point类上),所以hasOwnProperty()方法返回false。这些都与 ES5 的行为保持一致

point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true


------------------------------------------------------------------------
// 类的所有实例共享一个原型对象,它们的原型都是Point.prototype,所以__proto__属性是相等的
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__ === p2.__proto__  //true 相等

// 此时可以通过实例__proto__属性为"类"添加方法,并共享方法(一般不推荐此种方法,会改变原始定义类方法,污染全局!!!)
p1.__proto__.printName = function () { return 'Oops' };
p1.printName() // "Oops"
p2.printName() // "Oops" p2共享p1方法

6、类的对象属性(新写法)

ES6为类规定新的写法,定义在类的最顶层;

改变实例属性定义在constructor()方法里面的this上的写法。

javascript 复制代码
// 原来的写法
class IncreasingCounter {
  constructor() {
    this._count = 0;
  }
  increment() {
    this._count++;
  }
}

// 新写法
// 实例属性_count与increment()方法,处于同一个层级(不需要在实例属性前面加上this)
// 新写法定义的属性是实例对象自身的属性,而不是定义在实例对象的原型上面)
class IncreasingCounter {
  _count = 0;
  increment() {
    this._count++;
  }
}

7、类的取值函数(getter)和存值函数(setter)

与 ES5 一样,在"类"的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

javascript 复制代码
class MyClass {
  name = '张三'
  get prop() {
    console.log('getter: '+this.name);
    return this.name;
  }
  set prop(value) {
    this.name = value
    console.log('setter: '+this.name);
  }
}

let inst = new MyClass();

inst.prop = '刘五';
// setter: '刘五'

inst.prop
// getter:'张三'


// 存值函数和取值函数是设置在属性的 Descriptor 对象上的
var descriptor = Object.getOwnPropertyDescriptor(
  MyClass.prototype, "prop"
);

"get" in descriptor  // true
"set" in descriptor  // true

8、Class类的表达式

与函数一样,类也可以使用表达式的形式定义。

javascript 复制代码
const MyClass1 = class isMe {
  name = 'sun'
  getClassName() {
    console.log(this)
    console.log(isMe)
    console.log(isMe.name)
    return isMe.name;
  }
};

// 类的名字是isMe,但是isMe只在 Class 的内部可用,指代当前类。
// 在 Class 外部,这个类只能用MyClass引用

let inst = new MyClass1();
inst.getClassName() // isMe
isMe.name // ReferenceError: isMe is not defined

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

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

javascript 复制代码
// 简写表达式,类的名字在内部没有使用到
const MyClass1 = class { /* ... */ };

-------------------------------------------------
// 或采用 Class 表达式,可以写出立即执行的 Class。
let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('张三');

person.sayName(); // "张三"  person是一个立即执行的类的实例

9、Class的static静态方法与静态属性

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

如果在一个类的方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为"静态方法"

javascript 复制代码
class Foo {
  //静态属性(Class 本身的属性,即Foo.prop,而不是定义在实例对象(this)上的属性)
  static prop = 123;

  //静态方法(Class 本身的方法)
  static classMethod() {
    console.log(this)
    console.log(Foo.prop); // 123
    this.baz(); // 等同于类静态方法调用Foo.baz()
  }

  static baz() {
    console.log('hello');
  }

  baz() {
    console.log('world');
  }
}

Foo.classMethod() // 'hello'  直接使用类来调用
// 如果静态方法包含this关键字,这个this指的是类,而不是实例
// 静态方法classMethod 调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz()。另外,从这个例子还可以看出,静态方法可以与非静态方法重名
------------------------------------------------------------------

class Foo2 {
  classMethod() {
    return 'hello';
  }
}

Foo2.classMethod() 
// TypeError: Foo2.classMethod is not a function
// 只有静态方法才能直接使用类来调用,不然就报错

------------------------------------------------------------------
// 实例化类
let isfoo = new Foo();
isfoo.classMethod();
// TypeError: isfoo.classMethod is not a function
//(静态方法不能继承到实例对象中,无法调用)

子类能继承父类的静态方法

javascript 复制代码
class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
}

Bar.classMethod() // 'hello' 子类Bar直接类调用(已经继承了Foo父类的静态方法classMethod)
相关推荐
cdut_suye3 分钟前
C++11新特性探索:Lambda表达式与函数包装器的实用指南
开发语言·数据库·c++·人工智能·python·机器学习·华为
桃园码工10 分钟前
第一章:Go 语言概述 1.什么是 Go 语言? --Go 语言轻松入门
开发语言·后端·golang
bpmf_fff10 分钟前
十、事件类型(鼠标事件、焦点.. 、键盘.. 、文本.. 、滚动..)、事件对象、事件流(事件捕获、事件冒泡、阻止冒泡和默认行为、事件委托)
前端·javascript
努力更新中17 分钟前
Python浪漫之画一个音符♪
开发语言·python
Mr_Xuhhh26 分钟前
程序地址空间
android·java·开发语言·数据库
凤枭香35 分钟前
Python Selenium介绍(二)
开发语言·爬虫·python·selenium
疯狂吧小飞牛36 分钟前
C语言解析命令行参数
c语言·开发语言
z2023050840 分钟前
linux之调度管理(13)- wake affine 唤醒特性
java·开发语言
AI人H哥会Java41 分钟前
【JAVA】Java高级:Java网络编程——TCP/IP与UDP协议基础
java·开发语言
小白要加油哈1 小时前
Lua--1.基础知识
开发语言·junit·lua