实现单例模式
核心原理
- 使用 new 操作符,会隐式返回 this
- 使用闭包,保存了实例变量
方式 1 需要显式调用
var Singleton = function(name) {
this.name = name;
// new操作符调用 隐式返回this
};
Singleton.prototype.getName = function() {
console.log(this.name);
};
// 自执行函数 保存了instance的引用
Singleton.getInstance = (function() {
var instance = null;
return function(name) {
if (!instance) {
instance = new Singleton(name);
}
return instance;
};
})();
// 测试一下
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');
console.log(a === b); // true
方式 2 隐式调用之代理实现
var CreateDiv = function(html) {
this.html = html;
this.init();
// new操作符调用 隐式返回this
};
CreateDiv.prototype.init = function() {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
};
// 返回div实例
var ProxySingletonCreateDiv = (function() {
var instance;
return function(html) {
if (!instance) {
instance = new CreateDiv(html);
}
return instance;
};
})();
// 测试一下
var a = new ProxySingletonCreateDiv('sven1');
var b = new ProxySingletonCreateDiv('sven2');
console.log(a === b);
JavaScript 中的单例模式
书中说:“单例模式的核心是确保只有一个实例,并提供全局访问”。全部变量符合这个定义,但是又说全局变量不是单例模式,为什么呢?
因为全局变量虽然提供全局访问,但是全局变量可以创建多个实例,而且随时可能被覆写。
全局变量很容易产生冲突,可以使用命名空间或闭包减少全局变量的数量
动态创建命名空间
这个代码挺巧妙的,有 lodash 的风格,短小精悍~
// 分配内存地址0x001 指向{}
var MyApp = {};
// 以 MyApp.namespace('dom.style') 为例
MyApp.namespace = function(name) {
// 转为数组 parts = ['dom', 'style']
var parts = name.split('.');
// 保存当前命名空间的引用 current指向内存地址0x123
var current = MyApp;
for (var i in parts) {
// 当前命名空间不存在则增加 因为是引用 所以MyApp同步修改
if (!current[parts[i]]) {
// 第一次循环 current[dom] = {}(分配地址0x002) 同时 MyApp=current={'dom': {}}
// 第二次循环 地址0x002 增加style MyApp和current同步修改
current[parts[i]] = {};
}
// 这里是核心 每次遍历后 修改当前命名空间 用于下次循环
// current 指向地址0x002
current = current[parts[i]];
}
};
MyApp.namespace('event');
MyApp.namespace('dom.style');
console.dir(MyApp);
// 上述代码等价于:
var MyApp = {
event: {},
dom: {
style: {},
},
};
闭包封装私有变量
这里使用闭包创建了私有变量,外部只能通过暴露的方法获取。
var user = (function() {
var __name = 'sven';
return {
getUserInfo: function() {
return __name;
},
};
})();
typescript 的 private
私有变量,也就是其他语言 private 标识符修饰的变量,这就想到了 typescript 中的 private,它是怎么实现的,编译后的代码是什么?
class User {
private name = 'sven';
getName() {
return name;
}
}
const sven = new User();
console.log(sven.name); // Property 'name' is private and only accessible within class 'User'.
按照 private 的理解,我以为 typescript 编译后会有什么黑科技,来实现变量私有化,比如加个下划线啥的,结果并不是,没啥变化,只是编译时限制了类型,编译后就移除了。。并没有私有化,以下就是 babel 编译后的代码
'use strict';
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
class User {
constructor() {
_defineProperty(this, 'name', 'sven');
}
getName() {
return name;
}
}
const sven = new User();
console.log(sven.name); // sven 果然啥也没做 就是编译为普通的js代码
变量私有化的其他方式
私有化的核心是隐藏对象的 key。es6 新增了原始数据类型 Symbol,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。还是以 User 为例
// nameKey定义在内部,对外不暴露
const nameKey = Symbol('name');
class User {
constructor() {
this[nameKey] = 'sven';
}
getName() {
return this[nameKey];
}
}
const sven = new User();
console.log(sven.name); // undefined
console.log(sven.getName()); // sven
// 虽然这样可以访问,但是nameKey类似密码机制,虽然用密码可以获取,但是你得先拿到密码
console.log(sven[nameKey]); // sven
惰性单例
可复用单例的实现
var getSingle = function(fn) {
var result;
return function() {
return result || (result = fn.apply(this, arguments));
};
};
场景 1 创建唯一的 dom 元素
var createLoginLayer = function() {
var div = document.createElement('div');
div.innerHTML = '我是登录浮窗';
return div;
};
// 本次执行只是获取了一个创建div单例的函数
var createSingleLoginLayer = getSingle(createLoginLayer);
// 第一次执行 执行fn 创建div 保存到result
var loginLayer1 = createSingleLoginLayer();
// 第二次执行 得到result 也就是上次创建的div
var loginLayer2 = createSingleLoginLayer();
console.log(loginLayer1 === loginLayer2); // true
// 分析下执行过程
var getSingle = function(fn) {
// 单例标识
var result;
return function() {
// 这里代码比较精简 转化一下
// return result || (result = fn.apply(this, arguments));
if (!result) {
// 用当前函数上下文执行fn 获取实例或标识 赋值给result
result = fn.apply(this, arguments);
}
return result;
};
};
场景 2 模拟 jQuery one 事件
var bindEvent = getSingle(function() {
document.getElementById('div1').onclick = function() {
alert('click');
};
return true;
});
// 第一次执行 result为false 此时执行回调函数 result 改为ture
bindEvent();
// 第二次执行 result为true 什么也不做 返回 true
bindEvent();