软开(五):例题(一)
期末复习资料(一):JavaScript 基础核心
一、数组遍历方式对比(重点考点)
考试极可能考”写出多种遍历方式”或”选择合适的遍历方式解决问题”。
1.1 六种遍历方式速查
const arr = [10, 20, 30, 40, 50];
// ① for 循环(最基础,可控制下标)
for (let i = 0; i < arr.length; i++) {
console.log(i, arr[i]); // 0 10 / 1 20 / ...
}
// ② for...of(遍历元素值,不关心下标时用)
for (let item of arr) {
console.log(item); // 10 20 30 40 50
}
// ③ forEach(遍历但无返回值,不能 break)
arr.forEach((item, index) => {
console.log(index, item);
});
// ④ map(遍历并生成新数组,长度不变)
let doubled = arr.map(item => item * 2);
// [20, 40, 60, 80, 100]
// ⑤ filter(过滤,只保留满足条件的元素)
let big = arr.filter(item => item > 25);
// [30, 40, 50]
// ⑥ reduce(累积归并,生成单一结果)
let sum = arr.reduce((acc, item) => acc + item, 0);
// 150
1.2 各方式适用场景对比表
| 方法 | 返回值 | 改变原数组 | 最适合的场景 |
|---|---|---|---|
for / for...of | 无 | 否 | 需要 break/continue 控制流程时 |
forEach | 无(undefined) | 否 | 单纯遍历,执行副作用操作 |
map | 新数组(等长) | 否 | 对每个元素做转换,生成新数组 |
filter | 新数组(更短) | 否 | 筛选出满足条件的元素 |
reduce | 任意单值/对象 | 否 | 汇总、归并、统计计算 |
find | 单个元素 | 否 | 找到第一个满足条件的元素 |
findIndex | 数字下标(-1) | 否 | 找到第一个满足条件元素的位置 |
二、reduce 深度讲解(必考)
2.1 基本语法
array.reduce((accumulator, currentItem, index, array) => {
// 返回值会成为下一次循环的 accumulator
return 新的累积值;
}, 初始值);
accumulator:累积器。第一次循环时是初始值,之后是上一次回调的返回值。currentItem:当前正在处理的元素。- 初始值:
reduce第二个参数,强烈建议写上,避免数组为空时报错。
2.2 四种典型用法
// 用法1:数组求和
let nums = [1, 2, 3, 4, 5];
let total = nums.reduce((acc, n) => acc + n, 0); // 15
// 用法2:统计每个元素出现次数(转对象)
let fruits = ['苹果', '香蕉', '苹果', '橙子', '香蕉', '苹果'];
let count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
// { 苹果: 3, 香蕉: 2, 橙子: 1 }
// 用法3:数组扁平化
let nested = [[1, 2], [3, 4], [5]];
let flat = nested.reduce((acc, arr) => acc.concat(arr), []);
// [1, 2, 3, 4, 5]
// 用法4:订单金额汇总(最接近考试场景)
const orders = [
{ orderid: 1, amount: 150.00 },
{ orderid: 2, amount: 320.50 },
{ orderid: 3, amount: 88.00 },
];
let totalAmount = orders.reduce((acc, order) => acc + order.amount, 0);
// 558.50
2.3 ⭐ 重要考题:订单商品总额计算
// 给定带 items 的订单数组,计算每个订单的商品总价
const orders = [
{
orderid: 10001,
customerid: 'AHDTSM',
items: [
{ productid: 1001, unitprice: 35.50, quantity: 100, discount: 0.03 },
{ productid: 1002, unitprice: 20.00, quantity: 50, discount: 0.00 }
]
},
{
orderid: 10002,
customerid: 'AHPPSP',
items: [
{ productid: 2001, unitprice: 12.00, quantity: 200, discount: 0.05 },
]
}
];
// 用 map + reduce 计算每个订单总金额
const ordersWithTotal = orders.map(order => {
const totalAmount = order.items.reduce((sum, item) => {
// 金额 = 单价 × 数量 × (1 - 折扣)
return sum + item.unitprice * item.quantity * (1 - item.discount);
}, 0);
return {
...order, // 保留订单原有所有字段
totalAmount: parseFloat(totalAmount.toFixed(2)) // 保留两位小数
};
});
console.log(ordersWithTotal);
// ordersWithTotal[0].totalAmount = 35.50*100*0.97 + 20*50*1 = 3443.5 + 1000 = 4443.50
// ordersWithTotal[1].totalAmount = 12*200*0.95 = 2280.00
三、对象操作(重点)
3.1 创建与访问
// 对象字面量
let student = {
name: '张三',
age: 20,
scores: { math: 95, english: 88 },
// 方法作为属性
greet() { return `我是${this.name}`; }
};
// 两种访问方式
console.log(student.name); // '张三'(点语法,静态访问)
console.log(student['name']); // '张三'(括号语法,动态访问)
// 动态属性名(考试重点!)
let field = 'age';
console.log(student[field]); // 20(等价于 student.age)
考试重点:教材代码中大量使用动态属性名,例如:
node[this.state.myTable1.treefield]这里treefield = 'subcategoryid',等价于node.subcategoryid
3.2 对象的展开运算符(浅拷贝)
let original = { a: 1, b: 2, c: 3 };
// 浅拷贝(修改新对象的基本类型属性,不影响原对象)
let copy = { ...original };
// 覆盖某个属性(先展开旧对象,再写新值,新值覆盖旧值)
let updated = { ...original, b: 99 };
// { a: 1, b: 99, c: 3 }
// 合并两个对象(后面的属性覆盖前面的)
let obj1 = { x: 1, y: 2 };
let obj2 = { y: 99, z: 3 };
let merged = { ...obj1, ...obj2 };
// { x: 1, y: 99, z: 3 }
3.3 解构赋值
// 对象解构
let { name, age, scores: { math } } = student;
// 等价于:let name = student.name; let age = student.age; let math = student.scores.math;
// 数组解构
let [first, second, ...rest] = [10, 20, 30, 40, 50];
// first=10, second=20, rest=[30, 40, 50]
// 函数参数解构(教材中 render 函数常见写法)
render() {
let { data, pageno, pagesize } = this.state; // 一次性取出多个状态变量
}
3.4 Object 常用方法
const obj = { a: 1, b: 2, c: 3 };
Object.keys(obj); // ['a', 'b', 'c'] 返回所有键的数组
Object.values(obj); // [1, 2, 3] 返回所有值的数组
Object.entries(obj); // [['a',1],['b',2],['c',3]] 返回键值对数组
// 常见用法:对象转数组后排序
let stats = { '张三': 88, '李四': 95, '王五': 76 };
let sorted = Object.entries(stats)
.sort((a, b) => b[1] - a[1]) // 按成绩降序
.map(([name, score]) => ({ name, score }));
// [{name:'李四',score:95}, {name:'张三',score:88}, {name:'王五',score:76}]
四、树结构操作(重点)
4.1 树的数据结构
// 教材标准树形结构格式
let tree = [
{
id: 'A',
text: '饮料',
level: 1, // 层级深度(根节点=1)
ancestor: '', // 所有祖先节点ID,#号连接(根节点为空)
parentnodeid: '', // 直接父节点ID(根节点为空)
isparentflag: 1, // 是否有子节点(1=有,0=无)
children: [
{
id: 'A1',
text: '非酒精饮料',
level: 2,
ancestor: 'A#',
parentnodeid: 'A',
isparentflag: 0,
children: []
}
]
}
];
4.2 三大核心操作
① 递归查找节点
function findNode(tree, targetId) {
for (let node of tree) {
if (node.id === targetId) return node; // 找到了,返回
if (node.children && node.children.length > 0) {
let result = findNode(node.children, targetId); // 去子树里找
if (result) return result; // 子树里找到了,向上返回
}
}
return null; // 没找到
}
let found = findNode(tree, 'A1');
// { id: 'A1', text: '非酒精饮料', ... }
② 插入节点
function insertNode(tree, parentId, newNode) {
for (let node of tree) {
if (node.id === parentId) {
// 1. 计算新节点的属性
newNode.level = node.level + 1;
newNode.ancestor = node.ancestor + node.id + '#';
newNode.parentnodeid = node.id;
newNode.isparentflag = 0; // 新节点默认没有子节点
if (!newNode.children) newNode.children = [];
// 2. 更新父节点状态
node.isparentflag = 1;
if (!node.children) node.children = [];
// 3. 插入
node.children.push(newNode);
return true;
}
if (node.children?.length > 0) {
let done = insertNode(node.children, parentId, newNode);
if (done) return true;
}
}
return false;
}
③ 删除节点
function removeNode(tree, targetId) {
for (let i = 0; i < tree.length; i++) {
if (tree[i].id === targetId) {
tree.splice(i, 1); // 直接从数组中删除这个元素
return true;
}
if (tree[i].children?.length > 0) {
let done = removeNode(tree[i].children, targetId);
if (done) {
// 如果删完后没有子节点了,更新 isparentflag
if (tree[i].children.length === 0) tree[i].isparentflag = 0;
return true;
}
}
}
return false;
}
五、自定义函数(重点)
5.1 函数的几种写法
// 写法1:function 声明(有变量提升,可以在声明前调用)
function add(a, b) { return a + b; }
// 写法2:函数表达式(无变量提升)
const add = function(a, b) { return a + b; };
// 写法3:箭头函数(简洁,this 绑定父级上下文)
const add = (a, b) => a + b;
// 写法4:类方法中的箭头函数(教材常用,自动绑定 this)
class MyClass {
handleClick = () => {
this.setState(...); // this 永远正确
}
}
5.2 高阶函数(函数作为参数)
// 函数作为参数(回调函数)
function processData(data, callback) {
return data.map(item => callback(item));
}
let result = processData([1, 2, 3], x => x * 2);
// [2, 4, 6]
// 函数作为返回值(闭包)
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
let counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
六、DOM 对象操作
6.1 常用 DOM 操作
// 获取元素
let el = document.getElementById('myDiv');
let els = document.getElementsByClassName('myClass');
let el2 = document.querySelector('.myClass'); // CSS 选择器
// 修改内容与样式
el.innerHTML = '<b>新内容</b>';
el.textContent = '纯文本内容';
el.style.color = 'red';
el.style.fontSize = '16px';
// 添加/删除 CSS 类
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('selected');
// 滚动定位(教材 scrollTreeNode 中使用)
el.scrollIntoView(); // 滚动页面使元素可见
el.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 平滑滚动
6.2 React 中 DOM 与原生 JS 的区别
在 React 中一般不直接操作 DOM,而是通过 state 驱动视图更新。 但教材中有一个例外:定位树节点时用了原生 DOM 操作:
// antdTrees.js 中的 scrollTreeNode 函数
export const scrollTreeNode = () => {
setTimeout(() => {
// 通过原生 DOM API 找到选中的树节点元素
const selectedNodes = document.getElementsByClassName('ant-tree-treenode-selected');
if (selectedNodes.length > 0) {
selectedNodes[0].scrollIntoView(); // 滚动使其可见
}
});
}
七、模拟例题
【选择题】1. 以下哪个方法适合将数组转换为一个累积的单一值?
- A.
map() - B.
filter() - C.
reduce()✅ - D.
forEach()
【选择题】2. [...undefined] 的运算结果是?
- A. 报错
- B.
[undefined] - C.
[]✅(对象展开{...undefined}结果为{}) - D.
null
注意:数组展开
[...undefined]会报错,但对象展开{...undefined}不会。
【选择题】3. 下列关于 sort() 的说法,哪个正确?
- A. 不传参数时,数字数组按升序排列
- B. 不传参数时,按元素转为字符串后排序 ✅
- C.
sort()不会修改原数组 - D.
sort((a,b) => a-b)是降序排列
【小程序设计题】:
题目:给定以下学生数组,使用 reduce 统计每个科目的平均分,返回一个对象。
const scores = [
{ name: '张三', subject: '语文', score: 85 },
{ name: '李四', subject: '数学', score: 92 },
{ name: '王五', subject: '语文', score: 78 },
{ name: '赵六', subject: '数学', score: 88 },
{ name: '孙七', subject: '英语', score: 95 },
];
// 期望结果:{ 语文: 81.5, 数学: 90, 英语: 95 }
参考答案:
// 第一步:用 reduce 汇总每科的总分和人数
let stats = scores.reduce((acc, item) => {
if (!acc[item.subject]) {
acc[item.subject] = { total: 0, count: 0 };
}
acc[item.subject].total += item.score;
acc[item.subject].count += 1;
return acc;
}, {});
// 第二步:计算平均分
let averages = {};
for (let subject in stats) {
averages[subject] = stats[subject].total / stats[subject].count;
}
console.log(averages); // { 语文: 81.5, 数学: 90, 英语: 95 }