在上一节主要介绍了语法树的解析生成。就好比电脑已经听到了“你真聪明”这句话,现在要让电脑开始思考这句话的含义——是真聪明还是假聪明。

这是一个非常的复杂的过程,接下来将有连续几节内容介绍实现原理,本节则主要提前介绍一些相关的概念。

符号

在代码里面,可以定义一个变量、一个函数、或者一个类,这些定义都有一个名字,然后在其它地方可以通过名字引用这个定义。

这些定义统称为符号(Symbol)(注意和 ES6 里的 Symbol 不相干)。

当在代码里写了一个标识符名称(变量名),这个名称一定可以解析为某个符号(可能是变量、参数、函数或其它的,可能是用户自己写的,也可能系统自带),如果无法解析,那一定是一个错误。

一个符号一般和一个声明节点对应,比如一个变量符号就对应一个 var 声明节点或 let/const 声明节点。

可能存在一个符号对应多个声明节点的情况,比如:

class A{}
interface A{}

同名的类和接口只产生一个符号 A,但这个符号拥有两个声明。

可能存在一个符号没有源声明节点的情况,比如:

type T = {[key: string]: any}

T 有无限个成员,每个成员都是没有源声明节点的。

TS 中符号的定义:

export interface Symbol {
flags: SymbolFlags; // Symbol flags
escapedName: __String; // Name of symbol
declarations: Declaration[]; // Declarations associated with this symbol
valueDeclaration: Declaration; // First value declaration of the symbol
members?: SymbolTable; // Class, interface or object literal instance members
exports?: SymbolTable; // Module exports
globalExports?: SymbolTable; // Conditional global UMD exports
/* @internal */ id?: number; // Unique id (used to look up SymbolLinks)
/* @internal */ mergeId?: number; // Merge id (used to look up merged symbol)
/* @internal */ parent?: Symbol; // Parent symbol
/* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol
/* @internal */ nameType?: Type; // Type associated with a late-bound symbol
/* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums
/* @internal */ isReferenced?: SymbolFlags; // True if the symbol is referenced elsewhere. Keeps track of the meaning of a reference in case a symbol is both a type parameter and parameter.
/* @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol?
/* @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
/* @internal */ assignmentDeclarationMembers?: Map<Declaration>; // detected late-bound assignment declarations associated with the symbol
}

其中,declarations 表示关联的源节点。valueDeclaration 则表示第一个具有值的源节点。

注意两者都可能为 `undefined`,源码中之所以没将它们标上 `?`,主要是因为作者懒(不然代码需要经常判断空)。

其中,escapedName 表示符号的名称,名称本质是字符串,TS 在源码中有一些内部的特殊符号名称,这些名称都以“__”前缀,如果用户本身就定义了名字带__的,会被转义成其它名字,所以 TS 内部将转义后的名字标记成 __String 类型,运行期间它本质还是字符串,所以不影响性能。

作用域

允许定义符号的节点叫作用域(Scope),比如全局范围(源文件),函数,大括号。

在同一个作用域中,不能定义同名的符号。

作用域是一个树结构,查找变量时,先在就近的作用域查找,找不到就向外层查找。

如图共四个作用域:

仔细观察会发现,if 语句不是一个作用域,但 for 语句却是。

语言本身就是这么设计的,因为在 for 里面可以声明变量。

流程节点

流程节点是执行流程图的组成部分。

比如 a = b > c && d == 3 ? e : f 的执行顺序:

由于 JS 是动态语言,变量的类型可能随执行的流程发生变化,因此在分析时需要知道整个代码的执行顺序。

流程节点就是拥有记录这个顺序的对象。

开始流程

开始流程是整个流程图的根节点。

// FlowStart represents the start of a control flow. For a function expression or arrow
// function, the node property references the function (which in turn has a flowNode
// property for the containing control flow).
export interface FlowStart extends FlowNodeBase {
node?: FunctionExpression | ArrowFunction | MethodDeclaration;
}

代码总是从一个函数开始执行的,所以开始流程也会记录关联的函数声明。

标签流程

如果执行的时候出现判断,则可能从一个流程进入两个子流程,即流程跳转,跳转的目标叫标签流程:

// FlowLabel represents a junction with multiple possible preceding control flows.
export interface FlowLabel extends FlowNodeBase {
antecedents: FlowNode[] | undefined;
}
antecedents 中文意思是祖先,其实是代表执行这个流程节点的上一个父流程节点。
比如上图例子中,编号 5 就是一个标签流程,其父流程分别是 e 和 f 所属流程。
 
代码中的循环也是以标签流程的方式出现的。

缩小类型范围的流程

理论上,每行节点都可能对变量的类型有影响,比如上图例子中,e 所在的位置在流程上可以确认 d == 3。

那么 d == 3 就是一种缩小类型范围的流程,在这个流程节点后面,统一认为 d 就是 3。

TS 目前并不支持将所有的表达式都按缩小类型范围的流程处理,只支持特定的几种表达式,甚至有些表达式如果加了括号就不认识。

这主要是基于性能考虑,这样可以更少创建流程节点。

TS 目前支持的流程有:

// FlowAssignment represents a node that assigns a value to a narrowable reference,
// i.e. an identifier or a dotted name that starts with an identifier or 'this'.
export interface FlowAssignment extends FlowNodeBase {
node: Expression | VariableDeclaration | BindingElement;
antecedent: FlowNode;
} export interface FlowCall extends FlowNodeBase {
node: CallExpression;
antecedent: FlowNode;
} // FlowCondition represents a condition that is known to be true or false at the
// node's location in the control flow.
export interface FlowCondition extends FlowNodeBase {
node: Expression;
antecedent: FlowNode;
} export interface FlowSwitchClause extends FlowNodeBase {
switchStatement: SwitchStatement;
clauseStart: number; // Start index of case/default clause range
clauseEnd: number; // End index of case/default clause range
antecedent: FlowNode;
} // FlowArrayMutation represents a node potentially mutates an array, i.e. an
// operation of the form 'x.push(value)', 'x.unshift(value)' or 'x[n] = value'.
export interface FlowArrayMutation extends FlowNodeBase {
node: CallExpression | BinaryExpression;
antecedent: FlowNode;
}

finally 流程

try finally 流程稍微有些麻烦。

try {
// 1
// ...(其它代码)...
// 2
} catch {
// 3
// ...(其它代码)...
// 4
} finally {
// 5
}
// 6

对于位置 5,其父流程是 1/2/3/4 (假设 try 中任何一行代码都可能报错)

对于位置 6,其父流程只能是 5,且此时的 5 的父流程只能是 2/4,

所以位置 6 的父流程节点是 finally 结束位置的节点 5,而节点 5 的父流程有两种可能:1/2/3/4 或只有 2/4

所以 TS 在 5 的前后插入了两个特殊的流程节点:StartFinally  和 EndFinally,

当遍历到 EndFinally 时,则给 StartFinally 加锁,说明此时需要的是 finally 之后的流程节点,否则说明需要的是 finally 本身的父节点

export interface AfterFinallyFlow extends FlowNodeBase, FlowLock {
antecedent: FlowNode;
} export interface PreFinallyFlow extends FlowNodeBase {
antecedent: FlowNode;
lock: FlowLock;
}
  export interface FlowLock {
    locked?: boolean;
  }

综合

以上所有类型的流程节点,通过父节点的方式组成一个图

export type FlowNode =
| AfterFinallyFlow
| PreFinallyFlow
| FlowStart
| FlowLabel
| FlowAssignment
| FlowCall
| FlowCondition
| FlowSwitchClause
| FlowArrayMutation;

 export interface FlowNodeBase {
    flags: FlowFlags;
    id?: number;     // Node id used by flow type cache in checker
}

如果将流程节点可视化,将类似地铁图,每个节点就是一个站点,站点之间的线路存在分叉,也存在合并,也存在循环。

自测题

为了帮助验证到此为止的知识点是否都已掌握,这里准备了一些题目:

1. 解释以下术语:

  • Token
  • Node
  • SyntaxKind
  • Symbol
  • Declaration
  • TypeNode
  • FlowNode
  • Increamentable
  • fullStart
  • Trivial

2. 阐述编译器从源文件到符号的流程步骤

3. 猜测编译器在生成符号后的操作内容

TS 原理详细解读(7)绑定1-符号的更多相关文章

  1. TS 原理详细解读(5)语法2-语法解析

    在上一节介绍了语法树的结构,本节则介绍如何解析标记组成语法树. 对应的源码位于 src/compiler/parser.ts. 入口函数 要解析一份源码,输入当然是源码内容(字符串),同时还提供路径( ...

  2. SpringMVC 原理 - 设计原理、启动过程、请求处理详细解读

    SpringMVC 原理 - 设计原理.启动过程.请求处理详细解读 目录 一. 设计原理 二. 启动过程 三. 请求处理 一. 设计原理 Servlet 规范 SpringMVC 是基于 Servle ...

  3. MemCache超详细解读

    MemCache是什么 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高 ...

  4. C++多态的实现及原理详细解析

    C++多态的实现及原理详细解析 作者: 字体:[增加 减小] 类型:转载   C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型 ...

  5. MemCache超详细解读 图

    http://www.cnblogs.com/xrq730/p/4948707.html   MemCache是什么 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于 ...

  6. MemCache详细解读

    MemCache是什么 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高 ...

  7. 详细解读Volley(三)—— ImageLoader & NetworkImageView

    ImageLoader是一个加载网络图片的封装类,其内部还是由ImageRequest来实现的.但因为源码中没有提供磁盘缓存的设置,所以咱们还需要去源码中进行修改,让我们可以更加自如的设定是否进行磁盘 ...

  8. 详细解读ARM寄存器之CPSR【转】

    本文转载自:https://blog.csdn.net/david_luyang/article/details/6276533 详细解读ARM寄存器之CPSR 整理人:卢阳 QQ:820927872 ...

  9. 【UGUI源码分析】Unity遮罩之RectMask2D详细解读

    遮罩,顾名思义是一种可以掩盖其它元素的控件.常用于修改其它元素的外观,或限制元素的形状.比如ScrollView或者圆头像效果都有用到遮罩功能.本系列文章希望通过阅读UGUI源码的方式,来探究遮罩的实 ...

  10. 最新一线大厂Redis使用21条军规及详细解读

    说明:个人原创,本人在一线互联网大厂维护着几千套集群,关于redis使用的一些坑进行了经验总结,希望能给大家带来一些帮助 适用场景:并发量大.访问量大的业务 规范:介绍军规内容 解读:讲解军规设置原因 ...

随机推荐

  1. LeetCode 1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit (绝对差不超过限制的最长连续子数组)

    给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit . 如果不存在满足条件的子数组,则返回 ...

  2. uniapp电子签名盖章实现详解

    项目开发中用到了电子签名.签好名的图片需要手动实现横竖屏旋转.并将绘制的签名图片放到pdf转换后的base64的图片上,可以手动拖动签名到合适的位置,最后合成签名和合同图片并导出.和以往一样,先发一下 ...

  3. 怎么理解vue的单向数据流

    单向数据流是父组件传给子组件的数据,子组件没有权利修改,只能委托父组件修改,然后子组件更新

  4. Promise.all、race和any方法都是什么意思?

    // // 执行多个并行任务 const promiseAll = [ thenFs.readFile('./files/1.txt','utf8'), thenFs.readFile('./file ...

  5. 74.数组map能干什么,会改变原数组吗

    map是处理数据的方法,不会改变原数组,会返回一个新数组 : filter 也不会改变原数组,会返回新数组 : forEach 也不会改变原数组,不会返回新数组 : reduce不会改变原数组 : 是 ...

  6. 亿图图示最新版本(Win和Mac)适用的最高45天免费会员,Edrawmax,MindMaster,windows和mac兼用免费会员

    活动随时都在变化,感兴趣的快点去领吧! 亿图图示:https://www.edrawsoft.cn/viral-marketing/Invited.html?s_uid=26349406&pr ...

  7. Android复习(二)应用资源——>菜单

    菜单资源定义可通过 MenuInflater 进行扩充的应用菜单,包括选项菜单.上下文菜单和子菜单. 有关使用菜单的指南,请参阅菜单开发者指南. 文件位置: res/menu/filename.xml ...

  8. 基于Jenkins + Argo 实现多集群的持续交付

    作者:周靖峰,青云科技容器顾问,云原生爱好者,目前专注于 DevOps,云原生领域技术涉及 Kubernetes.KubeSphere.Argo. 前文概述 前面我们已经掌握了如何通过 Jenkins ...

  9. KubeSphere 3.1.0 GA:混合多云走向边缘,让应用无处不在

    2021 年 4 月 29 日,KubeSphere 开源社区激动地向大家宣布,KubeSphere 3.1.0 正式发布!为了帮助企业最大化资源利用效率,KubeSphere 打造了一个以 Kube ...

  10. Fluent Operator 2.5.0 发布:新增多个插件

    日前,Fluent Operator 发布了 v2.5.0. Fluent Operator v2.5.0 新增 11 个 features, 其中 Fluent Bit 新增支持 7 个插件, Fl ...