Web前端最全解析vue2,2024年最新开发岗位面试题

结尾

正式学习前端大概 3 年多了,很早就想整理这个书单了,因为常常会有朋友问,前端该如何学习,学习前端该看哪些书,我就讲讲我学习的道路中看的一些书,虽然整理的书不多,但是每一本都是那种看一本就秒不绝口的感觉。

以下大部分是我看过的,或者说身边的人推荐的书籍,每一本我都有些相关的推荐语,如果你有看到更好的书欢迎推荐呀。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端学习书籍导图-1

function sameVnode(oldVnode, vnode){

return vnode.key === oldVnode.key && vnode.sel === oldVnode.sel

}

两个 vnode 的 key 和 sel 相同才去比较它们,比如 p 和 span,div.classA 和 div.classB 都被认为是不同结构而不去比较它们。

如果值得比较会执行 patchVnode(oldVnode, vnode),稍后会详细讲 patchVnode 函数。

当节点不值得比较,进入 else 中

else {

const oEl = oldVnode.el

let parentEle = api.parentNode(oEl)

createEle(vnode)

if (parentEle !== null) {

api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl))

api.removeChild(parentEle, oldVnode.el)

oldVnode = null

}

}

过程如下:

  • 取得 oldvnode.el 的父节点,parentEle 是真实 dom

  • createEle(vnode) 会为 vnode 创建它的真实 dom,令 vnode.el =真实 dom

  • parentEle 将新的 dom 插入,移除旧的 dom

当不值得比较时,新节点直接把老节点整个替换了

最后

return vnode

patch 最后会返回 vnode,vnode 和进入 patch 之前的不同在哪?

没错,就是 vnode.el,唯一的改变就是之前 vnode.el = null, 而现在它引用的是对应的真实 dom。

var oldVnode = patch (oldVnode, vnode)

至此完成一个 patch 过程。

patchVnode

========================================================================

两个节点值得比较时,会调用 patchVnode 函数

patchVnode (oldVnode, vnode) {

const el = vnode.el = oldVnode.el

let i, oldCh = oldVnode.children, ch = vnode.children

if (oldVnode === vnode) return

if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {

api.setTextContent(el, vnode.text)

}else {

updateEle(el, vnode, oldVnode)

if (oldCh && ch && oldCh !== ch) {

updateChildren(el, oldCh, ch)

}else if (ch){

createEle(vnode) //create el’s children dom

}else if (oldCh){

api.removeChildren(el)

}

}

}

const el = vnode.el = oldVnode.el 这是很重要的一步,让 vnode.el 引用到现在的真实 dom,当 el 修改时,vnode.el 会同步变化。

节点的比较有5种情况

1、 if (oldVnode === vnode),他们的引用一致,可以认为没有变化。

2、if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text),文本节点的比较,需要修改,则会调用 Node.textContent = vnode.text

3、if( oldCh && ch && oldCh !== ch ), 两个节点都有子节点,而且它们不一样,这样我们会调用 updateChildren 函数比较子节点,这是 diff 的核心,后边会讲到。

4、else if (ch),只有新的节点有子节点,调用 createEle(vnode),vnode.el 已经引用了老的 dom 节点,createEle 函数会在老 dom 节点上添加子节点。

5、else if (oldCh),新节点没有子节点,老节点有子节点,直接删除老节点。

updateChildren

============================================================================

updateChildren (parentElm, oldCh, newCh) {

let oldStartIdx = 0, newStartIdx = 0

let oldEndIdx = oldCh.length - 1

let oldStartVnode = oldCh[0]

let oldEndVnode = oldCh[oldEndIdx]

let newEndIdx = newCh.length - 1

let newStartVnode = newCh[0]

let newEndVnode = newCh[newEndIdx]

let oldKeyToIdx

let idxInOld

let elmToMove

let before

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {

if (oldStartVnode == null) { //对于vnode.key的比较,会把oldVnode = null

oldStartVnode = oldCh[++oldStartIdx]

}else if (oldEndVnode == null) {

oldEndVnode = oldCh[–oldEndIdx]

}else if (newStartVnode == null) {

newStartVnode = newCh[++newStartIdx]

}else if (newEndVnode == null) {

newEndVnode = newCh[–newEndIdx]

}else if (sameVnode(oldStartVnode, newStartVnode)) {

patchVnode(oldStartVnode, newStartVnode)

oldStartVnode = oldCh[++oldStartIdx]

newStartVnode = newCh[++newStartIdx]

}else if (sameVnode(oldEndVnode, newEndVnode)) {

patchVnode(oldEndVnode, newEndVnode)

oldEndVnode = oldCh[–oldEndIdx]

newEndVnode = newCh[–newEndIdx]

}else if (sameVnode(oldStartVnode, newEndVnode)) {

patchVnode(oldStartVnode, newEndVnode)

api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))

oldStartVnode = oldCh[++oldStartIdx]

newEndVnode = newCh[–newEndIdx]

}else if (sameVnode(oldEndVnode, newStartVnode)) {

patchVnode(oldEndVnode, newStartVnode)

api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)

oldEndVnode = oldCh[–oldEndIdx]

newStartVnode = newCh[++newStartIdx]

}else {

// 使用key时的比较

if (oldKeyToIdx === undefined) {

oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表

}

idxInOld = oldKeyToIdx[newStartVnode.key]

if (!idxInOld) {

api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)

newStartVnode = newCh[++newStartIdx]

}

else {

elmToMove = oldCh[idxInOld]

if (elmToMove.sel !== newStartVnode.sel) {

api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)

}else {

patchVnode(elmToMove, newStartVnode)

oldCh[idxInOld] = null

api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)

}

newStartVnode = newCh[++newStartIdx]

}

}

}

if (oldStartIdx > oldEndIdx) {

before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el

addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)

}else if (newStartIdx > newEndIdx) {

removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)

}

}

代码很密集,为了形象的描述这个过程,可以看看这张图。

在这里插入图片描述

过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 StartIdx 和 EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用 key 进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。

具体的 diff 分析

=========================================================================

设置 key 和不设置 key 的区别:

不设 key,newCh 和 oldCh 只会进行头尾两端的相互比较,设 key 后,除了头尾两端的比较外,还会从用 key 生成的对象 oldKeyToIdx 中查找匹配的节点,所以为节点设置 key 可以更高效的利用 dom。

diff 的遍历过程中,只要是对 dom 进行的操作都调用 api.insertBeforeapi.insertBefore 只是原生 insertBefore 的简单封装。

比较分为两种,一种是有 vnode.key 的,一种是没有的。但这两种比较对真实 dom 的操作是一致的。

对于与 sameVnode(oldStartVnode, newStartVnode)sameVnode(oldEndVnode,newEndVnode)true 的情况,不需要对 dom 进行移动。

总结遍历过程,有3种 dom 操作:

1、当 oldStartVnode,newEndVnode 值得比较,说明 oldStartVnode.el 跑到 oldEndVnode.el 的后边了。

图中假设 startIdx 遍历到1。

在这里插入图片描述

2、当 oldEndVnode,newStartVnode 值得比较,说明 oldEndVnode.el 跑到了newStartVnode.el 的前边。(这里笔误,应该是 “ oldEndVnode.el 跑到了oldStartVnode.el 的前边”,准确的说应该是 oldEndVnode.el 需要移动到 oldStartVnode.el 的前边”)

在这里插入图片描述

3、newCh 中的节点 oldCh 里没有, 将新节点插入到 oldStartVnode.el 的前边。

在这里插入图片描述

在结束时,分为两种情况:

1、oldStartIdx > oldEndIdx,可以认为oldCh先遍历完。当然也有可能newCh此时也正好完成了遍历,统一都归为此类。此时newStartIdx和newEndIdx之间的vnode是新增的,调用addVnodes,把他们全部插进before的后边,before很多时候是为null的。addVnodes调用的是insertBefore操作dom节点,我们看看insertBefore的文档:parentElement.insertBefore(newElement, referenceElement)

如果referenceElement为null则newElement将被插入到子节点的末尾。如果newElement已经在DOM树中,newElement首先会从DOM树中移除。所以before为null,newElement将被插入到子节点的末尾。

在这里插入图片描述

2、newStartIdx > newEndIdx,可以认为newCh先遍历完。此时 oldStartIdx 和 oldEndIdx 之间的 vnode 在新的子节点里已经不存在了,调用 removeVnodes 将它们从 dom 里删除。

在这里插入图片描述

下面举个例子,画出 diff 完整的过程,每一步 dom 的变化都用不同颜色的线标出。

1、a,b,c,d,e 假设是4个不同的元素,我们没有设置 key 时,b没有复用,而是直接创建新的,删除旧的。

在这里插入图片描述

2、当我们给4个元素加上唯一key时,b得到了的复用。

在这里插入图片描述

这个例子如果我们使用手工优化,只需要3步就可以达到。

总结

================================================================

  • 尽量不要跨层级的修改dom

  • 设置key可以最大化的利用节点

  • diff的效率并不是每种情况下都是最优的

原文地址

---------------------------(正文完)------------------------------------

一个前端的学习交流群,想进来面基的,可以点击这个logo外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传,或者手动search群号:685486827

写在最后: 约定优于配置-------软件开发的简约原则.

-------------------------------- (完)--------------------------------------

我的:

个人网站: https://neveryu.github.io/neveryu/
Github: https://github.com/Neveryu
新浪微博: https://weibo.com/Neveryu
更多学习资源请关注我的新浪微博…

框架相关

原生JS虽能实现绝大部分功能,但要么就是过于繁琐,要么就是存在缺陷,故绝大多数开发者都会首选框架开发方案。现阶段较热门是React、Vue两大框架,两者工作原理上存在共通点,也存在一些不同点,对于校招来说,不需要两个框架都学得特别熟,一般面试官会针对你简历中写的框架进行提问。

在框架方面,生命周期、钩子函数、虚拟DOM这些基本知识是必须要掌握的,在学习的过程可以结合框架的官方文档

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

Vue框架

知识要点:
1. vue-cli工程
2. vue核心知识点
3. vue-router
4. vuex
5. http请求
6. UI样式
7. 常用功能
8. MVVM设计模式

React框架

知识要点:
1. 基本知识
2. React 组件
3. React Redux
4. React 路由

、钩子函数、虚拟DOM这些基本知识是必须要掌握的**,在学习的过程可以结合框架的官方文档

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

Vue框架

知识要点:
1. vue-cli工程
2. vue核心知识点
3. vue-router
4. vuex
5. http请求
6. UI样式
7. 常用功能
8. MVVM设计模式

[外链图片转存中…(img-chrlpIoj-1715896447425)]

React框架

知识要点:
1. 基本知识
2. React 组件
3. React Redux
4. React 路由

[外链图片转存中…(img-FVilynMP-1715896447425)]

  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值