数组转树形结构 convert
const list = [
{ id: 1, name: '部门A', parentId: 0 },
{ id: 2, name: '部门B', parentId: 1 },
{ id: 3, name: '部门C', parentId: 1 },
{ id: 4, name: '部门D', parentId: 2 },
{ id: 5, name: '部门E', parentId: 2 },
{ id: 6, name: '部门F', parentId: 3 },
]
function convert(arr) {
const res = []
const map = new Map()
for (let item of arr) {
const { id, parentId } = item
map.set(id, item)
const parentNode = map.get(parentId)
if (parentNode) {
parentNode.children = parentNode.children ? parentNode.children : []
parentNode.children.push(item)
} else {
res.push(item)
}
}
return res
}
// test
convert(list)
树形结构转数组
const data = [
{
id: 1,
name: "部门A",
parentId: 0,
children: [
{
id: 2,
name: "部门B",
parentId: 1,
children: [
{
id: 4,
name: "部门D",
parentId: 2,
},
{
id: 5,
name: "部门E",
parentId: 2,
},
],
},
{
id: 3,
name: "部门C",
parentId: 1,
children: [
{
id: 6,
name: "部门F",
parentId: 3,
},
],
},
],
},
];
// 转换为数组
const list = [
{ id: 1, name: "部门A", parentId: 0 },
{ id: 2, name: "部门B", parentId: 1 },
{ id: 3, name: "部门C", parentId: 1 },
{ id: 4, name: "部门D", parentId: 2 },
{ id: 5, name: "部门E", parentId: 2 },
{ id: 6, name: "部门F", parentId: 3 },
];
function reverse(roots) {
const res = [];
const dfs = (node) => {
// Create a copy without the children property
const { children, ...nodeWithoutChildren } = node;
res.push(nodeWithoutChildren);
if (node.children) {
node.children.forEach((child) => dfs(child));
}
};
// Process all root nodes (convert returns an array of roots)
roots.forEach((root) => dfs(root));
return res;
}
// test
const reverseRes = reverse(data);
console.log("reverse result:", reverseRes);
实现 instanceof
/**
* 实现 instanceof 运算符
* @param {Object} obj 要检测的对象
* @param {Function} constructor 要检测的构造函数
* @return {boolean} 如果 obj 是 constructor 的实例则返回 true,否则返回 false
*/
function myInstanceof(obj, constructor) {
// 如果检测的对象为 null 或不是对象类型,直接返回 false
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
return false;
}
// 获取对象的原型
let proto = Object.getPrototypeOf(obj);
// 获取构造函数的 prototype 对象
const prototype = constructor.prototype;
// 沿着原型链向上查找
while (proto !== null) {
// 如果在原型链中找到了构造函数的 prototype 对象,返回 true
if (proto === prototype) {
return true;
}
// 继续在原型链上向上查找
proto = Object.getPrototypeOf(proto);
}
// 找到原型链顶端仍未找到,返回 false
return false;
}
// 测试用例
function Person() {}
const person = new Person();
console.log(myInstanceof(person, Person)); // true
console.log(myInstanceof(person, Object)); // true
console.log(myInstanceof(person, Array)); // false
console.log(myInstanceof(null, Object)); // false
console.log(myInstanceof({}, Object)); // true
console.log(myInstanceof([], Array)); // true
数组扁平化
concat 方式
function flatten_concat(arr) {
let res = []
arr.forEach(item => {
if (Array.isArray(item)) {
res = res.concat(flatten_concat(item))
} else {
res.push(item)
}
})
return res
}
// 测试
const list = [1, [2, [3, 4], 5], 6]
flatten_concat(list)
// 输出 [1, 2, 3, 4, 5, 6]
reduce
function flatten_reduce(arr) {
return arr.reduce((acc, cur) => {
return acc.concat(Array.isArray(cur) ? flatten_reduce(cur) : cur)
}, [])
}
// 测试
const list = [1, [2, [3, 4], 5], 6]
flatten_reduce(list)
// 输出 [1, 2, 3, 4, 5, 6]
函数科里化 curry
function curry(fn) {
const fnLength = fn.length
let args = []
function calc(...newArgs) {
args = [
...args,
...newArgs
]
if (args.length < fnLength) {
return calc
} else {
return fn.apply(this, args.slice(0, fnLength))
}
}
return calc
}
function add(a, b, c) {
return a + b + c
}
const addCurry = curry(add)
const res = addCurry(1)(2)(3)
console.log(res) // 输出6
获取参数类型 getType
function getType(x) {
const originType = Object.prototype.toString.call(x) // '[object String]'
const spaceIndex = originType.indexOf(' ')
const type = originType.slice(spaceIndex + 1, -1) // 'String'
return type.toLowerCase() // 'string'
}
new
function myNew(constructor, ...args) {
const obj = Object.create(constructor.prototype)
const res = constructor.apply(obj, args)
return (typeof res === 'object' || typeof res ==='function') ? res: obj
}
bind
Function.prototype.customBind = function (context, ...bindArgs) {
// context 是 bind 传入的 this,bindArgs 是 bind 传入的各个参数
// 当前函数本身
const self = this
return function (...args) {
// 拼接参数
const newArgs = [...bindArgs, ...args]
return self.apply(context, newArgs)
}
}
fn1.customBind({ x: 100 }, 10)
call
Function.prototype.customCall = function (context, ...args) {
if (context === null) {
context = globalThis
}
if (typeof context !== 'object') {
context = new Object(this)
}
// 防止出现属性名称的覆盖
const fnKey = new Symbol()
// this 就是当前的函数
context[fnKey] = this
const res = context[fnKey](...args)
delete context[fnKey]
return res
}
apply
Function.prototype.customApply = function (context, args) {
if (context === null) {
context = globalThis
}
if (typeof context !== 'object') {
context = new Object(context)
}
const fnKey = new Symbol()
context[fnKey] = this
const res = context[fnKey](...args)
delete context[fnKey]
return res
}
LazyMan
class LazyMan {
private name: string
// 任务列表
private tasks: Function[] = []
constructor(name) {
this.name = name
setTimeout(() => {
this.next()
})
}
private next() {
// 取出当前 tasks 的第一个任务
const task = this.tasks.shift()
if (task) task()
}
eat(food: string) {
const task = () => {
console.log(`${this.name} eat ${food}`)
this.next()
}
this.tasks.push(task)
// 支持链式调用需要返回 this
return this
}
sleep(s: number) {
const task = () => {
console.log(`${this.name} start sleep`)
setTimeout(() => {
console.log(`${this.name} sleep ${s}秒`)
this.next()
}, s * 1000)
}
this.tasks.push(task)
// 支持链式调用需要返回 this
return this
}
}
const xiaoming = new LazyMan("xiaoming")
xiaoming.eat("apple").eat("banana").sleep(3).eat("orange")
事件总线 EventBus
class EventBus{
private events: {
[key:string]: Array<{fn: Function; isOnce: boolean}>
}
constructor() {
this.events = {}
}
on(type:string, fn: Function, isOnce: boolean=false) {
const events = this.events
if(!events[type]) {
// 初始化
events[type] = []
}
events[type].push({fn, isOnce})
}
once(type:string, fn: Function) {
this.on(type, fn, true)
}
off(type:string, fn?: Function) {
if(!fn) {
// 解绑所有 type 事件
this.events[type] = []
} else {
// 解绑单个 fn
const fnList = this.events[type]
if(fnList) {
this.events[type] = fnList.filter(item=> item.fn!==fn)
}
}
}
emit(type:string, ...args:any[]) {
const fnList = this.events[type]
if(fnList) {
this.events[type] = fnList.filter(item=> {
const {fn, isOnce} = item
fn(...args)
// once 执行一次就要被过滤掉
if(!isOnce) return true
return false
})
}
}
}
// 演示
const e = new EventBus()
function fn1(a:any, b:any){console.log('fn1',a,b)}
function fn2(a:any, b:any){console.log('fn2',a,b)}
function fn3(a:any, b:any){console.log('fn3',a,b)}
e.on('key1', fn1)
e.on('key1', fn2)
e.once('key1', fn3)
e.on('kkkkkkey', fn3)
e.emit('key1', 10,20)
e.emit('key1', 10,20)
e.off('key1', fn1)
观察者模式
// 定义发布者类
class Publisher {
constructor() {
this.observers = []
console.log('Publisher created')
}
// 增加订阅者
add(observer) {
console.log('Publisher.add invoked')
this.observers.push(observer)
}
// 移除订阅者
remove(observer) {
console.log('Publisher.remove invoked')
this.observers.forEach((item, i) => {
if (item === observer) {
this.observers.splice(i, 1)
}
})
}
// 通知所有订阅者
notify() {
console.log('Publisher.notify invoked')
this.observers.forEach((observer) => {
observer.update(this)
})
}
}
// 定义订阅者类
class Observer {
constructor() {
console.log('Observer created')
}
update() {
console.log('Observer.update invoked')
}
}
BFS DFS 实现document.querySelectAll('.a')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<p class="a">1</p>
<p class="a">2</p>
<p class="a">3</p>
</div>
</body>
<script>
// 深度优先遍历
function dfsFindNode(node) {
const res = []
if (node && node.nodeType === 1) {
if (/\ba\b/.test(node.className)) {
res.push(node)
}
const children = node.children
if (children) {
for (let i = 0; i < children.length; i++) {
let child = children[i]
res.push(...dfsFindNode(child))
}
}
}
return res
}
// 广度优先遍历
function bfsFindNode(node) {
const res = []
const queue = [node]
while (queue.length) {
let curNode = queue.shift()
if (curNode && curNode.nodeType === 1) {
if (/\ba\b/.test(curNode.className)) {
res.push(curNode)
}
const children = curNode.children
if (children) {
for (let i = 0; i < children.length; i++) {
const child = children[i]
queue.push(child)
}
}
}
}
return res
}
const dfsRes = dfsFindNode(document.body)
const bfsRes = bfsFindNode(document.body)
console.log('dfsRes', dfsRes)
console.log('bfsRes', bfsRes)
</script>
</html>
手写浅拷贝
// es6的Object.assign
Object.assign(target, source1, source2);
// 扩展运算符
{...obj1, ...obj2}
// 数组的浅拷贝
Array.prototype.slice
Array.prototype.concat
// 手动实现
function shallowCopy(object) {
if(!object || typeof object !== 'object') return;
let newObj = Array.isArray(object)?[]:{};
for(let key in object) {
if(object.hasOwnProperty(key)) {
newObj[key] = object(key);
}
}
return newObj;
}
手写深拷贝 deepClone
function deepClone(obj:any, map = new WeakMap()):any{
if(typeof obj === null || typeof obj !=='object') return obj
// 避免循环引用
const objFromMap = map.get(obj)
if(objFromMap) return objFromMap
let target: any = {}
map.set(obj, target)
// 如果是 Map
if(obj instanceof Map) {
target = new Map()
obj.forEach((v, k)=> {
const v1 = deepClone(v, map)
const k1 = deepClone(k, map)
target.set(k1,v1)
})
}
// 如果是 Set
if(obj instanceof Set) {
target = new Set()
obj.forEach(v=> {
const v1 = deepClone(v, map)
target.add(v1)
})
}
// 如果是数组
if(obj instanceof Array) {
target = obj.map(item=> deepClone(item, map))
}
// 如果是对象
for(let key in obj) {
const val = deepClone(obj[key], map)
target[key] = val
}
return target
}
实现迭代器
function makeIterator(arr) {
let index = 0
return {
next: function () {
return index < arr.length ? {
value: arr[index++],
done: false
} : {
value: undefined,
done: true
}
}
}
}
var it = makeIterator(["a", "b"]);
console.log(it.next()); // { value: "a", done: false }
console.log(it.next()); // { value: "b", done: false }
console.log(it.next()); // { value: undefined, done: true }
Object.create()
function create(obj) {
function Fun(){
}
Func.prototype = obj;
Func.prototype.constructor = Func;
return new Fun();
}
防抖
function debounce(fn, wait) {
let timer = null;
return function() {
if(timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn.apply(this, arguments);
}, wait);
}
}
节流
function throttle(fn, delay) {
let timer = null;
return function () {
if (timer) return;
timer = setTimeout(() => {
timer = null;
return fn.apply(this, arguments);
}, delay)
}
}
简单实现async/await中的async函数
async/await 语法糖就是使用 Generator 函数+自动执行器来运作的,注意只要要实现async函数就是实现一个 generate 函数+执行器的语法糖
// 定义了一个promise,用来模拟异步请求,作用是传入参数++
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}
//自动执行器,如果一个Generator函数没有执行完,则递归调用
function asyncFun(func){
var gen = func();
function next(data){
var result = gen.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
// 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步
var func = function* (){
var f1 = yield getNum(1);
var f2 = yield getNum(f1);
console.log(f2) ;
};
asyncFun(func);
手写深度比较 lodash.isEqual
// 手写深度比较 lodash.isEqual
// 判断是否是对象或者数组
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
// 全相等(深度比较)
function isEqual(obj1, obj2) {
if (!isObject(obj1) || !isObject(obj2)) {
// 值类型(注意,参与 equal 的一般不会是函数)
return obj1 === obj2
}
if (obj1 === obj2) {
return true
}
// 两个都是对象或数组,而且不相等
// 1. 先去除 obj1 和 obj2 的 keys,比较个数
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
if (obj1Keys.length !== obj2Keys.length) {
return false
}
// 2. 以 obj1 为基准,和 obj2 依次递归比较
for (let key in obj1) {
// 比较当前 key 的val
const res = isEqual(obj1[key], obj2[key])
if (!res) {
return false
}
}
// 3. 全相等
return true
}
const obj1 = { a: 10, b: { x: 100, y: 200 } }
const obj2 = { a: 10, b: { x: 100, y: 200 } }
console.log(isEqual(obj1, obj2))
手写-实现一个对象的 flatten 方法
const obj = {
a: {
b: 1,
c: 2,
d: { e: 5 }
},
b: [1, 3, { a: 2, b: 3 }],
c: 3
}
// 结果返回如下
// {
// 'a.b': 1,
// 'a.c': 2,
// 'a.d.e': 5,
// 'b[0]': 1,
// 'b[1]': 3,
// 'b[2].a': 2,
// 'b[2].b': 3
// c: 3
// }
function isObject(obj) {
return typeof obj === 'object' && typeof obj !== null
}
function flatten(obj) {
if (!isObject(obj)) { return obj }
const res = {}
function dfs(cur, prefix) {
if (isObject(cur)) {
if (Array.isArray(cur)) {
// 数组
cur.forEach((item, index) => {
dfs(item, `${prefix}[${index}]`)
})
} else {
// 对象
for (let key in cur) {
dfs(cur[key], `${prefix}${prefix ? "." : ""}${key}`)
}
}
} else {
res[prefix] = cur
}
}
dfs(obj, '')
return res
}
flatten(obj)
setTimeout 模拟实现 setInterval
- 使用setInterval时,某些间隔会被跳过
- 可能多个定时器会连续执行
每个setTimeout产生的任务会直接push到任务队列中;而setInterval在每次把任务push到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中)。因而我们一般用setTimeout模拟setInterval,来规避掉上面的缺点
function mySetInterval(fn, t) {
let timerId = null;
function interval() {
fn();
timerId = setTimeout(interval, t); // 递归调用
}
timerId = setTimeout(interval, t); // 首次调用
return {
// 利用闭包的特性 保存timerId
cancel: () => {
clearTimeout(timerId)
}
}
}
// 测试
var a = mySetInterval(() => {
console.log(111);
}, 1000)
var b = mySetInterval(() => {
console.log(222)
}, 1000)
// 终止定时器
a.cancel()
b.cancel()
setInterval 模拟实现 setTimeout
const mySetTimeout = (fn, t) => {
const timer = setInterval(() => {
clearInterval(timer);
fn();
}, t);
};
将虚拟 Dom 转化为真实 Dom
// 将虚拟 Dom 转化为真实 Dom
const vnode = {
tag: 'DIV',
attrs: {
id: 'app'
},
children: [
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
}
//把上述虚拟Dom转化成下方真实Dom
// < div id = "app" >
// <span>
// <a></a>
// </span>
// <span>
// <a></a>
// <a></a>
// </span>
// </div >
// 真正的渲染函数
function _render(vnode) {
// 如果是数字类型转化为字符串
if (typeof vnode === "number") {
vnode = String(vnode);
}
// 字符串类型直接就是文本节点
if (typeof vnode === "string") {
return document.createTextNode(vnode);
}
// 普通DOM
const dom = document.createElement(vnode.tag);
if (vnode.attrs) {
// 遍历属性
Object.keys(vnode.attrs).forEach((key) => {
const value = vnode.attrs[key];
dom.setAttribute(key, value);
});
}
// 子数组进行递归操作 这一步是关键
vnode.children.forEach((child) => dom.appendChild(_render(child)));
return dom;
}
把一个 DOM 节点输出 JSON 的格式
<div>
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>
// 把上述 dom 结构转成下面的JSON格式
{
tag: 'DIV',
children: [
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
}
function dom2Json(domtree) {
let obj = {};
obj.tag = domtree.tagName;
obj.children = [];
domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child)));
return obj;
}
js下划线转驼峰处理
function camelCase(str) {
return str.replace(/_([a-z])/g, function (match, group1) {
return group1.toUpperCase()
})
}
console.log(camelCase("some_string")); // "someString"
实现有并发限制的 Promise 调度器
class Scheduler {
constructor(limit) {
this.limit = limit; // 最大并行任务数
this.running = 0; // 当前运行的任务数
this.queue = []; // 任务队列,每一个任务都是一个函数
}
// 创建一个任务
createTask(callback, duration) {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
callback();
resolve();
}, duration);
});
};
}
/**
* 添加一个任务
* @param {Function} callback 任务,一个函数
* @param {number} duration 任务的运行时长,使用定时器模拟
* @returns void
*/
addTask(callback, duration) {
const task = this.createTask(callback, duration);
this.queue.push(task);
}
// 启动,开始处理队列里的任务
start() {
for (let i = 0; i < this.limit; i++) {
this.schedule();
}
}
// 调度任务
schedule() {
// 当任务队列为空时或者目前并发执行的任务 >= limit 时,停止任务调度
if (this.queue.length === 0 || this.running >= this.limit) {
return;
}
this.running++;
const task = this.queue.shift();
task().then(() => {
this.running--;
this.schedule();
});
}
}
// 实例化一个调度器
const scheduler = new Scheduler(2);
// 添加任务
scheduler.addTask(() => {
console.log("任务1");
}, 1000);
scheduler.addTask(() => {
console.log("任务2");
}, 500);
scheduler.addTask(() => {
console.log("任务3");
}, 300);
scheduler.addTask(() => {
console.log("任务4");
}, 400);
// 任务执行
scheduler.start();
lazyLoad 图片懒加载
function mapImagesAndTryLoad() {
const images = document.querySelectorAll('img[data-src]')
if(images.length === 0) return
images.forEach(img=> {
const rect = img.getBoundingClientRect()
if(rect.top < window.innerHeight) {
// 图片露出
img.src = img.dataset.src
// 移除 data-src 属性,为下次执行减少计算
img.removeAttribute('data-src')
}
})
}
document.addEventListener('scroll', _.throttle(()=> {
mapImagesAndTryLoad()
}, 100))
// 初始化需要加载一次
mapImagesAndTryLoad()
洋葱圈 koa-compone
function compose (middleware) {
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
手写 promise.all 和 race
//静态方法
static all(promiseArr) {
let result = [];
//声明一个计数器 每一个promise返回就加一
let count = 0;
return new Mypromise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
//这里用 Promise.resolve包装一下 防止不是Promise类型传进来
Promise.resolve(promiseArr[i]).then(
(res) => {
//这里不能直接push数组 因为要控制顺序一一对应
result[i] = res;
count++;
//只有全部的promise执行成功之后才resolve出去
if (count === promiseArr.length) {
resolve(result);
}
},
(err) => {
reject(err);
}
);
}
});
}
//静态方法
static race(promiseArr) {
return new Mypromise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
Promise.resolve(promiseArr[i]).then(
(res) => {
//promise数组只要有任何一个promise 状态变更 就可以返回
resolve(res);
},
(err) => {
reject(err);
}
);
}
});
}
}
用 JS 实现一个 LRU 缓存
class LRUCache {
constructor(length) {
this.length = length
this.map = new Map()
}
set(key, val) {
if (this.map.has(key)) {
this.map.delete(key)
}
this.map.set(key, val)
if (this.map.size > this.length) {
const deleteKey = this.map.keys().next().value
this.map.delete(deleteKey)
}
}
get(key) {
if (!this.map.has(key)) return null
const val = this.map.get(key)
this.map.delete(key)
this.map.set(key, val)
}
}
const lruCache = new LRUCache(2)
lruCache.set(1, 2)
lruCache.set(2, 3)
lruCache.set(3, 4)
lruCache.set(4, 5)
console.log(lruCache.get(1))
console.log(lruCache)
BFS和DFS实现document.querySelectAll('.a')
DFS
const dfsFindNode = (node) => {
const res = [];
if(node && node.nodeType === 1){ // 判断当前节点是否为元素节点
if(/\ba\b/.test(node.className)){
res.push(node);
}
const children = node.children;// 获取当前节点的子节点元素列表
for(let i = 0; i < children.length; i++){
const child = children[i];
res.push(...dfsFindNode(child))
}
}
return res;
}
const Nodes = dfsFindNode(document.body); // 查找 document.body 下所有 class 为 a 的元素节点
BFS:
function bfsFindNode(node) {
const res = []; // 存放符合条件的节点的数组
const queue = [node]; // 定义一个队列,初始值为根节点
while (queue.length > 0) { // 当队列不为空时进行遍历
const cur = queue.shift(); // 取出队头元素,并作为当前处理的节点
if (cur.nodeType === 1 && /\ba\b/.test(cur.className)) { // 判断当前节点是否为元素节点,并且其 class 属性是否包含 a
res.push(cur); // 如果符合条件,则将当前节点加入结果数组中
}
const children = cur.children; // 获取当前节点的所有子元素
for (let i = 0; i < children.length; i++) { // 遍历每个子元素
const child = children[i];
queue.push(child); // 将子元素加入队列尾部,等待被处理
}
}
return res; // 返回所有符合条件的节点组成的数组
}
const Nodes = bfsFindNode(document.body); // 查找 document.body 下所有 class 为 a 的元素节点
快照沙箱 - SnapshotSandbox
class SnapshotSandbox {
constructor() {
this.windowSnapshot = {}
this.modifyPropsMap = {}
}
active() {
// 1. 保存 window 的快照
for (let prop in window) {
if (window.hasOwnProperty(prop)) {
this.windowSnapshot[prop] = window[prop]
}
}
// 2. 再次激活时,将 window 还原到上次 active 的状态,modifyPropsMap 存储了上次 active 时在 widow 上修改了哪些属性
Object.keys(modifyPropsMap).forEach(prop => {
window[prop] = this.modifyPropsMap[prop]
})
}
inactive() {
for(let prop in window) {
if (window.hasOwnProperty(prop)) {
// 两者不相同,表示修改了某个 prop 记录当前在 window 上修改了的 prop
if (window[prop] !== this.windowSnapshot[prop]) {
this.modifyPropsMap[prop] = window[prop]
}
// 还原 window
window[prop] = this.windowSnapshot[prop]
}
}
}
}
//验证 case
window.city = 'beijing'
const ss = new SnapshotSandbox()
console.log('window.city0 ', window.city)
ss.active() // 激活
window.city = '上海'
console.log('window.city1 ', window.city) // 上海
ss.inactive()
console.log('window.city2 ', window.city) // beijing
ss.active()
console.log('window.city3 ', window.city) // 上海
ss.inactive()
console.log('window.city4 ', window.city) // beijing
ss.active()
console.log('window.city5 ', window.city) // 上海
代理沙箱 - ProxySandbox
class ProxySandbox {
constructor() {
// 沙箱是否是激活状态
this.isRunning = false
const fakeWindow = Object.create(null)
const _this = this
this.proxyWindow = new Proxy(fakeWindow, {
set(target, prop, value) {
// 只有激活状态下,才做处理
if (_this.isRunning) {
target[prop] = value
return true
}
},
get(target, prop, reciver) {
// 如果fakeWindow里面有,就从fakeWindow里面取,否则,就从外部的window里面取
return prop in target ? target[prop] : window[prop]
}
})
}
active() {
this.isRunning = true
}
inactive() {
this.isRunning = false
}
}
window.city = '北京'
const p1 = new ProxySandbox()
const p2 = new ProxySandbox()
// 激活
p1.active()
p2.active()
p1.proxyWindow.city = '上海'
p2.proxyWindow.city = '杭州'
console.log(p1.proxyWindow.city) // '上海'
console.log(p2.proxyWindow.city) // '杭州'
console.log(window.city) // 北京
// 失活
p1.inactive()
p2.inactive()
console.log(p1.proxyWindow.city) // '上海'
console.log(p2.proxyWindow.city) // '杭州'
console.log(window.city) // '北京'
版本号排序
// 常规实现
function sortVersions(versions) {
return versions.sort((a, b) => {
const partsA = a.split('.').map(Number);
const partsB = b.split('.').map(Number);
const maxLength = Math.max(partsA.length, partsB.length);
for (let i = 0; i < maxLength; i++) {
const numA = partsA[i] || 0; // 如果部分不存在,默认为0
const numB = partsB[i] || 0;
if (numB > numA) return 1; // b应该排在a前面
if (numB < numA) return -1; // a应该排在b前面
}
return 0; // 版本号完全相同
});
}
// 测试
const input = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'];
const result = sortVersions(input);
console.log('排序结果:', result);
// 更简洁的实现
function sortVersions(versions) {
return versions.sort((a, b) => {
const partsA = a.split('.').map(Number);
const partsB = b.split('.').map(Number);
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
const numA = partsA[i] || 0;
const numB = partsB[i] || 0;
if (numB !== numA) return numB - numA;
}
return 0;
});
}
Promise 以及相关方法的实现
class Mypromise {
constructor(fn) {
// 表示状态
this.state = "pending";
// 表示then注册的成功函数
this.successFun = [];
// 表示then注册的失败函数
this.failFun = [];
let resolve = (val) => {
// 保持状态改变不可变(resolve和reject只准触发一种)
if (this.state !== "pending") return;
// 成功触发时机 改变状态 同时执行在then注册的回调事件
this.state = "success";
// 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里为模拟异步
setTimeout(() => {
// 执行当前事件里面所有的注册函数
this.successFun.forEach((item) => item.call(this, val));
});
};
let reject = (err) => {
if (this.state !== "pending") return;
// 失败触发时机 改变状态 同时执行在then注册的回调事件
this.state = "fail";
// 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里模拟异步
setTimeout(() => {
this.failFun.forEach((item) => item.call(this, err));
});
};
// 调用函数
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
// 实例方法 then
then(resolveCallback, rejectCallback) {
// 判断回调是否是函数
resolveCallback =
typeof resolveCallback !== "function" ? (v) => v : resolveCallback;
rejectCallback =
typeof rejectCallback !== "function"
? (err) => {
throw err;
}
: rejectCallback;
// 为了保持链式调用 继续返回promise
return new Mypromise((resolve, reject) => {
// 将回调注册到successFun事件集合里面去
this.successFun.push((val) => {
try {
// 执行回调函数
let x = resolveCallback(val);
//(最难的一点)
// 如果回调函数结果是普通值 那么就resolve出去给下一个then链式调用 如果是一个promise对象(代表又是一个异步) 那么调用x的then方法 将resolve和reject传进去 等到x内部的异步 执行完毕的时候(状态完成)就会自动执行传入的resolve 这样就控制了链式调用的顺序
x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
} catch (error) {
reject(error);
}
});
this.failFun.push((val) => {
try {
// 执行回调函数
let x = rejectCallback(val);
x instanceof Mypromise ? x.then(resolve, reject) : reject(x);
} catch (error) {
reject(error);
}
});
});
}
//静态方法
static all(promiseArr) {
let result = [];
//声明一个计数器 每一个promise返回就加一
let count = 0;
return new Mypromise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
//这里用 Promise.resolve包装一下 防止不是Promise类型传进来
Promise.resolve(promiseArr[i]).then(
(res) => {
//这里不能直接push数组 因为要控制顺序一一对应
result[i] = res;
count++;
//只有全部的promise执行成功之后才resolve出去
if (count === promiseArr.length) {
resolve(result);
}
},
(err) => {
reject(err);
}
);
}
});
}
//静态方法
static race(promiseArr) {
return new Mypromise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
Promise.resolve(promiseArr[i]).then(
(res) => {
//promise数组只要有任何一个promise 状态变更 就可以返回
resolve(res);
},
(err) => {
reject(err);
}
);
}
});
}
}
// 使用
// let promise1 = new Mypromise((resolve, reject) => {
// setTimeout(() => {
// resolve(123);
// }, 2000);
// });
// let promise2 = new Mypromise((resolve, reject) => {
// setTimeout(() => {
// resolve(1234);
// }, 1000);
// });
// Mypromise.all([promise1,promise2]).then(res=>{
// console.log(res);
// })
// Mypromise.race([promise1, promise2]).then(res => {
// console.log(res);
// });
// promise1
// .then(
// res => {
// console.log(res); //过两秒输出123
// return new Mypromise((resolve, reject) => {
// setTimeout(() => {
// resolve("success");
// }, 1000);
// });
// },
// err => {
// console.log(err);
// }
// )
// .then(
// res => {
// console.log(res); //再过一秒输出success
// },
// err => {
// console.log(err);
// }
// );
JSON.stringify()
function myStringify(target) {
if (typeof target !== 'object' || target === null) {
// 对字符串特殊处理,多双引号
if(typeof target==='string'){
return `"${target}"`
}else{
return String(target);
}
}else{
//处理对象和数组
const json = [];
for(let key in target){
let value = target[key];
if(Array.isArray(target)){
json.push(`${myStringify(value)}`)
}else{
json.push(`"${key}":${myStringify(value)}`)
}
}
//这里需要注意,当数组或对象被转化为字符串时,就没了括号
if (Array.isArray(target)) {
return `[${json}]`
} else {
return `{${json}}`
}
}
}
console.log(myStringify('string'))
// "string"
console.log(myStringify(666))
// 666
console.log(myStringify({name1: {name2: "abc"}}))
// {"name1":{"name2":"abc"}}
console.log(myStringify([1, "false", false]))
// [1,"false",false]
dom树转json对象以及dom的遍历
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="header">
<div class="content1">111</div>
<div class="content2">222</div>
<div class="content3">333</div>
</div>
<div id="main">
<div class="content4">
<span>内容1</span>
<span>内容2</span>
<span>内容3</span>
</div>
</div>
<div id="footer">
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
</body>
<script>
window.onload = function (){
let root = document.getElementsByTagName('body')[0];
if(!root.length) return [];
// dom树转json
// function dom2json(root){
// let obj = {};
// obj.name = root.tagName;
// console.log(root.tagName);
// obj.children = [];
// root.childNodes.forEach(child=>{
// if(child.tagName!==undefined) {
// obj.children.push(dom2json(child))
// }
// })
// return obj
// }
// console.log(dom2json(root))
//层次遍历dom树
const nodes = []
const queue = [root]
while(queue.length) {
const curNode = queue.shift()
nodes.push(curNode)
if(curNode.children.length) {
queue.push(...curNode.children)
}
}
return nodeList
}
</script>
</html>
将省市区三级树结构转换为数组结构的JavaScript算法
// 示例输入数据格式
const treeData = {
name: "江苏省",
children: [
{
name: "南京市",
children: [{ name: "玄武区" }, { name: "秦淮区" }, { name: "建邺区" }],
},
{
name: "苏州市",
children: [{ name: "姑苏区" }, { name: "虎丘区" }, { name: "吴中区" }],
},
],
};
function convertTreeToArray(treeData) {
const result = [];
function traverse(node, path = []) {
// 将当前节点名称加入路径
const currentPath = [...path, node.name];
// 如果是叶子节点(没有子节点),将完整路径加入结果
if (!node.children || node.children.length === 0) {
result.push(currentPath.join('-'));
return;
}
// 递归遍历子节点
if (node.children && node.children.length > 0) {
for (const child of node.children) {
traverse(child, currentPath);
}
}
}
traverse(treeData);
return result;
}
// 使用示例
const result = convertTreeToArray(treeData);
console.log(result);
// 输出: ['江苏省-南京市-玄武区', '江苏省-南京市-秦淮区', '江苏省-南京市-建邺区', '江苏省-苏州市-姑苏区', '江苏省-苏州市-虎丘区', '江苏省-苏州市-吴中区']
function convertTreeToArrayGeneric(treeData, separator = '-') {
const result = [];
function traverse(node, path = []) {
const currentPath = [...path, node.name];
// 如果没有子节点,说明是叶子节点,将路径加入结果
if (!node.children || node.children.length === 0) {
result.push(currentPath.join(separator));
return;
}
// 递归遍历所有子节点
for (const child of node.children) {
traverse(child, currentPath);
}
}
traverse(treeData);
return result;
}
// 使用示例
const result2 = convertTreeToArrayGeneric(treeData);
console.log(result2);
// 多个省份的数据格式
const multiProvinceData = [
{
name: '安徽省',
children: [
{
name: '安庆市',
children: [
{ name: '大观区' },
{ name: '迎江区' },
{ name: '宜秀区' }
]
}
]
},
{
name: '江苏省',
children: [
{
name: '南京市',
children: [
{ name: '玄武区' },
{ name: '秦淮区' },
{ name: '建邺区' }
]
},
{
name: '苏州市',
children: [
{ name: '姑苏区' },
{ name: '虎丘区' },
{ name: '吴中区' }
]
}
]
}
];
function convertMultipleTreesToArray(trees) {
const result = [];
for (const tree of trees) {
const treeResult = convertTreeToArray(tree);
result.push(...treeResult);
}
return result;
}
// 使用示例
const multiResult = convertMultipleTreesToArray(multiProvinceData);
console.log(multiResult);
// 输出: ['安徽省-安庆市-大观区', '安徽省-安庆市-迎江区', '安徽省-安庆市-宜秀区', '江苏省-南京市-玄武区', '江苏省-南京市-秦淮区', '江苏省-南京市-建邺区', '江苏省-苏州市-姑苏区', '江苏省-苏州市-虎丘区', '江苏省-苏州市-吴中区']