Tillbaka till Utvecklare

JavaScript

Grundläggande JavaScript för testare och utvecklare — språkgrunder, asynkronitet och OOP.

Grunder

const vs let vs var: vilken använder du och varför?
Använd const som standard (bindningen kan inte tilldelas om), let när du behöver tilldela om, och undvik var — den har funktionsomfång och hissas med förvirrande beteende, medan const/let har blockomfång. Observera: const förhindrar omtilldelning, inte mutation: egenskaperna hos ett const-objekt kan fortfarande ändras.
Vad gör map, filter och reduce, och hur skiljer de sig från forEach?
map transformerar varje element och returnerar en ny array av samma längd; filter returnerar en ny array med endast elementen som klarar ett predikat; reduce ackumulerar arrayen till ett enda värde. forEach itererar bara för sidoeffekter och returnerar inget. map/filter/reduce är rena (muterar inte originalet); push/pop/splice/sort muterar.
Vad används destrukturering och spread-operatorn till?
Destrukturering packar upp värden från arrayer eller objekt till variabler: const { name, age } = user, eller i funktionsparametrar function f({ id }). Spread (...) kopierar/slår ihop: const merged = { ...base, env: 'prod' } eller const all = [...a, ...b]. Spread-kopior är ytliga — nästlade objekt delas, klonas inte.
Vad gör optional chaining (?.) och nullish coalescing (??)?
Optional chaining kortsluter till undefined i stället för att kasta fel när ett mellanvärde är null/undefined: res.data?.user?.name. Nullish coalescing returnerar högra sidan endast när vänstra är null eller undefined (inte för 0 eller '', till skillnad från ||): const total = res.data?.order?.total ?? 0. Båda är vanliga när man verifierar formen på API-svar.
I en React-kodningsutmaning definierar en komponent sitt tillstånd med en `let`-variabel istället för den korrekta React-mekanismen. Vad är fel med detta tillvägagångssätt och hur åtgärdar man det?
Att använda en let-variabel för tillstånd i en React-komponent utlöser inte omrenderingar när värdet ändras, så gränssnittet kommer inte att uppdateras. Lösningen är att ersätta let-variabeln med hooken useState. Till exempel, ändra let count = 0 till const [count, setCount] = useState(0), och uppdatera värdet med setter-funktionen (setCount). Detta säkerställer att React spårar tillståndsändringen och omrenderar komponenten korrekt.
Skriv en rekursiv funktion för att beräkna fakulteten av ett tal.
En rekursiv fakultetsfunktion fungerar genom att multiplicera talet med fakulteten av (talet - 1), med ett basfall som returnerar 1 när indata är 0 eller 1: function factorial(n) { if (n == 1 || n == 0) return 1; else return n * factorial(n - 1); } Obs: I en produktionsimplementation bör du också lägga till ett villkor för att hantera negativa tal (t.ex. kasta ett fel eller returnera ett lämpligt värde).
Hur anropar du API-tjänster i en React-applikation?
I React kan du anropa API-tjänster med antingen axios (ett promise-baserat HTTP-klientbibliotek) eller det inbyggda fetch-API:et. Båda låter dig göra HTTP-förfrågningar till externa tjänster från dina komponenter, vanligtvis inom en useEffect-hook.
Hur testar du React-komponenter?
React-komponenter kan testas med Jest som testrunner och assertion-bibliotek, tillsammans med React Testing Library för att rendera komponenter och simulera användarinteraktioner i en testmiljö.
Vilka är de viktigaste områdena att fokusera på när man förbereder sig för en frontend-kodningsutmaning på plattformar som HackerRank?
Viktiga områden att fokusera på inkluderar DOM-manipulation och att konsumera APIs för att visa data i användargränssnittet. Det är avgörande att vara bekväm med att hämta data från endpoints och dynamiskt uppdatera sidan för de flesta frontend-kodningsutmaningar.
Implementera en lösning för logganalys med Node.js streams som läser en loggfil och kategoriserar loggposter efter nivå (ERROR, WARN, DEBUG, INFO).
Det finns två vanliga tillvägagångssätt: **Enkelt tillvägagångssätt med readline:** javascript import fs from "fs"; import readline from "readline"; const stream = fs.createReadStream("app.log"); const rl = readline.createInterface({ input: stream, crlfDelay: Infinity }); const counters = { ERROR: 0, WARN: 0, DEBUG: 0, INFO: 0, UNKNOWN: 0 }; rl.on("line", (line) => { if (line.includes("[ERROR]")) counters.ERROR++; else if (line.includes("[WARN]")) counters.WARN++; else if (line.includes("[DEBUG]")) counters.DEBUG++; else if (line.includes("[INFO]")) counters.INFO++; else counters.UNKNOWN++; }); rl.on("close", () => { console.log("Loggsammanfattning:"); console.log(counters); }); **Med rena Transform streams:** javascript import fs from "fs"; import { Transform } from "stream"; class LogParser extends Transform { constructor() { super({ readableObjectMode: true }); this.buffer = ""; } _transform(chunk, encoding, callback) { this.buffer += chunk.toString(); const lines = this.buffer.split("\n"); this.buffer = lines.pop(); // ofullständig rad for (const line of lines) { this.push(this.parseLine(line)); } callback(); } _flush(callback) { if (this.buffer) { this.push(this.parseLine(this.buffer)); } callback(); } parseLine(line) { if (line.includes("[ERROR]")) return { level: "ERROR", line }; if (line.includes("[WARN]")) return { level: "WARN", line }; if (line.includes("[DEBUG]")) return { level: "DEBUG", line }; if (line.includes("[INFO]")) return { level: "INFO", line }; return { level: "UNKNOWN", line }; } } fs.createReadStream("app.log") .pipe(new LogParser()) .on("data", (log) => { console.log(log); }); **Förbättring med regex för mer flexibel matchning:** javascript const patterns = { ERROR: /\[ERROR\]/, WARN: /\[WARN(ING)?\]/, DEBUG: /\[DEBUG\]/, INFO: /\[INFO\]/ }; function detectLevel(line) { for (const [level, regex] of Object.entries(patterns)) { if (regex.test(line)) return level; } return "UNKNOWN"; } Transform stream-metoden föredras eftersom den bearbetar data bit för bit utan att ladda hela filen i minnet, hanterar ofullständiga rader via en buffert och genererar strukturerade objekt för vidare bearbetning.
Hur skulle du upptäcka överlappande händelser i en lista med tidsbaserade händelser med JavaScript?
Sortera händelse-arrayen efter starttid, iterera sedan genom den sorterade arrayen och jämför varje händelses starttid med föregående händelses sluttid. Om den aktuella händelsens start är före den föregående händelsens slut finns det en överlappning. Här är ett exempel på implementering: const hasOverlappingEvents = (events) => { const sorted = [...events].sort((a, b) => a.start.localeCompare(b.start) ); for (let i = 1; i < sorted.length; i++) { if (sorted[i].start < sorted[i - 1].end) { return true; } } return false; }; Detta tillvägagångssätt har O(n log n) tidskomplexitet på grund av sorteringen, följt av en enda O(n) genomgång av arrayen.
När ska man använda event.preventDefault() i JavaScript?
event.preventDefault() används för att stoppa standardbeteendet hos vissa HTML-element. Det vanligaste användningsfallet är att förhindra att sidan laddas om när ett formulär skickas, vilket är standardbeteendet för ett HTML-formulärs submit-event. Det behövs specifikt när du implementerar en onClick-hanterare på element som en anchor-tagg (<a>) eller en knapp inuti ett formulär med type="submit". Många utvecklare vet vad event.preventDefault() gör, men nyckeln är att känna till rätt användningsfall: man använder det när man vill hantera en händelse med anpassad JavaScript-logik samtidigt som man undertrycker webbläsarens inbyggda svar på den händelsen.
Vilka är vanliga teoretiska JavaScript-frågor i frontend-intervjuer?
Vanliga teoretiska JavaScript-frågor i frontend-intervjuer inkluderar: 1) Hur fungerar hoisting i JS? 2) Vad är event loop och hur fungerar den? 3) Vad är skillnaden mellan var, let och const? 4) Vad är en closure och hur skulle du implementera en? 5) Hur fungerar nyckelordet 'this' i JS? 6) Vad är destructuring och hur gör man det med objekt och arrayer? 7) Vad är skillnaden mellan map och forEach? 8) Vad är skillnaden mellan find och some? 9) Vad är skillnaden mellan == och ===? 10) Hur fungerar promises? 11) Vad gör 'finally' i ett try/catch-block? Dessa är kärnkoncept i språket som hjälper intervjuare att bedöma om en kandidat har tagit sig tid att djupt förstå JavaScripts grunder.
I en React-applikation, varför rekommenderas det generellt att använda CSS-klasser eller en separat CSS-fil istället för inline-stilar?
Att använda inline-stilar i React (via syntaxen style={{}}) anses vara dålig praxis. Även om du bara behöver ett par stildeklarationer är det bättre att skapa en CSS-fil eller använda CSS-klasser. Inline-stilar minskar underhållbarhet, återanvändbarhet och separation of concerns. Under kodgranskningar och tekniska intervjuer kan användning av inline-stilar ses negativt eftersom det indikerar bristande efterlevnad av bästa praxis inom frontendutveckling. Om en CSS-fil redan finns tillgänglig (som i miljöer som CodePen) bör du använda den genom att skapa lämpliga klasser eller ID:n istället för att tillämpa stilar direkt på element.
Vilka är de vanligaste live coding-övningarna i fullstack (React/Node) tekniska intervjuer?
Vanliga live coding-övningar varierar, men ofta rapporterade inkluderar: (1) Algoritmutmaningar som Fibonacci-sekvensen, hitta den kortaste vägen till en nod, och strängmanipulation (t.ex. räkna hur många gånger ett ord kan bildas från en given sträng). (2) För React-specifika intervjuer: implementera en listkomponent, bygga en oändlig scroll, felsöka och fixa en befintlig komponent, refaktorisera en komponent för att separera ansvar, och eliminera onödiga omrenderingar. (3) Backend/generella övningar: konsumera ett API med paginering, och implementera en tabell med server-side paginering. Det rekommenderas att öva på plattformar som LeetCode eller HackerRank på medel till svår nivå, täcka edge cases och sträva efter optimala lösningar.
Vad är `this` i JavaScript?
this i JavaScript är en referens till det aktuella objektet — specifikt det objekt som exekverar eller anropar funktionen vid tidpunkten för anropet. Inom klasser används det för att referera till egenskaper eller metoder hos samma instans. Dess beteende varierar dock beroende på kontexten: i vanliga funktioner refererar this till objektet som anropade funktionen, medan det i arrow functions ärvs från det omslutande lexikala scopet (ett scope uppåt). I grund och botten bestäms this utifrån exekveringskontexten vid körning. Konceptet är analogt med self i Python och this i C# eller Java, där det refererar till attribut eller metoder hos den aktuella objektinstansen.
Vilka React-koncept frågas det ofta om i frontend-intervjuer?
Vanliga React-ämnen som ofta dyker upp i frontend-intervjuer inkluderar: syftet med useState (hantera lokalt komponenttillstånd) och useEffect (hantera sidoeffekter som datahämtning eller prenumerationer), skillnaden mellan state och props (state är internt och muterbart inom komponenten, medan props är externa indata som skickas från en föräldrakomponent och är skrivskyddade), samt konceptet Virtual DOM (en lättviktig in-memory-representation av det verkliga DOM som React använder för att beräkna den minsta uppsättningen ändringar som behövs innan webbläsaren uppdateras).
Skriv en funktion som avgör om en array innehåller några duplicerade element. Tillhandahåll lösningar i både TypeScript och Java.
Två tillvägagångssätt visas nedan, båda med tidskomplexitet O(n). **TypeScript (med Set — idiomatiskt och koncist):** typescript function isDuplicated(array: number[]): boolean { const seen = new Set<number>(); for (const value of array) { if (seen.has(value)) { return true; } seen.add(value); } return false; } Arrayen itereras; om det aktuella värdet redan finns i Set:en har ett duplikat hittats och true returneras omedelbart. Annars läggs värdet till i setet. **Java (med HashMap):** java private static boolean isDuplicated(Integer[] array) { Map<Integer, Integer> tempMemory = new HashMap<>(); for (int i = 0; i < array.length; i++) { // O(n) if (tempMemory.containsKey(array[i])) { return true; } else { tempMemory.put(array[i], 1); } } return false; } Varje element lagras som en nyckel i en HashMap. Om nyckeln redan finns har ett duplikat detekterats. Båda lösningarna avbryter direkt när det första duplikatet hittas.
Hur skulle du implementera en Node.js-lösning som läser en stor loggfil som en stream, analyserar varje rad efter loggnivå (ERROR, DEBUG, INFO, WARNING), skriver matchande rader till separata utdatafiler och tillämpar ytterligare villkorslogik (t.ex. flagga WARNING-rader där ett CPU-värde är ≥ 98%)?
Använd en Node.js Transform stream för att bearbeta loggfilen fragment för fragment utan att ladda hela filen i minnet. Buffra ofullständiga rader mellan fragment, dela sedan på radbrytningar och bearbeta varje fullständig rad. Extrahera loggnivån från varje rad och skicka matchande rader till motsvarande skrivbar utdataström. Tillämpa eventuell ytterligare villkorslogik (t.ex. regex-matchning för CPU-procent) innan skrivning. Exempel: js import fs from "fs"; import { Transform } from "stream"; const input = fs.createReadStream("./data/logfile.log", { encoding: "utf8" }); const outputs = { ERROR: fs.createWriteStream("./data/error.log"), DEBUG: fs.createWriteStream("./data/debug.log"), INFO: fs.createWriteStream("./data/info.log"), WARNING: fs.createWriteStream("./data/warning.log"), HIGHCPU: fs.createWriteStream("./data/highcpu.log"), }; const transformStream = new Transform({ decodeStrings: false, transform(chunk, _, cb) { this.buffer = (this.buffer || "") + chunk; const lines = this.buffer.split("\n"); this.buffer = lines.pop(); for (const line of lines) { if (!line.trim()) continue; const parts = line.split(" - "); if (parts.length < 3) continue; const status = parts[1].trim(); const message = parts.slice(2).join(" - ").trim(); if (outputs[status]) { outputs[status].write(message + "\n"); } if (status === "WARNING") { const match = message.match(/(\d+)%/); if (match && Number(match[1]) >= 98) { outputs.HIGHCPU.write(message + "\n"); } } this.push(line + "\n"); } cb(); }, }); transformStream.on("end", () => { Object.values(outputs).forEach(s => s.end()); }); input.pipe(transformStream); För en enklare sammanfattning (räkna rader per nivå utan att skriva separata filer) räcker readline-gränssnittet över ett createReadStream: js import fs from "fs"; import readline from "readline"; const rl = readline.createInterface({ input: fs.createReadStream("app.log"), crlfDelay: Infinity }); const counters = { ERROR: 0, WARN: 0, DEBUG: 0, INFO: 0, UNKNOWN: 0 }; const patterns = { ERROR: /\[ERROR\]/, WARN: /\[WARN(ING)?\]/, DEBUG: /\[DEBUG\]/, INFO: /\[INFO\]/ }; rl.on("line", line => { const level = Object.entries(patterns).find(([, re]) => re.test(line))?.[0] ?? "UNKNOWN"; counters[level]++; }); rl.on("close", () => console.log(counters)); Viktiga punkter: - Buffra alltid den sista (potentiellt ofullständiga) raden och lägg till den i början av nästa fragment. - Använd readableObjectMode: true på Transform om du vill skicka parsade objekt nedströms istället för råa strängar. - Regex-mönster är renare än kedjade includes-anrop och lättare att utöka (t.ex. matcha både [WARN] och [WARNING]).
Implementera en funktion `merge_coverage(segments)` som tar emot en lista med segment på formen `(member_id, start_day, end_day)`. För samma `member_id` ska segment som överlappar eller berör varandra i ändpunkterna (inklusive) slås ihop. Returnera en dictionary som mappar varje `member_id` till dess lista med sammanslagna `(start_day, end_day)`-tupler. Exempel: ``` segments = [ ("m1", 1, 3), ("m1", 2, 6), ("m1", 8, 10), ("m1", 15, 18), ] // Förväntat: { "m1": [(1, 6), (8, 10), (15, 18)] } segments = [("m1", 1, 4), ("m1", 4, 5)] // Förväntat: { "m1": [(1, 5)] } // ändpunkter som berör varandra slås ihop ```
Algoritmen har tre steg: (1) gruppera segment efter member_id, (2) sortera varje grupp efter start_day, (3) iterera och slå ihop segment vars start är mindre än eller lika med slutet på det aktuella sammanslagna segmentet. javascript function mergeCoverage(segments) { // Steg 1: gruppera efter member_id const groups = {}; for (const [memberId, start, end] of segments) { if (!groups[memberId]) groups[memberId] = []; groups[memberId].push([start, end]); } // Steg 2 och 3: sortera och slå ihop const result = {}; for (const [memberId, segs] of Object.entries(groups)) { segs.sort((a, b) => a[0] - b[0]); const merged = [[...segs[0]]]; for (let i = 1; i < segs.length; i++) { const [start, end] = segs[i]; const last = merged[merged.length - 1]; if (start <= last[1]) { // överlappning eller beröring — förläng det aktuella segmentet last[1] = Math.max(last[1], end); } else { merged.push([start, end]); } } result[memberId] = merged; } return result; } Viktig detalj: sammanslagningsvillkoret är start <= last[1] (inte strikt <) så att ändpunkter som berör varandra, t.ex. [1,4] och [4,5], också slås ihop till [1,5]. En relaterad hjälpfunktion — att upptäcka om två händelser överlappar (utan att slå ihop) — följer samma mönster med sortering och jämförelse: javascript const hasOverlappingEvents = (events) => { const sorted = [...events].sort((a, b) => a.start - b.start); for (let i = 1; i < sorted.length; i++) { if (sorted[i].start < sorted[i - 1].end) return true; } return false; }; Tidskomplexitet: O(n log n) per medlem på grund av sorteringen; minneskomplexitet: O(n).
Givet en array av event-objekt, varje med ett `userId`, ett `start`-tidsstämpel och ett `end`-tidsstämpel, skriv en funktion som returnerar det totala antalet minuter varje användare har tillbringat i sina event.
Använd Array.reduce() för att ackumulera minuter per userId. För varje event beräknas varaktigheten i minuter genom att konvertera start- och slutsträngarna till Date-objekt, subtrahera dem för att få millisekunder och sedan dividera med (1000 * 60). Lägg till det värdet till det löpande totalet för motsvarande userId i ackumulatorobjektet. js // dateUtils.js const calcDiffInMinutes = (start, end) => { const startDate = new Date(start); const endDate = new Date(end); return (endDate - startDate) / (1000 * 60); }; // eventService.js const calculateMinutesByUser = (events) => { return events.reduce((acc, event) => { const minutes = calcDiffInMinutes(event.start, event.end); acc[event.userId] = (acc[event.userId] || 0) + minutes; return acc; }, {}); }; // Exempel const input = [ { userId: 1, start: '2024-01-01T10:00:00', end: '2024-01-01T10:30:00' }, { userId: 1, start: '2024-01-01T11:00:00', end: '2024-01-01T11:45:00' }, { userId: 2, start: '2024-01-01T09:00:00', end: '2024-01-01T10:00:00' } ]; console.log(calculateMinutesByUser(input)); // { '1': 75, '2': 60 }
Givet en array av event-objekt, varje med en `start`- och `end`-tidssträng, skriv en funktion som returnerar `true` om några två event överlappar varandra, och `false` annars.
Sortera event-arrayen efter starttid med hjälp av localeCompare (eftersom strängarna i ISO-format sorteras lexikografiskt). Iterera sedan från det andra elementet och kontrollera om varje events starttid är tidigare än föregående events sluttid. Om så är fallet finns en överlappning. js const hasOverlappingEvents = (events) => { const sorted = [...events].sort((a, b) => a.start.localeCompare(b.start) ); for (let i = 1; i < sorted.length; i++) { if (sorted[i].start < sorted[i - 1].end) { return true; } } return false; }; // Exempel const events = [ { start: '10:00', end: '11:00' }, { start: '10:30', end: '11:30' } ]; console.log(hasOverlappingEvents(events)); // true
Givet en sträng, avgör om den är ett palindrom. Lösningen måste hantera strängar som innehåller mellanslag, skiljetecken och blandad skiftlägeskänslighet (t.ex. ska 'A man, a plan, a canal, Panama' returnera true).
Använd en tvåpekare-strategi: placera en pekare i början av strängen och en annan i slutet. Flytta båda pekarna mot mitten, hoppa över icke-alfanumeriska tecken och jämför återstående tecken utan hänsyn till skiftläge. Om alla motsvarande par matchar när pekarna möts är strängen ett palindrom.
Vilka typer av utmaningar brukar ges i live coding-intervjuer för React frontend-utvecklarpositioner?
Vanliga live coding-utmaningar i React inkluderar: implementera en listkomponent och oändlig scroll, hitta och åtgärda fel i en befintlig komponent (debugging), refaktorera en komponent för att separera ansvar korrekt, samt optimera koden för att undvika onödiga omrenderingar.
Vad är skillnaden mellan throttling och debounce vid hantering av JavaScript-händelser?
Både throttling och debounce är tekniker för att kontrollera hur ofta en funktion körs som svar på händelser. Throttling begränsar körningen genom att blockera eller ignorera anrop som sker oftare än ett angivet intervall, vilket säkerställer att funktionen körs högst en gång per period. Debounce introducerar däremot en fördröjning och väntar tills en viss tid har gått sedan det senaste anropet innan funktionen körs.
Givet en sträng med gemena tecken, returnera indexet för det första unika (icke upprepade) tecknet i strängen. Om inget sådant tecken finns, returnera -1.
Använd en frekvenskarta för att räkna hur många gånger varje tecken förekommer i strängen. Iterera sedan igenom strängen en andra gång och returnera indexet för det första tecknet vars antal är exakt 1. Om inget sådant tecken hittas, returnera -1. Ett vanligt misstag är att bara lagra om tecknet har setts (ett booleskt värde) istället för hur många gånger det har förekommit (ett antal), vilket gör det omöjligt att skilja verkligt unika tecken från upprepade. javascript function firstUniqueChar(s) { const freq = new Map(); for (let i = 0; i < s.length; i++) { const char = s[i]; freq.set(char, (freq.get(char) || 0) + 1); } for (let i = 0; i < s.length; i++) { if (freq.get(s[i]) === 1) { return i; } } return -1; } // Exempel: firstUniqueChar("dummyadummy") => 5 (tecknet 'a' på index 5 förekommer bara en gång) Tidskomplexitet: O(n). Rymdkomplexitet: O(1) eftersom teckenuppsättningen är begränsad (26 gemena bokstäver).
Vad är skillnaden i felhantering mellan promises, callbacks och async/await?
Callbacks använder error-first-konventionen: första argumentet är ett felobjekt (om det finns), och man hanterar det manuellt i varje callback. Promises använder .catch() eller det andra argumentet till .then() för att hantera avvisningar, och fel propageras längs kedjan. Async/await låter dig använda vanliga try/catch-block runt await-anrop, vilket gör felhanteringen synkron och linjär. Ohanterade promise-avvisningar kan misslyckas tyst, medan oupptäckta undantag i async/await kastas inom try/catch-scopet.
Vad är debouncing och varför är det viktigt i en autocomplete-inmatning?
Debouncing fördröjer exekveringen av en funktion tills en angiven tid har passerat sedan senaste anropet. I en autocomplete förhindrar det att ett API-anrop skickas vid varje tangenttryckning; istället skickas begäran först efter att användaren pausar (t.ex. 300ms). Det minskar serverbelastningen, undviker flimrande resultat och förbättrar prestanda. Det implementeras vanligtvis med setTimeout och clearTimeout i en useEffect eller en anpassad hook.
Vilken HTML5-funktion låter dig skapa en fullt fungerande komponent utan att förlita sig på externa ramverk?
Web Components kombinerar tre webb-API:er: Custom Elements (för att definiera nya HTML-taggar), Shadow DOM (för att kapsla in stilar och uppmärkning) och HTML Templates. Tillsammans möjliggör de återanvändbara, självständiga UI-komponenter direkt i webbläsaren utan något ramverk.
Vilken JavaScript-funktion möjliggör reaktiv datatracking och UI-uppdateringar utan ett ramverk?
Proxy-objektet låter dig intercepta och omdefiniera grundläggande operationer på ett objekt – läsning av egenskaper, tilldelning och radering. Genom att fånga dessa operationer kan du detektera mutationer och utlösa DOM-uppdateringar. Vue 3 använder exakt denna mekanism för sitt reaktivitetssystem.
Vad är Tree Shaking i JavaScript-bundlers?
Tree Shaking är en teknik för eliminering av död kod som används av moderna bundlers (Webpack, Rollup, Vite) för att ta bort exporter som aldrig importeras i applikationen. Det fungerar med ES6-moduler (import/export) eftersom deras beroendegraf kan analyseras statiskt vid byggtid – till skillnad från CommonJS require()-anrop som är dynamiska. Resultatet är ett mindre produktionsbundle.
Vad är skillnaden mellan Microtasks och Macrotasks i JavaScript event loop?
Microtasks (Promise-callbacks, queueMicrotask(), MutationObserver) körs omedelbart efter att den synkrona uppgiften är klar, innan webbläsaren renderar eller plockar upp en macrotask. Macrotasks (setTimeout, setInterval, I/O-händelser, UI-händelser) bearbetas en per event-loop-iteration efter att alla väntande microtasks tömt kön. Cykeln är: synkron kod → töm microtask-kön → rendera → en macrotask → upprepa.
Vad är skillnaden mellan async/await och Promises i JavaScript?
De är olika lager av samma mekanism: varje async-funktion returnerar en Promise under huven och varje await-uttryck är syntaktiskt socker över .then(). async/await ger mer lättläst, synkront utseende kod och förenklar felhantering med try/catch. Promises exponerar kombinatorer som Promise.all(), Promise.race() och Promise.allSettled() som saknar direkta async/await-motsvarigheter.
Vad orsakar minnesläckor i JavaScript och hur förhindrar man dem?
JavaScript använder garbage collection för att frigöra objekt som inte längre är nåbara. Läckor uppstår när referenser hålls kvar oavsiktligt: glömda timers/intervaller, inaktuella event listeners, closures som fångar stora objekt och frånkopplade DOM-noder. SPAs är särskilt utsatta. Förebygg dem genom att alltid rensa timers och ta bort event listeners i cleanup-funktioner (useEffect-cleanup i React, AbortController för fetch).
Vad är en closure i JavaScript?
En closure är en funktion som behåller åtkomst till variabler från sitt yttre lexikala scope även efter att den yttre funktionen har returnerats. Detta ger den inre funktionen en bestående referens till sitt skapandekontext. Closures är fundamentala för mönster som datainkapsling, fabriksfunktioner, memoisation och React hooks, vilka förlitar sig på closures för att fånga tillstånd per rendering.
Vad är skillnaden mellan Service Workers och Web Workers?
Båda kör JavaScript utanför huvudtråden men tjänar olika syften. Web Workers hanterar CPU-intensiv beräkning (t.ex. bildbehandling) utan att blockera UI:t; de lever bara medan sidan är öppen och kan inte komma åt DOM. Service Workers agerar som nätverksproxy, möjliggör offline-cache, push-notifikationer och bakgrundssynkronisering; de fortsätter köra även när alla flikar stängs och interceptar fetch-händelser.
Vad är Type Narrowing i TypeScript?
Type Narrowing är processen att förfina en variabel från en bred unionstyp till en mer specifik typ baserat på körtids-villkorskontroller som typeof, instanceof eller in. Efter kontrollen vet TypeScript-kompilatorn den exakta typen inuti det blocket och tillämpar korrekta metoder och egenskaper. Till exempel behandlar TypeScript value som string inuti blocket efter if (typeof value === 'string').
Vad är skillnaden mellan any, unknown och never i TypeScript?
any inaktiverar helt typkontroll – du kan anropa vad som helst på det; praktiskt men osäkert och bör undvikas. unknown är den typsäkra motsvarigheten: du måste narrowa typen (typeof, instanceof eller typguard) innan du utför operationer. never representerar värden som aldrig kan existera; det används för funktioner som alltid kastar undantag och för uttömmande kontroller i diskriminerade unioner.
Vad är skillnaden mellan interface och type alias i TypeScript?
Båda kan beskriva objektformer men skiljer sig i kapacitet. Interfaces stödjer deklarationsmerging (två deklarationer med samma namn kombineras automatiskt) och använder extends för arv. Type aliases använder intersection (&) för komposition och kan även representera primitiver, unionstyper, tupler och mappade typer. Använd interface för publika API-kontrakt och type för komplexa kompositioner eller när merging är oönskad.
Vad är skillnaden mellan en Function Declaration och en Function Expression i JavaScript?
En Function Declaration (function foo() {}) är fullständigt hoistad – motorn flyttar både namn och kropp till toppen av scopet, så den kan anropas innan den syns i källkoden. En Function Expression (const foo = function() {} eller en arrow function) tilldelar en funktion till en variabel; variabeln hoistas men initialiseras till undefined, så att anropa den före tilldelningen kastar ett TypeError. Arrow functions är en typ av function expression och saknar dessutom sin egen this-bindning.
Hur skulle du implementera en egen version av `Promise.all` utan att använda den inbyggda metoden `Promise.all`? Funktionen ska ta emot en array av värden som kan vara promises eller vanliga värden, och ska simulera samma resolve/reject-beteende.
Den anpassade funktionen ska returnera ett nytt Promise som löser sig med en array av alla lösta värden när varje inmatning har slutförts framgångsrikt, och omedelbart avvisar med den första avvisningsorsaken som påträffas. Varje element i inmatningsarrayen bör omsluts med Promise.resolve() så att vanliga (icke-promise) värden hanteras korrekt. Exempelimplementering med forEach (mer läsbar än reduce): javascript function myPromiseAll(items) { return new Promise((resolve, reject) => { const results = []; let resolvedCount = 0; if (items.length === 0) { resolve(results); return; } items.forEach((item, index) => { Promise.resolve(item) .then(value => { results[index] = value; resolvedCount++; if (resolvedCount === items.length) { resolve(results); } }) .catch(reject); }); }); } Viktigaste punkterna: - Promise.resolve(item) normaliserar både promise- och icke-promise-inmatningar. - Resultaten lagras per index för att bevara inmatningsordningen oavsett i vilken ordning de löser sig. - Det yttre Promise avvisas så snart något inre promise avvisas. - async/await kan också användas inuti hjälpfunktionen om så önskas.
Implementera en `debounce`-funktion i JavaScript. Den ska ta emot en funktion och en väntetid i millisekunder, och returnera en ny funktion som fördröjer exekveringen av originalet tills den angivna tiden har gått utan att några nya anrop har gjorts.
En debounce-funktion använder en closure för att bevara en referens till en timervariabel. Varje gång den returnerade funktionen anropas avbryts den föregående timern med clearTimeout och en ny schemaläggs med setTimeout. Originalfunktionen körs först när hela vänteperioden passerat utan ett nytt anrop. javascript function debounce(fn, wait) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, wait); }; } Nyckelkoncept: closuren bevarar timer-referensen mellan anrop. Varje nytt anrop återställer nedräkningen, vilket innebär att den inlindade funktionen bara körs efter en inaktivitetsperiod på wait millisekunder.
Hur skulle du utöka en inbyggd JavaScript Array-metod, eller lägga till en anpassad metod tillgänglig på alla array-instanser?
I JavaScript kan du utöka inbyggda objekt som Array genom att lägga till egenskaper eller metoder direkt på deras prototyp. Till exempel: Array.prototype.minAnpassadeMetod = function() { /* implementation */ }; lägger till minAnpassadeMetod på alla array-instanser, eftersom alla arrayer ärver från Array.prototype via prototypkedjan. Även om tekniken fungerar bör den användas med försiktighet i produktionskod, eftersom den kan skapa konflikter med framtida inbyggda metoder eller tredjepartsbibliotek som också modifierar samma prototyp.
Skriv en funktion som avgör om en array med tal innehåller några duplicerade värden.
Både en HashMap (Java) och ett Set (TypeScript/JavaScript) löser problemet med tidskomplexitet O(n). TypeScript-lösning med ett Set (ren och idiomatisk): typescript function isDuplicated(array: number[]): boolean { const seen = new Set<number>(); for (const value of array) { if (seen.has(value)) { return true; } seen.add(value); } return false; } Java-lösning med en HashMap: java private static boolean isDuplicated(Integer[] array) { Map<Integer, Integer> tempMemory = new HashMap<>(); for (int i = 0; i < array.length; i++) { if (tempMemory.containsKey(array[i])) { return true; } else { tempMemory.put(array[i], 1); } } return false; } Båda metoderna itererar arrayen en gång och lagrar varje element i en hash-baserad struktur. Så snart ett upprepat element hittas returnerar funktionen true omedelbart (tidig utväg). Om loopen slutförs utan att hitta duplikat returneras false. Tidskomplexiteten är O(n) och rumskomplexiteten är O(n) i värsta fall.
Givet en loggfil där varje rad följer formatet `<timestamp> - <STATUS> - <message>`, implementera en Node.js `Transform`-ström kallad `transformStream` som läser filen och dirigerar varje rad till en separat utdatafil baserat på dess status (ERROR, DEBUG, INFO, WARNING). Dessutom ska varje WARNING-rad som innehåller ett CPU-procentvärde på 98% eller högre även skrivas till en HIGHCPU-loggfil.
Använd Node.js stream.Transform-klassen för att bearbeta filen chunk för chunk och buffra ofullständiga rader mellan chunks genom att dela på radbrytningar och behålla det sista (möjligen ofullständiga) segmentet i en buffer. För varje fullständig rad, dela på - för att extrahera statusfältet och meddelandet. Skriv sedan meddelandet till motsvarande utgående write-ström. För WARNING-rader, kontrollera dessutom med ett reguljärt uttryck (/\d+%/) om procentvärdet är >= 98 och skriv i så fall även till HIGHCPU-strömmen. Exempelimplementation: js function captureLogs(includeSignature = false) { const fs = require('fs'); const { Transform } = require('stream'); const input = fs.createReadStream('./data/logfile.log', { encoding: 'utf8' }); const outputs = { ERROR: fs.createWriteStream('./data/error.log'), DEBUG: fs.createWriteStream('./data/debug.log'), INFO: fs.createWriteStream('./data/info.log'), WARNING: fs.createWriteStream('./data/warning.log'), HIGHCPU: fs.createWriteStream('./data/highcpu.log'), }; // MUST be named transformStream const transformStream = new Transform({ decodeStrings: false, transform(chunk, _, cb) { this.buffer = (this.buffer || '') + chunk; const lines = this.buffer.split('\n'); this.buffer = lines.pop(); for (const line of lines) { if (!line.trim()) continue; const parts = line.split(' - '); if (parts.length < 3) continue; const status = parts[1].trim(); const message = parts.slice(2).join(' - ').trim(); if (outputs[status]) { outputs[status].write(message + '\n'); } if (status === 'WARNING') { const match = message.match(/(\d+)%/); if (match && Number(match[1]) >= 98) { outputs.HIGHCPU.write(message + '\n'); } } this.push(line + '\n'); } cb(); }, }); transformStream.on('end', () => { if (includeSignature) { const signature = { signature: generateSignature() }; fs.writeFileSync('./data/signature.json', JSON.stringify(signature)); } Object.values(outputs).forEach((s) => s.end()); }); input.pipe(transformStream); } Viktiga punkter: (1) Buffra ofullständiga chunks mellan transform-anrop för att undvika att dela en rad mitt i. (2) Använd outputs[status]-uppslag istället för en lång if-else-kedja. (3) HIGHCPU-kontrollen är additiv — raden skrivs fortfarande till WARNING-filen också. (4) Stäng alla write-strömmar i end-händelsehanteraren.

Asynkronitet & OOP

Vad är en Promise och vilka tillstånd kan den ha?
En Promise representerar det eventuella resultatet av en asynkron operation. Den är pending medan den pågår, fulfilled vid framgång (hanteras med .then), eller rejected vid fel (hanteras med .catch); .finally körs alltid. Promise.all kör många parallellt och avvisas om någon misslyckas, Promise.allSettled väntar på alla oavsett utfall, och Promise.race löser med den första som blir klar.
Hur förhåller sig async/await till Promises, och sekventiell vs parallell await?
async/await är syntaktiskt socker över Promises: en async-funktion returnerar alltid en Promise, och await pausar inuti den tills Promisen blir klar, med fel hanterade via try/catch. Att await:a i sekvens (await a; await b) är långsamt; kör oberoende arbete parallellt med await Promise.all([a, b]).
Vad behöver man veta om JavaScript-klasser?
En klass har en constructor som sätter instansfält med this, instansmetoder, och statiska medlemmar som nås på själva klassen (MathUtils.PI), inte på instanser. Getters/setters exponerar beräknade eller skyddade egenskaper (get fahrenheit(), set celsius(v) med validering). Statiska factory-metoder som ApiResponse.ok(body) är ett rent sätt att skapa vanliga instanser.
Hur fungerar arv och polymorfism i JavaScript?
En subklass använder extends och måste anropa super(...) i sin constructor innan this används; den kan åsidosätta metoder och anropa super.metod() för att utöka beteendet. Polymorfism innebär att samma anrop beter sig olika per klass — t.ex. en lista av Vehicle/Car/Motorbike som var och en implementerar info(). instanceof kontrollerar prototypkedjan.
Hur kapslar man in tillstånd i JavaScript?
Använd privata klassfält med prefixet # (this.#balance) så att de inte kan läsas eller skrivas utifrån; exponera kontrollerad åtkomst via getters och metoder som validerar indata. Den klassiska metoden före # är en closure (en factory-funktion med en privat variabel). Att returnera en kopia (t.ex. [...this.#history]) undviker att läcka interna referenser.
Vilka designmönster dyker ofta upp i testramverk?
Singleton (en delad instans, t.ex. en Config), Factory (bygga konfigurerade objekt, t.ex. en HttpClient per miljö), Builder (flytande steg-för-steg-konstruktion, t.ex. en RequestBuilder) och Observer/EventEmitter (prenumerera på händelser, som en test runner som sänder pass/fail). Dependency injection — att skicka in samarbetspartners i stället för att hårdkoda dem — gör alla dessa lätta att mocka.