2025面试题——(12)
一、var和let const 的区别
- 作用域:var 是函数级作用域,let/const 是块级作用域({} 内)。
- 变量提升:var 存在,let/const 不存在(有暂时性死区)。
- 重复声明:var 允许,let/const 不允许。
- 赋值:var/let 可重复赋值,const 声明时必须初始化且不能重新赋值(引用类型属性可改)
二、typeof 返回哪些类型
返回以下 7 种基本类型字符串:
undefined
、boolean
、number
、string
、symbol
、bigint
、function
以及用于对象(非函数)的 object
(注意:null
会返回 object
,是历史遗留问题)。
三、列举强制类型转换和隐式类型转换
强制类型转换(显式转换)
- 转数字:
Number()
、parseInt()
、parseFloat()
- 转字符串:
String()
、toString()
- 转布尔:
Boolean()
隐式类型转换(自动转换)
- 算术运算:
1 + "2" → "12"
(数字转字符串) - 比较运算:
"5" == 5 → true
(字符串转数字) - 逻辑判断:
if ("")
(空字符串转 false) - 一元运算符:
+ "123" → 123
(字符串转数字)
五、手写深度比较,模拟 lodash isEqual
六、split()和join0 的区别
split(separator)
:将字符串按分隔符拆分为数组(字符串 → 数组)
例:'a,b,c'.split(',') → ['a','b','c']
join(separator)
:将数组元素按分隔符拼接为字符串(数组 → 字符串)
例:['a','b','c'].join(',') → 'a,b,c'
七、数组的 pop push unshift shift 分别做什么?
pop()
:删除数组最后一个元素,返回被删除元素(改变原数组)push()
:向数组末尾添加元素,返回新长度(改变原数组)unshift()
:向数组开头添加元素,返回新长度(改变原数组)shift()
:删除数组第一个元素,返回被删除元素(改变原数组)
八、数组slice和splice区别?
- slice(start, end):返回数组从 start 到 end(不包含)的子数组,不改变原数组(纯函数)。
例:[1,2,3].slice(1,2) → [2]
- splice(start, deleteCount, ...items):从 start 开始删除 deleteCount 个元素,可添加新元素,改变原数组,返回被删除元素。
例:[1,2,3].splice(1,1,4) → 原数组变为 [1,4,3],返回 [2]
九、 [10,20,30].map(parseInt) 返回结果是什么 ?
结果:[10, NaN, NaN]
解析:
- map 传递 (元素,索引) 给 parseInt,即:
parseInt(10, 0)
→ 10(基数 0 视为 10)parseInt(20, 1)
→ NaN(基数 1 无效)parseInt(30, 2)
→ NaN(30 不是二进制数)
十、ajax 请求 get 和 post 的区别 ?精简回答
- 数据位置:get 数据在 URL 中;post 数据在请求体中。
- 长度限制:get 受 URL 长度限制;post 无(取决于服务器)。
- 缓存:get 可缓存;post 通常不可。
- 语义:get 用于获取数据;post 用于提交数据(修改服务器状态)。
九、函数 call 和 apply 的区别
- 参数传递方式不同:
call(thisArg, arg1, arg2, ...)
:逐个传递参数apply(thisArg, [argsArray])
:以数组形式传递参数
function fn(a, b) { console.log(a + b); } fn.call(null, 1, 2); // 3(参数逐个传) fn.apply(null, [1, 2]); // 3(参数放数组里)
十一、事件代理(委托)是什么?
定义:将子元素的事件监听委托给父元素,利用事件冒泡触发父元素的监听函数,再通过 event.target
判断具体触发元素。
作用:减少事件监听数量,优化性能;支持动态新增元素的事件处理。
// 父元素代理所有子元素的点击事件
document.getElementById('parent').addEventListener('click', (e) => {if (e.target.tagName === 'LI') { // 判断触发源是子元素LIconsole.log('点击了LI:', e.target.textContent);}
});
十二、闭包是什么,有什么特性?有什么负面影响?
定义:函数嵌套中,内部函数引用外部函数的变量 / 参数,且内部函数被外部访问,形成闭包。
特性:
- 延长外部函数变量的生命周期(不被垃圾回收)
- 内部函数可访问外部作用域变量
function outer() {let count = 0;return function inner() { // 闭包:inner引用了outer的countcount++;return count;};
}
const fn = outer();
console.log(fn()); // 1(count被保留)
console.log(fn()); // 2
负面影响:
- 变量长期驻留内存,可能导致内存泄漏
- 过度使用会增加内存消耗,影响性能
十三、如何阻止事件冒泡和默认行为?
- 阻止冒泡:
event.stopPropagation()
(IE 低版本用event.cancelBubble = true
) - 阻止默认行为:
event.preventDefault()
(IE 低版本用event.returnValue = false
) - 同时阻止:事件处理函数中返回
false
(仅部分框架有效)
十四、查找、添加、删除、移动 DOM 节点的方法 ?
- 查找:
getElementById()
、querySelector()
、querySelectorAll()
、getElementsByClassName()
等 - 添加:
appendChild()
、insertBefore()
、createElement()
结合append()
- 删除:
removeChild()
、remove()
(直接删除自身) - 移动:先
removeChild()
再appendChild()
或insertBefore()
十五、如何减少 DOM 操作 ?
- 使用文档片段(
DocumentFragment
)批量操作 - 合并 DOM 修改,减少重排重绘
- 用虚拟 DOM(如 React)间接操作
- 缓存 DOM 查询结果,避免重复查找
- 离线更新 DOM(先隐藏再修改)
十六、解释 jsonp 的原理,为何它不是真正的 ajax ?
- 原理:利用 script 标签不受同源策略限制的特性,通过动态创建 script 标签,请求带回调函数名的跨域接口,服务器返回回调函数包裹的 JSON 数据,实现跨域数据获取。
- 不是真正的 ajax:ajax 基于 XMLHttpRequest 对象,而 jsonp 基于 script 标签请求;ajax 可发送多种请求方式,jsonp 只能用 GET;ajax 遵循同源策略,jsonp 是规避同源策略的技巧。
十七、document load 和 ready 的区别?
- ready:DOM 结构加载完成后触发(无需等待样式、图片等资源),可多次触发。
- load:整个页面(包括 DOM、样式、图片等所有资源)加载完成后触发,仅触发一次。
十八、==和 ===的不同
- ==:宽松相等,会先进行类型转换再比较值是否相等。
- ===:严格相等,不进行类型转换,直接比较值和类型是否都相等
十九、函数声明和函数表达式的区别 ?
- 函数声明:
function fn() {}
,存在变量提升,可在声明前调用。 - 函数表达式:
const fn = function() {}
,无变量提升,声明前调用会报错。 - 函数声明必须有函数名,表达式可匿名。
二十、new Object()和 Object.create()的区别?
new Object()
:创建空对象,原型指向Object.prototype
,类似{}
。Object.create(proto)
:以指定对象为原型创建新对象,若传null
则新对象无原型。
二十一、关于 this 的场景题
二十二、关于作用域和自由变量的场景题-1
- 全局 / 普通函数:指向全局对象(浏览器为 window,Node 为 global)。
- 对象方法调用:指向调用该方法的对象。
- 构造函数 (new):指向新创建的实例。
- call/apply/bind:指向传入的第一个参数。
- 箭头函数:无自身 this,继承外层作用域的 this。
var a = 10;
function fn() {console.log(a); // 自由变量a,向上查找外层作用域的a → 10
}
function bar() {var a = 20;fn();
}
bar();
二十三、判断字符串以字母开头,后面字母数字下划线,长度 6-30
- 作用域:
fn
的作用域链为自身 → 全局,bar
的作用域不影响fn
。 - 自由变量:
fn
中未声明的a
是自由变量,沿作用域链查找(非调用位置),结果为全局a=10
。
const reg = /^[a-zA-Z][a-zA-Z0-9_]{5,29}$/;
// 解析:
// ^[a-zA-Z] → 开头必须是字母
// [a-zA-Z0-9_]{5,29} → 后续5-29个字符(字母/数字/下划线)
// $ → 结束符
二十四、关于作用域和自由变量的场景题-2
let x = 10;
function outer() {let x = 20;function inner() {console.log(x); // 自由变量x,查找最近外层作用域的x → 20}inner();
}
outer();
- 作用域嵌套:
inner
作用域嵌套于outer
,优先访问outer
中的x
。 - 自由变量查找规则:沿作用域链向上查找(静态作用域,定义时确定,非执行时)。
<script>let a = 100;function test() {alert(a);a = 10;alert(a);}test();alert(a);</script>
- 执行过程分析:
- 首先,
let a = 100
声明了全局变量a
并赋值为100
。 - 调用
test
函数:- 第一个
alert(a)
:此时在test
函数作用域内,查找a
,因为函数内没有声明a
,所以向上查找全局作用域的a
,输出100
。 - 然后
a = 10
:这里是对全局变量a
进行赋值,将全局的a
改为10
。 - 第二个
alert(a)
:在test
函数作用域内,a
已经被修改为10
(全局变量),输出10
。
- 第一个
- 函数
test
执行完后,执行alert(a)
:此时访问的是全局变量a
,其值已经被改为10
,输出10
。
- 首先,
- 最终输出结果依次为:
100
、10
、10
。
二十五、常见正则表达式
1. 邮政编码
正则:/\d{6}/
功能:匹配 6 位数字(符合邮政编码规则)
优化:若需严格 “仅 6 位数字”,建议加锚点 ^
和 $
→ /^\d{6}$/
,避免匹配 “1234567” 里的 “234567”
2. 小写英文字母
正则:/^[a-z]+$/
功能:验证字符串仅包含 1 个及以上小写字母(如 abc
匹配,Abc
、12
不匹配 )
3. 英文字母
正则:/^[a-zA-Z]+$/
功能:验证字符串仅包含 1 个及以上大小写字母(如 AbC
匹配,123
、a-b
不匹配 )
4. 日期格式(2019.12.1 )
正则:/^\d{4}-\d{1,2}-\d{1,2}$/
功能:匹配 “年 - 月 - 日” 格式(如 2025-08-08
匹配,2025.08.08
不匹配 )
问题:未校验日期合法性(如 2025-13-32
也会匹配 ),若需严格校验需更复杂正则或代码逻辑
5. 用户名
正则:/^[a-zA-Z]\w{5,17}$/
功能:
- 以字母开头(
a-zA-Z
) - 后续跟
5~17
个 字母、数字、下划线(总长度6~18
)
问题:\w
含下划线,若需限制可显式写字符集(如[a-zA-Z0-9_]
语义更清晰 )
6. 简单的 IP 地址匹配
正则:/\d+/
功能:匹配 “1 个及以上数字”(完全不满足 IP 规则 )
问题:IP 需 “4 段数字(0 - 255)+ 点分隔”,正确写法示例:
/^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/
(校验每段 0~255
,且用点分隔 )
二十六、手写字符串 trim 方法,保证浏览器兼容性?
function myTrim(str) {if (String.prototype.trim) {return str.trim(); // 利用原生方法}// 不支持原生trim时,用正则移除首尾空白return str.replace(/^\s+|\s+$/g, '');
}
二十七、如何获取多个数字中的最大值?
- 扩展运算符:
Math.max(...[num1, num2, ...])
- apply 方法:
Math.max.apply(null, [num1, num2, ...])
- 遍历比较:初始化 max,循环更新最大值
二十八、如何用 JS 实现继承 ?
- 原型链继承:
Child.prototype = new Parent()
- 构造函数继承:父类.call (this, 参数)(解决属性继承)
- 组合继承:原型链 + 构造函数(兼顾属性和方法)
- ES6 class 继承:
class Child extends Parent { constructor() { super() } }
(语法糖,本质基于原型)
二十九、如何捕获 JS 程序中的异常 ?
1.try/catch/finally 语句
这是最常用的异常捕获方式,适用于同步代码和标记为 await
的异步代
try {// 可能抛出异常的代码const result = riskyOperation();
} catch (error) {// 捕获并处理异常(error 包含错误信息)console.error('发生错误:', error.message);
} finally {// 无论是否发生异常,都会执行的代码(如资源清理)console.log('操作结束');
}
2.异步代码的异常处理
对于 Promise
,使用 .catch()
方法
fetchData().then(data => process(data)).catch(error => console.error('请求失败:', error));
3.用于捕获未被局部处理的异常,避免程序崩溃
- 浏览器环境:
window.onerror
或window.addEventListener('error')
- Node.js 环境:
process.on('uncaughtException')
- Promise 未捕获异常:
window.addEventListener('unhandledrejection')
(浏览器)或process.on('unhandledRejection')
(Node.js)
三十、什么是 JSON ?
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,具有以下特点:
语法规则:
- 基于 JavaScript 对象字面量语法,但独立于语言(任何语言均可解析)。
- 键必须用双引号包裹,值可以是字符串、数字、布尔值、null、数组或对象。
{"name": "Alice","age": 30,"isStudent": false,"hobbies": ["reading", "coding"]
}
用途:
- 前后端数据传输(如 API 请求 / 响应)。
- 配置文件存储。
- 数据序列化(将对象转换为字符串便于传输或存储)。
JS 中的 JSON 方法:
JSON.stringify()
:将 JS 对象转换为 JSON 字符串。JSON.parse()
:将 JSON 字符串解析为 JS 对象(若格式错误会抛出异常)。
三十一、获取当前页面 url 参数
获取 URL 参数的方案需考虑兼容性、边界情况和易用性,以下是进阶实现思路:
1. 核心实现(解析 URLSearchParams)
现代浏览器支持 URLSearchParams
API,可简洁处理参数:
function getUrlParams() {const params = new URLSearchParams(window.location.search);const result = {};// 迭代所有参数并转为对象for (const [key, value] of params.entries()) {// 处理多值参数(如 ?ids=1&ids=2)if (result.hasOwnProperty(key)) {result[key] = Array.isArray(result[key]) ? [...result[key], value] : [result[key], value];} else {result[key] = value;}}return result;
}
2. 兼容旧环境(手动解析)
对于不支持 URLSearchParams
的环境(如 IE),可手动解析:
function getUrlParams() {const search = window.location.search.slice(1); // 去除问号if (!search) return {};const params = {};const pairs = search.split('&');pairs.forEach(pair => {// 处理空参数(如 ?key= 或 ?key)const [key, value = ''] = pair.split('=').map(decodeURIComponent);if (params[key] !== undefined) {params[key] = Array.isArray(params[key]) ? [...params[key], value] : [params[key], value];} else {params[key] = value;}});return params;
}
3. 高级特性扩展
- 类型转换:自动将数字、布尔值、null 转换为对应类型:
function convertValue(value) {if (value === 'null') return null;if (value === 'true') return true;if (value === 'false') return false;if (!isNaN(Number(value))) return Number(value);return value; }
单例模式:避免重复解析,缓存结果:
const urlParams = (() => {// 解析逻辑(同上)return parsedParams;
})();
4. 面试加分点
- 提及
URL
构造函数:new URL(window.location.href).searchParams
。 - 注意参数编码问题(
decodeURIComponent
处理空格、特殊字符)。 - 区分
location.search
(查询参数)与location.hash
(哈希值)。 - 对于 SPA 路由(如 React Router),需通过路由库的 API 获取参数(如
useSearchParams
)。
三十二、手写深拷贝
function deepClone(obj = {}) {// 1. 基本数据类型或者 null,直接返回(递归的终止条件)// typeof null 的结果是 'object',所以单独判断 obj === nullif (typeof obj!== 'object' || obj === null) {return obj;}// 2. 初始化用于存储深拷贝结果的容器let result;// 如果是数组,就初始化一个空数组if (obj instanceof Array) {result = [];} else {// 不是数组,就初始化一个空对象result = {};}// 3. 遍历原对象/数组的可枚举自有属性(不遍历原型链上的属性)for (let key in obj) {// 保证 key 是当前对象自身的属性,而不是继承自原型链的if (obj.hasOwnProperty(key)) {// 递归调用 deepClone,对属性值进行深拷贝// 把拷贝后的结果赋值给新对象/数组对应的属性result[key] = deepClone(obj[key]);}}// 4. 返回深拷贝后的结果return result;
}
三十三、介绍-下 RAF requestAnimateFrame ?
requestAnimationFrame
是浏览器提供的用于同步动画渲染的 API,主要特点:
工作原理:
- 告诉浏览器 "我要执行动画",浏览器会在下一次重绘前调用指定回调函数
- 回调函数接收一个时间戳参数(performance.now () 返回值),表示当前执行时间
优势:
- 自动匹配浏览器刷新率(通常 60fps),避免过度绘制导致的性能浪费
- 当页面处于后台或标签页不可见时,会暂停执行,节省 CPU 资源
- 比 setTimeout/setInterval 更精准,避免因主线程繁忙导致的动画卡
基本用法:
let progress = 0;function animate(timestamp) {progress += 1;if (progress < 100) {// 更新动画状态(如DOM样式)element.style.left = progress + 'px';// 继续请求下一帧requestId = requestAnimationFrame(animate);} }// 启动动画 const requestId = requestAnimationFrame(animate);// 取消动画(如需中途停止) cancelAnimationFrame(requestId);
适用场景:
- DOM 动画(如位置、大小、透明度变化)
- Canvas/SVG 动画
- 数据可视化动态效果
三十四、前端性能如何优化,一般从哪几个方面考虑 ?
前端性能优化需从用户体验和技术实现双维度考虑,核心目标是:减少加载时间、提升交互响应速度、降低资源消耗。
1. 网络层优化
资源加载策略:
- 实施 HTTP/2(多路复用)或 HTTP/3(QUIC 协议)
- 静态资源 CDN 分发,减少跨地域延迟
- 资源压缩:JS/CSS 压缩(Terser/CSSNano)、图片压缩(WebP/AVIF 格式)
- 资源合并:合理合并 JS/CSS(避免过度合并导致缓存失效)
缓存机制:
- 强缓存(Cache-Control/Expires):长期不变资源(如图片、库文件)
- 协商缓存(ETag/Last-Modified):频繁更新但不常变资源
- Service Worker:实现离线缓存和请求拦截
预加载策略:
preload
:高优先级资源(如首屏关键 CSS/JS)prefetch
:低优先级资源(如后续页面可能用到的资源)- 懒加载:图片、视频、组件(基于 IntersectionObserver)
2. 渲染层优化
DOM 操作优化:
- 减少重排(Reflow)和重绘(Repaint):
- 使用 DocumentFragment 批量操作 DOM
- 避免频繁读取 offsetTop 等触发重排的属性
- 将频繁变化的元素设为
will-change: transform
(触发 GPU 加速)
- 虚拟列表(Virtual List):处理大数据列表渲染
- 减少重排(Reflow)和重绘(Repaint):
CSS 优化:
- 避免复杂选择器(如嵌套过深的后代选择器)
- 减少使用
@import
(阻塞并行下载) - 关键 CSS 内联到 HTML 头部,非关键 CSS 异步加载
JavaScript 优化:
- 代码分割(Code Splitting):基于路由或组件动态导入
- 避免长任务阻塞主线程:将耗时操作放入 Web Worker
- 事件委托:减少事件监听器数量
- 使用 requestAnimationFrame 处理动画,避免使用 setTimeout
3. 代码层优化
- 算法与数据结构:优化复杂逻辑的时间复杂度(如避免 O (n²) 循环)
- 树摇(Tree Shaking):移除未使用的代码(依赖 ES6 模块)
- 第三方库优化:按需引入(如 lodash-es 代替完整 lodash)
- 内存管理:及时清除定时器、事件监听器,避免闭包导致的内存泄漏
4. 监控与量化
- 性能指标监控:
- 核心 Web 指标(LCP、FID、CLS)
- 传统指标(白屏时间、首屏时间、DOMContentLoaded)
- 性能分析工具:Lighthouse、Chrome Performance 面板
- 建立性能预算(Performance Budget):限制资源大小和加载时间
面试加分点
- 结合具体业务场景谈优化(如电商首页 vs 管理系统的不同策略)
- 提及新兴技术(如 HTTP/3、Web Assembly、边缘计算)
- 强调性能优化的 "性价比":避免过度优化导致的维护成本上升