web面试中经常会遇到的手写代码题

159 阅读4分钟

1、手写 bind、apply、call

  • bind
// bind
Function.prototype.bind = function (context, ...args) {
    context = context || window;
    const fnSymbol = Symbol("fn");
    context[fnSymbol] = this;

    return function (..._args) {
        args = args.concat(_args);

        context[fnSymbol](...args);
        delete context[fnSymbol];
    }
}
  • apply
// apply
Function.prototype.apply = function (context, argsArr) {
    context = context || window;
    const fnSymbol = Symbol("fn");
    context[fnSymbol] = this;

    context[fnSymbol](...argsArr);
    delete context[fnSymbol];
}
  • call
// call
Function.prototype.call = function (context, ...args) {
    context = context || window;
    const fnSymbol = Symbol("fn");
    contet[fnSymbol] = this;
    
    context[fnSymbol](...args);
    delete context[fnSymbol];
}

2、手写 Promise

class myPromise {
    static pending = 'pending';
    static fulfilled = 'fulfilled';
    static rejected = 'rejected';

    constructor(executor) {
        this.status = myPromise.pending; // 初始化状态为pending
        this.value = undefined; // 存储 this._resolve 即操作成功 返回的值
        this.reason = undefined; // 存储 this._reject 即操作失败 返回的值
        // 存储then中传入的参数
        // 至于为什么是数组呢?因为同一个Promise的then方法可以调用多次
        this.callbacks = [];
        executor(this._resolve.bind(this), this._reject.bind(this));
    }

    // onFulfilled 是成功时执行的函数
    // onRejected 是失败时执行的函数
    then(onFulfilled, onRejected) {
        // 返回一个新的Promise
        return new myPromise((nextResolve, nextReject) => {
            // 这里之所以把下一个Promsie的resolve函数和reject函数也存在callback中
            // 是为了将onFulfilled的执行结果通过nextResolve传入到下一个Promise作为它的value值
            this._handler({
                nextResolve,
                nextReject,
                onFulfilled,
                onRejected
            });
        });
    }

    _resolve(value) {
        // 处理onFulfilled执行结果是一个Promise时的情况
        // 这里可能理解起来有点困难
        // 当value instanof WPromise时,说明当前Promise肯定不会是第一个Promise
        // 而是后续then方法返回的Promise(第二个Promise)
        // 我们要获取的是value中的value值(有点绕,value是个promise时,那么内部存有个value的变量)
        // 怎样将value的value值获取到呢,可以将传递一个函数作为value.then的onFulfilled参数
        // 那么在value的内部则会执行这个函数,我们只需要将当前Promise的value值赋值为value的value即可
        if (value instanceof myPromise) {
            value.then(
                this._resolve.bind(this),
                this._reject.bind(this)
            );
            return;
        }
        this.value = value;
        this.status = myPromise.fulfilled; // 将状态设置为成功
        // 通知事件执行
        this.callbacks.forEach(cb => this._handler(cb));
    }

    _reject(reason) {
        if (reason instanceof myPromise) {
            reason.then(
                this._resolve.bind(this),
                this._reject.bind(this)
            );
            return;
        }
        this.reason = reason;
        this.status = myPromise.rejected; // 将状态设置为失败
        this.callbacks.forEach(cb => this._handler(cb));
    }

    _handler(callback) {
        const {
            onFulfilled,
            onRejected,
            nextResolve,
            nextReject
        } = callback;


        if (this.status === myPromise.pending) {
            this.callbacks.push(callback);
            return;
        }

        if (this.status === myPromise.fulfilled) {
            // 传入存储的值
            // 未传入onFulfilled时,value传入
            const nextValue = onFulfilled ? onFulfilled(this.value) : this.value;
            nextResolve(nextValue);
            return;
        }

        if (this.status === myPromise.rejected) {
            // 传入存储的错误信息
            // 同样的处理
            const nextReason = onRejected ? onRejected(this.reason) : this.reason;
            nextReject(nextReason);
        }
    }
}

3、手写 实现promise.all 方法

Promise.all = (arr) => {
    return new Promise((resolve, reject) => {
        let res = [];
        let count = 0;
        for (let i = 0; i < arr.length; i++) {
            // 面试点:
            Promise.resolve(arr[i]).then((value) => {
            	//坑点1:切记不能使用res.push(value); 因为此时属于异步,如果使用push,则会将原数组循序混乱,所以使用赋值方式避免
                res[i] = value;
               	//坑点2:切记不能直接使用res.length === arr.length;因为异步如果先执行了arr[1],arr.length长度会直接是2,所以需要记录,在判断记录值是否等于arr.length
                count ++;
                if(count === arr.length) {
                    resolve(res);
                }
            }).catch((reason) => {
                reject(reason);
            })
        }
    })
}

4、手写 实现promise.prototype.finally 方法

Promise.prototype.finally = function(callback) {
    return this.then((value) => {
        return Promise.resolve(callback()).then(() => value);
    }),
    (err) => {
        return Promise.resolve(callback()).then(() => {throw err});
    }
}

5、手写 Promise.allSeettled 方法, 需要返回所有promise的状态和结果

function PromiseAllSettled(promiseArray) {
    return new Promise(function (resolve, reject) {
        //判断参数类型
        if (!Array.isArray(promiseArray)) {
            return reject(new TypeError('arguments muse be an array'))
        }
        let counter = 0;
        const promiseNum = promiseArray.length;
        const resolvedArray = [];
        for (let i = 0; i < promiseNum; i++) {
            Promise.resolve(promiseArray[i])
                .then((value) => {
                    resolvedArray[i] = {
                        status: 'fulfilled',
                        value
                    };

                })
                .catch(reason => {
                    resolvedArray[i] = {
                        status: 'rejected',
                        reason
                    };
                })
                .finally(() => {
                    counter++;
                    if (counter == promiseNum) {
                        resolve(resolvedArray)
                    }
                })
        }
    })
}

6、手写 Promise.race

function myPromiseRace(arr) {
    return new Promise((resolve, reject) => {
        for(var i = 0; i < arr.length; i++) {
            return arr[i].then(resolve,reject);
        }
    })
}

7、手写 数据偏平化

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

const a = [1, [2, [3, 4]]];
console.log(flatten(a))    // [ 1, 2, 3, 4 ]

8、手写 实现柯里化

function createCurry(func, args) {
    var argity = func.length;
    var args = args || [];

    return function() {
        var _args = [].slice.apply(arguments);
        args.push(_args);

        if(args.length < argity) {
            return createCurry.call(this, func, args);
        }
        return func.apply(this, args);
    }
}

9、手写 实现深、浅拷贝函数

// 浅拷贝
function shallowClone(obj) { 
    let cloneObj = {}; 
    for (let i in obj) { 
        cloneObj[i] = obj[i]; 
    } return cloneObj; 
}


// 深拷贝
function deepCopy(obj) { 
    if (typeof obj === 'object') { 
        var result = obj.constructor === Array ? [] : {}; 
        for (var i in obj) { 
            result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i]; 
        } 
    } else { 
        var result = obj; 
    } 
    return result; 
}

10、手写 防抖、节流

  • 防抖:动作绑定事件,动作发生后一定时间后触发事件,在这段时间内,如果该动作又发生,则重新等待一定时间再触发事件。
function debounce(func, time) {
    let timer = null;
    return () => {
        clearTimeout(timer);
        timer = setTimeout(()=> {
            func.apply(this, arguments)
        }, time);
    }
}
  • 节流: 动作绑定事件,动作发生后一段时间后触发事件,在这段时间内,如果动作又发生,则无视该动作,直到事件执行完后,才能重新触发。
function throtte(func, time){
    let activeTime = 0;
    return () => {
        const current = Date.now();
        if(current - activeTime > time) {
            func.apply(this, arguments);
            activeTime = Date.now();
        }
    }
}

11、手写 instanceof

function myInstanceof(left, right) {
    let protoType = right.protoType;
    left = left.__proto__;
    while(true) {
        if(!left) return false;
        if(left == protoType) return true;
        left = left.__proto__;
    }
}

console.log(myInstanceof([],Array));   //true

12、手写 New

function Person(name,age) {
    this.name = name;
    this.age = age;
}
Person.protoType.sayHi() = function() {
    console.log('我是'this.name);
}

let p1 = new Person('abc',10);
//实现new
function create() {
    let obj = {};
    // 获取构造函数
    let fn = [].shift.call(arguments);   //将arguments对象提出来转化成数组
    obj.__proto__ = fn.prototype;
    let res = fn.apply(obj,arguments);   //改变this指向,为实例添加属性和方法
    // 确保返回的是一个对象
    return typeof res === 'Object' ? res : obj;
}

let p2 = create(Person,'def',13);
p2.sayHi();

13、手写 快速排序

function quicksort(arr) {
    if(arr.length <= 1) return arr;
    let pivotIndex = arr.length >> 1;
    let pivot = arr.splice(pivotIndex,1)[0];
    let left = [];
    let right = [];
    for(var i = 0; i < arr.length; i++) {
        if(arr[i] <= pivot) {
            left.push(arr[i]);
        }else {
            right.push(arr[i]);
        }
    }
    return quicksort(left).concat(pivot, quicksort(right));
}

console.log(quicksort([9,4,5,1,7,2]));   //[1,2,4,5,7,9]

14、异步笔试题,请写出下面代码的运行结果

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

//结果 script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2 ->setTimeout

15、数组排序

1、JS 的全排列

function permutate(str) {
    var result = [];

    if(str.length > 1) { 
        var left = str[0];
        var rest = str.slice(1,str.length); 
        var preResult = permutate(rest);

        for(var i=0; i<preResult.length; i++){ 
            for(var j=0;j<preResult[i].length; j++) {
                var tmp = preResult[i],slice(0, j) + left + preResult[i].slice(j,preResult[i].length);
                result.push(tmp);
            }
        }
    } else if (str.length== 1) { 
        return [str];
    }
    return result;
}

2、冒泡排序

function bubbleSort(arr) {
  let len = arr.length
  for (let i = 0; i < len - 1; i++) {
    // 从第一个元素开始,比较相邻的两个元素,前者大就交换位置
    for (let j = 0; j < len - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        let num = arr[j]
        arr[j] = arr[j + 1]
        arr[j + 1] = num
      }
    }
    // 每次遍历结束,都能找到一个最大值,放在数组最后
  }
  return arr
}

//测试
console.log(bubbleSort([2, 3, 1, 5, 4])) // [1, 2, 3, 4, 5]

3、sort排序

// 对数字进行排序,简写
const arr = [3, 2, 4, 1, 5]
arr.sort((a, b) => a - b)
console.log(arr) // [1, 2, 3, 4, 5]

// 对字母进行排序,简写
const arr = ['b', 'c', 'a', 'e', 'd']
arr.sort()
console.log(arr) // ['a', 'b', 'c', 'd', 'e']

16、编写render函数, 实现template render功能

//要求
const year = '2021';
const month = '10';
const day = '01';
let template = '${year}-${month}-${day}';
let context = { year, month, day };
const str = render(template)({year,month,day});
console.log(str) // 2021-10-01

//实现:高阶函数(函数返回函数)
function render(template) {
    return function(context) {
        return template.replace(/${(.*?)}/g, (match, key) => context[key]);
    } 
}

//.*表示:任意值
//?表示:匹配前面的表达式0或1个,或制定非贪婪限定符
//表达式 .* 就是单个字符匹配任意次,即贪婪匹配。 
//表达式 .*? 是满足条件的情况只匹配一次,即最小匹配.
//match: ${year} ${month} ${day}
//key: year month day

17、发布订阅模式

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

  on(name, fn) {
    if (this.cache[name]) {
      this.cache[name].push(fn)
    } else {
      this.cache[name] = [fn]
    }
  }

  off(name, fn) {
    const tasks = this.cache[name]
    if (tasks) {
      const index = tasks.findIndex((f) => f === fn || f.callback === fn)
      if (index >= 0) {
        tasks.splice(index, 1)
      }
    }
  }

  emit(name, once = false) {
    if (this.cache[name]) {
      // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
      const tasks = this.cache[name].slice()
      for (let fn of tasks) {
        fn();
      }
      if (once) {
        delete this.cache[name]
      }
    }
  }
}

// 测试
const eventBus = new EventEmitter()
const task1 = () => { console.log('task1'); }
const task2 = () => { console.log('task2'); }

eventBus.on('task', task1)
eventBus.on('task', task2)
eventBus.off('task', task1)
setTimeout(() => {
  eventBus.emit('task') // task2
}, 1000)