JavaScript
是没有作用域(局部)和命名空间这一概念的,而JavaScript
又是脚本(解释型)语言,当js
引擎在解析它时,将其放在「一起」自上而下开始解释(生成ast
,然后生成字节码,最后编译成机器码),这就在解释时会遇到一个显然的问题:符号覆盖(symbol overhead
)「在编译型语言中就是符号冲突,符号冲突在编译时就会报错,而符号覆盖并没有任何错误提示,能正常解释执行,显然这是无法接受的」
因此,在工程实践中,JavaScript
的模块化就显得十分必要。
在没有模块化的JS
世界中:
//index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>demo</title> <script src="alex.js" type="text/javascript"></script> <script src="lib.js" type="text/javascript"></script> </head> <body> </body> </html>
//lib.js let value = 'value'
//bob.js alert(value)
bob.js
中显然没有定义过变量value
,但是能正常输出变量值value
,这是因为在index.html
中先加载了alex.js
文件后加载的bob.js
文件,因为js
并没有命名空间这一概念,所以能正常输出(等价于将所有js
文件集中到同一个文件中)。
显然,这样是无法展开正常的编码的,各个文件(模块)无法做到有效隔离(全是各种变量污染)
(于是各种骚操作就来了...)
//lib.js let mod = (function () { let mod = {} mod.value = 'value' mod.say_sm = function(){ alert('say sm') } return mod })()
//bob.js alert(mod.value) mod.say_sm()
正常输出value
和say sm
直接在lib.js
中返回一个对象mod
,JavaScript
中是存在函数作用域的,所以通过立即调用函数,返回一个对象,这样其他js
文件就能直接使用这个对象了。(在这个立即调用函数内部可以定义各种变量和函数,直接将其给到这个mod对象即可),这是个一个妥协的方式,本质上并没有避免变量污染(mod
这个变量有可能污染其他js
文件中的同名变量mod
),并且在html
文件中存在先后依赖关系(被依赖的文件必须先被加载),这在存在大量js
文件的情况下...
JavaScript
自诞生起直到2015年都不支持模块化,直到ES6标准时才在语言层面支持模块化,而此前业界通常采用制定「行业规范」的方式来「实现(模拟)」模块化(module
)。
业界流行的规范为CommonJS
,这份规范被实现得完整通用。
ES6
对模块化的支持
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>demo</title> <script src="lib.js" type="module"></script> <script src="bob.js" type="module"></script> </head> <body> </body> </html>
//lib.js let value = 'value' function say_sm(){ alert('say sm') } export{ value, say_sm }
//bob.js import {value,say_sm} from './module/lib04.js' alert(value) say_sm()
如上所示,ES6
中引入2个关键字来支持模块化,import
和export
,这是js
的关键字而非函数,作用于js
的解释期(AST
),而非运行期。(因此不能支持条件导入)
let flag = 1 if(flag == 1){ import {value,say_sm} from './module/lib04.js' } //解释期报错,因为if条件需要等到执行期才能判断,在编译为AST时是无法判断的,而import在编译期就要开始生成代码。故直接parse语法错误
nodejs
对CommonJS
的支持
//lib.js let value = 'value' function say_sm(){ console.log(value) } module.exports.value = value module.exports.say_sm = say_sm
//bob.js const lib = require('./lib.js') console.log(lib.value) lib.say_sm()
以上就是nodejs
中的模块化支持(非语言层面的支持
),其中,每个js
文件就是一个模块,在nodejs
中,有一个全局对象module
,它是Module
类的一个实例,这个实例有一个属性,exports
,在也是一个对象,在module
实例被创建时默认初始化为{}
(一个无属性的对象)(为了「彻底」支持commonjs
规范,module
实例中还有一个属性exports
,这个值被直接赋值为module.exports
,即exports = module.exports
)
函数require
直接返回一个对象,这个对象并非一个新对象,而是直接返回lib.js
模块中的对象module
中的属性(对象)exports
所以,node
对commonjs
的支持本质还是在做「对象共享」这一件事。
一个js
文件被加载时,node
便实例化了一个module
对象,然后创建了一个对象exports
对象
let module = new Module() module.exports = {}
这个module.exports
对象位于堆内存的某个地址处address_x
。
当其他模块引用lib.js
时(require
),直接返回address_x
地址值(对象)
其他js
模块require
同一个lib.js
时,本质是在共享module.exports
对象,因此,任何一个模块对exports
对象的修改都会反应到其他js
文件中
//bob.js const lib = require('./lib.js') lib.value = 'bob' //在其他js文件中require('./lib.js'),value的值也被修改为bob
而ES6
中对模块的支持则不存在这一问题,其机制并非通过共享对象来实现的,而是通过Binding Env Block
机制来AST
期间实现
以上,就是基本的Js
中的导入与导出