XiaoboTalk

Dart/Swift/JS 不同的语言模型设计

最近又想玩一玩 flutter,再次回忆了一下 Dart 语言,随着接触的编程语言越来越多,渐渐习惯了通过比较不同编程语言的设计,视图了解语言背后的设计思路。我不是很喜欢一条一条的看语法,我认为语法是一个字典,使用到的时候,查一查,查着查着就记住了。
一般接触一门新的编程语言,我都会先了解它的内存模型设计,主要从以下三个方面:
  1. 编译型语言还是解释型,或者两者结合
  1. 内存是如何管理的,有 GC 还是无 GC
  1. 语言的类型系统如何设计:
    1. 值类型 & 引用类型
    2. 是否有装箱 Box
    3. 泛型系统是如何设计的
不同的设计语言,其实和它的设计目标有关。

一、JS

javascript 的设计目标:
  1. 简单
  1. 动态性
这两个目标的初衷,无非是 js 最初用于浏览器脚本,简单可以让开发者快速的在浏览器中使用起来。动态性,可以让 js 在浏览器中更加灵活,还有个原因是,html 是发送到浏览器中再进行解释、渲染的。JS 为了配合这个特效,最初也设计为解释执行。先发下 JS 代码到浏览器,然后由浏览器解释执行,保证足够的灵活性。

有 GC、值类型、装箱

有 GC 的语言,编写起来比较简单,也比较容易上手,开发这几乎不需要关心内存安全,野指针之类的问题。
初学 JS ,类型系统有点让人迷惑,例如既有 string 也有 String ,一个小写开头,一个大写开头,但事实上完全是两种类型,number,boolean,等也是如此。
string 来说,它其实是原始类型,什么是原始类型?其实就是值类型,字面量。它不可变,高效。
但是如果想要对这个值类型进行一些操作,例如拼接,查询字符串等操作,JS 就需要在背后,将这个值类型装箱为 String ,然后才能调用 String 这个对象的方法:length toUpperCase splice 等,操作完成后,在进行解包,将这个对象类型欢迎为新的原始值类型 string 。这一切都在运行时默默进行,开发者几乎察觉不到,但是性能开销就比较大了:值类型可以直接在粘上操作,装箱后,就需要新建对象,开辟堆内存,在堆上进行相关方法调用,然后再解包,还原。
这就是 JS 这个语言的特点,看着很简单,但一不小心就带来了很多性能开销。所以你会看到很多教程都建议尽量使用值类型。

二、Swift

swift 的设计目标:
  1. 当然是替换 iOS 开发的 objective-c 语言
  1. 安全、高效 (不能有 GC)
    1. Swift 本身是无 GC 的,沿用了 OC 的引用计数管理内存,引用计数无法解决循环引用问题,所以开发者需要时刻警惕内存泄露。
理解 Swift 语言,可以从语言类型的设计视角出发。先画一张 swift 数据类型推导图:
notion image
值类型和引用类型,主要注意点在于,进行赋值的时候是值拷贝,还是引用拷贝。一个值类型中包含了一个引用类型的时候,赋值的时候,依然只会拷贝引用本身。

struct / enum / class

现代语言的设计趋势,都逐渐放弃设计 Class 类型,例如 Rust Go 都没有专门设计 Class,Swift 比较特殊的是,需要兼容自家的历史语言 Objective-C,有时候需要和 OC 交互,使用 Class 更加方便。

1.1 struct 和 class 区别

Swift 的设计初衷一定是想优先使用 struct 值类型,值类型的一大好处,就是数据的不可变性和独立性。这非常有利于并发编程,很好的避免了数据竞争,所以写 swift 代码的时候,能用 struct 优先用 strut。此外 struct 和 class 有以下主要区别:
  1. Struct 是值类型,Class 是引用类型
  1. Struct 默认会隐式生成所有属性的构造函数,Class 并不会
  1. Class 具有继承的特性,struct 本身没有,不过可以通过 protocol 模拟实现。
    1. 可以运行时,动态转换类实例的实际类型。
  1. Class 有析构函数 Deinitialized,可以用来释放资源。

基于 protocol 设计的类型编程范式

基于 protocol ,swift 设计了:
  • 泛型 Generic, <T: Protocol>
  • 协议存在类型 (Boxed Type): (argu: any SomeProtocol)
  • 不透明类型 (Opaque Type): (argu: some SomeProtocol)

Swift 很少用到装箱

Swift 基本类型都是值类型,对值类型操作直接都是函数指针操作,保证语言的高效。就连泛型也是编译期泛型。不过为了同时兼具一定的动态性,支持了运行时动态类型,Boxed Type,当然和 JS 不同,这种装箱行为不是语言本身在后台支持的,需要开发者明确书写: 从而告诉开发者,非必要别这么写,你要知道运行时动态化设计,不可避免的带来了性能开销。

三、Dart 又得编译,还得解释,天生为了 Flutter

(未完)