A year ago, you might have been recognized for figuring out the closure, this, the prototype chain. But now, apparently not. In this article, we’ve sorted out some of the most difficult and frequent native JS questions that you may have never looked at before, or seen before, but haven’t studied carefully, but they are very important.
This article will be presented in the form of real interview questions. As you read, you are advised not to read my answers first, but to think about them for yourself. Although, all the answers in this article are given by me after browsing through various materials, thinking and verifying (by no means copy and paste). However, due to the limited level, my answer may not be the best, if you have a better answer, welcome to leave a message in the issue.
This article is long, but full of dry stuff! There are also cute memes waiting for you to finish reading.
Write a super sincere little sister wish everyone can find their favorite job.
More articles can be read: github.com/YvetteLau/B…
If you haven’t read the previous article, you can read it after you’ve read this article.
Little sister spent nearly 100 hours to complete this article, the length is long, I hope you read more patience, and strive to really master the relevant knowledge points.
1. Talk about the history of JS asynchrony
The earliest solutions to asynchrony are callback functions, such as callbacks to events, and callbacks in setInterval/setTimeout. But a very common problem with callback functions is the problem of callback hell (illustrated later);
To solve the problem of callback hell, the community came up with the Promise solution, which ES6 wrote into the language standard. Promises solve the problem of callback hell, but they also have some problems. For example, errors cannot be tried and caught. Moreover, using the chain invocation of Promise does not solve the problem of callback hell fundamentally, but just changes the way it is written.
The Generator function is introduced in ES6, which is an asynchronous programming solution. The Generator function is the implementation of coroutines in ES6. The biggest feature of the Generator function is that it can hand over the execution right of functions. Use the yield statement. However, Generator is more complex to use.
ES7 also proposed a new asynchronous solution :async/await, async is the syntactic sugar of Generator functions, async/await makes asynchronous code look like synchronous code, the goal of asynchronous programming development is to make asynchronous logic code look like synchronous code.
1. Callback function: callback
//node reads the file
fs.readFile(xxx, 'utf-8'.function(err, data) {
//code
});
Copy the code
Scenarios for using callback functions (including but not limited to):
- Event callback
- Node API
- A callback function in setTimeout/setInterval
Asynchronous callback nesting makes code difficult to maintain, and makes it difficult to uniformly handle errors, try catch and callback hell (such as reading A text, then reading B from A text, then reading C from B…). .
fs.readFile(A, 'utf-8'.function(err, data) {
fs.readFile(B, 'utf-8'.function(err, data) {
fs.readFile(C, 'utf-8'.function(err, data) {
fs.readFile(D, 'utf-8'.function(err, data) {
//....
});
});
});
});
Copy the code
2.Promise
Promise mainly solves the problem of callback hell. Promise was first proposed and implemented by the community. ES6 has written it into the language standard, unified the usage, and provided the Promise object natively.
So let’s take a look at how Promise solves the callback hell problem, again using readFile as an example.
function read(url) {
return new Promise((resolve, reject) = > {
fs.readFile(url, 'utf8', (err, data) => {
if(err) reject(err);
resolve(data);
});
});
}
read(A).then(data= > {
return read(B);
}).then(data= > {
return read(C);
}).then(data= > {
return read(D);
}).catch(reason= > {
console.log(reason);
});
Copy the code
To run the Code to see the effect, please stamp (小 sister is using VS Code Runner to execute the Code): github.com/YvetteLau/B…
Consider how you dealt with asynchronous concurrency prior to Promise, assuming you had a requirement to read three files, all successfully, and output the final result. So what happens when you have Promise? The code can be: github.com/YvetteLau/B…
Note: Bluebird can be used to promise the interface;
Extended: What are the advantages and problems of Promise?
3.Generator
The Generator function is an asynchronous programming solution provided by ES6. The Generator function is a encapsulated asynchronous task, or a container for asynchronous tasks. Where asynchronous operations need to be paused, use the yield statement.
Generator functions are usually used in conjunction with yield or Promise. Generator functions return iterators. For those unfamiliar with generators and iterators, please take a refresher on the basics. Let’s look at a simple use of Generator:
function* gen() {
let a = yield 111;
console.log(a);
let b = yield 222;
console.log(b);
let c = yield 333;
console.log(c);
let d = yield 444;
console.log(d);
}
let t = gen();
// The next method can take an argument that is treated as the return value of the previous yield expression
t.next(1); // The first time the next function was called, the argument passed was invalid
t.next(2); / / a output 2;
t.next(3); / / b output 3;
t.next(4); / / c output 4;
t.next(5); / / d output 5;
Copy the code
To give you a better idea of how this code works, I’ve drawn a diagram for each next method call:
Using the readFile example above, use the Generator + CO library to implement:
const fs = require('fs');
const co = require('co');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);
function* read() {
yield readFile(A, 'utf-8');
yield readFile(B, 'utf-8');
yield readFile(C, 'utf-8');
//....
}
co(read()).then(data= > {
//code
}).catch(err= > {
//code
});
Copy the code
How to achieve this without using the CO library? Can you write a simple my_co by yourself? Please stamp: github.com/YvetteLau/B…
PS: If you are not familiar with Generator/ Yield, it is recommended to read the ES6 documentation.
4.async/await
The concept of async/await was introduced in ES7. Async is a syntactic sugar that is implemented by wrapping Generator functions and auto-actuators (CO) in a single function.
Async /await has the advantage of clean code and handling callback hell without writing as many then chains as Promise. Errors can be try caught.
const fs = require('fs');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);
async function read() {
await readFile(A, 'utf-8');
await readFile(B, 'utf-8');
await readFile(C, 'utf-8');
//code
}
read().then((data) = > {
//code
}).catch(err= > {
//code
});
Copy the code
Executable code, please: github.com/YvetteLau/B…
How does async/await handle asynchronous concurrency? Github.com/YvetteLau/B…
If you have a better answer or idea, feel free to leave a comment below github: Tell us about the history of JS async development
2. Talk about the understanding of async/await. What is the realization principle of async/await?
Async /await is the syntactic sugar of the Generator, making asynchronous operations more convenient. Here’s a picture for comparison:
The async function replaces the asterisk (*) of a Generator function with async and yields with await.
We say async is a syntactic sugar for Generator, so what is the sweetness of this sugar?
1) Async function built-in executor. After function invocation, it will automatically execute and output the final result. The Generator needs to call next or work with the CO module.
2) Better semantics, async and await, better semantics than asterisks and yield. Async means that there is an asynchronous operation in a function, and await means that the following expression needs to wait for the result.
3) Wider applicability. The CO module convention is that yield can only be followed by Thunk or Promise, while async can be followed by await and Promise and primitive type values.
4) The return value is a Promise. Async functions return a Promise object and Generator returns an Iterator. Promise objects are more convenient to use.
Async functions are implemented by wrapping Generator functions and automatic actuators in one function.
Check out the code below (a little different from the spawn implementation, I think this is easier to understand). If you want to know how to write my_co step by step, you can check out github.com/YvetteLau/B…
function my_co(it) {
return new Promise((resolve, reject) = > {
function next(data) {
try {
var { value, done } = it.next(data);
}catch(e){
return reject(e);
}
if(! done) {// Done is true, indicating that the iteration is complete
// Value may not be a Promise, but a normal value. Use promise.resolve for wrapping.
Promise.resolve(value).then(val= > {
next(val);
}, reject);
} else {
resolve(value);
}
}
next(); // Execute next
});
}
function* test() {
yield new Promise((resolve, reject) = > {
setTimeout(resolve, 100);
});
yield new Promise((resolve, reject) = > {
// throw Error(1);
resolve(10)});yield 10;
return 1000;
}
my_co(test()).then(data= > {
console.log(data); / / output 1000
}).catch((err) = > {
console.log('err: ', err);
});
Copy the code
If you have better answers or ideas, please leave a comment on github corresponding to this topic: Talk about understanding async/await. What is the implementation principle of async/await?
3. What should I pay attention to when using async/await?
- The result of the await command may be rejected, which is equivalent to the async Promise returned by the async function. Hence the need for error handling. You can add catch methods to each await Promise; You can also put the await code in
try... catch
In the. - Asynchronous operations following multiple await commands, if there is no secondary relationship, it is best to let them fire simultaneously.
// Both of the following can be triggered simultaneously
/ / method
async function f1() {
await Promise.all([
new Promise((resolve) = > {
setTimeout(resolve, 600);
}),
new Promise((resolve) = > {
setTimeout(resolve, 600); }})])2 / / method
async function f2() {
let fn1 = new Promise((resolve) = > {
setTimeout(resolve, 800);
});
let fn2 = new Promise((resolve) = > {
setTimeout(resolve, 800);
})
await fn1;
await fn2;
}
Copy the code
- Await the await command can only be used in async functions and an error will be reported if it is used in normal functions.
- Async functions can preserve the run stack.
/** * function A runs an asynchronous task b() inside. When b() runs, function A () does not interrupt, but continues execution. * By the time b() is finished, it is possible that A () will have finished long ago, and the context of b() will have disappeared. * If b() or c() reports an error, the error stack will not include a(). * /
function b() {
return new Promise((resolve, reject) = > {
setTimeout(resolve, 200)}); }function c() {
throw Error(10);
}
const a = (a)= > {
b().then((a)= > c());
};
a();
/** * async */
const m = async() = > {await b();
c();
};
m();
Copy the code
The async function can retain the run stack.
If you have a better answer or idea, feel free to leave a comment below github for this topic: What to watch out for using async/await?
4. How to implement promise.race?
Before implementing the code, we need to understand the features of promise.race:
-
Promise.race still returns a Promise. Its state is the same as that of the first completed Promise. Depending on which state the first Promise is in, it may be either complete or rejects.
-
If the argument passed is non-iterable, an error will be thrown.
-
If the passed array of parameters is empty, the returned promise will wait forever.
-
If the iteration contains one or more non-promise values and/or resolved/rejected promises, promise.race resolves to the first value found in the iteration.
Promise.race = function (promises) {
// Promises must be an iterable data structure or make a mistake
return new Promise((resolve, reject) = > {
if (typeof promises[Symbol.iterator] ! = ='function') {
// True is not this error
Promise.reject('args is not iteratable! ');
}
if (promises.length === 0) {
return;
} else {
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then((data) = > {
resolve(data);
return;
}, (err) => {
reject(err);
return; }); }}}); }Copy the code
Test code:
// It is in a waiting state
Promise.race([]).then((data) = > {
console.log('success ', data);
}, (err) => {
console.log('err ', err);
});
/ / wrong
Promise.race().then((data) = > {
console.log('success ', data);
}, (err) => {
console.log('err ', err);
});
Promise.race([
new Promise((resolve, reject) = > { setTimeout((a)= > { resolve(100)},1000)}),new Promise((resolve, reject) = > { setTimeout((a)= > { resolve(200)},200)}),new Promise((resolve, reject) = > { setTimeout((a)= > { reject(100)},100) })
]).then((data) = > {
console.log(data);
}, (err) => {
console.log(err);
});
Copy the code
B: Promise. All/Promise. Reject/Promise. Resolve/Promise. The prototype. Finally/Promise. Prototype. Catch the principle, if you are not too will, stamp: Promise the source code to achieve
If you have a better answer or idea, feel free to leave a comment below on Github: How to implement promise.race?
5. What are the characteristics of traversable data structures?
An object that can be for… Iterator interface that the of loop calls must deploy the Iterator generation method on its symbol. Iterator property (or the object in the prototype chain has that method).
PS: Iterator objects are essentially characterized by next methods. Each time the next method is called, an information object representing the current member is returned, with both value and done attributes.
// Add an Iterator interface to an object;
let obj = {
name: "Yvette".age: 18.job: 'engineer'[Symbol.iterator]() {
const self = this;
const keys = Object.keys(self);
let index = 0;
return {
next() {
if (index < keys.length) {
return {
value: self[keys[index++]],
done: false
};
} else {
return { value: undefined.done: true}; }}}; }};for(let item of obj) {
console.log(item); //Yvette 18 engineer
}
Copy the code
Using the Generator function to short the symbol. iterator method, you can abbreviate it as follows:
let obj = {
name: "Yvette".age: 18.job: 'engineer',
* [Symbol.iterator] () {
const self = this;
const keys = Object.keys(self);
for (let index = 0; index < keys.length; index++) {yield self[keys[index]];Yield expressions can only be used in Generator functions}}};Copy the code
The data structures with the native Iterator interface are as follows.
- Array
- Map
- Set
- String
- TypedArray
- The arguments object for the function
- The NodeList object
- ES6 arrays, sets, and maps all deploy three methods: Entries (), keys(), and values(), which return a traverser object.
If you have a better answer or idea, feel free to leave a comment below on Github: What are the features of traversable data structures?
What is the difference between requestAnimationFrame and setTimeout/setInterval? What are the benefits of using requestAnimationFrame?
Before requestAnimationFrame, we mainly used setTimeout/setInterval to write JS animations.
The key to writing animation is the setting of the loop interval. On the one hand, the loop interval is short enough to make the animation look smooth and smooth. On the other hand, the loop interval needs to be long enough to ensure that the browser is able to render the resulting changes.
Most computer monitors refresh at 60 Hz, or 60 redraws per second. Most browsers limit redrawing to the rate at which the display can be redrawn, because beyond that the user experience will not improve. Therefore, the optimal loop interval for the smoothest animation is 1000ms / 60, which is about 16.7ms.
SetTimeout /setInterval has a significant drawback that the time is not precise. SetTimeout /setInterval can only ensure that the delay or interval is no less than the set time. Because they actually just add tasks to the task queue, they must wait if the previous task has not been executed.
RequestAnimationFrame only has the system time interval to maintain the best drawing efficiency, not because the interval is too short, resulting in excessive drawing and increased overhead; Also not because the interval is too long, the use of animation is not smooth, so that all kinds of web animation effects can have a unified refresh mechanism, so as to save system resources, improve system performance, improve visual effects.
In summary, requestAnimationFrame has the following advantages over setTimeout/setInterval when writing animations:
1. RequestAnimationFrame does not need to be set to a time. It uses the system time interval to achieve the best animation effect.
RequestAnimationFrame consolidates all DOM operations in each frame in a single redraw or reflow.
3. RequestAnimationFrame () is paused while running in a background TAB or hidden
Use requestAnimationFrame (try writing A moving ball from A to B using requestAnimationFrame):
function step(timestamp) {
//code...
window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);
Copy the code
If you have a better answer or idea, feel free to leave a comment below on Github: requestAnimationFrame and setTimeout/setInterval? What are the benefits of using requestAnimationFrame?
7. What are the rules for converting JS types?
The rules for casting are so confusing that I want to cry
JS type conversion is divided into cast and implicit type conversion.
-
Cast through Number(), parseInt(), parseFloat(), toString(), String(), Boolean().
-
Logical operators, &&, | |,!) , operators (+, -, *, /), relational operators (>, <, <=, >=), equality operators (==), or conditions on if/while may be implicitly cast.
Cast casting
1.Number() converts an argument of any type to a numeric type
Here are the rules:
- If Boolean, true and false are converted to 1 and 0, respectively
- If it is a number, return itself
- If null, 0 is returned
- If undefined, return
NAN
- If it is a string, follow these rules:
- If the string contains only numbers (or
0X
/0x
A leading hexadecimal numeric string, plus or minus signs allowed), is converted to decimal - If the string contains a valid floating-point format, convert it to a floating-point value
- If it is an empty string, it is converted to 0
- Returns any string not in the preceding format
NaN
- If the string contains only numbers (or
- If it is Symbol, an error is thrown
- If it is an object, the object’s
valueOf()
Method, and then convert the returned value according to the previous rules. If the result of the transformation isNaN
, the object’stoString()
Method to convert the returned string value again according to the previous rules.
Some of the built-in objects call the default valueOf behavior:
object | The return value |
---|---|
Array | The array itself (object type) |
Boolean | Boolean value (primitive type) |
Date | The number of milliseconds elapsed from midnight UTC, January 1, 1970, to the encapsulated date |
Function | Function itself (object type) |
Number | Numeric value (primitive type) |
Object | The object itself (object type) |
String | String value (primitive type) |
Number('0111'); / / 111
Number('0X11') / / 17
Number(null); / / 0
Number(' '); / / 0
Number('1a'); //NaN
Number(-0X11);/ / - 17
Copy the code
2.parseInt(param, radix)
If the first argument is passed as a string:
- Ignore the space before the string until the first non-empty character is found, or return NaN if it is an empty string
- If the first character is not a number or a plus or minus sign, NaN is returned
- If the first character is a number/plus or minus sign, parsing continues until the string is parsed or a non-number symbol is encountered
If the first argument is passed with type Number:
- A number that starts with 0 is parsed as an octal number (if it is an octal number); If it starts with 0x, it is parsed as a hexadecimal
If the first argument is null or undefined, or an object type:
- Returns NaN
If the first argument is an array: 1. Go to the first element of the array and parse according to the rules above
If the first argument is of type Symbol: 1. Throws an error
If the radix parameter is specified, the radix is analyzed
parseInt('0111'); / / 111
parseInt(0111); // Octal number 73
parseInt(' ');//NaN
parseInt('0X11'); / / 17
parseInt('1a') / / 1
parseInt('a1'); //NaN
parseInt(['10aa'.'aaa']);/ / 10
parseInt([]);//NaN; parseInt(undefined);
Copy the code
parseFloat
The same rule as parseInt takes a Number or string. If it is a string, only the first decimal point is valid.
toString()
Here are the rules:
- If the type is Number, the output is a numeric string
- If null or undefined, throw error
- If it is an array, then the array is expanded and output. An empty array, returns
' '
- If it is an object, return
[object Object]
- If it is Date, returns a literal representation of the Date
- If it is a function, print the corresponding string (demo below)
- If it is Symbol, print the Symbol string
let arry = [];
let obj = {a:1};
let sym = Symbol(100);
let date = new Date(a);let fn = function() {console.log('Hold on, we can win! ')}
let str = 'hello world';
console.log([].toString()); / /"
console.log([1.2.3.undefined.5.6].toString());/ / 1, 2, 3, 5, 6
console.log(arry.toString()); / / 1, 2, 3
console.log(obj.toString()); // [object Object]
console.log(date.toString()); // Sun Apr 21 2019 16:11:39 GMT+0800 (CST)
console.log(fn.toString());// function () {console.log(' Hold on, we can win! ')}
console.log(str.toString());// 'hello world'
console.log(sym.toString());// Symbol(100)
console.log(undefined.toString());/ / wrong
console.log(null.toString());/ / wrong
Copy the code
String()
ToString () converts toString() to null and undefined. The main difference is that null and undefined correspond to the strings ‘null’ and ‘undefined’.
Boolean
All are true except for undefined, null, false, ”, 0(including +0 and -0), NaN, and all are false.
Implicit type conversion
, &&, | |,! , if/while
The data needs to be converted to Boolean type. The conversion rules are the same as the Boolean cast
Operator: + – * /
The + operator can be used not only for adding numbers, but also for string concatenation.
Only if we have numbers on both sides of the plus sign, we’re adding. If both sides are strings, concatenation is done without implicit type conversion.
In addition to the above cases, if the operand is an object, value, or Boolean, the toString() method is called to get the string value (toString conversion rule). For undefined and NULL, call String() to explicitly convert to a String, and then concatenate.
console.log({}+10); //[object Object]10
console.log([1.2.3.undefined.5.6] + 10);/ / 1, 2, 3, 5610
Copy the code
The -, *, / operators are for operations, and if one of the values is not a value, the Number() function is implicitly called for conversion. If one of the conversions is NaN, the result is NaN.
Relational operators: ==, >, <, <=, >=
>, <, <=, >=
- If both operation values are numeric, a numeric comparison is performed
- If both operation values are strings, the character encoding values corresponding to the strings are compared
- If either party is of type Symbol, an error is thrown
- In all other cases, Number() is cast and then compared.
Note: NaN is a very special value that is not equal to any type of value, including itself, and returns false when it is larger than any type of value.
console.log(10 > {});/ / returns false.
/ * * * {...}). The valueOf - > {} {} *. The toString () - > '[object object]' - > NaN NaN and any type than size, return false * /
Copy the code
Equality operator: ==
- If the types are the same, no type conversion is required.
- If either operator is null or undefined, the other operator must be null or undefined to return true, otherwise false.
- If one of them is of type Symbol, return false.
- If the two operations are string and number, the string is converted to number
- If an operation value is Boolean, convert to number
- If one operation is object and the other is string, number, or symbol, then the object is converted to its original type and evaluated. (Call valueOf/toString on object to convert)
How is an object converted to a primitive data type
If the [symbol.toprimitive] interface is deployed, call this interface and throw an error if it returns something other than the underlying data type.
If the [symbol.toprimitive] interface is not deployed, the valueOf valueOf() is returned, and toString() is returned if it is not of the underlying type, and an exception is thrown.
// Call valueOf first, then toString
let obj = {
[Symbol.toPrimitive]() {
return 200;
},
valueOf() {
return 300;
},
toString() {
return 'Hello'; }}// If valueOf returns a non-basic data type, toString is called,
// If toString returns something other than a basic data type, an error is thrown
console.log(obj + 200); / / 400
Copy the code
If you have a better answer or idea, feel free to leave a comment on github for this topic: What are the rules for converting JS types?
8. Brief understanding of webWorker?
HTML5 puts forward the Web Worker standard, indicating that JS allows multi-threading, but sub-threads are completely controlled by the main thread and cannot operate DOM. Only the main thread can operate DOM, so JS is still a single-threaded language in essence.
Web worker is to start a child thread on the basis of js single thread execution to process the program without affecting the execution of the main thread. When the child thread finishes execution, it returns to the main thread without affecting the execution of the main thread in this process. PostMessage and onMessage interfaces are provided between the child thread and the main thread to send and receive data.
var worker = new Worker('./worker.js'); // Create a child thread
worker.postMessage('Hello');
worker.onmessage = function (e) {
console.log(e.data); //Hi
worker.terminate(); // Terminates the thread
};
Copy the code
//worker.js
onmessage = function (e) {
console.log(e.data); //Hello
postMessage("Hi"); // Send a message to the main process
};
Copy the code
This is the simplest example of code, and projects typically run code that takes a long time in child threads.
If you have a better answer or idea, feel free to leave a comment on github: Brief understanding of webWorker
9. What are the differences between ES6 modules and CommonJS modules?
-
At compile time, the ES6 module determines the module’s dependencies, as well as the input and output variables.
CommonJS module, loaded at runtime.
-
The ES6 module automatically adopts strict mode, regardless of whether “Use strict” is written in the module header.
-
Require can be dynamically loaded. Import statements cannot. Import statements must be in the top-level scope.
-
In ES6 modules, the top-level this refers to undefined, and in CommonJS modules, the top-level this refers to the current module.
-
The CommonJS module prints a copy of the value, the ES6 module prints a reference to the value.
The CommonJS module outputs a copy of the value, meaning that once a value is output, changes within the module do not affect that value. Such as:
//name.js
var name = 'William';
setTimeout((a)= > name = 'Yvette'.200);
module.exports = {
name
};
//index.js
const name = require('./name');
console.log(name); //William
setTimeout((a)= > console.log(name), 300); //William
Copy the code
Compare the ES6 modules:
ES6 modules operate differently from CommonJS. When the JS engine statically analyzes a script, it generates a read-only reference to the module load command import. When the script is actually executed, it will be evaluated in the loaded module based on the read-only reference.
//name.js
var name = 'William';
setTimeout((a)= > name = 'Yvette'.200);
export { name };
//index.js
import { name } from './name';
console.log(name); //William
setTimeout((a)= > console.log(name), 300); //Yvette
Copy the code
If you have a better answer or idea, feel free to leave a comment on github below: ES6 module vs. CommonJS module?
10. What is the mechanism of browser event proxy?
Before we talk about how the browser event broker mechanism works, let’s look at the concept of event streaming. In the early days, IE used event bubbling, while Netscape used event capturing. Dom2-level events divide the event flow into three phases: capture phase, target phase, and bubble phase. Modern browsers also follow this specification.
So what is an event broker?
Event broker, also known as event delegate, binds an event to the ancestor LEVEL DOM element. When triggering the event of the descendant level DOM element, the event bubble principle is used to trigger the event bound to the ancestor level DOM. Because events bubble layer by layer from the target element to the Document object.
Why event broker?
-
The number of events added to a page affects the performance of the page. If too many events are added, the performance of the page deteriorates. Using event proxy can greatly reduce the number of registered events.
-
In event brokering, a descendant element is added dynamically and does not need to be event bound again.
-
Instead of worrying that a DOM element that registered an event might not be able to reclaim its event handler after it is removed, we can avoid this problem by simply delegating the event handler to a higher-level element.
If you delegate all click events in a page to document:
AddEventListener takes three parameters: the name of the event to process, the function of the handler, and a Boolean value. The default Boolean value is false. Indicates that the event handler is invoked during the bubble phase or, if true, during the capture phase.
document.addEventListener('click'.function (e) {
console.log(e.target);
/** * The capture phase invokes the event handler, eventPhase is 1; * In target, eventPhase is 2 * Bubbling phase calls event handler, eventPhase is 1; * /
console.log(e.eventPhase);
});
Copy the code
If you have a better answer or idea, feel free to leave a comment below on Github: How does the browser event broker mechanism work?
11. How does js customize events?
Custom DOM events (not considering pre-IE9 versions)
There are three ways to customize an Event: use new Event(), createEvent(‘CustomEvent’), and new CustomEvent ()
- use
new Event()
Could not get event.detail
let btn = document.querySelector('#btn');
let ev = new Event('alert', {
bubbles: true.// Whether the event bubbles; The default value is false
cancelable: true.// Can the event be cancelled; The default value is false
composed: false
});
btn.addEventListener('alert'.function (event) {
console.log(event.bubbles); //true
console.log(event.cancelable); //true
console.log(event.detail); //undefined
}, false);
btn.dispatchEvent(ev);
Copy the code
- use
createEvent('CustomEvent')
(DOM3)
To create a CustomEvent, call createEvent(‘CustomEvent’), which returns an initCustomEvent method that takes the following four parameters:
- Type: string indicating the type of event triggered, ‘alert’ in this case
- Bubbles: Boolean value: indicates whether an event bubbles
- Cancelable: Boolean value indicating whether an event can be cancelled
- Detail: Any value stored in the detail property of the event object
let btn = document.querySelector('#btn');
let ev = btn.createEvent('CustomEvent');
ev.initCustomEvent('alert'.true.true.'button');
btn.addEventListener('alert'.function (event) {
console.log(event.bubbles); //true
console.log(event.cancelable);//true
console.log(event.detail); //button
}, false);
btn.dispatchEvent(ev);
Copy the code
- use
new customEvent()
(DOM4)
More convenient to use than createEvent(‘CustomEvent’)
var btn = document.querySelector('#btn');
/* * The first argument is the event type * the second argument is an object */
var ev = new CustomEvent('alert', {
bubbles: 'true'.cancelable: 'true'.detail: 'button'
});
btn.addEventListener('alert'.function (event) {
console.log(event.bubbles); //true
console.log(event.cancelable);//true
console.log(event.detail); //button
}, false);
btn.dispatchEvent(ev);
Copy the code
Custom non-DOM events (Observer mode)
The EventTarget type has a separate attribute, Handlers, for storing event handlers (observers).
AddHandler () registers an event handler for a given type of event;
Fire () is used to fire an event;
RemoveHandler () is used to unregister an event handler for an event type.
function EventTarget(){
this.handlers = {};
}
EventTarget.prototype = {
constructor:EventTarget,
addHandler:function(type,handler){
if(typeof this.handlers[type] === "undefined") {this.handlers[type] = [];
}
this.handlers[type].push(handler);
},
fire:function(event){
if(! event.target){ event.target =this;
}
if(this.handlers[event.type] instanceof Array) {const handlers = this.handlers[event.type];
handlers.forEach((handler) = >{ handler(event); }); }},removeHandler:function(type,handler){
if(this.handlers[type] instanceof Array) {const handlers = this.handlers[type];
for(var i = 0,len = handlers.length; i < len; i++){
if(handlers[i] === handler){
break;
}
}
handlers.splice(i,1); }}}/ / use
function handleMessage(event){
console.log(event.message);
}
// Create a new object
var target = new EventTarget();
// Add an event handler
target.addHandler("message", handleMessage);
// Trigger the event
target.fire({type:"message".message:"Hi"}); //Hi
// Delete the event handler
target.removeHandler("message",handleMessage);
// Trigger the event again, no event handler
target.fire({type:"message".message: "Hi"});
Copy the code
If you have a better answer or idea, feel free to leave a comment below github: how can JS customize events?
12. What are the cross-domain approaches? How does it work?
The browser has the same origin policy. Only when the protocol, domain name, and port number are the same, can it be called the same origin. There is one difference, that is, cross-domain.
So what does the same origin policy do? The same origin policy restricts how documents or scripts loaded from the same source can interact with resources from another source. This is an important security mechanism for isolating potentially malicious files.
So why do we need to cross domains? One is that the front-end and server are deployed separately, and the interface request needs to cross domains. The other is that we may load pages of other websites as iframe embedded.
What are the cross-domain approaches?
Common cross-domain methods
- jsonp
Although browsers have the same origin policy, the SRC attribute of the
Implementation principle:
Step1: create the callback method
Step2: Insert the script label
Step3: The background receives the request, parses the callback method passed by the front end, returns the call of the method, and passes the data as parameter to the method
Step4: the front end performs the method call returned by the server
The following code only illustrates the jSONP principle, please use mature libraries in the project. Take a look at the simple implementations of the front-end and server side respectively:
// Front-end code
function jsonp({url, params, cb}) {
return new Promise((resolve, reject) = > {
// Create the script tag
let script = document.createElement('script');
// Hang the callback function on the window
window[cb] = function(data) {
resolve(data);
// Delete the inserted script tag after executing the code
document.body.removeChild(script);
}
// The callback function is added to the request addressparams = {... params, cb}//wb=b&cb=show
let arrs = [];
for(let key in params) {
arrs.push(`${key}=${params[key]}`);
}
script.src = `${url}?${arrs.join('&')}`;
document.body.appendChild(script);
});
}
/ / use
function sayHi(data) {
console.log(data);
}
jsonp({
url: 'http://localhost:3000/say'.params: {
//code
},
cb: 'sayHi'
}).then(data= > {
console.log(data);
});
Copy the code
// Express starts a background service
let express = require('express');
let app = express();
app.get('/say', (req, res) => {
let {cb} = req.query; // Get the name of the callback function passed, with cb as key
res.send(`${cb}('Hello! ') `);
});
app.listen(3000);
Copy the code
From today, the principle of JSONP will be clear
- cors
Jsonp can only support GET requests, and CORS can support multiple requests. Cors doesn’t require the front end to do any work.
Simple cross-domain request:
Browsers Allow cross-domains as long as the access-Control-Allow-Origin Header set by the server matches the source of the request
- Request methods are GET, head or POST.
- Content-type is a value in Application/X-www-form-urlencoded, multipart/form-data, or Text /plain, optionally not set. The general default is Application/X-www-form-urlencoded.
- There is no custom HTTP header, such as X-Token, in the request. (Accept, accept-language, content-language, last-event-id, content-Type)
// Simple cross-domain request
app.use((req, res, next) = > {
res.setHeader('Access-Control-Allow-Origin'.'XXXX');
});
Copy the code
Are but a Preflighted cross – domain request
Dissatisfied with simple cross-domain requests, that is, cross-domain requests with precheck. The server needs to set access-Control-allow-origin, access-Control-allow-methods, and access-Control-allow-headers (Allowed headers)
app.use((req, res, next) = > {
res.setHeader('Access-Control-Allow-Origin'.'XXX');
res.setHeader('Access-Control-Allow-Headers'.'XXX'); // Allow return headers
res.setHeader('Access-Control-Allow-Methods'.'XXX');// Allow the put method to request an interface
res.setHeader('Access-Control-Max-Age'.6); // Precheck the survival time
if(req.method === "OPTIONS") {
res.end(); // If method is OPTIONS, no processing is done}});Copy the code
Learn more about CORS at HTTP Access Control (CORS).
- Nginx reverse proxy
Using nginx reverse proxy to achieve cross-domain, only need to modify the configuration of nginx to solve the cross-domain problem.
When web site A requests an interface from Web site B, it sends A request to Web site B. Nginx receives the request based on the configuration file and requests it instead of Web site A to Web site B. Nginx takes the resource and returns it to site A to solve the cross-domain problem.
For example, if the nginx port number is 8090, the requested server port number is 3000. Localhost :8090 request localhost:3000/say
Nginx configuration is as follows:
server {
listen 8090;
server_name localhost;
location / {
root /Users/liuyan35/Test/Study/CORS/1-jsonp;
index index.html index.htm;
}
location /say {
rewrite ^/say/(.*)$ /The $1 break;
proxy_pass http://localhost:3000;
add_header 'Access-Control-Allow-Origin' The '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
}
# others
}
Copy the code
- websocket
Websocket is a persistent protocol of HTML5, which realizes the full duplex communication between browser and server, and is also a cross-domain solution.
Websocket is not affected by the same origin policy and supports cross-domains without any configuration as long as it is supported on the server.
Front-end page on port 8080.
let socket = new WebSocket('ws://localhost:3000'); // The protocol is ws
socket.onopen = function() {
socket.send('Hi, how are you');
}
socket.onmessage = function(e) {
console.log(e.data)
}
Copy the code
Port 3000 on the server. You can see that websocket does not need to do cross-domain configuration.
let WebSocket = require('ws');
let wss = new WebSocket.Server({port: 3000});
wss.on('connection'.function(ws) {
ws.on('message'.function(data) {
console.log(data); // Receive a message from the page 'Hi, hello '
ws.send('Hi'); // Send a message to the page
});
});
Copy the code
- postMessage
PostMessage is used as a cross-domain before the front-end page, such as the cross-domain between the parent page and the iframe page. Window. postMessage method that allows communication across Windows, whether or not the two Windows are homologous.
In other words, there are not many cases where two pages need to communicate before. I have only used it twice in my work. One time, I sent postMessage in H5 page and received the message in ReactNative WebView and processed it accordingly. The other is a castable page. A castable page uses an IFrame page. In order to solve the sliding event conflict, the IFrame page listens for gestures and sends a message to tell the parent page whether it is left or right.
Child pages send messages to parent pages
The parent page
window.addEventListener('message', (e) => {
this.props.movePage(e.data);
}, false);
Copy the code
Child pages (iframe) :
if(/ * l * /) {
window.parent && window.parent.postMessage(- 1.The '*')}else if(/ * right * /) {window.parent && window.parent.postMessage(1.The '*')}Copy the code
The parent page sends messages to the child pages
The parent page:
let iframe = document.querySelector('#iframe');
iframe.onload = function() {
iframe.contentWindow.postMessage('hello'.'http://localhost:3002');
}
Copy the code
Child pages:
window.addEventListener('message'.function(e) {
console.log(e.data);
e.source.postMessage('Hi', e.origin); / / back to the message
});
Copy the code
- The node middleware
Node middleware cross-domain principle and nginx agent cross-domain, the same origin policy is restricted by the browser, the server does not have the same origin policy.
The cross-domain principle of Node middleware is as follows:
1. Accept client requests
2. Forward the request to the server.
3. Obtain the server response data.
4. Forward the response to the client.
Cross-domain methods are not commonly used
The following three cross-domain methods are rarely used. If you are interested, you may refer to the relevant information by yourself.
-
window.name + iframe
-
location.hash + iframe
-
Document.domain (primary domain must be the same)
If you have a better answer or idea, feel free to leave a comment under the corresponding Github: What are the cross-domain approaches? How does it work?
13. What are the asynchronous loading methods of JS?
-
The
The
The difference between defer and Async is that defer does not execute until the entire page has been rendered properly in memory;
Once async is finished downloading, the rendering engine interrupts the rendering, executes the script, and continues rendering. Defer is “render before execution” and Async is “download after execution”.
If there are multiple defer scripts, they are loaded in the order they appear on the page.
Multiple Async scripts do not guarantee loading order.
- Dynamically insert script scripts
function downloadJS() {
varelement = document.createElement("script");
element.src = "XXX.js";
document.body.appendChild(element);
}
// When to call the above method
Copy the code
- Conditional dynamic creation of scripts
So after page onload,
If you have a better answer or idea, feel free to leave a comment under github: What are the asynchronous loading methods of JS?
14. In what case does the following code A print 1?
/ /?
if(a == 1 && a == 2 && a == 3) {
console.log(1);
}
Copy the code
1. In type conversion, we know how to cast an object to its original data type. If [symbol.toprimitive] is deployed, the return value of symbol.toprimitive is returned. Of course, we could deploy this function on the valueOf or toString interface with the same effect.
// Use closures to extend the scope
let a = {
[Symbol.toPrimitive]: (function() {
let i = 1;
return function() {
returni++; }}}) ()Copy the code
(1). Compare a == 1, will call [Symbol. ToPrimitive], then I is 1, equal. (2). Continue to compare a == 2, call [Symbol. ToPrimitive], then I is 2, equal. (3). Continue to compare a == 3, call [Symbol. ToPrimitive], then I is 3, equal.
2. Define a property on window/global with Object.defineProperty.
let val = 1;
Object.defineProperty(window.'a', {
get: function() {
returnval++; }});Copy the code
3. Take advantage of arrays.
var a = [1.2.3];
a.join = a.shift;
Copy the code
The toString method of an array returns a string consisting of the return values of toString() for each element in the array joined () by commas.
Therefore, we can rejoin the method. Returns the first element and deletes it.
If you have a better answer or idea, feel free to leave a comment below on Github: when does the following code A print 1?
15. What is the output of the following code?
function Foo() {
getName = function() {console.log(1)};
return this;
}
Foo.getName = function() {console.log(2)};
Foo.prototype.getName = function() {console.log(3)};
var getName = function() {console.log(4)};
function getName() {console.log(5)};
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
Copy the code
** a classic interview question, only to help you review the knowledge point, deepen understanding, real work, is not possible to write code like this, otherwise, will certainly be killed.
1. In the precompilation phase, variable and function declarations are promoted to the top of their respective scopes.
So the above code compiles as follows (function declarations take precedence over variable declarations):
function Foo() {
getName = function() {console.log(1)};
return this;
}
function getName() {console.log(5)}; // function preference (function is promoted first)
var getName;// Duplicate declaration, ignored
Foo.getName = function() {console.log(2)};
Foo.prototype.getName = function() {console.log(3)};
getName = function() {console.log(4)};
Copy the code
2.Foo.getName(); Call getName directly on Foo, printing 2
3.getName(); Output 4, getName is reassigned
4.Foo().getName(); Foo() is executed, the window getName is reassigned, and this is returned; In the browser environment, in non-strict mode, this points to window, this.getName(); The output of 1.
In strict mode, this points to undefined, where an error is thrown.
If this refers to global in node, node’s global variable is not attached to global because global.getName corresponds to undefined, not function, and raises an error.
5.getName(); Has thrown the wrong nature can not move this step; Continue browser non-strict mode; Window.getname is reassigned and called again, and the output is 1
6.new Foo.getName(); New has no argument list, and the corresponding priority is 18; The member access operator., which corresponds to priority 19. So it’s equivalent to new (foo.getName)(); The new operator executes the method in the constructor, so the output is 2.
7. New Foo (). The getName (); New takes the argument list, priority 19, and the member access operator. The priorities are the same. Sibling operators, evaluated from left to right. New Foo() initializes Foo’s instantiation object. There is no getName method on the instance, so we need to prototype it
8.new new Foo().getName(); New takes a list of arguments and has priority 19, so it is equivalent to new (new Foo()).getName(); Initialize Foo’s instantiation object, and then new again the getName function on its prototype as a constructor, printing 3
So the end result is as follows:
Foo.getName(); / / 2
getName();/ / 4
Foo().getName();/ / 1
getName();/ / 1
new Foo.getName();/ / 2
new Foo().getName();/ / 3
new new Foo().getName();/ / 3
Copy the code
If you have a better answer or idea, feel free to leave a comment below on Github: What is the output of the following code?
16. How does implementing a two-way binding Proxy compare to Object.defineProperty?
-
Object.definedproperty is used to hijack the properties of an Object, the getter and setter methods of the property, to perform specific operations when the properties of the Object change. Proxy hijacks the entire object.
-
The Proxy returns a Proxy Object and we only need to manipulate the new Object, whereas Object.defineProperty can only be modified by iterating through Object attributes.
-
Object.definedproperty does not support arrays, or more specifically, the various apis for arrays, because it is possible to hijack only arry[I] = value, but this hijacking is not meaningful. Proxies can support various apis for arrays.
-
Although object.defineProperty has many drawbacks, it is more compatible than Proxy.
PS: vue2. x uses Object.defineProperty for two-way data binding, while V3.0 uses Proxy.
/ / the interceptor
let obj = {};
let temp = 'Yvette';
Object.defineProperty(obj, 'name', {
get() {
console.log("Read success");
return temp
},
set(value) {
console.log("Setup successful"); temp = value; }}); obj.name ='Chris';
console.log(obj.name);
Copy the code
PS: object.defineProperty Specifies a property that cannot be enumerated, modified, or configured.
We can see that the Proxy hijacks the whole object, reads properties in the object or modifies property values, so it gets hijacked. It is important to note, however, that complex data types monitor the reference address, not the value. If the reference address does not change, then the set will not fire.
let obj = {name: 'Yvette'.hobbits: ['travel'.'reading'].info: {
age: 20.job: 'engineer'
}};
let p = new Proxy(obj, {
get(target, key) { // The third parameter is proxy, which is generally not used
console.log('Read success');
return Reflect.get(target, key);
},
set(target, key, value) {
if(key === 'length') return true; // If the array length is changed, return.
console.log('Setup successful');
return Reflect.set([target, key, value]); }}); p.name =20; // The setting succeeded
p.age = 20; // Set successfully; You do not need to define this property beforehand
p.hobbits.push('photography'); // Read successfully; Caution Setting success is not triggered
p.info.age = 18; // Read successfully; Setting success will not trigger
Copy the code
Finally, let’s look at the differences between Object.definedProperty and Proxy for array hijacking
Object. DefinedProperty can hijack an index of an array as an attribute, but only supports direct operations on arry[I].
let arry = []
Object.defineProperty(arry, '0', {
get() {
console.log("Read success");
return temp
},
set(value) {
console.log("Setup successful"); temp = value; }}); arry[0] = 10; // Trigger setting succeeded
arry.push(10); // Cannot be hijacked
Copy the code
Proxies can listen for array changes and support various apis. Note that changes to the array may trigger get and set more than once, depending on the key if necessary.
let hobbits = ['travel'.'reading'];
let p = new Proxy(hobbits, {
get(target, key) {
// if(key === 'length') return true; // If the array length is changed, return.
console.log('Read success');
return Reflect.get(target, key);
},
set(target, key, value) {
// if(key === 'length') return true; // If the array length is changed, return.
console.log('Setup successful');
return Reflect.set([target, key, value]); }}); p.splice(0.1) // Trigger get and set, which can be hijacked
p.push('photography');// Trigger get and set
p.slice(1); // Trigger get; Slice does not modify the array
Copy the code
If you have a better answer or idea, feel free to comment on github: How does implementing a two-way binding Proxy compare to Object.defineProperty?
17.Object.is()
And the comparison operator= = =
,= =
What’s the difference?
Object. Is is considered equal in the following cases
Both values are undefined and both values are null and both values are undefinedtrueOr arefalseTwo values are strings of the same number of characters in the same order two values refer to the same object both of them are numbers and both of them are positive zero plus zero both of them are negative zero minus zero are NaN both of them are the same number other than zero and NaNCopy the code
Object.is() is similar to ===, but with some subtle differences, as follows:
- NaN is equal to NaN
- Minus 0 is not the same as plus 0
console.log(Object.is(NaN.NaN));//true
console.log(NaN= = =NaN);//false
console.log(Object.is(0, +0)); //false
console.log(0= = = +0); //true
Copy the code
Object. Is is not the same as ==. == requires a type conversion when the type is different, as explained in the previous section.
If you have a better answer or idea, feel free to leave a comment on github: Object.is() is different from the comparison operators ===, ==?
18. What is an event loop? What is the difference between a Node event loop and a JS event loop?
The last question is left for you to answer. It is too long to write further.
In view of this problem, will write a special article ~
Leave your answer: What is an event loop? What is the difference between a Node event loop and a JS event loop?
For information on event-loops in browsers, see my previous article: Understanding EventLoop in Browsers
Reference article:
- www.imooc.com/article/386…
- es6.ruanyifeng.com/
- www.imooc.com/article/725…
- www.cnblogs.com/LuckyWinty/…
- www.jianshu.com/p/a76dc7e0c…
- www.v2ex.com/t/351261
Follow-up writing plan (writing order variable)
1. Native JS: What You Need to Know
2. CSS you Need to Know
3. The End of the Road
4. What you Need to Know about Browsers this Job Search Season
5. What You Need to Know
6. Webpack Principles you Need to Know
React Stack:
1. The React Series
2. The ReactNative Series: What You Need to Know During The Winter Job Search Season
It took a lot of time to write this article, and I have learned a lot in the process. Thank you for your precious time to read this article. If this article gives you some help or inspiration, please don’t be too generous with your praise and Star, because your praise is definitely the biggest motivation for me to move forward. Github.com/YvetteLau/B…