这篇文章讲一下ES6新增的特性:class
。上一篇文章已经详细的讲了JavaScript中的原型机制,实现继承的两种主要方式,有了这些基础,这篇文章不用啰嗦很长了。我们先通过调试一个例子,看class
背后是如何运作的,然后用构造函数模拟实现一下class
。最后再盘点一下class
与构造函数的区别,避免日后在开发中踩坑。
class背后的机制和语法
先准备个🌰:
1 | class User { |
在chrome中打断点调试,展开User
查看:
嗯,可以看到,class
和构造函数非常像了。红色箭头表示了User
本身的原型链如下,User
也是继承于Function
。所以class
声明的其实也是一个函数,是类构造函数。
1 | User ------> Function -------> Object |
class
中是支持声明类的静态属性和方法的。本例中isFemale
这个方法,我在声明它时在前面加上了static
关键字,于是它成了User
自身的一个方法,因此需要用类名去访问:User.isFemale()
。
再看上图中绿色框中的内容,定义在class
中的方法会放在prototype
中,比如sayHi()
,这样这些方法就成了类的实例对象的原型方法。
class
中支持getter/setter
语法,使得能正常用点语法访问和设置属性,但当属性被访问和赋值时可以拦截到。本例中的username
使用了getter/setter
语法,这导致在User.prototype
自动生成了一个username
属性,User
的实例对象就可以访问该属性了。
User.prototype.username
是由于get username()
生成的,我们声明一个getter
,就会在prototype
中自动生成一个对应的属性。
class
中支持使用extends
关键字继承其他类,使用super
调用父类中的属性和方法。VIPUser
继承自User
,先展开VIPUser.prototype
看一下:
可以看到,VIPUser.prototype
继承了User.prototype
,这样VIPUser
的实例对象即可访问VIPUser
中的方法也可以访问User
中的方法了。VIPUser
重写了sayHi()
方法,所以在VIPUser.prototype
中出现了该方法。
再展开VIPUser.__proto__
看一下,VIPUser
本身的原型链是怎样的:
VIPUser
也同时继承了User
,因此User
的静态方法也一起被VIPUser
继承,VIPUser.greetings()
可以正常访问。
1 | VIPUser -------> User ---------> Function ----------> Object |
最后看一下user
的展开的内容:
user
的原型链如下:
1 | user ------> VIPUser.prototype -------> User.prototype --------> Object |
User
和VIPUser
中声明的变量#username
和level
,最终成了实例对象user
自身的属性。
class
中支持声明私有属性,私有属性以#
开头。尝试调用user.#username
会报错。
上面的分析已经基本列出了要如何使用class
,下面再将class
语法总结一下:
1 | class MyClass extends OtherClass { // MyClass继承自OtherClass |
模拟实现class声明的类
接下来用构造函数模拟一下上面User
类和VIPUser
类的实现,以便加深对class
理解:
1 | const User = (function() { |
可以再打开Chrome打断点调试看看,跟上面class
声明的User
和VIPUser
的原型结构一样。
class和构造函数的区别
尽管class
和构造函数很像,但还是有一些区别:
class
声明的类,必须使用new
运算符调用,否则会抛错误。class
中定义的方法都不可枚举,类的prototype
中的所有方法,enumerable
标记的都为false
。上面模拟User
中的sayHi
方法,更准确地应该像下面这样创建,VIPUser
中的sayHi
方法也一样:
1 | Object.defineProperty(User.prototype, 'sayHi', { |
这样在用for...in
遍历对象属性时,拿到的就都是对象本身的属性了,不需要hasOwnProperty
去判断。对普通函数构造出来的对象,for...in
会一起输出原型属性和对象本身的属性。
class
声明的类,跟let
、const
一样,不能提升。重复定义会报错,未声明先使用会报错。即使定义在全局作用域中,也不会挂在全局对象上,所以上面我们看到,User
和VIPUser
并没有在window
上。class
中的方法默认使用严格模式。
其他
- 也可以用表达式定义一个类:
1 | const Test = class { |
extends
允许后接任何表达式:
1 | function f(phrase) { |
函数f
可以根据条件决定返回什么类,这样User
就不是继承于一个固定的类了。
参考资料
【1】 ES6语法中的class、extends与super的原理
本文作者:意林
本文链接:http://shinancao.cn/2019/09/12/JS-Class/
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!