JavaScript继承

1、原型链继承
基本思想:让一个引用类型继承另一个引用类型的属性和方法;
构造函数、原型和实例的关系:
每个构造函数都有一个prototype属性,该属性指向该函数的原型对象;原型对象包含一个不可枚举的属性constructor,该属性指向它们的构造函数;当调用构造函数创建一个新实例之后,该实例内部包含一个内部指针proto ,指向该构造函数的原型对象。

//创建一个构造函数
function SubType(){ }
//构造函数存在prototype属性指向原型对象
SubType.prototype = SubType Prototype;
//实例化对象
var instance = new SubType();
//实例对象存在内部指针_proto_指向该构造函数的原型对象
instance._proto_ = SubType.prototype;
//原型对象存在constructor属性指向构造函数
SubType.prototype.constructor = SubType();

原型链继承的例子

function Father(){
    this.colors = ['red','yellow','blue'];
    this.age = 18;
    this.name = "lee";
}
Father.prototype.money = 1000000;
Father.prototype.house = 4;
function Son(){
}
//父类型实例化后代对象赋值给子类型的原型属性,重写了子类型的原型对象,
//new Father()会将Father构造里的信息和原型里的信息都交给Son
Son.prototype = new Father();
//实例化对象
var son = new Son();
var son2 = new Son();
//实例son改变引用类型colors
son.colors.push('black');
console.log(son.age);//18
console.log(son.money);//1000000
//引用类型值的原型属性被所有实例共享,如下,改变了son的colors,son1的colors也被改变
console.log(son.colors);//["red", "yellow", "blue", "black"]
console.log(son2.colors);//["red", "yellow", "blue", "black"]

问题:
(1)、引用类型值的原型属性被共享
(2)、过多的继承了没有用的属性
(3)、不能向父类型传递参数

2、借用构造函数

function Father(age,name){
    this.colors = ['red','yellow','blue'];
    this.age = age;
    this.name = name;
    this.say=function(){
        console.log(this.name);
    }
}
Father.prototype.money = 100000;
function Son(age,name){
    Father.call(this,age,name);//通过对象冒充
}

var son = new Son(18,"lee");
var son2 = new Son(29,"ooo");
son.colors.push('black');
console.log(son.age); //18
console.log(son.money); //undefined,继承不到原型里的信息
console.log(son2.name); //ooo
console.log(son.colors); //["red", "yellow", "blue", "black"]
console.log(son2.colors); //["red", "yellow", "blue"]
son.say(); //"lee"

问题:
(1)、只能继承到构造里的信息,继承不到原型里的信息;
(2)、方法放在构造函数中定义,每次实例都会分配一个内存地址,没有实现共享,无法实现复用;

3、组合继承
基本思想:将原型链和构造函数的技术组合到一起,发挥二者之长;使用原型链实现对原型属性和方法的继承,使用借用构造函数实现对实例属性的继承;
构造函数内放不需要被共享的属性,原型中放要被共享的属性和方法;

 //放在构造函数里的信息不被共享
function Father(){
    this.class = ['语文','数学','英语'];
}
Father.prototype.grade = 2016;//原型中的属性共享
Father.prototype.sayName = function(){//方法放在原型中,保证多次实例化只有一块内存地址,实现方法的复用
    console.log(this.name);
}
function Son(name,age){//借用构造函数
    Father.call(this);
    this.age = age;
    this.name = name;
}

Son.prototype = new Father();//原型链

var son = new Son("lee",18);
var son2 = new Son("ooo",19);

son.class.push('地理');

console.log("年级:"+son.grade+"----姓名:"+son.name+"----年龄:"+son.age+"----课程:"+son.class);
//年级:2016----姓名:lee----年龄:18----课程:语文,数学,英语,地理
console.log("年级:"+son2.grade+"----姓名:"+son2.name+"----年龄:"+son.age+"----课程:"+son2.class);
//年级:2016----姓名:ooo----年龄:18----课程:语文,数学,英语

问题:调用了两次父类型构造函数;

3-1、组合继承的优化1
上面的组合继承提到存在的问题是调用了两次父类型构造函数,所以我们进行优化,把原型链继承那一步改成Son.prototype = Father.prototype; 这样这里就没有调用构造函数,但也完成了继承。

//放在构造函数里的信息不被共享
function Father(age){
    this.colors = ['red','yellow','blue'];
    this.age = 18;
    this.name = "lee";
}
Father.prototype.money = 1000000;
Father.prototype.house = 4;
function Son(){
    Father.call(this);
}
Son.prototype = Father.prototype;//更改的地方
//实例化对象
var son = new Son();
var son2 = new Son();
var father = new Father();

Son.prototype.money = 1000;//子类重写该原型属性

son.colors.push('#ccc');
console.log(son.age);//18
console.log(son.colors,son2.colors);//["red", "yellow", "blue", "#ccc"] ,["red", "yellow", "blue"]
console.log(son.money);//1000
console.log(son2.money);//1000,一起都被更改
console.log(father.money);//1000 ,父类的原型对象也被更改

问题:从一开始就存在的问题,Son.prototype = Father.prototype;
或者Son.prototype = new Father(); 会重写Son的原型对象,所以Son就没有constructor这个属性,所以会向原型链上寻找,最终Son.prototype.constructor 指向Father这个构造函数;

console.log(Son.prototype.constructor === Father ); //true
console.log(Father.prototype.constructor === Father ); //true

解决办法是改变constructor指向;下面的组合继承优化2。
3-2、组合继承的优化2
在改变constructor指向之前,还要思考一个问题,由于Son.prototype = Father.prototype;,即他俩指向同一个地址,所以,在你改变Son.prototypeconstructor 指向时,也会影响到Father.prototypeconstructor 指向;同时在Son在重写了父类原型的值时,父类也受到影响;

Son.prototype = Father.prototype;
Son.prototype.constructor = Son; //改变`Son.prototype`的`constructor` 指向
Father.prototype.money = 1000000;//父类定义一个原型属性
Son.prototype.money = 1000;//子类重写该原型属性
console.log(father.money);//1000 ,父类的该原型属性也被更改
console.log(Son.prototype.constructor === Son );//true
console.log(Father.prototype.constructor === Father ); //false
console.log(Father.prototype.constructor === Son ); //true

由运行结果可以看到,Father.prototypeconstructor 指向也被改变为Son
所以解决这个问题时,在实现继承那一步就不能再写成Son.prototype = Father.prototype;,要改成Son.prototype = Object.create(Father.prototype); ,这里的Object.create(Father.prototype) 是创建了一个以Father.prototype 为原型的对象。
Son.prototype = Object.create(Father.prototype); 可以理解为:

var obj = Object.create(Father.prototype); //创建一个对象,这个对象的原型是Father.prototype
Son.prototype = obj;//这个对象同时又是Son的原型
//所以这里形成了一个原型链,但又同时把Son.prototype和Father.prototype隔离开来

下面再来改变Son.prototypeconstructor 指向

Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
console.log(Son.prototype.constructor === Son ); //true
console.log(Father.prototype.constructor === Father ); //true

这里就实现了既改变了Son.prototypeconstructor 指向,又不影响Father.prototypeconstructor 指向的功能。

完整的实现代码

function Father(age){
    this.colors = ['red','yellow','blue'];
    this.age = 18;
    this.name = "lee";
}
Father.prototype.money = 1000000;
Father.prototype.house = 4;
function Son(){
    Father.call(this);
}
//Father.prototype只把Father原型里的信息交给Son
//Son.prototype会重写原型链
Son.prototype = Object.create(Father.prototype);
//Son.prototype = Father.prototype;
Son.prototype.constructor = Son;
console.log(Son.prototype.constructor === Son );//true,已改变
console.log(Father.prototype.constructor === Father );//true,不受影响没被改变
//实例化对象
var son = new Son();
var son2 = new Son();
var father = new Father();

Son.prototype.money = 1000;//子类重写该原型属性

son.colors.push('#ccc');
console.log(son.age);//18
console.log(son.colors,son2.colors);//(4) ["red", "yellow", "blue", "#ccc"] (3) ["red", "yellow", "blue"]
console.log(son.money);//1000
console.log(son2.money);//1000,一起都被更改
console.log(father.money);//100000 父类的不受影响

有些情况是只需要继承部分,所以存在下面两种方法;
下面的两种方法都是只继承原型里的属性和方法,不继承构造函数里的

4、共享原型继承

function Father(age){
    this.colors = ['red','yellow','blue'];
    this.age = 18;
    this.name = "lee";
}
Father.prototype.money = 1000000;
Father.prototype.house = 4;
function Son(){
}
//Father.prototype只把Father原型里的信息交给Son
Son.prototype = Father.prototype;
//实例化对象
var son = new Son();
var son2 = new Son();
var father = new Father();

Son.prototype.money = 1000;//子类重写该原型属性

console.log(son.age);//undefined
console.log(son.colors);//undefined
console.log(son.money);//1000
console.log(son2.money);//1000,一起都被更改
console.log(father.money);//1000 父类也受到影响

缺点:
(1)、只继承原型里的信息,不继承构造函数里的信息;
(2)、若子类型更改,Son.prototype.money = 1000,
其父类型和其他子类型也一起被更改,因为它们共享一个内存地址;

5、圣杯模式

可以继承已有原型对象的成员(主要是公共方法和属性),同时可以根据自己的需求修改原型对象,而这个修改对已有实例不产生影响

//圣杯模式
function Father(name,age){
    this.name = name;
    this.age = aeg;
}
Father.prototype.money = 100000;
function Son(name,age){
    this.name = name;
    this.age = age;
}
Son.prototype.money = 4000;//子类型修改原型属性

var father = new Father("Bob",45);
var son = new Son("John",18);//实例化子类型对象,具有参数
var son1 = new Son();//实例化子类型对象,没有参数

//定义一个函数,参数是两个构造函数
function inherit(Target,Origin){
    function Lk(){}//创建一个临时构造函数,多加一个中间层
    Lk.prototype = Origin.prototype;//共享原型,临时构造函数的原型为Origin的原型,只继承Origin原型里的信息;
    Target.prototype = new Lk();//使Target的原型为Lk的实例化对象,继承Lk构造里和原型里的信息,Target修改原型修改的是Lk对象,调用的是Origin
    Target.prototype.constructor = Target;//由于Target原型重写,导致失去默认的constructor属性,这是使Target.prototype的constructor重新指向Target构造函数;
    Target.prototype.ancetor = Origin.prototype;//target最终继承自Origin
}

//或者写成下面这种样式,同上面一样的效果
/*
var inherit = (function(){
    function Lk(){}
    return function(Target,Origin){
        Lk.prototype = Origin.prototype;
        Target.prototype = new Lk();
        Target.prototype.constructor = Target;
        Target.prototype.ancetor = Origin;
    };
})();
*/

inherit(Son,Father);

console.log("这是儿子,姓名:"+son.name+"  年龄:"+son.age+"  存款:"+son.money);
//这是儿子,姓名:John  年龄:18  存款:4000;子类型这里修改成功
console.log("这是父亲,姓名:"+father.name+"  年龄:"+father.age+"  存款:"+father.money);
//这是父亲,姓名:Bob  年龄:45  存款:100000;可以看到,子类型修改的原型属性对父类型没有产生影响
console.log("这是小儿子,姓名:"+son1.name+"  年龄:"+son1.age);
//这是小儿子,姓名:undefined  年龄:undefined;父类型构造里的信息没有继承

上面的inherit函数可以利用Object.create()方法改写成

 function inherit(Target,Origin){
     Target.prototype = Object.create(Origin.prototype);
     Target.prototype.constructor = Target;//由于Target原型重写,导致失去默认的constructor属性,这是使Target.prototype的constructor重新指向Target构造函数;
     Target.prototype.ancetor = Origin.prototype;//target最终继承自Origin
}

问题:这种方法只继承原型里的信息,不继承构造里的信息;例如上例中son1没有赋值,就无法继承父类型构造里的信息;

6,寄生组合式继承

基本思想:借用构造函数和圣杯模式的结合(我自己理解的。。。);通过借用构造函数来继承属性,通过原型链的混成模式来继承方法;不必为了指定子类型的原型而调用父类型的构造函数,我们所需要的无非就是父类型原型的一个副本而已;
这个就是上面3-2提到的组合继承优化方法2;

function Teacher(grade){
  this.grade = 2016;

}
Teacher.prototype.class = 2;
function Student(grade,name,age){
   Teacher.call(this,grade);//借用构造函数
   this.name = name;
   this.age = age;

}
Student.prototype.class = 8;
var teacher = new Teacher();
var student = new Student(2016,"Bob",17);
var student1 = new Student();
//三种inherit方法
/*
function inherit(Target,Origin){
   function Lk(){}
   Lk.prototype = Origin.prototype;
   Target.prototype = new Lk();
   Target.prototype.constructor = Target;
   Target.prototype.ancetor = Origin;
}*/
/*
var inherit = (function(){
   function Lk(){}
   return function(Target,Origin){
       Lk.prototype = Origin.prototype;
       Target.prototype = new Lk();
       Target.prototype.constructor = Target;
       Target.prototype.ancetor = Origin;
   };
})();*/
 function inherit(Target,Origin){
     Target.prototype = Object.create(Origin.prototype);
     Target.prototype.constructor = Target;//由于Target原型重写,导致失去默认的constructor属性,这是使Target.prototype的constructor重新指向Target构造函数;
     Target.prototype.ancetor = Origin.prototype;//target最终继承自Origin
}

inherit(Student,Teacher);//这一步替换了组合继承中的Student.prototype = new Teacher();少调用了一次构造函数;
console.log("这是老师,年级:"+teacher.grade+"  老师带几门课:"+teacher.class+" 分别是:"+teacher.cource);
//这是老师,年级:2016  老师带几门课:2  分别是:语文,数学
console.log("这是学生1,年级:"+student.grade+"  姓名:"+student.name+"  年龄:"+student.age+"  学生有几门课:"+student.class+"  必修课是:"+teacher.cource);
//这是学生1,年级:2016  姓名:Bob  年龄:17  学生有几门课:8  必修课是:语文,数学
console.log("这是学生2,年级:"+student1.grade+"  学生有几门课:"+student1.class+"  必修课是:"+teacher.cource);
//这是学生2,年级:2016  学生有几门课:8 必修课是:语文,数学

这种方法可以继承父类型构造函数里的信息,同时可以继承原型里的信息,并且在不影响父类型原型信息的情况下修改子类型原型信息。(恩,我觉得是这样,以后有新的想法再改)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值