Published on

手撕函数实现

Authors
  • avatar
    Name
    Sorain
    Twitter

Guide


new

new.js
function myNew(constructor, ...args) {
    // 1. 使用 Object.create 创建一个以 constructor.prototype 为原型的新对象  
    const obj = Object.create(constructor.prototype);  
    // 2. 将构造函数内部的 this 绑定到新对象,并执行构造函数  
    const result = constructor.apply(obj, args);  
    // 3. 如果构造函数返回一个对象,则返回该对象,否则返回新对象  
    return result instanceof Object ? result : obj; 
}

function Person(name, gender) {
    this.name = name;
    this.gender = gender;
}

const person = myNew(Person, 'Sorain', 'male');
console.log(person);

Promise 相关

all

promise-all.js
Promise.myAll = function (params) {
    return new Promise((resolve, reject) => {
        const res = [];
        for (let index = 0; index < params.length; index++) {
            Promise.resolve(params[index]).then(_res => {
                res[index] = _res;
                // 不能用res.push(_res);是因为不知道params里的promise哪一个先完成,而all是要求按顺序返回
                if (res.length === params.length) resolve(res);
            }, reject);
        }
        if (!params?.length) resolve(res);
    });
}

Promise.myAll([Promise.resolve(0), 1, 2, 3, Promise.resolve(4), Promise.resolve(5), Promise.resolve(6), 7]).then((res) => {
    console.log("resolve:", res);
}, (err) => {
    console.log("reject:", err);
}).catch((err) => {
    console.log("catch:", err);
});

bind

bind.js
Function.prototype.myBind = function (ctx, ...args) {
    const fn = this;
    return function (...subArgs) {
        const _args = [...args, ...subArgs];
        if (new.target) {
            return new fn(..._args);
        } else {
            return fn.apply(ctx, _args);
        }
    };
};

function fn(a, b, c) {
    console.log('fn called.');
    console.log('args', a, b, c);
    console.log('this', this);
    return "fn return";
}

const newFn = fn.myBind('mockThis', 1, 2);
console.log(newFn(3));
console.log('----------');
console.log(new newFn(5));

柯里化 curry

curry.js
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return function(...subArgs) {
                return curried.apply(this, args.concat(subArgs));
            }
        }
    }
}

function add(a, b, c) {
    return a + b + c;
}

const curriedAdd = curry(add);
console.log(curriedAdd(1, 2)(3, 4));
console.log(curriedAdd(1, 2, 3));
console.log(curriedAdd(1)(2)(3));

深拷贝 deepClone

deepclone.js
function deepClone(value) {
    const hash = new WeakMap();
    
    function cloneHandler(value) {
        if (value === null || typeof value !== 'object') return value;
        if (hash.has(value)) return hash.get(value);
        
        const result = Array.isArray(value) ? [] : {};
        hash.set(value, result);
        for (let key in value) {
            if (value.hasOwnProperty(key)) {
                result[key] = cloneHandler(value[key]);
            }
        }

        return result;
    }

    return cloneHandler(value);
}

const obj = {
    arr: [1, 2, 3],
    a: 4
};
obj.sub = obj;
obj.arr.push(obj);

const clonedObj = deepClone(obj);

console.log(clonedObj);
console.log(clonedObj.arr === obj.arr);
console.log(clonedObj.sub === obj.sub);
console.log(clonedObj.arr[3] === obj);
console.log(clonedObj.arr[3] === clonedObj);

数组铺平 flat

flat.js
function myFlat(arr, depth = 1) {
    if (depth === 0) return [...arr];
    return arr.reduce((prev, cur) => {
        return prev.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur);
    }, []);
}

const mockArr = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
const flattenArr = myFlat(mockArr, 0)
console.log(flattenArr,'\n', mockArr, '\n', flattenArr === mockArr);

调度器 Scheduler

scheduler.js
class Scheduler {
    constructor(maxConcurrent) {
        this.maxConcurrent = maxConcurrent;
        this.runningCount = 0;
        this.queue = [];
    }

    addTask(task) {
        return new Promise((resolve, reject) => {
            this.queue.push({ task, resolve, reject });
            this.#runTask();
        })
    }

    #runTask() {
        if (this.runningCount < this.maxConcurrent && this.queue.length) {
            const { task, resolve, reject } = this.queue.shift();
            this.runningCount++;
            task().then(resolve).catch(reject).finally(() => {
                this.runningCount--;
                this.#runTask();
            });
        }
    }
}


function timer(ms)  {
    return new Promise((resolve) => setTimeout(() => {
        resolve();
    }, ms))
}

const scheduler = new Scheduler(3);
function addTask(ms, name) {
    scheduler.addTask(() => timer(ms).then(() => console.log(name)));
}

addTask(1000, 'task1');
addTask(1000, 'task2');
addTask(1000, 'task3');
addTask(1000, 'task4');

防抖 debounce

debounce.js
function debounce(func, delay) {
    let timer = null;
    return function(...args) {
        const context = this;
        clearTimeout(timer);
        timer = setTimeout(() => func.apply(context, args), delay);
    }
}

const handleResize = debounce(() => {
    console.log('resize');
}, 500);
window.addEventListener('resize', handleResize);

节流 throttle

throttle.js
function throttle1(fn, delay) {
    let timer = null;
    let lastTime = 0;
    
    return function(...args) {
        const context = this;
        if (!lastTime) {
            fn.apply(context, args);
            lastTime = new Date();
        } else {
            clearTimeout(timer);
            const now = new Date();
            timer = setTimeout(() => {
                if (now - lastTime > delay) {
                    fn.apply(context, args);
                    lastTime = now;
                }
            }, delay - (now - lastTime));
        }
    }
}

function throttle2(fn, delay) {
    let lastTime = 0;
    return function(...args) {
        let context = this;
        let now = new Date();
        if (now - lastTime > delay) {
            fn.apply(context, args);
            lastTime = now;
        }
    }
}

let n = 100;
const throttledLog1 = throttle1((x) => { console.log("throttle1:", x) }, 50);
const throttledLog2 = throttle2((x) => { console.log("throttle2:", x) }, 100);
while (n--) {
    throttledLog1(n);
}