JavaScript

Leg event delegation uit

Event delegation is een techniek waarbij event listeners aan een bovenliggend element worden toegevoegd in plaats van aan de onderliggende elementen. De listener wordt geactiveerd wanneer de gebeurtenis op de onderliggende elementen wordt geactiveerd als gevolg van het opborrelen van gebeurtenissen in de DOM. De voordelen van deze techniek zijn:

  • Het geheugenverbruik daalt omdat er slechts één enkele handler nodig is op het bovenliggende element, in plaats van event handlers aan elk onderliggend element te moeten koppelen.
  • Het is niet nodig om de handler los te koppelen van elementen die zijn verwijderd en om de gebeurtenis te koppelen voor nieuwe elementen.

Leg uit hoe 'this' werkt in JavaScript

Er is geen eenvoudige uitleg voor 'this'; het is een van de meest verwarrende concepten in JavaScript. Een vage uitleg is dat de waarde van 'this' afhankelijk is van hoe de functie wordt aangeroepen. Ik heb veel uitleg over 'this' online gelezen, en ik vond de uitleg van [Arnav Aggrawal] het duidelijkst. De volgende regels worden toegepast:

  1. Als het 'new' keyword wordt gebruikt bij het aanroepen van de functie, is 'this' binnen de functie een gloednieuw object.
  2. Als 'apply', 'call' of 'bind' worden gebruikt om een functie aan te roepen/te maken, is 'this' binnen de functie het object dat als argument wordt doorgegeven.
  3. Als een functie als een methode wordt aangeroepen, zoals 'obj.method()' - is 'this' het object waarvan de functie een eigenschap is.
  4. Als een functie wordt aangeroepen als een vrije functieaanroep, wat betekent dat deze werd aangeroepen zonder een van de bovenstaande voorwaarden, is 'this' het globale object. In een browser is dit het 'window' object. Als in strikte modus ('use strict'), zal 'this' 'undefined' zijn in plaats van het globale object.
  5. Als meerdere van de bovenstaande regels van toepassing zijn, wint de hoogste regel en stelt deze de 'this' waarde in.
  6. Als de functie een ES2015 arrow function is, negeert deze alle bovenstaande regels en ontvangt deze de 'this' waarde van zijn omringende scope op het moment dat deze wordt gemaakt.

Voor een diepgaande uitleg, bekijk zijn [artikel op Medium].

Kunt u een voorbeeld geven van een van de manieren waarop het werken met 'this' is veranderd in ES6?

ES6 stelt u in staat om [arrow functions] te gebruiken die de [omringende lexicale scope] gebruiken. Dit is meestal handig, maar voorkomt dat de aanroeper de context kan bepalen via '.call' of '.apply' - met als gevolg dat een bibliotheek zoals 'jQuery' 'this' niet correct zal binden in uw event handler functies. Daarom is het belangrijk om dit in gedachten te houden bij het refactoreren van grote legacy-applicaties.

Leg uit hoe prototypische overerving werkt

Alle JavaScript-objecten hebben een 'proto' eigenschap met uitzondering van objecten die zijn gemaakt met 'Object.create(null)', dat een verwijzing is naar een ander object, dat de 'prototype' van het object wordt genoemd. Wanneer een eigenschap wordt benaderd op een object en als de eigenschap niet op dat object wordt gevonden, kijkt de JavaScript-engine naar de 'proto' van het object, en de 'proto' van de 'proto' enzovoort, totdat het de eigenschap vindt die op een van de 'proto's is gedefinieerd of totdat het het einde van de prototypeketen bereikt. Dit gedrag simuleert klassieke overerving, maar het is eigenlijk meer [delegatie dan overerving].

Voorbeeld van prototypische overerving

// Constructor van het bovenliggende object. function Animal(name) { this.name = name; } // Voeg een methode toe aan het prototype van het bovenliggende object. Animal.prototype.makeSound = function () { console.log('De ' + this.constructor.name + ' maakt een geluid.'); }; // Constructor van het onderliggende object. function Dog(name) { Animal.call(this, name); // Roep de bovenliggende constructor aan. } // Stel het prototype van het onderliggende object in op het prototype van de ouder. Object.setPrototypeOf(Dog.prototype, Animal.prototype); // Voeg een methode toe aan het prototype van het onderliggende object. Dog.prototype.bark = function () { console.log('Woef!'); }; // Maak een nieuw exemplaar van Dog. const bolt = new Dog('Bolt'); // Roep methoden aan op het onderliggende object. console.log(bolt.name); // 'Bolt' bolt.makeSound(); // 'De Dog maakt een geluid.' bolt.bark(); // 'Woef!'

Wat opvalt is:

  • '.makeSound' is niet gedefinieerd op 'Dog', dus de engine gaat de prototypeketen omhoog en vindt '.makeSound' op de overgeërfde 'Animal'.
  • Het gebruik van 'Object.create' om de overervingsketen op te bouwen wordt niet langer aanbevolen. Gebruik in plaats daarvan 'Object.setPrototypeOf'.

Wat vindt u van AMD versus CommonJS?

Beide zijn manieren om een modulesysteem te implementeren, wat niet native aanwezig was in JavaScript totdat ES2015 kwam. CommonJS is synchroon terwijl AMD (Asynchronous Module Definition) duidelijk asynchroon is. CommonJS is ontworpen met server-side ontwikkeling in gedachten, terwijl AMD, met zijn ondersteuning voor asynchroon laden van modules, meer bedoeld is voor browsers.

Ik vind de AMD-syntaxis nogal omslachtig en CommonJS ligt dichter bij de stijl waarin je import statements in andere talen zou schrijven. Meestal vind ik AMD overbodig, want als je al je JavaScript in één geconcatenereerde bundelbestand zou serveren, zou je niet profiteren van de asynchrone laadeigenschappen. Bovendien ligt de CommonJS-syntaxis dichter bij de Node-stijl van het schrijven van modules en is er minder overhead bij het wisselen tussen client-side en server-side JavaScript-ontwikkeling.

Ik ben blij dat we met ES2015-modules, die ondersteuning hebben voor zowel synchroon als asynchroon laden, eindelijk bij één benadering kunnen blijven. Hoewel het nog niet volledig is uitgerold in browsers en in Node, kunnen we altijd transpilers gebruiken om onze code te converteren.

Leg uit waarom het volgende niet werkt als een IIFE: 'function foo(){ }();'. Wat moet er veranderd worden om het correct een IIFE te maken?

IIFE staat voor Immediately Invoked Function Expressions. De JavaScript-parser leest 'function foo(){ }();' als 'function foo(){ }' en '();', waarbij de eerste een function declaration is en de laatste (een paar haakjes) een poging is om een functie aan te roepen, maar er is geen naam opgegeven, vandaar dat het een 'Uncaught SyntaxError: Unexpected token )' genereert.

Hier zijn twee manieren om het te repareren waarbij meer haakjes betrokken zijn: '(function foo(){ })()' en '(function foo(){ }())'. Statements die beginnen met 'function' worden beschouwd als function declarations; door deze functie binnen '()' te plaatsen, wordt het een function expression die vervolgens kan worden uitgevoerd met de daaropvolgende '()'. Deze functies worden niet blootgesteld in de globale scope en u kunt zelfs de naam weglaten als u er geen behoefte aan hebt om ernaar te verwijzen binnen de body.

U kunt ook de 'void' operator gebruiken: 'void function foo(){ }();'. Helaas is er één probleem met een dergelijke aanpak. De evaluatie van de gegeven expressie is altijd 'undefined', dus als uw IIFE-functie iets retourneert, kunt u het niet gebruiken. Een voorbeeld:

const foo = void (function bar() { return 'foo'; })(); console.log(foo); // undefined

Wat is het verschil tussen een variabele die: 'null', 'undefined' of niet gedeclareerd is? Hoe zou u deze statussen controleren?

Niet-gedeclareerde variabelen worden gemaakt wanneer u een waarde toewijst aan een identificator die niet eerder is gemaakt met 'var', 'let' of 'const'. Niet-gedeclareerde variabelen worden globaal gedefinieerd, buiten de huidige scope. In strikte modus wordt een 'ReferenceError' gegenereerd wanneer u probeert een waarde toe te wijzen aan een niet-gedeclareerde variabele. Niet-gedeclareerde variabelen zijn slecht, net zoals globale variabelen slecht zijn. Vermijd ze ten koste van alles! Om ze te controleren, omringt u het gebruik ervan met een 'try'/'catch' blok.

function foo() { x = 1; // Werpt een ReferenceError in strikte modus } foo(); console.log(x); // 1

Een variabele die 'undefined' is, is een variabele die is gedeclareerd, maar waaraan geen waarde is toegewezen. Het is van het type 'undefined'. Als een functie geen waarde retourneert als resultaat van de uitvoering ervan en deze wordt toegewezen aan een variabele, heeft de variabele ook de waarde 'undefined'. Om dit te controleren, vergelijkt u met behulp van de strikte gelijkheidsoperator ('===') of 'typeof' die de 'undefined'-string zal geven. Merk op dat u de abstracte gelijkheidsoperator niet moet gebruiken om te controleren, aangezien deze ook 'true' zal retourneren als de waarde 'null' is.

var foo; console.log(foo); // undefined console.log(foo === undefined); // true console.log(typeof foo === 'undefined'); // true console.log(foo == null); // true. Fout, gebruik dit niet om te controleren! function bar() {} var baz = bar(); console.log(baz); // undefined

Een variabele die 'null' is, zal expliciet de waarde 'null' hebben gekregen. Het vertegenwoordigt geen waarde en verschilt van 'undefined' in de zin dat het expliciet is toegewezen. Om te controleren op 'null', vergelijkt u eenvoudigweg met de strikte gelijkheidsoperator. Merk op dat, net als hierboven, u de abstracte gelijkheidsoperator ('==') niet moet gebruiken om te controleren, aangezien deze ook 'true' zal retourneren als de waarde 'undefined' is.

var foo = null; console.log(foo === null); // true console.log(typeof foo === 'object'); // true console.log(foo == undefined); // true. Fout, gebruik dit niet om te controleren!

Als persoonlijke gewoonte laat ik mijn variabelen nooit niet-gedeclareerd of niet-toegewezen. Ik zal ze expliciet 'null' toewijzen na declaratie als ik ze nog niet van plan ben te gebruiken. Als u een linter in uw workflow gebruikt, zal deze meestal ook kunnen controleren of u geen niet-gedeclareerde variabelen aanroept.

Wat is een closure, en hoe/waarom zou je er een gebruiken?

Een closure is de combinatie van een functie en de lexicale omgeving waarbinnen die functie is gedeclareerd. Het woord 'lexical' verwijst naar het feit dat lexicale scoping de locatie gebruikt waar een variabele is gedeclareerd binnen de broncode om te bepalen waar die variabele beschikbaar is. Closures zijn functies die toegang hebben tot de variabelen van de buitenste (insluitende) functie - scope chain, zelfs nadat de buitenste functie is teruggekeerd.

Waarom zou je er een gebruiken?

  • Gegevensprivacy / emuleren van private methoden met closures. Veel gebruikt in het [module pattern].
  • [Gedeeltelijke applicaties of currying].

Kunt u het belangrijkste verschil beschrijven tussen een '.forEach' loop en een '.map()' loop en waarom u de ene of de andere zou kiezen?

Om de verschillen tussen de twee te begrijpen, kijken we naar wat elke functie doet.

forEach

  • Iterereert door de elementen in een array.
  • Voert een callback uit voor elk element.
  • Retourneert geen waarde.
const a = [1, 2, 3]; const doubled = a.forEach((num, index) => { // Doe iets met num en/of index. }); // doubled = undefined

map

  • Iterereert door de elementen in een array.
  • 'Mapt' elk element naar een nieuw element door de functie op elk element aan te roepen, waardoor een nieuwe array ontstaat.
const a = [1, 2, 3]; const doubled = a.map((num) => { return num * 2; }); // doubled = [2, 4, 6]

Het belangrijkste verschil tussen '.forEach' en '.map()' is dat '.map()' een nieuwe array retourneert. Als u het resultaat nodig hebt, maar de originele array niet wilt muteren, is '.map()' de duidelijke keuze. Als u eenvoudigweg over een array moet itereren, is 'forEach' een prima keuze.

Wat is een typisch gebruiksscenario voor anonieme functies?

Ze kunnen worden gebruikt in IIFE's om code binnen een lokale scope te inkapselen, zodat variabelen die erin zijn gedeclareerd niet naar de globale scope lekken.

(function () { // Enkele code hier. })();

Als een callback die eenmalig wordt gebruikt en nergens anders hoeft te worden gebruikt. De code zal meer zelfstandig en leesbaar lijken wanneer handlers direct in de aanroepende code worden gedefinieerd, in plaats van elders te moeten zoeken naar de functiebody.

setTimeout(function () { console.log('Hallo wereld!'); }, 1000);

Argumenten voor functionele programmeerconstructies of Lodash (vergelijkbaar met callbacks).

const arr = [1, 2, 3]; const double = arr.map(function (el) { return el * 2; }); console.log(double); // [2, 4, 6]

Hoe organiseert u uw code? (modulepatroon, klassieke overerving?)

Vroeger gebruikte ik Backbone voor mijn modellen, wat een meer OOP-aanpak stimuleert, door Backbone-modellen te creëren en methoden eraan te koppelen.

Het modulepatroon is nog steeds geweldig, maar tegenwoordig gebruik ik React/Redux, die een unidirectionele gegevensstroom gebruiken op basis van de Flux-architectuur. Ik zou de modellen van mijn app vertegenwoordigen met gewone objecten en utility pure functies schrijven om deze objecten te manipuleren. De status wordt gemanipuleerd met behulp van acties en reducers, net als in elke andere Redux-applicatie.

Ik vermijd waar mogelijk klassieke overerving. Wanneer ik dat wel doe, houd ik me aan [deze regels].

Wat is het verschil tussen hostobjecten en native objecten?

Native objecten zijn objecten die deel uitmaken van de JavaScript-taal gedefinieerd door de ECMAScript-specificatie, zoals 'String', 'Math', 'RegExp', 'Object', 'Function', enz.

Hostobjecten worden geleverd door de runtime-omgeving (browser of Node), zoals 'window', 'XMLHTTPRequest', enz.

Verschil tussen: 'function Person(){}', 'var person = Person()', en 'var person = new Person()'?

Deze vraag is vrij vaag. Mijn beste gok naar de intentie is dat het vraagt naar constructors in JavaScript. Technisch gezien is 'function Person(){}' slechts een normale functiedeclaratie. De conventie is om PascalCase te gebruiken voor functies die bedoeld zijn om als constructors te worden gebruikt.

'var person = Person()' roept de 'Person' aan als een functie, en niet als een constructor. Het aanroepen als zodanig is een veelvoorkomende fout als de functie bedoeld is om als constructor te worden gebruikt. Typisch retourneert de constructor niets, vandaar dat het aanroepen van de constructor als een normale functie 'undefined' retourneert en dat wordt toegewezen aan de variabele die bedoeld is als het exemplaar.

'var person = new Person()' creëert een instantie van het 'Person'-object met behulp van de 'new'-operator, die overerft van 'Person.prototype'. Een alternatief zou zijn om 'Object.create' te gebruiken, zoals: 'Object.create(Person.prototype)'.

function Person(name) { this.name = name; } var person = Person('John'); console.log(person); // undefined console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined var person = new Person('John'); console.log(person); // Person { name: 'John' } console.log(person.name); // 'john'

Wat is het verschil tussen '.call' en '.apply'?

Zowel '.call' als '.apply' worden gebruikt om functies aan te roepen en de eerste parameter wordt gebruikt als de waarde van 'this' binnen de functie. Echter, '.call' accepteert kommagescheiden argumenten als de volgende argumenten, terwijl '.apply' een array van argumenten accepteert als het volgende argument. Een eenvoudige manier om dit te onthouden is C voor 'call' en komma-gescheiden en A voor 'apply' en een array van argumenten.

function add(a, b) { return a + b; } console.log(add.call(null, 1, 2)); // 3 console.log(add.apply(null, [1, 2])); // 3

Leg 'Function.prototype.bind' uit.

Woord voor woord overgenomen van [MDN]:

De 'bind()' methode creëert een nieuwe functie die, wanneer aangeroepen, zijn 'this' keyword heeft ingesteld op de opgegeven waarde, met een gegeven reeks argumenten die voorafgaan aan alle argumenten die worden opgegeven wanneer de nieuwe functie wordt aangeroepen.

In mijn ervaring is het het meest nuttig voor het binden van de waarde van 'this' in methoden van klassen die u aan andere functies wilt doorgeven. Dit wordt vaak gedaan in React-componenten.

Wanneer zou u 'document.write()' gebruiken?

'document.write()' schrijft een tekenreeks naar een documentstroom die is geopend door 'document.open()'. Wanneer 'document.write()' wordt uitgevoerd nadat de pagina is geladen, roept het 'document.open' aan, wat het hele document ('<head>' en '<body>' verwijderd!) wist en de inhoud vervangt door de opgegeven parameterwaarde. Daarom wordt het meestal als gevaarlijk en gevoelig voor misbruik beschouwd.

Er zijn online enkele antwoorden die uitleggen dat 'document.write()' wordt gebruikt in analysecodes of [wanneer u stijlen wilt opnemen die alleen zouden moeten werken als JavaScript is ingeschakeld]. Het wordt zelfs gebruikt in HTML5 boilerplate om [scripts parallel te laden en de uitvoervolgorde te behouden]! Ik vermoed echter dat die redenen achterhaald kunnen zijn en dat ze in de moderne tijd kunnen worden bereikt zonder 'document.write()' te gebruiken. Corrigeer me alsjeblieft als ik hierin ongelijk heb.

Wat is het verschil tussen feature detection, feature inference en het gebruik van de UA-string?

Feature Detection

Feature detection houdt in dat wordt nagegaan of een browser een bepaald codeblok ondersteunt, en verschillende code wordt uitgevoerd afhankelijk van of dit wel (of niet) het geval is, zodat de browser altijd een werkende ervaring kan bieden in plaats van te crashen/fouten te geven in sommige browsers. Bijvoorbeeld:

if ('geolocation' in navigator) { // Kan navigator.geolocation gebruiken } else { // Behandel gebrek aan functie }

[Modernizr] is een geweldige bibliotheek om feature detection te verwerken.

Feature Inference

Feature inference controleert op een functie net als feature detection, maar gebruikt een andere functie omdat het ervan uitgaat dat deze ook zal bestaan, bijv.:

if (document.getElementsByTagName) { element = document.getElementById(id); }

Dit wordt niet echt aanbevolen. Feature detection is betrouwbaarder.

UA String

Dit is een door de browser gerapporteerde string waarmee de netwerkprotocolpartners het applicatietype, besturingssysteem, softwareleverancier of softwareversie van de aanvragende software user agent kunnen identificeren. Het is toegankelijk via 'navigator.userAgent'. De string is echter lastig te parseren en kan worden vervalst. Chrome rapporteert bijvoorbeeld zowel als Chrome als Safari. Dus om Safari te detecteren moet je controleren op de Safari-string en de afwezigheid van de Chrome-string. Vermijd deze methode.

Leg Ajax zo gedetailleerd mogelijk uit.

Ajax (asynchrone JavaScript en XML) is een verzameling webontwikkelingstechnieken die veel webtechnologieën aan de clientzijde gebruiken om asynchrone webapplicaties te creëren. Met Ajax kunnen webapplicaties asynchroon (op de achtergrond) gegevens naar en van een server verzenden en ophalen, zonder de weergave en het gedrag van de bestaande pagina te verstoren. Door de gegevensuitwisselingslaag los te koppelen van de presentatielaag, maakt Ajax het mogelijk dat webpagina's, en daarmee webapplicaties, de inhoud dynamisch wijzigen zonder dat de hele pagina opnieuw hoeft te worden geladen. In de praktijk gebruiken moderne implementaties vaak JSON in plaats van XML, vanwege de voordelen van JSON als native voor JavaScript.

De 'XMLHttpRequest' API wordt vaak gebruikt voor de asynchrone communicatie, of tegenwoordig de 'fetch()' API.

Wat zijn de voor- en nadelen van het gebruik van Ajax?

Voordelen

  • Betere interactiviteit. Nieuwe inhoud van de server kan dynamisch worden gewijzigd zonder de hele pagina opnieuw te hoeven laden.
  • Verminder verbindingen met de server, aangezien scripts en stylesheets slechts één keer hoeven te worden aangevraagd.
  • De status kan op een pagina worden gehandhaafd. JavaScript-variabelen en DOM-status blijven behouden omdat de hoofdcontainerpagina niet opnieuw is geladen.
  • Kortom, de meeste voordelen van een SPA.

Nadelen

  • Dynamische webpagina's zijn moeilijker te bookmarken.
  • Werkt niet als JavaScript is uitgeschakeld in de browser.
  • Sommige webcrawlers voeren JavaScript niet uit en zouden inhoud die met JavaScript is geladen niet zien.
  • Webpagina's die Ajax gebruiken om gegevens op te halen, zullen waarschijnlijk de opgehaalde externe gegevens moeten combineren met client-side templates om de DOM bij te werken. Om dit te laten gebeuren, moet JavaScript in de browser worden geparseerd en uitgevoerd, en low-end mobiele apparaten kunnen hiermee worstelen.
  • Kortom, de meeste nadelen van een SPA.

Leg uit hoe JSONP werkt (en hoe het niet echt Ajax is).

JSONP (JSON met Padding) is een methode die vaak wordt gebruikt om het beleid voor cross-domain aanvragen in webbrowsers te omzeilen, omdat Ajax-aanvragen van de huidige pagina naar een cross-origin domein niet zijn toegestaan.

JSONP werkt door een aanvraag te doen naar een cross-origin domein via een '<script>'-tag en meestal met een 'callback' queryparameter, bijvoorbeeld: 'https://example.com?callback=printData'. De server zal de gegevens vervolgens verpakken in een functie genaamd 'printData' en deze retourneren aan de client.

<script> function printData(data) { console.log(`Mijn naam is ${data.name}!`); } </script> <script src="[https://example.com?callback=printData](https://example.com?callback=printData)"></script>
printData({ name: 'Yang Shun' });

De client moet de 'printData'-functie in zijn globale scope hebben en de functie wordt door de client uitgevoerd wanneer de reactie van het cross-origin domein is ontvangen.

JSONP kan onveilig zijn en heeft enkele beveiligingsimplicaties. Aangezien JSONP eigenlijk JavaScript is, kan het alles doen wat JavaScript kan doen, dus u moet de provider van de JSONP-gegevens vertrouwen.

Tegenwoordig is [CORS] de aanbevolen aanpak en wordt JSONP als een hack gezien.

Heeft u ooit JavaScript templating gebruikt? Zo ja, welke bibliotheken heeft u gebruikt?

Ja. Handlebars, Underscore, Lodash, AngularJS en JSX. Ik hield niet van templating in AngularJS omdat het veel gebruik maakte van strings in de directives en typefouten onopgemerkt zouden blijven. JSX is mijn nieuwe favoriet omdat het dichter bij JavaScript staat en er nauwelijks syntax hoeft te worden geleerd. Tegenwoordig kunt u zelfs ES2015 template string literals gebruiken als een snelle manier om templates te maken zonder afhankelijk te zijn van code van derden.

const template = `<div>Mijn naam is: ${name}</div>`;

Wees u echter bewust van een potentiële XSS in de bovenstaande aanpak, aangezien de inhoud niet voor u wordt geëscapeerd, in tegenstelling tot in templating-bibliotheken.

Leg 'hoisting' uit.

Hoisting is een term die wordt gebruikt om het gedrag van variabeledeclaraties in uw code te verklaren. Variabelen die zijn gedeclareerd of geïnitialiseerd met het 'var'-keyword, krijgen hun declaratie 'verplaatst' naar de top van hun module-/functie-niveau scope, wat we hoisting noemen. Echter, alleen de declaratie wordt gehoist, de toewijzing (indien aanwezig) blijft waar deze is.

Merk op dat de declaratie niet daadwerkelijk wordt verplaatst - de JavaScript-engine parseert de declaraties tijdens compilatie en wordt zich bewust van declaraties en hun scopes. Het is gewoon gemakkelijker om dit gedrag te begrijpen door de declaraties te visualiseren als gehoist naar de top van hun scope. Laten we uitleggen met een paar voorbeelden.

console.log(foo); // undefined var foo = 1; console.log(foo); // 1

Functiedeclaraties hebben de body gehoist, terwijl de functie-expressies (geschreven in de vorm van variabeledeclaraties) alleen de variabeledeclaratie gehoist hebben.

// Functiedeclaratie console.log(foo); // [Functie: foo] foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); } console.log(foo); // [Functie: foo] // Functie-expressie console.log(bar); // undefined bar(); // Uncaught TypeError: bar is geen functie var bar = function () { console.log('BARRRR'); }; console.log(bar); // [Functie: bar]

Variabelen die zijn gedeclareerd via 'let' en 'const' worden ook gehoist. Echter, in tegenstelling tot 'var' en 'function', worden ze niet geïnitialiseerd en zal het benaderen ervan voor de declaratie resulteren in een 'ReferenceError' uitzondering. De variabele bevindt zich in een 'temporal dead zone' vanaf het begin van het blok totdat de declaratie is verwerkt.

x; // undefined y; // Referentiefout: y is niet gedefinieerd var x = 'local'; let y = 'local';

Beschrijf event bubbling.

Wanneer een gebeurtenis wordt geactiveerd op een DOM-element, zal het proberen de gebeurtenis af te handelen als er een listener is gekoppeld, waarna de gebeurtenis naar zijn bovenliggende element 'bubbled' wordt en hetzelfde gebeurt. Dit 'bubbling' gebeurt langs de voorouders van het element, helemaal tot aan het 'document'. Event bubbling is het mechanisme achter event delegation.

Wat is het verschil tussen een 'attribuut' en een 'eigenschap'?

Attributen worden gedefinieerd in de HTML-markup, maar eigenschappen worden gedefinieerd in de DOM. Om het verschil te illustreren, stel je voor dat we dit tekstveld in onze HTML hebben: '<input type="text" value="Hallo">'.

const input = document.querySelector('input'); console.log(input.getAttribute('value')); // Hallo console.log(input.value); // Hallo

Maar nadat je de waarde van het tekstveld hebt gewijzigd door 'Wereld!' toe te voegen, wordt dit:

console.log(input.getAttribute('value')); // Hallo console.log(input.value); // Hallo Wereld!

Waarom is het uitbreiden van ingebouwde JavaScript-objecten geen goed idee?

Het uitbreiden van een ingebouwd/native JavaScript-object betekent het toevoegen van eigenschappen/functies aan het 'prototype' ervan. Hoewel dit in eerste instantie een goed idee lijkt, is het in de praktijk gevaarlijk. Stel je voor dat je code een paar bibliotheken gebruikt die beide 'Array.prototype' uitbreiden door dezelfde 'contains'-methode toe te voegen, de implementaties zullen elkaar overschrijven en je code zal breken als het gedrag van deze twee methoden niet hetzelfde is.

De enige keer dat je een native object zou willen uitbreiden, is wanneer je een polyfill wilt maken, in wezen je eigen implementatie wilt bieden voor een methode die deel uitmaakt van de JavaScript-specificatie, maar mogelijk niet bestaat in de browser van de gebruiker omdat het een oudere browser is.

Verschil tussen document 'load' gebeurtenis en document 'DOMContentLoaded' gebeurtenis?

De 'DOMContentLoaded' gebeurtenis wordt geactiveerd wanneer het initiële HTML-document volledig is geladen en geparseerd, zonder te wachten tot stylesheets, afbeeldingen en subframes klaar zijn met laden.

De 'load' gebeurtenis van het 'window' wordt pas geactiveerd nadat de DOM en alle afhankelijke bronnen en assets zijn geladen.

Wat is het verschil tussen '==' en '==='?

'==' is de abstracte gelijkheidsoperator, terwijl '===' de strikte gelijkheidsoperator is. De '==' operator vergelijkt op gelijkheid na het uitvoeren van eventuele noodzakelijke typeconversies. De '===' operator doet geen typeconversie, dus als twee waarden niet van hetzelfde type zijn, zal '===' eenvoudigweg 'false' retourneren. Bij gebruik van '==' kunnen vreemde dingen gebeuren, zoals:

1 == '1'; // true 1 == [1]; // true 1 == true; // true 0 == ''; // true 0 == '0'; // true 0 == false; // true

Mijn advies is om de '==' operator nooit te gebruiken, behalve voor het gemak bij het vergelijken met 'null' of 'undefined', waarbij 'a == null' 'true' zal retourneren als 'a' 'null' of 'undefined' is.

var a = null; console.log(a == null); // true console.log(a == undefined); // true

Leg de 'same-origin policy' uit met betrekking tot JavaScript.

De 'same-origin policy' voorkomt dat JavaScript aanvragen doet over domeingrenzen heen. Een origine is gedefinieerd als een combinatie van URI-schema, hostname en poortnummer. Dit beleid voorkomt dat een kwaadaardig script op de ene pagina toegang krijgt tot gevoelige gegevens op een andere webpagina via het Document Object Model van die pagina.

Laat dit werken:

duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]
function duplicate(arr) { return arr.concat(arr); } duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

Of met ES6:

const duplicate = (arr) => [...arr, ...arr]; duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

Waarom wordt het een Ternaire expressie genoemd, wat duidt het woord 'Ternair' aan?

'Ternair' duidt op drie, en een ternaire expressie accepteert drie operanden: de testconditie, de 'dan'-expressie en de 'anders'-expressie. Ternaire expressies zijn niet specifiek voor JavaScript en ik weet niet zeker waarom het zelfs in deze lijst staat.

Wat is 'use strict';? Wat zijn de voor- en nadelen van het gebruik ervan?

'use strict' is een statement dat wordt gebruikt om de strikte modus in te schakelen voor hele scripts of individuele functies. De strikte modus is een manier om te kiezen voor een beperkte variant van JavaScript.

Voordelen:

  • Maakt het onmogelijk om per ongeluk globale variabelen aan te maken.
  • Zorgt ervoor dat toewijzingen die anders stilzwijgend zouden mislukken, een uitzondering gooien.
  • Zorgt ervoor dat pogingen om onverwijderbare eigenschappen te verwijderen een uitzondering gooien (waar de poging voorheen simpelweg geen effect zou hebben).
  • Vereist dat namen van functieparameters uniek zijn.
  • 'this' is undefined in de globale context.
  • Het vangt enkele veelvoorkomende programmeerfouten, door uitzonderingen te genereren.
  • Het schakelt functies uit die verwarrend of slecht doordacht zijn.

Nadelen:

  • Veel ontbrekende functies waar sommige ontwikkelaars misschien aan gewend zijn.
  • Geen toegang meer tot 'function.caller' en 'function.arguments'.
  • Het samenvoegen van scripts die in verschillende strikte modi zijn geschreven, kan problemen veroorzaken.

Over het algemeen denk ik dat de voordelen opwegen tegen de nadelen, en ik heb nooit hoeven te vertrouwen op de functies die de strikte modus blokkeert. Ik zou het gebruik van de strikte modus aanbevelen.

Maak een for-lus die tot '100' itereert en tegelijkertijd 'fizz' uitvoert bij veelvouden van '3', 'buzz' bij veelvouden van '5' en 'fizzbuzz' bij veelvouden van '3' en '5'.

Bekijk deze versie van FizzBuzz door [Paul Irish].

for (let i = 1; i <= 100; i++) { let f = i % 3 == 0, b = i % 5 == 0; console.log(f ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i); }

Ik zou u echter niet adviseren om het bovenstaande tijdens interviews te schrijven. Houd u gewoon aan de lange maar duidelijke aanpak. Voor meer bizarre versies van FizzBuzz, zie de onderstaande referentielink.

Waarom is het over het algemeen een goed idee om de globale scope van een website ongemoeid te laten en er nooit aan te raken?

Elk script heeft toegang tot de globale scope, en als iedereen de globale namespace gebruikt om zijn variabelen te definiëren, zullen botsingen waarschijnlijk optreden. Gebruik het modulepatroon (IIFE's) om uw variabelen binnen een lokale namespace in te kapselen.

Waarom zou u iets als de 'load' gebeurtenis gebruiken? Heeft deze gebeurtenis nadelen? Kent u alternatieven, en waarom zou u die gebruiken?

De 'load'-gebeurtenis wordt geactiveerd aan het einde van het documentlaadproces. Op dit punt bevinden alle objecten in het document zich in de DOM, en alle afbeeldingen, scripts, links en subframes zijn klaar met laden.

De DOM-gebeurtenis 'DOMContentLoaded' wordt geactiveerd nadat de DOM voor de pagina is opgebouwd, maar wacht niet tot andere bronnen zijn geladen. Dit heeft in bepaalde gevallen de voorkeur wanneer u niet de volledige pagina hoeft te laden voordat u initialiseert.

Leg uit wat een single page app is en hoe je er een SEO-vriendelijk maakt.

Het onderstaande is overgenomen uit de geweldige [Grab Front End Guide], die toevallig door mij is geschreven!

Webontwikkelaars noemen de producten die ze tegenwoordig bouwen webapps, in plaats van websites. Hoewel er geen strikt verschil is tussen de twee termen, zijn webapps doorgaans zeer interactief en dynamisch, waardoor de gebruiker acties kan uitvoeren en een reactie op zijn actie kan ontvangen. Traditioneel ontvangt de browser HTML van de server en rendert deze. Wanneer de gebruiker naar een andere URL navigeert, is een volledige paginavernieuwing vereist en stuurt de server verse nieuwe HTML naar de nieuwe pagina. Dit wordt server-side rendering genoemd.

In moderne SPA's wordt echter client-side rendering gebruikt. De browser laadt de initiële pagina van de server, samen met de scripts (frameworks, bibliotheken, app-code) en stylesheets die nodig zijn voor de hele app. Wanneer de gebruiker naar andere pagina's navigeert, wordt geen paginavernieuwing geactiveerd. De URL van de pagina wordt bijgewerkt via de [HTML5 History API]. Nieuwe gegevens die nodig zijn voor de nieuwe pagina, meestal in JSON-formaat, worden door de browser opgehaald via [AJAX]-aanvragen naar de server. De SPA werkt de pagina vervolgens dynamisch bij met de gegevens via JavaScript, die deze al heeft gedownload bij de initiële paginavernieuwing. Dit model is vergelijkbaar met de werking van native mobiele apps.

De voordelen:

  • De app voelt responsiever aan en gebruikers zien geen flits tussen pagina-navigaties als gevolg van volledige paginavernieuwingen.
  • Er worden minder HTTP-aanvragen naar de server gedaan, aangezien dezelfde assets niet opnieuw hoeven te worden gedownload voor elke paginavernieuwing.
  • Duidelijke scheiding van de belangen tussen client en server; u kunt eenvoudig nieuwe clients bouwen voor verschillende platforms (bijv. mobiel, chatbots, smartwatches) zonder de servercode te hoeven wijzigen. U kunt ook de technologiestack aan de client- en serverzijde onafhankelijk van elkaar wijzigen, zolang het API-contract niet wordt verbroken.

De nadelen:

  • Zwaardere initiële paginavernieuwing door het laden van framework, app-code en assets die nodig zijn voor meerdere pagina's.
  • Er is een extra stap op uw server nodig om deze te configureren om alle aanvragen naar een enkel toegangspunt te routeren en client-side routing vanaf daar het over te laten nemen.
  • SPA's zijn afhankelijk van JavaScript om inhoud te renderen, maar niet alle zoekmachines voeren JavaScript uit tijdens het crawlen, en ze zien mogelijk lege inhoud op uw pagina. Dit schaadt onbedoeld de Search Engine Optimization (SEO) van uw app. Meestal is SEO echter niet de belangrijkste factor bij het bouwen van apps, aangezien niet alle inhoud door zoekmachines hoeft te worden geïndexeerd. Om dit te omzeilen, kunt u uw app server-side renderen of services zoals [Prerender] gebruiken om 'uw javascript in een browser te renderen, de statische HTML op te slaan en deze terug te sturen naar de crawlers'.

Wat is de omvang van uw ervaring met Promises en/of hun polyfills?

Beschik over werkende kennis ervan. Een promise is een object dat in de toekomst een enkele waarde kan produceren: ofwel een opgeloste waarde, ofwel een reden waarom het niet is opgelost (bijv. er is een netwerkfout opgetreden). Een promise kan in een van de 3 mogelijke statussen verkeren: fulfilled, rejected of pending. Promise-gebruikers kunnen callbacks koppelen om de opgeloste waarde of de reden van afwijzing af te handelen.

Enkele veelvoorkomende polyfills zijn '$.deferred', Q en Bluebird, maar niet allemaal voldoen ze aan de specificatie. ES2015 ondersteunt Promises out of the box en polyfills zijn tegenwoordig meestal niet nodig.

Wat zijn de voor- en nadelen van het gebruik van Promises in plaats van callbacks?

Voordelen

  • Vermijd callback hell die onleesbaar kan zijn.
  • Maakt het gemakkelijk om sequentiële asynchrone code te schrijven die leesbaar is met '.then()'.
  • Maakt het gemakkelijk om parallelle asynchrone code te schrijven met 'Promise.all()'.
  • Met promises zullen deze scenario's, die aanwezig zijn in alleen callbacks-programmering, niet voorkomen:
    • Roep de callback te vroeg aan
    • Roep de callback te laat aan (of nooit)
    • Roep de callback te weinig of te vaak aan
    • Geen noodzakelijke omgeving/parameters doorgeven
    • Eventuele fouten/uitzonderingen die kunnen optreden, onderdrukken

Nadelen

  • Iets complexere code (discutabel).
  • In oudere browsers waar ES2015 niet wordt ondersteund, moet u een polyfill laden om het te kunnen gebruiken.

Wat zijn enkele voor- en nadelen van het schrijven van JavaScript-code in een taal die compileert naar JavaScript?

Enkele voorbeelden van talen die compileren naar JavaScript zijn CoffeeScript, Elm, ClojureScript, PureScript en TypeScript.

Voordelen:

  • Lost enkele van de langdurige problemen in JavaScript op en ontmoedigt JavaScript anti-patterns.
  • Stelt u in staat om kortere code te schrijven, door wat syntactische suiker bovenop JavaScript te bieden, wat ik ES5 vind missen, maar ES2015 is geweldig.
  • Statische typen zijn geweldig (in het geval van TypeScript) voor grote projecten die in de loop van de tijd moeten worden onderhouden.

Nadelen:

  • Vereist een build/compileerproces, aangezien browsers alleen JavaScript uitvoeren en uw code moet worden gecompileerd naar JavaScript voordat deze aan browsers wordt aangeboden.
  • Debuggen kan lastig zijn als uw sourcemaps niet netjes naar uw vooraf gecompileerde bron worden toegewezen.
  • De meeste ontwikkelaars zijn niet bekend met deze talen en zullen ze moeten leren. Er zijn opstartkosten gemoeid voor uw team als u het voor uw projecten gebruikt.
  • Kleinere gemeenschap (afhankelijk van de taal), wat betekent dat bronnen, tutorials, bibliotheken en tooling moeilijker te vinden zijn.
  • IDE/editor-ondersteuning kan ontbreken.
  • Deze talen zullen altijd achterlopen op de nieuwste JavaScript-standaard.
  • Ontwikkelaars moeten zich bewust zijn van waar hun code naar wordt gecompileerd - omdat dat is wat daadwerkelijk zal draaien, en dat is uiteindelijk wat telt.

Praktisch gezien heeft ES2015 JavaScript enorm verbeterd en veel prettiger gemaakt om te schrijven. Ik zie tegenwoordig niet echt de noodzaak voor CoffeeScript.

Welke tools en technieken gebruikt u voor het debuggen van JavaScript-code?

  • React en Redux
    • [React Devtools]
    • [Redux Devtools]
  • Vue
    • [Vue Devtools]
  • JavaScript
    • [Chrome Devtools]
    • 'debugger' statement
    • Ouderwets 'console.log' debuggen

Welke taalconstructies gebruikt u voor het itereren over objecteigenschappen en array-items?

Voor objecten:

  • 'for-in' loops - 'for (var property in obj) { console.log(property); }'. Echter, dit zal ook itereren door zijn overgeërfde eigenschappen, en u zult een 'obj.hasOwnProperty(property)' controle toevoegen voordat u het gebruikt.
  • 'Object.keys()' - 'Object.keys(obj).forEach(function (property) { ... })'. 'Object.keys()' is een statische methode die alle opsombare eigenschappen van het object dat u doorgeeft, opsomt.
  • 'Object.getOwnPropertyNames()' - 'Object.getOwnPropertyNames(obj).forEach(function (property) { ... })'. 'Object.getOwnPropertyNames()' is een statische methode die alle opsombare en niet-opsombare eigenschappen van het object dat u doorgeeft, opsomt.

Voor arrays:

  • 'for' loops - 'for (var i = 0; i < arr.length; i++)'. De veelvoorkomende valkuil hier is dat 'var' zich in de functiescope bevindt en niet in de blokscope en meestal wilt u een blokgescoped iteratorvariabele. ES2015 introduceert 'let' die blokscope heeft en het wordt aanbevolen om die in plaats daarvan te gebruiken. Dus dit wordt: 'for (let i = 0; i < arr.length; i++)'.
  • 'forEach' - 'arr.forEach(function (el, index) { ... })'. Deze constructie kan soms handiger zijn omdat u de 'index' niet hoeft te gebruiken als u alleen de array-elementen nodig hebt. Er zijn ook de 'every' en 'some' methoden waarmee u de iteratie vroegtijdig kunt beëindigen.
  • 'for-of' loops - 'for (let elem of arr) { ... }'. ES6 introduceert een nieuwe lus, de 'for-of' lus, waarmee u kunt lussen over objecten die voldoen aan het iterable protocol, zoals 'String', 'Array', 'Map', 'Set', enz. Het combineert de voordelen van de 'for' lus en de 'forEach()'-methode. Het voordeel van de 'for' lus is dat u eruit kunt breken, en het voordeel van 'forEach()' is dat het beknopter is dan de 'for' lus omdat u geen teller nodig hebt. Met de 'for-of' lus krijgt u zowel de mogelijkheid om uit een lus te breken als een beknoptere syntaxis.

Meestal geef ik de voorkeur aan de '.forEach'-methode, maar het hangt echt af van wat u probeert te doen. Voor ES6 gebruikten we 'for'-lussen wanneer we de lus voortijdig moesten beëindigen met 'break'. Maar nu met ES6 kunnen we dat doen met 'for-of'-lussen. Ik zou 'for'-lussen gebruiken wanneer ik nog meer flexibiliteit nodig heb, zoals het meer dan eens per lus verhogen van de iterator.

Bovendien, wanneer u de 'for-of' lus gebruikt, als u zowel de index als de waarde van elk array-element wilt benaderen, kunt u dit doen met de ES6 Array 'entries()'-methode en destructuring:

const arr = ['a', 'b', 'c']; for (let [index, elem] of arr.entries()) { console.log(index, ': ', elem); }

Leg het verschil uit tussen mutable en immutable objecten.

Onveranderlijkheid is een kernprincipe in functioneel programmeren, en heeft ook veel te bieden aan objectgeoriënteerde programma's. Een veranderlijk object is een object waarvan de toestand kan worden gewijzigd nadat het is gemaakt. Een onveranderlijk object is een object waarvan de toestand niet kan worden gewijzigd nadat het is gemaakt.

Wat is een voorbeeld van een onveranderlijk object in JavaScript?

In JavaScript zijn sommige ingebouwde typen (nummers, strings) onveranderlijk, maar aangepaste objecten zijn over het algemeen veranderlijk.

Enkele ingebouwde onveranderlijke JavaScript-objecten zijn 'Math', 'Date'.

Hier zijn een paar manieren om onveranderlijkheid toe te voegen/te simuleren op gewone JavaScript-objecten.

Object constante eigenschappen

Door 'writable: false' en 'configurable: false' te combineren, kunt u in wezen een constante (kan niet worden gewijzigd, opnieuw gedefinieerd of verwijderd) maken als een objecteigenschap, zoals:

let myObject = {}; Object.defineProperty(myObject, 'number', { value: 42, writable: false, configurable: false, }); console.log(myObject.number); // 42 myObject.number = 43; console.log(myObject.number); // 42

Extensies voorkomen

Als u wilt voorkomen dat er nieuwe eigenschappen aan een object worden toegevoegd, maar de rest van de eigenschappen van het object met rust wilt laten, roept u 'Object.preventExtensions(...)' aan:

var myObject = { a: 2, }; Object.preventExtensions(myObject); myObject.b = 3; myObject.b; // undefined

In non-strict mode mislukt het creëren van 'b' stilzwijgend. In strict mode werpt het een 'TypeError'.

Verzegelen (Seal)

'Object.seal()' creëert een 'verzegeld' object, wat betekent dat het een bestaand object neemt en er in wezen 'Object.preventExtensions()' op aanroept, maar ook al zijn bestaande eigenschappen markeert als 'configurable: false'.

Dus, u kunt niet alleen geen eigenschappen meer toevoegen, maar u kunt ook geen bestaande eigenschappen opnieuw configureren of verwijderen (hoewel u hun waarden nog steeds kunt wijzigen).

Vriezen (Freeze)

'Object.freeze()' creëert een bevroren object, wat betekent dat het een bestaand object neemt en er in wezen 'Object.seal()' op aanroept, maar het markeert ook alle 'data accessor'-eigenschappen als 'writable:false', zodat hun waarden niet kunnen worden gewijzigd.

Deze aanpak is het hoogste niveau van onveranderlijkheid dat u kunt bereiken voor een object zelf, aangezien het alle wijzigingen aan het object of aan een van zijn directe eigenschappen voorkomt (hoewel, zoals hierboven vermeld, de inhoud van eventueel gerefereerde andere objecten onaangetast blijft).

var immutable = Object.freeze({});

Het bevriezen van een object staat niet toe dat er nieuwe eigenschappen aan een object worden toegevoegd en voorkomt het verwijderen of wijzigen van de bestaande eigenschappen. 'Object.freeze()' behoudt de opsombaarheid, configureerbaarheid, schrijfbaarheid en het prototype van het object. Het retourneert het doorgegeven object en creëert geen bevroren kopie.

Wat zijn de voor- en nadelen van onveranderlijkheid?

Voordelen

  • Gemakkelijkere wijzigingsdetectie - Objectgelijkheid kan op een performante en eenvoudige manier worden bepaald door referentiële gelijkheid. Dit is handig voor het vergelijken van objectverschillen in React en Redux.
  • Programma's met onveranderlijke objecten zijn minder ingewikkeld om over na te denken, aangezien u zich geen zorgen hoeft te maken over hoe een object in de loop van de tijd kan evolueren.
  • Defensieve kopieën zijn niet langer nodig wanneer onveranderlijke objecten worden geretourneerd van of doorgegeven aan functies, aangezien er geen mogelijkheid is dat een onveranderlijk object erdoor wordt gewijzigd.
  • Eenvoudig delen via referenties - Eén kopie van een object is net zo goed als een andere, dus u kunt objecten cachen of hetzelfde object meerdere keren hergebruiken.
  • Draadveilig - Onveranderlijke objecten kunnen veilig worden gebruikt tussen threads in een multi-threaded omgeving, aangezien er geen risico is dat ze worden gewijzigd in andere gelijktijdig draaiende threads.
  • Bij gebruik van bibliotheken zoals ImmutableJS worden objecten gewijzigd met structurele delen en is er minder geheugen nodig voor meerdere objecten met vergelijkbare structuren.

Nadelen

  • Naïeve implementaties van onveranderlijke gegevensstructuren en de bewerkingen ervan kunnen leiden tot extreem slechte prestaties omdat elke keer nieuwe objecten worden gemaakt. Het wordt aanbevolen om bibliotheken te gebruiken voor efficiënte onveranderlijke gegevensstructuren en bewerkingen die gebruikmaken van structureel delen.
  • Toewijzing (en deallocatie) van vele kleine objecten in plaats van het wijzigen van bestaande objecten kan een prestatie-impact veroorzaken. De complexiteit van de allocator of de garbage collector hangt meestal af van het aantal objecten op de heap.
  • Cyclische gegevensstructuren zoals grafen zijn moeilijk te bouwen. Als u twee objecten hebt die na initialisatie niet kunnen worden gewijzigd, hoe kunt u ze dan naar elkaar laten verwijzen?

Hoe kunt u onveranderlijkheid in uw eigen code bereiken?

Het alternatief is om 'const'-declaraties te gebruiken in combinatie met de hierboven genoemde technieken voor creatie. Voor het 'muteren' van objecten, gebruikt u de spread-operator, 'Object.assign', 'Array.concat()', enz., om nieuwe objecten te maken in plaats van het originele object te muteren.

Voorbeelden:

// Array voorbeeld const arr = [1, 2, 3]; const newArr = [...arr, 4]; // [1, 2, 3, 4] // Object voorbeeld const human = Object.freeze({ race: 'human' }); const john = { ...human, name: 'John' }; // {race: 'human', name: 'John'} const alienJohn = { ...john, race: 'alien' }; // {race: 'alien', name: 'John'}

Leg het verschil uit tussen synchrone en asynchrone functies.

Synchrone functies blokkeren, terwijl asynchrone functies dat niet doen. In synchrone functies worden statements voltooid voordat de volgende statement wordt uitgevoerd. In dit geval wordt het programma precies in de volgorde van de statements geëvalueerd en wordt de uitvoering van het programma gepauzeerd als een van de statements erg lang duurt.

Asynchrone functies accepteren meestal een callback als parameter en de uitvoering gaat direct verder op de volgende regel nadat de asynchrone functie is aangeroepen. De callback wordt pas aangeroepen wanneer de asynchrone bewerking is voltooid en de call stack leeg is. Zware bewerkingen zoals het laden van gegevens van een webserver of het opvragen van een database moeten asynchroon worden uitgevoerd, zodat de hoofdthread andere bewerkingen kan blijven uitvoeren in plaats van te blokkeren totdat die lange bewerking is voltooid (in het geval van browsers zal de UI bevriezen).

Wat is de event loop? Wat is het verschil tussen call stack en task queue?

De event loop is een single-threaded lus die de call stack monitort en controleert of er werk te doen is in de task queue. Als de call stack leeg is en er callback-functies in de task queue staan, wordt een functie uit de wachtrij gehaald en op de call stack geplaatst om te worden uitgevoerd.

Als u de presentatie van Philip Robert over de Event Loop nog niet hebt bekeken, zou u dat moeten doen. Het is een van de meest bekeken video's over JavaScript.

Leg de verschillen uit in het gebruik van 'foo' tussen 'function foo() {}' en 'var foo = function() {}'

De eerste is een functiedeclaratie, terwijl de laatste een functie-expressie is. Het belangrijkste verschil is dat functiedeclaraties hun body gehoist hebben, maar de bodies van functie-expressies niet (ze hebben hetzelfde hoisting-gedrag als variabelen). Voor meer uitleg over hoisting, zie de vraag hierboven over hoisting. Als u een functie-expressie probeert aan te roepen voordat deze is gedefinieerd, krijgt u een 'Uncaught TypeError: XXX is not a function'-fout.

Functiedeclaratie

foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); }

Functie-expressie

foo(); // Uncaught TypeError: foo is geen functie var foo = function () { console.log('FOOOOO'); };

Wat zijn de verschillen tussen variabelen die zijn gemaakt met 'let', 'var' of 'const'?

Variabelen die zijn gedeclareerd met het 'var'-keyword zijn gelimiteerd tot de functie waarin ze zijn gemaakt, of als ze buiten elke functie zijn gemaakt, tot het globale object. 'let' en 'const' zijn blok-gelimiteerd, wat betekent dat ze alleen toegankelijk zijn binnen de dichtstbijzijnde set accolades (functie, if-else blok, of for-lus).

function foo() { // Alle variabelen zijn toegankelijk binnen functies. var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; console.log(bar); // bar console.log(baz); // baz console.log(qux); // qux } console.log(bar); // ReferenceError: bar is niet gedefinieerd console.log(baz); // ReferenceError: baz is niet gedefinieerd console.log(qux); // ReferenceError: qux is niet gedefinieerd
if (true) { var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; } // Met var gedeclareerde variabelen zijn overal in de functiescope toegankelijk. console.log(bar); // bar // Met let en const gedefinieerde variabelen zijn niet toegankelijk buiten het blok waarin ze zijn gedefinieerd. console.log(baz); // ReferenceError: baz is niet gedefinieerd console.log(qux); // ReferenceError: qux is niet gedefinieerd

'var' staat toe dat variabelen worden gehoist, wat betekent dat er in code naar kan worden verwezen voordat ze zijn gedeclareerd. 'let' en 'const' staan dit niet toe en gooien in plaats daarvan een fout.

console.log(foo); // undefined var foo = 'foo'; console.log(baz); // ReferenceError: kan geen toegang krijgen tot lexicale declaratie 'baz' voor initialisatie let baz = 'baz'; console.log(bar); // ReferenceError: kan geen toegang krijgen tot lexicale declaratie 'bar' voor initialisatie const bar = 'bar';

Het opnieuw declareren van een variabele met 'var' zal geen fout gooien, maar 'let' en 'const' wel.

var foo = 'foo'; var foo = 'bar'; console.log(foo); // 'bar' let baz = 'baz'; let baz = 'qux'; // Uncaught SyntaxError: Identificator 'baz' is al gedeclareerd

'let' en 'const' verschillen daarin dat 'let' het opnieuw toewijzen van de waarde van de variabele toestaat, terwijl 'const' dat niet doet.

// Dit is prima. let foo = 'foo'; foo = 'bar'; // Dit veroorzaakt een uitzondering. const baz = 'baz'; baz = 'qux';

Wat zijn de verschillen tussen ES6 class en ES5 function constructors?

Laten we eerst kijken naar een voorbeeld van elk:

// ES5 Function Constructor function Person(name) { this.name = name; } // ES6 Class class Person { constructor(name) { this.name = name; } }

Voor eenvoudige constructors lijken ze behoorlijk op elkaar.

Het belangrijkste verschil in de constructor komt naar voren bij het gebruik van overerving. Als we een 'Student'-klasse willen maken die een subklasse is van 'Person' en een 'studentId'-veld willen toevoegen, dan is dit wat we moeten doen naast het bovenstaande.

// ES5 Functie Constructor function Student(name, studentId) { // Roep de constructor van de superklasse aan om leden afgeleid van de superklasse te initialiseren. Person.call(this, name); // Initialiseer de eigen leden van de subklasse. this.studentId = studentId; } Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; // ES6 Class class Student extends Person { constructor(name, studentId) { super(name); this.studentId = studentId; } }

Het is veel omslachtiger om overerving in ES5 te gebruiken en de ES6-versie is gemakkelijker te begrijpen en te onthouden.

Kunt u een use case geven voor de nieuwe arrow => functie syntaxis? Hoe verschilt deze nieuwe syntaxis van andere functies?

Een duidelijk voordeel van arrow functions is het vereenvoudigen van de syntaxis die nodig is om functies te creëren, zonder de noodzaak van het 'function' keyword. De 'this' binnen arrow functions is ook gebonden aan de omringende scope, wat anders is dan bij reguliere functies waar de 'this' wordt bepaald door het object dat deze aanroept. Lexical-scoped 'this' is nuttig bij het aanroepen van callbacks, vooral in React-componenten.

Welk voordeel biedt het gebruik van de pijlsyntaxis voor een methode in een constructor?

Het belangrijkste voordeel van het gebruik van een arrow function als methode binnen een constructor is dat de waarde van 'this' wordt ingesteld op het moment dat de functie wordt gecreëerd en daarna niet meer kan veranderen. Dus, wanneer de constructor wordt gebruikt om een nieuw object te creëren, zal 'this' altijd verwijzen naar dat object. Laten we bijvoorbeeld zeggen dat we een 'Person' constructor hebben die een voornaam als argument neemt en twee methoden heeft om die naam te 'console.loggen', één als een reguliere functie en één als een arrow function:

const Person = function (firstName) { this.firstName = firstName; this.sayName1 = function () { console.log(this.firstName); }; this.sayName2 = () => { console.log(this.firstName); }; }; const john = new Person('John'); const dave = new Person('Dave'); john.sayName1(); // John john.sayName2(); // John // De reguliere functie kan zijn 'this' waarde laten veranderen, maar de arrow function niet john.sayName1.call(dave); // Dave (omdat 'this' nu het dave object is) john.sayName2.call(dave); // John john.sayName1.apply(dave); // Dave (omdat 'this' nu het dave object is) john.sayName2.apply(dave); // John john.sayName1.bind(dave)(); // Dave (omdat 'this' nu het dave object is) john.sayName2.bind(dave)(); // John var sayNameFromWindow1 = john.sayName1; sayNameFromWindow1(); // undefined (omdat 'this' nu het window object is) var sayNameFromWindow2 = john.sayName2; sayNameFromWindow2(); // John

De belangrijkste conclusie hier is dat 'this' kan worden gewijzigd voor een normale functie, maar de context blijft altijd hetzelfde voor een arrow function. Dus zelfs als u uw arrow function doorgeeft aan verschillende delen van uw applicatie, hoeft u zich geen zorgen te maken over een veranderende context.

Dit kan met name handig zijn in React-klassecomponenten. Als u een klassemethode definieert voor iets zoals een klikhandler met behulp van een normale functie, en u geeft die klikhandler vervolgens door aan een onderliggend component als een prop, moet u ook 'this' binden in de constructor van het bovenliggende component. Als u in plaats daarvan een arrow function gebruikt, is het niet nodig om 'this' ook te binden, aangezien de methode automatisch zijn 'this' waarde krijgt uit zijn omringende lexicale context.

Wat is de definitie van een hogere-orde functie?

Een hogere-orde functie is elke functie die één of meer functies als argumenten accepteert, die het gebruikt om op gegevens te opereren, en/of een functie als resultaat retourneert. Hogere-orde functies zijn bedoeld om een bewerking te abstraheren die herhaaldelijk wordt uitgevoerd. Het klassieke voorbeeld hiervan is 'map', dat een array en een functie als argumenten accepteert. 'map' gebruikt vervolgens deze functie om elk item in de array te transformeren, en retourneert een nieuwe array met de getransformeerde gegevens. Andere populaire voorbeelden in JavaScript zijn 'forEach', 'filter' en 'reduce'. Een hogere-orde functie hoeft niet alleen arrays te manipuleren, aangezien er veel use cases zijn voor het retourneren van een functie vanuit een andere functie. 'Function.prototype.bind' is een dergelijk voorbeeld in JavaScript.

Map

Laten we zeggen dat we een array van namen hebben waarvan we elke string naar hoofdletters moeten transformeren.

const names = ['irish', 'daisy', 'anna'];

De imperatieve manier zal als volgt zijn:

const transformNamesToUppercase = function (names) { const results = []; for (let i = 0; i < names.length; i++) { results.push(names[i].toUpperCase()); } return results; }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

Het gebruik van '.map(transformerFn)' maakt de code korter en meer declaratief.

const transformNamesToUppercase = function (names) { return names.map((name) => name.toUpperCase()); }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

Kunt u een voorbeeld geven van destructuring van een object of een array?

Destructuring is een expressie die beschikbaar is in ES6 en die een beknopte en handige manier mogelijk maakt om waarden uit objecten of arrays te extraheren en deze in afzonderlijke variabelen te plaatsen.

Array destructuring

// Variabele toewijzing. const foo = ['one', 'two', 'three']; const [one, two, three] = foo; console.log(one); // 'one' console.log(two); // 'two' console.log(three); // 'three'
// Variabelen omwisselen let a = 1; let b = 3; [a, b] = [b, a]; console.log(a); // 3 console.log(b); // 1

Object destructuring

// Variabele toewijzing. const o = { p: 42, q: true }; const { p, q } = o; console.log(p); // 42 console.log(q); // true

ES6 Template Literals bieden veel flexibiliteit bij het genereren van strings, kunt u een voorbeeld geven?

Template literals maken het eenvoudig om stringinterpolatie uit te voeren, of om variabelen in een string op te nemen. Vóór ES2015 was het gebruikelijk om zoiets te doen als dit:

var person = { name: 'Tyler', age: 28 }; console.log( 'Hallo, mijn naam is ' + person.name + ' en ik ben ' + person.age + ' jaar oud!', ); // 'Hallo, mijn naam is Tyler en ik ben 28 jaar oud!'

Met template literals kunt u nu diezelfde uitvoer op deze manier creëren:

const person = { name: 'Tyler', age: 28 }; console.log(`Hallo, mijn naam is ${person.name} en ik ben ${person.age} jaar oud!`); // 'Hallo, mijn naam is Tyler en ik ben 28 jaar oud!'

Merk op dat u backticks, geen aanhalingstekens, gebruikt om aan te geven dat u een template literal gebruikt en dat u expressies kunt invoegen binnen de ${} placeholders.

Een tweede nuttige use case is het creëren van meerregelige strings. Vóór ES2015 kon u een meerregelige string als volgt maken:

console.log('Dit is regel één.\nDit is regel twee.'); // Dit is regel één. // Dit is regel twee.

Of als u het in uw code in meerdere regels wilde opsplitsen, zodat u niet naar rechts hoefde te scrollen in uw tekstverwerker om een lange string te lezen, kon u het ook als volgt schrijven:

console.log('Dit is regel één.\n' + 'Dit is regel twee.'); // Dit is regel één. // Dit is regel twee.

Template literals behouden echter elke spatie die u eraan toevoegt. Om bijvoorbeeld dezelfde meerregelige uitvoer te creëren die we hierboven hebben gemaakt, kunt u eenvoudig doen:

console.log(`Dit is regel één. Dit is regel twee.`); // Dit is regel één. // Dit is regel twee.

Een andere use case van template literals zou zijn om te gebruiken als een substituut voor templating-bibliotheken voor eenvoudige variabele-interpolaties:

const person = { name: 'Tyler', age: 28 }; document.body.innerHTML = ` <div> <p>Naam: ${person.name}</p> <p>Leeftijd: ${person.age}</p> </div> `;

Let op dat uw code vatbaar kan zijn voor XSS door het gebruik van '.innerHTML'. Sanitiseer uw gegevens voordat u ze weergeeft als ze van een gebruiker afkomstig zijn!

Kunt u een voorbeeld geven van een curry-functie en waarom deze syntaxis een voordeel biedt?

Currying is een patroon waarbij een functie met meer dan één parameter wordt opgesplitst in meerdere functies die, wanneer ze opeenvolgend worden aangeroepen, alle benodigde parameters één voor één zullen verzamelen. Deze techniek kan nuttig zijn om code die in een functionele stijl is geschreven gemakkelijker leesbaar en composeerbaar te maken. Het is belangrijk op te merken dat voor een functie om te worden gecurried, deze moet beginnen als één functie, en vervolgens moet worden opgesplitst in een reeks functies die elk één parameter accepteren.

function curry(fn) { if (fn.length === 0) { return fn; } function _curried(depth, args) { return function (newArgument) { if (depth - 1 === 0) { return fn(...args, newArgument); } return _curried(depth - 1, [...args, newArgument]); }; } return _curried(fn.length, []); } function add(a, b) { return a + b; } var curriedAdd = curry(add); var addFive = curriedAdd(5); var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]

Wat zijn de voordelen van het gebruik van spread-syntaxis en hoe verschilt het van rest-syntaxis?

De spread-syntaxis van ES6 is erg nuttig bij het programmeren in een functioneel paradigma, omdat we gemakkelijk kopieën van arrays of objecten kunnen maken zonder toevlucht te nemen tot 'Object.create', 'slice' of een bibliotheekfunctie. Deze taalfunctie wordt vaak gebruikt in Redux- en RxJS-projecten.

function putDookieInAnyArray(arr) { return [...arr, 'dookie']; } const result = putDookieInAnyArray(['Ik', 'hou', 'echt', 'niet', 'van']); // ['Ik', 'hou', 'echt', 'niet', 'van', 'dookie'] const person = { name: 'Todd', age: 29, }; const copyOfTodd = { ...person };

De rest-syntaxis van ES6 biedt een afkorting voor het opnemen van een willekeurig aantal argumenten dat aan een functie moet worden doorgegeven. Het is als een omgekeerde van de spread-syntaxis, waarbij gegevens worden opgenomen en in een array worden gestopt in plaats van een array met gegevens uit te pakken, en het werkt in functieargumenten, evenals in array- en object-destructuring-toewijzingen.

function addFiveToABunchOfNumbers(...numbers) { return numbers.map((x) => x + 5); } const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10); // [9, 10, 11, 12, 13, 14, 15] const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4] const { e, f, ...others } = { e: 1, f: 2, g: 3, h: 4, }; // e: 1, f: 2, others: { g: 3, h: 4 }

Hoe kunt u code delen tussen bestanden?

Dit hangt af van de JavaScript-omgeving.

Op de client (browseromgeving), zolang de variabelen/functies in de globale scope ('window') zijn gedeclareerd, kunnen alle scripts ernaar verwijzen. U kunt ook de Asynchronous Module Definition (AMD) via RequireJS gebruiken voor een meer modulaire aanpak.

Op de server (Node.js) is de gebruikelijke manier om CommonJS te gebruiken. Elk bestand wordt behandeld als een module en kan variabelen en functies exporteren door ze aan het 'module.exports'-object te koppelen.

ES2015 definieert een modulesyntaxis die beide AMD en CommonJS wil vervangen. Dit zal uiteindelijk zowel in browser- als in Node-omgevingen worden ondersteund.

Waarom zou u statische klassemembers willen maken?

Statische klassemembers (eigenschappen/methoden) zijn niet gekoppeld aan een specifieke instantie van een klasse en hebben dezelfde waarde, ongeacht welke instantie ernaar verwijst. Statische eigenschappen zijn typisch configuratievariabelen en statische methoden zijn meestal zuivere utilityfuncties die niet afhankelijk zijn van de staat van de instantie.