返回

JS 戏精面试题,你都见过吗?

前端

前言

在 JS 面试中,经常会看到一些简单而又沙雕的题目,这些题目包含一些陷阱,但这些在我们规范的编码下或者业务中基本不会出现。有些面试官就是这样,不专注于制定代码的标准和规范上,却用不规范的代码去检验别人是否细心。这魔幻的世界就是一个攀比的世界,你在意什么,它就爱考什么。不过,只要我们具备扎实的基础知识,这些题目也就迎刃而解了。

戏精题目一:比较两个值是否相等

const a = '1';
const b = 1;
console.log(a == b); // true
console.log(a === b); // false

陷阱在于 ===== 的区别。== 仅比较值是否相等,而 === 比较值和类型是否都相等。因此,a == btrue,而 a === bfalse

戏精题目二:数组去重

const arr = [1, 2, 3, 1, 2, 3];
const newArr = [...new Set(arr)];
console.log(newArr); // [1, 2, 3]

陷阱在于 Set 对象的特性。Set 对象会自动去除重复元素,因此 new Set(arr) 会生成一个只包含唯一元素的新数组。而 ... 运算符可以将数组展开为一组参数,因此 [...new Set(arr)] 会生成一个新的数组,其中包含 Set 对象中所有的元素。

戏精题目三:函数柯里化

const add = (a, b) => a + b;
const add10 = add.bind(null, 10);
console.log(add10(20)); // 30

陷阱在于 bind() 方法的用法。bind() 方法可以将函数的第一个参数固定,返回一个新的函数。因此,add10 函数实际上是 add 函数的柯里化版本,第一个参数固定为 10。

戏精题目四:对象深拷贝

const obj = {
  name: 'John',
  age: 30,
  address: {
    street: '123 Main Street',
    city: 'New York',
    state: 'NY'
  }
};

const newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
// {
//   name: 'John',
//   age: 30,
//   address: {
//     street: '123 Main Street',
//     city: 'New York',
//     state: 'NY'
//   }
// }

陷阱在于 JSON.parse()JSON.stringify() 方法的使用。这两个方法可以将对象转换为 JSON 字符串,然后又可以将 JSON 字符串转换为对象。这样就可以实现对象的深拷贝,因为 JSON 字符串中包含了对象的所有属性和值。

戏精题目五:发布-订阅模式

class PubSub {
  constructor() {
    this.subscribers = {};
  }

  subscribe(event, callback) {
    if (!this.subscribers[event]) {
      this.subscribers[event] = [];
    }

    this.subscribers[event].push(callback);
  }

  publish(event, data) {
    if (this.subscribers[event]) {
      this.subscribers[event].forEach(callback => callback(data));
    }
  }
}

const pubSub = new PubSub();

pubSub.subscribe('message', (data) => {
  console.log(`Received message: ${data}`);
});

pubSub.publish('message', 'Hello world!'); // Received message: Hello world!

陷阱在于发布-订阅模式的实现。发布-订阅模式是一种设计模式,允许对象之间进行通信,而无需它们直接相互引用。在这个例子中,PubSub 类实现了发布-订阅模式,允许对象订阅事件并接收发布到该事件的数据。

戏精题目六:防抖和节流

const debounce = (func, delay) => {
  let timeout;

  return (...args) => {
    clearTimeout(timeout);

    timeout = setTimeout(() => {
      func(...args);
    }, delay);
  };
};

const throttle = (func, delay) => {
  let lastTime = 0;

  return (...args) => {
    const now = Date.now();

    if (now - lastTime >= delay) {
      func(...args);
      lastTime = now;
    }
  };
};

const handleClick = (e) => {
  console.log('Button clicked');
};

const debouncedHandleClick = debounce(handleClick, 500);
const throttledHandleClick = throttle(handleClick, 500);

document.querySelector('button').addEventListener('click', debouncedHandleClick);
document.querySelector('button').addEventListener('click', throttledHandleClick);

陷阱在于防抖和节流的实现。防抖和节流都是函数装饰器,可以控制函数的执行频率。防抖会延迟函数的执行,直到函数停止被调用一段时间后才执行。节流会限制函数在一定时间内只能执行一次。在这个例子中,debounce() 函数实现了防抖,而 throttle() 函数实现了节流。

戏精题目七:实现一个链表

class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
  }
}

class LinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
  }

  add(data) {
    const node = new Node(data);

    if (!this.head) {
      this.head = node;
      this.tail = node;
    } else {
      this.tail.next = node;
      this.tail = node;
    }
  }

  remove(data) {
    if (!this.head) {
      return;
    }

    if (this.head.data === data) {
      this.head = this.head.next;
      if (!this.head) {
        this.tail = null;
      }
      return;
    }

    let current = this.head;
    let previous = null;

    while (current) {
      if (current.data === data) {
        previous.next = current.next;
        if (!current.next) {
          this.tail = previous;
        }
        return;
      }

      previous = current;
      current = current.next;
    }
  }

  print() {
    let current = this.head;

    while (current) {
      console.log(current.data);
      current = current.next;
    }
  }
}

const linkedList = new LinkedList();

linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
linkedList.add(4);
linkedList.add(5);

linkedList.print(); // 1 2 3 4 5

linkedList.remove(3);

linkedList.print(); // 1 2 4 5

陷阱在于链表的实现。链表是一种数据结构,它由一组节点组成,每个节点都包含一个数据值和一个指向下一个节点的指针。在这个例子中,Node 类表示链表中的节点,而 LinkedList 类表示链表本身。add() 方法用于向链表中添加一个新的节点,remove() 方法用于从链表中删除一个节点,print() 方法用于打印链表中的所有节点。

结语

以上 7 个 JS 面试题只是冰山一角,还有很多类似的题目可能出现在面试中。这些题目看似简单,但往往包含一些陷阱,旨在检验面试者的细心程度和对 JS 语言的掌握程度。因此,在准备 JS 面试时,除了掌握基础知识外,还需要对这些戏精题目有所了解,这样才能在面试中游刃有余。