学习笔记之复盘——“立即执行函数、模块化、命名空间”

496 阅读6分钟

下述内容为个人学习的复习记录,主要是对一些网络资料的摘抄记录。以及一些自己的总结,可能会有一些错误,如有发现,欢迎指正!!!

学习内容主要来至:github.com/stephentian…

立即执行函数表达式(IIFE)

一、基本概念

  • IIFE: Imdiately Invoked Function Express(立即执行函数表达式) e.g.
/* 以下待会会自动执行输出'Hello World' */
;(function (str) {
  console.log(str)
})('Hello World')

二、括号的意义

1. 包裹 function 的括号

  • 该括号主要是为了将function(){}转换成表达式
  • 该括号也可用~+等符号来替代如
  • 该括号不能去掉,因为形如function(){}不是一个表达式

e.g.

~function () {
  console.log(123)
}()
+function () {
  console.log(123)
}()

2. 第二个括号

  • 第二个括号主要是为了能够执行表达式

三、参数的意义

;(function (str) {
  console.log(str)
})('Hello World')

1. 实参

  • 函数中的实参与形参会一一对应如上述例子中的 str 会对应的被赋值为Hello World

2. 形参

  • 普通形参
    • 即第二个括号内传入的参数
  • 特殊形参undefined
    • 防止 undefined 被更改,IE6 等低版本浏览器的 undefined 值可以被更改
    • 压缩代码可以压缩 undefined

模块

一、模块的几种写法

1. 原始写法

  • 模块就是实现特定功能的一组方法
  • 将不同函数(以及记录状态的变量)简单的放在一起,就算是一个模块

e.g.

function m1 () {
  /* code */
}

function m2 () {
  /* code */
}
  • 缺点:
    • 污染了全局变量
    • 模块成员之间看不出直接关系

2. 对象写法

  • 把所有的模块成员都放到一个对象里

e.g.

var module = {
  _count: 0,
  m1: function () {
    /* code */
  },
  m2: function () {
    /* code */
  }
}
  • 缺点:
    • 暴露了模块成员,内部状态可以直接被该部修改

3. 立即执行函数写法

  • 使用立即执行函数,将要暴露的模块成员返回出来,其余的不返回,起到保护内部成员的作用

e.g.

var module = (function () {
  var _count: 0
  var m1 = function () {
    /* code */
  }
  var m2 = function () {
    /* code */
  }
  return {
    m1: m1,
    m2: m2
  }
})()

4. 放大模式

  • 每次添加模块的时候,都进行命名空间的声明,这样可以不用考虑代码的执行顺序
  • 要注意的点
    • 每次往命名空间挂载功能模块时要先判断命名空间是否初始化,如果没有则进行初始化
    • 功能挂在完毕后要将新的命名空间返回,将命名空间进行更新

e.g.

var module = (function (module) {
  if (typeof module === 'undefined') {
    module = {}
  }
  module.m1 = function () {
    console.log('m1 function')
  }
  return module
})(module)

var module = (function (module) {
  if (typeof module === 'undefined') {
    module = {}
  }
  module.m2 = function () {
    console.log('m2 function')
  }
  return module
})(module)

5. 宽放大模式

  • 在实参中使用||进行取巧,使用window.module||(window.module = {}),如果 window 中命名空间 module 被实例化,则直接传入 module,否则执行后续的表达式(window.module={}) 并返回 window.module 的引用

e.g.

;(function (module) {
  if (typeof module === 'undefined') {
    module = {}
  }
  module.m2 = function () {
    console.log('m2 function')
  }
  return module
})(window.module || (window.module = {}))
  • 优点: 可以切割成多个文件进行加载,不必考虑文件加载的先后顺序(因为每次模块添加功能模块时都是检查 window 中是否创建了对应的命名空间,如果创建了会直接使用对应的命名空间)

模块书写注意内容

  • 使用立即执行函数表达式时最好在前面加上;以结束上一个代码段,否则会出现合并的问题;如 a 文件中有一句console.log(1);而 b 文件为一个开头无分号的立即执行函数表达式,那么合并后代码将变成
console.log(1)(function () {
  /* code */
})()

那么这将会被当作如下执行

console.log(1)(function () {
  /* code */
})()

代码将报错

模块的规范化

一、CommonJS 规范(同步加载模块)

  • 使用require方法来同步加载(遇到 require 语句将会将对应的模块文件加载进来,后续代码执行都需要等待模块去不加载完毕后才能执行)其他模块;通过exportsmodule.exports来导出需要暴露的接口

e.g.

/* 模块导入 */
require('Koa')
require('./module.js')

/* 模块导出 */
exports.m1 = function () {}
module.exports = someValue
  • 优点
    • 简单易使用
    • 服务器端模块便于复用
  • 缺点
    • 同步加载不适合浏览器环境,因为其加载速度会受网络影响。会影响浏览器资源的异步加载
    • 不能非阻塞的加载多个模块

二、AMD(异步加载模块)

  • 使用异步加载
  • 所有依赖模块的语句都会放到一个回调函数中,等待模块加载完毕才会执行

e.g.

// 定义模块
define('module', ['dep1', 'dep2'], function (d1, d2) {/* code */});

// 加载模块
require(["module","../app"],function(module,app){/* code */}
  • 优点
    • 适合在浏览器中加载模块(异步加载)
    • 可以并行加载多个模块
  • 缺点
    • 提高开发成本、代码阅读和书写困难、模块定义语句不通畅
    • 不符合通用的模块发思维方式
  • Tips: 实现 AMD 的规范代表是 require.js

三、CMD(异步加载模块)

  • 在 CMD 中一个模块就是一个文件

  • 定义模块使用全局函数 define,接收一个 factory 参数,factory 可以为函数\对象\字符串

  • 当 factory 是函数的时候,他有三个参数

    • require: 函数,接收模块作为唯一参数,用来获取其他模块的接口
    • exports: 对象,用来向外部提供接口
    • module: 对象,存储了与当前模块相关联的一些属性和方法
  • 优点

    • 依赖很近,延迟执行
    • 容易在 Node.js 中运行
  • 缺点

    • 依赖 SPM 打包,模块加载逻辑偏重

四、UMD

  • 是 AMD 和 CommonJS 的糅合
  • 先判断是否支持 Node.js 的 exports 是否存在,存在则使用 CommonJS 规范,否则再判断 AMD 模式是否支持(即 define 是否存在),存在则使用 AMD 规范

五、ES6 规范

  • 在语言标准层面上实现模块功能,实现简单,可以取代 CommonJS 和 AMD 规范,成为浏览器服务器通用解决方案
  • ES6 模块规范设计思想:尽量静态化、编译时确定模块依赖关系,以及输入输出的变量(CommonJS 和 AMD 模块都只能在运行时确定这一内容)

e.g.

// 导入
import "app.js"
import React from "react"

// 导出
export function app(){/* code */}
export const count=0
export default...
  • 优点
    • 容易进行静态分析
    • 面向未来的ES标准
  • 缺点
    • 原生浏览器端还没有实现该标准
    • 全新的命令字,新版的Node.js才支持

六、CommonJS和ES6的区别

  • CommonJS使用require/exports;ES6使用import/export

CommonJS

  • 模块中的基本数据类型被引入时会被复制,另一个模块可以对导出的基本类型值进行重新赋值,不影响其他模块的使用
  • 模块中的复杂数据类型被引入时会被浅拷贝,两个模块引用的对象指向同一个内存空间,当一个模块对其中的值进行更改会影响到另一个模块的执行
  • require加载某一模块时会执行该模块文件的代码,重复引入不会导致重复执行,会直接取用缓存中的值

ES6

  • ES6模块中的值属于动态只读引用
  • import引入的值会产生一个动态只读引用,模块遇到import时会对引入的变量生成一个动态只读的引用,执行时可以根据这个引用去取值,不能修改该值。引入变量的原始值发生变化时,加载的变量也会产生变化;上述的变量对基本数据类型和复杂数据类型都成立

注意的点

  • 不论是用什么规范,require和exports都是必须的,因为编写的import/export都是通过require/exports来执行的