ES6—class类详细教程(下)

2022-09-26 11:43:59 浏览数 (1)

上一期出了ES6中Class类用法详解的(上)半部分,以下是(下)半部分,需要复习上半部分的小伙伴可以点击文章末尾的“阅读原文”或者点击文中的超链接。

本期目录:

Class的常见语法(下)

请大家仔细阅读文中的代码块,尤其是注释部分

7. 静态方法

在Class的常见语法(上)我们知道,class关键字定义的类中有的方法,在这个类创建出来的对象上都可以使用,比如:

代码语言:javascript复制
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  toString() {
    return `我叫${this.name},今年${this.age}了`;
  }
  showName() {
    return this.name;
  }
}

const person = new Person('大潘', 101);
console.log(person.toString());// 我叫大潘,今年101了
console.log(person.showName());// 大潘

但是如果在Class中的方法前加上static关键字,那这个方法就不会被继承,只能通过类来调用,这样的方法称为"静态方法":

代码语言:javascript复制
class Person {
  // ...
  static classMethod() {
    return '我是一个Person类的方法,对象不能用哦';
  }
  objectMethod() {
    return '我虽然是Person类上的方法,但是被我创建出来的对象也可以用我哦';
  }
}

const person = new Person('大潘', 99);
console.log(Person.classMethod());// 我是一个Person类的方法,对象不能用哦
console.log(person.classMethod());// 报错:TypeError: person.classMethod is not a function
console.log(person.objectMethod());// 我虽然是Person类上的方法,但是被我创建出来的对象也可以用我哦

我们发现,“静态方法”只能被类调用,用类的实例对象调用的话会报错,表示不存在该方法,验证:

静态方法:

静态方法验证

非静态方法:

原型方法验证

而且,如果静态方法中包含this关键字,那么这个this指向类,而不是实例:

代码语言:javascript复制
class Person {
  // ...
  static classMethod() {
    console.log(this);
    return '我是一个Person类的方法,对象不能用哦';
  }
  // ...
}

Person.classMethod();// class Person{ //代码 },打印的是Person类

父类上的静态方法可以继承到其子类上(比如Son类继承了Person类,那么Son类可以通过Son.classMethod()来调用这个方法),之后我会专门出一期Class的继承

8. 静态属性

Class的“静态属性”和“静态方法”类似,都是只有Class才能调用,如果由这个Class创建出来的实例对象调用会报错。

之前,在Class内部添加静态属性的方法是:

代码语言:javascript复制
class Person {}
Person.a = 1;
console.log(Person.a);// 1

因为ES6明确规定Class内部只有静态方法,没有静态属性,但是现在有一个提案提供了Class的静态属性,写法是在实例属性前加上static关键字:

代码语言:javascript复制
class Person {
  static a = 2;
}
console.log(Person.a);// 2

这种显示声明静态属性的方法一定程度上方便了静态属性的表达。

与Class的“静态方法”一样,“静态属性”也会被继承到其子类上:

代码语言:javascript复制
class Person {
  static a = 3;
}
class Son extends Person {}
console.log(Son.a);// 3

9. 私有属性和私有方法

私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。

(1)私有属性

在ES2022中,提出了在属性名前加#表示Class的私有属性:

代码语言:javascript复制
class Person {
  #name = '大潘';
  name = 'Dapan';
  getName() {
    return `${this.#name}的英文名叫${this.name}`;
  }
}

const person2 = new Person();
console.log(person2.#name);//报错:Uncaught SyntaxError: Private field '#name' must be declared in an enclosing class
console.log(person2.age);// Dapan
console.log(person2.getName());// 大潘的英文名叫Dapan

上边代码表示,在Person类的外部调用带有#的私有属性会报错,调用不带#的属性不会报错。但是在Person内部调用#name属性不会报错。由于井号#是属性名的一部分,使用时必须带有#一起使用,所以#namename是两个不同的属性。另外,私有方法也可以设置getter和setter,方法与非私有属性一样,上一篇推文已经讲过,不再赘述。

(2)私有方法

在设置Class中私有方法的,Dapan推荐使用以下方式:

代码语言:javascript复制
class Person {
  // ...
  #getName() { // 和私有属性一样,在方法名前加‘#’表示私有方法
    // ...
  }
}

上面代码中,#getName()就是一个私有方法,它只可以在Person类的内部使用,在外部使用会报错:

代码语言:javascript复制
class Person {
  // ...
  #getName() {
    console.log('我是Person类的私有方法');
  }
}

Person.prototype.#getName();// 这里并没有打印出来'我是Person类的私有方法',而是报错:
                            // Uncaught SyntaxError: Private field '#getName' must be declared in an enclosing class

(PS:私有属性和私有方法也可以加static关键字成为静态的私有属性和方法)

10. in运算符

当我们访问某个类不存在的私有属性的时候会报错,但是访问不存在的共有属性并不会报错:

代码语言:javascript复制
class Person {}

const person3 = new Person()
console.log(person3.test);// undefined
console.log(person3.#test);// 报错:Uncaught SyntaxError: Private field '#test' must be declared in an enclosing class

我们可以根据这一特性,利用in运算符,判断某个对象是否为类的实例(判断这个对象上是否有这个类的私有属性,如果报错,则表示这个对象不是这个类的实例,反之则是):

代码语言:javascript复制
class Person {
  #name = 'Dapan';
  // ...
  static isNameInPersonClass(obj) {
    if (#name in obj) {
      return '这个对象是Person类的实例';
    } else {
      return '这个对象不是Person类的实例';
    }
  }
}

const person4 = new Person();
const person5 = Object.create(null);
console.log(Person.isNameInPersonClass(person4)); // 这个对象是Person类的实例
console.log(Person.isNameInPersonClass(person5)); // 这个对象不是Person类的实例

想一下,我这里为什么要在Person类里定义一个静态方法来辅助判断person4person5这两个对象是否为Person类的实例对象?如果要用普通方法进行判断该怎么写?

子类从父类继承的私有属性,也可以使用in运算符来判断:

代码语言:javascript复制
class Person {
  #name = 'dapan';
  static test(obj) {
    return #name in obj;
  }
}

// 定义Son类继承Person类:
class Son extends Person {}
const son = new Son();
const obj = Object.create(null);
console.log(Person.test(son)); // true 
console.log(Person.test(obj)); // false

有一点需要注意:in运算符只能用在类的内部。

10. 静态块

每个类只能有一个静态块,在静态属性声明后运行。静态块的内部不能有return语句,且静态块只运行一次。静态块内部可以使用类名或this,指代当前类。

可以通过静态块将私有属性传递到类的外部

代码语言:javascript复制
let getAge;
class Person {
  #age = 101;
  // ...
  static {
    getAge = (obj) => obj.#age;
  }
}

const person7 = new Person();
console.log(getAge(person7));// 打印101,拿到了Person类内部的私有属性

上面示例中,#age是类的私有属性,如果类外部的getAge()方法希望获取这个属性,以前是要写在类的constructor()方法里面,这样的话,每次新建实例都会定义一次getAge()方法。现在可以写在静态块里面,这样的话,只在类生成时定义一次。

11. 类的注意点

(1)类的声明不存在提升

ES6中,Class的声明不存在提升,这一点与ES5完全不同:

代码语言:javascript复制
// ES5:
new Person(); // 不报错
function Person() {}

// ES6:
new Person(); // 报错:Uncaught SyntaxError: Identifier 'Person' has already been declared
class Person {}

这样的规定是为了保证Class在继承(下下期推文会出“Class的继承”)的时候,子类在父类之后定义。

(2)name属性

获取类的名字:

代码语言:javascript复制
class Person {}
console.log(Person.name); // Person

(3)this指向

一般情况下,类的方法中如果包含this,它默认指向类的实例对象:

代码语言:javascript复制
class Person {
  name = 'dapan';
  lookThis() {
    return this;
  }
}

const person8 = new Person();
console.log(person8.lookThis());// 打印Person构造的实例对象:person8, {name: 'dapan'}

但是也有例外,比如单独使用该方法,this的指向为undefined:

代码语言:javascript复制
class Person {
  name = 'dapan';
  lookThis() {
    return this;
  }
}

const person8 = new Person();
const { lookThis } = person8;
console.log(lookThis());// undefined

此时的this指向该方法运行时所在的环境(由于class内部是严格模式,所以此时this指向的是undefined)。

解决办法就是,提前给lookThis()方法绑定this:

代码语言:javascript复制
class Person {
  name = 'dapan';
  // 绑定lookThis方法的this:
  lookThis = this.lookThis.bind(this);
  lookThis() {
    return this.name;
  }
}

const person8 = new Person();
const { lookThis } = person8;
console.log(lookThis()); // dapan

还有一种更简单的方法,使用箭头函数。因为箭头函数内部的this总是指向定义时所在的对象。上面代码中,箭头函数位于构造函数内部,它的定义生效的时候,是在构造函数执行的时候。这时,箭头函数所在的运行环境,肯定是实例对象,所以this会总是指向实例对象。相当于自动帮我们绑定了this:

代码语言:javascript复制
class Person {
  name = 'dapan';
  lookThis = () => { // 使用箭头函数
    return this.name;
  };
}

const person8 = new Person();
const { lookThis } = person8;
console.log(lookThis()); // dapan

(完)

以上内容均为Dapan阅读文章教程之后,花了4个多小时的个人整理,主要参考资料如下:

https://es6.ruanyifeng.com/#docs/class#静态方法

0 人点赞