常见JavaScript手写题,工具方法原理(持续更新。。。)

333 阅读6分钟

js原生工具方法原理实现

call原理实现

Function.prototype.call = function (ctx, ...params) {
    ctx === null ? ctx = window : null;
    // 如果上下文不是对象将上下文转化为对象
    !/^(object|function)$/.test(typeof ctx) ? ctx = Object(ctx) : null;
    const key = Symbol("key");
    ctx[key] = this;
    const result = ctx[key](...params);
    delete ctx[key];
    return result;
};

apply原理实现

Function.prototype.apply = function (ctx, params) {
    ctx === null ? ctx = window : null;
    if (!Array.isArray(params)) {
        throw new TypeError("CreateListFromArrayLike called on non-object");
    }
    // 如果上下文不是对象将上下文转化为对象
    !/^(object|function)$/.test(typeof ctx) ? ctx = Object(ctx) : null;
    const key = Symbol("key");
    ctx[key] = this;
    const result = ctx[key](...params);
    delete ctx[key];
    return result;
};

bind原理实现(真正的bind返回的函数没有原型链)

Function.prototype.Bind = function (target) {
    // 放在myBind在Function的原型上,将目标函数指向对象传入
    let self = this; // this表示调用的函数
    let Temp = function () {};
    let args = [].slice.call(arguments, 1); // 将arguments伪数组转化为真数组
    function func() { // 返回新的一个函数,this指向新的对象
        let _args = [].slice.call(arguments, 0); // 将arguments转化为真数组
        self.apply(this instanceof self ? this : target || window, args.concat(_args));
    }
    Temp.prototype = self.prototype;
    func.prototype = new Temp();
    return func;
};

es6的Map集合

class Map {
    constructor(iterable = []) {
        if (typeof iterable[Symbol.iterator] !== "function") {
            throw new Error(iterable + "传入不是可迭代对象");
        }
        this._Entries = [];
        for (const item of iterable) {
            if (typeof item[Symbol.iterator] !== "function") {
                throw new Error(item + "不是可迭代对象");
            }
            const iterator = item[Symbol.iterator]();
            const key = iterator.next().value;
            const value = iterator.next().value;
            this.set(key, value);
        }
    }

    set(key, value) {
        if (this.has(key)) {
            this._getObject(key).value = value;
        } else {
            this._Entries.push({
                key,
                value
            });
        }

    }

    get(key) {
        const item = this._getObject(key);
        if (item) {
            return item.value;
        } else {
            return false;
        }
    }

    get size() {
        return this._Entries.length;
    }

    _getObject(key) {
        for (const item of this._Entries) {
            if (this.isEqual(item.key, key)) return item;
        }
    }

    has(key) {
        return this._getObject(key) !== undefined;
    }

    delete(key) {
        for (let i = 0; i < this._Entries.length; i++) {
            if (this.isEqual(this._Entries[i].key, key)) {
                this._Entries.splice(i, 1);
                return true;
            }
        }
        return false;
    }

    clear() {
        this._Entries.length = 0;
    }

    isEqual(value1, value2) {
        if (value1 === 0 && value2 === 0) {
            return true;
        }
        return Object.is(value1, value2);
    }

    *[Symbol.iterator]() {
        for (const el of this._Entries) {
            yield [el.key, el.value];
        }
    }

    forEach(callback) {
        for (const el of this._Entries) {
            callback(el.value, el.key, el);
        }
    }
}

es6的Set集合

class Set {
    constructor(iterator = []) {
        if (typeof iterator[Symbol.iterator] !== "function") {
            throw new TypeError("传入的不是可迭代对象!");
        }
        this._datas = [];
        for (const data of iterator) {

            this.add(data);
        }
    }
    add(data) {
        if (!this.has(data)) {
            this._datas.push(data);
        }
    }
    has(data) {
        for (const item of this._datas) {
            if (this.isEqual(data, item)) {
                return true;
            }
        }
        return false;
    }
    delete(data) {
            for (let i = 0; i < this._datas.length; i++) {
                if (this.isEqual(data, this._datas[i])) {
                    this._datas.splice(i, 1);
                    return true;
                }
                return false;
            }
        }
        *[Symbol.iterator]() {
            for (const item of this._datas) {
                yield item;
            }
        }
    forEach(func) {
        for (const item of this._datas) {
            func(item, item, this);
        }
    }

    clear() {
        this._datas.length = 0;
    }
    isEqual(value1, value2) {
        if (value1 === 0 && value2 === 0) {
            return true;
        }
        return Object.is(value1, value2);
    }
    get size() {
        return this._datas.length;
    }
}

数组的高阶函数遍历方法原理实现

Array.prototype.myForEach = function (func, _this) {
    const arr = this;//得到调用数组本身(谁调用的这个方法,this就指向谁)
    const len = arr.length;//得到数组长度
    for (let i = 0; i < len; i++) {
        func.apply(_this, [arr[i], i, arr])
        // 执行传入的函数,_this为传入的对象,如果没有传入的_this指向,则为undefined,指向window
        // 分别将数组每一项值,数组索引,数组本身作为参数传入,执行函数
    }
}
Array.prototype.myFilter = function (func, _this) {
    const arr = this
    const len = arr.length
    const newArr = []
    for (let i = 0; i < len; i++) {
        func.apply(_this, [arr[i], i, arr]) && newArr.push(arr[i])
    }
    return newArr
}
Array.prototype.myReduce = function (func, initValue) {
    var _arr = this, len = this.length
    for (var i = 0; i < len; i++) {
        if (!initValue) {       //如果没有initValue
            initValue = _arr[0] //将数组第一个值赋给initValue
            i++                 //索引从第二个开始
        }
        initValue = func.apply(initValue, [initValue, _arr[i], i, _arr])
    }
    return initValue;
}
Array.prototype.myMap = function (func, _this) {
    const oldArr = this;
    const newArr = [];
    for (let i = 0; i < oldArr.length; i++) {
        // 处理稀松数组
        if (!Object.hasOwnProperty.call(oldArr, i)) continue;
        newArr.push(func.apply(_this, [oldArr[i], i, oldArr]));
    }
    return newArr;
};

Object.is()方法实现

Object.is = function (x, y) {
    if (x === y) {
        // 处理+0 !== -0
        // 如果x不是0,+0,-0直接返回true
        // 如果x是0,则两个都是0,根据+Infinite===-Infinite => false进行处理
        return x !== 0 || 1 / x === 1 / y;
    }
    // 处理NaN
    return x !== x && y !== y;
};
console.log(Object.is(NaN, NaN));

instanceOf原理实现

function instanceOf(obj, func) {
    while (Object.getPrototypeOf(obj)) {
        if (obj === func.prototype) return true;
        obj = Object.getPrototypeOf(obj);
    }
    return false;
}
console.log(instanceOf(t, Test));

new的实现

function myNew(func, ...args) {
    const obj = Object.create(func.prototype);
    const rt = func.apply(obj, args);
    return typeof rt === "object" ? rt : obj;
}

常见工具函数手写

jsonp前端简易实现

function jsonp({
    url,
    params,
    success
}) {
    let paramsStr = url.split("").indexOf("?") !== -1 ? "&" : "?";
    for (const key in params) {
        if (Object.hasOwnProperty.call(params, key)) {
            paramsStr += key + "=" + params[key] + "&";
        }
    }
    const callback = "callback" + Math.random().toString(36).replace(".", "");
    const path = url + paramsStr + "callback=" + callback;
    const script = document.createElement("script");
    script.src = path;
    window[callback] = success;
    document.body.appendChild(script);
    document.body.removeChild(script);
}
jsonp({
    url: "https://developer.duyiedu.com/edu/testJsonp",
    params: {
        name: "zf",
        like: "web"
    },
    success(data) {
        console.log(data);
    }
});

珂里化

function curry(func, ...args) {
    const arity = func.length;
    if (args.length >= arity) {
        return func.apply(this, args);
    } else {
        // 参数不够,返回一个函数,将这个新函数的参数和之前的参数进行组合执行
        return (...args1) => {
            const total = [...args, ...args1];
            return curry(func, ...total);
        };
    }
}
// 初始化不传参
function curry1(func) {
    let arity = [];
    return function next(...args) {
        arity = [...arity, ...args];
        if (arity.length >= func.length) {
            func.apply(func, arity);
        } else {
            return next;
        }
    };
}

深克隆

function deepCopy(obj) {
    if (typeof obj === "object" && obj !== null) {
        if (Object.prototype.toString.call(obj) === "[object Date]") {
            return new Date(obj);
        }
        if (Object.prototype.toString.call(obj) === "[object RegExp]") {
            return new RegExp(obj);
        }
        const retObj = Array.isArray(obj) ? [] : {};
        for (const key in obj) {
            if (Object.hasOwnProperty.call(obj, key)) {
                retObj[key] = deepCopy(obj[key]);
            }
        }
        return retObj;
    } else {
        return obj;
    }
}

判断是否是简单对象(redux)

function isPlainObject(obj) {
    if (typeof obj !== "object" || obj === null) return false;
    let proto = obj;
    while (Object.getPrototypeOf(proto)) {
        proto = Object.getPrototypeOf(proto);
    }
    return proto === Object.getPrototypeOf(obj);
}

jquery数据类型检测封装

const getProto = Object.getPrototypeOf;
const class2type = {};
const toString = class2type.toString;
const hasOwn = class2type.hasOwnProperty;
const fnToString = hasOwn.toString;
const ObjectFunctionString = fnToString.call(Object);
[
    "Boolean",
    "Number",
    "String",
    "Symbol",
    "Function",
    "Array",
    "Date",
    "RegExp",
    "Object",
    "Error",
    "GeneratorFunction"
].forEach(name => {
    class2type[`[object ${name}]`] = name.toLocaleLowerCase();
});

function toType(type) {
    // eslint-disable-next-line eqeqeq
    if (type == null) {
        return type + "";
    }
    return typeof type === "function" || typeof type === "object" ? class2type[toString.call(type)] || "object" : typeof type;
}

function isFunction(obj) {
    // 有一些浏览器dom类型用typeof检测是function
    return typeof obj === "function" && typeof obj.nodeType !== "number";
}
// 检测是否为window对象
function isWindow(obj) {
    return obj && obj === obj.window;
}
// 检测目标对象是否为数组或者类数组
function isArrayLike(obj) {
    const length = obj && "length" in obj && obj.length;
    const type = toType(obj);
    if (isFunction(obj) || isWindow(obj)) {
        return false;
    }
    return type === "array" ||
        length === 0 ||
        typeof length === "number" && length > 0 && (length - 1) in obj;
}

function isPlainObject(obj) {
    const proto = getProto(obj);
    if (!obj || toType(obj) !== "object") return false;
    // 没有原型链,可能是通过Object.create(null)创建的对象
    if (!proto) return true;
    const constructor = hasOwn.call(proto, "constructor") && proto.constructor;
    return typeof constructor === "function" && fnToString.call(constructor) === ObjectFunctionString;
}

function isEmptyObject(obj) {
    var keys = [
        ...Object.getOwnPropertyNames(obj),
        ...Object.getOwnPropertySymbols(obj)
    ];
    return keys.length === 0;
}

merge函数(合并对象)

// 引用上面实现的isPlainObject函数判断是否为简单对象
import isPlainObject from "./isPlainObject";

function merge(obj1, obj2) {
    let isPlainObj1 = isPlainObject(obj1);
    let isPlainObj2 = isPlainObject(obj2);
    if (!isPlainObj1) return obj2;
    if (!isPlainObj2) return obj1;
    // 都是简单对象
    for (const key in obj2) {
        if (Object.hasOwnProperty.call(obj2, key)) {
            obj1[key] = merge(obj1[key], obj2[key]);
        }
    }
    return obj1;
}

管道函数

function pipe(...func) {
    return function (value) {
        for (let i = 0; i < func.length; i++) {
            value = func[i](value);
        }
        return value;
    };
}

compose函数

  1. redux
function compose(...middleware) {
    if (middleware.length === 0) return (args) => args;
    if (middleware.length === 1) return middleware[0];
    return middleware.reduce((pre, cur) => {
        return (...arg) => {
            return pre(cur(...arg));
        };
    });
}
  1. koa洋葱模型
function compose(middleware) {
    return function () {
        return dispatch(0);

        function dispatch(i) {
            // 取出将要执行的函数
            let fn = middleware[i];
            // 如果fn为空 直接返回已决状态
            if (!fn) return Promise.resolve();
            // 将下一个函数作为参数next传给当前函数
            return Promise.resolve(fn(function next() {
                return dispatch(i + 1);
            }));
        }
    };
}

防抖函数

function debounce(func, wait = 300, immediate = false) {
    let timer = null;
    return (...params) => {
        const nowExec = immediate && !timer;
        clearTimeout(timer);
        timer = setTimeout(() => {
            timer = null;
            !immediate && func.call(this, ...params);
        }, wait);
        nowExec && func.call(this, ...params);
    };
}

节流函数

function throttle(func, wait = 300) {
    let preDate = 0;
    let timer = null;
    return (...params) => {
        const nowDate = Date.now();
        const remaining = wait - (nowDate - preDate);
        if (remaining <= 0) {
            // 时间到了,触发函数
            preDate = nowDate;
            clearTimeout(timer);
            timer = null;
            func.call(this, ...params);
        } else if (timer === null) {
            timer = setTimeout(() => {
                preDate = Date.now();
                func.call(this, ...params);
                timer = null;
            }, remaining);
        }
    };
}

生成器模拟async await

function func(x) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(x + 1);
        }, 1000);
    });
}

function* generator(x) {
    const r1 = yield func(x);
    console.log(r1);
    const r2 = yield func(r1);
    console.log(r2);
    const r3 = yield func(r2);
    console.log(r3);
}
const gen = generator();

function asyncGenerator(generator, ...params) {
    const gen = generator(...params);
    function next(args) {
        const {
            value,
            done
        } = gen.next(args);
        if (done) return;
        value.then(x => next(x));
    }
    next();
}
asyncGenerator(generator, 0);

给对象添加迭代器

Object.prototype[Symbol.iterator] = function () {
    let self = this;
    let index = 0;
    const keys = [...Object.getOwnPropertyNames(self), ...Object.getOwnPropertySymbols(self)];
    return {
        next() {
            return index < keys.length ? {
                done: false,
                value: self[keys[index++]]
            } : {
                done: true,
                value: undefined
            };
        }
    };
};

Promsie.all原理实现

  Promise.all = function (promiseArr) {
        return new Promise(function (resolve, reject) {
            var index = 0,
                ret = [];
            // 循环遍历promise实例数组
            for (var i = 0; i < promiseArr.length; i++) {
                // 创建闭包,保存i
                (function (i) {
                    // 取出每一个promise实例
                    var proItem = promiseArr[i];
                    // 如果元素不是promise对象,直接将对应数组位置赋值为当前值,index记录加一
                    if (!(proItem instanceof Promise)) {
                        index++;
                        ret[i] = proItem;
                        if (index === promiseArr.length) {
                            // 将最终的结果数组resolve
                            resolve(ret);
                        }
                        return;
                    }
                    // 当前proItem是promise实例,调用then方法获取resolve/reject处理结果
                    proItem.then(function (result) {
                        index++;
                        // 将获取的结果赋值给数组对应位置
                        ret[i] = result;
                        // 如果index记录等于promise实例数组长度,说明所有promise实例已经处理完毕
                        if (index === promiseArr.length) {
                            // 将最终的结果数组resolve
                            resolve(ret);
                        }
                    }).catch(function (err) {
                        // 只要有一个失败就整体失败
                        reject(err);
                    });
                }(i));
            }
        });
    };

Promise.race原理实现

  Promise.race = function (promiseArr) {
        return new Promise((resolve, reject) => {
            for (var i = 0; i < promiseArr.length; i++) {
                (function (i) {
                    var proItem = promiseArr[i];
                    if (!(proItem instanceof Promise)) {
                        resolve(proItem);
                    } else {
                        // 是promise实例
                        proItem.then(function (result) {
                            resolve(result);
                        }, function (err) {
                            reject(err);
                        });
                    }
                }(i));
            }
        });
    };

promise并发

const delay = function (interval) {
    return new Promise(function (resolve, reject) {
        if (interval === 1000) {
            reject(new Error(interval));
        }
        setTimeout(function () {
            resolve(interval);
        }, interval);
    });
};

const tasks = [
    () => delay(1002),
    () => delay(1004),
    () => delay(100),
    () => delay(1302),
    () => delay(1092),
    () => delay(1024)
];
// 创建请求池 tasks为任务列表,每个元素是一个函数,函数执行返回promise实例,pool是并发数量
function createRequestPool(tasks, pool = 5) {
    // 创建并发请求数组
    let togetherPool = new Array(pool).fill(0);
    // 记录索引,保持result位置和任务位置对应
    let index = 0;
    // 结果数组
    let result = [];
    // 返回pool个promise并发执行
    togetherPool = togetherPool.map(() => {
        return new Promise(function (resolve, reject) {
            const run = function () {
                // 如果没有请求了,执行resolve
                if (index >= tasks.length) {
                    resolve();
                    return;
                }
                // 保留索引,用于result结果保持相对位置
                let old_index = index;
                // 获取当前索引的task函数
                const task = tasks[index++];
                task().then(function (res) {
                    // 将任务结果放入result并运行下一个任务
                    result[old_index] = res;
                    run();
                }).catch(function (err) {
                    // 发生异常,直接reject
                    reject(err);
                });
            };
            // 直接执行run函数
            run();
        });
    });
    // 得到结果返回
    return Promise.all(togetherPool).then(() => result);
}
// 测试用例
let res = createRequestPool(tasks, 2).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
});

编程题

列表和树形结构的互相转化

const list = [{
    id: 1,
    text: "节点一",
    parentId: 0
}, {
    id: 2,
    text: "节点二",
    parentId: 1
}];
const tree = [{
    id: 1,
    text: "节点一",
    parentId: 0,
    children: [{
        id: 2,
        text: "节点二",
        parentId: 1
    }]
}];

function listToTree(list) {
    const temp = {};
    const ret = [];
    for (let i = 0; i < list.length; i++) {
        temp[list[i].id] = list[i];
    }
    for (const i in temp) {
        // 不是根节点
        if (temp[i].parentId !== 0) {
            // 将当前节点添加到父节点的children数组中
            if (!temp[temp[i].parentId].children) {
                temp[temp[i].parentId].children = [];
            }
            temp[temp[i].parentId].children.push(temp[i]);
        } else {
            // 是根节点
            ret.push(temp[i]);
        }
    }
    return ret;
}

function treeToList(tree) {
    let res = [];
    const dfs = (tree) => {
        tree.forEach(item => {
            if (item.children) {
                dfs(item.children);
                delete item.children;
            }
            res.push(item);
        });
    };
    dfs(tree);
    return res;
}
console.log(JSON.stringify(listToTree(list)));
console.log(treeToList(tree));

大数相加

function add(a, b) {
    const maxLen = Math.max(a.length, b.length);
    a = a.padStart(maxLen, 0);
    b = b.padStart(maxLen, 0);
    let sum = "";
    let carry = 0;
    for (let i = maxLen - 1; i >= 0; i--) {
        let curSum = parseInt(a[i]) + parseInt(b[i]) + carry;
        carry = Math.floor(curSum / 10);
        sum = curSum % 10 + sum;
    }
    if (carry === 1) {
        sum = carry + sum;
    }
    return sum;
}
console.log(add("34323532432423410", "9432545234324324320"));

斐波那契数列

1 1 2 3 5 8 13 21

  1. 递归
function fibonacci(n) {
    if (n < 1) throw ("不能小于1");
    if (n === 1 || n === 2) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}
  1. 循环
function fibonacci1(n) {
    if (n < 0) throw new Error("最小为0!");
    if (n === 0) return 0;
    if (n <= 2) return 1;
    let first = 1;
    let second = 1;
    let i = 3;
    while (i++ <= n) {
        // 保存第一个标量
        let temp = first;
        first = second;
        second += temp;
    }
    return second;
}

连加操作

function add(...args) {
    let allArgs = args;

    function fn(...newArgs) {
        allArgs = [...allArgs, ...newArgs];
        return fn;
    }
    fn.toString = function () {
        if (!allArgs.length) return;
        return allArgs.reduce((pre, cur) => pre + cur);
    };
    return fn;
}
console.log((add(3)(5)(8)).toString());

实现动态定时器

// 第 1 题:写一个 mySetInterVal(fn, a, b),每次间隔 a,a+b,a+2b,...,a+nb 的时间,然后写一个 myClear,停止上面的 mySetInterVal
function MySetInterVal(fn, a, b) {
    this.a = a;
    this.b = b;
    this.n = 0;
    this.timer = null;
    this.start = () => {
        this.timer = setTimeout(() => {
            fn();
            this.n++;
            this.start();
        }, this.a + this.n * this.b);
    };
    this.myClear = () => {
        clearTimeout(this.timer);
        this.n = 0;
        this.timer = null;
    };
}
const instance = new MySetInterVal(() => {
    console.log("你好");
}, 1000, 2000);
instance.start();

多维数组扁平化处理

function flatten(nums) {
    let result = [];
    for (let i = 0; i < nums.length; i++) {
        if (Array.isArray(nums[i])) {
            result = result.concat(flatten(nums[i]));
        } else {
            result = result.concat(nums[i]);
        }
    }
    return result;
}