在學(xué)習(xí)JavaScript中的繼承之前,需要先看一下之前總結(jié)的對(duì)象的體系結(jié)構(gòu),如圖所示
- 構(gòu)造函數(shù)中都有原型屬性prototype,該屬性值本質(zhì)也是對(duì)象(Object的實(shí)例對(duì)象),prototype本身也是占用內(nèi)存的
- 原型對(duì)象中也有一個(gè)特別的屬性constructor,該屬性指向原型所屬的構(gòu)造函數(shù)
- 實(shí)例對(duì)象當(dāng)中都有一個(gè)_proto_屬性,該屬性不是標(biāo)準(zhǔn)屬性,不能在編程中使用,并且該屬性指向原型對(duì)象
- 所有函數(shù)都是Function的實(shí)例對(duì)象
- Function函數(shù)本身也是自己的實(shí)例對(duì)象也就是Function._proto_ = ==Funciton.prototype //true
總的來(lái)說(shuō)就是_proto_位于實(shí)例對(duì)象,prototype位于構(gòu)造函數(shù);prototype可以在編程中直接使用,但是__proto_不可以,用于瀏覽器內(nèi)部使用
接下來(lái)簡(jiǎn)單感受下原型給我們帶來(lái)的好處
function Car(brand,color){
this.brand = brand;
this.color = color;
}
Car.prototype.showColor = function(){
console.log(this.brand+"顏色"+this.color);
}
var c1 = new Car('奧迪','black');
var c2 = new Car('奔馳','blue');
c1.showColor();//奧迪顏色black
c2.showColor();奔馳顏色blue
c1和c2都可以訪問(wèn)到showColor()
從上面的代碼可以看出來(lái)原型中的showColor是為了降低內(nèi)存的消耗,實(shí)現(xiàn)實(shí)例對(duì)象之間的數(shù)據(jù)共享
原型鏈
在深入學(xué)習(xí)原型繼承方式之前,我們先看下什么是原型鏈呢?
基本思想是實(shí)例對(duì)象和原型對(duì)象之間形成的鏈?zhǔn)浇Y(jié)構(gòu),該鏈?zhǔn)绞峭ㄟ^(guò)_proto_連接起來(lái)的(與構(gòu)造函數(shù)木有關(guān)系)
可能大家還是對(duì)原型鏈有點(diǎn)模糊,針對(duì)上邊的代碼我們看一下實(shí)例以及構(gòu)造函數(shù)和原型之間是怎樣的關(guān)系.如果顯示:
切記與構(gòu)造函數(shù)木有關(guān)系的哦,例如c1原型鏈?zhǔn)羌t色標(biāo)注的部分
那么原型鏈可以人為的變化嗎?答案是可以滴
function Foo1(info){
this.info = info;
}
Foo2.prototype = new Foo1('david');
function Foo2(info){
this.info = info;
}
Foo3.prototype = new Foo2('jim');
function Foo3(info){
this.info = info;
}
var f = new Foo3('lily');
console.dir(f);
f->new Foo2('jim')->new Foo1('david')-->Foo1.prototype-->Object.prototype-->null
運(yùn)行結(jié)果:
原型繼承
原型鏈的結(jié)構(gòu)可以通過(guò)重置原型對(duì)象的方式來(lái)修改,重新賦值指向另外一個(gè)位置,我們來(lái)體驗(yàn)一下
function Student(name,age,score){
this.age = age;this.name = name;
this.score = score;
}
Student.prototype.showName = function(){
console.log(this.name);
}
Student.prototype.showAge = function(){}
Student.prototype.showScore = function(){}
var stu = new Student('zs',12,88);
console.dir(stu);
打印結(jié)果如下:
上面Student.prototype內(nèi)容寫法其實(shí)還可以簡(jiǎn)化
function Student(name,age,score){
this.age = age;this.name = name;
this.score = score;
}
Student.prototype = {
constructor:Student,
showName: function(){
console.log(this.name);
},
showAge: function(){
console.log(this.age);
},
showScore: function(){
console.log(this.score);
}
}
var stu = new Student('zs',12,88);
console.dir(stu);
打印結(jié)果如下:
可能大家會(huì)有疑問(wèn),為什么需要手動(dòng)添加constructor屬性呢?
每當(dāng)創(chuàng)建一個(gè)函數(shù)(代碼中的Student構(gòu)造函數(shù))就同時(shí)創(chuàng)建了prototype對(duì)象,同時(shí)在prototype對(duì)象中就有了constructor屬性,但是在代碼中 Student.prototype = {...}相當(dāng)于是重寫默認(rèn)的prototype對(duì)象,指向了一個(gè)新的對(duì)象,新對(duì)象中是不存在constructor的,那么constructor屬性就不再是原來(lái)的原型指向的構(gòu)造函數(shù),而是指向新的Object構(gòu)造函數(shù)了
原型繼承對(duì)代碼的復(fù)用,提過(guò)生產(chǎn)率有明顯的效果
原型繼承雖然很好用,但是呢沒(méi)有辦法解決給繼承過(guò)來(lái)的屬性賦值,而不影響所有的實(shí)例
從下面代碼就能夠看出來(lái)
function Food(name,time){
this.name = name;
this.productTime = time;
}
function Bread(num){
this.num = num;
}
Bread.prototype = new Food('華帝');//由于prototype指向了一個(gè)實(shí)例對(duì)象,所以繼承Food之后的Bread實(shí)例中對(duì)應(yīng)的name,productTime 也為固定
var bread1= new Bread(01);
console.log(bread1.name);//調(diào)用bread.name會(huì)首先從實(shí)例對(duì)象中查找,如果實(shí)例對(duì)象中不存在該屬性,然后會(huì)在原型中查找
var bread2= new Bread(02);
console.log(bread2.name);//繼承過(guò)來(lái)的所有的實(shí)例都是共享的,這樣所有的面包名字都叫華帝,但現(xiàn)實(shí)生活中我們希望的是不共享
解決上面的問(wèn)題可以通過(guò)借用構(gòu)造函數(shù)的方式解決
借用構(gòu)造函數(shù)
借用構(gòu)造函數(shù),需要使用到call或者apply方法,這兩個(gè)作用就是
1.調(diào)用函數(shù)
2.改變所調(diào)用函數(shù)內(nèi)部的this指向
這里我們先看一個(gè)小例子理解一下,如何改變this的指向?
var a = 12;
var b = 23;
function sum(){
console.log(this.a+this.b);
}
var obj = {
a:1,
b:2
}
sum(); //35
sum.call(obj); //this被改為obj了,結(jié)果是3
sum.apply(obj); //此處使用apply也可以,this被改為obj了,結(jié)果是3
第一個(gè)參數(shù)就是改變this的指向
在這篇文章我們主要看下繼承,call和apply先會(huì)使用就行,后面在深入學(xué)習(xí),接下來(lái)繼續(xù)解決繼承過(guò)來(lái)的所有的實(shí)例都是共享的問(wèn)題
// 借用構(gòu)造函數(shù)繼承 Student借了Person
function Person(name,favour){
this.name = name;
this.favour =favour;
}
function Student(num,name,favour){
// 這里的this是Student構(gòu)造函數(shù)的實(shí)例
Person.call(this,name,favour);//ok借到了,這樣就相當(dāng)于
Person構(gòu)造函數(shù)中的this.name = name;this.favour =favour;這兩句寫到了此處的效果
this.num = num;
}
var stu1 = new Student(1,'bob',['coding']);
stu1.favour.push('swmming');
var stu2 = new Student(1,'play',['dancing']);
console.log(stu1.favour);
console.log(stu2.favour);
打印結(jié)果:
這樣就解決了繼承過(guò)來(lái)的所有的實(shí)例都是共享的問(wèn)題,你看bob愛好是游泳,play是沒(méi)有這個(gè)愛好的
難道這樣就完美了嗎?但是出現(xiàn)了一個(gè)新問(wèn)題.
Person中的原型如果有一個(gè)showName方法,Student原型是沒(méi)有這個(gè)方法的,但是student實(shí)例想用這個(gè)方法怎么辦?
Person.prototype.showName = function(){
console.log(this.name);
}
在調(diào)用stu1.showName();會(huì)報(bào)錯(cuò)
原因:構(gòu)造函數(shù)沒(méi)有new,所以只是一個(gè)普通的函數(shù),因此原型就沒(méi)有用,所以原型中的showName()沒(méi)有辦法使用,報(bào)錯(cuò)信息沒(méi)有這個(gè)方法
怎么解決這個(gè)問(wèn)題?
想訪問(wèn)Person實(shí)例中的showFavour方法咋整
組合繼承
原型繼承與借用構(gòu)造函數(shù)繼承的組合
function Person(name,favour){
this.name = name;
this.favour = favour;
}
Person.prototype.showFavour = function(){
console.log(this.favour);
}
function Teacher(name,favour,level){
Person.call(this,name,favour);
this.level = level;
}
Teacher.prototype = new Person();//這里的參數(shù)可以不寫,反正我們是為了繼承后用showName方法
var tea1 = new Teacher('zhangsan',['coding'],'T5');
var tea2 = new Teacher('lisi',['swimming'],'T5');
tea1.showFavour();
tea1.favour.push('singing');
tea2.showFavour();
console.dir(tea1);
console.dir(tea2);
運(yùn)行結(jié)果:
這樣就可以訪問(wèn)person原型上的方法了,并且實(shí)例自己的屬性不是共享的,完美了