Appearance
手写与布局
概念页讲「是什么 / 为什么」;本页练「怎么写」。建议顺序:先 CSS 布局,再 JS 工具函数,最后原理类手写。
先看全局路线:知识串联与应答路线 · 相关概念:JavaScript · HTML + CSS
1. CSS 布局实操
水平 / 垂直居中有哪些实现?
水平居中
| 场景 | 写法 |
|---|---|
| 行内 / 行内块 | 父级 text-align: center |
| 定宽块级 | 自身 margin: 0 auto |
| flex | 父级 display: flex; justify-content: center |
| grid | 父级 display: grid; justify-items: center(或 place-items) |
| 绝对定位 | left: 50%; transform: translateX(-50%) |
垂直居中 / 水平垂直都居中
| 方式 | 写法要点 |
|---|---|
| flex | justify-content: center + align-items: center(最常用) |
| grid | place-items: center |
| 绝对定位 + transform | top/left: 50% + transform: translate(-50%, -50%) |
| 绝对定位 + margin auto | 定宽高时:inset: 0; margin: auto |
| line-height | 单行文本:line-height 等于容器高度(局限大) |
| table-cell | 父级 display: table-cell; vertical-align: middle(偏旧) |
优先掌握:flex / grid / 绝对定位 + transform。
🎤 口述精简版
水平居中块级可用 margin: 0 auto,更通用是 flex 的 justify-content: center。垂直或水平垂直一起居中,首选 flex 或 grid;老写法是绝对定位加 translate(-50%, -50%)。
两栏 / 三栏自适应怎么写?
两栏(左固定 / 右自适应)
css
/* flex(推荐) */
.wrap { display: flex; }
.left { width: 200px; flex-shrink: 0; }
.right { flex: 1; }
/* grid */
.wrap {
display: grid;
grid-template-columns: 200px 1fr;
}float 了解即可:左 float: left; width: 200px,右开 BFC(如 overflow: hidden)。
三栏(左右固定、中间自适应)
css
.wrap { display: flex; }
.left, .right { width: 200px; flex-shrink: 0; }
.center { flex: 1; }
/* 或 */
.wrap {
display: grid;
grid-template-columns: 200px 1fr 200px;
}圣杯 / 双飞翼属于早期 float 方案,现在多用 flex / grid。
🎤 口述精简版
两栏三栏首选 flex 或 grid。固定侧写死宽度,自适应侧 flex: 1 或 1fr。
CSS 小三角怎么实现?
常用 border 法:元素宽高为 0,只留某一方向的 border 有颜色,其余 border 透明,看起来就是三角形。
css
.triangle-up {
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 12px solid #0f766e; /* 向上的三角:底边有色 */
}
.triangle-down {
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 12px solid #0f766e;
}场景:气泡框小尖角、下拉箭头、角标。也可用 clip-path: polygon(...),兼容性与习惯上 border 法更常考。
🎤 口述精简版
小三角:宽高设 0,三边 border 透明、一边有颜色,拼出一个三角。气泡尖角、下拉箭头常用这招。
2. JS 工具函数手写
防抖与节流
js
function debounce(fn, delay) {
let timer
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
function throttle(fn, interval) {
let last = 0
return function (...args) {
const now = Date.now()
if (now - last >= interval) {
last = now
fn.apply(this, args)
}
}
}| 防抖 debounce | 节流 throttle | |
|---|---|---|
| 行为 | 停下来后才触发 | 每隔一段时间触发 |
| 场景 | 搜索输入、resize 停了再算 | 滚动加载、高频点击 |
🎤 口述精简版
防抖是停了才执行;节流是按频率执行。搜索防抖,滚动节流。
浅拷贝、深拷贝(基础版)
js
function shallowClone(obj) {
if (Array.isArray(obj)) return [...obj]
if (obj && typeof obj === 'object') return { ...obj }
return obj
}
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (map.has(obj)) return map.get(obj)
const res = Array.isArray(obj) ? [] : {}
map.set(obj, res)
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
res[key] = deepClone(obj[key], map)
}
}
return res
}浅拷贝只拷一层;深拷贝递归,循环引用用 WeakMap 记下。
🎤 口述精简版
浅拷贝一层,深拷贝递归。基础版处理对象数组,循环引用用 WeakMap。
数组去重
js
const unique1 = (arr) => [...new Set(arr)]
const unique2 = (arr) => arr.filter((item, i) => arr.indexOf(item) === i)
const unique3 = (arr) => {
const map = {}
return arr.filter((item) => (map[item] ? false : (map[item] = true)))
}对象数组按字段去重可用 Map 以 id 为键。
🎤 口述精简版
最简用 Set;兼容可用 indexOf;对象数组按业务 id 去重。
手写 reduce / 求和
js
function sum(arr) {
return arr.reduce((total, n) => total + n, 0)
}
function myReduce(arr, fn, init) {
let start = 0
let acc = init
if (acc === undefined) {
acc = arr[0]
start = 1
}
for (let i = start; i < arr.length; i++) {
acc = fn(acc, arr[i], i, arr)
}
return acc
}🎤 口述精简版
reduce 带累积值遍历;求和初始 0,回调里累加。
数组 flat 扁平化
js
function flat(arr, depth = 1) {
if (depth <= 0) return arr.slice()
return arr.reduce(
(res, item) =>
res.concat(Array.isArray(item) ? flat(item, depth - 1) : item),
[]
)
}
const flatAll = (arr) =>
arr.reduce((res, item) => res.concat(Array.isArray(item) ? flatAll(item) : item), [])🎤 口述精简版
flat 用 reduce + 递归,depth 控制层数;不限层就一直拆到没有数组。
3. JS 原理手写
手写 call / apply / bind
js
Function.prototype.myCall = function (ctx, ...args) {
ctx = ctx == null ? globalThis : Object(ctx)
const key = Symbol()
ctx[key] = this
const result = ctx[key](...args)
delete ctx[key]
return result
}
Function.prototype.myApply = function (ctx, args = []) {
return this.myCall(ctx, ...args)
}
Function.prototype.myBind = function (ctx, ...args) {
const fn = this
return function (...rest) {
return fn.myCall(ctx, ...args, ...rest)
}
}思路:把函数临时挂到目标对象上调用,利用隐式 this。
🎤 口述精简版
手写 call:函数挂到目标对象上当方法调,再删掉。apply 转成 call;bind 包一层返回新函数。
手写 new
js
function myNew(Ctor, ...args) {
const obj = Object.create(Ctor.prototype)
const res = Ctor.apply(obj, args)
return res !== null && (typeof res === 'object' || typeof res === 'function')
? res
: obj
}🎤 口述精简版
Object.create 连原型,再 apply 构造;返回值是对象就用返回值,否则用新对象。
手写简易 Promise 核心
js
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilled = []
this.onRejected = []
const resolve = (v) => {
if (this.state !== 'pending') return
this.state = 'fulfilled'
this.value = v
this.onFulfilled.forEach((fn) => fn(v))
}
const reject = (r) => {
if (this.state !== 'pending') return
this.state = 'rejected'
this.reason = r
this.onRejected.forEach((fn) => fn(r))
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const done = (fn, val) => {
try {
const x = fn ? fn(val) : val
resolve(x)
} catch (e) {
reject(e)
}
}
if (this.state === 'fulfilled') queueMicrotask(() => done(onFulfilled, this.value))
else if (this.state === 'rejected') queueMicrotask(() => done(onRejected, this.reason))
else {
this.onFulfilled.push((v) => done(onFulfilled, v))
this.onRejected.push((r) => done(onRejected, r))
}
})
}
}写清即可:状态锁、then 回调队列、返回新 Promise;不必一次写完美 Promise/A+。
🎤 口述精简版
pending 改成功/失败只能一次;then 收集回调;链式就再返回新 Promise。