Příručka:Kódovací konvence/JavaScript

This page is a translated version of the page Manual:Coding conventions/JavaScript and the translation is 83% complete.

Tato stránka popisuje kódovací konvence pro JavaScript v MediaWiki codebase. Viz také obecné konvence.

Odkazování

Jako náš nástroj pro kvalitu kódu používáme ESLint s přednastavením eslint-config-wikimedia pro kódování většiny našich stylů kódování a pravidel kvality kódu. Na stránce Integrations na eslint.org najdete mnoho textových editorů nebo IDE s pluginy, které poskytují živou zpětnou vazbu při psaní.

Konfigurace Linter

Chcete-li z analýzy vyloučit soubory nebo adresáře (např. knihovny třetích stran), můžete konfigurovat vzory ignorování v ESLint prostřednictvím souboru .eslintignore.[1] Všimněte si, že node_modules je ve výchozím nastavení vyloučen, takže většina repozitářů nemusí nastavovat žádná pravidla ignorování.

Každé repo potřebuje soubor .eslintrc.json v kořenovém adresáři úložiště. Následuje příklad konfiguračního souboru ESLint:

{
	"root": true,
	"extends": [
		"wikimedia/client-es5",
		"wikimedia/jquery",
		"wikimedia/mediawiki"
	],
	"globals": {
		// Project-wide globals.
	},
	"rules": {
		// Project-wide rule variations. Keep to a minimum.
	}
}

Živé příklady viz .eslintrc.json v MediaWiki a VisualEditoru.

Nezapomeňte nastavit "root": true, abyste předešli nechtěnému "magickému dědění" konfigurací ESLint v nesouvisejících nadřazených adresářích, které jste vy nebo server CI mohli nastavit na disku (například mezi rozšířením MediaWiki a jádrem MediaWiki nebo mezi vaším projektovým adresářem a něčím v váš domovský adresář).

Přednastavení eslint-config-wikimedia poskytuje několik profilů, ze kterých si mohou projekty vybrat, jak uznají za vhodné, jako jsou různé jazykové příchutě nebo globální prostředí běhového prostředí. Například:

  • wikimedia/client-es5 - pro kód prohlížeče a očekává podporu ES5
  • wikimedia/client-es6 - pro kód prohlížeče, který používá ES6
  • wikimedia/server - pro kód Node.js 10+ a očekává podporu ES2018
  • wikimedia/qunit - mix pro testy QUnit

Měli byste očekávat použití více souborů .eslintrc.json v repo pro nastavení různých environmentálních očekávání pro různé podadresáře. Tím je zajištěno, že nepoužijete náhodou metody window v kódu serveru, který očekáváte na serveru, nebo že v produkčním kódu neodkážete na QUnit. .eslintrc.json soubory v podadresářích automaticky dědí z konfigurace nadřazeného adresáře, takže musí obsahovat pouze věci, které jsou specifické pro daný adresář (example file).

Průběžná integrace

Projektům se doporučuje, aby prosadily předávání ESLint tím, že do něj zahrnou svůj npm test skript pomocí příkazu "test" do package.json. Více informací o tom najdete na Continuous integration/Entry points.

Pokud má váš projekt poměrně náročný testovací kanál, lze definovat skript npm run lint nebo dokonce npm run lint:js v package.json, aby bylo snadné spouštět pouze ty z příkazového řádku během místního vývoje (abyste nemuseli otevírat každý soubor v editoru a/nebo čekat na CI).

Chcete-li odhalit další funkce příkazového řádku z ESLint (jako je lintování jednotlivých souborů mimo textový editor nebo použití funkce --fix), definujte "eslint" jako vlastní skript v package.json bez jakéhokoli argumentu. Poté jej můžete vyvolat z příkazového řádku následovně:

# Entire directory, recursive
$ npm run eslint -- .
$ npm run eslint -- resources/

# Single file
$ npm run eslint -- resources/ext.foo/bar.js

# Fixer
$ npm run eslint -- --fix resources/ext.foo/

Prázdné místo

Mezery

Používáme následující konvence:

  • Odsazení s tabulátory.
  • Žádné mezery na konci.
  • Oddělení jednoho bloku logicky souvisejícího kódu od druhého pomocí prázdných řádků.
  • Jedna mezera na obou stranách binárních operátorů a operátorů přiřazení.
  • Klíčová slova následovaná "(" (levá závorka) musí být oddělena jednou mezerou. To poskytuje vizuální rozlišení mezi klíčovými slovy a vyvoláním funkcí.
  • Mezi názvem funkce a levou závorkou seznamu argumentů by neměla být žádná mezera.
  • Uvnitř závorek (jako jsou příkazy if, volání funkcí a seznamy argumentů) by měla být jedna mezera.
  • Nepoužívejte operátory, jako by to byly funkce (například delete, void, typeof, new, return, ..).

Tyto a další aspekty našeho průvodce stylem jsou vynuceny pomocí ESLint.

Příklady mezer
Správně Špatně
a.foo = bar + baz;

if ( foo ) {
	foo.bar = doBar();
}

function foo() {
	return bar;
}

foo = function () {
	return 'bar';
};

foo = typeof bar;

function baz( foo, bar ) {
	return 'gaz';
}

baz( 'banana', 'pear' );

foo = bar[ 0 ];
foo = bar[ baz ];
foo = [ bar, baz ];
a.foo=bar+baz;

if( foo ){
	foo.bar = doBar () ;
}

function foo () {
	return bar;
};

foo = function() {
	return('bar');
};

foo = typeof( bar );

function baz(foo, bar) {
	return 'gaz';
}

baz('banana', 'pear');

foo = bar[0];
foo = bar[baz];
foo = [bar,baz];

Délka řádku

Řádky by neměly být delší než 80–100 znaků. Pokud se příkaz nevejde na jeden řádek, rozdělte příkaz na více řádků. Pokračování prohlášení by mělo být odsazeno o jednu úroveň navíc.

Volání funkcí a objekty by měly být buď na jednom řádku, nebo rozděleny na více řádků s jedním řádkem pro každý segment. Vyvarujte se zavírání volání funkce nebo objektu v jiném odsazení, než je jeho otevření.

Příklady zalamování řádků
Ano
// One line
if ( mw.foo.hasBar() && mw.foo.getThis() === 'that' ) {
	return { first: 'Who', second: 'What' };
} else {
	mw.foo( 'first', 'second' );
}

// Multi-line (one component per line)
if (
	// Condition head indented one level.
	mw.foo.hasBar() &&
	mw.foo.getThis() === 'that' &&
	!mw.foo.getThatFrom( 'this' )
) {
    // ↖ Closing parenthesis at same level as opening.
	return {
		first: 'Who',
		second: 'What',
		third: 'I don\'t know'
	};
} else {
	mw.foo(
		[ 'first', 'nested', 'value' ],
		'second'
	);
}
Ne
// No: Mixed one line and multi-line
if ( mw.foo.hasBar() && mw.foo.getThis() === 'that' &&
	!mw.foo.getThatFrom( 'this' ) ) {

	// No: varying number of segments per line.
	return { first: 'Who', second: 'What',
		third: 'I don\'t know' };

} else {
	mw.foo( 'first', 'second',
		'third' );

	// No: Statements looking like they are split over multiple lines but still on line.
	// Visualy looks like a call with one parameter, or with an array as first parameter.
	mw.foo(
		'first', 'second', 'third'
	);

	mw.foo(
		[ 'first', 'nested', 'value' ], 'second'
	);
}

Struktura

Ve zkratce:

// Variables that have literal and cheap initial values
var baz = 42;
var quux = 'apple';

// Local functions
function local( x ) {
	return x * quux.length;
}

// Main statements
var foo = local( baz );
var bar = baz + foo;

Uzavření

Pokud balíček modulů nelze nebo z jiného důvodu ještě není registrován pomocí package files , pak by jeho jednotlivé soubory JavaScriptu měly mít kolem kódu uzávěr na úrovni souboru.[2] To dává kódu jeho vlastní rozsah a zabraňuje úniku proměnných z nebo do jiných souborů, včetně režimu ladění, a způsobem, který je srozumitelný pro statickou analýzu. Tento vzor je známý jako ihned vyvolaný funkční výraz (nebo "iffy").[3]

U souborů balíčků to není potřeba, protože se spouštějí jako soubor "modul" spíše než soubor "script", který má přirozeně svůj vlastní lokální rozsah. ESLint by měl být také nakonfigurován odpovídajícím způsobem (nastavte "no-implicit-globals": "off", jako v tomto příkladu).

Prohlášení

Proměnné musí být deklarovány před použitím. Každé zadání musí být na samostatném řádku. Proměnné mohou být deklarovány blízko nebo při jejich prvním přiřazení.

var waldo = 42;
var quux = 'apple';

var foo, bar;
var flob = [ waldo ];
if ( isFound( waldo ) ) {
    foo = 1;
    bar = waldo * 2;
} else {
    foo = 0;
    bar = 0;
}

for ( var i = 0; i < flob.length; i++ ) {
    // ...
}

Pokud kombinujete více přiřazení var v jednom příkazu, pak deklarace, které nepřiřazují hodnotu, by měly být uvedeny před přiřazením. Následující řádky by měly být odsazeny o další úroveň.

var $content,
	$wrapper = $( '<div>' ),
	$button = $( '<a>' )
		.attr( 'href', '#' )
		.text( 'Test' );

Funkce by měly být deklarovány před použitím. V těle funkce by deklarace funkcí měly následovat po deklaracích proměnných a před jakýmikoli hlavními příkazy.

Komentáře

Komentáře by měly být na samostatném řádku a měly by být nad kódem, který popisují.

V rámci komentáře by měla být úvodní syntaxe (např. lomítko-lomítko nebo lomítko-hvězda) oddělena od textu jednou mezerou a text by měl začínat velkým písmenem. Pokud je komentář platnou větou, měla by být na jejím konci umístěna tečka.

Používejte řádkové komentáře (// foo) ve funkcích a dalších blocích kódu (včetně víceřádkových komentářů).

Používejte blokové komentáře (/* foo */) pouze pro bloky dokumentace. To pomáhá udržovat konzistentní formátování vkládaných komentářů (např. ne některé jako bloky a některé jako víceřádkové komentáře nebo nutnost převádět z jednoho na druhý). Vyhnete se také matoucím dokumentačním enginům. Usnadňuje také deaktivaci částí kódu během vývoje pouhým posunutím zápisu koncového komentáře o několik řádků dolů, aniž by byl zkratován vloženým blokovým komentářem.

Buďte v komentářích liberální a nebojte se kvůli tomu velikosti souboru. Veškerý kód je před odesláním automaticky minimalizován o ResourceLoader .

Komentáře k dokumentaci

  • Text v blocích volného tvaru by měl být velkými a malými písmeny (např. popis metod, parametrů, návratových hodnot atd.)
  • Věty začínejte velkým písmenem.
  • V popisu pokračujte na dalším řádku, odsazeném o jednu mezeru navíc.
/**
 * Get the user name.
 *
 * Elaborate in an extra paragraph after the first one-line summary in
 * the imperative mood.
 *
 * @param {string} foo Description of a parameter that spans over on the
 *  next line of the comment.
 * @param {number} bar
 * @return {string} User name
 */

ES5 classes

To document a class that uses ES5 syntax, with the class and constructor defined together as function MyClass(…) {…}, use:

  • a @classdesc tag to document the class
  • a @description tag to document the constructor
/**
 * @classdesc Class description.
 *
 * @description Constructor description.
 *
 * @param {string} myParam
 * @return {string} Description
 */

ES6 classes

To document a class that uses ES6 syntax, with the constructor defined using constructor(), use separate comments to document the class and the constructor.

/**
 * Class description.
 */
class myClass {
	/**
	 * Constructor description.
	 *
     * @param {string} myParam
     * @return {string} Description
	 */
	constructor() {...}
}

Vygenerovaná dokumentace

K vytvoření dokumentace použijte JSDoc (viz https://doc.wikimedia.org). To set up and publish JSDoc documentation, see JSDoc .

Rovnost

  • Použijte operátory přísné rovnosti (=== a !==) místo (volné) rovnosti (== a !=). Ten druhý dělá typ nátlaku.
  • Nepoužívejte Yodovy podmínky.

Kontroly typů

  • string: typeof val === 'string'
  • number: typeof val === 'number'
  • boolean: typeof val === 'boolean'
  • Function: typeof val === 'function'
  • null: val === null
  • object: val === Object( val )
  • Plain Object: jQuery.isPlainObject( val )
  • Array: Array.isArray( val )
  • HTMLElement: obj.nodeType === Node.ELEMENT_NODE
  • undefined:
    • Místní proměnné: variable === undefined
    • Vlastnosti: obj.prop === undefined
    • Globální proměnné typeof variable === 'undefined'

Řetězce

Pro řetězcové literály používejte jednoduché uvozovky místo dvojitých. Pamatujte, že v JavaScriptu nejsou žádné "kouzelné uvozovky", tj. \n a \t fungují všude.

Chcete-li extrahovat část řetězce, použijte pro konzistenci metodu slice(). Vyhněte se metodám substr() nebo substring(), které jsou nadbytečné, snadno zaměnitelné a mohou mít neočekávané vedlejší účinky.[4][5][6]

Export

Použijte globální hodnoty vystavené prohlížečem (například document, location, navigator) přímo a ne jako vlastnosti objektu window. To zvyšuje důvěru v kód prostřednictvím statické analýzy a může také umožňovat další funkce IDE. Kromě globálů prohlížeče jsou bezpečné k použití pouze mw, $ a OO .

Vyhněte se vytváření nových globálních proměnných. Vyhněte se úpravám globálních prvků, které "nevlastní" váš kód. Například vestavěné globály, jako je String nebo Object, nesmí být rozšířeny o další obslužné metody a podobně by těmto globálům neměly být přiřazeny funkce související s OOjs nebo jQuery.

Chcete-li veřejně vystavit funkce pro opětovné použití, použijte module.exports ze souborů balíčku anebo přiřaďte vlastnosti v rámci hierarchie mw, např. mw.echo.Foo.

Všimněte si, že konfigurační proměnné vystavené MediaWiki musí být přístupné přes mw.config .

Prostředí

Úprava vestavěných prototypů, jako je Object.prototype, je považována za škodlivou. Toto není podporováno v kódu MediaWiki a pravděpodobně to povede k poškození nesouvisejících funkcí.

Pojmenovávání

Všechny proměnné musí být pojmenovány pomocí CamelCase začínající malým písmenem, nebo pokud proměnná představuje nějaký druh konstantní hodnoty, použijte velká písmena (s podtržítky pro oddělení).

Všechny funkce musí být pojmenovány pomocí CamelCase, obvykle začínající malým písmenem, pokud není funkce konstruktorem třídy, v takovém případě musí začínat velkým písmenem. Funkce Metod jsou preferovány se začátečním slovesem, např. getFoo() místo foo().

Zkratky

Jména, která obsahují zkratky, by měla zkratku považovat za normální slovo a podle potřeby používat pouze velké první písmeno. To platí i pro dvoupísmenné zkratky, například Id. Například getHtmlApiSource na rozdíl od "getHTMLAPISource".

jQuery

Odlište uzly DOM od objektů jQuery předponou proměnným znakem dolaru, pokud budou obsahovat objekt jQuery, např. $foo = $( '#bar' ). To pomáhá omezit chyby tam, kde podmínky používají nesprávné podmíněné kontroly, jako je if ( foo ) namísto if ( $foo.length ). Tam, kde metody DOM často vracejí hodnotu null (což je nepravda), metody jQuery vracejí prázdný objekt kolekce (které jsou, stejně jako nativní pole a další objekty v JavaScriptu, pravdivé).

npm

Při publikování samostatného projektu na npmjs.org zvažte jeho publikování pod jmenným prostorem @wikimedia. Všimněte si, že některé samostatné projekty, které jsou zaměřeny na použití mimo komunitu Wikimedia a mají dostatečně jedinečný název, v současnosti používají název balíčku bez jmenných prostorů (např. "oojs" a "visualeditor"). T239742

Vytváření prvků

Chcete-li vytvořit prostý prvek, použijte jednoduchou syntaxi ‎<tag> v konstruktoru jQuery:

$hello = $( '<div>' )
	.text( 'Hello' );

Při vytváření prvků na základě názvu značky z proměnné (která může obsahovat libovolný html):

// Fetch 'span' or 'div' etc.
tag = randomTagName();
$who = $( document.createElement( tag ) );

Only use $('<a title="valid html" href="#syntax">like this</a>'); when you need to parse HTML (as opposed to creating a plain element).

Sbírky

Různé typy kolekcí někdy vypadají podobně, ale mají odlišné chování a mělo by se s nimi tak zacházet. Tento zmatek je většinou způsoben tím, že pole v JavaScriptu vypadají hodně jako pole v jiných jazycích, ale ve skutečnosti jsou pouze rozšířením Object. Používáme následující konvence:

Nepoužívejte smyčku for-in k opakování přes pole (na rozdíl od prostého objektu), protože for-in bude mít za následek mnoho neočekávaných chování, včetně: Klíčů jako řetězců, nestabilního pořadí iterací, indexů může přeskakovat mezery, opakování může zahrnovat jiné nečíselné znaky vlastnosti.

Úložiště

Klíče v localStorage anebo sessionStorage by měly být přístupné přes mw.storage nebo mw.storage.session.

Klíče

eys by měl začínat mw a používat Camel Case anebo spojovníky. Nepoužívejte podtržítka ani jiné oddělovače. Příklady skutečných klíčů:

  • mwuser-sessionId
  • mwedit-state-templatesUsed
  • mwpreferences-prevTab

Dejte si pozor na to, že na rozdíl od souborů cookie přes mw.cookie není ve výchozím nastavení přidána žádná wiki předpona nebo předpona cookie. Pokud se hodnoty musí lišit podle wiki, musíte ručně zahrnout wgCookiePrefix jako součást klíče.

Hodnoty

Hodnoty musí být řetězce. Pozor, pokus o uložení jiných typů hodnot se tiše přetypuje na řetězec (např. z false se stane "false").

Prostor je omezený. Kde je to možné, používejte krátké a výstižné hodnoty před objektovými strukturami. Několik příkladů:

  • Pro booleovský stav (pravda/nepravda, rozbaleno/sbaleno) použijte "1" nebo "0".
  • Hodnoty, které jsou vždy čísly, je uložte tak, jak jsou, a na cestě ven přetypujte s Number (vyhněte se parseInt).
  • Hodnoty, které jsou vždy řetězce, uložte tak, jak jsou.
  • U seznamů softwarově definovaných hodnot zvažte řetězce oddělené čárkami nebo svislou čarou, abyste snížili prostor a náklady na zpracování.
  • Pro seznamy hodnot, které mohou být vytvořeny uživatelem nebo jsou jinak složité povahy, použijte JSON.

Strategie vylučování

Pamatujte, že místní úložiště nemá ve výchozím nastavení žádnou strategii vylučování. Proto je třeba se vyhnout následujícímu:

  • Nepoužívejte jako součást názvu klíče vstup generovaný uživatelem.
  • Vyhněte se klíčům obsahujícím identifikátory entit generovaných uživateli (např. uživatelská jména, názvy kategorií, ID stránek nebo jiné proměnné poskytnuté uživatelem nebo systémem).
  • Obecně se vyhněte přístupům, které zahrnují vytváření potenciálně velkého počtu klíčů úložiště.

Pokud například funkce potřebuje uložit stav proměnné entity (např. aktuální stránky), může mít smysl použít jeden klíč pro tuto funkci jako celek a omezit uložené informace pouze na několik posledních iterací (LRU). . Mírné zvýšení nákladů na vyhledání (celý klíč namísto samostatných menších) se považuje za vhodné, protože jinak by počet klíčů nekontrolovatelně rostl.

I když klíče nezávisí na vstupu uživatele, možná budete chtít pro svou funkci použít jeden klíč, protože jinak budou mít předchozí verze vašeho softwaru uložená data, která nebudete moci vyčistit. Použitím jediného klíče jej můžete přepínat způsobem, který přirozeně nepřetrvává neznámé dílčí vlastnosti.

Použití sledovacího klíče je také anti-vzor a nevyhne se výše uvedeným problémům, protože by mohlo dojít k úniku kvůli závodním podmínkám ve webovém úložišti HTML5, které je sdíleno a není atomické mezi více otevřenými kartami prohlížeče.

Když má být funkce místního úložiště odstraněna, nezapomeňte nejprve implementovat strategii vystěhování, abyste odstranili staré hodnoty. Obvykle se mw.requestIdleCallback používá k elegantnímu vyhledání klíče a jeho odstranění. Viz T121646 pro škálovatelnější přístup.

Osobní údaje

Neukládejte do místního úložiště osobní údaje, které zůstanou, když se uživatel odhlásí nebo zavře prohlížeč. Použijte místo toho úložiště relací. Podívejte se na stránku T179752.

Asynchronní kód

Asynchronní kód by se měl řídit standardem Promise a být s ním kompatibilní.

Pokud definujete asynchronní metodu pro volání jiného kódu, můžete interně vytvořit vrácený objekt thenable pomocí $.Deferred nebo nativního Promise.

Když zavoláte asynchronní metodu, použijte pouze standardní metody kompatibilní s Promise, jako jsou then() a catch(). Vyhněte se používání metod specifických pro jQuery, jako je done() nebo fail(), které by mohly přestat fungovat bez varování, pokud metoda, kterou voláte, interně přejde z $.Deferred na nativní Promise.

Všimněte si, že ve zpětných voláních done a fail existují také jemné starší chování. Při migraci stávajícího kódu z done() na then() věnujte zvýšenou pozornost, protože to může způsobit, že kód přestane správně fungovat. Konkrétně, zpětná volání done a fail vyvolají vaše zpětné volání synchronně, pokud bylo odložené volání již vyřízeno v době, kdy připojíte zpětné volání. To znamená, že vaše zpětné volání může být vyvoláno před dokončením příkazu připojení.

Například:

function getSqrt( num ) { return $.Deferred().resolve( Math.sqrt( num ) ); }

console.log( "A" );
getSqrt( 49 ).done( function ( val ) {
    console.log( "C" ); // can be A C B, or, A B C
} );
console.log( "B" );

console.log( "A" );
getSqrt( 49 ).then( function ( val ) {
    console.log( "C" ); // always A B C
} );
console.log( "B" );

var y = getSqrt( 49 ).then( function ( val ) {
    console.log( y.state(), val ); // "resolved", 7
} );
var x = getSqrt( 49 ).done( function ( val ) {
    console.log( x.state(), val ); // Uncaught TypeError: x is undefined
} );
var z = getSqrt( 49 );
z.done( function ( val ) {
    console.log( x.state(), val ); // "resolved", 7
} );

Závěrečné poznámky

Opětovné použití modulů ResourceLoader

Neobjevujte znovu kolo. Mnoho funkcí JavaScriptu a utilit souvisejících s MediaWiki se dodává s jádrem MediaWiki, které jsou stabilní a můžete se na ně (doslova) spolehnout. Než nasadíte svůj vlastní kód, podívejte se na ResourceLoader/Core modules .

Úskalí

  • Buďte opatrní, abyste zachovali kompatibilitu s jazyky psanými zleva doprava a zprava doleva (tj. float: right nebo text-align: left), zejména při stylování textových kontejnerů. Vložení těchto deklarací do souboru CSS umožní jejich automatické převrácení pro jazyky RTL z CSSJanus na ResourceLoader .
  • Použijte přiměřeně attr() a prop().
    Přečtěte si více:
  • Konzistentní hodnoty selektoru quote atribut: [foo="bar"] místo [foo=bar] (jqbug 8229).
  • Od jQuery 1.4 má konstruktor jQuery novou funkci, která umožňuje předat objekt jako druhý argument, například: $( '<div>', { foo: 'bar', click: function () {}, css: { .. } } );. Toto nepoužívejte. Ztěžuje sledování kódu, selhává u atributů (jako je 'size' (velikost)), které jsou také metodami, a je nestabilní kvůli tomuto míšení metod jQuery s atributy prvků. Budoucí metoda jQuery nebo plugin nazvaný "title" může převést prvek na nadpis, což znamená, že atribut title již nelze nastavit touto metodou. Buďte explicitní a volejte .attr(), .prop(), .on() atd. přímo.

Použijte CSS pro úpravu stylů mnoha prvků

Neaplikujte styl na mnoho prvků najednou. Oslabuje to výkon. Místo toho použijte společnou rodičovskou třídu (nebo jednu přidejte) a použijte CSS v souboru .css nebo .less. Díky ResourceLoaderu se to vše načte ve stejném požadavku HTTP, takže za samostatný soubor CSS nedochází k žádnému snížení výkonu. Nenastavujte CSS do inline atributů "style", nevkládejte ani prvky "style" z JS.

Poznámky pod čarou