前言
“设计模式是软件设计中经过了大量实际项目验证的可复用的优秀解决方案,它有助于程序员写出可复用和可维护性高的程序。许多优秀的JavaScript开源框架都运用了不少设计模式,越来越多的程序员从设计模式中获益,也许是改善了自己编写的某个软件,也许是更好的理解了面向对象的编程思维。无论如何,系统的学习设计模式都会令你收益匪浅。”--《JavaScript设计模式与开发实践》
作为科班出身的前端开发者,在校学习期间或者日常工作中都或多或少的接触过“设计模式”相关的信息。在一来二去的接触中,我对这个熟悉而又陌生的东西产生了浓厚的兴趣。经过一番对比之下我选择了《JavaScript设计模式与开发实践》一书作为我学习设计模式的启蒙之书。该书共分为三大部分:一、JavaScript面向对象和函数式编程方面的知识,二、由浅入深讲解十六个设计模式,三、面向对象的设计原则及其在设计模式中的体现。接下来我就按照书中的目录介绍我在阅读过程中的感悟和想法。
目录
基础知识
面向对象
作为科班出身的开发人员,面向对象编程是必定熟悉的一种概念,与其相关的封装、多态、继承也是脱口而出。但是在JavaScript中并没有提供传统面向对象的类式继承,也没有在语言层面提供对抽象类和接口的支持,这些差异导致我们使用js学习和使用设计模式的时候要跟传统静态类型语言加以区别。
多态
多态的实际含义是:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈。比如:让狗叫是“汪汪汪”,让鸡叫是“咯咯咯”,对于同一个命令,他们的反应是不同的。按照多态的思想,我们把鸡狗的行为和我的指令抽取出来,也就是把“做什么”和“谁去做”分离了出来。实现这一点首先要做到的是类型之间的解耦,而在JavaScript中,变量的类型在运行期间是可变的,这就意味着JavaScript对象的多态性是与生俱来的。
比如在如下例子中,makeSound的参数类型是可变的,传入其中的参数只需要保证有sound方法即可完成不同对象的不同调用结果,即多态的实现。
var makeSound=function(animal){
animal.sound();
};
var Duck=function(){};
Duck.prototype.sound=function(){
console.log('嘎嘎嘎');
};
var Chicken=function(){};
Chicken.prototype.sound=function(){
console.log('咯咯咯');
}
makeSound(new Duck()); // 嘎嘎嘎
makeSound(new Chicken()); // 咯咯咯
封装
封装的目的是将信息隐藏。在许多语言的对象系统中,封装数据是由语法解析来是实现的,这些语言也许提供了private、public、protected等关键字来提供不同的访问权限。但是JavaScript并没有提供对这些关键字的支持,我们只能依赖变量的作用域来实现封装特性,而且只能模拟出public和private这两种封装性。
var myObject = (function () {
var _name = 'sven'; // 私有(private)变量
return {
getName: function () { // 公开(public)变量
return _name;
}
}
})();
console.log(myObject.getName()); // 输出:sven
console.log(myObject._name); // 输出:undefined
原型继承和原型模式
从设计模式的角度来讲,原型模式是用于创建对象的一种模式,如果我们想要创建一个对象,一种方法是先指定他的类型,然后通过类来创建这个对象。原型模式选择了另外一种方式,我们不再关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一摸一样的对象。
var Plane = function () {
thid.blood = 100;
this.attackLevel = 1;
this.defenseLevel = 1;
};
var plane = new Plane();
plane.blood = 500;
plane.attackLevel = 10;
plane.defenseLevel = 7;
var clonePlane = Object.create(plane);
console.log(clonePlane.blood); //输出 500
console.log(clonePlane.attackLevel); //输出: 10
console.log(clonePlane.defenseLevel); //输出: 7
// 在不支持Object.create方法的浏览器中,则可以使用如下代码
Object.create = Object.create || function (obj) {
var F = function () { };
F.prototype = obj;
return new F();
}
设计模式
单例模式
单例模式是一种常用的设计模式,其定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。在JavaScript中由于语言本身无类,传统的单例模式在JavaScript中并不适用。根据单例模式的核心概念(确保只有一个实例,并提供全局访问),我们很容易的会联想到全局变量,但是全局变量并不是单例,只是我们经常把全局变量当作单例使用。用全局变量var a={}来创建的对象确实是独一无二的,但是全局变量很容易早成命名空间的污染。在大型项目中要尽量避免这样的变量,减少变量冲突带来的问题。因此在书中作者提到了几种变量的使用方式:
1.使用命名空间
var MyApp = {};
MyApp.namespace = function (name) {
var parts = name.split('.');
var current = MyApp;
for (const i in parts) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
};
MyApp.namespace('event');
MyApp.namespace('dom.style');
console.log(MyApp);
// 以上等价于
var Myapp = {
event: {},
dom: {
style: {}
}
}
2.使用闭包
var user = (function () {
var _name = 'sven',
_age = 29;
return {
getUserInfo: function () {
return _name + '-' + _age
}
}
}())
惰性单例
惰性单例指的是在需要的时候才创建对象实例。不仅实现了实例的懒加载还能够减少实例频繁的创建和删除导致的额外成本。
var getSingle = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments))
}
}
// 创建div
var createLoginLayer = function () {
var div = document.createElement('div');
div.innerHTML = '我是登录窗口';
div.style.display = 'none';
document.body.appendChild(div);
return div;
}
var createSingleLoginLayer = getSingle(createLoginLayer);
document.getElementById('loginBth').onclick = function () {
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
}
// 创建iframe
var createSingleIframe = getSingle(function () {
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
return iframe;
})
document.getElementById('loginBtn').onclick = function () {
var loginLayer = createSingleIframe();
loginLayer.src = 'http://baidu.com';
}
在上面示例中,getSingle用作创建并返回单例。在getSingle中传入fn(需要作为单例的行为),之后再让getSingle返回一个新的函数,并用一个变量result保存fn的结果。result因为在闭包中,所以永远不会被销毁,在之后的请求中,如果result已经被赋值将会永远返回该值,达成单例效果。
发现总结:在日常开发中antd的弹出组件如modal,confirm等都是惰性单例实现。观察其实现就会发现在未触发弹出的时候,页面中并无组件渲染。当弹出一次后组件将被渲染并在以后的隐藏时也不会去除元素,而只是更改其显示隐藏。