Alternative alle variabili di classe ES6


493

Attualmente in ES5 molti di noi stanno usando il seguente modello nei framework per creare classi e variabili di classe, il che è comodo:

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

In ES6 è possibile creare classi in modo nativo, ma non è possibile avere variabili di classe:

// ES6
class MyClass {
    const MY_CONST = 'string'; // <-- this is not possible in ES6
    constructor(){
        this.MY_CONST;
    }
}

Purtroppo, quanto sopra non funzionerà, poiché le classi possono contenere solo metodi.

Capisco che posso this.myVar = truein constructor... ma io non voglio 'junk' il mio costruttore, specialmente quando ho 20-30 + params per una classe più grande.

Stavo pensando a molti modi per gestire questo problema, ma non ne ho ancora trovati di buoni. (Ad esempio: creare un ClassConfiggestore e passare un parameteroggetto, che viene dichiarato separatamente dalla classe. Quindi il gestore si collegherebbe alla classe. Stavo pensando WeakMapsdi integrarmi, in qualche modo.)

Che tipo di idee avresti dovuto gestire questa situazione?


1
il tuo problema principale è che avrai una ripetizione this.member = membernel tuo costruttore con 20-30 parametri?
Θεόφιλος Μουρατίδης

1
Non puoi semplicemente usare public variable2 = truein classe? Questo lo definirebbe sul prototipo.

14
@ Θεόφιλος Μουρατίδης: Sì, e anche io voglio usare il mio costruttore per le procedure di inizializzazione e non per le dichiarazioni variabili.
Wintercounter

@derylius: questo è il problema principale, non ha questa caratteristica. Anche l'uso pubblico / privato non è ancora deciso nel progetto ES6. Fai un giro di prova: es6fiddle.net
wintercounter

Secondo l'ultimo, ha questa funzione: wiki.ecmascript.org/doku.php?id=harmony:classes

Risposte:


515

Aggiornamento 2018:

Ora c'è una proposta per la fase 3: non vedo l'ora di rendere obsoleta questa risposta tra qualche mese.

Nel frattempo chiunque usi TypeScript o babel può usare la sintassi:

varName = value

All'interno di un corpo di dichiarazione / espressione di classe e definirà una variabile. Spero che tra qualche mese / settimana potrò pubblicare un aggiornamento.

Aggiornamento: Chrome 74 ora viene fornito con questa sintassi funzionante.


Le note nel wiki ES per la proposta in ES6 ( classi al massimo minime ) notano:

Non esiste (intenzionalmente) alcun modo dichiarativo diretto per definire le proprietà della classe delle proprietà dei dati del prototipo (diverse dai metodi) o la proprietà dell'istanza

Le proprietà della classe e le proprietà dei dati del prototipo devono essere create al di fuori della dichiarazione.

Alle proprietà specificate in una definizione di classe vengono assegnati gli stessi attributi come se fossero visualizzati in un oggetto letterale.

Ciò significa che ciò che stai chiedendo è stato preso in considerazione e deciso esplicitamente contro.

ma perché?

Buona domanda. Le brave persone del TC39 vogliono che le dichiarazioni di classe dichiarino e definiscano le capacità di una classe. Non i suoi membri. Una dichiarazione di classe ES6 definisce il contratto per l'utente.

Ricorda, una definizione di classe definisce i metodi prototipo - la definizione delle variabili sul prototipo non è generalmente qualcosa che fai. Puoi ovviamente usare:

constructor(){
    this.foo = bar
}

Nel costruttore come hai suggerito. Vedi anche il riassunto del consenso .

ES7 e oltre

È in fase di elaborazione una nuova proposta per ES7 che consente variabili di istanza più concise attraverso dichiarazioni ed espressioni di classe - https://esdiscuss.org/topic/es7-property-initializers


4
@wintercounter l'importante è che consentire la definizione delle proprietà le definirebbe sul prototipo come i metodi e non su ogni istanza. Le classi massimamente minime sono ancora alla sua eredità prototipica fondamentale. Quello che vuoi veramente fare nel tuo caso è condividere la struttura e assegnare membri per ogni istanza. Questo non è semplicemente l'obiettivo delle classi in ES6: condividere le funzionalità. Quindi sì, per condividere la struttura dovresti attenersi alla vecchia sintassi. Almeno fino all'ES7 :)
Benjamin Gruenbaum l'

5
Potresti voler menzionare le staticproprietà
Bergi,

8
Ops, scoreggia cerebrale. Ho dimenticato che staticfunziona solo per i metodi.
Bergi,

638
Forse le brave persone di TC39 dovrebbero chiamare questo concetto qualcosa di diverso da "classe" se non vogliono che si comporti come il resto del mondo della programmazione si aspetta da qualcosa chiamato "classe".
Alex

7
Vedi anche la proposta "Campi di classe e proprietà statiche" (già implementata in Babel : github.com/jeffmo/es-class-fields-and-static-properties
Matt Browne

127

Solo per aggiungere alla risposta di Benjamin: le variabili di classe sono possibili, ma non le useresti prototypeper impostarle.

Per una vera variabile di classe vorresti fare qualcosa del tipo:

class MyClass {}
MyClass.foo = 'bar';

Dall'interno di un metodo di classe è possibile accedere a tale variabile come this.constructor.foo(o MyClass.foo).

Queste proprietà della classe non sarebbero generalmente accessibili dall'istanza della classe. cioè MyClass.foo'bar'ma new MyClass().fooèundefined

Se vuoi avere anche accesso alla tua variabile di classe da un'istanza, dovrai definire ulteriormente un getter:

class MyClass {
    get foo() {
        return this.constructor.foo;
    }
}

MyClass.foo = 'bar';

Ho provato questo solo con Traceur, ma credo che funzionerà allo stesso modo in un'implementazione standard.

JavaScript non ha davvero classi . Anche con ES6 stiamo guardando un linguaggio basato su oggetti o prototipi piuttosto che un linguaggio basato su classi. In qualsiasi function X () {}, X.prototype.constructorpunti a X. Quando newsi utilizza l' operatore X, viene creato un nuovo oggetto che eredita X.prototype. Qualsiasi proprietà non definita in quel nuovo oggetto (incluso constructor) viene cercata da lì. Possiamo pensare a questo come alla generazione di proprietà di oggetti e classi.


29

Babel supporta variabili di classe in ESNext, controlla questo esempio :

class Foo {
  bar = 2
  static iha = 'string'
}

const foo = new Foo();
console.log(foo.bar, foo.iha, Foo.bar, Foo.iha);
// 2, undefined, undefined, 'string'

1
Puoi rendere bar una variabile di classe privata fingendola con "#" in questo modo: #bar = 2;
Lonnie Best,

26

Nel tuo esempio:

class MyClass {
    const MY_CONST = 'string';
    constructor(){
        this.MY_CONST;
    }
}

A causa di MY_CONST è primitivo https://developer.mozilla.org/en-US/docs/Glossary/Primitive possiamo semplicemente fare:

class MyClass {
    static get MY_CONST() {
        return 'string';
    }
    get MY_CONST() {
        return this.constructor.MY_CONST;
    }
    constructor() {
        alert(this.MY_CONST === this.constructor.MY_CONST);
    }
}
alert(MyClass.MY_CONST);
new MyClass

// alert: string ; true

Ma se MY_CONSTè un tipo di riferimento come l' static get MY_CONST() {return ['string'];}output di avviso è stringa, falso . In tal caso l' deleteoperatore può fare il trucco:

class MyClass {
    static get MY_CONST() {
        delete MyClass.MY_CONST;
        return MyClass.MY_CONST = 'string';
    }
    get MY_CONST() {
        return this.constructor.MY_CONST;
    }
    constructor() {
        alert(this.MY_CONST === this.constructor.MY_CONST);
    }
}
alert(MyClass.MY_CONST);
new MyClass

// alert: string ; true

E infine per la variabile di classe non const:

class MyClass {
    static get MY_CONST() {
        delete MyClass.MY_CONST;
        return MyClass.MY_CONST = 'string';
    }
    static set U_YIN_YANG(value) {
      delete MyClass.MY_CONST;
      MyClass.MY_CONST = value;
    }
    get MY_CONST() {
        return this.constructor.MY_CONST;
    }
    set MY_CONST(value) {
        this.constructor.MY_CONST = value;
    }
    constructor() {
        alert(this.MY_CONST === this.constructor.MY_CONST);
    }
}
alert(MyClass.MY_CONST);
new MyClass
// alert: string, true
MyClass.MY_CONST = ['string, 42']
alert(MyClass.MY_CONST);
new MyClass
// alert: string, 42 ; true

5
Si prega di evitare l' deleteoperatore, se solo per motivi di prestazioni. Quello che vuoi davvero qui è Object.defineProperty.
Bergi,

@shinzou Stavo pensando la stessa cosa. Non necessariamente per colpa dell'OP; è davvero il linguaggio che manca di meccanismi adeguati per rappresentare i dati in un modo che rifletta le sue relazioni con il mondo reale.
user1974458

17

Poiché il tuo problema è principalmente stilistico (non vuoi riempire il costruttore con un sacco di dichiarazioni), può essere risolto anche stilisticamente.

Per come la vedo io, molti linguaggi basati su classi hanno il costruttore come una funzione che prende il nome dal nome stesso della classe. Stilisticamente potremmo usarlo per rendere una classe ES6 che stilisticamente ha ancora senso ma non raggruppa le azioni tipiche che si svolgono nel costruttore con tutte le dichiarazioni di proprietà che stiamo facendo. Usiamo semplicemente l'attuale costruttore JS come "area di dichiarazione", quindi creiamo una funzione denominata classe che altrimenti trattiamo come "altra roba del costruttore", chiamandola alla fine del vero costruttore.

"usa rigoroso";

classe MyClass
{
    // dichiara solo le proprietà e quindi chiama this.ClassName (); da qui
    costruttore(){
        this.prop1 = 'blah 1';
        this.prop2 = 'blah 2';
        this.prop3 = 'blah 3';
        this.MyClass ();
    }

    // ogni sorta di altre cose "costruttive", non più confuse con le dichiarazioni
    La mia classe() {
        fare qualsiasi cosa();
    }
}

Entrambi verranno chiamati quando viene costruita la nuova istanza.

A Sorta piace avere 2 costruttori in cui separare le dichiarazioni e le altre azioni del costruttore che si desidera intraprendere e stilisticamente non è difficile capire che è quello che sta succedendo.

Trovo che sia uno stile piacevole da usare quando si ha a che fare con molte dichiarazioni e / o molte azioni che devono avvenire durante l'istanza e che vogliono mantenere le due idee distinte l'una dall'altra.


NOTA : non uso molto intenzionalmente le idee idiomatiche tipiche di "inizializzazione" (come un metodo init()o initialize()) perché spesso vengono utilizzate in modo diverso. C'è una sorta di presunta differenza tra l'idea di costruire e inizializzare. Lavorando con i costruttori, le persone sanno che vengono chiamate automaticamente come parte dell'istanza. Vedendo un initmetodo che molte persone assumeranno senza una seconda occhiata che devono fare qualcosa nella forma di var mc = MyClass(); mc.init();, perché in questo modo si inizializza in genere. Non sto cercando di aggiungere un processo di inizializzazione per l'utente della classe, sto cercando di aggiungere al processo di costruzione della classe stessa.

Mentre alcune persone possono fare un doppio take per un momento, questo è in realtà il punto del problema: comunica loro che l'intento è parte della costruzione, anche se questo li fa fare un po 'di doppio take e andare "non lo è come funzionano i costruttori ES6 "e dedica un secondo a guardare il costruttore reale per andare" oh, lo chiamano in fondo, vedo ", è molto meglio di NON comunicare quell'intento (o comunicarlo in modo errato) e probabilmente ottenere un sacco di le persone lo usano male, cercando di inizializzarlo dall'esterno e dalla posta indesiderata. È molto intenzionale allo schema che suggerisco.


Per coloro che non vogliono seguire quel modello, può funzionare anche l'esatto contrario. Inizialmente, inserire le dichiarazioni in un'altra funzione. Forse chiamalo "proprietà" o "publicProperties" o qualcosa del genere. Quindi metti il ​​resto del materiale nel normale costruttore.

"usa rigoroso";

classe MyClass
{
    proprietà() {
        this.prop1 = 'blah 1';
        this.prop2 = 'blah 2';
        this.prop3 = 'blah 3';
    }

    constructor () {
        this.properties ();
        fare qualsiasi cosa();
    }
}

Si noti che questo secondo metodo può sembrare più pulito ma presenta anche un problema intrinseco in cui propertiesviene sovrascritto quando una classe che utilizza questo metodo ne estende un'altra. Dovresti dare nomi più univoci propertiesper evitarlo. Il mio primo metodo non ha questo problema perché la sua metà falsa del costruttore ha un nome univoco dopo la classe.


1
Non utilizzare un metodo prototipo che prende il nome dalla classe stessa. Non è idiomatico in JS, non cercare di farlo sembrare un'altra lingua. Se vuoi davvero usare questo approccio, il nome canonico per il metodo è init.
Bergi,

1
@Bergi - initè un modello usato spesso e spesso pensato per essere chiamato dall'esterno della classe quando l'utente esterno vuole inizializzare, ad es var b = new Thing(); b.init();. Questa è una scelta stilistica al 100% che preferirei comunicare che si tratta di una seconda funzione che viene automaticamente chiamata sfruttando i modelli trovati in altre lingue. È molto meno probabile che qualcuno lo guardi e presuma che debbano chiamare il MyClassmetodo dall'esterno, più probabile che si rendano conto che l'intento è un secondo metodo che agisce nella costruzione (cioè chiamato da solo all'istanza).
Jimbo Jonny,

Potrei rendermene conto osservando il costruttore, ma non dal nome del metodo MyClass.
Bergi,

1
@Bergi - Prendere uno schema da un'altra lingua e applicarlo a JS in un modo che tecnicamente non sta succedendo, ma funziona comunque non completamente senza precedenti. Non puoi dirmi che nessuno ha notato che il $myvarmodo standard di riferirsi alle variabili destinate a contenere oggetti jQuery non è convenientemente simile al modello di avere $ all'inizio delle variabili a cui sono abituati così tanti programmatori PHP. Solo quella piccola implicazione che "sì, non è la stessa cosa esatta ... ma guarda ... è ancora una variabile perché è un modo in cui le variabili sono fatte in alcune lingue!" aiuta.
Jimbo Jonny,

1
Preferisco la tua ultima opzione con la properties()funzione. Tuttavia, le cose diventano un po 'più complicate quando si estendono le classi, quindi suggerirei che se si utilizza una properties()funzione in tutte le classi per dichiarare le proprietà della classe che si prefigge il nome del metodo con il nome della classe (IE: MyClassProperties()al fine di evitare l'override accidentale chiamate di funzione all'interno di sottoclassi. Inoltre, tieni presente che qualsiasi chiamata a super()deve essere dichiarata per prima nel costruttore della classe.
TheDarkIn1978,

14

Che dire del vecchio stile scolastico?

class MyClass {
     constructor(count){ 
          this.countVar = 1 + count;
     }
}
MyClass.prototype.foo = "foo";
MyClass.prototype.countVar = 0;

// ... 

var o1 = new MyClass(2); o2 = new MyClass(3);
o1.foo = "newFoo";

console.log( o1.foo,o2.foo);
console.log( o1.countVar,o2.countVar);

Nel costruttore menzioni solo i vari parametri che devono essere calcolati. Mi piace l'ereditarietà del prototipo per questa funzione: può aiutare a risparmiare molta memoria (nel caso in cui ci siano molti vars non assegnati).


4
Ciò non consente di risparmiare memoria e rende le prestazioni molto peggiori rispetto a quelle dichiarate nel costruttore. codereview.stackexchange.com/a/28360/9258
Esailija

2
Inoltre, questo sconfigge lo scopo dell'utilizzo delle classi ES6 in primo luogo.
Allo stato

4
@Kokodoko real oop - vuoi dire, come ad esempio in Java? Sono d'accordo, ho notato che molte persone arrabbiate con JS perché provano a usarlo come Java, come si abituano, perché la sintassi di JS sembra simile e presumono che funzioni allo stesso modo ... Ma da l'interno, è abbastanza diverso, come sappiamo.
Zarkone,

2
Non si tratta solo di sintassi del linguaggio. La filosofia OOP si presta abbastanza bene alla programmazione in generale, specialmente durante la creazione di app e giochi. JS è stato tradizionalmente implementato nella costruzione di pagine Web che è abbastanza diverso. In qualche modo questi due approcci devono unirsi, dal momento che il web sta diventando sempre più incentrato sulle app.
Kokodoko,

1
@Kokodoko Solo perché è un OOP diverso non significa che non sia OOP. I prototipi sono un approccio OOP valido al 100%; nessuno chiamerà Self "non-OOP" perché usa prototipi. Non abbinare un altro paradigma OOP significa proprio questo: è diverso.
Dave Newton,

13

Come ha detto Benjamin nella sua risposta, TC39 ha esplicitamente deciso di non includere questa funzione almeno per ES2015. Tuttavia, il consenso sembra essere che lo aggiungeranno in ES2016.

La sintassi non è stata ancora decisa, ma esiste una proposta preliminare per ES2016 che ti permetterà di dichiarare proprietà statiche su una classe.

Grazie alla magia di Babele, puoi usarlo oggi. Abilita la trasformazione delle proprietà della classe secondo queste istruzioni e sei a posto. Ecco un esempio della sintassi:

class foo {
  static myProp = 'bar'
  someFunction() {
    console.log(this.myProp)
  }
}

Questa proposta è in uno stato molto precoce, quindi preparati a modificare la sintassi con il passare del tempo.


Sì, non è ES6. Se non sai di cosa si tratta, non dovresti usarlo.
Bergi,

10

[Discussione lunga, non sono sicuro che sia già elencato come opzione ...].
Una semplice alternativa ai soli costanti sarebbe definire la const al di fuori della classe. Questo sarà accessibile solo dal modulo stesso, a meno che non sia accompagnato da un getter.
In questo modo prototypenon è sporca e ottieni const.

// will be accessible only from the module itself
const MY_CONST = 'string'; 
class MyClass {

    // optional, if external access is desired
    static get MY_CONST(){return MY_CONST;}

    // access example
    static someMethod(){
        console.log(MY_CONST);
    }
}

Cosa succede se a) usi a varinvece di const2) usi più istanze di questa classe? Quindi le variabili esterne verranno modificate con ogni istanza della classe
nick carraway

1
fail point @nickcarraway, la mia offerta rappresenta solo const, non come la domanda intitolata.
Hertzel Guinness il

6

ES7 sintassi dei membri della classe:

ES7ha una soluzione per "spazzare" la tua funzione di costruzione. Ecco un esempio:

class Car {
  
  wheels = 4;
  weight = 100;

}

const car = new Car();
console.log(car.wheels, car.weight);

L'esempio sopra dovrebbe apparire come segue ES6:

class Car {

  constructor() {
    this.wheels = 4;
    this.weight = 100;
  }

}

const car = new Car();
console.log(car.wheels, car.weight);

Quando si utilizza questo, tenere presente che questa sintassi potrebbe non essere supportata da tutti i browser e potrebbe essere necessario compilare una versione precedente di JS.

Bonus: una fabbrica di oggetti:

function generateCar(wheels, weight) {

  class Car {

    constructor() {}

    wheels = wheels;
    weight = weight;

  }

  return new Car();

}


const car1 = generateCar(4, 50);
const car2 = generateCar(6, 100);

console.log(car1.wheels, car1.weight);
console.log(car2.wheels, car2.weight);


3
Hai realizzato variabili di istanza, non variabili di classe.
Tjad Clark,

5

Puoi imitare il comportamento delle classi es6 ... e utilizzare le variabili di classe :)

Guarda mamma ... niente lezioni!

// Helper
const $constructor = Symbol();
const $extends = (parent, child) =>
  Object.assign(Object.create(parent), child);
const $new = (object, ...args) => {
  let instance = Object.create(object);
  instance[$constructor].call(instance, ...args);
  return instance;
}
const $super = (parent, context, ...args) => {
  parent[$constructor].call(context, ...args)
}
// class
var Foo = {
  classVariable: true,

  // constructor
  [$constructor](who){
    this.me = who;
    this.species = 'fufel';
  },

  // methods
  identify(){
    return 'I am ' + this.me;
  }
}

// class extends Foo
var Bar = $extends(Foo, {

  // constructor
  [$constructor](who){
    $super(Foo, this, who);
    this.subtype = 'barashek';
  },

  // methods
  speak(){
    console.log('Hello, ' + this.identify());
  },
  bark(num){
    console.log('Woof');
  }
});

var a1 = $new(Foo, 'a1');
var b1 = $new(Bar, 'b1');
console.log(a1, b1);
console.log('b1.classVariable', b1.classVariable);

L'ho messo su GitHub


0

Il modo in cui ho risolto questo, che è un'altra opzione (se hai jQuery disponibile), è stato quello di definire i campi in un oggetto di vecchia scuola e quindi estendere la classe con quell'oggetto. Inoltre, non volevo impacchettare il costruttore con incarichi, questa sembrava essere una soluzione ordinata.

function MyClassFields(){
    this.createdAt = new Date();
}

MyClassFields.prototype = {
    id : '',
    type : '',
    title : '',
    createdAt : null,
};

class MyClass {
    constructor() {
        $.extend(this,new MyClassFields());
    }
};

- Aggiornamento A seguito del commento di Bergi.

Nessuna versione di JQuery:

class SavedSearch  {
    constructor() {
        Object.assign(this,{
            id : '',
            type : '',
            title : '',
            createdAt: new Date(),
        });

    }
}

Finisci ancora con il costruttore "grasso", ma almeno è tutto in una classe e assegnato in un colpo.

EDIT # 2: ora sono andato al punto di partenza e ora sto assegnando valori nel costruttore, ad es

class SavedSearch  {
    constructor() {
        this.id = '';
        this.type = '';
        this.title = '';
        this.createdAt = new Date();
    }
}

Perché? Semplicemente, usando quanto sopra più alcuni commenti JSdoc, PHPStorm è stato in grado di eseguire il completamento del codice sulle proprietà. Assegnare tutte le varianti in un colpo solo è stato bello, ma l'incapacità di codificare le proprietà, imo, non vale il vantaggio (quasi certamente minuscolo) delle prestazioni.


2
Se riesci a fare la classsintassi, puoi anche farlo Object.assign. Non è necessario per jQuery.
Bergi,

Non vedo alcun punto per creare MyClassFieldsun costruttore - non ha alcun metodo. Puoi spiegare perché l'hai fatto (invece di, diciamo, una semplice funzione di fabbrica che restituisce un oggetto letterale)?
Bergi,

@Bergi - Probabilmente forza dell'abitudine piuttosto che altro, a dire il vero. Anche bella chiamata su Object.assign - non lo sapevo!
Steve Childs,

0

Bene, puoi dichiarare le variabili all'interno del costruttore.

class Foo {
    constructor() {
        var name = "foo"
        this.method = function() {
            return name
        }
    }
}

var foo = new Foo()

foo.method()

1
È necessario creare un'istanza di questa classe per utilizzare questa variabile. Le funzioni statiche non possono accedere a quella variabile
Aditya,

0

Tuttavia non puoi dichiarare alcuna classe come in altri linguaggi di programmazione. Ma puoi creare quante più variabili di classe. Ma il problema è l'ambito dell'oggetto di classe. Quindi secondo me, la migliore programmazione OOP in Javascript ES6: -

class foo{
   constructor(){
     //decalre your all variables
     this.MY_CONST = 3.14;
     this.x = 5;
     this.y = 7;
     // or call another method to declare more variables outside from constructor.
     // now create method level object reference and public level property
     this.MySelf = this;
     // you can also use var modifier rather than property but that is not working good
     let self = this.MySelf;
     //code ......... 
   }
   set MySelf(v){
      this.mySelf = v;
   }
   get MySelf(v){
      return this.mySelf;
   }
   myMethod(cd){
      // now use as object reference it in any method of class
      let self = this.MySelf;
      // now use self as object reference in code
   }
}

-1

Questa è una combinazione un po 'di hacker di elettricità statica e funziona per me

class ConstantThingy{
        static get NO_REENTER__INIT() {
            if(ConstantThingy._NO_REENTER__INIT== null){
                ConstantThingy._NO_REENTER__INIT = new ConstantThingy(false,true);
            }
            return ConstantThingy._NO_REENTER__INIT;
        }
}

altrove usato

var conf = ConstantThingy.NO_REENTER__INIT;
if(conf.init)...

7
Ti consiglierei singleton_instancecome nome della proprietà in modo che tutti capiscano cosa stai facendo qui.
Bergi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.