冰岩前端组 2024 年笔试 - 个人解答
需要特别注意,本文内容并非标准答案,均为本人个人认识,仅留作自己学习使用。
本文正在施工中,并非最终版本,只有前半部分题目的解答。
Part 0
Q1
- rem: root em, 代表根元素的
font-size
大小 - vw: viewport width, 代表 viewport 宽度的 1%, 即为浏览器中网站可见内容宽度的 1%
- vh: viewport height, 代表 viewport 高度的 1%, 即为浏览器中网站可见内容高度的 1%
px 为绝对单位,而 rem vw vh 为相对单位,后者在进行响应式布局时可以进行自适应的修正,为开发带来便利。
Q2
HTML 语义化提高了代码的可读性,使代码的作用清晰可见。
HTML5 添加了:
<canvas>
: 标签定义图形,比如图表和其他图像。该标签基于 JavaScript 的绘图 API.<audio>
: 定义音频内容<video>
: 定义视频(video 或者 movie)<source>
: 定义多媒体资源<video>
和<audio>
<embed>
: 定义嵌入的内容,比如插件。<track>
: 为诸如<video>
和<audio>
元素之类的媒介规定外部文本轨道。<datalist>
: 定义选项列表。请与<input>
元素配合使用该元素,来定义 input 可能的值。<keygen>
: 规定用于表单的密钥对生成器字段。<output>
: 定义不同类型的输出,比如脚本的输出。<article>
: 定义页面独立的内容区域。<aside>
: 定义页面的侧边栏内容。<bdi>
: 允许您设置一段文本,使其脱离其父元素的文本方向设置。<command>
: 定义命令按钮,比如单选按钮、复选框或按钮<details>
: 用于描述文档或文档某个部分的细节<dialog>
: 定义对话框,比如提示框<summary>
: 标签包含<details>
元素的标题<figure>
: 规定独立的流内容(图像、图表、照片、代码等等)。<figcaption>
: 定义<figure>
元素的标题<footer>
: 定义<section>
或<document>
的页脚。<header>
: 定义了文档的头部区域<mark>
: 定义带有记号的文本。<meter>
: 定义度量衡。仅用于已知最大和最小值的度量。<nav>
: 定义导航链接的部分。<progress>
: 定义任何类型的任务的进度。<ruby>
: 定义 ruby 注释(中文注音或字符)。<rt>
: 定义字符(中文注音或字符)的解释或发音。<rp>
: 在 ruby 注释中使用,定义不支持 ruby 元素的浏览器所显示的内容。<section>
: 定义文档中的节(section、区段)。<time>
: 定义日期或时间。<wbr>
: 规定在文本中的何处适合添加换行符。
Refer to: https://www.runoob.com/html/html5-new-element.html
Q3
CSS 伪类用于扩展选择器,用于选择位于某一特殊状态的真实存在的元素。
而 CSS 伪元素并非选择某一真实存在的元素,而是相当于在一个虚构出的元素上应用效果。
Q4
text-overflow: ellipsis;
Q5
50px. 相接触的两个元素的外边距会合并,若二者均为正 / 负,则取其中绝对值最大的作为二者间距。
顺带一提,若两个外边距为一正一负,则会将二者相加,将和作为二者间距。
将其更改为 margin-top: 70px;
即可。
Q6
<input type="radio" name="bingyan" />
<div><span> 选项A </span></div>
<input type="radio" name="bingyan" />
<div><span> 选项B </span></div>
<style>
input[type="radio"]:checked+div span {
color: blue;
}
</style>
添加 name 属性,使两个 radio 互斥。
input[type="radio"]
选择单选项,而 :checked
伪类选择了被选中的 radio input.
最后,+
选择了紧接在 radio input 之后的 div 内的 span 元素。
Q7
margin: auto;
- 在父元素中使用
display: flex; justify-content: center; align-items: center;
Q8
<div class="triangle">▲</div>
<style>
.triangle {
-webkit-text-stroke: 4px black; /* 描边宽度及颜色 */
color: transparent;
font-size: 50px; /* 三角形大小 */
}
</style>
利用了三角型的文字,并使用 -webkit-text-stroke
设置了文字描边。
Refer to: Stack Overflow
也可以使用 svg 或 css 的 clip-path 来实现。
Part 1
Q1
基本类型:
- string
- number
- boolean
- null
- undefined
- bigint
- symbol
对象类型:
- Object
- Array
- Function
- Date
- RegExp
- Error
Q2
null
是 JS 基本类型之一,虽然typeof null == 'object'
,但是它真的是一个基本类型。它用于指代对象的值为空,或许可以算是一个空指针?undefined
也是 JS 基本类型之一,代表变量还未赋值或对象的属性不存在。NaN
(Not a Number),当一个数字无法被表示时,会返回 NaN。值得注意的是,NaN 与任何值都不相等,包括自身。
Q3
for in
用于遍历对象的可枚举字符串属性。
而 for of
用于遍历可迭代对象的值序列,就像 forEach
那样。
Q4
JS 的事件循环包含一个或多个 task queue 以及一个 microtask queue :
- 从 task queue 中取出并执行一个 task,这个 task 被常常称为一个宏任务。
- 宏任务执行完成后,检查 microtask queue 内是否有任务,若有,则依次执行至 queue 为空。
- 重复上述过程。
Q5
#button:hover {
border: 3px solid rgb(111, 219, 53); /* 边框颜色 */
document.getElementById("button").addEventListener("click", () => {
alert("button clicked 🎉");
});
Q6
下两行的空格代表换行:
- 5 5 5 5 5
- 0 1 2 3 4
1 中使用了 var 声明,而 2 使用了 let 声明。
在 for 循环中使用 var 声明的变量会被提升到函数作用域的顶部,而 let 声明的变量则只是语句的局部变量,在每次循环中都会被重新创建。也就是说,这相当于 var i 是在 for 外部声明的,而 let i 是 for 内部的一个局部变量。
因此,在 for 循环结束后,setTimeout 开始依次执行时,它们引用的 var i 是同一个变量,因此输出的值均为 5,而 let i 则是在每次循环中重新创建的,因此输出的值为 0 1 2 3 4。
Q7
undefined
5
1
6
20
200
根据 JS 的作用域链,访问变量时会先在当前作用域查找,若找不到则会向上查找直至全局作用域。
L1 中定义了全局变量 var a = 1,此时全局作用域中 a = 1.
在 function fn 中,又定义了 var a,使得 fn 函数作用域中也有了 a 变量。
L4 输出变量 a 的值,由于 L4 在 fn 作用域中,而此时 a 仍未初始化,因此输出 undefined。
L5 将 a 的值赋为 5,此时 fn 作用域中的 a = 5。
L6 在函数 fn 中,因此输出 fn 作用域中 a 的值,输出 5。
L7 使 fn 作用域中 a 自增,此时 fn 作用域中 a = 6。
L8 为 var a,但是由于 var 定义会被提升至作用域顶部,因此此行无效果。
L9 调用 fn3 函数,而 fn3 位于全局作用域,因此输出全局作用域中的 a,即输出 1。
同时,fn3 中还将 a 的值赋为 200,此时全局作用域中 a = 200。
L10 调用 fn2 函数,而 fn2 位于 fn 函数作用域中,因此输出fn 作用域中的 a,即输出 6。
同时,fn2 中还将 a 的值赋为 20,使 fn 作用域中 a = 20。
L11 输出变量 a 的值,由于 L11 在 fn 作用域中,因此输出 20。
最后在 L25 中,输出全局作用域中的 a,即输出 200。
Q8
1 == true // true
1 + 1 == true // false
'' == false // true
0 == '' // true
0 == '0' // true
0 === '0' // false
['a'] === 'a' // false
['a'] == 'a' // true
[] == [] // false
[] == true // false
![] == '' // true
[] == ![] // true
Q9
<body>
指 document.body 的值,在控制台中显示的内容可能因浏览器而异。
<body>
Promise, <body>
setTimeout, <body>
setTimeout(() => {
console.log("setTimeout, ", document.body);
});
Promise.resolve().then(() => {
console.log("Promise, ", document.body);
});
console.log(document.body);
setTimeout 在 task queue 中创建了一个新的 task,而 Promise 的 then 方法则会创建一个 microtask。
根据事件循环,此时先执行当前的 task,即 console.log(document.body);
,输出 <body>
。
输出完成后,task 执行完毕,开始依次执行 microtask queue 中的任务,输出 Promise, <body>
。
最后,task queue 中的 setTimeout 任务执行,输出 setTimeout, <body>
。
Q10
function compareVersion(a, b) {
if (a === b) return 0;
let splittedA = a.split(/[\.-]/);
let splittedB = b.split(/[\.-]/);
for (let i = 0; i < 3; i++) {
let a = parseInt(splittedA[i]);
let b = parseInt(splittedB[i]);
if (a < b) return -1;
if (a > b) return 1;
}
if (splittedA.length > 3 && splittedB.length === 3) return -1;
if (splittedA.length === 3 && splittedB.length > 3) return 1;
if (splittedA[3] > splittedB[3]) return 1;
if (splittedA[3] < splittedB[3]) return -1;
if (splittedA[4] > splittedB[4]) return 1;
if (splittedA[4] < splittedB[4]) return -1;
return 0;
}
Q11
function _myget(object, path, defaultValue) {
if (typeof path === 'string') {
path = path.replaceAll(/\[(.*?)\]/g, '.$1')
}
let paths = typeof path === 'string' ? path.split('.') : path;
for (let next of paths) {
if (object[next] === undefined) return defaultValue;
object = object[next];
}
return object;
}
Part 3
Q1
useRef 用于储存一个不需要渲染的值,改变其值不会引起组件的重新渲染。
而 useState 用于储存一个状态值,改变其值会引起组件的重新渲染。
useRef 可用于操作 DOM 元素,例如为元素添加事件监听器等。
而 useState 用于储存组件的状态,例如用户输入的值等。
Q2
使用 Context,或使用状态管理库,如 redux 等。
Q3
使用 useMemo hook 将计算结果缓存,避免重复计算和渲染。
Q4
const newPersonList = personList;
并未创建新的数组,而是将 newPersonList 指向了 personList 的引用,此时,newPersonList 和 personList 指向同一个对象。
根据 React 文档,
如果你提供的新值与当前 state 相同(由 Object.is 比较确定),React 将 跳过重新渲染该组件及其子组件。
此时若调用 setState(newPersonList),由于 newPersonList 和 personList 指向同一个对象,因此 React 认为两者相同,不会触发重新渲染。
可改为:const newPersonList = [...personList];
,这样会创建一个新的数组,避免了此问题。
或使用深拷贝,const newPersonList = JSON.parse(JSON.stringify(personList));
。
也可以使用 immer
进行更便捷的修改。
Q5
根据 React 文档,
set 函数仅更新下一次渲染的状态变量。如果在调用 set 函数后读取状态变量,则仍会得到在调用之前显示在屏幕上的旧值。
React 会批量处理状态更新。
因此,三次连续调用 setNumber 时,读取的 number 变量实际上是同一个值,而在三次调用全部结束后,React 才会处理状态更新并触发重新渲染,因此只会使 number +1.
setNumber(number + 3);
- 三次
setNumber(n => n + 1);
Q6
bowl
bowll
首先,使用 useState 将 bowl 的值设置为 'bowl'。
在第一个 useEffect 中,将 bowl 的值设置为了 'bowll',但是由于 useEffect 中的状态更新并不会立即执行的,
因此在第一个 useEffect 中设置的值并不会立即生效,其后的 console.log(bowl) 输出的是 'bowl'。
之后,React 会重新渲染组件,此时 useEffect 中的状态更新生效,
因此重新渲染后再次触发 console.log(bowl),其输出的是 'bowll'。