【JavaScript】11. 构造函数类与class类

472 阅读4分钟

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

类与继承

  • ES6之前:使用构造函数模拟类
  • ES6之后:使用class语法糖

(1)构造函数

用构造函数创建类

  • 思路:把对象中一些公共的属性和方法抽取出来,然后封裝到这个函数里面
    • 然后通过构造函数+new创建不同的对象
// 创建一个Person类
function Person(name, age) {
    this.name = name
    this.age = age
}
// 这里实例化对象
let person1 = new Person("xx", 10);
let person2 = new Person("yy", 20);

  • 类的共享的属性和方法要绑定在构造函数的原型上

    Person.prototype.say = function(){
        console.log('I can say')
    }
    Person.prototype.run = function() {
        console.log('I can run')
    }
    // 这样写比较繁琐,因此,可以作如下改进
    Person.prototype = {
        // 注意:需要让constructor 重新指回构造函数,
        // 因为这里我们是重写了 prototype,会覆盖原来的属性+方法
        constructor: Person,
        // 将方法作为对象原型的一个属性
        say: function(){
            console.log('I can say')
        },
        // 或者直接写方法
        run(){
            console.log('I can run')
        }
    }
    
  • 不过,对于原型对象的构造函数的重新指向,它应该是不可枚举

    • 因此,在这里的原型对象的重新指向,应该使用Object.defineProperty的方式
    Object.defineProperty(Person.prototype, "constructor", {    
        configurable: false,    
        enumerable: false,    
        writable: true,    
        value: Person
    })
    
    

类规范:

  • 类名首字母大写
  • 原型上的私有方法,默认以下划线开始

属性继承:

  • 通过call改变this指向的方式,实现子类继承父类的属性
function Father(arg1, arg2){    
    this.arg1 = arg1
    this.arg2 = arg2
}

function Son(arg1, arg2){
    // 为了让子构造函数继承自父构造函数,利用call()
    // 调用父构造函数,传入参数
    // 这里的 this是改变 父构造函数的 this指向,让它指向子构造函数
    // 这样 父构造函数的 this.uname就等于 Son.uname 了
    // 即,让子构造函数继承了父构造函数的 属性
    // 对于属性的继承    
    Father.call(this, arg1, arg2)
}

方法继承

  • 方法和属性是不同的继承,因为方法是复杂数据类型

  • 需要在原型上操作

  • 直接将父类的原型赋值给子类,同时注意改变原型的构造函数的指向

Son.prototype = Father.prototype

Son.prototype.constructor = Son

  • 但是,对象的赋值只是浅拷贝,修改子类的原型对象,也会修改父类的原型对象

  • 因此,可以将子类的原型对象指向父类的一个实例对象

    • 按照原型链的理论,实例对象也拥有Father原型对象上的方法,并且Father的实例对象和原型对象不是同一个对象

    • 这时,因为 Son的原型对象指向的是实例对象,因此Son也可以获得 Father的方法,并且修改Son的原型对象不会影响Father的原型对象,因为修改的并不是同一个对象

    • 最后,还是需要手动让Son指回原来的构造函数

Son.prototype = new Father()

Son.prototype.constructor = Son

  • 也可以使用Object方法

    Son.prototype = Object.create(Father.prototype);
    
    Son.prototype.constructor = Son;
    
    
    // 或者
    Son.prototype = Object.create(Father.prototype, {
        constructor: {
            value: Son,
            enumerable: false,
            writable: false,
            configurable: false
        }
    })
    
    

(2)class

使用class语法糖构建类

  • class类不存在变量提升
  • 只能通过new操作符调用
// 创建一个Person类
class Person {
    constructor(name, age){
        this.name = name
        this.age = age
    }
	// 共有方法
    say(){
        console.log('I can say')
    }
    run(){
        console.log('I can run')
    }
}

// 创建一个实例化对象,必须使用 new 关键词
let person1 = new Person("xx", 10);

关于类的构造函数constructor

  • 直接写函数名,不需要写 function
  • 接收传递过来的参数,返回一个实例对象
  • 使用 new 生成实例对象时,会自动调用这个构造函数
  • 必须有这个构造函数,如果没有,那么类也会自动生成

继承

  • 通过extends关键字声明继承
    • extends可以继承任何类型的表达式,包括原生对象
    • 只要该表达式最终返回的是一个可继承的函数,即具有[[constructor]]
  • 通过super在子类调用父类的构造函数
    • 非必须,如果在子类缺省了构造函数constructor,则会默认调用super函数,并传入所有参数
class Father {
    constructor(arg1, arg2){
        
    }
}

// 使用 extends 关键字,表示继承自父类
class Son extends Father {
    constructor(arg1, arg2, arg3){
        // 必须先调用父类的构造方法,再使用子类的构造方法
        super(arg1, arg2) // 相当于调用了父类的构造函数来赋值,这样才可以使用父类的方法
        this.arg3 = arg3 // 如果子类有自己的属性,则应该在super之后
    }
}

关于继承中的方法

  1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
  2. 继承中,如果子类里面没有就去查找父类有没有这个方法,如果有,就执行父类的这个方法(遵循就近原则)
  3. 但可以通过super主动调用父类里面的方法:super.方法()

本人前端小菜鸡,如有不对请谅解