前端模块化之路

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/angelsunshuli/article/details/60765073

作为一个前端方向的探路者,近期写项目应用到模块化开发的知识。参考别人项目加上之前自己的经验积累,我发现自己对require、export、module、define这些模块化用到的语法词汇有些混淆,于是决定一探究竟。


前端模块化路径:函数封装(缺点:污染了全局变量)——>对象的写法(缺点:外部可以随意修改内部成员)——>立即执行函数


经查阅,前端模块化有以下几种:common.js规范、AMD/CMD规范、UMD规范、ES6module。下面依次介绍这几种规范。

一、common.js规范——node.js采用

//require方法默认读取js文件,所以可以省略js后缀
var test = require('./foobar').foobar;
test.bar();
//私有变量
var test = 123;
//公有方法
function foobar () {
    this.foo = function () {
        // do someing ...
    }
    this.bar = function () {
        //do someing ...
    }
}
//exports对象上的方法和变量是公有的
var foobar = new foobar();
exports.foobar = foobar;

(一)要点:

1、CommonJs 是服务器端模块的规范

2、CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。

3、加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。

{
  id: '...',  //id是模块名
  exports: { ... },  //exports是该模块导出的接口
  loaded: true,  //loaded表示模块是否加载完毕。
  ...
}

4、根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在一个文件定义的变量(还包括函数和类),都是私有的,对其他文件是不可见的。

(二)适用性:

      像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。

(三)浏览器环境能不能用

得力于 Browserify 这样的第三方工具,我们可以在浏览器端使用采用CommonJS规范的js文件。

二、AMD/CMD规范

(一) AMD异步模块加载规范——require.js

//通过数组引入依赖 ,回调函数通过形参传入依赖
define(['someModule1', ‘someModule2’], function (someModule1, someModule2) {
    function foo () {
        /// someing
        someModule1.test();
    }
    return {foo: foo}
});

它采用异步方式加载模块,模块的加载不影响它后面语句的运行。 但是,加载内部是同步的(加载完模块后立即执行回调)。

(二)CMD通用模块加载规范——SeaJS

//require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接
//口:require(id)
//exports 是一个对象,用来向外提供模块接口
//module 是一个对象,上面存储了与当前模块相关联的一些属性和方法
define(function (require, exports, module) {
    // 依赖可以就近书写
    var a = require('./a');
    a.test();  
    // ...
    if (status) {
        // 依赖可以就近书写
        var b = requie('./b');
        b.test();
    }
});

依赖可就近书写

(三)AMD和CMD的区别:

2、对依赖模块的执行时机处理不同:

  • 同样都是异步加载模块,AMD在加载模块完成后就会执行改模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行

  • CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的

三、UMD规范(通用模块规范)——AMD和CommonJS的糅合

(function (window, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {   
        define(factory);
    } else {   
        window.eventUtil = factory();
    }
})(this, function () {
    //module ...
});

四、ES6module

(一)es6的设计思想:

      尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出变量。而common.js和AMD模块都只能在运行时确定这些东西。所以es6效率比较高。

(二) es6的模块采用严格模式,所以有以下限制

  • 变量必须声明后再使用;
  • 函数的参数不能有同名属性,否则报错;
  • 不能使用with语句;
  • 不能对只读属性赋值;
  • 不能使用前缀0表示八进制数;
  • 不能删除不可删除的属性;
  • 不能删除变量,只能删除属性;
  • eval不会在其外层作用域引入变量;
  • eval和arguments不能被重新赋值;
  • arguments不会自动反映函数参数的变化;
  • 不能使用arguments.callee、arguments.caller
  • 禁止this指向全局对象;
  • 不能使用fn.caller和fn.arguments获取函数调用的堆栈;
  • 增加了保留字(如protected、static、interface)

(三)es6通过import、export实现模块的输入输出。

其中import命令用于输入其他模块提供的功能,export命令用于规定模块的对外接口。一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。

1、export

var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName,lastName,year};

(1)export可以输出变量、函数或者类
(2)export导出的变量只能位于文件的顶层,如果处于块级作用域内,会报错。
(3)export语句输出的值是动态绑定,绑定其所在的模块。

export var foo='bar';
setTimeout(()=>foo='baz',500);
//代码输出变量foo,值为bar,500毫秒之后变成baz

(4)可以通过as给变量函数等重新命名

2、import

import {firstName,lastName,year} from './profile';

(1)import命令具有提升效果
(2)import和export可以写在一起

3、模块的整体加载

import * as circle from './circle'
console.log("圆的面积:"+circle.area(4))
//其中area是circle文件里定义的函数

4、export default为模块指定默认输出

默认输出,import语句不需要使用大括号

(四)ES6模块加载的实质

1、CommonJS模块输出的是一个值的拷贝,而es6模块输出的是值的引用。它并不会缓存运行结果,而是动态地区被加载的模块中取值。
2、es6输出的模块变量知识一个“符号链接”,所以这个变量是只读的,对它进行重新赋值会报错。

(五)循环加载:

1、理解:a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。

2、commonjs和es6处理循环加载的原理:


  • commonjs:

(1)模块加载原理:require命令第一次加载该脚本就会执行整个脚本,然后在内存中生成一个exports对象。以后需要用到这个模块时,就会到exports属性上取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存中取值。


(2)模块的循环加载:一旦出现某个模块被“循环加载”,就只输出已经执行的部分,未执行的部分不会输出。


  • es6:

es6模块时动态引用,遇到模块加载明星import时不会去执行模块,只是生成一个指向被加载模块的引用,需要开发者自己保证真正取值时能够取到值。

猜你喜欢

转载自blog.csdn.net/angelsunshuli/article/details/60765073