一.原型链继承
关键核心:让父类的实例作为子类的原型
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.running = function () {
console.log('在跑步~');
};
function Student() {
this.score = 99;
}
Student.prototype = new Person();
Student.prototype.goToSchool = function () {
console.log('去上学~');
};
let s1 = new Student();
console.log(s1);
console.log(s1.running());
缺点:不能传递参数,且引用值类型的数据会被实例共享。
看下面代码:
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['Yasuo', 'Zed', 'Yi'];
}
Person.prototype.running = function () {
console.log('在跑步~');
};
function Student() {
this.score = 99;
}
Student.prototype = new Person();
Student.prototype.goToSchool = function () {
console.log('去上学~');
};
let s1 = new Student();
let s2 = new Student();
s1.friends.push('LeeSin');
console.log(s1.friends);
console.log(s2.friends);
可以看到,明明我只往s1的friends中 push 了LeeSin,但是s2的friends中也有LeeSin了,这就是原型链继承的缺点之一:引用值类型的数据会被实例共享。
二.盗用构造函数继承
关键核心:在子类构造函数中使用 call() 调用父类构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['Yasuo', 'Zed', 'Yi'];
}
Person.prototype.running = function () {
console.log('在跑步~');
};
function Student(name, age) {
Person.call(this, name, age);
this.score = 99;
}
// Student.prototype = new Person();
Student.prototype.goToSchool = function () {
console.log('去上学~');
};
let s1 = new Student('Cyan', 18);
let s2 = new Student('Csy', 22);
console.log(s1.name);
console.log(s2.name);
s1.friends.push('LeeSin');
console.log(s1.friends);
console.log(s2.friends);
console.log(s1.running());
优点:解决了原型链继承中不能传参且引用值共享问题。
缺点:由上图可知,这个方式不能调用父类原型上的方法,因为 Student.prototype 和 Person.prototype 根本没有任何关系。
三.组合继承(原型链 + 构造函数)
关键核心:使用 call() 调用父类的属性,使用 new 获取父类原型上的方法
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['Yasuo', 'Zed', 'Yi'];
}
Person.prototype.running = function () {
console.log('在跑步~');
};
function Student(name, age) {
Person.call(this, name, age);
this.score = 99;
}
Student.prototype = new Person();
Student.prototype.goToSchool = function () {
console.log('去上学~');
};
let s1 = new Student('Cyan', 18);
let s2 = new Student('Csy', 22);
console.log(s1.name);
console.log(s2.name);
s1.friends.push('LeeSin');
console.log(s1.friends);
console.log(s2.friends);
console.log(s1.running());
优点:解决了不能调用父类原型上的方法的问题。
缺点:多次调用了父类构造函数。
实际上,父类构造函数中的this.name和this.age在子类中是取不到的。因为通过子类 new 出来的对象在访问属性或者方法时,会先看 new 出自己的类上面有没有这个属性和方法,没有才去原型上找,原型上没有再去父类找,父类找不到再去父类的原型上找。显然,你都已经调用 call 了,那么父类(Person)中的属性和方法子类(Student)中必然也有。
四.寄生式组合继承
关键核心:使用 Object.create() 来解决多次调用父类构造函数问题
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['Yasuo', 'Zed', 'Yi'];
}
Person.prototype.running = function () {
console.log('在跑步~');
};
function Student(name, age) {
Person.call(this, name, age);
this.score = 99;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.goToSchool = function () {
console.log('去上学~');
};
let s1 = new Student('Cyan', 18);
let s2 = new Student('Csy', 22);
console.log(s1.name);
console.log(s2.name);
s1.friends.push('LeeSin');
console.log(s1.friends);
console.log(s2.friends);
console.log(s1.running());

Student.prototype = Object.create(Person.prototype)这段代码的意思是先用 Object.create() 创建一个新对象,然后让这个新对象的__proto__指向Person.prototype,最后再让Student.prototype.__proto__指向这个新对象。
注意!不能用Student.prototype = Person.prototype,因为如果这时一个 Teacher 子类也想继承 Person 类并且如果 Student 或者 Teacher 修改了他们对应的原型上的方法的话,另外一个类的实例也会受到影响,如下面代码:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.running = function () {
console.log('在跑步~');
};
function Student(name, age) {
Person.call(this, name, age);
this.score = 99;
}
function Teacher(name, age) {
Person.call(this, name, age);
this.salary = 9999;
}
Student.prototype = Person.prototype;
Teacher.prototype = Person.prototype;
let s1 = new Student('Cyan', 18);
let t1 = new Teacher('Csy', 22);
Student.prototype.running = function () {
console.log('学生在跑步~');
};
console.log(s1.running());
console.log(t1.running());
可以看到当我修改了 Student.prototype 上的 running 方法后,t1 实例调用 running 时结果也改变了,这显然不是我们想要的。
使用 Object.create() 就不会发生这样的情况了,看下面代码:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.running = function () {
console.log('在跑步~');
};
function Student(name, age) {
Person.call(this, name, age);
this.score = 99;
}
function Teacher(name, age) {
Person.call(this, name, age);
this.salary = 9999;
}
Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
let s1 = new Student('Cyan', 18);
let t1 = new Teacher('Csy', 22);
Student.prototype.running = function () {
console.log('学生在跑步~');
};
console.log(s1.running());
console.log(t1.running());
当修改Student.prototype上的方法时不会对Teacher.prototype造成影响。实际上,他们两个指向的不是同一个对象。
console.log(Student.prototype === Teacher.prototype); // false
如果是下面这种情况:
Student.prototype = Person.prototype;
Teacher.prototype = Person.prototype;
console.log(Student.prototype === Teacher.prototype); // true
因为他们两个都指向Person.prototype,所以当其中一个对象改变时,另一个也会跟着改变。
好的,以上就是关于 JS 实现继承的几种方法!