一、JS 基础 & 数据类型
1. JS 基本数据类型
-
原始类型(Primitive) :存储在 栈内存,按值访问。
Number:NaN、InfinityStringBooleanNullUndefinedSymbolBigInt
-
引用类型(Reference) :存储在 堆内存,变量存的是引用地址。
Object、Array、Function、Date等
📌 追问:为什么 typeof null === 'object'?
👉 因为 JS 最初的实现里用二进制前三位表示数据类型,000 代表对象,null 的值全是 0,被误判为对象,成了历史遗留。
2. Symbol
Symbol用来生成独一无二的值:
const s1 = Symbol("id");
const s2 = Symbol("id");
console.log(s1 === s2); // false
- 常用于 对象属性键,避免冲突:
const id = Symbol("id");
let obj = { [id]: 123 };
console.log(obj[id]); // 123
- 内置的 Symbol.iterator 用于迭代协议。
3. 深浅拷贝
-
浅拷贝:只复制第一层,内部对象仍然共享引用。
const obj = { a: 1, b: { c: 2 } }; const copy = { ...obj }; copy.b.c = 3; console.log(obj.b.c); // 3 -
深拷贝:
-
JSON 方法(缺陷:丢失属性值为 “”函数、undefined、Symbol“)
JSON.parse(JSON.stringify(obj)); JSON.stringify({x: undefined, y: Symbol()}); // "{}" JSON.stringify([undefined, Symbol()]); // "[null,null]" -
现代方案:
structuredClone(obj) -
库:
lodash.cloneDeep
-
// 手写深拷贝
function deepClone (obj,map = new WeakMap()){
if(obj === null || typeof obj!=='object') return obj;// 基本数据类型直接返回
if(map.has(obj)) return map.get(obj) // 解决循环引用
const result = Array.isArray(obj)?[]:{};
map.set(obj,result);
// **for...in**遍历的结果可能包含原型链上继承的属性,
// 为了确保安全性,常常需要使用`hasOwnProperty`方法进行过滤,从而增加了些许复杂性
for(let key in obj){
if(obj.hasOwnProperty(key)){
result[key] = deepClone(obj[key],map)
}
}
}
4. 判断两个对象是否相等
- 简单:
JSON.stringify(a) === JSON.stringify(b)(要求 key 顺序一致) - 复杂:递归比较 key/value
- 专业:
_.isEqual(obj1, obj2)
5. 数组扁平化
const arr = [1, [2, [3, 4]]];
arr.flat(Infinity); // [1,2,3,4]
手写递归:
function flatten(arr) {
return arr.reduce((res, item) =>
res.concat(Array.isArray(item) ? flatten(item) : item), []);
}
6. 判断是不是数组
Array.isArray(obj)✅ 推荐obj instanceof ArrayObject.prototype.toString.call(obj) === "[object Array]"
🔹 二、异步 & Promise
7. Promise.all 错误处理
- 默认行为:任意一个失败就 reject。
- 如果要拿到全部结果:用
Promise.allSettled
Promise.allSettled([
Promise.resolve(1),
Promise.reject("error"),
Promise.resolve(2)
]).then(console.log);
输出:
[
{ status: "fulfilled", value: 1 },
{ status: "rejected", reason: "error" },
{ status: "fulfilled", value: 2 }
]
8. 作用域链 & 闭包
- 作用域链:访问变量时,先查当前函数 → 上层函数 → 全局
- 闭包:函数(内部函数)记住了它定义时候的作用域,即使函数在外部执行,也能访问当时作用域里的变量
function outer() {
let count = 0;
return function inner() {
return ++count;
}
}
const add = outer();
console.log(add()); // 1
console.log(add()); // 2
👉 闭包常用于缓存、模块化、数据私有化。
🔹 三、浏览器机制
9. 输入 URL 发生了什么
- DNS 解析 → IP
- TCP 三次握手
- 发送 HTTP 请求
- 服务器返回响应
- 浏览器解析 HTML → 构建 DOM 树
- 下载 CSS → 构建 CSSOM
- 合并 DOM + CSSOM → Render Tree
- Layout(计算位置/大小)
- Paint(绘制像素)
- Composite(GPU 合成)
🔹 扩展下三次握手过程
假设:客户端(Client)要连接服务端(Server)。
-
第一次握手 —— 客户端发起连接请求
- Client → Server:
SYN=1, seq=x - 作用:告诉服务端 “我要建立连接,我这边的初始序号是 x”。
- Client → Server:
-
第二次握手 —— 服务端确认并响应
- Server → Client:
SYN=1, ACK=1, ack=x+1, seq=y - 作用:服务端确认收到了客户端的请求(ack=x+1),同时告诉客户端 “我也可以建立连接,我这边的初始序号是 y”。
- Server → Client:
-
第三次握手 —— 客户端确认
- Client → Server:
ACK=1, ack=y+1, seq=x+1 - 作用:客户端确认收到了服务端的响应。
- Client → Server:
此时连接建立完成,双向通信通道就建立了。
10. 事件循环
-
宏任务:setTimeout, setInterval, I/O, script
-
微任务:Promise.then, MutationObserver, queueMicrotask
-
执行顺序:
- 执行一个宏任务(脚本/定时器回调)
- 执行完后清空所有微任务
- 再取下一个宏任务
例子:
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出:1, 4, 3, 2
步骤执行顺序:
-
整个脚本(
script)是一个宏任务。- 执行
console.log(1)→ 输出 1 - 执行
setTimeout→ 把回调放入宏任务队列(等当前宏任务执行完后再调度)。 - 执行
Promise.then→ 把回调放入微任务队列。 - 执行
console.log(4)→ 输出 4
- 执行
-
当前宏任务执行完,清空微任务队列:
- 执行
console.log(3)→ 输出 3
- 执行
-
取下一个宏任务:
- 执行定时器回调 →
console.log(2)→ 输出 2
- 执行定时器回调 →
🔹 四、CSS & 页面优化
11. 语义化
-
用合适的标签表达含义
<header><article><footer> -
好处:
- SEO 友好
- 可读性强
- 对屏幕阅读器/无障碍更友好
常见语义化标签分类
(1) 页面结构标签
<header>:页面或区块的头部。<nav>:导航栏。<main>:文档主体部分,一个页面只能有一个<main>。<section>:页面中的一个区块,通常有标题。<article>:文章、独立内容块(博客文章、论坛帖子)。<aside>:侧边栏,相关信息或广告。<footer>:底部信息。
(2) 文本内容标签
<h1>~<h6>:标题。<p>:段落。<blockquote>:长引用。<cite>:引用来源。<strong>:强调,语义上是重要。<em>:强调,语气上突出。<mark>:标记高亮。<abbr>:缩写解释。<time>:时间。
(3) 列表相关
<ul>:无序列表。<ol>:有序列表。<li>:列表项。<dl>:定义列表。<dt>:定义标题。<dd>:定义描述。
(4) 表格相关
<table>:表格。<caption>:表格标题。<thead><tbody><tfoot>:表头/主体/表尾。<th>:表头单元格。<td>:数据单元格。
(5) 表单相关
<form>:表单。<label>:表单字段说明。<input>:输入框。<button>:按钮。<select><option>:下拉框。<textarea>:多行文本。
12. 重绘 & 回流
-
回流 (Reflow) :影响布局,比如
width/height/top/left -
重绘 (Repaint) :影响外观,比如
color/background -
优化手段:
- 批量 DOM 操作(DocumentFragment)
- 读写分离(先读 DOM,再写 DOM)
- 动画用
transform/opacity - 慎用
will-change
13. 居中弹框 + 溢出按钮
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.close-btn {
position: absolute;
top: -10px;
right: -10px;
}
14. 雪碧图
.icon {
background: url(sprite.png) no-repeat;
background-position: -20px -40px;
width: 16px; height: 16px;
}
🔹 五、Vue
15. 虚拟 DOM & diff
-
虚拟 DOM:用 JS 对象描述 DOM 节点(VNode)
-
diff 原理:
- 只比较同层节点
- 有 key 时复用节点,最小化更新
- 新旧节点相同 → 复用;不同 → 删除/新建
16. 插入节点
- 原生:
const el = document.createElement("div");
parent.appendChild(el);
-
Vue:
- 生成 VNode
- 调用 patch 比较
- 最终操作真实 DOM
17. Vuex:action vs mutation
- mutation:唯一能修改 state 的方法,必须同步
- action:支持异步逻辑,最后调用 mutation 修改 state
🔹 六、工程化 & 网络
18. Webpack 打包原理
- 从入口文件出发,构建依赖图
- Loader 转换源码(如 TS → JS)
- Plugin 扩展功能(压缩、热更新)
- 输出 bundle
19. HTTP 状态码
- 200 OK
- 301 永久重定向
- 302 临时重定向
- 304 Not Modified(协商缓存命中)
- 404 Not Found
- 500 Server Error
20. 缓存头
-
强缓存:
Cache-Control: max-age=3600Expires
-
协商缓存:
ETag/If-None-MatchLast-Modified/If-Modified-Since
🔹 七、ES6 语法
箭头函数
- 没有
this,继承外层 - 不能
new - 没有
arguments
数组展开符
const arr1 = [1,2];
const arr2 = [...arr1, 3,4];
let / const vs var
var:函数作用域,存在变量提升let/const:块级作用域,不可重复声明,TDZ(暂时性死区)
split / slice / splice / reduce
split: 字符串切分 → 数组slice(start,end): 截取数组,不修改原数组splice(start,deleteCount,items): 删除/插入,修改原数组reduce: 累计
[1,2,3].reduce((acc,v) => acc+v, 0); // 6
ES6 class转 ES5
class Person {
constructor(name,age){
this.name = name;
this.age = age;
}
sayName(){
console.log(this.name)
}
static createAnonymous(){
return new Person('anonymous',18)
}
}
const p = new Person('Tom',20)
p.sayName()
- 等价ES5 语法
// 构造函数
function Person(name,age){
this.name = name;
this.age = age;
}
// 原型方法
Person.prototype.sayName = functions90{
console.log(this.name)
}
// 静态方法
Person.createAnonymous = function(){
return new Person('anonymous',18)
}
var p = new Person("Tom", 20);
p.sayName(); // Tom
对比总结
- constructor 👉 构造函数
- class 里的普通方 👉 加在prototype 上
- static方法 👉 直接挂在构造函数对象上
- extends继承 👉 用
Object.create()+ call super 实现
带继承的例子
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + " makes a noise");
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(this.name + " barks");
}
}
// 转成ES5
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
console.log(this.name + " makes a noise");
};
function Dog(name, breed) {
// super(name)
// 是在 **子类构造函数 `Dog` 内“借用”父类构造函数 `Animal`** 来初始化当前正在创建的实例(也就是 `this`)。它对应 ES6 里的 `super(name)`
Animal.call(this, name);
this.breed = breed;
// Function.prototype.call是干嘛的?call的作用是 **指定函数执行时的this**,并立即调用该函数
// 所以 `Animal.call(this, name)` 就等价于:用当前的this(正被 new Dog(...))创建出来的那个对象,作为Animal的this去执行Animal(name)
}
// 继承原型
Dog.prototype = Object.create(Animal.prototype);// 让 Dog.prototype.__proto__ 指向 Animal.prototype
// 从而让 Dog实例可以通过原型链找到Animal的方法
Dog.prototype.constructor = Dog;
// 但是有个副作用:Object.create(Animal.prototype)返回的是一个新的对象,这个对象的constructor是指向Animal,所以需要Dog.prototype.constructor = Dog 来修正
// 重写方法
Dog.prototype.speak = function () {
console.log(this.name + " barks");
};
总结
- class是语法糖,就是构造函数+原型
- Babel 编译 ES6 → ES5 时,也是按照这种方式做的。
扩展复习下原型链
- 每个函数 都有一个属性 prototype ,这个属性是一个对象,里面可以放方法/属性
- 当你使用new来调用这个函数时,创建的对象会有一个隐藏的属性**
__proto__** ,指向构造函数的prototype
原型链是怎么工作的?
- 当访问一个对象的属性,js引擎会这样查找
- 先找对象自身的属性
- 如果没有,就顺着
__proto__→ 构造函数的 prototype 找 - 如果 prototype 上也没有,就继续往上找 prototype 的 prototype
- 一直追溯到
Object.prototype - 如果还没找到,返回
undefined
constructor 和 prototype的关系
- 构造函数有prototype,用来生成实例的原型
- prototype对象上有个constructor,指向构造函数本身
function Person{}
Person.prototype.constructor === Person // true