函数
8.1 函数的定义
函数使用function关键字来定义,可以用函数定义表达式或者函数声明语句。
function fn(){}
var fn=function(){}
函数声明语句会被提前,可以被在它定义之前出现的代码调用;而使用表达式方式定义的函数,变量声明提前了,但是变量赋值不会提前。所以,以表达式方式定义的函数在定义之前无法调用。
8.2 函数调用
有四种方式:
- 作为函数
- 作为方法
- 作为构造函数
- 通过它们的call()和apply()方法间接调用
8.2.2 方法调用
方法是保存在一个对象属性里的函数。
var o={};
//给o定义一个名为m()的方法
o.m=function fn(){};
//方法调用
o.m();
函数嵌套时,如果想访问外部函数的this,需要保存到变量里。
var self = this;
8.2.3 构造函数调用
如果函数或者方法调用之前带有关键字new,就构成构造函数调用。
var o = new Object();
8.3 函数的实参和形参
8.3.1 可选形参
当调用函数时传入的实参比形参个数少,剩下的形参都将设置为undefined值。
函数调用时,不能忽略第一个实参,传入第二个实参。必须显示传入null或者undefined。
在函数定义中使用注释/*optional*/
来强调形参是可选的。
function fn(o,/*optional*/a){
a = a || []; //如果函数调用时省略了第二个实参,那么创建一个空数组
}
fn({});
8.3.2 可变长的实参列表:实参对象
在函数体内,标识符arguments是指向实参对象的引用。实参对象是一个类数组对象(概念参见7.11)。
fn(x)的实参是x,也可以通过arguments[0]获得。arguments包含length属性。
下面这个函数可以接受任意数量的实参,这种函数称为“不定实参函数”:
//返回实参中的最大值
function max(/*...*/){
var max= Number.NEGATIVE_INFINITY;
for(var i=0;i<arguments.length;i++){
if(arguments[i] > max){
max=arguments[i];
}
}
return max;
}
max(1,5,10,99,81); //99
8.3.3 将对象属性用做实参
当一个函数包含超过三个形参,要记住调用顺序很困难。最好通过名/值对的形式来传入参数,这样参数的顺序就无关紧要。
function arraycopy(from,from_start,to,to_start,length){
//逻辑代码
}
function easycopy(args){
arraycopy(args.from,
args.from_start||0, //设置默认值
args.to,
args.to_start || 0,
args.length)
}
//调用
var a=[1,2,3], b=[];
easycopy({from:a,to:b,length:3});
8.4 作为值的函数
Javascript中,函数不仅是一种语法,也是值,可以将函数赋值给变量。
var a=[function(x){return x*x},20];
a[0](a[1]); //=> 400: 虽然看上去很奇怪,但这是合法的函数调用表达式
8.4.1 自定义函数属性
Javascript中,函数并不是原始值,而是一种特殊的对象,所以,函数可以拥有属性。
当函数需要一个“静态”变量,在调用时保持值不变,最方便的方式是给函数定义属性,而不是全局变量(污染命名空间)。
下面这个例子,实现每次调用函数都会返回唯一的整数:
//初始化函数对象的计数器属性
//因函数声明提前,因此可在之前给成员赋值
uniqueInteger.counter=0
function uniqueInteger(){
return uniqueInteger.counter++;
}
//调用结果
uniqueInteger() //=> 0
uniqueInteger() //=> 1
uniqueInteger() //=> 2
8.5 作为命名空间的函数
因为函数的作用域,可以定义一个函数作为命名空间,其内定义的变量都不会污染到全局命名空间。
定义一个匿名函数,会立即调用:
(function(){
//模块代码
}());
注意:function前的括号是必需的,不写会被解析为声明语句。
8.6 闭包
闭包概念: 函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性成为“闭包”。
var scope='global scope';
function checkscope(){
var scope='local scope';
function f(){
return scope;
}
return f;
}
checkscope()(); //=> "local scope"
垃圾回收
如果函数定义了嵌套函数,并将它作为返回值返回或者存储在某处的属性*v,这时就会有一个外部引用指向这个嵌套的函数。它就不会被当做垃圾回收*,并且它所指向的变量绑定对象也不会被当做垃圾回收。
闭包容易造成“循环引用”,在某些浏览器下会造成内存泄露。
闭包示例
在8.4.1中的uniqueInteger()函数,可能被恶意重置。如果使用闭包,可以将局部变量用作私有状态。
var uniqueInteger=(function(){
var counter=0;
return function(){return counter++;}
}());
uniqueInteger(); //=> 0
uniqueInteger(); //=> 1
当外部函数返回之后,其他任何代码无法访问counter变量,只有内部函数才能访问它。
8.7 函数属性、方法和构造函数
函数是Javascript中特殊的对象,所以可以拥有属性和方法,也可以用Function()构造函数来创建新的函数对象。
8.7.1 length属性
- 函数体里,
arguments.length
表示传入函数的实参个数 - 函数的length属性是只读的,表示形参个数(或期望传入的实参个数)
function fn(x,y){
return arguments.length;
}
fn.length //=> 2: fn函数有2个形参
fn(1,2,3,4) //=> 4: 实参个数为4个
8.7.2 prototype属性
每个函数包含prototype属性,指向“原型对象”的引用。当将函数用作构造函数时,新创建的对象会从原型对象上继承属性。
8.7.4 bind()方法
bind()
将函数绑定至某个对象。当在函数f()上调用bind()方法病传入一个对象o作为参数,这个方法将返回一个新的函数。
function f(y){
return this.x +y;
}
var o = {x:1};
var g = f.bind(o);
g(2) //=> 3
8.7.6 Function()构造函数
函数可以通过Function()构造函数来定义。
var f = new Function("x","y","return x*y;");
//等价于:
var f = function(x,y){ return x*y; }
注意:这种方式创建的函数并不是使用词法作用域,相反,函数体代码的编译总会在顶层函数执行。Function()狗仔函数在实际编程中很少用到。
var scope='global';
function constructFunction(){
var scope='local';
return new Function("return scope"); //无法捕获局部作用域
}
//所返回的函数使用的不是局部作用域
constructFunction()(); //=> "global": 通过Function()构造函数
8.8 函数式编程
8.8.2 高阶函数
高阶函数(higher-order function)就是操作函数的函数。接收一个或多个函数作为参数,并返回一个新函数。
下例中not()函数就是一个高阶函数:
function not(f){
return function(){
var result = f.apply(this,arguments);
return !result;
}
}
var even = function(x){
return x % 2 === 0;
}
var odd=not(even);
[1,1,3,5,5].every(odd); //=> true: 每个元素都是奇数