当前端工程师聊起模块化的时候,通常会相当是js模块化,但在前端领域模块化的概念可不仅仅有js,只不过在三剑客的模块化中JavaScript走得相对远一些、快一些。

我希望在这里能够分别聊一聊前端三剑客的模块化,尽量不谈历史,专注于模块化本身与当下解决方案。

JS模块化

首先什么才算是JS的模块化呢?或者JS模块化可以解决什么问题呢?

1、代码隔离

2、模块之间的依赖声明

3、如何执行模块

如果能解决这三个问题才能被视为模块化,如果只是满足其中之一或之二那只能算设计模式。

前端开发者所面临的问题是什么?

命名的冲突

在非严格模式下如果一个变量不被var所声明(当然这种情况应该还很少)或者在全局作用域下直接声明,该变量会被默认提升为全局对象的一个属性(浏览器下就是window)。当一个变量的作用域为全局的时候就很容易会面临命名冲突的风险。

对项目体积不断增长的支持

如果只是一个简单的页面,没有太多交互、特效,那么我们完全不需要模块化。但随着项目的不断膨胀,尤其是移动互联网时代的来临,更多的应用场景需要我们通过js来实现。

面对这种情况,将代码分割成许多独立的模块才可以使项目保持可维护性,代码的可复用性,提高生成效率,应对高速发展的互联网时代。

但众所周知JavaScript文件需要通过script标签来引入,且按照引入的顺序执行,所以当我们需要使用一个模块的时候,既要保证模块所在文件已经在html引入,也要保证它的顺序。

更高的需求?

懒加载(动态加载)。可不可以让一些模块在未加载执行完之前是不影响当前模块的加载执行的,当这些模块加载执行完再去在做相应的工作。

上面阐述了模块化的标准、没有模块化我们所面临的问题以及对于模块化更高的需求,那么带着这些问题与知识展开讨论。


首先现如今谈到模块化我们最先想当到的肯定是ES Module,还有应用在nodejs中的模块系统commonjs。

在ES Module和commonjs中一个文件就是一个模块,一个模块也是一个文件。

在ES Module中每个模块默认采用严格模式,也就是在模块内部不声明变量直接赋值会报错,而不会提升到全局对象下,最重要的是模块本身有module-level scope,这个作用域和其他模块的作用域相互隔离,这样不管在模块内部如何声明变量都不会产生冲突。

而commonjs则通过将每个模块封装在一个封闭的函数作用域内(例如下面代码),传入nodejs内置的requrie、module等全局对象实现模块化。在函数作用域内声明的变量自然不能被外部访问,这就不会产生命名冲突的问题,但如果要实现一个变量不被声明直接赋值却不提升到全局对上(global)还需要手动通过"use strict"来设置严格模式。



(function(require, module, exports) {
// your module
})(require, module, module.exports);



接下来再谈谈模块间的依赖

ES Module通过import和export关键字来导入导出模块,并且将在script标签的type设置为module来标明是一个模块。

会构建一个对依赖模块的树形结构,当需要引入一个模块时,需要首先引入该模块依赖的所有模块,以此类推,因此ES Module是一个静态的模块系统。

通过一个非常经典的图可以简单说明ES Module模块依赖特性。




js 给Item赋值 html赋值给js变量_模块化


引用whatwg官方对于module类型对script标签加载执行机制的解释:

the module script and its dependencies will be fetchedin parallel to parsing and evaluated when the page has finished parsing.

可以看出一个模块真正执行并被分配内存之前需要将所有依赖的模块同样加载进来。

与ES Module的静态特性相反,CommonJs在Node端的实现是一个动态的模块系统,这得益于Node端读取文件非常快。CommonJs加载执行一个模块并不需要提前加载执行它的依赖模块,而是代码执行时同步式读取。

最后再聊聊模块的执行。

ES Module和CommonJs都是只在模块第一次被加载的时候才会被执行。我想有以下几个原因:

  1. 如果模块执行时会产生副作用,这确保了只有在第一次被引入的时候才会产生,其他都不会再产生副作用了。
  2. 对于ES Module,当模块第一次被引入的时候,会对模块内的状态进行初始化,其他模块可以通过模块暴露的方法来修改该模块的状态,如果之后被引入的时候再执行模块代码,会使状态变得混乱。
  3. 对于CommonJs模块,当模块第一次执行时会计算得出需要导出的结果,导入模块只是导入值的拷贝,每次导入都执行的意义不大。

从上面模块执行机制可以看出ES Module和CommonJs的一个区别就是前者导入导出都是引用,而后者导入的是值的拷贝。

那么对于更高的需求呢?

ES Module这次很给力的提供了dynamic import相关的API来支持on-demand-loaded。


import('./cat.js', (cat) => {
// some code
})
// other code


在加载完cat模块之前不会阻塞当前模块的执行。

而CommonJs没有实现动态加载相关API,但其较好的可扩展性也为我们提供了支持的可能性。例如webpack就在require对象上扩展了require.ensure来支持动态加载。


require.ensure('./cat.js', (cat) => {
// some code
})
// other code


总结一下

JS模块化的出现使代码更好的被组织,处理变量更加的合理,保证变量即可以被共享又不能被随意更改。同时允许我们将项目分成很多非常细小职能单一的模块,并像拼乐高积木似的拼出一个完整的项目,当然如何更好地使用模块化也成为了代码健壮的关键。

虽然现如今ES Module的使用还需要借助webpack、babel等工具才可以使用,并且在Node端还没有支持,但既然规范已经被制定和支持,我们更应该拥抱它,深入探究强大的模块化方案。

欢迎留言讨论。

 

js 给Item赋值 html赋值给js变量_模块化_02