V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
ignor
V2EX  ›  JavaScript

js 在 import 之前,为什么需要先显式声明 export 呢?

  •  
  •   ignor · 2022-08-04 11:09:43 +08:00 · 3595 次点击
    这是一个创建于 876 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这边有个写给自己用的前端项目,js 都是用原始的 script 标签逐个引入的,里面的各种函数和变量都是全局的,现在渐渐感到各种依赖关系有些麻烦,需要模块化了,所以初步学习了一下。

    看了一下 ES6 模块语法,似乎之前定义的那些全局变量都需要一个个添加 export……之前写 Java, python 都没有这样的困扰,是什么原因造成了这样的设计呢?或者是我打开方式不对?正确的模块化改造步骤是怎样的?

    第 1 条附言  ·  2022-08-04 12:13:36 +08:00
    像 Java 一开始会强制让你决定哪些是 public ,哪些 private ,这在 import 的时候就没啥问题
    python 直接默认为"public",import 时用户自行引入自己需要的部分
    到 js 这为什么就直接全都是"private"了呢?我既然愿意把一个变量定义为全局的,自然是希望别的地方能够方便地使用,为什么要默认成"private"呢?
    20 条回复    2022-08-18 20:58:21 +08:00
    wayh
        1
    wayh  
       2022-08-04 11:15:35 +08:00
    因为模块有自己的作用域,默认模块内部的变量函数都是不导出的,如果需要外界访问到就需要 export 导出,或者声明变量的时候,直接 window.xxx 这种 就不用就 export 了,但这种就是全局变量了,和你之前的 script 方式没有本质区别。
    westoy
        2
    westoy  
       2022-08-04 11:17:45 +08:00
    java 又不会产生一个文件导出几个 public 类这种问题

    python 有__all__限制默认导出变量, 只是一开始没考虑到, 印象里是 2.1 才后加进去的
    retrocode
        3
    retrocode  
       2022-08-04 11:18:25 +08:00
    如果你开心的话也可以写个闭包, 然后闭包全局工具类
    wangtian2020
        4
    wangtian2020  
       2022-08-04 11:21:06 +08:00
    ```
    let mInit = () => {
    globalThis.fk ='114514'
    }

    export { mInit }
    ```
    lisongeee
        5
    lisongeee  
       2022-08-04 11:29:39 +08:00
    js 模块化之后就可以《模块热替换》,这是前端构建工具最重要的特性之一,有了它就能极大地提高开发速度

    你改动单个文件,构建工具根据这个文件找到依赖边界,浏览器界面就能进行局部刷新,而不是重新刷新这个标签页

    比如你改动 vue 文件的 template ,你的界面上这个组件的区域就会刷新,但是你的状态还在,不会刷新整个页面

    ![hmr]( https://github.com/lisonge/src/raw/main/img/2022-07-18_18-00-12.gif)
    iidear2015
        6
    iidear2015  
       2022-08-04 11:37:13 +08:00
    script 标签直接引入的 js 文件里,var 声明的变量是挂在全局作用域下的,var xx = 1 和 window.xx = 1 一样。
    模块化之后的 js 文件里,var 声明的变量是挂在模块作用域下的,等于是模块的私有变量。只有 export 出去的变量才能被其他模块访问访问。编译器是通过闭包实现的


    模块化改造就是要消灭全局变量,如果还是要使用全局变量,使用 window.xx
    DOLLOR
        7
    DOLLOR  
       2022-08-04 11:38:09 +08:00
    上面都是答非所问的。
    楼主问的是别的语言(像 python )不需要写 export 关键字,就能在别的模块引入该模块的变量。
    而 JS 需要显式声明 export 变量,才能在别的模块里 import 进来。

    我也好奇这种不同的设计有什么优点缺点,出于什么考虑。
    aaronlam
        8
    aaronlam  
       2022-08-04 11:46:43 +08:00
    其实比较值得说的是,为什么 `import` 的方式不改成类似这样的 `from './demo.js' import { Demo }` 书写顺序,更加的符合直觉。
    dcsuibian
        9
    dcsuibian  
       2022-08-04 11:55:58 +08:00
    js 模块的话不 export 就是私有的,这就是封装嘛,不让你关注细节

    Java 有访问控制符啊,public 、protected 之类的。

    Python 倒是留给导入者决定的。
    但我感觉 JS 的 export 明显更好啊,看 export 部分就知道导出了啥,有哪些可用的。
    dcsuibian
        10
    dcsuibian  
       2022-08-04 11:59:27 +08:00
    @aaronlam
    我也感觉这么更好,如果这么写的话,那我写完 `from './demo.js' ` IDE 就可以推导了
    现在把 import 的放前面就很麻烦
    crysislinux
        11
    crysislinux  
       2022-08-04 12:17:58 +08:00 via Android   ❤️ 1
    @dcsuibian 这个问题很多相关讨论了,import 在后面一方面是跟 import 'xxx'这种引入整个包形式上更一致,二是往往我们更关心导入了什么,设想一下如果 from 后面的路径比较长会是什么样子。
    ailer
        12
    ailer  
       2022-08-04 12:21:55 +08:00 via Android   ❤️ 1
    @aaronlam from 的地址可能会很长,但意义不大,放后面可以突出导出的内容
    aaronlam
        13
    aaronlam  
       2022-08-04 13:04:33 +08:00
    @crysislinux
    @ailer

    是的,其实指出这种方式,是希望在导入时能利用上 IDE 的推导便于导入所需要的内容,不过如果路径很长的确是一种反作用。
    dcsuibian
        14
    dcsuibian  
       2022-08-04 13:55:16 +08:00
    @crysislinux 但实际上不是很长。
    ES 在给出模块化定义的时候,没给出推荐的形式(至少我是从没听人提起过),比如 Java 的域名反写,那确实到会是很长。
    SingeeKing
        15
    SingeeKing  
       2022-08-04 14:01:35 +08:00
    你可以换一种理解方式,把 JS 中的 export 当成 Java 中的 public 理解

    export let x = 123

    export function xxx() {}
    dcsuibian
        16
    dcsuibian  
       2022-08-04 14:02:31 +08:00
    @crysislinux 以前我在初学 npm 和 python 的 pip 的时候,就有过疑问:他们要怎么解决命名冲突?为啥不参考 maven 的 pom 坐标的方式?
    当时我在网上搜索的时候,得到的答案是:js 开发者倾向于简单的做法。(不记得在哪儿看到了)
    我接受了这个理由,所以才会觉得 ES 官方的导入语法不太合适。

    我个人感觉 JS 和 Python 是非常相近的,都是脚本语言,追求简单。npm install xxx 和 pip install xxx ,但 Python 就提供了 from xxx import xxx 的语法。
    lsdxl
        17
    lsdxl  
       2022-08-04 14:21:59 +08:00
    兄弟姐妹们,我有个和这不相干的问题咨询下:
    就是在 npm 安装了 vue3 最新版本,typescript 使用引入 vue 文件,我没有去手动增加 vue module 的声明,但是 vue 模块被识别了(调用 tsc 类型也可以通过),这难道源码某个声明文件有实现吗 我没找到 有了解的吗
    chnwillliu
        18
    chnwillliu  
       2022-08-18 20:27:48 +08:00 via Android
    那你先列举一下默认导出顶层作用域中的变量比默认不导出有什么优势。顶层变量不想导出还得专门引入一种语法来声明不是?像 python 一样用下划线么?还是用其他关键字?结果和默不导出不就是一回事吗?

    let a=1; //不导出
    export let b = 2; //加关键字导出,和 java 的 public 岂不是一样?
    export {a}; // export 还可以导出已有内部变量
    chnwillliu
        19
    chnwillliu  
       2022-08-18 20:37:12 +08:00 via Android
    下划线开头的变量名在 ES6 之前的版本中并没有实际意义,强行引入额外的语义只会挖坑。并且当时已经流行的社区 js 模块化方案 commonjs requireJS 都是显式声明导出的变量。显式声明导出也符合从无模块化到有模块化迁移的过程。那时候用闭包模拟模块肯定没法做到默认导出模块顶层作用域。
    ignor
        20
    ignor  
    OP
       2022-08-18 20:58:21 +08:00 via Android
    @chnwillliu 最开始项目小,单文件的时候定义的全局变量,就是该项目最公开的部分,那么拆成模块后,就理应是该模块最公开的部分,个人觉得这是很直观的。
    所以说到底还是历史原因吧,就是大家已经习惯了“闭包没办法导出顶层作用域”
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3574 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 10:26 · PVG 18:26 · LAX 02:26 · JFK 05:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.