instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
1、类的声明
1.1 基本的类声明语法
要声明一个类,首先编写class关键字,紧跟着的是类的名字。
class Person {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
类的使用
let person = new Person("yff");
person.sayName();//output yff
console.log(person instanceof Person);
console.log(person instanceof Object);
console.log(typeof Person);//"function"
console.log(typeof Person.prototype.sayName);//"function"
执行结果:
类中使用简洁语法来定义方法,因而不需要添加function关键字。
除constructor外没有其他保留的方法名,所以可以尽情添加方法。
自有属性是实例中的属性,不会出现在原型上,且只能在类的构造或方法中创建,此例中的name就是一个自有属性(成员变量)。
1.2 为何使用类语法
类和函数的区别,类的特点:
- 函数声明可以被提升,而类声明不能被提升。真正执行声明语句之前,它们会一直存在于临时死区中。
- 类声明中的所有代码将自动运行在严格模式,而且无法强行让代码脱离严格模式执行。
- 在自定义类型中,需要通过Object.defineProperty()方法手工指定某个方法为不可枚举;而在类中,所有方法都是不可枚举的。
- 每个类都有一个名为[[Construct]]的内部方法,通过关键字new调用那些不含[[Construct]]的方法会导致程序抛出错误。
- 使用除关键字new以外的方式调用类的构造函数会导致程序抛出错误。
- 在类中修改类名会导致程序报错
2、类表达式
类和函数都有两种存在形式:声明形式和表达式形式。
- 声明形式的函数和类都由相应的关键字(function和class)进行定义,随后跟一个标识符。
- 表达式形式不需要在关键字后添加符。
声明形式:
//类的声明形式
class Person {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
//函数的声明形式
function test() {
console.log("test");
}
类的表达式不需要标识符在类后。类声明和类表达式功能一样,不会像函数声明和函数表达式一样被提升。
//类的表达式语法
let Person = class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
3、作为一等公民的类
一等公民是指:
- 可以传入函数。
- 可以从函数返回。
- 可以赋值给变量的值。
JavaScript中函数是一等公民、类也是一等公民。
示例1:调用createObject()函数时传入一个匿名表达式作为参数,然后通过关键字new实例化这个类并返回实例,将其储存在变量obj中。
function createObject(classDef) {
return new classDef();
}
let obj = createObject(
class Person {
sayName() {
console.log("yxy");
}
}
)
obj.sayName();
示例2:通过立即调用类构造函数创建单例。用new调用类表达式,紧接着通过一对小括号调用这个表达式,例如:
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}("yxy");
person.sayName();//"yxy"
4、访问器属性
尽管应该在类构造函数中创建自己的属性,但是类也支持直接在原型上定义访问器属性。
- 创建getter时,需要在关键字get后紧跟一个空格和相应的标识符。
- 创建setter时,把关键字get替换为set即可。
class CustomHTMLElement {
constructor(element) {
this.element = element;
}
get html() {
return this.element.innerHTML;
}
set html(value) {
this.element.innerHTML = value;
}
};
let descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html");
console.log("get" in descriptor);//true
console.log("set" in descriptor);//true
下面是运行结果:
这段代码中的CustomHTMLElement类是一个针对现有DOM元素的包装器,并通过getter和setter方法将这个元素的innerHTML方法委托给html属性,这个访问器属性是在CustomHTMLElement.prototype上创建的。与其他方法一样,创建时声明该属性不可枚举。
5、可计算成员名称
类和对象字面量还有很多相似之处,类方法和访问器属性也支持使用可计算名称。下面示例中,Person类通过变量来给类定义中的方法命名,字符串"sayName"被赋值给methodName变量,然后methodName又被用于声明随后可直接访问的sayName()方法。
let methodName = "sayName";
class Person {
constructor(name) {
this.name = name;
}
[methodName]() {
console.log(this.name);
}
};
let person1 = new Person("yxy");
person1.sayName();//"yxy"
6、生成器方法
通过Symbol.iterator定义生成器方法即可为类定义默认叠加器。
class Collection {
constructor() {
this.items = [];
}
*[Symbol.iterator]() {
yield* this.items.values();
}
};
let collection = new Collection();
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}
运行结果如下:
这个示例用可计算名称创建了一个代理this.items数组values()迭代器的生成器方法。任何管理一系列值的类都应该引入默认迭代器,因为一些与特定集合有关的操作需要所操作的集合含有一个迭代器。现在可以将Collection的实例直接用于for-of循环中或用展开运算符操作它。
7、静态成员
在类方法前面加上static关键字,则该方法定义为静态方法。类中的所有方法和属性都可以使用static关键字来定义,但不能用来定义构造函数。
class Person {
constructor(name) {
this.name = name;
}
sayName() {
Person.singSong();//"lalalala"
console.log(this.name);
}
static singSong() {
console.log("lalalala");
}
};
let person1 = new Person("yxy");
person1.sayName();//"yxy"
// person1.singSong();//报错,person1.singSong is not a function
let person2 = Person.singSong();//"lalalala"
静态方法的使用:
- 静态方法不能在类实例中访问,只能通过类名.方法名的方法访问。
8、继承和派生类
使用extends关键字可以指定类继承的函数。原型会自动调整,通过调用super()方法即可访问基类的构造函数。
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.length;
}
}
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
}
let square1 = new Square(3);
console.log(square1.getArea());//9
console.log(square1 instanceof Square);//true
**instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
继承自其他类的类被称作派生类,如果在派生类中指定了构造函数则必须要调用super(),如果不调用,程序会报错。如果选择不使用构造函数,则创建新的类实例时会自动调用super()并传入参数。
super()注意事项
1、只可在派生类的构造函数中使用super(),如果在非派生类(不是用extends声明的类)或函数中使用则会抛出错误。
2、在构造函数中,访问this前,一定要调用super(),它负责初始化this,如果在调用super()之前尝试访问this会导致程序出错。
3、如果不想调用super(),则唯一的方法是让类的构造函数返回一个对象。
8.1 类方法遮蔽
派生类中的方法总会覆盖基类中的同名方法。
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.length;
}
}
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
getArea() {
let sArea = super.getArea();
console.log("sArea:" + sArea);//9
return this.length * this.length * 3;
}
}
let square1 = new Square(3);
console.log(square1.getArea());//27
由于为Square定义了getArea()方法,便不能在Square的实例中调用Rectangle.prototype.getArea()方法,如果想调用基类中的该方法,可以调用super.getArea()方法。
8.2 静态成员继承
如果基类中有静态成员,那这些静态成员也可以在派生类中使用。
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
static create(length, width) {
return new Rectangle(length, width);
}
}
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
}
let rect = Square.create(3, 4);
console.log(rect.getArea());//12,不是9
console.log(rect instanceof Square);//false,rect是Rectangle的实例不是Square的实例