Web全栈基础能力诊断:HTML/CSS/PHP/JS/Ajax核心考点解析
2026/9/19 10:35:37 网站建设 项目流程

简介:本资源是中山大学软件工程专业《Web2.0程序设计》课程的期末考试真题(B卷)及标准答案,面向高校计算机类专业学生、Web开发初学者及备考者,用于检验HTML/CSS、PHP、JavaScript/DOM、Ajax/XML与Web基础等核心模块的综合掌握程度。试卷共5大题100分,涵盖HTML/CSS渲染追踪、PHP表单验证、DOM操作与事件处理、Ajax异步请求实现、Web安全与协议基础等典型考点,题型设计注重实践能力与原理理解并重。资源为单个PDF文件,大小199KB,内容完整包含试题页、答题要求、评分标准及详细解析思路,排版清晰、标注明确,便于自学自测与考前冲刺。目前已有958人学习下载,适合需要对标名校考核标准、查漏补缺或开展教学参考的师生使用。

1. 这不是一张普通试卷:它是一份 Web2.0 全栈能力诊断图谱

你手头拿到的这份《Web 2.0 程序设计》期末试卷(B 卷),表面看是中山大学软件学院 2010 年代初的开卷考题,但拆开细看,它精准卡在 Web 技术演进的关键断层线上——它不考 React/Vue 框架语法,也不测 Node.js 版本兼容性,而是用五道题,把现代 Web 开发的底层逻辑链条完整串起:从静态渲染(HTML/CSS)→ 服务端逻辑(PHP)→ 客户端交互(JS/DOM)→ 异步通信(Ajax/XML)→ 协议与架构认知(Web Fundamentals)。这种结构至今未过时:2024 年面试一个初级全栈工程师,问的仍是“表单提交后密码怎么脱敏”“点击按钮如何批量删 DOM 节点”“XML 解析怎么防空节点崩溃”——而这些,全在这张试卷的第 2、3、4 题里埋着标准答案。它适合两类人:一是刚学完 HTML+CSS+PHP+JS 基础、正卡在“能写代码但不懂协作流程”的开发者,用来检验自己是否真理解各层职责边界;二是带新人的 Tech Lead,拿它当 Code Review 的检查清单——比如第 2 题 PHP 代码里$_REQUEST的使用、preg_match的正则边界、header("Content-type: text/plain")的响应头设置,全是真实生产环境里高频踩坑点。别被“开卷考试”误导,这张卷子的难点不在记忆,而在判断:什么时候该用textContent而不是innerHTML?为什么XMLHttpRequest必须注册onSuccess而非直接return?这些细节,才是区分“会写”和“能交付”的分水岭。

2. HTML/CSS 渲染追踪:从像素到盒模型的逆向工程

2.1 为什么这道题比“写个响应式页面”更能暴露基础漏洞

HTML/CSS Tracing 题目要求考生手绘浏览器渲染结果,表面是画图,实则是强制你执行一次完整的 CSS 优先级计算和盒模型推演。很多开发者能写出美观页面,却说不清为什么<h1 id="foo">的黄色背景会覆盖<p class="foo">的黑色边框——这恰恰暴露了对选择器权重(Specificity)和层叠(Cascade)机制的模糊认知。本题中#foo(权重 100)>.foo(权重 10)>h1(权重 1),所以<h1>同时获得text-align: centerfont-size: 300%background-color: yellow,而<p class="foo">只继承.fooborder: 2px solid black,其background-color保持透明。这种“反向推导”能力,在调试第三方 UI 组件样式冲突时至关重要——比如当你发现 Ant Design 的 Button 背景色被全局 CSS 覆盖,就得像解这道题一样,逐层检查!important、内联样式、ID 选择器、类选择器的权重顺序。

2.2 关键渲染路径还原:手绘步骤即调试思维训练

要准确还原渲染效果,必须按浏览器实际执行顺序分步推演:

2.2.1 HTML 结构解析阶段
  • <h1 id="foo">heading</h1>:生成块级元素,id="foo"创建唯一标识符
  • <p>A paragraph</p>:普通段落,无 class/id,仅受标签默认样式影响
  • <p class="foo">...<img src="stickman.png" ... />...</p>:此段落同时匹配.foo(类选择器)和<p>(标签选择器),但.foo权重更高,故应用border: 2px solid black
  • <h2>A second heading</h2><h2>标签选择器生效,应用font-family: monospaceborder: 2px dashed black
  • <p id="bar">Another paragraph</p>#barID 选择器生效,但width: 2em在无display: inline-blockfloat时对段落无效(块级元素默认占满父容器宽度)
2.2.2 CSS 样式计算阶段

重点验证三个易错点:

  • #foo { background-color: yellow; }:黄色背景仅作用于<h1>,不影响其内部文本颜色(文本色由color属性控制,此处未设置,继承浏览器默认)
  • h2 { border-bottom: none; }border: 2px dashed black已声明四边,border-bottom: none会覆盖底边,最终呈现为上/左/右边框为虚线,底边无边框
  • #bar, .bar { width: 2em; }#bar匹配成功,但width: 2em对块级<p>无效(除非设display: inline-block),因此<p id="bar">实际宽度仍为 100%

提示:现代开发者常依赖 DevTools 的 Computed Styles 面板,但本题训练的是脱离工具的底层推理能力。当你面对一个无法访问源码的遗留系统时,这种能力能让你在 3 分钟内定位样式失效根源。

2.3 实战验证:用 Chrome DevTools 复现并校验

手动推演后,必须用浏览器验证。以下是可复现的验证步骤:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Web2.0 试卷渲染验证</title> <style> h1 { text-align: center; font-size: 300%; } h2 { font-family: monospace; border: 2px dashed black; border-bottom: none; } .foo { border: 2px solid black; } #foo { background-color: yellow; } #bar, .bar { width: 2em; } </style> </head> <body> <h1 id="foo">heading</h1> <p>A paragraph</p> <p class="foo">Here is a picture of a stick man: <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='200'><circle cx='50' cy='30' r='20'/><line x1='50' y1='50' x2='50' y2='120'/><line x1='30' y1='80' x2='70' y2='80'/><line x1='50' y1='120' x2='30' y2='160'/><line x1='50' y1='120' x2='70' y2='160'/></svg>" alt="stick man" /> I love stick men <br />because they are so great.</p> <h2>A second heading</h2> <p id="bar">Another paragraph</p> </body> </html>

将上述代码保存为render-test.html,在 Chrome 中打开后执行:

  1. 右键<h1>→ “检查”,在 Elements 面板确认background-color: yellow生效
  2. 右键<h2>→ “检查”,在 Styles 面板查看border-bottom是否为none(显示为 strikethrough)
  3. 右键<p id="bar">→ “检查”,在 Computed 面板搜索width,确认值为auto(证明width: 2em未生效)
表:关键 CSS 属性生效状态验证表
元素选择器属性期望值DevTools 实际值是否生效原因
<h1>#foobackground-coloryellowrgb(255, 255, 0)ID 选择器权重最高
<h2>h2border-bottomnonenoneborder-bottom: none覆盖border声明
<p id="bar">#barwidth2emauto块级元素width需配合display: inline-block

3. PHP 表单验证:从正则到安全边界的实战编码

3.1 为什么这道题的 PHP 实现是服务端验证的黄金范式

第 2 题要求处理表单提交并验证姓名、密码、信用卡号,其参考答案虽短(仅 15 行),却浓缩了服务端验证的核心原则:输入即不可信、输出需转义、错误要明确、敏感数据必脱敏。尤其在 2024 年,当 OWASP Top 10 将“不安全的反序列化”和“注入攻击”列为高危项时,回看这份十年前的代码,会发现它早已规避了常见陷阱:不用$_POST而用$_REQUEST(兼容 GET/POST)、密码用*替换而非明文返回、信用卡号用preg_replace("/-/", "", $cc)清洗后再输出——这些都不是“过时语法”,而是经过时间检验的安全实践。更关键的是,它用正则"/^\d{4}[-]?\d{4}[-]?\d{4}[-]?\d{4}$/"精确匹配 16 位数字(允许可选分隔符),而非简单str_replace("-", "", $cc)strlen()判断,避免了1234-5678-1234-56789这类超长输入绕过验证。

3.2 正则表达式深度解析:从模式到边界控制

参考答案中的正则/^\d{4}[-]?\d{4}[-]?\d{4}[-]?\d{4}$/需逐字符解读:

  • ^:字符串开始锚点,防止abc1234-5678-1234-5678这类前缀干扰
  • \d{4}:匹配连续 4 位数字(\d等价[0-9]
  • [-]?:匹配 0 或 1 个连字符-?表示前一项出现 0~1 次)
  • $:字符串结束锚点,防止1234-5678-1234-5678xyz这类后缀干扰

但该正则存在一个隐性缺陷:它允许1234--5678-1234-5678(双连字符)。更健壮的写法应为/^\d{4}(-\d{4}){3}$/,即:

  • ^\d{4}:开头 4 位数字
  • (-\d{4}){3}:匹配 3 组“连字符+4位数字”
  • $:结尾锚点
<?php // 改进版信用卡号验证(支持 1234-5678-1234-5678 和 1234567812345678) function validateCreditCard($cc) { // 方案1:严格格式(推荐用于支付场景) if (preg_match('/^\d{4}(-\d{4}){3}$/', $cc)) { return str_replace('-', '', $cc); // 返回纯数字 } // 方案2:宽松格式(兼容用户粘贴的多种分隔符) $clean = preg_replace('/[^0-9]/', '', $cc); return (strlen($clean) === 16) ? $clean : false; } // 使用示例 $testCases = ['1234-5678-1234-5678', '1234567812345678', '1234--5678-1234-5678']; foreach ($testCases as $cc) { $result = validateCreditCard($cc); echo "$cc => " . ($result ? "valid: $result" : "invalid") . "\n"; } ?>

注意:生产环境绝不能仅靠前端 JS 验证信用卡号。本题中preg_match是服务端最后一道防线,即使用户禁用 JS 或篡改请求,也能拦截非法输入。

3.3 密码脱敏与响应头设置:被忽略的 HTTP 协议细节

参考答案首行header("Content-type: text/plain");常被初学者忽略,但它决定了浏览器如何解析响应:

  • 若无此头,PHP 默认输出Content-Type: text/html,导致print "Successful.\nEric, *******, 1234567812345678\n"被当作文本渲染,换行符\n不生效(HTML 中换行需<br>
  • 设为text/plain后,浏览器以纯文本显示,\n正确换行,符合题目“两行输出”要求

密码脱敏用preg_replace("/./", "*", $pw)而非str_repeat("*", strlen($pw)),看似等效,实则更安全:/./匹配任意单字符(包括 Unicode 符号),避免mb_strlen()编码问题;而str_repeat在多字节字符(如中文)下可能长度错乱。

4. JavaScript/DOM 动态操作:事件驱动下的节点生命周期管理

4.1 为什么getElementsByTagName+for循环删除是经典陷阱

第 3 题要求“点击 Delete 按钮,删除文本值能被输入数字整除的按钮”,参考答案给出两种实现。第一种(for循环)看似直观,却隐藏着 DOM 操作的经典陷阱:

// ❌ 危险写法:边遍历边删除导致索引错位 var buttons = $("q2buttons").getElementsByTagName("button"); for (var i = 0; i < buttons.length; i++) { if (buttons[i].textContent % divisor == 0) { buttons[i].remove(); // 删除后,buttons[i+1] 自动前移至 i 位置 } } // 结果:只删除偶数索引的按钮(如 i=0 删除后,原 i=1 变为 i=0,但循环已跳至 i=1)

正确做法是倒序遍历收集待删节点后批量删除

// ✅ 方案1:倒序遍历(推荐,性能好) var buttons = $("q2buttons").getElementsByTagName("button"); for (var i = buttons.length - 1; i >= 0; i--) { if (parseInt(buttons[i].textContent) % divisor === 0) { buttons[i].remove(); } } // ✅ 方案2:先收集再删除(语义清晰) var buttons = $("q2buttons").getElementsByTagName("button"); var toRemove = []; for (var i = 0; i < buttons.length; i++) { if (parseInt(buttons[i].textContent) % divisor === 0) { toRemove.push(buttons[i]); } } toRemove.forEach(function(btn) { btn.remove(); });

提示:parseInt()显式转换比textContent % divisor更安全,避免"11"字符串参与模运算时的隐式类型转换风险。

4.2textContentvsinnerHTML:DOM 属性选择的语义鸿沟

题目要求“按钮 whose text value is divisible”,关键在text value—— 这明确指向textContent(纯文本内容),而非innerHTML(含 HTML 标签)。若误用innerHTML,当按钮含<b>11</b>时,innerHTML返回"<b>11</b>"parseInt("<b>11</b>")11,看似正确,但若按钮为<span>11</span>parseInt仍得11;而textContent始终返回"11",语义更精准。更重要的是,textContent不会触发 XSS,而innerHTML若拼接用户输入则极度危险。

4.3 事件绑定的现代演进:从onclickaddEventListener

参考答案用$("del").onclick = divideAll,这是 2010 年代典型的事件绑定方式。但现代开发应使用addEventListener,因其支持:

  • 同一事件绑定多个处理器(onclick会被覆盖)
  • 事件捕获/冒泡阶段控制(useCapture参数)
  • 更精确的this绑定
// ✅ 现代写法 document.getElementById("del").addEventListener("click", function() { const divisor = document.getElementById("divisor").value; const buttons = document.querySelectorAll("#q2buttons button"); buttons.forEach(button => { const num = parseInt(button.textContent); if (!isNaN(num) && num % divisor === 0) { button.remove(); } }); });

5. Ajax/XML 数据解析:异步通信中的错误防御与 DOM 注入

5.1XMLHttpRequest的成败关键:状态码与回调分离

第 4 题要求用 Ajax 获取movie.xml并解析<line>标签。参考答案使用 Prototype.js 的Ajax.Request,但核心逻辑适用于原生XMLHttpRequest。关键在于:必须检查readyStatestatus双重条件,而非仅依赖onload

// ✅ 安全的原生 Ajax 实现 function fetchMovieLines() { const xhr = new XMLHttpRequest(); xhr.open("GET", "movie.xml", true); xhr.onreadystatechange = function() { // readyState 4 表示请求完成,status 200 表示服务器成功响应 if (xhr.readyState === 4) { if (xhr.status === 200) { displayLines(xhr.responseXML); } else { console.error("XML load failed:", xhr.status, xhr.statusText); document.getElementById("content").innerHTML = "<p>加载失败,请检查 movie.xml 文件路径</p>"; } } }; xhr.send(); } function displayLines(xmlDoc) { try { const character = xmlDoc.getElementsByTagName("character")[0]; if (!character) throw new Error("未找到 character 元素"); const lines = character.getElementsByTagName("line"); const contentDiv = document.getElementById("content"); contentDiv.innerHTML = ""; // 清空旧内容 for (let i = 0; i < lines.length; i++) { const time = lines[i].getAttribute("time") || "00:00"; const text = lines[i].textContent || ""; const p = document.createElement("p"); p.textContent = "(" + time + ") " + text; contentDiv.appendChild(p); } } catch (e) { console.error("XML 解析错误:", e); document.getElementById("content").innerHTML = "<p>解析失败:" + e.message + "</p>"; } }
表:Ajax 请求状态码防御策略
xhr.status含义应对措施是否需重试
200成功正常解析 XML
404文件未找到提示用户检查路径是(检查文件名大小写)
0跨域或网络错误显示离线提示是(检查 CORS 配置)
500服务器内部错误记录日志,提示稍后重试

5.2 XML 解析的健壮性加固:空节点与属性缺失处理

参考答案假设movie.xml结构完美,但真实场景中<line>可能缺失time属性或textContent为空。加固方案如下:

// 在 displayLines 函数中增强 for (let i = 0; i < lines.length; i++) { // 安全获取 time 属性,提供默认值 const timeAttr = lines[i].getAttribute("time"); const time = timeAttr ? timeAttr.trim() : "00:00"; // 安全获取文本内容,过滤空白 const text = lines[i].textContent ? lines[i].textContent.trim().replace(/\s+/g, " ") : "[无台词]"; // 防止空时间戳导致 DOM 错误 if (!time || !text) continue; const p = document.createElement("p"); p.textContent = "(" + time + ") " + text; contentDiv.appendChild(p); }

5.3 从 XML 到 JSON 的演进启示:为什么现代项目改用 Fetch API

虽然本题指定 XML,但 2024 年主流已转向 JSON。对比可知技术演进逻辑:

  • XML:需responseXML解析,getElementsByTagName查找,属性用getAttribute(),冗余标签多
  • JSONresponse.json()直接得 JS 对象,data.character.lines.map()一行遍历,属性直接line.time
// ✅ 现代 Fetch + JSON 写法(对比 XML) fetch("movie.json") .then(r => r.json()) .then(data => { const contentDiv = document.getElementById("content"); contentDiv.innerHTML = data.character.lines.map(line => `<p>(${line.time}) ${line.text}</p>` ).join(""); }) .catch(e => console.error("JSON 加载失败:", e));

这种演进不是“淘汰 XML”,而是数据格式服务于传输效率与解析成本。当你的后端 API 返回 JSON,前端用fetch+async/await,就自然避开了本题中XMLHttpRequest的复杂状态管理——而这正是中山大学这份试卷留给我们的深层启示:技术会变,但“如何安全、健壮、可维护地连接前后端”的本质问题,十年如一日。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询