文章目录
js中的八种继承方法,原型链继承、构造函数继承(经典继承)、组合面试、原型式继承、Object.create()、寄生式继承、寄生组合式继承、ES6类继承
原型(对象属性)
每一个函数都有一个prototype对象属性,指向原型对象(原型链上面的)。
prototype(对象属性)的所有属性和方法,都会被构造函数的实例继承。
prototype(对象属性)可以让所有对象实例共享它所包含的属性和方法
原型链 (JS原型与原型链继承)
实例对象与原型之间的连接,叫做原型链。proto( 隐式连接 )
JS在创建对象的时候,都有一个叫做proto的内置属性,用于指向创建它的函数对象的原型对象prototype。
1、每个对象都有一个proto属性,原型链上的对象正是依靠这个属性连结在一起
2、作为一个对象,当你访问其中的一个属性或方法的时候,如果这个对象中没有这个 方法或属性,那么Javascript引擎将会访问这个对象的proto属性所指向上一个对 象,并在那个对象中查找指定的方法或属性,如果不能找到,那就会继续通过那个对象 的proto属性指向的对象进行向上递归查找,直到这个链表结束。
原型的作用:
1.数据共享
2.实现继承
注意:函数是一个对象,对象不一定是一个函数,对象有_proto_属性,函数有prototype属性。
js中的八种继承方法,原型链继承、构造函数继承(经典继承)、组合面试、原型式继承、Object.create()、寄生式继承、寄生组合式继承、ES6类继承
1. 原型链继承:
原型链继承是JavaScript中最基本的继承方式。每个对象都有一个原型对象,通过原型链将属性和方法沿着对象链传递下来。在原型链继承中,通过将子构造函数的原型对象指向父构造函数的实例,实现了继承。这意味着子对象可以访问父对象原型链上的属性和方法。
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello');
};
function Child() {
this.name = 'Child';
}
Child.prototype = new Parent();
var child = new Child();
child.sayHello(); // Hello
在这个例子中,Child对象的原型对象被设置为Parent的一个实例。这样,当我们调用child.sayHello()时,它会首先在子对象上查找sayHello方法,然后在父对象原型链上找到并执行该方法。
注意:原型链继承的缺点是,所有子对象共享同一个原型对象,对原型对象的修改会影响到所有子对象。
2. 构造函数继承(经典继承):
构造函数继承是通过在子构造函数中调用父构造函数来实现继承。在构造函数继承中,通过在子构造函数中使用**call()或apply()**方法,将父构造函数的上下文设置为子对象的上下文,从而实现继承。
function Parent(name) {
this.name = name;
}
function Child(name) {
Parent.call(this, name);
}
var child = new Child('Child');
console.log(child.name); // Child
在这个例子中,我们通过在Child构造函数内部调用Parent构造函数,并传递子对象特定的参数,实现了继承。
注意:构造函数继承只能继承父构造函数的实例属性,无法继承父构造函数的原型对象上的方法。
3. 组合继承:
组合继承结合了原型链继承和构造函数继承,既继承了父构造函数的属性,又继承了父构造函数原型对象上的方法。在组合继承中,通过调用父构造函数的方式实现属性的继承,通过将子构造函数的原型对象指向父构造函数的实例实现方法的继承。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello');
};
function Child(name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
var child = new Child('Child');
child.sayHello(); // Hello
在这个例子中,我们通过调用**Parent.call(this, name)来继承父构造函数的属性,并通过Child.prototype = new Parent()**将子构造函数的原型对象指向父构造函数的实例,从而实现方法的继承。组合继承的优点是既能够继承父构造函数的属性,又能够继承父构造函数原型对象上的方法。
缺点:在创建子对象时会调用两次父构造函数,一次是在设置原型时,一次是在创建子对象时。这样会产生一些不必要的开销。
4. 原型式继承:
原型式继承是通过使用一个临时构造函数和**Object.create()**方法来实现继承。
var parent = {
name: 'Parent',
sayHello: function() {
console.log('Hello');
}
};
var child = Object.create(parent);
console.log(child.name); // Parent
child.sayHello(); // Hello
在这个例子中,我们创建了一个parent对象,然后使用**Object.create()**方法创建了一个新对象child,并将其原型对象指向parent对象,实现了继承。原型式继承的本质是创建一个新对象,将其原型对象指向另一个已有的对象。这种方式可以实现属性和方法的继承,但是不能传递构造函数的参数。
5. Object.create()
Object.create() 是把现有对象的属性,挂到新建对象的原型上,新建对象为空对象
ECMAScript 5通过增加Object.create()方法将原型式继承的概念规范化了。这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)。在只有一个参数时,Object.create()与这里的函数A方法效果相同。
let person = {
name: 'mjy',
age: 19,
hoby: ['唱', '跳'],
showName() {
console.log('my name is: ', this.name)
}
}
let child1 = Object.create(person)
child1.name = 'xxt'
child1.hoby.push('rap')
let child2 = Object.create(person)
console.log(child1) //{name:"xxt"}
console.log(child2) //{}
console.log(person.hoby) // ['唱', '跳', 'rap']
优点: 不需要单独创建构造函数。
缺点: 属性中包含的引用值始终会在相关对象间共享,子类实例不能向父类传参
6. 寄生式继承
寄生式继承的思路与(寄生) 原型式继承
和 工厂模式
似, 即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}
function createAnother(obj) {
let clone = objectCopy(obj);
clone.showName = function () {
console.log('my name is:', this.name);
};
return clone;
}
let person = {
name: "mjy",
age: 18,
hoby: ['唱', '跳']
}
let child1 = createAnother(person);
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
child1.showName(); // my name is: mjy
let child2 = createAnother(person);
console.log(child2.hoby); // ['唱', '跳', 'rap']
优点: 写法简单,不需要单独创建构造函数。
缺点: 通过寄生式继承给对象添加函数会导致函数难以重用。使用寄生式继承来为对象添加函数, 会由于不能做到函数复用而降低效率;这一点与构造函数模式类似.
7. 寄生组合式继承
前面讲过,组合继承是常用的经典继承模式,不过,组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数;一次是在创建子类型的时候,一次是在子类型的构造函数内部。寄生组合继承就是为了降低父类构造函数的开销而实现的。
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}
function inheritPrototype(child, parent) {
let prototype = objectCopy(parent.prototype);
prototype.constructor = child;
Child.prototype = prototype;
}
function Parent(name) {
this.name = name;
this.hoby = ['唱', '跳']
}
Parent.prototype.showName = function () {
console.log('my name is:', this.name);
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.showAge = function () {
console.log('my age is:', this.age);
}
let child1 = new Child("mjy", 18);
child1.showAge(); // 18
child1.showName(); // mjy
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
let child2 = new Child("yl", 18);
child2.showAge(); // 18
child2.showName(); // yl
console.log(child2.hoby); // ['唱', '跳']
优点: 高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变;
缺点: 代码复杂
8. ES6类继承:
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello');
}
}
class Child extends Parent {
constructor(name) {
super(name);
}
}
const child = new Child('Child');
console.log(child.name); // Child
child.sayHello(); // Hello
在这个例子中,我们定义了一个Parent类,通过extends关键字实现子类Child对父类Parent的继承。子类使用super关键字调用父类的构造函数,并可以访问父类的属性和方法。ES6类继承提供了更加语法简洁和面向对象的继承方式。