Visste du att DOM-element med ID är tillgängliga i JavaScript som globala variabler? Det är en av de saker som har funnits, typ för evigt, men jag gräver verkligen i det för första gången.
Om det här är första gången du hör talas om det, gör dig beredd! Vi kan se det i aktion helt enkelt genom att lägga till ett ID till ett element i HTML:
Normalt skulle vi definiera en ny variabel med hjälp av querySelector("#cool")
or getElementById("cool")
för att välja det elementet:
var el = querySelector("#cool");
Men vi har faktiskt redan tillgång till #cool
utan det där rigorala:
Så, vilken som helst id
- eller name
attribut, för den delen — i HTML kan nås i JavaScript med hjälp av window[ELEMENT_ID]
. Återigen, det här är inte precis "nytt" men det är verkligen ovanligt att se.
Som du kanske gissar är det inte den bästa idén att komma åt det globala omfånget med namngivna referenser. Vissa människor har kommit att kalla detta "den globala förorenaren". Vi kommer in på varför det är så, men först...
Vissa sammanhang
Denna metod är beskrivs i HTML-specifikationen, där det beskrivs som "namngiven åtkomst på Window
objekt."
Internet Explorer var först med att implementera funktionen. Alla andra webbläsare lade till det också. Gecko var den enda webbläsaren vid den tiden som inte stödde den direkt i standardläge, utan valde istället att göra den till en experimentell funktion. Det var tvekan om att implementera det alls, men det gick vidare i namnet webbläsarkompatibilitet (Gecko försökte till och med övertyga WebKit för att flytta den ur standardläge) och så småningom kom den till standardläge i Firefox 14.
En sak som kanske inte är välkänd är att webbläsare var tvungna att vidta några försiktighetsåtgärder – med varierande grad av framgång – för att säkerställa att genererade globaler inte bryter webbsidan. En sådan åtgärd är...
Variabel skuggning
Den förmodligen mest intressanta delen av den här funktionen är att namngivna elementreferenser inte gör det skugga befintliga globala variabler. Så, om ett DOM-element har en id
som redan är definierad som en global, kommer den inte att åsidosätta den befintliga. Till exempel:
window.foo = "bar";
I won't override window.foo
console.log(window.foo); // Prints "bar"
Och motsatsen är också sant:
I will be overridden :(
window.foo = "bar";
console.log(window.foo); // Prints "bar"
Detta beteende är viktigt eftersom det upphäver farliga åsidosättanden som t.ex
, vilket annars skulle skapa en konflikt genom att ogiltigförklara alert
API. Denna skyddsteknik kan mycket väl vara anledningen till att du - om du är som jag - lär dig om detta för första gången.
Fallet mot namngivna globaler
Tidigare sa jag att det kanske inte är den bästa idén att använda globala namngivna element som referenser. Det finns många anledningar till det, vilket TJ VanToll har täckt fint över på sin blogg och jag ska sammanfatta här:
- Om DOM ändras, så gör referensen det också. Det gör några riktigt "spröda" (specens term för det) kod där separationen av problem mellan HTML och JavaScript kan vara för mycket.
- Oavsiktliga referenser är alldeles för lätta. Ett enkelt stavfel kan mycket väl sluta hänvisa till en namngiven global och ge dig oväntade resultat.
- Det implementeras annorlunda i webbläsare. Till exempel ska vi kunna komma åt ett ankare med en
id
- t.ex— men vissa webbläsare (nämligen Safari och Firefox) returnerar en
ReferenceError
i konsolen. - Det kanske inte returnerar vad du tror. Enligt specifikationen, när det finns flera instanser av samma namngivna element i DOM - säg två instanser av
— webbläsaren ska returnera en
HTMLCollection
med en rad instanser. Firefox returnerar dock bara den första instansen. Sedan igen, specen säger vi borde använda en instans av enid
i ett elements träd ändå. Men att göra det hindrar inte en sida från att fungera eller något liknande. - Kanske finns det en prestationskostnad? Jag menar, webbläsaren måste göra den listan med referenser och underhålla den. Ett par personer körde tester i denna StackOverflow-tråd, där namngivna globaler faktiskt fanns mer presterande i ett test och mindre presterande i ett nyare test.
Ytterligare överväganden
Låt oss säga att vi kastar bort kritiken mot att använda namngivna globaler och använder dem ändå. Allt är bra. Men det finns några saker du kanske vill tänka på när du gör.
Polyfills
Hur extremt det än kan låta är dessa typer av globala kontroller ett typiskt installationskrav för polyfills. Kolla in följande exempel där vi sätter en cookie med den nya CookieStore
API, polyfilling det i webbläsare som inte stöder det ännu:
// Polyfill the CookieStore API if not yet implemented.
// https://developer.mozilla.org/en-US/docs/Web/API/CookieStore
if (!window.cookieStore) {
window.cookieStore = myCookieStorePolyfill;
}
cookieStore.set("foo", "bar");
Den här koden fungerar perfekt i Chrome, men ger följande fel i Safari.:
TypeError: cookieStore.set is not a function
Safari saknar stöd för CookieStore
API när detta skrivs. Som ett resultat appliceras inte polyfillen eftersom img
element-ID skapar en global variabel som krockar med cookieStore
global.
JavaScript API-uppdateringar
Vi kan vända på situationen och hitta ännu ett problem där uppdateringar av webbläsarens JavaScript-motor kan bryta ett namngivet elements globala referenser.
Till exempel:
window.BarcodeDetector.focus();
Det skriptet tar en referens till indataelementet och anropar focus()
på det. Det fungerar korrekt. Ändå vet vi inte hur lång det kommer att fortsätta att fungera.
Du förstår, den globala variabeln vi använder för att referera till inmatningselementet kommer att sluta fungera så snart webbläsare börjar stödja BarcodeDetector
API. Vid den tidpunkten window.BarcodeDetector
global kommer inte längre att vara en referens till indataelementet och .focus()
kommer att kasta en "window.BarcodeDetector.focus
är inte en funktion”-fel.
Bonus: Alla namngivna element genererar inte globala referenser
Vill du höra något roligt? För att lägga förolämpning till skadan är namngivna element tillgängliga som globala variabler endast om namnen inte innehåller annat än bokstav. Webbläsare skapar inte en global referens för ett element med ett ID som innehåller specialtecken och siffror, till exempel hello-world
och item1
.
Slutsats
Låt oss sammanfatta hur vi kom hit:
- Alla större webbläsare skapar automatiskt globala referenser till varje DOM-element med en
id
(eller, i vissa fall, aname
attribut). - Att komma åt dessa element genom deras globala referenser är opålitligt och potentiellt farligt. Använda sig av
querySelector
orgetElementById
istället. - Eftersom globala referenser genereras automatiskt kan de ha vissa biverkningar på din kod. Det är en bra anledning att undvika att använda
id
attribut om du inte verkligen behöver det.
I slutet av dagen är det förmodligen en bra idé att undvika att använda namngivna globaler i JavaScript. Jag citerade specifikationen tidigare om hur den leder till "skör" kod, men här är hela texten för att driva poängen hem:
Som en allmän regel kommer att förlita sig på detta leda till skör kod. Vilka ID:n som slutar mappa till detta API kan variera över tiden, eftersom nya funktioner till exempel läggs till webbplattformen. Använd istället för detta
document.getElementById()
ordocument.querySelector()
.
Jag tror att det faktum att HTML-specifikationen i sig rekommenderar att man håller sig borta från den här funktionen talar för sig själv.