前端 拼多多4399笔试题目
拼多多
3 选择题
- opacity|visibity|display区别
在CSS中,opacity: 0
和visibility: hidden
都可以让元素不可见,但它们的行为不同:
✅ opacity: 0
(透明度为0)
- 元素仍然占据空间(不移除文档流)
- 元素可以被交互(例如仍然能点击,除非配合
pointer-events: none
) - 元素可通过动画慢慢变回可见
📌 元素是“看不见的”但仍在页面上
visibility: hidden
✅ - 元素仍然占据空间
- 元素不可被交互(鼠标点击不到)
- 通常用于暂时隐藏,但仍保留布局
📌 元素是“不可见的”而且“不可交互的”
哪个“会显示”?
- 两个都不会显示元素(肉眼看不见)
- 但如果你问“哪个会被点击 / 可以响应事件”,
opacity: 0
的元素仍然可响应点击事件(默认),而visibility: hidden
的不行。
补充比较:
属性 | 占位 | 可点击 | 动画支持 | 常见用途 |
---|---|---|---|---|
opacity: 0 | ✅ | ✅ | ✅ | 淡入淡出、过渡动画 |
visibility: hidden | ✅ | ❌ | 一般 | 控制可见性但保留布局 |
如果你还想知道和 display: none
的对比,也可以告诉我。
- 浏览器渲染流程
是的,从www.sitecom
页面中通过<link>
标签加载csdn.css
(假设来自 CSDN 域名,如https://csdn.net/style/csdn.css
),属于跨域资源加载,是否会触发限制取决于资源的类型和浏览器规则:
✅ 结论:不会触发 CORS 跨域限制,但可能被 CSP 限制
加载方式 | 是否跨域 | 是否需要 CORS | 是否容易被拦截 |
---|---|---|---|
<link rel="stylesheet" href="https://csdn.net/style/csdn.css"> | ✅ 是 | ❌ 不需要 CORS | ⚠️ 可能被 CSP 或平台策略限制 |
🔍 详细解释:
1. CSS 是“非简单请求”,但不需要 CORS
- 加载 CSS 文件不会触发浏览器的 CORS 机制(即不会检查
Access-Control-Allow-Origin
)。 - CSS、图片、JS 文件通过
<link>
、<img>
、<script>
加载时,浏览器默认允许跨域加载资源,只要资源本身能被正常访问。
🔸 所以 只要 CSDN 那个 CSS 地址是公开的、状态码为 200,浏览器就会加载它,不会报 CORS 错误。
2. 可能会被 CSP(Content Security Policy)限制
如果你在 www.sitecom
设置了 CSP(内容安全策略):
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self'">
那就只能加载本站点的 CSS,CSDN 的外链就会被拦截,控制台会报 CSP 相关的错误。
✅ 若你需要允许 CSDN 资源,需要在 CSP 中加上:
<style-src 'self' https://csdn.net;
3. 浏览器和平台限制(可能导致问题)
- 浏览器隐私策略或插件:部分插件或隐私增强浏览器可能拦截外部资源。
- CDN 限制:CSDN 的 CDN 可能不允许“站外调用”(Referrer 检查或 token 限制),返回 403。
- 移动端容器或小程序:某些平台(如 WebView、快应用、小程序)限制外链样式表加载,尤其是非 https 的。
✅ 总结
你在 www.sitecom
页面中加载 https://csdn.net/style/csdn.css
:
- 不会触发 CORS 错误
- 可能被 CSP 策略拦截(如有配置)
- 可能被目标站点拒绝服务(如有防盗链)
- 推荐方式:若必须使用,最好将 CSDN 的 CSS 下载后自行托管,避免未来不可控问题。
需要我帮你检查具体 CSS 链接是否可访问或如何托管在本地吗?
- 计算机网络相关内容
使用https
可以增强安全性,但它:
⚠️ 不能完全预防 XSS 和 CSRF 攻击。
✅ HTTPS 的作用是:
- 加密传输内容(防止被中间人窃听或篡改)
- 验证服务端身份(防止中间人伪装网站)
- 防止会话被劫持(如Cookie被拦截)
❌ HTTPS 无法直接防御以下攻击:
1. XSS(跨站脚本攻击)
-
原理:攻击者将恶意脚本注入页面(如用户输入没被过滤)
-
HTTPS 无法阻止恶意脚本执行
-
✅ 防御方式:
- 输入/输出过滤(如 HTML escape)
- 设置 CSP(Content Security Policy)
- 禁用内联脚本、
eval()
等危险特性
2. CSRF(跨站请求伪造)
-
原理:用户登录后被诱导访问恶意链接,对目标站点执行操作(如伪造请求)
-
HTTPS 无法阻止攻击者诱导用户发送请求
-
✅ 防御方式:
- 使用 CSRF Token 验证请求来源
- 设置
SameSite
Cookie 策略(如SameSite=Lax
或Strict
) - 验证 Referer / Origin 头
✅ HTTPS + XSS/CSRF 防护措施 = 安全基础
类型 | HTTPS是否有效 | 真正防护方式 |
---|---|---|
XSS | ❌ 无效 | 输入/输出过滤、CSP |
CSRF | ❌ 无效 | CSRF Token、SameSite Cookie |
中间人 | ✅ 有效 | HTTPS 加密、防止劫持 |
会话劫持 | ✅ 有效 | HTTPS + HttpOnly/SameSite Cookie |
🟢 最佳实践:
- 始终使用 HTTPS(含子域名)
- 开启
HttpOnly
、Secure
、SameSite
Cookie - 使用 CSP 头防御 XSS
- 使用 CSRF Token 机制防御伪造请求
- 输入内容做过滤和转义
如你需要,我可以帮你配置一套完整的 HTTP 安全响应头(CSP、HSTS、X-Frame-Options 等)或演示如何防御 XSS/CSRF。要吗?
3代码题(问答题)
只能说没有环境,纯纯手写,也不知道写没写对,难道还要HR看?很困惑
1. 实现16进制的数的和(含大数)
function hexAddBig(hex1, hex2) {// 将十六进制字符串加上 '0x' 前缀,转换为 BigIntconst num1 = BigInt('0x' + hex1);const num2 = BigInt('0x' + hex2);// 相加并转换为十六进制大写字符串const result = (num1 + num2).toString(16).toUpperCase();return result;
}// 示例
console.log(hexAddBig("FFFFFFFFFFFFFFFF", "1")); // 输出:10000000000000000
2. 实现Git类(根据需求写一个类)
明白了,你希望 `log()` 只显示已经 `push()` 过的提交记录。我帮你改下代码,`push()` 会把当前所有提交标记为已推送,`log()` 只打印这些已推送的提交。---```javascript
class Git {constructor(repoName) {this.repo = repoName || "unnamed-repo";this._commits = [];this._pushedCommits = [];}commit(message, time) {if (!message) {console.warn(`[${this.repo}] Commit message is required.`);return this;}this._commits.push({message,timestamp: time || new Date().toISOString(),});return this;}push() {if (this._commits.length === 0) {console.log(`[${this.repo}] Nothing to push.`);} else {// 把当前未推送的提交都移到 _pushedCommitsthis._pushedCommits.push(...this._commits);this._commits = [];console.log(`[${this.repo}] Pushed ${this._pushedCommits.length} commit(s).`);}return this;}log() {if (this._pushedCommits.length === 0) {console.log(`[${this.repo}] No pushed commits yet.`);} else {this._pushedCommits.forEach(({ message, timestamp }) => {console.log(`${this.repo} ${timestamp} ${message}`);});}return this;}static init(repoName) {return new Git(repoName);}
}
使用示例:
const git = Git.init("my-repo");git.commit("init commit").commit("fix bug").log(); // 这里不会打印任何内容,因为还没 pushgit.push().log(); // 这里会打印之前 push 的提交git.commit("new feature").log(); // 还是只打印 push 过的提交,不包含新提交git.push().log(); // 打印所有 push 过的提交,包括新 feature
这样逻辑是:
commit()
新提交存储在_commits
push()
把_commits
全部移动到_pushedCommits
,代表已经推送过log()
只打印_pushedCommits
3. 基于Promise实现应用场景(接力棒赛跑)
目标回顾
你有一个 teamsConfig
,结构是:
{team1: [runnerFn1, runnerFn2, ...],team2: [runnerFn1, runnerFn2, ...],...
}
每个 runnerFn
是一个返回 Promise 的函数,表示该运动员跑完后异步完成。
你希望:
- 所有队伍的第一个运动员同时开始跑
- 同一队伍的运动员必须一个接一个跑完,串行执行
- 所有队伍的最后一个运动员跑完后,根据耗时排序,输出排名
代码
function relayRace(teamsConfig) {var results = []; // 用来保存每个队伍的完成时间和队名var finishedCount = 0; // 记录已经完成的队伍数var teamNames = Object.keys(teamsConfig); // ['team1', 'team2', ...]return new Promise(function (resolve) {// 遍历所有队伍teamNames.forEach(function (teamName) {var team = teamsConfig[teamName]; // 该队伍运动员数组var startTime = Date.now(); // 记录队伍起跑时间// 定义一个递归函数,用于串行执行队伍中的运动员function runRunner(i) {if (i >= team.length) {// 运动员全部跑完,计算耗时var duration = Date.now() - startTime;// 保存结果,包含队伍名和耗时results.push({ teamName: teamName, duration: duration });finishedCount++; // 完成队伍数量加1// 如果所有队伍都跑完了,排序并返回结果if (finishedCount === teamNames.length) {// 按耗时升序排序,耗时少的排在前面results.sort(function (a, b) {return a.duration - b.duration;});// 结束 Promise,返回排序后的结果resolve(results);}return; // 结束该队伍递归}// 当前运动员跑步,返回 Promisevar runner = team[i];runner().then(function () {// 当前运动员跑完后,递归调用执行下一个运动员runRunner(i + 1);});}// 开始执行第一个运动员,所有队伍第一个运动员会同时触发runRunner(0);});});
}
详细解释
1. 为什么用 Object.keys
?
teamsConfig
是对象,不是数组。我们要拿到队伍名称列表:
var teamNames = Object.keys(teamsConfig);
这样我们能遍历每个队伍,知道“哪个队伍”对应哪个运动员数组。
2. 为什么用 Promise
?
整个比赛是异步的,要等所有队伍跑完才能得到最终排名,所以用 Promise
来包装,方便外面用 .then()
获取结果。
3. 为什么递归 runRunner(i)
?
同一队伍运动员必须一个接一个跑,不能同时跑。调用第 i 个运动员函数得到一个 Promise,跑完后才跑 i+1。
递归写法:
- 如果
i >= team.length
,说明跑完了 - 否则
runner[i]()
返回 Promise,跑完后递归跑下一个
这样保证串行执行。
4. 为什么 finishedCount
?
我们有多个队伍同时比赛,forEach
启动所有队伍跑步。
需要知道什么时候所有队伍都完成,才能排序输出结果。
finishedCount
就是计数器,每当一个队伍跑完(递归结束时)就加一。
当等于队伍总数时,就调用 resolve
,通知外面跑完了。
5. 为什么每个队伍用自己的 startTime
?
题目要求:
- 所有队伍的第一个运动员同时开始跑
- 记录的是每个队伍自己的耗时
我们在 forEach
循环里对每个队伍分别调用 Date.now()
,开始时间就是该队伍第一个运动员实际开始的时刻。
因为是并行启动的,这样自然符合“同时开始”的要求。
6. 排序和输出
比赛结束后,把 results
按耗时排序:
results.sort(function (a, b) {return a.duration - b.duration;
});
耗时越少排名越靠前。
7. 如何使用?
你需要传入格式是:
{team1: [runnerFn, runnerFn, ...],team2: [runnerFn, runnerFn, ...],...
}
每个 runnerFn
是一个返回 Promise 的函数,表示运动员跑步异步完成。
示例代码
function createRunner(name, time) {return function () {return new Promise(function (resolve) {console.log(name + " start running");setTimeout(function () {console.log(name + " finished running");resolve();}, time);});};
}var teamsConfig = {team1: [createRunner("Team1-Runner1", 1000), createRunner("Team1-Runner2", 800)],team2: [createRunner("Team2-Runner1", 900), createRunner("Team2-Runner2", 1200)],team3: [createRunner("Team3-Runner1", 1100), createRunner("Team3-Runner2", 700)],
};relayRace(teamsConfig).then(function (rankings) {console.log("Race finished! Rankings:");rankings.forEach(function (res, i) {console.log("#" + (i + 1) + " " + res.teamName + " - Time: " + res.duration + "ms");});
});
总结
- 使用
Object.keys
遍历队伍名,遍历对象 - 用递归函数串行执行队伍中运动员
- 用计数器判断所有队伍完成
- 结果排序输出,给出排名
这样实现没有用 async/await
和 generator,逻辑清晰,符合题目要求。
如果你需要我帮你写注释版代码,或者进一步改进(比如加超时检测、错误处理),告诉我!
4399
10选择题目
3个简答题目
1.XSS攻击
0.1+0.2等于0.3吗
3个编程题目
1. HTML/CSS基础题目,基于Flex布局实现一个卡片布局
你发的内容是一个数字串:0272700
。请问你想了解或操作它的什么方面?以下是几种可能的解释方向,你可以确认一下你的意图:
🧾 可能的理解方式:
类型 | 解释 |
---|---|
📦 条形码或产品编号 | 可能是商品、仓库或物流中的 SKU 编号(可能需要查数据库或系统) |
🏢 公司代码(如韩国) | 可能是股票代码,比如韩国证券交易所(KRX)中 三星电机 的股票代码是 027270 ,你写的是 0272700 ,可能多了一个 0 。需确认。 |
🔢 数值或编码含义 | 可能是你在做编程、算法题中用到的值,或者某种内部 ID(比如用户编号) |
🧪 作为输入测试数据 | 也可能是你用于测试输入框、校验、正则等功能的字符串 |
❓ 手滑或误输入 | 有时是误发的内容,你可直接说明你想做什么 |
🧭 如果你能补充:
- 你是在哪看到的这个数字?
- 想查什么?(比如含义、归属、正则、格式处理?)
- 是用于前端输入、JS 脚本、接口测试等吗?
我就能更准确地帮你分析或写代码。欢迎补充上下文。
2. 实现一个类
下面是一个基于 ES6+ 实现的 AsyncBatcher
类,支持以下功能:
✅ 功能要求实现
- 构造函数支持
batchSize
和delay
(单位 ms) addTask(task)
方法:添加异步任务(是个函数,返回 Promise)- 任务将在达到批大小或延迟后批量执行
flush()
方法:立即执行当前批次所有等待任务- 每个
addTask
返回的 Promise 可拿到对应任务的结果
✅ 实现代码
class AsyncBatcher {constructor(batchSize, delay) {this.batchSize = batchSize;this.delay = delay;this.queue = [];this.timer = null;}addTask(taskFn) {return new Promise((resolve, reject) => {this.queue.push({ taskFn, resolve, reject });if (this.queue.length === 1) {// 启动延迟定时器this.timer = setTimeout(() => this._runBatch(), this.delay);}if (this.queue.length >= this.batchSize) {this._runBatch();}});}flush() {if (this.queue.length > 0) {this._runBatch();}}async _runBatch() {clearTimeout(this.timer);this.timer = null;const currentBatch = this.queue.splice(0, this.batchSize);await Promise.all(currentBatch.map(async ({ taskFn, resolve, reject }) => {try {const result = await taskFn();resolve(result);} catch (err) {reject(err);}}));}
}
✅ 使用示例
const batcher = new AsyncBatcher(3, 2000);// 模拟异步任务
const createTask = (id) => () =>new Promise((res) => {setTimeout(() => res(`任务 ${id} 完成`), 500);});batcher.addTask(createTask(1)).then(console.log);
batcher.addTask(createTask(2)).then(console.log);
batcher.addTask(createTask(3)).then(console.log); // 达到 batchSize,会立即执行
batcher.addTask(createTask(4)).then(console.log); // 新一批等待 delay 或手动 flushsetTimeout(() => {batcher.flush(); // 手动触发剩余任务
}, 1000);
🧠 注意点
- 所有任务执行都是并发的(
Promise.all
) - 如果任务数没满,但超时到了,也会触发
flush()
可随时让队列中的任务立即执行- 若需要支持自定义批处理逻辑(例如任务之间组合处理),可额外传入一个批处理器函数
如果你需要:
- 限制并发数(如批任务内部并发上限)
- 允许添加回调(batch 完成后通知)
- 支持取消 pending 的任务
我也可以帮你扩展这一类。是否需要增强版?
3. 回溯遍历一个vDOM,获得所有路径
你问得很对!**使用回溯(Backtracking)**思路确实更清晰、更符合“路径查找”的语义,尤其在以下情况:
- 你想找出所有从根到目标节点的路径
- 你想在遍历中动态构建路径、尝试、撤销
- 你希望未来支持提前剪枝、多个目标匹配、路径过滤等功能
✅ 回溯解法本质上就是 DFS + 路径数组动态维护:
相比 DFS 用 newPath = [...path, name]
,回溯更高效,因为它在一个数组上操作,并在每次递归后“回退”一步,避免复制数组。
✅ 回溯实现版本(更接近真实算法语义)
function findComponentPaths(vNode, targetComponent) {const results = [];const path = [];function backtrack(node) {if (!node || typeof node !== 'object') return;const name =typeof node.type === 'string'? node.type: node.type?.name || 'Anonymous';path.push(name);const isMatch =typeof targetComponent === 'string'? name === targetComponent: node.type === targetComponent;if (isMatch) {results.push([...path]); // 必须复制,否则后面修改 path 会影响已存路径}const children = node.children || [];const list = Array.isArray(children) ? children : [children];for (const child of list) {backtrack(child);}path.pop(); // 回退一步}backtrack(vNode);return results;
}
✅ 回溯相比 DFS + 拷贝的优势
比较点 | DFS 复制路径 | 回溯方式(path 共享 + pop) |
---|---|---|
内存消耗 | 每步都复制 path | path 一条,只在 push/pop |
表达意图 | 逻辑能通但略啰嗦 | 更贴近“探索 -> 撤销”的语义 |
扩展剪枝能力 | 略繁琐 | 可随时加 if 控制递归/剪枝 |
性能 | 多次 path.slice() 复制 | 低复制开销,更适合大树 |
✅ 示例输入再次验证
const tree = {type: 'App',children: [{type: 'Layout',children: [{ type: 'Sidebar' },{type: 'Content',children: [{ type: 'Sidebar' }]}]}]
};console.log(findComponentPaths(tree, 'Sidebar'));
// 输出:
// [
// ['App', 'Layout', 'Sidebar'],
// ['App', 'Layout', 'Content', 'Sidebar']
// ]
🧠 总结
-
✅ 回溯方式更适合路径类搜索问题
-
✅ 代码更高效、优雅、语义化
-
如果你想未来加入:
- 匹配多个目标组件
- 限制最大路径长度
- 路径过滤条件
- 找到一个路径就返回(如 DFS 找最近)
那回溯结构更便于扩展。
需要我进一步加上“匹配多个组件”、“路径包含某关键字的路径”、“目标只找第一个”等扩展功能吗?