面向大厂编程,100道你必须掌握的js手写题(二)

639 阅读5分钟

面向大厂编程,100道你必须掌握的js手写题(二)

11、检测JavaScript中的数据类型

编写一个函数来检测任意数据的类型,除了基本的类型,还需要处理常用的复合数据类型包括 ArrayArrayBufferMapSetDateFunction

需要注意的是,返回的类型应该小写

实现:

/**
 * @param {any} data
 * @return {string}
 */
function detectType(data) {
    if(typeof data == 'object'){
        if(data == null) return 'null'
        if(data instanceof FileReader) return 'object'
        return data.constructor.name.toLowerCase();
    }
    return typeof data
}

也可以用Object.prototype.toString.call()获取对象具体的数据类型

12、实现 JSON.stringify()

JSON.stringify()方法可以将JavaScript对象或值转换成JSON字符串,如果指定了替换器函数,则可以选择替换值,如果指定了替换器数组,则可以选择包含指定的属性

JSON.stringify(value)中,如果value中有toJSON方法,那么它负责定义将序列化的数据

实现一个最简单的JSON.stringify:

function stringify(data) {
    if ([NaN, null, undefined, Infinity].includes(data)) {
        return 'null'
    }
    const type = typeof data
    switch (type) {
        case 'function': return undefined;
        case 'bigint': throw Error('bigints are not supported');
        case 'string': return `"${data}"`;
        case 'object': {
            if (Array.isArray(data)) {
                return `[${data.map(item => {
                    return (typeof item) == 'symbol' ? 'null' : stringify(item)
                }).join()}]`
            }
            if (data instanceof Date) {
                return `"${data.toISOString()}"`
            }
            return '{' + Object.keys(data).filter(key => data[key] !== undefined).map(key => `"${key}":${stringify(data[key])}`).join() + '}'
        }
        default: return String(data);
    }
} 

13、实现JSON.parse

实现:

/**
 * @param {string} str
 * @return {object | Array | string | number | boolean | null}
 */
function parse(str) {
    if (str === '') {
        throw Error();
    }
    if (str[0] === "'") {
        throw Error();
    }
    if (str === 'null') {
        return null;
    }
    if (str === '{}') {
        return {};
    }
    if (str === '[]') {
        return [];
    }
    if (str === 'true') {
        return true;
    }
    if (str === 'false') {
        return false;
    }
    if (str[0] === '"') {
        return str.slice(1, -1);
    }
    if (+str === +str) {
        return Number(str);
    }
    if (str[0] === '{') {
        return str.slice(1, -1).split(',').reduce((acc, item) => {
            const index = item.indexOf(':');
            const key = item.slice(0, index)
            const value = item.slice(index + 1);
            acc[parse(key)] = parse(value);
            return acc;
        }, {});
    }
    if (str[0] === '[') {
        return str.slice(1, -1).split(',').map((value) => parse(value));
    }
}

14、通过Stack实现一个Queue

在JavaScript中,我们可以使用数组作为堆栈或队列

作为堆栈:

cosnt arr = [1, 2, 3, 4]
arr.push(5)
arr.pop()

作为队列:

const arr = [1, 2, 3, 4]
arr.push(5)
arr.shift()

现在假设有一个Stack,它只有以下接口:

class Stack {
    push(element) { /* add element to stack */ }
    peek() { /* get the top element */ }
    pop() { /* remove the top element */ }
    size() { /* count of elements */ }
}

要求只使用上面的Stack来实现队列,必须具备以下接口

class Queue {
    enqueue(element) { /* add element to queue */ }
    peek() { /* get the head element */ }
    dequeue() { /* remove the head head */ }
    size() { /* count of elements */ }
}

实现:

class Queue {
    constructor() {
        this.stack = new Stack()
    }
    enqueue(element) {
        return this.stack.push(element)
    }
    peek() {
        // get the head element
        return this._reserveStack(this.stack).peek()
    }
    size() {
        // return count of element
        return this.stack.size()
    }
    dequeue() {
        // remove the head element
        this.stack = this._reserveStack(this.stack)
        return this.stack.pop()
    }
    _reserveStack(stack) {
        const tempStack = new Stack();

        while (this.stack.size() > 0) {
            tempStack.push(this.stack.pop())
        }

        return tempStack
    }
}

15、为DOM element创建一个简单的store

ES6中的Map的键值可以是任何数据,比如是DOM element

const map = new Map()
map.set(domNode, data)

要求实现一个Node Store,在ES5等环境下支持DOM element作为key

class NodeStore {
    set(node, value){}
    get(node) {}
    has(node) {}
}

实现:

class NodeStore {
    constructor() {
        this.map = {}
        this.nodeKey = Symbol('nodeKey')
        this.counter = 0
    }
    /**
    * @param {Node} node
    * @param {any} value
    */
    set(node, value) {
        if(!node.dataset[this.nodeKey]) {
            node.dataset[this.nodeKey] = ++this.counter
            this.map[this.counter] = value
        }else {
            this.map[node.dataset[this.nodeKey]] = value
        }
    }
    /**
     * @param {Node} node
     * @return {any}
     */
    get(node) {
        return this.map[node.dataset[this.nodeKey]]
    }

    /**
     * @param {Node} node
     * @return {Boolean}
     */
    has(node) {
        return this.map.hasOwnProperty(node.dataset[this.nodeKey])
    }
}

16、实现Object.assign()

Object.assign方法将所有可枚举的自身属性从一个或多个源对象复制到目标对象,并返回目标对象。

Object Spread运算符实际上在内部与Object.assign()相同
let result = { ...sourceObj }
// 等同于
let result = Object.assin({}, sourceObj)

实现:

/**
 * @param {any} target
 * @param {any[]} sources
 * @return {object}
 */
function objectAssign(target, ...sources) {
    if (target == null || target == undefined) throw new Error('invalid target')
    let result = target
    // 支持以number、string、boolean类型的值为target
    if (['number', 'string', 'boolean'].includes(typeof target)) {
        result = Object(target)
    }
    // 判断对象是否可以写入
    function isOwnPropertyWritable(obj, prop) {
        const des = Object.getOwnPropertyDescriptor(obj, prop);
        return des == null || des.writable || !!des.set;
    }

    sources.map(item => {
        if ([NaN, null, undefined, Infinity].includes(item)) { return }
        // 支持以Symbol为键值
        let keyArr = [...Object.keys(item), ...Object.getOwnPropertySymbols(item)]
        keyArr.map(key => {
            if (isOwnPropertyWritable(target, key)) {
                target[key] = item[key]
            } else {
                throw new Error('cannot assign to read-only properties')
            }
        })
    })

    return result
}

也可以通过Reflect.set给对象设置属性,判断是否更新成功

17、实现 _.once 方法

_once(func)用于强制一个函数只调用一次,后面的调用只返回第一次调用的结果。

要求只能执行一次func

举例:

function func(num){
    return num
}

const onced = once(func)
onced(1) // 1
onced(2) // 1
function once(func) {
    let result
    let called = false
    return function (...args) {
        if (!called) {
            result = func.apply(this, args)
            called = true
        }
        return result
    }
}

18、实现一个async helper方法 sequence

要求实现一个异步函数helper,像是pipe方法那样可以将异步函数连接起来

举例:

假设所有异步函数都有以下interface

type Callback = (error: Error, data: any) => void
type AsyncFunc = (
    callback: Callback,
    data: any
) => void

sequence()可以接受AsyncFunc数组,并通过回调中的数据作为新数据传给下一个AsyncFunc

const asyncTimes = (callback, num) => {
    setTiout(() => callback(null, num * 2), 100)
}

const asyncTimesArr = sequence(
    [
        asyncTimes,
        asyncTimes
    ]
)
asyncTimesArr((err, data) =>{
    console.log(data) // 4
}, 1)

使用promise实现:

/**
 * @param {Function} func
 * @return {Function}
 */
function sequence(funcs) {
    const promiseFuncs = funcs.map(promisify)
    return function (callback, input) {

        let promise = Promise.resolve(input)

        promiseFuncs.forEach((promiseFunc) => {
            promise = promise.then(promiseFunc)
        });

        promise.then((data) => {
            callback(undefined, data)
        }).catch(callback)
    }
}

function promisify(callback) {
    return function (input) {
        return new Promise((resolve, reject) => {
            callback((err, data) => {
                if (err) {
                    reject(err)
                    return
                }
                resolve(data)
            }, input)
        })
    }
}

不使用promise实现:

/**
 * @param {Function} func
 * @return {Function}
 */
function sequence(funcs) {
    const funcsQueue = [...funcs];

    return function run(finalCB, data) {
        if (funcsQueue.length === 0) {
            finalCB(undefined, data)
            return
        }
        const currentFunc = funcsQueue.shift()

        const cb = (error, num) => {
            if(error) {
                finalCB(error, num)
                return 
            }
            run(finalCB, num)
        }
        
        currentFunc(cb, data)
    }
}

19、实现一个async helper方法 parallel

parallel()类似Promise.all(),和前面的sequence()不同,异步函数的执行没有先后顺序,在parallel()中是同时触发。

注意:paralle需要返回一个function,这个function将在所有的的异步函数完成或者error发生的时候被触发。

只有第一个error需要被传递到callback,其他的error和data都被忽略

举例:

const async1 = (callback) => {
    setTimeout(() => callback(undefined, 1))
}
const async2 = (callback) => {
    setTimeout(() => callback(undefined, 2))
}
const async3 = (callback) => {
    setTimeout(() => callback(undefined, 3))
}

const all = parallel(
    [
        async1, 
        async2,
        async3
    ]
)
all((error, data) => {
    console.log(data) // [1, 2, 3]
})

使用promise实现:

function parallel(funcs) {
    const promiseFuncs = funcs.map(promisify)
    return function (callback, input) {
        Promise
            .all(promiseFuncs.map(func => func(input)))
            .then(res => callback(undefined, res))
            .catch(err => callback(err, undefined))
    }
}

function promisify(callback) {
    return function (input) {
        return new Promise((resolve, reject) => {
            callback((err, data) => {
                if (err) {
                    reject(err)
                    return
                }
                resolve(data)
            }, input)
        })
    }
}

不用promise实现:

function parallel(funcs) {
    return function (callback) {
        let isError = false
        const res = []

        funcs.map((func, index) => {
            func((error, data) => {
                if (!isError) {
                    if (error) {
                        isError = true
                        callback(error, undefined)
                    }
                    res.push(data)
                    if (res.length === funcs.length) {
                        callback(undefined, res)
                    }
                }
            })
        })
    }
}

20、实现一个async helper方法 race

race类似Promise.race()。parallel会等待所有的function执行结束,race()会在任何一个function结束或产生error的时候调用最终的callback。

注意:race()在任何一个function结束或产生error的时候调用最终的 callback

举例:

const async1 = (callback) => {
    setTimeout(() => callback(undefined, 1), 300)
}
const async2 = (callback) => {
    setTimeout(() => callback(undefined, 2), 100)
}
const async3 = (callback) => {
    setTimeout(() => callback(undefined, 3), 200)
}

const first = race(
    [
        async1,
        async2,
        async3
    ]
)
first((err, data) => {
    // 2
    console.log(data)
}, 1)

实现:

function race(funcs) {
    return function (callback) {
        let handled = false
        funcs.map((func) => {
            func((err, data) => {
                if (!handled) {
                    handled = true
                    callback(err, data)
                }
            })
        })
    }
}

往期文章推荐

面向大厂编程,100道你必须掌握的js手写题(一)

带你彻底了解微信小程序的createIntersectionObserver,实现长列表性能优化、图片懒加载、吸顶等等操作