震惊!!就连业界大佬的为之颤抖的JavaScript

1、JavaScript 模块发展史

1.1 Vanilla JS(1995~2009)

JavaScript 被开发出来的时候,是没有模块标准的,因为 JavaScript 的设计初衷就是作为一个 toy script,在浏览器中做一些简单的交互。但是随着互联网的高速发展,人们已经不再满足于简单的交互,而代码的复杂度也日益增长,维护难度也越来越高。

那么维护指的是维护什么呢?指的是维护变量。因为随着项目不断迭代,多人协同开发是不可避免的。在 JS 初期所有变量都写在全局作用域上,那么很可能出现的问题是什么呢?变量的覆盖、篡改和删除,这是一个很头疼的问题。很可能突然有一天你的功能报错了,就是因为你的某个变量被另一位开发者所删除了。

所以对于模块的引入初衷是为了解对变量的控制。当然还有其他的好处,例如对代码的封装、复用等等。

那么初期在没有模块标准的支持下,开发者们是如何实现类似模块的效果呢?有 2 种方式。

1.1.1 Object Literal Pattern(对象字面量)

使用 JS 内置的对象对变量进行控制:

function Person(name) {

  this.name = name;

}

Person.prototype.talk = function () {

  console.log("my name is", this.name);

};

const p = new Person("anson");

p.talk();

复制代码

这样就可以通过 new Person 的方式把变量都控制在对象内部。

1.1.2 IIFE(Immediately Invoked Function Expression)

我们知道在 JavaScript 中有作用域(Scope)的概念,在作用域内的变量,只在作用域内可见。在 ES6 之前,作用域只有 2 种,分别是:

全局作用域(Global Scope)

函数作用域(Function Scope)

上面提到了对变量的控制,那么肯定是把变量的作用范围控制的越小越好,所以毫无疑问把变量写在函数内是最好的办法。但是,这又引发了另一个问题,函数中的变量要如何提供给外部使用呢?

这个问题在初期并没有很好的解决方法,你必须把变量暴露到全局作用域中,例如经典的 jQuery。

而开发者们通常会使用 IIFE 去实现:

// lib.js

(function() {

  const base = 10;

  this.sumDOM = function(id) {

    // 依赖 jQuery

    return base + +$(id).text();

  }

})();

复制代码

在 HTML 中引入 lib.js:

// index.html

 

   

   

 

 

   

 

复制代码

但是 IIFE 有几个问题:

至少一个变量污染全局作用域;

模块之间的依赖关系模糊,不明确(lib.js 不能直观看出依赖 jquery.js);

加载顺序无法保证,不好维护(必须确保 jquery.js 必须在 lib.js 前加载完成,否则会报错)。

所以,JavaScript 非常需要一个模块标准来解决上述问题。

1.2 Non-Native Module Format & Module Loader(2009~2015)

由于模块能为我们解决上述问题,所以开发者尝试着自己去设计一些非原生模块标准如 CommonJS、AMD (Asynchronous Module Definition)、UMD (Universal Module Definition),然后搭配对应的 Module Loader 如 cjs-loader、RequireJS、SystemJS 可以实现模块的效果,我们下面过一下几个流行的非原生模块标准。

1.2.1 CommonJS (CJS)

2009 年,来自 Mozilla 的工程师 Kevin 提出了为运行在浏览器以外的 JavaScript 建立一个模块标准 CommonJS,主要应用在服务端如 Node.js。因为使用效果不错,随后也被用在浏览器的模块开发中,但由于浏览器并不支持 CommonJS,所以代码需要通过 Babel 等 transpiler 转换为 ES5 才能在浏览器上运行。

CommonJS 的特征是使用 require 来导入依赖,exports 来导出接口。

// lib.js

module.exports.add = function add() {};

// main.js

const { add } = require("./lib.js");

add();

复制代码

1.2.2 AMD

因为 CommonJS 设计初衷是应用在服务端的,所以模块的加载执行也都是同步的(因为本地文件的 IO 很快)。但是同步的方式运用到浏览器就不友好了,因为在浏览器中模块文件都是通过网络加载的,单线程阻塞在模块加载上,这是不可接受的。所以在 2011 年有人提出了 AMD,对 CommonJS 兼容的同时支持异步加载。

AMD 的特征是使用 define(deps, callback) 来异步加载模块。

// 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 () {};

});

复制代码

1.2.3 UMD

因为 CommonJS 和 AMD 的流行,随后又有人提出了 UMD 的模块标准,UMD 通过对不同的环境特性进行检测,对 AMD、CommonJS 和 Global Variable 三种格式兼容。

// UMD

(function (root, factory) {

  if (typeof define === 'function' && define.amd) {

    // AMD

    define(['jquery', 'underscore'], factory);

  } else if (typeof exports === 'object') {

    // Node, CommonJS-like

    module.exports = factory(require('jquery'), require('underscore'));

  } else {

    // Browser globals (root is window)

    root.returnExports = factory(root.jQuery, root._);

  }

}(this, function ($, _) {

  //    methods

  function a(){};    //    private because it's not returned (see below)

  function b(){};    //    public because it's returned

  function c(){};    //    public because it's returned

  //    exposed public methods

  return {

    b: b,

    c: c

  }

}));

复制代码

因为 UMD 的兼容性好,不少库都会提供 UMD 的版本。

1.3 ESM(2015~now)

随着 ECMAScript 的逐渐规范化、标准化,终于在 2015 年发布了 ES6(ES 2015),在这次版本更新中,制定了 JS 模块标准即 ES Modules,ES Modules 使用 import 声明依赖,export 声明接口。

// lib.mjs

const lib = function() {};

export default lib;

// main.js

import lib from './lib.mjs';

复制代码

截止到 2018 年,大部分主流浏览器都已经支持 ES Modules,在 HTML 中通过为

你可能感兴趣的:(震惊!!就连业界大佬的为之颤抖的JavaScript)