Home> headlines> body text

A selection of classic front-end interview questions from major manufacturers (with answers)

Release: 2020-09-01 16:27:16
forward
16993 people have browsed it

A selection of classic front-end interview questions from major manufacturers (with answers)

[Related recommendations:Front-end interview questions(2020)]

1. Why do you write React/Vue projects? What is the function of writing a key in a component?

The function of key is to find the corresponding node faster when the diff algorithm is executed and improve the diff speed.

Vue and react both use the diff algorithm to compare the old and new virtual nodes to update the nodes. In vue's diff function. You can first understand the diff algorithm.

During cross comparison, when there is no result in cross comparison between the new node and the old node, the key in the old node array will be compared according to the key of the new node to find the corresponding old node (here corresponding is a map with key => index). If it is not found, it is considered to be a new node. If there is no key, a traversal search method will be used to find the corresponding old node. One is map mapping, the other is traversal search. in comparison. map mapping is faster.

Part of the source code of vue is as follows:

// vue 项目 src/core/vdom/patch.js -488 行 // oldCh 是一个旧虚拟节点数组, if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
Copy after login

Create map function:

function createKeyToOldIdx (children, beginIdx, endIdx) { let i, key const map = {} for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key if (isDef(key)) map[key] = i } return map }
Copy after login

Traverse and search:

// sameVnode 是对比新旧节点是否相同的函数 function findIdxInOld (node, oldCh, start, end) { for (let i = start; i < end; i++) { const c = oldCh[i] if (isDef(c) && sameVnode(node, c)) return i } }
Copy after login

2. Parse ['1', '2', '3'].map(parseInt)

First look When it comes to this question, the answer that pops into my mind is [1, 2, 3], but the real answer is [1, NaN, NaN].

First let us review the first parameter callback of the map function:

var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
Copy after login

This callback can receive a total of three parameters, the first parameter represents the currently processed element, and The second parameter represents the index of the element.

And parseInt is used to parse a string, making the string an integer with a specified base.

parseInt(string, radix) receives two parameters, the first represents the value to be processed (string), and the second represents the radix during parsing.

After understanding these two functions, we can simulate the operation situation;

parseInt('1', 0) //When radix is 0, and the string parameter does not end with "0x" and When it starts with "0", it is processed based on 10. At this time, 1 is returned;

parseInt('2', 1) // Among the numbers represented by base 1 (base 1), the maximum value is less than 2, so it cannot be parsed and NaN is returned;

parseInt('3', 2) // Among the numbers represented by base 2 (binary), the maximum value is less than 3, so it cannot be parsed and NaN is returned.

The map function returns an array, so the final result is [1, NaN, NaN].

3. What are anti-shake and throttling? What's the difference? How to achieve?

1) Anti-shake

The function will only be executed once within n seconds after a high-frequency event is triggered. If the high-frequency event occurs again within n seconds is triggered, the time will be recalculated;

Idea:

Cancel the previous delay call method every time the event is triggered:

function debounce(fn) { let timeout = null; // 创建一个标记用来存放定时器的返回值 return function () { clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉 timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数 fn.apply(this, arguments); }, 500); }; } function sayHi() { console.log('防抖成功'); } var inp = document.getElementById('inp'); inp.addEventListener('input', debounce(sayHi)); // 防抖
Copy after login

2) Throttling

High-frequency events are triggered, but will only be executed once within n seconds, so throttling will dilute the execution frequency of the function.

Idea:

Every time an event is triggered, it is judged whether there is a delay function waiting to be executed.

function throttle(fn) { let canRun = true; // 通过闭包保存一个标记 return function () { if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return canRun = false; // 立即设置为 false setTimeout(() => { // 将外部传入的函数的执行放在 setTimeout 中 fn.apply(this, arguments); // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉 canRun = true; }, 500); }; } function sayHi(e) { console.log(e.target.innerWidth, e.target.innerHeight); } window.addEventListener('resize', throttle(sayHi));
Copy after login

4. Introduce the differences between Set, Map, WeakSet and WeakMap?

1)Set

members are unique, unordered and non-repeating;

[value, value], key value It is consistent with the key name (or only the key value, no key name);

can be traversed, and the methods are: add, delete, and has.

2)WeakSet

members are all objects;

members are all weak references and can be recycled by the garbage collection mechanism and can be used to save DOM Node, it is not easy to cause memory leaks;

cannot be traversed, and the methods include add, delete, and has.

3)Map

is essentially a collection of key-value pairs, similar to a collection;

can be traversed, has many methods, and can be associated with various data Format conversion.

4)WeakMap

Only accepts objects as key names (except null), and does not accept other types of values as key names;

Key names It is a weak reference. The key value can be arbitrary. The object pointed to by the key name can be garbage collected. At this time, the key name is invalid;

cannot be traversed. The methods include get, set, has, and delete.

5. Introduce depth-first traversal and breadth-first traversal, and how to implement them?

Depth-first traversal (DFS)

Depth-First-Search is a type of search algorithm. Traverse the nodes of the tree according to the depth of the tree, searching the branches of the tree as deep as possible. When all edges of node v have been explored, backtracking is performed to the starting node of the edge where node v was found. This process continues until all other nodes have been explored from the source node. If there are still undiscovered nodes, select one of the undiscovered nodes as the source node and repeat the above operation until all nodes are explored.

简单的说,DFS 就是从图中的一个节点开始追溯,直到最后一个节点,然后回溯,继续追溯下一条路径,直到到达所有的节点,如此往复,直到没有路径为止。

DFS 可以产生相应图的拓扑排序表,利用拓扑排序表可以解决很多问题,例如最大路径问题。一般用堆数据结构来辅助实现 DFS 算法。

注意:深度 DFS 属于盲目搜索,无法保证搜索到的路径为最短路径,也不是在搜索特定的路径,而是通过搜索来查看图中有哪些路径可以选择。

步骤:

访问顶点 v;

依次从 v 的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和 v 有路径相通的顶点都被访问;

若此时途中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止。

实现:

Graph.prototype.dfs = function() { var marked = [] for (var i=0; i
        
Copy after login

测试:

graph.dfs() // 1 // 4 // 3 // 2 // 5
Copy after login

测试成功。

广度优先遍历(BFS)

广度优先遍历(Breadth-First-Search)是从根节点开始,沿着图的宽度遍历节点,如果所有节点均被访问过,则算法终止,BFS 同样属于盲目搜索,一般用队列数据结构来辅助实现 BFS。

BFS 从一个节点开始,尝试访问尽可能靠近它的目标节点。本质上这种遍历在图上是逐层移动的,首先检查最靠近第一个节点的层,再逐渐向下移动到离起始节点最远的层。

步骤:

创建一个队列,并将开始节点放入队列中;

若队列非空,则从队列中取出第一个节点,并检测它是否为目标节点;

若是目标节点,则结束搜寻,并返回结果;

若不是,则将它所有没有被检测过的字节点都加入队列中;

若队列为空,表示图中并没有目标节点,则结束遍历。

实现:

Graph.prototype.bfs = function(v) { var queue = [], marked = [] marked[v] = true queue.push(v) // 添加到队尾 while(queue.length > 0) { var s = queue.shift() // 从队首移除 if (this.edges.has(s)) { console.log('visited vertex: ', s) } let neighbors = this.edges.get(s) for(let i=0;i
        
Copy after login

测试:

graph.bfs(1) // visited vertex: 1 // visited vertex: 4 // visited vertex: 3 // visited vertex: 2 // visited vertex: 5
Copy after login

测试成功。

6. 异步笔试题

请写出下面代码的运行结果:

// 今日头条面试题 async function async1() { console.log('async1 start') await async2() console.log('async1 end') } async function async2() { console.log('async2') } console.log('script start') setTimeout(function () { console.log('settimeout') }) async1() new Promise(function (resolve) { console.log('promise1') resolve() }).then(function () { console.log('promise2') }) console.log('script end')
Copy after login

题目的本质,就是考察setTimeout、promise、async await的实现及执行顺序,以及 JS 的事件循环的相关问题。

答案:

script start async1 start async2 promise1 script end async1 end promise2 settimeout
Copy after login

7. 将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组

Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})
Copy after login

8.JS 异步解决方案的发展历程以及优缺点。

1)回调函数(callback)

setTimeout(() => { // callback 函数体 }, 1000)
Copy after login

缺点:回调地狱,不能用 try catch 捕获错误,不能 return

回调地狱的根本问题在于:

缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符;

嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转);

嵌套函数过多的多话,很难处理错误。

ajax('XXX1', () => { // callback 函数体 ajax('XXX2', () => { // callback 函数体 ajax('XXX3', () => { // callback 函数体 }) }) })
Copy after login

优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行)。

2)Promise

Promise 就是为了解决 callback 的问题而产生的。

Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装。

优点:解决了回调地狱的问题。

ajax('XXX1') .then(res => { // 操作逻辑 return ajax('XXX2') }).then(res => { // 操作逻辑 return ajax('XXX3') }).then(res => { // 操作逻辑 })
Copy after login

缺点:无法取消 Promise ,错误需要通过回调函数来捕获。

3)Generator

特点:可以控制函数的执行,可以配合 co 函数库使用。

function *fetch() { yield ajax('XXX1', () => {}) yield ajax('XXX2', () => {}) yield ajax('XXX3', () => {}) } let it = fetch() let result1 = it.next() let result2 = it.next() let result3 = it.next()
Copy after login

4)Async/await

async、await 是异步的终极解决方案。

优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题;

缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。

async function test() { // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式 // 如果有依赖性的话,其实就是解决回调地狱的例子了 await fetch('XXX1') await fetch('XXX2') await fetch('XXX3') }
Copy after login

下面来看一个使用 await 的例子:

let a = 0 let b = async () => { a = a + await 10 console.log('2', a) // -> '2' 10 } b() a++ console.log('1', a) // -> '1' 1
Copy after login

对于以上代码你可能会有疑惑,让我来解释下原因:

首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来;

因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码;

同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10。

The above explanation mentioned that await implements a generator internally. In fact, await is the syntax sugar of generator plus Promise, and it implements automatic execution of generator internally. If you are familiar with co, you can actually implement such syntactic sugar yourself.

9. Talk about your understanding of TCP three-way handshake and four-way wave

A selection of classic front-end interview questions from major manufacturers (with answers)

Related labels:
source:前端大学
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!