1. 箭头函数和普通函数的区别
this: 箭头函数不绑定this,从外层词法捕获;普通函数this由调用方式决定arguments: 箭头函数没有arguments; 用剩余参数(...args);普通函数有argumentsnew:箭头函数不能作为构造函数,没有prototype;普通函数可以new。super/new.target:箭头函数里取的是外层的。return:箭头函数:如果函数体是一个单表达式,可以省略{}和return,隐式返回这个值。 普通函数:必须写return。
const obj = {
x: 1,
a() {
// 如果在这里直接console.log(this.a)那么打印出 1; 但是下面的setTimeOut不是立即执行函数,
// 是把传进去的回调函数function()存起来,直到0ms后触发,
// 而setTimeout是window的方法,相当于window.setTimeout(function(){ console.log(this.x) }, 0)
setTimeout(function() {
console.log(this.x)
}, 0)
}, // 打印 undefined
b() {
// 箭头函数中,this会继承定义的this,定义时this是obj
setTimeout(() => {
console.log(this.x)
}, 0)
} // 1
};
obj.a();
obj.b();
a() 方法中的 function 回调
setTimeout(function() { console.log(this.x) }, 0)
- 普通函数里的
this取决于调用者。 - 在
setTimeout执行时,这个回调函数是由window(浏览器环境) /global(Node.js) 调用的,而不是obj。 - 因此
this指向全局对象,this.x是undefined。
2. b() 方法中的箭头函数回调
setTimeout(() => { console.log(this.x) }, 0)
- 箭头函数 不会创建自己的
this,它会从定义时所在的作用域捕获this。 - 这里箭头函数定义在
b()方法里面,而调用obj.b()时,this就是obj。 - 所以箭头函数中的
this依然指向obj。 - 因此
this.x输出1。
2.Promise 解决了什么问题?
- 痛点:
- 回调地狱
- 反转控制(失败时没人兜底)
- 错误很难统一捕获
- Promise 的改进
- 链式调用,顺序/并行 更清晰
- 统一错误通道:链末尾
.catch() - 组合:
all / race / allSettled / any
3. 重排(回流)和 重绘
-
重排:布局变化(几何属性)→ 计算布局 → 成本高
- 触发:增删 DOM、改变尺寸/位置、读写混用布局信息(
offsetTop等)
- 触发:增删 DOM、改变尺寸/位置、读写混用布局信息(
-
重绘:仅视觉更新(颜色、阴影、背景)→ 成本低
优化:
-
批量 DOM 操作应使用:(
DocumentFragment/requestAnimationFrame) - 读写分离:先读再写,避免强制同步布局
- 使用
transform/opacity做动画(避免重排) will-change(慎用,别滥开)
浏览器渲染流程:
-
- JavaScript 改变 DOM 或 CSS
-
- 浏览器需要重新计算样式(Recalculate Style)
-
- 计算元素的几何信息(Layout / Reflow = 回流/重排)
-
- 把结果绘制到位图(Paint = 重绘)
-
- 把位图送到 GPU 合成(Composite)
👉 性能最贵的是 Layout (重排) ,其次是 Paint (重绘),最轻的是 Composite。
所以优化的目标就是:尽量少触发回流 / 重绘,能走 GPU 就别走 CPU。
- 把位图送到 GPU 合成(Composite)
👉 性能最贵的是 Layout (重排) ,其次是 Paint (重绘),最轻的是 Composite。
4. 跨域
-
原因:同源策略限制(协议/域名/端口三者任一不同即跨域),浏览器出于安全阻止 JS 读取响应。
-
解决: 服务端设置响应头(根本办法)
Access-Control-Allow-Origin: https://xxx.com(或*,但配合凭证要配置具体域)Access-Control-Allow-Methods / Headers- 凭证:
Access-Control-Allow-Credentials: true+ 前端fetch(..., { credentials: 'include' })
5. 首屏优化(为何“路由懒加载不一定优化首屏”)
首屏的瓶颈通常在:首屏 HTML、关键 CSS、首屏 JS 执行、数据获取、首屏图片。
-
路由懒加载会把非首屏路由的 JS 切分并延后——对首屏不一定有帮助(首屏仍需加载其自身 JS/CSS/数据)。
-
真正有效的首屏优化:
- SSR/SSG(直出 HTML)
- Critical CSS(内联首屏关键样式)
- 按组件级切分(首屏仅加载必需组件)
- 缓存与预取(HTTP 缓存、
<link rel=prefetch/preload>) - 图像懒加载与占位(LQIP/Skeleton)
- 降低 JS 体积(tree-shaking、移除未用依赖)
6.代码切分方式 & 路由异步加载
通用代码切分
- 动态导入:
const Comp = lazy(() => import('./Comp')) - Webpack magic comments:
import(/* webpackChunkName: "user" */ './User') - 分离 vendor / runtime(SplitChunks)
React 路由(v6)异步
import { lazy, Suspense } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const User = lazy(() => import('./pages/User'));
const router = createBrowserRouter([
{ path: '/', element: <Home/> },
{ path: '/user', element: <User/> },
]);
export default function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<RouterProvider router={router}/>
</Suspense>
);
}
要点:首屏就是 /,那 / 页的包仍会进入首屏;只有非首屏路由才被延后加载。
7) 异步路由怎么实现(原理一嘴)
- 路由匹配到组件时,返回的不是组件本身,而是一个 动态 import 的 Promise。
- 路由层在渲染前等待(或用
Suspense边界托底),当模块加载完成再渲染真正组件。 - Bundler 看到
import()就会把该模块打进独立 chunk,按需加载。
8) XSS 攻击场景 & 防护
类型
- 反射型:恶意脚本在 URL 参数中,服务器回显。
- 存储型:恶意脚本存进数据库,所有访问者都会中招。
- DOM 型:前端直接把不可信数据注入 DOM(
innerHTML)。
防护
- 转义输出(HTML、URL、JS 上下文分别转义)
- 内容安全策略(CSP) :
Content-Security-Policy阻止内联脚本等 - 输入校验 & 白名单(富文本用 DOMPurify 等进行 sanitize)
- Cookie 设置
HttpOnly、SameSite
9) 防抖(debounce)——含取消
语义:短时间内高频触发,只在最后一次或第一次(可选)触发后执行。常用于输入框搜索。
lodash 文档(英文要点)
_.debounce(func, wait, [options])
-
options.leading(boolean): invoke on the leading edge -
options.trailing(boolean): invoke on the trailing edge -
options.maxWait(number): maximum wait time before forced invoke -
Returns a debounced function with methods:
.cancel()→ cancel delayed invocation.flush()→ immediately invoke pending
使用示例(含取消) :
import debounce from 'lodash/debounce';
// 500ms 内只执行一次;首次不执行,最后一次执行
const onSearch = debounce((q) => {
fetch(`/api?q=${encodeURIComponent(q)}`)
}, 500, { leading: false, trailing: true });
// 组件卸载时取消
// React:
useEffect(() => {
return () => onSearch.cancel();
}, []);
// 需要立刻执行当前等待的调用
onSearch.flush();
// 需要中途取消(比如清空输入时不再请求)
onSearch.cancel();
面试对比:
- 防抖(debounce):频繁触发 → 合并为一次(最后或第一次)。
- 节流(throttle):固定间隔内最多执行一次。