javascript 继承理解

javascript的继承方式有好多方式,之前ES5通过原型链继承,ES6中引入了Class,javascript可以像java一样使用extends关键字继承。现在整理一下js中的继承方式。

ES5中的继承方式

一、构造函数的继承

现在有一个”动物”对象的构造函数

function Animal(){
  this.species = "动物";
}

还有一个”猫”对象的构造函数。

function Cat(name,color){
  this.name = name;
  this.color = color;
}

1.构造函数绑定:使用call或apply方法,将父对象的构造函数绑定在子对象上。

function Cat(name,color){
  Animal.apply(this, arguments);
  this.name = name;
  this.color = color;
}
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

此种继承方式只能继承父构造函数中的属性,不能继承父构造函数原型上的属性.Animal.apply(this, arguments)也可用Animal.call(this)替换.cat对象有两个层级,第一级存放着自有属性以及父构造器中的属性,第二级存放着自己函数原型上的属性(Cat.prototype)

2.prototype模式:

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

遵循一点,即如果替换了prototype对象,

o.prototype = {};

那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。

o.prototype.constructor = o;

此种继承方式既能继承父构造函数中的属性,也能继承父构造函数原型上的属性.cat对象有三个层级,第一级存放着自有属性,第二级存放着父构造器的属性(加上constructor,指向创建该对象(cat)的构造器(Cat)),第三级存放着父构造器原型上的属性.

3.直接继承prototype:
第3种方法是对第2种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。

先将Animal对象改写

function Animal(){ }
Animal.prototype.species = "动物";

然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。

Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

此种继承方式只能继承父构造器原型上的属性.cat对象有两个层级,第一级存放着自有属性,第二级存放着父构造器原型上的属性(加上constructor,指向创建该对象(cat)的构造器(Cat)).注意:Cat.prototype.constructor = Cat会将Animal.prototype.constructor也改成Cat,从而影响父构造器创建对象

4.空对象作为中介:

function extend(Child, Parent) {
  var F = function(){};
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
  Child.uber = Parent.prototype;
}

用法:

extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

此种继承方式只能继承父构造器原型上的属性.cat对象有三个层级,第一级存放着自有属性,第二级存放着临时构造器F的属性(加上constructor,指向创建该对象(cat)的构造器(Cat)),第三级存放着父构造器原型上的属性.注意:临时构造器只充当中介的作用,一般不会有自己的属性.

5.浅拷贝继承:
首先,还是把Animal的所有不变属性,都放到它的prototype对象上。

function Animal(){ }
Animal.prototype.species = "动物";

然后,再写一个函数,实现属性拷贝的目的。

function extend2(Child, Parent) {
  var p = Parent.prototype;
  var c = Child.prototype;
  for (var i in p) {
    c[i] = p[i];
  }
  c.uber = p;
}

用法:

extend2(Cat, Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

此种继承方式只能继承父构造器原型上的属性.cat对象有两个层级,第一级存放着自有属性,第二级存放着父构造器原型上的除了constructor和proto的属性(不会拷贝原型上的constructor和proto属性).适合父构造器原型上的自增属性为基本数据类型的情况.

二、非构造函数的继承

比如,现在有一个对象,叫做”中国人”。

var Chinese = {
  nation:'中国'
};

还有一个对象,叫做”医生”。

var Doctor ={
  career:'医生'
}

1.object()方法

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

这个object()函数,其实只做一件事,就是把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起。
用法:
第一步先在父对象的基础上,生成子对象:

var Doctor = object(Chinese);

然后,再加上子对象本身的属性:

Doctor.career = '医生';

这时,子对象已经继承了父对象的属性了。

alert(Doctor.nation); //中国

2.浅拷贝
除了使用”prototype链”以外,还有另一种思路:把父对象的属性,全部拷贝给子对象,也能实现继承。

就是在做拷贝:

function extendCopy(p) {
var c = {};
for (var i in p) { 
  c[i] = p[i];
}
c.uber = p;
return c;
}

用法,这样写:

var Doctor = extendCopy(Chinese);
Doctor.career = '医生';
alert(Doctor.nation); // 中国

样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。

3.深拷贝
所谓”深拷贝”,就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用”浅拷贝”就行了。

function deepCopy(p, c) {
  var c = c || {};
  for (var i in p) {
    if (typeof p[i] === 'object') {
      c[i] = (p[i].constructor === Array) ? [] : {};
      deepCopy(p[i], c[i]);
      } else {
        c[i] = p[i];
      }
    }
  return c;
}

使用的时候这样写:

var Doctor = deepCopy(Chinese);

现在,给父对象加一个属性,值为数组。然后,在子对象上修改这个属性:
这时,父对象就不会受到影响了。
目前,jQuery库使用的就是这种继承方法。

ES6中的继承方式

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

class Point {
  //一些属性
}
class ColorPoint extends Point {
  //一些属性
}
// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}
  • 上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类。
  • 如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。
  • 在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
  • ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。

猜你喜欢

转载自blog.csdn.net/u014627807/article/details/80237022
今日推荐