在ES6 class类声明出来之前,ES5的继承十分得蛋疼,这里就写一下ES5继承的具体操作,最后和ES6做一下对比,你会发现ES6出来真是太好了!!!
在讲知识点之前,首先明确几个概念
- ES5中构造函数声明方式:
function A(){}(大写的函数名) - 实例化:
new A(); - 实例化生成的对象称为实例:
var a = new A() - 原型:
A.prototype - 实例可以通过__proto__访问原型,即:
a.__proto__ === A.prototype - 原型上的constructor属性指向构造函数本身(可以修改),即:
A.prototype.constructor === A
ES5
首先给出一个构造函数
function Person(opt){
this.name = opt.name;
this.age = opt.age;
}
Person.prototype.sayName = function(){
alert(this.name);
}
- 私有属性的继承
function Student(opt){
Person.call(this,opt);//当生成实例时,此步骤可以将Person的私有属性挂载至实例上
this.sex = opt.sex;//扩展私有属性
}
- 原型链的继承
继承中最麻烦的就是原型链的继承,为了大家便于理解,这里给出几种方案,并比较他们的优劣。
方案一:
Student.prototype = Person.prototype
此种方案虽然简便,但是有个十分严重的缺点,因为原型本身是一个对象,通过直接赋值的形式,则你在Student的原型上做的所有扩展都会影响到Person.
思路:
既然不能直接将原型赋值给子类,那么势必要通过中间介质来访问父类的原型,利用原型链,底层实例可以向上层原型链访问的特点,我们可以利用Person生成一个实例,并把它赋值给Student.prototype,此时Student.prototype就可以通过__proto__访问到Person的原型,也就可以使用它的方法
方案二:
Student.prototype = new Person({});
可能看了上面的代码,你们可能有点不理解,不急下面给出解释.
首先从代码本身来看,我们把Person实例出的一个对象赋值给了Student的原型,那么我们就来看看,现在Student的原型是什么样的
let opt = {
name: "erha",
age: 18,
sex: "男"
};
let student = new Student(opt);
console.log(new Person({}));
console.log(student);
console.log(student.__proto__ === Student.prototype);//true
sayName的方法,但是student实例可以通过原型链找到Person.prototype原型上的sayName方法
Student.prototype.sayAge = function(){
alert(this.age);
}
student.sayName();//弹窗'erha'
student.sayAge();//弹窗18
let person = new Person(opt);
person.sayName();//弹窗'erha'
person.sayAge();//报错
此时原型链的情况
但是我们看到这种方法,会在Student的原型上产生无用的公有属性
因此给出改进的方案三:
/*既然方案二会产生无用的公有属性,那么我们就定义一个没有私有属性的构造函数*/
function A(){};
A.prototype = Person.prototype;//然后和Person的原型关联
Student.prototype = new A();//和方案二原理类似
Student.prototype.sayAge = function(){
alert(this.age);
}
let studentA = new Student(opt);
console.log(studentA);
可以看到方案三不仅继承了方案二的优点,而且完善了方案二原型上会有无效的公有属性的缺点.
最后,因为通过new A({})生成的实例并没有constructor属性,所以我们还需要修正一下Student原型上的construtor.
Student.prototype.constructor = Student;
最后给出完成的方案三代码
function Person(opt){
this.name = opt.name;
this.age = opt.age;
}
Person.prototype.sayName = function(){
alert(this.name);
}
function Student(opt){
Person.call(this,opt);//当生成实例时,此步骤可以将Person的私有属性挂载至实例上
this.sex = opt.sex;//扩展私有属性
}
let opt = {
name: "erha",
age: 18,
sex: "男"
};
/***************/
function A(){};
A.prototype = Person.prototype;//然后Person的原型关联
Student.prototype = new A();
/***************/
Student.prototype.sayAge = function(){
alert(this.age);
}
Student.prototype.constructor = Student;
let studentA = new Student(opt);
console.log(studentA);
虽然方案三,完美地解决了继承和扩展的问题,但是还是有它的缺点:步骤繁琐
方案四:
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
function Person(opt){
this.name = opt.name;
this.age = opt.age;
}
Person.prototype.sayName = function(){
alert(this.name);
}
function Student(opt){
Person.call(this,opt);//当生成实例时,此步骤可以将Person的私有属性挂载至实例上
this.sex = opt.sex;//扩展私有属性
}
let opt = {
name: "erha",
age: 18,
sex: "男"
};
/***************/
Student.prototype = Object.create(Person.prototype);//创建一个新对象,并将Person原型提供给新建对象的__proto__,最后赋值给Student的原型
/***************/
Student.prototype.sayAge = function(){
alert(this.age);
}
let studentA = new Student(opt);
console.log(studentA);
最后结果和方案三一模一样,但是使用原生JS自带的方法省去很多步骤,但是它们的原理是一样的.
最后来看看ES6是如何实现继承的.
ES6
class Person{
constructor(opt){
this.name = opt.name;
this.age = opt.age;
}
sayName(){
alert(this.name);
}
}
class Student extends Person{
constructor(opt){
super(opt);//继承私有属性
this.sex = opt.sex;
}
//扩展方法
sayAge(){
alert(this.age);
}
}
let opt = {
name: "erha",
age: 18,
sex: "男"
};
let studentA = new Student(opt);
console.log(studentA);
可以看到使用extends,直接解决了ES5中最麻烦的原型链继承,且调用super()也直接继承了父类的私有属性,并且私有属性的扩展和公有属性的扩展都写在了class内部,让人看上去更加得直观,并且class写法更贴合其他真正面向对象语言的写法