8道题穿透前端原理层

436 阅读4分钟
graph LR
    A[前端面试八股] --> B[存储: localStorage vs Cookie]
    A --> C[CSS: 选择器 + 盒模型 + 布局]
    A --> D[JS: 原型链 + 构造函数 + new]
    A --> E[异步: Promise 精髓]
    
    B --> F[安全/作用域/大小]
    C --> G[兼容性/自适应布局]
    D --> H[instanceof / new 模拟]
    E --> I[链式调用 / 错误处理]

    style A fill:#f9f,stroke:#333
    style F fill:#ff9,stroke:#333
    style G fill:#9ff,stroke:#333

1. localStorage 和 cookie 有什么区别?

维度localStoragecookie
容量~5MB~4KB
生命周期永久(除非手动清除)可设置过期时间
是否随请求发送❌ 不会✅ 每次HTTP请求都带
作用域同源同源 + 可设 domain/path
APIsetItem/getItemdocument.cookie 字符串操作

「Q1: cookie 能被 XSS 窃取吗?」
A1: ✅ 能!除非加 HttpOnly 标志,这样 JS 无法读取,但 HTTP 请求仍携带。
👉安全底线!⚠️

「Q2: localStorage 有安全风险吗?」
A2: 有!XSS 攻击可直接读取 localStorage,所以敏感信息(如 Token)建议用 HttpOnly Cookie 存储。

「Q3: 如何实现跨标签页通信?」
A3: 用 localStoragestorage 事件:

window.addEventListener('storage', (e) => {
  console.log(e.key, e.newValue); // 其他标签页修改时触发
});

⚠️注意:当前页面修改不会触发!


2. CSS选择器有哪些?

分类如下(图示):

%% =========================================================
%% 竖向「CSS 选择器全景图」
%% 主题:自上而下、层级清晰、代码高亮
%% 适配:深色 / 浅色模式自适应
%% 图标:FontAwesome 6.5
%% =========================================================

%% 1. 全局样式
%% 2. 竖向树形
%% 3. 颜色语义
%% 4. 图标标识
%% 5. 动效提示

%% ========== 1. 全局样式 ==========
%% 主色:#0ea5e9(sky-500)
%% 辅色:#10b981(emerald-500)
%% 背景:#ffffff / #111827(自适应)
%% 字体:Inter, system-ui, sans-serif
%% 圆角:8px
%% 阴影:0 4px 6px -1px rgba(0,0,0,0.1)

flowchart TD
    classDef root     fill:#0ea5e9,stroke:#0284c7,color:#fff,stroke-width:3px,rx:8px,ry:8px
    classDef category fill:#10b981,stroke:#059669,color:#fff,stroke-width:2px,rx:8px,ry:8px
    classDef item     fill:#f8fafc,stroke:#cbd5e1,color:#1e293b,stroke-width:1px,rx:6px,ry:6px
    classDef code     fill:#e2e8f0,stroke:#94a3b8,color:#0f172a,stroke-width:1px,rx:4px,ry:4px

    %% ========== 2. 竖向树形 ==========
    CSS["<i class='fa fa-code'></i> CSS 选择器"]:::root

    %% 基础
    B["<i class='fa fa-layer-group'></i> 基础"]:::category
    B1["标签选择器 <code>div</code>"]:::item
    B2["类选择器 <code>.box</code>"]:::item
    B3["ID 选择器 <code>#header</code>"]:::item
    B4["通配符 <code>*</code>"]:::item

    %% 组合
    C["<i class='fa fa-project-diagram'></i> 组合"]:::category
    C1["后代 <code>.box p</code>"]:::item
    C2["子代 <code>.box > p</code>"]:::item
    C3["相邻兄弟 <code>.box + p</code>"]:::item
    C4["通用兄弟 <code>.box ~ p</code>"]:::item

    %% 属性
    D["<i class='fa fa-filter'></i> 属性"]:::category
    D1["精确匹配 <code>[type='text']</code>"]:::item
    D2["前缀匹配 <code>[href^='https']</code>"]:::item

    %% 伪类 / 伪元素
    E["<i class='fa fa-magic'></i> 伪类 / 伪元素"]:::category
    E1["伪类 <code>:hover :nth-child()</code>"]:::item
    E2["伪元素 <code>::before ::after</code>"]:::item

    %% ========== 3. 层级关系 ==========
    CSS --> B
    B  --> B1
    B  --> B2
    B  --> B3
    B  --> B4

    CSS --> C
    C  --> C1
    C  --> C2
    C  --> C3
    C  --> C4

    CSS --> D
    D  --> D1
    D  --> D2

    CSS --> E
    E  --> E1
    E  --> E2

    %% ========== 4. 动效提示 ==========
    %% 部署时可通过 CSS 实现:
    %% .node:hover { transform: scale(1.03); transition: 0.2s; }

⚠️权重计算易错?💥

选择器权重(优先级):

  • !important > 行内 > ID(100) > Class(10) > Tag(1)

「Q4: :nth-child(2n) 是什么意思?」
A4: 选偶数位置的子元素,等价于 :nth-child(even)

「Q5: ::before 和 :before 区别?」
A5: ::before 是伪元素(生成新DOM节点),:before 是伪类(状态类)。双冒号是CSS3规范,防混淆。


3. 盒子模型,以及标准情况和IE下的区别

标准盒模型 vs IE盒模型:

标准模型(W3C)
+-----------------------+
|       margin          |
|   +---------------+   |
|   |    border     |   |
|   |   +-------+   |   |
|   |   |  padding  |   |
|   |   |  +---+  |   |   width = 内容宽度
|   |   |  |内容|  |   |
|   |   |  +---+  |   |
|   |   +-------+   |   |
|   +---------------+   |
+-----------------------+

IE模型(怪异模式)
+-----------------------+
|       margin          |
|   +---------------+   |
|   |    border     |   |
|   |   +-------+   |   |
|   |   |  padding  |   |
|   |   |  +---+  |   |   width = 内容+padding+border
|   |   |  |内容|  |   |
|   |   |  +---+  |   |
|   |   +-------+   |   |
|   +---------------+   |
+-----------------------+

⚠️这差异让布局错位?🤯

解决:使用 box-sizing 统一:

* {
  box-sizing: border-box; /* 推荐:开发更直观 */
}

「Q6: 如何强制页面进入怪异模式?」
A6: 不写或写错 DOCTYPE,如 <html> 前有注释或空格。

「Q7: border-box 和 content-box 区别?」
A7:

  • content-box:width 只算内容(默认)
  • border-box:width 包含 padding + border

4. 如何实现高度自适应?

常见场景与方案:

场景方案
两栏布局(侧边栏+主内容)flexposition: absolute
全屏布局height: 100vhflex: 1
圣杯/双飞翼flex + margin 负值(已淘汰)
动态内容撑高min-height + overflow

代码示例(flex 实现全屏自适应):

.app {
  display: flex;
  flex-direction: column;
  height: 100vh;
}
.header {
  height: 60px;
}
.main {
  flex: 1; /* 自动占满剩余空间 */
  overflow-y: auto;
}
.footer {
  height: 40px;
}

「Q8: vh 和 % 有什么区别?」
A8:

  • vh:视口高度百分比
  • %:相对父元素高度
    👉父元素没设高时,% 失效!

「Q9: 如何监听高度变化?」
A9: 用 ResizeObserver

new ResizeObserver(entries => {
  console.log(entries[0].contentRect.height);
}).observe(document.body);

5. prototype 和 proto 区别?

这是原型链的核心!图示:

export_hkeq4.png

关系总结:

  • prototype:函数才有,是实例的原型对象
  • __proto__:所有对象都有,指向其构造函数的 prototype

「Q10: 所有对象的尽头是谁?」
A10: Object.prototype.__proto__ === null,原型链终点。

「Q11: 为什么函数有 prototype 而普通对象没有?」
A11: 因为 prototype 是“用来生成实例的模板”,只有构造函数需要。


6. constructor 是什么?

constructorprototype 上的一个属性,指向构造函数本身。

function Person() {}
console.log(Person.prototype.constructor === Person) // true

const p = new Person()
console.log(p.constructor === Person) // true(通过__proto__链找到)

⚠️易错点:重写 prototype 会丢失 constructor:

Person.prototype = {
  say() {}
}
console.log(Person.prototype.constructor === Person) // ❌ false

修复:

Person.prototype.constructor = Person

「Q12: constructor 能用来判断类型吗?」
A12: 不完全可靠!可被改写。推荐用 instanceofObject.prototype.toString.call()


7. new 是怎么实现的?

手写一个 myNew 函数,理解本质:

function myNew(Constructor, ...args) {
  // 1. 创建空对象,继承构造函数的原型
  const obj = Object.create(Constructor.prototype);
  // 2. 绑定 this 并执行构造函数
  const result = Constructor.apply(obj, args);
  // 3. 返回对象(如果是引用类型则返回 result)
  return result instanceof Object ? result : obj;
}

// 测试
function Person(name) {
  this.name = name;
}
const p = myNew(Person, 'Tom');
console.log(p.name); // Tom

// 注:Object.create 实现了 proto 链接

「Q13: 如果构造函数返回一个对象会怎样?」
A13: new 会忽略 this,返回该对象。若返回原始值,则仍返回 this。

「Q14: new 的4个步骤是什么?」
A14:

  1. 创建新对象
  2. 链接到原型
  3. 绑定 this
  4. 返回实例(或构造函数返回的对象)

8. promise 的精髓,以及优缺点

Promise 精髓:解决回调地狱,统一异步错误处理

核心状态机:

stateDiagram-v2
    [*] --> Pending
    Pending --> Fulfilled: resolve(value)
    Pending --> Rejected: reject(error)
    Fulfilled --> [*]
    Rejected --> [*]

⚠️状态不可逆?👉继续看!

优点:

  • 链式调用 .then().catch()
  • 错误冒泡,统一捕获
  • 可组合:Promise.all / race

缺点:

  • 无法取消(ES2018 Cancelable Promise 提案)
  • 中途不能中断链
  • 错误需 .catch,否则静默失败

「Q15: Promise 构造函数里的错误怎么处理?」
A15: 用 try/catch 包裹 resolve/reject,或直接 reject(error)

「Q16: 如何实现 Promise.finally?」
A16: 无论成功失败都执行:

Promise.prototype.finally = function (cb) {
  return this.then(
    value => Promise.resolve(cb()).then(() => value),
    error => Promise.resolve(cb()).then(() => { throw error })
  );
}