一、ES6 的6种变量声明方式(含实战陷阱与追问链)
ES6(ECMAScript 2015)带来了语言层面的巨大升级,其中变量声明机制 是开发者最直观感受到的变革。它不再局限于 var
,而是引入了更安全、更语义化的多种声明方式。
我们先看一张核心对比图:
⚡图:ES6 六大声明方式全景图
⚠️这一步暗藏变量提升陷阱?
1. var
------ 被时代淘汰但仍在运行时存在
- 函数作用域
- 存在变量提升(hoisting)
- 可重复声明
- 不推荐在现代项目中使用
js
console.log(a); // undefined(不是报错!)
var a = 1;
2. let
------ 块级作用域新标准
- 块级作用域(
{}
内有效) - 不存在"暂时性死区"外的访问
- 不可重复声明
- 推荐用于可变变量
js
{
let b = 2;
console.log(b); // 2
}
console.log(b); // ReferenceError!
3. const
------ 常量声明(但≠不可变!)
- 块级作用域
- 必须初始化
- 引用地址不可变,但对象属性可改
js
const obj = { name: 'ES6' };
obj.name = 'Modern JS'; // ✅ 合法
obj = {}; // ❌ 报错:Assignment to constant variable.
4. function
------ 函数声明(也是ES6前就有的)
- 提升且初始化
- 仅在定义的作用域内有效
- 与
let/const
冲突时会抛错
js
function foo() { return 1; }
5. class
------ 语法糖,本质仍是函数
- 必须先定义后使用(TDZ)
- 不会被提升
class
声明创建一个不可重新赋值的常量
js
class Person {
constructor(name) {
this.name = name;
}
}
const p = new Person('Tom');
6. import
------ 模块导入即声明
- 静态分析,编译时绑定
- 所有
import
自动提升到文件顶部 - 绑定是只读引用,不是拷贝
js
import { useState } from 'react'; // 声明了一个只读绑定
📌 主流共识:
let
和const
是现代 JS 开发的默认选择,优先使用const
,仅当需要重新赋值时用let
。
「Q1: var
和 let
最大区别是什么?💥」
A1: var
是函数作用域且会变量提升并初始化为 undefined
;let
是块级作用域,存在暂时性死区(TDZ),在声明前访问会报错。
「Q2: const
定义的对象为什么还能修改属性?🤯」
A2: const
保证的是引用地址不变 ,而非对象内部不可变。要真正冻结对象需用 Object.freeze(obj)
。
「Q3: import
是不是也受 TDZ 影响?」
A3: 是的!虽然 import
会被提升,但在模块执行前无法访问,属于 TDZ 范畴。比如动态 import()
可以规避。
「Q4: class
声明会不会被提升?」
A4: class
声明存在 TDZ,不会被提升。typeof MyClass
在声明前会报错,不像 function
可以提前使用。
「Q5: 为什么 ES6 不直接废弃 var
?」
A5: 为了向后兼容。大量旧代码依赖 var
行为,直接移除会导致生态崩溃。但规范鼓励使用 let/const
。
二、CSS 元素垂直居中的 7 种方式
垂直居中是前端永恒话题。我们先上一张布局方式演进图:
sql
+------------------+ +------------------+
| display:table | | position + 负margin |
+------------------+ +------------------+
| |
v v
+------------------+ +------------------+
| flex: center!! |<--->| grid layout |
+------------------+ +------------------+
|
v
+------------------+
| transform:translate(-50%) |
+------------------+
⚡图:CSS 垂直居中方法演进路径
⚠️移动端慎用负 margin?
方法 1:Flexbox(推荐 ✅)
css
.container {
display: flex;
justify-content: center; /* 水平 */
align-items: center; /* 垂直 */
height: 100vh;
}
方法 2:Grid(现代方案)
css
.container {
display: grid;
place-items: center; /* 水平+垂直 */
height: 100vh;
}
方法 3:绝对定位 + transform
css
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
方法 4:绝对定位 + margin auto
css
.container {
position: relative;
height: 300px;
}
.child {
position: absolute;
top: 0; bottom: 0;
left: 0; right: 0;
margin: auto;
width: 100px;
height: 50px;
}
方法 5:table-cell(老派但兼容好)
css
.container {
display: table-cell;
vertical-align: middle;
text-align: center;
width: 300px;
height: 300px;
}
方法 6:line-height(仅限单行文本)
css
.box {
height: 100px;
line-height: 100px;
text-align: center;
}
方法 7:padding 调整(固定高度可用)
css
.box {
padding: 50px 0;
height: 200px;
box-sizing: border-box;
}
「Q1: Flex 居中会不会影响子元素布局?💥」
A1: 不会,align-items
和 justify-content
只控制主轴和交叉轴对齐,子元素仍可自由布局。
「Q2: transform: translate
会导致模糊?🤯」
A2: 是的!非整数像素位移可能引起亚像素渲染模糊。可加 will-change: transform
或强制 GPU 加速缓解。
「Q3: Grid 和 Flex 如何选?」
A3: Grid 适合二维布局(行列都复杂),Flex 适合一维(主轴方向)。居中场景两者皆可,优先 Flex。
「Q4: 为什么不用 vertical-align: middle
?」
A4: 因为它只对 inline
或 table-cell
元素有效,块级元素无效,常被误解。
「Q5: 移动端适配居中要注意什么?🌍」
A5: 避免固定高度,优先使用 Flex 或 Grid,结合 vh
单位,并处理 iOS 安全区(env() 函数)。
三、Flex 布局的三大伸缩属性详解(含权重计算)
Flex 布局核心在于三个伸缩属性:
⚡图:Flex 伸缩三属性关系
⚠️默认值组合有坑?
1. flex-grow
:剩余空间分配权重
- 默认 0
- 数值越大,分得越多
- 不会缩小已有内容
css
.item1 { flex-grow: 1; }
.item2 { flex-grow: 2; } /* 分得两倍空间 */
2. flex-shrink
:溢出时压缩比例
- 默认 1
- 设为 0 表示不压缩
- 压缩量 = (自身大小 × shrink) / 总权重
css
.item { flex-shrink: 0; } /* 固定宽度,不压缩 */
3. flex-basis
:分配前的初始大小
- 类似
width
,但优先级更高 - 可设为
auto
(按内容)、0
、具体值
css
.item { flex-basis: 100px; }
简写 flex
的常见组合:
写法 | 等价于 |
---|---|
flex: 1 |
1 1 0% 或 1 1 0px (浏览器差异) |
flex: auto |
1 1 auto |
flex: none |
0 0 auto (不伸不缩) |
💡 实战建议:用
flex: 1
快速填满剩余空间,flex: none
防止压缩。
「Q1: flex: 1
到底等于 1 1 0
还是 1 1 auto
?💥」
A1: 规范定义为 1 1 0
,但某些浏览器实现为 0%
。为明确行为,建议写全。
「Q2: flex-basis: 0
和 width: 0
一样吗?🤯」
A2: 不同!flex-basis
参与 flex 计算,width
在 flex 中可能被覆盖。flex-basis: 0
更适合均分。
「Q3: 多个 item 设置 flex-grow
如何计算?」
A3: 按权重比例分配剩余空间。如 grow=1 和 grow=2,则比例 1:2。
「Q4: flex-shrink
为负数会怎样?」
A4: 无效!flex-shrink
必须是非负数,负值会被忽略或转为 0。
「Q5: 为什么有时 flex: 1
不占满?」
A5: 可能父容器无固定宽度,或子元素有 min-width
限制。检查 min-width: auto
是否干扰。
四、LeetCode 开平方根题(第69题)------ 二分法 vs 牛顿法
题目:实现 int sqrt(int x)
,返回 √x 的整数部分。
例:输入 8 → 输出 2(√8 ≈ 2.828)
方法 1:二分查找(推荐 ✅)
js
function mySqrt(x) {
if (x < 2) return x;
let left = 1, right = Math.floor(x / 2);
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const square = mid * mid;
if (square === x) return mid;
if (square < x) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return right; // 最大满足 mid*mid <= x 的值
}
方法 2:牛顿迭代法(更快收敛)
js
function mySqrt(x) {
if (x < 2) return x;
let r = x;
while (r * r > x) {
r = Math.floor((r + x / r) / 2); // 牛顿公式: r = (r + x/r)/2
}
return r;
}
时间复杂度:二分 O(log x),牛顿法接近 O(log log x)
「Q1: 为什么 right = x / 2
?💥」
A1: 因为当 x ≥ 4 时,√x ≤ x/2。边界优化可减少搜索范围。
「Q2: 牛顿法会不会死循环?🤯」
A2: 不会,因为每次迭代都更接近真实值,且整数除法会收敛。但需用 Math.floor
控制精度。
「Q3: 能不能用 Math.sqrt
?」
A3: 面试中通常要求手动实现。若允许,直接 Math.floor(Math.sqrt(x))
。
「Q4: 浮点数开方怎么做?」
A4: 扩展牛顿法,设置精度阈值(如 1e-6),直到 |r*r - x| < eps
。
「Q5: 大数溢出怎么处理?」
A5: 用 BigInt
或将比较改为 mid <= x / mid
避免乘法溢出。