前端模块化开发

一、模块化概述

1.概念

  • 随着前端应用日趋复杂,项目代码也大量膨胀,模块化就是一种最主流的代码组织方式
  • 一个模块就是一个实现特定功能的文件,它通过把我们的复杂代码按照功能的不同,划分为不同的模块单独维护的这种方式,去提高我们的开发效率,降低维护成本。要用什么功能就加载什么模块
  • 模块化开发是当下最重要的前端开发范式之一,其只是思想,不包含具体实现

2.模块化开发的好处

  • 避免变量污染、命名冲突等
  • 提高代码复用率
  • 提高维护性
  • 依赖关系的管理

3.模块化演变过程

(1)文件划分方式

  • 将每个功能和相关的一些状态数据单独存放在不同的文件当中,此时一个文件就是一个独立的模块。然后将这个模块引入页面当中,直接调用模块中的成员(变量/函数),一个script标签就对应一个模块,所有模块都在全局范围内工作。
  • 缺点:
    • 污染全局作用域
    • 命名冲突问题
    • 无法管理模块依赖关系

(2)对象封装

  • 将所有模块成员封装在一个对象中,当要使用的时候就调用这个对象的属性
  • 缺点:
    • 没有私有空间,模块成员仍然可以在外部被访问或修改
    • 无法管理模块依赖关系

(3)立即执行函数

  • 采用该方式为模块提供私有空间,将模块中每个成员都放在一个函数提供对的私有作用域中,可确保私有成员的安全。私有成员只能在模块成员内通过闭包的形式访问

二、模块化规范

1、CommonJS规范

(1)概述
  • node应用由模块组成,采用common.js模块规范。每一个文件就是一个模块,拥有自己独立的作用域、变量、方法等,读其他的模块不可见
  • CommonJS规范规定,module变量代表当前模块,其是一个对象,其属性exports,即module.exports,是对外的接口
  • require方法用于加载模块,即加载模块的module.exports属性
(2)CommonJS模块的特点
  • 所有代码都运行在模块作用域,不会污染全局作用域
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了;以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。
(3)moudle对象
a. module对象的属性

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

b. module.exports属性

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

c. exports变量
  • 为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。
var exports = module.exports
  • 在对外输出模块接口时,可以向exports对象添加方法,如
exports.area = function (r) {
  return Math.PI * r * r;
};

exports.circumference = function (r) {
  return 2 * Math.PI * r;
};
  • 不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系。下面这样的写法是无效的,因为exports不再指向module.exports了。
exports = function(x) {console.log(x)};
  • 如果一个模块的对外接口,就是一个单一的值,不能使用exports输出,只能使用module.exports输出。
module.exports = function (x){ console.log(x);};

总结:尽量使用module.exports

(4)require命令
a.基本用法

require命令用于加载模块文件,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。如:

  • example.js
exports.name = 'tom';exports.age = 50;
  • 同目录下的demo.js文件
var example = require('./example.js');
console.log(example.name); // tom
console.log(example.age); // 50

或者写函数

  • example.js
 function fn(){console.log(1)};
var name =  'tom'
module.exports = {fn:fn,name:name} 
  • 同目录下的demo.js文件
var example = require('./example.js');
example.fn(); // 1
console.log(example.name); // tom
b.加载规则
  • require命令用于加载文件,后缀名默认为.js。
var foo = require('foo');
//  等同于
var foo = require('foo.js');

根据参数的不同格式,require命令去不同路径寻找模块文件。
在这里插入图片描述
在这里插入图片描述

c.目录的加载规则
  • 通常,我们会把相关的文件会放在一个目录里面,便于组织。这时,最好为该目录设置一个入口文件,让require方法可以通过这个入口文件,加载整个目录。
  • 在目录中放置一个package.json文件,并且将入口文件写入main字段,如
// package.json
{ "name" : "some-library",
  "main" : "./lib/some-library.js" }

require发现参数字符串指向一个目录以后,会自动查看该目录的package.json文件,然后加载main字段指定的入口文件。如果package.json文件没有main字段,或者根本就没有package.json文件,则会加载该目录下的index.js文件或index.node文件。

d.模块的缓存
  • 第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。
  • 如果想要多次执行某个模块,可以让该模块输出一个函数,然后每次require这个模块的时候,重新执行一下输出的函数。
  • 所有缓存的模块保存在require.cache之中,删除模块的缓存:
// 删除指定模块的缓存
delete require.cache[moduleName];

// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
  delete require.cache[key];
})

缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require命令还是会重新加载该模块。

e.模块的循环加载
  • 如果发生模块的循环加载,即A加载B,B又加载A,则B将加载A的不完整版本。
f.require.main
  • require方法有一个main属性,可以用来判断模块是直接执行,还是被调用执行。直接执行的时候(node module.js),require.main属性指向模块本身。
require.main === module
// true
  • 调用执行的时候(通过require加载该脚本执行),上面的表达式返回false。
(5)node.js的配置
  • CommonJs服务器端模块的规范,Node.js采用了这个规范。
  • CommonJS 加载模块是同步的,即只有加载完成才能执行后面的操作。 像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。

参考
node.js简介

循环依赖

2. AMD规范

(1)概述
  • AMD是Asynchronous Module Definition的缩写,意思就是"异步模块定义"
  • 异步模块模式AMD是当请求发出后,继续其他业务逻辑,直到模块加载完成执行后续逻辑,实现模块开发中的对模块加载完成后的引用
  • 。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不需要异步加载,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用异步模式,因此浏览器端一般采用AMD规范。
  • AMD对应的就是很有名的RequireJS
  • RequireJS是一个工具库,主要用于客户端的模块管理。它可以让客户端的代码分成一个个模块,实现异步或动态加载,从而提高代码的性能和可维护性。它的模块管理遵守AMD规范。
(2)require.js的使用

a.下载require.js
b.引用:

<script src="js/require.js" data-main="js/main"></script>
  • data-main属性的作用是,指定网页程序的主模块,它会在require.js加载完成后首个加载,总的文件入口

c.语法

 define([id], [dependencies], factory)
  • id:可选,字符串类型,定义模块标识,如果没有提供参数,默认为文件名
  • dependencies:可选,字符串数组

AMD 推崇依赖前置,即当前模块依赖的其他模块,模块依赖
必须在真正执 行具体的factory方法前解决

  • factory:必需,工厂方法,初始化模块需要执行的函数或对象。如果为函数,它只被执行一次。如果是对象,此对象会作为模块的输出值。
(3)模块的写法
 require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
                // 代码
 });
  • 一般情况下主模块都会依赖于其他模块,此时则需要使用AMD规范定义的的require()函数
  • require()函数接受两个参数,第一个参数是数组,表示所依赖的模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。
  • 加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。
  • require()异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
(3)定义模块define()
  • 定义模块是同一个define()函数来定义的
//定义一个对象模块
//该对象作为输出值传递给其它引入它的模块
define({
 a:'this is a ',
 add:function(x,y){
        return x + y 
    }
})
//定义模块作为函数
//此函数需要有返回值,作为它的输出值
define(function(){
    //做一些什么事情...
    retunn {
    a:'this is a ',
    add:function(x,y){
        return x + y 
    }
}    
})
//引入依赖模块moduleB
//moduleB的输出值作为函数的参数
//moduleA.js
define(['moduleB'],function(moduleB){
    //调用moduleB中的log函数
    moduleB.log()
    
    retunn {
    a:'this is a ',
    add:function(x,y){
        return x + y 
    }
}    
})
//moduleB.js
define(function(){
    return {
        log:function(){
             console.log('this is moduleB');
        }
    }
})

  • 定义好模块之后就可以在其他的模块引入了

3.CMD规范

CMD,即Common Module Definition
参考

参考文章

三、JS代码规范

1.变量和函数的命名规则

参考1

参考2

变量:

用有意义且常用的单词命名变量,且用驼峰法命名

函数:
  • 参数越少越好
  • 函数名能够“顾名思义”
  • 函数只做一件事情
  • 不要过多嵌套函数
  • 删除重复代码
  • 给对象设置默认属性

参考博客

2.ESlint:代码质量规范

ESlint是代码质量规范,如定义了一个变量,但没有使用过它,ESlint会告诉你

配置ESlint

参考

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值