小红书里种来的美团一面

2,472 阅读8分钟

我是All in AI的前端老兵shunwuyu,带班攻战互联网大厂面试十年了。春招、秋招,都感觉与同学们一样热血沸腾。我以同学的口吻写了面试系列,希望对大厂入职大厂有所帮助。

如果您喜欢我的面试系列,欢迎点赞,收藏,也欢迎加我V(shunwuyu), 入群一起交流。

前言

能在小红书里找到美团面试,大家惊愕了吧。我这个面试来自美团一位实习生的文章,说他要去上课了,可以内推面他的坑位。没想一投,面试就安排到位了。 大二升大三的第一次大厂面试,不求黄袍加身,但求面试官温柔待我。反正远程实习干着,大厂都面面。

面试题截图

47319b19a233f7ca8e05aa8491f8857.png

自我介绍

可以看介绍公式

判断数组的方法

  • Array.isArray
  • instanceof 操作符
    let arr = [1, 2, 3]; 
    console.log(arr instanceof Array); // true
  • Object.prototype.toString.call()
  • 使用 typeof 和 length 属性
function isArrayLike(obj) { 
    return obj != null && typeof obj === 'object' && 'length' in obj; 
}
let arr = [1, 2, 3]; 
console.log(isArrayLike(arr)); // true

小结: 普通考题,但能展现js基础能力,答题主要在于全面。

箭头函数和普通函数的区别

  • this 绑定:普通函数的 this 是动态绑定的,箭头函数的 this 是词法绑定的。
  • 构造函数:普通函数可以作为构造函数使用,箭头函数不能。
  • arguments 对象:普通函数有 arguments 对象,箭头函数没有,但可以通过 rest 参数替代。
  • 函数体简写:箭头函数支持简写形式,普通函数需要完整的函数体。
  • 适用场景:根据具体需求选择合适的函数类型。

js中的异步方法

JavaScript 是一种单线程语言,这意味着它在同一时间只能执行一个任务。然而,JavaScript 通过异步编程模型来处理耗时的操作,如网络请求、文件读写等,从而避免阻塞主线程。

JavaScript 的异步方法主要有三种:回调函数、Promise 和 Async/Await。

  • 回调函数callback

    回调函数是最基本的异步处理方式。它通过将一个函数作为参数传递给另一个函数,在某个操作完成后调用这个回调函数。

    优点:简单易懂。 缺点:(Callback Hell):多层嵌套的回调函数会导致代码难以阅读和维护。

  • Promise

    Promise 是一种更现代的异步处理方式,它代表一个异步操作的最终完成(或失败)及其结果值。Promise 有三种状态:pending(进行中)、fulfilled(已完成)和 rejected(已失败)。

    优点:更好的错误处理、避免回调地狱。 缺点:语法相对复杂。

  • Async/Await

    Async/Await 是基于 Promise 的语法糖,使异步代码看起来更像同步代码,提高了代码的可读性和可维护性。

    优点:代码更简洁、易读,错误处理更直观。

async/await原理

  1. 概念async/await 是基于 Promise 的语法糖,目的是简化异步代码的书写,避免回调地狱,提供一种更类似于同步代码的异步编程体验。
  2. async 函数:使用 async 关键字定义的函数总是返回一个 Promise 对象。即使函数内部没有明确返回 Promise,JavaScript 也会将返回值自动封装为 Promise.resolve()
  3. await 表达式await 用于暂停 async 函数的执行,等待一个 Promise 解析。await 后面必须是一个 Promise 对象或可转化为 Promise 的值。函数会在 Promise 解析完成后恢复执行,并返回 Promise 的结果。如果 Promise 被拒绝,await 会抛出异常,可以使用 try/catch 来捕获。
  4. 事件循环async/await 并不会阻塞主线程,而是将异步操作推入事件循环的微任务队列(microtask queue)。当同步代码执行完毕后,JavaScript 引擎会检查微任务队列并执行相应的回调,这就是为什么 await 不会阻塞整个程序。

总结:async/await 是基于 Promise 实现的语法糖,利用事件循环将异步任务放入微任务队列,使得异步代码看起来像同步代码,极大简化了代码的可读性。

事件冒泡机制

  1. 定义:事件冒泡是浏览器处理 DOM 事件的一种机制。当一个事件发生在一个元素上时,它首先会被该元素处理,然后事件会按照父子层级向上(从目标元素到根元素 document)依次触发父级元素的相同事件监听器,直到到达最顶层。

  2. 事件传播阶段:事件传播分为三个阶段:

    • 捕获阶段:事件从根元素向目标元素进行传播,但此时监听器并未触发。
    • 目标阶段:事件到达目标元素,并触发目标元素的事件监听器。
    • 冒泡阶段:事件从目标元素向上冒泡到其父级元素,依次触发所有祖先元素的事件监听器,直至根元素。
  3. 默认行为:事件冒泡是浏览器的默认行为,但可以通过 event.stopPropagation() 来阻止事件进一步冒泡。

  4. 实际应用

    • 事件委托:利用事件冒泡,可以在父级元素上绑定事件监听器,通过捕获冒泡中的事件来处理子元素的事件,提升性能,尤其适用于动态内容。
    • 阻止冒泡:某些场景下,出于业务逻辑考虑,可能需要使用 event.stopPropagation() 来阻止事件冒泡,避免不必要的事件触发。
  5. 通个addEventListener的第三个参数,可以控制时间在哪个阶段触发。

addEventListener 的第三个参数可以是一个布尔值或一个对象,它的作用是指定事件监听器的选项,主要包括以下几种情况:

  1. capture(布尔值):

    • 当第三个参数为 true 时,表示事件监听器在捕获阶段触发(即从父元素往目标元素传播的过程中触发)。
    • 当为 false(默认值)时,事件监听器在冒泡阶段触发(即从目标元素往父元素传播的过程中触发)。
  2. options(对象): 第三个参数也可以是一个包含以下属性的对象,用于更精细地控制事件监听器的行为:

    • capture: (同上)控制是否在捕获阶段触发。
    • once: 当为 true 时,监听器会在触发一次后自动移除,不再响应后续的事件。
    • passive: 当为 true 时,表示监听器不会调用 preventDefault(),用于优化滚动性能,避免阻塞页面滚动。

事件循环

可以查看事件循环

解构url

直接上 死磕 36 个 JS 手写题(搞懂后,提升真的大)作为一个程序员,代码能力毋庸置疑是非常非常重要的,就像现在为什么大厂面 - 掘金 (juejin.cn)

vue 生命周期

生命周期demo

  • vue2 生命周期
  • vue3 生命周期
  • vue3 composition api 生命周期
  • vue3 keep alive 两个生命周期
  • react useEffect

请求数据应该放在哪个生命周期?为什么?

在 Vue 3 中,请求数据通常放在 onMounted 生命周期钩子中。原因是 onMounted 会在组件的 DOM 渲染完成后调用,这时可以安全地进行异步请求并更新组件的状态,而不会阻塞页面的初次渲染。

聊聊vue响应式数据的proxy

在 Vue 3 中,响应式系统采用了 Proxy 代替 Vue 2 中的 Object.defineProperty,以更灵活、强大的方式实现响应式数据。

  • 全面拦截Proxy 能拦截对对象的13种操作,包括读取属性、设置属性、has、删除属性(deleteProperty)、拦截返回对象的键名列表的操作(ownKeys)、获取属性描述符(getOwnPropertyDescriptor)、定义或修改属性描述符(defineProperty)、setPrototypeOf(设置对象原型操作)、获取对象原型(getPrototypeOf)、拦截 new 操作符,即实例化对象操作等,不仅限于现有的属性,还能处理动态添加或删除的属性,甚至数组的变化。而defineProperty只能做gettersetter, 可读、可写、可配置、可迭代等操作。
let handler = { ownKeys(target) { console.log('Listing keys'); return Object.keys(target); } };
let handler = {
  getOwnPropertyDescriptor(target, prop) {
    console.log(`Getting descriptor for ${prop}`);
    return Object.getOwnPropertyDescriptor(target, prop);
  }
};

  • 深度监听:对于嵌套的对象,必须递归地遍历所有属性,这会导致性能问题。 Object.defineProperty 需要递归遍历对象的所有嵌套属性,将其转换为响应式。这意味着在初始化时会有较高的性能开销。

    Proxy 可以自动代理对象及其嵌套属性,无需递归遍历。这使得初始化时的性能开销较低。

    Proxy 通过动态代理和透明代理的方式,避免了在初始化时递归遍历所有嵌套属性的需要。这种懒加载的方式不仅提高了初始化性能,还使得响应式系统更加灵活和强大。在 Vue 3 中,Proxy 的使用正是基于这些优点,使得响应式系统的实现更加高效和简洁。

function lazyReactive(target) {
  const handler = {
    get(target, key, receiver) {
      const value = Reflect.get(target, key, receiver);
      if (typeof value === 'object' && value !== null) {
        // 懒代理:只有在访问时才代理嵌套对象
        return lazyReactive(value);
      }
      return value;
    },
    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver);
      const result = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) {
        // 触发视图更新
        updateView();
      }
      return result;
    }
  };

  return new Proxy(target, handler);
}

// 模拟视图更新
function updateView() {
  console.log('View updated');
}

const state = lazyReactive({
  count: 0,
  nested: {
    name: 'Vue 3',
    details: {
      version: '3.0.0'
    }
  },
  array: [1, 2, 3]
});

console.log('Initial state:', state);

state.nested.name = 'Vue 3.1'; // 懒代理,只在访问 nested 时代理
console.log('Updated nested.name:', state.nested.name);

state.nested.details.version = '3.1.0'; // 懒代理,只在访问 nested.details 时代理
console.log('Updated nested.details.version:', state.nested.details.version);

console.log('Final state:', state);
  • 新增属性

    defineProperty 不会对新增属性自动代理,proxy 会

function reactive(target) { 
    return new Proxy(target, { 
        get(target, key, receiver) { 
            return Reflect.get(target, key, receiver); 
        }, 
        set(target, key, value, receiver) { 
            const result = Reflect.set(target, key, value, receiver); 
            console.log(`${key} changed to ${value}`); 
            return result; } }); } 
      const state = reactive({ count: 0 }); 
      state.count = 1; // 输出: count changed to 1 console.log(state.count); // 1 // 新增属性 state.newProp = 'new value'; console.log(state.newProp); // 'new value' // 新增属性会触发响应式 state.newProp = 'updated value'; // 输出: newProp changed to updated value console.log(state.newProp); // 'updated value'

有一串只有"["、"]"、"{"、"}"、"("、")" 判断是否是合法

function isValid(s) {
  // 用于存储左括号的栈
  const stack = [];
  
  // 括号对映射
  const mapping = {
    ')': '(',
    '}': '{',
    ']': '['
  };
  
  // 遍历字符串中的每个字符
  for (const char of s) {
    if (char in mapping) {
      // 如果是右括号,检查栈顶是否有匹配的左括号 #只是一个占位符
      const topElement = stack.length === 0 ? '#' : stack.pop();
      if (mapping[char] !== topElement) {
        return false;
      }
    } else {
      // 如果是左括号,将其推入栈中
      stack.push(char);
    }
  }
  
  // 检查栈是否为空(所有的左括号都应该匹配)
  return stack.length === 0;
}

// 示例用法
console.log(isValid("()")); // 输出 true
console.log(isValid("()[]{}")); // 输出 true
console.log(isValid("(]")); // 输出 false
console.log(isValid("([)]")); // 输出 false
console.log(isValid("{[]}")); // 输出 true

总结

最近我们挺多同学在小红书上找到一些面试线索的, 还有远程实习了, 大家也可以试试。