Operator null'owego scalania (??)

Operator null'owego scalania (??) - to operator logiczny, stosowany w wyrażeniach, który zwraca to co jest po jego lewej stronie, tak długo, jak to nie jest null, albo undefined, wtedy zwraca to, co jest po prawej.

Początkowo ewaluowane jest tylko wyrażenie z lewej strony.
Dopiero, gdy zachodzi taka potrzeba, ewaluowane jest prawe wyrażenie.

Jest podobny do logicznego operatora LUB (||)Logical OR (||) ), ale LUB decyduje o tym, czy wziąć prawą wartość, na innej podstawie:
Czy lewa wartość jest fałszopodobna (Falsy)?
null i undefined są fałszopodobne, ale sęk w tym, że nie tylko one, ale również np. 0, czy "". Czasem nie chce się podmieniać niczego za 0 i "", uważając je za dopuszczalne wartości.
Wtedy właśnie przydaje się ten operator.

Składnia

LWyr ?? PWyr
Pole Opis
LWyr Wyrażenie główne, ewaluowane na początku.
Jeżeli jego wartość wynikowa jest null, albo undefined, to nie ona zostanie zwrócona, ale wartość wynikowa wyrażenia PWyr
PWyr Wyrażenie zamienne, ewaluowane, kiedy wartość LWyr jest null, albo undefined.

Przykłady

Użycie operatora

W tym przykładzie użyjemy operatora null'owego scalania do podania zapasowych wartości, przy inicjowaniu stałych:

const wartoscNull = null;
const warA = wartoscNull ?? "domyślne dla A";
console.log(warA);

// konsola: "domyślne dla A"



const pustyTekst = "";   // fałszopodobny
const warB = pustyTekst ?? "domyślne dla B";
console.log(warB);

// konsola: ""
// ponieważ "??" reaguje tylko konkretnie na null albo undefined



const jakasLiczba = 42;
const warC = jakasLiczba ?? 0;
console.log(warC);

// konsola: 42

Porównanie działania "??" i "||"

Wcześniej przed pojawieniem się tego operatora, używano LUB (||) (Logical OR (||)):

let liczba;
let zabezpLicz = liczba || 1;
// zmienna "wejscie" nie była nigdy zapełniona żadną wartością,
// więc była "undefined", a undefined jest fałszopodobne, więc
// JavaScript wziął zapasowe '1'.

console.log(2 * zabezpLicz);
// konsola: 2

To działa, ale...
przez to jakim operatorem jest LUB, nie tylko undefined zostanie tu zamienione, ale też i 0, które, w kontekście tego przykładu, powiedzmy, że jest wartością, która powinna być ok:

let liczba = 0;
let zabezpLicz = liczba || 1;
// zmienna "wejscie" została zapełniona zerem, ale jest fałszopodobne,
//więc JavaScript wziął zapasowe '1'.

console.log(2 * zabezpLicz);
// konsola: 2
// chcieliśmy: 0

Operator null'owego scalania rozwiązuje ten problem:

let liczba = 0;
let zabezpLicz = liczba ?? 1;
// zmienna "wejscie" została zapełniona zerem,
//mimo tego, że jest fałszopodobne, "??" akceptuje je, bo to nie null, ani undefined, i
//JavaScript zostawia '0'.

console.log(2 * zabezpLicz);
// konsola: 0

Pomijanie ewaluacji

Podobnie jak inne operatory logiczne LUB (Logical OR (||)) i I (Logical AND (&&)), ten operator rozpoczyna od ewaluacji wyrażenia po lewej stronie, i dopiero gdy trzeba, zajmuje się prawą stroną:

function A(){
  console.log("Tu A!");
  return false;
}
function B(){
  console.log("Tu B!");
  return true;
}

if( A() ?? B() ) console.log("Otrzymano 'true'");
else console.log("Otrzymano 'false'");
// konsola: "Otrzymano 'false'"

i :

function A(){
  console.log("Tu A!");
  return null; // teraz tu jest zwracany null, na który reaguje "??"
}
function B(){
  console.log("Tu B!");
  return true;
}

if( A() ?? B() ) console.log("Otrzymano 'true'");
else console.log("Otrzymano 'false'");
// konsola: "Otrzymano 'true'"

Nie działa seryjnie, ani z LUB, ani z I

W wyrażeniach, nie można stawiać zwyczajnie operatora null'owego scalania w otoczeniu operatorów LUB i I, mimo ustalonej kolejności wykonywania działań. Będzie to odbierane jako błąd składniowy:

null || undefined ?? "yyy..."; // to będzie SyntaxError
true || undefined ?? "aha.";   // to też będzie SyntaxError
//bo tu chodzi o to, że jest "||" i "??" razem, nie o wartości.

Trzeba je rozdzielić nawiasami:

(null || undefined) ?? "yyy...";  // zwraca: "yyy..."
null || (undefined ?? "działa!"); // zwraca: "działa!"

Odniesienie do Operatora opcjonalnego dostępu "?."

Sposób w który operator null'owego scalania pracuje z wartościami null i undefined, jest intuicyjny analogicznie u operatora opcjonalnego dostępu (Optional chaining (?.), i razem pozwalają na ciekawe akcje, np.:

class objekt{
  tekst;

  constructor(tekst = null){
    this.tekst = tekst;
  }

  odczytTekst(){
    return this.tekst;
  }
  wpisTekst(w){
    this.tekst = w;
    return true;
  }
}

let objekty = [new objekt("cześć!"), null, new objekt()];



// zadanie kodu: zamień tekst'y w objekt'ach na duże litery,
// używając funkcji dostępu, a pozostawiając puste wartości
// bez zmian


// wersja typeof
for(let i = 0; i < objekty.length; i++)
  if(typeof(objekty[i]) == "object")
    if(typeof(objekty[i].odczytTekst()) == "string")
      objekty[i].wpisTekst(objekty[i].odczytTekst().toUpperCase());


// wersja operatorów "??" i "?."
for(let i = 0; i < objekty.length; i++)
  objekty[i]?.wpisTekst(objekty[i]?.odczytTekst()?.toUpperCase() ?? null);

console.log(objekty);

Czasami użycie tych operatorów upraszcza kod.
Poza tym każda funkcja jest wywoływana najwyżej raz i może to być co kolwiek.

Specyfikacje

Specification
ECMAScript (ECMA-262)
The definition of 'nullish coalescing expression' in that specification.

Wsparcie przeglądarek

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
Nullish coalescing operator (??)Chrome Full support 80Edge Full support 80Firefox Full support 72IE No support NoOpera Full support 67Safari Full support 13.1WebView Android Full support 80Chrome Android Full support 80Firefox Android No support NoOpera Android No support NoSafari iOS Full support 13.4Samsung Internet Android No support Nonodejs Full support 14.0.0

Legend

Full support  
Full support
No support  
No support

Postępy implementacji

Tabelka pod spodem pokazuje codzienny status implementacji tej funkcji. Jest tak, ponieważ nie osiągnięto jeszcze stabilności między przeglądarkami.
Dane są generowane, poprzez przeprowadzanie odpowiednich testów funkcji w Test262, standardowym zestawie testów dla JavaScripta, na najnowszych kompilacjach (en: "nightly build"), lub najnowszych wydaniach (en: "latest release") silników JavaScripta przeglądarek.

Zobacz też...