一、原型链继承
// 原型链继承
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();