掘金几年第一次发文。新人求关注
7 月中领了大礼包后,一直没有马上找工作的想法。玩儿了俩月,考了个摩托 D 本。国庆节后开始刷题投简历,到今天也有一个月了。算上今天这个面试,一共只有两个面试。
我都无语了,boss 打招呼已读不回,我开的会员强提醒他们,已读不回,要不不合适。前几天有带佬说可能是我简历问题,找他改了改,改完昨天又找他过了一遍,一会儿发完文章我赶紧改简历。周日还有场考试,还得再刷刷题。
A 公司是前同事内推的;
B 公司是半个月前投的简历,可能是一直没有招到合适的人?才把我捞起来?B 司 995。
俩公司都没过,也就不说那么多了,只探讨面试题部分。
另:求内推机会,给个孩子个机会吧
求内推机会,给个机会吧,我打招呼刷岗位都没空刷八股文了。
A 公司
一面
1. 浏览器拿到数据后的渲染过程
浏览器在接收到网页的 HTML、CSS 和 JavaScript 数据后,经历一系列步骤来渲染网页,这个过程通常被称为浏览器的渲染过程。以下是浏览器渲染过程的主要步骤:
- 解析 HTML 和构建 DOM 树:
- 浏览器开始解析 HTML 数据,并构建文档对象模型(DOM)树,表示网页的结构和内容。DOM 树是一个树状结构,其中每个 HTML 元素都被表示为一个节点,包括文本、标签、属性等。
- 解析 CSS 和构建 CSSOM 树:
- 同时,浏览器也开始解析 CSS 数据,并构建 CSS 对象模型(CSSOM)树,表示网页的样式和布局信息。CSSOM 树与 DOM 树一一对应,每个元素都有对应的样式信息。
- 合并 DOM 和 CSSOM,构建渲染树:
- 浏览器将 DOM 树和 CSSOM 树合并,构建渲染树(Render Tree)。渲染树包含了需要渲染的页面内容,但不包括那些被 CSS 隐藏的元素。
- 布局计算:
- 浏览器开始计算每个元素在页面中的精确位置和尺寸,这个过程称为布局计算。浏览器确定元素如何放置和相互布局。
- 绘制页面:
- 浏览器使用计算出的位置和尺寸信息来绘制页面。这包括将页面内容绘制到屏幕上的像素点上,以及处理字体渲染、图像显示等。
- 处理 JavaScript:
- 如果页面包含 JavaScript,浏览器将执行 JavaScript 代码。JavaScript 可能会修改 DOM 树和样式,从而触发重新布局和绘制。
- 反复执行布局和绘制:
- 如果 JavaScript 或用户交互导致页面内容发生变化,浏览器会根据需要执行布局和绘制的步骤。这个过程可能会多次发生。
- 渲染完毕:
- 一旦浏览器完成所有的布局和绘制,页面就会呈现给用户,用户可以看到并与页面进行交互。
这个渲染过程是高度优化的,浏览器会尽力减少布局和绘制的次数,以提供更快的性能。同时,浏览器还可以通过缓存和其他技术来加速页面的加载和渲染。不过,开发人员也可以通过优化 HTML、CSS 和 JavaScript 代码来改善页面的加载速度和性能。
2. 重绘和回流,什么情况下会回流,重绘
重绘(Repaint)和回流(Reflow)是与浏览器渲染引擎相关的概念,它们可以影响网页的性能。以下是它们的定义和触发情况:
- 重绘 (Repaint) :
- 重绘是指更新页面元素的可见样式,而不影响其布局。这包括改变颜色、背景、字体等属性,使元素的外观发生变化。
- 重绘不会影响元素的位置或大小,因此开销较小。
- 触发重绘的情况:改变元素的样式,但不影响布局,如修改背景颜色、文本颜色等。
- 回流 (Reflow) :
- 回流是指更新页面元素的布局,通常涉及大小、位置或结构的变化。当页面布局发生变化时,浏览器需要重新计算元素的位置和大小。
- 回流是开销较大的操作,因为它会触发整个页面的重新布局计算,可能涉及多个元素。
- 触发回流的情况:改变页面布局的操作,如修改元素的宽度、高度、边距、添加/删除元素等。
常见触发回流和重绘的操作包括:
- 修改元素的几何属性,如宽度、高度、位置等。
- 改变字体大小、字体样式等文本属性。
- 增加、删除或改变 DOM 节点。
- 改变浏览器窗口大小。
- 获取某些计算值,如元素的宽度或高度。
- 使用 JavaScript 计算样式或布局。
为了提高性能,应尽量减少回流和重绘的次数。一种方法是尽量合并多个 DOM 操作,以减少回流的频率。另外,使用 CSS3 动画和过渡效果,以及使用 transform 和 opacity 属性,可以减少回流和重绘的开销,提高页面性能。此外,懒加载和异步加载可以帮助减少不必要的渲染操作。
3. CSS 实现左侧定宽 200px,右侧撑满,五种方式
要实现一个左侧定宽 200px,右侧撑满的布局,可以使用以下五种方式:
- Flex 布局:
css
.container {
display: flex;
}
.left {
width: 200px;
}
.right {
flex: 1;
}
- Grid 布局:
css
.container {
display: grid;
grid-template-columns: 200px 1fr;
}
- 浮动 (Float) :
css
.left {
width: 200px;
float: left;
}
.right {
overflow: hidden; /* 清除浮动 */
}
- 绝对定位 (Absolute Positioning) :
css
.container {
position: relative;
}
.left {
width: 200px;
position: absolute;
}
.right {
margin-left: 200px;
}
- CSS 表格布局:
css
.container {
display: table;
}
.left {
width: 200px;
display: table-cell;
}
.right {
display: table-cell;
}
以上方法中,Flex 布局和 Grid 布局是最现代和灵活的方式,特别适用于复杂布局。浮动、绝对定位和 CSS 表格布局也是传统的布局方式,它们在某些情况下仍然有用,但需要更多的样式规则来处理边界情况。选择哪种方式取决于项目的具体需求和所支持的浏览器版本。
4. CSS 实现 0.5px 边框
在标准 CSS 中,通常无法直接设置 0.5px 的边框宽度,因为 CSS 像素是浏览器的最小渲染单位,而不支持亚像素级的渲染。但是,你可以通过一些技巧来模拟实现一个看起来像 0.5px 边框的效果,具体方式如下:
- 使用伪元素 :
你可以使用伪元素::before
或::after
来创建一个额外的边框层,并设置其高度为 0.5px。例如:
css
.element {
position: relative;
border: 1px solid #000; /* 1px 主要边框 */
}
.element::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 0.5px; /* 模拟 0.5px 边框 */
background: #000; /* 边框颜色 */
}
这样,你在元素的上边框下面会创建一个看起来像 0.5px 的额外边框。
- 使用阴影效果 :
你可以使用box-shadow
属性来模拟一个细边框的效果:
css
.element {
border: 1px solid transparent; /* 隐藏主边框 */
box-shadow: 0 0 0 0.5px #000; /* 模拟 0.5px 边框 */
}
这将在元素周围创建一个 0.5px 的模拟边框效果。
请注意,这些方法都是模拟 0.5px 边框的效果,实际上浏览器仍然以整数像素为单位渲染,但这些技巧可以欺骗视觉,使边框看起来像 0.5px。这在某些情况下很有用,但不适用于所有情况。在移动设备和高分辨率屏幕上,这些技巧可能不会像预期的那样工作。
在标准 CSS 中,模拟 0.5px 边框的方法有限,因为 CSS 的渲染机制通常以整数像素为单位。除了上述伪元素和阴影效果的方法,还可以使用 CSS 的 transform
属性来实现一种类似的效果,但请注意这些方法在不同浏览器和设备上可能会有不同的表现。
- 使用
transform
缩放 :
你可以使用transform
缩放来调整元素的高度,以模拟 0.5px 边框。这个方法可能对某些情况有效,但在某些情况下可能会产生不一致的效果。例如:
css
.element {
border: 1px solid #000; /* 1px 主要边框 */
transform: scaleY(0.5); /* 垂直方向缩放 */
}
这将使元素的高度减小一半,从视觉上看起来像是一个 0.5px 的边框。这个方法的效果可能因浏览器和设备而异,因此需要测试和调整以满足特定需求。
请注意,这些方法都是模拟效果,因为 CSS 的渲染机制通常不支持亚像素级别的渲染。这些技巧适用于某些情况,但在不同浏览器和设备上可能会有不同的表现,因此需要谨慎使用并进行测试。如果需要确切的 0.5px 边框,通常需要使用图形处理软件生成具有所需效果的图像或使用其他技术。
5. 判断对象是空对象
javascript
var data = {};
var b = JSON.stringify(data) == "{}";
console.log(b); // true
Object.getOwnPropertyNames()
Object 对象的 getOwnPropertyNames 方法,获取到对象中的属性名,存到一个数组中,返回数组对象,我们可以通过判断数组的 length 来判断此对象是否为空。
javascript
var data = {};
var arr = Object.getOwnPropertyNames(data);
console.log(arr.length == 0); // true
getOwnPropertySymbols
javascript
console.log(Object.getOwnPropertySymbols(a).length === 0) // false
最终解决方案
结合 getOwnPropertySymbols 和 getOwnPropertyNames
javascript
const a = { [Symbol()]: 'a' }
const b = { a: 'a' }
const c = {}
console.log(Object.getOwnPropertyNames(a).length === 0 && Object.getOwnPropertySymbols(a).length === 0) // false
console.log(Object.getOwnPropertyNames(b).length === 0 && Object.getOwnPropertySymbols(b).length === 0) // false
console.log(Object.getOwnPropertyNames(c).length === 0 && Object.getOwnPropertySymbols(c).length === 0) // true
Reflect.ownKeys()
javascript
const a = { [Symbol()]: 'a' }
const b = { a: 'a' }
const c = {}
console.log(Reflect.ownKeys(a).length === 0) // false
console.log(Reflect.ownKeys(b).length === 0) // false
console.log(Reflect.ownKeys(c).length === 0) // true
6. 如何判断页面滚动到底部
判断页面是否滚动到底部通常是为了实现无限滚动加载(Infinite Scroll)等功能。你可以使用 JavaScript 来检测页面是否滚动到底部,以下是一种常见的方法:
javascript
window.onscroll = function() {
// 可见窗口的高度
var windowHeight = window.innerHeight;
// 整个页面的高度
var documentHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
// 已经滚动的高度
var scrollTop = window.scrollY || window.pageYOffset || document.documentElement.scrollTop;
// 判断是否滚动到底部
if (windowHeight + scrollTop >= documentHeight) {
// 滚动到底部的处理逻辑
console.log("已滚动到底部");
}
};
这段代码使用 onscroll
事件监听页面的滚动。在事件处理函数中,首先获取可见窗口的高度 windowHeight
、整个页面的高度 documentHeight
以及已经滚动的高度 scrollTop
。然后,通过比较它们的和是否等于 documentHeight
,判断是否滚动到底部。
当页面滚动到底部时,你可以在处理逻辑中执行加载更多内容的操作。这是实现无限滚动加载的基本思路。
注意:这只是一种基本方法,具体情况可能因项目需求和页面结构而异。在实际应用中,你可能需要根据你的页面结构和需求进行更复杂的滚动检测和加载逻辑。
7. DsBridge 如何理解,以及原理
DSBridge 是一种用于实现 JavaScript 与原生移动应用之间通信的桥接库。它的原理是在原生应用和 WebView(Web视图)之间建立一个通信通道,使得 JavaScript 和原生代码能够互相调用和传递数据。
DSBridge 的原理基本如下:
- 在原生应用中:
- 在原生应用中,你需要引入 DSBridge 的原生库(通常是一个 SDK 或库文件)。
- 该库提供了一组 API,允许你注册原生方法,以便 JavaScript 可以调用这些方法。这些注册的方法通常是被 JavaScript 调用的接口,以便执行原生功能。
- 此外,原生库还提供了一种方式来执行 JavaScript 代码,以便原生代码可以调用 JavaScript 函数。
- 在 WebView 中:
- WebView 是一个用于显示网页内容的组件,通常嵌入在原生应用中。
- 在 WebView 中,你需要引入 DSBridge 的 JavaScript 库。这个库提供了用于与原生通信的接口。
- JavaScript 通过 DSBridge 提供的接口注册函数,以便供原生应用调用。
- JavaScript 可以使用 DSBridge 发送请求,请求原生应用执行注册的原生方法,并将数据传递给原生应用。
- 通信:
- 当 JavaScript 发送请求到原生应用时,请求中包含方法名称、参数等信息。
- 原生应用接收到请求后,根据请求的方法名称调用相应的原生方法,并传递参数。
- 原生方法执行完成后,可以返回结果给 JavaScript。
- 同样,原生应用也可以通过执行 JavaScript 代码来与 WebView 中的 JavaScript 通信。
这种双向通信机制使得 JavaScript 和原生代码可以相互调用,从而实现了 WebView 中的 JavaScript 与原生应用之间的紧密集成。DSBridge 的原理类似于其他类似桥接库,如WebView JavaScript Bridge(WebViewJavascriptBridge)、React Native Bridge 等,它们都提供了一种在 Web 应用和原生应用之间进行通信的方式,从而实现了混合应用的开发。
8. 最快方式寻找一个有序数组的给定值
在有序数组中寻找给定值,最快的方式通常是使用二分查找(Binary Search),也称为折半查找。二分查找是一种高效的搜索算法,它的时间复杂度为 O(log n),其中 n 是数组的大小。以下是使用二分查找来寻找一个有序数组中给定值的示例 JavaScript 代码:
javascript
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid; // 找到目标值,返回索引
} else if (arr[mid] < target) {
left = mid + 1; // 目标值在右半部分
} else {
right = mid - 1; // 目标值在左半部分
}
}
return -1; // 没有找到目标值
}
const sortedArray = [1, 3, 5, 7, 9, 11, 13, 15, 17];
const targetValue = 7;
const index = binarySearch(sortedArray, targetValue);
if (index !== -1) {
console.log(`找到目标值 ${targetValue},位于索引 ${index}`);
} else {
console.log(`未找到目标值 ${targetValue}`);
}
二分查找通过不断将搜索范围减半,迅速找到目标值或确认其不存在。这使得它成为在有序数组中寻找值的最快方式之一。请注意,前提是数组必须有序,否则二分查找将不适用。
9. 洗牌算法
洗牌算法(Shuffle Algorithm)用于随机排列或打乱数组或列表中的元素顺序,以产生随机性。下面是一种常见的洗牌算法,称为 Fisher-Yates 洗牌算法(也称为 Knuth 洗牌算法),它可以在 O(n) 的时间内完成洗牌操作,其中 n 是数组的大小。
javascript
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
// 生成 0 到 i 之间的随机索引
const j = Math.floor(Math.random() * (i + 1));
// 交换 array[i] 和 array[j]
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// 使用示例
const originalArray = [1, 2, 3, 4, 5];
const shuffledArray = shuffleArray(originalArray);
console.log(shuffledArray);
这段代码中,shuffleArray
函数遍历数组,从最后一个元素开始,依次随机选择一个索引 j
(在 0 到 i
之间),然后交换 array[i]
和 array[j]
的位置。通过重复这个过程,每个元素都有平等的机会被放置在数组的不同位置,从而实现了随机洗牌。
Fisher-Yates 洗牌算法确保了每个排列都有相等的概率,是一种高效且常用的洗牌算法。这对于游戏、随机播放音乐列表以及生成随机样本等应用非常有用。
二面
人事问完问题就开始上机操作
实现一个自适应列表
- 通过改变内容元素块的宽度,实现自适应
- 元素问隙固定20px
- 元素最大宽度250px,最小宽度xxx px
- 容器与两边元素无间隙
- 最后一行左对齐,元素宽度和上面元素都一样
- 屏幕宽度没有限制
grid 布局实现,还有 js 也可以实现
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
ul {
display: grid;
grid-template-columns: repeat( auto-fill, minmax(100px, 1fr) );
grid-gap: 20px;
}
li {
list-style: none;
width: 100%;
height: 100px;
border: 1px solid #000;
}
</style>
</head>
<body>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</body>
</html>
三面
CTO 谈天说地 等等,几乎没聊项目
结果 没成,想招前端架构
B 公司
一面
- Vue2,3 的区别
- Vue3 性能更好在哪里
- Vue diff 算法原理
- Vue 生命周期
- Vue v-for 的 key 值是什么,没有唯一性的 key 值怎么办
- css 0.5px
- css 实现三角形
- transform 和 animation 的区别
- svg 懂吗,可以讲一下
其他想不起来了
二面
前端专家
- print(fb) 执行后打印什么,我最先说
100
,然后做完第二题又改口200
,哈哈哈哈哈哈
javascript
function print(fb) {
const b = 200;
fb();
}
const b = 100;
function fb() {
console.log(b); // 100
}
print(fb);
2.fn() 执行后打印什么
javascript
function create() {
let a = 100;
return function () {
console.log(a); // 100
}
}
const fn = create()
const a = 200;
fn();
- 打印什么
javascript
console.log("script start");
const promiseA = new Promise((resolve, reject) => {
console.log("init promiseA")
resolve("promiseA");
});
const promiseB = new Promise((resolve, reject) => {
console.log("init promiseB");
resolve("promiseB");
});
setTimeout(()=> {
console. log("set Timeout run"),
promiseB.then(res => {
console.log("promiseB res => ", res);
})
console.log("setTimeout end");
}, 0);
promiseA.then(res => {
console.log("promiseA res =>> ",res);
});
console.log("script end");
// VM15077:1 script start
// VM15077:4 init promiseA
// VM15077:9 init promiseB
// VM15077:25 script end
// VM15077:22 promiseA res =>> promiseA
// VM15077:14 set Timeout run
// VM15077:18 setTimeout end
// VM15077:16 promiseB res => promiseB
- 问:打印什么?之前看过,忘完了
javascript
function* test(x) {
const y = 2 * (yield (x + 1))
const z = yield (y / 3)
console.log('x', x, 'y', y, '2', z)
return x + y + z
}
const b = test(5)
console.log(b.next())
console.log(b.next(12))
console.log(b.next(13))
// VM16133:8 {value: 6, done: false}
// VM16133:9 {value: 8, done: false}
// VM16133:4 x 5 y 24 2 13
// VM16133:10 {value: 42, done: true}
- 共享屏幕,实现一个 Promise.all
javascript
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
promiseAll([promise1, promise2, promise3])
.then((results) => {
console.log(results); // 输出 [1, 2, 3]
})
.catch((error) => {
console.error(error);
});
之前看的八股忘完了,磕磕绊绊的,没写全
javascript
function MyPromiseAll (promiseList) {
return new Promise((resolve, reject) => {
let thenList = []
promiseList.forEach(item => {
Promise.resolve(item).then((data) =>{
thenList.push(data)
resolve(thenList)
}, (err) => {
reject(err)
})
})
});
}
从网上找了一个
javascript
// 输入不仅仅只有Array
function promiseAll (args) {
return new Promise((resolve, reject) => {
const promiseResults = [];
let iteratorIndex = 0;
// 已完成的数量,用于最终的返回,不能直接用完成数量作为iteratorIndex
// 输出顺序和完成顺序是两码事
let fullCount = 0;
// 用于迭代iterator数据
for (const item of args) {
// for of 遍历顺序,用于返回正确顺序的结果
// 因iterator用forEach遍历后的key和value一样,所以必须存一份for of的 iteratorIndex
let resultIndex = iteratorIndex;
iteratorIndex += 1;
// 包一层,以兼容非promise的情况
Promise.resolve(item).then(res => {
promiseResults[resultIndex] = res;
fullCount += 1;
// Iterator 接口的数据无法单纯的用length和size判断长度,不能局限于Array和 Map类型中
if (fullCount === iteratorIndex) {
resolve(promiseResults)
}
}).catch(err => {
reject(err)
})
}
// 处理空 iterator 的情况
if(iteratorIndex===0){
resolve(promiseResults)
}
}
)
}
if (!Promise.all) Promise.all = promiseAll;
最后,Generator
那个一时做不出来,之前看过,忘了已经。
PromiseAll
写的不好,磕磕绊绊的,最后被叫停了。
最后结果是没过, 虽然是八股文,但是背的还是不好