JS模块化
模块化是一个语言膨胀的必经之路,它能够帮助开发者拆分和组织代码。
module模块
在模块化之前
-
函数方式:一个函数就相当于一个模块,通过调用函数来引用模块
这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
-
对象方式:可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({ _count : 0, m1 : function (){ //... }, m2 : function (){ //... } });
样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
-
立即执行函数写法:可以达到不暴露私有成员的目的。
var module1 = (function(){ var _count = 0; var m1 = function(){ //... }; var m2 = function(){ //... }; return { m1 : m1, m2 : m2 }; })();
module1就是Javascript模块的基本写法。下面,再对这种写法进行加工。
-
放大模式
var module1 = (function (mod){ mod.m3 = function () { //... }; return mod; })(module1);
以module1为参数,给他添加新的属性,方法,返回一个新的模块给module1
-
宽放大模式(Loose augmentation)
在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"。
var module1 = ( function (mod){ //... return mod; })(window.module1 || {});
-
输入全局变量
var module1 = (function ($, YAHOO) { //... })(jQuery, YAHOO);
上面的module1模块需要使用jQuery库和YUI库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。
模块化规范
1. CommonJS
CommonJS主要用在Node开发上,每个文件就是一个模块,没个文件都有自己的一个作用域。通过module.exports
暴露public成员。例如:
// 文件名:1.js
let a = 0;
function add(x, y) {
return x + y
}
module.exports.a = a;
module.exports.add = add
//文件名:2.js
let x = require("./1");
console.log(x);
//{ a: 0, add: [Function: add] }
此外,CommonJS通过require()引入模块依赖,require函数可以引入Node的内置模块、自定义模块和npm等第三方模块。
从上面代码我们可以看出,require函数同步加载了1.js
,并且返回了module.exports
输出字面量的拷贝值。可能有人会问module.exports.x = x;
不是赋值吗,怎么肥事呢?我们说,Module模式是模块化规范的基石,CommonJS也是对Module模式的一种封装。我们完全可以用Module模式来实现上面的代码效果:
let xModule = (function (){
let x = 1;
function add() {
x += 1;
return x;
}
return { x, add };
})();
let xm = xModule;
console.log(xm.x); // 1
console.log(xm.add()); // 2
console.log(xm.x); // 1
通过Module模式模拟的CommonJS原理,我们就可以很好的解释CommonJS的特性了。因为CommonJS需要通过赋值的方式来获取匿名函数自调用的返回值,所以require函数在加载模块是同步的。然而CommonJS模块的加载机制局限了CommonJS在客户端上的使用,因为通过HTTP同步加载CommonJS模块是非常耗时的。
2. AMD和CMD
AMD 推崇依赖前置,CMD 推崇依赖就近。
AMD
基本语法
定义暴露模块
//定义没有依赖的模块
define(function(){
return module
})
//定义有依赖的模块
define(['module1','module2'],function(m1,m2){
return module
})
定义引入模块
CMD(不重要)
define(function(require, exports, module) {
// 同步加载模块
var a = require('./a');
a.doSomething();
// 异步加载一个模块,在加载完成时,执行回调
require.async(['./b'], function(b) {
b.doSomething();
});
// 对外暴露成员
exports.doSomething = function() {};
});
// 使用模块
seajs.use('path');
CMD集成了CommonJS和AMD的的特点,支持同步和异步加载模块。CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。因此,在CMD中require
函数同步加载模块时没有HTTP请求过程。
ES6 module(很重要)
有两大特点:
- 模块化规范输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- 模块化规范是运行时加载,ES6 模块是编译时输出接口。
模块化规范输出的是一个对象,该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,ES6 module 是一个多对象输出,多对象加载的模型。从原理上来说,模块化规范是匿名函数自调用的封装,而ES6 module则是用匿名函数自调用去调用输出的成员。
引入 import
暴露:export
单例模式
定义
在应用单例模式时,生成单例的类必须保证只有一个实例的存在,很多时候整个系统只需要拥有一个全局对象,才有利于协调系统整体的行为。比如在整个系统的配置文件中,配置数据有一个单例对象进行统一读取和修改,其他对象需要配置数据的时候也统一通过该单例对象来获取配置数据,这样就可以简化复杂环境下的配置管理。
总结:单例模式就是只允许一次实例化的对象。
功能
-
管理命名空间,井井有条的管理对象上的属性和方法。
JS中有事件覆盖,例如在页面中定义的绑定方法on,如果日后其他人要为页面添加需求,增加代码定义而定义了一个on方法或者重写了on方法,会导致初始定义的on方法被覆盖,这个时候就可以用单例模式来管理命名空间。命名空间就是人们日常所说得namespace,它开辟出一个独立的空间,把相关的变量、属性或者方法放在里面,然年后通过空间名来访问空间内的东西,单例模式管理命名空间其实很常见,例如jQuery就是,引用jQuery的方法或者变量的时候,都需要使用一个‘ ’ 符 号 , 这 个 ‘ ’符号,这个‘ ’符号,这个‘’就是jQuery的命名空间名。
-
模块分明–管理代码库中的各个模块
var A = { Util: { util_method1: function() {}, util_method2: function() {}, }, Tool: { tool_method1: function() {}, tool_method2: function() {} }, Ajax: { get: function() {}, post: function() {}, //..... }, Others: { //.... } }
当要使用公共模块、工具的时候,就可以像下面这样调用:
A.Util.util_method1();
A.Tool.tool_method1();
仔细看一下可以发现,用单例模式规范代码模块其实是管理命名空间的升级版,规范代码模块把命名空间里的东西又进一步进行了模块划分,让代码层次更加清晰。 -
管理静态变量
JS中没有类似static的这类的关键字,所以理论上定义的变量都是可以更改的,所以在JS中实现静态变量很重要。JS是一个非常灵活的语言,所以有这么一个办法,用单例模式定义变量,并且不提供赋值变量的方式,只提供获取变量的方式,这样的话,就可以做到限制变量的修改,并且可以供外界访问,为实现创建后就能使用这一需求,需要让创建的函数执行异议,此时我们创建的对象内保存静态变量通过取值器访问,最后将这个对象作为一个单例放在全局命名空间作为静态变量单例对象供他人使用。简单代码如下
var conf = (function () { //私有变量 var conf = { MAX_NUM: 100, MIN_NUM: 1, coUNT: 1000 } //返回取值器对象 return { //返回取值器方法 get: function (name) { return conf[name] ? conf[name] : null; } } })() //不将变量暴露给外部,直接暴露变量的取值方法
JS实现单例模式大方法
- 使用闭包方式来模拟私有数据
var single = (function(){
var unique;
function getInstance(){
if( unique === undefined ){
unique = new Construct();
}
return unique;//返回对象的引用
}
function Construct(){
// ... 生成单例的构造函数的代码
}
return {
getInstance : getInstance
}
})();
//通过 single.getInstance() 来获取到单例,并且每次调用均获取到同一个单例。这就是 单例模式 所实现的效果
- 对象字面量
var singleton = {
attr : 1,
method : function(){ return this.attr; }
}
var t1 = singleton ;
var t2 = singleton ;
//显然:t1===t2
- 构造函数
function Construct(){
// 确保只有单例
if( Construct.unique !== undefined ){
return Construct.unique;
}
// 其他代码
this.name = "NYF";
this.age="24";
Construct.unique = this;
}
var t1 = new Construct() ;
var t2 = new Construct() ;
//有 t1===t2
总结
总的来说,单例模式相对而言是各大模式中较为简单的,但是单例模式也是较为常用并且很有用的模式。在JS中尤为突出(每个对象字面量都可以看做是一个单例么~)。
记住,是否严格的只需要一个实例对象的类(虽然JS没有类的概念),那么就要考虑使用单例模式。
使用数据缓存来存储该单例,用作判断单例是否已经生成,是单例模式主要的实现思路。
工厂模式
工厂模式是函数
把相同属性和功能进行封装
,以此来实现批量生产(后来想要实现这个功能,我们只需要执行函数就可以)
发布订阅模式
jQuery中的发布订阅
-
创建一个事件池$.Callbacks()
let $pond1=$.Callbacks(); $(".submit").click(function(){ //通知事件池中的方法执行 $pond1.fire(); //fire 通知事件池中的方法执行,并且可以传参(给每一个方法传参) }) //把需要做的事件放到事件池中,移除是remove(),jquery中没有做去重处理,fn1会运行三次 $pond1.add(fn1); $pond1.add(fn1); $pond1.add(fn1);
基于ES6自己封装发布订阅模式
-
构造函数模式
let _Subscribe = (function() { // 发布订阅类 // console.log("定义发布订阅类"); class Subscribe { constructor() { // 创建一个事件池,用来存储后续需要执行的方法 this.pond = []; } // 向事件池中追加方法 add(func) { if (typeof func === 'function') { let flag = this.pond.some(item => { return item === func }); if (!flag) { this.pond.push(func); return } this.catch(new Error('You haved added the same function!')) } else { this.catch(new Error(func + ' is not a function!')) } } // 移除事件池中移除方法 remove(func) { this.pond.splice(this.pond.indexOf(func), 1); } // 通知事件池中的方法按照添加顺序执行 fire(...args) { this.pond.forEach(item => { item.call(this, ...args) // item.apply(this, args) // 三个以上的参数call性能更加好 }) } catch (error) { console.error(error); } } // 暴露给外部使用 return function() { console.log("You haved created pond successfully!"); return new Subscribe(); } })();
-
整体思路的实现
-
数组塌陷问题的解决方法
m.apply(this, args)
// 三个以上的参数call性能更加好
})
}
catch (error) {
console.error(error);
}
}
// 暴露给外部使用
return function() {
console.log(“You haved created pond successfully!”);
return new Subscribe();
}
})();
* 整体思路的实现
* 数组塌陷问题的解决方法
[外链图片转存中...(img-sMXTJJQk-1600598722571)]
事件池,当进行到:修改remove,不直接删除数组元素,而是用null来代替占位