函数(Functions)

函数对象(Function Objects)

在JavaScript中函数就是对象(Functions in Javascript are objects),通过字面符产生的对象指向Object.prototype,函数对象指向Function.prototype,Function.prototype指向Object.prototype。每个函数被创建后带有一个prototype属性,prototype属性的值是一个包含constructor属性的对象,而constructor属性的值是该函数(function)。

函数字面符号(Function Literal)

通过字面符号创建函数对象。

1
2
3
var add = function (a, b) { 
	return a + b;
};

调用(Invocation)

每个函数接受两个额外的参数:this, arguments。可通过4种模式调用函数。

  1. 方法调用(method invocation)

当一个函数被赋值/存储为一个对象属性,文件叫它为一个方法。当一个方法调用时this被绑定到这个对象上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Create myObject. It has a value and an increment 
// method. The increment method takes an optional
// parameter. If the argument is not a number, then 1 
// is used as the default.
var myObject = { 
	value: 0,
	increment: function (inc) {
		this.value += typeof inc === 'number' ? inc : 1;
	} 
};
myObject.increment(); 
document.writeln(myObject.value);   // 1
myObject.increment(2); 
document.writeln(myObject.value);    // 3
  1. 函数调用(function invocation)

一个函数不是一个对象属性是,被当作函数调用,此时this被绑定到global对象。当内部函数被调用时,this仍然被绑定到了外部函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
myObject.double = function () {
	var that = this; // Workaround.
	var helper = function () {
		that.value = add(that.value, that.value);
	};
	helper(); // Invoke helper as a function. 
};
// Invoke double as a method.
myObject.double( ); 
document.writeln(myObject.value);
  1. 构造函数调用(constructor invocation)

当一个函数通过new 前缀调用,一个新的对象将被创建,this与新创建的对象绑定。

当一个函数被定义使用new调用时,被叫做构造器(constructor),习惯上首字母大写,用于与普通函数区分。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Create a constructor function called Quo. 
// It makes an object with a status property.
var Quo = function (string) { 
	this.status = string;
};
// Give all instances of Quo a public method
// called get_status.
Quo.prototype.get_status = function () { 
	return this.status;
};
// Make an instance of Quo.
var myQuo = new Quo("confused");
document.writeln(myQuo.get_status()); // confused
  1. apply调用(apply invocation)

apply方法包括两个参数,第一个参数用于绑定this,第二个参数是一个数组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Make an array of 2 numbers and add them.
var array = [3, 4];
var sum = add.apply(null, array); // sum is 7
// Make an object with a status member.
var statusObject = { 
	status: 'A-OK'
};
// statusObject does not inherit from Quo.prototype, // but we can invoke the get_status method on
// statusObject even though statusObject does not have // a get_status method.
var status = Quo.prototype.get_status.apply(statusObject); // status is 'A-OK'

参数(Arguments)

每个函数被调用时都有一个隐式的arguments参数,包含了调用的参数,可以通过类似数组的方式访问,但是arguments 不是一个真正的数组(只有length属性,没有数组的任何方法)。

返回值(Return)

一个函数总会返回一个值,如果没有指定则返回undefined。如果一个函数通过new的方式调用返回this(new的对象)。

异常(Exceptions)

throw语句会打断一个函数的执行,一个exception对象包括一个name属性(唯一标示一个异常类型)和一个message属性(异常描述)。

1
2
3
4
5
6
7
8
9
var add = function (a, b) {
	if (typeof a !== 'number' || typeof b !== 'number') {
		throw {
			name: 'TypeError',
			message: 'add needs numbers'
		}; 
	}
	return a + b; 
}

try语句只有一个catch块(捕获所有的异常),如果有多种类型的异常需哟处理,可根据异常名称确定异常的类型。

1
2
3
4
5
6
7
8
var try_it = function () { 
	try {
		add("seven"); 
	} catch (e) {
		document.writeln(e.name + ': ' + e.message); 
	}
}
try_it( );

扩展类型功能

在JavaScript中可以对基本类型进行功能扩展。对Object.prototype添加方法对所有对象都可用,包括:函数(functions)、数组(arrays)、字符串(strings)、数字(numbers)、正则表达式(regular expressions)、布尔(booleans)。

通过Function.prototype添加方式对所有的函数都有效。

1
2
3
4
Function.prototype.method = function (name, func) { 
	this.prototype[name] = func;
	return this;
};
1
2
3
4
5
6
7
// Add a method conditionally.
Function.prototype.method = function (name, func) { 
	if (!this.prototype[name]) {
		this.prototype[name] = func;
    return this;
  }
};

添加整数方法到Number

1
2
3
Number.method('integer', function () {
  return Math[this < 0 ? 'ceil' : 'floor'](this);
});
1
document.writeln((-10 / 3).integer()); // -3

添加删除空格的方法到string

1
2
3
String.method('trim', function () {
	return this.replace(/^\s+|\s+$/g, '');
});
1
document.writeln('"' + " neat ".trim() + '"');

递归(Recursion)

尾部递归优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Make a factorial function with tail
// recursion. It is tail recursive because 
// it returns the result of calling itself.
// JavaScript does not currently optimize this form.
var factorial = function factorial(i, a) { 
	a = a || 1;
  if (i < 2) {
  	return a;
	}
	return factorial(i - 1, a * i);
};
document.writeln(factorial(4)); // 24

作用域(Scope)

JavaScript只有函数作用域,没有块作用域。最好在函数体的顶部声明变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var = function () { 
	a = 3, b = 5;
	var bar = function () { 
		var b = 7, c = 11;
		// At this point, a is 3, b is 7, and c is 11 
		a += b + c;
    // At this point, a is 21, b is 7, and c is 11
  };
	// At this point, a is 3, b is 5, and c is not defined 
	bar();
  // At this point, a is 21, b is 5
};

闭包(Closure)

内部函数可以访问定义它的外部函数的参数和变量(this和arguments除外)。

通过闭包实现封装,保护不想让外部程序访问的属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 不是赋值一个函数给对象,而是赋值一个函数调用的返回值。
var myObject = (function () { 
	var value = 0;
  return {
  	increment: function (inc) {
			value += typeof inc === 'number' ? inc : 1; 
		},
		getValue: function () { 
			return value;
		} 
	};
}());
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var quo = function (status) { 
	return {
			get_status: function () { return status;
			} 
	};
};

// Make an instance of quo.
var myQuo = quo("amazed");
document.writeln(myQuo.get_status( ));

例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Define a function that sets a DOM node's color 
// to yellow and then fades it to white.
var fade = function (node) { 
	var level = 1;
	var step = function () {
		var hex = level.toString(16); 
		node.style.backgroundColor = '#FFFF' + hex + hex; 
		if (level < 15) {
			level += 1;
      setTimeout(step, 100);
    }
	};
  setTimeout(step, 100);
};
fade(document.body);

正例(避免在循环中创建函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 var add_the_handlers = function (nodes) { 
 	var helper = function (i) {
   	return function (e) {
     	alert(i);
 		}; 
 	};
 	var i;
 	for (i = 0; i < nodes.length; i += 1) {
 		nodes[i].onclick = helper(i); 
 	}
};

反例(弹出框提示总是最后一个值

1
2
3
4
5
6
7
8
var add_the_handlers = function (nodes) { 
	var i;
	for (i = 0; i < nodes.length; i += 1) { 
		nodes[i].onclick = function (e) {
			alert(i); 
		};
	} 
};

回调(Callbacks)

在异步请求中传递一个函数作为参数,当响应返回时该函数被调用。

1
2
3
request = prepare_the_request();
response = send_request_synchronously(request); 
display(response);

使用回调方式

1
2
3
request = prepare_the_request(); send_request_asynchronously(request, function (response) {
	display(response);
});

模块化(Module)

我们可以使用函数和闭包实现模块化。一个模块是一个函数或对象的接口表现,隐藏了具体的实现和状态。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
String.method('deentityify', function () {
	// The entity table. It maps entity names to characters.
	var entity = { 
		quot: '"',
		lt: '<',
		gt: '>' 
	};
	// Return the deentityify method. 
	return function () {
		// This is the deentityify method. It calls the string 
		// replace method, looking for substrings that start 
		// with '&' and end with ';'. If the characters in
		// between are in the entity table, then replace the 
		// entity with the character from the table. 
		return this.replace(/&([^&;]+);/g, function (a, b) {
			var r = entity[b];
			return typeof r === 'string' ? r : a; 
		}); 
	};
}());
1
document.writeln( '&lt;&quot;&gt;'.deentityify()); // <">

及联调用(Cascade)

如果一个函数返回this则可以使用及联调用。

柯里化(Curry)

柯里化允许我们通过组合一个函数和一个参数产生一个新的函数。JavaScript没有柯里化方法,我们可以通过给Function.prototpe添加一个方法实现。

1
2
3
4
5
6
7
8
Function.method('curry', function () { 
	var slice = Array.prototype.slice, 
		args = slice.apply(arguments),
		that = this; 
	return function () {
		return that.apply(null, args.concat(slice.apply(arguments))); 
	};
});
1
2
var add1 = add.curry(1); 
document.writeln(add1(6)); // 7

内存化(Memoization)

函数使用对象保存前一个操作的结果,从而避免不必要的重复计算。

1
2
3
4
5
6
7
var fibonacci = function (n) {
	return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};

for (var i = 0; i <= 10; i += 1) { 
	document.writeln('// ' + i + ': ' + fibonacci(i));
}

内存化后

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var fibonacci = (function () { 
	var memo = [0, 1];
	var fib = function (n) {
		var result = memo[n];
		if (typeof result !== 'number') {
			result = fib(n - 1) + fib(n - 2);
			memo[n] = result; 
		}
    return result;
  };
	return fib; 
}());

提出函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var memoizer = function (memo, formula) { 
	var recur = function (n) {
		var result = memo[n];
		if (typeof result !== 'number') {
			result = formula(recur, n);
			memo[n] = result; 
		}
  	return result;
  };
  return recur;
};
1
2
3
4
5
6
7
var fibonacci = memoizer([0, 1], function (recur, n) { 
	return recur(n - 1) + recur(n - 2);
});

var factorial = memoizer([1, 1], function (recur, n) { 
	return n * recur(n - 1);
});