js|对象继承的几种方式☂️☂️

106 阅读4分钟

一、原型链继承

// 原型链继承
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.play = [1, 2, 3];
    this.setName = function () {
        return this.name;
    }
}
Person.prototype.setAge = function () {
    return this.age;
}
function Student(score) {
    this.score = score;
    this.setScore = function () {
        return this.score;
    }
}
// 子类的原型指向父类的实例对象
Student.prototype = new Person('tom', 18);
let stu1 = new Student(80);
let stu2 = new Student(90);
console.log(stu1.setAge()); // 18 子类继承父类原型上的方法
console.log(stu1.setName()); // tom 子类继承父类自身的方法
console.log(stu1.setScore()); // 80 子类调用自己的方法
// ==========================================================
stu1.play.push(4); // 子类实例1 修改父类属性,会影响到子类实例2
console.log(stu1.play); //  [ 1, 2, 3, 4 ]
console.log(stu2.play); // [ 1, 2, 3, 4 ]
let person = new Person(); // 父类属性不会改变
console.log(person.play); // [ 1, 2, 3 ]

这样实现的实质是将子类的原型指向父类的实例,所以子类可以将父类的私有,公有方法和属性都当做子类的公有属性 我们知道在操作基本数据类型的时候操作的是值,在操作引用数据类型的时候操作的是地址,如果父类的私有属性中有引用类型的属性,那么他被子类继承时候会作为公有属性,这样子类1操作这个属性的时候,就会影响到子类2

s1.play.push(4);
console.log(s1.play,s2.play); // [ 1, 2, 3, 4 ] [ 1, 2, 3, 4 ]

s1的属性发生改变的时候,s2的属性也发生了改变

我们在子类中添加新的方法或者是重写父类的方法的时候,切记一定要放到替换原型的语句后面

       function Person(name, age) {
           this.name = name,
           this.age = age
       }
       Person.prototype.setAge = function () {}
       function Student(price) {
           this.price = price
           this.setScore = function () { }
       }
       // Student.prototype.sayHello = function () { }//在这里写子类的原型方法和属性是无效的,
      //因为会改变原型的指向,所以应该放到重新指定之后
       Student.prototype = new Person()
       Student.prototype.sayHello = function () {}
       var s1 = new Student(15000)
       console.log(s1)

写子类的原型方法和属性是无效的, //因为会改变原型的指向,所以应该放到重新指定之后

特点:

  • 父类新增的原型方法/原型属性,子类都能访问到
  • 简单,易于实现

缺点:

  • 无法实现多继承
  • 来自原型对象的所有属性被实例所共享
  • 创造子类实例时,无法向父类构造参数传参
  • 要想为子类新增属性和方法,必须在替换原型语句后面,不能放到构造器中

二、盗用构造函数继承

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.play = [1, 2, 3];
    this.setName = function () {
        return this.name;
    }
}
Person.prototype.setAge = function () {
    return this.age;
}
function Student(name, age, play, score) {
    console.log("this-->", this); // this是Student {}
    Person.call(this, name, age, play);
    this.score = score;
}
// console.log(stu1.setAge()); // 报错,无法继承父类原型上的方法
let stu1 = new Student('tom', 18, [4], 98);
stu1.play.push(4); // stu1对引用类型的改变,不会影响stu2
console.log(stu1);
// Student {
//     name: 'tom',
//     age: 18,
//     play: [ 1, 2, 3, 4 ],
//     setName: [Function (anonymous)],
//     score: 98
//   }
let stu2 = new Student('tom', 18, [4], 98);
console.log(stu2);
// Student {
//     name: 'tom',
//     age: 18,
//     play: [ 1, 2, 3 ],
//     setName: [Function (anonymous)],
//     score: 98
//   }

这种方式,只能继承父类的属性和方法,不能继承父类原型的属性和方法

特点:

  • 解决了原型链继承中子类实例共享父类引用属性的问题
  • 创建子类实例的时候,可以向父类传递参数
  • 可以实现多继承,call多个父类对象

缺点:

  • 实例并不是父类的实例,只是子类的实例
  • 只能继承父类的方法和属性,不能继承原型的属性和方法
  • 无法实现函数复用,每一个子类都有父类实例函数的副本,影响性能

三、原型链+构造函数(组合继承)

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.setAge = function () { }
}
Person.prototype.setAge = function () {
    console.log('111');
}
function Student(name, age, price) {
    Person.call(this, name, age) // 构造函数
    this.price = price
    this.setScore = function () { }
}
Student.prototype = new Person(); // 原型链 
Student.prototype.constructor = Student; // 修复构造函数指向
Student.prototype.sayHello = function (){};
var s1 = new Student('tom',20,14000);
console.log(s1);

特点:

  • 可以继承实例属性/方法 也可以继承原型属性/方法
  • 不存在引用属性共享问题
  • 可传参
  • 函数可以复用

缺点:

  • 调用了两次父类构造函数,生成了两份实例

四、组合继承优化1

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.setAge = function () { }
}
Person.prototype.setAge = function () {
    console.log('111');
}
function Student(name, age, price) {
    Person.call(this, name, age);
    this.price = price;
    this.setScore = function () { }
}
Student.prototype = Person.prototype; // 父类原型和子类原型指向同一个对象
Student.prototype.sayHello = function () { };
var s1 = new Student('ton', 20, 1212);
console.log(s1);

优点:

  • 不会初始化两次实例方法/属性 避免组合继承的缺点

缺点

  • 没有办法辨别实例是子类还是父类创建的,子类和父类的构造函数指向同一个

五、组合继承优化2

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.setAge = function () {
    console.log('111');
}
function student(name, age, price) {
    Person.call(this, name, age);
    this.price = price;
    this.setScore = function () { }
}
student.prototype = Object.create(Person.prototype); // 核心代码
// var B = Object.create(A)以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
student.prototype.constructor = student; //核心代码
var s1 = new student('tom',12,19002);
console.log(s1 instanceof student , s1 instanceof Person); // true true
console.log(s1.constructor); // student
console.log(s1);

优点:

  • 继承了父类原型对象的属性和方法,最完美的继承方法

六、ES6中的class继承

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    showName() {
        console.log("调用父类的方法");
        console.log(this.name, this.age);
    }
}
let p1 = new Person('tom', 18);
console.log(p1);
class Student extends Person {
    constructor(name, age, salary) {
        super(name, age);
        this.salary = salary;
    }
    showName() {
        console.log('调用子类的方法');
        console.log(this.name, this.age, this.salary);
    }
}
let s1 = new Student('tom',18,100000);
console.log(s1);
s1.showName();