普通视图
解锁前端高阶调试:浏览器/IDE/Git技巧分享
【vue高频面试题】第2题:Vue 3 中 ref 和 reactive 的区别是什么?什么时候用哪一个?
【vue高频面试题】第一题:Vue 3 相比 Vue 2,有哪些重大变化?
「无界」全局浮窗组件设计与父子组件最佳实践
轻量+响应式!React瀑布流插件react-masonry-css的详细教程和案例
React 事件系实现
Trae Solo 实战指南:从"会用"到"用好"的协作方法论
Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(一)
把 Claude Code 变成靠谱“协作开发”:一份真的能落地的 Code 提示词指南
效能工具十之接入deepseek实现AI学习PDF文档读后感文件批量生成功能
Grid组件核心参数解析:控制器与布局选项详解
Figma画布协议揭秘:组件实例的SymbolOverrides覆盖机制
前端如何自己实现一个webpack的热更新?
🚀从 autofit 到 vfit:Vue 开发者该选哪个大屏适配工具?
前端高频面试题之CSS篇(二)
1、如何实现两栏布局?
两栏布局指的是左边宽度固定,右边宽度自适应。
DOM 结构如下:
<body>
<div class="box">
<div class="left"></div>
<div class="right"></div>
</div>
</body>
1.1 利用 flex 布局实现
实现思路:将父元素设为 flex 布局,左边元素宽度固定,右边元素设为 flex: 1,即自适应。
.box {
display: flex;
width: 500px;
height: 100px;
}
.left {
width: 200px;
background: yellow;
}
.right {
flex: 1;
background: green;
}
优点:布局灵活、响应式布局。
缺点:IE9 及以下不支持。
1.2 利用 float 布局实现
实现思路:将左边元素设置为浮动元素 float: left,右边元素用 margin-left,这样能让右边元素占据父元素剩余宽度。
.box {
width: 500px;
height: 100px;
}
.left {
float: left;
width: 200px;
height: 100%;
background: yellow;
}
.right {
margin-left: 200px;
height: 100%;
background: green;
}
优点:简单、支持 IE。
缺点:浮动易导致问题(如高度塌陷),不适合复杂布局。
1.3 利用 Grid 布局实现
实现思路:将父元素设为 grid 布局,并设置 grid-template-columns: 200px 1fr 即可。
.box {
display: grid;
grid-template-columns: 200px 1fr;
width: 500px;
height: 100px;
}
.left {
background: yellow;
}
.right {
background: green;
}
优点:二维布局强大,实现多栏布局十分方便。
缺点:IE9 及以下不支持,不适合一维布局。
1.4 利用 position 绝对定位实现
实现思路:父级为相对定位,右边子元素为绝对定位,并同时设置 left、right、top、bottom 值,以实现宽高的自动拉伸。
.box {
position: relative;
width: 500px;
height: 100px;
}
.left {
width: 200px;
height: 100px;
background: yellow;
}
.right {
position: absolute;
left: 200px;
right: 0;
top: 0;
bottom: 0;
background: green;
}
优点:可以精确控制位置。
缺点:脱离文档流,响应式差。
2、如何实现三栏布局?
三栏布局指的是页面分为三栏,左侧和右侧固定宽度,中间自适应。
DOM 结构如下:
<body>
<div class="box">
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</div>
</body>
2.1 利用 flex 布局实现
.box {
display: flex;
width: 500px;
height: 100px;
}
.left {
width: 100px;
background: yellow;
}
.center {
flex: 1;
background: pink;
}
.right {
width: 100px;
background: green;
}
2.2 利用 float 布局实现
.box {
width: 500px;
height: 100px;
}
.left {
float: left;
width: 100px;
height: 100px;
background: yellow;
}
.center {
height: 100px;
margin: 0 100px;
background: pink;
}
.right {
float: right;
width: 100px;
height: 100px;
background: green;
}
这里需要注意,中间栏的 DOM 需要放在最后,以避免浮动元素影响,所以其 DOM 结构如下:
<div class="box">
<div class="left"></div>
<div class="right"></div>
<div class="center"></div>
</div>
2.3 利用 grid 布局实现
.box {
display: grid;
grid-template-columns: 100px 1fr 100px;
width: 500px;
height: 100px;
}
.left {
background: yellow;
}
.center {
background: pink;
}
.right {
background: green;
}
2.4 利用 position 绝对定位实现
.box {
position: relative;
width: 500px;
height: 100px;
}
.left {
position: absolute;
left: 0;
width: 100px;
height: 100px;
background: yellow;
}
.center {
margin: 0 100px;
height: 100px;
background: pink;
}
.right {
position: absolute;
right: 0;
top: 0;
width: 100px;
height: 100px;
background: green;
}
2.5 经典三栏布局之圣杯布局
圣杯布局实现三列布局左右列固定宽度、中间列自适应,其原理是通过相对定位和负边距来实现侧边栏的定位。
<style>
.box {
padding: 0 150px 0 200px;
}
.wrapper::after {
display: table;
content: '';
clear: both;
}
.column {
float: left;
height: 200px;
}
.left {
width: 200px;
position: relative;
margin-left: -100%;
right: 200px;
background-color: aqua;
}
.center {
width: 100%;
background-color: red;
}
.right {
width: 150px;
margin-right: -150px;
background-color: green;
}
</style>
<body>
<div class="box">
<!-- 中间列 center 放第一个是为了在文档流中优先渲染,因为 DOM 是从上往下依次渲染的-->
<div class="center column">center</div>
<div class="left column">left</div>
<div class="right column">right</div>
</div>
</body>
2.6 经典三栏布局之双飞翼布局
双飞翼布局实现三列布局左右列固定宽度、中间列自适应,其原理是通过使用嵌套的 div 元素来实现侧边栏的定位,以及使用负外边距将主内容区域撑开。
<style>
.box {
background-color: red;
width: 100%;
}
.column {
float: left;
height: 200px;
}
.center {
margin: 0 150px 0 200px;
}
.left {
width: 200px;
background-color: aqua;
margin-left: -100%;
}
.right {
width: 150px;
background-color: green;
margin-left: -150px;
}
</style>
<body>
<div class="box column">
<div class="center">center</div>
</div>
<div class="left column">left</div>
<div class="right column">right</div>
</body>
3、如何实现水平垂直居中?
3.1 文本类可以使用 line-height 和 text-align
.box {
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
}
3.2 使用 flex 布局
.box {
display: flex;
justify-content: center;
align-items: center;
}
3.3 使用绝对定位 postion + transform/负 margin
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
当元素固定宽度时,也可以用 负 margin 替代 transform。
.parent {
position: relative;
width: 200px;
height: 200px;
background-color: red;
}
.child {
width: 50px;
height: 50px;
position: absolute;
left: 50%;
top: 50%;
margin-top: -25px; /* 自身容器高度的一半 */
margin-left: -25px; /* 自身容器宽度的一半 */
background-color: green;
}
3.4 使用 absolute + margin: auto
.box {
position: absolute;
inset: 0;
margin: auto
}
3.5 使用 Grid 布局
.box {
display: grid;
place-items: center;
}
3.6 使用 table 布局
.box {
display: table-cell;
vertical-align: middle;
}
4、实现一个三角形
4.1 利用 border
在 CSS 中,实现三角形最常见的方法是利用元素的**边框(border)**属性。通过设置元素的宽度和高度为 0,然后调整边框的宽度和颜色,可以形成各种方向的三角形。
.triangle {
width: 0;
height: 0;
border-left: 10px solid transparent; /* 左透明 */
border-right: 10px solid transparent; /* 右透明 */
border-bottom: 30px solid red; /* 底有颜色,形成向上三角 */
}
4.2 使用 clip-path
.triangle {
width: 100px;
height: 100px;
background: purple;
clip-path: polygon(50% 0%, 0% 100%, 100% 100%); /* 向上三角 */
}
5、实现一个扇形
在上面画三角形的基础上,再增加样式 border-radius: 100%;即可实现一个扇形。
.sector {
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 30px solid red;
border-radius: 100%; /* 增加 border-radius */
}
6、实现一个梯形
.box {
width: 200px;
height: 60px;
position: relative;
margin: 32px auto;
font-size: 60px;
text-align: center;
}
.box::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: red;
transform: perspective(.5em) rotateX(5deg);
}
7、画一条 0.5px 的线
对于大部分普通屏幕来说,像素是最小的显示单位,因此定义 0.5px 是没有意义的,浏览器遇到小于 1px 时会向上取整渲染 1px,这个是浏览器渲染的局限性。
但是在一些高分辨率屏幕(如 Retina 屏幕)上,1 物理像素点可以被分成多个虚拟像素(比如 2x 屏幕将每个物理像素分为 4 个虚拟像素)。这样的话,1px 的物流像素就等于 2 个虚拟像素,所以 0.5px 也能被渲染出来。
所以对于普通屏幕来说,我们需要做一些兼容方案来渲染出 0.5px 的效果。
我们可以使用 CSS transform 缩放法 来实现 0.5px 的线,代码如下:
.line {
height: 1px;
background-color: red;
transform: scaleY(0.5); /* 对高度进行垂直方向的缩放 */
}
小结
上面是整理的前端面试关于 CSS 高频考察的布局和图形,如有错误或者可以优化的地方欢迎评论区指正。
JavaScript新手必看系列之预编译
前言
预编译是JavaScript的核心概念,也是新手向中级进阶的必经之路。理解它,意味着你能:
- 彻底搞懂变量提升
- 理解函数声明的“特权”
- 避免常见的作用域陷阱
本文用最简单的语言和示例,带你快速掌握全局预编译与函数预编译的完整过程。
What's that?
简单说,JS在执行代码前会进行“准备工作” —— 预编译。在这个阶段JS的V8引擎会进行变量声明,函数声明的提升
预编译的两种场景
1. 全局作用域下的预编译
分三步:
1.创建 GO(Global Object)
-
在浏览器中就是 window 对象
-
在 Node.js 中就是 global 对象
2.找到变量声明,作为作为GO属性名,值为undefined
-
找到函数声明,作为GO属性名,值为函数体
举个简单的例子:
console.log(a) // undefined
var a = 1
console.log(a) // 1
发什么了什么?
- 创建 GO ={}
- 找到变量声明 var a ,GO = { a : undefined }
- 找函数声明,没有函数声明就不找
执行过程如下:
//预编译后就相当于:
var a = undefined
console.log(a) //输出 undefined
a = 1
console.log(a) //输出 1
2.函数作用域下的预编译
函数较为复杂,四步:
-
创建AO(Activation Object)
-
找形参和变量声明,作为AO属性名,值为 undefined
-
将实参赋给形参
-
找函数声明,作为AO 属性名,值为函数体
举例:
function fn(a) {
console.log(a);
var a = 123
console.log(a);
function a() {}
var b = function() {}
console.log(b);
function c() {}
var c = a
console.log(c);
}
fn(1)
干了什么?
- 创建AO = {
2.找形参和变量声明,作为AO属性名,值为 undefined
AO = { a : undefined
b : undefined
c : undefined }
3.将实参赋给形参
AO = { a : 1
b : undefined
c : undefined
}
4.找函数声明,作为AO 属性名,值为函数体
AO = { a : function a() {}
b : undefined
c : function c() {}
}
函数体内执行过程:
// 预编译后的状态:
var c
var b
var a
console.log(a); // function a() {}
a = 123
console.log(a); // 123
b = function() {}
console.log(b); //function b() {}
c = a
console.log(c); //123
掌握了预编译,你就真正理解了 JavaScript 的执行机制,这对后续学习闭包、作用域链等概念至关重要!
希望这篇文章能帮助你彻底理解 JavaScript 预编译,如果有任何疑问,欢迎在评论区讨论!
前端跨页面通讯终极指南②:BroadcastChannel 用法全解析
前言
上一篇介绍了PostMessage跨页面通讯的方式。有没有一种更简洁的方式,兄弟页面也能像父子页面一样通讯。今天就介绍一个更高效、更简洁的方案——BroadcastChannel API,它能轻松搞定父子、子父、兄弟页面间的通讯。
1. BroadcastChannel是什么?
BroadcastChannel 接口表示给定源的任何浏览上下文都可以订阅的命名频道。它允许同源的不同浏览器窗口、标签页、frame 或者 iframe 下的不同文档之间相互通信。消息通过 message 事件进行广播,该事件在侦听该频道的所有 BroadcastChannel 对象上触发,发送消息的对象除外。
简单来说,它就像一个“无线电台”,多个页面只要订阅了同一个“频道”(指定相同的频道名称),就能接收该频道发送的所有消息,实现数据的双向流转。
需要注意,BroadcastChannel仅支持同源页面,兼容性略低,需要搭配其他方案作为降级处理。
2. 如何使用
BroadcastChannel的使用流程如下:
- 创建频道
- 订阅消息
- 发送消息
- 关闭订阅
2.1 创建频道(主题)
通过 new BroadcastChannel('channel-name')创建一个“频道”(相当于发布-订阅中的“主题”)。所有加入同一频道的上下文,共享同一个通信通道。
// 1. 创建/订阅指定名称的频道(关键:多页面频道名必须一致)
const channel = new BroadcastChannel('my-channel');
2.2 订阅消息(监听)
通过监听 message事件订阅该频道的消息(相当于订阅者注册回调):
channel.onmessage = (e) => {
console.log('收到消息:', e.data); // e.data 就是发送的消息内容
// 根据消息类型执行对应逻辑
if (e.data.type === 'refresh') {
// 执行刷新操作
}
};
2.3 发布消息(发送)
通过 postMessage()向频道发送消息(相当于发布者触发发布):
channel.postMessage({
type: 'refresh',
data: { id: 123 }
});
2.4 退订(关闭)
通过 close()方法离开频道(可选,浏览器通常会在上下文销毁时自动清理):
window.addEventListener('beforeunload', () => {
channel.close();
});
// 1. 创建/订阅指定名称的频道(关键:多页面频道名必须一致)
const channel = new BroadcastChannel('my-channel');
// 2. 接收消息
channel.onmessage = (e) => {
console.log('收到消息:', e.data); // e.data 就是发送的消息内容
// 根据消息类型执行对应逻辑
if (e.data.type === 'refresh') {
// 执行刷新操作
}
};
// 3. 发送消息(支持字符串、对象等多种数据类型)
channel.postMessage({
type: 'refresh',
data: { id: 123 }
});
// 4. 关闭频道(页面卸载时调用,避免内存泄漏)
window.addEventListener('beforeunload', () => {
channel.close();
});
3、实践场景
下面我们针对前端最常见的父子页面(父窗口打开子窗口)、子父页面(子窗口向父窗口反馈)、兄弟页面(同一父页面打开的多个子窗口)三种场景,分别给出具体的实现代码。
3.1 父子通讯
父页面点击“刷新子页面”按钮,子页面接收到指令后刷新数据。
父页面代码(打开子窗口并发送消息):
// 1. 创建频道
const parentChannel = new BroadcastChannel('parent-child-channel');
// 2. 点击按钮向子窗口发送消息
document.getElementById('refreshChildBtn').addEventListener('click', () => {
parentChannel.postMessage({
type: 'refresh-data',
message: '父页面指令:请刷新数据'
});
});
// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
parentChannel.close();
});
子页面代码(接收父页面消息并执行操作):
// 1. 订阅同一个频道(频道名必须和父页面一致)
const childChannel = new BroadcastChannel('parent-child-channel');
// 2. 接收父页面消息
childChannel.onmessage = (e) => {
const { type, message } = e.data;
if (type === 'refresh-data') {
// 显示接收的消息
document.getElementById('message').textContent = message;
// 执行刷新数据的逻辑
refreshData();
}
};
// 刷新数据的核心函数
function refreshData() {
// 模拟请求接口刷新数据
console.log('子页面正在刷新数据...');
// 这里写具体的刷新逻辑
}
// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
childChannel.close();
});
3.2 子父通讯(子窗口向父窗口反馈结果)
需求:子页面完成表单提交后,向父页面发送“提交成功”的消息,父页面接收到后关闭子窗口并刷新自身数据。
子页面代码(提交表单后发送消息):
// 1. 订阅频道(和父页面保持一致)
const childChannel = new BroadcastChannel('child-parent-channel');
// 2. 表单提交逻辑
document.getElementById('submitForm').addEventListener('submit', async (e) => {
e.preventDefault();
try {
// 模拟表单提交接口请求
await submitFormData();
// 提交成功后向父页面发送消息
childChannel.postMessage({
type: 'submit-success',
data: { formId: 456, status: 'success' }
});
// 延迟关闭子窗口,确保消息发送完成
setTimeout(() => {
window.close();
}, 300);
} catch (error) {
console.error('提交失败:', error);
}
});
// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
childChannel.close();
});
父页面代码(接收子页面消息并执行操作):
// 1. 创建频道
const parentChannel = new BroadcastChannel('child-parent-channel');
// 2. 接收子页面消息
parentChannel.onmessage = (e) => {
const { type, data } = e.data;
if (type === 'submit-success') {
console.log('子页面表单提交成功:', data);
// 执行父页面刷新逻辑
parentRefreshData();
// (可选)关闭子窗口(如果子窗口未自行关闭)
// childWindow.close();
}
};
// 父页面刷新数据函数
function parentRefreshData() {
console.log('父页面正在刷新数据...');
// 具体刷新逻辑
}
// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
parentChannel.close();
});
3.3 兄弟通讯(多个子窗口间同步状态)
需求:父页面打开两个子窗口A和B,当子窗口A修改数据后,子窗口B实时同步更新数据。
子窗口A代码(修改数据后发送消息):
// 1. 订阅兄弟通讯频道
const brotherChannel = new BroadcastChannel('brother-channel');
// 2. 模拟修改数据操作
document.getElementById('updateDataBtn').addEventListener('click', () => {
const newData = {
id: 789,
content: '子窗口A修改后的新内容',
updateTime: new Date().toLocaleString()
};
// 保存修改后的数据
saveData(newData);
// 向频道发送消息,通知其他兄弟窗口
brotherChannel.postMessage({
type: 'data-updated',
newData: newData
});
});
// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
brotherChannel.close();
});
子窗口B代码(接收消息并同步数据):
// 1. 订阅同一个兄弟通讯频道
const brotherChannel = new BroadcastChannel('brother-channel');
// 2. 接收子窗口A发送的消息
brotherChannel.onmessage = (e) => {
const { type, newData } = e.data;
if (type === 'data-updated') {
console.log('收到子窗口A的更新消息:', newData);
// 同步更新页面数据展示
document.getElementById('dataContent').textContent = newData.content;
document.getElementById('updateTime').textContent = newData.updateTime;
}
};
// 3. 页面卸载时关闭频道
window.addEventListener('beforeunload', () => {
brotherChannel.close();
});
接收输入如下:
![]()
4. 总结
最后总结一下:对比传统的postMessage跨页面通讯方案,BroadcastChannel兄弟页面无需转发,几行代码就能轻松实现通讯。
el-button源码解读3——:class="buttonKls"与颜色系统的关系
说明 :class="buttonKls" 与颜色系统的关系:
1. buttonKls 的作用
buttonKls 是一个计算属性,生成按钮需要的所有 CSS 类名:
const buttonKls = computed(() => [
// el-button
ns.b(),
ns.m(_type.value),
ns.m(_size.value),
ns.is('disabled', _disabled.value),
ns.is('loading', props.loading),
ns.is('plain', _plain.value),
ns.is('round', _round.value),
ns.is('circle', props.circle),
ns.is('text', _text.value),
ns.is('link', props.link),
ns.is('has-bg', props.bg),
])
假设 type="primary",buttonKls 可能包含:
['el-button', 'el-button--primary', 'el-button--small', 'is-loading', ...]
2. 颜色系统的触发机制
颜色系统通过 CSS 类选择器触发。当 buttonKls 包含 'el-button--primary' 时,会匹配到对应的 CSS 规则。
3. 完整的关系链
用户使用: <el-button type="primary">
↓
props.type = 'primary'
↓
_type.value = 'primary'
↓
buttonKls = ['el-button', 'el-button--primary', ...]
↓
:class="buttonKls" → class="el-button el-button--primary"
↓
CSS 匹配: .el-button--primary { ... }
↓
button-variant('primary') 生成 CSS 变量
↓
应用颜色样式
4. 在 CSS 中的对应关系
看 button.scss 中的定义:
@each $type in (primary, success, warning, danger, info) {
@include m($type) {
@include button-variant($type);
}
}
当 $type = 'primary' 时:
-
@include m('primary')→ 生成.el-button--primary选择器 -
@include button-variant('primary')→ 生成颜色相关的 CSS 变量
5. 实际渲染过程
步骤 1:Vue 组件生成类名
<el-button type="primary">按钮</el-button>
// buttonKls 计算后
['el-button', 'el-button--primary']
<!-- 最终渲染 -->
<button class="el-button el-button--primary">
按钮
</button>
步骤 2:CSS 匹配类名
浏览器看到 class="el-button--primary",匹配到:
// button.scss 中生成的
.el-button--primary {
// button-variant('primary') 生成的 CSS 变量
--el-button-bg-color: var(--el-color-primary);
--el-button-border-color: var(--el-color-primary);
--el-button-text-color: var(--el-color-white);
--el-button-hover-bg-color: var(--el-color-primary-light-3);
--el-button-active-bg-color: var(--el-color-primary-dark-2);
}
步骤 3:应用颜色
基础样式使用这些 CSS 变量:
.el-button {
background-color: var(--el-button-bg-color);
border-color: var(--el-button-border-color);
color: var(--el-button-text-color);
}
因为 .el-button--primary 定义了这些变量,所以按钮会显示 primary 的颜色。
6. 关键理解
:class="buttonKls" 是连接 Vue 组件和 CSS 颜色系统的桥梁:
Vue 组件层面 CSS 样式层面
─────────────────────────────────
buttonKls .el-button--primary
↓ ↓
'el-button--primary' → 匹配 CSS 选择器
↓ ↓
应用类名 应用颜色样式
7. 不同类型的关系
| 用户传入 | buttonKls 包含 | CSS 匹配 | 颜色应用 |
|---|---|---|---|
type="primary" |
'el-button--primary' |
.el-button--primary |
蓝色 (#409eff) |
type="success" |
'el-button--success' |
.el-button--success |
绿色 (#67c23a) |
type="warning" |
'el-button--warning' |
.el-button--warning |
橙色 (#e6a23c) |
type="danger" |
'el-button--danger' |
.el-button--danger |
红色 (#f56c6c) |
8. 完整示例
<el-button type="primary" size="small" :loading="true">
提交
</el-button>
生成的类名:
buttonKls = [
'el-button', // 基础类
'el-button--primary', // 类型类(触发颜色系统)
'el-button--small', // 尺寸类
'is-loading' // 状态类
]
最终 HTML:
<button class="el-button el-button--primary el-button--small is-loading">
提交
</button>
CSS 匹配:
.el-button--primary { // ← 这个类触发了颜色系统
--el-button-bg-color: var(--el-color-primary);
// ...
}
9. 总结
-
:class="buttonKls"生成类名(如el-button--primary) - CSS 通过类选择器匹配这些类名
- 匹配到的规则通过
button-variant生成颜色变量 - 基础样式使用这些变量,最终显示对应颜色
核心关系:buttonKls 中的类型类名(如 el-button--primary)是触发颜色系统的开关,CSS 通过这个类名应用对应的颜色样式。