JavaScript作为一门面向对象语言,自然有封装、继承、多态的特性,在其他面向对象语言中(如Java、python等),通过类来实现继承,而JavaScript中没有类的概念,于是JavaScript的继承方式区别于传统的继承方式,且有多种实现
以下为JavaScript中常见实现继承的方式,不代表全部。
function Father() {
this.name = "father";
this.say = function () {
console.log("father say")
}
}
Father.prototype.pname = "father prototype";
Father.prototype.run = function () {
console.log("father prototype run")
}
function Sun() {
Father.call(this)
//通过call方法将Father函数的this指向
//绑定为new Sun()的对象上实现继承
}
const sun1 = new Sun();
const sun2 = new Sun();
console.log(sun1.name); //father
sun2.name = "sun2";
console.log(sun1.name); //father
sun1.say(); //father say
console.log(sun1.pname); //报错,未找到该属性
sun1.run(); //报错,未找到该函数
优点
:简单。sun1
和sun2
对象继承了Father构造函数
中的属性与方法,并且分别维护。
缺点
:只继承了Father构造函数中定义的属性与方法,却没有继承
到Father构造函数的原型对象
中的属性与方法。
function Father() {
this.name = "father";
this.say = function () {
console.log("father say")
}
}
Father.prototype.pname = "father prototype";
Father.prototype.run = function () {
console.log("father prototype run")
}
function Sun() {}
//将Sun构造函数的原型对象修改为
//Father实例对象,则由Sun构造函数
//实例化的对象都将有__proto__指针
//指向这个Father实例对象(原型对象)
Sun.prototype = new Father();
//修改原型对象的constructor指向用于
//优化instanceof 判断问题
Sun.prototype.constructor = Sun;
const sun1 = new Sun();
const sun2 = new Sun();
sun1.say(); //father say
sun1.run(); //father prototype run
console.log(sun1.pname); //father prototype
console.log(sun1.name); //father
console.log(sun2.name); //father
sun1.__proto__.name = "sun1";//修改原型对象上的属性将影响所有实例
console.log(sun1.name); //sun1
console.log(sun2.name); //sun1
优点
:现在由Sun构造函数创建的实例对象可以访问Father构造函数的原型对象了,即形成了一条原型链:null --> Object.prototype --> Father.prototype --> Sun.prototype(Father实例对象)--> sun1
。
缺点
:Father实例上定义的属性是全部实例都可共享
的,这就出现了只要一个实例修改了原型对象上的属性值,将会影响所有的实例
。
只要将上面两种继承的方式合并一下
,即可把各自的缺点给弥补
function Father() {
this.name = "father";
this.say = function () {
console.log("father say")
}
}
Father.prototype.pname = "father prototype";
Father.prototype.run = function () {
console.log("father prototype run")
}
function Sun() {
Father.call(this) //构造继承
}
Sun.prototype = new Father(); //修改原型对象
Sun.prototype.constructor = Sun; //优化instanceof判断问题
const sun1 = new Sun();
const sun2 = new Sun();
sun1.say(); //father say
sun1.run(); //father prototype run
console.log(sun1.pname); //father prototype
console.log(sun1.name); //father
console.log(sun2.name); //father
sun1.__proto__.name = "sun1";
sun1.name = "sun1";
console.log(sun1.name); //sun1
console.log(sun2.name); //father
优点
:现在每个实例对象都维护自身的属性了,修改自身属性及原型对象上的属性均不会影响其他实例,而且形成了一条原型链,可访问原型链中所有属性及方法。
缺点
:由于我们使用了call函数
使得每个实例上都存在与Sun原型对象同名的属性及方法
,所以Sun原型对象
上的属性及方法皆可不要,因为增加了内存开销
。
基于上一个继承方式的情况,我们可以很自然的得出一个解决方案:将Sun构造函数的原型对象,即Father实例对象上的不必要的属性及方法除去
即可。
function Father() {
this.name = "father";
this.say = function () {
console.log("father say")
}
}
Father.prototype.pname = "father prototype";
Father.prototype.run = function () {
console.log("father prototype run")
}
function Sun() {
Father.call(this)
}
Sun.prototype = (function () {
function Foo() { } // 声明一个空的构造函数
//将该函数的原型对象修改为Father构造函数的原型对象
Foo.prototype = Father.prototype
//返回该函数的实例化对象,此对象为空对象,且__proto__指针
//指向Father.prototype。以此做到了在不断掉原型链的情况下
//将Sun构造函数的原型对象置为空对象。
return new Foo()
})();
Sun.prototype.constructor = Sun;
const sun1 = new Sun();
console.log(sun1.__proto__.name); //undefined
sun1.run(); //father prototype run
该方法应该算是JavaScript继承方案中的最优解了,通过babel转译成es5代码时所用的继承方案
以上是个人学习的一些总结,作为记录特此写下这篇博客,日后忘了可回头看看。