经过这么些面试,对于所做过的字体和 Electron,要么不根据项目来问,直接就是 Vue 原理,要么就是前端八股,最多也就是几个简单的 Electron 使用的问题。没想到在非互联网大厂的面试,碰到几个非常有意思的问题。

  • 为什么我们习惯中英文之间使用空格隔开?从字体的角度出发,折行(换行)是怎么处理的?
  • ASCII、Utf8、Utf16、Utf32 分别是什么?
  • 系统字体与 APP 内字体有什么区别。相同的字体和内容,不同字号、字重信息,保存的文件大小是否一致?为什么?
  • Electron 能在 MacOS 下构建 Window 内容吗?编译的二进制文件是否包含架构信息?
  • 再编译了解过吗?在前端使用过 node-sass 吗?是如何做到可以被使用的?

如果你也不知道,不妨来花两分钟看看,讨论讨论吧。

1. 为什么我们习惯中英文之间使用空格隔开?从字体的角度出发,折行(换行)是怎么处理的?

当时从直觉能想到的就是中英文的字宽与高度不一致,从设计角度,中英文空格能有更明显的区分,阅读体验更佳。

如果从字体角度出发,需要你去设计排版和折行处理呢?
其实可以再稍微做些扩展。

对于混排

字间距:

在字体渲染过程中,字符间距是指字符之间的空间距离。中文字符和英文字符的默认间距通常是不同的。为了使中英文混排的文本看起来更加协调,通常会在中英文之间添加额外的空格,以调整字符间距。

字体度量:

字体度量包括字符的宽度、高度、基线位置等信息。中文字符和英文字符的字体度量差异较大,也就是说的字宽这些,因此在混排时需要通过空格来调整这些度量,以确保文本在视觉上的平衡。

折行

布局引擎会根据字符宽度、字体大小、对齐方式等参数来计算最佳的换行位置。尽量的避免单词拆分到两行、中文避免将某些字符(如标点符号)放在行首或行尾。英文单词是根据空格进行拆分的,中文的话,根据字的宽度来拆分。所以添加空格会影响折行的计算,是否添加折行符号或者直接整个单词换行。

2. ASCII、Utf8、Utf16、Utf32 分别是什么?

这个也挺简单的,也比较基础了。想到的是ASCII不包含中文字符集,只有英文字符集,UTF 则是 Unicode 字符集,包含中(CJK中日韩)英文。8、16、32是表示字符编码需要的长度。ASCII 比较简单,内存使用高效。

但其实还可以更多一些:

ASCII

编码方式: 每个字符用一个字节(8位)表示,但只使用其中的7位。最高位通常用作奇偶校验位或保留位。 优点: 简单、高效,适用于英语和一些西欧语言。
缺点: 只能表示有限的字符集,无法表示其他语言的字符,如中文、日文、韩文等。

UTF-8

编码方式:

  • 对于 ASCII 字符(U+0000 到 U+007F),UTF-8 使用1个字节,与 ASCII 编码完全兼容。
  • 对于其他 Unicode 字符,UTF-8 使用2到4个字节。例如,U+0080 到 U+07FF 使用2个字节,U+0800 到 U+FFFF 使用3个字节,U+10000 到 U+10FFFF 使用4个字节。
    优点: 兼容 ASCII,节省空间(对于 ASCII 字符),可以表示所有 Unicode 字符。
    缺点: 对于非 ASCII 字符,可能需要更多的存储空间。

UTF-16

编码方式:

  • 对于基本多语言平面(BMP)中的字符(U+0000 到 U+FFFF),UTF-16 使用1个16位代码单元。
  • 对于辅助平面中的字符(U+10000 到 U+10FFFF),UTF-16 使用2个16位代码单元(称为代理对)。
    优点: 对于 BMP 中的字符,使用固定长度的编码,节省空间。
    缺点: 对于辅助平面中的字符,需要更多的存储空间,且处理复杂。

UTF-32

编码方式: 每个字符使用一个32位(4字节)的代码单元,直接表示 Unicode 码点。
优点: 固定长度编码,处理简单,可以直接通过索引访问字符。
缺点: 占用大量存储空间,尤其是对于 ASCII 字符和其他常用字符。

大部分没直接用到这些特性的,基本也不太可能记得住的吧!还有 GBK(国标扩展)、GB2312(国标2312)。
还是了解下 ASCII 与其他比如 Unicode 就好🤦🏻‍♀️。
ASCII:适用于英语和一些西欧语言,简单高效,但字符集有限。
UTF-8:兼容 ASCII,节省空间,可以表示所有 Unicode 字符,广泛用于互联网和文本文件。
UTF-16:适用于需要处理大量非 ASCII 字符的场景,如 Windows 操作系统中的字符串处理。
UTF-32:适用于需要固定长度编码和直接索引访问字符的场景,但占用大量存储空间。

那 emoji 呢?是什么标准。

后来纳入 Unicode 标准的。

3. 系统字体与 APP 内字体有什么区别。相同的字体和内容,不同字号、字重信息,保存的文件大小是否一致?为什么?

这个问题也是比较简单的,系统字体是系统自带的,APP 内字体是 APP 自己的。区别可能就是通用性和系统优化了,可以减少 APP 内存占用,和可能系统层级上有渲染的硬件优化。就好比公共函数一般。
对于相同的字体和内容,不同字号、字重信息,保存的文件大小是否一致。
从抄 Opentype.js 源码的理解来说。不同的字号、字重这些信息,只是包含在字体内部的某个属性或者可变轴信息上,在渲染的时候,会根据这些结合字形贝塞尔曲线信息来计算出对应的字形。所以渲染时候有差异,保存的是一致的。

4. Electron 能在 MacOS 下构建 Window 内容吗?编译的二进制文件是否包含架构信息?

更为直观简单的问题了,很多表象都能说明。是包含的。
比如:MacOS Rosetta,在 Arm 架构运行 x86 的二进制文件。Electron 构建指令中的 arch 参数等等。
包含的话,那是怎么在 MacOS 下构建 Window 内容的(交叉编译)。
这个问题其实与再编译是一个问题,需要对编译有个大概的了解。编译就是:高级编程语言的源代码,转换成为机器可执行代码的过程。有词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成这些环节。
所以理论上,同样的高级语言代码,只要知道最终编译的目标平台,就可以编译出对应的二进制文件。

5. 再编译了解过吗?在前端使用过 node-sass 吗?是如何做到可以被使用的?

再编译:并不是将编译好的二进制文件,处理成新的文件。再编译其实也就是编译。是根据新的环境或者需求,把源码文件重新编译的过程。
node-sass 依赖是通过 Libsass C++ 原生编写的,所以在不同运行环境(win、mac)不同版本(node)环境上,需要通过 node-gyp 再次编译成为支持当前环境的代码,才供使用。也就是说,node-sass 安装的过程,下载的是二进制文件,然后再编译成支持当前环境的代码。 (当时没反应过来 QAQ )

延伸:Node 是如何与 Rust\C++ 代码相互调用的

只要遵循了 FFI(Foreign Function Interface)协议,不同语言之间就是相互调用。FFI 是一种机制,通过定义通用的 ABI(Application Binary Interface)来实现的(通常是 C ABI:C 语言的 ABI)。常见的就是将原生编译成 .node 文件,对 node-sass 的处理也是这样。

最后

作为一个前端,这几个题目算是最近碰到最有意思的了。希望能给大家带来一些帮助。