前端面试题 每日一道
这个flag从好几个星期前就开始了 但是就一直没有实施 我觉得问题就是我的时间点不对 要把面试题提前到第一件事情做 后面的事情再慢慢来吧 好 那就开始⑧
这里是整理出来的几个面试题库网址
关于面试题型
1.前端页面加载流程 TCP请求到页面交互
2.前端框架对比 React 和 Vue
3.理解函数式组件的产生背景和优势 理解js class逐渐被react和vue抛弃的原因
4. js 基础知识
5.优化方案
TCP请求
接口请求
页面绘制
正文👇
1)关于盒子模型的正确表述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sHvtbglG-1646491405264)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1643254629136.png)]
这题选A
找到了一个方便快捷的解释 就是标准盒模型和IE盒模型的总宽都是一样的,区别在于width IE盒的width包括了content padding border 而标准盒的width包括的只有content
标准盒模型
content-box
**= width border padding margin ; width = content **IE盒模型
border-box
**= width margin ; width= border + content **
**应该选择“标准 w3c 盒子模型(就是标准盒模型)”
了 。假如加上了 doctype
声明,那么所有浏览器都会采用标准 w3c 盒子模型去解释你的盒子,网页就能在各个浏览器中显示一致了。 **
2)关于Object.defineProperty
Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
其实看到这个题目的时候我甚至不知道ObjectProperty
是什么东西 然后就去搜了一下👇
Object.defineProperty()语法说明
Object.defineProperty()
的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性Object.defineProperty(obj, prop, desc)
- obj 需要定义属性的当前对象
- prop 当前需要定义的属性名
- desc 属性描述符
一般通过为对象的属性赋值的情况下,对象的属性可以修改也可以删除,但是通过Object.defineProperty()定义属性,通过描述符的设置可以进行更精准的控制对象属性。
所以言归正传 问题的答案是什么呢?👇
- Object.defineProperty无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;
- Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy可以劫持整个对象,并返回一个新的对象。
- Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
3)html5新特性、语义化
HTML 5的页面结构和HTML 4或早先的HTML有什么不同?
一个典型的Web页面有页眉(header),页脚(footer),导航(navigation),正文(central area)和侧栏(side bar)。现在如果是在HTML 4中,HTML部分中的上述这些专用名词需要使用DIV标签来描述。
但是,如果是在HTML 5,可以专门为这些区域创建特定的元素名,让HTML更具可读性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-shTYepzE-1646491405266)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1643257242043.png)]
以下是形成页面结构的HTML 5元素的更多细节。
<header>:表现HTML的标题数据。 <footer>:页面的页脚部分。 <nav>:页面中的导航元素。 <article>:正文内容。 <section>:用在正文中定义section或区段内容。 <aside>:表现页面侧边栏内容。
4)浏览器渲染机制、重绘、重排
网页生成过程:
HTML
被HTML解析器解析成DOM
树css
则被css解析器解析成CSSOM
树- 结合
DOM
树和CSSOM
树,生成一棵渲染树(Render Tree
)- 生成布局(
flow
),即将所有渲染树的所有节点进行平面合成- 将布局绘制(
paint
)在屏幕上重排(也称回流): 当
DOM
的变化影响了元素的几何信息(DOM
对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。 触发:
- 添加或者删除可见的DOM元素
- 元素尺寸改变——边距、填充、边框、宽度和高度
重绘: 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。 触发:
- 改变元素的
color、background、box-shadow
等属性重排优化建议:
- 分离读写操作
- 样式集中修改
- 缓存需要修改的
DOM
元素- 尽量只修改
position:absolute
或fixed
元素,对其他元素影响不大- 动画开始
GPU
加速,translate
使用3D
变化
transform
不重绘,不回流 是因为**transform
属于合成属性**,对合成属性进行transition/animate
动画时,将会创建一个合成层。这使得动画元素在一个独立的层中进行渲染。当元素的内容没有发生改变,就没有必要进行重绘。浏览器会通过重新复合来创建动画帧。
5)Css样式优先级
important > style > id > class
6)什么是BFC?BFC的布局规则是什么?如何创建BFC?BFC应用?
BFC
是Block Formatting Context
的缩写,即块格式化上下文。BFC
是CSS布局的一个概念,是一个环境,里面的元素不会影响外面的元素。布局规则:Box是CSS布局的对象和基本单位,页面是由若干个Box组成的。元素的类型和display属性,决定了这个Box的类型。不同类型的Box会参与不同的
Formatting Context
。创建:浮动元素
display:inline-block
position:absolute
应用: 1.分属于不同的
BFC
时,可以防止margin
重叠 2.清除内部浮动 3.自适应多栏布局
7)DOM, BOM对象
**
BOM(Browser Object Model)
**是指浏览器对象模型,**可以对浏览器窗口进行访问和操作。**使用 BOM,开发者可以移动窗口、改变状态栏中的文本以及执行其他与页面内容不直接相关的动作。 使JavaScript
有能力与浏览器"对话"。**
DOM (Document Object Model)
**是指文档对象模型,通过它,可以访问HTML
文档的所有元素。DOM
是W3C
(万维网联盟)的标准。DOM
定义了访问HTML
和XML
文档的标准: “W3C 文档对象模型(DOM)是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容、结构和样式。”W3C DOM
标准被分为 3 个不同的部分:
- 核心
DOM
- 针对任何结构化文档的标准模型XML DOM
- 针对 XML 文档的标准模型HTML DOM
- 针对 HTML 文档的标准模型什么是
XML DOM
?
XML DOM
定义了所有 XML 元素的对象和属性,以及访问它们的方法。什么是 HTML DOM?
HTML DOM
定义了所有 HTML 元素的对象和属性,以及访问它们的方法。
8)暂时性死区
//情景一、 if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 }
上面代码中,在
let
命令声明变量tmp
之前,都属于变量tmp
的“死区”。//情景二 var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
上面代码中,存在全局变量
tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
ES6
明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。//情景三 // 不报错 var x = x; // 报错 let x = x; // ReferenceError: x is not defined
上面代码报错,也是因为暂时性死区。**使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。**上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“
总结
var
和函数声明在执行上下文中的变量环境里,在编译阶段,当JS
遇到var
声明的变量时,会自动先赋值一个undefined
,当遇到函数时,会先预编译函数,检查是否存在闭包(如果存在,会将闭包引入的变量从栈存放在堆中作为持久引用),然后将函数放在变量环境里,函数是会放在变量前面的。而
let
,const
的变量名都是存储在词法环境里的,词法环境里的变量是不会事先赋值一个undefined
的,没有赋值自然就会报错了。
9)闭包
闭包是指**有权访问另一个函数作用域中的变量的函数 **
当函数可以记住并访问所在的词法作用域时,就产生了闭包
即使函数是在当前词法作用域之外执行
闭包用途:
- 能够访问函数定义时所在的词法作用域(阻止其被回收)
- 私有化变量
- 模拟块级作用域
- 创建模块
闭包缺点:
会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
10)Diff
算法
好早之前就想学这个东西了 好像说什么这个是要会的 刚好今天看到这篇文章 就来学一哈吧 ~
关键词:
patch
sameVnode
patchNode
updateChildren
什么是虚拟DOM?
虚拟DOM其实就是一个对象 一个用来表示真实DOM的对象
<ul id="list">
<li class="item">哈哈</li>
<li class="item">呵呵</li>
<li class="item">嘿嘿</li>
</ul>
对应的虚拟DOM
为👇
let vDOM = { // 旧虚拟DOM
tagName: 'ul', // 标签名
props: { // 标签属性
id: 'list'
},
children: [ // 标签子节点
{
tagName: 'li', props: { class: 'item' }, children: ['哈哈']
},
{
tagName: 'li', props: { class: 'item' }, children: ['呵呵']
},
{
tagName: 'li', props: { class: 'item' }, children: ['嘿嘿']
},
]
}
虚拟DOM把真实DOM解析成一个对象 包含标签名 标签属性和标签子节点
解释一下 👇
props是属性
效率问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fRIJdi27-1646491405267)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1646101497991.png)]
虚拟DOM算法操纵真实DOM性能高于操作真实DOM
虚拟DOM算法
= 虚拟DOM + Diff算法
正文
其实Diff算法
就是一种对比算法 ,对比两者是旧虚拟DOM和新虚拟DOM
,对比出是哪个虚拟节点
更改了,找出这个虚拟节点
,并只更新这个虚拟节点所对应的真实节点
,而不用更新其他数据没发生改变的节点,实现精准
地更新真实DOM,进而提高效率
。
-
使用虚拟DOM算法的损耗计算
: 总损耗 = 虚拟DOM增删改+(与Diff
算法效率有关)真实DOM差异增删改+(较少的节点)排版与重绘 -
直接操作真实DOM的损耗计算
: 总损耗 = 真实DOM完全增删改+(可能较多的节点)排版与重绘
Diff
算法的原理
新旧虚拟DOM对比的时候,Diff算法
比较只会在同层级进行, 不会跨层级比较。 所以Diff算法
是:深度优先算法
。 时间复杂度:O(n)
当数据改变时,会触发setter
,并且通过Dep.notify
去通知所有订阅者Watcher
,订阅者们就会调用patch方法
,给真实DOM打补丁,更新相应的视图。
patch方法
这个方法作用就是,对比当前同层的虚拟节点是否为同一种类型的标签(同一类型的标准,下面会讲)
:
- 是:继续执行
patchVnode方法
进行深层比对 - 否:没必要比对了,直接整个节点替换成
新虚拟节点
核心代码👇
function patch(oldVnode, newVnode) {
// 比较是否为一个类型的节点
if (sameVnode(oldVnode, newVnode)) {
// 是:继续进行深层比较
patchVnode(oldVnode, newVnode)
} else {
// 否
const oldEl = oldVnode.el // 旧虚拟节点的真实DOM节点
const parentEle = api.parentNode(oldEl) // 获取父节点
createEle(newVnode) // 创建新虚拟节点对应的真实DOM节点
if (parentEle !== null) {
api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新元素添加进父元素
api.removeChild(parentEle, oldVnode.el) // 移除以前的旧元素节点
// 设置null,释放内存
oldVnode = null
}
}
return newVnode
}
sameVnode
方法
判断是否为同一类型节点
核心原理代码👇
function sameVnode(oldVnode, newVnode) { return ( oldVnode.key === newVnode.key && // key值是否一样 oldVnode.tagName === newVnode.tagName && // 标签名是否一样 oldVnode.isComment === newVnode.isComment && // 是否都为注释节点 isDef(oldVnode.data) === isDef(newVnode.data) && // 是否都定义了data sameInputType(oldVnode, newVnode) // 当标签为input时,type必须是否相同 ) } 复制代码
如果通过
sameVnode(a, b)
对比之后还是不一样的话 就直接用新的节点替换掉之前的节点
patchVnode
方法
这个函数做了以下事情:
- 找到对应的
真实DOM
,称为el
- 判断
newVnode
和oldVnode
是否指向同一个对象,如果是,那么直接return
- 如果他们都有文本节点并且不相等,那么将
el
的文本节点设置为newVnode
的文本节点。 - 如果
oldVnode
有子节点而newVnode
没有,则删除el
的子节点 - 如果
oldVnode
没有子节点而newVnode
有,则将newVnode
的子节点真实化之后添加到el
- 如果两者都有子节点,则执行
updateChildren
函数比较子节点,这一步很重要
updateChildren
就是使用首尾指针法
进行对比的
⭐最终的渲染结果都要以
newVDOM
为准原理图如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xrw801Iq-1646491405267)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1646488628620.png)]
11)在 Vue
中为什么不推荐用 index
做 key
具体diff
解释可以看看第十题
Vue3.0
中 在 patchChildren
方法中有这么一段源码
patchChildren
根据是否存在 key 进行真正的 diff
或者直接 patch
(也就是直接用新的节点替换掉旧的节点)。
-
同步头部节点
第一步的事情就是从头开始寻找相同的
vnode
,然后进行patch
,如果发现不是相同的节点,那么立即跳出循环。isSameVNodeType
作用就是判断当前vnode
类型 和vnode
的key
是否相等key 在
diff
算法的作用,就是用来判断是否是同一个节点。 -
同步尾部节点
第二步从尾开始同前
diff
经历第一步操作之后,如果发现没有 patch 完,那么立即进行第二步,从尾部开始遍历依次向前
diff
。如果发现不是相同的节点,那么立即跳出循环。 -
添加新的节点
第三步如果老节点全部 patch,新节点没有被 patch 完,创建新的
vnode
-
删除多余节点
第四步如果新节点全部被 patch,老节点有剩余,那么卸载所有老节点
为什么不要用index
-
性能消耗
使用 index 做 key,破坏顺序操作的时候, 因为每一个节点都找不到对应的 key,导致部分节点不能复用,所有的新
vnode
都需要重新创建。当我们在前面加了一条数据时 index 顺序就会被打断,导致新节点 key 全部都改变了,所以导致我们页面上的 数据都被重新渲染了。
用唯一值作为 key 可以节约开销
-
数据错位
往 input 里面输入一些值,添加一位同学效果: 采用 index 作为 key 时,当在比较时,发现虽然文本值变了,但是当继续向下比较时发现 DOM 节点还是和原来一摸一样,就复用了,但是没想到 input 输入框残留输入的值,这时候就会出现输入的值出现错位的情况
-
解决方案
既然知道用 index 在某些情况下带来很不好的影响,那平时我们在开发当中怎么去解决这种情况呢?其实只要保证 key 唯一不变就行,一般在开发中用的比较多就是下面三种情况。
- 在开发中最好每条数据使用唯一标识固定的数据作为 key,比如后台返回的 ID,手机号,身份证号等唯一值
- 可以采用 Symbol 作为 key,Symbol 是
ES6
引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
let a = Symbol('测试')
let b = Symbol('测试')
console.log(a===b)//false
- 可以采用
uuid
作为 key ,uuid
是 Universally Unique Identifier 的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符
⭐总结
关键词:破坏顺序-----输入类的DOM-----唯一标识的key
- 用 index 作为 key 时,在对数据进行,逆序添加,逆序删除等破坏顺序的操作时,会产生没必要的真实 DOM更新,从而导致效率低
- 用 index 作为 key 时,如果结构中包含输入类的 DOM,会产生错误的 DOM 更新
- 在开发中最好每条数据使用唯一标识固定的数据作为 key,比如后台返回的 ID,手机号,身份证号等唯一值
- 如果不存在对数据逆序添加,逆序删除等破坏顺序的操作时,仅用于渲染展示用时,使用 index 作为 key 也是可以的(但是还是不建议使用,养成良好开发习惯)。
- key作为虚拟DOM对象的唯一标识 在
diff
算法中起到了至关重要的作用
12)
13)
14)
15)
16)
17)
18)
19)
20)
🛫
不懂的知识点
1)Flexbox
Flexbox
可以简单快速的创建一个具有弹性功能的布局,当在一个小屏幕上显示的时候,Flexbox
可以让元素在容器(伸缩容器)中进行自由扩展和收缩,从而容易调整整个布局。它的目的是使用常见的布局模式,比如说三列布局,可以非常简单的实现。一个
Flexbox
布局是由一个伸缩容器(flex containers)和在这个容器里的伸缩项目(flex items)组成。 伸缩容器(flex containers)是一个HTML标签元素,并且“display”属性显式的设置了“flex”属性值。在伸缩容器中的所有子元素都会自动变成伸缩项目(flex items)。display:flex
ey作为虚拟DOM对象的唯一标识** 在diff
算法中起到了至关重要的作用
12)
13)
14)
15)
16)
17)
18)
19)
20)
🛫
不懂的知识点
1)Flexbox
Flexbox
可以简单快速的创建一个具有弹性功能的布局,当在一个小屏幕上显示的时候,Flexbox
可以让元素在容器(伸缩容器)中进行自由扩展和收缩,从而容易调整整个布局。它的目的是使用常见的布局模式,比如说三列布局,可以非常简单的实现。一个
Flexbox
布局是由一个伸缩容器(flex containers)和在这个容器里的伸缩项目(flex items)组成。 伸缩容器(flex containers)是一个HTML标签元素,并且“display”属性显式的设置了“flex”属性值。在伸缩容器中的所有子元素都会自动变成伸缩项目(flex items)。display:flex