Przejdź do głównej zawartości

Pytania z JavaScript

Odpowiedzi do Front-end Job Interview Questions - JS Questions. Pull requesty mile widziane, jeśli masz sugestie i poprawki!

Wyjaśnij delegowanie zdarzeń

Delegowanie zdarzeń to technika polegająca na dodawaniu event listenerów do elementu nadrzędnego zamiast dodawania ich do elementów potomnych. Listener będzie wyzwalany za każdym razem, gdy zdarzenie zostanie wyzwolone na elementach potomnych z powodu wystąpienia zdarzenia propagującego DOM. Korzyści z tej techniki to:

  • Memory footprint zmniejsza się, ponieważ do elementu nadrzędnego potrzebny jest tylko jeden moduł obsługi, zamiast konieczności dołączania modułów obsługi zdarzeń do każdego elementu potomnego.
  • Nie ma potrzeby odłączania modułu obsługi od usuwanych elementów i wiązania zdarzenia dla nowych elementów.
Bibliografia

Wyjaśnij jak this działa w JavaScript

Nie ma prostego wyjaśnienia dla this; jest to jedna z najbardziej mylących koncepcji w JavaScript. Wytłumaczeniem na szybko jest to, że wartość this zależy od tego, jak wywoływana jest funkcja. Przeczytałem wiele wyjaśnień na temat this w Internecie i znalazłem wytłumaczenie od Arnav Aggrawal, jako najbardziej klarowne. Stosuje się następujące zasady:

  1. Jeśli słowo kluczowe new jest używane podczas wywoływania funkcji, this wewnątrz funkcji jest nowym obiektem.
  2. Jeśli apply, call, lub bind służą do wywoływania/tworzenia funkcji, this wewnątrz funkcji jest obiektem przekazywanym jako argument.
  3. Jeśli funkcja jest wywoływana jako metoda, na przykład obj.method() — this jest obiektem, którego funkcja jest własnością.
  4. Jeśli funkcja jest wywoływana jako wywołanie wolnej funkcji, co oznacza, że została wywołana bez żadnego z powyższych warunków, this jest globalnym obiektem. W przeglądarce, jest obiektem window. Jeśli jest w trybie strict mode ('use strict'), this będzie undefined zamiast globalnego obiektu.
  5. Jeśli stosuje się wiele powyższych zasad, reguła, która jest wyższa, wygrywa i ustawi wartość this.
  6. Jeśli funkcja jest funkcją strzałkową (arrow function) ES2015, ignoruje wszystkie powyższe reguły i otrzymuje wartość this swojego otaczającego zakresu w momencie jej tworzenia.

Aby uzyskać szczegółowe wyjaśnienie, sprawdź jego artykuł na Medium.

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

ES6 umożliwia korzystanie z funkcji strzałkowych (arrow functions) które wykorzystują enclosing lexical scope. Jest to zwykle wygodne, ale nie zabezpiecza caller przed kontrolowaniem kontekstu przez .call lub .apply— konsekwencją jest to, że biblioteka taka jak jQuery nie będzie poprawnie bindować this w funkcjach obsługi zdarzeń. Dlatego ważne jest, aby o tym pamiętać przy refaktoryzacji dużych aplikacji.

Bibliografia

Wyjaśnij, jak działa dziedziczenie prototypowe

To jest bardzo częste pytanie dotyczące rozmowy rekrutacyjnej w JavaScript. Wszystkie obiekty JavaScript mają właściwość __proto__, jest to odniesienie do innego obiektu, który nazywa się "prototypem" obiektu. Gdy właściwość jest udostępniana na obiekt i jeśli właściwość nie została znaleziona na tym obiekcie, silnik JavaScript sprawdza __proto__ obiektu oraz __proto__ z __proto__ i tak dalej, dopóki nie znajdzie właściwości zdefiniowanej w jednym z __proto__ lub dopóki nie osiągnie końca łańcucha prototypów. To zachowanie symuluje klasyczne dziedziczenie, ale tak naprawdę jest bardziej delegowaniem niż dziedziczeniem.

Przykład dziedziczenia prototypowego

Mamy już wbudowane Object.create, ale gdybyś dostarczył dla niego polyfill, mogłoby to wyglądać:

if (typeof Object.create !== 'function') {
Object.create = function (parent) {
function Tmp() {}
Tmp.prototype = parent;
return new Tmp();
};
}

const Parent = function () {
this.name = 'Parent';
};

Parent.prototype.greet = function () {
console.log('hello from Parent');
};

const child = Object.create(Parent.prototype);

child.cry = function () {
console.log('waaaaaahhhh!');
};

child.cry();
// Outputs: waaaaaahhhh!

child.greet();
// Outputs: hello from Parent

Warto zwrócić uwagę na:

  • .greet nie jest zdefiniowane w child, więc silnik idzie w górę łańcucha prototypów i znajduje .greet odziedziczone z Parent.
  • Musimy wywołać Object.create na jeden z następujących sposobów dziedziczenia prototypowych metod:
    • Object.create(Parent.prototype);
    • Object.create(new Parent(null));
    • Object.create(objLiteral);
    • W tej chwili, child.constructor wskazuje na Parent:
child.constructor
ƒ () {
this.name = "Parent";
}
child.constructor.name
"Parent"
  • Jeśli chcielibyśmy to naprawić, jedną z opcji byłoby:
function Child() {
Parent.call(this);
this.name = 'child';
}

Child.prototype = Parent.prototype;
Child.prototype.constructor = Child;

const c = new Child();

c.cry();
// Outputs: waaaaaahhhh!

c.greet();
// Outputs: hello from Parent

c.constructor.name;
// Outputs: "Child"
Bibliografia

Co sądzisz o AMD vs CommonJS?

Oba sposoby implementacji systemu modułowego, który nie pojawił się natywnie w JavaScript przed pojawieniem się ES2015. CommonJS jest synchroniczny, podczas gdy AMD (definicja modułu asynchronicznego) 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 przeznaczone dla przeglądarek.

Uważam, że składnia AMD jest dość wymowna, a CommonJS jest bliższy stylowi, w którym pisałbyś instrukcje importu w innych językach. Przez większość czasu uważam, że AMD jest niepotrzebne, ponieważ jeśli podałeś cały JavaScript w jednym połączonym pliku pakietu, nie skorzystałbyś z właściwości ładowania asynchronicznego. Ponadto składnia CommonJS jest bliższa stylowi pisania modułów w Node, a przełączanie między tworzeniem JavaScript po stronie klienta i serwera jest mniejsze.

Cieszę się, że dzięki modułom ES2015, które obsługują zarówno ładowanie synchroniczne, jak i asynchroniczne, możemy w końcu zastosować jedno podejście. Chociaż nie został w pełni wdrożony w przeglądarkach i w Node, zawsze możemy użyć transpilatorów do konwersji naszego kodu.

Bibliografia

Wyjaśnij, dlaczego następujące elementy nie działają jako IIFE: function foo(){ }();. Co należy zmienić, aby poprawnie uczynić to IIFE?

IIFE oznacza Immediately Invoked Function Expressions. Parser JavaScript czyta function foo(){ }(); jako function foo(){ } i ();, gdzie ten pierwszy to deklaracja funkcji i ten drugi (para nawiasów) jest próbą wywołania funkcji, ale nie ma określonej nazwy, dlatego rzuca Uncaught SyntaxError: Unexpected token ).

Oto dwa sposoby rozwiązania tego problemu, polegające na dodaniu większej liczby nawiasów: (function foo(){ })() oraz (function foo(){ }()). Deklaracje zaczynające się od function są uważane za deklaracji funkcji; poprzez zawinięcie tej funkcji wewnątrz (), staje się wyrażeniem funkcji które mogą być następnie wykonane z kolejnym (). Funkcje te nie są ujawniane w zakresie globalnym i można nawet pominąć jego nazwę, jeśli nie trzeba odwoływać się do ciała.

Możesz także użyć operatora void: void function foo(){ }();. Niestety istnieje jeden problem związany z takim podejściem. Ocena danego wyrażenia jest zawsze undefined, więc jeśli funkcja IIFE zwraca cokolwiek, nie możesz jej użyć. Przykład:

const foo = void (function bar() {
return 'foo';
})();

console.log(foo); // undefined
Bibliografia

Jaka jest różnica między zmienną: null, undefined lub niezadeklarowaną? Jak sprawdziłbyś którykolwiek z tych stanów?

Niezadeklarowane zmienne są tworzone, gdy przypisujesz wartość do identyfikatora, który nie był wcześniej tworzony przy użyciu var, let lub const. Niezadeklarowane zmienne zostaną zdefiniowane globalnie, poza bieżącym zakresem. W trybie strict mode, ReferenceError zostanie rzucony, gdy spróbujesz przypisać do niezadeklarowanej zmiennej. Niezadeklarowane zmienne są złe, tak jak zmienne globalne są złe. Unikaj ich za wszelką cenę! Aby je sprawdzić, zawiń jego użycie w bloku try/catch.

function foo() {
x = 1; // Throws a ReferenceError in strict mode
}

foo();
console.log(x); // 1

Zmienna undefined jest zmienną, która została zadeklarowana, ale nie ma przypisanej wartości. Jest to typu undefined. Jeśli funkcja nie zwraca żadnej wartości, ponieważ w wyniku jej wykonania jest przypisana do zmiennej, zmienna ma również wartość undefined. Aby to sprawdzić, porównaj, stosując operatora ścisłej równości (===) lub typeof który da string 'undefined'. Zauważ, że nie powinieneś używać operatora abstrakcyjnej równości do sprawdzania, ponieważ zwróci on również wartość true, jeśli wartość wynosi null.

var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === 'undefined'); // true

console.log(foo == null); // true. Wrong, don't use this to check!

function bar() {}
var baz = bar();
console.log(baz); // undefined

Zmienna która jest null zostanie wyraźnie przypisana do wartości null. Nie reprezentuje żadnej wartości i różni się od undefined w tym sensie, że zostało to wyraźnie przypisane. Aby sprawdzić null, po prostu porównaj, używając operatora ścisłej równości. Pamiętaj, że podobnie jak powyżej, nie powinieneś używać abstrakcyjnego operatora równości (==) do sprawdzenia, to również zwróci 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. Wrong, don't use this to check!

Jako osobisty nawyk nigdy nie pozostawiam moich zmiennych niezadeklarowanych ani nieprzypisanych. Wyraźnie przypiszę im null po zadeklarowaniu, jeśli nie zamierzam jej jeszcze używać. Jeśli użyjesz lintera w swoim przepływie pracy, zwykle będzie on również w stanie sprawdzić, czy nie odwołujesz się do niezadeklarowanych zmiennych.

Bibliografia

Czym jest domknięcie i jak/dlaczego miałbyś je zastosować?

Domknięcie (closure) jest kombinacją funkcji i środowiska leksykalnego, w którym zadeklarowano tę funkcję. Słowo "leksykalny" odnosi się do faktu, że zakres leksykalny wykorzystuje lokalizację, w której zmienna jest zadeklarowana w kodzie źródłowym, w celu ustalenia, gdzie ta zmienna jest dostępna. Domknięcia to funkcje, które mają dostęp do zmiennych funkcji zewnętrznej (obejmującej) - łańcuch zasięgu nawet po zwróceniu funkcji zewnętrznej.

Dlaczego miałbyś skorzystać z tego?

Bibliografia

Czy możesz opisać główną różnicę pomiędzy pętlą .forEach, a pętlą .map() i dlaczego wybrałbyś jeden albo drugi?

Aby zrozumieć różnice między nimi, spójrzmy na to, co robi każda funkcja.

forEach

  • Iteruje przez elementy w tablicy.
  • Wykonuje callback dla każdego elementu.
  • Nie zwraca wartości.
const a = [1, 2, 3];
const doubled = a.forEach((num, index) => {
// Do something with num and/or index.
});

// doubled = undefined

map

  • Iteruje przez elementy w tablicy.
  • "Mapuje" każdy element do nowego elementu, 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łówna różnica między .forEach i .map() to to, że .map() zwraca nową tablicę. Jeśli potrzebujesz wyniku, ale nie chcesz mutować oryginalnej tablicy, .map() jest jasnym wyborem. Jeśli potrzebujesz po prostu iterować tablicę, forEach jest dobrym wyborem.

Bibliografia

Jaki jest typowy przypadek użycia funkcji anonimowych?

Można ich użyć w IIFE do enkapsulacji części kodu w zakresie lokalnym, tak aby zmienne zadeklarowane w nim nie przenikały do zakresu globalnego.

(function () {
// Some code here.
})();

Jako callback, które jest używane raz i nie musi być używane nigdzie indziej. Kod będzie wydawał się bardziej samodzielny i czytelny, gdy procedury obsługi zostaną zdefiniowane bezpośrednio w kodzie wywołującym je, zamiast konieczności szukania gdzie indziej w celu znalezienia w ciele funkcji.

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

Argumenty do konstrukcji funkcjonalnego programowania lub Lodasha (podobne do callbacków).

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

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

W przeszłości używałem Backbone do moich modeli, co zachęca do bardziej otwartego podejścia, tworzenia modeli Backbone i dołączania do nich metod.

Wzorzec modułu jest nadal świetny, ale obecnie używam React/Redux, które wykorzystują jednokierunkowy przepływ danych oparty na architekturze Flux. Reprezentowałbym modele mojej aplikacji przy użyciu prostych obiektów i pisał funkcje czysto użytkowe do manipulowania tymi obiektami. Stan jest manipulowany za pomocą akcji i reduktorów, jak w każdej innej aplikacji Redux.

W miarę możliwości unikam dziedziczenia klasycznego. Kiedy już i jeśli to zrobię, trzymam się tych reguł.

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

Obiekty macierzyste to obiekty, które są częścią języka JavaScript zdefiniowanego w specyfikacji ECMAScript, takie jak String, Math, RegExp, Object, Function, etc.

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

Bibliografia

Różnica pomiędzy: function Person(){}, var person = Person(), i var person = new Person()?

To pytanie jest dość niejasne. Myślę, że jego intencją jest to, że pyta o konstruktory w JavaScript. Z technicznego punktu widzenia, function Person(){} jest zwykłą deklaracją funkcji. Konwencja polega na wykorzystaniu PascalCase do funkcji, które mają być używane jako konstruktory.

var person = Person() wywołuje Person jako funkcję, i nie jako konstruktor. Wywołanie jako takie jest częstym błędem, jeśli funkcja ma być używana jako konstruktor. Zazwyczaj konstruktor niczego nie zwraca, dlatego wywołanie konstruktora jak normalnej funkcji zwróci undefined, a to zostanie przypisane do zmiennej przeznaczonej jako instancja.

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

Jaka jest różnica pomiędzy .call i .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 .call przyjmuje argumenty oddzielone przecinkami jako kolejne argumenty, podczas gdy .apply przyjmuje tablicę argumentów jako następny argument. Łatwym sposobem na zapamiętanie tego jest C dla call i oddzielone przecinkami, i A dla apply oraz tablica 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

Wytłumacz Function.prototype.bind.

Wzięte słowo w słowo zMDN:

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

Z mojego doświadczenia wynika, że jest to najbardziej przydatne do bindowania wartości this w metodach klas, które chcesz przekazać innym funkcjom. Często odbywa się to w komponentach React.

Bibliografia

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

document.write() zapisuje ciąg tekstu do strumienia dokumentów otwartego przez document.open(). Kiedy document.write() jest wykonywane po załadowaniu strony, wywoła document.open, który usuwa cały dokument (<head>i <body> usunięto!) i zamienia zawartość na podaną wartość parametru. Dlatego jest zwykle uważany za niebezpieczny i podatny na niewłaściwe użycie.

Istnieje kilka odpowiedzi online, które wyjaśniają, że w kodzie analitycznym używany jest document.write() lub gdy chcesz dołączyć style, które powinny działać tylko wtedy, gdy JavaScript jest włączony. Jest nawet używany w HTML5 boilerplate do równoległego ładowania skryptów i zachowania kolejności wykonywania! Podejrzewam jednak, że przyczyny te mogą być nieaktualne i we współczesnych czasach można je osiągnąć bez użycia document.write(). Proszę, popraw mnie, jeśli się mylę.

Bibliografia

Jaka jest różnica między wykrywaniem funkcji, feature inference i używaniem UA string?

Feature Detection

Wykrywanie funkcji polega na sprawdzeniu, czy przeglądarka obsługuje określony blok kodu, i uruchomieniu innego kodu w zależności od tego, czy to robi (czy nie), tak aby przeglądarka zawsze zapewniała działanie w przypadku awarii/błędów w niektórych przeglądarkach. Na przykład:

if ('geolocation' in navigator) {
// Can use navigator.geolocation
} else {
// Handle lack of feature
}

Modernizr to świetna biblioteka do obsługi feature detection.

Feature Inference

Feature inference sprawdza funkcję podobnie 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);
}

To nie jest naprawdę zalecane. Wykrywanie funkcji jest bardziej niezawodne.

UA String

Jest to string zgłaszany przez przeglądarkę, który umożliwia elementom protokołu sieciowego identyfikowanie typu aplikacji, systemu operacyjnego, dostawcy oprogramowania lub wersji oprogramowania żądającego agenta użytkownika oprogramowania. Można uzyskać do niego dostęp za pośrednictwem navigator.userAgent. Jednak string jest trudny do przeanalizowania i może zostać sfałszowany. Na przykład Chrome zgłasza zarówno Chrome, jak i Safari. Aby wykryć Safari, musisz sprawdzić string Safari i nieobecność Chrome string. Unikaj tej metody.

Bibliografia

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

Ajax (asynchronous JavaScript and XML - asynchroniczny JavaScript i XML) to zestaw technik tworzenia stron internetowych wykorzystujących wiele technologii sieciowych po stronie klienta do tworzenia asynchronicznych aplikacji internetowych. Dzięki Ajax aplikacje internetowe mogą wysyłać dane i pobierać je z serwera asynchronicznie (w tle) bez ingerencji w wyświetlanie i zachowanie istniejącej strony. Oddzielając warstwę wymiany danych od warstwy prezentacji, Ajax pozwala stronom internetowym, a poprzez rozszerzenia aplikacji internetowych, dynamicznie zmieniać treść bez konieczności ponownego ładowania całej strony. W praktyce nowoczesne implementacje często zastępują użycie JSON zamiast XML, ze względu na zalety natywnej obsługi JSON w JavaScript.

API XMLHttpRequest jest często używany do komunikacji asynchronicznej lub w dzisiejszych czasach, fetch API.

Bibliografia

Jakie są zalety i wady korzystania z Ajax?

Zalety

  • Lepsza interaktywność. Nowa zawartość z serwera może być zmieniana dynamicznie bez potrzeby przeładowywania całej strony.
  • Zmniejsza liczbę połączeń z serwerem, ponieważ skrypty i arkusze stylów muszą być wymagane tylko raz.
  • Stan można utrzymać na stronie. Zmienne JavaScript i stan DOM zostaną zachowane, 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 jest wyłączony w przeglądarce.
  • Niektóre przeglądarki internetowe nie wykonują JavaScript i nie widzą treści załadowanych przez JavaScript.
  • Strony internetowe wykorzystujące Ajax do pobierania danych prawdopodobnie będą musiały połączyć pobrane dane zdalne z szablonami po stronie klienta, aby zaktualizować DOM. Aby tak się stało, JavaScript musi zostać przeanalizowany i wykonany w przeglądarce, a urządzenia mobilne z niższej półki mogą mieć z tym problem.
  • Zasadniczo większość wad SPA.

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

JSONP (JSON with Padding) jest metodą powszechnie używaną do omijania zasad międzydomenowych w przeglądarkach internetowych, ponieważ żądania Ajax z bieżącej strony do domeny międzydomenowej są niedozwolone.

JSONP działa poprzez wysłanie żądania do domeny cross-origin za pomocą znacznika <script> i zwykle za pomocą parametru zapytania callback, na przykład: https://example.com?callback=printData. Serwer następnie opakowuje dane w funkcję o nazwie printData i zwraca je klientowi.

<!-- https://mydomain.com -->
<script>
function printData(data) {
console.log(`My name is ${data.name}!`);
}
</script>

<script src="https://example.com?callback=printData"></script>
// File loaded from https://example.com?callback=printData
printData({ name: 'Yang Shun' });

Klient musi mieć funkcję printData w swoim globalnym zasięgu, a funkcja zostanie wykonana przez klienta po otrzymaniu odpowiedzi z domeny cross-origin.

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

Obecnie, CORS jest zalecanym podejściem, a JSONP jest postrzegany jako hack.

Bibliografia

Czy kiedykolwiek używałeś szablonów JavaScript? Jeśli tak, z jakich bibliotek korzystałeś?

Tak. Handlebars, Underscore, Lodash, AngularJS, i JSX. Nie podobało mi się tworzenie szablonów w AngularJS, ponieważ często używało łańcuchów w dyrektywach, a literówki nie zostały złapane. JSX jest moim nowym ulubionym, ponieważ jest bliżej JavaScript i nie ma prawie żadnej składni do nauki. W dzisiejszych czasach możesz nawet używać literałów ciągów szablonów ES2015 jako szybkiego sposobu tworzenia szablonów bez konieczności korzystania z kodu innej firmy.

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

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

Wytłumacz "hoisting".

Hoisting to termin używany do wyjaśnienia zachowania deklaracji zmiennych w kodzie. Zmienne zadeklarowane lub zainicjowane słowem kluczowym var będą miały swoją deklarację "przeniesioną" na górę zakresu na poziomie modułu/funkcji, który nazywamy windowaniem. Jednak tylko deklaracja jest windowana, przydział (jeśli taki istnieje) pozostanie tam, gdzie jest.

Zauważ, że deklaracja nie została faktycznie przeniesiona - silnik JavaScript analizuje deklaracje podczas kompilacji i dowiaduje się o deklaracjach i ich zakresach. Po prostu łatwiej jest zrozumieć to zachowanie, wizualizując deklaracje jako podnoszone na szczyt ich zakresu. Wyjaśnijmy kilka przykładów.

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

W deklaracjach funkcji podnoszone jest ciało, podczas gdy w wyrażeniach funkcji (zapisanych w formie deklaracji zmiennych) windowanana jest tylko deklaracja zmiennej.

// Function Declaration
console.log(foo); // [Function: foo]
foo(); // 'FOOOOO'
function foo() {
console.log('FOOOOO');
}
console.log(foo); // [Function: foo]

// Function Expression
console.log(bar); // undefined
bar(); // Uncaught TypeError: bar is not a function
var bar = function () {
console.log('BARRRR');
};
console.log(bar); // [Function: bar]

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

x; // undefined
y; // Reference error: y is not defined

var x = 'local';
let y = 'local';
Bibliografia

Opisz event bubbling.

Kiedy zdarzenie zostanie wyzwolone na elemencie DOM, spróbuje obsłużyć to zdarzenie, jeśli dołączony jest detektor, a następnie zdarzenie zostanie przekazane do jego obiektu nadrzędnego i nastąpi to samo. To 'bubbling' występuje u przodków elementu aż do document. Event bubbling jest mechanizmem delegowania zdarzeń.

Jaka jest różnica pomiędzy "attribute", a "property"?

Atrybuty są zdefiniowane w znacznikach HTML, ale właściwości są zdefiniowane w DOM. Aby zilustrować różnicę, wyobraź sobie, że mamy to pole tekstowe w naszym HTML: <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 przez dodanie "World!" staje się to:

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

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

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

Jedynym momentem, w którym możesz chcieć rozszerzyć obiekt macierzysty, jest utworzenie polyfill, zasadniczo zapewniające własną implementację metody, która jest częścią specyfikacji JavaScript, ale może nie istnieć w przeglądarce użytkownika, jeśli jest to starsza przeglądarka.

Bibliografia

Różnica między document load event, a document DOMContentLoaded event?

Zdarzenie DOMContentLoaded jest uruchamiane, gdy początkowy dokument HTML został całkowicie załadowany i przeanalizowany, bez oczekiwania na zakończenie ładowania arkuszy stylów, obrazów i podramek.

windowsowe zdarzenie load jest uruchamiane dopiero po załadowaniu DOM i wszystkich zależnych zasobów i assetów.

Bibliografia

Jaka jest różnica pomiędzy == i ===?

== jest abstrakcyjnym operatorem równości, podczas gdy === jest operatorem ścisłej równości. Operator == porówna pod kątem równości po wykonaniu niezbędnych konwersji typu. Operator === nie dokona konwersji typu, więc jeśli dwie wartości nie są tego samego typu, === po prostu zwróci false. Gdy używasz ==, mogą się zdarzyć dziwne rzeczy, takie jak:

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

Radzę nigdy nie korzystać z operatora ==, z wyjątkiem wygody przy porównywaniu 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
Bibliografia

Wyjaśnij same-origin policy w odniesieniu do JavaScript.

Same-origin policy zapobiega przesyłaniu żądań JavaScript przez granice domen. Źródło jest zdefiniowane jako kombinacja schematu URI, nazwy hosta i numeru portu. Ta zasada uniemożliwia złośliwemu skryptowi na jednej stronie uzyskanie dostępu do poufnych danych na innej stronie internetowej za pomocą modelu obiektowego dokumentu (DOM) tej strony.

Bibliografia

Spraw aby 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"?

"Ternary" wskazuje trzy, a wyrażenie potrójne akceptuje trzy operandy, warunek testu, wyrażenie "then" i wyrażenie "else". Wyrażenia trójskładnikowe nie są specyficzne dla JavaScript i nie jestem pewien, dlaczego znajduje się nawet na tej liście.

Bibliografia

Czym jest "use strict";? Jakie są zalety i wady korzystania z tego?

'use strict' to instrukcja używana do włączenia trybu strict mode do całych skryptów lub poszczególnych funkcji. Tryb strict mode to sposób na włączenie ograniczonej wersji JavaScript.

Zalety:

  • Uniemożliwia przypadkowe utworzenie zmiennych globalnych.
  • Wykonuje zadania, które w przeciwnym razie cicho nie rzucałyby wyjątku.
  • Podejmuje próby usunięcia właściwości nieusuwalnych (gdzie przed próbą po prostu nie miałby żadnego efektu).
  • Wymaga, aby nazwy parametrów funkcji były unikalne.
  • this jest niezdefiniowane w kontekście globalnym.
  • Łapie niektóre popularne coding bloopers, rzucając wyjątki.
  • Wyłącza funkcje, które są mylące lub źle przemyślane.

Wady:

  • Wiele brakujących funkcji, do których niektórzy programiści mogą być przyzwyczajeni.
  • Nie ma już dostępu do function.caller i function.arguments.
  • Łączenie skryptów napisanych w różnych trybach strict mode może powodować problemy.

Ogólnie rzecz biorąc, uważam, że korzyści przeważają nad wadami i nigdy nie musiałem polegać na funkcjach blokujących tryb strict mode. Poleciłbym użycie trybu strict mode.

Bibliografia

Utwórz pętlę for, która będzie się powtarzać do 100 podczas wysyłania "fizz" w wielokrotnościach 3, "buzz" w wielokrotnościach 5 i "fizzbuzz" w wielokrotnościach 3 oraz 5.

Sprawdź tę wersję FizzBuzz od 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);
}

Nie radzę jednak pisać wyżej podczas wywiadów. Trzymaj się długiego, ale wyraźnego podejścia. Aby uzyskać bardziej zwariowane wersje FizzBuzz, sprawdź odnośnik poniżej.

Bibliografia

Dlaczego generalnie dobrym pomysłem jest pozostawienie globalnego zasięgu witryny takim, jakim jest, i nigdy go nie dotykać?

Każdy skrypt ma dostęp do zakresu globalnego, a jeśli wszyscy użyją globalnej przestrzeni nazw do zdefiniowania swoich zmiennych, prawdopodobnie dojdzie do kolizji. Wzorzec modułu (IIFE) służy do enkapsulacji zmiennych w lokalnej przestrzeni nazw.

Dlaczego miałbyś używać czegoś takiego jak zdarzenie load? Czy to wydarzenie ma wady? Czy znasz jakieś alternatywy i dlaczego miałbyś je wykorzystać?

Zdarzenie load odpala po zakończeniu ładowania dokumentu. W tym momencie wszystkie obiekty w dokumencie znajdują się w DOM, a wszystkie obrazy, skrypty, łącza i podramki zostały załadowane.

Zdarzenie DOM DOMContentLoaded uruchomi się po zbudowaniu DOM dla strony, ale nie czekaj na zakończenie ładowania innych zasobów. Jest to preferowane w niektórych przypadkach, gdy nie trzeba ładować całej strony przed inicjalizacją.

TODO.

Bibliografia

Wyjaśnij, czym jest SAP i jak uczynić ją przyjazną SEO.

Poniżej zaczerpnięto z niesamowitego Grab Front End Guide, który przypadkiem jest napisany przeze mnie!

Obecnie twórcy stron internetowych odnoszą się do produktów, które budują, jako aplikacji internetowych, a nie stron internetowych. Chociaż nie ma ścisłej różnicy między tymi dwoma terminami, aplikacje internetowe wydają się być wysoce interaktywne i dynamiczne, umożliwiając użytkownikowi wykonywanie działań i otrzymywanie odpowiedzi na ich działanie. Tradycyjnie przeglądarka odbiera HTML z serwera i renderuje go. Gdy użytkownik przechodzi do innego adresu URL, wymagane jest odświeżenie całej strony, a serwer wysyła nowy kod HTML na nową stronę. Nazywa się to renderowaniem po stronie serwera.

Jednak w nowoczesnych SPA stosuje się zamiast tego renderowanie po stronie klienta. Przeglądarka ładuje stronę początkową 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żanie strony nie jest uruchamiane. Adres URL strony jest aktualizowany za pośrednictwem HTML5 History API. Nowe dane wymagane dla nowej strony, zwykle w formacie JSON, są pobierane przez przeglądarkę za pośrednictwem żądań AJAX do serwera. SPA następnie dynamicznie aktualizuje stronę danymi za pomocą JavaScript, który został już pobrany podczas ładowania strony. Ten model jest podobny do działania natywnych aplikacji mobilnych.

Korzyści:

  • Aplikacja jest bardziej responsywna, a użytkownicy nie widzą flashowania między nawigacjami po stronach z powodu odświeżania całej strony.
  • Na serwer przesyłanych jest mniej żądań HTTP, ponieważ te same zasoby nie muszą być pobierane ponownie przy każdym ładowaniu strony.
  • Jasne rozdzielenie spraw między klientem a serwerem; możesz łatwo budować nowych klientów dla różnych platform (np. mobilnych, chatbotów, inteligentnych zegarków) bez konieczności modyfikowania kodu serwera. Możesz także modyfikować stos technologii na kliencie i serwerze niezależnie, o ile umowa API nie zostanie zerwana.

Wady:

  • Cięższe początkowe ładowanie strony ze względu na ładowanie frameworka, kodu aplikacji i zasobów wymaganych na wielu stronach.
  • Na serwerze należy wykonać dodatkowy krok, aby skonfigurować go tak, aby kierował wszystkie żądania do jednego punktu wejścia i pozwalał na przejęcie od niego routingu po stronie klienta.
  • Usługi SPA są zależne od JavaScript do renderowania treści, ale nie wszystkie wyszukiwarki wykonują JavaScript podczas przeszukiwania i mogą widzieć pustą treść na twojej stronie. To mimowolnie szkodzi optymalizacji silnika wyszukiwarki (Search Engine Optimization - SEO) twojej aplikacji. Jednak przez większość czasu, gdy tworzysz aplikacje, SEO nie jest najważniejszym czynnikiem, ponieważ nie wszystkie treści muszą być indeksowane przez wyszukiwarki. Aby temu zaradzić, możesz renderować aplikację po stronie serwera lub korzystać z usług takich jak Prerender aby "wyrenderuj javascript w przeglądarce, zapisz statyczny kod HTML i zwróć go do pliku crawlers".
Bibliografia

Jaki jest zakres twojego doświadczenia z Promises i/lub ich polyfills?

Posiadanie praktycznej wiedzy na ten temat. Obietnica (promise) to obiekt, który może kiedyś wygenerować jedną wartość: wartość rozwiązaną lub przyczynę, dla której nie została ona 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 (fulfilled, rejected, pending). Użytkownicy promise mogą dołączać callbacks, aby obsłużyć spełnioną wartość lub przyczynę odrzucenia.

Niektóre wspólne polyfills są $.deferred, Q oraz Bluebird ale nie wszystkie z nich są zgodne ze specyfikacją. ES2015 obsługuje obietnice od razu po wyjęciu z pudełka, a obecnie polifilly zwykle nie są potrzebne.

Bibliografia

Jakie są zalety i wady korzystania z obietnic zamiast callbacks?

Zalety

  • Unikanie callback hell, które może być nieczytelne.
  • Ułatwia pisanie sekwencyjnego kodu asynchronicznego, który można odczytać za pomocą .then().
  • Ułatwia pisanie równoległego kodu asynchronicznego za pomocą Promise.all().
  • W przypadku obietnic te scenariusze występujące w kodowaniu callbacks-only nie wystąpią:
    • wywołanie callback za wcześnie
    • wywołanie callback za późno (lub nigdy)
    • wywołanie callback za mało lub zbyt wiele razy
    • nieprzekazanie niezbędnego środowiska/parametrów
    • Połknięcie ewentualnych błędów/wyjątków

Wady

  • Nieco bardziej złożony kod (dyskusyjny).
  • W starszych przeglądarkach, w których ES2015 nie jest obsługiwany, w celu korzystania z niego należy załadować polyfill.
Bibliografia

Jakie są zalety/wady pisania kodu JavaScript w języku kompilującym się w JavaScript?

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

Zalety:

  • Naprawia niektóre długotrwałe problemy z JavaScriptem i odradza antywzorce JavaScript.
  • Umożliwia pisanie krótszego kodu, poprzez dodanie cukru syntaktycznego do JavaScript, którego moim zdaniem ES5 brakuje, ale ES2015 jest niesamowity.
  • Typy statyczne są niesamowite (w przypadku TypeScript) dla dużych projektów, które muszą być utrzymywane z czasem.

Wady:

  • Wymaga procesu builda/kompilacji, ponieważ przeglądarki obsługują tylko JavaScript, a twój kod będzie musiał zostać skompilowany w JavaScript przed podaniem do przeglądarki.
  • Debugowanie może być uciążliwe, jeśli mapy źródłowe nie są ładnie odwzorowane na wstępnie skompilowanym źródle.
  • Większość programistów nie zna tych języków i będzie musiała się ich nauczyć. Jeśli używasz go do swoich projektów, wiąże się to z dodatkowymi kosztami.
  • Mniejsza społeczność (zależy od języka), co oznacza, że trudniej będzie znaleźć zasoby, samouczki, biblioteki i narzędzia.
  • Może brakować obsługi IDE / edytora.
  • Te języki zawsze będą w tyle za najnowszym standardem JavaScript.
  • Programiści powinni być świadomi, do czego ich kod jest kompilowany - ponieważ tak właśnie by się działało, i to w końcu ma znaczenie.

W praktyce, ES2015 znacznie poprawił JavaScript i znacznie ułatwił pisanie. Obecnie nie widzę potrzeby używania CoffeeScript.

Bibliografia

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

Bibliografia

Jakich konstrukcji językowych używasz do iteracji właściwości obiektów i elementów tablicy?

Dla obiektów:

  • pętle for-in - for (var property in obj) { console.log(property); }. Spowoduje to jednak również iterację po jej odziedziczonych właściwościach, a przed użyciem dodanie sprawdzenia obj.hasOwnProperty (właściwość).
  • Object.keys() - Object.keys(obj).forEach(function (property) { ... }). Object.keys() jest statyczną metodą, która wyświetli wszystkie wyliczalne właściwości obiektu, który przekazujesz.
  • Object.getOwnPropertyNames() - Object.getOwnPropertyNames(obj).forEach(function (property) { ... }). Object.getOwnPropertyNames() jest statyczną metodą, która wyświetli wszystkie wyliczalne i niepoliczalne właściwości przekazywanego obiektu.

Dla tablic:

  • pętle for - for (var i = 0; i < arr.length; i++). Częstą pułapką jest to, że var jest w zakresie funkcji, a nie w zakresie bloku, i przez większość czasu chciałbyś mieć zmienną iteratora o zasięgu blokowym. ES2015 wprowadza let, który ma zakres blokowy i zaleca się jego użycie zamiast tego. To staje się: for (let i = 0; i < arr.length; i++).
  • forEach - arr.forEach(function (el, index) { ... }). Ta konstrukcja może być czasem wygodniejsza, ponieważ nie musisz używać index, jeśli wszystko, czego potrzebujesz, to elementy tablicy. Istnieją również metody every i some, które pozwalają na wcześniejsze zakończenie iteracji.
  • pętle for-of - for (let elem of arr) { ... }. ES6 wprowadza nową pętlę, pętlę for-of, która pozwala zapętlać obiekty zgodne z iterowalnym protokołem takie jak String, Array, Map, Set, etc. Łączy zalety pętli for i metody forEach(). Zaletą pętli for jest to, że można się od niej oderwać, a zaletą forEach() jest to, że jest ona bardziej zwięzła niż pętla for, ponieważ nie jest potrzebna zmienna licznika. Pętla for-of daje zarówno możliwość zerwania z pętli, jak i bardziej zwięzłą składnię.

Przez większość czasu wolałbym metodę .forEach, ale tak naprawdę zależy to od tego, co próbujesz zrobić. Przed ES6 używaliśmy pętli for, gdy potrzebowaliś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żywałbym pętli for, gdy potrzebuję jeszcze większej elastyczności, takiej jak zwiększanie iteratora więcej niż raz na pętlę.

Ponadto, gdy korzystasz z 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 ESR Array entry() i destrukcji:

const arr = ['a', 'b', 'c'];

for (let [index, elem] of arr.entries()) {
console.log(index, ': ', elem);
}
Bibliografia

Wyjaśnij różnicę między obiektami mutable, a immutable.

Niezmienność jest podstawową zasadą programowania funkcjonalnego i ma wiele do zaoferowania także programom obiektowym. Zmienny obiekt to obiekt, którego stan można zmodyfikować po utworzeniu. Niezmienny obiekt to obiekt, którego stanu nie można zmienić po jego utworzeniu.

Jaki jest przykład niezmiennego obiektu w JavaScript?

W JavaScript niektóre wbudowane typy (liczby, stringi) są niezmienne, ale obiekty niestandardowe są na ogół modyfikowalne.

Niektóre wbudowane niezmienne obiekty JavaScript są Math, Date.

Oto kilka sposobów dodania / symulacji niezmienności prostych obiektów JavaScript.

Object Constant Properties

Łącząc writable: false i configurable: false, możesz zasadniczo stworzyć stałą (nie można jej zmienić, przedefiniować ani usunąć) jako właściwość obiektu, na przykład:

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

Prevent Extensions

Jeśli chcesz uniemożliwić dodawanie do obiektu nowych właściwości, ale w przeciwnym razie pozostaw 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 innym niż strict tworzenie b kończy się bezgłośnie. W trybie strict mode rzuca TypeError.

Seal

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

Dlatego nie tylko nie możesz dodawać żadnych dodatkowych właściwości, ale także nie możesz rekonfigurować ani usuwać żadnych istniejących właściwości (chociaż nadal możesz modyfikować ich wartości).

Freeze

Object.freeze() tworzy zamrożony obiekt, co oznacza, że pobiera istniejący obiekt i zasadniczo wywołuje na nim Object.seal(), ale oznacza również wszystkie właściwości "data accessor" jako writable:false, dzięki czemu ich wartości nie można zmienić.

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

var immutable = Object.freeze({});

Zamrożenie obiektu nie pozwala na dodanie nowych właściwości do obiektu i uniemożliwia usunięcie lub zmianę 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ą zalety i wady immutability?

Zalety

  • Łatwiejsze wykrywanie zmian - Równość obiektów można ustalić w wydajny i łatwy sposób dzięki równości referencyjnej. Jest to przydatne do porównywania różnic obiektów w React i Redux.
  • Programy z niezmiennymi obiektami są mniej skomplikowane do przemyślenia, ponieważ nie musisz martwić się o to, jak obiekt może ewoluować w czasie.
  • Kopie obronne nie są już potrzebne, gdy niezmienne obiekty wracają z funkcji lub są przekazywane do nich, ponieważ nie ma możliwości, aby niezmienny obiekt został przez nią zmodyfikowany.
  • Łatwe udostępnianie za pośrednictwem referencji - Jedna kopia obiektu jest tak samo dobra jak inna, dzięki czemu można buforować obiekty lub wielokrotnie używać tego samego obiektu.
  • Bezpieczne dla wątków - Niezmienne obiekty mogą być bezpiecznie używane między wątkami w środowisku wielowątkowym, ponieważ nie ma ryzyka ich modyfikacji w innych równolegle działających wątkach.
  • Korzystając z bibliotek takich jak ImmmutableJS, obiekty są modyfikowane przy użyciu współdzielenia strukturalnego i potrzeba mniej pamięci do posiadania wielu obiektów o podobnych strukturach.

Wady

  • Naiwne implementacje niezmiennych struktur danych i ich operacje mogą powodować wyjątkowo niską wydajność, ponieważ za każdym razem tworzone są nowe obiekty. Zaleca się stosowanie bibliotek do wydajnych niezmiennych struktur danych i operacji wykorzystujących współużytkowanie strukturalne.
  • Alokacja (i dezalokacja) wielu małych obiektów zamiast modyfikowania istniejących może mieć wpływ na wydajność. Złożoność alokatora lub modułu wyrzucania elementów bezużytecznych zwykle zależy od liczby obiektów na stercie.
  • Trudno jest zbudować cykliczne struktury danych, takie jak wykresy. Jeśli masz dwa obiekty, których nie można zmodyfikować po inicjalizacji, w jaki sposób można je skierować na siebie?
Bibliografia

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

Jednym ze sposobów osiągnięcia niezmienności jest użycie bibliotek takich jak immutable.js, mori lub immer.

Alternatywą jest użycie deklaracji const w połączeniu ze wspomnianymi wyżej technikami tworzenia. Aby "mutować" obiekty, użyj operatora rozkładania, Object.assign, Array.concat() itp., Aby utworzyć nowe obiekty zamiast mutować obiekt oryginalny.

Przykłady:

// Array Example
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]

// Object Example
const human = Object.freeze({ race: 'human' });
const john = { ...human, name: 'John' }; // {race: "human", name: "John"}
const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}
Bibliografia

Wyjaśnij różnicę między funkcjami synchronicznymi i asynchronicznymi.

Funkcje synchroniczne blokują, a funkcje asynchroniczne nie. W funkcjach synchronicznych instrukcje kończą się przed uruchomieniem następnej instrukcji. W takim przypadku program jest oceniany dokładnie w kolejności instrukcji, a wykonywanie programu jest wstrzymywane, jeśli jedna z instrukcji zajmuje bardzo dużo czasu.

Funkcje asynchroniczne zwykle akceptują wywołanie zwrotne (callback) jako parametr i wykonywanie jest kontynuowane w następnym wierszu natychmiast po wywołaniu funkcji asynchronicznej. Wywołanie zwrotne jest wywoływane tylko wtedy, gdy operacja asynchroniczna jest zakończona, a stos wywołań jest pusty. Intensywne operacje, takie jak ładowanie danych z serwera WWW lub wyszukiwanie w bazie danych, powinny być wykonywane asynchronicznie, aby główny wątek mógł kontynuować wykonywanie innych operacji zamiast blokować do czasu zakończenia tej długiej operacji (w przypadku przeglądarek interfejs użytkownika (UI) zawiesza się).

Co to jest pętla zdarzeń? Jaka jest różnica między stosem wywołań (call stack) a kolejką zadań (task queue)?

Pętla zdarzeń jest pętlą jednowątkową, która monitoruje stos wywołań i sprawdza, czy w kolejce zadań jest jakaś praca do wykonania. Jeśli stos wywołań jest pusty, a w kolejce zadań znajdują się funkcje wywołania zwrotnego, funkcja jest usuwana z kolejki i przekazywana na stos wywołań, który ma zostać wykonany.

Jeśli jeszcze nie sprawdziłeś Philipa Roberta i jego talk odnośnie Event Loop, powinieneś. Jest to jeden z najczęściej oglądanych filmów w JavaScript.

Bibliografia

Wyjaśnij różnice w korzystaniu z foo pomiędzy function foo() {} i var foo = function() {}

Pierwsza jest deklaracją funkcji, a druga jest wyrażeniem funkcji. Kluczową różnicą jest to, że deklaracje funkcji mają swoje ciała windowane, ale ciała wyrażeń funkcji nie są (mają takie samo zachowanie podczas windowania jak zmienne). Więcej informacji na temat windowania znajduje się w powyższym pytaniu na temat windowania. 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');
};
Bibliografia

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

Zmienne zadeklarowane przy użyciu słowa kluczowego var mają zasięg do funkcji, w której zostały utworzone, lub jeśli zostały utworzone poza jakąkolwiek funkcją, do obiektu globalnego. Parametry let i constblock scoped, co oznacza, że są dostępne tylko w najbliższym zestawie nawiasów klamrowych (funkcja, blok if-else lub pętla for).

function foo() {
// All variables are accessible within functions.
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';
}

// var declared variables are accessible anywhere in the function scope.
console.log(bar); // bar
// let and const defined variables are not accessible outside of the block they were defined in.
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined

var umożliwia windowanie zmiennych, co oznacza, że można do nich odwoływać się w kodzie przed ich zadeklarowaniem. let oraz const nie pozwolą na to, zamiast tego zgłoszą 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' oraz '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.

// This is fine.
let foo = 'foo';
foo = 'bar';

// This causes an exception.
const baz = 'baz';
baz = 'qux';
Bibliografia

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

Najpierw spójrzmy na przykład każdego:

// ES5 Function Constructor
function Person(name) {
this.name = name;
}

// ES6 Class
class Person {
constructor(name) {
this.name = name;
}
}

W przypadku prostych konstruktorów wyglądają bardzo podobnie.

Główna różnica w konstruktorze występuje podczas korzystania z dziedziczenia. Jeśli chcemy stworzyć klasę Student, która jest podklasą Person i dodać pole studentId, musimy to zrobić dodatkowo do powyższego.

// ES5 Function Constructor
function Student(name, studentId) {
// Call constructor of superclass to initialize superclass-derived members.
Person.call(this, name);

// Initialize subclass's own members.
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;
}
}

Używanie dziedziczenia w ES5 jest znacznie bardziej szczegółowe, a wersja ES6 jest łatwiejsza do zrozumienia i zapamiętania.

Bibliografia

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

Jedną oczywistą zaletą funkcji strzałek jest uproszczenie składni potrzebnej do tworzenia funkcji, bez potrzeby stosowania słowa kluczowego function. Funkcja this w funkcjach strzałek jest również związana z otaczającym zakresem, który jest inny w porównaniu do zwykłych funkcji, w których this jest określane przez obiekt wywołujący go. Leksykonowalne this jest przydatne, gdy wywołuje się callbacki, szczególnie w komponentach React.

Jaka jest zaleta korzystania ze składni arrow syntax 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 utworzenia nowego obiektu, this zawsze będzie odnosić się do tego obiektu. Na przykład, powiedzmy, że mamy konstruktor Person, który bierze imię jako argument i ma dwie metody console.log o tej nazwie, jedną jako funkcję zwykłą, 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

// The regular function can have its 'this' value changed, but the arrow function cannot
john.sayName1.call(dave); // Dave (because "this" is now the dave object)
john.sayName2.call(dave); // John

john.sayName1.apply(dave); // Dave (because 'this' is now the dave object)
john.sayName2.apply(dave); // John

john.sayName1.bind(dave)(); // Dave (because 'this' is now the dave object)
john.sayName2.bind(dave)(); // John

var sayNameFromWindow1 = john.sayName1;
sayNameFromWindow1(); // undefined (because 'this' is now the window object)

var sayNameFromWindow2 = john.sayName2;
sayNameFromWindow2(); // John

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

Może to być szczególnie pomocne w komponentach klasy React. Jeśli zdefiniujesz metodę klasy dla czegoś takiego jak moduł obsługi kliknięć za pomocą normalnej funkcji, a następnie przekażesz ten moduł obsługi kliknięć w dół do komponentu podrzędnego jako rekwizytu, musisz również powiązać this w konstruktorze komponentu nadrzędnego. Jeśli zamiast tego użyjesz funkcji strzałkowej, nie ma również potrzeby wiązania "this", ponieważ metoda automatycznie uzyska wartość "this" z otaczającego kontekstu leksykalnego. (Zobacz ten artykuł dla doskonałej demonstracji i przykładowego kodu: https://medium.com/@machnicki/handle-events-in-react-with-arrow-functions-ede88184bbb)

Bibliografia

Jaka jest definicja funkcji wyższego rzędu?

Funkcja wyższego rzędu to dowolna funkcja, która przyjmuje jedną lub więcej funkcji jako argumenty, których używa do działania na niektórych danych i / lub zwraca w rezultacie funkcję. Funkcje wyższego rzędu mają na celu wyodrębnienie niektórych operacji, które są wykonywane wielokrotnie. Klasycznym przykładem tego jest map, który 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 transformowanymi 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 do zwrócenia funkcji z innej funkcji. Function.prototype.bind jest jednym z takich przykładów w JavaScript.

Map

Powiedzmy, że mamy tablicę nazw, których potrzebujemy do przekształcenia każdego stringa na wielkie litery.

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

Imperatywnym sposobem będzie 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']
Bibliografia

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

Destrukturyzacja to wyrażenie dostępne w ES6, które umożliwia zwięzły i wygodny sposób na wydobycie wartości Obiektów (Objects) lub Tablic (Arrays) i umieszczenie ich w odrębnych zmiennych.

Destrukturyzacja Array

// Variable assignment.
const foo = ['one', 'two', 'three'];

const [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
// Swapping variables
let a = 1;
let b = 3;

[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

Destrukturyzacja Object

// Variable assignment.
const o = { p: 42, q: true };
const { p, q } = o;

console.log(p); // 42
console.log(q); // true
Bibliografia

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

Literały szablonów ułatwiają interpolację stringów lub uwzględnianie zmiennych w stringach. Przed ES2015 było coś takiego:

var person = { name: 'Tyler', age: 28 };
console.log(
'Hi, my name is ' + person.name + ' and I am ' + person.age + ' years old!',
);
// 'Hi, my name is Tyler and I am 28 years old!'

With template literals, you can now create that same output like this instead:

const person = { name: 'Tyler', age: 28 };
console.log(`Hi, my name is ${person.name} and I am ${person.age} years old!`);
// 'Hi, my name is Tyler and I am 28 years old!'

Zauważ, że używasz odwrotnych znaków, a nie cudzysłowów, aby wskazać, że używasz literału szablonu 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 wielu wierszy, taki jak ten:

console.log('This is line one.\nThis is line two.');
// This is line one.
// This is line two.

Lub jeśli chcesz podzielić go na wiele wierszy w kodzie, abyś nie musiał przewijać w edytorze tekstu w prawo, aby odczytać długi ciąg, możesz również napisać w następujący sposób:

console.log('This is line one.\n' + 'This is line two.');
// This is line one.
// This is line two.

Literały szablonów zachowują jednak wszelkie dodane do nich odstępy. Na przykład, aby utworzyć to samo wyjście wieloliniowe, które stworzyliśmy powyżej, możesz po prostu zrobić:

console.log(`This is line one.
This is line two.`);
// This is line one.
// This is line two.

Innym przykładem użycia literałów szablonów byłoby użycie jako zamiennika bibliotek szablonów dla prostych interpolacji zmiennych:

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

Zauważ, że twój kod może być podatny na XSS przy użyciu .innerHTML. Wyczyść swoje dane przed wyświetleniem, jeśli pochodzą one od użytkownika!

Bibliografia

Czy możesz podać przykład curry function i dlaczego ta składnia ma tę zaletę?

Currying to wzorzec, w którym funkcja z więcej niż jednym parametrem jest podzielona na wiele funkcji, które po wywołaniu szeregowym będą gromadzić wszystkie wymagane parametry pojedynczo. Ta technika może być użyteczna do ułatwienia czytania i komponowania kodu napisanego w funkcjonalnym stylu. Ważne jest, aby pamiętać, że aby funkcja była curry, musi zacząć od jednej funkcji, a następnie podzielić ją 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]
Bibliografia

Jakie są zalety korzystania ze składni spread syntax i czym różni się od rest syntax?

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

function putDookieInAnyArray(arr) {
return [...arr, 'dookie'];
}

const result = putDookieInAnyArray(['I', 'really', "don't", 'like']); // ["I", "really", "don't", "like", "dookie"]

const person = {
name: 'Todd',
age: 29,
};

const copyOfTodd = { ...person };

Rest syntax z ES6 oferuje skrót do włączenia dowolnej liczby argumentów, które należy przekazać do funkcji. To jest jak odwrotność spread syntax, biorąc dane i upychając je do tablicy, zamiast rozpakowywać tablicę danych, i działa w argumentach funkcyjnych, a także w przypisaniach destrukturyzacji tablicy 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 }
Bibliografia

Jak współdzielić kod między plikami?

To zależy od środowiska JavaScript.

Na kliencie (środowisko przeglądarki), o ile zmienne / funkcje są zadeklarowane w zasięgu globalnym (window), wszystkie skrypty mogą się do nich odnosić. Ewentualnie zastosuj Asynchronous Module Definition (AMD) za pośrednictwem RequireJS, aby uzyskać bardziej modułowe podejście.

Na serwerze (Node.js) powszechnym sposobem było użycie 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łu, która ma zastąpić zarówno AMD, jak i CommonJS. To będzie ostatecznie obsługiwane zarówno w przeglądarce, jak i w środowisku Node.

Bibliografia

Dlaczego warto tworzyć członków klasy statycznej?

Statyczne elementy klasy (właściwości / metody) nie są powiązane z konkretnym wystąpieniem klasy i mają tę samą wartość niezależnie od tego, do którego wystąpienia się odnosi. Właściwości statyczne są zwykle zmiennymi konfiguracyjnymi, a metody statyczne są zwykle czystymi funkcjami narzędziowymi, które nie zależą od stanu instancji.

Bibliografia

Inne odpowiedzi


Stworzone przez @yangshun polska wersja od @mbiesiad

Table of Contents