JavaScript:继承

在JavaScript中,有很多地方都涉及到继承的使用,这样不仅可以合理的利用数据,而且可以结合实际开发情况衍生一些特定的属性,我们先定义一个类:

function Person(name) {
    // 属性
    this.name = name || 'zhangsan';
    // 实例方法
    this.sayName = function () {
        console.log('大家好,我是' + this.name);
    }
}
// 原型方法
Person.prototype.drive = function (car) {
    console.log(this.name + '正在开' + car);
};

1.原型链继承:创建一个类的实例作为继承子类的原型

function Tom() {};
Tom.prototype = new Person();
Tom.prototype.name = "Tom";

var Tom = new Tom();
console.log(Tom.name);
console.log(Tom.drive('benz'));
console.log(Tom.sayName());
console.log(Tom instanceof Person);// true
console.log(Tom instanceof Tom); // true

我们要新建一个类Tom,创建方法是Tom的prototype(显示原型)为new出来的Person,通过改变Tom显示原型上的一些属性和方法,来获得我们需要的新建类Tom。原型链继承有以下几个特点:

  • 新建的类与原始类是纯粹的继承关系,实力是子类的实例同时也是原始类(父类)的实例;
  • 原始类(父类)上新增属性和方法,继承的子类均可以访问;

但是该方式也有几个缺点:

  • 给子类添加实例属性比较麻烦,在上述新建的Tom构造函数中,必须要在new的Person之后才可执行;
  • 不能实现多继承;
  • 来自于原型对象的所有属性和方法可以被所有的实例共享;
  • 创建子类的实例的时候,不能向原始类(父类)构造函数传参;

2.构造函数继承:复制父类的实例给子类

function Jerry(name) {
    Person.call(this);
    this.name = name || 'Jerry';
}

// 使用构造函数继承
var girl = new Jerry();
console.log(girl.name);
console.log(girl.sayHello());
console.log(girl instanceof Person); //false
console.log(girl instanceof Jerry); // true

从上述示例中可以看到,构造函数的特性就是使用.call()和.apply()将父类构造函数引入子类函数,使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类。我们这里借用构造函数继承的重点就在于Person.call(this.name),调用了Person构造函数,这样,Jerry的每个实例都会将Person中的属性复制一份。构造函数有以下几个特点:

  • 解决了子类实例共享父类引用属性的问题;
  • 新建子类时可以向父类传递参数;
  • 可以在新建子类的时候继承多个父类(即使用多个call());

但是它也有几个缺点:

  • 使用构造函数所创建的实例是子类自己的实例,而不是父类的实例;
  • 只能继承父类实例上的属性和方法,无法继承父类原型的属性和方法;
  • 不能实现函数的复用,每个子类都有父类实例函数的副本,在整体上影响性能;

3.组合继承:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Park(name) {
    Person.call(this);
    this.name = name || 'park';
}
Park.prototype = new Person();

//修复构造函数指向
Park.prototype.constructor = Park;

// 使用组合继承的方式
var park = new Park();
console.log(park.name);
console.log(park.sayHello());
console.log(park instanceof Person); // true
console.log(park instanceof Park); // true

组合继承的方式就是将原型链继承构造函数继承这两种模式的优点组合在一起,通过调用父类构造,继承父类的属性并保留传参,然后通过将父类实例作为子类原型,实现函数复用。其主要思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。它的特点就很明显了:

  • 这种方式既可以继承父类实例的属性和方法,可以继承父类原型上的属性和方法,不存在引用属性共享的问题;
  • 它既是子类的实例,同时也是父类的实例;
  • 创建子类实例的时候也可以传参;
  • 函数可以复用;

但是这种方式也有缺点:

  • 实质上调用了两次父类的构造函数,生成了两份实例,因此,在使用子类创建实例对象时,其原型中会存在两份相同的属性和方法,这样会占用一定的内存;

4.实例继承:为父类实例添加新特性,作为子类实例返回

function Herry() {
    var instance = new Person();
    instance.name = name || 'Tom';
    return instance;
}

// 使用实例继承
var herry = new Herry();
console.log(herry.name);
console.log(herry.sayHello());
console.log(herry instanceof Person); // true
console.log(herry instanceof Herry); // false

特点在于:

  • 不限制调用方式,可以new一个子类,也可以直接使用子类,二者调用方式返回的对象的效果一样;

但是缺点也很明显:

  • 它所构建的实例实际上是父类的实例,而不是子类自己的实例;
  • 不能实现多继承;

5.拷贝继承

function Cap(name) {
    var person = new Person();
    // 遍历拷贝属性
    for (var p in person) {
        Cap.prototype[p] = person[p];
    }
    Cap.prototype.name = name || 'Tom';
}

// 使用拷贝的方式继承
var cap = new Cap();
console.log(cap.name);
console.log(cap.sayHello());
console.log(cap instanceof Person); // false
console.log(cap instanceof Cap); // true

看上述示例就可以看到,这种方式简单粗暴,复制父类的属性和方法,它的特点就是:

  • 可以支持多继承

但是缺点也比较明显:

  • 因为要执行拷贝的缘故,所以运行效率比较低,而且会占用过高的内存;
  • 无法获取父类中不可枚举的方法,例如利用symbol()标注的等;

6.寄生式继承:这种方式是来创建一个用于封装继承过程的函数,相当于一个流水线,在这个函数的内部以某种方式来增强对象,最后返回一个构造函数

function createAnother(original) {
    varclone = object(original); // 过调用函数创建一个新对象
    clone.sayHi = function () { // 以某种方式增强这个对象
        alert("hi");
    };
    return clone; // 返回对象
}

// 函数的主要作用是为构造函数新增属性和方法,以增强函数
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

这种方法的缺点就是:

  • 原型链内继承多个实例的引用类型的属性指向是相同的,有一定被篡改的可能;
  • 不能传递参数,没有用到原型,也没有办法复用;

7.寄生组合式继承:通过上述寄生的方式,再砍掉父类的实例属性,这样可以再调用两次父类的构造的时候,就不会初始化两次实例的方法和属性

function Bean(name) {
    Person.call(this);
    this.name = name || 'bean';
}
(function () {
    // 创建一个没有实例方法的类
    var Cooker = function () {};
    Cooker.prototype = Person.prototype;
    //将实例作为子类的原型
    Bean.prototype = new Cooker();
})();

// 寄生组合式继承
var bean = new Bean();
console.log(bean.name);
console.log(bean.sayHello());
console.log(bean instanceof Person); // true
console.log(bean instanceof Bean); //true

Bean.prototype.constructor = Bean; // 需要修复下构造函数

这种寄生组合式继承的特点就是----金星老师的招牌动作----完美!!!!!!但是也有一点瑕疵,就是实现起来比较麻烦。

 

好了,各位前辈同僚,JavaScript中的一些继承方式就介绍到这里,还有其它的方式,平时没用上,所以不加介绍了,如果有不足之处希望大家指出来,当然了,有更好的方法,也希望大家分享,一起学习,一起进步!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值