本文详细解析ES6中的class类语法,涵盖从基本定义到高级特性的完整用法。重点讲解类与函数的本质差异、访问器属性与可计算成员名称、默认迭代器的定义,以及extends关键字在继承内建对象和表达式类中的应用。同时深入剖析super()方法、抽象类创建及static静态成员的使用规则,帮助开发者掌握更灵活的对象建模方式。
ES6类语法概述
与Java、C++等面向对象语言一致,ES6正式引入了class类定义及extends继承语法糖,并支持静态成员、派生类、抽象类、迭代器及单例模式等特性,同时结合ES6新特性衍生出多种实用场景。
类的基本定义
一、类的基本定义
绝大多数面向对象编程语言都支持类的封装与继承,那么究竟什么是类?
类是面向对象程序设计的基础概念,它封装了数据、操作数据的函数以及传递消息的机制。通过类创建的具体实例被称为对象。
在ES5标准之前,开发者通常通过构造函数来模拟类的实现,具体代码如下:
// 构造函数
function Person(name) {
this.name = name;
}
// 原型上的方法
Person.prototype.sayName = function(){
console.log(this.name);
};
// new 一个实例
var friend = new Person("Jenny");
friend.sayName(); // Jenny
console.log(friend instanceof Person); // true
console.log(friend instanceof Object); // true
总结而言,定义一个类的核心思路包括以下三点:
1. 使用构造函数封装数据 2. 在原型上添加方法以操作数据, 3. 通过New关键字创建实例
ES6引入了class关键字来定义类,该类包含一个名为[[Construct]]的特殊方法用于定义构造函数。当使用new创建实例时,系统会自动调用[[Construct]]方法,示例如下:
/*ES6*/
// 等价于 let Person = class {
class Person {
// 构造函数
constructor(name) {
this.name = name;
}
// 等价于Person.prototype.sayName
sayName() {
console.log(this.name);
}
}
console.log(typeof Person); // function
console.log(typeof Person.prototype.sayName); // function
let friend = new Person("Jenny");
friend.sayName(); // Jenny
console.log(friend instanceof Person); // true
console.log(friend instanceof Object); // true
上述例子中,使用class定义的类与手动模拟的函数在功能上看似无异,但两者在底层机制上存在显著差异:
- 函数声明支持提升(hoisting),而class类声明与let变量类似,不具备提升特性;
- 类声明内部默认处于严格模式,等同于添加了“use strict”指令;
- 类中定义的所有方法默认不可枚举,即enumerable属性值为false。
更灵活的类特性
二、更灵活的类
类与函数一样,属于JavaScript的一等公民(可作为参数传递、从函数返回或赋值)。此外,类与对象字面量具有更多相似性,这些特性使得类的定义和使用更加灵活。
2.1 拥有访问器属性
对象属性分为数据属性和访问器属性,在类中可以通过get和set关键字定义访问器属性,示例如下:
class Person {
constructor(name) {
this.name = name;
}
get value () {
return this.name + this.age
}
set value (num) {
this.age = num
}
}
let friend = new Person("Jenny");
// 调用的是 setter
friend.value = 18
// 调用的是 getter
console.log(friend.value) // Jenny18
2.2 可计算的成员名称
类似于ES6对象字面量中支持的可计算属性名,类也允许使用[表达式]来定义可计算的成员名称,这包括类中的方法和访问器属性,示例如下:
let methodName = 'sayName'
class Person {
constructor(name) {
this.name = name;
}
[methodName + 'Default']() {
console.log(this.name);
}
get [methodName]() {
return this.name
}
set [methodName](str) {
this.name = str
}
}
let friend = new Person("Jenny");
// 方法
friend.sayNameDefault(); // Jenny
// 访问器属性
friend.sayName = 'lee'
console.log(friend.sayName) // lee若需进一步了解对象新特性,可参考:【ES6】对象的新功能与解构赋值
2.3 定义默认迭代器
ES6中常用的集合对象(如数组、Set、Map)和字符串均为可迭代对象。如果类旨在表示此类可迭代值,定义一个默认迭代器将极具实用价值。
ES6通过为Symbol.iterator属性添加生成器的方式,来定义默认迭代器,示例如下:
class Person {
constructor(name) {
this.name = name;
}
*[Symbol.iterator]() {
for (let item of this.name){
yield item
}
}
}
var abbrName = new Person(new Set(['j', 'j', 'e', 'e', 'n', 'y', 'y', 'y',]))
for (let x of abbrName) {
console.log(x); // j e n y
}
console.log(...abbrName) // j e n y
定义默认迭代器后,类的实例即可使用for-of循环和展开运算符(...)等迭代功能。
若对上述迭代器内容感到困惑,可参考:【ES6】迭代器与可迭代对象
2.4 作为参数的类
作为“一等公民”的类可以作为参数传入函数中,同样也可以从函数中返回,示例如下:
function createClass(className, val) {
return new className(val)
}
let person = createClass(Person,'Jenny')
console.log(person) // Person { name: 'Jenny' }
console.log(typeof person) // object
2.5 创建单例
使用类语法创建单例的方式是通过new立即调用类表达式,示例如下:
let singleton = new class {
constructor(name) {
this.name = name;
}
}('Jenny')
console.log(singleton.name) // Jenny
此处先创建匿名类表达式,随后通过new调用该表达式,并利用小括号立即执行。这种基于类语法的单例创建方式不会在作用域中暴露类的引用。
类的继承机制
三、类的继承
回顾ES6之前如何实现继承?常用方式包括原型链、构造函数以及组合继承等。
ES6的类使用熟悉的extends关键字指定继承的父类,并可通过super()方法访问父类的构造函数。
例如继承一个Person类的示例如下:
class Friend extends Person {
constructor(name, phone){
super(name)
this.phone = phone
}
}
let myfriend = new Friend('lee',2233)
console.log(myfriend) // Friend { name: 'lee', phone: 2233 }
Friend类继承了Person类,术语上称Person为基类,Friend为派生类。
需要注意的是,super()方法只能在派生类中使用,它负责初始化this对象。因此,在派生类中使用this之前,必须调用super()。
3.1 继承内建对象
ES6的类继承机制支持继承内建对象(如Array、Set、Map等),继承后可拥有基类的所有内建功能。例如:
class MyArray extends Array {
}
let arr = new MyArray(1, 2, 3, 4),
subarr = arr.slice(1, 3)
console.log(arr.length) // 4
console.log(arr instanceof MyArray) // true
console.log(arr instanceof Array) // true
console.log(subarr instanceof MyArray) // true
注意到上例中,不仅arr是派生类MyArray的实例,subarr也是派生类MyArray的实例。内建对象继承的实用之处在于能够改变返回对象的类型。
浏览器引擎底层通过[Symbol.species]属性实现这一行为,该属性被用作返回函数的静态访问器属性。内建对象中定义了[Symbol.species]属性的包括Array、ArrayBuffer、Set、Map、Promise、RegExp以及Typed arrays。
3.2 继承表达式的类
目前extends关键字可以继承类和内建对象,但更强大的功能在于从表达式导出类!
该表达式要求能被解析为函数,并具有[[Construct]]属性和原型,示例如下:
function Sup(val) {
this.value = val
}
Sup.prototype.getVal = function () {
return 'hello' + this.value
}
class Derived extends Sup {
constructor(val) {
super(val)
}
}
let der = new Derived('world')
console.log(der) // Derived { value: 'world' }
console.log(der.getVal()) // helloworld
3.3 只能继承的抽象类
ES6引入了new.target元属性,用于判断函数是否通过new关键字调用。类的构造函数也可以通过new.target确定类的调用方式。
可以通过new.target创建抽象类(即不能直接实例化的类),示例如下:
class Abstract {
constructor(){
if(new.target === Abstract) {
throw new Error('抽象类(不能直接实例化)')
}
}
}
class Instantiable extends Abstract {
constructor() {
super()
}
}
// let abs = new Abstract() // Error: 抽象类(不能直接实例化)
let abs = new Instantiable()
console.log(abs instanceof Abstract) // true虽然不能直接使用Abstract抽象类创建实例,但它可以作为基类来派生其他类。
类的静态成员
四、类的静态成员
ES6使用static关键字声明静态成员或方法。在类的方法或访问器属性前均可使用static,唯一的限制是不能用于构造函数。
静态成员的作用在于实现某些类成员的私有化,即不可在实例中访问,必须直接在类上访问。
class Person {
constructor(name) {
this.name = name;
}
static create(name) {
return new Person(name);
}
}
let beauty = Person.create("Jenny");
// beauty.create('lee') // TypeError
如果基类包含静态成员,那么这些静态成员在派生类中同样可以使用。
例如将上例的Person作为基类,派生出Friend类并使用基类的静态方法create(),示例如下:
class Friend extends Person {
constructor(name){
super(name)
}
}
var friend = Friend.create('lee')
console.log(friend instanceof Person) // true
console.log(friend instanceof Friend) // false
可以看出,派生类依然可以访问和使用基类的静态方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。