一、初始化时分支
在编写函数库时,我们经常需要进行兼容性操作,例如处理事件监听器的添加和移除。以下是一个示例代码:
```javascript
var utils = {
addListener: function(el, type, fn) {
if (typeof window.addEventListener === 'function') {
el.addEventListener(type, fn, false);
} else if (typeof document.attachEvent === 'function') {
el.attachEvent('on' + type, fn);
} else {
el['on' + type] = fn;
}
},
removeListener: function(el, type, fn) {
//...
}
}
```
这段代码每次运行时都需要判断浏览器类型,效率较低。我们希望有一种方法来解决这个问题。解决方案是在初始化时根据浏览器特征加载相应的代码。为了暴露我们需要的接口,我们使用对象字面量的方式。首先列出我们的接口:
```javascript
var utils = {
addListener: null,
removeListener: null
}
```
由于代码是按顺序执行的,我们需要对其进行接口实现,并在其中进行浏览器特性检查。
这种方式很好的改善了效率低下的问题,而且也相当简单,只需要将重复性的代码从函数实现中分离出来,在外部实现即可。
二.函数属性
有些时候我们不得不应对一些计算量比较大或者耗时比较长的操作,比方说模糊查询,搜索功能等。减少重复性的工作是我们写代码的时候一直注重的一条准则,那么有什么办法能在这些耗时操作中能够尽量提高效率呢?其中一个方法便是缓存, 在javascript中,函数就是对象,是对象那就必然是有属性的,我们可以给任意一个函数添加任意的属性,比如:
```javascript
if (typeof window.addEventListener === 'function') {
utils.addListener = function(el, type, fn) {
el.addEventListener(type, fn, false);
};
utils.removeListener = function(el, type, fn) {
//...
};
} else if (typeof document.attachEvent === 'function') {
utils.addListener = function(el, type, fn) {
el.attachEvent('on' + type, fn);
};
utils.removeListener = function(el, type, fn) {
//...
};
} else {
utils.addListener = function(el, type, fn) {
el['on' + type] = fn;
};
utils.removeListener = function(el, type, fn) {
//...
}
}
```
一、实现缓存功能
我们可以给比较耗时的函数添加一个自身属性,以便缓存计算结果。缓存可以以任意类型的数据形式存在。以下是两种实现方式:
1. 无参形式
```javascript
// 无参形式
var fn = function() {
var self = arguments.callee, result;
if (!self.cache) {
result = {};
// 耗时操作
self.cache = result;
}
return self.cache;
};
fn.cache = null;
```
2. 有参形式
```javascript
// 有参形式
var fn = function() {
var key = JSON.stringify(Array.prototype.slice.call(arguments)), result;
if (!arguments.callee.cache[key]) {
result = {};
// 耗时操作
fn.cache[key] = result;
}
return fn.cache[key];
};
fn.cache = {};
```
二、部分应用(curry化)
在这里我们考虑一种情况,有一个函数,需要传递多个参数去实现,而这个函数会在很多时候用到,很多时候我们会传递一些其中一些相同的参数。例如:
```javascript
// 调用
fn(1, 2, 3, 4);
fn(1, 2, 5, 6);
fn(1, 2, 7, 8);
fn(3, 4, 5, 6);
```
可以看到上面这个函数,我们进行了四次调用,而其有三次调用的前两个参数都是一样的。因此我们可以重新封装一个新的函数,以便减少重复性的参数输入。为了能够自动生成我们需要的函数,我们需要创建工具函数。
以下是重构后的内容:
一.柯里化函数(Curry Function)
柯里化函数接受一个需要进行柯里化的函数作为第一个参数,后面为接受柯里化函数的部分或全部参数。使用示例如下:
```javascript
// 1. 创建柯里化函数
var newFn = curry(fn, 1);
newFn(2, 3, 4);
// 2. 创建另一个柯里化函数
var newFn2 = curry(fn, 2, 3);
newFn2(5, 6);
```
二.命名空间(Namespace)
在项目开发中,我们可能需要对对象进行管理,以避免污染全局空间。为了实现命名空间,我们可以简单地创建一个对象,并在该对象内实现所需的操作。例如:
```javascript
// 1. 定义全局命名空间
var Main = Main || {};
// 2. 在Main的命名空间内添加模块
Main.utils = Main.utils || {};
Main.utils.http = Main.utils.http || {};
```
当我们希望创建一个名为`Main.utils.http`的模块时,我们需要进行三次检查来判断`Main`、`utils`和`http`这几个模块是否存在。这样做显然不太方便。因此,我们可以使用一个工具函数来帮助我们创建新模块:
有了命名空间辅助工具函数,我们能够轻易地构建自己的常用工具库:
1. Ken.namespace(‘utils.http’);
2. Ken.utils.http = (function() {
3. var privatePropoty = ‘ken’;
4. function ajax(option) {
5. //todo
6. }
7. function jsonp(option) {
8. //todo
9. }
10. //...
11. return {
12. ajax: ajax,
13. jsonp: jsonp
14. }
15. })();
当然,在项目开发中,我们总是希望能够让代码尽量模块化。因此,我们可能会经常这样操作。
```javascript// 封装模块
var model = (function() {
var fn = function() {};
fn.prototype = {};
var a = 1;
function fn2() {}
return {
fn: fn,
fn2: fn2,
a: a
};
})();
// 使用模块
require([
'jquery',
'ux/jquery.common'
], function($, common) {
var a = 1, b = 2;
//todo...
});
```
```javascript// 创建一个名为Sandbox的对象和其属性
var Sandbox = {};
Sandbox.modules = {};
// 为Sandbox对象添加dom模块函数
Sandbox.modules.dom = function (box) {
box.getElement = function () {}; // 空函数作为占位符
box.foo = "bar"; // 一个字符串值作为示例属性
};
// 为Sandbox对象添加event模块函数
Sandbox.modules.event = function (box) {
box.attachEvent = function () {}; // 空函数作为占位符
box.dettachEvent = function () {}; // 空函数作为占位符
};
// 定义Sandbox构造函数,接收参数并执行回调函数
function Sandbox() {
var args = Array.prototype.slice.call(arguments), // 将传入的参数转换为数组形式
callback = args.pop(), // 取出最后一个参数作为回调函数
modules = (args[0] && typeof args[0] === "string") ? args : args[0], // 如果第一个参数是字符串或存在且为字符串,则将其赋值给modules;否则将第一个参数本身赋值给modules
i;
if (!(this instanceof Sandbox)) { // 如果当前上下文不是Sandbox实例,则返回一个新的实例
return new Sandbox(modules, callback);
}
if (!modules || modules === "*") { // 如果modules不存在或等于“*”,则初始化modules数组,并将所有模块名称推入该数组
modules = [];
for (i in Sandbox.modules) { // 从Sandbox对象中枚举所有模块名称
if (Sandbox.modules.hasOwnProperty(i)) { // 确保该属性为自身属性而不是原型链中的属性
modules.push(i);
}
}
}
for (i = 0; i < modules.length; i += 1) { // 对于每个模块名称,调用相应的模块函数并传入当前上下文(即Sandbox实例)
Sandbox.modules[modules[i]](this);
}
callback(this); // 在执行完所有操作后,调用传入的回调函数并传入当前上下文(即Sandbox实例)
}
// 为Sandbox原型对象添加name和version属性以及getName方法(用于获取name属性值)
Sandbox.prototype = {
name: "ken", // name属性值为"ken"字符串
version: "1.0", // version属性值为"1.0"字符串
getName: function() { // 该方法返回name属性值(即"ken"字符串)
return this.name; // 直接访问name属性值并返回结果
},
};
```
JavaScript设计模式是编程实践中的一种重要思想,它提供了一套经过时间考验的最佳实践,用来解决常见的编程问题和提高代码的可维护性、可扩展性和可复用性。JavaScript设计模式包括创建型模式、结构型模式和行为型模式,其中创建型模式有单例模式、工厂模式等等,结构型模式有装饰器模式、组合模式等等,行为型模式有观察者模式、策略模式等等。