2026-01-19-杂记 2026-01-19

6442 个字
32 分钟
2026-01-19-杂记 2026-01-19

闭包#

函数+创建时的词法环境

当一个函数能够“记住并访问”它被创建时所处的词法作用域中的变量,即使这个函数在它原本的作用域之外被执行,这个函数就形成了闭包。

“闭包就是函数和其词法环境的组合” “闭包就是能够读取其他函数内部变量的函数”

闭包是语言运行时的一种行为现象

// 计数器
function createCounter() {
let count = 0; // 处于外部函数作用域的变量
return function() { // 内部函数形成了闭包
return count++;
};
}
const counter = createCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2

作用:封装,保存状态,延迟执行 判断:能否访问已销毁外部作用域的变量(基于函数定义位置)

闭包应用场景#

  • 在定时器、事件监听、Ajax 请求、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包:
// 定时器
setTimeout(function handler(){
console.log('1');
},1000);
// 事件监听
document.getElementById(app).addEventListener('click', () => {
console.log('Event Listener');
});
  • 作为函数参数传递的形式:
var a = 1;
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
// 这是闭包
fn();
}
foo(); // 输出2,而不是1
  • IIFE(立即执行函数),创建了闭包,保存了全局作用域(window)和当前函数的作用域,因此可以输出全局的变量:
var a = 2;
(function IIFE(){
console.log(a); // 输出2
})();

IIFE 是一种自执行匿名函数,这个匿名函数拥有独立的作用域。这不仅可以避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。

  • 结果缓存(备忘模式)

备忘模式就是应用闭包的特点的一个典型应用。比如下面函数:

function add(a) {
return a + 1;
}

当多次执行 add() 时,每次得到的结果都是重新计算得到的,如果是开销很大的计算操作的话就比较消耗性能了,这里可以对已经计算过的输入做一个缓存。所以这里可以利用闭包的特点来实现一个简单的缓存,在函数内部用一个对象存储输入的参数,如果下次再输入相同的参数,那就比较一下对象的属性,如果有缓存,就直接把值从这个对象里面取出来。实现代码如下:

function memorize(fn) {
var cache = {}
return function() {
var args = Array.prototype.slice.call(arguments)
var key = JSON.stringify(args)
return cache[key] || (cache[key] = fn.apply(fn, args))
}
}
function add(a) {
return a + 1
}
var adder = memorize(add)
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(2) // 输出: 3 当前: cache: { '[1]': 2, '[2]': 3 }

使用 ES6 的方式实现:

function memorize(fn) {
const cache = {}
return function(...args) {
const key = JSON.stringify(args)
return cache[key] || (cache[key] = fn.apply(fn, args))
}
}
function add(a) {
return a + 1
}
const adder = memorize(add)
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(2) // 输出: 3 当前: cache: { '[1]': 2, '[2]': 3 }

备忘函数中用 JSON.stringify 把传给 adder 函数的参数序列化成字符串,把它当做 cache 的索引,将 add 函数运行的结果当做索引的值传递给 cache,这样 adder 运行的时候如果传递的参数之前传递过,那么就返回缓存好的计算结果,不用再计算了,如果传递的参数没计算过,则计算并缓存 fn.apply(fn, args),再返回计算的结果。

循环输出问题#

最后来看一个常见的和闭包相关的循环输出问题,代码如下:

for(var i = 1; i <= 5; i ++){
setTimeout(function() {
console.log(i)
}, 0)
}

这段代码输出的结果是 5 个 6,那为什么都是 6 呢?如何才能输出 1、2、3、4、5 呢?

可以结合以下两点来思考第一个问题:

  • setTimeout 为宏任务,由于 JS 中单线程 eventLoop 机制,在主线程同步任务执行完后才去执行宏任务,因此循环结束后 setTimeout 中的回调才依次执行。
  • 因为 setTimeout 函数也是一种闭包,往上找它的父级作用域链就是 window,变量 i 为 window 上的全局变量,开始执行 setTimeout 之前变量 i 已经就是 6 了,因此最后输出的连续就都是 6。

那如何按顺序依次输出 1、2、3、4、5 呢?

1)利用 IIFE

可以利用 IIFE(立即执行函数),当每次 for 循环时,把此时的变量 i 传递到定时器中,然后执行,改造之后的代码如下。

for(var i = 1;i <= 5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
}, 0)
})(i)
}

可以看到,通过这样改造使用 IIFE(立即执行函数),可以实现序号的依次输出。利用立即执行函数的入参来缓存每一个循环中的 i 值。

2)使用 ES6 中的 let

ES6 中新增的 let 定义变量的方式,使得 ES6 之后 JS 发生革命性的变化,让 JS 有了块级作用域,代码的作用域以块级为单位进行执行。

for(let i = 1; i <= 5; i++){
setTimeout(function() {
console.log(i);
},0)
}

可以看到,通过 let 定义变量的方式,重新定义 i 变量,则可以用最少的改动成本,解决该问题。

3)定时器第三个参数

setTimeout 作为经常使用的定时器,它是存在第三个参数的。我们经常使用前两个,一个是回调函数,另外一个是定时时间,setTimeout 从第三个入参位置开始往后,是可以传入无数个参数的。这些参数会作为回调函数的附加参数存在。那么结合第三个参数,调整完之后的代码如下:

for(var i=1;i<=5;i++){
setTimeout(function(j) {
console.log(j)
}, 0, i)
}

可以看到,第三个参数的传递,可以改变 setTimeout 的执行逻辑,从而实现想要的结果



高阶函数(Higher-Order Function)#

JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

一个最简单的高阶函数:

function add(x, y, f) {
return f(x) + f(y);
}

柯里化(Currying)#

柯里化是将一个接受 多个参数 的函数,转换为 一系列只接受单个参数 的函数的变换过程。

f(a, b, c) === f(a)(b)(c)

把“一次传入多个参数” → 拆成“多次每次只传一个参数”的调用链。

// 普通函数(非柯里化)
function add(a, b, c) {
return a + b + c;
}
add(1, 2, 3); // 6
// 柯里化版本
function addCurried(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
addCurried(1)(2)(3); // 6
// 更常见的简写形式(箭头函数)
const addC = a => b => c => a + b + c;
addC(1)(2)(3); // 6


防抖(Debounce)#

定义:当事件被触发后,延迟 n 秒再执行回调函数。如果在这 n 秒内事件再次被触发,则重新开始计时。

// 方式1:简单版(非立即执行)
function debounce(fn, wait) {
let timer = null;
return function (...args) {
// 每次触发都清除之前的定时器
if (timer) clearTimeout(timer);
// 重新设置定时器
timer = setTimeout(() => {
fn.apply(this, args);
}, wait);
};
}
// 方式2:立即执行版 + 尾部执行(更常用)
function debounce(fn, wait, immediate = false) {
let timer = null;
return function (...args) {
const context = this;
if (timer) clearTimeout(timer);
if (immediate) {
const callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) fn.apply(context, args);
} else {
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
}
};
}

**应用:

  • 搜索框输入自动搜索(用户输入完再发请求)
  • 表单验证(输入停止后才校验)
  • 按钮短时间内多次点击(防止重复提交)
  • window.resize 事件

节流(Throttle)#

定义:规定时间内只执行一次函数,无论这段时间内触发多少次。

// 方式1:时间戳版(第一次触发会立即执行)
function throttle(fn, wait) {
let prev = 0;
return function (...args) {
const now = Date.now();
if (now - prev >= wait) {
fn.apply(this, args);
prev = now;
}
};
}
// 方式2:定时器版(最后一次一定会执行)
function throttle(fn, wait) {
let timer = null;
let lastArgs = null;
return function (...args) {
lastArgs = args;
if (!timer) {
fn.apply(this, lastArgs);
lastArgs = null;
timer = setTimeout(() => {
if (lastArgs) {
fn.apply(this, lastArgs);
}
timer = null;
lastArgs = null;
}, wait);
}
};
}
// 方式3:最推荐的综合版(头尾都能执行)
function throttle(fn, wait) {
let timer = null;
let previous = 0;
return function (...args) {
const now = Date.now();
const remaining = wait - (now - previous);
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(this, args);
previous = now;
} else if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
previous = Date.now();
timer = null;
}, remaining);
}
};
}

应用场景

  • 页面滚动到底部加载更多(无限滚动)
  • 拖拽元素时实时计算位置
  • 鼠标移动时画板实时绘制
  • 游戏中技能释放冷却(最典型的思想来源)

call、apply 和 bind#

call、apply 和 bind 是 JavaScript 中 Function 对象提供的三个非常重要且经常被面试考察的方法,它们的主要作用都是改变函数执行时的 this 指向,但在用法、参数传递方式和执行时机上存在显著差异。

特性callapplybind
主要功能立即执行函数,并改变 this 指向立即执行函数,并改变 this 指向不立即执行,返回一个新函数,永久绑定 this
参数传递方式逐个参数列出参数以数组形式传递逐个参数列出(与 call 相同)
返回值函数执行后的返回值函数执行后的返回值新的绑定了 this 的函数
是否立即执行
参数是否可以后续补充否(一次性传完)否(一次性传完)是(可以分两次传参)
典型使用场景需要明确控制 this 并立即执行时处理类数组对象/动态参数时事件处理、固定 this 的回调函数
语法示例fn.call(obj, 1, 2, 3)fn.apply(obj, [1, 2, 3])const newFn = fn.bind(obj, 1, 2)
// 准备一个测试函数和对象
function greet(greeting, punctuation) {
console.log(`${greeting}, 我是${this.name}${punctuation}`);
}
const person = {
name: "张三"
};
const anotherPerson = {
name: "李四"
};

1. call()#

立即执行,参数逐个传入

greet.call(person, "您好", "!");
// 输出:您好,我是张三!
greet.call(anotherPerson, "早上好", "~");
// 输出:早上好,我是李四~

2. apply()#

立即执行,参数以数组形式传入(非常适合处理 arguments 或类数组对象)

greet.apply(person, ["下午好", "。"]);
// 输出:下午好,我是张三。
// 经典用法:求数组最大/最小值(以前常考,现在 Math.max(...arr) 更常用)
const numbers = [5, 2, 9, 1, 7];
console.log(Math.max.apply(null, numbers)); // 9
// 现代写法:Math.max(...numbers)

3. bind()#

最重要的特点:不立即执行,而是返回一个永久绑定了 this 的新函数

// 绑定 this,并预设部分参数(函数柯里化常用)
const greetZhangSan = greet.bind(person, "你好");
greetZhangSan("!");
// 输出:你好,我是张三!
greetZhangSan("呀~");
// 输出:你好,我是张三呀~
// 事件处理中最常见的用法
class Button {
constructor(text) {
this.text = text;
this.element = document.createElement("button");
this.element.textContent = text;
// 错误写法:this.handleClick 中的 this 会指向 DOM 元素
// this.element.addEventListener("click", this.handleClick);
// 正确写法:
this.element.addEventListener("click", this.handleClick.bind(this));
}
handleClick() {
console.log(`按钮 ${this.text} 被点击了`);
}
}

场景:

  • 需要立刻执行 → 选 callapply
  • 处理数组/类数组参数 → 优先考虑 apply
  • 需要保存一个固定 this 的函数(最常见:事件回调、setTimeout、React class 组件等)→ 选 bind
  • 同时固定 this 又预设部分参数(函数柯里化)→ 也用 bind


IIFE#

IIFE是 Immediately Invoked Function Expression 的缩写

JavaScript 中一种非常常见且重要的设计模式,主要用于创建独立的作用域并立即执行代码,同时避免污染全局命名空间

ES6前常用

(function() {
// 这里的所有变量都是局部变量
var privateVar = '这是一个私有变量';
console.log('立即执行!');
})(); // 最后的 () 负责立即调用

事件循环#

事件循环是 JavaScript 单线程运行时用来处理异步任务的调度器,它通过“同步代码 → 微任务 → 一个宏任务 → 微任务 → 下一个宏任务……”的循环,不断地把异步回调安排到合适的时机执行,从而实现了看似并发的效果

组成部分英文名称主要职责执行时机示例任务来源
调用栈Call Stack存放当前正在执行的同步代码,按 LIFO(后进先出)执行立即执行函数调用、代码块
微任务队列Microtask Queue存放优先级最高的异步回调当前宏任务结束后立即清空Promise.then/catch/finally、queueMicrotask、MutationObserver
宏任务队列Task Queue / Macrotask Queue存放普通的异步回调微任务队列清空后取一个执行setTimeout、setInterval、setImmediate、I/O、UI rendering、requestAnimationFrame
Web APIs(浏览器提供)处理真正的异步操作(定时器、网络请求、DOM事件等),完成后将回调放入队列由浏览器/运行时独立线程管理XMLHttpRequest、fetch、DOM事件监听
事件循环(Event Loop)Event Loop不停询问:调用栈是否为空?→ 是的话先清空微任务 → 再取一个宏任务执行持续运行

事件循环一次完整循环的执行顺序#

  1. 执行当前调用栈中的所有同步代码(直到调用栈清空)
  2. 清空微任务队列(执行所有微任务,直到微任务队列为空) 只要有微任务,就会一直执行完所有微任务
  3. 宏任务队列中取一个任务执行(注意:只取一个!)
  4. 执行完这个宏任务后 → 回到第 2 步,再次清空微任务队列
  5. 重复 3~4 步,直到所有任务处理完毕

代码示例与执行顺序演示#

console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => console.log('3'));
setTimeout(() => console.log('4'));
}, 0);
Promise.resolve().then(() => {
console.log('5');
Promise.resolve().then(() => console.log('6'));
});
console.log('7');

执行结果顺序

1
7
5
6
2
3
4
  1. 同步代码 → 打印 1、7
  2. 微任务队列有 then(5) → 执行 → 打印 5,并产生新的微任务 then(6)
  3. 继续清空微任务 → 执行 then(6) → 打印 6
  4. 微任务队列清空,执行第一个宏任务(setTimeout 0ms)→ 打印 2
  5. 宏任务执行中产生微任务 then(3) → 宏任务结束后立即清微任务 → 打印 3
  6. 微任务清空,继续下一个宏任务(setTimeout 里面的)→ 打印 4


序号问题回答
1项目里用了泛型,怎么根据传入类型适配不同处理函数通常用泛型约束 + 映射类型 + 条件类型 + infer 实现函数重载效果 最常见两种做法: 1. 传入对象类型 → 用 keyof T 约束字段名 2. 传入联合类型 → 用条件类型分发实现不同分支
2泛型是什么泛型(Generics) 是 TypeScript(以及许多现代编程语言)中一种强大的类型系统特性,其核心目的是允许开发者编写可复用、类型安全的代码,同时让代码在不同具体类型下都能保持正确的类型推断和约束。

简单来说,泛型就是**“类型参数化”**:把类型当作参数一样传入,让同一个函数、类、接口能够处理多种类型的数据,而无需为每种类型都重复编写代码。
3interface 和 type 的区别现在差别已经很小,主要记住这几点: • interface 可多次声明自动合并(对库/模块声明补丁最有用) • type 可做联合、交叉、映射、条件、模板字符串等复杂类型操作 • interface 性能略好(声明合并时) • 目前绝大多数团队:能用 type 就用 type,除非明确需要声明合并
4keyof、in、typeof、infer 实际工作中怎么用- keyof:取对象所有键名(表单字段校验、权限映射最常用) - in:映射类型必备(生成只读、部分、可选等类型) - typeof:获取的类型(尤其是从 const 断言对象取类型) - infer:在条件类型中推断出某一部分类型(最强大,常用于提取 Promise 返回值、数组元素类型、函数返回值等)
5封装表单组件,要求字段名必须是后端返回字段的一部分,怎么处理最推荐做法(2024~2026 主流): ts
type ApiResponse = { id: number; name: string; age: number; email?: string }

type FormField = keyof T | ${keyof T}.${string}

function Form<T extends Record<string, any>>(props: { fields: FormField[] }) { … }
进阶:用 const assertion + typeof 做更严格的路径类型
6回流(reflow)和重绘(repaint)的区别回流(Layout/Reflow):几何属性/布局变化 → 影响元素位置/大小 → 必须重新计算布局 重绘(Repaint):外观变化(颜色、visibility、阴影等)→ 不影响布局 回流代价远大于重绘(通常是重绘的几倍到几十倍)
7浏览器加载一个页面时,整个渲染流程(关键节点)1. DNS → TCP → TLS 2. 请求 HTML 3. 构建 DOM + CSSOM 4. 合成 Render Tree 5. Layout(回流) 6. Paint(重绘) 7. Composite(图层合成) 重要:Parse → Style → Layout → Paint → Composite
8做骨架屏时,怎么判断骨架内容和真实内容之间的切换时机推荐组合方式(从激进到保守): 1. 最激进:第一个/几个关键接口请求完成就切 2. 常用:所有必须数据接口都 resolve 后切(Promise.all) 3. 保守:主图/首屏关键资源加载完成(img onload + 定时兜底) 4. 极致体验:分层渐进式替换(文字先换、图片后换)
9移动端怎么做适配(2025~2026 主流方案)当前最主流组合: 1. postcss-px-to-viewport-8-plugin / postcss-px-to-rem + tailwindcss 2. 设计稿 375px → vw 方案(750 设计稿用 100vw = 750px) 3. 大屏/横屏特殊处理:100vh polyfill + env(safe-area-inset-*) 4. 字体:clamp() + rem(根字体 100px 常见)
10async 函数在执行栈中的位置是怎么样的async 函数本身是同步执行到第一个 await 第一个 await 之后 → 后续代码作为微任务放入微任务队列 所以:async 函数 = 同步前半段 + 多个微任务
11Promise.all 和 Promise.allSettled 的区别Promise.all:有一个 reject 就整体 reject,结果顺序严格对应 Promise.allSettled:永远 resolve,返回每个 promise 的 {status, value/reason},永远不会 reject
12JS 是单线程,为什么可以实现并发加载多个接口?JS 单线程指的是执行 JavaScript 代码的线程只有一个 真正的网络请求、定时器、DOM事件等都由浏览器底层多线程完成 JS 只负责注册回调,回调通过事件循环被调度执行 → 所以宏观上是并发
13setTimeout 和 requestAnimationFrame 哪个更精确requestAnimationFrame 更精确(与浏览器刷新率同步,通常 16.7ms/60fps) setTimeout 实际延迟往往偏长(最低 4ms + 事件循环排队延迟) 动画/滚动相关优先使用 rAF
14帧率监控你们是怎么做的主流几种方式(按精度/成本排序): 1. requestAnimationFrame 循环 + performance.now() 差值(最常用) 2. PerformanceObserver + ‘longtask’(监控长任务) 3. Web Vitals(CLS、FID、LCP) 4. 第三方:sentry、ddtrace、阿里云 ARMS 等
15小程序渲染卡顿时怎么优化(微信/支付宝小程序)优先级排序(效果最明显→一般): 1. setData 合并 + 减少次数(一次 setData 尽量多字段) 2. 使用 block wx 代替大量 wx 3. 列表用 virtual-list / recycle-view 4. 图片用 webp + lazy-load 5. 避免在循环里写 setData 6. 自定义组件纯数据组件
16H5 页面嵌入小游戏时,遇到过页面内存持续上涨吗?怎么解决的?非常常见,尤其 canvas + webgl 小游戏 常见原因: • 事件监听未移除 • canvas 未清理(ctx.clearRect 不够) • 纹理/缓冲区未释放 • 闭包引用大对象 • requestAnimationFrame 未取消 解决办法: 1. 组件卸载时务必 cancelAnimationFrame 2. 清理纹理:gl.deleteTexture/gl.deleteBuffer 3. 用 WeakMap/WeakSet 存引用 4. 定时检查 performance.memory(chrome devtools) 5. 必要时主动销毁整个 canvas 元素再重建

JS中异步的概念是什么?#

JavaScript异步是指代码的执行不必等待某个操作完成,可以继续执行后续代码。当异步操作完成后,通过回调函数、Promise或async/await来处理结果。这是为了解决JS单线程可能导致的阻塞问题。

核心机制:事件循环 + 回调队列

  • 同步任务在主线程执行
  • 异步任务交由Web APIs处理
  • 完成后的回调进入任务队列
  • 事件循环从队列中取出回调执行

为什么JS是单线程还需要事件循环?#

看似矛盾实则巧妙的设计

  1. 单线程的必然性:避免多线程的复杂性(锁、竞态条件)
  2. 事件循环的价值
    • 处理I/O密集型操作(网络、文件)
    • 维持UI响应性
    • 管理定时器、用户交互
    • 实现”非阻塞”的并发

简单比喻:就像餐厅里只有一个服务员(单线程),但通过叫号系统(事件循环)可以高效服务多桌客人。

栈内存和堆内存的差异?#

特性栈内存堆内存
存储内容基本类型、引用地址引用类型(对象、数组)
分配方式自动分配/释放动态分配,GC回收
大小固定,较小动态,较大
访问速度
管理系统自动管理开发者/GC管理
示例let a = 1let obj = {x: 1}

为什么数组要存在堆内存而不是栈内存?#

根本原因

  1. 大小不确定:数组长度动态变化,栈内存固定大小无法满足
  2. 性能考虑:复制大数组到栈中成本高
  3. 内存管理:堆内存可动态扩展,GC负责回收
  4. 引用语义:多个变量可共享同一数组

JS中有哪些基本数据类型?#

7种基本类型

  1. String
  2. Number
  3. Boolean
  4. Undefined
  5. Null
  6. Symbol(ES6)
  7. BigInt(ES2020)

1种引用类型:Object(包括Array、Function、Date等)

函数内部的this含义是什么?#

this的指向规则

  1. 默认绑定:独立函数调用 → window/undefined(严格模式)
  2. 隐式绑定:作为对象方法调用 → 该对象
  3. 显式绑定:call/apply/bind → 指定的对象
  4. new绑定:构造函数调用 → 新创建的对象
  5. 箭头函数:无自己的this,继承外层

call、apply、bind的区别?#

方法参数传递立即执行返回
call参数列表函数结果
apply数组函数结果
bind参数列表新函数
func.call(thisArg, arg1, arg2);
func.apply(thisArg, [arg1, arg2]);
const newFunc = func.bind(thisArg, arg1, arg2);

TypeScript联合类型和交叉类型的概念?#

联合类型 (Union Types)|表示”或”

type Status = "success" | "error" | "loading";
let value: string | number;

交叉类型 (Intersection Types)&表示”且”

interface A {
x: number;
}
interface B {
y: string;
}
type C = A & B; // { x: number, y: string }

区别

  • 联合:可接受任一类型
  • 交叉:必须同时满足所有类型

二、浏览器/网络类#

什么是同源策略?#

同源:协议、域名、端口完全相同

限制内容

  • DOM访问
  • Cookie/LocalStorage访问
  • AJAX请求
  • 脚本执行

目的:防止恶意网站窃取用户数据

如何实现跨域请求?#

常用方案

  1. CORS:服务器设置响应头
  2. JSONP:利用<script>标签
  3. 代理服务器:同源服务器转发
  4. WebSocket:不受同源限制
  5. postMessage:窗口间通信
  6. nginx反向代理:服务器端代理

为什么代理可以绕过同源限制?#

核心原理

  1. 同源限制是浏览器的安全策略,不是服务器的
  2. 代理服务器与页面同源,不受限制
  3. 服务器间无同源限制,可自由通信

流程

浏览器(同源) → 代理服务器(同源) → 目标服务器(不同源)

Cookie和Session的定义和差异?#

特性CookieSession
存储位置客户端服务器端
安全性较低(可篡改)较高
存储大小4KB左右更大
生命周期可设置过期时间依赖服务器配置
跨域可设置domain依赖Cookie或token

Cookie是怎么种下的?#

两种方式

  1. HTTP响应头Set-Cookie: name=value; options
  2. JavaScript设置document.cookie = "name=value; options"

关键属性

  • HttpOnly:禁止JS访问
  • Secure:仅HTTPS传输
  • SameSite:限制跨站发送
  • Domain/Path:作用域

LocalStorage和SessionStorage的区别?#

特性LocalStorageSessionStorage
生命周期永久,需手动清除标签页关闭即清除
作用域同源窗口共享仅当前标签页
存储大小5-10MB5-10MB
数据同步同源窗口实时同步仅当前标签页

HTTP强缓存和协商缓存的区别?#

强缓存

  • 响应头:Cache-ControlExpires
  • 状态码:200 (from cache)
  • 过程:不请求服务器,直接使用缓存

协商缓存

  • 响应头:ETag/If-None-MatchLast-Modified/If-Modified-Since
  • 状态码:304 (Not Modified)
  • 过程:请求服务器,验证缓存有效性

301和302状态码的区别?#

301 Moved Permanently

  • 永久重定向
  • 搜索引擎更新索引
  • 浏览器缓存新地址
  • 权重传递

302 Found (临时重定向)

  • 临时重定向
  • 搜索引擎不更新
  • 浏览器不缓存
  • 权重不传递

实际使用

  • 网站改版用301
  • 登录跳转用302
  • 移动端适配用302


三、CSS类#

1. 设备像素和逻辑像素的区别?#

设备像素 (Physical Pixel)

  • 物理像素,屏幕实际发光点
  • 固定不变
  • 高DPI设备像素更密集

逻辑像素 (CSS Pixel)

  • 编程使用的像素单位
  • 与实际像素有比例关系
  • devicePixelRatio决定

关系物理像素 = 逻辑像素 × devicePixelRatio

2. CSS两种盒模型的差异#

标准盒模型 (content-box)

  • width/height= 内容宽度
  • 总宽度 = width + padding + border + margin

IE盒模型 (border-box)

  • width/height= 内容 + padding + border
  • 总宽度 = width + margin

设置方式

box-sizing: content-box; /* 默认 */
box-sizing: border-box; /* 常用 */

分享到社交平台

将本文分享给你的朋友们

2026-01-19-杂记 2026-01-19
https://firefly.cuteleaf.cn/posts/2026-01-19-杂记2026-01-19/
作者
Zhongye
发布于
2026-01-20
版权声明
CC BY-NC-SA 4.0

评论

Profile Image of the Author
Zhongye
南漂中
公告
新的博客站!旧站点传送门 zhongye1.github.io/Arknight-notes
音乐
专辑封面

音乐

暂无播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章数
142
分类数
14
标签数
214
总字数
339,690
运行天数
0
最后更新
0 天前

目录