JavaScript - 原型/原型链 & 继承

109 阅读3分钟

原型 && 原型链

‌ 原型:   在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象

 原型链: 函数的原型链对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用__proto__一直指向Object的原型对象上,而Object原型对象用Object.prototype.__ proto__ = null表示原型链顶端。如此形成了js的原型链继承。同时所有的js对象都有Object的基本防范

原型关系:

  • 每个对象构造器都有显示原型 prototype
  • 每个实例都有隐式原型 _ proto_
  • 实例的_ proto_指向对应对象构造器的 prototype

特点:   JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

继承

原型链继承

要点:父类的实例赋值给子类的原型

缺点:

  • 引用类型的属性被所有实例共享
  • 在创建子类型的实例时,不能向超类的构造函数中个传递参数

示例:

function Parent () {
    this.names = ['kevin', 'daisy'];
}

function Child () {

}

Child.prototype = new Parent();


var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy", "yayu"]

构造函数继承

要点:使用父类的构造函数call执行在子类内部,来增强子类实例

优点:

  • 可以在子类构造函数中向父类传参数
  • 父类的引用属性不会被共享

缺点:

  • 子类上面存在非必要的属性
  • 无法函数复用(方法都在构造函数中定义,每次创建实例都会创建一遍方法),浪费性能
  • 实例只是子类的实例

示例:

function Father() {
    this.name = 'elvin';
}
function Son() {
    Father.call(this)
}
let son = new Son()
console.log(son instanceof Son)     //true
console.log(son instanceof Father)  //false

组合继承

要点:父类的实例赋值给子类的

优点:

  • 父类的方法可以复用
  • 可以在Child构造函数中向Parent构造函数中传参
  • 父类构造函数中的引用属性不会被共享

缺点:

  • 两次副本,影响性能
    • 第一次调用SuperType():给子类原型写入两个属性name,color。
    • 第二次调用SuperType():给子类实例写入两个属性name,color。

示例:

function SuperType (name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
    alert(this.name);
};

function SubType (name, age) {
    // 继承属性
    // 第二次调用SuperType()
    SuperType.call(this, name);
    this.age = age;
}

// 继承方法
// 构建原型链
// 第一次调用SuperType()
SubType.prototype = new SuperType();
// 重写SubType.prototype的constructor属性,指向自己的构造函数SubType
SubType.prototype.constructor = SubType; 

原型式继承

要点:借助了原型可以基于已有的对象创建新对象,而且还不必为此去创建自定义的类型

优点:

缺点:

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  • 无法传递参数

示例:

var person = {
    name:'A',
    colors:['green','red','yello']
}
var p1 = Object.create(person)
var p2 = Object.create(person)

寄生式继承

要点:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象.

缺点:

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  • 无法传递参数
  • 由于不能做到函数复用而降低效率

示例:

function createPerson(origin) {
    var clone = Object.create(origin)
    clone.sayGood = function () {
        console.log('hello world');
    }
    return clone;
}

寄生组合式继承

要点:构造函数传递参数和寄生模式实现继承

优点:

只调用了一次 SuperType 构造函数,并且因此避免了在 SuperType.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变

示例:


function SuperType(name){
    this.name = name
    this.colors = ['red','yellow']
}

superType.prototype.sayName = function () {
    console.log(this.name);
    
}

// 创建超类原型的一个副本
var anotherPrototype = Object.create(SuperType.prototype)
anotherPrototype.constructor = SubType
SubType.prototype = anotherPrototype


function SubType(name,age){
    SuperType.call(this.name)
    this.age = age;
}
SubType.prototype.sayAge = function(){
    console.log(this.age);
}