本文总结了JS常见的实现继承的方式,例子与一些标注都是自己的思考,欢迎各位交流,结构上主要参考了这个文章1。
先来看看原型链继承方式吧,代码如下:
function Person() {
this.nation = ["china"];
} // 内部属性
Person.prototype.say = function () {
let res = this.nation;
res = `I have been ${res}`;
return res;
}; // 原型对象上的属性方法
function Chinese() {}
Chinese.prototype = new Person(); // 这里通过prototype继承
p1 = new Chinese();
p1.nation.push("japan");
p2 = new Chinese();
p2.nation = "korea"; // 这里是相当于新申请了一个空间,并赋值给p2.nation
p3 = new Chinese();
p3.nation.push("america");
console.log("p1", p1);
console.log("p2", p2);
console.log("p3", p3); // 注意这里p3 与 p1 公用了nation
// 所以这种方式存在的两种问题
// 问题1:原型中包含的引用类型属性将被所有实例共享;
// 问题2:子类在实例化的时候不能给父类构造函数传参;
我们来看一下运行结果,如下图所示
从上图中我们可以看到,p1
在调用Person
原型对象(prototype
)上的say
方法时,对于原型上的引用类型nation
是大家共享的,所以p1
也会说,自己去过america。而p2
则因为赋值操作于词法分析阶段,在p2的身上追加了一个nation
属性,在js运行时,在自身有所需属性时,不会去原型链上查找了,所以此处没有公用原型链上的nation
。
function Person(name) {
this.name = name;
this.nation = ["china"];
this.say = function () {
let res = this.nation;
res = `I have been ${res}`;
return res;
};
}
function Chinese(name) {
Person.call(this, name); // 对this,进行绑定
}
Chinese.prototype = new Person(); // 这里继承
p1 = new Chinese("Li Hua");
p1.nation.push("japan");
p2 = new Chinese("Han Mei Mei");
p2.nation = "korea";
p3 = new Chinese("Lao Wang");
p3.nation.push("america");
console.log("p1", p1);
console.log("p2", p2);
console.log("p3", p3);
// 这种方式虽然解决了原型方式中的不可传参的问题,与引用类型共享的问题,
// 但是同时也引入了新的问题,即每次创建实例的时候都会重新创建一遍对象方法
运行结果如下图所示:
我们可以看到,这种方式解决了原型方式的两个不足,他可以传递参数,而且对于原型上引用类型属性也不是公用的。但是我们同时也能清楚的看到,每个实例对象都重复的创建了say这个方法,这可以说是一种新的不足。
代码如下:
function Person(name) {
this.name = name;
this.nation = ["china"];
}
// 公用方法还是用原型对象的方式
Person.prototype.say = function () {
let res = this.nation;
res = `I have been ${res}`;
return res;
};
function Chinese(name, age) {
Person.call(this, name); // 私有属性利用构造函数,下面指定constructor为自身
this.age = age;
}
Chinese.prototype = new Person(); // 继承name nation 与 say方法
Chinese.prototype.constructor = Chinese; // 构造器用Chinese
p1 = new Chinese("Li Hua", 16);
p1.nation.push("japan");
p2 = new Chinese("Han Mei Mei", 16);
p2.nation = "korea";
p3 = new Chinese("Lao Wang", 35);
p3.nation.push("america");
console.log("p1", p1);
console.log("p2", p2);
console.log("p3", p3);
运行结果如下图所示:
如上图所示:组合继承结合了原型链方式和借用构造函数方式,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
// 组合继承已经相对完善了,但还是存在问题,它的问题就是调用了 2 次父类构造函数
// 第一次是 在Person.call(this,name) 第二次 是在new Person()的时候
// 改进如下,主要是第20行,不在用new操作符了
function Person(name) {
this.name = name;
this.nation = ["china"];
}
Person.prototype.say = function () {
let res = this.nation;
res = `I have been ${res}`;
return res;
};
function Chinese(name, age) {
Person.call(this, name);
this.age = age;
}
Chinese.prototype = Object.create(Person.prototype); // 继承name nation 与 say方法,但是不再使用new来调用。
Chinese.prototype.constructor = Chinese; // 构造器用Chinese
p1 = new Chinese("Li Hua", 16);
p1.nation.push("japan");
p2 = new Chinese("Han Mei Mei", 16);
p2.nation = "korea";
p3 = new Chinese("Lao Wang", 35);
p3.nation.push("america");
console.log("p1", p1);
console.log("p2", p2);
console.log("p3", p3);
class Person {
constructor(name) {
this.name = name;
this.nation = ["china"];
}
say() {
let res = this.nation;
res = `I have been ${res}`;
return res;
}
}
class Chinese extends Person {
constructor(name, age) {
super(name);
this.age = age;
}
}
p1 = new Chinese("Li Hua", 16);
p1.nation.push("japan");
p2 = new Chinese("Han Mei Mei", 16);
p2.nation = "korea";
p3 = new Chinese("Lao Wang", 35);
p3.nation.push("america");
console.log("p1", p1);
console.log("p2", p2);
console.log("p3", p3);
运行结果如下图所示:
在class这里我们发现,利用class实现和ES5寄生组合式的实现表现基本一致,但是更加清晰。通过一步步的实现与对比,我们更好的理解了原型链等相关基础知识。如果这篇文章对您有帮助,欢迎您在评论区与我交流,不足之处也请指正。谢谢!
https://mp.weixin.qq.com/s/mG3MTIQFcKvUU2WBByIugg ↩︎