js继承

目录

一、通过原生链使用继承

原型属性和本身属性的区别

将共享属性迁移到原型中去

只继承于原型

临时构造器----new F()

uber---子对象访问父对象的方式 

将继承部分封装成函数

二,通过属性烤贝实现继承

浅拷贝的缺点

对象之间的继承

深拷贝

原型继承与属性拷贝的混合应用

三,寄生式继承:

四,构造器借用法


一、通过原生链使用继承

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的内容。这就是通过原生链实现的继承。

步骤:

  1. a先在Triangle中查找name属性
  2. 没找到去原型链TwoDShape()查找,同样没找到
  3. 这时在去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 ]

注意:

  1. p.__proto__.hasOwnProperty(i) 说明传入的值p只对是对象,而不可以是构造器,是构造器修改为(p.prototype.hasOwnProperty(i))   作用是查看原型中有没有属性i。b.shu没有拷贝就是这段代码实现的
  2. 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的继承很虚。难点其实就是内存空间。谁和谁共享内存空间,谁有独立的内容空间。把这此懂了,上边的继承方式只是换了个外壳。说到底就是把数据放在那。

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值