ES6 类多重继承

IT技术 javascript ecmascript-6
2021-02-02 21:01:20

我已经在BabelJSMDN(根本没有信息)上完成了我的大部分研究,但是如果我在四处寻找有关 ES6 规范的更多信息时不够小心,请随时告诉我。

我想知道 ES6 是否像其他鸭子类型的语言一样支持多重继承。例如,我可以做这样的事情:

class Example extends ClassOne, ClassTwo {
    constructor() {
    }
}

将多个类扩展到新类?如果是这样,解释器会更喜欢 ClassTwo 的方法/属性而不是 ClassOne 吗?

6个回答

检查下面的示例,super方法按预期工作。使用一些技巧甚至instanceof有效(大部分时间):

// base class
class A {  
  foo() {
    console.log(`from A -> inside instance of A: ${this instanceof A}`);
  }
}

// B mixin, will need a wrapper over it to be used
const B = (B) => class extends B {
  foo() {
    if (super.foo) super.foo(); // mixins don't know who is super, guard against not having the method
    console.log(`from B -> inside instance of B: ${this instanceof B}`);
  }
};

// C mixin, will need a wrapper over it to be used
const C = (C) => class extends C {
  foo() {
    if (super.foo) super.foo(); // mixins don't know who is super, guard against not having the method
    console.log(`from C -> inside instance of C: ${this instanceof C}`);
  }
};

// D class, extends A, B and C, preserving composition and super method
class D extends C(B(A)) {  
  foo() {
    super.foo();
    console.log(`from D -> inside instance of D: ${this instanceof D}`);
  }
}

// E class, extends A and C
class E extends C(A) {
  foo() {
    super.foo();
    console.log(`from E -> inside instance of E: ${this instanceof E}`);
  }
}

// F class, extends B only
class F extends B(Object) {
  foo() {
    super.foo();
    console.log(`from F -> inside instance of F: ${this instanceof F}`);
  }
}

// G class, C wrap to be used with new decorator, pretty format
class G extends C(Object) {}

const inst1 = new D(),
      inst2 = new E(),
      inst3 = new F(),
      inst4 = new G(),
      inst5 = new (B(Object)); // instance only B, ugly format

console.log(`Test D: extends A, B, C -> outside instance of D: ${inst1 instanceof D}`);
inst1.foo();
console.log('-');
console.log(`Test E: extends A, C -> outside instance of E: ${inst2 instanceof E}`);
inst2.foo();
console.log('-');
console.log(`Test F: extends B -> outside instance of F: ${inst3 instanceof F}`);
inst3.foo();
console.log('-');
console.log(`Test G: wraper to use C alone with "new" decorator, pretty format -> outside instance of G: ${inst4 instanceof G}`);
inst4.foo();
console.log('-');
console.log(`Test B alone, ugly format "new (B(Object))" -> outside instance of B: ${inst5 instanceof B}, this one fails`);
inst5.foo();

会打印出来

测试 D:扩展 A、B、C -> D 的外部实例:真
从 A -> A 的内部实例:true
从 B -> B 的内部实例:true
从 C -> C 的实例内部:true
从 D -> D 的内部实例:true
——
测试 E:扩展 A、C -> E 的外部实例:真
从 A -> A 的内部实例:true
从 C -> C 的实例内部:true
从 E -> E 的内部实例:true
——
测试 F:扩展 B -> F 的外部实例:真
从 B -> B 的内部实例:true
从 F -> F 的实例内部:true
——
测试 G:包装器单独使用 C 和“新”装饰器,漂亮的格式 -> G 的外部实例:true
从 C -> C 的实例内部:true
——
单独测试 B,丑陋的格式 "new (B(Object))" -> B 的外部实例:false,这个失败
从 B -> B 的内部实例:true

链接摆弄

@Aaron 我们可以做的是使用函数默认参数const B = (B = Object) => class extends B {,然后class F extends B() {用于更漂亮的用法,但更丑陋的 hack Kappa
2021-03-18 21:01:20
const B = (B) => class extends (B||Object) {会让你替换inst5 = new (B(Object)); // instance only B, ugly formatinst5 = new (B());,或者我可能误解了上下文......
2021-03-21 21:01:20
@Aaron 是的,它可以正常工作,直到console.log('from B -> inside instance of B: ${this instanceof B}');女巫失败Right-hand side of 'instanceof' is not an objectconst B = (B = Object) => class extends B {如前所述使用将通过 instanceof 测试,inst5 = new (B());如果您愿意,还可以为您提供用法。
2021-03-30 21:01:20
@Aaron 我不确定我是否在关注您(或者您正在关注我)。如果F extends (B||Object)不是F extends B(Object),它将扩展 B mixin(作为函数),因此 F 将只扩展默认的 Function 原型,因为 B 从未执行过。通过使用,F extends B(Object)我们实际上是在执行 B 函数,而 F 将扩展 B 函数返回的“任何”,在这种情况下,它是 B 函数内部定义的 B 类……保持类命名正确的小技巧。
2021-04-03 21:01:20
您可以通过使 B 扩展来修复 B(Object) 的“丑陋格式” (B||Object)
2021-04-12 21:01:20

一个对象只能有一个原型。从两个类继承可以通过创建一个父对象作为两个父原型的组合来完成。

子类化的语法使得可以在声明中做到这一点,因为extends子句的右侧可以是任何表达式。因此,您可以编写一个根据您喜欢的任何标准组合原型的函数,并在类声明中调用该函数。

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...让我相信类名 [extends] { // class body } 意味着 [extends] 是一个类的数组,这些类都可以混合到新class。那么,“super()”如何工作?
2021-03-17 21:01:20
@qwertymk 请记住,__proto__它本身是一个已弃用的功能。它反映了内部原型链接,但它并不是真正的内部原型链接。
2021-03-25 21:01:20
我一直想知道,有没有办法在__proto__链接上设置一个 getter来将道具查找转发到正确的对象?我试过了,但从来没有让它工作
2021-04-09 21:01:20
@qwertymk 好吧,我不能权威地说这是否绝对不可能。我个人在 JavaScript 中很少使用继承。事实上,就此而言,我很少使用原型。
2021-04-09 21:01:20
这是我想出的解决方案:esdiscuss.org/topic/symbol-for-modifying-property-lookup示例:class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }传递给最后一个构造函数的方法和属性new MultiClass具有最高优先级,它们只是混合到新的原型中。如果使用 ES6 代理重新实现,我认为存在更好的解决方案,但目前还没有足够的原生支持。
2021-04-09 21:01:20

Sergio Carneiro 和 Jon 的实现要求您为除一个类之外的所有类定义初始化函数。这是聚合函数的修改版本,它在构造函数中使用默认参数。还包括我的一些评论。

var aggregation = (baseClass, ...mixins) => {
    class base extends baseClass {
        constructor (...args) {
            super(...args);
            mixins.forEach((mixin) => {
                copyProps(this,(new mixin));
            });
        }
    }
    let copyProps = (target, source) => {  // this function copies all properties and symbols, filtering out some special ones
        Object.getOwnPropertyNames(source)
              .concat(Object.getOwnPropertySymbols(source))
              .forEach((prop) => {
                 if (!prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
                    Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
               })
    }
    mixins.forEach((mixin) => { // outside contructor() to allow aggregation(A,B,C).staticFunction() to be called etc.
        copyProps(base.prototype, mixin.prototype);
        copyProps(base, mixin);
    });
    return base;
}

这是一个小演示:

class Person{
   constructor(n){
      this.name=n;
   }
}
class Male{
   constructor(s='male'){
      this.sex=s;
   }
}
class Child{
   constructor(a=12){
      this.age=a;
   }
   tellAge(){console.log(this.name+' is '+this.age+' years old.');}
}
class Boy extends aggregation(Person,Male,Child){}
var m = new Boy('Mike');
m.tellAge(); // Mike is 12 years old.

此聚合函数将优先选择稍后出现在类列表中的类的属性和方法。

一个小问题是在您的示例中m instanceof Child返回 false。
2021-03-20 21:01:20
当我尝试将它与 react 一起使用时Component,它不起作用。仅供其他可能为此目的想要它的人使用。
2021-03-26 21:01:20
这会覆盖具有相同名称的变量和函数。
2021-03-31 21:01:20

Justin Fagnani描述了一种非常干净(恕我直言)将多个类组合成一个的方法,因为在 ES2015 中,可以使用类表达式创建类

表达式与声明

基本上,就像您可以使用表达式创建函数一样:

function myFunction() {}      // function declaration
var myFunction = function(){} // function expression

你可以对类做同样的事情:

class MyClass {}             // class declaration
var MyClass = class {}       // class expression

表达式在运行时计算,当代码执行时,而声明是预先执行的。

使用类表达式创建 mixin

您可以使用它来创建一个仅在调用函数时动态创建类的函数:

function createClassExtending(superclass) {
  return class AwesomeClass extends superclass {
    // you class body here as usual
  }
}

很酷的一点是,您可以预先定义整个类,并且只在调用函数时决定它应该扩展哪个类:

class A {}
class B {}
var ExtendingA = createClassExtending(A)
var ExtendingB = createClassExtending(B)

如果你想将多个类混合在一起,因为 ES6 类只支持单继承,你需要创建一个包含所有你想混合在一起的类的类链。因此,假设您想创建一个扩展 A 和 B 的类 C,您可以这样做:

class A {}
class B extends A {}
class C extends B {}  // C extends both A and B

问题在于它非常静态。如果您后来决定创建一个扩展 B 但不扩展 A 的 D 类,那么您就有问题了。

但是通过使用类可以是表达式这一事实的一些巧妙技巧,您可以通过不直接将 A 和 B 创建为类,而是将其创建为类工厂(为简洁起见使用箭头函数)来解决这个问题:

class Base {} // some base class to keep the arrow functions simple
var A = (superclass) => class A extends superclass
var B = (superclass) => class B extends superclass
var C = B(A(Base))
var D = B(Base)

请注意我们如何仅在最后一刻决定将哪些类包含在层次结构中。

我的答案似乎更少的代码,它对我有用:

    class Nose {
      constructor() {
        this.booger = 'ready'; 
      }
      
      pick() {
        console.log('pick your nose')
      } 
    }
    
    class Ear {
      constructor() {
        this.wax = 'ready'; 
      }
      
      dig() {
        console.log('dig in your ear')
      } 
    }
    
    class Gross extends Classes([Nose,Ear]) {
      constructor() {
        super();
        this.gross = true;
      }
    }
    
    function Classes(bases) {
      class Bases {
        constructor() {
          bases.forEach(base => Object.assign(this, new base()));
        }
      }
      bases.forEach(base => {
        Object.getOwnPropertyNames(base.prototype)
        .filter(prop => prop != 'constructor')
        .forEach(prop => Bases.prototype[prop] = base.prototype[prop])
      })
      return Bases;
    }

    
    // test it
    
    var grossMan = new Gross();
    grossMan.pick(); // eww
    grossMan.dig();  // yuck!

想要typescript版本
2021-03-26 21:01:20
加一创意!
2021-04-04 21:01:20
一个想法为什么我会得到(使用你的代码,没有任何改变,只是执行)Uncaught TypeError: base.prototype.properties is not a function
2021-04-05 21:01:20
lmao 真是个​​有趣的例子
2021-04-05 21:01:20
@exside 因为我没有在我的代码示例中包含我的属性扩展函数,这就是为什么哈哈。对不起。它是对象上的一个简短扩展函数,它使用标准 js 函数检索属于对象的所有属性。我更改了代码以删除调用。
2021-04-13 21:01:20