目录
一、通过原生链使用继承
function Shape(){
this.name = 'Shape';
this.toString = function(){
return this.name;
};
}
undefined
function TwoDShape(){
this.Two = 'TwoDShape';
}
undefined
function Triangle(side,height){
this.side = side;
this.height = height;
this.getArea = function(){
return this.side * this.height /2;
};
}
undefined
TwoDShape.prototype = new Shape();
Object { name: "Shape", toString: toString() }
Triangle.prototype = new TwoDShape();
Object { Two: "TwoDShape" }
var a = new Triangle();
Object { side: undefined, height: undefined, getArea: getArea() }
a.name;
"Shape"
虽然a为Triangle的对象,但它却可以访问Shape的内容。这就是通过原生链实现的继承。
步骤:
- a先在Triangle中查找name属性
- 没找到去原型链TwoDShape()查找,同样没找到
- 这时在去TwoDShape的原形Shape查找,找到了。返回
这就是原生链继承的原理。先在本身查找属性,找不到在去它所指向的原型查找。
当我们对对象的prototype属性进行完全替换时(TwoDShape.prototype = new Shape();),要手动对对象的constructor属性进行重置
TwoDShape.prototype.constructor = TwoDShape;
Triangle.prototype.constructor = Triangle;
原型属性和本身属性的区别
原型是一个引用,无论new多次,指向的是同一个对象。也就是指向同一块内存空间。a对象修改了原型,b对象也会发生变化。而本身属性有自己的内存空间。相互不会影响。
function Shape(){
this.a = 'Shape'
}
undefined
Shape.prototype.b = 'proty';
"proty"
var a = new Shape();
undefined
var b = new Shape();
undefined
a.a = 'A Shape';
"A Shape"
a.__proto__.b = 'A Proty';
"A Proty"
b.a;
"Shape"
b.b;
"A Proty"
将共享属性迁移到原型中去
通过原型属性与自身属性的关系,可知将共享属性迁移到原型更加节约内存空间(new构建对象时,原型不会开辟新空间。而自身属性需要开辟新空间)
如 function Shape(){this.name = 'Shape';} ,我们每次new Shape()创建实体都会有一个全新的name,并在内存中有自己独立的存储空间。而name如果在原型中,这样一来所有原型就会共享存储空间。
首先。只有不可变属性与对象的共有方法才适合迁移到原型。因为当一个对象修改了原形属性,会影响其它共享这个原形的对象。
注意:通常在对原形的扩展之前,先完成相关的继承关系构建。否则继承关系构建会抹除我们扩展的内容
function Shape(){}
undefined
Shape.prototype.name = 'zsw';
"zsw"
function TwoDShape(){}
undefined
TwoDShape.prototype.two = '2';
"2"
TwoDShape.prototype = new Shape(); //这时无论我们之前扩展了多少属性,都会被抹除。正确的作法是先构建继承关系在扩展属性
Object { }
var a = new TwoDShape();
undefined
a.name; //name是继承的
"zsw"
a.two;//two是我们扩展的,但被抹除了
undefined
只继承于原型
既然公有属性方法,都在原形中。那只继承于原型,会更加有效率。因为new 创建的实体,势必将无法共享的属性创建。这也造成了内存浪费。
function Shape(){}
undefined
Shape.prototype.name = 'shape';
"shape"
Shape.prototype.toString = function(){
return this.name;
};
function toString()
function TwoDShape(){}
undefined
TwoDShape.prototype = Shape.prototype; //只继承于原型。这也导致TowDShape与Shape共享一个原型
Object { name: "shape", toString: toString(), … }
TwoDShape.prototype.constructor = TwoDShape;
function TwoDShape()
TwoDShape.prototype.name = '2D shape';
"2D shape"
function Triangle(side,height){
this.side = side;
this.height = height;
}
undefined
Triangle.prototype = TwoDShape.prototype;
Object { name: "2D shape", toString: toString(), … }
Triangle.prototype.constructor = Triangle;
function Triangle()
Triangle.prototype.name = 'Triangle';
"Triangle"
Triangle.prototype.getAr
undefined
Triangle.prototype.getArea = function(){
return this.side * this.height / 2;
}
function getArea()
var my = new Triangle(5,10);
undefined
my.getArea();
25
my.toString();
"Triangle"
通过
Triangle.prototype == Shape.prototype;
true
Shape.prototype == TwoDShape.prototype;
true
发现所有prototype属性都指向了一个相同的对象,这时my.toString()与原先有什么不同,原先查询需要四步或三步。而现在只需要两步(自身没找到,去原型找,因为共享一个原型,找到返回)。这样拷贝原型虽然效率上快,不过很多场景并不适合。
临时构造器----new F()
所有prototype属性都指向同一个对象,父对象就会受到子对象的影响。子对象修改了原型父对象也会改变。这时想我们为什么直接继承原型,就是因为new会把没必要共享的对象,在创建一份,浪费空间。通过直接继承原型,解决了这个问题。那如果有一个方法即能只继承原型,还能让父对象不受子对象的影响。 就更好了。也就引出了临时构造器。
var F = function(){}
undefined
F.prototype = Shape.prototype;
Object { name: "Triangle", toString: toString(), getArea: getArea(), … }
TwoDShape.prototype = new F();
Object { }
TwoDShape.prototype.constructor = TwoDShape;
通过这种方法。TwoDShape即只继承了原型。而且它指向的原型有自己独立的空间,不会干扰父对象。
通过F()真的解决子对象不能影响父对象了吗,不是的。既然子对象还是能访问父对象,说明它们还是有连接的,只是多了一层链接
function A(){}
undefined
A.prototype.name = 12;
12
function B(){}
undefined
function F(){}
undefined
F.prototype = A.prototype;
Object { name: 12, … }
B.prototype = new F();
var a = new A();
undefined
var b = new B();
undefined
b.__proto__.__proto__.name = 22; //b.__proto__等于F(),而F.__proto__就是A的原型了
22
a.name;
22
uber---子对象访问父对象的方式
js并没有类似java的supper,就这个uber也很虚。如 TwoDShape.uber = Shape.prototype; 对TwoDShape来说,uber就是一个普通属性,只是通过借用它,来达到类似子对象访问父对象的目的。
function Shape(){}
undefined
Shape.prototype.name = 'shape';
"shape"
Shape.prototype.toString = function(){
var const1 = this.constructor;
return const1.uber ? const1.uber.toString() + ','+ this.name:this.name;
};
function toString()
function TwoDShape(){}
undefined
var F = function(){};
undefined
F.prototype = Shape.prototype;
Object { name: "shape", toString: toString(), … }
TwoDShape.prototype = new F();
Object { }
TwoDShape.prototype.constructor = TwoDShape;
function TwoDShape()
TwoDShape.uber = Shape.prototype; //uber
Object { name: "shape", toString: toString(), … }
TwoDShape.prototype.name = '2D shape';
"2D shape"
function Triangle(side,height){
this.side = side;
this.height = height;
}
undefined
var F = function(){}
undefined
F.prototype = TwoDShape.prototype;
Object { constructor: TwoDShape(), name: "2D shape" }
Triangle.prototype = new F();
Object { }
Triangle.prototype.constructor = Triangle;
function Triangle()
Triangle.uber = TwoDShape.prototype;
Object { constructor: TwoDShape(), name: "2D shape" }
Triangle.prototype.name = 'Triangle';
"Triangle"
Triangle.prototype.getArea = function(){
return this.side * this.height / 2;
};
function getArea()
var my = new Triangle(5,10);
undefined
my.toString();
"shape,2D shape,Triangle"
将继承部分封装成函数
现在我们已经找到通过原型实现继承的最佳方式。就是在只继承原型的基础上,每个构造器原型都有自己的存储空间。让子对象与父对象互不影响。
function extend(Child,Parent){ //Child继承Parent
function F(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
二,通过属性烤贝实现继承
属性拷贝,类似我把你有的复制一份过来。使用原形继承,当父原型添加新属性后,子对象能‘实时’引用,属性拷贝是通过uber达到类似‘实时’效果的。
function extend2(Child,Parent){ //属性拷贝实现
var p = Parent.prototype;
var c = Child.prototype;
for(var i in p){
c[i] = p[i];
}
c.uber = p;
}
undefined
var A = function(){}
undefined
A.prototype.age = 12;
12
var B = function(){}
undefined
extend2(B,A);
undefined
var b = new B();
undefined
b.age;
12
b.uber.age = 14;
14
a.age;
undefined
var a = new A();
undefined
a.age;
14
和原型继承还一个区别。属性拷贝是对原型的扩展。也就不需要重置B.prototype.constructor,它没有类似 b.prototype = new F()代码。与原型相比这个方法在效率上略逊一筹,因为这里执行的是子对象原型的逐一拷贝,而非简单的原型链查询。
浅拷贝的缺点
上面的拷贝方法就属于浅拷贝。下面让我们看一下为什么说是浅拷贝。
var a = [1,2,3];
undefined
var b = a;
undefined
var c = a;
undefined
b == a;
true
可见对数组或对象进行简单的赋值运算,传递的是引用。也就是当属性是数组或对象时,子类只是简单拷贝父类。子对象修改数组父对象也会受到影响。这也称为浅烤贝。解决方法就是对应的深拷贝。
对象之间的继承
上边的继承都是基于构造器,也可以不通过构造器直接继承一个对象。原理一样,就是复制吗,不过把复制的对象换了
function extendCopy(p){
var c = {};
for(i in p){
c[i] = p[i];
}
c.uber = p;
return c;
}
undefined
var shape = {
name:'Shape',
toString:function(){return this.name;}
}
undefined
var a = extendCopy(shape);
undefined
a.name = 'a'
"a"
a.toString(); //a已经继承了shape的属性
"a"
深拷贝
深拷贝就是通过判断属性是否是object类型,如果是 将这个属性在拷贝一遍(调用本身)
function deepCopy(p,c){
c = c || {}; //当c存在时c=c,当c不存在c = {}
for(var i in p){
if(p.__proto__.hasOwnProperty(i)){ //判断p原型有没有i。当然这是可选的。因为原型中才是可以共享的属性
if(typeof p[i] === 'object'){//浅拷贝的缺点就是数组与对象只拷贝原型。我们通过typeof先判断。当是object时,特别处理
c[i] = Array.isArray(p[i]) ? []:{}; //判断p[i]是数组还是对象
deepCopy(p[i],c[i]);
}else{ //如果是普通属性只需要简单拷贝就可以了
c[i] = p[i];
}
}
}
return c;
}
undefined
var a = { shu:'自身属性'}
undefined
a.prot
undefined
a.__proto__.shu1 = '原型属性';
"原型属性"
a.__proto__.shu2 = [1,2,3,4];
var b = deepCopy(a);
undefined
b.shu;
undefined
b.shu1
"原型属性"
b.shu2 = [1,2]
Array [ 1, 2 ]
a.shu2
Array(4) [ 1, 2, 3, 4 ]
注意:
- p.__proto__.hasOwnProperty(i) 说明传入的值p只对是对象,而不可以是构造器,是构造器修改为(p.prototype.hasOwnProperty(i)) 作用是查看原型中有没有属性i。b.shu没有拷贝就是这段代码实现的
-
b.shu2 = [1,2] Array [ 1, 2 ] a.shu2 Array(4) [ 1, 2, 3, 4 ]
可见,深拷贝,对object属性,不是简单的拷贝引用。这样子对象与父对象就互不影响了。
原型继承与属性拷贝的混合应用
就是继承一个对象原型,拷贝另一个对象的属性。将两个操作合并成一个操作
function objectPlus(o,stuff){
var n;
function F(){};
F.prototype = o.prototype;
n = new F();
n.uber = o;
for(var i in stuff){
n[i] = stuff[i]; //浅拷贝,可以修改为深拷贝
}
return n;
}
可见返回的对象n,即继承了o的原型,同时也拷贝了stuff的属性
三,寄生式继承:
在创建对象的函数中直接吸收其他对象的功能,然后对其进行扩展并返回。
function triangle(){
var that;
for(var i in twoD){
that[i] = twoD[i]; //在创建对象的函数中直接吸收其他对象的功能。这里是浅拷贝,可以改成深拷贝
}
that.name = 'Triangle'; //进行扩展
return that;
}
可以想想 var a = trianle()与var a = new triangle()有区别吗。是没有的都是返回that。new Xxx()当return返回的是一个对象时,就默认返回对象。只有当return返回的不是一个对象,才会返回this
四,构造器借用法
子对象构造器可以通过call()或apply()方法来调用 父对象的构造器。因而,它通常被称为构造器盗用法或构造器借用法。
function Shape(id){
this.id = id;
}
Shape.prototype.name = 'Shape';
"Shape"
Shape
Shape.prototype.toString = function(){
return this.name;
}
function Triangle(){
Shape.apply(this,arguments);
}
undefined
Triangle.prototype.name = 'Triangle';
"Triangle"
var t = new Triangle(101);
undefined
t.id //可见,新的triangle对象继承了其父对象的id属性
101
但是我们是没有继承Shape原型的,因为我们从来没有调用new Shape()创建任何一个实例。
function Triangle(){
Shape.apply(this,arguments);
}
Triangle.prototype = new Shape();
Triangle.prototype.name = 'Triangle';
这样一来,父对象的自身属性事实上被继承了两次。一次发生在Shape.apply时,一次发生在new()时。
解决方法是把Triangle.prototype = new Shape();使用别的代替。因为这个我们只想继承原型,一种方法是通过深拷贝的方式只拷贝原型。就可以了。
终于学完继承了,仔细学习你会发现,js的继承很虚。难点其实就是内存空间。谁和谁共享内存空间,谁有独立的内容空间。把这此懂了,上边的继承方式只是换了个外壳。说到底就是把数据放在那。