注册 登录

清河洛

ES6中模块的import加载

qingheluo2023-05-08清河洛379
在一个模块文件中,使用export语句定义了模块的对外接口以后,其他JS脚本就可以通过import语句加载这个模块中的导出变量import语句可以出现在脚本顶层代码中的任何位置,如果处于块级作用域内会报错,因为处于条件代码块之中,就没法做静态优化了,违背了ES6模块的设计初衷引入语句:使用 "import {vals} from file" 格式引入import { name, age } from './demo.js'; console.log(`我的名字:${name},我的年龄${age}`); 变量重命名:使用关键字 asimport { name as m...

在一个模块文件中,使用export语句定义了模块的对外接口以后,其他JS脚本就可以通过import语句加载这个模块中的导出变量

import语句可以出现在脚本顶层代码中的任何位置,如果处于块级作用域内会报错,因为处于条件代码块之中,就没法做静态优化了,违背了ES6模块的设计初衷

引入语句:使用 "import {vals} from file" 格式引入

import { name, age } from './demo.js';
console.log(`我的名字:${name},我的年龄${age}`);

变量重命名:使用关键字 as

import { name as myname, age as myage } from './demo.js';
console.log(`我的名字:${myname},我的年龄${myage}`);

整体引入:使用星号"*"和as关键字可以一次性导入模块中的所有变量

import * from './demo.js';
    // 导入模块中的所有变量,忽略默认接口
import { * as obj } from './demo.js';
    // 此时我们可以将所有引入的变量作为一个整体对象,每个变量为该对象的一个属性
console.log(obj.name);
console.log(obj.sleep());
    // 注意,模块整体加载所在的那个对象,应该是可以静态分析的,所以不允许运行时改变
    // 不可以改变其属性值,不可以添加或删除属性

引入的变量为只读

import语句引入的变量都是只读的,因为它的本质是输入接口

对其重新赋值就会报错,但是,如果是一个对象,改写其属性是允许的

并且由于其动态绑定的特性,改写一个对象的属性值后,其他模块也可以读到改写后的值。不过,这样很难查错,建议所有变量都当作完全只读,不要轻易改变它的属性

执行提升

import语句具有提升效果,会提升到整个脚本的头部首先执行

这种行为的本质是,import命令是编译阶段执行的,在代码运行之前

console.log(name);
import { name } from "./demo.js";

上例不会报错,因为import的执行早于name的输出

静态引入

由于import是静态执行,所以不能使用表达式和变量等只有在运行时才能得到结果的语法结构

import { 'f' + 'oo' } from 'my_module';  // 报错

let module = 'my_module';
import { foo } from module;  // 报错

if (x === 1) {               // 报错
  import { foo } from 'module1';
}
上面的例子中在静态分析阶段,这些语法都是没法得到值的,所以都会报错

引入时执行

import语句在引入模块时会执行所加载的模块文件,但多次引入同一个模块时仅执行一次

import "path/mod.js";
import "path/mod.js";
// 仅执行mod模块文件,但不引入任何值
// 两次引入也只会执行一次

import { name } from "path/mod.js";
import { age } from "path/mod.js";
// 两次从同一个模块文件中引入变量,模块文件仅执行一次
// 等同于 import { name , age } from "path/mod.js";

引入属性

模块功能在浏览器中默认是不开启的,如果需要使用模块功能,需要<script>标签中的type属性值设为"module"

只有设置了type属性值,在通过src加载的js文件或标签中的js脚本才可以使用模块功能

否则加载的js文件或标签中的js脚本中出现import语句会报错

<script type="module" src="path/index.js"></script>
<script type="module">
    // some_code
</script>

浏览器对于带有type="module"的<script>,都是异步加载,等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性

我们也可以显式的设置为async,这样只要加载完成,渲染引擎就会中断渲染立即执行

ES6的模块功能为了利于编译器提高效率,采用静态分析,先于模块内的其他语句执行,这导致无法在运行时加载模块,在语法上,条件加载就不可能实现

ES2020提案引入import()函数,支持动态加载模块

import()返回一个 Promise 对象

import(`path/${val}.js`)
.then(module => {module.loadPageInto(main);})
.catch(err => {main.textContent = err.message;});

import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用

它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块

import()函数与所加载的模块没有静态连接关系,这点与import语句不相同

由于import()返回 Promise对象,考虑到代码的清晰,更推荐使用await命令

async function renderWidget() {
    const container = document.getElementById('widget');
    if (container !== null) {
        const widget = await import('./widget.js');
        widget.render(container);
        // 等同于
        // import("./widget").then(widget => {
        //     widget.render(container);
        // });
    }
}
renderWidget();
上例中,await命令对比then()的写法明显更简洁易读

import.meta

开发者使用一个模块时,有时需要知道模板本身的一些信息,ES2020 为 import 命令添加了一个元属性import.meta,返回当前模块的元信息

import.meta只能在模块内部使用,如果在模块外部使用会报错

这个属性返回一个对象,该对象的各种属性就是当前运行的脚本的元信息。具体包含哪些属性,标准没有规定,由各个运行环境自行决定。一般来说至少会有下面两个属性

import.meta.url:返回当前模块的 URL 路径

import.meta.scriptElement:是浏览器特有的元属性,返回加载模块的那个