手写代码

作者: shaokang 时间: February 23, 2022字数:21737

Object.create

function create(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}

实现 new

function myNew() {
    const obj = {}; // 1
    const constructor = Array.prototype.shift.call(arguments);
    obj.__proto__ = constructor.prototype;
    const result = constructor.apply(obj, arguments);
    return typeof result === 'object' ? result : obj;
}
function myNew() {
    const constructor = Array.prototype.shift.call(arguments);
    const obj = Object.create(constructor.prototype);
    const result = constructor.apply(obj, arguments);
    return typeof result === 'object' ? result : obj;
}

实现柯里化

function myCurry(fn, args) {
    var len = fn.length;
    var args = args || [];
    return function () {
        var _args_ = args.concat(Array.prototype.slice.call(arguments));

        if (_args.length < len) {
            return myCurry.call(this, fn, _args);
        }

        return fn.apply(this, fn, _args);
    };
}

实现 add(1)(2)(3)

function add(...args){
		let func = function (..._args) {
			return add(...args, ..._args);
		}

		func.toString() = function () {
			return args.reduce((a,b)=>a+b)
		}

		return func;
}

防抖和节流

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

function debounce(fn, wait) {
    let timer = null;
    return function () {
        const context = this;
        const params = [...arguments];

        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            fn.apply(this, params);
            timer = null;
        })
    }
}

function debounce(fn, wait) {
    let timer = null;
    return function (...args) {
        const context = this;

        if (timer) {
            clearTimeout(timer);
            timer = null;
        }

        // 设置定时器,使事件间隔指定事件后执行
        timer = setTimeout(() => {
            fn.apply(context, args);
        }, wait);
    };
}

// 定时器版本
function throttle(fun, wait) {
    let timeout = null
    return function (...args) {
        let context = this;

        if (!timeout) {
            timeout = setTimeout(() => {
                fun.apply(context, args)
                timeout = null;
            }, wait)
        }
    };
}

// 时间戳版
function throttle(fn, wait) {
    const oldTime = Date.now();

    return function (...args) {
        const context = this,
        const newTime = Data.now();

        if (newTime - oldTime >= delay) {
            oldTime = Data.now();
            return fn.apply(context, args);
        }
    };
}

为何在此处需要声明一个中间变量 `context` 来确定 `this` 的指向呢? 一般情况下是没有问题的,但在一些复杂的情况下,比如将防抖/节流函数作为回调函数传递,this 指向可能会发生变化

const myObject = {
    value: 0,
    increment() {
        this.value++;
        console.log(this.value);
    },
};

myObject.debouncedIncrement = debounce(myObject.increment, 300);

setTimeout(myObject.debouncedIncrement, 1000); // 这里的 this 不再指向 myObject

apply、call、bind

Function.prototype.myCall = function (context) {
    if (typeof this !== 'function') {
        console.error('type error');
    }
    const args = [...arguments].slice(1);
    context = context || window;
    context.fn = this;
    const result = (result = context.fn(...args));
    delete context.fn;
    return result;
};

// apply 函数实现
Function.prototype.myApply = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error');
    }

    context = context || window;
    context.fn = this;
    let result = null;
    if (arguments[1]) {
        result = context.fn();
    } else {
        result = context.fn(arguments[1]);
    }
    delete context.fn;
    return result;
};

Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error');
    }
    const args = [...arguments].slice(1);
    const self = this;
    return function F() {
        return self.apply(
            this instanceof F ? this : context,
            args.concat(...arguments)
        );
    };
};

Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error');
    }
    // this 指向调用者
    var self = this;
    // 实现第2点,因为第1个参数是指定的this,所以只截取第1个之后的参数
    var args = Array.prototype.slice.call(arguments, 1);

    // 创建一个空对象
    var fNOP = function () {};

    // 实现第3点,返回一个函数
    var fBound = function () {
        // 实现第4点,获取 bind 返回函数的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        // 然后同传入参数合并成一个参数数组,并作为 self.apply() 的第二个参数
        return self.apply(
            this instanceof fNOP ? this : context,
            args.concat(bindArgs)
        );
        // 注释1
    };

    // 注释2
    // 空对象的原型指向绑定函数的原型
    fNOP.prototype = this.prototype;
    // 空对象的实例赋值给 fBound.prototype
    fBound.prototype = new fNOP();
    return fBound;
};

赋值 context.fn,是为了定义 this 绑定(函数作为对象的方法调用时,this 指向调用该方法的对象)。

异步任务调度

function asyncRetry(fn, times, timeout = 0) {
    // fn必需
    // times必需,且为大于0的正整数
    times = parseInt(times);
    if (times <= 0 || isNaN(times)) {
        throw new TypeError('times必需是大于0的正整数');
    }
    // Number.isInteger()

    return Promise.race([
        // 错误后重复
        new Promise(async (resolve, reject) => {
            while (times--) {
                try {
                    const res = await fn();
                    resolve(res);
                    break;
                } catch (error) {
                    if (times <= 0) {
                        reject('retry error');
                    }
                }
            }
        }),
        // 超时任务
        new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('retry error');
            }, timeout);
        }),
    ]);
}

正则

下划线转驼峰

function underscoreToCamelCase(str) {
    return str.replace(/_([a-z])/g, function (match, group) {
        return group.toUpperCase();
    });
}

驼峰转下划线

function camelCaseToUnderscore(str) {
    return str.replace(/([A-Z])/g, function (match, group) {
        return '_' + group.toLowerCase();
    });
}

模板渲染

let str = '我是,年龄,性别';
let person = {
    name: '张三',
    age: 18,
    sex: '',
};
function compile(template, data) {
    return template.replace(//g, (match, key) => {
        return key.trim() in data ? data[key.trim()] : match;
    });
}

let result = compile(str, person);
console.log(result); // 输出: 我是张三,年龄18,性别男

实现 flat 方法

function flat(arr) {
    let result = [];
    for (let i = 0; i < arr.length; i++) {
        const cur = arr[i];

        if (Array.isArray(cur)) {
            result = result.concat(flat(cur));
        } else {
            result.push(cur);
        }
    }
    return result;
}

指定扁平化的层次

function customFlat(arr, depth = 1) {
    let result = [];

    function flatten(arr, currDepth) {
        for (let i = 0; i < arr.length; i++) {
            if (Array.isArray(arr[i]) && currDepth < depth) {
                flatten(arr[i], currDepth + 1); // 递归调用 flatten 方法,并增加当前深度
            } else {
                result.push(arr[i]);
            }
        }
    }

    flatten(arr, 0);

    return result;
}

洋葱模型

async function mid1(ctx, next) {
    console.log(1);
    await next();
    console.log(4);
}

async function mid2(ctx, next) {
    console.log(2);
    await next();
    console.log(5);
}

async function mid3(ctx, next) {
    console.log(3);
    await next();
    console.log(6);
}

function compose(middleware) {
    return function (ctx) {
        async function dispatch(i) {
            if (i === middleware.length) {
                // 所有中间件函数执行完毕
                return;
            }

            try {
                const fn = middleware[i];
                await fn(ctx, () => dispatch(i + 1));
            } catch (err) {
                console.error(err);
            }
        }

        return dispatch(0);
    };
}

const middleware = [mid1, mid2, mid3];
compose(middleware)({});

API 中间件模式

// 异步API执行器
const asyncApiExecutor = async (...args) => {
    console.log('Async API called with args:', ...args);
    return new Promise(resolve => {
        setTimeout(() => {
            console.log('Async API execution completed');
            resolve('API Result');
        }, 1000);
    });
};

// 中间件机制
function applyMiddlewares(fn, middlewares) {
    return function (...args) {
        let index = -1;

        function dispatch(i) {
            if (i <= index) {
                throw new Error('next() called multiple times');
            }
            index = i;
            if (i === middlewares.length) {
                return fn(...args);
            }

            const middleware = middlewares[i];
            if (!middleware) return;

            return middleware(dispatch.bind(null, i + 1))(...args);
        }

        return dispatch(0);
    };
}

// 中间件定义
const loggingMiddleware =
    next =>
    async (...args) => {
        console.log('Logging Middleware before');
        const result = await next(...args);
        console.log('Logging Middleware after');
        return result;
    };

const timingMiddleware =
    next =>
    async (...args) => {
        console.log('Timing Middleware before');
        const start = Date.now();
        const result = await next(...args);
        const end = Date.now();
        console.log('Timing Middleware after');
        console.log(`Execution time: ${end - start}ms`);
        return result;
    };

const errorHandlingMiddleware =
    next =>
    async (...args) => {
        try {
            console.log('Error Handling Middleware before');
            const result = await next(...args);
            console.log('Error Handling Middleware after');
            return result;
        } catch (error) {
            console.error('Error caught in Middleware:', error);
        }
    };

// 使用中间件包装异步API
const enhancedApiExecutor = applyMiddlewares(asyncApiExecutor, [
    errorHandlingMiddleware,
    loggingMiddleware,
    timingMiddleware,
]);

// 调用增强后的API
enhancedApiExecutor('arg1', 'arg2')
    .then(result => {
        console.log('Final result:', result);
    })
    .catch(error => {
        console.error('Unhandled error:', error);
    });

深拷贝

考虑内置对象 Date、RegExp 和循环引用的问题

const isObject = target =>
    (typeof target === 'object' || typeof target === 'function') &&
    target !== null;

function deepClone(target, map = new WeakMap()) {
    if (map.get(target)) {
        return target;
    }
    // 获取当前值的构造函数:获取它的类型
    let constructor = target.constructor;
    // 检测当前对象target是否与正则、日期格式对象匹配
    if (/^(RegExp|Date)$/i.test(constructor.name)) {
        // 创建一个新的特殊对象(正则类/日期类)的实例
        return new constructor(target);
    }
    if (isObject(target)) {
        map.set(target, true); // 为循环引用的对象做标记
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (let prop in target) {
            if (target.hasOwnProperty(prop)) {
                cloneTarget[prop] = deepClone(target[prop], map);
            }
        }
        return cloneTarget;
    } else {
        return target;
    }
}

并发异步调度器

class Scheduler {
    constructor(maxConcurrency) {
        this.maxConcurrency = maxConcurrency;
        this.currentConcurrency = 0;
        this.taskQueue = [];
    }

    addTask(delay, value) {
        const task = () =>
            new Promise(resolve => {
                setTimeout(() => {
                    console.log(value);
                    resolve();
                }, delay);
            });

        if (this.currentConcurrency < this.maxConcurrency) {
            this.runTask(task);
        } else {
            this.taskQueue.push(task);
        }
    }

    runTask(task) {
        this.currentConcurrency++;
        task().then(() => {
            this.currentConcurrency--;
            if (this.taskQueue.length > 0) {
                const nextTask = this.taskQueue.shift();
                this.runTask(nextTask);
            }
        });
    }
}

const scheduler = new Scheduler(2);

scheduler.addTask(1000, '1');
scheduler.addTask(500, '2');
scheduler.addTask(300, '3');
scheduler.addTask(400, '4');

实现一个简易的 Vue

class Vue {
    constructor(options) {
        this.$options = options;
        this.$data = options.data;
            this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
            this._proxyData(this.$data)
            new Observer(this.$data)
            new Compiler(this)
    },
    _proxyData(data) {
        Object.keys(data).forEach((key) => {
            Object.defineProperty(this, key, {
                enumerable: true,
                configurable: true,
                get() {
                    return data[key]
                },
                set(newVal) {
                    if (newVal !== data[key]) {
                        data[key] = newVal
                    }
                }
            })
        })
    }
}


class Dep {
    constructor() {
        this.subs = []
    }

    addSub(sub) {
        this.subs.push(sub);
    }

    notify() {
        this.subs.forEach(sub => sub.update());
    }
}

class Observer {
    constructor(data) {
        this.observe(data);
    }

    observe(data) {
        if (!data || typeof data !== 'object') {
            return;
        }
        Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]));
    }

    defineReactive(obj, key, val) {
        const dep = new Dep();
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                if (Dep.target) {
                    dep.addSub(Dep.target);
                }
                return val;
            },
            set(newVal) {
                if (val !== newVal) {
                    val = newVal;
                    this.observe(newVal);
                    dep.notify();
                }
            }
        });
    }
}

class Watcher {
    constructor(vm, key, callback) {
        this.vm = vm;
        this.key = key;
        this.callback = callback;

        Dep.target = this;
        this.oldValue = this.vm[key];
        Dep.target = null;
    }

    update() {
        const value = this.vm[this.key];
        if (value !== this.oldValue) {
            this.callback(value);
        }
    }
}

class Compiler {
    constructor(vm) {
        this.el = vm.$el;
        this.vm = vm;
        this.compile(this.el);
    }
    compile(el) {
        let childNodes = el.childNodes;
        Array.from(childNodes).forEach((node) => {
            // 处理文本节点
            if (this.isTextNode(node)) {
                this.compileText(node);
            } else if (this.isElementNode(node)) {
                // 处理元素节点
                this.compileElement(node);
            }

            // 判断node节点,是否有子节点,如果有子节点,要递归调用compile
            if (node.childNodes && node.childNodes.length) {
                this.compile(node);
            }
        });
    }
    // 编译元素节点,处理指令
    compileElement(node) {
        // console.log(node.attributes)
        // 遍历所有的属性节点
        Array.from(node.attributes).forEach((attr) => {
            // 判断是否是指令
            let attrName = attr.name;
            if (this.isDirective(attrName)) {
                // v-text --> text
                attrName = attrName.substr(2);
                let key = attr.value;
                this.update(node, key, attrName);
            }
        });
    }

    update(node, key, attrName) {
        let updateFn = this[attrName + "Updater"];
        updateFn && updateFn.call(this, node, this.vm[key], key);
    }

    // 处理 v-text 指令
    textUpdater(node, value, key) {
        node.textContent = value;
        new Watcher(this.vm, key, (newValue) => {
            node.textContent = newValue;
        });
    }
    // v-model
    modelUpdater(node, value, key) {
        node.value = value;
        new Watcher(this.vm, key, (newValue) => {
            node.value = newValue;
        });
        // 双向绑定
        node.addEventListener("input", () => {
            this.vm[key] = node.value;
        });
    }

    // 编译文本节点,处理差值表达式
    compileText(node) {
        // console.dir(node)
        // 
        let reg = /\{\{(.+?)\}\}/;
        let value = node.textContent;
        if (reg.test(value)) {
            let key = RegExp.$1.trim();
            node.textContent = value.replace(reg, this.vm[key]);

            // 创建watcher对象,当数据改变更新视图
            new Watcher(this.vm, key, (newValue) => {
                node.textContent = newValue;
            });
        }
    }
    // 判断元素属性是否是指令
    isDirective(attrName) {
        return attrName.startsWith("v-");
    }
    // 判断节点是否是文本节点
    isTextNode(node) {
        return node.nodeType === 3;
    }
    isElementNode(node) {
        return node.nodeType === 1;
    }
}

实现 lodash 的 get 方法

function get(object, path, defaultValue = 'undefined') {
    const paths = Array.isArray(path)
        ? path
        : path.replace(/\[/g, '.').replace(/\]/, '').split('.');

    return (
        paths.reduce((obj, key) => {
            console.log(obj, key);
            return (obj || {})[key];
        }, object) || defaultValue
    );
}

限制异步操作的并发个数,并尽可能快的完成全部

有 8 个图片资源的 url,已经存储在数组 urls 中。

urls 类似于[‘https://image1.png’, ‘https://image2.png’, ….]

而且已经有一个函数 function loadImg,输入一个 url 链接,返回一个 Promise,该 Promise 在图片下载完成的时候 resolve,下载失败则 reject。

但有一个要求,任何时刻同时下载的链接数量不可以超过 3 个。

请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。

var urls = [
    'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png',
    'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png',
    'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png',
    'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png',
    'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png',
    'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png',
    'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png',
    'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png',
];
function loadImg(url) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = function () {
            console.log('一张图片加载完成');
            resolve(img);
        };
        img.onerror = function () {
            reject(new Error('Could not load image at' + url));
        };
        img.src = url;
    });
}
function limitLoad(urls, handler, limit) {
    let sequence = [].concat(urls); // 复制urls
    // 这一步是为了初始化 promises 这个"容器"
    let promises = sequence.splice(0, limit).map((url, index) => {
        return handler(url).then(() => {
            // 返回下标是为了知道数组中是哪一项最先完成
            return index;
        });
    });
    // 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
    return sequence
        .reduce((pCollect, url) => {
            return pCollect
                .then(() => {
                    return Promise.race(promises); // 返回已经完成的下标
                })
                .then(fastestIndex => {
                    // 获取到已经完成的下标
                    // 将"容器"内已经完成的那一项替换
                    promises[fastestIndex] = handler(url).then(() => {
                        return fastestIndex; // 要继续将这个下标返回,以便下一次变量
                    });
                })
                .catch(err => {
                    console.error(err);
                });
        }, Promise.resolve()) // 初始化传入
        .then(() => {
            // 最后三个用.all来调用
            return Promise.all(promises);
        });
}
limitLoad(urls, loadImg, 3)
    .then(res => {
        console.log('图片全部加载完毕');
        console.log(res);
    })
    .catch(err => {
        console.error(err);
    });

实现一个串行队列

实现一个串行请求队列 serial 函数,接收包含异步请求的数组,按顺序依次执行。

function serial(arr) {
    return arr.reduce((pre, cur) => pre.then(() => cur()), Promise.resolve());
}

// forEach 版本
function serial(arr) {
    var sequence = Promise.resolve();
    arr.forEach(function (item) {
        sequence = sequence.then(item);
    });
    return sequence;
}

实现 mergePromise 函数

实现 mergePromise 函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组 data 中。

const time = timer => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, timer);
    });
};
const ajax1 = () =>
    time(2000).then(() => {
        console.log(1);
        return 1;
    });
const ajax2 = () =>
    time(1000).then(() => {
        console.log(2);
        return 2;
    });
const ajax3 = () =>
    time(1000).then(() => {
        console.log(3);
        return 3;
    });

function mergePromise() {
    // 在这里写代码
}

mergePromise([ajax1, ajax2, ajax3]).then(data => {
    console.log('done');
    console.log(data); // data 为 [1, 2, 3]
});

// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]
function mergePromise(arr) {
    return arr.reduce((pre, cur) => {
        return pre.then(data => {
            return cur().then(res => [...data, res]);
        });
    }, Promise.resolve([]));
}

将数字转换成汉语

function trans(number) {
    const integral = number + '';
    const digits = ['', '', '', '', '', '', '', '', '', ''];
    const radices = ['', '', '', ''];
    const bigRadices = ['', '', '亿'];

    let result = '';
    let zeroCount = 0;
    for (let i = 0; i < integral.length; i++) {
        const digit = +integral[i];
        // 尾数个数
        const p = integral.length - 1 - i;
        // 有几个 4 位
        const quotient = Math.floor(p / 4);
        // 单位
        const module = p % 4;

        if (digit === 0) {
            zeroCount++;
        } else {
            if (zeroCount > 0) {
                result += digits[0];
                zeroCount = 0;
            }

            // 10 要简写为十
            if (digit === 1 && module === 1) {
                result += radices[module];
            } else {
                result += digits[digit] + radices[module];
            }
        }

        // 刚好是 4 位的时候加上 万 或者 亿
        if (module === 0 && zeroCount < 4) {
            result += bigRadices[quotient];
        }
    }
    return result;
}

实现 reduce

Array.prototype.myReduce = function (fn, initialValue) {
    let result = initialValue ? initialValue : this[0];
    for (let i = initialValue ? 1 : 0; i < this.length; i++) {
        result = fn(result, this[i], i, this);
    }

    return result;
}