Appearance
JavaScript 核心概念深度解析
静态作用域和动态作用域
- JavaScript 采用词法作用域(静态作用域),函数作用域在定义时就确定
- 动态作用域中,函数作用域在调用时才确定
执行上下文与变量对象
当执行可执行代码时,会创建对应的执行上下文,包含三个核心属性:
- 变量对象(VO)
- 作用域链
- this
函数上下文
- 函数上下文中使用活动对象(AO) 表示变量对象
- AO 在进入函数上下文时创建,通过
arguments属性初始化 - AO 变化过程:
- 进入执行上下文:添加形参、函数声明、变量声明
- 代码执行阶段:修改变量值
js
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
// 进入执行上下文时AO:
AO = {
arguments: {0: 1, length: 1},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
// 代码执行后AO:
AO = {
arguments: {0: 1, length: 1},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}作用域链
- 查找变量时从当前上下文的变量对象开始,沿父级执行上下文逐级查找
- 作用域链是由多个执行上下文的变量对象构成的链表
js
function foo() {
function bar() {
// bar作用域链:[bar.AO, foo.AO, global.VO]
}
}词法作用域和原型链
词法作用域:解决的是 “变量从哪里来?” 的问题,即变量的查找规则。
- JavaScript 引擎通过 作用域链(Scope Chain) 来查找变量。作用域链是由当前执行上下文和所有外层执行上下文的变量对象组成。查找变量时,会从当前作用域开始,逐级向上(向外)查找,直到全局作用域。如果找不到,就会抛出 ReferenceError。
原型链:解决的是 “属性或方法从哪里来?” 的问题,即对象属性的查找规则。
- 原型链是 JavaScript 实现继承的主要机制。每个对象都有一个内部属性 [[Prototype]](可通过 proto 或 Object.getPrototypeOf() 访问),它指向另一个对象(它的原型)。当试图访问一个对象的属性时,如果该对象自身没有这个属性,就会在其原型对象上查找,依此类推,直到找到属性或到达链的末端(null)。
函数参数传递
- ECMAScript 中所有函数参数都是按值传递
- 基本类型:传递值的副本
- 引用类型:传递引用的副本(共享传递)
js
var obj = { value: 1 };
function foo(o) {
o = 2; // 修改引用副本
}
foo(obj);
console.log(obj.value); // 1(原对象未改变)手写核心方法
实现 call
js
Function.prototype.call2 = function (context, ...args) {
context = context || window;
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};实现 bind
js
Function.prototype.myBind = function (context) {
const self = this;
return function (...args) {
return self.apply(context, args);
};
};实现 new
js
function myNew(Fn, ...args) {
const obj = Object.create(Fn.prototype);
const result = Fn.apply(obj, args);
return result instanceof Object ? result : obj;
}AJAX vs Fetch
| 特性 | AJAX (XMLHttpRequest) | Fetch |
|---|---|---|
| 设计 | 回调函数 | Promise |
| API 设计 | 集中式 | 模块化(Request/Response/Headers) |
| 数据流 | 不支持分块读取 | 支持 Stream 分块读取 |
| CORS | 需手动处理 | 默认不发送 cookies |
let 底层实现
- 编译阶段:扫描函数体,为 let 变量生成初始词法环境
- 执行上下文:进入块级作用域创建新词法环境
- 绑定变量值:运行时在词法环境中搜索变量
- 块级作用域:创建子遮蔽环境实现作用域隔离
垃圾回收机制
引用计数
- 优点:立即回收垃圾
- 缺点:循环引用问题,计数器占用空间
标记清除
- 标记所有对象为垃圾(0)
- 从根对象遍历,标记可达对象(1)
- 清除标记为 0 的对象
- 优点:实现简单
- 缺点:内存碎片
分代回收
- 新生代:新创建对象,回收频率高
- 老生代:长期存活对象,回收频率低
函数式编程核心
- 纯函数:相同输入 => 相同输出,无副作用js
function add(a, b) { return a + b; } - 不可变性:数据创建后不可修改js
const newArr = [...arr, newItem]; - 高阶函数:函数作为参数或返回值js
function multiplier(factor) { return (num) => num * factor; } - 函数组合:js
const process = compose(func1, func2, func3);
发布订阅 vs 观察者模式
| 特性 | 发布订阅 | 观察者 |
|---|---|---|
| 耦合度 | 完全解耦 | 相互耦合 |
| 通信方式 | 消息通道 | 直接调用 |
| 扩展性 | 高(多发布/订阅者) | 低 |
精度丢失问题
js
0.1 + 0.2 !== 0.3; // true
9007199254740991 + 1; // 精度丢失原因:浮点数二进制表示存在近似值
WeakMap vs Map
| 特性 | WeakMap | Map |
|---|---|---|
| Key 类型 | 仅对象 | 任意值 |
| 引用类型 | 弱引用 | 强引用 |
| 垃圾回收 | 自动回收 | 不回收 |
| 可枚举 | 否 | 是 |
小程序双线程架构
- 渲染层:WebView 渲染界面
- 逻辑层:JsCore 线程运行 JS 脚本 优势:避免 JS 执行阻塞渲染
重绘 vs 回流
| 重绘 | 回流 | |
|---|---|---|
| 触发条件 | 外观变化 | 布局变化 |
| 性能影响 | 较小 | 较大 |
| 优化建议 | 避免频繁样式修改 | 批量 DOM 操作 |
setTimeout vs requestAnimationFrame
| setTimeout | requestAnimationFrame | |
|---|---|---|
| 执行时机 | 指定时间后 | 下一帧开始前 |
| 主线程 | 阻塞 | 不阻塞 |
| 后台运行 | 继续执行 | 暂停 |
| 适用场景 | 通用定时 | 动画优化 |
script 加载策略
| 正常加载 | async | defer | |
|---|---|---|---|
| HTML 解析 | 暂停 | 并行 | 并行 |
| 执行顺序 | 顺序执行 | 下载完立即执行 | HTML 解析完后顺序执行 |
| 使用建议 | - | 独立脚本 | 依赖 DOM 的脚本 |
内存泄漏场景
- 意外全局变量js
function leak() { temp = "leak"; } - 未清除定时器js
setInterval(() => {...}, 1000); - DOM 引用未释放js
const element = document.getElementById("my-element"); // 未在不需要时解除引用 - 闭包滥用js
function outer() { const bigData = new Array(1000000); return function inner() { console.log(bigData.length); }; }
Vite 为何启动快
- 开发环境:直接使用 ES6 Module,无需打包
- 按需编译:仅编译当前请求的模块
- Esbuild 预构建:极速的依赖预构建
Vue vs React 对比
| Vue | React | |
|---|---|---|
| 模板系统 | 基于 HTML 的模板 | JSX |
| 更新粒度 | 组件级 | 从根节点调度 |
| 响应式 | Proxy 拦截 | setState 触发 |
| 渲染方式 | 递归 | 可中断循环 |
|
前端打包必要性
- 性能优化:Tree-Shaking/压缩/合并
- 语法转换:TS/ES6+/SCSS → 浏览器兼容
- 工程能力:Lint/测试/CI/CD 集成
开发环境标识
| 环境 | 全称 | 用途 |
|---|---|---|
| DEV | Development | 开发 |
| SIT | System Integration Test | 系统整合测试 |
| UAT | User Acceptance Test | 用户验收测试 |
| PROD | Production | 生产环境 |
循环方法对比
| 方法 | 可中断 | 遍历内容 | 适用对象 |
|---|---|---|---|
forEach | ❌ | 值 | Array |
for...in | ✅ | 键 | Object/Array |
for...of | ✅ | 值 | Array/Map/Set |
数据类型细节
undefined:变量声明但未初始化null:空对象指针
递减操作符
js
let a = 5;
let b = a-- + 2; // b=7, a=4(先计算后递减)
let c = --a + 2; // a=3, c=5(先递减后计算)检查对象属性是否存在的4中方式
- in 运算符:key in obj
特点:检测自身+原型链上的key,存在返回true(含继承属性)。
- obj.hasOwnProperty(key):
特点:仅检测对象自身的key(排除原型链),最常用且精准。
- obj[key] !== undefined:
特点:简单直接,但无法区分key存在但值为undefined的情况(如obj={a:undefined})。
- Reflect.has(obj, key):
特点:ES6新增,行为与in一致(检测自身+原型链),更符合函数式编程风格,可安全处理特殊对象(如冻结对象)。
什么是模块化?
- 模块化是一种将复杂系统拆分为独立、可复用的小单元(模块) 的开发范式。每个模块聚焦单一功能,通过清晰定义的接口与其他模块通信,内部实现细节则被封装隐藏。
核心价值有 3 点:
- 解耦性:将代码按功能拆分(如请求库、工具函数、UI组件),避免牵一发而动全身
- 复用性:比如封装的防抖函数、表单组件,可在多个页面甚至多个项目中复用,减少重复开发
- 可维护性:模块职责单一,问题定位更精准(如网络错误只需检查API模块)
什么是工程化?
- “工程化是用系统化、规范化、工具化的方式管理前端全生命周期,核心是解决多人协作、大型项目、高效交付的问题,把零散的开发流程变成可落地的标准流程。
前端工程化的核心落地环节:
- 开发规范:ESLint/Prettier保障代码风格统一,Git提交规范记录变更语义
- 构建优化:利用Webpack/Vite实现代码压缩、Tree-Shaking、按需加载
- 自动化流程:CI/CD流水线自动运行测试、部署发布,减少人工干预
- 架构设计:建设私有组件库、搭建BFF层、落地微前端方案等
模块化解决的是「代码层面的混乱」,工程化解决的是「项目层面的混乱」(比如开发、构建、部署全流程的不规范)。