概述
- ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过
class关键字,可以定义类。可以将class看作是一个语法糖,是构造函数的另一种写法。只是让对象原型的写法更加清晰、更像面向对象编程
class Person{
constructor(name, age){
this.name = name;
this.age = age;
}
toString(){
return `name:${this.name}, age: ${this.age}`;
}
}
console.log(typeof Person)
console.log(Person.prototype.constructor === Person)
let p = new Person('张三', 20);
console.log(p)
- 在
class中定义的所有方法,其实都定义在类(函数)的prototype上,且是不可枚举的。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName() {
console.log(this.name);
}
}
console.log(Person.prototype)
Object.keys(Person.prototype)
Object.getOwnPropertyNames(Person.prototype)
function Person2() {}
Person2.prototype.sayName = function () {}
Object.keys(Person2.prototype)
Object.getOwnPropertyNames(Person2.prototype)
- 类必须使用
new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
constructor()
- 此方法是类的默认方法,通过
new生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显示定义,会默认添加一个空的。
- 默认返回实例对象(即this),但也可以指定返回另一个对象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
类的实例
- 类的属性和方法,除非显式定义在其本身(即定义在
this对象上),否则都是定义在原型上(即定义在class上)。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
let point = new Point(2, 3);
point.hasOwnProperty('x')
point.hasOwnProperty('y')
point.hasOwnProperty('toString')
point.__proto__.hasOwnProperty('toString')
- 与ES5一样,所有实例对象共享一个原型对象。也就意味着通过实例改写原型,会影响到所有实例对象。
let p1 = new Point(2,3);
let p2 = new Point(3,2);
Object.getPrototypeOf(p1) === Object.getPrototypeOf(p2)
实例属性新写法
- ES2022 为类的实例属性又规定了一种新写法。实例属性现在除了可以定义在
constructor()方法里面的this上面,也可以定义在类内部的最顶层。
class Foo {
bar = 'hello';
baz = 'world';
constructor(name) {
this.name = name;
}
}
let f = new Foo('张三');
console.log(f)
setter和getter
- 在类的内部可以使用
get和set关键字,对某个属性设置存值和取值函数,拦截该属性的存取行为。存取函数是设置在属性的属性描述对象上的。
class Foo {
bar = 'hello';
baz = 'world';
constructor(name) {
this.name = name;
}
get age() {
return 100;
}
set age(value) {
console.log(value)
}
}
let f = new Foo()
f.age = 20;
f.age
class表达式
const myClass = class Me{}
const MyClass = class { };
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName();
静态方法
- 类相当于实例的原型,所有在类中定义的方法都会被实例继承。如果在一个方法前加上
static关键字,就表示该方法不会被实例继承,而是直接通过类来调用。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod()
var foo = new Foo();
foo.classMethod()
- 如果静态方法包含
this,则this指向的是类,而不是实例。
- 静态方法还可以与非静态方法重名。
class Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.bar()
静态属性
- 静态属性指的是类本身的属性,而不是定义在实例对象(
this)上的属性。
class Foo {
}
Foo.prop = 1;
class Foo {
static prop = 1;
}
Foo.prop
静态块
- 在类的内部设置一个代码块,在类生成时运行且只运行一次,主要作用是对静态属性进行初始化。以后,新建类的实例时,这个块就不运行了。
- 每个类允许有多个静态块,每个静态块中只能访问之前声明的静态属性。静态块的内部不能有
return语句。静态块内部的this指向当前类。
class CountFac{
static x = 0;
static{
this.x += 100;
}
inc(){
CountFac.x++;
}
get value(){
return CountFac.x;
}
}
let counter = new CountFac();
counter.inc();
counter.value
let counter2 = new CountFac();
counter2.inc();
counter2.value
私有属性、方法
- 写法是属性、方法名之前使用
#表示。只能在类的内部使用,在外部使用会报错。
- 如果读取一个不存在的私有属性、私有方法,在类的内部也会报错。
- 私有属性、方法的前面也可以加上
static,表示这是一个静态的私有属性或方法。
class CountFac{
#count = 0;
#countInc(){
this.#count++;
}
get value(){
return this.#count;
}
inc(){
this.#countInc()
}
}
let counter = new CountFac();
counter.inc();
counter.#count
counter.value
in 运算符
- 判断某个对象是否有某个私有属性。它不会报错,而是返回一个布尔值。
class C {
#brand;
static isC(obj) {
return #brand in obj;
}
}
C.isC(new C())
- 判断私有属性时,
in只能用在类的内部。要判断的私有属性一定要先声明,否则报错。
class A {
m() {
console.log(#foo in this);
}
}
注意事项
- 类的内部默认就是
严格模式。
- 类不存在变量提升,必须先声明,后使用。
- 本质上类只是函数的一层包装,所以也具有
name属性。
class Point {}
Point.name
- 如果某个方法之前加上星号(
*),就表示该方法是一个 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);
}
- 类的方法内部如果含有
this,它默认指向类的实例。一旦单独使用该方法,很可能报错。
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName();