JS:js面向对象ES5和ES6的继承的方法考点(五颗星)
面向对象的继承方法总共有7种,前6种是ES5实现继承的方法,最后1种是ES6实现继承的方法。
1.ES5继承的方法(有六种)
1.混入式继承(也叫拷贝继承)
需求: 使用混入式实现继承(了解)
实现原理:将父类成员拷贝到子对象中(浅拷贝), 拷贝(复制)!!! 使用for in循环 将父类复制到子类中。
实现方法:for…in…循环遍历父类,子类[key]=父类[key]
缺点:共享数据安全问题,修改子类,会影响父类,引用数据类型浅拷贝,会修改引用地址,数据安全问题,会造成一改全改,原因在于大家引用的是同一个内存区域中的数据。
<script> //假设 obj1父类 obj2子类 var obj1={ name:"张三",//基本类型 age:20, wife:{//引用类型 name:"美丽", age:18 } } var obj2={} // 混入式继承也叫拷贝继承-------语法还是重点 for(var k in obj1){ obj2[k]=obj1[k] } obj2.name="李四" obj2.wife.name="王五" console.log(obj1) console.log(obj2) </script>
2.原型式继承
目标: 实现原型式继承(了解)
实现原理: 子类的原型对象指向父类的原型对象,将父类中的原型成员添加到子类的原型链中。
实现方式:子类.prototype = 父类.prototype
弊端: 数据安全,子类会修改父类原型 原型链结构混乱,数据共享安全,只能继承父类原型对象中成员,不能继父类实例对象成员
需求: 子类的实例对象 使用 父类方法:
<script> // 父类 function Person(name,age){ this.name=name this.age=age } Person.prototype.eat=function(){ console.log(this.name+"爱吃鱼") } // 子类 function Student(name,age,score){ this.name=name this.age=age this.score=score } // 实现继承 Student.prototype=Person.prototype Student.prototype.getScore=function(){ console.log(this.score) } var s=new Student("张三",19,100) console.log(s) s.eat() s.getScore() </script>
3.原型链继承
目的: 实现 原型链继承(掌握)
实现原理:将子类的原型对象指向父类的实例对象。
1. 实现原型链继承 子类原型指向父类实例
Student.prototype = new Person(); // 不需要传参
2. 注意点: 记得将constructor属性指向子类
Student.prototype.constructor = Student;
3. 子类需要方法,记得加上
Student.prototype.方法= function () { }
实现方法:子类.prototype = new 父类()
存在问题:存在数据共享问题,无法给父类构造函数传递参数,只能继承原型成员,不能继承实例成员,父类的实例成员中属性和子类中的实例成员有相同的属性,但是子类不能使用父类的实例成员。造成代码有重复,属性多次
需求: 子类的实例对象 使用 父类方法
<script> // 父类 function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log(this.name + "爱吃木桶猪脚饭"); } // 子类 function Student(name, age, score) { this.name = name; this.age = age; this.score = score; } // 1. ***实现原型链继承 子类原型指向父类实例 Student.prototype = new Person(); // 不需要传参 // 2. ***注意点: 记得将constructor属性指向子类 Student.prototype.constructor = Student; // 3. 子类需要方法,记得加上 Student.prototype.getScore = function () { console.log(this.name + "考试" + this.score + "分"); } // 创建子类的实例 var s = new Student("张三",20,130); var p=new Person("李四",12) console.log(s); console.log(p); s.eat(); s.getScore(); console.log(s.constructor); </script>
4.借用构造函数继承
目的: 实现 借用构造函数继承
思路: 代码重复-----函数封装
弊端: 只能继承实例成员,不能继承原型成员
实现原理:在子构造函数中调用父构造函数,达到继承并向父构造函数传参的目的,父类构造*函数* 在子类中运行。
实现方法:(改变this指向)
- 将父对象的构造函数设置为子对象的成员,即将父类构造函数赋值给子类的实例对象
- 调用这个方法,类似于将父构造函数中的代码复制到子构造函数中来执行,即子类实例去调用这个方法---父类内部的this指向子类的实例
- 用完之后删掉
这种继承方式都存在下面两个问题:
- 如果父子构造函数存在相同的成员,那么子构造函数会覆盖父构造函数中的成员
- 不能继承原型链中的成员
<script> // 需求: 子类继承父类的实例成员 function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log(this.name + "爱吃鱼"); } function Student(name,age,score){ // 方法一:函数之间调用,造成this指向window的问题,window中有name,age,score属性 // Person(name,age)//造成this指向window的问题,window中有name,age,score属性 // 方法二:重点:改变this指向 // 思路:1.先将父类构造函数赋值给子类的实例对象,2.子类实例去调用这个方法---父类内部的this指向子类的实例。3.使用完就删除 // 1.先将父类构造函数赋值给子类的实例对象 this.fn=Person // 2.子类实例去调用这个方法---父类内部的this指向子类的实例 this.fn(name,age) this.score=score // 3.用完之后就删除 delete this.fn } var s=new Student("张三",10,100) // s.eat()//报错,s.eat is not a function,因为借用构造函数继承只能继承实例成员不能继承原型成员 console.log(s) </script>
高级实现方法:凡是要借用方法,首先想到使用call或apply
<script> // 需求: 子类继承父类的实例成员 function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log(this.name + "爱吃鱼"); } function Student(name,age,score){ //方法三:使用call或apply方法 //Person.call(this,name,age) Person.apply(this,[name,age]) this.score=score } var s=new Student("张三",10,100) // s.eat()//报错,s.eat is not a function,因为借用构造函数继承只能继承实例成员不能继承原型成员 console.log(s) </script>
5.组合继承
实现原理:基原型链继承(实现原型成员继承)+借用构造函数继承(实现实例成员继承)
目的: 实现 组合继承
缺点:子类的原型对象上有无用属性,子类的原型对象中有无效数据
<script> //父类 function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log(this.name + "爱吃盖浇螺蛳面"); } // 子类 function Student(name,age,score){ // 借用构造函数---call和apply更容易实现---------------实现实例成员的继承 Person.apply(this,[name,age]) this.score=score } // 原型链继承----------实现原型成员继承 Student.prototype=new Person() Student.prototype.constructor=Student Student.prototype.getScore=function(){ console.log(this.score) } var s=new Student("张三",20,100) console.log(s) s.eat(); </script>
6.寄生组合继承
实现原理:寄生式继承 + 借用构造函数(call apply)。即组合继承的基础上,改造原本的原型链继承。子类和父类中间创建一个空类,过滤掉无用的父类实例属性。
寄生式继承--------对原型链继承的优化
思路: 创造一个中间类,过滤无效数据
script> function Person(name,age){ this.name=name this.age=age } Person.prototype.eat=function(){ console.log(this.name+"爱吃鱼") } function Student(name,age,score){ // 借用构造函数---call,apply更容易实现 Person.apply(this,[name,age]) this.score=score } //使用delete删除Super函数或者用立即执行函数删除Super函数 (function(){ // 立即执行函数:目的是只执行一次 Super 用完之后就会删除 // 函数中的变量在函数执行完毕之后 就会销毁 // 函数局部变量的生命周期-------从创建到销毁的过程 var Super=function(){} Super.prototype=Person.prototype//对象a=对象b,然后销毁对象b,对象a不会受影响 Student.prototype=new Super() Student.prototype.constructor=Student })() Student.prototype.getScore=function(){ console.log(this.score) } var s=new Student("张三",20,100) console.log(s) s.eat() </script>
总结: ES5实现继承的方式不止一种。这是因为ES5 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。
2.ES6继承的方法(一种)
7.class的继承
继承语法:(ES6中class用extends 和 super实现继承)
class 子类 extends 父类{// 类似原型链继承
constructor(name,age,score){
super(name,age);// 类似借用构造函数继承
}
}
js原则上没有类的概念,底层还是原型链,类的概念只是一个语法糖
类-----类型-----构造函数------构造器
语法:
// 需求: 使用 ES6的class类 创造一个 类 ,然后实例化一个对象
class 类名{
// 构造函数 constructor------这里面是实例成员
constructor(){}
// constructor同级的函数-----原型成员
方法(){ }
// 静态成员
static 方法(){}
}
需求:使用ES6的class实现继承
class实现继承的细节:
- Person中定义了人都应该有的属性和方法
- 使用extends关键字实现Student类继承Person类的功能,此时他们两就属于继承关系了。
- 在Student的构造方法中,使用super关键字调用父类中的构造方法。
<script> // 父类 class Person{ constructor (name,age){ this.name=name this.age=age } eat(){ console.log(this.name+"爱吃鱼") } } // 子类 class Student extends Person{ constructor(name,age,score){ super(name,age) this.score=score } getScore(){ console.log(this.score) } } var s=new Student("张三",20,100) console.log(s) s.eat() </script>
前端面试的一些常问问题、问题的具体实现(可直接运行)以及底层原理