点击上方“ 前端印象 ”,选择“ 设为星标 ” 第一时间关注技术干货!
ES6中出现
class
语法,只是创建构造函数的一种语法糖,那为何要新增一种语法去实现同一个功能呢?其实目的还是为了跟上一些主流编程语言的脚步,例如
java
、
C++
、
Python
,他们内部都是用
class
语法来实现的面向对象编程,所以咱们的
JavaScript
也不能落后,不然很多学习过
java c++ python
的小伙伴跑来学习
js
时,就很难理解构造函数这一概念了。
注意:因为
class
语法涉及到大量的JavaScript中对象的概念,所以如果还没有了解过对象的小伙伴可以查看我之前写的几篇剖析对象概念的文章,下面放上文章链接,点击即可跳转
- 充分了解JavaScript中【对象】的概念(一)
- 充分了解JavaScript中【对象】的概念(二)
- 充分了解JavaScript中【对象】的概念(三)
好了话不多说,我们开始讲解
class
吧
一、构造函数
在学习
class
之前,我们先来回顾在ES6之前,创建一个实例对象是通过构造函数来实现的
我们通过
new
关键字调用构造函数,即可生成一个实例对象。不妨我们再来回顾一下
new
关键字的作用过程,即
var person = new Person('Jack', 18)
等价于以下代码
通过以上代码我们可以得知,构造函数中的
this
指向的是新生成的实例对象,下文会讲到,在
class
语法中,
this
在不同情况下会有不同的含义
二、class的语法
(1)体验class语法
接下来,我们来看看
class
语法引入以后,创建实例对象有何变化,这里我们就直接改写上述例子了,方便大家进行比较
通过调用实例对象的属性
name
、
age
以及方法
show
,我们可以看到,跟构造函数没有任何的区别,所以说
class
语法就是构造函数的一个语法糖,即构造函数的另一种写法,这两者并无本质区别
其实我们还可以通过
typeof
来验证一下
class
定义的类的类型
(2)constructor
当我们用
class
定义了一个类,然后用关键字
new
调用该类,则会自动调用该类中的
constructor
函数,最后生成一个实例对象。
constructor
函数内部的
this
指向的也是新生成的实例对象。
如果要生成一个不需要任何属性的实例对象,则我们不需要在
constructor
函数里写任何代码,此时可以省略它,例如
上述代码省略了
constructor
函数,此时JavaScript会默认生成一个空的
constructor
函数,例如
以上两段代码是等价的
也正是因为
constructor
函数的存在,
class
定义的类必须通过
new
来创建实例对象,否则就会报错
而传统的构造函数就可以不通过
new
来调用,因为其本身就是一个函数,若不加关键字
new
,则相当于直接执行该函数
(3)类方法的定义
在传统的构造函数中,为了使每个实例对象都拥有共同的方法,在构造函数的原型上进行方法的定义,例如
因此,
class
语法定义的方法也是在原型上的,不过这里称之为类的原型上,同时省略了大量的代码,直接将方法写在
class
内即可
细心的小伙伴肯定发现了,虽然方法都是写在
{}
内的,但是每个方法之间无需用
,
隔开,否则就会报错,这个一定要注意一下
其实以上定义类方法的代码等价于以下代码
这其实跟为构造函数定义方法一样,但是整体看上去代码量就非常得大
虽说构造函和类两者定义的方法都是定义在其原型上的,但还是有略微的区别,即前者定义的方法具有 可枚举性;而后者定义的方法具有 不可枚举性。
为了验证两者区别,我们要用到ES5中提供的两个新方法
- Object.keys(): 会返回一个数组,数组中的元素就是对象中可枚举的自有属性名
- Object.getOwnPropertyNames(): 返回一个数组,数组中的元素是对象中所有自有属性的名称,不管属性是否具有可枚举性都能被返回。
首先我们来验证一下构造函数定义的方法的枚举性
我们可以看到,
Object.keys()
方法返回
[ 'show', 'hide' ]
,证明这定义的两个方法是自有属性且是可枚举的;
Object.getOwnPropertyNames()
方法返回
[ 'constructor', 'show', 'hide' ]
,说明构造函数内有一个自有属性方法
constructor
,且不可枚举。
接下来我们再来看一下
class
定义的类中定义的方法的枚举性
我们看到
Object.keys()
返回
[]
,说明
class
类定义的方法具有不可枚举性;
Object.getOwnPropertyNames()
方法返回
[ 'constructor', 'show', 'hide' ]
,可以看到同样也具有一个不可枚举的自有属性
constructor
方法。
(4)get函数和set函数
在
class
类中,可以使用两个内部定义的函数,即
get
和
set
,语法为
get/set 属性名() {}
,分别表示读取属性/设置属性时调用此函数,其中
set
函数接收一个参数,表示所设置的值
我们来看个例子
当我们访问属性
number
时,会调用
get number() {}
函数,故返回
18
;当设置属性
number
的值为
20
时,会调用
set number() {}
函数,故打印了
现在的number值为:20
表面上看,
get
和
set
函数是方法,但其实并不是,我们可以用
Object.getOwnPropertyNames()
方法来验证一下
我们可以看到,返回的数组中只有
class
类自带的
constructor
函数和
number
属性,并没有看到
get
和
set
函数。
了解ES5中对象概念的小伙伴应该知道,对象中有两个存储器属性,分别为
getter
和
setter
,它们是对象中某个属性的特性,并且可以通过
Object.getOwnPropertyDescriptor()
方法获得对象中某个属性的属性描述符
因此,我们在
class
类中写的
get
和
set
函数只是设置了某个属性的属性特性,而不是该类的方法。
(5) 静态方法
在
class
类中的方法都是写在原型上的,因此生成的实例对象可以直接调用。现在有一个关键字
static
,若写在方法的前面,则表示此方法不会被写在原型上,而只作为该类的一个方法,这样的方法叫做静态方法;相反,若没加关键字
static
的方法就叫做非静态方法
我们来看一下具体的例子
我们分析一下这个例子:
首先我们直接调用
Person
类的
show
方法,实际调用的就是有关键字
static
的
show
方法;
然后我们生成了一个实例对象
person
,然后调用
person
实例对象上的
show
方法,实际调用的就是没有关键字
static
的
show
方法,从这我们可以看出,静态方法和非静态方法可以重名;
最后我们调用了
person
实例对象上的
hide
方法,但报错了,因为在
class
类中,我们定义的是静态方法,即有关键字
static
的
hide
方法,也就是此方法没有被写进类的原型中,因而实例对象
person
无法调用此方法。
我们都知道,类中定义的方法内的
this
指向的是实例对象,但在静态方法中的
this
指向的是类对象
我们来看一个例子
我们来分析一下这段代码:
首先我们直接调用
Person
类的静态方法
cite
,执行代码
this.show()
,因为静态方法中的
this
指向
Person
类,所以其实调用的就是静态方法
show
,所以打印了
我是非静态方法show
然后我们生成了一个实例对象
person
,调用
person
的
show
方法,因为在非静态方法
show
中,
this
指向的是实例对象
person
,因此打印了
Lpyexplore
(6)实例属性的简易写法
原先我们为实例对象定义的属性都是写在
constructor
函数中的,例如
现在我们用实例对象的属性新写法来改写以上代码
这种写法就是将
constructor
函数中的属性定义放到了外部,同时不需要写
this
,因为此时的属性定义与其他方法也处于同一个层级。因此这样的写法看上去就会比较一目了然,一眼就能看到实例对象有几个属性有几个方法。
虽然这样的写法比较简便,但也有一定的缺点,那就是用这种写法定义的属性是写死的。
我们都知道在生成实例对象时,可以传入参数,传入的参数会作为
constructor
函数的参数,所以我们在
constructor
函数中定义的属性的值就可以动态地根据参数改变而改变。
而实例属性的简易写法就无法根据参数的改变而改变,所以用这种写法的时候需要稍微注意一下。
(7)静态属性
既然有静态方法,那怎么能少了静态属性呢?其实,原本的
class
类中是没有静态属性这个概念的,后来才加上的。静态属性就只属于
class
类的属性,而不会被实例对象访问到的属性。
同样的,静态属性的申明就是在属性的前面加关键字
static
。上面我们刚讲到,实例对象的属性的定义可以不写在
constructor
函数中,而是直接写在外部,此时我们可以暂且称之为非静态属性
这段代码中,定义了非静态属性
name
、静态属性
name
和 静态属性
age
。
因此我们在访问
Person
类的
name
属性时,访问的是静态属性
name
,即加了关键字
static
的
name
属性;
生成实例对象
person
,访问其
name
属性,实际访问的就是非静态属性
name
,即没有加关键字
static
的
name
属性;
最后我们访问实例对象
person
的
age
属性,返回了
undefined
。因为
age
是静态属性,是属于
Person
类的,而不会被实例对象
person
访问到。
三、class的继承
继承是面向对象编程中一个非常重要的概念,那什么是继承呢?
(1)继承的概念
继承就是使一个类获得另一个类的属性和方法。就好比一个手艺精湛的师傅传授给你他所有的毕生绝学,那么就相当于你继承了他,此时你既学会了你师傅教你的技能,同时你也一定有属于自己的技能,这不是从你师傅那学来的。
(2)ES5中实现继承
其实在ES5中是通过修改原型链实现继承的,我们可以来看一下简单的例子
我们可以看到,我们通过改变构造函数
Child
的原型
prototype
为构造函数
Parent
生成的实例对象,实现了继承,即通过构造函数
Child
生成的实例对象具有
Parent
中定义的属性
name1
和方法
show1
,同时也具有属于自己的属性
name2
和方法
show2
(3)ES6中class实现继承
ES5中实现继承的写法显然有些麻烦,所以在
class
类中,我们可以通过关键字
extends
来实现继承
我们来改写一下ES5中的继承实现
继承得实现整体上看上去非常得简洁
在上述代码中,我们看到了,我们在定义
Child
类时用到了关键字
extends
,申明了
Child
类继承
Parent
类,同时在
Child
类得
constructor
函数中调用了
super
函数。仅仅用两个关键字就实现了继承,这里我们要对
super
进行详细得讲解
(4)super
在ES6中规定了,在子类继承了父类以后,必须先在子类的
constructor
函数中调用
super
函数,其表示的就是父级的
constructor
函数,作用就是为子类生成
this
对象,将父类的属性和方法赋值到子类的
this
上。因此,若没有调用
super
函数,则子类无法获取到
this
对象,紧接着就会报错
上述代码中,
B
类继承
A
类,但
B
类的
constructor
函数中没有调用
super
函数,因此没有生成
this
对象,所以
this.name2 = 'B'
就报错了
若子类省略了
constructor
函数,则默认会帮你调用
super
函数的
super()
代表的是父类的构造函数,其实
super
还可以作为对象使用,即不作为函数调用。当
super
在子类的普通方法内时,指向的是父类的原型对象;在子类的静态方法内时,指向的时父类
上述代码,
B
类继承
A
类,其中
A
类有一个
show1
方法,是写在其原型
A.prototype
上的,而在
B
类的
show2
方法中调用了
super.show1()
,我们说过
super
在普通的方法中指向的是父类的原型对象,所以
super.show1()
相当于
A.prototype.show1()
我们再来看一个
super
在子类的静态方法中的例子
上述代码,
B
类继承
A
类,
B
类在其静态方法
hide2
中调用了
super.hide1()
,因为
super
在静态方法中指向的是父类,所以
super.hide1()
就相当于
A.hide1()
说到静态方法,其实类的继承,也是可以继承静态方法的
还需要注意的是,当我们在子类的普通方法中通过
super
调用父类的方法时,方法中的
this
指向的是当前子类的实例对象
那么,当我们在子类的静态方法中通过
super
调用父类的方法时,方法中的
this
指向的是子类,而不是子类的实例对象
上述代码中,我们在
B
类的静态方法
show2
中通过
super
调用了
A
类的静态方法
show1
,执行代码
console.log(this.x)
,此时的
this
指向的是
B
类,但因为
B
类的
constructor
函数中定义的属性
x
是定义在
B
类的实例对象上的,所以
this.x
返回的是
undefined
。
所以我们在
B
类上定义一个属性
x
并且值为
3
,此时再此调用
B.show2()
,返回的就是
3
了。
(5)prototype和__proto__
在
class
类中有两个属性,分别表示着一条继承链,即
prototype
和
__proto__
子类的
__proto__
总是指向父类;子类的
prototype
的
__proto__
总是指向父类的原型
我们来验证一下
四、class类的补充
对于
class
类还有几点需要补充以下
(1)不存在变量提升
构造函数本身就是个函数,存在变量提升,所以通过构造函数生成实例对象时,可以将构造函数写在生成实例对象的代码后面
虽然
class
类的数据类型也属于
function
,但是它是不存在变量提升的,即不可以在申明类之前生成实例对象,否则就会报错
(2)new.target
class
类必须通过
new
来生成实例对象,因此ES6引入了一个新的属性
new.target
,该属性一般用于
constructor
函数中,表示通过关键字
new
作用的构造函数的名称,若不是通过
new
命令调用的,则返回
undefined
当子类继承父类,并调用父类的
constructor
函数时,
new.target
返回的是子类的构造函数名以及继承自哪个父类
五、结束语
好了,ES6的
class
语法就讲到这里,希望这篇文章能帮助到大家。
👇👇👇 欢迎留言讨论 👇👇👇 推荐阅读
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5CZ2MjYllTNkdDZ0U2M1MGMhNzNjRWY5YDMjRTO3Y2Ml9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
【JS面试题】用四种方式实现数组扁平化你会吗
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5CZ2MjYllTNkdDZ0U2M1MGMhNzNjRWY5YDMjRTO3Y2Ml9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
【JS面试题】数组去重(6种方法)震惊面试官
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5CZ2MjYllTNkdDZ0U2M1MGMhNzNjRWY5YDMjRTO3Y2Ml9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
【超高频面试题】这两段代码的返回结果你知道是什么吗?
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5CZ2MjYllTNkdDZ0U2M1MGMhNzNjRWY5YDMjRTO3Y2Ml9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
Vue.js巧妙运用修饰符,帮你后期维护代码省下大量的时间
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5CZ2MjYllTNkdDZ0U2M1MGMhNzNjRWY5YDMjRTO3Y2Ml9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
建议收藏 | 最全的JavaScript常见的操作数组的函数方法宝典
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5CZ2MjYllTNkdDZ0U2M1MGMhNzNjRWY5YDMjRTO3Y2Ml9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
【面试题】如此高效的斐波那契数列你见过吗?
END
❤支持三连
1.看到这里了就点个在看支持下吧,你的「在看」是我创作的动力。
2.关注公众号
前端印象
,「一起交流进步」!
3.关注公众号回复【加群】,拉你进技术交流群一起玩转前端。