Skip to content

手写与布局 ​

概念页讲「是什么 / 为什么」;本页练「怎么写」。建议顺序:先 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%)

垂直居中 / 水平垂直都居中 ​

方式写法要点
flexjustify-content: center + align-items: center(最常用)
gridplace-items: center
绝对定位 + transformtop/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。