一面
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 }) => {};
使用高阶组件:
- 如果有多个工作台之间有共享的逻辑 使用高阶组件提取共享逻辑
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> ));
使用 Context API
- 如果多个工作台之间需要共享状态或数据 使用 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> ); };
代码分割+懒加载
- 使用 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. 多个工作台 有个性化差异 如何更好的处理差异点?
组件化和模块化:将每个工作台的特有部分拆分成独立的组件或模块
// 共享的基础组件 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> ); };
配置驱动
使用配置对象定义每个工作台的特性:通过调整配置项实现差异化,无需修改大量代码。
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> ); };
自定义 hook
- 对于多个工作台可复用的逻辑 自定义 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. 若后续有差异化的方案 有哪些手段可以保证不会影响之前的代码?
使用 Git 管理代码 确保每次更改都有详细的提交信息
编写单元测试
- 当引入新的差异化方案时 运行测试确保没有破坏之前的功能
- 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. 数据类型+数据类型的检测方式?
基础数据类型-> 传值 :STring Number Boolean Null Undefined 保存在数据栈中
引用数据类型-> 传址:Object Array Function 键值对
引用数据类型保存在堆内存中,并在数据栈中保存指向这个堆的地址
检测方法
typeof 数据 => 返回类型
- 简单数据类型:不能判断 null 会返回 object
- 复杂数据类型:仅函数可以判断 其余均返回 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
数据 instanceof 类型 => 返回布尔值
不能判断简单数据类型和 Function
常用:Array、Object
如果 instanceof 左边的数据,是通过右边的构造函数构造出来的,则返回 true,否则返回 false
用途: 用于判断复杂数据类型,不能正确判断基础数据类型
// 1 对于简单数据类型会报错 '123' instanceof String // false // 2 复杂数据类型,常用Array Object [1,2,3] instanceof Array // true {} instanceof Array // false function(){} instanceof Object // true
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);
数据.constructor => 返回 ƒ 数据类型() { [native code] }
不能判断
null
和undefined
用途:主要用于构造函数的判断,也可用于判断基本数据类型和引用数据类型 (null 和 undefined 除外)
用于构造函数时: 对于构造函数 返回值为 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] }
Object.prototype.toString.call(数据) => 返回[object 数据类型]
- 原理:每一个继承的 Object 对象都有 toString 方法,如果 toString 方法没有改写,会返回[object 类型]字符串,但是除了 Object 类型外、数组、函数、基本数据类型等若直接.toString(),会返回字符串。因此需要使用.call 或.apply 改变 toString 的执行上下文(改变 this 指向)。
- 为什么需要调用 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 如何判断?
- A instanceof B 返回 true 或 false
- A.constructor === B 返回 true 或 false [
A.constructor 输出 function B() { ... }
]
6. const 对于对象属性是否能修改?
对象内部的属性或元素可以被修改
7. 箭头函数和普通函数的区别?
this:
- 箭头函数:没有自己的 this 定义的时候根据上下文确定
- 普通函数:调用时确定
构造函数和原型
- 箭头函数:没有 prototype 属性 不能构造实例
- 普通函数:可以作为构造函数 通过 new 构造实例
arguments 对象
- 箭头函数:没有 arguments 对象 可以通过剩余参数实现类似效果
- 普通函数:有内置的 arguments 对象 包含传递过来的所有参数
super:
- 箭头函数:类的方法中 使用箭头函数 super 不可用
- 普通函数:类的方法中 可以通过 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. 为什么选择前端?
2. 输入网址到展示页面的整个过程?
3. 解析html标签时 哪些是异步的?哪些是同步的?
- img 异步
- src link 同步
4. async 和 defer script 有什么区别?
5. 回流和重绘是什么?
6. 对于回流和重绘 开发的时候需要注意些什么?
7. 了解浏览器的缓存机制吗?
8. http 链接创建的过程?
9. 长链接和短链接的区别是什么?
- 长链接:在一个连接上可以连续发送多个数据包
- 短连接:只在需要发送数据时才去建立一个连接,数据发送完成后则断开此连接,即每次连接只完成一项业务的发送
10. http 和 https 之前有什么区别?
11. 加密的具体过程有了解吗?
12. http2.0 有了解吗?
- 二进制协议:HTTP 2.0 采用了二进制格式而非文本格式,有效地减少了数据传输量,提高了数据传输的可靠性和效率。
- 多路复用(Multiplexing):HTTP 2.0 支持在一个单一的 TCP 连接上并发地处理多个请求和响应,显著减少了建立新连接的开销和延迟。
- 头部压缩(Header Compression):通过使用 HPACK 算法对请求和响应头信息进行压缩,减少了数据传输量。
- 服务器推送(Server Push):允许服务器在客户端请求之前主动推送资源到客户端,提高了客户端的响应速度。
- 流量控制(Flow Control):通过流控制窗口和流控制令牌等机制,防止客户端或服务器端接收数据过快而无法处理的情况。
- 安全性增强:HTTP 2.0 强制使用 TLS(Transport Layer Security)加密协议,提供了更强的安全性保护。