上一期出了ES6中Class类用法详解的(上)半部分,以下是(下)半部分,需要复习上半部分的小伙伴可以点击文章末尾的“阅读原文”或者点击文中的超链接。
本期目录:
Class的常见语法(下)
请大家仔细阅读文中的代码块,尤其是注释部分
7. 静态方法
在Class的常见语法(上)我们知道,class
关键字定义的类中有的方法,在这个类创建出来的对象上都可以使用,比如:
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
关键字,那这个方法就不会被继承,只能通过类来调用,这样的方法称为"静态方法":
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
指向类,而不是实例:
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
关键字:
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的私有属性:
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
属性不会报错。由于井号#
是属性名的一部分,使用时必须带有#
一起使用,所以#name
和name
是两个不同的属性。另外,私有方法也可以设置getter和setter,方法与非私有属性一样,上一篇推文已经讲过,不再赘述。
(2)私有方法
在设置Class中私有方法的,Dapan推荐使用以下方式:
代码语言:javascript复制class Person {
// ...
#getName() { // 和私有属性一样,在方法名前加‘#’表示私有方法
// ...
}
}
上面代码中,#getName()
就是一个私有方法,它只可以在Person类的内部使用,在外部使用会报错:
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
运算符,判断某个对象是否为类的实例(判断这个对象上是否有这个类的私有属性,如果报错,则表示这个对象不是这个类的实例,反之则是):
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类里定义一个静态方法来辅助判断person4
和person5
这两个对象是否为Person类的实例对象?如果要用普通方法进行判断该怎么写?
子类从父类继承的私有属性,也可以使用in
运算符来判断:
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
,它默认指向类的实例对象:
class Person {
name = 'dapan';
lookThis() {
return this;
}
}
const person8 = new Person();
console.log(person8.lookThis());// 打印Person构造的实例对象:person8, {name: 'dapan'}
但是也有例外,比如单独使用该方法,this
的指向为undefined:
class Person {
name = 'dapan';
lookThis() {
return this;
}
}
const person8 = new Person();
const { lookThis } = person8;
console.log(lookThis());// undefined
此时的this
指向该方法运行时所在的环境(由于class内部是严格模式,所以此时this
指向的是undefined
)。
解决办法就是,提前给lookThis()
方法绑定this:
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:
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#静态方法