js手写
浅拷贝实现
js
// 浅拷贝的实现;
// 浅拷贝的详细实现
function shallowCopy(object) {
// 只拷贝对象类型的数据(包括数组和对象),如果不是对象直接返回
if (!object || typeof object !== "object") return;
// 判断原对象是数组还是普通对象,创建对应的新对象
let newObject = Array.isArray(object) ? [] : {};
// 使用 for...in 遍历对象的所有可枚举属性(包括自有属性和原型链上的属性)
for (let key in object) {
// 只拷贝对象自身的属性,不拷贝原型链上的属性
// hasOwnProperty 是 Object 的一个实例方法,用于判断某个属性是否为对象自身的属性(不是原型链上的属性)
// 例如:
// let obj = {a: 1};
// obj.hasOwnProperty('a'); // true
// obj.hasOwnProperty('toString'); // false,因为 toString 来自原型链
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
return newObject;
}
深拷贝的实现(拷贝自身,而不是原型链上的)
js
// 深拷贝函数实现
function deepCopy(object) {
// 如果传入的不是对象或为null,直接返回(不进行拷贝)
if (!object || typeof object !== "object") return;
// 判断原对象是数组还是普通对象,创建对应的新对象
let newObject = Array.isArray(object) ? [] : {};
// 遍历对象的所有可枚举属性(包括自有属性和原型链上的属性)
for (let key in object) {
// 只拷贝对象自身的属性,不拷贝原型链上的属性
if (object.hasOwnProperty(key)) {
// 如果属性值是对象,则递归调用deepCopy进行深拷贝,否则直接赋值
newObject[key] =
typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
}
}
// 返回深拷贝后的新对象
return newObject;
}
防抖
js
function debounce(func,delay) {
let timer;
return function{
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
func()
},delay)
}
}
节流
js
// timer 需要被置为 null,是为了在定时器回调执行完毕后,允许下一次触发节流逻辑。
// 如果不将 timer 置为 null,那么 if(!timer) 条件将永远不成立,后续的触发都不会再执行。
// 这样可以保证每隔 delay 毫秒,func 只会被执行一次。
function throttle(func, delay) {
let timer = null;
return function() {
if (!timer) {
timer = setTimeout(() => {
func(); // 注意应为 func() 而不是 fn
timer = null; // 关键:定时器执行完后重置 timer,允许下次触发
}, delay);
}
}
}
手写定时器
html
<div id="clock"></div>
<button onclick="start()">Start</button>
<button onclick="stop()">Stop</button>
js
let timerId;
// 更新页面时间
function updateTime() {
const now = new Date();
// innerHTML 是 DOM 元素的一个属性,用于设置或获取元素内部的 HTML 内容(字符串形式),会解析为 HTML 结构。
// 还有一个类似的 API 叫 textContent,用于设置或获取元素的纯文本内容(不会解析为 HTML 标签,只显示文本)。
// innerHTML 会解析标签,textContent 只显示文本。
// toLocaleTimeString() 是 Date 对象的一个方法,用于将当前时间格式化为本地时间字符串(如 "12:34:56"),会根据用户的本地设置自动调整显示格式。
document.getElementById("clock").innerHTML = now.toLocaleTimeString();
// document.getElementById("clock").textContent = now.toLocaleTimeString(); // 只显示文本,不解析HTML
}
// 启动定时器(先停止旧的)
function start() {
stop(); // 避免重复启动
updateTime(); // 立即显示一次
timerId = setInterval(updateTime, 1000);
}
// 停止定时器
function stop() {
clearInterval(timerId);
}
React手写定时器
jsx
手写Promise.all
手写call
js
解析URL
js
/**
* 解析URL中的hash部分为键值对对象
* @param {string} url - 需要解析的URL
* @return {object} 解析后的hash参数对象
*/
function parseHashUrl(url) {
// 获取hash部分(去除#符号)
const hashString = (url.split('#')[1] || '');
// 如果hash为空,返回空对象
if (!hashString) return {};
// 解析hash字符串
const result = {};
// 处理有&分隔的多参数情况
const pairs = hashString.split('&');
pairs.forEach(pair => {
// 处理键值对
const [key, value] = pair.split('=');
// 如果有值则解码,否则设为true(表示参数存在)
if (key) {
result[key] = value ? decodeURIComponent(value) : true;
}
});
return result;
}
// 使用示例
const url = "https://example.com/page?query=test#id=123&name=%E5%BC%A0%E4%B8%89&active";
const hashParams = parseHashUrl(url);
console.log(hashParams);
// 输出: { id: "123", name: "张三", active: true }
数组扁平化
- 使用ES6 flat方法
js
const arr = [1,[2,[3,4]]]
arr.flat(Infinity)
- 使用reduce方法
js
function flatten(arr) {
return arr.reduce((prev,curr) => {
// 对于当前元素,检查是否为数组
// 如果是数组,递归调用flatten函数继续扁平化
// 如果不是数组,直接使用该元素
// 最后通过concat方法将结果合并到累加器中
// 这里用 prev.concat 而不是 curr.concat,是因为 reduce 的累加器 prev 是“已经累加好的结果数组”,
// 我们要把当前元素(或其扁平化结果)拼接到累加器后面,形成新的累加结果。
// 如果用 curr.concat(prev),顺序就会反了,而且 curr 可能不是数组,concat 也会报错。
return prev.concat(Array.isArray(curr) ? flatten(curr) : curr)
},[])
}
- 使用递归
js
function flatten(arr) {
let res = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(i)) {
res = res.concat(flatten(arr[i]))
} else {
res.push(arr[i])
}
}
return res;
}
对象扁平化
js
// DOM树扁平化
function flattenDOM(node, result = {}, path = '') {
// 为每个节点创建唯一id
const id = path || 'root' // 为当前节点创建一个唯一标识符,如果没有提供路径,则使用'root'作为标识符
result[id] = node; // 将当前节点存储在结果对象中,使用唯一标识符作为键
// 处理子节点
if(node.children) { // 检查当前节点是否有子节点
Array.from(node.children).forEach((child, index) => { // 将子节点集合转换为数组并遍历
flattenDom(child, result, `${id}_${index}`) // 递归处理每个子节点,并为其创建一个新的路径标识符
// 新路径由父节点id和子节点索引组成,确保唯一性
})
}
return result;
}
原型链继承
js
// ES6 class继承
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类构造函数
this.age = age;
}
sayAge() {
console.log(this.age);
}
}
// 测试
const child = new Child('小明', 16);
child.sayName(); // 输出: 小明
child.sayAge(); // 输出: 16
js
// 组合继承
// 父构造函数
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
// 子构造函数
function Child(name, age) {
// 继承属性
Parent.call(this, name); // 第一次调用父构造函数
this.age = age;
}
// 继承方法
Child.prototype = new Parent(); // 第二次调用父构造函数
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
// 测试
const child1 = new Child('张三', 18);
const child2 = new Child('李四', 20);
child1.sayName(); // 输出: 张三
child1.sayAge(); // 输出: 18
child1.colors.push('black');
console.log(child1.colors); // 输出: ["red", "blue", "green", "black"]
console.log(child2.colors); // 输出: ["red", "blue", "green"]
手写单例模式(基于Static
js
class Storage{
construct(){
this.data = {}
}
static getInstance() {
if(!Storage.instance()){
Storage.instance = new Storage()
}
return Storage.instance;
}
getItem([key]){
return this.data[key]
}
setItem([value,key]){
this.data[key] = value
return value
}
}
class Storage{
construct(){
this.data = {}
}
static getInstance() {
if(!Storage.instance) {
Storage.instance = new Storage()
}
// 如果存在,直接返回
return Storage.instance;
}
getItem(key) {
reurn this.data[key];
}
setItem(key,value) {
this.data[key] = value;
return value
}
}
const storage1 = Storage.getInstance();
const storage2 = Storage.getInstance();
storage1.setItem('name', 'A')
console.log(storage1.getItem())
// A
console.log(storage2.getItem())
// A
console.log(storage1 == storage2)
// true
reduce()实现数组求和
js
// 1. 带有初始值的数组求和
const numbers2 = [1, 2, 3, 4, 5];
const sumWithInitial = numbers2.reduce((acc, cur) => acc + cur, 10);
// sumWithInitial = 25
// 2. 处理多维数组求和
const multiArray = [[1, 2], [3, 4], [5, 6]];
const flatSum = multiArray.reduce((acc, cur) => {
return acc + cur.reduce((a, b) => a + b, 0);
}, 0);
// flatSum = 21
并发控制
js
/**
* 并发控制器
* @param {number} maxConcurrent 最大并发数
*/
function asyncPool(maxConcurrent = 3) {
// 任务队列,存储待执行的任务
const queue = [];
// 当前正在执行的任务数量
let running = 0;
// 执行队列中的任务
function run() {
// 如果队列为空或者已达到最大并发数,则不执行任何操作
if (queue.length === 0 || running >= maxConcurrent) return;
// 增加正在执行的任务计数
running++;
// 从队列头部取出一个任务
const { fn, resolve, reject } = queue.shift();
// 执行任务函数并处理结果
Promise.resolve(fn())
.then(resolve) // 任务成功时,调用外部Promise的resolve
.catch(reject) // 任务失败时,调用外部Promise的reject
.finally(() => {
// 无论成功失败,任务完成后都要减少计数
running--;
// 尝试执行队列中的下一个任务
run();
});
}
// 返回一个函数,用于添加新任务
return function addTask(fn) {
// 每个任务都包装成一个Promise
return new Promise((resolve, reject) => {
// 将任务及其resolve/reject函数一起推入队列
queue.push({ fn, resolve, reject });
// 尝试立即执行任务(如果并发数未达到上限)
run();
});
};
}
// 使用示例
// 创建一个最大并发数为2的控制器
const runTask = asyncPool(2);
// 这是一个使用示例,展示了如何使用并发控制器
// 首先创建一个最大并发数为2的任务控制器
// 创建一个模拟异步任务的函数
// 这个函数返回一个闭包,闭包内部创建一个Promise来模拟异步操作
function createTask(id, delay = 1000) {
return () => new Promise(resolve => {
console.log(`任务${id}开始`);
setTimeout(() => {
console.log(`任务${id}完成`);
resolve(`结果${id}`);
}, delay);
});
}
// 这段代码是测试用例,用于演示并发控制器的实际效果
// 添加5个任务,但由于并发限制为2,任务会分批执行:
// 先并发执行任务1和2
// 当任务1或2完成后,立即开始执行任务3
// 当另一个任务完成后,开始执行任务4
// 以此类推,直到所有任务完成
for (let i = 1; i <= 5; i++) {
runTask(createTask(i))
.then(result => console.log(result));
}
Vue3响应式系统简易实现
js
// 存储当前激活的副作用函数
let activeEffect = null;
// 存储所有响应式对象的依赖关系
const targetMap = new WeakMap();
// 注册副作用函数
function effect(fn) {
activeEffect = fn;
// 立即执行一次副作用函数来收集依赖
fn();
activeEffect = null;
return fn;
}
// 收集依赖
function track(target, key) {
if (!activeEffect) return;
// 获取目标对象的依赖映射
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// 获取特定属性的依赖集合
let deps = depsMap.get(key);
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
// 添加当前激活的副作用函数
deps.add(activeEffect);
}
// 触发更新
function trigger(target, key) {
// 获取目标对象的依赖映射
const depsMap = targetMap.get(target);
if (!depsMap) return;
// 获取特定属性的依赖集合
const deps = depsMap.get(key);
if (!deps) return;
// 执行所有相关的副作用函数
deps.forEach(effect => effect());
}
// 创建响应式对象
function reactive(target) {
return new Proxy(target, {
// 拦截属性读取操作
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
// 收集依赖
track(target, key);
return result;
},
// 拦截属性设置操作
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 触发更新
trigger(target, key);
return result;
}
});
}
// 创建响应式基础类型值
function ref(value) {
const refObj = {
get value() {
track(refObj, 'value');
return value;
},
set value(newValue) {
value = newValue;
trigger(refObj, 'value');
}
};
return refObj;
}