了解es6之模块化,以及模块化的历史进程

#####ES6模块

ES6模块自动采用严格模式,通过export命令显示指定输出代码,再通过import命令输入。

  • export输出命令:export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
1
2
3
4
5
6
7
//profile.js
export var name = 'tom'
export var age = 18
---------推荐以下这种写法
var name ='tom'
var age =18
export {name,age}
1
2
3
4
5
6
7
8
9
10
11
#对外输出函数fn
export function fn(x,y){
return x+y
}
---------或者
function fn(x,y){
return x+y
}
export {fn}
#重命名
export {fn as m}
  • import 命令: 具体提升效果,会提升到整个模块的头部
1
2
3
4
//main.js
import{name,age} from './profile.js' //js后缀可以省略
-----------也可以重命名
import {name as firstName} from './profile'
  • 模块整体加载

    除了指定加载某个输出值,还可以使用整体加载,即用星号(*指定一个对象,所有输出值都加载在这个对象上面。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //circle.js
    function area(radius){
    return Math.PI * radius * radius
    }
    function circumference(radius){
    return 2*Math.PI * radius
    }
    export{area,circumference}
    ------------------
    import{area,circumference} form './circle'
    console.log(area(2))
    --或者
    import * as circle from './circle' 用*指定一个对象,可以用重命名的circle.xxx来调用属性或者方法
    console.log('面积:'+ circle.area(2))
  • export default:为模块指定默认输出,不需要知道对外输出的变量名或者函数名,用import引入的时候重命名就行。

    注:一个模块只能有一个默认输出,所以export default只能使用1次

    1
    2
    3
    4
    5
    6
    7
    8
    //export-default.js
    export default function(){
    console.log('fuck')
    } //默认输出一个函数,其他模块加载时,可以为改匿名函数指定任意名字
    ----------------
    // import-default.js
    import customName from './export-default'
    customName() // 'fuck' customName就是上面那个函数名。此时import不需要{}了!!
    1
    2
    3
    4
    5
    6
    非匿名函数也可以用export default来输出,但是在其他模块加载的时候,视同匿名函数加载
    //fn.js
    export default function fn(){
    //...
    }
    import fn from './fn.js'
  • export 和 import的复合写法

    在一个模块中,先输入后输出同一个模块,可以用以下写法export{} from ‘’

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    export {foo,bar} from 'myModule'
    等同于
    import {foo,bar} from 'myModule'
    export {foo,bar}
    -------------------
    整体输出:export * from 'myModule'
    -------------------
    默认接口写法:export {default} from 'myModule'
    具名接口改为默认接口:export {foo as default} from 'myModule'
    等同于 import {foo} from './myModule'
    export default foo
模块在浏览器中的加载

默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。

如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。

1
2
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

上面代码中,<script>标签打开deferasync属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。

deferasync的区别是:前者要等到整个页面正常渲染结束,才会执行;后者一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

  1. 浏览器加载ES6模块,也适用<script>标签,但是要加入type=”module”属性,加了这个属性等同于加了defer。

    1
    <script type="module" src="foo.js"></script>
    1
    2
    #也可以添加async
    <script type="module" src="foo.js" async></script>
  2. ES6模块和CommonJS模块的差异

    • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

      CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。而ES6模块原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

    • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

      CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。


模块化的进程
  1. 各种全局变量

    1
    2
    3
    4
    5
    6
    7
    8
    var $topbar = $('#topbar')
    $topbar.on('click',functuion(){
    console.log('topbar')
    })
    var $banners = $('#banners')
    $banners.on('click',function(){
    console.log('banners')
    })
  2. 用立即执行函数消除全局变量(或者用{}块级作用域+let)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    !function(){
    var $topbar = $('#topbar')
    $topbar.on('click',functuion(){
    console.log('topbar')
    })
    }() //让这些变量变成局部变量,放进一个函数里并且调用,但是函数前要加上一个符号(+,-,!等)
    !function(){
    var $banners = $('#banners')
    $banners.on('click',functuion(){
    console.log('banners')
    })
    }()
    ------------------
    {
    let $topbar = $('#topbar')
    $topbar.on('click',functuion(){
    console.log('topbar')
    })
    } //或者用es6语法:块级作用域,局部变量
  3. 按照上述方法,作用域与作用域之间是隔开的,如果作用域B想使用作用域A的变量,怎么办?可以使用两个作用域都能访问到的全局变量window,作为桥梁,赋值给window属性并访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    !function(){
    var $topbar = $('#topbar')
    var user = {
    name:'yom',
    age:20
    }
    window.user = user //全局变量
    }()
    !function(){
    console.log(window.user)
    }()

    如果不能让别的作用域改user,只能读(暴露一个函数,不要暴露整个变量)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    !function(){
    var $topbar = $('#topbar')
    var user = {
    name:'yom',
    age:20
    }
    window.user = {
    nameGetter:function(){return user.name},
    ageGetter:fucntion(){return user.age}
    }
    }()
    !function(){
    console.log(window.user.nameGetter) //yom
    }()

    什么时候闭包:只要一个函数使用了它外面的变量,这个函数就是闭包,闭包是指这个函数以及它能访问到的这个变量。闭包是作用域的一种特殊的使用方式。

  4. 举例:年龄增长器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function olderMaker(){
    var user = {
    name:'jack',
    age:18
    }
    return function(){
    user.age += 1
    }
    }
    var older = olderMaker() //函数
    older.call() 每call一下,年龄增加一岁
  5. require.js(固定使用window.require()与window.define())

    • 首先引入require.js

      1
      <script src="js/require.js"></script>
    • 加载完require.js后,加载自己的代码,如果我们的代码主文件是main.js,那么需要用data-main=”./main”来引入(data-main属性的作用是,指定网页程序的主模块。)

      1
      <script src="js/require.js" data-main="js/main"></script>
    • 主模块的写法,需要使用AMD规范定义的require()函数。

      function里的参数可以是任何命名,但是参数是按照顺序的

      1
      2
      3
      4
      5
      6
      //main.js
      require(['./moduleA','moduleB','moduleC'],function(moduleA, moduleB, moduleC){
      some code here
      })
      //require()函数接受两个参数。第一个参数是一个数组,表示所依赖的模块,上例就是['moduleA', 'moduleB', 'moduleC'],即主模块依赖这三个模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,并且参数是按照模块顺序的!!
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      require.config({
          paths: {
            "jquery": "lib/jquery.min",
            "underscore": "lib/underscore.min",
            "backbone": "lib/backbone.min"
          }
        });
      //使用require.config()方法,我们可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。
      ---------------------or直接改变基目录(baseUrl)。
      require.config({
      baseUrl:'js/lib',
      paths:{
       "jquery": "jquery.min",
         "underscore": "underscore.min",
         "backbone": "backbone.min"
      }
      })
    • 假定现在有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      // math.js
        define(function(){
       var add = function (x,y){
            return x+y;
          };
          return {
            add: add
          };
        })
      加载方法如下:
        // main.js
        require(['math'], function (math){
          alert(math.add(1,1));
        });
    • 如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。

        define([‘myLib’], function(myLib){

          function foo(){

            myLib.doSomething();

          }

          return {

            foo : foo

          };

        });

      当require()函数加载上面这个模块的时候,就会先加载myLib.js文件。

  6. 举例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    index.html
    require.js
    main.js
    ......topbar.js
    ......banners.js
    ......slides.js
    .......plugin.js
    index.html:
    <script src="./require.js" data-main="./main"><script/> //data-main="" 路径
    plugin.js:-----------
    define(function(){
    console.log('this is plugin')
    function aaa(){
    console.log('bbb')
    }
    return aaa
    })
    banners.js:----------
    define(functuon(){
    console.log('banners')
    })
    topbar.js:------------
    define([‘./plugin’],function(plugin){
    console.log('topbar')
    console.log(aaa)
    })
    slides.js:----------
    define([‘./plugin’],function(plugin){
    console.log('slides')
    aaa()
    })
    main.js:-----------
    require(['./slides','./banners','./topbar'],function(){
    console.log('main is runned')
    })

    加载顺序:1. 先加在没有依赖的模块js

    ​ 2.再加载子模块依赖的plugin模块

​ 3.最后加载main.js主文件

common.js

暴露:exports.xxx={} / module.exports={} ,接收:let 变量=require('./xxx')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//适用于node.js,不适用于浏览器的异步
ModuleA.js:
exports.xxx={ //暴露一个变量
name:'Tom'
}
---------------
index.js
let a = require('./MoudleA')
console.log(a)
//{xxx:{name:'Tom'}} 得到一个xxx属性的对象,相当于a=exports
等价于-----------
module.exports={
xxx:{
name:'Tom'
}
}

:如果ModuleA.js中只想暴露name,不像暴露xxx,不能写成这样

1
2
3
4
5
6
7
8
9
10
11
exports={
name:'Jack'
}
//index.js运行的结果是空对象{}
//这是因为默认module.exports={},标准写法是exports.xxx=yyy,表示在这个空对象里加属性,但是如果直接像上面那样写exports={xxx:yyy},表示另外一块内存,原先的module.exports={}不变
module.exports={
name:'jack'
}
或者
exports.name='jack'

AMD(异步模块定义)

require.js

ES Modules

1
2
3
4
5
6
7
8
9
10
//使用模块
import {* as a} from './ModuleA'
console.log(a.xxx)
console.log(a.yyy)
//暴露
let xxx={}
export xxx
let yyy={}
export yyy