JavaScript

Wyjaśnij delegowanie zdarzeń

Delegowanie zdarzeń to technika polegająca na dodawaniu nasłuchiwaczy zdarzeń do elementu nadrzędnego zamiast dodawania ich do elementów potomnych. Nasłuchiwacz zostanie uruchomiony za każdym razem, gdy zdarzenie zostanie wyzwolone na elementach potomnych z powodu bąbelkowania zdarzeń w górę drzewa DOM. Zalety tej techniki to:

  • Zmniejszenie zużycia pamięci, ponieważ potrzebny jest tylko jeden pojedynczy handler na elemencie nadrzędnym, zamiast konieczności dołączania handlerów zdarzeń do każdego potomka.
  • Nie ma potrzeby odłączania handlera od usuniętych elementów i wiązania zdarzenia dla nowych elementów.

Wyjaśnij, jak działa `this` w JavaScript

Nie ma prostego wyjaśnienia dla this; jest to jedna z najbardziej mylących koncepcji w JavaScript. Ogólne wyjaśnienie mówi, że wartość this zależy od sposobu wywołania funkcji. Czytałem wiele wyjaśnień na temat this w internecie i wyjaśnienie [Arnav Aggrawal] wydało mi się najjaśniejsze. Stosuje się następujące zasady:

  1. Jeśli podczas wywoływania funkcji użyto słowa kluczowego new, this wewnątrz funkcji jest zupełnie nowym obiektem.
  2. Jeśli apply, call lub bind są używane do wywołania/utworzenia funkcji, this wewnątrz funkcji jest obiektem, który jest przekazany jako argument.
  3. Jeśli funkcja jest wywoływana jako metoda, np. obj.method()this jest obiektem, którego właściwością jest funkcja.
  4. Jeśli funkcja jest wywoływana jako wolne wywołanie funkcji, co oznacza, że została wywołana bez żadnego z powyższych warunków, this jest obiektem globalnym. W przeglądarce jest to obiekt window. W trybie ścisłym ('use strict'), this będzie undefined zamiast obiektu globalnego.
  5. Jeśli zastosowanie ma wiele z powyższych reguł, wygrywa reguła wyższa i ustala wartość this.
  6. Jeśli funkcja jest funkcją strzałkową ES2015, ignoruje wszystkie powyższe zasady i przyjmuje wartość this swojego otaczającego zakresu w momencie jej utworzenia.

Szczegółowe wyjaśnienie znajdziesz w jego [artykule na Medium].

Czy możesz podać przykład jednego ze sposobów, w jaki praca z tym zmieniła się w ES6?

ES6 pozwala na używanie [funkcji strzałkowych], które używają [zakresu leksykalnego otoczenia]. Jest to zazwyczaj wygodne, ale uniemożliwia wywołującemu kontrolowanie kontekstu za pomocą .call lub .apply – konsekwencją jest to, że biblioteka taka jak jQuery nie będzie prawidłowo wiązać this w funkcjach obsługi zdarzeń. Ważne jest więc, aby o tym pamiętać podczas refaktoryzacji dużych, starszych aplikacji.

Wyjaśnij, jak działa dziedziczenie prototypowe

Wszystkie obiekty JavaScript, z wyjątkiem obiektów utworzonych za pomocą Object.create(null), posiadają właściwość __proto__, która jest odniesieniem do innego obiektu, zwanego „prototypem” obiektu. Gdy właściwość jest dostępna na obiekcie, a jeśli właściwość nie zostanie znaleziona na tym obiekcie, silnik JavaScript sprawdza __proto__ obiektu, a następnie __proto__ __proto__ i tak dalej, aż znajdzie właściwość zdefiniowaną na jednym z __proto__ lub aż osiągnie koniec łańcucha prototypów. To zachowanie symuluje klasyczne dziedziczenie, ale tak naprawdę jest to bardziej [delegacja niż dziedziczenie].

Przykład dziedziczenia prototypowego

// Konstruktor obiektu nadrzędnego. function Animal(name) { this.name = name; } // Dodaj metodę do prototypu obiektu nadrzędnego. Animal.prototype.makeSound = function () { console.log('The ' + this.constructor.name + ' makes a sound.'); }; // Konstruktor obiektu potomnego. function Dog(name) { Animal.call(this, name); // Wywołaj konstruktor nadrzędny. } // Ustaw prototyp obiektu potomnego na prototyp obiektu nadrzędnego. Object.setPrototypeOf(Dog.prototype, Animal.prototype); // Dodaj metodę do prototypu obiektu potomnego. Dog.prototype.bark = function () { console.log('Woof!'); }; // Utwórz nową instancję Dog. const bolt = new Dog('Bolt'); // Wywołaj metody na obiekcie potomnym. console.log(bolt.name); // "Bolt" bolt.makeSound(); // "The Dog makes a sound." bolt.bark(); // "Woof!"

Co należy zauważyć:

  • .makeSound nie jest zdefiniowane na Dog, więc silnik idzie w górę łańcucha prototypów i znajduje .makeSound z odziedziczonego Animal.
  • Używanie Object.create do budowania łańcucha dziedziczenia nie jest już zalecane. Zamiast tego użyj Object.setPrototypeOf.

Co sądzisz o AMD vs CommonJS?

Oba są sposobami implementacji systemu modułów, który nie był natywnie obecny w JavaScript, dopóki nie pojawił się ES2015. CommonJS jest synchroniczny, podczas gdy AMD (Asynchronous Module Definition) jest oczywiście asynchroniczny. CommonJS został zaprojektowany z myślą o rozwoju po stronie serwera, podczas gdy AMD, ze wsparciem dla asynchronicznego ładowania modułów, jest bardziej przeznaczony dla przeglądarek.

Uważam składnię AMD za dość rozwlekłą, a CommonJS jest bliższy stylowi, w jakim pisałoby się instrukcje importu w innych językach. Przez większość czasu uważam AMD za niepotrzebne, ponieważ jeśli wszystkie swoje JavaScripty umieściłbyś w jednym skonsolidowanym pliku pakietu, nie skorzystałbyś z właściwości ładowania asynchronicznego. Ponadto, składnia CommonJS jest bliższa stylowi Node do pisania modułów i występuje mniejszy narzut przełączania kontekstu podczas przełączania między rozwojem JavaScript po stronie klienta a serwera.

Cieszę się, że dzięki modułom ES2015, które obsługują zarówno ładowanie synchroniczne, jak i asynchroniczne, możemy w końcu trzymać się jednego podejścia. Chociaż nie zostało to jeszcze w pełni wdrożone w przeglądarkach i w Node, zawsze możemy użyć transpilerów do konwersji naszego kodu.

Wyjaśnij, dlaczego poniższe nie działa jako IIFE: `function foo(){ }();`. Co należy zmienić, aby poprawnie uczynić z tego IIFE?

IIFE to skrót od Immediately Invoked Function Expressions (natychmiast wywoływane wyrażenia funkcyjne). Parser JavaScript odczytuje function foo(){ }(); jako function foo(){ } i ();, gdzie to pierwsze jest deklaracją funkcji, a to drugie (para nawiasów) jest próbą wywołania funkcji, ale nie podano nazwy, stąd wyrzuca Uncaught SyntaxError: Unexpected token ).

Oto dwa sposoby naprawy, które wymagają dodania większej liczby nawiasów: (function foo(){ })() i (function foo(){ }()). Instrukcje zaczynające się od function są uważane za deklaracje funkcji; przez zawinięcie tej funkcji w (), staje się ona wyrażeniem funkcyjnym, które następnie może być wykonane za pomocą kolejnych (). Te funkcje nie są eksponowane w globalnym zakresie i możesz nawet pominąć ich nazwę, jeśli nie potrzebujesz odwoływać się do siebie wewnątrz ciała.

Możesz również użyć operatora void: void function foo(){ }();. Niestety, istnieje jeden problem z takim podejściem. Ocena danego wyrażenia zawsze wynosi undefined, więc jeśli funkcja IIFE cokolwiek zwraca, nie możesz tego użyć. Przykład:

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

Jaka jest różnica między zmienną, która jest: `null`, `undefined` lub niezadeklarowana? Jak sprawdzić te stany?

Niezadeklarowane zmienne są tworzone, gdy przypisujesz wartość do identyfikatora, który nie został wcześniej utworzony za pomocą var, let lub const. Niezadeklarowane zmienne zostaną zdefiniowane globalnie, poza bieżącym zakresem. W trybie ścisłym zostanie zgłoszony ReferenceError podczas próby przypisania do niezadeklarowanej zmiennej. Niezadeklarowane zmienne są złe, tak jak złe są zmienne globalne. Unikaj ich za wszelką cenę! Aby je sprawdzić, opakuj ich użycie w blok try/catch.

function foo() { x = 1; // Zgłasza ReferenceError w trybie ścisłym } foo(); console.log(x); // 1

Zmienna, która jest undefined, to zmienna, która została zadeklarowana, ale nie przypisano jej wartości. Jest typu undefined. Jeśli funkcja nie zwraca żadnej wartości w wyniku jej wykonania, a wynik ten jest przypisany do zmiennej, zmienna również ma wartość undefined. Aby to sprawdzić, porównaj za pomocą operatora ścisłej równości (===) lub typeof, który zwróci ciąg 'undefined'. Zauważ, że nie powinieneś używać abstrakcyjnego operatora równości do sprawdzania, ponieważ zwróci on również true, jeśli wartość jest null.

var foo; console.log(foo); // undefined console.log(foo === undefined); // true console.log(typeof foo === 'undefined'); // true console.log(foo == null); // true. Błąd, nie używaj tego do sprawdzania! function bar() {} var baz = bar(); console.log(baz); // undefined

Zmienna, która jest null, została jawnie przypisana do wartości null. Reprezentuje brak wartości i różni się od undefined w tym sensie, że została jawnie przypisana. Aby sprawdzić null, po prostu porównaj za pomocą operatora ścisłej równości. Zauważ, że podobnie jak powyżej, nie powinieneś używać abstrakcyjnego operatora równości (==) do sprawdzania, ponieważ zwróci on również true, jeśli wartość jest undefined.

var foo = null; console.log(foo === null); // true console.log(typeof foo === 'object'); // true console.log(foo == undefined); // true. Błąd, nie używaj tego do sprawdzania!

Z osobistego nawyku nigdy nie pozostawiam moich zmiennych niezadeklarowanych ani nieprzypisanych. Jawnie przypiszę im null po zadeklarowaniu, jeśli nie zamierzam ich jeszcze używać. Jeśli używasz lintera w swoim procesie pracy, zazwyczaj będzie on również w stanie sprawdzić, czy nie odwołujesz się do niezadeklarowanych zmiennych.

Czym jest domknięcie i jak/dlaczego byś go użył?

Domknięcie to kombinacja funkcji i środowiska leksykalnego, w którym ta funkcja została zadeklarowana. Słowo „leksykalny” odnosi się do faktu, że leksykalne określanie zakresu używa miejsca, w którym zmienna jest zadeklarowana w kodzie źródłowym, aby określić, gdzie ta zmienna jest dostępna. Domknięcia to funkcje, które mają dostęp do zmiennych funkcji zewnętrznej (obejmującej) – łańcucha zakresu, nawet po zwróceniu funkcji zewnętrznej.

Dlaczego byś go użył?

  • Prywatność danych / emulowanie prywatnych metod za pomocą domknięć. Powszechnie używane w [wzorcu modułu].
  • [Częściowe aplikacje lub currying].

Czy możesz opisać główną różnicę między pętlą `.forEach` a pętlą `.map()` i dlaczego wybrałbyś jedną zamiast drugiej?

Aby zrozumieć różnice między tymi dwoma, przyjrzyjmy się, co robi każda funkcja.

forEach

  • Iteruje po elementach w tablicy.
  • Wykonuje callback dla każdego elementu.
  • Nie zwraca wartości.
const a = [1, 2, 3]; const doubled = a.forEach((num, index) => { // Zrób coś z num i/lub index. }); // doubled = undefined

map

  • Iteruje po elementach w tablicy.
  • „Mapuje” każdy element na nowy element, wywołując funkcję na każdym elemencie, tworząc w rezultacie nową tablicę.
const a = [1, 2, 3]; const doubled = a.map((num) => { return num * 2; }); // doubled = [2, 4, 6]

Główną różnicą między .forEach a .map() jest to, że .map() zwraca nową tablicę. Jeśli potrzebujesz wyniku, ale nie chcesz zmieniać oryginalnej tablicy, .map() jest oczywistym wyborem. Jeśli po prostu potrzebujesz iterować po tablicy, forEach jest dobrym wyborem.

Jaki jest typowy przypadek użycia funkcji anonimowych?

Mogą być używane w IIFE do hermetyzowania kodu w lokalnym zakresie, tak aby zmienne zadeklarowane w nim nie wyciekały do zakresu globalnego.

(function () { // Jakiś kod tutaj. })();

Jako callback, który jest używany raz i nie musi być używany nigdzie indziej. Kod będzie wydawał się bardziej samodzielny i czytelny, gdy handlery są definiowane bezpośrednio w kodzie, który je wywołuje, zamiast szukać funkcji w innym miejscu.

setTimeout(function () { console.log('Hello world!'); }, 1000);

Argumenty do konstrukcji programowania funkcyjnego lub Lodash (podobnie jak callbacki).

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

Jak organizujesz swój kod? (wzorzec modułowy, dziedziczenie klasyczne?)

W przeszłości używałem Backbone dla moich modeli, co zachęca do bardziej obiektowego podejścia, tworząc modele Backbone i dołączając do nich metody.

Wzorzec modułu jest nadal świetny, ale w dzisiejszych czasach używam React/Redux, które wykorzystują jednokierunkowy przepływ danych oparty na architekturze Flux. Przedstawiałbym modele mojej aplikacji za pomocą zwykłych obiektów i pisał czyste funkcje narzędziowe do manipulowania tymi obiektami. Stan jest manipulowany za pomocą akcji i reduktorów, tak jak w każdej innej aplikacji Redux.

Unikam dziedziczenia klasycznego, jeśli to możliwe. Kiedy i jeśli to robię, trzymam się [tych zasad].

Jaka jest różnica między obiektami hosta a obiektami natywnymi?

Obiekty natywne to obiekty, które są częścią języka JavaScript zdefiniowane przez specyfikację ECMAScript, takie jak String, Math, RegExp, Object, Function itp.

Obiekty hosta są dostarczane przez środowisko uruchomieniowe (przeglądarkę lub Node), takie jak window, XMLHTTPRequest itp.

Różnica między: `function Person(){}`, `var person = Person()`, a `var person = new Person()`?

To pytanie jest dość niejasne. Najlepszym moim domysłem co do jego intencji jest to, że pyta o konstruktory w JavaScript. Technicznie rzecz biorąc, function Person(){} to tylko zwykła deklaracja funkcji. Konwencją jest używanie PascalCase dla funkcji, które mają być używane jako konstruktory.

var person = Person() wywołuje Person jako funkcję, a nie jako konstruktor. Wywołanie w ten sposób jest częstym błędem, jeśli funkcja ma być używana jako konstruktor. Zazwyczaj konstruktor niczego nie zwraca, stąd wywołanie konstruktora jak zwykłej funkcji zwróci undefined, a to zostanie przypisane do zmiennej przeznaczonej na instancję.

var person = new Person() tworzy instancję obiektu Person za pomocą operatora new, który dziedziczy z Person.prototype. Alternatywą byłoby użycie Object.create, np.: 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"

Jaka jest różnica między `.call` a `.apply`?

Zarówno .call, jak i .apply są używane do wywoływania funkcji, a pierwszy parametr zostanie użyty jako wartość this w funkcji. Jednakże .call przyjmuje argumenty oddzielone przecinkami jako następne argumenty, podczas gdy .apply przyjmuje tablicę argumentów jako następny argument. Łatwym sposobem na zapamiętanie tego jest C dla call i oddzielonych przecinkami oraz A dla apply i tablicy argumentów.

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

Wyjaśnij `Function.prototype.bind`.

Dosłownie cytując [MDN]:

Metoda bind() tworzy nową funkcję, która po wywołaniu ma swoje słowo kluczowe this ustawione na podaną wartość, z daną sekwencją argumentów poprzedzającą wszelkie podane podczas wywoływania nowej funkcji.

Z mojego doświadczenia wynika, że jest to najbardziej przydatne do wiązania wartości this w metodach klas, które chcesz przekazać do innych funkcji. Jest to często robione w komponentach React.

Kiedy użyłbyś `document.write()`?

document.write() zapisuje ciąg tekstowy do strumienia dokumentu otwartego przez document.open(). Kiedy document.write() jest wykonywane po załadowaniu strony, wywoła document.open, co czyści cały dokument (<head> i <body> zostają usunięte!) i zastępuje zawartość podaną wartością parametru. Dlatego jest to zazwyczaj uważane za niebezpieczne i podatne na niewłaściwe użycie.

W Internecie można znaleźć odpowiedzi, które wyjaśniają, że document.write() jest używane w kodzie analitycznym lub [gdy chcesz dołączyć style, które powinny działać tylko wtedy, gdy JavaScript jest włączony]. Jest ono nawet używane w HTML5 boilerplate do [równoległego ładowania skryptów i zachowania kolejności wykonywania]! Podejrzewam jednak, że te powody mogą być przestarzałe i w dzisiejszych czasach można je osiągnąć bez użycia document.write(). Proszę mnie poprawić, jeśli się mylę w tej kwestii.

Jaka jest różnica między wykrywaniem funkcji, wnioskowaniem o funkcjach i używaniem ciągu UA?

Wykrywanie funkcji

Wykrywanie funkcji polega na ustaleniu, czy przeglądarka obsługuje dany blok kodu, i uruchamianiu innego kodu w zależności od tego, czy obsługuje (lub nie), tak aby przeglądarka zawsze zapewniała działające doświadczenie, a nie zawieszała się/błędy w niektórych przeglądarkach. Na przykład:

if ('geolocation' in navigator) { // Można użyć navigator.geolocation } else { // Obsłuż brak funkcji }

[Modernizr] to świetna biblioteka do obsługi wykrywania funkcji.

Wnioskowanie o funkcjach

Wnioskowanie o funkcjach sprawdza funkcję tak samo jak wykrywanie funkcji, ale używa innej funkcji, ponieważ zakłada, że ona również będzie istnieć, np.:

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

Nie jest to zbytnio zalecane. Wykrywanie funkcji jest bardziej niezawodne.

Ciąg UA

Jest to ciąg zgłaszany przez przeglądarkę, który umożliwia węzłom protokołu sieciowego identyfikację typu aplikacji, systemu operacyjnego, dostawcy oprogramowania lub wersji oprogramowania agenta użytkownika żądającego oprogramowania. Można go uzyskać za pomocą navigator.userAgent. Jednak ciąg jest trudny do przetworzenia i można go sfałszować. Na przykład Chrome zgłasza się zarówno jako Chrome, jak i Safari. Aby więc wykryć Safari, musisz sprawdzić ciąg Safari i brak ciągu Chrome. Unikaj tej metody.

Wyjaśnij Ajax tak szczegółowo, jak to możliwe.

Ajax (asynchroniczny JavaScript i XML) to zestaw technik tworzenia stron internetowych wykorzystujących wiele technologii internetowych po stronie klienta do tworzenia asynchronicznych aplikacji internetowych. Dzięki Ajax, aplikacje internetowe mogą wysyłać i pobierać dane z serwera asynchronicznie (w tle) bez zakłócania wyświetlania i zachowania istniejącej strony. Dzięki oddzieleniu warstwy wymiany danych od warstwy prezentacji, Ajax pozwala na dynamiczną zmianę treści stron internetowych, a co za tym idzie aplikacji internetowych, bez konieczności ponownego ładowania całej strony. W praktyce, nowoczesne implementacje powszechnie używają JSON zamiast XML, ze względu na zalety JSON, który jest natywny dla JavaScript.

Do komunikacji asynchronicznej często używa się API XMLHttpRequest lub, obecnie, API fetch().

Jakie są zalety i wady używania Ajax?

Zalety

  • Lepsza interaktywność. Nowa zawartość z serwera może być zmieniana dynamicznie bez konieczności ponownego ładowania całej strony.
  • Zmniejszenie liczby połączeń z serwerem, ponieważ skrypty i arkusze stylów muszą być żądane tylko raz.
  • Stan może być utrzymany na stronie. Zmienne JavaScript i stan DOM będą trwać, ponieważ główna strona kontenera nie została ponownie załadowana.
  • Zasadniczo większość zalet SPA.

Wady

  • Dynamiczne strony internetowe są trudniejsze do dodania do zakładek.
  • Nie działa, jeśli JavaScript został wyłączony w przeglądarce.
  • Niektóre roboty indeksujące strony internetowe nie wykonują JavaScriptu i nie widzą treści, które zostały załadowane przez JavaScript.
  • Strony internetowe używające Ajax do pobierania danych prawdopodobnie będą musiały połączyć pobrane dane zdalne z szablonami po stronie klienta, aby zaktualizować DOM. Aby to nastąpiło, JavaScript będzie musiał zostać przeanalizowany i wykonany w przeglądarce, a urządzenia mobilne niskiej klasy mogą mieć z tym problemy.
  • Zasadniczo większość wad SPA.

Wyjaśnij, jak działa JSONP (i dlaczego to tak naprawdę nie jest Ajax).

JSONP (JSON z dopełnieniem) to metoda powszechnie używana do ominięcia zasad dotyczących różnych domen w przeglądarkach internetowych, ponieważ żądania Ajax z bieżącej strony do domeny pochodzącej z innego źródła nie są dozwolone.

JSONP działa poprzez wysyłanie żądania do domeny pochodzącej z innego źródła za pośrednictwem tagu <script> i zazwyczaj z parametrem zapytania callback, na przykład: https://example.com?callback=printData. Serwer następnie opakowuje dane w funkcję o nazwie printData i zwraca ją klientowi.

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

Klient musi mieć funkcję printData w swoim globalnym zakresie, a funkcja zostanie wykonana przez klienta po otrzymaniu odpowiedzi z domeny pochodzącej z innego źródła.

JSONP może być niebezpieczne i ma pewne konsekwencje dla bezpieczeństwa. Ponieważ JSONP to tak naprawdę JavaScript, może robić wszystko, co JavaScript, więc musisz ufać dostawcy danych JSONP.

W dzisiejszych czasach [CORS] jest zalecanym podejściem, a JSONP jest postrzegane jako hack.

Czy kiedykolwiek używałeś szablonowania JavaScript? Jeśli tak, jakich bibliotek używałeś?

Tak. Handlebars, Underscore, Lodash, AngularJS i JSX. Nie lubiłem szablonowania w AngularJS, ponieważ w dużym stopniu wykorzystywało ciągi znaków w dyrektywach, a literówki pozostawały niewykryte. JSX jest moim nowym ulubionym, ponieważ jest bliżej JavaScriptu i prawie nie ma składni do nauczenia. Obecnie można nawet używać literałów ciągów szablonowych ES2015 jako szybkiego sposobu tworzenia szablonów bez polegania na kodzie stron trzecich.

const template = `<div>Moje imię to: ${name}</div>`;

Należy jednak pamiętać o potencjalnym XSS w powyższym podejściu, ponieważ zawartość nie jest dla Ciebie uciekająca, w przeciwieństwie do bibliotek szablonowych.

Wyjaśnij "hoisting".

Hoisting to termin używany do wyjaśnienia zachowania deklaracji zmiennych w twoim kodzie. Zmienne zadeklarowane lub zainicjowane słowem kluczowym var będą miały swoje deklaracje „przeniesione” na początek ich zakresu modułu/funkcji, co nazywamy hoistingiem. Jednakże, tylko deklaracja jest hoistowana, przypisanie (jeśli takie istnieje) pozostanie na swoim miejscu.

Zauważ, że deklaracja nie jest faktycznie przenoszona - silnik JavaScript analizuje deklaracje podczas kompilacji i staje się świadomy deklaracji i ich zakresów. Łatwiej jest zrozumieć to zachowanie, wizualizując deklaracje jako podniesione na szczyt ich zakresu. Wyjaśnijmy to na kilku przykładach.

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

Deklaracje funkcji mają hoistowane ciało, podczas gdy wyrażenia funkcyjne (napisane w formie deklaracji zmiennych) mają hoistowaną tylko deklarację zmiennej.

// Deklaracja funkcji console.log(foo); // [Function: foo] foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); } console.log(foo); // [Function: foo] // Wyrażenie funkcji console.log(bar); // undefined bar(); // Uncaught TypeError: bar is not a function var bar = function () { console.log('BARRRR'); }; console.log(bar); // [Function: bar]

Zmienne zadeklarowane za pomocą let i const również są hoistowane. Jednakże, w przeciwieństwie do var i function, nie są one inicjowane, a dostęp do nich przed deklaracją spowoduje wyjątek ReferenceError. Zmienna znajduje się w „tymczasowej strefie martwej” od początku bloku do momentu przetworzenia deklaracji.

x; // undefined y; // Reference error: y is not defined var x = 'local'; let y = 'local';

Opisz bąbelkowanie zdarzeń.

Kiedy zdarzenie zostanie wyzwolone na elemencie DOM, spróbuje ono obsłużyć zdarzenie, jeśli jest do niego przypięty nasłuchiwacz, a następnie zdarzenie jest bąbelkowane do jego rodzica i dzieje się to samo. To bąbelkowanie następuje w górę hierarchii przodków elementu aż do document. Bąbelkowanie zdarzeń jest mechanizmem stojącym za delegowaniem zdarzeń.

Jaka jest różnica między "atrybutem" a "właściwością"?

Atrybuty są definiowane w znacznikach HTML, natomiast właściwości są definiowane w DOM. Aby zilustrować różnicę, wyobraźmy sobie, że w naszym HTML-u mamy takie pole tekstowe: <input type="text" value="Hello">.

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

Ale po zmianie wartości pola tekstowego poprzez dodanie do niego „World!”, wygląda to następująco:

console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello World!

Dlaczego rozszerzanie wbudowanych obiektów JavaScript nie jest dobrym pomysłem?

Rozszerzanie wbudowanego/natywnego obiektu JavaScript oznacza dodawanie właściwości/funkcji do jego prototype. Chociaż na początku może się to wydawać dobrym pomysłem, w praktyce jest to niebezpieczne. Wyobraź sobie, że twój kod używa kilku bibliotek, które obie rozszerzają Array.prototype poprzez dodanie tej samej metody contains, implementacje nadpiszą się nawzajem, a twój kod zostanie zepsuty, jeśli zachowanie tych dwóch metod nie będzie takie samo.

Jedyny raz, kiedy możesz chcieć rozszerzyć natywny obiekt, to kiedy chcesz stworzyć polifill, zasadniczo dostarczając własną implementację dla metody, która jest częścią specyfikacji JavaScript, ale może nie istnieć w przeglądarce użytkownika z powodu tego, że jest to starsza przeglądarka.

Różnica między zdarzeniem `load` dokumentu a zdarzeniem `DOMContentLoaded` dokumentu?

Zdarzenie DOMContentLoaded jest wywoływane, gdy początkowy dokument HTML został całkowicie załadowany i przetworzony, bez czekania na załadowanie arkuszy stylów, obrazów i podkader.

Zdarzenie load obiektu window jest wywoływane tylko po załadowaniu DOM oraz wszystkich zależnych zasobów i zasobów.

Jaka jest różnica między `==` a `===`?

== to abstrakcyjny operator równości, podczas gdy === to ścisły operator równości. Operator == porówna równość po wykonaniu wszelkich niezbędnych konwersji typów. Operator === nie wykona konwersji typów, więc jeśli dwie wartości nie są tego samego typu, === po prostu zwróci false. Podczas używania == mogą dziać się dziwne rzeczy, takie jak:

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

Moja rada to nigdy nie używać operatora ==, z wyjątkiem wygody podczas porównywania z null lub undefined, gdzie a == null zwróci true, jeśli a jest null lub undefined.

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

Wyjaśnij zasadę tego samego pochodzenia w odniesieniu do JavaScript.

Zasada tego samego pochodzenia uniemożliwia JavaScriptowi wykonywanie żądań między granicami domen. Pochodzenie jest definiowane jako kombinacja schematu URI, nazwy hosta i numeru portu. Ta zasada zapobiega złośliwemu skryptowi na jednej stronie uzyskaniu dostępu do poufnych danych na innej stronie internetowej za pośrednictwem modelu obiektowego dokumentu tej strony.

Spraw, by to działało:

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]

Lub z ES6:

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

Dlaczego nazywa się to wyrażeniem trójargumentowym, co oznacza słowo "Trójargumentowe"?

Słowo "trójargumentowe" oznacza trzy, a wyrażenie trójargumentowe akceptuje trzy operandy: warunek testowy, wyrażenie "wtedy" i wyrażenie "w przeciwnym razie". Wyrażenia trójargumentowe nie są specyficzne dla JavaScriptu i nie jestem pewien, dlaczego w ogóle znajdują się na tej liście.

Czym jest `"use strict";`? Jakie są zalety i wady jego używania?

'use strict' to instrukcja używana do włączania trybu ścisłego w całych skryptach lub pojedynczych funkcjach. Tryb ścisły to sposób na wybranie ograniczonego wariantu JavaScript.

Zalety:

  • Uniemożliwia przypadkowe tworzenie zmiennych globalnych.
  • Powoduje, że przypisania, które w przeciwnym razie zakończyłyby się cicho, rzucają wyjątek.
  • Powoduje, że próby usunięcia właściwości niemożliwych do usunięcia rzucają wyjątek (gdzie wcześniej próba po prostu nie odniosłaby skutku).
  • Wymaga, aby nazwy parametrów funkcji były unikalne.
  • this jest niezdefiniowane w kontekście globalnym.
  • Wyłapuje niektóre typowe błędy w kodowaniu, rzucając wyjątki.
  • Wyłącza funkcje, które są mylące lub słabo przemyślane.

Wady:

  • Wiele brakujących funkcji, do których niektórzy programiści mogli się przyzwyczaić.
  • Brak dostępu do function.caller i function.arguments.
  • Konkatenacja skryptów napisanych w różnych trybach ścisłych może powodować problemy.

Ogólnie uważam, że korzyści przeważają nad wadami, i nigdy nie musiałem polegać na funkcjach, które tryb ścisły blokuje. Polecam używać trybu ścisłego.

Utwórz pętlę for, która iteruje do `100`, wyświetlając **"fizz"** dla wielokrotności `3`, **"buzz"** dla wielokrotności `5` i **"fizzbuzz"** dla wielokrotności `3` i `5`.

Sprawdź tę wersję FizzBuzz autorstwa [Paula Irisha].

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); }

Nie radziłbym jednak pisać powyższego podczas rozmów kwalifikacyjnych. Po prostu trzymaj się długiego, ale jasnego podejścia. Aby uzyskać więcej zwariowanych wersji FizzBuzz, sprawdź poniższy link referencyjny.

Dlaczego generalnie dobrym pomysłem jest pozostawienie globalnego zakresu strony internetowej bez zmian i nigdy go nie dotykać?

Każdy skrypt ma dostęp do globalnego zakresu, a jeśli wszyscy używają globalnej przestrzeni nazw do definiowania swoich zmiennych, prawdopodobnie wystąpią kolizje. Użyj wzorca modułu (IIFE), aby hermetyzować swoje zmienne w lokalnej przestrzeni nazw.

Dlaczego używałbyś czegoś takiego jak zdarzenie `load`? Czy to zdarzenie ma wady? Czy znasz jakieś alternatywy i dlaczego byś ich użył?

Zdarzenie load wyzwala się na końcu procesu ładowania dokumentu. W tym momencie wszystkie obiekty w dokumencie znajdują się w DOM, a wszystkie obrazy, skrypty, linki i sub-ramki zostały załadowane.

Zdarzenie DOM DOMContentLoaded zostanie wyzwolone po skonstruowaniu DOM dla strony, ale nie czeka na załadowanie innych zasobów. Jest to preferowane w niektórych przypadkach, gdy nie potrzebujesz pełnego załadowania strony przed inicjalizacją.

Wyjaśnij, czym jest aplikacja jednostronicowa (SPA) i jak uczynić ją przyjazną dla SEO.

Poniższe pochodzi z niesamowitego [Grab Front End Guide], który, zbiegiem okoliczności, jest napisany przeze mnie!

Współcześni twórcy stron internetowych określają tworzone przez siebie produkty jako aplikacje internetowe, a nie strony internetowe. Chociaż nie ma ścisłej różnicy między tymi dwoma terminami, aplikacje internetowe mają tendencję do bycia wysoce interaktywnymi i dynamicznymi, umożliwiając użytkownikowi wykonywanie działań i otrzymywanie odpowiedzi na swoje działania. Tradycyjnie przeglądarka otrzymuje HTML z serwera i go renderuje. Gdy użytkownik przechodzi do innego adresu URL, wymagane jest pełne odświeżenie strony, a serwer wysyła nowy HTML do nowej strony. Nazywa się to renderowaniem po stronie serwera.

Jednak w nowoczesnych SPA zamiast tego używane jest renderowanie po stronie klienta. Przeglądarka ładuje początkową stronę z serwera, wraz ze skryptami (frameworki, biblioteki, kod aplikacji) i arkuszami stylów wymaganymi dla całej aplikacji. Gdy użytkownik przechodzi do innych stron, odświeżenie strony nie jest wyzwalane. Adres URL strony jest aktualizowany za pomocą [HTML5 History API]. Nowe dane wymagane dla nowej strony, zazwyczaj w formacie JSON, są pobierane przez przeglądarkę za pośrednictwem żądań [AJAX] do serwera. SPA następnie dynamicznie aktualizuje stronę danymi za pomocą JavaScriptu, który został już pobrany podczas początkowego ładowania strony. Ten model jest podobny do działania natywnych aplikacji mobilnych.

Korzyści:

  • Aplikacja wydaje się bardziej responsywna, a użytkownicy nie widzą migotania między nawigacjami po stronach z powodu pełnego odświeżania strony.
  • Mniej żądań HTTP jest wysyłanych do serwera, ponieważ te same zasoby nie muszą być ponownie pobierane przy każdym ładowaniu strony.
  • Wyraźne oddzielenie zagadnień między klientem a serwerem; możesz łatwo budować nowych klientów dla różnych platform (np. urządzeń mobilnych, chatbotów, inteligentnych zegarków) bez konieczności modyfikowania kodu serwera. Możesz również niezależnie modyfikować stos technologiczny po stronie klienta i serwera, o ile umowa API nie zostanie naruszona.

Wady:

  • Cięższe początkowe ładowanie strony z powodu ładowania frameworka, kodu aplikacji i zasobów wymaganych dla wielu stron.
  • Na serwerze należy wykonać dodatkowy krok, polegający na skonfigurowaniu go tak, aby kierował wszystkie żądania do jednego punktu wejścia i umożliwiał przejęcie routingu po stronie klienta.
  • SPA są uzależnione od JavaScriptu do renderowania treści, ale nie wszystkie wyszukiwarki wykonują JavaScript podczas indeksowania i mogą widzieć pustą zawartość na Twojej stronie. To mimowolnie szkodzi optymalizacji pod kątem wyszukiwarek (SEO) Twojej aplikacji. Jednak przez większość czasu, gdy budujesz aplikacje, SEO nie jest najważniejszym czynnikiem, ponieważ nie wszystkie treści muszą być indeksowalne przez wyszukiwarki. Aby to przezwyciężyć, możesz albo renderować aplikację po stronie serwera, albo użyć usług takich jak [Prerender], aby "renderować swój JavaScript w przeglądarce, zapisać statyczny HTML i zwrócić go robotom indeksującym".

Jaki jest zakres Twojego doświadczenia z Obietnicami i/lub ich polifillami?

Posiadam praktyczną wiedzę na ten temat. Obietnica to obiekt, który może wygenerować pojedynczą wartość w pewnym momencie w przyszłości: albo wartość rozwiązana, albo powód, dla którego nie została rozwiązana (np. wystąpił błąd sieci). Obietnica może być w jednym z 3 możliwych stanów: spełniona, odrzucona lub oczekująca. Użytkownicy obietnic mogą dołączać wywołania zwrotne do obsługi spełnionej wartości lub powodu odrzucenia.

Niektóre popularne polifille to $.deferred, Q i Bluebird, ale nie wszystkie z nich są zgodne ze specyfikacją. ES2015 obsługuje Obietnice od razu po wyjęciu z pudełka, a polifille zazwyczaj nie są już potrzebne.

Jakie są plusy i minusy używania Obietnic zamiast wywołań zwrotnych?

Plusy

  • Unikaj piekła wywołań zwrotnych, które może być nieczytelne.
  • Ułatwia pisanie sekwencyjnego kodu asynchronicznego, który jest czytelny za pomocą .then().
  • Ułatwia pisanie równoległego kodu asynchronicznego za pomocą Promise.all().
  • Dzięki obietnicom, te scenariusze, które występują w kodowaniu tylko z wywołaniami zwrotnymi, nie będą miały miejsca:
    • Wywołanie wywołania zwrotnego zbyt wcześnie
    • Wywołanie wywołania zwrotnego zbyt późno (lub nigdy)
    • Wywołanie wywołania zwrotnego zbyt mało lub zbyt wiele razy
    • Brak przekazania niezbędnego środowiska/parametrów
    • Połykanie wszelkich błędów/wyjątków, które mogą wystąpić

Minusy

  • Nieco bardziej złożony kod (kwestia sporna).
  • W starszych przeglądarkach, gdzie ES2015 nie jest obsługiwany, musisz załadować polifill, aby z niego korzystać.

Jakie są zalety/wady pisania kodu JavaScript w języku, który kompiluje się do JavaScriptu?

Niektóre przykłady języków, które kompilują się do JavaScriptu, to CoffeeScript, Elm, ClojureScript, PureScript i TypeScript.

Zalety:

  • Naprawia niektóre z długotrwałych problemów w JavaScript i zniechęca do antywzorców JavaScript.
  • Umożliwia pisanie krótszego kodu, dostarczając cukier składniowy nad JavaScriptem, którego moim zdaniem brakuje ES5, ale ES2015 jest świetny.
  • Typy statyczne są niesamowite (w przypadku TypeScript) dla dużych projektów, które muszą być utrzymywane w czasie.

Wady:

  • Wymagają procesu budowania/kompilacji, ponieważ przeglądarki uruchamiają tylko JavaScript, a Twój kod będzie musiał zostać skompilowany do JavaScriptu przed dostarczeniem do przeglądarek.
  • Debugowanie może być uciążliwe, jeśli Twoje mapy źródłowe nie mapują się ładnie do Twojego prekompilowanego źródła.
  • Większość programistów nie zna tych języków i będzie musiała się ich nauczyć. Istnieje koszt wdrożenia dla Twojego zespołu, jeśli używasz ich w swoich projektach.
  • Mniejsza społeczność (zależy od języka), co oznacza, że trudniej będzie znaleźć zasoby, samouczki, biblioteki i narzędzia.
  • Wsparcie IDE/edytora może być niewystarczające.
  • Te języki zawsze będą w tyle za najnowszym standardem JavaScript.
  • Programiści powinni być świadomi, do czego kompiluje się ich kod – ponieważ to właśnie będzie faktycznie działać i to jest ostatecznie najważniejsze.

Praktycznie, ES2015 znacznie ulepszył JavaScript i uczynił go znacznie przyjemniejszym w pisaniu. Obecnie nie widzę potrzeby używania CoffeeScript.

Jakich narzędzi i technik używasz do debugowania kodu JavaScript?

  • React i Redux
    • [React Devtools]
    • [Redux Devtools]
  • Vue
    • [Vue Devtools]
  • JavaScript
    • [Chrome Devtools]
    • instrukcja debugger
    • Stary, dobry debugging console.log

Jakich konstrukcji językowych używasz do iterowania po właściwościach obiektów i elementach tablic?

Dla obiektów:

  • Pętle for-in - for (var property in obj) { console.log(property); }. Jednak będzie to również iterować przez dziedziczone właściwości, i będziesz musiał dodać sprawdzenie obj.hasOwnProperty(property) przed jego użyciem.
  • Object.keys() - Object.keys(obj).forEach(function (property) { ... }). Object.keys() to statyczna metoda, która wyświetla wszystkie wyliczalne właściwości obiektu, który jej przekazujesz.
  • Object.getOwnPropertyNames() - Object.getOwnPropertyNames(obj).forEach(function (property) { ... }). Object.getOwnPropertyNames() to statyczna metoda, która wyświetla wszystkie wyliczalne i niewyliczalne właściwości obiektu, który jej przekazujesz.

Dla tablic:

  • Pętle for - for (var i = 0; i < arr.length; i++). Częstą pułapką jest to, że var znajduje się w zakresie funkcji, a nie w zakresie bloku, i najczęściej będziesz chciał zmiennej iteratora o zakresie bloku. ES2015 wprowadza let, który ma zakres bloku i zaleca się go używać zamiast tego. Tak więc staje się to: for (let i = 0; i < arr.length; i++).
  • forEach - arr.forEach(function (el, index) { ... }). Ta konstrukcja może być czasami wygodniejsza, ponieważ nie musisz używać index, jeśli potrzebujesz tylko elementów tablicy. Istnieją również metody every i some, które pozwolą ci zakończyć iterację wcześniej.
  • Pętle for-of - for (let elem of arr) { ... }). ES6 wprowadza nową pętlę, pętlę for-of, która pozwala na iterowanie po obiektach zgodnych z protokołem iterowalnym, takich jak String, Array, Map, Set itp. Łączy ona zalety pętli for i metody forEach(). Zaletą pętli for jest to, że możesz z niej wyjść, a zaletą forEach() jest to, że jest ona bardziej zwięzła niż pętla for, ponieważ nie potrzebujesz zmiennej licznika. Dzięki pętli for-of zyskujesz zarówno możliwość wyjścia z pętli, jak i bardziej zwięzłą składnię.

W większości przypadków preferowałbym metodę .forEach, ale to naprawdę zależy od tego, co próbujesz zrobić. Przed ES6 używaliśmy pętli for, gdy musieliśmy przedwcześnie zakończyć pętlę za pomocą break. Ale teraz, dzięki ES6, możemy to zrobić za pomocą pętli for-of. Użyłbym pętli for, gdy potrzebuję jeszcze większej elastyczności, na przykład inkrementowania iteratora więcej niż raz na pętlę.

Ponadto, używając pętli for-of, jeśli potrzebujesz dostępu zarówno do indeksu, jak i wartości każdego elementu tablicy, możesz to zrobić za pomocą metody entries() tablicy ES6 i destrukturyzacji:

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

Wyjaśnij różnicę między obiektami mutowalnymi a niemutowalnymi.

Niezmienność jest podstawową zasadą w programowaniu funkcyjnym i ma wiele do zaoferowania również programom obiektowym. Obiekt mutowalny to obiekt, którego stan może być modyfikowany po jego utworzeniu. Obiekt niemutowalny to obiekt, którego stan nie może być modyfikowany po jego utworzeniu.

Jaki jest przykład obiektu niemutowalnego w JavaScript?

W JavaScript niektóre wbudowane typy (liczby, ciągi znaków) są niemutowalne, ale niestandardowe obiekty są zazwyczaj mutowalne.

Niektóre wbudowane, niemutowalne obiekty JavaScript to Math, Date.

Oto kilka sposobów na dodanie/symulację niezmienności na zwykłych obiektach JavaScript.

Stałe właściwości obiektu

Poprzez połączenie writable: false i configurable: false, możesz zasadniczo stworzyć stałą (nie może być zmieniona, ponownie zdefiniowana ani usunięta) jako właściwość obiektu, tak jak:

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

Zapobieganie rozszerzeniom

Jeśli chcesz uniemożliwić dodawanie nowych właściwości do obiektu, ale w pozostałych przypadkach pozostawić resztę właściwości obiektu w spokoju, wywołaj Object.preventExtensions(...):

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

W trybie niestricte stworzenie b kończy się cicho niepowodzeniem. W trybie ścisłym rzuca TypeError.

Pieczeniowanie (Seal)

Object.seal() tworzy „zapieczętowany” obiekt, co oznacza, że bierze istniejący obiekt i zasadniczo wywołuje na nim Object.preventExtensions(), ale także oznacza wszystkie jego istniejące właściwości jako configurable: false.

Tak więc nie tylko nie możesz dodawać więcej właściwości, ale także nie możesz ponownie konfigurować ani usuwać żadnych istniejących właściwości (chociaż nadal możesz modyfikować ich wartości).

Zamrażanie (Freeze)

Object.freeze() tworzy zamrożony obiekt, co oznacza, że bierze istniejący obiekt i zasadniczo wywołuje na nim Object.seal(), ale także oznacza wszystkie właściwości „dostępu do danych” jako writable:false, tak aby ich wartości nie mogły być zmieniane.

To podejście jest najwyższym poziomem niezmienności, jaki można osiągnąć dla samego obiektu, ponieważ zapobiega wszelkim zmianom w obiekcie lub w jego bezpośrednich właściwościach (chociaż, jak wspomniano powyżej, zawartość wszelkich innych obiektów, do których się odwołuje, pozostaje nienaruszona).

var immutable = Object.freeze({});

Zamrażanie obiektu nie pozwala na dodawanie nowych właściwości do obiektu i zapobiega usuwaniu lub zmienianiu istniejących właściwości. Object.freeze() zachowuje wyliczalność, konfigurowalność, zapisywalność i prototyp obiektu. Zwraca przekazany obiekt i nie tworzy zamrożonej kopii.

Jakie są plusy i minusy niezmienności?

Plusy

  • Łatwiejsze wykrywanie zmian - Równość obiektów może być określona w wydajny i łatwy sposób poprzez równość referencyjną. Jest to przydatne do porównywania różnic obiektów w React i Redux.
  • Programy z obiektami niezmiennymi są mniej skomplikowane do zrozumienia, ponieważ nie musisz martwić się o to, jak obiekt może ewoluować w czasie.
  • Kopie obronne nie są już konieczne, gdy niezmienne obiekty są zwracane z funkcji lub przekazywane do nich, ponieważ nie ma możliwości, aby obiekt niezmienny został przez nie zmodyfikowany.
  • Łatwe udostępnianie za pomocą referencji - Jedna kopia obiektu jest tak samo dobra jak inna, więc możesz buforować obiekty lub ponownie używać tego samego obiektu wiele razy.
  • Odporne na wątki - Obiekty niezmienne mogą być bezpiecznie używane między wątkami w środowisku wielowątkowym, ponieważ nie ma ryzyka ich modyfikacji w innych jednocześnie działających wątkach.
  • Korzystając z bibliotek takich jak ImmutableJS, obiekty są modyfikowane za pomocą współdzielenia strukturalnego, a mniej pamięci jest potrzebne do posiadania wielu obiektów o podobnych strukturach.

Minusy

  • Naiwne implementacje niemutowalnych struktur danych i ich operacji mogą skutkować niezwykle słabą wydajnością, ponieważ za każdym razem tworzone są nowe obiekty. Zaleca się używanie bibliotek dla efektywnych niemutowalnych struktur danych i operacji, które wykorzystują współdzielenie strukturalne.
  • Alokacja (i dealokacja) wielu małych obiektów zamiast modyfikowania istniejących może powodować wpływ na wydajność. Złożoność alokatora lub modułu zbierającego śmieci zwykle zależy od liczby obiektów na stercie.
  • Cykliczne struktury danych, takie jak grafy, są trudne do zbudowania. Jeśli masz dwa obiekty, których nie można modyfikować po inicjalizacji, jak możesz sprawić, by wskazywały na siebie nawzajem?

Jak osiągnąć niezmienność we własnym kodzie?

Alternatywą jest użycie deklaracji const w połączeniu z wyżej wymienionymi technikami tworzenia. Do „mutowania” obiektów używaj operatora spread, Object.assign, Array.concat() itp., aby tworzyć nowe obiekty zamiast modyfikować oryginalny obiekt.

Przykłady:

// Przykład tablicy const arr = [1, 2, 3]; const newArr = [...arr, 4]; // [1, 2, 3, 4] // Przykład obiektu const human = Object.freeze({ race: 'human' }); const john = { ...human, name: 'John' }; // {race: "human", name: "John"} const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}

Wyjaśnij różnice między funkcjami synchronicznymi a asynchronicznymi.

Funkcje synchroniczne są blokujące, podczas gdy funkcje asynchroniczne nie. W funkcjach synchronicznych instrukcje kończą się, zanim zostanie uruchomiona następna instrukcja. W tym przypadku program jest ewaluowany dokładnie w kolejności instrukcji, a wykonanie programu jest wstrzymywane, jeśli jedna z instrukcji zajmuje bardzo dużo czasu.

Funkcje asynchroniczne zazwyczaj przyjmują callback jako parametr, a wykonanie kontynuuje się w następnej linii natychmiast po wywołaniu funkcji asynchronicznej. Callback jest wywoływany tylko wtedy, gdy operacja asynchroniczna jest zakończona, a stos wywołań jest pusty. Operacje wymagające dużych zasobów, takie jak ładowanie danych z serwera internetowego lub zapytania do bazy danych, powinny być wykonywane asynchronicznie, aby główny wątek mógł kontynuować wykonywanie innych operacji zamiast blokować się do momentu zakończenia tej długiej operacji (w przypadku przeglądarek, interfejs użytkownika zamarznie).

Czym jest pętla zdarzeń? Jaka jest różnica między stosem wywołań a kolejką zadań?

Pętla zdarzeń to jednowątkowa pętla, która monitoruje stos wywołań i sprawdza, czy w kolejce zadań jest coś do zrobienia. Jeśli stos wywołań jest pusty i w kolejce zadań znajdują się funkcje zwrotne, funkcja zostaje usunięta z kolejki i wpychana na stos wywołań w celu wykonania.

Jeśli jeszcze nie obejrzałeś wystąpienia Philipa Roberta na temat pętli zdarzeń, powinieneś to zrobić. Jest to jedno z najczęściej oglądanych filmów na temat JavaScript.

Wyjaśnij różnice w użyciu `foo` między `function foo() {}` a `var foo = function() {}`

Pierwsza to deklaracja funkcji, a druga to wyrażenie funkcyjne. Kluczową różnicą jest to, że deklaracje funkcji mają hoistowane ciało, ale ciała wyrażeń funkcyjnych nie (mają takie samo zachowanie hoistingu jak zmienne). Aby uzyskać więcej wyjaśnień na temat hoistingu, odwołaj się do powyższego pytania o hoistingu. Jeśli spróbujesz wywołać wyrażenie funkcyjne przed jego zdefiniowaniem, otrzymasz błąd Uncaught TypeError: XXX is not a function.

Deklaracja funkcji

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

Wyrażenie funkcji

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

Jakie są różnice między zmiennymi utworzonymi za pomocą `let`, `var` lub `const`?

Zmienne zadeklarowane za pomocą słowa kluczowego var są ograniczone do funkcji, w której zostały utworzone, lub, jeśli zostały utworzone poza jakąkolwiek funkcją, do obiektu globalnego. let i constograniczone do bloku, co oznacza, że są dostępne tylko w najbliższym zestawie nawiasów klamrowych (funkcja, blok if-else lub pętla for).

function foo() { // Wszystkie zmienne są dostępne w funkcjach. 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 not defined console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined
if (true) { var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; } // Zmienne zadeklarowane za pomocą var są dostępne wszędzie w zakresie funkcji. console.log(bar); // bar // Zmienne zdefiniowane za pomocą let i const nie są dostępne poza blokiem, w którym zostały zdefiniowane. console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined

var pozwala na hoisting zmiennych, co oznacza, że można się do nich odwoływać w kodzie przed ich zadeklarowaniem. let i const nie pozwalają na to, zamiast tego rzucają błąd.

console.log(foo); // undefined var foo = 'foo'; console.log(baz); // ReferenceError: can't access lexical declaration 'baz' before initialization let baz = 'baz'; console.log(bar); // ReferenceError: can't access lexical declaration 'bar' before initialization const bar = 'bar';

Ponowne zadeklarowanie zmiennej za pomocą var nie spowoduje błędu, ale let i const tak.

var foo = 'foo'; var foo = 'bar'; console.log(foo); // "bar" let baz = 'baz'; let baz = 'qux'; // Uncaught SyntaxError: Identifier 'baz' has already been declared

let i const różnią się tym, że let pozwala na ponowne przypisanie wartości zmiennej, podczas gdy const nie.

// To jest w porządku. let foo = 'foo'; foo = 'bar'; // To powoduje wyjątek. const baz = 'baz'; baz = 'qux';

Jakie są różnice między klasą ES6 a konstruktorami funkcji ES5?

Najpierw przyjrzyjmy się przykładom każdego z nich:

// Konstruktor funkcji ES5 function Person(name) { this.name = name; } // Klasa ES6 class Person { constructor(name) { this.name = name; } }

W przypadku prostych konstruktorów wyglądają one dość podobnie.

Główna różnica w konstruktorze pojawia się przy użyciu dziedziczenia. Jeśli chcemy utworzyć klasę Student, która dziedziczy po Person i dodać pole studentId, to właśnie musimy zrobić oprócz powyższego.

// Konstruktor funkcji ES5 function Student(name, studentId) { // Wywołaj konstruktor nadklasy, aby zainicjować składowe pochodzące z nadklasy. Person.call(this, name); // Zainicjuj własne składowe podklasy. this.studentId = studentId; } Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; // Klasa ES6 class Student extends Person { constructor(name, studentId) { super(name); this.studentId = studentId; } }

Użycie dziedziczenia w ES5 jest znacznie bardziej obszerne, a wersja ES6 jest łatwiejsza do zrozumienia i zapamiętania.

Czy możesz podać przypadek użycia nowej składni funkcji strzałkowej `=>`? Czym ta nowa składnia różni się od innych funkcji?

Jedną z oczywistych zalet funkcji strzałkowych jest uproszczenie składni potrzebnej do tworzenia funkcji, bez konieczności używania słowa kluczowego function. this wewnątrz funkcji strzałkowych jest również związane z otaczającym zakresem, co różni się od zwykłych funkcji, gdzie this jest określane przez obiekt, który je wywołuje. this o zakresie leksykalnym jest przydatne podczas wywoływania callbacków, zwłaszcza w komponentach React.

Jaka jest zaleta używania składni strzałkowej dla metody w konstruktorze?

Główną zaletą używania funkcji strzałkowej jako metody w konstruktorze jest to, że wartość this jest ustawiana w momencie tworzenia funkcji i nie może się później zmienić. Tak więc, gdy konstruktor jest używany do tworzenia nowego obiektu, this zawsze będzie odnosić się do tego obiektu. Na przykład, załóżmy, że mamy konstruktor Person, który przyjmuje imię jako argument i ma dwie metody do console.log tego imienia, jedną jako zwykłą funkcję, a drugą jako funkcję strzałkową:

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 // Wartość 'this' zwykłej funkcji może zostać zmieniona, ale funkcja strzałkowa nie john.sayName1.call(dave); // Dave (ponieważ "this" jest teraz obiektem dave) john.sayName2.call(dave); // John john.sayName1.apply(dave); // Dave (ponieważ 'this' jest teraz obiektem dave) john.sayName2.apply(dave); // John john.sayName1.bind(dave)(); // Dave (ponieważ 'this' jest teraz obiektem dave) john.sayName2.bind(dave)(); // John var sayNameFromWindow1 = john.sayName1; sayNameFromWindow1(); // undefined (ponieważ 'this' jest teraz obiektem window) var sayNameFromWindow2 = john.sayName2; sayNameFromWindow2(); // John

Główną wnioskiem jest to, że this może być zmienione dla normalnej funkcji, ale kontekst zawsze pozostaje taki sam dla funkcji strzałkowej. Więc nawet jeśli przekazujesz swoją funkcję strzałkową do różnych części aplikacji, nie musisz martwić się o zmianę kontekstu.

Może to być szczególnie pomocne w komponentach klasowych React. Jeśli zdefiniujesz metodę klasy dla czegoś takiego jak handler kliknięcia za pomocą normalnej funkcji, a następnie przekazujesz ten handler kliknięcia do komponentu potomnego jako prop, będziesz musiał również powiązać this w konstruktorze komponentu nadrzędnego. Jeśli zamiast tego użyjesz funkcji strzałkowej, nie ma potrzeby wiązania this, ponieważ metoda automatycznie otrzyma swoją wartość this z otaczającego kontekstu leksykalnego.

Jaka jest definicja funkcji wyższego rzędu?

Funkcja wyższego rzędu to każda funkcja, która przyjmuje jedną lub więcej funkcji jako argumenty, których używa do operowania na danych, i/lub zwraca funkcję jako wynik. Funkcje wyższego rzędu mają na celu abstrakcję pewnej operacji, która jest wykonywana wielokrotnie. Klasycznym przykładem jest map, która przyjmuje tablicę i funkcję jako argumenty. map następnie używa tej funkcji do transformacji każdego elementu w tablicy, zwracając nową tablicę z przetworzonymi danymi. Inne popularne przykłady w JavaScript to forEach, filter i reduce. Funkcja wyższego rzędu nie musi tylko manipulować tablicami, ponieważ istnieje wiele przypadków użycia zwracania funkcji z innej funkcji. Function.prototype.bind jest jednym z takich przykładów w JavaScript.

Map

Powiedzmy, że mamy tablicę imion, które musimy przekształcić na wielkie litery.

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

Sposób imperatywny będzie wyglądał tak:

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']

Użycie .map(transformerFn) sprawia, że kod jest krótszy i bardziej deklaratywny.

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

Czy możesz podać przykład destrukturyzacji obiektu lub tablicy?

Destrukturyzacja to wyrażenie dostępne w ES6, które umożliwia zwięzłe i wygodne wydobywanie wartości z obiektów lub tablic i umieszczanie ich w oddzielnych zmiennych.

Destrukturyzacja tablicy

// Przypisanie zmiennej. const foo = ['one', 'two', 'three']; const [one, two, three] = foo; console.log(one); // "one" console.log(two); // "two" console.log(three); // "three"
// Zamiana zmiennych let a = 1; let b = 3; [a, b] = [b, a]; console.log(a); // 3 console.log(b); // 1

Destrukturyzacja obiektu

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

Literaly szablonowe ES6 oferują dużą elastyczność w generowaniu ciągów znaków, czy możesz podać przykład?

Literaly szablonowe ułatwiają interpolację ciągów znaków lub włączanie zmiennych do ciągu znaków. Przed ES2015 często robiono coś takiego:

var person = { name: 'Tyler', age: 28 }; console.log( 'Cześć, nazywam się ' + person.name + ' i mam ' + person.age + ' lat!', ); // 'Cześć, nazywam się Tyler i mam 28 lat!'

Z literaliami szablonowymi możesz teraz utworzyć ten sam wynik w ten sposób:

const person = { name: 'Tyler', age: 28 }; console.log(`Cześć, nazywam się ${person.name} i mam ${person.age} lat!`); // 'Cześć, nazywam się Tyler i mam 28 lat!'

Zauważ, że używasz apostrofów, a nie cudzysłowów, aby wskazać, że używasz literału szablonowego i że możesz wstawiać wyrażenia wewnątrz symboli zastępczych ${}.

Drugim pomocnym przypadkiem użycia jest tworzenie ciągów wieloliniowych. Przed ES2015 można było utworzyć ciąg wieloliniowy w ten sposób:

console.log('To jest pierwsza linia.\nTo jest druga linia.'); // To jest pierwsza linia. // To jest druga linia.

Albo jeśli chciałeś podzielić go na wiele linii w swoim kodzie, aby nie musieć przewijać w prawo w edytorze tekstu, aby przeczytać długi ciąg znaków, możesz również napisać go tak:

console.log('To jest pierwsza linia.\n' + 'To jest druga linia.'); // To jest pierwsza linia. // To jest druga linia.

Literaly szablonowe jednak zachowują wszelkie odstępy, które do nich dodajesz. Na przykład, aby utworzyć ten sam wieloliniowy wynik, który utworzyliśmy powyżej, możesz po prostu zrobić:

console.log(`To jest pierwsza linia. To jest druga linia.`); // To jest pierwsza linia. // To jest druga linia.

Innym przypadkiem użycia literałów szablonowych byłoby użycie ich jako zamiennika dla bibliotek szablonowania do prostych interpolacji zmiennych:

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

Zauważ, że Twój kod może być podatny na XSS poprzez użycie .innerHTML. Oczyść swoje dane przed ich wyświetleniem, jeśli pochodzą od użytkownika!

Czy możesz podać przykład funkcji currying i dlaczego ta składnia oferuje przewagę?

Currying to wzorzec, w którym funkcja z więcej niż jednym parametrem jest rozbita na wiele funkcji, które po wywołaniu szeregowo, będą gromadzić wszystkie wymagane parametry pojedynczo. Ta technika może być przydatna do ułatwienia czytania i komponowania kodu napisanego w stylu funkcyjnym. Ważne jest, aby pamiętać, że aby funkcja była curried, musi najpierw być jedną funkcją, a następnie zostać rozbita na sekwencję funkcji, z których każda akceptuje jeden parametr.

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]

Jakie są korzyści z używania składni spread i czym różni się od składni rest?

Składnia spread z ES6 jest bardzo przydatna podczas kodowania w paradygmacie funkcyjnym, ponieważ możemy łatwo tworzyć kopie tablic lub obiektów bez uciekania się do Object.create, slice lub funkcji bibliotecznej. Ta cecha języka jest często używana w projektach Redux i RxJS.

function putDookieInAnyArray(arr) { return [...arr, 'dookie']; } const result = putDookieInAnyArray(['Naprawdę', 'nie', 'lubię']); // ["Naprawdę", "nie", "lubię", "dookie"] const person = { name: 'Todd', age: 29, }; const copyOfTodd = { ...person };

Składnia rest z ES6 oferuje skrót do uwzględniania dowolnej liczby argumentów, które mają zostać przekazane do funkcji. Jest to jak odwrotność składni spread, pobierając dane i wkładając je do tablicy, zamiast rozpakowywać tablicę danych, i działa w argumentach funkcji, a także w przypisaniach destrukturyzacyjnych tablic i obiektów.

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 }

Jak można udostępniać kod między plikami?

To zależy od środowiska JavaScript.

Po stronie klienta (środowisko przeglądarki), dopóki zmienne/funkcje są deklarowane w globalnym zasięgu (window), wszystkie skrypty mogą się do nich odwoływać. Alternatywnie, zastosuj Asynchronous Module Definition (AMD) za pośrednictwem RequireJS dla bardziej modułowego podejścia.

Po stronie serwera (Node.js) powszechnym sposobem było używanie CommonJS. Każdy plik jest traktowany jako moduł i może eksportować zmienne i funkcje, dołączając je do obiektu module.exports.

ES2015 definiuje składnię modułów, która ma zastąpić zarówno AMD, jak i CommonJS. Będzie to ostatecznie wspierane zarówno w środowiskach przeglądarkowych, jak i Node.

Dlaczego warto tworzyć statyczne składowe klasy?

Statyczne składowe klasy (właściwości/metody) nie są powiązane z konkretną instancją klasy i mają tę samą wartość niezależnie od tego, która instancja się do nich odwołuje. Właściwości statyczne to zazwyczaj zmienne konfiguracyjne, a metody statyczne to zazwyczaj czyste funkcje użytkowe, które nie zależą od stanu instancji.