[Recommandations associées : Questions d'entretien préliminaires(2020)]
Pourquoi écrivez-vous React Projets /Vue ? Quelle est la fonction d'écrire une clé dans un composant ? La fonction de la
clé est de trouver le nœud correspondant plus rapidement lorsque l'algorithme de comparaison est exécuté et d'améliorer la vitesse de comparaison.
Vue et React utilisent l'algorithme diff pour comparer les nouveaux et les anciens nœuds virtuels afin de mettre à jour les nœuds. Dans la fonction diff de vue. Vous pouvez d'abord comprendre l'algorithme de comparaison.
Lors d'une comparaison croisée, lorsqu'il n'y a aucun résultat de comparaison croisée entre le nouveau nœud et l'ancien nœud, la clé de l'ancien tableau de nœuds sera comparée en fonction de la clé du nouveau nœud pour trouver l'ancien nœud correspondant. nœud (ici correspondant est une carte avec clé => index). S'il n'est pas trouvé, il est considéré comme un nouveau nœud. S'il n'y a pas de clé, une méthode de recherche traversante sera utilisée pour trouver l'ancien nœud correspondant. L’un est le mappage cartographique, l’autre est la recherche traversante. En comparaison. la cartographie est plus rapide.
Une partie Vue du code source est la suivante :
// 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)
Fonction de création de carte :
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 }
Traverser et trouver :
// 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)
Quand j'ai vu cette question pour la première fois, la réponse qui m'est venue à l'esprit était [1, 2, 3], mais la vraie réponse est [1, NaN, NaN].
Tout d'abord, passons en revue le premier paramètre de rappel de la fonction map :
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
Ce rappel peut recevoir un total de trois paramètres, dont le premier paramètre représente l'élément en cours de traitement , et le deuxième paramètre représente l'index de l'élément.
Et parseInt est utilisé pour analyser la chaîne, faisant de la chaîne un entier de la base spécifiée.
parseInt(string, radix) reçoit deux paramètres Le premier représente la valeur à traiter (string), et le second représente la base lors de l'analyse.
Après avoir compris ces deux fonctions, nous pouvons simuler l'opération
parseInt('1', 0) //radix vaut 0, et le paramètre de chaîne ne se termine pas par "0x " et lorsqu'il commence par "0", il est traité sur la base de 10. A ce moment, 1 est renvoyé ;
parseInt('2', 1) // Parmi les nombres représentés par la base 1 (base 1), la valeur maximale est inférieure à 2, elle ne peut donc pas être analysée et NaN est renvoyé ;
parseInt('3', 2) // Parmi les nombres représentés en base 2 (binaire), la valeur maximale est inférieure à 3, elle ne peut donc pas être analysée et NaN est renvoyé.
La fonction map renvoie un tableau, donc le résultat final est [1, NaN, NaN].
3. Que sont l'anti-tremblement et l'étranglement ? Quelle est la différence ? Comment y parvenir ?
1) Anti-tremblement
La fonction ne sera exécutée qu'une seule fois dans les n secondes après le déclenchement d'un événement haute fréquence si le. l'événement à haute fréquence se reproduit dans les n secondes est déclenché, le temps sera recalculé
Idée :
Annulez la méthode d'appel différé précédente à chaque fois que l'événement est déclenché :
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) Limitation
Des événements à haute fréquence sont déclenchés, mais ils ne seront exécutés qu'une fois toutes les n secondes, donc la limitation diluera la fréquence d'exécution de la fonction.
Idée :
Chaque fois qu'un événement est déclenché, il est jugé s'il y a une fonction de retard en attente d'exécution.
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. Présenter les différences entre Set, Map, WeakSet et WeakMap ?
1) Les membres de l'ensemble
sont uniques, non ordonnés et non répétitifs
[valeur, valeur], clé ; value Il est cohérent avec le nom de la clé (ou seulement la valeur de la clé, aucun nom de clé
ne peut être parcouru, et les méthodes sont : add, delete et has).
2) Les membres WeakSet
sont tous des objets
les membres sont tous des références faibles et peuvent être recyclés par le mécanisme de collecte des ordures et peuvent être utilisés ; pour enregistrer les nœuds DOM, il n'est pas facile de provoquer des fuites de mémoire
ne peut pas être parcouru, et les méthodes incluent l'ajout, la suppression et le has.
3) Map
est essentiellement une collection de paires clé-valeur, similaire à une collection
peut être parcourue, a de nombreuses méthodes, et peut être associé à diverses conversions de formats de données.
4) WeakMap
n'accepte que les objets comme noms de clé (sauf null) et n'accepte pas d'autres types de valeurs comme noms de clé
< ; 🎜> noms de clé Il s'agit d'une référence faible, la valeur de la clé peut être arbitraire et l'objet pointé par le nom de la clé peut être récupéré. À ce stade, le nom de la clé n'est pas valide ; , et les méthodes incluent get, set, has et delete.5. Introduire le parcours en profondeur d'abord et le parcours en largeur d'abord, et comment les mettre en œuvre ?
Depth-first Traversal (DFS)
Depth-First-Search est un type d'algorithme de recherche Parcourez les nœuds de l'arborescence en fonction. jusqu'à la profondeur de l'arbre, en fouillant les branches de l'arbre aussi profondément que possible. Lorsque tous les bords du nœud v ont été explorés, un retour en arrière est effectué jusqu'au nœud de départ du bord où le nœud v a été trouvé. Ce processus se poursuit jusqu'à ce que le nœud source ait été exploré sur tous les autres nœuds. S'il reste des nœuds non découverts, sélectionnez l'un des nœuds non découverts comme nœud source et répétez l'opération ci-dessus jusqu'à ce que tous les nœuds aient été explorés.简单的说,DFS 就是从图中的一个节点开始追溯,直到最后一个节点,然后回溯,继续追溯下一条路径,直到到达所有的节点,如此往复,直到没有路径为止。
DFS 可以产生相应图的拓扑排序表,利用拓扑排序表可以解决很多问题,例如最大路径问题。一般用堆数据结构来辅助实现 DFS 算法。
注意:深度 DFS 属于盲目搜索,无法保证搜索到的路径为最短路径,也不是在搜索特定的路径,而是通过搜索来查看图中有哪些路径可以选择。
步骤:
访问顶点 v;
依次从 v 的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和 v 有路径相通的顶点都被访问;
若此时途中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止。
实现:
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
测试成功。
广度优先遍历(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<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
测试成功。
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')
题目的本质,就是考察setTimeout、promise、async await的实现及执行顺序,以及 JS 的事件循环的相关问题。
答案:
script start async1 start async2 promise1 script end async1 end promise2 settimeout
7. 将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组
Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})
8.JS 异步解决方案的发展历程以及优缺点。
1)回调函数(callback)
setTimeout(() => { // callback 函数体 }, 1000)
缺点:回调地狱,不能用 try catch 捕获错误,不能 return
回调地狱的根本问题在于:
缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符;
嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转);
嵌套函数过多的多话,很难处理错误。
ajax('XXX1', () => { // callback 函数体 ajax('XXX2', () => { // callback 函数体 ajax('XXX3', () => { // callback 函数体 }) }) })
优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行)。
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 => { // 操作逻辑 })
缺点:无法取消 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()
4)Async/await
async、await 是异步的终极解决方案。
优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题;
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
async function test() { // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式 // 如果有依赖性的话,其实就是解决回调地狱的例子了 await fetch('XXX1') await fetch('XXX2') await fetch('XXX3') }
下面来看一个使用 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
对于以上代码你可能会有疑惑,让我来解释下原因:
首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来;
因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码;
同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10。
L'explication ci-dessus mentionne que wait implémente un générateur en interne. En fait, wait est le sucre syntaxique du générateur plus Promise, et il implémente l'exécution automatique du générateur en interne. Si vous êtes familier avec co, vous pouvez réellement implémenter vous-même un tel sucre syntaxique.
9. Parlez de votre compréhension de la poignée de main à trois voies TCP et de la vague à quatre voies