当前位置: 首页 > news >正文

JS常见问题

​​1.解释JavaScript中的数据类型​​

  • 基本类型:String、Number、Boolean、Null、Undefined、Symbol、BigInt
  • 引用类型:Object(包括Array、Function、Date等)

2.let、const和var的区别​

特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升提升且初始化为 undefined提升但未初始化(TDZ)提升但未初始化(TDZ)
重复声明允许不允许不允许
重新赋值允许允许不允许(常量必须初始化)
暂时性死区
(1)函数作用域(var)
变量仅在声明它的函数内部有效:
function test() {var x = 10;if (true) {var x = 20; // 同一个变量!}console.log(x); // 20(if块内修改影响了外部)
}
(2)块级作用域(let/const)
变量仅在声明它的 {} 块内有效:
function test() {let x = 10;if (true) {let x = 20; // 不同的变量}console.log(x); // 10(if块内的x不影响外部)
}
(3)变量提升(Hoisting)
var 的变量提升
声明会被提升到作用域顶部,并初始化为 undefined:
console.log(a); // undefined(不会报错)
var a = 10;
let/const 的变量提升
声明会提升,但不会被初始化(处于 TDZ):
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;
(4)暂时性死区(TDZ, Temporal Dead Zone)
在变量声明之前访问 let/const 会触发 TDZ 错误:
javascript
// TDZ 开始
console.log(c); // ReferenceError
// TDZ 结束
const c = 30;
(5)重新赋值与声明
const 必须初始化且不能重新赋值:
const PI = 3.14;
PI = 3.1415; // TypeError: Assignment to constant variable
但对象属性可修改(因为对象引用不变):
const obj = { name: "Alice" };
obj.name = "Bob"; // 允许

let 允许重新赋值:

let count = 1;
count = 2; // 允许

var 允许重复声明(易导致 bug):

var name = "Alice";
var name = "Bob"; // 不报错

3、什么是闭包?举例说明​

闭包 是指 函数能够访问并记住其词法作用域(lexical scope)外的变量。简单来说,闭包让函数可以“记住”它被创建时的环境
(1)闭包的核心特点
函数嵌套函数:外部函数包裹内部函数。
内部函数引用外部变量:内部函数使用了外部函数的变量。
外部函数执行后,变量仍被保留:即使外部函数已经执行完毕,内部函数仍能访问其变量。
(2)闭包的经典示例
示例 1:计数器(利用闭包保存状态)
function createCounter() {let count = 0; // 外部函数的变量return function() {count++; // 内部函数引用外部变量return count;};
}const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3// createCounter() 执行后,返回一个内部函数。
// 内部函数 counter 仍然可以访问 count,即使 createCounter() 已经执行完毕。
// count 不会被垃圾回收,因为闭包保留了它的引用。
示例 2:循环中的闭包(经典面试题)
for (var i = 0; i < 3; i++) {setTimeout(function() {console.log(i); // 输出 3, 3, 3}, 1000);
}
// var 是函数作用域,i 在整个循环中共享同一个变量。
// setTimeout 回调执行时,循环已经结束,i 的值是 3。
// 解决方案(使用let):
for (let i = 0; i < 3; i++) {setTimeout(function() {console.log(i); // 输出 0, 1, 2}, 1000);
}
// let 具有 块级作用域,每次循环都会创建一个新的变量绑定,而 var 是函数作用域,导致循环共享同一个变量。
// 解决方案(使用闭包):
for (var i = 0; i < 3; i++) {(function(j) { // 立即执行函数(IIFE)创建闭包setTimeout(function() {console.log(j); // 输出 0, 1, 2}, 1000);})(i);
}
// 每次循环时,i 的值被传给 j,j 被闭包保留,因此 setTimeout 回调访问的是不同的 j。
(3)注意事项
避免滥用闭包,防止内存泄漏。
在需要数据封装或状态管理时优先考虑闭包。

4、解释事件循环(Event Loop)​

事件循环是 JavaScript 处理异步操作的底层机制,它决定了代码的执行顺序,使得单线程的 JavaScript 能够非阻塞地运行。		
(1)为什么需要事件循环?
JavaScript 是单线程语言,但需要处理异步任务(如网络请求、定时器、用户交互)。事件循环通过 任务队列 和 回调机制 实现非阻塞执行。
(2)事件循环的核心组成
组成部分作用
调用栈(Call Stack)同步代码的执行栈,遵循“后进先出”(LIFO)原则。
任务队列(Task Queue)存放异步任务的回调函数(如 setTimeout、DOM 事件)。
微任务队列(Microtask Queue)存放优先级更高的异步回调(如 Promise.then、MutationObserver)。
事件循环线程持续检查调用栈是否为空,然后按规则调度任务。
(3)事件循环的工作流程

时间循环的工作流程
具体步骤:

同步代码:直接进入调用栈执行(如 console.log)。微任务(高优先级):每当调用栈为空时,先清空所有微任务(包括执行微任务时新产生的微任务)。常见微任务:Promise.then、queueMicrotask、MutationObserver。宏任务(低优先级):只有当微任务队列完全清空后,才会执行一个宏任务。常见宏任务:setTimeout、setInterval、DOM 事件、I/O 操作。循环:重复上述过程。
(4)经典示例分析
示例 1:同步 + Promise + setTimeout
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
输出顺序:1432
解释:同步代码:14 直接输出。微任务(Promise.then):3 优先于宏任务执行。宏任务(setTimeout):2 最后执行。
示例 2:嵌套微任务
setTimeout(() => console.log("A"), 0); // 宏任务
Promise.resolve().then(() => {console.log("B"); // 微任务1Promise.resolve().then(() => console.log("C")); // 微任务2(嵌套)}).then(() => console.log("D")); // 微任务3
console.log("E"); // 同步
输出顺序:EBCDA
执行过程:同步代码:E。清空微任务队列:执行第一个 then 输出 B,生成新的微任务 C。执行 C(微任务队列未空)。执行第二个 then 输出 D。执行宏任务:A
(5)总结
同步代码 > 微任务 > 宏任务。每次调用栈清空后,必须彻底清空微任务队列。一个事件循环周期只执行一个宏任务(然后回到微任务检查)。

5、解释this关键字的指向​

this 是 JavaScript 中一个动态绑定的关键字,它的指向 取决于函数的调用方式,而非定义位置。
(1)默认绑定(独立函数调用)
规则:在普通函数中(非严格模式),this 指向全局对象(浏览器中为 window,Node.js 中为 global);严格模式('use strict')下为 undefined。
function showThis() {console.log(this);
}
showThis(); // 浏览器中输出: window(非严格模式)// 严格模式下输出: undefined
(2) 隐式绑定(方法调用)
规则:当函数作为对象的方法调用时,this 指向 调用该方法的对象。
const user = {name: "Alice",greet() {console.log(`Hello, ${this.name}!`);}
};
user.greet(); // 输出: "Hello, Alice!"(this → user)

易错点:方法赋值后丢失 this

const greet = user.greet;
greet(); // 输出: "Hello, undefined!"(this → window,因为独立调用)
(3) 显式绑定(call/apply/bind)
规则:通过 call、apply 或 bind 强制指定 this 的指向。

方法 作用 示例
call 立即调用函数,指定 this 和参数列表 func.call(obj, arg1, arg2)
apply 类似 call,参数以数组传递 func.apply(obj, [arg1, arg2])
bind 返回一个绑定 this 的新函数 const boundFunc = func.bind(obj)
javascript

function introduce(lang) {console.log(`${this.name} codes in ${lang}`);
}
const dev = { name: "Bob" };introduce.call(dev, "JavaScript");  // 输出: "Bob codes in JavaScript"
introduce.apply(dev, ["Python"]);   // 输出: "Bob codes in Python"const boundFunc = introduce.bind(dev);
boundFunc("Java");                  // 输出: "Bob codes in Java"
(4)new 绑定(构造函数调用)
规则:使用 new 调用构造函数时,this 指向 新创建的对象实例。
function Person(name) {this.name = name;
}
const alice = new Person("Alice");
console.log(alice.name); // 输出: "Alice"(this → alice 实例)
(5) 箭头函数的 this
规则:箭头函数没有自己的 this,它继承 外层作用域的 this(定义时决定,不可更改)。
const obj = {name: "Alice",regularFunc: function() {console.log(this.name); // this → obj},arrowFunc: () => {console.log(this.name); // this → 外层作用域(如 window)}
};obj.regularFunc(); // 输出: "Alice"
obj.arrowFunc();   // 输出: undefined(假设外层为全局)

典型应用:解决回调函数 this 丢失

const timer = {count: 0,start() {setInterval(() => {this.count++; // this 继承自 start 方法(指向 timer)console.log(this.count);}, 1000);}
};timer.start(); // 每秒输出递增的 count
(6) 特殊场景

(1)DOM 事件处理函数

规则:this 指向 触发事件的元素。
button.addEventListener("click", function() {console.log(this); // 输出: <button> 元素
});

(2)类(Class)中的 this

规则:类方法中的 this 指向实例,但单独提取方法时可能丢失绑定。

class User {constructor(name) {this.name = name;}sayHi() {console.log(`Hi, ${this.name}!`);}
}
const user = new User("Alice");
user.sayHi(); // 输出: "Hi, Alice!"
const sayHi = user.sayHi;
sayHi();      // 报错: Cannot read property 'name' of undefined
普通函数:谁调用,this 指向谁(无调用者则指向全局)。箭头函数:this 是“透明的”,继承定义时的上下文。强制绑定:用 call/apply/bind 手动控制。

6、如何避免全局变量污染

(1)使用模块化(ES Modules)
通过 import/export 隔离作用域,每个文件拥有独立作用域。
// utils.js
export function calculate() { /* ... */ }
// main.js
import { calculate } from './utils.js';
calculate(); // 无需全局变量
(2)立即执行函数(IIFE)
适用场景:传统非模块化项目快速隔离作用域。
(function() {const privateVar = "局部变量"; // 不会污染全局window.publicApi = { // 按需暴露少数全局接口init() { console.log(privateVar); }};
})();
publicApi.init(); // 可控的全局暴露
(3)命名空间模式
解决冲突:将多个变量封装到一个全局对象下。
// 统一全局入口
const MY_APP = {utils: {version: '1.0',getData() { /* ... */ }},config: { /* ... */ }
};
MY_APP.utils.getData(); // 替代全局函数 getData()
(4)块级作用域(let/const)
替代 var:用 let/const 限制变量作用域。
{const tempData = []; // 只在当前块有效// ...
}
console.log(tempData); // ReferenceError
(5)类封装(Class)
面向对象:通过类组织代码,避免暴露内部状态。
class DataService {#privateField = "私有"; // 私有字段(ES2022)fetch() {console.log(this.#privateField);}
}
const service = new DataService();
service.fetch(); // 不泄露私有变量
(6)闭包保护变量
隐藏私有数据:函数作用域保护变量不被外部访问。
function createCounter() {let count = 0; // 受闭包保护return {increment() { count++; },get() { return count; }};
}
const counter = createCounter();
counter.increment();
console.log(counter.get()); // 1
console.log(count); // ReferenceError
(7)严格模式(‘use strict’)
预防意外全局:禁止未声明的变量赋值。
'use strict';
accidentalGlobal = 10; // 抛出 ReferenceError

7、async/await的工作原理

async函数返回Promise
await暂停async函数执行,等待Promise解决

8、cookie、localStorage、sessionStorage区别

特性CookieLocalStorageSessionStorage
设计用途客户端与服务端通信(如身份验证)持久化存储客户端数据临时存储会话级数据
存储容量≤ 4KB(单个域名下所有 Cookie 总和)5MB~10MB per domain(浏览器差异)同 LocalStorage
生命周期可设置过期时间(默认会话级关闭失效)永久存储,需手动清除页面会话结束时自动清除(标签页关闭)
数据共享范围同域名下所有窗口和标签页同域名下所有窗口和标签页仅当前标签页(同源页面可共享)
是否自动发送到服务端每次请求自动携带在 HTTP Headers仅客户端存储,不自动发送同 LocalStorage
适用场景用户登录状态、CSRF 令牌用户偏好设置、缓存数据表单草稿、页面间临时传递数据

9、跨域解决方案

当浏览器发起跨域请求时,同源策略(Same-Origin Policy)会阻止不同源(协议、域名、端口任一不同)的资源交互。
(1)服务端解决方案(推荐)
方案原理适用场景
CORS服务端设置 Access-Control-Allow-Origin 等响应头主流跨域方案
反向代理通过同域代理服务器转发请求(如 Nginx)前端无法修改服务端时
JSONP利用 <script> 标签不受同源限制的特性仅限GET请求(历史遗留方案)
① CORS(跨域资源共享)

服务端设置响应头:

// Node.js 示例(Express)

app.use((req, res, next) => {res.header('Access-Control-Allow-Origin', '*'); // 或指定域名如 'https://example.com'res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');res.header('Access-Control-Allow-Credentials', 'true'); // 允许携带Cookienext();
});

前端请求:

fetch('https://api.example.com/data', {credentials: 'include' // 如需携带Cookie
});
② 反向代理(Nginx)

nginx

# nginx.conf
server {listen 80;server_name localhost;location /api {proxy_pass https://api.example.com; # 转发到目标服务器proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;}
}

前端直接请求同域接口:

fetch('/api/data') // 实际代理到 https://api.example.com/data
③ JSONP(历史方案)

前端:

function handleResponse(data) {console.log(data);
}
const script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);

服务端需返回JS代码:

// 返回格式:handleResponse({ "data": "xxx" })
(2)纯前端解决方案
方案原理缺点
WebSocket全双工通信协议,不受同源限制需服务端支持
postMessagewindow.postMessage 实现跨窗口通信仅限特定窗口间通信
document.domain强制修改为相同基础域名(如 a.example.com 和 b.example.com)仅限同主域二级域名
① WebSocket(全双工通信)
原理:WebSocket协议不受同源策略限制,适合实时通信。
要求:目标服务器需支持WebSocket。

javascript

// 前端代码

const socket = new WebSocket('wss://api.example.com/socket');
socket.onopen = function() {socket.send(JSON.stringify({ action: 'subscribe' }));
};
socket.onmessage = function(event) {console.log('收到消息:', JSON.parse(event.data));
};
socket.onerror = function(error) {console.error('WebSocket错误:', error);
};
② postMessage(跨窗口通信)
原理:通过 window.postMessage 实现跨域窗口/iframe间通信。
场景:主站与嵌入式iframe或弹出窗口交互。

主页面代码

// 发送消息到子iframe(或弹出窗口)
const iframe = document.getElementById('my-iframe');
iframe.contentWindow.postMessage({ key: 'value' }, 'https://子页面域名.com'
);// 接收子页面消息
window.addEventListener('message', (event) => {if (event.origin !== 'https://子页面域名.com') return; // 安全检查console.log('收到子页面消息:', event.data);
});

子页面代码

// 接收主页面消息
window.addEventListener('message', (event) => {if (event.origin !== 'https://主页面域名.com') return;console.log('收到主页面消息:', event.data);// 回复消息event.source.postMessage({ response: '数据已收到' },event.origin);
});

10、前端性能优化

(1)网络层优化
① 减少HTTP请求
合并CSS/JS文件(Webpack打包)
使用CSS Sprites雪碧图
内联关键CSS(Critical CSS)
② CDN加速
<!-- 使用CDN加载第三方库 -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.37/dist/vue.global.min.js"></script>
③ 预加载关键资源
<link rel="preload" href="font.woff2" as="font" crossorigin>
(2)渲染优化
① 减少重排重绘
// 批量DOM操作
const fragment = document.createDocumentFragment();
items.forEach(item => fragment.appendChild(createElement(item)));
container.appendChild(fragment);
② 使用transform代替top/left
.box {transform: translateX(100px); /* 触发GPU加速 */
}
③ 防抖/节流
// Lodash节流
window.addEventListener('scroll', _.throttle(updatePosition, 100));
④ 虚拟滚动
<VirtualScroller :items="bigList" item-height="50px"/>
(3)JS执行优化
① 代码分割
// 动态导入
const module = await import('./heavy-module.js');
② Web Worker
// 主线程
const worker = new Worker('task.js');
worker.postMessage(data);
③ 避免内存泄漏
// 移除无用事件监听
window.removeEventListener('resize', handleResize);
(4)缓存策略
① 强缓存
// http
Cache-Control: max-age=31536000
② 协商缓存
// http
ETag: "33a64df5"
If-None-Match: "33a64df5"
③ Service Worker
// 缓存API响应
self.addEventListener('fetch', event => {event.respondWith(caches.match(event.request));
});
http://www.lryc.cn/news/605746.html

相关文章:

  • BatchNorm 一般放在哪里?
  • InfluxDB 与 Python 框架结合:Django 应用案例(二)
  • DoRA详解:从LoRA到权重分解的进化
  • 小杰数据结构(three day)——静以修身,俭以养德。
  • 【Linux系统】库的制作与原理
  • 【数据结构】算法代码
  • 渗透RCE
  • TS 常用类型与语法
  • Cesium 快速入门(六)实体类型介绍
  • Jmeter 性能测试常用图表、服务器资源监控
  • C语言指针(三):数组传参本质、冒泡排序与二级指针详解
  • FISCO BCOS Gin调用WeBASE-Front接口发请求
  • [硬件电路-111]:滤波的分类:模拟滤波与数字滤波; 无源滤波与有源滤波;低通、带通、带阻、高通滤波;时域滤波与频域滤波;低价滤波与高阶滤波。
  • 操作系统数据格式相关(AI回答)
  • 无人船 | 图解基于LQR控制的路径跟踪算法(以欠驱动无人艇Otter为例)
  • 学习笔记《区块链技术与应用》第4天 比特币脚本语言
  • Docker部署Seata
  • Linux核心转储(Core Dump)原理、配置与调试实践
  • 前端-移动Web-day2
  • 野生动物巡查系统(H题)--2025 年全国大学生电子设计竞赛试题
  • ENSP防火墙部署
  • 快速理解RTOS中的pendsv中断和systick中断
  • Java Stream进阶:map是“一对一”,flatMap是“一对多”
  • H.266 vs H.265/AV1/H.264:从工程落地看下一代视频系统的技术演进
  • 前端核心技术Node.js(五)——Mongodb、Mongoose和接口
  • Web3:在 VSCode 中基于 Foundry 快速构建 Solidity 智能合约本地开发环境
  • 硬核技术协同:x86 生态、机密计算与云原生等技术如何为产业数字化转型筑底赋能
  • 云原生环境 DDoS 防护:容器化架构下的流量管控与弹性应对
  • 对git 熟悉时,常用操作
  • Java学习第九十一部分——OkHttp