JS面试题

189 阅读5分钟

 柯里化

题目描述:柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。

简述:把多个参数变成传入单一参数的函数

function currying(fn,...args){
    const length=fn.length //原函数的参数个数
    let arrArgs = [...args]
    const res = (...newArgs)=>{
        allArgs = [...arrArgs,...newArgs] //收集参数
        if(allArgs.length === length){ //如果搜集到的参数和原函数一样多,则执行原函数,否则继续搜集参数
            return fn(...allArgs)
        }else{
            return res
        }
    }
    return res
}
//把参数收集起来最终放到原函数里
const add = (a, b, c) => a + b + c;
const a = currying(add, 1);
console.log(a(2,3))

数组扁平化

题目描述:实现一个方法使多维数组变成一维数组

简述:利用数组的reduce方法。递归调用

function flatter(arr) {
  if (!arr.length) return;
  return arr.reduce(
    (pre, cur) =>
      Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur],
    []
  );
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

寄生组合继承

题目描述:实现一个你认为不错的 js 继承方式

Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

function Parent(name) {
  this.name = name;
  this.say = () => {
    console.log(111);
  };
}
Parent.prototype.play = () => {
  console.log(222);
};
function Children(name) {
  Parent.call(this);
  this.name = name;
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
// let child = new Children("111");
// // console.log(child.name);
// // child.say();
// // child.play();

实现有并行限制的 Promise 调度器

题目描述:JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个

 addTask(1000,"1");
 addTask(500,"2");
 addTask(300,"3");
 addTask(400,"4");
 的输出顺序是:2 3 1 4

 整个的完整执行流程:

一开始12两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4

实现代码如下:

class Scheduler {
  constructor(limit) {
    this.queue = [];//执行的数组队列
    this.maxCount = limit;//同时执行的最大数量
    this.runCounts = 0;//正在执行的数量
  }
  add(time, order) {//添加时间,和输出值
    const promiseCreator = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(order);
          resolve();
        }, time);
      });
    };
    this.queue.push(promiseCreator);//把要执行的函数推到执行队列里。
  }
  taskStart() {
    for (let i = 0; i < this.maxCount; i++) {//初次,取出可以执行的数量开始执行。
      this.request();
    }
  }
  request() {
    if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
      return;
    }
    this.runCounts++;
    //下面是取取出队列的第一个执行,利用返回的promise.then递归调用下一个
    //shift()方法移除数组的第一项 shift()方法会改变数组的长度。
    //shift方法的返回值是被移除的项目。shift()方法会改变原始数组。
    //如需删除数组的最后一项,请使用 pop()方法。
    this.queue
      .shift()()
      .then(() => {
        this.runCounts--;
        this.request();
      });
  }
}
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
  scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();

new 操作符

题目描述:手写 new 操作符实现

function myNew(fn, ...args) {
  let obj = Object.create(fn.prototype);
  let res = fn.call(obj, ...args);
  if (res && (typeof res === "object" || typeof res === "function")) {
    return res;
  }
  return obj;
}
用法如下:
// // function Person(name, age) {
// //   this.name = name;
// //   this.age = age;
// // }
// // Person.prototype.say = function() {
// //   console.log(this.age);
// // };
// // let p1 = myNew(Person, "lihua", 18);
// // console.log(p1.name);
// // console.log(p1);
// // p1.say();

深拷贝(考虑到复制 Symbol 类型)

Symbol 一种新的原始数据类型 Symbol ,表示独一无二的值。最大的用法是用来定义对象的唯一属性名。 ES6 数据类型除了 Number 、 String 、 Boolean 、 Object、 null 和 undefined ,还新增了 Symbol

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。
Reflect 对象提供了以下静态方法,这些方法与proxy handler methods (en-US)的命名相同.

其中的一些方法与 Object相同, 尽管二者之间存在 某些细微上的差别 .

function isObject(val) {
  return typeof val === "object" && val !== null;
}

function deepClone(obj, hash = new WeakMap()) {
  if (!isObject(obj)) return obj;
  if (hash.has(obj)) {//我总觉得这个没用
    return hash.get(obj);
  }
  let target = Array.isArray(obj) ? [] : {};
  hash.set(obj, target);
  Reflect.ownKeys(obj).forEach((item) => {
    if (isObject(obj[item])) {
      target[item] = deepClone(obj[item], hash);
    } else {
      target[item] = obj[item];
    }
  });

  return target;
}

// var obj1 = {
// a:1,
// b:{a:2}
// };
// var obj2 = deepClone(obj1);
// console.log(obj1);

instanceof

题目描述:手写 instanceof 操作符实现

function myInstanceof(left, right) {
  while (true) {//return会停止while循环
    if (left === null) {
      return false;
    }
    if (left.__proto__ === right.prototype) {
      return true;
    }
    left = left.__proto__;
  }
}

冒泡排序--时间复杂度 n^2

要点 两层循环 内层把最大的移动到到最右边,最后一个最大就不比了依次减小 外层把每个都比较一遍

function bubbleSort(arr){
    const len = arr.length  //3
    for(let i= 0;i<len;i++){ //0 1
        for(let j=0;j<len-1-i;j++){ //0 231 1 213 //0 123 1 123
            if(arr[j]>arr[j+1]){
                [arr[j],arr[j+1]]=[arr[j+1],arr[j]]
            }
        }
    }
    return arr
}
console.log(bubbleSort([5,4,3,2,1]));

选择排序--时间复杂度 n^2

选择排序 选出最小的放在第一个位置上依次查询最小的
要点 两层循环,外层比较到每一个,取剩下的每一个和假设最小的比较,取到最小的下标值

function selectSort(arr){
    const len = arr.length
    let minIndex
    for(let i=0;i<len-1;i++){
        minIndex = i
        for (let j=i;j<len;j++){
            if(arr[j]<arr[minIndex]){
                minIndex=j
            }
        }
    
        if(minIndex !==i){
            [arr[i],arr[minIndex]]=[arr[minIndex],arr[i]]
        }
    }
    return arr
}
console.log(selectSort([5,4,3,2,1]));

插入排序--时间复杂度 n^2

插入排序 把大的向后挪,把小的
要点 外层循环每一个,内层循环当前位置前的,把这个放到比他大的前面 互换位置大的依次n->n->n->n,小的依次n<-n<-n<-

function insertSort(arr){
    for (let i=1;i<arr.length;i++){
        let j=i
        let target = arr[j]
        while(j>0&& arr[j-1]>target){
            arr[j] = arr[j-1]
            j--
        }
        arr[j]=target
    }//1 45321  2 43521 34521 3 34251  32451 23451
    return arr
}
console.log(insertSort([5,3,9,2,1,4]));

快排--时间复杂度 nlogn~ n^2 之间

要点 取出一个值,比它大的放右边,比它小的放左边,还有和他相等的的,注意不要重复放置,要去掉当前下标的。

function quickSort(arr){
    if(arr.length<2){
        return arr
    }
    const cur = arr[arr.length-1]
    console.log(cur);
    const left = arr.filter((v,i)=>v<=cur&& i!==arr.length-1)
    const right = arr.filter(v=>v>cur)
    return [...quickSort(left),cur,...quickSort(right)]
}
// console.log(quickSort([1,2,3,4,5,6]));
console.log(quickSort([6,5,4,3,2,1]));
// console.log(quickSort([1,2,3,6,5,4]));
// 222222
function quickSort(arr){
    if(arr.length<2){
        return arr
    }
    const cur = arr[0]
    console.log(cur);
    const left = arr.filter((v,i)=>v<=cur&& i!==0)
    const right = arr.filter(v=>v>cur)
    return [...quickSort(left),cur,...quickSort(right)]
}
console.log(quickSort([6,5,4,3,2,1]));
// console.log(quickSort([1,2,3,4,5,6]));

归并排序--时间复杂度 nlog(n)

本文摘自juejin.cn/post/696871… 作者写的精炼,自己每次看过就会忘记,自己添加了系列注释帮助理解