这是我参与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之后
}
}
关于继承中的方法
- 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
- 继承中,如果子类里面没有就去查找父类有没有这个方法,如果有,就执行父类的这个方法(遵循就近原则)
- 但可以通过
super主动调用父类里面的方法:super.方法()
本人前端小菜鸡,如有不对请谅解