以下为JavaScript基础篇面试考察点总结,具体知识点不会太详细,主要梳理面试核心考察点,为面试做准备。掌握这些基础知识,不仅是完成日常工作的基本要求,更是未来向中高级发展的基石。
- 2025前端面试题-Vue3基础篇
- 2025前端面试题-Vue3进阶篇
- 2025前端面试题-React基础篇
- 2025前端面试题-React进阶篇
- 2025前端面试题-React高阶篇
- 2025前端面试题-TS理论篇
- 2025前端面试题-TS实战篇
一、 数据类型与内存机制
1. 基本数据类型
StringNumberBooleanNullUndefinedSymbol(ES6)BigInt(ES2020)
特点
- 存储在 栈内存 (Stack) 中;
- 当一个变量被赋值为基本类型时,它直接持有该值;
- 变量之间的赋值是值的复制。
2. 引用数据类型
主要是 : Object (包括普通对象、Array、Function、Date等)。
特点
- 值本身(即对象实体)存储在堆内存 (Heap)中,而变量的值是指向该堆内存地址的引用 ,该引用存储在栈内存中;
- 变量之间的赋值是引用的复制。
面试题代码示例
示例1: 基本类型赋值
js
let a = 10;
let b = a;
b = 20;
console.log(a); // 输出什么?
// 解析: a 的值仍然是 10。因为 b = a 是值的复制,后续对 b 的修改不影响 a。
示例2: 引用类型赋值
js
let obj1 = { name: 'Mickey' };
let obj2 = obj1;
obj2.name = 'Donald';
console.log(obj1.name); // 输出什么?
// 解析: 输出 'Donald'。因为 obj2 = obj1 是引用的复制,obj1 和 obj2 指向同一个堆内存地址。
// 通过 obj2 修改对象的属性,obj1 也会发生这个变化。
3. 类型判断
typeof
通常用于判断基本类型,但有两个特例:
typeof null返回'object'(这是一个历史遗留问题)typeof无法细分对象类型(如Array,Date)。
instanceof
用于判断一个对象是否是某个构造函数的实例 ,其原理是基于原型链的查找。
js
console.log(typeof 123); // "number"
console.log(typeof null); // "object"
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
const arr = [];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true (因为 Array 的原型链最终指向 Object)
二、 变量声明: var, let 与 const
这是ES6以来前端面试的必考点,要求清晰阐述三者的核心差异。
| 特性 | var |
let |
const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 ({}) |
块级作用域 ({}) |
| 变量提升 (Hoisting) | 存在 (声明提升,赋值不提升) | 不存在 (存在暂时性死区TDZ) |
不存在 (存在暂时性死区 TDZ) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 重新赋值 | 允许 | 允许 | 不允许 |
面试题代码示例
1. 作用域与变量提升
js
// var 的变量提升
console.log(foo); // undefined
var foo = 'Hello';
// let 的暂时性死区
// console.log(bar); // Uncaught ReferenceError: Cannot access 'bar' before initialization
let bar = 'World';
2. const 的不变性
面试会考察是否理解 const 的"不变"是指针(引用)的不变,而不是其指向内容的不变。
ini
const person = {
name: 'Mickey',
age: 30
};
// person = {}; // 会抛出 TypeError: Assignment to constant variable.
person.age = 31; // 这是允许的,因为我们修改的是对象内部的属性,而不是 person 变量的引用。
console.log(person.age); // 31
三、 运算符
-
算术运算符 :
+,-,*,/,%(取模),**(ES7幂运算)。 -
赋值运算符 :
=,+=,-=,*=,/=,%=。 -
比较运算符 :
==,===,!=,!==,>,<,>=,<=。 -
逻辑运算符 :
&&(逻辑与),||(逻辑或),!(逻辑非)。 -
三元运算符 :
condition ? exprIfTrue : exprIfFalse。
面试题代码示例
&& 和 || 具有短路特性,在实际编码中非常有用。
js
// && (与): 如果第一个操作数为假值(false),则直接返回第一个操作数,不再计算第二个。
const result1 = 0 && console.log('This will not run');
console.log(result1); // 0
const user = { name: 'Mickey' };
const userName = user && user.name; // 安全地访问嵌套属性
console.log(userName); // 'Mickey'
// || (或): 如果第一个操作数为真值(true),则直接返回第一个操作数,不再计算第二个。
const result2 = 'Hello' || console.log('This will not run');
console.log(result2); // 'Hello'
function greet(name) {
name = name || 'Guest'; // 为函数参数提供默认值 (ES6之前常用)
console.log(`Welcome, ${name}`);
}
greet(); // Welcome, Guest
greet('Mickey'); // Welcome, Mickey
四、 流程控制
条件语句
-
if...else if...else: 用于处理一系列的条件判断。 -
switch: 用于基于一个表达式的值来执行多个不同的操作,通常比一长串if...else if更清晰。
循环语句
-
for: 最常用的循环,当循环次数已知时非常方便。 -
while: 在指定条件为真时重复执行代码块。 -
do...while: 与while类似,但至少会执行一次代码块。 -
for...of(ES6): 用于遍历可迭代对象(如Array,String,Map,Set)的值。
代码示例
js
// switch 语句
function getFruitColor(fruit) {
let color;
switch (fruit) {
case 'apple':
color = 'red';
break; // break 语句至关重要,否则会发生"穿透"
case 'banana':
color = 'yellow';
break;
case 'grape':
color = 'purple';
break;
default:
color = 'unknown';
}
return color;
}
console.log(getFruitColor('banana')); // yellow
// for 循环
const numbers = [10, 20, 30];
for (let i = 0; i < numbers.length; i++) {
console.log(`Element at index ${i} is ${numbers[i]}`);
}
// for...of 循环 (更简洁的遍历)
for (const number of numbers) {
console.log(`Element value is ${number}`);
}
五、 函数
函数是JavaScript的一等公民,是组织和复用代码的基本单元。
函数声明
js
function sum(a, b) {
return a + b;
}
特点:存在函数提升 (Hoisting) ,即在声明之前就可以调用。
函数表达式
js
const multiply = function(a, b) {
return a * b;
};
特点:作为变量赋值,遵循变量的提升规则(var提升声明,let/const不提升),在赋值前无法调用。
箭头函数 (ES6)
js
const subtract = (a, b) => a - b;
特点:语法简洁,且其 this 值由词法作用域决定,而不是由调用方式决定。
参数与返回值
函数可以接收参数,并通过 return 关键字返回一个值。如果没有 return 语句,函数默认返回 undefined。
面试题代码示例 (函数提升)
js
console.log(declaredSum(5, 5)); // 10 (正常工作,因为函数声明被提升)
function declaredSum(a, b) {
return a + b;
}
// console.log(expressedSum(5, 5)); // TypeError: expressedSum is not a function
// (如果用var声明,变量提升了但赋值没提升,此时expressedSum是undefined)
// Uncaught ReferenceError: Cannot access 'expressedSum' before initialization
// (如果用const/let声明,存在暂时性死区)
const expressedSum = function(a, b) {
return a + b;
};
六、 对象
创建
通常使用字面量语法 const obj = {};。
属性访问
-
点表示法 (
.) :obj.key,当属性名是有效的标识符时使用。 -
方括号表示法 (
[]) :obj['key'],当属性名包含特殊字符、是变量、或需要动态计算时使用。
属性操作
-
添加/修改 :
obj.newKey = 'value';或obj['newKey'] = 'value';。 -
删除 :
delete obj.key;。 -
for...in循环 : 用于遍历对象自身及原型链上的可枚举属性。
代码示例
js
const car = {
make: 'Toyota',
model: 'Camry',
'year-of-manufacture': 2022, // 包含特殊字符的键
start: function() {
console.log('Engine started!');
}
};
// 访问属性
console.log(car.make); // 'Toyota'
console.log(car['year-of-manufacture']); // 2022 (必须用方括号)
// 修改属性
car.model = 'Corolla';
// 添加属性
car.color = 'blue';
// 删除属性
delete car.color;
// 遍历对象
for (const key in car) {
// 推荐使用 hasOwnProperty 确保只遍历对象自身的属性
if (Object.prototype.hasOwnProperty.call(car, key)) {
console.log(`${key}: ${car[key]}`);
}
}
七、 数组
-
创建 :
const arr = []; -
访问 : 通过索引
arr[0]。 -
length属性: 获取或设置数组的长度。
常用方法
改变原数组
-
push()/pop(): 在末尾添加/删除元素。 -
unshift()/shift(): 在开头添加/删除元素。 -
splice(): 在任意位置添加/删除/替换元素。 -
sort(): 对数组进行排序。
不改变原数组
-
concat(): 合并数组。 -
slice(): 提取数组的一部分。 -
forEach(): 遍历数组,对每个元素执行一个函数。 -
map(): 创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。 -
filter(): 创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。 -
find(): 返回数组中满足提供的测试函数的第一个元素的值。
代码示例 (map vs forEach)
js
const ids = [1, 2, 3, 4];
// forEach: 仅用于迭代,没有返回值
ids.forEach(id => {
console.log(`Processing ID: ${id}`);
});
// map: 用于转换数组,返回一个新数组
const urls = ids.map(id => `/users/${id}`);
console.log(urls); // ['/users/1', '/users/2', '/users/3', '/users/4']
console.log(ids); // [1, 2, 3, 4] (原数组未改变)
八、 DOM 操作与事件模型
尽管如今的框架都封装了'完美'的DOM操作,但原生DOM API是所有上层建筑的基础。
1. 高效的DOM节点操作
当需要向DOM中添加大量元素时,直接在循环中 appendChild 会导致多次重排 (reflow) 和重绘 (repaint) ,影响性能。推荐使用 DocumentFragment 作为临时容器。
js
const list = document.getElementById('my-list');
const fragment = document.createDocumentFragment(); // 创建一个文档片段
for (let i = 0; i < 1000; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i + 1}`;
fragment.appendChild(item); // 先将所有新节点附加到 fragment
}
list.appendChild(fragment); // 最后一次性将 fragment 添加到真实DOM,只触发一次重排。
2. 事件委托
利用事件冒泡机制,将事件监听器绑定在父元素上,统一处理子元素的事件。这在动态添加子元素的场景下尤为高效。
html
<ul id="parent-list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
js
const parentList = document.getElementById('parent-list');
parentList.addEventListener('click', function(event) {
// event.target 是实际被点击的元素
if (event.target && event.target.nodeName === 'LI') {
console.log(`点击了: ${event.target.textContent}`);
}
});
九、 AJAX 与本地存储
AJAX
fetch API 是目前主流的异步请求方案,它基于 Promise,语法更简洁。
js
async function fetchUserData() {
try {
const response = await fetch('https://api.example.com/user/1');
// fetch 不会因 404/500 等 HTTP 错误状态码而 reject Promise,需要手动检查
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json(); // 解析 JSON 响应体
console.log(data);
} catch (error) {
console.error('Fetch failed:', error);
}
}
fetchUserData();
本地存储方案对比
| 方案 | 生命周期 | 存储大小 | 与服务器通信 |
|---|---|---|---|
localStorage |
永久,除非手动清除 | 约 5MB | 否,仅在客户端 |
sessionStorage |
浏览器标签页关闭即清除 | 约 5MB | 否,仅在客户端 |
Cookie |
可设置过期时间 | 约 4KB | 每次HTTP请求都会携带 |
以上是JavaScript基础篇面试考察点的内容,如有错误欢迎评论区指正。