能够提高你js水平的一些题

135 阅读6分钟

最近面试结束,整理了一下最近的js手写题吧

Promise相关

1. Promise.all实现

Promise.all: 按顺序拿到所有结果, 有错误就直接返回。

function promiseAll(arr) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(arr)) reject('not array!');
        const result = [];
        const count = 0;
        arr.forEach((item, index) => {
            // 确保是promise
            Promise.resolve(item).then(res => {
                // 确保结果的顺序
                result[index] = res;
                count++;
                if (count === arr.length) resolve(result);
            }, rej => {
                reject(rej);
            });

        });
    });
}

2.Promise.allSettled实现

面试的时候,叫你延伸,实现不管是否reject,都要拿到所有结果,其实就是promise.allSettled的实现。

方法一:Promise.all的基础上改造一下。

function promiseAllSettled(promises) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) reject([]);
        const result = [];
        let count = 0;
        let len = promises.length;
        promises.forEach((item, index) => {
            Promise.resolve(item).then(res => {
                result[index] = {
                    status: 'success',
                    res,
                };
                count++;
                if (count >= len) resolve(result);
            }, rej => {
                result[index] = {
                    status: 'error',
                    res,
                };
                count++;
                if (count >= len) resolve(result);
            })
        });
    });
}

方法二:先包一层promise,全部resolve,再调用Promise.all。

function promiseAllSettled1(promises) {
    if (!Array.isArray(promises)) Promise.reject('Not an array');
    const success = promises.map(item => {
        return new Promise((resolve, reject) => {
            Promise.resolve(item).then(resolve, resolve);
        });
    })
    return Promise.all(success);
}

3. Promise.race实现

Promise.race: 拿到最快的结果

function promiseRace(arr) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(arr)) reject('not array!');
        arr.forEach(item => {
            Promise.resolve(item).then(resolve,reject);
        });
    });
}

简单应用: 一个是定义一个myFetch方法,实现timeout秒服务端没返回的话,就报错

function myFetch(params, timeout) {
    return Promise.race([new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('error');
        }, timeout);
    }), fetch(params)])
}

4. Promise封装ajax

function ajax(methods, url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(methods, url);
        xhr.onreadystatechange = () => {
            if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                resolve(xhr.response);
            } else {
                reject(xhr.status);
            }
        }
        xhr.send();
    });
}

5.链式调用(两种方法)

1.Promise.resolve()辅助实现

class Chains {
  task = Promise.resolve()
  eat() {
    this.task = this.task.then(() => {
      console.log('eat')
    })
    return this;
  }
  work() {
    this.task = this.task.then(() => {
      console.log('work')
    })
    return this;
  }
  sleep(delay) {
    this.task = this.task.then(() => {
      return new Promise((resolve) => {
        setTimeout(() => {
          console.log('sleep')
          resolve()
        }, delay)
      })
    })
    return this;
  }
}
const chain = () => new Chains()
chain().eat().sleep(1000).work();
//输出:eat,(等待1秒)sleep,work

抛出一个疑问:如果要实现(不使用队列):

 chain().eat().sleep(1000).firstRun().work();
 // 输出:firstRun,eat,(等待1秒)sleep,work

这个怎么实现?? 我的思路:可能先把firstRun放微任务,其他放宏任务。后面如果实现了再来补充吧 如果你实现了,欢迎评论留言你的方法,我会标明作者更新在这里。

2.队列+next()辅助实现

这里是使用队列可以比较清晰的实现:

class Arrange {
  constructor(name) {
    this.name = name;
    this.queue = [];
    this.queue.push(() => {
      console.log('init', this.name);
      this.next();
    });
    Promise.resolve().then(res => {
      this.next();
    });
  }
  next() {
    const fn = this.queue.shift();
    fn && fn();
  }
  dosth(param) {
    const fn = () => {
      console.log(param);
      this.next();
    }
    this.queue.push(fn);
    return this;
  }
  awaitFirst(timeout) {
    const fn = () => {
      setTimeout(() => {
        console.log('awaitFirst', timeout);
        this.next();
      }, timeout);
    }
    this.queue.unshift(fn);
    return this;
  }
  await(timeout) {
    const fn = () => {
      setTimeout(() => {
        console.log('await', timeout);
        this.next();
      }, timeout);
    }
    this.queue.push(fn);
    return this;
  }
  execute() {
    const fn = () => {
      console.log('execute');
      this.next();
    }
    this.queue.push(fn);
    return this;
  }
}
new Arrange('william').dosth('push').awaitFirst(1000).await(2000).execute();

6.异步并发控制(思路清晰)

关键思路是:当任务达到最大的时候,使用promise来阻塞后续代码的执行,并且将该promise的resolve存入队列,其他正在执行的任务执行完后,后续再拿出来执行。

// 异步任务调度器
class Scheduler {
  constructor(max) {
    // 最大可并发任务数
    this.max = max;
    // 当前并发任务数
    this.count = 0;
    // 阻塞的任务队列
    this.queue = [];
  }

  async add(fn) {
    if (this.count >= this.max) {
      // 若当前正在执行的任务,达到最大容量max
      // 阻塞在此处,等待前面的任务执行完毕后将resolve弹出并执行
      await new Promise(resolve => this.queue.push(resolve));
    }
    // 当前并发任务数++
    this.count++;
    // 使用await执行此函数
    const res = await fn();
    // 执行完毕,当前并发任务数--
    this.count--;
    // 若队列中有值,将其resolve弹出,并执行
    // 以便阻塞的任务,可以正常执行
    this.queue.length && this.queue.shift()();
    // 返回函数执行的结果
    return res;
  }
}
const scheduler = new Scheduler(3);

const timeout = (time) => {
  return new Promise(resolve => {setTimeout(resolve, time);})
}

const addTask = (time, order) => {
  scheduler.add(() => timeout(time)).then((res) => console.log(`task ${order} done`));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");

两个常见算法

1.快排

const quickSort = (arr = []) => {
    if (arr.length <= 1) return arr;
    const left = [];
    const right = [];
    let pivotIndex = Math.floor(arr.length / 2);
    let pivot = arr.splice(pivotIndex, 1)[0];
    for (const item of arr) {
        if (item <= pivot) {
            left.push(item);
        } else {
            right.push(item);
        }
    }
    return quickSort(left).concat([pivot], quickSort(right));
}
console.log(quickSort([3,5,7,7,2,3,6,8,0,76,3,3,5,67,73,5,56]));

抛砖:leecode:215. 数组中的第K个最大元素。(答案就不贴了,官网很多解法)

2.二分查找

二分查找的变式很多,个人感觉是,关键点需要找到缩小范围的条件,和你要拿到的值。

const binSearch = (arr, target) => {
    let left = 0;
    let right = arr.length - 1;
    while (left <= right) {
        let mid = Math.floor((left + right) / 2);
        if (arr[mid] < target) {
            left = mid + 1;
        } else if (arr[mid] > target) {
            right = mid - 1;
        } else {
            return mid;
        }
    }
    return -1;
}

console.log(binSearch([1, 3, 5, 7, 9, 11, 55, 84, 97, 99],9));

抛砖:数组中小于t的元素的个数

/** 给一个递增的数组 a (元素可以有相同的值),含有 a.length 个元素,一个目标值 t, 求数组 a 中小于 t 的元素个数
    (请用最高效的算法实现,并注明时间复杂度)
    
    参数:  
    - a: 递增的数组
    - t: 目标值 t
    
    return 数组 a 中小于 t 的元素的个数
    
    例子:
        f([1, 3, 6, 6, 8, 12], 0) === 0  
        f([1, 3, 6, 6, 8, 12], 1) === 0  
        f([1, 3, 6, 6, 8, 12], 2) === 1  
        f([1, 3, 6, 6, 8, 12], 3) === 1  
        f([1, 3, 6, 6, 8, 12], 4) === 2  
        f([1, 3, 6, 6, 8, 12], 6) === 2  
        f([1, 3, 6, 6, 8, 12], 7) === 4  
        f([1, 3, 6, 6, 8, 12], 8) === 4  
        f([1, 3, 6, 6, 8, 12], 9) === 5  
        f([1, 3, 6, 6, 8, 12], 12) === 5  
        f([1, 3, 6, 6, 8, 12], 13) === 6  
*/

// 思考下??


function f(arr, target) {
    let left = 0;
    let right = arr.length - 1;
    while (left <= right) {
     let  mid = (left + right) >> 1;
      if (arr[mid] < target) {
        left = mid + 1;
      } else if (arr[mid] > target) {
        right = mid - 1;
      } else {
        return mid;
      }
    }
    return left;
  }
  console.log(f([1, 3, 6, 6,7, 8, 12], 7))
 

js原生实现

1.实现new

new一个对象的过程:

  1. 新建一个对象A
  2. 将该对象A的原型指向构造函数的原型
  3. 将构造函数的this指向这个对象A,执行构造函数
  4. 返回构造函数返回的对象,或者返回该对象A
function myNew(constructor, ...args) {
    // 将构造函数的原型绑定到新创的对象实例上
    const obj = Object.create(constructor.prototype);
    //  执行构造函数
    const res = con.call(obj, ...args);
    return res && res instanceof Object ? res : obj;
}

function myNew1(constructor, ...args) {
    const obj = {};
    // 这句是影响性能的。参考MDN
    obj.__proto__ = constructor.prototype;
    const res = constructor.call(obj, ...args);
    return res && res instanceof Object ? res : obj;
}
// 摘自MDN:
// 由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,
// 更改对象的[[Prototype]]在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。
// 其在更改继承的性能上的影响是微妙而又广泛的,
// 这不仅仅限于 obj.__proto__ = ...语句上的时间花费,
// 而且可能会延伸到任何代码,那些可以访问任何[[Prototype]]已被更改的对象的代码。
// 如果你关心性能,你应该避免设置一个对象的[[Prototype]]。
// 相反,你应该使用 Object.create()来创建带有你想要的[[Prototype]]的新对象。

2.实现instanceOf

A instanceOf B: 判断A的原型链上是否有B

function instanceOf(left, right) {
    let leftVal = left.__proto__;
    let rightVal = right.prototype;
    while (true) {
        if (leftVal === null) return false;
        if (rightVal === rightVal) return true;
        leftVal = leftVal.__proto__;
    }
}

3.实现数组reduce

Array.prototype.myReduce = function (callback, initialValue) {
    let result = initialValue ? initialValue : this[0];
    for (let i = initialValue ? 0 : 1; i < this.length; i++) {
        result = callback(result, this[i], i, this);
    }
    return result;
}

console.log([1, 2, 3, 4, 5].myReduce((a, b) => a + b,10));

4.reduce实现map

Array.prototype.myMap = function (fn, thisArg) {
    return (thisArg || this).reduce((pre, cur, i, arr) => {
        pre.push(fn.call(thisArg, cur, i, arr))
        return pre;
    }, [])
}

console.log([1, 2, 3, 4].myMap((item, i, arr) => {
    return item * 2;
}, [2, 3, 4]))

5. 实现深拷贝(解决循环引用)

const deepClone = (obj, cached = new Set()) => {
    const copy = Array.isArray(obj) ? [] : {};
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            // 如果是对象,并且缓存中没有改对象,才进行深拷贝
            if (typeof obj[key] === 'object' && !cached.has(obj[key])) {
                // 把对象加入缓存中
                cached.add(obj[key]);
                copy[key] = deepClone(obj[key], cached);
            } else {
                copy[key] = obj[key];
            }
        }
    }
    return copy;
}

const obj = {
    name: 'william',
    age: 18,
    address: {
        city: 'beijing',
        street: 'xizhimen'
    },
    hobbies: ['basketball', 'football'],
}
obj.family = obj;

console.log(obj);
const copy = deepClone(obj);
console.log(copy);
copy.name = '梨花';
console.log(obj);
console.log(copy);


6.防抖节流

//防抖:一段时间内触发,重新计时,只执行最后一次。例子:百度搜索的联想
function debounce(fn, timeout) {
    let timer = null;
    return (...args) => {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, timeout);
    }
}
// 节流:一段时间只执行一次,稀释作用,例子:onresize等
// 时间戳版
function throttle(fn, timeout) {
    let pre = 0;
    return (...args) => {
        let cur = new Date().getTime();
        if (cur - pre > timeout) {
            fn.apply(this, args);
            pre = cur;
        }
    }
}
// 定时器版
function throttle1(fn, delay) {
    let timer = null;
    return (...args) => {
        if (!timer) {
            timer = setTimeout(() => {
                fn(...args);
                timer = null;
            }, delay);
        }
    }
}
// 时间戳+定时器 TODO

7.实现数组flat扁平化

Array.prototype.myFlat = function (deep = Infinity) {
    const arr = this;
    if (deep === 0) {
        return arr;
    }
    return arr.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? cur.myFlat(--deep) : cur);
    }, [])
}
console.log([[[[1], 2], 3], 8].myFlat());

8.实现call,apply,bind

使用Symbol,你可以理解为防止覆盖掉context上的其他方法

Function.prototype.myCall = function (context, ...args) {
    context = typeof context === 'object' ? context : window;
    const key = Symbol('myCall');
    // this代表的就是执行的函数
    context[key] = this;
    return context[key](...args);
}
Function.prototype.myApply = function (context, args) {
    context = typeof context === 'object' ? context : window;
    const key = Symbol('myApply');
    context[key] = this;
    return context[key](...args);
}
Function.prototype.myBind = function (context, ...args) {
    context = typeof context === 'object' ? context : window;
    const key = Symbol('myBind');
    context[key] = this;
    return (...args1) => { 
        context[key](...args,...args1);
     };
}

9.TODO