require()、import、import()是我们常用的引入模块的三种方式,代码中几乎处处用到。如果对它们存在模糊,就会在工作过程中不断产生困惑,更无法做到对它们的使用挥洒自如。今天我们来一起捋一下,它们之间有哪些区别?

一、前世今生

学一个东西,先弄清楚它为什么会出现、它的发展历史、它是做什么的,是很有必要的。看似与技术无关,却很有助于你对技术的理解。

require():

require()是CommonJS引入模块的函数。CommonJS是一种规范,主要用于nodejs,无法用于浏览器端。它是规范,而不是语法。本质上,它是在JS本身不支持模块的情况下,程序员发挥聪明才智,模拟出来的模块系统,在语言层面没有对JS做任何改动。它的原理实际上还是立即执行函数(IIFE)。

import:

JS在设计之初极为简单,没有模块系统,但开发中对模块化的需求却与日俱增。终于,ES6正式在语言标准的层面,引入了模块系统。import就是ES6的模块引入语句,作为ES6本身的语法,只要能写ES6的地方,都能使用它。它最大的特点,是静态引入,在编译时完成模块加载。这带来了加载效率高、静态分析等好处。

require()对比import,类似于VUE之于JS,对比ES6之于ES5的关系。

import():

相比require(),import好处多多,却也丢失了动态引入的优点,即在运行时根据实际需要选择引入模块。怎么办呢?

在ES2020中,引入了import()函数,它和require()一样是动态加载,不同的是,它是异步的,返回一个Promise对象,而require()是同步的。

二、缓存方式

(1)先来回答一个面试中常问的问题,一个模块多次引入,会执行几次?

require():

// 2.js
console.log('模块执行开始');
let num = 1;
module.exports = { num };
console.log('模块执行结束'); // testRequire.js
let a = require('./2.js');
let b = require('./2.js');
console.log(typeof a);
console.log(a === b); // 执行结果
// 模块执行开始
// 模块执行结束
// object
// true

import:

// 1.js
console.log('模块执行开始');
export let num = 1;
console.log('模块执行结束'); // testImport.js
import * as a from './1.js';
import * as b from './1.js';
console.log(typeof a);
console.log(a === b); // 执行结果
// 模块开始执行
// 模块执行结束
// object
// true

import():

// 1.js
console.log('模块执行开始');
export let num = 1;
console.log('模块执行结束'); // testImportFunction.js
let a = await import('./1.js');
let b = await import('./1.js');
console.log(typeof a);
console.log(a === b); // 执行结果
// 模块开始执行
// 模块执行结束
// object
// true

由此可见,它们三个,在代码中多次引入同一模块,模块都是只会执行一次。并且,输出结果完全相等(===),也就是指向同一个引用。

我们可以判断,它们都是第一次执行后把输出结果缓存了起来,多次引入不会再次执行,而是直接返回输出结果的引用。

(2)那么,它们对输出结果的缓存方式一样吗?

require():

// 2.js
let num = 1;
let obj = {
num: 1
};
function add() {
num += 1;
obj.num += 1;
}
module.exports = { num, obj, add }; // testRequire.js
let a = require('./2.js');
console.log(a.num); // 1
console.log(a.obj.num); // 1
a.add();
console.log(a.num); // 1
console.log(a.obj.num); // 2

require的缓存方式,是对输出结果进行拷贝,而且是浅拷贝。值类型直接拷贝,引用类型拷贝内存地址。

import:

// 1.js
let num = 1;
let obj = {
num: 1
};
function add() {
num += 1;
obj.num += 1;
}
export { num, obj, add }; // testImport.js
import * as a from './1.js';
console.log(a.num); // 1
console.log(a.obj.num); // 1
a.add();
console.log(a.num); // 2
console.log(a.obj.num); // 2

import并不对输出结果进行拷贝,而是直接指向输出结果的引用。

import():

// 1.js
let num = 1;
let obj = {
num: 1
};
function add() {
num += 1;
obj.num += 1;
}
export { num, obj, add }; // testImportFunction.js
let a = await import('./1.js');
console.log(a.num); // 1
console.log(a.obj.num); // 1
a.add();
console.log(a.num); // 2
console.log(a.obj.num); // 2

import()也是一样的,直接指向输出结果的引用。

三、静态?动态?

静态引入:

所谓静态引入,就是在编译时引入,那么就不能使用在运行时才能得到结果的语法结构了,比如不能包在if语句里,引用路径不能使变量和表达式,要求必须是字符串字面量。

import就是静态引入。

动态引入:

动态引入,就是在运行时引入。因此可以根据条件判断来按需引入,引用路径也可以写成变量或表达式。

require()和import()都是动态引入。

四、同步?异步?

require():

// 2.js
console.log('模块执行开始');
let num = 1;
for (var i = 0; i < 1000000000; i++) { }
module.exports = { num };
console.log('模块执行结束'); // testRequire.js
let a = require('./2.js');
console.log('执行其他代码'); // 执行结果
// 模块执行开始
// 若干秒后...
// 模块执行结束
// 执行其他代码

require()引入模块,是同步的,但是因为是在服务端本地引用,同步引入完全没有问题。

import:

// 1.js
console.log('模块执行开始');
let num = 1;
await new Promise(resolve => {
setTimeout(resolve, 3000);
});
export { num };
console.log('模块执行结束'); // testImport.js
import * as a from './1.js';
console.log('执行其他代码'); // 执行结果
// 模块执行开始
// 3秒后...
// 模块执行结束
// 执行其他代码

这儿让我非常意外,也很困惑。都说import是异步引入的,为什么这儿的结果却显示它是同步的?哪位能解答我的疑惑?

import():

// 1.js
console.log('模块执行开始');
let num = 1;
await new Promise(resolve => {
setTimeout(resolve, 3000);
});
export { num };
console.log('模块执行结束'); // testImportFunction.js
import('./1.js');
console.log('执行其他代码'); // 执行结果
// 执行其他代码
// 模块执行开始
// 3秒后...
// 模块执行结束

没问题,import()是异步引入,返回的是一个Promise对象。

五、相互引用

require():

require()无法引入ES6模块,但可以使用import()函数来引入ES6模块。

import:

// 2.js
let num = 1;
let obj = {
num: 1
};
module.exports = { num, obj, add }; // testImport.js
import a from './2.js';
console.log(a.num); // 1
console.log(a.obj.num); // 1

import可以引入CommonJS模块,是把module.exports对象整体引入,类似于对exports default的接收,直接用一个变量来接收。

import():

// 2.js
let num = 1;
let obj = {
num: 1
};
module.exports = { num, obj, add }; // testImportFunction.js
let a = await import('./2.js');
console.log(a.default.num); // 1
console.log(a.default.obj.num); // 1

import()整体接收module.exports这个对象,并把它放在default属性下。

就总结这些吧。本人水平非常有限,写作主要是为了把自己学过的东西捋清楚。如有错误,还请指正,感激不尽。

require()、import、import()有哪些区别?的更多相关文章

  1. JS 中的require 和 import 区别整理

    ES6标准发布后,module成为标准,标准的使用是以export指令导出接口,以import引入模块,但是在我们一贯的node模块中,我们采用的是CommonJS规范,使用require引入模块,使 ...

  2. module、export、require、import的使用

    module 每个文件就是一个模块.文件内定义的变量.函数等等都是在自己的作用域内,都是自身所私有的,对其它文件不可见. 每个文件内部都有一个module对象,它包含以下属性 id: 模块的识别符,通 ...

  3. 转:彻底搞清楚javascript中的require、import和export

    原文地址:彻底搞清楚javascript中的require.import和export   为什么有模块概念 理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块. 但是,Ja ...

  4. require与import

    require 和 import,都是为了JS模块化使用.最近项目中,因为多人协同开发,出现了一个项目中同时使用了require 和 import 引入依赖的情况.正常情况下,一个项目中最好是对引入方 ...

  5. 关于export 和 require(import)的一些技巧和常用方法

    多重export 这样做的好处是可以在引入页面按需加载,也非常的清晰加载了哪一些东西 //exportexport const setError = ({dispatch}, error) => ...

  6. @import和link的区别

    @import和link的区别 1.link语法结构    <link href="CSSurl路径" rel="stylesheet" type=&qu ...

  7. #import和#include的区别 关键字@class的作用

    一.#import和#include的区别当我们在代码中使用两次#include的时候会报错:因为#include相当于拷贝头文件中的声明内容,所以会报重复定义的错误但是使用两次#import的话,不 ...

  8. 008_Node中的require和import

    一.js的对象的解构赋值 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuri ...

  9. NodeJS中的require和import

    ES6标准发布后,module成为标准,标准的使用是以export指令导出接口,以import引入模块,但是在我们一贯的node模块中,我们采用的是CommonJS规范,使用require引入模块,使 ...

  10. vue 更新了vue-cli到最新版本后引发的问题: require和import、vue-loader的问题

    "vue-loader": "^12.1.0", "vue-loader": "^12.1.0", "vue- ...

随机推荐

  1. django执行makemigrations报AttributeError: 'str' object has no attribute 'decode'

    顺着报错文件点进去,找到query = query.decode(errors='replace')将decode修改为encode即可.

  2. P7167 Fountain

    原题 有n个盘子组成的塔,向第i个盘子上倒水,若溢出会落到下面第一个直径大的盘子里,直到落到底部的水池为止.现给出q次询问,\((r,v)\)代表向第r个盘子里倒入体积为v的水,求水最终会停在哪个盘子 ...

  3. 一文搞懂TCP的三次握手和四次挥手

    目录 1.三次握手 2.四次挥手 3.11种状态名词解析 TCP的三次握手和四次挥手实质就是TCP通信的连接和断开. 三次握手:为了对每次发送的数据量进行跟踪与协商,确保数据段的发送和接收同步,根据所 ...

  4. 基于C#的窗体阴影效果方案 - 开源研究系列文章

    最近在研究C#的Winform窗体的效果,上次介绍了窗体动画效果的博文( 基于C#的无边框窗体动画效果的完美解决方案 - 开源研究系列文章 ),这次将窗体阴影效果的方案进行一个介绍. 找了一下度娘,具 ...

  5. [Python]数组基础

    在python中,一般使用列表表示数组.例如: 一维数组 arr1 = [1,2,3,4] 二维数组 arr2 = [[1,2,3,4],[5,6,7,8]] 数组的常用操作 追加 利用append( ...

  6. 聊一聊使用Spring事物时不生效的场景

    前言 今天介绍一下Spring事物不生效的场景,事物是我们在项目中经常使用的,如果是Java的话,基本上都使用Spring的事物,不过Spring的事物如果使用不当,那么就会导致事物失效或者不回滚,最 ...

  7. 请大家一定不要像我们公司这样打印log日志

    前言 最近接手了公司另一个项目,熟悉业务和代码苦不堪言. 我接手一个新项目,有个习惯,就是看结构,看数据库,搜代码. 其中搜代码是我个人这些年不知不觉形成的癖好,我下面给大家展示下这个小癖好. 正文 ...

  8. 图解 LeetCode 算法汇总——回溯

    本文首发公众号:小码A梦 回溯算法是一种常见的算法,常见用于解决排列组合.排列问题.搜索问题等算法,在一个搜索空间中寻找所有的可能的解.通过向分支不断尝试获取所有的解,然后找到合适的解,找完一个分支后 ...

  9. 什么是IP协议?

    简介: IP(Internet Protocol)协议,又称网际协议,是TCP/IP协议的核心.它负责Internet上网络之间的通信,并规定了将数据报从一个网络传输到另一个网络所应遵循的规则.具体来 ...

  10. Unity 游戏开发、02 基础篇 | 知识补充、简单使用动画、动画状态机

    前置笔记(由浅入深) Unity 游戏开发.01 基础篇 2 场景操作 3D场景 Q 手型工具(鼠标中键):上下左右移动场景 ALT + 鼠标左键:以视图为中心旋转 鼠标右键:以观察者为中心旋转 SH ...