We tried to do something interesting and unusual for you. I really hope that we have succeeded. We did not want to leave you without answers and explanations why this is so. Let's figure it out.
First I want to remind you how the competition took place, there were 4 rounds with 15 questions about JS, 1 non-competitive round with 15 questions about React and a final with 10 questions.
Under the cut - analysis of the tasks of the first 4 rounds.
This is the second part of our analysis.
Analysis of questions about React here
How did we all do it? We decided that we need to generate about 80-90 questions so that there is a stock to choose from. After that, we divided everything into topics:
After that distributed questions on 4 rounds. We tried to make all the tours the same in complexity, for this we did several passes by passing these tests and determining where the questions were simpler, where it was more difficult and replaced the outstanding questions with more appropriate ones. And we did in each round about the same number of questions on a particular topic. As a result, it turned out that in different tours there were similar, but not identical questions.
Because of this, sorting out the tours seems not very convenient because there will be a lot of duplicate explanations, I suggest looking at them by topic. Let's start with the simplest.
console.log(0,1 + 0,2); a) 0.30000000000000004 b) 0.3 c) 2 d) 0 1 2
d) 0 1 2
Here between the numbers ,
, and not .
If you format the question like this:console.log(0, 1 + 0, 2);
everything will become clear
(() => { 'use strict'; a = null + undefined; console.log(a); })(); a) 0 b) NaN c) null d)
d) errora
is created not as a variable (not a Variable Declaration), here an implicit assignment (Assignment Expression) to this.a
which very often may not be what you expect, because the global variable window.a
will be created in strict mode, this is prohibited.
let foo = function bar() { return 123; }; console.log( typeof bar() ); a) 'function' b) 'number' c) 'undefined' d)
d) error
This is a functional expression (expression) - the function name in this case is local to the function. To call a function, you must call foo
, not bar
. If this were a declaration, the answer would be number
.
console.log(0.1 ** 2); a) 0.2 b) 0.01 c) 0.010000000000000002 d) NaN
c) 0.010000000000000002
console.log(0.1 + 0.2); a) 0.30000000000000004 b) 0.3 c) 2 d) NaN
a) 0.30000000000000004**
- this is an analogue of Math.pow. We put 0.1
in the square - it should be 0.01
, but in JS (as in many other languages) there is a known problem with the precision of operations when working with floating point numbers . It will be 0.010000000000000002
This is due to the fact that in the binary system an infinite fraction is obtained. exactly 64 bits are always allocated to a number in JS - all numbers are always double precision floating point. The same will happen when adding.
We turn to the questions a little more difficult.
elem.onclick = function(event) { } a) event.target event.currentTarget b) event.target this c) event.currentTarget this d)
c) event.currentTarget and thisthis
will always point to the element.currentTarget
- the element on which the event hangstarget
- the element on which the event occurred
div.onclick = function() { console.log(1) }; div.onclick = function() { console.log(2) }; div.addEventListener('click', function() { console.log(3) }); a) 1 b) 1 3 c) 2 3 d) 3
c) 2 3
onclick will add the console.log(1)
handler, but in the next line we will rub it with a new function and only console.log(2)
remains. onclick
is a DOM property it is always the same
Events will work in the order in which they are hung, first 2 will be displayed, then 3.
If we did addEventListener
several times, then each of them would work, since Handlers add events to the queue.
Questions section about various APIs
(() => { const obj = { key: 1 }; Object.defineProperty(obj, 'key', { enumerable: false, configurable: false, writable: false, value: 2 }); console.log(obj.key); obj.key = 3; console.log(obj.key); })(); a) 1, 2 b) 2, 2 c) 2, 3 d)
b) 2, 2
(() => { 'use strict'; const obj = { key: 1 }; Object.defineProperty(obj, 'key', { enumerable: false, configurable: false, writable: false, value: 2 }); console.log(obj.key); obj.key = 3; console.log(obj.key); })(); a) 1, 2 b) 2, 2 c) 2, 3 d) 2,
d) 2, error
(() => { const obj = { key: 1 }; Object.defineProperty(obj, 'key', { enumerable: false, configurable: false, writable: true, value: 2 }); console.log(obj.key); obj.key = 3; console.log(obj.key); })(); a) 1, 2 b) 2, 2 c) 2, 3 d)
c) 2, 3
In all the questions above, knowledge of the defineProperty
method is defineProperty
and specifically, the settings of writable
. If it is set to false
then it is forbidden to change values to the key passed in the second parameter in defineProperty
. The only difference is that without strict mode - the use strict
engine will pretend that everything is fine, but the value will not change, and in strict mode there will be an error.
let x = 5; console.log(x++); a) 5 b) 6 c) '5++' d)
a) 5
const a = 5; console.log(a++); a) 5 b) 6 c) '5++' d)
d) error
When using the postfix form of the increment, the value before the increase is returned.
And with prefix after, i.e. console.log(++5)
would print 6
const
can not be overwritten, but because Number is a primitive, then when it is increased, the variable will overwrite the new value and there will be an error.
const a = [...new Set([1, 1, 2, , 3, , 4, 5, 5])]; console.log(a); a) [1, 1, 2, , 3, , 4, 5, 5] b) [1, 2, undefined, 3, 4, 5] c) [1, 1, 2, undefined, 3, undefined, 4, 5, 5] d)
b) [1, 2, undefined, 3, 4, 5]
let set = new Set([10, '10', new Number(10), 1e1, 0xA]); console.log(set.size); a) 5 b) 3 c) 2 d) 1
b) 3
let obj = {}; let set = new Set([obj, obj, {}, {}, {...{}}, {...obj}]); console.log(set.size); a) 6 b) 5 c) 2 d) 1
b) 5Set
is a set; by definition, it cannot contain identical values. The question is how these values are compared. Primitives are compared by value, and objects by reference.
It does not in itself lead to data types and can store values of any type 1e1
and 0xA
- they will be converted into a decimal system and it will turn out 10
.
And new objects are always not equal: console.log({} == {})
returns false
because Objects will be created by new in different places of memory and their links will not be equal.
console.log(Infinity / Infinity); a) NaN b) 1 c) Error d) Infinity
a) NaN
It is impossible to divide infinity into infinity and subtract infinity from infinity because from a mathematical point of view, uncertainty is obtained, the same thing will happen when multiplying Infinity
and 0
errors; mathematical operations do not cause - will be NaN
const a = { ...{ a: 1, b: 2, c: 3 }, ...{ a: 2, c: 4, d: 8 } }; console.log(a); a) { a: 2, b: 2, c: 4, d: 8 } c) { a: 1, b: 2, c: 3, d: 8 } c) { a: 1, b: 2, c: 3, a: 2, c: 4, d: 8 } d)
a) {a: 2, b: 2, c: 4, d: 8}
const a = [...[1, 2], ...[[3, 4]], ...[5, 6]]; console.log(a); a) [1, 2, 3, 4, 5, 6] b) [1, 2, [3, 4], 5, 6] c) [[1, 2], [[3, 4]], 5, 6] e)
b) [1, 2, [3, 4], 5, 6]Spread
operator is used to parse an object or an array into parts. Takes values from the entity after ...
and copies them to the created one. It is worth noting that for an array and an object it expands to 1 level ...[[1]]
returns an array with one element, and not the element itself. In objects, duplicate values cannot be, therefore, the values disclosed after will overwrite those that were disclosed earlier. This can be used to specify default parameters.
const fn = (actualProps) => ({ ...defaultProps, ...actualProps })
All default values will be overridden by passed values, if any.
console.log(parseInt(' -10,3 ')); a) -10,3 b) -10 c) TypeError d) NaN
b) -10
Exhaustive description with MDN :
If the parseInt function encounters a character that is not a number in the specified number system, it skips this and all subsequent characters (even if they are suitable) and returns an integer converted from the part of the string that precedes this character. parseInt cuts the fractional part of the number. Spaces at the beginning and end of the line are allowed.
const t = { a: 6, b: 7 }; const p = new Proxy(t, { get() { return 12; }, }); console.log(pa); pa = 18; console.log(pa); console.log(ta); a) b) 12 18 18 c) 12 18 6 d) 12 12 18 e) 6 18 6
d) 12 12 18Proxy
intercepts all calls to the object. In this case, we only proxify the get
method and always return 12
no matter which field of the object we are accessing. In this case, we do not touch set, and when accessing a proxy, the value in the object will be replaced.
let arr = []; arr[1] = 1; arr[5] = 10; console.log(arr.length); a) 1 b) 5 c) 6 d) 10
c) 6
let arr = new Array(3); console.log(arr[1]); a) undefined b) 1 c) 3 d)
a) undefined
When we create an Array
with one numeric argument, it means the length of the array. This creates an empty array, all values are undefined
. The same thing happens if you create a call to a nonexistent array field. It is worth noting that if you transfer not a number to Array
, an array with this element will be returned. Array('a')
returns ['a']
&&
, ||
, ==
etc .: console.log([] && 'foo' && undefined && true && false); a) [] b) 'foo' c) undefined d) true
c) undefined
console.log(0 || 1 && 2 || 3); a) 0 b) 1 c) 2 d) 3
c) 2
console.log(0 || '' || 2 || undefined || true || false); a) 0 b) false c) 2 d) true
c) 2
console.log(2 && '1' && null && undefined && true && false); a) 2 b) false c) undefined d) null
d) null
console.log([] && {} || null && 100 || ''); a) true b) 100 c) '' d) {}
d) {}
An empty array []
is true
as an empty object {}
.
The empty string ''
, null
and undefined
is false
Logical or ||
- returns the left operand, if it is true, in other cases returns the right operand.
Logical and &&
- returns the left operand if it is false, otherwise it returns the right operand.
This can sometimes be found in the code, before the default parameters appear, it was often written like this - if there are no parameters in the function, we take the default parameters:
function f(userParams) { var params = userParams || defaultParams; }
Now in React, it is often checked that if the condition is true, then we render something:
{ isDivVisible && <div>bla-bla</div> }
const arrayFoo = [1, 2, 3, 4]; const arrayBaz = [1, 2, 3, 4]; console.log(arrayFoo == arrayBaz && arrayFoo == arrayBaz); a) false b) true c) undefined d)
a) false
console.log([null, 0, -0].map(x => 0 <= x)); a) [false, true, false] b) [false, true, true] c) [false, false, false] d) [true, true, true]
d) [true, true]
const arrayFoo = [1, 2, 3, 4]; const arrayBaz = [1, 2, 3, 4]; console.log(arrayFoo >= arrayBaz && arrayFoo <= arrayBaz); a) true b) false c) undefined d)
a) true
const foo = [1, 2, 3, 4]; const baz = '1,2,3,4'; console.log(foo >= baz && foo <= baz); a) false b) true c) undefined d)
b) true
When ==
compare the link.
with the operation >, >=, <, <=
operands are converted to primitives and the arrayFoo
method is called on arrayFoo
, which should return the primitive value of arrayFoo
, but it returns a reference to the same array. Next, the conversion to a primitive value occurs by calling the toString
method, which in turn returns the string representation of the array in the form "1,2,3,4", compares lexicographically two arrays and returns true
console.log(+0 == -0); console.log(+0 === -0); console.log(Object.is(+0, -0)); a) true, false, false b) true, true, false c) false, true, true d) false, false. false
b) true, true, false
Exhaustive explanation with MDN :
The behavior of this method (talking about Object.is
) is not similar to the ===
operator. The ===
operator (as well as the ==
operator) considers the numeric values of -0
and +0
equal, and the value of Number.NaN
not equal to itself.
Questions about hoisting:
console.log(str); const str = 'HeadHunter'; a) 'HeadHunter' b) undefined c)
c) error
var arrayFunction = []; for (let i = 0; i <= 10; i++) { arrayFunction.push(() => i); } console.log(arrayFunction[3]()); a) 4 b) 0 c) 11 d) 3
d) 3
console.log(str); var str = 'HeadHunter'; a) 'HeadHunter' b) undefined c) null c)
b) undefined
console.log(foo); var foo; foo = foo ? 1 : 0; console.log(foo); a) b) undefined 0 c) '' 1 d) 0 0
b) undefined 0
getCompanyName(); function getCompanyName() { return 'HeadHunter'; } a) b) , . c)
a) yes
var arrayFunction = []; for (var i = 0; i <= 10; i++) { arrayFunction.push(() => i); } console.log(arrayFunction[3]()); a) 4 b) 0 c) 11 d) 3
c) 11
The function declarations pop up, but there is no expression.var
pops up, but until initialization is equal to undefined
.let
and const
do not float and have scope in the block i.e. limited to {}
.
In order for the loop to work properly with var
you must use a closure, the value will be stored in it.
(it used to be a classic task for interviews, and now we have let)
var arrayFunction = []; for (var i = 0; i <= 10; i++) { (function(i) { arrayFunction.push(() => i); })(i); } console.log(arrayFunction[3]());
console.log(true + false); a) true b) false c) 1 d) 0
c) 1
None of the operators is a string, +
leads to a number. It turns out 1 + 0
console.log([] - 100 + ![]); a) false b) '-100' c) -100 d) NaN
c) -100
The array is reduced to a string, after that, because of -
we cast to a number, we get -100
, then we cast the array to false
, and this is 0
console.log([[], []] + 1); a) 1 b) '1' c) ',1' d) NaN
c) ', 1'
Call toString
on the object, while toString
will also be called on all elements of the array. [].toString
returns an empty string ''
. It turns out , + 1
- the answer ,1
.
console.log([] + 100 + 5); a) 105 b) '1005' c) 1005 d) NaN
b) '1005'
The array is cast to a string, and then concatenation takes place.
console.log(1 + { a: 3 } + '2'); a) 6 b) '1[object Object]2' c) 3 d) NaN
b) '1 [object Object] 2'
Convert to a string - here is just a concatenation.
console.log(10.toString() + 10 + 0x1); a) '10101' b) 21 c) '10100x1' d)
d) error
For the number point .
means the beginning of the fractional part, we expect a number there - there will be an error.
To make this example work properly, you need to write 10..toString()
console.log(5 + false - null + true); a) '0true' b) NaN c) 6 d)
c) 6
Here everything is led to a number, it turns out 5 + 0 - 0 + 1
console.log(true + NaN + false); a) true b) NaN c) false d) 1
b) NaN
We bring everything to number, when adding numbers with NaN
- we get NaN
console.log('0x1' + '1' - '1e1'); a) 17 b) 7 c) '0x111e1' d) NaN
b) 7
Here the lines after the first concatenation are: '0x11' - '1e1'
. Because of the sign -
we bring everything to a number.0x11
- hexadecimal notation in decimal is 17
.1e1
- the exponential form is the same as 1 * 10 ** 1
- i.e. just 10
.
let foo = () => { return null; }; console.log( typeof typeof foo ); a) 'function' b) 'string' c) 'null' d)
b) 'string'
typeof function() {}.prototype; a) 'function' b) 'object' c) 'undefined' d)
b) 'object'typeof
always returns a string that has lower priority than a function call, so the function is executed first, and typeof
is applied to the result returned by it. Function objects are inherited from Function.prototype. SPECA explicitly determines that this is an object.
Promise.reject() .then(() => console.log(1), () => console.log(2)) .then(() => console.log(3), () => console.log(4)); a) 1 4 b) 1 3 c) 2 3 d) 2 4
c) 2 3
Promise.reject('foo') .then(() => Promise.resolve('bar'), () => {}) .then((a) => {console.log(a)}) a) foo b) bar c) undefined d)
c) undefinedPromise.reject
- returns promise in rejected state.
It is necessary to remember that then
takes 2 parameters, onFulfill
and onReject
Kolbeck. If an error occurs before this, then we get into the onReject
callback. If there is no error in it, then we fall into the onFulfill
next. And do not forget that () => {}
returns not an empty object, but undefined
, to return an empty object, you should write like this: () => ({})
async function get1() { return 1; } function get2() { return 2; } (async () => { console.log(await get1()); })(); console.log(get2()); a) 1,2 b) 2,1 c) 1 d) 2
b) 2.1
setTimeout(() => {console.log('in timeout')}); Promise.resolve() .then(() => {console.log('in promise')}); console.log('after'); a) in timeout, in promise, after b) after, in promise, in timeout c) after, in timeout, in promise d) in timeout, after, in promise
b) after, in promise, in timeout
let __promise = new Promise((res, rej) => { setTimeout(res, 1000); }); async function test(i) { await __promise; console.log(i); } test(1); test(2); a) 1, 2 b) 2, 1 c) 1 d) 2
a) 1, 2
console.log('FUS'); setTimeout(() => {console.log('RO')}) Promise.resolve('DAH!').then(x => console.log(x)); a FUS RO DAH! b) FUS DAH! RO c) RO FUS DAH! d) DAH! RO FUS
b) FUS DAH! RO
console.log(1); setTimeout(() => console.log('setTimeout'), 0); console.log(2); Promise.resolve().then(() => console.log('promise1 resolved')); console.log(3); a) 1, 2, 3, 'setTimeout', 'promise1 resolved' b) 1, 'setTimeout', 2, 'promise1 resolved', 3 c) 1, 2, 3, 'promise1 resolved', 'setTimeout' d) 1, 2, 'promise1 resolved', 3, 'setTimeout'
c) 1, 2, 3, 'promise1 resolved', 'setTimeout'
At first, all synchronous calls are triggered, after that, when the call stack is empty, what is in the queue is called (asynchronous tasks). Microtasks are performed first - promises and mutation observer
. At the end of the current task, all microtasks are executed, in connection with this microtasks, you can block the event loop, after the task is completed in the browser, rendering takes place. After that, the macro taskouts are executed.
This is a very simplified example, in more detail I would advise you to watch the speech of Mikhail Bashurov
const p = Promise.resolve(); (async () => { await p; console.log('1'); })(); p.then(() => console.log('2')) .then(() => console.log('3'));
a) 1 2 3
b) 2 1 3
c) 2 3 1
d) 3 2 1
c) 2 3 1
According to the spec, first the promises added through then
should be executed and only after that should they continue
asynchronous function execution. Specs . For a more detailed understanding of why this is so, I advise you to read an excellent article on v8.dev
Source: https://habr.com/ru/post/431698/
All Articles