JavaScript的9种继承实现方式归纳

   这篇文章主要介绍了JavaScript的9种继承实现方式归纳,本文讲解了原型链继承、原型继承(非原型链)、临时构造器继承、属性拷贝、对象间继承等继承方式,需要的朋友可以参考下

  不同于基于类的编程语言,如 C++ 和 Java,JavaScript 中的继承方式是基于原型的。同时由于 JavaScript 是一门非常灵活的语言,其实现继承的方式也非常多。

  首要的基本概念是关于构造函数和原型链的,父对象的构造函数称为Parent,子对象的构造函数称为Child,对应的父对象和子对象分别为parent和child。

  对象中有一个隐藏属性[[prototype]](注意不是prototype),在 Chrome 中是__proto__,而在某些环境下则不可访问,它指向的是这个对象的原型。在访问任何一个对象的属性或方法时,首先会搜索本对象的所有属性,如果找不到的话则会根据[[prototype]]沿着原型链逐步搜索其原型对象上的属性,直到找到为止,否则返回undefined。

  1.原型链继承:

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

   代码如下:

  function Parent() {}

  function Child() {}

  Child.prototype = new Parent()

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

   代码如下:

  Child.prototype.constructor = Child

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

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

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

   代码如下:

  Child.prototype = Parent.prototype

  Child.prototype.constructor = Child

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

  3.临时构造器继承:

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

   代码如下:

  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)

  4.属性拷贝:

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

   代码如下:

  function extend2(Child, Parent) {

  var p = Parent.prototype

  var c = Child.prototype

  for (var i in p) {

  c[i] = p[i]

  }

  c.uber = p

  }

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

  5.对象间继承:

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

  浅拷贝:

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

   代码如下:

  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操作符无法判断来自不同框架的数组变量,但这种情况比较少。

  6.原型继承:

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

   代码如下:

  function object(o) {

  var n

  function F() {}

  F.prototype = o

  n = new F()

  n.uber = o

  return n

  }

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

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

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

   代码如下:

  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

  }

  8.多重继承:

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

   代码如下:

  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

  }

  根据对象传入的顺序依次进行拷贝,也就是说,如果后传入的对象包含和前面对象相同的属性,后者将会覆盖前者。

  9.构造器借用:

  JavaScript中的call()和apply()方法非常好用,其改变方法执行上下文的功能在继承的实现中也能发挥作用。所谓构造器借用是指在子对象构造器中借用父对象的构造函数对this进行操作:

  代码如下:

  function Parent() {}

  Parent.prototype.name = 'parent'

  function Child() {

  Parent.apply(this, arguments)

  }

  var child = new Child()

  console.log(child.name)

  这种方式的最大优势就是,在子对象的构造器中,是对子对象的自身属性进行完全的重建,引用类型的变量也会生成一个新值而不是一个引用,所以对子对象的任何操作都不会影响父对象。

  而这种方法的缺点在于,在子对象的构建过程中没有使用过new操作符,因此子对象不会继承父级原型对象上的任何属性,在上面的代码中,child的name属性将会是undefined。

  要解决这个问题,可以再次手动将子对象构造器原型设为父对象的实例:

  代码如下:

  Child.prototype = new Parent()

  但这样又会带来另一个问题,即父对象的构造器会被调用两次,一次是在父对象构造器借用过程中,另一次是在继承原型过程中。

  要解决这个问题,就要去掉一次父对象构造器的调用,构造器借用不能省略,那么只能去掉后一次调用,实现继承原型的另一方法就是迭代复制:

  代码如下:

  extend2(Child, Parent)

  使用之前实现的extend2()方法即可。

时间: 2024-08-22 18:54:49

JavaScript的9种继承实现方式归纳的相关文章

JavaScript的9种继承实现方式归纳_javascript技巧

不同于基于类的编程语言,如 C++ 和 Java,JavaScript 中的继承方式是基于原型的.同时由于 JavaScript 是一门非常灵活的语言,其实现继承的方式也非常多. 首要的基本概念是关于构造函数和原型链的,父对象的构造函数称为Parent,子对象的构造函数称为Child,对应的父对象和子对象分别为parent和child. 对象中有一个隐藏属性[[prototype]](注意不是prototype),在 Chrome 中是__proto__,而在某些环境下则不可访问,它指向的是这个

javascript的几种继承方法介绍_基础知识

1.原型链继承:构造函数.原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针.确认原型和实例之间的关系用instanceof. 原型链继承缺点:字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数 function Parent(){ this.name='mike'; } function Child(){ this.age=12; } //儿子继承父亲(原型链) Child.prototype

Javascript的一种代码结构方式——插件式

上几周一直在做公司的webos的前端代码的重构,之中对javascript的代码进行了重构(之前的代码耦合严重.拓展.修改起来比较困难),这里总结一下当中使用的一种代码结构--插件式(听起来怎么像独孤九剑一样.....). 代码结构  这直接上代码结构图(Javascript部分) ps:箭头的指向A->B,表示A调用B 由上面可以看到四种类型的东西: 控制类:提供一个全局的命名空间.保存上下文信息.组件.组件提供的全局方法,负责调用组件初始化. 代码示例如下(不完整): var webos=

Javascript学习总结:Js继承的两种方式

文章简介:总结就是利用对象冒充机制的call方法把父类的属性给抓取下来,而成员方法尽量写进被所有对象实例共享的prototype域中,以防止方法副本重复创建.然后子类继承父类prototype域来抓取下来所有的方法.如想彻底理清这些调用链的关系,推荐大家多关注Js中prototype的constru 一直想对Javascript再次做一些总结,正好最近自己写了一个小型Js UI库,总结了一下Js的继承机制,在网上也看了一些前辈们博客里的总结,感觉分析不是特别全面.这里仅仅是把自己的学习体会拿出来

Javascript中的几种继承方式对比分析_基础知识

开篇从'严格'意义上说,javascript并不是一门真正的面向对象语言.这种说法原因一般都是觉得javascript作为一门弱类型语言与类似java或c#之类的强型语言的继承方式有很大的区别,因而默认它就是非主流的面向对象方式,甚至竟有很多书将其描述为'非完全面向对象'语言.其实个人觉得,什么方式并不重要,重要的是是否具有面向对象的思想,说javascript不是面向对象语言的,往往都可能没有深入研究过javascript的继承方式,故特撰此文以供交流. 为何需要利用javascript实现继

javascript四种继承方式总结及深入了解

js是一个很自由的语言,没有强类型的语言的那种限制,实现一个功能往往有很多做法.继承就是其中的一个,在js中继承大概可以分为四大类,上面一篇文章也提及过一些,下面开始详细说说js的继承. 1.原型继承---最简单,最常用的       function funcA(){    this.show=function(){        console.log("hello");    }}function funcB(){ }funcB.prototype=new funcA();var

Javascript中3种实现继承的方法和代码实例_javascript技巧

继承是我们在实现面向对象编程的时候很重要的一个手段.虽然我们讲不能过度继承,多利用组合代替继承,但是继承总是免不了的.这里要讨论的就是Javascript中的继承机制. Javascript中实际上是没有继承的概念的,但是我们可以通过一些手段来模仿实现它.这种继承实际上把一个对象复制到另外一个对象内部.你需要注意的是所有的本地类和宿主类是不能作为基类被继承的,主要是为了安全方面的考虑. Javascript中的继承大约有三类:1.对象冒充:2.原型继承:3.二者的混合. 一.对象冒充 其实对象冒

JavaScript是如何实现继承的(六种方式)_javascript技巧

前言:大多OO语言都支持两种继承方式: 接口继承和实现继承 ,而ECMAScript中无法实现接口继承,ECMAScript只支持实现继承,而且其实现继承主要是依靠 原型链 来实现. 1.原型链 基本思想:利用原型让一个引用类型继承另外一个引用类型的属性和方法. 构造函数,原型,实例之间的关系:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针. 原型链实现继承例子: function SuperType() { this.property

浅析四种常见的Javascript声明循环变量的书写方式_基础知识

Javascript中的循环变量声明,到底应该放在哪儿? 习惯1:不声明直接使用 function loop(arr) { for (i = 0; i < arr.length; i++) { // do something } } 非常危险的使用习惯,一般情况下循环变量将成为window对象上的一个属性被全局使用,极有可能影响程序的正常逻辑实现. 需要着重提一下的是,在strict模式下,未声明变量而直接赋值的使用方式会直接抛出异常,早就该这么做啦!引用一下ecma-262标准附录C中的一段话