当前位置: 首页 > 工具软件 > esnext > 使用案例 >

[ESNext]现在就学起来:class

俞俊逸
2023-12-01

ES5 及之前

定义

在 ES5 中,并没有类的概念,但是已经有了类型的概念。

那时,我们想创建一个类型,是这样做的:

function MyClass() {
   alert(this instanceof MyClass);
}

如此,我们定义了一个类型,但同时,他也是一个函数。

我们看看不同调用方法所得到的结果:

MyClass(); // false
new MyClass(); // true

第一句直接调用会输出 false ,因为函数内上下文 (context) 会指向 window ,因此会去检测 window 的 prototype 是否存在 MyClass 的原型(即,是否从 MyClass 继承而来)。答案是显而易见的。

而使用 new 去创建一个对象并链接到 MyClass 的时候,new 出来的对象 prototype 是存在 MyClass 的原型的。而且在执行时他的上下文会指向用 new 创建出的这个对象,所以 this 指向 (new MyClass 创建出的对象) instanceof MyClass 则是成立的。

 

继承

上文中我们提到了继承,但是在没有类的情况下如何进行继承呢?

这个时候会有人想到,既然有原型的概念,那么是不是可以在原型上去累加原型形成一个原型链来实现这个效果?

于是,出现了这样一种写法:

function A() {
    this.text = "father";
}

A.prototype.Print = function () {
    alert(this.text);
};

function B() {
    this.text = "son";
}

B.prototype = new A();
B.prototype.constructor = B;

var a = new A();
a.Print();

var b = new B();
b.Print();

初看是不是一头雾水?谁是父类?谁是子类? this.text 到底指向谁?B.prototype = new A(); 是什么鬼?直接 = A 为什么不行?

B.prototype.constructor 为什么要等于 B?不等于不行吗?等等等等问题~

我们一步一步解析看看~

首先,A 是父类,B是子类。

this.text 主要看 new 出来的对象指向的是由 A 创建的还是 B 创建的。

B.prototype 如果直接等于 A ,那么如果修改 B.prototype 下的内容,就相当于直接修改了 A 的内容,而我们其实想修改的是 A 创建出来的副本对象。

B.prototype.constructor 默认指向 B 的构造器,但是由于 B.prototype 被修改,所以需要重新指向,否则会造成构造器相关操作错误的指向 A 进而出错。

哇~看到这里你发现,怎么这么麻烦啊,我只是单纯的想让 B 继承一下 A 的东西而已啊。

没错,就是这么一个简单的事情在 ES5 及之前并做不到很简单的去写和去理解。

 

私有函数、私有变量

这两个真的做不到,没办法。不论是从语法、解析、可能的写法,都做不到。

什么?你说在函数和变量前加一个 _ 下划线代表私有?

那是不是意味着我可以随意对你的库进行注入修改逻辑或者截取秘密数据?

 

 ES6 时代

定义

在 ES6 时代,已经有了 class 关键字及其概念。

这时,想定义一个类型就非常简单了:

class MyClass {
    constructor() {
        console.dir(this);
    }
}

注意:constructor 不是必须的,只有当你在 new 的时候想传入一些参数或者做一些事的时候才需要。这里为了演示。

为什么这个演示代码中我不再写 this instanceof MyClass 了呢?

因为这个时候我们直接调用 MyClass() 已经会报错了。这种写法在检查上已经和函数有了一定的区别。

Uncaught TypeError: Class constructor MyClass cannot be invoked without 'new',

但是请注意, typeof MyClass 依然输出的是 function 。

虽然如此,给我们带来的好处还是很多的,上面这条算一个。

另外,this 上下文已经不会因为误调用而产生指向问题。这里特别指出,我着重了“误调用”,因为你如果使用 .call 或者 .apply 调用对象内函数强制改变上下文还是可以做到的。

 

继承

既然定义有了改变,那么继承方式是否有改变呢?答案是肯定的,这次改动隐藏了绝大部分的概念和复杂逻辑。

写法如下:

class A {
    constructor() {
        this.text = "father";
    }

    Print() {
        alert(`father's ${this.text}`);
    }
}

class B extends A {
    constructor() {
        super();

        this.text = "son";
    }

    Print() {
        alert(`son's ${this.text}`);
    }

    PrintA() {
        super.Print();
    }
}

const a = new A();
a.Print();

const b = new B();
b.Print();
b.PrintA();

在此例子中,我们定义了一个 class A,赋值了一个 text 为 father ,然后定义了一个函数 Print 。在这里为了区别子类,特意加上了 father's 前缀进行输出。

然后定义了一个 class B,并赋值了 text 为 son,注意!constructor 的第一行必须是 super(); 也就是说,子类必须在构造自身前先对父类进行构造,否则在操作 this 时会报错。

我们同时对 class B 中定义一个函数 Print 和 PrintA。

然后对 A 和 B 分别用 new 运算符创建对象。

调用 a.Print() 输出 father's father

调用 b.Print() 输出 son's son

从开始到现在我们都没有问题,需要留意的是下一句:

b.PrintA()

之类需要注意一下,我们内部是怎么写的呢?

super.Print();

也就是说,我们调用了继承的父类的 Print 函数,此时输出 father's son

在这个例子中,我们没有因为语法写法而感到迷惑,也不会造成,哦天啊!他们两个混为一谈了这样的问题。

可以说是十分简洁了。

最后需要注意一下! ES6 的 class 语法只支持单继承,也就是说, B 不能同时继承 A、C、....等等更多的类,该语法只支持继承一个。当然,有其他方式可以解决,这是后话了。

 

私有函数、私有变量

虽然在语法上有了革新,但是因底层限制,依然没有私有函数于私有变量。

 

EXNext

从 ES6 开始,EcmaScript 不再以版本号作为升级标识而是改成了年份标识。

也就是说,一年会升级一次或更多次。我们统称其为 ESNext。

定义

class A {
    #text = "nivk";

    age = 52;

    get Text() {
        return this.#text;
    }

    Print() {
        alert(`我叫 ${this.Text},我今年 ${this.age} 岁。`);
    }
}

class B extends A {
    #text = "dog";

    age = 26;
}

const a = new A();
a.Print();

const b = new B();
b.Print();

我们定义了 A ,并写了一个私有变量 #text ,写了一个公共变量 age,一个 get 访问器 Text 还有一个函数 Print。

再定义一个 B,覆写了 #text 私有变量和公共变量 age。

输出结果令人惊奇:

我叫 nivk,我今年 52 岁。

我叫 nivk,我今年 26 岁。

毕竟,我不是 dog 不是么~(题外话)

为什么 age 被覆写了而 #text 没有呢?这里并不是这个 get 访问器起了作用,而是 #text 本身被定义成了私有的,那么他的可访问性就受到了限制,#text 只能被自身读写,而不能被子类、外部任何代码中访问和写入。

私有属性或私有函数使用前缀 # 井号来标识,为何使用此符号标识呢?这涉及到了一些性能问题,如有兴趣请至 TC39 委员会的 Github 仓库查阅。

这就是私有变量。同样我们也可以在 class 中定义私有变量如下所示(仅关键代码):

#TestFunction() {
    // balabala....
}

Print() {
    this.#TestFunction();
}

如此一来,我们便可安心将类库发布出去也不怕别人误用或可规避一些隐性安全风险。

 

结尾

EcmaScript 标准一直在更新,所以此文仅代表发表此文时的最新状态,如果后续有语法、功能的更新则我也会在这里进行更新。

素质二连!!~~~关注、点赞~~~撒花!~

 

 类似资料: