更新时间:2024-10-21 GMT+08:00
分享

AstroZero脚本开发规则及约束限制

脚本性能检查规则

在AstroZero脚本编辑器中编写脚本代码时,请仔细阅读如下内容,了解脚本性能的检查规则。在AstroZero标准页面和高级页面中,对页面性能进行分析时,会分析页面所引用脚本的性能。如果脚本存在性能问题,界面会通过弹框提示您,方便您直观的发现脚本中的问题。如何在标准页面和高级页面进行性能分析,请参见查看AstroZero标准页面性能查看AstroZero高级页面性能

AstroZero低代码平台对脚本进行性能检查遵循的总体原则是:主要检查SELECT语句中,WHERE查询字段是否创建索引,另外对SELECT查询字段和WHERE限制条件进行检查。
  • 静态检查规则
    • 规则1:SELECT语句需要带上where条件

      SELECT语句中缺少查询条件,存在由于查询结果数量过大,导致脚本性能不佳的风险。请确认查询结果,如果查询结果数量过大,建议增加限制条件或采用分页查询,分页查询请见下方推荐代码示例。

      错误代码示例:
      import * as db from 'db';
      let errorDemo = db.sql().exec("select object_name from object_demo order by createddate desc")
      正确代码示例:
      import * as db from 'db';
      let correctDemo = db.sql().exec("select object_name from object_demo where id = ? order by createddate desc")
      推荐代码示例:
      import * as db from 'db';
      let se = db.sql()
      let sqlResult = []
      for(let i=0; i <1000; i++){
          let query_sql = se.exec("select object_name from object_demo limit ${5000*i},5000")
          sqlResult.push(query_sql)
          if(query_sql.length<5000){
              break
          }
      }
    • 规则2:SELECT语句中单次limit查询数量需要低于平台最高查询个数5000的限制

      SELECT语句中单次limit查询数量大于平台限制个数,存在由于查询结果数量过大,导致脚本性能不佳的风险。如果查询结果过大,请判断是否需要增加限制条件或采用分页查询,分页查询请见下方推荐代码示例。

      错误代码示例:
      import * as db from 'db';
      let errorDemo = db.sql().exec("select object_name from object_demo  where object_type = 'Test' limit 100000")
      正确代码示例:
      import * as db from 'db';
      let correctDemo = db.sql().exec("select object_name from object_demo  where object_type = 'Test' limit 3000")
      推荐代码示例:
      import * as db from 'db';
      let se = db.sql()
      let sqlResult = []
      for(let i=0; i <1000; i++){
          let query_sql = se.exec(`select object_name from object_demo limit ${5000*i},5000`)
          sqlResult.push(query_sql)
          if(query_sql.length<5000){
              break
          }
      }
    • 规则3:SELECT语句中谨慎使用区间查询条件

      SELECT语句中使用区间查询(“<>”、“<”、“>”、“>=”、“<=”),存在由于查询结果数量过大,导致脚本性能不佳的风险。如果必须采用区间查询,建议增加limit限制条件,以免影响查询效率。

      错误代码示例:
      import * as db from 'db';
      let errorDemo = db.sql().exec("select object_name from object_demo  where object_num > 50")
      正确代码示例:
      import * as db from 'db';
      let correctDemo = db.sql().exec("select object_name from object_demo  where object_num > 50 limit 3000")
    • 规则4:SELECT语句中查询字段不在表的索引库中

      如果SELECT语句where条件中,查询字段并未创建索引,请判断该字段是否需要创建索引,以提高代码查询效率。

      错误代码示例:
      import * as db from 'db';
      let errorDemo = db.sql().exec("select object_name from object_demo  where object_id = ?")

      表“object_demo”中的“object_id”并没有创建索引。

      正确代码示例:
      import * as db from 'db';
      let correctDemo = db.sql().exec("select object_name from object_demo where id = ?")

      表“object_demo”中的“id”创建了索引。

    • 规则5:SELECT语句中尽量避免同时从大于等于4张表中取数据

      SELECT语句中,进行多表关联查询时,尽量不要同时从大于或等于4张表中获取数据。如果必须要查询大于或等于4张表的数据时,建议先通过关联少于4张表进行查询,然后根据查询结果再做关联查询,保证每次关联查询的表数量少于4,以提高查询效率。

      错误代码示例:
      import * as db from 'db';
      let errorDemo = db.sql().exec("select a.item as item1, b.item as item2, c.item as item3 ,d.item as item4, e.item as itmem5 from object_demo1 as a object_demo2 as b, object_demo3 as c ,object_demo4 as d, object_demo5 as e where a.id=b.objectid and b.name = c.objectid and c.name = d.objectid and d.name = e.objectid and e.name ='Test'")
      正确代码示例:
      import * as db from 'db';
      let sqlResult1 = db.sql().exec("select c.item as item3, d.item as item4, e.item as itmem5 from object_demo3 as c ,object_demo4 as d, object_demo5 as e where c.name = d.objectid and d.name = e.objectid and e.name ='Test'")
      let sqlResult = []
      let item3s = "'"+sqlResult1.map(item=>{item.item3}).join("','")+"'"
      let sqlResult2 = db.sql().exec('select a.item as item1, b.item as item2, c.item as item3 from object_demo1 as a object_demo2 as b, object_demo3 as c where a.id=b.objectid and b.name = c.objectid and c.item in ('+item3s+')')
      for(let i in sqlResult1){
       for(let j in sqlResult2){
        if i.item3 == j.item3:
         sqlResult.append({
          "item1":j.item1,
          "item2":j.item2,
          "item3":i.item3,
          "item4":i.item4,
          "item5":i.item5
         })
       }
      }
    • 规则6:SELECT语句中聚集函数必须增加别名

      SELECT语句中,聚合函数必须使用别名方式存储查询结果,以免因聚合函数返回的结果不一致,导致存在兼容性问题。

      错误代码示例:
      import * as db from 'db';
      let errorDemo = db.sql().exec("select count(*) from object_demo where object_name = 'Test'")
      正确代码示例:
      import * as db from 'db';
      let correctDemo = db.sql().exec("select count(*) as count from object_demo where object_name = 'Test'")
      推荐代码示例:
      ***聚合函数示例 ****
      select count(*) as count_res,
      select max(*) as max_res,
      select min(*) as min_res,
      select avg(*) as avg_res,
      select sum(*) as sum_res  
    • 规则7:SELECT语句中严禁使用“select from...”形式查询语句

      严禁使用“select ...”形式查询语句,请指出select的具体字段。

      错误代码示例:
      import * as db from 'db';
      let errorDemo = db.sql().exec("select from object_demo where object_name = 'test'")
      正确代码示例:
      import * as db from 'db';
      let correctDemo = db.sql().exec("select id, object_type from object_demo where object_name = 'test'")    
    • 规则8:SELECT语句中拼接的参数值请谨慎使用入参变量

      SELECT语句中,拼接的参数值请谨慎使用入参变量,以免引起SQL注入的风险。

      错误代码示例:
      import * as db from 'db';
      let errorDemo = "select id,name from object_demo where id = ";
      errorDemo += input.parameter 
      let errorDemoResult = db.sql().exec(errorDemo)

      其中,“input.parameter”为脚本入参。

      正确代码示例:
      import * as db from 'db';
      let correctDemo = "select id,name from object_demo where id = ?";
      let correctDemoResult = db.sql().exec(correctDemo, { params: [input.parameter] })

      其中,“input.parameter”为脚本入参。

    • 规则9:“for”循环中请谨慎使用SELECT语句

      “for”循环中,请谨慎使用SELECT语句,以免后续进行结果赋值时,由于数据过多,导致内存溢出。

      错误代码示例:
      import * as db from 'db';
      for (let i = 0; i < input.para.length; i++) {
        db.sql().exec("select id,name from object_demo where id = Test'");
      }
      正确代码示例:
      import * as db from 'db';
      db.sql().exec("select id,name from object_demo where id = 'Test'");
    • 规则10:SELECT语句中谨慎使用order by

      SELECT语句中,请谨慎使用order by。如果需要使用order by,请为排序字段增加索引,以提高查询效率。如果无法增加索引,需要关注是否存在查询性能低下的风险。

      错误代码示例:
      import * as db from 'db';
      let errorDemo = db.sql().exec("select object_name from object_demo  where object_id = 'Test' Orde by createdDate") 

      表“object_demo”中的“createdDate”,并没有创建索引。

      正确代码示例:
      import * as db from 'db';
      let correctDemo = db.sql().exec("select object_name from object_demo where id = 'Test' Order by createdDate")

      “object_demo”中的“createdDate”,创建了索引。

  • 动态检查规则

    脚本运行时,触发动态检查。动态检查规则以静态检查规则为基础,无法检查SQL注入以及for循环中的SQL项。

    • 当存在脚本性能问题时,通过弹框界面提示给用户,方便用户直观发现脚本中的问题。
    • 当不存在性能问题时,便不展示弹框页面,以免影响用户开发。

脚本开发规范及限制

在AstroZero脚本编辑器中编写脚本代码时,建议您先了解脚本编写的规范和限制,便于快速地完成脚本代码的编写。

  • 命名限制

    所有的名称定义要能体现其作用,避免使用缩写(专有名词除外)。

    • 脚本采用小驼峰命名,例如createDeviceInstance。
    • 结构体(struct)采用大驼峰命名,例如QueryPaymentResult。
    • 结构体内的字段采用小驼峰命名,例如customerName 。
    • 类、枚举值和接口采用大驼峰命名。
      class Comments {...}
      interface Console {...}
      enum Direction { Up = 1, Down, Left, Right}
    • 函数采用小驼峰命名。
      addOfferingAssociatePriceAndStock(input: Input): Output {...}
    • 属性或变量采用小驼峰命名。
      class Comments {
                 userId: String;
                 content: String;
             }
      let oneComments = newComments();

      变量为单数时,命名包含对象名称,如level1Catalog、level2Catalog。变量为复数时,命名包含集合名称,如attributeRelationRuleList。

  • 脚本定义规则
    • 每个字段的定义,均需要定义type、label、description、required和isCollection,有默认值的非必填。
    • 当字段为集合类型时,需要定义成“[]”
      @action.param({
          type: "Attribute",
          label: "Attribute",
          description: "attributeList",
          required:false
          isCollection: true
      })
      attributeList: Attribute[];
    • @action.object时,需要在脚本中详细定义清楚Object,不要引用其他脚本的Object。
    • 如果是嵌套结构体,则从下到上粒度依次变小。
      export class ProductObject {
             //ignore
      }
      
      export class StockObject {
             //ignore
      }
      
      export class ProductStock {
          @action.param({ type: 'Struct', label: "ProductObject", isCollection: false })
          productInfomation: ProductObject;
      
          @action.param({ type: 'Struct', label: "StockObject", isCollection: false })
          stock: StockObject;
      }
      
      @action.object({ type: "param" })
      export class Input {
          @action.param({ type: 'String', label: "productId", isCollection: false })
          productId: String;
      }
    • 不需要定义多个Output对象,可以直接在方法使用定义的对象出参。

      【推荐】:

      @action.object({ type: "param" })
      export class Input {
          @action.param({ type: 'String', label: "productId", isCollection: false })
          productId: String;
      }
      
      @action.object({ type: "param" })
      export class getProductStock {
      @action.method({ input: "Input", output: "ProductStock", label: 'getProductStock' })
      getProductStock(input: Input): ProductStock {

      【不推荐】:如果出参对象包含从外部引入的对象,还是要按该方式定义。

      @action.object({ type: "param" })
      export class Input {
          @action.param({ type: 'String', label: "productId", isCollection: false })
          productId: String;
      }
      
      @action.object({ type: "param" })
      export class Output {
          @action.param({ type: 'Object', isCollection: false })
          ProductStock: ProductStock;
      }
      
      @action.object({ type: "param" })
      export class getProductStock {
      @action.method({ input: "Input", output: "Output", label: 'getProductStock' })
      getProductStock(input: Input): Output {
    • 除非业务有特殊要求,新增修改脚本不返回结果码和结果信息。
  • 脚本注释的原则和写法
    无用的代码不能以注释形式存在,能用代码说明的尽量不要添加注释,脚本注释尽可能简洁。建议注释统一用英文,出入参不必写注释说明。
    • 在函数方法和结构体的元数据描述上,添加注释。
      /**
      * 根据产品ID查询产品详情信息
      */
      @action.method({ input: "Input", output: "Output", label: 'queryProductDetailForCart' })
    • 方法内关键业务语句前,必须添加注释。
      • 方法内的单行注释以“//”开头,应放在相关代码的上方或右方,不可放于下方。如果放于上方,注释需与前面的代码间用空行隔开。
      • 注释与代码的比例没有量化标准。在删掉所有的代码内容、仅保留代码层级结构和注释,如果通过注释,可以很容易理解方法内都做了哪些事情(类似于伪代码),则认为注释比例合理。
      // 校验密码是否正确
      let password = input.password;
      if (accountRecord["Password"] != password) {
      error.name = "CM-001003";
      error.message = "Invalid loginId or password.";
      throw error;
      }
  • 脚本间相互引用规则
    • 脚本中不要包含没用到的标准库或对象的引用。
      例如,如果实际没用到sys模块的任何方法,则不要包含如下语句:
      import * as sys from 'sys';

      如果实际上只用到了CA_PC_Offering对象,则示例语句中其他对象需要删除:

      @useObject(['CA_PC_FeeItemDef', 'CA_PC_Offering', 'CA_PC_CatalogOffering', 'CA_PC_Catalog', 'CA_PC_OfferingAttribute'])
    • 只引用使用到的对象,而不用import *。

      【推荐】:

      import { OfferingObjectQuery } from './ca_pc__getOfferingObject';
      import { setI18nError } from 'context';
      import { sql } from 'db';

      【不推荐】:

      import * as queryOfferingObjectAction from './ca_pc__getOfferingObject';
      import * as context from 'context';
      import * as db from 'db';
    • @action.method属于必须的方法注解,写在方法上面。
      @action.method({ input: "Input", output: "Output", label: 'queryClassification' })
      queryClassification(input: Input): Output {
      ...
      }
    • 为方便页面直接调用脚本,仅在脚本最后统一导出需要的对象,而不是导出所有对象。如果不需要,可以不用导出。

      例如,getOfferingObject脚本中最后一行导出OfferingObjectQuery对象:

      export let theAction = new OfferingObjectQuery();
    • 可以把公共的对象Object定义,按分类放在一些公共的脚本里(例如pc_XXX.ts、cm_XXX.ts),其他需要的脚本直接引用即可。
  • 语法规则
    • 对象判空

      单个对象示例:

      if(object){
          //ignore
      }

      集合对象示例:

      if(collection&&collection.length!=0){
          //ignore
      }
    • 数字类型统一定义成“Number”、日期类型定义成“Date”
      @action.param({
          type: "Date",
          label: "effectiveTime",
          description: "Date."
      })
      effectiveTime: Date;
      
      @action.param({
          type: "Number",
          label: "salePrice",
          description: "salePrice."
      })
      salePrice: number;
    • 变量或数组定义时,要说明类型,集合需要加上泛型。
      let isDone:Boolean = false;
      let decLiteral : number =6;
      let productList = new Array<productObject>();
    • 遍历循环推荐用“forEach”,不推荐用“for…in”
      testArray.forEach((value, index, array )=>{
             //ignore
      });
    • 推荐用“let”变量声明,不推荐用“var”

      【推荐】:

      let offeringId : String = “aaa”;

      【不推荐】:

      var offeringId = “aaa”;
    • 前段代码有对象声明时,推荐使用“.fieldName”获取对象的字段,而不是用“[‘fieldName’]”

      【推荐】:

      offeringStruct.id = result[i].base.offeringId;

      【不推荐】:

      offeringStruct.id = result[i][‘base’][‘offeringId’];
    • 在需要默认值的情况下,使用“||”代替“if”判断。
      function(value){
             //使用||对value进行判空
             value = value || “hello”
      }
    • 创建一个已知对象时,推荐用“new”方法。

      【推荐】:

      let offeringIdRequest = new OfferingIdRequest();
      offeringIdRequest.catalogList = catalogList;
      offeringIdRequest.classificationList:=classificationList;
      let getOfferingIdByConditionInput = {
             "OfferingIdRequest": offeringIdRequest
      }

      【不推荐】:

      let getOfferingIdByConditionInput = {
             "OfferingIdRequest": {
                    "catalogList": catalogList,
                    "classificationList": classificationList,
                    "status": "",
                    "stockCondition": "",
                    "keyWord": "",
                    "offset": "",
                    "limit": ""
             }
      }
    • 函数定义
      let traitRec = function(xxxx,xxx) {
          //ignore
       }

      只能在函数定义后的语句中,使用该函数。

    • 没有初始化值的变量申明,使用“undefined”,不要使用“null”
      let object = undefined;
    • 局部变量需要在“class”内定义,不要在全局命名空间内定义类型/值(即不要在“class”外定义变量),常量可以定义成全局。
      let identityIdList = [];
    • 使用“lambda”表达式代替匿名函数。

      只有需要时,才把“arrow”函数的参数括起来。正确使用“arrow”的示例如下:

      x => x + x
      (x,y) => x + y
      <T>(x: T, y: T) => x === y
  • 脚本SQL语句规则
    • 不推荐用拼接SQL方法,避免注入风险。
      let sql = "select id,name,ExternalCode,FeeType,FeeItemName,FeeItemDescription,Remark,Status from CA_PC_FeeItemDef where 1=1";
      if (!InputParams.id && !InputParams.name && !InputParams.feeType && !InputParams.feeItemName && !InputParams.status) {
             context.setI18nError("ca_pc__001013");
             return;
      }
      if (InputParams.id) {
             sql += "and id ='" + InputParams.id + "'"
      }
    • 多表复杂查询建议用“sql.exec()”“sql.excute()”方法,“excute()”方法比“exec()”多返回字段集和操作成功数。
      let result = execsql.exec("select id,name,OfferingId,ParentId,SkuCode,ChannelId,Status,ProductLabel,PriceCode,DefaultChoose,PaymentType from CA_PC_Product where id in (" + str + ")",
      {
             params: productId
      });

      多表复杂查询只能使用拼接SQL方法,但是有限制,例如示例中的“str”要求如下:

      • str如果来源于入参,则入参在拼接SQL之前需要进行校验,以免引入SQL注入攻击。如果来源于内部数据,可以不进行校验。
      • 不推荐直接在SQL中拼接入参,应该采用在SQL中拼接占位符,然后把入参放入参数的数组中,例如:
        attriSql = "select AttrDef from DE_DeviceDefAttri where DeviceDef=? and ExternalCode=? and AttrType='DYNAMIC' and ValueType='1'";
        attriRecords = db.sql().exec(attriSql, {
        		params: [deviceDefId, defExternalCode]
        	});
    • 对于单表查询和增删改SQL,推荐使用Orm接口方法。

      Options选项可以选择返回字段、排序、聚合运算和分页等功能。当value不存在时,默认为null类型。

      let CA_PC_Stock = db.object('CA_PC_Stock');
      let amountCount = 0;
      if (UpdateStock.amount) {
             let record = { Amount: UpdateStock.amount };
             amountCount = CA_PC_Stock.updateByCondition({
                    "conjunction": "AND",
                    "conditions": [{
                           "field": "SkuCode",
                           "operator": "eq",
                           "value": UpdateStock.skuCode
                    }]
             }, record);
      }
    • 避免在循环中调用方法和操作数据库,可以用“in”来查询在集合中的结果。
      let productIdList = input.productId;
      let str = "";
      let arr = [];
      for (let i = 0; i < productIdList.length; i++) {
             arr[i] = "?";
      }
      str = arr.toString();
      let result = execsql.exec("select id,name,OfferingId,ParentId,SkuCode,ChannelId,Status,ProductLabel,PriceCode,DefaultChoose,PaymentType from CA_PC_Product where id in (" + str + ")",
      {
             params: productIdList
      });
    • 对sql进行优化时,尽量使用有索引的字段,避免使用没有索引的字段。
    • 批量操作数据库时,尽量使用已封装好的批量操作接口。

      例如,Orm.batchInsert、Orm.batchUpdate接口和Orm.deleteByCondition批量删除接口。如果批量创建父子对象记录,且批量创建的记录在一个完整的事务中,全部成功或全部失败,建议使用Orm.compositeInsert接口。

      var s = db.object('Customer__CST');
      var records = [];
      var record = {
          "name": "hello",
          "count__CST": 123,
          "Contacts": {
              "records": [
                  {
                      "name": "hello_contact1"
                  },
                  {
                      "name": "hello_contact2"
                  },
              ]
          }
      };
      records.push(record);
      var ids = s.compositeInsert(records);
      console.log("id list = ", ids);
      
      count = s.count();
      console.log("record count = ", count);
    • 匹配查询推荐用like,日期比较推荐用“<”“>”

      【推荐】:

      select id from t where name like ‘abc%’
      select id from t where createdate>=’2005-11-30’ and createdate<’2005-12-1’

      【不推荐】:

      select id from t where substring(name,1,3)=’abc’
      select id from t where datediff(day,createdate, ‘2005-11-30’)
    • 使用exists替代in,使用not exists替代not in。

      【推荐】:

      SELECT * FROM EMP (基础表) WHERE EMPNO > 0 AND EXISTS (SELECT ‘X’FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB’)

      【不推荐】:

      SELECT * FROM EMP (基础表) WHERE EMPNO > 0 AND DEPTNO IN(SELECT DEPTNO FROM DEPT WHERE LOC =’MELB’)
    • 避免在索引列上使用is null和is not null,会造成索引失效。

      【推荐】:

      SELECT … FROM DEPARTMENT WHERE DEPT_CODE >=0

      【不推荐】:

      SELECT … FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL
    • 注意事项
      • 尽量避免Select子句中,使用“*”。
      • 尽量避免在where子句中,使用!=或<>操作符。
      • 尽量避免在where子句中,对字段进行函数操作。
      • 尽量避免在SQL中,使用 “!=” 、“||”、“+”符号。
      • 尽量避免在where条件中,做数据筛选。
      • 尽量避免查询中,按字段排序。
      • 尽量避免多表关联查询和嵌套查询,不要使用超过2表的关联查询。
      • 不要在循环内重复使用同一条件查询,应该在循环外处理。例如,公共数据仅在循环外查询一次。
      • 不要在同一个脚本的多个方法内,使用同一条件多次查询,可以定义类的成员变量。
      • 避免关联查询

        关联的条件很多情况下都是唯一的,可以提前做单独查询。例如,使用公共数据作为后续条件,避免关联查询。

      • 避免频繁的数据库交互

        例如,查询5000条数据,查询补充数据的时候,不要在循环内多次交互数据库,把可以合并的条件在循环外拼接并进行一次性查询,在循环内只需要从结果集中获取数据,可以极大提升查询性能。

      • 尽量利用对象做临时缓存

        例如查询到DeviceDef后,按照“id:Object”的方式存起来,后续查询时先判断缓存对象中是否已经存在,如果存在则直接获取不再查询。

  • 脚本代码风格限制
    • 每句话后面加分号,脚本写完右键选择“Format Document”统一格式。
    • string类型赋值统一使用双引号,获取字段统一使用单引号。
    • 相关代码写在一起,不相关逻辑最好以空行隔开。
    • 总是使用“{}”把循环体和条件语句括起来。
    • 开始的“{”总是在同一行。
    • 小括号里开始不要有空白逗号,冒号、分号后要有一个空格。
      for (var i = 0, n = str.length; i < 10; i++) { }
      if (x < 10) { }
      function f(x: number, y: string): void { }
    • 每个变量声明语句只声明一个变量。

      例如,使用如下方式

      var x = 1; var y = 2; 

      而不是下面的方式

      var x = 1, y = 2;
    • else要在结束的“}”后,另起一行。
    • 一个函数仅完成一件功能,即使是简单功能也应该编写单独的方法实现。
    • 单个方法的方法体不要太长,建议控制在150行以内,保证代码可读性,也方便维护、测试。
  • 脚本扩展名限制

    因为脚本实际上是存在数据库中的,所以脚本没有路径的概念,扩展名也没有特别的意义。

    导入模块时,尽量采用不带扩展的方式。如果必须要带扩展名,只允许“.ts”扩展名文件。

    import * as circle from './circle';
  • 脚本循环依赖限制

    当循环调用模块时,一个模块可能在未完成执行时被返回。因此,需要仔细地规划模块间调用,以允许循环模块依赖在应用程序内正常工作。

    例如,脚本a:

    console.log('a 开始');
    exports.done = false;
    import * as b from 'b';
    console.log('在 a 中,b.done = ', b.done);
    exports.done = true;
    console.log('a 结束');

    脚本b:

    console.log('b 开始');
    exports.done = false;
    import * as a from 'a';
    console.log('在 b 中,a.done = ', a.done);
    exports.done = true;
    console.log('b 结束');

    脚本main:

    console.log('main 开始');
    import * as a from 'a';
    import * as b from 'b';
    console.log('在 main 中,a.done = ', a.done ',b.done = ', b.done);

    当main加载a时,a又加载b。 此时,b又会尝试去加载a。 为了防止无限的循环,会返回一个a的exports对象的未完成的副本给b模块。然后b完成加载,并将exports对象提供给a模块。当main加载这两个模块时,都已完成加载,因此该程序的输出会是:

    main 开始
    a 开始
    b 开始
    在 b 中,a.done = false
    b 结束
    在 a 中,b.done = true
    a 结束
    在 main 中,a.done=true,b.done=true

相关文档