この記事では、javascript に関する関連知識を提供し、主にモジュラー プログラミング仕様、CommonJS、AMD、CMD、ES6 関連の問題について紹介します。
関連する推奨事項: JavaScript 学習チュートリアル
AMD、 CMD と CommonJs
は ES5
で提供されるモジュール式プログラミング ソリューションであり、import/export
は ES6
で提供される新しいモジュール式プログラミング ソリューションです。
それでは、AMD、CMD、CommonJs
とは一体何なのでしょうか?それらの違いは何ですか?プロジェクト開発にはどのモジュール式プログラミング仕様を使用する必要がありますか?また、それをどのように使用するか?このブログ投稿では、上記の質問に 1 つずつ答えます。
AMD
は、「Asynchronous Module Definition
」、つまり「Asynchronous」の略称です。モジュール定義」。モジュールは非同期でロードされ、モジュールのロードは後続のステートメントの実行には影響しません。
ここでの非同期とは、ブラウザの他のタスク (dom
構築、css
レンダリングなど) をブロックしないことを指しますが、読み込みは内部的に同期されます (読み込み直後)。モジュール実行コールバック)。
RequireJS
: これは、モジュールの読み込み方法に従って、JS
ファイルを非同期的に読み込むことができるAMD
フレームワークです。 define() 関数の定義。最初のパラメータは配列であり、いくつかの依存パッケージを定義します。2 番目のパラメータはコールバック関数で、変数を通じてモジュール内のメソッドを参照し、最後に return を通じて出力します。
AMD
は、プロモーション プロセス中の RequireJS
のモジュール定義の標準化された出力です。これは概念であり、RequireJS
はこの概念の実装は、JavaScript
言語が ECMAScript
仕様の実装であるのと同じです。 AMD
は組織であり、RequireJS
はこの組織の下でカスタマイズされたスクリプト言語のセットです。
CommonJS とは異なり、2 つのパラメータが必要です。
require([module], callback);
[module] は、メンバーを含む配列であり、モジュールはモジュールです
callback はロード完了後のコールバック関数です。上記のコードを
AMD メソッドに変更すると、
require(['math'], function(math) { math.add(2, 3);})
requireJSLoading モジュールは
AMD 仕様を採用しています。つまり、モジュールは
AMD で指定された方法で作成する必要があります。
define() 関数を使用して定義する必要があります。モジュールが他のモジュールに依存していない場合は、
define() 関数に直接記述することができます。
define(id, dependencies, factory);
- id
: モジュールの名前。このパラメータが指定されていない場合、モジュール名はデフォルトで、モジュール ローダーによって要求された指定されたスクリプト名になります。
- dependency
: モジュールの依存関係、モジュールによって定義されたモジュール識別子の配列リテラル。依存関係パラメータはオプションです。このパラメータを省略した場合は、デフォルトで
["require", "exports", "module"]になります。ただし、ファクトリ メソッドの長さ属性が 3 未満の場合、ローダーは関数の長さ属性で指定された引数の数でファクトリ メソッドを呼び出すことを選択します。
- factory
: モジュールのファクトリ関数、モジュールの初期化中に実行される関数またはオブジェクト。関数の場合は、一度だけ実行する必要があります。それがオブジェクトの場合、このオブジェクトはモジュールの出力値である必要があります。
math モジュールを定義する
math.js ファイルがあると仮定します。次に、
math.js は次のように記述されます。
// math.jsdefine(function() { var add = function(x, y) { return x + y; } return { add: add }})
// main.jsrequire(['math'], function(math) { alert(math.add(1, 1));})
math モジュールも依存している場合他のモジュールの場合、次のように記述されます。
// math.jsdefine(['dependenceModule'], function(dependenceModule) { // ...})
require() 関数が
math モジュールをロードすると、
dependencyModule モジュールは次のようになります。最初にロードされました。複数の依存関係がある場合、すべての依存関係は
define() 関数の最初のパラメーター配列に書き込まれるため、
AMD は事前依存になります。これは、近接性に依存する
CMD 仕様とは異なります。
CMD
CMD は
Common Module Definition共通モジュール定義であり、
SeaJS ですプロモーションプロセス中のモジュール定義の標準化された出力は、
SeaJSの標準である同期されたモジュール定義です。
SeaJSは
CMDの概念です。実装、
SeaJS は、淘宝網チーム Yubo が提供するモジュール開発
js フレームワークです。
CMD仕様は国内で開発されており、
AMD には
requireJS があり、
CMD にはブラウザ実装
SeaJS があるのと同じように、
SeaJS 解くべき問題は
requireJS と同じですが、モジュールの定義方法とモジュールの読み込み(実行、解析とも言えます)のタイミングが異なります。
CMD
通过define()
定义,没有依赖前置,通过require
加载jQuery
插件,CMD
是依赖就近,在什么地方使用到插件就在什么地方require
该插件,即用即返,这是一个同步的概念。
在 CMD
规范中,一个模块就是一个文件。代码的书写格式如下:
define(function(require, exports, module) { // 模块代码});
其中,
require
是可以把其他模块导入进来的一个参数;exports
是可以把模块内的一些属性和方法导出的;module
是一个对象,上面存储了与当前模块相关联的一些属性和方法。
AMD
是依赖关系前置,在定义模块的时候就要声明其依赖的模块;CMD
是按需加载依赖就近,只有在用到某个模块的时候再去require
,示例代码如下:
// CMDdefine(function(require, exports, module) { var a = require('./a') a.doSomething() // 此处略去 100 行 var b = require('./b') // 依赖可以就近书写 b.doSomething() // ... })// AMD 默认推荐的是define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 a.doSomething() // 此处略去 100 行 b.doSomething() ...})
CommonJS
规范是通过module.exports
定义的,在前端浏览器里面并不支持module.exports
,通过node.js
后端使用。Nodejs
端使用CommonJS
规范,前端浏览器一般使用AMD
、CMD
、ES6
等定义模块化开发规范。
CommonJS
的终极目标是提供一个类似Python
,Ruby
和Java
的标准库。这样的话,开发者可以使用CommonJS API
编写应用程序,然后这些应用就可以运行在不同的JavaScript
解释器和不同的主机环境中。
在兼容CommonJS
的系统中,你可以使用JavaScript
开发以下程序:
- 服务器端
JavaScript
应用程序;- 命令行工具;
- 图形界面应用程序;
- 混合应用程序(如,Titanium或Adobe AIR);
2009年,美国程序员Ryan Dahl创造了node.js
项目,将javascript
语言用于服务器端编程。这标志"Javascript
模块化编程"正式诞生。NodeJS
是CommonJS
规范的实现,webpack
也是以CommonJS
的形式来书写。
node.js
的模块系统,就是参照CommonJS
规范实现的。在CommonJS
中,有一个全局性方法require()
,用于加载模块。假定有一个数学模块math.js
,就可以像下面这样加载。
var math = require('math');
然后,就可以调用模块提供的方法:
var math = require('math');math.add(2,3); // 5
CommonJS
定义的模块分为:模块引用(require)、 模块定义(exports)、模块标识(module)。
其中,
require()
用来引入外部模块;exports
对象用于导出当前模块的方法或变量,唯一的导出口;module
对象就代表模块本身。
虽说NodeJS
遵循CommonJS
的规范,但是相比也是做了一些取舍,添了一些新东西的。
NPM
作为Node
包管理器,同样遵循CommonJS
规范。
下面讲讲commonJS
的原理以及简易实现:
1、原理
浏览器不兼容CommonJS
的根本原因,在于缺少四个Node.js
环境变量。
module exports require global
只要能够提供这四个变量,浏览器就能加载 CommonJS
模块。
下面是一个简单的示例。
var module = { exports: {}};(function(module, exports) { exports.multiply = function (n) { return n * 1000 }; }(module, module.exports))var f = module.exports.multiply; f(5) // 5000
上面代码向一个立即执行函数提供 module 和 exports 两个外部变量,模块就放在这个立即执行函数里面。模块的输出值放在 module.exports 之中,这样就实现了模块的加载。
2、Browserify 的实现Browserify
是目前最常用的 CommonJS
格式转换工具。
请看一个例子,main.js
模块加载 foo.js
模块。
// foo.jsmodule.exports = function(x) { console.log(x);};// main.jsvar foo = require("./foo");foo("Hi");
使用下面的命令,就能将main.js
转为浏览器可用的格式。
$ browserify main.js > compiled.js
其中,Browserify
到底做了什么?安装一下browser-unpack
,就清楚了。
$ npm install browser-unpack -g
然后,将前面生成的compile.js解包。
$ browser-unpack < compiled.js
[ { "id":1, "source":"module.exports = function(x) {\n console.log(x);\n};", "deps":{} }, { "id":2, "source":"var foo = require(\"./foo\");\nfoo(\"Hi\");", "deps":{"./foo":1}, "entry":true }]
可以看到,browerify
将所有模块放入一个数组,id
属性是模块的编号,source
属性是模块的源码,deps
属性是模块的依赖。
因为 main.js
里面加载了 foo.js
,所以 deps
属性就指定 ./foo
对应1号模块。执行的时候,浏览器遇到 require('./foo')
语句,就自动执行1号模块的 source
属性,并将执行后的 module.exports
属性值输出。
有关es6
模块特性,强烈推荐阮一峰老师的:ECMAScript 6 入门 - Module 的语法专栏。
要说 ES6
模块特性,那么就先说说 ES6
模块跟 CommonJS
模块的不同之处。
ES6
模块输出的是值的引用,输出接口动态绑定,而CommonJS
输出的是值的拷贝;ES6
模块编译时执行,而CommonJS
模块总是在运行时加载。
CommonJS
模块输出的是值的拷贝(原始值的拷贝),也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
// a.jsvar b = require('./b');console.log(b.foo);setTimeout(() => { console.log(b.foo); console.log(require('./b').foo);}, 1000);// b.jslet foo = 1;setTimeout(() => { foo = 2;}, 500);module.exports = { foo: foo,};// 执行:node a.js// 执行结果:// 1// 1// 1
上面代码说明,b 模块加载以后,它的内部 foo 变化就影响不到输出的 exports.foo 了。这是因为 foo 是一个原始类型的值,会被缓存。所以如果你想要在 CommonJS
中动态获取模块中的值,那么就需要借助于函数延时执行的特性。
// a.jsvar b = require('./b');console.log(b.foo);setTimeout(() => { console.log(b.foo); console.log(require('./b').foo);}, 1000);// b.jsmodule.exports.foo = 1; // 同 exports.foo = 1 setTimeout(() => { module.exports.foo = 2;}, 500);// 执行:node a.js// 执行结果:// 1// 2// 2
所以我们可以总结一下:
CommonJS
模块重复引入的模块并不会重复执行,再次获取模块直接获得暴露的module.exports
对象。- 如果你需要处处获取到模块内的最新值的话,也可以每次更新数据的时候每次都要去更新
module.exports
上的值- 如果暴露的
module.exports
的属性是个对象,那就不存在这个问题了。
相关推荐:javascript视频教程
以上がJavaScript モジュラー プログラミング仕様 CommonJS、AMD、CMD、ES6の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。