经过阅读本篇文章你能够学习到:javascript
在没有CommonJS
和ES6
的时候,咱们想要达到模块化的效果可能有这么三种:html
<script> function m1 () { // ... } function m2 () { // ... } </script>
缺点:污染了全局变量,没法保证不会与其它模块发生冲突,并且模块成员之间看不出直接关系。
对象写法 为了解决上面的缺点,能够把模块写成一个对象,全部的模块成员都放到这个对象里面。前端
index.htmljava
<script> var module1 = new Object({ _sum: 0, foo1: function () {}, foo2: function () {} }) </script>
缺点:会暴露全部模块成员,内部的状态可能被改写。
例如,咱们若是只是想暴露出两个方法而不暴露出 _sum
,就作不到。node
而此时,_sum
可能被外部改写:jquery
module1._sum = 2;
<script> var module1 = (function() { var _sum = 0; var foo1 = function () {}; var foo2 = function () {}; return { foo1: foo1, foo2: foo2 } })(); </script>
利用当即执行函数内的做用域已经闭包来实现模块功能,导出咱们想要导出的成员。面试
此时外部代码就不能读取到 _sum
了:npm
console.log(module1._sum) // undefined
这里不作具体的介绍了,我只把一些重要的知识点以及混淆点例举出来。json
主要是从这四个方面说:segmentfault
正确的暴露方式:
暴露模块有两种方式:
module.exports = {}
exports.xxx = 'xxx'
例若有一个 m1.js
文件:
第一种暴露方式:
module.exports = { name: 'lindaidai', sex: 'boy' }
第二种暴露方式:
exports.name = 'lindaidai'; exports.sex = 'boy'
为何能够有这两种写法呢?
我是这样理解的:module
这个变量它表明的就是整个模块,也就是m1.js
。而其实这个module
变量是有一个属性exports
的,它是一个叫作exports
变量的引用,咱们能够写一下伪代码:
var exports = {}; var module = { exports: exports } return module.exports
(固然这只是伪代码啊,实际你这么去用会发现没有效果)
最后导出的是module.exports
,而不是exports
。
容易混淆的暴露方式:
若是你在代码中试图 exports = { name: 'lindaidai' }
,你会发如今引入的地方根本获取不到name
属性。
// m1.js exports = { name: 'lindaidai' }
// test.js const math = require('./m1.js') console.log(m1); // {}
在控制台执行 node test.js
,发现打印出来的 m1
是一个空的对象。
我是这样理解的:整个模块的导出是靠 module.exports
的,若是你从新对整个 exports
对象赋值的话,它和 module.exports
就不是同一个对象了,由于它们指向的引用地址都不一样:
module.exports -> {} // 指向一个空的对象 exports -> { name: 'lindaidai' } // 指向的是另外一个对象
因此你对 exports = {}
作任何操做都影响不到 module.exports
。
让咱们来看几个正确和错误的示例吧:
// m1.js // 1. 正确 module.exports = { name: 'lindaidai', sex: 'boy' } // 2. 正确 exports.name = 'lindaidai'; exports.sex = 'boy' // 3. 正确 module.exports.name = 'lindaidai'; module.exports.sex = 'boy' // 4. 无效 exports = { name: 'lindaidai', sex: 'boy' }
能够看到
exports.name = xxx
是 module.exports.name = xxx
的缩写。exports = {}
却不是 module.exports = {}
的缩写。对于模块的引用使用全局方法 require()
就能够了。
注意⚠️这个全局方法是 node
中的方法哈,它不是 window
下面的,因此若是你没作任何处理想直接在 html
里用确定就是不行的了:
index.html:
<body> <script> var m1 = require('./m1.js') console.log(m1); </script> </body>
例如上面👆这样你打开页面控制台确定就报错了:
Uncaught ReferenceError: require is not defined at index.html:11
而若是你是在另外一个 js
文件中引用(例如 test.js
),并在终端执行 node test.js
是能够用的:
test.js:
var m1 = require('./m1.js') console.log(m1);
那是由于你的电脑上全局安装了 Node.js
,因此能够这样玩。
因此咱们能够发现 require()
它是 Node.js
中的一个全局方法,并非CommonJS独有的,CommonJS只是众多规范中的其中一种。
这种规范容许咱们:
module.exports = {}
或者 exports.name = xxx
导出模块const m1 = require('./m1')
引入模块注意⚠️:
另外还有一点比较重要,那就是 require()
的参数甚至能容许你是一个表达式。
也就是说你能够把它设置为一个变量:
test.js:
var m1Url = './m1.js'; var m1 = require(m1Url); // 甚至作一些字符串拼接: var m1 = require('./m' + '1.js');
模块标识符其实就是你在引入模块时调用 require()
函数的参数。
你会看到咱们常常会有这样的用法:
// 直接导入 const path = require('path'); // 相对路径 const m1 = require('./m1.js'); // 直接导入 const lodash = require('lodash');
这实际上是由于咱们引入的模块会有不一样的分类,像path
这种它是Node.js
就自带的模块,m1
是路径模块,lodash
是咱们使用npm i lodash
下载到node_modules
里的模块。
分为如下三种:
Node.js
自带的模块)node_modules
里的模块)三种模块的查找方式:
node_modules
里找这个模块,若是没有,它会往上一级目录查找,查找上一级的node_modules
,依次往上,直到根目录下都没有, 就抛出错误。自定义模块的查找过程:
这个过程其实也叫作路径分析。
如今我把刚刚的test.js
来改一下:
// var m1 = require('./m1.js'); // console.log(m1); console.log(module.paths)
而后在终端执行:
node test.js
会发现输出了下面的一个数组:
LinDaiDaideMBP:commonJS lindaidai$ node test.js [ '/Users/lindaidai/codes/test/CommonJS和ES6/commonJS/node_modules', '/Users/lindaidai/codes/test/CommonJS和ES6/node_modules', '/Users/lindaidai/codes/test/node_modules', '/Users/lindaidai/codes/node_modules', '/Users/lindaidai/node_modules', '/Users/node_modules', '/node_modules' ]
这里所说的查找,是指查找你如今用的这个模块,我如今用的是test.js
,你可能看不出什么效果。如今让咱们来模拟一个咱们使用npm i
安装的一个自定义模块功能。
首先,我在根目录下新建了一个名叫node_modules
的文件夹,并在其中新建了一个名叫lindaidai.js
的文件,用来模拟一个npm
安装的依赖。
目录结构:
稍微编写一下lindaidai.js
:
module.exports = { print: function () { console.log('lindaidai') } } console.log('lindaidai模块:', module.paths)
而后在test.js
中引入这个lindaidai
模块:
// var m1 = require('./m1.js'); // console.log(m1); // console.log(module.paths) var lindaidai = require('lindaidai'); lindaidai.print();
如今执行node test.js
,会发现输出了:
LinDaiDaideMBP:commonJS lindaidai$ node test.js lindaidai模块: [ '/Users/lindaidai/codes/test/CommonJS和ES6/commonJS/node_modules', '/Users/lindaidai/codes/test/CommonJS和ES6/node_modules', '/Users/lindaidai/codes/test/node_modules', '/Users/lindaidai/codes/node_modules', '/Users/lindaidai/node_modules', '/Users/node_modules', '/node_modules' ] lindaidai
因此如今你能够知道,日常咱们使用这种依赖的时候,它是怎样的一个查找顺序了吧,它其实就是按照自定义模块的顺序来进行查找。
文件定位:
上面👆已经介绍完了路径分析,可是还有一个问题,就是咱们导入的模块它的后缀(扩展名)是能够省略的啊,那Node
怎么知道咱们是导入了一个js
仍是一个json
呢?这其实就涉及到了文件定位。
在NodeJS中, 省略了扩展名的文件, 会依次补充上.js, .node, .json来尝试, 若是传入的是一个目录, 那么NodeJS会把它当成一个包来看待, 会采用如下方式肯定文件名
第一步, 找出目录下的package.json, 用JSON.parse()解析出main字段
第二步, 若是main字段指定的文件仍是省略了扩展, 那么会依次补充.js, .node, .json尝试.
第三步, 若是main字段制定的文件不存在, 或者根本就不存在package.json, 那么会默认加载这个目录下的index.js, index.node, index.json文件.
以上就是文件定位的过程, 再搭配上路径分析的过程, 进行排列组合, 这得有多少种可能呀. 因此说, 自定义模块的引入, 是最费性能的.
(总结来源:https://zhuanlan.zhihu.com/p/...
我先把CommonJS
规范的一些特色列举出来吧,而后咱们再一点一点的去看例子。
require
返回的值是被输出的值的拷贝,模块内部的变化也不会影响这个值)。(总结来源:https://juejin.im/post/5db95e...
第一点仍是好理解的,咱模块的一个重要的功能不就是这个吗。
第二点同步加载,这个写个案例咱们来验证一下
同步加载案例:
_m1.js_:
console.log('我是m1模块') module.exports = { name: 'lindaidai', sex: 'boy' }
test.js
var m1 = require('./m1'); console.log('我是test模块');
能够看到,test
模块依赖于m1
,且是先下载的m1
模块,因此若是我执行node test.js
,会有如下的执行结果:
LinDaiDaideMBP:commonJS lindaidai$ node test.js 我是m1模块 我是test模块
这也就验证了CommonJS
中,模块是同步加载的,即只有加载完成,才能执行后面的操做。
第三点模块首次执行后会缓存,咱们也能够写个案例来验证一下。
模块首次执行后会缓存案例:
_m1.js_:
var name = 'lindaidai'; var sex = 'boy'; exports.name = name; exports.sex = sex;
_test.js_:
var m1 = require('./m1'); m1.sex = 'girl'; console.log(m1); var m2 = require('./m1'); console.log(m2);
test
一样依赖于m1
,可是我会在其中导入两次m1
,第一次导入的时候修改了m1.sex
的值,第二次的时候命名为m2
,可是结果m1
和m2
居然是相等的:
LinDaiDaideMBP:commonJS lindaidai$ node test.js { name: 'lindaidai', sex: 'girl' } { name: 'lindaidai', sex: 'girl' }
也就是说模块在首次执行后就会缓存,再次加载只返回缓存结果,这里我是用了改变m1.sex
的值来证实它确实是取了缓存结果。
那么就有小伙伴会疑惑了,其实你这样写也并不能证实啊,由于你改变了m1.sex
也多是影响本来m1
模块里的sex
属性呀,这样的话第二次m2
拿到的确定就是被改变的值了。
唔...我正想证实来着呢。由于CommonJS
的第四个特色就能够很好的解决你这个疑问。
第四点CommonJS输出是值的拷贝,也就是说你用require()
引入了模块,可是你在最新的模块中怎样去改变,也不会影响你已经require()
的模块。来看个案例。
CommonJS输出是值的拷贝案例:
_m1.js_:
var name = 'lindaidai'; var sex = 'boy'; var advantage = ['handsome'] setTimeout(function () { sex = 'girl'; advantage.push('cute'); }, 500) exports.name = name; exports.sex = sex; exports.advantage = advantage;
_test.js_:
var m1 = require('./m1'); setTimeout(function () { console.log('read count after 1000ms in commonjs is', m1.sex) console.log('read count after 1000ms in commonjs is', m1.advantage) }, 1000)
执行node test.js
以后的执行结果是:
LinDaiDaideMBP:commonJS lindaidai$ node test.js read count after 1000ms in commonjs is boy read count after 1000ms in commonjs is [ 'handsome', 'cute' ]
也就是说,在开始var m1 = require('./m1')
的时候,m1
已经被引入进来了,可是过了500ms
后我改变了本来m1
里的一些属性,sex
这种基本数据类型是不会被改变的,可是advantage
这种引用类型共用的仍是同一个内存地址。(这种复制的关系让我想到了以前学原型链继承的时候,它那里也是,会影响Father.prototype
上的引用类型)
备注 其实这里的拷贝是指 JavaScript 的浅拷贝,若是对于 JavaScript 的深浅拷贝有疑问,能够参考 JavaScript 的浅拷贝和深拷贝
若是这里你是这样写的话:
_m1.js_:
var name = 'lindaidai'; var sex = 'boy'; var advantage = ['handsome'] setTimeout(function () { sex = 'girl'; // advantage.push('cute'); advantage = ['cute']; }, 500) exports.name = name; exports.sex = sex; exports.advantage = advantage;
如今的执行结果确定就是:
LinDaiDaideMBP:commonJS lindaidai$ node test.js read count after 1000ms in commonjs is boy read count after 1000ms in commonjs is [ 'handsome' ]
由于至关于对m1
的advantage
从新赋值了。
固然,或者若是你的m1.js
中返回的值是会有一个函数的话,在test.js
也能拿到变化以后的值了,好比这里的一个例子:
var counter = 3; function incCounter() { counter++; } module.exports = { get counter() { return counter }, incCounter: incCounter, };
由于在这里实际就造成了一个闭包,而counter
属性就是一个取值器函数。
好滴,这基本就是CommonJS
的特色了,总结就不写了,在开头已经说过了,不过对于最后一点:CommonJS输出是值的拷贝,这个对于引用类型的变量来讲仍是会有一点歧义的,好比上面的advantage
那个例子,你们知道就好了。
上面介绍的CommonJS
规范看起来挺好用的啊,为何又还要有其它的规范呢?好比AMD、CMD
,那它们和CommonJS
又有什么渊源呢?
咱们知道,模块化这种概念不只仅适用于服务器端,客户端一样也适用。
而CommonJS
规范就不太适合用在客户端(浏览器)环境了,好比上面的那个例子,也就是:
test.js:
const m1 = require('./m1.js') console.log(m1); // 与m1模块无关的一些代码 function other () {} other();
这段代码放在浏览器环境中,它会如何运行呢?
m1.js
m1.js
加载完毕以后才执行后面的内容这点其实在CommonJS规范的特色中已经提到过了。
后面的内容要等待m1
加载完才会执行,若是m1
加载的很慢呢?那不就形成了卡顿,这对于客户端来讲确定是不友好的。像这种要等待上一个加载完才执行后面内容的状况咱们能够叫作"同步加载"
,很显然,这里咱们更但愿的是other()
的执行不须要等m1
加载完才执行,也就是咱们但愿m1
它是"异步加载"
的,这也就是AMD
。
在介绍AMD
以前让咱们看看CommonJS
规范对服务器端和浏览器的不一样,它有助于让你理解为何说CommonJS
不太适合于客户端:
有了上面这层背景,咱们就知道了,AMD
它的产生很大一部分缘由就是为了能让咱们采用异步的方式加载模块。
因此如今来让咱们看看它的介绍吧。
AMD
是Asynchronous Module Definition
的缩写,也就是"异步模块定义"
。(前面的A
就很好记了,它让我不自觉的就想到async
这个定义异步函数的修饰符)
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。全部依赖这个模块的语句,都定义在一个回调函数中,等到加载完成以后,这个回调函数才会运行。
此时就须要另外一个重要的方法来定义咱们的模块:define()
。
它实际上是会有三个参数:
define(id?, dependencies?, factory)
坑一:
那其实就有一个问题了,看了这么多的教材,但我想要去写案例的时候,我觉得这个define
能直接像require
同样去用,结果发现控制台一直再报错:
ReferenceError: define is not defined
看来它还并非Node.js
自带的一个方法啊,搜寻了一下,原来它只是名义上规定的这样一个方法,可是你真的想要去用仍是得使用对应的JavaScript
库,也就是咱们经常听到的:
目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js。
我酸了...
让咱们去requirejs的官网看看如何使用它,因为个人案例都是在Node
执行环境中,因而我采用npm install
的方式来下载了:
我新建了一个叫AMD
的文件夹,做为AMD
的案例。
在项目的根目录下执行:
npm i requirejs
(找了一圈NPM也没看到能使用CDN
远程引入的)
执行完毕以后,项目的根目录下出现了依赖包,打开看了看,确实是下载下来了:
如今能够开心的在项目里用define()
了 😊。
来看个小例子,我从新定义了一个math.js
:
math.js
define(function () { var add = function (a, b) { return a + b; } return { add: add } })
这里模块很简单,导出了一个加法函数。
(至于这里为何add: add
要这样写,而不是只简写为add
呢?别忘了这种对象同名属性简写是ES6
才出来的哦)
坑二:
OK👌,既然模块已经能导出了,那就让咱们来看看如何引用吧,依照着教材,我在test.js
中引入了math
模块并想要调用add()
方法:
test.js:
require(['math'],function(math) { console.log(math) console.log(math.add(1, 2)); })
以后熟练的执行node test.js
。
我酸了...
又报错了,擦...
throw new ERR_INVALID_ARG_TYPE(name, 'string', value); TypeError [ERR_INVALID_ARG_TYPE]: The "id" argument must be of type string. Received an instance of Array
确认了一下,和教材们中的写法同样啊,第一个参数为要加载的模块数组,第二个参数为加载完以后的回调。
难受😣...原来上面👆require([modules], callback)
这样的写法它和define
同样都只是个噱头,若是你真得用的话,仍是得用JavaScript
库中的方法。
因为上面已经安装过requirejs
了,这里我直接使用就能够了,如今我修改了一下test.js
文件:
var requirejs = require("requirejs"); //引入requirejs模块 requirejs(['math'],function(math) { console.log(math) console.log(math.add(1, 2)); })
好了,如今执行node test.js
就能够正常使用了...
(很难受...感受明明已是很常见耳熟能详的一些知识了,真的要去用的时候发现和不少教材中说的不是那么一回事...也但愿你们在看完了一些教材以后最好能亲自去实践一下,由于本身也是写博客的,因此也知道有些时候一些知识点可能也是从别人的文章那里看来可是没有通过实践的,因此最好也仍是本身动动手)
能够看到define
它还有另外两个参数的,第一个是模块的名称,没啥好说的,让咱们来看看第二个它所依赖的模块。
还记得在CommonJS
规范那里咱们写了一个m1.js
吗?如今就让咱们把这个模块拿来用下,把它做为math.js
中的一个依赖。
m1.js:
console.log('我是m1, 我被加载了...') module.exports = { name: 'lindaidai', sex: 'boy' }
而后修改一下math.js
:
math.js:
define(['m1'], function (m1) { console.log('我是math, 我被加载了...') var add = function (a, b) { return a + b; } var print = function () { console.log(m1.name) } return { add: add, print: print } })
另外,为了方便你们看,咱们再来修改一下刚刚的test.js
:
var requirejs = require("requirejs"); //引入requirejs模块 requirejs(['math'],function(math) { console.log('我是test, 我被加载了...') console.log(math.add(1, 2)); math.print(); }) function other () { console.log('我是test模块内的, 可是我不依赖math') }; other();
因此咱们能够看到,依赖关系依次为:
test -> math -> m1
若是按照AMD
的规范,模块的加载须要依靠前一个模块加载完才会执行回调函数内的内容,那么咱们能够想象当我在终端输入node test.js
的时候,要出现的结果应该是:
LinDaiDaideMBP:commonJS lindaidai$ node test.js 我是test模块内的, 可是我不依赖math 我是m1, 我被加载了... 我是math, 我被加载了... 我是test, 我被加载了... 3 lindaidai
(这个,相信你们应该都看清了彼此的依赖关系吧😢)
可是现实老是那么的残酷,当我按下回车的时候,又报错了...
再酸...
ReferenceError: module is not defined
看了一下这个报错的内容,是在m1.js
中...呆了几秒钟反应了过来...
既然是使用AMD
的规范,那咱们确定是要一统到底了,m1.js
中用的仍是CommonJS
的规范,固然不行了。
OK,来修改一下m1.js
:
m1.js:
define(function () { console.log('我是m1, 我被加载了...') return { name: 'lindaidai', sex: 'boy' } })
OK👌,此次没啥问题了,按照咱们预期的去执行了...😊。
(固然据个人了解,requirejs
还可用于在script
中引用而后定义网页程序的主模块等使用,能够看一下:
http://www.ruanyifeng.com/blo..._js.html)
AMD
的知识点大概就介绍到了这里,相信你们也知道它的基本使用了吧,至于其中的一些区别什么的我在最后也会列一份清单,不过如今让咱们先来看看CMD
吧。
CMD (Common Module Definition), 是seajs推崇的规范,依赖就近,用的时候再require。
来看段代码,大概感觉一下它是怎样用的:
define(function(require, exports, module) { var math = require('./math'); math.print() })
看着和AMD
有点像的,没错,其实define()
的参数甚至都是同样的:
define(id?, dependencies?, factory)
可是区别在于哪里呢?让咱们来看看最后一个factory
它参数。
factory
函数中是会接收三个参数:
require
exports
module
这三个很好理解,对应着以前的CommonJS
那不就是:
require
:引入某个模块exports
:当前模块的exports
,也就是module.exports
的简写module
:当前这个模块如今再来讲说AMD
和CMD
的区别。
虽然它们的define()
方法的参数都相同,可是:
AMD
中会把当前模块的依赖模块放到dependencies
中加载,并在factory
回调中拿到加载成功的依赖CMD
通常不在dependencies
中加载,而是写在factory
中,使用require
加载某个依赖模块所以才有了咱们经常看到的一句话:
AMD和CMD最大的区别是对依赖模块的执行时机处理不一样,注意不是加载的时机或者方式不一样,两者皆为异步加载模块。
(好吧,仔细读了2遍感受仍是没太明白,没事,后面呆呆还会详细说到)
比较有名一点的,seajs
,来看看它推荐的CMD 模块书写格式吧:
// 全部模块都经过 define 来定义 define(function(require, exports, module) { // 经过 require 引入依赖 var $ = require('jquery'); var Spinning = require('./spinning'); // 经过 exports 对外提供接口 exports.doSomething = ... // 或者经过 module.exports 提供整个接口 module.exports = ... });
这是官网的一个小案例,我也去seajs的文档中看了一下没啥太大问题,这里就不举例了。
AMD和CMD最大的区别是对依赖模块的执行时机处理不一样,注意不是加载的时机或者方式不一样,两者皆为异步加载模块。
仍是上面那句话,让咱们来看个小例子理解一下。
一样是math
模块中须要加载m1
模块。
在AMD
中咱们会这样写:
math.js
define(['m1'], function (m1) { console.log('我是math, 我被加载了...') var add = function (a, b) { return a + b; } var print = function () { console.log(m1.name) } return { add: add, print: print } })
可是对于CMD
,咱们会这样写:
math.js
define(function (require, exports, module) { console.log('我是math, 我被加载了...') var m1 = require('m1'); var add = function (a, b) { return a + b; } var print = function () { console.log(m1.name) } module.exports = { add: add, print: print } })
假如此时m1.js
中有一个语句是在m1
模块被加载的时候打印出"我是m1, 我被加载了..."
。
执行结果区别:
AMD
,会先加载m1
,"我是m1"
会先执行CMD
,我是"我是math"
会先执行,由于本题中console.log('我是math, 我被加载了...')
是放在require('m1')
前面的。如今能够很明显的看到区别了。
AMD
依赖前置,js
很方便的就知道要加载的是哪一个模块了,由于已经在define
的dependencies
参数中就定义好了,会当即加载它。
CMD
是就近依赖,也就是说模块的回调函数执行到加载语句时才会去加载。
OK👌,来看个总结:
二者之间,最明显的区别就是在模块定义时对依赖的处理不一样
一、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块二、CMD推崇就近依赖,只有在用到某个模块的时候再去require
ES6
标准出来后,ES6 Modules
规范算是成为了前端的主流吧,以import
引入模块,export
导出接口被愈来愈多的人使用。
下面,我也会从这么几个方面来介绍ES6 Modules
规范:
export
命令和import
命令能够出如今模块的任何位置,只要处于模块顶层就能够。若是处于块级做用域内,就会报错,这是由于处于条件代码块之中,就无法作静态优化了,违背了ES6模块的设计初衷。
export有两种模块导出方式:
命名式导出
来看几种正确和错误的写法吧:
// 如下两种为错误 // 1. export 1; // 2. const a = 1; export a; // 如下为正确 // 3. const a = 1; export { a }; // 4. 接口名与模块内部变量之间,创建了一一对应的关系 export const a = 1, b = 2; // 5. 接口名与模块内部变量之间,创建了一一对应的关系 export const a = 1; export const b = 2; // 或者用 as 来命名 const a = 1; export { a as outA }; const a = 1; const b = 2; export { a as outA, b as outB };
容易混淆的多是2
和4
两种写法了,看着很像,可是2
却不行。2
直接导出一个值为1
的变量是和状况一同样,没有什么意义,由于你在后面要用的时候并不能完成解构。
可是4
中,接口名与模块内部变量之间,创建了一一对应的关系,因此能够。
默认导出
默认导出会在export
后面加上一个default
:
// 1. const a = 1; export default a; // 2. const a = 1; export default { a }; // 3. export default function() {}; // 能够导出一个函数 export default class(){}; // 也能够出一个类
其实,默认导出能够理解为另外一种形式上的命名导出,也就是说a
这个属性名至关因而被我重写了成了default
:
const a = 1; export defalut a; // 等价于 export { a as default }
因此,咱们才能够用const a = 1; export default a;
这种方式导出一个值。
import模块导入与export模块导出功能相对应,也存在两种模块导入方式:命名式导入(名称导入)和默认导入(定义式导入)。
来看看写法:
// 某个模块的导出 moudule.js export const a = 1; // 模块导入 // 1. 这里的a得和被加载的模块输出的接口名对应 import { a } from './module' // 2. 使用 as 换名 import { a as myA } from './module' // 3. 如果只想要运行被加载的模块能够这样写,可是即便加载2次也只是运行一次 import './module' // 4. 总体加载 import * as module from './module' // 5. default接口和具名接口 import module, { a } from './module'
第四种写法会获取到module
中全部导出的东西,而且赋值到module
这个变量下,这样咱们就能够用module.a
这种方式来引用a
了。
其实还有一种写法,能够将export
和from
结合起来用。
例如,我有三个模块a、b、c
。
c
模块如今想要引入a
模块,可是它不不直接引用a
,而是经过b
模块来引用,那么你可能会想到b
应该这样写:
import { someVariable } from './a'; export { someVariable };
引入someVariable
而后再导出。
这还只是一个变量,咱们得导入再导出,如果有不少个变量须要这样,那无疑会增长不少代码量。
因此这时候能够用下面这种方式来实现:
export { someVariable } from './a';
不过这种方式有一点须要注意:
b
)中使用someVariable
。总结一下它的特色哈:
export
import
export...from...
这种写法来达到一个"中转"
的效果export
命令和import
命令能够出如今模块的任何位置,只要处于模块顶层就能够。若是处于块级做用域内,就会报错,这是由于处于条件代码块之中,就无法作静态优化了,违背了ES6模块的设计初衷。import
命令具备提高效果,会提高到整个模块的头部,首先执行。还有一点就是,若是你有使用过一些ES6的Babel的话,你会发现当使用export/import
的时候,Babel也会把它转换为exports/require
的形式。
例如个人输出:
_m1.js_:
export const count = 0;
个人输入:
_index.js_:
import {count} from './m1.js' console.log(count)
当使用Babel编译以后,各自会被转换为:
_m1.js_:
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.count = void 0; const count = 0; exports.count = count;
_index.js_:
"use strict"; var _m = require("./m1.js"); console.log(_m.count);
正是由于这种转换关系,才能让咱们把exports
和import
结合起来用:
也就是说你能够这样用:
// 输出模块 m1.js exports.count = 0; // index.js中引入 import {count} from './m1.js' console.log(count)
😂,我相信不少人就比较关心它两区别的问题,由于基本上面试问的就是这个。好吧,这里来作一个算是比较详细的总结吧。
require()
方法;而ES6 Modules只能是字符串this
指向当前模块,ES6 Modulesthis
指向undefined
arguments
、require
、module
、exports
、__filename
、__dirname
关于第一个差别,是由于CommonJS 加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
(应该还一些区别我没想到的,欢迎补充👏😊)
知识无价,支持原创。
参数文章: