JavaScript
语言基础
数据类型
7 种原始类型 + 1 种引用类型:
// 原始类型(值传递,不可变)
undefined
null
boolean
number // IEEE 754 双精度浮点(Number.MAX_SAFE_INTEGER = 2^53 - 1)
bigint // BigInt(9007199254740991n)
string
symbol // Symbol('description')
// 引用类型(引用传递,可变)
object // Array, Function, Date, RegExp, Map, Set ...
typeof 与 instanceof:
typeof undefined // "undefined"
typeof null // "object" (JS 历史遗留 bug)
typeof true // "boolean"
typeof 42 // "number"
typeof 42n // "bigint"
typeof 'hello' // "string"
typeof Symbol() // "symbol"
typeof {} // "object"
typeof [] // "object"
typeof function(){} // "function" (内置特殊处理)
// instanceof 检查原型链
[] instanceof Array // true
[] instanceof Object // true(Array.prototype 继承自 Object.prototype)
typeof null === 'object' 原因:JS 底层用 type tag(000: object),null 的表示为 0x00,恰好被识别为 object 类型。
类型转换
隐式转换规则表:
| 运算 | 规则 |
|---|---|
+ 且一方为字符串 |
字符串拼接 |
+ 双方都不是字符串 |
转为 number |
- * / % |
全部转为 number |
== 与 number 比较 |
另一侧转为 number |
== 与 string 比较 |
另一侧转为 string |
> < >= <= |
都转为 number |
if() && ` |
显式转换:
String(123) // "123"
Number('123') // 123
Number('abc') // NaN
Boolean(0) // false
Boolean('') // false
Boolean([]) // true(空数组是对象)
parseInt('10px', 10) // 10
parseFloat('3.14em') // 3.14
falsy 值(其他值都为 truthy):
false
0, -0, 0n
'' (空字符串)
null
undefined
NaN
== 的坑与 === 的推荐:
// == 会进行类型转换
[] == false // true([] → '' → 0, false → 0)
[] == 0 // true
' \t\r\n' == 0 // true(字符串转为 NaN?不,空串或空白转为 0)
null == undefined // true
// 始终使用 === 和 !==(除了判断 null/undefined 时可用 == null)
声明提升与 TDZ
// var —— 声明提升到函数/全局作用域顶部,初始化为 undefined
console.log(a); // undefined(不会报错)
var a = 1;
// let / const —— 存在 TDZ(Temporal Dead Zone),提升但不初始化
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;
// function —— 声明+定义同时提升
foo(); // 正常运行
function foo() {}
TDZ(暂时性死区):从作用域开始到变量声明的区域,访问变量会抛出 ReferenceError。
let x = 1;
{
console.log(x); // ReferenceError: x 在 TDZ 中
let x = 2;
}
var / let / const 对比:
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数级 | 块级 | 块级 |
| 提升 | 声明提升(undefined) | 声明提升(TDZ) | 声明提升(TDZ) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 全局声明 | window 属性 | 非 window 属性 | 非 window 属性 |
| 重新赋值 | 允许 | 允许 | 不允许 |
| 定义时必须赋值 | 否 | 否 | 是 |
原型与继承
原型链
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
const alice = new Person('Alice');
// 关系链
alice.__proto__ === Person.prototype // true
Person.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // true
Person.__proto__ === Function.prototype // true
Function.prototype.__proto__ === Object.prototype // true
// 属性查找:alice.name → alice.__proto__.sayHello → alice.__proto__.__proto__
__proto__ vs prototype:
| 属性 | 谁有 | 用途 |
|---|---|---|
prototype |
函数 | 作为 new 调用的实例的原型对象 |
__proto__ |
所有对象 | 指向构造函数的 prototype(原型链查找) |
继承方式演进
// 1. 原型链继承 —— 引用类型共享问题
function Parent() { this.names = ['a', 'b']; }
function Child() {}
Child.prototype = new Parent();
const c1 = new Child(), c2 = new Child();
c1.names.push('c');
console.log(c2.names); // ['a', 'b', 'c'] —— 被污染
// 2. 构造函数继承 —— 方法不能复用
function Parent(name) { this.name = name; }
function Child(name) {
Parent.call(this, name); // 复制父类属性
}
// 方法需要写在 Parent.prototype 上,但 Child 无法访问
// 3. 组合继承(原型链+构造函数)—— 两次调用 Parent
function Parent(name) { this.name = name; }
Parent.prototype.say = function() { console.log(this.name); };
function Child(name, age) {
Parent.call(this, name); // 第一次
this.age = age;
}
Child.prototype = new Parent(); // 第二次
Child.prototype.constructor = Child;
// 4. 寄生组合继承(推荐)—— 只调用一次 Parent
function inherit(Child, Parent) {
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
}
function Parent(name) { this.name = name; }
Parent.prototype.say = function() { console.log(this.name); };
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inherit(Child, Parent);
Child.prototype.sayAge = function() { console.log(this.age); };
class 语法糖本质
class Person {
constructor(name) {
this.name = name;
}
// 原型方法
sayHello() { console.log(`Hi, ${this.name}`); }
// 静态方法(Person.create 上)
static create(name) { return new Person(name); }
// 私有字段(ES2022+)
#privateField = 42;
// getter/setter
get fullName() { return this.name; }
set fullName(val) { this.name = val; }
}
// 本质:function 的语法糖
typeof Person // "function"
Person.prototype.sayHello // function
// 继承
class Student extends Person {
constructor(name, grade) {
super(name); // 必须先调用 super 才可使用 this
this.grade = grade;
}
sayHello() {
super.sayHello(); // 调用父类方法
console.log(`Grade: ${this.grade}`);
}
}
new 运算符原理
function _new(Constructor, ...args) {
// 1. 创建空对象,原型指向 Constructor.prototype
const obj = Object.create(Constructor.prototype);
// 2. 执行构造函数,this 指向 obj
const result = Constructor.apply(obj, args);
// 3. 若构造函数返回对象则用该对象,否则用 obj
return result instanceof Object ? result : obj;
}
闭包与作用域
词法环境与执行上下文
执行上下文:JS 引擎执行代码时的环境,分为三种:
- 全局执行上下文
- 函数执行上下文(每次调用创建)
- Eval 执行上下文
每个执行上下文包含:
ExecutionContext = {
ThisBinding: <this>,
LexicalEnvironment: { // let/const 存放处
EnvironmentRecord: { bindings },
Outer: <outer reference>
},
VariableEnvironment: { // var 存放处(其他同 LexicalEnvironment)
EnvironmentRecord: { bindings },
Outer: <outer reference>
}
}
执行上下文栈:
function a() {
function b() {
console.log('inner');
}
b();
}
a();
// 栈变化:
// 1. global EC (压栈)
// 2. a EC (压栈)
// 3. b EC (压栈)→ 执行 b → b EC 出栈
// 4. a EC 继续 → 执行完成 → a EC 出栈
闭包形成原理
function outer() {
let count = 0; // outer 的局部变量
return function inner() {
count++; // inner 引用了 outer 作用域中的 count
return count;
};
}
const counter = outer(); // outer 执行后,其执行上下文本该被销毁
console.log(counter()); // 1 —— 但 inner 保持了对 count 的引用
console.log(counter()); // 2 —— count 仍存在且可变
闭包形成条件:函数嵌套 + 内部函数引用外部函数的变量。
闭包原理:JS 引擎在函数创建时保存外部作用域的引用([[Scopes]] 属性),使该作用域不会被 GC 回收。
闭包应用
// 1. 模块化(IIFE 模块)
const CounterModule = (function() {
let count = 0; // 私有变量
return {
increment() { count++; return this; },
decrement() { count--; return this; },
getCount() { return count; },
};
})();
// 2. 柯里化
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...next) => curried(...args, ...next);
};
}
// 3. 防抖/节流 —— 见高阶技巧
// 4. 私有变量
function createPerson(name) {
let _name = name; // 私有
return {
getName() { return _name; },
setName(n) { _name = n; },
};
}
闭包的内存问题
// ❌ 内存泄漏:闭包长期持有大对象
function leak() {
const heavyData = new Array(1000000).fill('*');
return function() {
console.log('闭包持有 heavyData'); // heavyData 永远不会被 GC
};
}
// ✅ 改进:用完后置空
function noLeak() {
const heavyData = new Array(1000000).fill('*');
const result = function() {
console.log('闭包持有 heavyData');
};
heavyData = null; // 闭包内不再持有
return result;
}
this 指向
五种绑定规则
// 1. 默认绑定(非严格模式 → window/globalThis,严格模式 → undefined)
function foo() { console.log(this); }
foo(); // window(非严格)或 undefined(严格)
// 2. 隐式绑定(谁调用指向谁)
const obj = { name: 'obj', foo };
obj.foo(); // obj
// 隐式丢失
const bar = obj.foo;
bar(); // window(引用丢失)
// 3. 显式绑定(call / apply / bind)
foo.call(obj, arg1, arg2);
foo.apply(obj, [arg1, arg2]);
const bound = foo.bind(obj); // 返回新函数,永久绑定 this
// 4. new 绑定
function Person(name) { this.name = name; }
const p = new Person('Alice'); // this → 新对象
// 5. 箭头函数(无独立 this,继承外层词法 this)
const arrow = () => { console.log(this); };
arrow(); // 外层 this
优先级判断
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
箭头函数:无视以上规则,取定义时的外层 this
// bind 不能改变箭头函数的 this
const obj = { name: 'obj' };
const fn = () => { console.log(this); };
fn.call(obj); // window(箭头函数无视 call)
// bind 返回的函数不能被 new 改变 this
function Person() { console.log(this); }
const BoundPerson = Person.bind({ fake: true });
new BoundPerson(); // Person 实例(new 优先于 bind)
常见陷阱
// 定时器中的 this
const obj = {
count: 0,
start() {
setInterval(function() {
this.count++; // this → window
}, 1000);
},
startCorrect() {
setInterval(() => {
this.count++; // this → obj(箭头函数)
}, 1000);
}
};
异步编程
Promise
三种状态:pending → fulfilled / rejected(状态不可逆)。
const p = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => resolve('done'), 1000);
// 或 reject(new Error('fail'));
});
p.then(
(value) => console.log(value), // onFulfilled
(reason) => console.error(reason) // onRejected(可选)
).catch((err) => {
// 处理上方 then 中抛出的错误
}).finally(() => {
// 无论成功失败都会执行
});
Promise 静态方法:
// all —— 全部成功则返回结果数组,任一失败则立即 reject
Promise.all([p1, p2, p3])
.then(([r1, r2, r3]) => {})
.catch(err => {});
// allSettled —— 等待所有结束,返回 {status, value/reason} 数组
Promise.allSettled([p1, p2, p3])
.then(results => {
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.error(r.reason);
});
});
// race —— 返回第一个完成的(无论成功或失败)
Promise.race([p1, p2]).then(v => {});
// any —— 返回第一个成功的,全失败则 AggregateError
Promise.any([p1, p2]).then(v => {}).catch(err => {
console.error(err.errors); // 所有失败原因
});
手写 Promise 核心步骤:
function MyPromise(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state !== 'pending') return;
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
};
const reject = (reason) => {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// then 返回新的 Promise,支持链式调用
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 值穿透
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r; };
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
// ... 类似 handleRejected
if (this.state === 'fulfilled') handleFulfilled();
else if (this.state === 'pending') {
this.onFulfilledCallbacks.push(handleFulfilled);
}
});
return promise2;
};
// resolvePromise 处理 x 可能为 Promise 的情况(递归拆解)
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) return reject(new TypeError('Chaining cycle'));
if (x instanceof MyPromise) {
x.then(y => resolvePromise(promise2, y, resolve, reject), reject);
} else {
resolve(x);
}
}
async/await
// async 函数总是返回 Promise
async function fetchData() {
// 值 → Promise.resolve(值)
return 'data'; // 等价于 return Promise.resolve('data')
}
// await 暂停执行直到 Promise 完成
async function getUser(id) {
try {
const user = await api.getUser(id); // 等待
const posts = await api.getPosts(user.id); // 串行
return { user, posts };
} catch (err) {
console.error('请求失败:', err);
throw err; // 让调用方继续处理
}
}
错误处理:
// 方式一:try/catch(推荐)
async function safe() {
try {
await risky();
} catch (err) {
// 处理错误
}
}
// 方式二:链式 catch
async function safe2() {
const result = await risky().catch(err => {
console.error(err);
return fallbackValue; // 降级值
});
}
// 方式三:全局错误(未被捕获的 Promise reject)
window.addEventListener('unhandledrejection', (event) => {
console.error('未捕获的 Promise 错误:', event.reason);
});
并发控制:
// 串行(性能差但有依赖关系时必需)
const r1 = await task1();
const r2 = await task2(r1); // 依赖 r1
// 并行(无依赖关系)
const [a, b] = await Promise.all([taskA(), taskB()]);
// 限流并发(控制并发数)
async function parallelLimit(tasks, limit) {
const results = [];
const executing = [];
for (const task of tasks) {
const p = Promise.resolve().then(() => task());
results.push(p);
if (limit <= tasks.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
Generator
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
const gen = generateSequence();
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: true }
gen.next(); // { value: undefined, done: true }
用 Generator 实现异步流程控制:
function asyncGenerator(genFn) {
return function(...args) {
const gen = genFn.apply(this, args);
return new Promise((resolve, reject) => {
function step(key, arg) {
try {
const { value, done } = gen[key](arg);
if (done) return resolve(value);
Promise.resolve(value).then(
v => step('next', v),
e => step('throw', e)
);
} catch (err) {
reject(err);
}
}
step('next');
});
};
}
// 使用:类似 async/await
const fetchUser = asyncGenerator(function*(id) {
const user = yield api.getUser(id); // yield Promise → await
const posts = yield api.getPosts(user.id);
return { user, posts };
});
ES6+
解构赋值
// 数组解构
const [a, b = 10, ...rest] = [1, 2, 3, 4];
// a=1, b=2, rest=[3,4]
// 对象解构
const { name, age, ...others } = { name: 'Alice', age: 25, city: 'NY' };
// 重命名 + 默认值
const { name: userName = 'Guest' } = obj;
// 嵌套解构
const { address: { city, zip } } = person;
// 函数参数解构
function render({ title = '', width = 100, height = 100 } = {}) {}
展开与剩余
// 展开数组
const arr = [1, 2, 3];
const copy = [...arr];
const merged = [...arr, 4, 5];
Math.max(...arr);
// 展开对象(浅拷贝)
const obj = { a: 1, b: 2 };
const copy = { ...obj };
const merged = { ...obj, c: 3 };
// 剩余参数(代替 arguments)
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
Symbol
const sym1 = Symbol('desc');
const sym2 = Symbol('desc');
sym1 === sym2 // false(唯一)
// 内置 Symbol
Symbol.iterator // 可迭代对象
Symbol.toStringTag // Object.prototype.toString
Symbol.hasInstance // instanceof
Symbol.species // 派生对象构造函数
Map / Set / WeakMap / WeakSet
// Map —— key 可以是任意类型
const map = new Map();
map.set(obj, 'value'); // 对象作为 key
map.get(obj);
map.has(obj);
map.delete(obj);
map.size;
// 与 Object 对比:
// - Object key 只能是 string/Symbol,Map key 可以是任意类型
// - Map 有序迭代,Object 无序
// - Map size 直接取,Object 需 Object.keys().length
// - Map 频繁增删性能更优
// Set —— 值唯一
const set = new Set([1, 2, 3, 1, 2]);
// set: {1, 2, 3}
// WeakMap —— key 必须是对象,弱引用,不阻止 GC
let obj = { data: 'large' };
const weakMap = new WeakMap();
weakMap.set(obj, 'metadata');
obj = null; // weakMap 中对应的键值对会被 GC(不可预测时间)
// WeakSet —— 同理,值必须是对象,弱引用
// 用途:DOM 节点关联数据,不会因 Map 持有引用造成内存泄漏
Proxy / Reflect
const target = { name: 'Alice', age: 25 };
const proxy = new Proxy(target, {
// 拦截读取
get(target, prop, receiver) {
console.log(`GET ${String(prop)}`);
// return target[prop]; // 推荐用 Reflect
return Reflect.get(target, prop, receiver);
},
// 拦截写入
set(target, prop, value, receiver) {
console.log(`SET ${String(prop)} = ${value}`);
return Reflect.set(target, prop, value, receiver);
},
// 拦截 in 操作
has(target, prop) {
return Reflect.has(target, prop);
},
// 拦截 delete
deleteProperty(target, prop) {
return Reflect.deleteProperty(target, prop);
},
// 拦截函数调用
apply(target, thisArg, args) {
console.log('函数被调用');
return Reflect.apply(target, thisArg, args);
},
});
// 响应式原理(Vue3)
function reactive(obj) {
const deps = new Map();
return new Proxy(obj, {
get(target, key, receiver) {
track(deps, key); // 收集依赖
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(deps, key); // 触发更新
return result;
},
});
}
Optional Chaining / Nullish Coalescing
// Optional Chaining(?.)
user?.address?.city; // 链中任一为 null/undefined 则返回 undefined
users?.[0]; // 数组访问
obj?.method?.(); // 方法调用
// Nullish Coalescing(??)
const value = input ?? 'default'; // 仅当 input 为 null/undefined 时使用默认
// vs ||
const value = input || 'default'; // input 为 '' / 0 / false 也使用默认
模块化
CommonJS vs ES Module
// ========== CommonJS(Node.js,同步,运行时加载) ==========
// 导出
module.exports = { foo, bar };
exports.foo = foo; // 相当于 module.exports.foo
// 导入
const module = require('./module');
const { foo } = require('./module');
// 特性:运行时动态加载,值拷贝(基本类型)
// ========== ES Module(标准,异步,静态解析) ==========
// 导出
export const name = 'value';
export function foo() {}
export default class App {}
export { name, foo };
// 导入
import { name, foo } from './module.js';
import defaultExport from './module.js';
import * as all from './module.js';
import { name as alias } from './module.js';
// 动态导入(运行时)
const module = await import('./module.js');
// 特性:
// - 静态分析支持 Tree Shaking
// - 值引用(模块内的变量变化会反映在导入方)
// - 必须使用 .mjs 或 package.json type: "module"
区别总结:
| 特性 | CommonJS | ES Module |
|---|---|---|
| 加载时机 | 运行时 | 编译时(静态分析) |
| 加载方式 | 同步 | 异步 |
| 值传递 | 值拷贝(基本类型) | 值引用(动态绑定) |
| Tree Shaking | 不支持 | 支持 |
| 循环依赖 | 部分支持(可拿到未完成的 exports) | 支持(有 TDZ 机制) |
| 顶层 this | module.exports | undefined |
Tree Shaking 原理
Tree Shaking 依赖于 ES Module 的静态结构(import/export 在编译时确定),构建工具(webpack/rollup)通过:
- 标记未使用的导出(
/*#__PURE__*/标记纯函数调用) - 移除无用代码(terser 等压缩工具执行)
必要条件:
- ES Module(
import/export) - 生产模式
- 使用副作用标记(
"sideEffects": false或"sideEffects": ["./*.css"])
高阶技巧
防抖(Debounce)
// 防抖:最后一次触发后 delay ms 执行
function debounce(fn, delay = 300) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
};
}
// 立即执行版(第一次立即触发,之后等待)
function debounceImmediate(fn, delay = 300) {
let timer = null;
return function(...args) {
const callNow = !timer;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
}, delay);
if (callNow) fn.apply(this, args);
};
}
// 使用
const handleSearch = debounce((e) => {
api.search(e.target.value);
}, 500);
节流(Throttle)
// 节流:固定时间间隔内只执行一次
function throttle(fn, interval = 300) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 尾调用版(停止触发后还会执行最后一次)
function throttleTrailing(fn, interval = 300) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, interval);
}
};
}
// 完整版(头尾都执行)
function throttleFull(fn, interval = 300) {
let lastTime = 0, timer = null;
return function(...args) {
const now = Date.now();
const remaining = interval - (now - lastTime);
if (remaining <= 0) {
if (timer) { clearTimeout(timer); timer = null; }
lastTime = now;
fn.apply(this, args);
} else if (!timer) {
timer = setTimeout(() => {
lastTime = Date.now();
timer = null;
fn.apply(this, args);
}, remaining);
}
};
}
防抖 vs 节流:
| 场景 | 方案 |
|---|---|
| 搜索输入框 | 防抖(用户停之后再搜) |
| 无限滚动 | 节流(固定间隔检查位置) |
| 窗口 resize | 防抖(停止变化后重排) |
| 拖拽/鼠标移动 | 节流(固定频率更新位置) |
深拷贝
// 1. JSON 方法(简单但有限制)
const copy = JSON.parse(JSON.stringify(obj));
// 限制:undefined/Symbol/function → 丢失;Date → 字符串;RegExp → {};循环引用 → 报错
// 2. 递归深拷贝(支持循环引用)
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
// 处理循环引用
if (map.has(obj)) return map.get(obj);
// 处理特殊对象
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Map) {
const copy = new Map();
map.set(obj, copy);
obj.forEach((v, k) => copy.set(deepClone(k, map), deepClone(v, map)));
return copy;
}
if (obj instanceof Set) {
const copy = new Set();
map.set(obj, copy);
obj.forEach(v => copy.add(deepClone(v, map)));
return copy;
}
const copy = Array.isArray(obj) ? [] : {};
map.set(obj, copy);
// 遍历自身属性(包含 Symbol 属性)
for (const key of [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)]) {
copy[key] = deepClone(obj[key], map);
}
return copy;
}
函数柯里化
// 柯里化:将多参数函数转为接受单参数的函数链
function curry(fn) {
return function curried(...args) {
// 参数够了就调用
if (args.length >= fn.length) {
return fn.apply(this, args);
}
// 不够就返回新函数继续收集
return (...args2) => curried(...args, ...args2);
};
}
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6
偏函数(Partial Application)
// 偏函数:固定部分参数
function partial(fn, ...fixedArgs) {
return function(...remainingArgs) {
return fn.apply(this, [...fixedArgs, ...remainingArgs]);
};
}
const add = (a, b, c) => a + b + c;
const add5 = partial(add, 5);
add5(3, 2); // 10
compose / pipe
// compose: 从右到左组合函数
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
// pipe: 从左到右组合函数(更常见)
function pipe(...fns) {
return function(x) {
return fns.reduce((acc, fn) => fn(acc), x);
};
}
const add1 = (x) => x + 1;
const double = (x) => x * 2;
const toString = (x) => `${x}`;
const process = pipe(add1, double, toString);
process(5); // "12"(先加1得6,再乘2得12,再转字符串)
const process2 = compose(toString, double, add1);
process2(5); // "12"(执行顺序从右到左:5+1=6, 6*2=12, "12")