JavaScript类

135 阅读5分钟

在JavaScript中,类使用基于原型的继承。如果两个对象从同一个原型继承属性(通常是以函数作为值的属性,或者方法),那我们说这些对象是同一个类的实例。ES6新增了关于类的语法(包括class关键字),新语法创建的类和旧式的类原理相同。

类和原型

在JavaScript中,类意味着一组对象从同一个原型对象继承属性,因此,原型对象是类的核心特征。如果我们定义了一个原型对象,然后使用Obejct.create()创建一个继承它的对象,那我们就定义了一个JavaScript类。常见的做法是定义一个工厂函数创建和初始化新对象,以下代码就是一个这样的过程,创建一个关于人物信息的类,并且继承了原型中的输出名字和年龄的方法。

// 定义一个工厂函数返回一个新对象
function person(name, age){
    // 使用Obejct.create()创建一个对象,继承person函数的prototype。
    let per = Object.create(person.prototype);

    per.name = name;
    per.age = age;

    return per;
}

// 在person函数的原型对象中添加两个方法用于输出名字和年龄
person.prototype = {
    printName: function(){
        console.log(this.name);
    },
    printAge: function(){
        console.log(this.age);
    }
}

let per = person('小松', 25);
per.printName(); // '小松'
per.printAge(); // 25

类和构造函数

构造函数是一种专门用于初始化新对象的函数,需要使用new关键字进行调用。构造函数的特性在于它的prototype属性将被用作新对象的原型。

function Person(name, age){
    this.name = name;
    this.age = age;
}

// 在Person函数的原型对象中添加两个方法用于输出名字和年龄
Person.prototype = {
    printName: function(){
        console.log(this.name);
    },
    printAge: function(){
        console.log(this.age);
    }
}

let per = new Person('小松', 25);
per.printName(); // '小松'
per.printAge(); // 25

对比以上两种定义类的方式的区别,构造函数名首字母一定要大写,这是一种约定。构造函数在用调用之前就已经创建好了对象,并将其与this绑定。

使用class创建类

ES6引入的class关键字并未改变JavaScript类基于原型的本质,class语法虽然明确、方便,但最好把它看成一种“语法糖”。

class声明体中直接添加的属性是实例属性,只能通过实例访问,无法通过类进行访问,会报错。内部引用这些属性时也和构造函数一样需要使用this

constructor关键字用于定义类的构造函数,但实际定义的函数并不叫“constructor”。class声明语句会定义一个变量xxx,并将这个构造函数的值赋给该变量。如果类不需要任何初始化,可以省略constructor,解释器会隐式创建已给空构造函数。

class Person{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    // 实例属性、方法只能通过实例访问。
    hobby = '打游戏';

    printName(){
        console.log(this.name);
    }

    printAge(){
        console.log(this.age);
    }

    printHobby(){
        console.log(this.hobby);
    }
}

let per = new Person('小松', 25);
per.printName(); // '小松'
per.printAge(); // 25
per.printHobby(); // '打游戏'
Person.printHobby(); // Uncaught TypeError: Person.printHobby is not a function

class声明体中的所有代码默认是处于严格模式,并且类声明不会提升。

静态方法

使用static声明的属性是静态属性(类属性),只能通过类去访问。静态方法通过类调用,this指向该类。

static sex = '女';
static printSex(){
    console.log(this.sex);
}
console.log(Person.sex); // '女'
console.log(per.sex); // undefined
Person.printSex(); // '女'
per.printSex(); // Uncaught TypeError: per.printSex is not a function

公有、私有和静态字段

面向对象的特点:封装、继承和多态。

封装是为了确保数据的安全性,对象是一个用来存储不同属性的容器,不仅负责存储,还要负责数据的安全。直接添加到对象中的属性并不安全,因为可以被任意修改。

如何确保数据的安全:

  • 私有化数据:实例属性前添加#就表示该属性为私有属性,只能在类的内部访问,外部无法访问。
class Person{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    #sex = '女';

    printSex(){
        console.log(this.#sex);
    }

}

let per = new Person('小松', 25);
per.printSex(); // '女'
console.log(per.#sex); // Uncaught SyntaxError: Private field '#sex' must be declared in an enclosing
  • 提供setter()和getter()方法来开放对数据的操作,这样可以控制属性的读写权限并且可以设置方法对属性的值进行验证,使用方式具体如下。
class Person{
    #sex = '女';
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    // 获取属性
    get sex(){
        return this.#sex;
    }
    // 设置属性
    set sex(sexValue){
        // 验证属性值
        if(sexValue === '男' || sexValue === '女'){
            this.#sex = sexValue;
        }else{
            console.log('属性错误');
        }
    }

}

let per = new Person('小松', 25);
console.log(per.sex); // '女'
per.sex = '男';
console.log(per.sex); // '男'

子类

在JavaScript中,通过继承可以减少重复的代码,并且可以在不修改一个类的前提下对其进行扩展。在ES6及以后,要继承父类,可以简单地在类声明中加上一个extends子句。

class Animal{
    constructor(name){
        this.name = name;
    }

    printName(){
        console.log(this.name);
    }
}

class Dog extends Animal{

}

class Cat extends Animal{

}

const dog = new Dog('小糖');
const cat = new Cat('小松');

dog.printName(); // '小糖'
cat.printName(); // '小松'

通过继承可以在不修改父类的情况下对其扩展,遵循OCP(Open-Closed Principle)开闭原则,程序应该对修改关闭,对扩展开放。

在子类中,可以通过创建同名方法重写父类的方法。也可以重写构造函数,注意重写构造函数时必须使用super()调用父类构造函数并且要注意参数的传递。如果没有在子类中定义构造函数,解释器也会自动创建一个。这个隐式定义的构造函数会将参数传递给super()。

class Animal{
    constructor(name){
        this.name = name;
    }

    printName(){
        console.log(this.name);
    }
}

class Dog extends Animal{
    // 重写printName方法
    printName(){
        console.log('方法重写');
    }
}

class Cat extends Animal{
    // 重写构造函数
    constructor(name){
        // 如果需要继承父类,第一行代码必须是super(),用于调用父类的构造函数。
        super(name)
    }
}

const dog = new Dog('小糖');
const cat = new Cat('小松');

dog.printName(); // ''方法重写''
cat.printName(); // '小松'

多态指的是函数参数的灵活性,在JS中不会检查参数类型,参数可以是任何数据类型。

class Person{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

}

let per = new Person('小松', 25);

function printName(obj){
    console.log(obj.name);
}

printName(per);

类的对象结构

通过类创建的对象中存储属性的区域实际有两个,对象自身和原型对象。

初始化的属性,以及通过·语法添加的属性(包括方法)位于对象自身中。

初始化的方法位于原型对象中,注意如果是

class Person{
    name = '小松';
    age = 18;
    printName(){
        console.log(this.name);
    }
}

let per = new Person();
per.sex = '女';
per.printSex = function(){
    console.log(this.sex);
}
console.log(per);

微信图片_20230128172638.png