JavaScript-类

72 阅读5分钟

介绍

本章主要介绍ES6的类,类是用于创建对象的模板,他们用代码封装数据以处理该数据,JS中的类建立在原型上,但也具有某些语法和语义未与ES5类相似语义共享。

定义类

实际上,类是“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类表达式和类声明。

类声明

定义类的一种方法是使用类声明。要声明一个类,你可以使用带有class关键字的类名(这里是Rectangle)。

class Rectangle {
    constructor(height,width) {
        this.height = height;
        this.width = wdith;
    }
}

提升

函数声明和类声明之间的一个重要区别在于,函数声明会提升,类声明不会。你首先需要声明你的类,然后再访问它,否则类似以下的代码将抛出ReferenceError:

let p = new Rectangle(); // ReferenceError

class Rectangle {}

类表达式

类表达式是定义类的另一种方法。类表达式可以命名或不命名。命名类表达式的名称是该类体的局部名称。(不过,可以通过类的(而不是一个实例的)name属性来检索它)。

// 未命名/匿名类
let Rectangle = class {
    constructor(height,width) {
        this.height = height;
        this.width = width;
    }
};
console.log(Rectangle.name); // output: "Rectangle"

// 命名类
let Rectangle = class Rectangle2 {
    constructor(height,width) {
        this.height = height;
        this.width = width;
    }
};
console.log(Rectangle); // Rectangle2

类表达式也同样受到类声明部分中提到的类型提升的限制。

类体和方法定义

一个类的类体是一对花括号/大括号{}中的部分。这是你定义类成员的位置。如方法或构造函数、

严格模式

类声明和类表达式的主体都执行在严格模式下,比如,构造函数,静态方法,原型方法,getter和setter都在严格模式下执行。

构造函数

constructor方法是一个特殊的方法,这种方法用于创建和初始化一个由class创建的对象。一个类的只能拥有一个名为"constructor"的特殊方法。如果类包含多个constructor的方法,则将抛出一个SyntaxError。

一个构造函数可以使用super关键字来调用一个父类的构造函数。

原型方法

class Rectangle {
    // constructor
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
    // Getter
    get area() {
        return this.calcArea()
    }
    // Method
    calcArea() {
        return this.height * this.width;
    }
}
const square = new Rectanle(10,10);

console.log(square.area); // 100

静态方法

static关键字用来定义一个类的静态方法。调用静态方法不需要实例化该类。但不能通过一个类实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。

class Point {
    constructor(x ,y) {
        this.x = x;
        this.y = y;
    }
    static displayName = "Point";

    static distance(a, b) {
        const dx = a.x - b.x;
        const dy = a.y - b.y;
        return Math.hypot(dx, dy);
    }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.dispalyNmae; // undefined
p1.distance; // undefined

console.log(Point.displayName); // Point
console.log(Point.distance(p1, p2)); // 7.0710678118654755
console.log(Math.hypot(-5,-5)); // 7.0710678118654755

用原型和静态方法绑定this

当调用静态或原型方法时没有指定this的值,那么方法内的this值将被置为undefined。即使你未设置"use strict",因为class体内部的代码总是在严格模式下执行。

class Animal {
    speak() {
        return this;
    }
    static eat() {
        return this;
    }
}

let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undeinfed

Animal.eat(); // class Animal
let eat = Animal.eat;
eat(); // undeinfed

如果上述代码通过传统的基于函数的语法来实现,那么依据初始的this值,在非严格模式下方法调用会发生自动装箱,若初始值是undefined,this值会被设定为全局对象。

严格模式下不会发生自动装箱,this值将保留传入状态。

function Animal() { }

Animal.prototype.speak = function(){
    return this;
}

Animal.eat = function(){
    return this;
}

let obj = new Animal();
let speak = obj.speak;
speak(); // global object

let eat = Animal.eat;
eat(); // global object

上面的两个例子,分别使用类和函数返回this,由类创建的方法,在赋值后this指向因为严格模式成为undeinfed, 而函数因为赋值后使用全局对象上的变量调用的方法,所以this指向全局对象window,关于this指向详细请浏览我的另一篇文章JavaScript-this指向

使用extends扩展子类

extends关键字在 类声明或类表达式中用于创建一个类作为另一个类的子类。

class Animal {
    constructor(name){
        this.name = name;
    }
    speak() {
        console.log(`${this.name} makes a noise.`);
    }
}

class Dog extends Animal {
    constructor(name){
        super(name); // 调用超类构造函数并传入name参数
    }
    
    speak() {
        console.log(`${this.name} barks.`)
    }
}

var d = new Dog('Mitzie');
d.speak(); // 'Mitzie barks.'

如果子类中定义了构造函数,那么它必须先调用super才能使用this。 也可以继承传统的基于函数的“类”:

function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function() {
    console.log(this.name + ' makes a noise.')
}

calss Dog extends Animal {
    speak() {
        super.speak(); // 执行原型对象身上的speak方法
        console.log(this.name + ' barks.');
    }
}

var d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise. Mitzie barks.

请注意,类不能继承常规对象(不可构造的)。如果要继承常规对象,可以改用Object.setPrototypeOf():

var Animal = {
    speak(){
        console.log(this.name + ' makes a noise.');
    }
}

class Dog {
    constructor(name){
        this.name = name;
    }
}
Object.setPrototypeOf(Dog,prototype, Animal); // 修改原型对象,如果不这样做在调用speak时会返回TypeError

vra d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.

Es5中的类和Es6中的类

Es5中的类

1.构造函数

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

2.prototype 原型对象 (在类的原型对象上定义方法)

Person.prototype.eat = function() {
    console.log("log eat funciton");
}

3.继承方法:子类的构造函数通过父类.call继承属性 子类的原型对象等于new 父类 继承方法 此时原型对象的constructor是父类的原型对象,正常情况下,一个类的constructor应该指向此类的原型对象,所以我们要把Dog赋值给Dog.prototype.constructor

function Dog(name, age){
    //继承    在子类的构造函数 通过父类.call继承属性
    Person.call(this, name, age);
}
Dog.prototype = new Person();
Dog.prototype.constructor = Dog;

Es6中的类

1.es6中类的定义比es5方便很多,直接就可以在constructor中定义我们的数据,在里边直接定义函数方法

class person {
    constructor(name) {
        this.name = name;
    }
    //方法
    eat() {
         console.log("水蜜桃")
    }
    static say() {
        console.log('hello');
    }
}

2.类的继承通过extends方法继承父类,constructor中新增我们的数据,利用super继承父类的数据,就继承完毕了

class Dog extends person {
    constructor(name, age) {
        super(name);
        this.age = age;
    }
}
var d = new Dog('旺财','3');
console.log(d); // Dog {name: '旺财', age: '3'}