一些手写记录

34 阅读13分钟

数组转树形结构 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);
// 输出: ['安徽省-安庆市-大观区', '安徽省-安庆市-迎江区', '安徽省-安庆市-宜秀区', '江苏省-南京市-玄武区', '江苏省-南京市-秦淮区', '江苏省-南京市-建邺区', '江苏省-苏州市-姑苏区', '江苏省-苏州市-虎丘区', '江苏省-苏州市-吴中区']