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


Posted in Javascript onMay 18, 2015

不同于基于类的编程语言,如 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()方法即可。
Javascript 相关文章推荐
页面只能打开一次Cooike如何实现
Dec 04 Javascript
防止按钮在短时间内被多次点击的方法
Mar 10 Javascript
jQuery实现仿路边灯箱广告图片轮播效果
Apr 15 Javascript
学习JavaScript事件流和事件处理程序
Jan 25 Javascript
JS针对浏览器窗口关闭事件的监听方法集锦
Jun 24 Javascript
jQuery源码分析之init的详细介绍
Feb 13 Javascript
vue-cli实现多页面多路由的示例代码
Jan 30 Javascript
Node.js的Koa实现JWT用户认证方法
May 05 Javascript
angularJs中跳转到指定的锚点实例($anchorScroll)
Aug 31 Javascript
js实现每日签到功能
Nov 29 Javascript
Vue页面切换和a链接的本质区别详解
Nov 12 Javascript
微信小程序点击生成朋友圈分享图(遇到的坑)
Jun 17 Javascript
JQuery中上下文选择器实现方法
May 18 #Javascript
JQuery中两个ul标签的li互相移动实现方法
May 18 #Javascript
JQuery球队选择实例
May 18 #Javascript
JQuery实现动态添加删除评论的方法
May 18 #Javascript
TypeError document.getElementById(...) is null错误原因
May 18 #Javascript
JQuery实现带排序功能的权限选择实例
May 18 #Javascript
JQuery中clone方法复制节点
May 18 #Javascript
You might like
安装APACHE
2007/01/15 PHP
PHP小程序自动提交到自助友情连接
2009/11/24 PHP
PHP实现下载断点续传的方法
2014/11/12 PHP
php版微信公众平台接口开发之智能回复开发教程
2016/09/22 PHP
详解php与ethereum客户端交互
2018/04/28 PHP
extjs tabpanel限制选项卡数量实现思路及代码
2013/04/02 Javascript
node爬取微博的数据的简单封装库nodeweibo使用指南
2015/01/02 Javascript
jquery右下角自动弹出可关闭的广告层
2015/05/08 Javascript
开启Javascript中apply、call、bind的用法之旅模式
2015/10/28 Javascript
jquery动态遍历Json对象的属性和值的方法
2016/07/27 Javascript
Angular2 多级注入器详解及实例
2016/10/30 Javascript
javascript 操作cookies详解及实例
2017/02/22 Javascript
jQuery+CSS实现的table表格行列转置功能示例
2018/01/08 jQuery
除Console.log()外更多的Javascript调试命令
2018/01/24 Javascript
koa2 数据api中间件设计模型的实现方法
2020/07/13 Javascript
JavaScript实现简易计算器小功能
2020/10/22 Javascript
Python实现简单的可逆加密程序实例
2015/03/05 Python
编写简单的Python程序来判断文本的语种
2015/04/07 Python
在Django框架中伪造捕捉到的URLconf值的方法
2015/07/18 Python
Python将图片批量从png格式转换至WebP格式
2020/08/22 Python
简单了解python关系(比较)运算符
2019/07/08 Python
python实现差分隐私Laplace机制详解
2019/11/25 Python
python爬取招聘要求等信息实例
2020/11/20 Python
欧洲、亚洲、非洲和拉丁美洲的度假套餐:Great Value Vacations
2019/03/30 全球购物
英国名牌服装购物网站:OD’s Designer
2019/09/02 全球购物
英国领先的游戏零售商:GAME
2019/09/24 全球购物
毕业生简历自我评价范文
2014/04/09 职场文书
孩子教育的心得体会
2014/09/01 职场文书
党员民主评议个人总结
2014/10/20 职场文书
打架赔偿协议书范本
2014/10/26 职场文书
商铺租房协议书范本
2014/12/04 职场文书
客房部经理岗位职责
2015/02/02 职场文书
《全神贯注》教学反思
2016/02/22 职场文书
使用 JavaScript 制作页面效果
2021/04/21 Javascript
一篇文章学会Vue中间件管道
2021/06/20 Vue.js
Windows server 2003卸载和安装IIS的图文教程
2022/07/15 Servers