JavaScript中的继承方式详细解析

// 父类
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function(){
    console.log(this.name);
}

// 子类
function Child(name, age) {
    Parent.call(this, name); // 在子类构造函数中调用父类构造函数,传入子类实例(this)和参数
    this.age = age;
}

// 创建子类实例
var child1 = new Child('John', 10);
var child2 = new Child('Jane', 12);

// 修改子类实例属性
child1.colors.push('black');

console.log(child1.name); // 输出: John
console.log(child1.age); // 输出: 10
console.log(child1.colors); // 输出: ["red", "blue", "green", "black"]

console.log(child2.name); // 输出: Jane
console.log(child2.age); // 输出: 12
console.log(child2.colors); // 输出: ["red", "blue", "green"]

child1.sayName()//报错


可以从上面代码中发现,父类原型上定义的方法,子类是无法继承这些方法的。相比于第一种原型链继承的方法,构造函数继承的方式的父类应用不会被共享,优化了第一种继承方式的弊端。但是缺点也很明显,即不能继承原型属性或者方法。

构造函数继承小结:

优点:

  • 可以向父类传递参数:子类可以通过在构造函数中调用父类构造函数来传递参数
  • 没有共享属性:每个实例都有自己的属性,不会互相影响

缺点:

  • 无法继承方法:子类无法继承父类原型中的方法,导致方法无法复用
  • 造成内存浪费:每个实例都会单独拥有一份方法的副本,造成内存浪费
  • 无法形成真正的原型链,导致无法使用原型链上的一些方法和属性

组合继承

组合继承结合了原型链继承和构造函数继承的优点,两者结合,避免了两者的缺点。通过组合继承,我们可以实现实例属性的独立性,同时实现方法的共享,提高了代码的可维护性和复用性。

// 父类
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function(){
    console.log(this.name);
}
// 子类
function Child(name, age) {
    Parent.call(this, name); // 在子类构造函数中调用父类构造函数,传入子类实例(this)和参数
    this.age = age;
}

Child.prototype = new Parent(); // 子类的原型指向父类的实例
Child.prototype.constructor = Child; // 修正子类的构造函数为自己

var s1 = new Child('hzz11', 18);
var s2 = new Child('hzz22',22);
console.log(s1)
console.log(s1.name); // hzz11
s1.colors.push('black');

console.log(s1.colors); // ["red", "blue", "green", "black"]
console.log(s2.colors); // ["red", "blue", "green"]
s1.sayName(); // hzz11
s2.sayName(); // hzz22

这种方式看上去觉得还行,似乎没什么问题的样子。但是仔细观察你会发现,上面代码中的Parent执行了两次(Parent.call(this, name)、new Parent()),造成了多构造一次的性能开销。

组合式继承小结:

优点:

  • 结合了构造函数和原型链继承的优点:通过构造函数实现实例属性,通过原型链继承共享方法,既够传递参数,又能实现方法的复用
  • 方法共享:子类实例共享父类原型中的方法,节省内存

缺点:

  • 重复的调用构造函数:在创建子类实例时,会调用两次父类构造函数,一次是在原型链继承时,一次是在构造函数继承时,可能会导致一些不必要的性能开销。

原型式继承

原型式继承是通过浅复制一个对象来创建一个新的对象,并将新对象的原型指向这个被复制的对象。这样新对象就能够继承被复制对象的属性和方法

// 原型对象
var person = {
    name: 'John',
    age: 30,
    sayHello: function() {
        console.log('Hello, my name is ' + this.name);
    }
};

// 创建一个继承自 person 的新对象
var john = Object.create(person);

console.log(john.name); // 输出: John
console.log(john.age); // 输出: 30
john.sayHello(); // 输出: Hello, my name is John

Object.create()方法说明:Object.create()的作用是创建一个新的对象,并将该对象的原型链指向另外一个对象或者null

  1. 创建新对象Object.create() 可以创建一个新的对象,该对象继承了指定的原型对象的属性和方法。
  2. 指定原型链:通过指定一个对象作为参数,新创建的对象将会继承该对象的属性和方法,并且其原型链将指向这个对象。
  3. 继承属性和方法:新创建的对象将会继承指定对象的属性和方法,包括原型链上的属性和方法。
  4. 属性描述符继承:新创建的对象会继承指定对象的属性的属性描述符(比如可枚举、可写、可配置等)。
  5. 创建原型链终点:当传入 null 作为参数时,将创建一个没有原型链的对象,这个对象将不会继承任何属性和方法,相当于创建了原型链的终点。

语法:

Object.create(proto[, propertiesObject])

其中,proto 参数是新对象的原型对象,可以是 null 或者一个对象。propertiesObject 是可选参数,用于定义额外的属性,该参数的对象属性将被添加到新创建的对象中,属性描述符则将与 Object.defineProperty() 方法的功能一致。

下面是模拟Object.create()方法的实现:

function createObject (parent) {
  function F () { } // 创建一个临时构造函数
  F.prototype = parent; // 将临时构造函数的原型指向 parent 对象
  return new F(); // 返回新对象
}
原型式继承小结:

优点:

  • 简单灵活:使用方便,可以快速创建对象
  • 可以通过原型链并进行属性和方法的复用

缺点:

  • 共享引用类型属性:如果一个对象的引用类型属性被修改,会影响到所有继承自同一个原型的对象

寄生式继承

寄生式继承是在原型式继承的基础上增加了对象,返回一个增强后的对象。这种模式在原有对象的基础上添加额外的方法或者属性,从而实现继承

  • 原型式继承的基础:在原型式继承中,我们通过浅复制一个对象来创建一个新对象,新对象的原型链指向被复制的对象。这样,新对象就继承了被复制对象的属性和方法。
  • 增强对象:在寄生式继承中,我们不仅创建了一个新对象,还对这个新对象进行了增强。这个增强可以包括添加新的方法、修改已有方法等。
  • 返回增强后的对象:最后,我们将这个增强后的对象返回,以供使用者使用。
// 寄生式继承函数
function createChild(parent) {
    var child = Object.create(parent); // 基于原型式继承创建一个对象
    child.sayHello = function() {      // 增加额外的方法
        console.log('Hello from Child');
    };
    return child;                      // 返回增强后的对象
}

// 原型对象
var parent = {
    name: 'Parent',
    sayName: function() {
        console.log('My name is ' + this.name);
    }
};

// 创建一个继承自 parent 的新对象
var child = createChild(parent);

console.log(child.name); // 输出: Parent
child.sayName();         // 输出: My name is Parent
child.sayHello();        // 输出: Hello from Child

其优缺点也很明显,跟上面讲的原型式继承一样

寄生组合式继承

寄生组合式继承是JavaScript中一种高效的继承模式,它结合了寄生式和组合式继承的优点。寄生组合式继承的核心思想是在子类构造函数中调用父类构造函数,通过寄生式继承来继承父类的原型,以实现方法的共享和实力属性的对立性

// 父类
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

// 父类原型方法
Parent.prototype.sayHello = function() {
    console.log('Hello from ' + this.name);
};

// 子类
function Child(name, age) {
    Parent.call(this, name); // 构造函数继承,继承实例属性
    this.age = age;
}

// 使用寄生式继承继承父类原型
function inheritPrototype(child, parent) {
    var prototype = Object.create(parent.prototype); // 创建父类原型的副本
    prototype.constructor = child; // 修正构造函数指向
    child.prototype = prototype; // 将子类原型指向父类原型的副本
}

// 将子类原型继承父类
inheritPrototype(Child, Parent);

// 创建子类实例
var child1 = new Child('John', 10);
var child2 = new Child('Jane', 12);

// 修改子类实例属性
child1.colors.push('black');

console.log(child1.name); // 输出: John
console.log(child1.age); // 输出: 10
console.log(child1.colors); // 输出: ["red", "blue", "green", "black"]

console.log(child2.name); // 输出: Jane
console.log(child2.age); // 输出: 12
console.log(child2.colors); // 输出: ["red", "blue", "green"]

child1.sayHello(); // 输出: Hello from John
child2.sayHello(); // 输出: Hello from Jane

在这个示例中,Parent 是父类构造函数,Child 是子类构造函数。通过 Parent.call(this, name) 实现了构造函数继承,继承了父类的实例属性。然后,通过 inheritPrototype(Child, Parent) 函数实现了寄生式继承,将子类原型继承父类原型的副本,从而实现了方法的共享。最后,我们创建了两个子类实例 child1child2,它们分别拥有独立的实例属性和共享的方法。

寄生组合式继承小结

优点:

  • 实现了方法的共享和实力属性的独立性,避免了构造函数继承和原型链继承的缺点
  • 避免了构造函数继承是调用两次父类构造函数的性能开销

extends继承

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值