【前端面经 | 美团】2024 面试复盘第五弹——美团

295 阅读10分钟

一面

1. 工作台业务迭代 多个工作台时 若越来越大 这里如何优化?

  1. 拆分组件:拆分成功能组件和业务组件

    1. 针对业务组件:拆分成多个 小的 可复用的组件 每个组件负责一部分逻辑 使代码更加模块化
    // 原始的大组件
    function Workbench({ id, ...props }) {
      // 复杂的业务逻辑和视图
    }
    
    // 拆分成多个小组件
    const Workbench = ({ id, ...props }) => {
      switch (id) {
        case "workbench1":
          return <Workbench1 {...props} />;
        case "workbench2":
          return <Workbench2 {...props} />;
        // 其他工作台
        default:
          return <DefaultWorkbench {...props} />;
      }
    };
    // 专门处理工作台1的逻辑和视图
    const Workbench1 = ({ ...props }) => {};
    // // 默认工作台逻辑和视图
    const DefaultWorkbench = ({ ...props }) => {};
    
  2. 使用高阶组件:

    1. 如果有多个工作台之间有共享的逻辑 使用高阶组件提取共享逻辑
    const withCommonLogic = (WrappedComponent) => (props) => {
      const commonData = someCommonLogic();
      return <WrappedComponent {...props} commonData={commonData} />;
    };
    
    const Workbench1 = withCommonLogic(({ commonData, ...props }) => (
      <div>Workbench 1 with common data: {commonData}</div>
    ));
    
  3. 使用 Context API

    1. 如果多个工作台之间需要共享状态或数据 使用 Context
    const WorkbenchContext = React.createContext();
    
    const WorkbenchProvider = ({ children }) => {
      const [sharedState, setSharedState] = useState({});
      return (
        <WorkbenchContext.Provider value={{ sharedState, setSharedState }}>
          {children}
        </WorkbenchContext.Provider>
      );
    };
    
    const Workbench1 = () => {
      const { sharedState } = useContext(WorkbenchContext);
      return (
        <div>
          Workbench 1 with shared state: {JSON.stringify(sharedState)}
        </div>
      );
    };
    
  4. 代码分割+懒加载

    1. 使用 React 的动态导入(import())来实现代码分割和懒加载,以减少初始加载时间
    const Workbench = ({ id, ...props }) => {
      let Component;
      switch (id) {
        case "workbench1":
          Component = React.lazy(() => import("./Workbench1"));
          break;
        case "workbench2":
          Component = React.lazy(() => import("./Workbench2"));
          break;
        // 其他工作台
        default:
          Component = React.lazy(() => import("./DefaultWorkbench"));
      }
    
      return (
        <React.Suspense fallback={<div>Loading...</div>}>
          <Component {...props} />
        </React.Suspense>
      );
    };
    

2. 多个工作台 有个性化差异 如何更好的处理差异点?

  1. 组件化和模块化:将每个工作台的特有部分拆分成独立的组件或模块

    // 共享的基础组件
    const BaseWorkbench = ({ children, ...props }) => (
      <div className="workbench">
        {/* 基础布局和共享功能 */}
        {children}
      </div>
    );
    
    // 工作台1的特有组件
    const Workbench1Content = (props) => (
      <div>Workbench 1 specific content</div>
    );
    
    // 根据ID选择相应的工作台内容
    const Workbench = ({ id, ...props }) => {
      let ContentComponent;
    
      switch (id) {
        case "workbench1":
          ContentComponent = Workbench1Content;
          break;
        default:
          ContentComponent = () => <div>Default Workbench</div>;
      }
    
      return (
        <BaseWorkbench {...props}>
          <ContentComponent {...props} />
        </BaseWorkbench>
      );
    };
    
  2. 配置驱动

    1. 使用配置对象定义每个工作台的特性:通过调整配置项实现差异化,无需修改大量代码。

      const workbenchConfig = {
        workbench1: {
          title: "Workbench 1",
          content: <Workbench1Content />,
          // 其他配置项
        },
        workbench2: {
          title: "Workbench 2",
          content: <Workbench2Content />,
          // 其他配置项
        },
        // 更多工作台
      };
      
      const Workbench = ({ id, ...props }) => {
        const config = workbenchConfig[id] || workbenchConfig.default;
      
        return (
          <BaseWorkbench {...props} title={config.title}>
            {config.content}
          </BaseWorkbench>
        );
      };
      
  3. 自定义 hook

    1. 对于多个工作台可复用的逻辑 自定义 hook
    const useWorkbenchLogic = (id) => {
      const [state, setState] = useState({});
    
      useEffect(() => {
        // 根据 id 执行不同的逻辑
        if (id === "workbench1") {
          // 工作台1的逻辑
        } else if (id === "workbench2") {
          // 工作台2的逻辑
        }
      }, [id]);
      return state;
    };
    
    const Workbench1 = (props) => {
      const state = useWorkbenchLogic("workbench1");
      return <div>Workbench 1 with state: {JSON.stringify(state)}</div>;
    };
    

3. 若后续有差异化的方案 有哪些手段可以保证不会影响之前的代码?

  1. 使用 Git 管理代码 确保每次更改都有详细的提交信息

  2. 编写单元测试

    1. 当引入新的差异化方案时 运行测试确保没有破坏之前的功能
    2. Jest 测试库
    // 测试 Workbench 组件
    // Workbench.test.js
    import React from "react";
    // render 渲染组件 screen 查找dom元素
    import { render, screen } from "@testing-library/react";
    import Workbench from "./Workbench";
    // describe 定义测试套件
    describe("Workbench Component", () => {
      // 测试用例 1: 渲染 Workbench 1 内容
      it("renders Workbench 1 content correctly 测试描述", () => {
        // 渲染 Workbench 组件,并传入 id 为 "workbench1"。
        render(<Workbench id="workbench1" />);
        // 验证在渲染的组件中是否存在指定的文本内容
        expect(
          //  React Testing Library 提供的一个查询方法 用于查找页面中包含特定文本的元素
          screen.getByText("Workbench 1 specific content")
          // toBeInTheDocument 是一个 Jest 匹配器 用于断言某个元素存在于文档中
        ).toBeInTheDocument();
      });
    
      it("renders default content when no valid id is provided", () => {
        render(<Workbench id="unknown" />);
        expect(screen.getByText("Default Workbench")).toBeInTheDocument();
      });
    });
    

4. 数据类型+数据类型的检测方式?

  1. 基础数据类型-> 传值 :STring Number Boolean Null Undefined 保存在数据栈中

  2. 引用数据类型-> 传址:Object Array Function 键值对

    引用数据类型保存在堆内存中,并在数据栈中保存指向这个堆的地址

  3. 检测方法

    1. typeof 数据 => 返回类型

      1. 简单数据类型:不能判断 null 会返回 object
      2. 复杂数据类型:仅函数可以判断 其余均返回 object
        // 简单数据类型
        console.log(typeof 123); // number
        console.log(typeof "小红"); // string
        console.log(typeof true); // boolean
        console.log(typeof undefined); // undefined
        console.log(typeof null); // object 空对象
        
        // 复杂数据类型
        console.log(typeof []); // object
        console.log(typeof {}); // object
        console.log(typeof function () {}); // function
        
    2. 数据 instanceof 类型 => 返回布尔值

      1. 不能判断简单数据类型和 Function 常用:Array、Object

      2. 如果 instanceof 左边的数据,是通过右边的构造函数构造出来的,则返回 true,否则返回 false

      3. 用途: 用于判断复杂数据类型,不能正确判断基础数据类型

        // 1 对于简单数据类型会报错
           '123' instanceof String // false
        
           // 2 复杂数据类型,常用Array Object
           [1,2,3] instanceof Array // true
           {} instanceof Array // false
           function(){} instanceof Object // true
        
      4. instanceof 只有同一个全局 window 才会返回 true

        setTimeout((_) => {
          // 拿到外部html(frame方式引入)嵌套过来的数组,
          // console.log('window.frames[0].iArr', window.frames[0].iArr) // 存在跨域问题 报错 不报错时可以得到[]
          // console.log('外部html引入的数据类型判断', window.frames[0].iArr instanceof Array) // false
        }, 100);
        
    3. 数据.constructor => 返回 ƒ 数据类型() { [native code] }

      1. 不能判断 nullundefined

      2. 用途:主要用于构造函数的判断,也可用于判断基本数据类型和引用数据类型 (null 和 undefined 除外)

      3. 用于构造函数时: 对于构造函数 返回值为 true/false : 实例对象.constructor===构造函数 主要用于判断构造实例是否属于某个构造器-构造出来的

        const num = 1,
          str = "zs",
          boo = true,
          nul = null,
          und = undefined,
          obj = {},
          arr = [],
          fn = function () {};
        // 简单数据类型
        console.log(num.constructor); // ƒ Number() { [native code] }
        console.log(str.constructor); // ƒ String() { [native code] }
        console.log(boo.constructor); // ƒ Boolean() { [native code] }
        console.log(nul.constructor); // 报错
        console.log(und.constructor); // 报错
        
        // 复杂数据类型
        console.log(obj.constructor); // ƒ Object() { [native code] }
        console.log(arr.constructor); // ƒ Array() { [native code] }
        console.log(fn.constructor); // ƒ Function() { [native code] }
        
    4. Object.prototype.toString.call(数据) => 返回[object 数据类型]

      1. 原理:每一个继承的 Object 对象都有 toString 方法,如果 toString 方法没有改写,会返回[object 类型]字符串,但是除了 Object 类型外、数组、函数、基本数据类型等若直接.toString(),会返回字符串。因此需要使用.call 或.apply 改变 toString 的执行上下文(改变 this 指向)。
      2. 为什么需要调用 call:因为该数据的 toString 方法被改写,通过 call 方法,将 this 指向该数据,让该数据调用 Object 上的 toString 方法,用以判断当前数据的类型。
        // 简单数据类型
        Object.prototype.toString.call(1); // [object Number]
        Object.prototype.toString.call("小红"); // [object String]
        Object.prototype.toString.call(true); // [object Boolean]
        Object.prototype.toString.call(undefined); // [object Undefined]
        Object.prototype.toString.call(null); // [object Null]
        
        // 复杂数据类型
        Object.prototype.toString.call([1, 2, 3]); // [object Array]
        Object.prototype.toString.call({ age: 18 }); // [object Object]
        Object.prototype.toString.call(function () {}); // [object Function]
        

5. 有一个对象 A 判断 A 是否属于某一个类 B 如何判断?

  1. A instanceof B 返回 true 或 false
  2. A.constructor === B 返回 true 或 false [A.constructor 输出 function B() { ... }]

6. const 对于对象属性是否能修改?

对象内部的属性或元素可以被修改

7. 箭头函数和普通函数的区别?

  1. this:

    1. 箭头函数:没有自己的 this 定义的时候根据上下文确定
    2. 普通函数:调用时确定
  2. 构造函数和原型

    1. 箭头函数:没有 prototype 属性 不能构造实例
    2. 普通函数:可以作为构造函数 通过 new 构造实例
  3. arguments 对象

    1. 箭头函数:没有 arguments 对象 可以通过剩余参数实现类似效果
    2. 普通函数:有内置的 arguments 对象 包含传递过来的所有参数
  4. super:

    1. 箭头函数:类的方法中 使用箭头函数 super 不可用
    2. 普通函数:类的方法中 可以通过 super 访问父类的方法和属性
    class Parent {
      constructor() {
        this.parentProperty = "I am from Parent";
      }
    
      parentMethod() {
        console.log("Parent method");
      }
    }
    
    class Child extends Parent {
      constructor() {
        super(); // This works fine in a regular constructor
        this.childProperty = "I am from Child";
      }
    
      // 箭头函数
      childMethod1 = () => {
        super.parentMethod(); // error : functions don't have their own `this`
      };
      // 普通函数
      childMethod2() {
        super.parentMethod(); // This works fine
      }
    }
    
    const childInstance = new Child();
    childInstance.childMethod1(); // TypeError: Cannot read property 'parentProperty'
    

8. 【代码】实现一个 call 改变 this 指向

Function.prototype.myCall = function(context, ...args) {
    // 如果 context 为 null 或 undefined,则默认设置为全局对象 window
    context = context;
 
    // 创建唯一属性名,避免覆盖冲突
    const fnSymbol = Symbol();
 
    // 将当前函数(即调用 myCall 的函数)赋值给 context 的一个属性
    context[fnSymbol] = this;
 
    // 使用 context 调用该函数,并传入参数
    const result = context[fnSymbol](...args);
 
    // 删除之前添加到 context 上的属性,保持干净
    delete context[fnSymbol];
 
    // 返回函数的执行结果
    return result;
};
 
// 测试
function greet(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`);
}
 
const person = {
    name: 'Alice'
};
 
greet.myCall(person, 'Hello', '!');  // 输出: Hello, Alice!

9. 【代码】实现一个 promise.all

function myPromiseAll(promises){
      return new Promise((resolve,reject)=>{
         let res=[]
         let count=0
         promises.forEach((item,index)=>{
            item.then(value=>{
               res[index]=value
               count++
               if(count===promises.length){
                  resolve(res)
               }
            },err=>{
               reject(err)
            })
         })
      })
   }

   // 使用示例
   myPromiseAll([
      Promise.resolve(1),
      Promise.resolve(2),
      Promise.resolve(3)
   ]).then((values) => {
      console.log(values); // [1, 2, 3]
   });

10. 节流和防抖的使用场景?

  1. 节流:
    1. 窗口大小调整
    2. 滚动事件
    3. 搜索建议:在输入框输入内容时 使用节流限制请求频率
  2. 防抖:
    1. 输入框搜索
    2. 按钮点击
    3. 自动保存

二面

1. 为什么选择前端?

2. 输入网址到展示页面的整个过程?

3. 解析html标签时 哪些是异步的?哪些是同步的?

  1. img 异步
  2. src link 同步

4. async 和 defer script 有什么区别?

5. 回流和重绘是什么?

6. 对于回流和重绘 开发的时候需要注意些什么?

7. 了解浏览器的缓存机制吗?

8. http 链接创建的过程?

9. 长链接和短链接的区别是什么?

  1. 长链接:在一个连接上可以连续发送多个数据包
  2. 短连接:只在需要发送数据时才去建立一个连接,数据发送完成后则断开此连接,即每次连接只完成一项业务的发送

10. http 和 https 之前有什么区别?

11. 加密的具体过程有了解吗?

12. http2.0 有了解吗?

  1. 二进制协议:HTTP 2.0 采用了二进制格式而非文本格式,有效地减少了数据传输量,提高了数据传输的可靠性和效率。
  2. 多路复用(Multiplexing):HTTP 2.0 支持在一个单一的 TCP 连接上并发地处理多个请求和响应,显著减少了建立新连接的开销和延迟。
  3. 头部压缩(Header Compression):通过使用 HPACK 算法对请求和响应头信息进行压缩,减少了数据传输量。
  4. 服务器推送(Server Push):允许服务器在客户端请求之前主动推送资源到客户端,提高了客户端的响应速度。
  5. 流量控制(Flow Control):通过流控制窗口和流控制令牌等机制,防止客户端或服务器端接收数据过快而无法处理的情况。
  6. 安全性增强:HTTP 2.0 强制使用 TLS(Transport Layer Security)加密协议,提供了更强的安全性保护。

14. 同源策略是什么?

15. 怎样判断是否是同源?

16. JSONP 和 Cors 为什么可以解决同源问题?

17. 除了这两种方式 还有什么方式可以解决同源问题?

18. vue3 和 react 有什么区别?

19. 为什么有 hooks?hooks 是干什么用的?

20. react 有哪些常见的 hooks?

21. 说一下原生的 hooks?

22. useContext 如何进行组件之间的传输?

23. zustand 和 useContext 之间有什么区别?

24. zustand 引入是全量的引入 如果想不全量的引入要怎么办?

25. react router 详细介绍一下?

26. hash 模式和 history 模式有什么区别?

27. 【代码】函数提升 输出的题?

28. 【代码】 实现 promise any 方法 和 once 方法?