常见前端手写题(更新中)

209 阅读6分钟

JS篇

*Promise相关

class myPromise{
    constructor(fn){
        this.state = "pending"
        this.successFun = []
        this.failFun = []
        let resolve = (val)=>{
            if(this.state!=="pending") return;
            this.state = "success"
            setTimeout(()=>{
                this.successFun.forEach((item)=>item.call(this,val))
            })
        };
        let reject = (err)=>{
            if(this.state!=="pending")return;
            this.state = "fail"
            setTimeout(()=>{
                this.failFun.forEach((item)=>item.call(this,err))
            })
        }
        try{
            fn(resolve,reject);
        }catch(error){
            reject(error)
        }
    }
    
    then(resolveCallback,rejectCallback){
        resolveCallback = typeof resolveCallback!=="function"?(v)=>v:resolveCallback;
        rejectCallback = typeof rejectCallback!=="function"?(v)=>v:rejectCallback;
        return new myPromise((resolve,reject)=>{
            this.successFun.push((val)=>{
                try{
                    let x = resolveCallback(val)
                    x instanceof myPromise?x.then(resolve,reject):resolve(x);
                }catch(error){
                    reject(error)
                }
            });
            this.failFun.push((val)=>{
                try{
                    let x = rejectCallback(val);
                    x instanceof myPromise?x.then(resolve,reject):reject(x);
                }catch(error){
                    reject(error)
                }
            });
        });
    }
    static all(promiseArr){
        let result = [];
        let count = 0;
        return new myPromise((resolve,reject)=>{
            for(let i=0;i<promiseArr.length;i++){
                promiseArr[i].then((res)=>{
                    result[i] = res
                    count++
                    if(count===promiseArr.length){
                        resolve(result)
                    }
                },(err)=>{
                    reject(err)
                })
            }
        })
    }
    static race(promiseArr){
        return new myPromise((resolve,reject)=>{
            for(let i=0;i<promiseArr.length;i++){
                promiseArr[i].then((res)=>{resolve(res)},(err)=>{reject(err)})
            }
        })
    }
}

let promise1 = new myPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(123);
    }, 2000);
});
let promise2 = new myPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(1234);
    }, 1000);
});

myPromise.all([promise1, promise2]).then(res => {
    console.log(res);
})

myPromise.race([promise1, promise2]).then(res => {
    console.log(res);
});

promise1.then(res => {
        console.log(res); //过两秒输出123
        return new myPromise((resolve, reject) => {
            setTimeout(() => {
                resolve("success");
            }, 1000);
        });
    },
        err => {console.log(err);}).then(
            res => {
            console.log(res); //再过一秒输出success
        },
        err => {
            console.log(err);
        }
);

1.实现一个节流函数

function throttle(fn,delay=500){
    let timer = null
    return function(){
        if(!timer){
            fn.apply(this,arguments)
            timer = setTimeout(()=>{
                timer = null
            },delay)
        }
    }
}

2.实现一个防抖函数

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

应用 通过防抖和节流,来获取滚动条距离顶部的距离

<html>
    <head>
    
    <style>
        div{
            height:2000px;
            background-color:blue
        }
    </style>
    </head>
    <body>
        <div>233</div>
    <script>
        //节流函数,同一个单位时间内只执行一次函数,
        function throttle(fn,delay = 2000){
            let timer = null
            return function(){
                if(timer==null){
                    fn.apply(this,arguments)
                    timer = setTimeout(()=>{
                        timer = null
                    },delay)
                }
            }
        }
        //防抖函数,在一段时间后,函数执行,如果期间再次触发函数,则重新计时
        function debounce(fn,delay=2000){
            let timer = null
            return function(){
                clearTimeout(timer)
                timer = setTimeout(()=>{
                    fn.apply(this,arguments)
                },delay)
            }
        }
        function showTop(){
            let scrollTop = document.body.scrollTop||document.documentElement.scrollTop;
            console.log("滚动条位置:",scrollTop)
        }
        // window.onscroll=throttle(showTop,3000)
        window.onscroll=debounce(showTop)
    </script>
    </body>
</html>

3.对象的继承

ES5

function Parents(name){
    this.name = name
}
Parents.prototype.eat = function(){
    console.log(this.name," is eating")
}

function Child(name,age){
    Parents.call(this,name)
    this.age = age
}
Child.prototype = Object.create(Parents.prototype)

let xiaoming = new Child("xiaoming",20)
console.log(xiaoming.name)
console.log(xiaoming.age)
console.log(xiaoming.eat())

//xiaoming
//20
//xiaoming  is eating

ES6

class Parents{
    constructor(name){
        this.name = name
    }
    eat(){
        console.log(this.name," is eating")
    }
}

class Child extends Parents{
    constructor(name,age){
        super(name)
        this.age = age
    }
}

let xiaoming = new Child("xiaoming",20)
console.log(xiaoming.name)
console.log(xiaoming.age)
console.log(xiaoming.eat())

//xiaoming
//20
//xiaoming  is eating

4.浅拷贝

浅拷贝的方法

  1. Object.assign()
  2. Array.prototype.concat()
  3. Array.prototype.slice()
function shallowCopy(object){
    if(!object||typeof object !=='object'){
        return object
    }
    let newObject = Array.isArray(object)?[]:{}
    
    for(let key in object){
        if(object.hasOwnProperty(key)){
            newObject[key] = object[key]
        }
    }
    return newObject
}

5.深拷贝

深拷贝的方法

  1. json.parse(json.stringify())
  2. lodash的cloneDeep方法
function deepCopy(target){
    let type = Object.prototype.toString.call(target)
    let res = undefined
    if(type ==='[object Object]'){
        res = {}
    }else if(type==='[object Array]'){
        res = []
    }else{
        return target
    }
    
    for(let key in target){
        let item = target[key]
        let itemType = Object.prototype.toString.call(item)
        if(itemType==='[object Object]'||itemType==='[object Array]'){
            res[key] = deepCopy(item)
        }else{
            res[key] = item
        }
    }
    return res
}

应用

let a = [1,2,3,{a:"a1",b:{name:"张三"}}]

let b = deepCopy(a)
let c = shallowCopy(a)

b[3].b.name = "我是b"
c[3].b.name = "我是c"

console.log(a)
console.log(b)
console.log(c)
//[ 1, 2, 3, { a: 'a1', b: { name: '我是c' } } ]
//[ 1, 2, 3, { a: 'a1', b: { name: '我是b' } } ]
//[ 1, 2, 3, { a: 'a1', b: { name: '我是c' } } ]

6.call

fn.call(上下文环境,执行所需的单个参数)

会使当前函数立即执行,用来改变函数的this指向

Function.prototype.MyCall = function(ctx){
    //判断对象是否为函数
    if(typeof this!== 'function'){
        throw new TypeError("类型错误")
    }
    //获取参数
    let args = [...arguments].slice(1)
    //判断上下文是否为null
    ctx = ctx||window
    //将调用函数设为对象的方法
    ctx.fn = this
    let result = null
    result = ctx.fn(...args)
    delete ctx.fn
    return result
}

7.apply

fn.apply(上下文环境,执行所需的数组) 会使当前函数立即执行,用来改变函数的this指向

Function.prototype.MyApply = function(ctx){
    if(typeof this!=='function'){
        throw new TypeError("类型错误")
    }
    ctx = ctx||window
    ctx.fn = this
    let result = null
    if(arguments[1]){
        result = ctx.fn(...arguments[1])
    }else{
        result = ctx.fn
    }
    delete ctx.fn
    return result
}

8.bind

bind 的作用与 call 和 apply 相同,

区别是后两者是立即调用函数,而bind是返回了一个函数,需要调用时再执行

Function.prototype.MyBind=function(ctx){
    if(typeof this!=='function'){
        throw new TypeError("类型错误")
    }
    let fn = this
    return function(){
        fn.apply(ctx,arguments)
    }
}

9.jsonp

function jsonp(url,data={},callback = 'callback'){
    data.callback = callback
    let params = []
    for(let key in data){
        params.push(key+"="+data[key])
    }
    let script = document.createElement('script')
    script.src = url+'?'+params.join("&")
    document.body.appendChild(script)
    
    return new Promise((resolve,reject)=>{
        window[callback] = (data)=>{
            try{
                resolve(data)
            }catch{
                reject(data)
            }finally{
                //移除插入的script元素
                script.parentNode.removeChild(script)
            }
        }
    })
}

10.instanceof

顺着原型链去找,直到找到相同的原型对象

function myInstanceof(left,right){
    // 这里先用typeof来判断基础数据类型,如果是,直接返回false
    if(typeof left !== 'object' || left === null){
        return false
    }
    //getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
    let proto = Object.getPrototypeOf(left)
    while(proto!==null){
        if(proto===right.prototype){
            return true
        }
        proto = Object.getPrototypeOf(proto)
    }
    return false
}

11.render虚拟DOM渲染函数

function render(elementObj) {
    //1.根据tag名称创建节点
    let el = document.createElement(elementObj.tagName)
    //2.为创建的节点添加属性
    for (let propName in elementObj.props) {
        let propValue = elementObj.props[propName]
        el.setAttribute(propName, propValue)
    }
    //3.如果存在内容,通过 innerText 加入
    if (elementObj.content) {
        el.innerText = elementObj.content
    }
    //4.如果存在子节点,依次遍历递归添加
    if (elementObj.children) {
        elementObj.children.forEach((child) => {
            el.appendChild(render(child))
        })
    }
    return el
}

设计模式

1.观察者模式

观察者定义了一种一对多的依赖关系,让多个观察者同时监听某一个目标对象

当这个目标对象的状态发生了变化时,会通知所有观察者对象,使它们能够自动更新

class Publisher{
    constructor(){
        this.observers = []
    }
    addObserver(observer){
        this.observers.push(observer)
    }
    removeObserver(observer){
        this.observers.forEach((item,index)=>{
            if(observer===item){
                this.observers.splice(index,1)
            }
        })
    }
    notifyObservers(){
        this.observers.forEach(item=>{
            item.update(this)
        })
    }
}

class Observer{
    update(Publisher){}
}

class StarPublisher extends Publisher{
    constructor(){
        super()
        this.state = null
    }
    getData(){
        return this.state
    }
    setData(data){
        this.state = data
        this.notifyObservers()
    }
}
class StarObserver extends Observer{
    constructor(id){
        super()
        this.id = id
        this.state = null
    }
    update(publisher){
        this.state = publisher.getData()
        this.remind()
    }
    remind(){
        console.log(this.id+" 接受消息: ",this.state)
    }
}
let star = new StarPublisher()
let fun1 = new StarObserver("#01")
let fun2 = new StarObserver("#02")
let fun3 = new StarObserver("#03")

star.addObserver(fun1)
star.addObserver(fun2)
star.addObserver(fun3)

star.setData("Jay Chou 出新专辑了!")
star.removeObserver(fun2)
star.setData("不信谣不传谣...")

//#01 接受消息:  Jay Chou 出新专辑了!
//#02 接受消息:  Jay Chou 出新专辑了!
//#03 接受消息:  Jay Chou 出新专辑了!
//#01 接受消息:  不信谣不传谣...
//#03 接受消息:  不信谣不传谣...

2.发布订阅模式

观察者模式:发布者直接接触订阅者;

发布——订阅模式:发布者和订阅者通过中间平台间接接触, 发布者无法感知订阅者

class EventEmitter {
    constructor() {
        this.handlers = {}
    }

    //注册事件监听器
    on(event, callback) {
        if (!this.handlers[event]) {
            this.handlers[event] = []
        }
        this.handlers[event].push(callback)
    }

    //移除某个事件回调队列里的指定回调函数
    off(event, callback) {
        const callbacks = this.handlers[event],
            index = callbacks.indexOf(callback)
        if (index !== -1) {
            callbacks.splice(index, 1)
        }
    }
    // 触发目标事件
    emit(event, ...params) {
        if (this.handlers[event]) {
            this.handlers[event].forEach((callback) => {
                callback(...params);
            });
        }
    }

    // 为事件注册单次监听器
    once(event, callback) {
        // 对回调函数进行包装,使其执行完毕自动被移除
        const wrapper = (...params) => {
            callback(...params);
            this.off(event, wrapper);
        };
        this.on(event, wrapper);
    }
}


排序算法

排序算法的讲解可以看我这篇文章八大算法的Golang实现,这里只贴出相关代码

冒泡排序

/**
 * @param {number[][]} grid
 * @return {number[]}
 * @param {number[]} array
 */
function bubbleSort(array){
    let sortBorder=array.length-1
    let lastExchangeIndex =0
    for(let i=0;i<array.length-1;i++){
        let isSort = true
        for(let j=0;j<sortBorder;j++){
            if(array[j]>array[j+1]){
                let temp = array[j]
                array[j] = array[j+1]
                array[j+1] = temp
                isSort = false
                lastExchangeIndex = j
            }
        }
        sortBorder = lastExchangeIndex
        if(isSort){
            return array
        }

    }
    return array
}

var arr = [3,1,4,6,5,7,2,3]
console.log(bubbleSort(arr));

选择排序

/**
 * 
 * @param {Array} array 
 */
let array = [5,9,6,1,3,4,7,2,8,5]
function selectionSort(array){
    let minIndex = 0
    for(let i=0;i<array.length-1;i++){
        minIndex = i
        for(let j=i+1;j<array.length;j++){
            if(array[j]<array[minIndex]){
                minIndex = j
            }
        }
        [array[i],array[minIndex]] = [array[minIndex],array[i]]
    }
    return array
}

console.log(selectionSort(array))

插入排序

/**
 * @param {Array} array 
 */
function insertionSort(array){
    for(let i=1;i<array.length;i++){
        let insertValue = array[i]
        let j = i-1
        for(j;j>=0&&array[j]>insertValue;j--){
            array[j+1]=array[j]
        }
        array[j+1] = insertValue
    }return array
}
let array = [5,9,6,1,3,4,7,2,8,5]
console.log(insertionSort(array))

希尔排序

/**
 * 
 * @param {int[]} array 
 */

function ShellSort(array){
    let n =array.length
    // console.log(n)
    for(let gap=Math.floor(n/2);gap>0;gap=Math.floor(gap/2)){
        for(let i=gap;i<n;i++){
            let temp=array[i]
            let j=i-gap
            while(j>=0&&array[j]>temp){
                array[j+gap]=array[j]
                j=j-gap
            }
            array[j+gap] = temp
        }
    }
}
let array=[5,3,9,12,6,1,7,2,13,4,11,8,10]
ShellSort(array)
console.log(array)

归并排序

/**
 * 
 * @param {number[]} array 
 * @param {number} start
 * @param {number} end
 */
function mergeSort(array,startIndex,endIndex){
    if(startIndex<endIndex){
        let mid=parseInt((startIndex+endIndex)/2)
        mergeSort(array,startIndex,mid)
        mergeSort(array,mid+1,endIndex)

        merge(array,startIndex,mid,endIndex)
    }
}

function merge(array,start,mid,end){
    let tempArray = Array(end-start+1)
    let p1 = start
    let p2 = mid +1
    let p=0
    while(p1<=mid&&p2<=end){
        if(array[p1]<=array[p2]){
            tempArray[p]=array[p1]
            p1++
        }else{
            tempArray[p]=array[p2]
            p2++
        }
        p++
    }
    while(p1<=mid){
        tempArray[p]=array[p1]
        p++
        p1++
    }
    while(p2<=end){
        tempArray[p]=array[p2]
        p++
        p2++
    }
    for(let i=0;i<tempArray.length;i++){
        array[start+i]=tempArray[i]
    }
}
let array=[5,3,9,12,6,1,7,2,13,4,11,8,10]
mergeSort(array,0,array.length-1)
console.log(array)

快速排序


/**
 * @param {number[]} array
 */

function quickSort(array,startIndex,endIndex){
    if(startIndex>endIndex){
        return
    }
    
    let pivotIndex = partition(array,startIndex,endIndex)
    quickSort(array,startIndex,pivotIndex-1)
    quickSort(array,pivotIndex+1,endIndex)
}

function partition(array,startIndex,endIndex){
    let pivot = array[startIndex]
    let left = startIndex,right = endIndex
    while(left!=right){
        while(left<right&&array[right]>pivot){
            right--
        }
        while(left<right&&array[left]<=pivot){
            left++
        }
        if(left<right){
            [array[left],array[right]] = [array[right],array[left]]
        }
    }
    [array[left],array[startIndex]] = [array[startIndex],array[left]]
    
    return left
}

桶排序

/**
 * 
 * @param {number[]} array 
 */
function bucketSort(array){
    //1.获取数组的最大值和最小值,算出差值 d
    let min = Math.min.apply(null,array)
    let max = Math.max.apply(null,array)
    //2.初始化桶
    let bucketNum = parseInt((max-min)/array.length)+1
    let bucketList = Array(bucketNum).fill(0).map(()=>Array())
    //3.遍历原始数组,将每个元素放入桶中
    for(let i=0;i<array.length;i++){
        let num = parseInt((array[i]-min)/array.length)
        bucketList[num].push(array[i])
    }
    //4.对每个桶中的数组进行排序
    for(let i=0;i<bucketList.length;i++){
        bucketList[i].sort()
    }
    //5.输出
    let index=0
    for(let i=0;i<bucketList.length;i++){
        for(let j=0;j<bucketList[i].length;j++){
            array[index] = bucketList[i][j]
            index++
        }
    }
}

let array=[5,16,95,1,45,66,75,20,62,38,76,94,43]
bucketSort(array)
console.log(array)