深入理解ES6之——JS类的相关知识

基本的类声明

类声明以class关键字开始,其后是类的名称;剩余部分的语法看起来像对象字面量中的方法简写,并且在方法之间不需要使用逗号。

class Person {
    //等价于prototype的构造器
    constructor(name) {
        this.name = name;
    }

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

let per = new Person("cf");
per.SayName();//cf

console.log(typeof Person);//function

构造器最大的用处就是在创建对象时执行初始化,当创建一个对象时,系统会为这个对象的实例进行默认的初始化。如果想改变这种默认的初始化,就可以通过自定义构造器来实现。

类与自定义类型的区别

  1. 类声明不会被提升。类声明的行为类似let,因此在程序的执行到达声明处之前,类会存在于暂时性死区内。
  2. 类声明中的所有代码会自动运行在严格模式下,并且也无法退出严格模式
  3. 类的所有方法都是不可枚举的
  4. 类的所有方法内部都没有[[Construct]],因此使用new来调用他们会抛出错误
  5. 调用类构造器时不使用new会抛出错误。
  6. 试图在类的内部修改重写类名,会抛出错误。

类与函数有相似之处,即它们都有两种形式:声明与表达式

基本的类表达式

let PersonClass = class {
    constructor(name) {
        this.name = name;
    }

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

let per = new PersonClass("cc");
per.SayName();

类表达式和类声明都不会被提升

具名类表达式

let PersonClass = class PersonClass2 {
    constructor(name) {
        this.name = name;
    }

    SayName() {
        console.log(this.name);
        console.log(PersonClass2);
    }
}

let per = new PersonClass("cc");
per.SayName();

此例中的类表达式被命名为PersonClass2.PersonClass2只在类定义内部存在,因此只能用在类方法内部。

作为以及公民的类

在编程中,能被当做值来使用的就成为一级公民,意味着他能作为参数,能作为函数返回值,能用来给变量赋值。js的函数就是一级公民。

let createObject = function (classDef) {
    return new classDef();
}

let obj = new createObject(class {
    SayHi() {
        console.log("hi");
    }
})

obj.SayHi();

自有属性需要在类构造器中创建,而类还允许你在原型上定义访问器属性。为了创建一个getter ,要使用 get 关键字,并要与后方标识符之间留出空格;创建 setter 用相同方式,只是要换用 set 关键字

class CustomHtmlElement {
    constructor(element) {
        this.element = element;
    }
    get html() {
        return this.element.innerHTML;
    }

    set html(value) {
        this.element.innerHTML = value;
    }
}

需计算的成员名

类方法与类访问器属性也能使用需计算的名称。语法相同于对象字面量中的需计算名称:无须使用标识符,而是用方括号来包裹一个表达式

let methodName = "SayName";
class PersonClass {
    constructor(name) {
        this.name = name;
    }

    [methodName]() {
        console.log(this.name);
    }
}

let per = new PersonClass("cc");
per.SayName();

生成器方法

可以使用Symbol.iterator来定义生成器方法,从而定义出类的默认迭代器。

class Collection {
    constructor() {
        this.items = [];
    }

    *[Symbol.iterator]() {
        yield this.items;
    }
}

let collection = new Collection();

collection.items.push(1);
collection.items.push(2);
collection.items.push(3);

for (let num of collection.items) {
    console.log(num);
}

静态成员

静态成员不能通过实例来访问,你始终需要直接用类自身来访问

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

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

    static create() {
        return new PersonClass(name);
    }
}

let per = new PersonClass.create("cc");

使用派生类进行继承

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

    getArea() {
        return this.length * this.width;
    }
}

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }
}

let sq = new Square(10);
console.log(sq.getArea());//100

继承了其他类的类被称为派生类。如果派生类指定了构造器,就需要使用super(),否则就会出错。如果不定义构造器,super()方法会被自动调用,并会使用创建新实例时提供的所有参数。例如:

class Square extends Rectangle {

}

let sq = new Square(10, 10);
console.log(sq.getArea());//100

使用super需要牢记以下几点

  1. 你只能在派生类中使用super。
  2. 在构造器中,你必须在访问this之前调用super()。由于super()负责初始化this,因此试图先访问this自然后报错。
  3. 唯一能避免调用super()的办法,是从类构造器中返回一个对象。

屏蔽类方法

派生类中的方法总是会屏蔽基类的同名方法。例如:你可以将getArea()方法添加到Square类,以便重新定义它的功能。

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

    getArea() {
        return this.length * this.width;
    }
}

class Square extends Rectangle {
    constructor(length, name) {
        super(length, length);
        this.name = name;
    }

    getArea() {
        return `this is ${this.name} input ${this.length}`
    }
}

你也可以使用super.getArea()方法来调用基类中的同名方法

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }

    getArea() {
        return super.getArea();
    }
}

继承静态成员

如果基类包含静态成员,那么这些静态成员在派生类中也是可用的。

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 sqr = Square.create(10, 10);

console.log(sqr.getArea());//100

从表达式中派生类

在ES6中派生类的最强大能力,或许就是能够从表达式中派生类。只要一个表达式能够返回一个具有[[Constructor]]属性以及原型的函数,你就可以对其使用extends。

function Rectangle(length,width){
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function(){
    return this.length * this.width;
}

class Square extends Rectangle{
    constructor(length){
        super(length,length);
    }
}
let x = new Square(10);

console.log(x.getArea());//100

extends后面能接受任意类型的表达式,这带来了巨大的可能性,例如动态的决定要继承的类。

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function () {
    return this.length * this.width;
}

function getBase() {
    return Rectangle;
}

class Square extends getBase() {
    constructor(length) {
        super(length, length);
    }
}

let x = new Square(10);

console.log(x.getArea());

任意表达式都能在extends关键字后使用,但并非所有表达式的结果都是一个有效的类。

  1. null
  2. 生成器函数

继承内置对象

在ES6类的继承中,this的值会先被基类创建,随后才被派生类的构造器所修改。结果是this初始就拥有作为基类的内置对象的所有功能,并能正确的接收与之关联的所有功能。

class MyArray extends Array {

}

let arr = new MyArray();
arr[0] = 'red';
console.log(arr.length);

arr.length = 0;
console.log(arr[0]);

Symbol.species属性

继承内置对象的一个有趣方面是:任意能返回内置对象实例的方法,在派生类上却会自动返回派生类的实例。

class MyArray extends Array {

}

let arr = new MyArray(1, 2, 3);
let subitems = arr.slice(1, 2);
console.log(subitems);

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java后端技术栈cwnait

你确定能把main方法解释清楚?

main方法是我们最熟悉的方法了。从最初的开始入门Java开始就接触它了,main方法是Java程序的入口点,由Java虚拟机自动调用。因此,在很多人眼里,将m...

7440
来自专栏leon的专栏

类的继承

例子以图书馆中的书入库归类为例。 以下是简化后的父类Book(也可称为基类)。 目的是通过继承该父类,产出Computer(计算机)子类。 并且,子类拥有新方法...

7720
来自专栏码匠的流水账

聊聊dubbo的ServiceBeanExportedEvent

dubbo-2.7.3/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/conf...

8530
来自专栏腾讯云IoT

【IoT迷你赛】TencentOS tiny学习源码分析(6)——互斥锁

互斥锁又称互斥互斥锁,是一种特殊的信号量,它和信号量不同的是,它具有互斥锁所有权、递归访问以及优先级继承等特性,在操作系统中常用于对临界资源的独占式处理。在任意...

16650
来自专栏明丰随笔

WCF学习笔记 2

在学习WCF的ABCB之前,我们先创建一个NetNamedPipeBinding绑定方式的服务。

7820
来自专栏gfu

java多线程实战(后续还会更新)

这是我写的一个简单的多线程应用。主要就是创建两个对象,一个是剑姬,一个是剑圣。通过extends Thread和implements Runable的方式创建线...

10020
来自专栏PHPer技术栈

PHP7.2新特性

扩展文件不再需要通过文件加载 (Unix下以.so为文件扩展名,在Windows下以 .dll 为文件扩展名) 进行指定。可以在php.ini配置文件进行启用

9650
来自专栏gfu

java基础之构造器

构造器都通过,但是其中的原理不是每个人都知道,先用简单的代码来描述一下构造器的使用。

6230
来自专栏PHPer技术栈

PHP 编写守护进程

11020
来自专栏格姗知识圈

学会它,再多 Bug 也不怕

对于一名开发者来说,找出并处理掉Bug是不可或缺的能力。能够熟练的调试程序将大大提升开发的效率。学好DeBug,再多Bug也不怕。Debug用来追踪代码的运行流...

7630

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励