基本语法
定义类
Class 类是 ES6 新增的,在之前本身是不具有类的概念的。都是通过构造函数来模拟面对对象的。
function Point(x,y){
this.x = x
this.y = y
}
Point.prototype.toString = function(){
return `(${this.x}, ${this.y})`
}
let point = new Point(1,2)
即使是ES6 的class也可以看作只是一个语法糖。
class Point {
constructor(x,y){
this.x = x
this.y = y
}
toString(){
return `(${this.x}, ${this.y})`
}
}
let point = new Point(1,2)
console.log(point);
console.log(typeof Point); // function
console.log(Point === Point.prototype.constructor); // true
通过关键字class定义类,里面的constructor()方法就是构造方法,用来初始化类的。这里的this指向由当前类实例的对象。
类的数据类型是function,依然有原型对象,且原型对象的constructor属性执行类本身。使用的时候也是通过new的,和构造函数一致。
constructor() 方法
constructor()方法是类的默认方法,当new一个类时,会自动调用该方法,如果未定义的话,相当于调用一个空的constructor()。constructor()方法默认返回实例对象(this)
class Point{}
// 等同于
class Point{
constructor(){
return this
}
}
constructor()函数返回一个新的对象,导致实例对象不是Point类的实例。
class Point{
constructor(){
return Object.create(null)
}
}
let point = new Point()
console.log(point instanceof Point); // false
类的实例
生成类的实例必须通过new来调用,否则报错。
类的方法和属性除非是定义在this对象上,否则都是定义在class上(原型上)
class Point{
constructor(x,y){
this.x = x
this.y = y
}
toString(){
return `(${this.x}, ${this.y})`
}
}
let point = new Point(1,2)
console.log(point.toString()); // (1, 2)
console.log(point.hasOwnProperty('x')); // true
console.log(point.hasOwnProperty('y')); // true
console.log(point.hasOwnProperty('toString')); // false
console.log(point.__proto__.hasOwnProperty('toString')); // true
上面代码,x和y都是实例对象point上(this对象),所以hasOwnProperty()方法返回true,而toString()方法则返回false
类的所有实例共享一个原型对象。
let p1 = new Point(1, 2)
let p2 = new Point(2, 3)
console.log(p1.__proto__ === p2.__proto__) // true
p1.__proto__.range = function () {
return this.x * this.y
}
console.log(p1.range()); // 2
console.log(p2.range()); // 6
上面代码中,p1和p2都是Point的实例,它们的原型指向相同。在p1原型上定义的方法,p2同样可以调用
实例属性
在class中显示声明属性,不用在构造函数中创建定义
class Person {
name
age = 0
gender
hobby
constructor(name, gender) {
this.name = name
this.gender = gender
}
}
let person = new Person('jack', '男')
console.log(person.name) // jack
console.log(person.age) // 0
console.log(person.gender) // 男
console.log(person.hobby) // undefined
在class顶部显示定义了属性,依然可以在constructor()方法中设置属性的值(设置name的值为jack)。属性可以设置默认值,不设置就是undefined
静态属性
静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
class Person{
static strict = true
}
Person.type = 1
console.log(Person.type); // 1
console.log(Person.strict); // true
目前有两种写法,一种是通过static关键字,另一种是直接在类上定义属性。
私有属性
私有属性是通过在属性名之前使用#定义。私有属性只能在类的内部使用,外部调用会报错
class Counter {
#count = 0
get value() {
return this.#count;
}
increment() {
this.#count++;
}
getValue(){
return this.#count
}
}
let counter = new Counter()
// console.log(counter.#count); // 报错
counter.increment()
console.log(counter.getValue()); // 1
Getter 和 Setter
class Counter{
_count = 0
get count(){
return this._count
}
set count(value){
this._count = value
}
}
let counter = new Counter()
counter.count = 10
console.log(counter.count); // 10
属性表达式
类的属性名,可以采用表达式进行动态设置
let methodName = 'getArea'
class Square {
constructor(x, y) {
this.x = x
this.y = y
}
[methodName]() {
return this.x * this.y
}
}
let square = new Square(1, 2)
console.log(square.getArea()) // 2
console.log(square[methodName]()) // 2
console.log(square['getArea']()) // 2
Class 表达式
const myCounter = class Counter{
getClassName() {
return Counter.name;
}
}
let counter = new myCounter()
console.log(counter.getClassName()); // Counter
console.log(Counter.name); // 报错 Counter is not defined
console.log(myCounter.name); // Counter
Counter只能在Class内部使用。在Class外部这个类只能通过myCounter调用
立即执行的 Class。
const myClass = new class{
toString(){
console.log('Class');
}
}()
myClass.toString() // Class
实例方法
在类中定义的方法会被实例继承,可以访问和修改实例数据。实例方法可以调用其它实例方法和静态方法
class Person {
name = ''
constructor(name) {
this.name = name
}
getName() {
return this.name
}
printName() {
console.log(`my name is ${this.getName()}`)
}
}
let p = new Person('jack')
console.log(p.getName()) // jack
p.printName() // my name is jack
静态方法
在一个方法前,加上static关键字,就表示该方法不会被实例继承,只能直接通过类来调用。
- 静态方法可以访问静态字段
- 静态方法无法访问实例字段
- 静态方法中的
this指向的是类 - 静态方法可以与非静态方法重名,静态调用时调用静态重名方法,实例调用时调用实例重名方法
class Person {
static name = 'Unknown'
static getName() {
return this.name
}
static a(){
this.b()
}
static b(){
console.log('b');
}
b(){
console.log('bb');
}
}
let p = new Person('jack')
// console.log(p.getName()) // p.getName is not a function
console.log(Person.getName()); // Unknown
Person.a() // b
p.b() // bb
私有方法
私有方法是只能在类的内部访问的方法。
class Counter {
a = 1
#b = 2
#printB(){
console.log(this.#b);
}
print(){
this.#printB()
}
}
let counter = new Counter()
counter.print() // 2
静态私有方法和静态私有属性
静态的私有属性或私有方法只能在内部调用
class Counter {
static #a = 1
static #printA(){
console.log(Counter.#a);
}
static printA(){
Counter.#printA()
}
}
Counter.printA() // 1
运算符
in 运算符
in运算符判断某个对象属性是否存在。判断私有属性时,in只能用在类的内部。
class A {
#foo = 0;
static isA(obj) {
if (#foo in obj) {
return true;
} else {
return false;
}
}
m() {
console.log(#foo in this); // true
console.log(#bar in this); // false
}
}
instanceof 运算符
instanceof运算符判断对象是否为类的实例
class Person {
name
constructor(name) {
this.name = name
}
}
let p = new Person('jack')
let obj = {}
console.log(p instanceof Person) // true
console.log(obj instanceof Person) // false
继承
Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。
新建子类实例时,父类的构造函数必定会先运行一次,必须调用super()。
class A {
constructor(x, y) {
this.x = x
this.y = y
}
toString() {
console.log(`${this.x},${this.y}`)
}
}
class B extends A {
constructor(x, y) {
super(x, y) // 调用父类的constructor(x, y)
}
}
let b = new B(1, 2)
b.toString() // 1,2
console.log(b instanceof A) // true
console.log(b instanceof B) // true
私有属性和私有方法的继承
子类无法继承父类的私有属性和私有方法,可以通过在父类定义普通方法来调用私有属性和方法。
class A {
#a = 2
#printA(){
console.log(this.#a);
}
print(){
this.#printA()
}
}
class B extends A{
constructor(){
super()
}
printB(){
this.#printA() // 报错 在类 "A" 外部不可访问
}
}
let b = new B()
console.log(b);
console.log(b.#a); // 报错 在类 "A" 外部不可访问
b.print() // 2
静态属性和静态方法的继承
父类的静态属性和静态方法,会被子类继承。静态属性是通过软拷贝实现继承的。
class A {
static count = 1
static obj = {
name:'A'
}
static printA(){
console.log(A.obj.name);
}
}
class B extends A {
constructor(){
super()
B.count--
B.obj.name = 'B'
}
}
A.printA() // A
let b = new B()
console.log(B.count); // 0
console.log(A.count); // 1
B.printA() // B
A.printA() // B
在A类中定义的基础属性count,在B类内部修改B.count影响不到A.count。因为继承静态属性,是通过浅拷贝复制的属性值,因为A.count和B.count是两个独立的属性。
在B类内部修改B.obj.name影响到了A.obj.name,是因为浅拷贝复制了引用地址。
Object.getPrototypeOf()
Object.getPrototypeOf()方法可以用来从子类上获取父类。
class A { }
class B extends A { }
console.log(Object.getPrototypeOf(B)); // class A { }
console.log(Object.getPrototypeOf(B) === A); // true
new.target 属性
new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined。
function Person(name) {
if (new.target !== undefined) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
// 另一种写法
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错
Class 内部调用new.target,返回当前 Class。如果是子类继承父类时,new.target会返回子类。
class A {
constructor(x) {
console.log(new.target === A);
this.x = x;
}
}
class B extends A {
constructor(y){
super(y)
}
}
var a = new A(3); // true
var b = new B(1) // false
利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error('本类不能实例化');
}
}
}
class Rectangle extends Shape {
constructor(length, width) {
super();
// ...
}
}
var x = new Shape(); // 报错
var y = new Rectangle(3, 4); // 正确
super 关键字
super关键字有两种使用方式,当作函数使用或者对象使用。
作为函数调用时,super代表父类的构造函数。子类的构造函数必须执行一次super()函数。
class A {}
class B extends A {
constructor() {
super();
}
}
调用super()就是调用父类的构造函数生成子类的this对象,把父类的实例属性和方法添加到this对象。然后子类可以在这个this对象上进行操作。
super作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类。
class A {
static printA(msg) {
console.log('static', msg);
}
printA(msg) {
console.log('instance', msg);
}
}
class B extends A {
static printB(msg) {
super.printA(msg); // 等同于 A.printA()
}
printB(msg) {
super.printA(msg); // 等同于 A.prototype.printA()
}
static print(){
super.print()
}
}
B.printB(1); // static 1
var b = new B();
b.printB(2); // instance 2
类的 prototype 属性和__proto__属性
Class 类作为构造函数的语法糖同样拥有prototype属性和__proto__属性
- 子类的
__proto__属性,表示构造函数的继承,总是指向父类。 - 子类
prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A {
a = 1
printA() {
console.log(this.a)
}
}
class B extends A {
b = 2
printB() {
console.log(this.b)
}
}
console.log(B.__proto__) // class A
console.log(B.__proto__ === A) // true
console.log(B.prototype.__proto__) // class A prototype
console.log(B.prototype.__proto__ === A.prototype) // true