前端面试
1 一、HTML
❓ 如何理解 HTML 语义化?
答案
● 语义化标签的主要目的就是发挥标签和属性的作用,通过标签本身的意来优化 HTML 文档结构。
● 优点:
○ 对搜索引擎:让搜索引擎更方便的根据 HTML 标签来确定上下文和权重,有利于 SEO。
○ 对开发:增加代码可读性,便于阅读和维护。
○ 对用户:在没有 CSS 样式下,页面也能呈现出良好的内容结构。
❓ 默认情况下,哪些 HTML 标签是块级元素、哪些是内联元素?它们之间有何区别?
答案
● 块级元素:display: block / table
○ 包含: div、p、h1、h2、ul、ol、table 等
● 內联元素:display: inline / inline-block
○ 包含:span、img、input、button 等
● 区别:
a. 格式上:默认情况下,内联元素不会以新行开始,而块级元素会新起一行。
b. 内容上:默认情况下,内联元素只能包含文本和其他内联元素。而块级元素可以包含内联元素和其他块级元素。
c. 属性上:内联元素设置 width 无效,height 无效(可以设置 line-height),设置 margin 和 padding 的上下不会对其他元素产生影响。
❓ script 标签中 defer 和 async 的区别?
答案
script:会阻碍 HTML 解析,只有下载好并执行完脚本才会继续解析 HTML。
defer script:完全不会阻碍 HTML 的解析,解析 HTML 过程中进行脚本的异步下载,HTML 解析完成之后再按照顺序执行脚本。
async script:有可能会阻断 HTML 的解析,解析 HTML 过程中进行脚本的异步下载,下载成功立马执行。
蓝色线代表脚本的网络加载;红色线代表脚本的执行时间;绿色线代表 HTML 解析
defer 更常用、更优选
❓ prefetch 和 dns-prefetch 的区别?
答案
● prefetch 是一种由浏览器原生提供的资源预获取方案(和 preload 相关)
● dns-prefetch 是 DNS 预查询(和 preconnect 相关)
preload 和 prefetch
● preload :预加载,用来声明当前页面的关键资源,强制浏览器尽快加载。简单来说,就是通过 标签显式声明一个高优先级资源,强制浏览器提前请求资源,同时不阻塞文档正常 onload。
● prefetch :预提取,用来声明将来可能用到的资源,在浏览器空闲时进行加载。它是一种浏览器机制,会在浏览器空闲时下载或预取用户不久将要访问的文档。网页向浏览器提供一组预取提示,并在浏览器完成当前页面的加载后开始静默地拉取指定的文档并将其存储在缓存中。当用户访问其中一个预取文档时,便可以快速的从浏览器缓存中得到。
dns-prefetch 和 preconnect
● dns-prefetch 即 dns 预获取,是尝试在请求资源之前解析域名,可掩盖 DNS 解析延迟。
● preconnect 即 dns 预连接,当我们的站点需要对别的域下的资源进行请求的时候,就需要和那个域建立连接,然后才能开始下载资源,如果已经知道了是和哪个域进行通信,那就可以先建立连接,然后等需要进行资源请求的时候就可以直接进行下载了。 ❓ onload 和 DOMContentLoaded 的区别? 答案 区别onload 和 DOMContentLoaded 都是事件,用于在文档加载完成后执行 JavaScript 代码,但它们的触发时机不同。
● onload 事件是在整个页面和所有资源(如图片和样式表等)都加载完成后触发的。因此,如果页面中包含大量的图片或其他资源,onload 事件可能会比较慢。
● DOMContentLoaded 事件则是在文档解析完成后触发的,不必等待其他资源的加载完成。
使用场景
● onload 事件适用于需要等待页面中所有资源加载完成后再执行的情况,例如在计算页面加载时间时。
● DOMContentLoaded 事件适用于需要在页面结构解析完成后立即执行的情况,例如初始化页面元素或绑定事件等操作。
二、 CSS
❓ 盒子模型的宽度如何计算?
答案
offsetWidth
● offsetWidth = (内容宽度 + 内边距 + 边框)
● 可以通过设置 box-sizing 即可改变 width 的计算方式。
box-sizing: content-box
● 标准盒模型
● offsetWidth = broder-left + padding-left + width + padding-right + border-right;
box-sizing: border-box
● IE 盒模型
● offsetWidth = width;
模拟题目
✅ 优先级是由 A 、B、C、D 的值来决定的,其中它们的值计算规则如下:
● 如果存在内联样式,那么 A = 1,否则 A = 0 ;
● B 的值等于 ID选择器(#id) 出现的次数;
● C 的值等于 类选择器(.class) 和 属性选择器(a[href="https://example.org"]) 和 伪类选择器(:first-child) 出现的总次数;
● D 的值等于 标签选择器(h1,a,div) 和 伪元素选择器(::before,::after) 出现的总次数。
从左至右比较,如果是样式优先级相等,取后面出现的样式。
❓ 说一下伪类和伪元素?
答案
● 伪类:以单冒号(:)开头,用于选择处于特定状态的元素。例如 :hover、:focus、:active、:visited 等。
● 伪元素:以双冒号(::)开头,用于向某个选择器选中的元素的某个部分添加样式或内容。例如 ::before 和 ::after。
❓ 说一下对 BFC 理解和应用?
答案
定义
BFC(Block format context)即块级格式化上下文,它是一块独立的渲染区域,内部元素的渲染不会影响到边界以外的元素,即内部元素不会脱离容器的控制范围。
特点
-
属于同一个 BFC 的相邻 Box 垂直排列
-
属于同一个 BFC 的相邻 Box 的上下 margin 会发生外边距重叠
-
BFC 在页面中是独立的容器,外面的元素不会影响里面的元素,反之亦然。
-
BFC 的区域不会与浮动元素区域重叠
-
计算 BFC 的高度时,浮动子元素也参与计算
创建方式 -
body 根元素
-
float 不是 none
-
overflow 不是 visible(默认为 visible)
-
position 是 absolute 或 fixed
-
display 是 flex 、inline-block 等
应用
● 阻止 margin 重叠:同一个 BFC 下外边距(margin)会发生折叠,放入不同的 BFC 即可
● 清除浮动 :清除内部浮动(清除浮动的原理是两个 div 都位于同一个 BFC 区域之中)
● 自适应两栏布局:左 float + 右 BFC,是利用了 BFC 的区域不会与 float 的元素区域重叠的机制
❓说一下浮动,如何清除浮动?
答案
浮动会造成什么
● 父元素高度塌陷
● 与浮动元素同级的非浮动元素会填补原有位置
如何清除浮动的影响
● 使用 ::after 在父元素末尾加一个伪元素,并添加 clear: both 属性【最常用】
● 利用 BFC:给父元素添加 overflow:hidden 或者 auto 样式
● 给父元素设置高度 height
● 在浮动元素后面添加 clear:both 的空 div 或者 br 标签
clearfix 代码
/* 手写 clearfix */
.clearfix::after {
content: "";
display: table;
clear: both;
}
.clearfix {
zoom: 1; / 兼容 IE 低版本 */
}
❓ 居中对齐有哪些实现方式?
答案
❓如何判断一个元素是否在视口中?
答案
使用场景:在懒加载等业务场景中,经常需要监听页面滚动去判断相关元素是否处于视口中。
方案一【兼容性最好,性能不好】
监听滚动事件,回调函数里判断:
window.addEventListener("scroll", () => {
el.offsetTop <= window.innerHeight + window.scrollY;
});
方案二【兼容性一般,性能不好】
Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口 的位置,进而计算是否位于视口中。
在主线程中,每一帧都调用 Element.getBoundingClientReact() 会造成性能问题(例如不当使用导致页面强制重排)。
监听滚动事件,回调函数里判断:
window.addEventListener("scroll", () => {
el.getBoundingClientRect().top <= window.innerHeight;
});
方案三【兼容性一般,性能最好】
Intersection Observer API 提供了一种异步检测目标元素与视口相交情况变化的方法。这个 API 支持注册回调函数,当被监视的元素和视口的相交情况发生变化时触发回调。这样,就将相交的判断交给浏览器自行管理优化,进而提高滚动性能。
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// 元素进入视口
} else {
// 元素离开视口
}
});
});
observer.observe(element);
❓ CSS 中样式引入的几种方式和特点?
答案
在 HTML 样式中有 4 种 CSS 引用方式,分别为:
行内样式:
● 使用:在元素上通过 style="" 属性直接写样式。
● 特点:行内样式只作用于当前标签。HTML页面不纯净,后期维护不方便。
内部样式表:
● 使用:通过,通常写在 里面。
● 特点:内部样式作用于当前文件。
链入外部样式表:
● 使用:,一般写在
的前面。
● 特点:
○ 页面加载时 CSS 文件被同时加载,不会阻塞 HTML 页面的解析和渲染。
○ 可以在页面中使用多个 标签,引入多个 CSS 文件。
○ 可以在 标签中使用 media 属性指定样式应该应用于哪些设备。
导入外部样式表:
● 使用:通过 @import 引入其他的 CSS 文件。
● 特点:CSS 文件被加载时会阻塞页面的渲染,因为要等待 CSS 文件加载完毕才能继续渲染。
综上所述,方式是更常见和推荐的 CSS 引入方式,它不仅性能更好,而且使用更加灵活。不推荐使用 @import。
❓ px、em、rem是什么?
答案
● px:绝对长度单位,页面按精确像素展示。
● em:相对长度单位,相对于父元素的 font-size,如果自身定义了font-size 按自身来计算。
● rem:相对长度单位,相对于根元素(html 标签)的 font-size,常用于响应式布局。
❓ 隐藏页面中某个元素的方法有哪些?
答案
三、Javascript
❓ 基本的数据类型介绍,值类型和引用类型的区别 ?
答案
在 JS 中共有 8 种基本的数据类型,分别为:
● Undefined:值类型
● Null:值类型
● Boolean:值类型
● Number:值类型
● String:值类型
● Object:引用类型
● Symbol:ES6 新增,代表独一无二的值,最大的用法是用来定义对象的唯一属性名
● BigInt:ES6 新增,可以表示任意大小的整数
存储位置:
● 值类型:是直接存储在栈中的简单数据段。由于它们占据空间小、大小固定,所以属于被频繁使用数据,,放入栈中存储。
● 引用类型:在 栈 中存储内存地址,指向存储在 堆 中的对象。由于它们占据空间大、大小不固定,因此如果直接存储在栈中,将会影响程序运行的性能。
值类型和引用类型的赋值和拷贝机制分离开来,是基于内存空间和 CPU 的计算耗时考虑的。
❓ 说一下 undefined 和 null
答案
● undefined
○ 含义:无值
○ 用法:如果要表达的是这里可能是有任何东西,但是目前没有值,用 undefined
● null
○ 含义:无对象
○ 用法:如果要表达的是这里原本应该是一个对象,但是目前没有对象,用 null
❓ 数据类型的判断?
答案
● 能判断所有值类型和函数类型。不可对 null、对象、数组进行精确判断,因为都返回 object 。
// 可以判断
console.log(typeof undefined); // undefined
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof "str"); // string
console.log(typeof Symbol("foo")); // symbol
console.log(typeof 2172141653n); // bigint
console.log(typeof function () {}); // function
// 不能判别
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
instanceof
● 能判断对象类型,不能判断值类型。
● instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
比如考虑以下代码:
class People {}
class Student extends People {}
const leo = new Student();
console.log(leo instanceof People); // true
console.log(leo instanceof Student); // true
其实现就是顺着原型链去找,如果能找到对应的 Xxxxx.prototype 即为 true 。比如这里的 leo 作为实例,顺着原型链能找到 Student.prototype 及 People.prototype ,所以都为 true 。
Object.prototype.toString.call()(手写类型判断函数)
● 所有原始数据类型都是能判断的,还有 Error 对象,Date 对象等。后边接上 .slice(8, -1) 即可得到具体的值。
Object.prototype.toString.call(2); // "[object Number]"
Object.prototype.toString.call(""); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(Math); // "[object Math]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(function () {}); // "[object Function]"
❓ 说一下 var、let、const 的区别?
答案
- 【顶级对象的属性】:
○ 全局范围内用 var 声明变量,会成为顶级对象的属性。
○ 全局范围内用 let、const 声明变量,不会成为顶级对象的属性。 - 【块级作用域限制】:
○ var 没有块级作用域。
○ let 和 const 添加了块级作用域。 - 【重复声明限制】:
○ 同一个作用域下,多个 var 可以重复声明。
○ 同一个作用域下,let 和 const 不可重复声明,重复声明会报错(let 和 var 混着用也会报错) - 【变量提升 - 暂时性死区 TDZ】:
○ var 声明的变量会存在变量提升,在声明语句之前访问,得到的值是 undefined。
○ let 和 const 声明的变量也会存在变量提升,但是在提升到的第一行与声明语句之间存在一个暂时性死区(TDZ),在这个区域内访问这个变量会报错。 - 【const 常量限制】:
○ const 声明的是常量,声明时必须初始化,且后面的代码中不能修改该常量的值。
○ let 和 var 可以后续再定义。
❓ 如果希望 const 的对象属性也不能被修改怎么做?
答案 - Object.defineProperty() 修改属性描述符 writable: false
const obj = {
x: 100,
};
Object.defineProperty(obj, "x", {
writable: false, // 设置不允许写入
});
obj.x = 66;
console.log(obj); // - Object.freeze()
冻结一个对象,被冻结对象自身的所有属性都不可能以任何方式被修改且不可新增属性。但如果一个属性的值是个对象,则这个对象中的属性是可以修改的,除非它也是个冻结对象。
const obj = {
a: 1,
b: {
x: 100,
},
};
Object.freeze(obj);
obj.a = 111; // 无法被修改
console.log(obj.a); // 1
obj.c = 3; // 无法新增
console.log(obj.c); // undefined
obj.b.x = 200; // 可以被修改
console.log(obj.b.x); // 200 - 利用 Proxy 代理对象的 setter。
❓ 说一下变量提升和函数提升?
答案
变量提升
ES6 之前我们一般使用 var 来声明变量,提升简单来说就是把我们所写的类似于 var a = 123; 这样的代码,声明提升到它所在作用域的顶端去执行,到我们代码所在的位置来赋值。
console.log(name); // undefined
var name = "Sunshine_Lin";
if (false) {
var age = 23;
}
console.log(age); // undefined 不会报错
函数提升
javascript 中不仅仅是变量声明有提升的现象,函数的声明也是一样;具名函数的声明有两种方式:
● 函数声明式:函数声明式 的函数提升是整个代码块提升到它所在的作用域的最开始执行
● 函数字面量式:函数字面量式 的声明和变量提升的结果是一样的,函数只是一个具体的值
// 函数声明式
function bar() {}
// 函数字面量式
var foo = function () {};
console.log(fun); // function fun() {}
function fun() {}
if (false) {
function fun2() {}
}
console.log(fun2); // undefined 不会报错
函数提升优先级 > 变量提升优先级
console.log(fun); // function fun() {}
var fun = "leo";
function fun() {}
console.log(fun); // 'leo'
❓ ES6 有哪些新特性
答案
-
let、const
-
变量的解构赋值
-
模板字符串
-
箭头函数
-
Map、Set 数据结构
-
Proxy、Reflect 等内置对象
-
Promise 和 async await
-
Iterator 和 for…of 循环
-
Class 基本语法
-
Module 的语法,import、export
❓ 聊一聊 Map、Set 、WeakMap、WeakSet
答案
❓ == 和 ===,说一下使用 == 时候的隐式类型转换
答案
❓ for…in 和 for…of 有什么区别?
答案
● for...in 可以用在可枚举(属性描述符 enumerable 是 true)的数据,如对象、数组、字符串,得到 key
● for...of 用于可迭代(实现了 Iterator 接口)的数据,如 Map、Set、数组、字符串,得到 value
❓ 根据 0.1 + 0.2 !== 0.3,讲讲 IEEE 754 ,如何让其相等?
答案
❓ 如何理解 原型 与 原型链
答案
❓ 作用域 与 作用域链
答案
❓ 说一下对闭包的理解
答案
❓ 说一说 this 的 4 种绑定方式
答案
❓ 普通函数 和 箭头函数 有什么区别?
答案
❓ 说一下 JS 的继承方式有哪些?
答案
❓ 如何理解 JS 的异步【单线程是异步产生的原因】
答案
❓ 阐述一下 JS 的事件循环【事件循环是异步的实现方式】
答案
❓ Event loop、宏任务和微任务
答案
❓ Promise 相关
答案
❓ CommonJS 和 ES6 Module 有什么区别?
答案
四、手写面试题
❓ 手写 Object.create
答案
❓ 手写 instanceof
答案
❓ 手写类型判断函数
答案
❓ 手写 new 操作符
答案
❓ 实现深拷贝
答案
/**
-
深拷贝
-
@param {Object} target 要拷贝的对象
-
@param {WeakMap} map 用于存储循环引用对象的地址
*/
function deepClone(target, map = new WeakMap()) {
// 基础类型或者 null 直接返回
if (typeof target !== "object" || target === null) {
return target;
}
// 克隆后的结果
let cloneTarget = {};
// 防止循环引用
if (map.has(target)) return map.get(target);
map.set(target, cloneTarget);
// ===== Map =====
if (target instanceof Map) {
cloneTarget = new Map();
target.forEach((value, key) => {
const v = deepClone(value, map);
const k = deepClone(key, map);
cloneTarget.set(k, v);
});
}
// ===== Set =====
else if (target instanceof Set) {
cloneTarget = new Set();
target.forEach(value => {
const v = deepClone(value, map);
cloneTarget.add(v);
});
}
// ===== Array =====
else if (target instanceof Array) {
cloneTarget = target.map(item => deepClone(item, map));
}
// ===== Object =====
else {
for (const key in target) {
// 遍历每一个属性保证 key 不是原型属性
if (target.hasOwnProperty(key)) {
// 递归调用
cloneTarget[key] = deepClone(target[key], map);
}
}
}return cloneTarget;
}
❓ 手写防抖函数
答案
❓ 手写节流函数
答案
❓ 实现数组的扁平化
答案
五、计算机网络
❓ 从浏览器地址栏输入 URL 到请求返回发生了什么?
答案
❓ 说一下 DNS 解析 URL 对应的 IP 地址的过程
答案
❓ TCP 协议和 UDP 协议有什么区别 ?
答案
❓ 说一下 TCP 的三次握手
答案
❓ 说一下 TCP 的四次挥手
答案
❓ HTTP 缓存
答案
❓ HTTP 状态码
答案
❓ 常见的 HTTP 请求头和响应头
答案
❓ HTTP 1.0、HTTP 1.1、HTTP 2.0 之间有哪些区别?
答案
❓ 对 keep-alive 的理解
答案

浙公网安备 33010602011771号