立即执行函数和模块化

参考文档:《深入理解js立即执行函数》

概述

很多时候我们需要创建一个私有的命名空间,该命名空间的变量和方法,不会破坏污染全局的命名空间。此时若是想访问全局对象,将全局对象以参数形式传进去即可,如jQuery代码结构:

(function(window, undefined){ 
    //jquery code
})(window);

匿名函数和表达式

在了解立即执行函数之前先明确一下函数声明、函数表达式及匿名函数的形式:

//函数声明
function test(){  

}

//函数表达式
var test = function(){

}

//匿名函数
function(){

}

上面只有函数表达式是立即执行的。

立即执行函数

如果我们要使函数立即执行,就需要将该函数转换为函数表达式,转换的方式有下面几种:

(function(test){
    console.log(test);
})(123);
(function(test){
    console.log(test);
}(123));
!function(test){
    console.log(test);
}(123);
+function(test){
    console.log(test);
}(123);
-function(test){
    console.log(test);
}(123);
var fn = functin(test){
    console.log(test);
}(123);

模块化

Vue的框架立即执行函数如下,通过上面分析我们已经知道会立即执行 2,3,4 行,我们分析一下为什么下面执行到了 helloword.

(function(global, factory){
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global = global || self, global.Vue = factory());
}(this, function(){
    document.writeln("hello world");
}))

在 ES6 之前没有模块化的概念,现在 JavaScript 支持了 module, 分为导出 export 和导入 import 两个模块。

每一个文件就是一个模块,在文件中定义的变量,函数,对象在外部是无法获取的。如果你希望外部可以读取模块当中的内容,就必须使用 export 来对其进行暴露(输出)。

export 是用来定义模块的,可以导出对象、函数、类、字符串等等。

const a = 1;
let b = 2;
function show(){
    console.log(10);
}
export {  //暴露模块
    a,
    b as c,
    show
}
show();  // 10
console.log(a);  // 1
console.log(b);  // 2
console.log(c);  // c is not defined  因为在这个js里他还是b,只不过导出到另外一个文件里才叫c
<script type="module">
    import {a,c,show} from './1.js';
    show();  // 10
    console.log(a); // 1
    console.log(c); // 2
    console.log(b); // b is not defined  已经将导入的b更名为c,所以这里叫c
</script>

上面的例子中可以将export导出的内容通过as进行更名,import导入的也可以通过as改名。

<script type="module">
    import * as goto from './1.js';  // * 代表1.js中导出的全部的内容,但是不能直接输出*,必须改名
    console.log(goto);  // 整个json对象
    console.log(goto.a);    // 1
    goto.show();    // 10
</script>

导出的方式还有另外一种:export default {}export {} 的区别是前者导出的东西需要在导入的时候加 {},而后者则不需要。

const a = 1;
const b = 2;
const c = 3;
export {a,b}
export default c;
<script type="module">
    import c,{a,b} from './1.js';  // 同时导入export和export default的时候,必须把默认的放在前面
    console.log(a,b,c);  // 1 2 3
</script>

还有一种动态引入方式,例如:

<script type="module">
    import('./1.js').then(res =>{
        console.log(res.a); // 1
    });
</script>

CommonJs规范

上面的 exports 命令实际上是 CommonJS 模块规范中的 module.exports 的缩写,在每个模块(文件)头部都有如下命令:

var exports = module.exports;

module.exports 属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取 module.exports 变量。

Node 应用由模块组成,采用 CommonJS 模块规范。Node内部提供一个Module构建函数。所有模块都是Module的实例。

function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  // ...

每个模块内部,都有一个module对象,代表当前模块。它有以下属性。

  • module.id 模块的识别符,通常是带有绝对路径的模块文件名。
  • module.filename 模块的文件名,带有绝对路径。
  • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
  • module.parent 返回一个对象,表示调用该模块的模块。
  • module.children 返回一个数组,表示该模块要用到的其他模块。
  • module.exports 表示模块对外输出的值。

AMD规范

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。

AMD规范使用define方法定义模块,下面就是一个例子:

define(['package/lib'], function(lib){
  function foo(){
    lib.log('hello world!');
  }

  return {
    foo: foo
  };
});

AMD规范允许输出的模块兼容CommonJS规范,这时define方法需要写成下面这样:

define(function (require, exports, module){
  var someModule = require("someModule");
  var anotherModule = require("anotherModule");

  someModule.doTehAwesome();
  anotherModule.doMoarAwesome();

  exports.asplode = function (){
    someModule.doTehAwesome();
    anotherModule.doMoarAwesome();
  };
});

所以Vue框架的入口代码实际上是判断了当前的模块化规范,最后根据对应模块规范将 factory 方法暴漏出去。

(function(global, factory){
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global = global || self, global.Vue = factory());
}(this, function(){
    document.writeln("hello world");
}))