JavaScript海底两万里:类
争取一文帮自己搞清楚JavaScript中的类class
。
在ES6之前,JavaScript中并不存在类的概念,开发者们对于原型和继承可谓是绞尽脑汁,通过各种方式来实现基于原型链的继承。
日常开发中我们经常需要创建许多相同类型的对象,在学习了构造器之后,我们可以想到通过new Function()
的形式来实现这种需求。不过在现代JavaScript中引入了更高级的“类”的概念,它包含许多适用于面向对象的新功能。
我作为一个半吊子Java开发者,一直在与面向对象编程打交道,而连名字都蹭Java热度的JavaScript,在面向对象这块的设计跟Java不能说一模一样,只能说是大差不差,除了没有显示的public
和private
字段之外,别的方面基本都是相通的。
class
1 |
|
这是声明一个类的例子,可以看到JavaScript采用显示的constructor
关键字来声明构造方法,而不是像Java那样用与类名相同的方法。
在JavaScript中,类的本质是函数,所以它不完全是语法层面的特性。JavaScript的类的底层实现事实上是构造器constructor
和原型(链)[[Prototype]]
。当我们new
一个对象时,JavaScript会通过以类名命名的构造器来创建对象。
但这并不意味着class
就仅仅是一个语法糖,它们之间还是存在一定差异的:
- 通过
class
创建的函数内部包含[[IsClassConstructor]]: true
属性标志,解释器会检查这个属性来判断是否存在语法错误。 - 类方法不能枚举,当对一个对象调用
for..in
时不会出现类方法。 - 类自动采用
"use strict"
模式。
类表达式
与方法一样,类也可以在另一个表达式中被定义、赋值、传递、返回等。
1 |
|
类继承
之前说JavaScript中类的底层是基于构造器和原型(链),类的继承就是通过原型链来实现的。
还是那句话,跟Java很像,通过extends
关键字可以实现继承,子类的构造方法也必须先调用父类super
的构造方法。
一个特殊情况是箭头函数,箭头函数不仅没有
this
,也没有super
,如果在箭头函数中访问this
或super
,它会从外部函数获取,所以能够融入到就近的上下文中。
在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中没有像public
和private
这样的关键字,开发者之间存在一个约定,私有的属性或方法的命名通常以下划线_
开头。
不过在新标准中,如果属性和方法的名字以井号#
开头,它们将只能够在类内部可以被访问,这是语言级别的特性。
类检查 instanceof
1 |
|
如果child
属于parent
及其子类则返回true
。instanceof
在检查中会将原型链考虑在内。
再说一个方法:
1 |
|
如果objA
在objB
的原型链中则返回true
。
所以如果把上述instranceof
的例子改为isPrototypeOf
方法,我们需要判断parent
是否在child
的原型链中,即判断parent
是否为child.__proto__.__proto__.__proto__...
中某一个父类的对象。
我们都知道typeof
关键字可以判断一个对象的类型,而日常开发中看似不起眼的toString
方法事实上可以比typeof
和istanceof
更加强大,我们可以通过方法借用func.call
来实现这一点。
1 |
|
可以看到打印了"[object Array]"
。
所以说通过toString
方法也可以检查对象的类型。