[Verwandte Empfehlungen: Front-End-Interviewfragen(2020)]
1. Warum schreiben Sie React /Vue-Projekte? Welche Funktion hat das Schreiben eines Schlüssels in einer Komponente? Die Funktion der Taste
besteht darin, den entsprechenden Knoten schneller zu finden, wenn der Diff-Algorithmus ausgeführt wird, und die Diff-Geschwindigkeit zu verbessern.
Sowohl Vue als auch React verwenden den Diff-Algorithmus, um neue und alte virtuelle Knoten zu vergleichen und die Knoten zu aktualisieren. In der Diff-Funktion von Vue. Sie können zunächst den Diff-Algorithmus verstehen.
Wenn beim Kreuzvergleich zwischen dem neuen Knoten und dem alten Knoten kein Ergebnis erzielt wird, wird der Schlüssel im alten Knotenarray mit dem Schlüssel des neuen Knotens verglichen, um den entsprechenden alten zu finden Knoten (hier entspricht eine Karte mit Schlüssel => Index). Wenn er nicht gefunden wird, wird er als neuer Knoten betrachtet. Wenn kein Schlüssel vorhanden ist, wird eine Traversal-Suchmethode verwendet, um den entsprechenden alten Knoten zu finden. Das eine ist die Kartenzuordnung, das andere die Traversalsuche. Im Vergleich. Die Kartenzuordnung ist schneller.
Vue-Teil des Quellcodes lautet wie folgt:
// 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)
Kartenfunktion erstellen:
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 }
Traverse und suchen:
// 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 } }
2. Parse ['1', '2', '3'].map(parseInt)
Erster Blick Wenn es um diese Frage geht, fällt mir als Antwort [1, 2, 3] ein, aber die eigentliche Antwort ist [1, NaN, NaN].
Betrachten wir zunächst den ersten Parameter-Callback der Map-Funktion:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
Dieser Callback kann insgesamt drei Parameter empfangen, wobei der erste Parameter das aktuell verarbeitete Element darstellt, und Der zweite Parameter stellt den Index des Elements dar.
Und parseInt wird zum Parsen der Zeichenfolge verwendet, wodurch die Zeichenfolge eine Ganzzahl der angegebenen Basis wird.
parseInt(string, radix) empfängt zwei Parameter. Der erste stellt den zu verarbeitenden Wert (String) und der zweite die Basis während der Analyse dar.
Nachdem wir diese beiden Funktionen verstanden haben, können wir die Operation simulieren;
parseInt('1', 0) //radix ist 0 und der String-Parameter endet nicht mit „0x“. " und wenn es mit „0“ beginnt, wird es basierend auf 10 verarbeitet. Zu diesem Zeitpunkt wird 1 zurückgegeben;
parseInt('2', 1) // Unter den durch Basis 1 (Basis 1) dargestellten Zahlen ist der Maximalwert kleiner als 2 und kann daher nicht analysiert werden NaN wird zurückgegeben;
parseInt('3', 2) // Unter den durch Basis 2 (binär) dargestellten Zahlen ist der Maximalwert kleiner als 3, sodass er nicht analysiert werden kann und NaN zurückgegeben wird.
Die Kartenfunktion gibt ein Array zurück, sodass das Endergebnis [1, NaN, NaN] ist.
3. Was sind Anti-Shake und Throttling? Was ist der Unterschied? Wie erreicht man es?
1) Anti-Shake
Die Funktion wird nur einmal innerhalb von n Sekunden ausgeführt, nachdem ein Hochfrequenzereignis ausgelöst wurde Tritt das Hochfrequenzereignis innerhalb von n Sekunden erneut auf, wird die Zeit neu berechnet.
Idee:
Brechen Sie die vorherige Verzögerungsaufrufmethode jedes Mal ab, wenn das Ereignis ausgelöst wird:
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)); // 防抖
2) Drosselung
Hochfrequente Ereignisse werden ausgelöst, aber nur einmal in n Sekunden ausgeführt, sodass die Drosselung die Ausführungshäufigkeit der Funktion verringert.
Idee:
Jedes Mal, wenn ein Ereignis ausgelöst wird, wird beurteilt, ob eine Verzögerungsfunktion auf die Ausführung wartet.
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));
4. Stellen Sie die Unterschiede zwischen Set, Map, WeakSet und WeakMap vor?
1) Set
Mitglieder sind eindeutig, ungeordnet und sich nicht wiederholend;
[Wert, Wert], Schlüssel value Es stimmt mit dem Schlüsselnamen überein (oder nur mit dem Schlüsselwert, ohne Schlüsselnamen);
kann durchlaufen werden, und die Methoden sind: Hinzufügen, Löschen und Has.
2) WeakSet
Mitglieder sind alle Objekte;
Mitglieder sind alle schwache Referenzen und können vom Garbage Collection-Mechanismus recycelt und verwendet werden Zum Speichern von DOM-Knoten ist es nicht einfach, Speicherverluste zu verursachen.
können nicht durchlaufen werden, und die Methoden umfassen Hinzufügen, Löschen und Has.
3) Karte
ist im Wesentlichen eine Sammlung von Schlüssel-Wert-Paaren, ähnlich einer Sammlung
kann durchlaufen werden und verfügt über viele Methoden. und kann mit verschiedenen Datenformatkonvertierungen verknüpft werden.
4) WeakMap
akzeptiert nur Objekte als Schlüsselnamen (außer Null) und akzeptiert keine anderen Wertetypen als Schlüsselnamen; 🎜> Schlüsselnamen Es handelt sich um eine schwache Referenz, der Schlüsselwert kann beliebig sein und das Objekt, auf das der Schlüsselname zeigt, kann durch Müll gesammelt werden. Zu diesem Zeitpunkt ist der Schlüsselname ungültig , und die Methoden umfassen get, set, has und delete.
5. Führen Sie die Tiefendurchquerung und die Breitendurchquerung ein und wie werden sie implementiert?
Depth-First-Traversal (DFS)
Depth-First-Search ist eine Art Suchalgorithmus, der die Knoten des Baums durchläuft Suchen Sie entsprechend der Tiefe des Baumes die Äste des Baumes so tief wie möglich ab. Wenn alle Kanten des Knotens v erkundet wurden, wird eine Rückverfolgung zum Startknoten der Kante durchgeführt, an der der Knoten v gefunden wurde. Dieser Vorgang wird fortgesetzt, bis alle anderen Knoten vom Quellknoten erkundet wurden. Wenn noch nicht erkannte Knoten vorhanden sind, wählen Sie einen der nicht erkannten Knoten als Quellknoten aus und wiederholen Sie den obigen Vorgang, bis alle Knoten erkundet wurden. 简单的说,DFS 就是从图中的一个节点开始追溯,直到最后一个节点,然后回溯,继续追溯下一条路径,直到到达所有的节点,如此往复,直到没有路径为止。 DFS 可以产生相应图的拓扑排序表,利用拓扑排序表可以解决很多问题,例如最大路径问题。一般用堆数据结构来辅助实现 DFS 算法。 注意:深度 DFS 属于盲目搜索,无法保证搜索到的路径为最短路径,也不是在搜索特定的路径,而是通过搜索来查看图中有哪些路径可以选择。 步骤: 访问顶点 v; 依次从 v 的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和 v 有路径相通的顶点都被访问; 若此时途中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止。 实现: 测试: 测试成功。 广度优先遍历(BFS) 广度优先遍历(Breadth-First-Search)是从根节点开始,沿着图的宽度遍历节点,如果所有节点均被访问过,则算法终止,BFS 同样属于盲目搜索,一般用队列数据结构来辅助实现 BFS。 BFS 从一个节点开始,尝试访问尽可能靠近它的目标节点。本质上这种遍历在图上是逐层移动的,首先检查最靠近第一个节点的层,再逐渐向下移动到离起始节点最远的层。 步骤: 创建一个队列,并将开始节点放入队列中; 若队列非空,则从队列中取出第一个节点,并检测它是否为目标节点; 若是目标节点,则结束搜寻,并返回结果; 若不是,则将它所有没有被检测过的字节点都加入队列中; 若队列为空,表示图中并没有目标节点,则结束遍历。 实现: 测试: 测试成功。 6. 异步笔试题 请写出下面代码的运行结果: 题目的本质,就是考察setTimeout、promise、async await的实现及执行顺序,以及 JS 的事件循环的相关问题。 答案: 7. 将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组 8.JS 异步解决方案的发展历程以及优缺点。 1)回调函数(callback) 缺点:回调地狱,不能用 try catch 捕获错误,不能 return 回调地狱的根本问题在于: 缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符; 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转); 嵌套函数过多的多话,很难处理错误。 优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行)。 2)Promise Promise 就是为了解决 callback 的问题而产生的。 Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装。 优点:解决了回调地狱的问题。 缺点:无法取消 Promise ,错误需要通过回调函数来捕获。 3)Generator 特点:可以控制函数的执行,可以配合 co 函数库使用。 4)Async/await async、await 是异步的终极解决方案。 优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题; 缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。 下面来看一个使用 await 的例子: 对于以上代码你可能会有疑惑,让我来解释下原因: 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来; 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码; 同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10。 Die oben erwähnte Erklärung, dass Wait einen Generator intern implementiert, ist tatsächlich der Syntaxzucker von Generator plus Promise und implementiert die automatische Ausführung des Generators intern. Wenn Sie sich mit Co auskennen, können Sie solchen syntaktischen Zucker tatsächlich selbst implementieren. 9. Sprechen Sie über Ihr Verständnis von TCP-Drei-Wege-Handshake und Vier-Wege-Welle Graph.prototype.dfs = function() {
var marked = []
for (var i=0; i<this.vertices.length; i++) {
if (!marked[this.vertices[i]]) {
dfsVisit(this.vertices[i])
}
}
function dfsVisit(u) {
let edges = this.edges
marked[u] = true
console.log(u)
var neighbors = edges.get(u)
for (var i=0; i<neighbors.length; i++) {
var w = neighbors[i]
if (!marked[w]) {
dfsVisit(w)
}
}
}
}
graph.dfs()
// 1
// 4
// 3
// 2
// 5
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<neighbors.length;i++) {
var w = neighbors[i]
if (!marked[w]) {
marked[w] = true
queue.push(w)
}
}
}
}
graph.bfs(1)
// visited vertex: 1
// visited vertex: 4
// visited vertex: 3
// visited vertex: 2
// visited vertex: 5
// 今日头条面试题
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')
script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout
Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})
setTimeout(() => {
// callback 函数体
}, 1000)
ajax('XXX1', () => {
// callback 函数体
ajax('XXX2', () => {
// callback 函数体
ajax('XXX3', () => {
// callback 函数体
})
})
})
ajax('XXX1')
.then(res => {
// 操作逻辑
return ajax('XXX2')
}).then(res => {
// 操作逻辑
return ajax('XXX3')
}).then(res => {
// 操作逻辑
})
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()
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1