聊聊前端模块化开发

随着JavaScript开发变得愈来愈广泛,命名空间和依赖性变得愈来愈难以处理。前端开发者都以模块化的方式处理该问题。在这篇文章中,咱们将探讨前端开发人员目前使用的模块化方案以及试图解决的问题。javascript

为何须要JavaScript模块?

模块化可使你的代码低耦合,功能模块直接不相互影响。html

  1. 可维护性:每一个模块都是单独定义的,之间相互独立。模块尽量的须要和外部撇清关系,方便咱们独立的对其进行维护与改造。维护一个模块比在全局中修改逻辑判断要好的多。
  2. 命名空间:为了不在JavaScript中的全局污染,咱们经过模块化的方式利用函数做用域来构建命名空间。
  3. 可复用性:虽然粘贴复制很简单,可是考虑到咱们以后的维护以及迭代,你会至关崩溃。

模块化的解决方案有哪些?

讲完了JavaScript模块化的好处,咱们来看下有哪些解决方案来实现JavaScript的模块化。前端

揭示模块模式(Revealing Module)

var myRevealingModule = (function () {
    var privateVar = "Ben Cherry",
        publicVar = "Hey there!";
        
    function privateFunction() {
        console.log( "Name:" + privateVar );
    }
    function publicSetName( strName ) {
        privateVar = strName;
    }
    function publicGetName() {
        privateFunction();
    }
    // Reveal public pointers to
    // private functions and properties
    return {
        setName: publicSetName,
        greeting: publicVar,
        getName: publicGetName
    };
})();
myRevealingModule.setName( "Paul Kinlan" );

经过这种构造,咱们经过使用函数有了本身的做用域或“闭包”。java

这种方法的好处在于,你能够在函数内部使用局部变量,而不会意外覆盖同名全局变量,但仍然可以访问到全局变量。node

优势:es6

  • 能够在任何地方实现(没有库,不须要语言支持)。
  • 能够在单个文件中定义多个模块。

缺点:编程

  • 没法以编程方式导入模块(除非使用eval)。
  • 须要手动处理依赖关系。
  • 没法异步加载模块。
  • 循环依赖可能很麻烦。
  • 很难经过静态代码分析器进行分析。

CommonJS

CommonJS是一个旨在定义一系列规范的项目,以帮助开发服务器端JavaScript应用程序。CommonJS团队试图解决的一个领域就是模块。Node.js开发人员最初打算遵循CommonJS规范,但后来决定反对它。segmentfault

在 CommonJS 的规范中,每一个 JavaScript 文件就是一个独立的模块上下文(module context),在这个上下文中默认建立的属性都是私有的。也就是说,在一个文件定义的变量(还包括函数和类),都是私有的,对其余文件是不可见的。浏览器

须要注意的一点是,CommonJS以服务器优先的方式来同步载入模块,假使咱们引入三个模块的话,他们会一个个地被载入。服务器

// In circle.js
const PI = Math.PI;
exports.area = (r) => PI * r * r;
exports.circumference = (r) => 2 * PI * r;
// In some file
const circle = require('./circle.js');
console.log( `The area of a circle of radius 4 is ${circle.area(4)}`);

Node.js的模块系统经过library的方式对CommonJS的基础上进行了模块化实现。

在Node和CommonJS的模块中,基本上有两个与模块系统交互的关键字:require和exports。

require是一个函数,可用于将接口从另外一个模块导入当前范围。传递给的参数require是模块的id。在Node的实现中,它是node_modules目录中模块的名称(或者,若是它不在该目录中,则是它的路径)。

exports是一个特殊的对象:放入它的任何东西都将做为公共元素导出。

Node和CommonJS之间的一个独特区别在于module.exports对象的形式。

在Node中,module.exports是导出的真正特殊对象,而exports它只是默认绑定到的变量module.exports。

另外一方面,CommonJS没有任何module.exports对象。实际意义是,在Node中,没法经过如下方式导出彻底预构造的对象module.exports:

// This won't work, replacing exports entirely breaks the binding to
// modules.exports.
exports = (width) => {
    return {
        area: () => width * width
    };
}
// This works as expected.
module.exports = (width) => {
    return {
        area: () => width * width
    };
}

优势

  • 简单:开发人员能够在不查看文档的状况下掌握概念。
  • 集成了依赖管理:模块须要其余模块并按所需顺序加载。
  • require能够在任何地方调用:模块能够经过编程方式加载。
  • 支持循环依赖。

缺点

  • 同步API使其不适合某些用途(客户端)。
  • 每一个模块一个文件。
  • 浏览器须要加载程序库或转换。
  • 模块没有构造函数(Node支持)。
  • 很难进行静态代码分析。

AMD

AMD诞生于一群对CommonJS的研究方向不满的开发人员。事实上,AMD在开发早期就与CommonJS分道扬镳,AMD和CommonJS之间的主要区别在于它支持异步模块加载。

//Calling define with a dependency array and a factory function
define(['dep1', 'dep2'], function (dep1, dep2) {
    //Define the module value by returning a value.
    return function () {};
});
// Or:
define(function (require) {
    var dep1 = require('dep1'),
    dep2 = require('dep2');
    return function () {};
});

经过使用JavaScript的传统闭包来实现异步加载:

在请求的模块加载完成时调用函数。模块定义和导入模块由同一个函数承载:定义模块时,其依赖关系是明确的。所以,AMD加载器能够在运行时具备项目的模块依赖图。所以能够同时加载彼此不依赖的库。这对于浏览器尤为重要,由于启动时间对于良好的用户体验相当重要。

优势

  • 异步加载(更好的启动时间)。
  • 支持循环依赖。
  • require和的兼容性exports。
  • 彻底整合了依赖管理。
  • 若有必要,能够将模块拆分为多个文件。
  • 支持构造函数。
  • 插件支持(自定义加载步骤)。

缺点

  • 语法稍微复杂一些。
  • 除非编译,不然须要加载程序库。
  • 很难分析静态代码。

除了异步加载之外,AMD的另外一个优势是你能够在模块里使用对象、函数、构造函数、字符串、JSON或者别的数据类型,而CommonJS只支持对象。

UMD

统一模块定义(UMD:Universal Module Definition )就是将 AMD 和 CommonJS 合在一块儿的一种尝试,常见的作法是将CommonJS 语法包裹在兼容 AMD 的代码中。

(function(define) {
    define(function () {
        return {
            sayHello: function () {
                console.log('hello');
            }
        };
    });
}(
    typeof module === 'object' && module.exports && typeof define !== 'function' ?
    function (factory) { module.exports = factory(); } :
    define
));

该模式的核心思想在于所谓的 IIFE(Immediately Invoked Function Expression),该函数会根据环境来判断须要的参数类别

ES6模块

支持JavaScript标准化的ECMA团队决定解决模块问题, 兼容同步和异步操做模式。

//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}
//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

ES6 模块的设计思想是尽可能的静态化,使得编译时就能肯定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时肯定这些东西。

因为 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,好比引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。除了静态加载带来的各类好处,ES6 模块还有如下好处。

  • 再也不须要UMD模块格式了,未来服务器和浏览器都会支持 ES6 模块格式。目前,经过各类工具库,其实已经作到了这一点。
  • 未来浏览器的新 API 就能用模块格式提供,再也不必须作成全局变量或者navigator对象的属性。
  • 再也不须要对象做为命名空间(好比Math对象),将来这些功能能够经过模块提供。

ES6 的模块自动采用严格模式,无论有没有在模块头部加上"use strict";。 

严格模式主要有如下限制。

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

其中,尤为须要注意this的限制。ES6 模块之中,顶层的this指向undefined,即不该该在顶层代码使用this。

export

export语法被用来建立JavaScript模块。你能够用它来导出对象(包括函数)和原始值(primitive values)。导出有两种类型:named和default。

// named
// lib.js
export function sum(a, b) {
    return a + b;
}
export function substract(a, b) {
    return a - b;
}
function divide(a, b) {
    return a / b;
}
export { divide };
// default
// dog.js
export default class Dog {
    bark() {
    console.log('bark!');
    }
}

import

import语句用来导入其余模块。

整个导入

// index.js 
import * as lib from './lib.js'; console.log(lib.sum(1,2)); console.log(lib.substract(3,1)); console.log(lib.divide(6,3));

导入一个或多个named导出

// index.js
import { sum, substract, divide } from './lib';
console.log(sum(1,2));
console.log(substract(3,1));
console.log(divide(6,3));

须要注意,相应的导入导出名字必须匹配。

导入一个default导出

// index.js 
import Dog from './dog.js'; 
const dog = new Dog(); 
dog.bark(); // 'bark!'

注意,defualt导出在导入时,能够用任意的名字。因此咱们能够这样作:

import Cat from './dog.js';

const dog = new Cat();
dog.bark(); // 'bark!'

参考

  1. 很全很全的JavaScript的模块讲解 http://www.javashuo.com/article/p-phjafnrd-h.html
  2. JavaScript Module Systems Showdown: CommonJS vs AMD vs ES2015 https://auth0.com/blog/javascript-module-systems-showdown/
  3. ECMAScript 6 入门http://es6.ruanyifeng.com/#docs/module

原文出处:https://www.cnblogs.com/jingh/p/10915466.html

相关文章
相关标签/搜索