什么叫单例设计模式
假如我们不用对象,要描述一个人的信息。
let name = '杨帅';
let age = 18;
let GF = false;
let name = '梁大伟';
let age = 20;
let GF = true;
可以看到,每添加一个人的信息都要写一堆代码,而且还有变量名冲突,首先我们来解决变量名冲突的问题
// 利用闭包
(function() {
let name = '杨帅';
let age = 18;
let GF = false;
})();
(function() {
let name = '梁大伟';
let age = 20;
let GF = true;
})();
如果使用对象,就可以基于对象进行分组,每一个对象都是 Object 这个类的实例,我们把这种思想叫做"单例设计模式"。
JS 中的 单例设计模式: 基于单独的实例,来实现信息分组,这样可以避免全局变量的污染。
注意,这和后端的单例模式不太一样,通常我们认为 new 两次得到的是同一个实例,保证一个类仅有一个实例,并提供一个访问它的全局访问点这样叫单例模式,但其实这种方法在 js 中叫做构造函数模式。
let ys = {
name: '杨帅',
age: 18,
GF:false
}
let ldw = {
name: '杨帅',
age: 20,
GF:true
}
闭包是利用栈内存来进行分组的,而对象则利用的是堆内存,ys 和 ldw 可以被称之为"命名空间"
高级单例模式
我们早期项目中用到的模块化,其实就是基于闭包和对象去设计的。
// 新闻板块
(function() {
let time = new Date();
const query = function query() {
// ...
}
const handle = function handle() {
// ...
}
})();
// 皮肤板块
(function() {
let time = '1970-03-24';
const handle = function handle() {
// ...
}
})();
这样可以实现两个模块之间的变量隔离,但是如果我想在皮肤板块用到新闻板块的东西,怎么办呢? 思路: 把公用的东西放到 window 上
// 新闻板块
(function() {
// ...
// 把供其他板块调用的方法,暴露到全局对象「局限:暴露的内容比较多,则还会引发全局变量冲突」
window.query = query;
})();
为了避免再次引发全局冲突,我们可以在此闭包的基础上,再次使用单例的思想来做优化。
// 新闻板块
let newsModule = (function() {
// ...
return {
query,
handle
}
})();
newsModule 就是一个变量命名空间,这个变量包含了供其他模块调用自身属性的方法。
这样只要能保住命名空间不冲突,就能实现模块化,这种组合了闭包和单例的实现方案就是单例模式的高级用法,我们称之为高级单例模式。
模块化编程的历史
1:高级单例模式「闭包 + 对象的组合方式」
弊端:我们需要控制模块的导入顺序(JS 执行顺序),保证依赖的模块提前加载。
2:AMD 按需加载(require.js)
// 指定依赖模块 并使用 define 声明新模块 其实使用的还是 闭包 + 对象,只是做了一些封装
define(['newsModule'], function(moduleA) {
newsModule.query(); // 使用依赖模块的方法
return {
handle
}
});
// 依赖前置 提前加载
require(['newsModule', 'skinModule'], function(newsModule, skinModule) {
newsModule.query();
});
3:CMD「sea.js」 & CommonJS 规范「Node.js」 在 node 中,我们使用的 CommonJs 规范在浏览器环境默认是不支持的,所以阿里的玉伯大神就开发了一套浏览器环境的 CommonJs 规范,称之为 CMD 规范,所以其实他们是一套规范,只不过支持的平台差异而已。
// CommonJS 规范演示
// a.js
const sum = function sum(...params) {
return params.reduce((prev, next) => prev + next);
}
module.exports = {
sum
}
// b.js
let name = '哈哈';
let A = require('./a');
// 求平均值
const average = function average(...params) {
return (A.sum(...parmas) / pramas.length).toFixed(2);
}
module.exports = {
average
}
// main.js
let A = require('./a');
console.log(A.sum(10, 20, 30, 40));
let B = require('./b'); // 依赖后置 按需加载
console.log(B.average(10, 20, 30, 40));
需要注意的是,浏览器环境引入 main.js 会报错,因为客户端浏览器是不支持 CommonJs 规范的「如果导入 sea.js 可以支持」,当代打包工具 webpack 也是支持 CommonJs 规范的,最后按照 CommonJs 把各个模块进行打包,编译为浏览器可以支持的代码「webpack 本身是基于 node 环境运行的,基于 webpack 打包后的代码,是 webpack 自己实现了一套 CMD 规范!」
4:ES6 Module 规范 浏览器和 webpack 都可以支持的一套方案。
// a.js
const sum = function sum(...params) {
return params.reduce((prev, next) => prev + next);
}
// 导出多个
// export const A = 'xx';
// export const B = 'xx';
// 导出一个
// export default sum;
export default {
sum
};
// b.js
import A from './a.js';
const average = function average(...params) {
return (A.sum(...parmas) / pramas.length).toFixed(2);
}
export default average;
// main.js
import A from './a.js';
import average from './B.js';
console.log(A.sum(10, 20, 30, 40));
console.log(average(10, 20, 30, 40));