#01 js防抖
在 JavaScript 中,防抖(debounce)是一种常用的优化技术,用于限制某个函数在一定时间内的触发次数。
一、防抖的概念
当用户进行某些频繁触发的操作时,比如滚动事件、输入框的输入事件等,如果直接绑定事件处理函数,可能会导致函数被频繁调用,从而影响性能。防抖的目的就是在用户操作停止一段时间后,才真正执行相应的函数,避免不必要的频繁调用。
二、实现防抖的方法
以下是一个简单的防抖函数的实现:
function debounce(func, delay) {
let timer;
return function() {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}
这个函数接受两个参数:要执行的函数 func
和延迟时间 delay
。它返回一个新的函数,当这个新函数被调用时,会先清除之前设置的定时器,然后重新设置一个定时器。如果在延迟时间内没有再次调用这个新函数,定时器到期后就会执行原始函数。
三、使用防抖的示例
假设我们有一个输入框,当用户在输入框中输入内容时,我们希望在用户停止输入一段时间后才进行搜索操作。可以这样使用防抖函数:
html:
<input type="text" id="searchInput">
js:
const input = document.getElementById('searchInput');
function search() {
console.log('Searching...');
}
input.addEventListener('input', debounce(search, 500));
在这个例子中,当用户在输入框中输入内容时,debounce(search, 500)
返回的新函数会被触发。如果用户在 500 毫秒内没有再次输入,那么 search
函数就会被执行,进行搜索操作。如果用户在 500 毫秒内继续输入,那么定时器会被清除,重新开始计时。
四、防抖的应用场景
搜索框输入:如上述例子所示,避免在用户输入过程中频繁进行搜索,提高性能。
窗口大小调整:当用户调整窗口大小时,可能会触发一些需要重新布局或计算的操作。使用防抖可以在用户停止调整窗口一段时间后再执行这些操作,避免频繁计算。
按钮点击:如果一个按钮在短时间内可能被多次点击,使用防抖可以确保只有在用户停止点击一段时间后才执行相应的操作,避免重复执行。
#02 js深拷贝
在 JavaScript 中,深拷贝是指创建一个对象的完全独立的副本,包括对象的所有嵌套属性和子对象。以下是关于 JavaScript 中深拷贝的详细介绍:
一、为什么需要深拷贝
避免数据共享:当你有一个复杂的对象,并且希望对其进行修改而不影响原始对象时,深拷贝就非常有用。如果只是进行浅拷贝(例如使用赋值操作或 Object.assign()
),修改副本可能会意外地修改原始对象,因为它们共享相同的嵌套对象引用。
数据独立性:在某些情况下,你可能需要将一个对象传递给不同的部分代码,并且确保这些部分代码对该对象的修改不会相互影响。深拷贝可以提供这种数据独立性。
二、实现深拷贝的方法
使用递归函数:
function deepCopy(obj) {
if (obj === null || typeof obj!== 'object') {
return obj;
}
let copy;
if (Array.isArray(obj)) {
copy = [];
for (let i = 0; i < obj.length; i++) {
copy[i] = deepCopy(obj[i]);
}
} else {
copy = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
}
return copy;
}
使用 JSON 序列化和反序列化:
const originalObject = { a: 1, b: { c: 2 } };
const copiedObject = JSON.parse(JSON.stringify(originalObject));
三、应用场景
对象克隆:当你需要创建一个与现有对象完全相同但独立的副本时,可以使用深拷贝。例如,在某些数据处理场景中,你可能需要对一个对象进行多次操作,但又不想影响原始对象。
参数传递:在函数调用中,如果你希望传递一个对象的副本而不是原始对象,以避免函数内部的修改影响到外部的对象,可以使用深拷贝来传递参数。
数据持久化:当你需要将对象保存到本地存储或数据库时,通常需要进行深拷贝以确保保存的是对象的独立副本,而不是对原始对象的引用。
总之,深拷贝在 JavaScript 中是一个重要的技术,用于创建对象的独立副本,避免数据共享和意外的修改。在实际应用中,根据具体情况选择合适的深拷贝方法可以提高代码的可靠性和可维护性。
#03 js节流
在 JavaScript 中,节流(throttle)是一种用于控制函数执行频率的技术,确保函数在特定时间间隔内最多执行一次。
一、节流的作用
性能优化:在一些频繁触发的事件中,如滚动事件、鼠标移动事件、窗口大小调整事件等,如果直接绑定事件处理函数,可能会导致函数被频繁调用,从而占用大量的计算资源,影响性能。节流可以限制函数的执行频率,减少不必要的计算,提高性能。
防止过度触发:在某些情况下,我们希望函数在一定时间内只执行一次,即使事件被频繁触发。例如,在发送网络请求时,我们可能希望在用户输入完成后再发送请求,而不是每次输入都发送请求。节流可以帮助我们实现这种需求,防止函数被过度触发。
二、节流的实现方法
使用时间戳和定时器:
function throttle(func, delay) {
let lastTime = 0;
let timer = null;
return function() {
const now = Date.now();
const context = this;
const args = arguments;
if (now - lastTime > delay) {
func.apply(context, args);
lastTime = now;
} else if (!timer) {
timer = setTimeout(() => {
func.apply(context, args);
timer = null;
lastTime = Date.now();
}, delay);
}
};
}
使用定时器和标志位:
function throttle(func, delay) {
let isThrottled = false;
let timer = null;
return function() {
const context = this;
const args = arguments;
if (!isThrottled) {
func.apply(context, args);
isThrottled = true;
setTimeout(() => {
isThrottled = false;
}, delay);
} else if (!timer) {
timer = setTimeout(() => {
func.apply(context, args);
timer = null;
}, delay);
}
};
}
三、节流的使用场景
滚动事件处理:在网页滚动时,可能需要执行一些计算或更新操作。如果直接绑定滚动事件处理函数,可能会导致函数被频繁调用,影响性能。使用节流可以限制函数在滚动事件中的执行频率,提高性能。
window.addEventListener('scroll', throttle(function() {
// 执行滚动事件处理函数
}, 200));
鼠标移动事件处理:在某些应用中,可能需要根据鼠标移动的位置进行实时计算或更新。使用节流可以限制函数在鼠标移动事件中的执行频率,减少不必要的计算,提高性能。
document.addEventListener('mousemove', throttle(function(event) {
// 执行鼠标移动事件处理函数
}, 50));
输入框实时搜索:在输入框中输入内容时,可能需要实时搜索并显示搜索结果。使用节流可以限制搜索函数的执行频率,避免在用户输入过程中频繁发送网络请求,提高性能。
const input = document.getElementById('searchInput');
input.addEventListener('input', throttle(function() {
const query = input.value;
// 执行搜索函数
}, 300));
总之,节流是一种非常有用的技术,可以在 JavaScript 中控制函数的执行频率,提高性能,防止过度触发。在实际应用中,根据具体情况选择合适的节流方法和时间间隔,可以有效地优化代码的性能。
#04 js手写Promise
以下是用 JavaScript 手写一个简单的 Promise:
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach((callback) => callback(this.value));
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach((callback) => callback(this.reason));
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function'? onFulfilled : (value) => value;
onRejected = typeof onRejected === 'function'? onRejected : (reason) => { throw reason; };
let promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
} else {
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
try {
let x = onRejected(reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called = false;
if (x instanceof MyPromise) {
x.then((y) => {
resolvePromise(promise2, y, resolve, reject);
}, reject);
} else if (x!== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, (y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
if (called) return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
你可以使用这个自定义的 Promise 如下:
let myPromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('Success!');
}, 1000);
});
myPromise.then((value) => {
console.log(value);
return 'Another value';
}).then((value) => {
console.log(value);
});
这个实现虽然简单,但涵盖了 Promise 的基本功能,包括异步执行、状态转换和链式调用。在实际应用中,可能需要进一步扩展和优化这个实现以满足更复杂的需求。
#05 js 异步控制并发数
在 JavaScript 中,可以通过多种方式来控制异步操作的并发数。以下是一种常见的实现方法:
function asyncFunctionWithConcurrencyLimit(asyncFunctions, concurrencyLimit) {
let inFlightCount = 0;
let results = [];
let index = 0;
function executeNext() {
if (index < asyncFunctions.length && inFlightCount < concurrencyLimit) {
inFlightCount++;
const currentIndex = index;
index++;
const asyncFunc = asyncFunctions[currentIndex];
asyncFunc().then((result) => {
results[currentIndex] = result;
inFlightCount--;
executeNext();
}).catch((error) => {
results[currentIndex] = error;
inFlightCount--;
executeNext();
});
}
}
for (let i = 0; i < concurrencyLimit && i < asyncFunctions.length; i++) {
executeNext();
}
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
if (results.length === asyncFunctions.length) {
clearInterval(interval);
resolve(results);
}
}, 100);
});
}
使用方法如下:
// 模拟一些异步函数
function asyncTask(index) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Task ${index} completed`);
}, Math.random() * 2000);
});
}
});
const tasks = Array.from({ length: 10 }, (_, index) => asyncTask(index));
const concurrencyLimit = 3;
asyncFunctionWithConcurrencyLimit(tasks, concurrencyLimit).then((results) => {
console.log(results);
在这个例子中,asyncFunctionWithConcurrencyLimit
函数接受一个异步函数数组和一个并发数限制作为参数。它通过控制同时执行的异步函数数量来确保不会超出并发限制。当一个异步函数完成时,会自动启动下一个异步函数,直到所有的异步函数都完成。最后,它返回一个 Promise,当所有异步函数都完成时,这个 Promise 会被 resolve,结果数组包含了所有异步函数的执行结果。
#06 js继承
在 JavaScript 中,实现继承有多种方式。以下是几种常见的方法:
一、原型链继承
基本原理:
示例代码:
function Parent() {
this.parentProperty = 'parent value';
}
Parent.prototype.parentMethod = function() {
console.log('This is a parent method.');
};
function Child() {}
Child.prototype = new Parent();
const child = new Child();
console.log(child.parentProperty); // 'parent value'
child.parentMethod(); // 'This is a parent method.'
优缺点:
二、构造函数继承
基本原理:
示例代码:
function Parent(name) {
this.parentProperty = 'parent value';
this.name = name;
}
function Child(name) {
Parent.call(this, name);
this.childProperty = 'child value';
}
const child = new Child('child name');
console.log(child.parentProperty); // 'parent value'
console.log(child.name); // 'child name'
优缺点:
三、组合继承
基本原理:
示例代码:
function Parent(name) {
this.parentProperty = 'parent value';
this.name = name;
}
Parent.prototype.parentMethod = function() {
console.log('This is a parent method.');
};
function Child(name) {
Parent.call(this, name);
this.childProperty = 'child value';
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child('child name');
console.log(child.parentProperty); // 'parent value'
console.log(child.name); // 'child name'
child.parentMethod(); // 'This is a parent method.
优缺点:
四、寄生组合式继承
基本原理:
示例代码:
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
function Parent(name) {
this.parentProperty = 'parent value';
this.name = name;
}
Parent.prototype.parentMethod = function() {
console.log('This is a parent method.');
};
function Child(name) {
Parent.call(this, name);
this.childProperty = 'child value';
}
inheritPrototype(Child, Parent);
const child = new Child('child name');
console.log(child.parentProperty); // 'parent value'
console.log(child.name); // 'child name'
child.parentMethod(); // 'This is a parent method.'
优缺点:
总之,JavaScript 中的继承方式各有优缺点,开发者可以根据实际情况选择合适的继承方式。
#07 js数组排序
在 JavaScript 中,可以使用数组的 sort
方法对数组进行排序。sort
方法会对数组进行原地排序,即直接修改原数组。
一、基本排序
升序排序:
const numbers = [4, 2, 5, 1, 3];
const sortedNumbers = numbers.sort();
console.log(sortedNumbers); // [1, 2, 3, 4, 5]
降序排序:
const numbers = [4, 2, 5, 1, 3];
const sortedNumbers = numbers.sort((a, b) => b - a);
console.log(sortedNumbers); // [5, 4, 3, 2, 1]
二、对象数组排序
根据对象属性排序:
const people = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 35 }
];
const sortedPeople = people.sort((a, b) => a.age - b.age);
console.log(sortedPeople);
// [
// { name: 'Bob', age: 25 },
// { name: 'Alice', age: 30 },
// { name: 'Charlie', age: 35 }
// ]
多个属性排序:
const people = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 30 },
{ name: 'David', age: 25 }
];
const sortedPeople = people.sort((a, b) => {
if (a.age!== b.age) {
return a.age - b.age;
} else {
return a.name.localeCompare(b.name);
}
});
console.log(sortedPeople);
// [
// { name: 'Bob', age: 25 },
// { name: 'David', age: 25 },
// { name: 'Alice', age: 30 },
// { name: 'Charlie', age: 30 }
// ]
三、稳定性
稳定性的重要性:
JavaScript 的 sort
方法的稳定性:
四、自定义比较函数
灵活的比较逻辑:
const fruits = ['apple', 'banana', 'cherry', 'date'];
const sortedFruits = fruits.sort((a, b) => {
if (a.length!== b.length) {
return a.length - b.length;
} else {
return a.localeCompare(b);
}
});
console.log(sortedFruits);
// ['date', 'apple', 'cherry', 'banana']
处理不同类型的数据:
JavaScript 的数组排序功能非常强大,可以通过简单的方式实现各种排序需求。在使用 sort
方法时,需要注意比较函数的返回值规则,以确保得到正确的排序结果。同时,根据具体情况选择合适的排序算法和比较函数,可以提高排序的效率和稳定性。
#08 js 数组去重
在 JavaScript 中,可以通过以下几种方法实现数组去重:
一、使用 ES6 的 Set 和扩展运算符
基本原理:
示例代码:
const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3, 4, 5]
二、使用 for 循环和 indexOf
基本原理:
示例代码:
const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = [];
for (let i = 0; i < arr.length; i++) {
if (uniqueArr.indexOf(arr[i]) === -1) {
uniqueArr.push(arr[i]);
}
}
console.log(uniqueArr); // [1, 2, 3, 4, 5]
三、使用 forEach 和 includes
基本原理:
示例代码:
const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = [];
arr.forEach(item => {
if (!uniqueArr.includes(item)) {
uniqueArr.push(item);
}
});
console.log(uniqueArr); // [1, 2, 3, 4, 5]
四、使用 reduce
基本原理:
示例代码:
const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = arr.reduce((acc, item) => {
if (!acc.includes(item)) {
acc.push(item);
}
return acc;
}, []);
console.log(uniqueArr); // [1, 2, 3, 4, 5]
五、对象属性法
基本原理:
示例代码:
const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = [];
const temp = {};
arr.forEach(item => {
if (!temp[item]) {
temp[item] = true;
uniqueArr.push(item);
}
});
console.log(uniqueArr); // [1, 2, 3, 4, 5]
这些方法各有优缺点,可以根据具体情况选择合适的方法。例如,使用 Set 和扩展运算符的方法简洁高效,但不支持在低版本的浏览器中使用;使用 for 循环和 indexOf 的方法兼容性好,但代码相对较长。
#09 js 获取URL参数
在 JavaScript 中,可以通过以下几种方式获取 URL 中的参数:
一、使用正则表达式
基本原理:
示例代码:
function getUrlParams(url) {
const params = {};
const regex = /[?&]([^=#]+)=([^]*)/g;
let match;
while ((match = regex.exec(url))!== null) {
params[match[1]] = decodeURIComponent(match[2]);
}
return params;
}
const url = 'https://example.com/page?param1=value1¶m2=value2';
const params = getUrlParams(url);
console.log(params); // {param1: "value1", param2: "value2"}
二、使用 URLSearchParams API
基本原理:
示例代码:
const url = 'https://example.com/page?param1=value1¶m2=value2';
const urlParams = new URLSearchParams(url.split('?')[1]);
const params = {};
for (const [key, value] of urlParams.entries()) {
params[key] = value;
}
console.log(params); // {param1: "value1", param2: "value2"}
三、手动解析 URL
基本原理:
示例代码:
function getUrlParams(url) {
const params = {};
const queryString = url.split('?')[1];
if (!queryString) return params;
const pairs = queryString.split('&');
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i].split('=');
const key = decodeURIComponent(pair[0]);
const value = decodeURIComponent(pair[1]);
params[key] = value;
}
return params;
}
const url = 'https://example.com/page?param1=value1¶m2=value2';
const params = getUrlParams(url);
console.log(params); // {param1: "value1", param2: "value2"}
这些方法可以根据具体的需求和环境选择使用。如果浏览器支持URLSearchParams
API,那么使用它是一种比较简洁和现代的方式。如果需要兼容旧版本的浏览器,可以使用正则表达式或手动解析 URL 的方法。
#10 js发布订阅模式
在 JavaScript 中,发布订阅模式是一种常用的设计模式,它允许对象之间进行松耦合的通信。在发布订阅模式中,有三个主要的角色:发布者、订阅者和事件中心。
一、发布订阅模式的概念
发布者(Publisher):发布者是产生事件的对象。当发布者发生某些事件时,它会通知事件中心。
订阅者(Subscriber):订阅者是对特定事件感兴趣的对象。订阅者向事件中心注册自己,以便在特定事件发生时得到通知。
事件中心(Event Center):事件中心是发布者和订阅者之间的中介。它负责管理事件的订阅和发布,当发布者发布事件时,事件中心会通知所有订阅了该事件的订阅者。
二、发布订阅模式的实现
使用对象实现:
const eventCenter = {
events: {},
subscribe(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
},
publish(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback(data));
}
},
unsubscribe(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(cb => cb!== callback);
}
}
};
使用类实现:
class EventEmitter {
constructor() {
this.events = {};
}
subscribe(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
publish(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback(data));
}
}
unsubscribe(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(cb => cb!== callback);
}
}
}
三、发布订阅模式的使用
订阅事件:
ventCenter.subscribe('eventName', (data) => {
console.log(`Received event "${eventName}" with data: ${data}`);
});
eventCenter.publish('eventName', 'event data');
取消订阅事件:
const callback = (data) => {
console.log(`Received event "${eventName}" with data: ${data}`);
};
eventCenter.subscribe('eventName', callback);
eventCenter.unsubscribe('eventName', callback);
四、发布订阅模式的优点
松耦合:发布者和订阅者之间没有直接的依赖关系,它们通过事件中心进行通信。这使得代码更加灵活和可维护。
可扩展性:可以轻松地添加新的发布者和订阅者,而不会影响现有的代码。
事件驱动:发布订阅模式是一种事件驱动的编程方式,它可以更好地处理异步操作和并发事件。
五、发布订阅模式的缺点
内存管理:如果订阅者没有正确地取消订阅事件,可能会导致内存泄漏。
调试困难:由于发布者和订阅者之间没有直接的调用关系,调试起来可能会比较困难。
发布订阅模式是一种非常有用的设计模式,它可以帮助你实现松耦合的通信,提高代码的可维护性和可扩展性。在使用发布订阅模式时,需要注意内存管理和调试问题,以确保代码的正确性和性能。
该文章在 2024/10/19 12:40:37 编辑过