JavaScript 继承优缺点详解

JavaScript 继承

一,构造函数继承(对象冒充继承)

原理:调用父类构造函数,并改变其中的this指向(bind,call,apply)

局限:只继承了父类构造函数的属性,没有继承父类原型的属性

//创建一个猫的构造函数
function Cat(name, color) {
    this.name = name;
    this.color = color;
    this.eating = function () {
        console.log('吃东西');
    }
}
Cat.prototype.shopping = function () {
    console.log('购物');
}
function Pig(){
    this.sleep=function(){
        console.log('睡觉');
    }
}
//狗类
function Dog(name, color, food) {
    this.food = food;
    //狗冒充猫,访问猫的属性或方法 (改变this指向)
    Cat.call(this, name, color);
    Pig.call(this);
}
var dog1=new Dog('大黄','黄','骨头');
console.log(dog1.name);//大黄
console.log(dog1.color);//黄
dog1.sleep();//睡觉
dog1.eating();//吃东西
dog1.shopping();//报错,无法继承父类原型上的方法
1.优点
  • 可以继承多个构造函数的属性或方法(call多个)
  • 在子实例中
2.缺点
  • 只能继承父类构造函数的属性
  • 每个新实例都有父类构造函数的副本,特别臃肿(根本原因是不能继承prototype上的属性或方法)
  • 无法实现构造函数的复用.(每次用每次都要重新调用)

二.原型链继承

让新实例的原型等于父类的实例,实现原型链继承

//创建一个猫的构造函数
function Cat(name, color) {
    this.name = name;
    this.color = color;
    this.eating = function () {
        console.log('吃东西');
    }
}
Cat.prototype.shopping = function () {
    console.log('购物');
}
//狗类
function Dog(name, color, food) {
    this.food = food;
}
//原型链继承
Dog.prototype=new Cat();//把狗的原型指向猫的实例对象
var dog1=new Dog('大黄','黄','骨头');
console.log(dog1.name);//undefined
console.log(dog1.color);//undefined
dog1.shopping();//睡觉
dog1.eating();//吃东西
1.优点
  • 实例可以继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性(新实例不会继承父类实例的属性!)
2.缺点
  • 新实例无法向父类构造哈数传参
  • 继承单一,不能实现多继承
  • 所有新实例都会共享父类实例的属性.(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型实例属性也会被修改)

三.组合继承(常用)

原理:组合原型链继承和借用构造函数继承,结合了两种模式的优点.传参和复用(常用的一种继承方式)

//创建一个猫的构造函数
function Cat(name, color) {
    this.name = name;
    this.color = color;
    this.eating = function () {
        console.log('吃东西');
    }
}
Cat.prototype.shopping = function () {
    console.log('购物');
}
//狗类
function Dog(name, color, food) {
    this.food = food;
    //构造函数继承
    Cat.call(this, name, color);
}
//原型链继承
Dog.prototype = new Cat();
var dog1 = new Dog('大黄', '黄', '骨头');
console.log(dog1.name);//大黄
console.log(dog1.color);//黄
dog1.shopping();//睡觉
dog1.eating();//吃东西
1.优点
  • 可以继承父类原型上的属性,可以传参,可以复用
  • 每个新实例引入的构造函数属性是私有的.
2.缺点
  • 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数

四.拷贝继承

通过改变新实例对象的指针指向被继承的实例对象的地址,达到继承的效果

通过遍历复制前一个对象的属性和方法达到拷贝的效果

方法一:

//创建一个猫的构造函数
var Cat = {
    name: "大黄",
    color: "黄色",
    eating: function () {
        console.log('吃东西');
    }
}
//将猫的属性给狗  实际上就是浅拷贝赋值
var Dog = Cat;
console.log(Dog.name);//大黄
console.log(Dog.color);//黄色
Dog.eating();//吃东西

方法二:

// 创建父对象
var superObj = {
  name: 'Li',
  age: 25,
  friends: ['小明', '小李', '小赵'],
  showName: function(){
    alert(this.name);
  }
}

// 创建需要继承的子对象
var subObj = {};

// 开始拷贝属性(使用for...in...循环)
for( var i in superObj ){
  subObj[i] = superObj[i];
}

console.log(subObj)
console.log(superObj)
1.优点:
  • 支持多继承
2.缺点
  • 效率较低,内存占用高(因为要拷贝父类的属性)
  • 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
  • 如果继承过来的成员是引用类型的话,那么这个引用类型的成员在父对象和子对象之间是共享的,也就是说修改了之后,父子对象都会受到影响。(实际上浅拷贝原理)

五.原型式继承

用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。

就是给原型式继承外面套了个壳子。

方法一:

// 原型式继承
function createAnother(obj) {
    function F() {
    }
    F.prototype = obj;//继承传入的参数
    //返回函数对象
    return new F();
}
var person = {
    name: 'jia',
    age: 18,
    job: '打杂的',
    show: function () {
        console.log('显示');
    }
};
var anotherPerson = createAnother(person);
console.log(anotherPerson.name);//jia
console.log(anotherPerson.job);//打杂的
anotherPerson.show();//显示

方法二:

Object.create()接收的第二个参数,是为新对象定义额外的属性,指定的任何属性都会覆盖原型上的同名属性。

//创建一个猫的构造函数
var Cat = {
    name: '大黄',
    color: '黄色',
    eating: function () {
        console.log('吃东西');
    },
    getName: function () {
        return this.name;
    }
}
var dog = Object.create(Cat, {
    name: {
        value: '大黑'
    },
    color: {
        value: '黑色'
    }
});
console.log(dog.name);//大黑
console.log(dog.color);//黑色
dog.eating();//吃东西
1.优点
  • 兼容性好,最简单的对象继承
2.缺点
  • 所有实例都会继承原型上的属性
  • 无法实现复用(新实例属性都是后面添加的)

六.实例继承

为父类实例添加新特性,作为子类实例返回

//创建一个猫的构造函数
function Cat(name, color) {
    this.name = name;
    this.color = color;
    this.eating = function () {
        console.log('吃东西');
    }
    this.getColor=function(){
       console.log(this.color);
    }
}
function Dog() {
    var instance = new Cat();
    instance.name = "大黄";
    return instance;
}
var dog1 = new Dog();
console.log(dog1.name);
dog1.color = "黄色";
dog1.getColor();
dog1.eating();//吃东西
1.优点
  • 不限调用方式,不管是 new 子类()还是子类() 返回的对象具有相同的效果
2.缺点
  • 实例是父类的实例,不是子类的实例
  • 不支持多继承

七.寄生继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象

使用场景:专门为对象来做某种固定方式的增强。

// 原型式继承
function objectM(obj) {
    function F() {
    }
    F.prototype = obj;//继承传入的参数
    //返回函数对象
    return new F();
}
//寄生式继承,给原型式继承套个壳子
function createAnother(obj) {
    var clone = objectM(obj);//通过调用函数创建一个新
    clone.sayHello = function () {//给新对象添加方法
        console.log("hello");
    }
    return clone;
}
var person = {
    name: '大黄',
    age: 18,
    job: '吃屎的',
    show: function () {
        console.log('显示');
    }
};
var anotherPerson = createAnother(person);
console.log(anotherPerson.name);//jia
console.log(anotherPerson.job);//吃屎的
anotherPerson.sayHello();//hello
anotherPerson.show();//显示
1.优点
  • 没有创建自定义类型,因为只是套了个壳子返回对象,这个函数顺利成章的就成了创建的新对象
2.缺点
  • 不能做到函数复用,即不能继承原型的方法

八.寄生组合继承(常用)

集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式

比较完美的解决方式,不过就是实现复杂

// 定义一个动物类
function Animal(name) {
    // 属性
    this.name = name || 'Animal';
    // 实例方法
    this.sleep = function () {
        console.log(this.name + '正在睡觉!');
    }
}
// 原型方法
Animal.prototype.eat = function (food) {
    console.log(this.name + '正在吃:' + food);
};
function Cat(name) {
    Animal.call(this);
    this.name = name || 'Tom';
}
(function () {
    // 创建一个没有实例方法的类
    var Super = function () { };
    Super.prototype = Animal.prototype;
    //将实例作为子类的原型
    Cat.prototype = new Super();
    Cat.prototype.constructor = Cat; // 需要修复下构造函数
})();
var cat = new Cat("大黑");
console.log(cat.name);//大黑
cat.sleep();//大黑正在睡觉!
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
1.优点
  • 可以继承实例属性/方法,也可以继承原型属性/方法
  • 即是之类的实例,也是父类的实例
  • 不存在引用属性共享问题
  • 可传参
  • 函数可复用
2.缺点
  • 实现起来比复杂

九.ES6 Class继承(不用想了,我就要用这个,妥妥的)

ES6继承的原理跟寄生组合式继承是一样的。但是Class是语法糖,写起来甜甜的

ES5继承与ES6继承的区别:

  • ES5的继承实质上是先创建子类的实例对象,再将父类的方法添加到this上

  • ES6的继承是先创建父类的实例对象this,再用子类的构造函数修改this

    因为子类没有自己的this对象,所以必须先调用父类的super()方法。

class Person{
    constructor(name, age){
        this.name=name;
        this.age=age;
    }
    show(){
        alert(this.name);
        alert(this.age);
    }
}

class Worker extends Person{
    constructor(name, age, job){
        super(name, age);
        this.job=job;
    }
    showJob(){
        alert(this.job);
    }
}
let me=new Worker('jia', 18, '前端攻城狮');
me.show();//jia
me.showJob();//前端攻城狮
1.优点
  • 原理还是参照寄生组合继承,基本原理是一样,语法糖,写起来方便,比较完美
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值