在JavaScript中实现继承的几种方式

文章目录

  • 前言
  • 继承的几种方式
    • 构造方式继承
      • 进行测试
      • 结论
    • 修改原型继承
      • 进行测试
      • 结论
    • (构造函数+修改原型)组合继承
      • 进行测试
      • 结论
    • 基于组合继承的优化
      • 测试
      • 结论
  • 总结


前言

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(); //报错,未找到该函数

结论

优点:简单。sun1sun2对象继承了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原型对象上的属性及方法皆可不要,因为增加了内存开销

在JavaScript中实现继承的几种方式_第1张图片


基于组合继承的优化

基于上一个继承方式的情况,我们可以很自然的得出一个解决方案:将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代码时所用的继承方案


总结

以上是个人学习的一些总结,作为记录特此写下这篇博客,日后忘了可回头看看。

你可能感兴趣的:(JavaScript,基础知识,javascript,前端,开发语言)