前言
函数
- 函数可以作为带有属性和方法的值当然还有参数进行传递
- Javascript中的作用域都是用函数来标明的,并不像C++中用 ” { } “ 来提供局部作用域,可以稍微参见一下闭包
创建函数的方式主要有两种:
- 函数表达式
var Foo = function () {};
- 函数声明
function Foo() {}
API 模式
- 回调模式:将函数作为参数传递,类似于C++里面的函数指针,但在Javascript里面会更容易实现
- 配置对象:控制函数参数数量,通过传递一个对象,将配置参数包含在此对象中,避免传递大量函数参数
- 返回函数:函数返回值为函数的函数
- Curry化:函数的部分求值
接下来详细介绍一下各模式:
-
回调模式的形式如下:
function httpRequest (url,callback) {
// ...
callback();
}经常可以利用此类模式去增加代码的复用性,便于扩展。
虽然在许多情况下这种方法简单而且可行,但是存在一些场合。比如回调函数不是匿名函数或全局函数,而是对象的方法。如果回调方法里面使用this来引用他所属的对象,这将导致不同的行为发生。例如:
//假设回调函数是myapp.paint()
var myapp = {
color: 'green'
};
myapp.paint = function(node) {
node.style.color = this.color;
};
var findNodes = function(callback) {
//寻找DOM中的一个节点找到后存入found中
// ...
if (typeof callback === 'function') {
callback(found);
}
// ...
};
/*
如果调用findNodes(myapp.paint),他并不会按照预期那样运行。因为this.color中的this并不是指向myapp而是全局对象:比如window
*/
/*
解决方法
*/
var findNodes = function(callback, callback_obj) {
// ...
if (typeof callback === 'function') {
callback.call(callback_obj, found);
}
// ...
} - 配置对象模式形式如下:
var person = {};
function addPerson(person, conf) {
person.name = conf.name || 'default name';
person.age = conf.age || 'default age';
person.sex = conf.sex || 'default sex';
person.id = conf.id || 'default id';
}
addPerson(person, {
name: 'James',
id: '0001',
});
// person: Object {name: "James", age: "default age", sex: "default sex", id: "0001"}此类模式的好处就是在设置参数的时候不用记住很多的参数和顺序,可以忽略可选参数。但是就是得记住参数的名称
-
返回函数:
// 函数初始化,然后接着继续执行...
var setup = function() {
var counter = 0;
return function() {
return ++counter;
}
}
var plus = setup();
plus(); // 1
plus(); // 2
plus(); // 3
// .... -
Curry化(也就是函数部分求值,如下):
// F(x,y) = x + y;
// F(0,y) = y;
// G(x) = F(0,y);
// G(1) = 1;
//
// 部分求值可以解释如下:
// 当函数的输入参数不全时,他会返回另一个函数来接收剩余的参数。
function currylize(fn) {
var slice = Array.prototype.slice,
stored_args = slice.call(arguments, 1);
return function() {
var new_args = slice.call(arguments),
args = stored_args.concat(new_args);
return fn.apply(null, args);
}
}
function add(x, y) {
return x + y;
}
var newAdd = currylize(add, 1);
newAdd(2); // 3Curry化的使用场合:当发现正在调用同一个函数,并且传递的参数绝大多数是相同的,那么这可能是个用于Curry化的不错选择
初始化模式
- 即时函数:定义好就立刻执行
- 即时对象初始化:匿名对象组织了初始化任务,提供可被立即调用的方法
- 初始化分支:帮助分支代码在执行过程中仅检测一次,避免程序多次检测
接下来将详细介绍各模式:
-
即时函数模式:
这种模式本质上是一个函数表达式,只不过他会在函数定义的时候立刻执行,其形式如下:
// 最常见的样子
(function() {
//...
console.log('init');
})();
// 当然也可以这样
(function() {
// ...
console.log('init');
}());
// 同样也可以将参数传递到及时函数里
(function(who, what) {
console.log(who + 'says: ' + what);
})('James', new Date());
//也可以将全局对象以参数形式传递进来
(function(global) {
// ...
})(this);
// 即时函数仍然是个函数,当然也可以返回函数
// 结合之前返回函数的例子
var increase = (function(initValue) {
var counter = initValue;
return function() {
return counter++;
};
})(0);
increase(); // 0
increase(); // 1
// 当定义对象时,同样可以利用及时函数去初始化一些值
var o = {
_message: (function() {
// ...
return 'Hello?'
})(),
getMsg: function() {
return this._message;
}
};
o.getMsg(); // Hello?即时函数模式最主要的特点就是,不会留下任何局部变量,保护全局空间。同样也可以用此模式模块化的包装一些功能,使程序变得更容易扩展
-
即时对象初始化:
这是保护全局域不受污染的另一种方法,这种模式使用带有init()方法的对象,该方法创建对象后将会立刻执行,形式如下
({
height:768,
width:1024,
init: function(){
// ...
}
}).init();
//其一般形式如下:
({...}).init();此方法与之前的方法相比,会更具结构化一点。按书上来讲,其缺点是绝大多数的Javascript minifier不会有效缩减这种模式。
-
初始化分支:
初始化分支(init-time branching)也称为加载时分支(load-time branching)是一种优化模式,当知道某个条件在整个程序的生命周期内都不会发生改变的时候,仅对该条件做一次测试是很有意义的。例如浏览器的功能检测。例如:
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 {
// ...
}
},
removeListener: function(el, type, fn) {}
}
// 这段代码的问题在于效率低下,每次在调用utils.addEventListener()时都要重复的检查。
// 使用初始化分支的时候可以在脚本初始化加载时一次性探测出浏览器的特性,例如:
// after
var utils = {
addListener: null,
removeListener: null
};
if (typeof window.addEventListener === 'function') {
utils.addListener = function(el, type, fn) {
el.addEventListener(type, fn, false);
}
} else if (typeof document.attachEvent === 'function') {
utils.addListener = function(el, type, fn) {
el.attachEvent('on' + type, fn);
}
} else {
// ...
}