手写 call 函数
手写 bind 函数
手写 apply 函数
千分位分割
函数柯里化
promise.all
1、手写手写EventBus
2、并发限制的异步调度器 Scheduler
3、对象数组去重
实现数组的扁平化
4、数组转树,
5. 实现数组的扁平化
5、防抖(Debounce)
6、节流(Throttle)
7、深拷贝
8、字符串中出现的不重复字符的最长长度
9、 手写 instanceof 方法
10. 大数相加
11、红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
// 手写 call 函数
Function.prototype.myCall = function(context, ...args) {
// 1. 如果 context 是 null 或 undefined,则默认指向 window(浏览器)或 global(Node.js)
context = context || window;
// 2. 将当前函数(this)绑定到 context 上作为一个临时属性
const fnSymbol = Symbol('fn'); // 使用 Symbol 避免属性名冲突
context[fnSymbol] = this; // this 指向要调用的函数
// 3. 执行函数
const result = context[fnSymbol](...args);
// 4. 删除临时属性
delete context[fnSymbol];
// 5. 返回结果
return result;
};
----------------------------------------------------------
// 手写 apply 函数
Function.prototype.myApply = function(context, argsArray) {
// 1. 如果 context 是 null 或 undefined,则默认指向 window(浏览器)或 global(Node.js)
context = context || window;
// 2. 将当前函数(this)绑定到 context 上作为一个临时属性
const fnSymbol = Symbol('fn'); // 使用 Symbol 避免属性名冲突
context[fnSymbol] = this; // this 指向要调用的函数
// 3. 执行函数,第二个参数是数组
let result;
if (argsArray && Array.isArray(argsArray)) {
result = context[fnSymbol](...argsArray);
} else {
result = context[fnSymbol](); // 如果没有传数组参数,则无参调用
}
// 4. 删除临时属性
delete context[fnSymbol];
// 5. 返回结果
return result;
};
----------------------------------------------------------
// 最简单版本的 bind 函数
Function.prototype.myBind = function(context) {
// 保存原函数
const self = this;
// 获取绑定时传入的参数(除了第一个 this 参数)
const args = Array.prototype.slice.call(arguments, 1);
// 返回一个新函数
return function() {
// 获取调用时传入的参数
const callArgs = Array.prototype.slice.call(arguments);
// 合并参数
const allArgs = args.concat(callArgs);
// 执行原函数,绑定 this
return self.apply(context, allArgs);
};
};
千分位分割
function formatNumber(num) {
// 1. 转为字符串,分割整数和小数部分
const str = num.toString();
const [integer, decimal] = str.split('.');
// 2. 整数部分转为数组,从右往左每3位添加逗号
const arr = integer.split('');
let result = '';
let count = 0;
// 从右往左遍历
for (let i = arr.length - 1; i >= 0; i--) {
result = arr[i] + result;
count++;
// 每3位添加逗号,但不是在第一位
if (count % 3 === 0 && i !== 0) {
result = ',' + result;
}
}
// 3. 拼接小数部分
return decimal ? result + '.' + decimal : result;
}
函数柯里化
function sum(...args) {
return args.reduce((pre, cur) => pre + cur)
}
// 函数柯里化
function currying(fn) {
// 用于存储参数
const args = []
return function result(...rest) {
// 当前无参数,则调用原来的函数(递归的结束条件)
if(rest.length===0) {
return fn(...args)
} else {
args.push(...rest)
return result
}
}
}
console.log(currying(sum)(1)(2)()); // 3
console.log(currying(sum)(1,2,3)()); // 6
console.log(currying(sum)(1,2)(3,4,5)()); // 15
Promise.all
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('arguments must be an array'));
}
const results = [];
let count = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => {
results[index] = value;
count++;
if (count === promises.length) {
resolve(results);
}
})
.catch(reject);
});
});
};
1、手写EventBus
class EventBus {
constructor() {
this.events = new Map();
}
// 订阅事件
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event).push(callback);
// 返回取消订阅函数
return () => this.off(event, callback);
}
// 发布事件
emit(event, ...args) {
if (!this.events.has(event)) return;
this.events.get(event).forEach(fn => fn(...args));
}
// 取消订阅
off(event, callback) {
if (!this.events.has(event)) return;
if (!callback) {
// 移除该事件所有监听器
this.events.delete(event);
return;
}
// 移除特定监听器
const callbacks = this.events.get(event);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
// 一次性订阅
once(event, callback) {
const onceWrapper = (...args) => {
callback(...args);
this.off(event, onceWrapper);
};
this.on(event, onceWrapper);
}
// 清空所有事件
clear() {
this.events.clear();
}
}
2、并发限制的异步调度器 Scheduler
class Scheduler {
constructor(limit = 3) {
this.limit = limit;
this.task = [];
this.runningCount = 0;
}
add(task) {
return new Promise((resolve, reject) => {
this.task.push({
task,
resolve,
reject
})
this._run();
})
}
_run() {
while(this.runningCount < this.limit && this.task.length > 0) {
const { task, resolve, reject } = this.task.shift();
this.runningCount++;
task().then(resolve, reject).finally(() => {
this.runningCount--;
this._run();
})
}
}
}
3、对象数组去重 //www.bilibili.com/video/BV121…
const isObject = (val) => typeof val === 'object' && val !== null;
for (let i = 0; i < newArr.length; i++) {
for (let j = i + 1; j < newArr.length; j++) {
if (equals(newArr[i], newArr[j])) {
newArr.splice(j, 1);
j--;
}
}
}
function equals(val1, val2) {
if (!isObject(val1) || !isObject(val2)) return Object.is(val1, val2);
if (val1 === val2) return true;
const val1Keys = Object.keys(val1);
const val2Keys = Object.keys(val2);
if (val1Keys.length !== val2Keys.length) return false;
for (const key of val1Keys) {
if (!val2Keys.includes(key)) return false;
const res = equals(val1[key], val2[key]);
if (!res) return false;
}
return true;
}
- 实现数组的扁平化
普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:
let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
flatten(arr); // [1, 2, 3, 4,5]
方法2
ES6 中的 flat
我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:`arr.flat([depth])`
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.flat(Infinity);
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
4、数组转树
const aa = (items) => {
const result = [];
const itemsMap = {};
for (const item of items) {
itemsMap[item.id] = { ...item, children: [] };
}
for (const item of items) {
const id = item.id;
const pid = item.pid;
const childItem = itemsMap[id];
if (pid === 0) {
result.push(childItem);
} else {
if (!itemsMap[pid]) {
itemsMap[pid] = { children: [] };
}
itemsMap[pid].children.push(childItem);
}
}
console.log("items", items)
return result;
}
const arr = [
{ id: 1, name: '部门1', pid: 0 },
{ id: 2, name: '部门2', pid: 1 },
{ id: 3, name: '部门3', pid: 1 },
{ id: 4, name: '部门4', pid: 3 },
];
console.log(aa(arr))
5、防抖(Debounce)
function debounce(func, wait) {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
func.apply(context, args);
}, wait);
};
}
// 使用示例
const myEfficientFn = debounce(function() {
// 需要防抖执行的函数
}, 250);
window.addEventListener('resize', myEfficientFn);
6、节流(Throttle)
function throttle(func, limit) {
let inThrottle;
return function() {
const context = this;
const args = arguments;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(function() {
return inThrottle = false;
}, limit);
}
};
}
// 使用示例
const myEfficientFn = throttle(function() {
// 需要节流执行的函数
}, 250);
window.addEventListener('scroll', myEfficientFn);
7、深拷贝
function deepCopy(obj, hash = new WeakMap()) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 如果是日期或正则对象则直接返回一个新对象
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 如果hash中有这个对象,则直接返回hash中存储的对象引用
if (hash.has(obj)) {
return hash.get(obj);
}
let newObj = Array.isArray(obj) ? [] : {};
hash.set(obj, newObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepCopy(obj[key], hash);
}
}
return newObj;
}
8、字符串中出现的不重复字符的最长长度
function lengthOfLongestSubstring(s) {
let start = 0; // 窗口起始位置
let maxLength = 0; // 最长不重复子串的长度
let seen = new Set(); // 用于存储窗口内的字符
for (let end = 0; end < s.length; end++) {
// 如果当前字符已经在窗口内,则移动窗口的起始位置
while (seen.has(s[end])) {
seen.delete(s[start]);
start++;
}
// 将当前字符添加到窗口内
seen.add(s[end]);
// 更新最长不重复子串的长度
maxLength = Math.max(maxLength, end - start + 1);
}
return maxLength;
}
// 示例
const s = "abcabcbb";
console.log(lengthOfLongestSubstring(s)); // 输出 3
9、 手写 instanceof 方法
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
首先获取类型的原型
然后获得对象的原型
然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 获取对象的原型
prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
- 大数相加
function addBigNumbers(num1, num2) {
// 结果数组
const result = [];
let carry = 0; // 进位
// 从个位开始计算
let i = num1.length - 1;
let j = num2.length - 1;
// 逐位相加
while (i >= 0 || j >= 0 || carry > 0) {
// 获取当前位的数字,如果没有则为0
const digit1 = i >= 0 ? parseInt(num1[i]) : 0;
const digit2 = j >= 0 ? parseInt(num2[j]) : 0;
// 计算当前位的和(包括进位)
const sum = digit1 + digit2 + carry;
// 当前位的结果
result.unshift(sum % 10);
// 计算进位
carry = Math.floor(sum / 10);
// 移动指针
i--;
j--;
}
return result.join('');
}
11、红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
// Async/Await 版本
async function taskAsync() {
console.log('=== Async/Await 版本 ===');
const light = (color, time) => {
console.log(`${color}灯亮`);
return new Promise(resolve => setTimeout(resolve, time));
};
// 无限循环
while (true) {
await light('红', 3000);
await light('绿', 1000);
await light('黄', 2000);
}
}