最全最新javascript的继承总结

157 阅读4分钟

继承类型

类式继承

function Parent(){
    ...
}
function Child(){
    ...
}
Child.prototype = new Child();
Child.prototype.childFunc(){
    ...
}

类式继承是直接修改子类的原型对象指向父对象的实例。 它有三个缺点:

  1. 如果父类上的属性有引用属性的话,子类共用这个属性时,子类的某个实例就有可能改变全局的继承实例
  2. 子类无法传参到父类中去初始化实例父类,因为Child.prototype在构造函数外面
  3. 继承单一。

构造函数继承

function Parent(Pparam){
    ...
}
Parent.prototype.parentFunc(){
    ...
}
function Child(Cparam){
    Parent.call(this,Cparam)
    ...
}
new Child(123).parentFunc() //error

它有一个缺点:

  1. 由于只是将父类函数的构造方法里的this指针改变,所有没有涉及父类的原型prototype,所有原型上的方法无法使用。

组合继承

就是将上面的类式继承和构造函数继承组合

function Parent(Pparam){
    ...
}
Parent.prototype.parentFunc(){
    ...
}
function Child(Cparam){
    Parent.call(this,Cparam)
    ...
}
Child.prototype = new Parent();

这个继承方式的缺陷是: 在子类构造函数执行了一边父类的构造函数,而在子类的原型上又实例了一次父类构造函数,这两次的执行不是最优解决方案。

原型式继承

function createChild(obj){
    function F(){};
    F.prototype = obj;
    return new F();
}
function Parent(){
    ...
}
let child = createChild(new Parent())
child.func()=function(){
    ...
}

这个继承方式其实是在类式继承上的一次封装,类式继承存在的问题在它上面任然存在,但是它作为抛砖引玉的功能,所有我们继续下面的继承模式

寄生继承

寄生继承其实就是对原型继承的再一次封装,但是它可以在寄生函数里面做一些子类的定义,寄生的概念就是来源于寄生在函数内部实现功能。

function createChild(obj){
    function F(){};
    F.prototype = obj;
    return new F();
}
function createChildAdd(obj){
    let o = createChild(obj);
    o.func = function(){
        ...
    }
    return o
}
<!--使用-->
function Parent(){
    ...
}
let child = createChildAdd(new Parent())

寄生组合式继承

终极继承方式:将寄生继承和组合继承组合使用

function createChild(obj){
    function F(){};
    F.prototype = obj;
    return new F();
}
//寄生继承
function extendClass(child,parent){
    let p = createChild(parent.prototype);
    p.constructor = child;
    child.prototype = p;
}
<!--使用-->
function Parent(Pparam){
    ...
}
Parent.prototype.parentFunc(){
    ...
}
function Child(Cparam){
    //构造函数继承,继承父类的实例上的属性和方法
    Parent.call(this,Cparam)
    ...
}
//寄生继承,继承父类的原型上的方法
extendClass(Child,Parent);
let child = new Child(12);
child.parentFunc();

使用寄生继承去创建父类原型上的实例挂载到子类上,同时改变子类的构造函数指向子类,这样实例执行instanceof判断才能返回子类,让后用构造函数继承去继承父类构造函数上的方法和属性, 这中既有原型继承,构造函数继承,组合继承,寄生继承的方式就是寄生组合式继承。

深入继承内容

多继承

javascript中只要一条原型链,无法继承多个原型链,所有多继承的实现方式就考虑对象的合并,在对象合并这里又会涉及到对象的浅复制和深复制。

浅复制

function shallowClone(source) {
    let target = {};
    for(leti in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }
    return target;
}

所谓的浅复制就是,只能对javascript中的基本类型进行深拷贝,对引用类型,只是复制的引用类型的指针。 在ES6种的Object.assign()就是浅拷贝

深拷贝

function isObject(x) {
    return (Object.prototype.toString.call(x) === '[object Object]'|| Object.prototype.toString.call(x) === '[object Array]');
}
function clone(source) {
    let target = {};
    for(let i in source) {
        if (source.hasOwnProperty(i)) {
            if (isObject(source[i])) {
                target[i] = clone(source[i]); // 判断仍是对象,就进行递归
            } else {
                target[i] = source[i];
            }
        }
    }
    return target;
}

对数组和对象再次递归进行一次深拷贝,对于函数,直接共用就可以。 对于这块的优化,我们可以思考对递归的优化,因为睡着对象深度的加深,这种浅拷贝的实现还是有点影响性能的。 使用JSON.parse(JSON.stringify(oldObj))可以实现深拷贝,但是由于JSON对函数的限制,所有无法深拷贝函数。

尾调用优化

由于递归的使用,会使调用栈不断增加,可以使用函数的尾调用,来实现执行栈只有一次插入。

<!--计算阶乘-->
//递归使用
function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}
//尾递归
function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。

多态

多态就是同一个方法多种参数调用的方式,在javascript中函数里面的arguments属性能够完成这样的需求

function Compute(){
    function add1(num){
        return 10 + num;
    }
    function add2(num1,num2){
        return num1 + num2;
    }
    function add3(num1,num2,num3){
        return (num1+num2)^num3;
    }
    this.add = function(){
        let len = arguments.length
        switch(){
            case 1:
                return add1(...arguments);
            case 2:
                return add2(...arguments);
            case 3:
                return add3(...arguments);
        }
    }
}