这是我参与2022首次更文挑战的第21天活动详情查看:2022首次更文挑战」。
1.原型
对于ES6出现了以class为基础的继承,但是底层ES5的继承也值得我们去探索。首先就是理解JavaScript原型的概念。我之前文章也写到过,对于原型目的是为了解决这么一个问题,解决多个对象都有同一个方法但是都要新开辟一个内存空间,使用原型就开辟一个内存空间,所有实例的共有方法都集放置在这么一个原型对象上面。
function Person(name, age) {
this.name = "name";
this.age = 23;
this.name = name;
this.age = age;
}
Person.prototype.doEat = function (a, b) {
console.log("a", "b", a, b);
};
2.原型和构造函数,实例
在真正做继承之前,我们要先理解一下三个定义,原型是上面说到放相同方法在一个内存空间的地方,这个堆空间就叫原型对象。构造函数指只要通过new关键字创建对象的函数叫做构造函数,实例指的是通过new 构造函数所创建的对象就是实例,他们三者关系如下图所示
3.原型链继承
我们想到最简单的继承就是,将子类的原型指向父类的实例化对象,这样就可以使用父类所有的方法了,再将原型的构造函数指向子类,这样形成新的闭环。这样父类不管是构造函数的方法,还是父类原型的方法都在其实例中能够找到。这也是JavaScript最基本的继承思路。
function Parent(name, age) {
this.name = name;
this.age = age;
}
function Son(favor, sex) {
this.favor = favor; // 兴趣爱好
this.sex = sex;
}
Son.prototype.playSex = function () {
console.log("我的性别是");
};
Son.prototype = new Parent("好好的", 23); // 98
Son.prototype.constructor = Son;
let sonObj = new Son("篮球", "男");
4.构造继承
通过3提到的原型链继承我们可以发现存在两个问题,第一是子类原型上的方法不能使用了,这可以通过子类继承中重新赋值原型方法解决。第二个问题是无法在子类向父类传递参数,都只能只有在外面实例化父类传递参数,显然是有些许问题的。下面代码所示是构造继承,使用call方法借调实现父类的构造方法,可以理解为将子类调用父类的构造函数,并且可以传递参数给父类构造方法,在TS中的super实现
function Parent(name, age) {
this.name = name;
this.age = age;
this.playAge = function () {
console.log("年龄是", this.age);
};
}
Parent.prototype.playName = function () {
console.log("姓名是", this.name);
};
function Son(weight, height, name, age) {
this.weight = weight; // 兴趣爱好
this.height = height;
Parent.call(this, name, age); //TS中使用super
}
const p = new Parent("140", "180");
p.playName();
const son = new Son("140", "180", "张三", 24);
son.playAge();
5.原型+构造继承
构造继承的弊端就是父类的原型方法完全没法使用,因为只是相当于拷贝了父类构造方法的属性和方法。我们可以集合原型+构造函数继承的方法,互相弥补两种方式的弊端。
function Parent(name, age) {
this.name = name;
this.age = age;
this.playName = function () {
console.log("我的姓名是", this.name);
};
}
Parent.prototype.playAge = function () {
console.log("我的年龄是", this.age);
};
function Son(weight, height, name, age) {
Parent.call(this, name, age);
this.weight = weight; // 兴趣爱好
this.height = height;
this.playWeight = function () {
console.log("我的体重是", this.weight);
};
}
Son.prototype.playHeight = function () {
console.log("我的身高是", this.height);
};
Son.prototype = new Parent("外面姓名", 24); // 98
Son.prototype.constructor = Son;
let son = new Son("120", "175", "里面姓名", 22);
6.完美继承
通过上面的说到的原型+构造函数继承已经是比较完美的方法了,但是还是会有些许瑕疵,比如父类构造方法其实执行了两次,第一次是父类实例化的时候执行,第二次是构造继承的时候借调执行,浪费了性能。而是父类实例化传递的参数完全无用,被子类借调的时候覆盖了,这也容易造成误解。然后可能说可以采用继承父类原型+构造,这个时候很多地方都是原型就会都被改到,这个时候要采用拷贝一份原型代码出来继承,如下代码所示
function Parent(name, age) {
this.name = name;
this.age = age;
this.playName = function () {
console.log("我的姓名是", this.name);
};
}
Parent.prototype.playAge = function () {
console.log("我的年龄是", this.age);
};
function Son(weight, height, name, age) {
this.weight = weight; // 兴趣爱好
this.height = height;
Parent.call(this, name, age);
this.playWeight = function () {
console.log("我的体重是", this.weight);
};
}
Son.prototype.playHeight = function () {
console.log("我的身高是", this.height);
};
function Temp() {}
Temp.prototype = Parent.prototype;
tempobj = new Temp();
Son.prototype = tempobj;
tempobj.constructor = Son;
let son = new Son("120", "175", "里面姓名", 22);
console.log(son);
// son.playHeight()
son.playAge();
son.playWeight();
son.playName();