一、ES5实现继承
1、原型链
在JavaScript中,通过new操作符创建一个实例对象,这个实例对象就会拥有一个【__proto__】属性,这个属性指向构造函数的prototype对象;构造函数的prototype对象也拥有一个【__proto__】属性,这个属性指向Object的prototype对象;Object.prototype的【__proto__】指向顶级对象null;按照这种一层一层向上查找的方式,就形成了一个原型链,这也是前端实现继承的最核心的点,有了原型链之后,实例对象就可以访问到它的祖先元素的属性和方法了。复制代码
2、继承
2-1、原型链继承
【代码实现】
function Parent (name) { this.name = name; this.friends = ['zhangsan', 'lisi'];}Parent.prototype.getName = function () { console.log(name);}function Child (age) { this.age = age; }Child.prototype = new Parent();Child.prototype.getAge = function () { console.log(this.age);}var child = new Child(22);child.friends.push('wangwu');var child1 = new Child(23);console.log(child.friends);// [ 'zhangsan', 'lisi', 'wangwu' ]console.log(child1.friends);// [ 'zhangsan', 'lisi', 'wangwu' ]复制代码
【问题!!!】
1>、使用原型链实现继承时,不能使用字面量的方式为子类添加方法,会断开原型链,导致子类无法访问父类。2>、使用原型链继承,父类的属性和方法与子类共享,如果父类的属性是引用类型,那么子类在修改该属性时,也会修改父类的该属性。3>、在创建子类的实例时,无法向父类传递参数。复制代码
2-2、借用构造函数继承
使用构造函数继承,子类可以向父类传递参数复制代码
【代码实现】
function Parent (name) { this.name = name; this.friends = ['zhangsan', 'lisi'];}Parent.prototype.getName = function () { console.log(name);}function Child (age) { Parent.call(this); this.age = age; }Child.prototype.getAge = function () { console.log(this.age);}var child = new Child(22);child.friends.push('wangwu');var child1 = new Child(23);console.log(child.friends);// ['zhangsan', 'lisi', 'wangwu']console.log(child1.friends);// ['zhangsan', 'lisi']child.getName();// error复制代码
【问题!!!】
使用构造函数的方式实现继承时,子类只能访问直接定义在父类上的属性和方法,而定义在父类的prototype对象上的方法和属性子类是访问不到的。无法实现函数的复用。复制代码
2-3、组合继承
组合继承,也叫伪经典继承;他是构造函数和原型链的结合。原理就是:使用原型链实现对属性和方法的继承。复制代码
【代码实现】
function Parent (name) { this.name = name; this.friends = ['zhangsan', 'lisi'];}Parent.prototype.getName = function () { console.log(this.name);}function Child (name, age) { Parent.call(this, name); this.age = age; }Child.prototype = new Parent();Child.prototype.getAge = function () { console.log(this.age);}var child = new Child('xiao', 22);child.friends.push('wangwu');var child1 = new Child('meng', 23);console.log(child.__proto__);console.log(child.friends);console.log(child1.friends);child.getName();// xiao复制代码
【结论】
每个实例对象都有自己的属性和方法,子类可以访问父类的原型链上的方法和属性。复制代码
2-4、原型式继承
使用一个中间对象作为基础,继承父类的原型复制代码
【代码实现】
function Parent (name) { this.name = name; this.friends = ['zhangsan', 'lisi'];}Parent.prototype.getName = function () { console.log(this.name);}function Child (name, age) { Parent.call(this, name); this.age = age; }Child.prototype = Object.create(Parent.prototype);Child.prototype.getAge = function () { console.log(this.age);}var child = new Child('xiao', 22);child.friends.push('wangwu');var child1 = new Child('meng', 23);console.log(child.__proto__);console.log(child.friends);console.log(child1.friends);child.getName();// xiao复制代码
二、ES6继承
【简述】
在ES6中,继承通过extends关键字实现。通过extends关键字实现继承时,只是实现了父类的复制。复制代码
【注意事项】
- 使用extends之后,如果不使用super方法,子类的实例对象调用父类的方法时,父类的this指向已经发生了变化,指向了子类。- 子类必须在constructor方法中,调用super方法,否则新建实例会报错。 【子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的属性和方法,然后再添加子类自己的属性和方法】复制代码
class Person { //调用类的构造方法 constructor(name, age) { this.name = name this.age = age } //定义一般的方法 showName() { console.log("调用父类的方法") console.log(this.name, this.age); }}let p1 = new Person('kobe', 39)console.log(p1)//定义一个子类class Student extends Person { constructor(name, age, salary) { super(name, age)//通过super调用父类的构造方法 this.salary = salary } showName() {//在子类自身定义方法 console.log("调用子类的方法") console.log(this.name, this.age, this.salary); }}let s1 = new Student('wade', 38, 1000000000)console.log(s1)s1.showName()复制代码
【ES5继承和ES6继承的区别】
- ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this对象上。- ES6的继承,实质是先将父类实例对象的属性和方法添加到this对象上【所以必须先调用super方法】,然后再用子类的构造函数修改this。- 如果子类没有定义constructor方法,这个方法会被默认添加,即:不管有没有显示定义,任何一个子类都有constructor方法。【注意】:在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建基于父类实例,只有super方法才能调用父类实例。复制代码