一、Swift 构造过程

  • 构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程,这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。
  • Swift 构造函数使用 init() 方法。与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
  • 类实例也可以通过定义析构器(deinitializer)在类实例释放之前执行清理内存的工作。

二、存储型属性的初始赋值

  • 类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。
  • 存储属性在构造器中赋值时,它们的值是被直接设置的,不会触发任何属性观测器。
  • 存储属性在构造器中赋值流程:
    • 创建初始值;
    • 在属性定义中指定默认属性值;
    • 初始化实例,并调用 init() 方法。

三、构造器

① 语法

  • 构造器在创建某特定类型的新实例时调用,它的最简形式类似于一个不带任何参数的实例方法,以关键字 init 命名。
init() {// 实例化后执行的代码
}

② 实例

  • 以下结构体定义了一个不带参数的构造器 init,并在里面将存储型属性 length 和 breadth 的值初始化为 6 和 12:
struct rectangle {var length: Doublevar breadth: Doubleinit() {length = 6breadth = 12}
}
var area = rectangle()
print("矩形面积为 \(area.length*area.breadth)")
  • 以上程序执行输出结果为:
矩形面积为 72.0

四、默认属性值

  • 可以在构造器中为存储型属性设置初始值;同样,也可以在属性声明时为其设置默认值。使用默认值能让构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型。
  • 以下实例在属性声明时为其设置默认值:
struct rectangle {// 设置默认值var length = 6var breadth = 12
}
var area = rectangle()
print("矩形的面积为 \(area.length*area.breadth)")
  • 以上程序执行输出结果为:
矩形面积为 72

五、构造参数

  • 可以在定义构造器 init() 时提供构造参数,如下所示:
struct Rectangle {var length: Doublevar breadth: Doublevar area: Doubleinit(fromLength length: Double, fromBreadth breadth: Double) {self.length = lengthself.breadth = breadtharea = length * breadth}init(fromLeng leng: Double, fromBread bread: Double) {self.length = lengself.breadth = breadarea = leng * bread}
}let ar = Rectangle(fromLength: 6, fromBreadth: 12)
print("面积为: \(ar.area)")let are = Rectangle(fromLeng: 36, fromBread: 12)
print("面积为: \(are.area)")
  • 以上程序执行输出结果为:
面积为: 72.0
面积为: 432.0

六、内部和外部参数名

  • 跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。
  • 然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的参数名和类型来确定需要调用的构造器。
  • 如果在定义构造器时没有提供参数的外部名字,Swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名。
struct Color {let red, green, blue: Doubleinit(red: Double, green: Double, blue: Double) {self.red   = redself.green = greenself.blue  = blue}init(white: Double) {red   = whitegreen = whiteblue  = white}
}// 创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)print("red 值为: \(magenta.red)")
print("green 值为: \(magenta.green)")
print("blue 值为: \(magenta.blue)")// 创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器
let halfGray = Color(white: 0.5)
print("red 值为: \(halfGray.red)")
print("green 值为: \(halfGray.green)")
print("blue 值为: \(halfGray.blue)")
  • 以上程序执行输出结果为:
red 值为: 1.0
green 值为: 0.0
blue 值为: 1.0
red 值为: 0.5
green 值为: 0.5
blue 值为: 0.5
  • 没有外部名称参数,如果不希望为构造器的某个参数提供外部名字,可以使用下划线_来显示描述它的外部名。
struct Rectangle {var length: Doubleinit(frombreadth breadth: Double) {length = breadth * 10}init(frombre bre: Double) {length = bre * 30}//不提供外部名字init(_ area: Double) {length = area}
}// 调用不提供外部名字
let rectarea = Rectangle(180.0)
print("面积为: \(rectarea.length)")// 调用不提供外部名字
let rearea = Rectangle(370.0)
print("面积为: \(rearea.length)")// 调用不提供外部名字
let recarea = Rectangle(110.0)
print("面积为: \(recarea.length)")
  • 以上程序执行输出结果为:
面积为: 180.0
面积为: 370.0
面积为: 110.0

七、可选属性类型

  • 如果定制的类型包含一个逻辑上允许取值为空的存储型属性,都需要将它定义为可选类型optional type(可选属性类型)。
  • 当存储属性声明为可选时,将自动初始化为空 nil。
struct Rectangle {var length: Double?init(frombreadth breadth: Double) {length = breadth * 10}init(frombre bre: Double) {length = bre * 30}init(_ area: Double) {length = area}
}let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")let rearea = Rectangle(370.0)
print("面积为:\(rearea.length)")let recarea = Rectangle(110.0)
print("面积为:\(recarea.length)")
  • 以上程序执行输出结果为:
面积为:Optional(180.0)
面积为:Optional(370.0)
面积为:Optional(110.0)

八、构造过程中修改常量属性

  • 只要在构造过程结束前常量的值能确定,可以在构造过程中的任意时间点修改常量属性的值。
  • 对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
  • 尽管 length 属性现在是常量,仍然可以在其类的构造器中设置它的值:
struct Rectangle {let length: Double?init(frombreadth breadth: Double) {length = breadth * 10}init(frombre bre: Double) {length = bre * 30}init(_ area: Double) {length = area}
}let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")let rearea = Rectangle(370.0)
print("面积为:\(rearea.length)")let recarea = Rectangle(110.0)
print("面积为:\(recarea.length)")
  • 以上程序执行输出结果为:
面积为:Optional(180.0)
面积为:Optional(370.0)
面积为:Optional(110.0)

九、默认构造器

  • 默认构造器将简单的创建一个所有属性值都设置为默认值的实例。如下所示,ShoppingListItem 类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个可以为所有属性设置默认值的默认构造器:
class ShoppingListItem {var name: String?var quantity = 1var purchased = false
}
var item = ShoppingListItem()print("名字为: \(item.name)")
print("数理为: \(item.quantity)")
print("是否付款: \(item.purchased)")
  • 以上程序执行输出结果为:
名字为: nil
数理为: 1
是否付款: false
  • 结构体的逐一成员构造器,如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。
  • 在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。
  • 下面例子中定义了一个结构体 Rectangle,它包含两个属性 length 和 breadth。Swift 可以根据这两个属性的初始赋值100.0 、200.0自动推导出它们的类型 Double。
struct Rectangle {var length = 100.0, breadth = 200.0
}
let area = Rectangle(length: 24.0, breadth: 32.0)print("矩形的面积: \(area.length)")
print("矩形的面积: \(area.breadth)")
  • 由于这两个存储型属性都有默认值,结构体 Rectangle 自动获得了一个逐一成员构造器 init(width:height:),可以用它来为 Rectangle 创建新的实例。以上程序执行输出结果为:
矩形的面积: 24.0
矩形的面积: 32.0

十、值类型的构造器代理

  • 构造器可以通过调用其它构造器来完成实例的部分构造过程,这一过程称为构造器代理,它能减少多个构造器间的代码重复。
  • 如下所示,Rect 结构体调用了 Size 和 Point 的构造过程:
struct Size {var width = 0.0, height = 0.0
}
struct Point {var x = 0.0, y = 0.0
}struct Rect {var origin = Point()var size = Size()init() {}init(origin: Point, size: Size) {self.origin = originself.size = size}init(center: Point, size: Size) {let originX = center.x - (size.width / 2)let originY = center.y - (size.height / 2)self.init(origin: Point(x: originX, y: originY), size: size)}
}// origin和size属性都使用定义时的默认值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0):
let basicRect = Rect()
print("Size 结构体初始值: \(basicRect.size.width, basicRect.size.height) ")
print("Rect 结构体初始值: \(basicRect.origin.x, basicRect.origin.y) ")// 将origin和size的参数值赋给对应的存储型属性
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),size: Size(width: 5.0, height: 5.0))print("Size 结构体初始值: \(originRect.size.width, originRect.size.height) ")
print("Rect 结构体初始值: \(originRect.origin.x, originRect.origin.y) ")//先通过center和size的值计算出origin的坐标。
//然后再调用(或代理给)init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),size: Size(width: 3.0, height: 3.0))print("Size 结构体初始值: \(centerRect.size.width, centerRect.size.height) ")
print("Rect 结构体初始值: \(centerRect.origin.x, centerRect.origin.y) ")
  • 以上程序执行输出结果为:
Size 结构体初始值: (0.0, 0.0)
Rect 结构体初始值: (0.0, 0.0)
Size 结构体初始值: (5.0, 5.0)
Rect 结构体初始值: (2.0, 2.0)
Size 结构体初始值: (3.0, 3.0)
Rect 结构体初始值: (2.5, 2.5)
  • 构造器代理规则:
值类型 类类型
不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器,可以使用self.init在自定义的构造器中引用其它的属于相同值类型的构造器 它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化

十一、类的继承和构造过程

  • Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。
指定构造器 便利构造器
类中最主要的构造器 类中比较次要的、辅助型的构造器
初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化 可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例
每一个类都必须拥有至少一个指定构造器 只在必要的时候为类提供便利构造器
Init(parameters) { statements } convenience init(parameters) { statements}
  • 指定构造器实例
class mainClass {var no1 : Int // 局部存储变量init(no1 : Int) {self.no1 = no1 // 初始化}
}
class subClass : mainClass {var no2 : Int // 新的子类存储变量init(no1 : Int, no2 : Int) {self.no2 = no2 // 初始化super.init(no1:no1) // 初始化超类}
}let res = mainClass(no1: 10)
let res2 = subClass(no1: 10, no2: 20)print("res 为: \(res.no1)")
print("res2 为: \(res2.no1)")
print("res2 为: \(res2.no2)")
  • 以上程序执行输出结果为:
res 为: 10
res 为: 10
res 为: 20
  • 便利构造器实例
class mainClass {var no1 : Int // 局部存储变量init(no1 : Int) {self.no1 = no1 // 初始化}
}class subClass : mainClass {var no2 : Intinit(no1 : Int, no2 : Int) {self.no2 = no2super.init(no1:no1)}// 便利方法只需要一个参数override convenience init(no1: Int)  {self.init(no1:no1, no2:0)}
}
let res = mainClass(no1: 20)
let res2 = subClass(no1: 30, no2: 50)print("res 为: \(res.no1)")
print("res2 为: \(res2.no1)")
print("res2 为: \(res2.no2)")
  • 以上程序执行输出结果为:
res 为: 20
res2 为: 30
res2 为: 50

十二、构造器的继承和重载

  • Swift 中的子类不会默认继承父类的构造器。
  • 父类的构造器仅在确定和安全的情况下被继承。
  • 当重写一个父类指定构造器时,需要写 override 修饰符。
class SuperClass {var corners = 4var description: String {return "\(corners) 边"}
}
let rectangle = SuperClass()
print("矩形: \(rectangle.description)")class SubClass: SuperClass {override init() {  //重载构造器super.init()corners = 5}
}let subClass = SubClass()
print("五角型: \(subClass.description)")
  • 以上程序执行输出结果为:
矩形: 4 边
五角型: 5 边
  • 如下所示,在操作中展示指定构造器、便利构造器和自动构造器的继承,它定义了包含两个个类 MainClass、SubClass 的类层次结构,并将演示它们的构造器是如何相互作用的:
class MainClass {var name: Stringinit(name: String) {self.name = name}convenience init() {self.init(name: "[匿名]")}
}
let main = MainClass(name: "Runoob")
print("MainClass 名字为: \(main.name)")let main2 = MainClass()
print("没有对应名字: \(main2.name)")class SubClass: MainClass {var count: Intinit(name: String, count: Int) {self.count = countsuper.init(name: name)}override convenience init(name: String) {self.init(name: name, count: 1)}
}let sub = SubClass(name: "Runoob")
print("MainClass 名字为: \(sub.name)")let sub2 = SubClass(name: "Runoob", count: 3)
print("count 变量: \(sub2.count)")
  • 以上程序执行输出结果为:
MainClass 名字为: Runoob
没有对应名字: [匿名]
MainClass 名字为: Runoob
count 变量: 3

十三、类的可失败构造器

  • 如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器。
  • 变量初始化失败可能的原因有:
    • 传入无效的参数值;
    • 缺少某种所需的外部资源;
    • 没有满足特定条件。
  • 为了妥善处理这种构造过程中可能会失败的情况,可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器,其语法为在 init 关键字后面加添问号(init?)。
  • 如下,定义了一个名为 Animal 的结构体,其中有一个名为 species 的,String 类型的常量属性,同时该结构体还定义了一个,带一个 String 类型参数 species 的,可失败构造器。这个可失败构造器,被用来检查传入的参数是否为一个空字符串,如果为空字符串,则该可失败构造器,构建对象失败,否则成功。
struct Animal {let species: Stringinit?(species: String) {if species.isEmpty { return nil }self.species = species}
}//通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功
// someCreature 的类型是 Animal? 而不是 Animal
let someCreature = Animal(species: "长颈鹿")// 打印 "动物初始化为长颈鹿"
if let giraffe = someCreature {print("动物初始化为\(giraffe.species)")
}
  • 以上程序执行输出结果为:
动物初始化为长颈鹿
  • 枚举类型的可失败构造器,可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。
  • 如下所示,定义了一个名为 TemperatureUnit 的枚举类型,其中包含了三个可能的枚举成员(Kelvin,Celsius,和 Fahrenheit)和一个被用来找到 Character 值所对应的枚举成员的可失败构造器:
enum TemperatureUnit {// 开尔文,摄氏,华氏case Kelvin, Celsius, Fahrenheitinit?(symbol: Character) {switch symbol {case "K":self = .Kelvincase "C":self = .Celsiuscase "F":self = .Fahrenheitdefault:return nil}}
}let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {print("这是一个已定义的温度单位,所以初始化成功。")
}let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {print("这不是一个已定义的温度单位,所以初始化失败。")
}
  • 以上程序执行输出结果为:
这是一个已定义的温度单位,所以初始化成功。
这不是一个已定义的温度单位,所以初始化失败。
  • 值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。但是,类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。
  • 如下,定义了一个名为 StudRecord 的类,因为 studname 属性是一个常量,所以一旦 StudRecord 类构造成功,studname 属性肯定有一个非 nil 的值。
class StudRecord {let studname: String!init?(studname: String) {self.studname = studnameif studname.isEmpty { return nil }}
}
if let stname = StudRecord(studname: "失败构造器") {print("模块为 \(stname.studname)")
}
  • 以上程序执行输出结果为:
模块为 失败构造器

十四、覆盖一个可失败构造器

  • 就如同其它构造器一样,你也可以用子类的可失败构造器覆盖基类的可失败构造器。也可以用子类的非可失败构造器覆盖一个基类的可失败构造器。可以用一个非可失败构造器覆盖一个可失败构造器,但反过来却行不通。
  • 一个非可失败的构造器永远也不能代理调用一个可失败构造器。
  • 以下实例描述了可失败与非可失败构造器:
class Planet {var name: Stringinit(name: String) {self.name = name}convenience init() {self.init(name: "[No Planets]")}
}
let plName = Planet(name: "Mercury")
print("行星的名字是: \(plName.name)")let noplName = Planet()
print("没有这个名字的行星: \(noplName.name)")class planets: Planet {var count: Intinit(name: String, count: Int) {self.count = countsuper.init(name: name)}override convenience init(name: String) {self.init(name: name, count: 1)}
}
  • 以上程序执行输出结果为:
行星的名字是: Mercury
没有这个名字的行星: [No Planets]

十五、可失败构造器 init

  • 通常来说通过在 init 关键字后添加问号的方式(init?)来定义一个可失败构造器,但也可以使用通过在 init 后面添加惊叹号的方式来定义一个可失败构造器(init!)。
  • 实例如下:
struct StudRecord {let stname: Stringinit!(stname: String) {if stname.isEmpty {return nil }self.stname = stname}
}let stmark = StudRecord(stname: "Runoob")
if let name = stmark {print("指定了学生名")
}let blankname = StudRecord(stname: "")
if blankname == nil {print("学生名为空")
}
  • 以上程序执行输出结果为:
指定了学生名
学生名为空

十六、析构过程

  • 在一个类的实例被释放之前,析构函数被立即调用,用关键字 deinit 来标示析构函数,类似于初始化函数用 init 来标示,析构函数只适用于类类型。

① 析构过程原理

  • Swift 会自动释放不再需要的实例以释放资源。
  • Swift 通过自动引用计数(ARC)处理实例的内存管理。
  • 通常当实例被释放时不需要手动地去清理。但是,当使用自己的资源时,可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,可能需要在类实例被释放之前关闭该文件。

② 语法

  • 在类的定义中,每个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号:
deinit {// 执行析构过程
}

③ 实例

var counter = 0;  // 引用计数器
class BaseClass {init() {counter += 1;}deinit {counter -= 1;}
}var show: BaseClass? = BaseClass()
print(counter)
show = nil
print(counter)
  • 以上程序执行输出结果为:
1
0
  • 当 show = nil 语句执行后,计算器减去 1,show 占用的内存就会释放:
var counter = 0;  // 引用计数器class BaseClass {init() {counter += 1;}deinit {counter -= 1;}
}var show: BaseClass? = BaseClass()print(counter)
print(counter)
  • 以上程序执行输出结果为:
1
1

Swift之深入解析构造过程和析构过程相关推荐

  1. 关于构造与析构过程中调用虚函数的问题

    今天面试碰到一个构造函数与析构函数中调用需虚函数的问题,当时不太确定,回来敲了一下,发现确实表现的不一样,在网上一查发现另有玄机. 代码: # gcc version 4.8.2 (Ubuntu 4. ...

  2. swift学习笔记之-析构过程

    //析构过程deist import UIKit /*析构过程(Deinitialization):析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用.析构器用关键字deinit来标示 ...

  3. C++绝不在构造和析构过程中调用virtual函数

    绝不在构造和析构过程中调用virtual函数 如果希望在继承体系中根据类型在构建对象时表现出不同行为,可以会想到在基类的构造函数中调用一个虚函数: class Transaction { //所有交易 ...

  4. 【C++深度剖析教程1】C++中的经典问题解析-c++中的对象的构造顺序与析构顺序

    c++中的对象的构造顺序与析构顺序 问题一 当程序中存在多个对象时,如何确定这些对象的析构顺序? 一.单个函数创建时构造函数的调用顺序 1.调用父类的构造过程 2.调用成员变量的构造函数(调用顺序与声 ...

  5. Effective C++条款09:绝不在构造和析构过程中调用virtual函数

    Effective C++条款09:绝不在构造和析构过程中调用virtual函数(Never call virtual functions during construction or destruc ...

  6. html怎么用chrome测试,通过chrome调试器测试了解浏览器解析和渲染HTML的过程

    基础知识了解chrome的Timeline工具 主要过程 解析与构建DOM树1外部样式脚本并行加载外部样式会阻塞后续脚本执行直到外部样式加载并解析完毕 2 外部样式不会阻塞后续脚本的加载但会阻塞后续脚 ...

  7. Swift中文教程(十五) 析构

    在一个类的实例被释放之前,析构函数会被调用.用关键字deinit来定义析构函数,类似于初始化函数用init来定义.析构函数只适用于class类型. 1.析构过程原理 Swift 会自动释放不再需要的实 ...

  8. MyBatis核心源码剖析(SqlSession XML解析 Mapper executor SQL执行过程 自定义类型处理器 缓存 日志)

    MyBatis核心源码剖析 MyBatis核心源码剖析 1 MyBatis源码概述 1.1 为什么要看MyBatis框架的源码 1.2 如何深入学习MyBatis源码 1.3 源码分析的5大原则 2 ...

  9. 虚继承c语言例子,C/C++ 多继承{虚基类,虚继承,构造顺序,析构顺序}

    C/C++:一个基类继承和多个基类继承的区别 1.对多个基类继承会出现类之间嵌套时出现的同名问题,如果同名变量或者函数出现不在同一层次,则底层派生隐藏外层比如继承基类的同名变量和函数,不会出现二义性, ...

最新文章

  1. 荒岛余生最后一个包裹_豆瓣评分8.5,一个人撑起整部电影,最后的彩蛋让人无语...
  2. 双目视觉惯性里程计的在线初始化与自标定算法
  3. 如何查看夜神模拟器的端口
  4. MASK -RCNN
  5. 01、python数据分析与机器学习实战——Python数据可视化库-Matplotlib
  6. SpringBoot创建项目入门案例
  7. 人工智能AI实战100讲(一)-机器人语义建图(下)
  8. 1-3docker commit定制镜像
  9. CodeVs——T 4919 线段树练习4
  10. 采用计算机发布调度命令时 必须严格遵守,实用文档其他之铁路调度命令格式图片调度命令规范格式...
  11. (转)淘淘商城系列——导入商品数据到索引库——Service层
  12. 红安一中高考2021成绩查询,红安一中2019高考喜报成绩、一本二本上线人数情况...
  13. 宅在家里写数据库中排序和分页
  14. 四十六 常用内建模块 itertools
  15. 获取.propertys文件获取文件内容
  16. rs232读取智能电表_供电局智能费控电表功能和通讯方式介绍
  17. python文件都是脚本吗_.py文件是python脚本吗
  18. Windows域服务权限提升漏洞(CVE-2021-42287, CVE-2021-42278)
  19. Java如何实现网页截图?
  20. 计算机应用期刊查重率要求,计算机学报先审稿还是先查重

热门文章

  1. i5 11300h和R5 5600H 参数对比哪个好
  2. Eclipse + Apache Axis2 发布RESTful WebService(一)基础知识
  3. python学习day11
  4. Node.js基本讲解
  5. sqlserver 的一些好用的插件
  6. “进度条”博客——第四周
  7. 第二天,终于搞定 —— 2014.10.28
  8. 我所遭遇过的游戏中间件---HumanIK
  9. ATL dll注册失败
  10. vue前端服务器端口_解密智联招聘的大前端架构 Ada