期末复习

软开(五):例题(一)

2026 年 07 月 01 日 约 7476 字 · 19 分钟 期末复习

期末复习资料(一):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 }