各位观众,各位朋友,大家好!我是今天的主讲人,很高兴能和大家一起聊聊JavaScript构造函数(constructor
)和super()
这对好基友,以及它们在继承中扮演的关键角色。这俩哥们,一个负责创建对象,一个负责“继承遗产”,可谓是强强联合,缺一不可。
咱们今天的内容,将深入浅出,保证大家听完之后,不仅能理解,还能熟练运用,从此告别继承中的各种“坑”。
一、构造函数:对象的“出生证明”
首先,咱们得搞清楚啥是构造函数。说白了,构造函数就是一个用来创建对象的函数。在JavaScript中,任何函数都可以当做构造函数来使用。但是,为了区分普通函数和构造函数,我们通常会约定俗成地使用大写字母开头来命名构造函数。
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log(`你好,我是${this.name},今年${this.age}岁。`);
};
}
// 使用new关键字调用构造函数,创建对象
const person1 = new Person("张三", 30);
const person2 = new Person("李四", 25);
person1.greet(); // 输出:你好,我是张三,今年30岁。
person2.greet(); // 输出:你好,我是李四,今年25岁。
上面这段代码,Person
就是一个构造函数。当我们使用new
关键字调用它时,会发生以下几件事:
- 创建一个新的空对象。 就好像医院里的新生儿,刚出生时啥也没有。
- 将
this
指向这个新对象。 这个新对象就是this
的老大了,this
说啥就是啥。 - 执行构造函数中的代码,给新对象添加属性和方法。 给新生儿穿衣服、喂奶,让它逐渐成长。
- 如果构造函数没有显式返回对象,则返回新对象。 孩子养大了,就交给父母了。
如果构造函数显式返回一个对象,那么new
操作符会忽略构造函数内部的this
指向和属性赋值,直接返回构造函数返回的对象。这在一些特殊情况下会用到,但一般情况下我们不推荐这么做,容易造成混乱。
二、super()
:继承的“接力棒”
继承是面向对象编程的重要特性,它允许我们创建一个新的类(子类),继承已有类(父类)的属性和方法,从而实现代码的复用。在JavaScript中,我们可以使用extends
关键字来实现继承。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log("动物叫!");
}
}
class Dog extends Animal {
constructor(name, breed) {
// 调用父类的构造函数
super(name);
this.breed = breed;
}
speak() {
console.log("汪汪汪!");
}
fetch() {
console.log("狗狗捡球!");
}
}
const dog1 = new Dog("旺财", "金毛");
dog1.speak(); // 输出:汪汪汪!
dog1.fetch(); // 输出:狗狗捡球!
console.log(dog1.name); // 输出:旺财
在这个例子中,Dog
类继承了Animal
类。Dog
类可以使用Animal
类的name
属性和speak
方法,并且还可以定义自己的breed
属性和fetch
方法。
重点来了,super()
是啥?super()
函数用于调用父类的构造函数。在子类的构造函数中,必须先调用super()
,才能使用this
关键字。 这就像接力赛跑,你得先接过上一棒,才能继续跑。
super()
的作用:
- 调用父类的构造函数,初始化父类的属性。 让子类拥有父类的“基因”。
- 在子类的构造函数中使用
this
之前,必须先调用super()
。 这是JavaScript的强制规定,否则会报错。
三、super()
的用法详解
super()
不仅仅是用来调用父类的构造函数,它还有其他用法:
super()
: 调用父类的构造函数,并传递参数。super.methodName()
: 调用父类的methodName
方法。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log("动物叫!");
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类的构造函数
this.breed = breed;
}
speak() {
// 调用父类的speak方法
super.speak();
console.log("汪汪汪!");
}
}
const dog1 = new Dog("旺财", "金毛");
dog1.speak(); // 输出:动物叫!n汪汪汪!
在这个例子中,Dog
类的speak
方法不仅输出了“汪汪汪!”,还调用了父类Animal
的speak
方法,输出了“动物叫!”。这体现了继承的特性:子类可以扩展父类的功能。
四、构造函数和super()
的“爱恨情仇”:初始化流程
现在,让我们来梳理一下继承中的初始化流程:
- 创建子类对象。 就像盖房子,先打地基。
- 调用子类的构造函数。 开始装修房子。
- 在子类的构造函数中,必须先调用
super()
。 接过父类的“遗产”,初始化父类的属性。 - 执行子类构造函数中的代码,初始化子类的属性。 继续装修,添加子类特有的东西。
- 返回子类对象。 房子装修好了,可以入住了。
如果子类没有定义构造函数,那么会自动创建一个默认的构造函数,并调用super()
。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log("动物叫!");
}
}
class Dog extends Animal {
// 没有定义构造函数,会自动创建
// constructor(...args) {
// super(...args);
// }
speak() {
console.log("汪汪汪!");
}
}
const dog1 = new Dog("旺财");
console.log(dog1.name); // 输出:旺财
五、常见错误及避坑指南
在使用构造函数和super()
时,有一些常见的错误需要避免:
- 忘记调用
super()
。 这是最常见的错误,会导致this
无法使用,程序崩溃。 - 在调用
super()
之前使用this
。 JavaScript会报错,告诉你“必须先调用super()
”。 super()
的参数传递错误。 需要根据父类构造函数的参数列表,正确传递参数。- 过度使用继承。 继承虽然强大,但也要适度使用,避免过度耦合,造成代码难以维护。
为了帮助大家更好地理解,我们用一张表格来总结一下:
概念 | 说明 | 示例 |
---|---|---|
构造函数 | 用于创建对象的函数,约定俗成使用大写字母开头命名。 | function Person(name, age) { this.name = name; this.age = age; } |
new 关键字 |
用于调用构造函数,创建对象。 | const person1 = new Person("张三", 30); |
extends 关键字 |
用于实现继承。 | class Dog extends Animal { ... } |
super() |
调用父类的构造函数。必须在子类构造函数中使用this 之前调用。 |
constructor(name, breed) { super(name); this.breed = breed; } |
super.methodName() |
调用父类的methodName 方法。 |
speak() { super.speak(); console.log("汪汪汪!"); } |
初始化流程 | 1. 创建子类对象。 2. 调用子类构造函数。 3. 调用super() 。 4. 初始化子类属性。 5. 返回子类对象。 |
详见上文描述 |
常见错误 | 1. 忘记调用super() 。 2. 在调用super() 之前使用this 。 3. super() 的参数传递错误。 4. 过度使用继承。 |
详见上文描述 |
六、实战演练:一个稍微复杂点的例子
咱们来个稍微复杂点的例子,加深一下理解。
class Shape {
constructor(color) {
this.color = color;
}
getArea() {
return 0; // 默认面积为0
}
toString() {
return `这是一个${this.color}色的形状,面积为${this.getArea()}。`;
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color);
this.radius = radius;
}
getArea() {
return Math.PI * this.radius * this.radius;
}
}
class Rectangle extends Shape {
constructor(color, width, height) {
super(color);
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
const circle1 = new Circle("红色", 5);
const rectangle1 = new Rectangle("蓝色", 4, 6);
console.log(circle1.toString()); // 输出:这是一个红色的形状,面积为78.53981633974483。
console.log(rectangle1.toString()); // 输出:这是一个蓝色的形状,面积为24。
在这个例子中,Shape
是父类,Circle
和Rectangle
是子类。Circle
和Rectangle
都继承了Shape
的color
属性和toString
方法,并重写了getArea
方法,计算各自的面积。
七、总结与展望
今天,我们深入探讨了JavaScript构造函数和super()
在继承中的作用。希望通过今天的讲解,大家能够更加熟练地运用这两个关键概念,写出更加优雅、健壮的JavaScript代码。
继承是面向对象编程的重要组成部分,掌握继承的原理和技巧,对于提高代码的复用性和可维护性至关重要。
当然,JavaScript的继承机制还有一些其他的细节,例如原型链继承、组合继承等等,这些内容我们可以在以后的机会再进行深入探讨。
最后,希望大家在学习和工作中,多多实践,不断总结经验,成为真正的JavaScript高手!感谢大家的聆听!
有什么问题,欢迎大家提问!