zl程序教程

您现在的位置是:首页 >  后端

当前栏目

随笔-ES6 class语法糖 及继承性实现

ES6 实现 语法 Class 随笔
2023-09-14 09:13:41 时间

 ES5构造函数的缺点

在ES5时,JS还没有“类”的概念,创建对象使用的都是构造函数,而构造函数和普通函数没有什么本质区别,只是在书写上要求构造函数的函数名首字母大写,方便和普通函数区分。即ES5构造函数不仅可以new调用,还可以当成普通函数调用。

如果我们要限制ES5构造函数不能当成普通函数调用,则需要做额外处理

function Person(name){
    if(this instanceof Person) {
        this.name = name
    } else {
        throw new Error('必须使用new调用')
    }
}

其次,ES5构造函数中不适合设置被所有实例共享的属性和方法

注意下面的say方法,eyenum属性对所有Person实例的逻辑都是相同,即不需要在new Person时传入say函数,所以如果将say,eyenum设置到this上,会导致每次new都会给实例创建一个新的say方法和eyenum属性,尽管逻辑相同

function Person(name){
    this.name = name
    this.say = function(){
        console.log('my name is', this.name)
    }
    this.eyenum = 2
}

const p1 = new Person('张三')
const p2 = new Person('李四')

console.log(p1.say === p2.say) // false

解决方案是,将共享属性和方法定义到构造函数原型上,因为存在以下关系

Person.prototype === p1.__proto__

当p1自身找不到方法时,会沿着原型链去p1.__proto__上找

function Person(name){
    this.name = name
}

Person.prototype.say = function(){
    console.log('my name is', this.name)
}
Person.prototype.eyenum = 2

const p1 = new Person('张三')
const p2 = new Person('李四')

console.log(p1.say === p2.say) // true

这样改的好处是节省内存,坏处是破坏了面向对象的类的结构

另外对于静态属性和静态方法的定义,也会被定义到构造函数外部,体验破外了类的结构

function Person(name){
    this.name = name
}

Person.prototype.say = function(){
    console.log('my name is', this.name)
}
Person.prototype.eyenum = 2

Person.run = function(){
    console.log('running')
}

Person.cate = '灵长类'

整体下来感觉很松散。

ES6 类

ES6 class其实并没有改变JS类的模型,而只是对于ES5构造函数的一种封装处理,算是一种语法糖

首先可以看出class Person中Person依旧是一个函数,所以只是关键字从function变为了class,另外也不需要带函数的参数列表了,那么它是否可以像函数一样定义成函数表达式形式呢?

答案是可以的

那么ES6的class可以当成函数调用吗?

答案是不可以,只能通过new调用 ,这是对ES5构造函数的缺陷弥补

ES6 class内部默认有一个空参constructor函数,当我们new Person时会调用默认的空参constructor函数,如果我们额外指定了constructor函数,则class不再提供默认的空参构造函数。

在constructor中我们可以指定被创建的实例对象的私有属性和方法

和ES5构造函数中定义this.name = name没有什么区别,可以说一模一样

ES6 class定义共享方法时,需要在内部定义成简写方法形式,最终会被定义到类(函数)的原型上

这里在实现结果上和ES5构造函数定义共享方法是一致的,但是有一点差别,那就是ES6 class定义到原型上的方法或属性默认是不可遍历的,即属性描述符enumerable:false

 而使用ES5构造函数时,直接定义在构造函数原型上的共享方法的enumerable是true

 这其实算是ES6 class的一种改进措施,因为我们不希望在for...in遍历实例对象属性时,将原型上的一些属性也遍历出来,for...in只会遍历enumerable为true的属性。

ES6 class定义共享属性时

有两个特点:
1、共享属性并没有定义到原型上,而是定义到了每个实例对象上

2、我们需要注意class中定义共享方法和共享属性不能带function,var,let,const等声明指令

这里ES6 class为什么要将共享属性定义从原型对象挪到实例对象上呢?挪窝后,这还是共享属性吗?答案,不是共享的了,而是每个实例私有的属性。

为什么要这样搞呢?那我们怎么定义共享属性呢?

其实在面向对象设计比较成熟的一些语言中,如Java,Java语言中,类的所有实例能共享的属性其实就是类上的属性,即静态属性。 Java语言底层做了优化,即可以通过实例对象直接访问定义在类上的静态属性,虽然底层实现依旧是 类.静态属性

所以JS这里其实有了两种方式实现共享属性

1、将属性定义到原型上:可以直接通过对象访问

2、将属性定义为类的静态属性:只能通过类.静态属性访问

我觉得ES6是想让我们使用2,而不是1,但是实际使用上2的体验并不好,因为无法通过实例对象直接访问到共享属性,很不方便

但是Java苦恼的却是为啥实例对象可以访问到类上的静态属性......所以说没有最完美的语言

 所以这里大家就不要在class中想定义被所有实例共享的属性了,如果非要搞得话,可以去类的原型上搞

剩下的就是ES6 class中定义静态属性和方法了,即定义类(构造函数)本身的属性和方法

我们通常只在类(构造函数)上的 方法,当作工具方法,一来调用简单,直接Person.run(),而来节省内存,内存中只存在一份

除了以上语法上的不同,ES6 class 和ES5 构造函数还有以下几点不同,我们做一个总结:

ES6 class和ES5 构造函数区别总结

1、ES6 class虽然本质也是函数,但是不会发送声明提升,即声明class前,无法使用class

         

      ES5 构造函数本质就是普通函数,会发送声明提升,即声明前,可以使用它

        

2、ES6 class 内部默认开启严格模式,而ES5构造函数内部默认非严格模式

  如严格模式下,不允许访问函数的arguments上的callee

 非严格模式下,可以访问函数的arguments上的callee

3、ES6 class定义在原型上的共享方法是不可遍历的,ES5 构造函数定义在原型上的方法是可遍历的

4、ES6 class只能new调用,ES5 构造函数既可以new调用,也可以函数调用

ES5 构造函数实现继承 最好的方式(寄生组合式继承)

function Sup(name){
    this.name = name
}

Sup.prototype.say = function(){
    console.log('my name is', this.name)
}

function Sub(name,age) {
    Sup.call(this,name)
    this.age = age
}

Sub.prototype = Object.create(Sup.prototype, {
    constructor: {
        value: Sub,
        configurable: true,
        writeable: true,
        enumerable: false
    }
})

Sub.prototype.intro = function(){
    console.log('I am', this.age, 'years old')
}

const lisi = new Sub('lisi', 18)

lisi.say() // my name is lisi
lisi.intro() // I am 18 years old

 ES6 class实现继承extends

class Sup{
    constructor(name){
        this.name = name
    }

    say() {
        console.log('my name is', this.name)
    }
}

class Sub extends Sup{
    constructor(name,age) {
        super(name)
        this.age = age
    }

    intro(){
        console.log('I am', this.age, 'years old')
    }
}

const lisi = new Sub('lisi', 18)

lisi.say() // my name is lisi
lisi.intro() // I am 18 years old

可以发现ES6 class extends继承底层实现本质就是 ES5构造函数的寄生组合式继承