前端模块化开发

前端模块化开发

什么是模块化?
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元

编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块,是将不同功能的函数封装起来,并提供使用接口,他们彼此之间互不影响,想要什么功能,就加载什么模块,可以实现按需加载。

简单来说,模块化就是把系统分成各个独立的部分,每个部分单独实现功能,将系统分割成多个可独立的功能部分。

重点:模块化的好处是什么?(为什么要用模块化)
当我们在一个html文件中引入了多个js文件,此时需要依赖多个模块,就意味着会发送多个请求,从而导致请求过多,同时我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。将不同的功能封装成不同的全局函数时会导致全局变量污染, 容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系,这也间接导致我们所写出的代码难以维护。

模块化作用
1.高内聚低耦合,有利于团队作战,当项目很复杂的时候,将项目划分为子模块分给不同的人开发,最后再组合在一起,这样可以降低模块与模块之间的依赖关系体现低耦合,模块又有特定功能体现高内聚。

2.可重用,方便维护,模块的特点就是有特定功能,当两个项目都需要某种功能的时候,我们定义一个特定的模块来实现该功能,这样只需要在两个项目中都引入这个模块就能够实现该功能,不需要书写重复性的代码;并且当需求变更该功能需要改变的时候,我们直接修改该模块,这样就能够修改所有项目的功能,维护起来很方便。

3、通过 exports 暴露接口。这意味着不需要命名空间了,更不需要全局变量。这是一种彻底的命名冲突解决方案。

4、通过 require 引入依赖。**这可以让依赖内置,开发者只需关心当前模块的依赖,其他事情 Sea.js(CMD)/ Require.js (AMD)都会自动处理好

commonjs依赖关系

CommonJS是node.js遵守的模块化规范, commonjs 规范应用于 nodejs 应用中,在 nodejs 应用中每个文件就是一个模块,拥有自己的作用域,文件中的变量、函数都是私有的,与其他文件相隔离。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。主要用于服务器端。

nodejs下载与安装

链接: ==nodejs下载与配置==
注意事项:
1.nodejs有兴趣的可以跟博客配置,仅使用全部下一步安装完并检查是否安装成功即可(建议下最新版)
2.在vscode上安装code runner扩展
3.做完上述内容就可以在vscode上直接运行js代码,使用方法为在js文件上右击选择run code,或者右上角的运行按钮,或者ctrl+alt+n

conmonjs

CommonJS规范规定:
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
在模块中使用global 定义全局变量,不需要导出,在别的文件中可以访问到。
每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。
通过 require加载模块,读取并执行一个js文件,然后返回该模块的exports对象。
所有代码都运行在模块作用域,不会污染全局作用域。
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
模块加载的顺序,按照其在代码中出现的顺序。

用例子来了解一下commonjs依赖关系

//main1
console.log("main导入a模块");
const a = require('./a');
//-----------------------------
//main2
console.log("main输出a模块----");
console.log(a)
//a1
console.log('a模块--导入b之前')
const b = require('./b'); 
//----------------------------
//a2
console.log("a输出B模块----");
console.log(b)
console.log('a模块--导入b之后')
module.exports = '我是A模块'

//b1
console.log('b模块--导入a之前')
const a = require('./a'); 
//b2
//----------------------------
console.log("B输出a模块----");
console.log(a)
console.log('b模块--导入a之后')
module.exports = '我是B模块'

运行结果
前端模块化开发_第1张图片
模块a、b会被require分为上下两部分,在main.js中遇到require后,加载并执行a模块,a.js中遇到require,加载并执行b模块,b.js中又遇到require(‘./a’)发生了循环引用,因为模块a已经被加载过,直接从缓存中取,由于取出的a模块还没执行完,因此输出a是个空对象。b.js执行完后会返回到刚刚a.js的地方继续执行。三个文件的执行流程为:main1->a1->b1->b2->a2->main2。因此说CommonJS 模块在执行阶段分析模块依赖,采用深度优先遍历(depth-first traversal),执行顺序是父 -> 子 -> 父。由于存在一个缓存机制,所以可以解决循环引用的问题。

ESM

在 ES Module 中用 export 用来导出模块,import 用来导入模块。CommonJS 模块同步加载并执行模块文件,ESM 模块提前加载并执行模块文件,ESM模块在预处理阶段分析模块依赖,在执行阶段执行模块,两个阶段都采用深度优先遍历,执行顺序是子 -> 父。

CommonJS 输出的是一个值的拷贝;ES Modules 生成一个引用,等到真的需要用到时,再到模块里面去取值,模块里面的变量,绑定其所在的模块。以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。

//count文件
let num = 1
function add(){
    return num++
}
module.exports = {num,add}
//ESM
let num = 1
function add(count){
    return count++
}
export {num,add}

const {num,add} = require('./count')
console.log(num)   //1
add()
console.log(num)   //1
//ESM
import {num,add} from './count'
console.log(num)  //1
add(num)
console.log(num)  //2

AMD规范(requirejs)

AMD规范,全称是Asynchronous Module Definition,即异步模块加载机制。完整描述了模块的定义,依赖关系,引用关系以及加载机制。

AMD的核心是预加载,先对依赖的全部文件进行加载,加载完了再进行处理。
适合在浏览器环境中异步加载模块。可以并行加载多个模块。 并可以按需加载。

遵循AMD规范的主要指requirejs,下面来进行requirejs的学习

requirejs下载与配置

官网链接:https://requirejs.org/docs/download.html
在这里插入图片描述
将文件另存为js文件即可

requirejs

定义暴露模块
        定义没有依赖的模块
        define(function(){
            return moudle//模块
        })
        定义有依赖的模块
        define([
            'require',
            'dependency'
        ], function(require, factory) {
            'use strict';
            
        });
    引用注入模块
          require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
        代码,使用moudleA,B,C
        });

RequireJS主要解决了两个问题:

多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长

加载文件
之前的例子中加载模块都是本地js,但是大部分情况下网页需要加载的JS可能来自本地服务器、其他网站或CDN,这样就不能通过这种方式来加载了,我们以加载一个jquery库为例:

require.config({
    paths : {
        "jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery"]   
    }
})
require(["jquery","js/a"],function($){
    $(function(){
        alert("load finished");  
    })
})

使用
然后再页面中使用下面的方式来使用requirejs:

<script data-main="js/main" src="js/require.js"></script>
src引入requirejs,date-main将main模块为主模块加载

解释一下,加载 requirejs 脚本的 script 标签加入了data-main属性,这个属性指定的 js 将在加载完 require.js 后处理,我们把require.config的配置加入到data-main后,就可以使每一个页面都使用这个配置,然后页面中就可以直接使用require来加载所有的短模块名

data-main还有一个重要的功能,当script标签指定data-main属性时,require会默认的将data-main指定的js为根路径,是什么意思呢?如上面的data-main="js/main"设定后,我们在使用require([‘jquery’])后(不配置jquery的paths),require会自动加载js/jquery.js这个文件,而不是jquery.js,相当于默认配置了:

require.config({
    baseUrl : "js"
})

引入第三方模块
通过require加载的模块一般都需要符合AMD规范即使用define来申明模块,但是部分时候需要加载非AMD规范的js,这时候就需要用到另一个功能:shim,shim解释起来也比较难理解,shim直接翻译为"垫",其实也是有这层意思的,目前我主要用在两个地方

非AMD模块输出,将非标准的AMD模块"垫"成可用的模块,例如:在老版本的jquery中,是没有继承AMD规范的,所以不能直接require[“jquery”],这时候就需要shim,比如我要是用underscore类库,但是他并没有实现AMD规范,插件形式的非AMD模块,我们经常会用到jquery插件,而且这些插件基本都不符合AMD规范,比如jquery.form插件,这时候就需要将form插件"垫"到jquery中:

require.config({
    shim: {
        "underscore" : {
            exports : "_";
        },
        "jquery.form" : {
            deps : ["jquery"]//为非模块化内容添加依赖项
        }
    }
})
require(["underscore"], function(_){
    _.each([1,2,3], alert);
})

require.config(["jquery", "jquery.form"], function($){
    $(function(){
        $("#form").ajaxSubmit({...});
    })
})

CMD规范(seajs)

CMD规范:全称是Common Module Definition,即通用模块定义。按需加载。在 CMD 规范中,一个模块就是一个文件,使用define来进行模块,define是一个全局函数。
factory 可以是一个函数,也可以是一个对象或字符串。

factory 为对象、字符串时,表示模块的接口就是该对象、字符串。通过require载入它们时,就是返回对象或者字符串。

factory 为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory 方法在执行时,默认会传入三个参数:require、exports 和 module。这三个参数可根据情况省略,甚至全部省略也可以。

define({'website':'oecom'});
define('这里是OECOM');
define(function(require,exports,module){

})

require参数也是一个方法,接收的参数为模块标识,其实就是需要加载模块的相对路径,作用就是加载其他模块。
define(function(require,exports,module){
    var a = require('./a');
    a.out();//假设模块a有out方法。
})
直接使用require加载属于是同步加载,require提供了async方法来在模块内部进行也不加载模块,并在加载完成以后执行指定的回调函数。
define(function(require,exports,module){
    require.async('./a',function(a){
        a.doSomething()
        })
        require.async(['./c','./b'],function(c,b){
        c.doSomething()
        b.doSomething()
        })
    }
require 是同步往下执行,require.async 则是异步回调执行。require.async 一般用来加载可延迟异步加载的模块。

exports是一个用来想外接提供模块接口的对象
define(function(require,exports){
    var name = 10;
    exports.name = name;
    exports.out = function(){
        console.log("输出内容")
    }
    })
    当然导出模块还可以直接使用return的方式
define(function(require){
    return{
        name:10,
        out:function(){
            console.log("输出内容")
        }
    }
})

AMD和CMD最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。

异步加载:不阻碍后面代码加载执行

AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了哪些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。

提升代码质量

参考博客:https://blog.csdn.net/z591102/article/details/107542071?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%A6%82%E4%BD%95%E8%AE%A9js%E4%BB%A3%E7%A0%81%E6%9B%B4%E5%8A%A0%E8%A7%84%E8%8C%83%E5%8C%96&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-1-107542071.142v47new_blog_pos_by_title,201v3control_1&spm=1018.2226.3001.4187

文件结构

创建项目结构
dist打包生成的文件
libs第三方文件引入(jquery,requirejs等)
src源代码所在的目录
	moudle1
	moudle2
	moudle3
	app.js应用主模块
index.html
package.json
{
name:"包名",
version:"版本号"
}

想了解的package.json的可以参考博客:https://blog.csdn.net/qq_34703156/article/details/121401990?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166253120616782428667436%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=166253120616782428667436&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-121401990-null-null.142v47new_blog_pos_by_title,201v3control_1&utm_term=package.json&spm=1018.2226.3001.4187

webapp

web app是用HTML5编写的移动web运用,依赖于浏览器(微信浏览器、qq浏览器),可以不加修改,直接运行在Android、iOS、pc端。看起来像是一个app,其实就是给web站变异打包了个app壳,可以发布到各个平台上,比如:安卓、iOS、黑莓、win phone等等。

但是web app与原生app相比有几点不足

Web基于DOM,而DOM很慢。浏览器打开网页时,需要解析文档,在内存中生成DOM结构,如果遇到复杂的文档,这个过程是很慢的。可以想象一下,如果网页上有上万个、甚至几十万个形状(不管是图片或CSS),生成DOM需要多久?更不要提与其中某一个形状互动了。

DOM拖慢JavaScript。所有的DOM操作都是同步的,会堵塞浏览器。JavaScript 操作 DOM 时,必须等前一个操作结束,才能执行后一个操作。只要一个操作有卡顿,整个网页就会短暂失去响应。浏览器重绘网页的频率是60FPS(即16毫秒/帧),JavaScript 做不到在16毫秒内完成 DOM 操作,因此产生了跳帧。用户体验上的不流畅、不连贯就源于此。

虽然现在浏览器也支持多线程,比如JS解析在一个线程,渲染在一个线程,但一般是按照先解析再渲染再执行 JS 这样的顺序执行的。

在Android早期的版本中,webview的性能很差
webapp相关操作,可以参考这个博客:https://blog.csdn.net/qq_36232611/article/details/96032839?ops_request_misc=&request_id=&biz_id=102&utm_term=webapp&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-4-96032839.142v47new_blog_pos_by_title,201v3control_1&spm=1018.2226.3001.4187

你可能感兴趣的:(前端,javascript,node.js,es6)