前端常见手写方法

88 阅读6分钟

防抖

/**
 * 防抖:一定的时间内,多次触发同一个事件,只执行最后一次
 */
function debounce(fn, delay) {
    let timer = null
    // 这里的 this 是 window,因为 debounce 是在 window 上调用的
    return function () {
        // 这里的 this 是 <input />
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => { // 这里因为使用了 箭头函数 ,所以用了外部的 this
            // 函数调用 
            fn.apply(this)
            clearTimeout(timer)
        }, delay)
    }
}

let input = document.querySelector('input')
input.oninput = debounce(function () { // 回调函数声明
    console.log(this.value)
}, 1000)

节流

/**
 * 节流:一个时间段内最多触发一次
 */
function throttle(fn, delay) {
    let timer = null
    return function () {
        if (timer) return
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null;
        }, delay)
    }
}

红绿灯交替

// 模拟一个红绿灯变化,红灯 1 秒,绿灯 1 秒,黄灯 1 秒,然后循环
function red() {
    console.log('red')
}
function green() {
    console.log('green')
}
function yellow() {
    console.log('yellow')
}
function light(cb) {
    return new Promise((resolve) => {
        setTimeout(() => {
            cb()
            resolve()
        }, 1000)
    })
}
function start() {
    light(red).then(() => {
        return light(green)
    }).then(() => {
        return light(yellow)
    }).finally(() => {
        start()
    })
}
start()

深拷贝

/**
 * 1.基本类型数据:赋值就是深拷贝了。
 * 2.引用类型数据:内部的引用类型数据全都独立开一块内存空间。数据之间完全互不干扰。
 * 3. 深拷贝的实现方式:
 *    a. 递归
 *    b. JSON.parse(JSON.stringify())
 *    c. structuredClone() const clonedObj = structuredClone(originalObj); 法拷贝函数、Symbol、DOM 节点等特殊对象
 */
function deepClone(obj, hash = new WeakMap()) {
    if (!obj || typeof obj !== 'object') return obj
    if (hash.has(obj)) return hash.get(obj)
    const newObj = Array.isArray(obj) ? [] : {}
    hash.set(obj, newObj)
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = deepClone(obj[key], hash)
        }
    }
    return newObj
}

浅拷贝

/**
 * 1. 基本数据类型:不存在浅拷贝概念
 * 2. 对象的浅拷贝是指创建一个新对象,新对象与原对象共享基本数据类型的属性值,但对于引用类型的属性,只复制其引用地址而非深层内容
 * 浅拷贝仅复制对象的第一层属性,对于嵌套对象仍共享引用
 * 3. 浅拷贝的实现方式:
 *    a. for...in 循环
 *    b. Object.assign({},{a:1,b:{c:2}}) 
 *    c. 展开运算符 ...
 */
function shallowCopy(obj) {
    if (!obj || typeof obj !== 'object') return obj
    const newObj = Array.isArray(obj) ? [] : {}
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) { // obj.hasOwnProperty(key) 用于判断对象 obj 是否 自身拥有 指定属性 key ,而不是从原型链继承的属性。
            newObj[key] = obj[key]
        }
    }
    return newObj
}

树转数组

树的层序遍历

const obj = {
    id: 1,
    name: '部门A',
    children: [
        {
            id: 2,
            name: '部门B',
            children: [
                { id: 4, name: '部门D' },
                { id: 5, name: '部门E' }
            ]
        },
        {
            id: 3,
            name: '部门C',
            children: [{ id: 6, name: '部门F' }]
        }
    ]
}
function convert1(root) {
    const nodeToParent = new Map() // key 当前节点 value 父节点
    const arr = []
    // 广度优先遍历, queue
    const queue = []
    queue.push(root) // 根节点 入队
    while (queue.length > 0) {
        const curNode = queue.shift() // 出队
        if (curNode == null) break
        const { id, name, children = [] } = curNode
        // 创建数组 item 并 push
        const parentNode = nodeToParent.get(curNode)
        const parentId = parentNode?.id || 0
        const item = { id, name, parentId }
        arr.push(item)
        // 子节点入队
        children.forEach(child => {
            // 映射 parent
            nodeToParent.set(child, curNode)
            // 入队
            queue.push(child)
        })
    }
    return arr
}

数组转树

const arr = [
    { id: 2, name: '部门B', parentId: 1 },
    { id: 4, name: '部门D', parentId: 2 },
    { id: 5, name: '部门E', parentId: 2 },
    { id: 6, name: '部门F', parentId: 3 },
    { id: 3, name: '部门C', parentId: 1 },
    { id: 1, name: '部门A', parentId: 0 },
]
function handle(arr) {
    const map = new Map();
    let root = null;
    // 先为每个节点创建newNode并存储到map,确保操作的是新节点
    arr.forEach(item => {
        const newNode = { id: item.id, name: item.name, children: [] };
        map.set(item.id, newNode);
        if (item.parentId === 0) {
            root = newNode; // 根节点指向newNode
        }
    });
    // 再为每个节点添加子节点(关联newNode)
    arr.forEach(item => {
        const parentNode = map.get(item.parentId);
        if (parentNode) {
            parentNode.children.push(map.get(item.id)); // 子节点是newNode
        }
    });
    return root;
}
console.log(handle(arr))

Promise

class Commitment {
    static PENDING = '待定'; static FULFILLED = '成功'; static REJECTED = '拒绝';
    constructor(func) {
        this.status = Commitment.PENDING;
        this.result = null;
        this.resolveCallbacks = [];
        this.rejectCallbacks = [];
        func(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(result) {
        setTimeout(() => {
            if (this.status === Commitment.PENDING) {
                this.status = Commitment.FULFILLED;
                this.result = result;
                this.resolveCallbacks.forEach(callback => {
                    callback(result);
                });
            }
        });
    }
    reject(result) {
        setTimeout(() => {
            if (this.status === Commitment.PENDING) {
                this.status = Commitment.REJECTED;
                this.result = result;
                this.rejectCallbacks.forEach(callback => {
                    callback(result);
                });
            }
        });
    }
    then(onFULFILLED, onREJECTED) {
        if (this.status === Commitment.PENDING) {
            this.resolveCallbacks.push(onFULFILLED);
            this.rejectCallbacks.push(onREJECTED);
        }
        if (this.status === Commitment.FULFILLED) {
            setTimeout(() => {
                onFULFILLED(this.result);
            });
        }
        if (this.status === Commitment.REJECTED) {
            setTimeout(() => {
                onREJECTED(this.result);
            });
        }
    }
}

Promise.race

// 有一个状态改变的时候就返回
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("SSSSSS")
    }, 1000)
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("XXXXXX")
    }, 2000)
})
Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        promises.forEach((item) => {
            // Promise.resolve(item) 确保 item 是一个 Promise 对象。如果 item 本身就是 Promise,它会直接返回该 Promise;如果 item 不是 Promise,它会将 item 包装成一个已 fulfilled 状态的 Promise 。
            Promise.resolve(item).then(resolve, reject)
        })
    })
}
Promise.race([p1, p2]).then(res => console.log(res)) // "SSSSSS"

Promise.all

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("SSSSSS")
    }, 1000)
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("XXXXXX")
    }, 2000)
})


Promise.all = function(promises) {
    let res = []
    let count = 0
    return new Promise((resolve, reject) => {
        promises.forEach((item, index) => {
            Promise.resolve(item).then((ans) => {
                res[index] = ans
                count ++
                if(promises.length === count) return resolve(res)
            },() => {
                return reject(res)
            })
        })
    })
}

Promise.all([p1, p2]).then(res => console.log(res))//·['SSSSSS', 'XXXXXX']

new

function customNew(constructor, ...args) {
    const obj = {}
    obj.__proto__ = constructor.prototype
    constructor.apply(obj, args)
    return obj
}
const obj = customNew(Foo, 'zhangsan')

LRU

class LRU {
    constructor(len) {
        this.len = len
        this.map = new Map()
    }
    set(key, value) {
        if (this.map.has(key)) {
            this.map.delete(key)
        }
        this.map.set(key, value)
        if (this.map.size > this.len) {
            this.map.delete(this.map.keys().next().value)
        }
    }
    get(key) {
        if (!this.map.has(key)) return null
        const value = this.map.get(key)
        this.map.delete(key)
        this.map.set(key, value)
        return value
    }
    has(key) {
        return this.map.has(key)
    }
}

LazyMan

/*
const me = new LazyMan('张三')
me.eat('苹果').eat('香蕉').sleep(5).eat('葡萄') // 打印结果如下:

// '张三 eat 苹果'
// '张三 eat 香蕉'
// (等待 5s) 
// '张三 eat 葡萄' 
*/
class LazyMan {
    constructor(name) {
        this.name = name
        this.tasks = []
        setTimeout(() => {
            this.next()
        })
    }
    next() {
        const task = this.tasks.shift() // 取出当前 tasks 的第一个任务
        if (task) task()
    }
    eat(food) {
        const task = () => {
            console.info(`${this.name} eat ${food}`)
            this.next() // 立刻执行下一个任务
        }
        this.tasks.push(task)
        return this // 链式调用
    }
    sleep(seconds) {
        const task = () => {
            console.info(`${this.name} 开始睡觉`)
            setTimeout(() => {
                console.info(`${this.name} 已经睡完了 ${seconds}s,开始执行下一个任务`)
                this.next() // xx 秒之后再执行下一个任务
            }, seconds * 1000)
        }
        this.tasks.push(task)
        return this // 链式调用
    }
}
new LazyMan('Tony')
    .eat('lunch')
    .eat('dinner')
    .sleep(2)
    .eat('junk food')

instanceof

export function myInstanceOf(instance, origin) {
    if (instance == null) return false // null undefined
    const type = typeof instance
    if (type !== 'object' && type !== 'function') {
        // 值类型
        return false
    }
    let tempInstance = instance // 为了防止修改 instance
    while (tempInstance) {
        if (tempInstance.__proto__ === origin.prototype) {
            return true // 配上了
        }
        // 未匹配
        tempInstance = tempInstance.__proto__ // 顺着原型链,往上找
    }
    return false
}

getType

// 获取详细的变量类型
function getType(data) {
    // 获取到 "[object Type]",其中 Type 是 Null、Undefined、Array、Function、Error、Boolean、Number、String、Date、RegExp 等。
    const originType = Object.prototype.toString.call(data)
    // 可以直接截取第8位和倒数第一位,这样就获得了 Null、Undefined、Array、Function、Error、Boolean、Number、String、Date、RegExp 等
    const type = originType.slice(8, -1)
    // 再转小写,得到 null、undefined、array、function 等
    return type.toLowerCase()
}

EventBus

/**
 * {
 *      'key1': [
 *             { fn: fn1, once: false },
 *             { fn: fn2, once: true }
 *        ],
 *       'key2': []
 *       'key3': []
 *  }   
 * }
 */
class EventBus {
    constructor() {
        this.events = {};
    }
    on(eventName, callback, isOnce = false) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        this.events[eventName].push({
            fn: callback,
            once: isOnce
        });
    }
    once(eventName, callback) {
        this.on(eventName, callback, true);
    }
    off(eventName, callback) {
        if (this.events[eventName]) {
            this.events[eventName] = this.events[eventName].filter(item => item.fn !== callback);
        }
    }
    emit(eventName, ...args) {
        if (this.events[eventName]) {
            this.events[eventName] = this.events[eventName].filter(item => {
                item.fn(...args);
                return !item.once;
            });
        } else {
            return
        }
    }
}

domTree

// 访问节点
function visitNode(n) {
    if (n instanceof Comment) {
        // 注释
        console.info('Comment node ----', n.textContent)
    }
    if (n instanceof Text) {
        // 文本
        const t = n.textContent?.trim()
        if (t) {
            console.info('Text node ----', t)
        }
    }
    if (n instanceof HTMLElement) {
        // element
        console.info('Element node ----', `<${n.tagName.toLowerCase()}>`)
    }
}

// 深度遍历
function depthFirstTraverse1(root) {
    visitNode(root)
    const childNodes = root.childNodes // .childNodes 和 .children 不一样
    if (childNodes.length) {
        childNodes.forEach(child => {
            depthFirstTraverse1(child) // 递归
        })
    }
}
// 非递归
function depthFirstTraverse2(root) {
    const stack = []
    // 根节点压栈
    stack.push(root)
    while (stack.length > 0) {
        const curNode = stack.pop() // 出栈
        if (curNode == null) break
        visitNode(curNode)
        // 子节点压栈
        const childNodes = curNode.childNodes
        if (childNodes.length > 0) {
            // reverse 反顺序压栈
            Array.from(childNodes).reverse().forEach(child => stack.push(child))
        }
    }
}
/**
 * 广度优先遍历
 * @param root dom node
 */
function breadthFirstTraverse(root) {
    const queue = [] // 数组 vs 链表
    // 根节点入队列
    if (root) queue.push(root); // 根节点入队
    while (queue.length > 0) {
        const curNode = queue.shift(); // 从队列头部取出节点
        if (!curNode) continue; // 跳过null节点
        visitNode(curNode)
        // 子节点入队
        const childNodes = curNode.childNodes
        if (childNodes.length) {
            childNodes.forEach(child => queue.push(child));
        }
    }
}

curry

/**
 * function add(a,b,c){
 *      return a+b+c;
 * }
 * add(1,2,3)
 * 等价于
 * const curryAdd = curry(add);
 * curryAdd(1)(2)(3)
 */
function curry(fn) {
    // 获取传入函数的参数长度
    const length = fn.length
    let args = []
    function calc(...newArgs) {
        // 积累参数
        args = [...args, ...newArgs]
        if (args.length < length) {
            // 参数不够时,返回函数
            return calc
        } else {
            // 参数够的时候,返回值
            return fn.apply(this, args.slice(0, length))
        }
    }
    return calc
}

compose

// 组合与管道
/**
 * value (1+1)*3 // 6
 * function add1(x){
 *    return x + 1
 * }
 * function mul3(x){
 *    return x * 3
 * }
 * console.log(mul3(add1(1)))
 * function compose(f,g){ // 函数从右向左执行
 *      return function(x){
 *          return f(g(x))
 *      }
 * }
 * console.log(compose(mul3,add1)(1))
 */

function compose(...funs) {
    let count = funs.length - 1;
    let result = undefined;
    return function fn(x) {
        if (count < 0) {
            return result;
        }
        result = funs[count--](x)
        return fn(result)
    }
}
console.log(compose(mul3, add1)(1)) //6

function compose(...funs) {
    let callback = function (f, g) {
        return function (x) {
            return f(g(x))
        }
    }
    let fn = funs[0]
    for (let i = 1; i < funs.length; i++) {
        fn = callback(fn, funs[i])
    }
    return fn;
}
console.log(compose(mul3, add1)(1)) //6

call

/*
function introduce(city, country) {
    console.log(`I am ${this.name} from ${city}, ${country}.`);
}
const person = { name: 'Bob' }; 
introduce.call(person, 'New York', 'USA');
*/
Function.prototype.myCall = function (context, ...args) {
    // 处理 null/undefined 并转换基本类型为对象
    context = context || globalThis;
    const ctx = typeof context !== 'object' ? Object(context) : context;
    // 使用 Symbol 避免属性冲突
    const fnSymbol = Symbol();
    ctx[fnSymbol] = this; // this 就是当前的函数
    // 直接使用剩余参数
    const result = ctx[fnSymbol](...args);
    delete ctx[fnSymbol];
    return result;
}

bind

/*
    返回一个新函数,但不执行,绑定 this 和 部分参数,无法改变箭头函数的 this
    function introduce(city, country) {
        console.log(`I am ${this.name} from ${city}, ${country}.`);
    }
    const person = { name: 'Bob' };
    const introduceBob = introduce.bind(person);
    introduceBob('Paris', 'France');
*/
Function.prototype.MyBind = function (context, ...args) {
    const self = this;
    context = context ?? globalThis;
    const ctx = typeof context !== 'object' ? Object(context) : context;
    const fnSymbol = Symbol();
    return function (...innerArgs) {
        ctx[fnSymbol] = self;
        const result = ctx[fnSymbol](...args, ...innerArgs);
        delete ctx[fnSymbol];
        return result;
    }
}

ArrayFlatten

// 数组扁平化
function flatten1(arr) {
    const result = []
    for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
            result.push(...flatten1(arr[i]))
        } else {
            result.push(arr[i])
        }
    }
    return result
}
// console.log(flatten1([1, [2, [3, [4, [5]]]]]))

function flattenArray(arr, depth = Infinity) {
    if (!Array.isArray(arr)) {
        return [arr];
    }
    if (arr.length === 0) {
        return [];
    }
    if (depth <= 0) {
        return [...arr];
    }
    const result = [];
    for (const item of arr) {
        if (Array.isArray(item) && depth > 0) {
            result.push(...flattenArray(item, depth - 1));
        } else {
            result.push(item);
        }
    }
    return result;
}
console.log(flattenArray([1, [2, [3, [4, [5]]]]], 1))
console.log(flattenArray([1, [2, [3, [4, [5]]]]], 2))
console.log(flattenArray([1, [2, [3, [4, [5]]]]], 3))
console.log(flattenArray([1, [2, [3, [4, [5]]]]], 4))

apply

/*
    function introduce(city, country) {
        console.log(`I am ${this.name} from ${city}, ${country}.`);
    }
    const person = { name: 'Bob' };
    introduce.apply(person, ['London', 'UK']);
*/
Function.prototype.myApply = function (context, args) {
    // 处理 null/undefined 并转换基本类型为对象
    context = context || globalThis;
    const ctx = typeof context !== 'object' ? Object(context) : context;
    // 使用 Symbol 避免属性冲突
    const fnSymbol = Symbol();
    ctx[fnSymbol] = this; // this 就是当前的函数
    // 直接使用剩余参数
    const result = ctx[fnSymbol](...args);
    delete ctx[fnSymbol];
    return result;
}

all

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("SSSSSS")
    }, 1000)
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("XXXXXX")
    }, 2000)
})
Promise.all = function(promises) {
    let res = []
    let count = 0
    return new Promise((resolve, reject) => {
        promises.forEach((item, index) => {
            Promise.resolve(item).then((ans) => {
                res[index] = ans
                count ++
                if(promises.length === count) return resolve(res)
            },() => {
                return reject(res)
            })
        })
    })
}
Promise.all([p1, p2]).then(res => console.log(res))//·['SSSSSS', 'XXXXXX']