工程化 面试题

共 87 道 工程化 面试题。答案默认折叠,便于先自行作答。 1. webpack 中的 Loader ,链式调用顺序会影响编译结果吗? 难度:2 · 类型:QA 题目要点 Webpack Loader 是一个按流水线执行的源码转换系统,Loader 的链式顺序直接决定源码被处理的阶段;配置顺序为从左到右,但执行顺序为从右到左,每个 Loader 都处理前一个 Loader 的输出,因此顺序错误会导致语义阶段错乱甚至编译失败;通常应遵循“靠近源码的 Loader 放右侧,靠近运行时的 Loader 放左侧”的原则,同时 Loader 还存在 pitch 与 normal 两阶段执行机制,使顺序对最终编译结果具有决定性影响。 参考答案 会,而且这是 Webpack Loader 机制中非常关键且经常被误解的一点: Loader 的链式调用顺序不仅会影响编译结果,而且很多语法是否能正确工作,完全取决于 Loader 的执行顺序。 理解这个问题的关键在于:Loader 本质是一个“源码转换流水线(transform pipeline)”。 一、Loader 本质:源码的逐步转换 在 Webpack 中 hookup 的 Loader,并不是同时运行的,它们会对模块源码进行一层一层的转换: 原始源码 → loaderA → loaderB → loaderC → 最终 JS 模块 每个 Loader: 接收上一个 Loader 的输出 返回新的源码字符串(或 AST 转换结果) 交给下一个 Loader 因此: ...

性能优化 面试题

共 52 道 性能优化 面试题。答案默认折叠,便于先自行作答。 1. 响应式开发中,如何避免窗口大小监听导致的重排抖动? 难度:2 · 类型:QA 题目要点 响应式开发中 resize 导致抖动的本质原因是窗口变化会高频触发事件,从而反复触发布局计算。常见优化方式包括对 resize 事件进行节流或防抖以降低执行频率,避免在回调中混合 DOM 读写以减少强制同步布局,利用 requestAnimationFrame 控制更新时机,以及使用 ResizeObserver、媒体查询或容器查询等浏览器能力,让布局响应更多地交给浏览器完成,从而降低重排和重绘的成本。 参考答案 在响应式开发中,如果直接监听 resize 事件并在回调中执行布局计算或 DOM 操作,很容易引发频繁的 重排(reflow)和重绘(repaint)。原因在于浏览器在拖动窗口尺寸时会持续触发 resize 事件,如果每次触发都进行样式读取和 DOM 更新,就会造成布局计算不断被打断,从而出现明显的抖动或性能下降。 实际工程中通常会从 事件触发频率控制、布局计算方式优化、浏览器能力利用 三个层面进行处理。 首先是对 resize 事件进行频率控制。浏览器在拖拽窗口时可能每秒触发几十到上百次 resize,如果每次都重新计算布局,主线程压力会非常大。常见做法是通过 节流(throttle)或防抖(debounce) 将计算频率降低。例如在节流策略下,每隔一段时间才执行一次布局更新,从而避免在连续拖动过程中频繁触发布局计算。对于布局实时性要求较低的场景,防抖也比较适合,即在窗口停止变化后再执行计算。 其次是避免在 resize 回调中混合 DOM 读写操作。浏览器在读取布局信息(例如 offsetWidth、getBoundingClientRect)时,如果之前存在未提交的样式修改,会强制触发布局计算,这被称为 强制同步布局(Forced Reflow)。因此更好的方式是将读取和写入操作进行分离,例如先批量读取尺寸,再统一修改样式,或者借助 requestAnimationFrame 将 DOM 更新安排到浏览器下一帧执行,避免频繁打断渲染流程。 另外一个更现代的方案是尽量减少对 window.resize 的依赖,而是使用 元素级尺寸监听机制。浏览器已经提供了 ResizeObserver API,可以直接监听某个容器尺寸变化。当布局响应依赖的是容器宽度而不是窗口宽度时,这种方式更加精确,同时也减少无关 resize 触发带来的性能损耗。 在 CSS 层面也可以减少 JavaScript 的参与。例如使用 媒体查询(media query) 或 容器查询(container query) 来处理布局变化,让浏览器在样式计算阶段直接完成响应式适配。CSS 驱动的响应式布局通常比 JavaScript 监听 resize 更高效,因为浏览器可以对样式计算进行统一调度。 ...

算法 面试题

共 22 道 算法 面试题。答案默认折叠,便于先自行作答。 1. 不重复最大子串 难度:2 · 类型:QA 给定一个字符串,请实现一个函数来找到其中的不重复最大子串。例如,对于字符串"abcabcbb",不重复最大子串是"abc",长度为3。 请写出实现该功能的代码,并说明其时间复杂度。 考虑到性能优化,你认为还有哪些改进空间?请提出优化思路并实现优化后的代码。 题目要点 滑动窗口 是解决最长不重复子串问题的核心思想。 Set 方法简单直观,但每遇到重复字符可能多次移动左指针。 Map 优化通过记录字符索引,直接跳过重复区域,减少不必要操作。 时间复杂度 O(n),空间复杂度 O(Σ)。 参考答案 一、滑动窗口实现 function lengthOfLongestSubstring(s) { let set = new Set(); let left = 0, maxLen = 0; for (let right = 0; right < s.length; right++) { while (set.has(s[right])) { set.delete(s[left]); left++; } set.add(s[right]); maxLen = Math.max(maxLen, right - left + 1); } return maxLen; } // 测试 console.log(lengthOfLongestSubstring("abcabcbb")); // 输出 3 思路 使用 滑动窗口 [left, right] 遍历字符串。 用 Set 存储当前窗口内字符。 当遇到重复字符时,移动左指针,直到窗口内无重复字符。 每次窗口扩大时更新最大长度。 时间复杂度 每个字符 最多进出窗口一次 → O(n) 空间复杂度:O(min(n, Σ)),Σ 是字符集大小。 二、性能优化 上面方法每遇到重复字符,需要 逐个删除左边字符。可以进一步优化为 直接跳过重复字符的索引,使用 Map 存储字符上次出现的索引。 ...

编程题 面试题

共 136 道 编程题 面试题。答案默认折叠,便于先自行作答。 1. 实现发布订阅模式 难度:2 · 类型:QA 题目要点 发布订阅模式通过事件中心解耦消息发送者与接收者,核心实现是维护事件名与回调函数列表之间的映射关系。订阅阶段注册回调函数,发布阶段遍历执行所有监听函数,取消订阅则从列表中移除对应回调。工程实践中通常会扩展一次性订阅、事件隔离等能力,但在大型应用中需要避免滥用事件总线,以免造成事件链复杂和内存管理问题。 参考答案 发布订阅模式(Publish–Subscribe Pattern)是一种典型的 消息通信模式。其核心思想是将消息的发送者(Publisher)与接收者(Subscriber)进行解耦,消息不会直接发送给某个具体对象,而是发布到一个事件或主题(Topic)上,由所有订阅该事件的监听者统一接收。 在前端应用中,这种模式常用于 组件通信、事件总线、状态变化通知、插件系统等场景。例如一个模块只负责发布事件,而其他模块只需要订阅事件即可响应变化,双方不需要知道彼此的存在。 实现发布订阅模式通常需要三个核心能力: 注册订阅(subscribe / on) 发布事件(publish / emit) 取消订阅(unsubscribe / off) 本质上可以通过维护一个 事件名 → 回调函数列表 的映射结构来实现。 一个简单实现如下: class EventEmitter { constructor() { this.events = Object.create(null); } on(eventName, handler) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].push(handler); } emit(eventName, ...args) { const handlers = this.events[eventName]; if (!handlers) return; handlers.forEach(fn => fn(...args)); } off(eventName, handler) { const handlers = this.events[eventName]; if (!handlers) return; this.events[eventName] = handlers.filter(fn => fn !== handler); } } 使用方式如下: ...

计算机基础 面试题

共 19 道 计算机基础 面试题。答案默认折叠,便于先自行作答。 1. 进程、线程、协程分别是什么概念? 难度:1 · 类型:QA 题目要点 进程 是独立的资源分配单位,每个进程有自己的内存和资源。 线程 是进程中的执行单元,线程之间共享进程的资源,但每个线程有自己的执行栈。 协程 是一种用户级的轻量级线程,通过程序控制的方式在任务之间进行切换,适合需要高效并发的场景。 参考答案 进程、线程和协程是计算机程序设计中不同层次的执行单元,各自有不同的概念和特点。以下是它们的详细解释: 1. 进程(Process) 概念:进程是操作系统分配资源的基本单位,是正在执行的程序的实例。每个进程都有自己的地址空间、内存、文件描述符等资源。 特点: 独立性:进程是相互独立的,互不干扰。一个进程的崩溃不会直接影响到其他进程。 资源分配:每个进程有独立的内存空间和系统资源。 开销大:由于需要独立的资源和内存,进程之间的切换(上下文切换)开销相对较大。 应用:常用于需要高隔离性和独立性的场景,如多进程服务器、操作系统服务等。 2. 线程(Thread) 概念:线程是进程中的执行单元,是程序执行的最小单位。一个进程可以包含多个线程,这些线程共享进程的资源(如内存)。 特点: 共享资源:线程之间共享进程的内存和资源,这使得线程间通信更加高效,但也带来了同步和竞争的问题。 开销小:线程的创建和销毁比进程要快,线程之间的切换也比进程切换更高效。 协作:线程之间可以进行协作,适合进行多任务处理。 应用:适用于需要并发执行的场景,如多线程应用程序、并行计算等。 3. 协程(Coroutine) 概念:协程是一种轻量级的线程,允许在执行过程中挂起和恢复,支持非抢占式的任务切换。协程可以在单线程中并发执行多个任务,但它们之间的切换由程序控制而不是操作系统。 特点: 协作式切换:协程通过显式的挂起和恢复操作进行切换,不需要操作系统的调度。 轻量级:协程的创建和切换开销非常小,通常比线程更高效。 适用场景:适用于需要大量并发操作但不需要多线程资源的场景,如异步编程、事件驱动编程等。 应用:广泛用于异步编程、游戏开发、网络编程等领域。许多现代编程语言(如 Python 的 asyncio、JavaScript 的 async/await)都支持协程。 2. 解释性语言和编译型语言有什么区别? 难度:2 · 类型:QA 题目要点 解释性语言:逐行解释执行,适合快速开发和跨平台,但执行速度较慢。 编译型语言:预先编译成机器码,执行速度较快,适合性能要求高的应用,但开发周期较长。 现代编程语言和环境往往结合这两种方式,以兼顾开发效率和执行性能。 参考答案 解释性语言和编译型语言是两种不同的编程语言执行方式,它们在代码执行、编译过程、执行效率等方面有显著差异。以下是它们的主要区别: 1. 解释性语言(Interpreted Languages) 定义:解释性语言的代码在运行时由解释器逐行解释和执行。解释器将源代码逐步翻译成机器码或中间代码,然后立即执行。 执行方式: 代码在每次执行时都会被解释器逐行翻译。 不需要预先编译成机器码。 优点: 开发和测试灵活性:支持即时运行和调试,便于快速开发和修改。 跨平台性:代码可以在不同平台上运行,只要有相应的解释器。 缺点: ...

计算机网络 面试题

共 104 道 计算机网络 面试题。答案默认折叠,便于先自行作答。 1. 说说流式输出的原理及其应用场景 难度:3 · 类型:QA 题目要点 流式输出的本质是将结果按时间拆分为连续数据流,实现边生成、边传输、边消费;它通过降低首字节时间和提升反馈及时性来改善用户体验;常见于大模型推理、长耗时任务、实时数据和媒体传输等场景;但同时也带来更高的工程复杂度,需要在体验收益与系统成本之间进行权衡。 参考答案 流式输出并不是一种新的计算模型,而是一种结果传递与消费方式的改变:从“一次性生成、一次性返回”,变为“边生成、边传输、边消费”。理解它的关键,不在于某个具体 API,而在于数据生产方、传输层和消费方三者如何协同工作。 一、流式输出的基本原理 1. 传统非流式模式 在非流式模式下,系统的执行流程是严格串行的: 服务端完整计算结果 将结果一次性写入响应 客户端在接收完全部数据后再进行处理或渲染 这种模式的特点是实现简单,但首字节时间(TTFB)和用户感知延迟较高,尤其当计算过程本身很慢时,用户在很长一段时间内得不到任何反馈。 2. 流式输出模式 流式输出的核心变化在于:结果不再作为一个整体返回,而是被拆分为多个连续的数据块。 典型流程是: 服务端在计算过程中,阶段性地产生部分结果 每当有新数据可用,就立刻写入响应流并 flush 客户端持续读取数据流,并逐步消费、渲染或处理 在传输层,这通常依赖于长连接 + 分块传输,例如 HTTP chunked encoding、Server-Sent Events 或 WebSocket。 3. 从系统视角看流式输出 从系统设计角度,流式输出具备以下特征: 数据是按时间顺序增量产生的 消费方不需要等待生产方完全结束 生产与消费之间形成一种弱同步关系 这使得系统整体从“请求-响应”模型,转变为一种更接近“发布-订阅”的交互方式。 二、前端视角下的实现机制 在前端,流式输出通常体现在如何读取并渲染增量数据。 以 HTTP 流为例: 浏览器通过 Fetch API 获取一个 ReadableStream 通过 getReader() 持续读取字节块 将字节解码为文本或结构化数据 逐步更新 UI,而不是等全部完成 这种方式要求前端具备更细粒度的状态管理能力,例如处理中间态、取消、错误恢复等问题。 三、典型应用场景 1. 大模型推理与对话系统 这是当前最典型的应用场景之一。 模型在生成文本时是逐 token 产生的,流式输出可以让用户几乎立即看到内容开始出现,显著降低等待焦虑,同时也便于中途打断和重试。 ...

设计模式 面试题

共 7 道 设计模式 面试题。答案默认折叠,便于先自行作答。 1. 开发的过程中你用到过哪些设计模式? 难度:3 · 类型:QA 题目要点 设计模式是一种被广泛接受并经过验证的面向对象软件开发中的最佳实践。它们提供了一套解决常见问题的可重用设计方案。 参考答案 设计模式是一种被广泛接受并经过验证的面向对象软件开发中的最佳实践。它们提供了一套解决常见问题的可重用设计方案。 以下是一些常用的设计模式: 单例模式(Singleton):确保一个类只有一个实例,并提供全局访问点来获取该实例。 工厂模式(Factory):通过工厂方法创建对象,而不是直接使用new操作符。这样可以隐藏具体实现,并根据需要创建所需类型的对象。 观察者模式(Observer):定义了一种一对多的依赖关系,当一个对象状态发生改变时,它的所有依赖者(观察者)都会收到通知并自动更新。 装饰器模式(Decorator):动态地将责任附加到对象上。通过将对象包装在装饰器对象中,可以在运行时为对象添加新的行为。 策略模式(Strategy):定义了一系列算法,将每个算法封装起来并使它们可以相互替换。策略模式可以让算法独立于客户端而变化。 适配器模式(Adapter):将一个类的接口转换成客户端所期望的另一个接口。适配器模式使得原本由于接口不匹配而无法一起工作的类可以协同工作。 每个设计模式都有其特定的应用场景和优缺点,可以根据具体情况来选择使用。设计模式可以提高代码结构的灵活性、可维护性和可扩展性,并促进重用和解耦。然而,需要根据实际需求慎重选择和应用设计模式,避免过度设计或不必要的复杂性。 2. 什么是 MVVM?比之 MVC 有什么区别?什么又是 MVP ? 难度:3 · 类型:QA 题目要点 MVC(Model-View-Controller):将应用程序分为模型、视图和控制器。控制器处理用户输入并更新模型和视图。 MVVM(Model-View-ViewModel):将应用程序分为模型、视图和视图模型。视图模型处理视图和模型之间的交互,通过数据绑定简化状态管理。 MVP(Model-View-Presenter):将应用程序分为模型、视图和展示者。展示者处理视图和模型之间的交互,并负责更新视图和模型。 每种模式都有其适用场景和优缺点,选择哪一种模式通常取决于应用程序的需求和团队的开发习惯。 参考答案 MVC、MVP 和 MVVM 是三种常见的软件架构设计模式,主要通过分离关注点的方式来组织代码结构,优化我们的开发效率。 比如说我们实验室在以前项目开发的时候,使用单页应用时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文件里。页面的渲染、数据的获取,对用户事件的响应所有的应用逻辑都混合在一起,这样在开发简单项目时,可能看不出什么问题,当时一旦项目变得复杂,那么整个文件就会变得冗长,混乱,这样对我们的项目开发和后期的项目维护是非常不利的。 MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Co ntroller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新。 ...

趣味题 面试题

共 35 道 趣味题 面试题。答案默认折叠,便于先自行作答。 1. 过桥问题 难度:2 · 类型:QA 在一个夜晚,同时有4人需要过一桥,一次最多只能通过两个人,且只有一只手电筒,而且每人的速度不同。A,B,C,D需要时间分别为:1,2,5,10分钟。问:在17分钟内这四个人怎么过桥? 题目要点 第一步:A、B过花时间2分钟。 参考答案 第一步:A、B过花时间2分钟。 第二步:B回花时间2分钟。 第三步:C、D过花时间10分钟。 第四步:A回花时间1分钟。 第五步:A、B再过花时间2分钟。 2. 圆环转圈问题 难度:2 · 类型:QA 两个圆环,半径分别是1和2,小圆在大圆内部绕大圆圆周一周,问小圆自身转了几周?如果在大圆的外部,小圆自身转几周呢? 题目要点 小圆所转动的路程便是小圆圆心走过的距离 参考答案 小圆所转动的路程便是小圆圆心走过的距离 所以在大圆外部转动,小圆圆心饶半径为3的大圆心转动,走了三圈 所以在大圆内部转动,小圆圆心饶半径为1的大圆心转动,走了一圈 3. 推理问题(二) 难度:1 · 类型:QA 赵女士买了一些水果和小食品准备去看望一个朋友,谁知,这些水果和小食品被他的儿子们偷吃了,但她不知道是哪个儿子。为此,赵女士非常生气,就盘问4个儿子谁偷吃了水果和小食品。老大说道:“是老二吃的。”老二说道:“是老四偷吃的。”老三说道:“反正我没有偷吃。”老四说道:“老二在说谎。”这4个儿子中只有一个人说了实话,其他的3个都在撒谎。那么,到底是谁偷吃了这些水果和小食品? 题目要点 是老三偷吃了水果和小食品,只有老四说了实话。 参考答案 是老三偷吃了水果和小食品,只有老四说了实话。 用假设法分别假设老大、老二、老三、老四都说了实话,看是否与题意矛盾,就可以得出答案。 4. 最后剩下谁? 难度:2 · 类型:QA 1~50 号运动员按顺序排成一排。教练下令:“单数运动员出列!”剩下的运动员重新排队编号。教练又下令:“单数运动员出列!”如此下去,最后只剩下一个人,他是几号运动员?如果教练下的令是“双数运动员出列!”最后剩下的又是谁? 题目要点 教练下令“单数”运动员出列时,教练只要下5次命令,就能知道剩下的那个人。 参考答案 教练下令“单数”运动员出列时,教练只要下5次命令,就能知道剩下的那个人。 此人在下第五次令之前排序为2,在下4次令之前排序为4,在下3次令之前排序为8,在下2次令之前排序为16,在下1次令之前排序为32,即32位运动员,因此答案是32号。 如果教练下的令是“双数运动员出列!”,最后剩下的是1号。 5. 推理问题(一) 难度:1.5 · 类型:QA 甲、乙、丙三个人在一起做作业,有一道数学题比较难,当他们三个人都把自己的解法说出来以后: 甲说:“我做错了。” 乙说:“甲做对了。” 丙说:“我做错了。” 在一旁的丁看到他们的答案并听了她们的意见后说:“你们三个人中有一个人做对了,有一个人说对了。” 请问,他们三人中到底谁做对了? 题目要点 假设丙做对了,那么甲、乙都做错了,这样,甲说的是正确的,乙、丙都说错了,符合条件,因此,丙做对了。 参考答案 假设丙做对了,那么甲、乙都做错了,这样,甲说的是正确的,乙、丙都说错了,符合条件,因此,丙做对了。 ...

跨端技术 面试题

共 19 道 跨端技术 面试题。答案默认折叠,便于先自行作答。 1. 跨平台的开发中,如何处理不同平台的 API 差异? 难度:2 · 类型:QA 题目要点 条件编译:在编译阶段,通过特定注释(如 // #ifdef MP-WEIXIN)来区分不同平台,编写差异化代码。 适配器模式(Adapter Pattern):在运行时,封装一个统一的工具类(Adapter),在内部判断环境并调用对应的原生 API。例如,封装一个 StorageAdapter,内部判断环境并分别调用 uni.setStorageSync 或浏览器的 localStorage,对业务代码提供统一的 Promise 接口 。 参考答案 处理不同平台的 API 差异,是跨平台开发中的核心挑战。其目标是在享受跨平台开发效率的同时,确保应用在各平台上的功能完整、性能稳定、体验原生。关键在于通过良好的架构设计来隔离与抽象平台细节。 核心原则与架构思想 抽象与统一接口层:这是最根本的策略。不要在各处业务代码中直接调用平台特定的 API,而是封装一个统一的接口层。业务逻辑只与这个抽象层交互,由它来屏蔽底层的平台差异。例如,一个文件操作模块,可以定义统一的 readFile 和 writeFile 方法,然后在内部根据平台调用不同的原生实现 。这种设计使得核心业务代码保持平台无关性,极大提升了可维护性 。 渐进增强与平稳退化:在设计功能时,采用“渐进增强”思想。即,优先保证核心功能在所有平台都能运行,然后为高版本平台或特定平台增强更优的体验。同时,做好“平稳退化”,对于不支持某些高级功能的平台,提供合适的降级方案,而不是让功能崩溃 。 关键技术方案与手段 条件编译:这是在编译时处理差异的高效方式。通过预定义的宏或配置,编译器只会将当前目标平台的代码打包到最终产物中 。例如,在 C++ 或前端构建工具中,可以使用 #ifdef _WIN32 或类似语法,为不同平台编写不同的代码路径。这种方式打包结果纯净,但需要构建工具支持 。 运行时检测与分支判断:这是在代码运行时根据当前所处的平台环境,动态选择执行不同的逻辑。这是最常见和灵活的方式 。各框架通常提供全局的 API 用于获取平台信息,例如 React Native 的 Platform.OS 和 Platform.select() 方法 ,或 UniApp 中的 uni.getSystemInfoSync().platform 。这种方式灵活直观,但代码中会存在一些分支判断。 文件/模块约定:一些框架支持通过特定的文件命名约定来区分平台代码。例如,在 React Native 中,可以创建 Component.ios.js 和 Component.android.js 文件。在导入时,只需 import Component from './Component',框架会自动根据平台加载正确的文件 。这是一种将平台差异隔离在文件级别的优雅实践。 ...

选择题 面试题

共 154 道 选择题 面试题。答案默认折叠,便于先自行作答。 1. 对以下代码说法正确的是? 难度:1 · 类型:Choice let arr = [1,2,3,4,5]; let arr2 = [1, , 3]; A. 执行 arr.length = 3,此时数组为 [1,2,3] B. 执行 arr[10] = 11,此时 arr.length 为 6 C. 执行 delete arr[2],此时 arr.length 为 4,数组为 [1,2,4,5] D. arr2.length 的长度为 2 题目要点 考察的是数组的length属性: 参考答案 正确答案:A 考察的是数组的length属性: 设置数组的length值为小于当前值的非负整数n,则任何索引大于或等于n的数组元素都会从数组中被删除 如果给一个索引为i​的数组元素赋值,而i大于或等于数组当前的length,则数组的length属性会被设 置为i+1​ 对数组元素使用delete操作符不会修改length属性,使用splice()​会 如果省略数组字面量中的一个值,被省略的元素是不存在的,但它的索引是存在的 2. 下列哪些不可以实现浏览器存储数据? 难度:1 · 类型:Choice A. cookie B. localStorage C. session D. sessionStorage 题目要点 session 不是浏览器端 API,指的是服务端会话机制,不能直接用于浏览器存储 参考答案 正确答案:C session 不是浏览器端 API,指的是服务端会话机制,不能直接用于浏览器存储 ...