前端八股文之手写系列

1/20/2021 JavaScript面试

面试中常常会遇到同样的问题,大多数面经都将其整理成“面试八股文”以供背诵,尤其是技术乱七八糟一大堆的前端,八股文现象尤为显著

所以面试中击败你的,不光是 985 博士和海龟等精英,还有很多“八股文选手”

本着打不过就加入的原则,本篇文章会持续更新前端八股文系列

# 快排

这里给出分治+递归的写法 代码比较短,方便记忆

const sort = array => {
    if (array.length < 2) {
        return array
    }

    const mid = array.splice(Math.floor(array.length / 2), 1)[0]
    const left = []
    const right = []

    array.forEach(item => {
        if (item < mid) {
            left.push(item)
        } else {
            right.push(item)
        }
    })

    return [...sort(left), mid, ...sort(right)]
}

# 数组扁平化

建议背前 3 个,后面的可以理解下思路(如果面试官问到别的方法的话)

递归(推荐) 元素可以是任意类型

const flatArray = array => {
    const result = []
    array.forEach(item => {
        if (Array.isArray(item)) {
            result.push(...flatArray(item))
        } else {
            result.push(item)
        }
    })
    return result
}

循环+concat(推荐) 元素可以是任意类型

const flatArray = array => {
    while (array.some(item => Array.isArray(item))) {
        array = [].concat(...array)
    }
    return array
}

reduce(推荐) 元素可以是任意类型

const flatArray = array => {
    return []
        .concat(array)
        .reduce(
            (item, next) =>
                item.concat(Array.isArray(array) ? flatArray(next) : next),
            []
        )
}

toString+split(不推荐) 代码短但是数组元素只能是number或者同一种类型的基本类型

const flatArray = array => {
    return array
        .toString()
        .split(',')
        .map(item => Number(item))
}

join+split(不推荐) 优缺点同上,join 也可以转字符串

const flatArray = array => {
    return array
        .join(',')
        .split(',')
        .map(item => Number(item))
}

JSON+正则(不推荐) 优缺点同上,正则不太好记忆

const flatArray = array => {
    return JSON.stringify(array)
        .replace(/(\[|\])/g, '')
        .split(',')
        .map(item => Number(item))
}

ES6 自带的 flat(不推荐) 千万别写(就是考你 flat 实现的,你直接调了现成的。。。) 但是要了解一下 ES6 这个新引入的函数

const flatArray = array => {
    while (array.some(item => Array.isArray(item))) {
        array = array.flat()
    }
    return array
}

# call、apply、bind

# call

  • 判断nullundefined时指向全局对象
  • 对基本类型装箱:new Object(...)
Function.prototype._call = function(context) {
    if (context === undefined || context === null) {
        context = globalThis
    }

    context = new Object(context)

    context.fn = this
    const args = []

    Array.prototype.forEach.call(
        Array.prototype.slice.call(arguments, 1),
        item => args.push(item)
    )

    const result = context.fn(...args)

    delete context.fn
    return result
}

# apply

类似 call 的实现,传参方式不同

Function.prototype._apply = function(context, args) {
    if (context === undefined || context === null) {
        context = globalThis
    }

    context = new Object(context)
    context.fn = this

    const result = context.fn(...args)

    delete context.fn
    return context
}

# bind

简易版 存在问题:返回的函数不能构造器调用(new xxx())

Function.prototype._bind = function(...args) {
    const fn = this
    const that = args[0]
    const bindArgs = args.slice(1)

    return (...args) => {
        fn.apply(that, [...bindArgs, ...args])
    }
}

类似 MDN 的实现

Function.prototype._bind = function(context) {
    if (typeof this !== 'function') {
        throw new Error(
            'Function.prototype.bind - what is trying to be bound is not callable'
        )
    }

    const fn = this
    const args = Array.prototype.slice.call(arguments, 1)
    const noop = function() {}
    const result = function() {
        const bindArgs = Array.prototype.slice.call(arguments)
        return fn.apply(this instanceof noop ? this : context, args.bindArgs)
    }

    noop.prototype = fn.prototype
    result.prototype = new noop()
    return result
}

# 数组去重

有很多种思路,这里只列出几种代码较少的

利用 Set 利用 Set 的不可重复特性(ES6)

const unique = array => [...new Set(array)]

利用 Map 将元素设置为 Map 的键再检索(ES6)

const unique = array => {
    const map = new Map()
    return array.filter(item => !map.has(item) && map.set(item, 1))
}

reduce

const unique = array =>
    array.reduce((item, next) =>
        item.includes(next) ? item : [...item, next], [])
}

ES5 对象 这个方法会将'1'1看做相同

const unique = array => {
    const map = {}
    return array.filter(item => !map.hasOwnProperty(item) && (map[item] = 1))
}

# 防抖、节流

# 防抖

const debounce = (fn, time) => {
    let timer = null
    return function() {
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(this, arguments)
        }, time)
    }
}

# 节流

const throttle = (fn, time) => {
    let flag = true
    return function() {
        if (!flag) {
            return
        }
        flag = false
        setTimeout(() => {
            fn.apply(this, arguments)
            flag = true
        }, time)
    }
}

# 函数柯里化

实现 add 函数,使得add(1,2,3)add(1)(2)(3)add(1,2)(3)都得到正确结果

function add() {
    const args = [...arguments]

    function fn() {
        args.push(...arguments)
        return fn
    }

    fn.toString = function() {
        return args.reduce((item, next) => item + next)
    }

    return fn
}

# 类数组转数组

类数组是指具有 length 属性,但不具有数组方法,索引都为非负整数的对象

Array.from

const trans = Array.from

Array.slice

const transform = likeArray => Array.prototype.slice.call(likeArray)

concat

const transform = likeArray => Array.prototype.concat.apply([], likeArray)

# 深/浅拷贝

# 深拷贝

利用 JSON 对函数、undefinedSymbol元素无效

const deepCopy = obj => JSON.parse(JSON.stringify(obj))

递归

const deepCopy = obj => {
    let objNew = Array.isArray(obj) ? [] : {}
    if (obj && typeof obj === 'object') {
        for (key in obj) {
            if (obj.hasOwnProperty(key)) {
                if (obj[key] && typeof obj[key] === 'object') {
                    objNew[key] = deepCopy(obj[key])
                } else {
                    objNew[key] = obj[key]
                }
            }
        }
    }
    return objNew
}

# 浅拷贝

循环判断

const shallowCopy = obj => {
    let newObj = obj instanceof Array ? [] : {}
    for (key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key]
        }
    }
    return newObj
}

Object.assign

const shallowCopy = obj => Object.assign({}, obj)

# 链式调用

class Person {
    constructor() {
        this._name = ''
        this._age = 0
    }

    name(name) {
        this._name = name
        return this
    }

    age(age) {
        this._age = age
        return this
    }
}

// 使用
let person = new Person().name('王昭君').age(21)