一、简述
在提到js的类的时候,相信大部分人都习惯性地想到了class这个关键字。而我阅读过一些文章发现,class关键字并不是一开始就有的,是ES6才出现的。也就是意味着,以前是没有这个class的,那么就会想,以前是怎么实现类和它相关的继承的这个功能的呢?
二、类的继承发展
2.1 原型链实现
原理:通过直接附加在原型链上的属性或方法,往后可以直接提供给继承者使用
// 父类
function Person() {
this.name = "yz";
this.friends = [];
}
Person.prototype.eating = function() {
console.log(this.name + " eating");
}
// 子类
function Student() {
// 学号
this.sno = 111
}
// 原型链实现
var p1 = new Person();
Student.prototype = p1;
Student.prototype.studying = function() {
console.log(this.name + " studying");
}
var stu = new Student();
console.log(stu.__proto__ === p1) // true
console.log(stu.name);
stu.eating();
console.log(Person.prototype.constructor);
// 原型链实现继承的弊端
// 1.打印对象,继承的属性是看不到的
console.log(stu);
// 2.创建两个stu对象
var stu1 = new Student()
var stu2 = new Student()
// 获取引用,修改引用的值,会相互影响
stu1.friends.push("kobe");
console.log(stu1.friends);
console.log(stu2.friends);
在以上代码实现类的过程中,我们可以通过该系列的第一章——原型链,画出以下图
在前面实现类的过程中是有弊端的, 加上以下代码后,很容易发现弊端,共用的friends会同时影响两个新生成的对象stu1、stu2
// 原型链实现继承的弊端
// 1.打印对象,继承的属性是看不到的
console.log(stu);
// 2.创建两个stu对象
var stu1 = new Student()
var stu2 = new Student()
// 获取引用,修改引用的值,会相互影响
stu1.friends.push("kobe");
console.log(stu1.friends);
console.log(stu2.friends);
- 以上的studying和eating函数都没有传递参数
- 借用构造函数也是有弊端的,Person函数被调用两次
- stu对象上会多出属性,但是这些属性是没有必要存在的
2.2 原型链+借用构造函数实现
原理:通过函数所具有的call方法重新改变this的指向,并在子类中使用父类执行。即:用父类作为构造函数
// 父类
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function() {
console.log(this.name + " eating");
}
// 子类
function Student(name, age, friends, sno) {
// 借用构造函数
Person.call(this, name, age, friends);
this.sno = sno;
}
// 直接将父类赋值给子类,这种方案是不正确的
Student.prototype = new Person();
Student.prototype.studying = function() {
console.log(this.name + " studying");
}
// 解决第3个弊端
var stu1 = new Student("yz", 18, ["kobe"], 111)
var stu2 = new Student("yz2", 20, ["james"], 130)
// 解决第1个弊端
console.log(stu1);
stu1.eating()
stu1.friends.push("haha");
// 解决第2个弊端
console.log(stu1.friends);
console.log(stu2.friends);
弊端
- Person函数至少被调用两次
- stu原型对象上会多余属性
2.3 父类原型赋值给子类(不正确的方案)
// 父类
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function() {
console.log(this.name + " eating");
}
// 子类
function Student(name, age, friends, sno) {
// 借用构造函数
Person.call(this, name, age, friends);
this.sno = sno;
}
// 直接将父类赋值给子类,这种方案是不正确的
Student.prototype = Person.prototype;
// study被加上了person上面了
Student.prototype.studying = function() {
console.log(this.name + " studying");
}
var stu = new Student("yz", 18, ["kobe"], 123);
console.log(stu);
stu.eating();
弊端:违背了继承的概念,将Student上的属性和方法会直接转移到Person上,不合继承的功能。
2.4 原型式继承
这种继承方式在出现方法Object.setPrototypeOf之后流行,通过一个工厂函数,传入父类并通过一个中介函数并让其的原型指向父类,然后创造并返回生成的新的对象
var obj = {
name: "yz",
age: 18
}
function createObject(o) {
var newObj = {};
// 有这个方法的时候就用这个方法实现
Object.setPrototypeOf(newObj, o);
return newObj;
}
// 2006年的时候没有setPrototypeOf
function createObject2(o) {
function Fn() {}
Fn.prototype = o;
var newObj = new Fn();
return newObj;
}
// var info = createObject(obj);
// 最新的提供的api
var info = Object.create(obj);
console.log(info);
console.log(info.__proto__);
2.5 寄生式继承
原理:通过Object.create创建出父类对象并作为子类对象的继承对象,以下写了一个以Student创造对象的工厂函数
var personObj = {
running: function() {
console.log("running");
}
}
function createStudent(name) {
var stu = Object.create(personObj);
stu.name= name;
stu.studying = function() {
console.log("studying~")
}
return stu;
}
var stuObj = createStudent("yz");
2.6 寄生组合式继承
原理:整合寄生和组合的方式,关键在于inheritPrototype函数,指定并实现了父类和子类的继承关系
function inheritPrototype(SubType, SuperType) {
SubType.prototype = Object.create(SuperType.prototype);
Object.defineProperty(SubType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person(name, age, friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.running = function () {
console.log("running");
}
Person.prototype.eating = function () {
console.log("eating");
}
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends);
this.sno = sno;
this.score = score;
}
inheritPrototype(Student, Person);
Student.prototype.studying = function () {
console.log("studying");
}
var stu = new Student("yz", 18, ["jam"], 111, 180);
console.log(stu);
stu.studying();
stu.running();
stu.eating();
三、总结全新class的使用
- 类声明:使用
class关键字可以声明一个类,后面跟随类的名称。
class MyClass {
// 类的定义
}
- 构造函数:类可以包含一个特殊的方法,称为构造函数,它在创建类的实例时被调用。
class MyClass {
constructor() {
// 构造函数代码
}
}
- 实例方法:可以在类中定义方法,这些方法可以在类的实例上调用。
class MyClass {
myMethod() {
// 方法代码
}
}
- 静态方法:类还可以定义静态方法,这些方法直接在类上调用,而不是在类的实例上调用。
class MyClass {
static myStaticMethod() {
// 静态方法代码
}
}
- 继承:类可以通过
extends关键字继承另一个类的属性和方法。
class ChildClass extends ParentClass {
// 子类的定义
}
- super关键字:在子类中使用
super关键字可以调用父类的构造函数和方法。
class ChildClass extends ParentClass {
constructor() {
super(); // 调用父类的构造函数
}
myMethod() {
super.myMethod(); // 调用父类的方法
}
}
- 访问修饰符:类中的属性和方法可以使用访问修饰符来定义其可访问性,包括
public、private和protected。
class MyClass {
publicProperty = 123; // 公共属性
#privateProperty = 456; // 私有属性
publicMethod() {
// 公共方法
}
#privateMethod() {
// 私有方法
}
}
js中并没有protected的属性,通过添加前缀_表示该属性属于protected属性
class MyClass {
_protectedProperty = 123; // 受保护的属性
_protectedMethod() {
// 受保护的方法
}
}
这些是JavaScript类的主要特性和用法。类提供了一种组织和封装代码的方式,使代码更易于理解和维护,并支持面向对象的编程范式。