JavaScript 中继续完成体式格局归结

差别于基于类的编程言语,如 C++ 和 Java,JavaScript 中的继承体式格局是基于原型的。同时由于 JavaScript 是一门异常天真的言语,其完成继承的体式格局也异常多。

主要的基础概念是关于组织函数和原型链的,父对象的组织函数称为Parent,子对象的组织函数称为Child,对应的父对象和子对象分别为parentchild

对象中有一个隐蔽属性[[prototype]](注重不是prototype),在 Chrome 中是__proto__,而在某些环境下则不可接见,它指向的是这个对象的原型。在接见任何一个对象的属性或要领时,首先会搜刮本对象的一切属性,假如找不到的话则会依据[[prototype]]沿着原型链逐渐搜刮其原型对象上的属性,直到找到为止,不然返回undefined

原型链继承

原型链是 JavaScript 中完成继承的默许体式格局,假如要让子对象继承父对象的话,最简朴的体式格局是将子对象组织函数的prototype属性指向父对象的一个实例:

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

这个时刻,Childprototype属性被重写了,指向了一个新对象,然则这个新对象的constructor属性却没有准确指向Child,JS 引擎并不会自动为我们完成这件事情,这须要我们手动去将Child的原型对象的constructor属性从新指向Child:

javascriptChild.prototype.constructor = Child

以上就是 JavaScript 中的默许继承机制,将须要重用的属性和要领迁移至原型对象中,而将不可重用的部份设置为对象的本身属性,但这类继承体式格局须要新建一个实例作为原型对象,效力上会低一些。

原型继承(非原型链)

为了防止上一个要领须要反复建立原型对象实例的题目,可以直接将子对象组织函数的prototype指向父对象组织函数的prototype,如许,一切Parent.prototype中的属性和要领也能被重用,同时不须要反复建立原型对象实例:

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

然则我们晓得,在 JavaScript 中,对象是作为援用范例存在的,这类要领实际上是将Child.prototypeParent.prototype中保留的指针指向了同一个对象,因而,当我们想要在子对象原型中扩大一些属性以便今后继承继承的话,父对象的原型也会被改写,由于这里的原型对象实例一直只要一个,这也是这类继承体式格局的瑕玷。

暂时组织器继承

为了处理上面的题目,可以借用一个暂时组织器起到一个中间层的作用,一切子对象原型的操纵都是在暂时组织器的实例上完成,不会影响到父对象原型:

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

同时,为了可以在子对象中接见父类原型中的属性,可以在子对象组织器上到场一个指向父对象原型的属性,如uber,如许,可以在子对象上直接经由过程child.constructor.uber接见到父级原型对象。

我们可以将上面的这些事情封装成一个函数,今后挪用这个函数就可以轻易完成这类继承体式格局了:

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

然后就可以如许挪用:

javascriptextend(Dog, Animal)

属性拷贝

这类继承体式格局基础没有转变原型链的关联,而是直接将父级原型对象中的属性悉数复制到子对象原型中,固然,这里的复制仅仅适用于基础数据范例,对象范例只支撑援用通报。

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

这类体式格局对部份原型属性举行了重修,构建对象的时刻效力会低一些,然则可以削减原型链的查找。不过我个人以为这类体式格局的长处并不显著。

对象间继承

除了基于组织器间的继承要领,还可以抛开组织器直接举行对象间的继承。即直接举行对象属性的拷贝,个中包括浅拷贝和深拷贝。

浅拷贝

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

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

拷贝完成今后关于新对象中须要改写的属性可以举行手动改写。

深拷贝

浅拷贝的题目也不言而喻,它不能拷贝对象范例的属性而只能通报援用,要处理这个题目就要运用深拷贝。深拷贝的重点在于拷贝的递归挪用,检测到对象范例的属性时就建立对应的对象或数组,并一一复制个中的基础范例值。

javascriptfunction 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。

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

然则运用instanceof操纵符没法推断来自差别框架的数组变量,但这类状况比较少。

原型继承

借助父级对象,经由过程组织函数建立一个以父级对象为原型的新对象:

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

这里,直接将父对象设置为子对象的原型,ES5 中的 Object.create()要领就是这类完成体式格局。

原型继承和属性拷贝混用

原型继承要领中以传入的父对象为原型构建子对象,同时还可以在父对象供应的属性以外分外传入须要拷贝属性的对象:

javascriptfunction 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
}

多重继承

这类体式格局不触及原型链的操纵,传入多个须要拷贝属性的对象,顺次举行属性的全拷贝:

javascriptfunction 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
}

依据对象传入的递次顺次举行拷贝,也就是说,假如后传入的对象包括和前面对象雷同的属性,后者将会掩盖前者。

组织器借用

JavaScript中的call()apply()要领异常好用,其转变要领实行上下文的功能在继承的完成中也能发挥作用。所谓组织器借用是指在子对象组织器中借用父对象的组织函数对this举行操纵:

javascriptfunction Parent() {}
Parent.prototype.name = 'parent'

function Child() {
    Parent.apply(this, arguments)
}
var child = new Child()
console.log(child.name)

这类体式格局的最大上风就是,在子对象的组织器中,是对子对象的本身属性举行完整的重修,援用范例的变量也会天生一个新值而不是一个援用,所以对子对象的任何操纵都不会影响父对象。

而这类要领的瑕玷在于,在子对象的构建过程当中没有运用过new操纵符,因而子对象不会继承父级原型对象上的任何属性,在上面的代码中,childname属性将会是undefined

要处理这个题目,可以再次手动将子对象组织器原型设为父对象的实例:

javascriptChild.prototype = new Parent()

但如许又会带来另一个题目,即父对象的组织器会被挪用两次,一次是在父对象组织器借用过程当中,另一次是在继承原型过程当中。

要处理这个题目,就要去掉一次父对象组织器的挪用,组织器借用不能省略,那末只能去掉后一次挪用,完成继承原型的另一要领就是迭代复制:

javascriptextend2(Child, Parent)

运用之前完成的extend2()要领即可。

本文同步自我的 GitHub 博客

    原文作者:classicemi
    原文地址: https://segmentfault.com/a/1190000002657451
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞