如何在原生JavaScript中导入/导出类(class)

44
我正在使用纯JavaScript (JS)。现在,我正在尝试利用 ECMA-2015 (ECMA-6) 发布的 import/export 模块的概念。
请参阅下面的代码片段:

rectangle.js:

export default class  Rectangle{
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

myHifiRectangle.js:

import Rectangle from 'rectangle.js';

class MyHiFiRectangle extends Rectangle {
  constructor(height, width) {
      super(height,width);
      this.foo= "bar";  
 }
}

我正在尝试在名为test.html的HTML页面中引用上述JS文件(请参考代码片段):

<!DOCTYPE html>
<html lang = "en">
   <head>
      <meta charset = "UTF-8">
      <title>Javascipt by Rasik Bihari Tiwari</title>
       <script src="Scripts/rectangle.js"></script>
       <script src="Scripts/myHiFiRectangle.js"></script>
      <script type="text/javascript">
    
   var v = new MyHiFiRectangle(2,4);
   console.debug(v.foo);
      </script>
   </head>
   <body >

   </body>

</html>

然后,我尝试在浏览器中加载test.html。不同的浏览器结果不同。

在Google Chrome上,我得到以下错误:

Uncaught SyntaxError: Unexpected token export

在Mozilla Firefox上,我得到以下错误:

SyntaxError: export declarations may only appear at top level of a module

SyntaxError: import declarations may only appear at top level of a module

ReferenceError: MyHiFiRectangle is not defined[Learn More]

我尝试重新排列在HTML文件的head标签中引用的JS文件,但没有影响。

注意:再次明确,我没有使用任何像Babel这样的转换器。我正在尝试检查Vanilla JS中导出/导入classmodule构造的本地支持以及它是如何工作的。


我已经回答了你的问题。 - curious.netter
1
删除 <script src="Scripts/rectangle.js"></script> 并将 <script src="Scripts/myHiFiRectangle.js"></script> 替换为 <script src="Scripts/myHiFiRectangle.js" type="module"></script>。这解决了一个问题。另一个问题是 myHifiRectangle.js 没有创建全局变量(你应该停止使用它)。要解决这个问题,在 myHifiRectangle.js 的末尾添加 window.MyHiFiRectangle = MyHiFiRectangle; - connexo
4个回答

38

我已经通过了这个问题,并使用第三个js文件作为模块提供了一个解决方案。 rectangle.js 保持不变,而myHifiRectangle.js 文件仅有一个修改。

import Rectangle from './rectangle.js';

export default class MyHiFiRectangle extends Rectangle {
      constructor(height, width) {
      super(height,width);
      this.foo= "bar";  
   }
}

现在,我们需要第三个文件,这将是一个模块文件,假设叫做script.js

import MyHiFiRectangle from './myHifiRectangle.js'

var v = new MyHiFiRectangle(2,4);
console.log(v.foo);

现在,第三个文件 script.js 应该被制作成一个模块。关于模块的更多信息可以在这里找到。我已经将这三个文件放在 modelJS 文件夹下。

<script type="module" src="/modelJS/script.js"></script>

现在,当你运行时,你应该在开发者工具的控制台选项卡中看到“bar”被打印出来。


1
太棒了!我不知道module属性。可能是因为Chrome在提问时还没有实现它。顺便说一下,我能够在我的HTML文件中使用module属性来实现它,就像我自己的答案中提到的那样。我不需要单独的script.js文件来调用我的模块。我真的很感激你为我的旧问题付出的努力。今天我学到了新东西。 - RBT

5

在看了curiou.netter's answer的提示后,我添加了一个答案。

我将会按照原始代码文件中精确的顺序指出错误。顺便说一下,我能够在不涉及额外的script.js文件的情况下解决问题:

  1. While referring to JS modules, the script type should be module instead. I was referring to myHiFiRectancle.js like a regular JS file using src tag as:

    src="Scripts/myHiFiRectangle.js"
    

    I also imported MyHiFiRectangle module. Here is how the head tag now looks in the test.html file after fixing this error:

    <head>
      <meta charset = "UTF-8">
      <title>Javascipt by Rasik Bihari Tiwari</title>
      <script type="module">
         import MyHiFiRectangle from './scripts/myHiFirectangle.js';
         var v = new MyHiFiRectangle(2,4);
         console.debug(v.foo);
      </script>
    </head>
    
  2. export default statement was missing in myHiFiRectangle.js file. Every class has to be exported as a module when it has to be used from a different place. The rectified myHiFiRectangle.js file looks like below:

    import Rectangle from './rectangle.js';
    export default class MyHiFiRectangle extends Rectangle {
      constructor(height, width) {
      super(height,width);
      this.foo= "bar";  
     }
    }
    
  3. My script files had another error in the way I was importing modules.

    Incorrect way:

    import Rectangle from 'rectangle.js';
    import MyHiFiRectangle from '/scripts/myHiFirectangle.js';
    

    It causes below error which is self explanatory:

    Uncaught TypeError: Failed to resolve module specifier "rectangle.js". Relative references must start with either "/", "./", or "../".

    Correct way:

    import Rectangle from './rectangle.js';
    import MyHiFiRectangle from './scripts/myHiFirectangle.js';
    

1
我的文件扩展名命名为.mjs,这破坏了导入。因此,在保持type="module"的同时,我将文件扩展名切换为.js,现在它可以工作了。 - Frizzant

3

我想向您展示与@Andy Gaskell发布的解决方案不同的替代方案。

首先,您需要babel来确保您可以在浏览器中使用ES6。这是为了确保您的代码仍然可以工作,因为一些浏览器(像IE这样的旧版本)不支持现代JavaScript(ES6及以上)功能,例如import / export和类。

您可以添加以下脚本

`<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>`

首先,要在上面提到的任何其他javascript文件之前包含你的javascript文件。

其次,如果你将你的javascript类内联包含,那么这些类的作用域将变为全局,即使它们存在于它们自己的物理js文件中。

我在下面包含了一个工作示例,稍微改变了一下,以便它能在代码片段中运行。你要用包含你的javascript文件的脚本替换这个脚本,就像你在你的代码中所做的那样。

<!DOCTYPE html>
<html lang = "en">
   <head>
      <meta charset = "UTF-8">
      <title>Javascipt by Rasik Bihari Tiwari</title>
       <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>

      <!-- Replace them with script with src pointing to your javascript -->       
      <script type="text/javascript"> 
        class  Rectangle{
          constructor(height, width) {
            this.height = height;
            this.width = width;
          }
        }

        class MyHiFiRectangle extends Rectangle {
          constructor(height, width) {
              super(height,width);
              this.foo= "bar";  
         }
        }
           
       var v = new MyHiFiRectangle(2,4);
       console.log(v.foo);
       </script>
   </head>
   <body >

   </body>

</html>

更新

好的,很棒!顺便问一下,如果我将所有类定义都放在HTML页面的脚本标签中,那么我甚至不需要在head标签中引用babel-core。为什么还需要它呢?

你可能需要它来支持不支持类(如IE)的浏览器。但是,如果您的需求中不涉及旧版浏览器的兼容性,则不需要它。

...那我甚至需要导出-导入吗?在原生JavaScript中,当每个类几乎都是全局的时候,模块导出的重要性是什么?

确实,由于你的类是全局的,你不需要使用导出-导入。只有在您想使用模块系统时才会使用此功能。如果您不使用import/export,您的类应该是全局的,因此应该可以工作。但是,如果某种情况下未正常工作,您可以通过将其附加到window对象来确保它全局存在,例如:

 window.myClass = class MyClass { /* Class definition */ }

好的,很酷!顺便说一下,如果我将所有类定义带入HTML页面的脚本标签中,那么我甚至不需要在头标签中引用babel-core。为什么还需要呢?从ECMA-6开始,class构造函数已经在JavaScript中得到了本地支持。在使用本地JavaScript代码时,我应该不需要转译器。转译器本身并没有起到任何作用,但当它们被某些模块打包工具(如webpack或browserify)调用时,它们才会发挥作用。 - RBT
1
现在剩下的问题是,当我把我的类移动到单独的js文件中时,所有这些都不起作用。目前似乎出现了问题的是单独的js文件中发生的“export”和“import”。但是,如果我按顺序/必需的方式(它们被引用的顺序)包含了所有包含类文件的js文件,那么我甚至需要导出-导入吗?在本机JavaScript中模块导出的重要性是什么,当每个类或多或少都是全局的时候? - RBT
好的。所以根据模块系统(假设我们在当前浏览器已经实现了导出/导入功能的那一天),如果我在一个独立的js文件中使用export语句来导出一个类,那么它就会默认变成非全局的,并且只能通过ECMA6规范中可用的import关键字显式地导入到那些脚本文件中。这是一个公平的说法吗? - RBT
没错,我相信这是准确的。当您尝试导入一个没有 export ... 的文件(在您的 js 应用程序中)时,您可以看到这是真实的。试图导入该模块的文件将会报错。这是由于模块系统造成的。代码的作用域在文件内得以保留,除非它们被导入/导出。 - Mμ.
为了完整起见,您能否在回答中添加一条注释,指出由于各种浏览器供应商仍处于ECMA-6规范的实现阶段,导出/导入关键字今天不起作用。即使它们位于自己的物理*.js文件中,所有类默认都是全局的,因此如果我从中删除导出导入语句,我的代码片段也将正常工作。 - RBT

1

大多数浏览器中,这是通过功能标志启用的。

Chrome:转到about:flags并启用“实验性Web平台功能”。

Firefox 54版本及以上:启用dom.moduleScripts.enabled

Edge 15或更高版本:在about:flags中启用“实验性JavaScript功能”。


我可以看到访问Chrome标志的URL是chrome://flags/ - RBT
我按照你推荐的方法,在Chrome和Firefox中启用了隐藏的功能标志,但是当我浏览包含“class”构造的js文件的网页时,它们仍然在开发者工具控制台选项卡中给出相同的错误输出。 - RBT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接