• TS 原理详细解读(7)绑定1-符号


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

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

    符号

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

    这些定义统称为符号(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. 猜测编译器在生成符号后的操作内容

  • 相关阅读:
    android实现简单计算器
    象牙塔尖
    Jquery 限制文本框输入字数【转】
    jquery 文字向上滚动+CSS伪类before和after的应用
    鼠标移入 移出div div会消失的处理
    ionic 项目分享【转】
    JS+CSS简单实现DIV遮罩层显示隐藏【转藏】
    封装鼠标滚轮事件,兼容方法。。。拿去用吧
    3、bootstrap3.0 栅格偏移 布局中的一个特产
    html input[type=file] css样式美化【转藏】
  • 原文地址:https://www.cnblogs.com/xuld/p/12359115.html
Copyright © 2020-2023  润新知