JavaScript海底两万里:类

争取一文帮自己搞清楚JavaScript中的类class


在ES6之前,JavaScript中并不存在的概念,开发者们对于原型和继承可谓是绞尽脑汁,通过各种方式来实现基于原型链的继承。

日常开发中我们经常需要创建许多相同类型的对象,在学习了构造器之后,我们可以想到通过new Function()的形式来实现这种需求。不过在现代JavaScript中引入了更高级的“”的概念,它包含许多适用于面向对象的新功能。

我作为一个半吊子Java开发者,一直在与面向对象编程打交道,而连名字都蹭Java热度的JavaScript,在面向对象这块的设计跟Java不能说一模一样,只能说是大差不差,除了没有显示的publicprivate字段之外,别的方面基本都是相通的。

class

1
2
3
4
5
6
7
8
class MyClass {
<prop> = <value>;
constructor() {}
<method1>() {}
<method2>() {}
<method3>() {}
// ...
}

这是声明一个类的例子,可以看到JavaScript采用显示的constructor关键字来声明构造方法,而不是像Java那样用与类名相同的方法。

在JavaScript中,类的本质是函数,所以它不完全是语法层面的特性。JavaScript的类的底层实现事实上是构造器constructor和原型(链)[[Prototype]]。当我们new一个对象时,JavaScript会通过以类名命名的构造器来创建对象。

但这并不意味着class就仅仅是一个语法糖,它们之间还是存在一定差异的:

  1. 通过class创建的函数内部包含[[IsClassConstructor]]: true属性标志,解释器会检查这个属性来判断是否存在语法错误。
  2. 类方法不能枚举,当对一个对象调用for..in时不会出现类方法。
  3. 类自动采用"use strict"模式。

类表达式

与方法一样,类也可以在另一个表达式中被定义、赋值、传递、返回等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类名Hello将将仅在类内部可见
let hello = class Hello {
name = ''
greet() {
console.log('Hello!');
}
}

// 返回一个类
function returnClass() {
return class {
hello() {
console.log('Hello!');
}
}
}

类继承

之前说JavaScript中类的底层是基于构造器和原型(链),类的继承就是通过原型链来实现的。

还是那句话,跟Java很像,通过extends关键字可以实现继承,子类的构造方法也必须先调用父类super的构造方法。

一个特殊情况是箭头函数,箭头函数不仅没有this,也没有super,如果在箭头函数中访问thissuper,它会从外部函数获取,所以能够融入到就近的上下文中。

在Java中我们经常继承标准库中的一些类,比如继承Thread来实现线程,在Android开发中也需要继承Activity来编写我们自己的逻辑,而JavaScript也不例外,它的内建类也可以被继承,但是需要注意的一点是内建类相互间不继承静态方法,因为继承关系并不存在于这些类之间,而是存在于它们的prototype之间,注意是prototype而不是[[Prototype]]

flowchart
newDate["new Date()"]

subgraph 结构
direction BT

subgraph 原型链
direction BT
newDate -->|"[[Prototype]]"| Date.prototype
Date.prototype -->|"[[Prototype]]"| Object.prototype
end

subgraph 类
direction BT
Date ---|"×"| Object
end

end

Date --> Date.prototype
Object --> Object.prototype

静态属性&静态方法

在属性或方法前添加static关键字,对Java用户来说无需多言。

静态属性和方法也能够被继承。

怎么实现private属性和方法

在JavaScript中没有像publicprivate这样的关键字,开发者之间存在一个约定,私有的属性或方法的命名通常以下划线_开头。

不过在新标准中,如果属性和方法的名字以井号#开头,它们将只能够在类内部可以被访问,这是语言级别的特性。

类检查 instanceof

1
<child> instanceof <parent>

如果child属于parent及其子类则返回trueinstanceof在检查中会将原型链考虑在内。

再说一个方法:

1
<objA>.isPrototypeOf(<objB>);

如果objAobjB的原型链中则返回true

所以如果把上述instranceof的例子改为isPrototypeOf方法,我们需要判断parent是否在child的原型链中,即判断parent是否为child.__proto__.__proto__.__proto__...中某一个父类的对象。

我们都知道typeof关键字可以判断一个对象的类型,而日常开发中看似不起眼的toString方法事实上可以比typeofistanceof更加强大,我们可以通过方法借用func.call来实现这一点。

1
2
// 我们让一个数组借用Object的toString方法
console.log(Object.prototype.toString.call([]));

可以看到打印了"[object Array]"

所以说通过toString方法也可以检查对象的类型。


JavaScript海底两万里:类
https://skycurtain.github.io/2022/09/06/javascript-drowning-in-class/
作者
Skycurtain
发布于
2022年9月6日
许可协议