JavaScript夯实基础系列(五):类
JavaScript中没有类,是通过使用构造函数和原型模式的组合来实现类似其它面向对象编程语言中“类”的功能。ES6引入的关键字class,形式上向其它面向对象编程语言靠拢,其实质只是一个语法糖,绝大部分功能ES5都可以实现。
一、class
在ES6之前,创建自定义对象最常用的方式是组合使用构造函数和原型模式。而ES6通过class关键字在形式上加以规范。
// ES6之前的写法
function OldStudent (name,age) {
this.name = name
this.age = age
}
OldStudent.prototype.sayMessage = function () {
console.log(`我叫${this.name},今年${this.age}岁。`)
}
// ES6写法
class NewStudent{
constructor (name, age) {
this.name = name
this.age = age
}
sayMessage() {
console.log(`我叫${this.name},今年${this.age}岁。`)
}
}
let LiLei = new OldStudent('LiLei',20)
LiLei.sayMessage() // 我叫LiLei,今年20岁。
let HanMeiMei = new NewStudent('HanMeiMei',18)
HanMeiMei.sayMessage() // 我叫HanMeiMei,今年18岁。
console.log(typeof NewStudent) // function
由上代码可以看出,通过class关键字定义的代码块也是函数。比较特殊的是通过class定义的函数只能通过new关键字来调用,并且不存在变量提升,只能先定义后使用。另外,class定义的函数内部模式是严格模式。
class中的constructor方法相当于ES5中的构造函数,在实例化对象时必须调用constructor方法,如果没有显式定义,引擎也会自动添加一个空的constructor方法。constructor方法和构造函数生成对象的规则一样:创建一个新对象,作用方法的执行上下文,执行方法中的代码;如果没有明确return一个对象,则返回新创建的对象。
class中除了定义constructor方法之外的方法是直接定义在构造函数的原型对象上的。如果该方法前面加了static关键字,则该方法为静态方法,直接定义在构造函数上而不是原型对象上。ES6明确规定class中可以定义静态方法,无法在其中定义静态变量。想要添加静态变量,可以在class之外直接向函数上添加。如下代码所示:
// sayHello为静态方法
class NewStudent{
static sayHello() {
console.log('hello')
}
}
// 添加静态属性
NewStudent.type = 'student'
NewStudent.sayHello() // hello
console.log(NewStudent.type) // student
注意:通过class关键字向原型对象上只能添加方法而不能添加属性。也许是为了防止在原型对象上添加对象的情况,这样会导致在继承的时候子对象可以修改原型链上对象的数据。但是这样有些矫枉过正了,JavaScript的很大优势在于灵活性,class关键字定义类,外形规范的同时降低了灵活性。
为了实现数据共享,可以在通过class定义过类之后,再向原型对象中添加属性。或者在class中定义getter方法,这样的话就不能在子对象上直接添加同名属性,要通过Object.defineProperty()方法来定义子对象上的同名属性。如下代码所示:
class Person{
get age () {
return 18
}
}
class Student extends Person{
constructor (){
super()
}
}
let LiLei = new Student()
console.log(LiLei.age) // 18
LiLei.age = 20 // 非严格模式下赋值失败,严格模式下报错
console.log(LiLei.age) // 18
// 通过Object.defineProperty()方法修改
Object.defineProperty(LiLei, 'age', {
value: 20
})
console.log(LiLei.age) // 20
由上代码所示,ES6的原型属性添加很麻烦,如果想要往原型对象上添加属性,直接往类的prototype属性赋值最为简单。
二、extends
实现引用类型继承的最佳方式是寄生组合式继承,ES6通过extends关键字来使这种继承方式形式更加优雅,如下代码所示:
// ES6之前的继承
function PersonA (name) {
this.name = name
}
PersonA.prototype.sayName = function () {
console.log(this.name)
}
function StudentA (name,age) {
PersonA.call(this,name)
this.age = age
}
StudentA.prototype = Object.create(PersonA.prototype)
// ES6的继承
class PersonB{
constructor (name){
this.name = name
}
sayName () {
console.log(this.name)
}
}
class StudentB extends PersonB{
constructor (name, age) {
super(name)
this.age = age
}
}
let LiLei = new StudentA('LiLei',20)
LiLei.sayName() // LiLei
let HanMeiMei = new StudentB('HanMeiMei',18)
HanMeiMei.sayName() // HanMeiMei
子类的constructor中必须调用super方法,在执行super()之前子类不能使用this,这是因为ES6之前的寄生组合式继承是先创建子类的实例,然后通过call或者apply方法将该实例作为执行上下文执行一遍父类构造函数。而ES6正好相反,先创建父构造函数的实例对象,然后将实例对象作为this执行子构造函数。这就导致如果不调用super方法子类中就不能使用this。
ES6继承中先创建父类实例的特性还带来一个ES5无法实现的功能:继承原生构造函数。如下代码所示:
class MyArray extends Array{
constructor (...args) {
super(...args)
}
}
let number = new MyArray(1,2,3)
console.log(number.length) // 3
number.push(4)
console.log(number.length) // 4
通过extends子类同时能够继承父类的静态方法。如下代码所示:
class Person{
constructor (){}
static sayHello () {
console.log('hello')
}
}
class Student extends Person{
constructor () {
super()
}
}
Student.sayHello() // hello
super可以作为方法在子类构造函数中调用,也可以作为对象使用。当super作为对象时,在普通方法中指向父类的原型对象,在静态方法中,指向父类。另外,super在普通方法中指向的是父类的原型对象,无法通过super来访问父类实例中的属性和方法。如下代码所示:
class Person{
constructor (name){
this.name = name
}
static sayStatic () {
console.log('static')
}
}
class Student extends Person{
constructor (name) {
super(name)
}
static sayStatic () {
super.sayStatic()
}
sayName () {
console.log(super.name)
}
}
let LiLei = new Student('LiLei')
Student.sayStatic() // static
LiLei.sayName() // undefined
三、总结
ES6新增的关键字class、extends在形式上规范了JavaScript对类的模拟,使代码看起来更加优雅。而且能够通过extends来扩展内建的对象类型,比如Array或者RegExp。
class带来的也不全是好处,将.prototype隐藏起来,使人更加迷惑。实际上JavaScript是没有类的,通过原型链来实现继承更恰当的说法应该是委托。而且仅仅通过class关键定义构造函数和原型对象的混合体灵活性有所降低,比如:在原型对象上只能添加方法,无法添加属性。
如需转载,烦请注明出处:https://www.cnblogs.com/lidengfeng/p/9342084.html