问题文章的链接:2022-2023 六年前端中大厂面试总结(仅题目,无答案)本人是个伤心🦐仔,从去年年底被裁员后断断续续面试了三个月, - 掘金 JavaScript
- js的事件循环机制,几个api是宏任务还是微任务 / 说一下事件循环机制 / 说一下事件循环
JS 里面事件循环的过程,浏览器和 Node 的 / 怎么理解 JS 的异步?
**答:浏览器的事件循环是js处理异步任务的核心,允许非阻塞i/o操作。js是单线程的,在事件循环下可以处理多个任务,比如定时器、网络请求。会交给对应的线程处理,对应线程处理完成以后,会把回调放进任务队列。当主线程处理完当前所有任务以后,会去任务队列获取任务执行。
**在浏览器事件循环中,任务分宏任务和微任务。执行一个宏任务会情况当前所有的微任务,包括新产生的微任务。宏任务主要有: setTimeout, setInterval, I/O, 网络请求,dom事件, script脚本, MessageChannel **。
微任务: Promise, MutationObserver
**node的事件循环机制:
六个核心阶段:
-
-
- Timers :执行
setTimeout
和setInterval
回调。 - Pending Callbacks:处理系统操作(如 TCP 错误)的回调。
- Idle/Prepare:内部使用。
- Poll:检索新的 I/O 事件(如文件读取、网络请求)。
- Check :执行
setImmediate
回调。 - Close Callbacks :处理关闭事件的回调(如
socket.on('close')
)。
- Timers :执行
-
-
垃圾回收机制 / 说一下V8的垃圾回收机制 / 从垃圾回收的标记清除原理来理解一下闭包 (什么是GO,AO,比如在 setTimeout 里引用函数变量)
**答:垃圾回收机制是管理内存的机制,防止内存泄漏。该机制分代回收,因为对象存活的时间长短不一,大部分对象存活时间短,分为新生代,存活时间长的为老生代。
****新生代使用Scavenge算法:
****内存布局分为: From-space 和 To-space
****过程:新的对象分配至From-space,当From-space满时,触发垃圾回收:第一步复制存活对象,从根节点出发,标记存活的对象,并将它们复制到To-space,同时整理内存碎片。
****第二步:交换空间,复制完成以后,From-space和To-space角色互换
****晋升:在经过上面几次过程以后,对象仍然存活,或者To-space超过25%,则晋升为老生代。
****老生代使用标记清除和标记压缩算法:
****从根节点出发,遍历所有可达对象标记为存活,回收未被标记的块,产生内存碎片。存活对象会一直内存两外一端,保证剩余空间连续。
****从垃圾回收机制看闭包:
**标记清除算法在遇到闭包的时候是怎么处理的呢?当一个外部函数内部声明了变量同时声明了一个函数,内部函数访问了其变量,同时内部函数被setTimeout引用,此时当外部函数执行完成以后,按理应该会销毁其上下文,但是因为内部函数被setTimeout引用,形成闭包,而不会释放内存
javascript
function outer() {
var x = 10;
setTimeout(function inner() {
console.log(x);
}, 1000);
}
outer(); // outer执行完毕,但inner仍引用x
**这种场景只要等待setTimeout执行完成,就会释放内存。
当内部函数被全局变量引用的话就会存在内存泄漏的场景。
**
javascript
function createHeavyClosure() {
var largeData = new Array(1000000).fill("*"); // 占用大量内存
return function() {
// 未直接使用largeData,但闭包仍持有引用
};
}
var leak = createHeavyClosure();
**这种场景虽然largeData没有直接被使用,但是其放回的函数被全局变量leak引用,形成闭包,闭包的作用域链保留了其上下文
****全局对象(GO):所有的全局变量挂在在GO上
**活动对象(AO):函数执行时创建的上下文,包括其变量和参数。闭包访问其内部变量时,被闭包作用与链保留,知道闭包不可达。
- 为什么在2^53范围内是安全的?
答:js 是用的双精度浮点数 总共存储63位,有效位为52位,有一位默认为1,总共53位。二进制表示2^52 + 2^51 .... + 2^1 + 2^0 等比求和 为2^53 - 1位 - 介绍一下函数节流,什么时候会用到,和防抖的区别。/ 防抖和节流的区别 / 说一下防抖节流定义,写其中一个函数 / 如果防抖在首次触发怎么写
防抖:函数不会直接执行,会等待一段时间才执行,这段时间内如果再次执行,则会重置前端函数,让最新的函数执行,知道时间满足无新的函数执行。比较适合用户交互的场景,比如输入框输入
**节流:设置一个时间段,在这个时间段内函数只会执行一次。当函数执行时这个等待时间得到重置。节流适合用在高频场景例如滚动事件。
**
javascript
Function.prototype.debounce = function (fn, delay) {
let timer = null
return function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
Function.prototype.throllte = function (fn, delay) {
let timer = 0
return function (...args) {
const t = new Date().getTime()
if (t - timer < delay) return
fn.apply(this, args)
timer = t
}
}
// func.call(obj, arg1, arg2, arg3)
// func.apply(obj, [arg1, arg2, arg3])
// ...arg => [1, 2, 4]
- 如果有多个异步函数,怎么串行执行?回答 async/await / 如果不使用 async/await 怎么实现?写一下 / 如何处理多个
.then
导致代码一大坨很丑怎么解决?(回答了用async/await
)
javascript
const functions = [func1, func2]; // 支持任意数量的异步函数
functions.reduce((prevPromise, currentFunc) => {
return prevPromise.then(currentFunc);
}, Promise.resolve()) // 初始 Promise
.catch(error => console.error("Error:", error));
- 能说说你对
Promise
的理解吗?
**三种状态:
****Pending
****Fulfilled
****Rejected
****不可变性:状态一旦确定不会改变
**链式调用:可以同then,catch 链式调用 generator
函数和async
函数有什么区别Promise.all
如果有报错,是在then
还是catch
接收数据?接收的是什么样的数据?
javascript
Promise.all([
Promise.resolve(1),
Promise.reject(2),
Promise.resolve(3),
])
.then((res) => console.log('then...', res))
.catch((err) => console.log('catch...', err))
Promise.allSettled([
Promise.resolve(1),
Promise.reject(2),
Promise.resolve(3),
])
.then((res) => console.log('then...', res))
.catch((err) => console.log('catch...', err))
Promise.race([
Promise.reject(2),
Promise.resolve(1),
Promise.resolve(3),
])
.then((res) => console.log('race then...', res))
.catch((err) => console.log('race catch...', err))
打印如下:
then... [
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 2 },
{ status: 'fulfilled', value: 3 }
]
catch... 2
race catch... 2
- promise其中一个then报错,中间的then会执行吗?catch后面的then会执行吗
不会 , 会 - script标签,defer 和 async 什么区别? / async 和 defer 区别,使用 async 需要注意什么? 那么 Webpack 是如何解决这个问题的?
**答:区别简单说就是 async / defer都可以异步加载资源。
****但是async会在加载完成以后立刻执行,所以有可能阻塞dom树的构建
**而defer会等待渲染树完成以后执行,不会阻塞html渲染。 async/await
和Promise
什么区别? /async/await
怎么实现的?
**区别:
****promise在创建以后代码会继续同步执行,当Promise被解析或者拒绝时,附加的回调函数添加到微任务队列中,等待主线程调用执行
****Async / await中的 await关键字会导致Javascript引擎暂停async函数的执行,直到promise被解析或者拒绝。当async函数等待Promise解析时,它不会阻止调用栈,并且可以执行其他同步代码。
****promise解决了传统callback函数导致的地狱回调问题。aysnc/awiat则是异步代码看起来像同步代码更加简洁。
**async/await实现:
javascript
async function example() {
const res = await somePromise
console.log(res)
}
function example() {
return spawn(function *() {
const result = yield somePromise
console.log(result)
})
}
function spawn(gen) {
return new Promise((resolve, reject) => {
const g = gen()
const step = (nextFn) => {
try {
const { value, done } = nextFn()
if (done) return resolve(value)
Promise.resolve(value)
.then(
(v) => step(g.next(v)),
(e) => step(g.throw(e))
)
} catch (error) {
reject(error)
}
}
step(() => g.next())
})
}
- 异步的一些 API,比如
setTimeout
,setInterval
,requestIdleCallback
和requestAnimationFrame
还有Promise
,这几个有什么区别?
答: setTimeout, setInterval 是宏任务,用来延迟执行某个函数或者方法。setInterval会一直重复执行,执行时间过长会有间隔不准确的情况。requestAnmationFrame是和浏览器渲染周期下相关,通常用于动画,保证每次重绘前执行,避免卡顿。requestIdleCallback是浏览器空闲时间时会执行。适合处理不紧急的任务,比如日志上报等。Promise是微任务,上面大部分是宏任务,会在下个宏任务执行前执行完所有微任务。
- 数据获取是用的哪个 API? 回答 Axios。Axios 中有做哪些全局处理吗?
答: - 说一下link和script标签的加载过程中会对DOM树构建有什么影响?
**答: 这个设计到浏览器的渲染机制,浏览器在渲染HTML文档时,会变下载边解析,遇到没有defer和async属性的Script标签时会停止解析等待script脚本下载完成执行完成。这是因为脚本可能修改dom,如果script标签带了async或者defer标签,async 会下载完成以后立刻执行,同样会阻塞页面的渲染,如果是defer标签,下载完成以后等到页面解析完成才执行,不会阻塞DOM渲染。
**对于link标签,通常是用于加载外部css,css文件下载是和解析并行执行,构建CSSDOM也是在DOM构建完成以后进行一般不会阻塞页面。有种情况是当link标签后面跟了script标签时,由于script标签可能会访问css样式信息,所有要等css下载完成,script标签执行得到阻塞,DOM构建也被阻塞。 - JS的基本数据类型。
基本类型: undefined、null、boolean、number、string、symbol、bigint
使用Object.prototype.toString.call()进行判断:
javascript
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(42); // [object Number]
Object.prototype.toString.call("hello"); // [object String]
Object.prototype.toString.call(Symbol()); // [object Symbol]
Object.prototype.toString.call(123n); // [object BigInt]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(new Date()); // [object Date]
value === null; // 直接全等判断
Array.isArray([]); // true
Number.isNaN(NaN); // true(注意:NaN !== NaN)
还有typeOf和 instanceof
- 怎么理解值类型和引用类型
答: **值类型有:undefined、null、boolean、number、string、symbol、bigint
****值类型存储在栈内存中,赋值时是赋值值的副本。引用类型值是存储在堆内存中,变量保存的是内存地址。
****值类型传值,引用类型传址;
**修改引用类型,处处跟着变! - CommonJS 和 ES Module 有什么区别? / CommonJS 和 ES Module 有什么区别 / 为什么 ES Module 需要把 import 放在顶部,CommonJS 不需要? / 说一下 CommonJS 和 ES Module 的差异。
**区别:
****加载方式: commonjs 运行时动态加载,es module编译时静态解析
****导出值类型: commonjs是值的拷贝,es module是值的引用
****顶层this指向:commonjs指向当前exports对象,es module是undefined
****处理循环依赖处理: commonjs可能得到未导出模块, 静态分析保证引用完整性
****require可以放在代码任何位置,import必须位于模块顶层
**为什么放在顶部?ESM设计目标之一就是静态分析,引擎可以确定所有依赖关系从而可以进行优化(tree-shaking)。另外一点就是解析阶段建立引用链接,形成图谱,确保循环引用的问题。 - 0.1+0.2是否等于0.3 / 0.1 + 0.2 是否等于 0.3,如何解决?
因为0.1 和 0.2 转二进制以后相加 的数是一个重复无限循环二级制数,转成十进制会丢失精度导致不会0.3. - ES6 新增了哪些数据类型,说一下用法
Symbol
ini
const sym1 = Symbol('key');
const sym2 = Symbol('key');
console.log(sym1 === sym2); // false
Bigint
ini
const big1 = 123456789012345678901234567890n;
const big2 = BigInt("12345678901234567890");
- JS 里面如果实现拖拽的功能。
javascript
const dragElement = document.getElementById('dragElement');
dragElement.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', e.target.id); // 保存数据
});
// 放置区域
const dropZone = document.getElementById('dropZone');
dropZone.addEventListener('dragover', (e) => {
e.preventDefault(); // 必须阻止默认行为
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
const id = e.dataTransfer.getData('text/plain');
const draggedElement = document.getElementById(id);
e.target.appendChild(draggedElement); // 将元素移动到放置区域
});
-
CORS 如何实现跨域?
**答:CORS是指跨域资源共享,它是通过指定服务器返回特定的头来实现跨域请求。
****基本流程是当浏览器检测到是跨域请求时,会附加Origin头(表明请求头),根据服务器返回的CORS头来决定是否允许跨域。
**当非简单请求时,会先发一个OPTIONS预请求,来确认服务器是否支持实际的请求。
-
简单请求和非简单请求的区别。
**答:从触发条件、流程、请求头等对比
****触发条件区别:
****简单请求触发的条件:特定的方法,比如GET POST HEAD,简单的头。
****复杂请求触发条件: 其他方法(PUT, DELETE, PATCH), 复杂的头,自定义头等。
****简单请求会直接发送真实的请求, 复杂请求会先发送OPTIONS预检请求,确认后再发送真实的请求
****仅允许简单头:
**
Accept
、Accept-Language
、Content-Language
、Content-Type
(仅限text/plain
、multipart/form-data
、application/x-www-form-urlencoded
**)****复杂请求头:
**包含自定义头(如
Authorization
)或非简单Content-Type
(如application/json
) -
如何处理跨域? iframe中,外面如何获取里面的真实高度?跨 iframe 通信是否了解过?
**CORS, JSONP,代理服务器
****iframe + domain浏览器已经弃用,存在安全性问题
****iframe + postmessage
**获取子iframe 都可以通过postmessage + 父message事件完成。
-
同一个请求发送多次,如何保证获取的是最后一次的结果
**取消前面的请求(推荐)
**序号法标记
scss
let controller = null;
async function sendRequest() {
// 取消之前的请求
if (controller) controller.abort();
// 创建新的 AbortController
controller = new AbortController();
try {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal,
});
const data = await response.json();
// 处理最终结果
console.log('Latest data:', data);
} catch (error) {
// 忽略被中止的请求错误
if (error.name !== 'AbortError') {
console.error('Request failed:', error);
}
}
}
// axios的取消
if (cancelTokenSource) cancelTokenSource.cancel();
// 创建新的 CancelToken
cancelTokenSource = axios.CancelToken.source();
// 结合react
useEffect(() => {
// 创建新的 AbortController
controller = new AbortController();
return () => {
// 取消请求
controller.abort();
}
}, [])
-
浏览器内存泄漏是否可以通过开发者工具看到,如何看到?
**查看内存是否持续增长且只高不降
**打开 Performance,点击memory,点击录制一段时间结束即可
-
JS 严格模式有什么了解?有什么不一样的地方?
-
有什么方法可以改变
this
指针 / this 有哪几种指向**隐式绑定 : obj.method()
****显式绑定: apply, call, bind
****new
**箭头函数
-
数组操作
map
和forEach
有什么区别,是否可以打断循环?**map会返回一个新数组,forEach不会返回值
****map适用于基于原数组生成新数组的情况,foreach只是执行数组每一项
****都不能终止整个循环,foreach的return只会终止当前的循环,map每个元素被处理也无法终止。
**foreach性能略好一点,不用开辟新的内存。
-
filter
和find
返回值有什么区别?
filter返回数组 find返回item -
十万个数组取第1万和第6万个元素速度有什么区别吗?为什么?
**时间在同一级别,基本没啥区别
****获取时间复杂度O(1)
****问元素时,直接通过索引计算内存偏移量,一步定位到地址:
**内存地址 = 基地址 + 索引 X 元素大小
-
数组的
sort
默认是按什么排序的?使用的什么算法**以前是二分,现在是TimSort排序
**TimSort排序大概是:分块处理,合并策略,插入排序优化
-
ES6 中的
Set
和Map
有什么区别?可以遍历吗,如何遍历?**数据结构:set是值的集合(类似数组), map是键值对的集合(类似对象)
****set集合的值唯一不会重复,map的键可以是任意类型( 数组,函数)
**都可以用for ...of, foreach遍历
-
真实数组和伪数组有什么区别?
类型不同,都有length属性,类数组是一个含有length属性的对象 -
WeakSet
和WeakMap
有什么区别?是否可以遍历?
WeakSet值必须是对象,WeakMap键必须是对象,其都是对值的弱引用(无其他对象引用会被回收)
**WeakSet支持方法:add()
,**has()
,delete()
******WeakMap支持方法:get, set ,has ,delete
**都不可遍历,无引用时都会自动被回收,不可以遍历
-
订阅发布和观察者模式有什么区别
**观察者模式耦合度高,目标直接通知观察者,Vue响应系统就是
**订阅者发布模式耦合度低,发布者向事件中心发布,Vue的EventBus
-
单例模式和工厂函数有什么区别|
答: **单例模式:确保仅有一个实例
**工厂模式:封装对象创建过程,根据输入返回不同实例。
-
说一下你用过 meta 标签
ini
// 声明字符编码
<meta charset="UTF-8">
// 视口设置(移动端必选)
<meta name="viewport" content="width=device-width, initial-scale=1.0">
// 页面描述 SEO优化
<meta name="description" content="这是一个关于前端技术的教程网站">
// 关键词
<meta name="keywords" content="HTML,CSS,Javascript,前端开发">
// robots
<meta name="robot" content="noindex, nofollow">
// 主题颜色
<meta name="theme-color" content="#00000" media="(prefers-color-scheme:dark)">
// 作者
<meta name="author" content="Johb Doe">
// 自动刷新
<meta http-equiv="refresh" content="https://example.com" >
- 如果想实现一个吸顶功能怎么实现,回答
position: sticky;
,那么不用 CSS 怎么实现。
ini
const header = document.getElementById('stickyHeader');
const placeholder = document.createElement('div');
let isSticky = false;
function checkSticky() {
const headerTop = header.getBoundingClientRect().top;
if (headerTop <= 0 && !isSticky) {
header.classList.add('fixed');
placeholder.style.height = header.offsetHeight + 'px';
header.parentNode.insertBefore(placeholder, header);
isSticky = true;
} else if (headerTop > 0 && isSticky) {
header.classList.remove('fixed');
placeholder.remove();
isSticky = false;
}
}
window.addEventListener('scroll', throttle(checkSticky, 100));
- JS 数组去重怎么实现。
**[...new Set(arr)]
****filter
**reduce
TypeScript
- 有了解过 TypeScript 吗,讲一下泛型? / 讲一讲 TS 泛型
泛型可以从名字去理解,指可以使用广泛的类型。ts中的泛型一般可以用创建可复用、类型安全灵活的组件的特性。可以在定义函数,接口,类的时候不指定具体类型,在使用的时候才指定具体的类型。
csharp
// 函数泛型
function identity<T>(items: T[]): T[] {
retrun items.reverse()
}
// 类泛型
class Box<T> {
private content: T
constructor(value: T) {
this.content = value
}
}
// 接口泛型
interface KeyValuePair(K, V) {
key: K;
value: V
}
泛型约束:
通过extends限制类型范围
typescript
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(obj: T): void {}
- infer 关键字。
infer功能可以提取前面出现过的类型。可以理解为占位。
typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Result = ReturnType<() => number;>; // number
返回类型被占位了,传入的函数返回值是numebr,所以提取最后类型是number
typescript
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type Params = Parameters<(a: string, b: number) => void;>; // [string, number]
**同理,传入的函数参数类型有string和number,infer占位了传入参数类型,所以最后提取出[string,number]
**
- type和interface的区别
答:在定义一个对象类型且没有任何操作的时候两者皆可以。但是如果在定义类class时或者需要进行继承/实现 (extends / implements)的时候,或者是声明合并的时候优先使用interface
typescript
// 定义类
interface Animal {
name: string;
}
// 继承
interface Bear extends Animal {
honey: boolean;
}
class PolarBear implements Bear {
name = "Ice";
honey = true;
}
interface User { name: string; }
interface User { age: number; }
// 合并为 { name: string; age: number; }
定义非对象类型的时候,比如联合类型,元数组,函数类型, 还有一些操作比如typeOf, in, keyOf等优先使用type
typescript
type Result = Success | Failure; // 联合类型
type Pair = [string, number]; // 元组
type Handler = () => Promise<void>; // 函数类型
type Keys = keyof SomeObject; // 获取键的联合类型
type ID = string | number;
void
和never
区别。
**两者都可以表示函数没有返回值,但是void是表示函数没有return 或者返回undefined
**never则是函数永远不会执行完成,或者正常执行
javascript
// void
function logMessage(): void {
console.log("Hello");
// 没有 return,或 return undefined
}
// never
function throwError(): never {
throw new Error("Something failed");
}
function infiniteLoop(): never {
while (true) {}
}
- 是否用过 TS,用到什么功能
这些题你都可以说 any
和unknow
的区别 / TS 中 any 和 unknow 的区别
**两者都是顶级类型,any和unknow都代表不确定的类型
****但是any可以绕过TypeScript的类型检测,可以赋值给任何值也可以任何类型值复制给any,运行直接操作(调用方法,访问内部属性等)而引发错误。
**unknow则会类型检测,操作/使用前必须类型断言,否则会抛出错误
ini
let value: any = "hello";
value.toFixed(2); // 编译通过,但运行时报错(字符串没有 toFixed 方法)
let value: unknown = "hello";
value.toFixed(2); // 编译报错:Object is of type 'unknown'
// 正确用法:需显式类型断言
if (typeof value === "number") {
value.toFixed(2); // 安全
}
也就是说你是any你可以任意操作,你是unknow,不知道你是不是number,所以不能赋值、调用函数等操作
- 定义了一个 interface 只用到之前的 interface 的某几个字段则怎么办
**pick
**也可以用omit 排除不是的属性
typescript
interface UserObj {
readonly name: string;
age: number;
id: number;
sex: 0 | 1;
address: string;
weight: number;
}
// 使用 Pick 来创建新类型
type Person = Pick<UserObj, "name" | "id">;
// 现在 Person 类型等同于:
interface Person {
readonly name: string;
id: number;
}
// omit
interface Person {
name: string;
age: number;
address: string;
}
type WithoutAddress = Omit<Person, "address">; // { name: string; age: number }
- Pick 怎么实现,写一下
scala
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
- omit怎么实现的
omit是通过TypeScript的内置对象Exclued和pick实现的
scala
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
- TS中,如果声明了一个对象,又声明了一个对象和之前的对象大部分key是相同的,怎么做。
typescript
interface Original {
id: string;
name: string;
age: number;
}
type PickObject = Pick<Original, 'id' | 'name'>
// interface
interface ExtendedObject extends PickObject {
other: string
}
// type
type ExtendedObject = PickObject & {
other: string
}
React
- React Router 如何实现权限拦截?
可以分为路由级,组件级,API级
-
- 高阶组件的拦截
- 自定义路由组件****
- react的context封装组件拦截
ini
// src/contexts/auth-context.tsx
import React, { createContext, useContext, useMemo, useState } from 'react';
type UserRole = 'admin' | 'editor' | 'viewer' | null;
type Permissions = {
viewDashboard: boolean;
editContent: boolean;
deleteContent: boolean;
manageUsers: boolean;
};
type AuthContextType = {
user: string | null;
role: UserRole;
permissions: Permissions;
login: (username: string, role: UserRole) => void;
logout: () => void;
};
const AuthContext = createContext<AuthContextType>({
user: null,
role: null,
permissions: {
viewDashboard: false,
editContent: false,
deleteContent: false,
manageUsers: false
},
login: () => {},
logout: () => {}
});
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<string | null>(null);
const [role, setRole] = useState<UserRole>(null);
// 动态计算权限
const permissions = useMemo<Permissions>(() => ({
viewDashboard: !!role,
editContent: role === 'admin' || role === 'editor',
deleteContent: role === 'admin',
manageUsers: role === 'admin'
}), [role]);
const login = (username: string, newRole: UserRole) => {
setUser(username);
setRole(newRole);
};
const logout = () => {
setUser(null);
setRole(null);
};
return (
<AuthContext.Provider value={{ user, role, permissions, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
typescript
// src/components/route-guard.tsx
import { useAuth } from '../contexts/auth-context';
import { Navigate, Outlet } from 'react-router-dom';
type RouteGuardProps = {
requireLogin?: boolean;
requiredPermissions?: Array<keyof Permissions>;
};
export const RouteGuard = ({
requireLogin = true,
requiredPermissions = []
}: RouteGuardProps) => {
const { user, permissions } = useAuth();
if (requireLogin && !user) {
return <Navigate to="/login" replace />;
}
const hasPermission = requiredPermissions.every(
perm => permissions[perm]
);
if (!hasPermission) {
return <Navigate to="/403" replace />;
}
return <Outlet />;
};
<AuthProvider>
<Routes>
{/* 公开路由 */}
<Route path="/login" element={<LoginPage />} />
<Route path="/403" element={<ForbiddenPage />} />
{/* 需要登录的路由 */}
<Route element={<RouteGuard requireLogin />}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
{/* 需要管理权限的路由 */}
<Route element={<RouteGuard requiredPermissions={['manageUsers']} />}>
<Route path="/admin/users" element={<UserManagement />} />
</Route>
{/* 需要编辑权限的路由 */}
<Route element={<RouteGuard requiredPermissions={['editContent']} />}>
<Route path="/content/edit" element={<ContentEditor />} />
</Route>
</Routes>
</AuthProvider>
javascript
// src/hocs/with-permission.tsx
import { useAuth } from '../contexts/auth-context';
import { ReactNode } from 'react';
export const withPermission = (
requiredPermissions: Array<keyof Permissions>,
FallbackComponent: ReactNode = null
) => {
return ({ children }: { children: ReactNode }) => {
const { permissions } = useAuth();
const hasPermission = requiredPermissions.every(
perm => permissions[perm]
);
return hasPermission ? children : FallbackComponent;
};
};
// 使用示例
const AdminPanel = withPermission(['manageUsers'])(() => (
<div>管理员面板</div>
));
- 你提到了 React Context,说一下它的原理。
ini
const MyContext = React.createContext(defaultValue);
- **创建的组件包含Provider和Consumer组件
**在Provider组件提供Context值,并将属性value存储到fiber节点的context属性中
xml
<MyContext.Provider value={currentValue}>
{children}
</MyContext.Provider>
-
当使用useContext或者Context.consumer时,React会把该组件订阅到provider中,内部的
readContext
去执行的逻辑操作 -
当Priovde的值发生变化,React会触发更新流程,重新渲染订阅了该Context的组件。
-
怎么理解 React 中的受控组件和非受控组件?在平时怎么写一个组件?
**受控组件是:将数据赋值给state交给react控制,符合单向数据流
**非受控组件:用ref获取组件数据,数据有DOM自身管理
-
你是怎么理解 JSX 的?
用js写html -
React 组件是怎么渲染为 DOM 元素到页面上的 / React 中
setState
调用以后会经历哪些流程? -
为什么setdata要设计成异步的
合并多次更新,保持状态一致性,支持并发模式 -
如何进行数据管理?
-
redux内的状态是怎么传到组件内部的
**1. react-redux的Proivder组件包裹最外层,将Store注册到整个应用中
**2. 使用connect高阶组件
-
能说一下 Redux 的原理吗?
-
使用过哪些 React Hooks?
-
useState
是怎么实现的? -
说一下 React 中的 Diff 算法。
-
说一个 Fiber 架构。
React 中 Fiber 有了解过吗
对 React 的 Fiber 架构有什么了解吗?中间的节点怎么通过链表连接起来
-
React 的 HOC 是什么
高阶组件 -
React 里面有一个 A 组件,里面包裹了 B 子组件,A 组件是 10 个属性,B 只引用了其中两三个,如何提升性能
-
PureComponent
-
useCallback 和 useMemo 有什么区别 / useMemo,useCallback,useRef 的作
-
hooks 可以放在函数的内部吗?为什么
-
对 React Hooks 的理解
-
useEffect 的返回值有什么作用,执行时机
-
有开发过高阶组件吗?它的使用场景是什么?
**高阶组件是指一个函数组件,接受一个组件作为参数,返回一个新的组件,用于复用逻辑。
**场景:鉴权 ,见第一个问题
kotlin
const withAuth = (WrappedComponent) => {
return (props) => {
if (!isLoggedIn()) return <Redirect to="/login" />;
return <WrappedComponent {...props} />;
};
};
const withData = (WrappedComponent, fetchData) => {
return class extends React.Component {
state = { data: null };
async componentDidMount() {
const data = await fetchData();
this.setState({ data });
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
};
- 父子组件渲染声明周期和更新生命周期的顺序
**声明周期说的是class组件,因为函数组件没有生命周期说法。
****挂载时声明周期有:
********constructor -> getDerivedStateFromProps -> render -> componentDidMount
****更新时候声明周期有:
****getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componetDidUpdate
vue 和 react 父子组件的生命周期顺序,都是以render为分界线,类似洋葱模型,因为render会触发子组件执行,父组件挂载完成的时候,子组件也会完成挂载:
****挂载:
****父constructor → 父render → 子constructor → 子render → 子componentDidMount → 父componentDidMount
****更新:
**父getDerivedStateFromProps → 父shouldComponentUpdate → 父render → 子getDerivedStateFromProps → 子shouldComponentUpdate → 子render → 子getSnapshotBeforeUpdate → 父getSnapshotBeforeUpdate → 子componentDidUpdate → 父componentDidUpdate。 - 什么是纯函数什么是副作用函数
Vue
- 介绍一下Vue的生命周期 / 父子执行规律
**答:beforeCreated: 有this, 但是没有数据,方法等, Created: 有this, 有数据方法等,没有el, beforeMounted: 调用render生成了虚拟dom,但是没有真实的dom, Mounted: , beforeUnmounted(beforeDestroy): 卸载之前,此时实例仍然可以访问,用于释放内存,定时器,取消请求,解绑事件等操作, Unmounted(Destroy): 实例销毁,监听器被移除
****beforeUpdate:数据变化之前,打补丁之前, Updated: 虚拟dom重新渲染并且应用之后
**规律: 类似洋葱模型 - 组件销毁的时候一般会进行什么样的操作
答:清除定时器,取消网络请求,消除dom引用,移除事件监听器,第三方库的销毁,框架本身的操作比如解除数据绑定,销毁观察者 - Vue编写时有哪些优化手段 / 是否做过 Vue 相关的性能优化
**答:数据优化: Object.freeze(), 合理使用computed()
****组件优化: 懒加载() => import(), keep-alive, 函数组件
其他: 图片懒加载v-lazy,事件合理使用防抖截流。v-show / v-if
** - 是否用过 Vue3 的 hooks
ini
import { ref } from 'vue';
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
const loading = ref(false);
const fetchData = async () => {
try {
loading.value = true;
const response = await fetch(url);
data.value = await response.json();
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
};
return { data, error, loading, fetchData };
}
-
Vue2 中的 mixin 是否了解,原理是什么?
答:初始化的时候合并data, 生命期钩子: 全局mixin -> 组件mixin -> 组件钩子 -
在响应式上有什么区别?如何拦截 for...in / Proxy 和 Object.defineProperty 区别
**答:vue2使用的是Object.defineProperty实现的,它只能劫持现有的属性,对于新增的属性必须用Vue.set添加才具有响应式。对于数组直接修改数组元素和修改其长度时无法触发更新,必须通过重写的的方法进行修改(push, pop, unshift, shift, splice, sclie, concat)。
**vue3使用proxy实现的响应式,它是对整个对象进行代理的,可以监听到属性的删除和修改。数组可以监听下标或者长度的改变。
-
Vue2 和 Vue3 有哪些区别
Vue2 和 Vue3 的区别
Vue2 和 Vue3 的区别,如何考虑使用哪一个
Vue3 和 Vue2 的本质区别 / Vue3 和 Vue2 有什么区别?怎么把 Vue2 项目升级到 Vue3
Vue2 和 Vue3 响应式的性能有区别吗?为什么
介绍一下 Vue3 的新特性,相对 Vue2 来说
defineProperty 有什么问题?
Vue3 有什么其他的优化,比如静态节点的优化?
**答:响应式系统,组合api,性能优化,TypeScript支持,新特性: Fragment, Teleport, Suspense。
****tree-shaking的支持,允许工具移除没有使用的功能,如v-model,体积约为10kb。
****底层diff算法优化
**编译与性能优化: 静态节点提升,补丁标记,块状树
-
Vue2 和 Vue3 的 diff 用了什么算法库
2.0 和 3.0 算法差异速度提高多少有了解过吗
详细说一下 Vue2 和 Vue3 diff 算法的详细过程。这两个算法的复杂度
在 Diff 过程中怎么做的,Diff 算法有了解过吗
通过下标指定 key 的话会有什么问题 / 这个问题出现在 diff 算法的哪个节点
**答:vue2 和 vue3 是通过自研实现的diff算法。
**其中vue2的算法是参考 **Snabbdom改进而来。
****vue2的算法是双端比较算法(双指针算法),通过四个指针同时从新旧节点的2端向中间遍历,经可能复用多的节点。
****vue3是通过跳过相同的前缀和后缀,通过map记录可以复用的旧节点,记住需要移动的位置。然后使用最长递增子序列确定需要最少移动的dom次数
****vue优化点: 编译时标记静态节点,跳过diff。标记动态绑定的属性,仅更新变化部分。以块为范围跟踪动态节点,减少遍历范围。
**vue3比vue2 diff快了1.3 ~2 倍。减少50 - 70%的dom对比。跳过90%+的静态节点比较
-
Vue 双向绑定原理 / 介绍一下 Vue 的主要原理
**答:聊浅一点就是:
****vue2在初始化的时候,内部会执行initData函数,将数据属性经过Object.defineProperty处理转化成getter 和 setter。并且每一个属性有一个Dep(依赖管理)实例,用于存储所有的Watcher(如渲染Watcher)。当组件初始化时,会访问data所有属性,触发getter,收集该属性的所有的Watcher到dep中。当数据修改时触发setter。dep通知所有watcher进行更新。
****聊深一点:
****在我们修改数据时,为什么组件中的computed值会变,watcher方法会变,template中的值会重新渲染呢,是因为全部收集到了dep的watcher实例中。
****(可以给出自己思考)在看这部分源码时候,思考过为什么需要加入watcher实例这一层呢,为什么不可以直接把所有更新方法加入到dep中,等触发setter时候直接调用即可呢?
****回答: 因为watcher实例会提供异步批量更新调度机制,通过watch.id 做到去重,避免重复调用,通过watch.id 做到排序,确保父组件优先于组件更新,用户自身watcher函数优先于渲染watcher(updateComponent),然后再通过nextTick下个事件循环批量更新
****(聊优化)所以我们在写组件的时候,尽量避免不必要的data出现在template减少依赖收集,多使用computed减少计算。
****vue3:
在初始化的时候,把被ref或者reactive包裹的数据进行响应式处理,ref 如果是基本类型会包裹为{ value: ... }形式,最终都是经过reative函数处理成响应式。处理后会进行proxy代理,有getter和setter操作。并且仅在访问对象时创建响应式代理,避免了递归初始化带来的性能消耗。在getter时会调用track来收集依赖,在修改时触发setter通过trigger来更新依赖。**
scss
function ref(value) {
const wrapper = {
get value() {
track(wrapper, 'value'); // 收集依赖
return isObject(value) ? reactive(value) : value;
},
set value(newVal) {
value = newVal;
trigger(wrapper, 'value'); // 触发更新
}
};
return wrapper;
}
-
Proxy 和 Reflect 有什么关系
**答:没有直接关系,只是Proxy的handle参数和Reflect一样的。vue3使用Reflect有几个好处:
****1. Reflect执行有返回结果
****2. Reflect执行报错不会影响后续代码执行
**3. receiver参数具有不可替代性 。Reflect的receiver配合Proxy能更准确的获取目标对象的值而非代理的值
-
Vue3 的 hook 和 React 的差别
React hooks 和 Vue3 的 compose api 有什么区别
**答: React的Hook是根据初始化时候形成的链表顺序来确定副作用的来源的,所以给出了以下限制:
****a. 不能在循环,条件,嵌套函数中调用HOOK
****b. 必须保证hook在顶层被调用
****c. useEffect等必须手动确定依赖关系
****而Compose Api是基于vue响应式系统实现的,与react hook相比
****a. 可以在条件, 循环, 嵌套函数中实现
****b. react hook需要手动触发更新,compose api可以自动依赖更新。
**c. vue 是定向更新,react更新则是全量对比,GC比vue压力更大
-
处理一个对象 a.b. 是怎么处理的, Proxy 是怎么处理的。React 没有响应式,是怎么实现的 / Vue 或 React 中我们连续三次赋值,比如
this.a
,会怎么实现 -
KeepAlive 有用过吗?实现机制 / 如果一个组件既在 exclude 又在 include 会被缓存吗?为什么这么设计
**答: keeplive实现原理: 获取组件vnode和key。vue2使用keys缓存组件的key,cache来缓存key对应的组件。当缓存组件个数大于max时,使用LRU策略来移除最近未使用的组件。
****vue3中使用keys数据结构为set, cache数据结构为map
**当组件同时存在于exclude和include时,组件不会被缓存。因为exclude优先级高于include。这样设计更灵活,可以在包括诸多组件中排除特别的组件。
-
Vue 的
nextTick
是用来做什么的?它的原理是什么 / nextTick 实现原理**答:用于dom更新之后执行延迟回调。
****原理是用:promise如果不支持使用MuationObserver, setImmedite, 最后是Settimeout.
**vue3只用了promise,并没有做降级处理。
javascript
const callbacks = []
let pending = false
function flushCallbacks() {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc
if (typeof Promise !== 'undefined') {
const p = Promise.resolve()
timerFunc = () => p.then(flushCallbacks)
} else if (typeof MutationObserver !== 'undefined') {
// 降级处理...
} else {
timerFunc = () => setTimeout(flushCallbacks, 0)
}
export function nextTick(cb, ctx) {
callbacks.push(() => { cb.call(ctx) })
if (!pending) {
pending = true
timerFunc()
}
}
- Vue 中
$set
是做什么的。写一个$set
的伪代码(写代码的时候用了Object.create(null)
,问了一下它是做什么的)
**答:当对象添加一个新的属性的时候,直接赋值的话,Vue无法检测到这个属性的变化,即没有响应式。使用$set可以确保该属性具有响应式,修改这个属性的值会触发试图更新
**伪代码:
vbnet
function $set(targe, key, value) {
// 数组 不管是否是响应式数据 直接用重写方法修改数据
if (Array.isArray(targe) && typeof key === 'number') {
return targe.splice(key, 1, value)
}
if (key in targe) {
target[key] = value
return
}
// 是否是响应式
const ob = target.__ob__
// 如果不是响应式 直接修改数据
if (!ob) {
target[key] = value
return
}
defineReactive(toolbar.value, key, value)
ob.dep.notify()
}
Object.create(null)创建的对象没有原型链
-
Vue 中的数据状态管理用的什么?Vuex 的作用和用法 / pinia的作用和用法|
**vue2 + Vuex
****vue3 + pina
****Vuex的作用:
****集中式管理状态,提供全局的store, 管理跨组件的共享状态。
****数据流的规范,通过state、mutation、actions确保状态变更的可预测性
****模块化:通过modules拆分状态逻辑
****Pinia的作用:
****代替Vuex,支持Compiose api,支持Typescript,模块化,简化api
****用法:
javascript
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state) { state.count++ }
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => commit('increment'), 1000)
}
},
getters: {
doubleCount: state => state.count * 2
}
})
export default store
javascript
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() { this.count++ },
async incrementAsync() {
setTimeout(() => this.increment(), 1000)
}
},
getters: {
doubleCount: (state) => state.count * 2
},
})
// 使用composition API模式定义store
export const useCounterStoreForSetup = defineStore('counterForSetup', () => {
const count = ref<number>(1);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
return { count, doubleCount, increment };
});
- Vue.extend 有用过吗?什么原理
Vue.extend返回vue的子类,需要进行实例化。实例化以后就是该组件(传入extend的参数)的实例,需要进行挂载。将该实例放在Vue原型链上可以全局调用该实例上的方法。
php
// 创建构造器
const MyComponent = Vue.extend({
template: '<div>{{ message }}</div>',
data() {
return { message: 'Hello Vue!' };
}
});
// 实例化并挂载
const instance = new MyComponent().$mount('#app');
原理:
ini
Vue.extend = function (extendOptions) {
const Super = this; // Vue 基类
const Sub = function (options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
// 合并选项(如 mixins、全局组件等)
Sub.options = mergeOptions(Super.options, extendOptions);
return Sub;
};
- Vue2 里面 data 为什么是一个 function
答: 避免组件被多处引用data是引用同一个地址,导致数据混乱,使用function 可以每次生成不一样的地址引用 - Vue Router 的原理,hash 和 history 有什么区别 / 前端路由的实现方式 / Vue 的路由模式哪几种,实现原理。history 服务端需要做哪些配置
**hash变化不会触发浏览器请求,路由完全由前端控制。通过window.onhashchange 监听哈希变化,触发组件渲染。内部通过维护一个历史栈,通过push和replace方法更新路由。hash模式不利于seo。
****history模式url改变会触发浏览器发送请求到后台,需要后端配合配置所有路由返回index.html交给前端控制路由。基于pushState,replaceState和popstate事件。window.location.pathname匹配React路由。
** - 你怎么看 React 和 Vue / 怎么看 Vue 和 React?
**答:(个人理解回答)React没有响应式,靠手动触发视图更新,每次从根节点遍历effect,进行更新。react有fiber架构,双缓存机制,时间分片,任务调度等
**vue具有响应式,颗粒度更细。
工程化
-
Webpack 有做过 Loader 和 Plugin 吗? / Webpack 是否写过 Plugin 和 Loader / 有哪些loader
-
打包规则。首页加载少的js,splitChunks的规则。
-
monorepo相关
-
说一下使用 Webpack 的优化
-
Webpack 和 Vite,Vite 为什么快?
-
Webpack 和 Vite 的构建流程有什么差异
Webpack 和 Vite 热更新有什么区别
Webpack 和 Vite 热更新区别
Webpack 和 Vite 区别
-
Eslint 代码检查的过程
-
husky 的配置,配置过什么
-
pm2 原理
-
微服务是否有了解过
-
模块联邦有什么优缺点?
-
Webpack 如何实现热更新
-
Babel 中的 stage0,1,2,3 是指什么
-
Webpack 项目中通过 script 标签引入资源,在项目中如何处理
-
果打包100个文件,只有某个文件发生变化,有办法单独做更新吗
-
Tree Shaking 的原理,CommonJS能用吗,Tree Shaking 有什么副作用吗
-
Webpack 和 Vite 的特点和区别
-
Vite 中你们怎么使用的模块联邦 / 有没有什么样式冲突的问题
-
Webpack的初始化流程
-
module,chunk,bundle 是什么意思
-
你可以说一下对 Webpack,Vite,EsBuild,SWC的理解,如何进行选择
-
打包速度慢怎么解决
Node.js
- NodeJS 里面的模块化是什么?
- 对 Node 是否有了解?如何读取一个 20G 大小的文件
- Node 中 master 和 slave 是否了解
Css
- CSS
display
有哪些值、哪些用法?
block, inline, inline-block, flex, inline-flex, grid, inline-flex - 其中
inline
和inline-block
什么区别?
**inline被显示为内联元素,前后没有换行符,设置marign,padding,width,height不生效
****block会显示为块级元素,前后会有换行符
****inline-block是上面2者结合,前后没有换行符,设置margin,padding....会生效
****设置外部类型: inline 、block
**内部显示类型: flex、grid - 介绍一下 flex 布局 / flex 都有什么相关属性?flex 居中怎么实现?/ flex 实现两个元素垂直方向居中怎么实现?
**display:flex设置容器内部子元素为flex布局
****作用在上面的属性有:
****flex-direction
****flex-wrap: 如何换行 wrap nowrap wrap-reverse
****flex-flow: 上面属性结合所以可以忽略
****justify-content: 主轴对齐方式 (主轴居中center)
****align-items: 交叉轴对齐方式 (交叉轴居中center)
****align-content: 与交叉轴对齐方式 flex-start flex-end center space-between space-around stretch
****作用在项目上的属性:
****order: 子元素排列顺序
****flex-grow: 子元素占据空间 默认0 不放大 都设置为1 等分剩余空间(grow: 扩大, 有剩余空间起作用)
****flex-shrink: 子元素缩小比例 默认是1 不缩放 (shrink: 缩小, 剩余空间不足起作用)
****flex-basis: 子元素占据的尺寸 默认auto 自身尺寸 (basis: 基础)
**flex: 前面三个的结合 - flex: 1 代表什么?
flex-basis
可以设置哪些值,和width
哪个优先级更高? /flex-basis
是做什么的,和width
哪个优先高 /flex:1
的含义
**答: flex: flex-grow flex-shrink flex-basis
****flex: 1 表示 flex-grow: 1 flex-shrink: 1(默认值) flex-basis: 0%()
****flex:1 可以自动分配空间
**flex如果第一个值是没有单位的数值则是flex-grow的值,如果是具体的单位值(px rem em ...)则是basis的值 space-between
和space-around
区别,是否能调整他们间隙的值?
**space-between和space-around都是作用于主轴上的排列方式
****space-between让子元素均匀排列在主轴上,子元素的首位会紧贴父容器边缘。
****space-around也会让子元素均匀排列,每个元素间隙相同,导致中间间隙是首页间隙的2倍
****可以使用gap属性调整间隙,但是有兼容性问题
**也可以使用margin
css
.flex-container {
display: flex;
justify-content: space-between; /* 或 space-around */
gap: 20px; /* 强制项目间固定间隙 */
}
.flex-item {
margin-right: 20px; /* 自定义项目右侧间隙 */
}
align-content
和justify-content
区别?
**justify-content定义了主轴的对齐方式
****align-content定义了多根轴线(交叉轴)对齐方式,一根主线不会生效
**一根轴线使用align-items 多根使用align-content- position 定位的方式。说一下 sticky。
**position定位: static, relative, position, fixed, sticky
**sticky是粘性布局
css
#element {
position: sticky;
top: 0; /* 当页面滚动到这个元素顶部与视口顶部重合时,它将被"粘住" */
}
- less 和 sass 什么区别?
less更接近css,sass更快
less
// less
.border-radius(@radius) {
border-radius: @radius;
}
.box { .border-radius(5px); }
less
// sass 支持参数、默认值、条件判断(@if)和循环(@for, @each),功能更强大。
@mixin border-radius($radius, $color: red) {
border-radius: $radius;
@if $radius > 10px { border-color: $color; }
}
.box { @include border-radius(15px, blue); }
- flex 中如果有剩余元素或者空间不够会怎么办
**有剩余空间会根据flex-basis / width + flex-grow计算出的扩大量进行扩大
**如果空间不够,如果设置换行flex-wrap: wrap会换行,如果不换行,会根据flex-shrink比例进行缩放,如果flex-shrink为0 可能会超出元素
Http
- HTTP 缓存,协商缓存,强缓存。 / 协商缓存如何判断命不命中?/ 和缓存相关的有哪些?强缓存和协商缓存同时存在哪个优先级高
**答: http缓存分强缓存和协商缓存
****首先强缓存主要是本地缓存,不会像服务器发送请求。它的实现方式涉及有Expires 和 Cache-Control这2个http字段。
**Expires是旧的实现方式,格式: Wed, 21 Oct 2025 07:28:00 GMT **,会存在客服端和服务端时间不一致的问题。
****Cache-Control是http 1.1引入的,可以用max-age设置相对值,比如max-age=3600 就是一个小时后过期。Cache-Control有如下字段: max-age,资源在相对时间内有效,no-cache: 跳过强缓存直接进入协商缓存。
****no-store: 禁止任何缓存,包括协商缓存。public: 允许进行代理缓存。private:仅允许客户端进行缓存
****然后就是协商缓存,当强缓存失效以后,客服端会像服务端发送一个请求,验证资源是否可用。这个时候会验证2个字段Last-Modified和Etag。
****Last-Modified是资源的最后修改时间,而Etag是资源的唯一标识。
****Last-Modified会存在精度不够的问题,比如秒级,同一秒多次修改则无法识别。
****Etag问题在于会增加服务器负担,特别是大文件
****策略:
**静态资源(如JS/CSS/图片):
-
- 文件名添加哈希(如
app.a1b2c3.js
),设置Cache-Control: max-age=31536000
(一年)。 - 内容变化后哈希改变,URL不同,自然触发更新。
- 文件名添加哈希(如
-
- HTML文件 :
-
-
- 设置
Cache-Control: no-cache
或max-age=0
,确保及时获取最新版本。
- 设置
-
-
- API请求 :
-
-
- 对数据实时性要求高的接口,使用
Cache-Control: no-store
。****
- 对数据实时性要求高的接口,使用
-
-
HTTP1.1和HTTP2的区别?(我回答提到了哈夫曼编码的方式实现首部压缩)/ HTTP1.1和HTTP2的区别 / HTTP1.1 和 HTTP2 做了什么?队头阻塞有没有了解过,在 HTTP2 中有这个问题吗 / 浏览器对队头阻塞有什么优化?
**答:window.chrome.loadTimes(), npnNegotiatedProtocol 查看http的版本
****HTTP1.1和HTTP2的本质区别在于协议格式的区别导致。
****HTTP1.1协议格式是基于文本,HTTP2.0是基于二进制帧进行传输。这导致处理传输数据方式不一样,HTTP1.1会不断接受一个请求的字节数据,知道遇到clrf分隔符停止,这导致HTTP1.1必须要按顺序去处理每一个请求。如果一个请求时间过长就会导致后面请求卡住,导致队头阻塞问题。
****HTTP2.0则是允许在同一个tcp连接中同时发起多个请求,请求和响应的数据会分解成帧,每一帧都有流标识。这样流可以交错传输,互不干扰。即可多路复用。解决了队头阻塞问题。
****HTTP2.0还进行了头部压缩优化,采用了HPACK算法压缩头部(静态表,动态表,哈夫曼编码进行压缩)
****服务器端还能主动推送消息。还可以对请求进行优先级、流量控制。
**队头阻塞:HTTP2.0只是解决了应用层的阻塞,传输层还是按照顺序进行数据传输存队头阻塞问题。
-
哈夫曼编码的原理。
**答:基于HTTP2的哈夫曼编码原理。哈夫曼编码分动态和静态哈夫曼编码,HTTP2哈夫曼是基于静态哈夫曼编码,即根据预先统计的频率构建一颗树,同时存在编码器和解码器2端。则无需在http中传输。
****然而哈夫曼算法原理就是会根据字符串出现的频率进行统计,然后构建一颗按频率生序排列的队列。重复取出频率最小的2个节点,可以看出是树的左右节点,依次合并成一个根节点,最终可以形成一颗哈夫曼树。
****然后就是分配编码,节点左侧为0 右侧为1, 编码规律就是从根节点到某一个节点的路径就是其编码。
一个字符8位,现在进行编码以后成1位或者3位。到达压缩的效果。**
scss
根(11)
/ \
A(5) 新(6)
/ \
新(4) 新(2)
/ \ / \
B(2) R(2) C(1) D(1)
- 浏览器接收到HTML到渲染页面的过程是什么? / 输入url到渲染页面背后发生了什么事情?
**答: 一,首先是将HTML转化为dom树,与此同时预解析线程会扫描字符串html,把发现的静态资源如css,图片等资源交给网络线程进行并行下载,并行下载操作可以加快整体的速度。在解析dom过程中,如遇到script标签会停止解析,等到script下载完成并且执行完成以后再继续进行解析,这是由于js 可能动态操作dom元素。
****二,dom树解析完成以后接下来是样式计算最终结果称为compted style,这个过程会解析默认样式、外部样式、行内样式,很多预设值会变成绝对值,比如red会变成rgb格式,rem em变成px。最后生成一颗带有样式的dom树
****三,接着是进行布局layout,遍历每一个dom节点根据其盒子模型计算几何信息,包过节点的宽高,和位置信息,完成后会生成一颗layout tree。
四,接着会进行分层,根据z-index, transform, opacity等属性创建图层树,这样做的好处是将来某一个图层改变,只需要修改该图层,提高效率。分层完成以后是进行绘制,生成各个图层的绘制指令,记录绘制顺序。( 分层隔离机制减少重绘范围)
****接下来是合成线程完成步骤:
****五,分块,将每个图层进行分块,多个分块线程同时进行,分成256 * 256 或者 512 * 521的块,优先绘制可视区域的图块。
****六,分块完成以后是光栅化, GPU开启多个线程光栅化,结果就是多个块的页面。
**七, 接着就是合成,完整的网页 - 域名解析之后如何找到目标主机
- 输入url到渲染页面背后发生了什么事情?
- HTTP 103 / 301 / 302 / 204 / 206是什么意思?
**答:103表示服务收到部分请求,等待剩余部分,通常用于分块请求,比喻http1.1的分块传输编码
****301永久重定向
****302临时重定向
****204表示服务器收到请求不需要返回内容,比如发送邮件、删除资源
**206表示服务器成功处理了部分请求,比如:视频请求一部分内容 - HTTP的请求头你知道的有哪些。
-
- 基础控制 :
Host
、User-Agent
、Accept
、Accept-Encoding
。 - 缓存管理 :
Cache-Control
、If-Modified-Since
、If-None-Match
。 - 内容协商 :
Content-Type
、Content-Length
。 - 身份认证 :
Authorization
、Cookie
。 - 跨域处理 :
Origin
、Access-Control-Request-*
。 - 安全与代理 :
X-Forwarded-For
、Sec-Fetch-*
。
- 基础控制 :
-
使用 HTTPS 一定是安全的吗
HTTPS如何保证安全性
HTTP 和 HTTPS有什么区别?
**答:非对称加密连接,对称加密进行内容传输。
****客户端会发送一个请求给服务端支持哪些hash算法.
****服务端会把信息以数字信息格式返回给客户端,包含:证书内容有公钥,网站地址,证书颁布机构,失效日期。
****客服端会验证证书的合法性,是否过期,请求的地址是否正确。
****验证通过后客服端会随机生成一个随机的对称密钥,并用公钥进行加密。服务端用对应的私钥进行解密。拿到随机密钥,用于之后信息对成加密
****也不是绝对安全:
-
有了解过 HTTP3 吗?为'用 UDP
HTTP3的协议一定是TCP
UDP 协议有什么优点?
-
大文件断点下载。
答: 核心在于记录文件的下载进度,并通过HTTP协议的Range
**头部实现对文件指定范围的请求**Range: bytes=200-1000
编程题
- 限制函数并发数的题目,大概说一下,具体题目记不清了。
// countLimit 是一个函数,执行fn,执行的并发度是 2,返回一个 Promise
let countLimit = pLimit(fn, 2)
countLimit(a) // 立即执行
countLimit(b) // 立即执行
countLimit(c) // 前两个函数执行完再执行
// 求实现函数 pLimit
- 编程题:扁平结构转嵌套结构
- 编程题:判断一个只包含左右括号的字符串中,括号是否匹配?
- 编程题:一个数组中找到和等于指定值target的两个元素,并输出他们的下标
- 编程题:实现防抖和节流,平时我写的都是很简单的,面试官特意要求的加上对上下文的处理
- 编程题:给定由
[]{}()
组成的字符串,判断括号是否正确匹配。 - 构造两个以整型数字为值的链表,其中的值是单调递增的。将两个链表合并,保持递增。要求空间复杂度 O ( 1 )
- 实现一个深拷贝,拷贝对象,要考虑循环引用
- 生成一个随机数,4-6,保留两位有效数字
- 代码题,已知数据格式,实现一个函数 fn ,给一个 id 找出链条中其对应的所有的父级 name (用DFS写了一遍)能用广度优先写一遍吗
- 实现一个获取一个对象嵌套属性的函数
- 每次获取一个对象的属性都会打印
获取对象xxx的xxx
,比如获取obj.a.b
,怎么实现 - juejin.cn/post/746364...
算法
- 算法题,股票买卖求最大收益,只买卖一次
- 找一个字符串中最长的不含重复字符的子串
性能优化
- 如何优化一个网站的性能
- 如何在前一个页面对下一个页面进行优化
- 项目中请求的错误处理是怎么做的?
- 如果有多个错误如何保证只触发一次弹窗?(防抖)
- 上线后怎么提示用户刷新当前页面
- 项目有什么监控,比如性能类的。(回答了sentry)
- sentry推送信息是什么方式?
- 页面上的性能优化
其他
- 问了一个小项目的具体做了什么。
- 怎么做技术选型
- 页面数据,表格特别多的时候,是怎么解决的。
- 编辑表格更新是怎么实现的
- 介绍项目,问了一些项目细节,项目是如何推进的。
- 项目排期的管控。
- 排期和手头工作冲突怎么做?平时主要工作内容
- 面试中有遇到过什么比较好的问题?为什么?(emmm)
- 发版是怎么做的?
- 有做过什么基建工具?
- 最近有了解哪些新技术吗
- 介绍下项目A
- 项目有什么效果,如何判断效率提升率?(本地测还是统计数据发送到服务端?)
- 项目后续规划,现在有什么问题?风险怎么处理?
- 项目中有什么是收获很多的?
- 推广过程中遇到什么困难?
- 重新做一遍你觉得有哪些你觉得可以做得做好的事情?
- 用到一些插件的原理
- 平时怎么学习前端技术?
- 如何保证项目的正确性,稳定性
- 你认为一个五年的前端工程师应该需要哪些能力?你哪方面做的好或不好
- 说一个挑战比较大的事情和项目。然后具体问了项目的事情。
- 还有什么有亮点的项目。
- 如果用户白屏但是你的电脑是正常的,你要怎么处理?
- 项目是怎么部署的?怎么监听服务,怎么灰度上线。