Block底层原理

基础概念 Block 是 Apple 在 C 语言基础上扩展的语法特性(也称为 Closure / 闭包),它允许将一段代码和其执行时需要的上下文环境封装为一个可传递的对象。Block 在 Objective-C 和 C/C++ 中均可使用,是 GCD、动画 API、回调等场景的核心机制。 // Block 的声明与调用 void (^myBlock)(NSString *) = ^(NSString *name) { NSLog(@"Hello, %@", name); }; myBlock(@"World"); 一个核心问题是:Block 是函数指针还是对象? 答案是——Block 本质上是一个 OC 对象。它的底层是一个包含 isa 指针的 C 结构体,满足 OC 对象的基本条件;同时它也包含一个函数指针(FuncPtr),指向 Block 实际执行的代码。因此可以说,Block 是一个用对象形式包装了函数指针和捕获变量的特殊 OC 对象。 底层数据结构 通过 clang -rewrite-objc 可以将 Block 语法转换为 C++ 代码,观察其底层结构。这个转换虽然不完全等同于最终编译产物,但准确反映了 Block 的数据布局和调用机制。 编译器转换示例 对以下源码: int main() { int a = 10; void (^block)(void) = ^{ NSLog(@"%d", a); }; block(); return 0; } Clang 会将其转换为如下结构: ...

May 2, 2026

KISS原则

什么是KISS原则? KISS(Keep It Simple, Stupid)原则是软件工程中最重要的设计原则之一。它的核心理念是:简单的解决方案比复杂的解决方案更好。 核心思想 简单不等于简陋 KISS 原则强调的"简单"是指: 易于理解:代码的意图一目了然 易于修改:改动时不会牵一发而动全身 易于调试:出问题时容易定位原因 易于测试:测试用例简单直接 简单不意味着功能缺失或代码草率,而是用最直接的方式解决问题。 复杂性的代价 不必要的复杂性会带来: 更高的学习成本:新成员需要更长时间理解代码 更多的 bug:复杂逻辑更容易出错 更难维护:修改一处可能影响多处 更慢的开发速度:需要考虑更多的边界情况 graph LR A[复杂性增加] --> B[理解成本上升] A --> C[Bug 概率上升] A --> D[维护难度上升] B --> E[开发效率下降] C --> E D --> E iOS开发中的KISS实践 1. 选择简单的架构 // 过度设计:小项目使用复杂的 VIPER 架构 // 每个模块都有 View, Interactor, Presenter, Entity, Router // 一个简单的列表页面需要创建 5+ 个文件 // KISS:根据项目规模选择合适的架构 // 小项目:MVC 就足够了 class UserListViewController: UIViewController { private var users: [User] = [] override func viewDidLoad() { super.viewDidLoad() loadUsers() } private func loadUsers() { Task { users = try await UserService.shared.fetchUsers() tableView.reloadData() } } } // 随着项目增长,再逐步引入更复杂的架构 2. 避免过早优化 // 违反 KISS:过早引入复杂的缓存机制 class ImageLoaderOverEngineered { private let memoryCache = NSCache<NSString, UIImage>() private let diskCache: DiskCache private let networkQueue = DispatchQueue(label: "imageLoader", attributes: .concurrent) private let cacheQueue = DispatchQueue(label: "cacheQueue") private var pendingRequests: [URL: [(UIImage?) -> Void]] = [:] private let requestLock = NSLock() func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) { // 复杂的多级缓存、请求合并、线程同步逻辑... // 对于大多数 App 来说,这可能是过度设计 } } // KISS:从简单开始 class SimpleImageLoader { private let cache = NSCache<NSString, UIImage>() func loadImage(from url: URL) async -> UIImage? { let key = url.absoluteString as NSString // 先检查缓存 if let cached = cache.object(forKey: key) { return cached } // 从网络加载 guard let (data, _) = try? await URLSession.shared.data(from: url), let image = UIImage(data: data) else { return nil } // 存入缓存 cache.setObject(image, forKey: key) return image } } // 当真正遇到性能问题时,再逐步优化 3. 简化条件逻辑 // 违反 KISS:复杂的嵌套条件 func processOrder(_ order: Order) -> Result<Receipt, OrderError> { if order.items.count > 0 { if order.customer != nil { if order.customer!.isVerified { if order.paymentMethod != nil { if order.shippingAddress != nil { if order.totalAmount > 0 { // 终于可以处理订单了... return .success(Receipt()) } else { return .failure(.invalidAmount) } } else { return .failure(.noShippingAddress) } } else { return .failure(.noPaymentMethod) } } else { return .failure(.unverifiedCustomer) } } else { return .failure(.noCustomer) } } else { return .failure(.emptyOrder) } } // KISS:使用 Guard 早期返回 func processOrderSimple(_ order: Order) -> Result<Receipt, OrderError> { guard !order.items.isEmpty else { return .failure(.emptyOrder) } guard let customer = order.customer else { return .failure(.noCustomer) } guard customer.isVerified else { return .failure(.unverifiedCustomer) } guard order.paymentMethod != nil else { return .failure(.noPaymentMethod) } guard order.shippingAddress != nil else { return .failure(.noShippingAddress) } guard order.totalAmount > 0 else { return .failure(.invalidAmount) } return .success(Receipt()) } 4. 使用标准库而非自己实现 // 违反 KISS:自己实现数组去重 func removeDuplicatesManual<T: Hashable>(from array: [T]) -> [T] { var seen = Set<T>() var result = [T]() for element in array { if !seen.contains(element) { seen.insert(element) result.append(element) } } return result } // KISS:使用标准库 let uniqueArray = Array(Set(array)) // 或保持顺序 let uniqueOrderedArray = array.reduce(into: [T]()) { result, element in if !result.contains(element) { result.append(element) } } // 更简单:使用 Swift Algorithms import Algorithms let unique = array.uniqued() 5. 避免过度泛型化 // 违反 KISS:过度泛型,难以理解 protocol DataProviding { associatedtype DataType associatedtype ErrorType: Error func fetch() async throws -> DataType } protocol DataTransforming { associatedtype Input associatedtype Output func transform(_ input: Input) -> Output } class GenericDataLoader< Provider: DataProviding, Transformer: DataTransforming > where Provider.DataType == Transformer.Input { // 复杂的泛型约束... } // KISS:简单直接的实现 class UserLoader { func loadUsers() async throws -> [User] { let data = try await APIClient.shared.fetch("/users") return try JSONDecoder().decode([User].self, from: data) } } // 当真正需要复用时,再考虑抽象 6. 简化 API 设计 // 违反 KISS:过多的配置选项 func createButton( title: String, titleColor: UIColor = .white, titleFont: UIFont = .systemFont(ofSize: 16), backgroundColor: UIColor = .blue, cornerRadius: CGFloat = 8, borderWidth: CGFloat = 0, borderColor: UIColor = .clear, shadowColor: UIColor = .black, shadowOffset: CGSize = .zero, shadowRadius: CGFloat = 0, shadowOpacity: Float = 0, contentInsets: UIEdgeInsets = .init(top: 12, left: 24, bottom: 12, right: 24), isEnabled: Bool = true, // ... 更多参数 ) -> UIButton { // ... } // KISS:提供简单的默认实现和预设样式 enum ButtonStyle { case primary case secondary case destructive case text } func createButton(title: String, style: ButtonStyle = .primary) -> UIButton { let button = UIButton() button.setTitle(title, for: .normal) switch style { case .primary: button.backgroundColor = .systemBlue button.setTitleColor(.white, for: .normal) button.layer.cornerRadius = 8 case .secondary: button.backgroundColor = .systemGray5 button.setTitleColor(.systemBlue, for: .normal) button.layer.cornerRadius = 8 case .destructive: button.backgroundColor = .systemRed button.setTitleColor(.white, for: .normal) button.layer.cornerRadius = 8 case .text: button.backgroundColor = .clear button.setTitleColor(.systemBlue, for: .normal) } return button } // 使用简单 let primaryButton = createButton(title: "Submit") let cancelButton = createButton(title: "Cancel", style: .secondary) 7. 选择合适的数据结构 // 违反 KISS:用复杂的数据结构解决简单问题 class UserStatusManager { private var statusTree: RedBlackTree<String, UserStatus> private var statusIndex: [String: TreeNode<String, UserStatus>] // 复杂的树结构来管理用户状态... } // KISS:简单的字典就够了 class SimpleUserStatusManager { private var userStatus: [String: UserStatus] = [:] func setStatus(_ status: UserStatus, for userId: String) { userStatus[userId] = status } func getStatus(for userId: String) -> UserStatus? { return userStatus[userId] } } 如何平衡简单与功能 渐进式复杂度 遵循"简单开始,按需增长"的原则: ...

May 2, 2026

KMP(Kotlin Multiplatform)详解

KMP(Kotlin Multiplatform)是 JetBrains 推出的跨平台代码共享方案。它与 Flutter、React Native 的"一套代码一套 UI"思路不同,走的是"业务逻辑共享,UI 原生“的路线:Kotlin 写的网络、数据库、ViewModel、业务规则等代码编译成 iOS/Android/HarmonyOS/Web 可直接使用的二进制产物,而 UI 仍然由各平台原生框架(SwiftUI/UIKit、Jetpack Compose、ArkUI、DOM/Compose Web)自己实现。2023 年 11 月 KMP 正式 Stable;2024 年 6 月华为开发者大会(HDC 2024)上 JetBrains 与华为合作宣布 Kotlin 原生支持 HarmonyOS NEXT,KMP 正式扩展到"Android + iOS + 鸿蒙 + Web"的四端矩阵;随后 Compose Multiplatform for iOS 经历 Alpha → Beta 演进,使得"连 UI 也可以一起共享"成为可选项。 对 iOS 开发者而言,KMP 的意义在于:Android/鸿蒙同事写的 Kotlin 代码你可以像 Swift 一样调用,而代价只是多一个 XCFramework 和一点桥接代码。本文从架构开始,一路讲到编译原理、内存模型、iOS/鸿蒙互操作细节与工程最佳实践。 一、KMP 是什么 1.1 一句话定义 KMP 是 Kotlin 官方的跨平台编译工具链,允许你用 Kotlin 写一份"业务逻辑"代码,编译到多个目标平台: ...

May 2, 2026

OOP、POP与AOP

在iOS开发中,经常会接触到三种重要的编程范式:面向对象编程(OOP)、面向协议编程(POP)和面向切面编程(AOP)。 OOP - 面向对象编程 基本概念 面向对象编程(Object-Oriented Programming)是一种以对象为核心的编程范式。它将数据和操作数据的方法封装在一起,形成对象。 OOP的四大核心特性: 特性 说明 iOS中的体现 封装 隐藏内部实现细节,只暴露必要的接口 @interface/@implementation分离,private属性 继承 子类继承父类的属性和方法 UIViewController继承自UIResponder 多态 同一接口可以有不同的实现 子类重写父类方法 抽象 提取共同特征形成抽象类型 抽象基类、协议 Objective-C中的OOP Objective-C是一门典型的面向对象语言,它在C语言的基础上添加了面向对象的特性。 // 基类定义 @interface Animal : NSObject @property (nonatomic, copy) NSString *name; - (void)speak; - (void)eat:(NSString *)food; @end @implementation Animal - (void)speak { NSLog(@"Animal speaks"); } - (void)eat:(NSString *)food { NSLog(@"%@ is eating %@", self.name, food); } @end // 子类继承 @interface Dog : Animal @property (nonatomic, copy) NSString *breed; @end @implementation Dog // 重写父类方法 - 多态 - (void)speak { NSLog(@"%@ barks: Woof!", self.name); } // 子类特有方法 - (void)fetch { NSLog(@"%@ is fetching", self.name); } @end Swift中的OOP Swift同样支持面向对象编程,但提供了更现代的语法和更强的类型安全。 ...

May 2, 2026

单例模式

定义 单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。 单例模式的核心要点: 唯一性:整个应用程序生命周期内只存在一个实例 全局访问:提供一个全局访问点获取该实例 懒加载:通常在第一次使用时才创建实例 为什么需要单例模式 单例模式要解决的核心问题是:确保某个类在整个应用中只有一个实例,并提供全局访问点。 问题场景:假设我们正在开发一个App,需要管理用户的登录状态和用户信息。 如果不使用单例,可能会遇到这样的问题: class UserManager { var currentUser: User? var isLoggedIn: Bool = false func login(user: User) { currentUser = user isLoggedIn = true } } // 在登录页面 let userManager1 = UserManager() userManager1.login(user: user) // 在个人中心页面 let userManager2 = UserManager() print(userManager2.isLoggedIn) // false!因为是不同的实例 这种方式有什么问题? 状态不一致:不同地方创建的UserManager是不同的实例,它们的状态相互独立,导致登录状态无法共享 资源浪费:如果UserManager管理的是数据库连接、网络会话等资源,创建多个实例会浪费系统资源 数据同步困难:多个实例之间的数据需要手动同步,容易出错 单例模式的解决思路: 单例模式确保整个应用只有一个UserManager实例,所有地方都访问同一个对象: class UserManager { static let shared = UserManager() private init() {} var currentUser: User? var isLoggedIn: Bool = false func login(user: User) { currentUser = user isLoggedIn = true } } // 在登录页面 UserManager.shared.login(user: user) // 在个人中心页面 print(UserManager.shared.isLoggedIn) // true!同一个实例 适合使用单例的场景: ...

May 2, 2026

小红书-社招-5年 · 第 2 轮 · 技术面试

← 第 1 轮 · 返回本次面经 · 已是最后一轮 → 本轮概述: 该轮面试主要考察了浏览器的工作原理、缓存策略、跨域、Vue生命周期、组件通信等方面的知识。题目涉及理论知识和实际应用场景。 本轮共 14 道题。答案默认折叠,便于先自行作答。 1. 详细讲一下从url输入网址到页面渲染的过程 题库原题:简单描述从输入网址到页面显示的过程 题目要点 当输入URL到页面加载完成,发生了以下几个关键过程: DNS解析:浏览器将URL解析为对应的IP地址。这个过程涉及多级DNS服务器,从本地缓存开始,如果没有找到,则递归查询根域名服务器、顶级域名服务器,直到找到目标服务器的IP地址。 TCP连接:浏览器通过三次握手与服务器建立TCP连接。一旦连接建立,浏览器可以发送HTTP请求。 HTTP请求:浏览器构建HTTP请求报文,通过TCP连接发送到服务器。请求报文包含请求行、请求头和请求正文。 服务器处理请求:服务器接收HTTP请求,解析请求内容,执行相应的处理(如数据库查询、文件读取等),并构建HTTP响应报文。 HTTP响应:服务器将响应报文通过TCP连接发送回浏览器。响应报文包含状态码、响应头和响应正文。 浏览器解析渲染:浏览器接收到HTTP响应后,解析HTML文档构建DOM树,解析CSS构建CSSOM树,合并两者形成渲染树,然后开始渲染页面。 连接结束:当浏览器完成页面渲染或收到服务器关闭连接的信号时,浏览器会发送TCP连接关闭的信号,服务器收到后,双方断开连接。 参考答案 很多大公司面试喜欢问这样一道面试题,输入URL到看见页面发生了什么? 简单来说,共有以下几个过程: DNS解析 发起TCP连接 发送HTTP请求 服务器处理请求并返回HTTP报文 浏览器解析渲染页面 连接结束 下面我们来看看具体的细节。 DNS解析 DNS解析实际上就是寻找你所需要的资源的过程。假设你输入www.baidu.com,而这个网址并不是百度的真实地址,互联网中每一台机器都有唯一标识的IP地址,这个才是关键,但是它不好记,乱七八糟一串数字谁记得住啊,所以就需要一个网址和IP地址的转换,也就是DNS解析。 DNS解析其实是一个递归的过程。 输入www.google.com网址后,首先在本地的域名服务器中查找,没找到去根域名服务器查找,没有再去com顶级域名服务器查找,,如此的类推下去,直到找到IP地址,然后把它记录在本地,供下次使用。大致过程就是.-> .com ->google.com. -> www.google.com.。 (最后这个.对应的就是根域名服务器,默认情况下所有的网址的最后一位都是.,为了方便用户,通常都会省略,浏览器在请求DNS的时候会自动加上) DNS优化 既然已经懂得了解析的具体过程,我们可以看到上述一共经过了N个过程,每个过程有一定的消耗和时间的等待,因此我们得想办法解决一下这个问题! DNS缓存 DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,ISP服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。 DNS负载均衡 比如访问baidu.com的时候,每次响应的并非是同一个服务器(IP地址不同),一般大公司都有成百上千台服务器来支撑访问。DNS可以返回一个合适的机器的IP给用户,例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等,这种过程就是DNS负载均衡。 发起TCP连接 TCP提供一种可靠的传输,这个过程涉及到三次握手,四次挥手。 三次握手 第一次握手: 客户端发送syn包(Seq=x)到服务器,并进入SYN_SEND状态,等待服务器确认; 第二次握手: 服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(Seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态; 第三次握手: 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。 四次挥手 数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,假设客户端主动关闭,服务器被动关闭。 第一次挥手: 客户端发送一个FIN,用来关闭客户端到服务器的数据传送,也就是客户端告诉服务器:我已经不 会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,客户端依然会重发这些数据),但是,此时客户端还可以接受数据。 FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。 第二次挥手: 服务器收到FIN包后,发送一个ACK给对方并且带上自己的序列号seq,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。 ...

February 1, 2026

小红书-社招-3年 · 第 2 轮 · 技术面试

← 第 1 轮 · 返回本次面经 · 第 3 轮 → 本轮概述: 这一轮主要考察了小程序的性能优化、核心监控指标、首屏时间的计算、业务价值验证、高频事件处理等方面的知识。 本轮共 11 道题。答案默认折叠,便于先自行作答。 1. 小程序性能优化做了哪些事情? 题库原题:小程序可以做哪些性能优化? 题目要点 小程序性能优化围绕逻辑层与视图层分离架构展开,重点是减少 setData 频率与数据体积,控制列表节点数量,优化首包体积与启动链路,并通过分包加载、WXS 与懒加载等手段降低渲染压力。核心原则是减少跨线程通信与避免无意义重渲染,从架构层面解决性能瓶颈。 参考答案 小程序的性能优化,不能只从“前端渲染”角度看。它的运行模型和 Web 不同,存在 逻辑层(JSCore)与视图层(WebView)分离 的架构特征,核心瓶颈往往出在: 跨线程通信成本 setData 传输体积 渲染节点数量 包体与启动链路 优化必须围绕这些机制展开。 一、理解运行架构是前提 以 微信小程序 为例: 逻辑层:JS 执行环境 视图层:WebView 渲染 两层通过 JSON 序列化通信 每一次 setData: 数据序列化 线程间传输 视图层 diff 真实节点更新 所以小程序性能优化的第一原则是: 减少跨线程通信的数据量与次数。 二、控制 setData 的粒度与频率 1. 避免大对象全量更新 错误方式: this.setData({ form: newFormObject }) 如果 form 很大,每次都会整体传输。 正确方式: this.setData({ "form.username": value }) 使用路径更新,最小化数据传输。 2. 合并多次 setData 多次调用: ...

February 1, 2026

小红书-社招-1年 · 第 2 轮 · 技术面试

← 第 1 轮 · 返回本次面经 · 已是最后一轮 → 本轮概述: 这一轮主要考察了项目经验和技术选型,包括Vue3+TS的选择理由、UI框架的优势、TypeScript的应用、组件化方案、Canvas离屏渲染、Service Worker等内容。 本轮共 14 道题。答案默认折叠,便于先自行作答。 1. 讲一下你觉的最有成就感的项目 题目要点 实际问题, 创新性, 用户价值, 项目背景, 技术选型, 实现过程, 最终效果, 亮点, 贡献 参考答案 最有成就感的项目通常是那些能够解决实际问题、具有创新性并且对用户有显著价值的项目。在回答这个问题时,可以从项目的背景、目标、技术选型、实现过程以及最终效果等方面进行详细描述。重点突出项目的亮点和自己在其中的贡献。 2. 项目为什么选择Vue3+TS而非React 题目要点 Vue3 + TypeScript 的选择通常基于团队技术栈一致性、Composition API 带来的逻辑复用能力、与 TypeScript 的良好类型推导,以及在中后台场景中更成熟的生态和更低的工程复杂度,从而提升整体开发效率与可维护性。 参考答案 在技术选型阶段通常会综合考虑团队技术栈、项目复杂度以及开发效率等因素。对于以中后台系统或业务管理平台为主的项目,Vue3 在整体开发体验和约束能力上往往更容易形成统一规范。 首先是框架设计层面的差异。Vue3 通过 Composition API 将逻辑拆分为可组合的函数单元,在复杂业务场景下可以很好地解决 Options API 中逻辑分散的问题,同时又保留了模板语法,使得结构、逻辑、样式之间的职责划分更加清晰。对于需要长期维护的业务项目,这种组织方式通常更容易理解和维护。 其次是 TypeScript 的结合程度。Vue3 在框架层面对 TypeScript 做了较深的适配,例如 defineComponent、setup、ref、reactive 等 API 都有较完整的类型推导能力,配合 &lt;script setup&gt; 可以获得较好的类型提示和开发体验。在大型项目中,类型系统能够帮助提前暴露接口契约问题,提高代码的可维护性。 另外在生态与工程效率方面,Vue3 在国内中后台领域的组件生态较成熟,例如组件库、低代码平台以及相关工具链都比较完善,能够快速搭建业务系统。对于团队成员技术背景主要偏 Vue 的情况下,可以降低学习成本和沟通成本,提高整体开发效率。 从工程复杂度角度看,React 在灵活性上更强,但很多能力需要通过生态组合实现,例如状态管理、表单方案等,需要额外制定规范。Vue3 本身提供了较完整的开发范式,在团队规模较大的情况下更容易形成统一的代码风格。 3. NutUI在营销活动中相比其他UI框架有什么优势 题目要点 移动端, 轻量, 高性能, 易于使用, 组件库, 用户体验, 开发效率, 按需加载, 应用体积 ...

February 1, 2026

字节-技术中台-校招 · 第 2 轮 · 二面

← 第 1 轮 · 返回本次面经 · 已是最后一轮 → 本轮要点: 本次面试主要考察小程序、前端基础及算法等方面的知识。 本轮共 17 道题。答案默认折叠,便于先自行作答。 1. 讲一下你的小程序项目 题目要点 这是一个典型的主观型问题,没有唯一的标准答案。面试官主要考察候选人的项目经验、技术选型能力、问题解决能力以及对小程序生态的整体理解。 答题时建议围绕项目背景、个人职责、技术挑战与解决方案、项目成果与思考等方面展开,体现出系统性和条理性。 参考答案 在我的某某项目中,我主要负责开发一个基于微信小程序的某某功能模块,比如一个校园服务小程序,其中我负责了课程表、成绩查询以及校园活动发布的功能。 项目启动之初,我们对需求进行了详细的分析。我选择了小程序作为技术栈,主要是考虑到其轻量级、无需下载安装的特性,能够快速触达用户,并且微信生态提供了丰富的API支持,例如微信支付、地理位置等,非常适合我们校园服务的场景。在技术选型上,我们使用了原生小程序开发模式,结合了小程序自定义组件的能力,将课程表、活动列表等通用UI和逻辑封装成可复用组件,大大提高了开发效率和代码的可维护性。 在开发过程中,我遇到了一些挑战。例如,课程表数据的展示,由于涉及到大量的时间和格子计算,以及复杂的状态管理,我采用了组件化的方式来解耦视图和逻辑。为了避免频繁调用setData导致性能问题,我仔细规划了数据结构,并通过局部更新和事件代理的方式,将数据更新的粒度控制到最小,确保了界面的流畅性。此外,在处理校园活动图片上传和展示时,也遇到了图片内存飙升的问题,我通过启用lazy-load属性,并对图片进行后端压缩和CDN分发,有效解决了内存占用过高的问题,提升了用户体验。在保证安全性的前提下,对于用户敏感信息,我们严格遵循小程序的加密存储和传输规范。 项目上线后,用户反馈良好,尤其是加载速度快、操作流畅得到了肯定。通过这个项目,我不仅深化了对小程序双线程架构、生命周期、组件化等核心概念的理解,也锻炼了我在实际项目中解决复杂性能问题的能力。同时,也让我认识到在项目开发中,前期的技术选型、架构设计以及对潜在性能瓶颈的预判都是至关重要的。 2. 小程序的架构组成 题目要点 宿主环境: 了解小程序运行的载体,如微信、支付宝等App提供的运行环境。 双线程模型: 掌握小程序的逻辑层和视图层分离,通过异步通信机制进行数据交换。 组件系统: 熟悉小程序内置组件和自定义组件的构成。 数据层: 理解小程序数据管理和更新机制。 小程序生命周期: 熟悉小程序应用和页面的生命周期函数。 参考答案 1.1 原理说明 宿主环境: 小程序运行在一个特定的宿主环境中,这个环境由微信、支付宝等App提供。它为小程序提供了运行所需的API、渲染能力、文件系统等。 双线程模型: 小程序采用双线程模型,即逻辑层和视图层分别运行在不同的线程中。 逻辑层: 运行在JSCore(或V8引擎)中,负责处理业务逻辑、数据请求、数据处理等,使用JavaScript编写。它不直接操作DOM。 视图层: 负责渲染页面,使用WebView(或独立的渲染引擎)渲染WXML和WXSS。 通信机制: 逻辑层和视图层之间通过Native层进行异步通信。视图层将用户操作(如点击、输入)传递给逻辑层,逻辑层处理后将数据变化通知视图层进行更新。 组件系统: 小程序提供了一套丰富的基础组件,如view、text、image等,开发者也可以基于这些基础组件创建自定义组件,实现模块化开发。 数据层: 小程序的数据管理主要通过Page.prototype.data和this.setData进行。数据变化后,通过双线程通信机制同步到视图层,触发视图更新。 1.2 核心用法 + 示例代码 在小程序中,开发者主要通过JavaScript编写逻辑层代码,WXML构建页面结构,WXSS定义页面样式。 app.js 定义小程序的全局逻辑,app.json 进行全局配置,app.wxss 定义全局样式。 每个页面由 .js(逻辑)、.json(配置)、.wxml(结构)、.wxss(样式)组成。 示例:页面数据更新 // page.js Page({ data: { message: 'Hello Mini Program!' }, onLoad: function() { setTimeout(() => { this.setData({ message: 'Data Updated!' }); }, 2000); } }); 这种架构分离了业务逻辑和页面渲染,提高了性能和安全性,也方便平台对小程序进行管控。 1.3 常见误区或面试陷阱 误区: 认为小程序是基于浏览器内核直接运行的H5页面。实际上,小程序运行在独立的宿主环境,有其特定的渲染机制和API。 误区: 将双线程通信理解为同步通信。实际上是异步通信,需要注意数据同步的时序问题。 陷阱: 不清楚小程序无法直接操作DOM的原因,本质上是为了安全和性能考量,避免开发者直接对渲染层进行复杂操作,影响性能和用户体验。 3. 小程序和 H5 在开发和使用上有哪些主要的区别和优势? 题目要点 运行环境: 区分小程序和H5的运行载体和能力限制。 开发模式: 掌握两种技术栈的开发流程和工具链。 性能: 对比加载速度、运行流畅度等方面的差异。 功能和权限: 了解各自能调用的系统能力和接口。 用户体验: 分析用户感知到的流畅性、便捷性。 分发和推广: 比较获取用户、传播的难易程度。 参考答案 1.1 原理说明 ...

September 2, 2025

字节跳动-商业化-校招 · 第 2 轮 · 二面

← 第 1 轮 · 返回本次面经 · 已是最后一轮 → 本轮要点: 本次面试主要考察前端基础(CSS布局、JavaScript原型链)、React框架深入理解(虚拟DOM、性能优化)、设计模式应用以及算法能力。 本轮共 13 道题。答案默认折叠,便于先自行作答。 1. 实现一个三栏布局,左右两侧固定宽度,中间区域自适应 题目要点 面试官出这道题主要想确认哪些知识维度? 考生对CSS布局(如Flexbox, Grid, 浮动, 定位, 表格布局等)的掌握程度。 考生如何处理不同元素之间的空间分配和自适应能力。 该题所考知识点中有哪些高频实际应用点? 页面整体布局,如经典后台管理系统布局(侧边栏固定,内容区自适应)。 组件内部布局,如卡片、列表项中固定图标/按钮,内容区域自适应。 参考答案 1.1 原理说明 三栏布局是一种常见的网页布局方式,其核心在于实现左右两边固定宽度,中间内容区域根据可用空间自适应。这要求布局方案能够灵活处理流体宽度和固定宽度元素的共存,并且能够正确处理元素之间的堆叠和清除浮动等问题。不同的CSS布局技术提供了不同的实现原理和特性,选择合适的方案取决于兼容性要求、布局复杂度和维护成本。 1.2 核心用法 + 示例代码 以下是几种实现三栏布局的常见方法,并说明其原理和适用场景: 1.2.1 Flexbox 布局 原理: Flexbox (弹性盒子) 是一种一维布局模式,它在父容器中管理子项的分布和对齐。通过设置主轴和交叉轴,可以非常灵活地控制子元素的排列。 使用场景: 现代浏览器环境下的首选布局方式,适用于需要灵活对齐、空间分配的场景。 示例代码: <div class="container-flex"> <div class="left">Left</div> <div class="center">Center</div> <div class="right">Right</div> </div> .container-flex { display: flex; /* 开启Flex布局 */ } .left, .right { width: 200px; /* 左右固定宽度 */ flex-shrink: 0; /* 防止收缩 */ } .center { flex-grow: 1; /* 中间区域自适应,占据剩余空间 */ } 优势: 代码简洁,语义化好,对齐和分配空间非常方便。 1.2.2 Grid 布局 ...

September 2, 2025