js继承的几种方式
继承是面向对象技术当中的一个概念。这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承在js中占有非常重要的地位,那么在js中有很多中继承的方式,不过每一种继承方式都有优缺点。下面就列举几种继承的方式。
1.构造函数继承
实现方式 :使用call等方法 使一个对象的this指向另一个对象 从而让其拥有这个对象的属性和方法在将来创建的新对象里执行
super.call(this)//其实这个this指的就是当前的对象,
//因为用new 构造出来的实例,会把this指向该实例本身,所以
function Father(name) {
this.name=name
}
function Child(age) {
Father.call(this,'小黄')
this.age=age;
}
let child=new Child(19)
console.log(child);//Child {name: "小黄", age: 19};
优点:
创建子类实例时,可以向父类传递参数
可以实现多继承(call多个父类对象)
缺点 : 无法继承父类原型上面的方法 对于子类不需要的方法 每次创建都会拥有 浪费内存空间
2.原型链继承
**核心:**父类的实例作为子类的原型
function Animal() {
this.color=['red','blue']
}
Animal.prototype.eat=function(){
console.log('eat')
};
function Dog(){
}
Dog.prototype=new Animal();
let dog=new Dog();
console.log(dog.color);
dog.eat(); //eat
优点:
简单易于实现,父类的新增的实例与属性子类都能访问
缺点:
可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面
无法实现多继承
创建子类实例时,不能向父类构造函数中传参数
3.原型拷贝继承(混入式继承)
实现方式 :使用for-in遍历父类原型里面的所有属性,依次加入子类的原型中
function Parent(name, age){
this.name = name;
this.age = age;
this.work = function(){
console.log("我要赚钱养家");
}
}
Parent.prototype.test = function(){
console.log(111);
}
Parent.prototype.type = "人类";
function Child(name, age){
this.name = name;
this.age = age;
}
// 原型对象拷贝
for(var key in Parent.prototype){
Child.prototype[key] = Parent.prototype[key];
console.log(Parent.prototype[key]);//function(){console.log(111);}
}
var c1 = new Child("zhangsan", 12)
c1.test();
console.log(c1.type); //人类
缺点 :原型中子类不需要的方法或者属性也继承过来了 占用内存空间
优点 : 支持多继承
4.实例继承(原型式继承)
function Wonman(name){
let instance = new People();
instance.name = name || 'wangxiaoxia';
return instance;
}
let wonmanObj = new Wonman();
优点:
不限制调用方式
简单,易实现
缺点:不能多次继承
5.组合式继承
也叫伪经典继承。指的是将原型链和借用构造函数的技术组合到一起,从而发挥二者之长。
调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用
function Father(name) {
this.name=name
}
Father.prototype.sayName=function () {
console.log(this.name);
}
function Child(name,age) {
Father.call(this,name)
this.age=age;
}
Child.prototype=new Father();
Child.prototype.sayAge=function(){
console.log(this.age);
}
let child1=new Child('xiao',13);
child1.sayName();
child1.sayAge()
let child2=new Child('big',14);
child2.sayName()
child2.sayAge();
缺点:
由于调用了两次父类,所以产生了两份实例
优点:
既通过在原型上定义方法实现了函数复用,又能保证每一个实例都有它自己的数组。
6.寄生组合继承
是JavaScript最常用的继承模式。
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
function createAnother(original) {
var clone = object(original); // 通过调用 object() 函数创建一个新对象
clone.sayHi = function() {
// 以某种方式来增强对象
console.log("hi");
};
return clone; // 返回这个对象
}
// 函数的主要作用是为构造函数新增属性和方法,以增强函数
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"```
7…ES5 和 ES6继承
ES6继承
实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。
ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象
/*
class是个关键词,语言糖,这样能更清晰的读懂所创建的对象,
通过属性constructor来接收控制方法传入的参数,如果不写这个属性,默认是没有参数的
es5中constructor为隐式属性
class中定义方法时,前后不能加function,全部定义在class的protopyte属性中
class中定义的所有方法是不可枚举的
class中只能定义方法,不能定义对象,变量等
*/
class People{
constructor(name='wang',age='27'){
this.name = name;
this.age = age;
}
eat(){
console.log(`${this.name} ${this.age} eat food`)
}
}
//继承父类
class Woman extends People{
constructor(name = 'ren',age = '27'){
//继承父类属性
super(name, age);
}
eat(){
//继承父类方法
super.eat()
}
}
let wonmanObj=new Woman('xiaoxiami');
wonmanObj.eat();
ES5继承
ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上
ES5 通过新增 Object.create()方法规范化了原型式继承,此方法可以接受两个参数,第一个参数最为新对象原型的对象 和一个为新对象定义额外属性的对象
var person = {
name: "kebi",
friends: ["kuli", "hadeng"]
};
var onePerson = Object.create(person, {
name: "heyushuo"
});
onePerson.friends.push("heyushuo");
var twoPerson = Object.create(person, {
name: "yaoming"
});
twoPerson.friends.push("yaoming");
//这里打印
console.log(twoPerson); //['kuli','hadeng','heyushuo','yaoming']
// 主:在没有必要创建构造函数,而是指向让一个对象与另外一个对象保持类似的情况下,原型式继承完全可以胜任
ES5继承和ES6继承的区别
1.ES5先创建子类,在实例化父类并添加到子类this中
2.ES6先创建父类,在实例化子集中通过调用super方法访问父级后,在通过修改this实现继承