Javascript 为什么从';进行相对导入';父索引文件导致异步模块解析行为?

Javascript 为什么从';进行相对导入';父索引文件导致异步模块解析行为?,javascript,typescript,import,dependencies,Javascript,Typescript,Import,Dependencies,我有一份回购协议,其结构如下: src/ index.ts main.ts bar/ file.ts index.ts foo/ fooFile.ts src/index只是一个顶级索引文件,用于导出包中的所有内容 但是,我正在跟踪的行为实际上需要调用此文件中的某些功能,我稍后将对此进行介绍 src/bar/file.ts导出普通字符串 src/foo/fooFile.ts从父级导入该字符串。索引 // comments relat

我有一份回购协议,其结构如下:

src/ 
   index.ts
   main.ts
   bar/
      file.ts
      index.ts
   foo/
      fooFile.ts
src/index
只是一个顶级索引文件,用于导出包中的所有内容

但是,我正在跟踪的行为实际上需要调用此文件中的某些功能,我稍后将对此进行介绍

src/bar/file.ts
导出普通字符串

src/foo/fooFile.ts
从父级
导入该字符串。
索引

// comments relate to what happens if you run `node lib/index.js`
import { fileName } from "..";
import {fileName as fileName2} from "../bar"; 

export const test = "test"; 

const myData = {
    data: fileName,  // This resolves to undefined, 
    data2: fileName2 //This resolves to "bar"
}; 


export function main() {
    console.log(myData); 
    console.log(fileName);  // This resolves to "bar"
}
如果my
src/index.ts
看起来像:

import {main} from "./foo/fooFile";
export * from "./bar"; 
export * from "./foo/fooFile"; 

main();
然后我们得到了这种异步行为—从
导入
fileName
在声明
myData
常量时解析为未定义,但在运行时解析字符串

而如果我从
。/bar
导入,则在这两个实例中都会得到字符串

即产出:

{ data: undefined, data2: 'bar' }
bar
但是,这种行为似乎只有在我从索引文件调用
main()
函数时才会发生。如果我在
main.ts中执行相同操作

import {main} from "./index";

main();
我不明白这种行为

{ data: 'bar', data2: 'bar' }
bar
我想,这种行为的原因是节点模块解析和循环依赖性——有人能确切解释为什么会发生这种行为吗

此处的回购协议:


请注意,我已经使用TypeScript创建了此复制-我想这不是问题的原因-但这是我复制实际面临的问题的最佳方式。

你是对的,这是因为循环依赖关系

那么在第一种情况下会发生什么呢

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
var fooFile_1 = require("./foo/fooFile");
__exportStar(require("./bar"), exports);
__exportStar(require("./foo/fooFile"), exports);
fooFile_1.main();
  • 开始处理
    索引.ts
  • index.ts
    imports
    foo/fooFile.ts
  • 开始处理
    foo/fooFile.ts
  • foo/fooFile.ts
    imports
    index.ts
  • index.ts
    已经在处理中,在nodejs中,您将使用
    export
    对象并为其分配属性,然后在
    require
    此文件的某处时,此对象将从
    require
    函数返回。如果存在循环依赖项,则返回“未完成的”
    导出
    对象,因此它获取已分配给它的属性,但nodejs不会尝试继续执行导出该对象的文件,以获取由于循环依赖项而丢失的属性。因此,在本例中,“未完成的”
    导出
    对象只是一个空对象,因为我们没有导出任何内容。这意味着,如果您在
    foo/fooFile.ts
    中从“…”将*作为obj导入,
    obj
    将只是一个空对象。由于导入了
    {fooFile}
    fooFile
    将变得未定义
  • 过程的其余部分应该很清楚,当
    main
    函数启动时,
    index.ts
    已经导出了一些变量,包括
    file
    变量,因此
    main
    函数能够使用它

    现在是第二个案例。我假设要看到这个问题,我应该删除
    index.ts
    中的
    main()
    行,因为如果我没有-我没有观察到你在说什么,它仍然显示
    {data:undefined,data2:'bar}

    所以我删除了
    index.ts
    中的
    main()
    行。看起来一切都应该是一样的,因为不管怎样,您导入的都是相同的
    index.ts
    文件,如果不是因为一个小问题,事实上也是如此:

    请注意,我已经使用TypeScript创建了这个repu-我想这不是问题的原因

    实际上有点像。如果编译此项目并在这两种情况下打开
    index.ts
    文件,您将看到这一点。第一种情况:

    "use strict";
    var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
    }) : (function(o, m, k, k2) {
        if (k2 === undefined) k2 = k;
        o[k2] = m[k];
    }));
    var __exportStar = (this && this.__exportStar) || function(m, exports) {
        for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
    };
    Object.defineProperty(exports, "__esModule", { value: true });
    var fooFile_1 = require("./foo/fooFile");
    __exportStar(require("./bar"), exports);
    __exportStar(require("./foo/fooFile"), exports);
    fooFile_1.main();
    
    第二种情况,当我注释掉
    main()

    注意到区别了吗?如果删除
    main()。通常,typescript不会删除它,因为即使导入是无用的,它也可能会产生一些副作用,但在这里,您还是可以稍后从
    fooFile
    导入一些内容:
    export*from./foo/fooFile'
    。嗯,不是导入而是重新导出,但是在nodejs的上下文中,这没有任何区别

    所以现在一切都开始有意义了:

  • 开始处理
    main.ts
    ,导入
    index.ts
  • 开始处理
    index.ts
  • index.ts
    导入
    /bar
    ,获取
    文件名
    ,然后将其导出。现在这个“未完成的”
    导出
    对象不是空的,它包含
    {fileName:'bar'}
  • index.ts
    导入
    /foo/fooFile
  • /foo/fooFile
    导入
    index.ts
    ,但它已导出
    fileName
    ,因此可以使用它
  • 其余的应该是清楚的

    你可以向自己保证,这是实际发生的事情,但改变了进口的顺序。手动在开始时向编译的
    index.js
    文件添加一行
    require('./foo/fooFile')
    ,这样可以像第一种情况一样保留导入顺序,并且不会发生任何更改。或者在
    index.ts
    中交换
    /bar
    /foo/fooFile
    导入,这将是相同的

    这部分是由typescript造成的,因为如果您只是使用nodejs运行这些文件,它不会删除顶部未使用的导入,并且不会再次发生任何更改