javascript继承的方式

快乐打工仔 分类:实例代码

本章节介绍一下在javascript中可以实现继承的几种方式。

需要的朋友可以做一下参考,下面进入正题。

一.原型链方式继承:

原型链是 JavaScript 中实现继承的默认方式,如果要让子对象继承父对象的话,最简单的方式是将子对象构造函数的prototype属性指向父对象的一个实例,代码如下:

unction Parent() {}
function Child() {}
Child.prototype = new Parent()

这个时候,Child的prototype属性被重写了,指向了一个新对象,但是这个新对象的constructor属性却没有正确指向Child,JS 引擎并不会自动为我们完成这件工作,这需要我们手动去将Child的原型对象的constructor属性重新指向Child:

Child.prototype.constructor = Child

以上就是javascript中的默认继承机制,将需要重用的属性和方法迁移至原型对象中,而将不可重用的部分设置为对象的自身属性,但这种继承方式需要新建一个实例作为原型对象,效率上会低一些。

二.原型继承(非原型链):

为了避免上一个方法需要重复创建原型对象实例的问题,可以直接将子对象构造函数的prototype指向父对象构造函数的prototype,这样,所有Parent.prototype中的属性和方法也能被重用,同时不需要重复创建原型对象实例:

Child.prototype = Parent.prototype
Child.prototype.constructor = Child

但是我们知道,在 JavaScript 中,对象是作为引用类型存在的,这种方法实际上是将Child.prototype和Parent.prototype中保存的指针指向了同一个对象,因此,当我们想要在子对象原型中扩展一些属性以便之后继续继承的话,父对象的原型也会被改写,因为这里的原型对象实例始终只有一个,这也是这种继承方式的缺点。

三.临时构造器继承:

为了解决上面的问题,可以借用一个临时构造器起到一个中间层的作用,所有子对象原型的操作都是在临时构造器的实例上完成,不会影响到父对象原型:

var F = function() {}
F.prototype = Parent.prototype
Child.prototype = new F()
Child.prototype.constructor = Child

同时,为了可以在子对象中访问父类原型中的属性,可以在子对象构造器上加入一个指向父对象原型的属性,如uber,这样,可以在子对象上直接通过child.constructor.uber访问到父级原型对象。

我们可以将上面的这些工作封装成一个函数,以后调用这个函数就可以方便实现这种继承方式了:

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

然后就可以这样调用:

extend(Dog, Animal)

四.属性拷贝:

这种继承方式基本没有改变原型链的关系,而是直接将父级原型对象中的属性全部复制到子对象原型中,当然,这里的复制仅仅适用于基本数据类型,对象类型只支持引用传递。

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

这种方式对部分原型属性进行了重建,构建对象的时候效率会低一些,但是能够减少原型链的查找。不过我个人觉得这种方式的优点并不明显。

五.对象间继承:

除了基于构造器间的继承方法,还可以抛开构造器直接进行对象间的继承。即直接进行对象属性的拷贝,其中包括浅拷贝和深拷贝。

浅拷贝:

接受要继承的对象,同时创建一个新的空对象,将要继承对象的属性拷贝至新对象中并返回这个新对象:

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

拷贝完成之后对于新对象中需要改写的属性可以进行手动改写。

深拷贝:

浅拷贝的问题也显而易见,它不能拷贝对象类型的属性而只能传递引用,要解决这个问题就要使用深拷贝。深拷贝的重点在于拷贝的递归调用,检测到对象类型的属性时就创建对应的对象或数组,并逐一复制其中的基本类型值。

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

其中用到了一个 ES5 的Array.isArray()方法用于判断参数是否为数组,没有实现此方法的环境需要自己手动封装一个 shim。

Array.isArray = function(p) {
  return p instanceof Array
}

但是使用instanceof操作符无法判断来自不同框架的数组变量,但这种情况比较少。

六.原型继承:

借助父级对象,通过构造函数创建一个以父级对象为原型的新对象:

function object(o) {
  var n
  function F() {}
  F.prototype = o
  n = new F()
  n.uber = o
  return n
}

这里,直接将父对象设置为子对象的原型,ES5 中的 Object.create()方法就是这种实现方式。

七.原型继承和属性拷贝混用:

原型继承方法中以传入的父对象为原型构建子对象,同时还可以在父对象提供的属性之外额外传入需要拷贝属性的对象:

function ojbectPlus(o, stuff) {
  var n
  function F() {}
  F.prototype = o
  n = new F()
  n.uber = o
  for (var i in stuff) {
    n[i] = stuff[i]
  }
  return n
}

八.多重继承:

这种方式不涉及原型链的操作,传入多个需要拷贝属性的对象,依次进行属性的全拷贝:

function multi() {
  var n = {}, stuff, i = 0,
        len = arguments.length
  for (i = 0; i < len; i++) {
    stuff = arguments[i]
    for (var key in stuff) {
      n[i] = stuff[i]
    }
  }
  return n
}

回复

我来回复
  • 暂无回复内容