总结
为了帮助大家更好温习重点知识、更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。
内容包括html,css,JavaScript,ES6,计算机网络,浏览器,工程化,模块化,Node.js,框架,数据结构,性能优化,项目等等。
包含了腾讯、字节跳动、小米、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目,涵盖了初中级前端技术点。
前端面试题汇总
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
JavaScript
性能
linux
https://github.com/snabbdom/snabbdom
可以说理解了snabbdom 中的具体方法即理解了vue中虚拟dom的核心思想:
当状态更新时,将新的JavaScript对象和旧的JavaScript对象进行比较(即diff算法),运算出两者的差异,将差异应用到真正的DOM,以此减少了DOM的操作。
diff 策略
- 只比较同一级,不跨级比较
- 标签名不同,直接删除,不继续深度比较
- 标签名相同,key相同就认为是相同节点,不继续比较
patch
patch方法是虚拟dom核心中的核心。在VNode(虚拟节点)改变后和初始化前都会调用。
主要逻辑如下:
- 调用 module 中的 pre hook(生命周期相关)
- 如果传入的是 Element 转成空的 vnode(这里在首次创建时,会传入一个dom元素)
- 新旧节点为 sameVnode 的话,则调用 patchVnode 更新 vnode , 否则创建新节点
- 创建新节点 ,插入新节点 ,移除旧节点
- 调用元素上的 insert hook(生命周期相关)
- 调用元素上的 post hook(生命周期相关)
源码解析:
function patch(oldVnode: VNode | Element, vnode: VNode): VNode {
let i: number, elm: Node, parent: Node;
const insertedVnodeQueue: VNodeQueue = [];
// 调用 module 中的 pre hook
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
// 如果传入的是 Element 转成空的 vnode
if (!isVnode(oldVnode)) {
//创建一个空的vnode 并关联DOM元素 emptyNodeAt
oldVnode = emptyNodeAt(oldVnode);
}
// sameVnode 时 (sel 和 key相同) 调用 patchVnode
// 判断两个参数是否是相同的vnode
// 比较标签名 key
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue);
} else {
elm = oldVnode.elm as Node;
parent = api.parentNode(elm);
// 创建新的 dom 节点 vnode.elm
createElm(vnode, insertedVnodeQueue);
if (parent !== null) {
// 插入 dom
api.insertBefore(parent, vnode.elm as Node, api.nextSibling(elm));
// 移除旧 dom
removeVnodes(parent, [oldVnode], 0, 0);
}
}
// 调用元素上的 insert hook,注意 insert hook 在 module 上不支持
for (i = 0; i < insertedVnodeQueue.length; ++i) {
(((insertedVnodeQueue[i].data as VNodeData).hook as Hooks).insert as any)(insertedVnodeQueue[i]);
}
// 调用 module post hook
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
return vnode;
}
function emptyNodeAt(elm: Element) {
const id = elm.id ? '#' + elm.id : '';
const c = elm.className ? '.' + elm.className.split(' ').join('.') : '';
return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);
}
// key 和 selector 相同
function sameVnode(vnode1: VNode, vnode2: VNode): boolean {
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
}
patchVnode
主要逻辑如下:
- 调用 module 中的 prepatch hook(生命周期相关)
- 按照新旧Vnode 的text 和children 的差异做出差异化处理
- 值得注意的是:新旧节点均存在 children,且不一样时,对 children 进行 diff 调用 updateChildren
function patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
let i: any, hook: any;
// 调用 prepatch hook 生命周期钩子
if (isDef((i = vnode.data)) && isDef((hook = i.hook)) && isDef((i = hook.prepatch))) {
i(oldVnode, vnode);
}
//设置新的vnode的dom 关联
const elm = (vnode.elm = oldVnode.elm as Node);
let oldCh = oldVnode.children;
let ch = vnode.children;
if (oldVnode === vnode) return;
if (vnode.data !== undefined) {
// 调用 module 上的 update hook 生命周期钩子
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
i = vnode.data.hook;
// 调用 vnode 上的 update hook 生命周期钩子
if (isDef(i) && isDef((i = i.update))) i(oldVnode, vnode);
}
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
// 新旧节点均存在 children,且不一样时,对 children 进行 diff
if (oldCh !== ch) updateChildren(elm, oldCh as Array<VNode>, ch as Array<VNode>, insertedVnodeQueue);
} else if (isDef(ch)) {
// 旧节点不存在 children 新节点有 children
// 旧节点存在 text 置空
if (isDef(oldVnode.text)) api.setTextContent(elm, '');
// 加入新的 vnode
addVnodes(elm, null, ch as Array<VNode>, 0, (ch as Array<VNode>).length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
// 新节点不存在 children 旧节点存在 children 移除旧节点的 children
removeVnodes(elm, oldCh as Array<VNode>, 0, (oldCh as Array<VNode>).length - 1);
} else if (isDef(oldVnode.text)) {
// 旧节点存在 text 置空
api.setTextContent(elm, '');
}
} else if (oldVnode.text !== vnode.text) {
// 更新 text
api.setTextContent(elm, vnode.text as string);
}
// 调用 postpatch hook 生命周期钩子
if (isDef(hook) && isDef((i = hook.postpatch))) {
i(oldVnode, vnode);
}
}
核心方法updateChildren
新的Vnode 和 老的Vnode 都有children时,进入方法。
- 对新旧node分别赋值 newStartIdx oldStartIdx newEndIdx oldEndIdx 进行指针循环遍历
- 对不符合same 的节点进行劲增 其他的进行patchVnode 的替换
- 循环结束处理剩余节点 进行批量删除或批量新增
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
**ES6**
* 列举常用的ES6特性:
* 箭头函数需要注意哪些地方?
* let、const、var
* 拓展:var方式定义的变量有什么样的bug?
* Set数据结构
* 拓展:数组去重的方法
* 箭头函数this的指向。
* 手写ES6 class继承。
![](https://img-blog.csdnimg.cn/img_convert/aac1740e50faadb9a6a7a5b97f9ccba8.png)
**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**
**微信小程序**
* 简单描述一下微信小程序的相关文件类型?
* 你是怎么封装微信小程序的数据请求?
* 有哪些参数传值的方法?
* 你使用过哪些方法,来提高微信小程序的应用速度?
* 小程序和原生App哪个好?
* 简述微信小程序原理?
* 分析微信小程序的优劣势
* 怎么解决小程序的异步请求问题?
![](https://img-blog.csdnimg.cn/img_convert/60b1dbe5c76e264468aa993416a9a031.png)