1.类声明
类是一种“特殊的函数”,就像函数声明定义方式和函数表达式定义方式一样,定义类的方式也有两种:类声明定义方式和类表达式定义方式。
//类声明定义方式
class Rectangle {
//类体
}
//类表达式定义方式
//命名类
let Rectangle = class Rectangle2 {
//类体
};
console.log(Rectangle.name);
//输出类名称:Rectangle2
//未命名/匿名类
let Rectangle = class {
//类体
};
console.log(Rectangle.name);
//输出类名称:Rectangle
2.继承
一个类只能继承自一个父类,不可以继承自多个父类。
class 子类名 extends 父类名 {
//子类体
}
3.抽象(Abstract)类
虽然 JavaScript 没有专门支持抽象类的语法,但是我们可以通过 new.target
来实现抽象类。通过在实例化时检测 new.target
是不是抽象父类,可以阻止对抽象父类的实例化。
class Vehicle {
constructor() {
console.log(new.target);
if (new.target === Vehicle) {
throw new Error('Vehicle cannot be directly instantiated');
}
}
}
class Bus extends Vehicle {}
new Bus(); //class Bus {}
new Vehicle(); //Error: Vehicle cannot be directly instantiated
4.重写(Override)
可以在抽象父类构造函数中进行检查,以要求子类必须定义某个方法。
//父类
class Vehicle {
constructor() {
if (new.target === Vehicle) {
throw new Error('Vehicle cannot be directly instantiated');
}
if(!this.foo) {
throw new Error('Inheriting class must define foo()');
}
console.log('success!');
}
}
//子类
class Bus extends Vehicle {
foo() {};
}
//子类
class Van extends Vehicle {}
new Bus(); // success!
new Van(); // Error: Inheriting class must define foo()
5.构造函数
一个类非必须有构造函数。如果是父类没有构造函数,相当于父类中定义了一个空的构造函数。如果是子类没有构造函数,相当于使用继承自父类的构造函数。如果有构造函数,则只能有一个构造函数。
class 类名 {
constructor() {
//构造函数体
}
}
//实例属性的值只能由构造函数的参数传递给它。
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
//实例字段不仅仅可以由构造函数的参数传递给它,还可以由访问器属性或方法等方式操作它。
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
6.静态初始化块(Static Initialization Blocks)
一个类可以有多个静态初始化块。
构造函数是用于实例字段的初始化,而静态初始化块(Static Initialization Blocks)是用于静态字段的初始化。
class 类名 {
static {
//静态初始化块体
}
}
//示例
class ClassWithStaticInitializationBlock {
static staticProperty1 = 'Property 1';
static staticProperty2;
static {
this.staticProperty2 = 'Property 2';
}
}
console.log(ClassWithStaticInitializationBlock.staticProperty1);
//输出:Property 1
console.log(ClassWithStaticInitializationBlock.staticProperty2);
//输出:Property 2
7.创建对象
7.1类创建方式
//如果没有参数传递,可省略括号,一般不推荐省略。
let 对象 = new 类名();
let 对象 = new 类名(参数);
7.2对象字面量创建方式
let 对象 = {
数据属性名: 值,
方法名: function() {
//方法体
},
//方法名简写
方法名() {
//方法体
}
};
7.3Object()构造函数创建方式
Object()
构造函数创建方式目前已很少使用,而对象字面量创建方式和类创建方式非常流行。
//等同于对象字面量创建方式let 对象 = {};
let 对象 = new Object();
对象.属性名 = 值;
对象.方法名 = function() {
//方法体
};
8.访问成员
//类定义方式使用.访问
类.静态字段名
类.静态访问器属性名
类.静态方法名()
对象.实例字段名
对象.实例访问器属性名
对象.实例方法名()
//对象字面量方式使用.访问
对象.数据属性名
对象.访问器属性名
对象.方法名()
//对象字面量方式还可以使用[]访问属性,但不可以使用[]访问方法。
//[]内可以使用单引号或双引号,没有区别。
对象['数据属性名']
对象['访问器属性名']
使用方括号 []
的主要优势在于支持可计算属性名。
let person = {
name: '张三',
age: 28
};
console.log(person.name); // 张三
console.log(person['name']); // 张三
let a = "name";
console.log(person[a]); // 张三
9.重载(Overload)
JavaScript不支持重载,如果定义了两个同名函数,则后定义的会覆盖先定义的。可以通过检查参数的类型和数量,然后执行不同的逻辑来模拟函数重载。
10.访问器属性
访问器属性 getter 和 setter 可以使用相同的名称。
10.1用于类
class ClassWithGetSet {
//静态getter
static get staticGetter() {
//访问器属性体
}
//静态setter
static set staticSetter(参数) {
//访问器属性体
}
//实例getter
get instanceGetter() {
//访问器属性体
}
//实例setter
set instanceSetter(参数) {
//访问器属性体
}
}
10.2用于对象字面量
如果给已存在对象字面量添加访问器属性,可以使用 Object.defineProperty()
方法。
const 对象 = {
get 访问器属性名() {
//访问器属性体
},
set 访问器属性名() {
//访问器属性体
}
}
11.属性值简写
在对象字面量创建对象方式中,当属性名和代表属性值的变量名一样时,可以只使用变量名简写语法。
//属性名与变量名不一样
let a = '张三';
let person = {
//前面name是属性名,后面a是变量名。
name: a
};
console.log(person); // { name: "张三" }
//属性名与变量名一样
let name = '张三';
let person = {
//前面name是属性名,后面name是变量名。
name: name
};
//只使用变量名简写语法
let person = {
name
};
console.log(person); // { name: "张三" }
当右侧是返回对象字面量的函数时,也支持属性值简写。
function makePerson(name) {
return {
name
};
}
let person = makePerson('张三');
console.log(person); // { name: "张三" }
12.可计算名
12.1用于类成员
可计算名还可用于实例字段等类成员。
const PREFIX = "prefix";
class ClassWithField {
[`${PREFIX}Field`] = "prefixed field";
}
const instance = new ClassWithField();
console.log(instance.prefixField); // prefixed field
12.2用于属性和方法
可计算名可用于数据属性名、访问器属性名、方法名。
12.2.1Object()构造函数创建对象方式
const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
let person = new Object();
person[nameKey] = '张三';
person[ageKey] = 28;
person[jobKey] = 'Software engineer';
console.log(person);
//输出:{ name: "张三", age: 28, job: "Software engineer" }
12.2.2对象字面量创建对象方式
//数据属性名
const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
let person = {
[nameKey]: '张三',
[ageKey]: 28,
[jobKey]: 'Software engineer'
};
console.log(person);
//输出:{ name: "张三", age: 28, job: "Software engineer" }
//访问器属性名
const expr = "foo";
const obj = {
get [expr]() {
return "bar";
}
};
console.log(obj.foo); // bar
//方法名
const methodKey = 'sayName';
let person = {
[methodKey](name) {
console.log(name);
}
}
person.sayName('张三'); // 张三
12.3用于对象解构赋值
const key = "z";
const { [key]: foo } = { z: "bar" };
console.log(foo); // bar
13.super关键字
使用场景 | 调用方式 |
调用父类的构造函数。 | 只支持 super(arguments) 一种方式,必须放在 this 关键字 和 构造函数返回 之前。 |
调用父类的成员(支持静态字段、静态方法、实例方法,不支持实例方法)。 | 支持 super.prop 和 super[expr] 两种方式。 |
调用父对象字面量的访问器属性和方法。 | 支持 super.prop 和 super[expr] 两种方式。 |
//调用父类的构造函数
class Foo {
constructor(name) {
this.name = name;
}
getNameSeparator() {
return '-';
}
}
class FooBar extends Foo {
constructor(name, index) {
super(name);
this.index = index;
}
getFullName() {
return this.name + super.getNameSeparator() + this.index;
}
}
const firstFooBar = new FooBar('foo', 1);
console.log(firstFooBar.name); //输出:foo
console.log(firstFooBar.getFullName()); //输出:foo-1
//调用父类的成员
//静态方法
class Rectangle {
static logNbSides() {
return "I have 4 sides";
}
}
class Square extends Rectangle {
static logDescription() {
return `${super.logNbSides()} which are all equal`;
}
}
Square.logDescription();
//输出:I have 4 sides which are all equal
//静态字段和实例方法
class Base {
static baseStaticField = 90;
baseMethod() {
return 10;
}
}
class Extended extends Base {
extendedField = super.baseMethod(); // 10
static extendedStaticField = super.baseStaticField; // 90
}
//调用父对象字面量的属性
//访问器属性
const parent = { prop: 1 };
const child = {
myParent() {
console.log(super.prop);
}
};
Object.setPrototypeOf(child, parent);
child.myParent(); //输出:1
//方法
const obj1 = {
method1() {
console.log("method 1");
},
};
const obj2 = {
method2() {
super.method1();
},
};
Object.setPrototypeOf(obj2, obj1);
obj2.method2(); //输出:method 1
14.this关键字
函数体可以包含 this
关键字,但具体 this
关键字指向谁,大多数情况下,this
指向此函数被真正调用的上下文,极个别情况下,this
指向此函数被真正声明的上下文。
//指向函数被真正调用的上下文
class MyClass {
name = "MyClass";
getName() {
return this.name;
}
}
const c = new MyClass();
const obj = {
name: "obj",
getName: c.getName,
};
console.log(obj.getName()); //输出:obj
//指向函数真正被声明的上下文
class MyClass {
name = "MyClass";
getName = () => {
return this.name;
};
}
const c = new MyClass();
const g = c.getName;
console.log(g()); //输出:MyClass
14.1全局上下文
全局上下文 | this指向 |
全局 | 指向函数被真正调用的上下文,所以指向 window 。 |
14.2类上下文
类上下文 | this指向 |
静态上下文(静态初始化块、静态方法) | 指向函数被真正调用的上下文,所以指向类。 |
实例上下文(构造函数、实例方法) | 指向函数被真正调用的上下文,所以指向对象。 |
14.3函数上下文
函数上下文 | this指向 |
函数(普通函数、回调函数) | 指向函数被真正调用的上下文。 |
箭头函数 | 指向函数被真正声明的上下文。 |
14.4bind()方法
调用 f.bind(someObject)
会创建一个与 f
函数具有相同函数体和作用域的新函数,但是 this
的值将永久绑定到 bind()
方法的第一个参数,无论如何调用该函数。
function f() {
return this.a;
}
const g = f.bind({ a: "azerty" });
console.log(g()); //输出:azerty
//bind()方法只可以运行一次
const h = g.bind({ a: "yoo" });
console.log(h()); //输出:azerty
const o = { a: 37, f, g, h };
console.log(o.a, o.f(), o.g(), o.h()); //输出:37, 37, azerty, azerty
原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/javascript/javascriptlang/4729.html