Câu hỏi ES6 Class Nhiều thừa kế


Tôi đã thực hiện hầu hết nghiên cứu của mình về vấn đề này BabelJS và hơn thế nữa MDN (mà không có thông tin gì cả), nhưng xin vui lòng cho tôi biết nếu tôi đã không được cẩn thận, đủ để nhìn xung quanh để biết thêm thông tin về spec ES6.

Tôi tự hỏi có hay không ES6 hỗ trợ đa thừa kế trong cùng một thời trang như các ngôn ngữ vịt gõ khác làm. Ví dụ, tôi có thể làm một cái gì đó như:

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

để mở rộng nhiều lớp vào lớp mới? Nếu vậy, liệu người thông dịch có thích các phương thức / thuộc tính từ ClassTwo hơn ClassOne không?


76
2018-04-26 15:03


gốc


Điều này là không thực sự có thể với cách kế thừa hiện tại làm việc trong js, gần nhất bạn có thể làm là một mixin - qwertymk
Bạn có thể cung cấp một số loại tài liệu tham khảo mà nói rằng điều này là không thể trong spec mới, và nếu như vậy, bạn có thể làm cho nó một câu trả lời để tôi có thể chấp nhận nó? - BTC
Tôi đọc các lớp ES6 mới không thêm bất kỳ chức năng mới nào, chúng chỉ là đường cú pháp. - Oriol
Đây là một cách viết khá tốt về cơ chế của tính năng lớp. - Pointy
@ Oriol, họ là đường cú pháp, nhưng tôi đã tự hỏi nếu đường đó đã làm điều gì đó với nhiều lớp học trong nội bộ. - BTC


Các câu trả lời:


Một đối tượng chỉ có thể có một nguyên mẫu. Kế thừa từ hai lớp có thể được thực hiện bằng cách tạo một đối tượng cha mẹ như là một sự kết hợp của hai nguyên mẫu cha.

Cú pháp cho lớp con làm cho nó có thể làm điều đó trong khai báo, vì phía bên phải của extends mệnh đề có thể là bất kỳ biểu thức nào. Vì vậy, bạn có thể viết một hàm kết hợp các nguyên mẫu theo bất kỳ tiêu chí nào bạn thích, và gọi hàm đó trong khai báo lớp.


39
2018-04-26 15:11



Tôi đã luôn luôn tự hỏi, có cách nào để thiết lập một getter trên __proto__ liên kết để chuyển tiếp tra cứu prop đến đối tượng chính xác? Tôi đã thử nhưng chưa bao giờ làm việc đó - qwertymk
@qwertymk cũng lưu ý rằng __proto__ chính nó là một tính năng không được chấp nhận. Nó phản ánh liên kết nguyên mẫu nội bộ, nhưng nó không có thật không liên kết nguyên mẫu nội bộ. - Pointy
vì vậy không bao giờ có bất kỳ cơ hội nào của hack như vậy bao giờ làm việc? core-js đã làm một cái gì đó tương tự với hỗ trợ weakmap bằng cách sử dụng getters. Nhiều thừa kế sẽ rất tuyệt - qwertymk
@ qwertymk tôi cũng không thể nói với thẩm quyền cho dù đó là chắc chắn không thể. Cá nhân tôi sử dụng thừa kế trong JavaScript rất, rất hiếm khi. Trong thực tế, tôi sử dụng nguyên mẫu khá hiếm khi, cho rằng vấn đề. - Pointy
tôi cũng vậy. Tôi chỉ thích thử thách kỹ thuật;) - qwertymk


Kiểm tra ví dụ của tôi dưới đây, super phương pháp làm việc như mong đợi. Sử dụng một vài thủ thuật thậm chí instanceof công trình (hầu hết thời gian):

// 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();

Sẽ in ra

Kiểm tra D: mở rộng A, B, C -> trường hợp bên ngoài của D: true
từ A -> thể hiện bên trong của A: true
từ B -> bên trong thể hiện của B: true
từ C -> bên trong instance của C: true
từ D -> bên trong instance của D: true
- -
Kiểm tra E: mở rộng A, C -> trường hợp bên ngoài của E: true
từ A -> thể hiện bên trong của A: true
từ C -> bên trong instance của C: true
từ E -> bên trong thể hiện của E: true
- -
Kiểm tra F: mở rộng B -> trường hợp bên ngoài của F: true
từ B -> bên trong thể hiện của B: true
từ F -> bên trong thể hiện của F: true
- -
Kiểm tra G: bao bọc để sử dụng C một mình với trình trang trí "mới", định dạng đẹp -> trường hợp bên ngoài của G: true
từ C -> bên trong instance của C: true
- -
Thử nghiệm B một mình, định dạng xấu xí "mới (B (đối tượng))" -> trường hợp bên ngoài của B: sai, điều này không thành công
từ B -> bên trong thể hiện của B: true

Liên kết đến fiddle xung quanh


56
2018-03-10 19:03



Bạn có thể sửa "định dạng xấu" đó của B (đối tượng) bằng cách làm cho B mở rộng (B||Object). - Aaron
@ Aaron Tôi không thực sự chắc chắn tôi đang theo dõi bạn trên này (hoặc bạn đang theo tôi). Nếu F extends (B||Object) thay vì F extends B(Object), nó sẽ mở rộng mixin B như nó (như một hàm) vì vậy F sẽ chỉ mở rộng nguyên mẫu Function mặc định vì B chưa bao giờ được thực thi. Bằng cách sử dụng F extends B(Object) chúng tôi đang thực sự thực hiện chức năng B và F sẽ mở rộng 'bất cứ điều gì' chức năng B trả về, trong trường hợp này nó là lớp B được định nghĩa bên trong chức năng B ... hack nhỏ để giữ cho các lớp đặt tên đúng. - Poelinca Dorin
@Aaron những gì chúng ta có thể làm là sử dụng các tham số mặc định của hàm const B = (B = Object) => class extends B { và sau đó sử dụng class F extends B() { cho một cách sử dụng đẹp hơn, nhưng hack xấu hơn Kappa - Poelinca Dorin
const B = (B) => class extends (B||Object) { sẽ cho phép bạn thay thế inst5 = new (B(Object)); // instance only B, ugly format với inst5 = new (B());hoặc có lẽ tôi hiểu sai ngữ cảnh ... - Aaron
@Aaron yes sẽ hoạt động tốt cho đến khi console.log('from B -> inside instance of B: ${this instanceof B}'); phù thủy sẽ thất bại Right-hand side of 'instanceof' is not an object. Sử dụng const B = (B = Object) => class extends B { như đã đề cập trước đó sẽ vượt qua kiểm tra instanceof và cung cấp cho bạn inst5 = new (B()); sử dụng nếu bạn muốn như vậy. - Poelinca Dorin


Triển khai của Sergio Carneiro và Jon yêu cầu bạn định nghĩa hàm khởi tạo cho tất cả trừ một lớp. Đây là một phiên bản sửa đổi của hàm tổng hợp, sử dụng các tham số mặc định trong các hàm tạo. Bao gồm cũng là một số ý kiến ​​của tôi.

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

Đây là một bản demo nhỏ:

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.

Hàm tổng hợp này sẽ thích các thuộc tính và phương thức của một lớp xuất hiện sau trong danh sách lớp.


11
2017-07-26 16:45



khi tôi thử dùng nó với phản ứng Component, nó không hoạt động. chỉ FYI cho bất cứ ai khác có thể đã muốn nó cho mục đích này. - r3wt
Điều đó ghi đè các biến và hàm có cùng tên. - Vincent Hoch-Drei


Điều này là không thực sự có thể với cách thức làm việc thừa kế nguyên mẫu. Chúng ta hãy xem cách đạo cụ kế thừa hoạt động trong js

var parent = {a: function() { console.log('ay'); }};
var child = Object.create(parent);
child.a() // first look in child instance, nope let's go to it's prototype
          // then look in parent, found! return the method

chúng ta hãy xem điều gì sẽ xảy ra khi bạn truy cập một prop không tồn tại:

child.b; // first look in child instance, nope let's go to it's prototype
         // then look in parent, nope let's go to it's prototype
         // then look in Object.prototype, nope let's go to it's prototype
         // then look at null, give up and return undefined

Bạn có thể dùng mixins để có được một số chức năng đó nhưng bạn sẽ không bị ràng buộc muộn:

var a = {x: '1'};
var b = {y: '2'};
var c = createWithMixin([a, b]);
c.x; // 1
c.y; // 2
b.z = 3;
c.z; // undefined

so với

var a = {x: 1}
var o = Object.create(a);
o.x; // 1
a.y = 2;
o.y; // 2

8
2018-04-26 15:11



Chấp nhận câu trả lời của @ Pointy bởi vì anh ấy đã nói về việc mở rộng từ khóa đó là câu hỏi thực tế được đóng khung xung quanh và không phải là mẫu thừa kế, nhưng cảm ơn bạn đã quan tâm! - BTC


Justin Fagnani mô tả một cách rất gọn gàng (imho) để soạn nhiều lớp thành một lớp bằng cách sử dụng thực tế trong ES2015, các lớp có thể được tạo với lớp biểu thức.

Biểu thức so với khai báo

Về cơ bản, giống như bạn có thể tạo một hàm với một biểu thức:

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

bạn có thể làm tương tự với các lớp:

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

Biểu thức được đánh giá ở thời gian chạy, khi mã thực hiện, trong khi một khai báo được thực hiện trước đó.

Sử dụng biểu thức lớp để tạo mixin

Bạn có thể sử dụng điều này để tạo ra một hàm chỉ tự động tạo lớp khi hàm được gọi:

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

Điều thú vị về nó là bạn có thể định nghĩa toàn bộ lớp trước và chỉ quyết định lớp nào sẽ mở rộng theo thời gian bạn gọi hàm:

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

Nếu bạn muốn kết hợp nhiều lớp với nhau, vì các lớp ES6 chỉ hỗ trợ kế thừa đơn, bạn cần phải tạo một chuỗi các lớp có chứa tất cả các lớp mà bạn muốn kết hợp với nhau. Vì vậy, giả sử bạn muốn tạo một lớp C mở rộng cả A và B, bạn có thể thực hiện điều này:

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

Vấn đề với điều này là nó rất tĩnh. Nếu sau đó bạn quyết định bạn muốn tạo một lớp D mở rộng B nhưng không phải là A, bạn có một vấn đề.

Nhưng với một số thủ thuật thông minh bằng cách sử dụng thực tế là các lớp có thể là biểu thức, bạn có thể giải quyết điều này bằng cách tạo A và B không trực tiếp dưới dạng lớp, nhưng là nhà máy lớp (sử dụng các hàm mũi tên để ngắn gọn):

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)

Lưu ý cách chúng ta chỉ quyết định vào thời điểm cuối cùng mà các lớp cần đưa vào trong hệ thống phân cấp.

Giúp chúng tôi xây dựng tương lai!

Tất nhiên nếu bạn giống như tôi, điều này truyền cảm hứng cho bạn để xây dựng thư viện cuối cùng cho nhiều thừa kế trong Javascript. Nếu bạn đang lên đến nó, xin vui lòng giúp tôi làm chính xác điều đó! Kiểm tra dự án này và giúp đỡ nếu bạn có thể!

mics

mics (phát âm: mix) là một thư viện làm cho nhiều kế thừa trong Javascript trở nên dễ dàng. Lấy cảm hứng từ bài đăng tuyệt vời trên blog "Real" Mixins with Javascript Classes của Justin Fagnani, mics cố gắng xây dựng một thư viện tối thiểu xung quanh khái niệm sử dụng các biểu thức lớp (các nhà máy) làm mixin. mics mở rộng các khái niệm được trình bày trong bài đăng blog bằng cách tạo các mixin công dân hạng nhất có thể được sử dụng trực tiếp để khởi tạo các đối tượng và có thể được trộn lẫn với các mixin khác thay vì chỉ với các lớp.


4
2017-10-02 11:31





Tôi sẽ tìm ra giải pháp sau:

'use strict';

const _         = require( 'lodash' );

module.exports  = function( ParentClass ) {

    if( ! ParentClass ) ParentClass = class {};

    class AbstractClass extends ParentClass {
        /**
         * Constructor
        **/
        constructor( configs, ...args ) {
            if ( new.target === AbstractClass )
                throw new TypeError( "Cannot construct Abstract instances directly" );

            super( args );

            if( this.defaults === undefined )
                throw new TypeError( new.target.name + " must contain 'defaults' getter" );

            this.configs = configs;
        }
        /**
         * Getters / Setters
        **/
        // Getting module configs
        get configs() {
            return this._configs;
        }
        // Setting module configs
        set configs( configs ) {
            if( ! this._configs ) this._configs = _.defaultsDeep( configs, this.defaults );
        }
    }

    return AbstractClass;
}

sử dụng:

const EventEmitter  = require( 'events' );
const AbstractClass = require( './abstracts/class' )( EventEmitter );

class MyClass extends AbstractClass {
    get defaults() {
        return {
            works: true,
            minuses: [
                'u can have only 1 class as parent wich was\'t made by u',
                'every othere classes should be your\'s'
            ]
        };
    }
}

Miễn là bạn làm cho những thủ thuật này với các lớp học viết tùy chỉnh của bạn, nó có thể bị xích lại. nhưng chúng tôi sớm như u muốn mở rộng một số chức năng / lớp học bằng văn bản không như thế - bạn sẽ không có cơ hội để tiếp tục vòng lặp.

const EventEmitter  = require( 'events' );
const A = require( './abstracts/a' )(EventEmitter);
const B = require( './abstracts/b' )(A);
const C = require( './abstracts/b' )(B);

làm việc cho tôi trong nút v5.4.1 với --harmony flag


2
2018-01-19 00:00



Tôi không nghĩ bạn cần cờ hài hòa cho nút 4x trở lên. - Umayr


Từ trang es6-features.org/#ClassInheritanceFromExpressions, có thể viết một hàm tổng hợp để cho phép đa thừa kế:

lớp Rectangle mở rộng tập hợp (Shape, Colored, ZCoord) {}

var aggregation = (baseClass, ...mixins) => {
    let base = class _Combined extends baseClass {
        constructor (...args) {
            super(...args)
            mixins.forEach((mixin) => {
                mixin.prototype.initializer.call(this)
            })
        }
    }
    let copyProps = (target, source) => {
        Object.getOwnPropertyNames(source)
            .concat(Object.getOwnPropertySymbols(source))
            .forEach((prop) => {
            if (prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
                return
            Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop))
        })
    }
    mixins.forEach((mixin) => {
        copyProps(base.prototype, mixin.prototype)
        copyProps(base, mixin)
    })
    return base
}

Nhưng điều đó đã được cung cấp trong các thư viện như  tập hợp.


2
2018-04-01 14:25