JavaScript 核心知识点详解
JavaScript 核心知识点详解
目录
1. JavaScript 原型,原型链
核心概念
JavaScript 是基于原型(Prototype)的面向对象语言,而非传统的类继承,核心是「原型对象」和「原型链」:
(1)原型(Prototype)
- 每个函数(除箭头函数)都有
prototype属性(原型对象),默认包含constructor(指向函数本身)和__proto__(指向父级原型) - 每个实例对象都有
__proto__属性(隐式原型),指向其构造函数的prototype - 实例可访问原型上的属性 / 方法,实现「属性复用」
(2)原型链
- 当访问对象的属性 / 方法时,JS 先在对象自身查找,找不到则通过
__proto__向上查找父级原型,直到Object.prototype(顶端),这个链式结构就是「原型链」 Object.prototype.__proto__ === null(原型链终点)- 作用:实现继承(如原型继承、组合继承),属性共享
示例
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() { // 原型上的方法
console.log(`Hi, ${this.name}`);
};
const p1 = new Person('张三');
p1.sayHi(); // 自身无sayHi,通过__proto__找到原型上的方法
console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
原型链图示
p1 (实例)
__proto__ → Person.prototype
__proto__ → Object.prototype
__proto__ → null
2. 事件代理是什么?冒泡和捕获?
(1)事件代理(事件委托)
核心:利用事件冒泡,将子元素的事件绑定到父元素上,由父元素统一处理
优势:
- 减少事件绑定数量(优化性能)
- 支持动态新增子元素(无需重新绑定)
场景:列表项点击、动态生成的 DOM 元素事件处理
(2)事件流(冒泡 + 捕获)
JS 事件触发分为三个阶段:
- 捕获阶段:事件从
window向下传播到目标元素(从上到下) - 目标阶段:事件到达目标元素
- 冒泡阶段:事件从目标元素向上传播回
window(从下到上)
事件冒泡:默认阶段,事件从目标元素向上冒泡到父级 / 祖先元素(如点击按钮 → 触发按钮事件 → 触发父 div 事件 → 触发 body 事件)
事件捕获:需通过 addEventListener 第三个参数 useCapture: true 开启,事件从顶层向下捕获到目标元素
示例(事件代理)
<ul id="list">
<li>项1</li>
<li>项2</li>
</ul>
<script>
const list = document.getElementById('list');
list.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') { // 判断目标元素
console.log('点击了', e.target.textContent);
}
});
// 动态新增li仍能触发事件
const newLi = document.createElement('li');
newLi.textContent = '项3';
list.appendChild(newLi);
</script>
事件流图示
捕获阶段:window → document → html → body → div → button
目标阶段:button (事件触发)
冒泡阶段:button → div → body → html → document → window
3. 如何解决跨域问题?
跨域:浏览器因「同源策略」(协议、域名、端口一致)限制,禁止不同源的 AJAX 请求 / 资源访问。
常见解决方案对比
| 方案 | 原理 / 适用场景 | 优缺点 |
|---|---|---|
| CORS(跨域资源共享) | 后端在响应头配置 Access-Control-* |
标准方案,支持所有请求类型;需后端配合 |
| JSONP | 利用 <script> 标签无跨域限制,回调传数据 |
仅支持 GET;易受 XSS 攻击 |
| 代理服务器 | 前端请求同源代理,代理转发到目标服务器 | 无跨域限制;需部署代理 |
| WebSocket | 双向通信协议,无同源限制 | 适用于实时通信;需服务端支持 |
| postMessage | 窗口间通信 API,可跨域传数据 | 适用于 iframe / 多窗口通信 |
示例(CORS,后端 Node.js)
// 后端配置响应头
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*'); // 允许所有源(生产环境指定具体域名)
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
next();
});
JSONP 示例
// 前端
function jsonp(url, callback) {
const script = document.createElement('script');
script.src = `${url}?callback=${callback}`;
document.body.appendChild(script);
}
// 后端返回:callback({ data: 'xxx' })
4. for 循环和 forEach 循环,哪个性能更好?
结论:for 循环性能 > forEach(及 for...of/for...in)
核心原因
- for 循环:纯语法结构,无额外函数调用 / 上下文开销,可通过
break/continue中断,执行效率最高 - forEach:数组原型方法,本质是「遍历 + 回调函数」,每次迭代需创建回调函数上下文、处理
this绑定,且无法中断(return仅跳过当前迭代) - 性能排序:原生
for循环 >for...of>forEach>for...in(for...in需遍历原型属性,最慢)
性能测试示例
const arr = new Array(1000000).fill(1);
// for循环
console.time('for');
for (let i = 0, len = arr.length; i < len; i++) { arr[i]; }
console.timeEnd('for'); // ~1ms
// forEach
console.time('forEach');
arr.forEach(item => item);
console.timeEnd('forEach'); // ~5ms
注意
- 日常开发中,
forEach可读性更高,除非处理超大数据量(百万级 +),否则优先考虑可读性 - 若需中断遍历,只能用
for/for...of(forEach无break)
5. 变量提升的理解?
变量提升(Hoisting):JS 引擎在执行代码前,会将变量 / 函数声明「提升」到其作用域顶部,但赋值操作不提升。
(1)变量提升分类
| 声明方式 | 提升规则 | 示例 |
|---|---|---|
| var 变量 | 声明提升,赋值不提升;默认值为 undefined |
console.log(a); // undefined; var a = 1; |
| let/const 变量 | 「暂时性死区(TDZ)」:声明提升但不可访问 | console.log(b); // 报错; let b = 2; |
| 函数声明 | 声明 + 赋值全提升,优先级高于 var |
fn(); // 执行; function fn() {} |
| 函数表达式 | 仅变量提升(同 var),赋值不提升 |
fn2(); // 报错; var fn2 = function() {} |
(2)核心原理
JS 执行分为「编译阶段」和「执行阶段」:
- 编译阶段:引擎扫描代码,将变量 / 函数声明存入「词法环境」,此时
var变量初始化为undefined,let/const未初始化(TDZ) - 执行阶段:按顺序执行代码,赋值操作生效
示例
// 1. var提升
console.log(a); // undefined(声明提升,赋值未提升)
var a = 10;
// 2. 函数声明提升
foo(); // "hello"(全提升)
function foo() { console.log('hello'); }
// 3. let TDZ
console.log(b); // Uncaught ReferenceError(TDZ内不可访问)
let b = 20;
执行顺序示例
console.log(a); // undefined
var a = 1;
function a() {}
console.log(a); // 1
// 等价于:
function a() {} // 函数声明优先级最高
var a; // var 声明(被函数声明覆盖)
console.log(a); // function a() {}
a = 1; // 赋值
console.log(a); // 1
6. 防抖和节流的理解?
核心场景
解决高频事件(如 resize、scroll、输入框输入、按钮点击)触发过多导致的性能问题,本质是「控制函数执行频率」。
(1)防抖(Debounce)
原理:触发事件后,延迟 n 秒执行函数;若 n 秒内再次触发,重置延迟时间(「最后一次触发」后才执行)
场景:输入框搜索联想、窗口 resize、按钮防重复点击
示例(防抖)
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer); // 重置定时器
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用:输入框联想
const input = document.querySelector('input');
input.addEventListener('input', debounce((e) => {
console.log('搜索:', e.target.value);
}, 500));
(2)节流(Throttle)
原理:触发事件后,n 秒内仅执行一次函数(「固定频率」执行,无视中间触发)
场景:滚动加载、鼠标移动、高频点击
示例(节流)
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) { // 达到间隔才执行
fn.apply(this, args);
lastTime = now;
}
};
}
// 使用:滚动加载
window.addEventListener('scroll', throttle(() => {
console.log('滚动中...');
}, 1000));
核心区别
| 特性 | 防抖(Debounce) | 节流(Throttle) |
|---|---|---|
| 执行时机 | 延迟执行,最后一次触发后执行 | 固定频率执行 |
| 适用场景 | 输入框搜索、resize | 滚动加载、鼠标移动 |
| 比喻 | 电梯等人(最后一个人进来才关门) | 地铁发车(固定时间发车) |
防抖:「延迟执行,最后一次生效」(如输入框停止输入后才搜索)
节流:「固定频率执行」(如每 1 秒执行一次滚动逻辑)
7. 如何渲染几万条数据并不卡住界面?
核心问题
JS 单线程,一次性渲染几万条 DOM 会阻塞主线程(UI 渲染 / 事件响应暂停),导致界面卡顿。
解决方案(核心:分批次渲染 + 非阻塞主线程)
1. 分片渲染(requestAnimationFrame + 分批)
原理:利用 requestAnimationFrame(与浏览器重绘同步,60fps),每次渲染少量数据(如 20 条),分批次完成,不阻塞主线程
优势:贴合浏览器渲染节奏,无卡顿
示例
function renderBigList(data, batchSize = 20) {
const container = document.getElementById('list');
let index = 0; // 当前渲染索引
function renderBatch() {
const end = Math.min(index + batchSize, data.length);
for (; index < end; index++) {
const li = document.createElement('li');
li.textContent = data[index];
container.appendChild(li);
}
// 未渲染完则在下一帧继续
if (index < data.length) {
requestAnimationFrame(renderBatch);
}
}
requestAnimationFrame(renderBatch);
}
// 使用:渲染10万条数据
const bigData = new Array(100000).fill(0).map((_, i) => `数据项${i+1}`);
renderBigList(bigData);
2. 虚拟列表(Virtual List)
原理:仅渲染「可视区域」的 DOM,滚动时动态替换内容(计算可视区域范围,只渲染可见项)
优势:无论数据量多大,DOM 节点数固定(仅可视区域数量),性能最优
场景:长列表(如电商商品列表、聊天记录)
简化版虚拟列表示例
<div id="virtualList" style="height: 500px; overflow: auto; position: relative;">
<div id="content" style="position: absolute; top: 0; left: 0; width: 100%;"></div>
</div>
<script>
const list = document.getElementById('virtualList');
const content = document.getElementById('content');
const itemHeight = 30; // 每条数据高度
const visibleCount = Math.ceil(list.clientHeight / itemHeight); // 可视区域条数
const data = new Array(100000).fill(0).map((_, i) => `虚拟列表项${i+1}`);
// 设置总高度(让滚动条正常显示)
content.style.height = `${data.length * itemHeight}px`;
function renderVisible() {
const scrollTop = list.scrollTop;
const start = Math.floor(scrollTop / itemHeight); // 可视区域起始索引
const end = start + visibleCount; // 可视区域结束索引
const visibleData = data.slice(start, end); // 可视区域数据
// 清空并渲染可视区域
content.innerHTML = '';
visibleData.forEach((item, i) => {
const li = document.createElement('li');
li.style.height = `${itemHeight}px`;
li.style.position = 'absolute';
li.style.top = `${(start + i) * itemHeight}px`;
li.textContent = item;
content.appendChild(li);
});
}
// 滚动时触发渲染(加节流优化)
const throttledRender = throttle(renderVisible, 16);
list.addEventListener('scroll', throttledRender);
// 初始化渲染
renderVisible();
</script>
3. Web Worker(可选)
原理:若数据需预处理(如格式化、过滤),将耗时计算放到 Web Worker(子线程),避免阻塞主线程
配合分片渲染:Worker 处理数据后,分批次传给主线程渲染
示例
// main.js
const worker = new Worker('data-processor.js');
worker.postMessage(bigData);
worker.onmessage = (e) => {
const processedData = e.data;
renderBigList(processedData);
};
// data-processor.js
self.onmessage = (e) => {
const data = e.data;
// 处理数据
const processed = data.map(item => processItem(item));
self.postMessage(processed);
};
关键优化点
- 减少 DOM 操作:批量创建 DOM(如
DocumentFragment),避免频繁appendChild - 避免同步重排 / 重绘:渲染时隐藏容器(
display: none),渲染完成后显示 - 防抖滚动事件:虚拟列表的
scroll事件加节流,减少渲染次数
方案对比
| 方案 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| 分片渲染 | 小数据量(万级) | 实现简单,无卡顿 | DOM 节点多,内存占用大 |
| 虚拟列表 | 大数据量(十万/百万级) | DOM 节点少,性能最优 | 实现复杂,需固定高度 |
| Web Worker | 数据预处理耗时 | 不阻塞主线程 | 需额外线程,通信开销 |
总结
- 小数据量(万级):分片渲染(
requestAnimationFrame)足够 - 大数据量(十万 / 百万级):虚拟列表(Virtual List)是最优解
- 核心思想:「减少一次性 DOM 操作」+「仅渲染必要内容」+「不阻塞主线程」
最佳实践总结
原型与原型链
- 理解
prototype和__proto__的区别 - 掌握原型链查找机制
- 合理使用原型实现继承
事件处理
- 优先使用事件代理处理动态元素
- 理解事件冒泡和捕获的区别
- 合理使用事件委托优化性能
跨域解决方案
- 现代项目优先使用 CORS
- 了解 JSONP 的局限性
- 开发环境可使用代理服务器
性能优化
- 大数据量遍历优先使用
for循环 - 高频事件使用防抖或节流
- 长列表使用虚拟列表或分片渲染
变量提升
- 优先使用
let/const避免提升问题 - 理解 TDZ(暂时性死区)
- 注意函数声明和函数表达式的区别
更多推荐



所有评论(0)