nodeJS学习笔记——JavaScript模块开发(1)

 

什么是 JavaScript?
     一种弱类型编程语言,运行在浏览器中 ;JavaScript一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。

  

 浏览器和 JavaScript 是什么关系?

     浏览器和 JavaScript 是一种寄生、宿主关系
     浏览器中的 JavaScript 必须和页面绑定到一起才可以运行,无法独立

   

浏览器中的 JavaScript 可以做什么?

     操作DOM:表单验证、动画
     数据交互:ajax
     通常所值的 JavaScript :BOM  DOM (W3C) ECMAScript

   

从功能角度 浏览器中的 JavaScript 和其它语言有什么差别?

     浏览器中的 JavaScript 可以操作 DOM 对象 浏览器中集成了一个 JavaScript 解释器,编译器:
     BOM 和 DOM 是浏览器环境提供的接口对象,例如操作一个 DOM对象
     js 解析引擎是不管的,还是这个编程环境
     读写文件,接受HTTP请求,提供数据服务

   

JavaScript 只可以运行在浏览器中吗?

     JavaScript 不仅仅能运行在浏览器中

     JavaScript 还可以运行在服务器环境中,例如:node

 

什么是模块化

  + 模块化就是一种解决复杂事物的一种方式

 

使用模块化开发的方式带来的好处

  + 开发效率高
  + 便于维护

解决:

- 恼人的命名冲突
- 繁琐的文件依赖


模块化开发演变


1、全局函数
    污染了全局变量

 

    模块成员之间看不出直接关系

 

 

/**
 * 全局函数
 * cal + substract + multiply + divide 四个全局函数组成了一个计算器模块
 * 污染了全局变量
 * 从代码解析角度来看模块成员之间看不出直接关系
 */

function add(x, y) {
  return parseFloat(x) + parseFloat(y)
}

function substract(x, y) {
  return parseFloat(x) - parseFloat(y)
}

function multiply(x, y) {
  return parseFloat(x) * parseFloat(y)
}

function divide(x, y) {
  return parseFloat(x) / parseFloat(y)
}

 

 

 

 

 

 

2、命名空间

 

/**
 * 命名空间
 * 采用命名空间的方式:理论意义上减少了全局变量污染
 * 缺点:暴露了所有的模块成员,内部状态可以被外部改写,不安全
 *       命名空间前缀会越来越长,对于开发人员使用不友好
 */

var calculator = {}

// calculator._foo = 'bar'

calculator.add = function (x, y) {
  return parseFloat(x) + parseFloat(y)
}

calculator.substract = function (x, y) {
  return parseFloat(x) - parseFloat(y)
}

calculator.multiply = function (x, y) {
  return parseFloat(x) * parseFloat(y)
}

calculator.divide = function (x, y) {
  return parseFloat(x) / parseFloat(y)
}

 

 

 

 

 

3、私有空间

 

/**
 * 划分私有空间
 * 公有私有分离
 */

;
(function(cal) {
  // var calculator = {}

  // 私有模块成员,外部拿不到
  // 加下划线的目的是给开发人员看的
  var _foo = 'bar'

  // calculator._foo = 'bar'

  cal.add = function(x, y) {
    return parseFloat(x) + parseFloat(y)
  }

  cal.substract = function(x, y) {
    return parseFloat(x) - parseFloat(y)
  }

  cal.multiply = function(x, y) {
    return parseFloat(x) * parseFloat(y)
  }

  cal.divide = function(x, y) {
    return parseFloat(x) / parseFloat(y)
  }

  // 当这个匿名自执行函数执行完毕之后,就会在全局挂载一个变量:calculator
  window.calculator = cal
  
})(window.calculator || {})

 

 

 

 

 

4、模块的维护和扩展

 

/**
 * 模块的维护和扩展
 * 加载的时候不需要考虑顺序的问题了
 * 对于模块的维护和扩展,最好本着开闭原则,对于扩展开放,对于修改封闭
 * 最好不要去修改原始的模块文件,可能会造成原有模块功能发生错误
 * 这样做的话,如果 当前扩展的模块出了问题,可以移除当前模块,而不影响原有的模块功能
 */

;
(function(cal) {

  // else 中的 声明语句造成了变量提升,所以找不到全局中的 calculator
  // var calculator
  // if (typeof calculator === 'object') {
  //   alert('123')
  //   calculator.mod = function(x, y) {
  //     return x % y
  //   }
  // } else {
  //   var calculator = {}
  //   calculator.mod = function(x, y) {
  //     return x % y
  //   }
  //   window.calculator = calculator
  // }


  cal.mod = function (x, y) {
    return x % y
  }

  // 如果全局没有 calculator,所以再次暴露一下
  window.calculator = cal

  // 通过传递参数的方式
  // 先看一下全局中有没有,如果有就使用全局中的,如果没有就给一个默认的
})(window.calculator || {})

 

 

 

 

 

 

5、模块的第三方依赖

 

/**
 * 模块的第三方依赖
 * 不要在模块中直接使用第三方提供的接口对象或者函数(看不出直接的依赖关系)
 * 所以一定最好把依赖项以参数的形式传递进来,这样的话依赖关系就更加明确
 */

;
(function(cal, $) {

  $(function () {
    
  })


  cal.mod = function (x, y) {
    return x % y
  }

  window.calculator = cal
})(window.calculator || {}, window.jQuery)

 

以后如果不适用第三方规范的情况下,
如果写模块可以采用下面这种方式

格式:

```js
;
(function (形参模块名, 依赖项, 依赖项) {
  / 通过 形参模块名 修改模块

  window.模块名 = 形参模块名
})(window.模块名 || {}, 依赖项, 依赖项)



下面是一个关于模块化的面试题,一起观察和分析这段代码的作用:


```js

/**
 * 模块的第三方依赖
 * 不要在模块中直接使用第三方提供的接口对象或者函数(看不出直接的依赖关系)
 * 所以一定最好把依赖项以参数的形式传递进来,这样的话依赖关系就更加明确
 */

;
(function(cal, $) {

  $(function () {
    
  })


  cal.mod = function (x, y) {
    return x % y
  }

  window.calculator = cal
})(window.calculator || {}, window.jQuery)



 模块化规范


> 了解常见的 JavaScript 模块化规范

 

 

 

CommonJS
    CommonJS 是一套适用于 JavaScript 服务器环境的一个模块定义规范
    一般是在 Node 中使用 CommonJS 规范构建模块系统
    在 Node 中,CommonJS 只是规范,Node 天生就已经根据 CommonJS 实现了自己的一套模块系统API
 

 

 

 

 AMD
  AMD 是 RequireJS 在推广的过程中形成的一套前端模块化规范
 

CMD
   [Common Module Definition](https://github.com/seajs/seajs/issues/242)
   SeaJS 在推广的过程中形成了一套前端模块化规范


ECMAScript 6 Module

   015 年 ECMAScript 官方发布了最新的 JavaScript 标准规范,其中就包含了 模块系统规范
   import 用来加载模块
   export 用来暴露接口
 

UMD

   Universal Module Definition
   使用 UMD 规范定义的模块,其实就是兼容了 CommonJS + AMD + CMD 这些常见的模块定义规范


有这么多模块定义规范的原因就在于 JavaScript 的历史原因,天生就没有模块化规范概念。
 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

SeaJS


> A Module Loader for the Web, Enjoy the fun of programming.

 

 

- 关于 SeaJS

  + SeaJS 是一个适用于浏览器环境的模块加载器
  + SeaJS 的作者是阿里巴巴支付宝前端架构师,花名:玉伯
  + 官网:http://seajs.org/
  + github:https://github.com/seajs/seajs/

 

 

 

- 为什么学习和使用 SeaJS ?

  + **简单友好的模块定义规范**:SeaJS 遵循 CMD 规范,可以像 Node 一样书写模块代码
  + **自然直观的代码组织方式**:依赖的自动加载、配置简洁清晰,可以让我们更多的享受编码的乐趣
  + SeaJS兼容性非常好,几乎可以运行在任何浏览器引擎上
  + 注1:SeaJS 只是实现模块化开发的一种方式或者说一种工具而已,重在模块化思想的理解
  + 注2:因为 SeaJS 采用的 CMD 模块规范和 Node 中的 CommonJS 模块规范非常一致,所以有利于我们学习 Node 中的模块化编程

- 谁在用?
  + 淘宝网、支付宝、京东、爱奇艺。。。

 

 


RequireJS

 

RequireJS 是一个类似于 SeaJS 的一个模块加载器,API也非常的相似。

RequireJS is a JavaScript file and module loader. 

RequireJS 和 SeaJS 在使用方式上差别不大

## RequireJS 和 SeaJS 的差异

相同点:都是模块加载器

不同点:
规范定义角度:
SeaJS 采用的是 CMD 模块定义规范,RequireJS 采用的是 AMD 模块定义规范

代码加载执行角度:
从文件模块的加载角度来说,两者都是预加载:在代码开始执行的时候,就通过异步并行的方式把所有的 js 脚本文件下载到了本地

SeaJS 是在预加载、懒执行
RequireJS 是预加载、预执行

RequireJS 是预先加载所有的文件,执行的时候,是从最后的一个依赖的模块开始执行,
往前倒序执行,这种代码执行顺序不适合程序员的顺序编程思维。




## 使用 gulp 对 SeaJS 模块进行打包

 

 

 

### 快速上手


0. 下载 sea.js 库
1. 在页面中引入 sea.js
2. 使用 `define` 函数定义模块
3. 使用 `require` 函数加载模块
4. 使用 `module.exports` 对外暴露接口对象
5. 使用 `seajs.use` 函数启动模块系统


### API 详解


#### `seajs.use`


加载模块,启动模块系统。


- 加载一个模块 `seajs.use('id')`
- 加载一个模块,在加载完成时,执行回调 `seajs.use('id', callback)`
- 加载多个模块,加载完成时,执行回调 `seajs.use(['id1','id2',...],callback)`


- 注意:
  + 在调用 seajs.use 之前,需要先引入 sea.js 文件
  + seajs.use 与 `DOM ready` 事件没有任何关系。如果某些操作要确保在 `DOM ready` 后执行,需要使用 jquery 等类库来保证
  + seajs.use 理论上只用于加载启动,不应该出现在 `define` 中的模块代码里


#### `define(factory)`


`define` 是一个全局函数,用来定义模块。


`define` 接受 `factory` 参数,`factory` 可以是一个函数,也可以是一个对象或字符串。


`factory` 为对象、字符串时,表示模块的接口就是该对象、字符串。


- factory 是一个对象时


- factory 是一个字符串时


- factory 是一个函数时


#### require


require 用来获取指定模块的接口对象 `module.exports`。


使用注意:


- 正确拼写
  + 模块 factory 构造方法的第一个参数 `必须` 命名为 require
- 不要修改
  + 不要重命名 require 函数,或在任何作用域中给 require 重新赋值
- 使用字符串直接量
  + require 的参数值 必须 是字符串直接量


Tips: 把 `require` 看做是语法关键字就好啦
 


模块标识

 


模块标识是一个字符串,用来标识模块。


- 模块标识可以不包含文件后缀名,比如 `.js`
- 模块标识可以是 **相对** 或 **顶级** 标识


- 相对标识


相对标识以 `.` 开头,永远相对于当前模块所处的路径来解析。


- 顶级标识


顶级标识不以 `.` 或 `/` 开始,会相对模块系统的基础路径(base路径,默认是 sea.js 文件所属的路径)。
可以手动配置 base 路径。


```js
seajs.config({
  base: './js'
})
```


- 普通路径


除了相对和顶级标识之外的标识都是普通路径。
普通路径的解析规则,会相对当前页面解析。


```js
// 假设当前页面是 http://example.com/path/to/page/index.html


// 绝对路径是普通路径:
require.resolve('http://cdn.com/js/a');
  // => http://cdn.com/js/a.js


// 根路径是普通路径:
require.resolve('/js/b');
  // => http://example.com/js/b.js


// use 中的相对路径始终是普通路径:
seajs.use('./c');
  // => 加载的是 http://example.com/path/to/page/c.js


seajs.use('../d');
  // => 加载的是 http://example.com/path/to/d.js
```


**Tips**:


- 顶级标识始终相对 `base` 基础路径解析。
  + 如果不设置,base 路径默认就是 sea.js 库文件所属的路径
  + 可以通过 `seajs.config({ base: '基础路径' })` 来配置基础路径
- 绝对路径和根路径始终相对当前页面解析。
- 相对标识永远相对于当前文件
- `seajs.use` 中的相对路径始终相对当前页面来解析。


#### module


module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。


- `module.id`
  + 模块的唯一标识,可以通过 `define` 方法的第一个参数来指定,默认为该模块文件的绝对路径
- `module.uri`
  + 模块的绝对路径
- `module.dependencies`
  + dependencies 是一个数组,表示当前模块的依赖
- `module.exports`
  + 当前模块对外提供的接口


#### exports


exports 仅仅是 module.exports 的一个引用。
也就是说修改了 exports 就相当于修改了 module.exports。


但是一旦在 factory 内部给 exports 重新赋值,并不会改变 module.exports 的值。
因此给 exports 赋值是无效的。


### exports 和 module.exports 的区别


- 每个模块内部对外到处的接口对象始终都是 `module.exports`
- 可以通过修改 `module.exports` 或给它赋值改变模块接口对象
- `exports` 是 `module.exports` 的一个引用,就好比在每一个模块定义最开始的地方写了这么一句代码:`var exports = module.exports`


关于这俩哥们儿的区别请分析一下代码:


```js
var module = {
  exports: {}
}


function changeExports (exports, module) {
  exports.foo = 'bar'
  exports = function () {}
  return module.exports
}


changeExports(module.exports, module)
```


那为啥要有 `exports` ?
为了开发体验,API更友好,使用 exports 的时候,可以少写一个点儿。


如果你实在分不清楚 `exports` 和 `module.exports` 之间的区别,就只记得 `module.exports` 就可以了。


### 如何将一个普通的模块文件改造为兼容 CMD 规范的模块


```js
if (typeof define === "function" && define.cmd) {
  // 有 Sea.js 等 CMD 模块加载器存在
  define(function (require, exports, module) {
    // 使用 module.exports 向外暴露接口对象
  })
}
```


### 高级配置 `seajs.config(options)`


可以对 Sea.js 进行配置,让模块编写、开发调试更方便。


```js
seajs.config({


  // 别名配置
  alias: {
    'es5-safe': 'gallery/es5-safe/0.9.3/es5-safe',
    'json': 'gallery/json/1.0.2/json',
    'jquery': 'jquery/jquery/1.10.1/jquery'
  },


  // 路径配置
  paths: {
    'gallery': 'https://a.alipayobjects.com/gallery'
  },


  // Sea.js 的基础路径
  base: 'http://example.com/path/to/base/',
});

 

 

 

 

 

 

  总结


JavaScript 长久以来一直被限制在浏览器的沙箱中运行,它的能力取决于浏览器中间层提供的支持多少。
Node 将高性能的 V8 带到了服务器端,使 JavaScript 也可以开发出实时高性能的服务器。
在 Node 中,不再与CSS样式表,DOM树打交道,可以随意的访问本地文件,搭建 WebSocket 服务器,连接数据库等系统级底层操作。
Node 不处理 UI,只关心数据,无论是本地数据还是网络数据。
前后端编程统一,大大降低了前后端编程切换的代码。

 

 

 

 

 

 

 

你可能感兴趣的:(nodeJS)