参考书籍Learning Javascript Design Patterns
一、设计模式概述与应用场景
首先引用原书的一段话:
Patterns are proven solutions: They provide solid approaches to solving issues in software development using proven techniques that reflect the experience and insights the developers that helped define them bring to the pattern.
Patterns can be easily reused: A pattern usually reflects an out of the box solution that can be adapted to suit our own needs. This feature makes them quite robust.
Patterns can be expressive: When we look at a pattern there’s generally a set structure and vocabulary to the solution presented that can help express rather large solutions quite elegantly.
这里提到了三点:
- 设计模式是开发者定义的
- 设计模式必须具有可重用性,必须很好地处理各种异常情况
- 设计模式必须有自己的一套完整表述
光这样写可能既抽象又无趣,因而不妨从几个小点展开说一下。
设计模式在robustness(鲁棒性,我对这个翻译一直很不满)上的卓越表现,使得开发者可以省去一些花在代码结构组织上的经历,而更专于于业务逻辑开发,同时这也可以省去开发者日后重构代码的不便。
设计模式的通用性也是它的泛用性,它不局限于特定的使用环境,也不局限于特定的语言,你用C++,C#,Java,Python,JS都能写,语言是可以任选的。
同时好的设计模式也可以有效减小代码体积。
设计模式分为三大类:
- 创造型设计模式(creational design patterns)
- 结构型设计模式(structural design patterns)
- 表现型设计模式(behaviorial design patterns)
以下将主要说说第一类设计模式,之后两类可能会在以后的博客提及。
二、构造者模式
考虑一个最基本的JS对象构造函数:
function Car( model, year, miles ) {
this.model = model;
this.year = year;
this.miles = miles;
this.toString = function () {
return this.model + " has done " + this.miles + " miles";
};
}
当需要使用这个构造函数构造一个对象时,只要在函数调用之前加上new操作符,JS引擎将会识别出这是一个构造函数调用,这样,这个函数会默认返回一个this对象,这个对象的原型就是构造函数的prototype,因此,函数中对model,year等属性的赋值,就是对这个返回的对象进行的各种操作,使得我们能得到我们想要的对象。
此处的构造函数使用this.<prop>的形式添加新属性,但实际上新属性的添加有四种方式,除去这一种,还有三种:
- 方括号语法,
newObject["someKey"] = "Hello World";
Object.defineProperty方法
Object.defineProperty( newObject, “someKey”, {
value: "for more control of the property's behavior", writable: true, enumerable: true, configurable: true
});
Object.defineProperties方法
Object.defineProperties( newObject, {
“someKey”: {
value: "Hello World", writable: true
},
“anotherKey”: {
value: "Foo bar", writable: false
}
});
此处举的是原文中getter的例子,若要使用setter,可参考原文。
原文此处还提到了在构造函数的原型上定义公有方法的方式,这么做可以使得每次使用构造函数创建对象时不会重新创建一个属于被创建对象的方法,而是全部使用这个公有方法。
三、模块化模式
构造者模式的思想非常好,但是在一点上它有所欠缺,即私有变量。在Java中,声明私有变量可以采用private关键字,限制变量只能被一个类使用,它的后代,它实例化出来的对象都不能访问这个变量。
这是一种非常重要的编程思想,那么如果想用JS去实现应该怎么做呢?原文中举的例子以IIFE(立即执行函数)为主,为了与es6接轨,下面的例子将围绕es6的module展开。
首先介绍一下es6的module。为了处理日益增长的js文件造成的命名冲突和安全问题,ECMA引入了module,在module中声明的变量不会添加到全局作用域中,这样就可以避免全局污染,同时module可以指定需要输出的变量和方法。这里只举一个简单的导出与导入例子:
// export data
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
// export function
export function sum(num1, num2) {
return num1 + num1;
}
// export class
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
// this function is private to the module
function subtract(num1, num2) {
return num1 - num2;
}
// define a function...
function multiply(num1, num2) {
return num1 * num2;
}
// ...and then export it later
export { multiply };
代码来自nicolas zakas所著understanding es6中“用模块封装代码”一章,若对es6的模块化感兴趣,可阅读相关章节。
回到我们刚刚说的问题上来,为了实现“私有变量”这个概念,以上的代码中定义了一个substract方法,因为它没有被导出,所以此方法仅在此模块内可用,这样es6就从标准上实现了私有变量。
原文里详细描述了模块化模式,它主要包装了公有和私有方法,在上面的例子中,被导出的变量、常量、函数、类均可视为公有方法。模块化模式的思想就是导出一部分公有api,而维持在闭包之内的变量私密。
为了详细地说明这种思想,我们还是举一段原文的代码来做说明(es6的module同样可以实现类似的效果)
var testModule = (function () {
var counter = 0;
return {
incrementCounter: function () {
return counter++;
},
resetCounter: function () {
console.log( "counter value prior to reset: " + counter );
counter = 0;
}
};
})();
请关注这里的核心var counter = 0
,这个私有变量是在导出的公有方法中进行操作的,用户获得的对象并没有办法直接操作这个私有变量。看到这里你可能会联想到闭包,没错,它们的思想是类似的。
模块化模式同样允许传入全局变量如$,并对它进行一些操作。
模块化也是有一些缺点的,最致命的就是私有变量不能被之后添加的方法操作,这对于debug而言绝对是一场噩梦。
了解模块化更多相关请狠狠戳这篇文章