前言
函数用于指定对象的行为,是JavaScript的基础单元。所谓编程,就是将一组需求分解为一组函数和数据结构的功能。
1、函数对象
JavaScript中的函数就是对象。每个函数在创建时会附加两个隐藏的属性:函数的上下文和实现函数调用的代码。
函数可以像其他的值一样被使用,可以被保存在另一个对象中,可以作为参数传递,可以返回函数,还可以有方法,唯一不同的是可以被调用。
2、函数字面量
通过函数字面量创建函数:
var add = function (a, b) {
return a + b;
}
函数由以下几个部分组成:
function保留字;函数名,function后面跟函数名,可省略(省略函数名的称为匿名函数);
参数,圆括号中的参数;
函数主体,花括号里面的一组语句,在函数调用时执行。
3、调用
函数在调用时接收两个附加的参数:this和argument。函数调用符是函数值表达式后的一对圆括号。
argument是实际参数,当实际参数与形式参数个数不匹配时,程序不会报错。实际参数值过多,超出的参数会被省略,实际参数值过少,缺少的参数会被替换为undefined。
函数有四种调用方式:方法调用模式、函数调用模式、构造器调用模式、apply调用模式。
4、方法调用模式
函数作为对象的一个属性时,称为方法。当一个方法被调用时,this绑定为该对象。
var myObject = {
value: 0,
increment: function (inc) {
this.value + = typeof inc === 'number' ? inc : 1;
}
}
<pre name="code" class="javascript">myObject.increment(2);
5、函数调用模式
一个函数不作为对象的属性时,就是函数调用方式。
此模式下,this绑定到全局对象。有时引用内部函数时,会带来不必要的错误,解决方法将this传递给另一个变量。
var sum = add(3, 4);
var that = this;
6、构造器调用模式
构造器调用模式,使用new来调用一个函数,创建一个连接到该函数prototype成员的新对象。
函数创建的目的就是以这种方式调用,称为构造器函数。构造器函数第一个字母需要大写。如果没有使用new来调用构造器函数不会报错,因此不推荐使用构造器调用模式。
7、Apply调用模式
函数具有apply方法,可以指定this的值。
apply接收两个参数,第一个参数是this的值,第二个参数是传递的参数数组。
8、参数
函数调用时,会得到一个参数——arguments数组,这个数组是调用函数时传递进来的参数列表,包括多余的参数。
arguments不是一个真正的数组,仅包含length属性,没有其他任何数组的方法。
9、返回
return可以提前使函数提前返回。
一个函数总会返回一个值,如果没有指定返回值,则返回undefined。
使用new前缀调用函数,且返回值不是一个对象,则返回this(该新对象)。
10、异常
JavaScript 提供了一套异常处理机制。
throw 语句可以中断函数的执行,抛出的 exception 对象具有两个属性,异常类型 name 属性和描述性的 message 属性。
try...catch 语句中,如果在 try 中遇到异常,程序会跳到 catch 中继续执行。一个 try 语句只有一个捕获异常的 catch 代码块。
11、扩充类型的功能
JavaScript允许给语言的基本类型扩充功能。给Object.propertype添加方法,让该方法对所有对象都可用。这种方式同样对函数(Function)、数组(Array)、字符(String)、数字(Number)、正则表达式和布尔值同样适用。
举例,给Function.propertype增加一个method方法,下次再给对象增加方法时,可用省略propertype。
Function.propertype.method = function (name, func) {
this.propertype[name] = func;
return this;
}
<pre name="code" class="javascript">String.method('trim', function (){
return this.replace(/^s+|\s+$/g, '');
})<span style="font-family: Arial, Helvetica, sans-serif;">;</span>
12、递归
递归函数就是直接或间接调用自身的一种函数。
一个问题能分解为一组相似的问题,每个问题都能用一个简单的方法去解决,递归就是调用自身去解决它的子问题。
举例,遍历DOM中的所有节点,每个节点都执行函数func。
var walk_the_DOM = function walk(node, func) {
func(node); // 从节点node开始
node = node.firstChild; //<span style="font-family: Arial, Helvetica, sans-serif;">找到下一个子节点</span>
while (node) {
walk(node, func); //对子节点执行函数
node = node.nextSibling; //找到下一个子节点
}
}
尾递归,会返回自身调用的结果,是一种在函数的最后执行递归调用语句特殊形式的递归。(个人理解:尾递归是在递归的最后调用一个特殊的语句,而不同于找不到子节点或堆栈溢出而结束递归。)
尾递归优化,函数返回自身递归调用的结果,调用过程会被替换成一个循环,可以显著提高速度。
13、作用域
作用域控制着变量与参数的可见性及生命周期。
JavaScript 支持函数作用域,在函数中的参数和变量在函数外部是不可见的,在一个函数的内部任何位置定义的变量,在该函数内部任何地方都可见。作用域的好处是内部函数可以访问它们的外部函数的参数和变量(除了this和arguments)。
14、闭包
内部函数拥有比它的外部函数更长的生命周期。内部函数可以访问它被创建时所处的上下文环境,这被称为闭包。
15、回调
网络上的同步请求时间过长会导致客户端进入假死状态,发起异步请求,提供一个当服务器的响应到达时随时出发的回调函数,客户端就不会阻塞。
16、模块
使用函数和闭包来构造模块。
模块模式利用了函数作用域和闭包来创建被绑定对象与私有成员的关联。
模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最有返回这个特权函数,或者把它们保存到一个可访问到的地方。
var serial_marker = function (argument) {
var prefix = ''; // 私有变量
var seq = 0; // <span style="font-family: Arial, Helvetica, sans-serif;">私有变量</span>
return {
set_prefix: function (p) {
prefix = String(p);
},
set_seq: function (s) {
seq = s;
},
gensym: function () {
var result = prefix + seq;
seq += 1;
return result;
}
}
}
var seqer = serial_marker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym();
document.writeln('1. unique: '+unique+'<br/>'); // 1. unique: Q1000
// 即使增加了新的方法,不能更改私有变量的值
seqer.newset_prefix = function (p) {
prefix = String(p);
}
seqer.newset_prefix('W') //<span style="font-family: Arial, Helvetica, sans-serif;"> 即使增加了新的方法,不能更改私有变量的值</span>
var unique = seqer.gensym();
document.writeln('2. unique: '+unique); // 2. unique: Q1001
17、级联
有一些方法是没有返回值的,比如一些修改或者设置对象的某个状态的方法,不需要返回值。可以让这些方法可以返回 this 而不是 undefined ,就可以启用级联。这些方法每一个都返回该对象,每次返回的结果都可以被下一次调用。
getElement('myBoxDiv')
.move(350, 150)
.width(100)
.height(100)
.color('red')
18、柯里化
调用柯里化方法生成新函数,调用新函数时,是返回调用原始函数的结果,传递调用柯里化方法的参数加上调用新函数的参数。
柯里化方法的新函数调用 = 调用原始函数 + ( 柯里化方法的参数 + 新函数调用参数 )
Function.method('curry', function () {
var slice = Array.prototype.slice,
args = slice.apply(arguments), // arguments不是真正的数组,除length方法外无数组的其他方法
// args现在是真正的数组,有数组的方法
that = this;
// 返回柯里化新函数
return function () {
// 新函数包含原函数调用
// 和柯里化函数时的参数
// 和调用新函数时的参数
return that.apply(null, args.contact(slice.apply(arguments)))
};
});
19、记忆
函数将先操作的结果记录在某个对象里,避免无谓的重复运算,这种优化被称为记忆。