DFS:深度优先搜索
使用DFS的方式遍历DOM树,先从根节点开始遍历,如果当前节点有子节点,就递归遍历它的子节点,如果子节点还有子节点,就递归遍历其子节点,直到遍历到叶子节点为止。
在存储DOM节点时,我们可以使用一个数组来保存遍历到的DOM节点,同时递归遍历子节点,并将子节点保存在数组中。由于DFS的遍历方式是先遍历到最深处,因此在数组中的顺序也是按照深度的顺序存储的。
function dfs(node, nodes = []) {
if (node) {
nodes.push(node);
const children = node.childNodes;
for(let i = 0, len = children.length; i < len; i++) {
dfs(children[i], nodes);
}
}
return nodes;
}
const root = document.body; // 根节点为document.body
const nodes = dfs(root); // 存储DOM节点到数组中
BFS:广度优先搜索
使用BFS的方式遍历DOM树,先从根节点开始遍历,将其子节点加入队列中,然后将子节点出队,再将子节点的子节点加入队列中。这样就能够保证先遍历到根节点,然后依次遍历其子节点,再依次遍历子节点的子节点,以此类推。
在存储DOM节点时,我们可以使用一个队列来保存遍历到的DOM节点,从队列头部取出DOM节点进行遍历,同时将其子节点按照先后顺序加入队列中。由于BFS的遍历方式是按照层次递进的,因此在队列中的顺序也是按照层次顺序存储的。
function bfs(node) {
const nodes = [];
const queue = [];
queue.push(node);
while(queue.length > 0) {
const current = queue.shift();
nodes.push(current);
const children = current.childNodes;
for(let i = 0, len = children.length; i < len; i++) {
queue.push(children[i]);
}
}
return nodes;
}
const root = document.body; // 根节点为document.body
const nodes = bfs(root); // 存储DOM节点到数组中
关键字高亮
以下是一个使用React框架和正则表达式实现关键字高亮效果的示例代码。该代码使用了Ant Design作为UI组件库,在输入框中输入关键字时,文本中匹配到的关键字会高亮显示。
import React, { useState } from 'react';
import { Input } from 'antd';
import './App.css';
function App() {
const [text, setText] = useState('');
const [highlightedText, setHighlightedText] = useState('');
// 正则表达式匹配
function highlight(text, keyword) {
if (!keyword) {
return text;
}
const pattern = new RegExp(`(${keyword})`, 'gi');
const matches = text.match(pattern);
if (!matches) {
return text;
}
return text.split(pattern).map((str, i) =>
matches.includes(str) ? <mark key={i}>{str}</mark> : str,
);
}
function handleChange(e) {
const keyword = e.target.value;
setText(text);
setHighlightedText(highlight(text, keyword));
}
return (
<div className="App">
<h1>文字高亮示例</h1>
<Input placeholder="请输入关键字" onChange={handleChange} />
<div className="text">
{highlightedText || '请在上方输入框输入关键字'}
</div>
</div>
);
}
export default App;
在该示例代码中,我们使用了useState
来维护文本和高亮文本的状态。同时,我们定义了一个highlight
函数,使用正则表达式匹配文本中的关键字,将匹配到的关键字使用mark
标签包裹,实现关键字高亮的效果。
在输入框中输入关键字时,我们使用handleChange
函数来处理输入框的变化。该函数会将输入关键字和文本内容传入highlight
函数中,返回高亮文本,并更新组件的状态。在div
元素中,我们将高亮文本作为子节点展示在页面中。如果没有输入关键字,则显示提示信息。
在CSS中,我们可以设置mark
标签的颜色和背景色,来实现不同的高亮效果。
DOM转JSON
function domToJson(node) {
// 处理元素节点
if(node.nodeType === Node.ELEMENT_NODE) {
// 获取所有属性
const attributes = Array.from(node.attributes);
const attrs = {};
for(let i = 0; i < attributes.length; i++) {
attrs[attributes[i].name] = attributes[i].value;
}
// 获取所有子节点
const children = Array.from(node.childNodes);
const childrenArr = [];
for(let i = 0; i < children.length; i++) {
childrenArr.push(domToJson(children[i]));
}
// 构造JSON对象
const obj = {
tag: node.tagName,
attrs,
children: childrenArr,
};
return obj;
}
// 处理文本节点
if(node.nodeType === Node.TEXT_NODE) {
return node.textContent;
}
// 处理注释节点
if(node.nodeType === Node.COMMENT_NODE) {
return { comment: node.textContent };
}
}
// 使用
const domNode = document.querySelector('#example');
const jsonData = domToJson(domNode);
console.log(jsonData);
在代码中,我们将元素节点转换为一个拥有tag
、attrs
和children
三个属性的JSON对象,其中tag
表示节点的标签名,attrs
表示节点的属性,children
表示节点的子节点。
观察者模式
Observer类用于存储将要被观察的对象和它们对应的状态改变函数。Observable类中包含了注册观察者、从观察者列表中移除观察者、通知观察者以及设置状态的方法:
// Observer类用于存储一个将要被观察的对象及其对应的状态改变函数
class Observer {
constructor(obj, onStateChanged) {
this.obj = obj;
this.onStateChanged = onStateChanged;
}
}
// Observable类用于将Observer添加到观察者列表中,移除观察者,以及通知观察者状态改变的事件。
class Observable {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(toRemove) {
this.observers = this.observers.filter(observer => observer !== toRemove);
}
notifyObservers() {
this.observers.forEach(observer => {
observer.onStateChanged(this);
});
}
setState(newState) {
this.state = newState;
this.notifyObservers();
}
}
使用示例:
const observable = new Observable();
const observer1 = new Observer(observable, () => {
console.log('Observer 1: State changed to', observable.state);
});
const observer2 = new Observer(observable, () => {
console.log('Observer 2: State changed to', observable.state);
});
observable.addObserver(observer1);
observable.addObserver(observer2);
observable.setState('new state');
observable.removeObserver(observer2);
observable.setState('another state');
创建了一个新的Observable
对象,并向其中添加了两个Observer
。在Observable
对象中调用setState()
方法并改变其状态时,观察者列表中的每个观察者都将会被通知,状态改变函数也会被调用。
发布订阅模式
class EventEmitter {
constructor() {
// 构造函数中存储了一个对象,用于存储不同的事件及其对应的执行函数。
this.events = {};
}
on(eventName, listener) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(listener);
}
off(eventName, listenerToRemove) {
if (!this.events[eventName]) return;
this.events[eventName] = this.events[eventName].filter(listener => listener !== listenerToRemove);
}
emit(eventName, ...args) {
if (!this.events[eventName]) return;
this.events[eventName].forEach(listener => {
listener(...args);
});
}
once(eventName, listener) {
const wrapper = (...args) => {
listener(...args);
this.off(eventName, wrapper);
};
this.on(eventName, wrapper);
}
}
使用示例:
const emitter = new EventEmitter();
const listener1 = arg => {
console.log('Listener 1 called with', arg);
};
const listener2 = arg => {
console.log('Listener 2 called with', arg);
};
emitter.on('update', listener1);
emitter.on('update', listener2);
emitter.emit('update', { data: 'some data' });
emitter.off('update', listener2);
emitter.emit('update', { data: 'some other data' });
emitter.once('login', user => {
console.log('User logged in', user);
});
emitter.emit('login', { name: 'John Doe', email: 'johndoe@example.com' }); // Logs 'User logged in { name: "John Doe", email: "johndoe@example.com" }'
off()
方法用于从事件中删除一个特定的监听器,而once()
方法用于添加一个只会运行一次的监听器。
装饰器模式
装饰器模式是一种动态地为对象添加功能的设计模式,它通过将一个对象嵌入到另一个对象中实现对原对象的包装,并且可以在不改变原有对象的情况下添加新的功能。
在装饰器模式中,有三个核心角色:
抽象构件(Component):定义一个对象接口,可以给这些对象动态地添加职责。
具体构建(ConcreteComponent):实现抽象构件接口,提供了具体的功能实现。
装饰器(Decorator):持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
装饰器模式通过将对象的包装层层叠加,在不改变原有对象的基础上,动态地添加新的行为或功能,这使得行为或功能的添加变得更加灵活和方便,并且降低了系统的耦合度,增强了对象的扩展性。
举个例子来说,我们可以使用装饰器模式为一个类动态地添加新的方法或改变其属性,而不需要修改原有的代码。例如,我们可以给一个已经存在的类添加一个日志记录的功能:
class Order {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
// 日志装饰器
class OrderLogDecorator {
constructor(order) {
this.order = order;
}
getName() {
console.log(`Order has been created with name: ${this.order.getName()}`);
return this.order.getName();
}
}
// 使用装饰器
const order = new Order('test');
const orderLogDecorator = new OrderLogDecorator(order);
orderLogDecorator.getName(); // 输出结果:Order has been created with name: test
在上述代码中,我们首先定义了一个原有的Order类。然后我们又定义了一个装饰器类OrderLogDecorator,它持有了一个Order对象的实例,通过在getName()方法中添加日志记录的功能,实现了对原有Order类的包装。最后,我们将Order对象传递给OrderLogDecorator,通过调用OrderLogDecorator的getName()方法来让Order对象拥有了日志记录的功能。
装饰器模式常常与其他设计模式相结合,如工厂方法模式、抽象工厂模式、建造者模式等,以实现更复杂的功能和逻辑。
获取文件扩展名
function getFileExtension(filename) {
return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);
}
洗牌算法 (Knuth-Shuffle/Fisher–Yates Shuffle)
首先,从数组中的 N 个元素中随机选出一个元素,并将其与第 N 个位置上的元素进行交换,然后从前 N-1 个元素中随机选出一个元素并将其与第 N-1 个位置上的元素进行交换,以此类推,直到数组的第 1 个位置。最终得到的数组就是一个随机排序的数组。
以下是一种 Javascript 实现方式:
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // 随机产生一个小于等于 i 的整数
[array[i], array[j]] = [array[j], array[i]]; // 将位置 i 和位置 j 的元素进行交换
}
}
时间复杂度为O(n),并且能够保证每个元素被随机分布的概率相等。
leetcode
- Array
- 基础
- [X] 27 Remove Element
- [x] 26 Remove Duplicates from Sorted Array
- [x] 80 Remove Duplicates from Sorted Array II
- [x] 277 Find the Celebrity
- [x] 41 First Missing Positive
- [x] 189 Rotate Array
- [x] 299 Bulls and Cows
- [x] 118 Pascal's Triangle
- [x] 119 Pascal's Triangle II
- [x] 169 Majority Element
- [x] 229 Majority Element II
- [x] 274 H-Index
- [x] 275 H-Index II
- [ ]243 Shortest Word Distance 1
- [ ]244 Shortest Word Distance 2
- [ ]245 Shortest Word Distance 3
- [ ]217 Contains Duplicate
- [ ] 219 Contains Duplicate II
- [ ] 220 Contains Duplicate III
- [ ] 55 Jump Game
- [ ] 45 Jump Game II
- [ ] 121 Best Time to Buy and Sell Stock
- [ ] 122 Best Time to Buy and Sell Stock II
- [ ] 123 Best Time to Buy and Sell Stock III
- [ ] 188 Best Time to Buy and Sell Stock IV
- [ ] 309 Best Time to Buy and Sell Stock with Cooldown
- [ ] 11 Container With Most Water
- [ ] 42 Trapping Rain Water
- [ ] 334 Increasing Triplet Subsequence
- [ ] 128 Longest Consecutive Sequence
- [ ] 164 Maximum Gap Bucket
- [ ] 287 Find the Duplicate Number
- [ ] 135 Candy
- [ ] 330 Patching Array
- [ ] 提高
- [ ] 4 Median of Two Sorted Arrays
- [ ] 321 Create Maximum Number
- [ ] 327 Count of Range Sum
- [ ] 289 Game of Life
- [ ] Interval
- [ ] 57 Insert Interval
- [ ] 56 Merge Intervals
- [ ] 252 Meeting Rooms
- [ ] 253 Meeting Rooms II
- [ ] 352 Data Stream as Disjoint Intervals TreeMap
- [ ] Counter
- [ ] 239 Sliding Window Maximum
- [ ] 295 Find Median from Data Stream
- [ ] 53 Maximum Subarray
- [ ] 325 Maximum Size Subarray Sum Equals k
- [ ] 209 Minimum Size Subarray Sum
- [ ] 238 Product of Array Except Self
- [ ] 152 Maximum Product Subarray
- [ ] 228 Summary Ranges
- [ ] 163 Missing Ranges
- [ ] Counter
- [ ] 88 Merge Sorted Array
- [ ] 75 Sort Colors
- [ ] 283 Move Zeroes
- [ ] 376 Wiggle Subsequence
- [ ] 280 Wiggle Sort
- [ ] 324 Wiggle Sort II
- [ ] 278 First Bad Version
- [ ] 35 Search Insert Position
- [ ] 33 Search in Rotated Sorted Array
- [ ] 81 Search in Rotated Sorted Array II
- [ ] 153 Find Minimum in Rotated Sorted Array
- [ ] 154 Find Minimum in Rotated Sorted Array II
- [ ] 162 Find Peak Element
- [ ] 374 Guess Number Higher or Lower
- [ ] 34 Find First and Last Position of Element in Sorted Array
- [ ] 349 Intersection of Two Arrays
- [ ] 350 Intersection of Two Arrays II
- [ ] 315 Count of Smaller Numbers After Self
- [ ] 300 Longest Increasing Subsequence
- [ ] 354 Russian Doll Envelopes